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

#include <rand.h>

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

/* time to wait before broadcasting the primary key (>PK1WAIT) */
#define IKBWAIT 180
/* time to wait before broadcasting the hidden key */
#define IKHWAIT 300
/* reduced lifetime if primary key is not cyphercast(tm) (in PK1WAIT units) */
#define PK1CUT 8
/* lifetime (in PK1WAIT units) */
#define PKLIFE 40
/* seconds to sleep between primary key broadcasts */
#define PKWAIT 120

/**********************/
/* for testing - wait only for receiver start */
#undef IKBWAIT
#define IKBWAIT 20
#undef IKHWAIT
#define IKHWAIT 90


#define RNSIZE 12
/*--------------------------------------------------*/
/* Generate new keypair */
int genkeys(char *pub, char *sec)
{
  DSA *dsakey = DSA_new();
  DH *dhkey = DH_new();
  unsigned long long keyid = 0;
  unsigned long long keyid2 = 0;
  FILE *pk, *sk;
  unsigned char randname[40];
  int i;
  unsigned char *pp = NULL;

  /* THE MOST COMMON PROBLEM */
  if (setkeyring5("./dh_params") == NULL ||
      getkey5(NULL, &dsakey, NULL, &keyid) ||
      getkey5(&dhkey, NULL, NULL, &keyid2))
    return -11;
  DSA_generate_key(dsakey);
  DH_generate_key(dhkey);
  RAND_bytes(randname, RNSIZE);
  randname[RNSIZE] = 0;
  for (i = 0; i < RNSIZE; i++)
    randname[i] &= 15, randname[i] += 'g';
  sk = fopen(sec, "wb");
  pk = fopen(pub, "wb");
  keyout5(sk, pk, dhkey, dsakey, pp, randname);
  fprintf(keyf, "%s\n", sec);
  fflush(keyf);
  return 0;
}

/*--------------------------------------------------*/
int quorumchk()
{
  FILE *fp;
  int nhosts = 0, ourkeys = 0;
  unsigned long long keyid = 0;
  time_t keytime;
  DH *dh_key = DH_new();
  unsigned char *pp = 0;

  if (NULL == (fp = fopen(RINGFILE, "r")))
    return 0;
  setkeyring5("./secring.skr");
  while (!feof(fp)) {
    if (2 > fscanf(fp, "%*x %qx %*s %ld\n", &keyid, &keytime))
      break;
    if (time(NULL) - keytime > STALETIME * 2)
      break;
    if (getkey5(&dh_key, NULL, pp, &keyid))
      ourkeys++;                /* our key has been published */
    nhosts++;
  }
  DH_free(dh_key);
  fclose(fp);
  return (ourkeys && nhosts >= QUORUM);
}

/*--------------------------------------------------*/
/* broadcast Keys */
void bcastpks()
{
  FILE *fp;
  int keyage = 0, hide = 0, leaveon = 3;
  char kfn[20] = "pubone.pkr";
  char skfn[20] = "SECKEY.one";
  char mfn[20] = "INBOX.-pkone";
  char tfn[20];
  unsigned char c;

  strcpy(av0, "ATHENS-KEYXMT       ");
  if (fork()) {                 /* setup for hidden key */
    hide = 2;
    strcpy(av0, "ATHENS-keyxmh       ");
    strcpy(kfn, "pubtwo.pkr");
    strcpy(skfn, "SECKEY.two");
    strcpy(mfn, "INBOX.-pktwo");
    while (!quorumchk())
      sleep(IKHWAIT);
    strcpy(av0, "ATHENS-KEYXMH       ");
    leaveon = 0;
  } else
    sleep(IKBWAIT);             /* wait for a potential quorum */

  for (;;) {
    if (NULL != (fp = fopen(kfn, "rb"))) {
      msg[0] = PTYPEPK;
      msglen = 1 + fread(&msg[1], 1, MAXUDP - 1, fp);
      fclose(fp);
      hide = (hide & ~1) | quorumchk();
      if (!(hide & 1))
        leaveon = 3;
      if (!leaveon && hide & 1) {  /* quorum - hide in a message */
        sprintf(tfn, "tmp.%d", getpid());
        fp = fopen(tfn, "wb");
        fwrite(msg, 1, msglen, fp);
        fclose(fp);
        rename(tfn, mfn);       /* prevent partial */
      } else if (!(hide & 2)) { /* broadcast key if public */
        keyage = PK1CUT;        /* keep until shortly after a quorum exists */
        if (msglen != sendto(sockfd, msg, msglen, 0, SADDR & remote_addr, cl))
          fprintf(stderr, "key broadcast sendto failed\n");
      }
      if (leaveon)
        leaveon--;
    } else if (!genkeys(kfn, skfn)) {
      keyage = PKLIFE;
      continue;
    } else
      fprintf(stderr, "KEYGEN failed\n");
    RAND_bytes(&c, 1);          /* randomize time */
    if (!leaveon)
      sleep((PKWAIT + c) / 2);
    else
      sleep(c / 8);
    if (!keyage--) {
      unlink(kfn);
      hide |= 1;
    }
  }
}
