371a4e894611c0e1eb091d315afbbfa555b70d67
[freeradius.git] / src / main / soh.c
1 /*
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.
6  *
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.
11  *
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
15  */
16
17 /*
18  * $Id$
19  *
20  * @file soh.c
21  * @brief Implements the MS-SOH parsing code. This is called from rlm_eap_peap
22  *
23  * @copyright 2010 Phil Mayers <p.mayers@imperial.ac.uk>
24  */
25
26 RCSID("$Id$")
27
28 #include <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/soh.h>
30
31 /*
32  * This code implements parsing of MS-SOH data into FreeRadius AVPs
33  * allowing for FreeRadius MS-NAP policies
34  */
35
36 /**
37  * EAP-SOH packet
38  */
39 typedef struct {
40         uint16_t tlv_type;      /**< ==7 for EAP-SOH */
41         uint16_t tlv_len;
42         uint32_t tlv_vendor;
43
44         /**
45          * @name soh-payload
46          * @brief either an soh request or response */
47         uint16_t soh_type;      /**< ==2 for request, 1 for response */
48         uint16_t soh_len;
49
50         /* an soh-response may now follow... */
51 } eap_soh;
52
53 /**
54  * SOH response payload
55  * Send by client to server
56  */
57 typedef struct {
58         uint16_t outer_type;
59         uint16_t outer_len;
60         uint32_t vendor;
61         uint16_t inner_type;
62         uint16_t inner_len;
63 } soh_response;
64
65 /**
66  * SOH mode subheader
67  * Typical microsoft binary blob nonsense
68  */
69 typedef struct {
70         uint16_t outer_type;
71         uint16_t outer_len;
72         uint32_t vendor;
73         uint8_t corrid[24];
74         uint8_t intent;
75         uint8_t content_type;
76 } soh_mode_subheader;
77
78 /**
79  * SOH type-length-value header
80  */
81 typedef struct {
82         uint16_t tlv_type;
83         uint16_t tlv_len;
84 } soh_tlv;
85
86 /**
87  * @brief read big-endian 2-byte unsigned from p
88  *
89  * caller must ensure enough data exists at "p"
90  */
91 uint16_t soh_pull_be_16(uint8_t const *p) {
92         uint16_t r;
93
94         r = *p++ << 8;
95         r += *p++;
96
97         return r;
98 }
99 /**
100  * @brief read big-endian 3-byte unsigned from p
101  *
102  * caller must ensure enough data exists at "p"
103  */
104 uint32_t soh_pull_be_24(uint8_t const *p) {
105         uint32_t r;
106
107         r = *p++ << 16;
108         r += *p++ << 8;
109         r += *p++;
110
111         return r;
112 }
113 /**
114  * @brief read big-endian 4-byte unsigned from p
115  *
116  * caller must ensure enough data exists at "p"
117  */
118 uint32_t soh_pull_be_32(uint8_t const *p) {
119         uint32_t r;
120
121         r = *p++ << 24;
122         r += *p++ << 16;
123         r += *p++ << 8;
124         r += *p++;
125
126         return r;
127 }
128
129 /**
130  * @brief Parses the MS-SOH type/value (note: NOT type/length/value) data and
131  *      update the sohvp list
132  *
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
136  * failure case.
137  *
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
142  */
143 static int eapsoh_mstlv(REQUEST *request, uint8_t const *p, unsigned int data_len) {
144         VALUE_PAIR *vp;
145         uint8_t c;
146         int t;
147
148         while (data_len > 0) {
149                 c = *p++;
150                 data_len--;
151
152                 switch (c) {
153                         case 1:
154                                 /* MS-Machine-Inventory-Packet
155                                  * MS-SOH section 2.2.4.1
156                                  */
157                                 if (data_len < 18) {
158                                         RDEBUG("insufficient data for MS-Machine-Inventory-Packet");
159                                         return 0;
160                                 }
161                                 data_len -= 18;
162
163                                 vp = pairmake_packet("SoH-MS-Machine-OS-vendor", "Microsoft", T_OP_EQ);
164                                 if (!vp) return 0;
165
166                                 vp = pairmake_packet("SoH-MS-Machine-OS-version", NULL, T_OP_EQ);
167                                 if (!vp) return 0;
168
169                                 vp->vp_integer = soh_pull_be_32(p); p+=4;
170
171                                 vp = pairmake_packet("SoH-MS-Machine-OS-release", NULL, T_OP_EQ);
172                                 if (!vp) return 0;
173
174                                 vp->vp_integer = soh_pull_be_32(p); p+=4;
175
176                                 vp = pairmake_packet("SoH-MS-Machine-OS-build", NULL, T_OP_EQ);
177                                 if (!vp) return 0;
178
179                                 vp->vp_integer = soh_pull_be_32(p); p+=4;
180
181                                 vp = pairmake_packet("SoH-MS-Machine-SP-version", NULL, T_OP_EQ);
182                                 if (!vp) return 0;
183
184                                 vp->vp_integer = soh_pull_be_16(p); p+=2;
185
186                                 vp = pairmake_packet("SoH-MS-Machine-SP-release", NULL, T_OP_EQ);
187                                 if (!vp) return 0;
188
189                                 vp->vp_integer = soh_pull_be_16(p); p+=2;
190
191                                 vp = pairmake_packet("SoH-MS-Machine-Processor", NULL, T_OP_EQ);
192                                 if (!vp) return 0;
193
194                                 vp->vp_integer = soh_pull_be_16(p); p+=2;
195                                 break;
196
197                         case 2:
198                                 /* MS-Quarantine-State - FIXME: currently unhandled
199                                  * MS-SOH 2.2.4.1
200                                  *
201                                  * 1 byte reserved
202                                  * 1 byte flags
203                                  * 8 bytes NT Time field (100-nanosec since 1 Jan 1601)
204                                  * 2 byte urilen
205                                  * N bytes uri
206                                  */
207                                 p += 10;
208                                 t = soh_pull_be_16(p);  /* t == uri len */
209                                 p += 2;
210                                 p += t;
211                                 data_len -= 12 + t;
212                                 break;
213
214                         case 3:
215                                 /* MS-Packet-Info
216                                  * MS-SOH 2.2.4.3
217                                  */
218                                 RDEBUG3("SoH MS-Packet-Info %s vers=%i", *p & 0x10 ? "request" : "response", *p & 0xf);
219                                 p++;
220                                 data_len--;
221                                 break;
222
223                         case 4:
224                                 /* MS-SystemGenerated-Ids - FIXME: currently unhandled
225                                  * MS-SOH 2.2.4.4
226                                  *
227                                  * 2 byte length
228                                  * N bytes (3 bytes IANA enterprise# + 1 byte component id#)
229                                  */
230                                 t = soh_pull_be_16(p);
231                                 p += 2;
232                                 p += t;
233                                 data_len -= 2 + t;
234                                 break;
235
236                         case 5:
237                                 /* MS-MachineName
238                                  * MS-SOH 2.2.4.5
239                                  *
240                                  * 1 byte namelen
241                                  * N bytes name
242                                  */
243                                 t = soh_pull_be_16(p);
244                                 p += 2;
245
246                                 vp = pairmake_packet("SoH-MS-Machine-Name", NULL, T_OP_EQ);
247                                 if (!vp) return 0;
248
249                                 memcpy(vp->vp_strvalue, p, t);
250                                 vp->vp_strvalue[t] = 0;
251
252                                 p += t;
253                                 data_len -= 2 + t;
254                                 break;
255
256                         case 6:
257                                 /* MS-CorrelationId
258                                  * MS-SOH 2.2.4.6
259                                  *
260                                  * 24 bytes opaque binary which we might, in future, have
261                                  * to echo back to the client in a final SoHR
262                                  */
263                                 vp = pairmake_packet("SoH-MS-Correlation-Id", NULL, T_OP_EQ);
264                                 if (!vp) return 0;
265
266                                 pairmemcpy(vp, p, 24);
267                                 p += 24;
268                                 data_len -= 24;
269                                 break;
270
271                         case 7:
272                                 /* MS-Installed-Shvs - FIXME: currently unhandled
273                                  * MS-SOH 2.2.4.7
274                                  *
275                                  * 2 bytes length
276                                  * N bytes (3 bytes IANA enterprise# + 1 byte component id#)
277                                  */
278                                 t = soh_pull_be_16(p);
279                                 p += 2;
280                                 p += t;
281                                 data_len -= 2 + t;
282                                 break;
283
284                         case 8:
285                                 /* MS-Machine-Inventory-Ex
286                                  * MS-SOH 2.2.4.8
287                                  *
288                                  * 4 bytes reserved
289                                  * 1 byte product type (client=1 domain_controller=2 server=3)
290                                  */
291                                 p += 4;
292                                 vp = pairmake_packet("SoH-MS-Machine-Role", NULL, T_OP_EQ);
293                                 if (!vp) return 0;
294
295                                 vp->vp_integer = *p;
296                                 p++;
297                                 data_len -= 5;
298                                 break;
299
300                         default:
301                                 RDEBUG("SoH Unknown MS TV %i stopping", c);
302                                 return 0;
303                 }
304         }
305         return 1;
306 }
307 /**
308  * @brief Convert windows Health Class status into human-readable
309  *      string. Tedious, really, really tedious...
310  */
311 static char const* clientstatus2str(uint32_t hcstatus) {
312         switch (hcstatus) {
313                 /* this lot should all just be for windows updates */
314                 case 0xff0005:
315                         return "wua-ok";
316                 case 0xff0006:
317                         return "wua-missing";
318                 case 0xff0008:
319                         return "wua-not-started";
320                 case 0xc0ff000c:
321                         return "wua-no-wsus-server";
322                 case 0xc0ff000d:
323                         return "wua-no-wsus-clientid";
324                 case 0xc0ff000e:
325                         return "wua-disabled";
326                 case 0xc0ff000f:
327                         return "wua-comm-failure";
328
329                 /* these next 3 are for all health-classes */
330                 case 0xc0ff0002:
331                         return "not-installed";
332                 case 0xc0ff0003:
333                         return "down";
334                 case 0xc0ff0018:
335                         return "not-started";
336         }
337         return NULL;
338 }
339
340 /**
341  * @brief convert a Health Class into a string
342  */
343 static char const* healthclass2str(uint8_t hc) {
344         switch (hc) {
345                 case 0:
346                         return "firewall";
347                 case 1:
348                         return "antivirus";
349                 case 2:
350                         return "antispyware";
351                 case 3:
352                         return "updates";
353                 case 4:
354                         return "security-updates";
355         }
356         return NULL;
357 }
358
359 /**
360  * @brief Parse the MS-SOH response in data and update sohvp.
361  *
362  * Note that sohvp might still have been updated in event of a failure.
363  *
364  * @param request Current request
365  * @param data MS-SOH blob
366  * @param data_len length of MS-SOH blob
367  *
368  * @return 0 on success, -1 on failure
369  *
370  */
371 int soh_verify(REQUEST *request, uint8_t const *data, unsigned int data_len) {
372
373         VALUE_PAIR *vp;
374         eap_soh hdr;
375         soh_response resp;
376         soh_mode_subheader mode;
377         soh_tlv tlv;
378         int curr_shid=-1, curr_shid_c=-1, curr_hc=-1;
379
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;
383
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);
386                 return -1;
387         }
388
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);
393                 return -1;
394         }
395
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;
402
403
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);
406                 return -1;
407         }
408         switch (resp.inner_type) {
409                 case 1:
410                         /* no mode sub-header */
411                         RDEBUG("SoH without mode subheader");
412                         break;
413                 case 2:
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];
420                         data += 2;
421
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);
424                                 return -1;
425                         }
426                         RDEBUG3("SoH with mode subheader");
427                         break;
428                 default:
429                         RDEBUG("SoH invalid inner type %i", resp.inner_type);
430                         return -1;
431         }
432
433         /* subtract off the relevant amount of data */
434         if (resp.inner_type==2) {
435                 data_len = resp.inner_len - 34;
436         } else {
437                 data_len = resp.inner_len;
438         }
439
440         /* TLV
441          * MS-SOH 2.2.1
442          * See also 2.2.3
443          *
444          * 1 bit mandatory
445          * 1 bit reserved
446          * 14 bits tlv type
447          * 2 bytes tlv length
448          * N bytes payload
449          *
450          */
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;
454
455                 data_len -= 4;
456
457                 switch (tlv.tlv_type) {
458                         case 2:
459                                 /* System-Health-Id TLV
460                                  * MS-SOH 2.2.3.1
461                                  *
462                                  * 3 bytes IANA/SMI vendor code
463                                  * 1 byte component (i.e. within vendor, which SoH component
464                                  */
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);
468                                 break;
469
470                         case 7:
471                                 /* Vendor-Specific packet
472                                  * MS-SOH 2.2.3.3
473                                  *
474                                  * 4 bytes vendor, supposedly ignored by NAP
475                                  * N bytes payload; for Microsoft component#0 this is the MS TV stuff
476                                  */
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);
480                                 } else {
481                                         RDEBUG2("SoH unhandled vendor-specific TLV %08x/component=%i %i bytes payload", curr_shid, curr_shid_c, tlv.tlv_len);
482                                 }
483                                 break;
484
485                         case 8:
486                                 /* Health-Class
487                                  * MS-SOH 2.2.3.5.6
488                                  *
489                                  * 1 byte integer
490                                  */
491                                 RDEBUG2("SoH Health-Class %i", data[0]);
492                                 curr_hc = data[0];
493                                 break;
494
495                         case 9:
496                                 /* Software-Version
497                                  * MS-SOH 2.2.3.5.7
498                                  *
499                                  * 1 byte integer
500                                  */
501                                 RDEBUG2("SoH Software-Version %i", data[0]);
502                                 break;
503
504                         case 11:
505                                 /* Health-Class status
506                                  * MS-SOH 2.2.3.5.9
507                                  *
508                                  * variable data; for the MS System Health vendor, these are 4-byte
509                                  * integers which are a really, really dumb format:
510                                  *
511                                  *  28 bits ignore
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
516                                  */
517                                 RDEBUG2("SoH Health-Class-Status - current shid=%08x component=%i", curr_shid, curr_shid_c);
518
519                                 if (curr_shid==0x137 && curr_shid_c==128) {
520
521                                         char const *s, *t;
522                                         uint32_t hcstatus = soh_pull_be_32(data);
523
524                                         RDEBUG2("SoH Health-Class-Status microsoft DWORD=%08x", hcstatus);
525
526                                         vp = pairmake_packet("SoH-MS-Windows-Health-Status", NULL, T_OP_EQ);
527                                         if (!vp) return 0;
528
529                                         switch (curr_hc) {
530                                                 case 4:
531                                                         /* security updates */
532                                                         s = "security-updates";
533                                                         switch (hcstatus) {
534                                                                 case 0xff0005:
535                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok all-installed", s);
536                                                                         break;
537                                                                 case 0xff0006:
538                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn some-missing", s);
539                                                                         break;
540                                                                 case 0xff0008:
541                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn never-started", s);
542                                                                         break;
543                                                                 case 0xc0ff000c:
544                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error no-wsus-srv", s);
545                                                                         break;
546                                                                 case 0xc0ff000d:
547                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error no-wsus-clid", s);
548                                                                         break;
549                                                                 case 0xc0ff000e:
550                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn wsus-disabled", s);
551                                                                         break;
552                                                                 case 0xc0ff000f:
553                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error comm-failure", s);
554                                                                         break;
555                                                                 case 0xc0ff0010:
556                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn needs-reboot", s);
557                                                                         break;
558                                                                 default:
559                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
560                                                                         break;
561                                                         }
562                                                         break;
563
564                                                 case 3:
565                                                         /* auto updates */
566                                                         s = "auto-updates";
567                                                         switch (hcstatus) {
568                                                                 case 1:
569                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn disabled", s);
570                                                                         break;
571                                                                 case 2:
572                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=check-only", s);
573                                                                         break;
574                                                                 case 3:
575                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=download", s);
576                                                                         break;
577                                                                 case 4:
578                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=install", s);
579                                                                         break;
580                                                                 case 5:
581                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn unconfigured", s);
582                                                                         break;
583                                                                 case 0xc0ff0003:
584                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn service-down", s);
585                                                                         break;
586                                                                 case 0xc0ff0018:
587                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn never-started", s);
588                                                                         break;
589                                                                 default:
590                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
591                                                                         break;
592                                                         }
593                                                         break;
594
595                                                 default:
596                                                         /* other - firewall, antivirus, antispyware */
597                                                         s = healthclass2str(curr_hc);
598                                                         if (s) {
599                                                                 /* bah. this is vile. stupid microsoft
600                                                                  */
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
604                                                                          */
605                                                                         t = clientstatus2str(hcstatus);
606                                                                         if (t) {
607                                                                                 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %s", s, t);
608                                                                         } else {
609                                                                                 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
610                                                                         }
611                                                                 } else {
612                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue),
613                                                                                         "%s ok snoozed=%i microsoft=%i up2date=%i enabled=%i",
614                                                                                         s,
615                                                                                         hcstatus & 0x8 ? 1 : 0,
616                                                                                         hcstatus & 0x4 ? 1 : 0,
617                                                                                         hcstatus & 0x2 ? 1 : 0,
618                                                                                         hcstatus & 0x1 ? 1 : 0
619                                                                                         );
620                                                                 }
621                                                         } else {
622                                                                 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%i unknown %08x", curr_hc, hcstatus);
623                                                         }
624                                                         break;
625                                         }
626                                 } else {
627                                         vp = pairmake_packet("SoH-MS-Health-Other", NULL, T_OP_EQ);
628                                         if (!vp) return 0;
629
630                                         /* FIXME: what to do with the payload? */
631                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%08x/%i ?", curr_shid, curr_shid_c);
632                                 }
633                                 break;
634
635                         default:
636                                 RDEBUG("SoH Unknown TLV %i len=%i", tlv.tlv_type, tlv.tlv_len);
637                                 break;
638                 }
639
640                 data += tlv.tlv_len;
641                 data_len -= tlv.tlv_len;
642
643         }
644
645         return 0;
646 }