| /* |
| * EAP-TNC - TNCS (IF-IMV, IF-TNCCS, and IF-TNCCS-SOH) |
| * Copyright (c) 2007-2008, Jouni Malinen <j@w1.fi> |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README for more details. |
| */ |
| |
| #include "includes.h" |
| #include <dlfcn.h> |
| |
| #include "common.h" |
| #include "base64.h" |
| #include "tncs.h" |
| #include "eap_common/eap_tlv_common.h" |
| #include "eap_common/eap_defs.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_MessageType)); |
| if (imv->supported_types == NULL) |
| return TNC_RESULT_FATAL; |
| os_memcpy(imv->supported_types, supportedTypes, |
| typeCount * sizeof(TNC_MessageType)); |
| 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); |
| tncs_global_data = NULL; |
| } |
| |
| |
| struct wpabuf * tncs_build_soh_request(void) |
| { |
| struct wpabuf *buf; |
| |
| /* |
| * Build a SoH Request TLV (to be used inside SoH EAP Extensions |
| * Method) |
| */ |
| |
| buf = wpabuf_alloc(8 + 4); |
| if (buf == NULL) |
| return NULL; |
| |
| /* Vendor-Specific TLV (Microsoft) - SoH Request */ |
| wpabuf_put_be16(buf, EAP_TLV_VENDOR_SPECIFIC_TLV); /* TLV Type */ |
| wpabuf_put_be16(buf, 8); /* Length */ |
| |
| wpabuf_put_be32(buf, EAP_VENDOR_MICROSOFT); /* Vendor_Id */ |
| |
| wpabuf_put_be16(buf, 0x02); /* TLV Type - SoH Request TLV */ |
| wpabuf_put_be16(buf, 0); /* Length */ |
| |
| return buf; |
| } |
| |
| |
| struct wpabuf * tncs_process_soh(const u8 *soh_tlv, size_t soh_tlv_len, |
| int *failure) |
| { |
| wpa_hexdump(MSG_DEBUG, "TNC: SoH TLV", soh_tlv, soh_tlv_len); |
| *failure = 0; |
| |
| /* TODO: return MS-SoH Response TLV */ |
| |
| return NULL; |
| } |