/*
 *
 *	RADIUS
 *	Remote Authentication Dial In User Service
 *
 * ASCEND: @(#)usr_read.c	1.5 (95/07/25 00:55:42)
 *
 *	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.
 *
 *      Copyright (c) 1995 Ascend Communications, Inc.
 *      All rights reserved.
 *
 *	Permission to copy all or part of this material for any purpose is
 *	granted provided that the above copyright notice and this paragraph
 *	are duplicated in all copies.  THIS SOFTWARE IS PROVIDED ``AS IS''
 *	AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
 *	LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *	FOR A PARTICULAR PURPOSE.
 */

/* $Id: usr_read.c,v 1.2 1996/12/12 00:04:57 baskar Exp $ */

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

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

#include	"radius.h"

extern char	*progname;
extern char	*radius_dir;
extern char	*radius_users;
extern FILE	*errf;
int		curParseLine;	/* current line being parsed */

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

#if defined(__STDC__) && (__STDC__ == 1)
#	define	P__(s)	s
#else
#	define	P__(s)	(/* s */)
#endif

#if __STDC__ == 1
#	include	<stdarg.h>
#else
#	include	<varargs.h>
#endif

extern void warn P__((CONST char *fmt, ...));
static int test_eof P__((FILE *userfd));
static int probably_eor P__((FILE *userfd));
extern int user_read P__((FILE **userfd, char *name, char *content));

int	warning;

/*************************************************************************
 *
 *	Function: test_eof
 *
 *	Purpose: See if we are at the end of file (before we actually
 *		 get there).
 *
 *************************************************************************/

static int
test_eof(userfd)
FILE	*userfd;
{
	int	c;
	int	status;

	c = fgetc(userfd);
	if( c == EOF ) {
		status = TRUE;
	}
	else {
		if( ungetc(c, userfd) != c ) {
			perror("test_eof");
			warn("Problem with ungetc");
		}
		status = FALSE;
	}
	return status;
}

/*************************************************************************
 *
 *	Function: probably_eor
 *
 *	Purpose: Simple heuristic to see if the missing comma really
 *		 indicates the end of the record or if the user simply
 *		 forgot to put one in.
 *
 *		 If the next line starts with a space or a tab, assume
 *		 the user forgot the comma, else it's really the end.
 *		 If there is no next line, then it's also the end of the
 *		 record.
 *
 *		NOTE: missing commas followed by comment lines, followed
 *			by additional lines of the record will NOT be
 *			so interpreted; the next non-comment line will be
 *			assumed to be the start of a new record.
 *
 *************************************************************************/

static int
probably_eor(userfd)
FILE	*userfd;
{
	int	c;
	int	status;

	c = fgetc(userfd);
	if( c == EOF ) {
		status = TRUE;
	}
	else if( (c == ' ') || (c == '\t') ) {
		if( ungetc(c, userfd) != c ) {
			perror("probably_eor");
			warn("Problem with ungetc");
		}
		status = FALSE;
	}
	else {
		if( ungetc(c, userfd) != c ) {
			perror("probably_eor");
			warn("Problem with ungetc");
		}
		status = TRUE;
	}
	return status;
}

/*************************************************************************
 *
 *	Function: user_read
 *
 *	Purpose: Return the next user in the database - name is key content
 *		 is 2 strings - check values, and reply values seperated
 *		 by a newline.
 *
 *************************************************************************/

int
user_read(userfd, name, content)
FILE	**userfd;
char	*name;
char	*content;
{
	char		buf[4 * 1024];
	char		*ptr;
	int		mode;
	int		submode;
	long		fpos;
	int		replyCount = 0;

	/*
	 * Open the user table
	 */
	if(*userfd == (FILE *)NULL) {
		sprintf(buf, "%s/%s", radius_dir, radius_users);
		if((*userfd = fopen(buf, "r")) == (FILE *)NULL) {
			fprintf(errf, "%s: Couldn't open %s for reading\n",
					progname, buf);
			exit(USERFILE_READ_ERR);
		}
		curParseLine = 0;
	}

	mode = FIND_MODE_NAME;
	submode = FIND_MODE_NAME;
	fpos = ftell(*userfd);
	while(fgets(buf, sizeof(buf), *userfd) != (char *)NULL) {
		curParseLine++;
		/* skip lines starting with '#' or newline
			name is the string until the first
				blank, tab, newline, or NUL
			if no name, skip to next record
			if name, but no password on same line,
				start over on next line 
			Alert the user about missing names or passwords,
			and commas at the end of the first record line
		*/

		if(mode == FIND_MODE_NAME) {
			/*
			 * Find the entry starting with the users name
			 */
			if( (*buf != '#') && (*buf != '\n') ) {
				char *origname = name;

				*name = '\0';	/* name found flag */
				ptr = buf;
				while( (*ptr != ' ') && (*ptr != '\t')
				    && (*ptr != '\n') && (*ptr != '\0') ) {
					*name++ = *ptr++;
				}
				if( *origname == '\0' ) {
					name = origname;
					if( submode == FIND_MODE_SKIP ) {
						continue;
					}
					while( *ptr == ' ' || *ptr == '\t') {
						ptr++;
					}
					if( *ptr == '\n' || *ptr == '\0') {
						warn("No name or attributes\n\t- skipping blank line");
					}
					else {
						warn("No name found\n\t- possible missing comma on a previous line\n\t... skipping to next record");
						submode = FIND_MODE_SKIP;
					}
					continue;	/* no name found */
				}
				if( (*ptr == '\n') || (*ptr == '\0') ) {
					name = origname;
					warn( "Missing mandatory password\n\tfor user '%s'", name);
					submode = FIND_MODE_SKIP;
					continue;	/* no pwd on line */
				}
				*name = '\0';	/* NUL-terminate name */
				ptr++;
				while(*ptr == ' ' || *ptr == '\t') {
					ptr++;
				}
				*content = 0;
				if( (*ptr == '\n') || (*ptr == '\0') ) {
					name = origname;
					warn( "Missing mandatory password\n\tfor user '%s'", name);
					submode = FIND_MODE_SKIP;
					continue;	/* no pwd on line */
				}
				submode = FIND_MODE_NAME;
				strcpy(content, ptr);
				content += strlen(content);

				/* if line ends with ",\n" then whine */
				if( content[-2] == ',' ) {
					warn("Trailing comma on name-pwd line");
				}
				mode = FIND_MODE_REPLY;
				replyCount = 0;	/* no reply attributes yet */
				if( test_eof(*userfd) ) {
					warn("Premature end of file");
					return 0;
				}
			}
		}
		else {	/* not first line of record */
			/* line must start with tab or blank
			   if it starts with a '#' skip it, but warn the
			     user since previously, flat file searches would
			     terminate at comment lines inside a record.
			   if there is a comma on the last line of the
			     record warn the user.
			   if there is no comma but it is not the last
			     line of the record then insert one
			*/
			replyCount++;
			if(*buf == ' ' || *buf == '\t') {
				ptr = buf;
				while(*ptr == ' ' || *ptr == '\t') {
					ptr++;
				}
				if( *ptr == '\n' || *ptr == '\0' ) {
					warn("No attributes on line\n\t- assuming end of record");
					return 0;
				}
				strcpy(content, ptr);
				content += strlen(content);
				content -= 2;
				while(*content == ' ' || *content == '\t' ) {
					content--;
				}
				content++;
				*content = '\0';

				/* check for end of record */
				if(*(content - 1) != ',') {
					/* double-check */
					if( probably_eor(*userfd) ) {
						return(0);
					}
					else {
						warn("Missing comma at eol");
						/* force a comma */
						*content++ = ',';
						*content = '\0';
					}
				}
				if( test_eof(*userfd) ) {
					warn("Premature end of file");
					return 0;
				}
			}
			else if( *buf == '#' ) {
				warn("Comment in record");
				if( test_eof(*userfd) ) {
					warn("Premature end of file");
					return 0;
				}
				else {
					continue;
				}
			}
			else if( *buf == '\n' ) {
				if( replyCount > 1 ) {
					warn("Trailing comma on last record");
				}
				return(0);
			}
			else {
				/* We are done */
				warn("No tab before attribute\n\t- assuming end of record and rescanning");
				fseek(*userfd, fpos, SEEK_SET);
				curParseLine--;
				replyCount--;
				return(0);
			}
		}
		fpos = ftell(*userfd);
	}
	fclose(*userfd);
	*userfd = (FILE *)NULL;
	return(END_OF_USERS_LIST);
}

void
#if __STDC__ == 1
warn (CONST char *fmt, ...)
#else
warn (va_alist) va_dcl
#endif
{
	va_list ap;
#if __STDC__ == 1
	va_start (ap, fmt);
#else
	CONST char *fmt;
	va_start (ap);
	fmt = va_arg(ap, char *);
#endif
	if( !warning ) {
		va_end (ap);
		return;
	}
	fprintf(errf, "%s: %s/%s: line %d: Warning: ",
		progname, radius_dir, radius_users, curParseLine);
	vfprintf(errf, fmt, ap);
	va_end(ap);
	fputc('\n', errf);
}
