From 63d5e56a29159cfce3f05fa1f58f2c2dc221419a Mon Sep 17 00:00:00 2001 From: David du Colombier <0intro@gmail.com> Date: Fri, 6 Apr 2001 00:00:00 +0000 Subject: [PATCH] Plan 9 from Bell Labs 2001-04-06 --- port/devtls.c | 536 +++++++++++++++++++++++++++++++------------------- 1 file changed, 338 insertions(+), 198 deletions(-) diff --git a/port/devtls.c b/port/devtls.c index a0e9278af05578a27507a3f0e05797dc337b775c..8c8943dd56334c28f15010434ae0718eee694d41 100644 --- a/port/devtls.c +++ b/port/devtls.c @@ -18,34 +18,29 @@ enum { MaxCipherRecLen = MaxRecLen + 2048, //ZZZ - Maxdstate = 64, RecHdrLen = 5, TLSVersion = 0x0301, SSL3Version = 0x0300, ProtocolVersion = 0x0301, /* maximum version we speak */ MinProtoVersion = 0x0300, /* limits on version we accept */ MaxProtoVersion = 0x03ff, -}; -/* connection states */ -enum { - SHandshake, // doing handshake - SOpen, // application data can be sent - SRemoteClosed, // remote side has closed down - SError, // some sort of error has occured - SClosed, // it is all over -}; + /* 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 */ -enum { + /* record types */ RChangeCipherSpec = 20, RAlert, RHandshake, RApplication, -}; -/* alerts */ -enum { + /* alerts */ ECloseNotify = 0, EUnexpectedMessage = 10, EBadRecordMac = 20, @@ -97,19 +92,22 @@ struct TlsRec Chan *c; /* io channel */ int ref; /* serialized by dslock for atomic destroy */ int version; /* version of the protocol we are speaking */ - int verset; /* version has been set */ + char verset; /* version has been set */ + char opened; /* opened command every issued? */ + char err[ERRLEN]; /* error message to return to handshake requests */ Lock statelk; - int state; /* must be set using setstate */ + int state; /* record layer mac functions for different protocol versions */ void (*packMac)(Secret*, uchar*, uchar*, uchar*, uchar*, int, uchar*); - /* input side -- protected by in.q */ + /* 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; int hqref; Queue *handq; /* queue of handshake messages */ @@ -133,11 +131,11 @@ struct TlsErrs{ static TlsErrs tlserrs[] = { {ECloseNotify, ECloseNotify, ECloseNotify, - 0, "remote close"}, + 0, "close notify"}, {EUnexpectedMessage, EUnexpectedMessage, EUnexpectedMessage, 1, "unexpected message"}, {EBadRecordMac, EBadRecordMac, EBadRecordMac, - 1, "bad record MAC"}, + 1, "bad record mac"}, {EDecryptionFailed, EIllegalParameter, EDecryptionFailed, 1, "decryption failed"}, {ERecordOverflow, EIllegalParameter, ERecordOverflow, @@ -179,10 +177,15 @@ static TlsErrs tlserrs[] = { {EUserCanceled, ECloseNotify, EUserCanceled, 0, "handshake canceled by user"}, {ENoRenegotiation, EUnexpectedMessage, ENoRenegotiation, - 0, "renegotiation not supported"}, + 0, "no renegotiation"}, {-1}, }; +enum +{ + Maxdstate = 64, +}; + static Lock dslock; static int dshiwat; static int maxdstate = 128; @@ -206,12 +209,15 @@ enum{ #define CONV(x) (((x).path >> 5)&(Maxdstate-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 TlsRec* dsclone(Chan *c); -static void dsnew(Chan *c, 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 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); @@ -221,7 +227,7 @@ 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 newstate); +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, ...); @@ -382,7 +388,7 @@ tlsopen(Chan *c, int omode) error(Eperm); break; case Qclonus: - tr = dsclone(c); + tr = newtls(c); if(tr == nil) error(Enodev); break; @@ -397,30 +403,28 @@ tlsopen(Chan *c, int omode) pp = &dstate[CONV(c->qid)]; tr = *pp; if(tr == nil) - dsnew(c, pp); - else { - 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); -//ZZZ what is the correct buffering here? - tr->handq = qopen(2 * MaxRecLen, 0, nil, nil); - if(tr->handq == nil) - error("can't allocate handshake queue"); - tr->hqref = 1; + 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); - poperror(); + nexterror(); } - tr->ref++; + lock(&tr->hqlock); + if(tr->handq != nil) + error(Einuse); +//ZZZ what is the correct buffering here? + tr->handq = qopen(2 * MaxRecLen, 0, nil, nil); + if(tr->handq == nil) + error("can't allocate handshake queue"); + tr->hqref = 1; + unlock(&tr->hqlock); + poperror(); } + tr->ref++; unlock(&dslock); poperror(); break; @@ -495,8 +499,13 @@ tlsclose(Chan *c) dstate[CONV(c->qid)] = nil; unlock(&dslock); + if(tr->c != nil && !waserror()){ + checkstate(tr, 0, SOpen|SHandshake|SRClose); + sendAlert(tr, ECloseNotify); + poperror(); + } tlshangup(tr); - if(tr->c) + if(tr->c != nil) cclose(tr->c); free(tr->in.sec); free(tr->in.new); @@ -638,6 +647,18 @@ qremove(Block **l, int n, int discard) return first; } +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 @@ -655,6 +676,8 @@ tlsrecread(TlsRec *tr) if(waserror()){ if(strcmp(up->error, Eintr) == 0) regurgitate(tr, header, nconsumed); + else + tlsError(tr, "channel error"); nexterror(); } ensure(tr, &tr->unprocessed, RecHdrLen); @@ -665,9 +688,7 @@ tlsrecread(TlsRec *tr) len = get16(header+3); if(ver != tr->version && (tr->verset || ver < MinProtoVersion || ver > MaxProtoVersion)) rcvError(tr, EProtocolVersion, "invalid version in record layer"); - if(len <= 0) - rcvError(tr, EIllegalParameter, "invalid length in record layer"); - if(len > MaxRecLen) + if(len > MaxRecLen || len < 0) rcvError(tr, ERecordOverflow, "record message too long"); ensure(tr, &tr->unprocessed, len); nconsumed = 0; @@ -679,14 +700,18 @@ tlsrecread(TlsRec *tr) * 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 = qremove(&tr->unprocessed, len, 0); in = &tr->in; if(waserror()){ -//ZZZ kill the connection? qunlock(&in->seclock); - if(b != nil) - freeb(b); nexterror(); } qlock(&in->seclock); @@ -715,7 +740,7 @@ tlsrecread(TlsRec *tr) switch(type) { default: rcvError(tr, EIllegalParameter, "invalid record message 0x%x", type); - return; + break; case RChangeCipherSpec: if(len != 1 || p[0] != 1) rcvError(tr, EDecodeError, "invalid change cipher spec"); @@ -733,24 +758,26 @@ tlsrecread(TlsRec *tr) case RAlert: if(len != 2) rcvError(tr, EDecodeError, "invalid alert"); - if(p[0] == 1) { - if(p[1] == ECloseNotify) { - tlsSetState(tr, SRemoteClosed); -// handclose(tr, "remote close"); - error("remote close"); - } - /* - * propate messages to handshaker - * EUserCancelled ENoRenegotiation -ZZZ better comment, better thoughts about this - */ -// if(p[1] == ENoRenegotiation) -// handclose(tr, "no renegotiation"); -// if(p[1] == EUserCancelled) -// handclose(tr, "user cancelled"); - } else { + 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: /* @@ -764,15 +791,21 @@ ZZZ better comment, better thoughts about this 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 if(tr->verset && tr->version != SSL3Version) sendAlert(tr, ENoRenegotiation); break; case RApplication: -//ZZZ race on state - if(tr->state != SOpen) + if(!tr->opened) rcvError(tr, EUnexpectedMessage, "application message received before handshake completed"); tr->processed = b; b = nil; @@ -780,6 +813,113 @@ ZZZ better comment, better thoughts about this } 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; + } + } + + 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[ERRLEN]; + va_list arg; + + va_start(arg, fmt); + doprint(msg, msg+sizeof(msg), fmt, arg); + va_end(arg); + + 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 *volatile b; + int n; + + lock(&tr->hqlock); + if(tr->handq == nil){ + unlock(&tr->hqlock); + return; + } + tr->hqref++; + unlock(&tr->hqlock); + + n = strlen(msg); + b = nil; + if(waserror()){ + if(b != nil) + freeb(b); + 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"); } /* @@ -813,9 +953,7 @@ tlsbread(Chan *c, long n, ulong offset) } qlock(&tr->in.io); if(TYPE(c->qid) == Qdata){ -//ZZZ race on state - if(tr->state != SOpen) - error(Ebadusefd); + checkstate(tr, 0, SOpen); while(tr->processed == nil) tlsrecread(tr); @@ -824,12 +962,24 @@ tlsbread(Chan *c, long n, ulong offset) qunlock(&tr->in.io); poperror(); }else{ -//ZZZ race on state - while(tr->state == SHandshake && !qcanread(tr->handq)) + 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 && !qcanread(tr->handq)) tlsrecread(tr); + qunlock(&tr->in.io); poperror(); b = qbread(tr->handq, n); + if(*b->rp++ == RAlert){ + strncpy(up->error, (char*)b->rp, ERRLEN - 1); + up->error[ERRLEN - 1] = '\0'; + error(up->error); + } } return b; @@ -904,7 +1054,7 @@ tlsrecwrite(TlsRec *tr, int type, Block *b) Block *nb; uchar *p, seq[8]; OneWay *volatile out; - int n, maclen; + int n, maclen, ok; out = &tr->out; bb = b; @@ -916,10 +1066,11 @@ tlsrecwrite(TlsRec *tr, int type, Block *b) } qlock(&out->io); + ok = SHandshake|SOpen|SRClose; + if(type == RAlert) + ok |= SAlert; while(bb != nil){ -//ZZZ race on state - if(tr->state != SHandshake && tr->state != SOpen && tr->state != SRemoteClosed) - error(Ebadusefd); + checkstate(tr, type != RApplication, ok); /* * get at most one maximal record's input, @@ -985,7 +1136,13 @@ tlsrecwrite(TlsRec *tr, int type, Block *b) * 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->error, "interrupted") != 0) + tlsError(tr, "channel error"); + nexterror(); + } devtab[tr->c->type]->bwrite(tr->c, nb, 0); + poperror(); } qunlock(&out->io); poperror(); @@ -1010,9 +1167,7 @@ tlsbwrite(Chan *c, Block *b, ulong offset) tlsrecwrite(tr, RHandshake, b); break; case Qdata: -//ZZZ race on state - if(tr->state != SOpen) - error(Ebadusefd); + checkstate(tr, 0, SOpen); tlsrecwrite(tr, RApplication, b); break; } @@ -1161,7 +1316,7 @@ tlswrite(Chan *c, void *a, long n, vlong off) if(strcmp(cb->f[0], "fd") == 0){ if(cb->nf != 3) - error("usage: fd n version"); + error("usage: fd open-fd version"); if(tr->c != nil) error(Einuse); m = strtol(cb->f[2], nil, 0); @@ -1169,10 +1324,10 @@ tlswrite(Chan *c, void *a, long n, vlong off) error("unsupported version"); tr->c = buftochan(cb->f[1]); tr->version = m; - tlsSetState(tr, SHandshake); + tlsSetState(tr, SHandshake, SClosed); }else if(strcmp(cb->f[0], "version") == 0){ if(cb->nf != 2) - error("usage: version n"); + error("usage: version vers"); if(tr->c == nil) error("must set fd before version"); if(tr->verset) @@ -1186,53 +1341,6 @@ tlswrite(Chan *c, void *a, long n, vlong off) error("unsupported version"); tr->verset = 1; tr->version = m; - }else if(strcmp(cb->f[0], "opened") == 0){ - if(cb->nf != 1) - error("usage: opened"); - lock(&tr->statelk); - if(tr->state != SHandshake && tr->state != SOpen){ - unlock(&tr->statelk); -//ZZZ bad error message - error("can't set open state"); - } - tr->state = SOpen; - unlock(&tr->statelk); - }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); - - return n; - }else if(strcmp(cb->f[0], "changecipher") == 0){ - if(cb->nf != 1) - error("usage: changecipher"); - if(tr->out.new == nil) - error("can't 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], "secret") == 0){ if(cb->nf != 5) error("usage: secret hashalg encalg isclient secretdata"); @@ -1282,6 +1390,59 @@ tlswrite(Chan *c, void *a, long n, vlong off) free(x); poperror(); + }else if(strcmp(cb->f[0], "changecipher") == 0){ + if(cb->nf != 1) + error("usage: changecipher"); + if(tr->out.new == nil) + error("can't 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 configure before enabling data messages"); + lock(&tr->statelk); + if(tr->state != SHandshake && tr->state != SOpen){ + unlock(&tr->statelk); +//ZZZ bad error message + error("can't set open state"); + } + 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 error(Ebadarg); @@ -1376,10 +1537,13 @@ sendAlert(TlsRec *tr, int err) { Block *b; int i, fatal; + char *msg; 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 @@ -1392,54 +1556,36 @@ sendAlert(TlsRec *tr, int err) b = allocb(2); *b->wp++ = fatal + 1; *b->wp++ = err; + if(fatal) + tlsSetState(tr, SAlert, SOpen|SHandshake|SRClose); tlsrecwrite(tr, RAlert, b); -//ZZZ race on state if(fatal) - tlsSetState(tr, SError); + tlsError(tr, msg); } -/* - * got a fatal alert message - */ static void -rcvAlert(TlsRec *tr, int err) +tlsError(TlsRec *tr, char *msg) { - char *s; - int i; + int s; - s = "unknown error"; - for(i=0; i < nelem(tlserrs); i++){ - if(tlserrs[i].err == err){ - s = tlserrs[i].msg; - break; - } + lock(&tr->statelk); + s = tr->state; + tr->state = SError; + if(s != SError){ + strncpy(tr->err, msg, ERRLEN - 1); + tr->err[ERRLEN - 1] = '\0'; } -//ZZZ need to kill session if fatal error -// handclose(tr, err); - tlshangup(tr); - tlsSetState(tr, SError); - error(s); -} - -static void -rcvError(TlsRec *tr, int err, char *fmt, ...) -{ - char msg[ERRLEN]; - va_list arg; - - sendAlert(tr, err); - va_start(arg, fmt); - strcpy(msg, "tls local %s"); - doprint(strchr(msg, '\0'), msg+sizeof(msg), fmt, arg); - va_end(arg); - error(msg); + unlock(&tr->statelk); + if(s != SError) + alertHand(tr, msg); } static void -tlsSetState(TlsRec *tr, int newstate) +tlsSetState(TlsRec *tr, int new, int old) { lock(&tr->statelk); - tr->state = newstate; + if(tr->state & old) + tr->state = new; unlock(&tr->statelk); } @@ -1462,14 +1608,14 @@ tlshangup(TlsRec *tr) tlsrecwrite(tr, RAlert, ECloseNotify); - tlsSetState(tr, SClosed); + tlsSetState(tr, SClosed, ~0); } static TlsRec* -dsclone(Chan *ch) +newtls(Chan *ch) { TlsRec **pp, **ep, **np; - int newmax; + int t, newmax; if(waserror()) { unlock(&dslock); @@ -1477,12 +1623,9 @@ dsclone(Chan *ch) } lock(&dslock); ep = &dstate[maxdstate]; - for(pp = dstate; pp < ep; pp++) { - if(*pp == nil) { - dsnew(ch, pp); + for(pp = dstate; pp < ep; pp++) + if(*pp == nil) break; - } - } if(pp >= ep) { if(maxdstate >= Maxdstate) { unlock(&dslock); @@ -1493,41 +1636,38 @@ dsclone(Chan *ch) if(newmax > Maxdstate) newmax = Maxdstate; np = smalloc(sizeof(TlsRec*) * newmax); - if(np == nil) - error(Enomem); memmove(np, dstate, sizeof(TlsRec*) * maxdstate); dstate = np; pp = &dstate[maxdstate]; memset(pp, 0, sizeof(TlsRec*)*(newmax - maxdstate)); maxdstate = newmax; - dsnew(ch, pp); } + *pp = mktlsrec(); + if(pp - dstate >= dshiwat) + dshiwat++; + t = TYPE(ch->qid); + if(t == Qclonus) + t = Qctl; + ch->qid.path = QID(pp - dstate, t); + ch->qid.vers = 0; unlock(&dslock); poperror(); return *pp; } -static void -dsnew(Chan *ch, TlsRec **pp) +static TlsRec * +mktlsrec(void) { - TlsRec *s; - int t; + TlsRec *tr; - *pp = s = malloc(sizeof(*s)); - if(!s) + tr = mallocz(sizeof(*tr), 1); + if(tr == nil) error(Enomem); - if(pp - dstate >= dshiwat) - dshiwat++; - memset(s, 0, sizeof(*s)); - s->state = SClosed; - s->ref = 1; - strncpy(s->user, up->user, sizeof(s->user)); - s->perm = 0660; - t = TYPE(ch->qid); - if(t == Qclonus) - t = Qctl; - ch->qid.path = QID(pp - dstate, t); - ch->qid.vers = 0; + tr->state = SClosed; + tr->ref = 1; + strncpy(tr->user, up->user, sizeof(tr->user)); + tr->perm = 0660; + return tr; } /*