]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
rename TACACS+ packet types
authorAlan T. DeKok <aland@freeradius.org>
Sat, 28 Jan 2023 01:45:11 +0000 (20:45 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Sun, 29 Jan 2023 22:07:40 +0000 (17:07 -0500)
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

raddb/sites-available/tacacs
share/dictionary/tacacs/dictionary.freeradius.internal
src/listen/tacacs/proto_tacacs.c
src/modules/rlm_tacacs/rlm_tacacs_tcp.c
src/process/tacacs/base.c
src/protocols/tacacs/base.c
src/protocols/tacacs/decode.c
src/protocols/tacacs/encode.c
src/protocols/tacacs/tacacs.h
src/tests/tacacs/authorize_error.out
src/tests/tacacs/config/radiusd.conf

index 9ec459d0f21f6fe2e65f5ba66a3d77c4af6c62d1..dfacbf7578b4e0706ac8e061cd3756e07e0164eb 100644 (file)
@@ -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
index a2ec143e98dfca8cc35961334dd188dc325892e6..566578b40235b6b2eb0390c149a83da2f32864d4 100644 (file)
 #
 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
index 7f4518954b1b0a27242e056251bba3a0a6ce7ae0..9da87aded9889c2d03ca33ce6f9b1d7f8a056224 100644 (file)
@@ -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;
index af36d0efc5423a31313dcca3cf5327f47aaeaa3d..d4ba19cb908ce41a20f3d86bb5f5d37a297bdae6 100644 (file)
@@ -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;
index ae34fca97e07c9229caa0adaecbecf75e939af75..552e939060d0dc1a0a2c1da548c256d789208b1b 100644 (file)
@@ -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
+                                               //!<captures.
 
-       CONF_SECTION    *server_cs;
+       fr_state_tree_t *state_tree;            //!< State tree to link multiple requests/responses.
+} process_tacacs_auth_t;
+
+typedef struct {
+       CONF_SECTION    *server_cs;             //!< Our virtual server.
+
+       uint32_t        session_id;             //!< current session ID
+
+       process_tacacs_sections_t sections;     //!< Pointers to various config sections
+                                               ///< we need to execute
+
+       process_tacacs_auth_t   auth;           //!< Authentication configuration.
 } process_tacacs_t;
 
 #define PROCESS_PACKET_TYPE            fr_tacacs_packet_code_t
 #define PROCESS_CODE_MAX               FR_TACACS_CODE_MAX
-#define PROCESS_CODE_DO_NOT_RESPOND    FR_TACACS_DO_NOT_RESPOND
 #define PROCESS_PACKET_CODE_VALID      FR_TACACS_PACKET_CODE_VALID
 #define PROCESS_INST                   process_tacacs_t
 
-#define PROCESS_STATE_EXTRA_FIELDS     fr_dict_attr_t const **attr_process; \
-                                       fr_dict_attr_t const **attr_status; \
-                                       char const      *attr_process_section; \
-                                       uint8_t         status_fail; \
-                                       char const      *fail_message;
-
 #include <freeradius-devel/server/process.h>
 
 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), "<via Auth-Type = %s>",
+                                        fr_dict_enum_name_by_value(auth_type->da, &auth_type->data));
+                               password_str = password_buff;
+                       } else {
+                               password_str = "<no User-Password attribute>";
+                       }
+               } else if (fr_pair_find_by_da(&request->request_pairs, NULL, attr_chap_password)) {
+                       password_str = "<CHAP-Password>";
+               }
+       }
+#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("<no User-Name attribute>"),
+             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,
index 978b77a021028b43741589fee86ed608627785dc..9017c81c3ee110da0d6722a3e7ed75a91dad0d45 100644 (file)
@@ -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);
 
index 0c8c91e0bd4e9bed4dfdb23b0639cc180679fe51..74541d787bc558a8fccbad442a31f71e62ae8504 100644 (file)
 #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:
index 56be8168bd3651c47e3eed61dd2641f25ea5a50e..3075f2ea75aa056965584936e67660a6af62a0a6 100644 (file)
 #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)
index c1a13d3fbc01b2265c9e5a34e28b1d436591a4d2..09e2058e68f00a2ae124a98fe8e3ff6ebca2b1cc 100644 (file)
 #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);
 
index 13af136d573a540e7e8e9b6f77106ecadca3d6a7..6ad8f59116d225d05e1fb1c399c2662d9cb98a0a 100644 (file)
@@ -1,2 +1,2 @@
-status: ERROR
+status: FAIL
 server_msg: b'Authorization-Request failed for caju'
index 38b057b4d484dd3326aae0f9c6a244a96cc8442f..3f5719b89e80f11b008ce2a4e1c24cb95bc68b5a 100644 (file)
@@ -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
        }