/* * Parse a configuration section, and populate a HV. * This function is recursively called (allows to have nested hashes.) */ static void perl_parse_config(CONF_SECTION *cs, int lvl, HV *rad_hv) { if (!cs || !rad_hv) return; int indent_section = (lvl + 1) * 4; int indent_item = (lvl + 2) * 4; DEBUG("%*s%s {", indent_section, " ", cf_section_name1(cs)); CONF_ITEM *ci = NULL; while ((ci = cf_item_next(cs, ci))) { /* * This is a section. * Create a new HV, store it as a reference in current HV, * Then recursively call perl_parse_config with this section and the new HV. */ if (cf_item_is_section(ci)) { CONF_SECTION *sub_cs = cf_item_to_section(ci); char const *key = cf_section_name1(sub_cs); /* hash key */ HV *sub_hv; SV *ref; if (!key) continue; if (hv_exists(rad_hv, key, strlen(key))) { WARN("Ignoring duplicate config section '%s'", key); continue; } sub_hv = newHV(); ref = newRV_inc((SV*) sub_hv); (void)hv_store(rad_hv, key, strlen(key), ref, 0); perl_parse_config(sub_cs, lvl + 1, sub_hv); } else if (cf_item_is_pair(ci)){ CONF_PAIR *cp = cf_item_to_pair(ci); char const *key = cf_pair_attr(cp); /* hash key */ char const *value = cf_pair_value(cp); /* hash value */ if (!key || !value) continue; /* * This is an item. * Store item attr / value in current HV. */ if (hv_exists(rad_hv, key, strlen(key))) { WARN("Ignoring duplicate config item '%s'", key); continue; } (void)hv_store(rad_hv, key, strlen(key), newSVpvn(value, strlen(value)), 0); DEBUG("%*s%s = %s", indent_item, " ", key, value); } } DEBUG("%*s}", indent_section, " "); }
static int mod_instantiate(void *instance, CONF_SECTION *listen_cs) { int rcode; CONF_SECTION *server_cs; proto_detail_process_t *inst = talloc_get_type_abort(instance, proto_detail_process_t); vp_tmpl_rules_t parse_rules; memset(&parse_rules, 0, sizeof(parse_rules)); parse_rules.dict_def = dict_freeradius; rad_assert(listen_cs); server_cs = cf_item_to_section(cf_parent(listen_cs)); rad_assert(strcmp(cf_section_name1(server_cs), "server") == 0); rcode = unlang_compile_subsection(server_cs, "recv", NULL, inst->recv_type, &parse_rules); if (rcode < 0) return rcode; if (rcode == 0) { cf_log_err(server_cs, "Failed finding 'recv { ... }' section of virtual server %s", cf_section_name2(server_cs)); return -1; } rcode = unlang_compile_subsection(server_cs, "send", "ok", inst->send_type, &parse_rules); if (rcode < 0) return rcode; rcode = unlang_compile_subsection(server_cs, "send", "fail", inst->send_type, &parse_rules); if (rcode < 0) return rcode; return 0; }
/* * Ensure that the unlang sections are compiled. */ static int mod_instantiate(UNUSED void *instance, CONF_SECTION *listen_cs) { int rcode; CONF_SECTION *server_cs; vp_tmpl_rules_t parse_rules; memset(&parse_rules, 0, sizeof(parse_rules)); parse_rules.dict_def = dict_freeradius; rad_assert(listen_cs); server_cs = cf_item_to_section(cf_parent(listen_cs)); rad_assert(strcmp(cf_section_name1(server_cs), "server") == 0); rcode = unlang_compile_subsection(server_cs, "new", "client", MOD_AUTHORIZE, &parse_rules); if (rcode < 0) return rcode; if (rcode == 0) { cf_log_err(server_cs, "Failed finding 'new client { ... }' section of virtual server %s", cf_section_name2(server_cs)); return -1; } rcode = unlang_compile_subsection(server_cs, "add", "client", MOD_POST_AUTH, &parse_rules); if (rcode < 0) return rcode; rcode = unlang_compile_subsection(server_cs, "deny", "client", MOD_POST_AUTH, &parse_rules); if (rcode < 0) return rcode; return 0; }
/** Allow for Status-Server ping checks * * @param[in] ctx to allocate data in (instance of proto_radius). * @param[out] out Where to write our parsed data. * @param[in] parent Base structure address. * @param[in] ci #CONF_PAIR specifying the name of the type module. * @param[in] rule unused. * @return * - 0 on success. * - -1 on failure. */ static int status_check_type_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, UNUSED CONF_PARSER const *rule) { char const *type_str = cf_pair_value(cf_item_to_pair(ci)); CONF_SECTION *cs = cf_item_to_section(cf_parent(ci)); fr_dict_enum_t const *type_enum; uint32_t code; /* * Allow the process module to be specified by * packet type. */ type_enum = fr_dict_enum_by_alias(attr_packet_type, type_str, -1); if (!type_enum) { invalid_code: cf_log_err(ci, "Unknown or invalid RADIUS packet type '%s'", type_str); return -1; } code = type_enum->value->vb_uint32; /* * Cheat, and re-use the "type" array for allowed packet * types. */ if (!code || (code >= FR_MAX_PACKET_CODE) || (!type_interval_config[code].name)) goto invalid_code; /* * Add irt / mrt / mrd / mrc parsing, in the parent * configuration section. */ cf_section_rule_push(cf_item_to_section(cf_parent(cs)), &type_interval_config[code]); memcpy(out, &code, sizeof(code)); /* * Nothing more to do here, so we stop. */ if (code == FR_CODE_STATUS_SERVER) return 0; cf_section_rule_push(cs, status_check_update_config); return 0; }
/** Return the top level #CONF_SECTION holding all other #CONF_ITEM * * @param[in] ci to traverse up from. * @return * - NULL if ci was NULL. * - The top level #CONF_SECTION */ CONF_SECTION *_cf_root(CONF_ITEM const *ci) { CONF_ITEM const *ci_p; if (!ci) return NULL; for (ci_p = ci; ci_p->parent; ci_p = ci_p->parent); return cf_item_to_section(ci_p); }
/** Set which types of packets we can parse * * @param[in] ctx to allocate data in (instance of rlm_radius). * @param[out] out Where to write the parsed data. * @param[in] parent Base structure address. * @param[in] ci #CONF_PAIR specifying the name of the type module. * @param[in] rule unused. * @return * - 0 on success. * - -1 on failure. */ static int type_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, UNUSED CONF_PARSER const *rule) { char const *type_str = cf_pair_value(cf_item_to_pair(ci)); CONF_SECTION *cs = cf_item_to_section(cf_parent(ci)); fr_dict_enum_t const *type_enum; uint32_t code; /* * Must be the RADIUS module */ rad_assert(cs && (strcmp(cf_section_name1(cs), "radius") == 0)); /* * Allow the process module to be specified by * packet type. */ type_enum = fr_dict_enum_by_alias(attr_packet_type, type_str, -1); if (!type_enum) { invalid_code: cf_log_err(ci, "Unknown or invalid RADIUS packet type '%s'", type_str); return -1; } code = type_enum->value->vb_uint32; /* * Status-Server packets cannot be proxied. */ if (code == FR_CODE_STATUS_SERVER) { cf_log_err(ci, "Invalid setting of 'type = Status-Server'. Status-Server packets cannot be proxied."); return -1; } if (!code || (code >= FR_MAX_PACKET_CODE) || (!type_interval_config[code].name)) goto invalid_code; /* * If we're doing async proxying, push the timers for the * various packet types. */ cf_section_rule_push(cs, &type_interval_config[code]); memcpy(out, &code, sizeof(code)); return 0; }
/** Wrapper around dl_instance * * @param[in] ctx to allocate data in (instance of proto_radius). * @param[out] out Where to write a dl_instance_t containing the module handle and instance. * @param[in] parent Base structure address. * @param[in] ci #CONF_PAIR specifying the name of the type module. * @param[in] rule unused. * @return * - 0 on success. * - -1 on failure. */ static int transport_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, UNUSED CONF_PARSER const *rule) { char const *name = cf_pair_value(cf_item_to_pair(ci)); dl_instance_t *parent_inst; CONF_SECTION *cs = cf_item_to_section(cf_parent(ci)); CONF_SECTION *transport_cs; transport_cs = cf_section_find(cs, name, NULL); /* * Allocate an empty section if one doesn't exist * this is so defaults get parsed. */ if (!transport_cs) transport_cs = cf_section_alloc(cs, cs, name, NULL); parent_inst = cf_data_value(cf_data_find(cs, dl_instance_t, "rlm_radius")); rad_assert(parent_inst); return dl_instance(ctx, out, transport_cs, parent_inst, name, DL_TYPE_SUBMODULE); }
/** Resolve polymorphic item's from a module's #CONF_SECTION to a subsection in another module * * This allows certain module sections to reference module sections in other instances * of the same module and share #CONF_DATA associated with them. * * @verbatim example { data { ... } } example inst { data = example } * @endverbatim * * @param[out] out where to write the pointer to a module's config section. May be NULL on success, * indicating the config item was not found within the module #CONF_SECTION * or the chain of module references was followed and the module at the end of the chain * did not a subsection. * @param[in] module #CONF_SECTION. * @param[in] name of the polymorphic sub-section. * @return * - 0 on success with referenced section. * - 1 on success with local section. * - -1 on failure. */ int module_sibling_section_find(CONF_SECTION **out, CONF_SECTION *module, char const *name) { CONF_PAIR *cp; CONF_SECTION *cs; CONF_DATA const *cd; module_instance_t *mi; char const *inst_name; #define FIND_SIBLING_CF_KEY "find_sibling" *out = NULL; /* * Is a real section (not referencing sibling module). */ cs = cf_section_find(module, name, NULL); if (cs) { *out = cs; return 0; } /* * Item omitted completely from module config. */ cp = cf_pair_find(module, name); if (!cp) return 0; if (cf_data_find(module, CONF_SECTION, FIND_SIBLING_CF_KEY)) { cf_log_err(cp, "Module reference loop found"); return -1; } cd = cf_data_add(module, module, FIND_SIBLING_CF_KEY, false); /* * Item found, resolve it to a module instance. * This triggers module loading, so we don't have * instantiation order issues. */ inst_name = cf_pair_value(cp); mi = module_by_name(NULL, inst_name); if (!mi) { cf_log_err(cp, "Unknown module instance \"%s\"", inst_name); return -1; } if (!mi->instantiated) { CONF_SECTION *parent = module; /* * Find the root of the config... */ do { CONF_SECTION *tmp; tmp = cf_item_to_section(cf_parent(parent)); if (!tmp) break; parent = tmp; } while (true); _module_instantiate(module_by_name(NULL, inst_name), NULL); } /* * Remove the config data we added for loop * detection. */ cf_data_remove(module, cd); /* * Check the module instances are of the same type. */ if (strcmp(cf_section_name1(mi->dl_inst->conf), cf_section_name1(module)) != 0) { cf_log_err(cp, "Referenced module is a rlm_%s instance, must be a rlm_%s instance", cf_section_name1(mi->dl_inst->conf), cf_section_name1(module)); return -1; } *out = cf_section_find(mi->dl_inst->conf, name, NULL); return 1; }
/** Create a client CONF_SECTION using a mapping section to map values from a result set to client attributes * * If we hit a CONF_SECTION we recurse and process its CONF_PAIRS too. * * @note Caller should free CONF_SECTION passed in as out, on error. * Contents of that section will be in an undefined state. * * @param[in,out] out Section to perform mapping on. Either the root of the client config, or a parent section * (when this function is called recursively). * Should be alloced with cf_section_alloc, or if there's a separate template section, the * result of calling cf_section_dup on that section. * @param[in] map section. * @param[in] func to call to retrieve CONF_PAIR values. Must return a talloced buffer containing the value. * @param[in] data to pass to func, usually a result pointer. * @return 0 on success else -1 on error. */ int client_map_section(CONF_SECTION *out, CONF_SECTION const *map, client_value_cb_t func, void *data) { CONF_ITEM const *ci; for (ci = cf_item_find_next(map, NULL); ci != NULL; ci = cf_item_find_next(map, ci)) { CONF_PAIR const *cp; CONF_PAIR *old; char *value; char const *attr; /* * Recursively process map subsection */ if (cf_item_is_section(ci)) { CONF_SECTION *cs, *cc; cs = cf_item_to_section(ci); /* * Use pre-existing section or alloc a new one */ cc = cf_section_sub_find_name2(out, cf_section_name1(cs), cf_section_name2(cs)); if (!cc) { cc = cf_section_alloc(out, cf_section_name1(cs), cf_section_name2(cs)); cf_section_add(out, cc); if (!cc) return -1; } if (client_map_section(cc, cs, func, data) < 0) return -1; continue; } cp = cf_item_to_pair(ci); attr = cf_pair_attr(cp); /* * The callback can return 0 (success) and not provide a value * in which case we skip the mapping pair. * * Or return -1 in which case we error out. */ if (func(&value, cp, data) < 0) { cf_log_err_cs(out, "Failed performing mapping \"%s\" = \"%s\"", attr, cf_pair_value(cp)); return -1; } if (!value) continue; /* * Replace an existing CONF_PAIR */ old = cf_pair_find(out, attr); if (old) { cf_pair_replace(out, old, value); talloc_free(value); continue; } /* * ...or add a new CONF_PAIR */ cp = cf_pair_alloc(out, attr, value, T_OP_SET, T_BARE_WORD, T_SINGLE_QUOTED_STRING); if (!cp) { cf_log_err_cs(out, "Failed allocing pair \"%s\" = \"%s\"", attr, value); talloc_free(value); return -1; } talloc_free(value); cf_item_add(out, cf_pair_to_item(cp)); } return 0; }
/** Open a UDP listener for DHCPV4 * */ static int mod_open(fr_listen_t *li) { proto_dhcpv4_udp_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_dhcpv4_udp_t); proto_dhcpv4_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_dhcpv4_udp_thread_t); int sockfd, rcode; uint16_t port = inst->port; CONF_SECTION *server_cs; CONF_ITEM *ci; li->fd = sockfd = fr_socket_server_udp(&inst->ipaddr, &port, inst->port_name, true); if (sockfd < 0) { PERROR("Failed opening UDP socket"); error: return -1; } /* * Set SO_REUSEPORT before bind, so that all packets can * listen on the same destination IP address. */ if (1) { int on = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0) { ERROR("Failed to set socket 'reuseport': %s", fr_syserror(errno)); close(sockfd); return -1; } } if (inst->broadcast) { int on = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0) { ERROR("Failed to set broadcast option: %s", fr_syserror(errno)); close(sockfd); return -1; } } /* * SUID up is really only needed if interface is set, OR port <1024. */ rad_suid_up(); rcode = fr_socket_bind(sockfd, &inst->ipaddr, &port, inst->interface); rad_suid_down(); if (rcode < 0) { close(sockfd); PERROR("Failed binding socket"); goto error; } thread->sockfd = sockfd; ci = cf_parent(inst->cs); /* listen { ... } */ rad_assert(ci != NULL); ci = cf_parent(ci); rad_assert(ci != NULL); server_cs = cf_item_to_section(ci); thread->name = fr_app_io_socket_name(thread, &proto_dhcpv4_udp, NULL, 0, &inst->ipaddr, inst->port, inst->interface); DEBUG("Listening on dhcpv4 address %s bound to virtual server %s", thread->name, cf_section_name2(server_cs)); return 0; }
static rlm_rcode_t dhcp_process(REQUEST *request) { rlm_rcode_t rcode; unsigned int i; VALUE_PAIR *vp; dhcp_socket_t *sock; /* * If there's a giaddr, save it as the Relay-IP-Address * in the response. That way the later code knows where * to send the reply. */ vp = fr_pair_find_by_num(request->packet->vps, DHCP_MAGIC_VENDOR, 266, TAG_ANY); /* DHCP-Gateway-IP-Address */ if (vp && (vp->vp_ipv4addr != htonl(INADDR_ANY))) { VALUE_PAIR *relay; /* DHCP-Relay-IP-Address */ MEM(relay = fr_pair_afrom_num(request->reply, DHCP_MAGIC_VENDOR, 222)); relay->vp_ipv4addr = vp->vp_ipv4addr; fr_pair_add(&request->reply->vps, relay); } vp = fr_pair_find_by_num(request->packet->vps, DHCP_MAGIC_VENDOR, 53, TAG_ANY); /* DHCP-Message-Type */ if (vp) { fr_dict_enum_t *dv = fr_dict_enum_by_value(vp->da, &vp->data); if (dv) { CONF_SECTION *server, *unlang; RDEBUG("Trying sub-section dhcp %s {...}", dv->alias); server = cf_item_to_section(cf_parent(request->listener->cs)); unlang = cf_section_find(server, "dhcp", dv->alias); rcode = unlang_interpret(request, unlang, RLM_MODULE_NOOP); } else { REDEBUG("Unknown DHCP-Message-Type %d", vp->vp_uint8); rcode = RLM_MODULE_FAIL; } } else { REDEBUG("Failed to find DHCP-Message-Type in packet!"); rcode = RLM_MODULE_FAIL; } vp = fr_pair_find_by_num(request->reply->vps, DHCP_MAGIC_VENDOR, 53, TAG_ANY); /* DHCP-Message-Type */ if (vp) { request->reply->code = vp->vp_uint8; } else switch (rcode) { case RLM_MODULE_OK: case RLM_MODULE_UPDATED: if (request->packet->code == FR_DHCP_DISCOVER) { request->reply->code = FR_DHCP_OFFER; break; } else if (request->packet->code == FR_DHCP_REQUEST) { request->reply->code = FR_DHCP_ACK; break; } request->reply->code = FR_DHCP_NAK; break; default: case RLM_MODULE_REJECT: case RLM_MODULE_FAIL: case RLM_MODULE_INVALID: case RLM_MODULE_NOOP: case RLM_MODULE_NOTFOUND: if (request->packet->code == FR_DHCP_DISCOVER) { request->reply->code = 0; /* ignore the packet */ } else { request->reply->code = FR_DHCP_NAK; } break; case RLM_MODULE_HANDLED: request->reply->code = 0; /* ignore the packet */ break; } /* * TODO: Handle 'output' of RLM_MODULE when acting as a * DHCP relay We may want to not forward packets in * certain circumstances. */ /* * Handle requests when acting as a DHCP relay */ vp = fr_pair_find_by_num(request->packet->vps, DHCP_MAGIC_VENDOR, 256, TAG_ANY); /* DHCP-Opcode */ if (!vp) { RPEDEBUG("Someone deleted the DHCP-Opcode!"); return RLM_MODULE_FAIL; } /* BOOTREPLY received on port 67 (i.e. from a server) */ if (vp->vp_uint8 == 2) { return dhcprelay_process_server_reply(request); } /* Packet from client, and we have DHCP-Relay-To-IP-Address */ if (fr_pair_find_by_num(request->control, DHCP_MAGIC_VENDOR, 270, TAG_ANY)) { return dhcprelay_process_client_request(request); } /* else it's a packet from a client, without relaying */ rad_assert(vp->vp_uint8 == 1); /* BOOTREQUEST */ sock = request->listener->data; /* * Handle requests when acting as a DHCP server */ /* * Releases don't get replies. */ if (request->packet->code == FR_DHCP_RELEASE) { request->reply->code = 0; } if (request->reply->code == 0) { return RLM_MODULE_OK; } request->reply->sockfd = request->packet->sockfd; /* * Copy specific fields from packet to reply, if they * don't already exist */ for (i = 0; i < sizeof(attrnums) / sizeof(attrnums[0]); i++) { uint32_t attr = attrnums[i]; if (fr_pair_find_by_num(request->reply->vps, DHCP_MAGIC_VENDOR, attr, TAG_ANY)) continue; vp = fr_pair_find_by_num(request->packet->vps, DHCP_MAGIC_VENDOR, attr, TAG_ANY); if (vp) { fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, vp)); } } vp = fr_pair_find_by_num(request->reply->vps, DHCP_MAGIC_VENDOR, 256, TAG_ANY); /* DHCP-Opcode */ rad_assert(vp != NULL); vp->vp_uint8 = 2; /* BOOTREPLY */ /* * Allow NAKs to be delayed for a short period of time. */ if (request->reply->code == FR_DHCP_NAK) { vp = fr_pair_find_by_num(request->reply->vps, 0, FR_FREERADIUS_RESPONSE_DELAY, TAG_ANY); if (vp) { if (vp->vp_uint32 <= 10) { request->response_delay.tv_sec = vp->vp_uint32; request->response_delay.tv_usec = 0; } else { request->response_delay.tv_sec = 10; request->response_delay.tv_usec = 0; } } else { #ifndef USEC #define USEC 1000000 #endif vp = fr_pair_find_by_num(request->reply->vps, 0, FR_FREERADIUS_RESPONSE_DELAY_USEC, TAG_ANY); if (vp) { if (vp->vp_uint32 <= 10 * USEC) { request->response_delay.tv_sec = vp->vp_uint32 / USEC; request->response_delay.tv_usec = vp->vp_uint32 % USEC; } else { request->response_delay.tv_sec = 10; request->response_delay.tv_usec = 0; } } } } /* * Prepare the reply packet for sending through dhcp_socket_send() */ request->reply->dst_ipaddr.af = AF_INET; request->reply->src_ipaddr.af = AF_INET; request->reply->src_ipaddr.prefix = 32; /* * Packet-Src-IP-Address has highest precedence */ vp = fr_pair_find_by_num(request->reply->vps, 0, FR_PACKET_SRC_IP_ADDRESS, TAG_ANY); if (vp) { request->reply->if_index = 0; /* Must be 0, we don't know the outbound if_index */ request->reply->src_ipaddr.addr.v4.s_addr = vp->vp_ipv4addr; /* * The request was unicast (via a relay) */ } else if (request->packet->dst_ipaddr.addr.v4.s_addr != htonl(INADDR_BROADCAST) && request->packet->dst_ipaddr.addr.v4.s_addr != htonl(INADDR_ANY)) { request->reply->src_ipaddr.addr.v4.s_addr = request->packet->dst_ipaddr.addr.v4.s_addr; request->reply->if_index = request->packet->if_index; /* * The listener was bound to an IP address, or we determined * the address automatically, as it was the only address bound * to the interface, and we bound to the interface. */ } else if (sock->src_ipaddr.addr.v4.s_addr != htonl(INADDR_ANY)) { request->reply->src_ipaddr.addr.v4.s_addr = sock->src_ipaddr.addr.v4.s_addr; #ifdef WITH_IFINDEX_IPADDR_RESOLUTION /* * We built with udpfromto and have the if_index of the receiving * interface, which we can now resolve to an IP address. */ } else if (request->packet->if_index > 0) { fr_ipaddr_t primary; if (fr_ipaddr_from_ifindex(&primary, request->packet->sockfd, request->packet->dst_ipaddr.af, request->packet->if_index) < 0) { RPEDEBUG("Failed determining src_ipaddr from if_index"); return RLM_MODULE_FAIL; } request->reply->src_ipaddr.addr.v4.s_addr = primary.addr.v4.s_addr; #endif /* * There's a Server-Identification attribute */ } else if ((vp = fr_pair_find_by_num(request->reply->vps, DHCP_MAGIC_VENDOR, 54, TAG_ANY))) { request->reply->src_ipaddr.addr.v4.s_addr = vp->vp_ipv4addr; } else { REDEBUG("Unable to determine correct src_ipaddr for response"); return RLM_MODULE_FAIL; } request->reply->dst_port = request->packet->src_port; request->reply->src_port = request->packet->dst_port; /* * Answer to client's nearest DHCP relay. * * Which may be different than the giaddr given in the * packet to the client. i.e. the relay may have a * public IP, but the gateway a private one. */ vp = fr_pair_find_by_num(request->reply->vps, DHCP_MAGIC_VENDOR, 272, TAG_ANY); /* DHCP-Relay-IP-Address */ if (vp && (vp->vp_ipv4addr != ntohl(INADDR_ANY))) { RDEBUG2("Reply will be unicast to giaddr from original packet"); request->reply->dst_ipaddr.addr.v4.s_addr = vp->vp_ipv4addr; request->reply->dst_port = request->packet->dst_port; vp = fr_pair_find_by_num(request->reply->vps, 0, FR_PACKET_DST_PORT, TAG_ANY); if (vp) request->reply->dst_port = vp->vp_uint16; return RLM_MODULE_OK; } /* * Answer to client's nearest DHCP gateway. In this * case, the client can reach the gateway, as can the * server. * * We also use *our* source port as the destination port. * Gateways are servers, and listen on the server port, * not the client port. */ vp = fr_pair_find_by_num(request->reply->vps, DHCP_MAGIC_VENDOR, 266, TAG_ANY); /* DHCP-Gateway-IP-Address */ if (vp && (vp->vp_ipv4addr != htonl(INADDR_ANY))) { RDEBUG2("Reply will be unicast to giaddr"); request->reply->dst_ipaddr.addr.v4.s_addr = vp->vp_ipv4addr; request->reply->dst_port = request->packet->dst_port; return RLM_MODULE_OK; } /* * If it's a NAK, or the broadcast flag was set, ond * there's no client-ip-address, send a broadcast. */ if ((request->reply->code == FR_DHCP_NAK) || ((vp = fr_pair_find_by_num(request->reply->vps, DHCP_MAGIC_VENDOR, 262, TAG_ANY)) && /* DHCP-Flags */ (vp->vp_uint32 & 0x8000) && ((vp = fr_pair_find_by_num(request->reply->vps, DHCP_MAGIC_VENDOR, 263, TAG_ANY)) && /* DHCP-Client-IP-Address */ (vp->vp_ipv4addr == htonl(INADDR_ANY))))) { /* * RFC 2131, page 23 * * Broadcast on * - DHCPNAK * or * - Broadcast flag is set up and ciaddr == NULL */ RDEBUG2("Reply will be broadcast"); request->reply->dst_ipaddr.addr.v4.s_addr = htonl(INADDR_BROADCAST); return RLM_MODULE_OK; } /* * RFC 2131, page 23 * * Unicast to ciaddr if present, otherwise to yiaddr. */ if ((vp = fr_pair_find_by_num(request->reply->vps, DHCP_MAGIC_VENDOR, 263, TAG_ANY)) && /* DHCP-Client-IP-Address */ (vp->vp_ipv4addr != htonl(INADDR_ANY))) { RDEBUG2("Reply will be sent unicast to &DHCP-Client-IP-Address"); request->reply->dst_ipaddr.addr.v4.s_addr = vp->vp_ipv4addr; return RLM_MODULE_OK; } vp = fr_pair_find_by_num(request->reply->vps, DHCP_MAGIC_VENDOR, 264, TAG_ANY); /* DHCP-Your-IP-Address */ if (!vp) { REDEBUG("Can't assign address to client: Neither &reply:DHCP-Client-IP-Address nor " "&reply:DHCP-Your-IP-Address set"); /* * There is nowhere to send the response to, so don't bother. */ request->reply->code = 0; return RLM_MODULE_FAIL; } #ifdef SIOCSARP /* * The system is configured to listen for broadcast * packets, which means we'll need to send unicast * replies, to IPs which haven't yet been assigned. * Therefore, we need to update the ARP table. * * However, they haven't specified a interface. So we * can't update the ARP table. And we must send a * broadcast response. */ if (sock->lsock.broadcast && !sock->src_interface) { WARN("You MUST set \"interface\" if you have \"broadcast = yes\""); RDEBUG2("Reply will be broadcast as no interface was defined"); request->reply->dst_ipaddr.addr.v4.s_addr = htonl(INADDR_BROADCAST); return RLM_MODULE_OK; } RDEBUG2("Reply will be unicast to &DHCP-Your-IP-Address"); request->reply->dst_ipaddr.addr.v4.s_addr = vp->vp_ipv4addr; /* * When sending a DHCP_OFFER, make sure our ARP table * contains an entry for the client IP address. * Otherwise the packet may not be sent to the client, as * the OS has no ARP entry for it. * * This is a cute hack to avoid us having to create a raw * socket to send DHCP packets. */ if (request->reply->code == FR_DHCP_OFFER) { VALUE_PAIR *hwvp = fr_pair_find_by_num(request->reply->vps, DHCP_MAGIC_VENDOR, 267, TAG_ANY); /* DHCP-Client-Hardware-Address */ if (!hwvp) return RLM_MODULE_FAIL; if (fr_dhcpv4_udp_add_arp_entry(request->reply->sockfd, sock->src_interface, &vp->vp_ip, hwvp->vp_ether) < 0) { RPEDEBUG("Failed adding arp entry"); return RLM_MODULE_FAIL; } } #else if (request->packet->src_ipaddr.addr.v4.s_addr != ntohl(INADDR_NONE)) { RDEBUG2("Reply will be unicast to the unicast source IP address"); request->reply->dst_ipaddr.addr.v4.s_addr = request->packet->src_ipaddr.addr.v4.s_addr; } else { RDEBUG2("Reply will be broadcast as this system does not support ARP updates"); request->reply->dst_ipaddr.addr.v4.s_addr = htonl(INADDR_BROADCAST); } #endif return RLM_MODULE_OK; }
/** Allow the admin to set packet contents for Status-Server ping checks * * @param[in] ctx to allocate data in (instance of proto_radius). * @param[out] out Where to write our parsed data * @param[in] parent Base structure address. * @param[in] ci #CONF_SECTION specifying the things to update * @param[in] rule unused. * @return * - 0 on success. * - -1 on failure. */ static int status_check_update_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, UNUSED CONF_PARSER const *rule) { int rcode; CONF_SECTION *cs; char const *name2; vp_map_t *head = NULL; rad_assert(cf_item_is_section(ci)); cs = cf_item_to_section(ci); name2 = cf_section_name2(cs); if (!name2 || (strcmp(name2, "request") != 0)) { cf_log_err(cs, "You must specify 'request' as the destination list"); return -1; } /* * Compile the "update" section. */ { vp_tmpl_rules_t parse_rules = { .allow_foreign = true /* Because we don't know where we'll be called */ }; rcode = map_afrom_cs(ctx, &head, cs, &parse_rules, &parse_rules, unlang_fixup_update, NULL, 128); if (rcode < 0) return -1; /* message already printed */ if (!head) { cf_log_err(cs, "'update' sections cannot be empty"); return -1; } } /* * Rely on "bootstrap" to do sanity checks between 'type * = Access-Request', and 'update' containing passwords. */ memcpy(out, &head, sizeof(head)); return 0; } static void mod_radius_signal(REQUEST *request, void *instance, void *thread, void *ctx, fr_state_signal_t action) { rlm_radius_t const *inst = talloc_get_type_abort_const(instance, rlm_radius_t); rlm_radius_thread_t *t = talloc_get_type_abort(thread, rlm_radius_thread_t); /* * We've been told we're done. Clean up. * * Note that the caller doesn't necessarily need to send * us the signal, as he can just talloc_free(request). * But it is more polite to send a signal, and it allows * the IO modules to do additional debugging if * necessary. */ if (action == FR_SIGNAL_CANCEL) { talloc_free(ctx); return; } /* * We received a duplicate packet, but we're not doing * synchronous proxying. Ignore the dup, and rely on the * IO submodule to time it's own retransmissions. */ if ((action == FR_SIGNAL_DUP) && !inst->synchronous) return; if (!inst->io->signal) return; inst->io->signal(request, inst->io_instance, t->thread_io_ctx, ctx, action); }
/* * Expand the variables in an input string. */ char const *cf_expand_variables(char const *cf, int *lineno, CONF_SECTION *outer_cs, char *output, size_t outsize, char const *input, bool *soft_fail) { char *p; char const *end, *ptr; CONF_SECTION const *parent_cs; char name[8192]; if (soft_fail) *soft_fail = false; /* * Find the master parent conf section. * We can't use main_config->root_cs, because we're in the * process of re-building it, and it isn't set up yet... */ parent_cs = cf_root(outer_cs); p = output; ptr = input; while (*ptr) { /* * Ignore anything other than "${" */ if ((*ptr == '$') && (ptr[1] == '{')) { CONF_ITEM *ci; CONF_PAIR *cp; char *q; /* * FIXME: Add support for ${foo:-bar}, * like in xlat.c */ /* * Look for trailing '}', and log a * warning for anything that doesn't match, * and exit with a fatal error. */ end = strchr(ptr, '}'); if (end == NULL) { *p = '\0'; INFO("%s[%d]: Variable expansion missing }", cf, *lineno); return NULL; } ptr += 2; /* * Can't really happen because input lines are * capped at 8k, which is sizeof(name) */ if ((size_t) (end - ptr) >= sizeof(name)) { ERROR("%s[%d]: Reference string is too large", cf, *lineno); return NULL; } memcpy(name, ptr, end - ptr); name[end - ptr] = '\0'; q = strchr(name, ':'); if (q) { *(q++) = '\0'; } ci = cf_reference_item(parent_cs, outer_cs, name); if (!ci) { if (soft_fail) *soft_fail = true; ERROR("%s[%d]: Reference \"${%s}\" not found", cf, *lineno, name); return NULL; } /* * The expansion doesn't refer to another item or section * it's the property of a section. */ if (q) { CONF_SECTION *find = cf_item_to_section(ci); if (ci->type != CONF_ITEM_SECTION) { ERROR("%s[%d]: Can only reference properties of sections", cf, *lineno); return NULL; } switch (fr_str2int(conf_property_name, q, CONF_PROPERTY_INVALID)) { case CONF_PROPERTY_NAME: strcpy(p, find->name1); break; case CONF_PROPERTY_INSTANCE: strcpy(p, find->name2 ? find->name2 : find->name1); break; default: ERROR("%s[%d]: Invalid property '%s'", cf, *lineno, q); return NULL; } p += strlen(p); ptr = end + 1; } else if (ci->type == CONF_ITEM_PAIR) { /* * Substitute the value of the variable. */ cp = cf_item_to_pair(ci); /* * If the thing we reference is * marked up as being expanded in * pass2, don't expand it now. * Let it be expanded in pass2. */ if (cp->pass2) { if (soft_fail) *soft_fail = true; ERROR("%s[%d]: Reference \"%s\" points to a variable which has not been expanded.", cf, *lineno, input); return NULL; } if (!cp->value) { ERROR("%s[%d]: Reference \"%s\" has no value", cf, *lineno, input); return NULL; } if (p + strlen(cp->value) >= output + outsize) { ERROR("%s[%d]: Reference \"%s\" is too long", cf, *lineno, input); return NULL; } strcpy(p, cp->value); p += strlen(p); ptr = end + 1; } else if (ci->type == CONF_ITEM_SECTION) { CONF_SECTION *subcs; /* * Adding an entry again to a * section is wrong. We don't * want an infinite loop. */ if (cf_item_to_section(ci->parent) == outer_cs) { ERROR("%s[%d]: Cannot reference different item in same section", cf, *lineno); return NULL; } /* * Copy the section instead of * referencing it. */ subcs = cf_item_to_section(ci); subcs = cf_section_dup(outer_cs, outer_cs, subcs, cf_section_name1(subcs), cf_section_name2(subcs), false); if (!subcs) { ERROR("%s[%d]: Failed copying reference %s", cf, *lineno, name); return NULL; } subcs->item.filename = ci->filename; subcs->item.lineno = ci->lineno; cf_item_add(outer_cs, &(subcs->item)); ptr = end + 1; } else { ERROR("%s[%d]: Reference \"%s\" type is invalid", cf, *lineno, input); return NULL; } } else if (strncmp(ptr, "$ENV{", 5) == 0) { char *env; ptr += 5; /* * Look for trailing '}', and log a * warning for anything that doesn't match, * and exit with a fatal error. */ end = strchr(ptr, '}'); if (end == NULL) { *p = '\0'; INFO("%s[%d]: Environment variable expansion missing }", cf, *lineno); return NULL; } /* * Can't really happen because input lines are * capped at 8k, which is sizeof(name) */ if ((size_t) (end - ptr) >= sizeof(name)) { ERROR("%s[%d]: Environment variable name is too large", cf, *lineno); return NULL; } memcpy(name, ptr, end - ptr); name[end - ptr] = '\0'; /* * Get the environment variable. * If none exists, then make it an empty string. */ env = getenv(name); if (env == NULL) { *name = '\0'; env = name; } if (p + strlen(env) >= output + outsize) { ERROR("%s[%d]: Reference \"%s\" is too long", cf, *lineno, input); return NULL; } strcpy(p, env); p += strlen(p); ptr = end + 1; } else { /* * Copy it over verbatim. */ *(p++) = *(ptr++); } if (p >= (output + outsize)) { ERROR("%s[%d]: Reference \"%s\" is too long", cf, *lineno, input); return NULL; } } /* loop over all of the input string. */ *p = '\0'; return output; }
/** Open a UDP listener for RADIUS * */ static int mod_open(fr_listen_t *li) { proto_radius_udp_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_radius_udp_t); proto_radius_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_radius_udp_thread_t); int sockfd; uint16_t port = inst->port; CONF_SECTION *server_cs; CONF_ITEM *ci; li->fd = sockfd = fr_socket_server_udp(&inst->ipaddr, &port, inst->port_name, true); if (sockfd < 0) { PERROR("Failed opening UDP socket"); error: return -1; } /* * Set SO_REUSEPORT before bind, so that all packets can * listen on the same destination IP address. */ if (1) { int on = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0) { ERROR("Failed to set socket 'reuseport': %s", fr_syserror(errno)); return -1; } } #ifdef SO_RCVBUF if (inst->recv_buff_is_set) { int opt; opt = inst->recv_buff; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(int)) < 0) { WARN("Failed setting 'recv_buf': %s", fr_syserror(errno)); } } #endif #ifdef SO_SNDBUF if (inst->send_buff_is_set) { int opt; opt = inst->send_buff; if (setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(int)) < 0) { WARN("Failed setting 'send_buf': %s", fr_syserror(errno)); } } #endif if (fr_socket_bind(sockfd, &inst->ipaddr, &port, inst->interface) < 0) { close(sockfd); PERROR("Failed binding socket"); goto error; } thread->sockfd = sockfd; ci = cf_parent(inst->cs); /* listen { ... } */ rad_assert(ci != NULL); ci = cf_parent(ci); rad_assert(ci != NULL); server_cs = cf_item_to_section(ci); thread->name = fr_app_io_socket_name(thread, &proto_radius_udp, NULL, 0, &inst->ipaddr, inst->port, inst->interface); // @todo - also print out auth / acct / coa, etc. DEBUG("Listening on radius address %s bound to virtual server %s", thread->name, cf_section_name2(server_cs)); return 0; }