#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <time.h>

#include "libpgp5.h"
#include "athens.h"

#include <rand.h>

void sendpad(unsigned char *);
int xorbuf(unsigned char *c, unsigned char *d);

/*--------------------------------------------------*/
void shuffle(int *tab, int n)
{
  int i, j, k;
  unsigned char shuf[32], min;

  RAND_bytes(shuf, n);
  for (i = 0; i < n; i++)
    while (shuf[i] == 0xff)
      RAND_bytes(&shuf[i], 1);
  /* sort the random buffer */
  k = 0;
  while (k < n) {
    /* find next minimum */
    min = 255;
    for (i = 0, j = -1; i < n; i++)
      if (shuf[i] < min)
        j = i, min = shuf[i];
    /* log minimum */
    tab[k++] = j;
    shuf[j] = 0xff;
  }
}

/*--------------------------------------------------*/
/* create new path */
#define MXHNAME 80

void newpad()
{
  FILE *fp;
  unsigned char *bp, *cp;
  int i, ll, j;
  unsigned long long keyid;

  int nhosts, ourhost;
  int shuftab[MAXRING];
  DH *dh_key = DH_new();
  unsigned char thishop[MXHNAME], nexthop[MXHNAME], thishost[MXHNAME];
  unsigned long long idtbl[MAXRING];
  char hosttbl[MAXRING][MXHNAME];
  int oplen[MAXRING];
  time_t keytime;
  unsigned char outpath[MAXRING][1280], *pp = NULL;
  unsigned char pn[8];
  unsigned char nexthost[MXHNAME];

  RAND_bytes(pn, 8);
  sprintf(chkfile, "%02X%02X%02X%02X", pn[0], pn[1], pn[2], pn[3]);
  for (;;) {
    /* read some keys table */
    fp = fopen(RINGFILE, "r");
    ourhost = -1;
    nhosts = 0;
    setkeyring5("./secring.skr");
    while (fp && !feof(fp) && nhosts < MAXRING) {
      /* MXHNAME - 1 */
      fscanf(fp, "%*x %qx %48s %ld\n", &keyid, hosttbl[nhosts], &keytime);
      if (time(NULL) - keytime > STALETIME)  /* end at stale keys */
        break;

      idtbl[nhosts] = keyid;
      if (ourhost == -1 && !getkey5(&dh_key, NULL, pp, &keyid)) {
        ourhost = nhosts;       /* grab the freshest of ours */
        strcpy(thishost, hosttbl[nhosts]);
      }                         /* keep going until we get our key */
      if (ourhost != -1 || nhosts < MAXRING - 1)
        nhosts++;
    }
    if (fp)
      fclose(fp);
    if (ourhost >= 0 && nhosts >= QUORUM)
      break;
    sleep(NEWPADRT);
  }
  if (nhosts > QUORUM + 1)      /* prevent saturation - don't wait for stale */
    nhosts -= 2;
  DH_free(dh_key);
  /* move our host to the bottom and shuffle the rest */
  nhosts--;
#if 0
/*************INCOMPLETE************** vary the size */
  if (nhosts > MINRING)
    nhosts = MINRING + (nhosts - MINRING);  /*  + 1) * random{[0..1)} */
#endif
  strcpy(hosttbl[ourhost], hosttbl[nhosts]);
  keyid = idtbl[ourhost];
  idtbl[ourhost] = idtbl[nhosts];
  ourhost = nhosts;
  idtbl[nhosts] = keyid;
  strcpy(hosttbl[nhosts], thishost);
  shuffle(shuftab, nhosts);     /* random one-hit-per-host walk, except */
  shuftab[nhosts] = nhosts;     /* last entry returns here */
  nhosts++;
  strcpy(nexthop, thishost);    /* top entry points back here */

  /* this has to occur after quorum test to open the filled file */
  memset(scribpad, 0, DSIZE);
  /* write key to padno file */
  fp = fopen(chkfile, "w");
  fclose(fp);
  setkeyring5("./pubring.pkr");
  for (i = 0; i < nhosts; i++) {
    if (i == nhosts - 1) {      /* Are we home yet? */
      strcpy(nexthost, nexthop);
      strcpy(thishop, chkfile);
    } else
      strcpy(thishop, nexthop);

    strcpy(nexthop, hosttbl[shuftab[i]]);  /* get next hop ready */
#ifdef DEBUG2
    fprintf(stderr, "%qX [%s->[%s]]\n", idtbl[shuftab[i]], nexthop, thishop);
#endif
    /* add length and checksum to string for encryption */
    for (ll = 0, j = 0; thishop[j]; j++)
      ll += thishop[j];
    thishop[j + 1] = ll >> 8;
    thishop[j + 2] = ll;

    memmove(&thishop[1], thishop, j + 4);
    thishop[0] = j;

    cp = &thishop[j + 4];
    *cp = CYPHER;
    RAND_bytes(&cp[1], 24);
    cfbinit(&cp[1], &cp[17], *cp);
    memset(auxpad, 0, DSIZE);
    docfb(auxpad, DSIZE, 1);
    xorbuf(scribpad, auxpad);
    j += 25;

    oplen[i] = pkeenc5(idtbl[shuftab[i]], outpath[i], thishop, j + 4, 0x10);
  }
  shuffle(shuftab, nhosts);     /* shuffle the path heads and write them out */
  /* compress to nhosts:1/{len:2,outpath:len} */
  bp = pathbuf;
  *bp++ = nhosts;
  for (i = 0; i < nhosts; i++) {
    *bp++ = oplen[shuftab[i]] >> 8;
    *bp++ = oplen[shuftab[i]];
    memcpy(bp, outpath[shuftab[i]], oplen[shuftab[i]]);
    bp += oplen[shuftab[i]];
  }
  pblen = bp - pathbuf;

  if (!fork()) {                /* pad monitor */
    int padtmr;
    struct stat s;

    strcpy(av0, "ATHENS-PADMON       ");
    padtmr = PADTMO;
    for (;;) {
      sleep(PADCHECK);
      if (stat(chkfile, &s))
        exit(0);                /* pad returned */
      if ((padtmr -= PADCHECK) <= 0)
        break;
    }
    unlink(chkfile);            /* delete (or rename) old */
    fprintf(stderr, "Pad Timeout: %s\n", chkfile);
    for (i = 0; i < nhosts; i++)
      fprintf(stderr, "   %qX\n", idtbl[i]);

    strcpy(av0, "ATHENS-NEWPAD       ");
    newpad();                   /* recurse, really should be goto top */
  }
  sendpad(nexthost);
}
