summaryrefslogtreecommitdiffstats
path: root/src/modules/proto_vmps
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/proto_vmps')
-rw-r--r--src/modules/proto_vmps/README.md10
-rw-r--r--src/modules/proto_vmps/all.mk8
-rw-r--r--src/modules/proto_vmps/vmps.c124
-rw-r--r--src/modules/proto_vmps/vqp.c721
-rw-r--r--src/modules/proto_vmps/vqp.h44
-rwxr-xr-xsrc/modules/proto_vmps/vqpcli.pl207
6 files changed, 1114 insertions, 0 deletions
diff --git a/src/modules/proto_vmps/README.md b/src/modules/proto_vmps/README.md
new file mode 100644
index 0000000..0c7ace4
--- /dev/null
+++ b/src/modules/proto_vmps/README.md
@@ -0,0 +1,10 @@
+# proto_vmps
+## Metadata
+<dl>
+ <dt>category</dt><dd>protocols</dd>
+</dl>
+
+## Summary
+
+Implements the VLAN Management Policy Server (VMPS) protocol, as
+used by Cisco switches to dynamically assign VLANs.
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";