diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/9/port/devsdp.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/9/port/devsdp.c')
-rwxr-xr-x | sys/src/9/port/devsdp.c | 2317 |
1 files changed, 2317 insertions, 0 deletions
diff --git a/sys/src/9/port/devsdp.c b/sys/src/9/port/devsdp.c new file mode 100755 index 000000000..5ed0472a1 --- /dev/null +++ b/sys/src/9/port/devsdp.c @@ -0,0 +1,2317 @@ +#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: + sprint(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; +} |