/*
** Implementation of an RC4 codec for TCL.
*/
const char rc4_c_version[] = "$Header: /readi/code/tobe/rc4.c,v 1.6 2007/05/08 21:53:56 drh Exp $";
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <tcl.h>

char *getPwdKey(char *keybuf);

//#include <sqlite3.h>

/*
** An RC4 codec is an instance of the following structure.
*/
typedef struct Rc4Codec Rc4Codec;
struct Rc4Codec {
  unsigned char i, j;
  unsigned char s[256];
};
static Tcl_WideInt next_random_number = 1;

/*
** Initialize an RC4 codec with the given key sequence.
*/
static void rc4_init(Rc4Codec *p, int nByte, unsigned char *pKey){
  int k, l;
  unsigned char i, j, t, *s;
  i = j = p->i = p->j = 0;
  s = p->s;
  for(k=0; k<256; k++){
    s[k] = k;
  }
  l = 0;
  for(k=0; k<256; k++){
    t = s[k];
    j += t + pKey[l++];
    if( l>=nByte ) l = 0;
    s[k] = s[j];
    s[j] = t;
  }

  /* Discard the first 1024 bytes of key stream to thwart the
  ** Fluhrer-Mantin-Shamir attack.
  */
  for(k=0; k<1024; k++){
    t = s[++i];
    j += t;
    s[i] = s[j];
    s[j] = t;
  }
  p->j = j;
}

/*
** Encode/Decode nBytes bytes of traffic using the given codec.
*/
static void rc4_coder(Rc4Codec *p, int nByte, unsigned char *pData){
  register unsigned char ti, tj, i, j, *s;
  s = p->s;
  i = p->i;
  j = p->j;
  while( nByte-->0 ){
    ti = s[++i];
    j += ti;
    tj = s[i] = s[j];
    s[j] = ti;
    tj += ti;
    *(pData++) ^= s[tj];
  }
  p->i = i;
  p->j = j;
}

/*
** Usage:    NAME TEXT
**
** There is a separate TCL command created for each rc4 codec instance.
** This is the implementation of that command.  Apply the codec to the
** input and return the results.
*/
static int CodecObjCmd(
  void *pCodec,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  unsigned char *data;
  int nData;
  Tcl_Obj *pResult;
  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 2, objv, "TEXT");
    return TCL_ERROR;
  }
  data = Tcl_GetByteArrayFromObj(objv[1], &nData);
  pResult = Tcl_NewByteArrayObj(data, nData);
  data = Tcl_GetByteArrayFromObj(pResult, 0);
  rc4_coder((Rc4Codec*)pCodec, nData, data);
  Tcl_SetObjResult(interp, pResult);
  return TCL_OK;
}

/*
** Destructor for codec.
*/
static void CodecDestructor(void *pCodec){
  Tcl_Free(pCodec);
}

/*
** Usage:   rc4  NAME  PASSWORD
**
** Create a new rc4 codec called NAME and initialized using PASSWORD.
*/
static int Rc4ObjCmd(
  void *NotUsed,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  Rc4Codec *pCodec;
  const char *zName;
  unsigned char *pKey;
  int nKey;

  if( objc!=3 ){
    Tcl_WrongNumArgs(interp, 2, objv, "NAME PASSWORD");
    return TCL_ERROR;
  }
  zName = Tcl_GetStringFromObj(objv[1], 0);
  pKey = Tcl_GetByteArrayFromObj(objv[2], &nKey);
  pCodec = (Rc4Codec*)Tcl_Alloc( sizeof(*pCodec) );
  rc4_init(pCodec, nKey, pKey);
  Tcl_CreateObjCommand(interp, zName, CodecObjCmd, pCodec, CodecDestructor);
  return TCL_OK;
}

/*
** The characters used for HTTP base64 encoding.
*/
static const unsigned char zBase[] = 
  "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~";

/*
** Encode a string using a base-64 encoding.
** The encoding can be reversed using the <b>decode64</b> function.
**
** Space to hold the result comes from Tcl_Alloc().
*/
static char *encode64(const char *zData, int nData, int *pnOut){
  char *z64;
  int i, n;

  if( nData<=0 ){
    nData = strlen(zData);
  }
  z64 = Tcl_Alloc( (nData*4)/3 + 6 );
  for(i=n=0; i+2<nData; i+=3){
    z64[n++] = zBase[ (zData[i]>>2) & 0x3f ];
    z64[n++] = zBase[ ((zData[i]<<4) & 0x30) | ((zData[i+1]>>4) & 0x0f) ];
    z64[n++] = zBase[ ((zData[i+1]<<2) & 0x3c) | ((zData[i+2]>>6) & 0x03) ];
    z64[n++] = zBase[ zData[i+2] & 0x3f ];
  }
  if( i+1<nData ){
    z64[n++] = zBase[ (zData[i]>>2) & 0x3f ];
    z64[n++] = zBase[ ((zData[i]<<4) & 0x30) | ((zData[i+1]>>4) & 0x0f) ];
    z64[n++] = zBase[ ((zData[i+1]<<2) & 0x3c) ];
  }else if( i<nData ){
    z64[n++] = zBase[ (zData[i]>>2) & 0x3f ];
    z64[n++] = zBase[ ((zData[i]<<4) & 0x30) ];
  }
  z64[n] = 0;
  if( pnOut ) *pnOut = n;
  return z64;
}

/*
** This function treats its input as a base-64 string and returns the
** decoded value of that string.  Characters of input that are not
** valid base-64 characters (such as spaces and newlines) are ignored.
**
** Space to hold the decoded string is obtained from Tcl_Alloc().
*/
char *decode64(const char *z64, int n64, int *pnOut){
  char *zData;
  int i, j;
  int a, b, c, d;
  static int isInit = 0;
  static int trans[128];

  if( !isInit ){
    for(i=0; i<128; i++){ trans[i] = 0; }
    for(i=0; zBase[i]; i++){ trans[zBase[i] & 0x7f] = i; }
    isInit = 1;
  }
  if( n64<0 ){
    n64 = strlen(z64);
  }
  while( n64>0 && z64[n64-1]=='=' ) n64--;
  zData = Tcl_Alloc( (n64*3)/4 + 4 );
  for(i=j=0; i+3<n64; i+=4){
    a = trans[z64[i] & 0x7f];
    b = trans[z64[i+1] & 0x7f];
    c = trans[z64[i+2] & 0x7f];
    d = trans[z64[i+3] & 0x7f];
    zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03);
    zData[j++] = ((b<<4) & 0xf0) | ((c>>2) & 0x0f);
    zData[j++] = ((c<<6) & 0xc0) | (d & 0x3f);
  }
  if( i+2<n64 ){
    a = trans[z64[i] & 0x7f];
    b = trans[z64[i+1] & 0x7f];
    c = trans[z64[i+2] & 0x7f];
    zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03);
    zData[j++] = ((b<<4) & 0xf0) | ((c>>2) & 0x0f);
  }else if( i+1<n64 ){
    a = trans[z64[i] & 0x7f];
    b = trans[z64[i+1] & 0x7f];
    zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03);
  }
  zData[j] = 0;
  if( pnOut ) *pnOut = j;
  return zData;
}

static unsigned char randomByte(void) {
  char i;
  /* RAND_MAX assumed to be 256 */
  char repeat=(next_random_number % 10)+2;
  for(i=0;i<repeat;i++) {
    next_random_number = next_random_number * 1103515245 + 12345;
  }
  return ((unsigned)(next_random_number/256) % 256);
}

static void rc4_randomness(int N, void *pBuf) {
  unsigned char *zBuf = pBuf;
  while( N-- ){
    *(zBuf++) = randomByte();
  }
}

static int Rc4SeedObjCmd(
  void *NotUsed,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 2, objv, "SEEDINT");
    return TCL_ERROR;
  }
  if(Tcl_GetWideIntFromObj(interp,objv[1],&next_random_number)) {
    return TCL_ERROR;
  }
  return TCL_OK;
}

/*
** Usage:   rc4encrypt  PASSWORD   TEXT
**
** Encrypt TEXT using PASSWORD and a random nonce.  Encode the result
** as a single token using base64.
*/
static int Rc4EncryptObjCmd(
  void *NotUsed,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  const char *zPasswd;
  int nPasswd;
  char *zIn;
  int nIn;
  char *zBuf;
  char *zOut;
  int nOut;
  char zKey[256];
  Rc4Codec codec;
  extern void sqliteRandomness(int,void*);

  if( objc!=3 ){
    Tcl_WrongNumArgs(interp, 2, objv, "PASSWORD TEXT");
    return TCL_ERROR;
  }
  zPasswd = Tcl_GetStringFromObj(objv[1], &nPasswd);

  rc4_randomness(4, zKey);
  if( nPasswd>252 ) nPasswd = 252;
  memcpy(&zKey[4], zPasswd, nPasswd);
  rc4_init(&codec, nPasswd+4, (unsigned char*)zKey);
  zIn = Tcl_GetStringFromObj(objv[2], &nIn);
  zBuf = Tcl_Alloc( nIn + 5 );
  memcpy(zBuf, zKey, 4);
  memcpy(&zBuf[4], zIn, nIn);
  rc4_coder(&codec, nIn, (unsigned char*)&zBuf[4]);
  zOut = encode64(zBuf, nIn+4, &nOut);
  Tcl_SetObjResult(interp, Tcl_NewStringObj(zOut, nOut));
  Tcl_Free((char *)zOut);
  Tcl_Free((char *)zBuf);
  return TCL_OK;
}

/*
** Usage:   rc4decrypt  PASSWORD   CYPHERTEXT
**
** Decrypt CYPHERTEXT using PASSWORD and a nonce found at the beginning of
** the cyphertext.  The cyphertext is base64 encoded.
*/
static int Rc4DecryptObjCmd(
  void *NotUsed,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  const char *zPasswd;
  int nPasswd;
  char *zIn;
  int nIn;
  char *zOut;
  int nOut;
  char zKey[256];
  Rc4Codec codec;

  if( objc!=3 ){
    Tcl_WrongNumArgs(interp, 2, objv, "PASSWORD TEXT");
    return TCL_ERROR;
  }
  zPasswd = Tcl_GetStringFromObj(objv[1], &nPasswd);
  zIn = Tcl_GetStringFromObj(objv[2], &nIn);
  zOut = decode64(zIn, nIn, &nOut);
  if( nOut<4 ){
    return TCL_OK;
  }
  memcpy(zKey, zOut, 4);
  if( nPasswd>252 ) nPasswd = 252;
  memcpy(&zKey[4], zPasswd, nPasswd);
  rc4_init(&codec, nPasswd+4, (unsigned char*)zKey);
  rc4_coder(&codec, nOut-4, (unsigned char*)&zOut[4]);
  Tcl_SetObjResult(interp, Tcl_NewStringObj(&zOut[4], nOut-4));
  Tcl_Free(zOut);
  return TCL_OK;
}


/*
** Usage:   source_encrypt TEXT
**
** Encrypt TEXT using compiled in PASSWORD and a random nonce.  Encode the result
** as a single token using base64.
*/
static int Rc4EncryptSourceObjCmd(
  void *NotUsed,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  char zPasswd[32];
  int nPasswd;
  char *zIn;
  int nIn;
  char *zBuf;
  char *zOut;
  int nOut;
  char zKey[256];
  Rc4Codec codec;
  extern void sqliteRandomness(int,void*);

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 2, objv, "TEXT");
    return TCL_ERROR;
  }
  getPwdKey(zPasswd);
  nPasswd=strlen(zPasswd);
  
  rc4_randomness(4, zKey);
  if( nPasswd>252 ) nPasswd = 252;
  memcpy(&zKey[4], zPasswd, nPasswd);
  rc4_init(&codec, nPasswd+4, (unsigned char*)zKey);
  zIn = Tcl_GetStringFromObj(objv[1], &nIn);
  zBuf = Tcl_Alloc( nIn + 5 );
  memcpy(zBuf, zKey, 4);
  memcpy(&zBuf[4], zIn, nIn);
  rc4_coder(&codec, nIn, (unsigned char*)&zBuf[4]);
  zOut = encode64(zBuf, nIn+4, &nOut);
  Tcl_SetObjResult(interp, Tcl_NewStringObj(zOut, nOut));
  Tcl_Free((char *)zOut);
  Tcl_Free((char *)zBuf);
  return TCL_OK;
}

/*
** Usage:   source_decrypt   CYPHERTEXT
**
** Decrypt CYPHERTEXT using compiled in PASSWORD and a nonce
** found at the beginning of
** the cyphertext.  The cyphertext is base64 encoded.
*/
static int Rc4DecryptSourceObjCmd(
  void *NotUsed,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  char zPasswd[32];
  int nPasswd;
  char *zIn;
  int nIn;
  char *zOut;
  int nOut;
  char zKey[256];
  Rc4Codec codec;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 2, objv, "TEXT");
    return TCL_ERROR;
  }
  getPwdKey(zPasswd);
  nPasswd=strlen(zPasswd);

  zIn = Tcl_GetStringFromObj(objv[1], &nIn);
  zOut = decode64(zIn, nIn, &nOut);
  if( nOut<4 ){
    return TCL_OK;
  }
  memcpy(zKey, zOut, 4);
  if( nPasswd>252 ) nPasswd = 252;
  memcpy(&zKey[4], zPasswd, nPasswd);
  rc4_init(&codec, nPasswd+4, (unsigned char*)zKey);
  rc4_coder(&codec, nOut-4, (unsigned char*)&zOut[4]);
  Tcl_SetObjResult(interp, Tcl_NewStringObj(&zOut[4], nOut-4));
  Tcl_Free(zOut);
  return TCL_OK;
}

/*
** Usage:   eval_decrypt   CYPHERTEXT
**
** Decrypt CYPHERTEXT using compiled in PASSWORD and a nonce
** found at the beginning of
** the cyphertext.  The cyphertext is base64 encoded.
*/
static int Rc4DecryptEvalObjCmd(
  void *NotUsed,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  char zPasswd[32];
  int nPasswd;
  char *zIn;
  int nIn;
  char *zOut;
  int nOut;
  char zKey[256];
  Rc4Codec codec;
  Tcl_Obj *cleartext;
  int code=TCL_OK;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 2, objv, "TEXT");
    return TCL_ERROR;
  }
  getPwdKey(zPasswd);
  nPasswd=strlen(zPasswd);

  zIn = Tcl_GetStringFromObj(objv[1], &nIn);
  zOut = decode64(zIn, nIn, &nOut);
  if( nOut<4 ){
    return TCL_OK;
  }
  memcpy(zKey, zOut, 4);
  if( nPasswd>252 ) nPasswd = 252;
  memcpy(&zKey[4], zPasswd, nPasswd);
  rc4_init(&codec, nPasswd+4, (unsigned char*)zKey);
  rc4_coder(&codec, nOut-4, (unsigned char*)&zOut[4]);
  cleartext=Tcl_NewStringObj(&zOut[4], nOut-4);
  Tcl_IncrRefCount(cleartext);
  code=Tcl_EvalObjEx(interp,cleartext,NULL);
  Tcl_DecrRefCount(cleartext);
  Tcl_Free(zOut);
  return code;
}

/*
** Initialize the rc4 codec subsystem.
*/
DLLEXPORT int Rc4_Init(Tcl_Interp *interp){
  Tcl_CreateObjCommand(interp, "rc4", Rc4ObjCmd, 0, 0);
  Tcl_CreateObjCommand(interp, "rc4seed", Rc4SeedObjCmd, 0, 0);
  Tcl_CreateObjCommand(interp, "rc4encrypt", Rc4EncryptObjCmd, 0, 0);
  Tcl_CreateObjCommand(interp, "rc4decrypt", Rc4DecryptObjCmd, 0, 0);
  Tcl_CreateObjCommand(interp, "source_encrypt", Rc4EncryptSourceObjCmd, 0, 0);
  //Tcl_CreateObjCommand(interp, "source_decrypt", Rc4DecryptSourceObjCmd, 0, 0);
  Tcl_CreateObjCommand(interp, "eval_decrypt", Rc4DecryptEvalObjCmd, 0, 0);
  return TCL_OK;
}
