From: Jorge Pereira Date: Fri, 14 Aug 2020 15:34:57 +0000 (-0300) Subject: tacacs: Add fr_tacacs_encode() function X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f169e7cc204987c03f3afbca4f2be7ed4d8a2e48;p=thirdparty%2Ffreeradius-server.git tacacs: Add fr_tacacs_encode() function It replaces the previsous implementation fixing several issues against the https://tools.ietf.org/id/draft-ietf-opsawg-tacacs-07.html --- diff --git a/src/protocols/tacacs/encode.c b/src/protocols/tacacs/encode.c index 4a6963330c1..030b7cd60be 100644 --- a/src/protocols/tacacs/encode.c +++ b/src/protocols/tacacs/encode.c @@ -23,191 +23,697 @@ * @copyright 2017 The FreeRADIUS server project * @copyright 2017 Network RADIUS SARL (legal@networkradius.com) */ -#include -#include + +#include +#include #include #include +#include #include -#include +#include +#include #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 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 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 +}; diff --git a/src/protocols/tacacs/tacacs.h b/src/protocols/tacacs/tacacs.h index 3c65c202e28..6bdede70ac1 100644 --- a/src/protocols/tacacs/tacacs.h +++ b/src/protocols/tacacs/tacacs.h @@ -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);