Plan 9 from Bell Labs’s /usr/web/sources/contrib/axel/8021x/v01/8021x.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


// 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, &ethercfd);
	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");
		}
	}
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.