>From b0d2fe37ca86bbb476efd12bd4bf0cf2719a55dc Mon Sep 17 00:00:00 2001
From: Phil Mayers <p.mayers@imperial.ac.uk>
Date: Mon, 11 Oct 2010 14:35:35 +0100
Subject: [PATCH 2/4] move soh code into server, so we can see REQUEST/RDEBUG

---
 src/lib/soh.c  |  579 --------------------------------------------------------
 src/main/soh.c |  579 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 579 insertions(+), 579 deletions(-)
 delete mode 100644 src/lib/soh.c
 create mode 100644 src/main/soh.c

diff --git a/src/lib/soh.c b/src/lib/soh.c
deleted file mode 100644
index 3310c36..0000000
--- a/src/lib/soh.c
+++ /dev/null
@@ -1,579 +0,0 @@
-/*
- * soh.c contains the interfaces that are called from eap
- *
- * Version:     $Id$
- *
- *   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.
- *
- *   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.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- *
- *   Copyright 2010 Phil Mayers <p.mayers@imperial.ac.uk>
- */
-
-#include <freeradius-devel/ident.h>
-RCSID("$Id$")
-
-#include <freeradius-devel/soh.h>
-
-/*
- * This code implements parsing of MS-SOH data into FreeRadius AVPs
- * allowing for FreeRadius MS-NAP policies
- */
-
-typedef struct {
-	uint16_t tlv_type;	/* ==7 */
-	uint16_t tlv_len;
-	uint32_t tlv_vendor;
-
-	/* then it's either an soh request or response */
-	uint16_t soh_type;	/* ==2 for request, 1 for response */
-	uint16_t soh_len;
-
-	/* an soh-response may now follow... */
-} eap_soh;
-
-typedef struct {
-	uint16_t outer_type;
-	uint16_t outer_len;
-	uint32_t vendor;
-	uint16_t inner_type;
-	uint16_t inner_len;
-} soh_response;
-
-typedef struct {
-	uint16_t outer_type;
-	uint16_t outer_len;
-	uint32_t vendor;
-	uint8_t corrid[24];
-	uint8_t intent;
-	uint8_t content_type;
-} soh_mode_subheader;
-
-typedef struct {
-	uint16_t tlv_type;
-	uint16_t tlv_len;
-} soh_tlv;
-
-/* utils for pulling big-endian 2/3/4 byte integers
- * caller must ensure enough data exists at "p"
- */
-uint16_t soh_pull_be_16(const uint8_t *p) {
-	uint16_t r;
-
-	r = *p++ << 8;
-	r += *p++;
-
-	return r;
-}
-uint32_t soh_pull_be_24(const uint8_t *p) {
-	uint32_t r;
-
-	r = *p++ << 16;
-	r += *p++ << 8;
-	r += *p++;
-
-	return r;
-}
-uint32_t soh_pull_be_32(const uint8_t *p) {
-	uint32_t r;
-
-	r = *p++ << 24;
-	r += *p++ << 16;
-	r += *p++ << 8;
-	r += *p++;
-
-	return r;
-}
-
-/*
- * This parses the microsoft type/value (note: NOT type/length/value) data; see
- * section 2.2.4 of MS-SOH. Because there's no "length" field we CANNOT just skip
- * unknown types; we need to know their length ahead of time. Therefore, we abort
- * if we find an unknown type.
- */
-static int eapsoh_mstlv(VALUE_PAIR *sohvp, const uint8_t *p, unsigned int data_len) {
-	VALUE_PAIR *vp;
-	uint8_t c;
-	int t;
-
-	while (data_len > 0) {
-		c = *p++;
-		data_len--;
-
-		switch (c) {
-			case 1:
-				/* MS-Machine-Inventory-Packet
-				 * MS-SOH section 2.2.4.1
-				 */
-				if (data_len < 18) {
-					DEBUG("insufficient data for MS-Machine-Inventory-Packet\n");
-					return 0;
-				}
-				data_len -= 18;
-
-				vp = pairmake("SoH-MS-Machine-OS-vendor", "Microsoft", T_OP_EQ);
-				pairadd(&sohvp, vp);
-
-				vp = pairmake("SoH-MS-Machine-OS-version", NULL, T_OP_EQ);
-				vp->vp_integer = soh_pull_be_32(p); p+=4;
-				pairadd(&sohvp, vp);
-
-				vp = pairmake("SoH-MS-Machine-OS-release", NULL, T_OP_EQ);
-				vp->vp_integer = soh_pull_be_32(p); p+=4;
-				pairadd(&sohvp, vp);
-
-				vp = pairmake("SoH-MS-Machine-OS-build", NULL, T_OP_EQ);
-				vp->vp_integer = soh_pull_be_32(p); p+=4;
-				pairadd(&sohvp, vp);
-
-				vp = pairmake("SoH-MS-Machine-SP-version", NULL, T_OP_EQ);
-				vp->vp_integer = soh_pull_be_16(p); p+=2;
-				pairadd(&sohvp, vp);
-
-				vp = pairmake("SoH-MS-Machine-SP-release", NULL, T_OP_EQ);
-				vp->vp_integer = soh_pull_be_16(p); p+=2;
-				pairadd(&sohvp, vp);
-
-				vp = pairmake("SoH-MS-Machine-Processor", NULL, T_OP_EQ);
-				vp->vp_integer = soh_pull_be_16(p); p+=2;
-				pairadd(&sohvp, vp);
-
-				break;
-
-			case 2:
-				/* MS-Quarantine-State - FIXME: currently unhandled
-				 * MS-SOH 2.2.4.1
-				 *
-				 * 1 byte reserved
-				 * 1 byte flags
-				 * 8 bytes NT Time field (100-nanosec since 1 Jan 1601)
-				 * 2 byte urilen
-				 * N bytes uri
-				 */
-				p += 10;
-				t = soh_pull_be_16(p);	/* t == uri len */
-				p += 2;
-				p += t;
-				data_len -= 12 + t;
-				break;
-
-			case 3:
-				/* MS-Packet-Info
-				 * MS-SOH 2.2.4.3
-				 */
-				DEBUG("SoH MS-Packet-Info %s vers=%i\n", *p & 0x10 ? "request" : "response", *p & 0xf);
-				p++;
-				data_len--;
-				break;
-
-			case 4:
-				/* MS-SystemGenerated-Ids - FIXME: currently unhandled
-				 * MS-SOH 2.2.4.4
-				 *
-				 * 2 byte length
-				 * N bytes (3 bytes IANA enterprise# + 1 byte component id#)
-				 */
-				t = soh_pull_be_16(p);
-				p += 2;
-				p += t;
-				data_len -= 2 + t;
-				break;
-
-			case 5:
-				/* MS-MachineName
-				 * MS-SOH 2.2.4.5
-				 *
-				 * 1 byte namelen
-				 * N bytes name
-				 */
-				t = soh_pull_be_16(p);
-				p += 2;
-
-				vp = pairmake("SoH-MS-Machine-Name", NULL, T_OP_EQ);
-				memcpy(vp->vp_strvalue, p, t);
-				vp->vp_strvalue[t] = 0;
-
-				pairadd(&sohvp, vp);
-				p += t;
-				data_len -= 2 + t;
-				break;
-
-			case 6:
-				/* MS-CorrelationId
-				 * MS-SOH 2.2.4.6
-				 *
-				 * 24 bytes opaque binary which we might, in future, have
-				 * to echo back to the client in a final SoHR
-				 */
-				vp = pairmake("SoH-MS-Correlation-Id", NULL, T_OP_EQ);
-				memcpy(vp->vp_octets, p, 24);
-				vp->length = 24;
-				pairadd(&sohvp, vp);
-				p += 24;
-				data_len -= 24;
-				break;
-
-			case 7:
-				/* MS-Installed-Shvs - FIXME: currently unhandled
-				 * MS-SOH 2.2.4.7
-				 *
-				 * 2 bytes length
-				 * N bytes (3 bytes IANA enterprise# + 1 byte component id#)
-				 */
-				t = soh_pull_be_16(p);
-				p += 2;
-				p += t;
-				data_len -= 2 + t;
-				break;
-
-			case 8:
-				/* MS-Machine-Inventory-Ex
-				 * MS-SOH 2.2.4.8
-				 *
-				 * 4 bytes reserved
-				 * 1 byte product type (client=1 domain_controller=2 server=3)
-				 */
-				p += 4;
-				vp = pairmake("SoH-MS-Machine-Role", NULL, T_OP_EQ);
-				vp->vp_integer = *p;
-				pairadd(&sohvp, vp);
-				p++;
-				data_len -= 5;
-				break;
-
-			default:
-				DEBUG("SoH Unknown MS TV %i stopping\n", c);
-				return 0;
-		}
-	}
-	return 1;
-}
-
-static const char* clientstatus2str(uint32_t hcstatus) {
-	switch (hcstatus) {
-		/* this lot should all just be for windows updates */
-		case 0xff0005:
-			return "wua-ok";
-		case 0xff0006:
-			return "wua-missing";
-		case 0xff0008:
-			return "wua-not-started";
-		case 0xc0ff000c:
-			return "wua-no-wsus-server";
-		case 0xc0ff000d:
-			return "wua-no-wsus-clientid";
-		case 0xc0ff000e:
-			return "wua-disabled";
-		case 0xc0ff000f:
-			return "wua-comm-failure";
-
-		/* these next 3 are for all health-classes */
-		case 0xc0ff0002:
-			return "not-installed";
-		case 0xc0ff0003:
-			return "down";
-		case 0xc0ff0018:
-			return "not-started";
-	}
-	return NULL;
-}
-
-static const char* healthclass2str(uint8_t hc) {
-	switch (hc) {
-		case 0:
-			return "firewall";
-		case 1:
-			return "antivirus";
-		case 2:
-			return "antispyware";
-		case 3:
-			return "updates";
-		case 4:
-			return "security-updates";
-	}
-	return NULL;
-}
-
-int soh_verify(VALUE_PAIR *sohvp, const uint8_t *data, unsigned int data_len) {
-
-	VALUE_PAIR *vp;
-	eap_soh hdr;
-	soh_response resp;
-	soh_mode_subheader mode;
-	soh_tlv tlv;
-	int curr_shid=-1, curr_shid_c=-1, curr_hc=-1;
-
-	hdr.tlv_type = soh_pull_be_16(data); data += 2;
-	hdr.tlv_len = soh_pull_be_16(data); data += 2;
-	hdr.tlv_vendor = soh_pull_be_32(data); data += 4;
-
-	if (hdr.tlv_type != 7 || hdr.tlv_vendor != 0x137) {
-		DEBUG("SoH payload is %i %08x not a ms-vendor packet\n", hdr.tlv_type, hdr.tlv_vendor);
-		return -1;
-	}
-
-	hdr.soh_type = soh_pull_be_16(data); data += 2;
-	hdr.soh_len = soh_pull_be_16(data); data += 2;
-	if (hdr.soh_type != 1) {
-		DEBUG("SoH tlv %04x is not a response\n", hdr.soh_type);
-		return -1;
-	}
-
-	/* FIXME: check for sufficient data */
-	resp.outer_type = soh_pull_be_16(data); data += 2;
-	resp.outer_len = soh_pull_be_16(data); data += 2;
-	resp.vendor = soh_pull_be_32(data); data += 4;
-	resp.inner_type = soh_pull_be_16(data); data += 2;
-	resp.inner_len = soh_pull_be_16(data); data += 2;
-
-
-	if (resp.outer_type!=7 || resp.vendor != 0x137) {
-		DEBUG("SoH response outer type %i/vendor %08x not recognised\n", resp.outer_type, resp.vendor);
-		return -1;
-	}
-	switch (resp.inner_type) {
-		case 1:
-			/* no mode sub-header */
-			DEBUG("SoH without mode subheader\n");
-			break;
-		case 2:
-			mode.outer_type = soh_pull_be_16(data); data += 2;
-			mode.outer_len = soh_pull_be_16(data); data += 2;
-			mode.vendor = soh_pull_be_32(data); data += 4;
-			memcpy(mode.corrid, data, 24); data += 24;
-			mode.intent = data[0];
-			mode.content_type = data[1];
-			data += 2;
-
-			if (mode.outer_type != 7 || mode.vendor != 0x137 || mode.content_type != 0) {
-				DEBUG("SoH mode subheader outer type %i/vendor %08x/content type %i invalid\n", mode.outer_type, mode.vendor, mode.content_type);
-				return -1;
-			}
-			DEBUG("SoH with mode subheader\n");
-			break;
-		default:
-			DEBUG("SoH invalid inner type %i\n", resp.inner_type);
-			return -1;
-	}
-
-	/* subtract off the relevant amount of data */
-	if (resp.inner_type==2) {
-		data_len = resp.inner_len - 34;
-	} else {
-		data_len = resp.inner_len;
-	}
-
-	/* TLV
-	 * MS-SOH 2.2.1
-	 * See also 2.2.3
-	 *
-	 * 1 bit mandatory
-	 * 1 bit reserved
-	 * 14 bits tlv type
-	 * 2 bytes tlv length
-	 * N bytes payload
-	 *
-	 */
-	while (data_len >= 4) {
-		tlv.tlv_type = soh_pull_be_16(data); data += 2;
-		tlv.tlv_len = soh_pull_be_16(data); data += 2;
-
-		data_len -= 4;
-
-		switch (tlv.tlv_type) {
-			case 2:
-				/* System-Health-Id TLV
-				 * MS-SOH 2.2.3.1
-				 *
-				 * 3 bytes IANA/SMI vendor code
-				 * 1 byte component (i.e. within vendor, which SoH component
-				 */
-				curr_shid = soh_pull_be_24(data);
-				curr_shid_c = data[3];
-				DEBUG("SoH System-Health-ID vendor %08x component=%i\n", curr_shid, curr_shid_c);
-				break;
-
-			case 7:
-				/* Vendor-Specific packet
-				 * MS-SOH 2.2.3.3
-				 *
-				 * 4 bytes vendor, supposedly ignored by NAP
-				 * N bytes payload; for Microsoft component#0 this is the MS TV stuff
-				 */
-				if (curr_shid==0x137 && curr_shid_c==0) {
-					DEBUG("SoH MS type-value payload\n");
-					eapsoh_mstlv(sohvp, data + 4, tlv.tlv_len - 4);
-				} else {
-					DEBUG("SoH unhandled vendor-specific TLV %08x/component=%i %i bytes payload\n", curr_shid, curr_shid_c, tlv.tlv_len);
-				}
-				break;
-
-			case 8:
-				/* Health-Class
-				 * MS-SOH 2.2.3.5.6
-				 *
-				 * 1 byte integer
-				 */
-				DEBUG("SoH Health-Class %i\n", data[0]);
-				curr_hc = data[0];
-				break;
-
-			case 9:
-				/* Software-Version
-				 * MS-SOH 2.2.3.5.7
-				 *
-				 * 1 byte integer
-				 */
-				DEBUG("SoH Software-Version %i\n", data[0]);
-				break;
-
-			case 11:
-				/* Health-Class status
-				 * MS-SOH 2.2.3.5.9
-				 *
-				 * variable data; for the MS System Health vendor, these are 4-byte
-				 * integers which are a really, really dumb format:
-				 *
-				 *  28 bits ignore
-				 *  1 bit - 1==product snoozed
-				 *  1 bit - 1==microsoft product
-				 *  1 bit - 1==product up-to-date
-				 *  1 bit - 1==product enabled
-				 */
-				DEBUG("SoH Health-Class-Status - current shid=%08x component=%i\n", curr_shid, curr_shid_c);
-
-				if (curr_shid==0x137 && curr_shid_c==128) {
-
-					const char *s, *t;
-					uint32_t hcstatus = soh_pull_be_32(data);
-
-					DEBUG("SoH Health-Class-Status microsoft DWORD=%08x\n", hcstatus);
-
-					vp = pairmake("SoH-MS-Windows-Health-Status", NULL, T_OP_EQ);
-					switch (curr_hc) {
-						case 4:
-							/* security updates */
-							s = "security-updates";
-							switch (hcstatus) {
-								case 0xff0005:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok all-installed", s);
-									break;
-								case 0xff0006:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn some-missing", s);
-									break;
-								case 0xff0008:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn never-started", s);
-									break;
-								case 0xc0ff000c:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error no-wsus-srv", s);
-									break;
-								case 0xc0ff000d:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error no-wsus-clid", s);
-									break;
-								case 0xc0ff000e:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn wsus-disabled", s);
-									break;
-								case 0xc0ff000f:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error comm-failure", s);
-									break;
-								case 0xc0ff0010:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn needs-reboot", s);
-									break;
-								default:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
-									break;
-							}
-							break;
-
-						case 3:
-							/* auto updates */
-							s = "auto-updates";
-							switch (hcstatus) {
-								case 1:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn disabled", s);
-									break;
-								case 2:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=check-only", s);
-									break;
-								case 3:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=download", s);
-									break;
-								case 4:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=install", s);
-									break;
-								case 5:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn unconfigured", s);
-									break;
-								case 0xc0ff0003:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn service-down", s);
-									break;
-								case 0xc0ff0018:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn never-started", s);
-									break;
-								default:
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
-									break;
-							}
-							break;
-
-						default:
-							/* other - firewall, antivirus, antispyware */
-							s = healthclass2str(curr_hc);
-							if (s) {
-								/* bah. this is vile. stupid microsoft
-								 */
-								if (hcstatus & 0xff000000) {
-									/* top octet non-zero means an error
-									 * FIXME: is this always correct? MS-WSH 2.2.8 is unclear
-									 */
-									t = clientstatus2str(hcstatus);
-									if (t) {
-										snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %s", s, t);
-									} else {
-										snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
-									}
-								} else {
-									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue),
-											"%s ok snoozed=%i microsoft=%i up2date=%i enabled=%i",
-											s,
-											hcstatus & 0x8 ? 1 : 0,
-											hcstatus & 0x4 ? 1 : 0,
-											hcstatus & 0x2 ? 1 : 0,
-											hcstatus & 0x1 ? 1 : 0
-											);
-								}
-							} else {
-								snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%i unknown %08x", curr_hc, hcstatus);
-							}
-							break;
-					}
-				} else {
-					vp = pairmake("SoH-Other-Health-Status", NULL, T_OP_EQ);
-					/* FIXME: what to do with the payload? */
-					snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%08x/%i ?", curr_shid, curr_shid_c);
-				}
-				pairadd(&sohvp, vp);
-				break;
-
-			default:
-				DEBUG("SoH Unknown TLV %i len=%i\n", tlv.tlv_type, tlv.tlv_len);
-				break;
-		}
-
-		data += tlv.tlv_len;
-		data_len -= tlv.tlv_len;
-
-	}
-
-	return 0;
-}
diff --git a/src/main/soh.c b/src/main/soh.c
new file mode 100644
index 0000000..3310c36
--- /dev/null
+++ b/src/main/soh.c
@@ -0,0 +1,579 @@
+/*
+ * soh.c contains the interfaces that are called from eap
+ *
+ * Version:     $Id$
+ *
+ *   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.
+ *
+ *   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.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ *   Copyright 2010 Phil Mayers <p.mayers@imperial.ac.uk>
+ */
+
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
+
+#include <freeradius-devel/soh.h>
+
+/*
+ * This code implements parsing of MS-SOH data into FreeRadius AVPs
+ * allowing for FreeRadius MS-NAP policies
+ */
+
+typedef struct {
+	uint16_t tlv_type;	/* ==7 */
+	uint16_t tlv_len;
+	uint32_t tlv_vendor;
+
+	/* then it's either an soh request or response */
+	uint16_t soh_type;	/* ==2 for request, 1 for response */
+	uint16_t soh_len;
+
+	/* an soh-response may now follow... */
+} eap_soh;
+
+typedef struct {
+	uint16_t outer_type;
+	uint16_t outer_len;
+	uint32_t vendor;
+	uint16_t inner_type;
+	uint16_t inner_len;
+} soh_response;
+
+typedef struct {
+	uint16_t outer_type;
+	uint16_t outer_len;
+	uint32_t vendor;
+	uint8_t corrid[24];
+	uint8_t intent;
+	uint8_t content_type;
+} soh_mode_subheader;
+
+typedef struct {
+	uint16_t tlv_type;
+	uint16_t tlv_len;
+} soh_tlv;
+
+/* utils for pulling big-endian 2/3/4 byte integers
+ * caller must ensure enough data exists at "p"
+ */
+uint16_t soh_pull_be_16(const uint8_t *p) {
+	uint16_t r;
+
+	r = *p++ << 8;
+	r += *p++;
+
+	return r;
+}
+uint32_t soh_pull_be_24(const uint8_t *p) {
+	uint32_t r;
+
+	r = *p++ << 16;
+	r += *p++ << 8;
+	r += *p++;
+
+	return r;
+}
+uint32_t soh_pull_be_32(const uint8_t *p) {
+	uint32_t r;
+
+	r = *p++ << 24;
+	r += *p++ << 16;
+	r += *p++ << 8;
+	r += *p++;
+
+	return r;
+}
+
+/*
+ * This parses the microsoft type/value (note: NOT type/length/value) data; see
+ * section 2.2.4 of MS-SOH. Because there's no "length" field we CANNOT just skip
+ * unknown types; we need to know their length ahead of time. Therefore, we abort
+ * if we find an unknown type.
+ */
+static int eapsoh_mstlv(VALUE_PAIR *sohvp, const uint8_t *p, unsigned int data_len) {
+	VALUE_PAIR *vp;
+	uint8_t c;
+	int t;
+
+	while (data_len > 0) {
+		c = *p++;
+		data_len--;
+
+		switch (c) {
+			case 1:
+				/* MS-Machine-Inventory-Packet
+				 * MS-SOH section 2.2.4.1
+				 */
+				if (data_len < 18) {
+					DEBUG("insufficient data for MS-Machine-Inventory-Packet\n");
+					return 0;
+				}
+				data_len -= 18;
+
+				vp = pairmake("SoH-MS-Machine-OS-vendor", "Microsoft", T_OP_EQ);
+				pairadd(&sohvp, vp);
+
+				vp = pairmake("SoH-MS-Machine-OS-version", NULL, T_OP_EQ);
+				vp->vp_integer = soh_pull_be_32(p); p+=4;
+				pairadd(&sohvp, vp);
+
+				vp = pairmake("SoH-MS-Machine-OS-release", NULL, T_OP_EQ);
+				vp->vp_integer = soh_pull_be_32(p); p+=4;
+				pairadd(&sohvp, vp);
+
+				vp = pairmake("SoH-MS-Machine-OS-build", NULL, T_OP_EQ);
+				vp->vp_integer = soh_pull_be_32(p); p+=4;
+				pairadd(&sohvp, vp);
+
+				vp = pairmake("SoH-MS-Machine-SP-version", NULL, T_OP_EQ);
+				vp->vp_integer = soh_pull_be_16(p); p+=2;
+				pairadd(&sohvp, vp);
+
+				vp = pairmake("SoH-MS-Machine-SP-release", NULL, T_OP_EQ);
+				vp->vp_integer = soh_pull_be_16(p); p+=2;
+				pairadd(&sohvp, vp);
+
+				vp = pairmake("SoH-MS-Machine-Processor", NULL, T_OP_EQ);
+				vp->vp_integer = soh_pull_be_16(p); p+=2;
+				pairadd(&sohvp, vp);
+
+				break;
+
+			case 2:
+				/* MS-Quarantine-State - FIXME: currently unhandled
+				 * MS-SOH 2.2.4.1
+				 *
+				 * 1 byte reserved
+				 * 1 byte flags
+				 * 8 bytes NT Time field (100-nanosec since 1 Jan 1601)
+				 * 2 byte urilen
+				 * N bytes uri
+				 */
+				p += 10;
+				t = soh_pull_be_16(p);	/* t == uri len */
+				p += 2;
+				p += t;
+				data_len -= 12 + t;
+				break;
+
+			case 3:
+				/* MS-Packet-Info
+				 * MS-SOH 2.2.4.3
+				 */
+				DEBUG("SoH MS-Packet-Info %s vers=%i\n", *p & 0x10 ? "request" : "response", *p & 0xf);
+				p++;
+				data_len--;
+				break;
+
+			case 4:
+				/* MS-SystemGenerated-Ids - FIXME: currently unhandled
+				 * MS-SOH 2.2.4.4
+				 *
+				 * 2 byte length
+				 * N bytes (3 bytes IANA enterprise# + 1 byte component id#)
+				 */
+				t = soh_pull_be_16(p);
+				p += 2;
+				p += t;
+				data_len -= 2 + t;
+				break;
+
+			case 5:
+				/* MS-MachineName
+				 * MS-SOH 2.2.4.5
+				 *
+				 * 1 byte namelen
+				 * N bytes name
+				 */
+				t = soh_pull_be_16(p);
+				p += 2;
+
+				vp = pairmake("SoH-MS-Machine-Name", NULL, T_OP_EQ);
+				memcpy(vp->vp_strvalue, p, t);
+				vp->vp_strvalue[t] = 0;
+
+				pairadd(&sohvp, vp);
+				p += t;
+				data_len -= 2 + t;
+				break;
+
+			case 6:
+				/* MS-CorrelationId
+				 * MS-SOH 2.2.4.6
+				 *
+				 * 24 bytes opaque binary which we might, in future, have
+				 * to echo back to the client in a final SoHR
+				 */
+				vp = pairmake("SoH-MS-Correlation-Id", NULL, T_OP_EQ);
+				memcpy(vp->vp_octets, p, 24);
+				vp->length = 24;
+				pairadd(&sohvp, vp);
+				p += 24;
+				data_len -= 24;
+				break;
+
+			case 7:
+				/* MS-Installed-Shvs - FIXME: currently unhandled
+				 * MS-SOH 2.2.4.7
+				 *
+				 * 2 bytes length
+				 * N bytes (3 bytes IANA enterprise# + 1 byte component id#)
+				 */
+				t = soh_pull_be_16(p);
+				p += 2;
+				p += t;
+				data_len -= 2 + t;
+				break;
+
+			case 8:
+				/* MS-Machine-Inventory-Ex
+				 * MS-SOH 2.2.4.8
+				 *
+				 * 4 bytes reserved
+				 * 1 byte product type (client=1 domain_controller=2 server=3)
+				 */
+				p += 4;
+				vp = pairmake("SoH-MS-Machine-Role", NULL, T_OP_EQ);
+				vp->vp_integer = *p;
+				pairadd(&sohvp, vp);
+				p++;
+				data_len -= 5;
+				break;
+
+			default:
+				DEBUG("SoH Unknown MS TV %i stopping\n", c);
+				return 0;
+		}
+	}
+	return 1;
+}
+
+static const char* clientstatus2str(uint32_t hcstatus) {
+	switch (hcstatus) {
+		/* this lot should all just be for windows updates */
+		case 0xff0005:
+			return "wua-ok";
+		case 0xff0006:
+			return "wua-missing";
+		case 0xff0008:
+			return "wua-not-started";
+		case 0xc0ff000c:
+			return "wua-no-wsus-server";
+		case 0xc0ff000d:
+			return "wua-no-wsus-clientid";
+		case 0xc0ff000e:
+			return "wua-disabled";
+		case 0xc0ff000f:
+			return "wua-comm-failure";
+
+		/* these next 3 are for all health-classes */
+		case 0xc0ff0002:
+			return "not-installed";
+		case 0xc0ff0003:
+			return "down";
+		case 0xc0ff0018:
+			return "not-started";
+	}
+	return NULL;
+}
+
+static const char* healthclass2str(uint8_t hc) {
+	switch (hc) {
+		case 0:
+			return "firewall";
+		case 1:
+			return "antivirus";
+		case 2:
+			return "antispyware";
+		case 3:
+			return "updates";
+		case 4:
+			return "security-updates";
+	}
+	return NULL;
+}
+
+int soh_verify(VALUE_PAIR *sohvp, const uint8_t *data, unsigned int data_len) {
+
+	VALUE_PAIR *vp;
+	eap_soh hdr;
+	soh_response resp;
+	soh_mode_subheader mode;
+	soh_tlv tlv;
+	int curr_shid=-1, curr_shid_c=-1, curr_hc=-1;
+
+	hdr.tlv_type = soh_pull_be_16(data); data += 2;
+	hdr.tlv_len = soh_pull_be_16(data); data += 2;
+	hdr.tlv_vendor = soh_pull_be_32(data); data += 4;
+
+	if (hdr.tlv_type != 7 || hdr.tlv_vendor != 0x137) {
+		DEBUG("SoH payload is %i %08x not a ms-vendor packet\n", hdr.tlv_type, hdr.tlv_vendor);
+		return -1;
+	}
+
+	hdr.soh_type = soh_pull_be_16(data); data += 2;
+	hdr.soh_len = soh_pull_be_16(data); data += 2;
+	if (hdr.soh_type != 1) {
+		DEBUG("SoH tlv %04x is not a response\n", hdr.soh_type);
+		return -1;
+	}
+
+	/* FIXME: check for sufficient data */
+	resp.outer_type = soh_pull_be_16(data); data += 2;
+	resp.outer_len = soh_pull_be_16(data); data += 2;
+	resp.vendor = soh_pull_be_32(data); data += 4;
+	resp.inner_type = soh_pull_be_16(data); data += 2;
+	resp.inner_len = soh_pull_be_16(data); data += 2;
+
+
+	if (resp.outer_type!=7 || resp.vendor != 0x137) {
+		DEBUG("SoH response outer type %i/vendor %08x not recognised\n", resp.outer_type, resp.vendor);
+		return -1;
+	}
+	switch (resp.inner_type) {
+		case 1:
+			/* no mode sub-header */
+			DEBUG("SoH without mode subheader\n");
+			break;
+		case 2:
+			mode.outer_type = soh_pull_be_16(data); data += 2;
+			mode.outer_len = soh_pull_be_16(data); data += 2;
+			mode.vendor = soh_pull_be_32(data); data += 4;
+			memcpy(mode.corrid, data, 24); data += 24;
+			mode.intent = data[0];
+			mode.content_type = data[1];
+			data += 2;
+
+			if (mode.outer_type != 7 || mode.vendor != 0x137 || mode.content_type != 0) {
+				DEBUG("SoH mode subheader outer type %i/vendor %08x/content type %i invalid\n", mode.outer_type, mode.vendor, mode.content_type);
+				return -1;
+			}
+			DEBUG("SoH with mode subheader\n");
+			break;
+		default:
+			DEBUG("SoH invalid inner type %i\n", resp.inner_type);
+			return -1;
+	}
+
+	/* subtract off the relevant amount of data */
+	if (resp.inner_type==2) {
+		data_len = resp.inner_len - 34;
+	} else {
+		data_len = resp.inner_len;
+	}
+
+	/* TLV
+	 * MS-SOH 2.2.1
+	 * See also 2.2.3
+	 *
+	 * 1 bit mandatory
+	 * 1 bit reserved
+	 * 14 bits tlv type
+	 * 2 bytes tlv length
+	 * N bytes payload
+	 *
+	 */
+	while (data_len >= 4) {
+		tlv.tlv_type = soh_pull_be_16(data); data += 2;
+		tlv.tlv_len = soh_pull_be_16(data); data += 2;
+
+		data_len -= 4;
+
+		switch (tlv.tlv_type) {
+			case 2:
+				/* System-Health-Id TLV
+				 * MS-SOH 2.2.3.1
+				 *
+				 * 3 bytes IANA/SMI vendor code
+				 * 1 byte component (i.e. within vendor, which SoH component
+				 */
+				curr_shid = soh_pull_be_24(data);
+				curr_shid_c = data[3];
+				DEBUG("SoH System-Health-ID vendor %08x component=%i\n", curr_shid, curr_shid_c);
+				break;
+
+			case 7:
+				/* Vendor-Specific packet
+				 * MS-SOH 2.2.3.3
+				 *
+				 * 4 bytes vendor, supposedly ignored by NAP
+				 * N bytes payload; for Microsoft component#0 this is the MS TV stuff
+				 */
+				if (curr_shid==0x137 && curr_shid_c==0) {
+					DEBUG("SoH MS type-value payload\n");
+					eapsoh_mstlv(sohvp, data + 4, tlv.tlv_len - 4);
+				} else {
+					DEBUG("SoH unhandled vendor-specific TLV %08x/component=%i %i bytes payload\n", curr_shid, curr_shid_c, tlv.tlv_len);
+				}
+				break;
+
+			case 8:
+				/* Health-Class
+				 * MS-SOH 2.2.3.5.6
+				 *
+				 * 1 byte integer
+				 */
+				DEBUG("SoH Health-Class %i\n", data[0]);
+				curr_hc = data[0];
+				break;
+
+			case 9:
+				/* Software-Version
+				 * MS-SOH 2.2.3.5.7
+				 *
+				 * 1 byte integer
+				 */
+				DEBUG("SoH Software-Version %i\n", data[0]);
+				break;
+
+			case 11:
+				/* Health-Class status
+				 * MS-SOH 2.2.3.5.9
+				 *
+				 * variable data; for the MS System Health vendor, these are 4-byte
+				 * integers which are a really, really dumb format:
+				 *
+				 *  28 bits ignore
+				 *  1 bit - 1==product snoozed
+				 *  1 bit - 1==microsoft product
+				 *  1 bit - 1==product up-to-date
+				 *  1 bit - 1==product enabled
+				 */
+				DEBUG("SoH Health-Class-Status - current shid=%08x component=%i\n", curr_shid, curr_shid_c);
+
+				if (curr_shid==0x137 && curr_shid_c==128) {
+
+					const char *s, *t;
+					uint32_t hcstatus = soh_pull_be_32(data);
+
+					DEBUG("SoH Health-Class-Status microsoft DWORD=%08x\n", hcstatus);
+
+					vp = pairmake("SoH-MS-Windows-Health-Status", NULL, T_OP_EQ);
+					switch (curr_hc) {
+						case 4:
+							/* security updates */
+							s = "security-updates";
+							switch (hcstatus) {
+								case 0xff0005:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok all-installed", s);
+									break;
+								case 0xff0006:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn some-missing", s);
+									break;
+								case 0xff0008:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn never-started", s);
+									break;
+								case 0xc0ff000c:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error no-wsus-srv", s);
+									break;
+								case 0xc0ff000d:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error no-wsus-clid", s);
+									break;
+								case 0xc0ff000e:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn wsus-disabled", s);
+									break;
+								case 0xc0ff000f:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error comm-failure", s);
+									break;
+								case 0xc0ff0010:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn needs-reboot", s);
+									break;
+								default:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
+									break;
+							}
+							break;
+
+						case 3:
+							/* auto updates */
+							s = "auto-updates";
+							switch (hcstatus) {
+								case 1:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn disabled", s);
+									break;
+								case 2:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=check-only", s);
+									break;
+								case 3:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=download", s);
+									break;
+								case 4:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=install", s);
+									break;
+								case 5:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn unconfigured", s);
+									break;
+								case 0xc0ff0003:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn service-down", s);
+									break;
+								case 0xc0ff0018:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn never-started", s);
+									break;
+								default:
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
+									break;
+							}
+							break;
+
+						default:
+							/* other - firewall, antivirus, antispyware */
+							s = healthclass2str(curr_hc);
+							if (s) {
+								/* bah. this is vile. stupid microsoft
+								 */
+								if (hcstatus & 0xff000000) {
+									/* top octet non-zero means an error
+									 * FIXME: is this always correct? MS-WSH 2.2.8 is unclear
+									 */
+									t = clientstatus2str(hcstatus);
+									if (t) {
+										snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %s", s, t);
+									} else {
+										snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
+									}
+								} else {
+									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue),
+											"%s ok snoozed=%i microsoft=%i up2date=%i enabled=%i",
+											s,
+											hcstatus & 0x8 ? 1 : 0,
+											hcstatus & 0x4 ? 1 : 0,
+											hcstatus & 0x2 ? 1 : 0,
+											hcstatus & 0x1 ? 1 : 0
+											);
+								}
+							} else {
+								snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%i unknown %08x", curr_hc, hcstatus);
+							}
+							break;
+					}
+				} else {
+					vp = pairmake("SoH-Other-Health-Status", NULL, T_OP_EQ);
+					/* FIXME: what to do with the payload? */
+					snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%08x/%i ?", curr_shid, curr_shid_c);
+				}
+				pairadd(&sohvp, vp);
+				break;
+
+			default:
+				DEBUG("SoH Unknown TLV %i len=%i\n", tlv.tlv_type, tlv.tlv_len);
+				break;
+		}
+
+		data += tlv.tlv_len;
+		data_len -= tlv.tlv_len;
+
+	}
+
+	return 0;
+}
-- 
1.6.6.1

