#include <malloc.h>

#include "libpgp5.h"

#ifndef NO_IDEA
#include <idea.h>
#endif
#ifndef NO_DES
#include <des.h>
#endif
#ifndef NO_CAST
#include <cast.h>
#endif
#ifndef NO_BLOWFISH
#include <blowfish.h>
#endif
#ifndef NO_SAFER
#include "safer.h"
#endif

/*------------*/

union cipkey {
#ifndef NO_IDEA
  IDEA_KEY_SCHEDULE iks;
#endif
#ifndef NO_DES
  des_key_schedule desv[3];
#endif
#ifndef NO_CAST
  CAST_KEY castkey;
#endif
#ifndef NO_BLOWFISH
  BF_KEY bfkey;
#endif
#ifndef NO_ROTN
  unsigned char rotnk;
#endif
#ifndef NO_SAFER
  safer_key_t saferkey;
#endif
};

struct cipctx {
/* cipher state context */
  unsigned int cipnum;
  int edec;
  unsigned int ivleft;
  unsigned char cfbivec[8];
  union cipkey ck;
} CCTX;

int glbcipsiz[] =
{0, 16, 24, 16, 16, 1, 16};
int loccipsiz[] =
{-1, -1};
int cnvkeysiz(const int cipher)
{
  if (cipher < 7)
    return glbcipsiz[cipher];
  if (cipher > 99 && cipher < 102)
    return loccipsiz[cipher - 100];
  return -1;
}

void *cfbinit(unsigned char *key, const unsigned char *iv0, int cipno, int enc)
{
  struct cipctx *CCTX;
  if (cipno < 0)                /* already inited? */
    return NULL;

  CCTX = malloc(sizeof(struct cipctx));
  CCTX->cipnum = cipno;
  CCTX->ivleft = 0;
  memcpy(CCTX->cfbivec, iv0, 8);
  CCTX->edec = enc;
  switch (cipno) {
#ifndef NO_IDEA
  case 1:
    idea_set_encrypt_key(key, &CCTX->ck.iks);
    CCTX->edec = enc ? IDEA_ENCRYPT : IDEA_DECRYPT;
    break;
#endif
#ifndef NO_DES
  case 2:
    des_set_key((des_cblock *) key, CCTX->ck.desv[0]);
    des_set_key((des_cblock *) & key[8], CCTX->ck.desv[1]);
    des_set_key((des_cblock *) & key[16], CCTX->ck.desv[2]);
    CCTX->edec = enc ? DES_ENCRYPT : DES_DECRYPT;
#endif
  case 0:
    break;
#ifndef NO_CAST
  case 3:
    CAST_set_key(&CCTX->ck.castkey, CAST_KEY_LENGTH, key);
    CCTX->edec = enc ? CAST_ENCRYPT : CAST_DECRYPT;
    break;
#endif
#ifndef NO_BLOWFISH
  case 4:
    BF_set_key(&CCTX->ck.bfkey, 16, key);
    CCTX->edec = enc ? BF_ENCRYPT : BF_DECRYPT;
    break;
#endif
#ifndef NO_ROTN
  case 5:
    CCTX->ck.rotnk = key[0];
    break;
#endif
#ifndef NO_SAFER
  case 6:
    Safer_Init_Module();
    Safer_Expand_Userkey(key, &key[8],
                         SAFER_MAX_NOF_ROUNDS, 1, CCTX->ck.saferkey);
    break;
#endif
  default:
    exit(-1);
  }
  return CCTX;
}

static void cfbomatic(unsigned char *buf, int len, struct cipctx *CCTX)
{
  int i, ilen;
  unsigned char liv;

  while (len) {
    ilen = len > 8 ? 8 : len;
    if (CCTX->ivleft == 0)
      switch (CCTX->cipnum) {
#ifndef NO_SAFER
      case 6:
        Safer_Encrypt_Block(CCTX->cfbivec, CCTX->ck.saferkey, CCTX->cfbivec);
        break;
#endif
      default:
        break;
    } else if (ilen + CCTX->ivleft > 8)
      ilen = 8 - CCTX->ivleft;
    for (i = CCTX->ivleft; i < ilen + CCTX->ivleft; i++) {
      liv = *buf;
      *buf++ ^= CCTX->cfbivec[i];
      if (CCTX->edec)
        liv ^= CCTX->cfbivec[i];
      CCTX->cfbivec[i] = liv;
    }
    len -= ilen;
    CCTX->ivleft = (CCTX->ivleft + ilen) & 7;
  }
}

void docfb(unsigned char *buf, int len, void *cctx)
{
  struct cipctx *CCTX = cctx;

  switch (CCTX->cipnum) {
#ifndef NO_IDEA
  case 1:
    idea_cfb64_encrypt(buf, buf, len, &CCTX->ck.iks,
                       CCTX->cfbivec, &CCTX->ivleft, CCTX->edec);
    break;
#endif
#ifndef NO_DES
  case 2:
    des_ede3_cfb64_encrypt(buf, buf, len, CCTX->ck.desv[0], CCTX->ck.desv[1],
                           CCTX->ck.desv[2],
                     (des_cblock *) CCTX->cfbivec, &CCTX->ivleft, CCTX->edec);
#endif
  case 0:
    break;
#ifndef NO_CAST
  case 3:
    CAST_cfb64_encrypt(buf, buf, len, &CCTX->ck.castkey,
                       CCTX->cfbivec, &CCTX->ivleft, CCTX->edec);
    break;
#endif
#ifndef NO_BLOWFISH
  case 4:
    BF_cfb64_encrypt(buf, buf, len, &CCTX->ck.bfkey,
                     CCTX->cfbivec, &CCTX->ivleft, CCTX->edec);
    break;
#endif
#ifndef NO_ROTN
  case 5:
    while (len--)
      *buf = CCTX->edec ? *buf + CCTX->ck.rotnk : *buf - CCTX->ck.rotnk, buf++;
    break;
#endif
#ifndef NO_SAFER
  case 6:
#endif
  default:
    cfbomatic(buf, len, CCTX);
  }
}

void cfbreset(const unsigned char *save, void *cctx)
{
  struct cipctx *CCTX = cctx;
  memcpy((struct cipctx *) CCTX->cfbivec, save, 8);
  (struct cipctx *) CCTX->ivleft = 0;
}
