From: Alan T. DeKok Date: Tue, 15 Jun 2021 13:50:36 +0000 (-0400) Subject: copied from process_radius X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b8d50d220dcf8435af29f41eb6774539f2cd1e18;p=thirdparty%2Ffreeradius-server.git copied from process_radius with and removed pretty much everything other than Access-Request and replies. The inner tunnel for TTLS doesn't do accounting, CoA, etc. --- diff --git a/src/process/ttls/all.mk b/src/process/ttls/all.mk new file mode 100644 index 00000000000..0f2eaba3598 --- /dev/null +++ b/src/process/ttls/all.mk @@ -0,0 +1,10 @@ +TARGETNAME := process_ttls + +ifneq "$(TARGETNAME)" "" +TARGET := $(TARGETNAME).a +endif + +SOURCES := base.c + +TGT_PREREQS := libfreeradius-radius.a + diff --git a/src/process/ttls/base.c b/src/process/ttls/base.c new file mode 100644 index 00000000000..f1b748f46b8 --- /dev/null +++ b/src/process/ttls/base.c @@ -0,0 +1,861 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * @file src/process/ttls/base.c + * @brief TTLS process module + * + * @copyright 2021 The FreeRADIUS server project. + * @copyright 2021 Network RADIUS SARL (legal@networkradius.com) + */ +#include +#include + +#include +#include +#include +#include + +#include +#include + +static fr_dict_t const *dict_freeradius; +static fr_dict_t const *dict_radius; + +extern fr_dict_autoload_t process_ttls_dict[]; +fr_dict_autoload_t process_ttls_dict[] = { + { .out = &dict_freeradius, .proto = "freeradius" }, + { .out = &dict_radius, .proto = "radius" }, + { NULL } +}; + +static fr_dict_attr_t const *attr_auth_type; +static fr_dict_attr_t const *attr_module_failure_message; +static fr_dict_attr_t const *attr_module_success_message; +static fr_dict_attr_t const *attr_stripped_user_name; + +static fr_dict_attr_t const *attr_calling_station_id; +static fr_dict_attr_t const *attr_chap_password; +static fr_dict_attr_t const *attr_nas_port; +static fr_dict_attr_t const *attr_packet_type; +static fr_dict_attr_t const *attr_service_type; +static fr_dict_attr_t const *attr_state; +static fr_dict_attr_t const *attr_user_name; +static fr_dict_attr_t const *attr_user_password; +static fr_dict_attr_t const *attr_original_packet_code; +static fr_dict_attr_t const *attr_error_cause; + +extern fr_dict_attr_autoload_t process_ttls_dict_attr[]; +fr_dict_attr_autoload_t process_ttls_dict_attr[] = { + { .out = &attr_auth_type, .name = "Auth-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius }, + { .out = &attr_module_failure_message, .name = "Module-Failure-Message", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, + { .out = &attr_module_success_message, .name = "Module-Success-Message", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, + { .out = &attr_stripped_user_name, .name = "Stripped-User-Name", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, + + { .out = &attr_calling_station_id, .name = "Calling-Station-Id", .type = FR_TYPE_STRING, .dict = &dict_radius }, + { .out = &attr_chap_password, .name = "CHAP-Password", .type = FR_TYPE_OCTETS, .dict = &dict_radius }, + { .out = &attr_nas_port, .name = "NAS-Port", .type = FR_TYPE_UINT32, .dict = &dict_radius }, + { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_radius }, + { .out = &attr_service_type, .name = "Service-Type", .type = FR_TYPE_UINT32, .dict = &dict_radius }, + { .out = &attr_state, .name = "State", .type = FR_TYPE_OCTETS, .dict = &dict_radius }, + { .out = &attr_user_name, .name = "User-Name", .type = FR_TYPE_STRING, .dict = &dict_radius }, + { .out = &attr_user_password, .name = "User-Password", .type = FR_TYPE_STRING, .dict = &dict_radius }, + + { .out = &attr_original_packet_code, .name = "Extended-Attribute-1.Original-Packet-Code", .type = FR_TYPE_UINT32, .dict = &dict_radius }, + { .out = &attr_error_cause, .name = "Error-Cause", .type = FR_TYPE_UINT32, .dict = &dict_radius }, + + { NULL } +}; + +static fr_value_box_t const *enum_auth_type_accept; +static fr_value_box_t const *enum_auth_type_reject; + +extern fr_dict_enum_autoload_t process_ttls_dict_enum[]; +fr_dict_enum_autoload_t process_ttls_dict_enum[] = { + { .out = &enum_auth_type_accept, .name = "Accept", .attr = &attr_auth_type }, + { .out = &enum_auth_type_reject, .name = "Reject", .attr = &attr_auth_type }, + { NULL } +}; + +/* + * RADIUS state machine configuration + */ +typedef struct { + uint64_t nothing; // so that "access_request" isn't at offset 0 + + CONF_SECTION *access_request; + CONF_SECTION *access_accept; + CONF_SECTION *access_reject; + CONF_SECTION *access_challenge; + + CONF_SECTION *accounting_request; + CONF_SECTION *accounting_response; + + CONF_SECTION *status_server; + + CONF_SECTION *coa_request; + CONF_SECTION *coa_ack; + CONF_SECTION *coa_nak; + + CONF_SECTION *disconnect_request; + CONF_SECTION *disconnect_ack; + CONF_SECTION *disconnect_nak; + + CONF_SECTION *do_not_respond; + CONF_SECTION *protocol_error; /* @todo - allow protocol error as a reject reply? */ +} process_ttls_sections_t; + +typedef struct { + bool log_stripped_names; + bool log_auth; //!< Log authentication attempts. + bool log_auth_badpass; //!< Log successful authentications. + bool log_auth_goodpass; //!< Log failed authentications. + char const *auth_badpass_msg; //!< Additional text to append to successful auth messages. + char const *auth_goodpass_msg; //!< Additional text to append to failed auth messages. + + char const *denied_msg; //!< Additional text to append if the user is already logged + //!< in (simultaneous use check failed). + + fr_time_delta_t session_timeout; //!< Maximum time between the last response and next request. + uint32_t max_session; //!< Maximum ongoing session allowed. + + uint8_t state_server_id; //!< Sets a specific byte in the state to allow the + //!< authenticating server to be identified in packet + //! + +static const CONF_PARSER session_config[] = { + { FR_CONF_OFFSET("timeout", FR_TYPE_TIME_DELTA, process_ttls_auth_t, session_timeout), .dflt = "15" }, + { FR_CONF_OFFSET("max", FR_TYPE_UINT32, process_ttls_auth_t, max_session), .dflt = "4096" }, + { FR_CONF_OFFSET("state_server_id", FR_TYPE_UINT8, process_ttls_auth_t, state_server_id) }, + + CONF_PARSER_TERMINATOR +}; + +static const CONF_PARSER log_config[] = { + { FR_CONF_OFFSET("stripped_names", FR_TYPE_BOOL, process_ttls_auth_t, log_stripped_names), .dflt = "no" }, + { FR_CONF_OFFSET("auth", FR_TYPE_BOOL, process_ttls_auth_t, log_auth), .dflt = "no" }, + { FR_CONF_OFFSET("auth_badpass", FR_TYPE_BOOL, process_ttls_auth_t, log_auth_badpass), .dflt = "no" }, + { FR_CONF_OFFSET("auth_goodpass", FR_TYPE_BOOL,process_ttls_auth_t, log_auth_goodpass), .dflt = "no" }, + { FR_CONF_OFFSET("msg_badpass", FR_TYPE_STRING, process_ttls_auth_t, auth_badpass_msg) }, + { FR_CONF_OFFSET("msg_goodpass", FR_TYPE_STRING, process_ttls_auth_t, auth_goodpass_msg) }, + { FR_CONF_OFFSET("msg_denied", FR_TYPE_STRING, process_ttls_auth_t, denied_msg), .dflt = "You are already logged in - access denied" }, + + CONF_PARSER_TERMINATOR +}; + +static const CONF_PARSER auth_config[] = { + { FR_CONF_POINTER("log", FR_TYPE_SUBSECTION, NULL), .subcs = (void const *) log_config }, + + { FR_CONF_POINTER("session", FR_TYPE_SUBSECTION, NULL), .subcs = (void const *) session_config }, + + CONF_PARSER_TERMINATOR +}; + +static const CONF_PARSER config[] = { + { FR_CONF_POINTER("Access-Request", FR_TYPE_SUBSECTION, NULL), .subcs = (void const *) auth_config, + .offset = offsetof(process_ttls_t, auth), }, + + CONF_PARSER_TERMINATOR +}; + +/* + * Debug the packet if requested. + */ +static void radius_packet_debug(request_t *request, fr_radius_packet_t *packet, fr_pair_list_t *list, bool received) +{ +#ifdef WITH_IFINDEX_NAME_RESOLUTION + char if_name[IFNAMSIZ]; +#endif + + if (!packet) return; + if (!RDEBUG_ENABLED) return; + + log_request(L_DBG, L_DBG_LVL_1, request, __FILE__, __LINE__, "%s %s ID %d from %s%pV%s:%i to %s%pV%s:%i " +#ifdef WITH_IFINDEX_NAME_RESOLUTION + "%s%s%s" +#endif + "", + received ? "Received" : "Sending", + fr_packet_codes[packet->code], + packet->id, + packet->socket.inet.src_ipaddr.af == AF_INET6 ? "[" : "", + fr_box_ipaddr(packet->socket.inet.src_ipaddr), + packet->socket.inet.src_ipaddr.af == AF_INET6 ? "]" : "", + packet->socket.inet.src_port, + packet->socket.inet.dst_ipaddr.af == AF_INET6 ? "[" : "", + fr_box_ipaddr(packet->socket.inet.dst_ipaddr), + packet->socket.inet.dst_ipaddr.af == AF_INET6 ? "]" : "", + packet->socket.inet.dst_port +#ifdef WITH_IFINDEX_NAME_RESOLUTION + , packet->socket.inet.ifindex ? "via " : "", + packet->socket.inet.ifindex ? fr_ifname_from_ifindex(if_name, packet->socket.inet.ifindex) : "", + packet->socket.inet.ifindex ? " " : "" +#endif + ); + + if (received || request->parent) { + log_request_pair_list(L_DBG_LVL_1, request, NULL, list, NULL); + } else { + log_request_proto_pair_list(L_DBG_LVL_1, request, NULL, list, NULL); + } +} + +#define RAUTH(fmt, ...) log_request(L_AUTH, L_DBG_LVL_OFF, request, __FILE__, __LINE__, fmt, ## __VA_ARGS__) + +/* + * Return a short string showing the terminal server, port + * and calling station ID. + */ +static char *auth_name(char *buf, size_t buflen, request_t *request) +{ + fr_pair_t *cli; + fr_pair_t *pair; + uint32_t port = 0; /* RFC 2865 NAS-Port is 4 bytes */ + char const *tls = ""; + RADCLIENT *client = client_from_request(request); + + cli = fr_pair_find_by_da(&request->request_pairs, attr_calling_station_id, 0); + + pair = fr_pair_find_by_da(&request->request_pairs, attr_nas_port, 0); + if (pair != NULL) port = pair->vp_uint32; + + if (request->packet->socket.inet.dst_port == 0) tls = " via proxy to virtual server"; + + snprintf(buf, buflen, "from client %.128s port %u%s%.128s%s", + client ? client->shortname : "", port, + (cli ? " cli " : ""), (cli ? cli->vp_strvalue : ""), + tls); + + return buf; +} + +/* + * Make sure user/pass are clean and then create an attribute + * which contains the log message. + */ +static void CC_HINT(format (printf, 4, 5)) auth_message(process_ttls_auth_t const *inst, + request_t *request, bool goodpass, char const *fmt, ...) +{ + va_list ap; + + bool logit; + char const *extra_msg = NULL; + + char password_buff[128]; + char const *password_str = NULL; + + char buf[1024]; + char extra[1024]; + char *p; + char *msg; + fr_pair_t *username = NULL; + fr_pair_t *password = NULL; + + /* + * No logs? Then no logs. + */ + if (!inst->log_auth) return; + + /* + * Get the correct username based on the configured value + */ + if (!inst->log_stripped_names) { + username = fr_pair_find_by_da(&request->request_pairs, attr_user_name, 0); + } else { + username = fr_pair_find_by_da(&request->request_pairs, attr_stripped_user_name, 0); + if (!username) username = fr_pair_find_by_da(&request->request_pairs, attr_user_name, 0); + } + + /* + * Clean up the password + */ + if (inst->log_auth_badpass || inst->log_auth_goodpass) { + password = fr_pair_find_by_da(&request->request_pairs, attr_user_password, 0); + if (!password) { + fr_pair_t *auth_type; + + auth_type = fr_pair_find_by_da(&request->control_pairs, attr_auth_type, 0); + if (auth_type) { + snprintf(password_buff, sizeof(password_buff), "", + fr_dict_enum_name_by_value(auth_type->da, &auth_type->data)); + password_str = password_buff; + } else { + password_str = ""; + } + } else if (fr_pair_find_by_da(&request->request_pairs, attr_chap_password, 0)) { + password_str = ""; + } + } + + if (goodpass) { + logit = inst->log_auth_goodpass; + extra_msg = inst->auth_goodpass_msg; + } else { + logit = inst->log_auth_badpass; + extra_msg = inst->auth_badpass_msg; + } + + if (extra_msg) { + extra[0] = ' '; + p = extra + 1; + if (xlat_eval(p, sizeof(extra) - 1, request, extra_msg, NULL, NULL) < 0) return; + } else { + *extra = '\0'; + } + + /* + * Expand the input message + */ + va_start(ap, fmt); + msg = fr_vasprintf(request, fmt, ap); + va_end(ap); + + RAUTH("%s: [%pV%s%pV] (%s)%s", + msg, + username ? &username->data : fr_box_strvalue(""), + logit ? "/" : "", + logit ? (password_str ? fr_box_strvalue(password_str) : &password->data) : fr_box_strvalue(""), + auth_name(buf, sizeof(buf), request), + extra); + + talloc_free(msg); +} + +RECV(access_request) +{ + process_ttls_t const *inst = talloc_get_type_abort_const(mctx->instance, process_ttls_t); + + /* + * Requests with invalid state values + * are extremely unlikely to result + * in success, so reject them as quickly + * as we possible. + */ + if (fr_state_to_request(inst->auth.state_tree, request) < 0) { + fr_process_state_t const *state; + CONF_SECTION *cs; + + request->reply->code = FR_RADIUS_CODE_ACCESS_REJECT; + UPDATE_STATE_CS(reply); + return unlang_module_yield_to_section(p_result, request, + cs, state->rcode, state->send, + NULL, NULL); + } + + return CALL_RECV(generic); +} + +RESUME(auth_type); + +RESUME(access_request) +{ + rlm_rcode_t rcode = request->rcode; + fr_pair_t *vp; + CONF_SECTION *cs; + fr_dict_enum_t const *dv; + fr_process_state_t const *state; + process_ttls_t const *inst = talloc_get_type_abort_const(mctx->instance, process_ttls_t); + + PROCESS_TRACE; + + fr_assert(rcode < RLM_MODULE_NUMCODES); + + UPDATE_STATE(packet); + + request->reply->code = state->packet_type[rcode]; + if (!request->reply->code) request->reply->code = state->default_reply; + if (!request->reply->code) request->reply->code = PROCESS_CODE_DO_NOT_RESPOND; + UPDATE_STATE_CS(reply); + + if (request->reply->code == FR_RADIUS_CODE_DO_NOT_RESPOND) { + RDEBUG("The 'recv Access-Request' section returned %s - not sending a response", + fr_table_str_by_value(rcode_table, rcode, "???")); + + send_reply: + fr_assert(state->send != NULL); + return unlang_module_yield_to_section(p_result, request, + cs, state->rcode, state->send, + NULL, NULL); + } + + /* + * Run authenticate foo { ... } + */ + vp = fr_pair_find_by_da(&request->control_pairs, attr_auth_type, 0); + if (!vp) goto send_reply; + + dv = fr_dict_enum_by_value(vp->da, &vp->data); + if (!dv) goto send_reply; + + /* + * The magic Auth-Type accept value + * which means skip the authenticate + * section... + */ + if (fr_value_box_cmp(enum_auth_type_accept, dv->value) == 0) { + request->reply->code = FR_RADIUS_CODE_ACCESS_ACCEPT; + goto send_reply; + } else if (fr_value_box_cmp(enum_auth_type_reject, dv->value) == 0) { + request->reply->code = FR_RADIUS_CODE_ACCESS_REJECT; + goto send_reply; + } + + cs = cf_section_find(inst->server_cs, "authenticate", dv->name); + if (!cs) { + RDEBUG2("No 'authenticate %s { ... }' section found - skipping...", dv->name); + goto send_reply; + } + + /* + * Run the "Authenticate = foo" section. + * + * And continue with sending the generic reply. + */ + RDEBUG("Running 'authenticate %s' from file %s", cf_section_name2(cs), cf_filename(cs)); + return unlang_module_yield_to_section(p_result, request, + cs, RLM_MODULE_NOOP, resume_auth_type, + NULL, rctx); +} + +RESUME(auth_type) +{ + static const fr_process_rcode_t auth_type_rcode = { + [RLM_MODULE_OK] = FR_RADIUS_CODE_ACCESS_ACCEPT, + [RLM_MODULE_FAIL] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_INVALID] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_NOOP] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_NOTFOUND] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_REJECT] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_UPDATED] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_DISALLOW] = FR_RADIUS_CODE_ACCESS_REJECT, + }; + + rlm_rcode_t rcode = request->rcode; + fr_pair_t *vp; + fr_process_state_t const *state; + + PROCESS_TRACE; + + fr_assert(rcode < RLM_MODULE_NUMCODES); + fr_assert(FR_RADIUS_PACKET_CODE_VALID(request->reply->code)); + + if (auth_type_rcode[rcode] == FR_RADIUS_CODE_DO_NOT_RESPOND) { + request->reply->code = auth_type_rcode[rcode]; + UPDATE_STATE(reply); + + RDEBUG("The 'authenticate' section returned %s - not sending a response", + fr_table_str_by_value(rcode_table, rcode, "???")); + + fr_assert(state->send != NULL); + return state->send(p_result, mctx, request, rctx); + } + + /* + * Most cases except handled... + */ + if (auth_type_rcode[rcode]) request->reply->code = auth_type_rcode[rcode]; + + switch (request->reply->code) { + case 0: + RDEBUG("No reply code was set. Forcing to Access-Reject"); + request->reply->code = FR_RADIUS_CODE_ACCESS_REJECT; + FALL_THROUGH; + + /* + * Print complaints before running "send Access-Reject" + */ + case FR_RADIUS_CODE_ACCESS_REJECT: + RDEBUG2("Failed to authenticate the user"); + + /* + * Maybe the shared secret is wrong? + */ + vp = fr_pair_find_by_da(&request->request_pairs, attr_user_password, 0); + if (vp) { + if (RDEBUG_ENABLED2) { + uint8_t const *p; + + p = (uint8_t const *) vp->vp_strvalue; + while (*p) { + int size; + + size = fr_utf8_char(p, -1); + if (!size) { + RWDEBUG("Unprintable characters in the password. " + "Double-check the shared secret on the server " + "and the NAS!"); + break; + } + p += size; + } + } + } + break; + + /* + * Access-Challenge sections require a State. If there is + * none, create one here. This is so that the State + * attribute is accessible in the "send Access-Challenge" + * section. + */ + case FR_RADIUS_CODE_ACCESS_CHALLENGE: + if ((vp = fr_pair_find_by_da(&request->reply_pairs, attr_state, 0)) != NULL) { + uint8_t buffer[16]; + + fr_rand_buffer(buffer, sizeof(buffer)); + + MEM(pair_update_reply(&vp, attr_state) >= 0); + fr_pair_value_memdup(vp, buffer, sizeof(buffer), false); + } + break; + + default: + break; + + } + UPDATE_STATE(reply); + + fr_assert(state->send != NULL); + return state->send(p_result, mctx, request, rctx); +} + +RESUME_NO_RCTX(access_accept) +{ + fr_pair_t *vp; + process_ttls_t const *inst = talloc_get_type_abort_const(mctx->instance, process_ttls_t); + + PROCESS_TRACE; + + vp = fr_pair_find_by_da(&request->request_pairs, attr_module_success_message, 0); + if (vp) { + auth_message(&inst->auth, request, true, "Login OK (%pV)", &vp->data); + } else { + auth_message(&inst->auth, request, true, "Login OK"); + } + + /* + * Check that there is a name which can be used to + * identify the user. The configuration depends on + * User-Name or Stripped-User-Name existing, and being + * (mostly) unique to that user. + */ + if (!request->parent && + ((vp = fr_pair_find_by_da(&request->request_pairs, attr_user_name, 0)) != NULL) && + (vp->vp_strvalue[0] == '@') && + !fr_pair_find_by_da(&request->request_pairs, attr_stripped_user_name, 0)) { + RWDEBUG("User-Name is anonymized, and no Stripped-User-Name exists."); + RWDEBUG("It may be difficult or impossible to identify the user."); + RWDEBUG("Please update Stripped-User-Name with information which identifies the user."); + } + + fr_state_discard(inst->auth.state_tree, request); + RETURN_MODULE_OK; +} + +RESUME_NO_RCTX(access_reject) +{ + fr_pair_t *vp; + process_ttls_t const *inst = talloc_get_type_abort_const(mctx->instance, process_ttls_t); + + PROCESS_TRACE; + + vp = fr_pair_find_by_da(&request->request_pairs, attr_module_failure_message, 0); + if (vp) { + auth_message(&inst->auth, request, false, "Login incorrect (%pV)", &vp->data); + } else { + auth_message(&inst->auth, request, false, "Login incorrect"); + } + + fr_state_discard(inst->auth.state_tree, request); + RETURN_MODULE_OK; +} + +RESUME(access_challenge) +{ + CONF_SECTION *cs; + fr_process_state_t const *state; + process_ttls_t const *inst = talloc_get_type_abort_const(mctx->instance, process_ttls_t); + + PROCESS_TRACE; + + /* + * Cache the state context. + * + * If this fails, don't respond to the request. + */ + if (fr_request_to_state(inst->auth.state_tree, request) < 0) { + request->reply->code = FR_RADIUS_CODE_DO_NOT_RESPOND; + UPDATE_STATE_CS(reply); + return unlang_module_yield_to_section(p_result, request, + cs, state->rcode, state->send, + NULL, rctx); + } + + fr_assert(request->reply->code == FR_RADIUS_CODE_ACCESS_CHALLENGE); + RETURN_MODULE_OK; +} + +RESUME(protocol_error) +{ + fr_pair_t *vp; + + PROCESS_TRACE; + + fr_assert(FR_RADIUS_PACKET_CODE_VALID(request->reply->code)); + + /* + * https://tools.ietf.org/html/rfc7930#section-4 + */ + vp = fr_pair_find_by_da(&request->reply_pairs, attr_original_packet_code, 0); + if (!vp) { + vp = fr_pair_afrom_da(request->reply_ctx, attr_original_packet_code); + if (vp) { + vp->vp_uint32 = request->packet->code; + fr_pair_append(&request->reply_pairs, vp); + } + } + + /* + * If there's no Error-Cause, then include a generic 404. + */ + vp = fr_pair_find_by_da(&request->reply_pairs, attr_error_cause, 0); + if (!vp) { + vp = fr_pair_afrom_da(request->reply_ctx, attr_error_cause); + if (vp) { + vp->vp_uint32 = FR_ERROR_CAUSE_VALUE_INVALID_REQUEST; + fr_pair_append(&request->reply_pairs, vp); + } + } + + /* + * And do the generic processing after running a "send" section. + */ + return CALL_RESUME(send_generic); +} + +static unlang_action_t mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +{ + fr_process_state_t const *state; + + (void) talloc_get_type_abort_const(mctx->instance, process_ttls_t); + + PROCESS_TRACE; + + fr_assert(FR_RADIUS_PACKET_CODE_VALID(request->packet->code)); + + request->component = "radius"; + request->module = NULL; + fr_assert(request->dict == dict_radius); + + UPDATE_STATE(packet); + + radius_packet_debug(request, request->packet, &request->request_pairs, true); + + return state->recv(p_result, mctx, request); +} + +static int mod_instantiate(void *instance, UNUSED CONF_SECTION *process_app_cs) +{ + process_ttls_t *inst = instance; + + inst->auth.state_tree = fr_state_tree_init(inst, attr_state, main_config->spawn_workers, inst->auth.max_session, + inst->auth.session_timeout, inst->auth.state_server_id, + fr_hash_string(cf_section_name2(inst->server_cs))); + + return 0; +} + +static int mod_bootstrap(void *instance, CONF_SECTION *cs) +{ + process_ttls_t *inst = instance; + + inst->server_cs = cf_section_find_in_parent(cs, "server", CF_IDENT_ANY); + if (virtual_server_section_attribute_define(inst->server_cs, "authenticate", attr_auth_type) < 0) return -1; + + return 0; +} + +/* + * rcodes not listed under a packet_type + * mean that the packet code will not be + * changed. + */ +static fr_process_state_t const process_state[] = { + [ FR_RADIUS_CODE_ACCESS_REQUEST ] = { + .packet_type = { + [RLM_MODULE_NOOP] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_OK] = FR_RADIUS_CODE_ACCESS_ACCEPT, + [RLM_MODULE_UPDATED] = FR_RADIUS_CODE_ACCESS_ACCEPT, + + [RLM_MODULE_FAIL] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_INVALID] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_REJECT] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_DISALLOW] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_NOTFOUND] = FR_RADIUS_CODE_ACCESS_REJECT + }, + .rcode = RLM_MODULE_NOOP, + .recv = recv_access_request, + .resume = resume_access_request, + .section_offset = offsetof(process_ttls_sections_t, access_request), + }, + [ FR_RADIUS_CODE_ACCESS_ACCEPT ] = { + .packet_type = { + [RLM_MODULE_FAIL] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_INVALID] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_REJECT] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_DISALLOW] = FR_RADIUS_CODE_ACCESS_REJECT + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_access_accept, + .section_offset = offsetof(process_ttls_sections_t, access_accept), + }, + [ FR_RADIUS_CODE_ACCESS_REJECT ] = { + .packet_type = { + [RLM_MODULE_FAIL] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_INVALID] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_REJECT] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_DISALLOW] = FR_RADIUS_CODE_ACCESS_REJECT + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_access_reject, + .section_offset = offsetof(process_ttls_sections_t, access_reject), + }, + [ FR_RADIUS_CODE_ACCESS_CHALLENGE ] = { + .packet_type = { + [RLM_MODULE_FAIL] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_INVALID] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_REJECT] = FR_RADIUS_CODE_ACCESS_REJECT, + [RLM_MODULE_DISALLOW] = FR_RADIUS_CODE_ACCESS_REJECT + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_access_challenge, + .section_offset = offsetof(process_ttls_sections_t, access_challenge), + }, + + + [ FR_RADIUS_CODE_PROTOCOL_ERROR ] = { /* @todo - fill out required fields */ + .packet_type = { + [RLM_MODULE_FAIL] = FR_RADIUS_CODE_DO_NOT_RESPOND, + [RLM_MODULE_INVALID] = FR_RADIUS_CODE_DO_NOT_RESPOND, + [RLM_MODULE_REJECT] = FR_RADIUS_CODE_DO_NOT_RESPOND, + [RLM_MODULE_DISALLOW] = FR_RADIUS_CODE_DO_NOT_RESPOND + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_protocol_error, + .section_offset = offsetof(process_ttls_sections_t, protocol_error), + }, + [ FR_RADIUS_CODE_DO_NOT_RESPOND ] = { + .packet_type = { + [RLM_MODULE_NOOP] = FR_RADIUS_CODE_DO_NOT_RESPOND, + [RLM_MODULE_OK] = FR_RADIUS_CODE_DO_NOT_RESPOND, + [RLM_MODULE_UPDATED] = FR_RADIUS_CODE_DO_NOT_RESPOND, + [RLM_MODULE_HANDLED] = FR_RADIUS_CODE_DO_NOT_RESPOND, + + [RLM_MODULE_NOTFOUND] = FR_RADIUS_CODE_DO_NOT_RESPOND, + [RLM_MODULE_FAIL] = FR_RADIUS_CODE_DO_NOT_RESPOND, + [RLM_MODULE_INVALID] = FR_RADIUS_CODE_DO_NOT_RESPOND, + [RLM_MODULE_REJECT] = FR_RADIUS_CODE_DO_NOT_RESPOND, + [RLM_MODULE_DISALLOW] = FR_RADIUS_CODE_DO_NOT_RESPOND + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_send_generic, + .section_offset = offsetof(process_ttls_sections_t, do_not_respond), + } +}; + +static virtual_server_compile_t const compile_list[] = { + { + .name = "recv", + .name2 = "Access-Request", + .component = MOD_AUTHORIZE, + .offset = PROCESS_CONF_OFFSET(access_request), + }, + { + .name = "send", + .name2 = "Access-Accept", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(access_accept), + }, + { + .name = "send", + .name2 = "Access-Challenge", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(access_challenge), + }, + { + .name = "send", + .name2 = "Access-Reject", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(access_reject), + }, + + { + .name = "send", + .name2 = "Protocol-Error", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(protocol_error), + }, + { + .name = "send", + .name2 = "Do-Not-Respond", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(do_not_respond), + }, + { + .name = "authenticate", + .name2 = CF_IDENT_ANY, + .component = MOD_AUTHENTICATE + }, + COMPILE_TERMINATOR +}; + +extern fr_process_module_t process_ttls; +fr_process_module_t process_ttls = { + .magic = RLM_MODULE_INIT, + .name = "process_ttls", + .config = config, + .inst_size = sizeof(process_ttls_t), + + .bootstrap = mod_bootstrap, + .instantiate = mod_instantiate, + + .process = mod_process, + .compile_list = compile_list, + .dict = &dict_radius, +};