Use handler mutex for checks, not session mutex
[freeradius.git] / src / lib / soh.c
1 /*
2  * soh.c contains the interfaces that are called from eap
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  *   Copyright 2010 Phil Mayers <p.mayers@imperial.ac.uk>
21  */
22
23 #include <freeradius-devel/ident.h>
24 RCSID("$Id$")
25
26 #include <freeradius-devel/soh.h>
27
28 /*
29  * This code implements parsing of MS-SOH data into FreeRadius AVPs
30  * allowing for FreeRadius MS-NAP policies
31  */
32
33 typedef struct {
34         uint16_t tlv_type;      /* ==7 */
35         uint16_t tlv_len;
36         uint32_t tlv_vendor;
37
38         /* then it's either an soh request or response */
39         uint16_t soh_type;      /* ==2 for request, 1 for response */
40         uint16_t soh_len;
41
42         /* an soh-response may now follow... */
43 } eap_soh;
44
45 typedef struct {
46         uint16_t outer_type;
47         uint16_t outer_len;
48         uint32_t vendor;
49         uint16_t inner_type;
50         uint16_t inner_len;
51 } soh_response;
52
53 typedef struct {
54         uint16_t outer_type;
55         uint16_t outer_len;
56         uint32_t vendor;
57         uint8_t corrid[24];
58         uint8_t intent;
59         uint8_t content_type;
60 } soh_mode_subheader;
61
62 typedef struct {
63         uint16_t tlv_type;
64         uint16_t tlv_len;
65 } soh_tlv;
66
67 /* utils for pulling big-endian 2/3/4 byte integers
68  * caller must ensure enough data exists at "p"
69  */
70 uint16_t soh_pull_be_16(const uint8_t *p) {
71         uint16_t r;
72
73         r = *p++ << 8;
74         r += *p++;
75
76         return r;
77 }
78 uint32_t soh_pull_be_24(const uint8_t *p) {
79         uint32_t r;
80
81         r = *p++ << 16;
82         r += *p++ << 8;
83         r += *p++;
84
85         return r;
86 }
87 uint32_t soh_pull_be_32(const uint8_t *p) {
88         uint32_t r;
89
90         r = *p++ << 24;
91         r += *p++ << 16;
92         r += *p++ << 8;
93         r += *p++;
94
95         return r;
96 }
97
98 /*
99  * This parses the microsoft type/value (note: NOT type/length/value) data; see
100  * section 2.2.4 of MS-SOH. Because there's no "length" field we CANNOT just skip
101  * unknown types; we need to know their length ahead of time. Therefore, we abort
102  * if we find an unknown type.
103  */
104 static int eapsoh_mstlv(VALUE_PAIR *sohvp, const uint8_t *p, unsigned int data_len) {
105         VALUE_PAIR *vp;
106         uint8_t c;
107         int t;
108
109         while (data_len > 0) {
110                 c = *p++;
111                 data_len--;
112
113                 switch (c) {
114                         case 1:
115                                 /* MS-Machine-Inventory-Packet
116                                  * MS-SOH section 2.2.4.1
117                                  */
118                                 if (data_len < 18) {
119                                         DEBUG("insufficient data for MS-Machine-Inventory-Packet");
120                                         return 0;
121                                 }
122                                 data_len -= 18;
123
124                                 vp = pairmake("SoH-MS-Machine-OS-vendor", "Microsoft", T_OP_EQ);
125                                 pairadd(&sohvp, vp);
126
127                                 vp = pairmake("SoH-MS-Machine-OS-version", NULL, T_OP_EQ);
128                                 vp->vp_integer = soh_pull_be_32(p); p+=4;
129                                 pairadd(&sohvp, vp);
130
131                                 vp = pairmake("SoH-MS-Machine-OS-release", NULL, T_OP_EQ);
132                                 vp->vp_integer = soh_pull_be_32(p); p+=4;
133                                 pairadd(&sohvp, vp);
134
135                                 vp = pairmake("SoH-MS-Machine-OS-build", NULL, T_OP_EQ);
136                                 vp->vp_integer = soh_pull_be_32(p); p+=4;
137                                 pairadd(&sohvp, vp);
138
139                                 vp = pairmake("SoH-MS-Machine-SP-version", NULL, T_OP_EQ);
140                                 vp->vp_integer = soh_pull_be_16(p); p+=2;
141                                 pairadd(&sohvp, vp);
142
143                                 vp = pairmake("SoH-MS-Machine-SP-release", NULL, T_OP_EQ);
144                                 vp->vp_integer = soh_pull_be_16(p); p+=2;
145                                 pairadd(&sohvp, vp);
146
147                                 vp = pairmake("SoH-MS-Machine-Processor", NULL, T_OP_EQ);
148                                 vp->vp_integer = soh_pull_be_16(p); p+=2;
149                                 pairadd(&sohvp, vp);
150
151                                 break;
152
153                         case 2:
154                                 /* MS-Quarantine-State - FIXME: currently unhandled
155                                  * MS-SOH 2.2.4.1
156                                  *
157                                  * 1 byte reserved
158                                  * 1 byte flags
159                                  * 8 bytes NT Time field (100-nanosec since 1 Jan 1601)
160                                  * 2 byte urilen
161                                  * N bytes uri
162                                  */
163                                 p += 10;
164                                 t = soh_pull_be_16(p);  /* t == uri len */
165                                 p += 2;
166                                 p += t;
167                                 data_len -= 12 + t;
168                                 break;
169
170                         case 3:
171                                 /* MS-Packet-Info
172                                  * MS-SOH 2.2.4.3
173                                  */
174                                 DEBUG("SoH MS-Packet-Info %s vers=%i", *p & 0x10 ? "request" : "response", *p & 0xf);
175                                 p++;
176                                 data_len--;
177                                 break;
178
179                         case 4:
180                                 /* MS-SystemGenerated-Ids - FIXME: currently unhandled
181                                  * MS-SOH 2.2.4.4
182                                  *
183                                  * 2 byte length
184                                  * N bytes (3 bytes IANA enterprise# + 1 byte component id#)
185                                  */
186                                 t = soh_pull_be_16(p);
187                                 p += 2;
188                                 p += t;
189                                 data_len -= 2 + t;
190                                 break;
191
192                         case 5:
193                                 /* MS-MachineName
194                                  * MS-SOH 2.2.4.5
195                                  *
196                                  * 1 byte namelen
197                                  * N bytes name
198                                  */
199                                 t = soh_pull_be_16(p);
200                                 p += 2;
201
202                                 vp = pairmake("SoH-MS-Machine-Name", NULL, T_OP_EQ);
203                                 memcpy(vp->vp_strvalue, p, t);
204                                 vp->vp_strvalue[t] = 0;
205
206                                 pairadd(&sohvp, vp);
207                                 p += t;
208                                 data_len -= 2 + t;
209                                 break;
210
211                         case 6:
212                                 /* MS-CorrelationId
213                                  * MS-SOH 2.2.4.6
214                                  *
215                                  * 24 bytes opaque binary which we might, in future, have
216                                  * to echo back to the client in a final SoHR
217                                  */
218                                 vp = pairmake("SoH-MS-Correlation-Id", NULL, T_OP_EQ);
219                                 memcpy(vp->vp_octets, p, 24);
220                                 vp->length = 24;
221                                 pairadd(&sohvp, vp);
222                                 p += 24;
223                                 data_len -= 24;
224                                 break;
225
226                         case 7:
227                                 /* MS-Installed-Shvs - FIXME: currently unhandled
228                                  * MS-SOH 2.2.4.7
229                                  *
230                                  * 2 bytes length
231                                  * N bytes (3 bytes IANA enterprise# + 1 byte component id#)
232                                  */
233                                 t = soh_pull_be_16(p);
234                                 p += 2;
235                                 p += t;
236                                 data_len -= 2 + t;
237                                 break;
238
239                         case 8:
240                                 /* MS-Machine-Inventory-Ex
241                                  * MS-SOH 2.2.4.8
242                                  *
243                                  * 4 bytes reserved
244                                  * 1 byte product type (client=1 domain_controller=2 server=3)
245                                  */
246                                 p += 4;
247                                 vp = pairmake("SoH-MS-Machine-Role", NULL, T_OP_EQ);
248                                 vp->vp_integer = *p;
249                                 pairadd(&sohvp, vp);
250                                 p++;
251                                 data_len -= 5;
252                                 break;
253
254                         default:
255                                 DEBUG("SoH Unknown MS TV %i stopping", c);
256                                 return 0;
257                 }
258         }
259         return 1;
260 }
261
262 static const char* clientstatus2str(uint32_t hcstatus) {
263         switch (hcstatus) {
264                 /* this lot should all just be for windows updates */
265                 case 0xff0005:
266                         return "wua-ok";
267                 case 0xff0006:
268                         return "wua-missing";
269                 case 0xff0008:
270                         return "wua-not-started";
271                 case 0xc0ff000c:
272                         return "wua-no-wsus-server";
273                 case 0xc0ff000d:
274                         return "wua-no-wsus-clientid";
275                 case 0xc0ff000e:
276                         return "wua-disabled";
277                 case 0xc0ff000f:
278                         return "wua-comm-failure";
279
280                 /* these next 3 are for all health-classes */
281                 case 0xc0ff0002:
282                         return "not-installed";
283                 case 0xc0ff0003:
284                         return "down";
285                 case 0xc0ff0018:
286                         return "not-started";
287         }
288         return NULL;
289 }
290
291 static const char* healthclass2str(uint8_t hc) {
292         switch (hc) {
293                 case 0:
294                         return "firewall";
295                 case 1:
296                         return "antivirus";
297                 case 2:
298                         return "antispyware";
299                 case 3:
300                         return "updates";
301                 case 4:
302                         return "security-updates";
303         }
304         return NULL;
305 }
306
307 int soh_verify(VALUE_PAIR *sohvp, const uint8_t *data, unsigned int data_len) {
308
309         VALUE_PAIR *vp;
310         eap_soh hdr;
311         soh_response resp;
312         soh_mode_subheader mode;
313         soh_tlv tlv;
314         int curr_shid=-1, curr_shid_c=-1, curr_hc=-1;
315
316         hdr.tlv_type = soh_pull_be_16(data); data += 2;
317         hdr.tlv_len = soh_pull_be_16(data); data += 2;
318         hdr.tlv_vendor = soh_pull_be_32(data); data += 4;
319
320         if (hdr.tlv_type != 7 || hdr.tlv_vendor != 0x137) {
321                 fr_strerror_printf("SoH payload is %i %08x not a ms-vendor packet", hdr.tlv_type, hdr.tlv_vendor);
322                 return -1;
323         }
324
325         hdr.soh_type = soh_pull_be_16(data); data += 2;
326         hdr.soh_len = soh_pull_be_16(data); data += 2;
327         if (hdr.soh_type != 1) {
328                 fr_strerror_printf("SoH tlv %04x is not a response", hdr.soh_type);
329                 return -1;
330         }
331
332         /* FIXME: check for sufficient data */
333         resp.outer_type = soh_pull_be_16(data); data += 2;
334         resp.outer_len = soh_pull_be_16(data); data += 2;
335         resp.vendor = soh_pull_be_32(data); data += 4;
336         resp.inner_type = soh_pull_be_16(data); data += 2;
337         resp.inner_len = soh_pull_be_16(data); data += 2;
338
339
340         if (resp.outer_type!=7 || resp.vendor != 0x137) {
341                 fr_strerror_printf("SoH response outer type %i/vendor %08x not recognised", resp.outer_type, resp.vendor);
342                 return -1;
343         }
344         switch (resp.inner_type) {
345                 case 1:
346                         /* no mode sub-header */
347                         fr_strerror_printf("SoH without mode subheader");
348                         break;
349                 case 2:
350                         mode.outer_type = soh_pull_be_16(data); data += 2;
351                         mode.outer_len = soh_pull_be_16(data); data += 2;
352                         mode.vendor = soh_pull_be_32(data); data += 4;
353                         memcpy(mode.corrid, data, 24); data += 24;
354                         mode.intent = data[0];
355                         mode.content_type = data[1];
356                         data += 2;
357
358                         if (mode.outer_type != 7 || mode.vendor != 0x137 || mode.content_type != 0) {
359                                 fr_strerror_printf("SoH mode subheader outer type %i/vendor %08x/content type %i invalid", mode.outer_type, mode.vendor, mode.content_type);
360                                 return -1;
361                         }
362                         DEBUG("SoH with mode subheader");
363                         break;
364                 default:
365                         fr_strerror_printf("SoH invalid inner type %i", resp.inner_type);
366                         return -1;
367         }
368
369         /* subtract off the relevant amount of data */
370         if (resp.inner_type==2) {
371                 data_len = resp.inner_len - 34;
372         } else {
373                 data_len = resp.inner_len;
374         }
375
376         /* TLV
377          * MS-SOH 2.2.1
378          * See also 2.2.3
379          *
380          * 1 bit mandatory
381          * 1 bit reserved
382          * 14 bits tlv type
383          * 2 bytes tlv length
384          * N bytes payload
385          *
386          */
387         while (data_len >= 4) {
388                 tlv.tlv_type = soh_pull_be_16(data); data += 2;
389                 tlv.tlv_len = soh_pull_be_16(data); data += 2;
390
391                 data_len -= 4;
392
393                 switch (tlv.tlv_type) {
394                         case 2:
395                                 /* System-Health-Id TLV
396                                  * MS-SOH 2.2.3.1
397                                  *
398                                  * 3 bytes IANA/SMI vendor code
399                                  * 1 byte component (i.e. within vendor, which SoH component
400                                  */
401                                 curr_shid = soh_pull_be_24(data);
402                                 curr_shid_c = data[3];
403                                 DEBUG("SoH System-Health-ID vendor %08x component=%i", curr_shid, curr_shid_c);
404                                 break;
405
406                         case 7:
407                                 /* Vendor-Specific packet
408                                  * MS-SOH 2.2.3.3
409                                  *
410                                  * 4 bytes vendor, supposedly ignored by NAP
411                                  * N bytes payload; for Microsoft component#0 this is the MS TV stuff
412                                  */
413                                 if (curr_shid==0x137 && curr_shid_c==0) {
414                                         DEBUG("SoH MS type-value payload");
415                                         eapsoh_mstlv(sohvp, data + 4, tlv.tlv_len - 4);
416                                 } else {
417                                         fr_strerror_printf("SoH unhandled vendor-specific TLV %08x/component=%i %i bytes payload", curr_shid, curr_shid_c, tlv.tlv_len);
418                                 }
419                                 break;
420
421                         case 8:
422                                 /* Health-Class
423                                  * MS-SOH 2.2.3.5.6
424                                  *
425                                  * 1 byte integer
426                                  */
427                                 DEBUG("SoH Health-Class %i", data[0]);
428                                 curr_hc = data[0];
429                                 break;
430
431                         case 9:
432                                 /* Software-Version
433                                  * MS-SOH 2.2.3.5.7
434                                  *
435                                  * 1 byte integer
436                                  */
437                                 DEBUG("SoH Software-Version %i", data[0]);
438                                 break;
439
440                         case 11:
441                                 /* Health-Class status
442                                  * MS-SOH 2.2.3.5.9
443                                  *
444                                  * variable data; for the MS System Health vendor, these are 4-byte
445                                  * integers which are a really, really dumb format:
446                                  *
447                                  *  28 bits ignore
448                                  *  1 bit - 1==product snoozed
449                                  *  1 bit - 1==microsoft product
450                                  *  1 bit - 1==product up-to-date
451                                  *  1 bit - 1==product enabled
452                                  */
453                                 DEBUG("SoH Health-Class-Status - current shid=%08x component=%i", curr_shid, curr_shid_c);
454
455                                 if (curr_shid==0x137 && curr_shid_c==128) {
456
457                                         const char *s, *t;
458                                         uint32_t hcstatus = soh_pull_be_32(data);
459
460                                         DEBUG("SoH Health-Class-Status microsoft DWORD=%08x", hcstatus);
461
462                                         vp = pairmake("SoH-MS-Windows-Health-Status", NULL, T_OP_EQ);
463                                         switch (curr_hc) {
464                                                 case 4:
465                                                         /* security updates */
466                                                         s = "security-updates";
467                                                         switch (hcstatus) {
468                                                                 case 0xff0005:
469                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok all-installed", s);
470                                                                         break;
471                                                                 case 0xff0006:
472                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn some-missing", s);
473                                                                         break;
474                                                                 case 0xff0008:
475                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn never-started", s);
476                                                                         break;
477                                                                 case 0xc0ff000c:
478                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error no-wsus-srv", s);
479                                                                         break;
480                                                                 case 0xc0ff000d:
481                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error no-wsus-clid", s);
482                                                                         break;
483                                                                 case 0xc0ff000e:
484                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn wsus-disabled", s);
485                                                                         break;
486                                                                 case 0xc0ff000f:
487                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error comm-failure", s);
488                                                                         break;
489                                                                 case 0xc0ff0010:
490                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn needs-reboot", s);
491                                                                         break;
492                                                                 default:
493                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
494                                                                         break;
495                                                         }
496                                                         break;
497
498                                                 case 3:
499                                                         /* auto updates */
500                                                         s = "auto-updates";
501                                                         switch (hcstatus) {
502                                                                 case 1:
503                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn disabled", s);
504                                                                         break;
505                                                                 case 2:
506                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=check-only", s);
507                                                                         break;
508                                                                 case 3:
509                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=download", s);
510                                                                         break;
511                                                                 case 4:
512                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=install", s);
513                                                                         break;
514                                                                 case 5:
515                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn unconfigured", s);
516                                                                         break;
517                                                                 case 0xc0ff0003:
518                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn service-down", s);
519                                                                         break;
520                                                                 case 0xc0ff0018:
521                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn never-started", s);
522                                                                         break;
523                                                                 default:
524                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
525                                                                         break;
526                                                         }
527                                                         break;
528
529                                                 default:
530                                                         /* other - firewall, antivirus, antispyware */
531                                                         s = healthclass2str(curr_hc);
532                                                         if (s) {
533                                                                 /* bah. this is vile. stupid microsoft
534                                                                  */
535                                                                 if (hcstatus & 0xff000000) {
536                                                                         /* top octet non-zero means an error
537                                                                          * FIXME: is this always correct? MS-WSH 2.2.8 is unclear
538                                                                          */
539                                                                         t = clientstatus2str(hcstatus);
540                                                                         if (t) {
541                                                                                 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %s", s, t);
542                                                                         } else {
543                                                                                 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
544                                                                         }
545                                                                 } else {
546                                                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue),
547                                                                                         "%s ok snoozed=%i microsoft=%i up2date=%i enabled=%i",
548                                                                                         s,
549                                                                                         hcstatus & 0x8 ? 1 : 0,
550                                                                                         hcstatus & 0x4 ? 1 : 0,
551                                                                                         hcstatus & 0x2 ? 1 : 0,
552                                                                                         hcstatus & 0x1 ? 1 : 0
553                                                                                         );
554                                                                 }
555                                                         } else {
556                                                                 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%i unknown %08x", curr_hc, hcstatus);
557                                                         }
558                                                         break;
559                                         }
560                                 } else {
561                                         vp = pairmake("SoH-Other-Health-Status", NULL, T_OP_EQ);
562                                         /* FIXME: what to do with the payload? */
563                                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%08x/%i ?", curr_shid, curr_shid_c);
564                                 }
565                                 pairadd(&sohvp, vp);
566                                 break;
567
568                         default:
569                                 DEBUG("SoH Unknown TLV %i len=%i", tlv.tlv_type, tlv.tlv_len);
570                                 break;
571                 }
572
573                 data += tlv.tlv_len;
574                 data_len -= tlv.tlv_len;
575
576         }
577
578         return 0;
579 }