summaryrefslogtreecommitdiffstats
path: root/src/main/soh.c
blob: 754ad84e478a1cefde8624a17c19027d8ddf6a6b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
/*
 *   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
 */

/**
 * $Id$
 *
 * @file soh.c
 * @brief Implements the MS-SOH parsing code. This is called from rlm_eap_peap
 *
 * @copyright 2010 Phil Mayers <p.mayers@imperial.ac.uk>
 */

RCSID("$Id$")

#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/soh.h>
#include <freeradius-devel/rad_assert.h>

/*
 * This code implements parsing of MS-SOH data into FreeRadius AVPs
 * allowing for FreeRadius MS-NAP policies
 */

/**
 * EAP-SOH packet
 */
typedef struct {
	uint16_t tlv_type;	/**< ==7 for EAP-SOH */
	uint16_t tlv_len;
	uint32_t tlv_vendor;

	/**
	 * @name soh-payload
	 * @brief 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;

/**
 * SOH response payload
 * Send by client to server
 */
typedef struct {
	uint16_t outer_type;
	uint16_t outer_len;
	uint32_t vendor;
	uint16_t inner_type;
	uint16_t inner_len;
} soh_response;

/**
 * SOH mode subheader
 * Typical microsoft binary blob nonsense
 */
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;

/**
 * SOH type-length-value header
 */
typedef struct {
	uint16_t tlv_type;
	uint16_t tlv_len;
} soh_tlv;

/** Read big-endian 2-byte unsigned from p
 *
 * caller must ensure enough data exists at "p"
 */
uint16_t soh_pull_be_16(uint8_t const *p) {
	uint16_t r;

	r = *p++ << 8;
	r += *p++;

	return r;
}

/** Read big-endian 3-byte unsigned from p
 *
 * caller must ensure enough data exists at "p"
 */
uint32_t soh_pull_be_24(uint8_t const *p) {
	uint32_t r;

	r = *p++ << 16;
	r += *p++ << 8;
	r += *p++;

	return r;
}

/** Read big-endian 4-byte unsigned from p
 *
 * caller must ensure enough data exists at "p"
 */
uint32_t soh_pull_be_32(uint8_t const *p) {
	uint32_t r;

	r = *p++ << 24;
	r += *p++ << 16;
	r += *p++ << 8;
	r += *p++;

	return r;
}

static int eapsoh_mstlv(REQUEST *request, uint8_t const *p, unsigned int data_len) CC_HINT(nonnull);

/** Parses the MS-SOH type/value (note: NOT type/length/value) data and update the sohvp list
 *
 * 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. Note that sohvp may still have been modified in the
 * failure case.
 *
 * @param request Current request
 * @param p binary blob
 * @param data_len length of blob
 * @return 1 on success, 0 on failure
 */
static int eapsoh_mstlv(REQUEST *request, uint8_t const *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) {
				RDEBUG("insufficient data for MS-Machine-Inventory-Packet");
				return 0;
			}
			data_len -= 18;

			vp = pair_make_request("SoH-MS-Machine-OS-vendor", "Microsoft", T_OP_EQ);
			if (!vp) return 0;

			vp = pair_make_request("SoH-MS-Machine-OS-version", NULL, T_OP_EQ);
			if (!vp) return 0;

			vp->vp_integer = soh_pull_be_32(p); p+=4;

			vp = pair_make_request("SoH-MS-Machine-OS-release", NULL, T_OP_EQ);
			if (!vp) return 0;

			vp->vp_integer = soh_pull_be_32(p); p+=4;

			vp = pair_make_request("SoH-MS-Machine-OS-build", NULL, T_OP_EQ);
			if (!vp) return 0;

			vp->vp_integer = soh_pull_be_32(p); p+=4;

			vp = pair_make_request("SoH-MS-Machine-SP-version", NULL, T_OP_EQ);
			if (!vp) return 0;

			vp->vp_integer = soh_pull_be_16(p); p+=2;

			vp = pair_make_request("SoH-MS-Machine-SP-release", NULL, T_OP_EQ);
			if (!vp) return 0;

			vp->vp_integer = soh_pull_be_16(p); p+=2;

			vp = pair_make_request("SoH-MS-Machine-Processor", NULL, T_OP_EQ);
			if (!vp) return 0;

			vp->vp_integer = soh_pull_be_16(p); p+=2;
			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
			 */
			RDEBUG3("SoH MS-Packet-Info %s vers=%i", *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 = pair_make_request("SoH-MS-Machine-Name", NULL, T_OP_EQ);
			if (!vp) return 0;

			fr_pair_value_bstrncpy(vp, p, t);

			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 = pair_make_request("SoH-MS-Correlation-Id", NULL, T_OP_EQ);
			if (!vp) return 0;

			fr_pair_value_memcpy(vp, p, 24);
			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 = pair_make_request("SoH-MS-Machine-Role", NULL, T_OP_EQ);
			if (!vp) return 0;

			vp->vp_integer = *p;
			p++;
			data_len -= 5;
			break;

		default:
			RDEBUG("SoH Unknown MS TV %i stopping", c);
			return 0;
		}
	}
	return 1;
}
/** Convert windows Health Class status into human-readable string
 *
 * Tedious, really, really tedious...
 */
static char const* 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;
}

/** Convert a Health Class into a string
 *
 */
static char const* 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;
}

/** Parse the MS-SOH response in data and update sohvp
 *
 * Note that sohvp might still have been updated in event of a failure.
 *
 * @param request Current request
 * @param data MS-SOH blob
 * @param data_len length of MS-SOH blob
 *
 * @return 0 on success, -1 on failure
 *
 */
int soh_verify(REQUEST *request, uint8_t const *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;

	rad_assert(request->packet != NULL);

	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) {
		RDEBUG("SoH payload is %i %08x not a ms-vendor packet", 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) {
		RDEBUG("SoH tlv %04x is not a response", 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) {
		RDEBUG("SoH response outer type %i/vendor %08x not recognised", resp.outer_type, resp.vendor);
		return -1;
	}
	switch (resp.inner_type) {
	case 1:
		/* no mode sub-header */
		RDEBUG("SoH without mode subheader");
		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) {
			RDEBUG3("SoH mode subheader outer type %i/vendor %08x/content type %i invalid", mode.outer_type, mode.vendor, mode.content_type);
			return -1;
		}
		RDEBUG3("SoH with mode subheader");
		break;

	default:
		RDEBUG("SoH invalid inner type %i", 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];
			RDEBUG2("SoH System-Health-ID vendor %08x component=%i", 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) {
				RDEBUG2("SoH MS type-value payload");
				eapsoh_mstlv(request, data + 4, tlv.tlv_len - 4);
			} else {
				RDEBUG2("SoH unhandled vendor-specific TLV %08x/component=%i %i bytes payload",
					curr_shid, curr_shid_c, tlv.tlv_len);
			}
			break;

		case 8:
			/* Health-Class
			 * MS-SOH 2.2.3.5.6
			 *
			 * 1 byte integer
			 */
			RDEBUG2("SoH Health-Class %i", data[0]);
			curr_hc = data[0];
			break;

		case 9:
			/* Software-Version
			 * MS-SOH 2.2.3.5.7
			 *
			 * 1 byte integer
			 */
			RDEBUG2("SoH Software-Version %i", 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
			 */
			RDEBUG2("SoH Health-Class-Status - current shid=%08x component=%i", curr_shid, curr_shid_c);

			if (curr_shid == 0x137 && curr_shid_c == 128) {
				char const *s, *t;
				uint32_t hcstatus = soh_pull_be_32(data);

				RDEBUG2("SoH Health-Class-Status microsoft DWORD=%08x", hcstatus);

				vp = pair_make_request("SoH-MS-Windows-Health-Status", NULL, T_OP_EQ);
				if (!vp) return 0;

				switch (curr_hc) {
				case 4:
					/* security updates */
					s = "security-updates";
					switch (hcstatus) {
					case 0xff0005:
						fr_pair_value_sprintf(vp, "%s ok all-installed", s);
						break;

					case 0xff0006:
						fr_pair_value_sprintf(vp, "%s warn some-missing", s);
						break;

					case 0xff0008:
						fr_pair_value_sprintf(vp, "%s warn never-started", s);
						break;

					case 0xc0ff000c:
						fr_pair_value_sprintf(vp, "%s error no-wsus-srv", s);
						break;

					case 0xc0ff000d:
						fr_pair_value_sprintf(vp, "%s error no-wsus-clid", s);
						break;

					case 0xc0ff000e:
						fr_pair_value_sprintf(vp, "%s warn wsus-disabled", s);
						break;

					case 0xc0ff000f:
						fr_pair_value_sprintf(vp, "%s error comm-failure", s);
						break;

					case 0xc0ff0010:
						fr_pair_value_sprintf(vp, "%s warn needs-reboot", s);
						break;

					default:
						fr_pair_value_sprintf(vp, "%s error %08x", s, hcstatus);
						break;
					}
					break;

				case 3:
					/* auto updates */
					s = "auto-updates";
					switch (hcstatus) {
					case 1:
						fr_pair_value_sprintf(vp, "%s warn disabled", s);
						break;

					case 2:
						fr_pair_value_sprintf(vp, "%s ok action=check-only", s);
						break;

					case 3:
						fr_pair_value_sprintf(vp, "%s ok action=download", s);
						break;

					case 4:
						fr_pair_value_sprintf(vp, "%s ok action=install", s);
						break;

					case 5:
						fr_pair_value_sprintf(vp, "%s warn unconfigured", s);
						break;

					case 0xc0ff0003:
						fr_pair_value_sprintf(vp, "%s warn service-down", s);
						break;

					case 0xc0ff0018:
						fr_pair_value_sprintf(vp, "%s warn never-started", s);
						break;

					default:
						fr_pair_value_sprintf(vp, "%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) {
								fr_pair_value_sprintf(vp, "%s error %s", s, t);
							} else {
								fr_pair_value_sprintf(vp, "%s error %08x", s, hcstatus);
							}
						} else {
							fr_pair_value_sprintf(vp,
									"%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 {
						fr_pair_value_sprintf(vp, "%i unknown %08x", curr_hc, hcstatus);
					}
					break;
				}
			} else {
				vp = pair_make_request("SoH-MS-Health-Other", NULL, T_OP_EQ);
				if (!vp) return 0;

				/* FIXME: what to do with the payload? */
				fr_pair_value_sprintf(vp, "%08x/%i ?", curr_shid, curr_shid_c);
			}
			break;

		default:
			RDEBUG("SoH Unknown TLV %i len=%i", tlv.tlv_type, tlv.tlv_len);
			break;
		}

		data += tlv.tlv_len;
		data_len -= tlv.tlv_len;
	}

	return 0;
}