/* * This hack strips out Cisco's VSA duplicities in lines * (Cisco not implemented VSA's in standard way. * * Cisco sends it's VSA attributes with the attribute name *again* * in the string, like: H323-Attribute = "h323-attribute=value". * This sort of behaviour is nonsense. */ static void cisco_vsa_hack(REQUEST *request) { int vendorcode; char *ptr; char newattr[MAX_STRING_LEN]; VALUE_PAIR *vp; vp_cursor_t cursor; for (vp = fr_cursor_init(&cursor, &request->packet->vps); vp; vp = fr_cursor_next(&cursor)) { vendorcode = vp->da->vendor; if (!((vendorcode == 9) || (vendorcode == 6618))) { continue; /* not a Cisco or Quintum VSA, continue */ } if (vp->da->type != PW_TYPE_STRING) { continue; } /* * No weird packing. Ignore it. */ ptr = strchr(vp->vp_strvalue, '='); /* find an '=' */ if (!ptr) { continue; } /* * Cisco-AVPair's get packed as: * * Cisco-AVPair = "h323-foo-bar = baz" * Cisco-AVPair = "h323-foo-bar=baz" * * which makes sense only if you're a lunatic. * This code looks for the attribute named inside * of the string, and if it exists, adds it as a new * attribute. */ if (vp->da->attr == 1) { char const *p; p = vp->vp_strvalue; gettoken(&p, newattr, sizeof(newattr), false); if (dict_attrbyname(newattr) != NULL) { pair_make_request(newattr, ptr + 1, T_OP_EQ); } } else { /* h322-foo-bar = "h323-foo-bar = baz" */ /* * We strip out the duplicity from the * value field, we use only the value on * the right side of the '=' character. */ fr_pair_value_strcpy(vp, ptr + 1); } } }
/** Parse the MS-SOH response in data and update sohvp * * Note that sohvp might still have been updated in event of a failure. * * @param request Current request * @param data MS-SOH blob * @param data_len length of MS-SOH blob * * @return * - 0 on success. * - -1 on failure. */ int soh_verify(REQUEST *request, uint8_t const *data, unsigned int data_len) { VALUE_PAIR *vp; eap_soh hdr; soh_response resp; soh_mode_subheader mode; soh_tlv tlv; int curr_shid=-1, curr_shid_c=-1, curr_hc=-1; rad_assert(request->packet != NULL); hdr.tlv_type = soh_pull_be_16(data); data += 2; hdr.tlv_len = soh_pull_be_16(data); data += 2; hdr.tlv_vendor = soh_pull_be_32(data); data += 4; if (hdr.tlv_type != 7 || hdr.tlv_vendor != 0x137) { RDEBUG("SoH payload is %i %08x not a ms-vendor packet", hdr.tlv_type, hdr.tlv_vendor); return -1; } hdr.soh_type = soh_pull_be_16(data); data += 2; hdr.soh_len = soh_pull_be_16(data); data += 2; if (hdr.soh_type != 1) { RDEBUG("SoH tlv %04x is not a response", hdr.soh_type); return -1; } /* FIXME: check for sufficient data */ resp.outer_type = soh_pull_be_16(data); data += 2; resp.outer_len = soh_pull_be_16(data); data += 2; resp.vendor = soh_pull_be_32(data); data += 4; resp.inner_type = soh_pull_be_16(data); data += 2; resp.inner_len = soh_pull_be_16(data); data += 2; if (resp.outer_type!=7 || resp.vendor != 0x137) { RDEBUG("SoH response outer type %i/vendor %08x not recognised", resp.outer_type, resp.vendor); return -1; } switch (resp.inner_type) { case 1: /* no mode sub-header */ RDEBUG("SoH without mode subheader"); break; case 2: mode.outer_type = soh_pull_be_16(data); data += 2; mode.outer_len = soh_pull_be_16(data); data += 2; mode.vendor = soh_pull_be_32(data); data += 4; memcpy(mode.corrid, data, 24); data += 24; mode.intent = data[0]; mode.content_type = data[1]; data += 2; if (mode.outer_type != 7 || mode.vendor != 0x137 || mode.content_type != 0) { RDEBUG3("SoH mode subheader outer type %i/vendor %08x/content type %i invalid", mode.outer_type, mode.vendor, mode.content_type); return -1; } RDEBUG3("SoH with mode subheader"); break; default: RDEBUG("SoH invalid inner type %i", resp.inner_type); return -1; } /* subtract off the relevant amount of data */ if (resp.inner_type==2) { data_len = resp.inner_len - 34; } else { data_len = resp.inner_len; } /* TLV * MS-SOH 2.2.1 * See also 2.2.3 * * 1 bit mandatory * 1 bit reserved * 14 bits tlv type * 2 bytes tlv length * N bytes payload * */ while (data_len >= 4) { tlv.tlv_type = soh_pull_be_16(data); data += 2; tlv.tlv_len = soh_pull_be_16(data); data += 2; data_len -= 4; switch (tlv.tlv_type) { case 2: /* System-Health-Id TLV * MS-SOH 2.2.3.1 * * 3 bytes IANA/SMI vendor code * 1 byte component (i.e. within vendor, which SoH component */ curr_shid = soh_pull_be_24(data); curr_shid_c = data[3]; RDEBUG2("SoH System-Health-ID vendor %08x component=%i", curr_shid, curr_shid_c); break; case 7: /* Vendor-Specific packet * MS-SOH 2.2.3.3 * * 4 bytes vendor, supposedly ignored by NAP * N bytes payload; for Microsoft component#0 this is the MS TV stuff */ if (curr_shid==0x137 && curr_shid_c==0) { RDEBUG2("SoH MS type-value payload"); eapsoh_mstlv(request, data + 4, tlv.tlv_len - 4); } else { RDEBUG2("SoH unhandled vendor-specific TLV %08x/component=%i %i bytes payload", curr_shid, curr_shid_c, tlv.tlv_len); } break; case 8: /* Health-Class * MS-SOH 2.2.3.5.6 * * 1 byte integer */ RDEBUG2("SoH Health-Class %i", data[0]); curr_hc = data[0]; break; case 9: /* Software-Version * MS-SOH 2.2.3.5.7 * * 1 byte integer */ RDEBUG2("SoH Software-Version %i", data[0]); break; case 11: /* Health-Class status * MS-SOH 2.2.3.5.9 * * variable data; for the MS System Health vendor, these are 4-byte * integers which are a really, really dumb format: * * 28 bits ignore * 1 bit - 1==product snoozed * 1 bit - 1==microsoft product * 1 bit - 1==product up-to-date * 1 bit - 1==product enabled */ RDEBUG2("SoH Health-Class-Status - current shid=%08x component=%i", curr_shid, curr_shid_c); if (curr_shid == 0x137 && curr_shid_c == 128) { char const *s, *t; uint32_t hcstatus = soh_pull_be_32(data); RDEBUG2("SoH Health-Class-Status microsoft DWORD=%08x", hcstatus); vp = pair_make_request("SoH-MS-Windows-Health-Status", NULL, T_OP_EQ); if (!vp) return 0; switch (curr_hc) { case 4: /* security updates */ s = "security-updates"; switch (hcstatus) { case 0xff0005: fr_pair_value_sprintf(vp, "%s ok all-installed", s); break; case 0xff0006: fr_pair_value_sprintf(vp, "%s warn some-missing", s); break; case 0xff0008: fr_pair_value_sprintf(vp, "%s warn never-started", s); break; case 0xc0ff000c: fr_pair_value_sprintf(vp, "%s error no-wsus-srv", s); break; case 0xc0ff000d: fr_pair_value_sprintf(vp, "%s error no-wsus-clid", s); break; case 0xc0ff000e: fr_pair_value_sprintf(vp, "%s warn wsus-disabled", s); break; case 0xc0ff000f: fr_pair_value_sprintf(vp, "%s error comm-failure", s); break; case 0xc0ff0010: fr_pair_value_sprintf(vp, "%s warn needs-reboot", s); break; default: fr_pair_value_sprintf(vp, "%s error %08x", s, hcstatus); break; } break; case 3: /* auto updates */ s = "auto-updates"; switch (hcstatus) { case 1: fr_pair_value_sprintf(vp, "%s warn disabled", s); break; case 2: fr_pair_value_sprintf(vp, "%s ok action=check-only", s); break; case 3: fr_pair_value_sprintf(vp, "%s ok action=download", s); break; case 4: fr_pair_value_sprintf(vp, "%s ok action=install", s); break; case 5: fr_pair_value_sprintf(vp, "%s warn unconfigured", s); break; case 0xc0ff0003: fr_pair_value_sprintf(vp, "%s warn service-down", s); break; case 0xc0ff0018: fr_pair_value_sprintf(vp, "%s warn never-started", s); break; default: fr_pair_value_sprintf(vp, "%s error %08x", s, hcstatus); break; } break; default: /* other - firewall, antivirus, antispyware */ s = healthclass2str(curr_hc); if (s) { /* bah. this is vile. stupid microsoft */ if (hcstatus & 0xff000000) { /* top octet non-zero means an error * FIXME: is this always correct? MS-WSH 2.2.8 is unclear */ t = clientstatus2str(hcstatus); if (t) { fr_pair_value_sprintf(vp, "%s error %s", s, t); } else { fr_pair_value_sprintf(vp, "%s error %08x", s, hcstatus); } } else { fr_pair_value_sprintf(vp, "%s ok snoozed=%i microsoft=%i up2date=%i enabled=%i", s, hcstatus & 0x8 ? 1 : 0, hcstatus & 0x4 ? 1 : 0, hcstatus & 0x2 ? 1 : 0, hcstatus & 0x1 ? 1 : 0 ); } } else { fr_pair_value_sprintf(vp, "%i unknown %08x", curr_hc, hcstatus); } break; } } else { vp = pair_make_request("SoH-MS-Health-Other", NULL, T_OP_EQ); if (!vp) return 0; /* FIXME: what to do with the payload? */ fr_pair_value_sprintf(vp, "%08x/%i ?", curr_shid, curr_shid_c); } break; default: RDEBUG("SoH Unknown TLV %i len=%i", tlv.tlv_type, tlv.tlv_len); break; } data += tlv.tlv_len; data_len -= tlv.tlv_len; } return 0; }
/** Parses the MS-SOH type/value (note: NOT type/length/value) data and update the sohvp list * * See section 2.2.4 of MS-SOH. Because there's no "length" field we CANNOT just skip * unknown types; we need to know their length ahead of time. Therefore, we abort * if we find an unknown type. Note that sohvp may still have been modified in the * failure case. * * @param request Current request * @param p binary blob * @param data_len length of blob * @return * - 0 on success. * - -1 on failure. */ static int eapsoh_mstlv(REQUEST *request, uint8_t const *p, unsigned int data_len) { VALUE_PAIR *vp; uint8_t c; int t; char *q; while (data_len > 0) { c = *p++; data_len--; switch (c) { case 1: /* MS-Machine-Inventory-Packet * MS-SOH section 2.2.4.1 */ if (data_len < 18) { RDEBUG("insufficient data for MS-Machine-Inventory-Packet"); return 0; } data_len -= 18; vp = pair_make_request("SoH-MS-Machine-OS-vendor", "Microsoft", T_OP_EQ); if (!vp) return 0; vp = pair_make_request("SoH-MS-Machine-OS-version", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_integer = soh_pull_be_32(p); p+=4; vp = pair_make_request("SoH-MS-Machine-OS-release", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_integer = soh_pull_be_32(p); p+=4; vp = pair_make_request("SoH-MS-Machine-OS-build", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_integer = soh_pull_be_32(p); p+=4; vp = pair_make_request("SoH-MS-Machine-SP-version", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_integer = soh_pull_be_16(p); p+=2; vp = pair_make_request("SoH-MS-Machine-SP-release", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_integer = soh_pull_be_16(p); p+=2; vp = pair_make_request("SoH-MS-Machine-Processor", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_integer = soh_pull_be_16(p); p+=2; break; case 2: /* MS-Quarantine-State - FIXME: currently unhandled * MS-SOH 2.2.4.1 * * 1 byte reserved * 1 byte flags * 8 bytes NT Time field (100-nanosec since 1 Jan 1601) * 2 byte urilen * N bytes uri */ p += 10; t = soh_pull_be_16(p); /* t == uri len */ p += 2; p += t; data_len -= 12 + t; break; case 3: /* MS-Packet-Info * MS-SOH 2.2.4.3 */ RDEBUG3("SoH MS-Packet-Info %s vers=%i", *p & 0x10 ? "request" : "response", *p & 0xf); p++; data_len--; break; case 4: /* MS-SystemGenerated-Ids - FIXME: currently unhandled * MS-SOH 2.2.4.4 * * 2 byte length * N bytes (3 bytes IANA enterprise# + 1 byte component id#) */ t = soh_pull_be_16(p); p += 2; p += t; data_len -= 2 + t; break; case 5: /* MS-MachineName * MS-SOH 2.2.4.5 * * 1 byte namelen * N bytes name */ t = soh_pull_be_16(p); p += 2; vp = pair_make_request("SoH-MS-Machine-Name", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_strvalue = q = talloc_array(vp, char, t); vp->type = VT_DATA; memcpy(q, p, t); q[t] = 0; p += t; data_len -= 2 + t; break; case 6: /* MS-CorrelationId * MS-SOH 2.2.4.6 * * 24 bytes opaque binary which we might, in future, have * to echo back to the client in a final SoHR */ vp = pair_make_request("SoH-MS-Correlation-Id", NULL, T_OP_EQ); if (!vp) return 0; fr_pair_value_memcpy(vp, p, 24); p += 24; data_len -= 24; break; case 7: /* MS-Installed-Shvs - FIXME: currently unhandled * MS-SOH 2.2.4.7 * * 2 bytes length * N bytes (3 bytes IANA enterprise# + 1 byte component id#) */ t = soh_pull_be_16(p); p += 2; p += t; data_len -= 2 + t; break; case 8: /* MS-Machine-Inventory-Ex * MS-SOH 2.2.4.8 * * 4 bytes reserved * 1 byte product type (client=1 domain_controller=2 server=3) */ p += 4; vp = pair_make_request("SoH-MS-Machine-Role", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_integer = *p; p++; data_len -= 5; break; default: RDEBUG("SoH Unknown MS TV %i stopping", c); return 0; } } return 1; }
/* * Process the "diameter" contents of the tunneled data. */ PW_CODE eapttls_process(eap_handler_t *handler, tls_session_t *tls_session) { PW_CODE code = PW_CODE_ACCESS_REJECT; rlm_rcode_t rcode; REQUEST *fake; VALUE_PAIR *vp; ttls_tunnel_t *t; uint8_t const *data; size_t data_len; REQUEST *request = handler->request; chbind_packet_t *chbind; /* * Just look at the buffer directly, without doing * record_minus. */ data_len = tls_session->clean_out.used; tls_session->clean_out.used = 0; data = tls_session->clean_out.data; t = (ttls_tunnel_t *) tls_session->opaque; /* * If there's no data, maybe this is an ACK to an * MS-CHAP2-Success. */ if (data_len == 0) { if (t->authenticated) { RDEBUG("Got ACK, and the user was already authenticated"); return PW_CODE_ACCESS_ACCEPT; } /* else no session, no data, die. */ /* * FIXME: Call SSL_get_error() to see what went * wrong. */ RDEBUG2("SSL_read Error"); return PW_CODE_ACCESS_REJECT; } #ifndef NDEBUG if ((rad_debug_lvl > 2) && fr_log_fp) { size_t i; for (i = 0; i < data_len; i++) { if ((i & 0x0f) == 0) fprintf(fr_log_fp, " TTLS tunnel data in %04x: ", (int) i); fprintf(fr_log_fp, "%02x ", data[i]); if ((i & 0x0f) == 0x0f) fprintf(fr_log_fp, "\n"); } if ((data_len & 0x0f) != 0) fprintf(fr_log_fp, "\n"); } #endif if (!diameter_verify(request, data, data_len)) { return PW_CODE_ACCESS_REJECT; } /* * Allocate a fake REQUEST structure. */ fake = request_alloc_fake(request); rad_assert(!fake->packet->vps); /* * Add the tunneled attributes to the fake request. */ fake->packet->vps = diameter2vp(request, fake, tls_session->ssl, data, data_len); if (!fake->packet->vps) { talloc_free(fake); return PW_CODE_ACCESS_REJECT; } /* * Tell the request that it's a fake one. */ pair_make_request("Freeradius-Proxied-To", "127.0.0.1", T_OP_EQ); RDEBUG("Got tunneled request"); rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); /* * Update other items in the REQUEST data structure. */ fake->username = fr_pair_find_by_num(fake->packet->vps, PW_USER_NAME, 0, TAG_ANY); fake->password = fr_pair_find_by_num(fake->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY); /* * No User-Name, try to create one from stored data. */ if (!fake->username) { /* * No User-Name in the stored data, look for * an EAP-Identity, and pull it out of there. */ if (!t->username) { vp = fr_pair_find_by_num(fake->packet->vps, PW_EAP_MESSAGE, 0, TAG_ANY); if (vp && (vp->vp_length >= EAP_HEADER_LEN + 2) && (vp->vp_strvalue[0] == PW_EAP_RESPONSE) && (vp->vp_strvalue[EAP_HEADER_LEN] == PW_EAP_IDENTITY) && (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) { /* * Create & remember a User-Name */ t->username = fr_pair_make(t, NULL, "User-Name", NULL, T_OP_EQ); rad_assert(t->username != NULL); fr_pair_value_bstrncpy(t->username, vp->vp_octets + 5, vp->vp_length - 5); RDEBUG("Got tunneled identity of %s", t->username->vp_strvalue); /* * If there's a default EAP type, * set it here. */ if (t->default_method != 0) { RDEBUG("Setting default EAP type for tunneled EAP session"); vp = fr_pair_afrom_num(fake, PW_EAP_TYPE, 0); rad_assert(vp != NULL); vp->vp_integer = t->default_method; fr_pair_add(&fake->config, vp); } } else { /* * Don't reject the request outright, * as it's permitted to do EAP without * user-name. */ RWDEBUG2("No EAP-Identity found to start EAP conversation"); } } /* else there WAS a t->username */ if (t->username) { vp = fr_pair_list_copy(fake->packet, t->username); fr_pair_add(&fake->packet->vps, vp); fake->username = fr_pair_find_by_num(fake->packet->vps, PW_USER_NAME, 0, TAG_ANY); } } /* else the request ALREADY had a User-Name */ /* * Add the State attribute, too, if it exists. */ if (t->state) { vp = fr_pair_list_copy(fake->packet, t->state); if (vp) fr_pair_add(&fake->packet->vps, vp); } /* * If this is set, we copy SOME of the request attributes * from outside of the tunnel to inside of the tunnel. * * We copy ONLY those attributes which do NOT already * exist in the tunneled request. */ if (t->copy_request_to_tunnel) { VALUE_PAIR *copy; vp_cursor_t cursor; for (vp = fr_cursor_init(&cursor, &request->packet->vps); vp; vp = fr_cursor_next(&cursor)) { /* * The attribute is a server-side thingy, * don't copy it. */ if ((vp->da->attr > 255) && (vp->da->vendor == 0)) { continue; } /* * The outside attribute is already in the * tunnel, don't copy it. * * This works for BOTH attributes which * are originally in the tunneled request, * AND attributes which are copied there * from below. */ if (fr_pair_find_by_da(fake->packet->vps, vp->da, TAG_ANY)) { continue; } /* * Some attributes are handled specially. */ switch (vp->da->attr) { /* * NEVER copy Message-Authenticator, * EAP-Message, or State. They're * only for outside of the tunnel. */ case PW_USER_NAME: case PW_USER_PASSWORD: case PW_CHAP_PASSWORD: case PW_CHAP_CHALLENGE: case PW_PROXY_STATE: case PW_MESSAGE_AUTHENTICATOR: case PW_EAP_MESSAGE: case PW_STATE: continue; /* * By default, copy it over. */ default: break; } /* * Don't copy from the head, we've already * checked it. */ copy = fr_pair_list_copy_by_num(fake->packet, vp, vp->da->attr, vp->da->vendor, TAG_ANY); fr_pair_add(&fake->packet->vps, copy); } } if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { fake->server = vp->vp_strvalue; } else if (t->virtual_server) { fake->server = t->virtual_server; } /* else fake->server == request->server */ if ((rad_debug_lvl > 0) && fr_log_fp) { RDEBUG("Sending tunneled request"); } /* * Process channel binding. */ chbind = eap_chbind_vp2packet(fake, fake->packet->vps); if (chbind) { PW_CODE chbind_code; CHBIND_REQ *req = talloc_zero(fake, CHBIND_REQ); RDEBUG("received chbind request"); req->request = chbind; if (fake->username) { req->username = fake->username; } else { req->username = NULL; } chbind_code = chbind_process(request, req); /* encapsulate response here */ if (req->response) { RDEBUG("sending chbind response"); fr_pair_add(&fake->reply->vps, eap_chbind_packet2vp(fake, req->response)); } else { RDEBUG("no chbind response"); } /* clean up chbind req */ talloc_free(req); if (chbind_code != PW_CODE_ACCESS_ACCEPT) { return chbind_code; } } /* * Call authentication recursively, which will * do PAP, CHAP, MS-CHAP, etc. */ rad_virtual_server(fake); /* * Decide what to do with the reply. */ switch (fake->reply->code) { case 0: /* No reply code, must be proxied... */ #ifdef WITH_PROXY vp = fr_pair_find_by_num(fake->config, PW_PROXY_TO_REALM, 0, TAG_ANY); if (vp) { eap_tunnel_data_t *tunnel; RDEBUG("Tunneled authentication will be proxied to %s", vp->vp_strvalue); /* * Tell the original request that it's going * to be proxied. */ fr_pair_list_move_by_num(request, &request->config, &fake->config, PW_PROXY_TO_REALM, 0, TAG_ANY); /* * Seed the proxy packet with the * tunneled request. */ rad_assert(!request->proxy); request->proxy = talloc_steal(request, fake->packet); memset(&request->proxy->src_ipaddr, 0, sizeof(request->proxy->src_ipaddr)); memset(&request->proxy->src_ipaddr, 0, sizeof(request->proxy->src_ipaddr)); request->proxy->src_port = 0; request->proxy->dst_port = 0; fake->packet = NULL; rad_free(&fake->reply); fake->reply = NULL; /* * Set up the callbacks for the tunnel */ tunnel = talloc_zero(request, eap_tunnel_data_t); tunnel->tls_session = tls_session; tunnel->callback = eapttls_postproxy; /* * Associate the callback with the request. */ code = request_data_add(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK, tunnel, false); rad_assert(code == 0); /* * rlm_eap.c has taken care of associating * the handler with the fake request. * * So we associate the fake request with * this request. */ code = request_data_add(request, request->proxy, REQUEST_DATA_EAP_MSCHAP_TUNNEL_CALLBACK, fake, true); rad_assert(code == 0); fake = NULL; /* * Didn't authenticate the packet, but * we're proxying it. */ code = PW_CODE_STATUS_CLIENT; } else #endif /* WITH_PROXY */ { RDEBUG("No tunneled reply was found for request %d , and the request was not proxied: rejecting the user.", request->number); code = PW_CODE_ACCESS_REJECT; } break; default: /* * Returns RLM_MODULE_FOO, and we want to return PW_FOO */ rcode = process_reply(handler, tls_session, request, fake->reply); switch (rcode) { case RLM_MODULE_REJECT: code = PW_CODE_ACCESS_REJECT; break; case RLM_MODULE_HANDLED: code = PW_CODE_ACCESS_CHALLENGE; break; case RLM_MODULE_OK: code = PW_CODE_ACCESS_ACCEPT; break; default: code = PW_CODE_ACCESS_REJECT; break; } break; } talloc_free(fake); return code; }
static rlm_rcode_t rlm_sql_process_groups(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle, sql_fall_through_t *do_fall_through) { rlm_rcode_t rcode = RLM_MODULE_NOOP; VALUE_PAIR *check_tmp = NULL, *reply_tmp = NULL, *sql_group = NULL; rlm_sql_grouplist_t *head = NULL, *entry = NULL; char *expanded = NULL; int rows; rad_assert(request->packet != NULL); if (!inst->config->groupmemb_query) { RWARN("Cannot do check groups when group_membership_query is not set"); do_nothing: *do_fall_through = FALL_THROUGH_DEFAULT; /* * Didn't add group attributes or allocate * memory, so don't do anything else. */ return RLM_MODULE_NOTFOUND; } /* * Get the list of groups this user is a member of */ rows = sql_get_grouplist(inst, handle, request, &head); if (rows < 0) { REDEBUG("Error retrieving group list"); return RLM_MODULE_FAIL; } if (rows == 0) { RDEBUG2("User not found in any groups"); goto do_nothing; } rad_assert(head); RDEBUG2("User found in the group table"); /* * Add the Sql-Group attribute to the request list so we know * which group we're retrieving attributes for */ sql_group = pair_make_request(inst->group_da->name, NULL, T_OP_EQ); if (!sql_group) { REDEBUG("Error creating %s attribute", inst->group_da->name); rcode = RLM_MODULE_FAIL; goto finish; } entry = head; do { next: rad_assert(entry != NULL); fr_pair_value_strcpy(sql_group, entry->name); if (inst->config->authorize_group_check_query) { vp_cursor_t cursor; VALUE_PAIR *vp; /* * Expand the group query */ if (radius_axlat(&expanded, request, inst->config->authorize_group_check_query, inst->sql_escape_func, *handle) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto finish; } rows = sql_getvpdata(request, inst, request, handle, &check_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("Error retrieving check pairs for group %s", entry->name); rcode = RLM_MODULE_FAIL; goto finish; } /* * If we got check rows we need to process them before we decide to * process the reply rows */ if ((rows > 0) && (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0)) { fr_pair_list_free(&check_tmp); entry = entry->next; if (!entry) break; goto next; /* != continue */ } RDEBUG2("Group \"%s\": Conditional check items matched", entry->name); rcode = RLM_MODULE_OK; RDEBUG2("Group \"%s\": Merging assignment check items", entry->name); RINDENT(); for (vp = fr_cursor_init(&cursor, &check_tmp); vp; vp = fr_cursor_next(&cursor)) { if (!fr_assignment_op[vp->op]) continue; rdebug_pair(L_DBG_LVL_2, request, vp, NULL); } REXDENT(); radius_pairmove(request, &request->config, check_tmp, true); check_tmp = NULL; } if (inst->config->authorize_group_reply_query) { /* * Now get the reply pairs since the paircompare matched */ if (radius_axlat(&expanded, request, inst->config->authorize_group_reply_query, inst->sql_escape_func, *handle) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto finish; } rows = sql_getvpdata(request->reply, inst, request, handle, &reply_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("Error retrieving reply pairs for group %s", entry->name); rcode = RLM_MODULE_FAIL; goto finish; } *do_fall_through = fall_through(reply_tmp); RDEBUG2("Group \"%s\": Merging reply items", entry->name); rcode = RLM_MODULE_OK; rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp, NULL); radius_pairmove(request, &request->reply->vps, reply_tmp, true); reply_tmp = NULL; /* * If there's no reply query configured, then we assume * FALL_THROUGH_NO, which is the same as the users file if you * had no reply attributes. */ } else { *do_fall_through = FALL_THROUGH_DEFAULT; } entry = entry->next; } while (entry != NULL && (*do_fall_through == FALL_THROUGH_YES)); finish: talloc_free(head); fr_pair_delete_by_num(&request->packet->vps, 0, inst->group_da->attr, TAG_ANY); return rcode; }