#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/soh.h>
typedef struct {
uint16_t tlv_type;
uint16_t tlv_len;
uint32_t tlv_vendor;
uint16_t soh_type;
uint16_t soh_len;
} eap_soh;
typedef struct {
uint16_t outer_type;
uint16_t outer_len;
uint32_t vendor;
uint16_t inner_type;
uint16_t inner_len;
} soh_response;
typedef struct {
uint16_t outer_type;
uint16_t outer_len;
uint32_t vendor;
uint8_t corrid[24];
uint8_t intent;
uint8_t content_type;
} soh_mode_subheader;
typedef struct {
uint16_t tlv_type;
uint16_t tlv_len;
} soh_tlv;
uint16_t soh_pull_be_16(const uint8_t *p) {
uint16_t r;
r = *p++ << 8;
r += *p++;
return r;
}
uint32_t soh_pull_be_24(const uint8_t *p) {
uint32_t r;
r = *p++ << 16;
r += *p++ << 8;
r += *p++;
return r;
}
uint32_t soh_pull_be_32(const uint8_t *p) {
uint32_t r;
r = *p++ << 24;
r += *p++ << 16;
r += *p++ << 8;
r += *p++;
return r;
}
static int eapsoh_mstlv(REQUEST *request, VALUE_PAIR *sohvp, const uint8_t *p, unsigned int data_len) {
VALUE_PAIR *vp;
uint8_t c;
int t;
while (data_len > 0) {
c = *p++;
data_len--;
switch (c) {
case 1:
if (data_len < 18) {
RDEBUG("insufficient data for MS-Machine-Inventory-Packet");
return 0;
}
data_len -= 18;
vp = pairmake("SoH-MS-Machine-OS-vendor", "Microsoft", T_OP_EQ);
if (!vp) return 0;
pairadd(&sohvp, vp);
vp = pairmake("SoH-MS-Machine-OS-version", NULL, T_OP_EQ);
if (!vp) return 0;
vp->vp_integer = soh_pull_be_32(p); p+=4;
pairadd(&sohvp, vp);
vp = pairmake("SoH-MS-Machine-OS-release", NULL, T_OP_EQ);
if (!vp) return 0;
vp->vp_integer = soh_pull_be_32(p); p+=4;
pairadd(&sohvp, vp);
vp = pairmake("SoH-MS-Machine-OS-build", NULL, T_OP_EQ);
if (!vp) return 0;
vp->vp_integer = soh_pull_be_32(p); p+=4;
pairadd(&sohvp, vp);
vp = pairmake("SoH-MS-Machine-SP-version", NULL, T_OP_EQ);
if (!vp) return 0;
vp->vp_integer = soh_pull_be_16(p); p+=2;
pairadd(&sohvp, vp);
vp = pairmake("SoH-MS-Machine-SP-release", NULL, T_OP_EQ);
if (!vp) return 0;
vp->vp_integer = soh_pull_be_16(p); p+=2;
pairadd(&sohvp, vp);
vp = pairmake("SoH-MS-Machine-Processor", NULL, T_OP_EQ);
if (!vp) return 0;
vp->vp_integer = soh_pull_be_16(p); p+=2;
pairadd(&sohvp, vp);
break;
case 2:
p += 10;
t = soh_pull_be_16(p);
p += 2;
p += t;
data_len -= 12 + t;
break;
case 3:
RDEBUG3("SoH MS-Packet-Info %s vers=%i", *p & 0x10 ? "request" : "response", *p & 0xf);
p++;
data_len--;
break;
case 4:
t = soh_pull_be_16(p);
p += 2;
p += t;
data_len -= 2 + t;
break;
case 5:
t = soh_pull_be_16(p);
p += 2;
vp = pairmake("SoH-MS-Machine-Name", NULL, T_OP_EQ);
if (!vp) return 0;
memcpy(vp->vp_strvalue, p, t);
vp->vp_strvalue[t] = 0;
pairadd(&sohvp, vp);
p += t;
data_len -= 2 + t;
break;
case 6:
vp = pairmake("SoH-MS-Correlation-Id", NULL, T_OP_EQ);
if (!vp) return 0;
memcpy(vp->vp_octets, p, 24);
vp->length = 24;
pairadd(&sohvp, vp);
p += 24;
data_len -= 24;
break;
case 7:
t = soh_pull_be_16(p);
p += 2;
p += t;
data_len -= 2 + t;
break;
case 8:
p += 4;
vp = pairmake("SoH-MS-Machine-Role", NULL, T_OP_EQ);
if (!vp) return 0;
vp->vp_integer = *p;
pairadd(&sohvp, vp);
p++;
data_len -= 5;
break;
default:
RDEBUG("SoH Unknown MS TV %i stopping", c);
return 0;
}
}
return 1;
}
static const char* clientstatus2str(uint32_t hcstatus) {
switch (hcstatus) {
case 0xff0005:
return "wua-ok";
case 0xff0006:
return "wua-missing";
case 0xff0008:
return "wua-not-started";
case 0xc0ff000c:
return "wua-no-wsus-server";
case 0xc0ff000d:
return "wua-no-wsus-clientid";
case 0xc0ff000e:
return "wua-disabled";
case 0xc0ff000f:
return "wua-comm-failure";
case 0xc0ff0002:
return "not-installed";
case 0xc0ff0003:
return "down";
case 0xc0ff0018:
return "not-started";
}
return NULL;
}
static const char* healthclass2str(uint8_t hc) {
switch (hc) {
case 0:
return "firewall";
case 1:
return "antivirus";
case 2:
return "antispyware";
case 3:
return "updates";
case 4:
return "security-updates";
}
return NULL;
}
int soh_verify(REQUEST *request, VALUE_PAIR *sohvp, const uint8_t *data, unsigned int data_len) {
VALUE_PAIR *vp;
eap_soh hdr;
soh_response resp;
soh_mode_subheader mode;
soh_tlv tlv;
int curr_shid=-1, curr_shid_c=-1, curr_hc=-1;
hdr.tlv_type = soh_pull_be_16(data); data += 2;
hdr.tlv_len = soh_pull_be_16(data); data += 2;
hdr.tlv_vendor = soh_pull_be_32(data); data += 4;
if (hdr.tlv_type != 7 || hdr.tlv_vendor != 0x137) {
RDEBUG("SoH payload is %i %08x not a ms-vendor packet", hdr.tlv_type, hdr.tlv_vendor);
return -1;
}
hdr.soh_type = soh_pull_be_16(data); data += 2;
hdr.soh_len = soh_pull_be_16(data); data += 2;
if (hdr.soh_type != 1) {
RDEBUG("SoH tlv %04x is not a response", hdr.soh_type);
return -1;
}
resp.outer_type = soh_pull_be_16(data); data += 2;
resp.outer_len = soh_pull_be_16(data); data += 2;
resp.vendor = soh_pull_be_32(data); data += 4;
resp.inner_type = soh_pull_be_16(data); data += 2;
resp.inner_len = soh_pull_be_16(data); data += 2;
if (resp.outer_type!=7 || resp.vendor != 0x137) {
RDEBUG("SoH response outer type %i/vendor %08x not recognised", resp.outer_type, resp.vendor);
return -1;
}
switch (resp.inner_type) {
case 1:
RDEBUG("SoH without mode subheader");
break;
case 2:
mode.outer_type = soh_pull_be_16(data); data += 2;
mode.outer_len = soh_pull_be_16(data); data += 2;
mode.vendor = soh_pull_be_32(data); data += 4;
memcpy(mode.corrid, data, 24); data += 24;
mode.intent = data[0];
mode.content_type = data[1];
data += 2;
if (mode.outer_type != 7 || mode.vendor != 0x137 || mode.content_type != 0) {
RDEBUG3("SoH mode subheader outer type %i/vendor %08x/content type %i invalid", mode.outer_type, mode.vendor, mode.content_type);
return -1;
}
RDEBUG3("SoH with mode subheader");
break;
default:
RDEBUG("SoH invalid inner type %i", resp.inner_type);
return -1;
}
if (resp.inner_type==2) {
data_len = resp.inner_len - 34;
} else {
data_len = resp.inner_len;
}
while (data_len >= 4) {
tlv.tlv_type = soh_pull_be_16(data); data += 2;
tlv.tlv_len = soh_pull_be_16(data); data += 2;
data_len -= 4;
switch (tlv.tlv_type) {
case 2:
curr_shid = soh_pull_be_24(data);
curr_shid_c = data[3];
RDEBUG2("SoH System-Health-ID vendor %08x component=%i", curr_shid, curr_shid_c);
break;
case 7:
if (curr_shid==0x137 && curr_shid_c==0) {
RDEBUG2("SoH MS type-value payload");
eapsoh_mstlv(request, sohvp, data + 4, tlv.tlv_len - 4);
} else {
RDEBUG2("SoH unhandled vendor-specific TLV %08x/component=%i %i bytes payload", curr_shid, curr_shid_c, tlv.tlv_len);
}
break;
case 8:
RDEBUG2("SoH Health-Class %i", data[0]);
curr_hc = data[0];
break;
case 9:
RDEBUG2("SoH Software-Version %i", data[0]);
break;
case 11:
RDEBUG2("SoH Health-Class-Status - current shid=%08x component=%i", curr_shid, curr_shid_c);
if (curr_shid==0x137 && curr_shid_c==128) {
const char *s, *t;
uint32_t hcstatus = soh_pull_be_32(data);
RDEBUG2("SoH Health-Class-Status microsoft DWORD=%08x", hcstatus);
vp = pairmake("SoH-MS-Windows-Health-Status", NULL, T_OP_EQ);
if (!vp) return 0;
switch (curr_hc) {
case 4:
s = "security-updates";
switch (hcstatus) {
case 0xff0005:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok all-installed", s);
break;
case 0xff0006:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn some-missing", s);
break;
case 0xff0008:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn never-started", s);
break;
case 0xc0ff000c:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error no-wsus-srv", s);
break;
case 0xc0ff000d:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error no-wsus-clid", s);
break;
case 0xc0ff000e:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn wsus-disabled", s);
break;
case 0xc0ff000f:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error comm-failure", s);
break;
case 0xc0ff0010:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn needs-reboot", s);
break;
default:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
break;
}
break;
case 3:
s = "auto-updates";
switch (hcstatus) {
case 1:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn disabled", s);
break;
case 2:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=check-only", s);
break;
case 3:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=download", s);
break;
case 4:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=install", s);
break;
case 5:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn unconfigured", s);
break;
case 0xc0ff0003:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn service-down", s);
break;
case 0xc0ff0018:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn never-started", s);
break;
default:
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
break;
}
break;
default:
s = healthclass2str(curr_hc);
if (s) {
if (hcstatus & 0xff000000) {
t = clientstatus2str(hcstatus);
if (t) {
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %s", s, t);
} else {
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
}
} else {
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue),
"%s ok snoozed=%i microsoft=%i up2date=%i enabled=%i",
s,
hcstatus & 0x8 ? 1 : 0,
hcstatus & 0x4 ? 1 : 0,
hcstatus & 0x2 ? 1 : 0,
hcstatus & 0x1 ? 1 : 0
);
}
} else {
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%i unknown %08x", curr_hc, hcstatus);
}
break;
}
} else {
vp = pairmake("SoH-MS-Health-Other", NULL, T_OP_EQ);
if (!vp) return 0;
snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%08x/%i ?", curr_shid, curr_shid_c);
}
pairadd(&sohvp, vp);
break;
default:
RDEBUG("SoH Unknown TLV %i len=%i", tlv.tlv_type, tlv.tlv_len);
break;
}
data += tlv.tlv_len;
data_len -= tlv.tlv_len;
}
return 0;
}