#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/stat.h>

#include <rand.h>
#include <sha.h>

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

/* Number of retrys, time between for missing public key */
#define PKRETRYC 10
#define PKRETRYT 10

/*--------------------------------------------------*/
int xorbuf(unsigned char *c, unsigned char *d)
{
  int i = DSIZE;
  unsigned char j = 0;

  while (i--)
    j |= (*c++ ^= *d++);
  return j;
}

/*--------------------------------------------------*/
/* do hash */
int dohash(unsigned char *out, unsigned char *in, unsigned int len,
           unsigned char *comp)
{
  SHA_CTX hash;

  SHA1_Init(&hash);
  SHA1_Update(&hash, in, len);
  SHA1_Final(out, &hash);
  return (comp != NULL ? memcmp(comp, out, 20) : 0);
}

/*--------------------------------------------------*/
/* hash to filename */

char hntab[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@_";

void htofn(unsigned char *ap, int len, unsigned char *hnp)
{
  int i;
  unsigned char hashres[21], *hrb = hashres;

  dohash(hrb, ap, len, NULL);
  hrb[20] = 0;                  /* was 20 */
  for (i = 0; i < 9; i += 3, hrb += 3) {
    *hnp++ = hntab[*hrb >> 2];
    *hnp++ = hntab[((hrb[0] << 4) & 0x30) | ((hrb[1] >> 4) & 0xf)];
    *hnp++ = hntab[((hrb[1] << 2) & 0x3c) | ((hrb[2] >> 6) & 0x3)];
    *hnp++ = hntab[hrb[2] & 0x3f];
  }
  *hnp++ = 0;
}

/*--------------------------------------------------*/
/* try to insert message into pad */
void addmsg()
{
  int len;
  DIR *dfile;
  struct dirent *dent;
  FILE *fp;
  unsigned char tmpname[72], tmpname2[72];
  struct stat s;
  time_t ift;

  if ((dfile = opendir(".")) == NULL)
    exit(-4);
  tmpname[0] = 0;
  ift = time(NULL) + 30;
  len = 0;
  while (NULL != (dent = readdir(dfile))) {
    if (strncmp(dent->d_name, "INBOX.", 6))
      continue;
    len++;
    stat(dent->d_name, &s);
    /* pick out oldest */
    if (s.st_ctime < ift) {
      strcpy(tmpname, dent->d_name);
      ift = s.st_ctime;
    }
  }
  closedir(dfile);
  if (!tmpname[0])              /* no files */
    return;

  /* (#inbox*age)/256 chance of queueing, should be 1/pathlen */
  RAND_bytes(tmpname2, 1);
  if (tmpname2[0] > len * 32 + (time(NULL) - ift))
    return;

  sprintf(tmpname2, "msgq.%d", getpid());
  if (rename(tmpname, tmpname2))
    return;                     /* someone else got there first */

  fp = fopen(tmpname2, "rb");
  len = fread(&auxpad[2], 1, DSIZE - 24, fp);
  fclose(fp);

  auxpad[0] = len >> 8;
  auxpad[1] = len;
  RAND_bytes(&auxpad[2 + len], DSIZE - (2 + len));

  dohash(&auxpad[2 + len], &auxpad[2], len, NULL);
  htofn(&auxpad[2], len, chkfile);

  if (tmpname[6] != '-' && !fork()) {  /* message requeue monitor */
    int padtmr;

    strcpy(av0, "ATHENS-MSGMON       ");
    padtmr = PADTMO * 2;
    for (;;) {
      sleep(PADCHECK);
      if (stat(chkfile, &s))
        exit(0);                /* pad returned */
      if ((padtmr -= PADCHECK) <= 0)
        break;
    }
    sprintf(tmpname2, "INBOX.%d", getpid());
#ifdef DEBUG2
    fprintf(stderr, "Msg %s Requeued\n", chkfile);
#endif
    rename(chkfile, tmpname2);
    exit(0);
  }
  xorbuf(scribpad, auxpad);
  rename(tmpname2, chkfile);
}

/*--------------------------------------------------*/
void sendpad(unsigned char *fwdhost)
{
  unsigned char outckey[40];
  unsigned char nexthtmp[80];
  FILE *fp;
  unsigned char *bp;
  int len, n;
  unsigned long long keyid;
  time_t keytime;

#ifdef DEBUG2
  fprintf(stderr, "Forward To: %s\n", fwdhost);
#endif
  /* possibly insert message */
  addmsg();
  /* create a random conventional key */
  outckey[0] = CPKE;
  outckey[1] = CYPHER;
  RAND_bytes(&outckey[2], 24);
  cfbinit(&outckey[2], &outckey[18], outckey[1]);
  for (len = 0, n = 0; len < 26; len++)
    n += outckey[len];
  outckey[26] = n & 0xff;

  /* lookup key for next host */
  len = PKRETRYC;               /* timeout - the key may not be on the ring */
  while (len--) {
    fp = fopen(RINGFILE, "r");
    while (!feof(fp)) {
      fscanf(fp, "%*x %qx %s %ld\n", &keyid, nexthtmp, &keytime);
      if (time(NULL) - keytime > STALETIME * 2)
        break;
      if (strstr(nexthtmp, fwdhost))
        break;
      keyid = 0;
    }
    fclose(fp);

    if (keyid)
      break;
    if (len == PKRETRYC - 1)
      fprintf(stderr, "Missing or Stale PK for %s\n", fwdhost);
    if (!len)
      exit(-5);
    sleep(PKRETRYT);
  }
  /* encrypt conventional key */
  setkeyring5("./pubring.pkr");
  len = pkeenc5(keyid, auxpad, outckey, 27, 0x10);

  bp = msg;
  *bp++ = CPKE;
  *bp++ = len / 256;
  *bp++ = len & 255;
  memcpy(bp, auxpad, len);
  bp += len;
  memcpy(bp, scribpad, DSIZE);
  bp += DSIZE;
  memcpy(bp, pathbuf, pblen);
  bp -= DSIZE;
  docfb(bp, DSIZE + pblen, 1);

/******** pad message to random size with random bytes? */

  msglen = len + DSIZE + pblen + 3;
  if (msglen !=
      (n = sendto(sockfd, msg, msglen, 0, SADDR & remote_addr, cl))) {
    perror("pad broadcast sendto");
    fprintf(stderr, "%d bytes to %s returned %d\n",
            msglen, inet_ntoa(remote_addr.sin_addr), n);
  }
  exit(0);
}
