You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
521 lines
13 KiB
521 lines
13 KiB
/*
|
|
* $Id: sendserver.c,v 1.1 2004/11/14 07:26:26 paulus Exp $
|
|
*
|
|
* Copyright (C) 1995,1996,1997 Lars Fenneberg
|
|
*
|
|
* Copyright 1992 Livingston Enterprises, Inc.
|
|
*
|
|
* Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan
|
|
* and Merit Network, Inc. All Rights Reserved
|
|
*
|
|
* See the file COPYRIGHT for the respective terms and conditions.
|
|
* If the file is missing contact me at lf@elemental.net
|
|
* and I'll send you a copy.
|
|
*
|
|
*/
|
|
|
|
#include <includes.h>
|
|
#include <radiusclient.h>
|
|
#include <pathnames.h>
|
|
|
|
static void rc_random_vector (unsigned char *);
|
|
static int rc_check_reply (AUTH_HDR *, int, char *, unsigned char *, unsigned char);
|
|
|
|
/*
|
|
* Function: rc_pack_list
|
|
*
|
|
* Purpose: Packs an attribute value pair list into a buffer.
|
|
*
|
|
* Returns: Number of octets packed.
|
|
*
|
|
*/
|
|
|
|
static int rc_pack_list (VALUE_PAIR *vp, char *secret, AUTH_HDR *auth)
|
|
{
|
|
int length, i, pc, secretlen, padded_length;
|
|
int total_length = 0;
|
|
UINT4 lvalue;
|
|
unsigned char passbuf[MAX(AUTH_PASS_LEN, CHAP_VALUE_LENGTH)];
|
|
unsigned char md5buf[256];
|
|
unsigned char *buf, *vector, *lenptr;
|
|
|
|
buf = auth->data;
|
|
|
|
while (vp != (VALUE_PAIR *) NULL)
|
|
{
|
|
|
|
if (vp->vendorcode != VENDOR_NONE) {
|
|
*buf++ = PW_VENDOR_SPECIFIC;
|
|
|
|
/* Place-holder for where to put length */
|
|
lenptr = buf++;
|
|
|
|
/* Insert vendor code */
|
|
*buf++ = 0;
|
|
*buf++ = (((unsigned int) vp->vendorcode) >> 16) & 255;
|
|
*buf++ = (((unsigned int) vp->vendorcode) >> 8) & 255;
|
|
*buf++ = ((unsigned int) vp->vendorcode) & 255;
|
|
|
|
/* Insert vendor-type */
|
|
*buf++ = vp->attribute;
|
|
|
|
/* Insert value */
|
|
switch(vp->type) {
|
|
case PW_TYPE_STRING:
|
|
length = vp->lvalue;
|
|
*lenptr = length + 8;
|
|
*buf++ = length+2;
|
|
memcpy(buf, vp->strvalue, (size_t) length);
|
|
buf += length;
|
|
total_length += length+8;
|
|
break;
|
|
case PW_TYPE_INTEGER:
|
|
case PW_TYPE_IPADDR:
|
|
length = sizeof(UINT4);
|
|
*lenptr = length + 8;
|
|
*buf++ = length+2;
|
|
lvalue = htonl(vp->lvalue);
|
|
memcpy(buf, (char *) &lvalue, sizeof(UINT4));
|
|
buf += length;
|
|
total_length += length+8;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
*buf++ = vp->attribute;
|
|
switch (vp->attribute) {
|
|
case PW_USER_PASSWORD:
|
|
|
|
/* Encrypt the password */
|
|
|
|
/* Chop off password at AUTH_PASS_LEN */
|
|
length = vp->lvalue;
|
|
if (length > AUTH_PASS_LEN) length = AUTH_PASS_LEN;
|
|
|
|
/* Calculate the padded length */
|
|
padded_length = (length+(AUTH_VECTOR_LEN-1)) & ~(AUTH_VECTOR_LEN-1);
|
|
|
|
/* Record the attribute length */
|
|
*buf++ = padded_length + 2;
|
|
|
|
/* Pad the password with zeros */
|
|
memset ((char *) passbuf, '\0', AUTH_PASS_LEN);
|
|
memcpy ((char *) passbuf, vp->strvalue, (size_t) length);
|
|
|
|
secretlen = strlen (secret);
|
|
vector = (char *)auth->vector;
|
|
for(i = 0; i < padded_length; i += AUTH_VECTOR_LEN) {
|
|
/* Calculate the MD5 digest*/
|
|
strcpy ((char *) md5buf, secret);
|
|
memcpy ((char *) md5buf + secretlen, vector,
|
|
AUTH_VECTOR_LEN);
|
|
rc_md5_calc (buf, md5buf, secretlen + AUTH_VECTOR_LEN);
|
|
|
|
/* Remeber the start of the digest */
|
|
vector = buf;
|
|
|
|
/* Xor the password into the MD5 digest */
|
|
for (pc = i; pc < (i + AUTH_VECTOR_LEN); pc++) {
|
|
*buf++ ^= passbuf[pc];
|
|
}
|
|
}
|
|
|
|
total_length += padded_length + 2;
|
|
|
|
break;
|
|
#if 0
|
|
case PW_CHAP_PASSWORD:
|
|
|
|
*buf++ = CHAP_VALUE_LENGTH + 2;
|
|
|
|
/* Encrypt the Password */
|
|
length = vp->lvalue;
|
|
if (length > CHAP_VALUE_LENGTH) {
|
|
length = CHAP_VALUE_LENGTH;
|
|
}
|
|
memset ((char *) passbuf, '\0', CHAP_VALUE_LENGTH);
|
|
memcpy ((char *) passbuf, vp->strvalue, (size_t) length);
|
|
|
|
/* Calculate the MD5 Digest */
|
|
secretlen = strlen (secret);
|
|
strcpy ((char *) md5buf, secret);
|
|
memcpy ((char *) md5buf + secretlen, (char *) auth->vector,
|
|
AUTH_VECTOR_LEN);
|
|
rc_md5_calc (buf, md5buf, secretlen + AUTH_VECTOR_LEN);
|
|
|
|
/* Xor the password into the MD5 digest */
|
|
for (i = 0; i < CHAP_VALUE_LENGTH; i++) {
|
|
*buf++ ^= passbuf[i];
|
|
}
|
|
total_length += CHAP_VALUE_LENGTH + 2;
|
|
|
|
break;
|
|
#endif
|
|
default:
|
|
switch (vp->type) {
|
|
case PW_TYPE_STRING:
|
|
length = vp->lvalue;
|
|
*buf++ = length + 2;
|
|
memcpy (buf, vp->strvalue, (size_t) length);
|
|
buf += length;
|
|
total_length += length + 2;
|
|
break;
|
|
|
|
case PW_TYPE_INTEGER:
|
|
case PW_TYPE_IPADDR:
|
|
*buf++ = sizeof (UINT4) + 2;
|
|
lvalue = htonl (vp->lvalue);
|
|
memcpy (buf, (char *) &lvalue, sizeof (UINT4));
|
|
buf += sizeof (UINT4);
|
|
total_length += sizeof (UINT4) + 2;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
vp = vp->next;
|
|
}
|
|
return total_length;
|
|
}
|
|
|
|
/*
|
|
* Function: rc_send_server
|
|
*
|
|
* Purpose: send a request to a RADIUS server and wait for the reply
|
|
*
|
|
*/
|
|
|
|
int rc_send_server (SEND_DATA *data, char *msg, REQUEST_INFO *info)
|
|
{
|
|
int sockfd;
|
|
struct sockaddr salocal;
|
|
struct sockaddr saremote;
|
|
struct sockaddr_in *sin;
|
|
struct timeval authtime;
|
|
fd_set readfds;
|
|
AUTH_HDR *auth, *recv_auth;
|
|
UINT4 auth_ipaddr;
|
|
char *server_name; /* Name of server to query */
|
|
int salen;
|
|
int result;
|
|
int total_length;
|
|
int length;
|
|
int retry_max;
|
|
int secretlen;
|
|
char secret[MAX_SECRET_LENGTH + 1];
|
|
unsigned char vector[AUTH_VECTOR_LEN];
|
|
char recv_buffer[BUFFER_LEN];
|
|
char send_buffer[BUFFER_LEN];
|
|
int retries;
|
|
VALUE_PAIR *vp;
|
|
|
|
server_name = data->server;
|
|
if (server_name == (char *) NULL || server_name[0] == '\0')
|
|
return (ERROR_RC);
|
|
|
|
if ((vp = rc_avpair_get(data->send_pairs, PW_SERVICE_TYPE)) && \
|
|
(vp->lvalue == PW_ADMINISTRATIVE))
|
|
{
|
|
strcpy(secret, MGMT_POLL_SECRET);
|
|
if ((auth_ipaddr = rc_get_ipaddr(server_name)) == 0)
|
|
return (ERROR_RC);
|
|
}
|
|
else
|
|
{
|
|
if (rc_find_server (server_name, &auth_ipaddr, secret) != 0)
|
|
{
|
|
return (ERROR_RC);
|
|
}
|
|
}
|
|
|
|
sockfd = socket (AF_INET, SOCK_DGRAM, 0);
|
|
if (sockfd < 0)
|
|
{
|
|
memset (secret, '\0', sizeof (secret));
|
|
error("rc_send_server: socket: %s", strerror(errno));
|
|
return (ERROR_RC);
|
|
}
|
|
|
|
length = sizeof (salocal);
|
|
sin = (struct sockaddr_in *) & salocal;
|
|
memset ((char *) sin, '\0', (size_t) length);
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_addr.s_addr = htonl(rc_own_bind_ipaddress());
|
|
sin->sin_port = htons ((unsigned short) 0);
|
|
if (bind (sockfd, (struct sockaddr *) sin, length) < 0 ||
|
|
getsockname (sockfd, (struct sockaddr *) sin, &length) < 0)
|
|
{
|
|
close (sockfd);
|
|
memset (secret, '\0', sizeof (secret));
|
|
error("rc_send_server: bind: %s: %m", server_name);
|
|
return (ERROR_RC);
|
|
}
|
|
|
|
retry_max = data->retries; /* Max. numbers to try for reply */
|
|
retries = 0; /* Init retry cnt for blocking call */
|
|
|
|
/* Build a request */
|
|
auth = (AUTH_HDR *) send_buffer;
|
|
auth->code = data->code;
|
|
auth->id = data->seq_nbr;
|
|
|
|
if (data->code == PW_ACCOUNTING_REQUEST)
|
|
{
|
|
total_length = rc_pack_list(data->send_pairs, secret, auth) + AUTH_HDR_LEN;
|
|
|
|
auth->length = htons ((unsigned short) total_length);
|
|
|
|
memset((char *) auth->vector, 0, AUTH_VECTOR_LEN);
|
|
secretlen = strlen (secret);
|
|
memcpy ((char *) auth + total_length, secret, secretlen);
|
|
rc_md5_calc (vector, (char *) auth, total_length + secretlen);
|
|
memcpy ((char *) auth->vector, (char *) vector, AUTH_VECTOR_LEN);
|
|
}
|
|
else
|
|
{
|
|
rc_random_vector (vector);
|
|
memcpy (auth->vector, vector, AUTH_VECTOR_LEN);
|
|
|
|
total_length = rc_pack_list(data->send_pairs, secret, auth) + AUTH_HDR_LEN;
|
|
|
|
auth->length = htons ((unsigned short) total_length);
|
|
}
|
|
|
|
sin = (struct sockaddr_in *) & saremote;
|
|
memset ((char *) sin, '\0', sizeof (saremote));
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_addr.s_addr = htonl (auth_ipaddr);
|
|
sin->sin_port = htons ((unsigned short) data->svc_port);
|
|
|
|
for (;;)
|
|
{
|
|
sendto (sockfd, (char *) auth, (unsigned int) total_length, (int) 0,
|
|
(struct sockaddr *) sin, sizeof (struct sockaddr_in));
|
|
|
|
authtime.tv_usec = 0L;
|
|
authtime.tv_sec = (long) data->timeout;
|
|
FD_ZERO (&readfds);
|
|
FD_SET (sockfd, &readfds);
|
|
if (select (sockfd + 1, &readfds, NULL, NULL, &authtime) < 0)
|
|
{
|
|
if (errno == EINTR)
|
|
continue;
|
|
error("rc_send_server: select: %m");
|
|
memset (secret, '\0', sizeof (secret));
|
|
close (sockfd);
|
|
return (ERROR_RC);
|
|
}
|
|
if (FD_ISSET (sockfd, &readfds))
|
|
break;
|
|
|
|
/*
|
|
* Timed out waiting for response. Retry "retry_max" times
|
|
* before giving up. If retry_max = 0, don't retry at all.
|
|
*/
|
|
if (++retries >= retry_max)
|
|
{
|
|
error("rc_send_server: no reply from RADIUS server %s:%u",
|
|
rc_ip_hostname (auth_ipaddr), data->svc_port);
|
|
close (sockfd);
|
|
memset (secret, '\0', sizeof (secret));
|
|
return (TIMEOUT_RC);
|
|
}
|
|
}
|
|
salen = sizeof (saremote);
|
|
length = recvfrom (sockfd, (char *) recv_buffer,
|
|
(int) sizeof (recv_buffer),
|
|
(int) 0, &saremote, &salen);
|
|
|
|
if (length <= 0)
|
|
{
|
|
error("rc_send_server: recvfrom: %s:%d: %m", server_name,\
|
|
data->svc_port);
|
|
close (sockfd);
|
|
memset (secret, '\0', sizeof (secret));
|
|
return (ERROR_RC);
|
|
}
|
|
|
|
recv_auth = (AUTH_HDR *)recv_buffer;
|
|
|
|
result = rc_check_reply (recv_auth, BUFFER_LEN, secret, vector, data->seq_nbr);
|
|
|
|
data->receive_pairs = rc_avpair_gen(recv_auth);
|
|
|
|
close (sockfd);
|
|
if (info)
|
|
{
|
|
memcpy(info->secret, secret, sizeof(info->secret));
|
|
memcpy(info->request_vector, vector,
|
|
sizeof(info->request_vector));
|
|
}
|
|
memset (secret, '\0', sizeof (secret));
|
|
|
|
if (result != OK_RC) return (result);
|
|
|
|
*msg = '\0';
|
|
vp = data->receive_pairs;
|
|
while (vp)
|
|
{
|
|
if ((vp = rc_avpair_get(vp, PW_REPLY_MESSAGE)))
|
|
{
|
|
strcat(msg, vp->strvalue);
|
|
strcat(msg, "\n");
|
|
vp = vp->next;
|
|
}
|
|
}
|
|
|
|
if ((recv_auth->code == PW_ACCESS_ACCEPT) ||
|
|
(recv_auth->code == PW_PASSWORD_ACK) ||
|
|
(recv_auth->code == PW_ACCOUNTING_RESPONSE))
|
|
{
|
|
result = OK_RC;
|
|
}
|
|
else
|
|
{
|
|
result = BADRESP_RC;
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Function: rc_check_reply
|
|
*
|
|
* Purpose: verify items in returned packet.
|
|
*
|
|
* Returns: OK_RC -- upon success,
|
|
* BADRESP_RC -- if anything looks funny.
|
|
*
|
|
*/
|
|
|
|
static int rc_check_reply (AUTH_HDR *auth, int bufferlen, char *secret,
|
|
unsigned char *vector, unsigned char seq_nbr)
|
|
{
|
|
int secretlen;
|
|
int totallen;
|
|
unsigned char calc_digest[AUTH_VECTOR_LEN];
|
|
unsigned char reply_digest[AUTH_VECTOR_LEN];
|
|
|
|
totallen = ntohs (auth->length);
|
|
|
|
secretlen = strlen (secret);
|
|
|
|
/* Do sanity checks on packet length */
|
|
if ((totallen < 20) || (totallen > 4096))
|
|
{
|
|
error("rc_check_reply: received RADIUS server response with invalid length");
|
|
return (BADRESP_RC);
|
|
}
|
|
|
|
/* Verify buffer space, should never trigger with current buffer size and check above */
|
|
if ((totallen + secretlen) > bufferlen)
|
|
{
|
|
error("rc_check_reply: not enough buffer space to verify RADIUS server response");
|
|
return (BADRESP_RC);
|
|
}
|
|
/* Verify that id (seq. number) matches what we sent */
|
|
if (auth->id != seq_nbr)
|
|
{
|
|
error("rc_check_reply: received non-matching id in RADIUS server response");
|
|
return (BADRESP_RC);
|
|
}
|
|
|
|
/* Verify the reply digest */
|
|
memcpy ((char *) reply_digest, (char *) auth->vector, AUTH_VECTOR_LEN);
|
|
memcpy ((char *) auth->vector, (char *) vector, AUTH_VECTOR_LEN);
|
|
memcpy ((char *) auth + totallen, secret, secretlen);
|
|
rc_md5_calc (calc_digest, (char *) auth, totallen + secretlen);
|
|
|
|
#ifdef DIGEST_DEBUG
|
|
{
|
|
int i;
|
|
|
|
fputs("reply_digest: ", stderr);
|
|
for (i = 0; i < AUTH_VECTOR_LEN; i++)
|
|
{
|
|
fprintf(stderr,"%.2x ", (int) reply_digest[i]);
|
|
}
|
|
fputs("\ncalc_digest: ", stderr);
|
|
for (i = 0; i < AUTH_VECTOR_LEN; i++)
|
|
{
|
|
fprintf(stderr,"%.2x ", (int) calc_digest[i]);
|
|
}
|
|
fputs("\n", stderr);
|
|
}
|
|
#endif
|
|
|
|
if (memcmp ((char *) reply_digest, (char *) calc_digest,
|
|
AUTH_VECTOR_LEN) != 0)
|
|
{
|
|
#ifdef RADIUS_116
|
|
/* the original Livingston radiusd v1.16 seems to have
|
|
a bug in digest calculation with accounting requests,
|
|
authentication request are ok. i looked at the code
|
|
but couldn't find any bugs. any help to get this
|
|
kludge out are welcome. preferably i want to
|
|
reproduce the calculation bug here to be compatible
|
|
to stock Livingston radiusd v1.16. -lf, 03/14/96
|
|
*/
|
|
if (auth->code == PW_ACCOUNTING_RESPONSE)
|
|
return (OK_RC);
|
|
#endif
|
|
error("rc_check_reply: received invalid reply digest from RADIUS server");
|
|
return (BADRESP_RC);
|
|
}
|
|
|
|
return (OK_RC);
|
|
|
|
}
|
|
|
|
/*
|
|
* Function: rc_random_vector
|
|
*
|
|
* Purpose: generates a random vector of AUTH_VECTOR_LEN octets.
|
|
*
|
|
* Returns: the vector (call by reference)
|
|
*
|
|
*/
|
|
|
|
static void rc_random_vector (unsigned char *vector)
|
|
{
|
|
int randno;
|
|
int i;
|
|
int fd;
|
|
|
|
/* well, I added this to increase the security for user passwords.
|
|
we use /dev/urandom here, as /dev/random might block and we don't
|
|
need that much randomness. BTW, great idea, Ted! -lf, 03/18/95 */
|
|
|
|
if ((fd = open(_PATH_DEV_URANDOM, O_RDONLY)) >= 0)
|
|
{
|
|
unsigned char *pos;
|
|
int readcount;
|
|
|
|
i = AUTH_VECTOR_LEN;
|
|
pos = vector;
|
|
while (i > 0)
|
|
{
|
|
readcount = read(fd, (char *)pos, i);
|
|
pos += readcount;
|
|
i -= readcount;
|
|
}
|
|
|
|
close(fd);
|
|
return;
|
|
} /* else fall through */
|
|
|
|
for (i = 0; i < AUTH_VECTOR_LEN;)
|
|
{
|
|
randno = magic();
|
|
memcpy ((char *) vector, (char *) &randno, sizeof (int));
|
|
vector += sizeof (int);
|
|
i += sizeof (int);
|
|
}
|
|
|
|
return;
|
|
}
|