/* * This hack strips out Cisco's VSA duplicities in lines * (Cisco not implemented VSA's in standard way. * * Cisco sends it's VSA attributes with the attribute name *again* * in the string, like: H323-Attribute = "h323-attribute=value". * This sort of behaviour is nonsense. */ static void cisco_vsa_hack(REQUEST *request) { int vendorcode; char *ptr; char newattr[MAX_STRING_LEN]; VALUE_PAIR *vp; vp_cursor_t cursor; for (vp = fr_cursor_init(&cursor, &request->packet->vps); vp; vp = fr_cursor_next(&cursor)) { vendorcode = vp->da->vendor; if (!((vendorcode == 9) || (vendorcode == 6618))) { continue; /* not a Cisco or Quintum VSA, continue */ } if (vp->da->type != PW_TYPE_STRING) { continue; } /* * No weird packing. Ignore it. */ ptr = strchr(vp->vp_strvalue, '='); /* find an '=' */ if (!ptr) { continue; } /* * Cisco-AVPair's get packed as: * * Cisco-AVPair = "h323-foo-bar = baz" * Cisco-AVPair = "h323-foo-bar=baz" * * which makes sense only if you're a lunatic. * This code looks for the attribute named inside * of the string, and if it exists, adds it as a new * attribute. */ if (vp->da->attr == 1) { char const *p; p = vp->vp_strvalue; gettoken(&p, newattr, sizeof(newattr), false); if (dict_attrbyname(newattr) != NULL) { pairmake_packet(newattr, ptr + 1, T_OP_EQ); } } else { /* h322-foo-bar = "h323-foo-bar = baz" */ /* * We strip out the duplicity from the * value field, we use only the value on * the right side of the '=' character. */ pairstrcpy(vp, ptr + 1); } } }
/* * See if we have access to the huntgroup. */ static int huntgroup_access(REQUEST *request, PAIR_LIST *huntgroups) { PAIR_LIST *i; int r = RLM_MODULE_OK; VALUE_PAIR *request_pairs = request->packet->vps; /* * We're not controlling access by huntgroups: * Allow them in. */ if (!huntgroups) { return RLM_MODULE_OK; } for (i = huntgroups; i; i = i->next) { /* * See if this entry matches. */ if (paircompare(request, request_pairs, i->check, NULL) != 0) { continue; } /* * Now check for access. */ r = RLM_MODULE_REJECT; if (hunt_paircmp(request, request_pairs, i->reply) == 0) { VALUE_PAIR *vp; /* * We've matched the huntgroup, so add it in * to the list of request pairs. */ vp = pairfind(request_pairs, PW_HUNTGROUP_NAME, 0, TAG_ANY); if (!vp) { vp = radius_paircreate(request->packet, &request->packet->vps, PW_HUNTGROUP_NAME, 0); pairstrcpy(vp, i->name); } r = RLM_MODULE_OK; } break; } return r; }
static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { rlm_rcode_t rcode = RLM_MODULE_OK; ldap_rcode_t status; int ldap_errno; int i; ldap_instance_t *inst = instance; struct berval **values; VALUE_PAIR *vp; ldap_handle_t *conn; LDAPMessage *result, *entry; char const *dn = NULL; rlm_ldap_map_xlat_t expanded; /* faster than mallocing every time */ if (!request->username) { RDEBUG2("Attribute \"User-Name\" is required for authorization"); return RLM_MODULE_NOOP; } /* * Check for valid input, zero length names not permitted */ if (request->username->vp_length == 0) { RDEBUG2("Zero length username not permitted"); return RLM_MODULE_INVALID; } if (rlm_ldap_map_xlat(request, inst->user_map, &expanded) < 0) { return RLM_MODULE_FAIL; } conn = mod_conn_get(inst, request); if (!conn) return RLM_MODULE_FAIL; /* * Add any additional attributes we need for checking access, memberships, and profiles */ if (inst->userobj_access_attr) { expanded.attrs[expanded.count++] = inst->userobj_access_attr; } if (inst->userobj_membership_attr && (inst->cacheable_group_dn || inst->cacheable_group_name)) { expanded.attrs[expanded.count++] = inst->userobj_membership_attr; } if (inst->profile_attr) { expanded.attrs[expanded.count++] = inst->profile_attr; } if (inst->valuepair_attr) { expanded.attrs[expanded.count++] = inst->valuepair_attr; } expanded.attrs[expanded.count] = NULL; dn = rlm_ldap_find_user(inst, request, &conn, expanded.attrs, true, &result, &rcode); if (!dn) { goto finish; } entry = ldap_first_entry(conn->handle, result); if (!entry) { ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno)); goto finish; } /* * Check for access. */ if (inst->userobj_access_attr) { rcode = rlm_ldap_check_access(inst, request, conn, entry); if (rcode != RLM_MODULE_OK) { goto finish; } } /* * Check if we need to cache group memberships */ if (inst->cacheable_group_dn || inst->cacheable_group_name) { if (inst->userobj_membership_attr) { rcode = rlm_ldap_cacheable_userobj(inst, request, &conn, entry, inst->userobj_membership_attr); if (rcode != RLM_MODULE_OK) { goto finish; } } rcode = rlm_ldap_cacheable_groupobj(inst, request, &conn); if (rcode != RLM_MODULE_OK) { goto finish; } } #ifdef WITH_EDIR /* * We already have a Cleartext-Password. Skip edir. */ if (pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) { goto skip_edir; } /* * Retrieve Universal Password if we use eDirectory */ if (inst->edir) { int res = 0; char password[256]; size_t pass_size = sizeof(password); /* * Retrive universal password */ res = nmasldap_get_password(conn->handle, dn, password, &pass_size); if (res != 0) { REDEBUG("Failed to retrieve eDirectory password: (%i) %s", res, edir_errstr(res)); rcode = RLM_MODULE_FAIL; goto finish; } /* * Add Cleartext-Password attribute to the request */ vp = radius_paircreate(request, &request->config_items, PW_CLEARTEXT_PASSWORD, 0); pairstrcpy(vp, password); vp->vp_length = pass_size; if (RDEBUG_ENABLED3) { RDEBUG3("Added eDirectory password. control:%s += '%s'", vp->da->name, vp->vp_strvalue); } else { RDEBUG2("Added eDirectory password"); } if (inst->edir_autz) { RDEBUG2("Binding as user for eDirectory authorization checks"); /* * Bind as the user */ conn->rebound = true; status = rlm_ldap_bind(inst, request, &conn, dn, vp->vp_strvalue, true); switch (status) { case LDAP_PROC_SUCCESS: rcode = RLM_MODULE_OK; RDEBUG("Bind as user '%s' was successful", dn); break; case LDAP_PROC_NOT_PERMITTED: rcode = RLM_MODULE_USERLOCK; goto finish; case LDAP_PROC_REJECT: rcode = RLM_MODULE_REJECT; goto finish; case LDAP_PROC_BAD_DN: rcode = RLM_MODULE_INVALID; goto finish; case LDAP_PROC_NO_RESULT: rcode = RLM_MODULE_NOTFOUND; goto finish; default: rcode = RLM_MODULE_FAIL; goto finish; }; } } skip_edir: #endif /* * Apply ONE user profile, or a default user profile. */ if (inst->default_profile) { char profile[1024]; if (radius_xlat(profile, sizeof(profile), request, inst->default_profile, NULL, NULL) < 0) { REDEBUG("Failed creating default profile string"); rcode = RLM_MODULE_INVALID; goto finish; } rlm_ldap_map_profile(inst, request, &conn, profile, &expanded); } /* * Apply a SET of user profiles. */ if (inst->profile_attr) { values = ldap_get_values_len(conn->handle, entry, inst->profile_attr); if (values != NULL) { for (i = 0; values[i] != NULL; i++) { char *value; value = rlm_ldap_berval_to_string(request, values[i]); rlm_ldap_map_profile(inst, request, &conn, value, &expanded); talloc_free(value); } ldap_value_free_len(values); } } if (inst->user_map || inst->valuepair_attr) { RDEBUG("Processing user attributes"); RINDENT(); rlm_ldap_map_do(inst, request, conn->handle, &expanded, entry); REXDENT(); rlm_ldap_check_reply(inst, request); } finish: rlm_ldap_map_xlat_free(&expanded); if (result) ldap_msgfree(result); mod_conn_release(inst, conn); return rcode; }
/* * Internal function to cut down on duplicated code. * * Returns -1 on failure, 0 on no failure. returnrealm * is NULL on don't proxy, realm otherwise. */ static int check_for_realm(void *instance, REQUEST *request, REALM **returnrealm) { char *namebuf; char *username; char const *realmname = NULL; char *ptr; VALUE_PAIR *vp; REALM *realm; struct realm_config_t *inst = instance; /* initiate returnrealm */ *returnrealm = NULL; /* * If the request has a proxy entry, then it's a proxy * reply, and we're walking through the module list again. * * In that case, don't bother trying to proxy the request * again. * * Also, if there's no User-Name attribute, we can't * proxy it, either. */ if ((!request->username) #ifdef WITH_PROXY || (request->proxy != NULL) #endif ) { RDEBUG2("Proxy reply, or no User-Name. Ignoring."); return RLM_MODULE_OK; } /* * Check for 'Realm' attribute. If it exists, then we've proxied * it already ( via another rlm_realm instance ) and should return. */ if (pairfind(request->packet->vps, PW_REALM, 0, TAG_ANY) != NULL ) { RDEBUG2("Request already proxied. Ignoring."); return RLM_MODULE_OK; } /* * We will be modifing this later, so we want our own copy * of it. */ namebuf = talloc_strdup(request, request->username->vp_strvalue); username = namebuf; switch(inst->format) { case REALM_FORMAT_SUFFIX: /* DEBUG2(" rlm_realm: Checking for suffix after \"%c\"", inst->delim[0]); */ ptr = strrchr(username, inst->delim[0]); if (ptr) { *ptr = '\0'; realmname = ptr + 1; } break; case REALM_FORMAT_PREFIX: /* DEBUG2(" rlm_realm: Checking for prefix before \"%c\"", inst->delim[0]); */ ptr = strchr(username, inst->delim[0]); if (ptr) { *ptr = '\0'; ptr++; realmname = username; username = ptr; } break; default: realmname = NULL; break; } /* * Print out excruciatingly descriptive debugging messages * for the people who find it too difficult to think about * what's going on. */ if (realmname) { RDEBUG2("Looking up realm \"%s\" for User-Name = \"%s\"", realmname, request->username->vp_strvalue); } else { if (inst->ignore_null ) { RDEBUG2("No '%c' in User-Name = \"%s\", skipping NULL due to config.", inst->delim[0], request->username->vp_strvalue); talloc_free(namebuf); return RLM_MODULE_NOOP; } RDEBUG2("No '%c' in User-Name = \"%s\", looking up realm NULL", inst->delim[0], request->username->vp_strvalue); } /* * Allow DEFAULT realms unless told not to. */ realm = realm_find(realmname); if (!realm) { RDEBUG2("No such realm \"%s\"", (!realmname) ? "NULL" : realmname); talloc_free(namebuf); return RLM_MODULE_NOOP; } if( inst->ignore_default && (strcmp(realm->name, "DEFAULT")) == 0) { RDEBUG2("Found DEFAULT, but skipping due to config."); talloc_free(namebuf); return RLM_MODULE_NOOP; } RDEBUG2("Found realm \"%s\"", realm->name); /* * If we've been told to strip the realm off, then do so. */ if (realm->striprealm) { /* * Create the Stripped-User-Name attribute, if it * doesn't exist. * */ if (request->username->da->attr != PW_STRIPPED_USER_NAME) { vp = radius_paircreate(request, &request->packet->vps, PW_STRIPPED_USER_NAME, 0); RDEBUG2("Adding Stripped-User-Name = \"%s\"", username); } else { vp = request->username; RDEBUG2("Setting Stripped-User-Name = \"%s\"", username); } pairstrcpy(vp, username); request->username = vp; } /* * Add the realm name to the request. * If the realm is a regex, the use the realm as entered * by the user. Otherwise, use the configured realm name, * as realm name comparison is case insensitive. We want * to use the configured name, rather than what the user * entered. */ if (realm->name[0] != '~') realmname = realm->name; pairmake_packet("Realm", realmname, T_OP_EQ); RDEBUG2("Adding Realm = \"%s\"", realmname); talloc_free(namebuf); realmname = username = NULL; /* * Figure out what to do with the request. */ switch (request->packet->code) { default: RDEBUG2("Unknown packet code %d\n", request->packet->code); return RLM_MODULE_OK; /* don't do anything */ /* * Perhaps accounting proxying was turned off. */ case PW_ACCOUNTING_REQUEST: if (!realm->acct_pool) { RDEBUG2("Accounting realm is LOCAL."); return RLM_MODULE_OK; } break; /* * Perhaps authentication proxying was turned off. */ case PW_AUTHENTICATION_REQUEST: if (!realm->auth_pool) { RDEBUG2("Authentication realm is LOCAL."); return RLM_MODULE_OK; } break; } #ifdef WITH_PROXY RDEBUG2("Proxying request from user %s to realm %s", request->username->vp_strvalue, realm->name); /* * Skip additional checks if it's not an accounting * request. */ if (request->packet->code != PW_ACCOUNTING_REQUEST) { *returnrealm = realm; return RLM_MODULE_UPDATED; } /* * FIXME: Each server should have a unique server key, * and put it in the accounting packet. Every server * should know about the keys, and NOT proxy requests to * a server with key X if the packet already contains key * X. */ /* * If this request has arrived from another freeradius server * that has already proxied the request, we don't need to do * it again. */ vp = pairfind(request->packet->vps, PW_FREERADIUS_PROXIED_TO, 0, TAG_ANY); if (vp && (request->packet->src_ipaddr.af == AF_INET)) { int i; fr_ipaddr_t my_ipaddr; my_ipaddr.af = AF_INET; my_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr; /* * Loop over the home accounting servers for this * realm. If one of them has the same IP as the * FreeRADIUS-Proxied-To attribute, then the * packet has already been sent there. Don't * send it there again. */ for (i = 0; i < realm->acct_pool->num_home_servers; i++) { if (fr_ipaddr_cmp(&realm->acct_pool->servers[i]->ipaddr, &my_ipaddr) == 0) { RDEBUG2("Suppressing proxy due to FreeRADIUS-Proxied-To"); return RLM_MODULE_OK; } } /* * See detail_recv() in src/main/listen.c for the * additional checks. */ #ifdef WITH_DETAIL } else if ((request->listener->type == RAD_LISTEN_DETAIL) && !fr_inaddr_any(&request->packet->src_ipaddr)) { int i; /* * Loop over the home accounting servers for this * realm. If one of them has the same IP as the * FreeRADIUS-Proxied-To attribute, then the * packet has already been sent there. Don't * send it there again. */ for (i = 0; i < realm->acct_pool->num_home_servers; i++) { if ((fr_ipaddr_cmp(&realm->acct_pool->servers[i]->ipaddr, &request->packet->src_ipaddr) == 0) && (realm->acct_pool->servers[i]->port == request->packet->src_port)) { RDEBUG2("Suppressing proxy because packet was already sent to a server in that realm"); return RLM_MODULE_OK; } } #endif /* WITH_DETAIL */ } #endif /* WITH_PROXY */ /* * We got this far, which means we have a realm, set returnrealm */ *returnrealm = realm; return RLM_MODULE_UPDATED; }
static int sendrecv_eap(RADIUS_PACKET *rep) { RADIUS_PACKET *req = NULL; VALUE_PAIR *vp, *vpnext; int tried_eap_md5 = 0; if (!rep) return -1; /* * Keep a copy of the the User-Password attribute. */ if ((vp = pairfind(rep->vps, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) != NULL) { strlcpy(password, vp->vp_strvalue, sizeof(password)); } else if ((vp = pairfind(rep->vps, PW_USER_PASSWORD, 0, TAG_ANY)) != NULL) { strlcpy(password, vp->vp_strvalue, sizeof(password)); /* * Otherwise keep a copy of the CHAP-Password attribute. */ } else if ((vp = pairfind(rep->vps, PW_CHAP_PASSWORD, 0, TAG_ANY)) != NULL) { strlcpy(password, vp->vp_strvalue, sizeof(password)); } else { *password = '******'; } again: rep->id++; /* * if there are EAP types, encode them into an EAP-Message * */ map_eap_methods(rep); /* * Fix up Digest-Attributes issues */ for (vp = rep->vps; vp != NULL; vp = vp->next) { switch (vp->da->attr) { default: break; case PW_DIGEST_REALM: case PW_DIGEST_NONCE: case PW_DIGEST_METHOD: case PW_DIGEST_URI: case PW_DIGEST_QOP: case PW_DIGEST_ALGORITHM: case PW_DIGEST_BODY_DIGEST: case PW_DIGEST_CNONCE: case PW_DIGEST_NONCE_COUNT: case PW_DIGEST_USER_NAME: /* overlapping! */ { DICT_ATTR const *da; uint8_t *p, *q; p = talloc_array(vp, uint8_t, vp->length + 2); memcpy(p + 2, vp->vp_octets, vp->length); p[0] = vp->da->attr - PW_DIGEST_REALM + 1; vp->length += 2; p[1] = vp->length; da = dict_attrbyvalue(PW_DIGEST_ATTRIBUTES, 0); vp->da = da; /* * Re-do pairmemsteal ourselves, * because we play games with * vp->da, and pairmemsteal goes * to GREAT lengths to sanitize * and fix and change and * double-check the various * fields. */ memcpy(&q, &vp->vp_octets, sizeof(q)); talloc_free(q); vp->vp_octets = talloc_steal(vp, p); vp->type = VT_DATA; VERIFY_VP(vp); } break; } } /* * If we've already sent a packet, free up the old * one, and ensure that the next packet has a unique * ID and authentication vector. */ if (rep->data) { talloc_free(rep->data); rep->data = NULL; } fr_md5_calc(rep->vector, rep->vector, sizeof(rep->vector)); if (*password != '\0') { if ((vp = pairfind(rep->vps, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) != NULL) { pairstrcpy(vp, password); } else if ((vp = pairfind(rep->vps, PW_USER_PASSWORD, 0, TAG_ANY)) != NULL) { pairstrcpy(vp, password); } else if ((vp = pairfind(rep->vps, PW_CHAP_PASSWORD, 0, TAG_ANY)) != NULL) { pairstrcpy(vp, password); uint8_t *p; p = talloc_zero_array(vp, uint8_t, 17); rad_chap_encode(rep, p, rep->id, vp); pairmemsteal(vp, p); } } /* there WAS a password */ /* send the response, wait for the next request */ send_packet(rep, &req); if (!req) return -1; /* okay got back the packet, go and decode the EAP-Message. */ unmap_eap_methods(req); debug_packet(req, R_RECV); /* now look for the code type. */ for (vp = req->vps; vp != NULL; vp = vpnext) { vpnext = vp->next; switch (vp->da->attr) { default: break; case ATTRIBUTE_EAP_BASE+PW_EAP_MD5: if(respond_eap_md5(req, rep) && tried_eap_md5 < 3) { tried_eap_md5++; goto again; } break; case ATTRIBUTE_EAP_BASE+PW_EAP_SIM: if(respond_eap_sim(req, rep)) { goto again; } break; } } return 1; }
/* * Generate a challenge to be presented to the user. */ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { rlm_otp_t *inst = (rlm_otp_t *) instance; char challenge[OTP_MAX_CHALLENGE_LEN + 1]; /* +1 for '\0' terminator */ int auth_type_found; /* Early exit if Auth-Type != inst->name */ { VALUE_PAIR *vp; auth_type_found = 0; vp = pairfind(request->config_items, PW_AUTHTYPE, 0, TAG_ANY); if (vp) { auth_type_found = 1; if (strcmp(vp->vp_strvalue, inst->name)) { return RLM_MODULE_NOOP; } } } /* The State attribute will be present if this is a response. */ if (pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY) != NULL) { DEBUG("rlm_otp: autz: Found response to Access-Challenge"); return RLM_MODULE_OK; } /* User-Name attribute required. */ if (!request->username) { RWDEBUG("Attribute \"User-Name\" " "required for authentication"); return RLM_MODULE_INVALID; } if (otp_pwe_present(request) == 0) { RWDEBUG("Attribute " "\"User-Password\" or equivalent required " "for authentication"); return RLM_MODULE_INVALID; } /* * We used to check for special "challenge" and "resync" passcodes * here, but these are complicated to explain and application is * limited. More importantly, since we've removed all actual OTP * code (now we ask otpd), it's awkward for us to support them. * Should the need arise to reinstate these options, the most * likely choice is to duplicate some otpd code here. */ if (inst->allow_sync && !inst->allow_async) { /* This is the token sync response. */ if (!auth_type_found) { pairmake_config("Auth-Type", inst->name, T_OP_EQ); } return RLM_MODULE_OK; } /* * Generate a random challenge. */ otp_async_challenge(challenge, inst->challenge_len); /* * Create the State attribute, which will be returned to * us along with the response. * * We will need this to verify the response. * * It must be hmac protected to prevent insertion of arbitrary * State by an inside attacker. * * If we won't actually use the State (server config doesn't * allow async), we just use a trivial State. * * We always create at least a trivial State, so mod_authorize() * can quickly pass on to mod_authenticate(). */ { int32_t now = htonl(time(NULL)); //!< Low-order 32 bits on LP64. char gen_state[OTP_MAX_RADSTATE_LEN]; size_t len; VALUE_PAIR *vp; len = otp_gen_state(gen_state, challenge, inst->challenge_len, 0, now, inst->hmac_key); vp = paircreate(request->reply, PW_STATE, 0); if (!vp) { return RLM_MODULE_FAIL; } pairmemcpy(vp, (uint8_t const *) gen_state, len); pairadd(&request->reply->vps, vp); } /* * Add the challenge to the reply. */ { VALUE_PAIR *vp; char *expanded = NULL; ssize_t len; /* * First add the internal OTP challenge attribute to * the reply list. */ vp = paircreate(request->reply, PW_OTP_CHALLENGE, 0); if (!vp) { return RLM_MODULE_FAIL; } pairstrcpy(vp, challenge); vp->op = T_OP_SET; pairadd(&request->reply->vps, vp); /* * Then add the message to the user to they known * what the challenge value is. */ len = radius_axlat(&expanded, request, inst->chal_prompt, NULL, NULL); if (len < 0) { return RLM_MODULE_FAIL; } vp = paircreate(request->reply, PW_REPLY_MESSAGE, 0); if (!vp) { talloc_free(expanded); return RLM_MODULE_FAIL; } (void) talloc_steal(vp, expanded); vp->vp_strvalue = expanded; vp->length = len; vp->op = T_OP_SET; vp->type = VT_DATA; pairadd(&request->reply->vps, vp); } /* * Mark the packet as an Access-Challenge packet. * The server will take care of sending it to the user. */ request->reply->code = PW_CODE_ACCESS_CHALLENGE; DEBUG("rlm_otp: Sending Access-Challenge"); if (!auth_type_found) { pairmake_config("Auth-Type", inst->name, T_OP_EQ); } return RLM_MODULE_HANDLED; }
/* * Mangle username if needed, IN PLACE. */ static void rad_mangle(rlm_preprocess_t *inst, REQUEST *request) { int num_proxy_state; VALUE_PAIR *namepair; VALUE_PAIR *request_pairs; VALUE_PAIR *tmp; vp_cursor_t cursor; /* * Get the username from the request * If it isn't there, then we can't mangle the request. */ request_pairs = request->packet->vps; namepair = pairfind(request_pairs, PW_USER_NAME, 0, TAG_ANY); if (!namepair || (namepair->length == 0)) { return; } if (inst->with_ntdomain_hack) { char *ptr; char newname[MAX_STRING_LEN]; /* * Windows NT machines often authenticate themselves as * NT_DOMAIN\username. Try to be smart about this. * * FIXME: should we handle this as a REALM ? */ if ((ptr = strchr(namepair->vp_strvalue, '\\')) != NULL) { strlcpy(newname, ptr + 1, sizeof(newname)); /* Same size */ pairstrcpy(namepair, newname); } } if (inst->with_specialix_jetstream_hack) { /* * Specialix Jetstream 8500 24 port access server. * If the user name is 10 characters or longer, a "/" * and the excess characters after the 10th are * appended to the user name. * * Reported by Lucas Heise <*****@*****.**> */ if ((strlen(namepair->vp_strvalue) > 10) && (namepair->vp_strvalue[10] == '/')) { pairstrcpy(namepair, namepair->vp_strvalue + 11); } } /* * Small check: if Framed-Protocol present but Service-Type * is missing, add Service-Type = Framed-User. */ if (pairfind(request_pairs, PW_FRAMED_PROTOCOL, 0, TAG_ANY) != NULL && pairfind(request_pairs, PW_SERVICE_TYPE, 0, TAG_ANY) == NULL) { tmp = radius_paircreate(request->packet, &request->packet->vps, PW_SERVICE_TYPE, 0); tmp->vp_integer = PW_FRAMED_USER; } num_proxy_state = 0; for (tmp = fr_cursor_init(&cursor, &request->packet->vps); tmp; tmp = fr_cursor_next(&cursor)) { if (tmp->da->vendor != 0) { continue; } if (tmp->da->attr != PW_PROXY_STATE) { continue; } num_proxy_state++; } if (num_proxy_state > 10) { RWDEBUG("There are more than 10 Proxy-State attributes in the request"); RWDEBUG("You have likely configured an infinite proxy loop"); } }
static rlm_rcode_t rlm_sql_process_groups(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle, sql_fall_through_t *do_fall_through) { rlm_rcode_t rcode = RLM_MODULE_NOOP; VALUE_PAIR *check_tmp = NULL, *reply_tmp = NULL, *sql_group = NULL; rlm_sql_grouplist_t *head = NULL, *entry = NULL; char *expanded = NULL; int rows; rad_assert(request->packet != NULL); /* * Get the list of groups this user is a member of */ rows = sql_get_grouplist(inst, handle, request, &head); if (rows < 0) { REDEBUG("Error retrieving group list"); return RLM_MODULE_FAIL; } if (rows == 0) { RDEBUG2("User not found in any groups"); rcode = RLM_MODULE_NOTFOUND; *do_fall_through = FALL_THROUGH_DEFAULT; goto finish; } rad_assert(head); RDEBUG2("User found in the group table"); /* * Add the Sql-Group attribute to the request list so we know * which group we're retrieving attributes for */ sql_group = pairmake_packet("Sql-Group", NULL, T_OP_EQ); if (!sql_group) { REDEBUG("Error creating Sql-Group attribute"); rcode = RLM_MODULE_FAIL; goto finish; } entry = head; do { next: rad_assert(entry != NULL); pairstrcpy(sql_group, entry->name); if (inst->config->authorize_group_check_query) { vp_cursor_t cursor; VALUE_PAIR *vp; /* * Expand the group query */ if (radius_axlat(&expanded, request, inst->config->authorize_group_check_query, sql_escape_func, inst) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto finish; } rows = sql_getvpdata(request, inst, request, handle, &check_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("Error retrieving check pairs for group %s", entry->name); rcode = RLM_MODULE_FAIL; goto finish; } /* * If we got check rows we need to process them before we decide to * process the reply rows */ if ((rows > 0) && (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0)) { pairfree(&check_tmp); entry = entry->next; goto next; /* != continue */ } RDEBUG2("Group \"%s\": Conditional check items matched", entry->name); rcode = RLM_MODULE_OK; RDEBUG2("Group \"%s\": Merging assignment check items", entry->name); RINDENT(); for (vp = fr_cursor_init(&cursor, &check_tmp); vp; vp = fr_cursor_next(&cursor)) { if (!fr_assignment_op[vp->op]) continue; rdebug_pair(L_DBG_LVL_2, request, vp, NULL); } REXDENT(); radius_pairmove(request, &request->config_items, check_tmp, true); check_tmp = NULL; } if (inst->config->authorize_group_reply_query) { /* * Now get the reply pairs since the paircompare matched */ if (radius_axlat(&expanded, request, inst->config->authorize_group_reply_query, sql_escape_func, inst) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto finish; } rows = sql_getvpdata(request->reply, inst, request, handle, &reply_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("Error retrieving reply pairs for group %s", entry->name); rcode = RLM_MODULE_FAIL; goto finish; } *do_fall_through = fall_through(reply_tmp); RDEBUG2("Group \"%s\": Merging reply items", entry->name); rcode = RLM_MODULE_OK; rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp, NULL); radius_pairmove(request, &request->reply->vps, reply_tmp, true); reply_tmp = NULL; /* * If there's no reply query configured, then we assume * FALL_THROUGH_NO, which is the same as the users file if you * had no reply attributes. */ } else { *do_fall_through = FALL_THROUGH_DEFAULT; } entry = entry->next; } while (entry != NULL && (*do_fall_through == FALL_THROUGH_YES)); finish: talloc_free(head); pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY); return rcode; }
/* * Send one packet. */ static int send_one_packet(rc_request_t *request) { assert(request->done == false); /* * Remember when we have to wake up, to re-send the * request, of we didn't receive a reply. */ if ((sleep_time == -1) || (sleep_time > (int) timeout)) { sleep_time = (int) timeout; } /* * Haven't sent the packet yet. Initialize it. */ if (request->packet->id == -1) { int i; bool rcode; assert(request->reply == NULL); /* * Didn't find a free packet ID, we're not done, * we don't sleep, and we stop trying to process * this packet. */ retry: request->packet->src_ipaddr.af = server_ipaddr.af; rcode = fr_packet_list_id_alloc(pl, ipproto, &request->packet, NULL); if (!rcode) { int mysockfd; #ifdef WITH_TCP if (proto) { mysockfd = fr_tcp_client_socket(NULL, &server_ipaddr, server_port); } else #endif mysockfd = fr_socket(&client_ipaddr, 0); if (mysockfd < 0) { ERROR("Can't open new socket: %s", strerror(errno)); exit(1); } if (!fr_packet_list_socket_add(pl, mysockfd, ipproto, &server_ipaddr, server_port, NULL)) { ERROR("Can't add new socket"); exit(1); } goto retry; } assert(request->packet->id != -1); assert(request->packet->data == NULL); for (i = 0; i < 4; i++) { ((uint32_t *) request->packet->vector)[i] = fr_rand(); } /* * Update the password, so it can be encrypted with the * new authentication vector. */ if (request->password[0] != '\0') { VALUE_PAIR *vp; if ((vp = pairfind(request->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY)) != NULL) { pairstrcpy(vp, request->password); } else if ((vp = pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY)) != NULL) { bool already_hex = false; /* * If it's 17 octets, it *might* be already encoded. * Or, it might just be a 17-character password (maybe UTF-8) * Check it for non-printable characters. The odds of ALL * of the characters being 32..255 is (1-7/8)^17, or (1/8)^17, * or 1/(2^51), which is pretty much zero. */ if (vp->length == 17) { for (i = 0; i < 17; i++) { if (vp->vp_octets[i] < 32) { already_hex = true; break; } } } /* * Allow the user to specify ASCII or hex CHAP-Password */ if (!already_hex) { uint8_t *p; size_t len, len2; len = len2 = strlen(request->password); if (len2 < 17) len2 = 17; p = talloc_zero_array(vp, uint8_t, len2); memcpy(p, request->password, len); rad_chap_encode(request->packet, p, fr_rand() & 0xff, vp); vp->vp_octets = p; vp->length = 17; } } else if (pairfind(request->packet->vps, PW_MSCHAP_PASSWORD, 0, TAG_ANY) != NULL) { mschapv1_encode(request->packet, &request->packet->vps, request->password); } else { DEBUG("WARNING: No password in the request"); } } request->timestamp = time(NULL); request->tries = 1; request->resend++; #ifdef WITH_TCP /* * WTF? */ if (client_port == 0) { client_ipaddr = request->packet->src_ipaddr; client_port = request->packet->src_port; } #endif } else { /* request->packet->id >= 0 */ time_t now = time(NULL); /* * FIXME: Accounting packets are never retried! * The Acct-Delay-Time attribute is updated to * reflect the delay, and the packet is re-sent * from scratch! */ /* * Not time for a retry, do so. */ if ((now - request->timestamp) < timeout) { /* * When we walk over the tree sending * packets, we update the minimum time * required to sleep. */ if ((sleep_time == -1) || (sleep_time > (now - request->timestamp))) { sleep_time = now - request->timestamp; } return 0; } /* * We're not trying later, maybe the packet is done. */ if (request->tries == retries) { assert(request->packet->id >= 0); /* * Delete the request from the tree of * outstanding requests. */ fr_packet_list_yank(pl, request->packet); REDEBUG("No reply from server for ID %d socket %d", request->packet->id, request->packet->sockfd); deallocate_id(request); /* * Normally we mark it "done" when we've received * the reply, but this is a special case. */ if (request->resend == resend_count) { request->done = true; } stats.lost++; return -1; } /* * We are trying later. */ request->timestamp = now; request->tries++; } /* * Send the packet. */ if (rad_send(request->packet, NULL, secret) < 0) { REDEBUG("Failed to send packet for ID %d", request->packet->id); } return 0; }
static int sendrecv_eap(RADIUS_PACKET *rep) { RADIUS_PACKET *req = NULL; VALUE_PAIR *vp, *vpnext; int tried_eap_md5 = 0; /* * Keep a copy of the the User-Password attribute. */ if ((vp = pairfind(rep->vps, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) != NULL) { strlcpy(password, (char *)vp->vp_strvalue, sizeof(vp->vp_strvalue)); } else if ((vp = pairfind(rep->vps, PW_USER_PASSWORD, 0, TAG_ANY)) != NULL) { strlcpy(password, (char *)vp->vp_strvalue, sizeof(vp->vp_strvalue)); /* * Otherwise keep a copy of the CHAP-Password attribute. */ } else if ((vp = pairfind(rep->vps, PW_CHAP_PASSWORD, 0, TAG_ANY)) != NULL) { strlcpy(password, (char *)vp->vp_strvalue, sizeof(vp->vp_strvalue)); } else { *password = '******'; } again: rep->id++; /* * if there are EAP types, encode them into an EAP-Message * */ map_eap_methods(rep); /* * Fix up Digest-Attributes issues */ for (vp = rep->vps; vp != NULL; vp = vp->next) { switch (vp->da->attribute) { default: break; case PW_DIGEST_REALM: case PW_DIGEST_NONCE: case PW_DIGEST_METHOD: case PW_DIGEST_URI: case PW_DIGEST_QOP: case PW_DIGEST_ALGORITHM: case PW_DIGEST_BODY_DIGEST: case PW_DIGEST_CNONCE: case PW_DIGEST_NONCE_COUNT: case PW_DIGEST_USER_NAME: /* overlapping! */ memmove(&vp->vp_strvalue[2], &vp->vp_octets[0], vp->length); vp->vp_octets[0] = vp->da->attribute - PW_DIGEST_REALM + 1; vp->length += 2; vp->vp_octets[1] = vp->length; vp->da->attribute = PW_DIGEST_ATTRIBUTES; break; } } /* * If we've already sent a packet, free up the old * one, and ensure that the next packet has a unique * ID and authentication vector. */ if (rep->data) { talloc_free(rep->data); rep->data = NULL; } fr_md5_calc(rep->vector, rep->vector, sizeof(rep->vector)); if (*password != '\0') { if ((vp = pairfind(rep->vps, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) != NULL) { pairstrcpy(vp, password); } else if ((vp = pairfind(rep->vps, PW_USER_PASSWORD, 0, TAG_ANY)) != NULL) { pairstrcpy(vp, password); } else if ((vp = pairfind(rep->vps, PW_CHAP_PASSWORD, 0, TAG_ANY)) != NULL) { pairstrcpy(vp, password); rad_chap_encode(rep, vp->vp_octets, rep->id, vp); vp->length = 17; } } /* there WAS a password */ /* send the response, wait for the next request */ send_packet(rep, &req); /* okay got back the packet, go and decode the EAP-Message. */ unmap_eap_methods(req); debug_packet(req, R_RECV); /* now look for the code type. */ for (vp = req->vps; vp != NULL; vp = vpnext) { vpnext = vp->next; switch (vp->da->attribute) { default: break; case ATTRIBUTE_EAP_BASE+PW_EAP_MD5: if(respond_eap_md5(req, rep) && tried_eap_md5 < 3) { tried_eap_md5++; goto again; } break; case ATTRIBUTE_EAP_BASE+PW_EAP_SIM: if(respond_eap_sim(req, rep)) { goto again; } break; } } return 1; }
/** Convert group membership information into attributes * * @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. * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_cacheable_groupobj(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn) { rlm_rcode_t rcode = RLM_MODULE_OK; ldap_rcode_t status; int ldap_errno; LDAPMessage *result = NULL; LDAPMessage *entry; char base_dn[LDAP_MAX_DN_STR_LEN]; char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter }; char filter[LDAP_MAX_FILTER_STR_LEN + 1]; char const *attrs[] = { inst->groupobj_name_attr, NULL }; VALUE_PAIR *vp; char *dn; rad_assert(inst->groupobj_base_dn); if (!inst->groupobj_membership_filter) { RDEBUG2("Skipping caching group objects as directive 'group.membership_filter' is not set"); return RLM_MODULE_OK; } if (rlm_ldap_xlat_filter(request, filters, sizeof(filters) / sizeof(*filters), filter, sizeof(filter)) < 0) { return RLM_MODULE_INVALID; } if (radius_xlat(base_dn, sizeof(base_dn), request, inst->groupobj_base_dn, rlm_ldap_escape_func, NULL) < 0) { REDEBUG("Failed creating base_dn"); return RLM_MODULE_INVALID; } status = rlm_ldap_search(inst, request, pconn, base_dn, inst->groupobj_scope, filter, attrs, &result); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_NO_RESULT: RDEBUG2("No cacheable group memberships found in group objects"); default: goto finish; } entry = ldap_first_entry((*pconn)->handle, result); if (!entry) { ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno)); goto finish; } do { if (inst->cacheable_group_dn) { dn = ldap_get_dn((*pconn)->handle, entry); if (!dn) { ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); REDEBUG("Retrieving object DN from entry failed: %s", ldap_err2string(ldap_errno)); goto finish; } rlm_ldap_normalise_dn(dn, dn); MEM(vp = pairmake_config(inst->cache_da->name, NULL, T_OP_ADD)); pairstrcpy(vp, dn); RDEBUG("Added control:%s with value \"%s\"", inst->cache_da->name, dn); ldap_memfree(dn); } if (inst->cacheable_group_name) { struct berval **values; values = ldap_get_values_len((*pconn)->handle, entry, inst->groupobj_name_attr); if (!values) continue; MEM(vp = pairmake_config(inst->cache_da->name, NULL, T_OP_ADD)); pairstrncpy(vp, values[0]->bv_val, values[0]->bv_len); RDEBUG("Added control:%s with value \"%.*s\"", inst->cache_da->name, (int)values[0]->bv_len, values[0]->bv_val); ldap_value_free_len(values); } } while ((entry = ldap_next_entry((*pconn)->handle, entry))); finish: if (result) ldap_msgfree(result); return rcode; }
/** Convert group membership information into attributes * * @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] entry retrieved by rlm_ldap_find_user or rlm_ldap_search. * @param[in] attr membership attribute to look for in the entry. * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_cacheable_userobj(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn, LDAPMessage *entry, char const *attr) { rlm_rcode_t rcode = RLM_MODULE_OK; struct berval **values; size_t value_len = 0; TALLOC_CTX *value_pool; char *group_name[LDAP_MAX_CACHEABLE + 1]; char **name_p = group_name; char *group_dn[LDAP_MAX_CACHEABLE + 1]; char **dn_p; char *name; VALUE_PAIR *vp, **vps; TALLOC_CTX *ctx; vp_cursor_t cursor; int is_dn, i, count; rad_assert(entry); rad_assert(attr); /* * Parse the membership information we got in the initial user query. */ values = ldap_get_values_len((*pconn)->handle, entry, attr); if (!values) { RDEBUG2("No cacheable group memberships found in user object"); return RLM_MODULE_OK; } count = ldap_count_values_len(values); vps = radius_list(request, PAIR_LIST_CONTROL); ctx = radius_list_ctx(request, PAIR_LIST_CONTROL); fr_cursor_init(&cursor, vps); /* * Avoid allocing buffers for each value. * * The old code used ldap_get_values, which was likely doing * a very similar thing internally to produce \0 terminated * buffers from bervalues. */ for (i = 0; (i < LDAP_MAX_CACHEABLE) && (i < count); i++) value_len += values[i]->bv_len + 1; value_pool = talloc_pool(request, value_len); for (i = 0; (i < LDAP_MAX_CACHEABLE) && (i < count); i++) { is_dn = rlm_ldap_is_dn(values[i]->bv_val, values[i]->bv_len); if (inst->cacheable_group_dn) { /* * The easy case, we're caching DNs and we got a DN. */ if (is_dn) { MEM(vp = pairalloc(ctx, inst->cache_da)); pairstrncpy(vp, values[i]->bv_val, values[i]->bv_len); fr_cursor_insert(&cursor, vp); RDEBUG("Added %s with value \"%s\" to control list", inst->cache_da->name, vp->vp_strvalue); /* * We were told to cache DNs but we got a name, we now need to resolve * this to a DN. Store all the group names in an array so we can do one query. */ } else { *name_p++ = rlm_ldap_berval_to_string(value_pool, values[i]); } } if (inst->cacheable_group_name) { /* * The easy case, we're caching names and we got a name. */ if (!is_dn) { MEM(vp = pairalloc(ctx, inst->cache_da)); pairstrncpy(vp, values[i]->bv_val, values[i]->bv_len); fr_cursor_insert(&cursor, vp); RDEBUG("Added control:%s with value \"%s\"", inst->cache_da->name, vp->vp_strvalue); /* * We were told to cache names but we got a DN, we now need to resolve * this to a name. * Only Active Directory supports filtering on DN, so we have to search * for each individual group. */ } else { char *dn; dn = rlm_ldap_berval_to_string(value_pool, values[i]); rcode = rlm_ldap_group_dn2name(inst, request, pconn, dn, &name); talloc_free(dn); if (rcode != RLM_MODULE_OK) { ldap_value_free_len(values); talloc_free(value_pool); return rcode; } MEM(vp = pairalloc(ctx, inst->cache_da)); pairstrncpy(vp, name, talloc_array_length(name) - 1); fr_cursor_insert(&cursor, vp); RDEBUG("Added control:%s with value \"%s\"", inst->cache_da->name, name); talloc_free(name); } } } *name_p = NULL; rcode = rlm_ldap_group_name2dn(inst, request, pconn, group_name, group_dn, sizeof(group_dn)); ldap_value_free_len(values); talloc_free(value_pool); if (rcode != RLM_MODULE_OK) return rcode; dn_p = group_dn; while (*dn_p) { MEM(vp = pairalloc(ctx, inst->cache_da)); pairstrcpy(vp, *dn_p); fr_cursor_insert(&cursor, vp); RDEBUG("Added control:%s with value \"%s\"", inst->cache_da->name, *dn_p); ldap_memfree(*dn_p); dn_p++; } return rcode; }
/* * Authenticate a previously sent challenge. */ static int mschapv2_authenticate(void *arg, eap_handler_t *handler) { int rcode, ccode; uint8_t *p; mschapv2_opaque_t *data; EAP_DS *eap_ds = handler->eap_ds; VALUE_PAIR *challenge, *response, *name; rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg; REQUEST *request = handler->request; rad_assert(request != NULL); rad_assert(handler->stage == AUTHENTICATE); data = (mschapv2_opaque_t *) handler->opaque; /* * Sanity check the response. */ if (eap_ds->response->length <= 5) { REDEBUG("corrupted data"); return 0; } ccode = eap_ds->response->type.data[0]; switch (data->code) { case PW_EAP_MSCHAPV2_FAILURE: if (ccode == PW_EAP_MSCHAPV2_RESPONSE) { RDEBUG2("authentication re-try from client after we sent a failure"); break; } /* * if we sent error 648 (password expired) to the client * we might get an MSCHAP-CPW packet here; turn it into a * regular MS-CHAP2-CPW packet and pass it to rlm_mschap * (or proxy it, I guess) */ if (ccode == PW_EAP_MSCHAPV2_CHGPASSWD) { VALUE_PAIR *cpw; int mschap_id = eap_ds->response->type.data[1]; int copied=0,seq=1; RDEBUG2("password change packet received"); challenge = pairmake_packet("MS-CHAP-Challenge", NULL, T_OP_EQ); if (!challenge) { return 0; } pairmemcpy(challenge, data->challenge, MSCHAPV2_CHALLENGE_LEN); cpw = pairmake_packet("MS-CHAP2-CPW", NULL, T_OP_EQ); cpw->length = 68; cpw->vp_octets = p = talloc_array(cpw, uint8_t, cpw->length); p[0] = 7; p[1] = mschap_id; memcpy(p + 2, eap_ds->response->type.data + 520, 66); /* * break the encoded password into VPs (3 of them) */ while (copied < 516) { VALUE_PAIR *nt_enc; int to_copy = 516 - copied; if (to_copy > 243) to_copy = 243; nt_enc = pairmake_packet("MS-CHAP-NT-Enc-PW", NULL, T_OP_ADD); nt_enc->length = 4 + to_copy; nt_enc->vp_octets = p = talloc_array(nt_enc, uint8_t, nt_enc->length); p[0] = 6; p[1] = mschap_id; p[2] = 0; p[3] = seq++; memcpy(p + 4, eap_ds->response->type.data + 4 + copied, to_copy); copied += to_copy; } RDEBUG2("built change password packet"); debug_pair_list(request->packet->vps); /* * jump to "authentication" */ goto packet_ready; } /* * we sent a failure and are expecting a failure back */ if (ccode != PW_EAP_MSCHAPV2_FAILURE) { REDEBUG("Sent FAILURE expecting FAILURE but got %d", ccode); return 0; } failure: request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP; eap_ds->request->code = PW_EAP_FAILURE; return 1; case PW_EAP_MSCHAPV2_SUCCESS: /* * we sent a success to the client; some clients send a * success back as-per the RFC, some send an ACK. Permit * both, I guess... */ switch (ccode) { case PW_EAP_MSCHAPV2_SUCCESS: eap_ds->request->code = PW_EAP_SUCCESS; pairfilter(request->reply, &request->reply->vps, &data->mppe_keys, 0, 0, TAG_ANY); /* fall through... */ case PW_EAP_MSCHAPV2_ACK: #ifdef WITH_PROXY /* * It's a success. Don't proxy it. */ request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP; #endif pairfilter(request->reply, &request->reply->vps, &data->reply, 0, 0, TAG_ANY); return 1; } REDEBUG("Sent SUCCESS expecting SUCCESS (or ACK) but got %d", ccode); return 0; case PW_EAP_MSCHAPV2_CHALLENGE: if (ccode == PW_EAP_MSCHAPV2_FAILURE) goto failure; /* * we sent a challenge, expecting a response */ if (ccode != PW_EAP_MSCHAPV2_RESPONSE) { REDEBUG("Sent CHALLENGE expecting RESPONSE but got %d", ccode); return 0; } /* authentication happens below */ break; default: /* should never happen */ REDEBUG("unknown state %d", data->code); return 0; } /* * Ensure that we have at least enough data * to do the following checks. * * EAP header (4), EAP type, MS-CHAP opcode, * MS-CHAP ident, MS-CHAP data length (2), * MS-CHAP value length. */ if (eap_ds->response->length < (4 + 1 + 1 + 1 + 2 + 1)) { REDEBUG("Response is too short"); return 0; } /* * The 'value_size' is the size of the response, * which is supposed to be the response (48 * bytes) plus 1 byte of flags at the end. */ if (eap_ds->response->type.data[4] != 49) { REDEBUG("Response is of incorrect length %d", eap_ds->response->type.data[4]); return 0; } /* * The MS-Length field is 5 + value_size + length * of name, which is put after the response. */ if (((eap_ds->response->type.data[2] << 8) | eap_ds->response->type.data[3]) < (5 + 49)) { REDEBUG("Response contains contradictory length %d %d", (eap_ds->response->type.data[2] << 8) | eap_ds->response->type.data[3], 5 + 49); return 0; } /* * We now know that the user has sent us a response * to the challenge. Let's try to authenticate it. * * We do this by taking the challenge from 'data', * the response from the EAP packet, and creating VALUE_PAIR's * to pass to the 'mschap' module. This is a little wonky, * but it works. */ challenge = pairmake_packet("MS-CHAP-Challenge", NULL, T_OP_EQ); if (!challenge) { return 0; } pairmemcpy(challenge, data->challenge, MSCHAPV2_CHALLENGE_LEN); response = pairmake_packet("MS-CHAP2-Response", NULL, T_OP_EQ); if (!response) { return 0; } response->length = MSCHAPV2_RESPONSE_LEN; response->vp_octets = p = talloc_array(response, uint8_t, response->length); p[0] = eap_ds->response->type.data[1]; p[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN]; memcpy(p + 2, &eap_ds->response->type.data[5], MSCHAPV2_RESPONSE_LEN - 2); name = pairmake_packet("MS-CHAP-User-Name", NULL, T_OP_EQ); if (!name) { return 0; } /* * MS-Length - MS-Value - 5. */ name->length = (((eap_ds->response->type.data[2] << 8) | eap_ds->response->type.data[3]) - eap_ds->response->type.data[4] - 5); name->vp_octets = p = talloc_array(name, uint8_t, name->length + 1); memcpy(p, &eap_ds->response->type.data[4 + MSCHAPV2_RESPONSE_LEN], name->length); p[name->length] = '\0'; packet_ready: #ifdef WITH_PROXY /* * If this options is set, then we do NOT authenticate the * user here. Instead, now that we've added the MS-CHAP * attributes to the request, we STOP, and let the outer * tunnel code handle it. * * This means that the outer tunnel code will DELETE the * EAP attributes, and proxy the MS-CHAP attributes to a * home server. */ if (request->options & RAD_REQUEST_OPTION_PROXY_EAP) { char *username = NULL; eap_tunnel_data_t *tunnel; RDEBUG2("cancelling authentication and letting it be proxied"); /* * Set up the callbacks for the tunnel */ tunnel = talloc_zero(request, eap_tunnel_data_t); tunnel->tls_session = arg; tunnel->callback = mschap_postproxy; /* * Associate the callback with the request. */ rcode = request_data_add(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK, tunnel, NULL); rad_assert(rcode == 0); /* * The State attribute is NOT supposed to * go into the proxied packet, it will confuse * other RADIUS servers, and they will discard * the request. * * The PEAP module will take care of adding * the State attribute back, before passing * the handler & request back into the tunnel. */ pairdelete(&request->packet->vps, PW_STATE, 0, TAG_ANY); /* * Fix the User-Name when proxying, to strip off * the NT Domain, if we're told to, and a User-Name * exists, and there's a \\, meaning an NT-Domain * in the user name, THEN discard the user name. */ if (inst->with_ntdomain_hack && ((challenge = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY)) != NULL) && ((username = strchr(challenge->vp_strvalue, '\\')) != NULL)) { /* * Wipe out the NT domain. * * FIXME: Put it into MS-CHAP-Domain? */ username++; /* skip the \\ */ pairstrcpy(challenge, username); } /* * Remember that in the post-proxy stage, we've got * to do the work below, AFTER the call to MS-CHAP * authentication... */ return 1; } #endif /* * This is a wild & crazy hack. */ rcode = process_authenticate(PW_AUTHTYPE_MS_CHAP, request); /* * Delete MPPE keys & encryption policy. We don't * want these here. */ fix_mppe_keys(handler, data); /* * Take the response from the mschap module, and * return success or failure, depending on the result. */ response = NULL; if (rcode == RLM_MODULE_OK) { pairfilter(data, &response, &request->reply->vps, PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY); data->code = PW_EAP_MSCHAPV2_SUCCESS; } else if (inst->send_error) { pairfilter(data, &response, &request->reply->vps, PW_MSCHAP_ERROR, VENDORPEC_MICROSOFT, TAG_ANY); if (response) { int n,err,retry; char buf[34]; RDEBUG2("MSCHAP-Error: %s", response->vp_strvalue); /* * Pxarse the new challenge out of the * MS-CHAP-Error, so that if the client * issues a re-try, we will know which * challenge value that they used. */ n = sscanf(response->vp_strvalue, "%*cE=%d R=%d C=%32s", &err, &retry, &buf[0]); if (n == 3) { DEBUG2("Found new challenge from MS-CHAP-Error: err=%d retry=%d challenge=%s", err, retry, buf); fr_hex2bin(buf, data->challenge, 16); } else { DEBUG2("Could not parse new challenge from MS-CHAP-Error: %d", n); } } data->code = PW_EAP_MSCHAPV2_FAILURE; } else { eap_ds->request->code = PW_EAP_FAILURE; return 1; } /* * No response, die. */ if (!response) { REDEBUG("No MS-CHAP-Success or MS-CHAP-Error was found."); return 0; } /* * Compose the response (whatever it is), * and return it to the over-lying EAP module. */ eapmschapv2_compose(handler, response); pairfree(&response); return 1; }