/*
 * 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 */
static 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;
}


static 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;
}


static 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;
}


static 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;
}


static 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;
}


static 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;
}


static 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;
}
