#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/netif.h"
#include "../port/error.h"
#include <libsec.h>
#include "../port/thwack.h"
/*
* sdp - secure datagram protocol
*/
typedef struct Sdp Sdp;
typedef struct Conv Conv;
typedef struct OneWay OneWay;
typedef struct Stats Stats;
typedef struct AckPkt AckPkt;
typedef struct Algorithm Algorithm;
typedef struct CipherRc4 CipherRc4;
enum
{
Qtopdir= 1, /* top level directory */
Qsdpdir, /* sdp directory */
Qclone,
Qlog,
Qconvdir, /* directory per conversation */
Qctl,
Qdata, /* unreliable packet channel */
Qcontrol, /* reliable control channel */
Qstatus,
Qstats,
Qrstats,
MaxQ,
Maxconv= 256, // power of 2
Nfs= 4, // number of file systems
MaxRetries= 12,
KeepAlive = 300, // keep alive in seconds - should probably be about 60 but is higher to avoid linksys bug
SecretLength= 32, // a secret per direction
SeqMax = (1<<24),
SeqWindow = 32,
NCompStats = 8,
};
#define TYPE(x) (((ulong)(x).path) & 0xff)
#define CONV(x) ((((ulong)(x).path) >> 8)&(Maxconv-1))
#define QID(x, y) (((x)<<8) | (y))
struct Stats
{
ulong outPackets;
ulong outDataPackets;
ulong outDataBytes;
ulong outCompDataBytes;
ulong outCompBytes;
ulong outCompStats[NCompStats];
ulong inPackets;
ulong inDataPackets;
ulong inDataBytes;
ulong inCompDataBytes;
ulong inMissing;
ulong inDup;
ulong inReorder;
ulong inBadComp;
ulong inBadAuth;
ulong inBadSeq;
ulong inBadOther;
};
struct OneWay
{
Rendez statsready;
ulong seqwrap; // number of wraps of the sequence number
ulong seq;
ulong window;
uchar secret[SecretLength];
QLock controllk;
Rendez controlready;
Block *controlpkt; // control channel
ulong controlseq;
void *cipherstate; // state cipher
int cipherivlen; // initial vector length
int cipherblklen; // block length
int (*cipher)(OneWay*, uchar *buf, int len);
void *authstate; // auth state
int authlen; // auth data length in bytes
int (*auth)(OneWay*, uchar *buf, int len);
void *compstate;
int (*comp)(Conv*, int subtype, ulong seq, Block **);
};
// conv states
enum {
CFree,
CInit,
CDial,
CAccept,
COpen,
CLocalClose,
CRemoteClose,
CClosed,
};
struct Conv {
QLock;
Sdp *sdp;
int id;
int ref; // holds conv up
int state;
int dataopen; // ref count of opens on Qdata
int controlopen; // ref count of opens on Qcontrol
int reader; // reader proc has been started
Stats lstats;
Stats rstats;
ulong lastrecv; // time last packet was received
ulong timeout;
int retries;
// the following pair uniquely define conversation on this port
ulong dialid;
ulong acceptid;
QLock readlk; // protects readproc
Proc *readproc;
Chan *chan; // packet channel
char *channame;
char owner[KNAMELEN]; /* protections */
int perm;
Algorithm *auth;
Algorithm *cipher;
Algorithm *comp;
int drop;
OneWay in;
OneWay out;
};
struct Sdp {
QLock;
Log;
int nconv;
Conv *conv[Maxconv];
int ackproc;
};
enum {
TConnect,
TControl,
TData,
TCompData,
};
enum {
ControlMesg,
ControlAck,
};
enum {
ThwackU,
ThwackC,
};
enum {
ConOpenRequest,
ConOpenAck,
ConOpenAckAck,
ConClose,
ConCloseAck,
ConReset,
};
struct AckPkt
{
uchar cseq[4];
uchar outPackets[4];
uchar outDataPackets[4];
uchar outDataBytes[4];
uchar outCompDataBytes[4];
uchar outCompStats[4*NCompStats];
uchar inPackets[4];
uchar inDataPackets[4];
uchar inDataBytes[4];
uchar inCompDataBytes[4];
uchar inMissing[4];
uchar inDup[4];
uchar inReorder[4];
uchar inBadComp[4];
uchar inBadAuth[4];
uchar inBadSeq[4];
uchar inBadOther[4];
};
struct Algorithm
{
char *name;
int keylen; // in bytes
void (*init)(Conv*);
};
enum {
RC4forward = 10*1024*1024, // maximum skip forward
RC4back = 100*1024, // maximum look back
};
struct CipherRc4
{
ulong cseq; // current byte sequence number
RC4state current;
int ovalid; // old is valid
ulong lgseq; // last good sequence
ulong oseq; // old byte sequence number
RC4state old;
};
static Dirtab sdpdirtab[]={
"log", {Qlog}, 0, 0666,
"clone", {Qclone}, 0, 0666,
};
static Dirtab convdirtab[]={
"ctl", {Qctl}, 0, 0666,
"data", {Qdata}, 0, 0666,
"control", {Qcontrol}, 0, 0666,
"status", {Qstatus}, 0, 0444,
"stats", {Qstats}, 0, 0444,
"rstats", {Qrstats}, 0, 0444,
};
static int m2p[] = {
[OREAD] 4,
[OWRITE] 2,
[ORDWR] 6
};
enum {
Logcompress= (1<<0),
Logauth= (1<<1),
Loghmac= (1<<2),
};
static Logflag logflags[] =
{
{ "compress", Logcompress, },
{ "auth", Logauth, },
{ "hmac", Loghmac, },
{ nil, 0, },
};
static Dirtab *dirtab[MaxQ];
static Sdp sdptab[Nfs];
static char *convstatename[] = {
[CFree] "Free",
[CInit] "Init",
[CDial] "Dial",
[CAccept] "Accept",
[COpen] "Open",
[CLocalClose] "LocalClose",
[CRemoteClose] "RemoteClose",
[CClosed] "Closed",
};
static int sdpgen(Chan *c, char*, Dirtab*, int, int s, Dir *dp);
static Conv *sdpclone(Sdp *sdp);
static void sdpackproc(void *a);
static void onewaycleanup(OneWay *ow);
static int readready(void *a);
static int controlread();
static void convsetstate(Conv *c, int state);
static Block *readcontrol(Conv *c, int n);
static void writecontrol(Conv *c, void *p, int n, int wait);
static Block *readdata(Conv *c, int n);
static long writedata(Conv *c, Block *b);
static void convderef(Conv *c);
static Block *conviput(Conv *c, Block *b, int control);
static void conviconnect(Conv *c, int op, Block *b);
static void convicontrol(Conv *c, int op, Block *b);
static Block *convicomp(Conv *c, int op, ulong, Block *b);
static void convoput(Conv *c, int type, int subtype, Block *b);
static void convoconnect(Conv *c, int op, ulong dialid, ulong acceptid);
static void convopenchan(Conv *c, char *path);
static void convstats(Conv *c, int local, char *buf, int n);
static void convreader(void *a);
static void setalg(Conv *c, char *name, Algorithm *tab, Algorithm **);
static void setsecret(OneWay *cc, char *secret);
static void nullcipherinit(Conv*c);
static void descipherinit(Conv*c);
static void rc4cipherinit(Conv*c);
static void nullauthinit(Conv*c);
static void shaauthinit(Conv*c);
static void md5authinit(Conv*c);
static void nullcompinit(Conv*c);
static void thwackcompinit(Conv*c);
static Algorithm cipheralg[] =
{
"null", 0, nullcipherinit,
"des_56_cbc", 7, descipherinit,
"rc4_128", 16, rc4cipherinit,
"rc4_256", 32, rc4cipherinit,
nil, 0, nil,
};
static Algorithm authalg[] =
{
"null", 0, nullauthinit,
"hmac_sha1_96", 16, shaauthinit,
"hmac_md5_96", 16, md5authinit,
nil, 0, nil,
};
static Algorithm compalg[] =
{
"null", 0, nullcompinit,
"thwack", 0, thwackcompinit,
nil, 0, nil,
};
static void
sdpinit(void)
{
int i;
Dirtab *dt;
// setup dirtab with non directory entries
for(i=0; i<nelem(sdpdirtab); i++) {
dt = sdpdirtab + i;
dirtab[TYPE(dt->qid)] = dt;
}
for(i=0; i<nelem(convdirtab); i++) {
dt = convdirtab + i;
dirtab[TYPE(dt->qid)] = dt;
}
}
static Chan*
sdpattach(char* spec)
{
Chan *c;
int dev;
char buf[100];
Sdp *sdp;
int start;
dev = atoi(spec);
if(dev<0 || dev >= Nfs)
error("bad specification");
c = devattach('E', spec);
c->qid = (Qid){QID(0, Qtopdir), 0, QTDIR};
c->dev = dev;
sdp = sdptab + dev;
qlock(sdp);
start = sdp->ackproc == 0;
sdp->ackproc = 1;
qunlock(sdp);
if(start) {
snprint(buf, sizeof(buf), "sdpackproc%d", dev);
kproc(buf, sdpackproc, sdp);
}
return c;
}
static Walkqid*
sdpwalk(Chan *c, Chan *nc, char **name, int nname)
{
return devwalk(c, nc, name, nname, 0, 0, sdpgen);
}
static int
sdpstat(Chan* c, uchar* db, int n)
{
return devstat(c, db, n, nil, 0, sdpgen);
}
static Chan*
sdpopen(Chan* ch, int omode)
{
int perm;
Sdp *sdp;
Conv *c;
omode &= 3;
perm = m2p[omode];
USED(perm);
sdp = sdptab + ch->dev;
switch(TYPE(ch->qid)) {
default:
break;
case Qtopdir:
case Qsdpdir:
case Qconvdir:
if(omode != OREAD)
error(Eperm);
break;
case Qlog:
logopen(sdp);
break;
case Qclone:
c = sdpclone(sdp);
if(c == nil)
error(Enodev);
ch->qid.path = QID(c->id, Qctl);
break;
case Qdata:
case Qctl:
case Qstatus:
case Qcontrol:
case Qstats:
case Qrstats:
c = sdp->conv[CONV(ch->qid)];
qlock(c);
if(waserror()) {
qunlock(c);
nexterror();
}
if((perm & (c->perm>>6)) != perm)
if(strcmp(up->user, c->owner) != 0 || (perm & c->perm) != perm)
error(Eperm);
c->ref++;
if(TYPE(ch->qid) == Qdata) {
c->dataopen++;
// kill reader if Qdata is opened for the first time
if(c->dataopen == 1)
if(c->readproc != nil)
postnote(c->readproc, 1, "interrupt", 0);
} else if(TYPE(ch->qid) == Qcontrol) {
c->controlopen++;
}
qunlock(c);
poperror();
break;
}
ch->mode = openmode(omode);
ch->flag |= COPEN;
ch->offset = 0;
return ch;
}
static void
sdpclose(Chan* ch)
{
Sdp *sdp = sdptab + ch->dev;
Conv *c;
if(!(ch->flag & COPEN))
return;
switch(TYPE(ch->qid)) {
case Qlog:
logclose(sdp);
break;
case Qctl:
case Qstatus:
case Qstats:
case Qrstats:
c = sdp->conv[CONV(ch->qid)];
qlock(c);
convderef(c);
qunlock(c);
break;
case Qdata:
c = sdp->conv[CONV(ch->qid)];
qlock(c);
c->dataopen--;
convderef(c);
if(c->dataopen == 0)
if(c->reader == 0)
if(c->chan != nil)
if(!waserror()) {
kproc("convreader", convreader, c);
c->reader = 1;
c->ref++;
poperror();
}
qunlock(c);
break;
case Qcontrol:
c = sdp->conv[CONV(ch->qid)];
qlock(c);
c->controlopen--;
convderef(c);
if(c->controlopen == 0 && c->ref != 0) {
switch(c->state) {
default:
convsetstate(c, CClosed);
break;
case CAccept:
case COpen:
convsetstate(c, CLocalClose);
break;
}
}
qunlock(c);
break;
}
}
static long
sdpread(Chan *ch, void *a, long n, vlong off)
{
char buf[256];
char *s;
Sdp *sdp = sdptab + ch->dev;
Conv *c;
Block *b;
int rv;
USED(off);
switch(TYPE(ch->qid)) {
default:
error(Eperm);
case Qtopdir:
case Qsdpdir:
case Qconvdir:
return devdirread(ch, a, n, 0, 0, sdpgen);
case Qlog:
return logread(sdp, a, off, n);
case Qstatus:
c = sdp->conv[CONV(ch->qid)];
qlock(c);
n = readstr(off, a, n, convstatename[c->state]);
qunlock(c);
return n;
case Qctl:
snprint(buf, sizeof buf, "%lud", CONV(ch->qid));
return readstr(off, a, n, buf);
case Qcontrol:
b = readcontrol(sdp->conv[CONV(ch->qid)], n);
if(b == nil)
return 0;
if(BLEN(b) < n)
n = BLEN(b);
memmove(a, b->rp, n);
freeb(b);
return n;
case Qdata:
b = readdata(sdp->conv[CONV(ch->qid)], n);
if(b == nil)
return 0;
if(BLEN(b) < n)
n = BLEN(b);
memmove(a, b->rp, n);
freeb(b);
return n;
case Qstats:
case Qrstats:
c = sdp->conv[CONV(ch->qid)];
s = smalloc(1000);
convstats(c, TYPE(ch->qid) == Qstats, s, 1000);
rv = readstr(off, a, n, s);
free(s);
return rv;
}
}
static Block*
sdpbread(Chan* ch, long n, ulong offset)
{
Sdp *sdp = sdptab + ch->dev;
if(TYPE(ch->qid) != Qdata)
return devbread(ch, n, offset);
return readdata(sdp->conv[CONV(ch->qid)], n);
}
static long
sdpwrite(Chan *ch, void *a, long n, vlong off)
{
Sdp *sdp = sdptab + ch->dev;
Cmdbuf *cb;
char *arg0;
char *p;
Conv *c;
Block *b;
USED(off);
switch(TYPE(ch->qid)) {
default:
error(Eperm);
case Qctl:
c = sdp->conv[CONV(ch->qid)];
cb = parsecmd(a, n);
qlock(c);
if(waserror()) {
qunlock(c);
free(cb);
nexterror();
}
if(cb->nf == 0)
error("short write");
arg0 = cb->f[0];
if(strcmp(arg0, "accept") == 0) {
if(cb->nf != 2)
error("usage: accept file");
convopenchan(c, cb->f[1]);
} else if(strcmp(arg0, "dial") == 0) {
if(cb->nf != 2)
error("usage: dial file");
convopenchan(c, cb->f[1]);
convsetstate(c, CDial);
} else if(strcmp(arg0, "drop") == 0) {
if(cb->nf != 2)
error("usage: drop permil");
c->drop = atoi(cb->f[1]);
} else if(strcmp(arg0, "cipher") == 0) {
if(cb->nf != 2)
error("usage: cipher alg");
setalg(c, cb->f[1], cipheralg, &c->cipher);
} else if(strcmp(arg0, "auth") == 0) {
if(cb->nf != 2)
error("usage: auth alg");
setalg(c, cb->f[1], authalg, &c->auth);
} else if(strcmp(arg0, "comp") == 0) {
if(cb->nf != 2)
error("usage: comp alg");
setalg(c, cb->f[1], compalg, &c->comp);
} else if(strcmp(arg0, "insecret") == 0) {
if(cb->nf != 2)
error("usage: insecret secret");
setsecret(&c->in, cb->f[1]);
if(c->cipher)
c->cipher->init(c);
if(c->auth)
c->auth->init(c);
} else if(strcmp(arg0, "outsecret") == 0) {
if(cb->nf != 2)
error("usage: outsecret secret");
setsecret(&c->out, cb->f[1]);
if(c->cipher)
c->cipher->init(c);
if(c->auth)
c->auth->init(c);
} else
error("unknown control request");
poperror();
qunlock(c);
free(cb);
return n;
case Qlog:
cb = parsecmd(a, n);
p = logctl(sdp, cb->nf, cb->f, logflags);
free(cb);
if(p != nil)
error(p);
return n;
case Qcontrol:
writecontrol(sdp->conv[CONV(ch->qid)], a, n, 0);
return n;
case Qdata:
b = allocb(n);
memmove(b->wp, a, n);
b->wp += n;
return writedata(sdp->conv[CONV(ch->qid)], b);
}
}
long
sdpbwrite(Chan *ch, Block *bp, ulong offset)
{
Sdp *sdp = sdptab + ch->dev;
if(TYPE(ch->qid) != Qdata)
return devbwrite(ch, bp, offset);
return writedata(sdp->conv[CONV(ch->qid)], bp);
}
static int
sdpgen(Chan *c, char*, Dirtab*, int, int s, Dir *dp)
{
Sdp *sdp = sdptab + c->dev;
int type = TYPE(c->qid);
Dirtab *dt;
Qid qid;
if(s == DEVDOTDOT){
switch(TYPE(c->qid)){
case Qtopdir:
case Qsdpdir:
snprint(up->genbuf, sizeof(up->genbuf), "#E%ld", c->dev);
mkqid(&qid, Qtopdir, 0, QTDIR);
devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
break;
case Qconvdir:
snprint(up->genbuf, sizeof(up->genbuf), "%d", s);
mkqid(&qid, Qsdpdir, 0, QTDIR);
devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
break;
default:
panic("sdpwalk %llux", c->qid.path);
}
return 1;
}
switch(type) {
default:
// non directory entries end up here
if(c->qid.type & QTDIR)
panic("sdpgen: unexpected directory");
if(s != 0)
return -1;
dt = dirtab[TYPE(c->qid)];
if(dt == nil)
panic("sdpgen: unknown type: %lud", TYPE(c->qid));
devdir(c, c->qid, dt->name, dt->length, eve, dt->perm, dp);
return 1;
case Qtopdir:
if(s != 0)
return -1;
mkqid(&qid, QID(0, Qsdpdir), 0, QTDIR);
devdir(c, qid, "sdp", 0, eve, 0555, dp);
return 1;
case Qsdpdir:
if(s<nelem(sdpdirtab)) {
dt = sdpdirtab+s;
devdir(c, dt->qid, dt->name, dt->length, eve, dt->perm, dp);
return 1;
}
s -= nelem(sdpdirtab);
if(s >= sdp->nconv)
return -1;
mkqid(&qid, QID(s, Qconvdir), 0, QTDIR);
snprint(up->genbuf, sizeof(up->genbuf), "%d", s);
devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
return 1;
case Qconvdir:
if(s>=nelem(convdirtab))
return -1;
dt = convdirtab+s;
mkqid(&qid, QID(CONV(c->qid),TYPE(dt->qid)), 0, QTFILE);
devdir(c, qid, dt->name, dt->length, eve, dt->perm, dp);
return 1;
}
}
static Conv*
sdpclone(Sdp *sdp)
{
Conv *c, **pp, **ep;
c = nil;
ep = sdp->conv + nelem(sdp->conv);
qlock(sdp);
if(waserror()) {
qunlock(sdp);
nexterror();
}
for(pp = sdp->conv; pp < ep; pp++) {
c = *pp;
if(c == nil){
c = malloc(sizeof(Conv));
if(c == nil)
error(Enomem);
memset(c, 0, sizeof(Conv));
qlock(c);
c->sdp = sdp;
c->id = pp - sdp->conv;
*pp = c;
sdp->nconv++;
break;
}
if(c->ref == 0 && canqlock(c)){
if(c->ref == 0)
break;
qunlock(c);
}
}
poperror();
qunlock(sdp);
if(pp >= ep)
return nil;
assert(c->state == CFree);
// set ref to 2 - 1 ref for open - 1 ref for channel state
c->ref = 2;
c->state = CInit;
c->in.window = ~0;
strncpy(c->owner, up->user, sizeof(c->owner));
c->perm = 0660;
qunlock(c);
return c;
}
// assume c is locked
static void
convretryinit(Conv *c)
{
c->retries = 0;
// +2 to avoid rounding effects.
c->timeout = TK2SEC(m->ticks) + 2;
}
// assume c is locked
static int
convretry(Conv *c, int reset)
{
c->retries++;
if(c->retries > MaxRetries) {
if(reset)
convoconnect(c, ConReset, c->dialid, c->acceptid);
convsetstate(c, CClosed);
return 0;
}
c->timeout = TK2SEC(m->ticks) + (c->retries+1);
return 1;
}
// assumes c is locked
static void
convtimer(Conv *c, ulong sec)
{
Block *b;
if(c->timeout > sec)
return;
switch(c->state) {
case CInit:
break;
case CDial:
if(convretry(c, 1))
convoconnect(c, ConOpenRequest, c->dialid, 0);
break;
case CAccept:
if(convretry(c, 1))
convoconnect(c, ConOpenAck, c->dialid, c->acceptid);
break;
case COpen:
b = c->out.controlpkt;
if(b != nil) {
if(convretry(c, 1))
convoput(c, TControl, ControlMesg, copyblock(b, blocklen(b)));
break;
}
c->timeout = c->lastrecv + KeepAlive;
if(c->timeout > sec)
break;
// keepalive - randomly spaced between KeepAlive and 2*KeepAlive
if(c->timeout + KeepAlive > sec && nrand(c->lastrecv + 2*KeepAlive - sec) > 0)
break;
// can not use writecontrol
b = allocb(4);
c->out.controlseq++;
hnputl(b->wp, c->out.controlseq);
b->wp += 4;
c->out.controlpkt = b;
convretryinit(c);
if(!waserror()) {
convoput(c, TControl, ControlMesg, copyblock(b, blocklen(b)));
poperror();
}
break;
case CLocalClose:
if(convretry(c, 0))
convoconnect(c, ConClose, c->dialid, c->acceptid);
break;
case CRemoteClose:
case CClosed:
break;
}
}
static void
sdpackproc(void *a)
{
Sdp *sdp = a;
ulong sec;
int i;
Conv *c;
for(;;) {
tsleep(&up->sleep, return0, 0, 1000);
sec = TK2SEC(m->ticks);
qlock(sdp);
for(i=0; i<sdp->nconv; i++) {
c = sdp->conv[i];
if(c->ref == 0)
continue;
qunlock(sdp);
qlock(c);
if(c->ref > 0 && !waserror()) {
convtimer(c, sec);
poperror();
}
qunlock(c);
qlock(sdp);
}
qunlock(sdp);
}
}
Dev sdpdevtab = {
'E',
"sdp",
devreset,
sdpinit,
devshutdown,
sdpattach,
sdpwalk,
sdpstat,
sdpopen,
devcreate,
sdpclose,
sdpread,
devbread,
sdpwrite,
devbwrite,
devremove,
devwstat,
};
// assume hold lock on c
static void
convsetstate(Conv *c, int state)
{
if(0)print("convsetstate %d: %s -> %s\n", c->id, convstatename[c->state], convstatename[state]);
switch(state) {
default:
panic("setstate: bad state: %d", state);
case CDial:
assert(c->state == CInit);
c->dialid = (rand()<<16) + rand();
convretryinit(c);
convoconnect(c, ConOpenRequest, c->dialid, 0);
break;
case CAccept:
assert(c->state == CInit);
c->acceptid = (rand()<<16) + rand();
convretryinit(c);
convoconnect(c, ConOpenAck, c->dialid, c->acceptid);
break;
case COpen:
assert(c->state == CDial || c->state == CAccept);
c->lastrecv = TK2SEC(m->ticks);
if(c->state == CDial) {
convretryinit(c);
convoconnect(c, ConOpenAckAck, c->dialid, c->acceptid);
hnputl(c->in.secret, c->acceptid);
hnputl(c->in.secret+4, c->dialid);
hnputl(c->out.secret, c->dialid);
hnputl(c->out.secret+4, c->acceptid);
} else {
hnputl(c->in.secret, c->dialid);
hnputl(c->in.secret+4, c->acceptid);
hnputl(c->out.secret, c->acceptid);
hnputl(c->out.secret+4, c->dialid);
}
setalg(c, "hmac_md5_96", authalg, &c->auth);
break;
case CLocalClose:
assert(c->state == CAccept || c->state == COpen);
convretryinit(c);
convoconnect(c, ConClose, c->dialid, c->acceptid);
break;
case CRemoteClose:
wakeup(&c->in.controlready);
wakeup(&c->out.controlready);
break;
case CClosed:
wakeup(&c->in.controlready);
wakeup(&c->out.controlready);
if(c->readproc)
postnote(c->readproc, 1, "interrupt", 0);
if(c->state != CClosed)
convderef(c);
break;
}
c->state = state;
}
//assumes c is locked
static void
convderef(Conv *c)
{
c->ref--;
if(c->ref > 0) {
return;
}
assert(c->ref == 0);
assert(c->dataopen == 0);
assert(c->controlopen == 0);
if(0)print("convderef: %d: ref == 0!\n", c->id);
c->state = CFree;
if(c->chan) {
cclose(c->chan);
c->chan = nil;
}
if(c->channame) {
free(c->channame);
c->channame = nil;
}
c->cipher = nil;
c->auth = nil;
c->comp = nil;
strcpy(c->owner, "network");
c->perm = 0660;
c->dialid = 0;
c->acceptid = 0;
c->timeout = 0;
c->retries = 0;
c->drop = 0;
onewaycleanup(&c->in);
onewaycleanup(&c->out);
memset(&c->lstats, 0, sizeof(Stats));
memset(&c->rstats, 0, sizeof(Stats));
}
static void
onewaycleanup(OneWay *ow)
{
if(ow->controlpkt)
freeb(ow->controlpkt);
if(ow->authstate)
free(ow->authstate);
if(ow->cipherstate)
free(ow->cipherstate);
if(ow->compstate)
free(ow->compstate);
memset(ow, 0, sizeof(OneWay));
}
// assumes conv is locked
static void
convopenchan(Conv *c, char *path)
{
if(c->state != CInit || c->chan != nil)
error("already connected");
c->chan = namec(path, Aopen, ORDWR, 0);
c->channame = smalloc(strlen(path)+1);
strcpy(c->channame, path);
if(waserror()) {
cclose(c->chan);
c->chan = nil;
free(c->channame);
c->channame = nil;
nexterror();
}
kproc("convreader", convreader, c);
assert(c->reader == 0 && c->ref > 0);
// after kproc in case it fails
c->reader = 1;
c->ref++;
poperror();
}
static void
convstats(Conv *c, int local, char *buf, int n)
{
Stats *stats;
char *p, *ep;
int i;
if(local) {
stats = &c->lstats;
} else {
if(!waserror()) {
writecontrol(c, 0, 0, 1);
poperror();
}
stats = &c->rstats;
}
qlock(c);
p = buf;
ep = buf + n;
p += snprint(p, ep-p, "outPackets: %lud\n", stats->outPackets);
p += snprint(p, ep-p, "outDataPackets: %lud\n", stats->outDataPackets);
p += snprint(p, ep-p, "outDataBytes: %lud\n", stats->outDataBytes);
p += snprint(p, ep-p, "outCompDataBytes: %lud\n", stats->outCompDataBytes);
for(i=0; i<NCompStats; i++) {
if(stats->outCompStats[i] == 0)
continue;
p += snprint(p, ep-p, "outCompStats[%d]: %lud\n", i, stats->outCompStats[i]);
}
p += snprint(p, ep-p, "inPackets: %lud\n", stats->inPackets);
p += snprint(p, ep-p, "inDataPackets: %lud\n", stats->inDataPackets);
p += snprint(p, ep-p, "inDataBytes: %lud\n", stats->inDataBytes);
p += snprint(p, ep-p, "inCompDataBytes: %lud\n", stats->inCompDataBytes);
p += snprint(p, ep-p, "inMissing: %lud\n", stats->inMissing);
p += snprint(p, ep-p, "inDup: %lud\n", stats->inDup);
p += snprint(p, ep-p, "inReorder: %lud\n", stats->inReorder);
p += snprint(p, ep-p, "inBadComp: %lud\n", stats->inBadComp);
p += snprint(p, ep-p, "inBadAuth: %lud\n", stats->inBadAuth);
p += snprint(p, ep-p, "inBadSeq: %lud\n", stats->inBadSeq);
p += snprint(p, ep-p, "inBadOther: %lud\n", stats->inBadOther);
USED(p);
qunlock(c);
}
// c is locked
static void
convack(Conv *c)
{
Block *b;
AckPkt *ack;
Stats *s;
int i;
b = allocb(sizeof(AckPkt));
ack = (AckPkt*)b->wp;
b->wp += sizeof(AckPkt);
s = &c->lstats;
hnputl(ack->cseq, c->in.controlseq);
hnputl(ack->outPackets, s->outPackets);
hnputl(ack->outDataPackets, s->outDataPackets);
hnputl(ack->outDataBytes, s->outDataBytes);
hnputl(ack->outCompDataBytes, s->outCompDataBytes);
for(i=0; i<NCompStats; i++)
hnputl(ack->outCompStats+i*4, s->outCompStats[i]);
hnputl(ack->inPackets, s->inPackets);
hnputl(ack->inDataPackets, s->inDataPackets);
hnputl(ack->inDataBytes, s->inDataBytes);
hnputl(ack->inCompDataBytes, s->inCompDataBytes);
hnputl(ack->inMissing, s->inMissing);
hnputl(ack->inDup, s->inDup);
hnputl(ack->inReorder, s->inReorder);
hnputl(ack->inBadComp, s->inBadComp);
hnputl(ack->inBadAuth, s->inBadAuth);
hnputl(ack->inBadSeq, s->inBadSeq);
hnputl(ack->inBadOther, s->inBadOther);
convoput(c, TControl, ControlAck, b);
}
// assume we hold lock for c
static Block *
conviput(Conv *c, Block *b, int control)
{
int type, subtype;
ulong seq, seqwrap;
long seqdiff;
int pad;
c->lstats.inPackets++;
if(BLEN(b) < 4) {
c->lstats.inBadOther++;
freeb(b);
return nil;
}
type = b->rp[0] >> 4;
subtype = b->rp[0] & 0xf;
b->rp += 1;
if(type == TConnect) {
conviconnect(c, subtype, b);
return nil;
}
switch(c->state) {
case CInit:
case CDial:
c->lstats.inBadOther++;
convoconnect(c, ConReset, c->dialid, c->acceptid);
convsetstate(c, CClosed);
break;
case CAccept:
case CRemoteClose:
case CLocalClose:
c->lstats.inBadOther++;
freeb(b);
return nil;
}
seq = (b->rp[0]<<16) + (b->rp[1]<<8) + b->rp[2];
b->rp += 3;
seqwrap = c->in.seqwrap;
seqdiff = seq - c->in.seq;
if(seqdiff < -(SeqMax*3/4)) {
seqwrap++;
seqdiff += SeqMax;
} else if(seqdiff > SeqMax*3/4) {
seqwrap--;
seqdiff -= SeqMax;
}
if(seqdiff <= 0) {
if(seqdiff <= -SeqWindow) {
if(0)print("old sequence number: %ld (%ld %ld)\n", seq, c->in.seqwrap, seqdiff);
c->lstats.inBadSeq++;
freeb(b);
return nil;
}
if(c->in.window & (1<<-seqdiff)) {
if(0)print("dup sequence number: %ld (%ld %ld)\n", seq, c->in.seqwrap, seqdiff);
c->lstats.inDup++;
freeb(b);
return nil;
}
c->lstats.inReorder++;
}
// ok the sequence number looks ok
if(0) print("coniput seq=%ulx\n", seq);
if(c->in.auth != 0) {
if(!(*c->in.auth)(&c->in, b->rp-4, BLEN(b)+4)) {
if(0)print("bad auth %ld\n", BLEN(b)+4);
c->lstats.inBadAuth++;
freeb(b);
return nil;
}
b->wp -= c->in.authlen;
}
if(c->in.cipher != 0) {
if(!(*c->in.cipher)(&c->in, b->rp, BLEN(b))) {
if(0)print("bad cipher\n");
c->lstats.inBadOther++;
freeb(b);
return nil;
}
b->rp += c->in.cipherivlen;
if(c->in.cipherblklen > 1) {
pad = b->wp[-1];
if(pad > BLEN(b)) {
if(0)print("pad too big\n");
c->lstats.inBadOther++;
freeb(b);
return nil;
}
b->wp -= pad;
}
}
// ok the packet is good
if(seqdiff > 0) {
while(seqdiff > 0 && c->in.window != 0) {
if((c->in.window & (1<<(SeqWindow-1))) == 0) {
c->lstats.inMissing++;
}
c->in.window <<= 1;
seqdiff--;
}
if(seqdiff > 0) {
c->lstats.inMissing += seqdiff;
seqdiff = 0;
}
c->in.seq = seq;
c->in.seqwrap = seqwrap;
}
c->in.window |= 1<<-seqdiff;
c->lastrecv = TK2SEC(m->ticks);
switch(type) {
case TControl:
convicontrol(c, subtype, b);
return nil;
case TData:
c->lstats.inDataPackets++;
c->lstats.inDataBytes += BLEN(b);
if(control)
break;
return b;
case TCompData:
c->lstats.inDataPackets++;
c->lstats.inCompDataBytes += BLEN(b);
b = convicomp(c, subtype, seq, b);
if(b == nil) {
c->lstats.inBadComp++;
return nil;
}
c->lstats.inDataBytes += BLEN(b);
if(control)
break;
return b;
}
if(0)print("dropping packet id=%d: type=%d n=%ld control=%d\n", c->id, type, BLEN(b), control);
c->lstats.inBadOther++;
freeb(b);
return nil;
}
// assume hold conv lock
static void
conviconnect(Conv *c, int subtype, Block *b)
{
ulong dialid;
ulong acceptid;
if(BLEN(b) != 8) {
freeb(b);
return;
}
dialid = nhgetl(b->rp);
acceptid = nhgetl(b->rp + 4);
freeb(b);
if(0)print("sdp: conviconnect: %s: %d %uld %uld\n", convstatename[c->state], subtype, dialid, acceptid);
if(subtype == ConReset) {
convsetstate(c, CClosed);
return;
}
switch(c->state) {
default:
panic("unknown state: %d", c->state);
case CInit:
break;
case CDial:
if(dialid != c->dialid)
goto Reset;
break;
case CAccept:
case COpen:
case CLocalClose:
case CRemoteClose:
if(dialid != c->dialid
|| subtype != ConOpenRequest && acceptid != c->acceptid)
goto Reset;
break;
case CClosed:
goto Reset;
}
switch(subtype) {
case ConOpenRequest:
switch(c->state) {
case CInit:
c->dialid = dialid;
convsetstate(c, CAccept);
return;
case CAccept:
case COpen:
// duplicate ConOpenRequest that we ignore
return;
}
break;
case ConOpenAck:
switch(c->state) {
case CDial:
c->acceptid = acceptid;
convsetstate(c, COpen);
return;
case COpen:
// duplicate that we have to ack
convoconnect(c, ConOpenAckAck, acceptid, dialid);
return;
}
break;
case ConOpenAckAck:
switch(c->state) {
case CAccept:
convsetstate(c, COpen);
return;
case COpen:
case CLocalClose:
case CRemoteClose:
// duplicate that we ignore
return;
}
break;
case ConClose:
switch(c->state) {
case COpen:
convoconnect(c, ConCloseAck, dialid, acceptid);
convsetstate(c, CRemoteClose);
return;
case CRemoteClose:
// duplicate ConClose
convoconnect(c, ConCloseAck, dialid, acceptid);
return;
}
break;
case ConCloseAck:
switch(c->state) {
case CLocalClose:
convsetstate(c, CClosed);
return;
}
break;
}
Reset:
// invalid connection message - reset to sender
if(1)print("sdp: invalid conviconnect - sending reset\n");
convoconnect(c, ConReset, dialid, acceptid);
convsetstate(c, CClosed);
}
static void
convicontrol(Conv *c, int subtype, Block *b)
{
ulong cseq;
AckPkt *ack;
int i;
if(BLEN(b) < 4)
return;
cseq = nhgetl(b->rp);
switch(subtype){
case ControlMesg:
if(cseq == c->in.controlseq) {
if(0)print("duplicate control packet: %ulx\n", cseq);
// duplicate control packet
freeb(b);
if(c->in.controlpkt == nil)
convack(c);
return;
}
if(cseq != c->in.controlseq+1)
return;
c->in.controlseq = cseq;
b->rp += 4;
if(BLEN(b) == 0) {
// just a ping
freeb(b);
convack(c);
} else {
c->in.controlpkt = b;
if(0) print("recv %ld size=%ld\n", cseq, BLEN(b));
wakeup(&c->in.controlready);
}
return;
case ControlAck:
if(cseq != c->out.controlseq)
return;
if(BLEN(b) < sizeof(AckPkt))
return;
ack = (AckPkt*)(b->rp);
c->rstats.outPackets = nhgetl(ack->outPackets);
c->rstats.outDataPackets = nhgetl(ack->outDataPackets);
c->rstats.outDataBytes = nhgetl(ack->outDataBytes);
c->rstats.outCompDataBytes = nhgetl(ack->outCompDataBytes);
for(i=0; i<NCompStats; i++)
c->rstats.outCompStats[i] = nhgetl(ack->outCompStats + 4*i);
c->rstats.inPackets = nhgetl(ack->inPackets);
c->rstats.inDataPackets = nhgetl(ack->inDataPackets);
c->rstats.inDataBytes = nhgetl(ack->inDataBytes);
c->rstats.inCompDataBytes = nhgetl(ack->inCompDataBytes);
c->rstats.inMissing = nhgetl(ack->inMissing);
c->rstats.inDup = nhgetl(ack->inDup);
c->rstats.inReorder = nhgetl(ack->inReorder);
c->rstats.inBadComp = nhgetl(ack->inBadComp);
c->rstats.inBadAuth = nhgetl(ack->inBadAuth);
c->rstats.inBadSeq = nhgetl(ack->inBadSeq);
c->rstats.inBadOther = nhgetl(ack->inBadOther);
freeb(b);
freeb(c->out.controlpkt);
c->out.controlpkt = nil;
c->timeout = c->lastrecv + KeepAlive;
wakeup(&c->out.controlready);
return;
}
}
static Block*
convicomp(Conv *c, int subtype, ulong seq, Block *b)
{
if(c->in.comp == nil) {
freeb(b);
return nil;
}
if(!(*c->in.comp)(c, subtype, seq, &b))
return nil;
return b;
}
// c is locked
static void
convwriteblock(Conv *c, Block *b)
{
// simulated errors
if(c->drop && nrand(c->drop) == 0)
return;
if(waserror()) {
convsetstate(c, CClosed);
nexterror();
}
devtab[c->chan->type]->bwrite(c->chan, b, 0);
poperror();
}
// assume hold conv lock
static void
convoput(Conv *c, int type, int subtype, Block *b)
{
int pad;
c->lstats.outPackets++;
/* Make room for sdp trailer */
if(c->out.cipherblklen > 1)
pad = c->out.cipherblklen - (BLEN(b) + c->out.cipherivlen) % c->out.cipherblklen;
else
pad = 0;
b = padblock(b, -(pad+c->out.authlen));
if(pad) {
memset(b->wp, 0, pad-1);
b->wp[pad-1] = pad;
b->wp += pad;
}
/* Make space to fit sdp header */
b = padblock(b, 4 + c->out.cipherivlen);
b->rp[0] = (type << 4) | subtype;
c->out.seq++;
if(c->out.seq == (1<<24)) {
c->out.seq = 0;
c->out.seqwrap++;
}
b->rp[1] = c->out.seq>>16;
b->rp[2] = c->out.seq>>8;
b->rp[3] = c->out.seq;
if(c->out.cipher)
(*c->out.cipher)(&c->out, b->rp+4, BLEN(b)-4);
// auth
if(c->out.auth) {
b->wp += c->out.authlen;
(*c->out.auth)(&c->out, b->rp, BLEN(b));
}
convwriteblock(c, b);
}
// assume hold conv lock
static void
convoconnect(Conv *c, int op, ulong dialid, ulong acceptid)
{
Block *b;
c->lstats.outPackets++;
assert(c->chan != nil);
b = allocb(9);
b->wp[0] = (TConnect << 4) | op;
hnputl(b->wp+1, dialid);
hnputl(b->wp+5, acceptid);
b->wp += 9;
if(!waserror()) {
convwriteblock(c, b);
poperror();
}
}
static Block *
convreadblock(Conv *c, int n)
{
Block *b;
Chan *ch;
qlock(&c->readlk);
if(waserror()) {
c->readproc = nil;
qunlock(&c->readlk);
nexterror();
}
qlock(c);
if(c->state == CClosed) {
qunlock(c);
error("closed");
}
c->readproc = up;
ch = c->chan;
assert(c->ref > 0);
qunlock(c);
b = devtab[ch->type]->bread(ch, n, 0);
c->readproc = nil;
poperror();
qunlock(&c->readlk);
return b;
}
static int
readready(void *a)
{
Conv *c = a;
return c->in.controlpkt != nil || (c->state == CClosed) || (c->state == CRemoteClose);
}
static Block *
readcontrol(Conv *c, int n)
{
Block *b;
USED(n);
qlock(&c->in.controllk);
if(waserror()) {
qunlock(&c->in.controllk);
nexterror();
}
qlock(c); // this lock is not held during the sleep below
for(;;) {
if(c->chan == nil || c->state == CClosed) {
qunlock(c);
if(0)print("readcontrol: return error - state = %s\n", convstatename[c->state]);
error("conversation closed");
}
if(c->in.controlpkt != nil)
break;
if(c->state == CRemoteClose) {
qunlock(c);
if(0)print("readcontrol: return nil - state = %s\n", convstatename[c->state]);
poperror();
return nil;
}
qunlock(c);
sleep(&c->in.controlready, readready, c);
qlock(c);
}
convack(c);
b = c->in.controlpkt;
c->in.controlpkt = nil;
qunlock(c);
poperror();
qunlock(&c->in.controllk);
return b;
}
static int
writeready(void *a)
{
Conv *c = a;
return c->out.controlpkt == nil || (c->state == CClosed) || (c->state == CRemoteClose);
}
// c is locked
static void
writewait(Conv *c)
{
for(;;) {
if(c->state == CFree || c->state == CInit ||
c->state == CClosed || c->state == CRemoteClose)
error("conversation closed");
if(c->state == COpen && c->out.controlpkt == nil)
break;
qunlock(c);
if(waserror()) {
qlock(c);
nexterror();
}
sleep(&c->out.controlready, writeready, c);
poperror();
qlock(c);
}
}
static void
writecontrol(Conv *c, void *p, int n, int wait)
{
Block *b;
qlock(&c->out.controllk);
qlock(c);
if(waserror()) {
qunlock(c);
qunlock(&c->out.controllk);
nexterror();
}
writewait(c);
b = allocb(4+n);
c->out.controlseq++;
hnputl(b->wp, c->out.controlseq);
memmove(b->wp+4, p, n);
b->wp += 4+n;
c->out.controlpkt = b;
convretryinit(c);
convoput(c, TControl, ControlMesg, copyblock(b, blocklen(b)));
if(wait)
writewait(c);
poperror();
qunlock(c);
qunlock(&c->out.controllk);
}
static Block *
readdata(Conv *c, int n)
{
Block *b;
int nn;
for(;;) {
// some slack for tunneling overhead
nn = n + 100;
// make sure size is big enough for control messages
if(nn < 1000)
nn = 1000;
b = convreadblock(c, nn);
if(b == nil)
return nil;
qlock(c);
if(waserror()) {
qunlock(c);
return nil;
}
b = conviput(c, b, 0);
poperror();
qunlock(c);
if(b != nil) {
if(BLEN(b) > n)
b->wp = b->rp + n;
return b;
}
}
}
static long
writedata(Conv *c, Block *b)
{
int n;
ulong seq;
int subtype;
qlock(c);
if(waserror()) {
qunlock(c);
nexterror();
}
if(c->state != COpen) {
freeb(b);
error("conversation not open");
}
n = BLEN(b);
c->lstats.outDataPackets++;
c->lstats.outDataBytes += n;
if(c->out.comp != nil) {
// must generate same value as convoput
seq = (c->out.seq + 1) & (SeqMax-1);
subtype = (*c->out.comp)(c, 0, seq, &b);
c->lstats.outCompDataBytes += BLEN(b);
convoput(c, TCompData, subtype, b);
} else
convoput(c, TData, 0, b);
poperror();
qunlock(c);
return n;
}
static void
convreader(void *a)
{
Conv *c = a;
Block *b;
qlock(c);
assert(c->reader == 1);
while(c->dataopen == 0 && c->state != CClosed) {
qunlock(c);
b = nil;
if(!waserror()) {
b = convreadblock(c, 2000);
poperror();
}
qlock(c);
if(b == nil) {
if(strcmp(up->errstr, Eintr) != 0) {
convsetstate(c, CClosed);
break;
}
} else if(!waserror()) {
conviput(c, b, 1);
poperror();
}
}
c->reader = 0;
convderef(c);
qunlock(c);
pexit("hangup", 1);
}
/* ciphers, authenticators, and compressors */
static void
setalg(Conv *c, char *name, Algorithm *alg, Algorithm **p)
{
for(; alg->name; alg++)
if(strcmp(name, alg->name) == 0)
break;
if(alg->name == nil)
error("unknown algorithm");
*p = alg;
alg->init(c);
}
static void
setsecret(OneWay *ow, char *secret)
{
char *p;
int i, c;
i = 0;
memset(ow->secret, 0, sizeof(ow->secret));
for(p=secret; *p; p++) {
if(i >= sizeof(ow->secret)*2)
break;
c = *p;
if(c >= '0' && c <= '9')
c -= '0';
else if(c >= 'a' && c <= 'f')
c -= 'a'-10;
else if(c >= 'A' && c <= 'F')
c -= 'A'-10;
else
error("bad character in secret");
if((i&1) == 0)
c <<= 4;
ow->secret[i>>1] |= c;
i++;
}
}
static void
setkey(uchar *key, int n, OneWay *ow, char *prefix)
{
uchar ibuf[SHA1dlen], obuf[MD5dlen], salt[10];
int i, round = 0;
while(n > 0){
for(i=0; i<round+1; i++)
salt[i] = 'A'+round;
sha1((uchar*)prefix, strlen(prefix), ibuf, sha1(salt, round+1, nil, nil));
md5(ibuf, SHA1dlen, obuf, md5(ow->secret, sizeof(ow->secret), nil, nil));
i = (n<MD5dlen) ? n : MD5dlen;
memmove(key, obuf, i);
key += i;
n -= i;
if(++round > sizeof salt)
panic("setkey: you ask too much");
}
}
static void
cipherfree(Conv *c)
{
if(c->in.cipherstate) {
free(c->in.cipherstate);
c->in.cipherstate = nil;
}
if(c->out.cipherstate) {
free(c->out.cipherstate);
c->out.cipherstate = nil;
}
c->in.cipher = nil;
c->in.cipherblklen = 0;
c->out.cipherblklen = 0;
c->in.cipherivlen = 0;
c->out.cipherivlen = 0;
}
static void
authfree(Conv *c)
{
if(c->in.authstate) {
free(c->in.authstate);
c->in.authstate = nil;
}
if(c->out.authstate) {
free(c->out.authstate);
c->out.authstate = nil;
}
c->in.auth = nil;
c->in.authlen = 0;
c->out.authlen = 0;
}
static void
compfree(Conv *c)
{
if(c->in.compstate) {
free(c->in.compstate);
c->in.compstate = nil;
}
if(c->out.compstate) {
free(c->out.compstate);
c->out.compstate = nil;
}
c->in.comp = nil;
}
static void
nullcipherinit(Conv *c)
{
cipherfree(c);
}
static int
desencrypt(OneWay *ow, uchar *p, int n)
{
uchar *pp, *ip, *eip, *ep;
DESstate *ds = ow->cipherstate;
if(n < 8 || (n & 0x7 != 0))
return 0;
ep = p + n;
memmove(p, ds->ivec, 8);
for(p += 8; p < ep; p += 8){
pp = p;
ip = ds->ivec;
for(eip = ip+8; ip < eip; )
*pp++ ^= *ip++;
block_cipher(ds->expanded, p, 0);
memmove(ds->ivec, p, 8);
}
return 1;
}
static int
desdecrypt(OneWay *ow, uchar *p, int n)
{
uchar tmp[8];
uchar *tp, *ip, *eip, *ep;
DESstate *ds = ow->cipherstate;
if(n < 8 || (n & 0x7 != 0))
return 0;
ep = p + n;
memmove(ds->ivec, p, 8);
p += 8;
while(p < ep){
memmove(tmp, p, 8);
block_cipher(ds->expanded, p, 1);
tp = tmp;
ip = ds->ivec;
for(eip = ip+8; ip < eip; ){
*p++ ^= *ip;
*ip++ = *tp++;
}
}
return 1;
}
static void
descipherinit(Conv *c)
{
uchar key[8];
uchar ivec[8];
int i;
int n = c->cipher->keylen;
cipherfree(c);
if(n > sizeof(key))
n = sizeof(key);
/* in */
memset(key, 0, sizeof(key));
setkey(key, n, &c->in, "cipher");
memset(ivec, 0, sizeof(ivec));
c->in.cipherblklen = 8;
c->in.cipherivlen = 8;
c->in.cipher = desdecrypt;
c->in.cipherstate = smalloc(sizeof(DESstate));
setupDESstate(c->in.cipherstate, key, ivec);
/* out */
memset(key, 0, sizeof(key));
setkey(key, n, &c->out, "cipher");
for(i=0; i<8; i++)
ivec[i] = nrand(256);
c->out.cipherblklen = 8;
c->out.cipherivlen = 8;
c->out.cipher = desencrypt;
c->out.cipherstate = smalloc(sizeof(DESstate));
setupDESstate(c->out.cipherstate, key, ivec);
}
static int
rc4encrypt(OneWay *ow, uchar *p, int n)
{
CipherRc4 *cr = ow->cipherstate;
if(n < 4)
return 0;
hnputl(p, cr->cseq);
p += 4;
n -= 4;
rc4(&cr->current, p, n);
cr->cseq += n;
return 1;
}
static int
rc4decrypt(OneWay *ow, uchar *p, int n)
{
CipherRc4 *cr = ow->cipherstate;
RC4state tmpstate;
ulong seq;
long d, dd;
if(n < 4)
return 0;
seq = nhgetl(p);
p += 4;
n -= 4;
d = seq-cr->cseq;
if(d == 0) {
rc4(&cr->current, p, n);
cr->cseq += n;
if(cr->ovalid) {
dd = cr->cseq - cr->lgseq;
if(dd > RC4back)
cr->ovalid = 0;
}
} else if(d > 0) {
//print("missing packet: %uld %ld\n", seq, d);
// this link is hosed
if(d > RC4forward)
return 0;
cr->lgseq = seq;
if(!cr->ovalid) {
cr->ovalid = 1;
cr->oseq = cr->cseq;
memmove(&cr->old, &cr->current, sizeof(RC4state));
}
rc4skip(&cr->current, d);
rc4(&cr->current, p, n);
cr->cseq = seq+n;
} else {
//print("reordered packet: %uld %ld\n", seq, d);
dd = seq - cr->oseq;
if(!cr->ovalid || -d > RC4back || dd < 0)
return 0;
memmove(&tmpstate, &cr->old, sizeof(RC4state));
rc4skip(&tmpstate, dd);
rc4(&tmpstate, p, n);
return 1;
}
// move old state up
if(cr->ovalid) {
dd = cr->cseq - RC4back - cr->oseq;
if(dd > 0) {
rc4skip(&cr->old, dd);
cr->oseq += dd;
}
}
return 1;
}
static void
rc4cipherinit(Conv *c)
{
uchar key[32];
CipherRc4 *cr;
int n;
cipherfree(c);
n = c->cipher->keylen;
if(n > sizeof(key))
n = sizeof(key);
/* in */
memset(key, 0, sizeof(key));
setkey(key, n, &c->in, "cipher");
c->in.cipherblklen = 1;
c->in.cipherivlen = 4;
c->in.cipher = rc4decrypt;
cr = smalloc(sizeof(CipherRc4));
memset(cr, 0, sizeof(*cr));
setupRC4state(&cr->current, key, n);
c->in.cipherstate = cr;
/* out */
memset(key, 0, sizeof(key));
setkey(key, n, &c->out, "cipher");
c->out.cipherblklen = 1;
c->out.cipherivlen = 4;
c->out.cipher = rc4encrypt;
cr = smalloc(sizeof(CipherRc4));
memset(cr, 0, sizeof(*cr));
setupRC4state(&cr->current, key, n);
c->out.cipherstate = cr;
}
static void
nullauthinit(Conv *c)
{
authfree(c);
}
static void
shaauthinit(Conv *c)
{
authfree(c);
}
static void
seanq_hmac_md5(uchar hash[MD5dlen], ulong wrap, uchar *t, long tlen, uchar *key, long klen)
{
uchar ipad[65], opad[65], wbuf[4];
int i;
DigestState *digest;
uchar innerhash[MD5dlen];
for(i=0; i<64; i++){
ipad[i] = 0x36;
opad[i] = 0x5c;
}
ipad[64] = opad[64] = 0;
for(i=0; i<klen; i++){
ipad[i] ^= key[i];
opad[i] ^= key[i];
}
hnputl(wbuf, wrap);
digest = md5(ipad, 64, nil, nil);
digest = md5(wbuf, sizeof(wbuf), nil, digest);
md5(t, tlen, innerhash, digest);
digest = md5(opad, 64, nil, nil);
md5(innerhash, MD5dlen, hash, digest);
}
static int
md5auth(OneWay *ow, uchar *t, int tlen)
{
uchar hash[MD5dlen];
int r;
if(tlen < ow->authlen)
return 0;
tlen -= ow->authlen;
memset(hash, 0, MD5dlen);
seanq_hmac_md5(hash, ow->seqwrap, t, tlen, (uchar*)ow->authstate, 16);
r = memcmp(t+tlen, hash, ow->authlen) == 0;
memmove(t+tlen, hash, ow->authlen);
return r;
}
static void
md5authinit(Conv *c)
{
int keylen;
authfree(c);
keylen = c->auth->keylen;
if(keylen > 16)
keylen = 16;
/* in */
c->in.authstate = smalloc(16);
memset(c->in.authstate, 0, 16);
setkey(c->in.authstate, keylen, &c->in, "auth");
c->in.authlen = 12;
c->in.auth = md5auth;
/* out */
c->out.authstate = smalloc(16);
memset(c->out.authstate, 0, 16);
setkey(c->out.authstate, keylen, &c->out, "auth");
c->out.authlen = 12;
c->out.auth = md5auth;
}
static void
nullcompinit(Conv *c)
{
compfree(c);
}
static int
thwackcomp(Conv *c, int, ulong seq, Block **bp)
{
Block *b, *bb;
int nn;
ulong ackseq;
uchar mask;
// add ack info
b = padblock(*bp, 4);
ackseq = unthwackstate(c->in.compstate, &mask);
b->rp[0] = mask;
b->rp[1] = ackseq>>16;
b->rp[2] = ackseq>>8;
b->rp[3] = ackseq;
bb = allocb(BLEN(b));
nn = thwack(c->out.compstate, bb->wp, b->rp, BLEN(b), seq, c->lstats.outCompStats);
if(nn < 0) {
freeb(bb);
*bp = b;
return ThwackU;
} else {
bb->wp += nn;
freeb(b);
*bp = bb;
return ThwackC;
}
}
static int
thwackuncomp(Conv *c, int subtype, ulong seq, Block **bp)
{
Block *b, *bb;
ulong mask;
ulong mseq;
int n;
switch(subtype) {
default:
return 0;
case ThwackU:
b = *bp;
mask = b->rp[0];
mseq = (b->rp[1]<<16) | (b->rp[2]<<8) | b->rp[3];
b->rp += 4;
thwackack(c->out.compstate, mseq, mask);
return 1;
case ThwackC:
bb = *bp;
b = allocb(ThwMaxBlock);
n = unthwack(c->in.compstate, b->wp, ThwMaxBlock, bb->rp, BLEN(bb), seq);
freeb(bb);
*bp = nil;
if(n < 0) {
if(0)print("unthwack failed: %d\n", n);
freeb(b);
return 0;
}
b->wp += n;
mask = b->rp[0];
mseq = (b->rp[1]<<16) | (b->rp[2]<<8) | b->rp[3];
thwackack(c->out.compstate, mseq, mask);
b->rp += 4;
*bp = b;
return 1;
}
}
static void
thwackcompinit(Conv *c)
{
compfree(c);
c->in.compstate = malloc(sizeof(Unthwack));
if(c->in.compstate == nil)
error(Enomem);
unthwackinit(c->in.compstate);
c->out.compstate = malloc(sizeof(Thwack));
if(c->out.compstate == nil)
error(Enomem);
thwackinit(c->out.compstate);
c->in.comp = thwackuncomp;
c->out.comp = thwackcomp;
}
|