shithub: rd

Download patch

ref: baf602582a840a0253900d41a2f1cce7156dd770
author: Yaroslav Kolomiiets <[email protected]>
date: Mon Mar 7 16:32:43 EST 2016

rd: initial import

--- /dev/null
+++ b/cap.c
@@ -1,0 +1,271 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+/* 2.2.7.1.1 General Capability Set (TS_GENERAL_CAPABILITYSET) */
+void
+scangencaps(uchar* p, uchar* ep)
+{
+	int extraFlags, canrefresh, cansupress;
+
+	if(p+22>ep)
+		sysfatal(Eshort);
+	extraFlags  = GSHORT(p+14);
+	USED(extraFlags);
+	canrefresh = p[22];
+	cansupress = p[23];
+	if(!canrefresh)
+		sysfatal("server lacks support for Refresh Rect PDU");
+	if(!cansupress)
+		sysfatal("server lacks support for Suppress Output PDU");
+}
+
+/* 2.2.7.1.2 Bitmap Capability Set (TS_BITMAP_CAPABILITYSET) */
+void
+scanbitcaps(uchar* p, uchar* ep)
+{
+	int w, h, depth;
+
+	if(p+16> ep)
+		sysfatal(Eshort);
+	depth = GSHORT(p+4);
+	w = GSHORT(p+12);
+	h = GSHORT(p+14);
+
+	if(depth != rd.depth){
+		rd.depth = depth;
+		switch(depth){
+		case 8:
+			rd.chan = CMAP8;
+			break;
+		case 15:
+			rd.chan = RGB15;
+			break;
+		case 16:
+			rd.chan = RGB16;
+			break;
+		case 24:
+			rd.chan = RGB24;
+			break;
+		case 32:
+			rd.chan = XRGB32;
+			break;
+		default:
+			sysfatal("Unsupported server color depth: %uhd\n", depth);
+		}
+	}
+	if(w != rd.dim.x || h != rd.dim.y){
+		rd.dim.x = w;
+		rd.dim.y = h;
+		rd.dim.x = (rd.dim.x + 3) & ~3;	/* ensure width divides by 4 */
+	}
+}
+
+/* 2.2.7.1.1 General Capability Set (TS_GENERAL_CAPABILITYSET) */
+uchar*
+putgencaps(uchar *p, uchar *ep)
+{
+	int extraFlags;
+
+	extraFlags = 0
+		| CanFastpath
+		| NoBitcomphdr
+		| CanLongcred
+	;
+
+	if(p+24>ep)
+		sysfatal(Eshort);
+	PSHORT(p+0, CapGeneral);
+	PSHORT(p+2, GENCAPSIZE);
+	PSHORT(p+4, 0);	// OSMAJORTYPE_UNSPECIFIED
+	PSHORT(p+6, 0);	// OSMINORTYPE_UNSPECIFIED
+	PSHORT(p+8, 0x200);	// TS_CAPS_PROTOCOLVERSION
+	PSHORT(p+12, 0);	// generalCompressionTypes
+	PSHORT(p+14, extraFlags);
+	PSHORT(p+16, 0);	// updateCapabilityFlag
+	PSHORT(p+18, 0);	// remoteUnshareFlag 
+	PSHORT(p+20, 0);	// generalCompressionLevel
+	p[22] = 0;  	// refreshRectSupport - server only
+	p[23] = 0;  	// suppressOutputSupport - server only
+	return p+24;
+}
+
+
+/* 2.2.7.1.2 Bitmap Capability Set (TS_BITMAP_CAPABILITYSET) */
+uchar*
+putbitcaps(uchar *p, uchar *ep)
+{
+	if(p+30>ep)
+		sysfatal(Eshort);
+	PSHORT(p+0, CapBitmap);
+	PSHORT(p+2, BITCAPSIZE);
+	PSHORT(p+4, rd.depth);	// preferredBitsPerPixel
+	PSHORT(p+6, 1);	// receive1BitPerPixel
+	PSHORT(p+8, 1);	// receive4BitsPerPixel
+	PSHORT(p+10, 1);	// receive8BitsPerPixel
+	PSHORT(p+12, rd.dim.x);	// desktopWidth
+	PSHORT(p+14, rd.dim.y);	// desktopHeight
+	PSHORT(p+16, 0);	// pad2octets 
+	PSHORT(p+18, 1);	// desktopResizeFlag 
+	PSHORT(p+20, 1);	// bitmapCompressionFlag 
+	PSHORT(p+22, 0);	// highColorFlags 
+	PSHORT(p+24, 1);	// drawingFlags 
+	PSHORT(p+26, 1);	// multipleRectangleSupport
+	PSHORT(p+26, 0);	// pad2octetsB
+	return p+30;
+}
+
+/* 2.2.7.1.3 Order Capability Set (TS_ORDER_CAPABILITYSET) */
+uchar*
+putordcaps(uchar *p, uchar *ep)
+{
+	ushort orderFlags;
+	enum
+	{
+		NEGOTIATEORDERSUPPORT=		0x02,
+		ZEROBOUNDSDELTASSUPPORT=	0x08,
+		COLORINDEXSUPPORT=			0x20,
+		SOLIDPATTERNBRUSHONLY=		0x40,
+	};
+	
+	orderFlags = 0
+		| NEGOTIATEORDERSUPPORT
+		| ZEROBOUNDSDELTASSUPPORT
+		| COLORINDEXSUPPORT
+		| SOLIDPATTERNBRUSHONLY
+	;
+	if(p+88>ep)
+		sysfatal(Eshort);
+	PSHORT(p+0, CapOrder);
+	PSHORT(p+2, ORDCAPSIZE);
+	memset(p+4, 16, 0);	// terminalDescriptor
+	PLONG(p+20, 0);	// pad4octetsA 
+	PSHORT(p+24, 1);	// desktopSaveXGranularity 
+	PSHORT(p+26, 20);	// desktopSaveYGranularity 
+	PSHORT(p+28, 0);	// pad2octetsA
+	PSHORT(p+30, 1);	// maximumOrderLevel 
+	PSHORT(p+32, 0);	// numberFonts 
+	PSHORT(p+34, orderFlags);
+	memcpy(p+36, orderSupport, 32);
+	PSHORT(p+68, 0x6a1);	// textFlags
+	PSHORT(p+70, 0);	// orderSupportExFlags
+	PLONG(p+72, 0);	// pad4octetsB
+	PLONG(p+76, 480*480);	// desktopSaveSize
+	PSHORT(p+80, 0);	// pad2octetsC
+	PSHORT(p+82, 0);	// pad2octetsD
+	PSHORT(p+84, 0xe4);	// textANSICodePage
+	PSHORT(p+86, 0x04);	// pad2octetsE
+	return p+88;
+}
+
+/* 2.2.7.1.4 Bitmap Cache Capability Set (TS_BITMAPCACHE_CAPABILITYSET) */
+/* 2.2.7.1.4.2 Revision 2 (TS_BITMAPCACHE_CAPABILITYSET_REV2) */
+uchar*
+putbc2caps(uchar *p, uchar *ep)
+{
+	if(p+40>ep)
+		sysfatal(Eshort);
+	PSHORT(p+0, CapBitcache2);
+	PSHORT(p+2, BCACAPSIZE);
+	PSHORT(p+4, 0);	// CacheFlags (2 bytes):  
+	p[6] = 0;	// pad2
+	p[7] = 3;	// NumCellCaches
+	PLONG(p+8, 120);	// BitmapCache0CellInfo
+	PLONG(p+12, 120);	// BitmapCache1CellInfo
+	PLONG(p+16, 336);	// BitmapCache2CellInfo
+	PLONG(p+20, 0);	// BitmapCache3CellInfo
+	PLONG(p+24, 0);	// BitmapCache4CellInfo
+	memset(p+28, 12, 0); // Pad3
+	return p+40;
+}
+
+/* 2.2.7.1.5 Pointer Capability Set (TS_POINTER_CAPABILITYSET) */
+uchar*
+putptrcaps(uchar *p, uchar *ep)
+{
+	if(p+8>ep)
+		sysfatal(Eshort);
+	PSHORT(p+0, CapPointer);
+	PSHORT(p+2, PTRCAPSIZE);
+	PSHORT(p+4, 0);	// colorPointerFlag  
+	PSHORT(p+6, 20);	// colorPointerCacheSize 
+	return p+8;
+}
+
+/* 2.2.7.1.6 Input Capability Set (TS_INPUT_CAPABILITYSET) */
+uchar*
+putinpcaps(uchar *p, uchar *ep)
+{
+	long inputFlags;
+	enum
+	{
+		INPUT_FLAG_SCANCODES=		0x0001,
+		INPUT_FLAG_MOUSEX=			0x0004,
+		INPUT_FLAG_FASTPATH_INPUT=	0x0008,
+		INPUT_FLAG_UNICODE=			0x0010,
+		INPUT_FLAG_FASTPATH_INPUT2=	0x0020,
+	};
+
+	inputFlags = 0
+	| INPUT_FLAG_SCANCODES
+	| INPUT_FLAG_UNICODE
+	;
+
+	if(p+88>ep)
+		sysfatal(Eshort);
+	PSHORT(p+0, CapInput);
+	PSHORT(p+2, INPCAPSIZE);
+	PSHORT(p+4, inputFlags);	// inputFlags
+	PSHORT(p+6, 0);	// pad2octetsA
+
+	// the below SHOULD be the same as in the Client Core Data (section 2.2.1.3.2).
+	PLONG(p+8, 0x409);	// keyboardLayout
+	PLONG(p+12, 4);	// keyboardType: IBM enhanced (101- or 102-key)
+	PLONG(p+16, 0);	// keyboardSubType
+	PLONG(p+20, 12);	// keyboardFunctionKey
+	memset(p+24, 64, 0);	// imeFileName
+	return p+88;
+}
+
+/* 2.2.7.1.8 Glyph Cache Capability Set (TS_GLYPHCACHE_CAPABILITYSET) */
+uchar*
+putglycaps(uchar* p, uchar* ep)
+{
+	enum {
+		GLYPH_SUPPORT_NONE= 0,
+	};
+
+	if(p+52>ep)
+		sysfatal(Eshort);
+	PSHORT(p+0, CapGlyph);
+	PSHORT(p+2, GLYCAPSIZE);
+	PLONG(p+4, 0x0400fe);	// GlyphCache 0
+	PLONG(p+8, 0x0400fe);	// GlyphCache 1
+	PLONG(p+12, 0x0800fe);	// GlyphCache 2
+	PLONG(p+16, 0x0800fe);	// GlyphCache 3
+	PLONG(p+20, 0x1000fe);	// GlyphCache 4
+	PLONG(p+24, 0x2000fe);	// GlyphCache 5
+	PLONG(p+28, 0x4000fe);	// GlyphCache 6
+	PLONG(p+32, 0x8000fe);	// GlyphCache 7
+	PLONG(p+36, 0x10000fe);	// GlyphCache 8
+	PLONG(p+40, 0x8000040);	// GlyphCache 9
+	PLONG(p+44, 0x01000100);	// FragCache 
+	PSHORT(p+48, GLYPH_SUPPORT_NONE);	// GlyphSupportLevel
+	PSHORT(p+50, 0);	// pad2octets 
+	return p+52;
+}
+
+/* 2.2.7.1.11 Sound Capability Set (TS_SOUND_CAPABILITYSET) */
+uchar*
+putsndcaps(uchar* p, uchar* ep)
+{
+	if(p+8>ep)
+		sysfatal(Eshort);
+	PSHORT(p+0, CapSound);
+	PSHORT(p+2, SNDCAPSIZE);
+	PSHORT(p+4, 0);	// soundFlags
+	PSHORT(p+6, 0);	// pad2octetsA
+	return p+8;
+}
--- /dev/null
+++ b/dat.h
@@ -1,0 +1,111 @@
+typedef	struct	Rdp Rdp;
+typedef	struct	Vchan Vchan;
+typedef	struct	Rdpnego Rdpnego;
+
+#define	GSHORT(p)	((p)[0]|((p)[1]<<8))
+#define	GSHORTB(p)	((p)[0]<<8|((p)[1]))
+#define	GLONG(p) 	((p)[0]|((p)[1]<<8)|((p)[2]<<16)|((p)[3]<<24))
+#define	GLONGB(p)	(((p)[0]<<24)|((p)[1]<<16)|((p)[2]<<8)|(p)[3])
+#define	PSHORT(p,v)	(p)[0]=(v);(p)[1]=(v)>>8
+#define	PSHORTB(p,v)	(p)[0]=(v)>>8;(p)[1]=(v)
+#define	PLONG(p,v)	(p)[0]=(v);(p)[1]=(v)>>8;(p)[2]=(v)>>16;(p)[3]=(v)>>24
+#define	PLONGB(p,v)	(p)[0]=(v)>>24;(p)[1]=(v)>>16;(p)[2]=(v)>>8;(p)[3]=(v)
+
+#define	MIN(x,y)		(((x) < (y)) ? (x) : (y))
+
+struct Rdp
+{
+	int		fd;			/* connection I/O descriptor */
+	long		sproto;		/* server-selected security protocol to replay back */
+	int		autologon;	/* enable auto logon */
+	char		*label;		/* window label */
+	char		*local;		/* local system name */
+	char		*user;		/* user name */
+	char		*windom;		/* domain for auto logon */
+	char		*passwd;		/* password for auto logon (sic) */
+	char		*shell;		/* remote shell override */
+	char		*rwd;		/* remote working directory */
+	ulong	chan;		/* remote graphics channel descriptor */
+	int		depth;		/* color depth as exposed by the protocol */
+	int		hupreason;	/* hangup reason as server explains */
+	int		mcsuid;		/* MCS [T.122] userId */
+	int		userchan;		/* MCS user channelId */
+	int		shareid;		/* share ID - [T128] section 8.4.2 */
+	int		licensed;		/* licensing sub-protocol completion */
+	int		active;		/* T.128 action state */
+	int		wantconsole;	/* attach to the console sesstion */
+	Point		dim;			/* rfb size */
+};
+
+struct Vchan
+{
+	int		mcsid;		/* MCS channelId */
+	int		flags;
+	char		name[8];
+	int		defragging;
+	uchar*	buf;			/* defragmentation buffer */
+	int		nb;			/* sizeof buf */
+	int		pos;			/* next fragment offset */ 
+	void		(*fn)(uchar*,uchar*);
+};
+Vchan*	lookupvc(int);
+Vchan*	namevc(char*);
+int		sendvc(char*,uchar*,int);
+void		scanvcpdu(uchar*,uchar*,int);
+
+enum 
+{
+	/* 2.2.7 Capability Sets; T.128 */
+	CapGeneral=	1,
+	CapBitmap=	2,
+	CapOrder=	3,
+	CapPointer=	8,
+	CapBitcache2=	19,
+	CapInput=	13,
+	CapSound=	12,
+	CapGlyph=	16,
+	GENCAPSIZE=	24,
+	BITCAPSIZE=	30,
+	ORDCAPSIZE=	88,
+	BCACAPSIZE=	40,
+	PTRCAPSIZE=	8,
+	INPCAPSIZE=	88,
+	SNDCAPSIZE=	8,
+	GLYCAPSIZE=	52,
+
+	/* 2.2.7.1.1 General Capability Set (TS_GENERAL_CAPABILITYSET) */
+	CanFastpath	= 0x0001,
+	NoBitcomphdr	= 0x0400,
+	CanLongcred	= 0x0004,
+
+	/* 2.2.8.1.1.2.1 Basic (TS_SECURITY_HEADER) */
+	Scrypt			= 0x0008,
+	Sinfopk			= 0x0040,
+	Slicensepk		= 0x0080,
+
+	/* 2.2.8.1.1.3.1.1 Slow-Path Input Event (TS_INPUT_EVENT) */
+	InputSync=	0,
+	InputKeycode=	4,
+	InputUnicode=	5,
+	InputMouse=	0x8001,
+
+	TPKTFIXLEN=		4,
+	TPDATAFIXLEN=	(TPKTFIXLEN+3),
+	MCSCIFIXLEN=		(18+3*2+24*4),
+
+	MAXTPDU=	16386,	/* max TPDU size */
+	SRVCHAN=	1002,	/* server channel ID */
+	GLOBALCHAN=	1003,	/* MCS global channel's id */
+	NumOrders=		32,
+};
+
+extern Rdp	rd;
+extern uchar	orderSupport[NumOrders];
+extern uchar	cmap[256];	/* 8bpp translation table */
+
+extern Vchan	vctab[];		/* static virtual channels table */
+extern uint	nvc;			/* number of vctab entries */
+
+extern char	Eshort[];
+extern char	Ebignum[];
+extern char	Esmall[];
--- /dev/null
+++ b/eclip.c
@@ -1,0 +1,266 @@
+/*
+ * [MS-RDPECLIP]: Remote Desktop Protocol: Clipboard Virtual Channel Extension
+ * http://msdn.microsoft.com/en-us/library/cc241066.aspx
+ * 
+ * Standard Clipboard Formats
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168(v=vs.85).aspx
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+static char	cliprdr[]				= "CLIPRDR";
+static void		cliprequest(uint);
+
+enum
+{
+	CFunicode	= 13,
+};
+
+enum
+{
+	FlagOk			= (1<<0),
+	FlagErr			= (1<<1),
+};
+
+enum
+{
+	ClipReady			= 1,
+	ClipAnnounce		= 2,
+	ClipNoted			= 3,
+	ClipReq			= 4,
+	ClipResp			= 5,
+};
+
+typedef	struct	Clipmsg Clipmsg;
+struct Clipmsg
+{
+	uint		type;
+	uint		flags;
+	uint		fmtid;
+	uchar	*data;
+	uint		ndata;
+};
+
+static	int	clipputmsg(Clipmsg*,uchar*,int);
+static	int	clipgetmsg(Clipmsg*,uchar*,int);
+
+static	void	clipattached(Clipmsg*);
+static	void	clipnoted(Clipmsg*);
+static	void	cliprequested(Clipmsg*);
+static	void	clipprovided(Clipmsg*);
+
+static	void	(*clipcall[])(Clipmsg*) =
+{
+	[ClipReady]=		clipattached,
+	[ClipAnnounce]=	clipnoted,
+	[ClipReq]=		cliprequested,
+	[ClipResp]=		clipprovided,
+};
+
+void
+clipvcfn(uchar* p, uchar* ep)
+{
+	Clipmsg tx;
+
+	if(clipgetmsg(&tx, p, ep-p) < 0)
+		return;
+	if(tx.flags&FlagErr)
+		return;
+	if(tx.type >= nelem(clipcall))
+		return;
+	if(clipcall[tx.type] == nil)
+		return;
+	clipcall[tx.type](&tx);
+}
+
+void
+clipannounce(void)
+{
+	Clipmsg r;
+	uchar a[44];
+	int n;
+
+	r.type = ClipAnnounce;
+	r.flags = 0;
+	r.fmtid = CFunicode;
+	n = clipputmsg(&r, a, sizeof(a));
+	if(sendvc(cliprdr, a, n) < 0)
+		fprint(2, "clipannounce: %r\n");
+}
+
+static void
+cliprequest(uint fmtid)
+{
+	Clipmsg r;
+	uchar a[12];
+	int n;
+
+	r.type = ClipReq;
+	r.flags = 0;
+	r.fmtid = fmtid;
+	n = clipputmsg(&r, a, sizeof(a));
+	if(sendvc(cliprdr, a, n) < 0)
+		fprint(2, "cliprequest: %r\n");
+}
+
+static void
+clipattached(Clipmsg*)
+{
+	clipannounce();
+}
+
+static void
+clipnoted(Clipmsg *m)
+{
+	Clipmsg r;
+	uchar a[8];
+	int n;
+
+	if(m->fmtid)
+		cliprequest(m->fmtid);
+
+	r.type = ClipNoted;
+	r.flags = FlagOk;
+	n = clipputmsg(&r, a, sizeof(a));
+	if(sendvc(cliprdr, a, n) < 0)
+		fprint(2, "clipnoted: %r\n");
+}
+
+static void
+cliprequested(Clipmsg *m)
+{
+	Clipmsg r;
+	char* s;
+	uchar *b;
+	int n, ns, nb;
+
+	b = emalloc(8);
+	nb = 0;
+
+	r.type = ClipResp;
+	r.flags = FlagOk;
+	if(m->fmtid != CFunicode){
+		r.flags = FlagErr;
+		goto Respond;
+	}
+
+	s = getsnarf(&ns);
+	if(s == nil)
+		goto Respond;
+	nb = ns*4;
+	b = erealloc(b, nb+8);
+	nb = toutf16(b+8, nb, s, ns);
+	free(s);
+  Respond:
+	r.data = b+8;
+	r.ndata = nb;
+	n = clipputmsg(&r, b, nb+8);
+	if(sendvc(cliprdr, b, n) < 0)
+		fprint(2, "cliprequested: %r\n");
+	free(b);
+}
+
+static void
+clipprovided(Clipmsg *m)
+{
+	char *s;
+	int n, ns;
+
+	ns = m->ndata*UTFmax/2;
+	s = emalloc(ns);
+	n = fromutf16(s, ns, m->data, m->ndata);
+	putsnarf(s, n);
+	free(s);
+	return;
+}
+
+static int
+clipputmsg(Clipmsg *m, uchar *a, int n)
+{
+	if(n < 8){
+		werrstr(Esmall);
+		return -1;
+	}
+	PSHORT(a+0, m->type);
+	PSHORT(a+2, m->flags);
+	switch(m->type){
+	case ClipAnnounce:
+		m->data = a+8;
+		m->ndata = 4+32;
+		if(8 + m->ndata > n){
+			werrstr(Esmall);
+			return -1;
+		}
+		PLONG(a+8, m->fmtid);
+		memset(a+12, 0, 32);	/* fmt name - who cares? */
+		break;
+	case ClipReq:
+		m->data = a+8;
+		m->ndata = 4;
+		if(8 + m->ndata > n){
+			werrstr(Esmall);
+			return -1;
+		}
+		PLONG(a+8, m->fmtid);
+		break;
+	case ClipNoted:
+		m->ndata = 0;
+		m->data = a+8;
+		break;
+	}
+	if(8 + m->ndata > n){
+		werrstr(Esmall);
+		return -1;
+	}
+	PLONG(a+4, m->ndata);
+	memcpy(a+8, m->data, m->ndata);
+	return 8+m->ndata;
+}
+
+static int
+clipgetmsg(Clipmsg *m, uchar *p, int n)
+{
+	uint len, fmtid;
+	uchar *ep;
+
+	if(8 > n){
+		werrstr(Eshort);
+		return -1;
+	}
+	m->type = GSHORT(p);
+	m->flags = GSHORT(p+2);
+	len = GLONG(p+4);
+	if(8+len > n){
+		werrstr(Eshort);
+		return -1;
+	}
+	m->ndata = len;
+	m->data = p+8;
+
+	switch(m->type){
+	case ClipReq:
+		if(len < 4){
+			werrstr(Eshort);
+			return -1;
+		}
+		m->fmtid = GLONG(m->data);
+		break;
+	case ClipAnnounce:
+		m->fmtid = 0;
+		p += 8;
+		ep = p+len;
+		while(p < ep){
+			fmtid = GLONG(p);
+			if(fmtid == CFunicode){
+				m->fmtid = fmtid;
+				break;
+			}
+			p += 4+32*1;
+		}
+		break;
+	}
+	return 8+len;
+}
--- /dev/null
+++ b/egdi.c
@@ -1,0 +1,518 @@
+/*
+ * [MS-RDPEGDI]: Graphics Device Interface (GDI) Acceleration Extensions
+ * http://msdn.microsoft.com/en-us/library/cc241537.aspx
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+enum
+{
+	Bits2=	3,
+	Bits3=	7,
+	Bits6=	63,
+	Bits7=	127,
+	Bits8=	255,
+};
+enum /* 2.2.2.2.1 Drawing Order (DRAWING_ORDER) */
+{
+	Standard=		1<<0,
+	Secondary=	1<<1,
+};
+enum /* 2.2.2.2.1.1.2 Primary Drawing Order (PRIMARY_DRAWING_ORDER) */
+{
+	Clipped=		1<<2,
+	NewOrder= 	1<<3,
+	Diff=			1<<4,
+	SameClipping=	1<<5,
+	ZeroFieldBit0=	1<<6,
+	ZeroFieldBit1=	1<<7,
+};
+enum
+{
+	ROP2_COPY	= 0xcc,
+};
+enum
+{
+	/* orderSupport indices for capability negotiation */
+	CanDstBlt = 0,
+	CanPatBlt,				/* also OpaqueRect */
+	CanScrBlt,
+	CanMemBlt = 3,
+	CanMem3Blt,
+	CanDrawNineGrid = 7,
+	CanLineTo,
+	CanMultiDrawNineGrid,
+	CanSaveBitmap = 0x0B,
+	CanMultiDstBlt = 0x0F,
+	CanMultiPatBlt = 0x10,
+	CanMultiScrBlt,
+	CanMultiOpaqueRect,
+	CanFastIndex,
+	CanPolygonSC,		/* also PolygonCB */
+	CanPolygonCB,		/* also PolygonCB */
+	CanPolyline,
+	CanFastGlyph = 0x18,
+	CanEllipseSC,			/* also EllipseCB */
+	CanEllipseCB,			/* also EllipseSC */
+	CanGlyphIndex,
+};
+enum
+{
+	/* 2.2.2.2.1.1.2 Primary Drawing Order (PRIMARY_DRAWING_ORDER) */
+	PatBlt=1,
+	ScrBlt=2,
+	OpaqueRect=10,
+	MemBlt=13,
+	MultiOpaqueRect=18,
+};
+enum
+{
+	/* 2.2.2.2.1.2.1.1 Secondary Drawing Order Header */
+	CacheImage = 0,
+	CacheCmap,
+	CacheCompressed,
+	CacheGlyph,
+	CacheImage2,
+	CacheCompressed2,
+	CacheBrush = 7,
+	CacheCompressed3,
+};
+
+typedef	struct	Order Order;
+struct Order
+{
+	int	fsize;
+	uchar* (*fn)(uchar*,uchar*,int,int);
+};
+
+static	uchar*	scrblt(uchar*,uchar*,int,int);
+static	uchar*	memblt(uchar*,uchar*,int,int);
+static	uchar*	cacheimage2(uchar*,uchar*,int,int);
+static	uchar*	cacheimage(uchar*,uchar*,int,int);
+static	uchar*	cachecmap(uchar*,uchar*,int,int);
+
+Order ordtab[NumOrders] = {
+	[ScrBlt]= 		{ 1, scrblt },
+	[MemBlt]=	{ 2, memblt },
+	[PatBlt]= 		{ 2, nil },
+	[OpaqueRect]=	{ 1, nil },
+	[MultiOpaqueRect]=	{ 2, nil },
+};
+
+Order auxtab[8] = {
+	[CacheImage2]=		{ 0, cacheimage2 },
+	[CacheCompressed2]= 	{ 0, cacheimage2 },
+	[CacheImage]=			{ 0, cacheimage },
+	[CacheCompressed]= 	{ 0, cacheimage },
+	[CacheCmap]=			{ 0, cachecmap },
+};
+
+uchar
+orderSupport[NumOrders] = 
+{
+	[CanScrBlt]   	1,
+	[CanMemBlt]	1,
+//	[CanMultiOpaqueRect] 1,
+//	[CanPatBlt]   	1,	/* and OpaqueRect */
+};
+
+static struct GdiContext
+{
+	int order;
+	Rectangle clipr;
+} gc	= {PatBlt};
+
+static Image*	imgcache[3][600];
+
+static	uchar*	getclipr(Rectangle*,uchar*,uchar*);
+static	uchar*	getpt(Point*,uchar*,uchar*,int,int);
+
+/* 2.2.2.2 Fast-Path Orders Update (TS_FP_UPDATE_ORDERS) */
+void
+scanorders(uchar* p, uchar* ep, int count)
+{
+	int ctl, fmask, fsize;
+	int size, opt, xorder;
+
+	while(count-- > 0 && p<ep){	
+		fmask = 0;
+		ctl = *p;
+		if(!(ctl&Standard))
+			goto ErrNstd;	// GDI+ or out of sync
+		if(ctl&Secondary){
+			if(p+6>ep)
+				sysfatal(Eshort);
+			size = ((short)GSHORT(p+1))+13;
+			if(size < 0 || p+size > ep)
+				sysfatal(Eshort);
+			opt = GSHORT(p+3);
+			xorder = p[5];
+			if(xorder >= nelem(auxtab) || auxtab[xorder].fn == nil){
+				fprint(2, "egdi: unsupported secondary order %d\n", xorder);
+				p += size;
+				continue;
+			}
+
+			auxtab[xorder].fn(p, p+size, xorder, opt);
+			p += size;
+			continue;
+		}
+		p++;
+		if(ctl&NewOrder){
+			gc.order = *p++;
+			if(gc.order >= NumOrders)		// paranoia
+				gc.order = PatBlt;
+		}
+		fsize = ordtab[gc.order].fsize - ((ctl>>6)&Bits2);
+		switch(fsize){
+		default:
+			goto ErrFsize;
+		case 3:
+			fmask = p[0]|(p[1]<<8)|(p[2]<<16);
+			break;
+		case 2:
+			fmask = GSHORT(p);
+			break;
+		case 1:
+			fmask = p[0];
+		case 0:
+			break;
+		}
+		p += fsize;
+
+		if(ctl&Clipped && !(ctl&SameClipping))
+			p = getclipr(&gc.clipr, p, ep);
+
+		if(ordtab[gc.order].fn == nil)
+			goto ErrNotsup;
+		p = ordtab[gc.order].fn(p, ep, ctl, fmask);
+		if(p == nil)
+			break;
+	}
+	lockdisplay(display);
+	flushimage(display, 1);
+	unlockdisplay(display);
+	return;
+
+ErrNstd:
+	fprint(2, "egdi: non-standard order\n");
+	return;
+ErrFsize:
+	fprint(2, "egdi: bad field encoding bytes count for order %d\n", gc.order);
+	return;
+ErrNotsup:
+	fprint(2, "egdi: unsupported order %d\n", gc.order);
+	return;
+}
+
+static uchar*
+getclipr(Rectangle* pr, uchar* p, uchar* ep)
+{
+	int bctl;
+
+	bctl = *p++;
+	if(bctl&1<<4)
+		pr->min.x += (char)*p++;
+	else if(bctl&1<<0){
+		pr->min.x = GSHORT(p);
+		p += 2;
+	}
+	if(bctl&1<<5)
+		pr->min.y += (char)*p++;
+	else if(bctl&1<<1){
+		pr->min.y = GSHORT(p);
+		p += 2;
+	}
+	if(bctl&1<<6)
+		pr->max.x += (char)*p++;
+	else if(bctl&1<<2){
+		pr->max.x = GSHORT(p)+1;
+		p += 2;
+	}
+	if(bctl&1<<7)
+		pr->max.y += (char)*p++;
+	else if(bctl&1<<3){
+		pr->max.y = GSHORT(p)+1;
+		p += 2;
+	}
+	if(p>ep)
+		sysfatal(Eshort);
+	return p;
+}
+
+static uchar*
+getpt(Point* pp, uchar* s, uchar *es,  int ctl, int fmask)
+{
+	Point p;
+
+	p = *pp;
+	if(ctl&Diff){
+		if(fmask&1<<0)
+			p.x += (char)*s++;
+		if(fmask&1<<1)
+			p.y += (char)*s++;
+	}else{
+		if(fmask&1<<0){
+			p.x = GSHORT(s);
+			s += 2;
+		};
+		if(fmask&1<<1){
+			p.y = GSHORT(s);
+			s += 2;
+		};
+	}
+	if(s > es)
+		sysfatal(Eshort);
+	*pp = p;
+	return s;
+}
+
+static uchar*
+getoffrect(Rectangle* pr, uchar* p, uchar* ep, int ctl, int fmask){
+	Rectangle r;
+
+	r = *pr;
+	r.max = subpt(r.max, r.min);
+	p = getpt(&r.min, p, ep, ctl, fmask);
+	p = getpt(&r.max, p, ep, ctl, fmask>>2);
+	r.max = addpt(r.max, r.min);
+	*pr = r;
+	return p;
+}
+
+/* 2.2.2.2.1.1.2.7 ScrBlt (SCRBLT_ORDER) */
+static uchar*
+scrblt(uchar* p, uchar* ep, int ctl, int fmask)
+{
+	static Rectangle r;
+	static Point pt;
+	static int rop3;
+	Rectangle rs;
+	Point ps;
+
+	p = getoffrect(&r, p, ep, ctl, fmask);
+	if(fmask&1<<4)
+		rop3 = *p++;
+	p = getpt(&pt, p, ep, ctl, fmask>>5);
+
+	if(rop3 != ROP2_COPY){
+		fprint(2, "scrblt: rop3 %#hhux is not supported\n", rop3);
+		return p;
+	}
+	rs = r;
+	if(ctl&Clipped)
+		rectclip(&rs, gc.clipr);	// not replclipr: need to clip dst only
+	rs = rectaddpt(rs, screen->r.min);
+	ps = addpt(pt, screen->r.min);
+
+	lockdisplay(display);
+	draw(screen, rs, screen, nil, ps);
+	unlockdisplay(display);
+
+	return p;
+}
+
+/* 2.2.2.2.1.1.2.9 MemBlt (MEMBLT_ORDER) */
+static uchar*
+memblt(uchar* p, uchar* ep, int ctl, int fmask)
+{
+	static int cid;	/* cacheId */
+	static int coff;	/* cacheIndex */
+	static int rop3;
+	static Rectangle r;
+	static Point pt;
+	Image* img;
+
+	if(fmask&1<<0){
+		cid = GSHORT(p);
+		p += 2;
+	}
+	p = getoffrect(&r, p, ep, ctl, fmask>>1);
+	if(fmask&1<<5)
+		rop3 = *p++;
+	p = getpt(&pt, p, ep, ctl, fmask>>6);
+	if(fmask&1<<8){
+		coff = GSHORT(p);
+		p += 2;
+	}
+	if(p>ep)
+		sysfatal(Eshort);
+
+	cid &= Bits8;
+	if(cid >= nelem(imgcache) || coff >= nelem(*imgcache)){
+		fprint(2, "memblt: bad image cache spec [%d %d]\n", cid, coff);
+		return p;
+	}
+	img = imgcache[cid][coff];
+	if(img == nil){
+		fprint(2, "memblt: empty cache entry cid %d coff %d\n", cid, coff);
+		return p;
+	}
+
+	lockdisplay(display);
+	if(ctl&Clipped)
+		replclipr(screen, screen->repl, rectaddpt(gc.clipr, screen->r.min));
+	draw(screen, rectaddpt(r, screen->r.min), img, nil, pt);
+	if(ctl&Clipped)
+		replclipr(screen, screen->repl, screen->r);
+	unlockdisplay(display);
+
+	return p;
+}
+
+static Image*
+pickimage(int cid, int coff, Rectangle r, ulong chan)
+{
+	Image* img;
+
+	if(cid >= nelem(imgcache) || coff >= nelem(*imgcache)){
+		werrstr("bad image cache spec [%d %d]", cid, coff);
+		return nil;
+	}
+
+	img = imgcache[cid][coff];
+	if(img==nil || !eqrect(img->r, r)){
+		if(img != nil)
+			freeimage(img);
+		img = allocimage(display, r, chan, 0, DNofill);
+		if(img == nil)
+			sysfatal("%r");
+		imgcache[cid][coff] = img;
+	}
+	return img;
+}
+
+/* 2.2.2.2.1.2.2 Cache Bitmap - Revision 1 (CACHE_BITMAP_ORDER) */
+static uchar*
+cacheimage(uchar* p, uchar* ep, int xorder, int opt)
+{
+	int cid;	/* cacheId */
+	int coff;	/* cacheIndex */
+	int chan;
+	int zip;
+	int err;
+	int size;
+	Image* img;
+	Point d;	/* width, height */
+	Rectangle r;
+
+	if(p+15 >= ep)
+		sysfatal(Eshort);
+	cid = p[6];
+	d.x = p[8];
+	d.y = p[9];
+	size = GSHORT(p+11);
+	coff = GSHORT(p+13);
+	r.min = ZP;
+	r.max = d;
+	chan = rd.chan;
+	zip = (xorder==CacheCompressed);
+
+	if(zip)
+	if(opt&1<<10){
+		p += 8;	// bitmapComprHdr
+		size -= 8;
+	}
+	if(p+size > ep)
+		sysfatal(Eshort);
+	if((img = pickimage(cid, coff, r, chan)) == nil)
+		sysfatal("pickimage: %r");
+	err = (zip? loadrle : loadbmp)(img, r, p, size);
+	if(err < 0)
+		sysfatal("%r");
+	return p+size;
+}
+
+/* 2.2.2.2.1.2.3 Cache Bitmap - Revision 2 (CACHE_BITMAP_REV2_ORDER) */
+static uchar*
+cacheimage2(uchar* p,uchar* ep, int xorder, int opt)
+{
+	int n, c;
+	int zip;
+	int cid;	/* cacheId */
+	int coff;	/* cacheIndex */
+	int chan;
+	int size;
+	int err;
+	Image* img;
+	Point d;
+	Rectangle r;
+
+	if(p+9 >= ep)
+		sysfatal(Eshort);
+	p += 6;
+
+	chan = rd.chan;
+	zip = (xorder==CacheCompressed2);
+	cid = opt&Bits3;
+	opt >>= 7;
+
+	if(opt&1<<1)
+		p += 8;	// persistent cache key
+	c = *p++;
+	if(c&1<<7)
+		c = ((c&Bits7)<<8) | *p++;
+	d.x = c;
+	if(opt&1)
+		d.y = d.x;
+	else{
+		c = *p++;
+		if(c&1<<7)
+			c = ((c&Bits7)<<8) | *p++;
+		d.y = c;
+	}
+	r.min = ZP;
+	r.max = d;
+
+	c = *p++;
+	n = c>>6;
+	c &= Bits6;
+	switch(n){
+	case 3:	c = (c<<8) | *p++;
+	case 2:	c = (c<<8) | *p++;
+	case 1:	c = (c<<8) | *p++;
+	}
+	size = c;	// fixme: protect from integer overflow
+
+	c = *p++;
+	if(c&1<<7)
+		c = ((c&Bits7)<<8) | *p++;
+	coff = c;
+
+	if(zip&& !(opt&1<<3)){
+		p += 8;	// bitmapComprHdr
+		size -= 8;
+	}
+	if(p+size > ep)
+		sysfatal(Eshort);
+	if((img = pickimage(cid, coff, r, chan)) == nil)
+		sysfatal("pullimage: %r");
+	err = (zip? loadrle : loadbmp)(img, r, p, size);
+	if(err < 0)
+		sysfatal("%r");
+	return p+size;
+}
+
+/* 2.2.2.2.1.2.4 Cache Color Table (CACHE_COLOR_TABLE_ORDER) */
+static uchar*
+cachecmap(uchar* p,uchar* ep, int, int)
+{
+	int cid, n;
+	
+	cid = p[6];
+	n = GSHORT(p+7);
+	if(n != 256){
+		fprint(2, "cachecmap: %d != 256\n", n);
+		return nil;
+	}
+	if(p+9+4*256>ep){
+		werrstr(Eshort);
+		return nil;
+	}
+	/* fixed cmap here */
+	USED(cid);
+	return p+9+4*256;
+}
--- /dev/null
+++ b/ele.c
@@ -1,0 +1,144 @@
+/*
+ * [MS-RDPELE] 2.2.2 Licensing PDU (TS_LICENSING_PDU)
+ * http://msdn.microsoft.com/en-us/library/cc241913.aspx
+ *
+ * 2.2.1.12.1.1 Licensing Preamble [MS-RDPBCGR]
+ * 2.2.1.12.1.2 Licensing Binary Blob [MS-RDPBCGR]
+ * 2.2.2.2 Client New License Request [MS-RDPELE]
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+enum
+{
+	RandomSize=	32,
+
+	PreambleV3=	3,	/* RDP 5.0+ */
+	KeyExRSA=	1,
+
+	SNeedLicense=	1,
+	SHaveChal=	2,
+	SHaveLicense=	3,
+	SNeedRenew=	4,
+	CLicenseInfo=	0x12,
+ 	CNeedLicense=	0x13,
+	CChalResp=	0x15,
+	Notify=	0xFF,
+
+	Brandom=	2,
+	Berror=	4,
+	Bcuser=	15,
+	Bchost=	16,
+
+	ErrCNoLicense=	2,
+
+	TotalAbort=	1,
+	NoTransition=	2,
+};
+
+static void reqlicense(char*,char*);
+static void	senderr(int,int);
+
+void
+scanlicensepdu(uchar* p, uchar* ep)
+{
+	uchar type;
+
+	if(ep-p < 1)
+		sysfatal(Eshort);
+
+	/* type[1] flags[1] size[2] */
+	type = p[0];
+	switch(type){
+	case SNeedLicense:
+		reqlicense(rd.user, rd.local);
+		break;
+	case SHaveChal:
+		fprint(2, "unhandled SHaveChal PDU\n");
+		senderr(ErrCNoLicense, TotalAbort);
+		break;
+	case SNeedRenew:
+	case SHaveLicense:
+	case Notify:
+		rd.licensed = 1;
+		break;
+	}
+}
+
+static void
+reqlicense(char* user, char *host)
+{
+	uchar buf[180], *p, *ep;
+	int nb, ndata, usersize, hostsize;
+
+	usersize = strlen(user)+1;
+	hostsize = strlen(host)+1;
+	ndata = 24+usersize+hostsize+RandomSize+48;
+	nb = sizeof(buf);
+
+	p = prebuf(buf, nb, ndata, 0, Slicensepk);
+	if(p == nil)
+		sysfatal("reqlicense: %r");
+	ep = p+ndata;
+
+	/* 
+	 * type[1] flags[1] size[2]
+	 * kexalg[4] platfid[4] crandom[32]
+	 * premaster[blob] cuser[blob] chost[blob]
+	 * 
+	 * blob := type[2] len[2] data[len]
+	 */
+	p[0] = CNeedLicense;
+	p[1] = PreambleV3;
+	PSHORT(p+2, ndata);
+
+	PLONG(p+4, KeyExRSA);
+	PLONG(p+8, 0);
+	memset(p+12, RandomSize, 0);
+	p += 12+RandomSize;
+
+	PSHORT(p+0, Brandom);
+	PSHORT(p+2, 48);
+	memset(p+4, 48, 0);
+	p += 4+48;
+
+	PSHORT(p+0, Bcuser);
+	PSHORT(p+2, usersize);
+	memcpy(p+4, user, usersize);
+	p+= 4+usersize;
+
+	PSHORT(p+0, Bchost);
+	PSHORT(p+2, hostsize);
+	memcpy(p+4, host, hostsize);
+	p+= 4+hostsize;
+
+	assert(p == ep);
+	writen(rd.fd, buf, p-buf);
+}
+
+static void
+senderr(int errcode, int newstate)
+{
+	uchar buf[512], *p;
+	int nb, ndata;
+
+	nb = sizeof(buf);
+	ndata = 16;
+	p = prebuf(buf, nb, ndata, 0, Slicensepk);
+	if(p == nil)
+		sysfatal("prebuf: %r");
+
+	/* type[1] flags[1] size[2] errcode[4] newstate[4] blob.type[2] blob.len[2] */
+	p[0] = Notify;
+	p[1] = PreambleV3;
+	PSHORT(p+2, ndata);
+	PLONG(p+4, errcode);
+	PLONG(p+8, newstate);
+	PSHORT(p+12, Berror);
+	PSHORT(p+14, 0);
+
+	writen(rd.fd, buf, p-buf);
+}
--- /dev/null
+++ b/fns.h
@@ -1,0 +1,99 @@
+/* cap.c */
+void		scangencaps(uchar*,uchar*);
+void		scanbitcaps(uchar*,uchar*);
+uchar*	putgencaps(uchar*,uchar*);
+uchar*	putbitcaps(uchar*,uchar*);
+uchar*	putordcaps(uchar*,uchar*);
+uchar*	putbc2caps(uchar*,uchar*);
+uchar*	putptrcaps(uchar*,uchar*);
+uchar*	putinpcaps(uchar*,uchar*);
+uchar*	putsndcaps(uchar*,uchar*);
+uchar*	putglycaps(uchar*,uchar*);
+
+/* load.c */
+int		loadbmp(Image*,Rectangle,uchar*,int);
+int		loadrle(Image*,Rectangle,uchar*,int);
+
+/* mcs.c */
+int		mcschanid(uchar*,uchar*);
+int		mcstype(uchar*,uchar*);
+int		ismcshangup(uchar*,uchar*);
+uchar*	mcspayload(uchar*,uchar*);
+int		mkmcsci(uchar*, int, int);
+int		mkmcssdr(uchar*,int,int,int);
+int		mcsconnect(int);
+void		erectdom(int);
+int		attachuser(int);
+int		joinchannel(int,int);
+
+/* mpas.c */
+int		rdphandshake(int);
+void		readnet(int);
+int		isflowpdu(uchar*,uchar*);
+void		scanaspdu(uchar*, uchar*);
+void		scandatapdu(uchar*,uchar*);
+void		activating(uchar*,uchar*);
+void		passinput(ulong,int,int,int,int);
+void		turnupdates(int);
+int		sizesechdr(int);
+uchar*	prebuf(uchar*,int,int,int,int);
+
+/* draw.c */
+void		eresized(int);
+
+/* eclip.c */
+void		clipannounce(void);
+void		clipvcfn(uchar*,uchar*);
+
+/* egdi.c */
+void		scanorders(uchar*,uchar*,int);
+
+/* ele.c */
+void		scanlicensepdu(uchar*,uchar*);
+
+/* snarf.c */
+void		initsnarf(void);
+void		pollsnarf(void);
+char*	getsnarf(int*);
+void		putsnarf(char*,int);
+
+/* mouse.c */
+void		readdevmouse(void);
+void		warpmouse(int,int);
+
+/* kbd.c */
+void		readkbd(void);
+
+/* sec.c */
+
+/* mppc.c */
+uchar*	uncomp(uchar*,int,int,int*);
+
+/* rle.c */
+uchar*	unrle(uchar*,int, uchar*,int,int,int);
+
+/* utf16.c */
+int		fromutf16(char*,int,uchar*,int);
+int		toutf16(uchar*,int,char*,int);
+
+/* x224.c */
+int		mktpdat(uchar*,int,int);
+int		readpdu(int,uchar*,uint);
+int		mktpcr(uchar*,int,int);
+int		mktpdr(uchar*,int,int);
+int		istpkt(uchar*,uchar*);
+int		tpdutype(uchar*,uchar*);
+int		isdatatpdu(uchar*,uchar*);
+uchar*	tpdupayload(uchar*,uchar*);
+int		x224connect(int);
+int		x224disconnect(int);
+int		starttls(void);
+
+/* rd.c */
+void		atexitkiller(void);
+void		atexitkill(int pid);
+void*	emalloc(ulong);
+void*	erealloc(void*,ulong);
+char*	estrdup(char*);
+long		writen(int,void*,long);
+
--- /dev/null
+++ b/guide
@@ -1,0 +1,12 @@
+mk clean
+cpu -c 'objtype='$cputype mk
+window -r 2 44 1026 812
+kill 5.out | rc
+src -s memcpy 5.out
+g 
+h2d 
+d2h 
+dat.h fns.h mkfile /dist/replica/rd.proto
+diff -r . /sys/src/cmd/rd | sed '/^diff/!d; s//cp /'
+wc [~_]*.[ch] | sort -rn
+/n/dump/2015/0108/386/bin/rd	# legacy security
--- /dev/null
+++ b/kbd.c
@@ -1,0 +1,231 @@
+#include <u.h>
+#include <libc.h>
+#include <keyboard.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+
+enum
+{
+	Kbrk=	Spec|0x61,
+	KeyEx= 	0x0100,
+	KeyUp=	0x8000,
+};
+
+enum
+{
+	Sext=	(1<<16),
+	Sesc=	1,
+	Sbspace=	14,
+	Stab=	15,
+	Sq=	16,
+	Sw=	17,
+	Se=	18,
+	Sr=	19,
+	St=	20,
+	Sy=	21,
+	Su=	22,
+	Si=	23,
+	So=	24,
+	Sp=	25,
+	Srbrace=	27,
+	Sret=	28,
+	Slctrl=	29,
+	Sa=	30,
+	Ss=	31,
+	Sd=	32 ,
+	Sf=	33,
+	Sg=	34,
+	Sh=	35,
+	Sj=	36,
+	Sk=	37,
+	Sl=	38,
+	Slshift=	42,
+	Sbslash=	43,
+	Sz=	44,
+	Sx=	45,
+	Sc=	46,
+	Sv=	47,
+	Sb=	48,
+	Sn=	49,
+	Sm=	50,
+	Speriod=	52,
+	Sslash=	53,
+	Sprint=	Sext|55,
+	SF1=	59,
+	SF11=	87,
+	SF12=	88,
+	Shome=	199,
+	Sup=	200,
+	Spgup=	201,
+	Sleft=	203,
+	Sright=	205,
+	Sdown=	208,
+	Spgdown=	209,
+	Send=	207,
+	Sins=	210,
+	Sdel=	211,
+};
+
+struct {
+	uchar sc, mod;
+} rune2scan[] = {
+	Sdel, Slshift,
+	Sa,  Slctrl,
+	Sb,  Slctrl,
+	Sc,  Slctrl,
+	Sd,  Slctrl,
+	Se,  Slctrl,
+	Sf,  Slctrl,
+	Sg,  Slctrl,
+	Sbspace,  0,
+	Stab,    0,
+	Sret,  0,
+	Sk,  Slctrl,
+	Sl,  Slctrl,
+	Sm,    Slctrl,
+	Sn,  Slctrl,
+	So,  Slctrl,
+	Sp,  Slctrl,
+	Sq,  Slctrl,
+	Sr,  Slctrl,
+	Ss,  Slctrl,
+	St,  Slctrl,
+	Su,  Slctrl,
+	Sv,  Slctrl,
+	Sw,  Slctrl,
+	Sx,  Slctrl,
+	Sy,  Slctrl,
+	Sz,  Slctrl,
+	Sesc, 0,
+	Sbslash, Slctrl,
+	Srbrace, Slctrl,
+	Speriod, Slctrl,
+	Sslash,  Slctrl,
+};
+
+void
+kbdsendscan(int sc, int mod)
+{
+	long msec;
+	int f;
+
+	f = 0;
+	if(sc&Sext)
+		f = KeyEx;
+	sc &= ~Sext;
+
+	msec = time(nil);
+	if(mod != 0)
+		passinput(msec, InputKeycode, 0, mod, 0);
+	passinput(msec, InputKeycode, f|0, sc, 0);
+	passinput(msec, InputKeycode, f|KeyUp, sc, 0);
+	if(mod != 0)
+		passinput(msec, InputKeycode, KeyUp, mod, 0);
+}
+
+void
+kbdsendrune(Rune r)
+{
+	long msec;
+
+	msec = time(nil);
+	passinput(msec, InputUnicode, 0, r, 0);
+	passinput(msec, InputUnicode, KeyUp, r, 0);
+}
+
+void
+readkbd(void)
+{
+	char buf[256], k[10];
+	int ctlfd, fd, kr, kn, w;
+	uchar mod, sc;
+	Rune r;
+
+	if((fd = open("/dev/cons", OREAD)) < 0)
+		sysfatal("open %s: %r", buf);
+	if((ctlfd = open("/dev/consctl", OWRITE)) < 0)
+		sysfatal("open %s: %r", buf);
+	write(ctlfd, "rawon", 5);
+
+	kn = 0;
+	for(;;){
+		while(!fullrune(k, kn)){
+			kr = read(fd, k+kn, sizeof k - kn);
+			if(kr <= 0)
+				sysfatal("bad read from kbd");
+			kn += kr;
+		}
+		w = chartorune(&r, k);
+		kn -= w;
+		memmove(k, &k[w], kn);
+
+		if(r < nelem(rune2scan)){
+			sc = rune2scan[r].sc;
+			mod = rune2scan[r].mod;
+			kbdsendscan(sc, mod);
+			continue;
+		}
+
+		switch(r){
+		case Kins:
+			kbdsendscan(Sins, 0);
+			break;
+		case Kdel:
+			kbdsendscan(Sdel, 0);
+			break;
+		case Khome:
+			kbdsendscan(Shome, 0);
+			break;
+		case Kend:
+			kbdsendscan(Send, 0);
+			break;
+		case Kpgup:
+			kbdsendscan(Spgup, 0);
+			break;
+		case Kpgdown:
+			kbdsendscan(Spgdown, 0);
+			break;
+		case Kup:
+			kbdsendscan(Sup, 0);
+			break;
+		case Kdown:
+			kbdsendscan(Sdown,0 );
+			break;
+		case Kleft:
+			kbdsendscan(Sleft, 0);
+			break;
+		case Kright:
+			kbdsendscan(Sright, 0);
+			break;
+		case Kbrk:
+			exits("interrupt");
+			break;
+		case Kprint:
+			kbdsendscan(Sprint, 0);
+			break;
+		case KF|1:
+		case KF|2:
+		case KF|3:
+		case KF|4:
+		case KF|5:
+		case KF|6:
+		case KF|7:
+		case KF|8:
+		case KF|9:
+		case KF|10:
+			kbdsendscan(SF1+r-(KF|1), 0);
+			break;
+		case KF|11:
+			kbdsendscan(SF11, 0);
+			break;
+		case KF|12:
+			kbdsendscan(SF12, 0);
+			break;
+		default:
+			kbdsendrune(r);
+			break;
+		}
+	}
+}
--- /dev/null
+++ b/load.c
@@ -1,0 +1,70 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+/* like loadimage(2) but reverses scanline order and translates per cmap */
+int
+loadbmp(Image *i, Rectangle r, uchar *data, int ndata)
+{
+	int n, bpl;
+	uchar *a;
+
+	bpl = bytesperline(r, i->depth);
+	n = bpl*Dy(r);
+	if(n > ndata){
+		werrstr("loadbmp: insufficient data");
+		return -1;
+	}
+
+	if(i->depth == 8)
+		for(a = data; a <data+ndata; a++)
+			*a = cmap[*a];
+
+	n = bpl;
+	while(r.max.y > r.min.y){
+		a = bufimage(i->display, 21+n);
+		if(a == nil){
+			werrstr("bufimage failed");
+			return -1;
+		}
+		a[0] = 'y';
+		BPLONG(a+1, i->id);
+		BPLONG(a+5, r.min.x);
+		BPLONG(a+9, r.max.y-1);
+		BPLONG(a+13, r.max.x);
+		BPLONG(a+17, r.max.y);
+		memmove(a+21, data, n);
+		ndata += n;
+		data += n;
+		r.max.y--;
+	}
+	if(flushimage(i->display, 0) < 0)
+		return -1;
+	return ndata;
+}
+
+int
+loadrle(Image *i, Rectangle r, uchar *data, int ndata)
+{
+	int nb, bpl;
+	uchar *buf;
+
+	bpl = bytesperline(r, i->depth);
+	nb = bpl*Dy(r);
+	buf = emalloc(nb);
+
+	if(unrle(buf, nb, data, ndata, bpl, bpl/Dx(r)) == nil){
+		werrstr("loadrle: decompression failed");
+		free(buf);
+		return -1;
+	}
+	if(loadbmp(i, r, buf, nb) < 0){
+		werrstr("loadrle: r=%R i->r=%R: %r", r, i->r);
+		free(buf);
+		return -1;
+	}
+	free(buf);
+	return nb;
+}
--- /dev/null
+++ b/mcs.c
@@ -1,0 +1,591 @@
+/* T.122 MCS, T.124 Generic Conference Control, T.125 MCS protocol  */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+enum
+{
+	/* ASN.1 Universal tags */
+	TagBool		= 1,
+	TagInt		= 2,
+	TagOctetString	= 4,
+	TagEnum		= 10,
+	TagSeq		= 16,		/* also TagSeq OF */
+
+	/* ASN.1 tag numbers for MCS types */
+	Mci=		101,		/* Connect Initial */
+	Mcr=	102,		/* Connect Response */
+	Medr=	1,		/* Erect Domain Request */
+	Maur=	10,		/* Attach User Request */
+	Mauc=	11,		/* Attach User Confirm */
+	Mcjr=	14,		/* Channel Join Request */
+	Mcjc=	15,		/* Channel Join Confirm */
+	Msdr=	25,		/* Send Data Request */
+	Msdi=	26,		/* Send Data Indication */
+	Mdpu=	8,		/* Disconnect Provider Ultimatum */
+
+	Musrchanbase=	1001,
+
+	/* 2.2.1.3 Client MCS Connect Initial PDU with GCC Conference Create Request */
+	ClientCore=		0xC001,
+	ClientCluster=		0xC004,
+	ClientSec=		0xC002,
+	ClientNet=		0xC003,
+
+	CanErrinfo=	1,
+	Want32bpp=	2,
+
+	/* 2.2.1.3.1 User Data Header (TS_UD_HEADER) */
+	SrvCore				= 0x0C01,
+
+};
+
+int	mkgcccr(uchar*,int);
+int	sizegcccr(void);
+
+enum
+{
+	Bits5	= 0x1F,
+	Bits7 = 0x7F,
+};
+
+static	uchar*	gblen(uchar*,uchar*,int*);
+static	uchar*	gbtag(uchar*,uchar*,int*);
+static	void		pbshort(uchar*,int);
+
+static uchar*
+gbuint7(uchar *p, uchar* ep, int* pv)
+{
+	uint u,v,go;
+
+	v = 0;
+	go = 1;
+	while(go){
+		if(p >= ep){
+			werrstr(Eshort);
+			return nil;
+		}
+		u = *p;
+		v = (v<<7) | (u&Bits7);
+		if(v&(Bits7<<24)){
+			werrstr(Ebignum);
+			return nil;
+		}
+		go = u&(1<<7);
+		p++;
+	}
+	*pv = v;
+	return p;	
+}
+
+static uchar*
+gbtag(uchar *p, uchar* ep, int* ptag)
+{
+	if(p >= ep){
+		werrstr(Eshort);
+		return nil;
+	}
+	*ptag = p[0] & Bits5;
+	p += 1;
+	if(*ptag == Bits5)
+		p = gbuint7(p, ep, ptag);
+	return p;
+}
+
+static uchar*
+gblen(uchar *p, uchar* ep, int* plen)
+{
+	int c,v;
+
+	if(p >= ep){
+		werrstr(Eshort);
+		return nil;
+	}
+
+	v = *p++;
+	if(v < (1<<7)){
+		*plen = v;
+		return p;
+	}
+	c = v&Bits7;
+	if(p+c >= ep){
+		werrstr(Eshort);
+		return nil;
+	}
+	switch(c){
+	default:	werrstr(Ebignum); return nil;
+	case 0:	*plen = 0; break;
+	case 1:	*plen = p[0]; break;
+	case 2:	*plen = GSHORTB(p); break;
+	case 3:	*plen = (GSHORTB(p)<<8)|p[2]; break;
+	case 4:	*plen = GLONGB(p); break;
+	}
+	return p+c;
+}
+
+static void
+pbshort(uchar* p, int v)
+{
+	p[0]=2;
+	p[1]=2;
+	PSHORTB(p+2,v);
+}
+
+int
+mcstype(uchar* p, uchar* ep)
+{
+	if(!isdatatpdu(p,ep)){
+		werrstr("not an X.224 Data TPDU");
+		return -1;
+	}
+	p = tpdupayload(p, ep);
+	if(p == nil)
+		return -1;
+	if(p >= ep){
+		werrstr(Eshort);
+		return -1;
+	}
+	return p[0]>>2;
+}
+
+int
+ismcshangup(uchar* p, uchar* ep)
+{
+	return (mcstype(p,ep) == Mdpu);
+}
+
+int
+mcschanid(uchar *p, uchar* ep)
+{
+	if(mcstype(p,ep) != Msdi){
+		werrstr("not an MCS Send Data Indication: %r");
+		return -1;
+	}
+	if((p = tpdupayload(p, ep)) == nil)
+		return -1;
+	if(p+5 > ep){
+		werrstr(Eshort);
+		return -1;
+	}
+	return GSHORTB(p+3);
+}
+
+uchar*
+mcspayload(uchar *p, uchar* ep)
+{
+	if(mcstype(p,ep) != Msdi){
+		werrstr("not an MCS Send Data Indication: %r");
+		return nil;
+	}
+	if((p = tpdupayload(p, ep)) == nil)
+		return nil;
+
+	if(p+6 > ep){
+		werrstr(Eshort);
+		return nil;
+	}
+	if(p[6] & 0x80)
+		p += 8;
+	else
+		p += 7;
+	if(p > ep){
+		werrstr(Eshort);
+		return nil;
+	}
+	return p;
+}
+
+/* MCS Send Data Request */
+int
+mkmcssdr(uchar* p, int nb, int ndata, int chanid)
+{
+	if(nb < 8){
+		werrstr(Esmall);
+		return -1;
+	}
+	
+	p[0] = (Msdr<<2);
+	PSHORTB(p+1, rd.mcsuid);
+	PSHORTB(p+3, chanid);
+	p[5] = 0x70;
+	PSHORTB(p+6, ndata|0x8000);
+	return 8;
+}
+
+/* 2.2.1.3 Client MCS Connect Initial PDU with GCC Conference Create Request */
+int
+mkmcsci(uchar* buf, int nbuf, int ndata)
+{
+	uchar* p;
+	p = buf;
+	if(nbuf < ndata+MCSCIFIXLEN){
+		werrstr("buffer too small");
+		return -1;
+	}
+
+	PSHORTB(p, 0x7f65);	/* Connect-Initial tag */
+	p[2] = 0x82;		/* length in next 2 bytes  */
+	PSHORTB(p+3, ndata+MCSCIFIXLEN-2*2-1);
+	p += 5;
+
+	/* BER callingDomainSelector */
+	p[0] = TagOctetString;
+	p[1] = 1;		/* len */
+	p[2] = 1;		
+	p += 3;
+	/* BER calledDomainSelector */
+	p[0] = TagOctetString;
+	p[1] = 1;		/* len */
+	p[2] = 1;		
+	p += 3;
+	/* BER upwardFlag */
+	p[0] = TagBool;
+	p[1] = 1;		/* len */
+	p[2] = 0xff;	
+	p += 3;
+
+	/* BER MCS DomainParamaters: targetParameters */
+	p[0] = 0x30;			/* tag */
+	p[1] = 8*4;	/* len */
+	pbshort(p+2, 34);	/* maxChannelIds */
+	pbshort(p+6, 2); 	/* maxUserIds */
+	pbshort(p+10, 0);	/* maxTokenIds */
+	pbshort(p+14, 1);	/* maxPriorities */
+	pbshort(p+18, 0);	/* minThroughput */
+	pbshort(p+22, 1);	/* maxHeight (of a MCS provider) */
+	pbshort(p+26, 65535);	/* maxMCSPDUsize */
+	pbshort(p+30, 2);	/* (MCS) protocolVersion */
+	p += 34;
+
+	/* BER MCS DomainParamaters: minimumParameters */
+	p[0] = 0x30;			/* tag */
+	p[1] = 8*4;	/* len */
+	pbshort(p+2, 1);	/* maxChannelIds */
+	pbshort(p+6, 1); 	/* maxUserIds */
+	pbshort(p+10, 1);	/* maxTokenIds */
+	pbshort(p+14, 1);	/* maxPriorities */
+	pbshort(p+18, 0);	/* minThroughput */
+	pbshort(p+22, 1);	/* maxHeight (of a MCS provider) */
+	pbshort(p+26, 1056);	/* maxMCSPDUsize */
+	pbshort(p+30, 2);	/* (MCS) protocolVersion */
+	p += 34;
+
+	/* BER MCS DomainParamaters: maximumParameters */
+	p[0] = 0x30;			/* tag */
+	p[1] = 8*4;	/* len */
+	pbshort(p+2, 65535);	/* maxChannelIds */
+	pbshort(p+6, 65535); 	/* maxUserIds */
+	pbshort(p+10, 65535);	/* maxTokenIds */
+	pbshort(p+14, 1);	/* maxPriorities */
+	pbshort(p+18, 0);	/* minThroughput */
+	pbshort(p+22, 1);	/* maxHeight (of a MCS provider) */
+	pbshort(p+26, 65535);	/* maxMCSPDUsize */
+	pbshort(p+30, 2);	/* (MCS) protocolVersion */
+	p += 34;
+
+	/* BER userData */
+	p[0] = TagOctetString;
+	p[1] = 0x82;			/* length in next 2 bytes  */
+	PSHORTB(p+2, ndata);
+	/* userData should follow */
+
+	return MCSCIFIXLEN+ndata;
+}
+
+/* GCC Conference Create Request  [T.124 section 8.7] in ASN.1 PER [X.691] */
+int
+sizegcccr(void)
+{
+	int size;
+	size = 9+14+216+12+12 + 8+12*nvc;
+	return size;	// should agree with the below
+}
+
+int
+mkgcccr(uchar* buf, int nb)
+{
+	int i;
+	uchar *p, *ep;
+	long gccsize, earlyCapabilityFlags;
+
+	p = buf;
+	ep = buf+nb;
+	gccsize = sizegcccr()-9;
+	if(p+gccsize+9 > ep){
+		werrstr(Eshort);
+		return -1;
+	}
+
+	earlyCapabilityFlags = CanErrinfo;
+	if(rd.depth == 32)
+		earlyCapabilityFlags |= Want32bpp;
+
+	// t124IdentifierKey: 0.0.20.124.0.1
+	p[0] = 0;
+	p[1] = 5;
+	p[2] = 0;
+	p[3] = 20;
+	p[4] = 124;
+	p[5] = 0;
+	p[6] = 1;
+
+	// connectPDU as a PER octet string
+	PSHORTB(p+7, (gccsize | 0x8000));	// connectPDU length
+	PSHORTB(p+9, 8);		// ConferenceCreateRequest
+	PSHORTB(p+11, 16);
+	p[13] = 0;
+	PSHORT(p+14, 0xC001);	// userData key: h221NonStandard. Yes, in LE.
+	p[16] = 0;
+	memcpy(p+17, "Duca", 4);	// H.221 nonstandard key (as mandated in 3.2.5.3.3)
+	PSHORTB(p+21, ((gccsize-14) | 0x8000));		// userData length
+	p += 23;
+
+	// 2.2.1.3.2 Client Core Data
+	PSHORT(p+0, ClientCore);
+	PSHORT(p+2, 216);	// length of the data block
+	PLONG(p+4, 0x00080004);	// rdpVersion: RDP5=0x00080004
+	PSHORT(p+8, rd.dim.x);	// desktopWidth ≤ 4096
+	PSHORT(p+10, rd.dim.y);	// desktopHeight ≤ 2048
+	PSHORT(p+12, 0xCA01);	// colorDepth=8bpp, ignored
+	PSHORT(p+14, 0xAA03);	// SASSequence
+	PLONG(p+16, 0x409);	// keyboardLayout=us
+	PLONG(p+20, 2600); 	// clientBuild
+	toutf16(p+24, 32, rd.local, strlen(rd.local));	// clientName[32]
+	PSHORT(p+54, 0);		// zero-terminateclientName
+	PLONG(p+56, 4);	// keyboardType: 4="IBM enhanced (101-key or 102-key)"
+	PLONG(p+60, 0);	// keyboardSubType
+	PLONG(p+64, 12);	// keyboardFunctionKey
+	memset(p+68, 64, 0);	// imeFileName[64]
+	PSHORT(p+132, 0xCA01);	// postBeta2ColorDepth=8bpp, ignored
+	PSHORT(p+134, 1);	// clientProductId
+	PLONG(p+136, 0);	// serialNumber
+	PSHORT(p+140, MIN(rd.depth, 24));	// highColorDepth: 4, 8, 15, 16, 24 bpp.
+	PSHORT(p+142, 1+2+4+8);	// supportedColorDepths: 1=24, 2=16, 4=15, 8=32 bpp
+	PSHORT(p+144, earlyCapabilityFlags);	// earlyCapabilityFlags 
+	memset(p+146, 64, 0);	// clientDigProductId[64]
+	p[210] = 7;	// connectionType: 7=autodetect
+	p[211] = 0;	// pad1octet
+	PLONG(p+212, rd.sproto);	// serverSelectedProtocol
+	p += 216;
+	
+	// 2.2.1.3.3 Client Security Data
+	PSHORT(p+0, ClientSec);
+	PSHORT(p+2, 12);	// length of the data block
+	PLONG(p+4, 0); 	// (legacy) encryptionMethods
+	PLONG(p+8, 0); 	// extEncryptionMethods
+	p += 12;
+
+	// 2.2.1.3.5 Client Cluster Data		*optional*
+	PSHORT(p+0, ClientCluster);
+	PSHORT(p+2, 12);	// length of the data block
+	PLONG(p+4, (rd.wantconsole? 11 : 9));	// Flags
+	PLONG(p+8, 0);		// RedirectedSessionID
+	p += 12;
+
+	// 2.2.1.3.4 Client Network Data 	*optional*
+	// type[2] len[2] nchan[4] nchan*(name[8] options[4])
+	PSHORT(p+0, ClientNet);
+	PSHORT(p+2, 8+12*nvc);
+	PLONG(p+4, nvc);
+	for(i=0; i<nvc; i++){
+		memcpy(p+8+12*i+0, vctab[i].name, 8);
+		PLONGB(p+8+12*i+8, vctab[i].flags);
+	}
+	p += 8+12*nvc;
+
+	return p-buf;
+}
+
+void
+erectdom(int fd)
+{
+	uchar buf[20], *p;
+	int len, nb;
+
+	p = buf;
+	nb = sizeof(buf);
+	len = mktpdat(buf, nb, 5);
+	if(len < 0)
+		sysfatal("mktpdat: %r");
+	p += TPDATAFIXLEN;
+	
+	p[0] = (Medr<<2);
+	PSHORTB(p+1, 1);
+	PSHORTB(p+3, 1);	
+	if(writen(fd, buf, len) != len)
+		sysfatal("Erect Domain: write: %r");
+}
+
+int
+attachuser(int fd)
+{
+	int len, tag, r, nb;
+	uchar buf[20], *p, *ep;
+
+	nb = sizeof(buf);
+	len = mktpdat(buf, nb, 1);
+	if(len < 0)
+		sysfatal("mktpdat: %r");
+	buf[TPDATAFIXLEN] = (Maur<<2);
+	if(writen(fd, buf, len) != len)
+		sysfatal("Attach User: write: %r");
+
+	len = readpdu(fd, buf, nb);
+	if(len <= 0)
+		sysfatal("readpdu: %r");
+	p = buf;
+	ep = buf+len;
+	if(!isdatatpdu(p,ep))
+		sysfatal("MCS: expected Data TPDU\n");
+	p = tpdupayload(p, ep);
+	if(p+2 > ep)
+		sysfatal(Eshort);
+
+	tag = p[0]>>2;
+	r = p[1];
+	if(tag != Mauc)
+		sysfatal("expected tag %d (Mauc), got %d", Mauc, tag);
+	if(r != 0)
+		sysfatal("Mauc error result: %d", r);
+	if((p[0])&2){
+		if(p+4 > ep)
+			sysfatal(Eshort);
+		rd.mcsuid = GSHORTB(p+2);
+		rd.userchan = rd.mcsuid+Musrchanbase;
+	}
+	return r;
+}
+
+int
+joinchannel(int fd, int chanid)
+{
+	uchar buf[32], *p, *ep;
+	int tag, len, r, nb;
+
+	p = buf;
+	nb = sizeof(buf);
+	len = mktpdat(buf, nb, 5);
+	if(len < 0)
+		sysfatal("mktpdat: %r");
+	p += TPDATAFIXLEN;
+	p[0] = (Mcjr << 2);
+	PSHORTB(p+1, rd.mcsuid);
+	PSHORTB(p+3, chanid);
+	if(writen(fd, buf, len) != len)
+		sysfatal("Channel Join: write: %r");
+
+	len = readpdu(fd, buf, nb);
+	if(len <= 0)
+		sysfatal("readpdu: %r");
+	p = buf;
+	ep = buf+len;
+	if(!isdatatpdu(p,ep))
+		sysfatal("MCS: expected Data TPDU\n");
+	p = tpdupayload(p, ep);
+	if(p+2 > ep)
+		sysfatal(Eshort);
+
+	tag = p[0]>>2;
+	r = p[1];
+	if(tag != Mcjc)
+		sysfatal("expected tag %d (Mcjc), got %d", Mcjc, tag);
+	if(r != 0)
+		sysfatal("Mcjc error result: %d", r);
+
+	return r;
+
+}
+
+int
+mcsconnect(int fd)
+{
+	uchar buf[MAXTPDU], *p, *ep;
+	int n, ndata, nb, len, tag, r, ver, utype, ulen;
+
+	/* 2.2.1.3 Client MCS Connect Initial PDU with GCC Conference Create Request */
+	nb = sizeof(buf);
+	ndata = sizegcccr();
+	len = mktpdat(buf, nb, ndata+MCSCIFIXLEN);
+	if(len < 0)
+		sysfatal("mktpdat: %r");
+	p = buf+TPDATAFIXLEN;
+	ep = buf+nb;
+	n = mkmcsci(p, ep-p, ndata);
+	if(n != ndata+MCSCIFIXLEN)
+		sysfatal("mkmcsci: %r");
+	n = mkgcccr(p+MCSCIFIXLEN, ndata);
+	if(n != ndata)
+		sysfatal("mkgcccr: %r");
+	if(writen(fd, buf, len) != len)
+		sysfatal("TPDUDT: write: %r");
+
+	/* 2.2.1.4 Server MCS Connect Response PDU with GCC Conference Create Response */
+	len = readpdu(fd, buf, nb);
+	if(len <= 0){
+		werrstr("read MCS Connect Response PDU: %r");
+		return -1;
+	}
+	p = buf;
+	ep = buf+len;
+
+	if(!isdatatpdu(p,ep)){
+		werrstr("MCS: expected Data TPDU\n");
+		return -1;
+	}
+	p = tpdupayload(p, ep);
+
+	/* at MCS Connect-Response ASN.1 BER-encoded structure */
+	if((p = gbtag(p, ep, &tag)) == nil || tag != Mcr || (p = gblen(p, ep, &len)) == nil)
+		return -1;
+	
+	/* result */
+	if((p = gbtag(p, ep, &tag)) == nil || tag != TagEnum
+		|| (p = gblen(p, ep, &len)) == nil || len < 0 || p+len > ep)
+		return -1;
+	r = p[0];
+	if(r != 0){
+		werrstr("MCS Connect-Response: %d", r);
+		return -1;
+	}
+	p += len;
+
+	/* calledConnectId */
+	if((p = gbtag(p, ep, &tag)) == nil || tag != TagInt
+		|| (p = gblen(p, ep, &len)) == nil || len < 0 || p+len > ep)
+		return -1;
+	p += len;
+
+	/* domainParamaters */
+	if((p = gbtag(p, ep, &tag)) == nil || tag != TagSeq
+		|| (p = gblen(p, ep, &len)) == nil || len < 0 || p+len > ep)
+		return -1;
+	p += len;
+
+	/* Mcr userData */
+	if((p = gbtag(p, ep, &tag)) == nil || tag != TagOctetString
+		|| (p = gblen(p, ep, &len)) == nil || len < 0 || p+len > ep)
+		return -1;
+
+	/* GCC ConferenceCreateResponse [T.124] sect 8.7 */
+	if(p[21]&(1<<7))
+		p += 23;
+	else
+		p += 22;
+
+	while(p<ep){
+		/* 2.2.1.3.1 User Data Header (TS_UD_HEADER) */
+		utype = GSHORT(p+0);
+		ulen = GSHORT(p+2);
+		switch(utype){
+		case SrvCore:		/* 2.2.1.4.2 Server Core Data */
+			ver = GLONG(p+4);
+			assert(ver >= 0x00080004);
+			break;
+		}
+		p += ulen;
+	}
+
+	return r;
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,42 @@
+</$objtype/mkfile
+
+TARG=rd
+BIN=/$objtype/bin
+
+HFILES=fns.h dat.h
+CLEANFILES= x509.c
+OFILES=\
+	cap.$O\
+	eclip.$O\
+	egdi.$O\
+	ele.$O\
+	kbd.$O\
+	rle.$O\
+	load.$O\
+	mcs.$O\
+	mouse.$O\
+	mpas.$O\
+	mppc.$O\
+	rd.$O\
+	utf16.$O\
+	vchan.$O\
+	wsys.$O\
+	x224.$O\
+
+</sys/src/cmd/mkone
+
+x509.c:	/sys/src/libsec/port/x509.c
+	sed '
+	/^	ALG_sha1WithRSAEncryption,/a\
+		ALG_sha256WithRSAEncryption,\
+		ALG_shaWithRSASignatureOiw,
+	/^static Ints7 oid_sha1WithRSAEncryption =/a\
+	static Ints7 oid_sha256WithRSAEncryption ={7, 1, 2, 840, 113549, 1, 1, 11 };\
+	static Ints7 oid_shaWithRSASignatureOiw ={6, 1, 3, 14, 3, 2, 15 };
+	/^	\(Ints\*\)\&oid_sha1WithRSAEncryption,/a\
+		(Ints*)&oid_sha256WithRSAEncryption,\
+		(Ints*)&oid_shaWithRSASignatureOiw,
+	/^static DigestFun digestalg/ s/sha1,/sha1, sha2_256, sha1,/
+	' $prereq > $target
+
+$TARG: mkfile
--- /dev/null
+++ b/mouse.c
@@ -1,0 +1,100 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <mouse.h>
+#include "dat.h"
+#include "fns.h"
+
+enum {
+	MOUSE_FLAG_MOVE	= 0x0800,
+	MOUSE_FLAG_BUTTON1	= 0x1000,
+	MOUSE_FLAG_BUTTON2	= 0x2000,
+	MOUSE_FLAG_BUTTON3	= 0x4000,
+	MOUSE_FLAG_BUTTON4	= 0x0280,
+	MOUSE_FLAG_BUTTON5	= 0x0380,
+	MOUSE_FLAG_DOWN	= 0x8000,
+};
+
+static int mfd = -1;
+
+static void
+sendmouse(Mouse m, int flags)
+{
+	passinput(m.msec, InputMouse, flags, m.xy.x, m.xy.y);
+}
+
+static void
+mouseevent(Mouse m)
+{
+	ushort flags;
+	int chg;
+	static Mouse o;
+
+	switch(m.buttons){
+	case 8:
+		sendmouse(m, MOUSE_FLAG_BUTTON4|MOUSE_FLAG_DOWN);
+		sendmouse(m, MOUSE_FLAG_BUTTON4);
+		return;
+	case 16:
+		sendmouse(m, MOUSE_FLAG_BUTTON5|MOUSE_FLAG_DOWN);
+		sendmouse(m, MOUSE_FLAG_BUTTON5);
+		return;
+	}
+
+	if(!eqpt(m.xy, o.xy))
+		sendmouse(m, MOUSE_FLAG_MOVE);
+
+	chg = m.buttons ^ o.buttons;
+	if(chg&1){
+		flags = MOUSE_FLAG_BUTTON1;
+		if(m.buttons&1)
+			flags |= MOUSE_FLAG_DOWN;
+		sendmouse(m, flags);
+	}
+	if(chg&2){
+		flags = MOUSE_FLAG_BUTTON3;
+		if(m.buttons&2)
+			flags |= MOUSE_FLAG_DOWN;
+		sendmouse(m, flags);
+	}
+	if(chg&4){
+		flags = MOUSE_FLAG_BUTTON2;
+		if(m.buttons&4)
+			flags |= MOUSE_FLAG_DOWN;
+		sendmouse(m, flags);
+	}
+	o = m;
+}
+
+void
+readdevmouse(void)
+{
+	Mouse m;
+	char ev[1+4*12];
+
+	if((mfd = open("/dev/mouse", ORDWR)) < 0)
+		sysfatal("open /dev/mouse: %r");
+
+	for(;;){
+		if(read(mfd, ev, sizeof ev) != sizeof ev)
+			sysfatal("mouse eof");
+		if(*ev == 'm'){
+				m.xy.x = atoi(ev+1);
+				m.xy.y = atoi(ev+1+12);
+				m.buttons = atoi(ev+1+2*12) & 0x1F;
+				m.msec = atoi(ev+1+3*12);
+				m.xy = subpt(m.xy, screen->r.min);
+				mouseevent(m);
+		}else
+			eresized(1);
+	}
+}
+
+void
+warpmouse(int x, int y)
+{
+	if(mfd < 0)
+		return;
+
+	fprint(mfd, "m%d %d", x, y);
+}
--- /dev/null
+++ b/mpas.c
@@ -1,0 +1,884 @@
+/*
+ * Subset of: T.128 Multipoint application sharing
+ * 
+ * 2.2.8.1.1.1.1 Share Control Header (TS_SHARECONTROLHEADER)
+ * http://msdn.microsoft.com/en-us/library/cc240576.aspx
+ *
+ * totalLen[2] pduType[2] PDUSource[2] 
+ *
+ * 2.2.9.1.1.3 says there may be many of these.
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+#define DBG if(0)
+//#define DBG
+
+
+uchar	cmap[256];
+
+static const char	srcDesc[] = "Plan 9";	/* sourceDescriptor (T.128 section 8.4.1) */
+
+static void		scanfastpath(uchar*,uchar*);
+static void		scancursorpdu(uchar*, uchar*);
+static void		scangraphpdu(uchar*,uchar*);
+static void		scanimgupdate(uchar*,uchar*);
+static void		scanpalette(uchar*,uchar*);
+static void		scansrvcapset(uchar*,uchar*);
+static void		sendclientinfo(void);
+static void		confirmactive(void);
+static uchar*	putsdh(uchar*,uchar*,int,int);
+static void		assync(void);
+static void		asctl(int);
+static void		asfontls(void);
+
+enum
+{
+	Bits4=		0x0F,
+	SECHSIZE=	4,
+	SCHSIZE=		6,
+	SCDSIZE=		SCHSIZE+4+4+2*2,
+};
+
+enum /* 2.2.8.1.1.1.1 Share Control Header (TS_SHARECONTROLHEADER) */
+{
+	PDUTYPE_DEMANDACTIVEPDU	= 1,	/* Demand Active PDU (section 2.2.1.13.1) */
+	PDUTYPE_CONFIRMACTIVEPDU	= 3,	/* Confirm Active PDU (section 2.2.1.13.2) */
+	PDUTYPE_DEACTIVATEALLPDU	= 6,	/* Deactivate All PDU (section 2.2.3.1) */
+	PDUTYPE_DATAPDU				= 7,	/* Data PDU */
+	PDUTYPE_SERVER_REDIR_PKT		= 10,	/* Redirection PDU (section 2.2.13.3.1). */
+};
+
+enum /* 2.2.8.1.1.1.2 Share Data Header (TS_SHAREDATAHEADER) */
+{
+	PDUTYPE2_UPDATE=	2,
+	PDUTYPE2_CONTROL=	20,
+	PDUTYPE2_POINTER=	27,
+	PDUTYPE2_INPUT=		28,
+	PDUTYPE2_SYNCHRONIZE=	31,
+	PDUTYPE2_REFRESH_RECT=	33,
+	PDUTYPE2_SUPPRESS_OUTPUT=	35,
+	PDUTYPE2_FONTLIST=	39,
+	PDUTYPE2_FONTMAP=	40,
+	PDUTYPE2_SET_ERROR_INFO_PDU=	47,
+};
+
+enum /* 2.2.9.1.1.4 Server Pointer Update PDU (TS_POINTER_PDU) */
+{
+	TS_PTRMSGTYPE_SYSTEM=	1,
+	TS_PTRMSGTYPE_POSITION=	3,
+	TS_PTRMSGTYPE_COLOR=	6,
+	TS_PTRMSGTYPE_CACHED=	7,
+	TS_PTRMSGTYPE_POINTER=	8,
+};
+
+enum /* 2.2.9.1.1.3.1.2.2 Bitmap Data */
+{
+	Bcompress=	1,
+	Pcompress=	0x20,
+};
+
+enum /* 2.2.1.15.1 Control PDU Data */
+{
+	CTRLACTION_REQUEST_CONTROL= 	1,
+	CTRLACTION_GRANTED_CONTROL=	2,
+	CTRLACTION_DETACH=				3,
+	CTRLACTION_COOPERATE=			4,
+};
+
+enum /* 2.2.1.11.1.1 Info Packet (TS_INFO_PACKET) */
+{
+	INFO_MOUSE=			0x1,
+	INFO_DISABLECTRLALTDEL=	0x2,
+	INFO_AUTOLOGON=	0x8,
+	INFO_UNICODE=		0x10,
+	INFO_MAXIMIZESHELL=	0x20,
+	INFO_COMPRESSION=	0x80,
+	CompressionTypeMask=	0x1E00,
+		PACKET_COMPR_TYPE_8K=	0<<9,	// RDP 4.0 bulk compression ≡ MPPC
+		PACKET_COMPR_TYPE_64K=	1<<9,	// RDP 5.0 bulk compression (3.1.8.4.2)
+		PACKET_COMPR_TYPE_RDP6=	2<<9,	// RDP 6.0 bulk compression
+		PACKET_COMPR_TYPE_RDP61= 3<<9,	// RDP 6.1 bulk compression
+	INFO_ENABLEWINDOWSKEY=		0x100,
+	INFO_REMOTECONSOLEAUDIO=	0x2000,
+	INFO_FORCE_ENCRYPTED_CS_PDU=	0x4000,
+	INFO_RAIL=				0x8000,
+	INFO_LOGONERRORS=		0x10000,
+	INFO_MOUSE_HAS_WHEEL=	0x20000,
+	INFO_NOAUDIOPLAYBACK=	0x80000,
+	INFO_VIDEO_DISABLE=	0x400000,
+
+	PERF_DISABLE_WALLPAPER=	1<<0,
+	PERF_DISABLE_FULLWINDOWDRAG=	1<<1,
+	PERF_DISABLE_MENUANIMATIONS=	1<<2,
+	PERF_DISABLE_THEMING=	1<<3,
+	PERF_DISABLE_CURSOR_SHADOW=		1<<5,
+	PERF_DISABLE_CURSORSETTINGS=		1<<6,
+	PERF_ENABLE_FONT_SMOOTHING=	1<<7,
+};
+
+enum
+{
+	UPDATETYPE_ORDERS		= 0, 		/* [MS-RDPEGDI] section 2.2.2.2 */
+ 	UPDATETYPE_BITMAP		= 1,		/* section 2.2.9.1.1.3.1.2 */
+	UPDATETYPE_PALETTE		= 2,		/* section 2.2.9.1.1.3.1.1 */
+	UPDATETYPE_SYNCHRONIZE	= 3,		/* section 2.2.9.1.1.3.1.3 */
+};
+
+enum /* 2.2.9.1.2.1 Fast-Path Update (TS_FP_UPDATE) */
+{
+	FASTPATH_UPDATETYPE_ORDERS		= 0,	/* [MS-RDPEGDI] section 2.2.2.2 */
+	FASTPATH_UPDATETYPE_BITMAP		= 1,
+	FASTPATH_UPDATETYPE_PALETTE		= 2,
+	FASTPATH_UPDATETYPE_SYNCHRONIZE	= 3,
+	FASTPATH_UPDATETYPE_SURFCMDS	= 4,
+	FASTPATH_UPDATETYPE_PTR_NULL	= 5,
+	FASTPATH_UPDATETYPE_PTR_DEFAULT	= 6,
+	FASTPATH_UPDATETYPE_PTR_POSITION	= 8,
+	FASTPATH_UPDATETYPE_COLOR		= 9,
+	FASTPATH_UPDATETYPE_CACHED		= 10,
+	FASTPATH_UPDATETYPE_POINTER		= 11,
+
+};
+
+int
+rdphandshake(int)
+{
+	int i;
+
+	if(mcsconnect(rd.fd) < 0)
+		return -1;
+	erectdom(rd.fd);
+	if(attachuser(rd.fd) < 0)
+		return -1;
+	if(joinchannel(rd.fd, rd.userchan) < 0)
+		return -1;
+	if(joinchannel(rd.fd, GLOBALCHAN) < 0)
+		return -1;
+	for(i = 0; i < nvc; i++)
+		if(joinchannel(rd.fd, vctab[i].mcsid) < 0)
+			return -1;
+
+	sendclientinfo();
+	return rd.fd;
+}
+
+void
+readnet(int fd)
+{
+	int chanid, len, flags;
+	uchar *p, *ep, buf[MAXTPDU];
+	
+	for(;;){
+		len = readpdu(fd, buf, sizeof(buf));
+		if(len <= 0){
+			if(rd.active && !rd.hupreason)
+				fprint(2, "readpdu: %r\n");
+			return;
+		}
+		p = buf;
+		ep = buf+len;
+	
+		if(istpkt(p,ep) == 0){
+			scanfastpath(p, ep);
+			continue;
+		}
+		if(ismcshangup(p,ep)){
+			werrstr("Disconnect Provider Ultimatum");
+			return;
+		}
+
+		chanid = mcschanid(p,ep);
+		if(chanid < 0)
+			sysfatal("mcschanid: %r");
+
+		p = mcspayload(p, ep);
+
+		flags = GSHORT(p);
+		if(!rd.licensed && flags&Slicensepk){
+			/*
+			 * 2.2.8.1.1.2.1 Basic (TS_SECURITY_HEADER)
+			 * http://msdn.microsoft.com/en-us/library/cc240579.aspx
+			 */
+			p += SECHSIZE;
+			if(flags&Slicensepk){
+				scanlicensepdu(p, ep);
+				continue;
+			}
+			if(flags&Scrypt)
+				sysfatal("legacy encryption of a Slow-Path PDU");
+		}
+
+		if(chanid != GLOBALCHAN){
+			scanvcpdu(p, ep, chanid);
+			continue;
+		}
+
+		if(isflowpdu(p,ep))
+			continue;
+
+		scanaspdu(p,ep);
+	}
+}
+
+/* T.128 FlowPDU */
+int
+isflowpdu(uchar* p, uchar* ep)
+{
+	int marker;
+
+	if(p+2 > ep){
+		werrstr(Eshort);
+		return -1;
+	}
+	marker = GSHORT(p);
+	return marker == 0x8000;
+}
+
+/* 2.2.9.1.2 Server Fast-Path Update PDU
+ * enabled with CanFastpath in General Capability Set
+ */
+static void
+scanfastpath(uchar *p, uchar* ep)
+{
+	int hd, nb, nord, cflags, ulen, x, y, enc;
+	uchar *q, *eq;
+
+	enc = p[0]&(1<<7);
+	if(enc)
+		sysfatal("legacy encryption in a Fast-Path PDU");
+	if(p[1]&(1<<7))
+		p += 3;
+	else
+		p += 2;
+
+	eq = ep;
+	while(p+3 < ep){
+		/* updateHeader[1] compressionFlags[1]? size[2] updateData[*] */
+		hd = *p++;
+		if(hd&(1<<7))
+			cflags = *p++;
+		else
+			cflags = 0;
+		if(p+2 > ep)
+			sysfatal(Eshort);
+		nb = GSHORT(p);
+		p += 2;
+		q = p+nb;
+
+		if(cflags&Pcompress){
+			if(p+nb > ep)
+				sysfatal(Eshort);
+			if((p = uncomp(p, nb, cflags, &ulen)) == nil)
+				sysfatal("fast-path packet de-compression failed: %r cflags=%#x", cflags);
+			ep = p+ulen;
+		}
+
+		switch(hd&Bits4){
+		case FASTPATH_UPDATETYPE_ORDERS:
+			nord = GSHORT(p);
+			scanorders(p+2, ep, nord);
+			break;
+		case FASTPATH_UPDATETYPE_BITMAP:
+			scanimgupdate(p, ep);
+			break;
+		case FASTPATH_UPDATETYPE_PALETTE:
+			scanpalette(p, ep);
+			break;
+		case FASTPATH_UPDATETYPE_PTR_POSITION:
+			x = GSHORT(p+0);
+			y = GSHORT(p+2);
+			warpmouse(x, y);
+			break;
+		}
+
+		p = q;
+		ep = eq;
+	}
+
+	lockdisplay(display);
+	flushimage(display, 1);
+	unlockdisplay(display);
+}
+
+/* T.128 ASPDU */
+void
+scanaspdu(uchar* p, uchar* ep)
+{
+	int pdutype, len;
+
+	while(p+SCHSIZE <= ep){
+		len = GSHORT(p);
+		if(len < SCHSIZE || p+len > ep)
+			sysfatal("bad length in Share Control PDU header");
+	
+		pdutype = GSHORT(p+2)&Bits4;
+
+		switch(pdutype){
+		case PDUTYPE_DEMANDACTIVEPDU:
+			activating(p+SCHSIZE, p+len);
+			rd.active = 1;
+			break;
+		case PDUTYPE_DATAPDU:
+			scandatapdu(p+SCHSIZE, p+len);
+			break;
+		case PDUTYPE_DEACTIVATEALLPDU:
+			rd.active = 0;
+			break;
+		}
+		p += len;
+	}
+}
+
+/*
+ * 2.2.8.1.1.1.2 Share Data Header (TS_SHAREDATAHEADER)
+ * http://msdn.microsoft.com/en-us/library/cc240577.aspx
+ *
+ * shareId[4] pad1[1] streamId[1] uncomprLen[2]
+ * pduType2[1] comprType[1] comprLen[2]
+ */
+void
+scandatapdu(uchar *p, uchar* ep)
+{
+	int pduType2, ctype, clen, ulen, ulenr;
+
+	ulen = GSHORT(p+6);
+	pduType2 = p[8];
+	ctype = p[9];
+	clen = GSHORT(p+10);
+	p += 12;
+
+	if(ctype&(1<<5)){
+		clen -= SCDSIZE;
+		if(p+clen > ep)
+			sysfatal(Eshort);
+		if((p = uncomp(p, clen, ctype, &ulenr)) == nil)
+			sysfatal("decompression failed: %r");
+		if(ulen != ulenr+SCDSIZE)
+			sysfatal("bad length after decompression");
+		ep = p+ulenr;
+	}
+
+	switch (pduType2){
+	case PDUTYPE2_SYNCHRONIZE:
+	case PDUTYPE2_CONTROL:
+	case PDUTYPE2_FONTMAP:	/* denotes completion of the connection sequence */
+		break;
+	case PDUTYPE2_SET_ERROR_INFO_PDU:
+		/* 2.2.5.1.1 Set Error Info PDU Data (TS_SET_ERROR_INFO_PDU) */
+		rd.hupreason = GLONG(p);
+		break;
+	case PDUTYPE2_UPDATE:
+		scangraphpdu(p, ep);
+		break;
+	case PDUTYPE2_POINTER:
+		scancursorpdu(p, ep);
+		break;
+	}
+}
+
+/* 2.2.9.1.1.3.1 Slow-Path Graphics Update (TS_GRAPHICS_UPDATE) */
+static void
+scangraphpdu(uchar *p, uchar *ep)
+{
+	int uptype, nord;
+
+	if(p+2 > ep)
+		sysfatal(Eshort);
+
+	uptype = GSHORT(p);
+	switch(uptype){
+	case UPDATETYPE_ORDERS:
+		if(p+8 > ep)
+			sysfatal(Eshort);
+		nord = GSHORT(p+4);
+		scanorders(p+8, ep, nord);
+		break;
+	case UPDATETYPE_BITMAP:
+		scanimgupdate(p, ep);
+		break;
+	case UPDATETYPE_PALETTE:
+		scanpalette(p, ep);
+		break;
+	}
+	lockdisplay(display);
+	flushimage(display, 1);
+	unlockdisplay(display);
+}
+
+/* 2.2.9.1.1.4 Server Pointer Update PDU (TS_POINTER_PDU) */
+static void
+scancursorpdu(uchar* p, uchar* ep)
+{
+	int type, x, y;
+
+	if(p+2 > ep)
+		sysfatal(Eshort);
+	type = GSHORT(p);
+	switch(type){
+	case TS_PTRMSGTYPE_POSITION:
+		if(p+8 > ep)
+			sysfatal(Eshort);
+		x = GSHORT(p+4);
+		y = GSHORT(p+6);
+		warpmouse(x, y);
+		break;
+	}
+}
+
+/* 2.2.9.1.1.3.1.2.1 Bitmap Update Data (TS_UPDATE_BITMAP_DATA) */
+static void
+scanimgupdate(uchar* p, uchar* ep)
+{
+	uchar *s;
+	int err, nr, len, depth, chan, opt;
+	static Image* img;
+	Rectangle r, rs, d;
+
+	if(p+4 > ep)
+		sysfatal(Eshort);
+	chan = rd.chan;
+	rs = rectaddpt(Rpt(ZP, rd.dim), screen->r.min);
+	nr = GSHORT(p+2);
+	p += 4;
+
+	lockdisplay(display);
+
+	if(img==nil || !eqrect(img->r, rs)){
+		if(img != nil)
+			freeimage(img);
+		img = allocimage(display, rs, chan, 0, DNofill);
+		if(img == nil)
+			sysfatal("%r");
+	}
+
+	for(; nr>0 && p+18<ep; nr--){
+		/* 2.2.9.1.1.3.1.2.2 Bitmap Data (TS_BITMAP_DATA) */
+		d.min.x =	GSHORT(p+0);
+		d.min.y =	GSHORT(p+2);
+		d.max.x =	GSHORT(p+4) + 1;
+		d.max.y =	GSHORT(p+6) + 1;
+		r.min = ZP;
+		r.max.x =	GSHORT(p+8);
+		r.max.y =	GSHORT(p+10);
+		depth = 	GSHORT(p+12);
+		opt = 	GSHORT(p+14);
+		len =   	GSHORT(p+16);
+		p +=    	18;
+		s = p+len;
+		if(s > ep)
+			sysfatal(Eshort);
+		if(depth != img->depth)
+			sysfatal("bad image depth");
+		r = rectaddpt(r, img->r.min);
+
+		if(opt&Bcompress)
+		if(!(opt&NoBitcomphdr))
+			p += 8;
+
+		err = (opt&Bcompress? loadrle : loadbmp)(img, r, p, s-p);
+		if(err < 0)
+			sysfatal("%r");
+		draw(screen, rectaddpt(d, screen->r.min), img, nil, img->r.min);
+		p = s;
+	}
+	unlockdisplay(display);
+}
+
+static void
+scanpalette(uchar* p, uchar* ep)
+{
+	int i, n;
+
+	n = GSHORT(p+4);
+	p += 8;
+	if(n > sizeof(cmap)){
+		fprint(2, "scanpalette: palette too big");
+		return;
+	}
+	if(p+3*n > ep)
+		sysfatal(Eshort);
+	for(i = 0; i<n; i++, p+=3)
+		cmap[i] = rgb2cmap(p[0], p[1], p[2]);
+}
+
+static void
+scansrvcapset(uchar *p, uchar *ep)
+{
+	int ncap, type, len;
+
+	ncap = GSHORT(p);
+	p += 4;
+	for(; ncap>0 && p+4<ep; ncap--){
+		type = GSHORT(p+0);
+		len = GSHORT(p+2);
+		if(p+len > ep)
+			sysfatal("bad length in server's capability set");
+		switch (type){
+		case CapGeneral:
+			scangencaps(p, p+len);
+			break;
+		case CapBitmap:
+			scanbitcaps(p, p+len);
+			break;
+		}
+		p += len;
+	}
+}
+
+/*
+ * 2.2.1.13.1 Server Demand Active PDU
+ * http://msdn.microsoft.com/en-us/library/cc240484.aspx
+ */
+void
+activating(uchar* p, uchar* ep)
+{
+	int nsrc, ncaps;
+
+	rd.shareid = GLONG(p);
+	nsrc = GSHORT(p+4);
+	ncaps = GSHORT(p+6);
+	p += 8+nsrc;
+	if(p+ncaps >= ep){
+		werrstr(Eshort);
+		return;
+	}
+	scansrvcapset(p, p+ncaps);
+	confirmactive();
+	// server accepts input since this point
+	passinput(0, InputSync, 0, 0, 0);
+
+	assync();
+	asctl(CTRLACTION_COOPERATE);
+	asctl(CTRLACTION_REQUEST_CONTROL);
+	asfontls();	// unleashes the artist
+}
+
+/* 2.2.1.13.2 Client Confirm Active PDU */
+static void
+confirmactive(void)
+{
+	int ncap, nsrc, capsize, calen, pdusize;
+	uchar buf[512], *p, *q, *ep;
+
+	ncap = 8;
+	nsrc = sizeof(srcDesc);
+	capsize = 0
+		+ GENCAPSIZE
+		+ BITCAPSIZE
+		+ ORDCAPSIZE
+		+ BCACAPSIZE
+		+ PTRCAPSIZE
+		+ INPCAPSIZE
+		+ SNDCAPSIZE
+		+ GLYCAPSIZE
+		;
+	calen = 20+nsrc+capsize;
+
+	p = prebuf(buf, sizeof(buf), calen, 0, 0);
+	if(p == nil)
+		sysfatal("buffer not prepared: %r");
+	ep = p+calen;
+	pdusize = ep-buf;
+	q = p;
+
+	/* 2.2.8.1.1.1.1 Share Control Header */
+	/* totalLength[2] pduType[2] PDUSource[2] */
+	PSHORT(p+0, calen);
+	PSHORT(p+2, PDUTYPE_CONFIRMACTIVEPDU | (1<<4));
+	PSHORT(p+4, rd.userchan);
+
+	/* shareId[4] originatorId[2] sdlen[2] caplen[2] srcdesc[sdlen] ncap[2] pad[2] */
+	PLONG(p+6, rd.shareid);
+	PSHORT(p+10, SRVCHAN);
+	PSHORT(p+12, nsrc);
+	PSHORT(p+14, capsize+4);
+	memcpy(p+16, srcDesc, nsrc);
+	PSHORT(p+16+nsrc, ncap);
+	PSHORT(p+18+nsrc, 0);
+	p += nsrc+20;
+	p = putgencaps(p, ep);
+	p = putbitcaps(p, ep);
+	p = putordcaps(p, ep);
+	p = putbc2caps(p, ep);
+	p = putptrcaps(p, ep);
+	p = putinpcaps(p, ep);
+	p = putsndcaps(p, ep);
+	p = putglycaps(p, ep);
+	assert(p-calen == q);
+
+	writen(rd.fd, buf, pdusize);
+}
+
+/* 2.2.1.11 Client Info PDU */
+static void
+sendclientinfo(void)
+{
+	uchar a[1024], *p, *q;
+	int ndata, secflags, usize;
+	int opt, perfopt;
+	int ndom, nusr, npw, nsh, nwd;
+	uchar *wdom, *wusr, *wpw, *wsh, *wwd;
+
+	ndom = strlen(rd.windom)+1;
+	nusr = strlen(rd.user)+1;
+	npw = strlen(rd.passwd)+1;
+	nsh = strlen(rd.shell)+1;
+	nwd = strlen(rd.rwd)+1;
+	wdom = emalloc(4*ndom);
+	wusr = emalloc(4*nusr);
+	wpw = emalloc(4*npw);
+	wsh = emalloc(4*nsh);
+	wwd = emalloc(4*nwd);
+	ndom = toutf16(wdom, 4*ndom, rd.windom, ndom);
+	nusr = toutf16(wusr, 4*nusr, rd.user, nusr);
+	npw = toutf16(wpw, 4*npw, rd.passwd, npw);
+	nsh = toutf16(wsh, 4*nsh, rd.shell, nsh);
+	nwd = toutf16(wwd, 4*nwd, rd.rwd, nwd);
+
+	ndata = 18+ndom+nusr+npw+nsh+nwd+188;
+	
+	opt = 0
+	| INFO_MOUSE
+	| INFO_UNICODE
+	| INFO_DISABLECTRLALTDEL
+	| INFO_MAXIMIZESHELL
+	| INFO_ENABLEWINDOWSKEY
+	| INFO_FORCE_ENCRYPTED_CS_PDU
+	| INFO_COMPRESSION
+	| PACKET_COMPR_TYPE_8K
+	| PACKET_COMPR_TYPE_64K
+	;
+	perfopt = 0
+	| PERF_DISABLE_FULLWINDOWDRAG
+	| PERF_DISABLE_MENUANIMATIONS
+	| PERF_DISABLE_CURSORSETTINGS
+	| PERF_DISABLE_THEMING
+	;
+	if(rd.autologon)
+		opt |= INFO_AUTOLOGON;
+
+	secflags = Sinfopk;
+	p = prebuf(a, sizeof(a), ndata, 0, secflags);
+	if(p == nil)
+		sysfatal("sendclientinfo: %r");
+	usize = p+ndata-a;
+	q = p;
+
+	PLONG(q+0, 0);	// codePage; langId when opt&INFO_UNICODE
+	PLONG(q+4, opt);
+	PSHORT(q+8, ndom-2);
+	PSHORT(q+10, nusr-2);
+	PSHORT(q+12, npw-2);
+	PSHORT(q+14, nsh-2);
+	PSHORT(q+16, nwd-2);
+	q += 18;
+	memcpy(q, wdom, ndom);
+	q += ndom;
+	memcpy(q, wusr, nusr);
+	q += nusr;
+	memcpy(q, wpw, npw);
+	q += npw;
+	memcpy(q, wsh, nsh);
+	q += nsh;
+	memcpy(q, wwd, nwd);
+	q += nwd;
+
+	PSHORT(q+0, 2);	// cbClientAddress 
+	PSHORT(q+2, 0);	// clientAddress
+	PSHORT(q+4, 2);	// cbClientDir
+	PSHORT(q+6, 0);	// clientDir
+	memset(q+8, 172, 0);	// clientTimeZone
+	PLONG(q+180, 0);	// clientSessionId
+	PLONG(q+184, perfopt);	// performanceFlags 
+	q += 188;
+
+	assert(q == p+ndata);
+
+	free(wdom);
+	free(wusr);
+	free(wpw);
+	free(wsh);
+	free(wwd);
+
+	writen(rd.fd, a, usize);
+	
+}
+
+/* Share-Data Header (2.2.8.1.1.1.2 Share Data Header) */
+static uchar*
+putsdh(uchar* p, uchar* ep, int ndata, int pduType2)
+{
+	if(p+18>ep)
+		sysfatal(Eshort);
+	PSHORT(p+0, ndata);
+	PSHORT(p+2, (PDUTYPE_DATAPDU | 0x10));
+	PSHORT(p+4, rd.userchan);
+	PLONG(p+6, rd.shareid);
+	p[10] = 0;
+	p[11] = 1;
+	PSHORT(p+12, ndata);	// rdesktop used to put ndata-14...
+	p[14] = pduType2;
+	p[15] = 0; // ctype
+	PSHORT(p+16, 0); // clen
+	return p+18;
+}
+
+/* 2.2.1.14 Client Synchronize PDU */
+static void
+assync(void)
+{
+	uchar a[64], *p, *q;
+	int ndata, usize; 
+
+	ndata = 4+SCDSIZE;
+	p = prebuf(a, sizeof(a), ndata, 0, 0);
+	if(p == nil)
+		sysfatal("buffer not prepared: %r");
+	usize = p+ndata-a;
+
+	q = putsdh(p, p+ndata, ndata, PDUTYPE2_SYNCHRONIZE);
+	PSHORT(q+0, 1);
+	PSHORT(q+2, 1002);	// target MCS userId
+	writen(rd.fd, a, usize);
+}
+
+/* 2.2.1.15.1 Control PDU Data (TS_CONTROL_PDU) */
+static void
+asctl(int action)
+{
+	uchar a[64], *p, *q;
+	int ndata, usize; 
+
+	ndata = 8+SCDSIZE;
+	p = prebuf(a, sizeof(a), ndata, 0, 0);
+	if(p == nil)
+		sysfatal("buffer not prepared: %r");
+	usize = p+ndata-a;
+
+	q = putsdh(p, p+ndata, ndata, PDUTYPE2_CONTROL);
+	PSHORT(q+0, action);
+	PSHORT(q+2, 0);	// grantId[2]
+	PLONG(q+4, 0); 	// controlId[2]
+	writen(rd.fd, a, usize);
+}
+
+/* 2.2.1.18 Client Font List PDU */
+static void
+asfontls(void)
+{
+	uchar a[64], *p, *q;
+	int ndata, usize; 
+
+	ndata = 8+SCDSIZE;
+	p = prebuf(a, sizeof(a), ndata, 0, 0);
+	if(p == nil)
+		sysfatal("buffer not prepared: %r");
+	usize = p+ndata-a;
+
+	q = putsdh(p, p+ndata, ndata, PDUTYPE2_FONTLIST);
+	PSHORT(q+0, 0);	// numberFonts
+	PSHORT(q+2, 0);	// totalNumFonts
+	PSHORT(q+4, 2+1);	// listFlags: 1=first, 2=last
+	PSHORT(q+6, 50);	// entrySize
+
+	writen(rd.fd, a, usize);
+}
+
+/* 2.2.8.1.1.3.1.1 Slow-Path Input Event (TS_INPUT_EVENT) */
+void
+passinput(ulong msec, int mtype, int iflags, int iarg1, int iarg2)
+{
+	uchar a[64], *p, *q;
+	int ndata, usize; 
+
+	ndata = 16+SCDSIZE;
+	p = prebuf(a, sizeof(a), ndata, 0, 0);
+	if(p == nil)
+		sysfatal("buffer not prepared: %r");
+	usize = p+ndata-a;
+
+	q = putsdh(p, p+ndata, ndata, PDUTYPE2_INPUT);
+	PSHORT(q+0, 1); // numEvents
+	PSHORT(q+2, 0);
+	// 2.2.8.1.1.3.1.1 Slow-Path Input Event
+	PLONG(q+4, msec);
+	PSHORT(q+8, mtype);
+	// slowPathInputData[*]
+	PSHORT(q+10, iflags);
+	PSHORT(q+12, iarg1);
+	PSHORT(q+14, iarg2);
+
+	writen(rd.fd, a, usize);
+}
+
+/* 2.2.11.3.1 Suppress Output PDU Data (TS_SUPPRESS_OUTPUT_PDU) */
+void
+turnupdates(int allow)
+{
+	uchar a[64], *p, *q;
+	int ndata, usize; 
+
+	ndata = (allow?12:4)+SCDSIZE;
+	p = prebuf(a, sizeof(a), ndata, 0, 0);
+	if(p == nil)
+		sysfatal("buffer not prepared: %r");
+	usize = p+ndata-a;
+
+	q = putsdh(p, p+ndata, ndata, PDUTYPE2_SUPPRESS_OUTPUT);
+	q[0] = (allow?1:0); 
+	memset(q+1, 3, 0);
+	if(allow){
+		PSHORT(q+4, 0);	// left
+		PSHORT(q+6, 0);	// top
+		PSHORT(q+8, rd.dim.x-1);	// right
+		PSHORT(q+10, rd.dim.y-1);	// bottom
+	}
+	writen(rd.fd, a, usize);
+}
+
+int
+sizesechdr(int secflags)
+{
+	if(secflags&Scrypt)
+		return  12;	// flags[4] mac[8]
+	else if(secflags)
+		return 4;		// flags[4]
+	return 0;
+}
+
+uchar*
+prebuf(uchar* buf, int nb, int ndata, int chanid, int secflags)
+{
+	int n, len, shdsize;
+	uchar *p, *ep;
+
+	if(chanid==0)
+		chanid = GLOBALCHAN;
+
+	shdsize = sizesechdr(secflags);
+	len = TPDATAFIXLEN+8+shdsize+ndata;
+	if(len>nb){
+		werrstr("%s: provided %d, need %d, data %d", Esmall, nb, len, ndata);
+		return nil;
+	}
+	ep = buf+len;
+
+	ndata = len-TPDATAFIXLEN;
+	n = mktpdat(buf, nb, ndata);
+	if(n < 0)
+		sysfatal("mktpdat: %r");
+	p = buf+TPDATAFIXLEN;
+
+	ndata -= 8;
+	if(mkmcssdr(p, ep-p, ndata, chanid) < 0)
+		sysfatal("mkmcssdr: %r");
+	p += 8;
+
+	if(shdsize > 0)
+		PLONG(p, secflags);
+	return p + shdsize;
+}
+
--- /dev/null
+++ b/mppc.c
@@ -1,0 +1,263 @@
+/*
+ * lifted from /sys/src/cmd/ip/ppp/mppc.c (RFC 2118)
+ * plus the RDP 5.0 64K history (3.1.8.4.2 RDP 5.0)
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+#define DBG if(0)
+//#define DBG
+
+enum
+{
+	MaxHistorySize=	64*1024,
+
+	Preset=			0x80,	/* reset history */
+	Pfront=			0x40,	/* move packet to front of history */
+	Pcompress=		0x20,	/* packet is compressed */
+	Pbulk64=			0x01,	/* RPD5 bulk compression (64K history) */
+};
+
+enum
+{
+	Bits4=	0xf,
+	Bits5=	0x1f,
+	Bits6=	0x3f,
+	Bits7=	0x7f,
+	Bits8=	0xff,
+	Bits11=	0x7ff,
+	Bits13=	0x1fff,
+	Bits16=	0xffff,
+};
+
+enum
+{
+	Lit7,		/* seven bit literal */
+	Lit8,		/* eight bit literal */
+	Off6,		/* six bit offset */
+	Off8,		/* eight bit offset */
+	Off11,		/* eleven bit offset (64K history) */
+	Off13,		/* thirteen bit offset (8K history) */
+	Off16,		/* sixteen bit offset (64K history) */
+};
+
+/* decode first four bits (8K history) */
+static int decode8[16]=
+{
+	Lit7, Lit7, Lit7, Lit7,
+	Lit7, Lit7, Lit7, Lit7,
+	Lit8, Lit8, Lit8, Lit8,	
+	Off13, Off13, Off8, Off6,
+};
+
+/* decode first five bits (64K history) */
+static int decode64[32]=
+{
+	Lit7, Lit7, Lit7, Lit7,
+	Lit7, Lit7, Lit7, Lit7,
+	Lit7,	Lit7, Lit7, Lit7,
+	Lit7, Lit7, Lit7, Lit7,
+	Lit8, Lit8, Lit8, Lit8,	
+	Lit8, Lit8, Lit8, Lit8,	
+	Off16, Off16, Off16, Off16,
+	Off11, Off11, Off8, Off6,
+};
+
+typedef struct Uncstate Uncstate;
+struct Uncstate
+{
+	uchar	his[MaxHistorySize];
+	int	indx;		/* current indx in history */
+	int	size;		/* current history size */
+};
+
+static Uncstate uncstate;
+
+
+#define NEXTBYTE	sreg = (sreg<<8) | *p++; n--; bits += 8
+uchar*
+uncomp(uchar* buf, int nbytes, int flags, int* psize)
+{
+	int n, bits, off, len, ones, t;
+	int *decode, lookbits, lookmask, maxhis, maxones;
+	ulong sreg;
+	uchar *p, c, *hp, *hs, *he, *hq;
+	Uncstate *s;
+
+	s = &uncstate;
+	p = buf;
+	n = nbytes;
+
+	if(flags&Pbulk64){
+		maxhis = 64*1024;
+		maxones = 14;
+		decode = decode64;
+		lookbits = 5;
+		lookmask = Bits5;
+	}else{
+		maxhis = 8*1024;
+		maxones = 11;
+		decode = decode8;
+		lookbits = 4;
+		lookmask = Bits4;
+	}
+
+	if(flags&Preset){
+		s->indx = 0;
+		s->size = 0;
+		memset(s->his, maxhis, 0);
+	}
+	if(flags&Pfront){
+		s->indx = 0;
+DBG		fprint(2, "mppc: front flag set\n"); 
+	}
+	if(!(flags&Pcompress)){
+		*psize = n;
+		return buf;
+	}
+
+	bits = 0;
+	sreg = 0;
+	hs = s->his;			/* history start */
+	hp = hs+s->indx;		/* write pointer in history */
+	he = hs+maxhis;		/* history end */
+	for(;;){
+		if(bits<lookbits){
+			if(n==0) goto Done;
+			NEXTBYTE;
+		}
+		t = decode[(sreg>>(bits-lookbits))&lookmask];
+		switch(t){
+		default:
+			sysfatal("mppc: bad decode %d!", t);
+		case Lit7:
+			bits -= 1;
+			if(bits<7){
+				if(n==0) goto Done;
+				NEXTBYTE;
+			}
+			c = (sreg>>(bits-7))&Bits7;
+			bits -= 7;
+			if(hp >= he) goto His;
+			*hp++ = c;
+			continue;
+		case Lit8:
+			bits -= 2;
+			if(bits<7) {
+				if(n==0) goto Eof;
+				NEXTBYTE;
+			}
+			c = 0x80 | ((sreg>>(bits-7))&Bits7);
+			bits -= 7;
+			if(hp >= he) goto His;
+			*hp++ = c;
+			continue;
+		case Off6:
+			bits -= lookbits;
+			if(bits<6){
+				if(n==0) goto Eof;
+				NEXTBYTE;
+			}
+			off = (sreg>>(bits-6))&Bits6;
+			bits -= 6;
+			break;
+		case Off8:
+			bits -= lookbits;
+			if(bits<8){
+				if(n==0) goto Eof;
+				NEXTBYTE;
+			}
+			off = ((sreg>>(bits-8))&Bits8)+64;
+			bits -= 8;
+			break;
+		case Off13:	/* (8K history) */
+			bits -= 3;
+			while(bits<13){
+				if(n==0) goto Eof;
+				NEXTBYTE;
+			}
+			off = ((sreg>>(bits-13))&Bits13)+320;
+			bits -= 13;
+			break;
+		case Off11:	/* (64K history) */
+			bits -= 4;
+			while(bits<11){
+				if(n==0) goto Eof;
+				NEXTBYTE;
+			}
+			off = ((sreg>>(bits-11))&Bits11)+320;
+			bits -= 11;
+			break;
+		case Off16:	/* (64K history) */
+			bits -= 3;
+			while(bits<16){
+				if(n==0) goto Eof;
+				NEXTBYTE;
+			}
+			off = ((sreg>>(bits-16))&Bits16)+2368;
+			bits -= 16;
+			break;
+		}
+		for(ones=0;;ones++) {
+			if(bits == 0) {
+				if(n==0) goto Eof;
+				NEXTBYTE;
+			}
+			bits--;
+			if(!(sreg&(1<<bits)))
+				break;
+		}
+		if(ones>maxones){
+			werrstr("bad length %d\n", ones);
+			return nil;
+		}
+		if(ones == 0) {
+			len = 3;
+		} else {
+			ones++;
+			while(bits<ones) {
+				if(n==0) goto Eof;
+				NEXTBYTE;
+			}
+			len = (1<<ones) | ((sreg>>(bits-ones))&((1<<ones)-1));
+			bits -= ones;
+		}
+
+		hq = hp-off;
+		if(hq < hs) {
+			hq += maxhis;
+			if(hq-hs+len > s->size){
+//				goto His;
+fprint(2, "mppc: reference past valid history\n");
+			}
+		}
+		if(hp+len > he)
+			goto His;
+		while(len) {
+			*hp++ = *hq++;
+			len--;
+		}
+	}
+
+Done:
+	hq = hs+s->indx;
+	len = hp-hq;
+DBG fprint(2, "mppc: len %d bits = %d n=%d\n", len, bits, n);
+
+	s->indx += len;
+	if(s->indx > s->size)
+		s->size = s->indx;
+
+	*psize = len;
+	return hq;
+
+Eof:
+	werrstr("unexpected end of data");
+	return nil;
+His:
+	werrstr("bad history reference");
+	return nil;
+}
--- /dev/null
+++ b/rd.c
@@ -1,0 +1,281 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+Rdp rd = {
+	.fd = -1,
+	.chan = RGB16,
+	.depth = 16,
+	.dim = {800, 600},
+	.windom = "",
+	.passwd = "",
+	.shell = "",
+	.rwd = "",
+};
+
+char Eshort[]=	"short data";
+char Esmall[]=	"buffer too small";
+char Ebignum[]=	"number too big";
+
+static void
+usage(void)
+{
+	fprint(2, "usage: rd [-0A] [-T title] [-a depth] [-c wdir] [-d dom] [-k keyspec] [-n term] [-s shell] [net!]server[!port]\n");
+	exits("usage");
+}
+
+long
+writen(int fd, void* buf, long nbytes)
+{
+	long n, sofar;
+
+	sofar = 0;
+	while(sofar < nbytes){
+		n = write(fd, buf, nbytes-sofar);
+		if(n <= 0)
+			break;
+		sofar += n;
+	}
+	return sofar;
+}
+
+static int
+startmouseproc(void)
+{
+	int mpid;
+
+	switch(mpid = rfork(RFPROC|RFMEM)){
+	case -1:
+		sysfatal("rfork: %r");
+	case 0:
+		break;
+	default:
+		return mpid;
+	}
+	atexit(atexitkiller);
+	readdevmouse();
+	exits("mouse eof");
+	return 0;
+}
+
+static int
+startkbdproc(void)
+{
+	int pid;
+	switch(pid = rfork(RFPROC|RFMEM)){
+	case -1:
+		sysfatal("rfork: %r");
+	case 0:
+		break;
+	default:
+		return pid;
+	}
+	atexit(atexitkiller);
+	readkbd();
+	exits("kbd eof");
+	return 0;
+}
+
+static int
+startsnarfproc(void)
+{
+	int pid;
+
+	switch(pid = rfork(RFPROC|RFMEM)){
+	case -1:
+		sysfatal("rfork: %r");
+	case 0:
+		break;
+	default:
+		return pid;
+	}
+	atexit(atexitkiller);
+	pollsnarf();
+	exits("snarf eof");
+	return 0;
+}
+
+static int killpid[32];
+static int nkillpid;
+
+void
+atexitkiller(void)
+{
+	int i, pid;
+
+	pid = getpid();
+	for(i=0; i<nkillpid; i++)
+		if(pid != killpid[i])
+			postnote(PNPROC, killpid[i], "kill");
+}
+void
+atexitkill(int pid)
+{
+	killpid[nkillpid++] = pid;
+}
+
+void
+main(int argc, char *argv[])
+{
+	int doauth, fd;
+	char *server, *addr, *keyspec;
+	UserPasswd *creds;
+
+	rd.local = getenv("sysname");
+	rd.user = getenv("user");
+
+	keyspec = "";
+	doauth = 1;
+
+	ARGBEGIN {
+	case 'A':
+		doauth = 0;
+		break;
+	case 'k':
+		keyspec = EARGF(usage());
+		break;
+	case 'T':
+		rd.label = strdup(EARGF(usage()));
+		break;
+	case 'd':
+		rd.windom = strdup(EARGF(usage()));
+		break;
+	case 's':
+		rd.shell = strdup(EARGF(usage()));
+		break;
+	case 'c':
+		rd.rwd = strdup(EARGF(usage()));
+		break;
+	case 'n':
+		rd.local = estrdup(EARGF(usage()));
+		break;
+	case 'a':
+		rd.depth = atol(EARGF(usage()));
+		switch(rd.depth){
+		case 8:
+			rd.chan = CMAP8;
+			break;
+		case 15:
+			rd.chan = RGB15;
+			break;
+		case 16:
+			rd.chan = RGB16;
+			break;
+		case 24:
+			rd.chan = RGB24;
+			break;
+		case 32:
+			rd.chan = XRGB32;
+			break;
+		default:
+			sysfatal("bad color depth");
+		}
+		break;
+	case '0':
+		rd.wantconsole = 1;
+		break;
+	default:
+		usage();
+	} ARGEND
+
+	if (argc != 1)
+		usage();
+
+	server = argv[0];
+	if(rd.local == nil)
+		sysfatal("set $sysname or use -n\n");
+	if(rd.user == nil)
+		sysfatal("set $user");
+	if (rd.label == nil)
+		rd.label = smprint("rd %s", server);
+
+	if(doauth){
+		creds = auth_getuserpasswd(auth_getkey, "proto=pass service=rdp %s", keyspec);
+		if(creds == nil)
+			fprint(2, "factotum: %r\n");
+		else {
+			rd.user = creds->user;
+			rd.passwd = creds->passwd;
+			rd.autologon = 1;
+		}
+	}
+
+	addr = netmkaddr(server, "tcp", "3389");
+	fd = dial(addr, nil, nil, nil);
+	if(fd < 0)
+		sysfatal("dial %s: %r", addr);
+	rd.fd = fd;
+	if(x224connect(fd) < 0)
+		sysfatal("initial handshake failed: %r");
+
+	if(initdraw(drawerror, nil, rd.label) < 0)
+		sysfatal("initdraw: %r");
+	display->locking = 1;
+	unlockdisplay(display);
+
+	rd.dim.y = Dy(screen->r);
+	rd.dim.x = Dx(screen->r);
+	rd.dim.x = (rd.dim.x + 3) & ~3;	/* ensure width divides by 4 */
+
+	if(rdphandshake(fd) < 0)
+		sysfatal("handshake failed: %r");
+
+	atexit(atexitkiller);
+	atexitkill(getpid());
+	atexitkill(startmouseproc());
+	atexitkill(startkbdproc());
+	initsnarf();
+	atexitkill(startsnarfproc());
+
+	readnet(rd.fd);
+
+	x224disconnect(rd.fd);
+	close(rd.fd);
+	lockdisplay(display);
+	closedisplay(display);
+
+	if(!rd.active)
+		exits(nil);
+	if(rd.hupreason)
+		sysfatal("hangup: %d", rd.hupreason);
+	sysfatal("hangup");
+}
+
+void*
+emalloc(ulong n)
+{
+	void *b;
+
+	b = mallocz(n, 1);
+	if(b == nil)
+		sysfatal("out of memory allocating %lud: %r", n);
+	setmalloctag(b, getcallerpc(&n));
+	return b;
+}
+
+void*
+erealloc(void *a, ulong n)
+{
+	void *b;
+
+	b = realloc(a, n);
+	if(b == nil)
+		sysfatal("out of memory re-allocating %lud: %r", n);
+	setrealloctag(b, getcallerpc(&a));
+	return b;
+}
+
+char*
+estrdup(const char *s)
+{
+	char *b;
+
+	b = strdup(s);
+	if(b == nil)
+		sysfatal("strdup: %r");
+	setmalloctag(b, getcallerpc(&s));
+	return b;
+}
--- /dev/null
+++ b/rle.c
@@ -1,0 +1,256 @@
+/*
+ * 2.2.9.1.1.3.1.2.4 RLE Compressed Bitmap Stream (RLE_BITMAP_STREAM)
+ *	http://msdn.microsoft.com/en-us/library/cc240895.aspx
+ * 3.1.9 Interleaved RLE-Based Bitmap Compression
+ *	http://msdn.microsoft.com/en-us/library/dd240593.aspx
+*/
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+enum
+{
+	Bits4=	15,
+	Bits5=	31,
+};
+
+enum
+{
+	Bg,
+	Fg,
+	FgS,
+	Dith,
+	Fill,
+	Mix,
+	MixS,
+	Lit,
+	Spec,
+	Mix3,
+	Mix5,
+	Wpix,
+	Bpix,
+};
+
+enum
+{
+	Std= 0,
+	Ext= 1,
+};
+
+static int
+decode[2][16] =
+{
+[Std]	{
+		Bg, 	Bg, 	Fg, 	Fg,	Mix,	Mix,	Fill,	Fill,
+		Lit,	Lit,	-1, 	-1,	FgS,	MixS,	Dith,	-1,
+	},
+[Ext]	{
+		Bg, 	Fg, 	Mix,	Fill,	Lit,	-1, 	FgS,	MixS,
+		Dith,	Mix3,	Mix5,	-1, 	-1, 	Wpix,	Bpix,	-1,
+	}
+};
+
+static void*
+memfill(void *a1, ulong n1, void *a2, ulong n2)
+{
+	char *s1, *s2, *e1, *e2;
+
+	if((long)n1 < 0 || (long)n2 <= 0)
+		abort();
+	s1 = a1;
+	s2 = a2;
+	e1 = s1+n1;
+	e2 = s2+n2;
+	while(s1 < e1){
+		*s1++ = *s2++;
+		if(s2 >= e2)
+			s2 = a2;
+	}
+	return a1;
+}
+
+static void*
+memxor(void *a1, void *a2, ulong n)
+{
+	char *s1, *s2;
+
+	if((long)n < 0)
+		abort();
+	s1 = a1;
+	s2 = a2;
+	while(n > 0){
+		*s1++ ^= *s2++;
+		n--;
+	}
+	return a1;
+}
+
+uchar*
+unrle(uchar* d, int nd, uchar* s, int ns, int bpl, int pixelsize)
+{
+	int t, hdr, code, bits, len, wasline1, wasbg;
+	uchar pen[4], dpen[8], wpen[4], *p, *ep, *wp, *we;
+	uint sreg;
+
+	p = s;
+	ep = s+ns;
+	wp = d;
+	we = d+nd;
+	wasbg = 0;
+	wasline1 = 1;
+	PLONG(pen, DWhite);
+	PLONG(wpen, DWhite);
+
+	while(p < ep){
+		hdr = *p++;
+		code = hdr>>4;
+		if(code != Bits4){
+			t = decode[Std][code];
+			if(code>>2 == 3)
+				bits = Bits4;
+			else
+				bits = Bits5;
+			len = hdr&bits;
+			if(t==Mix || t==MixS){
+				if(len == 0)
+					len = 1+*p++;
+				else
+					len *= 8;
+			}else{
+				if(len == 0)
+					len = 1+bits+*p++;
+			}
+		}else{
+			code = hdr&Bits4;
+			if(code < 9){
+				len = GSHORT(p);
+				p += 2;
+			}else
+				len = 0;
+			t = decode[Ext][code];
+		}
+		len *= pixelsize;
+		if(wp+len > we){
+		   Overrun:
+			werrstr("unrle: output buffer is %uld bytes short", wp+len-we);
+			return nil;
+		}
+
+		if(t != Bg)
+			wasbg = 0;
+		if(wasline1 && wp-bpl >= d){
+			wasline1 = 0;
+			wasbg = 0;
+		}
+
+		switch(t){
+		default:
+			werrstr("unrle: bad decode");
+			return nil;
+		case Lit:
+			memmove(wp, p, len);
+			p += len;
+			break;
+		case Bg:
+			if(wasbg){
+				memmove(wp, pen, pixelsize);
+				if(wp-bpl >= d)
+					memxor(wp, wp-bpl, pixelsize);
+				wp += pixelsize;
+				len -= pixelsize;
+			}
+			if(wp-bpl >= d){
+				while(len>bpl){
+					memmove(wp, wp-bpl, bpl);
+					wp += bpl;
+					len -= bpl;
+				}
+				memmove(wp, wp-bpl, len);
+			}else
+				memset(wp, len, 0);
+			wasbg = 1;
+			break;
+		case FgS:
+			memmove(pen, p, pixelsize);
+			p += pixelsize;
+			/* fall through */
+		case Fg:
+			memfill(wp, len, pen, pixelsize);
+			if(wp-bpl >= d)
+				memxor(wp, wp-bpl, len);
+			break;
+		case Mix3:
+			sreg = 3;
+			bits = 8;
+			len = 8*pixelsize;
+			goto case_MixAll;
+		case Mix5:
+			sreg = 5;
+			bits = 8;
+			len = 8*pixelsize;
+			goto case_MixAll;
+		case MixS:
+			memmove(pen, p, pixelsize);
+			p += pixelsize;
+			/* fall through */
+		case Mix:
+			sreg = 0;
+			bits = 0;
+			/* fall through */
+		case_MixAll:
+			if(wp+len > we)
+				goto Overrun;
+			while(len > 0){
+				if(bits == 0){
+					sreg = *p++;
+					bits = 8;
+				}
+				if(sreg&1){
+					memmove(wp, pen, pixelsize);
+					if(wp-bpl >= d)
+						memxor(wp, wp-bpl, pixelsize);
+				}else{
+					if(wp-bpl >= d)
+						memmove(wp, wp-bpl, pixelsize);
+					else
+						memset(wp, pixelsize, 0);
+				}
+				wp += pixelsize;
+				len -= pixelsize;
+				sreg >>= 1;
+				bits--;
+			}
+			break;
+		case Fill:
+			memmove(dpen, p, pixelsize);
+			p += pixelsize;
+			memfill(wp, len, dpen, pixelsize);
+			break;
+		case Dith:
+			len *= 2;
+			if(wp+len > we)
+				goto Overrun;
+			memmove(dpen, p, pixelsize);
+			memmove(dpen+pixelsize, p+pixelsize, pixelsize);
+			p += 2*pixelsize;
+			memfill(wp, len, dpen, 2*pixelsize);
+			break;
+		case Wpix:
+			len = pixelsize;
+			if(wp+len > we)
+				goto Overrun;
+			memmove(wp, wpen, pixelsize);
+			break;
+		case Bpix:
+			len = pixelsize;
+			if(wp+len > we)
+				goto Overrun;
+			memset(wp, pixelsize, 0);
+			break;
+		}
+		wp += len;
+	}
+	return wp;
+}
--- /dev/null
+++ b/utf16.c
@@ -1,0 +1,105 @@
+/* /lib/rfc/rfc2781, also translates \n to \r\n */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+enum
+{
+	Bits10 = 0x03FF,
+	Bits16 = 0xFFFF,
+	Bits20 = 0x0FFFFF,
+	HHalfZoneS = 0xD800,	HHalfZoneE = 0xDBFF,
+	LHalfZoneS = 0xDC00,	LHalfZoneE = 0xDFFF,
+};
+
+int
+toutf16(uchar* buf, int nb, char* s, int ns)
+{
+	uchar *b, *eb;
+	char *es;
+	Rune r;
+
+	b = buf;
+	eb = b+nb;
+	es = s+ns;
+
+	while(s < es){
+		if(*s == '\n'){
+			if(b+2 > eb)
+				break;
+			PSHORT(b, '\r');
+			b+=2;
+		}
+		s += chartorune(&r, s);
+		if(b+2 > eb)
+			break;
+		if(r <= Bits16){
+			PSHORT(b, r);
+			b+=2;
+			continue;
+		} 
+		r -= Bits16+1;
+		if(r > Bits20){
+			PSHORT(b, Runeerror);
+			b+=2;
+			continue;
+		}
+		if(b+4 > eb)
+			break;
+		PSHORT(b+0, HHalfZoneS | (r >> 10));
+		PSHORT(b+2, LHalfZoneS | (r & Bits10));
+		b+=4;
+	}
+	return b-buf;
+}
+
+int
+fromutf16(char* str, int ns, uchar* ws, int nw)
+{
+	char *s, *es, buf[UTFmax];
+	uchar *q, *eq;
+	ushort w1, w2;
+	Rune r;
+	int n;
+
+	s = str;
+	es = str + ns;
+	q = ws;
+	eq = ws + nw;
+
+	while(q+2 <= eq){
+		w1 = GSHORT(q);
+		q += 2;
+		if(w1<HHalfZoneS || w1>LHalfZoneE){
+			r = w1;
+			goto Convert;
+		}
+		if(w1>HHalfZoneE){
+			r = Runeerror;
+			goto Convert;
+		}
+		if(q+2 > eq){
+			r = Runeerror;
+			goto Convert;
+		}
+		w2 = GSHORT(q);
+		q += 2;
+		if(w2<LHalfZoneS || w2>LHalfZoneE){
+			r = Runeerror;
+			goto Convert;
+		}
+		r = (w1 & Bits10)<<10 | (w2 & Bits10) + Bits16 + 1;
+	Convert:
+		n = runetochar(buf, &r);
+		if(buf[0] == '\r')
+			continue;
+		if(s+n > es)
+			break;
+		memmove(s, buf, n);
+		s += n;
+	}
+	return s-str;
+}
+
--- /dev/null
+++ b/vchan.c
@@ -1,0 +1,149 @@
+/*
+ * [MS-RDPBCGR] 3.1.5.2 Static Virtual Channels
+ * http://msdn.microsoft.com/en-us/library/cc240926.aspx
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+enum
+{
+	/* 2.2.1.3.4.1 Channel Definition Structure */
+	ChanInited		= 0x80000000,
+	ChanShowproto	= 0x00200000,
+
+	/* 2.2.6.1 Virtual Channel PDU */
+	ChanChunkLen			= 1600,
+
+	/* 2.2.6.1.1 Channel PDU Header */
+	CFfirst=	0x01,
+	CFlast=	0x02,
+	CFshowproto=	0x10,
+};
+
+Vchan vctab[] =
+{
+	{
+		.mcsid = GLOBALCHAN + 1,	/* iota */
+		.name = "CLIPRDR",
+		.fn = clipvcfn,
+		.flags = ChanInited | ChanShowproto,
+	},
+};
+uint nvc = nelem(vctab);
+
+Vchan*
+lookupvc(int mcsid)
+{
+	int i;
+	for(i=0; i<nvc; i++)
+	if(vctab[i].mcsid == mcsid)
+		return &vctab[i];
+	return nil;
+}
+
+Vchan*
+namevc(char* name)
+{
+	int i;
+	for(i=0; i<nvc; i++)
+	if(strcmp(vctab[i].name, name) == 0)
+		return &vctab[i];
+	return nil;
+}
+
+void
+scanvcpdu(uchar *p, uchar* ep, int chanid)
+{
+	int flags, len, rem;
+	uchar *q, *eq;
+	Vchan* vc;
+
+	vc = lookupvc(chanid);
+	if(vc == nil)
+		return;
+
+	len = GLONG(p+0);
+	flags = GLONG(p+4);
+	p += 8;
+	if(len < 0 || len > 8*1024*1024){
+		werrstr("bad length in virtual channel PDU header");
+		fprint(2, "scanvcpdu: %r\n");
+		return;
+	}
+	if(flags&CFfirst){
+		vc->defragging = 1;
+		vc->pos = 0;
+	}
+
+	if(!vc->defragging){
+		vc->fn(p, ep);
+		return;
+	}
+
+	vc->buf = erealloc(vc->buf, len);
+	vc->nb = len;
+	q = vc->buf + vc->pos;
+	eq = vc->buf + len;
+	rem = ep-p;
+	if(rem > eq-q)
+		rem = eq-q;
+	memcpy(q, p, rem);
+	vc->pos += rem;
+	if(flags&CFlast){
+		q = vc->buf;
+		vc->fn(q, eq);
+		free(vc->buf);
+		vc->buf = nil;
+		vc->nb = 0;
+		vc->defragging = 0;
+	}
+}
+
+int
+sendvc(char* cname, uchar* a, int n)
+{
+	int sofar, chunk;
+	int flags;
+	int nb, len, ndata;
+	uchar buf[40+ChanChunkLen];
+	uchar *p, *q;
+	Vchan* vc;
+	int chanid;
+	
+	vc = namevc(cname);
+	if(vc == nil){
+		werrstr("%s: no such vchannel", cname);
+		return -1;
+	}
+	chanid = vc->mcsid;
+	if(chanid < 0)
+		return -1;
+	if(n < 0)
+		return -1;
+
+	p = a;
+	nb = sizeof(buf);
+	flags = CFfirst | CFshowproto;
+
+	for(sofar=0; sofar<n; sofar += chunk){
+		chunk = n-sofar;
+		if(chunk > ChanChunkLen)
+			chunk = ChanChunkLen;
+		else
+			flags |= CFlast;
+		ndata = chunk+8;
+		q = prebuf(buf, nb, ndata, chanid, 0);
+		if(q == nil)
+			return -1;
+		PLONG(q+0, n);
+		PLONG(q+4, flags);
+		memcpy(q+8, p+sofar, chunk);
+		len = q-buf+ndata;
+		writen(rd.fd, buf, len);
+		flags &= ~CFfirst;
+	}
+	return n;
+}
--- /dev/null
+++ b/wsys.c
@@ -1,0 +1,138 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+static int snarffd;
+static ulong snarfvers;
+
+#define MAXSNARF 100*1024
+#ifndef BUFSIZE
+#define BUFSIZE	4*1024
+#endif
+
+static int
+ishidden(void)
+{
+	int wctl;
+	long n;
+	uchar buf[80];
+	char *p;
+
+	wctl = open("/dev/wctl", OREAD);
+	if(wctl < 0)
+		return 0;
+	n = read(wctl, buf, sizeof(buf));
+	close(wctl);
+	if(n < 12*4)
+		return 0;
+	buf[sizeof(buf)-1] = 0;
+	p = (char*)buf;
+	if(strstr(p, "hidden") != nil)
+		return 1;
+	return 0;
+}
+
+void
+eresized(int)
+{
+	int fd;
+	Point d;
+
+	lockdisplay(display);
+	if(getwindow(display, Refnone) < 0)
+		sysfatal("resize failed: %r");
+
+	/* lifted from /sys/src/cmd/vnc/wsys.c */
+	d = addpt(rd.dim, Pt(2*Borderwidth, 2*Borderwidth));
+	rd.dim.x = (rd.dim.x + 3) & ~3;	/* ensure width divides by 4 */
+	if(d.x < Dx(screen->r) || d.y < Dy(screen->r)){
+		fd = open("/dev/wctl", OWRITE);
+		if(fd >= 0){
+			fprint(fd, "resize -dx %d -dy %d", d.x, d.y);
+			close(fd);
+		}
+	}
+	turnupdates(0);
+	turnupdates(!ishidden());
+	unlockdisplay(display);
+}
+void
+initsnarf(void)
+{
+	snarffd = open("/dev/snarf", OREAD);
+}
+
+/*
+ * /dev/snarf updates when the file is closed, so we must open our own
+ * fd here rather than use snarffd
+ */
+void
+putsnarf(char* s, int nb)
+{
+	int fd, i;
+
+	if(nb==0 || nb > MAXSNARF)
+		return;
+	fd = open("/dev/snarf", OWRITE);
+	if(fd < 0)
+		return;
+	snarfvers++;
+	while(nb > 0){
+		i = write(fd, s, nb);
+		if(i < 0)
+			break;
+		s += i;
+		nb -= i;
+	}
+	close(fd);
+}
+
+char*
+getsnarf(int *pnb)
+{
+	int i, n;
+	char *s, buf[BUFSIZE];
+
+	if(snarffd < 0)
+		return nil;
+
+	s = nil;
+	i = 0;
+	seek(snarffd, 0, 0);
+	while((n = read(snarffd, buf, sizeof(buf))) > 0){
+		s = erealloc(s, i+n+1);
+		memmove(s+i, buf, n);
+		i += n;
+		s[i] = 0;
+	}
+	*pnb = i+1;	// for terminating zero
+	return s;
+}
+
+/* lifted from /sys/src/cmd/vnc/wsys.c */
+void
+pollsnarf(void)
+{
+	Dir *dir;
+
+	while(snarffd < 0){
+		snarffd = open("/dev/snarf", OREAD);
+		if(snarffd < 0)
+			sleep(1000*60);
+	}
+
+	for(;;){
+		sleep(1000);
+
+		dir = dirstat("/dev/snarf");
+		if(dir == nil)	/* old drawterm */
+			continue;
+		if(dir->qid.vers > snarfvers){
+			clipannounce();
+			snarfvers = dir->qid.vers;
+		}
+		free(dir);
+	}
+}
--- /dev/null
+++ b/x224.c
@@ -1,0 +1,364 @@
+#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;
+}