/* * Copyright (C) 2010-2015 Andreas Steffen * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #define _GNU_SOURCE /* for asprintf() */ #include "tnc_tnccs_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include typedef struct private_tnc_tnccs_manager_t private_tnc_tnccs_manager_t; typedef struct tnccs_entry_t tnccs_entry_t; typedef struct tnccs_connection_entry_t tnccs_connection_entry_t; /** * TNCCS constructor entry */ struct tnccs_entry_t { /** * TNCCS protocol type */ tnccs_type_t type; /** * constructor function to create instance */ tnccs_constructor_t constructor; }; /** * TNCCS connection entry */ struct tnccs_connection_entry_t { /** * TNCCS connection ID */ TNC_ConnectionID id; /** * TNCCS protocol type */ tnccs_type_t type; /** * TNCCS instance */ tnccs_t *tnccs; /** * TNCCS send message function */ tnccs_send_message_t send_message; /** * TNCCS request handshake retry flag */ bool *request_handshake_retry; /** * Maximum size of a PA-TNC message */ uint32_t max_msg_len; /** * collection of IMV recommendations */ recommendations_t *recs; }; /** * private data of tnc_tnccs_manager */ struct private_tnc_tnccs_manager_t { /** * public functions */ tnccs_manager_t public; /** * list of TNCCS protocol entries */ linked_list_t *protocols; /** * rwlock to lock the TNCCS protocol entries */ rwlock_t *protocol_lock; /** * connection ID counter */ TNC_ConnectionID connection_id; /** * list of TNCCS connection entries */ linked_list_t *connections; /** * rwlock to lock TNCCS connection entries */ rwlock_t *connection_lock; }; METHOD(tnccs_manager_t, add_method, void, private_tnc_tnccs_manager_t *this, tnccs_type_t type, tnccs_constructor_t constructor) { tnccs_entry_t *entry; entry = malloc_thing(tnccs_entry_t); entry->type = type; entry->constructor = constructor; this->protocol_lock->write_lock(this->protocol_lock); this->protocols->insert_last(this->protocols, entry); this->protocol_lock->unlock(this->protocol_lock); } METHOD(tnccs_manager_t, remove_method, void, private_tnc_tnccs_manager_t *this, tnccs_constructor_t constructor) { enumerator_t *enumerator; tnccs_entry_t *entry; this->protocol_lock->write_lock(this->protocol_lock); enumerator = this->protocols->create_enumerator(this->protocols); while (enumerator->enumerate(enumerator, &entry)) { if (constructor == entry->constructor) { this->protocols->remove_at(this->protocols, enumerator); free(entry); } } enumerator->destroy(enumerator); this->protocol_lock->unlock(this->protocol_lock); } METHOD(tnccs_manager_t, create_instance, tnccs_t*, private_tnc_tnccs_manager_t *this, tnccs_type_t type, bool is_server, identification_t *server_id, identification_t *peer_id, host_t *server_ip, host_t *peer_ip, tnc_ift_type_t transport, tnccs_cb_t cb) { enumerator_t *enumerator; tnccs_entry_t *entry; tnccs_t *protocol = NULL; this->protocol_lock->read_lock(this->protocol_lock); enumerator = this->protocols->create_enumerator(this->protocols); while (enumerator->enumerate(enumerator, &entry)) { if (type == entry->type) { protocol = entry->constructor(is_server, server_id, peer_id, server_ip, peer_ip, transport, cb); if (protocol) { break; } } } enumerator->destroy(enumerator); this->protocol_lock->unlock(this->protocol_lock); return protocol; } METHOD(tnccs_manager_t, create_connection, TNC_ConnectionID, private_tnc_tnccs_manager_t *this, tnccs_type_t type, tnccs_t *tnccs, tnccs_send_message_t send_message, bool* request_handshake_retry, uint32_t max_msg_len, recommendations_t **recs) { tnccs_connection_entry_t *entry; entry = malloc_thing(tnccs_connection_entry_t); entry->type = type; entry->tnccs = tnccs; entry->send_message = send_message; entry->request_handshake_retry = request_handshake_retry; entry->max_msg_len = max_msg_len; if (recs) { /* we assume a TNC Server needing recommendations from IMVs */ if (!tnc->imvs) { DBG1(DBG_TNC, "no IMV manager available!"); free(entry); return 0; } entry->recs = tnc->imvs->create_recommendations(tnc->imvs); *recs = entry->recs; } else { /* we assume a TNC Client */ if (!tnc->imcs) { DBG1(DBG_TNC, "no IMC manager available!"); free(entry); return 0; } entry->recs = NULL; } this->connection_lock->write_lock(this->connection_lock); entry->id = ++this->connection_id; this->connections->insert_last(this->connections, entry); this->connection_lock->unlock(this->connection_lock); DBG1(DBG_TNC, "assigned TNCCS Connection ID %u", entry->id); return entry->id; } METHOD(tnccs_manager_t, remove_connection, void, private_tnc_tnccs_manager_t *this, TNC_ConnectionID id, bool is_server) { enumerator_t *enumerator; tnccs_connection_entry_t *entry; if (is_server) { if (tnc->imvs) { tnc->imvs->notify_connection_change(tnc->imvs, id, TNC_CONNECTION_STATE_DELETE); } } else { if (tnc->imcs) { tnc->imcs->notify_connection_change(tnc->imcs, id, TNC_CONNECTION_STATE_DELETE); } } this->connection_lock->write_lock(this->connection_lock); enumerator = this->connections->create_enumerator(this->connections); while (enumerator->enumerate(enumerator, &entry)) { if (id == entry->id) { this->connections->remove_at(this->connections, enumerator); if (entry->recs) { entry->recs->destroy(entry->recs); } free(entry); DBG1(DBG_TNC, "removed TNCCS Connection ID %u", id); } } enumerator->destroy(enumerator); this->connection_lock->unlock(this->connection_lock); } METHOD(tnccs_manager_t, request_handshake_retry, TNC_Result, private_tnc_tnccs_manager_t *this, bool is_imc, TNC_UInt32 imcv_id, TNC_ConnectionID id, TNC_RetryReason reason) { enumerator_t *enumerator; tnccs_connection_entry_t *entry; if (id == TNC_CONNECTIONID_ANY) { DBG2(DBG_TNC, "%s %u requests handshake retry for all connections " "(reason: %u)", is_imc ? "IMC":"IMV", reason); } else { DBG2(DBG_TNC, "%s %u requests handshake retry for Connection ID %u " "(reason: %u)", is_imc ? "IMC":"IMV", imcv_id, id, reason); } this->connection_lock->read_lock(this->connection_lock); enumerator = this->connections->create_enumerator(this->connections); while (enumerator->enumerate(enumerator, &entry)) { if (id == TNC_CONNECTIONID_ANY || id == entry->id) { *entry->request_handshake_retry = TRUE; break; } } enumerator->destroy(enumerator); this->connection_lock->unlock(this->connection_lock); return TNC_RESULT_SUCCESS; } METHOD(tnccs_manager_t, send_message, TNC_Result, private_tnc_tnccs_manager_t *this, TNC_IMCID imc_id, TNC_IMVID imv_id, TNC_ConnectionID id, TNC_UInt32 msg_flags, TNC_BufferReference msg, TNC_UInt32 msg_len, TNC_VendorID msg_vid, TNC_MessageSubtype msg_subtype) { enumerator_t *enumerator; tnccs_connection_entry_t *entry; tnccs_send_message_t send_message = NULL; tnccs_t *tnccs = NULL; if (msg_vid == TNC_VENDORID_ANY || msg_subtype == TNC_SUBTYPE_ANY) { DBG1(DBG_TNC, "not sending message of invalid type 0x%02x/0x%08x", msg_vid, msg_subtype); return TNC_RESULT_INVALID_PARAMETER; } this->connection_lock->read_lock(this->connection_lock); enumerator = this->connections->create_enumerator(this->connections); while (enumerator->enumerate(enumerator, &entry)) { if (id == entry->id) { tnccs = entry->tnccs; send_message = entry->send_message; break; } } enumerator->destroy(enumerator); this->connection_lock->unlock(this->connection_lock); if (tnccs && send_message) { return send_message(tnccs, imc_id, imv_id, msg_flags, msg, msg_len, msg_vid, msg_subtype); } return TNC_RESULT_FATAL; } METHOD(tnccs_manager_t, provide_recommendation, TNC_Result, private_tnc_tnccs_manager_t *this, TNC_IMVID imv_id, TNC_ConnectionID id, TNC_IMV_Action_Recommendation rec, TNC_IMV_Evaluation_Result eval) { enumerator_t *enumerator; tnccs_connection_entry_t *entry; recommendations_t *recs = NULL; this->connection_lock->read_lock(this->connection_lock); enumerator = this->connections->create_enumerator(this->connections); while (enumerator->enumerate(enumerator, &entry)) { if (id == entry->id) { recs = entry->recs; break; } } enumerator->destroy(enumerator); this->connection_lock->unlock(this->connection_lock); if (recs) { recs->provide_recommendation(recs, imv_id, rec, eval); return TNC_RESULT_SUCCESS; } return TNC_RESULT_FATAL; } /** * Write the value of a boolean attribute into the buffer */ static TNC_Result bool_attribute(TNC_UInt32 buffer_len, TNC_BufferReference buffer, TNC_UInt32 *value_len, bool value) { *value_len = 1; if (buffer && buffer_len > 0) { *buffer = value ? 0x01 : 0x00; return TNC_RESULT_SUCCESS; } else { return TNC_RESULT_INVALID_PARAMETER; } } /** * Write the value of an uint32_t attribute into the buffer */ static TNC_Result uint_attribute(TNC_UInt32 buffer_len, TNC_BufferReference buffer, TNC_UInt32 *value_len, uint32_t value) { *value_len = sizeof(uint32_t); if (buffer && buffer_len >= *value_len) { htoun32(buffer, value); return TNC_RESULT_SUCCESS; } else { return TNC_RESULT_INVALID_PARAMETER; } } /** * Write the value of string attribute into the buffer */ static TNC_Result str_attribute(TNC_UInt32 buffer_len, TNC_BufferReference buffer, TNC_UInt32 *value_len, char *value) { *value_len = 1 + strlen(value); if (buffer && buffer_len >= *value_len) { snprintf(buffer, buffer_len, "%s", value); return TNC_RESULT_SUCCESS; } else { return TNC_RESULT_INVALID_PARAMETER; } } /** * Write the value of a TNC identity list into the buffer */ static TNC_Result identity_attribute(TNC_UInt32 buffer_len, TNC_BufferReference buffer, TNC_UInt32 *value_len, linked_list_t *list) { bio_writer_t *writer; enumerator_t *enumerator; uint32_t count; chunk_t value; tncif_identity_t *tnc_id; TNC_Result result = TNC_RESULT_INVALID_PARAMETER; count = list->get_count(list); writer = bio_writer_create(4 + TNCIF_IDENTITY_MIN_SIZE * count); writer->write_uint32(writer, count); enumerator = list->create_enumerator(list); while (enumerator->enumerate(enumerator, &tnc_id)) { tnc_id->build(tnc_id, writer); } enumerator->destroy(enumerator); value = writer->get_buf(writer); *value_len = value.len; if (buffer && buffer_len >= value.len) { memcpy(buffer, value.ptr, value.len); result = TNC_RESULT_SUCCESS; } writer->destroy(writer); return result; } METHOD(tnccs_manager_t, get_attribute, TNC_Result, private_tnc_tnccs_manager_t *this, bool is_imc, TNC_UInt32 imcv_id, TNC_ConnectionID id, TNC_AttributeID attribute_id, TNC_UInt32 buffer_len, TNC_BufferReference buffer, TNC_UInt32 *value_len) { enumerator_t *enumerator; tnccs_connection_entry_t *entry; bool attribute_match = FALSE, entry_found = FALSE; if (is_imc) { switch (attribute_id) { /* these attributes are unsupported */ case TNC_ATTRIBUTEID_SOHR: case TNC_ATTRIBUTEID_SSOHR: return TNC_RESULT_INVALID_PARAMETER; /* these attributes are supported */ case TNC_ATTRIBUTEID_PRIMARY_IMC_ID: attribute_match = TRUE; break; /* these attributes are yet to be matched */ default: break; } } else { switch (attribute_id) { /* these attributes are unsupported or invalid */ case TNC_ATTRIBUTEID_REASON_STRING: case TNC_ATTRIBUTEID_REASON_LANGUAGE: case TNC_ATTRIBUTEID_SOH: case TNC_ATTRIBUTEID_SSOH: return TNC_RESULT_INVALID_PARAMETER; /* these attributes are supported */ case TNC_ATTRIBUTEID_PRIMARY_IMV_ID: case TNC_ATTRIBUTEID_AR_IDENTITIES: attribute_match = TRUE; break; /* these attributes are yet to be matched */ default: break; } } if (!attribute_match) { switch (attribute_id) { /* these attributes are supported */ case TNC_ATTRIBUTEID_PREFERRED_LANGUAGE: case TNC_ATTRIBUTEID_MAX_ROUND_TRIPS: case TNC_ATTRIBUTEID_MAX_MESSAGE_SIZE: case TNC_ATTRIBUTEID_HAS_LONG_TYPES: case TNC_ATTRIBUTEID_HAS_EXCLUSIVE: case TNC_ATTRIBUTEID_HAS_SOH: case TNC_ATTRIBUTEID_IFTNCCS_PROTOCOL: case TNC_ATTRIBUTEID_IFTNCCS_VERSION: case TNC_ATTRIBUTEID_IFT_PROTOCOL: case TNC_ATTRIBUTEID_IFT_VERSION: break; /* these attributes are unsupported or unknown */ case TNC_ATTRIBUTEID_DHPN: case TNC_ATTRIBUTEID_TLS_UNIQUE: default: return TNC_RESULT_INVALID_PARAMETER; } } /* attributes specific to the TNCC or TNCS are unsupported */ if (id == TNC_CONNECTIONID_ANY) { return TNC_RESULT_INVALID_PARAMETER; } this->connection_lock->read_lock(this->connection_lock); enumerator = this->connections->create_enumerator(this->connections); while (enumerator->enumerate(enumerator, &entry)) { if (id == entry->id) { entry_found = TRUE; break; } } enumerator->destroy(enumerator); this->connection_lock->unlock(this->connection_lock); if (!entry_found) { return TNC_RESULT_INVALID_PARAMETER; } switch (attribute_id) { case TNC_ATTRIBUTEID_PREFERRED_LANGUAGE: { recommendations_t *recs; chunk_t pref_lang; recs = entry->recs; if (!recs) { return TNC_RESULT_INVALID_PARAMETER; } pref_lang = recs->get_preferred_language(recs); if (pref_lang.len == 0) { return TNC_RESULT_INVALID_PARAMETER; } *value_len = pref_lang.len; if (buffer && buffer_len >= pref_lang.len) { memcpy(buffer, pref_lang.ptr, pref_lang.len); } return TNC_RESULT_SUCCESS; } case TNC_ATTRIBUTEID_MAX_ROUND_TRIPS: return uint_attribute(buffer_len, buffer, value_len, 0xffffffff); case TNC_ATTRIBUTEID_MAX_MESSAGE_SIZE: return uint_attribute(buffer_len, buffer, value_len, entry->max_msg_len); case TNC_ATTRIBUTEID_HAS_LONG_TYPES: case TNC_ATTRIBUTEID_HAS_EXCLUSIVE: return bool_attribute(buffer_len, buffer, value_len, entry->type == TNCCS_2_0); case TNC_ATTRIBUTEID_HAS_SOH: return bool_attribute(buffer_len, buffer, value_len, entry->type == TNCCS_SOH); case TNC_ATTRIBUTEID_IFTNCCS_PROTOCOL: { char *protocol; switch (entry->type) { case TNCCS_1_1: case TNCCS_2_0: protocol = "IF-TNCCS"; break; case TNCCS_SOH: protocol = "IF-TNCCS-SOH"; break; default: return TNC_RESULT_INVALID_PARAMETER; } return str_attribute(buffer_len, buffer, value_len, protocol); } case TNC_ATTRIBUTEID_IFTNCCS_VERSION: { char *version; switch (entry->type) { case TNCCS_1_1: version = "1.1"; break; case TNCCS_2_0: version = "2.0"; break; case TNCCS_SOH: version = "1.0"; break; default: return TNC_RESULT_INVALID_PARAMETER; } return str_attribute(buffer_len, buffer, value_len, version); } case TNC_ATTRIBUTEID_IFT_PROTOCOL: { char *protocol; switch (entry->tnccs->get_transport(entry->tnccs)) { case TNC_IFT_EAP_1_0: case TNC_IFT_EAP_1_1: case TNC_IFT_EAP_2_0: protocol = "IF-T for Tunneled EAP"; break; case TNC_IFT_TLS_1_0: case TNC_IFT_TLS_2_0: protocol = "IF-T for TLS"; break; default: return TNC_RESULT_INVALID_PARAMETER; } return str_attribute(buffer_len, buffer, value_len, protocol); } case TNC_ATTRIBUTEID_IFT_VERSION: { char *version; switch (entry->tnccs->get_transport(entry->tnccs)) { case TNC_IFT_EAP_1_0: case TNC_IFT_TLS_1_0: version = "1.0"; break; case TNC_IFT_EAP_1_1: version = "1.1"; break; case TNC_IFT_EAP_2_0: case TNC_IFT_TLS_2_0: version = "2.0"; break; default: return TNC_RESULT_INVALID_PARAMETER; } return str_attribute(buffer_len, buffer, value_len, version); } case TNC_ATTRIBUTEID_AR_IDENTITIES: { linked_list_t *list; identification_t *peer_id; host_t *peer_ip; tnccs_t *tnccs; tncif_identity_t *tnc_id; uint32_t id_type, subject_type; chunk_t id_value; char *id_str; TNC_Result result; list = linked_list_create(); tnccs = entry->tnccs; peer_id = tnccs->tls.is_server(&tnccs->tls) ? tnccs->tls.get_peer_id(&tnccs->tls) : tnccs->tls.get_server_id(&tnccs->tls); if (peer_id) { switch (peer_id->get_type(peer_id)) { case ID_IPV4_ADDR: id_type = TNC_ID_IPV4_ADDR; subject_type = TNC_SUBJECT_MACHINE; break; case ID_IPV6_ADDR: id_type = TNC_ID_IPV6_ADDR; subject_type = TNC_SUBJECT_MACHINE; break; case ID_FQDN: id_type = TNC_ID_USERNAME; subject_type = TNC_SUBJECT_USER; break; case ID_RFC822_ADDR: id_type = TNC_ID_EMAIL_ADDR; subject_type = TNC_SUBJECT_USER; break; case ID_DER_ASN1_DN: id_type = TNC_ID_X500_DN; subject_type = TNC_SUBJECT_USER; break; default: id_type = TNC_ID_UNKNOWN; subject_type = TNC_SUBJECT_UNKNOWN; } if (id_type != TNC_ID_UNKNOWN && asprintf(&id_str, "%Y", peer_id) >= 0) { id_value = chunk_from_str(id_str); tnc_id = tncif_identity_create( pen_type_create(PEN_TCG, id_type), id_value, pen_type_create(PEN_TCG, subject_type), pen_type_create(PEN_TCG, tnccs->get_auth_type(tnccs))); list->insert_last(list, tnc_id); } } peer_ip = tnccs->tls.is_server(&tnccs->tls) ? tnccs->get_peer_ip(tnccs) : tnccs->get_server_ip(tnccs); if (peer_ip) { switch (peer_ip->get_family(peer_ip)) { case AF_INET: id_type = TNC_ID_IPV4_ADDR; break; case AF_INET6: id_type = TNC_ID_IPV6_ADDR; break; default: id_type = TNC_ID_UNKNOWN; } if (id_type != TNC_ID_UNKNOWN && asprintf(&id_str, "%H", peer_ip) >= 0) { id_value = chunk_from_str(id_str); tnc_id = tncif_identity_create( pen_type_create(PEN_TCG, id_type), id_value, pen_type_create(PEN_TCG, TNC_SUBJECT_MACHINE), pen_type_create(PEN_TCG, TNC_AUTH_UNKNOWN)); list->insert_last(list, tnc_id); } } result = identity_attribute(buffer_len, buffer, value_len, list); list->destroy_offset(list, offsetof(tncif_identity_t, destroy)); return result; } default: return TNC_RESULT_INVALID_PARAMETER; } } METHOD(tnccs_manager_t, set_attribute, TNC_Result, private_tnc_tnccs_manager_t *this, bool is_imc, TNC_UInt32 imcv_id, TNC_ConnectionID id, TNC_AttributeID attribute_id, TNC_UInt32 buffer_len, TNC_BufferReference buffer) { enumerator_t *enumerator; tnccs_connection_entry_t *entry; recommendations_t *recs = NULL; if (is_imc || id == TNC_CONNECTIONID_ANY || (attribute_id != TNC_ATTRIBUTEID_REASON_STRING && attribute_id != TNC_ATTRIBUTEID_REASON_LANGUAGE)) { return TNC_RESULT_INVALID_PARAMETER; } this->connection_lock->read_lock(this->connection_lock); enumerator = this->connections->create_enumerator(this->connections); while (enumerator->enumerate(enumerator, &entry)) { if (id == entry->id) { recs = entry->recs; break; } } enumerator->destroy(enumerator); this->connection_lock->unlock(this->connection_lock); if (recs) { chunk_t attribute = { buffer, buffer_len }; if (attribute_id == TNC_ATTRIBUTEID_REASON_STRING) { return recs->set_reason_string(recs, imcv_id, attribute); } else { return recs->set_reason_language(recs, imcv_id, attribute); } } return TNC_RESULT_INVALID_PARAMETER; } METHOD(tnccs_manager_t, destroy, void, private_tnc_tnccs_manager_t *this) { this->protocols->destroy_function(this->protocols, free); this->protocol_lock->destroy(this->protocol_lock); this->connections->destroy_function(this->connections, free); this->connection_lock->destroy(this->connection_lock); free(this); } /* * See header */ tnccs_manager_t *tnc_tnccs_manager_create() { private_tnc_tnccs_manager_t *this; INIT(this, .public = { .add_method = _add_method, .remove_method = _remove_method, .create_instance = _create_instance, .create_connection = _create_connection, .remove_connection = _remove_connection, .request_handshake_retry = _request_handshake_retry, .send_message = _send_message, .provide_recommendation = _provide_recommendation, .get_attribute = _get_attribute, .set_attribute = _set_attribute, .destroy = _destroy, }, .protocols = linked_list_create(), .connections = linked_list_create(), .protocol_lock = rwlock_create(RWLOCK_TYPE_DEFAULT), .connection_lock = rwlock_create(RWLOCK_TYPE_DEFAULT), ); return &this->public; }