Plan 9 from Bell Labs’s /usr/web/sources/contrib/axel/8021x/v04/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 <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)
	int authWhile;
} backendstate;

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)
	int heldWhile;
	int 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];

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

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

// other

static char *mydefId="";		// hard coded defaults?
static char *mydefPasswd="";	// hard coded defaults?
static UserPasswd*upwd;

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

static char errbuf[Blen];

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

static void
tick(PAEstate *s)
{
	if (s->authWhile >= 0)
		s->authWhile--;
	if (s->heldWhile >= 0)
		s->heldWhile--;
	if (s->startWhen >= 0)
		s->startWhen--;
}

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

	s = arg;
	for(;;) {
		recv(s->timerstart, nil);
		for (;;) {
			if (s->authWhile == -1 && s->heldWhile == -1 && s->startWhen == -1)
				break;
			sleep(100);
			if (s->authWhile == -1 && s->heldWhile == -1 && s->startWhen == -1)
				break;
			tick(s);
			if (s->authWhile == 0 || s->heldWhile == 0 || s->startWhen == 0) {
				send(s->timerchan, nil);
				break;
			}
		}
	}
}

static char*
timerName(PAEstate *s, int *p)
{
	if (p == &s->authWhile)
		return "authWhile";
	if (p == &s->heldWhile)
		return "heldWhile";
	if (p == &s->startWhen)
		return "startWhen";
	return "unknown";
}
static void
startTimer(PAEstate *s, int *p, int val)
{
	syslog(0, logname, "startTimer %s to %d", timerName(s, p), val);
	if (s->authWhile >= 0)
		syslog(0, logname, "startTimer oops: %s runs, val=%d", timerName(s, &s->authWhile), s->authWhile);
	if (s->heldWhile >= 0)
		syslog(0, logname, "startTimer oops: %s runs, val=%d", timerName(s, &s->heldWhile), s->heldWhile);
	if (s->startWhen >= 0)
		syslog(0, logname, "startTimer oops: %s runs, val=%d", timerName(s, &s->startWhen), s->startWhen);
	*p = val * 10;
	if (nbsend(s->timerstart, nil) == 0)
		syslog(0, logname, "startTimer oops: could not timerstart");
}

static void
resetTimer(PAEstate *s, int *p)
{
	syslog(0, logname, "resetTimer %s (val was %d)", timerName(s, p), *p);
	*p = -1;
	syslog(0, logname, "\tresetTimer %s (val is %d)", timerName(s, p), *p);
}

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

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

	s = arg;
	for(;;) {
		syslog(0, logname, "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->n != 0)
		//	sysfatal("assertion failed: rx->n != 0 (rx->n == %d)\n", rx->n);

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

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

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

// (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);
}
static void
getSuppRsp(PAEstate *s)
{
	// handle rxEtherEap
	// build txEtherEap
	Packet *rx, *tx;
	uchar *p, *beyond;
	char *ident, prompt[Pktlen], options[Pktlen];
	int len;
	int tlssucces, tlsfailed;

	syslog(0, logname, "getSuppRsp %p", s->rxEtherEap);

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

	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?
			memset(prompt, 0, sizeof(prompt));
			memset(options, 0, sizeof(options));
			beyond = rx->eapol->data + nhgets(rx->eap->ln);
			p = &rx->eap->data[1];
			while (*p != '\0' && p < beyond)
				p++;
			if (*p == '\0' && p < beyond) {
				memcpy(prompt, &rx->eap->data[1], p - &rx->eap->data[1]);
				p++;
				if (p < beyond)
					memcpy(options, p, beyond - p);
			}

			// 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, prompt=\"%s\" options=\"%s\"", prompt, options);
			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(&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)
				syslog(0, logname, "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:
		if (debug) print("getSuppRsp unexpected eap type %d\n", 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->n = (tx->eapol->data - tx->b) + nhgets(tx->eap->ln);
	}

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

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

	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;

	l = (s->txEtherEap->n > ETHERMINTU) ? s->txEtherEap->n : ETHERMINTU;
	if (debug) print("txSuppRsp writing to ether l=%d L=%d\n", l, s->txEtherEap->n);
	n = write(s->etherfd, s->txEtherEap->b, l);
	if (n != l)
		print("txSuppRsp: written %d of %d:%r", n, l);
	syslog(0, logname, "txSuppRsp: written %d", n);
}

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

static int
back(PAEstate *s)
{
	Packet *rx;
	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 {
			syslog(0, logname, "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 {
			fprint(2, "back Idle: should not happen\n");
			syslog(0, logname, "back Idle: should not happen");
			threadexitsall("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 {
			fprint(2, "back Request: should not happen\n");
			syslog(0, logname, "back Request: should not happen");
			threadexitsall("back Request: should not happen");
		}
		break;
	case Response:
		txSuppRsp(s);
		s->eapResp = 0;
		btrans(s, Receive);
		break;
	case Receive:
		startTimer(s, &s->authWhile, s->authPeriod);
		s->eapolEap = 0;
		switch(alt(a)) {
		case 0:	/* eap received */
			syslog(0, logname, "back Receive eap received");
			s->rxEtherEap = rx;
			s->eapolEap = 1;
			btrans(s, Request);
			break;
		case 1:	/* timer expiration event */
			syslog(0, logname, "back Receive timer expired");
			if (s->authWhile == 0)
				btrans(s, Timeout);
			else {
				fprint(2, "back Receive 1: should not happen\n");
				syslog(0, logname, "back Receive 1: should not happen");
				threadexitsall("back Receive 1: should not happen");
			}
			break;
		case 2:	/* eapSuccess or eapFail */
			syslog(0, logname, "back Receive eapSuccess or eapFail");
			if (s->eapFail)
				btrans(s, Fail);
			else if (s->eapSuccess)
				btrans(s, Success);
			else {
				fprint(2, "back Receive 2: should not happen\n");
				syslog(0, logname, "back Receive 2: should not happen");
				threadexitsall("back Receive 2: should not happen");
			}
			break;
		default:
			fprint(2, "back Receive: can't happen\n");
			syslog(0, logname, "back Receive: can't happen");
			threadexitsall("back Receive: can't happen");
		}
		resetTimer(s, &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)
{
	USED(s);
	syslog(0, logname, "------ restarting ------");
}


// 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) {
		snprint(errbuf, sizeof(errbuf), "could not read access point ether address from %s", s->etherdir);
		syslog(0, logname, "%s", errbuf);
		fprint(2, "%s\n", errbuf);
		threadexitsall(errbuf);
	}

	syslog(0, logname, "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->n = tx->eapol->data - tx->b;

	txSuppRsp(s);
}

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

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

static int
pae(PAEstate *s)
{
	int val, res;
	Packet *rx;
	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:
		startTimer(s, &s->startWhen, s->startPeriod);
		s->startCount ++;
		s->eapolEap = 0;
		txStart(s);
		switch(res = alt(a_c)) {
		case 0:	/* eap received */
			syslog(0, logname, "pae Connecting eap received");
			s->rxEtherEap = rx;
			s->eapolEap = 1;
			ptrans(s, Restart);
			break;
		case 1:	/* timer expiration event */
			syslog(0, logname, "pae Connecting timer expired");
			if (s->startWhen != 0) {
				fprint(2, "pae Connecting 1: should not happen\n");
				syslog(0, logname, "pae Connecting 1: should not happen");
				threadexitsall("pae Connecting 1: should not happen");
			} else if (s->startCount < s->maxStart)
				ptrans(s, Connecting);
			else if (s->startCount >= s->maxStart && s->portValid)
				ptrans(s, Authenticated);
			else if (s->startCount >= s->maxStart)
				ptrans(s, Held);
			else {
				fprint(2, "pae Connecting 0: should not happen\n");
				syslog(0, logname, "pae Connecting 0: should not happen");
				threadexitsall("pae Connecting 0: should not happen");
			}
			break;
		case 2:	/* eapSuccess or eapFail */
			syslog(0, logname, "pae Connecting eapSuccess or eapFail");
			if (s->eapSuccess || s->eapFail)
				ptrans(s, Authenticating);
			else {
				fprint(2, "pae Connecting 3: should not happen\n");
				syslog(0, logname, "pae Connecting 3: should not happen");
				threadexitsall("pae Connecting 3: should not happen");
			}
			break;
		default:
			fprint(2, "pae Connecting can't happen:%d\n", res);
			syslog(0, logname, "pae Connecting can't happen:%d", res);
			threadexitsall("can't happen");
		}
		resetTimer(s, &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 {
			fprint(2, "pae Authenticating: should not happen\n");
			syslog(0, logname, "pae Authenticating: should not happen");
			threadexitsall("pae Authenticating: should not happen");
		}
		break;
	case Held:
		startTimer(s, &s->heldWhile, s->heldPeriod);
		s->suppPortStatus = Unauthorized;
		switch(res = alt(a_h)) {
		case 0:	/* eap received */
			syslog(0, logname, "pae Held eap received");
			s->rxEtherEap = rx;
			s->eapolEap = 1;
			ptrans(s, Restart);
			break;
		case 1:	/* timer expiration event */
			syslog(0, logname, "pae Held timer expired");
			if (s->heldWhile != 0) {
				fprint(2, "pae Held: should not happen\n");
				syslog(0, logname, "pae Held: should not happen");
				threadexitsall("pae Held: should not happen");
			} else
				ptrans(s, Connecting);
			break;
		default:
			fprint(2, "pae Held can't happen:%d\n", res);
			syslog(0, logname, "pae Held can't happen:%d", res);
			threadexitsall("pae Held can't happen");
		}
		resetTimer(s, &s->heldWhile);
		break;
	case Authenticated:
		s->suppPortStatus = Authorized;
		switch(res = alt(a_a)) {
		case 0:	/* eap received */
			syslog(0, logname, "pae Authenticated eap received");
			if (s->portValid) {
				s->rxEtherEap = rx;
				s->eapolEap = 1;
				ptrans(s, Restart);
			}
			break;
		case 1:	/* port validity changed */
			syslog(0, logname, "pae Authenticated port validity changed");
			s->portValid = val;
			if (!s->portValid)
				ptrans(s, Disconnected);
			break;
		default:
			fprint(2, "pae Authenticated can't happen:%d\n", res);
			syslog(0, logname, "pae Authenticated can't happen:%d", res);
			threadexitsall("pae Authenticated can't happen");
		}
		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
update(PAEstate *s)
{
	int ps;

	ps = -2;
	while(ps != s->paeState)
		ps = pae(s);
}

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

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

	p = malloc(sizeof(Packet));
	if (p == nil)
		sysfatal("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)
{
	int i;

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

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

	s->maxStart = 3;

	s->heldWhile = -1;
	s->startWhen = -1;
	s->authWhile = -1;

	s->paeState = Disconnected;
	s->backState = Initialize;

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

void
usage(void)
{
	fprint(2, "usage: 8021x [-d] [-D] [-t /sys/lib/tls/xxx] [-x /sys/lib/tls/xxx.exclude]\n");
	exits("usage");
}

void
threadmain(int argc, char *argv[])
{
	char *thumbFile, *thumbFilex;
	PAEstate theState, *s;
	char buf[Blen];

	s = &theState;
	fmtinstall('E', eipfmt);

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

	init(s);

	if(thumbFilex && !thumbFile) {
		snprint(errbuf, sizeof(errbuf), "specifying -x without -t is useless");
		syslog(0, logname, "%s", errbuf);
		fprint(2, "%s\n", errbuf);
		threadexitsall(errbuf);
	}

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

	logname = "8021x";
	syslog(0, logname, "====== starting =======");
	
	snprint(buf, Blen, "%s!0x888e", s->etherdir);
	s->etherfd = dial(buf, 0, 0, &s->ethercfd);
	if(s->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(s->ourmac, s->etherdir) < 0) {
		snprint(errbuf, sizeof(errbuf), "could not read own ether addres from %s", s->etherdir);
		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 {
		sysfatal("cannot get user/passwd");
	}

	s->paeState = Disconnected;
	s->backState = Initialize;

	// must make all channels before executing first alt
	s->portchan = chancreate(sizeof(int), 0);
	s->timerchan = chancreate(sizeof(int), 0);
	s->timerstart = 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);

	proccreate(clockproc, s, STACK);

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

	proccreate(etherproc, s, STACK);


	s->portEnabled = 1;

	initTTLS(thumbFile, thumbFilex);

	for(;;) {
		update(s);
	}
}

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].