static int eap_aka_compose(eap_session_t *eap_session) { eap_aka_session_t *eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t); fr_cursor_t cursor; fr_cursor_t to_encode; VALUE_PAIR *head = NULL, *vp; REQUEST *request = eap_session->request; ssize_t ret; fr_sim_encode_ctx_t encoder_ctx = { .root = fr_dict_root(dict_eap_aka), .keys = &eap_aka_session->keys, .iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, .iv_included = false, .hmac_md = eap_aka_session->mac_md, .eap_packet = eap_session->this_round->request, .hmac_extra = NULL, .hmac_extra_len = 0 }; fr_cursor_init(&cursor, &eap_session->request->reply->vps); fr_cursor_init(&to_encode, &head); while ((vp = fr_cursor_current(&cursor))) { if (!fr_dict_parent_common(encoder_ctx.root, vp->da, true)) { fr_cursor_next(&cursor); continue; } vp = fr_cursor_remove(&cursor); /* * Silently discard encrypted attributes until * the peer should have k_encr. These can be * added by policy, and seem to cause * wpa_supplicant to fail if sent before the challenge. */ if (!eap_aka_session->allow_encrypted && fr_dict_parent_common(attr_eap_aka_encr_data, vp->da, true)) { RWDEBUG("Silently discarding &reply:%s: Encrypted attributes not allowed in this round", vp->da->name); talloc_free(vp); continue; } fr_cursor_append(&to_encode, vp); } RDEBUG2("Encoding EAP-AKA attributes"); log_request_pair_list(L_DBG_LVL_2, request, head, NULL); eap_session->this_round->request->type.num = eap_aka_session->type; eap_session->this_round->request->id = eap_aka_session->aka_id++ & 0xff; eap_session->this_round->set_request_id = true; ret = fr_sim_encode(eap_session->request, head, &encoder_ctx); fr_cursor_head(&to_encode); fr_cursor_free_list(&to_encode); if (ret < 0) { RPEDEBUG("Failed encoding EAP-AKA data"); return -1; } return 0; } /** Send an EAP-AKA identity request to the supplicant * * There are three types of user identities that can be implemented * - Permanent identities such as [email protected] * Permanent identities can be identified by the leading zero followed by * by 15 digits (the IMSI number). * - Ephemeral identities (pseudonyms). These are identities assigned for * identity privacy so the user can't be tracked. These can identities * can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses' * document section 14, where a set of up to 16 encryption keys are used * to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely * randomised and stored in a datastore. * - A fast resumption ID which resolves to data used for fast resumption. * * In order to perform full authentication the original IMSI is required for * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym, * or can't perform fast resumption, we need to request the full identity from * the supplicant. * * @param[in] eap_session to continue. * @return * - 0 on success. * - <0 on failure. */ static int eap_aka_send_identity_request(eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_aka_session_t *eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t); VALUE_PAIR *vp; RADIUS_PACKET *packet; fr_cursor_t cursor; RDEBUG2("Sending AKA-Identity (%s)", fr_int2str(sim_id_request_table, eap_aka_session->id_req, "<INVALID>")); eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; eap_aka_session->allow_encrypted = false; /* In case this is after failed fast-resumption */ packet = request->reply; fr_cursor_init(&cursor, &packet->vps); /* * Set the subtype to identity request */ vp = fr_pair_afrom_da(packet, attr_eap_aka_subtype); vp->vp_uint16 = FR_EAP_AKA_SUBTYPE_VALUE_AKA_IDENTITY; fr_cursor_append(&cursor, vp); /* * Select the right type of identity request attribute */ switch (eap_aka_session->id_req) { case SIM_ANY_ID_REQ: vp = fr_pair_afrom_da(packet, attr_eap_aka_any_id_req); break; case SIM_PERMANENT_ID_REQ: vp = fr_pair_afrom_da(packet, attr_eap_aka_permanent_id_req); break; case SIM_FULLAUTH_ID_REQ: vp = fr_pair_afrom_da(packet, attr_eap_aka_fullauth_id_req); break; default: rad_assert(0); } vp->vp_bool = true; fr_cursor_append(&cursor, vp); /* * Encode the packet */ if (eap_aka_compose(eap_session) < 0) { failure: fr_pair_list_free(&packet->vps); return -1; } /* * Digest the packet contents, updating our checkcode. */ if (!eap_aka_session->checkcode_state && fr_sim_crypto_init_checkcode(eap_aka_session, &eap_aka_session->checkcode_state, eap_aka_session->checkcode_md) < 0) { RPEDEBUG("Failed initialising checkcode"); goto failure; } if (fr_sim_crypto_update_checkcode(eap_aka_session->checkcode_state, eap_session->this_round->request) < 0) { RPEDEBUG("Failed updating checkcode"); goto failure; } return 0; }
/* * (foo == bar), with nested conditionals. */ static int parse_condition(policy_lex_file_t *lexer, policy_item_t **tail) { int rcode, seen_not = FALSE; policy_lex_t token, compare; char lhs[256], rhs[256]; policy_condition_t *this; token = policy_lex_file(lexer, 0, lhs, sizeof(lhs)); if (token != POLICY_LEX_L_BRACKET) { fprintf(stderr, "%s[%d]: Expected '(', got \"%s\"\n", lexer->filename, lexer->lineno, fr_int2str(rlm_policy_tokens, token, lhs)); return 0; } this = rad_malloc(sizeof(*this)); memset(this, 0, sizeof(*this)); this->item.type = POLICY_TYPE_CONDITIONAL; this->item.lineno = lexer->lineno; redo: token = policy_lex_file(lexer, 0, lhs, sizeof(lhs)); switch (token) { case POLICY_LEX_L_BRACKET: if (!policy_lex_push_token(lexer, token)) { rlm_policy_free_item((policy_item_t *) this); return 0; } this->compare = POLICY_LEX_L_BRACKET; this->child_condition = POLICY_LEX_L_BRACKET; rcode = parse_condition(lexer, &(this->child)); if (!rcode) { rlm_policy_free_item((policy_item_t *) this); return rcode; } break; case POLICY_LEX_L_NOT: if (seen_not) { fprintf(stderr, "%s[%d]: Syntax error at \"!!\"\n", lexer->filename, lexer->lineno); rlm_policy_free_item((policy_item_t *) this); return 0; } debug_tokens("[NOT] "); token = policy_lex_file(lexer, POLICY_LEX_FLAG_PEEK, NULL, 0); if (token != POLICY_LEX_L_BRACKET) { seen_not = this->sense = 1; goto redo; } this->compare = POLICY_LEX_L_NOT; rcode = parse_condition(lexer, &(this->child)); if (!rcode) { rlm_policy_free_item((policy_item_t *) this); return rcode; } break; case POLICY_LEX_BARE_WORD: this->lhs_type = token; token = policy_lex_file(lexer, POLICY_LEX_FLAG_PEEK, NULL, 0); if (token == POLICY_LEX_L_BRACKET) { debug_tokens("[IF-CALL %s] ", lhs); /* * Function call. */ if (rlm_policy_find(lexer->policies, lhs) == NULL) { fprintf(stderr, "%s[%d]: Undefined function \"%s\"\n", lexer->filename, lexer->lineno, lhs); rlm_policy_free_item((policy_item_t *) this); return 0; } /* * this->lhs set up below, after "check" */ this->lhs_type = POLICY_LEX_FUNCTION; /* * Copied from parse_call */ token = policy_lex_file(lexer, 0, NULL, 0); if (token != POLICY_LEX_L_BRACKET) { fprintf(stderr, "%s[%d]: Expected left bracket, got \"%s\"\n", lexer->filename, lexer->lineno, fr_int2str(rlm_policy_tokens, token, "?")); rlm_policy_free_item((policy_item_t *) this); return 0; } token = policy_lex_file(lexer, 0, NULL, 0); if (token != POLICY_LEX_R_BRACKET) { fprintf(stderr, "%s[%d]: Expected right bracket, got \"%s\"\n", lexer->filename, lexer->lineno, fr_int2str(rlm_policy_tokens, token, "?")); rlm_policy_free_item((policy_item_t *) this); return 0; } } /* else it's a comparison? */ goto check; case POLICY_LEX_DOUBLE_QUOTED_STRING: this->lhs_type = token; /* * Got word. May just be test for existence. */ check: token = policy_lex_file(lexer, POLICY_LEX_FLAG_PEEK, NULL, 0); if (token == POLICY_LEX_R_BRACKET) { debug_tokens("[TEST %s] ", lhs); this->lhs = strdup(lhs); this->compare = POLICY_LEX_CMP_TRUE; break; } compare = policy_lex_file(lexer, 0, rhs, sizeof(rhs)); switch (compare) { case POLICY_LEX_CMP_EQUALS: case POLICY_LEX_CMP_NOT_EQUALS: case POLICY_LEX_RX_EQUALS: case POLICY_LEX_RX_NOT_EQUALS: case POLICY_LEX_CMP_TRUE: case POLICY_LEX_CMP_FALSE: case POLICY_LEX_LT: case POLICY_LEX_GT: case POLICY_LEX_LE: case POLICY_LEX_GE: break; default: fprintf(stderr, "%s[%d]: Invalid operator \"%s\"\n", lexer->filename, lexer->lineno, fr_int2str(rlm_policy_tokens, compare, rhs)); rlm_policy_free_item((policy_item_t *) this); return 0; } token = policy_lex_file(lexer, 0, rhs, sizeof(rhs)); if ((token != POLICY_LEX_BARE_WORD) && (token != POLICY_LEX_DOUBLE_QUOTED_STRING)) { fprintf(stderr, "%s[%d]: Unexpected rhs token\n", lexer->filename, lexer->lineno); rlm_policy_free_item((policy_item_t *) this); return 0; } debug_tokens("[COMPARE (%s %s %s)] ", lhs, fr_int2str(rlm_policy_tokens, compare, "?"), rhs); this->lhs = strdup(lhs); this->compare = compare; this->rhs_type = token; this->rhs = strdup(rhs); break; default: fprintf(stderr, "%s[%d]: Unexpected lhs token\n", lexer->filename, lexer->lineno); rlm_policy_free_item((policy_item_t *) this); return 0; } token = policy_lex_file(lexer, 0, NULL, 0); if (token != POLICY_LEX_R_BRACKET) { fprintf(stderr, "%s[%d]: Expected ')', got \"%s\"\n", lexer->filename, lexer->lineno, fr_int2str(rlm_policy_tokens, token, "?")); rlm_policy_free_item((policy_item_t *) this); return 0; } /* * After the end of condition, we MAY have && or || */ token = policy_lex_file(lexer, POLICY_LEX_FLAG_PEEK, NULL, 0); if ((token == POLICY_LEX_L_AND) || (token == POLICY_LEX_L_OR)) { token = policy_lex_file(lexer, 0, NULL, 0); /* skip over it */ debug_tokens("[%s] ", fr_int2str(rlm_policy_tokens, token, "?")); this->child_condition = token; rcode = parse_condition(lexer, &(this->child)); if (!rcode) { rlm_policy_free_item((policy_item_t *) this); return 0; } } *tail = (policy_item_t *) this; return 1; }
/* * Log the message to the logfile. Include the severity and * a time stamp. */ int vradlog(log_type_t type, char const *fmt, va_list ap) { unsigned char *p; char buffer[10240]; /* The largest config item size, then extra for prefixes and suffixes */ char *unsan; size_t len; int colourise = default_log.colourise; /* * NOT debugging, and trying to log debug messages. * * Throw the message away. */ if (!debug_flag && ((type & L_DBG) != 0)) { return 0; } /* * If we don't want any messages, then * throw them away. */ if (default_log.dst == L_DST_NULL) { return 0; } buffer[0] = '\0'; len = 0; if (colourise) { len += strlcpy(buffer + len, fr_int2str(colours, type, ""), sizeof(buffer) - len) ; if (len == 0) { colourise = false; } } /* * Mark the point where we treat the buffer as unsanitized. */ unsan = buffer + len; /* * Don't print timestamps to syslog, it does that for us. * Don't print timestamps and error types for low levels * of debugging. * * Print timestamps for non-debugging, and for high levels * of debugging. */ if (default_log.dst != L_DST_SYSLOG) { if ((debug_flag != 1) && (debug_flag != 2)) { time_t timeval; timeval = time(NULL); CTIME_R(&timeval, buffer + len, sizeof(buffer) - len - 1); len = strlen(buffer); len += strlcpy(buffer + len, fr_int2str(levels, type, ": "), sizeof(buffer) - len); } else goto add_prefix; } else { add_prefix: if (len < sizeof(buffer)) switch (type) { case L_DBG_WARN: len += strlcpy(buffer + len, "WARNING: ", sizeof(buffer) - len); break; case L_DBG_ERR: len += strlcpy(buffer + len, "ERROR: ", sizeof(buffer) - len); break; default: break; } } if (len < sizeof(buffer)) { len += vsnprintf(buffer + len, sizeof(buffer) - len - 1, fmt, ap); } /* * Filter out control chars and non UTF8 chars */ for (p = (unsigned char *)unsan; *p != '\0'; p++) { int clen; switch (*p) { case '\r': case '\n': *p = ' '; break; case '\t': continue; default: clen = fr_utf8_char(p); if (!clen) { *p = '?'; continue; } p += (clen - 1); break; } } if (colourise && (len < sizeof(buffer))) { len += strlcpy(buffer + len, VTC_RESET, sizeof(buffer) - len); } if (len < (sizeof(buffer) - 2)) { buffer[len] = '\n'; buffer[len + 1] = '\0'; } else { buffer[sizeof(buffer) - 2] = '\n'; buffer[sizeof(buffer) - 1] = '\0'; } switch (default_log.dst) { #ifdef HAVE_SYSLOG_H case L_DST_SYSLOG: switch(type) { case L_DBG: case L_WARN: case L_DBG_WARN: case L_DBG_ERR: case L_DBG_ERR_REQ: case L_DBG_WARN_REQ: type = LOG_DEBUG; break; case L_AUTH: case L_PROXY: case L_ACCT: type = LOG_NOTICE; break; case L_INFO: type = LOG_INFO; break; case L_ERR: type = LOG_ERR; break; } syslog(type, "%s", buffer); break; #endif case L_DST_FILES: case L_DST_STDOUT: case L_DST_STDERR: return write(default_log.fd, buffer, strlen(buffer)); default: case L_DST_NULL: /* should have been caught above */ break; } return 0; }
/* * Parse a module statement. */ static int parse_module(policy_lex_file_t *lexer, policy_item_t **tail) { int component; policy_lex_t token; policy_module_t *this; char *p; const char *section_name; char filename[1024]; char buffer[2048]; CONF_SECTION *cs, *subcs; modcallable *mc; /* * And the filename */ token = policy_lex_file(lexer, 0, filename, sizeof(filename)); if (token != POLICY_LEX_DOUBLE_QUOTED_STRING) { fprintf(stderr, "%s[%d]: Expected filename, got \"%s\"\n", lexer->filename, lexer->lineno, fr_int2str(rlm_policy_tokens, token, "?")); return 0; } /* * See if we're including all of the files in a subdirectory. */ strlcpy(buffer, lexer->filename, sizeof(buffer)); p = strrchr(buffer, '/'); if (p) { strlcpy(p + 1, filename, sizeof(buffer) - 1 - (p - buffer)); } else { snprintf(buffer, sizeof(buffer), "%s/%s", radius_dir, filename); } /* * Include section calling a module. */ debug_tokens("including module section from file %s\n", buffer); cs = cf_file_read(buffer); if (!cs) { return 0; /* it prints out error messages */ } /* * The outer section is called "main", and can be ignored. * It should be a section, so there should be a subsection. */ subcs = cf_subsection_find_next(cs, NULL, NULL); if (!subcs) { fprintf(stderr, "%s[%d]: Expected section containing modules\n", lexer->filename, lexer->lineno); cf_section_free(&cs); return 0; } section_name = cf_section_name1(subcs); rad_assert(section_name != NULL); component = fr_str2int(policy_component_names, section_name, RLM_COMPONENT_COUNT); if (component == RLM_COMPONENT_COUNT) { fprintf(stderr, "%s[%d]: Invalid section name \"%s\"\n", lexer->filename, lexer->lineno, section_name); cf_section_free(&cs); return 0; } /* * Compile the module entry. */ mc = compile_modgroup(NULL, component, subcs); if (!mc) { cf_section_free(&cs); return 0; /* more often results in calling exit... */ } this = rad_malloc(sizeof(*this)); memset(this, 0, sizeof(*this)); this->item.type = POLICY_TYPE_MODULE; this->item.lineno = lexer->lineno; this->component = component; this->cs = cs; this->mc = mc; *tail = (policy_item_t *) this; return 1; }
/* * Parse an "include filename" statement * * FIXME: Tie this file into the CONF_SECTION for HUP handling! */ static int parse_include(policy_lex_file_t *lexer) { char *p; policy_lex_t token; char filename[1024]; char buffer[2048]; token = policy_lex_file(lexer, 0, filename, sizeof(filename)); if (token != POLICY_LEX_DOUBLE_QUOTED_STRING) { fprintf(stderr, "%s[%d]: Expected filename, got \"%s\"\n", lexer->filename, lexer->lineno, fr_int2str(rlm_policy_tokens, token, "?")); return 0; } /* * See if we're including all of the files in a subdirectory. */ strlcpy(buffer, lexer->filename, sizeof(buffer)); p = strrchr(buffer, '/'); if (p) { strlcpy(p + 1, filename, sizeof(buffer) - 1 - (p - buffer)); #ifdef HAVE_DIRENT_H p = strrchr(p + 1, '/'); if (p && !p[1]) { DIR *dir; struct dirent *dp; p++; dir = opendir(buffer); if (!dir) { fprintf(stderr, "%s[%d]: Error opening %s:%s\n", lexer->filename, lexer->lineno, buffer, strerror(errno)); return 0; } /* * Read the directory, ignoring "." files. */ while ((dp = readdir(dir)) != NULL) { struct stat buf; if (dp->d_name[0] == '.') continue; if (strchr(dp->d_name, '~') != NULL) continue; strlcpy(p, dp->d_name, sizeof(buffer) - (p - buffer)); if ((stat(buffer, &buf) != 0) || S_ISDIR(buf.st_mode)) continue; debug_tokens("\nincluding file %s\n", buffer); if (!rlm_policy_parse(lexer->policies, buffer)) { closedir(dir); return 0; } } closedir(dir); return 1; } /* else it must have been a normalx file */ #endif } else { snprintf(buffer, sizeof(buffer), "%s/%s", radius_dir, filename); } /* * Handle one include file. */ debug_tokens("\nincluding file %s\n", buffer); if (!rlm_policy_parse(lexer->policies, buffer)) { return 0; } return 1; }
/** 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; }
/* * Simple xlat to read text data from a URL */ static ssize_t rest_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace) { rlm_rest_t *inst = instance; void *handle; int hcode; int ret; ssize_t len, outlen = 0; char *uri = NULL; char const *p = fmt, *q; char const *body; http_method_t method; /* There are no configurable parameters other than the URI */ rlm_rest_section_t section = { .name = "xlat", .method = HTTP_METHOD_GET, .body = HTTP_BODY_NONE, .body_str = "application/x-www-form-urlencoded", .require_auth = false, .force_to = HTTP_BODY_PLAIN }; *out = '\0'; rad_assert(fmt); RDEBUG("Expanding URI components"); handle = fr_connection_get(inst->pool); if (!handle) return -1; /* * Extract the method from the start of the format string (if there is one) */ method = fr_substr2int(http_method_table, p, HTTP_METHOD_UNKNOWN, -1); if (method != HTTP_METHOD_UNKNOWN) { section.method = method; p += strlen(http_method_table[method].name); } /* * Trim whitespace */ while (isspace(*p) && p++); /* * Unescape parts of xlat'd URI, this allows REST servers to be specified by * request attributes. */ len = rest_uri_host_unescape(&uri, instance, request, handle, p); if (len <= 0) { outlen = -1; goto finish; } /* * Extract freeform body data (url can't contain spaces) */ q = strchr(p, ' '); if (q && (*++q != '\0')) { section.body = HTTP_BODY_CUSTOM_LITERAL; section.data = q; } RDEBUG("Sending HTTP %s to \"%s\"", fr_int2str(http_method_table, section.method, NULL), uri); /* * Configure various CURL options, and initialise the read/write * context data. * * @todo We could extract the User-Name and password from the URL string. */ ret = rest_request_config(instance, §ion, request, handle, section.method, section.body, uri, NULL, NULL); talloc_free(uri); if (ret < 0) { outlen = -1; goto finish; } /* * Send the CURL request, pre-parse headers, aggregate incoming * HTTP body data into a single contiguous buffer. */ ret = rest_request_perform(instance, §ion, request, handle); if (ret < 0) { outlen = -1; goto finish; } hcode = rest_get_handle_code(handle); switch (hcode) { case 404: case 410: case 403: case 401: { outlen = -1; error: rest_response_error(request, handle); goto finish; } case 204: goto finish; default: /* * Attempt to parse content if there was any. */ if ((hcode >= 200) && (hcode < 300)) { break; } else if (hcode < 500) { outlen = -2; goto error; } else { outlen = -1; goto error; } } len = rest_get_handle_data(&body, handle); if ((size_t) len >= freespace) { REDEBUG("Insufficient space to write HTTP response, needed %zu bytes, have %zu bytes", len + 1, freespace); outlen = -1; goto finish; } if (len > 0) { outlen = len; strlcpy(out, body, len + 1); /* strlcpy takes the size of the buffer */ } finish: rlm_rest_cleanup(instance, §ion, handle); fr_connection_release(inst->pool, handle); return outlen; } /* * 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_authorize(void *instance, REQUEST *request) { rlm_rest_t *inst = instance; rlm_rest_section_t *section = &inst->authorize; void *handle; int hcode; int rcode = RLM_MODULE_OK; int ret; if (!section->name) return RLM_MODULE_NOOP; handle = fr_connection_get(inst->pool); if (!handle) return RLM_MODULE_FAIL; ret = rlm_rest_perform(instance, section, handle, request, NULL, NULL); if (ret < 0) { rcode = RLM_MODULE_FAIL; goto finish; } hcode = rest_get_handle_code(handle); switch (hcode) { case 404: case 410: rcode = RLM_MODULE_NOTFOUND; break; case 403: rcode = RLM_MODULE_USERLOCK; break; case 401: /* * Attempt to parse content if there was any. */ ret = rest_response_decode(inst, section, request, handle); if (ret < 0) { rcode = RLM_MODULE_FAIL; break; } rcode = RLM_MODULE_REJECT; break; case 204: rcode = RLM_MODULE_OK; break; default: /* * Attempt to parse content if there was any. */ if ((hcode >= 200) && (hcode < 300)) { ret = rest_response_decode(inst, section, request, handle); if (ret < 0) rcode = RLM_MODULE_FAIL; else if (ret == 0) rcode = RLM_MODULE_OK; else rcode = RLM_MODULE_UPDATED; break; } else if (hcode < 500) { rcode = RLM_MODULE_INVALID; } else { rcode = RLM_MODULE_FAIL; } } finish: switch (rcode) { case RLM_MODULE_INVALID: case RLM_MODULE_FAIL: case RLM_MODULE_USERLOCK: rest_response_error(request, handle); break; default: break; } rlm_rest_cleanup(instance, section, handle); fr_connection_release(inst->pool, handle); return rcode; }
/** Convert CONFIG_PAIR (which may contain refs) to value_pair_map_t. * * Treats the left operand as an attribute reference * @verbatim<request>.<list>.<attribute>@endverbatim * * Treatment of left operand depends on quotation, barewords are treated as * attribute references, double quoted values are treated as expandable strings, * single quoted values are treated as literal strings. * * Return must be freed with talloc_free * * @param[in] ctx for talloc * @param[in] cp to convert to map. * @param[in] dst_request_def The default request to insert unqualified * attributes into. * @param[in] dst_list_def The default list to insert unqualified attributes * into. * @param[in] src_request_def The default request to resolve attribute * references in. * @param[in] src_list_def The default list to resolve unqualified attributes * in. * @return value_pair_map_t if successful or NULL on error. */ value_pair_map_t *radius_cp2map(TALLOC_CTX *ctx, CONF_PAIR *cp, request_refs_t dst_request_def, pair_lists_t dst_list_def, request_refs_t src_request_def, pair_lists_t src_list_def) { value_pair_map_t *map; char const *attr; char const *value; FR_TOKEN type; CONF_ITEM *ci = cf_pairtoitem(cp); if (!cp) return NULL; map = talloc_zero(ctx, value_pair_map_t); attr = cf_pair_attr(cp); value = cf_pair_value(cp); if (!value) { cf_log_err(ci, "Missing attribute value"); goto error; } map->dst = radius_attr2tmpl(map, attr, dst_request_def, dst_list_def); if (!map->dst) { cf_log_err(ci, "Syntax error in attribute definition"); goto error; } /* * Bare words always mean attribute references. */ type = cf_pair_value_type(cp); if (type == T_BARE_WORD) { if (*value == '&') { map->src = radius_attr2tmpl(map, value + 1, src_request_def, src_list_def); } else { if (!isdigit((int) *value) && ((strchr(value, ':') != NULL) || (dict_attrbyname(value) != NULL))) { map->src = radius_attr2tmpl(map, value, src_request_def, src_list_def); } if (map->src) { WDEBUG("%s[%d]: Please add '&' for attribute reference '%s = &%s'", cf_pair_filename(cp), cf_pair_lineno(cp), attr, value); } else { map->src = radius_str2tmpl(map, value, type); } } } else { map->src = radius_str2tmpl(map, value, type); } if (!map->src) { goto error; } map->op = cf_pair_operator(cp); map->ci = ci; /* * Lots of sanity checks for insane people... */ /* * We don't support implicit type conversion, * except for "octets" */ if (map->dst->da && map->src->da && (map->src->da->type != map->dst->da->type) && (map->src->da->type != PW_TYPE_OCTETS) && (map->dst->da->type != PW_TYPE_OCTETS)) { cf_log_err(ci, "Attribute type mismatch"); goto error; } /* * What exactly where you expecting to happen here? */ if ((map->dst->type == VPT_TYPE_ATTR) && (map->src->type == VPT_TYPE_LIST)) { cf_log_err(ci, "Can't copy list into an attribute"); goto error; } /* * Can't copy an xlat expansion or literal into a list, * we don't know what type of attribute we'd need * to create */ if ((map->dst->type == VPT_TYPE_LIST) && ((map->src->type == VPT_TYPE_XLAT) || (map->src->type == VPT_TYPE_LITERAL))) { cf_log_err(ci, "Can't copy value into list (we don't know which attribute to create)"); goto error; } /* * Depending on the attribute type, some operators are * disallowed. */ if (map->dst->type == VPT_TYPE_ATTR) { if ((map->op != T_OP_EQ) && (map->op != T_OP_CMP_EQ) && (map->op != T_OP_ADD) && (map->op != T_OP_SUB) && (map->op != T_OP_LE) && (map->op != T_OP_GE) && (map->op != T_OP_CMP_FALSE) && (map->op != T_OP_SET)) { cf_log_err(ci, "Invalid operator for attribute"); goto error; } } switch (map->src->type) { /* * Only += and -= operators are supported for list copy. */ case VPT_TYPE_LIST: switch (map->op) { case T_OP_SUB: case T_OP_ADD: break; default: cf_log_err(ci, "Operator \"%s\" not allowed " "for list copy", fr_int2str(fr_tokens, map->op, "<INVALID>")); goto error; } break; default: break; } return map; error: talloc_free(map); return NULL; }
/** Print a template to a string * * @param[out] buffer for the output string * @param[in] bufsize of the buffer * @param[in] vpt to print * @return the size of the string printed */ size_t radius_tmpl2str(char *buffer, size_t bufsize, value_pair_tmpl_t const *vpt) { char c; char const *p; char *q = buffer; char *end; switch (vpt->type) { default: return 0; case VPT_TYPE_REGEX: c = '/'; break; case VPT_TYPE_XLAT: c = '"'; break; case VPT_TYPE_LITERAL: /* single-quoted or bare word */ /* * Hack */ for (p = vpt->name; *p != '\0'; p++) { if (*p == ' ') break; if (*p == '\'') break; if (!dict_attr_allowed_chars[(int) *p]) break; } if (!*p) { strlcpy(buffer, vpt->name, bufsize); return strlen(buffer); } c = '\''; break; case VPT_TYPE_EXEC: c = '`'; break; case VPT_TYPE_ATTR: buffer[0] = '&'; if (vpt->request == REQUEST_CURRENT) { if (vpt->list == PAIR_LIST_REQUEST) { strlcpy(buffer + 1, vpt->da->name, bufsize - 1); } else { snprintf(buffer + 1, bufsize - 1, "%s:%s", fr_int2str(pair_lists, vpt->list, ""), vpt->da->name); } } else { snprintf(buffer + 1, bufsize - 1, "%s.%s:%s", fr_int2str(request_refs, vpt->request, ""), fr_int2str(pair_lists, vpt->list, ""), vpt->da->name); } return strlen(buffer); case VPT_TYPE_DATA: { VALUE_PAIR *vp; TALLOC_CTX *ctx; memcpy(&ctx, &vpt, sizeof(ctx)); /* hack */ vp = pairalloc(ctx, vpt->da); memcpy(&vp->data, vpt->vpd, sizeof(vp->data)); vp->length = vpt->length; q = vp_aprint(vp, vp); if ((vpt->da->type != PW_TYPE_STRING) && (vpt->da->type != PW_TYPE_DATE)) { strlcpy(buffer, q, bufsize); } else { /* * FIXME: properly escape the string... */ snprintf(buffer, bufsize, "\"%s\"", q); } talloc_free(q); pairfree(&vp); return strlen(buffer); } } if (bufsize <= 3) { no_room: *buffer = '\0'; return 0; } p = vpt->name; *(q++) = c; end = buffer + bufsize - 3; /* quotes + EOS */ while (*p && (q < end)) { if (*p == c) { if ((q - end) < 4) goto no_room; /* escape, char, quote, EOS */ *(q++) = '\\'; *(q++) = *(p++); continue; } switch (*p) { case '\\': if ((q - end) < 4) goto no_room; *(q++) = '\\'; *(q++) = *(p++); break; case '\r': if ((q - end) < 4) goto no_room; *(q++) = '\\'; *(q++) = 'r'; p++; break; case '\n': if ((q - end) < 4) goto no_room; *(q++) = '\\'; *(q++) = 'r'; p++; break; case '\t': if ((q - end) < 4) goto no_room; *(q++) = '\\'; *(q++) = 't'; p++; break; default: *(q++) = *(p++); break; } } *(q++) = c; *q = '\0'; return q - buffer; }
/** Compare two pair lists except for the password information. * * For every element in "check" at least one matching copy must be present * in "reply". * * @param[in] request Current request. * @param[in] req_list request valuepairs. * @param[in] check Check/control valuepairs. * @param[in,out] rep_list Reply value pairs. * * @return 0 on match. */ int paircompare(REQUEST *request, VALUE_PAIR *req_list, VALUE_PAIR *check, VALUE_PAIR **rep_list) { vp_cursor_t cursor; VALUE_PAIR *check_item; VALUE_PAIR *auth_item; DICT_ATTR const *from; int result = 0; int compare; bool first_only; for (check_item = fr_cursor_init(&cursor, &check); check_item; check_item = fr_cursor_next(&cursor)) { /* * If the user is setting a configuration value, * then don't bother comparing it to any attributes * sent to us by the user. It ALWAYS matches. */ if ((check_item->op == T_OP_SET) || (check_item->op == T_OP_ADD)) { continue; } if (!check_item->da->vendor) switch (check_item->da->attr) { /* * Attributes we skip during comparison. * These are "server" check items. */ case PW_CRYPT_PASSWORD: case PW_AUTH_TYPE: case PW_AUTZ_TYPE: case PW_ACCT_TYPE: case PW_SESSION_TYPE: case PW_STRIP_USER_NAME: continue; break; /* * IF the password attribute exists, THEN * we can do comparisons against it. If not, * then the request did NOT contain a * User-Password attribute, so we CANNOT do * comparisons against it. * * This hack makes CHAP-Password work.. */ case PW_USER_PASSWORD: if (check_item->op == T_OP_CMP_EQ) { WDEBUG("Found User-Password == \"...\"."); WDEBUG("Are you sure you don't mean Cleartext-Password?"); WDEBUG("See \"man rlm_pap\" for more information"); } if (pairfind(req_list, PW_USER_PASSWORD, 0, TAG_ANY) == NULL) { continue; } break; } /* * See if this item is present in the request. */ first_only = otherattr(check_item->da, &from); auth_item = req_list; try_again: if (!first_only) { while (auth_item != NULL) { if ((auth_item->da == from) || (!from)) { break; } auth_item = auth_item->next; } } /* * Not found, it's not a match. */ if (auth_item == NULL) { /* * Didn't find it. If we were *trying* * to not find it, then we succeeded. */ if (check_item->op == T_OP_CMP_FALSE) { continue; } else { return -1; } } /* * Else we found it, but we were trying to not * find it, so we failed. */ if (check_item->op == T_OP_CMP_FALSE) { return -1; } /* * We've got to xlat the string before doing * the comparison. */ radius_xlat_do(request, check_item); /* * OK it is present now compare them. */ compare = radius_callback_compare(request, auth_item, check_item, check, rep_list); switch (check_item->op) { case T_OP_EQ: default: RWDEBUG("Invalid operator '%s' for item %s: reverting to '=='", fr_int2str(fr_tokens, check_item->op, "<INVALID>"), check_item->da->name); /* FALL-THROUGH */ case T_OP_CMP_TRUE: case T_OP_CMP_FALSE: case T_OP_CMP_EQ: if (compare != 0) result = -1; break; case T_OP_NE: if (compare == 0) result = -1; break; case T_OP_LT: if (compare >= 0) result = -1; break; case T_OP_GT: if (compare <= 0) result = -1; break; case T_OP_LE: if (compare > 0) result = -1; break; case T_OP_GE: if (compare < 0) result = -1; break; #ifdef HAVE_REGEX_H case T_OP_REG_EQ: case T_OP_REG_NE: if (compare != 0) result = -1; break; #endif } /* switch over the operator of the check item */ /* * This attribute didn't match, but maybe there's * another of the same attribute, which DOES match. */ if ((result != 0) && (!first_only)) { auth_item = auth_item->next; result = 0; goto try_again; } } /* for every entry in the check item list */ return result; }
/* * Debug print a map / VP */ static void debug_map(REQUEST *request, value_pair_map_t const *map, VALUE_PAIR const *vp) { char *value; char buffer[1024]; switch (map->src->type) { /* * Just print the value being assigned */ default: case VPT_TYPE_LITERAL: vp_prints_value(buffer, sizeof(buffer), vp, '\''); value = buffer; break; case VPT_TYPE_XLAT: case VPT_TYPE_XLAT_STRUCT: vp_prints_value(buffer, sizeof(buffer), vp, '"'); value = buffer; break; case VPT_TYPE_DATA: vp_prints_value(buffer, sizeof(buffer), vp, 0); value = buffer; break; /* * Just printing the value doesn't make sense, but we still * want to know what it was... */ case VPT_TYPE_LIST: vp_prints_value(buffer, sizeof(buffer), vp, '\''); value = talloc_typed_asprintf(request, "&%s%s -> %s", map->src->name, vp->da->name, buffer); break; case VPT_TYPE_ATTR: vp_prints_value(buffer, sizeof(buffer), vp, '\''); value = talloc_typed_asprintf(request, "&%s -> %s", map->src->name, buffer); break; } switch (map->dst->type) { case VPT_TYPE_LIST: RDEBUG("\t%s%s %s %s", map->dst->name, vp->da->name, fr_int2str(fr_tokens, vp->op, "<INVALID>"), value); break; case VPT_TYPE_ATTR: if (vp->da->type != PW_TYPE_STRING) { RDEBUG("\t%s %s %s", map->dst->name, fr_int2str(fr_tokens, vp->op, "<INVALID>"), value); } else { RDEBUG("\t%s %s '%s'", map->dst->name, fr_int2str(fr_tokens, vp->op, "<INVALID>"), value); } break; default: break; } if (value != buffer) talloc_free(value); }
/** 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(void *instance, REQUEST *request, pair_lists_t list, unsigned int code) { int rcode = RLM_MODULE_NOOP; VALUE_PAIR *vp, **vps, *last; home_server *home; REALM *realm; home_pool_t *pool; RADIUS_PACKET *packet = NULL; instance = instance; /* -Wunused */ last = request->config_items; /* * Send as many packets as necessary to different * destinations. */ while (1) { vp = pairfind(last, PW_REPLICATE_TO_REALM, 0, TAG_ANY); if (!vp) break; last = vp->next; realm = realm_find2(vp->vp_strvalue); if (!realm) { RDEBUG2("ERROR: Cannot Replicate to unknown realm %s", realm); continue; } /* * We shouldn't really do this on every loop. */ switch (request->packet->code) { default: RDEBUG2("ERROR: Cannot replicate unknown packet code %d", request->packet->code); cleanup(packet); return RLM_MODULE_FAIL; case PW_AUTHENTICATION_REQUEST: pool = realm->auth_pool; break; #ifdef WITH_ACCOUNTING case PW_ACCOUNTING_REQUEST: pool = realm->acct_pool; break; #endif #ifdef WITH_COA case PW_COA_REQUEST: case PW_DISCONNECT_REQUEST: pool = realm->acct_pool; break; #endif } if (!pool) { RDEBUG2(" WARNING: Cancelling replication to Realm %s, as the realm is local.", realm->name); continue; } home = home_server_ldb(realm->name, pool, request); if (!home) { RDEBUG2("ERROR: 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(1); if (!packet) return RLM_MODULE_FAIL; packet->sockfd = -1; packet->code = code; packet->id = fr_rand() & 0xff; packet->sockfd = fr_socket(&home->src_ipaddr, 0); if (packet->sockfd < 0) { RDEBUG("ERROR: Failed opening socket: %s", fr_strerror()); rcode = RLM_MODULE_FAIL; goto done; } vps = radius_list(request, list); if (!vps) { RDEBUG("WARNING: List '%s' doesn't exist for " "this packet", fr_int2str(pair_lists, list, "¿unknown?")); rcode = RLM_MODULE_INVALID; goto done; } /* * Don't assume the list actually contains any * attributes. */ if (*vps) { packet->vps = paircopy(*vps); if (!packet->vps) { RDEBUG("ERROR: Out of memory!"); rcode = RLM_MODULE_FAIL; goto done; } } /* * For CHAP, create the CHAP-Challenge if * it doesn't exist. */ if ((code == PW_AUTHENTICATION_REQUEST) && (pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY) != NULL) && (pairfind(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY) == NULL)) { vp = radius_paircreate(request, &packet->vps, PW_CHAP_CHALLENGE, 0, PW_TYPE_OCTETS); vp->length = AUTH_VECTOR_LEN; memcpy(vp->vp_strvalue, 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++; free(packet->data); packet->data = NULL; 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, "¿unknown?"),realm->name); if (rad_send(packet, NULL, home->secret) < 0) { RDEBUG("ERROR: 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; }
/** Query the LDAP directory to check if a group object includes a user object as a member * * @param[in] inst rlm_ldap configuration. * @param[in] request Current request. * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect. * @param[in] check vp containing the group value (name or dn). * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_check_groupobj_dynamic(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn, VALUE_PAIR *check) { ldap_rcode_t status; char base_dn[LDAP_MAX_DN_STR_LEN + 1]; char filter[LDAP_MAX_FILTER_STR_LEN + 1]; char const *dn = base_dn; int ret; rad_assert(inst->groupobj_base_dn); switch (check->op) { case T_OP_CMP_EQ: case T_OP_CMP_FALSE: case T_OP_CMP_TRUE: case T_OP_REG_EQ: case T_OP_REG_NE: break; default: REDEBUG("Operator \"%s\" not allowed for LDAP group comparisons", fr_int2str(fr_tokens, check->op, "<INVALID>")); return 1; } RDEBUG2("Checking for user in group objects"); if (rlm_ldap_is_dn(check->vp_strvalue, check->vp_length)) { char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter }; RINDENT(); ret = rlm_ldap_xlat_filter(request, filters, sizeof(filters) / sizeof(*filters), filter, sizeof(filter)); REXDENT(); if (ret < 0) return RLM_MODULE_INVALID; dn = check->vp_strvalue; } else { char name_filter[LDAP_MAX_FILTER_STR_LEN]; char const *filters[] = { name_filter, inst->groupobj_filter, inst->groupobj_membership_filter }; if (!inst->groupobj_name_attr) { REDEBUG("Told to search for group by name, but missing 'group.name_attribute' " "directive"); return RLM_MODULE_INVALID; } snprintf(name_filter, sizeof(name_filter), "(%s=%s)", inst->groupobj_name_attr, check->vp_strvalue); RINDENT(); ret = rlm_ldap_xlat_filter(request, filters, sizeof(filters) / sizeof(*filters), filter, sizeof(filter)); REXDENT(); if (ret < 0) return RLM_MODULE_INVALID; /* * rlm_ldap_find_user does this, too. Oh well. */ RINDENT(); ret = radius_xlat(base_dn, sizeof(base_dn), request, inst->groupobj_base_dn, rlm_ldap_escape_func, NULL); REXDENT(); if (ret < 0) { REDEBUG("Failed creating base_dn"); return RLM_MODULE_INVALID; } } RINDENT(); status = rlm_ldap_search(inst, request, pconn, dn, inst->groupobj_scope, filter, NULL, NULL); REXDENT(); switch (status) { case LDAP_PROC_SUCCESS: RDEBUG("User found in group object \"%s\"", dn); break; case LDAP_PROC_NO_RESULT: return RLM_MODULE_NOTFOUND; default: return RLM_MODULE_FAIL; } return RLM_MODULE_OK; }
/** Process the Peer's response and advantage the state machine * */ static rlm_rcode_t mod_process(UNUSED void *instance, eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_aka_session_t *eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t); fr_sim_decode_ctx_t ctx = { .keys = &eap_aka_session->keys, }; VALUE_PAIR *vp, *vps, *subtype_vp; fr_cursor_t cursor; eap_aka_subtype_t subtype; int ret; /* * RFC 4187 says we ignore the contents of the * next packet after we send our success notification * and always send a success. */ if (eap_aka_session->state == EAP_AKA_SERVER_SUCCESS_NOTIFICATION) { eap_aka_state_enter(eap_session, EAP_AKA_SERVER_SUCCESS); return RLM_MODULE_HANDLED; } /* vps is the data from the client */ vps = request->packet->vps; fr_cursor_init(&cursor, &request->packet->vps); fr_cursor_tail(&cursor); ret = fr_sim_decode(eap_session->request, &cursor, dict_eap_aka, eap_session->this_round->response->type.data, eap_session->this_round->response->type.length, &ctx); /* * RFC 4187 says we *MUST* notify, not just * send an EAP-Failure in this case where * we cannot decode an EAP-AKA packet. */ if (ret < 0) { RPEDEBUG2("Failed decoding EAP-AKA attributes"); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } vp = fr_cursor_current(&cursor); if (vp && RDEBUG_ENABLED2) { RDEBUG2("EAP-AKA decoded attributes"); log_request_pair_list(L_DBG_LVL_2, request, vp, NULL); } subtype_vp = fr_pair_find_by_da(vps, attr_eap_aka_subtype, TAG_ANY); if (!subtype_vp) { REDEBUG("Missing EAP-AKA-Subtype"); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } subtype = subtype_vp->vp_uint16; switch (eap_aka_session->state) { /* * Here we expected the peer to send * us identities for validation. */ case EAP_AKA_SERVER_IDENTITY: switch (subtype) { case EAP_AKA_IDENTITY: if (process_eap_aka_identity(eap_session, vps) == 0) return RLM_MODULE_HANDLED; eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ /* * Case 1 where we're allowed to send an EAP-Failure * * This can happen in the case of a conservative * peer, where it refuses to provide the permanent * identity. */ case EAP_AKA_CLIENT_ERROR: { char buff[20]; vp = fr_pair_find_by_da(vps, attr_eap_aka_client_error_code, TAG_ANY); if (!vp) { REDEBUG("EAP-AKA Peer rejected AKA-Identity (%s) with client-error message but " "has not supplied a client error code", fr_int2str(sim_id_request_table, eap_aka_session->id_req, "<INVALID>")); } else { REDEBUG("Client rejected AKA-Identity (%s) with error: %s (%i)", fr_int2str(sim_id_request_table, eap_aka_session->id_req, "<INVALID>"), fr_pair_value_enum(vp, buff), vp->vp_uint16); } eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE); return RLM_MODULE_REJECT; } case EAP_AKA_NOTIFICATION: notification: { char buff[20]; vp = fr_pair_afrom_da(vps, attr_eap_aka_notification); if (!vp) { REDEBUG2("Received AKA-Notification with no notification code"); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } /* * Case 3 where we're allowed to send an EAP-Failure */ if (!(vp->vp_uint16 & 0x8000)) { REDEBUG2("AKA-Notification %s (%i) indicates failure", fr_pair_value_enum(vp, buff), vp->vp_uint16); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE); return RLM_MODULE_REJECT; } /* * ...if it's not a failure, then re-enter the * current state. */ REDEBUG2("Got AKA-Notification %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16); eap_aka_state_enter(eap_session, eap_aka_session->state); return RLM_MODULE_HANDLED; } default: unexpected_subtype: /* * RFC 4187 says we *MUST* notify, not just * send an EAP-Failure in this case. */ REDEBUG("Unexpected subtype %pV", &subtype_vp->data); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } /* * Process the response to our previous challenge. */ case EAP_AKA_SERVER_CHALLENGE: switch (subtype) { case EAP_AKA_CHALLENGE: switch (process_eap_aka_challenge(eap_session, vps)) { case 1: return RLM_MODULE_HANDLED; case 0: return RLM_MODULE_OK; case -1: eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } case EAP_AKA_SYNCHRONIZATION_FAILURE: REDEBUG("EAP-AKA Peer synchronization failure"); /* We can't handle these yet */ eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ /* * Case 1 where we're allowed to send an EAP-Failure */ case EAP_AKA_CLIENT_ERROR: { char buff[20]; vp = fr_pair_find_by_da(vps, attr_eap_aka_client_error_code, TAG_ANY); if (!vp) { REDEBUG("EAP-AKA Peer rejected AKA-Challenge with client-error message but " "has not supplied a client error code"); } else { REDEBUG("Client rejected AKA-Challenge with error: %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16); } eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE); return RLM_MODULE_REJECT; } /* * Case 2 where we're allowed to send an EAP-Failure */ case EAP_AKA_AUTHENTICATION_REJECT: REDEBUG("EAP-AKA Peer Rejected AUTN"); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE); return RLM_MODULE_REJECT; case EAP_AKA_NOTIFICATION: goto notification; default: goto unexpected_subtype; } /* * Peer acked our failure */ case EAP_AKA_SERVER_FAILURE_NOTIFICATION: switch (subtype) { case EAP_AKA_NOTIFICATION: RDEBUG2("AKA-Notification ACKed, sending EAP-Failure"); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE); return RLM_MODULE_REJECT; default: goto unexpected_subtype; } /* * Something bad happened... */ default: rad_assert(0); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } } /** Initiate the EAP-SIM session by starting the state machine * */ static rlm_rcode_t mod_session_init(void *instance, eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_aka_session_t *eap_aka_session; rlm_eap_aka_t *inst = instance; fr_sim_id_type_t type; fr_sim_method_hint_t method; MEM(eap_aka_session = talloc_zero(eap_session, eap_aka_session_t)); eap_session->opaque = eap_aka_session; /* * Set default configuration, we may allow these * to be toggled by attributes later. */ eap_aka_session->request_identity = inst->request_identity; eap_aka_session->send_result_ind = inst->protected_success; eap_aka_session->id_req = SIM_NO_ID_REQ; /* Set the default */ /* * This value doesn't have be strong, but it is * good if it is different now and then. */ eap_aka_session->aka_id = (fr_rand() & 0xff); /* * Process the identity that we received in the * EAP-Identity-Response and use it to determine * the initial request we send to the Supplicant. */ if (fr_sim_id_type(&type, &method, eap_session->identity, talloc_array_length(eap_session->identity) - 1) < 0) { RPWDEBUG2("Failed parsing identity, continuing anyway"); } /* * Unless AKA-Prime is explicitly disabled, * use it... It has stronger keying, and * binds authentication to the network. */ switch (eap_session->type) { case FR_EAP_AKA_PRIME: default: RDEBUG2("New EAP-AKA' session"); eap_aka_session->type = FR_EAP_AKA_PRIME; eap_aka_session->kdf = FR_EAP_AKA_KDF_VALUE_EAP_AKA_PRIME_WITH_CK_PRIME_IK_PRIME; eap_aka_session->checkcode_md = eap_aka_session->mac_md = EVP_sha256(); eap_aka_session->keys.network = (uint8_t *)talloc_bstrndup(eap_aka_session, inst->network_name, talloc_array_length(inst->network_name) - 1); eap_aka_session->keys.network_len = talloc_array_length(eap_aka_session->keys.network) - 1; switch (method) { default: RWDEBUG("EAP-Identity-Response hints that EAP-%s should be started, but we're " "attempting EAP-AKA'", fr_int2str(sim_id_method_hint_table, method, "<INVALID>")); break; case SIM_METHOD_HINT_AKA_PRIME: case SIM_METHOD_HINT_UNKNOWN: break; } break; case FR_EAP_AKA: RDEBUG2("New EAP-AKA session"); eap_aka_session->type = FR_EAP_AKA; eap_aka_session->kdf = FR_EAP_AKA_KDF_VALUE_EAP_AKA; /* Not actually sent */ eap_aka_session->checkcode_md = eap_aka_session->mac_md = EVP_sha1(); eap_aka_session->send_at_bidding = true; switch (method) { default: RWDEBUG("EAP-Identity-Response hints that EAP-%s should be started, but we're " "attempting EAP-AKA", fr_int2str(sim_id_method_hint_table, method, "<INVALID>")); break; case SIM_METHOD_HINT_AKA: case SIM_METHOD_HINT_UNKNOWN: break; } break; } eap_session->process = mod_process; /* * Admin wants us to always request an identity * initially. The RFC says this is also the * better way to operate, as the supplicant * can 'decorate' the identity in the identity * response. */ if (inst->request_identity) { request_id: /* * We always start by requesting * any ID initially as we can * always negotiate down. */ eap_aka_session->id_req = SIM_ANY_ID_REQ; eap_aka_state_enter(eap_session, EAP_AKA_SERVER_IDENTITY); return RLM_MODULE_HANDLED; } /* * Figure out what type of identity we have * and use it to determine the initial * request we send. */ switch (type) { /* * If there's no valid tag on the identity * then it's probably been decorated by the * supplicant. * * Request the unmolested identity */ case SIM_ID_TYPE_UNKNOWN: RWDEBUG("Identity format unknown, sending Identity request"); goto request_id; /* * These types need to be transformed into something * usable before we can do anything. */ case SIM_ID_TYPE_PSEUDONYM: case SIM_ID_TYPE_FASTAUTH: /* * Permanent ID means we can just send the challenge */ case SIM_ID_TYPE_PERMANENT: eap_aka_session->keys.identity_len = talloc_array_length(eap_session->identity) - 1; MEM(eap_aka_session->keys.identity = talloc_memdup(eap_aka_session, eap_session->identity, eap_aka_session->keys.identity_len)); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_CHALLENGE); return RLM_MODULE_HANDLED; } return RLM_MODULE_HANDLED; }
/** 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; }
/** Run the server state machine * */ static void eap_sim_state_enter(eap_session_t *eap_session, eap_sim_server_state_t new_state) { REQUEST *request = eap_session->request; eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); if (new_state != eap_sim_session->state) { RDEBUG2("Changed state %s -> %s", fr_int2str(sim_state_table, eap_sim_session->state, "<unknown>"), fr_int2str(sim_state_table, new_state, "<unknown>")); eap_sim_session->state = new_state; } else { RDEBUG2("Reentering state %s", fr_int2str(sim_state_table, eap_sim_session->state, "<unknown>")); } switch (new_state) { /* * Send our version list */ case EAP_SIM_SERVER_START: start: if (eap_sim_send_start(eap_session) < 0) { notify_failure: eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return; } break; /* * Send the EAP-SIM Challenge message. */ case EAP_SIM_SERVER_CHALLENGE: if (eap_sim_send_challenge(eap_session) < 0) goto notify_failure; break; case EAP_SIM_SERVER_REAUTHENTICATE: if (eap_sim_send_reauthentication(eap_session) < 0) goto start; break; /* * Sent a protected success notification */ case EAP_SIM_SERVER_SUCCESS_NOTIFICATION: if (eap_sim_send_eap_success_notification(eap_session) < 0) goto notify_failure; break; /* * Send the EAP Success message (we're done) */ case EAP_SIM_SERVER_SUCCESS: if (eap_sim_send_eap_success(eap_session) < 0) goto notify_failure; return; /* * Send a general failure notification */ case EAP_SIM_SERVER_FAILURE_NOTIFICATION: if (eap_sim_send_eap_failure_notification(eap_session) < 0) { /* Fallback to EAP-Failure */ eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); } return; /* * Send an EAP-Failure (we're done) */ case EAP_SIM_SERVER_FAILURE: eap_sim_send_eap_failure(eap_session); return; default: rad_assert(0); /* Invalid transition */ eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return; } }
/** Very simple state machine to process requests * * Unlike normal protocol requests which may have multiple distinct states, * we really only have REQUEST_INIT and REQUEST_RECV phases. * * Conversion of LDAPMessage to VALUE_PAIR structs is done in the listener * because we cannot easily duplicate the LDAPMessage to send it across to * the worker for parsing. * * Most LDAP directories can only handle between 2000-5000 modifications a second * so we're unlikely to be I/O or CPU bound using this division of responsibilities. * * @param[in] request to process. * @param[in] action If something has signalled that the request should stop * being processed. */ static void request_running(REQUEST *request, fr_state_signal_t action) { CONF_SECTION *unlang; char const *verb; char const *state; rlm_rcode_t rcode = RLM_MODULE_FAIL; REQUEST_VERIFY(request); /* * Async (in the same thread, tho) signal to be done. */ if (action == FR_SIGNAL_CANCEL) goto done; /* * We ignore all other actions. */ if (action != FR_SIGNAL_RUN) return; switch (request->request_state) { case REQUEST_INIT: if (RDEBUG_ENABLED) proto_ldap_packet_debug(request, request->packet, true); log_request_proto_pair_list(L_DBG_LVL_1, request, request->packet->vps, ""); request->server_cs = request->listener->server_cs; request->component = "ldap"; switch (request->packet->code) { case LDAP_SYNC_CODE_PRESENT: verb = "recv"; state = "Present"; break; case LDAP_SYNC_CODE_ADD: verb = "recv"; state = "Add"; break; case LDAP_SYNC_CODE_MODIFY: verb = "recv"; state = "Modify"; break; case LDAP_SYNC_CODE_DELETE: verb = "recv"; state = "Delete"; break; case LDAP_SYNC_CODE_COOKIE_LOAD: verb = "load"; state = "Cookie"; break; case LDAP_SYNC_CODE_COOKIE_STORE: verb = "store"; state = "Cookie"; break; default: rad_assert(0); return; } unlang = cf_section_find(request->server_cs, verb, state); if (!unlang) unlang = cf_section_find(request->server_cs, "recv", "*"); if (!unlang) { RDEBUG2("Ignoring %s operation. Add \"%s %s {}\" to virtual-server \"%s\"" " to handle", fr_int2str(ldap_sync_code_table, request->packet->code, "<INVALID>"), verb, state, cf_section_name2(request->server_cs)); rcode = RLM_MODULE_NOOP; goto done; } RDEBUG("Running '%s %s' from file %s", cf_section_name1(unlang), cf_section_name2(unlang), 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) goto done; if (rcode == RLM_MODULE_YIELD) return; /* FALL-THROUGH */ default: done: switch (rcode) { case RLM_MODULE_UPDATED: case RLM_MODULE_OK: { } default: break; } rad_assert(request->log.unlang_indent == 0); //request_delete(request); break; } }
/** Authenticate a previously sent challenge * */ static rlm_rcode_t mod_process(UNUSED void *instance, eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); fr_sim_decode_ctx_t ctx = { .keys = &eap_sim_session->keys, }; VALUE_PAIR *subtype_vp, *from_peer, *vp; fr_cursor_t cursor; eap_sim_subtype_t subtype; int ret; /* * VPS is the data from the client */ from_peer = eap_session->request->packet->vps; fr_cursor_init(&cursor, &request->packet->vps); fr_cursor_tail(&cursor); ret = fr_sim_decode(eap_session->request, &cursor, dict_eap_sim, eap_session->this_round->response->type.data, eap_session->this_round->response->type.length, &ctx); /* * RFC 4186 says we *MUST* notify, not just * send an EAP-Failure in this case where * we cannot decode an EAP-AKA packet. */ if (ret < 0) { RPEDEBUG2("Failed decoding EAP-SIM attributes"); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } vp = fr_cursor_current(&cursor); if (vp && RDEBUG_ENABLED2) { RDEBUG2("Decoded EAP-SIM attributes"); log_request_pair_list(L_DBG_LVL_2, request, vp, NULL); } subtype_vp = fr_pair_find_by_da(from_peer, attr_eap_sim_subtype, TAG_ANY); if (!subtype_vp) { REDEBUG("Missing EAP-SIM-Subtype"); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } subtype = subtype_vp->vp_uint16; switch (eap_sim_session->state) { /* * Response to our advertised versions and request for an ID * This is very similar to Identity negotiation in EAP-AKA['] */ case EAP_SIM_SERVER_START: switch (subtype) { case EAP_SIM_START: if (process_eap_sim_start(eap_session, from_peer) == 0) return RLM_MODULE_HANDLED; eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ /* * Case 1 where we're allowed to send an EAP-Failure * * This can happen in the case of a conservative * peer, where it refuses to provide the permanent * identity. */ case EAP_SIM_CLIENT_ERROR: { char buff[20]; vp = fr_pair_find_by_da(from_peer, attr_eap_sim_client_error_code, TAG_ANY); if (!vp) { REDEBUG("EAP-SIM Peer rejected SIM-Start (%s) with client-error message but " "has not supplied a client error code", fr_int2str(sim_id_request_table, eap_sim_session->id_req, "<INVALID>")); } else { REDEBUG("Client rejected SIM-Start (%s) with error: %s (%i)", fr_int2str(sim_id_request_table, eap_sim_session->id_req, "<INVALID>"), fr_pair_value_enum(vp, buff), vp->vp_uint16); } eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); return RLM_MODULE_REJECT; } case EAP_SIM_NOTIFICATION: notification: { char buff[20]; vp = fr_pair_afrom_da(from_peer, attr_eap_sim_notification); if (!vp) { REDEBUG2("Received SIM-Notification with no notification code"); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } /* * Case 2 where we're allowed to send an EAP-Failure */ if (!(vp->vp_uint16 & 0x8000)) { REDEBUG2("SIM-Notification %s (%i) indicates failure", fr_pair_value_enum(vp, buff), vp->vp_uint16); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); return RLM_MODULE_REJECT; } /* * ...if it's not a failure, then re-enter the * current state. */ REDEBUG2("Got SIM-Notification %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16); eap_sim_state_enter(eap_session, eap_sim_session->state); return RLM_MODULE_HANDLED; default: unexpected_subtype: /* * RFC 4186 says we *MUST* notify, not just * send an EAP-Failure in this case. */ REDEBUG("Unexpected subtype %pV", &subtype_vp->data); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } } /* * Process the response to our previous challenge. */ case EAP_SIM_SERVER_CHALLENGE: switch (subtype) { /* * A response to our EAP-Sim/Request/Challenge! */ case EAP_SIM_CHALLENGE: switch (process_eap_sim_challenge(eap_session, from_peer)) { case 1: return RLM_MODULE_HANDLED; case 0: return RLM_MODULE_OK; case -1: eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } case EAP_SIM_CLIENT_ERROR: { char buff[20]; vp = fr_pair_find_by_da(from_peer, attr_eap_sim_client_error_code, TAG_ANY); if (!vp) { REDEBUG("EAP-SIM Peer rejected SIM-Challenge with client-error message but " "has not supplied a client error code"); } else { REDEBUG("Client rejected SIM-Challenge with error: %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16); } eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); return RLM_MODULE_REJECT; } case EAP_SIM_NOTIFICATION: goto notification; default: goto unexpected_subtype; } /* * Peer acked our failure */ case EAP_SIM_SERVER_FAILURE_NOTIFICATION: switch (subtype) { case EAP_SIM_NOTIFICATION: RDEBUG2("SIM-Notification ACKed, sending EAP-Failure"); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); return RLM_MODULE_REJECT; default: goto unexpected_subtype; } /* * Something bad happened... */ default: rad_assert(0); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } } /* * Initiate the EAP-SIM session by starting the state machine * and initiating the state. */ static rlm_rcode_t mod_session_init(void *instance, eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_sim_session_t *eap_sim_session; rlm_eap_sim_t *inst = instance; fr_sim_id_type_t type; fr_sim_method_hint_t method; MEM(eap_sim_session = talloc_zero(eap_session, eap_sim_session_t)); eap_session->opaque = eap_sim_session; /* * Set default configuration, we may allow these * to be toggled by attributes later. */ eap_sim_session->send_result_ind = inst->protected_success; eap_sim_session->id_req = SIM_ANY_ID_REQ; /* Set the default */ /* * This value doesn't have be strong, but it is * good if it is different now and then. */ eap_sim_session->sim_id = (fr_rand() & 0xff); /* * Save the keying material, because it could change on a subsequent retrieval. */ RDEBUG2("New EAP-SIM session"); /* * Process the identity that we received in the * EAP-Identity-Response and use it to determine * the initial request we send to the Supplicant. */ if (fr_sim_id_type(&type, &method, eap_session->identity, talloc_array_length(eap_session->identity) - 1) < 0) { RPWDEBUG2("Failed parsing identity, continuing anyway"); } switch (method) { default: RWDEBUG("EAP-Identity-Response hints that EAP-%s should be started, but we're attempting EAP-SIM", fr_int2str(sim_id_method_hint_table, method, "<INVALID>")); break; case SIM_METHOD_HINT_SIM: case SIM_METHOD_HINT_UNKNOWN: break; } eap_session->process = mod_process; /* * Figure out what type of identity we have * and use it to determine the initial * request we send. */ switch (type) { /* * These types need to be transformed into something * usable before we can do anything. */ case SIM_ID_TYPE_UNKNOWN: case SIM_ID_TYPE_PSEUDONYM: case SIM_ID_TYPE_FASTAUTH: /* * Permanent ID means we can just send the challenge */ case SIM_ID_TYPE_PERMANENT: eap_sim_session->keys.identity_len = talloc_array_length(eap_session->identity) - 1; MEM(eap_sim_session->keys.identity = talloc_memdup(eap_sim_session, eap_session->identity, eap_sim_session->keys.identity_len)); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_START); return RLM_MODULE_HANDLED; } return RLM_MODULE_HANDLED; }
/** Modify user's object in LDAP * * Process a modifcation map to update a user object in the LDAP directory. * * @param inst rlm_ldap instance. * @param request Current request. * @param section that holds the map to process. * @return one of the RLM_MODULE_* values. */ static rlm_rcode_t user_modify(ldap_instance_t *inst, REQUEST *request, ldap_acct_section_t *section) { rlm_rcode_t rcode = RLM_MODULE_OK; ldap_handle_t *conn = NULL; LDAPMod *mod_p[LDAP_MAX_ATTRMAP + 1], mod_s[LDAP_MAX_ATTRMAP]; LDAPMod **modify = mod_p; char *passed[LDAP_MAX_ATTRMAP * 2]; int i, total = 0, last_pass = 0; char *expanded[LDAP_MAX_ATTRMAP]; int last_exp = 0; char const *attr; char const *value; char const *dn; /* * Build our set of modifications using the update sections in * the config. */ CONF_ITEM *ci; CONF_PAIR *cp; CONF_SECTION *cs; FR_TOKEN op; char path[MAX_STRING_LEN]; char *p = path; rad_assert(section); /* * Locate the update section were going to be using */ if (section->reference[0] != '.') { *p++ = '.'; } if (radius_xlat(p, (sizeof(path) - (p - path)) - 1, request, section->reference, NULL, NULL) < 0) { goto error; } ci = cf_reference_item(NULL, section->cs, path); if (!ci) { goto error; } if (!cf_item_is_section(ci)){ REDEBUG("Reference must resolve to a section"); goto error; } cs = cf_section_sub_find(cf_itemtosection(ci), "update"); if (!cs) { REDEBUG("Section must contain 'update' subsection"); goto error; } /* * Iterate over all the pairs, building our mods array */ for (ci = cf_item_find_next(cs, NULL); ci != NULL; ci = cf_item_find_next(cs, ci)) { int do_xlat = false; if (total == LDAP_MAX_ATTRMAP) { REDEBUG("Modify map size exceeded"); goto error; } if (!cf_item_is_pair(ci)) { REDEBUG("Entry is not in \"ldap-attribute = value\" format"); goto error; } /* * Retrieve all the information we need about the pair */ cp = cf_itemtopair(ci); value = cf_pair_value(cp); attr = cf_pair_attr(cp); op = cf_pair_operator(cp); if (!value || (*value == '\0')) { RDEBUG("Empty value string, skipping attribute \"%s\"", attr); continue; } switch (cf_pair_value_type(cp)) { case T_BARE_WORD: case T_SINGLE_QUOTED_STRING: break; case T_BACK_QUOTED_STRING: case T_DOUBLE_QUOTED_STRING: do_xlat = true; break; default: rad_assert(0); goto error; } if (op == T_OP_CMP_FALSE) { passed[last_pass] = NULL; } else if (do_xlat) { char *exp = NULL; if (radius_xlat(exp, 0, request, value, NULL, NULL) <= 0) { RDEBUG("Skipping attribute \"%s\"", attr); talloc_free(exp); continue; } expanded[last_exp++] = exp; passed[last_pass] = exp; /* * Static strings */ } else { memcpy(&(passed[last_pass]), &value, sizeof(passed[last_pass])); } passed[last_pass + 1] = NULL; mod_s[total].mod_values = &(passed[last_pass]); last_pass += 2; switch (op) { /* * T_OP_EQ is *NOT* supported, it is impossible to * support because of the lack of transactions in LDAP */ case T_OP_ADD: mod_s[total].mod_op = LDAP_MOD_ADD; break; case T_OP_SET: mod_s[total].mod_op = LDAP_MOD_REPLACE; break; case T_OP_SUB: case T_OP_CMP_FALSE: mod_s[total].mod_op = LDAP_MOD_DELETE; break; #ifdef LDAP_MOD_INCREMENT case T_OP_INCRM: mod_s[total].mod_op = LDAP_MOD_INCREMENT; break; #endif default: REDEBUG("Operator '%s' is not supported for LDAP modify operations", fr_int2str(fr_tokens, op, "<INVALID>")); goto error; } /* * Now we know the value is ok, copy the pointers into * the ldapmod struct. */ memcpy(&(mod_s[total].mod_type), &(attr), sizeof(mod_s[total].mod_type)); mod_p[total] = &(mod_s[total]); total++; } if (total == 0) { rcode = RLM_MODULE_NOOP; goto release; } mod_p[total] = NULL; conn = rlm_ldap_get_socket(inst, request); if (!conn) return RLM_MODULE_FAIL; dn = rlm_ldap_find_user(inst, request, &conn, NULL, false, NULL, &rcode); if (!dn || (rcode != RLM_MODULE_OK)) { goto error; } rcode = rlm_ldap_modify(inst, request, &conn, dn, modify); release: error: /* * Free up any buffers we allocated for xlat expansion */ for (i = 0; i < last_exp; i++) { talloc_free(expanded[i]); } rlm_ldap_release_socket(inst, conn); return rcode; }
/** Evaluate a map * * @param[in] request the REQUEST * @param[in] modreturn the previous module return code * @param[in] depth of the recursion (only used for debugging) * @param[in] c the condition to evaluate * @return -1 on error, 0 for "no match", 1 for "match". */ int radius_evaluate_map(REQUEST *request, UNUSED int modreturn, UNUSED int depth, fr_cond_t const *c) { int rcode; char *lhs, *rhs; value_pair_map_t *map; rad_assert(c->type == COND_TYPE_MAP); map = c->data.map; rad_assert(map->dst->type != VPT_TYPE_UNKNOWN); rad_assert(map->src->type != VPT_TYPE_UNKNOWN); rad_assert(map->dst->type != VPT_TYPE_LIST); rad_assert(map->src->type != VPT_TYPE_LIST); rad_assert(map->dst->type != VPT_TYPE_REGEX); rad_assert(map->dst->type != VPT_TYPE_REGEX_STRUCT); EVAL_DEBUG("Map %s ? %s", fr_int2str(template_names, map->dst->type, "???"), fr_int2str(template_names, map->src->type, "???")); /* * Verify regexes. */ if ((map->src->type == VPT_TYPE_REGEX) || (map->src->type == VPT_TYPE_REGEX_STRUCT)) { rad_assert(map->op == T_OP_REG_EQ); } else { rad_assert(!((map->op == T_OP_REG_EQ) || (map->op == T_OP_REG_NE))); } /* * They're both attributes. Do attribute-specific work. * * LHS is DST. RHS is SRC <sigh> */ if (!c->cast && (map->src->type == VPT_TYPE_ATTR) && (map->dst->type == VPT_TYPE_ATTR)) { VALUE_PAIR *lhs_vp, *rhs_vp; EVAL_DEBUG("ATTR to ATTR"); if ((radius_tmpl_get_vp(&lhs_vp, request, map->dst) < 0) || (radius_tmpl_get_vp(&rhs_vp, request, map->src) < 0)) { return -2; } return paircmp_op(lhs_vp, map->op, rhs_vp); } /* * LHS is a cast. Do type-specific comparisons, as if * the LHS was a real attribute. */ if (c->cast) { VALUE_PAIR *lhs_vp, *rhs_vp; /* * Try to copy data from the VP which is being * casted, instead of printing it to a string and * then re-parsing it. */ if (map->dst->type == VPT_TYPE_ATTR) { VALUE_PAIR *cast_vp; if (radius_tmpl_get_vp(&cast_vp, request, map->dst) < 0) { return false; } lhs_vp = pairalloc(request, c->cast); if (!lhs_vp) return false; /* * In a separate function for clarity */ if (do_cast_copy(lhs_vp, cast_vp) < 0) { talloc_free(lhs_vp); return false; } } else { rcode = get_cast_vp(&lhs_vp, request, map->dst, c->cast); if (rcode < 0) { return rcode; } } rad_assert(lhs_vp); /* * Get either a real VP, or parse the RHS into a * VP, and return that. */ if (map->src->type == VPT_TYPE_ATTR) { if (radius_tmpl_get_vp(&rhs_vp, request, map->src) < 0) { return -2; } } else { rcode = get_cast_vp(&rhs_vp, request, map->src, c->cast); if (rcode < 0) { return rcode; } rad_assert(rhs_vp); } if (!rhs_vp) return -2; EVAL_DEBUG("CAST to %s", fr_int2str(dict_attr_types, c->cast->type, "?Unknown?")); rcode = paircmp_op(lhs_vp, map->op, rhs_vp); pairfree(&lhs_vp); if (map->src->type != VPT_TYPE_ATTR) { pairfree(&rhs_vp); } return rcode; } /* * Might be a virtual comparison */ if ((map->dst->type == VPT_TYPE_ATTR) && (map->src->type != VPT_TYPE_REGEX) && (map->src->type != VPT_TYPE_REGEX_STRUCT) && (c->pass2_fixup == PASS2_PAIRCOMPARE)) { int ret; VALUE_PAIR *lhs_vp; EVAL_DEBUG("virtual ATTR to DATA"); rcode = get_cast_vp(&lhs_vp, request, map->src, map->dst->vpt_da); if (rcode < 0) { return rcode; } rad_assert(lhs_vp); /* * paircompare requires the operator be set for the * check attribute. */ lhs_vp->op = map->op; ret = paircompare(request, request->packet->vps, lhs_vp, NULL); talloc_free(lhs_vp); if (ret == 0) { return true; } return false; } rad_assert(c->pass2_fixup != PASS2_PAIRCOMPARE); /* * RHS has been pre-parsed into binary data. Go check * that. */ if ((map->dst->type == VPT_TYPE_ATTR) && (map->src->type == VPT_TYPE_DATA)) { VALUE_PAIR *lhs_vp, *rhs_vp; EVAL_DEBUG("ATTR to DATA"); if (radius_tmpl_get_vp(&lhs_vp, request, map->dst) < 0) { return -2; } rcode = get_cast_vp(&rhs_vp, request, map->src, map->dst->vpt_da); if (rcode < 0) { return rcode; } rad_assert(rhs_vp); #ifdef WITH_EVAL_DEBUG debug_pair(lhs_vp); debug_pair(rhs_vp); #endif rcode = paircmp_op(lhs_vp, map->op, rhs_vp); pairfree(&rhs_vp); return rcode; } rad_assert(map->src->type != VPT_TYPE_DATA); rad_assert(map->dst->type != VPT_TYPE_DATA); #ifdef HAVE_REGEX_H /* * Parse regular expressions. */ if ((map->src->type == VPT_TYPE_REGEX) || (map->src->type == VPT_TYPE_REGEX_STRUCT)) { return do_regex(request, map); } #endif /* * The RHS now needs to be expanded into a string. */ rcode = radius_expand_tmpl(&rhs, request, map->src); if (rcode < 0) { EVAL_DEBUG("FAIL %d", __LINE__); return rcode; } rad_assert(rhs != NULL); /* * User-Name == FOO * * Parse the RHS to be the same DA as the LHS. do * comparisons. So long as it's not a regex, which does * string comparisons. * * The LHS may be a virtual attribute, too. */ if (map->dst->type == VPT_TYPE_ATTR) { VALUE_PAIR *lhs_vp, *rhs_vp; EVAL_DEBUG("ATTR to non-REGEX"); /* * No LHS means no match */ if (radius_tmpl_get_vp(&lhs_vp, request, map->dst) < 0) { /* * Not a real attr: might be a dynamic comparison. */ if ((map->dst->type == VPT_TYPE_ATTR) && (map->dst->vpt_da->vendor == 0) && radius_find_compare(map->dst->vpt_da)) { rhs_vp = pairalloc(request, map->dst->vpt_da); rad_assert(rhs_vp != NULL); if (pairparsevalue(rhs_vp, rhs, 0) < 0) { talloc_free(rhs); EVAL_DEBUG("FAIL %d", __LINE__); return -1; } talloc_free(rhs); rcode = (radius_callback_compare(request, NULL, rhs_vp, NULL, NULL) == 0); pairfree(&rhs_vp); return rcode; } return -2; } /* * Get VP for RHS */ rhs_vp = pairalloc(request, map->dst->vpt_da); rad_assert(rhs_vp != NULL); if (pairparsevalue(rhs_vp, rhs, 0) < 0) { talloc_free(rhs); pairfree(&rhs_vp); EVAL_DEBUG("FAIL %d", __LINE__); return -1; } rcode = paircmp_op(lhs_vp, map->op, rhs_vp); talloc_free(rhs); pairfree(&rhs_vp); return rcode; } /* * The LHS is a string. Expand it. */ rcode = radius_expand_tmpl(&lhs, request, map->dst); if (rcode < 0) { EVAL_DEBUG("FAIL %d", __LINE__); return rcode; } rad_assert(lhs != NULL); EVAL_DEBUG("LHS is %s", lhs); /* * Loop over the string, doing comparisons */ if (all_digits(lhs) && all_digits(rhs)) { int lint, rint; lint = strtoul(lhs, NULL, 0); rint = strtoul(rhs, NULL, 0); talloc_free(lhs); talloc_free(rhs); switch (map->op) { case T_OP_CMP_EQ: return (lint == rint); case T_OP_NE: return (lint != rint); case T_OP_LT: return (lint < rint); case T_OP_GT: return (lint > rint); case T_OP_LE: return (lint <= rint); case T_OP_GE: return (lint >= rint); default: break; } } else { rad_assert(lhs != NULL); rad_assert(rhs != NULL); rcode = strcmp(lhs, rhs); talloc_free(lhs); talloc_free(rhs); switch (map->op) { case T_OP_CMP_EQ: return (rcode == 0); case T_OP_NE: return (rcode != 0); case T_OP_LT: return (rcode < 0); case T_OP_GT: return (rcode > 0); case T_OP_LE: return (rcode <= 0); case T_OP_GE: return (rcode >= 0); default: break; } } EVAL_DEBUG("FAIL %d", __LINE__); return -1; }
static int parse_sub_section(CONF_SECTION *parent, rlm_rest_section_t *config, rlm_components_t comp) { CONF_SECTION *cs; char const *name = section_type_value[comp].section; cs = cf_section_sub_find(parent, name); if (!cs) { config->name = NULL; return 0; } if (cf_section_parse(cs, config, section_config) < 0) { config->name = NULL; return -1; } /* * Add section name (Maybe add to headers later?). */ config->name = name; /* * Sanity check */ if ((config->username && !config->password) || (!config->username && config->password)) { cf_log_err_cs(cs, "'username' and 'password' must both be set or both be absent"); return -1; } /* * Convert HTTP method auth and body type strings into their integer equivalents. */ config->auth = fr_str2int(http_auth_table, config->auth_str, HTTP_AUTH_UNKNOWN); if (config->auth == HTTP_AUTH_UNKNOWN) { cf_log_err_cs(cs, "Unknown HTTP auth type '%s'", config->auth_str); return -1; } else if ((config->auth != HTTP_AUTH_NONE) && !http_curl_auth[config->auth]) { cf_log_err_cs(cs, "Unsupported HTTP auth type \"%s\", check libcurl version, OpenSSL build " "configuration, then recompile this module", config->auth_str); return -1; } config->method = fr_str2int(http_method_table, config->method_str, HTTP_METHOD_CUSTOM); config->timeout = ((config->timeout_tv.tv_usec * 1000) + (config->timeout_tv.tv_sec / 1000)); /* * We don't have any custom user data, so we need to select the right encoder based * on the body type. * * To make this slightly more/less confusing, we accept both canonical body_types, * and content_types. */ if (!config->data) { config->body = fr_str2int(http_body_type_table, config->body_str, HTTP_BODY_UNKNOWN); if (config->body == HTTP_BODY_UNKNOWN) { config->body = fr_str2int(http_content_type_table, config->body_str, HTTP_BODY_UNKNOWN); } if (config->body == HTTP_BODY_UNKNOWN) { cf_log_err_cs(cs, "Unknown HTTP body type '%s'", config->body_str); return -1; } switch (http_body_type_supported[config->body]) { case HTTP_BODY_UNSUPPORTED: cf_log_err_cs(cs, "Unsupported HTTP body type \"%s\", please submit patches", config->body_str); return -1; case HTTP_BODY_INVALID: cf_log_err_cs(cs, "Invalid HTTP body type. \"%s\" is not a valid web API data " "markup format", config->body_str); return -1; case HTTP_BODY_UNAVAILABLE: cf_log_err_cs(cs, "Unavailable HTTP body type. \"%s\" is not available in this " "build", config->body_str); return -1; default: break; } /* * We have custom body data so we set HTTP_BODY_CUSTOM_XLAT, but also need to try and * figure out what content-type to use. So if they've used the canonical form we * need to convert it back into a proper HTTP content_type value. */ } else { http_body_type_t body; config->body = HTTP_BODY_CUSTOM_XLAT; body = fr_str2int(http_body_type_table, config->body_str, HTTP_BODY_UNKNOWN); if (body != HTTP_BODY_UNKNOWN) { config->body_str = fr_int2str(http_content_type_table, body, config->body_str); } } if (config->force_to_str) { config->force_to = fr_str2int(http_body_type_table, config->force_to_str, HTTP_BODY_UNKNOWN); if (config->force_to == HTTP_BODY_UNKNOWN) { config->force_to = fr_str2int(http_content_type_table, config->force_to_str, HTTP_BODY_UNKNOWN); } if (config->force_to == HTTP_BODY_UNKNOWN) { cf_log_err_cs(cs, "Unknown forced response body type '%s'", config->force_to_str); return -1; } switch (http_body_type_supported[config->force_to]) { case HTTP_BODY_UNSUPPORTED: cf_log_err_cs(cs, "Unsupported forced response body type \"%s\", please submit patches", config->force_to_str); return -1; case HTTP_BODY_INVALID: cf_log_err_cs(cs, "Invalid HTTP forced response body type. \"%s\" is not a valid web API data " "markup format", config->force_to_str); return -1; default: break; } } return 0; }
char const *fr_token_name(int token) { return fr_int2str(fr_tokens, token, "???"); }
/* * Parse one statement. 'foo = bar', or 'if (...) {...}', or '{...}', * and so on. */ static int parse_statement(policy_lex_file_t *lexer, policy_item_t **tail) { int rcode; policy_reserved_word_t reserved; policy_lex_t token, assign; char lhs[256], rhs[256]; policy_assignment_t *this; /* * See what kind of token we have. */ token = policy_lex_file(lexer, 0, lhs, sizeof(lhs)); switch (token) { case POLICY_LEX_LC_BRACKET: rcode = parse_block(lexer, tail); if (!rcode) { return 0; } break; case POLICY_LEX_BARE_WORD: reserved = fr_str2int(policy_reserved_words, lhs, POLICY_RESERVED_UNKNOWN); switch (reserved) { case POLICY_RESERVED_IF: if (parse_if(lexer, tail)) { return 1; } return 0; break; case POLICY_RESERVED_CONTROL: case POLICY_RESERVED_REQUEST: case POLICY_RESERVED_REPLY: case POLICY_RESERVED_PROXY_REQUEST: case POLICY_RESERVED_PROXY_REPLY: if (parse_attribute_block(lexer, tail, reserved)) return 1; return 0; break; case POLICY_RESERVED_PRINT: if (parse_print(lexer, tail)) { return 1; } return 0; break; case POLICY_RESERVED_RETURN: if (parse_return(lexer, tail)) { return 1; } return 0; break; case POLICY_RESERVED_MODULE: if (parse_module(lexer, tail)) { return 1; } return 0; break; case POLICY_RESERVED_UNKNOWN: /* wasn't a reserved word */ /* * Is a named policy, parse the reference to it. */ if (rlm_policy_find(lexer->policies, lhs) != NULL) { if (!parse_call(lexer, tail, lhs)) { return 0; } return 1; } { const DICT_ATTR *dattr; /* * Bare words MUST be dictionary attributes */ dattr = dict_attrbyname(lhs); if (!dattr) { fprintf(stderr, "%s[%d]: Expected attribute name, got \"%s\"\n", lexer->filename, lexer->lineno, lhs); return 0; } debug_tokens("%s[%d]: Got attribute %s\n", lexer->filename, lexer->lineno, lhs); } break; default: fprintf(stderr, "%s[%d]: Unexpected reserved word \"%s\"\n", lexer->filename, lexer->lineno, lhs); return 0; } /* switch over reserved words */ break; /* * Return from nested blocks. */ case POLICY_LEX_RC_BRACKET: policy_lex_push_token(lexer, token); return 2; /* magic */ case POLICY_LEX_EOF: /* nothing more to do */ return 3; default: fprintf(stderr, "%s[%d]: Unexpected %s\n", lexer->filename, lexer->lineno, fr_int2str(policy_explanations, token, "string")); break; } /* * Parse a bare statement. */ assign = policy_lex_file(lexer, 0, rhs, sizeof(rhs)); switch (assign) { case POLICY_LEX_ASSIGN: case POLICY_LEX_SET_EQUALS: case POLICY_LEX_AND_EQUALS: case POLICY_LEX_OR_EQUALS: case POLICY_LEX_PLUS_EQUALS: break; default: fprintf(stderr, "%s[%d]: Unexpected assign %s\n", lexer->filename, lexer->lineno, fr_int2str(policy_explanations, assign, "string")); return 0; } this = rad_malloc(sizeof(*this)); memset(this, 0, sizeof(*this)); this->item.type = POLICY_TYPE_ASSIGNMENT; this->item.lineno = lexer->lineno; token = policy_lex_file(lexer, 0, rhs, sizeof(rhs)); if ((token != POLICY_LEX_BARE_WORD) && (token != POLICY_LEX_DOUBLE_QUOTED_STRING)) { fprintf(stderr, "%s[%d]: Unexpected rhs %s\n", lexer->filename, lexer->lineno, fr_int2str(policy_explanations, token, "string")); rlm_policy_free_item((policy_item_t *) this); return 0; } this->rhs_type = token; this->rhs = strdup(rhs); token = policy_lex_file(lexer, POLICY_LEX_FLAG_RETURN_EOL, rhs, sizeof(rhs)); if (token != POLICY_LEX_EOL) { fprintf(stderr, "%s[%d]: Expected EOL\n", lexer->filename, lexer->lineno); rlm_policy_free_item((policy_item_t *) this); return 0; } debug_tokens("[ASSIGN %s %s %s]\n", lhs, fr_int2str(rlm_policy_tokens, assign, "?"), rhs); /* * Fill in the assignment struct */ this->lhs = strdup(lhs); this->assign = assign; *tail = (policy_item_t *) this; return 1; }
/* * Print an Ascend binary filter attribute to a string, * Grrr... Ascend makes the server do this work, instead * of doing it on the NAS. * * Note we don't bother checking 'len' after the snprintf's. * This function should ONLY be called with a large (~1k) buffer. */ void print_abinary(char *out, size_t outlen, uint8_t const *data, size_t len, int8_t quote) { size_t i; char *p; ascend_filter_t const *filter; static char const *action[] = {"drop", "forward"}; static char const *direction[] = {"out", "in"}; p = out; /* * Just for paranoia: wrong size filters get printed as octets */ if (len != sizeof(*filter)) { strcpy(p, "0x"); p += 2; outlen -= 2; for (i = 0; i < len; i++) { snprintf(p, outlen, "%02x", data[i]); p += 2; outlen -= 2; } return; } if (quote > 0) { *(p++) = (char) quote; outlen -= 3; /* account for leading & trailing quotes */ } filter = (ascend_filter_t const *) data; i = snprintf(p, outlen, "%s %s %s", fr_int2str(filterType, filter->type, "??"), direction[filter->direction & 0x01], action[filter->forward & 0x01]); p += i; outlen -= i; /* * Handle IP filters */ if (filter->type == RAD_FILTER_IP) { if (filter->u.ip.srcip) { i = snprintf(p, outlen, " srcip %d.%d.%d.%d/%d", ((uint8_t const *) &filter->u.ip.srcip)[0], ((uint8_t const *) &filter->u.ip.srcip)[1], ((uint8_t const *) &filter->u.ip.srcip)[2], ((uint8_t const *) &filter->u.ip.srcip)[3], filter->u.ip.srcmask); p += i; outlen -= i; } if (filter->u.ip.dstip) { i = snprintf(p, outlen, " dstip %d.%d.%d.%d/%d", ((uint8_t const *) &filter->u.ip.dstip)[0], ((uint8_t const *) &filter->u.ip.dstip)[1], ((uint8_t const *) &filter->u.ip.dstip)[2], ((uint8_t const *) &filter->u.ip.dstip)[3], filter->u.ip.dstmask); p += i; outlen -= i; } i = snprintf(p, outlen, " %s", fr_int2str(filterProtoName, filter->u.ip.proto, "??")); p += i; outlen -= i; if (filter->u.ip.srcPortComp > RAD_NO_COMPARE) { i = snprintf(p, outlen, " srcport %s %d", fr_int2str(filterCompare, filter->u.ip.srcPortComp, "??"), ntohs(filter->u.ip.srcport)); p += i; outlen -= i; } if (filter->u.ip.dstPortComp > RAD_NO_COMPARE) { i = snprintf(p, outlen, " dstport %s %d", fr_int2str(filterCompare, filter->u.ip.dstPortComp, "??"), ntohs(filter->u.ip.dstport)); p += i; outlen -= i; } if (filter->u.ip.established) { i = snprintf(p, outlen, " est"); p += i; } /* * Handle IPX filters */ } else if (filter->type == RAD_FILTER_IPX) { /* print for source */ if (filter->u.ipx.src.net) { i = snprintf(p, outlen, " srcipxnet 0x%04x srcipxnode 0x%02x%02x%02x%02x%02x%02x", (unsigned int)ntohl(filter->u.ipx.src.net), filter->u.ipx.src.node[0], filter->u.ipx.src.node[1], filter->u.ipx.src.node[2], filter->u.ipx.src.node[3], filter->u.ipx.src.node[4], filter->u.ipx.src.node[5]); p += i; outlen -= i; if (filter->u.ipx.srcSocComp > RAD_NO_COMPARE) { i = snprintf(p, outlen, " srcipxsock %s 0x%04x", fr_int2str(filterCompare, filter->u.ipx.srcSocComp, "??"), ntohs(filter->u.ipx.src.socket)); p += i; outlen -= i; } } /* same for destination */ if (filter->u.ipx.dst.net) { i = snprintf(p, outlen, " dstipxnet 0x%04x dstipxnode 0x%02x%02x%02x%02x%02x%02x", (unsigned int)ntohl(filter->u.ipx.dst.net), filter->u.ipx.dst.node[0], filter->u.ipx.dst.node[1], filter->u.ipx.dst.node[2], filter->u.ipx.dst.node[3], filter->u.ipx.dst.node[4], filter->u.ipx.dst.node[5]); p += i; outlen -= i; if (filter->u.ipx.dstSocComp > RAD_NO_COMPARE) { i = snprintf(p, outlen, " dstipxsock %s 0x%04x", fr_int2str(filterCompare, filter->u.ipx.dstSocComp, "??"), ntohs(filter->u.ipx.dst.socket)); p += i; } } } else if (filter->type == RAD_FILTER_GENERIC) { int count; i = snprintf(p, outlen, " %u ", (unsigned int) ntohs(filter->u.generic.offset)); p += i; /* show the mask */ for (count = 0; count < ntohs(filter->u.generic.len); count++) { i = snprintf(p, outlen, "%02x", filter->u.generic.mask[count]); p += i; outlen -= i; } strcpy(p, " "); p++; outlen--; /* show the value */ for (count = 0; count < ntohs(filter->u.generic.len); count++) { i = snprintf(p, outlen, "%02x", filter->u.generic.value[count]); p += i; outlen -= i; } i = snprintf(p, outlen, " %s", (filter->u.generic.compNeq) ? "!=" : "=="); p += i; outlen -= i; if (filter->u.generic.more != 0) { i = snprintf(p, outlen, " more"); p += i; } } if (quote > 0) { *(p++) = (char) quote; } *p = '\0'; }
/* * Function to return a token saying what it read, and possibly * a buffer of the quoted string or bare word. */ static policy_lex_t policy_lex_file(policy_lex_file_t *lexer, int flags, char *mystring, size_t mystringlen) { policy_lex_t token = POLICY_LEX_BARE_WORD; /* to prime it */ if (lexer->debug & POLICY_DEBUG_PRINT_TOKENS) { flags |= POLICY_LEX_FLAG_PRINT_TOKEN; } if (!lexer->fp) { return POLICY_LEX_EOF; } /* * Starting off, the buffer needs to be primed. */ if (!lexer->parse) { lexer->parse = fgets(lexer->buffer, sizeof(lexer->buffer), lexer->fp); if (!lexer->parse) { return POLICY_LEX_EOF; } lexer->lineno = 1; } /* buffer is primed, read stuff */ if (lexer->token != POLICY_LEX_BAD) { token = lexer->token; lexer->token = POLICY_LEX_BAD; return token; } /* * Ignore whitespace, and keep filling the buffer */ while (lexer->parse) { const char *next; next = policy_lex_string(lexer->parse, &token, mystring, mystringlen); switch (token) { case POLICY_LEX_WHITESPACE: /* skip whitespace */ lexer->parse = next; continue; case POLICY_LEX_EOL: /* read another line */ lexer->parse = fgets(lexer->buffer, sizeof(lexer->buffer), lexer->fp); lexer->lineno++; if (flags & POLICY_LEX_FLAG_RETURN_EOL) { return POLICY_LEX_EOL; } break; /* read another token */ default: /* return the token */ if (!(flags & POLICY_LEX_FLAG_PEEK)) { lexer->parse = next; } if (flags & POLICY_LEX_FLAG_PRINT_TOKEN) { debug_tokens("[%s token %s] ", (flags & POLICY_LEX_FLAG_PEEK) ? "peek " : "", fr_int2str(rlm_policy_tokens, token, "?")); } return token; break; } } /* loop until EOF */ /* * Close it for the user. */ fclose(lexer->fp); lexer->fp = NULL; return POLICY_LEX_EOF; }
size_t xlat_sprint(char *buffer, size_t bufsize, xlat_exp_t const *node) { size_t len; char *p, *end; if (!node) { *buffer = '\0'; return 0; } p = buffer; end = buffer + bufsize; while (node) { switch (node->type) { case XLAT_LITERAL: strlcpy(p, node->fmt, end - p); p += strlen(p); break; case XLAT_PERCENT: p[0] = '%'; p[1] = node->fmt[0]; p += 2; break; case XLAT_ATTRIBUTE: *(p++) = '%'; *(p++) = '{'; if (node->ref != REQUEST_CURRENT) { strlcpy(p, fr_int2str(request_refs, node->ref, "??"), end - p); p += strlen(p); *(p++) = '.'; } if ((node->ref != REQUEST_CURRENT) || (node->list != PAIR_LIST_REQUEST)) { strlcpy(p, fr_int2str(pair_lists, node->list, "??"), end - p); p += strlen(p); *(p++) = ':'; } strlcpy(p, node->da->name, end - p); p += strlen(p); if (node->tag != TAG_ANY) { *(p++) = ':'; snprintf(p, end - p, "%u", node->tag); p += strlen(p); } if (node->num != 0) { *(p++) = '['; if (node->num == 65536) { *(p++) = '#'; } else if (node->num == 65537) { *(p++) = '*'; } else { snprintf(p, end - p, "%u", node->num); p += strlen(p); } *(p++) = ']'; } *(p++) = '}'; break; #ifdef HAVE_REGEX_H case XLAT_REGEX: snprintf(p, end - p, "%%{%u}", node->num); p += strlen(p); break; #endif case XLAT_VIRTUAL: *(p++) = '%'; *(p++) = '{'; strlcpy(p, node->fmt, end - p); p += strlen(p); *(p++) = '}'; break; case XLAT_MODULE: *(p++) = '%'; *(p++) = '{'; strlcpy(p, node->xlat->name, end - p); p += strlen(p); *(p++) = ':'; rad_assert(node->child != NULL); len = xlat_sprint(p, end - p, node->child); p += len; *(p++) = '}'; break; case XLAT_ALTERNATE: *(p++) = '%'; *(p++) = '{'; len = xlat_sprint(p, end - p, node->child); p += len; *(p++) = ':'; *(p++) = '-'; len = xlat_sprint(p, end - p, node->alternate); p += len; *(p++) = '}'; break; } if (p == end) break; node = node->next; } *p = '\0'; return p - buffer; }
/** Convert CONFIG_PAIR (which may contain refs) to value_pair_map_t. * * Treats the left operand as an attribute reference * @verbatim<request>.<list>.<attribute>@endverbatim * * Treatment of left operand depends on quotation, barewords are treated as * attribute references, double quoted values are treated as expandable strings, * single quoted values are treated as literal strings. * * Return must be freed with radius_mapfree. * * @param[in] cp to convert to map. * @param[in] dst_request_def The default request to insert unqualified * attributes into. * @param[in] dst_list_def The default list to insert unqualified attributes * into. * @param[in] src_request_def The default request to resolve attribute * references in. * @param[in] src_list_def The default list to resolve unqualified attributes * in. * @return value_pair_map_t if successful or NULL on error. */ value_pair_map_t *radius_cp2map(CONF_PAIR *cp, request_refs_t dst_request_def, pair_lists_t dst_list_def, request_refs_t src_request_def, pair_lists_t src_list_def) { value_pair_map_t *map; const char *attr; const char *value; FR_TOKEN type; CONF_ITEM *ci = cf_pairtoitem(cp); if (!cp) return NULL; map = rad_calloc(sizeof(value_pair_map_t)); attr = cf_pair_attr(cp); value = cf_pair_value(cp); if (!value) { cf_log_err(ci, "Missing attribute value"); goto error; } map->dst = radius_attr2tmpl(attr, dst_request_def, dst_list_def); if (!map->dst){ goto error; } /* * Bare words always mean attribute references. */ type = cf_pair_value_type(cp); map->src = type == T_BARE_WORD ? radius_attr2tmpl(value, src_request_def, src_list_def) : radius_str2tmpl(value, type); if (!map->src) { goto error; } map->op = cf_pair_operator(cp); map->ci = ci; /* * Lots of sanity checks for insane people... */ /* * We don't support implicit type conversion */ if (map->dst->da && map->src->da && (map->src->da->type != map->dst->da->type)) { cf_log_err(ci, "Attribute type mismatch"); goto error; } /* * What exactly where you expecting to happen here? */ if ((map->dst->type == VPT_TYPE_ATTR) && (map->src->type == VPT_TYPE_LIST)) { cf_log_err(ci, "Can't copy list into an attribute"); goto error; } switch (map->src->type) { /* * Only += and -= operators are supported for list copy. */ case VPT_TYPE_LIST: switch (map->op) { case T_OP_SUB: case T_OP_ADD: break; default: cf_log_err(ci, "Operator \"%s\" not allowed " "for list copy", fr_int2str(fr_tokens, map->op, "¿unknown?")); goto error; } break; /* * @todo add support for exec expansion. */ case VPT_TYPE_EXEC: cf_log_err(ci, "Exec values are not allowed"); break; default: break; } return map; error: radius_mapfree(&map); return NULL; }
/** Print data as integer, not as VALUE. * */ static ssize_t xlat_integer(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen) { VALUE_PAIR *vp; uint64_t int64 = 0; /* Needs to be initialised to zero */ uint32_t int32 = 0; /* Needs to be initialised to zero */ while (isspace((int) *fmt)) fmt++; if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) { *out = '\0'; return 0; } switch (vp->da->type) { case PW_TYPE_OCTETS: case PW_TYPE_STRING: if (vp->length > 8) { break; } if (vp->length > 4) { memcpy(&int64, vp->vp_octets, vp->length); return snprintf(out, outlen, "%" PRIu64, htonll(int64)); } memcpy(&int32, vp->vp_octets, vp->length); return snprintf(out, outlen, "%i", htonl(int32)); case PW_TYPE_INTEGER64: return snprintf(out, outlen, "%" PRIu64, vp->vp_integer64); /* * IP addresses are treated specially, as parsing functions assume the value * is bigendian and will convert it for us. */ case PW_TYPE_IPADDR: return snprintf(out, outlen, "%u", htonl(vp->vp_ipaddr)); case PW_TYPE_INTEGER: case PW_TYPE_DATE: case PW_TYPE_BYTE: case PW_TYPE_SHORT: return snprintf(out, outlen, "%u", vp->vp_integer); /* * Ethernet is weird... It's network related, so we assume to it should be * bigendian. */ case PW_TYPE_ETHERNET: memcpy(&int64, &vp->vp_ether, vp->length); return snprintf(out, outlen, "%" PRIu64, htonll(int64)); case PW_TYPE_SIGNED: return snprintf(out, outlen, "%i", vp->vp_signed); default: break; } REDEBUG("Type '%s' of length %zu cannot be converted to integer", fr_int2str(dict_attr_types, vp->da->type, PW_TYPE_INVALID), vp->length); *out = '\0'; return -1; }
void vradlog_request(log_type_t type, log_debug_t lvl, REQUEST *request, char const *msg, va_list ap) { size_t len = 0; char const *filename = default_log.file; FILE *fp = NULL; char buffer[10240]; /* The largest config item size, then extra for prefixes and suffixes */ char *p; char const *extra = ""; va_list aq; rad_assert(request); /* * Debug messages get treated specially. */ if ((type & L_DBG) != 0) { if (!radlog_debug_enabled(type, lvl, request)) { return; } /* * Use the debug output file, if specified, * otherwise leave it as the default log file. */ #ifdef WITH_COMMAND_SOCKET filename = default_log.debug_file; if (!filename) #endif filename = default_log.file; } if (request && filename) { radlog_func_t rl = request->log.func; request->log.func = NULL; /* * This is SLOW! Doing it for every log message * in every request is NOT recommended! */ /* FIXME: escape chars! */ if (radius_xlat(buffer, sizeof(buffer), request, filename, NULL, NULL) < 0) { return; } request->log.func = rl; p = strrchr(buffer, FR_DIR_SEP); if (p) { *p = '\0'; if (rad_mkdir(buffer, S_IRWXU) < 0) { ERROR("Failed creating %s: %s", buffer, fr_syserror(errno)); return; } *p = FR_DIR_SEP; } fp = fopen(buffer, "a"); } /* * Print timestamps to the file. */ if (fp) { time_t timeval; timeval = time(NULL); #ifdef HAVE_GMTIME_R if (log_dates_utc) { struct tm utc; gmtime_r(&timeval, &utc); ASCTIME_R(&utc, buffer, sizeof(buffer) - 1); } else #endif { CTIME_R(&timeval, buffer, sizeof(buffer) - 1); } len = strlen(buffer); p = strrchr(buffer, '\n'); if (p) { p[0] = ' '; p[1] = '\0'; } len += strlcpy(buffer + len, fr_int2str(levels, type, ": "), sizeof(buffer) - len); if (len >= sizeof(buffer)) goto finish; } if (request && request->module[0]) { len = snprintf(buffer + len, sizeof(buffer) - len, "%s : ", request->module); if (len >= sizeof(buffer)) goto finish; } /* * If we don't copy the original ap we get a segfault from vasprintf. This is apparently * due to ap sometimes being implemented with a stack offset which is invalidated if * ap is passed into another function. See here: * http://julipedia.meroh.net/2011/09/using-vacopy-to-safely-pass-ap.html * * I don't buy that explanation, but doing a va_copy here does prevent SEGVs seen when * running unit tests which generate errors under CI. */ va_copy(aq, ap); vsnprintf(buffer + len, sizeof(buffer) - len, msg, aq); va_end(aq); finish: switch (type) { case L_DBG_WARN: extra = "WARNING: "; type = L_DBG_WARN_REQ; break; case L_DBG_ERR: extra = "ERROR: "; type = L_DBG_ERR_REQ; break; default: break; } if (!fp) { if (debug_flag > 2) extra = ""; if (request) { uint8_t indent; indent = request->log.indent > sizeof(spaces) ? sizeof(spaces) : request->log.indent; radlog(type, "(%u) %.*s%s%s", request->number, indent, spaces, extra, buffer); } else { radlog(type, "%s%s", extra, buffer); } } else { if (request) { fprintf(fp, "(%u) %s", request->number, extra); } fputs(buffer, fp); fputc('\n', fp); fclose(fp); } }
/* * Allow single attribute values to be retrieved from the cache. */ static ssize_t cache_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace) { rlm_cache_entry_t *c = NULL; rlm_cache_t *inst = instance; rlm_cache_handle_t *handle = NULL; VALUE_PAIR *vp, *vps; pair_lists_t list; DICT_ATTR const *target; char const *p = fmt; size_t len; int ret = 0; p += radius_list_name(&list, p, PAIR_LIST_REQUEST); if (list == PAIR_LIST_UNKNOWN) { REDEBUG("Unknown list qualifier in \"%s\"", fmt); ret = -1; goto finish; } target = dict_attrbyname(p); if (!target) { REDEBUG("Unknown attribute \"%s\"", p); return -1; } if (cache_acquire(&handle, inst, request) < 0) return -1; switch (cache_find(&c, inst, request, handle, fmt)) { case RLM_MODULE_OK: /* found */ break; case RLM_MODULE_NOTFOUND: /* not found */ *out = '\0'; return 0; default: return -1; } switch (list) { case PAIR_LIST_REQUEST: vps = c->packet; break; case PAIR_LIST_REPLY: vps = c->reply; break; case PAIR_LIST_CONTROL: vps = c->control; break; default: REDEBUG("Unsupported list \"%s\"", fr_int2str(pair_lists, list, "<UNKNOWN>")); ret = -1; goto finish; } vp = pairfind(vps, target->attr, target->vendor, TAG_ANY); if (!vp) { RDEBUG("No instance of this attribute has been cached"); *out = '\0'; goto finish; } len = vp_prints_value(out, freespace, vp, 0); if (is_truncated(len, freespace)) { REDEBUG("Insufficient buffer space to write cached value"); ret = -1; goto finish; } finish: cache_free(inst, &c); cache_release(inst, request, &handle); return ret; }