/*
 *
 *	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.
 *
 */

static char sccsid[] =
"@(#)users.c	1.12 Copyright 1992 Livingston Enterprises Inc";

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

#include	<stdio.h>
#include	<netdb.h>
#include	<pwd.h>
#include	<time.h>
#include	<ctype.h>

#ifdef DBM

#include	<dbm.h>

#endif /* DBM */

#include	"radius.h"

extern char		*progname;
extern int		debug_flag;
extern char		*radius_dir;

static	void fieldcpy();
static	int  userparse();

#define FIND_MODE_NAME	0
#define FIND_MODE_REPLY	1
#define FIND_MODE_SKIP	2
#define FIND_MODE_FLUSH	3

/*************************************************************************
 *
 *	Function: user_find
 *
 *	Purpose: Find the named user in the database.  Create the
 *		 set of attribute-value pairs to check and reply with
 *		 for this user from the database.
 *
 *************************************************************************/

user_find(name, check_pairs, reply_pairs)
char	*name;
VALUE_PAIR	**check_pairs;
VALUE_PAIR	**reply_pairs;
{
	FILE		*userfd;
	char		buffer[256];
	char		msg[128];
	char		*ptr;
	int		namelen;
	int		mode;
	VALUE_PAIR	*check_first;
	VALUE_PAIR	*reply_first;
#ifdef DBM
	datum		named;
	datum		contentd;
#endif /* DBM */

	/* 
	 * Check for valid input, zero length names not permitted 
	 */

	mode = FIND_MODE_NAME;

	ptr=name;
	while (*ptr != '\0') {
		if (*ptr == ' ' || *ptr == '\t') {
			*ptr = '\0';
		} else {
			ptr++;
		}
	}

	namelen=strlen(name);

	if (namelen < 1) {
		fprintf(stderr, "%s: zero length username not permitted\n",progname);
		return(-1);
	}


	/*
	 * Open the user table
	 */
#ifndef WIN32
	sprintf(buffer, "%s/%s", radius_dir, RADIUS_USERS);
#else
	sprintf(buffer, "%s\\%s", radius_dir, RADIUS_USERS);
#endif
#ifdef DBM
	if(dbminit(buffer) != 0) {
#else /* DBM */
	if((userfd = fopen(buffer, "r")) == (FILE *)NULL) {
#endif /* DBM */
		fprintf(stderr, "%s:Couldn't open %s for reading\n",
				progname, buffer);
		return(-1);
	}

	check_first = (VALUE_PAIR *)NULL;
	reply_first = (VALUE_PAIR *)NULL;


#ifdef DBM
	named.dptr = name;
	named.dsize = strlen(name);
	contentd = fetch(named);

	if(contentd.dsize == 0) {
		named.dptr = "DEFAULT";
		named.dsize = strlen("DEFAULT");
		contentd = fetch(named);
		if(contentd.dsize == 0) {
			dbmclose();
			return(-1);
		}
	}

	/*
	 * Parse the check values
	 */
	ptr = contentd.dptr;
	contentd.dptr[contentd.dsize] = '\0';

	if(userparse(ptr, &check_first) != 0) {
		sprintf(msg, "%s: Parse error for user %s\n",
				progname, name);
		fprintf(stderr, msg);
		log_err(msg);
		pairfree(check_first);
		dbmclose();
		return(-1);
	}
	while(*ptr != '\n' && *ptr != '\0') {
		ptr++;
	}
	if(*ptr != '\n') {
		pairfree(check_first);
		dbmclose();
		return(-1);
	}
	ptr++;
	/*
	 * Parse the reply values
	 */
	if(userparse(ptr, &reply_first) != 0) {
		fprintf(stderr, "%s: Parse error for user %s\n",
			progname, name);
		pairfree(check_first);
		pairfree(reply_first);
		dbmclose();
		return(-1);
	}
	dbmclose();

#else /* DBM */

	while(fgets(buffer, sizeof(buffer), userfd) != (char *)NULL) {
		if(mode == FIND_MODE_NAME) {
			/*
			 * Find the entry starting with the users name
			 */
			if((strncmp(buffer, name, namelen) == 0 &&
		 	 (buffer[namelen] == ' ' || buffer[namelen] == '\t')) ||
					 strncmp(buffer, "DEFAULT", 7) == 0) {
				if(strncmp(buffer, "DEFAULT", 7) == 0) {
					ptr = &buffer[7];
				}
				else {
					ptr = &buffer[namelen];
				}
				/*
				 * Parse the check values
				 */
				if(userparse(ptr, &check_first) != 0) {
					sprintf(msg,"%s: Parse error for user %s\n",
						progname, name);
					fprintf(stderr,msg);
					log_err(msg);
					pairfree(check_first);
					fclose(userfd);
					return(-1);
				}
				mode = FIND_MODE_REPLY;
			}
		}
		else {
			if(*buffer == ' ' || *buffer == '\t') {
				/*
				 * Parse the reply values
				 */
				if(userparse(buffer, &reply_first) != 0) {
					fprintf(stderr,
						"%s: Parse error for user %s\n",
						progname, name);
					pairfree(check_first);
					pairfree(reply_first);
					fclose(userfd);
					return(-1);
				}
			}
			else {
				/* We are done */
				fclose(userfd);
				*check_pairs = check_first;
				*reply_pairs = reply_first;
				return(0);
			}
		}
	}
	fclose(userfd);
#endif /* DBM */

	/* Update the callers pointers */
	if(reply_first != (VALUE_PAIR *)NULL) {
		*check_pairs = check_first;
		*reply_pairs = reply_first;
		return(0);
	}
	return(-1);
}

#define PARSE_MODE_NAME		0
#define PARSE_MODE_EQUAL	1
#define PARSE_MODE_VALUE	2
#define PARSE_MODE_INVALID	3

/*************************************************************************
 *
 *	Function: userparse
 *
 *	Purpose: Parses the buffer to extract the attribute-value pairs.
 *
 *************************************************************************/

static int
userparse(buffer, first_pair)
char		*buffer;
VALUE_PAIR	**first_pair;
{
	int		mode;
	char		attrstr[64];
	char		valstr[64];
	DICT_ATTR	*attr;
	DICT_ATTR	*dict_attrfind();
	DICT_VALUE	*dval;
	DICT_VALUE	*dict_valfind();
	VALUE_PAIR	*pair;
	VALUE_PAIR	*link;
	UINT4		ipstr2long();
	UINT4		get_ipaddr();
	struct tm	*tm;
	time_t		timeval;

	mode = PARSE_MODE_NAME;
	while(*buffer != '\n' && *buffer != '\0') {

		if(*buffer == ' ' || *buffer == '\t' || *buffer == ',') {
			buffer++;
			continue;
		}

		switch(mode) {

		case PARSE_MODE_NAME:
			/* Attribute Name */
			fieldcpy(attrstr, &buffer);
			if((attr = dict_attrfind(attrstr)) ==
						(DICT_ATTR *)NULL) {
				return(-1);
			}
			mode = PARSE_MODE_EQUAL;
			break;

		case PARSE_MODE_EQUAL:
			/* Equal sign */
			if(*buffer == '=') {
				mode = PARSE_MODE_VALUE;
				buffer++;
			}
			else {
				return(-1);
			}
			break;

		case PARSE_MODE_VALUE:
			/* Value */
			fieldcpy(valstr, &buffer);

			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;

			switch(pair->type) {

			case PW_TYPE_STRING:
				strcpy(pair->strvalue, valstr);
				break;

			case PW_TYPE_INTEGER:
				if(isdigit(*valstr)) {
					pair->lvalue = atoi(valstr);
				}
				else if((dval = dict_valfind(valstr)) ==
							(DICT_VALUE *)NULL) {
					free(pair);
					return(-1);
				}
				else {
					pair->lvalue = dval->value;
				}
				break;

			case PW_TYPE_IPADDR:
				pair->lvalue = get_ipaddr(valstr);
				break;

			case PW_TYPE_DATE:
				timeval = time(0);
				tm = localtime(&timeval);
				user_gettime(valstr, tm);
#ifdef TIMELOCAL
				pair->lvalue = (UINT4)timelocal(tm);
#else /* TIMELOCAL */
				pair->lvalue = (UINT4)mktime(tm);
#endif /* TIMELOCAL */
				break;

			default:
				free(pair);
				return(-1);
			}
			pair->next = (VALUE_PAIR *)NULL;
			if(*first_pair == (VALUE_PAIR *)NULL) {
				*first_pair = pair;
			}
			else {
				link = *first_pair;
				while(link->next != (VALUE_PAIR *)NULL) {
					link = link->next;
				}
				link->next = pair;
			}
			mode = PARSE_MODE_NAME;
			break;

		default:
			mode = PARSE_MODE_NAME;
			break;
		}
	}
	return(0);
}

/*************************************************************************
 *
 *	Function: fieldcpy
 *
 *	Purpose: Copy a data field from the buffer.  Advance the buffer
 *		 past the data field.
 *
 *************************************************************************/

static	void
fieldcpy(string, uptr)
char	*string;
char	**uptr;
{
	char	*ptr;

	ptr = *uptr;
	if(*ptr == '"') {
		ptr++;
		while(*ptr != '"' && *ptr != '\0' && *ptr != '\n') {
			*string++ = *ptr++;
		}
		*string = '\0';
		if(*ptr == '"') {
			ptr++;
		}
		*uptr = ptr;
		return;
	}

	while(*ptr != ' ' && *ptr != '\t' && *ptr != '\0' && *ptr != '\n' &&
						*ptr != '=' && *ptr != ',') {
			*string++ = *ptr++;
	}
	*string = '\0';
	*uptr = ptr;
	return;
}

/*************************************************************************
 *
 *	Function: user_update
 *
 *	Purpose: Updates a user in the database.  Replaces the original
 *		 entry with the name, the list of check items, and the
 *		 list of reply items which are supplied.
 *
 *************************************************************************/

user_update(name, user_check, user_reply)
char		*name;
VALUE_PAIR	*user_check;
VALUE_PAIR	*user_reply;
{
	FILE		*oldfd;
	FILE		*userfd;
	char		buffer[256];
	char		buffer1[256];
	int		namelen;
	int		mode;

#ifndef WIN32
	sprintf(buffer, "%s/%s", radius_dir, RADIUS_USERS);
	sprintf(buffer1, "%s/%s", radius_dir, RADIUS_HOLD);
#else
	sprintf(buffer, "%s\\%s", radius_dir, RADIUS_USERS);
	sprintf(buffer1, "%s\\%s", radius_dir, RADIUS_HOLD);
#endif
	/* Move the user table to a temporary location */
	if(rename(buffer, buffer1) != 0) {
		fprintf(stderr, "%s: Couldn't rename %s\n",
				progname, buffer);
		return(-1);
	}

	/* Open the old user file (using the temporary name */
	if((oldfd = fopen(buffer1, "r")) == (FILE *)NULL) {
		fprintf(stderr, "%s: Couldn't open %s for reading\n",
				progname, buffer1);
		exit(-1);
	}

	/* Open the new user file */
	if((userfd = fopen(buffer, "w")) == (FILE *)NULL) {
		fprintf(stderr, "%s: Couldn't open %s for writing\n",
				progname, buffer);
		exit(-1);
	}

	mode = FIND_MODE_NAME;
	namelen = strlen(name);

	/* Copy the old to the new, only recreating the changed user */
	while(fgets(buffer, sizeof(buffer), oldfd) != (char *)NULL) {
		if(mode == FIND_MODE_NAME) {
			if((strncmp(buffer, name, namelen) == 0 &&
		 	 (buffer[namelen] == ' ' || buffer[namelen] == '\t'))) {

				/* Write our new information */
				fprintf(userfd, "%s\t", name);
				while(user_check != (VALUE_PAIR *)NULL) {
					fprint_attr_val(userfd, user_check);
					if(user_check->next !=
							(VALUE_PAIR *)NULL) {
						fprintf(userfd, ", ");
					}
					user_check = user_check->next;
				}
				fprintf(userfd, "\n\t");
				while(user_reply != (VALUE_PAIR *)NULL) {
					fprint_attr_val(userfd, user_reply);
					if(user_reply->next !=
							(VALUE_PAIR *)NULL) {
						fprintf(userfd, ",\n\t");
					}
					user_reply = user_reply->next;
				}
				fprintf(userfd, "\n");
				mode = FIND_MODE_SKIP;
			}
			else {
				fputs(buffer, userfd);
			}
		}
		else if(mode == FIND_MODE_SKIP) {
			if(*buffer != ' ' && *buffer != '\t') {
				fputs(buffer, userfd);
				mode = FIND_MODE_FLUSH;
			}
		}
		else {
			fputs(buffer, userfd);
		}
	}
	fclose(oldfd);
	fclose(userfd);
	return(0);
}

/*************************************************************************
 *
 *	Function: user_prtime
 *
 *	Purpose: Turns printable string into correct tm struct entries
 *
 *************************************************************************/

static char *months[] = {
	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

user_gettime(valstr, tm)
char		*valstr;
struct tm	*tm;
{
	char	buffer[48];
	int	i;

	/* Get the month */
	for(i = 0;i < 12;i++) {
		if(strncmp(months[i], valstr, 3) == 0) {
			tm->tm_mon = i;
			i = 13;
		}
	}

	/* Get the Day */
	tm->tm_mday = atoi(&valstr[4]);

	/* Now the year */
	tm->tm_year = atoi(&valstr[7]) - 1900;
}
