ref: baf602582a840a0253900d41a2f1cce7156dd770
dir: /x224.c/
#include <u.h> #include <libc.h> #include <draw.h> #include <mp.h> #include <libsec.h> #include "dat.h" #include "fns.h" enum { /* X.224 PDU codes */ ConReq= 0xE0, /* connection request */ ConCfrm= 0xD0, /* connection confirm */ HupReq= 0x80, /* disconnection request */ Data= 0xF0, /* data */ Err= 0x70, /* error */ /* Rdpnego.type */ Tnego= 1, Rnego= 2, /* Rdpnego.proto */ ProtoTLS= 1, ProtoCSSP= 2, ProtoUAUTH= 8, }; struct Rdpnego { int type; int flags; int proto; }; int getnego(Rdpnego*, uchar*, uint); int putnego(uchar*, uint, Rdpnego*); /* * examine a packet header at between p and ep * returns 1 if it's a TPKT-encapsulated TPDU (T.123 clause 8; RFC 1006) * returns 0 if not - likely a Fast-Path Update PDU ([MS-RDPBCGR] 5.3.8 and 5.4.4) */ int istpkt(uchar* p, uchar* ep) { int magic; if(p+1>ep){ werrstr(Eshort); return -1; } magic = p[0]; return (magic == 3); } int tpdutype(uchar* p, uchar* ep) { if(p+5 >= ep){ werrstr(Eshort); return -1; } return p[5]; } int isdatatpdu(uchar* p, uchar* ep) { return (tpdutype(p,ep) == Data); } /* * read a PDU: either TPKT-encapsulated TPDU or Fast-Path Update PDU */ int readpdu(int fd, uchar *buf, uint nbuf) { int n, len; uchar *p; p = buf; n = readn(fd, p, TPKTFIXLEN); if(n != TPKTFIXLEN){ werrstr("short read: %r"); return -1; } switch(istpkt(p, p+n)){ case -1: return -1; case 0: /* Fast-Path Update PDU */ len = p[1]; if(len&(1<<7)) len = ((len^(1<<7))<<8) | p[2]; break; default: /* TPKT-encapsulated TPDU */ len = GSHORTB(p+2); } if(len <= n || len > nbuf){ werrstr("bad length in PDU header: %d", len); return -1; } n += readn(fd, p+n, len-n); if(n != len) return -1; return n; } uchar* tpdupayload(uchar* p, uchar* ep) { uchar* q; if(istpkt(p, ep) == 0){ werrstr("Fast-Path Update PDU is not expected"); return nil; } if(tpdutype(p,ep) == Data) q = p+7; else q = p+11; if(q > ep){ werrstr(Eshort); return nil; } return q; } /* connect request */ int mktpcr(uchar* buf, int nbuf, int ndata) { int size; uchar *p; p = buf; size = TPKTFIXLEN+7+ndata; if(size > nbuf){ werrstr(Esmall); return -1; } /* TPKT header: version[1] unused[1] len[2] */ p[0] = 0x03; p[1] = 0; PSHORTB(p+2, size); /* ConReq: hdlen[1] type[1] dstref[2] srcref[2] class[1] */ p[4+0] = 7-1+ndata; p[4+1] = ConReq; PSHORTB(p+4+2, 0); PSHORTB(p+4+4, 0); p[4+6] = 0; return size; } /* data transfer */ int mktpdat(uchar* buf, int nbuf, int ndata) { int size; uchar *p; p = buf; size = TPDATAFIXLEN+ndata; if(size > nbuf){ werrstr("buffer too small: provided %d need %d", nbuf, size); return -1; } /* TPKT header: version[1] unused[1] len[2] */ p[0] = 0x03; p[1] = 0; PSHORTB(p+2, size); /* TPDU: hdlen[1] type[1] seqno[1] */ p[4] = 2; p[5] = Data; p[6] = (1<<7); /* seqno (0 in Class 0) + EOT mark (1<<7) */ return size; } /* disconnection request */ int mktpdr(uchar* buf, int nbuf, int ndata) { int size; uchar *p; p = buf; size = TPDATAFIXLEN+ndata; if(size > nbuf){ werrstr("buffer too small"); return -1; } /* TPKT header: version[1] unused[1] len[2] */ p[0] = 0x03; p[1] = 0; PSHORTB(p+2, size); /* HupReq: hdlen[1] type[1] seqno[1] */ p[4] = 2; p[5] = HupReq; p[6] = (1<<7); /* seqno (0 in Class 0) + EOT mark (1<<7) */ return size; } int putnego(uchar* b, uint nb, Rdpnego* m) { int len; len = 8; if(nb < 8){ werrstr(Esmall); return -1; } b[0] = m->type; b[1] = m->flags; PSHORT(b+2, len); PLONG(b+4, m->proto); return len; } int getnego(Rdpnego* m, uchar* b, uint nb) { int len; if(nb < 8){ werrstr(Eshort); return -1; } m->type = b[0]; m->flags = b[1]; len = GSHORT(b+2); m->proto = GLONG(b+4); if(len != 8){ werrstr("bad length in RDP Nego Response"); return -1; } return len; } int istrusted(uchar* cert, int certlen) { uchar digest[SHA1dlen]; Thumbprint *table; if(cert==nil || certlen <= 0) { werrstr("server did not provide TLS certificate"); return 0; } sha1(cert, certlen, digest, nil); table = initThumbprints("/sys/lib/tls/rdp", "/sys/lib/tls/rdp.exclude"); if(!table || !okThumbprint(digest, table)){ werrstr("server certificate %.*H not recognized", SHA1dlen, digest); return 0; } freeThumbprints(table); return 1; } /* lifted from /sys/src/cmd/upas/fs/imap4.c:/^starttls */ int starttls(void) { TLSconn conn; int sfd; fmtinstall('H', encodefmt); memset(&conn, 0, sizeof conn); sfd = tlsClient(rd.fd, &conn); if(sfd < 0){ werrstr("tlsClient: %r"); return -1; } if(!istrusted(conn.cert, conn.certlen)){ close(sfd); return -1; } close(rd.fd); rd.fd = sfd; return sfd; } /* 5.4.2.1 Negotiation-Based Approach */ int x224connect(int fd) { int n, ndata; uchar buf[4+7+25+8], *p, *ep; Rdpnego t, r; ndata = 25+8; n = mktpcr(buf, sizeof buf, ndata); if(n < 0) return -1; p = buf+n-ndata; ep = buf+n; memcpy(p, "Cookie: mstshash=eltons\r\n", 25); p += 25; t = (Rdpnego){Tnego, 0, ProtoTLS}; if(putnego(p, ep-p, &t) != 8){ werrstr("pnego failed: %r"); return -1; } if(writen(fd, buf, n) != n) return -1; n = readpdu(fd, buf, sizeof buf); if(n < 6){ werrstr("X.224: ConCfrm: %r"); return -1; } ep = buf+n; if(!istpkt(buf, ep) || tpdutype(buf, ep) != ConCfrm){ werrstr("X.224: protocol botch"); return -1; } if((p = tpdupayload(buf, ep)) == nil) return -1; if(getnego(&r, p, ep-p) < 0 || r.type != Rnego || !(r.proto&ProtoTLS)){ werrstr("server refused STARTTLS"); return -1; } fd = starttls(); if(fd < 0) return -1; rd.sproto = r.proto; return fd; } int x224disconnect(int fd) { int n, m; uchar buf[12]; n = mktpdr(buf, sizeof buf, 0); m = write(fd, buf, n); return m; }