From: Jorge Pereira Date: Fri, 14 Aug 2020 15:35:27 +0000 (-0300) Subject: tacacs: Add fr_tacacs_decode() function X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c2af8b631dd0cfd6ca8cb976c6242551fcc16615;p=thirdparty%2Ffreeradius-server.git tacacs: Add fr_tacacs_decode() 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/modules/proto_tacacs/proto_tacacs.c b/src/modules/proto_tacacs/proto_tacacs.c index 2ccc841fb59..842b914edc3 100644 --- a/src/modules/proto_tacacs/proto_tacacs.c +++ b/src/modules/proto_tacacs/proto_tacacs.c @@ -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) { diff --git a/src/protocols/tacacs/decode.c b/src/protocols/tacacs/decode.c index 7ec912f16d4..ee5c1902fd4 100644 --- a/src/protocols/tacacs/decode.c +++ b/src/protocols/tacacs/decode.c @@ -23,32 +23,92 @@ * @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" -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 +}; diff --git a/src/protocols/tacacs/tacacs.h b/src/protocols/tacacs/tacacs.h index 6bdede70ac1..90999e47ba4 100644 --- a/src/protocols/tacacs/tacacs.h +++ b/src/protocols/tacacs/tacacs.h @@ -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);