10b58b5029
Previously, hostapd had to be started with at least one of the configuration files enabling TNC for TNC to be usable. Change this to allow TNC to be enabled when the first interface with TNC enabled gets added during runtime. Signed-off-by: Jouni Malinen <j@w1.fi>
1202 lines
27 KiB
C
1202 lines
27 KiB
C
/*
|
|
* 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 "common/tnc.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.. */
|
|
|
|
#ifndef TNC_CONFIG_FILE
|
|
#define TNC_CONFIG_FILE "/etc/tnc_config"
|
|
#endif /* TNC_CONFIG_FILE */
|
|
#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 */
|
|
|
|
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 = dup_binstr(msg, len);
|
|
if (buf == NULL)
|
|
return TNCCS_PROCESS_ERROR;
|
|
|
|
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;
|
|
|
|
if (tncs_global_data)
|
|
return 0;
|
|
|
|
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;
|
|
}
|