// 802.1x thingy // // what do we have to do: // // be able to send/receive EAPOL frames // implement Supplicant state machine and sub-machine // set wep keys when applicable // // 802.1x thingy // // // our job: // // get access tocard interface // read/write eapol frames // be able to set wep keys // // supplicant state machine // key receival state machine // auth interaction #include #include #include #include #include #include #include #include "dat.h" #include "fns.h" char *mydefId=""; // hard coded defaults? char *mydefPasswd=""; // hard coded defaults? UserPasswd*upwd; typedef enum PortControl { Auto, ForceUnauthorized, ForceAuthorized, } PortControl; typedef enum AuthState { Unauthorized, Authorized, } AuthState; // Supplicant PAE state machine (8.2.11) states enum { Logoff, Disconnected, Connecting, Authenticating, Held, Authenticated, Restart, ForceAuth, ForceUnauth, }; char *paenames[] = { [Logoff] "Logoff", [Disconnected] "Disconnected", [Connecting] "Connecting", [Authenticating] "Authenticating", [Held] "Held", [Authenticated] "Authenticated", [Restart] "Restart", [ForceAuth] "ForceAuth", [ForceUnauth] "ForceUnauth", }; // Supplicant Backend state machine (8.2.12) states enum { Request, Response, Success, Fail, Timeout, Idle, Initialize, Receive, }; char *bnames[] = { [Request] "Request", [Response] "Response", [Success] "Success", [Fail] "Fail", [Timeout] "Timeout", [Idle] "Idle", [Initialize] "Initialize", [Receive] "Receive", }; // Supplicant PAE state machine constants (sect 8.2.11.1.2) static int heldPeriod = 60; //seconds static int startPeriod = 30; //seconds static int maxStart = 3; // Supplicant Backend state machine constants (sect 8.2.12.1.2) static int authPeriod = 30; //seconds static int backState = -1; // Supplicant PAE state machine variables (sect 8.2.11.1) static int eapRestart; static int logoffSent; static PortControl sPortMode; static int startCount; static int userLogoff; static int paeState = -1; // Supplicant Backend state machine variables (sect 8.2.12.1.1) static int eapNoResp; static int eapReq; static int eapResp; // Timers (sect 8.2.2.1) static int authWhile = -1; static int heldWhile = -1; static int startWhen = -1; // Global variables (sect 8.2.2.2) static int eapFail; static int eapolEap; static int eapSuccess; static int initialize; static int keyDone; static int keyRun; static PortControl portControl; static int portEnabled; static AuthState portStatus; static int portValid; static int suppAbort; static int suppFail; static AuthState suppPortStatus; static int suppStart; static int suppSuccess; static int suppTimeout; // other static int eapExpectTtlsStart; static int rcvdEtherEap; static uchar *txEtherEap; static int txEtherLen; int *etherAltOp; char ext_identity[] = ""; char *int_identity; uchar *pktr[10], *pktt; int pkgidx, pkgjdx; char *buf, *file, *p, *e; int etherfd, ethercfd; uchar defmac[6] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x03}; uchar ourmac[6]; uchar apmac[6]; static char errbuf[256]; // ========== Port timers 'state machine' (8.2.3) void tick(void) { if (authWhile >= 0) authWhile--; if (heldWhile >= 0) heldWhile--; if (startWhen >= 0) startWhen--; } void clockproc(void *arg) { int t; Channel *c; c = arg; for(t=0;; t++){ sleep(1000); sendul(c, t); } } // ========== receive eapol frames void etherproc(void *arg) { Channel *c; int n; Ether *eh; Eapol *lh; uchar*p; c = arg; for(;;){ // if (debug) print("etherproc: waiting for %d\n", etherfd); n = read(etherfd, pktr[pkgidx], Pktlen); // if (debug) print("etherproc: read %d\n", n); if(n <= 0) break; p = pktr[pkgidx]; eh = (Ether*)p; if (nhgets(eh->t) != ETEAPOL) { print("etherproc: skipping non-ETEAPOL %x\n", nhgets(eh->t)); continue; } lh = (Eapol*)eh->data; if (debug) print("etherproc: read %d pktr[pkgidx]=%p lh=%p ehsz=%x eapol type %d ver %d len %d\n", n, pktr[pkgidx], lh, ETHERHDR, lh->tp, lh->ver, nhgets(lh->ln)); switch(lh->tp){ case EapolTpEap: // if (debug) print("etherproc: eap pkt =%p\n", pktr[pkgidx]); sendul(c, pkgidx); pkgidx=(pkgidx+1)%10; break; case EapolTpStart: print("etherproc: start (ignored)\n"); break; case EapolTpLogoff: print("etherproc: logoff (ignored)\n"); break; case EapolTpKey: if (debug) print("etherproc: key\n"); handleKey(ethercfd, lh, n - (eh->data - eh->d)); break; case EapolTpAsf: print("etherproc: asf (ignored)\n"); break; default: print("etherproc: unknown type%d\n", lh->tp); break; } } print("etherproc: oops read %d...\n", n); } // ========== Key receive 'state machine' (8.2.7) // XXX do we do this in a separate thread/proc, or in the main one? // see key.c:/^handleKey // ========== Supplicant backend state machine // clean up/initialize void abortSupp(void) { eapSuccess = 0; eapFail = 0; eapNoResp = 0; eapReq = 0; eapResp = 0; } // (get info to) build response to most recent EAP request static void clear_eap(Eap*t) { memset(t, 0, sizeof(Eap)); } static void build_eap(Eap*t, int code, int id, int datalen) { t->code = code; t->id = id; hnputs(t->ln, EAPHDR + datalen); } static void show_notification(uchar *s, int l) { char buf[2048]; // should do better: rfc3748 says: // s contains UTF-8 encoded ISO 10646 [RFC2279]. memset(buf, 0, sizeof(s)); memcpy(buf, s, l); syslog(0, logname, "notification: %s", buf); } void getSuppRsp(void) { // handle rcvdEtherEap // build txEtherEap Ether *er, *et; Eapol *lr, *lt; Eap *r, *t; uchar tp; uchar *p, *br, *bt; char *ident; int len; int tlssucces, tlsfailed; // if (debug) print("getSuppRsp eapResp=%d eapNoResp=%d\n", eapResp, eapNoResp); if (eapResp||eapNoResp) print("oops... getSuppRsp called while result previous of prev call pending\n"); pkgjdx = rcvdEtherEap; p = pktr[pkgjdx]; // if (debug) print("pkgjdx=%d pkt=%p\n", pkgjdx, p); // pkgjdx = (pkgjdx+1)%10; er = (Ether*)p; lr = (Eapol*)er->data; r = (Eap*)lr->data; br = r->data; // if (debug) print("getSuppRsp p=%p er=%p lr=%p r=%p br=%p\n", p, er,lr,r,br); p = pktt; txEtherEap = p; memset(txEtherEap, 0, Pktlen); et = (Ether*)p; lt = (Eapol*)et->data; t = (Eap*)lt->data; bt = t->data; // if (debug) print("getSuppRsp et=%p lt=%p t=%p bt=%p\n", et,lt,t,bt); if (debug) print("getSuppRsp code=%d id=%d len=%d ", (uchar)r->code, (uchar)r->id, nhgets(r->ln)); switch(r->code){ case EapRequest: tp = br[0]; if (debug) print("getSuppRsp EapRequest: %d \n", tp); switch(tp){ case EapTpIdentity: // data format: [ prompt ] [ '\0' piggy-backed-options ] // show prompt? extract options? // the following is a HACK. // but: SNT macosX notes only mention config of // internal username and password (for TTLS-PAP), // and allow leaving external identity blank. // rfc3748 specifically says to _not_ include the // username in the external identity syslog(0, logname, "received EAP Identity request"); if (strcmp(ext_identity,"") != 0) ident = ext_identity; else if ((ident = strchr(int_identity, '@')) == nil) ident = ""; bt[0] = EapTpIdentity; memcpy(bt+1, ident, strlen(ident)); build_eap(t, EapResponse, r->id, 1+strlen(ident)); eapResp = 1; eapExpectTtlsStart = 1; break; case EapTpNotification: bt[0] = EapTpNotification; build_eap(t, EapResponse, r->id, 1); eapResp = 1; show_notification(br+1, nhgets(r->ln)-EAPHDR+1); break; case EapTpTtls: tlssucces = 0; tlsfailed = 0; len = processTTLS(br, nhgets(r->ln)-EAPHDR, eapExpectTtlsStart, bt, ETHERMAXTU-ETHERHDR-EAPOLHDR-EAPHDR, &tlssucces, &tlsfailed); eapExpectTtlsStart = 0; if (debug) print("processTTLS returns len=%d\n", len); if (len > 0) { build_eap(t, EapResponse, r->id, len); eapResp = 1; } else eapNoResp = 1; break; case EapTpNak: // only allowed in responses case EapTpExtp: case EapTpExus: default: bt[0] = EapTpNak; bt[1] = EapTpTtls; build_eap(t, EapResponse, r->id, 1+1); eapResp = 1; break; } break; case EapResponse: if (debug) print("getSuppRsp EapResponse\n"); eapNoResp = 1; break; case EapSuccess: if (debug) print("getSuppRsp EapSuccess\n"); syslog(0, logname, "success"); eapSuccess = 1; eapNoResp = 1; break; case EapFailure: if (debug) print("getSuppRsp EapFailure\n"); syslog(0, logname, "fail"); eapFail = 1; eapNoResp = 1; break; default: if (debug) print("getSuppRsp unknown eap type %d\n", r->code); break; } if (eapResp){ memcpy(et->s, er->d, 6); memcpy(et->d, er->s, 6); memcpy(et->t, er->t, 2); lt->ver = lr->ver; lt->tp = lr->tp; memcpy(lt->ln,t->ln,2); txEtherLen = nhgets(t->ln)+EAPOLHDR+ETHERHDR; } if (!(eapResp || eapNoResp || eapSuccess || eapFail)) print("internal error - no eap result set\n"); eapReq = 0; if (debug) print("getSuppRsp done eapResp=%d eapNoResp=%d\n", eapResp, eapNoResp); } // transmit EAP-Packet EAPOL frame to Authenticator void txSuppRsp(void) { int n, l; l = (txEtherLen>ETHERMINTU)?txEtherLen:ETHERMINTU; if (debug) print("txSuppRsp writing to ether l=%d L=%d\n", l, txEtherLen); n = write(etherfd, txEtherEap, l); if (n != l) print("txSuppRsp: writen %d of %d:%r", n, l); } void btrans(int *s, int new) { if (debug) print("back trans: %s -> %s\n", (*s>=0)?bnames[*s]:"-", bnames[new]); switch(new){ case Request: authWhile = 0; eapReq = 1; getSuppRsp(); break; case Response: txSuppRsp(); eapResp = 0; break; case Success: suppSuccess = 1; keyRun=1; portValid = 1; // we should actually check this or so break; case Fail: suppFail = 1; break; case Timeout: suppTimeout = 1; break; case Idle: suppStart = 0; break; case Initialize: abortSupp(); suppAbort = 0; break; case Receive: authWhile = authPeriod; eapolEap = 0; *etherAltOp = CHANRCV; eapNoResp = 0; break; } *s = new; } int back(int *s) { // if (debug) print("^"); //print("back: %s\n", *s, (*s>=0)?bnames[*s]:"-"); if (*s != Initialize && (initialize || suppAbort)) btrans(s, Initialize); switch(*s){ case Request: if (eapResp) btrans(s, Response); else if (eapNoResp) btrans(s, Receive); else if (eapFail) btrans(s, Fail); else if (eapSuccess) btrans(s, Success); break; case Response: btrans(s, Receive); break; case Success: btrans(s, Idle); break; case Fail: btrans(s, Idle); break; case Timeout: btrans(s, Idle); break; case Idle: if (eapolEap && suppStart) btrans(s, Request); else if(eapSuccess && suppStart) btrans(s, Success); else if(eapFail && suppStart) btrans(s, Fail); break; case Initialize: if (!initialize && !suppAbort) btrans(s, Idle); break; case Receive: if(eapolEap) btrans(s, Request); else if (eapFail) btrans(s, Fail); else if (authWhile == 0) btrans(s, Timeout); else if (eapSuccess) btrans(s, Success); break; } //print("back return: %s\n", bnames[*s]); return *s; } // ========== Supplicant PAE state machine // transmit EAPOL-Start frame to Authenticator void txStart(void) { Ether *et; Eapol *lt; uchar tp; uchar *p, *bt; int len; // get fresh ap mac - we may have roamed if (apetheraddr(apmac, file) < 0) { snprint(errbuf, sizeof(errbuf), "could not read access point ether address from %s", file); syslog(0, logname, "%s", errbuf); fprint(2, "%s\n", errbuf); threadexitsall(errbuf); } syslog(0, logname, "sending EAPOL Start frame to %E", apmac); p = pktt; txEtherEap = p; memset(txEtherEap, 0, Pktlen); et = (Ether*)p; lt = (Eapol*)et->data; lt->ver=EapolVersion; lt->tp=EapolTpStart; memset(lt->ln, 0, 2); memcpy(et->s, ourmac, 6); memcpy(et->d, apmac, 6); hnputs(et->t, ETEAPOL); txEtherLen = EAPOLHDR+ETHERHDR; txSuppRsp(); } // transmit EAPOL-Logoff frame to Authenticator void txLogoff(void) { } void ptrans(int *s, int new) { if (debug) print("pae trans: %s -> %s\n", (*s>=0)?paenames[*s]:"-", paenames[new]); switch(new){ case Logoff: txLogoff(); logoffSent = 1; suppPortStatus = Unauthorized; break; case Disconnected: sPortMode = Auto; startCount = 0; logoffSent = 0; suppPortStatus = Unauthorized; suppAbort = 1; break; case Connecting: startWhen = startPeriod; startCount ++; eapolEap = 0; txStart(); break; case Held: heldWhile=heldPeriod; suppPortStatus = Unauthorized; break; case Authenticated: suppPortStatus = Authorized; break; case Restart: eapRestart = 1; break; case ForceAuth: suppPortStatus = Authorized; sPortMode = ForceAuthorized; break; case ForceUnauth: suppPortStatus = Unauthorized; sPortMode = ForceUnauthorized; // no check?? txLogoff(); logoffSent = 1; break; case Authenticating: startCount = 0; suppSuccess = 0; suppFail = 0; suppTimeout = 0; keyRun = 0; keyDone = 0; suppStart = 1; break; } *s=new; } int pae(int *s) { int bs; // if (debug) print("_"); //print("pae: %s\n", (*s>=0)?paenames[*s]:"-"); if (*s!=Logoff && (userLogoff && !logoffSent && portEnabled && !initialize)) ptrans(s, Logoff); else if (*s!=Disconnected && ((portControl==Auto && sPortMode!=portControl) || initialize || !portEnabled)) ptrans(s, Disconnected); else if (*s!=ForceAuth && (portControl==ForceAuthorized && sPortMode!=ForceAuthorized && portEnabled && !initialize)) ptrans(s, ForceAuth); else if (*s!=ForceUnauth && (portControl==ForceUnauthorized && sPortMode!=ForceUnauthorized && portEnabled && !initialize)) ptrans(s, ForceUnauth); switch(*s){ case Logoff: if (!userLogoff) ptrans(s, Disconnected); break; case Disconnected: if (portEnabled) ptrans(s, Connecting); break; case Connecting: if (eapolEap) ptrans(s, Restart); else if (eapSuccess || eapFail) ptrans(s, Authenticating); else if (startWhen == 0 && startCount < maxStart) ptrans(s, Connecting); else if (startWhen == 0 && startCount >= maxStart && portValid) ptrans(s, Authenticated); else if (startWhen == 0 && startCount >= maxStart) ptrans(s, Held); break; case Authenticating: if (suppSuccess && portValid) ptrans(s, Authenticated); else if(suppSuccess) ; // ??? else if (suppFail || (keyDone && !portValid)) ptrans(s, Held); else if (suppTimeout) ptrans(s, Connecting); break; case Held: if (eapolEap) ptrans(s, Restart); else if (heldWhile == 0) ptrans(s, Connecting); break; case Authenticated: if(eapolEap && portValid) ptrans(s, Restart); else if (!portValid) ptrans(s, Disconnected); break; case Restart: if (!eapRestart) ptrans(s, Authenticating); break; case ForceAuth: break; case ForceUnauth: break; } //print("pae return: %s\n", paenames[*s]); bs = -2; while(bs != backState) bs = back(&backState); return *s; } // ========== run state machines void update(void) { int ps; ps = -2; while(ps != paeState) ps = pae(&paeState); } // ========== main thing void threadmain(int argc, char *argv[]) { int t; //ignored int idx; Alt a[] = { /* c v op */ {nil, &t, CHANRCV}, {nil, &idx, CHANRCV}, {nil, nil, CHANEND}, }; int i; fmtinstall('E', eipfmt); ARGBEGIN{ case 'd': debug++; break; case 'D': debugTLS++; break; }ARGEND; for(i=0; i<10;i++) { pktr[i] = malloc(Pktlen+16); pktr[i] += 16; } pktt = malloc(Pktlen+16); pktt += 16; buf = malloc(Blen); e = buf+Blen-1; if(argc == 0) file = "/net/ether0"; else file = argv[0]; logname = "8021x"; syslog(0, logname, "starting"); snprint(buf, Blen, "%s!0x888e", file); etherfd = dial(buf, 0, 0, ðercfd); if(etherfd < 0) { snprint(errbuf, sizeof(errbuf), "could not dial %s: %r", buf); syslog(0, logname, "%s", errbuf); fprint(2, "%s\n", errbuf); threadexitsall(errbuf); } if (myetheraddr(ourmac, file) < 0) { snprint(errbuf, sizeof(errbuf), "could not read own ether addres from %s", file); syslog(0, logname, "%s", errbuf); fprint(2, "%s\n", errbuf); threadexitsall(errbuf); } upwd = auth_getuserpasswd(auth_getkey, "proto=pass service=8021x-pap"); if (upwd) { myId = upwd->user; myPasswd = upwd->passwd; } else { myId = mydefId; myPasswd = mydefPasswd; } int_identity = myId; /* create clock event channel and clock process */ a[0].c = chancreate(sizeof(ulong), 0); /* clock event channel */ proccreate(clockproc, a[0].c, STACK); etherAltOp=&a[1].op; /* create eap channel and eapol receiver process */ a[1].c = chancreate(sizeof(int), 0); /* pkt* channel */ proccreate(etherproc, a[1].c, STACK); portEnabled = 1; initTTLS(); initialize = 1; update(); initialize = 0; update(); for(;;){ update(); if (eapRestart) { eapRestart = 0; // print("restarting\n"); syslog(0, logname, "restarting"); update(); } switch(alt(a)){ case 0: /* clock event */ if (debug) print("."); tick(); break; case 1: /* eap received */ if (debug) print("threadmain: eap idx=%d pkt=%p\n", idx, pktr[idx]); *etherAltOp = CHANNOP; if (eapolEap) print("threadmain: oops too fast eap pkt\n"); eapolEap = 1; rcvdEtherEap = idx; break; default: fprint(2, "can't happen\n"); syslog(0, logname, "%s", errbuf); threadexitsall("can't happen"); } } }