From: Alan T. DeKok Date: Sat, 28 Jan 2023 01:45:11 +0000 (-0500) Subject: rename TACACS+ packet types X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3d5fa72898419d0fa2f6ef9a557fa479705c1ac5;p=thirdparty%2Ffreeradius-server.git rename TACACS+ packet types for increased clarity, now that we're actually using it. and do a number of other cleanups. Fix the state machine to match the new packet types. Fix the state machine to track state based on synthesized information. Add authentication logging ala RADIUS --- diff --git a/raddb/sites-available/tacacs b/raddb/sites-available/tacacs index 9ec459d0f21..dfacbf7578b 100644 --- a/raddb/sites-available/tacacs +++ b/raddb/sites-available/tacacs @@ -16,6 +16,121 @@ server tacacs { # namespace = tacacs + # + # ### TACACS+ Configuration + # + # All of the configuration for processing TACAC+ packets goes here. + # + tacacs { + # + # #### Access-Request subsection + # + # This section contains configuration which is + # specific to processing `Access-Request` packets. + # + # Similar sections can be added, but are not + # necessary for Accounting-Request (and other) + # packets. At this time, there is no configuration + # needed for other packet types. + # + Authentication { + # + # log:: Logging configuration for TACACS+ authentication + # + log { + # + # stripped_names:: Log the full + # `User-Name` attribute, as it was + # found in the request. + # + # allowed values: {no, yes} + # + stripped_names = no + + # + # auth:: Log authentication requests + # to the log file. + # + # allowed values: {no, yes} + # + auth = no + + # + # auth_goodpass:: Log "good" + # passwords with the authentication + # requests. + # + # allowed values: {no, yes} + # + auth_goodpass = no + + # + # auth_badpass:: Log "bad" + # passwords with the authentication + # requests. + # + # allowed values: {no, yes} + # + auth_badpass = no + + # + # msg_goodpass:: + # msg_badpass:: + # + # Log additional text at the end of the "Login OK" messages. + # for these to work, the "auth" and "auth_goodpass" or "auth_badpass" + # configurations above have to be set to "yes". + # + # The strings below are dynamically expanded, which means that + # you can put anything you want in them. However, note that + # this expansion can be slow, and can negatively impact server + # performance. + # +# msg_goodpass = "" +# msg_badpass = "" + + # + # msg_denied:: + # + # The message when the user exceeds the Simultaneous-Use limit. + # + msg_denied = "You are already logged in - access denied" + } + + # + # session:: Controls how ongoing + # (multi-round) sessions are handled + # + # This section is primarily useful for EAP. + # It controls the number of EAP + # authentication attempts that can occur + # concurrently. + # + session { + # + # max:: The maximum number of ongoing sessions + # +# max = 4096 + + # + # timeout:: How long to wait before expiring a + # session. + # + # The timer starts when a response + # with a state value is sent. The + # timer stops when a request + # containing the previously sent + # state value is received. + # +# timeout = 15 + } + } + + # + # There is currently no configuration for other packet types. + # + } + listen { # # type:: The type of packet to accept. @@ -128,10 +243,12 @@ server tacacs { # # ### Send # - send Authentication-Start-Reply { - if (&Authentication-Status == Pass) { - &reply.Server-Message := "Hello %{User-Name}" - } + send Authentication-Reply-Success { + &reply.Server-Message := "Hello %{User-Name}" + } + + send Authentication-Reply-Fail { + &reply.Server-Message := "Failed login!" } # @@ -152,15 +269,6 @@ server tacacs { "%{Data}" } - # - # ### Send - # - send Authentication-Continue-Reply { - if (&Authentication-Status == Pass) { - &reply.Server-Message := "Hello %{User-Name}" - } - } - # # ## Authorization # @@ -222,7 +330,7 @@ server tacacs { # # ### Send # - send Accounting-Reply { + send Accounting-Reply-Success { &reply.Accounting-Status := Success &reply.Server-Message := "Success" &reply.Data := 0x00 diff --git a/share/dictionary/tacacs/dictionary.freeradius.internal b/share/dictionary/tacacs/dictionary.freeradius.internal index a2ec143e98d..566578b4023 100644 --- a/share/dictionary/tacacs/dictionary.freeradius.internal +++ b/share/dictionary/tacacs/dictionary.freeradius.internal @@ -15,15 +15,31 @@ # FLAGS internal +# +# Due to TACACS+ insanities, the unlang packet types are a combination +# of the TACACS+ type field, and of the status field. +# ATTRIBUTE Packet-Type 65536 uint32 -VALUE Packet-Type Authentication-Start 1 -VALUE Packet-Type Authentication-Start-Reply 2 -VALUE Packet-Type Authentication-Continue 3 -VALUE Packet-Type Authentication-Continue-Reply 4 -VALUE Packet-Type Authorization-Request 5 -VALUE Packet-Type Authorization-Reply 6 -VALUE Packet-Type Accounting-Request 7 -VALUE Packet-Type Accounting-Reply 8 -VALUE Packet-Type Do-Not-Respond 256 +VALUE Packet-Type Authentication-Start 1 +VALUE Packet-Type Authentication-Reply-Pass 2 +VALUE Packet-Type Authentication-Reply-Fail 3 +VALUE Packet-Type Authentication-Reply-GetData 4 +VALUE Packet-Type Authentication-Reply-GetUser 5 +VALUE Packet-Type Authentication-Reply-GetPass 6 +VALUE Packet-Type Authentication-Reply-Restart 7 +VALUE Packet-Type Authentication-Reply-Error 8 + +VALUE Packet-Type Authentication-Continue 9 +VALUE Packet-Type Authentication-Continue-Abort 10 + +VALUE Packet-Type Authorization-Request 11 +VALUE Packet-Type Authorization-Reply-Pass-Add 12 +VALUE Packet-Type Authorization-Reply-Pass-Replace 13 +VALUE Packet-Type Authorization-Reply-Fail 14 +VALUE Packet-Type Authorization-Reply-Error 15 + +VALUE Packet-Type Accounting-Request 16 +VALUE Packet-Type Accounting-Reply-Success 17 +VALUE Packet-Type Accounting-Reply-Error 18 ATTRIBUTE State 65537 octets diff --git a/src/listen/tacacs/proto_tacacs.c b/src/listen/tacacs/proto_tacacs.c index 7f4518954b1..9da87aded98 100644 --- a/src/listen/tacacs/proto_tacacs.c +++ b/src/listen/tacacs/proto_tacacs.c @@ -176,6 +176,7 @@ static int mod_decode(void const *instance, request_t *request, uint8_t *const d fr_io_track_t const *track = talloc_get_type_abort_const(request->async->packet_ctx, fr_io_track_t); fr_io_address_t const *address = track->address; RADCLIENT const *client; + int code; fr_tacacs_packet_t const *pkt = (fr_tacacs_packet_t const *)data; RHEXDUMP3(data, data_len, "proto_tacacs decode packet"); @@ -198,31 +199,22 @@ static int mod_decode(void const *instance, request_t *request, uint8_t *const d } /* - * Decode the header, etc. + * RFC 8907 Section 3.6 says: * - * The "type = ..." loader ensures that we only get request packets + * If an error occurs but the type of the incoming packet cannot be determined, a packet with the + * identical cleartext header but with a sequence number incremented by one and the length set to + * zero MUST be returned to indicate an error. + * + * This is substantially retarded. It should instead just close the connection. */ - switch (pkt->hdr.type) { - case FR_TAC_PLUS_AUTHEN: - if (packet_is_authen_start_request(pkt)) { - request->packet->code = FR_PACKET_TYPE_VALUE_AUTHENTICATION_START; - } else { - request->packet->code = FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE; - } - break; - - case FR_TAC_PLUS_AUTHOR: - request->packet->code = FR_PACKET_TYPE_VALUE_AUTHORIZATION_REQUEST; - break; - case FR_TAC_PLUS_ACCT: - request->packet->code = FR_PACKET_TYPE_VALUE_ACCOUNTING_REQUEST; - break; - - default: + code = fr_tacacs_packet_to_code(pkt); + if (code < 0) { + RPEDEBUG("Invalid packet"); return -1; } + request->packet->code = code; request->packet->id = data[2]; // seq_no request->reply->id = data[2]; // seq_no @@ -335,11 +327,29 @@ static ssize_t mod_encode(void const *instance, request_t *request, uint8_t *buf ssize_t data_len; RADCLIENT const *client; + /* + * @todo - RFC 8907 Section 4.4. says: + * + * When the session is complete, the TCP connection should be handled as follows, according to + * whether Single Connection Mode was negotiated: + * + * * If Single Connection Mode was not negotiated, then the connection should be closed. + * + * * If Single Connection Mode was enabled, then the connection SHOULD be left open (see + * "Single Connection Mode" (Section 4.3)) but may still be closed after a timeout period to + * preserve deployment resources. + * + * * If Single Connection Mode was enabled, but an ERROR occurred due to connection issues + * (such as an incorrect secret (see Section 4.5)), then any further new sessions MUST NOT be + * accepted on the connection. If there are any sessions that have already been established, + * then they MAY be completed. Once all active sessions are completed, then the connection + * MUST be closed. + */ + /* * Process layer NAK, or "Do not respond". */ if ((buffer_len == 1) || - (request->reply->code == FR_PACKET_TYPE_VALUE_DO_NOT_RESPOND) || !FR_TACACS_PACKET_CODE_VALID(request->reply->code)) { track->do_not_respond = true; return 1; @@ -388,7 +398,7 @@ static ssize_t mod_encode(void const *instance, request_t *request, uint8_t *buf data_len = fr_tacacs_encode(&FR_DBUFF_TMP(buffer, buffer_len), request->packet->data, client->secret, talloc_array_length(client->secret) - 1, - &request->reply_pairs); + request->reply->code, &request->reply_pairs); if (data_len < 0) { RPEDEBUG("Failed encoding TACACS+ reply"); return -1; diff --git a/src/modules/rlm_tacacs/rlm_tacacs_tcp.c b/src/modules/rlm_tacacs/rlm_tacacs_tcp.c index af36d0efc54..d4ba19cb908 100644 --- a/src/modules/rlm_tacacs/rlm_tacacs_tcp.c +++ b/src/modules/rlm_tacacs/rlm_tacacs_tcp.c @@ -622,7 +622,7 @@ static int encode(udp_handle_t *h, request_t *request, udp_request_t *u) * Encode the packet. */ packet_len = fr_tacacs_encode(&FR_DBUFF_TMP(u->packet, (size_t) inst->max_packet_size), NULL, - inst->secret, inst->secretlen, &request->request_pairs); + inst->secret, inst->secretlen, request->reply->code, &request->request_pairs); if (packet_len < 0) { RPERROR("Failed encoding packet"); return -1; diff --git a/src/process/tacacs/base.c b/src/process/tacacs/base.c index ae34fca97e0..552e939060d 100644 --- a/src/process/tacacs/base.c +++ b/src/process/tacacs/base.c @@ -45,6 +45,9 @@ fr_dict_autoload_t process_tacacs_dict[] = { }; 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_packet_type; static fr_dict_attr_t const *attr_tacacs_action; @@ -63,11 +66,17 @@ static fr_dict_attr_t const *attr_tacacs_privilege_level; static fr_dict_attr_t const *attr_tacacs_remote_address; static fr_dict_attr_t const *attr_tacacs_server_message; static fr_dict_attr_t const *attr_tacacs_session_id; +static fr_dict_attr_t const *attr_tacacs_sequence_number; static fr_dict_attr_t const *attr_tacacs_state; +static fr_dict_attr_t const *attr_user_name; + extern fr_dict_attr_autoload_t process_tacacs_dict_attr[]; fr_dict_attr_autoload_t process_tacacs_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_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_tacacs }, { .out = &attr_tacacs_action, .name = "Action", .type = FR_TYPE_UINT8, .dict = &dict_tacacs }, @@ -86,480 +95,632 @@ fr_dict_attr_autoload_t process_tacacs_dict_attr[] = { { .out = &attr_tacacs_privilege_level, .name = "Privilege-Level", .type = FR_TYPE_UINT8, .dict = &dict_tacacs }, { .out = &attr_tacacs_remote_address, .name = "Remote-Address", .type = FR_TYPE_STRING, .dict = &dict_tacacs }, { .out = &attr_tacacs_session_id, .name = "Packet.Session-Id", .type = FR_TYPE_UINT32, .dict = &dict_tacacs }, + { .out = &attr_tacacs_sequence_number, .name = "Packet.Sequence-Number", .type = FR_TYPE_UINT8, .dict = &dict_tacacs }, { .out = &attr_tacacs_server_message, .name = "Server-Message", .type = FR_TYPE_STRING, .dict = &dict_tacacs }, { .out = &attr_tacacs_state, .name = "State", .type = FR_TYPE_OCTETS, .dict = &dict_tacacs }, + { .out = &attr_user_name, .name = "User-Name", .type = FR_TYPE_STRING, .dict = &dict_tacacs }, + { 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_tacacs_dict_enum[]; +fr_dict_enum_autoload_t process_tacacs_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 } +}; + + typedef struct { uint64_t nothing; // so that the next field isn't at offset 0 CONF_SECTION *auth_start; - CONF_SECTION *auth_start_reply; + CONF_SECTION *auth_reply_pass; + CONF_SECTION *auth_reply_fail; + CONF_SECTION *auth_reply_getdata; + CONF_SECTION *auth_reply_getuser; + CONF_SECTION *auth_reply_getpass; + CONF_SECTION *auth_reply_restart; + CONF_SECTION *auth_reply_error; CONF_SECTION *auth_cont; - CONF_SECTION *auth_cont_reply; + CONF_SECTION *auth_cont_abort; CONF_SECTION *autz_request; - CONF_SECTION *autz_reply; + CONF_SECTION *autz_reply_pass_add; + CONF_SECTION *autz_reply_pass_replace; + CONF_SECTION *autz_reply_fail; + CONF_SECTION *autz_reply_error; CONF_SECTION *acct_request; - CONF_SECTION *acct_reply; + CONF_SECTION *acct_reply_success; + CONF_SECTION *acct_reply_error; CONF_SECTION *do_not_respond; } process_tacacs_sections_t; typedef struct { - fr_time_delta_t session_timeout; //!< Maximum time between the last response and next request. - uint32_t max_session; //!< Maximum ongoing session allowed. + bool log_stripped_names; + bool log_auth; //!< Log authentication attempts. + bool log_auth_badpass; //!< Log failed authentications. + bool log_auth_goodpass; //!< Log successful authentications. + char const *auth_badpass_msg; //!< Additional text to append to failed auth messages. + char const *auth_goodpass_msg; //!< Additional text to append to successful auth messages. - uint8_t state_server_id; //!< Sets a specific byte in the state to allow the - //!< authenticating server to be identified in packet - //!< captures. + char const *denied_msg; //!< Additional text to append if the user is already logged + //!< in (simultaneous use check failed). - fr_state_tree_t *state_tree; //!< State tree to link multiple requests/responses. + fr_time_delta_t session_timeout; //!< Maximum time between the last response and next request. + uint32_t max_session; //!< Maximum ongoing session allowed. - process_tacacs_sections_t sections; + 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_tacacs_t, session_timeout), .dflt = "15" }, - { FR_CONF_OFFSET("max", FR_TYPE_UINT32, process_tacacs_t, max_session), .dflt = "4096" }, - { FR_CONF_OFFSET("state_server_id", FR_TYPE_UINT8, process_tacacs_t, state_server_id) }, + { FR_CONF_OFFSET("timeout", FR_TYPE_TIME_DELTA, process_tacacs_t, auth.session_timeout), .dflt = "15" }, + { FR_CONF_OFFSET("max", FR_TYPE_UINT32, process_tacacs_auth_t, max_session), .dflt = "4096" }, + { FR_CONF_OFFSET("state_server_id", FR_TYPE_UINT8, process_tacacs_auth_t, state_server_id) }, CONF_PARSER_TERMINATOR }; -static const CONF_PARSER config[] = { +static const CONF_PARSER log_config[] = { + { FR_CONF_OFFSET("stripped_names", FR_TYPE_BOOL, process_tacacs_auth_t, log_stripped_names), .dflt = "no" }, + { FR_CONF_OFFSET("auth", FR_TYPE_BOOL, process_tacacs_auth_t, log_auth), .dflt = "no" }, + { FR_CONF_OFFSET("auth_badpass", FR_TYPE_BOOL, process_tacacs_auth_t, log_auth_badpass), .dflt = "no" }, + { FR_CONF_OFFSET("auth_goodpass", FR_TYPE_BOOL,process_tacacs_auth_t, log_auth_goodpass), .dflt = "no" }, + { FR_CONF_OFFSET("msg_badpass", FR_TYPE_STRING, process_tacacs_auth_t, auth_badpass_msg) }, + { FR_CONF_OFFSET("msg_goodpass", FR_TYPE_STRING, process_tacacs_auth_t, auth_goodpass_msg) }, + { FR_CONF_OFFSET("msg_denied", FR_TYPE_STRING, process_tacacs_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 void message_failed(request_t *request, PROCESS_INST *inst, fr_process_state_t const *ctx) +static const CONF_PARSER config[] = { + { FR_CONF_POINTER("Authentication", FR_TYPE_SUBSECTION, NULL), .subcs = (void const *) auth_config, + .offset = offsetof(process_tacacs_t, auth), }, + + CONF_PARSER_TERMINATOR +}; + + +#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) +{ + char const *tls = ""; + RADCLIENT *client = client_from_request(request); + + if (request->packet->socket.inet.dst_port == 0) tls = " via proxy to virtual server"; + + snprintf(buf, buflen, "from client %.128s%s", + client ? client->shortname : "", 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_tacacs_auth_t const *inst, + request_t *request, bool goodpass, char const *fmt, ...) { - fr_pair_t *vp; - char const *msg; + va_list ap; - msg = ctx->fail_message; + bool logit; + char const *extra_msg = NULL; - RPEDEBUG("%s", msg); +// 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; /* - * Set the server reply message. Note that we do not tell the user *why* they failed authentication. + * Get the correct username based on the configured value */ - if (!fr_pair_find_by_da(&request->reply_pairs, NULL, attr_tacacs_server_message)) { - MEM(pair_update_reply(&vp, attr_tacacs_server_message) >= 0); - fr_pair_value_strdup(vp, msg, false); + if (!inst->log_stripped_names) { + username = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_name); + } else { + username = fr_pair_find_by_da(&request->request_pairs, NULL, attr_stripped_user_name); + if (!username) username = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_name); } +#if 0 /* - * Set the status. + * Clean up the password */ - MEM(pair_update_reply(&vp, *ctx->attr_status) >= 0); - vp->vp_uint8 = ctx->status_fail; + if (inst->log_auth_badpass || inst->log_auth_goodpass) { + password = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_password); + if (!password) { + fr_pair_t *auth_type; + + auth_type = fr_pair_find_by_da(&request->control_pairs, NULL, attr_auth_type); + 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, NULL, attr_chap_password)) { + password_str = ""; + } + } +#endif + + 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'; + } - fr_state_discard(inst->state_tree, request); - fr_pair_delete_by_da(&request->request_pairs, attr_tacacs_state); + /* + * 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(tacacs) +static int state_create(TALLOC_CTX *ctx, fr_pair_list_t *out, request_t *request, bool reply) { + uint8_t buffer[12]; + uint32_t hash; + fr_pair_t *vp; + + vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_tacacs_session_id); + if (!vp) return -1; + + fr_nbo_from_uint32(buffer, vp->vp_uint32); + + vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_tacacs_sequence_number); + if (!vp) return -1; + + /* + * Requests have odd sequence numbers, and replies have even sequence numbers. + * So if we want to synthesize a state in a reply which gets matched with the next + * request, we have to add 2 to it. + */ + hash = vp->vp_uint8 + ((int) reply << 1); + + fr_nbo_from_uint32(buffer + 4, hash); + + /* + * Hash in the listener. For now, we don't allow internally proxied requests. + */ + fr_assert(request->async != NULL); + fr_assert(request->async->listen != NULL); + hash = fr_hash(&request->async->listen, sizeof(request->async->listen)); + + fr_nbo_from_uint32(buffer + 8, hash); + + vp = fr_pair_afrom_da(ctx, attr_tacacs_state); + if (!vp) return -1; + + fr_pair_append(out, vp); + + return 0; +} + +RECV(auth_start) +{ +// process_tacacs_t *inst = talloc_get_type_abort(mctx->inst->data, process_tacacs_t); + + /* + * There is no state to restore, so we just run the section as normal. + */ + + return CALL_RECV(generic); +} + +RESUME(auth_type); + +RESUME(auth_start) +{ + rlm_rcode_t rcode = *p_result; + fr_pair_t *vp; CONF_SECTION *cs; + fr_dict_enum_value_t const *dv; fr_process_state_t const *state; - PROCESS_INST *inst = mctx->inst->data; - fr_tacacs_packet_hdr_t const *pkt = (fr_tacacs_packet_hdr_t const *) request->packet->data; + process_tacacs_t const *inst = talloc_get_type_abort_const(mctx->inst->data, process_tacacs_t); PROCESS_TRACE; - UPDATE_STATE_CS(packet); - request->reply->code = state->default_reply; /* TCP, so we always reply */ + fr_assert(rcode < RLM_MODULE_NUMCODES); + + /* + * See if the return code from "recv" which says we reject, or continue. + */ + UPDATE_STATE(packet); + + request->reply->code = state->packet_type[rcode]; + if (!request->reply->code) request->reply->code = state->default_reply; + + /* + * If we're forcing a reply type (e.g. "getpass"), then skip "authenticate foo", + * and just send the reply. + */ + if (!request->reply->code) { + vp = fr_pair_find_by_da(&request->reply_pairs, NULL, attr_packet_type); + if (vp && FR_TACACS_PACKET_CODE_VALID(vp->vp_uint32)) { + request->reply->code = vp->vp_uint32; + } + } + + /* + * Something set reject, we're done. + */ + if (request->reply->code == FR_TACACS_CODE_AUTH_REPLY_FAIL) { + RDEBUG("The 'recv Authentication-Start' section returned %s - rejecting the request", + fr_table_str_by_value(rcode_table, rcode, "???")); + + send_reply: + UPDATE_STATE(reply); + + fr_assert(state->send != NULL); + return CALL_SEND_STATE(state); + } /* - * Track state across multiple packets. + * Run authenticate foo { ... } * - * @todo - for fake packets, too? + * If we can't find Auth-Type, OR if we can't find Auth-Type = foo, then it's a reject. + * + * We prefer the local Auth-Type to the Authentication-Type in the packet. But if there's no + * Auth-Type set by the admin, then we use what's in the packet. */ - if (!request->parent && request->async->listen) { - fr_pair_t *vp; - uint8_t buffer[sizeof(request->async->listen) + sizeof(pkt->session_id)]; + vp = fr_pair_find_by_da(&request->control_pairs, NULL, attr_auth_type); + if (!vp) vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_tacacs_authentication_type); + if (!vp) { + RDEBUG("No 'Auth-Type' attribute found, cannot authenticate the user - rejecting the request", + fr_table_str_by_value(rcode_table, rcode, "???")); + + reject: + request->reply->code = FR_TACACS_CODE_AUTH_REPLY_FAIL; + goto send_reply; + } - memcpy(buffer, &request->async->listen, sizeof(request->async->listen)); - memcpy(buffer + sizeof(request->async->listen), &pkt->session_id, sizeof(pkt->session_id)); + dv = fr_dict_enum_by_value(vp->da, &vp->data); + if (!dv) { + RDEBUG("Invalid value for '%s' attribute, cannot authenticate the user - rejecting the request", + vp->da->name, fr_table_str_by_value(rcode_table, rcode, "???")); - vp = fr_pair_afrom_da(request->request_ctx, attr_tacacs_state); - if (vp) { - fr_pair_value_memdup(vp, buffer, sizeof(buffer), false); - fr_pair_append(&request->request_pairs, vp); + goto reject; + } - fr_state_to_request(inst->state_tree, request); + /* + * The magic Auth-Type Accept value which means skip the authenticate section. + * + * And Reject means always reject. Tho the admin should just return "reject" from the section. + */ + if (vp->da == attr_auth_type) { + if (fr_value_box_cmp(enum_auth_type_accept, dv->value) == 0) { + request->reply->code = FR_TACACS_CODE_AUTH_REPLY_PASS; + goto send_reply; + + } else if (fr_value_box_cmp(enum_auth_type_reject, dv->value) == 0) { + request->reply->code = FR_TACACS_CODE_AUTH_REPLY_FAIL; + goto send_reply; } } - return CALL_RECV(generic); + cs = cf_section_find(inst->server_cs, "authenticate", dv->name); + if (!cs) { + RDEBUG2("No 'authenticate %s { ... }' section found - rejecting the request", dv->name); + goto reject; + } + + /* + * 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, mctx->rctx); } -RESUME(tacacs_type) +RESUME(auth_type) { + static const fr_process_rcode_t auth_type_rcode = { + [RLM_MODULE_OK] = FR_TACACS_CODE_AUTH_REPLY_PASS, + [RLM_MODULE_FAIL] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_INVALID] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_NOOP] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_NOTFOUND] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_REJECT] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_UPDATED] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_DISALLOW] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + }; + rlm_rcode_t rcode = *p_result; fr_process_state_t const *state; - PROCESS_INST *inst = mctx->inst->data; PROCESS_TRACE; fr_assert(rcode < RLM_MODULE_NUMCODES); - UPDATE_STATE(packet); + /* + * Most cases except handled... + */ + if (auth_type_rcode[rcode]) request->reply->code = auth_type_rcode[rcode]; - switch (rcode) { - case RLM_MODULE_FAIL: - case RLM_MODULE_INVALID: - case RLM_MODULE_NOOP: - case RLM_MODULE_NOTFOUND: - case RLM_MODULE_REJECT: - case RLM_MODULE_UPDATED: - case RLM_MODULE_DISALLOW: - default: - message_failed(request, inst, state); - break; + switch (request->reply->code) { + case 0: + RDEBUG("No reply code was set. Forcing to Authentication-Reply-Fail"); + request->reply->code = FR_TACACS_CODE_AUTH_REPLY_FAIL; + FALL_THROUGH; - case RLM_MODULE_OK: + /* + * Print complaints before running "send Access-Reject" + */ + case FR_TACACS_CODE_AUTH_REPLY_FAIL: + RDEBUG2("Failed to authenticate the user"); break; - case RLM_MODULE_HANDLED: + default: break; - } + } UPDATE_STATE(reply); fr_assert(state->send != NULL); return state->send(p_result, mctx, request); } -RESUME(recv_tacacs) +RESUME_NO_RCTX(auth_reply_pass) { - rlm_rcode_t rcode = *p_result; - CONF_SECTION *cs; - fr_process_state_t const *state; - PROCESS_INST *inst = mctx->inst->data; + fr_pair_t *vp; + process_tacacs_t const *inst = talloc_get_type_abort_const(mctx->inst->data, process_tacacs_t); PROCESS_TRACE; - fr_assert(rcode < RLM_MODULE_NUMCODES); + vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_module_success_message); + if (vp) { + auth_message(&inst->auth, request, true, "Login OK (%pV)", &vp->data); + } else { + auth_message(&inst->auth, request, true, "Login OK"); + } - UPDATE_STATE(packet); + // @todo - worry about user identity existing? - switch (rcode) { - case RLM_MODULE_NOOP: - case RLM_MODULE_NOTFOUND: - case RLM_MODULE_OK: - case RLM_MODULE_UPDATED: - request->reply->code = state->packet_type[rcode]; - - /* - * Run "authenticate foo" or "accounting foo" - */ - if (state->attr_process) { - fr_pair_t *vp; - fr_dict_enum_value_t const *dv; - CONF_SECTION *subcs; - - vp = fr_pair_find_by_da(&request->request_pairs, NULL, *state->attr_process); - if (!vp) vp = fr_pair_find_by_da(&request->control_pairs, NULL, *state->attr_process); - if (!vp) { - RDEBUG2("No attribute found for &request.%s - proceeding to 'send'", (*state->attr_process)->name); - break; - } + fr_state_discard(inst->auth.state_tree, request); + RETURN_MODULE_OK; +} - dv = fr_dict_enum_by_value(vp->da, &vp->data); - if (!dv) { - RDEBUG2("No name found for &request.%s value %pV - proceeding to 'send'", (*state->attr_process)->name, &vp->data); - break; - } +RESUME_NO_RCTX(auth_reply_fail) +{ + fr_pair_t *vp; + process_tacacs_t const *inst = talloc_get_type_abort_const(mctx->inst->data, process_tacacs_t); - subcs = cf_section_find(unlang_call_current(request), state->attr_process_section, dv->name); - if (!subcs) { - RDEBUG2("No '%s %s { ... }' section found - skipping", state->attr_process_section, dv->name); - break; - } + PROCESS_TRACE; - return unlang_module_yield_to_section(p_result, request, - subcs, RLM_MODULE_NOOP, resume_tacacs_type, - NULL, mctx->rctx); - } - break; + vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_module_failure_message); + if (vp) { + auth_message(&inst->auth, request, false, "Login incorrect (%pV)", &vp->data); + } else { + auth_message(&inst->auth, request, false, "Login incorrect"); + } - case RLM_MODULE_HANDLED: - fr_assert(request->reply->code != 0); - break; + fr_state_discard(inst->auth.state_tree, request); + RETURN_MODULE_OK; +} - case RLM_MODULE_FAIL: - case RLM_MODULE_INVALID: - case RLM_MODULE_REJECT: - case RLM_MODULE_DISALLOW: - default: - request->reply->code = state->packet_type[rcode]; - message_failed(request, inst, state); - break; +RESUME(auth_reply_get) +{ + process_tacacs_t const *inst = talloc_get_type_abort_const(mctx->inst->data, process_tacacs_t); + + PROCESS_TRACE; + + /* + * Cache the session state context. + */ + if ((state_create(request->reply_ctx, &request->reply_pairs, request, true) < 0) || + (fr_request_to_state(inst->auth.state_tree, request) < 0)) { + return CALL_SEND_TYPE(FR_TACACS_CODE_AUTH_REPLY_ERROR); } - UPDATE_STATE_CS(reply); + RETURN_MODULE_OK; +} - fr_assert(state->send != NULL); +RECV(auth_cont) +{ + process_tacacs_t const *inst = talloc_get_type_abort_const(mctx->inst->data, process_tacacs_t); + if ((state_create(request->request_ctx, &request->request_pairs, request, false) < 0) || + (fr_state_to_request(inst->auth.state_tree, request) < 0)) { + return CALL_SEND_TYPE(FR_TACACS_CODE_AUTH_REPLY_ERROR); + } + + return CALL_RECV(generic); +} + +/* + * The client aborted the session. The reply should be RESTART or FAIL. + */ +RECV(auth_cont_abort) +{ + process_tacacs_t const *inst = talloc_get_type_abort_const(mctx->inst->data, process_tacacs_t); + + if ((state_create(request->request_ctx, &request->request_pairs, request, false) < 0) || + (fr_state_to_request(inst->auth.state_tree, request) < 0)) { + return CALL_SEND_TYPE(FR_TACACS_CODE_AUTH_REPLY_ERROR); + } + + return CALL_RECV(generic); +} + +RESUME(auth_cont_abort) +{ + fr_process_state_t const *state; + + if (!request->reply->code) request->reply->code = FR_TACACS_CODE_AUTH_REPLY_RESTART; + + UPDATE_STATE(reply); + + fr_assert(state->send != NULL); return CALL_SEND_STATE(state); } -RESUME(send_tacacs) + +RESUME(acct_type) { - PROCESS_INST const *inst = mctx->inst->data; + static const fr_process_rcode_t acct_type_rcode = { + [RLM_MODULE_FAIL] = FR_TACACS_CODE_ACCT_REPLY_ERROR, + [RLM_MODULE_INVALID] = FR_TACACS_CODE_ACCT_REPLY_ERROR, + [RLM_MODULE_NOTFOUND] = FR_TACACS_CODE_ACCT_REPLY_ERROR, + [RLM_MODULE_REJECT] = FR_TACACS_CODE_ACCT_REPLY_ERROR, + [RLM_MODULE_DISALLOW] = FR_TACACS_CODE_ACCT_REPLY_ERROR, + }; + + rlm_rcode_t rcode = *p_result; + fr_process_state_t const *state; PROCESS_TRACE; - /* - * Save the state - */ - if (!request->parent && - (fr_pair_find_by_da(&request->request_pairs, NULL, attr_tacacs_state) != NULL)) { - fr_tacacs_packet_hdr_t const *pkt = (fr_tacacs_packet_hdr_t const *) request->packet->data; - - /* - * Keep the state around for - * authorization and accounting packets. - */ - if (pkt->seq_no >= 254) { - fr_state_discard(inst->state_tree, request); - - /* - * We can't save it, so... oh well. - */ - } else if (fr_request_to_state(inst->state_tree, request) < 0) { - RWDEBUG("Failed saving state"); - request->reply->code = FR_TACACS_DO_NOT_RESPOND; - } - } + fr_assert(rcode < RLM_MODULE_NUMCODES); + fr_assert(FR_TACACS_PACKET_CODE_VALID(request->reply->code)); - return CALL_RESUME(send_generic); -} + if (acct_type_rcode[rcode]) { + fr_assert(acct_type_rcode[rcode] == FR_TACACS_CODE_ACCT_REPLY_ERROR); -static fr_process_state_t const process_state[] = { - [FR_TACACS_AUTH_START] = { - .packet_type = { - [RLM_MODULE_OK] = FR_TACACS_AUTH_START_REPLY, - [RLM_MODULE_NOOP] = FR_TACACS_AUTH_START_REPLY, - [RLM_MODULE_UPDATED] = FR_TACACS_AUTH_START_REPLY, - - [RLM_MODULE_REJECT] = FR_TACACS_AUTH_START_REPLY, - [RLM_MODULE_FAIL] = FR_TACACS_AUTH_START_REPLY, - [RLM_MODULE_INVALID] = FR_TACACS_AUTH_START_REPLY, - [RLM_MODULE_DISALLOW] = FR_TACACS_AUTH_START_REPLY, - [RLM_MODULE_NOTFOUND] = FR_TACACS_AUTH_START_REPLY, - }, - .rcode = RLM_MODULE_NOOP, - .default_reply = FR_TACACS_AUTH_START_REPLY, - .recv = recv_tacacs, - .resume = resume_recv_tacacs, - .section_offset = offsetof(process_tacacs_sections_t, auth_start), + request->reply->code = acct_type_rcode[rcode]; + UPDATE_STATE(reply); - .attr_process = &attr_tacacs_authentication_type, - .attr_process_section = "authenticate", - .fail_message = "Failed to authenticate the user", - .attr_status = &attr_tacacs_authentication_status, - .status_fail = FR_TAC_PLUS_AUTHEN_STATUS_FAIL, - }, - [FR_TACACS_AUTH_START_REPLY] = { - .packet_type = { - [RLM_MODULE_OK] = FR_TACACS_AUTH_START_REPLY, - [RLM_MODULE_NOOP] = FR_TACACS_AUTH_START_REPLY, - [RLM_MODULE_UPDATED] = FR_TACACS_AUTH_START_REPLY, - - [RLM_MODULE_REJECT] = FR_TACACS_AUTH_START_REPLY, - [RLM_MODULE_FAIL] = FR_TACACS_AUTH_START_REPLY, - [RLM_MODULE_INVALID] = FR_TACACS_AUTH_START_REPLY, - [RLM_MODULE_DISALLOW] = FR_TACACS_AUTH_START_REPLY, - [RLM_MODULE_NOTFOUND] = FR_TACACS_AUTH_START_REPLY, - }, - .rcode = RLM_MODULE_NOOP, - .send = send_generic, - .resume = resume_send_tacacs, - .section_offset = offsetof(process_tacacs_sections_t, auth_start_reply), + RDEBUG("The 'accounting' section returned %s - not sending a response", + fr_table_str_by_value(rcode_table, rcode, "???")); - .fail_message = "Failed to authenticate the user", - .attr_status = &attr_tacacs_authentication_status, - .status_fail = FR_TAC_PLUS_AUTHEN_STATUS_FAIL, - }, + fr_assert(state->send != NULL); + return state->send(p_result, mctx, request); + } - [FR_TACACS_AUTH_CONTINUE] = { - .packet_type = { - [RLM_MODULE_OK] = FR_TACACS_AUTH_CONTINUE_REPLY, - [RLM_MODULE_NOOP] = FR_TACACS_AUTH_CONTINUE_REPLY, - [RLM_MODULE_UPDATED] = FR_TACACS_AUTH_CONTINUE_REPLY, - - [RLM_MODULE_REJECT] = FR_TACACS_AUTH_CONTINUE_REPLY, - [RLM_MODULE_FAIL] = FR_TACACS_AUTH_CONTINUE_REPLY, - [RLM_MODULE_INVALID] = FR_TACACS_AUTH_CONTINUE_REPLY, - [RLM_MODULE_DISALLOW] = FR_TACACS_AUTH_CONTINUE_REPLY, - [RLM_MODULE_NOTFOUND] = FR_TACACS_AUTH_CONTINUE_REPLY, - }, - .rcode = RLM_MODULE_NOOP, - .default_reply = FR_TACACS_AUTH_CONTINUE_REPLY, - .recv = recv_tacacs, - .resume = resume_recv_tacacs, - .section_offset = offsetof(process_tacacs_sections_t, auth_cont), + request->reply->code = FR_TACACS_CODE_ACCT_REPLY_SUCCESS; + UPDATE_STATE(reply); - .attr_process = &attr_tacacs_authentication_type, - .attr_process_section = "authenticate", - .fail_message = "Failed to authenticate the user", - .attr_status = &attr_tacacs_authentication_status, - .status_fail = FR_TAC_PLUS_AUTHEN_STATUS_FAIL, - }, - [FR_TACACS_AUTH_CONTINUE_REPLY] = { - .packet_type = { - [RLM_MODULE_OK] = FR_TACACS_AUTH_CONTINUE_REPLY, - [RLM_MODULE_NOOP] = FR_TACACS_AUTH_CONTINUE_REPLY, - [RLM_MODULE_UPDATED] = FR_TACACS_AUTH_CONTINUE_REPLY, - - [RLM_MODULE_REJECT] = FR_TACACS_AUTH_CONTINUE_REPLY, - [RLM_MODULE_FAIL] = FR_TACACS_AUTH_CONTINUE_REPLY, - [RLM_MODULE_INVALID] = FR_TACACS_AUTH_CONTINUE_REPLY, - [RLM_MODULE_DISALLOW] = FR_TACACS_AUTH_CONTINUE_REPLY, - [RLM_MODULE_NOTFOUND] = FR_TACACS_AUTH_CONTINUE_REPLY, - }, - .rcode = RLM_MODULE_NOOP, - .send = send_generic, - .resume = resume_send_tacacs, - .section_offset = offsetof(process_tacacs_sections_t, auth_cont_reply), + fr_assert(state->send != NULL); + return state->send(p_result, mctx, request); +} - .fail_message = "Failed to authenticate the user", - .attr_status = &attr_tacacs_authentication_status, - .status_fail = FR_TAC_PLUS_AUTHEN_STATUS_FAIL, - }, +RESUME(accounting_request) +{ + rlm_rcode_t rcode = *p_result; + fr_pair_t *vp; + CONF_SECTION *cs; + fr_dict_enum_value_t const *dv; + fr_process_state_t const *state; + process_tacacs_t const *inst = talloc_get_type_abort_const(mctx->inst->data, process_tacacs_t); - [FR_TACACS_AUTZ_REQUEST] = { - .packet_type = { - [RLM_MODULE_OK] = FR_TACACS_AUTZ_REPLY, - [RLM_MODULE_NOOP] = FR_TACACS_AUTZ_REPLY, - [RLM_MODULE_UPDATED] = FR_TACACS_AUTZ_REPLY, - - [RLM_MODULE_REJECT] = FR_TACACS_AUTZ_REPLY, - [RLM_MODULE_FAIL] = FR_TACACS_AUTZ_REPLY, - [RLM_MODULE_INVALID] = FR_TACACS_AUTZ_REPLY, - [RLM_MODULE_DISALLOW] = FR_TACACS_AUTZ_REPLY, - [RLM_MODULE_NOTFOUND] = FR_TACACS_AUTZ_REPLY, - }, - .rcode = RLM_MODULE_NOOP, - .default_reply = FR_TACACS_AUTZ_REPLY, - .recv = recv_tacacs, - .resume = resume_recv_tacacs, - .section_offset = offsetof(process_tacacs_sections_t, autz_request), + PROCESS_TRACE; - .fail_message = "Failed to authorize the user", - .attr_status = &attr_tacacs_authorization_status, - .status_fail = FR_TAC_PLUS_AUTHOR_STATUS_FAIL, - }, - [FR_TACACS_AUTZ_REPLY] = { - .packet_type = { - [RLM_MODULE_OK] = FR_TACACS_AUTZ_REPLY, - [RLM_MODULE_NOOP] = FR_TACACS_AUTZ_REPLY, - [RLM_MODULE_UPDATED] = FR_TACACS_AUTZ_REPLY, - - [RLM_MODULE_REJECT] = FR_TACACS_AUTZ_REPLY, - [RLM_MODULE_FAIL] = FR_TACACS_AUTZ_REPLY, - [RLM_MODULE_INVALID] = FR_TACACS_AUTZ_REPLY, - [RLM_MODULE_DISALLOW] = FR_TACACS_AUTZ_REPLY, - [RLM_MODULE_NOTFOUND] = FR_TACACS_AUTZ_REPLY, - }, - .rcode = RLM_MODULE_NOOP, - .send = send_generic, - .resume = resume_send_tacacs, - .section_offset = offsetof(process_tacacs_sections_t, autz_reply), + fr_assert(rcode < RLM_MODULE_NUMCODES); - .fail_message = "Failed to authorize the user", - .attr_status = &attr_tacacs_authorization_status, - .status_fail = FR_TAC_PLUS_AUTHOR_STATUS_FAIL, - }, + UPDATE_STATE(packet); + fr_assert(state->packet_type[rcode] != 0); - [FR_TACACS_ACCT_REQUEST] = { - .packet_type = { - [RLM_MODULE_OK] = FR_TACACS_ACCT_REPLY, - [RLM_MODULE_NOOP] = FR_TACACS_ACCT_REPLY, - [RLM_MODULE_UPDATED] = FR_TACACS_ACCT_REPLY, - - [RLM_MODULE_REJECT] = FR_TACACS_ACCT_REPLY, - [RLM_MODULE_FAIL] = FR_TACACS_ACCT_REPLY, - [RLM_MODULE_INVALID] = FR_TACACS_ACCT_REPLY, - [RLM_MODULE_DISALLOW] = FR_TACACS_ACCT_REPLY, - [RLM_MODULE_NOTFOUND] = FR_TACACS_ACCT_REPLY, - }, - .rcode = RLM_MODULE_NOOP, - .default_reply = FR_TACACS_ACCT_REPLY, - .recv = recv_tacacs, - .resume = resume_recv_tacacs, - .section_offset = offsetof(process_tacacs_sections_t, acct_request), + request->reply->code = state->packet_type[rcode]; + UPDATE_STATE_CS(reply); - .attr_process = &attr_tacacs_accounting_flags, - .attr_process_section = "accounting", - .fail_message = "Failed to process the accounting packet", - .attr_status = &attr_tacacs_accounting_status, - .status_fail = FR_TAC_PLUS_ACCT_STATUS_ERROR, - }, - [FR_TACACS_ACCT_REPLY] = { - .packet_type = { - [RLM_MODULE_OK] = FR_TACACS_ACCT_REPLY, - [RLM_MODULE_NOOP] = FR_TACACS_ACCT_REPLY, - [RLM_MODULE_UPDATED] = FR_TACACS_ACCT_REPLY, - - [RLM_MODULE_REJECT] = FR_TACACS_ACCT_REPLY, - [RLM_MODULE_FAIL] = FR_TACACS_ACCT_REPLY, - [RLM_MODULE_INVALID] = FR_TACACS_ACCT_REPLY, - [RLM_MODULE_DISALLOW] = FR_TACACS_ACCT_REPLY, - [RLM_MODULE_NOTFOUND] = FR_TACACS_ACCT_REPLY, - }, - .rcode = RLM_MODULE_NOOP, - .send = send_generic, - .resume = resume_send_tacacs, - .section_offset = offsetof(process_tacacs_sections_t, acct_reply), + /* + * Run accounting foo { ... } + */ + vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_tacacs_accounting_flags); + if (!vp) { + fail: + request->reply->code = FR_TACACS_CODE_ACCT_REPLY_ERROR; + UPDATE_STATE(reply); + fr_assert(state->send != NULL); + return CALL_SEND_STATE(state); + } - .fail_message = "Failed to process the accounting packet", - .attr_status = &attr_tacacs_accounting_status, - .status_fail = FR_TAC_PLUS_ACCT_STATUS_ERROR, - }, + dv = fr_dict_enum_by_value(vp->da, &vp->data); + if (!dv) goto fail; - [FR_TACACS_DO_NOT_RESPOND] = { - .packet_type = { - [RLM_MODULE_OK] = FR_TACACS_DO_NOT_RESPOND, - [RLM_MODULE_NOOP] = FR_TACACS_DO_NOT_RESPOND, - [RLM_MODULE_UPDATED] = FR_TACACS_DO_NOT_RESPOND, - - [RLM_MODULE_REJECT] = FR_TACACS_DO_NOT_RESPOND, - [RLM_MODULE_FAIL] = FR_TACACS_DO_NOT_RESPOND, - [RLM_MODULE_INVALID] = FR_TACACS_DO_NOT_RESPOND, - [RLM_MODULE_DISALLOW] = FR_TACACS_DO_NOT_RESPOND, - [RLM_MODULE_NOTFOUND] = FR_TACACS_DO_NOT_RESPOND, - }, - .rcode = RLM_MODULE_NOOP, - .send = send_generic, - .resume = resume_send_tacacs, - .section_offset = offsetof(process_tacacs_sections_t, do_not_respond), - }, -}; + cs = cf_section_find(inst->server_cs, "accounting", dv->name); + if (!cs) { + RDEBUG2("No 'accounting %s { ... }' section found - skipping...", dv->name); + goto fail; + } + + /* + * Run the "accounting foo { ... }" section. + * + * And continue with sending the generic reply. + */ + return unlang_module_yield_to_section(p_result, request, + cs, RLM_MODULE_NOOP, resume_acct_type, + NULL, mctx->rctx); +} static unlang_action_t mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) { @@ -587,17 +748,9 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) { process_tacacs_t *inst = talloc_get_type_abort(mctx->inst->data, process_tacacs_t); - /* - * Usually we use the 'State' attribute. But, in this - * case we are using the listener followed by the - * Session-ID as the state id. It is 32-bits of - * (allegedly) random value. It MUST be unique per TCP - * connection. - */ - inst->state_tree = fr_state_tree_init(inst, attr_tacacs_state, main_config->spawn_workers, inst->max_session, - inst->session_timeout, inst->state_server_id, - fr_hash_string(cf_section_name2(inst->server_cs))); - + inst->auth.state_tree = fr_state_tree_init(inst, attr_tacacs_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; } @@ -617,6 +770,225 @@ static int mod_bootstrap(module_inst_ctx_t const *mctx) 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[] = { + /* + * Authentication + */ + [ FR_TACACS_CODE_AUTH_START ] = { + .packet_type = { + [RLM_MODULE_FAIL] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_INVALID] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_REJECT] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_DISALLOW] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_NOTFOUND] = FR_TACACS_CODE_AUTH_REPLY_FAIL + }, + .rcode = RLM_MODULE_NOOP, + .recv = recv_auth_start, + .resume = resume_auth_start, + .section_offset = offsetof(process_tacacs_sections_t, auth_start), + }, + [ FR_TACACS_CODE_AUTH_REPLY_PASS ] = { + .packet_type = { + [RLM_MODULE_FAIL] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_INVALID] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_REJECT] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_DISALLOW] = FR_TACACS_CODE_AUTH_REPLY_FAIL + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_auth_reply_pass, + .section_offset = offsetof(process_tacacs_sections_t, auth_reply_pass), + }, + [ FR_TACACS_CODE_AUTH_REPLY_FAIL ] = { + .packet_type = { + [RLM_MODULE_FAIL] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_INVALID] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_REJECT] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_DISALLOW] = FR_TACACS_CODE_AUTH_REPLY_FAIL + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_auth_reply_fail, + .section_offset = offsetof(process_tacacs_sections_t, auth_reply_fail), + }, + [ FR_TACACS_CODE_AUTH_REPLY_GETDATA ] = { + .packet_type = { + [RLM_MODULE_FAIL] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_INVALID] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_REJECT] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_DISALLOW] = FR_TACACS_CODE_AUTH_REPLY_FAIL + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_auth_reply_get, + .section_offset = offsetof(process_tacacs_sections_t, auth_reply_getdata), + }, + [ FR_TACACS_CODE_AUTH_REPLY_GETPASS ] = { + .packet_type = { + [RLM_MODULE_FAIL] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_INVALID] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_REJECT] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_DISALLOW] = FR_TACACS_CODE_AUTH_REPLY_FAIL + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_auth_reply_get, + .section_offset = offsetof(process_tacacs_sections_t, auth_reply_getpass), + }, + [ FR_TACACS_CODE_AUTH_REPLY_GETUSER ] = { + .packet_type = { + [RLM_MODULE_FAIL] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_INVALID] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_REJECT] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_DISALLOW] = FR_TACACS_CODE_AUTH_REPLY_FAIL + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_auth_reply_get, + .section_offset = offsetof(process_tacacs_sections_t, auth_reply_getuser), + }, + + [ FR_TACACS_CODE_AUTH_CONT ] = { + .packet_type = { + [RLM_MODULE_FAIL] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_INVALID] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_REJECT] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_DISALLOW] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_NOTFOUND] = FR_TACACS_CODE_AUTH_REPLY_FAIL + }, + .rcode = RLM_MODULE_NOOP, + .recv = recv_auth_cont, + .resume = resume_auth_start, /* we go back to running 'authenticate', etc. */ + .section_offset = offsetof(process_tacacs_sections_t, auth_cont), + }, + [ FR_TACACS_CODE_AUTH_CONT_ABORT ] = { + .packet_type = { + [RLM_MODULE_FAIL] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_INVALID] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_REJECT] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_DISALLOW] = FR_TACACS_CODE_AUTH_REPLY_FAIL, + [RLM_MODULE_NOTFOUND] = FR_TACACS_CODE_AUTH_REPLY_FAIL + }, + .rcode = RLM_MODULE_NOOP, + .recv = recv_auth_cont_abort, + .resume = resume_auth_cont_abort, + .section_offset = offsetof(process_tacacs_sections_t, auth_cont_abort), + }, + + /* + * Authorization + */ + [ FR_TACACS_CODE_AUTZ_REQUEST ] = { + .packet_type = { + [RLM_MODULE_NOOP] = FR_TACACS_CODE_AUTZ_REPLY_PASS_ADD, + [RLM_MODULE_OK] = FR_TACACS_CODE_AUTZ_REPLY_PASS_ADD, + [RLM_MODULE_UPDATED] = FR_TACACS_CODE_AUTZ_REPLY_PASS_ADD, + [RLM_MODULE_HANDLED] = FR_TACACS_CODE_AUTZ_REPLY_PASS_ADD, + + [RLM_MODULE_FAIL] = FR_TACACS_CODE_AUTZ_REPLY_FAIL, + [RLM_MODULE_INVALID] = FR_TACACS_CODE_AUTZ_REPLY_FAIL, + [RLM_MODULE_NOTFOUND] = FR_TACACS_CODE_AUTZ_REPLY_FAIL, + [RLM_MODULE_REJECT] = FR_TACACS_CODE_AUTZ_REPLY_FAIL, + [RLM_MODULE_DISALLOW] = FR_TACACS_CODE_AUTZ_REPLY_FAIL, + }, + .rcode = RLM_MODULE_NOOP, + .recv = recv_generic, + .resume = resume_recv_generic, + .section_offset = offsetof(process_tacacs_sections_t, autz_request), + }, + [ FR_TACACS_CODE_AUTZ_REPLY_PASS_ADD ] = { + .packet_type = { + [RLM_MODULE_FAIL] = FR_TACACS_CODE_AUTZ_REPLY_FAIL, + [RLM_MODULE_INVALID] = FR_TACACS_CODE_AUTZ_REPLY_FAIL, + [RLM_MODULE_NOTFOUND] = FR_TACACS_CODE_AUTZ_REPLY_FAIL, + [RLM_MODULE_REJECT] = FR_TACACS_CODE_AUTZ_REPLY_FAIL, + [RLM_MODULE_DISALLOW] = FR_TACACS_CODE_AUTZ_REPLY_FAIL, + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_send_generic, + .section_offset = offsetof(process_tacacs_sections_t, autz_reply_pass_add), + }, + [ FR_TACACS_CODE_AUTZ_REPLY_PASS_REPLACE ] = { + .packet_type = { + [RLM_MODULE_FAIL] = FR_TACACS_CODE_AUTZ_REPLY_FAIL, + [RLM_MODULE_INVALID] = FR_TACACS_CODE_AUTZ_REPLY_FAIL, + [RLM_MODULE_NOTFOUND] = FR_TACACS_CODE_AUTZ_REPLY_FAIL, + [RLM_MODULE_REJECT] = FR_TACACS_CODE_AUTZ_REPLY_FAIL, + [RLM_MODULE_DISALLOW] = FR_TACACS_CODE_AUTZ_REPLY_FAIL, + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_send_generic, + .section_offset = offsetof(process_tacacs_sections_t, autz_reply_pass_replace), + }, + [ FR_TACACS_CODE_AUTZ_REPLY_FAIL ] = { + .packet_type = { + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_send_generic, + .section_offset = offsetof(process_tacacs_sections_t, autz_reply_fail), + }, + [ FR_TACACS_CODE_AUTZ_REPLY_ERROR ] = { + .packet_type = { + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_send_generic, + .section_offset = offsetof(process_tacacs_sections_t, autz_reply_error), + }, + + /* + * Accounting + */ + [ FR_TACACS_CODE_ACCT_REQUEST ] = { + .packet_type = { + [RLM_MODULE_NOOP] = FR_TACACS_CODE_ACCT_REPLY_SUCCESS, + [RLM_MODULE_OK] = FR_TACACS_CODE_ACCT_REPLY_SUCCESS, + [RLM_MODULE_UPDATED] = FR_TACACS_CODE_ACCT_REPLY_SUCCESS, + [RLM_MODULE_HANDLED] = FR_TACACS_CODE_ACCT_REPLY_SUCCESS, + + [RLM_MODULE_FAIL] = FR_TACACS_CODE_ACCT_REPLY_ERROR, + [RLM_MODULE_INVALID] = FR_TACACS_CODE_ACCT_REPLY_ERROR, + [RLM_MODULE_NOTFOUND] = FR_TACACS_CODE_ACCT_REPLY_ERROR, + [RLM_MODULE_REJECT] = FR_TACACS_CODE_ACCT_REPLY_ERROR, + [RLM_MODULE_DISALLOW] = FR_TACACS_CODE_ACCT_REPLY_ERROR, + }, + .rcode = RLM_MODULE_NOOP, + .recv = recv_generic, + .resume = resume_accounting_request, + .section_offset = offsetof(process_tacacs_sections_t, acct_request), + }, + [ FR_TACACS_CODE_ACCT_REPLY_SUCCESS ] = { + .packet_type = { + [RLM_MODULE_FAIL] = FR_TACACS_CODE_ACCT_REPLY_ERROR, + [RLM_MODULE_INVALID] = FR_TACACS_CODE_ACCT_REPLY_ERROR, + [RLM_MODULE_NOTFOUND] = FR_TACACS_CODE_ACCT_REPLY_ERROR, + [RLM_MODULE_REJECT] = FR_TACACS_CODE_ACCT_REPLY_ERROR, + [RLM_MODULE_DISALLOW] = FR_TACACS_CODE_ACCT_REPLY_ERROR + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_send_generic, + .section_offset = offsetof(process_tacacs_sections_t, acct_reply_success), + }, + [ FR_TACACS_CODE_ACCT_REPLY_ERROR ] = { + .packet_type = { + }, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_send_generic, + .section_offset = offsetof(process_tacacs_sections_t, acct_reply_error), + }, +}; + + static virtual_server_compile_t compile_list[] = { /** * Basically, the TACACS+ protocol use same type "authenticate" to handle @@ -639,9 +1011,45 @@ static virtual_server_compile_t compile_list[] = { }, { .name = "send", - .name2 = "Authentication-Start-Reply", + .name2 = "Authentication-Reply-Pass", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(auth_reply_pass), + }, + { + .name = "send", + .name2 = "Authentication-Reply-Fail", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(auth_reply_fail), + }, + { + .name = "send", + .name2 = "Authentication-Reply-GetData", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(auth_reply_getdata), + }, + { + .name = "send", + .name2 = "Authentication-Reply-GetUser", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(auth_reply_getuser), + }, + { + .name = "send", + .name2 = "Authentication-Reply-GetPass", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(auth_reply_getpass), + }, + { + .name = "send", + .name2 = "Authentication-Reply-Restart", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(auth_reply_restart), + }, + { + .name = "send", + .name2 = "Authentication-Reply-Error", .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(auth_start_reply), + .offset = PROCESS_CONF_OFFSET(auth_reply_error), }, { .name = "recv", @@ -650,10 +1058,10 @@ static virtual_server_compile_t compile_list[] = { .offset = PROCESS_CONF_OFFSET(auth_cont), }, { - .name = "send", - .name2 = "Authentication-Continue-Reply", - .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(auth_cont_reply), + .name = "recv", + .name2 = "Authentication-Continue-Abort", + .component = MOD_AUTHENTICATE, + .offset = PROCESS_CONF_OFFSET(auth_cont_abort), }, { @@ -672,9 +1080,27 @@ static virtual_server_compile_t compile_list[] = { }, { .name = "send", - .name2 = "Authorization-Reply", + .name2 = "Authorization-Reply-Pass-Add", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(autz_reply_pass_add), + }, + { + .name = "send", + .name2 = "Authorization-Reply-Pass-Replace", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(autz_reply_pass_replace), + }, + { + .name = "send", + .name2 = "Authorization-Reply-Fail", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(autz_reply_fail), + }, + { + .name = "send", + .name2 = "Authorization-Reply-Error", .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(autz_reply), + .offset = PROCESS_CONF_OFFSET(autz_reply_error), }, /* accounting */ @@ -687,10 +1113,17 @@ static virtual_server_compile_t compile_list[] = { }, { .name = "send", - .name2 = "Accounting-Reply", + .name2 = "Accounting-Reply-Success", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(acct_reply_success), + }, + { + .name = "send", + .name2 = "Accounting-Reply-Error", .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(acct_reply), + .offset = PROCESS_CONF_OFFSET(acct_reply_error), }, + { .name = "accounting", .name2 = CF_IDENT_ANY, diff --git a/src/protocols/tacacs/base.c b/src/protocols/tacacs/base.c index 978b77a0210..9017c81c3ee 100644 --- a/src/protocols/tacacs/base.c +++ b/src/protocols/tacacs/base.c @@ -100,17 +100,26 @@ fr_dict_attr_autoload_t libfreeradius_tacacs_dict_attr[] = { }; char const *fr_tacacs_packet_names[FR_TACACS_CODE_MAX] = { - [FR_PACKET_TYPE_VALUE_AUTHENTICATION_START] = "Authentication-Start", - [FR_PACKET_TYPE_VALUE_AUTHENTICATION_START_REPLY] = "Authentication-Start-Reply", - - [FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE] = "Authentication-Continue", - [FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE_REPLY] = "Authentication-Continue-Reply", - - [FR_PACKET_TYPE_VALUE_AUTHORIZATION_REQUEST] = "Authorization-Request", - [FR_PACKET_TYPE_VALUE_AUTHORIZATION_REPLY] = "Authorization-Reply", - - [FR_PACKET_TYPE_VALUE_ACCOUNTING_REQUEST] = "Accounting-Request", - [FR_PACKET_TYPE_VALUE_ACCOUNTING_REPLY] = "Accounting-Reply", + [FR_PACKET_TYPE_VALUE_AUTHENTICATION_START] = "Authentication-Start", + [FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_PASS] = "Authentication-Reply-Pass", + [FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_FAIL] = "Authentication-Reply-Fail", + [FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_GETDATA] = "Authentication-Reply-GetData", + [FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_GETUSER] = "Authentication-Reply-GetUser", + [FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_GETPASS] = "Authentication-Reply-GetPass", + [FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_RESTART] = "Authentication-Reply-Restart", + [FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_ERROR] = "Authentication-Reply-Error", + + [FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE] = "Authentication-Continue", + [FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE_ABORT] = "Authentication-Continue-Abort", + + [FR_PACKET_TYPE_VALUE_AUTHORIZATION_REQUEST] = "Authorization-Request", + [FR_PACKET_TYPE_VALUE_AUTHORIZATION_REPLY_PASS_ADD] = "Authorization-Reply-Pass-Add", + [FR_PACKET_TYPE_VALUE_AUTHORIZATION_REPLY_PASS_REPLACE] = "Authorization-Reply-Pass-Replace", + [FR_PACKET_TYPE_VALUE_AUTHORIZATION_REPLY_FAIL] = "Authorization-Reply-Fail", + + [FR_PACKET_TYPE_VALUE_ACCOUNTING_REQUEST] = "Accounting-Request", + [FR_PACKET_TYPE_VALUE_ACCOUNTING_REPLY_SUCCESS] = "Accounting-Reply-Success", + [FR_PACKET_TYPE_VALUE_ACCOUNTING_REPLY_ERROR] = "Accounting-Reply-Error", }; @@ -264,8 +273,8 @@ ssize_t fr_tacacs_length(uint8_t const *buffer, size_t buffer_len) if (packet_is_author_request(pkt)) { want = sizeof(pkt->hdr) + sizeof(pkt->author_req); } else { - fr_assert(packet_is_author_response(pkt)); - want = sizeof(pkt->hdr) + sizeof(pkt->author_res); + fr_assert(packet_is_author_reply(pkt)); + want = sizeof(pkt->hdr) + sizeof(pkt->author_reply); } break; @@ -569,7 +578,7 @@ void _fr_tacacs_packet_log_hex(fr_log_t const *log, fr_tacacs_packet_t const *pa } else { fr_log(log, L_DBG, file, line, " authorization-response"); - fr_assert(packet_is_author_response(packet)); + fr_assert(packet_is_author_reply(packet)); REQUIRE(6); diff --git a/src/protocols/tacacs/decode.c b/src/protocols/tacacs/decode.c index 0c8c91e0bd4..74541d787bc 100644 --- a/src/protocols/tacacs/decode.c +++ b/src/protocols/tacacs/decode.c @@ -33,6 +33,97 @@ #include "tacacs.h" #include "attrs.h" +int fr_tacacs_packet_to_code(fr_tacacs_packet_t const *pkt) +{ + switch (pkt->hdr.type) { + case FR_TAC_PLUS_AUTHEN: + if (pkt->hdr.seq_no == 1) return FR_PACKET_TYPE_VALUE_AUTHENTICATION_START; + + if ((pkt->hdr.seq_no & 0x01) == 1) { + if (pkt->authen_cont.flags == FR_TAC_PLUS_CONTINUE_FLAG_UNSET) return FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE; + + if (pkt->authen_cont.flags == FR_TAC_PLUS_CONTINUE_FLAG_ABORT) return FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE_ABORT; + + fr_strerror_const("Invalid TACACS+ value for authentication continue flag"); + return -1; + } + + switch (pkt->authen_reply.status) { + case FR_TAC_PLUS_AUTHEN_STATUS_PASS: + return FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_PASS; + + case FR_TAC_PLUS_AUTHEN_STATUS_FAIL: + return FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_FAIL; + + case FR_TAC_PLUS_AUTHEN_STATUS_GETDATA: + return FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_GETDATA; + + case FR_TAC_PLUS_AUTHEN_STATUS_GETUSER: + return FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_GETUSER; + + case FR_TAC_PLUS_AUTHEN_STATUS_GETPASS: + return FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_GETPASS; + + case FR_TAC_PLUS_AUTHEN_STATUS_RESTART: + return FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_RESTART; + + case FR_TAC_PLUS_AUTHEN_STATUS_ERROR: + return FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_ERROR; + + default: + break; + } + + fr_strerror_printf("Invalid value %u for authentication reply status", pkt->authen_reply.status); + return -1; + + case FR_TAC_PLUS_AUTHOR: + if ((pkt->hdr.seq_no & 0x01) == 1) { + return FR_PACKET_TYPE_VALUE_AUTHORIZATION_REQUEST; + } + + switch (pkt->author_reply.status) { + case FR_TAC_PLUS_AUTHOR_STATUS_PASS_ADD: + return FR_PACKET_TYPE_VALUE_AUTHORIZATION_REPLY_PASS_ADD; + + case FR_TAC_PLUS_AUTHOR_STATUS_PASS_REPL: + return FR_PACKET_TYPE_VALUE_AUTHORIZATION_REPLY_PASS_REPLACE; + + case FR_TAC_PLUS_AUTHOR_STATUS_FAIL: + return FR_PACKET_TYPE_VALUE_AUTHORIZATION_REPLY_FAIL; + + default: + break; + } + + fr_strerror_printf("Invalid value %u for authorization reply status", pkt->author_reply.status); + return -1; + + case FR_TAC_PLUS_ACCT: + if ((pkt->hdr.seq_no & 0x01) == 1) { + return FR_PACKET_TYPE_VALUE_ACCOUNTING_REQUEST; + } + + switch (pkt->acct_reply.status) { + case FR_TAC_PLUS_ACCT_STATUS_SUCCESS: + return FR_PACKET_TYPE_VALUE_ACCOUNTING_REPLY_SUCCESS; + + case FR_TAC_PLUS_ACCT_STATUS_ERROR: + return FR_PACKET_TYPE_VALUE_ACCOUNTING_REPLY_ERROR; + + default: + break; + } + + fr_strerror_printf("Invalid value %u for accounting reply status", pkt->acct_reply.status); + return -1; + + default: + fr_strerror_const("Invalid TACACS+ header type"); + return -1; + } +} + #define PACKET_HEADER_CHECK(_msg) do { \ if (p > end) { \ fr_strerror_printf("Header for %s is too small (%zu < %zu)", _msg, end - (uint8_t const *) pkt, p - (uint8_t const *) pkt); \ @@ -551,7 +642,6 @@ ssize_t fr_tacacs_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *bu DECODE_FIELD_STRING16(attr_tacacs_data, pkt->authen_reply.data_len); } else { - unknown_packet: fr_strerror_const("Unknown authentication packet"); goto fail; } @@ -614,7 +704,7 @@ ssize_t fr_tacacs_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *bu if (tacacs_decode_args(ctx, out, attr_tacacs_argument_list, pkt->author_req.arg_cnt, BODY(author_req), p, end) < 0) goto fail; - } else if (packet_is_author_response(pkt)) { + } else if (packet_is_author_reply(pkt)) { /* * 5.2. The Authorization RESPONSE Packet Body * @@ -643,30 +733,31 @@ ssize_t fr_tacacs_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *bu * We just echo whatever was sent in the request. */ - p = BODY(author_res); + p = BODY(author_reply); PACKET_HEADER_CHECK("Authorization RESPONSE"); - ARG_COUNT_CHECK("Authorization REQUEST", pkt->author_res.arg_cnt); + ARG_COUNT_CHECK("Authorization REQUEST", pkt->author_reply.arg_cnt); DECODE_FIELD_UINT8(attr_tacacs_packet_body_type, FR_PACKET_BODY_TYPE_RESPONSE); /* * Decode 1 octets */ - DECODE_FIELD_UINT8(attr_tacacs_authorization_status, pkt->author_res.status); + DECODE_FIELD_UINT8(attr_tacacs_authorization_status, pkt->author_reply.status); /* * Decode 2 fields, based on their "length" */ - DECODE_FIELD_STRING16(attr_tacacs_server_message, pkt->author_res.server_msg_len); - DECODE_FIELD_STRING16(attr_tacacs_data, pkt->author_res.data_len); + DECODE_FIELD_STRING16(attr_tacacs_server_message, pkt->author_reply.server_msg_len); + DECODE_FIELD_STRING16(attr_tacacs_data, pkt->author_reply.data_len); /* * Decode 'arg_N' arguments (horrible format) */ if (tacacs_decode_args(ctx, out, attr_tacacs_argument_list, - pkt->author_res.arg_cnt, BODY(author_res), p, end) < 0) goto fail; + pkt->author_reply.arg_cnt, BODY(author_reply), p, end) < 0) goto fail; } else { - goto unknown_packet; + fr_strerror_const("Unknown authorization packet"); + goto fail; } break; @@ -760,7 +851,8 @@ ssize_t fr_tacacs_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *bu /* Decode 1 octet */ DECODE_FIELD_UINT8(attr_tacacs_accounting_status, pkt->acct_reply.status); } else { - goto unknown_packet; + fr_strerror_const("Unknown accounting packet"); + goto fail; } break; default: diff --git a/src/protocols/tacacs/encode.c b/src/protocols/tacacs/encode.c index 56be8168bd3..3075f2ea75a 100644 --- a/src/protocols/tacacs/encode.c +++ b/src/protocols/tacacs/encode.c @@ -33,6 +33,100 @@ #include "tacacs.h" #include "attrs.h" +int fr_tacacs_code_to_packet(fr_tacacs_packet_t *pkt, uint32_t code) +{ + switch (code) { + case FR_PACKET_TYPE_VALUE_AUTHENTICATION_START: + pkt->hdr.type = FR_TAC_PLUS_AUTHEN; + pkt->hdr.seq_no = 1; + break; + + case FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_PASS: + pkt->hdr.type = FR_TAC_PLUS_AUTHEN; + pkt->authen_reply.status = FR_TAC_PLUS_AUTHEN_STATUS_PASS; + break; + + case FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_FAIL: + pkt->hdr.type = FR_TAC_PLUS_AUTHEN; + pkt->authen_reply.status = FR_TAC_PLUS_AUTHEN_STATUS_FAIL; + break; + + case FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_GETDATA: + pkt->hdr.type = FR_TAC_PLUS_AUTHEN; + pkt->authen_reply.status = FR_TAC_PLUS_AUTHEN_STATUS_GETDATA; + break; + + case FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_GETUSER: + pkt->hdr.type = FR_TAC_PLUS_AUTHEN; + pkt->authen_reply.status = FR_TAC_PLUS_AUTHEN_STATUS_GETUSER; + break; + + case FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_GETPASS: + pkt->hdr.type = FR_TAC_PLUS_AUTHEN; + pkt->authen_reply.status = FR_TAC_PLUS_AUTHEN_STATUS_GETPASS; + break; + + case FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_RESTART: + pkt->hdr.type = FR_TAC_PLUS_AUTHEN; + pkt->authen_reply.status = FR_TAC_PLUS_AUTHEN_STATUS_RESTART; + break; + + case FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_ERROR: + pkt->hdr.type = FR_TAC_PLUS_AUTHEN; + pkt->authen_reply.status = FR_TAC_PLUS_AUTHEN_STATUS_ERROR; + break; + + case FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE: + pkt->hdr.type = FR_TAC_PLUS_AUTHEN; + pkt->authen_cont.flags = FR_TAC_PLUS_CONTINUE_FLAG_UNSET; + break; + + case FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE_ABORT: + pkt->hdr.type = FR_TAC_PLUS_AUTHEN; + pkt->authen_cont.flags = FR_TAC_PLUS_CONTINUE_FLAG_ABORT; + break; + + case FR_PACKET_TYPE_VALUE_AUTHORIZATION_REQUEST: + pkt->hdr.type = FR_TAC_PLUS_AUTHOR; + break; + + case FR_PACKET_TYPE_VALUE_AUTHORIZATION_REPLY_PASS_ADD: + pkt->hdr.type = FR_TAC_PLUS_AUTHOR; + pkt->author_reply.status = FR_TAC_PLUS_AUTHOR_STATUS_PASS_ADD; + break; + + case FR_PACKET_TYPE_VALUE_AUTHORIZATION_REPLY_PASS_REPLACE: + pkt->hdr.type = FR_TAC_PLUS_AUTHOR; + pkt->author_reply.status = FR_TAC_PLUS_AUTHOR_STATUS_PASS_REPL; + break; + + case FR_PACKET_TYPE_VALUE_AUTHORIZATION_REPLY_FAIL: + pkt->hdr.type = FR_TAC_PLUS_AUTHOR; + pkt->author_reply.status = FR_TAC_PLUS_AUTHOR_STATUS_FAIL; + break; + + case FR_PACKET_TYPE_VALUE_ACCOUNTING_REQUEST: + pkt->hdr.type = FR_TAC_PLUS_ACCT; + break; + + case FR_PACKET_TYPE_VALUE_ACCOUNTING_REPLY_SUCCESS: + pkt->hdr.type = FR_TAC_PLUS_ACCT; + pkt->acct_reply.status = FR_TAC_PLUS_ACCT_STATUS_SUCCESS; + break; + + case FR_PACKET_TYPE_VALUE_ACCOUNTING_REPLY_ERROR: + pkt->hdr.type = FR_TAC_PLUS_ACCT; + pkt->acct_reply.status = FR_TAC_PLUS_ACCT_STATUS_ERROR; + break; + + default: + fr_strerror_const("Invalid TACACS+ packet type"); + return -1; + } + + return 0; +} + /** * Encode a TACACS+ 'arg_N' fields. */ @@ -175,7 +269,8 @@ static ssize_t tacacs_encode_field(fr_dbuff_t *dbuff, fr_pair_list_t *vps, fr_di /** * Encode VPS into a raw TACACS packet. */ -ssize_t fr_tacacs_encode(fr_dbuff_t *dbuff, uint8_t const *original_packet, char const *secret, size_t secret_len, fr_pair_list_t *vps) +ssize_t fr_tacacs_encode(fr_dbuff_t *dbuff, uint8_t const *original_packet, char const *secret, size_t secret_len, + unsigned int code, fr_pair_list_t *vps) { fr_pair_t *vp; fr_tacacs_packet_t *packet; @@ -474,7 +569,7 @@ ssize_t fr_tacacs_encode(fr_dbuff_t *dbuff, uint8_t const *original_packet, char } goto check_request; - } else if (packet_is_author_response(packet)) { + } else if (packet_is_author_reply(packet)) { /* * 5.2. The Authorization RESPONSE Packet Body * @@ -501,12 +596,12 @@ ssize_t fr_tacacs_encode(fr_dbuff_t *dbuff, uint8_t const *original_packet, char /* * Make room for such body request. */ - FR_DBUFF_ADVANCE_RETURN(&work_dbuff, sizeof(packet->author_res)); + FR_DBUFF_ADVANCE_RETURN(&work_dbuff, sizeof(packet->author_reply)); /* * Encode 1 mandatory field. */ - ENCODE_FIELD_UINT8(packet->author_res.status, attr_tacacs_authorization_status); + ENCODE_FIELD_UINT8(packet->author_reply.status, attr_tacacs_authorization_status); /* * Encode 'arg_N' arguments (horrible format) @@ -523,25 +618,25 @@ ssize_t fr_tacacs_encode(fr_dbuff_t *dbuff, uint8_t const *original_packet, char * When the status equals TAC_PLUS_AUTHOR_STATUS_FOLLOW, then the * arg_cnt MUST be 0. */ - if (!((packet->author_res.status == FR_AUTHORIZATION_STATUS_VALUE_ERROR) || - (packet->author_res.status == FR_AUTHORIZATION_STATUS_VALUE_FOLLOW))) { - packet->author_res.arg_cnt = tacacs_encode_body_arg_cnt(vps, attr_tacacs_argument_list); - if (packet->author_res.arg_cnt) FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, packet->author_res.arg_cnt); + if (!((packet->author_reply.status == FR_AUTHORIZATION_STATUS_VALUE_ERROR) || + (packet->author_reply.status == FR_AUTHORIZATION_STATUS_VALUE_FOLLOW))) { + packet->author_reply.arg_cnt = tacacs_encode_body_arg_cnt(vps, attr_tacacs_argument_list); + if (packet->author_reply.arg_cnt) FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, packet->author_reply.arg_cnt); } else { - packet->author_res.arg_cnt = 0; + packet->author_reply.arg_cnt = 0; } /* * Encode 2 mandatory fields. */ - ENCODE_FIELD_STRING16(packet->author_res.server_msg_len, attr_tacacs_server_message); - ENCODE_FIELD_STRING16(packet->author_res.data_len, attr_tacacs_data); + ENCODE_FIELD_STRING16(packet->author_reply.server_msg_len, attr_tacacs_server_message); + ENCODE_FIELD_STRING16(packet->author_reply.data_len, attr_tacacs_data); /* * Append 'args_body' to the end of buffer */ - if (packet->author_res.arg_cnt > 0) { - if (tacacs_encode_body_arg_n(&work_dbuff, packet->author_res.arg_cnt, &packet->author_res.arg_len[0], vps, attr_tacacs_argument_list) < 0) goto error; + if (packet->author_reply.arg_cnt > 0) { + if (tacacs_encode_body_arg_n(&work_dbuff, packet->author_reply.arg_cnt, &packet->author_reply.arg_len[0], vps, attr_tacacs_argument_list) < 0) goto error; } goto check_reply; @@ -709,6 +804,16 @@ ssize_t fr_tacacs_encode(fr_dbuff_t *dbuff, uint8_t const *original_packet, char return -1; } + /* + * Force the correct header type, and randomly-placed + * status fields. But only if there's no code field. + * Only the unit tests pass a zero code field, as that's + * normally invalid. The unit tests ensure that all of + * the VPs are passed to encode a packet, and they all + * must be correct + */ + if (code && (fr_tacacs_code_to_packet(packet, code) < 0)) return -1; + /* * The packet length we store in the header doesn't * include the size of the header. But we tell the @@ -776,7 +881,7 @@ static ssize_t fr_tacacs_encode_proto(UNUSED TALLOC_CTX *ctx, fr_pair_list_t *vp { fr_tacacs_ctx_t *test_ctx = talloc_get_type_abort(proto_ctx, fr_tacacs_ctx_t); - return fr_tacacs_encode(&FR_DBUFF_TMP(data, data_len), NULL, test_ctx->secret, (talloc_array_length(test_ctx->secret)-1), vps); + return fr_tacacs_encode(&FR_DBUFF_TMP(data, data_len), NULL, test_ctx->secret, (talloc_array_length(test_ctx->secret)-1), 0, vps); } static int _encode_test_ctx(fr_tacacs_ctx_t *proto_ctx) diff --git a/src/protocols/tacacs/tacacs.h b/src/protocols/tacacs/tacacs.h index c1a13d3fbc0..09e2058e68f 100644 --- a/src/protocols/tacacs/tacacs.h +++ b/src/protocols/tacacs/tacacs.h @@ -51,10 +51,10 @@ #define packet_is_authen_reply(p) ((p->hdr.type == FR_TAC_PLUS_AUTHEN) && ((p->hdr.seq_no % 2) == 0)) #define packet_is_author_request(p) ((p->hdr.type == FR_TAC_PLUS_AUTHOR) && ((p->hdr.seq_no % 2) == 1)) -#define packet_is_author_response(p) ((p->hdr.type == FR_TAC_PLUS_AUTHOR) && ((p->hdr.seq_no % 2) == 0)) +#define packet_is_author_reply(p) ((p->hdr.type == FR_TAC_PLUS_AUTHOR) && ((p->hdr.seq_no % 2) == 0)) #define packet_is_acct_request(p) ((p->hdr.type == FR_TAC_PLUS_ACCT) && ((p->hdr.seq_no % 2) == 1)) -#define packet_is_acct_reply(p) ((p->hdr.type == FR_TAC_PLUS_ACCT) && ((p->hdr.seq_no % 2) == 1)) +#define packet_is_acct_reply(p) ((p->hdr.type == FR_TAC_PLUS_ACCT) && ((p->hdr.seq_no % 2) == 0)) #define packet_has_valid_seq_no(p) (p->hdr.seq_no != 0) @@ -139,14 +139,14 @@ typedef struct CC_HINT(__packed__) { } fr_tacacs_packet_authen_start_hdr_t; typedef enum { - FR_TAC_PLUS_AUTHEN_STATUS_PASS = 0x01, - FR_TAC_PLUS_AUTHEN_STATUS_FAIL = 0x02, - FR_TAC_PLUS_AUTHEN_STATUS_GETDATA = 0x03, - FR_TAC_PLUS_AUTHEN_STATUS_GETUSER = 0x04, - FR_TAC_PLUS_AUTHEN_STATUS_GETPASS = 0x05, - FR_TAC_PLUS_AUTHEN_STATUS_RESTART = 0x06, - FR_TAC_PLUS_AUTHEN_STATUS_ERROR = 0x07, - FR_TAC_PLUS_AUTHEN_STATUS_FOLLOW = 0x21 + FR_TAC_PLUS_AUTHEN_STATUS_PASS = 0x01, /* accept */ + FR_TAC_PLUS_AUTHEN_STATUS_FAIL = 0x02, /* reject */ + FR_TAC_PLUS_AUTHEN_STATUS_GETDATA = 0x03, /* prompt for data */ + FR_TAC_PLUS_AUTHEN_STATUS_GETUSER = 0x04, /* prompt for username */ + FR_TAC_PLUS_AUTHEN_STATUS_GETPASS = 0x05, /* prmpt for password */ + FR_TAC_PLUS_AUTHEN_STATUS_RESTART = 0x06, /* client restarts with START and seq_no=1 */ + FR_TAC_PLUS_AUTHEN_STATUS_ERROR = 0x07, /* server has unrecoverable error */ + FR_TAC_PLUS_AUTHEN_STATUS_FOLLOW = 0x21 /* forward, should be treated as FR_TAC_PLUS_AUTHEN_STATUS_FAIL */ } fr_tacacs_authen_reply_status_t; typedef enum { @@ -199,20 +199,20 @@ typedef struct CC_HINT(__packed__) { } fr_tacacs_packet_author_req_hdr_t; typedef enum { - FR_TAC_PLUS_AUTHOR_STATUS_PASS_ADD = 0x01, - FR_TAC_PLUS_AUTHOR_STATUS_PASS_REPL = 0x02, - FR_TAC_PLUS_AUTHOR_STATUS_FAIL = 0x10, - FR_TAC_PLUS_AUTHOR_STATUS_ERROR = 0x11, - FR_TAC_PLUS_AUTHOR_STATUS_FOLLOW = 0x21 -} fr_tacacs_author_res_status_t; + FR_TAC_PLUS_AUTHOR_STATUS_PASS_ADD = 0x01, /* authorized, append new arguments (if any) */ + FR_TAC_PLUS_AUTHOR_STATUS_PASS_REPL = 0x02, /* authorized, replace arguments */ + FR_TAC_PLUS_AUTHOR_STATUS_FAIL = 0x10, /* reject */ + FR_TAC_PLUS_AUTHOR_STATUS_ERROR = 0x11, /* server error, no argument values */ + FR_TAC_PLUS_AUTHOR_STATUS_FOLLOW = 0x21 /* forward, should be treated as FR_TAC_PLUS_AUTHOR_STATUS_FAIL */ +} fr_tacacs_author_reply_status_t; typedef struct CC_HINT(__packed__) { - fr_tacacs_author_res_status_t status:8; + fr_tacacs_author_reply_status_t status:8; uint8_t arg_cnt; uint16_t server_msg_len; uint16_t data_len; uint8_t arg_len[]; -} fr_tacacs_packet_author_res_hdr_t; +} fr_tacacs_packet_author_reply_hdr_t; typedef enum { FR_TAC_PLUS_ACCT_FLAG_START = 0x02, @@ -245,7 +245,7 @@ typedef enum { typedef enum { FR_TAC_PLUS_ACCT_STATUS_SUCCESS = 0x01, FR_TAC_PLUS_ACCT_STATUS_ERROR = 0x02, - FR_TAC_PLUS_ACCT_STATUS_FOLLOW = 0x21 + FR_TAC_PLUS_ACCT_STATUS_FOLLOW = 0x21 /* forward, should be treated as FR_TAC_PLUS_ACCT_STATUS_ERROR */ } fr_tacacs_acct_reply_status_t; typedef struct CC_HINT(__packed__) { @@ -261,29 +261,41 @@ typedef struct CC_HINT(__packed__) { fr_tacacs_packet_authen_reply_hdr_t authen_reply; fr_tacacs_packet_authen_cont_hdr_t authen_cont; fr_tacacs_packet_author_req_hdr_t author_req; - fr_tacacs_packet_author_res_hdr_t author_res; + fr_tacacs_packet_author_reply_hdr_t author_reply; fr_tacacs_packet_acct_req_hdr_t acct_req; fr_tacacs_packet_acct_reply_hdr_t acct_reply; }; } fr_tacacs_packet_t; -/* - * Shorter names, and in an enum, so that mere humans can understand them. - */ typedef enum { - FR_TACACS_INVALID = 0, - FR_TACACS_AUTH_START = FR_PACKET_TYPE_VALUE_AUTHENTICATION_START, - FR_TACACS_AUTH_START_REPLY = FR_PACKET_TYPE_VALUE_AUTHENTICATION_START_REPLY, - FR_TACACS_AUTH_CONTINUE = FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE, - FR_TACACS_AUTH_CONTINUE_REPLY = FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE_REPLY, - FR_TACACS_AUTZ_REQUEST = FR_PACKET_TYPE_VALUE_AUTHORIZATION_REQUEST, - FR_TACACS_AUTZ_REPLY = FR_PACKET_TYPE_VALUE_AUTHORIZATION_REPLY, - FR_TACACS_ACCT_REQUEST = FR_PACKET_TYPE_VALUE_ACCOUNTING_REQUEST, - FR_TACACS_ACCT_REPLY = FR_PACKET_TYPE_VALUE_ACCOUNTING_REPLY, - FR_TACACS_CODE_MAX = 9, - FR_TACACS_DO_NOT_RESPOND = FR_PACKET_TYPE_VALUE_DO_NOT_RESPOND, + FR_TACACS_CODE_INVALID = 0, + + FR_TACACS_CODE_AUTH_START = FR_PACKET_TYPE_VALUE_AUTHENTICATION_START, + FR_TACACS_CODE_AUTH_REPLY_PASS = FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_PASS, + FR_TACACS_CODE_AUTH_REPLY_FAIL = FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_FAIL, + FR_TACACS_CODE_AUTH_REPLY_GETDATA = FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_GETDATA, + FR_TACACS_CODE_AUTH_REPLY_GETUSER = FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_GETUSER, + FR_TACACS_CODE_AUTH_REPLY_GETPASS = FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_GETPASS, + FR_TACACS_CODE_AUTH_REPLY_RESTART = FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_RESTART, + FR_TACACS_CODE_AUTH_REPLY_ERROR = FR_PACKET_TYPE_VALUE_AUTHENTICATION_REPLY_ERROR, + + FR_TACACS_CODE_AUTH_CONT = FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE, + FR_TACACS_CODE_AUTH_CONT_ABORT = FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE_ABORT, + + FR_TACACS_CODE_AUTZ_REQUEST = FR_PACKET_TYPE_VALUE_AUTHORIZATION_REQUEST, + FR_TACACS_CODE_AUTZ_REPLY_PASS_ADD = FR_PACKET_TYPE_VALUE_AUTHORIZATION_REPLY_PASS_ADD, + FR_TACACS_CODE_AUTZ_REPLY_PASS_REPLACE = FR_PACKET_TYPE_VALUE_AUTHORIZATION_REPLY_PASS_REPLACE, + FR_TACACS_CODE_AUTZ_REPLY_FAIL = FR_PACKET_TYPE_VALUE_AUTHORIZATION_REPLY_FAIL, + FR_TACACS_CODE_AUTZ_REPLY_ERROR = FR_PACKET_TYPE_VALUE_AUTHORIZATION_REPLY_ERROR, + + FR_TACACS_CODE_ACCT_REQUEST = FR_PACKET_TYPE_VALUE_ACCOUNTING_REQUEST, + FR_TACACS_CODE_ACCT_REPLY_SUCCESS = FR_PACKET_TYPE_VALUE_ACCOUNTING_REPLY_SUCCESS, + FR_TACACS_CODE_ACCT_REPLY_ERROR = FR_PACKET_TYPE_VALUE_ACCOUNTING_REPLY_ERROR, + + FR_TACACS_CODE_MAX = 19, } fr_tacacs_packet_code_t; + #define FR_TACACS_PACKET_CODE_VALID(_code) (((_code) > 0) && ((_code) < FR_TACACS_CODE_MAX)) extern char const *fr_tacacs_packet_names[FR_TACACS_CODE_MAX]; @@ -297,12 +309,17 @@ typedef struct { } fr_tacacs_ctx_t; /* encode.c */ -ssize_t fr_tacacs_encode(fr_dbuff_t *dbuff, uint8_t const *original, char const *const secret, size_t secret_len, fr_pair_list_t *vps); +ssize_t fr_tacacs_encode(fr_dbuff_t *dbuff, uint8_t const *original, char const *const secret, size_t secret_len, + unsigned int code, fr_pair_list_t *vps); + +int fr_tacacs_code_to_packet(fr_tacacs_packet_t *pkt, uint32_t code); /* decode.c */ ssize_t fr_tacacs_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *buffer, size_t buffer_len, UNUSED const uint8_t *original, char const * const secret, size_t secret_len); +int fr_tacacs_packet_to_code(fr_tacacs_packet_t const *pkt); + /* base.c */ ssize_t fr_tacacs_length(uint8_t const *buffer, size_t buffer_len); diff --git a/src/tests/tacacs/authorize_error.out b/src/tests/tacacs/authorize_error.out index 13af136d573..6ad8f59116d 100644 --- a/src/tests/tacacs/authorize_error.out +++ b/src/tests/tacacs/authorize_error.out @@ -1,2 +1,2 @@ -status: ERROR +status: FAIL server_msg: b'Authorization-Request failed for caju' diff --git a/src/tests/tacacs/config/radiusd.conf b/src/tests/tacacs/config/radiusd.conf index 38b057b4d48..3f5719b89e8 100644 --- a/src/tests/tacacs/config/radiusd.conf +++ b/src/tests/tacacs/config/radiusd.conf @@ -113,53 +113,53 @@ server test { recv Authentication-Start { if (&User-Name == "tapioca") { - &reply.Authentication-Status := Pass &reply.Server-Message := "Authentication-Start accepted" - ok + &control.Auth-Type := Accept } else { - &reply.Authentication-Status := Fail &reply.Server-Message := "Authentication-Start failed for %{User-Name}" + reject } } - send Authentication-Start-Reply { + send Authentication-Reply-Pass { + &reply.Data := "Authentication-Data" + } + + send Authentication-Reply-Fail { &reply.Data := "Authentication-Data" } recv Authentication-Continue { if (&User-Name == "tapioca") { - &reply.Authentication-Status := Pass &reply.Server-Message := "Authentication-Cont accepted" - ok + &control.Auth-Type := Accept } else { - &reply.Authentication-Status := Fail &reply.Server-Message := "Authentication-Cont failed for %{User-Name}" + reject } } - send Authentication-Continue-Reply { - &reply.Data := "Authentication-Data" - } - recv Authorization-Request { if (&User-Name == "tapioca") { &reply.Authorization-Status := Pass-Add &reply.Server-Message := "Authorization-Request accepted" - ok + &control.Auth-Type := Accept } else { - &reply.Authorization-Status := Error &reply.Server-Message := "Authorization-Request failed for %{User-Name}" + reject } } - send Authorization-Response { - &reply.Data := "Authorization-Data" + send Authorization-Reply-Pass-Add { + } + + send Authorization-Reply-Fail { } recv Accounting-Request { @@ -184,7 +184,7 @@ server test { &reply.Server-Message := "Accounting-Stop Section" } - send Accounting-Reply { + send Accounting-Reply-Success { &reply.Accounting-Status := Success &reply.Data := 0x12 }