[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;