/* * 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 */ RCSID("$Id$") #include #include #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; }