/* * devtls - record layer for transport layer security 1.0 and secure sockets layer 3.0 */ #include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "../port/error.h" #include typedef struct OneWay OneWay; typedef struct Secret Secret; typedef struct TlsRec TlsRec; typedef struct TlsErrs TlsErrs; enum { Statlen= 1024, /* max. length of status or stats message */ /* buffer limits */ MaxRecLen = 1<<14, /* max payload length of a record layer message */ MaxCipherRecLen = MaxRecLen + 2048, RecHdrLen = 5, MaxMacLen = SHA1dlen, /* protocol versions we can accept */ TLSVersion = 0x0301, SSL3Version = 0x0300, ProtocolVersion = 0x0301, /* maximum version we speak */ MinProtoVersion = 0x0300, /* limits on version we accept */ MaxProtoVersion = 0x03ff, /* connection states */ SHandshake = 1 << 0, /* doing handshake */ SOpen = 1 << 1, /* application data can be sent */ SRClose = 1 << 2, /* remote side has closed down */ SLClose = 1 << 3, /* sent a close notify alert */ SAlert = 1 << 5, /* sending or sent a fatal alert */ SError = 1 << 6, /* some sort of error has occured */ SClosed = 1 << 7, /* it is all over */ /* record types */ RChangeCipherSpec = 20, RAlert, RHandshake, RApplication, SSL2ClientHello = 1, HSSL2ClientHello = 9, /* local convention; see tlshand.c */ /* alerts */ ECloseNotify = 0, EUnexpectedMessage = 10, EBadRecordMac = 20, EDecryptionFailed = 21, ERecordOverflow = 22, EDecompressionFailure = 30, EHandshakeFailure = 40, ENoCertificate = 41, EBadCertificate = 42, EUnsupportedCertificate = 43, ECertificateRevoked = 44, ECertificateExpired = 45, ECertificateUnknown = 46, EIllegalParameter = 47, EUnknownCa = 48, EAccessDenied = 49, EDecodeError = 50, EDecryptError = 51, EExportRestriction = 60, EProtocolVersion = 70, EInsufficientSecurity = 71, EInternalError = 80, EUserCanceled = 90, ENoRenegotiation = 100, EMAX = 256 }; struct Secret { char *encalg; /* name of encryption alg */ char *hashalg; /* name of hash alg */ int (*enc)(Secret*, uchar*, int); int (*dec)(Secret*, uchar*, int); int (*unpad)(uchar*, int, int); DigestState *(*mac)(uchar*, ulong, uchar*, ulong, uchar*, DigestState*); int block; /* encryption block len, 0 if none */ int maclen; void *enckey; uchar mackey[MaxMacLen]; }; struct OneWay { QLock io; /* locks io access */ QLock seclock; /* locks secret paramaters */ ulong seq; Secret *sec; /* cipher in use */ Secret *new; /* cipher waiting for enable */ }; struct TlsRec { Chan *c; /* io channel */ int ref; /* serialized by tdlock for atomic destroy */ int version; /* version of the protocol we are speaking */ char verset; /* version has been set */ char opened; /* opened command every issued? */ char err[ERRMAX]; /* error message to return to handshake requests */ vlong handin; /* bytes communicated by the record layer */ vlong handout; vlong datain; vlong dataout; Lock statelk; int state; int debug; /* record layer mac functions for different protocol versions */ void (*packMac)(Secret*, uchar*, uchar*, uchar*, uchar*, int, uchar*); /* input side -- protected by in.io */ OneWay in; Block *processed; /* next bunch of application data */ Block *unprocessed; /* data read from c but not parsed into records */ /* handshake queue */ Lock hqlock; /* protects hqref, alloc & free of handq, hprocessed */ int hqref; Queue *handq; /* queue of handshake messages */ Block *hprocessed; /* remainder of last block read from handq */ QLock hqread; /* protects reads for hprocessed, handq */ /* output side */ OneWay out; /* protections */ char *user; int perm; }; struct TlsErrs{ int err; int sslerr; int tlserr; int fatal; char *msg; }; static TlsErrs tlserrs[] = { {ECloseNotify, ECloseNotify, ECloseNotify, 0, "close notify"}, {EUnexpectedMessage, EUnexpectedMessage, EUnexpectedMessage, 1, "unexpected message"}, {EBadRecordMac, EBadRecordMac, EBadRecordMac, 1, "bad record mac"}, {EDecryptionFailed, EIllegalParameter, EDecryptionFailed, 1, "decryption failed"}, {ERecordOverflow, EIllegalParameter, ERecordOverflow, 1, "record too long"}, {EDecompressionFailure, EDecompressionFailure, EDecompressionFailure, 1, "decompression failed"}, {EHandshakeFailure, EHandshakeFailure, EHandshakeFailure, 1, "could not negotiate acceptable security parameters"}, {ENoCertificate, ENoCertificate, ECertificateUnknown, 1, "no appropriate certificate available"}, {EBadCertificate, EBadCertificate, EBadCertificate, 1, "corrupted or invalid certificate"}, {EUnsupportedCertificate, EUnsupportedCertificate, EUnsupportedCertificate, 1, "unsupported certificate type"}, {ECertificateRevoked, ECertificateRevoked, ECertificateRevoked, 1, "revoked certificate"}, {ECertificateExpired, ECertificateExpired, ECertificateExpired, 1, "expired certificate"}, {ECertificateUnknown, ECertificateUnknown, ECertificateUnknown, 1, "unacceptable certificate"}, {EIllegalParameter, EIllegalParameter, EIllegalParameter, 1, "illegal parameter"}, {EUnknownCa, EHandshakeFailure, EUnknownCa, 1, "unknown certificate authority"}, {EAccessDenied, EHandshakeFailure, EAccessDenied, 1, "access denied"}, {EDecodeError, EIllegalParameter, EDecodeError, 1, "error decoding message"}, {EDecryptError, EIllegalParameter, EDecryptError, 1, "error decrypting message"}, {EExportRestriction, EHandshakeFailure, EExportRestriction, 1, "export restriction violated"}, {EProtocolVersion, EIllegalParameter, EProtocolVersion, 1, "protocol version not supported"}, {EInsufficientSecurity, EHandshakeFailure, EInsufficientSecurity, 1, "stronger security routines required"}, {EInternalError, EHandshakeFailure, EInternalError, 1, "internal error"}, {EUserCanceled, ECloseNotify, EUserCanceled, 0, "handshake canceled by user"}, {ENoRenegotiation, EUnexpectedMessage, ENoRenegotiation, 0, "no renegotiation"}, }; enum { /* max. open tls connections */ MaxTlsDevs = 1024 }; static Lock tdlock; static int tdhiwat; static int maxtlsdevs = 128; static TlsRec **tlsdevs; static char **trnames; static char *encalgs; static char *hashalgs; enum{ Qtopdir = 1, /* top level directory */ Qprotodir, Qclonus, Qencalgs, Qhashalgs, Qconvdir, /* directory for a conversation */ Qdata, Qctl, Qhand, Qstatus, Qstats, }; #define TYPE(x) ((x).path & 0xf) #define CONV(x) (((x).path >> 5)&(MaxTlsDevs-1)) #define QID(c, y) (((c)<<5) | (y)) static void checkstate(TlsRec *, int, int); static void ensure(TlsRec*, Block**, int); static void consume(Block**, uchar*, int); static Chan* buftochan(char*); static void tlshangup(TlsRec*); static void tlsError(TlsRec*, char *); static void alertHand(TlsRec*, char *); static TlsRec *newtls(Chan *c); static TlsRec *mktlsrec(void); static DigestState*sslmac_md5(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s); static DigestState*sslmac_sha1(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s); static DigestState*nomac(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s); static void sslPackMac(Secret *sec, uchar *mackey, uchar *seq, uchar *header, uchar *body, int len, uchar *mac); static void tlsPackMac(Secret *sec, uchar *mackey, uchar *seq, uchar *header, uchar *body, int len, uchar *mac); static void put64(uchar *p, vlong x); static void put32(uchar *p, u32int); static void put24(uchar *p, int); static void put16(uchar *p, int); static u32int get32(uchar *p); static int get16(uchar *p); static void tlsSetState(TlsRec *tr, int new, int old); static void rcvAlert(TlsRec *tr, int err); static void sendAlert(TlsRec *tr, int err); static void rcvError(TlsRec *tr, int err, char *msg, ...); static int rc4enc(Secret *sec, uchar *buf, int n); static int des3enc(Secret *sec, uchar *buf, int n); static int des3dec(Secret *sec, uchar *buf, int n); static int noenc(Secret *sec, uchar *buf, int n); static int sslunpad(uchar *buf, int n, int block); static int tlsunpad(uchar *buf, int n, int block); static void freeSec(Secret *sec); static char *tlsstate(int s); static void pdump(int, void*, char*); #pragma varargck argpos rcvError 3 static char *tlsnames[] = { [Qclonus] "clone", [Qencalgs] "encalgs", [Qhashalgs] "hashalgs", [Qdata] "data", [Qctl] "ctl", [Qhand] "hand", [Qstatus] "status", [Qstats] "stats", }; static int convdir[] = { Qctl, Qdata, Qhand, Qstatus, Qstats }; static int tlsgen(Chan *c, char*, Dirtab *, int, int s, Dir *dp) { Qid q; TlsRec *tr; char *name, *nm; int perm, t; q.vers = 0; q.type = QTFILE; t = TYPE(c->qid); switch(t) { case Qtopdir: if(s == DEVDOTDOT){ q.path = QID(0, Qtopdir); q.type = QTDIR; devdir(c, q, "#a", 0, eve, 0555, dp); return 1; } if(s > 0) return -1; q.path = QID(0, Qprotodir); q.type = QTDIR; devdir(c, q, "tls", 0, eve, 0555, dp); return 1; case Qprotodir: if(s == DEVDOTDOT){ q.path = QID(0, Qtopdir); q.type = QTDIR; devdir(c, q, ".", 0, eve, 0555, dp); return 1; } if(s < 3){ switch(s) { default: return -1; case 0: q.path = QID(0, Qclonus); break; case 1: q.path = QID(0, Qencalgs); break; case 2: q.path = QID(0, Qhashalgs); break; } perm = 0444; if(TYPE(q) == Qclonus) perm = 0555; devdir(c, q, tlsnames[TYPE(q)], 0, eve, perm, dp); return 1; } s -= 3; if(s >= tdhiwat) return -1; q.path = QID(s, Qconvdir); q.type = QTDIR; lock(&tdlock); tr = tlsdevs[s]; if(tr != nil) nm = tr->user; else nm = eve; if((name = trnames[s]) == nil){ name = trnames[s] = smalloc(16); sprint(name, "%d", s); } devdir(c, q, name, 0, nm, 0555, dp); unlock(&tdlock); return 1; case Qconvdir: if(s == DEVDOTDOT){ q.path = QID(0, Qprotodir); q.type = QTDIR; devdir(c, q, "tls", 0, eve, 0555, dp); return 1; } if(s < 0 || s >= nelem(convdir)) return -1; lock(&tdlock); tr = tlsdevs[CONV(c->qid)]; if(tr != nil){ nm = tr->user; perm = tr->perm; }else{ perm = 0; nm = eve; } t = convdir[s]; if(t == Qstatus || t == Qstats) perm &= 0444; q.path = QID(CONV(c->qid), t); devdir(c, q, tlsnames[t], 0, nm, perm, dp); unlock(&tdlock); return 1; case Qclonus: case Qencalgs: case Qhashalgs: perm = 0444; if(t == Qclonus) perm = 0555; devdir(c, c->qid, tlsnames[t], 0, eve, perm, dp); return 1; default: lock(&tdlock); tr = tlsdevs[CONV(c->qid)]; if(tr != nil){ nm = tr->user; perm = tr->perm; }else{ perm = 0; nm = eve; } if(t == Qstatus || t == Qstats) perm &= 0444; devdir(c, c->qid, tlsnames[t], 0, nm, perm, dp); unlock(&tdlock); return 1; } } static Chan* tlsattach(char *spec) { Chan *c; c = devattach('a', spec); c->qid.path = QID(0, Qtopdir); c->qid.type = QTDIR; c->qid.vers = 0; return c; } static Walkqid* tlswalk(Chan *c, Chan *nc, char **name, int nname) { return devwalk(c, nc, name, nname, nil, 0, tlsgen); } static int tlsstat(Chan *c, uchar *db, int n) { return devstat(c, db, n, nil, 0, tlsgen); } static Chan* tlsopen(Chan *c, int omode) { TlsRec *tr, **pp; int t, perm; perm = 0; omode &= 3; switch(omode) { case OREAD: perm = 4; break; case OWRITE: perm = 2; break; case ORDWR: perm = 6; break; } t = TYPE(c->qid); switch(t) { default: panic("tlsopen"); case Qtopdir: case Qprotodir: case Qconvdir: if(omode != OREAD) error(Eperm); break; case Qclonus: tr = newtls(c); if(tr == nil) error(Enodev); break; case Qctl: case Qdata: case Qhand: case Qstatus: case Qstats: if((t == Qstatus || t == Qstats) && omode != OREAD) error(Eperm); if(waserror()) { unlock(&tdlock); nexterror(); } lock(&tdlock); pp = &tlsdevs[CONV(c->qid)]; tr = *pp; if(tr == nil) error("must open connection using clone"); if((perm & (tr->perm>>6)) != perm && (strcmp(up->user, tr->user) != 0 || (perm & tr->perm) != perm)) error(Eperm); if(t == Qhand){ if(waserror()){ unlock(&tr->hqlock); nexterror(); } lock(&tr->hqlock); if(tr->handq != nil) error(Einuse); tr->handq = qopen(2 * MaxCipherRecLen, 0, nil, nil); if(tr->handq == nil) error("cannot allocate handshake queue"); tr->hqref = 1; unlock(&tr->hqlock); poperror(); } tr->ref++; unlock(&tdlock); poperror(); break; case Qencalgs: case Qhashalgs: if(omode != OREAD) error(Eperm); break; } c->mode = openmode(omode); c->flag |= COPEN; c->offset = 0; c->iounit = qiomaxatomic; return c; } static int tlswstat(Chan *c, uchar *dp, int n) { Dir *d; TlsRec *tr; int rv; d = nil; if(waserror()){ free(d); unlock(&tdlock); nexterror(); } lock(&tdlock); tr = tlsdevs[CONV(c->qid)]; if(tr == nil) error(Ebadusefd); if(strcmp(tr->user, up->user) != 0) error(Eperm); d = smalloc(n + sizeof *d); rv = convM2D(dp, n, &d[0], (char*) &d[1]); if(rv == 0) error(Eshortstat); if(!emptystr(d->uid)) kstrdup(&tr->user, d->uid); if(d->mode != ~0UL) tr->perm = d->mode; free(d); poperror(); unlock(&tdlock); return rv; } static void dechandq(TlsRec *tr) { lock(&tr->hqlock); if(--tr->hqref == 0){ if(tr->handq != nil){ qfree(tr->handq); tr->handq = nil; } if(tr->hprocessed != nil){ freeb(tr->hprocessed); tr->hprocessed = nil; } } unlock(&tr->hqlock); } static void tlsclose(Chan *c) { TlsRec *tr; int t; t = TYPE(c->qid); switch(t) { case Qctl: case Qdata: case Qhand: case Qstatus: case Qstats: if((c->flag & COPEN) == 0) break; tr = tlsdevs[CONV(c->qid)]; if(tr == nil) break; if(t == Qhand) dechandq(tr); lock(&tdlock); if(--tr->ref > 0) { unlock(&tdlock); return; } tlsdevs[CONV(c->qid)] = nil; unlock(&tdlock); if(tr->c != nil && !waserror()){ checkstate(tr, 0, SOpen|SHandshake|SRClose); sendAlert(tr, ECloseNotify); poperror(); } tlshangup(tr); if(tr->c != nil) cclose(tr->c); freeSec(tr->in.sec); freeSec(tr->in.new); freeSec(tr->out.sec); freeSec(tr->out.new); free(tr->user); free(tr); break; } } /* * make sure we have at least 'n' bytes in list 'l' */ static void ensure(TlsRec *s, Block **l, int n) { int sofar, i; Block *b, *bl; sofar = 0; for(b = *l; b; b = b->next){ sofar += BLEN(b); if(sofar >= n) return; l = &b->next; } while(sofar < n){ bl = devtab[s->c->type]->bread(s->c, MaxCipherRecLen + RecHdrLen, 0); if(bl == 0) error(Ehungup); *l = bl; i = 0; for(b = bl; b; b = b->next){ i += BLEN(b); l = &b->next; } if(i == 0) error(Ehungup); sofar += i; } if(s->debug) pprint("ensure read %d\n", sofar); } /* * copy 'n' bytes from 'l' into 'p' and free * the bytes in 'l' */ static void consume(Block **l, uchar *p, int n) { Block *b; int i; for(; *l && n > 0; n -= i){ b = *l; i = BLEN(b); if(i > n) i = n; memmove(p, b->rp, i); b->rp += i; p += i; if(BLEN(b) < 0) panic("consume"); if(BLEN(b)) break; *l = b->next; freeb(b); } } /* * give back n bytes */ static void regurgitate(TlsRec *s, uchar *p, int n) { Block *b; if(n <= 0) return; b = s->unprocessed; if(s->unprocessed == nil || b->rp - b->base < n) { b = allocb(n); memmove(b->wp, p, n); b->wp += n; b->next = s->unprocessed; s->unprocessed = b; } else { b->rp -= n; memmove(b->rp, p, n); } } /* * remove at most n bytes from the queue */ static Block* qgrab(Block **l, int n) { Block *bb, *b; int i; b = *l; if(BLEN(b) == n){ *l = b->next; b->next = nil; return b; } i = 0; for(bb = b; bb != nil && i < n; bb = bb->next) i += BLEN(bb); if(i > n) i = n; bb = allocb(i); consume(l, bb->wp, i); bb->wp += i; return bb; } static void tlsclosed(TlsRec *tr, int new) { lock(&tr->statelk); if(tr->state == SOpen || tr->state == SHandshake) tr->state = new; else if((new | tr->state) == (SRClose|SLClose)) tr->state = SClosed; unlock(&tr->statelk); alertHand(tr, "close notify"); } /* * read and process one tls record layer message * must be called with tr->in.io held * We can't let Eintrs lose data, since doing so will get * us out of sync with the sender and break the reliablity * of the channel. Eintr only happens during the reads in * consume. Therefore we put back any bytes consumed before * the last call to ensure. */ static void tlsrecread(TlsRec *tr) { OneWay *volatile in; Block *volatile b; uchar *p, seq[8], header[RecHdrLen], hmac[MD5dlen]; int volatile nconsumed; int len, type, ver, unpad_len; nconsumed = 0; if(waserror()){ if(strcmp(up->errstr, Eintr) == 0 && !waserror()){ regurgitate(tr, header, nconsumed); poperror(); }else tlsError(tr, "channel error"); nexterror(); } ensure(tr, &tr->unprocessed, RecHdrLen); consume(&tr->unprocessed, header, RecHdrLen); if(tr->debug)pprint("consumed %d header\n", RecHdrLen); nconsumed = RecHdrLen; if((tr->handin == 0) && (header[0] & 0x80)){ /* Cope with an SSL3 ClientHello expressed in SSL2 record format. This is sent by some clients that we must interoperate with, such as Java's JSSE and Microsoft's Internet Explorer. */ len = (get16(header) & ~0x8000) - 3; type = header[2]; ver = get16(header + 3); if(type != SSL2ClientHello || len < 22) rcvError(tr, EProtocolVersion, "invalid initial SSL2-like message"); }else{ /* normal SSL3 record format */ type = header[0]; ver = get16(header+1); len = get16(header+3); } if(ver != tr->version && (tr->verset || ver < MinProtoVersion || ver > MaxProtoVersion)) rcvError(tr, EProtocolVersion, "devtls expected ver=%x%s, saw (len=%d) type=%x ver=%x '%.12s'", tr->version, tr->verset?"/set":"", len, type, ver, (char*)header); if(len > MaxCipherRecLen || len < 0) rcvError(tr, ERecordOverflow, "record message too long %d", len); ensure(tr, &tr->unprocessed, len); nconsumed = 0; poperror(); /* * If an Eintr happens after this, we'll get out of sync. * Make sure nothing we call can sleep. * Errors are ok, as they kill the connection. * Luckily, allocb won't sleep, it'll just error out. */ b = nil; if(waserror()){ if(b != nil) freeb(b); tlsError(tr, "channel error"); nexterror(); } b = qgrab(&tr->unprocessed, len); if(tr->debug) pprint("consumed unprocessed %d\n", len); in = &tr->in; if(waserror()){ qunlock(&in->seclock); nexterror(); } qlock(&in->seclock); p = b->rp; if(in->sec != nil) { /* to avoid Canvel-Hiltgen-Vaudenay-Vuagnoux attack, all errors here should look alike, including timing of the response. */ unpad_len = (*in->sec->dec)(in->sec, p, len); if(unpad_len >= in->sec->maclen) len = unpad_len - in->sec->maclen; if(tr->debug) pprint("decrypted %d\n", unpad_len); if(tr->debug) pdump(unpad_len, p, "decrypted:"); /* update length */ put16(header+3, len); put64(seq, in->seq); in->seq++; (*tr->packMac)(in->sec, in->sec->mackey, seq, header, p, len, hmac); if(unpad_len < in->sec->maclen) rcvError(tr, EBadRecordMac, "short record mac"); if(memcmp(hmac, p+len, in->sec->maclen) != 0) rcvError(tr, EBadRecordMac, "record mac mismatch"); b->wp = b->rp + len; } qunlock(&in->seclock); poperror(); if(len < 0) rcvError(tr, EDecodeError, "runt record message"); switch(type) { default: rcvError(tr, EIllegalParameter, "invalid record message 0x%x", type); break; case RChangeCipherSpec: if(len != 1 || p[0] != 1) rcvError(tr, EDecodeError, "invalid change cipher spec"); qlock(&in->seclock); if(in->new == nil){ qunlock(&in->seclock); rcvError(tr, EUnexpectedMessage, "unexpected change cipher spec"); } freeSec(in->sec); in->sec = in->new; in->new = nil; in->seq = 0; qunlock(&in->seclock); break; case RAlert: if(len != 2) rcvError(tr, EDecodeError, "invalid alert"); if(p[0] == 2) rcvAlert(tr, p[1]); if(p[0] != 1) rcvError(tr, EIllegalParameter, "invalid alert fatal code"); /* * propate non-fatal alerts to handshaker */ if(p[1] == ECloseNotify) { tlsclosed(tr, SRClose); if(tr->opened) error("tls hungup"); error("close notify"); } if(p[1] == ENoRenegotiation) alertHand(tr, "no renegotiation"); else if(p[1] == EUserCanceled) alertHand(tr, "handshake canceled by user"); else rcvError(tr, EIllegalParameter, "invalid alert code"); break; case RHandshake: /* * don't worry about dropping the block * qbwrite always queues even if flow controlled and interrupted. * * if there isn't any handshaker, ignore the request, * but notify the other side we are doing so. */ lock(&tr->hqlock); if(tr->handq != nil){ tr->hqref++; unlock(&tr->hqlock); if(waserror()){ dechandq(tr); nexterror(); } b = padblock(b, 1); *b->rp = RHandshake; qbwrite(tr->handq, b); b = nil; poperror(); dechandq(tr); }else{ unlock(&tr->hqlock); if(tr->verset && tr->version != SSL3Version && !waserror()){ sendAlert(tr, ENoRenegotiation); poperror(); } } break; case SSL2ClientHello: lock(&tr->hqlock); if(tr->handq != nil){ tr->hqref++; unlock(&tr->hqlock); if(waserror()){ dechandq(tr); nexterror(); } /* Pass the SSL2 format data, so that the handshake code can compute the correct checksums. HSSL2ClientHello = HandshakeType 9 is unused in RFC2246. */ b = padblock(b, 8); b->rp[0] = RHandshake; b->rp[1] = HSSL2ClientHello; put24(&b->rp[2], len+3); b->rp[5] = SSL2ClientHello; put16(&b->rp[6], ver); qbwrite(tr->handq, b); b = nil; poperror(); dechandq(tr); }else{ unlock(&tr->hqlock); if(tr->verset && tr->version != SSL3Version && !waserror()){ sendAlert(tr, ENoRenegotiation); poperror(); } } break; case RApplication: if(!tr->opened) rcvError(tr, EUnexpectedMessage, "application message received before handshake completed"); if(BLEN(b) > 0){ tr->processed = b; b = nil; } break; } if(b != nil) freeb(b); poperror(); } /* * got a fatal alert message */ static void rcvAlert(TlsRec *tr, int err) { char *s; int i; s = "unknown error"; for(i=0; i < nelem(tlserrs); i++){ if(tlserrs[i].err == err){ s = tlserrs[i].msg; break; } } if(tr->debug) pprint("rcvAlert: %s\n", s); tlsError(tr, s); if(!tr->opened) error(s); error("tls error"); } /* * found an error while decoding the input stream */ static void rcvError(TlsRec *tr, int err, char *fmt, ...) { char msg[ERRMAX]; va_list arg; va_start(arg, fmt); vseprint(msg, msg+sizeof(msg), fmt, arg); va_end(arg); if(tr->debug) pprint("rcvError: %s\n", msg); sendAlert(tr, err); if(!tr->opened) error(msg); error("tls error"); } /* * make sure the next hand operation returns with a 'msg' error */ static void alertHand(TlsRec *tr, char *msg) { Block *b; int n; lock(&tr->hqlock); if(tr->handq == nil){ unlock(&tr->hqlock); return; } tr->hqref++; unlock(&tr->hqlock); n = strlen(msg); if(waserror()){ dechandq(tr); nexterror(); } b = allocb(n + 2); *b->wp++ = RAlert; memmove(b->wp, msg, n + 1); b->wp += n + 1; qbwrite(tr->handq, b); poperror(); dechandq(tr); } static void checkstate(TlsRec *tr, int ishand, int ok) { int state; lock(&tr->statelk); state = tr->state; unlock(&tr->statelk); if(state & ok) return; switch(state){ case SHandshake: case SOpen: break; case SError: case SAlert: if(ishand) error(tr->err); error("tls error"); case SRClose: case SLClose: case SClosed: error("tls hungup"); } error("tls improperly configured"); } static Block* tlsbread(Chan *c, long n, ulong offset) { int ty; Block *b; TlsRec *volatile tr; ty = TYPE(c->qid); switch(ty) { default: return devbread(c, n, offset); case Qhand: case Qdata: break; } tr = tlsdevs[CONV(c->qid)]; if(tr == nil) panic("tlsbread"); if(waserror()){ qunlock(&tr->in.io); nexterror(); } qlock(&tr->in.io); if(ty == Qdata){ checkstate(tr, 0, SOpen); while(tr->processed == nil) tlsrecread(tr); /* return at most what was asked for */ b = qgrab(&tr->processed, n); if(tr->debug) pprint("consumed processed %d\n", BLEN(b)); if(tr->debug) pdump(BLEN(b), b->rp, "consumed:"); qunlock(&tr->in.io); poperror(); tr->datain += BLEN(b); }else{ checkstate(tr, 1, SOpen|SHandshake|SLClose); /* * it's ok to look at state without the lock * since it only protects reading records, * and we have that tr->in.io held. */ while(!tr->opened && tr->hprocessed == nil && !qcanread(tr->handq)) tlsrecread(tr); qunlock(&tr->in.io); poperror(); if(waserror()){ qunlock(&tr->hqread); nexterror(); } qlock(&tr->hqread); if(tr->hprocessed == nil){ b = qbread(tr->handq, MaxRecLen + 1); if(*b->rp++ == RAlert){ kstrcpy(up->errstr, (char*)b->rp, ERRMAX); freeb(b); nexterror(); } tr->hprocessed = b; } b = qgrab(&tr->hprocessed, n); poperror(); qunlock(&tr->hqread); tr->handin += BLEN(b); } return b; } static long tlsread(Chan *c, void *a, long n, vlong off) { Block *volatile b; Block *nb; uchar *va; int i, ty; char *buf, *s, *e; ulong offset = off; TlsRec * tr; if(c->qid.type & QTDIR) return devdirread(c, a, n, 0, 0, tlsgen); tr = tlsdevs[CONV(c->qid)]; ty = TYPE(c->qid); switch(ty) { default: error(Ebadusefd); case Qstatus: buf = smalloc(Statlen); qlock(&tr->in.seclock); qlock(&tr->out.seclock); s = buf; e = buf + Statlen; s = seprint(s, e, "State: %s\n", tlsstate(tr->state)); s = seprint(s, e, "Version: 0x%x\n", tr->version); if(tr->in.sec != nil) s = seprint(s, e, "EncIn: %s\nHashIn: %s\n", tr->in.sec->encalg, tr->in.sec->hashalg); if(tr->in.new != nil) s = seprint(s, e, "NewEncIn: %s\nNewHashIn: %s\n", tr->in.new->encalg, tr->in.new->hashalg); if(tr->out.sec != nil) s = seprint(s, e, "EncOut: %s\nHashOut: %s\n", tr->out.sec->encalg, tr->out.sec->hashalg); if(tr->out.new != nil) seprint(s, e, "NewEncOut: %s\nNewHashOut: %s\n", tr->out.new->encalg, tr->out.new->hashalg); qunlock(&tr->in.seclock); qunlock(&tr->out.seclock); n = readstr(offset, a, n, buf); free(buf); return n; case Qstats: buf = smalloc(Statlen); s = buf; e = buf + Statlen; s = seprint(s, e, "DataIn: %lld\n", tr->datain); s = seprint(s, e, "DataOut: %lld\n", tr->dataout); s = seprint(s, e, "HandIn: %lld\n", tr->handin); seprint(s, e, "HandOut: %lld\n", tr->handout); n = readstr(offset, a, n, buf); free(buf); return n; case Qctl: buf = smalloc(Statlen); snprint(buf, Statlen, "%llud", CONV(c->qid)); n = readstr(offset, a, n, buf); free(buf); return n; case Qdata: case Qhand: b = tlsbread(c, n, offset); break; case Qencalgs: return readstr(offset, a, n, encalgs); case Qhashalgs: return readstr(offset, a, n, hashalgs); } if(waserror()){ freeblist(b); nexterror(); } n = 0; va = a; for(nb = b; nb; nb = nb->next){ i = BLEN(nb); memmove(va+n, nb->rp, i); n += i; } freeblist(b); poperror(); return n; } /* * write a block in tls records */ static void tlsrecwrite(TlsRec *tr, int type, Block *b) { Block *volatile bb; Block *nb; uchar *p, seq[8]; OneWay *volatile out; int n, maclen, pad, ok; out = &tr->out; bb = b; if(waserror()){ qunlock(&out->io); if(bb != nil) freeb(bb); nexterror(); } qlock(&out->io); if(tr->debug)pprint("send %d\n", BLEN(b)); if(tr->debug)pdump(BLEN(b), b->rp, "sent:"); ok = SHandshake|SOpen|SRClose; if(type == RAlert) ok |= SAlert; while(bb != nil){ checkstate(tr, type != RApplication, ok); /* * get at most one maximal record's input, * with padding on the front for header and * back for mac and maximal block padding. */ if(waserror()){ qunlock(&out->seclock); nexterror(); } qlock(&out->seclock); maclen = 0; pad = 0; if(out->sec != nil){ maclen = out->sec->maclen; pad = maclen + out->sec->block; } n = BLEN(bb); if(n > MaxRecLen){ n = MaxRecLen; nb = allocb(n + pad + RecHdrLen); memmove(nb->wp + RecHdrLen, bb->rp, n); bb->rp += n; }else{ /* * carefully reuse bb so it will get freed if we're out of memory */ bb = padblock(bb, RecHdrLen); if(pad) nb = padblock(bb, -pad); else nb = bb; bb = nil; } p = nb->rp; p[0] = type; put16(p+1, tr->version); put16(p+3, n); if(out->sec != nil){ put64(seq, out->seq); out->seq++; (*tr->packMac)(out->sec, out->sec->mackey, seq, p, p + RecHdrLen, n, p + RecHdrLen + n); n += maclen; /* encrypt */ n = (*out->sec->enc)(out->sec, p + RecHdrLen, n); nb->wp = p + RecHdrLen + n; /* update length */ put16(p+3, n); } if(type == RChangeCipherSpec){ if(out->new == nil) error("change cipher without a new cipher"); freeSec(out->sec); out->sec = out->new; out->new = nil; out->seq = 0; } qunlock(&out->seclock); poperror(); /* * if bwrite error's, we assume the block is queued. * if not, we're out of sync with the receiver and will not recover. */ if(waserror()){ if(strcmp(up->errstr, "interrupted") != 0) tlsError(tr, "channel error"); nexterror(); } devtab[tr->c->type]->bwrite(tr->c, nb, 0); poperror(); } qunlock(&out->io); poperror(); } static long tlsbwrite(Chan *c, Block *b, ulong offset) { int ty; ulong n; TlsRec *tr; n = BLEN(b); tr = tlsdevs[CONV(c->qid)]; if(tr == nil) panic("tlsbread"); ty = TYPE(c->qid); switch(ty) { default: return devbwrite(c, b, offset); case Qhand: tlsrecwrite(tr, RHandshake, b); tr->handout += n; break; case Qdata: checkstate(tr, 0, SOpen); tlsrecwrite(tr, RApplication, b); tr->dataout += n; break; } return n; } typedef struct Hashalg Hashalg; struct Hashalg { char *name; int maclen; void (*initkey)(Hashalg *, int, Secret *, uchar*); }; static void initmd5key(Hashalg *ha, int version, Secret *s, uchar *p) { s->maclen = ha->maclen; if(version == SSL3Version) s->mac = sslmac_md5; else s->mac = hmac_md5; memmove(s->mackey, p, ha->maclen); } static void initclearmac(Hashalg *, int, Secret *s, uchar *) { s->maclen = 0; s->mac = nomac; } static void initsha1key(Hashalg *ha, int version, Secret *s, uchar *p) { s->maclen = ha->maclen; if(version == SSL3Version) s->mac = sslmac_sha1; else s->mac = hmac_sha1; memmove(s->mackey, p, ha->maclen); } static Hashalg hashtab[] = { { "clear", 0, initclearmac, }, { "md5", MD5dlen, initmd5key, }, { "sha1", SHA1dlen, initsha1key, }, { 0 } }; static Hashalg* parsehashalg(char *p) { Hashalg *ha; for(ha = hashtab; ha->name; ha++) if(strcmp(p, ha->name) == 0) return ha; error("unsupported hash algorithm"); return nil; } typedef struct Encalg Encalg; struct Encalg { char *name; int keylen; int ivlen; void (*initkey)(Encalg *ea, Secret *, uchar*, uchar*); }; static void initRC4key(Encalg *ea, Secret *s, uchar *p, uchar *) { s->enckey = smalloc(sizeof(RC4state)); s->enc = rc4enc; s->dec = rc4enc; s->block = 0; setupRC4state(s->enckey, p, ea->keylen); } static void initDES3key(Encalg *, Secret *s, uchar *p, uchar *iv) { s->enckey = smalloc(sizeof(DES3state)); s->enc = des3enc; s->dec = des3dec; s->block = 8; setupDES3state(s->enckey, (uchar(*)[8])p, iv); } static void initclearenc(Encalg *, Secret *s, uchar *, uchar *) { s->enc = noenc; s->dec = noenc; s->block = 0; } static Encalg encrypttab[] = { { "clear", 0, 0, initclearenc }, { "rc4_128", 128/8, 0, initRC4key }, { "3des_ede_cbc", 3 * 8, 8, initDES3key }, { 0 } }; static Encalg* parseencalg(char *p) { Encalg *ea; for(ea = encrypttab; ea->name; ea++) if(strcmp(p, ea->name) == 0) return ea; error("unsupported encryption algorithm"); return nil; } static long tlswrite(Chan *c, void *a, long n, vlong off) { Encalg *ea; Hashalg *ha; TlsRec *volatile tr; Secret *volatile tos, *volatile toc; Block *volatile b; Cmdbuf *volatile cb; int m, ty; char *p, *e; uchar *volatile x; ulong offset = off; tr = tlsdevs[CONV(c->qid)]; if(tr == nil) panic("tlswrite"); ty = TYPE(c->qid); switch(ty){ case Qdata: case Qhand: p = a; e = p + n; do{ m = e - p; if(m > MaxRecLen) m = MaxRecLen; b = allocb(m); if(waserror()){ freeb(b); nexterror(); } memmove(b->wp, p, m); poperror(); b->wp += m; tlsbwrite(c, b, offset); p += m; }while(p < e); return n; case Qctl: break; default: error(Ebadusefd); return -1; } cb = parsecmd(a, n); if(waserror()){ free(cb); nexterror(); } if(cb->nf < 1) error("short control request"); /* mutex with operations using what we're about to change */ if(waserror()){ qunlock(&tr->in.seclock); qunlock(&tr->out.seclock); nexterror(); } qlock(&tr->in.seclock); qlock(&tr->out.seclock); if(strcmp(cb->f[0], "fd") == 0){ if(cb->nf != 3) error("usage: fd open-fd version"); if(tr->c != nil) error(Einuse); m = strtol(cb->f[2], nil, 0); if(m < MinProtoVersion || m > MaxProtoVersion) error("unsupported version"); tr->c = buftochan(cb->f[1]); tr->version = m; tlsSetState(tr, SHandshake, SClosed); }else if(strcmp(cb->f[0], "version") == 0){ if(cb->nf != 2) error("usage: version vers"); if(tr->c == nil) error("must set fd before version"); if(tr->verset) error("version already set"); m = strtol(cb->f[1], nil, 0); if(m == SSL3Version) tr->packMac = sslPackMac; else if(m == TLSVersion) tr->packMac = tlsPackMac; else error("unsupported version"); tr->verset = 1; tr->version = m; }else if(strcmp(cb->f[0], "secret") == 0){ if(cb->nf != 5) error("usage: secret hashalg encalg isclient secretdata"); if(tr->c == nil || !tr->verset) error("must set fd and version before secrets"); if(tr->in.new != nil){ freeSec(tr->in.new); tr->in.new = nil; } if(tr->out.new != nil){ freeSec(tr->out.new); tr->out.new = nil; } ha = parsehashalg(cb->f[1]); ea = parseencalg(cb->f[2]); p = cb->f[4]; m = (strlen(p)*3)/2; x = smalloc(m); tos = nil; toc = nil; if(waserror()){ freeSec(tos); freeSec(toc); free(x); nexterror(); } m = dec64(x, m, p, strlen(p)); if(m < 2 * ha->maclen + 2 * ea->keylen + 2 * ea->ivlen) error("not enough secret data provided"); tos = smalloc(sizeof(Secret)); toc = smalloc(sizeof(Secret)); if(!ha->initkey || !ea->initkey) error("misimplemented secret algorithm"); (*ha->initkey)(ha, tr->version, tos, &x[0]); (*ha->initkey)(ha, tr->version, toc, &x[ha->maclen]); (*ea->initkey)(ea, tos, &x[2 * ha->maclen], &x[2 * ha->maclen + 2 * ea->keylen]); (*ea->initkey)(ea, toc, &x[2 * ha->maclen + ea->keylen], &x[2 * ha->maclen + 2 * ea->keylen + ea->ivlen]); if(!tos->mac || !tos->enc || !tos->dec || !toc->mac || !toc->enc || !toc->dec) error("missing algorithm implementations"); if(strtol(cb->f[3], nil, 0) == 0){ tr->in.new = tos; tr->out.new = toc; }else{ tr->in.new = toc; tr->out.new = tos; } if(tr->version == SSL3Version){ toc->unpad = sslunpad; tos->unpad = sslunpad; }else{ toc->unpad = tlsunpad; tos->unpad = tlsunpad; } toc->encalg = ea->name; toc->hashalg = ha->name; tos->encalg = ea->name; tos->hashalg = ha->name; free(x); poperror(); }else if(strcmp(cb->f[0], "changecipher") == 0){ if(cb->nf != 1) error("usage: changecipher"); if(tr->out.new == nil) error("cannot change cipher spec without setting secret"); qunlock(&tr->in.seclock); qunlock(&tr->out.seclock); poperror(); free(cb); poperror(); /* * the real work is done as the message is written * so the stream is encrypted in sync. */ b = allocb(1); *b->wp++ = 1; tlsrecwrite(tr, RChangeCipherSpec, b); return n; }else if(strcmp(cb->f[0], "opened") == 0){ if(cb->nf != 1) error("usage: opened"); if(tr->in.sec == nil || tr->out.sec == nil) error("cipher must be configured before enabling data messages"); lock(&tr->statelk); if(tr->state != SHandshake && tr->state != SOpen){ unlock(&tr->statelk); error("cannot enable data messages"); } tr->state = SOpen; unlock(&tr->statelk); tr->opened = 1; }else if(strcmp(cb->f[0], "alert") == 0){ if(cb->nf != 2) error("usage: alert n"); if(tr->c == nil) error("must set fd before sending alerts"); m = strtol(cb->f[1], nil, 0); qunlock(&tr->in.seclock); qunlock(&tr->out.seclock); poperror(); free(cb); poperror(); sendAlert(tr, m); if(m == ECloseNotify) tlsclosed(tr, SLClose); return n; } else if(strcmp(cb->f[0], "debug") == 0){ if(cb->nf == 2){ if(strcmp(cb->f[1], "on") == 0) tr->debug = 1; else tr->debug = 0; } else tr->debug = 1; } else error(Ebadarg); qunlock(&tr->in.seclock); qunlock(&tr->out.seclock); poperror(); free(cb); poperror(); return n; } static void tlsinit(void) { struct Encalg *e; struct Hashalg *h; int n; char *cp; static int already; if(!already){ fmtinstall('H', encodefmt); already = 1; } tlsdevs = smalloc(sizeof(TlsRec*) * maxtlsdevs); trnames = smalloc((sizeof *trnames) * maxtlsdevs); n = 1; for(e = encrypttab; e->name != nil; e++) n += strlen(e->name) + 1; cp = encalgs = smalloc(n); for(e = encrypttab;;){ strcpy(cp, e->name); cp += strlen(e->name); e++; if(e->name == nil) break; *cp++ = ' '; } *cp = 0; n = 1; for(h = hashtab; h->name != nil; h++) n += strlen(h->name) + 1; cp = hashalgs = smalloc(n); for(h = hashtab;;){ strcpy(cp, h->name); cp += strlen(h->name); h++; if(h->name == nil) break; *cp++ = ' '; } *cp = 0; } Dev tlsdevtab = { 'a', "tls", devreset, tlsinit, devshutdown, tlsattach, tlswalk, tlsstat, tlsopen, devcreate, tlsclose, tlsread, tlsbread, tlswrite, tlsbwrite, devremove, tlswstat, }; /* get channel associated with an fd */ static Chan* buftochan(char *p) { Chan *c; int fd; if(p == 0) error(Ebadarg); fd = strtoul(p, 0, 0); if(fd < 0) error(Ebadarg); c = fdtochan(fd, -1, 0, 1); /* error check and inc ref */ return c; } static void sendAlert(TlsRec *tr, int err) { Block *b; int i, fatal; char *msg; if(tr->debug)pprint("sendAlert %d\n", err); fatal = 1; msg = "tls unknown alert"; for(i=0; i < nelem(tlserrs); i++) { if(tlserrs[i].err == err) { msg = tlserrs[i].msg; if(tr->version == SSL3Version) err = tlserrs[i].sslerr; else err = tlserrs[i].tlserr; fatal = tlserrs[i].fatal; break; } } if(!waserror()){ b = allocb(2); *b->wp++ = fatal + 1; *b->wp++ = err; if(fatal) tlsSetState(tr, SAlert, SOpen|SHandshake|SRClose); tlsrecwrite(tr, RAlert, b); poperror(); } if(fatal) tlsError(tr, msg); } static void tlsError(TlsRec *tr, char *msg) { int s; if(tr->debug)pprint("tleError %s\n", msg); lock(&tr->statelk); s = tr->state; tr->state = SError; if(s != SError){ strncpy(tr->err, msg, ERRMAX - 1); tr->err[ERRMAX - 1] = '\0'; } unlock(&tr->statelk); if(s != SError) alertHand(tr, msg); } static void tlsSetState(TlsRec *tr, int new, int old) { lock(&tr->statelk); if(tr->state & old) tr->state = new; unlock(&tr->statelk); } /* hand up a digest connection */ static void tlshangup(TlsRec *tr) { Block *b; qlock(&tr->in.io); for(b = tr->processed; b; b = tr->processed){ tr->processed = b->next; freeb(b); } if(tr->unprocessed != nil){ freeb(tr->unprocessed); tr->unprocessed = nil; } qunlock(&tr->in.io); tlsSetState(tr, SClosed, ~0); } static TlsRec* newtls(Chan *ch) { TlsRec **pp, **ep, **np; char **nmp; int t, newmax; if(waserror()) { unlock(&tdlock); nexterror(); } lock(&tdlock); ep = &tlsdevs[maxtlsdevs]; for(pp = tlsdevs; pp < ep; pp++) if(*pp == nil) break; if(pp >= ep) { if(maxtlsdevs >= MaxTlsDevs) { unlock(&tdlock); poperror(); return nil; } newmax = 2 * maxtlsdevs; if(newmax > MaxTlsDevs) newmax = MaxTlsDevs; np = smalloc(sizeof(TlsRec*) * newmax); memmove(np, tlsdevs, sizeof(TlsRec*) * maxtlsdevs); tlsdevs = np; pp = &tlsdevs[maxtlsdevs]; memset(pp, 0, sizeof(TlsRec*)*(newmax - maxtlsdevs)); nmp = smalloc(sizeof *nmp * newmax); memmove(nmp, trnames, sizeof *nmp * maxtlsdevs); trnames = nmp; maxtlsdevs = newmax; } *pp = mktlsrec(); if(pp - tlsdevs >= tdhiwat) tdhiwat++; t = TYPE(ch->qid); if(t == Qclonus) t = Qctl; ch->qid.path = QID(pp - tlsdevs, t); ch->qid.vers = 0; unlock(&tdlock); poperror(); return *pp; } static TlsRec * mktlsrec(void) { TlsRec *tr; tr = mallocz(sizeof(*tr), 1); if(tr == nil) error(Enomem); tr->state = SClosed; tr->ref = 1; kstrdup(&tr->user, up->user); tr->perm = 0660; return tr; } static char* tlsstate(int s) { switch(s){ case SHandshake: return "Handshaking"; case SOpen: return "Established"; case SRClose: return "RemoteClosed"; case SLClose: return "LocalClosed"; case SAlert: return "Alerting"; case SError: return "Errored"; case SClosed: return "Closed"; } return "Unknown"; } static void freeSec(Secret *s) { if(s != nil){ free(s->enckey); free(s); } } static int noenc(Secret *, uchar *, int n) { return n; } static int rc4enc(Secret *sec, uchar *buf, int n) { rc4(sec->enckey, buf, n); return n; } static int tlsunpad(uchar *buf, int n, int block) { int pad, nn; pad = buf[n - 1]; nn = n - 1 - pad; if(nn <= 0 || n % block) return -1; while(--n > nn) if(pad != buf[n - 1]) return -1; return nn; } static int sslunpad(uchar *buf, int n, int block) { int pad, nn; pad = buf[n - 1]; nn = n - 1 - pad; if(nn <= 0 || n % block) return -1; return nn; } static int blockpad(uchar *buf, int n, int block) { int pad, nn; nn = n + block; nn -= nn % block; pad = nn - (n + 1); while(n < nn) buf[n++] = pad; return nn; } static int des3enc(Secret *sec, uchar *buf, int n) { n = blockpad(buf, n, 8); des3CBCencrypt(buf, n, sec->enckey); return n; } static int des3dec(Secret *sec, uchar *buf, int n) { des3CBCdecrypt(buf, n, sec->enckey); return (*sec->unpad)(buf, n, 8); } static DigestState* nomac(uchar *, ulong, uchar *, ulong, uchar *, DigestState *) { return nil; } /* * sslmac: mac calculations for ssl 3.0 only; tls 1.0 uses the standard hmac. */ static DigestState* sslmac_x(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s, DigestState*(*x)(uchar*, ulong, uchar*, DigestState*), int xlen, int padlen) { int i; uchar pad[48], innerdigest[20]; if(xlen > sizeof(innerdigest) || padlen > sizeof(pad)) return nil; if(klen>64) return nil; /* first time through */ if(s == nil){ for(i=0; imac)(buf, 11, mackey, sec->maclen, 0, 0); (*sec->mac)(body, len, mackey, sec->maclen, mac, s); } static void tlsPackMac(Secret *sec, uchar *mackey, uchar *seq, uchar *header, uchar *body, int len, uchar *mac) { DigestState *s; uchar buf[13]; memmove(buf, seq, 8); memmove(&buf[8], header, 5); s = (*sec->mac)(buf, 13, mackey, sec->maclen, 0, 0); (*sec->mac)(body, len, mackey, sec->maclen, mac, s); } static void put32(uchar *p, u32int x) { p[0] = x>>24; p[1] = x>>16; p[2] = x>>8; p[3] = x; } static void put64(uchar *p, vlong x) { put32(p, (u32int)(x >> 32)); put32(p+4, (u32int)x); } static void put24(uchar *p, int x) { p[0] = x>>16; p[1] = x>>8; p[2] = x; } static void put16(uchar *p, int x) { p[0] = x>>8; p[1] = x; } static u32int get32(uchar *p) { return (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3]; } static int get16(uchar *p) { return (p[0]<<8)|p[1]; } static char *charmap = "0123456789abcdef"; static void pdump(int len, void *a, char *tag) { uchar *p; int i; char buf[65+32]; char *q; p = a; strcpy(buf, tag); while(len > 0){ q = buf + strlen(tag); for(i = 0; len > 0 && i < 32; i++){ if(*p >= ' ' && *p < 0x7f){ *q++ = ' '; *q++ = *p; } else { *q++ = charmap[*p>>4]; *q++ = charmap[*p & 0xf]; } len--; p++; } *q = 0; if(len > 0) pprint("%s...\n", buf); else pprint("%s\n", buf); } }