]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
tacacs: Add fr_tacacs_decode() function
authorJorge Pereira <jpereiran@gmail.com>
Fri, 14 Aug 2020 15:35:27 +0000 (12:35 -0300)
committerAlan T. DeKok <aland@freeradius.org>
Sat, 15 Aug 2020 10:54:45 +0000 (06:54 -0400)
It replaces the previsous implementation fixing several issues against
the https://tools.ietf.org/id/draft-ietf-opsawg-tacacs-07.html

src/modules/proto_tacacs/proto_tacacs.c
src/protocols/tacacs/decode.c
src/protocols/tacacs/tacacs.h

index 2ccc841fb595d627ff6e6b4a1de73fe99abaeff6..842b914edc3a2bb8b727c183884b47ec153dad30 100644 (file)
@@ -241,7 +241,7 @@ static void tacacs_running(REQUEST *request, fr_state_signal_t action)
 
        switch (request->request_state) {
        case REQUEST_INIT:
-               rc = fr_tacacs_packet_decode(request->packet);
+               rc = fr_tacacs_packet_decode(request->packet, request->client->secret, talloc_array_length(request->client->secret) - 1);
                if (rc == -2)   /* client abort no reply */
                        goto done;
                else if (rc < 0) {
index 7ec912f16d47c5ea573bacf90ff6d85df4203364..ee5c1902fd453967101c269e773cd0b807521d0f 100644 (file)
  * @copyright 2017 The FreeRADIUS server project
  * @copyright 2017 Network RADIUS SARL (legal@networkradius.com)
  */
-#include <freeradius-devel/util/base.h>
-#include <freeradius-devel/util/net.h>
+
+#include <freeradius-devel/io/test_point.h>
+#include <freeradius-devel/protocol/tacacs/dictionary.h>
 #include <freeradius-devel/server/base.h>
 #include <freeradius-devel/server/log.h>
+#include <freeradius-devel/util/base.h>
 #include <freeradius-devel/util/debug.h>
-#include <freeradius-devel/protocol/tacacs/dictionary.h>
+#include <freeradius-devel/util/net.h>
+#include <freeradius-devel/util/struct.h>
 
 #include "tacacs.h"
 #include "attrs.h"
 
-static int tacacs_decode_field(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *da,
-                              char const *field_name, uint8_t **field_data, size_t field_len, size_t *remaining)
+#define PACKET_HEADER_CHECKER(msg, leastwise) \
+       if (remaining < leastwise) { \
+               fr_strerror_printf(#msg" packet is too small: %d < %d", remaining, leastwise); \
+               return -1; \
+       } \
+       remaining -= leastwise;
+
+#define DECODE_GET_FIELD(attr, field) \
+       vp = fr_pair_afrom_da(ctx, attr); \
+       if (!vp) goto oom; \
+       vp->vp_uint8 = field; \
+       fr_cursor_append(&cursor, vp);
+
+/**
+ *     Decode a TACACS+ 'arg_N' fields.
+ */
+static int tacacs_decode_args(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *da,
+                               uint8_t arg_cnt, uint8_t *arg_body, uint8_t **args_data, uint16_t *remaining)
 {
-       uint8_t *p;
+       char *p = (char *)*args_data;
        VALUE_PAIR *vp;
 
-       p = *field_data;
+       /*
+        *      No one? Just get out!
+        */
+       if (!arg_cnt) return 0;
 
        /*
-        *      This field doesn't exist.  Ignore it.
+        *      Then, do the dirty job...
         */
-       if (!field_len) return 0;
+       *remaining -= arg_cnt;
+
+       for (uint8_t i = 0; i < arg_cnt; i++) {
+               uint8_t arg_len = *(arg_body + i);
+
+               fr_assert(arg_len <= *remaining);
+
+               if (arg_len > *remaining) {
+                       fr_strerror_printf("'%s' length overflows the remaining data in the packet: %d > %d",
+                                       da->name, arg_len, *remaining);
+                       return -1;
+               }
+
+               vp = fr_pair_afrom_da(ctx, da);
+               if (!vp) {
+                       fr_strerror_printf("Out of Memory");
+                       return -1;
+               }
+
+               fr_pair_value_bstrndup(vp, p, arg_len, true);
+               fr_cursor_append(cursor, vp);
+               p          += arg_len;
+               *remaining -= arg_len;
+               *args_data  = (uint8_t *)p;
+       }
 
-       if (*remaining < field_len) {
-               fr_strerror_printf("%s length overflows the remaining data in the packet: %zu > %zu",
-                                  field_name, field_len, *remaining);
+       return 0;
+}
+
+/**
+ *     Decode a TACACS+ field.
+ */
+static int tacacs_decode_field(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *da,
+                               uint8_t **field_data, uint16_t field_len, uint16_t *remaining)
+{
+       uint8_t *p = *field_data;
+       VALUE_PAIR *vp;
+
+       fr_assert(field_len <= *remaining);
+
+       if (field_len > *remaining) {
+               fr_strerror_printf("'%s' length overflows the remaining data in the packet: %d > %d",
+                                  da->name, field_len, *remaining);
                return -1;
        }
 
@@ -58,337 +118,463 @@ static int tacacs_decode_field(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_att
                return -1;
        }
 
-       fr_pair_value_bstrndup(vp, (char const *)p, field_len, true);
-       p += field_len;
-       *remaining -= field_len;
-       fr_cursor_append(cursor, vp);
+       if (field_len) {
+               fr_pair_value_bstrndup(vp, (char const *)p, field_len, true);
+               p          += field_len;
+               *remaining -= field_len;
+               *field_data = p;
+       }
 
-       *field_data = p;
+       fr_cursor_append(cursor, vp);
 
        return 0;
 }
 
-int fr_tacacs_packet_decode(RADIUS_PACKET * const packet)
+/**
+ *     Decode a TACACS+ packet
+ */
+ssize_t fr_tacacs_decode(TALLOC_CTX *ctx, uint8_t const *buffer, size_t buffer_len, UNUSED const uint8_t *original, char const * const secret, size_t secret_len, VALUE_PAIR **vps)
 {
-       int i;
-       fr_tacacs_packet_t *pkt;
-       fr_cursor_t cursor;
-       VALUE_PAIR *vp;
-       uint8_t *p;
-       uint32_t session_id;
-       size_t remaining;
-
-       fr_cursor_init(&cursor, &packet->vps);
+       fr_dict_attr_t const *tlv;
+       fr_tacacs_packet_t   *pkt;
+       fr_cursor_t          cursor;
+       VALUE_PAIR           *vp;
+       uint8_t              *p;
+       uint16_t             remaining;
+       uint8_t              *our_buffer;
 
        /*
-        *      There MUST be at least a TACACS packert header, and
-        *      packet->data_len == sizeof(pkt) + htonl(pkt->length),
-        *      which is enforced in fr_tacacs_packet_recv().
+        *      We need that to decrypt the body content.
         */
-       pkt = (fr_tacacs_packet_t *)packet->data;
-
-       remaining = ntohl(pkt->hdr.length);
-
-       vp = fr_pair_afrom_da(packet, attr_tacacs_version_minor);
-       if (!vp) {
+       our_buffer = talloc_memdup(ctx, buffer, buffer_len);
+       if (!our_buffer) {
        oom:
                fr_strerror_printf("Out of Memory");
                return -1;
        }
-       vp->vp_uint8 = pkt->hdr.ver.minor;
-       fr_cursor_append(&cursor, vp);
 
-       vp = fr_pair_afrom_da(packet, attr_tacacs_packet_type);
-       if (!vp) goto oom;
-       vp->vp_uint8 = pkt->hdr.type;
-       fr_cursor_append(&cursor, vp);
+       /*
+        * 3.4. The TACACS+ Packet Header
+        *
+        * 1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8
+        * +----------------+----------------+----------------+----------------+
+        * |major  | minor  |                |                |                |
+        * |version| version|      type      |     seq_no     |   flags        |
+        * +----------------+----------------+----------------+----------------+
+        * |                                                                   |
+        * |                            session_id                             |
+        * +----------------+----------------+----------------+----------------+
+        * |                                                                   |
+        * |                              length                               |
+        * +----------------+----------------+----------------+----------------+
+        */
+       fr_cursor_init(&cursor, vps);
 
-       packet->code = pkt->hdr.type;
+       /*
+        *      Call the struct encoder to do the actual work.
+        */
+       if (fr_struct_from_network(ctx, &cursor, attr_tacacs_packet, our_buffer, buffer_len, &tlv, NULL, NULL) < 0) {
+               fr_strerror_printf("Problems to decode %s using fr_struct_from_network()", attr_tacacs_packet->name);
+               return -1;
+       }
 
-       vp = fr_pair_afrom_da(packet, attr_tacacs_sequence_number);
-       if (!vp) goto oom;
-       vp->vp_uint8 = pkt->hdr.seq_no;
-       fr_cursor_append(&cursor, vp);
+       pkt       = (fr_tacacs_packet_t *)our_buffer;
+       remaining = ntohl(pkt->hdr.length);
 
-       vp = fr_pair_afrom_da(packet, attr_tacacs_flags);
-       if (!vp) goto oom;
-       vp->vp_uint8 = pkt->hdr.flags;
-       fr_cursor_append(&cursor, vp);
+       /*
+        *      3.6. Encryption
+        */
+       if (pkt->hdr.flags == TAC_PLUS_ENCRYPTED_MULTIPLE_CONNECTIONS_FLAG) {
+               uint8_t *body = (our_buffer + sizeof(fr_tacacs_packet_hdr_t));
 
-       vp = fr_pair_afrom_da(packet, attr_tacacs_session_id);
-       if (!vp) goto oom;
-       vp->vp_uint32 = ntohl(pkt->hdr.session_id);
-       fr_cursor_append(&cursor, vp);
-       session_id = vp->vp_uint32;
+               fr_assert(secret != NULL);
+               fr_assert(secret_len > 0);
+
+               if (!secret || secret_len < 1) {
+                       fr_strerror_printf("Packet is encrypted, but no secret is set.");
+                       return -1;
+               }
+
+               if (fr_tacacs_body_xor(pkt, body, ntohl(pkt->hdr.length), secret, secret_len) < 0) return -1;
+       }
 
-       switch ((tacacs_type_t)pkt->hdr.type) {
+       switch (pkt->hdr.type) {
        case TAC_PLUS_AUTHEN:
-               switch (pkt->hdr.seq_no) {
-               case 1:
-                       if (remaining < 8) {
-                               fr_strerror_printf("Authentication START packet is too small: %zu < 8",
-                                                  remaining);
-                               return -1;
-                       }
-                       remaining -= 8;
+               if (packet_is_authen_start_request(pkt)) {
+                       /**
+                        * 4.1. The Authentication START Packet Body
+                        *
+                        *  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8
+                        * +----------------+----------------+----------------+----------------+
+                        * |    action      |    priv_lvl    |  authen_type   | authen_service |
+                        * +----------------+----------------+----------------+----------------+
+                        * |    user_len    |    port_len    |  rem_addr_len  |    data_len    |
+                        * +----------------+----------------+----------------+----------------+
+                        * |    user ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |    port ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |    rem_addr ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |    data...
+                        * +----------------+----------------+----------------+----------------+
+                        */
 
-                       vp = fr_pair_afrom_da(packet, attr_tacacs_packet_body_type);
-                       if (!vp) goto oom;
-                       vp->vp_uint8 = TACACS_PACKET_BODY_TYPE_START;
-                       fr_cursor_append(&cursor, vp);
+                       PACKET_HEADER_CHECKER("Authentication START", 8)
+                       DECODE_GET_FIELD(attr_tacacs_packet_body_type, TACACS_PACKET_BODY_TYPE_START);
 
                        /*
                         *      Decode 4 octets of various flags.
                         */
-                       vp = fr_pair_afrom_da(packet, attr_tacacs_action);
-                       if (!vp) goto oom;
-                       vp->vp_uint8 = pkt->authen.start.action;
-                       fr_cursor_append(&cursor, vp);
-
-                       vp = fr_pair_afrom_da(packet, attr_tacacs_privilege_level);
-                       if (!vp) goto oom;
-                       vp->vp_uint8 = pkt->authen.start.priv_lvl;
-                       fr_cursor_append(&cursor, vp);
-
-                       vp = fr_pair_afrom_da(packet, attr_tacacs_authentication_type);
-                       if (!vp) goto oom;
-                       vp->vp_uint8 = pkt->authen.start.authen_type;
-                       fr_cursor_append(&cursor, vp);
-
-                       vp = fr_pair_afrom_da(packet, attr_tacacs_authentication_service);
-                       if (!vp) goto oom;
-                       vp->vp_uint8 = pkt->authen.start.authen_service;
-                       fr_cursor_append(&cursor, vp);
+                       DECODE_GET_FIELD(attr_tacacs_action, pkt->authen.start.action);
+                       DECODE_GET_FIELD(attr_tacacs_privilege_level, pkt->authen.start.priv_lvl);
+                       DECODE_GET_FIELD(attr_tacacs_authentication_type, pkt->authen.start.authen_type);
+                       DECODE_GET_FIELD(attr_tacacs_authentication_service, pkt->authen.start.authen_service);
 
                        /*
                         *      Decode 4 fields, based on their "length"
                         */
                        p = pkt->authen.start.body;
 
-                       if (tacacs_decode_field(packet, &cursor, attr_tacacs_user_name, "User",
-                                               &p, pkt->authen.start.user_len, &remaining) < 0) {
-                               return -1;
-                       }
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_user_name, &p,
+                                               pkt->authen.start.user_len, &remaining) < 0) return -1;
 
-                       if (tacacs_decode_field(packet, &cursor, attr_tacacs_client_port, "Port",
-                                               &p, pkt->authen.start.port_len, &remaining) < 0) {
-                               return -1;
-                       }
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_client_port, &p,
+                                               pkt->authen.start.port_len, &remaining) < 0) return -1;
 
-                       if (tacacs_decode_field(packet, &cursor, attr_tacacs_remote_address, "Remote address",
-                                               &p, pkt->authen.start.rem_addr_len, &remaining) < 0) {
-                               return -1;
-                       }
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_remote_address, &p,
+                                               pkt->authen.start.rem_addr_len, &remaining) < 0) return -1;
 
-                       if (tacacs_decode_field(packet, &cursor, attr_tacacs_data, "Data",
-                                               &p, pkt->authen.start.data_len, &remaining) < 0) {
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_data, &p,
+                                               pkt->authen.start.data_len, &remaining) < 0) {
                                return -1;
                        }
-                       break;
+               } else if (packet_is_authen_continue(pkt)) {
+                       /*
+                        * 4.3. The Authentication CONTINUE Packet Body
+                        *
+                        * This packet is sent from the client to the server following the receipt of
+                        * a REPLY packet.
+                        *
+                        *  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8
+                        * +----------------+----------------+----------------+----------------+
+                        * |          user_msg len           |            data_len             |
+                        * +----------------+----------------+----------------+----------------+
+                        * |     flags      |  user_msg ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |    data ...
+                        * +----------------+
+                        */
 
-               default:
-                       if (remaining < 5) {
-                               fr_strerror_printf("Authentication CONTINUE packet is too small: %zu < 5",
-                                                  remaining);
-                               return -1;
-                       }
-                       remaining -= 5;
+                       PACKET_HEADER_CHECKER("Authentication CONTINUE", 5);
+                       DECODE_GET_FIELD(attr_tacacs_packet_body_type, TACACS_PACKET_BODY_TYPE_CONTINUE);
 
-                       vp = fr_pair_afrom_da(packet, attr_tacacs_packet_body_type);
-                       if (!vp) goto oom;
-                       vp->vp_uint8 = TACACS_PACKET_BODY_TYPE_CONTINUE;
-                       fr_cursor_append(&cursor, vp);
+                       /*
+                        *      Decode 2 fields, based on their "length"
+                        */
+                       p = pkt->authen.cont.body;
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_user_message, &p,
+                                       ntohs(pkt->authen.cont.user_msg_len), &remaining) < 0) return -1;
 
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_data, &p,
+                                       ntohs(pkt->authen.cont.data_len), &remaining) < 0) return -1;
 
+                       DECODE_GET_FIELD(attr_tacacs_authentication_flags, pkt->authen.cont.flags);
+               } else if (packet_is_authen_reply(pkt)) {
                        /*
-                        *      Decode 2 fields, based on their 'length'
+                        * 4.2. The Authentication REPLY Packet Body
+                        *
+                        * 1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8
+                        * +----------------+----------------+----------------+----------------+
+                        * |     status     |      flags     |        server_msg_len           |
+                        * +----------------+----------------+----------------+----------------+
+                        * |           data_len              |        server_msg ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |           data ...
+                        * +----------------+----------------+
                         */
-                       p = pkt->authen.cont.body;
 
-                       if (tacacs_decode_field(packet, &cursor, attr_tacacs_user_message, "User message",
-                                               &p, ntohs(pkt->authen.cont.user_msg_len), &remaining) < 0) {
-                               return -1;
-                       }
+                       PACKET_HEADER_CHECKER("Authentication REPLY", 6);
+                       DECODE_GET_FIELD(attr_tacacs_packet_body_type, TACACS_PACKET_BODY_TYPE_REPLY);
 
-                       if (tacacs_decode_field(packet, &cursor, attr_tacacs_data, "Data",
-                                               &p, ntohs(pkt->authen.cont.data_len), &remaining) < 0) {
-                               return -1;
-                       }
+                       DECODE_GET_FIELD(attr_tacacs_authentication_status, pkt->authen.reply.status);
+                       DECODE_GET_FIELD(attr_tacacs_authentication_flags, pkt->authen.reply.flags);
 
                        /*
-                        *      Look at the abort flag after decoding the fields.
+                        *      Decode 2 fields, based on their "length"
                         */
-                       if (pkt->authen.cont.flags & TAC_PLUS_CONTINUE_FLAG_ABORT) {
-                               if (!ntohs(pkt->authen.cont.data_len) ||
-                                   !(vp = fr_cursor_tail(&cursor))) {
-                                       fr_strerror_printf("Client aborted authentication session %u "
-                                                          "with no message", session_id);
-                                       return -2;
-                               }
-
-                               if (ntohs(pkt->authen.cont.data_len) > 128) {
-                                       fr_strerror_printf("Client aborted authentication session %u "
-                                                          "with too long message", session_id);
-                                       return -2;
-                               }
-
-                               fr_strerror_printf("Client aborted authentication session %u with message %s",
-                                                  session_id, vp->vp_strvalue);
-                               return -2;
-                       }
+                       p = pkt->authen.reply.body;
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_server_message, &p,
+                                       htons(pkt->authen.reply.server_msg_len), &remaining) < 0) return -1;
+
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_data, &p,
+                                       htons(pkt->authen.reply.data_len), &remaining) < 0) return -1;
+               } else {
+                       /* Never */
+                       fr_assert(1);
                }
                break;
 
        case TAC_PLUS_AUTHOR:
-               if (remaining < 8) {
-                       fr_strerror_printf("Authorization REQUEST packet is too small: %zu < 8",
-                                          remaining);
-                       return -1;
-               }
-               remaining -= 8;
+               if (packet_is_author_request(pkt)) {
+                       /*
+                        * 5.1. The Authorization REQUEST Packet Body
+                        *
+                        *  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8
+                        * +----------------+----------------+----------------+----------------+
+                        * |  authen_method |    priv_lvl    |  authen_type   | authen_service |
+                        * +----------------+----------------+----------------+----------------+
+                        * |    user_len    |    port_len    |  rem_addr_len  |    arg_cnt     |
+                        * +----------------+----------------+----------------+----------------+
+                        * |   arg_1_len    |   arg_2_len    |      ...       |   arg_N_len    |
+                        * +----------------+----------------+----------------+----------------+
+                        * |   user ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   port ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   rem_addr ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   arg_1 ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   arg_2 ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   arg_N ...
+                        * +----------------+----------------+----------------+----------------+
+                        */
 
-               if (remaining < pkt->author.req.arg_cnt) {
-                       fr_strerror_printf("Authorization REQUEST packet arguments are smaller than arg_ctx: %zu < %u",
-                                          remaining, pkt->author.req.arg_cnt);
-                       return -1;
-               }
-               remaining -= pkt->author.req.arg_cnt;
-
-               /*
-                *      Skip the header and the N arguments.
-                */
-               p = pkt->author.req.body;
-               p += pkt->author.req.arg_cnt;
-
-               /*
-                *      Decode 4 octets of various flags.
-                */
-               vp = fr_pair_afrom_da(packet, attr_tacacs_authentication_method);
-               if (!vp) goto oom;
-               vp->vp_uint8 = pkt->author.req.authen_method;
-               fr_cursor_append(&cursor, vp);
-
-               vp = fr_pair_afrom_da(packet, attr_tacacs_privilege_level);
-               if (!vp) goto oom;
-               vp->vp_uint8 = pkt->author.req.priv_lvl;
-               fr_cursor_append(&cursor, vp);
-
-               vp = fr_pair_afrom_da(packet, attr_tacacs_authentication_type);
-               if (!vp) goto oom;
-               vp->vp_uint8 = pkt->author.req.authen_type;
-               fr_cursor_append(&cursor, vp);
-
-               vp = fr_pair_afrom_da(packet, attr_tacacs_authentication_service);
-               if (!vp) goto oom;
-               vp->vp_uint8 = pkt->author.req.authen_service;
-               fr_cursor_append(&cursor, vp);
-
-               /*
-                *      Decode 3 fields, based on their "length"
-                */
-               if (tacacs_decode_field(packet, &cursor, attr_tacacs_user_name, "User",
-                                       &p, pkt->author.req.user_len, &remaining) < 0) {
-                       return -1;
-               }
+                       PACKET_HEADER_CHECKER("Authorization REQUEST", 8);
 
-               if (tacacs_decode_field(packet, &cursor, attr_tacacs_client_port, "Port",
-                                       &p, pkt->authen.start.port_len, &remaining) < 0) {
-                       return -1;
-               }
+                       vp = fr_pair_afrom_da(ctx, attr_tacacs_packet_body_type);
+                       if (!vp) goto oom;
+                       vp->vp_uint8 = TACACS_PACKET_BODY_TYPE_REQUEST;
+                       fr_cursor_append(&cursor, vp);
 
-               if (tacacs_decode_field(packet, &cursor, attr_tacacs_remote_address, "Remote address",
-                                       &p, pkt->authen.start.rem_addr_len, &remaining) < 0) {
-                       return -1;
-               }
+                       /*
+                        *      Decode 4 octets of various flags.
+                        */
+                       DECODE_GET_FIELD(attr_tacacs_authentication_method, pkt->author.req.authen_method);
+                       DECODE_GET_FIELD(attr_tacacs_privilege_level, pkt->author.req.priv_lvl);
+                       DECODE_GET_FIELD(attr_tacacs_authentication_type, pkt->author.req.authen_type);
+                       DECODE_GET_FIELD(attr_tacacs_authentication_service, pkt->author.req.authen_service);
 
-               /* FIXME fully support arg */
-               p =  pkt->author.req.body;
-               for (i = 0; i < pkt->author.req.arg_cnt; i++) {
-                       if (remaining < p[i]) {
-                               fr_strerror_printf("Authorization REQUEST packet argument %d overflows the packet: %u > %zu",
-                                                  i, p[i], remaining);
-                               return -1;
-                       }
-                       remaining -= p[i];
+                       /*
+                        *      Decode 3 fields, based on their "length"
+                        */
+                       p = (pkt->author.req.body + pkt->author.req.arg_cnt);
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_user_name, &p,
+                                               pkt->author.req.user_len, &remaining) < 0) return -1;
+
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_client_port, &p,
+                                               pkt->author.req.port_len, &remaining) < 0) return -1;
+
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_remote_address, &p,
+                                               pkt->author.req.rem_addr_len, &remaining) < 0) return -1;
+
+                       /*
+                        *      Decode 'arg_N' arguments (horrible format)
+                        */
+                       if (tacacs_decode_args(ctx, &cursor, attr_tacacs_argument_list,
+                                       pkt->author.req.arg_cnt, pkt->author.req.body, &p, &remaining) < 0) return -1;
+               } else if (packet_is_author_response(pkt)) {
+                       /*
+                        * 5.2. The Authorization RESPONSE Packet Body
+                        *
+                        *  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8
+                        * +----------------+----------------+----------------+----------------+
+                        * |    status      |     arg_cnt    |         server_msg len          |
+                        * +----------------+----------------+----------------+----------------+
+                        * +            data_len             |    arg_1_len   |    arg_2_len   |
+                        * +----------------+----------------+----------------+----------------+
+                        * |      ...       |   arg_N_len    |         server_msg ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   data ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   arg_1 ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   arg_2 ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   arg_N ...
+                        * +----------------+----------------+----------------+----------------+
+                        */
+
+                       PACKET_HEADER_CHECKER("Authorization RESPONSE", 6);
+                       DECODE_GET_FIELD(attr_tacacs_packet_body_type, TACACS_PACKET_BODY_TYPE_RESPONSE);
+
+                       /*
+                        *      Decode 1 octets
+                        */
+                       DECODE_GET_FIELD(attr_tacacs_authorization_status, pkt->author.res.status);
+
+                       /*
+                        *      Decode 2 fields, based on their "length"
+                        */
+                       p = (pkt->author.res.body + pkt->author.res.arg_cnt);
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_server_message, &p,
+                                               htons(pkt->author.res.server_msg_len), &remaining) < 0) return -1;
+
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_data, &p,
+                                               htons(pkt->author.res.data_len), &remaining) < 0) return -1;
+
+                       /*
+                        *      Decode 'arg_N' arguments (horrible format)
+                        */
+                       if (tacacs_decode_args(ctx, &cursor, attr_tacacs_argument_list,
+                                       pkt->author.res.arg_cnt, pkt->author.res.body, &p, &remaining) < 0) return -1;
+               } else {
+                       /* never */
+                       fr_assert(1);
                }
                break;
 
        case TAC_PLUS_ACCT:
-               if (remaining < 9) {
-                       fr_strerror_printf("Accounting REQUEST packet is too small: %zu < 9",
-                                          remaining);
-                       return -1;
-               }
-               remaining -= 9;
+               if (packet_is_acct_request(pkt)) {
+                       /**
+                        * 6.1. The Account REQUEST Packet Body
+                        *
+                        1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8
+                        * +----------------+----------------+----------------+----------------+
+                        * |      flags     |  authen_method |    priv_lvl    |  authen_type   |
+                        * +----------------+----------------+----------------+----------------+
+                        * | authen_service |    user_len    |    port_len    |  rem_addr_len  |
+                        * +----------------+----------------+----------------+----------------+
+                        * |    arg_cnt     |   arg_1_len    |   arg_2_len    |      ...       |
+                        * +----------------+----------------+----------------+----------------+
+                        * |   arg_N_len    |    user ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   port ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   rem_addr ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   arg_1 ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   arg_2 ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |   arg_N ...
+                        * +----------------+----------------+----------------+----------------+
+                        */
 
-               if (remaining < pkt->author.req.arg_cnt) {
-                       fr_strerror_printf("Accounting REQUEST packet arguments are smaller than arg_ctx: %zu < %u",
-                                          remaining, pkt->author.req.arg_cnt);
-                       return -1;
-               }
-               remaining -= pkt->author.req.arg_cnt;
-
-               /*
-                *      Skip the header and the N arguments.
-                */
-               p = pkt->acct.req.body;
-               p += pkt->acct.req.arg_cnt;
-
-               /*
-                *      Decode 8 octets of various fields.
-                */
-               vp = fr_pair_afrom_da(packet, attr_tacacs_accounting_flags);
-               if (!vp) goto oom;
-               vp->vp_uint8 = pkt->acct.req.flags;
-               fr_cursor_append(&cursor, vp);
-
-               vp = fr_pair_afrom_da(packet, attr_tacacs_authentication_method);
-               if (!vp) goto oom;
-               vp->vp_uint8 = pkt->acct.req.authen_method;
-               fr_cursor_append(&cursor, vp);
-
-               vp = fr_pair_afrom_da(packet, attr_tacacs_privilege_level);
-               if (!vp) goto oom;
-               vp->vp_uint8 = pkt->acct.req.priv_lvl;
-               fr_cursor_append(&cursor, vp);
-
-               vp = fr_pair_afrom_da(packet, attr_tacacs_authentication_type);
-               if (!vp) goto oom;
-               vp->vp_uint8 = pkt->acct.req.authen_type;
-               fr_cursor_append(&cursor, vp);
-
-               vp = fr_pair_afrom_da(packet, attr_tacacs_authentication_service);
-               if (!vp) goto oom;
-               vp->vp_uint8 = pkt->acct.req.authen_service;
-               fr_cursor_append(&cursor, vp);
-
-               /*
-                *      Decode 3 fields, based on their "length"
-                */
-               if (tacacs_decode_field(packet, &cursor, attr_tacacs_user_name, "User",
-                                       &p, pkt->acct.req.user_len, &remaining) < 0) return -1;
-
-               if (tacacs_decode_field(packet, &cursor, attr_tacacs_client_port, "Port",
-                                       &p, pkt->acct.req.port_len, &remaining) < 0) return -1;
-
-               if (tacacs_decode_field(packet, &cursor, attr_tacacs_remote_address, "Remote address",
-                                       &p, pkt->acct.req.rem_addr_len, &remaining) < 0) return -1;
-
-               /* FIXME fully support arg */
-               p =  pkt->acct.req.body;
-               for (i = 0; i < pkt->acct.req.arg_cnt; i++) {
-                       if (remaining < p[i]) {
-                               fr_strerror_printf("Accounting REQUEST packet argument %d overflows the packet: %u > %zu",
-                                                  i, p[i], remaining);
-                               return -1;
-                       }
-                       remaining -= p[i];
+                       PACKET_HEADER_CHECKER("Accounting REQUEST", 9);
+                       DECODE_GET_FIELD(attr_tacacs_packet_body_type, TACACS_PACKET_BODY_TYPE_REQUEST);
+
+                       /*
+                        *      Decode 5 octets of various flags.
+                        */
+                       DECODE_GET_FIELD(attr_tacacs_accounting_flags, pkt->acct.req.flags);
+                       DECODE_GET_FIELD(attr_tacacs_authentication_method, pkt->acct.req.authen_method);
+                       DECODE_GET_FIELD(attr_tacacs_privilege_level, pkt->acct.req.priv_lvl);
+                       DECODE_GET_FIELD(attr_tacacs_authentication_type, pkt->acct.req.authen_type);
+                       DECODE_GET_FIELD(attr_tacacs_authentication_service, pkt->acct.req.authen_service);
+
+                       /*
+                        *      Decode 3 fields, based on their "length"
+                        */
+                       p = (pkt->acct.req.body + pkt->acct.req.arg_cnt);
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_user_name, &p,
+                                               pkt->acct.req.user_len, &remaining) < 0) return -1;
+
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_client_port, &p,
+                                               pkt->acct.req.port_len, &remaining) < 0) return -1;
+
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_remote_address, &p,
+                                               pkt->acct.req.rem_addr_len, &remaining) < 0) return -1;
+
+                       /*
+                        *      Decode 'arg_N' arguments (horrible format)
+                        */
+                       if (tacacs_decode_args(ctx, &cursor, attr_tacacs_argument_list,
+                                       pkt->acct.req.arg_cnt, pkt->acct.req.body, &p, &remaining) < 0) return -1;
+               } else if (packet_is_acct_reply(pkt)) {
+                       /**
+                        * 6.2. The Accounting REPLY Packet Body
+                        *
+                        * 1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8  1 2 3 4 5 6 7 8
+                        * +----------------+----------------+----------------+----------------+
+                        * |         server_msg len          |            data_len             |
+                        * +----------------+----------------+----------------+----------------+
+                        * |     status     |         server_msg ...
+                        * +----------------+----------------+----------------+----------------+
+                        * |     data ...
+                        * +----------------+
+                        */
+
+                       PACKET_HEADER_CHECKER("Accounting REPLY", 5);
+                       DECODE_GET_FIELD(attr_tacacs_packet_body_type, TACACS_PACKET_BODY_TYPE_REPLY);
+
+                       /*
+                        *      Decode 2 fields, based on their "length"
+                        */
+                       p = pkt->acct.reply.body;
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_server_message, &p,
+                                       htons(pkt->acct.reply.server_msg_len), &remaining) < 0) return -1;
+
+                       if (tacacs_decode_field(ctx, &cursor, attr_tacacs_data, &p,
+                                       htons(pkt->acct.reply.data_len), &remaining) < 0) return -1;
+
+                       /* Decode 1 octet */
+                       vp = fr_pair_afrom_da(ctx, attr_tacacs_accounting_status);
+                       if (!vp) goto oom;
+                       vp->vp_uint8 = pkt->acct.reply.status;
+                       fr_cursor_append(&cursor, vp);
+               } else {
+                       /* never */
+                       fr_assert(1);
                }
                break;
        default:
-               fr_strerror_printf("Unsupported TACACS+ type %u", pkt->hdr.type);
+               fr_strerror_printf("decode: Unsupported TACACS+ type %u", pkt->hdr.type);
                return -1;
        }
 
+       fr_assert(remaining == 0); /* Good enough */
+
+       return buffer_len;
+}
+
+/*
+ *     Test points for protocol decode
+ */
+static ssize_t fr_tacacs_decode_proto(TALLOC_CTX *ctx, VALUE_PAIR **vps, uint8_t const *data, size_t data_len, void *proto_ctx)
+{
+       fr_tacacs_ctx_t *test_ctx = talloc_get_type_abort(proto_ctx, fr_tacacs_ctx_t);
+
+       return fr_tacacs_decode(ctx, data, data_len, NULL, test_ctx->secret, (talloc_array_length(test_ctx->secret)-1), vps);
+}
+
+static int _encode_test_ctx(fr_tacacs_ctx_t *proto_ctx)
+{
+       talloc_const_free(proto_ctx->secret);
+
+       fr_tacacs_free();
+
        return 0;
 }
+
+static int decode_test_ctx(void **out, TALLOC_CTX *ctx)
+{
+       fr_tacacs_ctx_t *test_ctx;
+
+       if (fr_tacacs_init() < 0) return -1;
+
+       test_ctx = talloc_zero(ctx, fr_tacacs_ctx_t);
+       if (!test_ctx) return -1;
+
+       test_ctx->secret = talloc_strdup(test_ctx, "testing123");
+       test_ctx->root = fr_dict_root(dict_tacacs);
+       talloc_set_destructor(test_ctx, _encode_test_ctx);
+
+       *out = test_ctx;
+
+       return 0;
+}
+
+extern fr_test_point_proto_decode_t tacacs_tp_decode_proto;
+fr_test_point_proto_decode_t tacacs_tp_decode_proto = {
+       .test_ctx       = decode_test_ctx,
+       .func           = fr_tacacs_decode_proto
+};
index 6bdede70ac1f236a0169a6ff99116571cda3ddbe..90999e47ba4a45adf5a22b08b87b8eea9836c9bc 100644 (file)
@@ -279,7 +279,7 @@ typedef struct {
 ssize_t fr_tacacs_encode(uint8_t *buffer, size_t buffer_len, char const * const secret, size_t secret_len, VALUE_PAIR *vps);
 
 /* decode.c */
-int fr_tacacs_packet_decode(RADIUS_PACKET * const packet);
+ssize_t fr_tacacs_decode(TALLOC_CTX *ctx, uint8_t const *buffer, size_t buffer_len, UNUSED const uint8_t *original, char const * const secret, size_t secret_len, VALUE_PAIR **vps);
 
 /* base.c */
 int            fr_tacacs_init(void);