From: Martin Willi Date: Tue, 4 Sep 2012 09:45:46 +0000 (+0200) Subject: EAP-MS-SOH WIP X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8da070d37c37ea5688c6058a1972fd61fb1fa644;p=thirdparty%2Fstrongswan.git EAP-MS-SOH WIP --- diff --git a/src/libcharon/plugins/eap_ms_soh/eap_ms_soh.c b/src/libcharon/plugins/eap_ms_soh/eap_ms_soh.c index 4cc249785b..4cb628eeda 100644 --- a/src/libcharon/plugins/eap_ms_soh/eap_ms_soh.c +++ b/src/libcharon/plugins/eap_ms_soh/eap_ms_soh.c @@ -17,8 +17,128 @@ #include #include +#include +#include typedef struct private_eap_ms_soh_t private_eap_ms_soh_t; +typedef enum soh_tlv_t soh_tlv_t; +typedef enum soh_health_class_t soh_health_class_t; +typedef enum soh_attribute_t soh_attribute_t; +typedef enum soh_format_t soh_format_t; +typedef enum soh_status_t soh_status_t; +typedef enum soh_update_policy_t soh_update_policy_t; +typedef enum soh_procarch_t soh_procarch_t; +typedef enum soh_type_t soh_type_t; + +/** + * SoH TLV types + */ +enum soh_tlv_t { + SOH_SYSTEM_HEALTH_ID = 2, + SOH_IPV4_FIXUP_SERVERS = 3, + SOH_COMPLIANCE_RESULT_CODES = 4, + SOH_TIME_OF_LAST_UPDATE = 5, + SOH_CLIENT_ID = 6, + SOH_VENDOR = 7, + SOH_HEALTH_CLASS = 8, + SOH_SOFTWARE_VERSION = 9, + SOH_PRODUCT_NAME = 10, + SOH_HEALTH_CLASS_STATUS = 11, + SOH_GENERATION_TIME = 12, + SOH_ERROR_CODES = 13, + SOH_FAILURE_CATEGORY = 14, + SOH_IPV6_FIXUP_SERVERS = 15, +}; + +/** + * SoH component classes + */ +enum soh_health_class_t { + SOH_FIREWALL = 0, + SOH_ANTIVIRUS = 1, + SOH_ANTISPYWARE = 2, + SOH_AUTOMATIC_UPDATES = 3, + SOH_SECURITY_UPDATES = 4, +}; + +/** + * SSoH attributes + */ +enum soh_attribute_t { + MS_MACHINE_INVENTORY = 1, + MS_QUARANTINE_STATE = 2, + MS_PACKET_INFO = 3, + MS_SYSTEMGENERATED_IDS = 4, + MS_MACHINENAME = 5, + MS_CORRELATIONID = 6, + MS_INSTALLED_SHVS = 7, + MS_MACHINE_INVENTORY_EX = 8, +}; + +/** + * SoH packet format + */ +enum soh_format_t { + /* no sub header */ + SOH_FORMAT_DEFAULT = 1, + /* with sub header */ + SOH_FORMAT_SUB_HEADER = 2, +}; + +/** + * Health status for the different classes + */ +enum soh_status_t { + /* updates applied, success */ + SOH_NO_MISSING_UPDATES = 0x00FF0005, + /* different error codes */ + SOH_MISSING_UPDATES = 0x00FF0006, + SOH_WUA_SERVICE_NOT_STARTED_SINCE_BOOT = 0x00FF0008, + SOH_PRODUCT_NOT_INSTALLED = 0xC0FF0002, + SOH_WSC_SERVICE_DOWN = 0xC0FF0003, + SOH_NO_WUS_SERVER = 0xC0FF000C, + SOH_NO_CLIENT_ID = 0xC0FF000D, + SOH_WUA_SERVICE_DISABLED = 0xC0FF000E, + SOH_WUA_COMM_FAILURE = 0xC0FF000F, + SOH_UPDATES_INSTALLED_REQUIRE_REBOOT = 0xC0FF0010, + SOH_WSC_SERVICE_NOT_STARTED_SINCE_BOOT = 0xC0FF0018, + + /* bitmask of all flags, apply only if other bits not set */ + SOH_FLAGS = 0x0F, + SOH_ENABLED = (1<<0), + SOH_UP_TO_DATE = (1<<1), + SOH_MICROSOFT = (1<<2), + SOH_SNOOZED = (1<<3), +}; + +/** + * Windows Update policy + */ +enum soh_update_policy_t { + SOH_UPDATES_DISABLED = 1, + SOH_UPDATES_CHECK_ONLY = 2, + SOH_UPDATES_DOWNLOAD_ONLY = 3, + SOH_UPDATES_INSTALL = 4, + SOH_UPDATES_NOT_CONFIGURED = 5, + /* flag indicating that it has been set "by policy", whatever that means */ + SOH_UPDATES_BY_POLICY = 0x100, +}; + +/** + * Processor architecture + */ +enum soh_procarch_t { + SOH_PROCARCH_X86 = 0, + SOH_PROCARCH_IA64 = 6, + SOH_PROCARCH_X64 = 9, +}; +/** + * Vendor specific types in SOH_VENDOR for PEN_MICROSOFT + */ +enum soh_type_t { + SOH = 1, + SOH_REQUEST = 2, +}; /** * Private data of an eap_ms_soh_t object. @@ -34,18 +154,706 @@ struct private_eap_ms_soh_t { * Current EAP packet identifier */ u_int8_t identifier; + + /** + * Running Firewall? + */ + bool firewall; + + /** + * Running and up to date Antivirus + */ + bool antivirus; + + /** + * Running and up to date Antispyware + */ + bool antispyware; + + /** + * Automatic Update policy + */ + soh_update_policy_t update; + + /** + * All critical security updates applied? + */ + bool security_updates; }; +ENUM(soh_tlv_names, SOH_SYSTEM_HEALTH_ID, SOH_IPV6_FIXUP_SERVERS, + "System-Health-Id", + "IPv4-Fixup-Servers", + "Compliance-Result-Codes", + "Time-Of-Last-Update", + "Client-Id", + "Vendor", + "Health-Class", + "Software-Version", + "Product-Name", + "Health-Class-Status", + "Generation-Time", + "Error-Codes", + "Failure-Category", + "IPv6-Fixup-Servers", +); + +ENUM(soh_health_class_names, SOH_FIREWALL, SOH_SECURITY_UPDATES, + "Firewall", + "Antivirus", + "Antispyware", + "Automatic Updates", + "Security Updates", +); + + +ENUM(soh_attribute_names, MS_MACHINE_INVENTORY, MS_MACHINE_INVENTORY_EX, + "MS-Machine-Inventory", + "MS-Quarantine-State", + "MS-Packet-Info", + "MS-SystemGenerated-Ids", + "MS-MachineName", + "MS-CorrelationId", + "MS-Installed-Shvs", + "MS-Machine-Inventory-Ex", +); + +ENUM_BEGIN(soh_status_names, + SOH_NO_MISSING_UPDATES, + SOH_WUA_SERVICE_NOT_STARTED_SINCE_BOOT, + "no missing updates", + "7", + "missing updates", + "Windows Update not started since boot", +); +ENUM_NEXT(soh_status_names, + SOH_PRODUCT_NOT_INSTALLED, + SOH_WSC_SERVICE_DOWN, + SOH_WUA_SERVICE_NOT_STARTED_SINCE_BOOT, + "product not installed", + "Windows Security Center service down", +); +ENUM_NEXT(soh_status_names, + SOH_NO_WUS_SERVER, + SOH_UPDATES_INSTALLED_REQUIRE_REBOOT, + SOH_WSC_SERVICE_DOWN, + "no WSUS server", + "no WSUS client ID", + "Windows Update disabled", + "Windows Update communication failure", + "Updates installed, but reboot required", +); +ENUM_NEXT(soh_status_names, + SOH_WSC_SERVICE_NOT_STARTED_SINCE_BOOT, + SOH_WSC_SERVICE_NOT_STARTED_SINCE_BOOT, + SOH_UPDATES_INSTALLED_REQUIRE_REBOOT, + "Windows Security Center not started since boot", +); +ENUM_END(soh_status_names, SOH_WSC_SERVICE_NOT_STARTED_SINCE_BOOT); + +ENUM(soh_update_policy_names, + SOH_UPDATES_DISABLED, SOH_UPDATES_NOT_CONFIGURED, + "disabled", + "check only", + "download only", + "install automatically", + "not configured", +); + +ENUM_BEGIN(soh_procarch_names, SOH_PROCARCH_X86, SOH_PROCARCH_X86, + "x86", +); +ENUM_NEXT(soh_procarch_names, SOH_PROCARCH_IA64, SOH_PROCARCH_IA64, + SOH_PROCARCH_X86, + "IA64", +); +ENUM_NEXT(soh_procarch_names, SOH_PROCARCH_X64, SOH_PROCARCH_X64, + SOH_PROCARCH_IA64, + "x64", +); +ENUM_END(soh_procarch_names, SOH_PROCARCH_X64); + + METHOD(eap_method_t, initiate, status_t, private_eap_ms_soh_t *this, eap_payload_t **out) { - return FAILED; + bio_writer_t *writer; + + writer = bio_writer_create(32); + writer->write_uint8(writer, EAP_REQUEST); + writer->write_uint8(writer, this->identifier); + writer->write_uint16(writer, 24); + writer->write_uint8(writer, EAP_EXPANDED); + writer->write_uint24(writer, PEN_MICROSOFT); + writer->write_uint32(writer, EAP_MS_SOH); + writer->write_uint16(writer, SOH_VENDOR); + writer->write_uint16(writer, 8); + writer->write_uint32(writer, PEN_MICROSOFT); + writer->write_uint16(writer, SOH_REQUEST); + writer->write_uint16(writer, 0); + + *out = eap_payload_create_data(writer->get_buf(writer)); + writer->destroy(writer); + + return NEED_MORE; +} + +/** + * Minimalistic UTF-16 to ASCII conversion + */ +static void convert_utf16_to_ascii(chunk_t utf16, char *buf, size_t len) +{ + int pos = 0; + + while (utf16.len > 1 && pos < (len - 1)) + { + if (utf16.ptr[1] || utf16.ptr[0] > 127) + { + buf[pos] = '?'; + } + else + { + buf[pos] = utf16.ptr[0]; + } + utf16 = chunk_skip(utf16, 2); + pos++; + } + buf[pos] = 0; +} + +/** + * Evaluate and log a health class status + */ +static void evaluate_status(private_eap_ms_soh_t *this, soh_health_class_t class, + char *product, soh_status_t status) +{ + if (status & ~SOH_FLAGS) + { /* error/status code */ + DBG1(DBG_IKE, " %N: %s, %N", + soh_health_class_names, class, + status & SOH_MICROSOFT ? "builtin" : product, + soh_status_names, status); + if (class == SOH_SECURITY_UPDATES && + status == SOH_NO_MISSING_UPDATES) + { + this->security_updates = TRUE; + } + } + else + { + char *label = "disabled"; + bool running = FALSE; + + if (status & SOH_ENABLED) + { + if (status & SOH_SNOOZED) + { + label = "snoozed"; + } + else + { + label = "enabled"; + running = TRUE; + } + } + switch (class) + { + case SOH_AUTOMATIC_UPDATES: + this->update = status & ~SOH_UPDATES_BY_POLICY; + DBG1(DBG_IKE, " %N: %N", + soh_health_class_names, class, + soh_update_policy_names, this->update); + break; + case SOH_FIREWALL: + DBG1(DBG_IKE, " %N: %s, %s", + soh_health_class_names, class, + status & SOH_MICROSOFT ? "builtin" : product, label); + if (running) + { + this->firewall = TRUE; + } + break; + case SOH_ANTIVIRUS: + if (running) + { + this->antivirus = TRUE; + } + goto antilog; + case SOH_ANTISPYWARE: + if (running) + { + this->antispyware = TRUE; + } + goto antilog; + default: + antilog: + DBG1(DBG_IKE, " %N: %s, %s%s", + soh_health_class_names, class, + status & SOH_MICROSOFT ? "builtin" : product, label, + status & SOH_ENABLED ? + status & SOH_UP_TO_DATE ? + ", up-to-date" : ", outdated " + : ""); + break; + } + } +} + +/** + * Parse Microsoft SoH attributes + */ +static bool parse_soh_attributes(private_eap_ms_soh_t *this, + bio_reader_t *reader) +{ + u_int8_t type; + + while (reader->remaining(reader)) + { + if (!reader->read_uint8(reader, &type)) + { + return FALSE; + } + switch (type) + { + case MS_MACHINE_INVENTORY: + { + u_int32_t os_maj, os_min, os_build; + u_int16_t sp_maj, sp_min, arch; + + if (!reader->read_uint32(reader, &os_maj) || + !reader->read_uint32(reader, &os_min) || + !reader->read_uint32(reader, &os_build) || + !reader->read_uint16(reader, &sp_maj) || + !reader->read_uint16(reader, &sp_min) || + !reader->read_uint16(reader, &arch)) + { + return FALSE; + } + DBG1(DBG_IKE, " Windows Version: %d.%d.%d (SP %d.%d) on %N", + os_maj, os_min, os_build, sp_maj, sp_min, + soh_procarch_names, arch); + break; + } + case MS_PACKET_INFO: + { + u_int8_t ver; + + if (!reader->read_uint8(reader, &ver)) + { + return FALSE; + } + break; + } + case MS_MACHINENAME: + { + chunk_t name; + + if (!reader->read_data16(reader, &name)) + { + return FALSE; + } + DBG1(DBG_IKE, " Hostname: %.*s", (int)name.len, name.ptr); + break; + } + case MS_CORRELATIONID: + { + chunk_t id; + + if (!reader->read_data(reader, 24, &id)) + { + return FALSE; + } + break; + } + case MS_QUARANTINE_STATE: + { + u_int8_t qstate; + chunk_t prob, uri; + + if (!reader->read_uint8(reader, &qstate) || + !reader->read_uint8(reader, &qstate) || + !reader->read_data(reader, 8, &prob) || + !reader->read_data16(reader, &uri)) + { + return FALSE; + } + break; + } + case MS_MACHINE_INVENTORY_EX: + { + u_int32_t reserved; + u_int8_t product; + + if (!reader->read_uint32(reader, &reserved) || + !reader->read_uint8(reader, &product)) + { + return FALSE; + } + break; + } + case MS_SYSTEMGENERATED_IDS: + case MS_INSTALLED_SHVS: + { + chunk_t ids; + + if (!reader->read_data16(reader, &ids)) + { + return FALSE; + } + break; + } + default: + return FALSE; + } + } + return TRUE; +} + +/** + * Parse and skip SoH sub header, return FALSE to drop it silently + */ +static bool parse_sub_header(private_eap_ms_soh_t *this, bio_reader_t *reader) +{ + chunk_t id; + u_int8_t intent, content; + + if (!reader->read_data(reader, 24, &id) || + !reader->read_uint8(reader, &intent) || + !reader->read_uint8(reader, &content) || + content != 0) + { + return FALSE; + } + return TRUE; +} + +/** + * Parse Microsoft Statement of Health batch data + */ +static bool parse_soh_batch(private_eap_ms_soh_t *this, bio_reader_t *reader, + bool sub_header) +{ + bio_reader_t *inner; + u_int32_t vendor, status; + u_int16_t type, length; + u_int8_t system_health_id = 0, health_class = 0; + char product[256] = ""; + chunk_t data; + + DBG1(DBG_IKE, "Windows Statement of Health:"); + + /* contains: + * - one SoH Mode Sub-Header, if SOH_FORMAT_SUB_HEADER + * - one SSoH TLV, consisting of: + * - a System Health-ID attribute + * - a vendor specific attribute (MS, actually), containing: + * - a fixed set of MS attributes + * - zero or more SoHReportEntry TLV + */ + while (reader->remaining(reader)) + { + if (!reader->read_uint16(reader, &type) || + !reader->read_uint16(reader, &length)) + { + return FALSE; + } + switch (type) + { + case SOH_VENDOR: + if (length < sizeof(vendor) || + !reader->read_uint32(reader, &vendor) || + !reader->read_data(reader, length - sizeof(vendor), &data)) + { + return FALSE; + } + switch (vendor) + { + case PEN_MICROSOFT: + if (sub_header) + { + inner = bio_reader_create(data); + if (!parse_sub_header(this, inner)) + { + /* we SHOULD ignore this message */ + inner->destroy(inner); + return TRUE; + } + inner->destroy(inner); + /* only one */ + sub_header = FALSE; + break; + } + inner = bio_reader_create(data); + if (!parse_soh_attributes(this, inner)) + { + inner->destroy(inner); + return FALSE; + } + inner->destroy(inner); + break; + case 0x00013780: + /* "Flag" and "Version" fields use the "Microsoft magic + * vendor encoding". Both fields not very interesting. */ + break; + default: + DBG1(DBG_IKE, "SSOH Vendor %N TLV: %B", + pen_names, vendor, &data); + break; + } + break; + case SOH_SYSTEM_HEALTH_ID: + if (!reader->read_uint24(reader, &vendor) || + !reader->read_uint8(reader, &system_health_id)) + { + return FALSE; + } + break; + case SOH_HEALTH_CLASS: + if (!reader->read_uint8(reader, &health_class)) + { + return FALSE; + } + product[0] = 0; + break; + case SOH_PRODUCT_NAME: + if (!reader->read_data(reader, length, &data)) + { + return FALSE; + } + convert_utf16_to_ascii(data, product, sizeof(product)); + break; + case SOH_HEALTH_CLASS_STATUS: + if (!reader->read_uint32(reader, &status)) + { + return FALSE; + } + evaluate_status(this, health_class, product, status); + break; + default: + if (!reader->read_data(reader, length, &data)) + { + return FALSE; + } + DBG1(DBG_IKE, "%N: %B", soh_tlv_names, type, &data); + break; + } + } + return TRUE; +} + +/** + * Parse Statement of Health + */ +static bool parse_soh(private_eap_ms_soh_t *this, bio_reader_t *reader) +{ + bio_reader_t *inner; + u_int32_t vendor; + u_int16_t type, length, inner_type; + chunk_t data; + + while (reader->remaining(reader)) + { + bool sub_header = TRUE; + + if (!reader->read_uint16(reader, &type) || + !reader->read_uint16(reader, &length)) + { + return FALSE; + } + switch (type) + { + case SOH_VENDOR: + if (!reader->read_uint32(reader, &vendor)) + { + return FALSE; + } + switch (vendor) + { + case PEN_MICROSOFT: + if (!reader->read_uint16(reader, &inner_type) || + !reader->read_data16(reader, &data)) + { + return FALSE; + } + switch (inner_type) + { + case SOH_FORMAT_DEFAULT: + sub_header = FALSE; + /* fall */ + case SOH_FORMAT_SUB_HEADER: + inner = bio_reader_create(data); + if (!parse_soh_batch(this, inner, sub_header)) + { + inner->destroy(inner); + return FALSE; + } + inner->destroy(inner); + break; + default: + DBG1(DBG_IKE, "ignoring unknown SoH format"); + break; + } + break; + default: + DBG1(DBG_IKE, "ignoring %N Vendor SoH TLV", + pen_names, vendor); + if (!reader->read_data(reader, length, &data)) + { + return FALSE; + } + break; + } + break; + default: + DBG1(DBG_IKE, "ignoring SoH TLV %d", type); + if (!reader->read_data(reader, length, &data)) + { + return FALSE; + } + break; + } + } + return TRUE; +} + +/** + * Parse Microsoft vendor TLV + */ +static bool parse_ms_tlvs(private_eap_ms_soh_t *this, bio_reader_t *reader) +{ + bio_reader_t *inner; + u_int16_t type; + chunk_t data; + + while (reader->remaining(reader)) + { + if (!reader->read_uint16(reader, &type) || + !reader->read_data16(reader, &data)) + { + return FALSE; + } + switch (type) + { + case SOH: + inner = bio_reader_create(data); + if (!parse_soh(this, inner)) + { + inner->destroy(inner); + return FALSE; + } + inner->destroy(inner); + break; + default: + DBG1(DBG_IKE, "ignoring MS TLV %d", type); + break; + } + } + return TRUE; +} + +/** + * Parse contained TLV types + */ +static bool parse_tlv(private_eap_ms_soh_t *this, bio_reader_t *reader) +{ + bio_reader_t *inner; + u_int16_t type, length; + u_int32_t vendor; + chunk_t data; + + if (!reader->read_uint16(reader, &type) || + !reader->read_uint16(reader, &length)) + { + return FALSE; + } + switch (type) + { + case SOH_VENDOR: + if (length < sizeof(vendor) || + !reader->read_uint32(reader, &vendor) || + !reader->read_data(reader, length - sizeof(vendor), &data)) + { + return FALSE; + } + switch (vendor) + { + case PEN_MICROSOFT: + inner = bio_reader_create(data); + if (!parse_ms_tlvs(this, inner)) + { + inner->destroy(inner); + return FALSE; + } + inner->destroy(inner); + break; + default: + DBG1(DBG_IKE, "ignoring EAP-SOH %N TLV", + pen_names, vendor); + break; + } + break; + default: + if (!reader->read_data(reader, length, &data)) + { + return FALSE; + } + break; + } + return TRUE; +} + +/** + * Check if the client meets the Health requirements + */ +static bool check_compliance(private_eap_ms_soh_t *this) +{ + /* having update checks enabled sufficient, no Antispyware required */ + return this->firewall && this->antivirus && + this->update >= SOH_UPDATES_CHECK_ONLY && + this->security_updates; } METHOD(eap_method_t, process, status_t, private_eap_ms_soh_t *this, eap_payload_t *in, eap_payload_t **out) { - return FAILED; + bio_reader_t *reader; + u_int8_t code, identifier, type; + u_int16_t length; + u_int32_t vendor, vendor_type; + + reader = bio_reader_create(in->get_data(in)); + + if (!reader->read_uint8(reader, &code) || + !reader->read_uint8(reader, &identifier) || + !reader->read_uint16(reader, &length) || + !reader->read_uint8(reader, &type) || + !reader->read_uint24(reader, &vendor) || + !reader->read_uint32(reader, &vendor_type) || + code != EAP_RESPONSE || type != EAP_EXPANDED || + vendor != PEN_MICROSOFT || vendor_type != EAP_MS_SOH) + { + DBG1(DBG_IKE, "received invalid EAP-MS-SOH message"); + reader->destroy(reader); + return FAILED; + } + + while (reader->remaining(reader)) + { + if (!parse_tlv(this, reader)) + { + reader->destroy(reader); + DBG1(DBG_IKE, "parsing EAP-MS-SOH message failed!"); + return FAILED; + } + } + reader->destroy(reader); + + if (!check_compliance(this)) + { + DBG1(DBG_IKE, "client health requirements not met"); + return FAILED; + } + return SUCCESS; } METHOD(eap_method_t, get_type, eap_type_t, @@ -105,6 +913,7 @@ static private_eap_ms_soh_t *create_empty() .destroy = _destroy, }, }, + .update = SOH_UPDATES_NOT_CONFIGURED, ); return this; @@ -113,20 +922,11 @@ static private_eap_ms_soh_t *create_empty() eap_ms_soh_t *eap_ms_soh_create_server(identification_t *server, identification_t *peer) { - private_eap_ms_soh_t *this; - - this = create_empty(); - - /* generate a non-zero identifier */ - do { - this->identifier = random(); - } while (!this->identifier); - - return &this->public; + return &create_empty()->public; } eap_ms_soh_t *eap_ms_soh_create_peer(identification_t *server, - identification_t *peer) + identification_t *peer) { return &create_empty()->public; }