TNC: Added preliminary TNC implementation for hostapd
authorJouni Malinen <j@w1.fi>
Sun, 9 Mar 2008 08:37:18 +0000 (10:37 +0200)
committerJouni Malinen <j@w1.fi>
Sun, 9 Mar 2008 08:37:18 +0000 (10:37 +0200)
This adds EAP-TNC method and TNCS (IF-IMV and IF-TNCCS) functionality.
There is no integration with EAP-TTLS and EAP-FAST at this point, so this
version is not yet suitable for real use (i.e., EAP-TNC can only be tested
outside a tunnel which is not an allowed configuration for deployment).
However, the basic TNCS functionality is more or less complete and this
version seems to interoperate with wpa_supplicant.

hostapd/Makefile
hostapd/config.c
hostapd/config.h
hostapd/hostapd.c
src/eap_server/eap_methods.c
src/eap_server/eap_tnc.c [new file with mode: 0644]
src/eap_server/tncs.c [new file with mode: 0644]
src/eap_server/tncs.h [new file with mode: 0644]

index c0b68e0..343c8db 100644 (file)
@@ -258,6 +258,13 @@ OBJS += ../src/eap_common/eap_ikev2_common.o ../src/eap_common/ikev2_common.o
 NEED_DH_GROUPS=y
 endif
 
+ifdef CONFIG_EAP_TNC
+CFLAGS += -DEAP_TNC
+OBJS += ../src/eap_server/eap_tnc.o
+OBJS += ../src/eap_server/tncs.o
+NEED_BASE64=y
+endif
+
 # Basic EAP functionality is needed for EAPOL
 OBJS += ../src/eap_server/eap.o
 OBJS += ../src/eap_common/eap_common.o
index 57fbb75..725e7ed 100644 (file)
@@ -1474,6 +1474,10 @@ struct hostapd_config * hostapd_config_read(const char *fname)
                } else if (os_strcmp(buf, "eap_sim_aka_result_ind") == 0) {
                        bss->eap_sim_aka_result_ind = atoi(pos);
 #endif /* EAP_SIM */
+#ifdef EAP_TNC
+               } else if (os_strcmp(buf, "tnc") == 0) {
+                       bss->tnc = atoi(pos);
+#endif /* EAP_TNC */
 #endif /* EAP_SERVER */
                } else if (os_strcmp(buf, "eap_message") == 0) {
                        char *term;
index 4a7b24b..0c1a56b 100644 (file)
@@ -242,6 +242,7 @@ struct hostapd_bss_config {
        u8 *pac_opaque_encr_key;
        char *eap_fast_a_id;
        int eap_sim_aka_result_ind;
+       int tnc;
 
        char *radius_server_clients;
        int radius_server_auth_port;
index 1e6a7bb..e8e8b8a 100644 (file)
@@ -41,6 +41,7 @@
 #include "tls.h"
 #include "eap_server/eap_sim_db.h"
 #include "eap_server/eap.h"
+#include "eap_server/tncs.h"
 #include "version.h"
 #include "l2_packet/l2_packet.h"
 
@@ -1854,7 +1855,7 @@ int main(int argc, char *argv[])
        struct hapd_interfaces interfaces;
        int ret = 1, k;
        size_t i, j;
-       int c, debug = 0, daemonize = 0;
+       int c, debug = 0, daemonize = 0, tnc = 0;
        const char *pid_file = NULL;
 
        hostapd_logger_register_cb(hostapd_logger_cb);
@@ -1940,7 +1941,19 @@ int main(int argc, char *argv[])
                                                    setup_interface_done);
                if (ret)
                        goto out;
+
+               for (k = 0; k < (int) interfaces.iface[i]->num_bss; k++) {
+                       if (interfaces.iface[i]->bss[0]->conf->tnc)
+                               tnc++;
+               }
+       }
+
+#ifdef EAP_TNC
+       if (tnc && tncs_global_init() < 0) {
+               wpa_printf(MSG_ERROR, "Failed to initialize TNCS");
+               goto out;
        }
+#endif /* EAP_TNC */
 
        if (daemonize && os_daemonize(pid_file)) {
                perror("daemon");
@@ -1986,6 +1999,10 @@ int main(int argc, char *argv[])
        }
        os_free(interfaces.iface);
 
+#ifdef EAP_TNC
+       tncs_global_deinit();
+#endif /* EAP_TNC */
+
        eloop_destroy();
 
 #ifndef CONFIG_NATIVE_WINDOWS
index 4cb4532..dc47e00 100644 (file)
@@ -261,6 +261,13 @@ int eap_server_register_methods(void)
        }
 #endif /* EAP_IKEV2 */
 
+#ifdef EAP_TNC
+       if (ret == 0) {
+               int eap_server_tnc_register(void);
+               ret = eap_server_tnc_register();
+       }
+#endif /* EAP_TNC */
+
        return ret;
 }
 
diff --git a/src/eap_server/eap_tnc.c b/src/eap_server/eap_tnc.c
new file mode 100644 (file)
index 0000000..457c8d3
--- /dev/null
@@ -0,0 +1,301 @@
+/*
+ * EAP server method: EAP-TNC (Trusted Network Connect)
+ * Copyright (c) 2007-2008, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "base64.h"
+#include "eap_i.h"
+#include "tncs.h"
+
+
+struct eap_tnc_data {
+       enum { START, CONTINUE, RECOMMENDATION, DONE, FAIL } state;
+       enum { ALLOW, ISOLATE, NO_ACCESS, NO_RECOMMENDATION } recommendation;
+       struct tncs_data *tncs;
+};
+
+
+/* EAP-TNC Flags */
+#define EAP_TNC_FLAGS_LENGTH_INCLUDED 0x80
+#define EAP_TNC_FLAGS_MORE_FRAGMENTS 0x40
+#define EAP_TNC_FLAGS_START 0x20
+#define EAP_TNC_VERSION_MASK 0x07
+
+#define EAP_TNC_VERSION 1
+
+
+static void * eap_tnc_init(struct eap_sm *sm)
+{
+       struct eap_tnc_data *data;
+
+       data = os_zalloc(sizeof(*data));
+       if (data == NULL)
+               return NULL;
+       data->state = START;
+       data->tncs = tncs_init();
+       if (data->tncs == NULL) {
+               os_free(data);
+               return NULL;
+       }
+
+       return data;
+}
+
+
+static void eap_tnc_reset(struct eap_sm *sm, void *priv)
+{
+       struct eap_tnc_data *data = priv;
+       tncs_deinit(data->tncs);
+       os_free(data);
+}
+
+
+static struct wpabuf * eap_tnc_build_start(struct eap_sm *sm,
+                                          struct eap_tnc_data *data, u8 id)
+{
+       struct wpabuf *req;
+
+       req = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_TNC, 1, EAP_CODE_REQUEST,
+                           id);
+       if (req == NULL) {
+               wpa_printf(MSG_ERROR, "EAP-TNC: Failed to allocate memory for "
+                          "request");
+               data->state = FAIL;
+               return NULL;
+       }
+
+       wpabuf_put_u8(req, EAP_TNC_FLAGS_START | EAP_TNC_VERSION);
+
+       data->state = CONTINUE;
+
+       return req;
+}
+
+
+static struct wpabuf * eap_tnc_build(struct eap_sm *sm,
+                                    struct eap_tnc_data *data, u8 id)
+{
+       struct wpabuf *req;
+       u8 *rpos, *rpos1, *start;
+       size_t rlen;
+       char *start_buf, *end_buf;
+       size_t start_len, end_len;
+       size_t imv_len;
+
+       imv_len = tncs_total_send_len(data->tncs);
+
+       start_buf = tncs_if_tnccs_start(data->tncs);
+       if (start_buf == NULL)
+               return NULL;
+       start_len = os_strlen(start_buf);
+       end_buf = tncs_if_tnccs_end();
+       if (end_buf == NULL) {
+               os_free(start_buf);
+               return NULL;
+       }
+       end_len = os_strlen(end_buf);
+
+       rlen = 1 + start_len + imv_len + end_len;
+       req = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_TNC, rlen,
+                           EAP_CODE_REQUEST, id);
+       if (req == NULL) {
+               os_free(start_buf);
+               os_free(end_buf);
+               return NULL;
+       }
+
+       start = wpabuf_put(req, 0);
+       wpabuf_put_u8(req, EAP_TNC_VERSION);
+       wpabuf_put_data(req, start_buf, start_len);
+       os_free(start_buf);
+
+       rpos1 = wpabuf_put(req, 0);
+       rpos = tncs_copy_send_buf(data->tncs, rpos1);
+       wpabuf_put(req, rpos - rpos1);
+
+       wpabuf_put_data(req, end_buf, end_len);
+       os_free(end_buf);
+
+       wpa_hexdump_ascii(MSG_MSGDUMP, "EAP-TNC: Request", start, rlen);
+
+       return req;
+}
+
+
+static struct wpabuf * eap_tnc_build_recommendation(struct eap_sm *sm,
+                                                   struct eap_tnc_data *data,
+                                                   u8 id)
+{
+       switch (data->recommendation) {
+       case ALLOW:
+               data->state = DONE;
+               break;
+       case ISOLATE:
+               data->state = FAIL;
+               /* TODO: support assignment to a different VLAN */
+               break;
+       case NO_ACCESS:
+               data->state = FAIL;
+               break;
+       case NO_RECOMMENDATION:
+               data->state = DONE;
+               break;
+       default:
+               wpa_printf(MSG_DEBUG, "EAP-TNC: Unknown recommendation");
+               return NULL;
+       }
+
+       return eap_tnc_build(sm, data, id);
+}
+
+
+static struct wpabuf * eap_tnc_buildReq(struct eap_sm *sm, void *priv, u8 id)
+{
+       struct eap_tnc_data *data = priv;
+
+       switch (data->state) {
+       case START:
+               tncs_init_connection(data->tncs);
+               return eap_tnc_build_start(sm, data, id);
+       case CONTINUE:
+               return eap_tnc_build(sm, data, id);
+       case RECOMMENDATION:
+               return eap_tnc_build_recommendation(sm, data, id);
+       case DONE:
+       case FAIL:
+               return NULL;
+       }
+
+       return NULL;
+}
+
+
+static Boolean eap_tnc_check(struct eap_sm *sm, void *priv,
+                            struct wpabuf *respData)
+{
+       const u8 *pos;
+       size_t len;
+
+       pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_TNC, respData,
+                              &len);
+       if (pos == NULL || len == 0) {
+               wpa_printf(MSG_INFO, "EAP-TNC: Invalid frame");
+               return TRUE;
+       }
+
+       if ((*pos & EAP_TNC_VERSION_MASK) != EAP_TNC_VERSION) {
+               wpa_printf(MSG_DEBUG, "EAP-TNC: Unsupported version %d",
+                          *pos & EAP_TNC_VERSION_MASK);
+               return TRUE;
+       }
+
+       if (*pos & EAP_TNC_FLAGS_START) {
+               wpa_printf(MSG_DEBUG, "EAP-TNC: Peer used Start flag");
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+
+static void eap_tnc_process(struct eap_sm *sm, void *priv,
+                           struct wpabuf *respData)
+{
+       struct eap_tnc_data *data = priv;
+       const u8 *pos;
+       size_t len;
+       enum tncs_process_res res;
+
+       pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_TNC, respData, &len);
+       if (pos == NULL || len == 0)
+               return; /* Should not happen; message already verified */
+
+       wpa_hexdump_ascii(MSG_MSGDUMP, "EAP-TNC: Received payload", pos, len);
+
+       if (len == 1 && (data->state == DONE || data->state == FAIL)) {
+               wpa_printf(MSG_DEBUG, "EAP-TNC: Peer acknowledged the last "
+                          "message");
+               return;
+       }
+
+       res = tncs_process_if_tnccs(data->tncs, pos + 1, len - 1);
+       switch (res) {
+       case TNCCS_RECOMMENDATION_ALLOW:
+               wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS allowed access");
+               data->state = RECOMMENDATION;
+               data->recommendation = ALLOW;
+               break;
+       case TNCCS_RECOMMENDATION_NO_RECOMMENDATION:
+               wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS has no recommendation");
+               data->state = RECOMMENDATION;
+               data->recommendation = NO_RECOMMENDATION;
+               break;
+       case TNCCS_RECOMMENDATION_ISOLATE:
+               wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS requested isolation");
+               data->state = RECOMMENDATION;
+               data->recommendation = ISOLATE;
+               break;
+       case TNCCS_RECOMMENDATION_NO_ACCESS:
+               wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS rejected access");
+               data->state = RECOMMENDATION;
+               data->recommendation = NO_ACCESS;
+               break;
+       case TNCCS_PROCESS_ERROR:
+               wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS processing error");
+               data->state = FAIL;
+               break;
+       default:
+               break;
+       }
+}
+
+
+static Boolean eap_tnc_isDone(struct eap_sm *sm, void *priv)
+{
+       struct eap_tnc_data *data = priv;
+       return data->state == DONE;
+}
+
+
+static Boolean eap_tnc_isSuccess(struct eap_sm *sm, void *priv)
+{
+       struct eap_tnc_data *data = priv;
+       return data->state == DONE;
+}
+
+
+int eap_server_tnc_register(void)
+{
+       struct eap_method *eap;
+       int ret;
+
+       eap = eap_server_method_alloc(EAP_SERVER_METHOD_INTERFACE_VERSION,
+                                     EAP_VENDOR_IETF, EAP_TYPE_TNC, "TNC");
+       if (eap == NULL)
+               return -1;
+
+       eap->init = eap_tnc_init;
+       eap->reset = eap_tnc_reset;
+       eap->buildReq = eap_tnc_buildReq;
+       eap->check = eap_tnc_check;
+       eap->process = eap_tnc_process;
+       eap->isDone = eap_tnc_isDone;
+       eap->isSuccess = eap_tnc_isSuccess;
+
+       ret = eap_server_method_register(eap);
+       if (ret)
+               eap_server_method_free(eap);
+       return ret;
+}
diff --git a/src/eap_server/tncs.c b/src/eap_server/tncs.c
new file mode 100644 (file)
index 0000000..ace1a49
--- /dev/null
@@ -0,0 +1,1232 @@
+/*
+ * EAP-TNC - TNCS (IF-IMV and IF-TNCCS)
+ * Copyright (c) 2007-2008, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+#include <dlfcn.h>
+
+#include "common.h"
+#include "base64.h"
+#include "tncs.h"
+
+
+/* TODO: TNCS must be thread-safe; review the code and add locking etc. if
+ * needed.. */
+
+#define TNC_CONFIG_FILE "/etc/tnc_config"
+#define IF_TNCCS_START \
+"<?xml version=\"1.0\"?>\n" \
+"<TNCCS-Batch BatchId=\"%d\" Recipient=\"TNCS\" " \
+"xmlns=\"http://www.trustedcomputinggroup.org/IWG/TNC/1_0/IF_TNCCS#\" " \
+"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " \
+"xsi:schemaLocation=\"http://www.trustedcomputinggroup.org/IWG/TNC/1_0/" \
+"IF_TNCCS#https://www.trustedcomputinggroup.org/XML/SCHEMA/TNCCS_1.0.xsd\">\n"
+#define IF_TNCCS_END "\n</TNCCS-Batch>"
+
+/* TNC IF-IMV */
+
+typedef unsigned long TNC_UInt32;
+typedef unsigned char *TNC_BufferReference;
+
+typedef TNC_UInt32 TNC_IMVID;
+typedef TNC_UInt32 TNC_ConnectionID;
+typedef TNC_UInt32 TNC_ConnectionState;
+typedef TNC_UInt32 TNC_RetryReason;
+typedef TNC_UInt32 TNC_IMV_Action_Recommendation;
+typedef TNC_UInt32 TNC_IMV_Evaluation_Result;
+typedef TNC_UInt32 TNC_MessageType;
+typedef TNC_MessageType *TNC_MessageTypeList;
+typedef TNC_UInt32 TNC_VendorID;
+typedef TNC_UInt32 TNC_Subtype;
+typedef TNC_UInt32 TNC_Version;
+typedef TNC_UInt32 TNC_Result;
+typedef TNC_UInt32 TNC_AttributeID;
+
+typedef TNC_Result (*TNC_TNCS_BindFunctionPointer)(
+       TNC_IMVID imvID,
+       char *functionName,
+       void **pOutfunctionPointer);
+
+#define TNC_RESULT_SUCCESS 0
+#define TNC_RESULT_NOT_INITIALIZED 1
+#define TNC_RESULT_ALREADY_INITIALIZED 2
+#define TNC_RESULT_NO_COMMON_VERSION 3
+#define TNC_RESULT_CANT_RETRY 4
+#define TNC_RESULT_WONT_RETRY 5
+#define TNC_RESULT_INVALID_PARAMETER 6
+#define TNC_RESULT_CANT_RESPOND 7
+#define TNC_RESULT_ILLEGAL_OPERATION 8
+#define TNC_RESULT_OTHER 9
+#define TNC_RESULT_FATAL 10
+
+#define TNC_CONNECTION_STATE_CREATE 0
+#define TNC_CONNECTION_STATE_HANDSHAKE 1
+#define TNC_CONNECTION_STATE_ACCESS_ALLOWED 2
+#define TNC_CONNECTION_STATE_ACCESS_ISOLATED 3
+#define TNC_CONNECTION_STATE_ACCESS_NONE 4
+#define TNC_CONNECTION_STATE_DELETE 5
+
+#define TNC_IFIMV_VERSION_1 1
+
+#define TNC_VENDORID_ANY ((TNC_VendorID) 0xffffff)
+#define TNC_SUBTYPE_ANY ((TNC_Subtype) 0xff)
+
+/* TNCC-TNCS Message Types */
+#define TNC_TNCCS_RECOMMENDATION               0x00000001
+#define TNC_TNCCS_ERROR                                0x00000002
+#define TNC_TNCCS_PREFERREDLANGUAGE            0x00000003
+#define TNC_TNCCS_REASONSTRINGS                        0x00000004
+
+/* Possible TNC_IMV_Action_Recommendation values: */
+enum IMV_Action_Recommendation {
+       TNC_IMV_ACTION_RECOMMENDATION_ALLOW,
+       TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS,
+       TNC_IMV_ACTION_RECOMMENDATION_ISOLATE,
+       TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION
+};
+
+/* Possible TNC_IMV_Evaluation_Result values: */
+enum IMV_Evaluation_Result {
+       TNC_IMV_EVALUATION_RESULT_COMPLIANT,
+       TNC_IMV_EVALUATION_RESULT_NONCOMPLIANT_MINOR,
+       TNC_IMV_EVALUATION_RESULT_NONCOMPLIANT_MAJOR,
+       TNC_IMV_EVALUATION_RESULT_ERROR,
+       TNC_IMV_EVALUATION_RESULT_DONT_KNOW
+};
+
+struct tnc_if_imv {
+       struct tnc_if_imv *next;
+       char *name;
+       char *path;
+       void *dlhandle; /* from dlopen() */
+       TNC_IMVID imvID;
+       TNC_MessageTypeList supported_types;
+       size_t num_supported_types;
+
+       /* Functions implemented by IMVs (with TNC_IMV_ prefix) */
+       TNC_Result (*Initialize)(
+               TNC_IMVID imvID,
+               TNC_Version minVersion,
+               TNC_Version maxVersion,
+               TNC_Version *pOutActualVersion);
+       TNC_Result (*NotifyConnectionChange)(
+               TNC_IMVID imvID,
+               TNC_ConnectionID connectionID,
+               TNC_ConnectionState newState);
+       TNC_Result (*ReceiveMessage)(
+               TNC_IMVID imvID,
+               TNC_ConnectionID connectionID,
+               TNC_BufferReference message,
+               TNC_UInt32 messageLength,
+               TNC_MessageType messageType);
+       TNC_Result (*SolicitRecommendation)(
+               TNC_IMVID imvID,
+               TNC_ConnectionID connectionID);
+       TNC_Result (*BatchEnding)(
+               TNC_IMVID imvID,
+               TNC_ConnectionID connectionID);
+       TNC_Result (*Terminate)(TNC_IMVID imvID);
+       TNC_Result (*ProvideBindFunction)(
+               TNC_IMVID imvID,
+               TNC_TNCS_BindFunctionPointer bindFunction);
+};
+
+
+#define TNC_MAX_IMV_ID 10
+
+struct tncs_data {
+       struct tncs_data *next;
+       struct tnc_if_imv *imv; /* local copy of tncs_global_data->imv */
+       TNC_ConnectionID connectionID;
+       unsigned int last_batchid;
+       enum IMV_Action_Recommendation recommendation;
+       int done;
+
+       struct conn_imv {
+               u8 *imv_send;
+               size_t imv_send_len;
+               enum IMV_Action_Recommendation recommendation;
+               int recommendation_set;
+       } imv_data[TNC_MAX_IMV_ID];
+
+       char *tncs_message;
+};
+
+
+struct tncs_global {
+       struct tnc_if_imv *imv;
+       TNC_ConnectionID next_conn_id;
+       struct tncs_data *connections;
+};
+
+static struct tncs_global *tncs_global_data = NULL;
+
+
+static struct tnc_if_imv * tncs_get_imv(TNC_IMVID imvID)
+{
+       struct tnc_if_imv *imv;
+
+       if (imvID >= TNC_MAX_IMV_ID || tncs_global_data == NULL)
+               return NULL;
+       imv = tncs_global_data->imv;
+       while (imv) {
+               if (imv->imvID == imvID)
+                       return imv;
+               imv = imv->next;
+       }
+       return NULL;
+}
+
+
+static struct tncs_data * tncs_get_conn(TNC_ConnectionID connectionID)
+{
+       struct tncs_data *tncs;
+
+       if (tncs_global_data == NULL)
+               return NULL;
+
+       tncs = tncs_global_data->connections;
+       while (tncs) {
+               if (tncs->connectionID == connectionID)
+                       return tncs;
+               tncs = tncs->next;
+       }
+
+       wpa_printf(MSG_DEBUG, "TNC: Connection ID %lu not found",
+                  (unsigned long) connectionID);
+
+       return NULL;
+}
+
+
+/* TNCS functions that IMVs can call */
+TNC_Result TNC_TNCS_ReportMessageTypes(
+       TNC_IMVID imvID,
+       TNC_MessageTypeList supportedTypes,
+       TNC_UInt32 typeCount)
+{
+       TNC_UInt32 i;
+       struct tnc_if_imv *imv;
+
+       wpa_printf(MSG_DEBUG, "TNC: TNC_TNCS_ReportMessageTypes(imvID=%lu "
+                  "typeCount=%lu)",
+                  (unsigned long) imvID, (unsigned long) typeCount);
+
+       for (i = 0; i < typeCount; i++) {
+               wpa_printf(MSG_DEBUG, "TNC: supportedTypes[%lu] = %lu",
+                          i, supportedTypes[i]);
+       }
+
+       imv = tncs_get_imv(imvID);
+       if (imv == NULL)
+               return TNC_RESULT_INVALID_PARAMETER;
+       os_free(imv->supported_types);
+       imv->supported_types =
+               os_malloc(typeCount * sizeof(TNC_MessageTypeList));
+       if (imv->supported_types == NULL)
+               return TNC_RESULT_FATAL;
+       os_memcpy(imv->supported_types, supportedTypes,
+                 typeCount * sizeof(TNC_MessageTypeList));
+       imv->num_supported_types = typeCount;
+
+       return TNC_RESULT_SUCCESS;
+}
+
+
+TNC_Result TNC_TNCS_SendMessage(
+       TNC_IMVID imvID,
+       TNC_ConnectionID connectionID,
+       TNC_BufferReference message,
+       TNC_UInt32 messageLength,
+       TNC_MessageType messageType)
+{
+       struct tncs_data *tncs;
+       unsigned char *b64;
+       size_t b64len;
+
+       wpa_printf(MSG_DEBUG, "TNC: TNC_TNCS_SendMessage(imvID=%lu "
+                  "connectionID=%lu messageType=%lu)",
+                  imvID, connectionID, messageType);
+       wpa_hexdump_ascii(MSG_DEBUG, "TNC: TNC_TNCS_SendMessage",
+                         message, messageLength);
+
+       if (tncs_get_imv(imvID) == NULL)
+               return TNC_RESULT_INVALID_PARAMETER;
+
+       tncs = tncs_get_conn(connectionID);
+       if (tncs == NULL)
+               return TNC_RESULT_INVALID_PARAMETER;
+
+       b64 = base64_encode(message, messageLength, &b64len);
+       if (b64 == NULL)
+               return TNC_RESULT_FATAL;
+
+       os_free(tncs->imv_data[imvID].imv_send);
+       tncs->imv_data[imvID].imv_send_len = 0;
+       tncs->imv_data[imvID].imv_send = os_zalloc(b64len + 100);
+       if (tncs->imv_data[imvID].imv_send == NULL) {
+               os_free(b64);
+               return TNC_RESULT_OTHER;
+       }
+
+       tncs->imv_data[imvID].imv_send_len =
+               os_snprintf((char *) tncs->imv_data[imvID].imv_send,
+                           b64len + 100,
+                           "<IMC-IMV-Message><Type>%08X</Type>"
+                           "<Base64>%s</Base64></IMC-IMV-Message>",
+                           (unsigned int) messageType, b64);
+
+       os_free(b64);
+
+       return TNC_RESULT_SUCCESS;
+}
+
+
+TNC_Result TNC_TNCS_RequestHandshakeRetry(
+       TNC_IMVID imvID,
+       TNC_ConnectionID connectionID,
+       TNC_RetryReason reason)
+{
+       wpa_printf(MSG_DEBUG, "TNC: TNC_TNCS_RequestHandshakeRetry");
+       /* TODO */
+       return TNC_RESULT_SUCCESS;
+}
+
+
+TNC_Result TNC_TNCS_ProvideRecommendation(
+       TNC_IMVID imvID,
+       TNC_ConnectionID connectionID,
+       TNC_IMV_Action_Recommendation recommendation,
+       TNC_IMV_Evaluation_Result evaluation)
+{
+       struct tncs_data *tncs;
+
+       wpa_printf(MSG_DEBUG, "TNC: TNC_TNCS_ProvideRecommendation(imvID=%lu "
+                  "connectionID=%lu recommendation=%lu evaluation=%lu)",
+                  (unsigned long) imvID, (unsigned long) connectionID,
+                  (unsigned long) recommendation, (unsigned long) evaluation);
+
+       if (tncs_get_imv(imvID) == NULL)
+               return TNC_RESULT_INVALID_PARAMETER;
+
+       tncs = tncs_get_conn(connectionID);
+       if (tncs == NULL)
+               return TNC_RESULT_INVALID_PARAMETER;
+
+       tncs->imv_data[imvID].recommendation = recommendation;
+       tncs->imv_data[imvID].recommendation_set = 1;
+
+       return TNC_RESULT_SUCCESS;
+}
+
+
+TNC_Result TNC_TNCS_GetAttribute(
+       TNC_IMVID imvID,
+       TNC_ConnectionID connectionID,
+       TNC_AttributeID attribureID,
+       TNC_UInt32 bufferLength,
+       TNC_BufferReference buffer,
+       TNC_UInt32 *pOutValueLength)
+{
+       wpa_printf(MSG_DEBUG, "TNC: TNC_TNCS_GetAttribute");
+       /* TODO */
+       return TNC_RESULT_SUCCESS;
+}
+
+
+TNC_Result TNC_TNCS_SetAttribute(
+       TNC_IMVID imvID,
+       TNC_ConnectionID connectionID,
+       TNC_AttributeID attribureID,
+       TNC_UInt32 bufferLength,
+       TNC_BufferReference buffer)
+{
+       wpa_printf(MSG_DEBUG, "TNC: TNC_TNCS_SetAttribute");
+       /* TODO */
+       return TNC_RESULT_SUCCESS;
+}
+
+
+TNC_Result TNC_TNCS_BindFunction(
+       TNC_IMVID imvID,
+       char *functionName,
+       void **pOutFunctionPointer)
+{
+       wpa_printf(MSG_DEBUG, "TNC: TNC_TNCS_BindFunction(imcID=%lu, "
+                  "functionName='%s')", (unsigned long) imvID, functionName);
+
+       if (tncs_get_imv(imvID) == NULL)
+               return TNC_RESULT_INVALID_PARAMETER;
+
+       if (pOutFunctionPointer == NULL)
+               return TNC_RESULT_INVALID_PARAMETER;
+
+       if (os_strcmp(functionName, "TNC_TNCS_ReportMessageTypes") == 0)
+               *pOutFunctionPointer = TNC_TNCS_ReportMessageTypes;
+       else if (os_strcmp(functionName, "TNC_TNCS_SendMessage") == 0)
+               *pOutFunctionPointer = TNC_TNCS_SendMessage;
+       else if (os_strcmp(functionName, "TNC_TNCS_RequestHandshakeRetry") ==
+                0)
+               *pOutFunctionPointer = TNC_TNCS_RequestHandshakeRetry;
+       else if (os_strcmp(functionName, "TNC_TNCS_ProvideRecommendation") ==
+                0)
+               *pOutFunctionPointer = TNC_TNCS_ProvideRecommendation;
+       else if (os_strcmp(functionName, "TNC_TNCS_GetAttribute") == 0)
+               *pOutFunctionPointer = TNC_TNCS_GetAttribute;
+       else if (os_strcmp(functionName, "TNC_TNCS_SetAttribute") == 0)
+               *pOutFunctionPointer = TNC_TNCS_SetAttribute;
+       else
+               *pOutFunctionPointer = NULL;
+
+       return TNC_RESULT_SUCCESS;
+}
+
+
+static void * tncs_get_sym(void *handle, char *func)
+{
+       void *fptr;
+
+       fptr = dlsym(handle, func);
+
+       return fptr;
+}
+
+
+static int tncs_imv_resolve_funcs(struct tnc_if_imv *imv)
+{
+       void *handle = imv->dlhandle;
+
+       /* Mandatory IMV functions */
+       imv->Initialize = tncs_get_sym(handle, "TNC_IMV_Initialize");
+       if (imv->Initialize == NULL) {
+               wpa_printf(MSG_ERROR, "TNC: IMV does not export "
+                          "TNC_IMV_Initialize");
+               return -1;
+       }
+
+       imv->SolicitRecommendation = tncs_get_sym(
+               handle, "TNC_IMV_SolicitRecommendation");
+       if (imv->SolicitRecommendation == NULL) {
+               wpa_printf(MSG_ERROR, "TNC: IMV does not export "
+                          "TNC_IMV_SolicitRecommendation");
+               return -1;
+       }
+
+       imv->ProvideBindFunction =
+               tncs_get_sym(handle, "TNC_IMV_ProvideBindFunction");
+       if (imv->ProvideBindFunction == NULL) {
+               wpa_printf(MSG_ERROR, "TNC: IMV does not export "
+                          "TNC_IMV_ProvideBindFunction");
+               return -1;
+       }
+
+       /* Optional IMV functions */
+       imv->NotifyConnectionChange =
+               tncs_get_sym(handle, "TNC_IMV_NotifyConnectionChange");
+       imv->ReceiveMessage = tncs_get_sym(handle, "TNC_IMV_ReceiveMessage");
+       imv->BatchEnding = tncs_get_sym(handle, "TNC_IMV_BatchEnding");
+       imv->Terminate = tncs_get_sym(handle, "TNC_IMV_Terminate");
+
+       return 0;
+}
+
+
+static int tncs_imv_initialize(struct tnc_if_imv *imv)
+{
+       TNC_Result res;
+       TNC_Version imv_ver;
+
+       wpa_printf(MSG_DEBUG, "TNC: Calling TNC_IMV_Initialize for IMV '%s'",
+                  imv->name);
+       res = imv->Initialize(imv->imvID, TNC_IFIMV_VERSION_1,
+                             TNC_IFIMV_VERSION_1, &imv_ver);
+       wpa_printf(MSG_DEBUG, "TNC: TNC_IMV_Initialize: res=%lu imv_ver=%lu",
+                  (unsigned long) res, (unsigned long) imv_ver);
+
+       return res == TNC_RESULT_SUCCESS ? 0 : -1;
+}
+
+
+static int tncs_imv_terminate(struct tnc_if_imv *imv)
+{
+       TNC_Result res;
+
+       if (imv->Terminate == NULL)
+               return 0;
+
+       wpa_printf(MSG_DEBUG, "TNC: Calling TNC_IMV_Terminate for IMV '%s'",
+                  imv->name);
+       res = imv->Terminate(imv->imvID);
+       wpa_printf(MSG_DEBUG, "TNC: TNC_IMV_Terminate: %lu",
+                  (unsigned long) res);
+
+       return res == TNC_RESULT_SUCCESS ? 0 : -1;
+}
+
+
+static int tncs_imv_provide_bind_function(struct tnc_if_imv *imv)
+{
+       TNC_Result res;
+
+       wpa_printf(MSG_DEBUG, "TNC: Calling TNC_IMV_ProvideBindFunction for "
+                  "IMV '%s'", imv->name);
+       res = imv->ProvideBindFunction(imv->imvID, TNC_TNCS_BindFunction);
+       wpa_printf(MSG_DEBUG, "TNC: TNC_IMV_ProvideBindFunction: res=%lu",
+                  (unsigned long) res);
+
+       return res == TNC_RESULT_SUCCESS ? 0 : -1;
+}
+
+
+static int tncs_imv_notify_connection_change(struct tnc_if_imv *imv,
+                                            TNC_ConnectionID conn,
+                                            TNC_ConnectionState state)
+{
+       TNC_Result res;
+
+       if (imv->NotifyConnectionChange == NULL)
+               return 0;
+
+       wpa_printf(MSG_DEBUG, "TNC: Calling TNC_IMV_NotifyConnectionChange(%d)"
+                  " for IMV '%s'", (int) state, imv->name);
+       res = imv->NotifyConnectionChange(imv->imvID, conn, state);
+       wpa_printf(MSG_DEBUG, "TNC: TNC_IMC_NotifyConnectionChange: %lu",
+                  (unsigned long) res);
+
+       return res == TNC_RESULT_SUCCESS ? 0 : -1;
+}
+
+
+static int tncs_load_imv(struct tnc_if_imv *imv)
+{
+       if (imv->path == NULL) {
+               wpa_printf(MSG_DEBUG, "TNC: No IMV configured");
+               return -1;
+       }
+
+       wpa_printf(MSG_DEBUG, "TNC: Opening IMV: %s (%s)",
+                  imv->name, imv->path);
+       imv->dlhandle = dlopen(imv->path, RTLD_LAZY);
+       if (imv->dlhandle == NULL) {
+               wpa_printf(MSG_ERROR, "TNC: Failed to open IMV '%s' (%s): %s",
+                          imv->name, imv->path, dlerror());
+               return -1;
+       }
+
+       if (tncs_imv_resolve_funcs(imv) < 0) {
+               wpa_printf(MSG_ERROR, "TNC: Failed to resolve IMV functions");
+               return -1;
+       }
+
+       if (tncs_imv_initialize(imv) < 0 ||
+           tncs_imv_provide_bind_function(imv) < 0) {
+               wpa_printf(MSG_ERROR, "TNC: Failed to initialize IMV");
+               return -1;
+       }
+
+       return 0;
+}
+
+
+static void tncs_free_imv(struct tnc_if_imv *imv)
+{
+       os_free(imv->name);
+       os_free(imv->path);
+       os_free(imv->supported_types);
+}
+
+static void tncs_unload_imv(struct tnc_if_imv *imv)
+{
+       tncs_imv_terminate(imv);
+
+       if (imv->dlhandle)
+               dlclose(imv->dlhandle);
+
+       tncs_free_imv(imv);
+}
+
+
+static int tncs_supported_type(struct tnc_if_imv *imv, unsigned int type)
+{
+       size_t i;
+       unsigned int vendor, subtype;
+
+       if (imv == NULL || imv->supported_types == NULL)
+               return 0;
+
+       vendor = type >> 8;
+       subtype = type & 0xff;
+
+       for (i = 0; i < imv->num_supported_types; i++) {
+               unsigned int svendor, ssubtype;
+               svendor = imv->supported_types[i] >> 8;
+               ssubtype = imv->supported_types[i] & 0xff;
+               if ((vendor == svendor || svendor == TNC_VENDORID_ANY) &&
+                   (subtype == ssubtype || ssubtype == TNC_SUBTYPE_ANY))
+                       return 1;
+       }
+
+       return 0;
+}
+
+
+static void tncs_send_to_imvs(struct tncs_data *tncs, unsigned int type,
+                             const u8 *msg, size_t len)
+{
+       struct tnc_if_imv *imv;
+       TNC_Result res;
+
+       wpa_hexdump_ascii(MSG_MSGDUMP, "TNC: Message to IMV(s)", msg, len);
+
+       for (imv = tncs->imv; imv; imv = imv->next) {
+               if (imv->ReceiveMessage == NULL ||
+                   !tncs_supported_type(imv, type))
+                       continue;
+
+               wpa_printf(MSG_DEBUG, "TNC: Call ReceiveMessage for IMV '%s'",
+                          imv->name);
+               res = imv->ReceiveMessage(imv->imvID, tncs->connectionID,
+                                         (TNC_BufferReference) msg, len,
+                                         type);
+               wpa_printf(MSG_DEBUG, "TNC: ReceiveMessage: %lu",
+                          (unsigned long) res);
+       }
+}
+
+
+static void tncs_batch_ending(struct tncs_data *tncs)
+{
+       struct tnc_if_imv *imv;
+       TNC_Result res;
+
+       for (imv = tncs->imv; imv; imv = imv->next) {
+               if (imv->BatchEnding == NULL)
+                       continue;
+
+               wpa_printf(MSG_DEBUG, "TNC: Call BatchEnding for IMV '%s'",
+                          imv->name);
+               res = imv->BatchEnding(imv->imvID, tncs->connectionID);
+               wpa_printf(MSG_DEBUG, "TNC: BatchEnding: %lu",
+                          (unsigned long) res);
+       }
+}
+
+
+static void tncs_solicit_recommendation(struct tncs_data *tncs)
+{
+       struct tnc_if_imv *imv;
+       TNC_Result res;
+
+       for (imv = tncs->imv; imv; imv = imv->next) {
+               if (tncs->imv_data[imv->imvID].recommendation_set)
+                       continue;
+
+               wpa_printf(MSG_DEBUG, "TNC: Call SolicitRecommendation for "
+                          "IMV '%s'", imv->name);
+               res = imv->SolicitRecommendation(imv->imvID,
+                                                tncs->connectionID);
+               wpa_printf(MSG_DEBUG, "TNC: SolicitRecommendation: %lu",
+                          (unsigned long) res);
+       }
+}
+
+
+void tncs_init_connection(struct tncs_data *tncs)
+{
+       struct tnc_if_imv *imv;
+       int i;
+
+       for (imv = tncs->imv; imv; imv = imv->next) {
+               tncs_imv_notify_connection_change(
+                       imv, tncs->connectionID, TNC_CONNECTION_STATE_CREATE);
+               tncs_imv_notify_connection_change(
+                       imv, tncs->connectionID,
+                       TNC_CONNECTION_STATE_HANDSHAKE);
+       }
+
+       for (i = 0; i < TNC_MAX_IMV_ID; i++) {
+               os_free(tncs->imv_data[i].imv_send);
+               tncs->imv_data[i].imv_send = NULL;
+               tncs->imv_data[i].imv_send_len = 0;
+       }
+}
+
+
+size_t tncs_total_send_len(struct tncs_data *tncs)
+{
+       int i;
+       size_t len = 0;
+
+       for (i = 0; i < TNC_MAX_IMV_ID; i++)
+               len += tncs->imv_data[i].imv_send_len;
+       if (tncs->tncs_message)
+               len += os_strlen(tncs->tncs_message);
+       return len;
+}
+
+
+u8 * tncs_copy_send_buf(struct tncs_data *tncs, u8 *pos)
+{
+       int i;
+
+       for (i = 0; i < TNC_MAX_IMV_ID; i++) {
+               if (tncs->imv_data[i].imv_send == NULL)
+                       continue;
+
+               os_memcpy(pos, tncs->imv_data[i].imv_send,
+                         tncs->imv_data[i].imv_send_len);
+               pos += tncs->imv_data[i].imv_send_len;
+               os_free(tncs->imv_data[i].imv_send);
+               tncs->imv_data[i].imv_send = NULL;
+               tncs->imv_data[i].imv_send_len = 0;
+       }
+
+       if (tncs->tncs_message) {
+               size_t len = os_strlen(tncs->tncs_message);
+               os_memcpy(pos, tncs->tncs_message, len);
+               pos += len;
+               os_free(tncs->tncs_message);
+               tncs->tncs_message = NULL;
+       }
+
+       return pos;
+}
+
+
+char * tncs_if_tnccs_start(struct tncs_data *tncs)
+{
+       char *buf = os_malloc(1000);
+       if (buf == NULL)
+               return NULL;
+       tncs->last_batchid++;
+       os_snprintf(buf, 1000, IF_TNCCS_START, tncs->last_batchid);
+       return buf;
+}
+
+
+char * tncs_if_tnccs_end(void)
+{
+       char *buf = os_malloc(100);
+       if (buf == NULL)
+               return NULL;
+       os_snprintf(buf, 100, IF_TNCCS_END);
+       return buf;
+}
+
+
+static int tncs_get_type(char *start, unsigned int *type)
+{
+       char *pos = os_strstr(start, "<Type>");
+       if (pos == NULL)
+               return -1;
+       pos += 6;
+       *type = strtoul(pos, NULL, 16);
+       return 0;
+}
+
+
+static unsigned char * tncs_get_base64(char *start, size_t *decoded_len)
+{
+       char *pos, *pos2;
+       unsigned char *decoded;
+
+       pos = os_strstr(start, "<Base64>");
+       if (pos == NULL)
+               return NULL;
+
+       pos += 8;
+       pos2 = os_strstr(pos, "</Base64>");
+       if (pos2 == NULL)
+               return NULL;
+       *pos2 = '\0';
+
+       decoded = base64_decode((unsigned char *) pos, os_strlen(pos),
+                               decoded_len);
+       *pos2 = '<';
+       if (decoded == NULL) {
+               wpa_printf(MSG_DEBUG, "TNC: Failed to decode Base64 data");
+       }
+
+       return decoded;
+}
+
+
+static enum tncs_process_res tncs_derive_recommendation(struct tncs_data *tncs)
+{
+       enum IMV_Action_Recommendation rec;
+       struct tnc_if_imv *imv;
+       TNC_ConnectionState state;
+       char *txt;
+
+       wpa_printf(MSG_DEBUG, "TNC: No more messages from IMVs");
+
+       if (tncs->done)
+               return TNCCS_PROCESS_OK_NO_RECOMMENDATION;
+
+       tncs_solicit_recommendation(tncs);
+
+       /* Select the most restrictive recommendation */
+       rec = TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION;
+       for (imv = tncs->imv; imv; imv = imv->next) {
+               TNC_IMV_Action_Recommendation irec;
+               irec = tncs->imv_data[imv->imvID].recommendation;
+               if (irec == TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS)
+                       rec = TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS;
+               if (irec == TNC_IMV_ACTION_RECOMMENDATION_ISOLATE &&
+                   rec != TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS)
+                       rec = TNC_IMV_ACTION_RECOMMENDATION_ISOLATE;
+               if (irec == TNC_IMV_ACTION_RECOMMENDATION_ALLOW &&
+                   rec == TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION)
+                       rec = TNC_IMV_ACTION_RECOMMENDATION_ALLOW;
+       }
+
+       wpa_printf(MSG_DEBUG, "TNC: Recommendation: %d", rec);
+       tncs->recommendation = rec;
+       tncs->done = 1;
+
+       txt = NULL;
+       switch (rec) {
+       case TNC_IMV_ACTION_RECOMMENDATION_ALLOW:
+       case TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION:
+               txt = "allow";
+               state = TNC_CONNECTION_STATE_ACCESS_ALLOWED;
+               break;
+       case TNC_IMV_ACTION_RECOMMENDATION_ISOLATE:
+               txt = "isolate";
+               state = TNC_CONNECTION_STATE_ACCESS_ISOLATED;
+               break;
+       case TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS:
+               txt = "none";
+               state = TNC_CONNECTION_STATE_ACCESS_NONE;
+               break;
+       default:
+               state = TNC_CONNECTION_STATE_ACCESS_ALLOWED;
+               break;
+       }
+
+       if (txt) {
+               os_free(tncs->tncs_message);
+               tncs->tncs_message = os_zalloc(200);
+               if (tncs->tncs_message) {
+                       os_snprintf(tncs->tncs_message, 199,
+                                   "<TNCC-TNCS-Message><Type>%08X</Type>"
+                                   "<XML><TNCCS-Recommendation type=\"%s\">"
+                                   "</TNCCS-Recommendation></XML>"
+                                   "</TNCC-TNCS-Message>",
+                                   TNC_TNCCS_RECOMMENDATION, txt);
+               }
+       }
+
+       for (imv = tncs->imv; imv; imv = imv->next) {
+               tncs_imv_notify_connection_change(imv, tncs->connectionID,
+                                                 state);
+       }
+
+       switch (rec) {
+       case TNC_IMV_ACTION_RECOMMENDATION_ALLOW:
+               return TNCCS_RECOMMENDATION_ALLOW;
+       case TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS:
+               return TNCCS_RECOMMENDATION_NO_ACCESS;
+       case TNC_IMV_ACTION_RECOMMENDATION_ISOLATE:
+               return TNCCS_RECOMMENDATION_ISOLATE;
+       case TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION:
+               return TNCCS_RECOMMENDATION_NO_RECOMMENDATION;
+       default:
+               return TNCCS_PROCESS_ERROR;
+       }
+}
+
+
+enum tncs_process_res tncs_process_if_tnccs(struct tncs_data *tncs,
+                                           const u8 *msg, size_t len)
+{
+       char *buf, *start, *end, *pos, *pos2, *payload;
+       unsigned int batch_id;
+       unsigned char *decoded;
+       size_t decoded_len;
+
+       buf = os_malloc(len + 1);
+       if (buf == NULL)
+               return TNCCS_PROCESS_ERROR;
+
+       os_memcpy(buf, msg, len);
+       buf[len] = '\0';
+       start = os_strstr(buf, "<TNCCS-Batch ");
+       end = os_strstr(buf, "</TNCCS-Batch>");
+       if (start == NULL || end == NULL || start > end) {
+               os_free(buf);
+               return TNCCS_PROCESS_ERROR;
+       }
+
+       start += 13;
+       while (*start == ' ')
+               start++;
+       *end = '\0';
+
+       pos = os_strstr(start, "BatchId=");
+       if (pos == NULL) {
+               os_free(buf);
+               return TNCCS_PROCESS_ERROR;
+       }
+
+       pos += 8;
+       if (*pos == '"')
+               pos++;
+       batch_id = atoi(pos);
+       wpa_printf(MSG_DEBUG, "TNC: Received IF-TNCCS BatchId=%u",
+                  batch_id);
+       if (batch_id != tncs->last_batchid + 1) {
+               wpa_printf(MSG_DEBUG, "TNC: Unexpected IF-TNCCS BatchId "
+                          "%u (expected %u)",
+                          batch_id, tncs->last_batchid + 1);
+               os_free(buf);
+               return TNCCS_PROCESS_ERROR;
+       }
+       tncs->last_batchid = batch_id;
+
+       while (*pos != '\0' && *pos != '>')
+               pos++;
+       if (*pos == '\0') {
+               os_free(buf);
+               return TNCCS_PROCESS_ERROR;
+       }
+       pos++;
+       payload = start;
+
+       /*
+        * <IMC-IMV-Message>
+        * <Type>01234567</Type>
+        * <Base64>foo==</Base64>
+        * </IMC-IMV-Message>
+        */
+
+       while (*start) {
+               char *endpos;
+               unsigned int type;
+
+               pos = os_strstr(start, "<IMC-IMV-Message>");
+               if (pos == NULL)
+                       break;
+               start = pos + 17;
+               end = os_strstr(start, "</IMC-IMV-Message>");
+               if (end == NULL)
+                       break;
+               *end = '\0';
+               endpos = end;
+               end += 18;
+
+               if (tncs_get_type(start, &type) < 0) {
+                       *endpos = '<';
+                       start = end;
+                       continue;
+               }
+               wpa_printf(MSG_DEBUG, "TNC: IMC-IMV-Message Type 0x%x", type);
+
+               decoded = tncs_get_base64(start, &decoded_len);
+               if (decoded == NULL) {
+                       *endpos = '<';
+                       start = end;
+                       continue;
+               }
+
+               tncs_send_to_imvs(tncs, type, decoded, decoded_len);
+
+               os_free(decoded);
+
+               start = end;
+       }
+
+       /*
+        * <TNCC-TNCS-Message>
+        * <Type>01234567</Type>
+        * <XML><TNCCS-Foo type="foo"></TNCCS-Foo></XML>
+        * <Base64>foo==</Base64>
+        * </TNCC-TNCS-Message>
+        */
+
+       start = payload;
+       while (*start) {
+               unsigned int type;
+               char *xml, *xmlend, *endpos;
+
+               pos = os_strstr(start, "<TNCC-TNCS-Message>");
+               if (pos == NULL)
+                       break;
+               start = pos + 19;
+               end = os_strstr(start, "</TNCC-TNCS-Message>");
+               if (end == NULL)
+                       break;
+               *end = '\0';
+               endpos = end;
+               end += 20;
+
+               if (tncs_get_type(start, &type) < 0) {
+                       *endpos = '<';
+                       start = end;
+                       continue;
+               }
+               wpa_printf(MSG_DEBUG, "TNC: TNCC-TNCS-Message Type 0x%x",
+                          type);
+
+               /* Base64 OR XML */
+               decoded = NULL;
+               xml = NULL;
+               xmlend = NULL;
+               pos = os_strstr(start, "<XML>");
+               if (pos) {
+                       pos += 5;
+                       pos2 = os_strstr(pos, "</XML>");
+                       if (pos2 == NULL) {
+                               *endpos = '<';
+                               start = end;
+                               continue;
+                       }
+                       xmlend = pos2;
+                       xml = pos;
+               } else {
+                       decoded = tncs_get_base64(start, &decoded_len);
+                       if (decoded == NULL) {
+                               *endpos = '<';
+                               start = end;
+                               continue;
+                       }
+               }
+
+               if (decoded) {
+                       wpa_hexdump_ascii(MSG_MSGDUMP,
+                                         "TNC: TNCC-TNCS-Message Base64",
+                                         decoded, decoded_len);
+                       os_free(decoded);
+               }
+
+               if (xml) {
+                       wpa_hexdump_ascii(MSG_MSGDUMP,
+                                         "TNC: TNCC-TNCS-Message XML",
+                                         (unsigned char *) xml,
+                                         xmlend - xml);
+               }
+
+               start = end;
+       }
+
+       os_free(buf);
+
+       tncs_batch_ending(tncs);
+
+       if (tncs_total_send_len(tncs) == 0)
+               return tncs_derive_recommendation(tncs);
+
+       return TNCCS_PROCESS_OK_NO_RECOMMENDATION;
+}
+
+
+static struct tnc_if_imv * tncs_parse_imv(int id, char *start, char *end,
+                                         int *error)
+{
+       struct tnc_if_imv *imv;
+       char *pos, *pos2;
+
+       if (id >= TNC_MAX_IMV_ID) {
+               wpa_printf(MSG_DEBUG, "TNC: Too many IMVs");
+               return NULL;
+       }
+
+       imv = os_zalloc(sizeof(*imv));
+       if (imv == NULL) {
+               *error = 1;
+               return NULL;
+       }
+
+       imv->imvID = id;
+
+       pos = start;
+       wpa_printf(MSG_DEBUG, "TNC: Configured IMV: %s", pos);
+       if (pos + 1 >= end || *pos != '"') {
+               wpa_printf(MSG_ERROR, "TNC: Ignoring invalid IMV line '%s' "
+                          "(no starting quotation mark)", start);
+               os_free(imv);
+               return NULL;
+       }
+
+       pos++;
+       pos2 = pos;
+       while (pos2 < end && *pos2 != '"')
+               pos2++;
+       if (pos2 >= end) {
+               wpa_printf(MSG_ERROR, "TNC: Ignoring invalid IMV line '%s' "
+                          "(no ending quotation mark)", start);
+               os_free(imv);
+               return NULL;
+       }
+       *pos2 = '\0';
+       wpa_printf(MSG_DEBUG, "TNC: Name: '%s'", pos);
+       imv->name = os_strdup(pos);
+
+       pos = pos2 + 1;
+       if (pos >= end || *pos != ' ') {
+               wpa_printf(MSG_ERROR, "TNC: Ignoring invalid IMV line '%s' "
+                          "(no space after name)", start);
+               os_free(imv);
+               return NULL;
+       }
+
+       pos++;
+       wpa_printf(MSG_DEBUG, "TNC: IMV file: '%s'", pos);
+       imv->path = os_strdup(pos);
+
+       return imv;
+}
+
+
+static int tncs_read_config(struct tncs_global *global)
+{
+       char *config, *end, *pos, *line_end;
+       size_t config_len;
+       struct tnc_if_imv *imv, *last;
+       int id = 0;
+
+       last = NULL;
+
+       config = os_readfile(TNC_CONFIG_FILE, &config_len);
+       if (config == NULL) {
+               wpa_printf(MSG_ERROR, "TNC: Could not open TNC configuration "
+                          "file '%s'", TNC_CONFIG_FILE);
+               return -1;
+       }
+
+       end = config + config_len;
+       for (pos = config; pos < end; pos = line_end + 1) {
+               line_end = pos;
+               while (*line_end != '\n' && *line_end != '\r' &&
+                      line_end < end)
+                       line_end++;
+               *line_end = '\0';
+
+               if (os_strncmp(pos, "IMV ", 4) == 0) {
+                       int error = 0;
+
+                       imv = tncs_parse_imv(id++, pos + 4, line_end, &error);
+                       if (error)
+                               return -1;
+                       if (imv) {
+                               if (last == NULL)
+                                       global->imv = imv;
+                               else
+                                       last->next = imv;
+                               last = imv;
+                       }
+               }
+       }
+
+       os_free(config);
+
+       return 0;
+}
+
+
+struct tncs_data * tncs_init(void)
+{
+       struct tncs_data *tncs;
+
+       if (tncs_global_data == NULL)
+               return NULL;
+
+       tncs = os_zalloc(sizeof(*tncs));
+       if (tncs == NULL)
+               return NULL;
+       tncs->imv = tncs_global_data->imv;
+       tncs->connectionID = tncs_global_data->next_conn_id++;
+       tncs->next = tncs_global_data->connections;
+       tncs_global_data->connections = tncs;
+
+       return tncs;
+}
+
+
+void tncs_deinit(struct tncs_data *tncs)
+{
+       int i;
+       struct tncs_data *prev, *conn;
+
+       if (tncs == NULL)
+               return;
+
+       for (i = 0; i < TNC_MAX_IMV_ID; i++)
+               os_free(tncs->imv_data[i].imv_send);
+
+       prev = NULL;
+       conn = tncs_global_data->connections;
+       while (conn) {
+               if (conn == tncs) {
+                       if (prev)
+                               prev->next = tncs->next;
+                       else
+                               tncs_global_data->connections = tncs->next;
+                       break;
+               }
+               prev = conn;
+               conn = conn->next;
+       }
+
+       os_free(tncs->tncs_message);
+       os_free(tncs);
+}
+
+
+int tncs_global_init(void)
+{
+       struct tnc_if_imv *imv;
+
+       tncs_global_data = os_zalloc(sizeof(*tncs_global_data));
+       if (tncs_global_data == NULL)
+               return -1;
+
+       if (tncs_read_config(tncs_global_data) < 0) {
+               wpa_printf(MSG_ERROR, "TNC: Failed to read TNC configuration");
+               goto failed;
+       }
+
+       for (imv = tncs_global_data->imv; imv; imv = imv->next) {
+               if (tncs_load_imv(imv)) {
+                       wpa_printf(MSG_ERROR, "TNC: Failed to load IMV '%s'",
+                                  imv->name);
+                       goto failed;
+               }
+       }
+
+       return 0;
+
+failed:
+       tncs_global_deinit();
+       return -1;
+}
+
+
+void tncs_global_deinit(void)
+{
+       struct tnc_if_imv *imv, *prev;
+
+       if (tncs_global_data == NULL)
+               return;
+
+       imv = tncs_global_data->imv;
+       while (imv) {
+               tncs_unload_imv(imv);
+
+               prev = imv;
+               imv = imv->next;
+               os_free(prev);
+       }
+
+       os_free(tncs_global_data);
+}
diff --git a/src/eap_server/tncs.h b/src/eap_server/tncs.h
new file mode 100644 (file)
index 0000000..f4e2074
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * EAP-TNC - TNCS (IF-IMV and IF-TNCCS)
+ * Copyright (c) 2007-2008, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef TNCS_H
+#define TNCS_H
+
+struct tncs_data;
+
+struct tncs_data * tncs_init(void);
+void tncs_deinit(struct tncs_data *tncs);
+void tncs_init_connection(struct tncs_data *tncs);
+size_t tncs_total_send_len(struct tncs_data *tncs);
+u8 * tncs_copy_send_buf(struct tncs_data *tncs, u8 *pos);
+char * tncs_if_tnccs_start(struct tncs_data *tncs);
+char * tncs_if_tnccs_end(void);
+
+enum tncs_process_res {
+       TNCCS_PROCESS_ERROR = -1,
+       TNCCS_PROCESS_OK_NO_RECOMMENDATION = 0,
+       TNCCS_RECOMMENDATION_ERROR,
+       TNCCS_RECOMMENDATION_ALLOW,
+       TNCCS_RECOMMENDATION_NONE,
+       TNCCS_RECOMMENDATION_ISOLATE,
+       TNCCS_RECOMMENDATION_NO_ACCESS,
+       TNCCS_RECOMMENDATION_NO_RECOMMENDATION
+};
+
+enum tncs_process_res tncs_process_if_tnccs(struct tncs_data *tncs,
+                                           const u8 *msg, size_t len);
+
+int tncs_global_init(void);
+void tncs_global_deinit(void);
+
+#endif /* TNCS_H */