/* * Check password. * * Returns: 0 OK * -1 Password fail * -2 Rejected (Auth-Type = Reject, send Port-Message back) * 1 End check & return, don't reply * * NOTE: NOT the same as the RLM_ values ! */ static int rad_check_password(REQUEST *request) { VALUE_PAIR *auth_type_pair; VALUE_PAIR *cur_config_item; int auth_type = -1; int result; int auth_type_count = 0; result = 0; /* * Look for matching check items. We skip the whole lot * if the authentication type is PW_AUTHTYPE_ACCEPT or * PW_AUTHTYPE_REJECT. */ cur_config_item = request->config_items; while(((auth_type_pair = pairfind(cur_config_item, PW_AUTH_TYPE, 0, TAG_ANY))) != NULL) { auth_type = auth_type_pair->vp_integer; auth_type_count++; RDEBUG2("Found Auth-Type = %s", dict_valnamebyattr(PW_AUTH_TYPE, 0, auth_type)); cur_config_item = auth_type_pair->next; if (auth_type == PW_AUTHTYPE_REJECT) { RDEBUG2("Auth-Type = Reject, rejecting user"); return -2; } } /* * Warn if more than one Auth-Type was found, because only the last * one found will actually be used. */ if (( auth_type_count > 1) && (debug_flag)) { radlog_request(L_ERR, 0, request, "Warning: Found %d auth-types on request for user '%s'", auth_type_count, request->username->vp_strvalue); } /* * This means we have a proxy reply or an accept and it wasn't * rejected in the above loop. So that means it is accepted and we * do no further authentication. */ if ((auth_type == PW_AUTHTYPE_ACCEPT) #ifdef WITH_PROXY || (request->proxy) #endif ) { RDEBUG2("Auth-Type = Accept, accepting the user"); return 0; } /* * Check that Auth-Type has been set, and reject if not. * * Do quick checks to see if Cleartext-Password or Crypt-Password have * been set, and complain if so. */ if (auth_type < 0) { if (pairfind(request->config_items, PW_CRYPT_PASSWORD, 0, TAG_ANY) != NULL) { RDEBUG2W("Please update your configuration, and remove 'Auth-Type = Crypt'"); RDEBUG2W("Use the PAP module instead."); } else if (pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY) != NULL) { RDEBUG2W("Please update your configuration, and remove 'Auth-Type = Local'"); RDEBUG2W("Use the PAP or CHAP modules instead."); } /* * The admin hasn't told us how to * authenticate the user, so we reject them! * * This is fail-safe. */ RDEBUG2E("No Auth-Type found: rejecting the user via Post-Auth-Type = Reject"); return -2; } /* * See if there is a module that handles * this Auth-Type, and turn the RLM_ return * status into the values as defined at * the top of this function. */ result = module_authenticate(auth_type, request); switch (result) { /* * An authentication module FAIL * return code, or any return code that * is not expected from authentication, * is the same as an explicit REJECT! */ case RLM_MODULE_FAIL: case RLM_MODULE_INVALID: case RLM_MODULE_NOOP: case RLM_MODULE_NOTFOUND: case RLM_MODULE_REJECT: case RLM_MODULE_UPDATED: case RLM_MODULE_USERLOCK: default: result = -1; break; case RLM_MODULE_OK: result = 0; break; case RLM_MODULE_HANDLED: result = 1; break; } return result; }
/* * Process and reply to an authentication request * * The return value of this function isn't actually used right now, so * it's not entirely clear if it is returning the right things. --Pac. */ int rad_authenticate(REQUEST *request) { VALUE_PAIR *namepair; #ifdef WITH_SESSION_MGMT VALUE_PAIR *check_item; #endif VALUE_PAIR *auth_item = NULL; VALUE_PAIR *module_msg; VALUE_PAIR *tmp = NULL; int result; const char *password; char autz_retry = 0; int autz_type = 0; password = ""; #ifdef WITH_PROXY /* * If this request got proxied to another server, we need * to check whether it authenticated the request or not. */ if (request->proxy_reply) { switch (request->proxy_reply->code) { /* * Reply of ACCEPT means accept, thus set Auth-Type * accordingly. */ case PW_AUTHENTICATION_ACK: tmp = radius_paircreate(request, &request->config_items, PW_AUTH_TYPE, 0); if (tmp) tmp->vp_integer = PW_AUTHTYPE_ACCEPT; goto authenticate; /* * Challenges are punted back to the NAS without any * further processing. */ case PW_ACCESS_CHALLENGE: request->reply->code = PW_ACCESS_CHALLENGE; return RLM_MODULE_OK; /* * ALL other replies mean reject. (this is fail-safe) * * Do NOT do any authorization or authentication. They * are being rejected, so we minimize the amount of work * done by the server, by rejecting them here. */ case PW_AUTHENTICATION_REJECT: rad_authlog("Login incorrect (Home Server says so)", request, 0); request->reply->code = PW_AUTHENTICATION_REJECT; return RLM_MODULE_REJECT; default: rad_authlog("Login incorrect (Home Server failed to respond)", request, 0); return RLM_MODULE_REJECT; } } #endif /* * Get the username from the request. * * Note that namepair MAY be NULL, in which case there * is no User-Name attribute in the request. */ namepair = request->username; /* * Look for, and cache, passwords. */ if (!request->password) { request->password = pairfind(request->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY); } /* * Discover which password we want to use. */ auth_item = request->password; if (auth_item) { password = (const char *)auth_item->vp_strvalue; } else { /* * Maybe there's a CHAP-Password? */ if ((auth_item = pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY)) != NULL) { password = "******"; } else { /* * No password we recognize. */ password = "******"; } } request->password = auth_item; /* * Get the user's authorization information from the database */ autz_redo: result = module_authorize(autz_type, request); switch (result) { case RLM_MODULE_NOOP: case RLM_MODULE_NOTFOUND: case RLM_MODULE_OK: case RLM_MODULE_UPDATED: break; case RLM_MODULE_HANDLED: return result; case RLM_MODULE_FAIL: case RLM_MODULE_INVALID: case RLM_MODULE_REJECT: case RLM_MODULE_USERLOCK: default: if ((module_msg = pairfind(request->packet->vps, PW_MODULE_FAILURE_MESSAGE, 0, TAG_ANY)) != NULL) { char msg[MAX_STRING_LEN + 16]; snprintf(msg, sizeof(msg), "Invalid user (%s)", module_msg->vp_strvalue); rad_authlog(msg,request,0); } else { rad_authlog("Invalid user", request, 0); } request->reply->code = PW_AUTHENTICATION_REJECT; return result; } if (!autz_retry) { tmp = pairfind(request->config_items, PW_AUTZ_TYPE, 0, TAG_ANY); if (tmp) { autz_type = tmp->vp_integer; RDEBUG2("Using Autz-Type %s", dict_valnamebyattr(PW_AUTZ_TYPE, 0, autz_type)); autz_retry = 1; goto autz_redo; } } /* * If we haven't already proxied the packet, then check * to see if we should. Maybe one of the authorize * modules has decided that a proxy should be used. If * so, get out of here and send the packet. */ if ( #ifdef WITH_PROXY (request->proxy == NULL) && #endif ((tmp = pairfind(request->config_items, PW_PROXY_TO_REALM, 0, TAG_ANY)) != NULL)) { REALM *realm; realm = realm_find2(tmp->vp_strvalue); /* * Don't authenticate, as the request is going to * be proxied. */ if (realm && realm->auth_pool) { return RLM_MODULE_OK; } /* * Catch users who set Proxy-To-Realm to a LOCAL * realm (sigh). But don't complain if it is * *the* LOCAL realm. */ if (realm &&(strcmp(realm->name, "LOCAL") != 0)) { RDEBUG2W("You set Proxy-To-Realm = %s, but it is a LOCAL realm! Cancelling proxy request.", realm->name); } if (!realm) { RDEBUG2W("You set Proxy-To-Realm = %s, but the realm does not exist! Cancelling invalid proxy request.", tmp->vp_strvalue); } } #ifdef WITH_PROXY authenticate: #endif /* * Perhaps there is a Stripped-User-Name now. */ namepair = request->username; /* * Validate the user */ do { result = rad_check_password(request); if (result > 0) { /* don't reply! */ return RLM_MODULE_HANDLED; } } while(0); /* * Failed to validate the user. * * We PRESUME that the code which failed will clean up * request->reply->vps, to be ONLY the reply items it * wants to send back. */ if (result < 0) { RDEBUG2("Failed to authenticate the user."); request->reply->code = PW_AUTHENTICATION_REJECT; if ((module_msg = pairfind(request->packet->vps, PW_MODULE_FAILURE_MESSAGE, 0, TAG_ANY)) != NULL){ char msg[MAX_STRING_LEN+19]; snprintf(msg, sizeof(msg), "Login incorrect (%s)", module_msg->vp_strvalue); rad_authlog(msg, request, 0); } else { rad_authlog("Login incorrect", request, 0); } /* double check: maybe the secret is wrong? */ if ((debug_flag > 1) && (auth_item != NULL) && (auth_item->da->attr == PW_USER_PASSWORD)) { uint8_t *p; p = (uint8_t *) auth_item->vp_strvalue; while (*p) { int size; size = fr_utf8_char(p); if (!size) { log_debug(" WARNING: Unprintable characters in the password. Double-check the shared secret on the server and the NAS!"); break; } p += size; } } } #ifdef WITH_SESSION_MGMT if (result >= 0 && (check_item = pairfind(request->config_items, PW_SIMULTANEOUS_USE, 0, TAG_ANY)) != NULL) { int r, session_type = 0; char logstr[1024]; char umsg[MAX_STRING_LEN + 1]; const char *user_msg = NULL; tmp = pairfind(request->config_items, PW_SESSION_TYPE, 0, TAG_ANY); if (tmp) { session_type = tmp->vp_integer; RDEBUG2("Using Session-Type %s", dict_valnamebyattr(PW_SESSION_TYPE, 0, session_type)); } /* * User authenticated O.K. Now we have to check * for the Simultaneous-Use parameter. */ if (namepair && (r = module_checksimul(session_type, request, check_item->vp_integer)) != 0) { char mpp_ok = 0; if (r == 2){ /* Multilink attempt. Check if port-limit > simultaneous-use */ VALUE_PAIR *port_limit; if ((port_limit = pairfind(request->reply->vps, PW_PORT_LIMIT, 0, TAG_ANY)) != NULL && port_limit->vp_integer > check_item->vp_integer){ RDEBUG2("MPP is OK"); mpp_ok = 1; } } if (!mpp_ok){ if (check_item->vp_integer > 1) { snprintf(umsg, sizeof(umsg), "\r\nYou are already logged in %d times - access denied\r\n\n", (int)check_item->vp_integer); user_msg = umsg; } else { user_msg = "\r\nYou are already logged in - access denied\r\n\n"; } request->reply->code = PW_AUTHENTICATION_REJECT; /* * They're trying to log in too many times. * Remove ALL reply attributes. */ pairfree(&request->reply->vps); radius_pairmake(request, &request->reply->vps, "Reply-Message", user_msg, T_OP_SET); snprintf(logstr, sizeof(logstr), "Multiple logins (max %d) %s", check_item->vp_integer, r == 2 ? "[MPP attempt]" : ""); rad_authlog(logstr, request, 1); result = -1; } } } #endif /* * Result should be >= 0 here - if not, it means the user * is rejected, so we just process post-auth and return. */ if (result < 0) { return RLM_MODULE_REJECT; } /* * Set the reply to Access-Accept, if it hasn't already * been set to something. (i.e. Access-Challenge) */ if (request->reply->code == 0) request->reply->code = PW_AUTHENTICATION_ACK; if ((module_msg = pairfind(request->packet->vps, PW_MODULE_SUCCESS_MESSAGE, 0, TAG_ANY)) != NULL){ char msg[MAX_STRING_LEN+12]; snprintf(msg, sizeof(msg), "Login OK (%s)", module_msg->vp_strvalue); rad_authlog(msg, request, 1); } else { rad_authlog("Login OK", request, 1); } return result; }
/* * EAP authorization DEPENDS on other rlm authorizations, * to check for user existance & get their configured values. * It Handles EAP-START Messages, User-Name initilization. */ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request) { rlm_eap_t *inst; int status; VALUE_PAIR *vp; inst = (rlm_eap_t *)instance; #ifdef WITH_PROXY /* * We don't do authorization again, once we've seen the * proxy reply (or the proxied packet) */ if (request->proxy != NULL) return RLM_MODULE_NOOP; #endif /* * For EAP_START, send Access-Challenge with EAP Identity * request. even when we have to proxy this request * * RFC 2869, Section 2.3.1 notes that the "domain" of the * user, (i.e. where to proxy him) comes from the EAP-Identity, * so we CANNOT proxy the user, until we know his identity. * * We therefore send an EAP Identity request. */ status = eap_start(inst, request); switch(status) { case EAP_NOOP: return RLM_MODULE_NOOP; case EAP_FAIL: return RLM_MODULE_FAIL; case EAP_FOUND: return RLM_MODULE_HANDLED; case EAP_OK: case EAP_NOTFOUND: default: break; } /* * RFC 2869, Section 2.3.1. If a NAS sends an EAP-Identity, * it MUST copy the identity into the User-Name attribute. * * But we don't worry about that too much. We depend on * each EAP sub-module to look for handler->request->username, * and to get excited if it doesn't appear. */ vp = pairfind(request->config_items, PW_AUTH_TYPE, 0, TAG_ANY); if ((!vp) || (vp->vp_integer != PW_AUTHTYPE_REJECT)) { vp = pairmake_config("Auth-Type", inst->xlat_name, T_OP_EQ); if (!vp) { RDEBUG2("Failed to create Auth-Type %s: %s\n", inst->xlat_name, fr_strerror()); return RLM_MODULE_FAIL; } } else { RDEBUG2W("Auth-Type already set. Not setting to EAP"); } if (status == EAP_OK) return RLM_MODULE_OK; return RLM_MODULE_UPDATED; }
/** Copy packet to multiple servers * * Create a duplicate of the packet and send it to a list of realms * defined by the presence of the Replicate-To-Realm VP in the control * list of the current request. * * This is pretty hacky and is 100% fire and forget. If you're looking * to forward authentication requests to multiple realms and process * the responses, this function will not allow you to do that. * * @param[in] instance of this module. * @param[in] request The current request. * @param[in] list of attributes to copy to the duplicate packet. * @param[in] code to write into the code field of the duplicate packet. * @return RCODE fail on error, invalid if list does not exist, noop if no * replications succeeded, else ok. */ static int replicate_packet(UNUSED void *instance, REQUEST *request, pair_lists_t list, unsigned int code) { int rcode = RLM_MODULE_NOOP; VALUE_PAIR *vp, **vps, *last; home_server *home; REALM *realm; home_pool_t *pool; RADIUS_PACKET *packet = NULL; 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) { RDEBUG2E("Cannot Replicate to unknown realm %s", realm); continue; } /* * We shouldn't really do this on every loop. */ switch (request->packet->code) { default: RDEBUG2E("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) { RDEBUG2W("Cancelling replication to Realm %s, as the realm is local.", realm->name); continue; } home = home_server_ldb(realm->name, pool, request); if (!home) { RDEBUG2E("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(NULL, 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) { RDEBUGE("Failed opening socket: %s", fr_strerror()); rcode = RLM_MODULE_FAIL; goto done; } vps = radius_list(request, list); if (!vps) { RDEBUGW("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(packet, *vps); if (!packet->vps) { rcode = RLM_MODULE_FAIL; goto done; } } /* * For CHAP, create the CHAP-Challenge if * it doesn't exist. */ if ((code == PW_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); 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) { RDEBUGE("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; }
/** Decode an attribute name into a string * * This expands the various formats: * - %{Name} * - %{xlat:name} * - %{Name:-Other} * * Calls radius_xlat() to do most of the work. * * @param[in] from string to expand. * @param[in,out] to buffer for output. * @param[in] freespace remaining in output buffer. * @param[in] request Current server request. * @param[in] func Optional function to escape output; passed to radius_xlat(). * @param[in] funcarg pointer to pass to escape function. * @return 0 on success, -1 on failure. */ static int decode_attribute(const char **from, char **to, int freespace, REQUEST *request, RADIUS_ESCAPE_STRING func, void *funcarg) { int do_length = 0; const char *module_name, *xlat_str; char *p, *q, *l, *next = NULL; int retlen=0; const xlat_t *c; int varlen; char buffer[8192]; q = *to; *q = '\0'; /* * Copy the input string to an intermediate buffer where * we can mangle it. */ varlen = rad_copy_variable(buffer, *from); if (varlen < 0) { RDEBUG2E("Badly formatted variable: %s", *from); return -1; } *from += varlen; /* * Kill the %{} around the data we are looking for. */ p = buffer; p[varlen - 1] = '\0'; /* */ p += 2; if (*p == '#') { p++; do_length = 1; } /* * Handle %{%{foo}:-%{bar}}, which is useful, too. * * Did I mention that this parser is garbage? */ if ((p[0] == '%') && (p[1] == '{')) { int len1, len2; int expand2 = FALSE; /* * 'p' is after the start of 'buffer', so we can * safely do this. */ len1 = rad_copy_variable(buffer, p); if (len1 < 0) { RDEBUG2E("Badly formatted variable: %s", p); return -1; } /* * They did %{%{foo}}, which is stupid, but allowed. */ if (!p[len1]) { RDEBUG2("Improperly nested variable; %%{%s}", p); return -1; } /* * It SHOULD be %{%{foo}:-%{bar}}. If not, it's * an error. */ if ((p[len1] != ':') || (p[len1 + 1] != '-')) { RDEBUG2("No trailing :- after variable at %s", p); return -1; } /* * Parse the second bit. The second bit can be * either %{foo}, or a string "foo", or a string * 'foo', or just a bare word: foo */ p += len1 + 2; l = buffer + len1 + 1; if ((p[0] == '%') && (p[1] == '{')) { len2 = rad_copy_variable(l, p); if (len2 < 0) { RDEBUG2E("Invalid text after :- at %s", p); return -1; } p += len2; expand2 = TRUE; } else if ((p[0] == '"') || p[0] == '\'') { getstring((const char **) &p, l, strlen(l)); } else { l = p; } /* * Expand the first one. If we did, exit the * conditional. */ retlen = radius_xlat(q, freespace, buffer, request, func, funcarg); if (retlen) { q += retlen; goto done; } RDEBUG2("\t... expanding second conditional"); /* * Expand / copy the second string if required. */ if (expand2) { retlen = radius_xlat(q, freespace, l, request, func, funcarg); if (retlen) { q += retlen; } } else { strlcpy(q, l, freespace); q += strlen(q); } /* * Else the output is an empty string. */ goto done; } /* * See if we're supposed to expand a module name. */ module_name = NULL; for (l = p; *l != '\0'; l++) { /* * module:string */ if (*l == ':') { module_name = p; /* start of name */ *l = '\0'; p = l + 1; break; } /* * Module names can't have spaces. */ if ((*l == ' ') || (*l == '\t')) break; } /* * %{name} is a simple attribute reference, * or regex reference. */ if (!module_name) { if (isdigit(*p) && !p[1]) { /* regex 0..8 */ module_name = xlat_str = p; } else { xlat_str = p; } goto do_xlat; } /* * Maybe it's the old-style %{foo:-bar} */ if (*p == '-') { RDEBUG2W("Deprecated conditional expansion \":-\". See \"man unlang\" for details"); p++; xlat_str = module_name; next = p; goto do_xlat; } /* * FIXME: For backwards "WTF" compatibility, check for * {...}, (after the :), and copy that, too. */ /* module name, followed by (possibly) per-module string */ xlat_str = p; do_xlat: /* * Just "foo". Maybe it's a magic attr, which doesn't * really exist. * * If we can't find that, then assume it's a dictionary * attribute in the request. * * Else if it's module:foo, look for module, and pass it "foo". */ if (!module_name) { c = xlat_find(xlat_str); if (!c) c = xlat_find("request"); } else { c = xlat_find(module_name); } if (!c) { if (!module_name) { RDEBUG2W("Unknown Attribute \"%s\" in string expansion \"%%%s\"", xlat_str, *from); } else { RDEBUG2W("Unknown module \"%s\" in string expansion \"%%%s\"", module_name, *from); } return -1; } if (!c->internal) RDEBUG3("radius_xlat: Running registered xlat function of module %s for string \'%s\'", c->module, xlat_str); if (func) { /* xlat to a temporary buffer, then escape */ char tmpbuf[8192]; retlen = c->do_xlat(c->instance, request, xlat_str, tmpbuf, sizeof(tmpbuf)); if (retlen > 0) { retlen = func(request, q, freespace, tmpbuf, funcarg); if (retlen > 0) { RDEBUG2("\tescape: \'%s\' -> \'%s\'", tmpbuf, q); } else if (retlen < 0) { RDEBUG2("String escape failed"); } } } else { retlen = c->do_xlat(c->instance, request, xlat_str, q, freespace); } if (retlen > 0) { if (do_length) { snprintf(q, freespace, "%d", retlen); retlen = strlen(q); } } else if (next) { /* * Expand the second bit. */ RDEBUG2("\t... expanding second conditional"); retlen = radius_xlat(q, freespace, next, request, func, funcarg); } q += retlen; done: *to = q; return 0; }
/** Replace %whatever in a string. * * See 'doc/variables.txt' for more information. * * @param[out] out output buffer. * @param[in] outlen size of output buffer. * @param[in] fmt string to expand. * @param[in] request current request. * @param[in] func function to escape final value e.g. SQL quoting. * @param[in] funcarg pointer to pass to escape function. * @return length of string written @bug should really have -1 for failure */ size_t radius_xlat(char *out, int outlen, const char *fmt, REQUEST *request, RADIUS_ESCAPE_STRING func, void *funcarg) { int c, len, freespace; const char *p; char *q; char *nl; VALUE_PAIR *tmp; struct tm *TM, s_TM; char tmpdt[40]; /* For temporary storing of dates */ /* * Catch bad modules. */ if (!fmt || !out || !request) return 0; q = out; p = fmt; while (*p) { /* Calculate freespace in output */ freespace = outlen - (q - out); if (freespace <= 1) break; c = *p; if ((c != '%') && (c != '$') && (c != '\\')) { /* * We check if we're inside an open brace. If we are * then we assume this brace is NOT literal, but is * a closing brace and apply it */ *q++ = *p++; continue; } /* * There's nothing after this character, copy * the last '%' or "$' or '\\' over to the output * buffer, and exit. */ if (*++p == '\0') { *q++ = c; break; } if (c == '\\') { switch(*p) { case '\\': *q++ = *p; break; case 't': *q++ = '\t'; break; case 'n': *q++ = '\n'; break; default: *q++ = c; *q++ = *p; break; } p++; } else if (c == '%') switch(*p) { case '{': p--; if (decode_attribute(&p, &q, freespace, request, func, funcarg) < 0) return 0; break; case '%': *q++ = *p++; break; case 'd': /* request day */ TM = localtime_r(&request->timestamp, &s_TM); len = strftime(tmpdt, sizeof(tmpdt), "%d", TM); if (len > 0) { strlcpy(q, tmpdt, freespace); q += strlen(q); } p++; break; case 'l': /* request timestamp */ snprintf(tmpdt, sizeof(tmpdt), "%lu", (unsigned long) request->timestamp); strlcpy(q,tmpdt,freespace); q += strlen(q); p++; break; case 'm': /* request month */ TM = localtime_r(&request->timestamp, &s_TM); len = strftime(tmpdt, sizeof(tmpdt), "%m", TM); if (len > 0) { strlcpy(q, tmpdt, freespace); q += strlen(q); } p++; break; case 't': /* request timestamp */ CTIME_R(&request->timestamp, tmpdt, sizeof(tmpdt)); nl = strchr(tmpdt, '\n'); if (nl) *nl = '\0'; strlcpy(q, tmpdt, freespace); q += strlen(q); p++; break; case 'C': /* ClientName */ strlcpy(q,request->client->shortname,freespace); q += strlen(q); p++; break; case 'D': /* request date */ TM = localtime_r(&request->timestamp, &s_TM); len = strftime(tmpdt, sizeof(tmpdt), "%Y%m%d", TM); if (len > 0) { strlcpy(q, tmpdt, freespace); q += strlen(q); } p++; break; case 'G': /* request minute */ TM = localtime_r(&request->timestamp, &s_TM); len = strftime(tmpdt, sizeof(tmpdt), "%M", TM); if (len > 0) { strlcpy(q, tmpdt, freespace); q += strlen(q); } p++; break; case 'H': /* request hour */ TM = localtime_r(&request->timestamp, &s_TM); len = strftime(tmpdt, sizeof(tmpdt), "%H", TM); if (len > 0) { strlcpy(q, tmpdt, freespace); q += strlen(q); } p++; break; case 'I': /* Request ID */ snprintf(tmpdt, sizeof(tmpdt), "%i", request->packet->id); strlcpy(q, tmpdt, freespace); q += strlen(q); p++; break; case 'S': /* request timestamp in SQL format*/ TM = localtime_r(&request->timestamp, &s_TM); len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d %H:%M:%S", TM); if (len > 0) { strlcpy(q, tmpdt, freespace); q += strlen(q); } p++; break; case 'T': /* request timestamp */ TM = localtime_r(&request->timestamp, &s_TM); len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d-%H.%M.%S.000000", TM); if (len > 0) { strlcpy(q, tmpdt, freespace); q += strlen(q); } p++; break; case 'V': /* Request-Authenticator */ strlcpy(q,"Verified",freespace); q += strlen(q); p++; break; case 'Y': /* request year */ TM = localtime_r(&request->timestamp, &s_TM); len = strftime(tmpdt, sizeof(tmpdt), "%Y", TM); if (len > 0) { strlcpy(q, tmpdt, freespace); q += strlen(q); } p++; break; case 'Z': /* Full request pairs except password */ tmp = request->packet->vps; while (tmp && (freespace > 3)) { if (!(!tmp->da->vendor && (tmp->da->attr == PW_USER_PASSWORD))) { *q++ = '\t'; len = vp_prints(q, freespace - 2, tmp); q += len; freespace -= (len + 2); *q++ = '\n'; } tmp = tmp->next; } p++; break; default: RDEBUG2W("Unknown variable '%%%c': See 'doc/variables.txt'", *p); if (freespace > 2) { *q++ = '%'; *q++ = *p++; } break; } } *q = '\0'; RDEBUG2("\texpand: '%s' -> '%s'", fmt, out); return strlen(out); }
/* * Process the "diameter" contents of the tunneled data. */ int eapttls_process(EAP_HANDLER *handler, tls_session_t *tls_session) { int rcode = PW_AUTHENTICATION_REJECT; REQUEST *fake; VALUE_PAIR *vp; ttls_tunnel_t *t; const uint8_t *data; size_t data_len; REQUEST *request = handler->request; rad_assert(request != NULL); /* * Just look at the buffer directly, without doing * record_minus. */ data_len = tls_session->clean_out.used; tls_session->clean_out.used = 0; data = tls_session->clean_out.data; t = (ttls_tunnel_t *) tls_session->opaque; /* * If there's no data, maybe this is an ACK to an * MS-CHAP2-Success. */ if (data_len == 0) { if (t->authenticated) { RDEBUG("Got ACK, and the user was already authenticated."); return PW_AUTHENTICATION_ACK; } /* else no session, no data, die. */ /* * FIXME: Call SSL_get_error() to see what went * wrong. */ RDEBUG2("SSL_read Error"); return PW_AUTHENTICATION_REJECT; } #ifndef NDEBUG if ((debug_flag > 2) && fr_log_fp) { size_t i; for (i = 0; i < data_len; i++) { if ((i & 0x0f) == 0) fprintf(fr_log_fp, " TTLS tunnel data in %04x: ", (int) i); fprintf(fr_log_fp, "%02x ", data[i]); if ((i & 0x0f) == 0x0f) fprintf(fr_log_fp, "\n"); } if ((data_len & 0x0f) != 0) fprintf(fr_log_fp, "\n"); } #endif if (!diameter_verify(request, data, data_len)) { return PW_AUTHENTICATION_REJECT; } /* * Allocate a fake REQUEST structe. */ fake = request_alloc_fake(request); rad_assert(fake->packet->vps == NULL); /* * Add the tunneled attributes to the fake request. */ fake->packet->vps = diameter2vp(request, tls_session->ssl, data, data_len); if (!fake->packet->vps) { request_free(&fake); return PW_AUTHENTICATION_REJECT; } /* * Tell the request that it's a fake one. */ vp = pairmake("Freeradius-Proxied-To", "127.0.0.1", T_OP_EQ); if (vp) { pairadd(&fake->packet->vps, vp); } if ((debug_flag > 0) && fr_log_fp) { RDEBUG("Got tunneled request"); debug_pair_list(fake->packet->vps); } /* * Update other items in the REQUEST data structure. */ fake->username = pairfind(fake->packet->vps, PW_USER_NAME, 0, TAG_ANY); fake->password = pairfind(fake->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY); /* * No User-Name, try to create one from stored data. */ if (!fake->username) { /* * No User-Name in the stored data, look for * an EAP-Identity, and pull it out of there. */ if (!t->username) { vp = pairfind(fake->packet->vps, PW_EAP_MESSAGE, 0, TAG_ANY); if (vp && (vp->length >= EAP_HEADER_LEN + 2) && (vp->vp_strvalue[0] == PW_EAP_RESPONSE) && (vp->vp_strvalue[EAP_HEADER_LEN] == PW_EAP_IDENTITY) && (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) { /* * Create & remember a User-Name */ t->username = pairmake("User-Name", "", T_OP_EQ); rad_assert(t->username != NULL); memcpy(t->username->vp_strvalue, vp->vp_strvalue + 5, vp->length - 5); t->username->length = vp->length - 5; t->username->vp_strvalue[t->username->length] = 0; RDEBUG("Got tunneled identity of %s", t->username->vp_strvalue); /* * If there's a default EAP type, * set it here. */ if (t->default_eap_type != 0) { RDEBUG("Setting default EAP type for tunneled EAP session."); vp = paircreate(PW_EAP_TYPE, 0); rad_assert(vp != NULL); vp->vp_integer = t->default_eap_type; pairadd(&fake->config_items, vp); } } else { /* * Don't reject the request outright, * as it's permitted to do EAP without * user-name. */ RDEBUG2W("No EAP-Identity found to start EAP conversation."); } } /* else there WAS a t->username */ if (t->username) { vp = paircopy(t->username); pairadd(&fake->packet->vps, vp); fake->username = pairfind(fake->packet->vps, PW_USER_NAME, 0, TAG_ANY); } } /* else the request ALREADY had a User-Name */ /* * Add the State attribute, too, if it exists. */ if (t->state) { vp = paircopy(t->state); if (vp) pairadd(&fake->packet->vps, vp); } /* * If this is set, we copy SOME of the request attributes * from outside of the tunnel to inside of the tunnel. * * We copy ONLY those attributes which do NOT already * exist in the tunneled request. */ if (t->copy_request_to_tunnel) { VALUE_PAIR *copy; for (vp = request->packet->vps; vp != NULL; vp = vp->next) { /* * The attribute is a server-side thingy, * don't copy it. */ if ((vp->da->attr > 255) && (vp->da->vendor == 0)) { continue; } /* * The outside attribute is already in the * tunnel, don't copy it. * * This works for BOTH attributes which * are originally in the tunneled request, * AND attributes which are copied there * from below. */ if (pairfind(fake->packet->vps, vp->da->attr, vp->da->vendor, TAG_ANY)) { continue; } /* * Some attributes are handled specially. */ switch (vp->da->attr) { /* * NEVER copy Message-Authenticator, * EAP-Message, or State. They're * only for outside of the tunnel. */ case PW_USER_NAME: case PW_USER_PASSWORD: case PW_CHAP_PASSWORD: case PW_CHAP_CHALLENGE: case PW_PROXY_STATE: case PW_MESSAGE_AUTHENTICATOR: case PW_EAP_MESSAGE: case PW_STATE: continue; break; /* * By default, copy it over. */ default: break; } /* * Don't copy from the head, we've already * checked it. */ copy = paircopy2(vp, vp->da->attr, vp->da->vendor, TAG_ANY); pairadd(&fake->packet->vps, copy); } } if ((vp = pairfind(request->config_items, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { fake->server = vp->vp_strvalue; } else if (t->virtual_server) { fake->server = t->virtual_server; } /* else fake->server == request->server */ if ((debug_flag > 0) && fr_log_fp) { RDEBUG("Sending tunneled request"); debug_pair_list(fake->packet->vps); fprintf(fr_log_fp, "server %s {\n", (fake->server == NULL) ? "" : fake->server); } /* * Call authentication recursively, which will * do PAP, CHAP, MS-CHAP, etc. */ rad_virtual_server(fake); /* * Note that we don't do *anything* with the reply * attributes. */ if ((debug_flag > 0) && fr_log_fp) { fprintf(fr_log_fp, "} # server %s\n", (fake->server == NULL) ? "" : fake->server); RDEBUG("Got tunneled reply code %d", fake->reply->code); debug_pair_list(fake->reply->vps); } /* * Decide what to do with the reply. */ switch (fake->reply->code) { case 0: /* No reply code, must be proxied... */ #ifdef WITH_PROXY vp = pairfind(fake->config_items, PW_PROXY_TO_REALM, 0, TAG_ANY); if (vp) { eap_tunnel_data_t *tunnel; RDEBUG("Tunneled authentication will be proxied to %s", vp->vp_strvalue); /* * Tell the original request that it's going * to be proxied. */ pairmove2(&(request->config_items), &(fake->config_items), PW_PROXY_TO_REALM, 0, TAG_ANY); /* * Seed the proxy packet with the * tunneled request. */ rad_assert(request->proxy == NULL); request->proxy = fake->packet; memset(&request->proxy->src_ipaddr, 0, sizeof(request->proxy->src_ipaddr)); memset(&request->proxy->src_ipaddr, 0, sizeof(request->proxy->src_ipaddr)); request->proxy->src_port = 0; request->proxy->dst_port = 0; fake->packet = NULL; rad_free(&fake->reply); fake->reply = NULL; /* * Set up the callbacks for the tunnel */ tunnel = rad_malloc(sizeof(*tunnel)); memset(tunnel, 0, sizeof(*tunnel)); tunnel->tls_session = tls_session; tunnel->callback = eapttls_postproxy; /* * Associate the callback with the request. */ rcode = request_data_add(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK, tunnel, free); rad_assert(rcode == 0); /* * rlm_eap.c has taken care of associating * the handler with the fake request. * * So we associate the fake request with * this request. */ rcode = request_data_add(request, request->proxy, REQUEST_DATA_EAP_MSCHAP_TUNNEL_CALLBACK, fake, my_request_free); rad_assert(rcode == 0); fake = NULL; /* * Didn't authenticate the packet, but * we're proxying it. */ rcode = PW_STATUS_CLIENT; } else #endif /* WITH_PROXY */ { RDEBUG("No tunneled reply was found for request %d , and the request was not proxied: rejecting the user.", request->number); rcode = PW_AUTHENTICATION_REJECT; } break; default: /* * Returns RLM_MODULE_FOO, and we want to return * PW_FOO */ rcode = process_reply(handler, tls_session, request, fake->reply); switch (rcode) { case RLM_MODULE_REJECT: rcode = PW_AUTHENTICATION_REJECT; break; case RLM_MODULE_HANDLED: rcode = PW_ACCESS_CHALLENGE; break; case RLM_MODULE_OK: rcode = PW_AUTHENTICATION_ACK; break; default: rcode = PW_AUTHENTICATION_REJECT; break; } break; } request_free(&fake); return rcode; }
/* * Convert diameter attributes to our VALUE_PAIR's */ static VALUE_PAIR *diameter2vp(REQUEST *request, SSL *ssl, const uint8_t *data, size_t data_len) { uint32_t attr; uint32_t vendor; uint32_t length; size_t offset; size_t size; size_t data_left = data_len; VALUE_PAIR *first = NULL; VALUE_PAIR **last = &first; VALUE_PAIR *vp; while (data_left > 0) { rad_assert(data_left <= data_len); memcpy(&attr, data, sizeof(attr)); data += 4; attr = ntohl(attr); vendor = 0; memcpy(&length, data, sizeof(length)); data += 4; length = ntohl(length); /* * A "vendor" flag, with a vendor ID of zero, * is equivalent to no vendor. This is stupid. */ offset = 8; if ((length & (1 << 31)) != 0) { memcpy(&vendor, data, sizeof(vendor)); vendor = ntohl(vendor); data += 4; /* skip the vendor field, it's zero */ offset += 4; /* offset to value field */ if (attr > 65535) goto next_attr; if (vendor > FR_MAX_VENDOR) goto next_attr; } /* * FIXME: Handle the M bit. For now, we assume that * some other module takes care of any attribute * with the M bit set. */ /* * Get the length. */ length &= 0x00ffffff; /* * Get the size of the value portion of the * attribute. */ size = length - offset; /* * Vendor attributes can be larger than 255. * Normal attributes cannot be. */ if ((attr > 255) && (vendor == 0)) { RDEBUG2W("Skipping Diameter attribute %u", attr); goto next_attr; } /* * EAP-Message AVPs can be larger than 253 octets. */ if ((size > 253) && !((vendor == 0) && (attr == PW_EAP_MESSAGE))) { RDEBUG2W("diameter2vp skipping long attribute %u", attr); goto next_attr; } /* * RADIUS VSAs are handled as Diameter attributes * with Vendor-Id == 0, and the VSA data packed * into the "String" field as per normal. * * EXCEPT for the MS-CHAP attributes. */ if ((vendor == 0) && (attr == PW_VENDOR_SPECIFIC)) { ssize_t decoded; uint8_t buffer[256]; buffer[0] = PW_VENDOR_SPECIFIC; buffer[1] = size + 2; memcpy(buffer + 2, data, size); vp = NULL; decoded = rad_attr2vp(NULL, NULL, NULL, buffer, size + 2, &vp); if (decoded < 0) { RDEBUG2E("diameter2vp failed decoding attr: %s", fr_strerror()); goto do_octets; } if ((size_t) decoded != size + 2) { RDEBUG2E("diameter2vp failed to entirely decode VSA"); pairfree(&vp); goto do_octets; } *last = vp; do { last = &(vp->next); vp = vp->next; } while (vp != NULL); goto next_attr; } /* * Create it. If this fails, it's because we're OOM. */ do_octets: vp = paircreate(attr, vendor); if (!vp) { RDEBUG2("Failure in creating VP"); pairfree(&first); return NULL; } /* * If it's a type from our dictionary, then * we need to put the data in a relevant place. */ switch (vp->da->type) { case PW_TYPE_INTEGER: case PW_TYPE_DATE: if (size != vp->length) { const DICT_ATTR *da; /* * Bad format. Create a "raw" * attribute. */ raw: if (vp) pairfree(&vp); da = dict_attrunknown(attr, vendor, TRUE); if (!da) return NULL; vp = pairalloc(NULL, da); if (size >= 253) size = 253; vp->length = size; memcpy(vp->vp_octets, data, vp->length); break; } memcpy(&vp->vp_integer, data, vp->length); /* * Stored in host byte order: change it. */ vp->vp_integer = ntohl(vp->vp_integer); break; case PW_TYPE_INTEGER64: if (size != vp->length) goto raw; memcpy(&vp->vp_integer64, data, vp->length); /* * Stored in host byte order: change it. */ vp->vp_integer64 = ntohll(vp->vp_integer64); break; case PW_TYPE_IPADDR: if (size != vp->length) { RDEBUG2("Invalid length attribute %d", attr); pairfree(&first); pairfree(&vp); return NULL; } memcpy(&vp->vp_ipaddr, data, vp->length); /* * Stored in network byte order: don't change it. */ break; case PW_TYPE_BYTE: if (size != vp->length) goto raw; vp->vp_integer = data[0]; break; case PW_TYPE_SHORT: if (size != vp->length) goto raw; vp->vp_integer = (data[0] * 256) + data[1]; break; case PW_TYPE_SIGNED: if (size != vp->length) goto raw; memcpy(&vp->vp_signed, data, vp->length); vp->vp_signed = ntohl(vp->vp_signed); break; case PW_TYPE_IPV6ADDR: if (size != vp->length) goto raw; memcpy(&vp->vp_ipv6addr, data, vp->length); break; case PW_TYPE_IPV6PREFIX: if (size != vp->length) goto raw; memcpy(&vp->vp_ipv6prefix, data, vp->length); break; /* * String, octet, etc. Copy the data from the * value field over verbatim. */ case PW_TYPE_OCTETS: if (attr == PW_EAP_MESSAGE) { const uint8_t *eap_message = data; /* * vp exists the first time around. */ while (1) { vp->length = size; if (vp->length > 253) vp->length = 253; memcpy(vp->vp_octets, eap_message, vp->length); size -= vp->length; eap_message += vp->length; *last = vp; last = &(vp->next); if (size == 0) break; vp = paircreate(attr, vendor); if (!vp) { RDEBUG2("Failure in creating VP"); pairfree(&first); return NULL; } } goto next_attr; } /* else it's another kind of attribute */ /* FALL-THROUGH */ default: if (size >= 253) size = 253; vp->length = size; memcpy(vp->vp_strvalue, data, vp->length); break; } /* * User-Password is NUL padded to a multiple * of 16 bytes. Let's chop it to something * more reasonable. * * NOTE: This means that the User-Password * attribute CANNOT EVER have embedded zeros in it! */ if ((vp->da->vendor == 0) && (vp->da->attr == PW_USER_PASSWORD)) { /* * If the password is exactly 16 octets, * it won't be zero-terminated. */ vp->vp_strvalue[vp->length] = '\0'; vp->length = strlen(vp->vp_strvalue); } /* * Ensure that the client is using the * correct challenge. This weirdness is * to protect against against replay * attacks, where anyone observing the * CHAP exchange could pose as that user, * by simply choosing to use the same * challenge. * * By using a challenge based on * information from the current session, * we can guarantee that the client is * not *choosing* a challenge. * * We're a little forgiving in that we * have loose checks on the length, and * we do NOT check the Id (first octet of * the response to the challenge) * * But if the client gets the challenge correct, * we're not too worried about the Id. */ if (((vp->da->vendor == 0) && (vp->da->attr == PW_CHAP_CHALLENGE)) || ((vp->da->vendor == VENDORPEC_MICROSOFT) && (vp->da->attr == PW_MSCHAP_CHALLENGE))) { uint8_t challenge[16]; if ((vp->length < 8) || (vp->length > 16)) { RDEBUG("Tunneled challenge has invalid length"); pairfree(&first); pairfree(&vp); return NULL; } eapttls_gen_challenge(ssl, challenge, sizeof(challenge)); if (memcmp(challenge, vp->vp_octets, vp->length) != 0) { RDEBUG("Tunneled challenge is incorrect"); pairfree(&first); pairfree(&vp); return NULL; } } /* * Update the list. */ *last = vp; last = &(vp->next); next_attr: /* * Catch non-aligned attributes. */ if (data_left == length) break; /* * The length does NOT include the padding, so * we've got to account for it here by rounding up * to the nearest 4-byte boundary. */ length += 0x03; length &= ~0x03; rad_assert(data_left >= length); data_left -= length; data += length - offset; /* already updated */ } /* * We got this far. It looks OK. */ return first; }
/* * Dispatch an exec method */ static rlm_rcode_t exec_dispatch(void *instance, REQUEST *request) { rlm_exec_t *inst = (rlm_exec_t *) instance; int result; VALUE_PAIR **input_pairs, **output_pairs; VALUE_PAIR *answer = NULL; char msg[1024]; size_t len; /* * We need a program to execute. */ if (!inst->program) { radlog(L_ERR, "rlm_exec (%s): We require a program to execute", inst->xlat_name); return RLM_MODULE_FAIL; } /* * See if we're supposed to execute it now. */ if (!((inst->packet_code == 0) || (request->packet->code == inst->packet_code) || (request->reply->code == inst->packet_code) #ifdef WITH_PROXY || (request->proxy && (request->proxy->code == inst->packet_code)) || (request->proxy_reply && (request->proxy_reply->code == inst->packet_code)) #endif )) { RDEBUG2("Packet type is not %s. Not executing.", inst->packet_type); return RLM_MODULE_NOOP; } /* * Decide what input/output the program takes. */ input_pairs = decode_string(request, inst->input); output_pairs = decode_string(request, inst->output); if (!input_pairs) { RDEBUG2W("Possible parse error in %s", inst->input); return RLM_MODULE_NOOP; } /* * It points to the attribute list, but the attribute * list is empty. */ if (!*input_pairs) { RDEBUG2W("Input pairs are empty. No attributes will be passed to the script"); } /* * This function does it's own xlat of the input program * to execute. * * FIXME: if inst->program starts with %{, then * do an xlat ourselves. This will allow us to do * program = %{Exec-Program}, which this module * xlat's into it's string value, and then the * exec program function xlat's it's string value * into something else. */ result = radius_exec_program(inst->program, request, inst->wait, msg, sizeof(msg), *input_pairs, &answer, inst->shell_escape); if (result < 0) { radlog(L_ERR, "rlm_exec (%s): External script failed", inst->xlat_name); return RLM_MODULE_FAIL; } /* * Move the answer over to the output pairs. * * If we're not waiting, then there are no output pairs. */ if (output_pairs) pairmove(request, output_pairs, &answer); pairfree(&answer); if (result == 0) { return RLM_MODULE_OK; } if (result > RLM_MODULE_NUMCODES) { return RLM_MODULE_FAIL; } /* * Write any exec output to module failure message */ if (*msg) { /* Trim off returns and newlines */ len = strlen(msg); if (msg[len - 1] == '\n' || msg[len - 1] == '\r') { msg[len - 1] = '\0'; } RDEBUGE("%s", msg); } return result-1; }