ootp/urd/rad.c

1099 lines
31 KiB
C

/*
* Copyright (c) 2009 Mark Fullmer
* Copyright (c) 2009 Mark Fullmer and the Ohio State University
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: rad.c 157 2011-04-06 03:57:29Z maf $
*/
#include <sys/types.h>
#include <openssl/evp.h>
#include <netinet/in.h>
#include <inttypes.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#if HAVE_STRINGS_H
#include <strings.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#endif
#include "xerr.h"
#include "byte.h"
#include "rad.h"
uint8_t nta(uint8_t b);
/*
* Simple radius packet encode and decode routines. Length and other
* potential data corrupting input is sanity checked on receive.
*
* urd_ctx_new( ) Create urd_ctx, allocate and init resources.
* urd_req_free() Free resources associated with urd_ctx allocated
* with urd_ctx_new().
*
* urd_req_decode() decode radius UDP packet content with
* client/server shared secret rsecret.
* Initialize a list of TLV pointers with common
* TLV's available without searching. Unhide
* UserPassword TLV using rsecret. Create C style
* strings for UserName, UserPassword as the TLV's
* are not null terminated.
*
* urd_req_dump() dump contents of radius packet decoded with
* urd_req_decode().
*
* urd_rep_encode() Encode a radius reply as either ACCESS-ACCEPT,
* ACCESS-REJECT, or ACCESS-CHALLENGE. An
* ACCESS-CHALLENGE will encode a state variable
* used by the upper layer to emulate a session.
* State is required for a robust one time password
* authentication implementation.
*
* urd_debug() enable/disable debugging.
*
* urd_req_cache_update() Update request cache with code,state.
*
* urd_req_cache_lookup() Lookup request in request cache, return code,
* state.
* urd_state_cache_lookup() Lookup state variable in state cache.
*
* urd_req_cache_stats() Dump stats for request cache.
*
* urd_state_cache_stats() Dump stats for state cache.
*
*/
/*
* function: urd_req_decode()
*
* Sanity check and decode RADIUS Access-Request datagrams.
*
* Ignore Accounting-Request
*
* Drop requests with null authenticator.
*
* Setup pointers to TLV's for direct access:
* User-Name
* User-Password
* NAS-IP-Address
* NAS-Port
* NAS-Port-Type
* NAS-Identifier
* State
*
* Create C strings for User-Name and User-Password.
*
* User-Password is un-hidden per standard and available as user_pass
*
* Decode urd state variable into 64 bit counter.
*
* returns < 0 : fail
* 0 : success
*/
int urd_req_decode(struct urd_ctx *urdctx)
{
uint8_t md_val[EVP_MAX_MD_SIZE], md_i[RADIUS_AUTHENTICATOR_LEN];
uint8_t *bp, v, h;
uint md_len;
int bytes_left, i, j, na, tlv_count;
/* min packet length */
if (urdctx->req.pkt_len < URD_PACKET_LEN_MIN) {
xerr_warnx("decode_fail: pkt_len=%d<%d", urdctx->req.pkt_len,
URD_PACKET_LEN_MIN);
return -1;
}
/* first few bytes are the header */
bcopy(&urdctx->req.pkt_buf, &urdctx->req.dgram_header,
sizeof (struct radius_dgram_header));
/* length is in network byte order */
#if BYTE_ORDER == LITTLE_ENDIAN
SWAPINT16(urdctx->req.dgram_header.length);
#elif BYTE_ORDER == BIG_ENDIAN
#else
BYTE_ORDER not defined
#endif
/* check for null authenticator */
for (i = 0, na = 0; i < RADIUS_AUTHENTICATOR_LEN; ++i) {
if (urdctx->req.dgram_header.authenticator[i] != 0) {
na = 1;
break;
}
}
/* null authenticator? */
if (na == 0) {
xerr_warnx("decode_fail: null authenticator");
return -1;
}
/* verify length field does not overrun packet buffer */
if (urdctx->req.dgram_header.length > urdctx->req.pkt_len) {
xerr_warnx("decode_fail: pkg_header.length=%d > pkt_len=%d",
urdctx->req.dgram_header.length, urdctx->req.pkt_len);
return -1;
}
/* expecting ACCESS-REQUEST or ACCOUNTING_REQUEST */
if ((urdctx->req.dgram_header.code != RADIUS_CODE_ACCESS_REQUEST) &&
(urdctx->req.dgram_header.code != RADIUS_CODE_ACCOUNTING_REQUEST)) {
xerr_warnx("decode_fail: Unexpected code=0x%X",
(int)urdctx->req.dgram_header.code);
return -1;
}
/* ignore accounting packets */
if (urdctx->req.dgram_header.code == RADIUS_CODE_ACCOUNTING_REQUEST)
return 1;
/* start of TLV area */
bp = urdctx->req.pkt_buf + sizeof (struct radius_dgram_header);
/* no TLV's so far */
urdctx->req.tlv_count = 0;
/* bytes left before end of packet */
bytes_left = urdctx->req.pkt_len - sizeof (struct radius_dgram_header);
/*
* run through list of attributes (TLV), verify sane length's
* init TLV array and TLV shortcust.
*/
urdctx->req.tlv_User_Name = (struct urd_tlv*)0L;
urdctx->req.tlv_NAS_IP_Address = (struct urd_tlv*)0L;
urdctx->req.tlv_NAS_Port = (struct urd_tlv*)0L;
urdctx->req.tlv_NAS_Port_Type = (struct urd_tlv*)0L;
urdctx->req.tlv_NAS_Identifier = (struct urd_tlv*)0L;
urdctx->req.tlv_User_Password = (struct urd_tlv*)0L;
urdctx->req.tlv_State = (struct urd_tlv*)0L;
bzero(&urdctx->req.user_name_base, URD_USER_NAME_LEN+1);
bzero(&urdctx->req.user_name, URD_USER_NAME_LEN+1);
bzero(&urdctx->req.user_pass, URD_USER_PASS_LEN+1);
urdctx->req.state_counter = 0;
while (1) {
/* TLV count */
tlv_count = urdctx->req.tlv_count;
/* min 2 bytes left to decode tlv_count and tlv_len */
if (bytes_left < 2) {
xerr_warnx("decode_fail: bytes_left=%d < 2", bytes_left);
return -1;
}
if (tlv_count >= URD_MAX_TLV) {
xerr_warnx("decode:fail: TLV count %d exceeds %d", tlv_count,
URD_MAX_TLV);
return -1;
}
urdctx->req.tlv[tlv_count].type = *bp;
urdctx->req.tlv[tlv_count].len = *(bp+1);
if (urdctx->req.tlv[tlv_count].len < 2) {
xerr_warnx("decode_fail: illegal TLV len=%d < 2",
urdctx->req.tlv[tlv_count].len);
return -1;
}
/* len is now the length of the data item */
urdctx->req.tlv[tlv_count].len -= 2;
if (urdctx->req.tlv[tlv_count].len)
urdctx->req.tlv[tlv_count].val = bp+2;
else /* empty TLV */
urdctx->req.tlv[tlv_count].val = (uint8_t*)0L;
/* advance byte pointer past current TLV */
bp += urdctx->req.tlv[tlv_count].len + 2;
/* bytes left is less the current TLV */
bytes_left -= (urdctx->req.tlv[tlv_count].len + 2);
/*
* setup shortcuts to first instance of common attributes
*/
switch (urdctx->req.tlv[tlv_count].type) {
case RADIUS_ATTRIB_USER_NAME:
if (!urdctx->req.tlv_User_Name)
urdctx->req.tlv_User_Name = &urdctx->req.tlv[tlv_count];
break;
case RADIUS_ATTRIB_NAS_IP_ADDRESS:
if (!urdctx->req.tlv_NAS_IP_Address)
urdctx->req.tlv_NAS_IP_Address = &urdctx->req.tlv[tlv_count];
break;
case RADIUS_ATTRIB_NAS_PORT:
if (!urdctx->req.tlv_NAS_Port)
urdctx->req.tlv_NAS_Port = &urdctx->req.tlv[tlv_count];
break;
case RADIUS_ATTRIB_NAS_PORT_TYPE:
if (!urdctx->req.tlv_NAS_Port_Type)
urdctx->req.tlv_NAS_Port_Type = &urdctx->req.tlv[tlv_count];
break;
case RADIUS_ATTRIB_NAS_IDENTIFIER:
if (!urdctx->req.tlv_NAS_Identifier)
urdctx->req.tlv_NAS_Identifier = &urdctx->req.tlv[tlv_count];
break;
case RADIUS_ATTRIB_USER_PASSWORD:
if (!urdctx->req.tlv_User_Password)
urdctx->req.tlv_User_Password = &urdctx->req.tlv[tlv_count];
break;
case RADIUS_ATTRIB_STATE:
if (!urdctx->req.tlv_State)
urdctx->req.tlv_State = &urdctx->req.tlv[tlv_count];
break;
} /* switch */
urdctx->req.tlv_count ++;
if (!bytes_left)
break;
} /* decode TLV's */
/* C string */
if (urdctx->req.tlv_User_Name) {
if ((urdctx->req.tlv_User_Name->len > URD_USER_NAME_LEN) ||
(urdctx->req.tlv_User_Name->len == 0)) {
xerr_warnx("decode_fail: UserName TLV length=%d max=%d,min=0",
urdctx->req.tlv_User_Name->len, URD_USER_NAME_LEN);
return -1;
}
bcopy(urdctx->req.tlv_User_Name->val, &urdctx->req.user_name,
urdctx->req.tlv_User_Name->len);
urdctx->req.user_name[urdctx->req.tlv_User_Name->len] = 0;
/*
* hack to allow multiple usernames to authenticate off the same
* base name. Allows a single user to sign on to device more than
* once when the device only supports single user sessions.
*/
bcopy(urdctx->req.tlv_User_Name->val, &urdctx->req.user_name_base,
urdctx->req.tlv_User_Name->len);
urdctx->req.user_name_base[urdctx->req.tlv_User_Name->len] = 0;
for (i = 0; i < urdctx->req.tlv_User_Name->len; ++i)
if (urdctx->req.user_name_base[i] == '#')
urdctx->req.user_name_base[i] = 0;
} /* urdctx->req.tlv_User_Name */
/* C string */
if (urdctx->req.tlv_User_Password) {
if ((urdctx->req.tlv_User_Password->len > URD_USER_PASS_LEN) ||
(urdctx->req.tlv_User_Password->len == 0)) {
xerr_warnx("decode_fail: UserPassword TLV length=%d max=%d,min=0",
urdctx->req.tlv_User_Password->len, URD_USER_PASS_LEN);
return -1;
}
} /* urdctx->req.tlv_User_Password */
/* valid state? */
if (urdctx->req.tlv_State) {
/* urd:0123456789abcdef */
if (urdctx->req.tlv_State->len != 20) {
xerr_warnx("decode_fail: State TLV len=%d != 20",
(int)urdctx->req.tlv_State->len);
return -1;
}
if ((urdctx->req.tlv_State->val[0] != 'u') ||
(urdctx->req.tlv_State->val[1] != 'r') ||
(urdctx->req.tlv_State->val[2] != 'd') ||
(urdctx->req.tlv_State->val[3] != ':')) {
xerr_warnx("decode_fail: State TLV expecting urd:");
return -1;
}
for (i = 4; i < 20; ++i) {
h = urdctx->req.tlv_State->val[i];
/* decode nybble */
if (h >= '0' && h <= '9')
v = h - '0';
else if (h >= 'A' && h <= 'F')
v = h - 'A' + 10;
else if (h >= 'a' && h <= 'f')
v = h - 'a' + 10;
else {
xerr_warnx("decode_fail: State TLV expecting hex");
return -1;
}
/* shift in nybble */
urdctx->req.state_counter = (urdctx->req.state_counter<<4) | v;
} /* foreach hex digit */
} /* urdctx->req.tlv_State */
/* unmunge the User-Password? and convert to C string */
if (urdctx->rsecret && urdctx->req.tlv_User_Password) {
/* first round of MD5 input authenticator */
bcopy(&urdctx->req.dgram_header.authenticator, md_i,
RADIUS_AUTHENTICATOR_LEN);
for (i = 0; i < urdctx->req.tlv_User_Password->len; i += 16) {
/* MD5 hash of rsecret + md_i */
EVP_DigestInit_ex(&urdctx->req.mdctx, EVP_md5(), NULL);
EVP_DigestUpdate(&urdctx->req.mdctx, urdctx->rsecret,
strlen(urdctx->rsecret));
EVP_DigestUpdate(&urdctx->req.mdctx, md_i, RADIUS_AUTHENTICATOR_LEN);
EVP_DigestFinal_ex(&urdctx->req.mdctx, md_val, &md_len);
EVP_MD_CTX_cleanup(&urdctx->req.mdctx);
for (j = 0; j < 16; ++j) {
urdctx->req.user_pass[i+j] =\
urdctx->req.tlv_User_Password->val[i+j] ^ md_val[j];
}
/* next round of MD5 from previous */
bcopy(&urdctx->req.tlv_User_Password->val[i], md_i,
RADIUS_AUTHENTICATOR_LEN);
} /* for each 16 byte chunk of user password */
/* C string, null terminate */
urdctx->req.user_pass[urdctx->req.tlv_User_Password->len] = 0;
}
return 0;
} /* urd_req_decode */
/*
* function: urd_req_dump()
*
* Debug tool for RADIUS requests passed through urd_req_decode().
* dumps interesting fields and TLV's.
*
*/
void urd_req_dump(struct urd_ctx *urdctx)
{
int buf_l, i, j, decode_type;
char buf[1024];
buf_l = snprintf(buf, sizeof(buf),
"pkt.code=%2.2X, pkt.id=%2.2X, pkt.len=%2.2X, pkt.auth=",
(int)urdctx->req.dgram_header.code,
(int)urdctx->req.dgram_header.identifier,
(int)urdctx->req.dgram_header.length);
for (j = 0; j < RADIUS_AUTHENTICATOR_LEN; ++j)
buf_l += snprintf(buf+buf_l, sizeof(buf)-buf_l, "%2.2X",
((int)urdctx->req.dgram_header.authenticator[j]));
xerr_info(buf);
xerr_info("pkt_len=%d, tlv->count=%d", urdctx->req.pkt_len,
urdctx->req.tlv_count);
for (i = 0; i < urdctx->req.tlv_count; ++i) {
/* decode type */
switch (urdctx->req.tlv[i].type) {
case RADIUS_ATTRIB_USER_NAME:
decode_type = URD_DECODE_TYPE_CHAR;
break;
case RADIUS_ATTRIB_USER_PASSWORD:
decode_type = URD_DECODE_TYPE_HIDDEN;
break;
case RADIUS_ATTRIB_NAS_IP_ADDRESS:
case RADIUS_ATTRIB_FRAMED_IP_ADDRESS:
case RADIUS_ATTRIB_FRAMED_IP_NETMASK:
decode_type = URD_DECODE_TYPE_IP;
break;
default:
decode_type = URD_DECODE_TYPE_HEX;
break;
} /* switch */
buf_l = snprintf(buf, sizeof(buf),
" TLV type=%d, len=%d, val=", (int)urdctx->req.tlv[i].type,
(int)urdctx->req.tlv[i].len);
switch (decode_type) {
case URD_DECODE_TYPE_HEX:
buf_l += snprintf(buf+buf_l, sizeof(buf)-buf_l, "H ");
for (j = 0; j < urdctx->req.tlv[i].len; ++j)
buf_l += snprintf(buf+buf_l, sizeof(buf)-buf_l, "%2.2X",
(int)urdctx->req.tlv[i].val[j]);
break;
case URD_DECODE_TYPE_CHAR:
buf_l += snprintf(buf+buf_l, sizeof(buf)-buf_l, "C ");
for (j = 0; j < urdctx->req.tlv[i].len; ++j)
buf_l += snprintf(buf+buf_l, sizeof(buf)-buf_l, "%c",
urdctx->req.tlv[i].val[j]);
break;
case URD_DECODE_TYPE_IP:
buf_l += snprintf(buf+buf_l, sizeof(buf)-buf_l, "I %d.%d.%d.%d",
(int)urdctx->req.tlv[i].val[0], (int)urdctx->req.tlv[i].val[1],
(int)urdctx->req.tlv[i].val[2], (int)urdctx->req.tlv[i].val[3]);
break;
case URD_DECODE_TYPE_HIDDEN:
buf_l += snprintf(buf+buf_l, sizeof(buf)-buf_l, "X <hidden>");
break;
} /* switch */
xerr_info(buf);
} /* foreach TLV/Attribute */
} /* urd_req_dump */
/*
* function: urd_rep_encode()
*
* Encode radius reply from request.
*
* Request must be decoded by urd_req_decode() first.
*
* code (one of):
* RADIUS_CODE_ACCESS_ACCEPT
* RADIUS_CODE_ACCESS_REJECT
* RADIUS_CODE_ACCESS_CHALLENGE
*
* flags:
* URD_ENCODE_FLAG_STATE - state variable is encoded and added to TLV's
* URD_ENCODE_FLAG_MSG - message is encoded and added to TLV's
*
* state_counter is encoded as ASCII hex to avoid buggy client
* implementations which do not treat state as opaque.
*
* authenticator field is set based on packet contents and shared
* client/server secret per standard.
*
*/
int urd_rep_encode(struct urd_ctx *urdctx, uint8_t code,
uint64_t state_counter, uint64_t otp_count, int rep_encode_flags)
{
struct radius_dgram_header dgram_header, *dh;
struct urd_tlv_state tlv_state;
struct urd_tlv_rep_msg tlv_rep_msg;
u_char md_val[EVP_MAX_MD_SIZE], *c;
char fmt_buf[64];
uint md_len;
int i, pkt_p;
bzero(&dgram_header, sizeof dgram_header);
bzero(&tlv_state, sizeof tlv_state);
bzero(&tlv_rep_msg, sizeof tlv_rep_msg);
/* destination IP is source of request */
bcopy(&urdctx->req.rem_addr, &urdctx->rep.rem_addr,
sizeof (urdctx->req.rem_addr));
/* construct reply header from request */
dgram_header.code = code;
dgram_header.identifier = urdctx->req.dgram_header.identifier;
dgram_header.length = sizeof (dgram_header);
bcopy(&urdctx->req.dgram_header.authenticator, &dgram_header.authenticator,
sizeof dgram_header.authenticator);
/* add state? */
if (rep_encode_flags & URD_ENCODE_FLAG_STATE) {
dgram_header.length += sizeof (tlv_state);
/* encode state_counter as urd: followed by 8 bytes as hex/ASCII */
tlv_state.type = RADIUS_ATTRIB_STATE;
tlv_state.len = sizeof (tlv_state);
tlv_state.val[0] = 'u'; tlv_state.val[1] = 'r';
tlv_state.val[2] = 'd'; tlv_state.val[3] = ':';
c = (u_char*)&state_counter;
i = 19;
while (i > 4) {
tlv_state.val[i--] = nta(*c & 0x0F);
tlv_state.val[i--] = nta(*c >> 4);
++c;
}
} /* URD_ENCODE_FLAG_STATE */
/* add reply message? */
if (rep_encode_flags & URD_ENCODE_FLAG_MSG) {
i = snprintf(fmt_buf, sizeof(fmt_buf), "%" PRIu64, otp_count);
/* should never happen */
if (i > URD_TLV_REPLY_MSG_LEN) {
xerr_warnx("reply msg encode failed, i=%d", i);
i = URD_TLV_REPLY_MSG_LEN;
}
/*
* calculate size of tlv_rep_msg. The full URD_TLV_REPLY_MSG_LEN
* space ay not be used and is truncated. type(1) + len(1) + message(i)
*/
tlv_rep_msg.len = 1 + 1 + i;
/* add len to total encoded size */
dgram_header.length += tlv_rep_msg.len;
tlv_rep_msg.type = RADIUS_ATTRIB_REPLY_MESSAGE;
/* copy in otp_count in ASCII */
bcopy(&fmt_buf, &tlv_rep_msg.val, i);
} /* URD_ENCODE_FLAG_MSG */
/* preserve length in host byte order */
urdctx->rep.pkt_len = dgram_header.length;
/* length is in network byte order */
#if BYTE_ORDER == LITTLE_ENDIAN
SWAPINT16(dgram_header.length);
#elif BYTE_ORDER == BIG_ENDIAN
#else
BYTE_ORDER not defined
#endif
/* copy packet header into reply buffer */
bcopy(&dgram_header, &urdctx->rep.pkt_buf, sizeof (dgram_header));
pkt_p = sizeof (dgram_header);
if (rep_encode_flags & URD_ENCODE_FLAG_STATE) {
bcopy(&tlv_state, (char*)&urdctx->rep.pkt_buf + pkt_p, sizeof(tlv_state));
pkt_p += sizeof(tlv_state);
}
if (rep_encode_flags & URD_ENCODE_FLAG_MSG) {
bcopy(&tlv_rep_msg, (char*)&urdctx->rep.pkt_buf + pkt_p, tlv_rep_msg.len);
pkt_p += tlv_rep_msg.len;
}
/* MD5(reply packet + secret) */
EVP_DigestInit_ex(&urdctx->req.mdctx, EVP_md5(), NULL);
EVP_DigestUpdate(&urdctx->req.mdctx, &urdctx->rep.pkt_buf, pkt_p);
EVP_DigestUpdate(&urdctx->req.mdctx, urdctx->rsecret,
strlen(urdctx->rsecret));
EVP_DigestFinal_ex(&urdctx->req.mdctx, md_val, &md_len);
EVP_MD_CTX_cleanup(&urdctx->req.mdctx);
dh = (struct radius_dgram_header*)&urdctx->rep.pkt_buf;
/* copy this directly back into packet */
bcopy(&md_val, &dh->authenticator, sizeof dgram_header.authenticator);
return 0;
} /* urd_rep_encode */
/*
* function: urd_ctx_new()
*
* Allocates and initialize urd_ctx
*
* urd_ctx_free() will release relources.
*
* returns sturct urd_ctx*
* 0L for failure
*/
struct urd_ctx *urd_ctx_new(char *rsecret)
{
struct urd_ctx *urdctx;
uint i;
if (!(urdctx = (struct urd_ctx*)malloc(sizeof *urdctx))) {
xerr_warn("malloc(urd_ctx)");
return urdctx;
}
bzero(urdctx, sizeof (*urdctx));
strncpy(urdctx->rsecret, rsecret, URD_SECRET_LEN);
urdctx->rsecret[URD_SECRET_LEN] = 0;
for (i = 0; i < 1<<URD_REQ_HASH_BUCKET_BITS; ++i) {
LIST_INIT(&urdctx->req_cache_bucket[i]);
}
EVP_MD_CTX_init(&urdctx->req.mdctx);
return urdctx;
} /* urd_ctx_new */
/*
* function: urd_ctx_free()
*
* Free resources allocated with urd_ctx_new()
*
*/
void urd_ctx_free(struct urd_ctx *urdctx)
{
if (urdctx)
free (urdctx);
} /* urd_ctx_free */
/*
* function: urd_req_cache_update()
*
* UserName and UserPassWord TLV's must be in request (req)
* and be of length <= URD_USER_NAME_LEN/URD_USER_PASS_LEN
*
* The request cache is keyed from the authenticator field
* in a radius request. Requests have a lifetime started
* at create_time. The cache provides the code and state
* variables to the upper layer.
*
* Optionally update the state cache if flags has URD_CACHE_FLAG_STATE
* set. The state cache is keyed from the state cache TLV present
* in an Access-Request generated via a Access-Challenge response
* to a previous Access-Request.
*
* returns: < 0 : fail
* 0 : success
*
*/
int urd_req_cache_update(struct urd_ctx *urdctx, uint8_t rep_code,
uint64_t state_counter, uint64_t otp_count, int req_cache_flags)
{
uint16_t req_hash, req_hash_mask, state_hash, state_hash_mask;
struct urd_req_cache_entry *e;
int i;
req_hash = 0;
req_hash_mask = (uint16_t)((1<<URD_REQ_HASH_BUCKET_BITS)-1);
for (i = 0; i < RADIUS_AUTHENTICATOR_LEN; i += 2) {
req_hash ^= (uint16_t)(urdctx->req.dgram_header.authenticator[i]) |
(uint16_t)(urdctx->req.dgram_header.authenticator[i+1]<<8);
}
req_hash ^= urdctx->req.dgram_header.identifier;
req_hash &= req_hash_mask;
/* next free request cache entry */
e = &urdctx->req_cache[urdctx->req_cache_len++];
bzero(e, sizeof *e);
/* insert entry into hash bucket req_chain */
LIST_INSERT_HEAD(&urdctx->req_cache_bucket[req_hash], e, req_chain);
/* hash table for state? */
if (req_cache_flags & URD_CACHE_FLAG_STATE) {
state_hash_mask = (uint16_t)((1<<URD_STATE_HASH_BUCKET_BITS)-1);
state_hash = (state_counter & 0x0000FFFF);
state_hash ^= ((state_counter>>16) & 0x0000FFFF);
state_hash ^= ((state_counter>>32) & 0x0000FFFF);
state_hash ^= ((state_counter>>48) & 0x0000FFFF);
state_hash &= state_hash_mask;
/* insert entry into hash bucket state_chain */
LIST_INSERT_HEAD(&urdctx->state_cache_bucket[state_hash], e, state_chain);
e->flags = URD_STATE_CACHE_FLAG_INUSE;
} /* hash table for state */
/*
* fill in entry
*/
bcopy(urdctx->req.user_pass, e->user_pass, URD_USER_PASS_LEN+1);
bcopy(urdctx->req.user_name, e->user_name, URD_USER_NAME_LEN+1);
bcopy(urdctx->req.dgram_header.authenticator, e->rad_auth,
RADIUS_AUTHENTICATOR_LEN);
e->state_counter = state_counter;
e->create_time = time((time_t*)0L);
e->rad_code = rep_code;
e->otp_count = otp_count;
e->rad_id = urdctx->req.dgram_header.identifier;
e->rexmit_count = 0;
e->flags ^= URD_REQ_CACHE_FLAG_INUSE;
/* rollover */
if (urdctx->req_cache_len == URD_REQ_CACHE_ENTRIES)
urdctx->req_cache_len = 0;
/* if the current entry was previously in use, remove from hash chain */
if (urdctx->req_cache[urdctx->req_cache_len].flags &
URD_REQ_CACHE_FLAG_INUSE) {
LIST_REMOVE(&urdctx->req_cache[urdctx->req_cache_len], req_chain);
urdctx->req_cache[urdctx->req_cache_len].flags &=\
~URD_REQ_CACHE_FLAG_INUSE;
} /* in use? */
/* if the current entry was previously in use, remove from hash chain */
if (urdctx->req_cache[urdctx->req_cache_len].flags &
URD_STATE_CACHE_FLAG_INUSE) {
LIST_REMOVE(&urdctx->req_cache[urdctx->req_cache_len], state_chain);
urdctx->req_cache[urdctx->req_cache_len].flags &=\
~URD_STATE_CACHE_FLAG_INUSE;
} /* in use? */
return 0;
} /* urd_req_cache_update */
/*
* function: urd_req_cache_lookup()
*
* The request cache is used to provide re-transmitted requests
* (packet loss between client and server) to a client.
*
* UserName and UserPassWord TLV's must be in request (req)
* and be of length <= URD_USER_NAME_LEN/URD_USER_PASS_LEN
*
* code, otp_count, and state_counter will be updated on cache hit,
* else they are unchanged.
*
* Lookup request in cache by Authenticator field. Additionally
* verify identifier, user_name, user_pass and state (if present) match
* the cache'd request. If the entry has not expired (older than
* URD_REQ_CACHE_LIFETIME seconds), return code and state associated
* with cache.
*
* returns: URD_REQ_CACHE_HIT (cache hit)
* URD_REQ_CACHE_MISS (cache miss)
*
*/
int urd_req_cache_lookup(struct urd_ctx *urdctx, uint8_t *code,
uint64_t *state_counter, uint64_t *otp_count, int *cache_flags)
{
time_t now;
uint16_t hash, hash_mask;
struct urd_req_cache_entry *e;
int i, match, depth;
hash = 0;
hash_mask = (uint16_t)((1<<URD_REQ_HASH_BUCKET_BITS)-1);
for (i = 0; i < RADIUS_AUTHENTICATOR_LEN; i += 2) {
hash ^= (uint16_t)(urdctx->req.dgram_header.authenticator[i]) |
(uint16_t)(urdctx->req.dgram_header.authenticator[i+1]<<8);
}
hash ^= urdctx->req.dgram_header.identifier;
hash &= hash_mask;
/* not found yet */
match = 0;
/* debugging, depth of chain */
depth = 0;
/* run down the chain */
LIST_FOREACH(e, &urdctx->req_cache_bucket[hash], req_chain) {
/* debugging */
depth += 1;
/* match on key fields */
if (!(bcmp(&e->rad_auth, &urdctx->req.dgram_header.authenticator,
RADIUS_AUTHENTICATOR_LEN))) {
if ((!strcmp(e->user_name, urdctx->req.user_name)) &&
(!strcmp(e->user_pass, urdctx->req.user_pass)) &&
(e->rad_id == urdctx->req.dgram_header.identifier)) {
if ((!urdctx->req.tlv_State) ||
((urdctx->req.tlv_State) &&
(urdctx->req.state_counter == e->state_counter))) {
/* cache entry older than URD_REQ_CACHE_LIFETIME seconds? */
now = time((time_t*)0L);
if ((now - e->create_time) < URD_REQ_CACHE_LIFETIME) {
/*
* cache hit
*/
/* number of packet retransmits */
++e->rexmit_count;
if (urdctx->debug)
xerr_info("rexmit_count=%d", (int)e->rexmit_count);
/* cached result code and state */
*code = e->rad_code;
*state_counter = e->state_counter;
*otp_count = e->otp_count;
*cache_flags = e->flags;
match = 1;
break;
/* state timeout */
} else if (urdctx->debug) {
xerr_info("urd_req_cache_lookup: entry expired");
}
/* state match */
} else if (urdctx->debug) {
xerr_info("urd_req_cache_lookup: miss state");
}
/* inner match */
} else if (urdctx->debug) {
xerr_info("urd_req_cache_lookup: miss inner");
}
} /* match authenticator */
} /* LIST_FOREACH */
if (urdctx->debug)
xerr_info("urd_req_cache_lookup depth=%d", depth);
if (match)
return URD_REQ_CACHE_HIT;
else
return URD_REQ_CACHE_MISS;
} /* urd_req_cache_lookup */
/*
* function: urd_state_cache_lookup()
*
* The state cache is used to pair an Access-Request with state
* to a previous Access-Request without state. The initial
* Access-Request (no state) is used to verify a username/re-usable
* password pair. The stateful request performs the one time
* password authentication.
*
* UserName and UserPassWord TLV's must be in request (req)
* and be of length <= URD_USER_NAME_LEN/URD_USER_PASS_LEN
*
* code will be updated on cache hit, else it is left unchanged.
*
* Lookup request in cache by state field. Additionally
* verify user_name matches the initial Access-Request.
* If the entry has not expired (older than URD_STATE_CACHE_LIFETIME seconds
* return a hit.
*
* returns: URD_REQ_CACHE_HIT (cache hit)
* URD_REQ_CACHE_MISS (cache miss)
*
*/
int urd_state_cache_lookup(struct urd_ctx *urdctx, uint8_t *code)
{
time_t now;
uint16_t state_hash, state_hash_mask;
struct urd_req_cache_entry *e;
int match, depth;
state_hash = 0;
state_hash_mask = (uint16_t)((1<<URD_STATE_HASH_BUCKET_BITS)-1);
state_hash = (urdctx->req.state_counter & 0x0000FFFF);
state_hash ^= ((urdctx->req.state_counter>>16) & 0x0000FFFF);
state_hash ^= ((urdctx->req.state_counter>>32) & 0x0000FFFF);
state_hash ^= ((urdctx->req.state_counter>>48) & 0x0000FFFF);
state_hash &= state_hash_mask;
/* not found yet */
match = 0;
/* debugging, depth of chain */
depth = 0;
/* run down the chain */
LIST_FOREACH(e, &urdctx->state_cache_bucket[state_hash], state_chain) {
/* debugging */
depth += 1;
/* match on key fields */
if (e->state_counter == urdctx->req.state_counter) {
if (!strcmp(e->user_name, urdctx->req.user_name)) {
/* cache entry older than URD_STATE_CACHE_LIFETIME seconds? */
now = time((time_t*)0L);
if ((now - e->create_time) < URD_STATE_CACHE_LIFETIME) {
/*
* cache hit
*/
/* number of packet retransmits */
++e->rexmit_count;
if (urdctx->debug)
xerr_info("rexmit_count=%d", (int)e->rexmit_count);
/* cached result code and state */
*code = e->rad_code;
match = 1;
break;
/* state timeout */
} else if (urdctx->debug) {
xerr_info("urd_state_cache_lookup: entry expired");
}
/* inner match */
} else if (urdctx->debug) {
xerr_info("urd_state_cache_lookup: miss inner");
}
} /* match state_counter */
} /* LIST_FOREACH */
if (urdctx->debug)
xerr_info("urd_state_cache_lookup depth=%d", depth);
if (match)
return URD_STATE_CACHE_HIT;
else
return URD_STATE_CACHE_MISS;
} /* urd_state_cache_lookup */
/*
* function: urd_state_cache_stats()
*
* Dump state_cache hash table depths.
*
*/
void urd_state_cache_stats(struct urd_ctx *urdctx)
{
struct urd_req_cache_entry *e;
int depth, i;
xerr_info("state_cache_stats:");
for (i = 0; i < 1<<URD_STATE_HASH_BUCKET_BITS; ++i) {
if (LIST_EMPTY(&urdctx->state_cache_bucket[i]))
continue;
depth = 0;
LIST_FOREACH(e, &urdctx->state_cache_bucket[i], state_chain)
++depth;
xerr_info(" bucket=%d,depth=%d", i, depth);
} /* hash bucket */
} /* urd_state_cache_stats */
/*
* function: urd_req_cache_stats()
*
* Dump req_cache hash table depths.
*
*/
void urd_req_cache_stats(struct urd_ctx *urdctx)
{
struct urd_req_cache_entry *e;
int depth, i;
xerr_info("req_cache_stats:");
for (i = 0; i < 1<<URD_REQ_HASH_BUCKET_BITS; ++i) {
if (LIST_EMPTY(&urdctx->req_cache_bucket[i]))
continue;
depth = 0;
LIST_FOREACH(e, &urdctx->req_cache_bucket[i], req_chain)
++depth;
xerr_info(" bucket=%d,depth=%d", i, depth);
} /* hash bucket */
} /* urd_req_cache_stats */
/*
* function: urd_debug()
*
* set/clear urd context debug
*
*/
void urd_debug(struct urd_ctx *urdctx, int debug)
{
urdctx->debug = debug;
} /* urd_debug() */
/*
* function: nta()
*
* return ASCII value of low hex nybble
*
*/
uint8_t nta(uint8_t b)
{
if (b < 10)
b = '0' + b;
else
b = 'A' + (b-10);
return b;
} /* nta */