static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) { rlm_chap_t *inst = instance; VALUE_PAIR *vp; if (!fr_pair_find_by_da(request->packet->vps, attr_chap_password, TAG_ANY)) return RLM_MODULE_NOOP; /* * Create the CHAP-Challenge if it wasn't already in the packet. * * This is so that the rest of the code does not need to * understand CHAP. */ vp = fr_pair_find_by_da(request->packet->vps, attr_chap_challenge, TAG_ANY); if (!vp) { RDEBUG2("Creating CHAP-Challenge from the request authenticator"); MEM(vp = fr_pair_afrom_da(request->packet, attr_chap_challenge)); fr_pair_value_memcpy(vp, request->packet->vector, sizeof(request->packet->vector)); fr_pair_add(&request->packet->vps, vp); } if (!module_section_type_set(request, attr_auth_type, inst->auth_type)) return RLM_MODULE_NOOP; return RLM_MODULE_OK; }
/** Do any RADIUS-layer fixups for proxying. * */ static void radius_fixups(rlm_radius_t *inst, REQUEST *request) { VALUE_PAIR *vp; /* * Check for proxy loops. */ if (RDEBUG_ENABLED) { fr_cursor_t cursor; for (vp = fr_cursor_iter_by_da_init(&cursor, &request->packet->vps, attr_proxy_state); vp; vp = fr_cursor_next(&cursor)) { if (vp->vp_length != 4) continue; if (memcmp(&inst->proxy_state, vp->vp_octets, 4) == 0) { RWARN("Possible proxy loop - please check server configuration."); break; } } } if (request->packet->code != FR_CODE_ACCESS_REQUEST) return; if (fr_pair_find_by_da(request->packet->vps, attr_chap_password, TAG_ANY) && !fr_pair_find_by_da(request->packet->vps, attr_chap_challenge, TAG_ANY)) { MEM(pair_add_request(&vp, attr_chap_challenge) >= 0); fr_pair_value_memcpy(vp, request->packet->vector, sizeof(request->packet->vector)); } }
static int vector_opc_from_op(REQUEST *request, uint8_t const **out, uint8_t opc_buff[MILENAGE_OPC_SIZE], VALUE_PAIR *list, uint8_t const ki[MILENAGE_KI_SIZE]) { VALUE_PAIR *opc_vp; VALUE_PAIR *op_vp; opc_vp = fr_pair_find_by_da(list, attr_sim_opc, TAG_ANY); if (opc_vp) { if (opc_vp->vp_length != MILENAGE_OPC_SIZE) { REDEBUG("&control:%s has incorrect length, expected %u bytes got %zu bytes", attr_sim_opc->name, MILENAGE_OPC_SIZE, opc_vp->vp_length); return -1; } *out = opc_vp->vp_octets; return 0; } op_vp = fr_pair_find_by_da(list, attr_sim_op, TAG_ANY); if (op_vp) { if (op_vp->vp_length != MILENAGE_OP_SIZE) { REDEBUG("&control:%s has incorrect length, expected %u bytes got %zu bytes", attr_sim_op->name, MILENAGE_OP_SIZE, op_vp->vp_length); return -1; } if (milenage_opc_generate(opc_buff, op_vp->vp_octets, ki) < 0) { RPEDEBUG("Deriving OPc failed"); return -1; } *out = opc_buff; return 0; } *out = NULL; return 1; }
/* * Compare prefix/suffix. * * If they compare: * - if FR_STRIP_USER_NAME is present in check_list, * strip the username of prefix/suffix. * - if FR_STRIP_USER_NAME is not present in check_list, * add a FR_STRIPPED_USER_NAME to the request. */ static int prefix_suffix_cmp(UNUSED void *instance, REQUEST *request, VALUE_PAIR *req, VALUE_PAIR *check, VALUE_PAIR *check_list, UNUSED VALUE_PAIR **reply_list) { VALUE_PAIR *vp; char const *name; char rest[FR_MAX_STRING_LEN]; int len, namelen; int ret = -1; if (!request || !request->username) return -1; VP_VERIFY(check); name = request->username->vp_strvalue; RDEBUG3("Comparing name \"%s\" and check value \"%s\"", name, check->vp_strvalue); len = strlen(check->vp_strvalue); if (check->da == attr_prefix) { ret = strncmp(name, check->vp_strvalue, len); if (ret == 0) strlcpy(rest, name + len, sizeof(rest)); } else if (check->da == attr_suffix) { namelen = strlen(name); if (namelen >= len) { ret = strcmp(name + namelen - len, check->vp_strvalue); if (ret == 0) strlcpy(rest, name, namelen - len + 1); } } if (ret != 0) return ret; /* * If Strip-User-Name == No, then don't do any more. */ vp = fr_pair_find_by_da(check_list, attr_strip_user_name, TAG_ANY); if (vp && !vp->vp_uint32) return ret; /* * See where to put the stripped user name. */ vp = fr_pair_find_by_da(check_list, attr_stripped_user_name, TAG_ANY); if (!vp) { /* * If "request" is NULL, then the memory will be * lost! */ MEM(vp = fr_pair_afrom_da(request->packet, attr_stripped_user_name)); fr_pair_add(&req, vp); request->username = vp; } fr_pair_value_strcpy(vp, rest); return ret; }
/* * See if a VALUE_PAIR list contains Fall-Through = Yes */ static int fall_through(VALUE_PAIR *vp) { VALUE_PAIR *tmp; tmp = fr_pair_find_by_da(vp, attr_fall_through, TAG_ANY); return tmp ? tmp->vp_uint32 : 0; }
/** Create the requisite L2/L3 headers, and write a DHCPv4 packet to a raw socket * * @param[in] sockfd to write to. * @param[in] link_layer information, as returned by fr_dhcpv4_raw_socket_open. * @param[in] packet to write. * @return * - 0 on success. * - -1 on failure. */ int fr_dhcpv4_raw_packet_send(int sockfd, struct sockaddr_ll *link_layer, RADIUS_PACKET *packet) { uint8_t dhcp_packet[1518] = { 0 }; ethernet_header_t *eth_hdr = (ethernet_header_t *)dhcp_packet; ip_header_t *ip_hdr = (ip_header_t *)(dhcp_packet + ETH_HDR_SIZE); udp_header_t *udp_hdr = (udp_header_t *) (dhcp_packet + ETH_HDR_SIZE + IP_HDR_SIZE); dhcp_packet_t *dhcp = (dhcp_packet_t *)(dhcp_packet + ETH_HDR_SIZE + IP_HDR_SIZE + UDP_HDR_SIZE); uint16_t l4_len = (UDP_HDR_SIZE + packet->data_len); VALUE_PAIR *vp; /* set ethernet source address to our MAC address (DHCP-Client-Hardware-Address). */ uint8_t dhmac[ETH_ADDR_LEN] = { 0 }; if ((vp = fr_pair_find_by_da(packet->vps, attr_dhcp_client_hardware_address, TAG_ANY))) { if (vp->vp_type == FR_TYPE_ETHERNET) memcpy(dhmac, vp->vp_ether, sizeof(vp->vp_ether)); } /* fill in Ethernet layer (L2) */ memcpy(eth_hdr->ether_dst, eth_bcast, ETH_ADDR_LEN); memcpy(eth_hdr->ether_src, dhmac, ETH_ADDR_LEN); eth_hdr->ether_type = htons(ETH_TYPE_IP); /* fill in IP layer (L3) */ ip_hdr->ip_vhl = IP_VHL(4, 5); ip_hdr->ip_tos = 0; ip_hdr->ip_len = htons(IP_HDR_SIZE + UDP_HDR_SIZE + packet->data_len); ip_hdr->ip_id = 0; ip_hdr->ip_off = 0; ip_hdr->ip_ttl = 64; ip_hdr->ip_p = 17; ip_hdr->ip_sum = 0; /* Filled later */ /* saddr: Packet-Src-IP-Address (default: 0.0.0.0). */ ip_hdr->ip_src.s_addr = packet->src_ipaddr.addr.v4.s_addr; /* daddr: packet destination IP addr (should be 255.255.255.255 for broadcast). */ ip_hdr->ip_dst.s_addr = packet->dst_ipaddr.addr.v4.s_addr; /* IP header checksum */ ip_hdr->ip_sum = fr_ip_header_checksum((uint8_t const *)ip_hdr, 5); udp_hdr->src = htons(packet->src_port); udp_hdr->dst = htons(packet->dst_port); udp_hdr->len = htons(l4_len); udp_hdr->checksum = 0; /* UDP checksum will be done after dhcp header */ /* DHCP layer (L7) */ /* just copy what FreeRADIUS has encoded for us. */ memcpy(dhcp, packet->data, packet->data_len); /* UDP checksum is done here */ udp_hdr->checksum = fr_udp_checksum((uint8_t const *)(dhcp_packet + ETH_HDR_SIZE + IP_HDR_SIZE), ntohs(udp_hdr->len), udp_hdr->checksum, packet->src_ipaddr.addr.v4, packet->dst_ipaddr.addr.v4); return sendto(sockfd, dhcp_packet, (ETH_HDR_SIZE + IP_HDR_SIZE + UDP_HDR_SIZE + packet->data_len), 0, (struct sockaddr *) link_layer, sizeof(struct sockaddr_ll)); }
/** Send a failure message * */ static int eap_sim_send_eap_failure_notification(eap_session_t *eap_session) { REQUEST *request = eap_session->request; RADIUS_PACKET *packet = eap_session->request->reply; fr_cursor_t cursor; VALUE_PAIR *vp; eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); fr_cursor_init(&cursor, &packet->vps); vp = fr_pair_find_by_da(packet->vps, attr_eap_sim_notification, TAG_ANY); if (!vp) { vp = fr_pair_afrom_da(packet, attr_eap_sim_notification); vp->vp_uint16 = FR_EAP_SIM_NOTIFICATION_VALUE_GENERAL_FAILURE; fr_cursor_append(&cursor, vp); } /* * Change the failure notification depending where * we are in the state machine. */ if (eap_sim_session->challenge_success) { vp->vp_uint16 &= ~0x4000; /* Unset phase bit */ } else { vp->vp_uint16 |= 0x4000; /* Set phase bit */ } vp->vp_uint16 &= ~0x8000; /* In both cases success bit should be low */ RDEBUG2("Sending SIM-Notification (%pV)", &vp->data); eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; /* * Set the subtype to notification */ vp = fr_pair_afrom_da(packet, attr_eap_sim_subtype); vp->vp_uint16 = FR_EAP_SIM_SUBTYPE_VALUE_SIM_NOTIFICATION; fr_cursor_append(&cursor, vp); /* * If we're after the challenge phase * then we need to include a MAC to * protect notifications. */ if (eap_sim_session->challenge_success) { vp = fr_pair_afrom_da(packet, attr_eap_sim_mac); fr_pair_replace(&packet->vps, vp); } /* * Encode the packet */ if (eap_sim_compose(eap_session, NULL, 0) < 0) { fr_pair_list_free(&packet->vps); return -1; } return 0; }
/* * Not sure how to make this useful yet... */ static ssize_t soh_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen, UNUSED void const *mod_inst, UNUSED void const *xlat_inst, REQUEST *request, char const *fmt) { VALUE_PAIR* vp[6]; char const *osname; /* * There will be no point unless SoH-Supported = yes */ vp[0] = fr_pair_find_by_da(request->packet->vps, attr_soh_supported, TAG_ANY); if (!vp[0]) return 0; if (strncasecmp(fmt, "OS", 2) == 0) { /* OS vendor */ vp[0] = fr_pair_find_by_da(request->packet->vps, attr_soh_ms_machine_os_vendor, TAG_ANY); vp[1] = fr_pair_find_by_da(request->packet->vps, attr_soh_ms_machine_os_version, TAG_ANY); vp[2] = fr_pair_find_by_da(request->packet->vps, attr_soh_ms_machine_os_release, TAG_ANY); vp[3] = fr_pair_find_by_da(request->packet->vps, attr_soh_ms_machine_os_build, TAG_ANY); vp[4] = fr_pair_find_by_da(request->packet->vps, attr_soh_ms_machine_sp_version, TAG_ANY); vp[5] = fr_pair_find_by_da(request->packet->vps, attr_soh_ms_machine_sp_release, TAG_ANY); if (vp[0] && vp[0]->vp_uint32 == VENDORPEC_MICROSOFT) { if (!vp[1]) { snprintf(*out, outlen, "Windows unknown"); } else { switch (vp[1]->vp_uint32) { case 7: osname = "7"; break; case 6: osname = "Vista"; break; case 5: osname = "XP"; break; default: osname = "Other"; break; } snprintf(*out, outlen, "Windows %s %d.%d.%d sp %d.%d", osname, vp[1]->vp_uint32, vp[2] ? vp[2]->vp_uint32 : 0, vp[3] ? vp[3]->vp_uint32 : 0, vp[4] ? vp[4]->vp_uint32 : 0, vp[5] ? vp[5]->vp_uint32 : 0); } return strlen(*out); } } return 0; }
static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, UNUSED void *thread, REQUEST *request) { VALUE_PAIR *vp; int rv; /* try to find the MS-SoH payload */ vp = fr_pair_find_by_da(request->packet->vps, attr_ms_quarantine_soh, TAG_ANY); if (!vp) { RDEBUG2("SoH radius VP not found"); return RLM_MODULE_NOOP; } RDEBUG2("SoH radius VP found"); /* decode it */ rv = soh_verify(request, vp->vp_octets, vp->vp_length); if (rv < 0) { return RLM_MODULE_FAIL; } return RLM_MODULE_OK; }
/** Write the result of a set operation back to net-snmp * * Writes "DONE\n" on success, or an error as described in man snmpd.conf * on error. * * @param fd to write to. * @param error attribute. * @param head of list of attributes to convert and write. * @return * - 0 on success. * - -1 on failure. */ static int radsnmp_set_response(int fd, fr_dict_attr_t const *error, VALUE_PAIR *head) { VALUE_PAIR *vp; char buffer[64]; size_t len; struct iovec io_vector[2]; char newline[] = "\n"; vp = fr_pair_find_by_da(head, error, TAG_NONE); if (!vp) { if (write(fd, "DONE\n", 5) < 0) { fr_strerror_printf("Failed writing set response: %s", fr_syserror(errno)); return -1; } return 0; } len = fr_pair_value_snprint(buffer, sizeof(buffer), vp, '\0'); if (is_truncated(len, sizeof(buffer))) { assert(0); return -1; } io_vector[0].iov_base = buffer; io_vector[0].iov_len = len; io_vector[1].iov_base = newline; io_vector[1].iov_len = 1; DEBUG2("said: %s", buffer); if (writev(fd, io_vector, sizeof(io_vector) / sizeof(*io_vector)) < 0) { fr_strerror_printf("Failed writing set response: %s", fr_syserror(errno)); return -1; } return 0; }
/* * Write accounting information to this modules database. */ static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, UNUSED void *thread, REQUEST *request) { VALUE_PAIR *pair; int acct_status_type = 0; pair = fr_pair_find_by_da(request->packet->vps, attr_acct_status_type, TAG_ANY); if (pair != NULL) { acct_status_type = pair->vp_uint32; } else { REDEBUG("Invalid Accounting Packet"); return RLM_MODULE_INVALID; } switch (acct_status_type) { case FR_STATUS_START: if (((rlm_perl_t const *)instance)->func_start_accounting) { return do_perl(instance, request, ((rlm_perl_t const *)instance)->func_start_accounting); } else { return do_perl(instance, request, ((rlm_perl_t const *)instance)->func_accounting); } case FR_STATUS_STOP: if (((rlm_perl_t const *)instance)->func_stop_accounting) { return do_perl(instance, request, ((rlm_perl_t const *)instance)->func_stop_accounting); } else { return do_perl(instance, request, ((rlm_perl_t const *)instance)->func_accounting); } default: return do_perl(instance, request, ((rlm_perl_t const *)instance)->func_accounting); } }
static fr_io_final_t mod_process(UNUSED void const *instance, REQUEST *request, fr_io_action_t action) { rlm_rcode_t rcode; CONF_SECTION *unlang; REQUEST_VERIFY(request); /* * Pass this through asynchronously to the module which * is waiting for something to happen. */ if (action != FR_IO_ACTION_RUN) { unlang_signal(request, (fr_state_signal_t) action); return FR_IO_DONE; } switch (request->request_state) { case REQUEST_INIT: request->component = "radius"; unlang = cf_section_find(request->server_cs, "new", "client"); if (!unlang) { RWDEBUG("Failed to find 'new client' section"); request->reply->code = FR_CODE_ACCESS_REJECT; goto send_reply; } RDEBUG("Running 'new client' from file %s", cf_filename(unlang)); unlang_push_section(request, unlang, RLM_MODULE_NOOP, UNLANG_TOP_FRAME); request->request_state = REQUEST_RECV; /* FALL-THROUGH */ case REQUEST_RECV: rcode = unlang_interpret_resume(request); if (request->master_state == REQUEST_STOP_PROCESSING) return FR_IO_DONE; if (rcode == RLM_MODULE_YIELD) return FR_IO_YIELD; rad_assert(request->log.unlang_indent == 0); switch (rcode) { case RLM_MODULE_OK: case RLM_MODULE_UPDATED: request->reply->code = FR_CODE_ACCESS_ACCEPT; break; case RLM_MODULE_FAIL: case RLM_MODULE_HANDLED: request->reply->code = 0; /* don't reply */ break; default: case RLM_MODULE_REJECT: request->reply->code = FR_CODE_ACCESS_REJECT; break; } unlang = cf_section_find(request->server_cs, "add", "client"); if (!unlang) goto send_reply; rerun_nak: RDEBUG("Running '%s client' from file %s", cf_section_name1(unlang), cf_filename(unlang)); unlang_push_section(request, unlang, RLM_MODULE_NOOP, UNLANG_TOP_FRAME); request->request_state = REQUEST_SEND; /* FALL-THROUGH */ case REQUEST_SEND: rcode = unlang_interpret_resume(request); if (request->master_state == REQUEST_STOP_PROCESSING) return FR_IO_DONE; if (rcode == RLM_MODULE_YIELD) return FR_IO_YIELD; rad_assert(request->log.unlang_indent == 0); switch (rcode) { case RLM_MODULE_NOOP: case RLM_MODULE_OK: case RLM_MODULE_UPDATED: case RLM_MODULE_HANDLED: /* reply is already set */ break; default: /* * If we over-ride an ACK with a NAK, run * the NAK section. */ if (request->reply->code != FR_CODE_ACCESS_REJECT) { RWDEBUG("Failed running 'add client', trying 'deny client'."); deny: request->reply->code = FR_CODE_ACCESS_REJECT; unlang = cf_section_find(request->server_cs, "deny", "client"); if (unlang) goto rerun_nak; RWDEBUG("Not running 'deny client' section as it does not exist"); } break; } if (request->reply->code == FR_CODE_ACCESS_ACCEPT) { VALUE_PAIR *vp; vp = fr_pair_find_by_da(request->control, attr_freeradius_client_ip_address, TAG_ANY); if (!vp) fr_pair_find_by_da(request->control, attr_freeradius_client_ipv6_address, TAG_ANY); if (!vp) fr_pair_find_by_da(request->control, attr_freeradius_client_ip_prefix, TAG_ANY); if (!vp) fr_pair_find_by_da(request->control, attr_freeradius_client_ipv6_prefix, TAG_ANY); if (!vp) { ERROR("The 'control' list MUST contain a FreeRADIUS-Client.. IP address attribute"); goto deny; } vp = fr_pair_find_by_da(request->control, attr_freeradius_client_secret, TAG_ANY); if (!vp) { ERROR("The 'control' list MUST contain a FreeRADIUS-Client-Secret attribute"); goto deny; } /* * Else we're flexible. */ } send_reply: /* * This is an internally generated request. Don't print IP addresses. */ if (request->reply->code == FR_CODE_ACCESS_ACCEPT) { RDEBUG("Adding client"); } else { RDEBUG("Denying client"); } break; default: return FR_IO_FAIL; } return FR_IO_REPLY; }
/** Write accounting data to Couchbase documents * * Handle accounting requests and store the associated data into JSON documents * in couchbase mapping attribute names to JSON element names per the module configuration. * * When an existing document already exists for the same accounting section the new attributes * will be merged with the currently existing data. When conflicts arrise the new attribute * value will replace or be added to the existing value. * * @param instance The module instance. * @param thread specific data. * @param request The accounting request object. * @return Operation status (#rlm_rcode_t). */ static rlm_rcode_t mod_accounting(void *instance, UNUSED void *thread, REQUEST *request) { rlm_couchbase_t const *inst = instance; /* our module instance */ rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */ rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */ VALUE_PAIR *vp; /* radius value pair linked list */ char buffer[MAX_KEY_SIZE]; char const *dockey; /* our document key */ char document[MAX_VALUE_SIZE]; /* our document body */ char element[MAX_KEY_SIZE]; /* mapped radius attribute to element name */ int status = 0; /* account status type */ int docfound = 0; /* document found toggle */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ ssize_t slen; /* assert packet as not null */ rad_assert(request->packet != NULL); /* sanity check */ if ((vp = fr_pair_find_by_da(request->packet->vps, attr_acct_status_type, TAG_ANY)) == NULL) { /* log debug */ RDEBUG2("could not find status type in packet"); /* return */ return RLM_MODULE_NOOP; } /* set status */ status = vp->vp_uint32; /* acknowledge the request but take no action */ if (status == FR_STATUS_ACCOUNTING_ON || status == FR_STATUS_ACCOUNTING_OFF) { /* log debug */ RDEBUG2("handling accounting on/off request without action"); /* return */ return RLM_MODULE_OK; } /* get handle */ handle = fr_pool_connection_get(inst->pool, request); /* check handle */ if (!handle) return RLM_MODULE_FAIL; /* set couchbase instance */ lcb_t cb_inst = handle->handle; /* set cookie */ cookie_t *cookie = handle->cookie; /* attempt to build document key */ slen = tmpl_expand(&dockey, buffer, sizeof(buffer), request, inst->acct_key, NULL, NULL); if (slen < 0) { rcode = RLM_MODULE_FAIL; goto finish; } if ((dockey == buffer) && is_truncated((size_t)slen, sizeof(buffer))) { REDEBUG("Key too long, expected < " STRINGIFY(sizeof(buffer)) " bytes, got %zi bytes", slen); rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* attempt to fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, dockey); /* check error and object */ if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) { /* log error */ RERROR("failed to execute get request or parse returned json object"); /* free and reset json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* check cookie json object */ } else if (cookie->jobj) { /* set doc found */ docfound = 1; /* debugging */ RDEBUG3("parsed json body from couchbase: %s", json_object_to_json_string(cookie->jobj)); } /* start json document if needed */ if (docfound != 1) { /* debugging */ RDEBUG2("no existing document found - creating new json document"); /* create new json object */ cookie->jobj = json_object_new_object(); /* set 'docType' element for new document */ json_object_object_add(cookie->jobj, "docType", json_object_new_string(inst->doctype)); /* default startTimestamp and stopTimestamp to null values */ json_object_object_add(cookie->jobj, "startTimestamp", NULL); json_object_object_add(cookie->jobj, "stopTimestamp", NULL); } /* status specific replacements for start/stop time */ switch (status) { case FR_STATUS_START: /* add start time */ if ((vp = fr_pair_find_by_da(request->packet->vps, attr_acct_status_type, TAG_ANY)) != NULL) { /* add to json object */ json_object_object_add(cookie->jobj, "startTimestamp", mod_value_pair_to_json_object(request, vp)); } break; case FR_STATUS_STOP: /* add stop time */ if ((vp = fr_pair_find_by_da(request->packet->vps, attr_event_timestamp, TAG_ANY)) != NULL) { /* add to json object */ json_object_object_add(cookie->jobj, "stopTimestamp", mod_value_pair_to_json_object(request, vp)); } /* check start timestamp and adjust if needed */ mod_ensure_start_timestamp(cookie->jobj, request->packet->vps); break; case FR_STATUS_ALIVE: /* check start timestamp and adjust if needed */ mod_ensure_start_timestamp(cookie->jobj, request->packet->vps); break; default: /* don't doing anything */ rcode = RLM_MODULE_NOOP; /* return */ goto finish; } /* loop through pairs and add to json document */ for (vp = request->packet->vps; vp; vp = vp->next) { /* map attribute to element */ if (mod_attribute_to_element(vp->da->name, inst->map, &element) == 0) { /* debug */ RDEBUG3("mapped attribute %s => %s", vp->da->name, element); /* add to json object with mapped name */ json_object_object_add(cookie->jobj, element, mod_value_pair_to_json_object(request, vp)); } } /* copy json string to document and check size */ if (strlcpy(document, json_object_to_json_string(cookie->jobj), sizeof(document)) >= sizeof(document)) { /* this isn't good */ RERROR("could not write json document - insufficient buffer space"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* debugging */ RDEBUG3("setting '%s' => '%s'", dockey, document); /* store document/key in couchbase */ cb_error = couchbase_set_key(cb_inst, dockey, document, inst->expire); /* check return */ if (cb_error != LCB_SUCCESS) { RERROR("failed to store document (%s): %s (0x%x)", dockey, lcb_strerror(NULL, cb_error), cb_error); } finish: /* free and reset json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* release our connection handle */ if (handle) { fr_pool_connection_release(inst->pool, request, handle); } /* return */ return rcode; }
/* * Find the named user in this modules database. Create the set * of attribute-value pairs to check and reply with for this user * from the database. The authentication code only needs to check * the password, the rest is done here. */ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, UNUSED void *thread, REQUEST *request) { VALUE_PAIR *password, *chap; uint8_t pass_str[FR_MAX_STRING_LEN]; if (!request->username) { REDEBUG("&request:User-Name attribute is required for authentication"); return RLM_MODULE_INVALID; } chap = fr_pair_find_by_da(request->packet->vps, attr_chap_password, TAG_ANY); if (!chap) { REDEBUG("You set '&control:Auth-Type = CHAP' for a request that " "does not contain a CHAP-Password attribute!"); return RLM_MODULE_INVALID; } if (chap->vp_length == 0) { REDEBUG("&request:CHAP-Password is empty"); return RLM_MODULE_INVALID; } if (chap->vp_length != RADIUS_CHAP_CHALLENGE_LENGTH + 1) { REDEBUG("&request:CHAP-Password has invalid length"); return RLM_MODULE_INVALID; } password = fr_pair_find_by_da(request->control, attr_cleartext_password, TAG_ANY); if (password == NULL) { if (fr_pair_find_by_da(request->control, attr_user_password, TAG_ANY) != NULL){ REDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); REDEBUG("!!! Please update your configuration so that the \"known !!!"); REDEBUG("!!! good\" cleartext password is in Cleartext-Password, !!!"); REDEBUG("!!! and NOT in User-Password. !!!"); REDEBUG("!!! !!!"); REDEBUG("!!! Authentication will fail because of this. !!!"); REDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); } REDEBUG("&control:Cleartext-Password is required for authentication"); return RLM_MODULE_FAIL; } fr_radius_encode_chap_password(pass_str, request->packet, chap->vp_octets[0], password); if (RDEBUG_ENABLED3) { uint8_t const *p; size_t length; VALUE_PAIR *vp; RDEBUG3("Comparing with \"known good\" &control:Cleartext-Password value \"%s\"", password->vp_strvalue); vp = fr_pair_find_by_da(request->packet->vps, attr_chap_challenge, TAG_ANY); if (vp) { RDEBUG2("Using challenge from &request:CHAP-Challenge"); p = vp->vp_octets; length = vp->vp_length; } else { RDEBUG2("Using challenge from authenticator field"); p = request->packet->vector; length = sizeof(request->packet->vector); } RINDENT(); RDEBUG3("CHAP challenge : %pH", fr_box_octets(p, length)); RDEBUG3("Client sent : %pH", fr_box_octets(chap->vp_octets + 1, RADIUS_CHAP_CHALLENGE_LENGTH)); RDEBUG3("We calculated : %pH", fr_box_octets(pass_str + 1, RADIUS_CHAP_CHALLENGE_LENGTH)); REXDENT(); } else { RDEBUG2("Comparing with \"known good\" Cleartext-Password"); } if (fr_digest_cmp(pass_str + 1, chap->vp_octets + 1, RADIUS_CHAP_CHALLENGE_LENGTH) != 0) { REDEBUG("Password comparison failed: password is incorrect"); return RLM_MODULE_REJECT; } RDEBUG2("CHAP user \"%pV\" authenticated successfully", &request->username->data); return RLM_MODULE_OK; }
static fr_io_final_t mod_process(UNUSED void const *instance, REQUEST *request) { rlm_rcode_t rcode; CONF_SECTION *unlang; fr_dict_enum_t const *dv; VALUE_PAIR *vp; REQUEST_VERIFY(request); switch (request->request_state) { case REQUEST_INIT: if (request->parent && RDEBUG_ENABLED) { RDEBUG("Received %s ID %i", fr_packet_codes[request->packet->code], request->packet->id); log_request_pair_list(L_DBG_LVL_1, request, request->packet->vps, ""); } request->component = "radius"; unlang = cf_section_find(request->server_cs, "recv", "Status-Server"); if (!unlang) { RWDEBUG("Failed to find 'recv Status-Server' section"); request->reply->code = FR_CODE_ACCESS_REJECT; goto send_reply; } RDEBUG("Running 'recv Status-Server' from file %s", cf_filename(unlang)); unlang_interpret_push_section(request, unlang, RLM_MODULE_NOOP, UNLANG_TOP_FRAME); request->request_state = REQUEST_RECV; /* FALL-THROUGH */ case REQUEST_RECV: rcode = unlang_interpret_resume(request); if (request->master_state == REQUEST_STOP_PROCESSING) return FR_IO_DONE; if (rcode == RLM_MODULE_YIELD) return FR_IO_YIELD; rad_assert(request->log.unlang_indent == 0); switch (rcode) { case RLM_MODULE_OK: case RLM_MODULE_UPDATED: request->reply->code = FR_CODE_ACCESS_ACCEPT; break; case RLM_MODULE_FAIL: case RLM_MODULE_HANDLED: request->reply->code = 0; /* don't reply */ break; default: case RLM_MODULE_REJECT: request->reply->code = FR_CODE_ACCESS_REJECT; break; } /* * Allow for over-ride of reply code. */ vp = fr_pair_find_by_da(request->reply->vps, attr_packet_type, TAG_ANY); if (vp) request->reply->code = vp->vp_uint32; dv = fr_dict_enum_by_value(attr_packet_type, fr_box_uint32(request->reply->code)); unlang = NULL; if (dv) unlang = cf_section_find(request->server_cs, "send", dv->alias); if (!unlang) goto send_reply; rerun_nak: RDEBUG("Running 'send %s' from file %s", cf_section_name2(unlang), cf_filename(unlang)); unlang_interpret_push_section(request, unlang, RLM_MODULE_NOOP, UNLANG_TOP_FRAME); request->request_state = REQUEST_SEND; /* FALL-THROUGH */ case REQUEST_SEND: rcode = unlang_interpret_resume(request); if (request->master_state == REQUEST_STOP_PROCESSING) return FR_IO_DONE; if (rcode == RLM_MODULE_YIELD) return FR_IO_YIELD; rad_assert(request->log.unlang_indent == 0); switch (rcode) { case RLM_MODULE_NOOP: case RLM_MODULE_OK: case RLM_MODULE_UPDATED: case RLM_MODULE_HANDLED: /* reply is already set */ break; default: /* * If we over-ride an ACK with a NAK, run * the NAK section. */ if (request->reply->code != FR_CODE_ACCESS_REJECT) { dv = fr_dict_enum_by_value(attr_packet_type, fr_box_uint32(request->reply->code)); RWDEBUG("Failed running 'send %s', trying 'send Access-Reject'", dv->alias); request->reply->code = FR_CODE_ACCESS_REJECT; dv = fr_dict_enum_by_value(attr_packet_type, fr_box_uint32(request->reply->code)); unlang = NULL; if (!dv) goto send_reply; unlang = cf_section_find(request->server_cs, "send", dv->alias); if (unlang) goto rerun_nak; RWDEBUG("Not running 'send %s' section as it does not exist", dv->alias); } break; } send_reply: gettimeofday(&request->reply->timestamp, NULL); /* * Check for "do not respond". */ if (request->reply->code == FR_CODE_DO_NOT_RESPOND) { RDEBUG("Not sending reply to client."); break; } if (request->parent && RDEBUG_ENABLED) { RDEBUG("Sending %s ID %i", fr_packet_codes[request->reply->code], request->reply->id); log_request_pair_list(L_DBG_LVL_1, request, request->reply->vps, ""); } break; default: return FR_IO_FAIL; } return FR_IO_REPLY; }
static fr_io_final_t mod_process(UNUSED void const *instance, REQUEST *request, fr_io_action_t action) { VALUE_PAIR *vp; rlm_rcode_t rcode; CONF_SECTION *unlang; fr_dict_enum_t const *dv; REQUEST_VERIFY(request); /* * Pass this through asynchronously to the module which * is waiting for something to happen. */ if (action != FR_IO_ACTION_RUN) { unlang_interpret_signal(request, (fr_state_signal_t) action); return FR_IO_DONE; } switch (request->request_state) { case REQUEST_INIT: if (request->parent && RDEBUG_ENABLED) { RDEBUG("Received %s ID %i", fr_packet_codes[request->packet->code], request->packet->id); log_request_pair_list(L_DBG_LVL_1, request, request->packet->vps, ""); } request->component = "radius"; /* * We can run CoA-Request or Disconnect-Request sections here */ dv = fr_dict_enum_by_value(attr_packet_type, fr_box_uint32(request->packet->code)); if (!dv) { REDEBUG("Failed to find value for &request:Packet-Type"); return FR_IO_FAIL; } unlang = cf_section_find(request->server_cs, "recv", dv->alias); if (!unlang) { REDEBUG("Failed to find 'recv %s' section", dv->alias); return FR_IO_FAIL; } RDEBUG("Running 'recv %s' from file %s", dv->alias, cf_filename(unlang)); unlang_interpret_push_section(request, unlang, RLM_MODULE_NOOP, UNLANG_TOP_FRAME); request->request_state = REQUEST_RECV; /* FALL-THROUGH */ case REQUEST_RECV: rcode = unlang_interpret_resume(request); if (request->master_state == REQUEST_STOP_PROCESSING) return FR_IO_DONE; if (rcode == RLM_MODULE_YIELD) return FR_IO_YIELD; rad_assert(request->log.unlang_indent == 0); switch (rcode) { case RLM_MODULE_NOOP: case RLM_MODULE_NOTFOUND: case RLM_MODULE_OK: case RLM_MODULE_UPDATED: request->reply->code = request->packet->code + 1; /* ACK */ break; case RLM_MODULE_HANDLED: break; case RLM_MODULE_FAIL: case RLM_MODULE_INVALID: case RLM_MODULE_REJECT: case RLM_MODULE_USERLOCK: default: request->reply->code = request->packet->code + 2; /* NAK */ break; } /* * Allow for over-ride of reply code. */ vp = fr_pair_find_by_da(request->reply->vps, attr_packet_type, TAG_ANY); if (vp) request->reply->code = vp->vp_uint32; dv = fr_dict_enum_by_value(attr_packet_type, fr_box_uint32(request->reply->code)); unlang = NULL; if (dv) unlang = cf_section_find(request->server_cs, "send", dv->alias); if (!unlang) goto send_reply; /* * Note that for NAKs, we do NOT use * reject_delay. This is because we're acting as * a NAS, and we want to respond to the RADIUS * server as quickly as possible. */ rerun_nak: RDEBUG("Running 'send %s' from file %s", cf_section_name2(unlang), cf_filename(unlang)); unlang_interpret_push_section(request, unlang, RLM_MODULE_NOOP, UNLANG_TOP_FRAME); rad_assert(request->log.unlang_indent == 0); request->request_state = REQUEST_SEND; /* FALL-THROUGH */ case REQUEST_SEND: rcode = unlang_interpret_resume(request); if (request->master_state == REQUEST_STOP_PROCESSING) return FR_IO_DONE; if (rcode == RLM_MODULE_YIELD) return FR_IO_YIELD; rad_assert(request->log.unlang_indent == 0); switch (rcode) { /* * We need to send CoA-NAK back if Service-Type * is Authorize-Only. Rely on the user's policy * to do that. We're not a real NAS, so this * restriction doesn't (ahem) apply to us. */ case RLM_MODULE_FAIL: case RLM_MODULE_INVALID: case RLM_MODULE_REJECT: case RLM_MODULE_USERLOCK: default: /* * If we over-ride an ACK with a NAK, run * the NAK section. */ if (request->reply->code == request->packet->code + 1) { dv = fr_dict_enum_by_value(attr_packet_type, fr_box_uint32(request->reply->code)); RWDEBUG("Failed running 'send %s', trying corresponding NAK section.", dv->alias); request->reply->code = request->packet->code + 2; dv = fr_dict_enum_by_value(attr_packet_type, fr_box_uint32(request->reply->code)); unlang = NULL; if (!dv) goto send_reply; unlang = cf_section_find(request->server_cs, "send", dv->alias); if (unlang) goto rerun_nak; RWDEBUG("Not running 'send %s' section as it does not exist", dv->alias); } /* * Else it was already a NAK or something else. */ break; case RLM_MODULE_HANDLED: case RLM_MODULE_NOOP: case RLM_MODULE_NOTFOUND: case RLM_MODULE_OK: case RLM_MODULE_UPDATED: /* reply code is already set */ break; } send_reply: gettimeofday(&request->reply->timestamp, NULL); /* * Check for "do not respond". */ if (request->reply->code == FR_CODE_DO_NOT_RESPOND) { RDEBUG("Not sending reply to client."); break; } if (request->parent && RDEBUG_ENABLED) { RDEBUG("Sending %s ID %i", fr_packet_codes[request->reply->code], request->reply->id); log_request_pair_list(L_DBG_LVL_1, request, request->reply->vps, ""); } break; default: return FR_IO_FAIL; } return FR_IO_REPLY; }
/* * For a client, receive a DHCP packet from a raw packet * socket. Make sure it matches the ongoing request. * * FIXME: split this into two, recv_raw_packet, and verify(packet, original) */ RADIUS_PACKET *fr_dhcv4_raw_packet_recv(int sockfd, struct sockaddr_ll *link_layer, RADIUS_PACKET *request) { VALUE_PAIR *vp; RADIUS_PACKET *packet; uint8_t const *code; uint32_t magic, xid; ssize_t data_len; uint8_t *raw_packet; ethernet_header_t *eth_hdr; ip_header_t *ip_hdr; udp_header_t *udp_hdr; dhcp_packet_t *dhcp_hdr; uint16_t udp_src_port; uint16_t udp_dst_port; size_t dhcp_data_len; socklen_t sock_len; packet = fr_radius_alloc(NULL, false); if (!packet) { fr_strerror_printf("Failed allocating packet"); return NULL; } raw_packet = talloc_zero_array(packet, uint8_t, MAX_PACKET_SIZE); if (!raw_packet) { fr_strerror_printf("Out of memory"); fr_radius_packet_free(&packet); return NULL; } packet->sockfd = sockfd; /* a packet was received (but maybe it is not for us) */ sock_len = sizeof(struct sockaddr_ll); data_len = recvfrom(sockfd, raw_packet, MAX_PACKET_SIZE, 0, (struct sockaddr *)link_layer, &sock_len); uint8_t data_offset = ETH_HDR_SIZE + IP_HDR_SIZE + UDP_HDR_SIZE; /* DHCP data datas after Ethernet, IP, UDP */ if (data_len <= data_offset) DISCARD_RP("Payload (%d) smaller than required for layers 2+3+4", (int)data_len); /* map raw packet to packet header of the different layers (Ethernet, IP, UDP) */ eth_hdr = (ethernet_header_t *)raw_packet; /* * Check Ethernet layer data (L2) */ if (ntohs(eth_hdr->ether_type) != ETH_TYPE_IP) DISCARD_RP("Ethernet type (%d) != IP", ntohs(eth_hdr->ether_type)); /* * If Ethernet destination is not broadcast (ff:ff:ff:ff:ff:ff) * Check if it matches the source HW address used (DHCP-Client-Hardware-Address = 267) */ if ((memcmp(ð_bcast, ð_hdr->ether_dst, ETH_ADDR_LEN) != 0) && (vp = fr_pair_find_by_da(request->vps, attr_dhcp_client_hardware_address, TAG_ANY)) && ((vp->vp_type == FR_TYPE_ETHERNET) && (memcmp(vp->vp_ether, ð_hdr->ether_dst, ETH_ADDR_LEN) != 0))) { /* No match. */ DISCARD_RP("Ethernet destination (%pV) is not broadcast and doesn't match request source (%pV)", fr_box_ether(eth_hdr->ether_dst), &vp->data); } /* * Ethernet is OK. Now look at IP. */ ip_hdr = (ip_header_t *)(raw_packet + ETH_HDR_SIZE); /* * Check IPv4 layer data (L3) */ if (ip_hdr->ip_p != IPPROTO_UDP) DISCARD_RP("IP protocol (%d) != UDP", ip_hdr->ip_p); /* * note: checking the destination IP address is not * useful (it would be the offered IP address - which we * don't know beforehand, or the broadcast address). */ /* * Now check UDP. */ udp_hdr = (udp_header_t *)(raw_packet + ETH_HDR_SIZE + IP_HDR_SIZE); /* * Check UDP layer data (L4) */ udp_src_port = ntohs(udp_hdr->src); udp_dst_port = ntohs(udp_hdr->dst); /* * Check DHCP layer data */ dhcp_data_len = data_len - data_offset; if (dhcp_data_len < MIN_PACKET_SIZE) DISCARD_RP("DHCP packet is too small (%zu < %i)", dhcp_data_len, MIN_PACKET_SIZE); if (dhcp_data_len > MAX_PACKET_SIZE) DISCARD_RP("DHCP packet is too large (%zu > %i)", dhcp_data_len, MAX_PACKET_SIZE); dhcp_hdr = (dhcp_packet_t *)(raw_packet + ETH_HDR_SIZE + IP_HDR_SIZE + UDP_HDR_SIZE); if (dhcp_hdr->htype != 1) DISCARD_RP("DHCP hardware type (%d) != Ethernet (1)", dhcp_hdr->htype); if (dhcp_hdr->hlen != 6) DISCARD_RP("DHCP hardware address length (%d) != 6", dhcp_hdr->hlen); magic = ntohl(dhcp_hdr->option_format); if (magic != DHCP_OPTION_MAGIC_NUMBER) DISCARD_RP("DHCP magic cookie (0x%04x) != DHCP (0x%04x)", magic, DHCP_OPTION_MAGIC_NUMBER); /* * Reply transaction id must match value from request. */ xid = ntohl(dhcp_hdr->xid); if (xid != (uint32_t)request->id) DISCARD_RP("DHCP transaction ID (0x%04x) != xid from request (0x%04x)", xid, request->id) /* all checks ok! this is a DHCP reply we're interested in. */ packet->data_len = dhcp_data_len; packet->data = talloc_memdup(packet, raw_packet + data_offset, dhcp_data_len); TALLOC_FREE(raw_packet); packet->id = xid; code = fr_dhcpv4_packet_get_option((dhcp_packet_t const *) packet->data, packet->data_len, attr_dhcp_message_type); if (!code) { fr_strerror_printf("No message-type option was found in the packet"); fr_radius_packet_free(&packet); return NULL; } if ((code[1] < 1) || (code[2] == 0) || (code[2] > 8)) { fr_strerror_printf("Unknown value for message-type option"); fr_radius_packet_free(&packet); return NULL; } packet->code = code[2]; /* * Create a unique vector from the MAC address and the * DHCP opcode. This is a hack for the RADIUS * infrastructure in the rest of the server. * * Note: packet->data[2] == 6, which is smaller than * sizeof(packet->vector) * * FIXME: Look for client-identifier in packet, * and use that, too? */ memset(packet->vector, 0, sizeof(packet->vector)); memcpy(packet->vector, packet->data + 28, packet->data[2]); packet->vector[packet->data[2]] = packet->code & 0xff; packet->src_port = udp_src_port; packet->dst_port = udp_dst_port; packet->src_ipaddr.af = AF_INET; packet->src_ipaddr.addr.v4.s_addr = ip_hdr->ip_src.s_addr; packet->dst_ipaddr.af = AF_INET; packet->dst_ipaddr.addr.v4.s_addr = ip_hdr->ip_dst.s_addr; return packet; }
/** Create and insert a cache entry * * @return * - #RLM_MODULE_OK on success. * - #RLM_MODULE_UPDATED if we merged the cache entry. * - #RLM_MODULE_FAIL on failure. */ static rlm_rcode_t cache_insert(rlm_cache_t const *inst, REQUEST *request, rlm_cache_handle_t **handle, uint8_t const *key, size_t key_len, int ttl) { vp_map_t const *map; vp_map_t **last, *c_map; VALUE_PAIR *vp; bool merge = false; rlm_cache_entry_t *c; size_t len; TALLOC_CTX *pool; if ((inst->config.max_entries > 0) && inst->driver->count && (inst->driver->count(&inst->config, inst->driver_inst->data, request, handle) > inst->config.max_entries)) { RWDEBUG("Cache is full: %d entries", inst->config.max_entries); return RLM_MODULE_FAIL; } c = cache_alloc(inst, request); if (!c) return RLM_MODULE_FAIL; c->key = talloc_memdup(c, key, key_len); c->key_len = key_len; c->created = c->expires = request->packet->timestamp.tv_sec; c->expires += ttl; last = &c->maps; RDEBUG2("Creating new cache entry"); /* * Alloc a pool so we don't have excessive allocs when * gathering VALUE_PAIRs to cache. */ pool = talloc_pool(NULL, 2048); for (map = inst->maps; map != NULL; map = map->next) { VALUE_PAIR *to_cache = NULL; fr_cursor_t cursor; rad_assert(map->lhs && map->rhs); /* * Calling map_to_vp gives us exactly the same result, * as if this were an update section. */ if (map_to_vp(pool, &to_cache, request, map, NULL) < 0) { RDEBUG2("Skipping %s", map->rhs->name); continue; } for (vp = fr_cursor_init(&cursor, &to_cache); vp; vp = fr_cursor_next(&cursor)) { /* * Prevent people from accidentally caching * cache control attributes. */ if (map->rhs->type == TMPL_TYPE_LIST) switch (vp->da->attr) { case FR_CACHE_TTL: case FR_CACHE_STATUS_ONLY: case FR_CACHE_MERGE_NEW: case FR_CACHE_ENTRY_HITS: RDEBUG2("Skipping %s", vp->da->name); continue; default: break; } RINDENT(); if (RDEBUG_ENABLED2) map_debug_log(request, map, vp); REXDENT(); MEM(c_map = talloc_zero(c, vp_map_t)); c_map->op = map->op; /* * Now we turn the VALUE_PAIRs into maps. */ switch (map->lhs->type) { /* * Attributes are easy, reuse the LHS, and create a new * RHS with the fr_value_box_t from the VALUE_PAIR. */ case TMPL_TYPE_ATTR: c_map->lhs = map->lhs; /* lhs shouldn't be touched, so this is ok */ do_rhs: MEM(c_map->rhs = tmpl_init(talloc(c_map, vp_tmpl_t), TMPL_TYPE_DATA, map->rhs->name, map->rhs->len, T_BARE_WORD)); if (fr_value_box_copy(c_map->rhs, &c_map->rhs->tmpl_value, &vp->data) < 0) { REDEBUG("Failed copying attribute value"); error: talloc_free(pool); talloc_free(c); return RLM_MODULE_FAIL; } c_map->rhs->tmpl_value_type = vp->vp_type; if (vp->vp_type == FR_TYPE_STRING) { c_map->rhs->quote = is_printable(vp->vp_strvalue, vp->vp_length) ? T_SINGLE_QUOTED_STRING : T_DOUBLE_QUOTED_STRING; } break; /* * Lists are weird... We need to fudge a new LHS template, * which is a combination of the LHS list and the attribute. */ case TMPL_TYPE_LIST: { char attr[256]; MEM(c_map->lhs = tmpl_init(talloc(c_map, vp_tmpl_t), TMPL_TYPE_ATTR, map->lhs->name, map->lhs->len, T_BARE_WORD)); c_map->lhs->tmpl_da = vp->da; if (vp->da->flags.is_unknown) { /* for tmpl_verify() */ c_map->lhs->tmpl_unknown = fr_dict_unknown_acopy(c_map->lhs, vp->da); c_map->lhs->tmpl_da = c_map->lhs->tmpl_unknown; } c_map->lhs->tmpl_tag = vp->tag; c_map->lhs->tmpl_list = map->lhs->tmpl_list; c_map->lhs->tmpl_num = map->lhs->tmpl_num; c_map->lhs->tmpl_request = map->lhs->tmpl_request; /* * We need to rebuild the attribute name, to be the * one we copied from the source list. */ len = tmpl_snprint(attr, sizeof(attr), c_map->lhs); if (is_truncated(len, sizeof(attr))) { REDEBUG("Serialized attribute too long. Must be < " STRINGIFY(sizeof(attr)) " bytes, got %zu bytes", len); goto error; } c_map->lhs->len = len; c_map->lhs->name = talloc_typed_strdup(c_map->lhs, attr); } goto do_rhs; default: rad_assert(0); } *last = c_map; last = &(*last)->next; } talloc_free_children(pool); /* reset pool state */ } talloc_free(pool); /* * Check to see if we need to merge the entry into the request */ vp = fr_pair_find_by_da(request->control, attr_cache_merge_new, TAG_ANY); if (vp && vp->vp_bool) merge = true; if (merge) cache_merge(inst, request, c); for (;;) { cache_status_t ret; ret = inst->driver->insert(&inst->config, inst->driver_inst->data, request, *handle, c); switch (ret) { case CACHE_RECONNECT: if (cache_reconnect(handle, inst, request) == 0) continue; return RLM_MODULE_FAIL; case CACHE_OK: RDEBUG2("Committed entry, TTL %d seconds", ttl); cache_free(inst, &c); return merge ? RLM_MODULE_UPDATED : RLM_MODULE_OK; default: talloc_free(c); /* Failed insertion - use talloc_free not the driver free */ return RLM_MODULE_FAIL; } } }
static RADIUS_PACKET *fr_dhcpv4_recv_raw_loop(int lsockfd, #ifdef HAVE_LINUX_IF_PACKET_H struct sockaddr_ll *p_ll, #endif RADIUS_PACKET *request_p) { struct timeval tval; RADIUS_PACKET *reply_p = NULL; RADIUS_PACKET *cur_reply_p = NULL; int nb_reply = 0; int nb_offer = 0; dc_offer_t *offer_list = NULL; fd_set read_fd; int retval; memcpy(&tval, &tv_timeout, sizeof(struct timeval)); /* Loop waiting for DHCP replies until timer expires */ while (fr_timeval_isset(&tval)) { if ((!reply_p) || (cur_reply_p)) { // only debug at start and each time we get a valid DHCP reply on raw socket DEBUG("Waiting for %s DHCP replies for: %d.%06d", (nb_reply>0)?" additional ":" ", (int)tval.tv_sec, (int)tval.tv_usec); } cur_reply_p = NULL; FD_ZERO(&read_fd); FD_SET(lsockfd, &read_fd); retval = select(lsockfd + 1, &read_fd, NULL, NULL, &tval); if (retval < 0) { fr_strerror_printf("Select on DHCP socket failed: %s", fr_syserror(errno)); return NULL; } if (retval > 0 && FD_ISSET(lsockfd, &read_fd)) { /* There is something to read on our socket */ #ifdef HAVE_LINUX_IF_PACKET_H cur_reply_p = fr_dhcv4_raw_packet_recv(lsockfd, p_ll, request_p); #else # ifdef HAVE_LIBPCAP cur_reply_p = fr_dhcpv4_pcap_recv(pcap); # else # error Need <if/packet.h> or <pcap.h> # endif #endif } else { // Not all implementations of select clear the timer memset(&tval, 0, sizeof(tval)); } if (cur_reply_p) { nb_reply ++; if (fr_debug_lvl) print_hex(cur_reply_p); if (fr_dhcpv4_packet_decode(cur_reply_p) < 0) { ERROR("Failed decoding reply"); return NULL; } if (!reply_p) reply_p = cur_reply_p; if (cur_reply_p->code == FR_DHCP_OFFER) { VALUE_PAIR *vp1 = fr_pair_find_by_da(cur_reply_p->vps, attr_dhcp_dhcp_server_identifier, TAG_ANY); VALUE_PAIR *vp2 = fr_pair_find_by_da(cur_reply_p->vps, attr_dhcp_your_ip_address, TAG_ANY); if (vp1 && vp2) { nb_offer++; offer_list = talloc_realloc(request_p, offer_list, dc_offer_t, nb_offer); offer_list[nb_offer - 1].server_addr = vp1->vp_ipv4addr; offer_list[nb_offer - 1].offered_addr = vp2->vp_ipv4addr; } } } } if (0 == nb_reply) { DEBUG("No valid DHCP reply received"); return NULL; } /* display offer(s) received */ if (nb_offer > 0 ) { DEBUG("Received %d DHCP Offer(s):", nb_offer); int i; for (i = 0; i < nb_reply; i++) { char server_addr_buf[INET6_ADDRSTRLEN]; char offered_addr_buf[INET6_ADDRSTRLEN]; DEBUG("IP address: %s offered by DHCP server: %s", inet_ntop(AF_INET, &offer_list[i].offered_addr, offered_addr_buf, sizeof(offered_addr_buf)), inet_ntop(AF_INET, &offer_list[i].server_addr, server_addr_buf, sizeof(server_addr_buf)) ); } } return reply_p; }
/** Get one set of quintuplets from the request * */ static int vector_umts_from_quintuplets(eap_session_t *eap_session, VALUE_PAIR *vps, fr_sim_keys_t *keys) { REQUEST *request = eap_session->request; VALUE_PAIR *rand_vp = NULL, *xres_vp = NULL, *ck_vp = NULL, *ik_vp = NULL; VALUE_PAIR *autn_vp = NULL, *sqn_vp = NULL, *ak_vp = NULL; /* * Fetch AUTN */ autn_vp = fr_pair_find_by_da(vps, attr_eap_aka_autn, TAG_ANY); if (!autn_vp) { RDEBUG3("No &control:%s attribute found, not using UMTS quintuplets", attr_eap_aka_autn->name); return 1; } if (autn_vp->vp_length > SIM_VECTOR_UMTS_AUTN_SIZE) { REDEBUG("&control:%s incorrect length. Expected " STRINGIFY(SIM_VECTOR_UMTS_AUTN_SIZE) " bytes, got %zu bytes", attr_eap_aka_autn->name, autn_vp->vp_length); return -1; } /* * Fetch CK */ ck_vp = fr_pair_find_by_da(vps, attr_eap_aka_ck, TAG_ANY); if (!ck_vp) { RDEBUG3("No &control:%s attribute found, not using UMTS quintuplets", attr_eap_aka_ck->name); return 1; } if (ck_vp->vp_length > SIM_VECTOR_UMTS_CK_SIZE) { REDEBUG("&control:%s incorrect length. Expected " STRINGIFY(EAP_AKA_XRES_MAX_SIZE) " bytes, got %zu bytes", attr_eap_aka_ck->name, ck_vp->vp_length); return -1; } /* * Fetch IK */ ik_vp = fr_pair_find_by_da(vps, attr_eap_aka_ik, TAG_ANY); if (!ik_vp) { RDEBUG3("No &control:%s attribute found, not using UMTS quintuplets", attr_eap_aka_ik->name); return 1; } if (ik_vp->vp_length > SIM_VECTOR_UMTS_IK_SIZE) { REDEBUG("&control:%s incorrect length. Expected " STRINGIFY(SIM_VECTOR_UMTS_IK_SIZE) " bytes, got %zu bytes", attr_eap_aka_ik->name, ik_vp->vp_length); return -1; } /* * Fetch RAND */ rand_vp = fr_pair_find_by_da(vps, attr_eap_aka_rand, TAG_ANY); if (!rand_vp) { RDEBUG3("No &control:%s attribute found, not using quintuplet derivation", attr_eap_aka_rand->name); return 1; } if (rand_vp->vp_length != SIM_VECTOR_UMTS_RAND_SIZE) { REDEBUG("&control:%s incorrect length. Expected " STRINGIFY(SIM_VECTOR_UMTS_RAND_SIZE) " bytes, " "got %zu bytes", attr_eap_aka_rand->name, rand_vp->vp_length); return -1; } /* * Fetch XRES */ xres_vp = fr_pair_find_by_da(vps, attr_eap_aka_xres, TAG_ANY); if (!xres_vp) { RDEBUG3("No &control:%s attribute found, not using UMTS quintuplets", attr_eap_aka_xres->name); return 1; } if (xres_vp->vp_length > SIM_VECTOR_UMTS_XRES_MAX_SIZE) { REDEBUG("&control:%s incorrect length. Expected < " STRINGIFY(EAP_AKA_XRES_MAX_SIZE) " bytes, got %zu bytes", attr_eap_aka_xres->name, xres_vp->vp_length); return -1; } /* * Fetch (optional) AK */ ak_vp = fr_pair_find_by_da(vps, attr_eap_aka_ak, TAG_ANY); if (ak_vp && (ak_vp->vp_length != MILENAGE_AK_SIZE)) { REDEBUG("&control:%s incorrect length. Expected " STRINGIFY(MILENAGE_AK_SIZE) " bytes, got %zu bytes", attr_eap_aka_ak->name, ak_vp->vp_length); return -1; } /* * Fetch (optional) SQN */ sqn_vp = fr_pair_find_by_da(vps, attr_sim_sqn, TAG_ANY); if (sqn_vp && (sqn_vp->vp_length != MILENAGE_SQN_SIZE)) { REDEBUG("&control:%s incorrect length. Expected " STRINGIFY(MILENAGE_AK_SIZE) " bytes, got %zu bytes", attr_sim_sqn->name, sqn_vp->vp_length); return -1; } /* * SQN = AUTN[0..5] ⊕ AK * AK = AK */ if (ak_vp && !sqn_vp) { keys->sqn = uint48_from_buff(autn_vp->vp_octets) ^ uint48_from_buff(ak_vp->vp_octets); memcpy(keys->umts.vector.ak, ak_vp->vp_octets, sizeof(keys->umts.vector.ak)); /* * SQN = SQN * AK = AUTN[0..5] ⊕ SQN */ } else if (sqn_vp && !ak_vp) { keys->sqn = sqn_vp->vp_uint64; uint48_to_buff(keys->umts.vector.ak, uint48_from_buff(autn_vp->vp_octets) ^ sqn_vp->vp_uint64); /* * SQN = SQN * AK = AK */ } else if (sqn_vp && ak_vp) { keys->sqn = sqn_vp->vp_uint64; memcpy(keys->umts.vector.ak, ak_vp->vp_octets, sizeof(keys->umts.vector.ak)); /* * SQN = AUTN[0..5] * AK = 0x000000000000 */ } else { keys->sqn = uint48_from_buff(autn_vp->vp_octets); memset(keys->umts.vector.ak, 0, sizeof(keys->umts.vector.ak)); } memcpy(keys->umts.vector.autn, autn_vp->vp_octets, SIM_VECTOR_UMTS_AUTN_SIZE); memcpy(keys->umts.vector.ck, ck_vp->vp_octets, SIM_VECTOR_UMTS_CK_SIZE); memcpy(keys->umts.vector.ik, ik_vp->vp_octets, SIM_VECTOR_UMTS_IK_SIZE); memcpy(keys->umts.vector.rand, rand_vp->vp_octets, SIM_VECTOR_UMTS_RAND_SIZE); memcpy(keys->umts.vector.xres, xres_vp->vp_octets, xres_vp->vp_length); keys->umts.vector.xres_len = xres_vp->vp_length; /* xres is variable length */ return 0; }
static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, UNUSED void *thread, REQUEST *request) { #ifdef WITH_DHCP int rcode; VALUE_PAIR *vp; rlm_soh_t const *inst = instance; if (!inst->dhcp) return RLM_MODULE_NOOP; vp = fr_pair_find_by_da(request->packet->vps, attr_dhcp_vendor, TAG_ANY); if (vp) { /* * vendor-specific options contain * * vendor opt 220/0xdc - SoH payload, or null byte to probe, or string * "NAP" to indicate server-side support for SoH in OFFERs * * vendor opt 222/0xde - SoH correlation ID as utf-16 string, yuck... */ uint8_t vopt, vlen; uint8_t const *data; data = vp->vp_octets; while (data < vp->vp_octets + vp->vp_length) { vopt = *data++; vlen = *data++; switch (vopt) { case 220: if (vlen <= 1) { uint8_t *p; RDEBUG2("SoH adding NAP marker to DHCP reply"); /* client probe; send "NAP" in the reply */ vp = fr_pair_afrom_da(request->reply, attr_dhcp_vendor); p = talloc_array(vp, uint8_t, 5); p[0] = 220; p[1] = 3; p[4] = 'N'; p[3] = 'A'; p[2] = 'P'; fr_pair_value_memsteal(vp, p); fr_pair_add(&request->reply->vps, vp); } else { RDEBUG2("SoH decoding NAP from DHCP request"); /* SoH payload */ rcode = soh_verify(request, data, vlen); if (rcode < 0) { return RLM_MODULE_FAIL; } } break; default: /* nothing to do */ break; } data += vlen; } return RLM_MODULE_OK; } #endif return RLM_MODULE_NOOP; }
static FR_CODE eap_fast_eap_payload(REQUEST *request, eap_session_t *eap_session, tls_session_t *tls_session, VALUE_PAIR *tlv_eap_payload) { FR_CODE code = FR_CODE_ACCESS_REJECT; rlm_rcode_t rcode; VALUE_PAIR *vp; eap_fast_tunnel_t *t; REQUEST *fake; RDEBUG2("Processing received EAP Payload"); /* * Allocate a fake REQUEST structure. */ fake = request_alloc_fake(request, NULL); rad_assert(!fake->packet->vps); t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t); /* * Add the tunneled attributes to the fake request. */ fake->packet->vps = fr_pair_afrom_da(fake->packet, attr_eap_message); fr_pair_value_memcpy(fake->packet->vps, tlv_eap_payload->vp_octets, tlv_eap_payload->vp_length, false); RDEBUG2("Got tunneled request"); log_request_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); /* * Tell the request that it's a fake one. */ MEM(fr_pair_add_by_da(fake->packet, &vp, &fake->packet->vps, attr_freeradius_proxied_to) >= 0); fr_pair_value_from_str(vp, "127.0.0.1", sizeof("127.0.0.1"), '\0', false); /* * Update other items in the REQUEST data structure. */ fake->username = fr_pair_find_by_da(fake->packet->vps, attr_user_name, TAG_ANY); fake->password = fr_pair_find_by_da(fake->packet->vps, attr_user_password, 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_da(fake->packet->vps, attr_eap_message, TAG_ANY); if (vp && (vp->vp_length >= EAP_HEADER_LEN + 2) && (vp->vp_strvalue[0] == FR_EAP_CODE_RESPONSE) && (vp->vp_strvalue[EAP_HEADER_LEN] == FR_EAP_METHOD_IDENTITY) && (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) { /* * Create & remember a User-Name */ MEM(t->username = fr_pair_afrom_da(t, attr_user_name)); t->username->vp_tainted = true; fr_pair_value_bstrncpy(t->username, vp->vp_octets + 5, vp->vp_length - 5); RDEBUG2("Got tunneled identity of %pV", &t->username->data); } 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_copy(fake->packet, t->username); fr_pair_add(&fake->packet->vps, vp); fake->username = vp; } } /* else the request ALREADY had a User-Name */ if (t->stage == EAP_FAST_AUTHENTICATION) { /* FIXME do this only for MSCHAPv2 */ VALUE_PAIR *tvp; tvp = fr_pair_afrom_da(fake, attr_eap_type); tvp->vp_uint32 = t->default_provisioning_method; fr_pair_add(&fake->control, tvp); /* * RFC 5422 section 3.2.3 - Authenticating Using EAP-FAST-MSCHAPv2 */ if (t->mode == EAP_FAST_PROVISIONING_ANON) { tvp = fr_pair_afrom_da(fake, attr_ms_chap_challenge); fr_pair_value_memcpy(tvp, t->keyblock->server_challenge, RADIUS_CHAP_CHALLENGE_LENGTH, false); fr_pair_add(&fake->control, tvp); RHEXDUMP(L_DBG_LVL_MAX, t->keyblock->server_challenge, RADIUS_CHAP_CHALLENGE_LENGTH, "MSCHAPv2 auth_challenge"); tvp = fr_pair_afrom_da(fake, attr_ms_chap_peer_challenge); fr_pair_value_memcpy(tvp, t->keyblock->client_challenge, RADIUS_CHAP_CHALLENGE_LENGTH, false); fr_pair_add(&fake->control, tvp); RHEXDUMP(L_DBG_LVL_MAX, t->keyblock->client_challenge, RADIUS_CHAP_CHALLENGE_LENGTH, "MSCHAPv2 peer_challenge"); } } /* * Call authentication recursively, which will * do PAP, CHAP, MS-CHAP, etc. */ eap_virtual_server(request, fake, eap_session, t->virtual_server); /* * 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_da(fake->control, attr_proxy_to_realm, TAG_ANY); if (vp) { int ret; eap_tunnel_data_t *tunnel; RDEBUG2("Tunneled authentication will be proxied to %pV", &vp->data); /* * Tell the original request that it's going to be proxied. */ fr_pair_list_copy_by_da(request, &request->control, fake->control, attr_proxy_to_realm); /* * Seed the proxy packet with the tunneled request. */ rad_assert(!request->proxy); /* * FIXME: Actually proxy stuff */ request->proxy = request_alloc_fake(request, NULL); request->proxy->packet = talloc_steal(request->proxy, fake->packet); memset(&request->proxy->packet->src_ipaddr, 0, sizeof(request->proxy->packet->src_ipaddr)); memset(&request->proxy->packet->src_ipaddr, 0, sizeof(request->proxy->packet->src_ipaddr)); request->proxy->packet->src_port = 0; request->proxy->packet->dst_port = 0; fake->packet = NULL; fr_radius_packet_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; /* * Associate the callback with the request. */ ret = request_data_add(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK, tunnel, false, false, false); fr_cond_assert(ret == 0); /* * rlm_eap.c has taken care of associating the eap_session * with the fake request. * * So we associate the fake request with this request. */ ret = request_data_add(request, request->proxy, REQUEST_DATA_EAP_MSCHAP_TUNNEL_CALLBACK, fake, true, false, false); fr_cond_assert(ret == 0); fake = NULL; /* * Didn't authenticate the packet, but we're proxying it. */ code = FR_CODE_STATUS_CLIENT; } else #endif /* WITH_PROXY */ { REDEBUG("No tunneled reply was found, and the request was not proxied: rejecting the user"); code = FR_CODE_ACCESS_REJECT; } break; default: /* * Returns RLM_MODULE_FOO, and we want to return FR_FOO */ rcode = process_reply(eap_session, tls_session, request, fake->reply); switch (rcode) { case RLM_MODULE_REJECT: code = FR_CODE_ACCESS_REJECT; break; case RLM_MODULE_HANDLED: code = FR_CODE_ACCESS_CHALLENGE; break; case RLM_MODULE_OK: code = FR_CODE_ACCESS_ACCEPT; break; default: code = FR_CODE_ACCESS_REJECT; break; } break; } talloc_free(fake); return code; }
/** Synchronously load cookie data * * FIXME: This should not be synchronous, but integrating it into the event loop * before the server has started processing requests makes my head hurt. * * @param[in] ctx to allocate cookie buffer in. * @param[out] cookie Where to write the cookie we loaded. * @param[in] listen structure encapsulating the LDAP * @param[in] config of the sync we're loading the cookie for. * @return * - -1 on failure. * - 0 on success. * - 1 no cookie returned. */ static int proto_ldap_cookie_load(TALLOC_CTX *ctx, uint8_t **cookie, rad_listen_t *listen, sync_config_t const *config) { proto_ldap_inst_t *inst = talloc_get_type_abort(listen->data, proto_ldap_inst_t); REQUEST *request; CONF_SECTION *unlang; int ret = 0; rlm_rcode_t rcode; request = proto_ldap_request_setup(listen, inst, 0); if (!request) return -1; proto_ldap_attributes_add(request, config); request->packet->code = LDAP_SYNC_CODE_COOKIE_STORE; unlang = cf_section_find(request->server_cs, "load", "Cookie"); if (!unlang) { RDEBUG2("Ignoring %s operation. Add \"load Cookie {}\" to virtual-server \"%s\"" " to handle", fr_int2str(ldap_sync_code_table, request->packet->code, "<INVALID>"), cf_section_name2(request->server_cs)); } *cookie = NULL; rcode = unlang_interpret_synchronous(request, unlang, RLM_MODULE_NOOP); switch (rcode) { case RLM_MODULE_OK: case RLM_MODULE_UPDATED: { VALUE_PAIR *vp; vp = fr_pair_find_by_da(request->reply->vps, attr_ldap_sync_cookie, TAG_ANY); if (!vp) { if (config->allow_refresh) RDEBUG2("No &reply:Cookie attribute found. All entries matching " "sync configuration will be returned"); ret = 1; goto finish; } /* * So the request pool doesn't hang around indefinitely. */ MEM(*cookie = talloc_memdup(ctx, vp->vp_octets, vp->vp_length)); ret = 0; } break; case RLM_MODULE_NOOP: if (config->allow_refresh) RDEBUG2("Section returned \"noop\". All entries matching sync " "configuration will be returned"); ret = 1; break; default: RERROR("Section must return \"ok\", \"updated\", or \"noop\" for listener instantiation to succeed"); ret = -1; break; } finish: talloc_free(request); return ret; }
static rlm_rcode_t mod_cache_it(void *instance, UNUSED void *thread, REQUEST *request) { rlm_cache_entry_t *c = NULL; rlm_cache_t const *inst = instance; rlm_cache_handle_t *handle; fr_cursor_t cursor; VALUE_PAIR *vp; bool merge = true, insert = true, expire = false, set_ttl = false; int exists = -1; uint8_t buffer[1024]; uint8_t const *key; ssize_t key_len; rlm_rcode_t rcode = RLM_MODULE_NOOP; int ttl = inst->config.ttl; key_len = tmpl_expand((char const **)&key, (char *)buffer, sizeof(buffer), request, inst->config.key, NULL, NULL); if (key_len < 0) return RLM_MODULE_FAIL; if (key_len == 0) { REDEBUG("Zero length key string is invalid"); return RLM_MODULE_INVALID; } /* * If Cache-Status-Only == yes, only return whether we found a * valid cache entry */ vp = fr_pair_find_by_da(request->control, attr_cache_status_only, TAG_ANY); if (vp && vp->vp_bool) { RINDENT(); RDEBUG3("status-only: yes"); REXDENT(); if (cache_acquire(&handle, inst, request) < 0) return RLM_MODULE_FAIL; rcode = cache_find(&c, inst, request, &handle, key, key_len); if (rcode == RLM_MODULE_FAIL) goto finish; rad_assert(!inst->driver->acquire || handle); rcode = c ? RLM_MODULE_OK: RLM_MODULE_NOTFOUND; goto finish; } /* * Figure out what operation we're doing */ vp = fr_pair_find_by_da(request->control, attr_cache_allow_merge, TAG_ANY); if (vp) merge = vp->vp_bool; vp = fr_pair_find_by_da(request->control, attr_cache_allow_insert, TAG_ANY); if (vp) insert = vp->vp_bool; vp = fr_pair_find_by_da(request->control, attr_cache_ttl, TAG_ANY); if (vp) { if (vp->vp_int32 == 0) { expire = true; } else if (vp->vp_int32 < 0) { expire = true; ttl = -(vp->vp_int32); /* Updating the TTL */ } else { set_ttl = true; ttl = vp->vp_int32; } } RINDENT(); RDEBUG3("merge : %s", merge ? "yes" : "no"); RDEBUG3("insert : %s", insert ? "yes" : "no"); RDEBUG3("expire : %s", expire ? "yes" : "no"); RDEBUG3("ttl : %i", ttl); REXDENT(); if (cache_acquire(&handle, inst, request) < 0) return RLM_MODULE_FAIL; /* * Retrieve the cache entry and merge it with the current request * recording whether the entry existed. */ if (merge) { rcode = cache_find(&c, inst, request, &handle, key, key_len); switch (rcode) { case RLM_MODULE_FAIL: goto finish; case RLM_MODULE_OK: rcode = cache_merge(inst, request, c); exists = 1; break; case RLM_MODULE_NOTFOUND: rcode = RLM_MODULE_NOTFOUND; exists = 0; break; default: rad_assert(0); } rad_assert(!inst->driver->acquire || handle); } /* * Expire the entry if told to, and we either don't know whether * it exists, or we know it does. * * We only expire if we're not inserting, as driver insert methods * should perform upserts. */ if (expire && ((exists == -1) || (exists == 1))) { if (!insert) { rad_assert(!set_ttl); switch (cache_expire(inst, request, &handle, key, key_len)) { case RLM_MODULE_FAIL: rcode = RLM_MODULE_FAIL; goto finish; case RLM_MODULE_OK: if (rcode == RLM_MODULE_NOOP) rcode = RLM_MODULE_OK; break; case RLM_MODULE_NOTFOUND: if (rcode == RLM_MODULE_NOOP) rcode = RLM_MODULE_NOTFOUND; break; default: rad_assert(0); break; } /* If it previously existed, it doesn't now */ } /* Otherwise use insert to overwrite */ exists = 0; } /* * If we still don't know whether it exists or not * and we need to do an insert or set_ttl operation * determine that now. */ if ((exists < 0) && (insert || set_ttl)) { switch (cache_find(&c, inst, request, &handle, key, key_len)) { case RLM_MODULE_FAIL: rcode = RLM_MODULE_FAIL; goto finish; case RLM_MODULE_OK: exists = 1; if (rcode != RLM_MODULE_UPDATED) rcode = RLM_MODULE_OK; break; case RLM_MODULE_NOTFOUND: exists = 0; break; default: rad_assert(0); } rad_assert(!inst->driver->acquire || handle); } /* * We can only alter the TTL on an entry if it exists. */ if (set_ttl && (exists == 1)) { rad_assert(c); c->expires = request->packet->timestamp.tv_sec + ttl; switch (cache_set_ttl(inst, request, &handle, c)) { case RLM_MODULE_FAIL: rcode = RLM_MODULE_FAIL; goto finish; case RLM_MODULE_NOTFOUND: case RLM_MODULE_OK: if (rcode != RLM_MODULE_UPDATED) rcode = RLM_MODULE_OK; goto finish; default: rad_assert(0); } } /* * Inserts are upserts, so we don't care about the * entry state, just that we're not meant to be * setting the TTL, which precludes performing an * insert. */ if (insert && (exists == 0)) { switch (cache_insert(inst, request, &handle, key, key_len, ttl)) { case RLM_MODULE_FAIL: rcode = RLM_MODULE_FAIL; goto finish; case RLM_MODULE_OK: if (rcode != RLM_MODULE_UPDATED) rcode = RLM_MODULE_OK; break; case RLM_MODULE_UPDATED: rcode = RLM_MODULE_UPDATED; break; default: rad_assert(0); } rad_assert(!inst->driver->acquire || handle); goto finish; } finish: cache_free(inst, &c); cache_release(inst, request, &handle); /* * Clear control attributes */ for (vp = fr_cursor_init(&cursor, &request->control); vp; vp = fr_cursor_next(&cursor)) { again: if (!fr_dict_attr_is_top_level(vp->da)) continue; switch (vp->da->attr) { case FR_CACHE_TTL: case FR_CACHE_STATUS_ONLY: case FR_CACHE_ALLOW_MERGE: case FR_CACHE_ALLOW_INSERT: case FR_CACHE_MERGE_NEW: RDEBUG2("Removing &control:%s", vp->da->name); vp = fr_cursor_remove(&cursor); talloc_free(vp); vp = fr_cursor_current(&cursor); if (!vp) break; goto again; } } return rcode; }
static int vector_umts_from_ki(eap_session_t *eap_session, VALUE_PAIR *vps, fr_sim_keys_t *keys) { REQUEST *request = eap_session->request; VALUE_PAIR *ki_vp, *amf_vp, *sqn_vp, *version_vp; uint64_t sqn; uint8_t amf_buff[MILENAGE_AMF_SIZE] = { 0x00, 0x00 }; uint8_t opc_buff[MILENAGE_OPC_SIZE]; uint8_t const *opc_p; uint32_t version = 4; int i; ki_vp = fr_pair_find_by_da(vps, attr_sim_ki, TAG_ANY); if (!ki_vp) { RDEBUG3("No &control:%s found, not generating quintuplets locally", attr_sim_ki->name); return 1; } else if (ki_vp->vp_length != MILENAGE_KI_SIZE) { REDEBUG("&control:%s has incorrect length, expected %u bytes got %zu bytes", attr_sim_ki->name, MILENAGE_KI_SIZE, ki_vp->vp_length); return -1; } if (vector_opc_from_op(request, &opc_p, opc_buff, vps, ki_vp->vp_octets) < 0) return -1; amf_vp = fr_pair_find_by_da(vps, attr_sim_amf, TAG_ANY); if (amf_vp) { if (amf_vp->vp_length != sizeof(amf_buff)) { REDEBUG("&control:%s has incorrect length, expected %u bytes got %zu bytes", attr_sim_amf->name, MILENAGE_AMF_SIZE, amf_vp->vp_length); return -1; } memcpy(amf_buff, amf_vp->vp_octets, sizeof(amf_buff)); } sqn_vp = fr_pair_find_by_da(vps, attr_sim_sqn, TAG_ANY); sqn = sqn_vp ? sqn_vp->vp_uint64 : 2; /* * We default to milenage */ version_vp = fr_pair_find_by_da(vps, attr_sim_algo_version, TAG_ANY); if (version_vp) version = version_vp->vp_uint32; for (i = 0; i < SIM_VECTOR_UMTS_RAND_SIZE; i += sizeof(uint32_t)) { uint32_t rand = fr_rand(); memcpy(&keys->umts.vector.rand[i], &rand, sizeof(rand)); } switch (version) { case 4: { uint8_t sqn_buff[MILENAGE_SQN_SIZE]; keys->sqn = sqn_vp ? sqn_vp->vp_uint64 : 0; uint48_to_buff(sqn_buff, keys->sqn); RDEBUG3("Milenage inputs"); RINDENT(); /* * Don't change colon indent, matches other messages later... */ RHEXDUMP_INLINE(L_DBG_LVL_3, ki_vp->vp_octets, MILENAGE_KI_SIZE, "Ki :"); RHEXDUMP_INLINE(L_DBG_LVL_3, opc_p, MILENAGE_OPC_SIZE, "OPc :"); RHEXDUMP_INLINE(L_DBG_LVL_3, sqn_buff, MILENAGE_SQN_SIZE, "SQN :"); RHEXDUMP_INLINE(L_DBG_LVL_3, amf_buff, MILENAGE_AMF_SIZE, "AMF :"); REXDENT(); if (milenage_umts_generate(keys->umts.vector.autn, keys->umts.vector.ik, keys->umts.vector.ck, keys->umts.vector.ak, keys->umts.vector.xres, opc_p, amf_buff, ki_vp->vp_octets, sqn, keys->umts.vector.rand) < 0) { RPEDEBUG2("Failed deriving UMTS Quintuplet"); return -1; } keys->umts.vector.xres_len = 8; } return 0; case 1: case 2: case 3: REDEBUG("Only Milenage can be used to generate quintuplets"); return -1; default: REDEBUG("Unknown/unsupported algorithm %i", version); 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 int vector_gsm_from_ki(eap_session_t *eap_session, VALUE_PAIR *vps, int idx, fr_sim_keys_t *keys) { REQUEST *request = eap_session->request; VALUE_PAIR *ki_vp, *version_vp; uint8_t opc_buff[MILENAGE_OPC_SIZE]; uint8_t const *opc_p; uint32_t version; int i; /* * Generate a new RAND value, and derive Kc and SRES from Ki */ ki_vp = fr_pair_find_by_da(vps, attr_sim_ki, TAG_ANY); if (!ki_vp) { RDEBUG3("No &control:%sfound, not generating triplets locally", attr_sim_ki->name); return 1; } else if (ki_vp->vp_length != MILENAGE_KI_SIZE) { REDEBUG("&control:%s has incorrect length, expected 16 bytes got %zu bytes", attr_sim_ki->name, ki_vp->vp_length); return -1; } /* * Check to see if have a Ki for the IMSI, this allows us to generate the rest * of the triplets. */ version_vp = fr_pair_find_by_da(vps, attr_sim_algo_version, TAG_ANY); if (!version_vp) { if (vector_opc_from_op(request, &opc_p, opc_buff, vps, ki_vp->vp_octets) < 0) return -1; version = opc_p ? 4 : 3; /* * Version we explicitly specified, see if we can find the prerequisite * attributes. */ } else { version = version_vp->vp_uint32; if (version == 4) { if (vector_opc_from_op(request, &opc_p, opc_buff, vps, ki_vp->vp_octets) < 0) return -1; if (!opc_p) { RPEDEBUG2("No &control:%s or &control:%s found, " "can't run Milenage (COMP128-4)", attr_sim_op->name, attr_sim_opc->name); return -1; } } } for (i = 0; i < SIM_VECTOR_GSM_RAND_SIZE; i += sizeof(uint32_t)) { uint32_t rand = fr_rand(); memcpy(&keys->gsm.vector[idx].rand[i], &rand, sizeof(rand)); } switch (version) { case 1: comp128v1(keys->gsm.vector[idx].sres, keys->gsm.vector[idx].kc, ki_vp->vp_octets, keys->gsm.vector[idx].rand); break; case 2: comp128v23(keys->gsm.vector[idx].sres, keys->gsm.vector[idx].kc, ki_vp->vp_octets, keys->gsm.vector[idx].rand, true); break; case 3: comp128v23(keys->gsm.vector[idx].sres, keys->gsm.vector[idx].kc, ki_vp->vp_octets, keys->gsm.vector[idx].rand, false); break; case 4: if (milenage_gsm_generate(keys->gsm.vector[idx].sres, keys->gsm.vector[idx].kc, opc_p, ki_vp->vp_octets, keys->gsm.vector[idx].rand) < 0) { RPEDEBUG2("Failed deriving GSM triplet"); return -1; } return 0; default: REDEBUG("Unknown/unsupported algorithm %i", version); return -1; } return 0; }
/* * Do the statistics */ static rlm_rcode_t CC_HINT(nonnull) mod_stats(void *instance, void *thread, REQUEST *request) { int i; uint32_t stats_type; rlm_stats_thread_t *t = thread; rlm_stats_t *inst = instance; VALUE_PAIR *vp; rlm_stats_data_t mydata, *stats; fr_cursor_t cursor; char buffer[64]; uint64_t local_stats[sizeof(inst->stats) / sizeof(inst->stats[0])]; /* * Increment counters only in "send foo" sections. * * i.e. only when we have a reply to send. */ if (request->request_state == REQUEST_SEND) { int src_code, dst_code; src_code = request->packet->code; if (src_code >= FR_MAX_PACKET_CODE) src_code = 0; dst_code = request->reply->code; if (dst_code >= FR_MAX_PACKET_CODE) dst_code = 0; t->stats[src_code]++; t->stats[dst_code]++; /* * Update source statistics */ mydata.ipaddr = request->packet->src_ipaddr; stats = rbtree_finddata(t->src, &mydata); if (!stats) { MEM(stats = talloc_zero(t, rlm_stats_data_t)); stats->ipaddr = request->packet->src_ipaddr; stats->created = request->async->recv_time; (void) rbtree_insert(t->src, stats); } stats->last_packet = request->async->recv_time; stats->stats[src_code]++; stats->stats[dst_code]++; /* * Update destination statistics */ mydata.ipaddr = request->packet->dst_ipaddr; stats = rbtree_finddata(t->dst, &mydata); if (!stats) { MEM(stats = talloc_zero(t, rlm_stats_data_t)); stats->ipaddr = request->packet->dst_ipaddr; stats->created = request->async->recv_time; (void) rbtree_insert(t->dst, stats); } stats->last_packet = request->async->recv_time; stats->stats[src_code]++; stats->stats[dst_code]++; /* * @todo - periodically clean up old entries. */ if ((t->last_global_update + NANOSEC) > request->async->recv_time) { return RLM_MODULE_UPDATED; } t->last_global_update = request->async->recv_time; pthread_mutex_lock(&inst->mutex); for (i = 0; i < FR_MAX_PACKET_CODE; i++) { inst->stats[i] += t->stats[i]; t->stats[i] = 0; } pthread_mutex_unlock(&inst->mutex); return RLM_MODULE_UPDATED; } /* * Ignore "authenticate" and anything other than Status-Server */ if ((request->request_state != REQUEST_RECV) || (request->packet->code != FR_CODE_STATUS_SERVER)) { return RLM_MODULE_NOOP; } vp = fr_pair_find_by_da(request->packet->vps, attr_freeradius_stats4_type, TAG_ANY); if (!vp) { stats_type = FR_FREERADIUS_STATS4_TYPE_VALUE_GLOBAL; } else { stats_type = vp->vp_uint32; } /* * Create attributes based on the statistics. */ fr_cursor_init(&cursor, &request->reply->vps); MEM(pair_update_reply(&vp, attr_freeradius_stats4_type) >= 0); vp->vp_uint32 = stats_type; switch (stats_type) { case FR_FREERADIUS_STATS4_TYPE_VALUE_GLOBAL: /* global */ /* * Merge our stats with the global stats, and then copy * the global stats to a thread-local variable. * * The copy helps minimize mutex contention. */ pthread_mutex_lock(&inst->mutex); for (i = 0; i < FR_MAX_PACKET_CODE; i++) { inst->stats[i] += t->stats[i]; t->stats[i] = 0; } memcpy(&local_stats, inst->stats, sizeof(inst->stats)); pthread_mutex_unlock(&inst->mutex); vp = NULL; break; case FR_FREERADIUS_STATS4_TYPE_VALUE_CLIENT: /* src */ vp = fr_pair_find_by_da(request->packet->vps, attr_freeradius_stats4_ipv4_address, TAG_ANY); if (!vp) vp = fr_pair_find_by_da(request->packet->vps, attr_freeradius_stats4_ipv6_address, TAG_ANY); if (!vp) return RLM_MODULE_NOOP; mydata.ipaddr = vp->vp_ip; coalesce(local_stats, t, offsetof(rlm_stats_thread_t, src), &mydata); break; case FR_FREERADIUS_STATS4_TYPE_VALUE_LISTENER: /* dst */ vp = fr_pair_find_by_da(request->packet->vps, attr_freeradius_stats4_ipv4_address, TAG_ANY); if (!vp) vp = fr_pair_find_by_da(request->packet->vps, attr_freeradius_stats4_ipv6_address, TAG_ANY); if (!vp) return RLM_MODULE_NOOP; mydata.ipaddr = vp->vp_ip; coalesce(local_stats, t, offsetof(rlm_stats_thread_t, dst), &mydata); break; default: REDEBUG("Invalid value '%d' for FreeRADIUS-Stats4-type", stats_type); return RLM_MODULE_FAIL; } if (vp ) { vp = fr_pair_copy(request->reply, vp); if (vp) { fr_cursor_append(&cursor, vp); (void) fr_cursor_tail(&cursor); } } strcpy(buffer, "FreeRADIUS-Stats4-"); for (i = 0; i < FR_MAX_PACKET_CODE; i++) { fr_dict_attr_t const *da; if (!local_stats[i]) continue; strlcpy(buffer + 18, fr_packet_codes[i], sizeof(buffer) - 18); da = fr_dict_attr_by_name(dict_radius, buffer); if (!da) continue; vp = fr_pair_afrom_da(request->reply, da); if (!vp) return RLM_MODULE_FAIL; vp->vp_uint64 = local_stats[i]; fr_cursor_append(&cursor, vp); (void) fr_cursor_tail(&cursor); } return RLM_MODULE_OK; }
/* * Call the function_name inside the module * Store all vps in hashes %RAD_CONFIG %RAD_REPLY %RAD_REQUEST * */ static int do_perl(void *instance, REQUEST *request, char const *function_name) { rlm_perl_t *inst = instance; VALUE_PAIR *vp; int exitstatus=0, count; STRLEN n_a; HV *rad_reply_hv; HV *rad_config_hv; HV *rad_request_hv; HV *rad_state_hv; #ifdef WITH_PROXY HV *rad_request_proxy_hv; HV *rad_request_proxy_reply_hv; #endif /* * Radius has told us to call this function, but none * is defined. */ if (!function_name) return RLM_MODULE_FAIL; #ifdef USE_ITHREADS pthread_mutex_lock(&inst->clone_mutex); PerlInterpreter *interp; interp = rlm_perl_clone(inst->perl,inst->thread_key); { dTHXa(interp); PERL_SET_CONTEXT(interp); } pthread_mutex_unlock(&inst->clone_mutex); #else PERL_SET_CONTEXT(inst->perl); #endif { dSP; ENTER; SAVETMPS; rad_reply_hv = get_hv("RAD_REPLY", 1); rad_config_hv = get_hv("RAD_CONFIG", 1); rad_request_hv = get_hv("RAD_REQUEST", 1); rad_state_hv = get_hv("RAD_STATE", 1); perl_store_vps(request->packet, request, &request->packet->vps, rad_request_hv, "RAD_REQUEST", "request"); perl_store_vps(request->reply, request, &request->reply->vps, rad_reply_hv, "RAD_REPLY", "reply"); perl_store_vps(request, request, &request->control, rad_config_hv, "RAD_CONFIG", "control"); perl_store_vps(request->state_ctx, request, &request->state, rad_state_hv, "RAD_STATE", "session-state"); #ifdef WITH_PROXY rad_request_proxy_hv = get_hv("RAD_REQUEST_PROXY",1); rad_request_proxy_reply_hv = get_hv("RAD_REQUEST_PROXY_REPLY",1); if (request->proxy) { perl_store_vps(request->proxy->packet, request, &request->proxy->packet->vps, rad_request_proxy_hv, "RAD_REQUEST_PROXY", "proxy-request"); } else { hv_undef(rad_request_proxy_hv); } if (request->proxy && request->proxy->reply != NULL) { perl_store_vps(request->proxy->reply, request, &request->proxy->reply->vps, rad_request_proxy_reply_hv, "RAD_REQUEST_PROXY_REPLY", "proxy-reply"); } else { hv_undef(rad_request_proxy_reply_hv); } #endif /* * Store pointer to request structure globally so radiusd::xlat works */ rlm_perl_request = request; PUSHMARK(SP); /* * This way %RAD_xx can be pushed onto stack as sub parameters. * XPUSHs( newRV_noinc((SV *)rad_request_hv) ); * XPUSHs( newRV_noinc((SV *)rad_reply_hv) ); * XPUSHs( newRV_noinc((SV *)rad_config_hv) ); * PUTBACK; */ count = call_pv(function_name, G_SCALAR | G_EVAL | G_NOARGS); SPAGAIN; if (SvTRUE(ERRSV)) { REDEBUG("perl_embed:: module = %s , func = %s exit status= %s\n", inst->module, function_name, SvPV(ERRSV,n_a)); (void)POPs; exitstatus = RLM_MODULE_FAIL; } else if (count == 1) { exitstatus = POPi; if (exitstatus >= 100 || exitstatus < 0) { exitstatus = RLM_MODULE_FAIL; } } PUTBACK; FREETMPS; LEAVE; vp = NULL; if ((get_hv_content(request->packet, request, rad_request_hv, &vp, "RAD_REQUEST", "request")) == 0) { fr_pair_list_free(&request->packet->vps); request->packet->vps = vp; vp = NULL; /* * Update cached copies */ request->username = fr_pair_find_by_da(request->packet->vps, attr_user_name, TAG_ANY); request->password = fr_pair_find_by_da(request->packet->vps, attr_user_password, TAG_ANY); if (!request->password) request->password = fr_pair_find_by_da(request->packet->vps, attr_chap_password, TAG_ANY); } if ((get_hv_content(request->reply, request, rad_reply_hv, &vp, "RAD_REPLY", "reply")) == 0) { fr_pair_list_free(&request->reply->vps); request->reply->vps = vp; vp = NULL; } if ((get_hv_content(request, request, rad_config_hv, &vp, "RAD_CONFIG", "control")) == 0) { fr_pair_list_free(&request->control); request->control = vp; vp = NULL; } if ((get_hv_content(request->state_ctx, request, rad_state_hv, &vp, "RAD_STATE", "session-state")) == 0) { fr_pair_list_free(&request->state); request->state = vp; vp = NULL; } #ifdef WITH_PROXY if (request->proxy && (get_hv_content(request->proxy->packet, request, rad_request_proxy_hv, &vp, "RAD_REQUEST_PROXY", "proxy-request") == 0)) { fr_pair_list_free(&request->proxy->packet->vps); request->proxy->packet->vps = vp; vp = NULL; } if (request->proxy && request->proxy->reply && (get_hv_content(request->proxy->reply, request, rad_request_proxy_reply_hv, &vp, "RAD_REQUEST_PROXY_REPLY", "proxy-reply") == 0)) { fr_pair_list_free(&request->proxy->reply->vps); request->proxy->reply->vps = vp; vp = NULL; } #endif } return exitstatus; }
/** Allocate a new IP address from a pool * */ static ippool_rcode_t redis_ippool_allocate(rlm_redis_ippool_t const *inst, REQUEST *request, uint8_t const *key_prefix, size_t key_prefix_len, uint8_t const *device_id, size_t device_id_len, uint8_t const *gateway_id, size_t gateway_id_len, uint32_t expires) { struct timeval now; redisReply *reply = NULL; fr_redis_rcode_t status; ippool_rcode_t ret = IPPOOL_RCODE_SUCCESS; rad_assert(key_prefix); rad_assert(device_id); gettimeofday(&now, NULL); /* * hiredis doesn't deal well with NULL string pointers */ if (!gateway_id) gateway_id = (uint8_t const *)""; status = ippool_script(&reply, request, inst->cluster, key_prefix, key_prefix_len, inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout), lua_alloc_digest, lua_alloc_cmd, "EVALSHA %s 1 %b %u %u %b %b", lua_alloc_digest, key_prefix, key_prefix_len, (unsigned int)now.tv_sec, expires, device_id, device_id_len, gateway_id, gateway_id_len); if (status != REDIS_RCODE_SUCCESS) { ret = IPPOOL_RCODE_FAIL; goto finish; } rad_assert(reply); if (reply->type != REDIS_REPLY_ARRAY) { REDEBUG("Expected result to be array got \"%s\"", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } if (reply->elements == 0) { REDEBUG("Got empty result array"); ret = IPPOOL_RCODE_FAIL; goto finish; } /* * Process return code */ if (reply->element[0]->type != REDIS_REPLY_INTEGER) { REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } ret = reply->element[0]->integer; if (ret < 0) goto finish; /* * Process IP address */ if (reply->elements > 1) { vp_tmpl_t ip_rhs = { .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING }; vp_map_t ip_map = { .lhs = inst->allocated_address_attr, .op = T_OP_SET, .rhs = &ip_rhs }; switch (reply->element[1]->type) { /* * Destination attribute may not be IPv4, in which case * we want to pre-convert the integer value to an IPv4 * address before casting it once more to the type of * the destination attribute. */ case REDIS_REPLY_INTEGER: { if (ip_map.lhs->tmpl_da->type != FR_TYPE_IPV4_ADDR) { fr_value_box_t tmp; memset(&tmp, 0, sizeof(tmp)); tmp.vb_uint32 = ntohl((uint32_t)reply->element[1]->integer); tmp.type = FR_TYPE_UINT32; if (fr_value_box_cast(NULL, &ip_map.rhs->tmpl_value, FR_TYPE_IPV4_ADDR, NULL, &tmp)) { RPEDEBUG("Failed converting integer to IPv4 address"); ret = IPPOOL_RCODE_FAIL; goto finish; } } else { ip_map.rhs->tmpl_value.vb_uint32 = ntohl((uint32_t)reply->element[1]->integer); ip_map.rhs->tmpl_value_type = FR_TYPE_UINT32; } } goto do_ip_map; case REDIS_REPLY_STRING: ip_map.rhs->tmpl_value.vb_strvalue = reply->element[1]->str; ip_map.rhs->tmpl_value_length = reply->element[1]->len; ip_map.rhs->tmpl_value_type = FR_TYPE_STRING; do_ip_map: if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) { ret = IPPOOL_RCODE_FAIL; goto finish; } break; default: REDEBUG("Server returned unexpected type \"%s\" for IP element (result[1])", fr_int2str(redis_reply_types, reply->element[1]->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } } /* * Process Range identifier */ if (reply->elements > 2) { switch (reply->element[2]->type) { /* * Add range ID to request */ case REDIS_REPLY_STRING: { vp_tmpl_t range_rhs = { .name = "", .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING, .quote = T_DOUBLE_QUOTED_STRING }; vp_map_t range_map = { .lhs = inst->range_attr, .op = T_OP_SET, .rhs = &range_rhs }; range_map.rhs->tmpl_value.vb_strvalue = reply->element[2]->str; range_map.rhs->tmpl_value_length = reply->element[2]->len; range_map.rhs->tmpl_value_type = FR_TYPE_STRING; if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) { ret = IPPOOL_RCODE_FAIL; goto finish; } } break; case REDIS_REPLY_NIL: break; default: REDEBUG("Server returned unexpected type \"%s\" for range element (result[2])", fr_int2str(redis_reply_types, reply->element[2]->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } } /* * Process Expiry time */ if (inst->expiry_attr && (reply->elements > 3)) { vp_tmpl_t expiry_rhs = { .name = "", .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING, .quote = T_DOUBLE_QUOTED_STRING }; vp_map_t expiry_map = { .lhs = inst->expiry_attr, .op = T_OP_SET, .rhs = &expiry_rhs }; if (reply->element[3]->type != REDIS_REPLY_INTEGER) { REDEBUG("Server returned unexpected type \"%s\" for expiry element (result[3])", fr_int2str(redis_reply_types, reply->element[3]->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } expiry_map.rhs->tmpl_value.vb_uint32 = reply->element[3]->integer; expiry_map.rhs->tmpl_value_type = FR_TYPE_UINT32; if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) { ret = IPPOOL_RCODE_FAIL; goto finish; } } finish: fr_redis_reply_free(&reply); return ret; } /** Update an existing IP address in a pool * */ static ippool_rcode_t redis_ippool_update(rlm_redis_ippool_t const *inst, REQUEST *request, uint8_t const *key_prefix, size_t key_prefix_len, fr_ipaddr_t *ip, uint8_t const *device_id, size_t device_id_len, uint8_t const *gateway_id, size_t gateway_id_len, uint32_t expires) { struct timeval now; redisReply *reply = NULL; fr_redis_rcode_t status; ippool_rcode_t ret = IPPOOL_RCODE_SUCCESS; vp_tmpl_t range_rhs = { .name = "", .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING, .quote = T_DOUBLE_QUOTED_STRING }; vp_map_t range_map = { .lhs = inst->range_attr, .op = T_OP_SET, .rhs = &range_rhs }; gettimeofday(&now, NULL); /* * hiredis doesn't deal well with NULL string pointers */ if (!device_id) device_id = (uint8_t const *)""; if (!gateway_id) gateway_id = (uint8_t const *)""; if ((ip->af == AF_INET) && inst->ipv4_integer) { status = ippool_script(&reply, request, inst->cluster, key_prefix, key_prefix_len, inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout), lua_update_digest, lua_update_cmd, "EVALSHA %s 1 %b %u %u %u %b %b", lua_update_digest, key_prefix, key_prefix_len, (unsigned int)now.tv_sec, expires, htonl(ip->addr.v4.s_addr), device_id, device_id_len, gateway_id, gateway_id_len); } else { char ip_buff[FR_IPADDR_PREFIX_STRLEN]; IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix); status = ippool_script(&reply, request, inst->cluster, key_prefix, key_prefix_len, inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout), lua_update_digest, lua_update_cmd, "EVALSHA %s 1 %b %u %u %s %b %b", lua_update_digest, key_prefix, key_prefix_len, (unsigned int)now.tv_sec, expires, ip_buff, device_id, device_id_len, gateway_id, gateway_id_len); } if (status != REDIS_RCODE_SUCCESS) { ret = IPPOOL_RCODE_FAIL; goto finish; } if (reply->type != REDIS_REPLY_ARRAY) { REDEBUG("Expected result to be array got \"%s\"", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } if (reply->elements == 0) { REDEBUG("Got empty result array"); ret = IPPOOL_RCODE_FAIL; goto finish; } /* * Process return code */ if (reply->element[0]->type != REDIS_REPLY_INTEGER) { REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } ret = reply->element[0]->integer; if (ret < 0) goto finish; /* * Process Range identifier */ if (reply->elements > 1) { switch (reply->element[1]->type) { /* * Add range ID to request */ case REDIS_REPLY_STRING: range_map.rhs->tmpl_value.vb_strvalue = reply->element[1]->str; range_map.rhs->tmpl_value_length = reply->element[1]->len; range_map.rhs->tmpl_value_type = FR_TYPE_STRING; if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) { ret = IPPOOL_RCODE_FAIL; goto finish; } break; case REDIS_REPLY_NIL: break; default: REDEBUG("Server returned unexpected type \"%s\" for range element (result[1])", fr_int2str(redis_reply_types, reply->element[0]->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } } /* * Copy expiry time to expires attribute (if set) */ if (inst->expiry_attr) { vp_tmpl_t expiry_rhs = { .name = "", .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING, .quote = T_DOUBLE_QUOTED_STRING }; vp_map_t expiry_map = { .lhs = inst->expiry_attr, .op = T_OP_SET, .rhs = &expiry_rhs }; expiry_map.rhs->tmpl_value.vb_uint32 = expires; expiry_map.rhs->tmpl_value_type = FR_TYPE_UINT32; if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) { ret = IPPOOL_RCODE_FAIL; goto finish; } } finish: fr_redis_reply_free(&reply); return ret; } /** Release an existing IP address in a pool * */ static ippool_rcode_t redis_ippool_release(rlm_redis_ippool_t const *inst, REQUEST *request, uint8_t const *key_prefix, size_t key_prefix_len, fr_ipaddr_t *ip, uint8_t const *device_id, size_t device_id_len) { struct timeval now; redisReply *reply = NULL; fr_redis_rcode_t status; ippool_rcode_t ret = IPPOOL_RCODE_SUCCESS; gettimeofday(&now, NULL); /* * hiredis doesn't deal well with NULL string pointers */ if (!device_id) device_id = (uint8_t const *)""; if ((ip->af == AF_INET) && inst->ipv4_integer) { status = ippool_script(&reply, request, inst->cluster, key_prefix, key_prefix_len, inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout), lua_release_digest, lua_release_cmd, "EVALSHA %s 1 %b %u %u %b", lua_release_digest, key_prefix, key_prefix_len, (unsigned int)now.tv_sec, htonl(ip->addr.v4.s_addr), device_id, device_id_len); } else { char ip_buff[FR_IPADDR_PREFIX_STRLEN]; IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix); status = ippool_script(&reply, request, inst->cluster, key_prefix, key_prefix_len, inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout), lua_release_digest, lua_release_cmd, "EVALSHA %s 1 %b %u %s %b", lua_release_digest, key_prefix, key_prefix_len, (unsigned int)now.tv_sec, ip_buff, device_id, device_id_len); } if (status != REDIS_RCODE_SUCCESS) { ret = IPPOOL_RCODE_FAIL; goto finish; } if (reply->type != REDIS_REPLY_ARRAY) { REDEBUG("Expected result to be array got \"%s\"", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } if (reply->elements == 0) { REDEBUG("Got empty result array"); ret = IPPOOL_RCODE_FAIL; goto finish; } /* * Process return code */ if (reply->element[0]->type != REDIS_REPLY_INTEGER) { REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } ret = reply->element[0]->integer; if (ret < 0) goto finish; finish: fr_redis_reply_free(&reply); return ret; } /** Find the pool name we'll be allocating from * * @param[out] out Where to write the pool name. * @param[out] buff Where to write the pool name (in the case of an expansion). * @param[in] bufflen Size of the output buffer. * @param[in] inst This instance of the rlm_redis_ippool module. * @param[in] request The current request. * @return * - < 0 on error. * - 0 if no pool attribute exists, or the pool name is a zero length string. * - > 0 on success (length of data written to out). */ static inline ssize_t ippool_pool_name(uint8_t const **out, uint8_t buff[], size_t bufflen, rlm_redis_ippool_t const *inst, REQUEST *request) { ssize_t slen; slen = tmpl_expand(out, (char *)buff, bufflen, request, inst->pool_name, NULL, NULL); if (slen < 0) { if (inst->pool_name->type == TMPL_TYPE_ATTR) { RDEBUG2("Pool attribute not present in request. Doing nothing"); return 0; } REDEBUG("Failed expanding pool name"); return -1; } if (slen == 0) { RDEBUG2("Empty pool name. Doing nothing"); return 0; } if ((*out == buff) && is_truncated((size_t)slen, bufflen)) { REDEBUG("Pool name too long. Expected %zu bytes, got %zu bytes", bufflen, (size_t)slen); return -1; } return slen; } static rlm_rcode_t mod_action(rlm_redis_ippool_t const *inst, REQUEST *request, ippool_action_t action) { uint8_t key_prefix_buff[IPPOOL_MAX_KEY_PREFIX_SIZE], device_id_buff[256], gateway_id_buff[256]; uint8_t const *key_prefix, *device_id = NULL, *gateway_id = NULL; size_t key_prefix_len, device_id_len = 0, gateway_id_len = 0; ssize_t slen; fr_ipaddr_t ip; char expires_buff[20]; char const *expires_str; unsigned long expires = 0; char *q; slen = ippool_pool_name(&key_prefix, (uint8_t *)&key_prefix_buff, sizeof(key_prefix_len), inst, request); if (slen < 0) return RLM_MODULE_FAIL; if (slen == 0) return RLM_MODULE_NOOP; key_prefix_len = (size_t)slen; if (inst->device_id) { slen = tmpl_expand((char const **)&device_id, (char *)&device_id_buff, sizeof(device_id_buff), request, inst->device_id, NULL, NULL); if (slen < 0) { REDEBUG("Failed expanding device (%s)", inst->device_id->name); return RLM_MODULE_FAIL; } device_id_len = (size_t)slen; } if (inst->gateway_id) { slen = tmpl_expand((char const **)&gateway_id, (char *)&gateway_id_buff, sizeof(gateway_id_buff), request, inst->gateway_id, NULL, NULL); if (slen < 0) { REDEBUG("Failed expanding gateway (%s)", inst->gateway_id->name); return RLM_MODULE_FAIL; } gateway_id_len = (size_t)slen; } switch (action) { case POOL_ACTION_ALLOCATE: if (tmpl_expand(&expires_str, expires_buff, sizeof(expires_buff), request, inst->offer_time, NULL, NULL) < 0) { REDEBUG("Failed expanding offer_time (%s)", inst->offer_time->name); return RLM_MODULE_FAIL; } expires = strtoul(expires_str, &q, 10); if (q != (expires_str + strlen(expires_str))) { REDEBUG("Invalid offer_time. Must be an integer value"); return RLM_MODULE_FAIL; } ippool_action_print(request, action, L_DBG_LVL_2, key_prefix, key_prefix_len, NULL, device_id, device_id_len, gateway_id, gateway_id_len, expires); switch (redis_ippool_allocate(inst, request, key_prefix, key_prefix_len, device_id, device_id_len, gateway_id, gateway_id_len, (uint32_t)expires)) { case IPPOOL_RCODE_SUCCESS: RDEBUG2("IP address lease allocated"); return RLM_MODULE_UPDATED; case IPPOOL_RCODE_POOL_EMPTY: RWDEBUG("Pool contains no free addresses"); return RLM_MODULE_NOTFOUND; default: return RLM_MODULE_FAIL; } case POOL_ACTION_UPDATE: { char ip_buff[INET6_ADDRSTRLEN + 4]; char const *ip_str; if (tmpl_expand(&expires_str, expires_buff, sizeof(expires_buff), request, inst->lease_time, NULL, NULL) < 0) { REDEBUG("Failed expanding lease_time (%s)", inst->lease_time->name); return RLM_MODULE_FAIL; } expires = strtoul(expires_str, &q, 10); if (q != (expires_str + strlen(expires_str))) { REDEBUG("Invalid expires. Must be an integer value"); return RLM_MODULE_FAIL; } if (tmpl_expand(&ip_str, ip_buff, sizeof(ip_buff), request, inst->requested_address, NULL, NULL) < 0) { REDEBUG("Failed expanding requested_address (%s)", inst->requested_address->name); return RLM_MODULE_FAIL; } if (fr_inet_pton(&ip, ip_str, -1, AF_UNSPEC, false, true) < 0) { RPEDEBUG("Failed parsing address"); return RLM_MODULE_FAIL; } ippool_action_print(request, action, L_DBG_LVL_2, key_prefix, key_prefix_len, ip_str, device_id, device_id_len, gateway_id, gateway_id_len, expires); switch (redis_ippool_update(inst, request, key_prefix, key_prefix_len, &ip, device_id, device_id_len, gateway_id, gateway_id_len, (uint32_t)expires)) { case IPPOOL_RCODE_SUCCESS: RDEBUG2("Requested IP address' \"%s\" lease updated", ip_str); /* * Copy over the input IP address to the reply attribute */ if (inst->copy_on_update) { vp_tmpl_t ip_rhs = { .name = "", .type = TMPL_TYPE_DATA, .quote = T_BARE_WORD, }; vp_map_t ip_map = { .lhs = inst->allocated_address_attr, .op = T_OP_SET, .rhs = &ip_rhs }; ip_rhs.tmpl_value_length = strlen(ip_str); ip_rhs.tmpl_value.vb_strvalue = ip_str; ip_rhs.tmpl_value_type = FR_TYPE_STRING; if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) return RLM_MODULE_FAIL; } return RLM_MODULE_UPDATED; /* * It's useful to be able to identify the 'not found' case * as we can relay to a server where the IP address might * be found. This extremely useful for migrations. */ case IPPOOL_RCODE_NOT_FOUND: REDEBUG("Requested IP address \"%s\" is not a member of the specified pool", ip_str); return RLM_MODULE_NOTFOUND; case IPPOOL_RCODE_EXPIRED: REDEBUG("Requested IP address' \"%s\" lease already expired at time of renewal", ip_str); return RLM_MODULE_INVALID; case IPPOOL_RCODE_DEVICE_MISMATCH: REDEBUG("Requested IP address' \"%s\" lease allocated to another device", ip_str); return RLM_MODULE_INVALID; default: return RLM_MODULE_FAIL; } } case POOL_ACTION_RELEASE: { char ip_buff[INET6_ADDRSTRLEN + 4]; char const *ip_str; if (tmpl_expand(&ip_str, ip_buff, sizeof(ip_buff), request, inst->requested_address, NULL, NULL) < 0) { REDEBUG("Failed expanding requested_address (%s)", inst->requested_address->name); return RLM_MODULE_FAIL; } if (fr_inet_pton(&ip, ip_str, -1, AF_UNSPEC, false, true) < 0) { RPEDEBUG("Failed parsing address"); return RLM_MODULE_FAIL; } ippool_action_print(request, action, L_DBG_LVL_2, key_prefix, key_prefix_len, ip_str, device_id, device_id_len, gateway_id, gateway_id_len, 0); switch (redis_ippool_release(inst, request, key_prefix, key_prefix_len, &ip, device_id, device_id_len)) { case IPPOOL_RCODE_SUCCESS: RDEBUG2("IP address \"%s\" released", ip_str); return RLM_MODULE_UPDATED; /* * It's useful to be able to identify the 'not found' case * as we can relay to a server where the IP address might * be found. This extremely useful for migrations. */ case IPPOOL_RCODE_NOT_FOUND: REDEBUG("Requested IP address \"%s\" is not a member of the specified pool", ip_str); return RLM_MODULE_NOTFOUND; case IPPOOL_RCODE_DEVICE_MISMATCH: REDEBUG("Requested IP address' \"%s\" lease allocated to another device", ip_str); return RLM_MODULE_INVALID; default: return RLM_MODULE_FAIL; } } case POOL_ACTION_BULK_RELEASE: RDEBUG2("Bulk release not yet implemented"); return RLM_MODULE_NOOP; default: rad_assert(0); return RLM_MODULE_FAIL; } } static rlm_rcode_t mod_accounting(void *instance, UNUSED void *thread, REQUEST *request) CC_HINT(nonnull); static rlm_rcode_t mod_accounting(void *instance, UNUSED void *thread, REQUEST *request) { rlm_redis_ippool_t const *inst = instance; VALUE_PAIR *vp; /* * Pool-Action override */ vp = fr_pair_find_by_da(request->control, attr_pool_action, TAG_ANY); if (vp) return mod_action(inst, request, vp->vp_uint32); /* * Otherwise, guess the action by Acct-Status-Type */ vp = fr_pair_find_by_da(request->packet->vps, attr_acct_status_type, TAG_ANY); if (!vp) { RDEBUG2("Couldn't find &request:Acct-Status-Type or &control:Pool-Action, doing nothing..."); return RLM_MODULE_NOOP; } switch (vp->vp_uint32) { case FR_STATUS_START: case FR_STATUS_ALIVE: return mod_action(inst, request, POOL_ACTION_UPDATE); case FR_STATUS_STOP: return mod_action(inst, request, POOL_ACTION_RELEASE); case FR_STATUS_ACCOUNTING_OFF: case FR_STATUS_ACCOUNTING_ON: return mod_action(inst, request, POOL_ACTION_BULK_RELEASE); default: return RLM_MODULE_NOOP; } } static rlm_rcode_t mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) CC_HINT(nonnull); static rlm_rcode_t mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) { rlm_redis_ippool_t const *inst = instance; VALUE_PAIR *vp; /* * Unless it's overridden the default action is to allocate * when called in Post-Auth. */ vp = fr_pair_find_by_da(request->control, attr_pool_action, TAG_ANY); return mod_action(inst, request, vp ? vp->vp_uint32 : POOL_ACTION_ALLOCATE); } static rlm_rcode_t mod_post_auth(void *instance, UNUSED void *thread, REQUEST *request) CC_HINT(nonnull); static rlm_rcode_t mod_post_auth(void *instance, UNUSED void *thread, REQUEST *request) { rlm_redis_ippool_t const *inst = instance; VALUE_PAIR *vp; ippool_action_t action = POOL_ACTION_ALLOCATE; /* * Unless it's overridden the default action is to allocate * when called in Post-Auth. */ vp = fr_pair_find_by_da(request->control, attr_pool_action, TAG_ANY); if (vp) { if ((vp->vp_uint32 > 0) && (vp->vp_uint32 <= POOL_ACTION_BULK_RELEASE)) { action = vp->vp_uint32; } else { RWDEBUG("Ignoring invalid action %d", vp->vp_uint32); return RLM_MODULE_NOOP; } #ifdef WITH_DHCP } else if (request->dict == dict_dhcpv4) { vp = fr_pair_find_by_da(request->control, attr_message_type, TAG_ANY); if (!vp) goto run; if (vp->vp_uint8 == FR_DHCP_REQUEST) action = POOL_ACTION_UPDATE; #endif } run: return mod_action(inst, request, action); } static int mod_instantiate(void *instance, CONF_SECTION *conf) { static bool done_hash = false; CONF_SECTION *subcs = cf_section_find(conf, "redis", NULL); rlm_redis_ippool_t *inst = instance; rad_assert(inst->allocated_address_attr->type == TMPL_TYPE_ATTR); rad_assert(subcs); inst->cluster = fr_redis_cluster_alloc(inst, subcs, &inst->conf, true, NULL, NULL, NULL); if (!inst->cluster) return -1; if (!fr_redis_cluster_min_version(inst->cluster, "3.0.2")) { PERROR("Cluster error"); return -1; } /* * Pre-Compute the SHA1 hashes of the Lua scripts */ if (!done_hash) { fr_sha1_ctx sha1_ctx; uint8_t digest[SHA1_DIGEST_LENGTH]; fr_sha1_init(&sha1_ctx); fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_alloc_cmd, sizeof(lua_alloc_cmd) - 1); fr_sha1_final(digest, &sha1_ctx); fr_bin2hex(lua_alloc_digest, digest, sizeof(digest)); fr_sha1_init(&sha1_ctx); fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_update_cmd, sizeof(lua_update_cmd) - 1); fr_sha1_final(digest, &sha1_ctx); fr_bin2hex(lua_update_digest, digest, sizeof(digest)); fr_sha1_init(&sha1_ctx); fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_release_cmd, sizeof(lua_release_cmd) - 1); fr_sha1_final(digest, &sha1_ctx); fr_bin2hex(lua_release_digest, digest, sizeof(digest)); } /* * If we don't have a separate time specifically for offers * just use the lease time. */ if (!inst->offer_time) inst->offer_time = inst->lease_time; return 0; } static int mod_load(void) { fr_redis_version_print(); return 0; } extern module_t rlm_redis_ippool; module_t rlm_redis_ippool = { .magic = RLM_MODULE_INIT, .name = "redis", .type = RLM_TYPE_THREAD_SAFE, .inst_size = sizeof(rlm_redis_ippool_t), .config = module_config, .onload = mod_load, .instantiate = mod_instantiate, .methods = { [MOD_ACCOUNTING] = mod_accounting, [MOD_AUTHORIZE] = mod_authorize, [MOD_POST_AUTH] = mod_post_auth, }, };