// 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 <u.h>
#include <libc.h>
#include <thread.h>
#include <bio.h>
#include <ip.h>
#include <libsec.h>
#include <auth.h>
#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");
}
}
}
|