shithub: riscv

Download patch

ref: 5a0c34e9fbe074df1d7090e31ab38a3a37675949
parent: 5649042bff26fa0594217432d085b9f96b923298
author: cinap_lenrek <[email protected]>
date: Sun Jan 25 02:58:20 EST 2015

wpa: experimental wpa2 enterprise support

this adds support for eap-peap/mschapv2 and eap-ttls/pap.

code has only been tested with freeradius and a cheap
access point, not tested with actual eduroam network.

--- a/sys/src/cmd/aux/wpa.c
+++ b/sys/src/cmd/aux/wpa.c
@@ -6,6 +6,7 @@
 #include <auth.h>
 
 enum {
+	PMKlen = 256/8,
 	PTKlen = 512/8,
 	GTKlen = 256/8,
 
@@ -51,6 +52,34 @@
 	int	keylen;
 };
 
+typedef struct Eapconn Eapconn;
+typedef struct TLStunn TLStunn;
+
+struct Eapconn
+{
+	int	fd;
+	int	version;
+
+	uchar	type;
+	uchar	smac[Eaddrlen];
+	uchar	amac[Eaddrlen];
+
+	TLStunn	*tunn;
+
+	void	(*write)(Eapconn*, uchar *data, int datalen);
+};
+
+struct TLStunn
+{
+	int	fd;
+
+	int	clientpid;
+	int	readerpid;
+
+	uchar	id;
+	uchar	tp;
+};
+
 Cipher	tkip = { "tkip", 32 };
 Cipher	ccmp = { "ccmp", 16 };
 
@@ -62,6 +91,7 @@
 int	debug;
 int	fd, cfd;
 char	*dev;
+int	ispsk;
 char	devdir[40];
 uchar	ptk[PTKlen];
 char	essid[32+1];
@@ -70,6 +100,7 @@
 uchar rsntkipoui[4] = {0x00, 0x0F, 0xAC, 0x02};
 uchar rsnccmpoui[4] = {0x00, 0x0F, 0xAC, 0x04};
 uchar rsnapskoui[4] = {0x00, 0x0F, 0xAC, 0x02};
+uchar rsnawpaoui[4] = {0x00, 0x0F, 0xAC, 0x01};
 
 uchar	rsnie[] = {
 	0x30,			/* RSN */
@@ -99,6 +130,16 @@
 	0x00, 0x50, 0xf2, 0x02,	/* authentication suite PSK */
 };
 
+void*
+emalloc(int len)
+{
+	void *v;
+
+	if((v = mallocz(len, 1)) == nil)
+		sysfatal("malloc: %r");
+	return v;
+}
+
 int
 hextob(char *s, char **sp, uchar *b, int n)
 {
@@ -156,8 +197,18 @@
 }
 
 int
-connected(void)
+getbssid(uchar mac[Eaddrlen])
 {
+	char buf[64];
+
+	if(getifstats("bssid:", buf, sizeof(buf)) != nil)
+		return parseether(mac, buf);
+	return -1;
+}
+
+int
+connected(int assoc)
+{
 	char status[1024];
 
 	if(getifstats("status:", status, sizeof(status)) == nil)
@@ -166,6 +217,10 @@
 		return 0;
 	if(strcmp(status, "unauthenticated") == 0)
 		return 0;
+	if(assoc){
+		if(strcmp(status, "blocked") != 0 && strcmp(status, "associated") != 0)
+			return 0;
+	}
 	if(debug)
 		fprint(2, "status: %s\n", status);
 	return 1;
@@ -287,10 +342,15 @@
 		if((e - p) < 4)
 			goto trunc;
 
-		/* look for PSK oui */
 		if(rsne[0] == 0x30){
+			/* look for PSK oui */
 			if(memcmp(p, rsnapskoui, 4) == 0)
 				break;
+			/* look for WPA oui */
+			if(memcmp(p, rsnawpaoui, 4) == 0){
+				ispsk = 0;
+				break;
+			}
 		} else {
 			if(memcmp(p, wpaapskoui, 4) == 0)
 				break;
@@ -298,7 +358,7 @@
 		p += 4;
 	}
 	if(i >= n){
-		sysfatal("auth suite is not PSK; brsne: %s", buf);
+		sysfatal("auth suite is not PSK or WPA; brsne: %s", buf);
 		return 0;
 	}
 
@@ -315,7 +375,97 @@
 	return w - rsne;
 }
 
+char*
+factotumattr(char *attr, char *fmt, ...)
+{
+	char buf[1024];
+	va_list list;
+	AuthRpc *rpc;
+	char *val;
+	Attr *a;
+	int afd;
+
+	if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0)
+		return nil;
+	if((rpc = auth_allocrpc(afd)) == nil){
+		close(afd);
+		return nil;
+	}
+	va_start(list, fmt);
+	vsnprint(buf, sizeof(buf), fmt, list);
+	va_end(list);
+	val = nil;
+	if(auth_rpc(rpc, "start", buf, strlen(buf)) == 0){
+		if((a = auth_attr(rpc)) != nil){
+			if((val = _strfindattr(a, attr)) != nil)
+				val = strdup(val);
+			_freeattr(a);
+		}
+	}
+	auth_freerpc(rpc);
+	close(afd);
+
+	return val;
+}
+
+void
+freeup(UserPasswd *up)
+{
+	memset(up->user, 0, strlen(up->user));
+	memset(up->passwd, 0, strlen(up->passwd));
+	free(up);
+}
+
+char*
+getidentity(void)
+{
+	static char *identity;
+	char *s;
+
+	s = nil;
+	for(;;){
+		if(getessid() == nil)
+			break;
+		if((s = factotumattr("user", "proto=pass service=wpa essid=%q", essid)) != nil)
+			break;
+		if((s = factotumattr("user", "proto=mschapv2 role=client service=wpa essid=%q", essid)) != nil)
+			break;
+		break;
+	}
+	if(s != nil){
+		free(identity);
+		identity = s;
+	} else if(identity == nil)
+		identity = strdup("anonymous");
+	if(debug)
+		fprint(2, "identity: %s\n", identity);
+	return identity;
+}
+
 int
+factotumctl(char *fmt, ...)
+{
+	va_list list;
+	int r, fd;
+
+	if((fd = open("/mnt/factotum/ctl", OWRITE)) < 0)
+		return -1;
+	va_start(list, fmt);
+	r = vfprint(fd, fmt, list);
+	va_end(list);
+	close(fd);
+	return r;
+}
+
+int
+setpmk(uchar pmk[PMKlen])
+{
+	if(getessid() == nil)
+		return -1;
+	return factotumctl("key proto=wpapsk role=client essid=%q !password=%.32H\n", essid, pmk);
+}
+
+int
 getptk(	uchar smac[Eaddrlen], uchar amac[Eaddrlen], 
 	uchar snonce[Noncelen],  uchar anonce[Noncelen], 
 	uchar ptk[PTKlen])
@@ -495,23 +645,64 @@
 }
 
 void
-reply(int eapver, uchar smac[Eaddrlen], uchar amac[Eaddrlen], int flags, Keydescr *kd, uchar *data, int datalen)
+fdwrite(Eapconn *conn, uchar *data, int len)
 {
-	uchar buf[4096], *m, *p = buf;
+	if(write(conn->fd, data, len) != len)
+		sysfatal("write: %r");
+}
 
-	memmove(p, amac, Eaddrlen); p += Eaddrlen;
-	memmove(p, smac, Eaddrlen); p += Eaddrlen;
+void
+etherwrite(Eapconn *conn, uchar *data, int len)
+{
+	uchar *buf, *p;
+	int n;
+
+	if(debug)
+		fprint(2, "\nreply(v%d,t%d) %E -> %E: ", conn->version, conn->type, conn->smac, conn->amac);
+	n = 2*Eaddrlen + 2 + len;
+	if(n < 60) n = 60;	/* ETHERMINTU */
+	p = buf = emalloc(n);
+	/* ethernet header */
+	memmove(p, conn->amac, Eaddrlen); p += Eaddrlen;
+	memmove(p, conn->smac, Eaddrlen); p += Eaddrlen;
 	*p++ = 0x88;
 	*p++ = 0x8e;
+	/* eapol data */
+	memmove(p, data, len);
+	fdwrite(conn, buf, n);
+	free(buf);
+}
 
-	m = p;
-	*p++ = eapver;
-	*p++ = 0x03;
+void
+eapwrite(Eapconn *conn, uchar *data, int len)
+{
+	uchar *buf, *p;
+
+	p = buf = emalloc(len + 4);
+	/* eapol header */
+	*p++ = conn->version;
+	*p++ = conn->type;
+	*p++ = len >> 8;
+	*p++ = len;
+	/* eap data */
+	memmove(p, data, len); p += len;
+	etherwrite(conn, buf, p - buf);
+	free(buf);
+}
+
+void
+replykey(Eapconn *conn, int flags, Keydescr *kd, uchar *data, int datalen)
+{
+	uchar buf[4096], *p = buf;
+
+	/* eapol hader */
+	*p++ = conn->version;
+	*p++ = conn->type;
 	datalen += Keydescrlen;
 	*p++ = datalen >> 8;
 	*p++ = datalen;
 	datalen -= Keydescrlen;
-
+	/* key header */
 	memmove(p, kd, Keydescrlen);
 	kd = (Keydescr*)p;
 	kd->flags[0] = flags >> 8;
@@ -518,23 +709,409 @@
 	kd->flags[1] = flags;
 	kd->datalen[0] = datalen >> 8;
 	kd->datalen[1] = datalen;
+	/* key data */
 	p = kd->data;
 	memmove(p, data, datalen);
 	p += datalen;
-
+	/* mic */
 	memset(kd->mic, 0, MIClen);
 	if(flags & Fmic)
-		calcmic(kd, m, p - m);
-	if(debug != 0){
-		fprint(2, "\nreply(v%d) %E -> %E: ", eapver, smac, amac);
+		calcmic(kd, buf, p - buf);
+	etherwrite(conn, buf, p - buf);
+	if(debug)
 		dumpkeydescr(kd);
+}
+
+void
+eapresp(Eapconn *conn, int code, int id, uchar *data, int len)
+{
+	uchar *buf, *p;
+
+	len += 4;
+	p = buf = emalloc(len);
+	/* eap header */
+	*p++ = code;
+	*p++ = id;
+	*p++ = len >> 8;
+	*p++ = len;
+	memmove(p, data, len-4);
+	(*conn->write)(conn, buf, len);
+	free(buf);
+
+	if(debug)
+		fprint(2, "eapresp(code=%d, id=%d, data=%.*H)\n", code, id, len-4, data);
+}
+
+void
+tlsreader(TLStunn *tunn, Eapconn *conn)
+{
+	enum {
+		Tlshdrsz = 5,
+		TLStunnhdrsz = 6,
+	};
+	uchar *rec, *w, *p;
+	int fd, n, css;
+
+	fd = tunn->fd;
+	rec = nil;
+	css = 0;
+Reset:
+	w = rec;
+	w += TLStunnhdrsz;
+	for(;;w += n){
+		if((p = realloc(rec, (w - rec) + Tlshdrsz)) == nil)
+			break;
+		w = p + (w - rec), rec = p;
+		if(readn(fd, w, Tlshdrsz) != Tlshdrsz)
+			break;
+		n = w[3]<<8 | w[4];
+		if(n < 1)
+			break;
+		if((p = realloc(rec, (w - rec) + Tlshdrsz+n)) == nil)
+			break;
+		w = p + (w - rec), rec = p;
+		if(readn(fd, w+Tlshdrsz, n) != n)
+			break;
+		n += Tlshdrsz;	
+		
+		/* batch records that need to be send together */
+		if(!css){
+			/* Client Certificate */
+			if(w[0] == 22 && w[5] == 11)
+				continue;
+			/* Client Key Exchange */
+			if(w[0] == 22 && w[5] == 16)
+				continue;
+			/* Change Cipher Spec */
+			if(w[0] == 20){
+				css = 1;
+				continue;
+			}
+		}
+
+		/* check if we'r still the tunnel for this connection */
+		if(conn->tunn != tunn)
+			break;
+
+		/* flush records in encapsulation */
+		p = rec + TLStunnhdrsz;
+		w += n;
+		n = w - p;
+		*(--p) = n;
+		*(--p) = n >> 8;
+		*(--p) = n >> 16;
+		*(--p) = n >> 24;
+		*(--p) = 0x80;	/* flags: Length included */
+		*(--p) = tunn->tp;
+
+		eapresp(conn, 2, tunn->id, p, w - p);
+		goto Reset;
 	}
-	datalen = p - buf;
-	if(write(fd, buf, datalen) != datalen)
-		sysfatal("write: %r");
+	free(rec);
+}
+
+void ttlsclient(int);
+void peapclient(int);
+
+void
+eapreset(Eapconn *conn)
+{
+	TLStunn *tunn;
+
+	tunn = conn->tunn;
+	if(tunn == nil)
+		return;
+	if(debug)
+		fprint(2, "eapreset: kill client %d\n", tunn->clientpid);
+	conn->tunn = nil;
+	postnote(PNPROC, tunn->clientpid, "kill");
+}
+
+int
+tlswrap(int fd, char *label)
+{
+	TLSconn *tls;
+
+	tls = emalloc(sizeof(TLSconn));
+	if(debug)
+		tls->trace = print;
+	if(label != nil){
+		/* tls client computes the 1024 bit MSK for us */
+		tls->sessionType = "ttls";
+		tls->sessionConst = label;
+		tls->sessionKeylen = 128;
+		tls->sessionKey = emalloc(tls->sessionKeylen);
+	}
+	if((fd = tlsClient(fd, tls)) < 0)
+		sysfatal("tlsClient: %r");
+	if(label != nil){
+		/*
+		 * PMK is derived from MSK by taking the first 256 bits.
+		 * we store the PMK into factotum with setpmk() associated
+		 * with the current essid.
+		 */
+		if(setpmk(tls->sessionKey) < 0)
+			sysfatal("setpmk: %r");
+	}
+	return fd;
+}
+
+void
+eapreq(Eapconn *conn, int code, int id, uchar *data, int datalen)
+{
+	TLStunn *tunn;
+	int tp, frag;
+	char *user;
+
+	if(debug)
+		fprint(2, "eapreq(code=%d, id=%d, data=%.*H)\n", code, id, datalen, data);
+
+	switch(code){
+	case 1:	/* Request */
+		break;
+	case 4:	/* NAK */
+	case 3:	/* Success */
+		eapreset(conn);
+		if(code == 4 || debug)
+			fprint(2, "%s: eap code %s\n", argv0, code == 3 ? "Success" : "NAK");
+	default:
+		return;
+	}
+
+	if(datalen < 1)
+		return;
+	tp = data[0];
+	switch(tp){
+	case 1:		/* Identity */
+		user = getidentity();
+		datalen = 1+strlen(user);
+		memmove(data+1, user, datalen-1);
+		eapresp(conn, 2, id, data, datalen);
+		break;
+	case 2:
+		fprint(2, "%s: eap error: %.*s\n", argv0, datalen-1, (char*)data+1);
+		break;
+	case 33:	/* EAP Extensions (AVP) */
+		if(debug)
+			fprint(2, "eap extension: %.*H\n", datalen, data);
+		eapresp(conn, 2, id, data, datalen);
+		break;
+	case 26:	/* MS-CHAP-V2 */
+		data++;
+		datalen--;
+		if(datalen < 1)
+			break;
+
+		/* OpCode */	
+		switch(data[0]){
+		case 1:	/* Challenge */
+			if(datalen > 4) {
+				uchar cid, chal[16], resp[48];
+				char user[256+1];
+				int len;
+
+				cid = data[1];
+				len = data[2]<<8 | data[3];
+				if(data[4] != sizeof(chal))
+					break;
+				if(len > datalen || (5 + data[4]) > len)
+					break;
+				memmove(chal, data+5, sizeof(chal));
+				memset(user, 0, sizeof(user));
+				memset(resp, 0, sizeof(resp));
+				if(auth_respond(chal, sizeof(chal), user, sizeof(user), resp, sizeof(resp), nil,
+					"proto=mschapv2 role=client service=wpa essid=%q", essid) < 0){
+					fprint(2, "%s: eap mschapv2: auth_respond: %r\n", argv0);
+					break;
+				}
+				len = 5 + sizeof(resp) + 1 + strlen(user);
+				data[0] = 2;		/* OpCode - Response */
+				data[1] = cid;		/* Identifier */
+				data[2] = len >> 8;
+				data[3] = len;
+				data[4] = sizeof(resp)+1;	/* ValueSize */
+				memmove(data+5, resp, sizeof(resp));
+				data[5 + sizeof(resp)] = 0;	/* flags */
+				strcpy((char*)&data[5 + sizeof(resp) + 1], user);
+
+				*(--data) = tp, len++;
+				eapresp(conn, 2, id, data, len);
+			}
+			break;
+
+		case 3:	/* Success */
+		case 4:	/* Failure */
+			if(debug || data[0] == 4)
+				fprint(2, "%s: eap mschapv2 %s: %.*s\n", argv0,
+					data[0] == 3 ? "Success" : "Failure",
+					datalen < 4 ? 0 : datalen-4, (char*)data+4);
+			*(--data) = tp;
+			eapresp(conn, 2, id, data, 2);
+			break;
+		}
+		break;
+
+	case 21:	/* EAP-TTLS */
+	case 25:	/* PEAP */
+		if(datalen < 2)
+			break;
+		datalen -= 2;
+		data++;
+		tunn = conn->tunn;
+		if(*data & 0x20){	/* flags: start */
+			int p[2], pid;
+
+			if(tunn != nil){
+				if(tunn->id == id && tunn->tp == tp)
+					break;	/* is retransmit, ignore */
+				eapreset(conn);
+			}
+			if(pipe(p) < 0)
+				sysfatal("pipe: %r");
+			if((pid = fork()) == -1)
+				sysfatal("fork: %r");
+			if(pid == 0){
+				close(p[0]);
+				switch(tp){
+				case 21:
+					ttlsclient(p[1]);
+					break;
+				case 25:
+					peapclient(p[1]);
+					break;
+				}
+				exits(nil);
+			}
+			close(p[1]);
+			tunn = emalloc(sizeof(TLStunn));
+			tunn->tp = tp;
+			tunn->id = id;
+			tunn->fd = p[0];
+			tunn->clientpid = pid;
+			conn->tunn = tunn;
+			if((pid = rfork(RFPROC|RFMEM)) == -1)
+				sysfatal("fork: %r");
+			if(pid == 0){
+				pid = getpid();
+				tunn->readerpid = pid;
+				tlsreader(tunn, conn);
+				if(conn->tunn == tunn)
+					conn->tunn = nil;
+				close(tunn->fd);
+				free(tunn);
+				exits(nil);
+			}
+			break;
+		}
+		if(tunn == nil)
+			break;
+		if(id <= tunn->id || tunn->tp != tp)
+			break;
+		tunn->id = id;
+		frag = *data & 0x40;	/* flags: more fragments */
+		if(*data & 0x80){	/* flags: length included */
+			datalen -= 4;
+			data += 4;
+		}
+		data++;
+		if(datalen <= 0)
+			break;
+		if(write(tunn->fd, data, datalen) != datalen)
+			break;
+		if(frag || (tp == 25 && data[0] == 20)){	/* ack change cipher spec */
+			data -= 2;
+			data[0] = tp;
+			data[1] = 0;
+			eapresp(conn, 2, id, data, 2);
+		}
+		break;
+	}
 }
 
+int
+avp(uchar *p, int code, void *val, int len)
+{
+	int n;
+
+	n = 4 + 4 + len;
+
+	*p++ = code >> 24;
+	*p++ = code >> 16;
+	*p++ = code >> 8;
+	*p++ = code;
+	*p++ = 2;
+
+	*p++ = n >> 16;
+	*p++ = n >> 8;
+	*p++ = n;
+
+	memmove(p, val, len);
+	p += len;
+
+	n = (n + 3) & ~3;
+	memset(p, 0, n - len);
+
+	return n;
+}
+
+enum {
+	/* Avp Code */
+	AvpUserName = 1,
+	AvpUserPass = 2,
+	AvpChapPass = 3,
+	AvpChapChal = 60,
+};
+
 void
+ttlsclient(int fd)
+{
+	uchar buf[4096];
+	UserPasswd *up;
+	int n;
+
+	fd = tlswrap(fd, "ttls keying material");
+
+	/* we should really do challenge response here */
+	if((up = auth_getuserpasswd(nil, "proto=pass service=wpa essid=%q", essid)) == nil)
+		sysfatal("ttlsclient %d: no user/pass for essid=%q", getpid(), essid);
+
+	n = avp(buf, AvpUserName, up->user, strlen(up->user));
+	n += avp(buf+n, AvpUserPass, up->passwd, strlen(up->passwd));
+	freeup(up);
+	if(write(fd, buf, n) != n)
+		sysfatal("ttlsclient write: %r");
+}
+
+void
+peapwrite(Eapconn *conn, uchar *data, int len)
+{
+	fdwrite(conn, data + 4, len - 4);
+}
+
+void
+peapclient(int fd)
+{
+	static Eapconn conn;
+	uchar buf[4096], *p;
+	int n, id, code;
+
+	conn.fd = fd = tlswrap(fd, "client EAP encryption");
+	while((n = read(fd, p = buf, sizeof(buf))) > 0){
+		if(n > 4 && (p[2] << 8 | p[3]) == n && p[4] == 33){
+			code = p[0];
+			id = p[1];
+			p += 4, n -= 4;
+			conn.write = fdwrite;
+		} else {
+			code = 1;
+			id = 0;
+			conn.write = peapwrite;
+		}
+		eapreq(&conn, code, id, p, n);
+	}
+}
+
+void
 usage(void)
 {
 	fprint(2, "%s: [-dp12] [-s essid] dev\n", argv0);
@@ -544,9 +1121,10 @@
 void
 main(int argc, char *argv[])
 {
-	uchar mac[Eaddrlen], buf[1024];
+	uchar mac[Eaddrlen], buf[4096];
 	static uchar brsne[258];
-	char addr[128];
+	static Eapconn conn;
+	char addr[128], *s;
 	uchar *rsne;
 	int rsnelen;
 	int n, try;
@@ -608,19 +1186,12 @@
 			sysfatal("no essid set");
 	}
 
-	if(prompt){
-		char *s;
-
-		s = smprint("proto=wpapsk essid=%q !password?", essid);
-		auth_getkey(s);
-		free(s);
-	}
-
 Connect:
  	/* bss scan might not be complete yet, so check for 10 seconds.	*/
-	for(try = 10; (background || try >= 0) && !connected(); try--)
+	for(try = 10; (background || try >= 0) && !connected(0); try--)
 		sleep(1000);
 
+	ispsk = 1;
 	if(rsnelen <= 0 || rsne == brsne){
 		rsne = brsne;
 		rsnelen = buildrsne(rsne);
@@ -645,6 +1216,27 @@
 	if(write(cfd, buf, n) != n)
 		sysfatal("write auth: %r");
 
+	if(prompt){
+		prompt = 0;
+		if(ispsk){
+			s = smprint("proto=wpapsk essid=%q !password?", essid);
+			auth_getkey(s);
+			free(s);
+		} else {
+			UserPasswd *up;
+
+			s = smprint("proto=pass service=wpa essid=%q user? !password?", essid);
+			auth_getkey(s);
+			free(s);
+
+			if((up = auth_getuserpasswd(nil, "proto=pass service=wpa essid=%q", essid)) != nil){
+				factotumctl("key proto=mschapv2 role=client service=wpa essid=%q user=%q !password=%q\n",
+					essid, up->user, up->passwd);
+				freeup(up);
+			}
+		}
+	}
+
 	if(!background){
 		background = 1;
 		if(!debug){
@@ -659,11 +1251,23 @@
 			}
 		}
 	}
+
+	/* wait for getting associated before sending start message */
+	for(try = 10; (background || try >= 0) && !connected(1); try--)
+		sleep(500);
+
+	conn.fd = fd;
+	conn.write = eapwrite;
+	conn.type = 1;	/* Start */
+	conn.version = 1;
+	memmove(conn.smac, mac, Eaddrlen);
+	if(getbssid(conn.amac) == 0)
+		eapwrite(&conn, nil, 0);
 	
 	lastrepc = 0ULL;
 	for(;;){
-		uchar smac[Eaddrlen], amac[Eaddrlen], snonce[Noncelen], anonce[Noncelen], *p, *e, *m;
-		int proto, eapver, flags, vers, datalen;
+		uchar snonce[Noncelen], anonce[Noncelen], *p, *e, *m;
+		int proto, flags, vers, datalen;
 		uvlong repc, rsc, tsc;
 		Keydescr *kd;
 
@@ -671,8 +1275,9 @@
 			sysfatal("read: %r");
 
 		if(n == 0){
-			if(debug != 0)
+			if(debug)
 				fprint(2, "got deassociation\n");
+			eapreset(&conn);
 			goto Connect;
 		}
 
@@ -680,10 +1285,12 @@
 		e = buf+n;
 		if(n < 2*Eaddrlen + 2)
 			continue;
-		memmove(smac, p, Eaddrlen); p += Eaddrlen;
-		memmove(amac, p, Eaddrlen); p += Eaddrlen;
+
+		memmove(conn.smac, p, Eaddrlen); p += Eaddrlen;
+		memmove(conn.amac, p, Eaddrlen); p += Eaddrlen;
 		proto = p[0]<<8 | p[1]; p += 2;
-		if(proto != 0x888e || memcmp(smac, mac, Eaddrlen) != 0)
+
+		if(proto != 0x888e || memcmp(conn.smac, mac, Eaddrlen) != 0)
 			continue;
 
 		m = p;
@@ -690,23 +1297,45 @@
 		n = e - p;
 		if(n < 4)
 			continue;
-		eapver = p[0];
-		if((eapver != 0x01 && eapver != 0x02) || p[1] != 0x03)
-			continue;
 
-		if(debug != 0)
-			fprint(2, "\nrecv(v%d) %E <- %E: ", eapver, smac, amac);
-
+		conn.version = p[0];
+		if(conn.version != 0x01 && conn.version != 0x02)
+			continue;
+		conn.type = p[1];
 		n = p[2]<<8 | p[3];
 		p += 4;
-		if(n < Keydescrlen || p + n > e){
-			if(debug != 0)
+		if(p+n > e)
+			continue;
+		e = p + n;
+
+		if(debug)
+			fprint(2, "\nrecv(v%d,t%d) %E <- %E: ", conn.version, conn.type, conn.smac, conn.amac);
+
+		if(conn.type == 0x00 && !ispsk){
+			uchar code, id;
+
+			if(n < 4)
+				continue;
+			code = p[0];
+			id = p[1];
+			n = p[3] | p[2]<<8;
+			if(n < 4 || p + n > e)
+				continue;
+			p += 4, n -= 4;
+			eapreq(&conn, code, id, p, n);
+			continue;
+		}
+
+		if(conn.type != 0x03)
+			continue;
+
+		if(n < Keydescrlen){
+			if(debug)
 				fprint(2, "bad kd size\n");
 			continue;
 		}
-		e = p + n;
 		kd = (Keydescr*)p;
-		if(debug != 0)
+		if(debug)
 			dumpkeydescr(kd);
 
 		if(kd->type[0] != 0xFE && kd->type[0] != 0x02)
@@ -724,8 +1353,8 @@
 
 			memmove(anonce, kd->nonce, sizeof(anonce));
 			genrandom(snonce, sizeof(snonce));
-			if(getptk(smac, amac, snonce, anonce, ptk) != 0){
-				if(debug != 0)
+			if(getptk(conn.smac, conn.amac, snonce, anonce, ptk) != 0){
+				if(debug)
 					fprint(2, "getptk: %r\n");
 				continue;
 			}
@@ -734,13 +1363,13 @@
 			memset(kd->rsc, 0, sizeof(kd->rsc));
 			memset(kd->eapoliv, 0, sizeof(kd->eapoliv));
 			memmove(kd->nonce, snonce, sizeof(kd->nonce));
-			reply(eapver, smac, amac, (flags & ~(Fack|Fins)) | Fmic, kd, rsne, rsnelen);
+			replykey(&conn, (flags & ~(Fack|Fins)) | Fmic, kd, rsne, rsnelen);
 		} else {
 			uchar gtk[GTKlen];
 			int gtklen, gtkkid;
 
 			if(checkmic(kd, m, e - m) != 0){
-				if(debug != 0)
+				if(debug)
 					fprint(2, "bad mic\n");
 				continue;
 			}
@@ -754,7 +1383,7 @@
 				(uvlong)kd->repc[1]<<48 |
 				(uvlong)kd->repc[0]<<56;
 			if(repc <= lastrepc){
-				if(debug != 0)
+				if(debug)
 					fprint(2, "bad repc: %llux <= %llux\n", repc, lastrepc);
 				continue;
 			}
@@ -773,11 +1402,11 @@
 				else
 					datalen = aesunwrap(ptk+16, 16, kd->data, datalen);
 				if(datalen <= 0){
-					if(debug != 0)
+					if(debug)
 						fprint(2, "bad keywrap\n");
 					continue;
 				}
-				if(debug != 0)
+				if(debug)
 					fprint(2, "unwraped keydata[%.4x]=%.*H\n", datalen, datalen, kd->data);
 			}
 
@@ -792,7 +1421,7 @@
 				for(; p+2 <= e; p = x){
 					if((x = p+2+p[1]) > e)
 						break;
-					if(debug != 0)
+					if(debug)
 						fprint(2, "ie=%.2x data[%.2x]=%.*H\n", p[0], p[1], p[1], p+2);
 					if(p[0] == 0x30){ /* RSN */
 					}
@@ -822,7 +1451,7 @@
 					rsc = 0LL;
 				}
 				/* install pairwise receive key */
-				if(fprint(cfd, "rxkey %.*H %s:%.*H@%llux", Eaddrlen, amac,
+				if(fprint(cfd, "rxkey %.*H %s:%.*H@%llux", Eaddrlen, conn.amac,
 					peercipher->name, peercipher->keylen, ptk+32, tsc) < 0)
 					sysfatal("write rxkey: %r");
 
@@ -830,11 +1459,11 @@
 				memset(kd->rsc, 0, sizeof(kd->rsc));
 				memset(kd->eapoliv, 0, sizeof(kd->eapoliv));
 				memset(kd->nonce, 0, sizeof(kd->nonce));
-				reply(eapver, smac, amac, flags & ~(Fack|Fenc|Fins), kd, nil, 0);
+				replykey(&conn, flags & ~(Fack|Fenc|Fins), kd, nil, 0);
 				sleep(100);
 
 				/* install pairwise transmit key */ 
-				if(fprint(cfd, "txkey %.*H %s:%.*H@%llux", Eaddrlen, amac,
+				if(fprint(cfd, "txkey %.*H %s:%.*H@%llux", Eaddrlen, conn.amac,
 					peercipher->name, peercipher->keylen, ptk+32, tsc) < 0)
 					sysfatal("write txkey: %r");
 			} else
@@ -853,7 +1482,7 @@
 				memset(kd->rsc, 0, sizeof(kd->rsc));
 				memset(kd->eapoliv, 0, sizeof(kd->eapoliv));
 				memset(kd->nonce, 0, sizeof(kd->nonce));
-				reply(eapver, smac, amac, flags & ~(Fenc|Fack), kd, nil, 0);
+				replykey(&conn, flags & ~(Fenc|Fack), kd, nil, 0);
 			} else
 				continue;
 
@@ -860,7 +1489,7 @@
 			if(gtklen >= groupcipher->keylen && gtkkid != -1){
 				/* install group key */
 				if(fprint(cfd, "rxkey%d %.*H %s:%.*H@%llux",
-					gtkkid, Eaddrlen, amac, 
+					gtkkid, Eaddrlen, conn.amac, 
 					groupcipher->name, groupcipher->keylen, gtk, rsc) < 0)
 					sysfatal("write rxkey%d: %r", gtkkid);
 			}