2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 * @brief Implements the MS-SOH parsing code. This is called from rlm_eap_peap
23 * @copyright 2010 Phil Mayers <p.mayers@imperial.ac.uk>
28 #include <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/soh.h>
32 * This code implements parsing of MS-SOH data into FreeRadius AVPs
33 * allowing for FreeRadius MS-NAP policies
40 uint16_t tlv_type; /**< ==7 for EAP-SOH */
46 * @brief either an soh request or response */
47 uint16_t soh_type; /**< ==2 for request, 1 for response */
50 /* an soh-response may now follow... */
54 * SOH response payload
55 * Send by client to server
67 * Typical microsoft binary blob nonsense
79 * SOH type-length-value header
87 * @brief read big-endian 2-byte unsigned from p
89 * caller must ensure enough data exists at "p"
91 uint16_t soh_pull_be_16(uint8_t const *p) {
100 * @brief read big-endian 3-byte unsigned from p
102 * caller must ensure enough data exists at "p"
104 uint32_t soh_pull_be_24(uint8_t const *p) {
114 * @brief read big-endian 4-byte unsigned from p
116 * caller must ensure enough data exists at "p"
118 uint32_t soh_pull_be_32(uint8_t const *p) {
130 * @brief Parses the MS-SOH type/value (note: NOT type/length/value) data and
131 * update the sohvp list
133 * See section 2.2.4 of MS-SOH. Because there's no "length" field we CANNOT just skip
134 * unknown types; we need to know their length ahead of time. Therefore, we abort
135 * if we find an unknown type. Note that sohvp may still have been modified in the
138 * @param request Current request
139 * @param p binary blob
140 * @param data_len length of blob
141 * @return 1 on success, 0 on failure
143 static int eapsoh_mstlv(REQUEST *request, uint8_t const *p, unsigned int data_len) {
148 while (data_len > 0) {
154 /* MS-Machine-Inventory-Packet
155 * MS-SOH section 2.2.4.1
158 RDEBUG("insufficient data for MS-Machine-Inventory-Packet");
163 vp = pairmake_packet("SoH-MS-Machine-OS-vendor", "Microsoft", T_OP_EQ);
166 vp = pairmake_packet("SoH-MS-Machine-OS-version", NULL, T_OP_EQ);
169 vp->vp_integer = soh_pull_be_32(p); p+=4;
171 vp = pairmake_packet("SoH-MS-Machine-OS-release", NULL, T_OP_EQ);
174 vp->vp_integer = soh_pull_be_32(p); p+=4;
176 vp = pairmake_packet("SoH-MS-Machine-OS-build", NULL, T_OP_EQ);
179 vp->vp_integer = soh_pull_be_32(p); p+=4;
181 vp = pairmake_packet("SoH-MS-Machine-SP-version", NULL, T_OP_EQ);
184 vp->vp_integer = soh_pull_be_16(p); p+=2;
186 vp = pairmake_packet("SoH-MS-Machine-SP-release", NULL, T_OP_EQ);
189 vp->vp_integer = soh_pull_be_16(p); p+=2;
191 vp = pairmake_packet("SoH-MS-Machine-Processor", NULL, T_OP_EQ);
194 vp->vp_integer = soh_pull_be_16(p); p+=2;
198 /* MS-Quarantine-State - FIXME: currently unhandled
203 * 8 bytes NT Time field (100-nanosec since 1 Jan 1601)
208 t = soh_pull_be_16(p); /* t == uri len */
218 RDEBUG3("SoH MS-Packet-Info %s vers=%i", *p & 0x10 ? "request" : "response", *p & 0xf);
224 /* MS-SystemGenerated-Ids - FIXME: currently unhandled
228 * N bytes (3 bytes IANA enterprise# + 1 byte component id#)
230 t = soh_pull_be_16(p);
243 t = soh_pull_be_16(p);
246 vp = pairmake_packet("SoH-MS-Machine-Name", NULL, T_OP_EQ);
249 memcpy(vp->vp_strvalue, p, t);
250 vp->vp_strvalue[t] = 0;
260 * 24 bytes opaque binary which we might, in future, have
261 * to echo back to the client in a final SoHR
263 vp = pairmake_packet("SoH-MS-Correlation-Id", NULL, T_OP_EQ);
266 pairmemcpy(vp, p, 24);
272 /* MS-Installed-Shvs - FIXME: currently unhandled
276 * N bytes (3 bytes IANA enterprise# + 1 byte component id#)
278 t = soh_pull_be_16(p);
285 /* MS-Machine-Inventory-Ex
289 * 1 byte product type (client=1 domain_controller=2 server=3)
292 vp = pairmake_packet("SoH-MS-Machine-Role", NULL, T_OP_EQ);
301 RDEBUG("SoH Unknown MS TV %i stopping", c);
308 * @brief Convert windows Health Class status into human-readable
309 * string. Tedious, really, really tedious...
311 static char const* clientstatus2str(uint32_t hcstatus) {
313 /* this lot should all just be for windows updates */
317 return "wua-missing";
319 return "wua-not-started";
321 return "wua-no-wsus-server";
323 return "wua-no-wsus-clientid";
325 return "wua-disabled";
327 return "wua-comm-failure";
329 /* these next 3 are for all health-classes */
331 return "not-installed";
335 return "not-started";
341 * @brief convert a Health Class into a string
343 static char const* healthclass2str(uint8_t hc) {
350 return "antispyware";
354 return "security-updates";
360 * @brief Parse the MS-SOH response in data and update sohvp.
362 * Note that sohvp might still have been updated in event of a failure.
364 * @param request Current request
365 * @param data MS-SOH blob
366 * @param data_len length of MS-SOH blob
368 * @return 0 on success, -1 on failure
371 int soh_verify(REQUEST *request, uint8_t const *data, unsigned int data_len) {
376 soh_mode_subheader mode;
378 int curr_shid=-1, curr_shid_c=-1, curr_hc=-1;
380 hdr.tlv_type = soh_pull_be_16(data); data += 2;
381 hdr.tlv_len = soh_pull_be_16(data); data += 2;
382 hdr.tlv_vendor = soh_pull_be_32(data); data += 4;
384 if (hdr.tlv_type != 7 || hdr.tlv_vendor != 0x137) {
385 RDEBUG("SoH payload is %i %08x not a ms-vendor packet", hdr.tlv_type, hdr.tlv_vendor);
389 hdr.soh_type = soh_pull_be_16(data); data += 2;
390 hdr.soh_len = soh_pull_be_16(data); data += 2;
391 if (hdr.soh_type != 1) {
392 RDEBUG("SoH tlv %04x is not a response", hdr.soh_type);
396 /* FIXME: check for sufficient data */
397 resp.outer_type = soh_pull_be_16(data); data += 2;
398 resp.outer_len = soh_pull_be_16(data); data += 2;
399 resp.vendor = soh_pull_be_32(data); data += 4;
400 resp.inner_type = soh_pull_be_16(data); data += 2;
401 resp.inner_len = soh_pull_be_16(data); data += 2;
404 if (resp.outer_type!=7 || resp.vendor != 0x137) {
405 RDEBUG("SoH response outer type %i/vendor %08x not recognised", resp.outer_type, resp.vendor);
408 switch (resp.inner_type) {
410 /* no mode sub-header */
411 RDEBUG("SoH without mode subheader");
414 mode.outer_type = soh_pull_be_16(data); data += 2;
415 mode.outer_len = soh_pull_be_16(data); data += 2;
416 mode.vendor = soh_pull_be_32(data); data += 4;
417 memcpy(mode.corrid, data, 24); data += 24;
418 mode.intent = data[0];
419 mode.content_type = data[1];
422 if (mode.outer_type != 7 || mode.vendor != 0x137 || mode.content_type != 0) {
423 RDEBUG3("SoH mode subheader outer type %i/vendor %08x/content type %i invalid", mode.outer_type, mode.vendor, mode.content_type);
426 RDEBUG3("SoH with mode subheader");
429 RDEBUG("SoH invalid inner type %i", resp.inner_type);
433 /* subtract off the relevant amount of data */
434 if (resp.inner_type==2) {
435 data_len = resp.inner_len - 34;
437 data_len = resp.inner_len;
451 while (data_len >= 4) {
452 tlv.tlv_type = soh_pull_be_16(data); data += 2;
453 tlv.tlv_len = soh_pull_be_16(data); data += 2;
457 switch (tlv.tlv_type) {
459 /* System-Health-Id TLV
462 * 3 bytes IANA/SMI vendor code
463 * 1 byte component (i.e. within vendor, which SoH component
465 curr_shid = soh_pull_be_24(data);
466 curr_shid_c = data[3];
467 RDEBUG2("SoH System-Health-ID vendor %08x component=%i", curr_shid, curr_shid_c);
471 /* Vendor-Specific packet
474 * 4 bytes vendor, supposedly ignored by NAP
475 * N bytes payload; for Microsoft component#0 this is the MS TV stuff
477 if (curr_shid==0x137 && curr_shid_c==0) {
478 RDEBUG2("SoH MS type-value payload");
479 eapsoh_mstlv(request, data + 4, tlv.tlv_len - 4);
481 RDEBUG2("SoH unhandled vendor-specific TLV %08x/component=%i %i bytes payload", curr_shid, curr_shid_c, tlv.tlv_len);
491 RDEBUG2("SoH Health-Class %i", data[0]);
501 RDEBUG2("SoH Software-Version %i", data[0]);
505 /* Health-Class status
508 * variable data; for the MS System Health vendor, these are 4-byte
509 * integers which are a really, really dumb format:
512 * 1 bit - 1==product snoozed
513 * 1 bit - 1==microsoft product
514 * 1 bit - 1==product up-to-date
515 * 1 bit - 1==product enabled
517 RDEBUG2("SoH Health-Class-Status - current shid=%08x component=%i", curr_shid, curr_shid_c);
519 if (curr_shid==0x137 && curr_shid_c==128) {
522 uint32_t hcstatus = soh_pull_be_32(data);
524 RDEBUG2("SoH Health-Class-Status microsoft DWORD=%08x", hcstatus);
526 vp = pairmake_packet("SoH-MS-Windows-Health-Status", NULL, T_OP_EQ);
531 /* security updates */
532 s = "security-updates";
535 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok all-installed", s);
538 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn some-missing", s);
541 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn never-started", s);
544 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error no-wsus-srv", s);
547 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error no-wsus-clid", s);
550 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn wsus-disabled", s);
553 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error comm-failure", s);
556 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn needs-reboot", s);
559 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
569 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn disabled", s);
572 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=check-only", s);
575 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=download", s);
578 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=install", s);
581 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn unconfigured", s);
584 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn service-down", s);
587 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn never-started", s);
590 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
596 /* other - firewall, antivirus, antispyware */
597 s = healthclass2str(curr_hc);
599 /* bah. this is vile. stupid microsoft
601 if (hcstatus & 0xff000000) {
602 /* top octet non-zero means an error
603 * FIXME: is this always correct? MS-WSH 2.2.8 is unclear
605 t = clientstatus2str(hcstatus);
607 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %s", s, t);
609 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
612 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue),
613 "%s ok snoozed=%i microsoft=%i up2date=%i enabled=%i",
615 hcstatus & 0x8 ? 1 : 0,
616 hcstatus & 0x4 ? 1 : 0,
617 hcstatus & 0x2 ? 1 : 0,
618 hcstatus & 0x1 ? 1 : 0
622 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%i unknown %08x", curr_hc, hcstatus);
627 vp = pairmake_packet("SoH-MS-Health-Other", NULL, T_OP_EQ);
630 /* FIXME: what to do with the payload? */
631 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%08x/%i ?", curr_shid, curr_shid_c);
636 RDEBUG("SoH Unknown TLV %i len=%i", tlv.tlv_type, tlv.tlv_len);
641 data_len -= tlv.tlv_len;