/*
 *
 *	RADIUS
 *	Remote Authentication Dial In User Service
 *
 * ASCEND: @(#)users.c	1.2 (95/07/25 00:55:40)
 *
 *
 *	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>
#include	<string.h>
#include	<unistd.h>
#include	<stdlib.h>

#if defined(DBM_MODE)
#	if defined(SOLARIS)
#		include	</usr/ucbinclude/dbm.h>
#	else
#		include	<dbm.h>
#	endif	/* SOLARIS */
#endif /* DBM_MODE */

#include	"radius.h"
#include	"protos.h"

extern char		*progname;
extern int		debug_flag;
extern char		*radius_dir;
extern char		*radius_users;
extern FILE		*errf;

static int parse_record P__((char *name, char *linep, FILE *userfd,
	VALUE_PAIR **check_first, VALUE_PAIR **reply_first));
extern int user_read P__((FILE **userfd, char *name, char *content));
static int userparse P__((char **buf, VALUE_PAIR **first_pair));
static void fieldcpy P__((char *string, char **uptr));
static void user_gettime P__((char *valstr, struct tm *tm));
extern int curParseLine;	/* -- current line in file being parsed */


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

/*************************************************************************
 *
 *	Function: userinfo_open
 *
 *	Purpose: Opens the users information file. Do different things
 *		 different ways depending on whether we are using
 *		 a flat file or a dbm database.
 *
 *	Returns: zero upon success, non-zero upon failure
 *
 *************************************************************************/

static int
userinfo_open(userfd, infoname)
FILE	**userfd;
char	*infoname;
{
#if defined(DBM_MODE)
	(void)userfd;
	return dbminit(infoname);
#else
	*userfd = fopen(infoname, "r");
	return *userfd == (FILE *)NULL;
#endif
}

/*************************************************************************
 *
 *	Function: userinfo_close
 *
 *	Purpose: Close the users information file. Do different things
 *		 different ways depending on whether we are using
 *		 a flat file or a dbm database.
 *
 *************************************************************************/

static void
userinfo_close(userfd)
FILE	*userfd;
{
#if defined(DBM_MODE)
	(void)userfd;
	dbmclose();
#else
	fclose(userfd);
#endif
}

/*************************************************************************
 *
 *	Function: parse_record
 *
 *	Purpose: Given a record for the requested user, parse the record
 *		 into the required attributes (check_first) and the
 *		 attributes to return (reply_first).
 *		 Ensure that all is fine with the record.
 *
 *		 Perform parsing identically for flat-file and for
 *		 dbm databases.
 *
 *************************************************************************/

static int
parse_record(name, linep, userfd, check_first, reply_first)
char		*name;
char		*linep;
FILE		*userfd;
VALUE_PAIR	**check_first;
VALUE_PAIR	**reply_first;
{
	int stat;
	char		msg[128];

	/*
	 * Parse the check values
	 */
	if( (stat = userparse(&linep, check_first)) != 0) {
		sprintf(msg, "%s: Parse.check error %d for user %s\n",
				progname, stat, name);
		fprintf(errf, msg);
		log_err(msg);
		pairfree(*check_first);
		userinfo_close(userfd);
		return(stat);
	}
	while(*linep != '\n' && *linep != '\0') {
		linep++;
	}
	if(*linep != '\n') {
		pairfree(*check_first);
		userinfo_close(userfd);
		fprintf(errf, "%s: Missing NL for user %s\n",
				progname, name);
		return(MISSING_NEWLINE);
	}
	linep++;
	/*
	 * Parse the reply values
	 */
	if( (stat = userparse(&linep, reply_first)) != 0) {
		fprintf(errf, "%s: Parse.reply error %d for user %s\n",
			progname, stat, name);
		pairfree(*check_first);
		pairfree(*reply_first);
		userinfo_close(userfd);
		return(stat);
	}
	return 0;
}

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

int
user_find(name, check_pairs, reply_pairs)
char		*name;
VALUE_PAIR	**check_pairs;
VALUE_PAIR	**reply_pairs;
{
	FILE		*userfd;
	char		buf[256];
	char		*ptr;
	int		namelen;
	int		mode;
	VALUE_PAIR	*check_first;
	VALUE_PAIR	*reply_first;
#ifdef DBM_MODE
	datum		named;
	datum		contentd;
#else
	char		curname[512];
	char		line[4 * 1024];
#endif /* DBM_MODE */
	int		stat;

	/* 
	 * 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(errf, "%s: zero length username not permitted\n",
			progname);
		return(ZERO_LENGTH_NAME);
	}


	/*
	 * Open the user table
	 */
	sprintf(buf, "%s/%s", radius_dir, radius_users);
	if( userinfo_open(&userfd, buf) != 0 ) {
		fprintf(errf, "%s:Couldn't open %s for reading\n",
				progname, buf);
		return(NO_USER_FILE);
	}

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


#ifdef DBM_MODE
	named.dptr = name;
	named.dsize = strlen(name)+1;
	contentd = fetch(named);

	if(contentd.dsize == 0) {	/* name not found */
		named.dptr = "DEFAULT";
		named.dsize = strlen("DEFAULT")+1;
		contentd = fetch(named);
		if(contentd.dsize == 0) {	/* default not found */
			dbmclose();
			fprintf(errf, "%s:users %s and %s not found\n",
				progname, name, "DEFAULT");
			return(NO_USER_OR_DEFAULT_NAME);
		}
	}
	stat = parse_record(name, contentd.dptr, userfd,
				&check_first, &reply_first);

#else /* DBM_MODE */
	curParseLine = 0;
	while( (stat = user_read(&userfd, curname, line)) == 0 ) {
		if( (strcmp(name, curname) != 0)
		 && (strcmp("DEFAULT", curname) != 0) ) {
			continue;
		}
		stat = parse_record(name, line, userfd,
				&check_first, &reply_first);
		break;
	}

	if( stat == END_OF_USERS_LIST ) {
	    stat = NO_USER_OR_DEFAULT_NAME;
	}
#endif /* DBM_MODE */

	if( stat ) {
		return stat;
	}

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

#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(buf, first_pair)
char		**buf;
VALUE_PAIR	**first_pair;
{
	int		mode;
	char		attrstr[64];
#ifdef ORIG
	char		valstr[64];
#else
	char		valstr[256];
#endif
	DICT_ATTR	*attr;
	DICT_VALUE	*dval;
	VALUE_PAIR	*pair;
	VALUE_PAIR	*link;
	struct tm	*tm;
	time_t		timeval;
	char		*buffer;

	buffer = *buf;
	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) {
				*buf = buffer;
			fprintf(errf, "%s: attribute name %s not found\n",
					progname, attrstr);
				return(DICT_ATTRFIND_ERR);
			}
			mode = PARSE_MODE_EQUAL;
			break;

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

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

			if((pair = (VALUE_PAIR *)malloc(sizeof(VALUE_PAIR))) ==
						(VALUE_PAIR *)NULL) {
				fprintf(errf, "%s: no memory\n",
						progname);
				exit(MEMORY_ERR);
			}
			strcpy(pair->name, attr->name);
			pair->attribute = attr->value;
			pair->type = attr->type;

			switch(pair->type) {
#if defined( BINARY_FILTERS )
                        case PW_TYPE_FILTER_BINARY:
                                /*
                                 * special case to convert filter to binary
                                 */
                                if( filterBinary( pair, valstr ) == -1 ) {
                                    free(pair);
				    *buf = buffer;
                                    return(BINARY_FILTER_ERR);
                                }
				pair->size = pair->lvalue;
                                break;
#endif /* BINARY_FILTERS */

			case PW_TYPE_STRING:
				strcpy(pair->strvalue, valstr);
				pair->size = strlen(valstr) + 1;
				break;

			case PW_TYPE_INTEGER:
				if(isdigit(*valstr)) {
					pair->lvalue = atoi(valstr);
				}
				else if((dval = dict_valfind(valstr)) ==
							(DICT_VALUE *)NULL) {
					free(pair);
					*buf = buffer;
					return(DICT_VALFIND_ERR);
				}
				else {
					pair->lvalue = dval->value;
				}
				pair->size = sizeof(UINT4);
				break;

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

			case PW_TYPE_DATE:
				timeval = time((time_t *)0);
				if( timeval == -1 ) {
					log_err("userparse(): time() err\n");
					pair->lvalue = 0;
					break;
				}
				tm = localtime(&timeval);

				user_gettime(valstr, tm);
#ifdef TIMELOCAL
				pair->lvalue = (UINT4)timelocal(tm);
#else /* TIMELOCAL */
				pair->lvalue = (UINT4)mktime(tm);
#endif /* TIMELOCAL */
				pair->size = sizeof(UINT4);
				break;

			default:
				free(pair);
				*buf = buffer;
				return(DEFAULT_PARSE_ERR);
			}
			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;
		}
	}
	*buf = buffer;
	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.
 *
 *************************************************************************/
int
user_update(name, user_check, user_reply)
char		*name;
VALUE_PAIR	*user_check;
VALUE_PAIR	*user_reply;
{
	FILE		*oldfd;
	FILE		*userfd;
	char		buffer[1024];
	char		buffer1[256];
	int		namelen;
	int		mode;

	sprintf(buffer, "%s/%s", radius_dir, radius_users);
	sprintf(buffer1, "%s/%s", radius_dir, RADIUS_HOLD);

	/* Move the user table to a temporary location */
	if(rename(buffer, buffer1) != 0) {
		fprintf(errf, "%s: Couldn't rename %s\n",
				progname, buffer);
		return(USERFILE_RENAME_FAILED);
	}

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

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

	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" };

static void
user_gettime(valstr, tm)
char		*valstr;
struct tm	*tm;
{
#if(0)
	/* Use this code only if strtok() is not available */

	/*
	 * Date format: "Mmm.dd.yyyy"
	 */
	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;
#else
	/*
	 * Date format: "Mmm+[ \t,]+d+[ \t,]+yyyy"
	 */
	int	i;
	char	datestr[80];
	char	*str;

	strncpy(datestr, valstr, 79);
	datestr[79] = '\0';

	/* Get the month */
	str = strtok(datestr, " \t,");
	if( !str ) {
		return;
	}
	for(i = 0;i < 12;i++) {
		if(strncmp(months[i], str, 3) == 0) {
			tm->tm_mon = i;
			break;
		}
	}

	/* Get the Day */
	str = strtok((char *)0, " \t,");
	if( !str ) {
		return;
	}

	tm->tm_mday = atoi(str);

	/* Now the year */
	str = strtok((char *)0, " \t,");
	if( !str ) {
		return;
	}
	tm->tm_year = atoi(str) - 1900;
#endif
}
