[eap] Add support for sending an EAP identity

Allow the ${netX/username} setting to be used to specify an EAP
identity to be returned in response to a Request-Identity, and provide
a mechanism for responding with a NAK to indicate which authentication
types we support.

If no identity is specified then fall back to the current behaviour of
not sending any Request-Identity response, so that switches will time
out and switch to MAC Authentication Bypass (MAB) if applicable.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/include/ipxe/eap.h b/src/include/ipxe/eap.h
index 818862a..bbae517 100644
--- a/src/include/ipxe/eap.h
+++ b/src/include/ipxe/eap.h
@@ -12,6 +12,7 @@
 #include <stdint.h>
 #include <ipxe/netdevice.h>
 #include <ipxe/timer.h>
+#include <ipxe/tables.h>
 
 /** EAP header */
 struct eap_header {
@@ -26,17 +27,28 @@
 /** EAP request */
 #define EAP_CODE_REQUEST 1
 
-/** EAP request */
-struct eap_request {
+/** EAP response */
+#define EAP_CODE_RESPONSE 2
+
+/** EAP request/response message */
+struct eap_message {
 	/** Header */
 	struct eap_header hdr;
 	/** Type */
 	uint8_t type;
+	/** Type data */
+	uint8_t data[0];
 } __attribute__ (( packed ));
 
+/** EAP "no available types" marker */
+#define EAP_TYPE_NONE 0
+
 /** EAP identity */
 #define EAP_TYPE_IDENTITY 1
 
+/** EAP NAK */
+#define EAP_TYPE_NAK 3
+
 /** EAP success */
 #define EAP_CODE_SUCCESS 3
 
@@ -47,8 +59,8 @@
 union eap_packet {
 	/** Header */
 	struct eap_header hdr;
-	/** Request */
-	struct eap_request req;
+	/** Request/response message */
+	struct eap_message msg;
 };
 
 /** EAP link block timeout
@@ -87,7 +99,11 @@
 	/** Network device */
 	struct net_device *netdev;
 	/** Flags */
-	unsigned int flags;
+	uint16_t flags;
+	/** ID for current request/response */
+	uint8_t id;
+	/** Type for current request/response */
+	uint8_t type;
 	/**
 	 * Transmit EAP response
 	 *
@@ -117,6 +133,28 @@
  */
 #define EAP_FL_PASSIVE 0x0002
 
+/** An EAP method */
+struct eap_method {
+	/** Type */
+	uint8_t type;
+	/**
+	 * Handle EAP request
+	 *
+	 * @v supplicant	EAP supplicant
+	 * @v req		Request type data
+	 * @v req_len		Length of request type data
+	 * @ret rc		Return status code
+	 */
+	int ( * rx ) ( struct eap_supplicant *supplicant,
+		       const void *req, size_t req_len );
+};
+
+/** EAP method table */
+#define EAP_METHODS __table ( struct eap_method, "eap_methods" )
+
+/** Declare an EAP method */
+#define __eap_method __table_entry ( EAP_METHODS, 01 )
+
 extern int eap_rx ( struct eap_supplicant *supplicant,
 		    const void *data, size_t len );
 
diff --git a/src/net/eap.c b/src/net/eap.c
index 8ba87e2..fe01f13 100644
--- a/src/net/eap.c
+++ b/src/net/eap.c
@@ -23,7 +23,10 @@
 
 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 
+#include <stdlib.h>
 #include <errno.h>
+#include <string.h>
+#include <byteswap.h>
 #include <ipxe/netdevice.h>
 #include <ipxe/eap.h>
 
@@ -34,59 +37,173 @@
  */
 
 /**
- * Handle EAP Request-Identity
+ * Transmit EAP response
+ *
+ * @v supplicant	EAP supplicant
+ * @v rsp		Response type data
+ * @v rsp_len		Length of response type data
+ * @ret rc		Return status code
+ */
+static int eap_tx_response ( struct eap_supplicant *supplicant,
+			     const void *rsp, size_t rsp_len ) {
+	struct net_device *netdev = supplicant->netdev;
+	struct eap_message *msg;
+	size_t len;
+	int rc;
+
+	/* Allocate and populate response */
+	len = ( sizeof ( *msg ) + rsp_len );
+	msg = malloc ( len );
+	if ( ! msg ) {
+		rc = -ENOMEM;
+		goto err_alloc;
+	}
+	msg->hdr.code = EAP_CODE_RESPONSE;
+	msg->hdr.id = supplicant->id;
+	msg->hdr.len = htons ( len );
+	msg->type = supplicant->type;
+	memcpy ( msg->data, rsp, rsp_len );
+
+	/* Transmit response */
+	if ( ( rc = supplicant->tx ( supplicant, msg, len ) ) != 0 ) {
+		DBGC ( netdev, "EAP %s could not transmit: %s\n",
+		       netdev->name, strerror ( rc ) );
+		goto err_tx;
+	}
+
+ err_tx:
+	free ( msg );
+ err_alloc:
+	return rc;
+}
+
+/**
+ * Transmit EAP NAK
  *
  * @v supplicant	EAP supplicant
  * @ret rc		Return status code
  */
-static int eap_rx_request_identity ( struct eap_supplicant *supplicant ) {
+static int eap_tx_nak ( struct eap_supplicant *supplicant ) {
+	unsigned int max = table_num_entries ( EAP_METHODS );
+	uint8_t methods[ max + 1 /* potential EAP_TYPE_NONE */ ];
+	unsigned int count = 0;
+	struct eap_method *method;
+
+	/* Populate methods list */
+	for_each_table_entry ( method, EAP_METHODS ) {
+		if ( method->type > EAP_TYPE_NAK )
+			methods[count++] = method->type;
+	}
+	if ( ! count )
+		methods[count++] = EAP_TYPE_NONE;
+	assert ( count <= max );
+
+	/* Transmit response */
+	supplicant->type = EAP_TYPE_NAK;
+	return eap_tx_response ( supplicant, methods, count );
+}
+
+/**
+ * Handle EAP Request-Identity
+ *
+ * @v supplicant	EAP supplicant
+ * @v req		Request type data
+ * @v req_len		Length of request type data
+ * @ret rc		Return status code
+ */
+static int eap_rx_identity ( struct eap_supplicant *supplicant,
+			     const void *req, size_t req_len ) {
 	struct net_device *netdev = supplicant->netdev;
+	void *rsp;
+	int rsp_len;
+	int rc;
 
 	/* Treat Request-Identity as blocking the link */
 	DBGC ( netdev, "EAP %s Request-Identity blocking link\n",
 	       netdev->name );
+	DBGC_HDA ( netdev, 0, req, req_len );
 	netdev_link_block ( netdev, EAP_BLOCK_TIMEOUT );
 
 	/* Mark EAP as in progress */
 	supplicant->flags |= EAP_FL_ONGOING;
 
-	/* We have no identity to offer, so wait until the switch
-	 * times out and switches to MAC Authentication Bypass (MAB).
-	 */
-	supplicant->flags |= EAP_FL_PASSIVE;
+	/* Construct response, if applicable */
+	rsp_len = fetch_raw_setting_copy ( netdev_settings ( netdev ),
+					   &username_setting, &rsp );
+	if ( rsp_len < 0 ) {
+		/* We have no identity to offer, so wait until the
+		 * switch times out and switches to MAC Authentication
+		 * Bypass (MAB).
+		 */
+		DBGC2 ( netdev, "EAP %s has no identity\n", netdev->name );
+		supplicant->flags |= EAP_FL_PASSIVE;
+		rc = 0;
+		goto no_response;
+	}
 
-	return 0;
+	/* Transmit response */
+	if ( ( rc = eap_tx_response ( supplicant, rsp, rsp_len ) ) != 0 )
+		goto err_tx;
+
+ err_tx:
+	free ( rsp );
+ no_response:
+	return rc;
 }
 
+/** EAP Request-Identity method */
+struct eap_method eap_identity_method __eap_method = {
+	.type = EAP_TYPE_IDENTITY,
+	.rx = eap_rx_identity,
+};
+
 /**
  * Handle EAP Request
  *
  * @v supplicant	EAP supplicant
- * @v req		EAP request
+ * @v msg		EAP request
  * @v len		Length of EAP request
  * @ret rc		Return status code
  */
 static int eap_rx_request ( struct eap_supplicant *supplicant,
-			    const struct eap_request *req, size_t len ) {
+			    const struct eap_message *msg, size_t len ) {
 	struct net_device *netdev = supplicant->netdev;
+	struct eap_method *method;
+	const void *req;
+	size_t req_len;
 
-	/* Sanity check */
-	if ( len < sizeof ( *req ) ) {
+	/* Sanity checks */
+	if ( len < sizeof ( *msg ) ) {
 		DBGC ( netdev, "EAP %s underlength request:\n", netdev->name );
-		DBGC_HDA ( netdev, 0, req, len );
+		DBGC_HDA ( netdev, 0, msg, len );
 		return -EINVAL;
 	}
+	if ( len < ntohs ( msg->hdr.len ) ) {
+		DBGC ( netdev, "EAP %s truncated request:\n", netdev->name );
+		DBGC_HDA ( netdev, 0, msg, len );
+		return -EINVAL;
+	}
+	req = msg->data;
+	req_len = ( ntohs ( msg->hdr.len ) - sizeof ( *msg ) );
+
+	/* Record request details */
+	supplicant->id = msg->hdr.id;
+	supplicant->type = msg->type;
 
 	/* Handle according to type */
-	switch ( req->type ) {
-	case EAP_TYPE_IDENTITY:
-		return eap_rx_request_identity ( supplicant );
-	default:
-		DBGC ( netdev, "EAP %s requested type %d unknown:\n",
-		       netdev->name, req->type );
-		DBGC_HDA ( netdev, 0, req, len );
-		return -ENOTSUP;
+	for_each_table_entry ( method, EAP_METHODS ) {
+		if ( msg->type == method->type )
+			return method->rx ( supplicant, req, req_len );
 	}
+	DBGC ( netdev, "EAP %s requested type %d unknown:\n",
+	       netdev->name, msg->type );
+	DBGC_HDA ( netdev, 0, msg, len );
+
+	/* Send NAK if applicable */
+	if ( msg->type > EAP_TYPE_NAK )
+		return eap_tx_nak ( supplicant );
+
+	return -ENOTSUP;
 }
 
 /**
@@ -148,7 +265,7 @@
 	/* Handle according to code */
 	switch ( eap->hdr.code ) {
 	case EAP_CODE_REQUEST:
-		return eap_rx_request ( supplicant, &eap->req, len );
+		return eap_rx_request ( supplicant, &eap->msg, len );
 	case EAP_CODE_RESPONSE:
 		DBGC2 ( netdev, "EAP %s ignoring response\n", netdev->name );
 		return 0;