/* * Add hints to the info sent by the terminal server * based on the pattern of the username, and other attributes. */ static int hints_setup(PAIR_LIST *hints, REQUEST *request) { char const *name; VALUE_PAIR *add; VALUE_PAIR *tmp; PAIR_LIST *i; VALUE_PAIR *request_pairs; int updated = 0, ft; request_pairs = request->packet->vps; if (!hints || !request_pairs) return RLM_MODULE_NOOP; /* * Check for valid input, zero length names not permitted */ name = (tmp = fr_pair_find_by_num(request_pairs, PW_USER_NAME, 0, TAG_ANY)) ? tmp->vp_strvalue : NULL; if (!name || name[0] == 0) { /* * No name, nothing to do. */ return RLM_MODULE_NOOP; } for (i = hints; i; i = i->next) { /* * Use "paircompare", which is a little more general... */ if (((strcmp(i->name, "DEFAULT") == 0) || (strcmp(i->name, name) == 0)) && (paircompare(request, request_pairs, i->check, NULL) == 0)) { RDEBUG2("hints: Matched %s at %d", i->name, i->lineno); /* * Now add all attributes to the request list, * except PW_STRIP_USER_NAME and PW_FALL_THROUGH * and xlat them. */ add = fr_pair_list_copy(request->packet, i->reply); ft = fall_through(add); fr_pair_delete_by_num(&add, PW_STRIP_USER_NAME, 0, TAG_ANY); fr_pair_delete_by_num(&add, PW_FALL_THROUGH, 0, TAG_ANY); radius_pairmove(request, &request->packet->vps, add, true); updated = 1; if (!ft) { break; } } } if (updated == 0) { return RLM_MODULE_NOOP; } return RLM_MODULE_UPDATED; }
/* * compose EAP reply packet in EAP-Message attr of RADIUS. If * EAP exceeds 253, frame it in multiple EAP-Message attrs. */ int eap_basic_compose(RADIUS_PACKET *packet, eap_packet_t *reply) { VALUE_PAIR *vp; eap_packet_raw_t *eap_packet; int rcode; if (eap_wireformat(reply) == EAP_INVALID) { return RLM_MODULE_INVALID; } eap_packet = (eap_packet_raw_t *)reply->packet; fr_pair_delete_by_num(&(packet->vps), 0, PW_EAP_MESSAGE, TAG_ANY); vp = eap_packet2vp(packet, eap_packet); if (!vp) return RLM_MODULE_INVALID; fr_pair_add(&(packet->vps), vp); /* * EAP-Message is always associated with * Message-Authenticator but not vice-versa. * * Don't add a Message-Authenticator if it's already * there. */ vp = fr_pair_find_by_num(packet->vps, 0, PW_MESSAGE_AUTHENTICATOR, TAG_ANY); if (!vp) { vp = fr_pair_afrom_num(packet, 0, PW_MESSAGE_AUTHENTICATOR); vp->vp_length = AUTH_VECTOR_LEN; vp->vp_octets = talloc_zero_array(vp, uint8_t, vp->vp_length); fr_pair_add(&(packet->vps), vp); } /* Set request reply code, but only if it's not already set. */ rcode = RLM_MODULE_OK; if (!packet->code) switch (reply->code) { case PW_EAP_RESPONSE: case PW_EAP_SUCCESS: packet->code = PW_CODE_ACCESS_ACCEPT; rcode = RLM_MODULE_HANDLED; break; case PW_EAP_FAILURE: packet->code = PW_CODE_ACCESS_REJECT; rcode = RLM_MODULE_REJECT; break; case PW_EAP_REQUEST: packet->code = PW_CODE_ACCESS_CHALLENGE; rcode = RLM_MODULE_HANDLED; break; default: /* Should never enter here */ ERROR("rlm_eap: reply code %d is unknown, Rejecting the request.", reply->code); packet->code = PW_CODE_ACCESS_REJECT; break; } return rcode; }
static int mschapv1_encode(RADIUS_PACKET *packet, VALUE_PAIR **request, char const *password) { unsigned int i; uint8_t *p; VALUE_PAIR *challenge, *reply; uint8_t nthash[16]; fr_pair_delete_by_num(&packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY); fr_pair_delete_by_num(&packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY); challenge = fr_pair_afrom_num(packet, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT); if (!challenge) { return 0; } fr_pair_add(request, challenge); challenge->vp_length = 8; challenge->vp_octets = p = talloc_array(challenge, uint8_t, challenge->vp_length); for (i = 0; i < challenge->vp_length; i++) { p[i] = fr_rand(); } reply = fr_pair_afrom_num(packet, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT); if (!reply) { return 0; } fr_pair_add(request, reply); reply->vp_length = 50; reply->vp_octets = p = talloc_array(reply, uint8_t, reply->vp_length); memset(p, 0, reply->vp_length); p[1] = 0x01; /* NT hash */ if (mschap_ntpwdhash(nthash, password) < 0) { return 0; } smbdes_mschap(nthash, challenge->vp_octets, p + 26); return 1; }
/* * Find the named user in this modules database. Create the set * of attribute-value pairs to check and reply with for this user * from the database. The authentication code only needs to check * the password, the rest is done here. */ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { VALUE_PAIR *state; rlm_smsotp_t *inst = instance; /* * Look for the 'state' attribute. */ state = fr_pair_find_by_num(request->packet->vps, PW_STATE, 0, TAG_ANY); if (state != NULL) { DEBUG("rlm_smsotp: Found reply to access challenge (AUTZ), Adding Auth-Type '%s'",inst->authtype); fr_pair_delete_by_num(&request->config, PW_AUTH_TYPE, 0, TAG_ANY); /* delete old auth-type */ pair_make_config("Auth-Type", inst->authtype, T_OP_SET); } return RLM_MODULE_OK; }
/* * Run a virtual server auth and postauth * */ int rad_virtual_server(REQUEST *request) { VALUE_PAIR *vp; int result; RDEBUG("Virtual server %s received request", request->server); rdebug_pair_list(L_DBG_LVL_1, request, request->packet->vps, NULL); RDEBUG("server %s {", request->server); RINDENT(); /* * We currently only handle AUTH packets here. * This could be expanded to handle other packets as well if required. */ rad_assert(request->packet->code == PW_CODE_ACCESS_REQUEST); result = rad_authenticate(request); if (request->reply->code == PW_CODE_ACCESS_REJECT) { fr_pair_delete_by_num(&request->config, PW_POST_AUTH_TYPE, 0, TAG_ANY); vp = pair_make_config("Post-Auth-Type", "Reject", T_OP_SET); if (vp) rad_postauth(request); } if (request->reply->code == PW_CODE_ACCESS_ACCEPT) { rad_postauth(request); } REXDENT(); RDEBUG("} # server %s", request->server); RDEBUG("Virtual server sending reply"); rdebug_pair_list(L_DBG_LVL_1, request, request->reply->vps, NULL); return result; }
/* * rad_accounting: call modules. * * 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_accounting(REQUEST *request) { int result = RLM_MODULE_OK; #ifdef WITH_PROXY #define WAS_PROXIED (request->proxy) #else #define WAS_PROXIED (0) #endif /* * Run the modules only once, before proxying. */ if (!WAS_PROXIED) { VALUE_PAIR *vp; int acct_type = 0; result = module_preacct(request); switch (result) { /* * The module has a number of OK return codes. */ case RLM_MODULE_NOOP: case RLM_MODULE_OK: case RLM_MODULE_UPDATED: break; /* * The module handled the request, stop here. */ case RLM_MODULE_HANDLED: return result; /* * The module failed, or said the request is * invalid, therefore we stop here. */ case RLM_MODULE_FAIL: case RLM_MODULE_INVALID: case RLM_MODULE_NOTFOUND: case RLM_MODULE_REJECT: case RLM_MODULE_USERLOCK: default: return result; } /* * Do the data storage before proxying. This is to ensure * that we log the packet, even if the proxy never does. */ vp = fr_pair_find_by_num(request->config, PW_ACCT_TYPE, 0, TAG_ANY); if (vp) { acct_type = vp->vp_integer; DEBUG2(" Found Acct-Type %s", dict_valnamebyattr(PW_ACCT_TYPE, 0, acct_type)); } result = process_accounting(acct_type, request); switch (result) { /* * In case the accounting module returns FAIL, * it's still useful to send the data to the * proxy. */ case RLM_MODULE_FAIL: case RLM_MODULE_NOOP: case RLM_MODULE_OK: case RLM_MODULE_UPDATED: break; /* * The module handled the request, don't reply. */ case RLM_MODULE_HANDLED: return result; /* * Neither proxy, nor reply to invalid requests. */ case RLM_MODULE_INVALID: case RLM_MODULE_NOTFOUND: case RLM_MODULE_REJECT: case RLM_MODULE_USERLOCK: default: return result; } /* * Maybe one of the preacct modules has decided * that a proxy should be used. */ if ((vp = fr_pair_find_by_num(request->config, PW_PROXY_TO_REALM, 0, TAG_ANY))) { REALM *realm; /* * Check whether Proxy-To-Realm is * a LOCAL realm. */ realm = realm_find2(vp->vp_strvalue); if (realm && !realm->acct_pool) { DEBUG("rad_accounting: Cancelling proxy to realm %s, as it is a LOCAL realm.", realm->name); fr_pair_delete_by_num(&request->config, PW_PROXY_TO_REALM, 0, TAG_ANY); } else { /* * Don't reply to the NAS now because * we have to send the proxied packet * before that. */ return result; } } } #ifdef WITH_PROXY /* * We didn't see a reply to the proxied request. Fail. */ if (request->proxy && !request->proxy_reply) return RLM_MODULE_FAIL; #endif /* * We get here IF we're not proxying, OR if we've * received the accounting reply from the end server, * THEN we can reply to the NAS. * If the accounting module returns NOOP, the data * storage did not succeed, so radiusd should not send * Accounting-Response. */ switch (result) { /* * Send back an ACK to the NAS. */ case RLM_MODULE_OK: case RLM_MODULE_UPDATED: request->reply->code = PW_CODE_ACCOUNTING_RESPONSE; break; /* * Failed to log or to proxy the accounting data, * therefore don't reply to the NAS. */ case RLM_MODULE_FAIL: case RLM_MODULE_INVALID: case RLM_MODULE_NOOP: case RLM_MODULE_NOTFOUND: case RLM_MODULE_REJECT: case RLM_MODULE_USERLOCK: default: break; } return result; }
/* * Use a reply packet to determine what to do. */ static rlm_rcode_t CC_HINT(nonnull) process_reply(eap_handler_t *handler, tls_session_t *tls_session, REQUEST *request, RADIUS_PACKET *reply) { rlm_rcode_t rcode = RLM_MODULE_REJECT; VALUE_PAIR *vp; ttls_tunnel_t *t = tls_session->opaque; rad_assert(handler->request == request); /* * If the response packet was Access-Accept, then * we're OK. If not, die horribly. * * FIXME: Take MS-CHAP2-Success attribute, and * tunnel it back to the client, to authenticate * ourselves to the client. * * FIXME: If we have an Access-Challenge, then * the Reply-Message is tunneled back to the client. * * FIXME: If we have an EAP-Message, then that message * must be tunneled back to the client. * * FIXME: If we have an Access-Challenge with a State * attribute, then do we tunnel that to the client, or * keep track of it ourselves? * * FIXME: EAP-Messages can only start with 'identity', * NOT 'eap start', so we should check for that.... */ switch (reply->code) { case PW_CODE_ACCESS_ACCEPT: RDEBUG("Got tunneled Access-Accept"); rcode = RLM_MODULE_OK; /* * MS-CHAP2-Success means that we do NOT return * an Access-Accept, but instead tunnel that * attribute to the client, and keep going with * the TTLS session. Once the client accepts * our identity, it will respond with an empty * packet, and we will send EAP-Success. */ vp = NULL; fr_pair_list_move_by_num(tls_session, &vp, &reply->vps, PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY); if (vp) { RDEBUG("Got MS-CHAP2-Success, tunneling it to the client in a challenge"); rcode = RLM_MODULE_HANDLED; t->authenticated = true; /* * Delete MPPE keys & encryption policy. We don't * want these here. */ fr_pair_delete_by_num(&reply->vps, 7, VENDORPEC_MICROSOFT, TAG_ANY); fr_pair_delete_by_num(&reply->vps, 8, VENDORPEC_MICROSOFT, TAG_ANY); fr_pair_delete_by_num(&reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY); fr_pair_delete_by_num(&reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY); /* * Use the tunneled reply, but not now. */ if (t->use_tunneled_reply) { rad_assert(!t->accept_vps); fr_pair_list_move_by_num(t, &t->accept_vps, &reply->vps, 0, 0, TAG_ANY); rad_assert(!reply->vps); } } else { /* no MS-CHAP2-Success */ /* * Can only have EAP-Message if there's * no MS-CHAP2-Success. (FIXME: EAP-MSCHAP?) * * We also do NOT tunnel the EAP-Success * attribute back to the client, as the client * can figure it out, from the non-tunneled * EAP-Success packet. */ fr_pair_list_move_by_num(tls_session, &vp, &reply->vps, PW_EAP_MESSAGE, 0, TAG_ANY); fr_pair_list_free(&vp); } /* move channel binding responses; we need to send them */ fr_pair_list_move_by_num(tls_session, &vp, &reply->vps, PW_UKERNA_CHBIND, VENDORPEC_UKERNA, TAG_ANY); if (fr_pair_find_by_num(vp, PW_UKERNA_CHBIND, VENDORPEC_UKERNA, TAG_ANY) != NULL) { t->authenticated = true; /* * Use the tunneled reply, but not now. */ if (t->use_tunneled_reply) { rad_assert(!t->accept_vps); fr_pair_list_move_by_num(t, &t->accept_vps, &reply->vps, 0, 0, TAG_ANY); rad_assert(!reply->vps); } rcode = RLM_MODULE_HANDLED; } /* * Handle the ACK, by tunneling any necessary reply * VP's back to the client. */ if (vp) { RDEBUG("Sending tunneled reply attributes"); rdebug_pair_list(L_DBG_LVL_1, request, vp, NULL); vp2diameter(request, tls_session, vp); fr_pair_list_free(&vp); } /* * If we've been told to use the attributes from * the reply, then do so. * * WARNING: This may leak information about the * tunneled user! */ if (t->use_tunneled_reply) { fr_pair_delete_by_num(&reply->vps, PW_PROXY_STATE, 0, TAG_ANY); fr_pair_list_move_by_num(request->reply, &request->reply->vps, &reply->vps, 0, 0, TAG_ANY); } break; case PW_CODE_ACCESS_REJECT: RDEBUG("Got tunneled Access-Reject"); rcode = RLM_MODULE_REJECT; break; /* * Handle Access-Challenge, but only if we * send tunneled reply data. This is because * an Access-Challenge means that we MUST tunnel * a Reply-Message to the client. */ case PW_CODE_ACCESS_CHALLENGE: RDEBUG("Got tunneled Access-Challenge"); /* * Keep the State attribute, if necessary. * * Get rid of the old State, too. */ fr_pair_list_free(&t->state); fr_pair_list_move_by_num(t, &t->state, &reply->vps, PW_STATE, 0, TAG_ANY); /* * We should really be a bit smarter about this, * and move over only those attributes which * are relevant to the authentication request, * but that's a lot more work, and this "dumb" * method works in 99.9% of the situations. */ vp = NULL; fr_pair_list_move_by_num(t, &vp, &reply->vps, PW_EAP_MESSAGE, 0, TAG_ANY); /* * There MUST be a Reply-Message in the challenge, * which we tunnel back to the client. * * If there isn't one in the reply VP's, then * we MUST create one, with an empty string as * it's value. */ fr_pair_list_move_by_num(t, &vp, &reply->vps, PW_REPLY_MESSAGE, 0, TAG_ANY); /* also move chbind messages, if any */ fr_pair_list_move_by_num(t, &vp, &reply->vps, PW_UKERNA_CHBIND, VENDORPEC_UKERNA, TAG_ANY); /* * Handle the ACK, by tunneling any necessary reply * VP's back to the client. */ if (vp) { vp2diameter(request, tls_session, vp); fr_pair_list_free(&vp); } rcode = RLM_MODULE_HANDLED; break; default: RDEBUG("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code); rcode = RLM_MODULE_INVALID; break; } return rcode; }
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); if (!inst->config->groupmemb_query) { RWARN("Cannot do check groups when group_membership_query is not set"); do_nothing: *do_fall_through = FALL_THROUGH_DEFAULT; /* * Didn't add group attributes or allocate * memory, so don't do anything else. */ return RLM_MODULE_NOTFOUND; } /* * 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"); goto do_nothing; } 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 = pair_make_request(inst->group_da->name, NULL, T_OP_EQ); if (!sql_group) { REDEBUG("Error creating %s attribute", inst->group_da->name); rcode = RLM_MODULE_FAIL; goto finish; } entry = head; do { next: rad_assert(entry != NULL); fr_pair_value_strcpy(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, inst->sql_escape_func, *handle) < 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)) { fr_pair_list_free(&check_tmp); entry = entry->next; if (!entry) break; 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, 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, inst->sql_escape_func, *handle) < 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); fr_pair_delete_by_num(&request->packet->vps, 0, inst->group_da->attr, TAG_ANY); return rcode; }