/*
 *
 *	RADIUS
 *	Remote Authentication Dial In User Service
 *
 *
 *	Livingston Enterprises, Inc.
 *	6920 Koll Center Parkway
 *	Pleasanton, CA   94566
 *
 *	Copyright 1992 Livingston Enterprises, Inc.
 *
 *	Permission to use, copy, modify, and distribute this software for any
 *	purpose and without fee is hereby granted, provided that this
 *	copyright and permission notice appear on all copies and supporting
 *	documentation, the name of Livingston Enterprises, Inc. not be used
 *	in advertising or publicity pertaining to distribution of the
 *	program without specific prior permission, and notice be given
 *	in supporting documentation that copying and distribution is by
 *	permission of Livingston Enterprises, Inc.   
 *
 *	Livingston Enterprises, Inc. makes no representations about
 *	the suitability of this software for any purpose.  It is
 *	provided "as is" without express or implied warranty.
 *
 */

/* don't look here for the version, run radiusd -v or look in version.c */
static char sccsid[] =
"@(#)radiusd.c	1.17 Copyright 1992 Livingston Enterprises Inc";

#include	<sys/types.h>
#include	<sys/socket.h>
#include	<sys/time.h>
#include	<sys/file.h>
#include	<netinet/in.h>

#include	<stdio.h>
#include	<netdb.h>
#include	<fcntl.h>
#include	<pwd.h>
#include	<time.h>
#include	<ctype.h>
#include	<unistd.h>
#include	<signal.h>
#include	<errno.h>
#include	<sys/wait.h>

#if !defined(NOSHADOW)
#include	<shadow.h>
#endif /* !NOSHADOW */

#include	"radius.h"

char		recv_buffer[4096];
char		send_buffer[4096];
char		*progname;
int		sockfd;
int		acctfd;
int		debug_flag;
int		spawn_flag;
int		acct_pid;
char		*radius_dir;
char		*radacct_dir;
UINT4		expiration_seconds;
UINT4		warning_seconds;
extern int	errno;
static AUTH_REQ	*first_request;

void		sig_fatal();
void		sig_hup();
void		sig_cleanup();
void		rad_passchange();

main(argc, argv)
int	argc;
char	**argv;
{
	int			salen;
	int			result;
	struct	sockaddr	salocal;
	struct	sockaddr	saremote;
	struct	sockaddr_in	*sin;
	struct	servent		*svp;
        u_short                 lport;
	AUTH_REQ		*authreq;
	AUTH_REQ		*radrecv();
	char			argval;
	int			t;
	int			pid;
	int			cons;
	fd_set			readfds;
	int			status;

	progname = *argv++;
	argc--;

	debug_flag = 0;
	spawn_flag = 1;
	radacct_dir = RADACCT_DIR;
	radius_dir = RADIUS_DIR;

	signal(SIGHUP, sig_hup);
	signal(SIGINT, sig_fatal);
	signal(SIGQUIT, sig_fatal);
	signal(SIGILL, sig_fatal);
	signal(SIGTRAP, sig_fatal);
	signal(SIGIOT, sig_fatal);
	signal(SIGFPE, sig_fatal);
	signal(SIGTERM, sig_fatal);
	signal(SIGCHLD, sig_cleanup);

	while(argc) {

		if(**argv != '-') {
			usage();
		}

		argval = *(*argv + 1);
		argc--;
		argv++;

		switch(argval) {

		case 'a':
			if(argc == 0) {
				usage();
			}
			radacct_dir = *argv;
			argc--;
			argv++;
			break;
		
		case 'd':
			if(argc == 0) {
				usage();
			}
			radius_dir = *argv;
			argc--;
			argv++;
			break;
		
		case 's':	/* Single process mode */
			spawn_flag = 0;
			break;

		case 'v':
			version();
			break;

		case 'x':
			debug_flag = 1;
			break;
		
		default:
			usage();
			break;
		}
	}

	/* Initialize the dictionary */
	if(dict_init() != 0) {
		exit(-1);
	}

	/* Initialize Configuration Values */
	if(config_init() != 0) {
		exit(-1);
	}

	svp = getservbyname ("radius", "udp");
	if (svp == (struct servent *) 0) {
		fprintf (stderr, "%s: No such service: radius/udp\n",
			progname);
		exit(-1);
	}
	lport = (u_short) svp->s_port;

	sockfd = socket (AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0) {
		(void) perror ("auth socket");
		exit(-1);
	}

	sin = (struct sockaddr_in *) & salocal;
        memset ((char *) sin, '\0', sizeof (salocal));
	sin->sin_family = AF_INET;
	sin->sin_addr.s_addr = INADDR_ANY;
	sin->sin_port = lport;

	result = bind (sockfd, & salocal, sizeof (*sin));
	if (result < 0) {
		(void) perror ("auth bind");
		exit(-1);
	}

	/*
	 * Open Accounting Socket.
	 */
	/* svp = getservbyname ("radacct", "udp");
	if (svp == (struct servent *) 0) {
		fprintf (stderr, "%s: No such service: %s/%s\n",
			progname, "radacct", "udp");
		exit(-1);
	} */
	lport = htons(ntohs(lport) +1);
	acctfd = socket (AF_INET, SOCK_DGRAM, 0);
	if (acctfd < 0) {
		(void) perror ("acct socket");
		exit(-1);
	}

	sin = (struct sockaddr_in *) & salocal;
        memset ((char *) sin, '\0', sizeof (salocal));
	sin->sin_family = AF_INET;
	sin->sin_addr.s_addr = INADDR_ANY;
	sin->sin_port = lport;

	result = bind (acctfd, & salocal, sizeof (*sin));
	if (result < 0) {
		(void) perror ("acct bind");
		exit(-1);
	}

	/*
	 *	Disconnect from session
	 */
	if(debug_flag == 0) {
		pid = fork();
		if(pid < 0) {
			fprintf(stderr, "%s: Couldn't fork\n",
						progname);
			exit(-1);
		}
		if(pid > 0) {
			exit(0);
		}
	}

	/*
	 *	Disconnect from tty
	 */
	for (t = 32; t >= 3; t--) {
		if(t != sockfd && t != acctfd) {
			close(t);
		}
	}

#if !defined(M_UNIX)
	/*
	 * Open system console as stderr
	 */
	cons = open("/dev/console", O_WRONLY | O_NOCTTY);
	if(cons != 2) {
		dup2(cons, 2);
		close(cons);
	}
#endif
	/*
	 * If we are able to spawn processes, we will start a child
	 * to listen for Accounting requests.  If not, we will 
	 * listen for them ourself.
	 */
	if(spawn_flag) {
		acct_pid = fork();
		if(acct_pid < 0) {
			fprintf(stderr, "%s: Couldn't fork\n",
						progname);
			exit(-1);
		}
		if(acct_pid > 0) {
			close(acctfd);
			acctfd = -1;
		}
		else {
			close(sockfd);
			sockfd = -1;
		}
	}
		

	/*
	 *	Receive user requests
	 */
	sin = (struct sockaddr_in *) & saremote;

	for(;;) {

		FD_ZERO(&readfds);
		if(sockfd >= 0) {
			FD_SET(sockfd, &readfds);
		}
		if(acctfd >= 0) {
			FD_SET(acctfd, &readfds);
		}

		status = select(32, &readfds, NULL, NULL, NULL);
		if(status == -1) {
			if (errno == EINTR)
				continue;
			sig_fatal(101);
		}
		if(sockfd >= 0 && FD_ISSET(sockfd, &readfds)) {
			salen = sizeof (saremote);
			result = recvfrom (sockfd, (char *) recv_buffer,
				(int) sizeof(recv_buffer),
				(int) 0, & saremote, & salen);

			if(result > 0) {
				authreq = radrecv(
					ntohl(sin->sin_addr.s_addr),
					ntohs(sin->sin_port),
					recv_buffer, result);
				radrespond(authreq, sockfd);
			}
			else if(result < 0 && errno == EINTR) {
				result = 0;
			}
		}
		if(acctfd >=0 && FD_ISSET(acctfd, &readfds)) {
			salen = sizeof (saremote);
			result = recvfrom (acctfd, (char *) recv_buffer,
				(int) sizeof(recv_buffer),
				(int) 0, & saremote, & salen);

			if(result > 0) {
				authreq = radrecv(
					ntohl(sin->sin_addr.s_addr),
					ntohs(sin->sin_port),
					recv_buffer, result);
				radrespond(authreq, acctfd);
			}
			else if(result < 0 && errno == EINTR) {
				result = 0;
			}
		}
	}
}

/*************************************************************************
 *
 *	Function: radrecv
 *
 *	Purpose: Receive UDP client requests, build an authorization request
 *		 structure, and attach attribute-value pairs contained in
 *		 the request to the new structure.
 *
 *************************************************************************/

AUTH_REQ	*
radrecv(host, udp_port, buffer, length)
UINT4	host;
u_short	udp_port;
u_char	*buffer;
int	length;
{
	u_char		*ptr;
	AUTH_HDR	*auth;
	int		totallen;
	int		attribute;
	int		attrlen;
	DICT_ATTR	*attr;
	DICT_ATTR	*dict_attrget();
	char		string[64];
	UINT4		lvalue;
	char		*ip_hostname();
	VALUE_PAIR	*first_pair;
	VALUE_PAIR	*prev;
	VALUE_PAIR	*pair;
	AUTH_REQ	*authreq;

	/*
	 * Pre-allocate the new request data structure
	 */

	if((authreq = (AUTH_REQ *)malloc(sizeof(AUTH_REQ))) ==
						(AUTH_REQ *)NULL) {
		fprintf(stderr, "%s: no memory\n", progname);
		exit(-1);
	}

	auth = (AUTH_HDR *)buffer;
	totallen = ntohs(auth->length);

	DEBUG("radrecv: Request from host %lx code=%d, id=%d, length=%d\n",
				(u_long)host, auth->code, auth->id, totallen);

	/*
	 * Fill header fields
	 */
	authreq->ipaddr = host;
	authreq->udp_port = udp_port;
	authreq->id = auth->id;
	authreq->code = auth->code;
	memcpy(authreq->vector, auth->vector, AUTH_VECTOR_LEN);

	/*
	 * Extract attribute-value pairs
	 */
	ptr = auth->data;
	length -= AUTH_HDR_LEN;
	first_pair = (VALUE_PAIR *)NULL;
	prev = (VALUE_PAIR *)NULL;

	while(length > 0) {

		attribute = *ptr++;
		attrlen = *ptr++;
		if(attrlen < 2) {
			length = 0;
			continue;
		}
		attrlen -= 2;
		if((attr = dict_attrget(attribute)) == (DICT_ATTR *)NULL) {
			DEBUG("Received unknown attribute %d\n", attribute);
		}
		else if ( attrlen >= AUTH_STRING_LEN ) {
			DEBUG("attribute %d too long, %d >= %d\n", attribute,
				attrlen, AUTH_STRING_LEN);
		}
		else {
			if((pair = (VALUE_PAIR *)malloc(sizeof(VALUE_PAIR))) ==
						(VALUE_PAIR *)NULL) {
				fprintf(stderr, "%s: no memory\n",
						progname);
				exit(-1);
			}
			strcpy(pair->name, attr->name);
			pair->attribute = attr->value;
			pair->type = attr->type;
			pair->next = (VALUE_PAIR *)NULL;

			switch(attr->type) {

			case PW_TYPE_STRING:
				memcpy(pair->strvalue, ptr, attrlen);
				pair->strvalue[attrlen] = '\0';
				debug_pair(stdout, pair);
				if(first_pair == (VALUE_PAIR *)NULL) {
					first_pair = pair;
				}
				else {
					prev->next = pair;
				}
				prev = pair;
				break;
			
			case PW_TYPE_INTEGER:
			case PW_TYPE_IPADDR:
				memcpy(&lvalue, ptr, sizeof(UINT4));
				pair->lvalue = ntohl(lvalue);
				debug_pair(stdout, pair);
				if(first_pair == (VALUE_PAIR *)NULL) {
					first_pair = pair;
				}
				else {
					prev->next = pair;
				}
				prev = pair;
				break;
			
			default:
				DEBUG("    %s (Unknown Type %d)\n", attr->name,attr->type);
				free(pair);
				break;
			}

		}
		ptr += attrlen;
		length -= attrlen + 2;
	}
	authreq->request = first_pair;
	return(authreq);
}

/*************************************************************************
 *
 *	Function: radrespond
 *
 *	Purpose: Respond to supported requests:
 *
 *		 PW_AUTHENTICATION_REQUEST - Authentication request from
 *				a client network access server.
 *
 *		 PW_ACCOUNTING_REQUEST - Accounting request from
 *				a client network access server.
 *
 *		 PW_PASSWORD_REQUEST - User request to change a password.
 *
 *************************************************************************/

radrespond(authreq, activefd)
AUTH_REQ	*authreq;
int		activefd;
{
	switch(authreq->code) {

	case PW_AUTHENTICATION_REQUEST:
		if(spawn_flag) {
			rad_spawn_child(authreq, activefd);
		}
		else {
			rad_authenticate(authreq, activefd);
		}
		break;
	
	case PW_ACCOUNTING_REQUEST:
		rad_accounting(authreq, activefd);
		break;
	
	case PW_PASSWORD_REQUEST:
		rad_passchange(authreq, activefd);
		break;
	
	default:
		break;
	}
	return(0);
}



/*************************************************************************
 *
 *	Function: rad_spawn_child
 *
 *	Purpose: Spawns child processes to perform password authentication
 *		 and respond to RADIUS clients.  This functions also
 *		 cleans up complete child requests, and verifies that there
 *		 is only one process responding to each request (duplicate
 *		 requests are filtered out.
 *
 *************************************************************************/

rad_spawn_child(authreq, activefd)
AUTH_REQ	*authreq;
int		activefd;
{
	AUTH_REQ	*curreq;
	AUTH_REQ	*prevreq;
	UINT4		curtime;
	int		request_count;
	char		msg[128];
	char		*ip_hostname();
	int		child_pid;

	curtime = (UINT4)time(0);
	request_count = 0;
	curreq = first_request;
	prevreq = (AUTH_REQ *)NULL;
	while(curreq != (AUTH_REQ *)NULL) {
		if(curreq->child_pid == -1 &&
				curreq->timestamp + CLEANUP_DELAY <= curtime) {
			/* Request completed, delete it */
			if(prevreq == (AUTH_REQ *)NULL) {
				first_request = curreq->next;
				pairfree(curreq->request);
				free(curreq);
				curreq = first_request;
			}
			else {
				prevreq->next = curreq->next;
				pairfree(curreq->request);
				free(curreq);
				curreq = prevreq->next;
			}
		}
		else if(curreq->ipaddr == authreq->ipaddr &&
					curreq->id == authreq->id) {
			/* This is a duplicate request - just drop it */
			sprintf(msg, "Dropping duplicate: from %s - ID: %d\n",
				ip_hostname(authreq->ipaddr), authreq->id);
			log_err(msg);
			pairfree(authreq->request);
			free(authreq);
			return(0);
		}
		else {
			if(curreq->timestamp + MAX_REQUEST_TIME <= curtime &&
						curreq->child_pid != -1) {
				/* This request seems to have hung - kill it */
				child_pid = curreq->child_pid;
				sprintf(msg,
					"Killing unresponsive child pid %d\n",
								child_pid);
				log_err(msg);
				curreq->child_pid = -1;
				kill(child_pid, SIGHUP);
			}
			prevreq = curreq;
			curreq = curreq->next;
			request_count++;
		}
	}

	/* This is a new request */
	if(request_count > MAX_REQUESTS) {
		sprintf(msg, "Dropping request (too many): from %s - ID: %d\n",
				ip_hostname(authreq->ipaddr), authreq->id);
		log_err(msg);
		pairfree(authreq->request);
		free(authreq);
		return(0);
	}

	/* Add this request to the list */
	authreq->next = (AUTH_REQ *)NULL;
	authreq->child_pid = -1;
	authreq->timestamp = curtime;

	if(prevreq == (AUTH_REQ *)NULL) {
		first_request = authreq;
	}
	else {
		prevreq->next = authreq;
	}

	/* fork our child */
	child_pid = fork();
	if(child_pid < 0) {
		sprintf(msg, "Fork failed for request from %s - ID: %d\n",
				ip_hostname(authreq->ipaddr), authreq->id);
		log_err(msg);
	}
	if(child_pid == 0) {
		/* This is the child, it should go ahead and respond */
		rad_authenticate(authreq, activefd);
		exit(0);
	}

	/* Register the Child */
	authreq->child_pid = child_pid;
	return(0);
}

void
sig_cleanup()
{
	int		status;
        pid_t		pid;
	AUTH_REQ	*curreq;
 
        for (;;) {
		pid = waitpid((pid_t)-1,&status,WNOHANG);
		signal(SIGCHLD, sig_cleanup);
                if (pid <= 0)
                        return;

#if defined (aix)
		kill(pid, SIGKILL);
#endif

		if(pid == acct_pid) {
			sig_fatal(100);
		}
		curreq = first_request;
		while(curreq != (AUTH_REQ *)NULL) {
			if(curreq->child_pid == pid) {
				curreq->child_pid = -1;
				curreq->timestamp = (UINT4)time(0);
				break;
			}
			curreq = curreq->next;
		}
        }
}

/*************************************************************************
 *
 *	Function: rad_passchange
 *
 *	Purpose: Change a users password
 *
 *************************************************************************/

void
rad_passchange(authreq, activefd)
AUTH_REQ	*authreq;
int		activefd;
{
	VALUE_PAIR	*namepair;
	VALUE_PAIR	*check_item;
	VALUE_PAIR	*newpasspair;
	VALUE_PAIR	*oldpasspair;
	VALUE_PAIR	*curpass;
	VALUE_PAIR	*user_check;
	VALUE_PAIR	*user_reply;
	char		pw_digest[16];
	char		string[64];
	char		passbuf[AUTH_PASS_LEN];
	int		i;
	int		secretlen;
	char		msg[128];
	char		*ip_hostname();

	/* Get the username */
	namepair = authreq->request;
	while(namepair != (VALUE_PAIR *)NULL) {
		if(namepair->attribute == PW_USER_NAME) {
			break;
		}
		namepair = namepair->next;
	}
	if(namepair == (VALUE_PAIR *)NULL) {
		sprintf(msg,
			"Passchange: from %s - No User name supplied\n",
			ip_hostname(authreq->ipaddr));
		log_err(msg);
		pairfree(authreq->request);
		memset(authreq, 0, sizeof(AUTH_REQ));
		free(authreq);
		return;
	}

	/*
	 * Look the user up in the database
	 */
	if(user_find(namepair->strvalue, &user_check, &user_reply) != 0) {
		sprintf(msg,
			"Passchange: from %s - Invalid User: %s\n",
			ip_hostname(authreq->ipaddr), namepair->strvalue);
		log_err(msg);
		send_reject(authreq, (char *)NULL, activefd);
		pairfree(authreq->request);
		memset(authreq, 0, sizeof(AUTH_REQ));
		free(authreq);
		return;
	}

	/*
	 * Validate the user -
	 *
	 * We have to unwrap this in a special way to decrypt the
	 * old and new passwords.  The MD5 calculation is based
	 * on the old password.  The vector is different.  The old
	 * password is encrypted using the encrypted new password
	 * as its vector.  The new password is encrypted using the
	 * random encryption vector in the request header.
	 */

	/* Extract the attr-value pairs for the old and new passwords */
	check_item = authreq->request;
	while(check_item != (VALUE_PAIR *)NULL) {
		if(check_item->attribute == PW_PASSWORD) {
			newpasspair = check_item;
		}
		else if(check_item->attribute == PW_OLD_PASSWORD) {
			oldpasspair = check_item;
		}
		check_item = check_item->next;
	}

	/* Verify that both encrypted passwords were supplied */
	if(newpasspair == (VALUE_PAIR *)NULL ||
					oldpasspair == (VALUE_PAIR *)NULL) {
		/* Missing one of the passwords */
		sprintf(msg,
			"Passchange: from %s - Missing Password: %s\n",
			ip_hostname(authreq->ipaddr), namepair->strvalue);
		log_err(msg);
		send_reject(authreq, (char *)NULL, activefd);
		pairfree(authreq->request);
		pairfree(user_check);
		pairfree(user_reply);
		memset(authreq, 0, sizeof(AUTH_REQ));
		free(authreq);
		return;
	}

	/* Get the current password from the database */
	curpass = user_check;
	while(curpass != (VALUE_PAIR *)NULL) {
		if(curpass->attribute == PW_PASSWORD) {
			break;
		}
		curpass = curpass->next;
	}
	if((curpass == (VALUE_PAIR *)NULL) || curpass->strvalue == (char *)NULL) {
		/* Missing our local copy of the password */
		sprintf(msg,
			"Passchange: from %s - Missing Local Password: %s\n",
			ip_hostname(authreq->ipaddr), namepair->strvalue);
		log_err(msg);
		send_reject(authreq, (char *)NULL, activefd);
		pairfree(authreq->request);
		pairfree(user_check);
		pairfree(user_reply);
		memset(authreq, 0, sizeof(AUTH_REQ));
		free(authreq);
		return;
	}
	if(strcmp(curpass->strvalue,"UNIX") == 0) {
		/* Can't change passwords that aren't in users file */
		sprintf(msg,
			"Passchange: from %s - system password change not allowed: %s\n",
			ip_hostname(authreq->ipaddr), namepair->strvalue);
		log_err(msg);
		send_reject(authreq, (char *)NULL, activefd);
		pairfree(authreq->request);
		pairfree(user_check);
		pairfree(user_reply);
		memset(authreq, 0, sizeof(AUTH_REQ));
		free(authreq);
		return;
	}

	/* Decrypt the old password */
	secretlen = strlen(curpass->strvalue);
	memcpy(string, curpass->strvalue, secretlen);
	memcpy(string + secretlen, newpasspair->strvalue, AUTH_VECTOR_LEN);
	md5_calc(pw_digest, string, AUTH_VECTOR_LEN + secretlen);
	memcpy(passbuf, oldpasspair->strvalue, AUTH_PASS_LEN);
	for(i = 0;i < AUTH_PASS_LEN;i++) {
		passbuf[i] ^= pw_digest[i];
	}

	/* Did they supply the correct password ??? */
	if(strncmp(passbuf, curpass->strvalue, AUTH_PASS_LEN) != 0) {
		sprintf(msg,
			"Passchange: from %s - Incorrect Password: %s\n",
			ip_hostname(authreq->ipaddr), namepair->strvalue);
		log_err(msg);
		send_reject(authreq, (char *)NULL, activefd);
		pairfree(authreq->request);
		pairfree(user_check);
		pairfree(user_reply);
		memset(authreq, 0, sizeof(AUTH_REQ));
		free(authreq);
		return;
	}

	/* Decrypt the new password */
	memcpy(string, curpass->strvalue, secretlen);
	memcpy(string + secretlen, authreq->vector, AUTH_VECTOR_LEN);
	md5_calc(pw_digest, string, AUTH_VECTOR_LEN + secretlen);
	memcpy(passbuf, newpasspair->strvalue, AUTH_PASS_LEN);
	for(i = 0;i < AUTH_PASS_LEN;i++) {
		passbuf[i] ^= pw_digest[i];
	}

	/* Update the users password */
	strncpy(curpass->strvalue, passbuf, AUTH_PASS_LEN);

	/* Add a new expiration date if we are aging passwords */
	if(expiration_seconds != (UINT4)0) {
		set_expiration(user_check, expiration_seconds);
	}

	/* Update the database */
	if(user_update(namepair->strvalue, user_check, user_reply) != 0) {
		send_reject(authreq, (char *)NULL, activefd);
		sprintf(msg,
			"Passchange: unable to update password for %s\n",
			namepair->strvalue);
		log_err(msg);

	}
	else {
		send_pwack(authreq, activefd);
	}
	pairfree(authreq->request);
	pairfree(user_check);
	pairfree(user_reply);
	memset(authreq, 0, sizeof(AUTH_REQ));
	free(authreq);
	return;
}

/*************************************************************************
 *
 *	Function: rad_authenticate
 *
 *	Purpose: Process and reply to an authentication request
 *
 *************************************************************************/

rad_authenticate(authreq, activefd)
AUTH_REQ	*authreq;
int		activefd;
{
	VALUE_PAIR	*namepair;
	VALUE_PAIR	*check_item;
	VALUE_PAIR	*auth_item;
	VALUE_PAIR	*user_check;
	VALUE_PAIR	*user_reply;
	int		result;
	char		pw_digest[16];
	char		string[128];
	int		i;
	char		msg[128];
	char		umsg[128];
	char		*user_msg;
	char		*ip_hostname();
	int		retval;
	char		*ptr;

	/* Get the username from the request */
	namepair = authreq->request;
	while(namepair != (VALUE_PAIR *)NULL) {
		if(namepair->attribute == PW_USER_NAME) {
			break;
		}
		namepair = namepair->next;
	}
	if((namepair == (VALUE_PAIR *)NULL) || 
	   (strlen(namepair->strvalue) <= 0)) {
		sprintf(msg, "Authenticate: from %s - No User Name\n",
			ip_hostname(authreq->ipaddr));
		log_err(msg);
		pairfree(authreq->request);
		memset(authreq, 0, sizeof(AUTH_REQ));
		free(authreq);
		return;
	}

	/* Verify the client and Calculate the MD5 Password Digest */
	if(calc_digest(pw_digest, authreq) != 0) {
		/* We dont respond when this fails */
		sprintf(msg, "Authenticate: from %s - Security Breach: %s\n",
			ip_hostname(authreq->ipaddr), namepair->strvalue);
		log_err(msg);
		pairfree(authreq->request);
		memset(authreq, 0, sizeof(AUTH_REQ));
		free(authreq);
		return;
	}

	/* Get the user from the database */
	if(user_find(namepair->strvalue, &user_check, &user_reply) != 0) {
		sprintf(msg, "Authenticate: from %s - Invalid User: %s\n",
			ip_hostname(authreq->ipaddr), namepair->strvalue);
		log_err(msg);
		send_reject(authreq, (char *)NULL, activefd);
		pairfree(authreq->request);
		memset(authreq, 0, sizeof(AUTH_REQ));
		free(authreq);
		return;
	}

	/* Validate the user */

	/* Look for matching check items */
	result = 0;
	user_msg = (char *)NULL;
	check_item = user_check;
	while(result == 0 && check_item != (VALUE_PAIR *)NULL) {

		/*
		 * Check expiration date if we are doing password aging.
		 */
		if(check_item->attribute == PW_EXPIRATION) {
			/* Has this user's password expired */
			retval = pw_expired(check_item->lvalue);
			if(retval < 0) {
				result = -1;
				user_msg = "Password Has Expired\r\n";
			}
			else {
				if(retval > 0) {
					sprintf(umsg,
					  "Password Will Expire in %d Days\r\n",
					  retval);
					user_msg = umsg;
				}
				check_item = check_item->next;
			}
			continue;
		}

		/*
		 * Look for the matching attribute in the request.
		 */
		auth_item = authreq->request;
		while(auth_item != (VALUE_PAIR *)NULL) {
			if(check_item->attribute == auth_item->attribute) {
				break;
			}
			if(check_item->attribute == PW_PASSWORD &&
				     auth_item->attribute == PW_CHAP_PASSWORD) {
				break;
			}

			auth_item = auth_item->next;
		}
		if(auth_item == (VALUE_PAIR *)NULL) {
			result = -1;
			continue;
		}

		/*
		 * Special handling for passwords which are encrypted,
		 * and sometimes authenticated against the UNIX passwd database.
		 * Also they can come using the Three-Way CHAP.
		 *
		 */
		if(check_item->attribute == PW_PASSWORD) {
			if(auth_item->attribute == PW_CHAP_PASSWORD) {
				/* Use MD5 to verify */
				ptr = string;
				*ptr++ = *auth_item->strvalue;
				strcpy(ptr, check_item->strvalue);
				ptr += strlen(check_item->strvalue);
				memcpy(ptr, authreq->vector, AUTH_VECTOR_LEN);
				md5_calc(pw_digest, string,
					1 + CHAP_VALUE_LENGTH +
					strlen(check_item->strvalue));
				/* Compare them */
				if(memcmp(pw_digest, auth_item->strvalue + 1,
						CHAP_VALUE_LENGTH) != 0) {
					result = -1;
				}
			}
			else {
				/* Decrypt the password */
				memcpy(string,
					auth_item->strvalue, AUTH_PASS_LEN);
				for(i = 0;i < AUTH_PASS_LEN;i++) {
					string[i] ^= pw_digest[i];
				}
				string[AUTH_PASS_LEN] = '\0';
				/* Test Code for Challenge */
				if(strcmp(string, "challenge") == 0) {
					send_challenge(authreq, 
				"You want me to challenge you??\r\nOkay I will",
						"1",activefd);
					pairfree(authreq->request);
					memset(authreq, 0, sizeof(AUTH_REQ));
					free(authreq);
					return;
				}
				if(strcmp(check_item->strvalue, "UNIX") == 0) {
					if(unix_pass(namepair->strvalue,
								string) != 0) {
						result = -1;
						user_msg = (char *)NULL;
					}
				}
				else if(strcmp(check_item->strvalue,
								string) != 0) {
					result = -1;
					user_msg = (char *)NULL;
				}
			}
		}
		else {
			switch(check_item->type) {

			case PW_TYPE_STRING:
				if(strcmp(check_item->strvalue,
						auth_item->strvalue) != 0) {
					result = -1;
				}
				break;

			case PW_TYPE_INTEGER:
			case PW_TYPE_IPADDR:
				if(check_item->lvalue != auth_item->lvalue) {
					result = -1;
				}
				break;

			default:
				result = -1;
				break;
			}
		}
		check_item = check_item->next;
	}
	if(result != 0) {
		send_reject(authreq, user_msg, activefd);
	}
	else {
		send_accept(authreq, user_reply, user_msg, activefd);
	}
	pairfree(authreq->request);
	memset(authreq, 0, sizeof(AUTH_REQ));
	free(authreq);
	pairfree(user_check);
	pairfree(user_reply);
	return;
}

/*************************************************************************
 *
 *	Function: send_reject
 *
 *	Purpose: Reply to the request with a REJECT.  Also attach
 *		 any user message provided.
 *
 *************************************************************************/

send_reject(authreq, msg, activefd)
AUTH_REQ	*authreq;
char		*msg;
int		activefd;
{
	AUTH_HDR		*auth;
	struct	sockaddr	saremote;
	struct	sockaddr_in	*sin;
	char			*ip_hostname();
	char			digest[AUTH_VECTOR_LEN];
	int			secretlen;
	int			total_length;
	u_char			*ptr;
	int			len;

	auth = (AUTH_HDR *)send_buffer;

	/* Build standard response header */
	if(authreq->code == PW_PASSWORD_REQUEST) {
		auth->code = PW_PASSWORD_REJECT;
	}
	else {
		auth->code = PW_AUTHENTICATION_REJECT;
	}
	auth->id = authreq->id;
	memcpy(auth->vector, authreq->vector, AUTH_VECTOR_LEN);
	total_length = AUTH_HDR_LEN;

	/* Append the user message */
	if(msg != (char *)NULL) {
		len = strlen(msg);
		if(len > 0 && len < AUTH_STRING_LEN) {
			ptr = auth->data;
			*ptr++ = PW_PORT_MESSAGE;
			*ptr++ = len + 2;
			memcpy(ptr, msg, len);
			ptr += len;
			total_length += len + 2;
		}
	}

	/* Set total length in the header */
	auth->length = htons(total_length);

	/* Calculate the response digest */
	secretlen = strlen(authreq->secret);
	memcpy(send_buffer + total_length, authreq->secret, secretlen);
	md5_calc(digest, (char *)auth, total_length + secretlen);
	memcpy(auth->vector, digest, AUTH_VECTOR_LEN);
	memset(send_buffer + total_length, 0, secretlen);

	sin = (struct sockaddr_in *) &saremote;
        memset ((char *) sin, '\0', sizeof (saremote));
	sin->sin_family = AF_INET;
	sin->sin_addr.s_addr = htonl(authreq->ipaddr);
	sin->sin_port = htons(authreq->udp_port);

	DEBUG("Sending Reject of id %d to %lx (%s)\n",
		authreq->id, (u_long)authreq->ipaddr,
		ip_hostname(authreq->ipaddr));
	
	/* Send it to the user */
	sendto(activefd, (char *)auth, (int)total_length, (int)0,
			(struct sockaddr *) &saremote, sizeof(struct sockaddr_in));
}

/*************************************************************************
 *
 *	Function: send_challenge
 *
 *	Purpose: Reply to the request with a CHALLENGE.  Also attach
 *		 any user message provided and a state value.
 *
 *************************************************************************/

send_challenge(authreq, msg, state, activefd)
AUTH_REQ	*authreq;
char		*msg;
char		*state;
int		activefd;
{
	AUTH_HDR		*auth;
	struct	sockaddr_in	saremote;
	struct	sockaddr_in	*sin;
	char			*ip_hostname();
	char			digest[AUTH_VECTOR_LEN];
	int			secretlen;
	int			total_length;
	u_char			*ptr;
	int			len;

	auth = (AUTH_HDR *)send_buffer;

	/* Build standard response header */
	auth->code = PW_ACCESS_CHALLENGE;
	auth->id = authreq->id;
	memcpy(auth->vector, authreq->vector, AUTH_VECTOR_LEN);
	total_length = AUTH_HDR_LEN;

	/* Append the user message */
	if(msg != (char *)NULL) {
		len = strlen(msg);
		if(len > 0 && len < AUTH_STRING_LEN) {
			ptr = auth->data;
			*ptr++ = PW_PORT_MESSAGE;
			*ptr++ = len + 2;
			memcpy(ptr, msg, len);
			ptr += len;
			total_length += len + 2;
		}
	}

	/* Append the state info */
	if((state != (char *)NULL) && (strlen(state) > 0)) {
		len = strlen(state);
		*ptr++ = PW_STATE;
		*ptr++ = len + 2;
		memcpy(ptr, state, len);
		ptr += len;
		total_length += len + 2;
	}

	/* Set total length in the header */
	auth->length = htons(total_length);

	/* Calculate the response digest */
	secretlen = strlen(authreq->secret);
	memcpy(send_buffer + total_length, authreq->secret, secretlen);
	md5_calc(digest, (char *)auth, total_length + secretlen);
	memcpy(auth->vector, digest, AUTH_VECTOR_LEN);
	memset(send_buffer + total_length, 0, secretlen);

	sin = (struct sockaddr_in *) &saremote;
        memset ((char *) sin, '\0', sizeof (saremote));
	sin->sin_family = AF_INET;
	sin->sin_addr.s_addr = htonl(authreq->ipaddr);
	sin->sin_port = htons(authreq->udp_port);

	DEBUG("Sending Challenge of id %d to %lx (%s)\n",
		authreq->id, (u_long)authreq->ipaddr,
		ip_hostname(authreq->ipaddr));
	
	/* Send it to the user */
	sendto(activefd, (char *)auth, (int)total_length, (int)0,
			(struct sockaddr *) &saremote, sizeof(struct sockaddr_in));
}

/*************************************************************************
 *
 *	Function: send_pwack
 *
 *	Purpose: Reply to the request with an ACKNOWLEDGE.
 *		 User password has been successfully changed.
 *
 *************************************************************************/

send_pwack(authreq, activefd)
AUTH_REQ	*authreq;
int		activefd;
{
	AUTH_HDR		*auth;
	struct	sockaddr	saremote;
	struct	sockaddr_in	*sin;
	char			*ip_hostname();
	char			digest[AUTH_VECTOR_LEN];
	int			secretlen;

	auth = (AUTH_HDR *)send_buffer;

	/* Build standard response header */
	auth->code = PW_PASSWORD_ACK;
	auth->id = authreq->id;
	memcpy(auth->vector, authreq->vector, AUTH_VECTOR_LEN);
	auth->length = htons(AUTH_HDR_LEN);

	/* Calculate the response digest */
	secretlen = strlen(authreq->secret);
	memcpy(send_buffer + AUTH_HDR_LEN, authreq->secret, secretlen);
	md5_calc(digest, (char *)auth, AUTH_HDR_LEN + secretlen);
	memcpy(auth->vector, digest, AUTH_VECTOR_LEN);
	memset(send_buffer + AUTH_HDR_LEN, 0, secretlen);

	sin = (struct sockaddr_in *) &saremote;
        memset ((char *) sin, '\0', sizeof (saremote));
	sin->sin_family = AF_INET;
	sin->sin_addr.s_addr = htonl(authreq->ipaddr);
	sin->sin_port = htons(authreq->udp_port);

	DEBUG("Sending PW Ack of id %d to %lx (%s)\n",
		authreq->id, (u_long)authreq->ipaddr,
		ip_hostname(authreq->ipaddr));
	
	/* Send it to the user */
	sendto(activefd, (char *)auth, (int)AUTH_HDR_LEN, (int)0,
			&saremote, sizeof(struct sockaddr_in));
}

/*************************************************************************
 *
 *	Function: send_accept
 *
 *	Purpose: Reply to the request with an ACKNOWLEDGE.  Also attach
 *		 reply attribute value pairs and any user message provided.
 *
 *************************************************************************/

send_accept(authreq, reply, msg, activefd)
AUTH_REQ	*authreq;
VALUE_PAIR	*reply;
char		*msg;
int		activefd;
{
	AUTH_HDR		*auth;
	u_short			total_length;
	struct	sockaddr	saremote;
	struct	sockaddr_in	*sin;
	u_char			*ptr;
	int			len;
	UINT4			lvalue;
	u_char			digest[16];
	int			secretlen;
	char			*ip_hostname();

	auth = (AUTH_HDR *)send_buffer;

	/* Build standard header */
	auth->code = PW_AUTHENTICATION_ACK;
	auth->id = authreq->id;
	memcpy(auth->vector, authreq->vector, AUTH_VECTOR_LEN);

	DEBUG("Sending Ack of id %d to %lx (%s)\n",
		authreq->id, (u_long)authreq->ipaddr,
		ip_hostname(authreq->ipaddr));

	total_length = AUTH_HDR_LEN;

	/* Load up the configuration values for the user */
	ptr = auth->data;
	while(reply != (VALUE_PAIR *)NULL) {
		debug_pair(stdout, reply);
		*ptr++ = reply->attribute;

		switch(reply->type) {

		case PW_TYPE_STRING:
			len = strlen(reply->strvalue);
			if (len >= AUTH_STRING_LEN) {
				len = AUTH_STRING_LEN - 1;
			}
			*ptr++ = len + 2;
			memcpy(ptr, reply->strvalue,len);
			ptr += len;
			total_length += len + 2;
			break;
			
		case PW_TYPE_DATE:
                case PW_TYPE_INTEGER:
		case PW_TYPE_IPADDR:
			*ptr++ = sizeof(UINT4) + 2;
			lvalue = htonl(reply->lvalue);
			memcpy(ptr, &lvalue, sizeof(UINT4));
			ptr += sizeof(UINT4);
			total_length += sizeof(UINT4) + 2;
			break;

		default:
			break;
		}

		reply = reply->next;
	}

	/* Append the user message */
	if(msg != (char *)NULL) {
		len = strlen(msg);
		if(len > 0 && len < AUTH_STRING_LEN) {
			*ptr++ = PW_PORT_MESSAGE;
			*ptr++ = len + 2;
			memcpy(ptr, msg, len);
			ptr += len;
			total_length += len + 2;
		}
	}

	auth->length = htons(total_length);

	/* Append secret and calculate the response digest */
	secretlen = strlen(authreq->secret);
	memcpy(send_buffer + total_length, authreq->secret, secretlen);
	md5_calc(digest, (char *)auth, total_length + secretlen);
	memcpy(auth->vector, digest, AUTH_VECTOR_LEN);
	memset(send_buffer + total_length, 0, secretlen);

	sin = (struct sockaddr_in *) &saremote;
        memset ((char *) sin, '\0', sizeof (saremote));
	sin->sin_family = AF_INET;
	sin->sin_addr.s_addr = htonl(authreq->ipaddr);
	sin->sin_port = htons(authreq->udp_port);

	/* Send it to the user */
	sendto(activefd, (char *)auth, (int)total_length, (int)0,
			&saremote, sizeof(struct sockaddr_in));
}

/*************************************************************************
 *
 *	Function: unix_pass
 *
 *	Purpose: Check the users password against the standard UNIX
 *		 password table.
 *
 *************************************************************************/

unix_pass(name, passwd)
char	*name;
char	*passwd;
{
	struct passwd	*pwd;
	struct passwd	*getpwnam();
	char		*encpw;
	char		*crypt();
	char		*encrypted_pass;
#if !defined(NOSHADOW)
#if defined(M_UNIX)
	struct passwd	*spwd;
#else
	struct spwd	*spwd;
#endif
#endif /* !NOSHADOW */
	
	/* Get encrypted password from password file */
	if((pwd = getpwnam(name)) == NULL) {
		return(-1);
	}

	encrypted_pass = pwd->pw_passwd;

#if !defined(NOSHADOW)
	if(strcmp(pwd->pw_passwd, "x") == 0) {
		if((spwd = getspnam(name)) == NULL) {
			return(-1);
		}
#if defined(M_UNIX)
		encrypted_pass = spwd->pw_passwd;
#else
		encrypted_pass = spwd->sp_pwdp;
#endif	/* M_UNIX */
	}
#endif	/* !NOSHADOW */

	/* Run encryption algorythm */
	encpw = crypt(passwd, encrypted_pass);

	/* Check it */
	if(strcmp(encpw, encrypted_pass)) {
		return(-1);
	}
	return(0);
}

/*************************************************************************
 *
 *	Function: calc_digest
 *
 *	Purpose: Validates the requesting client NAS.  Calculates the
 *		 digest to be used for decrypting the users password
 *		 based on the clients private key.
 *
 *************************************************************************/

calc_digest(digest, authreq)
u_char		*digest;
AUTH_REQ	*authreq;
{
	FILE	*clientfd;
	u_char	buffer[128];
	u_char	secret[64];
	char	hostnm[256];
	char	msg[128];
	char	*ip_hostname();
	int	secretlen;
	UINT4	ipaddr;
	UINT4	get_ipaddr();

	/* Find the client in the database */
	sprintf(buffer, "%s/%s", radius_dir, RADIUS_CLIENTS);
	if((clientfd = fopen(buffer, "r")) == (FILE *)NULL) {
		fprintf(stderr, "%s: couldn't open %s to find clients\n",
				progname, buffer);
		return(-1);
	}
	ipaddr = (UINT4)0;
	while(fgets(buffer, sizeof(buffer), clientfd) != (char *)NULL) {
		if(*buffer == '#') {
			continue;
		}
		if(sscanf(buffer, "%s%s", hostnm, secret) != 2) {
			continue;
		}
		ipaddr = get_ipaddr(hostnm);
		if(ipaddr == authreq->ipaddr) {
			break;
		}
	}
	fclose(clientfd);
	memset(buffer, 0, sizeof(buffer));

	/*
	 * Validate the requesting IP address -
	 * Not secure, but worth the check for accidental requests
	 */
	if(ipaddr != authreq->ipaddr) {
		strcpy(hostnm,ip_hostname(ipaddr));
		sprintf(msg, "requester address mismatch: %s != %s\n",
			hostnm,
			ip_hostname(authreq->ipaddr));
		log_err(msg);
		memset(secret, 0, sizeof(secret));
		return(-1);
	}

	/* Use the secret to setup the decryption digest */
	secretlen = strlen(secret);
	strcpy(buffer, secret);
	memcpy(buffer + secretlen, authreq->vector, AUTH_VECTOR_LEN);
	md5_calc(digest, buffer, secretlen + AUTH_VECTOR_LEN);
	strcpy(authreq->secret, secret);
	memset(buffer, 0, sizeof(buffer));
	memset(secret, 0, sizeof(secret));
	return(0);
}

/*************************************************************************
 *
 *	Function: debug_pair
 *
 *	Purpose: Print the Attribute-value pair to the desired File.
 *
 *************************************************************************/

debug_pair(fd, pair)
FILE		*fd;
VALUE_PAIR	*pair;
{
	if(debug_flag) {
		fputs("    ", fd);
		fprint_attr_val(fd, pair);
		fputs("\n", fd);
	}
}

/*************************************************************************
 *
 *	Function: usage
 *
 *	Purpose: Display the syntax for starting this program.
 *
 *************************************************************************/

usage()
{
	fprintf(stderr, "Usage: %s [ -a acct_dir ] [ -s ] [ -x ] [ -d db_dir ]\n",progname);
	exit(-1);
}

/*************************************************************************
 *
 *	Function: log_err
 *
 *	Purpose: Log the error message provided to the error log with
		 a time stamp.
 *
 *************************************************************************/

log_err(msg)
char	*msg;
{
	FILE	*msgfd;
	char	buffer[128];
	time_t	timeval;

	sprintf(buffer, "%s/%s", radius_dir, RADIUS_LOG);
	if((msgfd = fopen(buffer, "a")) == (FILE *)NULL) {
		fprintf(stderr, "%s:Couldn't open %s for logging\n",
				progname, buffer);
		return(-1);
	}
	timeval = time(0);
	fprintf(msgfd, "%-24.24s: %s", ctime(&timeval), msg);
	fclose(msgfd);
	return(0);
}

/*************************************************************************
 *
 *	Function: config_init
 *
 *	Purpose: intializes configuration values:
 *
 *		 expiration_seconds - When updating a user password,
 *			the amount of time to add to the current time
 *			to set the time when the password will expire.
 *			This is stored as the VALUE Password-Expiration
 *			in the dictionary as number of days.
 *
 *		warning_seconds - When acknowledging a user authentication
 *			time remaining for valid password to notify user
 *			of password expiration.
 *
 *************************************************************************/

config_init()
{
	DICT_VALUE	*dval;
	DICT_VALUE	*dict_valfind();

	if((dval = dict_valfind("Password-Expiration")) == (DICT_VALUE *)NULL) {
		expiration_seconds = (UINT4)0;
	}
	else {
		expiration_seconds = dval->value * (UINT4)SECONDS_PER_DAY;
	}
	if((dval = dict_valfind("Password-Warning")) == (DICT_VALUE *)NULL) {
		warning_seconds = (UINT4)0;
	}
	else {
		warning_seconds = dval->value * (UINT4)SECONDS_PER_DAY;
	}
	return(0);
}

/*************************************************************************
 *
 *	Function: set_expiration
 *
 *	Purpose: Set the new expiration time by updating or adding
		 the Expiration attribute-value pair.
 *
 *************************************************************************/

set_expiration(user_check, expiration)
VALUE_PAIR	*user_check;
UINT4		expiration;
{
	VALUE_PAIR	*exppair;
	VALUE_PAIR	*prev;
	struct timeval	tp;
	struct timezone	tzp;

	if(user_check == (VALUE_PAIR *)NULL) {
		return(-1);
	}

	/* Look for an existing expiration entry */
	exppair = user_check;
	prev = (VALUE_PAIR *)NULL;
	while(exppair != (VALUE_PAIR *)NULL) {
		if(exppair->attribute == PW_EXPIRATION) {
			break;
		}
		prev = exppair;
		exppair = exppair->next;
	}
	if(exppair == (VALUE_PAIR *)NULL) {
		/* Add a new attr-value pair */
		if((exppair = (VALUE_PAIR *)malloc(sizeof(VALUE_PAIR))) ==
					(VALUE_PAIR *)NULL) {
			fprintf(stderr, "%s: no memory\n", progname);
			exit(-1);
		}
		/* Initialize it */
		strcpy(exppair->name, "Expiration");
		exppair->attribute = PW_EXPIRATION;
		exppair->type = PW_TYPE_DATE;
		*exppair->strvalue = '\0';
		exppair->lvalue = (UINT4)0;
		exppair->next = (VALUE_PAIR *)NULL;

		/* Attach it to the list. */
		prev->next = exppair;
	}

	/* calculate a new expiration */
	gettimeofday(&tp, &tzp);
	exppair->lvalue = tp.tv_sec + expiration;
	return(0);
}

/*************************************************************************
 *
 *	Function: pw_expired
 *
 *	Purpose: Tests to see if the users password has expired.
 *
 *	Return: Number of days before expiration if a warning is required
 8		otherwise 0 for success and -1 for failure.
 *
 *************************************************************************/

pw_expired(exptime)
UINT4	exptime;
{
	struct timeval	tp;
	struct timezone	tzp;
	UINT4		exp_remain;
	int		exp_remain_int;

	if(expiration_seconds == (UINT4)0) {
		return(0);
	}

	gettimeofday(&tp, &tzp);
	if(tp.tv_sec > exptime) {
		return(-1);
	}
	if(warning_seconds != (UINT4)0) {
		if(tp.tv_sec > exptime - warning_seconds) {
			exp_remain = exptime - tp.tv_sec;
			exp_remain /= (UINT4)SECONDS_PER_DAY;
			exp_remain_int = exp_remain;
			return(exp_remain_int);
		}
	}
	return(0);
}

void
sig_fatal(sig)
int	sig;
{
	if(acct_pid > 0) {
		kill(acct_pid, SIGKILL);
	}

	fprintf(stderr, "%s: exit on signal (%d)\n", progname, sig);
	fflush(stderr);
	exit(1);
}

void
sig_hup(sig)
int	sig;
{
	return;
}
