diff options
Diffstat (limited to 'src/modules/proto_vmps/vqp.c')
-rw-r--r-- | src/modules/proto_vmps/vqp.c | 721 |
1 files changed, 721 insertions, 0 deletions
diff --git a/src/modules/proto_vmps/vqp.c b/src/modules/proto_vmps/vqp.c new file mode 100644 index 0000000..9667387 --- /dev/null +++ b/src/modules/proto_vmps/vqp.c @@ -0,0 +1,721 @@ +/* + * vqp.c Functions to send/receive VQP packets. + * + * 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 2007 Alan DeKok <aland@deployingradius.com> + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> +#include <freeradius-devel/udpfromto.h> + +#include "vqp.h" + +#define MAX_VMPS_LEN (MAX_STRING_LEN - 1) + +/* @todo: this is a hack */ +# define debug_pair(vp) do { if (fr_debug_lvl && fr_log_fp) { \ + vp_print(fr_log_fp, vp); \ + } \ + } while(0) +/* + * http://www.openbsd.org/cgi-bin/cvsweb/src/usr.sbin/tcpdump/print-vqp.c + * + * Some of how it works: + * + * http://www.hackingciscoexposed.com/pdf/chapter12.pdf + * + * VLAN Query Protocol (VQP) + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Version | Opcode | Response Code | Data Count | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Transaction ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type (1) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Length | Data / + * / / + * / / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type (n) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Length | Data / + * / / + * / / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * VQP is layered over UDP. The default destination port is 1589. + * + */ +#define VQP_HDR_LEN (8) +#define VQP_VERSION (1) +#define VQP_MAX_ATTRIBUTES (12) + + +/* + * Wrapper for sendto which handles sendfromto, IPv6, and all + * possible combinations. + * + * FIXME: This is just a copy of rad_sendto(). + * Duplicate code is bad. + */ +static int vqp_sendto(int sockfd, void *data, size_t data_len, int flags, +#ifdef WITH_UDPFROMTO + fr_ipaddr_t *src_ipaddr, +#else + UNUSED fr_ipaddr_t *src_ipaddr, +#endif + fr_ipaddr_t *dst_ipaddr, + uint16_t dst_port) +{ + struct sockaddr_storage dst; + socklen_t sizeof_dst; + +#ifdef WITH_UDPFROMTO + struct sockaddr_storage src; + socklen_t sizeof_src; + + if (!fr_ipaddr2sockaddr(src_ipaddr, 0, &src, &sizeof_src)) { + return -1; /* Unknown address family, Die Die Die! */ + } +#endif + + if (!fr_ipaddr2sockaddr(dst_ipaddr, dst_port, &dst, &sizeof_dst)) { + return -1; /* Unknown address family, Die Die Die! */ + } + +#ifdef WITH_UDPFROMTO + /* + * Only IPv4 is supported for udpfromto. + * + * And if they don't specify a source IP address, don't + * use udpfromto. + */ + if ((dst_ipaddr->af == AF_INET) && + (src_ipaddr->af != AF_UNSPEC)) { + return sendfromto(sockfd, data, data_len, flags, + (struct sockaddr *)&src, sizeof_src, + (struct sockaddr *)&dst, sizeof_dst); + } +#endif + + /* + * No udpfromto, OR an IPv6 socket, fail gracefully. + */ + return sendto(sockfd, data, data_len, flags, + (struct sockaddr *)&dst, sizeof_dst); +} + +/* + * Wrapper for recvfrom, which handles recvfromto, IPv6, and all + * possible combinations. + * + * FIXME: This is copied from rad_recvfrom, with minor edits. + */ +static ssize_t vqp_recvfrom(int sockfd, RADIUS_PACKET *packet, int flags, + fr_ipaddr_t *src_ipaddr, uint16_t *src_port, + fr_ipaddr_t *dst_ipaddr, uint16_t *dst_port) +{ + struct sockaddr_storage src; + struct sockaddr_storage dst; + socklen_t sizeof_src = sizeof(src); + socklen_t sizeof_dst = sizeof(dst); + ssize_t data_len; + uint8_t header[4]; + size_t len; + uint16_t port; + + memset(&src, 0, sizeof_src); + memset(&dst, 0, sizeof_dst); + + /* + * 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. + */ + if (getsockname(sockfd, (struct sockaddr *)&dst, + &sizeof_dst) < 0) return -1; + + /* + * Read the length of the packet, from the packet. + * This lets us allocate the buffer to use for + * reading the rest of the packet. + */ + data_len = recvfrom(sockfd, header, sizeof(header), MSG_PEEK, + (struct sockaddr *)&src, &sizeof_src); + if (data_len < 0) return -1; + + /* + * Too little data is available, discard the packet. + */ + if (data_len < 4) { + rad_recv_discard(sockfd); + + return 0; + + /* + * Invalid version, packet type, or too many + * attributes. Die. + */ + } else if ((header[0] != VQP_VERSION) || + (header[1] < 1) || + (header[1] > 4) || + (header[3] > VQP_MAX_ATTRIBUTES)) { + rad_recv_discard(sockfd); + + return 0; + + } else { /* we got 4 bytes of data. */ + /* + * We don't care about the contents for now... + */ +#if 0 + /* + * How many attributes are in the packet. + */ + len = header[3]; + + if ((header[1] == 1) || (header[1] == 3)) { + if (len != VQP_MAX_ATTRIBUTES) { + rad_recv_discard(sockfd); + + return 0; + } + /* + * Maximum length we support. + */ + len = (12 * (4 + 4 + MAX_VMPS_LEN)); + + } else { + if (len != 2) { + rad_recv_discard(sockfd); + + return 0; + } + /* + * Maximum length we support. + */ + len = (12 * (4 + 4 + MAX_VMPS_LEN)); + } +#endif + } + + /* + * For now, be generous. + */ + len = (12 * (4 + 4 + MAX_VMPS_LEN)); + + packet->data = talloc_array(packet, uint8_t, len); + if (!packet->data) return -1; + + /* + * Receive the packet. The OS will discard any data in the + * packet after "len" bytes. + */ +#ifdef WITH_UDPFROMTO + if (dst.ss_family == AF_INET) { + data_len = recvfromto(sockfd, packet->data, len, flags, + (struct sockaddr *)&src, &sizeof_src, + (struct sockaddr *)&dst, &sizeof_dst); + } else +#endif + /* + * No udpfromto, OR an IPv6 socket. Fail gracefully. + */ + data_len = recvfrom(sockfd, packet->data, len, flags, + (struct sockaddr *)&src, &sizeof_src); + if (data_len < 0) { + return data_len; + } + + if (!fr_sockaddr2ipaddr(&src, sizeof_src, src_ipaddr, &port)) { + return -1; /* Unknown address family, Die Die Die! */ + } + *src_port = port; + + fr_sockaddr2ipaddr(&dst, sizeof_dst, dst_ipaddr, &port); + *dst_port = port; + + /* + * Different address families should never happen. + */ + if (src.ss_family != dst.ss_family) { + return -1; + } + + return data_len; +} + +RADIUS_PACKET *vqp_recv(int sockfd) +{ + uint8_t *ptr; + ssize_t length; + uint32_t id; + RADIUS_PACKET *packet; + + /* + * Allocate the new request data structure + */ + packet = rad_alloc(NULL, false); + if (!packet) { + fr_strerror_printf("out of memory"); + return NULL; + } + + length = vqp_recvfrom(sockfd, packet, 0, + &packet->src_ipaddr, &packet->src_port, + &packet->dst_ipaddr, &packet->dst_port); + + /* + * Check for socket errors. + */ + if (length < 0) { + fr_strerror_printf("Error receiving packet: %s", fr_syserror(errno)); + /* packet->data is NULL */ + rad_free(&packet); + return NULL; + } + packet->data_len = length; /* unsigned vs signed */ + + /* + * We can only receive packets formatted in a way we + * expect. However, we accept MORE attributes in a + * packet than normal implementations may send. + */ + if (packet->data_len < VQP_HDR_LEN) { + fr_strerror_printf("VQP packet is too short"); + rad_free(&packet); + return NULL; + } + + ptr = packet->data; + + if (ptr[3] > VQP_MAX_ATTRIBUTES) { + fr_strerror_printf("Too many VQP attributes"); + rad_free(&packet); + return NULL; + } + + if (packet->data_len > VQP_HDR_LEN) { + int attrlen; + + /* + * Skip the header. + */ + ptr += VQP_HDR_LEN; + length = packet->data_len - VQP_HDR_LEN; + + while (length > 0) { + if (length < 7) { + fr_strerror_printf("Packet contains malformed attribute"); + rad_free(&packet); + return NULL; + } + + /* + * Attributes are 4 bytes + * 0x00000c01 ... 0x00000c08 + */ + if ((ptr[0] != 0) || (ptr[1] != 0) || + (ptr[2] != 0x0c) || (ptr[3] < 1) || (ptr[3] > 8)) { + fr_strerror_printf("Packet contains invalid attribute"); + rad_free(&packet); + return NULL; + } + + /* + * Length is 2 bytes + * + * We support lengths 1..253, for internal + * server reasons. Also, there's no reason + * for bigger lengths to exist... admins + * won't be typing in a 32K vlan name. + * + * Except for received ethernet frames... + * they get chopped to 253 internally. + */ + if ((ptr[3] != 5) && + ((ptr[4] != 0) || (ptr[5] > MAX_VMPS_LEN))) { + fr_strerror_printf("Packet contains attribute with invalid length %02x %02x", ptr[4], ptr[5]); + rad_free(&packet); + return NULL; + } + attrlen = (ptr[4] << 8) | ptr[5]; + ptr += 6 + attrlen; + length -= (6 + attrlen); + } + } + + packet->sockfd = sockfd; + packet->vps = NULL; + + /* + * This is more than a bit of a hack. + */ + packet->code = PW_CODE_ACCESS_REQUEST; + + memcpy(&id, packet->data + 4, 4); + packet->id = ntohl(id); + + /* + * FIXME: Create a fake "request authenticator", to + * avoid duplicates? Or is the VQP sequence number + * adequate for this purpose? + */ + + return packet; +} + +/* + * We do NOT mirror the old-style RADIUS code that does encode, + * sign && send in one function. For VQP, the caller MUST perform + * each task manually, and separately. + */ +int vqp_send(RADIUS_PACKET *packet) +{ + if (!packet || !packet->data || (packet->data_len < 8)) return -1; + + /* + * Don't print out the attributes, they were printed out + * when it was encoded. + */ + + /* + * And send it on it's way. + */ + return vqp_sendto(packet->sockfd, packet->data, packet->data_len, 0, + &packet->src_ipaddr, &packet->dst_ipaddr, + packet->dst_port); +} + + +int vqp_decode(RADIUS_PACKET *packet) +{ + uint8_t *ptr, *end; + int attribute, length; + vp_cursor_t cursor; + VALUE_PAIR *vp; + + if (!packet || !packet->data) return -1; + + if (packet->data_len < VQP_HDR_LEN) return -1; + + fr_cursor_init(&cursor, &packet->vps); + vp = fr_pair_afrom_num(packet, PW_VQP_PACKET_TYPE, 0); + if (!vp) { + fr_strerror_printf("No memory"); + return -1; + } + vp->vp_integer = packet->data[1]; + debug_pair(vp); + fr_cursor_insert(&cursor, vp); + + vp = fr_pair_afrom_num(packet, PW_VQP_ERROR_CODE, 0); + if (!vp) { + fr_strerror_printf("No memory"); + return -1; + } + vp->vp_integer = packet->data[2]; + debug_pair(vp); + fr_cursor_insert(&cursor, vp); + + vp = fr_pair_afrom_num(packet, PW_VQP_SEQUENCE_NUMBER, 0); + if (!vp) { + fr_strerror_printf("No memory"); + return -1; + } + vp->vp_integer = packet->id; /* already set by vqp_recv */ + debug_pair(vp); + fr_cursor_insert(&cursor, vp); + + ptr = packet->data + VQP_HDR_LEN; + end = packet->data + packet->data_len; + + /* + * Note that vqp_recv() MUST ensure that the packet is + * formatted in a way we expect, and that vqp_recv() MUST + * be called before vqp_decode(). + */ + while (ptr < end) { + char *p; + DICT_ATTR const *da; + + if ((end - ptr) < 6) break; + + attribute = (ptr[2] << 8) | ptr[3]; + length = (ptr[4] << 8) | ptr[5]; + ptr += 6; + + if ((ptr + length) > end) break; + + /* + * Hack to get the dictionaries to work correctly. + */ + attribute |= 0x2000; + + /* + * We don't care about unknown attributes in VQP. + */ + da = dict_attrbyvalue(attribute, 0); + if (!da) continue; + + vp = fr_pair_afrom_da(packet, da); + if (!vp) { + fr_pair_list_free(&packet->vps); + + fr_strerror_printf("No memory"); + return -1; + } + + switch (vp->da->type) { + case PW_TYPE_ETHERNET: + if (length != 6) goto unknown; + + memcpy(&vp->vp_ether, ptr, 6); + vp->vp_length = 6; + break; + + case PW_TYPE_IPV4_ADDR: + if (length == 4) { + memcpy(&vp->vp_ipaddr, ptr, 4); + vp->vp_length = 4; + break; + } + + /* + * Value doesn't match the type we have for the + * valuepair so we must change it's da to an + * unknown attr. + */ + unknown: + vp->da = dict_unknown_afrom_fields(vp, vp->da->attr, vp->da->vendor); + /* FALL-THROUGH */ + + default: + case PW_TYPE_OCTETS: + if (length < 1024) { + fr_pair_value_memcpy(vp, ptr, length); + } else { + fr_pair_value_memcpy(vp, ptr, 1024); + } + break; + + case PW_TYPE_STRING: + if (length < 1024) { + vp->vp_length = length; + vp->vp_strvalue = p = talloc_array(vp, char, vp->vp_length + 1); + vp->type = VT_DATA; + memcpy(p, ptr, vp->vp_length); + p[vp->vp_length] = '\0'; + } else { + vp->vp_length = 1024; + vp->vp_strvalue = p = talloc_array(vp, char, 1025); + vp->type = VT_DATA; + memcpy(p, ptr, vp->vp_length); + p[vp->vp_length] = '\0'; + } + break; + } + + ptr += length; + debug_pair(vp); + fr_cursor_insert(&cursor, vp); + } + + /* + * FIXME: Map attributes to Calling-Station-Id, etc... + */ + + return 0; +} + +/* + * These are the MUST HAVE contents for a VQP packet. + * + * We don't allow the caller to give less than these, because + * it won't work. We don't encode more than these, because the + * clients will ignore it. + * + * FIXME: Be more generous? Look for CISCO + VQP attributes? + */ +static int contents[5][VQP_MAX_ATTRIBUTES] = { + { 0, 0, 0, 0, 0, 0 }, + { 0x0c01, 0x0c02, 0x0c03, 0x0c04, 0x0c07, 0x0c05 }, /* Join request */ + { 0x0c03, 0x0c08, 0, 0, 0, 0 }, /* Join Response */ + { 0x0c01, 0x0c02, 0x0c03, 0x0c04, 0x0c07, 0x0c08 }, /* Reconfirm */ + { 0x0c03, 0x0c08, 0, 0, 0, 0 } +}; + +int vqp_encode(RADIUS_PACKET *packet, RADIUS_PACKET *original) +{ + int i, code, length; + VALUE_PAIR *vp; + uint8_t *ptr; + VALUE_PAIR *vps[VQP_MAX_ATTRIBUTES]; + + if (!packet) { + fr_strerror_printf("Failed encoding VQP"); + return -1; + } + + if (packet->data) return 0; + + vp = fr_pair_find_by_num(packet->vps, PW_VQP_PACKET_TYPE, 0, TAG_ANY); + if (!vp) { + fr_strerror_printf("Failed to find VQP-Packet-Type in response packet"); + return -1; + } + + code = vp->vp_integer; + if ((code < 1) || (code > 4)) { + fr_strerror_printf("Invalid value %d for VQP-Packet-Type", code); + return -1; + } + + length = VQP_HDR_LEN; + memset(vps, 0, sizeof(vps)); + + vp = fr_pair_find_by_num(packet->vps, PW_VQP_ERROR_CODE, 0, TAG_ANY); + + /* + * FIXME: Map attributes from calling-station-Id, etc. + * + * Maybe do this via rlm_vqp? That's probably the + * best place to add the code... + */ + + /* + * No error: encode attributes. + */ + if (!vp) for (i = 0; i < VQP_MAX_ATTRIBUTES; i++) { + if (!contents[code][i]) break; + + vps[i] = fr_pair_find_by_num(packet->vps, contents[code][i] | 0x2000, 0, TAG_ANY); + + /* + * FIXME: Print the name... + */ + if (!vps[i]) { + fr_strerror_printf("Failed to find VQP attribute %02x", + contents[code][i]); + return -1; + } + + length += 6; + length += vps[i]->vp_length; + } + + packet->data = talloc_array(packet, uint8_t, length); + if (!packet->data) { + fr_strerror_printf("No memory"); + return -1; + } + packet->data_len = length; + + ptr = packet->data; + + ptr[0] = VQP_VERSION; + ptr[1] = code; + + if (!vp) { + ptr[2] = 0; + } else { + ptr[2] = vp->vp_integer & 0xff; + return 0; + } + + /* + * The number of attributes is hard-coded. + */ + if ((code == 1) || (code == 3)) { + uint32_t sequence; + + ptr[3] = VQP_MAX_ATTRIBUTES; + + sequence = htonl(packet->id); + memcpy(ptr + 4, &sequence, 4); + } else { + if (!original) { + fr_strerror_printf("Cannot send VQP response without request"); + return -1; + } + + /* + * Packet Sequence Number + */ + memcpy(ptr + 4, original->data + 4, 4); + + ptr[3] = 2; + } + + ptr += 8; + + /* + * Encode the VP's. + */ + for (i = 0; i < VQP_MAX_ATTRIBUTES; i++) { + if (!vps[i]) break; + if (ptr >= (packet->data + packet->data_len)) break; + + vp = vps[i]; + + debug_pair(vp); + + /* + * Type. Note that we look at only the lower 8 + * bits, as the upper 8 bits have been hacked. + * See also dictionary.vqp + */ + ptr[0] = 0; + ptr[1] = 0; + ptr[2] = 0x0c; + ptr[3] = vp->da->attr & 0xff; + + /* Length */ + ptr[4] = 0; + ptr[5] = vp->vp_length & 0xff; + + ptr += 6; + + /* Data */ + switch (vp->da->type) { + case PW_TYPE_IPV4_ADDR: + memcpy(ptr, &vp->vp_ipaddr, 4); + break; + + case PW_TYPE_ETHERNET: + memcpy(ptr, vp->vp_ether, vp->vp_length); + break; + + default: + case PW_TYPE_OCTETS: + case PW_TYPE_STRING: + memcpy(ptr, vp->vp_octets, vp->vp_length); + break; + } + ptr += vp->vp_length; + } + + return 0; +} |