/*
   PKCIPE - public key based configuration tool for CIPE

   packet.c - packet handling and signing

   Copyright 2000 Olaf Titz <olaf@bigred.inka.de>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version
   2 of the License, or (at your option) any later version.
*/
/* $Id: packet.c,v 1.10 2000/12/05 19:21:11 olaf Exp $ */

#include <alloca.h>
#include <unistd.h>
#include <sys/uio.h>
#include <openssl/evp.h>
#include "pkcipe.h"

int timeout=60;

/* Data is sent in packets. The packet format is as follows:
   1 byte marker  0x2a
   2 byte length  bigendian, each byte is XORed with 0x50
   n byte data    starting with type                + signed/encrypted
   1 byte check   sum of plaintext data mod 256     +
*/

static EVP_MD_CTX signCtx;
static EVP_MD_CTX vrfyCtx;
static EVP_CIPHER_CTX	encSendCtx;
static EVP_CIPHER_CTX	encRecvCtx;
static int encSend=0;
static int encRecv=0;

void signInit(void)
{
    EVP_SignInit(&signCtx, EVP_sha1());
}

void vrfyInit(void)
{
    EVP_VerifyInit(&vrfyCtx, EVP_sha1());
}

void setSendKey(const unsigned char *key)
{
    if (key) {
	EVP_EncryptInit(&encSendCtx, EVP_rc4(),
			/*XXOCONST*/(unsigned char *)key, NULL);
        if (protoVersion>1)
            EVP_CIPHER_CTX_set_key_length(&encSendCtx, 20);
	encSend=1;
    } else {
	EVP_CIPHER_CTX_cleanup(&encSendCtx);
	encSend=0;
    }
    debug((DEB_KEY, "setSendKey %s", encSend ?
           hexstr(key, EVP_CIPHER_key_length(&encSendCtx)) : "*"));
}

void setRecvKey(const unsigned char *key)
{
    if (key) {
	EVP_DecryptInit(&encRecvCtx, EVP_rc4(),
			/*XXOCONST*/(unsigned char *)key, NULL);
        if (protoVersion>1)
            EVP_CIPHER_CTX_set_key_length(&encRecvCtx, 20);
	encRecv=1;
    } else {
	EVP_CIPHER_CTX_cleanup(&encRecvCtx);
	encRecv=0;
    }
    debug((DEB_KEY, "setRecvKey %s", encRecv ?
           hexstr(key, EVP_CIPHER_key_length(&encRecvCtx)) : "*"));
}

int packetSend(int fd, unsigned char *d, int len)
{
    int i, ll;
    unsigned char hb[3];
    unsigned char c;
    struct iovec iov[2];
    unsigned char *b0;

    if (len<0 || len>PKTMAXLEN-2)
	return -1;
    hb[0]='*';
    hb[1]=(len>>8)^0x50;
    hb[2]=(len&255)^0x50;
    for (c=i=0; i<len; ++i)
	c+=d[i];
    d[len++]=c;

    debug((DEB_PKT,
	   "packetSend: %02x %02x %02x (%d)",
	   hb[0], hb[1], hb[2], len));
    if (*d&PKTF_SIGNED) {
	EVP_SignUpdate(&signCtx, d, len);
	debug((DEB_SIGN, "SignUpdate %d", len));
    }
    if (*d&PKTF_REVSIGN) {
	EVP_VerifyUpdate(&vrfyCtx, d, len);
	debug((DEB_SIGN, "VerifyUpdate rev %d", len));
    }
#ifdef DEBUG
    if (debugging&DEB_PDUMP)
	hexdump(d, len);
#endif
    iov[0].iov_base=hb;
    iov[0].iov_len=3;
    if (encSend) {
	b0=alloca(len);
	if (!b0)
	    return -1;
	ll=len;
	EVP_EncryptUpdate(&encSendCtx, b0, &ll,
			  /*XXOCONST*/(unsigned char *)d, len);
	if (ll!=len) {
	    Log(LOG_ERR, "packetSend: EncryptUpdate %d/%d", ll, len);
	    return -1;
	}
	iov[1].iov_base=b0;
    } else {
	iov[1].iov_base=d;
    }
    iov[1].iov_len=len;
    if ((i=xwritev(fd, iov, 2))<0) {
	Log(LOG_ERR, "packetSend: writev: %m");
        return -1;
    }
    d[len-1]=0; /* make the buffer re-usable as string */
    return i;
}

int packetSendBN(int fd, int typ, const BIGNUM *a)
{
    unsigned short l;
    unsigned char *b;

    l=BN_num_bytes(a);
    if (!(b=alloca(l+2)))
	return -1;
    b[0]=typ;
    BN_bn2bin(a, b+1);
    debug((DEB_BNUM, "packetSendBN: %d %s", l, hexstr(a->d, l)));
    return packetSend(fd, b, l+1);
}

int packetRecv(int fd, unsigned char *d, int len)
{
    int e, i, ll;
    unsigned char hb[3];

    /*memset(d, 0, len);*/
    alarm(timeout);
    if ((e=xread(fd, hb, 3))<0) {
        Log(LOG_ERR, "packetRecv: read error");
	return -1;
    }
    if (!e)
	return 0;
    ll=((hb[1]<<8)+(hb[2]))^0x5050;
    debug((DEB_PKT,
	   "packetRecv: %02x %02x %02x (%d)",
	   hb[0], hb[1], hb[2], ll));
    if (ll<1 || ll>PKTMAXLEN-2 || ll>len-2 || hb[0]!='*') {
        Log(LOG_ERR, "packetRecv: frame error");
	if (debugging&DEB_PKTERR) {
	    memcpy(d, hb, 3);
	    e=read(fd, d+3, len-3); /* not xread! */
	    Log(LOG_DEBUG, "packetRecv: frame error, received stuff follows");
	    hexdump(d, e+3);
	}
	return -1;
    }

    ++ll;
    if (encRecv) {
	unsigned char *b0=alloca(ll);
	if (!b0) {
            Log(LOG_ERR, "packetRecv: alloca failure");
	    return -1;
        }
	if (xread(fd, b0, ll)<ll) {
            Log(LOG_ERR, "packetRecv: read error");
	    return -1;
        }
	e=ll;
	EVP_DecryptUpdate(&encRecvCtx, d, &e, b0, ll);
	if (e!=ll) {
	    Log(LOG_ERR, "packetRecv: DecryptUpdate %d/%d", e, ll);
	    return -1;
	}
    } else {
	if (xread(fd, d, ll)<ll) {
            Log(LOG_ERR, "packetRecv: read error");
	    return -1;
        }
    }

    if (*d&PKTF_SIGNED) {
	EVP_VerifyUpdate(&vrfyCtx, d, ll);
	debug((DEB_SIGN, "VerifyUpdate %d", ll));
    }
    if (*d&PKTF_REVSIGN) {
	EVP_SignUpdate(&signCtx, d, ll);
	debug((DEB_SIGN, "SignUpdate rev %d", ll));
    }
#ifdef DEBUG
    if (debugging&DEB_PDUMP)
	hexdump(d, ll);
#endif
    --ll;
    for (e=i=0; i<ll; ++i)
	e+=d[i];
    if ((e&255)!=d[ll]) {
        Log(LOG_ERR, "packetRecv: checksum mismatch");
	return -1;
    }

    d[ll]=0;
    return ll;
}

BIGNUM *packetExtrBN(const unsigned char *d, int l)
{
    BIGNUM *a=BN_bin2bn(d+1, l-1, NULL);
    debug((DEB_BNUM, "packetExtrBN: %d %s", l-1, hexstr(a->d, l-1)));
    return a;
}


int signFinal(unsigned char *dst, int *len, const EVP_PKEY *key)
{
    return EVP_SignFinal(&signCtx, dst, len, /*XXOCONST*/(EVP_PKEY *)key);
}

int vrfyFinal(const unsigned char *dst, int len, const EVP_PKEY *key)
{
    return EVP_VerifyFinal(&vrfyCtx, /*XXOCONST*/(unsigned char *)dst,
			   len, /*XXOCONST*/(EVP_PKEY *)key);
}
