/*
 * OpenFWTK authentication server
 * Copyright (c) 2003-2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 * (C) 2003 ADVA Research Center
 * (C) 2003 Alexei Kravchuk
 * (C) 2003 ArkanoiD, portability modifications
 */

#include	<stdio.h>
#include	<unistd.h>
#include	<stdlib.h>
#include	<syslog.h>
#include	<time.h>
#include 	<stdarg.h>
#include    	<errno.h>
#include	<string.h>
#include	<strings.h>
#include	<ctype.h>
#include	<limits.h>
#include	<sysexits.h>

#include	"firewall.h"
#include	"firewall2.h"
#include	"fwfunc.h"
#include	"auth.h"
#include	"authdb.h"

static	char*	moduleId ATTR_UNUSED = "$Id: authsrv.c,v 1.32 2007/09/22 19:51:00 arkenoi Exp $";


#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <libgen.h>
#include <signal.h>
#include <sys/types.h>

struct  sockaddr_un *su;
#define UNIX_PATH_MAX sizeof((su)->sun_path)

#define MAX_AGENT_NUM 500

#ifdef OpenBSD
#define HAVE_getpeereid 1
#else
#ifdef linux
#define HAVE_SO_PEERCRED
#endif
#endif

typedef struct {
        int  uid;
        char sock_path[UNIX_PATH_MAX];
        int  fd;
} authsrv_agent_rec;


int start_authsrv_set(authsrv_agent_rec *authsrv_agents[], fd_set *w_sock_fdset);
int start_authsrv(int fd, int uid);
int create_socket(authsrv_agent_rec *authsrv_agents);
int my_getpeereid(int fd);
int authsrv_main();

#if defined(linux) || defined (SOLARIS)
extern void	initsetproctitle(int,char**);
#endif

#include        "gp_list.h"

#ifndef MAXINT
#define MAXINT 65535
#endif

struct 	sockaddr_un *su;
#define UNIX_PATH_MAX sizeof((su)->sun_path)

#define CONS_AUTH  1
#define CONS_DENY -1
#define NO_CONS    0

Cfg * proxy_get_match(char *tag, const Group gp[], char* arg[]);

extern	Cfg		*proxy_confp;
extern	void		getprotolist(char*,int);

static	char		*permdenied = "Permission Denied.";

static	char		username[AUTH_USIZ+1];
static	char		usercomment[MAX_STR];
static	Auth		userrec;
static	AProto		*authproto;
extern	AProto		*codetoproto();
extern	AProto		*nametoproto();
static	Auth		userrecauth;
static	int		authenticated = 0;
static	int		badsleep = -1;	/* how long to sleep if bad */
static	int		interactive = 0;
static	int		noboguschallenge = 0;

static	int	bogusprompt();

static	int	f_quit();
static	int	f_authorize();
static	int	f_response();
static	int	f_display();
static	int	f_adduser();
static	int	f_deluser();
static	int	f_enauser();
static	int	f_passwd();
static	int	f_proto();
static	int	f_wiz();
static	int	f_group();
static	int	f_addgroup();
static	int	f_revoke();
static	int	f_help();
static	int	f_login();
static	int	f_superwiz();
static	int	f_rename();
static	int	f_list();
static  int	f_authoper();

typedef	struct {
	char	*cnam;
	char	*help;
	int	(*cfun)();
} Cmd;
static Cmd ctab[] = {
{ "authorize",	 	"authorize username [comment]",		f_authorize   },
{ "authenticate",	"authenticate username",		f_authorize   },
{ "response",		"response <text>",			f_response    },
{ "quit",		"quit",					f_quit        },
{ "exit",		"exit",					f_quit        },
{ "display",		"display username",			f_display     },
{ "adduser",		"adduser username [fullname]",		f_adduser     },
{ "deluser",		"deluser username",			f_deluser     },
{ "enable",		"enable username [onetime]",		f_enauser     },
{ "disable",		"disable username",			f_enauser     },
{ "login",		NULL,					f_login       },
{ "password",		"password [username] passwordtext",	f_passwd      },
{ "passwd",		"passwd [username] passwordtext",	f_passwd      },
{ "proto",		"proto username protoname",		f_proto       },
{ "group",		"group username groupname [{ groupname}]",f_group     },
{ "addgroup",	   "addgroup username groupname [{ groupname}]",f_addgroup    },
{ "revoke",		"revoke username groupname",		f_revoke      },
{ "rename",		"rename username newname [fullname]",	f_rename      },
{ "wiz",		"wiz username groupname [{ groupname}]",f_wiz         },
{ "unwiz",		"unwiz username groupname [{ groupname}]",f_wiz       },
{ "superwiz",		"superwiz username",			f_superwiz    },
{ "operation","operation group/user username command dest [tokens]",f_authoper},
{ "list",		"list [group]",				f_list	      },
{ "ls",			"ls [group]",				f_list	      },
{ "?",			"?",					f_help        },
{ "help",		"help",					f_help	      },
{ NULL,			NULL,					NULL	      }
};

static Cmd * find_command();

const Group unknown_gp[]	= {{"unknown", 0},	{"\0\0", 0}};
const Group unauth_gp[]		= {{"unauth", 0},	{"\0\0", 0}};

sa_family_t get_sock_type(int fd);
Cfg* authsrv_proxy_conf_hosts (Cfg *confp, char *rladdr, char *riaddr);
Cfg* authsrv_init(int);
void log_usrmsg            (char *msg, char *prm1, char *prm2_pat, char *prm2);
void showusage             (char *usage);
int check_auth_console(char *cons_sockname, char []);
int makeup_cons_sockname(char *sockdir, char *host, char *username, char *sockname);

static int
time_ok(char *upper,char *lower);

char *sockdir = AUTHSRV_SOCK_PATH;

char console_pwd[AUTH_PWSIZ+1] = "";



int main(int argc, char *argv[])
{
        Cfg	*authsrvd_cfp, *cf;
        char    *sock_name;
        int     i, maxfd;
        authsrv_agent_rec *authsrv_agents[MAX_AGENT_NUM+1];
        fd_set  r_sock_fdset, sock_fdset;

#ifndef LOG_DAEMON
        openlog("authsrv",LOG_PID);
#else
        openlog("authsrv",LOG_PID|LOG_NDELAY,LFAC);
#endif

#if defined(linux) || defined (SOLARIS)
	initsetproctitle(argc, argv);
#endif


	interactive = isatty(0);

	switch (argc) {
		case 2:	if (!strcmp(argv[1],"-daemon")) {
				interactive = 0;
				break;
			} else if (strcmp(argv[1],"-s")) {
       				fprintf(stderr,"usage: %s [-daemon]\n", basename(argv[0]));
				exit(EX_USAGE);
			} else 
				interactive = 1;
		case 1: if (interactive && (getuid() != 0)) {
				syslog(LLEV,"securityalert: non-root user started authsrv in interactive mode");
       				fprintf(stderr,"Only root is allowed to use administration mode\n");
				exit(1);
			} 
			authsrv_main();
		default:fprintf(stderr,"usage: %s [-daemon]\n", basename(argv[0]));
		exit(EX_USAGE);
	}

       	FD_ZERO(&sock_fdset);
 	if((authsrvd_cfp = cfg_read("authsrv")) == (Cfg *)-1) {
       		fprintf(stderr,"Error: cannot read configuration file\n");
       		syslog(LLEV,"fwtkcfgerr: no netperm-table records for authsrvd");
       		exit(1);
       	}

       	sock_name = proxy_conf_string(authsrvd_cfp, "listen-socket");
       	i=0; 
	for (cf = cfg_get("listen-socket", authsrvd_cfp); cf != (Cfg*) 0; cf = cfg_get("listen-socket", (Cfg*) 0)) { 
		if (i >= MAX_AGENT_NUM) {
       			fprintf(stderr,"Error: too many sockets to listen\n");
       			syslog(LLEV,"fwtkcfgerr: too many sockets to listen");
       			exit(1);
		}
               	authsrv_agents[i] = (authsrv_agent_rec *)malloc(sizeof(authsrv_agent_rec));
               	strlcpy(authsrv_agents[i]->sock_path, cf->argv[0], sizeof(authsrv_agents[i]->sock_path));
		if ((cf->argc < 1) || (cf->argv[0][0] == '-')) {
       			fprintf(stderr,"Configuration error\n");
	 		syslog(LLEV,"fwtkcfgerr: missing parameter, line %d",cf->ln);
			exit(1);
		}
                authsrv_agents[i]->uid = -1;
		if (cf->argc == 3) {
			if (!strcmp(cf->argv[1],"-user")) {
                		if ((authsrv_agents[i]->uid = mapuid(cf->argv[2])) == -1) {
       					fprintf(stderr,"Configuration error\n");
		 			syslog(LLEV,"fwtkcfgerr: invalid user id %.128s, line %d",cf->argv[2],cf->ln);
					exit(1);
				}
			} else {
       				fprintf(stderr,"Configuration error\n");
		 		syslog(LLEV,"fwtkcfgerr: invalid parameter %.128s, line %d",cf->argv[2],cf->ln);
				exit(1);
			}
		} else if (cf->argc != 1) {
       			fprintf(stderr,"Configuration error\n");
		 	syslog(LLEV,"fwtkcfgerr: syntax error, line %d",cf->ln);
			exit(1);
		} 

                authsrv_agents[i]->fd = create_socket(authsrv_agents[i]);

                FD_SET(authsrv_agents[i]->fd, &sock_fdset);
                maxfd = (authsrv_agents[i]->fd > maxfd) ? authsrv_agents[i]->fd : maxfd;
		i++;
        }

	switch(fork()) {
		case -1:
			syslog(LLEV,"fwtksyserr: fork failed");
       			fprintf(stderr,"Cannot fork!\n");
			exit(1);
		case 0:
			break;
		default:
			exit(0);
	}
			
	cfg_free(authsrvd_cfp);
        authsrv_agents[i] = NULL;

        r_sock_fdset = sock_fdset;

        /* The cycle is never-ending actually */
        while((i = select(maxfd + 1, &r_sock_fdset, NULL, NULL, NULL))) {
                start_authsrv_set(authsrv_agents, &r_sock_fdset);
                r_sock_fdset = sock_fdset;
        }

        /* Free all agent records before exiting */
        for(i = 0; authsrv_agents[i]; i++) {
            free(authsrv_agents[i]);
        }


        return 0;
}



int create_socket(authsrv_agent_rec *authsrv_agent)
{
        int sock;
        char buf[MAX_STR] = "";
	char *p,*p1;

        umask(0177);

        strtrm(authsrv_agent->sock_path);
	p = xstrdup(authsrv_agent->sock_path);
	if ((p1 = strchr(p, ':'))) {
		*(p1++) = '\0';
		if (strcasecmp(p, "unix") == 0 || strcasecmp(p, "local") == 0) {
        		struct sockaddr_un sn;
			memset(&sn,0,sizeof(sn));
        		sn.sun_family = AF_UNIX;
        		sock = socket(PF_UNIX, SOCK_STREAM, 0);
        		if (sock < 0) {
                		perror("socket");
                		syslog(LOG_ERR, "fwtksyserr: can't create UNIX socket: %s", strerror(errno));
                		exit(1);
        		}
        		strlcpy(sn.sun_path, p1, sizeof(sn.sun_path));
        		unlink(sn.sun_path);
        		if (bind(sock, (struct sockaddr *)&sn, sizeof(sn)) < 0) {
                		perror("bind");
                		syslog(LOG_ERR, "fwtksyserr: can't bind UNIX socket %.128s: %s",sn.sun_path,strerror(errno));
                		exit(1);
        		}
        		chown(sn.sun_path, authsrv_agent->uid, -1);
        		snprintf(buf, sizeof(buf), "(for uid: %d)", authsrv_agent->uid);
		} else if (strcasecmp(p, "inet") == 0) {
			struct sockaddr_in si;
			char *bind_addr;

			if (authsrv_agent->uid != -1) {
				syslog(LOG_ERR, "fwtkcfgerr: cannot determine remote user id for inet sockets");
				exit(1);
			}
			memset(&si,0,sizeof(si));
			si.sin_family = AF_INET;
			if ((bind_addr = strchr(p1,'@'))) {
				*(bind_addr++) = '\0';
				si.sin_addr.s_addr = inet_addr(bind_addr);
			}
			si.sin_port = htons(str_to_port(p1));
        		sock = socket(PF_INET, SOCK_STREAM, 0);
        		if (sock < 0) {
                		perror("socket");
                		syslog(LOG_ERR, "fwtksyserr: can't create INET socket: %s", strerror(errno));
                		exit(1);
        		}
        		if (bind(sock, (struct sockaddr *)&si, sizeof(si)) < 0) {
                		perror("bind");
                		syslog(LOG_ERR, "fwtksyserr: can't bind INET port %.128s: %s",p1,strerror(errno));
                		exit(1);
        		}
		} else {
                	syslog(LOG_ERR, "fwtkcfgerr: unsupported socket type %.128s",p);
                	exit(1);
		}
	} else {
               	syslog(LOG_ERR, "fwtkcfgerr: socket definition sytax error %.128s",p);
               	exit(1);
	}
	free(p);
        if (listen(sock, 1) < 0) {
                perror("listen");
                syslog(LOG_ERR, "fwtksyserr: error while listening to the socket: %s", strerror(errno));
                return -1;
        }

        syslog(LLEV, "listening at %s%s", authsrv_agent->sock_path, (authsrv_agent->uid != -1) ? buf : "");

        return sock;
}


int start_authsrv_set(authsrv_agent_rec *authsrv_agents[], fd_set *w_sock_fdset)
{
        int i, num_of_started = 0;

        for(i=0; authsrv_agents[i]; i++)  {
            if(FD_ISSET(authsrv_agents[i]->fd, w_sock_fdset)) {
                if(start_authsrv(authsrv_agents[i]->fd, authsrv_agents[i]->uid) < 0) {
                    syslog(LLEV, "fwtksyserr: failed to start authsrv on the %.128s socket", authsrv_agents[i]->sock_path);
                }
                num_of_started++;
            }
        }

        return num_of_started;
}

int start_authsrv(int fd, int uid)
{
  int ch_pid;
  int authsrv_fd;

  authsrv_fd = accept(fd, NULL, NULL);

  if( uid != -1 )  {
      if(my_getpeereid(authsrv_fd) != uid
#ifdef ALLOW_ROOT
                                            && my_getpeereid(authsrv_fd) != 0
#endif
         ) {
        syslog(LLEV, "securityalert: peer eid verification failed, can't start authsrv,");
        close(authsrv_fd);
        return -1;
    }
  }
  ch_pid = fork();
  if(ch_pid < 0) { /* error */
    perror("fork");
    syslog(LLEV, "fwtksyserr: fork(): %s", strerror(errno));
    exit(1);
  }

  if(ch_pid)  {  /* parent */
        signal(SIGCHLD,SIG_IGN);
        close(authsrv_fd);
  }
  else {  /* child */
	if (close(fd) || close(0) || close(1) || close(2)) {
    		syslog(LLEV, "fwtksyserr: close(): %s", strerror(errno));
    		exit(1);
	}
        if ((dup2(authsrv_fd, 0) == -1) || 
	    (dup2(authsrv_fd, 1) == -1) ||
            (dup2(authsrv_fd, 2) == -1)) {
    		syslog(LLEV, "fwtksyserr: dup2(): %s", strerror(errno));
    		exit(1);
	}
        if(close(authsrv_fd)) {
    		syslog(LLEV, "fwtksyserr: close(): %s", strerror(errno));
    		exit(1);
	}
        authsrv_main();
        exit(1);     /* reach here only if error occured */
  }

  return 1;
}

int my_getpeereid(int fd)
{
#ifdef HAVE_getpeereid

  uid_t euid;
  gid_t egid;

  if(getpeereid(fd, &euid, &egid) == -1)
    return -1;                         /* error */

  return euid;

#elif defined(HAVE_getpeeruid)

  uid_t uid;
  gid_t gid;

  if(getpeeruid(fd, &uid, &gid) == -1)
    return -1;                         /* error */

  return uid;

#elif defined(HAVE_SO_PEERCRED)

  struct ucred ucred;
  socklen_t optlen;

  optlen = sizeof(ucred);

  if(getsockopt(fd, SOL_SOCKET, SO_PEERCRED, (void *) &ucred, &optlen) == -1) {

    return -1;
  }

  return ucred.uid;

#endif

return -1;
}


int
authsrv_main()
{
	Cmd	*cp;
	char	buf[MAX_STR];
	char	*xa[MAX_ARG];
	char	xb[MAX_STR];
	int	xc;
	char	*p;
	int	promptlen;
	char	*prompt;

	sosetline(SO_LLF);
	username[0] = '\0';
	prompt = "authsrv-> ";
	promptlen = strlen(prompt);

	authsrv_init(interactive);

	/* if invoked by root from a tty go into admin mode */
	if (interactive) {
			authenticated = 1;
			strlcpy(username,"administrator",sizeof(username));
			userrecauth.gp[0].name[0] = '\0';
			userrecauth.gp[0].name[1] = '\0';
			userrecauth.bcnt = 0;
			userrecauth.flgs = 0 | AUTHFLG_WIZ;
			userrecauth.last = (long)0;
			userrecauth.lastpw = (long)0;
			userrecauth.atyp = 'p';
			userrecauth.pw[0] = '*';
			userrecauth.pw[1] = '\0';
			prompt = "authsrv# ";
			promptlen = strlen(prompt);
	}

	if(auth_dbconfig(proxy_confp)) {
		if(interactive)
			fprintf(stderr,"Database not configured");
		exit(1);
	}

	if(!interactive) {

	       if(get_sock_type(0) == AF_INET && authsrv_proxy_conf_hosts(proxy_confp,proxy_stats.rladdr,proxy_stats.riaddr) == NULL)
	       {
			 sosay(0,"Access denied");
			 exit(0);
	       }

	       snprintf(buf,sizeof(buf),"Authsrv ready. (%s)",FWTK_VERSION_MINOR);
	       sosay(0, buf);
	}

	proxy_setugid();


        badsleep = proxy_conf_int(proxy_confp, "badsleep", -1, MAXINT, -1);

	if(proxy_conf_string(proxy_confp, "nobogus"))
	  	noboguschallenge++;

	while(1) {
		if(interactive)
			write(1,prompt,promptlen);
		if(sogets(0,buf,sizeof(buf)) < 1)
			break;
		if((p = rindex(buf,'\n')) != (char *)0)
			*p = '\0';

		xc = enargv(buf,xa,sizeof(xa)/sizeof(char *),xb,sizeof(xb));
		if(xc < 0) {
			sosay(0, "Too many command parameters.");
			continue;
		}
		if(xc == 0)
			continue;

		cp = find_command(ctab, xa[0]);
		if (!cp) {
			char	xuf[MAX_STR];
			syslog(LLEV,"ambiguous command: %.200s",xa[0]);
			snprintf(xuf,sizeof(xuf),"Command \"%.200s\" is ambiguous.",xa[0]);
			sosay(0, xuf);
			continue;
		}
		if(cp->cnam == (char *)0) {
			char	xuf[MAX_STR];
			syslog(LLEV,"unrecognized command: %.200s",xa[0]);
			snprintf(xuf,sizeof(xuf),"Command \"%.200s\" unrecognized.",xa[0]);
			sosay(0, xuf);
			continue;
		}
		(*cp->cfun)(xc,xa,buf,cp->help);
	}
	if(interactive)
		write(1,"\nEOT\n",5);
	exit (0);
}


static Cmd *
find_command (tab, cmd)
Cmd * tab;
char *cmd;
{
	Cmd *cp, *match;
	int nl;

	nl = strlen(cmd);
	match = 0;
	for (cp = tab; cp->cnam != (char *)0; cp++) {
		if (!strcmp(cp->cnam, cmd))
			return cp;
		else if (!strncasecmp(cp->cnam, cmd, nl)) {
			if (match && cp->cfun != match->cfun)
				return 0; /* ambiguous */
			match = cp;
		}
	}
	return match ? match : cp;
}

static	int
f_quit(ac,av,orig, usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
	exit(0);
     	return 0;
}



/* Convert hh:mm to munutes since midnight */
static int
minutes(p)
char *p;
{
#define D(x) isdigit((unsigned char) x)
	if (!D(p[0]) || !D(p[1]) || p[2] != ':' ||
	    !D(p[3]) || !D(p[4]) || p[5] != '\0') {
		syslog(LLEV,"fwtkcfgerr: invalid time specification: %.10s",p);
		return -1;
	}
	return((p[0] - '0') * 600 + (p[1] - '0') * 60 +
	       (p[3] - '0') * 10 +  (p[4] - '0'));
}


/*
 * Return 0 if current time is between upper and lower (inclusive)
 */
static int
time_ok(upper,lower)
char	*upper;
char	*lower;
{
	int		up;
	int		low;
	int 		present;
	time_t		time_sec;

	time(&time_sec);
	present = minutes(ctime(&time_sec) + 11);
	up = minutes(upper);
	low = minutes(lower);
	if (present < 0 || up < 0 || low < 0)
		return 1;	/* invalid time */
	if (up < low)
		return (low <= present || present <= up) ? 0 : 1;
	else
		return (low <= present && present <= up) ? 0 : 1;
}

static	int
f_authorize(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
	Cfg		*cfp;
	char		buf[MAX_STR];
	char		proxy_name[MAX_STR];
	char		ciaddr[MAX_HOSTNAME];
	char		*p;
	char*		args[3] = {av[1], proxy_name, ciaddr};

	char cons_sockname[UNIX_PATH_MAX];

	int cons_resp;
	
	username[0] = '\0';
	authenticated = 0;
        console_pwd[0] = '\0';  /* Clean up console-generated session pwd for the new auth context */

	if(ac < 2) {
		showusage(usage);
		return(1);
	}
	usercomment[0] = '\0';
	if(ac > 2) {
		strlcpy(usercomment,av[2],sizeof(usercomment));

		if ((p = rindex(usercomment,'/')) != (char *)0)
			strlcpy(ciaddr,p+1,sizeof(ciaddr));
		else
			strlcpy(ciaddr,"unknown",sizeof(ciaddr));

		strlcpy(proxy_name,usercomment,sizeof(proxy_name));

		if ((p = index(proxy_name,' ')) != (char *)0)
			*p= '\0';
	} else {
		strlcpy(proxy_name,"unknown",sizeof(proxy_name));
		strlcpy(ciaddr,"unknown",sizeof(ciaddr));
	}

	strlcpy(username,av[1],sizeof(username));

	/* if there is no record for the guy, send a dummy challenge */
	if(auth_dbgetu(username,&userrec)) {  
		log_usrmsg("BADAUTH %.100s",username," (%.512s)", usercomment);
		username[0] = '\0';
		if(noboguschallenge)
			return(sosay(0, "Userid nonexistent"));
		return(bogusprompt());
	}

        console_pwd[0] = '\0';
	cons_resp = NO_CONS;

	if(makeup_cons_sockname(sockdir, ciaddr, username, cons_sockname) &&
	   (cons_resp = check_auth_console(cons_sockname, orig)) == CONS_AUTH) {
		authenticated = 1;
		if(auth_dbgetu_forupdate(username,&userrec)) {
/*
 *			User mysteriously vanished while we checked the console!
 */
			log_usrmsg("BADAUTH %.100s",username," (%.512s)", usercomment);
			username[0] = '\0';
			if(noboguschallenge)
				return(sosay(0, "Userid nonexistent"));
			return(bogusprompt());
		}
		bcopy(&userrec,&userrecauth,sizeof(Auth));

		/* update last login */
		time((time_t*) &(userrecauth.last));
		if(strlen(console_pwd) > 0) {
			authenticated = 0;   /* Not authenticated yet. Should enter console-generated session pwd before */
			sosay(0, "password");
		}
		else {
			(void)auth_dbputu(username,&userrecauth);
			log_usrmsg("AUTHENTICATE %.100s",username," (%.512s)",usercomment);
			sosay(0, "ok");
		}
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	else if(cons_resp == CONS_DENY)
	{
		log_usrmsg("BADAUTH %.100s authentication denied via auth console",username, " (%.512s)", usercomment);
		username[0] = '\0';
		auth_dbclose();      /* Unlock and close DB */
		return(sosay(0, "Authentication denied via auth console"));
	}
	auth_dbclose();      /* Unlock and close DB */

	cfp = proxy_get_match("authentication", userrec.gp, args);

	if((cfp == (Cfg *)0) || (cfp->flags & PERM_DENY)) {
		log_usrmsg("BADAUTH %.100s authentication denied",username, " (%.512s)", usercomment);
		username[0] = '\0';

		if(noboguschallenge)
			return(sosay(0, "Authentication denied"));
		return(bogusprompt());
	}

	/* if the login is sleeping from bad logins */
	if(userrec.flgs & AUTHFLG_BSLP) {
		time_t	now;

		time(&now);
		if(badsleep == -1 || now - userrec.last < badsleep) {
			log_usrmsg("BADAUTH %.100s too many tries",username, " (%.512s)",usercomment);
			username[0] = '\0';
			if(noboguschallenge && badsleep != -1)
				return(sosay(0, "Userid temporarily disabled"));
			if(noboguschallenge)
				return(sosay(0, "Userid disabled"));
			return(bogusprompt());
		}
		else
			userrec.flgs &= ~AUTHFLG_BSLP;
	}

	/* if the login is disabled */
	if(userrec.flgs & AUTHFLG_DIS) {
		log_usrmsg("BADAUTH %.100s is disabled",username," (%.512s)",usercomment);
		username[0] = '\0';
		if(noboguschallenge)
			return(sosay(0, "Userid disabled"));
		return(bogusprompt());
	}

	/* figure out what authentication protocol this user uses */
	if((authproto = codetoproto(userrec.atyp)) == (AProto *)0) {
		syslog(LLEV,"fwtkcfgerr: %.100s: bad auth protocol %c",username,userrec.atyp);
		authenticated = 0;
		if(noboguschallenge)
			return(sosay(0, "Userid authentication protocol invalid"));
		return(bogusprompt());
	}

	/*
	if there is no challenge generator for this protocol
	just ask for a password.
	*/
	if(authproto->challf == 0) {
		sosay(0, "password");
		return(0);
	}

	/* ok, there IS a challenge generator - call it */
	strlcpy(buf,"challenge ",sizeof(buf));
	if((*authproto->challf)(username,&buf[10],sizeof(buf) - 10) != 0) {
		username[0] = '\0';
		if(noboguschallenge)
			return(sosay(0, &buf[10]));
		return(bogusprompt());
	}
	sosay(0, buf);
	return(0);
}

static int
f_authoper(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
	Cfg	*cfp;
	int	counter;
	const Group* gp;

	if(ac < 5 || (strcmp(av[1], "user") != 0 && strcmp(av[1], "group") != 0)){
		showusage(usage);
		return(1);
	}
	if(strcmp(av[2], "unauth") == 0) {
		gp = unauth_gp;
	} else	if (auth_dbgetu(av[2], &userrec)) {
		cfp = cfg_get("unknown", proxy_confp);
		if ((cfp == (Cfg *)0 || (cfp->flags & PERM_DENY)) ) {
			syslog(LLEV,"fwtkcfgerr: authsrv asked to verify user %.100s who does not exist?", av[2]);
			sosay(0, "User not found in auth DB");
			return (0);
		}
		/* Make sure the unknown user is specified. */
		for(counter = 0; counter < cfp->argc; counter++){
			if( nacasematch(cfp->argv[counter], av[2]))
				break;
		}
		if( counter == cfp->argc) {
			sosay(0, permdenied);
			return (0);
		}
		gp = unknown_gp;
	} else
		gp = userrec.gp;

	cfp = proxy_get_match("operation", gp, &av[2]);

	if(cfp == (Cfg *)0) {
		sosay(0, "No match in netperm-table");
		return(0);
	}

	if((cfp->flags & PERM_DENY) == 0) {
		sosay(0, "ok ");
		return (0);
	}

	sosay(0, permdenied);
	return(0);
}

static	int
f_help(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
	Cmd	*cp;

	sosay(0, "Command List:");
	sosay(0, "(Commands may be invoked with unique leading abbreviation)");
	for(cp = ctab; cp->cnam != (char *)0; cp++)

		if(cp->help != (char *)0)
			sosay(0, cp->help);
	return(0);
}

static	int
f_login(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
	sosay(0, "Login not supported directly in authsrv.");
	sosay(0, "Use 'authorize' and 'response'");
	return(0);
}


static	int
f_response(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
	char	rbuf[MAX_STR];
	int	authresponse;

	/* this guy is skanking us */
	if(authenticated || username[0] == '\0') {
		username[0] = '\0';
		authenticated = 0;
		sosay(0, permdenied);
		return(0);
	}
	if(ac != 2)
		av[1] = "";

        /* Re-read a user record and lock DB */
	if(auth_dbgetu_forupdate(username,&userrec)) {  /* DB remains locked to avoid a race condition */
		log_usrmsg("BADAUTH %.100s",username," (%.512s)", usercomment);
		username[0] = '\0';
		return(sosay(0, "Userid nonexistent"));
	}

	if(strlen(console_pwd) > 0) {
	        if((authproto = codetoproto('p')) == (AProto *)0) {
		      syslog(LLEV,"fwtkcfgerr: %.100s: can't set auth protocol 'p'",username);
		      authenticated = 0;
		      auth_dbclose();      /* Unlock and close DB */
		      return(sosay(0, "Can't set 'password' auth protocol"));
	      }
        }
	/* figure out what authentication protocol this user uses */
	else if(authproto != codetoproto(userrec.atyp)) {
		syslog(LLEV,"fwtkcfgerr: %.100s: auth protocol %c differs with the one used for auth request %c",username,userrec.atyp, authproto->typ);
		authenticated = 0;
		auth_dbclose();      /* Unlock and close DB */
		return(sosay(0, "Userid authentication protocol has been changed since auth was requested. Please re-request auth."));
	}


	if(authproto != (AProto *)0 && authproto->verf == 0) {
		strlcpy(rbuf,"ok",sizeof(rbuf));
		goto	ok_login;
	}

	if(authproto == (AProto *)0) {
		authresponse = 1;
	} else {
		authresponse = (*authproto->verf)(username,av[1],&userrec,rbuf);
	}
	if (authresponse == 2) {
		sosay(0, rbuf);
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	if (authresponse) {
		log_usrmsg("BADAUTH %.100s",username," (%.512s)",usercomment);
		/* the guy bombed out. :( */
		userrec.bcnt++;
		time((time_t*)&(userrec.last));

		/* set the guy to have a login delay */
		if((userrec.bcnt >= AUTH_MAXBAD) &&

			((userrec.flgs & (AUTHFLG_WIZ | AUTHFLG_GWIZ)) == 0)) {
				userrec.flgs |= AUTHFLG_BSLP;
			log_usrmsg("securityalert: repeated bad auth attempts %.100s",username," (%.512s)",usercomment);
		}
		(void)auth_dbputu(username,&userrec);
		username[0] = '\0';
		authenticated = 0;
		if(authproto == (AProto *)0)
			sosay(0, permdenied);
		else
			sosay(0, rbuf);
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}

ok_login:
	authenticated = 1;
	bcopy(&userrec,&userrecauth,sizeof(Auth));

	/* update last login */
	time((time_t*)&(userrecauth.last));
	userrecauth.bcnt = 0;
	userrecauth.flgs &= ~AUTHFLG_BSLP;
	if(userrecauth.flgs & AUTHFLG_ONETIME) {
		syslog(LLEV,"ONE-TIME %.100s will be disabled",username);
		userrecauth.flgs |= AUTHFLG_DIS;
	}
	(void)auth_dbputu(username,&userrecauth);
	auth_dbclose();      /* Unlock and close DB */
	log_usrmsg("AUTHENTICATE %.100s",username," (%.512s)",usercomment);
	sosay(0, rbuf);
	return(0);
}


static	int
f_adduser(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
        int gp_num, usr_gp_num;
	if(!authenticated) {
		sosay(0, permdenied);
		return(0);
	}


	if((userrecauth.flgs & AUTHFLG_WIZ) == 0 && (userrecauth.flgs & AUTHFLG_GWIZ) == 0) {
		sosay(0, permdenied);
		return(0);
	}
	if(ac < 2) {
		showusage(usage);
		return(0);
	}
	if(auth_dbgetu_forupdate(av[1],&userrec) == 0) { /* DB remains locked to avoid a race condition */
		sosay(0, "User already exists.");
		return(0);
	}

	userrec.ln[0] = '\0';
	if(ac == 3) 
		strlcpy(userrec.ln,av[2],AUTH_LNSIZ);
	userrec.pw[0] = '*';
	userrec.pw[1] = '\0';

	/* inherit authentication method from creator */
	userrec.atyp = userrecauth.atyp;

	userrec.bcnt = 0;
	userrec.flgs = 0 | AUTHFLG_DIS;
	userrec.last = (long)0;

	userrec.authproto_rec.active =   0 ;   /* Initially disable authentication until it has been initialized */
	memset(userrec.authproto_rec.buf, 0, AUTH_PROTO_BUF_LEN);

        for(gp_num=0; gp_num < AUTH_GNUM; gp_num++)
	{
	  userrec.gp[gp_num].name[0] = '\0'; userrec.gp[gp_num].name[1] = '\0';
	  userrec.gp[gp_num].flgs = 0;
	}

	/* if created by a group wizard, become a member of the group */

	for(usr_gp_num=0, gp_num=0; usr_gp_num < AUTH_GNUM; usr_gp_num++)
	{
	  if(end_gp_list(userrecauth.gp,usr_gp_num))
	    break;
	  if(userrecauth.gp[usr_gp_num].flgs & AUTHFLG_GWIZ) {
	    userrec.gp[gp_num].flgs = 0;
	    strlcpy(userrec.gp[gp_num++].name, userrecauth.gp[usr_gp_num].name, AUTH_GSIZ);
	  }
	}

	if(auth_dbputu(av[1],&userrec)) {
		sosay(0, "database error");
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	syslog(LLEV,"%.100s ADDED USER %.100s",username,av[1]);
	sosay(0, "ok - user added initially disabled");
	auth_dbclose();      /* Unlock and close DB */
	return(0);
}


static	int
f_rename(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
	if(!authenticated) {
		sosay(0, permdenied);
		return(0);
	}

	if((userrecauth.flgs & AUTHFLG_WIZ) == 0 && (userrecauth.flgs & AUTHFLG_GWIZ) == 0) {
		sosay(0, permdenied);
		return(0);
	}

	if(ac < 3) {
		showusage(usage);
		return(0);
	}
	if(auth_dbgetu(av[2],&userrec) == 0) {
		char	xuf[MAX_STR];

		snprintf(xuf,sizeof(xuf),"User %.100s already exists.",av[2]);
		sosay(0, xuf);
		return(0);
	}
	if(auth_dbgetu_forupdate(av[1],&userrec) != 0) { /* DB remains locked to avoid a race condition */
		char	xuf[MAX_STR];

		snprintf(xuf,sizeof(xuf),"User %.100s does not exist.",av[1]);
		sosay(0, xuf);
		return(0);
	}

	/* you can only do it if you're in that group or a wizard */


	if((userrecauth.flgs & AUTHFLG_WIZ) == 0 &&
	   ((userrecauth.flgs & AUTHFLG_GWIZ) == 0 || !check_gwiz(userrecauth.gp,userrec.gp))) {
		sosay(0, permdenied);
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}


	if(ac >= 4) 
		strlcpy(userrec.ln,av[3],AUTH_LNSIZ);

	if(auth_dbopen() || auth_dbputu(av[2],&userrec) ||
	   auth_dbdelu(av[1]) || auth_dbclose()) {
		sosay(0, "database error");
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	syslog(LLEV,"%.100s RENAMED USER %.100s to %.100s",username,av[1],av[2]);
	sosay(0, "ok - user renamed");
	auth_dbclose();      /* Unlock and close DB */
	return(0);
}


static	int
f_deluser(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
	if(!authenticated) {
		sosay(0, permdenied);
		return(0);
	}


	if((userrecauth.flgs & AUTHFLG_WIZ) == 0 && (userrecauth.flgs & AUTHFLG_GWIZ) == 0) {
		sosay(0, permdenied);
		return(0);
	}
	if(ac != 2) {
		showusage(usage);
		return(0);
	}
	if(auth_dbgetu_forupdate(av[1],&userrec)) { /* DB remains locked to avoid a race condition */
		auth_dbclose();      /* Unlock and close DB */
		sosay(0, "Unknown user");
		return(0);
	}

	/* if a group wizard, can delete a member of the group */

	if((userrecauth.flgs & AUTHFLG_WIZ) == 0 &&
	   ((userrecauth.flgs & AUTHFLG_GWIZ) == 0 || !check_gwiz(userrecauth.gp,userrec.gp))) {
		sosay(0, permdenied);
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}


	if(auth_dbdelu(av[1])) {
		sosay(0, "database error");
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	syslog(LLEV,"%.100s DELETED USER %.100s",username,av[1]);
	sosay(0, "deleted");
	auth_dbclose();      /* Unlock and close DB */
	return(0);
}


static	int
f_enauser(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
	int	onetime = 0;

	if(!authenticated) {
		sosay(0, permdenied);
		return(0);
	}


	if((userrecauth.flgs & AUTHFLG_WIZ) == 0 && (userrecauth.flgs & AUTHFLG_GWIZ) == 0) {
		sosay(0, permdenied);
		return(0);
	}

	if(ac < 2) {
		showusage(usage);
		return(0);
	}

	if(ac == 3 && !strncmp(av[2],"onetime",strlen(av[2])))
		onetime = 1;
	else if(ac > 2) {
		sosay(0, "Unrecognized command parameter");
		return(0);
	}

	if(auth_dbgetu_forupdate(av[1],&userrec)) { /* DB remains locked to avoid a race condition */
		sosay(0, "Unknown user");
		return(0);
	}

	/* if a group wizard, can enable/disable a member of the group */


	if((userrecauth.flgs & AUTHFLG_WIZ) == 0 &&
	   ((userrecauth.flgs & AUTHFLG_GWIZ) == 0 || !check_gwiz(userrecauth.gp,userrec.gp))) {
		sosay(0, permdenied);
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}


	if(av[0][0] == 'e' || av[0][0] == 'E') {
		userrec.flgs &= ~(AUTHFLG_DIS | AUTHFLG_BSLP | AUTHFLG_ONETIME);
		if(onetime)
			userrec.flgs |= AUTHFLG_ONETIME;
		userrec.bcnt = 0;
		if(auth_dbputu(av[1],&userrec)) {
			sosay(0, "database error");
			auth_dbclose();      /* Unlock and close DB */
			return(0);
		}
		syslog(LLEV,"%.100s ENABLED USER %.100s",username,av[1]);
		sosay(0, "enabled");
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	userrec.flgs |= AUTHFLG_DIS;
	userrec.bcnt = 0;
	if(auth_dbputu(av[1],&userrec)) {
		sosay(0, "database error");
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	syslog(LLEV,"%.100s DISABLED USER %.100s",username,av[1]);
	sosay(0, "disabled");
	auth_dbclose();      /* Unlock and close DB */
	return(0);
}


static	int
f_wiz(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
	int gwiz_gp_num, gp_num;
	char gp_buf[AUTH_GSIZ*AUTH_GNUM + 1];
        char cbuf[MAX_STR];

	if(!authenticated) {
		sosay(0, permdenied);
		return(0);
	}
	if((userrecauth.flgs & AUTHFLG_WIZ) == 0) {
		sosay(0, permdenied);
		return(0);
	}

	if(ac < 3) {
		showusage(usage);
		return(0);
	}

	if(auth_dbgetu_forupdate(av[1],&userrec)) { /* DB remains locked to avoid a race condition */
		sosay(0, "Unknown user");
		return(0);
	}

	for(gwiz_gp_num=2; gwiz_gp_num < ac; gwiz_gp_num++) {
		if((gp_num = strcmplist(av[gwiz_gp_num], userrec.gp)) == -1) {
			snprintf(cbuf, sizeof(cbuf), "User %.100s is not a member of the %.100s group", av[1], av[gwiz_gp_num]);
			syslog(LLEV, "wiz: User %.100s is not a member of the %.100s group", av[1], av[gwiz_gp_num]);
			sosay(0, cbuf);
			auth_dbclose();      /* Unlock and close DB */
			return 0;
		}
		if (strcmp(av[gwiz_gp_num],userrec.gp[gp_num].name))
			continue;
		if(av[0][0] == 'u' || av[0][0] == 'U')
			userrec.gp[gp_num].flgs &= ~AUTHFLG_GWIZ;
		else {
			userrec.gp[gp_num].flgs |= AUTHFLG_GWIZ;
			userrec.flgs |= AUTHFLG_GWIZ;
		}
		strlcat(gp_buf, av[gwiz_gp_num], sizeof(gp_buf));
		strlcat(gp_buf, " ", sizeof(gp_buf));
	}

        if(av[0][0] == 'u' || av[0][0] == 'U') 	{
		if(!check_gwiz(userrec.gp, userrec.gp))
			userrec.flgs &= ~AUTHFLG_GWIZ;

		if(auth_dbputu(av[1],&userrec)) {
			sosay(0, "database error");
			auth_dbclose();      /* Unlock and close DB */
			return(0);
		}
		syslog(LLEV,"%.100s UN-GWIZ USER %s for GROUPS %s",username,av[1], gp_buf);
		sosay(0, "unset group-wizard");
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}

	if(auth_dbputu(av[1],&userrec)) {
		sosay(0, "database error");
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	syslog(LLEV,"%.100s GWIZ USER %.100s for GROUPS %s",username,av[1], gp_buf);
	sosay(0, "set group-wizard");
	auth_dbclose();      /* Unlock and close DB */
	return(0);
}


static	int
f_superwiz(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
	if(!authenticated) {
		sosay(0, permdenied);
		return(0);
	}
	if((userrecauth.flgs & AUTHFLG_WIZ) == 0) {
		sosay(0, permdenied);
		return(0);
	}
	if(!interactive || !isatty(0)) {
		sosay(0, "Must be logged in as root on terminal.");
		return(0);
	}
	if(ac != 2) {
		showusage(usage);
		return(0);
	}

	if(auth_dbgetu_forupdate(av[1],&userrec)) { /* DB remains locked to avoid a race condition */
		sosay(0, "Unknown user");
		return(0);
	}

	userrec.flgs |= AUTHFLG_WIZ;
	if(auth_dbputu(av[1],&userrec)) {
		sosay(0, "database error");
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	syslog(LLEV,"%.100s WIZ USER %.100s",username,av[1]);
	sosay(0, "set wizard");
	auth_dbclose();      /* Unlock and close DB */
	return(0);
}


static	int
f_group(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
        int gp_num, arg_gp_num;

	char gp_buf[(AUTH_GSIZ+2) * (AUTH_GNUM + 1)] = "";
        char cbuf[MAX_STR];

	if(!authenticated) {
		sosay(0, permdenied);
		return(0);
	}
	if((userrecauth.flgs & AUTHFLG_WIZ) == 0) {
		sosay(0, permdenied);
		return(0);
	}

	if(ac < 3) {
		showusage(usage);
		return(0);
	}

	if(auth_dbgetu_forupdate(av[1],&userrec)) { /* DB remains locked to avoid a race condition */
		sosay(0, "Unknown user");
		return(0);
	}


	for(gp_num=0; gp_num < AUTH_GNUM; gp_num++)
	{
	  userrec.gp[gp_num].name[0] = '\0';	userrec.gp[gp_num].name[1] = '\0';   /* wipe out */
	  userrec.gp[gp_num].flgs = 0;
	}
	userrec.flgs &= ~AUTHFLG_GWIZ;

	gp_num = ac - 2;

	if(gp_num >= AUTH_GNUM) {
		snprintf(cbuf, sizeof(cbuf), "Max number of groups reached for user %s. Can't assign group %s", av[1], av[AUTH_GNUM + 2]);
		syslog(LLEV, cbuf);
		sosay(0, cbuf);
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	for(arg_gp_num = 2, gp_num = 0; arg_gp_num < ac; arg_gp_num++, gp_num++) {
		strlcpy(userrec.gp[gp_num].name,av[arg_gp_num],AUTH_GSIZ);
		userrec.gp[gp_num].flgs = 0;

		strlcat(gp_buf, av[arg_gp_num], sizeof(gp_buf));
		strlcat(gp_buf, " ", sizeof(gp_buf));
	}
	if(auth_dbputu(av[1],&userrec)) {
		sosay(0, "database error");
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	syslog(LLEV,"%.100s GROUP USER %.100s TO %.100s",username,av[1],gp_buf);
	sosay(0, "set group");
	auth_dbclose();      /* Unlock and close DB */
	return(0);
}



static	int
f_addgroup(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
        int gp_num, arg_gp_num;

	char gp_buf[(AUTH_GSIZ+2) * (AUTH_GNUM + 1)] = "";
        char cbuf[MAX_STR];

	if(!authenticated) {
		sosay(0, permdenied);
		return(0);
	}
	if((userrecauth.flgs & AUTHFLG_WIZ) == 0) {
		sosay(0, permdenied);
		return(0);
	}

	if(ac < 3) {
		showusage(usage);
		return(0);
	}

	if(auth_dbgetu_forupdate(av[1],&userrec)) { /* DB remains locked to avoid a race condition */
		sosay(0, "Unknown user");
		return(0);
	}


	gp_buf[0] = '\0';

        for(gp_num=0; gp_num < AUTH_GNUM; gp_num++) {
	    	if(userrec.gp[gp_num].name[0] == '\0')
	      		break;
		strlcat(gp_buf, userrec.gp[gp_num].name, sizeof(gp_buf));
		strlcat(gp_buf, " ", sizeof(gp_buf));
	}

	for(arg_gp_num = 2; arg_gp_num < ac; arg_gp_num++)
	{

          if(strcmplist(av[arg_gp_num], userrec.gp) != -1)
	    {
	      snprintf(cbuf, sizeof(cbuf), "User %s is already a member of the group %s", av[1], av[arg_gp_num]);
	      syslog(LLEV, cbuf);
	      sosay(0, cbuf);
	      return(0);
	    }
	}


	for(arg_gp_num = 2; arg_gp_num < ac; arg_gp_num++) {
		if(gp_num >= AUTH_GNUM) {
			snprintf(cbuf, sizeof(cbuf), "Max number of groups reached for user %s. Can't assign group %s", av[1], av[arg_gp_num]);
			syslog(LLEV, cbuf);
			sosay(0, cbuf);
			auth_dbclose();      /* Unlock and close DB */
			return(0);
		}
		strlcpy(userrec.gp[gp_num].name,av[arg_gp_num],AUTH_GSIZ);
		userrec.gp[gp_num].flgs = 0;
		strlcat(gp_buf, av[arg_gp_num], sizeof(gp_buf));
		strlcat(gp_buf, " ", sizeof(gp_buf));
		gp_num++;
	}
	if(auth_dbputu(av[1],&userrec)) {
		sosay(0, "database error");
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	syslog(LLEV,"%.100s GROUP USER %.100s TO %.100s",username,av[1],gp_buf);
	sosay(0, "set group");
	auth_dbclose();      /* Unlock and close DB */
	return(0);
}



static	int
f_revoke(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
        int gp_num, gp_numout, revoked_gnum;

	char gp_buf[(AUTH_GSIZ+2) * (AUTH_GNUM + 1)] = "";
        char cbuf[MAX_STR];

	if(!authenticated) {
		sosay(0, permdenied);
		return(0);
	}
	if((userrecauth.flgs & AUTHFLG_WIZ) == 0) {
		sosay(0, permdenied);
		return(0);
	}

	if(ac != 3) {
		showusage(usage);
		return(0);
	}

	if(auth_dbgetu_forupdate(av[1],&userrec)) { /* DB remains locked to avoid a race condition */
		sosay(0, "Unknown user");
		return(0);
	}

        if((revoked_gnum = strcmplist(av[2], userrec.gp)) == -1) {
		snprintf(cbuf, sizeof(cbuf), "User %s is not a member of the group %s", av[1], av[2]);
	      	syslog(LLEV, cbuf);
	      	sosay(0, cbuf);
		return(0);
	}

	gp_numout = 0;
	gp_buf[0] = '\0';

        for(gp_num=0; gp_num < AUTH_GNUM; gp_num++) {
	    	if(userrec.gp[gp_num].name[0] == '\0')
	      		break;
		if(gp_num == revoked_gnum)
			continue;
	    	strlcat(gp_buf, userrec.gp[gp_num].name , AUTH_GSIZ);
		strlcat(gp_buf, " ", sizeof(gp_buf));
		if (gp_numout != gp_num) 
			memcpy(&userrec.gp[gp_numout], &userrec.gp[gp_num], sizeof(Group));
		gp_numout++;
	}

	if(gp_num >= AUTH_GNUM) {
		snprintf(cbuf, sizeof(cbuf), "Max number of groups reached for user %s - possbile database migration error.", av[1]);
		syslog(LLEV,"fwtksyserr: database group count too high");
		sosay(0, cbuf);
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
 	userrec.gp[gp_numout].name[0] = '\0';	
	
	if(auth_dbputu(av[1],&userrec)) {
		sosay(0, "database error");
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	syslog(LLEV,"%.100s GROUP USER %.100s TO %.100s",username,av[1],gp_buf);
	sosay(0, "set group");
	auth_dbclose();      /* Unlock and close DB */
	return(0);
}


static	int
f_passwd(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
	char	*usernam;
	char	*password;
	AProto	*aprp;
	char	rbuf[MAX_STR];

	if(!authenticated) {
		sosay(0, permdenied);
		return(0);
	}

	if(ac < 2) {
		showusage(usage);
		return(0);
	}

	usernam = username;
	if(ac > 2) {
		if((userrecauth.flgs & AUTHFLG_WIZ) == 0 && (userrecauth.flgs & AUTHFLG_GWIZ) == 0) {
			sosay(0, permdenied);
			return(0);
		}
		usernam = av[1];
		password = av[2];
	} else {
		password = av[1];
	}

	if(auth_dbgetu_forupdate(usernam,&userrec)) { /* DB remains locked to avoid a race condition */
		sosay(0, "Unknown user");
		return(0);
	}

	if(ac > 2) {
	    if((userrecauth.flgs & AUTHFLG_WIZ) == 0 &&
	       ((userrecauth.flgs & AUTHFLG_GWIZ) == 0 || !check_gwiz(userrecauth.gp,userrec.gp))) {
			    sosay(0, permdenied);
			    auth_dbclose();      /* Unlock and close DB */
			    return(0);
	    }
	}

	if((aprp = codetoproto(userrec.atyp)) == (AProto *)0) {
		syslog(LLEV,"fwtkcfgerr: %.100s: bad auth protocol %c",usernam,userrec.atyp);
		sosay(0, "Bad authentication protocol for user - abort");
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}

	if(aprp->setf == 0) {
		sosay(0, "Password is not settable for this protocol");
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}

	if(strlen(password) >= AUTH_PWSIZ - 1) {
		sosay(0, "Password is too long");
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}

	/* ok, there IS a password setter - call it */
	if((*aprp->setf)(usernam,password,&userrec,rbuf)) {
		sosay(0, rbuf);
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	syslog(LLEV,"%.100s CHANGE PASSWORD %.100s",username,usernam);
	sosay(0, rbuf);
	time((time_t*)&(userrec.lastpw));
	auth_dbclose();      /* Unlock and close DB */
	return(0);
}




static	int
f_proto(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
	AProto	*aprp;

	if(!authenticated) {
		sosay(0, permdenied);
		return(0);
	}

	if((userrecauth.flgs & AUTHFLG_WIZ) == 0 && (userrecauth.flgs & AUTHFLG_GWIZ) == 0) {
		sosay(0, permdenied);
		return(0);
	}

	if(ac != 3) {
		showusage(usage);
		return(0);
	}

	if(auth_dbgetu_forupdate(av[1],&userrec)) { /* DB remains locked to avoid a race condition */
		sosay(0, "Unknown user");
		return(0);
	}


	if((userrecauth.flgs & AUTHFLG_WIZ) == 0 &&
	   ((userrecauth.flgs & AUTHFLG_GWIZ) == 0 || !check_gwiz(userrecauth.gp,userrec.gp))) {
		sosay(0, permdenied);
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}


	if((aprp = nametoproto(av[2])) == (AProto *)0) {
		char	puf[1024];
		char	ruf[2048];

		getprotolist(puf,sizeof(puf));
		syslog(LLEV,"fwtkcfgerr: %.100s: bad auth protocol %.100s",av[1],av[2]);
		snprintf(ruf,sizeof(ruf),"Unknown protocol \"%.100s\", use one of: %s",av[2],puf);
		sosay(0, ruf);
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	if(userrec.atyp != aprp->typ) {
		userrec.atyp = aprp->typ;
		userrec.authproto_rec.active = 0;
	}



	if(auth_dbputu(av[1],&userrec)) {
		sosay(0, "database error");
		auth_dbclose();      /* Unlock and close DB */
		return(0);
	}
	syslog(LLEV,"%.100s CHANGE PROTOCOL %.100s",username,av[1]);
	sosay(0, "changed");
	auth_dbclose();      /* Unlock and close DB */
	return(0);
}


/* client expects this command to be multiline. kludgy. */
static	int
f_display(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
	AProto	*ap;
	char	buf[1024];

	int gp_num;

	if(!authenticated) {
		sosay(0, permdenied);
		if(!interactive)
			sosay(0, ".");
		return(0);
	}

	if(ac != 2) {
		showusage(usage);
		if(!interactive)
			sosay(0, ".");
		return(0);
	}

	if(auth_dbgetu(av[1],&userrec)) {
		sosay(0, "Unknown user");
		if(!interactive)
			sosay(0, ".");
		return(0);
	}


	if((userrecauth.flgs & AUTHFLG_WIZ) == 0 &&
	   ((userrecauth.flgs & AUTHFLG_GWIZ) == 0 || !check_gwiz(userrecauth.gp,userrec.gp))) {
			sosay(0, permdenied);
			if(!interactive)
				sosay(0, ".");
			return(0);
	}

	/* print user and group */
	if(!end_gp_list(userrec.gp,0))
	{
          snprintf(buf,sizeof(buf),"Report for user %s\nGroups",av[1]);
	  for(gp_num=0; gp_num < AUTH_GNUM; gp_num++)
	  {
            if(end_gp_list(userrec.gp,gp_num))
	      break;
	    strlcat(buf, " ", sizeof(buf));
	    strlcat(buf, userrec.gp[gp_num].name, sizeof(buf));
	    if(userrec.gp[gp_num].flgs & AUTHFLG_GWIZ)
		strlcat(buf,"(GROUP-WIZARD)",sizeof(buf));

	  }
	}
	else
		snprintf(buf,sizeof(buf),"Report for user %s",av[1]);

	if(userrec.ln[0] != '\0') {
		strlcat(buf," [",sizeof(buf));
		strlcat(buf,userrec.ln,sizeof(buf));
		strlcat(buf,"]",sizeof(buf));
	}
	sosay(0, buf);

	/* print last login */
	if(userrec.last != 0) {
		char		*ap;

		ap = ctime((time_t *) &(userrec.last));
		ap[24] = '\0';
		snprintf(buf,sizeof(buf),"Last authenticated: %s",ap);
		sosay(0, buf);
	}
	/* print last password change */
	if(userrec.lastpw != 0) {
		char		*ap;

		ap = ctime((time_t *) &(userrec.lastpw));
		ap[24] = '\0';
		snprintf(buf,sizeof(buf),"Last changed password: %s",ap);
		sosay(0, buf);
	}

	/* print bad logins */
	if(userrec.bcnt != 0) {
		snprintf(buf,sizeof(buf),"%d bad logins",userrec.bcnt);
		sosay(0, buf);
	}

	/* misc flags */
	if((ap = codetoproto(userrec.atyp)) == (AProto *)0)
		strlcpy(buf,"Unknown authentication protocol",sizeof(buf));
	else
		snprintf(buf,sizeof(buf),"Authentication protocol: %s",ap->name);
	sosay(0, buf);

	strlcpy(buf,"Flags:",sizeof(buf));
	if(userrec.flgs & AUTHFLG_WIZ)
		strlcat(buf," WIZARD",sizeof(buf));
	if(userrec.flgs & AUTHFLG_DIS)
		strlcat(buf," DISABLED",sizeof(buf));
	if(buf[6] == '\0')
		strlcat(buf," none",sizeof(buf));
	sosay(0, buf);

	/* end multiline */
	if(!interactive)
		sosay(0, ".");
	syslog(LLEV,"%.100s DISPLAY %.100s",username,av[1]);
	return(0);
}


static	void
send_user_listing(user,a)
char	*user;
Auth	*a;
{
	AProto	*ap;
	char	buf[MAX_STR];
	char	dp;
	char	gp;
	char	wp = ' ';
	char	*rp;
	char	*an;
	int 	gp_num;

	/* setup status */
	if(a->flgs & AUTHFLG_DIS)
		dp = 'n';
	else
		if(a->flgs & AUTHFLG_BSLP)
			dp = 'b';
		else
			dp = 'y';
	if(a->flgs & AUTHFLG_WIZ)
		wp = 'W';

	gp = (!end_gp_list(a->gp,0) && (a->gp[0].flgs & AUTHFLG_GWIZ))? 'G':' ';
	
	/* last login */
	if(a->last == 0)
		rp = "never";
	else {
		rp = ctime((time_t*) &(a->last));
		rp[24] = '\0';
	}


	/* auth protocol */
	if((ap = codetoproto(a->atyp)) == (AProto *)0)
		an = "unknown";
	else
		an = ap->name;

	snprintf(buf,sizeof(buf),"%-12.12s %-12.12s %-12.12s%c%c%c %-12.12s  %-24.24s",user,a->gp[0].name,a->ln,dp,wp,gp,an,rp);	
	sosay(0, buf);
	if (end_gp_list(a->gp,0) || end_gp_list(a->gp,1))
		return;
	for (gp_num = 1; (gp_num < AUTH_GNUM) && 
		         !end_gp_list(a->gp,gp_num); gp_num++) {
		snprintf(buf,sizeof(buf),"             %-13.13s              %c",
			 a->gp[gp_num].name,
			 (a->gp[gp_num].flgs & AUTHFLG_GWIZ) ? 'G':' ');	
		sosay(0, buf);
	}
}



/* client expects this command to be multiline. kludgy. */
static	int
f_list(ac,av,orig,usage)
int	ac;
char	*av[];
char	*orig;
char    *usage;
{
	char	buf[MAX_STR];
	Auth	abuf;
	int	rc;
	int	wiz;

	if(!authenticated) {
		sosay(0, permdenied);
		if(!interactive)
			sosay(0, ".");
		return(0);
	}

	wiz  = userrecauth.flgs & AUTHFLG_WIZ;


	if(ac > 2) {
		showusage(usage);
		if(!interactive)
			sosay(0, ".");
		return(0);
	}
	if(ac > 1) {
		
		Group gp[2];
		gp[0].flgs = 0;
		strlcpy(gp[0].name, av[1], AUTH_GSIZ);
		gp[1].name[0] = '\0';
		gp[1].name[1] = '\0';

		if(wiz == 0 && ((userrecauth.flgs & AUTHFLG_GWIZ) == 0 || !check_gwiz(userrecauth.gp, gp))) {
				sosay(0, permdenied);
				if(!interactive)
					sosay(0, ".");
				return(0);
		}
		
	}		

	if(ac > 1) {
		syslog(LLEV,"%.100s LIST %.100s",username,av[1]);
		snprintf(buf,sizeof(buf),"Report for users in group %.100s",av[1]);
	} else {
		syslog(LLEV,"%.100s LIST",username);
		snprintf(buf,sizeof(buf),"Report for users in database");
	}
	sosay(0, buf);

sosay(0, "user           group      longname   status  proto      last");
sosay(0, "----         --------     --------    --- ------------  ----");

	if(auth_dbopen()) {
		sosay(0, "Cannot open database.");
		if(!interactive)
			sosay(0, ".");
		return(0);
	}
	rc = auth_dbtraversestart(buf,&abuf);
	while (rc == 0) {
		if(ac < 2 || ((ac > 1) && (strcmplist(av[1], abuf.gp)) != -1))
			send_user_listing(buf,&abuf);
		rc = auth_dbtraversenext(buf,&abuf);
	}
	auth_dbclose();
	/* end multiline */
	if(!interactive)
		sosay(0, ".");
	return(0);
}


/* generate a bogus challenge for users w/o accounts */
static	int
randomchallenge(c,siz)
char	*c;
int	siz;
{
	extern	long	randomnumber();
	int		len;

	for(len = 0; len < siz; len++)
		c[len] = (char)(((u_int)randomnumber() % 10) + '0');
	c[siz] = '\0';
	return(0);
}



/* if we somehow have a bogus user give them a bogus challenge */
static int
bogusprompt()
{
	char	cbuf[128];

	strlcpy(cbuf,"challenge Challenge \"",sizeof(cbuf));
	randomchallenge(&cbuf[21],6);
	strlcat(cbuf,"\": ",sizeof(cbuf));
	sosay(0, cbuf);
	return(0);
}

Cfg* authsrv_init(int interactive)
{
	char	*s;
	memset(proxy_name,0,sizeof(proxy_name));
	memset(proxy_chroot,0,sizeof(proxy_chroot));

	strlcpy(proxy_name,"authsrv",sizeof(proxy_name));

#ifndef LOG_DAEMON
	openlog(proxy_name,LOG_PID);
#else
	openlog(proxy_name,LOG_PID|LOG_NDELAY,LFAC);
#endif

/*
	We lack fastdaemon fucntions here yet.
	To be added later. Maybe. It is PITA to unify.
*/

	if ((proxy_confp = cfg_read(proxy_name)) == (Cfg *)-1) {
		syslog(LLEV,"fwtkcfgerr: no netperm-table records for %.128s",proxy_name);
		exit(1);
	}

	proxy_conf_groupid(proxy_confp);
	proxy_conf_userid(proxy_confp);
	proxy_timeout = proxy_conf_int(proxy_confp,"timeout",1,INT_MAX,PROXY_TIMEOUT);
	proxy_stats.child_limit = proxy_conf_int(proxy_confp,"maxchildren",1,4096,0);
	sotimeout(proxy_timeout);
	if ((s = proxy_conf_string(proxy_confp,"directory")))
		strlcpy(proxy_chroot,s,sizeof(proxy_chroot));

	time(&proxy_stats.start_time);

	if (!interactive && get_sock_type(0) == AF_INET && peername(0,proxy_stats.rladdr,proxy_stats.riaddr,sizeof(proxy_stats.rladdr))) {
		if (errno==ENOTCONN)
			syslog (LLEV, "lost connection");
		else
			syslog (LLEV, "fwtksyserr: cannot get peer name: %s", strerror(errno));
		exit(1);
	}

	proxy_stats.inbytes = 0;
	proxy_stats.outbytes = 0;
	proxy_update_status();
	return(proxy_confp);

}


void log_usrmsg(char *msg, char *prm1, char *prm2_pat, char *prm2)
{
  char buf[MAX_STR];

  strlcpy(buf, msg, sizeof(buf));
  if(prm2[0] != '\0')
  {
    strlcat(buf, prm2_pat, sizeof(buf));
    syslog(LLEV, buf, prm1, prm2);
  }
  else
    syslog(LLEV, buf, prm1);
}

void showusage(char *usage)
{
  char buf[MAX_STR] = "Usage: ";
  strlcat(buf, usage, sizeof(buf));
  sosay(0, buf);
}

int check_auth_console(char *cons_sockname, char auth_req[])
{
  struct sockaddr_un sn;

  char buf[MAX_STR];
  int cons_fd;
  struct stat stat_buf;
  char *pwd_tok, *p;
  static uint32_t auth_req_num = 0;

  if(stat(cons_sockname, &stat_buf) == 0 && S_ISSOCK(stat_buf.st_mode))
  {
	    cons_fd = socket(PF_UNIX, SOCK_STREAM, 0);
	    if (cons_fd < 0) {
		    syslog (LLEV, "fwtksyserr: socket(): %s", strerror(errno));
		    return NO_CONS;
	    }

	    strlcpy(sn.sun_path, cons_sockname,  UNIX_PATH_MAX);
	    sn.sun_family = AF_UNIX;

	    if (connect(cons_fd, (struct sockaddr *)&sn, sizeof(sn)) < 0) {
		syslog (LLEV, "fwtksyserr: connect(): %s", strerror(errno));
	        return NO_CONS;
	    }

	    if(cons_fd < 0) { return NO_CONS; }

	    if(sosay(cons_fd, auth_req)) { 
	        close(cons_fd);
	      	return NO_CONS; 
	    }

	    if (sogets(cons_fd, buf, sizeof(buf)) <= 0) {
	        close(cons_fd);
	      	return NO_CONS; 
	    }

	    if(!strncasecmp(buf, "auth", strlen("auth")))
	    {
	      if((pwd_tok = strchr(buf, ' ')) != NULL && strlen(++pwd_tok) > 0) /* password auth with console-generated pwd */
	      {
	        if((p=strrchr(buf, '\n')) != NULL) *p='\0';
	        strlcpy(console_pwd, pwd_tok, sizeof(console_pwd));
	      }
	      auth_req_num++;
	      return CONS_AUTH;
	    }
	    else
	    {
	      sowrite(0, "console: ",9);
	      sosay(0, buf);
	      return CONS_DENY;
	    }
  }
  return(NO_CONS);
}

int makeup_cons_sockname(char *sockdir, char *host, char *username, char *sockname)
{
   if(host != NULL && strlen(host) > 0 && strcmp(host, "unknown")) {
     	strlcpy(sockname, sockdir, UNIX_PATH_MAX); 
	strlcat(sockname, "/", UNIX_PATH_MAX);
     	strlcat(sockname, host, UNIX_PATH_MAX);      
	strlcat(sockname, ".", UNIX_PATH_MAX);
     	strlcat(sockname, username, UNIX_PATH_MAX);  
	strlcat(sockname, ".sock", UNIX_PATH_MAX);
     	return 1;
   }
   return 0;
}

sa_family_t get_sock_type(int fd)
{
	socklen_t		addrlen;
	struct	sockaddr_un	sua;
    	struct sockaddr         *sa  = (struct sockaddr *)   &sua;
    	struct sockaddr_in      *sia = (struct sockaddr_in *)&sua;
	int			protoval;
	socklen_t		protolen;
	char			tbuf[1500];


	addrlen = sizeof(struct sockaddr_un);

	protolen = sizeof(int);

	if (getsockopt(fd,SOL_SOCKET,SO_TYPE,(char *)&protoval,&protolen)) {
		syslog(LLEV,"fwtksyserr: cannot get protocol id, %s", strerror(errno));
		return(1);
	}

	switch (protoval) {
		case SOCK_STREAM:
			if (getpeername(fd,(struct sockaddr *)sa,&addrlen)<0) {
				if (errno!=ENOTCONN)
					syslog(LLEV,"fwtksyserr: getpeername failed: %s", strerror(errno));
				return(1);
			}
			break;
		case SOCK_DGRAM:
			if (recvfrom(fd,tbuf,sizeof(tbuf),MSG_PEEK,
				     (struct sockaddr *)sa,&addrlen) < 0) {
				syslog(LLEV,"fwtksyserr: UDP socket error %s", strerror(errno));
				return(1);
			}
			if (connect(fd,(struct sockaddr *)sa,sizeof(*sa))) {
				syslog(LLEV,"fwtksyserr: UDP socket connect error %s", strerror(errno));
				return(1);
			}
			break;
		default:
			syslog(LLEV,"fwtksyserr: unknown protocol %d",protoval);
			return(1);
	}

    	if(sia->sin_family == AF_INET)
      		return AF_INET;
    	else if(sua.sun_family == AF_UNIX)
      		return AF_UNIX;
    	else
      		return (sa_family_t) -1;

}

Cfg*
authsrv_proxy_conf_hosts (Cfg *confp, char *rladdr, char *riaddr)
{
  Cfg *cf = cfg_get ("hosts", confp);
  int i;

  while (cf != (Cfg *)0)
    {
      for (i = 0; i < cf->argc && cf->argv[i][0] != '-'; ++i)
	{
	  if (hostmatch (cf->argv[i], riaddr))
	    {
	      if (cf->flags & PERM_DENY)
		goto deny;
	      return cf;
	    }
	}
      cf = cfg_get ("hosts", (Cfg *)0);
    }
deny:
  syslog (LLEV, "deny host=%s/%s use of gateway", rladdr, riaddr);
  return (Cfg *)0;
}

Cfg * proxy_get_match(char *tag, const Group gp[], char* arg[])
{
	Cfg*	cfp;
	int	top;

	/* process netperm strings */
	for(cfp = cfg_get(tag, proxy_confp); cfp != (Cfg *) 0; cfp = cfg_get(tag, 0)) {
		if (cfp->argc < 3) {
			syslog(LLEV,"fwtkcfgerr: improperly formatted '%s' record, line %d",tag, cfp->ln);
			continue;
		}
		if(strcmp(cfp->argv[0], "group") == 0) {
			if(!nacasematchlist(cfp->argv[1], gp))
				continue;
		} else if(strcmp(cfp->argv[0], "user") == 0) {
			if(!nacasematch(cfp->argv[1], arg[0]))
				continue;
		} else {
			syslog(LLEV,"fwtkcfgerr: '%s' should specify user or group, line %d", tag, cfp->ln);
			sosay(0, "operation: syntax error in netperm-table");
			return (NULL);
		}
		if(!nacasematch(cfp->argv[2],arg[1]))
			continue;

		if(!hostmatch(cfp->argv[3], arg[2]) &&
			!nacasematch(cfp->argv[3], arg[2]))
			continue;


		top = cfp->argc;
		if(cfp->argc > 7 && !strcmp(cfp->argv[cfp->argc - 3],"time"))
			top = cfp->argc - 3;

		if(strcmp(tag, "operation") == 0) {
			int match = 1;
			int counter;
			char** interven_arg = arg + 3;

			/* checl destination */

			if(!hostmatch(cfp->argv[4], arg[3]) &&
				!nacasematch(cfp->argv[4], arg[3]))
				continue;

			/* match intervening tokens (if any) up to possible "time" field */
			for(counter = 5; counter < top; counter++) {
				if(!(match = nacasematch(cfp->argv[counter], *interven_arg ? *interven_arg++ : "blech")))
					break;
			}
			if(!match)
				continue;
		} else if(top > 4) { /* authentication */
			syslog(LLEV,"fwtkcfgerr: '%s' entry syntax is incorrect, \"time\" expected, line %d", tag, cfp->ln);
			sosay(0, "operation: syntax error in netperm-table");
			return (NULL);
		}

		if(cfp->argc != top) {
			if(time_ok(cfp->argv[cfp->argc - 1], cfp->argv[cfp->argc - 2]) != 0)
				continue;
		}
		break;
	}
	return cfp;
}


