#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)
#define VQP_HDR_LEN (8)
#define VQP_VERSION (1)
#define VQP_MAX_ATTRIBUTES (12)
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;
#endif
if (!fr_ipaddr2sockaddr(dst_ipaddr, dst_port, &dst, &sizeof_dst)) {
return -1;
}
#ifdef WITH_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;
#endif
return sendto(sockfd, data, data_len, flags,
(struct sockaddr *)&dst, sizeof_dst);
}
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);
if (getsockname(sockfd, (struct sockaddr *)&dst,
&sizeof_dst) < 0) return -1;
data_len = recvfrom(sockfd, header, sizeof(header), MSG_PEEK,
(struct sockaddr *)&src, &sizeof_src);
if (data_len < 0) return -1;
if (data_len < 4) {
recvfrom(sockfd, header, sizeof(header), flags,
(struct sockaddr *)&src, &sizeof_src);
return 0;
} 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 {
#if 0
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;
}
len = (12 * (4 + 4 + MAX_VMPS_LEN));
} else {
if (len != 2) {
recvfrom(sockfd, header, sizeof(header), 0,
(struct sockaddr *)&src, &sizeof_src);
return 0;
}
len = (12 * (4 + 4 + MAX_VMPS_LEN));
}
#endif
}
len = (12 * (4 + 4 + MAX_VMPS_LEN));
buf = malloc(len);
if (!buf) return -1;
#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
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;
}
*src_port = port;
fr_sockaddr2ipaddr(&dst, sizeof_dst, dst_ipaddr, &port);
*dst_port = port;
if (src.ss_family != dst.ss_family) {
free(buf);
return -1;
}
*pbuf = buf;
return data_len;
}
RADIUS_PACKET *vqp_recv(int sockfd)
{
uint8_t *ptr;
ssize_t length;
uint32_t id;
RADIUS_PACKET *packet;
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);
if (packet->data_len < 0) {
fr_strerror_printf("Error receiving packet: %s", strerror(errno));
free(packet);
return NULL;
}
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;
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;
}
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;
}
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;
packet->code = PW_AUTHENTICATION_REQUEST;
memcpy(&id, packet->data + 4, 4);
packet->id = ntohl(id);
return packet;
}
int vqp_send(RADIUS_PACKET *packet)
{
if (!packet || !packet->data || (packet->data_len < 8)) return -1;
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;
debug_pair(vp);
*tail = vp;
tail = &(vp->next);
ptr = packet->data + VQP_HDR_LEN;
end = packet->data + packet->data_len;
while (ptr < end) {
attribute = (ptr[2] << 8) | ptr[3];
length = (ptr[4] << 8) | ptr[5];
ptr += 6;
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;
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);
}
return 0;
}
static int contents[5][VQP_MAX_ATTRIBUTES] = {
{ 0, 0, 0, 0, 0, 0 },
{ 0x0c01, 0x0c02, 0x0c03, 0x0c04, 0x0c07, 0x0c05 },
{ 0x0c03, 0x0c08, 0, 0, 0, 0 },
{ 0x0c01, 0x0c02, 0x0c03, 0x0c04, 0x0c07, 0x0c08 },
{ 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);
if (!vp) for (i = 0; i < VQP_MAX_ATTRIBUTES; i++) {
if (!contents[code][i]) break;
vps[i] = pairfind(packet->vps, contents[code][i] | 0x2000);
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;
}
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;
}
memcpy(ptr + 4, original->data + 4, 4);
ptr[3] = 2;
}
ptr += 8;
for (i = 0; i < VQP_MAX_ATTRIBUTES; i++) {
if (!vps[i]) break;
vp = vps[i];
debug_pair(vp);
ptr[0] = 0;
ptr[1] = 0;
ptr[2] = 0x0c;
ptr[3] = vp->attribute & 0xff;
ptr[4] = 0;
ptr[5] = vp->length & 0xff;
ptr += 6;
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