diff options
Diffstat (limited to 'src/modules/proto_vmps')
-rw-r--r-- | src/modules/proto_vmps/all.mk | 8 | ||||
-rw-r--r-- | src/modules/proto_vmps/vmps.c | 124 | ||||
-rw-r--r-- | src/modules/proto_vmps/vqp.c | 721 | ||||
-rw-r--r-- | src/modules/proto_vmps/vqp.h | 44 | ||||
-rwxr-xr-x | src/modules/proto_vmps/vqpcli.pl | 207 |
5 files changed, 1104 insertions, 0 deletions
diff --git a/src/modules/proto_vmps/all.mk b/src/modules/proto_vmps/all.mk new file mode 100644 index 0000000..f7d4bb0 --- /dev/null +++ b/src/modules/proto_vmps/all.mk @@ -0,0 +1,8 @@ +TARGETNAME := proto_vmps + +ifneq "$(TARGETNAME)" "" +TARGET := $(TARGETNAME).a +endif + +SOURCES := vmps.c vqp.c + diff --git a/src/modules/proto_vmps/vmps.c b/src/modules/proto_vmps/vmps.c new file mode 100644 index 0000000..1fc0a19 --- /dev/null +++ b/src/modules/proto_vmps/vmps.c @@ -0,0 +1,124 @@ +/* + * vmps.c Handle VMPS traffic. + * + * Version: $Id$ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2007 The FreeRADIUS server project + * Copyright 2007 Alan DeKok <aland@deployingradius.com> + */ + +RCSID("$Id$") + +#include <freeradius-devel/radiusd.h> +#include <freeradius-devel/protocol.h> +#include <freeradius-devel/process.h> +#include <freeradius-devel/modules.h> +#include <freeradius-devel/rad_assert.h> + +#include "vqp.h" + +static int vmps_process(REQUEST *request) +{ + DEBUG2("Doing VMPS"); + process_post_auth(0, request); + DEBUG2("Done VMPS"); + + request->packet->code = 0; /* hack for VMPS */ + request->reply->code = PW_CODE_ACCESS_ACCEPT; + + return 0; +} + +/* + * Check if an incoming request is "ok" + * + * It takes packets, not requests. It sees if the packet looks + * OK. If so, it does a number of sanity checks on it. + */ +static int vqp_socket_recv(rad_listen_t *listener) +{ + RADIUS_PACKET *packet; + RAD_REQUEST_FUNP fun = NULL; + RADCLIENT *client; + + packet = vqp_recv(listener->fd); + if (!packet) { + ERROR("%s", fr_strerror()); + return 0; + } + + if ((client = client_listener_find(listener, + &packet->src_ipaddr, + packet->src_port)) == NULL) { + rad_free(&packet); + return 0; + } + + /* + * Do new stuff. + */ + fun = vmps_process; + + if (!request_receive(NULL, listener, packet, client, fun)) { + rad_free(&packet); + return 0; + } + + return 1; +} + + +/* + * Send an authentication response packet + */ +static int vqp_socket_send(rad_listen_t *listener, REQUEST *request) +{ + rad_assert(request->listener == listener); + rad_assert(listener->send == vqp_socket_send); + + if (vqp_encode(request->reply, request->packet) < 0) { + DEBUG2("Failed encoding packet: %s\n", fr_strerror()); + return -1; + } + + return vqp_send(request->reply); +} + + +static int vqp_socket_encode(UNUSED rad_listen_t *listener, REQUEST *request) +{ + return vqp_encode(request->reply, request->packet); +} + + +static int vqp_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request) +{ + return vqp_decode(request->packet); +} + +extern fr_protocol_t proto_vmps; +fr_protocol_t proto_vmps = { + .magic = RLM_MODULE_INIT, + .name = "vmps", + .inst_size = sizeof(listen_socket_t), + .parse = common_socket_parse, + .recv = vqp_socket_recv, + .send = vqp_socket_send, + .print = common_socket_print, + .encode = vqp_socket_encode, + .decode = vqp_socket_decode +}; 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; +} diff --git a/src/modules/proto_vmps/vqp.h b/src/modules/proto_vmps/vqp.h new file mode 100644 index 0000000..2e3f02f --- /dev/null +++ b/src/modules/proto_vmps/vqp.h @@ -0,0 +1,44 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ +#ifndef FR_VQP_H +#define FR_VQP_H + +/** + * $Id$ + * + * @file vqp.h + * @brief Structures and prototypes for Cisco's VLAN Query Protocol + * + * @copyright 2007 The FreeRADIUS server project + * @copyright 2007 Alan DeKok <aland@deployingradius.com> + */ + +RCSIDH(vqp_h, "$Id$") + +#ifdef __cplusplus +extern "C" { +#endif + +RADIUS_PACKET *vqp_recv(int sockfd); +int vqp_send(RADIUS_PACKET *packet); +int vqp_decode(RADIUS_PACKET *packet); +int vqp_encode(RADIUS_PACKET *packet, RADIUS_PACKET *original); + +#ifdef __cplusplus +} +#endif + +#endif /* FR_VQP_H */ diff --git a/src/modules/proto_vmps/vqpcli.pl b/src/modules/proto_vmps/vqpcli.pl new file mode 100755 index 0000000..a877046 --- /dev/null +++ b/src/modules/proto_vmps/vqpcli.pl @@ -0,0 +1,207 @@ +#!/usr/bin/env perl + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +# +# vqpcli.pl -s localhost -v mydomain -w 10.0.0.1 -i 2/4 -m 0010.a49f.30e3 +# + +use Socket; +$|=0; +#$DEBUG=1; +$DEBUG=0; + +sub formatItem($$) { + + my $mybuf; + undef($mybuf); + + $itemheader = shift; + $itemvalue = shift; + + $mybuf = $mybuf . pack("H*",(unpack("a*",$itemheader))); # Add header + + $payload = pack("a*",(unpack("a*",$itemvalue))); + $length=length($payload); + $length= pack("H*",(unpack("a*",sprintf("%04x",$length)))); + + $mybuf = $mybuf . $length . $payload; # Add payload + length + + return $mybuf; +} + +sub parseOpts() { + use Getopt::Std; + my $errors = ""; + + getopts("s:v:w:i:m:t:c:",\%opt) or usage(); + usage() if $opt{h}; + my %request = ( + server_ip => $opt{s} || "", + client_ip => $opt{w} || "127.0.0.1", # IP to say we are - VMPS doesn't care + port_name => $opt{i} || "Fa0/1", # Default port name to use + vlan => $opt{c} || "", # Isn't really needed. + vtp_domain => $opt{v} || "", # Is kinda important + macaddr => $opt{m} || "", # Likewise... + ); + + $opt{m} =~ tr/A-Z/a-z/; + $errors=$errors . "MAC address must be in nnnn.nnnn.nnnn format\n" + if ($opt{m} !~ /[a-z0-9][a-z0-9][a-z0-9][a-z0-9]\.[a-z0-9][a-z0-9][a-z0-9][a-z0-9]\.[a-z0-9][a-z0-9][a-z0-9][a-z0-9]/); + $errors=$errors . "VTP Domain must be specified\n" if ($opt{v} !~ /.*/); + $errors=$errors . "No Server name specified\n" if ($opt{s} =~ /^$/); + print STDERR $errors if ($errors); + usage() if ($errors); + $request{macaddr} =~ s/\.//g; + + return %request; +} + +sub usage() { + print STDERR << "EOO"; +Options: +-s ip VMPS Server to query +-v domain VMPS/VTP Domain to query +-w ip client switch IP to query for +-i iface client switch Interface name (ie: Fa0/17) +-m macaddr attached device MAC address in nnnn.nnnn.nnnn format +-c vlan Vlan to reconfirm membership to + +EOO + exit(1); + +} + +sub makeVQPrequest($) { + + my $request = $_; + my $buf; + + # Header... + $buf = $buf . pack("H*",(unpack("a*","01"))); # Header bit + + # Is a request to join a vlan + $buf = $buf . pack("H*",(unpack("a*","01"))); # Is a request + + # No error + $buf = $buf . pack("H*",(unpack("a*","00"))); # No error + + # 6 data items in inbound payload + $buf = $buf . pack("H*",(unpack("a*","06"))); + + # Sequence number of request + $buf = $buf . pack("H*",(unpack("a*","000 1234"))); # Bogus sequence number + + # Add Client switch IP + $buf = $buf . formatItem("000 0c01",(sprintf("%s",unpack("a*",inet_aton($request{client_ip}))))); + + # Add Port Name + $buf = $buf . formatItem("000 0c02",$request{port_name}); # Payload + + # Add VLAN to confirm to buffer + $buf = $buf . formatItem("000 0c03",$request{vlan}); # Payload + + # Add VTP domain name + $buf = $buf . formatItem("000 0c04",$request{vtp_domain}); # Payload + + # Add UNKNOWN data to buffer... + $buf = $buf . pack("H*",(unpack("a*","000 0c07"))); # Header + $buf = $buf . pack("H*",(unpack("a*","0001 0"))); # Unknown filler + + # Add MAC address to buffer + $buf = $buf . formatItem("000 0c06",sprintf("%s",pack("H*",(unpack("a*",$request{macaddr}))))); # Payload + + return "$buf"; +} + +sub sendVQP($$) { + + my $PORTNO="1589"; + my $HOSTNAME= shift; + my $buf = shift; + + if ($DEBUG==1) { + print "==============================\n"; + print "MESSAGE SENT:\n"; + open (HEX, "|/usr/bin/hexdump"); + select HEX; + print $buf; + close HEX; + select STDOUT; + print "==============================\n"; + } + + socket(SOCKET, PF_INET, SOCK_DGRAM, getprotobyname("udp")) or die "socket: $!"; + + my $ipaddr = inet_aton($HOSTNAME); + my $portaddr = sockaddr_in($PORTNO, $ipaddr); + send(SOCKET, $buf, 0, $portaddr) == length($buf) + or die "cannot send to $HOSTNAME($PORTNO): $!"; + + $portaddr = recv(SOCKET, $buf, 1500, 0); # or die "recv: $!"; + + if ($DEBUG==1) { + print "MESSAGE RECV:\n"; + open (HEX, "|/usr/bin/hexdump"); + select HEX; + print $buf; + close HEX; + select STDOUT; + print "==============================\n"; + } + return "$buf"; +} + +sub parseVQPresp($) { + + my %response = ( + status => "", + vlan => "", + macaddr => "", + ); + + my $buf = shift; + $buf =~ /^(.)(.)(.)(.)(....)/; + my ($header,$type,$status,$size,$sequence) = + (ord($1),ord($2),ord($3),ord($4),pack("a*",(unpack("H*",$5)))); + + $buf =~ s/^........//; + + $response{status}="ALLOW" if ($status == 0); + $response{status}="DENY" if ($status == 3); + $response{status}="SHUTDOWN" if ($status == 4); + $response{status}="WRONG_DOMAIN" if ($status == 5); + + for ($i=1;$i<=$size;$i++) { + + $payload_type=pack("a*",(unpack("H*",substr($buf,0,4)))); + $payload_size=sprintf("%d",hex(pack("a*",(unpack("H*",substr($buf,4,2)))))); + $payload=substr($buf,6,$payload_size); + + if ($payload_type eq "00000c03") { + $response{vlan}=$payload; + } elsif ($payload_type eq"00000c08") { + $response{macaddr}=pack("a*",(unpack("H*",$payload))); + } + substr($buf,0,($payload_size + 6)) = ""; + } + return %response; +} + +%request=parseOpts(); +$buf = makeVQPrequest(%request); +$buf = sendVQP($request{server_ip},$buf); +%response = parseVQPresp($buf); +print "Vlan: $response{vlan}\nMAC Address: $response{macaddr} \nStatus: $response{status}\n"; |