Plan 9 from Bell Labs’s /usr/web/sources/contrib/axel/8021x/v212/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 <fcall.h>
#include <thread.h>
#include <9p.h>
#include <bio.h>
#include <ip.h>
#include <mp.h>
#include <libsec.h>
#include <auth.h>
#include "dat.h"
#include "fns.h"

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",
};

typedef struct backendstate {
	// Supplicant Backend state machine constants (sect 8.2.12.1.2)
	int authPeriod;
	int backState;

	// Supplicant Backend state machine variables (sect 8.2.12.1.1)
	 int eapNoResp;
	int eapReq;
	int eapResp;

	// Timers (sect 8.2.2.1)
	Timer *authWhile;
} backendstate;

typedef struct phasestate {
	vlong startTime;
	vlong doneTime;
	char *type;
	int success;
} phasestate;

typedef struct PAEstate {
	// Supplicant PAE state machine constants (sect 8.2.11.1.2)
	int heldPeriod;
	int startPeriod;
	int maxStart;

	// Supplicant PAE state machine variables (sect 8.2.11.1)
	int eapRestart;
	int logoffSent;
	PortControl sPortMode;
	int startCount;
	int userLogoff;
	int paeState;

	// Timers (sect 8.2.2.1)
	Timer *heldWhile;
	Timer *startWhen;

	backendstate;

	// Global variables  (sect 8.2.2.2)
	int eapFail;
	int eapolEap;
	int eapSuccess;
	int initialize;
	int keyDone;
	int keyRun;
	PortControl portControl;
	int portEnabled;
	AuthState portStatus;
	int portValid;	// needs work. happens if we cannot see the AP; ifstats shows ap = 4444444 or so
	int suppAbort;
	int suppFail;
	AuthState suppPortStatus;
	int suppSuccess;
	int suppTimeout;

	// other
	int eapExpectTtlsStart;

	Packet *rxEtherEap;
	Packet *txEtherEap;

	char *etherdir;
	int etherfd, ethercfd;
	uchar ourmac[6];
	uchar apmac[6];
	char *prompt;
	char *options;

	Channel *etherchan;
	Channel *statuschan;
	Channel *timerchan;
	Channel *portchan;
	Channel *backstart;
	Channel *backdone;

	Packet *pktr[Npkt], *pktt;
	int pkgidx;

	ReadBuf keysbuf;
	ReadBuf notesbuf;

	vlong paeTime;
	vlong backTime;
	vlong startTime;
	vlong restartTime;
	vlong eapidTime;
	vlong keyTime;
	vlong noteTime;
	vlong verdictTime;

	phasestate phase[2];

	int lastEapId;
} PAEstate;

// other

static UserPasswd*upwd;

static uchar defmac[6] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x03};

// static char buf[Blen];
static  char buf[2048];

// ========== Port timers 'state machine' (8.2.3)

// see timer.c

// ========== 

static PAEstate theState;
static Timers TheTimers;
extern Srv fs;

// ========== receive eapol frames

static void
etherproc(void *arg)
{
	PAEstate *s;
	Packet *rx;
	int n;

	s = arg;
	for(;;) {
		loglog("etherproc: waiting for %d into %d", s->etherfd, s->pkgidx);
		rx = s->pktr[s->pkgidx];

		// don't do this: we do not reset rx-> for packets not sent over etherchan
		//if (rx->e != rx->b)
		//	logfatal("assertion failed: rx->e != rx->b (n == %ld)", rx->e - rx->b);

		n = read(s->etherfd, rx->b, Pktlen);
		loglog("etherproc: into %d read %d", s->pkgidx, n);
		if(n <= 0)
			break;

		rx->e = rx->b + n;

		if (rx->e < rx->ether->data) {
			logall("etherproc: skipping short packet (ether len=%d)", n);
			continue;
		}
		if (nhgets(rx->ether->t) != ETEAPOL) {
			logall("etherproc: skipping non-ETEAPOL %x", nhgets(rx->ether->t));
			continue;
		}
		if (rx->e < rx->eapol->data) {
			logall("etherproc: skipping short packet (ether len=%d)", n);
			continue;
		}
		if (rx->e < rx->eapol->data + nhgets(rx->eapol->ln)) {
			logall("etherproc: skipping short packet (ether len=%d)", n);
			continue;
		}
		switch(rx->eapol->tp) {
		case EapolTpEap:
			if (rx->e < rx->eap->data) {
				logall("etherproc: skipping short packet (ether len=%d)", n);
				continue;
			}
			if (rx->e < rx->eapol->data + nhgets(rx->eap->ln)) {
				logall("etherproc: skipping short packet (ether len=%d)", n);
				continue;
			}
			if (s->lastEapId == rx->eap->id)
				loglog("etherproc lastEapId==eap->id==%d", rx->eap->id);
			s->lastEapId = rx->eap->id;
			switch(rx->eap->code) {
			case EapRequest:
				loglog("- - - -  Eap Request id=%d - - - - ", rx->eap->id);
				loglog("etherproc: about to send %d ", s->pkgidx);
				send(s->etherchan, &rx);
				loglog("\tetherproc: done send %d ", s->pkgidx);
				s->pkgidx = (s->pkgidx+1)%Npkt;
				break;
			case EapResponse:
				loglog("- - - -  Eap Response id=%d - - - - ", rx->eap->id);
				break;
			case EapSuccess:
				loglog("- - - - success id=%d - - - -", rx->eap->id);
				syslog(0, logname, "etherproc: success id=%d", rx->eap->id);
				s->verdictTime = nsec();
				s->eapSuccess = 1;
				send(s->statuschan, nil);
				clearlog(getKeysbuf());
				break;
			case EapFailure:
				loglog("- - - - fail id=%d - - - -", rx->eap->id);
				syslog(0, logname, "etherproc: fail id=%d", rx->eap->id);
				s->verdictTime = nsec();
				s->eapFail = 1;
				send(s->statuschan, nil);
				clearlog(getKeysbuf());
				break;
			default:
				loglog("- - - - unknown eap id=%d type=%d - - - - ", rx->eap->id, rx->eap->code);
				syslog(0, logname, "etherproc: unknown eap id=%d type=%d", rx->eap->id, rx->eap->code);
				break;
			}
			break;
		case EapolTpStart:
			logall("etherproc: start (ignored)");
			break;
		case EapolTpLogoff:
			logall("etherproc: logoff (ignored)");
			break;
		case EapolTpKey:
			if (s->keyRun || s->eapSuccess) {
				loglog("- - - -  key - - - -");
				syslog(0, logname, "etherproc: key");
				s->keyTime = nsec();
				handleKey(s->ethercfd, rx->eapol, rx->e - rx->ether->data);
			} else
				logall("etherproc: ignoring key (not authed yet)");
			break;
		case EapolTpAsf:
			logall("etherproc: asf (ignored)");
			break;
		default:
			logall("etherproc: unknown type%d", rx->eapol->tp);
			break;
		}
	}
	logfatal(0, "etherproc: oops read %d...", 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
static void
abortSupp(PAEstate *s)
{
	s->eapSuccess = 0;
	s->eapFail = 0;
	s->eapNoResp = 0;
	s->eapReq = 0;
	s->eapResp = 0;
	s->suppAbort = 0;
//	abortTTLS();
}

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(PAEstate *s, uchar *m, int l)
{
	// should do better: rfc3748 says:
	// s contains UTF-8 encoded ISO 10646  [RFC2279].
	s->noteTime = nsec();
	memset(buf, 0, sizeof(m));
	memcpy(buf, m, l);
	logall("notification: %s", buf);
	appendlog(getNotesbuf(), 0, "%s", buf);
}
static void
getSuppRsp(PAEstate *s)
{
	// handle rxEtherEap
	// build txEtherEap
	Packet *rx, *tx;
	uchar *p, *beyond;
	char *ident, *prompt;
	int len;
	int tlssucces, tlsfailed;

	loglog("getSuppRsp %p", s->rxEtherEap);

	if (s->eapResp || s->eapNoResp)
		logall("oops... getSuppRsp called while result previous of prev call pending");

	rx = s->rxEtherEap;
	tx = s->pktt;
	s->txEtherEap = tx;
	memset(s->txEtherEap->b, 0, Pktlen);

	switch(rx->eap->code) {
	case EapRequest:
		if (debug) print("getSuppRsp EapRequest: %d \n", rx->eap->data[0]);
		switch(rx->eap->data[0]) {
		case EapTpIdentity:
			// data format: [ prompt ] [ '\0' piggy-backed-options ]
			// show prompt? extract options?
			beyond = rx->eapol->data + nhgets(rx->eap->ln);
			p = &rx->eap->data[1];
			prompt = (char*)p;
			for (; *p != '\0' && p+1 < beyond; p++)
				;
			memset(buf, 0, sizeof(buf));
			if (*p == '\0' && p+1 < beyond) {
				memcpy(buf, p+1, beyond - (p+1));
				logall("received EAP Identity request, prompt=\"%s\" options=\"%s\"", prompt, buf);
				free(s->prompt);
				s->prompt = strdup(prompt);
				free(s->options);
				s->options = strdup(buf);
//				while ((p = strchr(buf, ',')) != nil)
//					*p = '\n';
			} else {
				memcpy(buf, &rx->eap->data[1], beyond - &rx->eap->data[1]);
				logall("received EAP Identity request, data=\"%s\"", buf);
				free(s->prompt);
				s->prompt = strdup(buf);
				free(s->options);
				s->options = nil;
			}

			// 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
			if ((ident = strchr(myId, '@')) == nil)
				ident = "";
			tx->eap->data[0] = EapTpIdentity;
			memcpy(&tx->eap->data[1], ident, strlen(ident));
			build_eap(tx->eap, EapResponse, rx->eap->id, 1+strlen(ident));
			s->eapResp = 1;
			s->eapExpectTtlsStart = 1;
			break;
		case EapTpNotification:
			tx->eap->data[0] = EapTpNotification;
			build_eap(tx->eap, EapResponse, rx->eap->id, 1);
			s->eapResp = 1;
			show_notification(s, &rx->eap->data[1] , nhgets(rx->eap->ln)-EAPHDR+1);
			break;
		case EapTpTtls:
			tlssucces = 0;
			tlsfailed = 0;
			len = processTTLS(rx->eap->data, nhgets(rx->eap->ln)-EAPHDR, s->eapExpectTtlsStart, tx->eap->data, ETHERMAXTU-ETHERHDR-EAPOLHDR-EAPHDR, &tlssucces, &tlsfailed);
			if (tlsfailed)
				loglog("processTTLS failed");
			s->eapExpectTtlsStart = 0;
			if (debug) print("processTTLS returns len=%d\n", len);
			if (len > 0) {
				build_eap(tx->eap, EapResponse, rx->eap->id, len);
				s->eapResp = 1;
			} else
				s->eapNoResp = 1;
			break;
		case EapTpNak: // only allowed in responses
		case EapTpExtp:
		case EapTpExus:
		default: 
			// tell we can't deal with this type; tell we can only do ttls
			tx->eap->data[0] = EapTpNak; 
			tx->eap->data[1] = EapTpTtls;
			build_eap(tx->eap, EapResponse, rx->eap->id, 1+1);
			s->eapResp = 1;
			break;
		}
		break;
	default:
		logall("getSuppRsp unexpected eap type %d", rx->eap->code);
		break;
	}
	if (s->eapResp) {
		memcpy(tx->ether->s, rx->ether->d, 6);
		memcpy(tx->ether->d, rx->ether->s, 6);
		memcpy(tx->ether->t, rx->ether->t, 2);
		tx->eapol->ver = rx->eapol->ver;
		tx->eapol->tp = rx->eapol->tp;
		memcpy(tx->eapol->ln, tx->eap->ln, 2);
		tx->e = tx->eapol->data + nhgets(tx->eap->ln);
	}

	if (!(s->eapResp || s->eapNoResp || s->eapSuccess || s->eapFail))
		logall("internal error - no eap result set\n");

	// prepare for reuse
	memset(rx->b, 0, Pktlen);
	rx->e = 0; // or rx->e = rx->b; ???

	s->eapReq = 0;
	if (debug) print("getSuppRsp done eapResp=%d eapNoResp=%d\n", s->eapResp, s->eapNoResp);
}

// transmit EAP-Packet EAPOL frame to Authenticator
static void
txSuppRsp(PAEstate *s)
{
	int n, l, len;
	Packet *p;

	p = s->txEtherEap;
	len = p->e - p->b;
	l = (len > ETHERMINTU) ? len : ETHERMINTU;
	if (debug) print("txSuppRsp writing to ether l=%d L=%d\n", l, len);
	if (p->eapol->tp == EapolTpEap &&
	    p->eap->code == EapResponse &&
	    p->eap->data[0] == EapTpIdentity) {
		logall("sending eap external identity");
		s->eapidTime = nsec();
	}
	n = write(s->etherfd, p->b, l);
	if (n != l)
		logall("txSuppRsp: written %d of %d: %r", n, l);
	loglog("txSuppRsp: written %d", n);
}

static void
btrans(PAEstate *s, int new)
{
	loglog("back trans: %s -> %s", (s->backState>=0)?bnames[s->backState]:"-", bnames[new]);
	s->backState = new;
	s->backTime = nsec();
}

static int
back(PAEstate *s)
{
	Packet *rx;
	int done;
	Alt a[] = {
	/*	 c				v		op   */
		{s->etherchan,	&rx,	CHANRCV},
		{s->timerchan,	nil,	CHANRCV},
		{s->statuschan,	nil,	CHANRCV},
		{nil,			nil,	CHANEND},
	};

	if (s->backState != Initialize && (s->initialize || s->suppAbort))
		btrans(s, Initialize);

	switch(s->backState) {
	case Initialize:
		abortSupp(s);
		s->suppAbort = 0;
		if (!s->initialize && !s->suppAbort) {
		} else
			logall("back Initialize: should not happen");
		btrans(s, Idle);
		break;
	case Idle:
		send(s->backdone, nil);
		recv(s->backstart, nil);
		if (s->eapolEap)
			btrans(s, Request);
		else if(s->eapSuccess)
			btrans(s, Success);
		else if(s->eapFail)
			btrans(s, Fail);
		else if(s->suppAbort )
			btrans(s, Initialize);
		else
			logfatal(0, "back Idle: should not happen");
		break;
	case Request:
		s->eapReq = 1;
		getSuppRsp(s);
		if (s->eapResp)
			btrans(s, Response);
		else if (s->eapNoResp)
			btrans(s, Receive);
		else if (s->eapFail)
			btrans(s, Fail);
		else if (s->eapSuccess)
			btrans(s, Success);
		else if(s->suppAbort )
			btrans(s, Initialize);
		else
			logfatal(0, "back Request: should not happen");
		break;
	case Response:
		txSuppRsp(s);
		s->eapResp = 0;
		btrans(s, Receive);
		break;
	case Receive:
		s->eapolEap = 0;
		startTimer(s->authWhile, s->authPeriod);
		done = 0;
		while(!done)
			switch(alt(a)) {
			case 0:	/* eap received */
				loglog("back Receive eap received");
				done = 1;
				s->rxEtherEap = rx;
				s->eapolEap = 1;
				btrans(s, Request);
				break;
			case 1:	/* timer expiration event */
				// loglog("back Receive timer tick");
				tickTimer(s->authWhile);
				if (s->authWhile->counter == 0) {
					logall("authWhile timer expired");
					done = 1;
					btrans(s, Timeout);
				}
				break;
			case 2:	/* eapSuccess or eapFail */
				loglog("back Receive eapSuccess or eapFail");
				done = 1;
				if (s->eapFail)
					btrans(s, Fail);
				else if (s->eapSuccess)
					btrans(s, Success);
				else
					logfatal(0, "back Receive 2: should not happen");
				break;
			default:
				logfatal(0, "back Receive: can't happen");
			}
		resetTimer(s->authWhile);
		s->eapNoResp = 0;
		break;
	case Success:
		// try to avoid race: first set vars, then unset s->eapSuccess
		s->suppSuccess = 1;
		s->keyRun=1;
		s->portValid = 1; // we should actually check this
		s->eapSuccess = 0;
		btrans(s, Idle);
		break;
	case Fail:
		s->suppFail = 1;
		s->eapFail = 0;
		btrans(s, Idle);
		break;
	case Timeout:
		s->suppTimeout = 1;
		btrans(s, Idle);
		break;
	}

	return s->backState;
}

static void
backproc(void *arg)
{
	PAEstate *s;

	s = arg;
	for(;;) {
		back(s);
	}
}

// ========== Supplicant PAE state machine

static void
waitUntilUserLoggedOn(PAEstate *s)
{
	s->userLogoff = 0;
}

static void
waitUntilPortEnabled(PAEstate *s)
{
	s->portEnabled = 1;
}

static void
acknowledgeStart(PAEstate *s)
{
	loglog("------ restarting ------");
	syslog(0, logname, "restarting");
	s->restartTime = nsec();
}


// build EAPOL-Start frame and transmit to Authenticator
static void
txStart(PAEstate *s)
{
	Packet *tx;

	// get fresh ap mac - we may have roamed
	if (apetheraddr(s->apmac, s->etherdir) < 0)
		logfatal(0, "could not read access point ether address from %s", s->etherdir);

	loglog("sending EAPOL Start frame to %E", s->apmac);

	tx = s->pktt;
	s->txEtherEap = tx;
	memset(s->txEtherEap->b, 0, Pktlen);

	tx->eapol->ver = EapolVersion;
	tx->eapol->tp = EapolTpStart;
	memset(tx->eapol->ln, 0, 2);
	memcpy(tx->ether->s, s->ourmac, 6);
	memcpy(tx->ether->d, s->apmac, 6);
	hnputs(tx->ether->t, ETEAPOL);
	tx->e = tx->eapol->data;

	txSuppRsp(s);
}

// build EAPOL-Logoff frame and transmit to Authenticator
static void
txLogoff(PAEstate *s)
{
	USED(s);
}

static void
ptrans(PAEstate *s, int new)
{
	loglog("pae trans: %s -> %s", (s->paeState>=0)?paenames[s->paeState]:"-", paenames[new]);
	s->paeState = new;
	s->paeTime = nsec();
}

static int
pae(PAEstate *s)
{
	int val, res;
	Packet *rx;
	int done;
	Alt a_c[] = {
	/*	 c				v		op   */
		{s->etherchan,	&rx,	CHANRCV},
		{s->timerchan,	nil,	CHANRCV},
		{s->statuschan,	nil,	CHANRCV},
		{nil,			nil,	CHANEND},
	};
	Alt a_h[] = {
	/*	 c				v		op   */
		{s->etherchan,	&rx,	CHANRCV},
		{s->timerchan,	nil,	CHANRCV},
		{nil,			nil,	CHANEND},
	};
	Alt a_a[] = {
	/*	 c				v		op   */
		{s->etherchan,	&rx,	CHANRCV},
		{s->portchan,	&val,	CHANRCV},
		{nil,			nil,	CHANEND},
	};

	// if (debug) print("_");
	//print("pae: %s\n", (s->paeState>=0)?paenames[s->paeState]:"-");

	if (s->paeState!=Logoff && (s->userLogoff && !s->logoffSent && s->portEnabled && !s->initialize))
		ptrans(s, Logoff);
	else if (s->paeState!=Disconnected && ((s->portControl==Auto && s->sPortMode!=s->portControl) || s->initialize || !s->portEnabled))
		ptrans(s, Disconnected);
	else if (s->paeState!=ForceAuth && (s->portControl==ForceAuthorized && s->sPortMode!=ForceAuthorized && s->portEnabled && !s->initialize))
		ptrans(s, ForceAuth);
	else if (s->paeState!=ForceUnauth && (s->portControl==ForceUnauthorized && s->sPortMode!=ForceUnauthorized && s->portEnabled && !s->initialize))
		ptrans(s, ForceUnauth);
	switch(s->paeState) {
	case Logoff:
		txLogoff(s);
		s->logoffSent = 1;
		s->suppPortStatus = Unauthorized;
		waitUntilUserLoggedOn(s); // s->userLogoff = 0
		ptrans(s, Disconnected);
		break;
	case Disconnected:
		s->sPortMode = Auto;
		s->startCount = 0;
		s->logoffSent = 0;
		s->suppPortStatus = Unauthorized;
		s->suppAbort = 1;
		send(s->backstart, nil);
		recv(s->backdone, nil);
		waitUntilPortEnabled(s); // s->portEnabled = 1
		ptrans(s, Connecting);
		break;
	case Connecting:
		s->startCount ++;
		s->eapolEap = 0;
		clearlog(getNotesbuf()); // when should we do this???
		txStart(s);
		startTimer(s->startWhen, s->startPeriod);
		done = 0;
		while(!done)
			switch(res = alt(a_c)) {
			case 0:	/* eap received */
				loglog("pae Connecting eap received");
				done = 1;
				s->rxEtherEap = rx;
				s->eapolEap = 1;
				ptrans(s, Restart);
				break;
			case 1:	/* timer tick event */
				// loglog("pae Connecting startWhen timer tick");
				tickTimer(s->startWhen);
				if (s->startWhen->counter == 0) {
					logall("startWhen timer expired");
					done = 1;
					if (s->startCount < s->maxStart)
						ptrans(s, Connecting);
					else if (s->startCount >= s->maxStart && s->portValid) {
						logall("startCount >= maxStart (==%d), assume auth-ed", s->maxStart);
						ptrans(s, Authenticated);
					} else if (s->startCount >= s->maxStart) {
						logall("startCount >= maxStart (==%d), but port not valid", s->maxStart);
						ptrans(s, Held);
					} else
						logfatal(0, "pae Connecting 0: should not happen");
				}
				break;
			case 2:	/* eapSuccess or eapFail */
				loglog("pae Connecting eapSuccess or eapFail");
				done = 1;
				if (s->eapSuccess || s->eapFail)
					ptrans(s, Authenticating);
				else
					logfatal(0, "pae Connecting 3: should not happen");
				break;
			default:
				logfatal(0, "pae Connecting can't happen:%d", res);
			}
		resetTimer(s->startWhen);
		break;
	case Authenticating:
		s->startCount = 0;
		s->suppSuccess = 0;
		s->suppFail = 0;
		s->suppTimeout = 0;
		s->keyRun = 0;
		s->keyDone = 0;
		send(s->backstart, nil);
		recv(s->backdone, nil);
		if (s->suppSuccess && s->portValid)
			ptrans(s, Authenticated);
		else if(s->suppSuccess)
			USED(s);	// ???
		else if (s->suppFail || (s->keyDone && !s->portValid))
			ptrans(s, Held);
		else if (s->suppTimeout)
			ptrans(s, Connecting);
		else
			logfatal(0, "pae Authenticating: should not happen");
		break;
	case Held:
		startTimer(s->heldWhile, s->heldPeriod);
		s->suppPortStatus = Unauthorized;
		done = 0;
		while(!done)
			switch(res = alt(a_h)) {
			case 0:	/* eap received */
				loglog("pae Held eap received");
				done = 1;
				s->rxEtherEap = rx;
				s->eapolEap = 1;
				ptrans(s, Restart);
				break;
			case 1:	/* timer expiration event */
				// loglog("pae Held timer tick");
				tickTimer(s->heldWhile);
				if (s->heldWhile->counter == 0) {
					logall("heldWhile timer expired");
					done = 1;
					ptrans(s, Connecting);
				}
				break;
			default:
				logfatal(0, "pae Held can't happen:%d", res);
			}
		resetTimer(s->heldWhile);
		break;
	case Authenticated:
		s->suppPortStatus = Authorized;
		switch(res = alt(a_a)) {
		case 0:	/* eap received */
			loglog("pae Authenticated eap received");
			if (s->portValid) {
				s->rxEtherEap = rx;
				s->eapolEap = 1;
				ptrans(s, Restart);
			}
			break;
		case 1:	/* port validity changed */
			loglog("pae Authenticated port validity changed");
			s->portValid = val;
			if (!s->portValid)
				ptrans(s, Disconnected);
			break;
		default:
			logfatal(0, "pae Authenticated can't happen:%d", res);
		}
		break;
	case Restart:
		acknowledgeStart(s);
		ptrans(s, Authenticating);
		break;
	case ForceAuth:
		s->suppPortStatus = Authorized;
		s->sPortMode = ForceAuthorized;
		break;
	case ForceUnauth:
		s->suppPortStatus = Unauthorized;
		s->sPortMode = ForceUnauthorized;
		// no check??
		txLogoff(s);
		s->logoffSent = 1;
		break;
	}
	//print("pae return: %s\n", paenames[s->paeState]);
	return s->paeState;
}

// ========== run state machines

static void
paeproc(void *arg)
{
	PAEstate *s;

	s = arg;
	for(;;) {
		pae(s);
	}
}


// ========== fs support

typedef struct timeInfo {
	int id;
	vlong time;
} timeInfo;

static int
cmptimenfo(void*a, void*b)
{
	timeInfo *ta, *tb;
	vlong td;

	ta = a ;
	tb = b;

	if (ta->time == 0 && tb->time > 0)
		return 1;
	if (ta->time > 0 && tb->time == 0)
		return -1;

	td = ta->time - tb->time;
	if (td > 0)
		return 1;
	if (td < 0)
		return -1;

	return ta->id - tb->id;
}

enum {
	InitTime = 0,
	StartTime,
	PaeTime,
	BackTime,
	eapidTime,
	verdictTime,
	ph1startTime,
	ph1doneTime,
	ph2startTime,
	ph2doneTime,
	KeyTime,
	NoteTime,
	NTime,
};

static char *timeNames[] = {
[InitTime]		"Init",
[StartTime]		"Start",
[PaeTime]		"Pae",
[BackTime]		"Back",
[eapidTime]		"eapid",
[verdictTime]		"verdict",
[ph1startTime]		"ph1start",
[ph1doneTime]		"ph1done",
[ph2startTime]		"ph2start",
[ph2doneTime]		"ph2done",
[KeyTime]		"Key",
[NoteTime]		"Note",
[NTime]		"",
};

static timeInfo t[NTime];

static timeInfo*
getPAETimes(PAEstate *s)
{
	t[InitTime].time = s->startTime;
	t[InitTime].id = InitTime;
	t[StartTime].time = s->restartTime;
	t[StartTime].id = StartTime;
	t[PaeTime].time = s->paeTime;
	t[PaeTime].id = PaeTime;
	t[BackTime].time = s->backTime;
	t[BackTime].id = BackTime;
	t[eapidTime].time = s->eapidTime;
	t[eapidTime].id = eapidTime;
	t[verdictTime].time = s->verdictTime;
	t[verdictTime].id = verdictTime;
	t[ph1startTime].time = s->phase[0].startTime;
	t[ph1startTime].id = ph1startTime;
	t[ph1doneTime].time = s->phase[0].doneTime;
	t[ph1doneTime].id = ph1doneTime;
	t[ph2startTime].time = s->phase[1].startTime;
	t[ph2startTime].id = ph2startTime;
	t[ph2doneTime].time = s->phase[1].doneTime;
	t[ph2doneTime].id = ph2doneTime;
	t[KeyTime].time = s->keyTime;
	t[KeyTime].id = KeyTime;
	t[NoteTime].time = s->noteTime;
	t[NoteTime].id = NoteTime;
	t[NTime].time = 0;
	t[NTime].id = NTime;

	qsort(t, NTime, sizeof(timeInfo), cmptimenfo);
	return t;
}

void
getPAEStatus(char *b, int n)
{
	PAEstate *s;
	timeInfo *t;
	int i;

	s = &theState;
	*b = '\0';
	seprint(b+strlen(b), b+n, "Verdict:\t%s\n",
		((s->eapSuccess || s->suppSuccess) ? "success" :
		 ((s->eapFail || s->suppFail) ? "fail" : "")));
	seprint(b+strlen(b), b+n, "PaeState: %s\n", paenames[s->paeState]);
	seprint(b+strlen(b), b+n, "BackState: %s\n", bnames[s->backState]);
	seprint(b+strlen(b), b+n, "EapId Prompt: %s\n", getstring(s->prompt));
	seprint(b+strlen(b), b+n, "EapId Options: %s\n", getstring(s->options));
	seprint(b+strlen(b), b+n, "ph1type: %s\n", getstring(s->phase[0].type));
	seprint(b+strlen(b), b+n, "ph2type: %s\n", getstring(s->phase[1].type));

	t = getPAETimes(s);
	for (i=0; i < NTime; i++)
		seprint(b+strlen(b), b+n, "%s:\t%s\n", timeNames[t[i].id], nsctime(t[i].time));
		
	seprint(b+strlen(b), b+n, "startCount:\t%d\n", s->startCount);
	seprint(b+strlen(b), b+n, "maxStart:\t%d\n", s->maxStart);
	seprint(b+strlen(b), b+n, "heldWhile:\t%d\n", timerVal(s->heldWhile));
	seprint(b+strlen(b), b+n, "heldPeriod:\t%d\n", s->heldPeriod);
	seprint(b+strlen(b), b+n, "startWhen:\t%d\n", timerVal(s->startWhen));
	seprint(b+strlen(b), b+n, "startPeriod:\t%d\n", s->startPeriod);
	seprint(b+strlen(b), b+n, "authWhile:\t%d\n", timerVal(s->authWhile));
	seprint(b+strlen(b), b+n, "authPeriod:\t%d\n", s->authPeriod);
}

long
getChangetime(int qid)
{
	PAEstate *s;

	s = &theState;
	switch(qid){
	case Qstatus:
		if (s->paeTime > s->backTime)
			return nsec2sec(s->paeTime);
		else
			return nsec2sec(s->backTime);
	case Qkeys:
		return nsec2sec(s->keyTime);
	case Qnote:
		return nsec2sec(s->noteTime);
	default:
		return 0;
	}
}

ReadBuf*
getKeysbuf(void)
{
	PAEstate *s;

	s = &theState;
	return &s->keysbuf;
}

ReadBuf*
getNotesbuf(void)
{
	PAEstate *s;

	s = &theState;
	return &s->notesbuf;
}

// phases are 1, 2, ...
// s->phase  indices are 0, 1, ...
void
markPhaseStart(int phase, char *type)
{
	PAEstate *s;

	if (phase <= 0 || phase > nelem(s->phase))
		return;

	s = &theState;
	free(s->phase[phase-1].type);
	s->phase[phase-1].type = strdup(type);
	s->phase[phase-1].startTime = nsec();
	logall("auth phase %d starts: %s", phase, type);
}

void
markPhaseDone(int phase, char *type)
{
	PAEstate *s;

	if (phase <= 0 || phase > nelem(s->phase))
		return;

	s = &theState;
	free(s->phase[phase-1].type);
	s->phase[phase-1].type = strdup(type);
	s->phase[phase-1].doneTime = nsec();
	logall("auth phase %d done: %s", phase, type);
}

void
markPhaseResult(int phase, char *type, int success)
{
	PAEstate *s;

	if (phase <= 0 || phase > nelem(s->phase))
		return;

	s = &theState;
	free(s->phase[phase-1].type);
	s->phase[phase-1].type = strdup(type);
	s->phase[phase-1].success = success;
	s->phase[phase-1].doneTime = nsec();
	logall("auth phase %d done: %s: %s", phase, type, success ? "success" : "fail");
}

// ========== main thing

static void*
newPacket(void)
{
	Packet *p;

	p = malloc(sizeof(Packet));
	if (p == nil)
		logfatal(1, "could not allocate Packet");
	memset(p, 0, sizeof(Packet));
	p->ether = (Ether*)p->b;
	p->eapol = (Eapol*)p->ether->data;
	p->eap = (Eap*)p->eapol->data;
	return p;
}

static void
init(PAEstate *s, Timers *t)
{
	int i;

	memset(s, 0, sizeof(PAEstate));

	s->heldPeriod = 60; //seconds
	s->startPeriod = 30; //seconds
	s->authPeriod = 30; //seconds

	s->maxStart = 3;

	s->heldWhile = addTimer(t, "heldWhile");
	s->startWhen = addTimer(t, "startWhen");
	s->authWhile = addTimer(t, "authWhile");

	s->paeState = Disconnected;
	s->backState = Initialize;
	s->startTime = nsec();
	s->paeTime = s->startTime;
	s->backTime = s->startTime;
//	s->noteTime = s->startTime;
//	s->keyTime = s->startTime;
//	s->restartTime = nsec();


	s->lastEapId = -1;

	for (i = 0; i < Npkt; i++)
		s->pktr[i] = newPacket();
	s->pktt = newPacket();

	s->portchan = chancreate(sizeof(int), 0);
	s->backstart = chancreate(sizeof(int), 0);
	s->backdone = chancreate(sizeof(int), 0);
	s->etherchan = chancreate(sizeof(Packet*), 0);
	s->statuschan = chancreate(sizeof(int), 0);
	s->timerchan = t->timerchan;
}

void
usage(void)
{
	fprint(2, "usage: 8021x [-d] [-D] [-T] [-m mtpt] [-t /sys/lib/tls/xxx] [-x /sys/lib/tls/xxx.exclude]\n");
	syslog(0, logname, "usage");
	threadexitsall("usage");
}

void
threadmain(int argc, char *argv[])
{
	char *thumbFile, *thumbFilex, *mtpt;
	PAEstate *s;
	Timers *t;

	mtpt = "/net";
	s = &theState;
	t= &TheTimers;
	fmtinstall('E', eipfmt);

	thumbFile = nil;
	thumbFilex = nil;
	ARGBEGIN{
	case 'd':
		debug++;
		break;
	case 'D':
		chatty9p++;
		break;
	case 'T':
		debugTLS++;
		break;
	case 'm':
		mtpt = ARGF();
		break;
	case 't':
		thumbFile = EARGF(usage());
		break;
	case 'x':
		thumbFilex = EARGF(usage());
		break;
	}ARGEND;

	logname = "8021x";
	loglog("====== starting =======");
	syslog(0, logname, "starting");
	
	if(mtpt == nil || argc > 0)
		usage();

	if(thumbFilex && !thumbFile)
		logfatal(1, "specifying -x without -t is useless");

	initTimers(t);
	init(s, t);
	initTTLS(thumbFile, thumbFilex, t);
	initFs();

	if(argc == 0)
		s->etherdir = "/net/ether0";
	else
		s->etherdir = argv[0];

	snprint(buf, Blen, "%s!0x888e", s->etherdir);
	s->etherfd = dial(buf, 0, 0, &s->ethercfd);
	if(s->etherfd < 0)
		logfatal(1, "could not dial %s: %r", buf);

	if (myetheraddr(s->ourmac, s->etherdir) < 0)
		logfatal(1,  "could not read own ether addres from %s", s->etherdir);

	upwd = auth_getuserpasswd(auth_getkey, "proto=pass service=8021x-pap");
	if (upwd) {
		myId = upwd->user;
		myPasswd = upwd->passwd;
	} else
		logfatal(1, "cannot get user/passwd");

	// we could try to threadcreate backproc and paeproc
	// (since they are coroutines),
	// such that they are in a single process,
	// as we had them before we turned this into an fs,
	// but then the current process does not exit
	// and things don't work.
	// this may just be because of my limited understanding.

	proccreate(tickproc, t, SMALLSTACK);

	proccreate(backproc, s, STACK);
	recv(s->backdone, nil);

	proccreate(etherproc, s, SMALLSTACK);

	s->portEnabled = 1;
	proccreate(paeproc, s, STACK);

	threadpostmountsrv(&fs, logname, mtpt, MAFTER);
	threadexits(0);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].