vqp.c   [plain text]


/*
 * 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>
 */

#include	<freeradius-devel/ident.h>
RCSID("$Id$");

#include	<freeradius-devel/libradius.h>
#include	<freeradius-devel/udpfromto.h>
#include	<freeradius-devel/vqp.h>

#ifdef WITH_VMPS

#define MAX_VMPS_LEN (MAX_STRING_LEN - 1)

/*
 *  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,
		      fr_ipaddr_t *src_ipaddr, fr_ipaddr_t *dst_ipaddr,
		      int dst_port)
{
	struct sockaddr_storage	dst;
	socklen_t		sizeof_dst;

#ifdef WITH_UDPFROMTO
	struct sockaddr_storage	src;
	socklen_t		sizeof_src;

	fr_ipaddr2sockaddr(src_ipaddr, 0, &src, &sizeof_src);
#else
	src_ipaddr = src_ipaddr; /* -Wunused */
#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);
	}
#else
	src_ipaddr = src_ipaddr; /* -Wunused */
#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, uint8_t **pbuf, 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];
	void			*buf;
	size_t			len;
	int			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) {
		recvfrom(sockfd, header, sizeof(header), flags, 
			 (struct sockaddr *)&src, &sizeof_src);
		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)) {
		recvfrom(sockfd, header, sizeof(header), flags,
			 (struct sockaddr *)&src, &sizeof_src);
		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) {
				recvfrom(sockfd, header, sizeof(header), 0,
					 (struct sockaddr *)&src, &sizeof_src);
				return 0;
			}
			/*
			 *	Maximum length we support.
			 */
			len = (12 * (4 + 4 + MAX_VMPS_LEN));

		} else {
			if (len != 2) {
				recvfrom(sockfd, header, sizeof(header), 0, 
				 (struct sockaddr *)&src, &sizeof_src);
				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));

	buf = malloc(len);
	if (!buf) 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, buf, 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, buf, len, flags, 
				    (struct sockaddr *)&src, &sizeof_src);
	if (data_len < 0) {
		free(buf);
		return data_len;
	}

	if (!fr_sockaddr2ipaddr(&src, sizeof_src, src_ipaddr, &port)) {
		free(buf);
		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) {
		free(buf);
		return -1;
	}

	/*
	 *	Tell the caller about the data
	 */
	*pbuf = buf;

	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
	 */
	if ((packet = malloc(sizeof(*packet))) == NULL) {
		fr_strerror_printf("out of memory");
		return NULL;
	}
	memset(packet, 0, sizeof(*packet));

	packet->data_len = vqp_recvfrom(sockfd, &packet->data, 0,
					&packet->src_ipaddr, &packet->src_port,
					&packet->dst_ipaddr, &packet->dst_port);

	/*
	 *	Check for socket errors.
	 */
	if (packet->data_len < 0) {
		fr_strerror_printf("Error receiving packet: %s", strerror(errno));
		/* packet->data is NULL */
		free(packet);
		return NULL;
	}


	/*
	 *	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 (0) {
		int i;
		for (i = 0; i < packet->data_len; i++) {
			if ((i & 0x0f) == 0) fprintf(stderr, "%02x: ", i);
			fprintf(stderr, "%02x ", ptr[i]);
			if ((i & 0x0f) == 0x0f) fprintf(stderr, "\n");
		}
	  
	}

	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_AUTHENTICATION_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;
	VALUE_PAIR *vp, **tail;

	if (!packet || !packet->data) return -1;

	if (packet->data_len < VQP_HDR_LEN) return -1;

	tail = &packet->vps;

	vp = paircreate(PW_VQP_PACKET_TYPE, PW_TYPE_OCTETS);
	if (!vp) {
		fr_strerror_printf("No memory");
		return -1;
	}
	vp->lvalue = packet->data[1];
	debug_pair(vp);

	*tail = vp;
	tail = &(vp->next);

	vp = paircreate(PW_VQP_ERROR_CODE, PW_TYPE_OCTETS);
	if (!vp) {
		fr_strerror_printf("No memory");
		return -1;
	}
	vp->lvalue = packet->data[2];
	debug_pair(vp);

	*tail = vp;
	tail = &(vp->next);

	vp = paircreate(PW_VQP_SEQUENCE_NUMBER, PW_TYPE_OCTETS);
	if (!vp) {
		fr_strerror_printf("No memory");
		return -1;
	}
	vp->lvalue = packet->id; /* already set by vqp_recv */
	debug_pair(vp);

	*tail = vp;
	tail = &(vp->next);

	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) {
		attribute = (ptr[2] << 8) | ptr[3];
		length = (ptr[4] << 8) | ptr[5];
		ptr += 6;

		/*
		 *	Hack to get the dictionaries to work correctly.
		 */
		attribute |= 0x2000;
		vp = paircreate(attribute, PW_TYPE_OCTETS);
		if (!vp) {
			pairfree(&packet->vps);

			fr_strerror_printf("No memory");
			return -1;
		}

		switch (vp->type) {
		case PW_TYPE_IPADDR:
			if (length == 4) {
				memcpy(&vp->vp_ipaddr, ptr, 4);
				vp->length = 4;
				break;
			}
			vp->type = PW_TYPE_OCTETS;
			/* FALL-THROUGH */

		default:
		case PW_TYPE_OCTETS:
		case PW_TYPE_STRING:
			vp->length = (length > MAX_VMPS_LEN) ? MAX_VMPS_LEN : length;
			memcpy(vp->vp_octets, ptr, vp->length);
			vp->vp_octets[vp->length] = '\0';
			break;
		}
		ptr += length;
		debug_pair(vp);

		*tail = vp;
		tail = &(vp->next);
	}

	/*
	 *	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 = pairfind(packet->vps, PW_VQP_PACKET_TYPE);
	if (!vp) {
		fr_strerror_printf("Failed to find VQP-Packet-Type in response packet");
		return -1;
	}

	code = vp->lvalue;
	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 = pairfind(packet->vps, PW_VQP_ERROR_CODE);

	/*
	 *	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] = pairfind(packet->vps, contents[code][i] | 0x2000);

		/*
		 *	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]->length;
	}

	packet->data = malloc(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->lvalue & 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;
		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->attribute & 0xff;

		/* Length */
		ptr[4] = 0;
		ptr[5] = vp->length & 0xff;

		ptr += 6;

		/* Data */
		switch (vp->type) {
		case PW_TYPE_IPADDR:
			memcpy(ptr, &vp->vp_ipaddr, 4);
			break;

		default:
		case PW_TYPE_OCTETS:
		case PW_TYPE_STRING:
			memcpy(ptr, vp->vp_octets, vp->length);
			break;
		}
		ptr += vp->length;
	}

	return 0;
}
#endif