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