#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/libradius.h>
#include <freeradius-devel/md5.h>
#include <fcntl.h>
#include <ctype.h>
#ifdef WITH_UDPFROMTO
#include <freeradius-devel/udpfromto.h>
#endif
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#define MAX_PACKET_LEN 4096
int fr_max_attributes = 0;
FILE *fr_log_fp = NULL;
typedef struct radius_packet_t {
uint8_t code;
uint8_t id;
uint8_t length[2];
uint8_t vector[AUTH_VECTOR_LEN];
uint8_t data[1];
} radius_packet_t;
static fr_randctx fr_rand_pool;
static int fr_rand_initialized = 0;
static unsigned int salt_offset = 0;
const char *fr_packet_codes[FR_MAX_PACKET_CODE] = {
"",
"Access-Request",
"Access-Accept",
"Access-Reject",
"Accounting-Request",
"Accounting-Response",
"Accounting-Status",
"Password-Request",
"Password-Accept",
"Password-Reject",
"Accounting-Message",
"Access-Challenge",
"Status-Server",
"Status-Client",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"Resource-Free-Request",
"Resource-Free-Response",
"Resource-Query-Request",
"Resource-Query-Response",
"Alternate-Resource-Reclaim-Request",
"NAS-Reboot-Request",
"NAS-Reboot-Response",
"28",
"Next-Passcode",
"New-Pin",
"Terminate-Session",
"Password-Expired",
"Event-Request",
"Event-Response",
"35",
"36",
"37",
"38",
"39",
"Disconnect-Request",
"Disconnect-ACK",
"Disconnect-NAK",
"CoA-Request",
"CoA-ACK",
"CoA-NAK",
"46",
"47",
"48",
"49",
"IP-Address-Allocate",
"IP-Address-Release"
};
void fr_printf_log(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if ((fr_debug_flag == 0) || !fr_log_fp) {
va_end(ap);
return;
}
vfprintf(fr_log_fp, fmt, ap);
va_end(ap);
return;
}
static void print_hex(RADIUS_PACKET *packet)
{
int i;
if (!packet->data) return;
printf(" Code:\t\t%u\n", packet->data[0]);
printf(" Id:\t\t%u\n", packet->data[1]);
printf(" Length:\t%u\n", ((packet->data[2] << 8) |
(packet->data[3])));
printf(" Vector:\t");
for (i = 4; i < 20; i++) {
printf("%02x", packet->data[i]);
}
printf("\n");
if (packet->data_len > 20) {
int total;
const uint8_t *ptr;
printf(" Data:");
total = packet->data_len - 20;
ptr = packet->data + 20;
while (total > 0) {
int attrlen;
printf("\t\t");
if (total < 2) {
printf("%02x\n", *ptr);
break;
}
if (ptr[1] > total) {
for (i = 0; i < total; i++) {
printf("%02x ", ptr[i]);
}
break;
}
printf("%02x %02x ", ptr[0], ptr[1]);
attrlen = ptr[1] - 2;
ptr += 2;
total -= 2;
for (i = 0; i < attrlen; i++) {
if ((i > 0) && ((i & 0x0f) == 0x00))
printf("\t\t\t");
printf("%02x ", ptr[i]);
if ((i & 0x0f) == 0x0f) printf("\n");
}
if ((attrlen & 0x0f) != 0x00) printf("\n");
ptr += attrlen;
total -= attrlen;
}
}
fflush(stdout);
}
static int rad_sendto(int sockfd, void *data, size_t data_len, int flags,
fr_ipaddr_t *src_ipaddr, int src_port,
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, src_port, &src, &sizeof_src);
#else
src_port = src_port;
#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);
}
void rad_recv_discard(int sockfd)
{
uint8_t header[4];
struct sockaddr_storage src;
socklen_t sizeof_src = sizeof(src);
recvfrom(sockfd, header, sizeof(header), 0,
(struct sockaddr *)&src, &sizeof_src);
}
ssize_t rad_recv_header(int sockfd, fr_ipaddr_t *src_ipaddr, int *src_port,
int *code)
{
ssize_t data_len, packet_len;
uint8_t header[4];
struct sockaddr_storage src;
socklen_t sizeof_src = sizeof(src);
data_len = recvfrom(sockfd, header, sizeof(header), MSG_PEEK,
(struct sockaddr *)&src, &sizeof_src);
if (data_len < 0) {
if ((errno == EAGAIN) || (errno == EINTR)) return 0;
return -1;
}
if (data_len < 4) {
recvfrom(sockfd, header, sizeof(header), 0,
(struct sockaddr *)&src, &sizeof_src);
return 1;
} else {
packet_len = (header[2] * 256) + header[3];
if (packet_len < AUTH_HDR_LEN) {
recvfrom(sockfd, header, sizeof(header), 0,
(struct sockaddr *)&src, &sizeof_src);
return 1;
} else if (packet_len > MAX_PACKET_LEN) {
recvfrom(sockfd, header, sizeof(header), 0,
(struct sockaddr *)&src, &sizeof_src);
return 1;
}
}
if (!fr_sockaddr2ipaddr(&src, sizeof_src, src_ipaddr, src_port)) {
recvfrom(sockfd, header, sizeof(header), 0,
(struct sockaddr *)&src, &sizeof_src);
return 1;
}
*code = header[0];
return packet_len;
}
static ssize_t rad_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) {
if ((errno == EAGAIN) || (errno == EINTR)) return 0;
return -1;
}
if (data_len < 4) {
recvfrom(sockfd, header, sizeof(header), flags,
(struct sockaddr *)&src, &sizeof_src);
return 0;
} else {
len = (header[2] * 256) + header[3];
if (len < AUTH_HDR_LEN) {
recvfrom(sockfd, header, sizeof(header), flags,
(struct sockaddr *)&src, &sizeof_src);
return 0;
} else if (len > MAX_PACKET_LEN) {
recvfrom(sockfd, header, sizeof(header), flags,
(struct sockaddr *)&src, &sizeof_src);
return 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;
}
#define AUTH_PASS_LEN (AUTH_VECTOR_LEN)
static void make_secret(uint8_t *digest, const uint8_t *vector,
const char *secret, const uint8_t *value)
{
FR_MD5_CTX context;
int i;
fr_MD5Init(&context);
fr_MD5Update(&context, vector, AUTH_VECTOR_LEN);
fr_MD5Update(&context, (const uint8_t *) secret, strlen(secret));
fr_MD5Final(digest, &context);
for ( i = 0; i < AUTH_VECTOR_LEN; i++ ) {
digest[i] ^= value[i];
}
}
#define MAX_PASS_LEN (128)
static void make_passwd(uint8_t *output, size_t *outlen,
const uint8_t *input, size_t inlen,
const char *secret, const uint8_t *vector)
{
FR_MD5_CTX context, old;
uint8_t digest[AUTH_VECTOR_LEN];
uint8_t passwd[MAX_PASS_LEN];
int i, n;
int len;
len = inlen;
if (len > MAX_PASS_LEN) len = MAX_PASS_LEN;
memcpy(passwd, input, len);
memset(passwd + len, 0, sizeof(passwd) - len);
if (len == 0) {
len = AUTH_PASS_LEN;
}
else if ((len & 0x0f) != 0) {
len += 0x0f;
len &= ~0x0f;
}
*outlen = len;
fr_MD5Init(&context);
fr_MD5Update(&context, (const uint8_t *) secret, strlen(secret));
old = context;
fr_MD5Update(&context, vector, AUTH_PASS_LEN);
for (n = 0; n < len; n += AUTH_PASS_LEN) {
if (n > 0) {
context = old;
fr_MD5Update(&context,
passwd + n - AUTH_PASS_LEN,
AUTH_PASS_LEN);
}
fr_MD5Final(digest, &context);
for (i = 0; i < AUTH_PASS_LEN; i++) {
passwd[i + n] ^= digest[i];
}
}
memcpy(output, passwd, len);
}
static void make_tunnel_passwd(uint8_t *output, size_t *outlen,
const uint8_t *input, size_t inlen, size_t room,
const char *secret, const uint8_t *vector)
{
FR_MD5_CTX context, old;
uint8_t digest[AUTH_VECTOR_LEN];
uint8_t passwd[MAX_STRING_LEN + AUTH_VECTOR_LEN];
int i, n;
int len;
if (room > 253) room = 253;
room -= 2;
room -= (room & 0x0f);
room--;
if (inlen > room) inlen = room;
len = inlen + 1;
if ((len & 0x0f) != 0) {
len += 0x0f;
len &= ~0x0f;
}
*outlen = len + 2;
memcpy(passwd + 3, input, inlen);
memset(passwd + 3 + inlen, 0, sizeof(passwd) - 3 - inlen);
passwd[0] = (0x80 | ( ((salt_offset++) & 0x0f) << 3) |
(fr_rand() & 0x07));
passwd[1] = fr_rand();
passwd[2] = inlen;
fr_MD5Init(&context);
fr_MD5Update(&context, (const uint8_t *) secret, strlen(secret));
old = context;
fr_MD5Update(&context, vector, AUTH_VECTOR_LEN);
fr_MD5Update(&context, &passwd[0], 2);
for (n = 0; n < len; n += AUTH_PASS_LEN) {
if (n > 0) {
context = old;
fr_MD5Update(&context,
passwd + 2 + n - AUTH_PASS_LEN,
AUTH_PASS_LEN);
}
fr_MD5Final(digest, &context);
for (i = 0; i < AUTH_PASS_LEN; i++) {
passwd[i + 2 + n] ^= digest[i];
}
}
memcpy(output, passwd, len + 2);
}
static uint8_t *vp2data(const RADIUS_PACKET *packet,
const RADIUS_PACKET *original,
const char *secret, const VALUE_PAIR *vp, uint8_t *ptr,
size_t room)
{
uint32_t lvalue;
size_t len;
const uint8_t *data;
uint8_t array[4];
data = vp->vp_octets;
len = vp->length;
switch(vp->type) {
case PW_TYPE_STRING:
case PW_TYPE_OCTETS:
case PW_TYPE_IFID:
case PW_TYPE_IPV6ADDR:
case PW_TYPE_IPV6PREFIX:
case PW_TYPE_ABINARY:
break;
case PW_TYPE_BYTE:
len = 1;
array[0] = vp->vp_integer & 0xff;
data = array;
break;
case PW_TYPE_SHORT:
len = 2;
array[0] = (vp->vp_integer >> 8) & 0xff;
array[1] = vp->vp_integer & 0xff;
data = array;
break;
case PW_TYPE_INTEGER:
len = 4;
lvalue = htonl(vp->vp_integer);
memcpy(array, &lvalue, sizeof(lvalue));
data = array;
break;
case PW_TYPE_IPADDR:
data = (const uint8_t *) &vp->vp_ipaddr;
len = 4;
break;
case PW_TYPE_DATE:
lvalue = htonl(vp->vp_date);
data = (const uint8_t *) &lvalue;
len = 4;
break;
case PW_TYPE_SIGNED:
{
int32_t slvalue;
len = 4;
slvalue = htonl(vp->vp_signed);
memcpy(array, &slvalue, sizeof(slvalue));
break;
}
case PW_TYPE_TLV:
data = vp->vp_tlv;
if (!data) {
fr_strerror_printf("ERROR: Cannot encode NULL TLV");
return NULL;
}
break;
default:
fr_strerror_printf("ERROR: Unknown attribute type %d", vp->type);
return NULL;
}
if (len > room) len = room;
switch (vp->flags.encrypt) {
case FLAG_ENCRYPT_USER_PASSWORD:
make_passwd(ptr, &len, data, len,
secret, packet->vector);
break;
case FLAG_ENCRYPT_TUNNEL_PASSWORD:
if (room < 18) return ptr;
switch (packet->code) {
case PW_AUTHENTICATION_ACK:
case PW_AUTHENTICATION_REJECT:
case PW_ACCESS_CHALLENGE:
default:
if (!original) {
fr_strerror_printf("ERROR: No request packet, cannot encrypt %s attribute in the vp.", vp->name);
return NULL;
}
make_tunnel_passwd(ptr, &len, data, len, room,
secret, original->vector);
break;
case PW_ACCOUNTING_REQUEST:
case PW_DISCONNECT_REQUEST:
case PW_COA_REQUEST:
make_tunnel_passwd(ptr, &len, data, len, room,
secret, packet->vector);
break;
}
break;
case FLAG_ENCRYPT_ASCEND_SECRET:
make_secret(ptr, packet->vector, secret, data);
len = AUTH_VECTOR_LEN;
break;
default:
memcpy(ptr, data, len);
break;
}
return ptr + len;
}
static VALUE_PAIR *rad_vp2tlv(VALUE_PAIR *vps)
{
int maxattr = 0;
int length;
unsigned int attribute;
uint8_t *ptr, *end;
VALUE_PAIR *vp, *tlv;
attribute = vps->attribute & 0xffff00ff;
maxattr = vps->attribute & 0x0ff;
tlv = paircreate(attribute, PW_TYPE_TLV);
if (!tlv) return NULL;
tlv->length = 0;
for (vp = vps; vp != NULL; vp = vp->next) {
if (!vp->flags.is_tlv ||
vp->flags.encoded ||
(vp->flags.encrypt != FLAG_ENCRYPT_NONE) ||
((vp->attribute & 0xffff00ff) != attribute) ||
((vp->attribute & 0x0000ff00) <= maxattr)) {
break;
}
maxattr = vp->attribute & 0xff00;
tlv->length += vp->length + 2;
}
if (!tlv->length) {
pairfree(&tlv);
return NULL;
}
tlv->vp_tlv = malloc(tlv->length);
if (!tlv->vp_tlv) {
pairfree(&tlv);
return NULL;
}
ptr = tlv->vp_tlv;
maxattr = vps->attribute & 0x0ff;
for (vp = vps; vp != NULL; vp = vp->next) {
if (!vp->flags.is_tlv ||
vp->flags.encoded ||
(vp->flags.encrypt != FLAG_ENCRYPT_NONE) ||
((vp->attribute & 0xffff00ff) != attribute) ||
((vp->attribute & 0x0000ff00) <= maxattr)) {
break;
}
maxattr = vp->attribute & 0xff00;
end = vp2data(NULL, NULL, NULL, vp, ptr + 2,
tlv->vp_tlv + tlv->length - ptr);
if (!end) {
vp->length = ptr - vp->vp_tlv;
return tlv;
}
length = (end - ptr);
if (length > 255) return NULL;
ptr[0] = (vp->attribute & 0xff00) >> 8;
ptr[1] = length;
ptr += ptr[1];
vp->flags.encoded = 1;
}
return tlv;
}
static int rad_vp2continuation(const VALUE_PAIR *vp, uint8_t *start,
uint8_t *ptr)
{
size_t left, piece;
size_t hsize = (ptr - start);
uint8_t *this = start;
const uint8_t *data;
uint8_t header[16];
if (vp->flags.encrypt != FLAG_ENCRYPT_NONE) {
return 0;
}
memcpy(header, start, hsize);
left = vp->length;
switch (vp->type) {
case PW_TYPE_TLV:
data = vp->vp_tlv;
break;
case PW_TYPE_OCTETS:
case PW_TYPE_STRING:
data = vp->vp_octets;
break;
default:
return 0;
}
while (left > 0) {
memcpy(this, header, hsize);
ptr = this + hsize;
if (left > (254 - hsize)) {
piece = 254 - hsize;
*(ptr++) = 0x80;
} else {
piece = left;
*(ptr++) = 0x00;
}
memcpy(ptr, data, piece);
this[1] = hsize + piece + 1;
this[hsize - 1] = hsize - 6 + 1 + piece;
data += piece;
ptr += piece;
left -= piece;
this = ptr;
}
return (ptr - start);
}
int rad_vp2attr(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
const char *secret, const VALUE_PAIR *vp, uint8_t *start)
{
int vendorcode;
int len, total_length;
uint32_t lvalue;
uint8_t *ptr, *length_ptr, *vsa_length_ptr, *tlv_length_ptr;
uint8_t *end;
ptr = start;
end = ptr + 255;
vendorcode = total_length = 0;
length_ptr = vsa_length_ptr = tlv_length_ptr = NULL;
if ((vendorcode = VENDOR(vp->attribute)) == 0) {
*(ptr++) = vp->attribute & 0xFF;
length_ptr = ptr;
*(ptr++) = 2;
total_length += 2;
} else {
int vsa_tlen = 1;
int vsa_llen = 1;
int vsa_offset = 0;
DICT_VENDOR *dv = dict_vendorbyvalue(vendorcode);
if (dv) {
vsa_tlen = dv->type;
vsa_llen = dv->length;
if (dv->flags) vsa_offset = 1;
}
*ptr++ = PW_VENDOR_SPECIFIC;
vsa_length_ptr = ptr;
*ptr++ = 6;
lvalue = htonl(vendorcode);
memcpy(ptr, &lvalue, 4);
ptr += 4;
total_length += 6;
switch (vsa_tlen) {
case 1:
ptr[0] = (vp->attribute & 0xFF);
break;
case 2:
ptr[0] = ((vp->attribute >> 8) & 0xFF);
ptr[1] = (vp->attribute & 0xFF);
break;
case 4:
ptr[0] = 0;
ptr[1] = 0;
ptr[2] = ((vp->attribute >> 8) & 0xFF);
ptr[3] = (vp->attribute & 0xFF);
break;
default:
return 0;
}
ptr += vsa_tlen;
switch (vsa_llen) {
case 0:
length_ptr = vsa_length_ptr;
vsa_length_ptr = NULL;
break;
case 1:
ptr[0] = 0;
length_ptr = ptr;
break;
case 2:
ptr[0] = 0;
ptr[1] = 0;
length_ptr = ptr + 1;
break;
default:
return 0;
}
ptr += vsa_llen;
if (vsa_offset) {
if (vp->length > (((size_t) 254) - (ptr - start))) {
return rad_vp2continuation(vp, start, ptr);
}
ptr[0] = 0x00;
ptr++;
if (vp->flags.is_tlv) {
*(ptr++) = (vp->attribute & 0xff00) >> 8;
tlv_length_ptr = ptr;
*(ptr++) = 2;
vsa_offset += 2;
}
}
total_length += vsa_tlen + vsa_llen + vsa_offset;
if (vsa_length_ptr) *vsa_length_ptr += vsa_tlen + vsa_llen + vsa_offset;
*length_ptr += vsa_tlen + vsa_llen + vsa_offset;
}
if (vp->flags.has_tag && (vp->type == PW_TYPE_STRING) &&
(TAG_VALID(vp->flags.tag) ||
(vp->flags.encrypt == FLAG_ENCRYPT_TUNNEL_PASSWORD))) {
ptr[0] = vp->flags.tag;
end = vp2data(packet, original, secret, vp, ptr + 1,
(end - ptr) - 1);
} else {
end = vp2data(packet, original, secret, vp, ptr,
(end - ptr));
}
if (!end) return -1;
if (vp->flags.has_tag && (vp->type == PW_TYPE_INTEGER)) {
ptr[0] = vp->flags.tag;
}
if ((end == ptr) &&
(vp->attribute != PW_CHARGEABLE_USER_IDENTITY)) return 0;
len = (end - ptr);
*length_ptr += len;
if (vsa_length_ptr) *vsa_length_ptr += len;
if (tlv_length_ptr) *tlv_length_ptr += len;
ptr += len;
total_length += len;
return total_length;
}
int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
const char *secret)
{
radius_packet_t *hdr;
uint8_t *ptr;
uint16_t total_length;
int len;
VALUE_PAIR *reply;
const char *what;
char ip_buffer[128];
uint32_t data[(MAX_PACKET_LEN + 256) / 4];
if ((packet->code > 0) && (packet->code < FR_MAX_PACKET_CODE)) {
what = fr_packet_codes[packet->code];
} else {
what = "Reply";
}
DEBUG("Sending %s of id %d to %s port %d\n",
what, packet->id,
inet_ntop(packet->dst_ipaddr.af,
&packet->dst_ipaddr.ipaddr,
ip_buffer, sizeof(ip_buffer)),
packet->dst_port);
switch (packet->code) {
case PW_AUTHENTICATION_ACK:
case PW_AUTHENTICATION_REJECT:
case PW_ACCESS_CHALLENGE:
if (!original) {
fr_strerror_printf("ERROR: Cannot sign response packet without a request packet.");
return -1;
}
break;
case PW_ACCOUNTING_REQUEST:
case PW_DISCONNECT_REQUEST:
case PW_COA_REQUEST:
memset(packet->vector, 0, sizeof(packet->vector));
break;
default:
break;
}
hdr = (radius_packet_t *) data;
hdr->code = packet->code;
hdr->id = packet->id;
memcpy(hdr->vector, packet->vector, sizeof(hdr->vector));
total_length = AUTH_HDR_LEN;
ptr = hdr->data;
packet->offset = 0;
for (reply = packet->vps; reply; reply = reply->next) {
if ((VENDOR(reply->attribute) == 0) &&
((reply->attribute & 0xFFFF) > 0xff)) {
#ifndef NDEBUG
if (reply->attribute == PW_RAW_ATTRIBUTE) {
memcpy(ptr, reply->vp_octets, reply->length);
len = reply->length;
goto next;
}
#endif
continue;
}
if (reply->attribute == PW_MESSAGE_AUTHENTICATOR) {
reply->length = AUTH_VECTOR_LEN;
memset(reply->vp_strvalue, 0, AUTH_VECTOR_LEN);
packet->offset = total_length;
}
debug_pair(reply);
len = 0;
if (reply->flags.encoded) goto next;
if (reply->flags.is_tlv) {
VALUE_PAIR *tlv = rad_vp2tlv(reply);
if (tlv) {
tlv->next = reply->next;
reply->next = tlv;
}
reply = reply->next;
}
len = rad_vp2attr(packet, original, secret, reply, ptr);
if (len < 0) return -1;
if ((total_length + len) > MAX_PACKET_LEN) {
DEBUG("WARNING: Attributes are too long for packet. Discarding data past %d bytes", total_length);
break;
}
next:
ptr += len;
total_length += len;
}
packet->data_len = total_length;
packet->data = (uint8_t *) malloc(packet->data_len);
if (!packet->data) {
fr_strerror_printf("Out of memory");
return -1;
}
memcpy(packet->data, hdr, packet->data_len);
hdr = (radius_packet_t *) packet->data;
total_length = htons(total_length);
memcpy(hdr->length, &total_length, sizeof(total_length));
return 0;
}
int rad_sign(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
const char *secret)
{
radius_packet_t *hdr = (radius_packet_t *)packet->data;
if (packet->id < 0) {
fr_strerror_printf("ERROR: RADIUS packets must be assigned an Id.");
return -1;
}
if (!packet->data || (packet->data_len < AUTH_HDR_LEN) ||
(packet->offset < 0)) {
fr_strerror_printf("ERROR: You must call rad_encode() before rad_sign()");
return -1;
}
if (packet->offset > 0) {
uint8_t calc_auth_vector[AUTH_VECTOR_LEN];
switch (packet->code) {
case PW_ACCOUNTING_REQUEST:
case PW_ACCOUNTING_RESPONSE:
case PW_DISCONNECT_REQUEST:
case PW_DISCONNECT_ACK:
case PW_DISCONNECT_NAK:
case PW_COA_REQUEST:
case PW_COA_ACK:
case PW_COA_NAK:
memset(hdr->vector, 0, AUTH_VECTOR_LEN);
break;
case PW_AUTHENTICATION_ACK:
case PW_AUTHENTICATION_REJECT:
case PW_ACCESS_CHALLENGE:
if (!original) {
fr_strerror_printf("ERROR: Cannot sign response packet without a request packet.");
return -1;
}
memcpy(hdr->vector, original->vector,
AUTH_VECTOR_LEN);
break;
default:
break;
}
fr_hmac_md5(packet->data, packet->data_len,
(const uint8_t *) secret, strlen(secret),
calc_auth_vector);
memcpy(packet->data + packet->offset + 2,
calc_auth_vector, AUTH_VECTOR_LEN);
memcpy(hdr->vector, packet->vector, AUTH_VECTOR_LEN);
}
switch (packet->code) {
case PW_AUTHENTICATION_REQUEST:
case PW_STATUS_SERVER:
break;
default:
{
uint8_t digest[16];
FR_MD5_CTX context;
fr_MD5Init(&context);
fr_MD5Update(&context, packet->data, packet->data_len);
fr_MD5Update(&context, (const uint8_t *) secret,
strlen(secret));
fr_MD5Final(digest, &context);
memcpy(hdr->vector, digest, AUTH_VECTOR_LEN);
memcpy(packet->vector, digest, AUTH_VECTOR_LEN);
break;
}
}
return 0;
}
int rad_send(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
const char *secret)
{
VALUE_PAIR *reply;
const char *what;
char ip_buffer[128];
if (!packet || (packet->sockfd < 0)) {
return 0;
}
if ((packet->code > 0) && (packet->code < FR_MAX_PACKET_CODE)) {
what = fr_packet_codes[packet->code];
} else {
what = "Reply";
}
if (!packet->data) {
if (rad_encode(packet, original, secret) < 0) {
return -1;
}
if (rad_sign(packet, original, secret) < 0) {
return -1;
}
} else if (fr_debug_flag) {
DEBUG("Sending %s of id %d to %s port %d\n", what, packet->id,
inet_ntop(packet->dst_ipaddr.af,
&packet->dst_ipaddr.ipaddr,
ip_buffer, sizeof(ip_buffer)),
packet->dst_port);
for (reply = packet->vps; reply; reply = reply->next) {
if ((VENDOR(reply->attribute) == 0) &&
((reply->attribute & 0xFFFF) > 0xff)) continue;
debug_pair(reply);
}
}
return rad_sendto(packet->sockfd, packet->data, packet->data_len, 0,
&packet->src_ipaddr, packet->src_port,
&packet->dst_ipaddr, packet->dst_port);
}
static int digest_cmp(const uint8_t *a, const uint8_t *b, size_t length)
{
int result = 0;
size_t i;
for (i = 0; i < length; i++) {
result |= a[i] ^ b[i];
}
return result;
}
static int calc_acctdigest(RADIUS_PACKET *packet, const char *secret)
{
uint8_t digest[AUTH_VECTOR_LEN];
FR_MD5_CTX context;
memset(packet->data + 4, 0, AUTH_VECTOR_LEN);
fr_MD5Init(&context);
fr_MD5Update(&context, packet->data, packet->data_len);
fr_MD5Update(&context, (const uint8_t *) secret, strlen(secret));
fr_MD5Final(digest, &context);
if (digest_cmp(digest, packet->vector, AUTH_VECTOR_LEN) != 0) return 2;
return 0;
}
static int calc_replydigest(RADIUS_PACKET *packet, RADIUS_PACKET *original,
const char *secret)
{
uint8_t calc_digest[AUTH_VECTOR_LEN];
FR_MD5_CTX context;
if (original == NULL) {
return 3;
}
memcpy(packet->data + 4, original->vector, AUTH_VECTOR_LEN);
fr_MD5Init(&context);
fr_MD5Update(&context, packet->data, packet->data_len);
fr_MD5Update(&context, (const uint8_t *) secret, strlen(secret));
fr_MD5Final(calc_digest, &context);
memcpy(packet->data + 4, packet->vector, AUTH_VECTOR_LEN);
if (digest_cmp(packet->vector, calc_digest, AUTH_VECTOR_LEN) != 0) return 2;
return 0;
}
int rad_packet_ok(RADIUS_PACKET *packet, int flags)
{
uint8_t *attr;
int totallen;
int count;
radius_packet_t *hdr;
char host_ipaddr[128];
int require_ma = 0;
int seen_ma = 0;
int num_attributes;
if (packet->data_len < AUTH_HDR_LEN) {
fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: too short (received %d < minimum %d)",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)),
packet->data_len, AUTH_HDR_LEN);
return 0;
}
if (packet->data_len > MAX_PACKET_LEN) {
fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: too long (received %d > maximum %d)",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)),
packet->data_len, MAX_PACKET_LEN);
return 0;
}
totallen = (packet->data[2] << 8) | packet->data[3];
hdr = (radius_packet_t *)packet->data;
if ((hdr->code == 0) ||
(hdr->code >= FR_MAX_PACKET_CODE)) {
fr_strerror_printf("WARNING: Bad RADIUS packet from host %s: unknown packet code%d ",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)),
hdr->code);
return 0;
}
if (hdr->code == PW_STATUS_SERVER) require_ma = 1;
if (flags) require_ma = 1;
if (totallen < AUTH_HDR_LEN) {
fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: too short (length %d < minimum %d)",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)),
totallen, AUTH_HDR_LEN);
return 0;
}
if (totallen > MAX_PACKET_LEN) {
fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: too long (length %d > maximum %d)",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)),
totallen, MAX_PACKET_LEN);
return 0;
}
if (packet->data_len < totallen) {
fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: received %d octets, packet length says %d",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)),
packet->data_len, totallen);
return 0;
}
if (packet->data_len > totallen) {
memset(packet->data + totallen, 0, packet->data_len - totallen);
packet->data_len = totallen;
}
attr = hdr->data;
count = totallen - AUTH_HDR_LEN;
num_attributes = 0;
while (count > 0) {
if (count < 2) {
fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: attribute header overflows the packet",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)));
return 0;
}
if (attr[0] == 0) {
fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: Invalid attribute 0",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)));
return 0;
}
if (attr[1] < 2) {
fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: attribute %u too short",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)),
attr[0]);
return 0;
}
if (count < attr[1]) {
fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: attribute %u data overflows the packet",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)),
attr[0]);
return 0;
}
switch (attr[0]) {
default:
break;
case PW_EAP_MESSAGE:
require_ma = 1;
break;
case PW_MESSAGE_AUTHENTICATOR:
if (attr[1] != 2 + AUTH_VECTOR_LEN) {
fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: Message-Authenticator has invalid length %d",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)),
attr[1] - 2);
return 0;
}
seen_ma = 1;
break;
}
count -= attr[1];
attr += attr[1];
num_attributes++;
}
if (count != 0) {
fr_strerror_printf("WARNING: Malformed RADIUS packet from host %s: packet attributes do NOT exactly fill the packet",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)));
return 0;
}
if ((fr_max_attributes > 0) &&
(num_attributes > fr_max_attributes)) {
fr_strerror_printf("WARNING: Possible DoS attack from host %s: Too many attributes in request (received %d, max %d are allowed).",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)),
num_attributes, fr_max_attributes);
return 0;
}
if (require_ma && ! seen_ma) {
fr_strerror_printf("WARNING: Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)));
return 0;
}
packet->code = hdr->code;
packet->id = hdr->id;
memcpy(packet->vector, hdr->vector, AUTH_VECTOR_LEN);
return 1;
}
RADIUS_PACKET *rad_recv(int fd, int flags)
{
int sock_flags = 0;
RADIUS_PACKET *packet;
if ((packet = malloc(sizeof(*packet))) == NULL) {
fr_strerror_printf("out of memory");
return NULL;
}
memset(packet, 0, sizeof(*packet));
if (flags & 0x02) {
sock_flags = MSG_PEEK;
flags &= ~0x02;
}
packet->data_len = rad_recvfrom(fd, &packet->data, sock_flags,
&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 > MAX_PACKET_LEN) {
fr_strerror_printf("Discarding packet: Larger than RFC limitation of 4096 bytes.");
free(packet);
return NULL;
}
if ((packet->data_len == 0) || !packet->data) {
fr_strerror_printf("Empty packet: Socket is not ready.");
free(packet);
return NULL;
}
if (!rad_packet_ok(packet, flags)) {
rad_free(&packet);
return NULL;
}
packet->sockfd = fd;
packet->vps = NULL;
if (fr_debug_flag) {
char host_ipaddr[128];
if ((packet->code > 0) && (packet->code < FR_MAX_PACKET_CODE)) {
DEBUG("rad_recv: %s packet from host %s port %d",
fr_packet_codes[packet->code],
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)),
packet->src_port);
} else {
DEBUG("rad_recv: Packet from host %s port %d code=%d",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)),
packet->src_port,
packet->code);
}
DEBUG(", id=%d, length=%d\n", packet->id, packet->data_len);
}
return packet;
}
int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original,
const char *secret)
{
uint8_t *ptr;
int length;
int attrlen;
if (!packet || !packet->data) return -1;
ptr = packet->data + AUTH_HDR_LEN;
length = packet->data_len - AUTH_HDR_LEN;
while (length > 0) {
uint8_t msg_auth_vector[AUTH_VECTOR_LEN];
uint8_t calc_auth_vector[AUTH_VECTOR_LEN];
attrlen = ptr[1];
switch (ptr[0]) {
default:
break;
case PW_MESSAGE_AUTHENTICATOR:
memcpy(msg_auth_vector, &ptr[2], sizeof(msg_auth_vector));
memset(&ptr[2], 0, AUTH_VECTOR_LEN);
switch (packet->code) {
default:
break;
case PW_ACCOUNTING_REQUEST:
case PW_ACCOUNTING_RESPONSE:
case PW_DISCONNECT_REQUEST:
case PW_DISCONNECT_ACK:
case PW_DISCONNECT_NAK:
case PW_COA_REQUEST:
case PW_COA_ACK:
case PW_COA_NAK:
memset(packet->data + 4, 0, AUTH_VECTOR_LEN);
break;
case PW_AUTHENTICATION_ACK:
case PW_AUTHENTICATION_REJECT:
case PW_ACCESS_CHALLENGE:
if (!original) {
fr_strerror_printf("ERROR: Cannot validate Message-Authenticator in response packet without a request packet.");
return -1;
}
memcpy(packet->data + 4, original->vector, AUTH_VECTOR_LEN);
break;
}
fr_hmac_md5(packet->data, packet->data_len,
(const uint8_t *) secret, strlen(secret),
calc_auth_vector);
if (digest_cmp(calc_auth_vector, msg_auth_vector,
sizeof(calc_auth_vector)) != 0) {
char buffer[32];
fr_strerror_printf("Received packet from %s with invalid Message-Authenticator! (Shared secret is incorrect.)",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
buffer, sizeof(buffer)));
return -1;
}
memcpy(&ptr[2], msg_auth_vector, AUTH_VECTOR_LEN);
memcpy(packet->data + 4, packet->vector, AUTH_VECTOR_LEN);
break;
}
ptr += attrlen;
length -= attrlen;
}
if ((packet->code == 0) || (packet->code >= FR_MAX_PACKET_CODE)) {
char buffer[32];
fr_strerror_printf("Received Unknown packet code %d "
"from client %s port %d: Cannot validate signature.",
packet->code,
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
buffer, sizeof(buffer)),
packet->src_port);
return -1;
}
switch(packet->code) {
int rcode;
char buffer[32];
case PW_AUTHENTICATION_REQUEST:
case PW_STATUS_SERVER:
break;
case PW_COA_REQUEST:
case PW_DISCONNECT_REQUEST:
case PW_ACCOUNTING_REQUEST:
if (calc_acctdigest(packet, secret) > 1) {
fr_strerror_printf("Received %s packet "
"from client %s with invalid signature! (Shared secret is incorrect.)",
fr_packet_codes[packet->code],
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
buffer, sizeof(buffer)));
return -1;
}
break;
case PW_AUTHENTICATION_ACK:
case PW_AUTHENTICATION_REJECT:
case PW_ACCESS_CHALLENGE:
case PW_ACCOUNTING_RESPONSE:
case PW_DISCONNECT_ACK:
case PW_DISCONNECT_NAK:
case PW_COA_ACK:
case PW_COA_NAK:
rcode = calc_replydigest(packet, original, secret);
if (rcode > 1) {
fr_strerror_printf("Received %s packet "
"from home server %s port %d with invalid signature! (Shared secret is incorrect.)",
fr_packet_codes[packet->code],
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
buffer, sizeof(buffer)),
packet->src_port);
return -1;
}
break;
default:
fr_strerror_printf("Received Unknown packet code %d "
"from client %s port %d: Cannot validate signature",
packet->code,
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
buffer, sizeof(buffer)),
packet->src_port);
return -1;
}
return 0;
}
static VALUE_PAIR *data2vp(const RADIUS_PACKET *packet,
const RADIUS_PACKET *original,
const char *secret,
unsigned int attribute, size_t length,
const uint8_t *data, VALUE_PAIR *vp)
{
int offset = 0;
if (length > 253) length = 253;
vp->length = length;
vp->operator = T_OP_EQ;
vp->next = NULL;
if (vp->flags.has_tag) {
if (TAG_VALID(data[0]) ||
(vp->flags.encrypt == FLAG_ENCRYPT_TUNNEL_PASSWORD)) {
vp->flags.tag = data[0];
if ((vp->type == PW_TYPE_STRING) ||
(vp->type == PW_TYPE_OCTETS)) offset = 1;
}
}
memcpy(&vp->vp_octets[0], data + offset, length - offset);
vp->length -= offset;
switch (vp->flags.encrypt) {
case FLAG_ENCRYPT_USER_PASSWORD:
if (original) {
rad_pwdecode((char *)vp->vp_strvalue,
vp->length, secret,
original->vector);
} else {
rad_pwdecode((char *)vp->vp_strvalue,
vp->length, secret,
packet->vector);
}
if (vp->attribute == PW_USER_PASSWORD) {
vp->length = strlen(vp->vp_strvalue);
}
break;
case FLAG_ENCRYPT_TUNNEL_PASSWORD:
if (!original) goto raw;
if (rad_tunnel_pwdecode(vp->vp_octets, &vp->length,
secret, original->vector) < 0) {
goto raw;
}
break;
case FLAG_ENCRYPT_ASCEND_SECRET:
if (!original) {
goto raw;
} else {
uint8_t my_digest[AUTH_VECTOR_LEN];
make_secret(my_digest,
original->vector,
secret, data);
memcpy(vp->vp_strvalue, my_digest,
AUTH_VECTOR_LEN );
vp->vp_strvalue[AUTH_VECTOR_LEN] = '\0';
vp->length = strlen(vp->vp_strvalue);
}
break;
default:
break;
}
switch (vp->type) {
case PW_TYPE_STRING:
case PW_TYPE_OCTETS:
case PW_TYPE_ABINARY:
break;
case PW_TYPE_BYTE:
if (vp->length != 1) goto raw;
vp->vp_integer = vp->vp_octets[0];
break;
case PW_TYPE_SHORT:
if (vp->length != 2) goto raw;
vp->vp_integer = (vp->vp_octets[0] << 8) | vp->vp_octets[1];
break;
case PW_TYPE_INTEGER:
if (vp->length != 4) goto raw;
memcpy(&vp->vp_integer, vp->vp_octets, 4);
vp->vp_integer = ntohl(vp->vp_integer);
if (vp->flags.has_tag) vp->vp_integer &= 0x00ffffff;
{
DICT_VALUE *dval;
dval = dict_valbyattr(vp->attribute,
vp->vp_integer);
if (dval) {
strlcpy(vp->vp_strvalue,
dval->name,
sizeof(vp->vp_strvalue));
}
}
break;
case PW_TYPE_DATE:
if (vp->length != 4) goto raw;
memcpy(&vp->vp_date, vp->vp_octets, 4);
vp->vp_date = ntohl(vp->vp_date);
break;
case PW_TYPE_IPADDR:
if (vp->length != 4) goto raw;
memcpy(&vp->vp_ipaddr, vp->vp_octets, 4);
break;
case PW_TYPE_IFID:
if (vp->length != 8) goto raw;
break;
case PW_TYPE_IPV6ADDR:
if (vp->length != 16) goto raw;
break;
case PW_TYPE_IPV6PREFIX:
if (vp->length < 2 || vp->length > 18) goto raw;
if (vp->vp_octets[1] > 128) goto raw;
if (vp->length < 18) {
memset(vp->vp_octets + vp->length, 0,
18 - vp->length);
}
break;
case PW_TYPE_SIGNED:
if (vp->length != 4) goto raw;
memcpy(&vp->vp_integer, vp->vp_octets, 4);
vp->vp_integer = ntohl(vp->vp_integer);
memcpy(&vp->vp_signed, &vp->vp_integer, 4);
break;
case PW_TYPE_TLV:
vp->length = length;
vp->vp_tlv = malloc(length);
if (!vp->vp_tlv) {
pairfree(&vp);
fr_strerror_printf("No memory");
return NULL;
}
memcpy(vp->vp_tlv, data, length);
break;
case PW_TYPE_COMBO_IP:
if (vp->length == 4) {
vp->type = PW_TYPE_IPADDR;
memcpy(&vp->vp_ipaddr, vp->vp_octets, 4);
break;
} else if (vp->length == 16) {
vp->type = PW_TYPE_IPV6ADDR;
break;
}
default:
raw:
{
VALUE_PAIR *vp2;
vp2 = pairalloc(NULL);
if (!vp2) {
pairfree(&vp);
return NULL;
}
pairfree(&vp);
vp = vp2;
if (!paircreate_raw(attribute, PW_TYPE_OCTETS, vp)) {
return NULL;
}
vp->length = length;
memcpy(vp->vp_octets, data, length);
}
break;
}
return vp;
}
static void rad_sortvp(VALUE_PAIR **head)
{
int swapped;
VALUE_PAIR *vp, **tail;
do {
swapped = 0;
tail = head;
while (*tail) {
vp = *tail;
if (!vp->next) break;
if (vp->attribute > vp->next->attribute) {
*tail = vp->next;
vp->next = (*tail)->next;
(*tail)->next = vp;
swapped = 1;
}
tail = &(vp->next);
}
} while (swapped);
}
static uint8_t *rad_coalesce(unsigned int attribute, size_t length,
uint8_t *data,
size_t packet_length, size_t *ptlv_length)
{
uint32_t lvalue;
size_t tlv_length = length;
uint8_t *ptr, *tlv, *tlv_data;
for (ptr = data + length;
ptr != (data + packet_length);
ptr += ptr[1]) {
if ((ptr[0] != PW_VENDOR_SPECIFIC) ||
(ptr[1] < (2 + 4 + 3)) ||
(ptr[2] != 0) || (ptr[3] != 0)) {
continue;
}
memcpy(&lvalue, ptr + 2, 4);
lvalue = ntohl(lvalue);
lvalue <<= 16;
lvalue |= ptr[2 + 4];
if (lvalue != attribute) continue;
if ((ptr[2 + 4 + 1]) < 3) break;
tlv_length += ptr[2 + 4 + 1] - 3;
if ((ptr[2 + 4 + 1 + 1] & 0x80) == 0) break;
}
tlv = tlv_data = malloc(tlv_length);
if (!tlv_data) return NULL;
memcpy(tlv, data, length);
tlv += length;
for (ptr = data + length;
ptr != (data + packet_length);
ptr += ptr[1]) {
int this_length;
if ((ptr[0] != PW_VENDOR_SPECIFIC) ||
(ptr[1] < (2 + 4 + 3)) ||
(ptr[2] != 0) || (ptr[3] != 0)) {
continue;
}
memcpy(&lvalue, ptr + 2, 4);
lvalue = ntohl(lvalue);
lvalue <<= 16;
lvalue |= ptr[2 + 4];
if (lvalue != attribute) continue;
if ((ptr[2 + 4 + 1]) < 3) break;
this_length = ptr[2 + 4 + 1] - 3;
memcpy(tlv, ptr + 2 + 4 + 3, this_length);
tlv += this_length;
ptr[2 + 4] = 0;
if ((ptr[2 + 4 + 1 + 1] & 0x80) == 0) break;
}
*ptlv_length = tlv_length;
return tlv_data;
}
static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
const RADIUS_PACKET *original,
const char *secret, int attribute,
int length,
uint8_t *data, size_t packet_length,
int flag, DICT_ATTR *da)
{
size_t tlv_length, left;
uint8_t *ptr;
uint8_t *tlv_data;
VALUE_PAIR *vp, *head, **tail;
if (flag) {
tlv_data = rad_coalesce(attribute, length,
data, packet_length, &tlv_length);
if (!tlv_data) return NULL;
} else {
tlv_data = data;
tlv_length = length;
}
if (!da || (da->type != PW_TYPE_TLV)) {
not_well_formed:
if (tlv_data == data) {
tlv_data = malloc(tlv_length);
if (!tlv_data) return NULL;
memcpy(tlv_data, data, tlv_length);
}
vp = paircreate(attribute, PW_TYPE_OCTETS);
if (!vp) return NULL;
vp->type = PW_TYPE_TLV;
vp->flags.encrypt = FLAG_ENCRYPT_NONE;
vp->flags.has_tag = 0;
vp->flags.is_tlv = 0;
vp->vp_tlv = tlv_data;
vp->length = tlv_length;
return vp;
}
left = tlv_length;
for (ptr = tlv_data;
ptr != (tlv_data + tlv_length);
ptr += ptr[1]) {
if ((left < 2) ||
(ptr[1] < 2) ||
(ptr[1] > left)) {
goto not_well_formed;
}
left -= ptr[1];
}
head = NULL;
tail = &head;
for (ptr = tlv_data;
ptr != (tlv_data + tlv_length);
ptr += ptr[1]) {
vp = paircreate(attribute | (ptr[0] << 8), PW_TYPE_OCTETS);
if (!vp) {
pairfree(&head);
goto not_well_formed;
}
vp = data2vp(packet, original, secret,
ptr[0], ptr[1] - 2, ptr + 2, vp);
if (!vp) {
pairfree(&head);
goto not_well_formed;
}
*tail = vp;
tail = &(vp->next);
}
if (tlv_data != data) free(tlv_data);
if (head->next) rad_sortvp(&head);
return head;
}
VALUE_PAIR *rad_attr2vp(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
const char *secret, int attribute, int length,
const uint8_t *data)
{
VALUE_PAIR *vp;
vp = paircreate(attribute, PW_TYPE_OCTETS);
if (!vp) return NULL;
return data2vp(packet, original, secret, attribute, length, data, vp);
}
int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
const char *secret)
{
uint32_t lvalue;
uint32_t vendorcode;
VALUE_PAIR **tail;
VALUE_PAIR *pair;
uint8_t *ptr, *vsa_ptr;
int packet_length;
int attribute;
int attrlen;
int vendorlen;
radius_packet_t *hdr;
int vsa_tlen, vsa_llen, vsa_offset;
DICT_VENDOR *dv = NULL;
int num_attributes = 0;
hdr = (radius_packet_t *)packet->data;
ptr = hdr->data;
packet_length = packet->data_len - AUTH_HDR_LEN;
for (tail = &packet->vps; *tail != NULL; tail = &((*tail)->next)) {
}
vendorcode = 0;
vendorlen = 0;
vsa_tlen = vsa_llen = 1;
vsa_offset = 0;
while (packet_length > 0) {
attribute = -1;
attrlen = -1;
if (vendorcode == 0) {
if ((packet_length < 2) ||
(ptr[0] == 0) || (ptr[1] < 2) ||
(ptr[1] > packet_length)) break;
attribute = *ptr++;
attrlen = *ptr++;
attrlen -= 2;
packet_length -= 2;
if (attribute != PW_VENDOR_SPECIFIC) goto create_pair;
if (attrlen <= 4) goto create_pair;
vendorlen = 0;
}
if (vendorlen == 0) {
uint8_t *subptr;
int sublen;
int myvendor;
memcpy(&lvalue, ptr, 4);
myvendor = ntohl(lvalue);
if (myvendor == 0) goto create_pair;
if (myvendor > 65535) goto create_pair;
vsa_tlen = vsa_llen = 1;
vsa_offset = 0;
dv = dict_vendorbyvalue(myvendor);
if (dv) {
vsa_tlen = dv->type;
vsa_llen = dv->length;
if (dv->flags) vsa_offset = 1;
}
subptr = ptr + 4;
sublen = attrlen - 4;
do {
int myattr = 0;
if (sublen < (vsa_tlen + vsa_llen + vsa_offset)) goto create_pair;
switch (vsa_tlen) {
case 1:
myattr = subptr[0];
break;
case 2:
myattr = (subptr[0] << 8) | subptr[1];
break;
case 4:
if ((subptr[0] != 0) ||
(subptr[1] != 0)) goto create_pair;
myattr = (subptr[2] << 8) | subptr[3];
break;
default:
goto create_pair;
}
switch (vsa_llen) {
case 0:
attribute = (myvendor << 16) | myattr;
ptr += 4 + vsa_tlen;
attrlen -= (4 + vsa_tlen);
packet_length -= 4 + vsa_tlen;
goto create_pair;
case 1:
if (subptr[vsa_tlen] < (vsa_tlen + vsa_llen + vsa_offset))
goto create_pair;
if (subptr[vsa_tlen] > sublen)
goto create_pair;
if (vsa_offset &&
((subptr[vsa_tlen + vsa_llen] & 0x7f) != 0))
goto create_pair;
sublen -= subptr[vsa_tlen];
subptr += subptr[vsa_tlen];
break;
case 2:
if (subptr[vsa_tlen] != 0) goto create_pair;
if (subptr[vsa_tlen + 1] < (vsa_tlen + vsa_llen))
goto create_pair;
if (subptr[vsa_tlen + 1] > sublen)
goto create_pair;
sublen -= subptr[vsa_tlen + 1];
subptr += subptr[vsa_tlen + 1];
break;
default:
goto create_pair;
}
} while (sublen > 0);
vendorcode = myvendor;
vendorlen = attrlen - 4;
packet_length -= 4;
ptr += 4;
}
switch (vsa_tlen) {
case 1:
attribute = ptr[0];
break;
case 2:
attribute = (ptr[0] << 8) | ptr[1];
break;
default:
return -1;
}
attribute |= (vendorcode << 16);
vsa_ptr = ptr;
ptr += vsa_tlen;
switch (vsa_llen) {
case 1:
attrlen = ptr[0] - (vsa_tlen + vsa_llen + vsa_offset);
break;
case 2:
attrlen = ptr[1] - (vsa_tlen + vsa_llen);
break;
default:
return -1;
}
ptr += vsa_llen + vsa_offset;
vendorlen -= vsa_tlen + vsa_llen + vsa_offset + attrlen;
if (vendorlen == 0) vendorcode = 0;
packet_length -= (vsa_tlen + vsa_llen + vsa_offset);
if (attrlen == 0) goto next;
if (attribute == 0x60b50000) goto next;
if (vsa_offset) {
DICT_ATTR *da;
da = dict_attrbyvalue(attribute);
if (((vsa_ptr[2] & 0x80) == 0) &&
da && (da->type != PW_TYPE_TLV)) goto create_pair;
pair = rad_continuation2vp(packet, original, secret,
attribute, attrlen, ptr,
packet_length,
((vsa_ptr[2] & 0x80) != 0),
da);
goto created_pair;
}
create_pair:
if (!attrlen &&
(attribute != PW_CHARGEABLE_USER_IDENTITY)) goto next;
pair = rad_attr2vp(packet, original, secret,
attribute, attrlen, ptr);
if (!pair) {
pairfree(&packet->vps);
fr_strerror_printf("out of memory");
return -1;
}
created_pair:
*tail = pair;
while (pair) {
num_attributes++;
debug_pair(pair);
tail = &pair->next;
pair = pair->next;
}
if ((fr_max_attributes > 0) &&
(num_attributes > fr_max_attributes)) {
char host_ipaddr[128];
pairfree(&packet->vps);
fr_strerror_printf("WARNING: Possible DoS attack from host %s: Too many attributes in request (received %d, max %d are allowed).",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)),
num_attributes, fr_max_attributes);
return -1;
}
next:
ptr += attrlen;
packet_length -= attrlen;
}
fr_rand_seed(packet->data, AUTH_HDR_LEN);
return 0;
}
int rad_pwencode(char *passwd, size_t *pwlen, const char *secret,
const uint8_t *vector)
{
FR_MD5_CTX context, old;
uint8_t digest[AUTH_VECTOR_LEN];
int i, n, secretlen;
int len;
len = *pwlen;
if (len > 128) len = 128;
if (len == 0) {
memset(passwd, 0, AUTH_PASS_LEN);
len = AUTH_PASS_LEN;
} else if ((len % AUTH_PASS_LEN) != 0) {
memset(&passwd[len], 0, AUTH_PASS_LEN - (len % AUTH_PASS_LEN));
len += AUTH_PASS_LEN - (len % AUTH_PASS_LEN);
}
*pwlen = len;
secretlen = strlen(secret);
fr_MD5Init(&context);
fr_MD5Update(&context, (const uint8_t *) secret, secretlen);
old = context;
for (n = 0; n < len; n += AUTH_PASS_LEN) {
if (n == 0) {
fr_MD5Update(&context, vector, AUTH_PASS_LEN);
fr_MD5Final(digest, &context);
} else {
context = old;
fr_MD5Update(&context,
(uint8_t *) passwd + n - AUTH_PASS_LEN,
AUTH_PASS_LEN);
fr_MD5Final(digest, &context);
}
for (i = 0; i < AUTH_PASS_LEN; i++) {
passwd[i + n] ^= digest[i];
}
}
return 0;
}
int rad_pwdecode(char *passwd, size_t pwlen, const char *secret,
const uint8_t *vector)
{
FR_MD5_CTX context, old;
uint8_t digest[AUTH_VECTOR_LEN];
int i;
size_t n, secretlen;
if (pwlen > 128) pwlen = 128;
if (pwlen == 0) goto done;
secretlen = strlen(secret);
fr_MD5Init(&context);
fr_MD5Update(&context, (const uint8_t *) secret, secretlen);
old = context;
for (n = 0; n < pwlen; n += AUTH_PASS_LEN) {
if (n == 0) {
fr_MD5Update(&context, vector, AUTH_VECTOR_LEN);
fr_MD5Final(digest, &context);
context = old;
if (pwlen > AUTH_PASS_LEN) {
fr_MD5Update(&context, (uint8_t *) passwd,
AUTH_PASS_LEN);
}
} else {
fr_MD5Final(digest, &context);
context = old;
if (pwlen > (n + AUTH_PASS_LEN)) {
fr_MD5Update(&context, (uint8_t *) passwd + n,
AUTH_PASS_LEN);
}
}
for (i = 0; i < AUTH_PASS_LEN; i++) {
passwd[i + n] ^= digest[i];
}
}
done:
passwd[pwlen] = '\0';
return strlen(passwd);
}
int rad_tunnel_pwencode(char *passwd, size_t *pwlen, const char *secret,
const uint8_t *vector)
{
uint8_t buffer[AUTH_VECTOR_LEN + MAX_STRING_LEN + 3];
unsigned char digest[AUTH_VECTOR_LEN];
char* salt;
int i, n, secretlen;
unsigned len, n2;
len = *pwlen;
if (len > 127) len = 127;
for (n=len ; n>=0 ; n--) passwd[n+3] = passwd[n];
salt = passwd;
passwd += 2;
*passwd = len;
len += 1;
salt[0] = (0x80 | ( ((salt_offset++) & 0x0f) << 3) |
(fr_rand() & 0x07));
salt[1] = fr_rand();
n = len % AUTH_PASS_LEN;
if (n) {
n = AUTH_PASS_LEN - n;
for (; n > 0; n--, len++)
passwd[len] = 0;
}
*pwlen = len + 2;
secretlen = strlen(secret);
memcpy(buffer, secret, secretlen);
for (n2 = 0; n2 < len; n2+=AUTH_PASS_LEN) {
if (!n2) {
memcpy(buffer + secretlen, vector, AUTH_VECTOR_LEN);
memcpy(buffer + secretlen + AUTH_VECTOR_LEN, salt, 2);
fr_md5_calc(digest, buffer, secretlen + AUTH_VECTOR_LEN + 2);
} else {
memcpy(buffer + secretlen, passwd + n2 - AUTH_PASS_LEN, AUTH_PASS_LEN);
fr_md5_calc(digest, buffer, secretlen + AUTH_PASS_LEN);
}
for (i = 0; i < AUTH_PASS_LEN; i++) {
passwd[i + n2] ^= digest[i];
}
}
passwd[n2] = 0;
return 0;
}
int rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, const char *secret,
const uint8_t *vector)
{
FR_MD5_CTX context, old;
uint8_t digest[AUTH_VECTOR_LEN];
int secretlen;
unsigned i, n, len, reallen;
len = *pwlen;
if (len < 2) {
fr_strerror_printf("tunnel password is too short");
return -1;
}
if (len <= 3) {
passwd[0] = 0;
*pwlen = 0;
return 0;
}
len -= 2;
secretlen = strlen(secret);
fr_MD5Init(&context);
fr_MD5Update(&context, (const uint8_t *) secret, secretlen);
old = context;
fr_MD5Update(&context, vector, AUTH_VECTOR_LEN);
fr_MD5Update(&context, passwd, 2);
reallen = 0;
for (n = 0; n < len; n += AUTH_PASS_LEN) {
int base = 0;
if (n == 0) {
fr_MD5Final(digest, &context);
context = old;
reallen = passwd[2] ^ digest[0];
if (reallen >= len) {
fr_strerror_printf("tunnel password is too long for the attribute");
return -1;
}
fr_MD5Update(&context, passwd + 2, AUTH_PASS_LEN);
base = 1;
} else {
fr_MD5Final(digest, &context);
context = old;
fr_MD5Update(&context, passwd + n + 2, AUTH_PASS_LEN);
}
for (i = base; i < AUTH_PASS_LEN; i++) {
passwd[n + i - 1] = passwd[n + i + 2] ^ digest[i];
}
}
if (reallen > 239) reallen = 239;
*pwlen = reallen;
passwd[reallen] = 0;
return reallen;
}
int rad_chap_encode(RADIUS_PACKET *packet, uint8_t *output, int id,
VALUE_PAIR *password)
{
int i;
uint8_t *ptr;
uint8_t string[MAX_STRING_LEN * 2 + 1];
VALUE_PAIR *challenge;
if ((packet == NULL) || (password == NULL)) {
return -1;
}
i = 0;
ptr = string;
*ptr++ = id;
i++;
memcpy(ptr, password->vp_strvalue, password->length);
ptr += password->length;
i += password->length;
challenge = pairfind(packet->vps, PW_CHAP_CHALLENGE);
if (challenge) {
memcpy(ptr, challenge->vp_strvalue, challenge->length);
i += challenge->length;
} else {
memcpy(ptr, packet->vector, AUTH_VECTOR_LEN);
i += AUTH_VECTOR_LEN;
}
*output = id;
fr_md5_calc((uint8_t *)output + 1, (uint8_t *)string, i);
return 0;
}
void fr_rand_seed(const void *data, size_t size)
{
uint32_t hash;
if (!fr_rand_initialized) {
int fd;
memset(&fr_rand_pool, 0, sizeof(fr_rand_pool));
fd = open("/dev/urandom", O_RDONLY);
if (fd >= 0) {
size_t total;
ssize_t this;
total = this = 0;
while (total < sizeof(fr_rand_pool.randrsl)) {
this = read(fd, fr_rand_pool.randrsl,
sizeof(fr_rand_pool.randrsl) - total);
if ((this < 0) && (errno != EINTR)) break;
if (this > 0) total += this;
}
close(fd);
} else {
fr_rand_pool.randrsl[0] = fd;
fr_rand_pool.randrsl[1] = time(NULL);
fr_rand_pool.randrsl[2] = errno;
}
fr_randinit(&fr_rand_pool, 1);
fr_rand_pool.randcnt = 0;
fr_rand_initialized = 1;
}
if (!data) return;
hash = fr_rand();
if (!hash) hash = fr_rand();
hash = fr_hash_update(data, size, hash);
fr_rand_pool.randmem[fr_rand_pool.randcnt] ^= hash;
}
uint32_t fr_rand(void)
{
uint32_t num;
if (!fr_rand_initialized) {
fr_rand_seed(NULL, 0);
}
num = fr_rand_pool.randrsl[fr_rand_pool.randcnt++];
if (fr_rand_pool.randcnt >= 256) {
fr_rand_pool.randcnt = 0;
fr_isaac(&fr_rand_pool);
}
return num;
}
RADIUS_PACKET *rad_alloc(int newvector)
{
RADIUS_PACKET *rp;
if ((rp = malloc(sizeof(RADIUS_PACKET))) == NULL) {
fr_strerror_printf("out of memory");
return NULL;
}
memset(rp, 0, sizeof(*rp));
rp->id = -1;
rp->offset = -1;
if (newvector) {
int i;
uint32_t hash, base;
base = fr_rand();
for (i = 0; i < AUTH_VECTOR_LEN; i += sizeof(uint32_t)) {
hash = fr_rand() ^ base;
memcpy(rp->vector + i, &hash, sizeof(hash));
}
}
fr_rand();
return rp;
}
RADIUS_PACKET *rad_alloc_reply(RADIUS_PACKET *packet)
{
RADIUS_PACKET *reply;
if (!packet) return NULL;
reply = rad_alloc(0);
if (!reply) return NULL;
reply->sockfd = packet->sockfd;
reply->dst_ipaddr = packet->src_ipaddr;
reply->src_ipaddr = packet->dst_ipaddr;
reply->dst_port = packet->src_port;
reply->src_port = packet->dst_port;
reply->id = packet->id;
reply->code = 0;
memcpy(reply->vector, packet->vector,
sizeof(reply->vector));
reply->vps = NULL;
reply->data = NULL;
reply->data_len = 0;
return reply;
}
void rad_free(RADIUS_PACKET **radius_packet_ptr)
{
RADIUS_PACKET *radius_packet;
if (!radius_packet_ptr || !*radius_packet_ptr) return;
radius_packet = *radius_packet_ptr;
free(radius_packet->data);
pairfree(&radius_packet->vps);
free(radius_packet);
*radius_packet_ptr = NULL;
}