/** Check group membership attributes to see if a user is a member. * * @param[in] inst rlm_ldap configuration. * @param[in] request Current request. * @param[in] check vp containing the group value (name or dn). * * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_check_cached(ldap_instance_t const *inst, REQUEST *request, VALUE_PAIR *check) { VALUE_PAIR *vp; int ret; vp_cursor_t cursor; fr_cursor_init(&cursor, &request->config_items); /* * We return RLM_MODULE_INVALID here as an indication * the caller should try a dynamic group lookup instead. */ vp = fr_cursor_next_by_num(&cursor, inst->cache_da->attr, inst->cache_da->vendor, TAG_ANY); if (!vp) return RLM_MODULE_INVALID; fr_cursor_first(&cursor); while ((vp = fr_cursor_next_by_num(&cursor, inst->cache_da->attr, inst->cache_da->vendor, TAG_ANY))) { ret = paircmp_op(T_OP_CMP_EQ, vp, check); if (ret == 1) { RDEBUG2("User found. Matched cached membership"); return RLM_MODULE_OK; } if (ret < -1) { return RLM_MODULE_FAIL; } } RDEBUG2("Cached membership not found"); return RLM_MODULE_NOTFOUND; }
/** Check group membership attributes to see if a user is a member. * * @param[in] inst rlm_ldap configuration. * @param[in] request Current request. * @param[in] check vp containing the group value (name or dn). * * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_check_cached(ldap_instance_t const *inst, REQUEST *request, VALUE_PAIR *check) { VALUE_PAIR *vp; int ret; vp_cursor_t cursor; fr_cursor_init(&cursor, &request->config_items); vp = fr_cursor_next_by_num(&cursor, inst->cache_da->attr, inst->cache_da->vendor, TAG_ANY); if (!vp) { return RLM_MODULE_INVALID; } for (; vp; vp = fr_cursor_next_by_num(&cursor, inst->cache_da->attr, inst->cache_da->vendor, TAG_ANY)) { ret = radius_compare_vps(request, check, vp); if (ret == 0) { RDEBUG2("User found. Matched cached membership"); return RLM_MODULE_OK; } if (ret < -1) { return RLM_MODULE_FAIL; } } RDEBUG2("Membership not found"); return RLM_MODULE_NOTFOUND; }
/** Find the pair with the matching attribute * * @todo should take DAs and do a pointer comparison. */ VALUE_PAIR *pairfind(VALUE_PAIR *vp, unsigned int attr, unsigned int vendor, int8_t tag) { vp_cursor_t cursor; /* List head may be NULL if it contains no VPs */ if (!vp) return NULL; VERIFY_LIST(vp); (void) fr_cursor_init(&cursor, &vp); return fr_cursor_next_by_num(&cursor, attr, vendor, tag); }
/** Print out attribute info * * Prints out all instances of a current attribute, or all attributes in a list. * * At higher debugging levels, also prints out alternative decodings of the same * value. This is helpful to determine types for unknown attributes of long * passed vendors, or just crazy/broken NAS. * * It's also useful for exposing issues in the packet decoding functions, as in * some cases they get fed random garbage data. * * This expands to a zero length string. */ static ssize_t xlat_debug_attr(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, UNUSED size_t outlen) { VALUE_PAIR *vp, **vps; REQUEST *current; value_pair_tmpl_t vpt; vp_cursor_t cursor; char buffer[1024]; if (!RDEBUG_ENABLED2) { *out = '\0'; return -1; } while (isspace((int) *fmt)) fmt++; if (*fmt == '&') fmt++; if (radius_parse_attr(fmt, &vpt, REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) { return -1; } current = request; if (radius_request(¤t, vpt.request) < 0) return -2; vps = radius_list(current, vpt.list); if (!vps) { return -2; } RIDEBUG("Attributes matching \"%s\"", fmt); vp = fr_cursor_init(&cursor, vps); if (vpt.da) { vp = fr_cursor_next_by_num(&cursor, vpt.da->attr, vpt.da->vendor, TAG_ANY); } while (vp) { DICT_ATTR *dac = NULL; DICT_VENDOR *dv; VALUE_PAIR *vpc = NULL; FR_NAME_NUMBER const *type; vp_prints_value(buffer, sizeof(buffer), vp, '\''); if (vp->da->flags.has_tag) { RIDEBUG2("\t%s:%s:%i %s %s", fr_int2str(pair_lists, vpt.list, "<INVALID>"), vp->da->name, vp->tag, fr_int2str(fr_tokens, vp->op, "<INVALID>"), buffer); } else { RIDEBUG2("\t%s:%s %s %s", fr_int2str(pair_lists, vpt.list, "<INVALID>"), vp->da->name, fr_int2str(fr_tokens, vp->op, "<INVALID>"), buffer); } if (!RDEBUG_ENABLED3) { goto next_vp; } if (vp->da->vendor) { dv = dict_vendorbyvalue(vp->da->vendor); RDEBUG3("\t\tvendor : %i (%s)", vp->da->vendor, dv ? dv->name : "unknown"); } RDEBUG3("\t\ttype : %s", fr_int2str(dict_attr_types, vp->da->type, "<INVALID>")); RDEBUG3("\t\tlength : %zu", vp->length); dac = talloc_memdup(request, vp->da, sizeof(DICT_ATTR)); if (!dac) { return -1; } dac->flags.vp_free = 0; if (!RDEBUG_ENABLED4) { goto next_vp; } type = dict_attr_types; while (type->name) { int pad; ssize_t len; uint8_t const *data = NULL; vpc = NULL; if ((PW_TYPE) type->number == vp->da->type) { goto next_type; } switch (type->number) { case PW_TYPE_INVALID: /* Not real type */ case PW_TYPE_MAX: /* Not real type */ case PW_TYPE_EXTENDED: /* Not safe/appropriate */ case PW_TYPE_LONG_EXTENDED: /* Not safe/appropriate */ case PW_TYPE_TLV: /* Not safe/appropriate */ case PW_TYPE_VSA: /* @fixme We need special behaviour for these */ goto next_type; default: break; } dac->type = type->number; len = rad_vp2data(&data, vp); if (len < 0) { goto next_type; } if (data2vp(NULL, NULL, NULL, dac, data, len, len, &vpc) < 0) { goto next_type; } /* * data2vp has knowledge of expected format lengths, if the length * from rad_vp2data doesn't match, it encodes the attribute * as raw octets. This results in many useless debug lines with * the same hex string. */ if ((type->number != PW_TYPE_OCTETS) && (vpc->da->type == PW_TYPE_OCTETS)) { goto next_type; } if (!vp_prints_value(buffer, sizeof(buffer), vpc, '\'')) { goto next_type; } if ((pad = (11 - strlen(type->name))) < 0) { pad = 0; } /* * @fixme: if the value happens to decode as a VSA * (someone put a VSA into a VSA?), we probably to print * extended info for that/reparse */ RDEBUG4("\t\tas %s%*s: %s", type->name, pad, " ", buffer); next_type: talloc_free(vpc); type++; } next_vp: talloc_free(dac); if (vpt.da) { vp = fr_cursor_next_by_num(&cursor, vpt.da->attr, vpt.da->vendor, TAG_ANY); } else { vp = fr_cursor_next(&cursor); } } *out = '\0'; return 0; }
static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, pair_lists_t list, DICT_ATTR const *da, int8_t tag, int num, bool return_null) { VALUE_PAIR *vp, *vps = NULL; RADIUS_PACKET *packet = NULL; DICT_VALUE *dv; VALUE_PAIR myvp; /* * Arg. Too much abstraction is annoying. */ switch (list) { default: if (return_null) return NULL; return vp_aprinttype(ctx, da->type); case PAIR_LIST_CONTROL: vps = request->config_items; break; case PAIR_LIST_REQUEST: packet = request->packet; if (packet) vps = packet->vps; break; case PAIR_LIST_REPLY: packet = request->reply; if (packet) vps = packet->vps; break; #ifdef WITH_PROXY case PAIR_LIST_PROXY_REQUEST: packet = request->proxy; if (packet) vps = packet->vps; break; case PAIR_LIST_PROXY_REPLY: packet = request->proxy_reply; if (packet) vps = packet->vps; break; #endif #ifdef WITH_COA case PAIR_LIST_COA: case PAIR_LIST_DM: if (request->coa) packet = request->coa->packet; if (packet) vps = packet->vps; break; case PAIR_LIST_COA_REPLY: case PAIR_LIST_DM_REPLY: if (request->coa) packet = request->coa->reply; if (packet) vps = packet->vps; break; #endif } /* * Now that we have the list, etc. handled, * find the VP and print it. */ if ((da->vendor != 0) || (da->attr < 256) || (list == PAIR_LIST_CONTROL)) { print_vp: vp = pairfind(vps, da->attr, da->vendor, tag); if (!vp) { return NULL; } goto do_print; } /* * Some non-packet expansions */ switch (da->attr) { default: break; /* ignore them */ case PW_CLIENT_SHORTNAME: if (request->client && request->client->shortname) { return talloc_strdup(ctx, request->client->shortname); } return talloc_strdup(ctx, "<UNKNOWN-CLIENT>"); case PW_REQUEST_PROCESSING_STAGE: if (request->component) { return talloc_strdup(ctx, request->component); } return talloc_strdup(ctx, "server_core"); case PW_VIRTUAL_SERVER: if (!request->server) return NULL; return talloc_strdup(ctx, request->server); case PW_MODULE_RETURN_CODE: return talloc_asprintf(ctx, "%d", request->simul_max); /* hack */ } /* * All of the attributes must now refer to a packet. If * there's no packet, we can't print any attribute * referencing it. */ if (!packet) { if (return_null) return NULL; return vp_aprinttype(ctx, da->type); } memset(&myvp, 0, sizeof(myvp)); myvp.da = da; vp = NULL; switch (da->attr) { default: goto print_vp; case PW_PACKET_TYPE: dv = dict_valbyattr(PW_PACKET_TYPE, 0, packet->code); if (dv) return talloc_strdup(ctx, dv->name); return talloc_asprintf(ctx, "%d", packet->code); case PW_RESPONSE_PACKET_TYPE: { int code = 0; #ifdef WITH_PROXY if (request->proxy_reply && (!request->reply || !request->reply->code)) { code = request->proxy_reply->code; } else #endif if (request->reply) { code = request->reply->code; } return talloc_strdup(ctx, fr_packet_codes[code]); } case PW_PACKET_AUTHENTICATION_VECTOR: myvp.length = sizeof(packet->vector); memcpy(&myvp.vp_octets, packet->vector, sizeof(packet->vector)); vp = &myvp; break; case PW_CLIENT_IP_ADDRESS: case PW_PACKET_SRC_IP_ADDRESS: if (packet->src_ipaddr.af == AF_INET) { myvp.vp_ipaddr = packet->src_ipaddr.ipaddr.ip4addr.s_addr; vp = &myvp; } break; case PW_PACKET_DST_IP_ADDRESS: if (packet->dst_ipaddr.af == AF_INET) { myvp.vp_ipaddr = packet->dst_ipaddr.ipaddr.ip4addr.s_addr; vp = &myvp; } break; case PW_PACKET_SRC_IPV6_ADDRESS: if (packet->src_ipaddr.af == AF_INET6) { memcpy(&myvp.vp_ipv6addr, &packet->src_ipaddr.ipaddr.ip6addr, sizeof(packet->src_ipaddr.ipaddr.ip6addr)); vp = &myvp; } break; case PW_PACKET_DST_IPV6_ADDRESS: if (packet->dst_ipaddr.af == AF_INET6) { memcpy(&myvp.vp_ipv6addr, &packet->dst_ipaddr.ipaddr.ip6addr, sizeof(packet->dst_ipaddr.ipaddr.ip6addr)); vp = &myvp; } break; case PW_PACKET_SRC_PORT: myvp.vp_integer = packet->src_port; vp = &myvp; break; case PW_PACKET_DST_PORT: myvp.vp_integer = packet->dst_port; vp = &myvp; break; } do_print: /* * Hack up the virtual attributes. */ if (num && (vp == &myvp)) { char *p, *q; /* * [*] means only one. */ if (num == 65537) num = 0; /* * [n] means NULL, as there's only one. */ if ((num > 0) && (num < 65536)) { return NULL; } p = vp_aprint(ctx, vp); rad_assert(p != NULL); /* * Get the length of it. */ if (num == 65536) { q = talloc_asprintf(ctx, "%d", (int) strlen(p)); talloc_free(p); return q; } return p; } /* * We want the N'th VP. */ if (num) { int count = 0; vp_cursor_t cursor; /* * Return a count of the VPs. */ if (num == 65536) { fr_cursor_init(&cursor, &vp); while (fr_cursor_next_by_num(&cursor, da->attr, da->vendor, tag) != NULL) { count++; } return talloc_asprintf(ctx, "%d", count); } /* * Ugly, but working. */ if (num == 65537) { char *p, *q; (void) fr_cursor_init(&cursor, &vp); vp = fr_cursor_next_by_num(&cursor, da->attr, da->vendor, tag); if (!vp) return NULL; p = vp_aprint(ctx, vp); while ((vp = fr_cursor_next_by_num(&cursor, da->attr, da->vendor, tag)) != NULL) { q = vp_aprint(ctx, vp); p = talloc_strdup_append(p, ","); p = talloc_strdup_append(p, q); } return p; } (void) fr_cursor_init(&cursor, &vp); while ((vp = fr_cursor_next_by_num(&cursor, da->attr, da->vendor, tag)) != NULL) { if (count == num) { break; } count++; } } if (!vp) { if (return_null) return NULL; return vp_aprinttype(ctx, da->type); } return vp_aprint(ctx, vp); }
/** Start a process * * @param cmd Command to execute. This is parsed into argv[] parts, then each individual argv * part is xlat'ed. * @param request Current reuqest * @param exec_wait set to true to read from or write to child. * @param[in,out] input_fd pointer to int, receives the stdin file descriptor. Set to NULL * and the child will have /dev/null on stdin. * @param[in,out] output_fd pinter to int, receives the stdout file descriptor. Set to NULL * and child will have /dev/null on stdout. * @param input_pairs list of value pairs - these will be put into the environment variables * of the child. * @param shell_escape values before passing them as arguments. * @return * - PID of the child process. * - -1 on failure. */ pid_t radius_start_program(char const *cmd, REQUEST *request, bool exec_wait, int *input_fd, int *output_fd, VALUE_PAIR *input_pairs, bool shell_escape) { #ifndef __MINGW32__ char *p; VALUE_PAIR *vp; int n; int to_child[2] = {-1, -1}; int from_child[2] = {-1, -1}; pid_t pid; #endif int argc; int i; char const **argv_p; char *argv[MAX_ARGV], **argv_start = argv; char argv_buf[4096]; #define MAX_ENVP 1024 char *envp[MAX_ENVP]; size_t envlen = 0; TALLOC_CTX *input_ctx = NULL; /* * Stupid array decomposition... * * If we do memcpy(&argv_p, &argv, sizeof(argv_p)) src ends up being a char ** * pointing to the value of the first element. */ memcpy(&argv_p, &argv_start, sizeof(argv_p)); argc = rad_expand_xlat(request, cmd, MAX_ARGV, argv_p, true, sizeof(argv_buf), argv_buf); if (argc <= 0) { ERROR("Invalid command '%s'", cmd); return -1; } if (DEBUG_ENABLED3) { for (i = 0; i < argc; i++) DEBUG3("arg[%d] %s", i, argv[i]); } #ifndef __MINGW32__ /* * Open a pipe for child/parent communication, if necessary. */ if (exec_wait) { if (input_fd) { if (pipe(to_child) != 0) { ERROR("Couldn't open pipe to child: %s", fr_syserror(errno)); return -1; } } if (output_fd) { if (pipe(from_child) != 0) { ERROR("Couldn't open pipe from child: %s", fr_syserror(errno)); /* safe because these either need closing or are == -1 */ close(to_child[0]); close(to_child[1]); return -1; } } } envp[0] = NULL; if (input_pairs) { vp_cursor_t cursor; char buffer[1024]; input_ctx = talloc_new(request); /* * Set up the environment variables in the * parent, so we don't call libc functions that * hold mutexes. They might be locked when we fork, * and will remain locked in the child. */ for (vp = fr_cursor_init(&cursor, &input_pairs); vp && (envlen < ((sizeof(envp) / sizeof(*envp)) - 1)); vp = fr_cursor_next(&cursor)) { /* * Hmm... maybe we shouldn't pass the * user's password in an environment * variable... */ snprintf(buffer, sizeof(buffer), "%s=", vp->da->name); if (shell_escape) { for (p = buffer; *p != '='; p++) { if (*p == '-') { *p = '_'; } else if (isalpha((int) *p)) { *p = toupper(*p); } } } n = strlen(buffer); fr_pair_value_snprint(buffer + n, sizeof(buffer) - n, vp, shell_escape ? '"' : 0); DEBUG3("export %s", buffer); envp[envlen++] = talloc_strdup(input_ctx, buffer); } fr_cursor_init(&cursor, radius_list(request, PAIR_LIST_CONTROL)); while ((envlen < ((sizeof(envp) / sizeof(*envp)) - 1)) && (vp = fr_cursor_next_by_num(&cursor, 0, PW_EXEC_EXPORT, TAG_ANY))) { DEBUG3("export %s", vp->vp_strvalue); memcpy(&envp[envlen++], &vp->vp_strvalue, sizeof(*envp)); } /* * NULL terminate for execve */ envp[envlen] = NULL; } if (exec_wait) { pid = rad_fork(); /* remember PID */ } else { pid = fork(); /* don't wait */ } if (pid == 0) { int devnull; /* * Child process. * * We try to be fail-safe here. So if ANYTHING * goes wrong, we exit with status 1. */ /* * Open STDIN to /dev/null */ devnull = open("/dev/null", O_RDWR); if (devnull < 0) { ERROR("Failed opening /dev/null: %s\n", fr_syserror(errno)); /* * Where the status code is interpreted as a module rcode * one is subtracted from it, to allow 0 to equal success * * 2 is RLM_MODULE_FAIL + 1 */ exit(2); } /* * Only massage the pipe handles if the parent * has created them. */ if (exec_wait) { if (input_fd) { close(to_child[1]); dup2(to_child[0], STDIN_FILENO); } else { dup2(devnull, STDIN_FILENO); } if (output_fd) { close(from_child[0]); dup2(from_child[1], STDOUT_FILENO); } else { dup2(devnull, STDOUT_FILENO); } } else { /* no pipe, STDOUT should be /dev/null */ dup2(devnull, STDIN_FILENO); dup2(devnull, STDOUT_FILENO); } /* * If we're not debugging, then we can't do * anything with the error messages, so we throw * them away. * * If we are debugging, then we want the error * messages to go to the STDERR of the server. */ if (rad_debug_lvl == 0) { dup2(devnull, STDERR_FILENO); } close(devnull); /* * The server may have MANY FD's open. We don't * want to leave dangling FD's for the child process * to play funky games with, so we close them. */ closefrom(3); /* * I swear the signature for execve is wrong and should * take 'char const * const argv[]'. * * Note: execve(), unlike system(), treats all the space * delimited arguments as literals, so there's no need * to perform additional escaping. */ execve(argv[0], argv, envp); printf("Failed to execute \"%s\": %s", argv[0], fr_syserror(errno)); /* fork output will be captured */ /* * Where the status code is interpreted as a module rcode * one is subtracted from it, to allow 0 to equal success * * 2 is RLM_MODULE_FAIL + 1 */ exit(2); } /* * Free child environment variables */ talloc_free(input_ctx); /* * Parent process. */ if (pid < 0) { ERROR("Couldn't fork %s: %s", argv[0], fr_syserror(errno)); if (exec_wait) { /* safe because these either need closing or are == -1 */ close(to_child[0]); close(to_child[1]); close(from_child[0]); close(from_child[1]); } return -1; } /* * We're not waiting, exit, and ignore any child's status. */ if (exec_wait) { /* * Close the ends of the pipe(s) the child is using * return the ends of the pipe(s) our caller wants * */ if (input_fd) { *input_fd = to_child[1]; close(to_child[0]); } if (output_fd) { *output_fd = from_child[0]; close(from_child[1]); } } return pid; #else if (exec_wait) { ERROR("Wait is not supported"); return -1; } { /* * The _spawn and _exec families of functions are * found in Windows compiler libraries for * portability from UNIX. There is a variety of * functions, including the ability to pass * either a list or array of parameters, to * search in the PATH or otherwise, and whether * or not to pass an environment (a set of * environment variables). Using _spawn, you can * also specify whether you want the new process * to close your program (_P_OVERLAY), to wait * until the new process is finished (_P_WAIT) or * for the two to run concurrently (_P_NOWAIT). * _spawn and _exec are useful for instances in * which you have simple requirements for running * the program, don't want the overhead of the * Windows header file, or are interested * primarily in portability. */ /* * FIXME: check return code... what is it? */ _spawnve(_P_NOWAIT, argv[0], argv, envp); } return 0; #endif }
/* * Handles multiple EAP-Message attrs * ie concatenates all to get the complete EAP packet. * * NOTE: Sometimes Framed-MTU might contain the length of EAP-Message, * refer fragmentation in rfc2869. */ eap_packet_raw_t *eap_vp2packet(TALLOC_CTX *ctx, VALUE_PAIR *vps) { VALUE_PAIR *first, *i; eap_packet_raw_t *eap_packet; unsigned char *ptr; uint16_t len; int total_len; vp_cursor_t cursor; /* * Get only EAP-Message attribute list */ first = fr_pair_find_by_num(vps, 0, PW_EAP_MESSAGE, TAG_ANY); if (!first) { fr_strerror_printf("EAP-Message not found"); return NULL; } /* * Sanity check the length before doing anything. */ if (first->vp_length < 4) { fr_strerror_printf("EAP packet is too short"); return NULL; } /* * Get the Actual length from the EAP packet * First EAP-Message contains the EAP packet header */ memcpy(&len, first->vp_strvalue + 2, sizeof(len)); len = ntohs(len); /* * Take out even more weird things. */ if (len < 4) { fr_strerror_printf("EAP packet has invalid length (less than 4 bytes)"); return NULL; } /* * Sanity check the length, BEFORE allocating memory. */ total_len = 0; fr_cursor_init(&cursor, &first); while ((i = fr_cursor_next_by_num(&cursor, 0, PW_EAP_MESSAGE, TAG_ANY))) { total_len += i->vp_length; if (total_len > len) { fr_strerror_printf("Malformed EAP packet. Length in packet header %i, " "does not match actual length %i", len, total_len); return NULL; } } /* * If the length is SMALLER, die, too. */ if (total_len < len) { fr_strerror_printf("Malformed EAP packet. Length in packet header does not " "match actual length"); return NULL; } /* * Now that we know the lengths are OK, allocate memory. */ eap_packet = (eap_packet_raw_t *) talloc_zero_array(ctx, uint8_t, len); if (!eap_packet) { return NULL; } /* * Copy the data from EAP-Message's over to our EAP packet. */ ptr = (unsigned char *)eap_packet; /* RADIUS ensures order of attrs, so just concatenate all */ fr_cursor_first(&cursor); while ((i = fr_cursor_next_by_num(&cursor, 0, PW_EAP_MESSAGE, TAG_ANY))) { memcpy(ptr, i->vp_strvalue, i->vp_length); ptr += i->vp_length; } return eap_packet; }
/* * Check password. * * Returns: 0 OK * -1 Password fail * -2 Rejected (Auth-Type = Reject, send Port-Message back) * 1 End check & return, don't reply * * NOTE: NOT the same as the RLM_ values ! */ static int CC_HINT(nonnull) rad_check_password(REQUEST *request) { vp_cursor_t cursor; VALUE_PAIR *auth_type_pair; int auth_type = -1; int result; int auth_type_count = 0; /* * Look for matching check items. We skip the whole lot * if the authentication type is PW_AUTHTYPE_ACCEPT or * PW_AUTHTYPE_REJECT. */ fr_cursor_init(&cursor, &request->config); while ((auth_type_pair = fr_cursor_next_by_num(&cursor, PW_AUTH_TYPE, 0, TAG_ANY))) { auth_type = auth_type_pair->vp_integer; auth_type_count++; RDEBUG2("Found Auth-Type = %s", dict_valnamebyattr(PW_AUTH_TYPE, 0, auth_type)); if (auth_type == PW_AUTHTYPE_REJECT) { RDEBUG2("Auth-Type = Reject, rejecting user"); return -2; } } /* * Warn if more than one Auth-Type was found, because only the last * one found will actually be used. */ if ((auth_type_count > 1) && (debug_flag)) { RERROR("Warning: Found %d auth-types on request for user '%s'", auth_type_count, request->username->vp_strvalue); } /* * This means we have a proxy reply or an accept and it wasn't * rejected in the above loop. So that means it is accepted and we * do no further authentication. */ if ((auth_type == PW_AUTHTYPE_ACCEPT) #ifdef WITH_PROXY || (request->proxy) #endif ) { RDEBUG2("Auth-Type = Accept, accepting the user"); return 0; } /* * Check that Auth-Type has been set, and reject if not. * * Do quick checks to see if Cleartext-Password or Crypt-Password have * been set, and complain if so. */ if (auth_type < 0) { if (pairfind(request->config, PW_CRYPT_PASSWORD, 0, TAG_ANY) != NULL) { RWDEBUG2("Please update your configuration, and remove 'Auth-Type = Crypt'"); RWDEBUG2("Use the PAP module instead"); } else if (pairfind(request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY) != NULL) { RWDEBUG2("Please update your configuration, and remove 'Auth-Type = Local'"); RWDEBUG2("Use the PAP or CHAP modules instead"); } /* * The admin hasn't told us how to * authenticate the user, so we reject them! * * This is fail-safe. */ REDEBUG2("No Auth-Type found: rejecting the user via Post-Auth-Type = Reject"); return -2; } /* * See if there is a module that handles * this Auth-Type, and turn the RLM_ return * status into the values as defined at * the top of this function. */ result = process_authenticate(auth_type, request); switch (result) { /* * An authentication module FAIL * return code, or any return code that * is not expected from authentication, * is the same as an explicit REJECT! */ case RLM_MODULE_FAIL: case RLM_MODULE_INVALID: case RLM_MODULE_NOOP: case RLM_MODULE_NOTFOUND: case RLM_MODULE_REJECT: case RLM_MODULE_UPDATED: case RLM_MODULE_USERLOCK: default: result = -1; break; case RLM_MODULE_OK: result = 0; break; case RLM_MODULE_HANDLED: result = 1; break; } return result; }
/** Copy packet to multiple servers * * Create a duplicate of the packet and send it to a list of realms * defined by the presence of the Replicate-To-Realm VP in the control * list of the current request. * * This is pretty hacky and is 100% fire and forget. If you're looking * to forward authentication requests to multiple realms and process * the responses, this function will not allow you to do that. * * @param[in] instance of this module. * @param[in] request The current request. * @param[in] list of attributes to copy to the duplicate packet. * @param[in] code to write into the code field of the duplicate packet. * @return RCODE fail on error, invalid if list does not exist, noop if no replications succeeded, else ok. */ static rlm_rcode_t replicate_packet(UNUSED void *instance, REQUEST *request, pair_lists_t list, PW_CODE code) { int rcode = RLM_MODULE_NOOP; bool pass1 = true; vp_cursor_t cursor; VALUE_PAIR *vp; RADIUS_PACKET *packet = NULL; rcode = rlm_replicate_alloc(&packet, request, list, code); if (rcode != RLM_MODULE_OK) { return rcode; } /* * Send as many packets as necessary to different destinations. */ fr_cursor_init(&cursor, &request->config_items); while ((vp = fr_cursor_next_by_num(&cursor, PW_REPLICATE_TO_REALM, 0, TAG_ANY))) { home_server_t *home; REALM *realm; home_pool_t *pool; realm = realm_find2(vp->vp_strvalue); if (!realm) { REDEBUG2("Cannot Replicate to unknown realm \"%s\"", vp->vp_strvalue); continue; } /* * We shouldn't really do this on every loop. */ switch (request->packet->code) { default: REDEBUG2("Cannot replicate unknown packet code %d", request->packet->code); rcode = RLM_MODULE_FAIL; goto done; case PW_CODE_ACCESS_REQUEST: pool = realm->auth_pool; break; #ifdef WITH_ACCOUNTING case PW_CODE_ACCOUNTING_REQUEST: pool = realm->acct_pool; break; #endif #ifdef WITH_COA case PW_CODE_COA_REQUEST: case PW_CODE_DISCONNECT_REQUEST: pool = realm->acct_pool; break; #endif } if (!pool) { RWDEBUG2("Cancelling replication to Realm %s, as the realm is local", realm->name); continue; } home = home_server_ldb(realm->name, pool, request); if (!home) { REDEBUG2("Failed to find live home server for realm %s", realm->name); continue; } /* * For replication to multiple servers we re-use the packet * we built here. */ if (pass1) { packet->id = fr_rand() & 0xff; packet->sockfd = fr_socket(&home->src_ipaddr, 0); if (packet->sockfd < 0) { REDEBUG("Failed opening socket: %s", fr_strerror()); rcode = RLM_MODULE_FAIL; goto done; } pass1 = false; } else { size_t i; for (i = 0; i < sizeof(packet->vector); i++) { packet->vector[i] = fr_rand() & 0xff; } packet->id++; TALLOC_FREE(packet->data); packet->data_len = 0; } /* * (Re)-Write these. */ packet->dst_ipaddr = home->ipaddr; packet->dst_port = home->port; memset(&packet->src_ipaddr, 0, sizeof(packet->src_ipaddr)); packet->src_port = 0; /* * Encode, sign and then send the packet. */ RDEBUG("Replicating list '%s' to Realm '%s'", fr_int2str(pair_lists, list, "<INVALID>"), realm->name); if (rad_send(packet, NULL, home->secret) < 0) { REDEBUG("Failed replicating packet: %s", fr_strerror()); rcode = RLM_MODULE_FAIL; goto done; } /* * We've sent it to at least one destination. */ rcode = RLM_MODULE_OK; } done: talloc_free(packet); return rcode; }
/* * Initialize a radclient data structure and add it to * the global linked list. */ static int radclient_init(TALLOC_CTX *ctx, rc_file_pair_t *files) { FILE *packets, *filters = NULL; vp_cursor_t cursor; VALUE_PAIR *vp; rc_request_t *request; bool packets_done = false; uint64_t num = 0; assert(files->packets != NULL); /* * Determine where to read the VP's from. */ if (strcmp(files->packets, "-") != 0) { packets = fopen(files->packets, "r"); if (!packets) { ERROR("Error opening %s: %s", files->packets, strerror(errno)); return 0; } /* * Read in the pairs representing the expected response. */ if (files->filters) { filters = fopen(files->filters, "r"); if (!filters) { ERROR("Error opening %s: %s", files->filters, strerror(errno)); fclose(packets); return 0; } } } else { packets = stdin; } /* * Loop until the file is done. */ do { /* * Allocate it. */ request = talloc_zero(ctx, rc_request_t); if (!request) { ERROR("Out of memory"); goto error; } talloc_set_destructor(request, _rc_request_free); request->packet = rad_alloc(request, 1); if (!request->packet) { ERROR("Out of memory"); goto error; } #ifdef WITH_TCP request->packet->src_ipaddr = client_ipaddr; request->packet->src_port = client_port; request->packet->dst_ipaddr = server_ipaddr; request->packet->dst_port = server_port; request->packet->proto = ipproto; #endif request->files = files; request->packet->id = -1; /* allocate when sending */ request->num = num++; /* * Read the request VP's. */ if (readvp2(&request->packet->vps, request->packet, packets, &packets_done) < 0) { ERROR("Error parsing \"%s\"", files->packets); goto error; } fr_cursor_init(&cursor, &request->filter); vp = fr_cursor_next_by_num(&cursor, PW_PACKET_TYPE, 0, TAG_ANY); if (vp) { fr_cursor_remove(&cursor); request->packet_code = vp->vp_integer; talloc_free(vp); } else { request->packet_code = packet_code; /* Use the default set on the command line */ } /* * Read in filter VP's. */ if (filters) { bool filters_done; if (readvp2(&request->filter, request, filters, &filters_done) < 0) { ERROR("Error parsing \"%s\"", files->filters); goto error; } if (!request->filter) { goto error; } if (filters_done && !packets_done) { ERROR("Differing number of packets/filters in %s:%s " "(too many requests))", files->packets, files->filters); goto error; } if (!filters_done && packets_done) { ERROR("Differing number of packets/filters in %s:%s " "(too many filters))", files->packets, files->filters); goto error; } fr_cursor_init(&cursor, &request->filter); vp = fr_cursor_next_by_num(&cursor, PW_PACKET_TYPE, 0, TAG_ANY); if (vp) { fr_cursor_remove(&cursor); request->filter_code = vp->vp_integer; talloc_free(vp); } /* * xlat expansions aren't supported here */ for (vp = fr_cursor_init(&cursor, &request->filter); vp; vp = fr_cursor_next(&cursor)) { if (vp->type == VT_XLAT) { vp->type = VT_DATA; vp->vp_strvalue = vp->value.xlat; } } /* * This allows efficient list comparisons later */ pairsort(&request->filter, attrtagcmp); } /* * Determine the response code from the request (if not already set) */ if (!request->filter_code) { switch (request->packet_code) { case PW_CODE_AUTHENTICATION_REQUEST: request->filter_code = PW_CODE_AUTHENTICATION_ACK; break; case PW_CODE_ACCOUNTING_REQUEST: request->filter_code = PW_CODE_ACCOUNTING_RESPONSE; break; case PW_CODE_COA_REQUEST: request->filter_code = PW_CODE_COA_ACK; break; case PW_CODE_DISCONNECT_REQUEST: request->filter_code = PW_CODE_DISCONNECT_ACK; break; default: break; } } /* * Keep a copy of the the User-Password attribute. */ if ((vp = pairfind(request->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY)) != NULL) { strlcpy(request->password, vp->vp_strvalue, sizeof(request->password)); /* * Otherwise keep a copy of the CHAP-Password attribute. */ } else if ((vp = pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY)) != NULL) { strlcpy(request->password, vp->vp_strvalue, sizeof(request->password)); } else if ((vp = pairfind(request->packet->vps, PW_MSCHAP_PASSWORD, 0, TAG_ANY)) != NULL) { strlcpy(request->password, vp->vp_strvalue, sizeof(request->password)); } else { request->password[0] = '\0'; } /* * Fix up Digest-Attributes issues */ for (vp = fr_cursor_init(&cursor, &request->packet->vps); vp; vp = fr_cursor_next(&cursor)) { /* * Double quoted strings get marked up as xlat expansions, * but we don't support that in request. */ if (vp->type == VT_XLAT) { vp->vp_strvalue = vp->value.xlat; vp->value.xlat = NULL; vp->type = VT_DATA; } if (!vp->da->vendor) switch (vp->da->attr) { default: break; /* * Allow it to set the packet type in * the attributes read from the file. */ case PW_PACKET_TYPE: request->packet->code = vp->vp_integer; break; case PW_PACKET_DST_PORT: request->packet->dst_port = (vp->vp_integer & 0xffff); break; case PW_PACKET_DST_IP_ADDRESS: request->packet->dst_ipaddr.af = AF_INET; request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr; break; case PW_PACKET_DST_IPV6_ADDRESS: request->packet->dst_ipaddr.af = AF_INET6; request->packet->dst_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr; break; case PW_PACKET_SRC_PORT: request->packet->src_port = (vp->vp_integer & 0xffff); break; case PW_PACKET_SRC_IP_ADDRESS: request->packet->src_ipaddr.af = AF_INET; request->packet->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr; break; case PW_PACKET_SRC_IPV6_ADDRESS: request->packet->src_ipaddr.af = AF_INET6; request->packet->src_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr; break; case PW_DIGEST_REALM: case PW_DIGEST_NONCE: case PW_DIGEST_METHOD: case PW_DIGEST_URI: case PW_DIGEST_QOP: case PW_DIGEST_ALGORITHM: case PW_DIGEST_BODY_DIGEST: case PW_DIGEST_CNONCE: case PW_DIGEST_NONCE_COUNT: case PW_DIGEST_USER_NAME: /* overlapping! */ { DICT_ATTR const *da; uint8_t *p, *q; p = talloc_array(vp, uint8_t, vp->length + 2); memcpy(p + 2, vp->vp_octets, vp->length); p[0] = vp->da->attr - PW_DIGEST_REALM + 1; vp->length += 2; p[1] = vp->length; da = dict_attrbyvalue(PW_DIGEST_ATTRIBUTES, 0); if (!da) { ERROR("Out of memory"); goto error; } vp->da = da; /* * Re-do pairmemsteal ourselves, * because we play games with * vp->da, and pairmemsteal goes * to GREAT lengths to sanitize * and fix and change and * double-check the various * fields. */ memcpy(&q, &vp->vp_octets, sizeof(q)); talloc_free(q); vp->vp_octets = talloc_steal(vp, p); vp->type = VT_DATA; VERIFY_VP(vp); } break; } } /* loop over the VP's we read in */ /* * Automatically set the port if we don't have a global * or packet specific one. */ if ((server_port == 0) && (request->packet->dst_port == 0)) { radclient_get_port(request->packet->code, &request->packet->dst_port); } /* * Add it to the tail of the list. */ if (!request_head) { assert(rc_request_tail == NULL); request_head = request; request->prev = NULL; } else { assert(rc_request_tail->next == NULL); rc_request_tail->next = request; request->prev = rc_request_tail; } rc_request_tail = request; request->next = NULL; } while (!packets_done); /* loop until the file is done. */ if (packets != stdin) fclose(packets); if (filters) fclose(filters); /* * And we're done. */ return 1; error: talloc_free(request); if (packets != stdin) fclose(packets); if (filters) fclose(filters); return 0; }
/** Copy packet to multiple servers * * Create a duplicate of the packet and send it to a list of realms * defined by the presence of the Replicate-To-Realm VP in the control * list of the current request. * * This is pretty hacky and is 100% fire and forget. If you're looking * to forward authentication requests to multiple realms and process * the responses, this function will not allow you to do that. * * @param[in] instance of this module. * @param[in] request The current request. * @param[in] list of attributes to copy to the duplicate packet. * @param[in] code to write into the code field of the duplicate packet. * @return RCODE fail on error, invalid if list does not exist, noop if no replications succeeded, else ok. */ static int replicate_packet(UNUSED void *instance, REQUEST *request, pair_lists_t list, unsigned int code) { int rcode = RLM_MODULE_NOOP; VALUE_PAIR *vp, **vps; vp_cursor_t cursor; home_server_t *home; REALM *realm; home_pool_t *pool; RADIUS_PACKET *packet = NULL; /* * Send as many packets as necessary to different * destinations. */ fr_cursor_init(&cursor, &request->config); while ((vp = fr_cursor_next_by_num(&cursor, PW_REPLICATE_TO_REALM, 0, TAG_ANY))) { realm = realm_find2(vp->vp_strvalue); if (!realm) { REDEBUG2("Cannot Replicate to unknown realm \"%s\"", vp->vp_strvalue); continue; } /* * We shouldn't really do this on every loop. */ switch (request->packet->code) { default: REDEBUG2("Cannot replicate unknown packet code %d", request->packet->code); cleanup(packet); return RLM_MODULE_FAIL; case PW_CODE_ACCESS_REQUEST: pool = realm->auth_pool; break; #ifdef WITH_ACCOUNTING case PW_CODE_ACCOUNTING_REQUEST: pool = realm->acct_pool; break; #endif #ifdef WITH_COA case PW_CODE_COA_REQUEST: case PW_CODE_DISCONNECT_REQUEST: pool = realm->coa_pool; break; #endif } if (!pool) { RWDEBUG2("Cancelling replication to Realm %s, as the realm is local", realm->name); continue; } home = home_server_ldb(realm->name, pool, request); if (!home) { REDEBUG2("Failed to find live home server for realm %s", realm->name); continue; } /* * For replication to multiple servers we re-use the packet * we built here. */ if (!packet) { packet = rad_alloc(request, true); if (!packet) { return RLM_MODULE_FAIL; } packet->code = code; packet->id = fr_rand() & 0xff; packet->sockfd = fr_socket(&home->src_ipaddr, 0); if (packet->sockfd < 0) { REDEBUG("Failed opening socket: %s", fr_strerror()); rcode = RLM_MODULE_FAIL; goto done; } vps = radius_list(request, list); if (!vps) { RWDEBUG("List '%s' doesn't exist for this packet", fr_int2str(pair_lists, list, "<INVALID>")); rcode = RLM_MODULE_INVALID; goto done; } /* * Don't assume the list actually contains any * attributes. */ if (*vps) { packet->vps = fr_pair_list_copy(packet, *vps); if (!packet->vps) { rcode = RLM_MODULE_FAIL; goto done; } } /* * For CHAP, create the CHAP-Challenge if * it doesn't exist. */ if ((code == PW_CODE_ACCESS_REQUEST) && (fr_pair_find_by_num(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY) != NULL) && (fr_pair_find_by_num(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY) == NULL)) { uint8_t *p; vp = radius_pair_create(packet, &packet->vps, PW_CHAP_CHALLENGE, 0); vp->length = AUTH_VECTOR_LEN; vp->vp_octets = p = talloc_array(vp, uint8_t, vp->length); memcpy(p, request->packet->vector, AUTH_VECTOR_LEN); } } else { size_t i; for (i = 0; i < sizeof(packet->vector); i++) { packet->vector[i] = fr_rand() & 0xff; } packet->id++; TALLOC_FREE(packet->data); packet->data_len = 0; } /* * (Re)-Write these. */ packet->dst_ipaddr = home->ipaddr; packet->dst_port = home->port; memset(&packet->src_ipaddr, 0, sizeof(packet->src_ipaddr)); packet->src_port = 0; /* * Encode, sign and then send the packet. */ RDEBUG("Replicating list '%s' to Realm '%s'", fr_int2str(pair_lists, list, "<INVALID>"), realm->name); if (rad_send(packet, NULL, home->secret) < 0) { REDEBUG("Failed replicating packet: %s", fr_strerror()); rcode = RLM_MODULE_FAIL; goto done; } /* * We've sent it to at least one destination. */ rcode = RLM_MODULE_OK; } done: cleanup(packet); return rcode; }