]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
tacacs: Add fr_tacacs_encode() function
authorJorge Pereira <jpereiran@gmail.com>
Fri, 14 Aug 2020 15:34:57 +0000 (12:34 -0300)
committerAlan T. DeKok <aland@freeradius.org>
Sat, 15 Aug 2020 10:54:44 +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/protocols/tacacs/encode.c
src/protocols/tacacs/tacacs.h

index 4a6963330c19f74ee2232fcbd666d8d8c61246f7..030b7cd60bee6b5a23e86e3b5cb867e71ca73473 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"
 
-int fr_tacacs_packet_encode(RADIUS_PACKET * const packet, char const * const secret, UNUSED size_t secret_len)
+/**
+ *     Encode a TACACS+ 'arg_N' fields.
+ */
+static size_t tacacs_encode_body_arg_n_len(VALUE_PAIR *vps, fr_dict_attr_t const *da, uint8_t **body)
+{
+       VALUE_PAIR *vp;
+       fr_cursor_t     cursor;
+       size_t  arg_cnt = 0;
+       uint8_t *p = *body;
+
+       fr_assert(body != NULL);
+
+       for (vp = fr_cursor_init(&cursor, &vps);
+            vp;
+            vp = fr_cursor_next(&cursor)) {
+
+               if (vp->da != da) continue;
+
+               fr_assert(vp->vp_length <= 0xff);
+
+               if (vp->vp_length > 0xff) {
+                       fr_strerror_printf("The TACACS+ attribute %s overflows the values (%ld > 255)",
+                               da->name, vp->vp_length);
+                       return -1;
+               }
+
+               /* Append the <arg_N_len> fields length */
+               *p++     = vp->vp_length;
+               arg_cnt += 1;
+       }
+
+       if (!arg_cnt) return 0;
+
+       *body = p;
+
+       return arg_cnt;
+}
+
+static size_t tacacs_encode_body_arg_n(VALUE_PAIR *vps, fr_dict_attr_t const *da, uint8_t **body)
+{
+       VALUE_PAIR *vp;
+       fr_cursor_t     cursor;
+       size_t body_args_len = 0;
+
+       fr_assert(*body != NULL);
+
+       for (vp = fr_cursor_init(&cursor, &vps);
+            vp;
+            vp = fr_cursor_next(&cursor)) {
+
+               if (vp->da != da) continue;
+
+               /* Append the <arg_N> fields */
+               memcpy(*body + body_args_len, vp->vp_strvalue, vp->vp_length);
+               body_args_len += vp->vp_length;
+       }
+
+       return body_args_len;
+}
+
+/*
+ *     Encode a TACACS+ field.
+ */
+static ssize_t tacacs_encode_field(VALUE_PAIR *vps, fr_dict_attr_t const *da, uint8_t **field_data, uint16_t *body_len, size_t max_len)
+{
+       VALUE_PAIR *vp;
+
+       vp = fr_pair_find_by_da(vps, da, TAG_ANY);
+       if (!vp) return 0;
+
+       fr_assert(vp->vp_length <= max_len);
+
+       if (vp->vp_length > max_len) {
+               fr_strerror_printf("The TACACS+ attribute %s overflows the values (%ld > %ld)",
+                       da->name, vp->vp_length, max_len);
+               return -1;
+       }
+
+       if (vp->vp_length > 0) {
+               uint8_t *p = *field_data;
+
+               memcpy(p, vp->vp_strvalue, vp->vp_length);
+               p          += vp->vp_length;
+               *field_data = p;
+               *body_len  += vp->vp_length;
+       }
+
+       return vp->vp_length;
+}
+
+/**
+ *     Encode VPS into a raw TACACS packet.
+ */
+ssize_t fr_tacacs_encode(uint8_t *buffer, size_t buffer_len, char const * const secret, size_t secret_len, VALUE_PAIR *vps)
 {
-       uint8_t                 *ptr;
-       uint16_t                length_hdr;
-       uint16_t                length_body;
-       VALUE_PAIR const        *vp;
+       VALUE_PAIR              *vp;
+       fr_tacacs_packet_t      *packet;
        fr_cursor_t             cursor;
-       fr_tacacs_packet_t      *pkt;
-       struct {
-               VALUE_PAIR const        *server_msg;
-               VALUE_PAIR const        *data;
-       } field = {0};
+       fr_da_stack_t           da_stack;
+       uint8_t                 *p;
+       uint16_t                length_hdr = 0;
+       uint16_t                length_body = 0;
+       ssize_t                 len = 0;
+       size_t                  packet_len = 0;
 
-       uint8_t                 status = 0;
+       if (!vps) {
+               fr_strerror_printf("Cannot encode empty packet");
+               return -1;
+       }
 
-       tacacs_authen_reply_flags_t authen_reply_flags = TAC_PLUS_REPLY_FLAG_UNSET;
+       /*
+        *      Find the first attribute which is parented by TACACS-Packet.
+        */
+       for (vp = fr_cursor_init(&cursor, &vps);
+            vp;
+            vp = fr_cursor_next(&cursor)) {
+               if (vp->da->parent == attr_tacacs_packet) break;
+       }
 
        /*
-        *      Not zero'd to catch bugs via tacacs_packet_verify()
+        *      For simplicity, we allow the caller to omit things
+        *      that they don't care about.
         */
-       uint8_t data[sizeof(fr_tacacs_packet_t)];
+       if (!vp) {
+               fr_strerror_printf("No TACACS+ %s in the attribute list",
+                       attr_tacacs_packet->name);
+               return -1;
+       }
+
+       fr_proto_da_stack_build(&da_stack, attr_tacacs_packet);
+       FR_PROTO_STACK_PRINT(&da_stack, 0);
 
        /*
-        *      Use memory on the stack, until we know how
-        *      large the packet will be.
+        *      Call the struct encoder to do the actual work.
         */
-       pkt = (fr_tacacs_packet_t *)data;
+       length_hdr = fr_struct_to_network(buffer, buffer_len, &da_stack, 0, &cursor, NULL, NULL);
 
-       pkt->hdr.ver.major = TAC_PLUS_MAJOR_VER;
+       fr_assert(length_hdr == sizeof(fr_tacacs_packet_hdr_t));
 
-       pkt->hdr.flags = (secret)
-               ? TAC_PLUS_ENCRYPTED_MULTIPLE_CONNECTIONS_FLAG
-               : TAC_PLUS_UNENCRYPTED_FLAG;
+       if (length_hdr != sizeof(fr_tacacs_packet_hdr_t)) {
+               fr_strerror_printf("Problems to encode %s using fr_struct_to_network()",
+                                       attr_tacacs_packet->name);
+               return -1;
+       }
 
-       length_body = 0;
-       for (vp = fr_cursor_init(&cursor, &packet->vps);
-            vp != NULL;
-            vp = fr_cursor_next(&cursor)) {
-               VP_VERIFY(vp);
-
-               if (!vp->da->flags.internal) continue;
-
-               if (vp->da == attr_tacacs_version_minor) {
-                       pkt->hdr.ver.minor = vp->vp_uint8;
-               } else if (vp->da == attr_tacacs_packet_type) {
-                       pkt->hdr.type = vp->vp_uint8;
-               } else if (vp->da == attr_tacacs_sequence_number) {
-                       pkt->hdr.seq_no = vp->vp_uint8;
-               } else if (vp->da == attr_tacacs_session_id) {
-                       pkt->hdr.session_id = htonl(vp->vp_uint32);
-               } else if (vp->da == attr_tacacs_authentication_status) {
-                       pkt->authen.reply.status = vp->vp_uint8;
-                       status = vp->vp_uint8;
-               } else if (vp->da == attr_tacacs_authentication_flags) {
-                       authen_reply_flags |= vp->vp_uint8;
-               } else if (vp->da == attr_tacacs_authorization_status) {
-                       pkt->author.res.status = vp->vp_uint8;
-                       status = vp->vp_uint8;
-               } else if (vp->da == attr_tacacs_accounting_status) {
-                       pkt->acct.res.status = vp->vp_uint8;
-                       status = vp->vp_uint8;
-               } else if (vp->da == attr_tacacs_server_message) {
-                       length_body += vp->vp_length;
-                       field.server_msg = vp;
-               } else if (vp->da == attr_tacacs_data) {
-                       length_body += vp->vp_length;
-                       field.data = vp;
+       /*
+        *      Handle directly in the allocated buffer
+        */
+       packet = (fr_tacacs_packet_t *)buffer;
+
+       /*
+        *      Encode 8 octets of various fields not members of STRUCT
+        */
+       switch (packet->hdr.type) {
+       case TAC_PLUS_AUTHEN:
+               /*
+                * seq_no
+                *
+                * This is the sequence number of the current packet for the current session.
+                * The first packet in a session MUST have the sequence number 1 and each
+                * subsequent packet will increment the sequence number by one. Thus clients
+                * only send packets containing odd sequence numbers, and TACACS+ servers only
+                * send packets containing even sequence numbers.
+                */
+               if (packet_is_authen_start_request(packet)) { /* Start */
+                       /**
+                        * 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...
+                        * +----------------+----------------+----------------+----------------+
+                        */
+
+                       length_hdr += offsetof(fr_tacacs_packet_authen_start_hdr_t, body);
+
+                       /*
+                        *      Encode 4 octets of various flags.
+                        */
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_action, TAG_ANY);
+                       if (!vp) {
+                       invalid_authen_req:
+                               fr_strerror_printf("Invalid TACACS+ Authentication Request packet");
+                               return -1;
+                       }
+                       packet->authen.start.action = vp->vp_uint8;
+
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_privilege_level, TAG_ANY);
+                       if (!vp) goto invalid_authen_req;
+                       packet->authen.start.priv_lvl = vp->vp_uint8;
+
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_authentication_type, TAG_ANY);
+                       if (!vp) goto invalid_authen_req;
+                       packet->authen.start.authen_type = vp->vp_uint8;
+
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_authentication_service, TAG_ANY);
+                       if (!vp) goto invalid_authen_req;
+                       packet->authen.start.authen_service = vp->vp_uint8;
+
+                       /*
+                        *      Encode 4 fields, based on their "length"
+                        */
+                       p = packet->authen.start.body;
+
+                       len = tacacs_encode_field(vps, attr_tacacs_user_name, &p, &length_body, 0xff);
+                       if (len < 0) goto invalid_authen_req;
+                       packet->authen.start.user_len = len;
+
+                       len = tacacs_encode_field(vps, attr_tacacs_client_port, &p, &length_body, 0xff);
+                       if (len < 0) goto invalid_authen_req;
+                       packet->authen.start.port_len = len;
+
+                       len = tacacs_encode_field(vps, attr_tacacs_remote_address, &p, &length_body, 0xff);
+                       if (len < 0) goto invalid_authen_req;
+                       packet->authen.start.rem_addr_len = len;
+
+                       len = tacacs_encode_field(vps, attr_tacacs_data, &p, &length_body, 0xff);
+                       if (len < 0) goto invalid_authen_req;
+                       packet->authen.start.data_len = len;
+               } else if (packet_is_authen_continue(packet)) {
+                       /*
+                        * 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 ...
+                        * +----------------+
+                        */
+
+                       length_hdr += offsetof(fr_tacacs_packet_authen_cont_hdr_t, body);
+
+                       /*
+                        *      Encode 2 fields, based on their 'length'
+                        */
+                       p = packet->authen.cont.body;
+
+                       len = tacacs_encode_field(vps, attr_tacacs_user_message, &p, &length_body, 0xffff);
+                       if (len < 0) {
+                       invalid_authen_cont:
+                               fr_strerror_printf("Invalid TACACS+ Authentication Continue packet");
+                               return -1;
+                       }
+                       packet->authen.cont.user_msg_len = htons(len);
+
+                       len = tacacs_encode_field(vps, attr_tacacs_data, &p, &length_body, 0xffff);
+                       if (len < 0) goto invalid_authen_cont;
+                       packet->authen.cont.data_len = htons(len);
+
+                       /*
+                        *      Look at the abort flag after decoding the fields.
+                        */
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_authentication_flags, TAG_ANY);
+                       if (!vp) goto invalid_authen_cont;
+                       packet->authen.cont.flags = vp->vp_uint8;
+               } else if (packet_is_authen_reply(packet)) {
+                       /*
+                        * 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 ...
+                        * +----------------+----------------+
+                        */
+
+                       length_hdr += offsetof(fr_tacacs_packet_authen_reply_hdr_t, body);
+
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_authentication_status, TAG_ANY);
+                       if (!vp) {
+                       invalid_authen_reply:
+                               fr_strerror_printf("Invalid TACACS+ Authorization Reply packet");
+                               return -1;
+                       }
+                       packet->authen.reply.status = vp->vp_uint8;
+
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_authentication_flags, TAG_ANY);
+                       if (!vp) goto invalid_authen_reply;
+                       packet->authen.reply.flags = vp->vp_uint8;
+
+                       /*
+                        *      Encode 2 fields, based on their 'length'
+                        */
+                       p = packet->authen.reply.body;
+
+                       len = tacacs_encode_field(vps, attr_tacacs_server_message, &p, &length_body, 0xffff);
+                       if (len < 0) goto invalid_authen_reply;
+                       packet->authen.reply.server_msg_len = htons(len);
+
+                       len = tacacs_encode_field(vps, attr_tacacs_data, &p, &length_body, 0xffff);
+                       if (len < 0) goto invalid_authen_reply;
+                       packet->authen.reply.data_len = htons(len);
                } else {
-                       WARN("Unhandled %s", vp->da->name);
+                       /* Never */
+                       fr_assert(1);
                }
-       }
 
-       length_hdr = sizeof(fr_tacacs_packet_hdr_t);
-       switch (pkt->hdr.type) {
-       case TAC_PLUS_AUTHEN:
-               length_hdr += offsetof(fr_tacacs_packet_authen_reply_hdr_t, body);
-               pkt->authen.reply.flags = authen_reply_flags;
-               pkt->authen.reply.server_msg_len = htons(0);
-               pkt->authen.reply.data_len = htons(0);
                break;
 
        case TAC_PLUS_AUTHOR:
-               length_hdr += offsetof(fr_tacacs_packet_author_res_hdr_t, body);
-               pkt->author.res.arg_cnt = 0;
-               pkt->author.res.server_msg_len = htons(0);
-               pkt->author.res.data_len = htons(0);
+               if (packet_is_author_request(packet)) {
+                       /*
+                        * 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 ...
+                        * +----------------+----------------+----------------+----------------+
+                        */
+
+                       length_hdr += offsetof(fr_tacacs_packet_author_req_hdr_t, body);
+
+                       /*
+                        *      Encode 4 octets of various flags.
+                        */
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_authentication_method, TAG_ANY);
+                       if (!vp) {
+                       invalid_author_req:
+                               fr_strerror_printf("Invalid TACACS+ Authorization REQUEST packet");
+                               return -1;
+                       }
+                       packet->author.req.authen_method = vp->vp_uint8;
+
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_privilege_level, TAG_ANY);
+                       if (!vp) goto invalid_author_req;
+                       packet->author.req.priv_lvl = vp->vp_uint8;
+
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_authentication_type, TAG_ANY);
+                       if (!vp) goto invalid_author_req;
+                       packet->author.req.authen_type = vp->vp_uint8;
+
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_authentication_service, TAG_ANY);
+                       if (!vp) goto invalid_author_req;
+                       packet->author.req.authen_service = vp->vp_uint8;
+
+                       /*
+                        *      Encode 'arg_N' arguments (horrible format)
+                        */
+                       p = packet->author.req.body;
+
+                       len = tacacs_encode_body_arg_n_len(vps, attr_tacacs_argument_list, &p);
+                       if (len < 0) goto invalid_author_req;
+                       packet->author.req.arg_cnt = len;
+
+                       /*
+                        *      Encode 3 fields, based on their "length"
+                        */
+                       len = tacacs_encode_field(vps, attr_tacacs_user_name, &p, &length_body, 0xffff);
+                       if (len < 0) goto invalid_author_req;
+                       packet->author.req.user_len = len;
+
+                       len = tacacs_encode_field(vps, attr_tacacs_client_port, &p, &length_body, 0xffff);
+                       if (len < 0) goto invalid_author_req;
+                       packet->author.req.port_len = len;
+
+                       len = tacacs_encode_field(vps, attr_tacacs_remote_address, &p, &length_body, 0xffff);
+                       if (len < 0) goto invalid_author_req;
+                       packet->author.req.rem_addr_len = len;
+
+                       /*
+                        * Append 'args_body' to the end of buffer
+                        */
+                       if (packet->author.req.arg_cnt > 0) {
+                               length_body += (packet->author.req.arg_cnt + tacacs_encode_body_arg_n(vps, attr_tacacs_argument_list, &p));
+                       }
+               } else if (packet_is_author_response(packet)) {
+                       /*
+                        * 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 ...
+                        * +----------------+----------------+----------------+----------------+
+                        */
+
+                       length_hdr += offsetof(fr_tacacs_packet_author_res_hdr_t, body);
+
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_authorization_status, TAG_ANY);
+                       if (!vp) {
+                       invalid_author_res:
+                               fr_strerror_printf("Invalid TACACS+ Authorization Response packet");
+                               return -1;
+                       }
+                       packet->author.res.status = vp->vp_uint8;
+
+                       /*
+                        *      Encode 'arg_N' arguments (horrible format)
+                        */
+                       p = packet->author.res.body;
+                       len = tacacs_encode_body_arg_n_len(vps, attr_tacacs_argument_list, &p);
+                       if (len < 0) goto invalid_author_res;
+                       packet->author.res.arg_cnt = len;
+
+                       /*
+                        *      Encode 2 fields, based on their "length"
+                        */
+                       len = tacacs_encode_field(vps, attr_tacacs_server_message, &p, &length_body, 0xffff);
+                       if (len < 0) goto invalid_author_res;
+                       packet->author.res.server_msg_len = len;
+
+                       len = tacacs_encode_field(vps, attr_tacacs_data, &p, &length_body, 0xffff);
+                       if (len < 0) goto invalid_author_res;
+                       packet->author.res.data_len = len;
+
+                       /*
+                        * Append 'args_body' to the end of buffer
+                        */
+                       if (packet->author.res.arg_cnt > 0) {
+                               length_body += (packet->author.res.arg_cnt + tacacs_encode_body_arg_n(vps, attr_tacacs_argument_list, &p));
+                       }
+               } else {
+                       /* never */
+                       fr_assert(1);
+               }
+
                break;
 
        case TAC_PLUS_ACCT:
-               length_hdr += offsetof(fr_tacacs_packet_acct_res_hdr_t, body);
-               pkt->acct.res.server_msg_len = htons(0);
-               pkt->acct.res.data_len = htons(0);
+               if (packet_is_acct_request(packet)) {
+                       /**
+                        * 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 ...
+                        * +----------------+----------------+----------------+----------------+
+                        */
+
+                       length_hdr += offsetof(fr_tacacs_packet_acct_req_hdr_t, body);
+
+                       /*
+                        *      Encode 5 octets of various flags.
+                        */
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_accounting_flags, TAG_ANY);
+                       if (!vp) {
+                       invalid_acct_req:
+                               fr_strerror_printf("Invalid TACACS+ Accounting REQUEST packet");
+                               return -1;
+                       }
+                       packet->acct.req.flags = vp->vp_uint8;
+
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_authentication_method, TAG_ANY);
+                       if (!vp) goto invalid_acct_req;
+                       packet->acct.req.authen_method = vp->vp_uint8;
+
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_privilege_level, TAG_ANY);
+                       if (!vp) goto invalid_acct_req;
+                       packet->acct.req.priv_lvl = vp->vp_uint8;
+
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_authentication_type, TAG_ANY);
+                       if (!vp) goto invalid_acct_req;
+                       packet->acct.req.authen_type = vp->vp_uint8;
+
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_authentication_service, TAG_ANY);
+                       if (!vp) goto invalid_acct_req;
+                       packet->acct.req.authen_service = vp->vp_uint8;
+
+                       /*
+                        *      Encode 'arg_N' arguments (horrible format)
+                        */
+                       p = packet->acct.req.body;
+
+                       len = tacacs_encode_body_arg_n_len(vps, attr_tacacs_argument_list, &p);
+                       if (len < 0) goto invalid_acct_req;
+                       packet->acct.req.arg_cnt = len;
+
+                       /*
+                        *      Encode 3 fields, based on their "length"
+                        */
+                       len = tacacs_encode_field(vps, attr_tacacs_user_name, &p, &length_body, 0xffff);
+                       if (len < 0) goto invalid_acct_req;
+                       packet->acct.req.user_len = len;
+
+                       len = tacacs_encode_field(vps, attr_tacacs_client_port, &p, &length_body, 0xffff);
+                       if (len < 0) goto invalid_acct_req;
+                       packet->acct.req.port_len = len;
+
+                       len = tacacs_encode_field(vps, attr_tacacs_remote_address, &p, &length_body, 0xffff);
+                       if (len < 0) goto invalid_acct_req;
+                       packet->acct.req.rem_addr_len = len;
+
+                       /*
+                        * Append 'args_body' to the end of buffer
+                        */
+                       if (packet->acct.req.arg_cnt > 0) {
+                               length_body += (packet->acct.req.arg_cnt + tacacs_encode_body_arg_n(vps, attr_tacacs_argument_list, &p));
+                       }
+               } else if (packet_is_acct_reply(packet)) {
+                       /**
+                        * 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 ...
+                        * +----------------+
+                        */
+
+                       length_hdr += offsetof(fr_tacacs_packet_acct_reply_hdr_t, body);
+
+                       /*
+                        *      Encode 2 fields, based on their 'length'
+                        */
+                       p = packet->acct.reply.body;
+
+                       len = tacacs_encode_field(vps, attr_tacacs_server_message, &p, &length_body, 0xffff);
+                       if (len < 0) {
+                       invalid_acct_reply:
+                               fr_strerror_printf("Invalid TACACS+ Accounting Reply packet");
+                               return -1;
+                       }
+                       packet->acct.reply.server_msg_len = htons(len);
+
+                       len = tacacs_encode_field(vps, attr_tacacs_data, &p, &length_body, 0xffff);
+                       if (len < 0) goto invalid_acct_reply;
+                       packet->acct.reply.data_len = htons(len);
+
+                       vp = fr_pair_find_by_da(vps, attr_tacacs_accounting_status, TAG_ANY);
+                       if (!vp) goto invalid_acct_reply;
+                       packet->acct.reply.status = vp->vp_uint8;
+               } else {
+                       /* never */
+                       fr_assert(1);
+               }
                break;
 
-       /* unsupported type as per draft-ietf-opsawg-tacacs section 3.6 */
        default:
-fail:
-               length_hdr = sizeof(fr_tacacs_packet_hdr_t);
-               length_body = 0;
-               goto cook;
+               fr_strerror_printf("encode: TACACS+ type %u", packet->hdr.type);
+               return -1;
        }
 
-       /* if status is unset then send failure to client */
-       if (!status)
-               goto fail;
-
-cook:
        fr_assert(length_hdr + length_body < TACACS_MAX_PACKET_SIZE);
 
-       pkt->hdr.length = htonl(length_hdr - sizeof(fr_tacacs_packet_hdr_t) + length_body);
+       packet->hdr.length = htonl(length_hdr - sizeof(fr_tacacs_packet_hdr_t) + length_body);
+       packet_len = (length_hdr + length_body);
+
+       if (packet_len > buffer_len) {
+               fr_strerror_printf("TACACS+ packet overflows %ld of %ld bytes",
+                               (buffer_len - packet_len), buffer_len);
+               return -1;
+       }
 
        /*
-        *      Fill in the rest of the fields, and copy the data over
-        *      from the local stack to the newly allocated memory.
-        *
-        *      Yes, all this 'memcpy' is slow, but it means
-        *      that we only allocate the minimum amount of
-        *      memory for a request.
+        *      3.6. Encryption
         */
-       packet->data_len = length_hdr + length_body;
-       packet->data = talloc_array(packet, uint8_t, packet->data_len);
-       if (!packet->data) {
-               fr_strerror_printf("Out of memory");
-               return -1;
+       if (packet->hdr.flags == TAC_PLUS_ENCRYPTED_MULTIPLE_CONNECTIONS_FLAG) {
+               uint8_t *body = (buffer + sizeof(fr_tacacs_packet_hdr_t));
+
+               fr_assert(secret != NULL);
+               fr_assert(secret_len > 0);
+
+               if (!secret || secret_len < 1) {
+                       fr_strerror_printf("Packet is supposed to be encrypted, but no secret is set.");
+                       return -1;
+               }
+
+               if (fr_tacacs_body_xor(packet, body, ntohl(packet->hdr.length), secret, secret_len) != 0) return -1;
        }
 
-       memcpy(packet->data, pkt, length_hdr);
+       FR_PROTO_HEX_DUMP((const uint8_t *)packet, packet_len, "fr_tacacs_packet_t (encoded)");
 
-       pkt = (fr_tacacs_packet_t *)packet->data;
+       return packet_len;
+}
 
-       ptr = packet->data;
-       ptr += length_hdr;
+/*
+ *     Test points for protocol encode
+ */
+static ssize_t fr_tacacs_encode_proto(UNUSED TALLOC_CTX *ctx, VALUE_PAIR *vps, uint8_t *data, size_t data_len, UNUSED void *proto_ctx)
+{
+       fr_tacacs_ctx_t *test_ctx = talloc_get_type_abort(proto_ctx, fr_tacacs_ctx_t);
 
-       if (!length_body) goto skip_fields;
+       return fr_tacacs_encode(data, data_len, test_ctx->secret, (talloc_array_length(test_ctx->secret)-1), vps);
+}
 
-       if (field.server_msg) {
-               switch (pkt->hdr.type) {
-               default:
-                       fr_strerror_printf("Invalid packet type %i", pkt->hdr.type);
-                       talloc_free(packet->data);
-                       return -1;
+static int _encode_test_ctx(fr_tacacs_ctx_t *proto_ctx)
+{
+       talloc_const_free(proto_ctx->secret);
 
-               case TAC_PLUS_AUTHEN:
-                       pkt->authen.reply.server_msg_len = htons(field.server_msg->vp_length);
-                       break;
-               case TAC_PLUS_AUTHOR:
-                       pkt->author.res.server_msg_len = htons(field.server_msg->vp_length);
-                       break;
-               case TAC_PLUS_ACCT:
-                       pkt->acct.res.server_msg_len = htons(field.server_msg->vp_length);
-                       break;
-               }
-               memcpy(ptr, field.server_msg->vp_octets, field.server_msg->vp_length);
-               ptr += field.server_msg->vp_length;
-       }
+       fr_tacacs_free();
 
-       if (field.data) {
-               switch (pkt->hdr.type) {
-               default:
-                       fr_assert(0);
-                       break;
-
-               case TAC_PLUS_AUTHEN:
-                       pkt->authen.reply.data_len = htons(field.data->vp_length);
-                       break;
-               case TAC_PLUS_AUTHOR:
-                       pkt->author.res.data_len = htons(field.data->vp_length);
-                       break;
-               case TAC_PLUS_ACCT:
-                       pkt->acct.res.data_len = htons(field.data->vp_length);
-                       break;
-               }
-               memcpy(ptr, field.data->vp_octets, field.data->vp_length);
-       }
+       return 0;
+}
+
+static int encode_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;
 
-skip_fields:
        return 0;
 }
+
+/*
+ *     Test points
+ */
+extern fr_test_point_proto_encode_t tacacs_tp_encode_proto;
+fr_test_point_proto_encode_t tacacs_tp_encode_proto = {
+       .test_ctx       = encode_test_ctx,
+       .func           = fr_tacacs_encode_proto
+};
index 3c65c202e28e012ef7d233b19b83593706bd3862..6bdede70ac1f236a0169a6ff99116571cda3ddbe 100644 (file)
@@ -276,7 +276,7 @@ typedef struct {
 } fr_tacacs_ctx_t;
 
 /* encode.c */
-int fr_tacacs_packet_encode(RADIUS_PACKET * const packet, char const * const secret, UNUSED size_t secret_len);
+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);