From: Alan T. DeKok Date: Wed, 3 May 2017 15:20:34 +0000 (-0400) Subject: more renames and moves X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8fe79f57c1ffeec7fa19eefb663af87d095e6914;p=thirdparty%2Ffreeradius-server.git more renames and moves --- diff --git a/src/include/libradius.h b/src/include/libradius.h index 59aa47617bf..db9f63458f3 100644 --- a/src/include/libradius.h +++ b/src/include/libradius.h @@ -205,7 +205,7 @@ do { \ } while (0) /* - * radius.c + * protocols/radius/base.c */ #define AUTH_PASS_LEN (AUTH_VECTOR_LEN) #define MAX_PASS_LEN (128) @@ -225,7 +225,7 @@ void fr_radius_ascend_secret(uint8_t *digest, uint8_t const *vector, ssize_t fr_radius_recv_header(int sockfd, fr_ipaddr_t *src_ipaddr, uint16_t *src_port, unsigned int *code); /* - * radius_packet.c + * protocols/radius/packet.c */ RADIUS_PACKET *fr_radius_alloc(TALLOC_CTX *ctx, bool new_vector); RADIUS_PACKET *fr_radius_alloc_reply(TALLOC_CTX *ctx, RADIUS_PACKET *); @@ -259,7 +259,7 @@ typedef struct fr_radius_ctx { } fr_radius_ctx_t; /* - * radius_encode.c + * protocols/radius/encode.c */ int fr_radius_encode_password(char *encpw, size_t *len, char const *secret, uint8_t const *vector); @@ -272,7 +272,7 @@ ssize_t fr_radius_encode_value_hton(uint8_t *out, size_t outlen, VALUE_PAIR con ssize_t fr_radius_encode_pair(uint8_t *out, size_t outlen, vp_cursor_t *cursor, void *encoder_ctx); /* - * radius_decode.c + * protocols/radius/decode.c */ int fr_radius_decode_tlv_ok(uint8_t const *data, size_t length, size_t dv_type, size_t dv_length); diff --git a/src/protocols/radius/radius.c b/src/protocols/radius/base.c similarity index 99% rename from src/protocols/radius/radius.c rename to src/protocols/radius/base.c index 9a22d477011..de551fe9ad4 100644 --- a/src/protocols/radius/radius.c +++ b/src/protocols/radius/base.c @@ -17,7 +17,7 @@ /** * $Id$ * - * @file radius.c + * @file base.c * @brief Functions to send/receive radius packets. * * @copyright 2000-2003,2006 The FreeRADIUS server project diff --git a/src/protocols/radius/radius_decode.c b/src/protocols/radius/decode.c similarity index 99% rename from src/protocols/radius/radius_decode.c rename to src/protocols/radius/decode.c index b594e8d68d0..3340013b430 100644 --- a/src/protocols/radius/radius_decode.c +++ b/src/protocols/radius/decode.c @@ -17,7 +17,7 @@ /** * $Id$ * - * @file radius.c + * @file decode.c * @brief Functions to decode RADIUS attributes * * @copyright 2000-2003,2006-2015 The FreeRADIUS server project diff --git a/src/protocols/radius/radius_encode.c b/src/protocols/radius/encode.c similarity index 99% rename from src/protocols/radius/radius_encode.c rename to src/protocols/radius/encode.c index 5cfa3d09844..507995fad37 100644 --- a/src/protocols/radius/radius_encode.c +++ b/src/protocols/radius/encode.c @@ -17,7 +17,7 @@ /** * $Id$ * - * @file radius.c + * @file encode.c * @brief Functions to encode RADIUS attributes * * @copyright 2000-2003,2006-2015 The FreeRADIUS server project diff --git a/src/protocols/radius/list.c b/src/protocols/radius/list.c new file mode 100644 index 00000000000..8e837c8c388 --- /dev/null +++ b/src/protocols/radius/list.c @@ -0,0 +1,925 @@ +/* + * packet.c Generic packet manipulation functions. + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000-2006 The FreeRADIUS server project + */ + +/** + * $Id$ + * + * @file list.c + * @brief Functions to deal with outgoing lists / sets of packets. + * + * @copyright 2000-2017 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include +#include + +#include + +/* + * See if two packets are identical. + * + * Note that we do NOT compare the authentication vectors. + * That's because if the authentication vector is different, + * it means that the NAS has given up on the earlier request. + */ +int fr_packet_cmp(RADIUS_PACKET const *a, RADIUS_PACKET const *b) +{ + int rcode; + + /* + * 256-way fanout. + */ + if (a->id < b->id) return -1; + if (a->id > b->id) return +1; + + if (a->sockfd < b->sockfd) return -1; + if (a->sockfd > b->sockfd) return +1; + + /* + * Source ports are pretty much random. + */ + rcode = (int) a->src_port - (int) b->src_port; + if (rcode != 0) return rcode; + + /* + * Usually many client IPs, and few server IPs + */ + rcode = fr_ipaddr_cmp(&a->src_ipaddr, &b->src_ipaddr); + if (rcode != 0) return rcode; + + /* + * One socket can receive packets for multiple + * destination IPs, so we check that before checking the + * file descriptor. + */ + rcode = fr_ipaddr_cmp(&a->dst_ipaddr, &b->dst_ipaddr); + if (rcode != 0) return rcode; + + /* + * At this point, the order of comparing socket FDs + * and/or destination ports doesn't matter. One of those + * fields will make the socket unique, and the other is + * pretty much redundant. + */ + rcode = (int) a->dst_port - (int) b->dst_port; + return rcode; +} + +/** Determine if an address is the INADDR_ANY address for its address family + * + * @param ipaddr to check. + * @return + * - 0 if it's not. + * - 1 if it is. + * - -1 on error. + */ +int fr_is_inaddr_any(fr_ipaddr_t *ipaddr) +{ + + if (ipaddr->af == AF_INET) { + if (ipaddr->ipaddr.ip4addr.s_addr == INADDR_ANY) { + return 1; + } + +#ifdef HAVE_STRUCT_SOCKADDR_IN6 + } else if (ipaddr->af == AF_INET6) { + if (IN6_IS_ADDR_UNSPECIFIED(&(ipaddr->ipaddr.ip6addr))) { + return 1; + } +#endif + + } else { + fr_strerror_printf("Unknown address family"); + return -1; + } + + return 0; +} + + +/* + * Create a fake "request" from a reply, for later lookup. + */ +void fr_request_from_reply(RADIUS_PACKET *request, + RADIUS_PACKET const *reply) +{ + request->sockfd = reply->sockfd; + request->id = reply->id; +#ifdef WITH_TCP + request->proto = reply->proto; +#endif + request->src_port = reply->dst_port; + request->dst_port = reply->src_port; + request->src_ipaddr = reply->dst_ipaddr; + request->dst_ipaddr = reply->src_ipaddr; + request->if_index = reply->if_index; +} + +/* + * We need to keep track of the socket & it's IP/port. + */ +typedef struct fr_packet_socket_t { + int sockfd; + void *ctx; + + uint32_t num_outgoing; + + int src_any; + fr_ipaddr_t src_ipaddr; + uint16_t src_port; + + int dst_any; + fr_ipaddr_t dst_ipaddr; + uint16_t dst_port; + + bool dont_use; + +#ifdef WITH_TCP + int proto; +#endif + + uint8_t id[32]; +} fr_packet_socket_t; + + +#define FNV_MAGIC_PRIME (0x01000193) +#define MAX_SOCKETS (256) +#define SOCKOFFSET_MASK (MAX_SOCKETS - 1) +#define SOCK2OFFSET(sockfd) ((sockfd * FNV_MAGIC_PRIME) & SOCKOFFSET_MASK) + +/* + * Structure defining a list of packets (incoming or outgoing) + * that should be managed. + */ +struct fr_packet_list_t { + rbtree_t *tree; + + int alloc_id; + uint32_t num_outgoing; + int last_recv; + int num_sockets; + + fr_packet_socket_t sockets[MAX_SOCKETS]; +}; + + +/* + * Ugh. Doing this on every sent/received packet is not nice. + */ +static fr_packet_socket_t *fr_socket_find(fr_packet_list_t *pl, + int sockfd) +{ + int i, start; + + i = start = SOCK2OFFSET(sockfd); + + do { /* make this hack slightly more efficient */ + if (pl->sockets[i].sockfd == sockfd) return &pl->sockets[i]; + + i = (i + 1) & SOCKOFFSET_MASK; + } while (i != start); + + return NULL; +} + +bool fr_packet_list_socket_freeze(fr_packet_list_t *pl, int sockfd) +{ + fr_packet_socket_t *ps; + + if (!pl) { + fr_strerror_printf("Invalid argument"); + return false; + } + + ps = fr_socket_find(pl, sockfd); + if (!ps) { + fr_strerror_printf("No such socket"); + return false; + } + + ps->dont_use = true; + return true; +} + +bool fr_packet_list_socket_thaw(fr_packet_list_t *pl, int sockfd) +{ + fr_packet_socket_t *ps; + + if (!pl) return false; + + ps = fr_socket_find(pl, sockfd); + if (!ps) return false; + + ps->dont_use = false; + return true; +} + + +bool fr_packet_list_socket_del(fr_packet_list_t *pl, int sockfd) +{ + fr_packet_socket_t *ps; + + if (!pl) return false; + + ps = fr_socket_find(pl, sockfd); + if (!ps) return false; + + if (ps->num_outgoing != 0) return false; + + ps->sockfd = -1; + pl->num_sockets--; + + return true; +} + + +bool fr_packet_list_socket_add(fr_packet_list_t *pl, int sockfd, int proto, + fr_ipaddr_t *dst_ipaddr, uint16_t dst_port, + void *ctx) +{ + int i, start; + struct sockaddr_storage src; + socklen_t sizeof_src; + fr_packet_socket_t *ps; + + if (!pl || !dst_ipaddr || (dst_ipaddr->af == AF_UNSPEC)) { + fr_strerror_printf("Invalid argument"); + return false; + } + + if (pl->num_sockets >= MAX_SOCKETS) { + fr_strerror_printf("Too many open sockets"); + return false; + } + +#ifndef WITH_TCP + if (proto != IPPROTO_UDP) { + fr_strerror_printf("only UDP is supported"); + return false; + } +#endif + + ps = NULL; + i = start = SOCK2OFFSET(sockfd); + + do { + if (pl->sockets[i].sockfd == -1) { + ps = &pl->sockets[i]; + break; + } + + i = (i + 1) & SOCKOFFSET_MASK; + } while (i != start); + + if (!ps) { + fr_strerror_printf("All socket entries are full"); + return false; + } + + memset(ps, 0, sizeof(*ps)); + ps->ctx = ctx; +#ifdef WITH_TCP + ps->proto = proto; +#endif + + /* + * Get address family, etc. first, so we know if we + * need to do udpfromto. + * + * FIXME: udpfromto also does this, but it's not + * a critical problem. + */ + sizeof_src = sizeof(src); + memset(&src, 0, sizeof_src); + if (getsockname(sockfd, (struct sockaddr *) &src, + &sizeof_src) < 0) { + fr_strerror_printf("%s", fr_syserror(errno)); + return false; + } + + if (!fr_ipaddr_from_sockaddr(&src, sizeof_src, &ps->src_ipaddr, + &ps->src_port)) { + fr_strerror_printf("Failed to get IP"); + return false; + } + + ps->dst_ipaddr = *dst_ipaddr; + ps->dst_port = dst_port; + + ps->src_any = fr_is_inaddr_any(&ps->src_ipaddr); + if (ps->src_any < 0) return false; + + ps->dst_any = fr_is_inaddr_any(&ps->dst_ipaddr); + if (ps->dst_any < 0) return false; + + /* + * As the last step before returning. + */ + ps->sockfd = sockfd; + pl->num_sockets++; + + return true; +} + +static int packet_entry_cmp(void const *one, void const *two) +{ + RADIUS_PACKET const * const *a = one; + RADIUS_PACKET const * const *b = two; + + return fr_packet_cmp(*a, *b); +} + +void fr_packet_list_free(fr_packet_list_t *pl) +{ + if (!pl) return; + + talloc_free(pl->tree); + talloc_free(pl); +} + + +/* + * Caller is responsible for managing the packet entries. + */ +fr_packet_list_t *fr_packet_list_create(int alloc_id) +{ + int i; + fr_packet_list_t *pl; + + pl = talloc_zero(NULL, fr_packet_list_t); + if (!pl) return NULL; + pl->tree = rbtree_create(pl, packet_entry_cmp, NULL, 0); + if (!pl->tree) { + fr_packet_list_free(pl); + return NULL; + } + + for (i = 0; i < MAX_SOCKETS; i++) { + pl->sockets[i].sockfd = -1; + } + + pl->alloc_id = alloc_id; + + return pl; +} + + +/* + * If pl->alloc_id is set, then fr_packet_list_id_alloc() MUST + * be called before inserting the packet into the list! + */ +bool fr_packet_list_insert(fr_packet_list_t *pl, + RADIUS_PACKET **request_p) +{ + if (!pl || !request_p || !*request_p) return 0; + + return rbtree_insert(pl->tree, request_p); +} + +RADIUS_PACKET **fr_packet_list_find(fr_packet_list_t *pl, + RADIUS_PACKET *request) +{ + if (!pl || !request) return 0; + + return rbtree_finddata(pl->tree, &request); +} + + +/* + * This presumes that the reply has dst_ipaddr && dst_port set up + * correctly (i.e. real IP, or "*"). + */ +RADIUS_PACKET **fr_packet_list_find_byreply(fr_packet_list_t *pl, RADIUS_PACKET *reply) +{ + RADIUS_PACKET my_request, *request; + fr_packet_socket_t *ps; + + if (!pl || !reply) return NULL; + + ps = fr_socket_find(pl, reply->sockfd); + if (!ps) return NULL; + + /* + * Initialize request from reply, AND from the source + * IP & port of this socket. The client may have bound + * the socket to 0, in which case it's some random port, + * that is NOT in the original request->src_port. + */ + my_request.sockfd = reply->sockfd; + my_request.id = reply->id; + +#ifdef WITH_TCP + /* + * TCP sockets are always bound to the correct src/dst IP/port + */ + if (ps->proto == IPPROTO_TCP) { + reply->dst_ipaddr = ps->src_ipaddr; + reply->dst_port = ps->src_port; + reply->src_ipaddr = ps->dst_ipaddr; + reply->src_port = ps->dst_port; + + my_request.src_ipaddr = ps->src_ipaddr; + my_request.src_port = ps->src_port; + my_request.dst_ipaddr = ps->dst_ipaddr; + my_request.dst_port = ps->dst_port; + + } else +#endif + { + if (ps->src_any) { + my_request.src_ipaddr = ps->src_ipaddr; + } else { + my_request.src_ipaddr = reply->dst_ipaddr; + } + my_request.src_port = ps->src_port; + + my_request.dst_ipaddr = reply->src_ipaddr; + my_request.dst_port = reply->src_port; + } + +#ifdef WITH_TCP + my_request.proto = reply->proto; +#endif + request = &my_request; + + return rbtree_finddata(pl->tree, &request); +} + + +bool fr_packet_list_yank(fr_packet_list_t *pl, RADIUS_PACKET *request) +{ + rbnode_t *node; + + if (!pl || !request) return false; + + node = rbtree_find(pl->tree, &request); + if (!node) return false; + + rbtree_delete(pl->tree, node); + return true; +} + +uint32_t fr_packet_list_num_elements(fr_packet_list_t *pl) +{ + if (!pl) return 0; + + return rbtree_num_elements(pl->tree); +} + + +/* + * 1 == ID was allocated & assigned + * 0 == couldn't allocate ID. + * + * Note that this ALSO assigns a socket to use, and updates + * packet->request->src_ipaddr && packet->request->src_port + * + * In multi-threaded systems, the calls to id_alloc && id_free + * should be protected by a mutex. This does NOT have to be + * the same mutex as the one protecting the insert/find/yank + * calls! + * + * We assume that the packet has dst_ipaddr && dst_port + * already initialized. We will use those to find an + * outgoing socket. The request MAY also have src_ipaddr set. + * + * We also assume that the sender doesn't care which protocol + * should be used. + */ +bool fr_packet_list_id_alloc(fr_packet_list_t *pl, int proto, + RADIUS_PACKET **request_p, void **pctx) +{ + int i, j, k, fd, id, start_i, start_j, start_k; + int src_any = 0; + fr_packet_socket_t *ps= NULL; + RADIUS_PACKET *request = *request_p; + + if ((request->dst_ipaddr.af == AF_UNSPEC) || + (request->dst_port == 0)) { + fr_strerror_printf("No destination address/port specified"); + return false; + } + +#ifndef WITH_TCP + if ((proto != 0) && (proto != IPPROTO_UDP)) { + fr_strerror_printf("Invalid destination protocol"); + return false; + } +#endif + + /* + * Special case: unspec == "don't care" + */ + if (request->src_ipaddr.af == AF_UNSPEC) { + memset(&request->src_ipaddr, 0, sizeof(request->src_ipaddr)); + request->src_ipaddr.af = request->dst_ipaddr.af; + } + + src_any = fr_is_inaddr_any(&request->src_ipaddr); + if (src_any < 0) { + fr_strerror_printf("Can't check src_ipaddr"); + return false; + } + + /* + * MUST specify a destination address. + */ + if (fr_is_inaddr_any(&request->dst_ipaddr) != 0) { + fr_strerror_printf("Must specify a dst_ipaddr"); + return false; + } + + /* + * FIXME: Go to an LRU system. This prevents ID re-use + * for as long as possible. The main problem with that + * approach is that it requires us to populate the + * LRU/FIFO when we add a new socket, or a new destination, + * which can be expensive. + * + * The LRU can be avoided if the caller takes care to free + * Id's only when all responses have been received, OR after + * a timeout. + * + * Right now, the random approach is almost OK... it's + * brute-force over all of the available ID's, BUT using + * random numbers for everything spreads the load a bit. + * + * The old method had a hash lookup on allocation AND + * on free. The new method has brute-force on allocation, + * and near-zero cost on free. + */ + + id = fd = -1; + start_i = fr_rand() & SOCKOFFSET_MASK; + +#define ID_i ((i + start_i) & SOCKOFFSET_MASK) + for (i = 0; i < MAX_SOCKETS; i++) { + if (pl->sockets[ID_i].sockfd == -1) continue; /* paranoia */ + + ps = &(pl->sockets[ID_i]); + + /* + * This socket is marked as "don't use for new + * packets". But we can still receive packets + * that are outstanding. + */ + if (ps->dont_use) continue; + + /* + * All IDs are allocated: ignore it. + */ + if (ps->num_outgoing == 256) continue; + +#ifdef WITH_TCP + if (ps->proto != proto) continue; +#endif + + /* + * Address families don't match, skip it. + */ + if (ps->src_ipaddr.af != request->dst_ipaddr.af) continue; + + /* + * MUST match dst port, if we have one. + */ + if ((ps->dst_port != 0) && + (ps->dst_port != request->dst_port)) continue; + + /* + * MUST match requested src port, if one has been given. + */ + if ((request->src_port != 0) && + (ps->src_port != request->src_port)) continue; + + /* + * We don't care about the source IP, but this + * socket is link local, and the requested + * destination is not link local. Ignore it. + */ + if (src_any && (ps->src_ipaddr.af == AF_INET) && + (((ps->src_ipaddr.ipaddr.ip4addr.s_addr >> 24) & 0xff) == 127) && + (((request->dst_ipaddr.ipaddr.ip4addr.s_addr >> 24) & 0xff) != 127)) continue; + + /* + * We're sourcing from *, and they asked for a + * specific source address: ignore it. + */ + if (ps->src_any && !src_any) continue; + + /* + * We're sourcing from a specific IP, and they + * asked for a source IP that isn't us: ignore + * it. + */ + if (!ps->src_any && !src_any && + (fr_ipaddr_cmp(&request->src_ipaddr, + &ps->src_ipaddr) != 0)) continue; + + /* + * UDP sockets are allowed to match + * destination IPs exactly, OR a socket + * with destination * is allowed to match + * any requested destination. + * + * TCP sockets must match the destination + * exactly. They *always* have dst_any=0, + * so the first check always matches. + */ + if (!ps->dst_any && + (fr_ipaddr_cmp(&request->dst_ipaddr, + &ps->dst_ipaddr) != 0)) continue; + + /* + * Otherwise, this socket is OK to use. + */ + + /* + * Look for a free Id, starting from a random number. + */ + start_j = fr_rand() & 0x1f; +#define ID_j ((j + start_j) & 0x1f) + for (j = 0; j < 32; j++) { + if (ps->id[ID_j] == 0xff) continue; + + + start_k = fr_rand() & 0x07; +#define ID_k ((k + start_k) & 0x07) + for (k = 0; k < 8; k++) { + if ((ps->id[ID_j] & (1 << ID_k)) != 0) continue; + + ps->id[ID_j] |= (1 << ID_k); + id = (ID_j * 8) + ID_k; + fd = i; + break; + } + if (fd >= 0) break; + } +#undef ID_i +#undef ID_j +#undef ID_k + break; + } + + /* + * Ask the caller to allocate a new ID. + */ + if (fd < 0) { + fr_strerror_printf("Failed finding socket, caller must allocate a new one"); + return false; + } + + /* + * Set the ID, source IP, and source port. + */ + request->id = id; + + request->sockfd = ps->sockfd; + request->src_ipaddr = ps->src_ipaddr; + request->src_port = ps->src_port; + + /* + * If we managed to insert it, we're done. + */ + if (fr_packet_list_insert(pl, request_p)) { + if (pctx) *pctx = ps->ctx; + ps->num_outgoing++; + pl->num_outgoing++; + return true; + } + + /* + * Mark the ID as free. This is the one line from + * id_free() that we care about here. + */ + ps->id[(request->id >> 3) & 0x1f] &= ~(1 << (request->id & 0x07)); + + request->id = -1; + request->sockfd = -1; + request->src_ipaddr.af = AF_UNSPEC; + request->src_port = 0; + + return false; +} + +/* + * Should be called AFTER yanking it from the list, so that + * any newly inserted entries don't collide with this one. + */ +bool fr_packet_list_id_free(fr_packet_list_t *pl, + RADIUS_PACKET *request, bool yank) +{ + fr_packet_socket_t *ps; + + if (!pl || !request) return false; + + if (yank && !fr_packet_list_yank(pl, request)) return false; + + ps = fr_socket_find(pl, request->sockfd); + if (!ps) return false; + +#if 0 + if (!ps->id[(request->id >> 3) & 0x1f] & (1 << (request->id & 0x07))) { + fr_exit(1); + } +#endif + + ps->id[(request->id >> 3) & 0x1f] &= ~(1 << (request->id & 0x07)); + + ps->num_outgoing--; + pl->num_outgoing--; + + request->id = -1; + request->src_ipaddr.af = AF_UNSPEC; /* id_alloc checks this */ + request->src_port = 0; + + return true; +} + +/* + * We always walk RBTREE_DELETE_ORDER, which is like RBTREE_IN_ORDER, except that + * <0 means error, stop + * 0 means OK, continue + * 1 means delete current node and stop + * 2 means delete current node and continue + */ +int fr_packet_list_walk(fr_packet_list_t *pl, void *ctx, rb_walker_t callback) +{ + if (!pl || !callback) return 0; + + return rbtree_walk(pl->tree, RBTREE_DELETE_ORDER, callback, ctx); +} + +int fr_packet_list_fd_set(fr_packet_list_t *pl, fd_set *set) +{ + int i, maxfd; + + if (!pl || !set) return 0; + + maxfd = -1; + + for (i = 0; i < MAX_SOCKETS; i++) { + if (pl->sockets[i].sockfd == -1) continue; + FD_SET(pl->sockets[i].sockfd, set); + if (pl->sockets[i].sockfd > maxfd) { + maxfd = pl->sockets[i].sockfd; + } + } + + if (maxfd < 0) return -1; + + return maxfd + 1; +} + +/* + * Round-robins the receivers, without priority. + * + * FIXME: Add sockfd, if -1, do round-robin, else do sockfd + * IF in fdset. + */ +RADIUS_PACKET *fr_packet_list_recv(fr_packet_list_t *pl, fd_set *set) +{ + int start; + RADIUS_PACKET *packet; + + if (!pl || !set) return NULL; + + start = pl->last_recv; + do { + start++; + start &= SOCKOFFSET_MASK; + + if (pl->sockets[start].sockfd == -1) continue; + + if (!FD_ISSET(pl->sockets[start].sockfd, set)) continue; + +#ifdef WITH_TCP + if (pl->sockets[start].proto == IPPROTO_TCP) { + packet = fr_tcp_recv(pl->sockets[start].sockfd, false); + } else +#endif + packet = fr_radius_packet_recv(NULL, pl->sockets[start].sockfd, UDP_FLAGS_NONE, false); + if (!packet) continue; + + /* + * Call fr_packet_list_find_byreply(). If it + * doesn't find anything, discard the reply. + */ + + pl->last_recv = start; +#ifdef WITH_TCP + packet->proto = pl->sockets[start].proto; +#endif + return packet; + } while (start != pl->last_recv); + + return NULL; +} + +uint32_t fr_packet_list_num_incoming(fr_packet_list_t *pl) +{ + uint32_t num_elements; + + if (!pl) return 0; + + num_elements = rbtree_num_elements(pl->tree); + if (num_elements < pl->num_outgoing) return 0; /* panic! */ + + return num_elements - pl->num_outgoing; +} + +uint32_t fr_packet_list_num_outgoing(fr_packet_list_t *pl) +{ + if (!pl) return 0; + + return pl->num_outgoing; +} + +/* + * Debug the packet if requested. + */ +void fr_packet_header_print(FILE *fp, RADIUS_PACKET *packet, bool received) +{ + char src_ipaddr[FR_IPADDR_STRLEN]; + char dst_ipaddr[FR_IPADDR_STRLEN]; +#if defined(WITH_UDPFROMTO) && defined(WITH_IFINDEX_NAME_RESOLUTION) + char if_name[IFNAMSIZ]; +#endif + + if (!fp) return; + if (!packet) return; + + /* + * Client-specific debugging re-prints the input + * packet into the client log. + * + * This really belongs in a utility library + */ + if (is_radius_code(packet->code)) { + fprintf(fp, "%s %s Id %i from %s%s%s:%i to %s%s%s:%i " +#if defined(WITH_UDPFROMTO) && defined(WITH_IFINDEX_NAME_RESOLUTION) + "%s%s%s" +#endif + "length %zu\n", + received ? "Received" : "Sent", + fr_packet_codes[packet->code], + packet->id, + packet->src_ipaddr.af == AF_INET6 ? "[" : "", + fr_inet_ntop(src_ipaddr, sizeof(src_ipaddr), &packet->src_ipaddr), + packet->src_ipaddr.af == AF_INET6 ? "]" : "", + packet->src_port, + packet->dst_ipaddr.af == AF_INET6 ? "[" : "", + fr_inet_ntop(dst_ipaddr, sizeof(dst_ipaddr), &packet->dst_ipaddr), + packet->dst_ipaddr.af == AF_INET6 ? "]" : "", + packet->dst_port, +#if defined(WITH_UDPFROMTO) && defined(WITH_IFINDEX_NAME_RESOLUTION) + received ? "via " : "", + received ? fr_ifname_from_ifindex(if_name, packet->if_index) : "", + received ? " " : "", +#endif + packet->data_len); + } else { + fprintf(fp, "%s code %u Id %i from %s%s%s:%i to %s%s%s:%i " +#if defined(WITH_UDPFROMTO) && defined(WITH_IFINDEX_NAME_RESOLUTION) + "%s%s%s" +#endif + "length %zu\n", + received ? "Received" : "Sent", + packet->code, + packet->id, + packet->src_ipaddr.af == AF_INET6 ? "[" : "", + fr_inet_ntop(src_ipaddr, sizeof(src_ipaddr), &packet->src_ipaddr), + packet->src_ipaddr.af == AF_INET6 ? "]" : "", + packet->src_port, + packet->dst_ipaddr.af == AF_INET6 ? "[" : "", + fr_inet_ntop(dst_ipaddr, sizeof(dst_ipaddr), &packet->dst_ipaddr), + packet->dst_ipaddr.af == AF_INET6 ? "]" : "", + packet->dst_port, +#if defined(WITH_UDPFROMTO) && defined(WITH_IFINDEX_NAME_RESOLUTION) + received ? "via " : "", + received ? fr_ifname_from_ifindex(if_name, packet->if_index) : "", + received ? " " : "", +#endif + packet->data_len); + } +} diff --git a/src/protocols/radius/packet.c b/src/protocols/radius/packet.c index 44d7d7a7da2..4b328ecf948 100644 --- a/src/protocols/radius/packet.c +++ b/src/protocols/radius/packet.c @@ -1,8 +1,4 @@ /* - * packet.c Generic packet manipulation functions. - * - * Version: $Id$ - * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either @@ -16,901 +12,837 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file packet.c + * @brief Functions to deal with RADIUS_PACKET data structures. * - * Copyright 2000-2006 The FreeRADIUS server project + * @copyright 2000-2017 The FreeRADIUS server project */ RCSID("$Id$") -#include -#include - -#include +#include +#include -/* - * See if two packets are identical. - * - * Note that we do NOT compare the authentication vectors. - * That's because if the authentication vector is different, - * it means that the NAS has given up on the earlier request. - */ -int fr_packet_cmp(RADIUS_PACKET const *a, RADIUS_PACKET const *b) -{ - int rcode; +#ifdef WITH_UDPFROMTO +#include +#endif - /* - * 256-way fanout. - */ - if (a->id < b->id) return -1; - if (a->id > b->id) return +1; +#include +#include - if (a->sockfd < b->sockfd) return -1; - if (a->sockfd > b->sockfd) return +1; +typedef struct radius_packet_t { + uint8_t code; + uint8_t id; + uint8_t length[2]; + uint8_t vector[AUTH_VECTOR_LEN]; + uint8_t data[1]; +} radius_packet_t; - /* - * Source ports are pretty much random. - */ - rcode = (int) a->src_port - (int) b->src_port; - if (rcode != 0) return rcode; - /* - * Usually many client IPs, and few server IPs - */ - rcode = fr_ipaddr_cmp(&a->src_ipaddr, &b->src_ipaddr); - if (rcode != 0) return rcode; - - /* - * One socket can receive packets for multiple - * destination IPs, so we check that before checking the - * file descriptor. - */ - rcode = fr_ipaddr_cmp(&a->dst_ipaddr, &b->dst_ipaddr); - if (rcode != 0) return rcode; +/* + * Some messages get printed out only in debugging mode. + */ +#define FR_DEBUG_STRERROR_PRINTF if (fr_debug_lvl) fr_strerror_printf - /* - * At this point, the order of comparing socket FDs - * and/or destination ports doesn't matter. One of those - * fields will make the socket unique, and the other is - * pretty much redundant. - */ - rcode = (int) a->dst_port - (int) b->dst_port; - return rcode; -} -/** Determine if an address is the INADDR_ANY address for its address family +/** Allocate a new RADIUS_PACKET * - * @param ipaddr to check. + * @param ctx the context in which the packet is allocated. May be NULL if + * the packet is not associated with a REQUEST. + * @param new_vector if true a new request authenticator will be generated. * @return - * - 0 if it's not. - * - 1 if it is. - * - -1 on error. + * - New RADIUS_PACKET. + * - NULL on error. */ -int fr_is_inaddr_any(fr_ipaddr_t *ipaddr) +RADIUS_PACKET *fr_radius_alloc(TALLOC_CTX *ctx, bool new_vector) { + RADIUS_PACKET *rp; - if (ipaddr->af == AF_INET) { - if (ipaddr->ipaddr.ip4addr.s_addr == INADDR_ANY) { - return 1; - } + rp = talloc_zero(ctx, RADIUS_PACKET); + if (!rp) { + fr_strerror_printf("out of memory"); + return NULL; + } + rp->id = -1; + rp->offset = -1; -#ifdef HAVE_STRUCT_SOCKADDR_IN6 - } else if (ipaddr->af == AF_INET6) { - if (IN6_IS_ADDR_UNSPECIFIED(&(ipaddr->ipaddr.ip6addr))) { - return 1; - } -#endif + if (new_vector) { + int i; + uint32_t hash, base; - } else { - fr_strerror_printf("Unknown address family"); - return -1; + /* + * Don't expose the actual contents of the random + * pool. + */ + base = fr_rand(); + for (i = 0; i < AUTH_VECTOR_LEN; i += sizeof(uint32_t)) { + hash = fr_rand() ^ base; + memcpy(rp->vector + i, &hash, sizeof(hash)); + } } + fr_rand(); /* stir the pool again */ - return 0; + return rp; } - -/* - * Create a fake "request" from a reply, for later lookup. +/** Allocate a new RADIUS_PACKET response + * + * @param ctx the context in which the packet is allocated. May be NULL if + * the packet is not associated with a REQUEST. + * @param packet The request packet. + * @return + * - New RADIUS_PACKET. + * - NULL on error. */ -void fr_request_from_reply(RADIUS_PACKET *request, - RADIUS_PACKET const *reply) +RADIUS_PACKET *fr_radius_alloc_reply(TALLOC_CTX *ctx, RADIUS_PACKET *packet) { - request->sockfd = reply->sockfd; - request->id = reply->id; -#ifdef WITH_TCP - request->proto = reply->proto; -#endif - request->src_port = reply->dst_port; - request->dst_port = reply->src_port; - request->src_ipaddr = reply->dst_ipaddr; - request->dst_ipaddr = reply->src_ipaddr; - request->if_index = reply->if_index; -} + RADIUS_PACKET *reply; -/* - * We need to keep track of the socket & it's IP/port. - */ -typedef struct fr_packet_socket_t { - int sockfd; - void *ctx; - - uint32_t num_outgoing; + if (!packet) return NULL; - int src_any; - fr_ipaddr_t src_ipaddr; - uint16_t src_port; + reply = fr_radius_alloc(ctx, false); + if (!reply) return NULL; - int dst_any; - fr_ipaddr_t dst_ipaddr; - uint16_t dst_port; - - bool dont_use; + /* + * Initialize the fields from the request. + */ + reply->sockfd = packet->sockfd; + reply->dst_ipaddr = packet->src_ipaddr; + reply->src_ipaddr = packet->dst_ipaddr; + reply->dst_port = packet->src_port; + reply->src_port = packet->dst_port; + reply->if_index = packet->if_index; + reply->id = packet->id; + reply->code = 0; /* UNKNOWN code */ + memcpy(reply->vector, packet->vector, + sizeof(reply->vector)); + reply->vps = NULL; + reply->data = NULL; + reply->data_len = 0; #ifdef WITH_TCP - int proto; + reply->proto = packet->proto; #endif - - uint8_t id[32]; -} fr_packet_socket_t; - - -#define FNV_MAGIC_PRIME (0x01000193) -#define MAX_SOCKETS (256) -#define SOCKOFFSET_MASK (MAX_SOCKETS - 1) -#define SOCK2OFFSET(sockfd) ((sockfd * FNV_MAGIC_PRIME) & SOCKOFFSET_MASK) - -/* - * Structure defining a list of packets (incoming or outgoing) - * that should be managed. - */ -struct fr_packet_list_t { - rbtree_t *tree; - - int alloc_id; - uint32_t num_outgoing; - int last_recv; - int num_sockets; - - fr_packet_socket_t sockets[MAX_SOCKETS]; -}; + return reply; +} -/* - * Ugh. Doing this on every sent/received packet is not nice. +/** Free a RADIUS_PACKET + * */ -static fr_packet_socket_t *fr_socket_find(fr_packet_list_t *pl, - int sockfd) +void fr_radius_free(RADIUS_PACKET **radius_packet_ptr) { - int i, start; + RADIUS_PACKET *radius_packet; - i = start = SOCK2OFFSET(sockfd); + if (!radius_packet_ptr || !*radius_packet_ptr) return; + radius_packet = *radius_packet_ptr; - do { /* make this hack slightly more efficient */ - if (pl->sockets[i].sockfd == sockfd) return &pl->sockets[i]; + VERIFY_PACKET(radius_packet); - i = (i + 1) & SOCKOFFSET_MASK; - } while (i != start); + fr_pair_list_free(&radius_packet->vps); - return NULL; + talloc_free(radius_packet); + *radius_packet_ptr = NULL; } -bool fr_packet_list_socket_freeze(fr_packet_list_t *pl, int sockfd) +/** Duplicate a RADIUS_PACKET + * + * @param ctx the context in which the packet is allocated. May be NULL if + * the packet is not associated with a REQUEST. + * @param in The packet to copy + * @return + * - New RADIUS_PACKET. + * - NULL on error. + */ +RADIUS_PACKET *fr_radius_copy(TALLOC_CTX *ctx, RADIUS_PACKET const *in) { - fr_packet_socket_t *ps; + RADIUS_PACKET *out; - if (!pl) { - fr_strerror_printf("Invalid argument"); - return false; - } + out = fr_radius_alloc(ctx, false); + if (!out) return NULL; - ps = fr_socket_find(pl, sockfd); - if (!ps) { - fr_strerror_printf("No such socket"); - return false; - } - - ps->dont_use = true; - return true; -} + /* + * Bootstrap by copying everything. + */ + memcpy(out, in, sizeof(*out)); -bool fr_packet_list_socket_thaw(fr_packet_list_t *pl, int sockfd) -{ - fr_packet_socket_t *ps; + /* + * Then reset necessary fields + */ + out->sockfd = -1; - if (!pl) return false; + out->data = NULL; + out->data_len = 0; - ps = fr_socket_find(pl, sockfd); - if (!ps) return false; + out->vps = fr_pair_list_copy(out, in->vps); + out->offset = 0; - ps->dont_use = false; - return true; + return out; } -bool fr_packet_list_socket_del(fr_packet_list_t *pl, int sockfd) +/** Encode a packet + * + */ +int fr_radius_packet_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, + char const *secret) { - fr_packet_socket_t *ps; + radius_packet_t *hdr; + uint8_t *ptr; + uint16_t total_length; + int len; + VALUE_PAIR const *vp; + vp_cursor_t cursor; + fr_radius_ctx_t encoder_ctx = { .packet = packet, .original = original, .secret = secret }; + + /* + * A 4K packet, aligned on 64-bits. + */ + uint64_t data[MAX_PACKET_LEN / sizeof(uint64_t)]; + + /* + * Double-check some things based on packet code. + */ + switch (packet->code) { + case PW_CODE_ACCESS_ACCEPT: + case PW_CODE_ACCESS_REJECT: + case PW_CODE_ACCESS_CHALLENGE: + if (!original) { + fr_strerror_printf("ERROR: Cannot sign response packet without a request packet"); + return -1; + } + break; + + /* + * These packet vectors start off as all zero. + */ + case PW_CODE_ACCOUNTING_REQUEST: + case PW_CODE_DISCONNECT_REQUEST: + case PW_CODE_COA_REQUEST: + memset(packet->vector, 0, sizeof(packet->vector)); + break; - if (!pl) return false; + default: + break; + } - ps = fr_socket_find(pl, sockfd); - if (!ps) return false; + /* + * Use memory on the stack, until we know how + * large the packet will be. + */ + hdr = (radius_packet_t *) data; - if (ps->num_outgoing != 0) return false; + /* + * Build standard header + */ + hdr->code = packet->code; + hdr->id = packet->id; - ps->sockfd = -1; - pl->num_sockets--; + memcpy(hdr->vector, packet->vector, sizeof(hdr->vector)); - return true; -} + total_length = RADIUS_HDR_LEN; + /* + * Load up the configuration values for the user + */ + ptr = hdr->data; + packet->offset = 0; -bool fr_packet_list_socket_add(fr_packet_list_t *pl, int sockfd, int proto, - fr_ipaddr_t *dst_ipaddr, uint16_t dst_port, - void *ctx) -{ - int i, start; - struct sockaddr_storage src; - socklen_t sizeof_src; - fr_packet_socket_t *ps; + /* + * Loop over the reply attributes for the packet. + */ + fr_pair_cursor_init(&cursor, &packet->vps); + while ((vp = fr_pair_cursor_current(&cursor))) { + size_t last_len, room; + char const *last_name = NULL; - if (!pl || !dst_ipaddr || (dst_ipaddr->af == AF_UNSPEC)) { - fr_strerror_printf("Invalid argument"); - return false; - } + VERIFY_VP(vp); - if (pl->num_sockets >= MAX_SOCKETS) { - fr_strerror_printf("Too many open sockets"); - return false; - } + room = ((uint8_t *)data) + sizeof(data) - ptr; -#ifndef WITH_TCP - if (proto != IPPROTO_UDP) { - fr_strerror_printf("only UDP is supported"); - return false; - } + /* + * Ignore non-wire attributes, but allow extended + * attributes. + * + * @fixme We should be able to get rid of this check + * and just look at da->flags.internal + */ + if (vp->da->flags.internal || ((vp->da->vendor == 0) && (vp->da->attr >= 256))) { +#ifndef NDEBUG + /* + * Permit the admin to send BADLY formatted + * attributes with a debug build. + */ + if (vp->da->attr == PW_RAW_ATTRIBUTE) { + if (vp->vp_length > room) { + len = room; + } else { + len = vp->vp_length; + } + + memcpy(ptr, vp->vp_octets, len); + fr_pair_cursor_next(&cursor); + goto next; + } #endif + fr_pair_cursor_next(&cursor); + continue; + } - ps = NULL; - i = start = SOCK2OFFSET(sockfd); - - do { - if (pl->sockets[i].sockfd == -1) { - ps = &pl->sockets[i]; - break; + /* + * Set the Message-Authenticator to the correct + * length and initial value. + */ + if (!vp->da->vendor && (vp->da->attr == PW_MESSAGE_AUTHENTICATOR)) { + /* + * Cache the offset to the + * Message-Authenticator + */ + packet->offset = total_length; + last_len = 16; + } else { + last_len = vp->vp_length; } + last_name = vp->da->name; - i = (i + 1) & SOCKOFFSET_MASK; - } while (i != start); + if (room <= 2) break; - if (!ps) { - fr_strerror_printf("All socket entries are full"); - return false; - } + len = fr_radius_encode_pair(ptr, room, &cursor, &encoder_ctx); + if (len < 0) return -1; - memset(ps, 0, sizeof(*ps)); - ps->ctx = ctx; -#ifdef WITH_TCP - ps->proto = proto; + /* + * Failed to encode the attribute, likely because + * the packet is full. + */ + if (len == 0) { + if (last_len != 0) { + fr_strerror_printf("WARNING: Failed encoding attribute %s\n", last_name); + break; + } else { + fr_strerror_printf("WARNING: Skipping zero-length attribute %s\n", last_name); + } + } + +#ifndef NDEBUG + next: /* Used only for Raw-Attribute */ #endif + ptr += len; + total_length += len; + } /* done looping over all attributes */ /* - * Get address family, etc. first, so we know if we - * need to do udpfromto. + * Fill in the rest of the fields, and copy the data over + * from the local stack to the newly allocated memory. * - * FIXME: udpfromto also does this, but it's not - * a critical problem. + * Yes, all this 'memcpy' is slow, but it means + * that we only allocate the minimum amount of + * memory for a request. */ - sizeof_src = sizeof(src); - memset(&src, 0, sizeof_src); - if (getsockname(sockfd, (struct sockaddr *) &src, - &sizeof_src) < 0) { - fr_strerror_printf("%s", fr_syserror(errno)); - return false; + packet->data_len = total_length; + packet->data = talloc_array(packet, uint8_t, packet->data_len); + if (!packet->data) { + fr_strerror_printf("Out of memory"); + return -1; } - if (!fr_ipaddr_from_sockaddr(&src, sizeof_src, &ps->src_ipaddr, - &ps->src_port)) { - fr_strerror_printf("Failed to get IP"); - return false; - } + memcpy(packet->data, hdr, packet->data_len); + hdr = (radius_packet_t *) packet->data; - ps->dst_ipaddr = *dst_ipaddr; - ps->dst_port = dst_port; + total_length = htons(total_length); + memcpy(hdr->length, &total_length, sizeof(total_length)); - ps->src_any = fr_is_inaddr_any(&ps->src_ipaddr); - if (ps->src_any < 0) return false; + return 0; +} - ps->dst_any = fr_is_inaddr_any(&ps->dst_ipaddr); - if (ps->dst_any < 0) return false; +/** Calculate/check digest, and decode radius attributes + * + * @return + * - 0 on success + * - -1 on decoding error. + */ +int fr_radius_packet_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original, char const *secret) +{ + int packet_length; + uint32_t num_attributes; + uint8_t *ptr; + radius_packet_t *hdr; + VALUE_PAIR *head = NULL; + vp_cursor_t cursor, out; + fr_radius_ctx_t decoder_ctx = { + .original = original, + .packet = packet, + .secret = secret + }; /* - * As the last step before returning. + * Extract attribute-value pairs */ - ps->sockfd = sockfd; - pl->num_sockets++; - - return true; -} + hdr = (radius_packet_t *)packet->data; + ptr = hdr->data; + packet_length = packet->data_len - RADIUS_HDR_LEN; + num_attributes = 0; -static int packet_entry_cmp(void const *one, void const *two) -{ - RADIUS_PACKET const * const *a = one; - RADIUS_PACKET const * const *b = two; + fr_pair_cursor_init(&cursor, &head); - return fr_packet_cmp(*a, *b); -} + /* + * Loop over the attributes, decoding them into VPs. + */ + while (packet_length > 0) { + ssize_t my_len; -void fr_packet_list_free(fr_packet_list_t *pl) -{ - if (!pl) return; + /* + * This may return many VPs + */ + my_len = fr_radius_decode_pair(packet, &cursor, fr_dict_root(fr_dict_internal), + ptr, packet_length, &decoder_ctx); + if (my_len < 0) { + fr_pair_list_free(&head); + return -1; + } - talloc_free(pl->tree); - talloc_free(pl); -} + /* + * This should really be an assertion. + */ + if (my_len == 0) break; + /* + * Count the ones which were just added + */ + while (fr_pair_cursor_next(&cursor)) num_attributes++; -/* - * Caller is responsible for managing the packet entries. - */ -fr_packet_list_t *fr_packet_list_create(int alloc_id) -{ - int i; - fr_packet_list_t *pl; + /* + * VSA's may not have been counted properly in + * fr_radius_packet_ok() above, as it is hard to count + * then without using the dictionary. We + * therefore enforce the limits here, too. + */ + if ((fr_max_attributes > 0) && (num_attributes > fr_max_attributes)) { + char host_ipaddr[INET6_ADDRSTRLEN]; + + fr_pair_list_free(&head); + fr_strerror_printf("Possible DoS attack from host %s: Too many attributes in request " + "(received %d, max %d are allowed)", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr)), + num_attributes, fr_max_attributes); + return -1; + } - pl = talloc_zero(NULL, fr_packet_list_t); - if (!pl) return NULL; - pl->tree = rbtree_create(pl, packet_entry_cmp, NULL, 0); - if (!pl->tree) { - fr_packet_list_free(pl); - return NULL; + ptr += my_len; + packet_length -= my_len; } - for (i = 0; i < MAX_SOCKETS; i++) { - pl->sockets[i].sockfd = -1; - } + fr_pair_cursor_init(&out, &packet->vps); + fr_pair_cursor_last(&out); /* Move insertion point to the end of the list */ + fr_pair_cursor_merge(&out, head); - pl->alloc_id = alloc_id; + /* + * Merge information from the outside world into our + * random pool. + */ + fr_rand_seed(packet->data, RADIUS_HDR_LEN); - return pl; + return 0; } -/* - * If pl->alloc_id is set, then fr_packet_list_id_alloc() MUST - * be called before inserting the packet into the list! +/** See if the data pointed to by PTR is a valid RADIUS packet. + * + * Packet is not 'const * const' because we may update data_len, if there's more data + * in the UDP packet than in the RADIUS packet. + * + * @param packet to check + * @param require_ma to require Message-Authenticator + * @param reason if not NULL, will have the failure reason written to where it points. + * @return + * - True on success. + * - False on failure. */ -bool fr_packet_list_insert(fr_packet_list_t *pl, - RADIUS_PACKET **request_p) +bool fr_radius_packet_ok(RADIUS_PACKET *packet, bool require_ma, decode_fail_t *reason) { - if (!pl || !request_p || !*request_p) return 0; + char host_ipaddr[INET6_ADDRSTRLEN]; + + if (!fr_radius_ok(packet->data, &packet->data_len, require_ma, reason)) { + FR_DEBUG_STRERROR_PRINTF("Bad packet received from host %s - %s", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr)), + fr_strerror()); + return false; + } - return rbtree_insert(pl->tree, request_p); + /* + * Fill RADIUS header fields + */ + packet->code = packet->data[0]; + packet->id = packet->data[1]; + memcpy(packet->vector, packet->data + 4, sizeof(packet->vector)); + return true; } -RADIUS_PACKET **fr_packet_list_find(fr_packet_list_t *pl, - RADIUS_PACKET *request) + +/** Verify the Request/Response Authenticator (and Message-Authenticator if present) of a packet + * + */ +int fr_radius_packet_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original, char const *secret) { - if (!pl || !request) return 0; + uint8_t const *original_data; + char buffer[INET6_ADDRSTRLEN]; - return rbtree_finddata(pl->tree, &request); + if (!packet->data) return -1; + + if (original) { + original_data = original->data; + } else { + original_data = NULL; + } + + if (fr_radius_verify(packet->data, original_data, + (uint8_t const *) secret, talloc_array_length(secret) - 1) < 0) { + fr_strerror_printf("Received packet from %s with %s", + inet_ntop(packet->src_ipaddr.af, &packet->src_ipaddr.ipaddr, + buffer, sizeof(buffer)), + fr_strerror()); + return -1; + } + + return 0; } -/* - * This presumes that the reply has dst_ipaddr && dst_port set up - * correctly (i.e. real IP, or "*"). +/** Sign a previously encoded packet + * */ -RADIUS_PACKET **fr_packet_list_find_byreply(fr_packet_list_t *pl, RADIUS_PACKET *reply) +int fr_radius_packet_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, + char const *secret) { - RADIUS_PACKET my_request, *request; - fr_packet_socket_t *ps; - - if (!pl || !reply) return NULL; - - ps = fr_socket_find(pl, reply->sockfd); - if (!ps) return NULL; + int rcode; + uint8_t const *original_data; - /* - * Initialize request from reply, AND from the source - * IP & port of this socket. The client may have bound - * the socket to 0, in which case it's some random port, - * that is NOT in the original request->src_port. - */ - my_request.sockfd = reply->sockfd; - my_request.id = reply->id; + if (original) { + original_data = original->data; + } else { + original_data = NULL; + } -#ifdef WITH_TCP /* - * TCP sockets are always bound to the correct src/dst IP/port + * Copy the random vector to the packet. Other packet + * codes have the Request Authenticator be the packet + * signature. */ - if (ps->proto == IPPROTO_TCP) { - reply->dst_ipaddr = ps->src_ipaddr; - reply->dst_port = ps->src_port; - reply->src_ipaddr = ps->dst_ipaddr; - reply->src_port = ps->dst_port; - - my_request.src_ipaddr = ps->src_ipaddr; - my_request.src_port = ps->src_port; - my_request.dst_ipaddr = ps->dst_ipaddr; - my_request.dst_port = ps->dst_port; - - } else -#endif - { - if (ps->src_any) { - my_request.src_ipaddr = ps->src_ipaddr; - } else { - my_request.src_ipaddr = reply->dst_ipaddr; - } - my_request.src_port = ps->src_port; - - my_request.dst_ipaddr = reply->src_ipaddr; - my_request.dst_port = reply->src_port; + if ((packet->code == PW_CODE_ACCESS_REQUEST) || + (packet->code == PW_CODE_STATUS_SERVER)) { + memcpy(packet->data + 4, packet->vector, sizeof(packet->vector)); } -#ifdef WITH_TCP - my_request.proto = reply->proto; -#endif - request = &my_request; + rcode = fr_radius_sign(packet->data, original_data, + (uint8_t const *) secret, talloc_array_length(secret) - 1); + if (rcode < 0) return rcode; - return rbtree_finddata(pl->tree, &request); + memcpy(packet->vector, packet->data + 4, AUTH_VECTOR_LEN); + return 0; } -bool fr_packet_list_yank(fr_packet_list_t *pl, RADIUS_PACKET *request) +/** Wrapper for recvfrom, which handles recvfromto, IPv6, and all possible combinations + * + */ +static ssize_t rad_recvfrom(int sockfd, RADIUS_PACKET *packet, int flags) { - rbnode_t *node; + ssize_t data_len; - if (!pl || !request) return false; + data_len = fr_radius_recv_header(sockfd, &packet->src_ipaddr, &packet->src_port, &packet->code); + if (data_len < 0) { + if ((errno == EAGAIN) || (errno == EINTR)) return 0; + return -1; + } - node = rbtree_find(pl->tree, &request); - if (!node) return false; + if (data_len == 0) return -1; /* invalid packet */ - rbtree_delete(pl->tree, node); - return true; -} + packet->data = talloc_array(packet, uint8_t, data_len); + if (!packet->data) return -1; -uint32_t fr_packet_list_num_elements(fr_packet_list_t *pl) -{ - if (!pl) return 0; + packet->data_len = data_len; - return rbtree_num_elements(pl->tree); + return udp_recv(sockfd, packet->data, packet->data_len, flags, + &packet->src_ipaddr, &packet->src_port, + &packet->dst_ipaddr, &packet->dst_port, + &packet->if_index, &packet->timestamp); } -/* - * 1 == ID was allocated & assigned - * 0 == couldn't allocate ID. - * - * Note that this ALSO assigns a socket to use, and updates - * packet->request->src_ipaddr && packet->request->src_port +/** Receive UDP client requests, and fill in the basics of a RADIUS_PACKET structure * - * In multi-threaded systems, the calls to id_alloc && id_free - * should be protected by a mutex. This does NOT have to be - * the same mutex as the one protecting the insert/find/yank - * calls! - * - * We assume that the packet has dst_ipaddr && dst_port - * already initialized. We will use those to find an - * outgoing socket. The request MAY also have src_ipaddr set. - * - * We also assume that the sender doesn't care which protocol - * should be used. */ -bool fr_packet_list_id_alloc(fr_packet_list_t *pl, int proto, - RADIUS_PACKET **request_p, void **pctx) +RADIUS_PACKET *fr_radius_packet_recv(TALLOC_CTX *ctx, int fd, int flags, bool require_ma) { - int i, j, k, fd, id, start_i, start_j, start_k; - int src_any = 0; - fr_packet_socket_t *ps= NULL; - RADIUS_PACKET *request = *request_p; - - if ((request->dst_ipaddr.af == AF_UNSPEC) || - (request->dst_port == 0)) { - fr_strerror_printf("No destination address/port specified"); - return false; + ssize_t data_len; + RADIUS_PACKET *packet; + + /* + * Allocate the new request data structure + */ + packet = fr_radius_alloc(ctx, false); + if (!packet) { + fr_strerror_printf("out of memory"); + return NULL; } -#ifndef WITH_TCP - if ((proto != 0) && (proto != IPPROTO_UDP)) { - fr_strerror_printf("Invalid destination protocol"); - return false; + data_len = rad_recvfrom(fd, packet, flags); + if (data_len < 0) { + FR_DEBUG_STRERROR_PRINTF("Error receiving packet: %s", fr_syserror(errno)); + fr_radius_free(&packet); + return NULL; } -#endif +#ifdef WITH_VERIFY_PTR /* - * Special case: unspec == "don't care" + * Double-check that the fields we want are filled in. */ - if (request->src_ipaddr.af == AF_UNSPEC) { - memset(&request->src_ipaddr, 0, sizeof(request->src_ipaddr)); - request->src_ipaddr.af = request->dst_ipaddr.af; + if ((packet->src_ipaddr.af == AF_UNSPEC) || + (packet->src_port == 0) || + (packet->dst_ipaddr.af == AF_UNSPEC) || + (packet->dst_port == 0)) { + FR_DEBUG_STRERROR_PRINTF("Error receiving packet: %s", fr_syserror(errno)); + fr_radius_free(&packet); + return NULL; } +#endif - src_any = fr_is_inaddr_any(&request->src_ipaddr); - if (src_any < 0) { - fr_strerror_printf("Can't check src_ipaddr"); - return false; - } + packet->data_len = data_len; /* unsigned vs signed */ /* - * MUST specify a destination address. + * If the packet is too big, then rad_recvfrom did NOT + * allocate memory. Instead, it just discarded the + * packet. */ - if (fr_is_inaddr_any(&request->dst_ipaddr) != 0) { - fr_strerror_printf("Must specify a dst_ipaddr"); - return false; + if (packet->data_len > MAX_PACKET_LEN) { + FR_DEBUG_STRERROR_PRINTF("Discarding packet: Larger than RFC limitation of 4096 bytes"); + fr_radius_free(&packet); + return NULL; } /* - * FIXME: Go to an LRU system. This prevents ID re-use - * for as long as possible. The main problem with that - * approach is that it requires us to populate the - * LRU/FIFO when we add a new socket, or a new destination, - * which can be expensive. - * - * The LRU can be avoided if the caller takes care to free - * Id's only when all responses have been received, OR after - * a timeout. - * - * Right now, the random approach is almost OK... it's - * brute-force over all of the available ID's, BUT using - * random numbers for everything spreads the load a bit. - * - * The old method had a hash lookup on allocation AND - * on free. The new method has brute-force on allocation, - * and near-zero cost on free. + * Read no data. Continue. + * This check is AFTER the MAX_PACKET_LEN check above, because + * if the packet is larger than MAX_PACKET_LEN, we also have + * packet->data == NULL */ - - id = fd = -1; - start_i = fr_rand() & SOCKOFFSET_MASK; - -#define ID_i ((i + start_i) & SOCKOFFSET_MASK) - for (i = 0; i < MAX_SOCKETS; i++) { - if (pl->sockets[ID_i].sockfd == -1) continue; /* paranoia */ - - ps = &(pl->sockets[ID_i]); - - /* - * This socket is marked as "don't use for new - * packets". But we can still receive packets - * that are outstanding. - */ - if (ps->dont_use) continue; - - /* - * All IDs are allocated: ignore it. - */ - if (ps->num_outgoing == 256) continue; - -#ifdef WITH_TCP - if (ps->proto != proto) continue; -#endif - - /* - * Address families don't match, skip it. - */ - if (ps->src_ipaddr.af != request->dst_ipaddr.af) continue; - - /* - * MUST match dst port, if we have one. - */ - if ((ps->dst_port != 0) && - (ps->dst_port != request->dst_port)) continue; - - /* - * MUST match requested src port, if one has been given. - */ - if ((request->src_port != 0) && - (ps->src_port != request->src_port)) continue; - - /* - * We don't care about the source IP, but this - * socket is link local, and the requested - * destination is not link local. Ignore it. - */ - if (src_any && (ps->src_ipaddr.af == AF_INET) && - (((ps->src_ipaddr.ipaddr.ip4addr.s_addr >> 24) & 0xff) == 127) && - (((request->dst_ipaddr.ipaddr.ip4addr.s_addr >> 24) & 0xff) != 127)) continue; - - /* - * We're sourcing from *, and they asked for a - * specific source address: ignore it. - */ - if (ps->src_any && !src_any) continue; - - /* - * We're sourcing from a specific IP, and they - * asked for a source IP that isn't us: ignore - * it. - */ - if (!ps->src_any && !src_any && - (fr_ipaddr_cmp(&request->src_ipaddr, - &ps->src_ipaddr) != 0)) continue; - - /* - * UDP sockets are allowed to match - * destination IPs exactly, OR a socket - * with destination * is allowed to match - * any requested destination. - * - * TCP sockets must match the destination - * exactly. They *always* have dst_any=0, - * so the first check always matches. - */ - if (!ps->dst_any && - (fr_ipaddr_cmp(&request->dst_ipaddr, - &ps->dst_ipaddr) != 0)) continue; - - /* - * Otherwise, this socket is OK to use. - */ - - /* - * Look for a free Id, starting from a random number. - */ - start_j = fr_rand() & 0x1f; -#define ID_j ((j + start_j) & 0x1f) - for (j = 0; j < 32; j++) { - if (ps->id[ID_j] == 0xff) continue; - - - start_k = fr_rand() & 0x07; -#define ID_k ((k + start_k) & 0x07) - for (k = 0; k < 8; k++) { - if ((ps->id[ID_j] & (1 << ID_k)) != 0) continue; - - ps->id[ID_j] |= (1 << ID_k); - id = (ID_j * 8) + ID_k; - fd = i; - break; - } - if (fd >= 0) break; - } -#undef ID_i -#undef ID_j -#undef ID_k - break; + if ((packet->data_len == 0) || !packet->data) { + FR_DEBUG_STRERROR_PRINTF("Empty packet: Socket is not ready"); + fr_radius_free(&packet); + return NULL; } /* - * Ask the caller to allocate a new ID. + * See if it's a well-formed RADIUS packet. */ - if (fd < 0) { - fr_strerror_printf("Failed finding socket, caller must allocate a new one"); - return false; + if (!fr_radius_packet_ok(packet, require_ma, NULL)) { + fr_radius_free(&packet); + return NULL; } /* - * Set the ID, source IP, and source port. + * Remember which socket we read the packet from. */ - request->id = id; - - request->sockfd = ps->sockfd; - request->src_ipaddr = ps->src_ipaddr; - request->src_port = ps->src_port; + packet->sockfd = fd; /* - * If we managed to insert it, we're done. + * FIXME: Do even more filtering by only permitting + * certain IP's. The problem is that we don't know + * how to do this properly for all possible clients... */ - if (fr_packet_list_insert(pl, request_p)) { - if (pctx) *pctx = ps->ctx; - ps->num_outgoing++; - pl->num_outgoing++; - return true; - } /* - * Mark the ID as free. This is the one line from - * id_free() that we care about here. + * Explicitely set the VP list to empty. */ - ps->id[(request->id >> 3) & 0x1f] &= ~(1 << (request->id & 0x07)); + packet->vps = NULL; - request->id = -1; - request->sockfd = -1; - request->src_ipaddr.af = AF_UNSPEC; - request->src_port = 0; +#ifndef NDEBUG + if ((fr_debug_lvl > 3) && fr_log_fp) fr_radius_print_hex(packet); +#endif - return false; + return packet; } -/* - * Should be called AFTER yanking it from the list, so that - * any newly inserted entries don't collide with this one. +/** Reply to the request + * + * Also attach reply attribute value pairs and any user message provided. */ -bool fr_packet_list_id_free(fr_packet_list_t *pl, - RADIUS_PACKET *request, bool yank) +int fr_radius_packet_send(RADIUS_PACKET *packet, RADIUS_PACKET const *original, + char const *secret) { - fr_packet_socket_t *ps; - - if (!pl || !request) return false; + /* + * Maybe it's a fake packet. Don't send it. + */ + if (packet->sockfd < 0) { + return 0; + } - if (yank && !fr_packet_list_yank(pl, request)) return false; + /* + * First time through, allocate room for the packet + */ + if (!packet->data) { + /* + * Encode the packet. + */ + if (fr_radius_packet_encode(packet, original, secret) < 0) { + return -1; + } - ps = fr_socket_find(pl, request->sockfd); - if (!ps) return false; + /* + * Re-sign it, including updating the + * Message-Authenticator. + */ + if (fr_radius_packet_sign(packet, original, secret) < 0) { + return -1; + } -#if 0 - if (!ps->id[(request->id >> 3) & 0x1f] & (1 << (request->id & 0x07))) { - fr_exit(1); + /* + * If packet->data points to data, then we print out + * the VP list again only for debugging. + */ } -#endif - - ps->id[(request->id >> 3) & 0x1f] &= ~(1 << (request->id & 0x07)); - ps->num_outgoing--; - pl->num_outgoing--; +#ifndef NDEBUG + if ((fr_debug_lvl > 3) && fr_log_fp) fr_radius_print_hex(packet); +#endif - request->id = -1; - request->src_ipaddr.af = AF_UNSPEC; /* id_alloc checks this */ - request->src_port = 0; +#ifdef WITH_TCP + /* + * If the socket is TCP, call write(). Calling sendto() + * is allowed on some platforms, but it's not nice. Even + * worse, if UDPFROMTO is defined, we *can't* use it on + * TCP sockets. So... just call write(). + */ + if (packet->proto == IPPROTO_TCP) { + ssize_t rcode; - return true; -} + rcode = write(packet->sockfd, packet->data, packet->data_len); + if (rcode >= 0) return rcode; -/* - * We always walk RBTREE_DELETE_ORDER, which is like RBTREE_IN_ORDER, except that - * <0 means error, stop - * 0 means OK, continue - * 1 means delete current node and stop - * 2 means delete current node and continue - */ -int fr_packet_list_walk(fr_packet_list_t *pl, void *ctx, rb_walker_t callback) -{ - if (!pl || !callback) return 0; + fr_strerror_printf("sendto failed: %s", fr_syserror(errno)); + return -1; + } +#endif - return rbtree_walk(pl->tree, RBTREE_DELETE_ORDER, callback, ctx); + /* + * And send it on it's way. + */ + return udp_send(packet->sockfd, packet->data, packet->data_len, 0, + &packet->src_ipaddr, packet->src_port, packet->if_index, + &packet->dst_ipaddr, packet->dst_port); } -int fr_packet_list_fd_set(fr_packet_list_t *pl, fd_set *set) +static void print_hex_data(uint8_t const *ptr, int attrlen, int depth) { - int i, maxfd; - - if (!pl || !set) return 0; - - maxfd = -1; + int i; + static char const tabs[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; - for (i = 0; i < MAX_SOCKETS; i++) { - if (pl->sockets[i].sockfd == -1) continue; - FD_SET(pl->sockets[i].sockfd, set); - if (pl->sockets[i].sockfd > maxfd) { - maxfd = pl->sockets[i].sockfd; - } + for (i = 0; i < attrlen; i++) { + if ((i > 0) && ((i & 0x0f) == 0x00)) + fprintf(fr_log_fp, "%.*s", depth, tabs); + fprintf(fr_log_fp, "%02x ", ptr[i]); + if ((i & 0x0f) == 0x0f) fprintf(fr_log_fp, "\n"); } - - if (maxfd < 0) return -1; - - return maxfd + 1; + if ((i & 0x0f) != 0) fprintf(fr_log_fp, "\n"); } -/* - * Round-robins the receivers, without priority. - * - * FIXME: Add sockfd, if -1, do round-robin, else do sockfd - * IF in fdset. - */ -RADIUS_PACKET *fr_packet_list_recv(fr_packet_list_t *pl, fd_set *set) -{ - int start; - RADIUS_PACKET *packet; - - if (!pl || !set) return NULL; - - start = pl->last_recv; - do { - start++; - start &= SOCKOFFSET_MASK; - if (pl->sockets[start].sockfd == -1) continue; +void fr_radius_print_hex(RADIUS_PACKET const *packet) +{ + int i; - if (!FD_ISSET(pl->sockets[start].sockfd, set)) continue; + if (!packet->data || !fr_log_fp) return; + fprintf(fr_log_fp, " Socket:\t%d\n", packet->sockfd); #ifdef WITH_TCP - if (pl->sockets[start].proto == IPPROTO_TCP) { - packet = fr_tcp_recv(pl->sockets[start].sockfd, false); - } else + fprintf(fr_log_fp, " Proto:\t%d\n", packet->proto); #endif - packet = fr_radius_packet_recv(NULL, pl->sockets[start].sockfd, UDP_FLAGS_NONE, false); - if (!packet) continue; - /* - * Call fr_packet_list_find_byreply(). If it - * doesn't find anything, discard the reply. - */ + if (packet->src_ipaddr.af == AF_INET) { + char buffer[INET6_ADDRSTRLEN]; - pl->last_recv = start; -#ifdef WITH_TCP - packet->proto = pl->sockets[start].proto; -#endif - return packet; - } while (start != pl->last_recv); + fprintf(fr_log_fp, " Src IP:\t%s\n", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + buffer, sizeof(buffer))); + fprintf(fr_log_fp, " port:\t%u\n", packet->src_port); - return NULL; -} + fprintf(fr_log_fp, " Dst IP:\t%s\n", + inet_ntop(packet->dst_ipaddr.af, + &packet->dst_ipaddr.ipaddr, + buffer, sizeof(buffer))); + fprintf(fr_log_fp, " port:\t%u\n", packet->dst_port); + } -uint32_t fr_packet_list_num_incoming(fr_packet_list_t *pl) -{ - uint32_t num_elements; + if (packet->data[0] < FR_MAX_PACKET_CODE) { + fprintf(fr_log_fp, " Code:\t\t(%d) %s\n", packet->data[0], fr_packet_codes[packet->data[0]]); + } else { + fprintf(fr_log_fp, " Code:\t\t%u\n", packet->data[0]); + } + fprintf(fr_log_fp, " Id:\t\t%u\n", packet->data[1]); + fprintf(fr_log_fp, " Length:\t%u\n", ((packet->data[2] << 8) | + (packet->data[3]))); + fprintf(fr_log_fp, " Vector:\t"); + for (i = 4; i < 20; i++) { + fprintf(fr_log_fp, "%02x", packet->data[i]); + } + fprintf(fr_log_fp, "\n"); - if (!pl) return 0; + if (packet->data_len > 20) { + int total; + uint8_t const *ptr; + fprintf(fr_log_fp, " Data:"); - num_elements = rbtree_num_elements(pl->tree); - if (num_elements < pl->num_outgoing) return 0; /* panic! */ + total = packet->data_len - 20; + ptr = packet->data + 20; - return num_elements - pl->num_outgoing; -} + while (total > 0) { + int attrlen; + unsigned int vendor = 0; -uint32_t fr_packet_list_num_outgoing(fr_packet_list_t *pl) -{ - if (!pl) return 0; + fprintf(fr_log_fp, "\t\t"); + if (total < 2) { /* too short */ + fprintf(fr_log_fp, "%02x\n", *ptr); + break; + } - return pl->num_outgoing; -} + if (ptr[1] > total) { /* too long */ + for (i = 0; i < total; i++) { + fprintf(fr_log_fp, "%02x ", ptr[i]); + } + break; + } -/* - * Debug the packet if requested. - */ -void fr_packet_header_print(FILE *fp, RADIUS_PACKET *packet, bool received) -{ - char src_ipaddr[FR_IPADDR_STRLEN]; - char dst_ipaddr[FR_IPADDR_STRLEN]; -#if defined(WITH_UDPFROMTO) && defined(WITH_IFINDEX_NAME_RESOLUTION) - char if_name[IFNAMSIZ]; -#endif + fprintf(fr_log_fp, "%02x %02x ", ptr[0], ptr[1]); + attrlen = ptr[1] - 2; + + if ((ptr[0] == PW_VENDOR_SPECIFIC) && + (attrlen > 4)) { + vendor = (ptr[3] << 16) | (ptr[4] << 8) | ptr[5]; + fprintf(fr_log_fp, "%02x%02x%02x%02x (%u) ", + ptr[2], ptr[3], ptr[4], ptr[5], vendor); + attrlen -= 4; + ptr += 6; + total -= 6; + + } else { + ptr += 2; + total -= 2; + } - if (!fp) return; - if (!packet) return; + print_hex_data(ptr, attrlen, 3); - /* - * Client-specific debugging re-prints the input - * packet into the client log. - * - * This really belongs in a utility library - */ - if (is_radius_code(packet->code)) { - fprintf(fp, "%s %s Id %i from %s%s%s:%i to %s%s%s:%i " -#if defined(WITH_UDPFROMTO) && defined(WITH_IFINDEX_NAME_RESOLUTION) - "%s%s%s" -#endif - "length %zu\n", - received ? "Received" : "Sent", - fr_packet_codes[packet->code], - packet->id, - packet->src_ipaddr.af == AF_INET6 ? "[" : "", - fr_inet_ntop(src_ipaddr, sizeof(src_ipaddr), &packet->src_ipaddr), - packet->src_ipaddr.af == AF_INET6 ? "]" : "", - packet->src_port, - packet->dst_ipaddr.af == AF_INET6 ? "[" : "", - fr_inet_ntop(dst_ipaddr, sizeof(dst_ipaddr), &packet->dst_ipaddr), - packet->dst_ipaddr.af == AF_INET6 ? "]" : "", - packet->dst_port, -#if defined(WITH_UDPFROMTO) && defined(WITH_IFINDEX_NAME_RESOLUTION) - received ? "via " : "", - received ? fr_ifname_from_ifindex(if_name, packet->if_index) : "", - received ? " " : "", -#endif - packet->data_len); - } else { - fprintf(fp, "%s code %u Id %i from %s%s%s:%i to %s%s%s:%i " -#if defined(WITH_UDPFROMTO) && defined(WITH_IFINDEX_NAME_RESOLUTION) - "%s%s%s" -#endif - "length %zu\n", - received ? "Received" : "Sent", - packet->code, - packet->id, - packet->src_ipaddr.af == AF_INET6 ? "[" : "", - fr_inet_ntop(src_ipaddr, sizeof(src_ipaddr), &packet->src_ipaddr), - packet->src_ipaddr.af == AF_INET6 ? "]" : "", - packet->src_port, - packet->dst_ipaddr.af == AF_INET6 ? "[" : "", - fr_inet_ntop(dst_ipaddr, sizeof(dst_ipaddr), &packet->dst_ipaddr), - packet->dst_ipaddr.af == AF_INET6 ? "]" : "", - packet->dst_port, -#if defined(WITH_UDPFROMTO) && defined(WITH_IFINDEX_NAME_RESOLUTION) - received ? "via " : "", - received ? fr_ifname_from_ifindex(if_name, packet->if_index) : "", - received ? " " : "", -#endif - packet->data_len); + ptr += attrlen; + total -= attrlen; + } } + fflush(stdout); } diff --git a/src/protocols/radius/radius_packet.c b/src/protocols/radius/radius_packet.c deleted file mode 100644 index 0e1278a6481..00000000000 --- a/src/protocols/radius/radius_packet.c +++ /dev/null @@ -1,848 +0,0 @@ -/* - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA - */ - -/** - * $Id$ - * - * @file radius_packet.c - * @brief Functions to deal with RADIUS_PACKET data structures. - * - * @copyright 2000-2017 The FreeRADIUS server project - */ - -RCSID("$Id$") - -#include -#include - -#ifdef WITH_UDPFROMTO -#include -#endif - -#include -#include - -typedef struct radius_packet_t { - uint8_t code; - uint8_t id; - uint8_t length[2]; - uint8_t vector[AUTH_VECTOR_LEN]; - uint8_t data[1]; -} radius_packet_t; - - -/* - * Some messages get printed out only in debugging mode. - */ -#define FR_DEBUG_STRERROR_PRINTF if (fr_debug_lvl) fr_strerror_printf - - -/** Allocate a new RADIUS_PACKET - * - * @param ctx the context in which the packet is allocated. May be NULL if - * the packet is not associated with a REQUEST. - * @param new_vector if true a new request authenticator will be generated. - * @return - * - New RADIUS_PACKET. - * - NULL on error. - */ -RADIUS_PACKET *fr_radius_alloc(TALLOC_CTX *ctx, bool new_vector) -{ - RADIUS_PACKET *rp; - - rp = talloc_zero(ctx, RADIUS_PACKET); - if (!rp) { - fr_strerror_printf("out of memory"); - return NULL; - } - rp->id = -1; - rp->offset = -1; - - if (new_vector) { - int i; - uint32_t hash, base; - - /* - * Don't expose the actual contents of the random - * pool. - */ - base = fr_rand(); - for (i = 0; i < AUTH_VECTOR_LEN; i += sizeof(uint32_t)) { - hash = fr_rand() ^ base; - memcpy(rp->vector + i, &hash, sizeof(hash)); - } - } - fr_rand(); /* stir the pool again */ - - return rp; -} - -/** Allocate a new RADIUS_PACKET response - * - * @param ctx the context in which the packet is allocated. May be NULL if - * the packet is not associated with a REQUEST. - * @param packet The request packet. - * @return - * - New RADIUS_PACKET. - * - NULL on error. - */ -RADIUS_PACKET *fr_radius_alloc_reply(TALLOC_CTX *ctx, RADIUS_PACKET *packet) -{ - RADIUS_PACKET *reply; - - if (!packet) return NULL; - - reply = fr_radius_alloc(ctx, false); - if (!reply) return NULL; - - /* - * Initialize the fields from the request. - */ - reply->sockfd = packet->sockfd; - reply->dst_ipaddr = packet->src_ipaddr; - reply->src_ipaddr = packet->dst_ipaddr; - reply->dst_port = packet->src_port; - reply->src_port = packet->dst_port; - reply->if_index = packet->if_index; - reply->id = packet->id; - reply->code = 0; /* UNKNOWN code */ - memcpy(reply->vector, packet->vector, - sizeof(reply->vector)); - reply->vps = NULL; - reply->data = NULL; - reply->data_len = 0; - -#ifdef WITH_TCP - reply->proto = packet->proto; -#endif - return reply; -} - - -/** Free a RADIUS_PACKET - * - */ -void fr_radius_free(RADIUS_PACKET **radius_packet_ptr) -{ - RADIUS_PACKET *radius_packet; - - if (!radius_packet_ptr || !*radius_packet_ptr) return; - radius_packet = *radius_packet_ptr; - - VERIFY_PACKET(radius_packet); - - fr_pair_list_free(&radius_packet->vps); - - talloc_free(radius_packet); - *radius_packet_ptr = NULL; -} - -/** Duplicate a RADIUS_PACKET - * - * @param ctx the context in which the packet is allocated. May be NULL if - * the packet is not associated with a REQUEST. - * @param in The packet to copy - * @return - * - New RADIUS_PACKET. - * - NULL on error. - */ -RADIUS_PACKET *fr_radius_copy(TALLOC_CTX *ctx, RADIUS_PACKET const *in) -{ - RADIUS_PACKET *out; - - out = fr_radius_alloc(ctx, false); - if (!out) return NULL; - - /* - * Bootstrap by copying everything. - */ - memcpy(out, in, sizeof(*out)); - - /* - * Then reset necessary fields - */ - out->sockfd = -1; - - out->data = NULL; - out->data_len = 0; - - out->vps = fr_pair_list_copy(out, in->vps); - out->offset = 0; - - return out; -} - - -/** Encode a packet - * - */ -int fr_radius_packet_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, - char const *secret) -{ - radius_packet_t *hdr; - uint8_t *ptr; - uint16_t total_length; - int len; - VALUE_PAIR const *vp; - vp_cursor_t cursor; - fr_radius_ctx_t encoder_ctx = { .packet = packet, .original = original, .secret = secret }; - - /* - * A 4K packet, aligned on 64-bits. - */ - uint64_t data[MAX_PACKET_LEN / sizeof(uint64_t)]; - - /* - * Double-check some things based on packet code. - */ - switch (packet->code) { - case PW_CODE_ACCESS_ACCEPT: - case PW_CODE_ACCESS_REJECT: - case PW_CODE_ACCESS_CHALLENGE: - if (!original) { - fr_strerror_printf("ERROR: Cannot sign response packet without a request packet"); - return -1; - } - break; - - /* - * These packet vectors start off as all zero. - */ - case PW_CODE_ACCOUNTING_REQUEST: - case PW_CODE_DISCONNECT_REQUEST: - case PW_CODE_COA_REQUEST: - memset(packet->vector, 0, sizeof(packet->vector)); - break; - - default: - break; - } - - /* - * Use memory on the stack, until we know how - * large the packet will be. - */ - hdr = (radius_packet_t *) data; - - /* - * Build standard header - */ - hdr->code = packet->code; - hdr->id = packet->id; - - memcpy(hdr->vector, packet->vector, sizeof(hdr->vector)); - - total_length = RADIUS_HDR_LEN; - - /* - * Load up the configuration values for the user - */ - ptr = hdr->data; - packet->offset = 0; - - /* - * Loop over the reply attributes for the packet. - */ - fr_pair_cursor_init(&cursor, &packet->vps); - while ((vp = fr_pair_cursor_current(&cursor))) { - size_t last_len, room; - char const *last_name = NULL; - - VERIFY_VP(vp); - - room = ((uint8_t *)data) + sizeof(data) - ptr; - - /* - * Ignore non-wire attributes, but allow extended - * attributes. - * - * @fixme We should be able to get rid of this check - * and just look at da->flags.internal - */ - if (vp->da->flags.internal || ((vp->da->vendor == 0) && (vp->da->attr >= 256))) { -#ifndef NDEBUG - /* - * Permit the admin to send BADLY formatted - * attributes with a debug build. - */ - if (vp->da->attr == PW_RAW_ATTRIBUTE) { - if (vp->vp_length > room) { - len = room; - } else { - len = vp->vp_length; - } - - memcpy(ptr, vp->vp_octets, len); - fr_pair_cursor_next(&cursor); - goto next; - } -#endif - fr_pair_cursor_next(&cursor); - continue; - } - - /* - * Set the Message-Authenticator to the correct - * length and initial value. - */ - if (!vp->da->vendor && (vp->da->attr == PW_MESSAGE_AUTHENTICATOR)) { - /* - * Cache the offset to the - * Message-Authenticator - */ - packet->offset = total_length; - last_len = 16; - } else { - last_len = vp->vp_length; - } - last_name = vp->da->name; - - if (room <= 2) break; - - len = fr_radius_encode_pair(ptr, room, &cursor, &encoder_ctx); - if (len < 0) return -1; - - /* - * Failed to encode the attribute, likely because - * the packet is full. - */ - if (len == 0) { - if (last_len != 0) { - fr_strerror_printf("WARNING: Failed encoding attribute %s\n", last_name); - break; - } else { - fr_strerror_printf("WARNING: Skipping zero-length attribute %s\n", last_name); - } - } - -#ifndef NDEBUG - next: /* Used only for Raw-Attribute */ -#endif - ptr += len; - total_length += len; - } /* done looping over all attributes */ - - /* - * 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. - */ - packet->data_len = total_length; - packet->data = talloc_array(packet, uint8_t, packet->data_len); - if (!packet->data) { - fr_strerror_printf("Out of memory"); - return -1; - } - - memcpy(packet->data, hdr, packet->data_len); - hdr = (radius_packet_t *) packet->data; - - total_length = htons(total_length); - memcpy(hdr->length, &total_length, sizeof(total_length)); - - return 0; -} - - -/** Calculate/check digest, and decode radius attributes - * - * @return - * - 0 on success - * - -1 on decoding error. - */ -int fr_radius_packet_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original, char const *secret) -{ - int packet_length; - uint32_t num_attributes; - uint8_t *ptr; - radius_packet_t *hdr; - VALUE_PAIR *head = NULL; - vp_cursor_t cursor, out; - fr_radius_ctx_t decoder_ctx = { - .original = original, - .packet = packet, - .secret = secret - }; - /* - * Extract attribute-value pairs - */ - hdr = (radius_packet_t *)packet->data; - ptr = hdr->data; - packet_length = packet->data_len - RADIUS_HDR_LEN; - num_attributes = 0; - - fr_pair_cursor_init(&cursor, &head); - - /* - * Loop over the attributes, decoding them into VPs. - */ - while (packet_length > 0) { - ssize_t my_len; - - /* - * This may return many VPs - */ - my_len = fr_radius_decode_pair(packet, &cursor, fr_dict_root(fr_dict_internal), - ptr, packet_length, &decoder_ctx); - if (my_len < 0) { - fr_pair_list_free(&head); - return -1; - } - - /* - * This should really be an assertion. - */ - if (my_len == 0) break; - - /* - * Count the ones which were just added - */ - while (fr_pair_cursor_next(&cursor)) num_attributes++; - - /* - * VSA's may not have been counted properly in - * fr_radius_packet_ok() above, as it is hard to count - * then without using the dictionary. We - * therefore enforce the limits here, too. - */ - if ((fr_max_attributes > 0) && (num_attributes > fr_max_attributes)) { - char host_ipaddr[INET6_ADDRSTRLEN]; - - fr_pair_list_free(&head); - fr_strerror_printf("Possible DoS attack from host %s: Too many attributes in request " - "(received %d, max %d are allowed)", - inet_ntop(packet->src_ipaddr.af, - &packet->src_ipaddr.ipaddr, - host_ipaddr, sizeof(host_ipaddr)), - num_attributes, fr_max_attributes); - return -1; - } - - ptr += my_len; - packet_length -= my_len; - } - - fr_pair_cursor_init(&out, &packet->vps); - fr_pair_cursor_last(&out); /* Move insertion point to the end of the list */ - fr_pair_cursor_merge(&out, head); - - /* - * Merge information from the outside world into our - * random pool. - */ - fr_rand_seed(packet->data, RADIUS_HDR_LEN); - - return 0; -} - - -/** See if the data pointed to by PTR is a valid RADIUS packet. - * - * Packet is not 'const * const' because we may update data_len, if there's more data - * in the UDP packet than in the RADIUS packet. - * - * @param packet to check - * @param require_ma to require Message-Authenticator - * @param reason if not NULL, will have the failure reason written to where it points. - * @return - * - True on success. - * - False on failure. - */ -bool fr_radius_packet_ok(RADIUS_PACKET *packet, bool require_ma, decode_fail_t *reason) -{ - char host_ipaddr[INET6_ADDRSTRLEN]; - - if (!fr_radius_ok(packet->data, &packet->data_len, require_ma, reason)) { - FR_DEBUG_STRERROR_PRINTF("Bad packet received from host %s - %s", - inet_ntop(packet->src_ipaddr.af, - &packet->src_ipaddr.ipaddr, - host_ipaddr, sizeof(host_ipaddr)), - fr_strerror()); - return false; - } - - /* - * Fill RADIUS header fields - */ - packet->code = packet->data[0]; - packet->id = packet->data[1]; - memcpy(packet->vector, packet->data + 4, sizeof(packet->vector)); - return true; -} - - -/** Verify the Request/Response Authenticator (and Message-Authenticator if present) of a packet - * - */ -int fr_radius_packet_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original, char const *secret) -{ - uint8_t const *original_data; - char buffer[INET6_ADDRSTRLEN]; - - if (!packet->data) return -1; - - if (original) { - original_data = original->data; - } else { - original_data = NULL; - } - - if (fr_radius_verify(packet->data, original_data, - (uint8_t const *) secret, talloc_array_length(secret) - 1) < 0) { - fr_strerror_printf("Received packet from %s with %s", - inet_ntop(packet->src_ipaddr.af, &packet->src_ipaddr.ipaddr, - buffer, sizeof(buffer)), - fr_strerror()); - return -1; - } - - return 0; -} - - -/** Sign a previously encoded packet - * - */ -int fr_radius_packet_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, - char const *secret) -{ - int rcode; - uint8_t const *original_data; - - if (original) { - original_data = original->data; - } else { - original_data = NULL; - } - - /* - * Copy the random vector to the packet. Other packet - * codes have the Request Authenticator be the packet - * signature. - */ - if ((packet->code == PW_CODE_ACCESS_REQUEST) || - (packet->code == PW_CODE_STATUS_SERVER)) { - memcpy(packet->data + 4, packet->vector, sizeof(packet->vector)); - } - - rcode = fr_radius_sign(packet->data, original_data, - (uint8_t const *) secret, talloc_array_length(secret) - 1); - if (rcode < 0) return rcode; - - memcpy(packet->vector, packet->data + 4, AUTH_VECTOR_LEN); - return 0; -} - - -/** Wrapper for recvfrom, which handles recvfromto, IPv6, and all possible combinations - * - */ -static ssize_t rad_recvfrom(int sockfd, RADIUS_PACKET *packet, int flags) -{ - ssize_t data_len; - - data_len = fr_radius_recv_header(sockfd, &packet->src_ipaddr, &packet->src_port, &packet->code); - if (data_len < 0) { - if ((errno == EAGAIN) || (errno == EINTR)) return 0; - return -1; - } - - if (data_len == 0) return -1; /* invalid packet */ - - packet->data = talloc_array(packet, uint8_t, data_len); - if (!packet->data) return -1; - - packet->data_len = data_len; - - return udp_recv(sockfd, packet->data, packet->data_len, flags, - &packet->src_ipaddr, &packet->src_port, - &packet->dst_ipaddr, &packet->dst_port, - &packet->if_index, &packet->timestamp); -} - - -/** Receive UDP client requests, and fill in the basics of a RADIUS_PACKET structure - * - */ -RADIUS_PACKET *fr_radius_packet_recv(TALLOC_CTX *ctx, int fd, int flags, bool require_ma) -{ - ssize_t data_len; - RADIUS_PACKET *packet; - - /* - * Allocate the new request data structure - */ - packet = fr_radius_alloc(ctx, false); - if (!packet) { - fr_strerror_printf("out of memory"); - return NULL; - } - - data_len = rad_recvfrom(fd, packet, flags); - if (data_len < 0) { - FR_DEBUG_STRERROR_PRINTF("Error receiving packet: %s", fr_syserror(errno)); - fr_radius_free(&packet); - return NULL; - } - -#ifdef WITH_VERIFY_PTR - /* - * Double-check that the fields we want are filled in. - */ - if ((packet->src_ipaddr.af == AF_UNSPEC) || - (packet->src_port == 0) || - (packet->dst_ipaddr.af == AF_UNSPEC) || - (packet->dst_port == 0)) { - FR_DEBUG_STRERROR_PRINTF("Error receiving packet: %s", fr_syserror(errno)); - fr_radius_free(&packet); - return NULL; - } -#endif - - packet->data_len = data_len; /* unsigned vs signed */ - - /* - * If the packet is too big, then rad_recvfrom did NOT - * allocate memory. Instead, it just discarded the - * packet. - */ - if (packet->data_len > MAX_PACKET_LEN) { - FR_DEBUG_STRERROR_PRINTF("Discarding packet: Larger than RFC limitation of 4096 bytes"); - fr_radius_free(&packet); - return NULL; - } - - /* - * Read no data. Continue. - * This check is AFTER the MAX_PACKET_LEN check above, because - * if the packet is larger than MAX_PACKET_LEN, we also have - * packet->data == NULL - */ - if ((packet->data_len == 0) || !packet->data) { - FR_DEBUG_STRERROR_PRINTF("Empty packet: Socket is not ready"); - fr_radius_free(&packet); - return NULL; - } - - /* - * See if it's a well-formed RADIUS packet. - */ - if (!fr_radius_packet_ok(packet, require_ma, NULL)) { - fr_radius_free(&packet); - return NULL; - } - - /* - * Remember which socket we read the packet from. - */ - packet->sockfd = fd; - - /* - * FIXME: Do even more filtering by only permitting - * certain IP's. The problem is that we don't know - * how to do this properly for all possible clients... - */ - - /* - * Explicitely set the VP list to empty. - */ - packet->vps = NULL; - -#ifndef NDEBUG - if ((fr_debug_lvl > 3) && fr_log_fp) fr_radius_print_hex(packet); -#endif - - return packet; -} - -/** Reply to the request - * - * Also attach reply attribute value pairs and any user message provided. - */ -int fr_radius_packet_send(RADIUS_PACKET *packet, RADIUS_PACKET const *original, - char const *secret) -{ - /* - * Maybe it's a fake packet. Don't send it. - */ - if (packet->sockfd < 0) { - return 0; - } - - /* - * First time through, allocate room for the packet - */ - if (!packet->data) { - /* - * Encode the packet. - */ - if (fr_radius_packet_encode(packet, original, secret) < 0) { - return -1; - } - - /* - * Re-sign it, including updating the - * Message-Authenticator. - */ - if (fr_radius_packet_sign(packet, original, secret) < 0) { - return -1; - } - - /* - * If packet->data points to data, then we print out - * the VP list again only for debugging. - */ - } - -#ifndef NDEBUG - if ((fr_debug_lvl > 3) && fr_log_fp) fr_radius_print_hex(packet); -#endif - -#ifdef WITH_TCP - /* - * If the socket is TCP, call write(). Calling sendto() - * is allowed on some platforms, but it's not nice. Even - * worse, if UDPFROMTO is defined, we *can't* use it on - * TCP sockets. So... just call write(). - */ - if (packet->proto == IPPROTO_TCP) { - ssize_t rcode; - - rcode = write(packet->sockfd, packet->data, packet->data_len); - if (rcode >= 0) return rcode; - - fr_strerror_printf("sendto failed: %s", fr_syserror(errno)); - return -1; - } -#endif - - /* - * And send it on it's way. - */ - return udp_send(packet->sockfd, packet->data, packet->data_len, 0, - &packet->src_ipaddr, packet->src_port, packet->if_index, - &packet->dst_ipaddr, packet->dst_port); -} - -static void print_hex_data(uint8_t const *ptr, int attrlen, int depth) -{ - int i; - static char const tabs[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; - - for (i = 0; i < attrlen; i++) { - if ((i > 0) && ((i & 0x0f) == 0x00)) - fprintf(fr_log_fp, "%.*s", depth, tabs); - fprintf(fr_log_fp, "%02x ", ptr[i]); - if ((i & 0x0f) == 0x0f) fprintf(fr_log_fp, "\n"); - } - if ((i & 0x0f) != 0) fprintf(fr_log_fp, "\n"); -} - - -void fr_radius_print_hex(RADIUS_PACKET const *packet) -{ - int i; - - if (!packet->data || !fr_log_fp) return; - - fprintf(fr_log_fp, " Socket:\t%d\n", packet->sockfd); -#ifdef WITH_TCP - fprintf(fr_log_fp, " Proto:\t%d\n", packet->proto); -#endif - - if (packet->src_ipaddr.af == AF_INET) { - char buffer[INET6_ADDRSTRLEN]; - - fprintf(fr_log_fp, " Src IP:\t%s\n", - inet_ntop(packet->src_ipaddr.af, - &packet->src_ipaddr.ipaddr, - buffer, sizeof(buffer))); - fprintf(fr_log_fp, " port:\t%u\n", packet->src_port); - - fprintf(fr_log_fp, " Dst IP:\t%s\n", - inet_ntop(packet->dst_ipaddr.af, - &packet->dst_ipaddr.ipaddr, - buffer, sizeof(buffer))); - fprintf(fr_log_fp, " port:\t%u\n", packet->dst_port); - } - - if (packet->data[0] < FR_MAX_PACKET_CODE) { - fprintf(fr_log_fp, " Code:\t\t(%d) %s\n", packet->data[0], fr_packet_codes[packet->data[0]]); - } else { - fprintf(fr_log_fp, " Code:\t\t%u\n", packet->data[0]); - } - fprintf(fr_log_fp, " Id:\t\t%u\n", packet->data[1]); - fprintf(fr_log_fp, " Length:\t%u\n", ((packet->data[2] << 8) | - (packet->data[3]))); - fprintf(fr_log_fp, " Vector:\t"); - for (i = 4; i < 20; i++) { - fprintf(fr_log_fp, "%02x", packet->data[i]); - } - fprintf(fr_log_fp, "\n"); - - if (packet->data_len > 20) { - int total; - uint8_t const *ptr; - fprintf(fr_log_fp, " Data:"); - - total = packet->data_len - 20; - ptr = packet->data + 20; - - while (total > 0) { - int attrlen; - unsigned int vendor = 0; - - fprintf(fr_log_fp, "\t\t"); - if (total < 2) { /* too short */ - fprintf(fr_log_fp, "%02x\n", *ptr); - break; - } - - if (ptr[1] > total) { /* too long */ - for (i = 0; i < total; i++) { - fprintf(fr_log_fp, "%02x ", ptr[i]); - } - break; - } - - fprintf(fr_log_fp, "%02x %02x ", ptr[0], ptr[1]); - attrlen = ptr[1] - 2; - - if ((ptr[0] == PW_VENDOR_SPECIFIC) && - (attrlen > 4)) { - vendor = (ptr[3] << 16) | (ptr[4] << 8) | ptr[5]; - fprintf(fr_log_fp, "%02x%02x%02x%02x (%u) ", - ptr[2], ptr[3], ptr[4], ptr[5], vendor); - attrlen -= 4; - ptr += 6; - total -= 6; - - } else { - ptr += 2; - total -= 2; - } - - print_hex_data(ptr, attrlen, 3); - - ptr += attrlen; - total -= attrlen; - } - } - fflush(stdout); -}