/*
 * xtacacsd.c Edit history:
 *
 * 29-Jun-94: Add ARAP and CHAP support.
 * 27-Jun-94: Add support for SLIP ACCESS LISTS.  This includes:
 *		minor version support, supplementary data file, and a new
 *		slipon() function.
 * 19-Apr-94: Add support for TACACS_LOGINREQ reason and null
 *              usernames in slipon requests.
 *  8-Feb-93: fix debugging printf's to always have crlfs.
 *  2-Feb-93: fix calls to inet_ntoa to explicitly pass by reference.
 *              (compiler was sometimes passing by value and causing probs)
 *  2-Feb-93: avoid allignment problems on risc machines by using
 *              ((char *)tp) + XTACACSSIZE instead of (char *) (tp + 1);
 * 13-Jan-93: fix structure alignment problems on risc machines (use
 *		XTACACSSIZE instead of sizeof)
 * 13-Jan-93: Only set the accesslist field of the response packet if
 *		the user specified a special passwd file (the group id
 *		fields in /etc/passwd are unlikely to be meaningful
 *		access-list numbers (for extended tacacs)
 */

#define DEBUG1
/* Uncomment the following to get lots more debugging.
 * (Warning some passwords/secrets will get printed.)
 */
/* #define DEBUG_HAIRY */

/*
 * TACACS daemon suitable for using on Un*x systems.
 *
 * Janruary 1989, Greg Satz
 *
 * Copyright (c) 1989-1993 by cisco Systems, Inc.
 * All rights reserved.
 */

#include <string.h>
#ifndef SYSV
#include <strings.h>
#endif /* !SYSV */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/file.h>

#include <netinet/in.h>

#include <stdio.h>
#include <errno.h>
#include <pwd.h>
#include <netdb.h>
#include <sys/syslog.h>
#include <utmp.h>
#ifdef SYSV
#include <fcntl.h>
#define index strchr
#endif /* SYSV */
#ifdef CHAP_SUPPORT
#include "md5.h"
#endif /* CHAP_SUPPORT */
/*
 * TACACS protocol defintions
 */
#define uchar unsigned char
#define ulong unsigned long
#include "tacacs.h"
#define oresponse namelen
#define oreason pwlen

#define	TIMEOUT		(5*60)

#define	TACACS_PORT	49

#define SEC_IN_DAY      (24*60*60)	/* seconds in a day */
#define WARNING_PERIOD  14		/* days of expiration warning */
#define	PASSWD_LENGTH	14		/* length of password for crypt */
#define SOME_ARBITRARILY_LARGE_NUMBER 100

int debug;				/* debugging flag */
int logging;				/* syslog logging flag */
int stand;				/* running standalone or not */
char *file;				/* validation filename */
char *supfile;				/* supplementary validation file */
int userpwfile;				/* flag - user specified pw file */
char *wtmpfile;				/* wtmp format filename */
FILE *wtmpf;
unsigned long querytime;		/* time query came in */

struct sockaddr_in from;
int fromlen;
struct hostent *hp;
char buf[BUFSIZ];

char *monthname[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
		     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
long days_ere_month[] = {0, 31, 59, 90, 120, 151,
			 181, 212, 243, 273, 304, 334};

/*
 * main
 * We can be called from inetd or via the rc scripts directly
 * Parse arguments are act appropiately.
 */

main (argc, argv)
    int argc;
    char **argv;
{
    int c, on = 1, s;
    struct servent *sp;
    tacacstype *tp;
    extern char *optarg;

    debug = 0;				/* no debugging */
    logging = 0;			/* no logging */
    stand = 0;				/* under inetd */
    file = NULL;			/* /etc/passwd */
    supfile = NULL;			/* no default */
    userpwfile = 0;			/* not specified by user */
    wtmpfile = NULL;
    wtmpf = NULL;
    openlog("tacacsd", LOG_PID, LOG_LOCAL1);
    while ((c = getopt(argc, argv, "df:F:lsw:")) != EOF)
	switch (c) {
	case 'd':			/* debug */
	    debug = 1;
	    break;
	case 'f':			/* file name */
	    file = optarg;
	    userpwfile = 1;		/* specified by user */
	    break;
	case 'F':			/* supplementary file name */
	    supfile = optarg;
	    break;
	case 'l':			/* logging */
	    logging = 1;
	    break;
	case 's':			/* stand-alone */
	    stand = 1;
	    break;
	case 'w':
	    wtmpfile = optarg;
	    break;
	default:
	    fprintf(stderr, "%s: illegal switch\r\n", argv[0]);
	    exit(1);
	}

    if (debug)
	syslog(LOG_DEBUG, "server starting");


    if (stand) {
	/*
	 * Background ourselves and let go of controlling tty
	 */
	if (!debug) {
	    if (fork())
		exit(0);
	    for (c = 0; c < getdtablesize(); c++)
		(void) close(c);
	    (void) open("/", O_RDONLY);
	    (void) dup2(0, 1);
	    (void) dup2(0, 2);
#ifndef SYSV
	    c = open("/dev/tty", O_RDWR);
	    if (c >= 0) {
		ioctl(c, TIOCNOTTY, (char *)0);
		(void) close(c);
	    }
#endif
	    openlog("tacacsd", LOG_PID, LOG_LOCAL1);
	}
    }

    if (stand) {
	/*
	 * Pick up a socket
	 */
	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
	    syslog(LOG_ERR, "socket: %m\n");
	    perror("socket()");
	    exit(1);
	}

	/*
	 * Get port we need to pay attention to
	 */
	bzero((caddr_t)&from, sizeof(from));
#ifdef sun
	from.sin_family = AF_INET;
	from.sin_addr.s_addr = INADDR_ANY;
#endif
	sp = getservbyname("tacacs", "udp");
	if (sp == NULL)
	    from.sin_port = ntohs(TACACS_PORT);
	else
	    from.sin_port = ntohs(sp->s_port);

	if (bind(s, &from, sizeof(from)) < 0) {
	    syslog(LOG_ERR, "bind: %m\n");
	    perror("bind()");
	    exit(1);
	}
    } else {
	s = 0;
	if (ioctl(s, FIONBIO, &on) < 0) {
	    syslog(LOG_ERR, "ioctl(FIONBIO): %m\n");
	    exit(1);
	}
    }

    /*
     * For 4.3BSD machines, this routine sets the file the pw routines use
     * to the given argument. We emulate it for others.
     */
/*    if (file != NULL)
	setpwfile(file); */

    if (wtmpfile != NULL) {
	wtmpf = fopen(wtmpfile, "a+");
	if (!wtmpf)
	    fprintf(stderr, "\nCan't open wtmp file \"%s\"\r\n",wtmpfile);
    }
    if (!stand)
	alarm(TIMEOUT);

again:
    fromlen = sizeof(from);
    c = recvfrom(s, buf, sizeof(buf), 0, (caddr_t)&from, &fromlen);
    if (c <= 0) {
	if (errno == EINTR && stand)
	    goto again;
	syslog(LOG_ERR, "recvfrom: %m\n");
	exit(1);
    }
#ifdef DEBUG
    hp = gethostbyaddr(&from.sin_addr, sizeof (struct in_addr), AF_INET);
    fprintf(stderr, "main: received validation request from %s\r\n",
	    hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr));
#endif

    if (logging) {
	hp = gethostbyaddr(&from.sin_addr, sizeof (struct in_addr), AF_INET);
	syslog(LOG_INFO, "validation request from %s",
	    hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr));
#ifdef DEBUG
	fprintf(stderr, "main: logged validation request\r\n");
#endif
    }


    tp = (tacacstype *)buf;

    if (tp->version == TA_VERSION)
	old_process(s, &from, tp);
    else if (tp->version == XTA_VERSION)	/* Minor number == 0 */
	new_process(s, &from, tp);
    else if (((tp->version & XTA_MAJOR_VERSION_MASK) == XTA_VERSION) &&
	     ((tp->version & XTA_MINOR_VERSION_MASK) <= XTA_MINOR_VERSION))
	new_process_with_minor(s, &from, tp);
    else if (logging)
	syslog(LOG_INFO, "illegal version specified: %d\n", tp->version);

    if (stand)
	goto again;

    exit(0);
}

/*
 * pw_verify
 * verify the provided name/password.
 */
pw_verify (name, passwd, ppw)
#ifdef SYSV
    char name[SOME_ARBITRARILY_LARGE_NUMBER];
    char passwd[SOME_ARBITRARILY_LARGE_NUMBER];
#else    
    char *name, *passwd;
#endif
    struct passwd **ppw;
{
    struct passwd *pw=(struct passwd *)0;
    FILE *doc_pwfile;

#ifdef DEBUG
    fprintf(stderr, "pw_verify: calling setpwent\r\n");
#endif

/*    setpwent();
    if (file != NULL)
	setpwfile(file); */

    if(!(doc_pwfile=fopen(file,"r")))
      if(!(doc_pwfile=fopen("/etc/passwd","r")))
        return(0); /* fail */

    while((pw=fgetpwent(doc_pwfile)) && strcmp(pw->pw_name,name));
 
    fclose(doc_pwfile);
   
/*    pw = getpwnam(name); */
#ifdef DEBUG
    fprintf(stderr, "pw_verify: returned %x from getpwnam\r\n", pw);
#endif      

    /*
     * Verify the entry.
     */
    if (pw != NULL && *pw->pw_passwd != '\0' &&
	passwd != NULL && *passwd != '\0') {
#ifdef SYSV
	strcpy(passwd, (char *)crypt(passwd, pw->pw_passwd));
#else
	passwd = (char *)crypt(passwd, pw->pw_passwd);
#endif	
#ifdef DEBUG
	fprintf(stderr, "encrypted: real %s, query %s\r\n", pw->pw_passwd, passwd);
#endif
	*ppw = pw;
	if (strcmp(passwd, pw->pw_passwd) == 0)
	    return(1);
    }
    /*
     * If passwd passed to this routine is NULL but the user exists in the
     * passwd file, then return passwd file info but still return 0 (FAIL).
     */
    if(passwd == NULL && pw != NULL)
	*ppw = pw;
    else
	*ppw = NULL;
    return(0);
}

/*
 * process
 * Perform necessary stuff to do a query operation. Return ANSWER.
 */

old_process (s, client, tp)
    int s;
    struct sockaddr_in *client;
    tacacstype *tp;
{
#ifdef SYSV
    char name[SOME_ARBITRARILY_LARGE_NUMBER];
    char passwd[SOME_ARBITRARILY_LARGE_NUMBER];
#else    
    char *name, *passwd;
#endif
    struct passwd *pw;
    int expired;

    querytime = time(NULL);
#ifdef DEBUG
    fprintf(stderr, "process: starting\r\n");
    fprintf(stderr, "process: namelen %d, pwdlen %d\r\n",
	    tp->namelen, tp->pwlen);
#endif
#ifndef SYSV    
    name = (char *)malloc(tp->namelen+1); 
#ifdef DEBUG
    fprintf(stderr, "process: malloc returned (name)\r\n");
    if (name == NULL)
	fprintf(stderr, "process: malloc failed on name (%d bytes)\r\n",
		tp->namelen+1);
#endif      
    passwd = (char *)malloc(tp->pwlen+1);
#ifdef DEBUG
    if (passwd == NULL)
	fprintf(stderr, "process: malloc failed on passwd (%d bytes)\r\n",
		tp->pwlen+1);
#endif      
    if (name == NULL || passwd == NULL)
	return;
#endif /* not SYSV */
    
    strncpy(name, ((char *)tp) + TACACS_SIZE, tp->namelen);
    name[tp->namelen] = '\0';
    strncpy(passwd, ((char *)tp) + TACACS_SIZE + tp->namelen, tp->pwlen);
    if (tp->pwlen > PASSWD_LENGTH)
	tp->pwlen = PASSWD_LENGTH;
    passwd[tp->pwlen] = '\0';

    /*
     * Assume failure
     */
    tp->oresponse = TA_A_REJECTED;
    tp->oreason = TA_A_DENIED;
    if (pw_verify(name, passwd, &pw)) {
	tp->oresponse = TA_A_ACCEPTED;
	tp->oreason = TA_A_NONE;
	
	/*
	 * Now check the expiration time.
	 */
	
	expired = check_expiration(pw->pw_shell);
	if (expired == 2) {
	    tp->oresponse = TA_A_DENIED;
	    tp->oreason = TA_A_EXPIRING;
	} else if (expired == 1)
	    tp->oreason = TA_A_EXPIRING;
    }


#ifdef DEBUG
    fprintf(stderr, "process: logging query result\r\n");
#endif
    if (logging) {
	if (pw != NULL)
	    syslog(LOG_INFO, "login query for %s (%s) %s\n", name, pw->pw_gecos,
		tp->oresponse == TA_A_ACCEPTED ? "accepted" : "rejected");
	else
	    syslog(LOG_INFO, "login query for %s %s\n", name,
		tp->oresponse == TA_A_ACCEPTED ? "accepted" : "rejected");
    }

    tp->type = TA_ANSWER;
#ifdef DEBUG
    fprintf(stderr, "process: sending query result to client\r\n");
#endif
    if (sendto(s, buf, TACACS_SIZE, 0, client,
	sizeof(struct sockaddr_in)) != TACACS_SIZE)
	syslog(LOG_ERR, "write: %m\n");

#ifndef SYSV
    free(name);
    free(passwd);
#endif    
#ifdef DEBUG
    fprintf(stderr, "process: done\r\n");
#endif
}

/*
 * new_process
 * Perform necessary stuff to do a query operation. Return ANSWER.
 */

new_process (s, client, tp)
    int s;
    struct sockaddr_in *client;
    xtacacstype *tp;
{
#ifdef DEBUG
    fprintf(stderr, "new_process: start\r\n");
#endif
    querytime = time(NULL);
    switch (tp->type) {
    case XTA_SLIPADDR:
    case XTA_LOGIN:
	xlogin(s, client, tp);
	break;
    case XTA_CONNECT:
	xconnect(s, client, tp);
	break;
    case XTA_ENABLE:
	xenable(s, client, tp);
	break;
    case XTA_LOGOUT:
	xlogout(s, client, tp);
	break;
    case XTA_RELOAD:
	xreload(s, client, tp);
	break;
    case XTA_SLIPON:
	xslipon(s, client, tp, 0/*OLD*/);
	break;
    case XTA_SLIPOFF:
	xslipoff(s, client, tp);
	break;
    default:
	if (logging)
	    syslog(LOG_INFO, "illegal type specified: %d", tp->type);
    }
}

/*
 * new_process_with_minor
 * This is the extended tacacs processing routine for minor version requests
 */

new_process_with_minor (s, client, tp)
    int s;
    struct sockaddr_in *client;
    xtacacstype *tp;
{
#ifdef DEBUG
    fprintf(stderr, "new_process_with_minor: (minor version %d): start\r\n",
	    (int)(tp->version & XTA_MINOR_VERSION_MASK));
#endif

    querytime = time(NULL);

    switch (tp->type) {
    case XTA_SLIPON:
	xslipon(s, client, tp, 1/*NEW*/);
	break;
#ifdef CHAP_SUPPORT
    case XTA_CHAP_AUTH:
	xchap_authenticate(s, client, tp);
	break;
#endif /* CHAP_SUPPORT */
#ifdef ARAP_SUPPORT
    case XTA_ARAP_AUTH:
	xarap_authenticate(s, client, tp);
	break;
#endif /* ARAP_SUPPORT */
    default:
	/* If request is not a "new" request, then try the "old" requests,
	 * but make some noise about the minor number being out of synch.
	 */
#ifdef DEBUG
	fprintf(stderr, "%s: WARNING: recieved request with a valid minor\r\n",
		"new_process_with_minor");
	fprintf(stderr, "number (%d), but there is no corresponding new\r\n",
		(tp->version & XTA_MINOR_VERSION_MASK));
	fprintf(stderr, "function for type (%d).  Trying older functions.\r\n",
		tp->type);
#endif
	new_process(s, client, tp);
	break;
    }
}

check_expiration(date)
    char *date;
{
    long day, month, year, leaps, now, expiration, warning;
    char monthstr[10];

    /*
     * If no date or a shell, let it pass.  (Backward compatability.)
     */
    if (!date || (strlen(date) == 0) || (*date == '/'))
	return(0);
    
    /*
     * Parse date string.  Fail it upon error.
     */
    if (sscanf(date, "%s %d %d", monthstr, &day, &year) != 3)
	return(2);

    /*
     * Compute the expiration date in days.
     */
    for (month = 0; month < 12; month++)
	if (strncmp(monthstr, monthname[month], 3) == 0)
	    break;
    if (month > 11)
	return(2);
    leaps = (year-1969)/4 + (((year % 4) == 0) && (month > 2));
    expiration = (((year-1970)*365) + days_ere_month[month] + (day-1) + leaps);
    warning = expiration - WARNING_PERIOD;

    /*
     * Get the current time (to the day)
     */
    now = querytime/SEC_IN_DAY;
    
    if (now > expiration)
	return(2);
    if (now > warning)
	return(1);
    return(0);
}

#ifndef BSD43
/*
 * setpwfile
 * Hack to get around the default for the pw routines using /etc/passwd
 */

#include <sys/stat.h>

setpwfile (file)
    char *file;
{
    FILE *f;
    struct stat pwbuf, fbuf;
    int i;
    char *c;

    if (stat("/etc/passwd", &pwbuf) < 0) {
	syslog(LOG_ERR, "stat: %m\n");
	exit(1);
    }

    setpwent();				/* open /etc/passwd */

    /*
     * This loop assumes that the stdio file buffers are contiguous
     * which isn't true for 4.3, but then we won't be here.
     */

    for (f = stderr + 1; f < stdin + getdtablesize(); f++)
        if (f->_flag & (_IOREAD|_IOWRT|_IORW) &&
	    fstat(fileno(f), &fbuf) >= 0 &&
	    pwbuf.st_dev == fbuf.st_dev &&
	    pwbuf.st_ino == fbuf.st_ino) {
	    freopen(file, "r", f);
	    fprintf(stderr, "hit at %d\r\n", fileno(f));
	    return;
	}

    syslog(LOG_ERR, "couldn't find /etc/passwd to replace");
    exit(1);
}
#endif

#ifdef SYSV
getdtablesize()
{
  return(_NFILE);
}
#endif

wtmp_entry (line, name, host)
    char *line, *name, *host;
{
    struct utmp entry;

    if (wtmpf == NULL)
	return;

    bzero(&entry, sizeof entry);

    if (strlen(line) < sizeof entry.ut_line)
	strcpy(entry.ut_line, line);
    else bcopy(line, entry.ut_line, sizeof entry.ut_line);

    if (strlen(name) < sizeof entry.ut_name)
	strcpy(entry.ut_name, name);
    else bcopy(name, entry.ut_name, sizeof entry.ut_name);

    if (strlen(host) < sizeof entry.ut_host)
	strcpy(entry.ut_host, host);
    else bcopy(host, entry.ut_host, sizeof entry.ut_host);

    entry.ut_time = querytime;

    if (fwrite(&entry, sizeof entry, 1, wtmpf) != 1) {
	if (logging)
	    syslog(LOG_ERR, "couldn't write syslog file");
    } else
	fflush(wtmpf);
	    

#ifdef DEBUG1
    fprintf(stderr, "\nwtmp: %s, %s %s %d\r\n", line, name, host, querytime);
#endif

}

xlogin (s, client, tp)
    int s;
    struct sockaddr_in *client;
    xtacacstype *tp;
{

    int routing;
#ifdef SYSV
    char name[SOME_ARBITRARILY_LARGE_NUMBER];
    char passwd[SOME_ARBITRARILY_LARGE_NUMBER];
#else    
    char *name, *passwd;
#endif
    struct passwd *pw;
    int expired;
    char linename[20];

#ifdef DEBUG
    fprintf(stderr, "xlogin: starting\r\n");
    if (tp->type == XTA_SLIPADDR)
	routing = ((tp->flags & XTA_F_ROUTING) != 0);
	fprintf(stderr, "xlogin: Slip address request %s\n",
		routing ? "(routing)" : "(not routing)");
    fprintf(stderr, "xlogin: namelen %d, pwdlen %d\r\n",
	    tp->namelen, tp->pwlen);
#endif
#ifndef SYSV    
    name = (char *)malloc(tp->namelen+1); 
#ifdef DEBUG
    fprintf(stderr, "xlogin: malloc returned (name)\r\n");
    if (name == NULL)
      fprintf(stderr, "xlogin: malloc failed on name (%d bytes)\r\n",
	      tp->namelen+1);
#endif      
    passwd = (char *)malloc(tp->pwlen+1);
#ifdef DEBUG
    if (passwd == NULL)
      fprintf(stderr, "xlogin: malloc failed on passwd (%d bytes)\r\n",
	      tp->pwlen+1);
#endif      
    if (name == NULL || passwd == NULL)
	return;
#endif /* not SYSV */
    strncpy(name, ((char *)tp)+XTACACSSIZE, tp->namelen);
    name[tp->namelen] = '\0';
    strncpy(passwd, ((char *)tp)+XTACACSSIZE + tp->namelen, tp->pwlen);
    if (tp->pwlen > PASSWD_LENGTH)
	tp->pwlen = PASSWD_LENGTH;
    passwd[tp->pwlen] = '\0';

#ifdef DEBUG1
    fprintf(stderr, "\nxlogin: user %s on tty%o, host %s\r\n", name, tp->lport,
	    hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr));	    
#endif
    /*
     * Assume failure
     */
    tp->response = TA_A_REJECTED;
    tp->reason = TA_A_DENIED;
    if (pw_verify(name, passwd, &pw)) {
	tp->response = XTA_A_ACCEPTED;
	tp->reason = XTA_A_NONE;
	tp->uuid = pw->pw_uid;
	if (userpwfile != 0)	/* only use GID from user defined files */
	    tp->accesslist = pw->pw_gid;
	else tp->accesslist = 0;
	tp->flags = xta_getflags(pw->pw_gecos);

	/*
	 * Now check the expiration time.
	 */

	expired = check_expiration(pw->pw_shell);
	if (expired == 2) {
	    tp->response = TA_A_DENIED;
	    tp->reason = TA_A_EXPIRING;
	} else if (expired == 1)
	    tp->reason = TA_A_EXPIRING;
    }

#ifdef DEBUG
    fprintf(stderr, "xlogin: logging query result\r\n");
#endif
    sprintf(linename, "TTY%o", tp->lport);
    if (tp->response == TA_A_ACCEPTED && tp->type == XTA_LOGIN)
	wtmp_entry(linename, name, 
	       hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr));

#ifdef DEBUG
    fprintf(stderr, "xlogin: sending query result to client\r\n");
#endif
    if (logging && tp->type == XTA_LOGIN) {
	if (pw != NULL)
	    syslog(LOG_INFO, "xlogin query from %s %s for %s (%s) %s\n", 
		   hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr), 
		   linename, name, pw->pw_gecos,
		tp->response == TA_A_ACCEPTED ? "accepted" : "rejected");
	else
	    syslog(LOG_INFO, "xlogin query from %s %s for %s %s\n",
		   hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr),
		   linename, name,
		   tp->response == TA_A_ACCEPTED ? "accepted" : "rejected");
    }
    if (logging && tp->type == XTA_SLIPADDR) {
	if (pw != NULL)
	    syslog(LOG_INFO, "slipaddress from %s %s for %s (%s) %s%s\n", 
		   hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr), 
		   linename, name, pw->pw_gecos,
		   routing ? "(routing) " : " ",
		   tp->response == TA_A_ACCEPTED ? "accepted" : "rejected");
	else
	    syslog(LOG_INFO, "slipaddress from %s %s for %s %s%s\n",
		   hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr),
		   linename, name,
		   routing ? "(routing) " : " ",
		   tp->response == TA_A_ACCEPTED ? "accepted" : "rejected");
    }

    tp->type = TA_ANSWER;
    if (sendto(s, buf, XTACACSSIZE, 0, client,
	sizeof(struct sockaddr_in)) != XTACACSSIZE)
	syslog(LOG_ERR, "write: %m\n");

#ifndef SYSV
    free(name);
    free(passwd);
#endif    
#ifdef DEBUG
    fprintf(stderr, "xlogin: done\r\n");
#endif
}

xconnect(s, client, tp)
    int s;
    struct sockaddr_in *client;
    xtacacstype *tp;
{
    struct hostent *hp1;
    char *name = ((char *)tp)+XTACACSSIZE;

    name[tp->namelen] = 0;
    hp1 = gethostbyaddr(&tp->dhost, sizeof (struct in_addr), AF_INET);

#ifdef DEBUG1
    fprintf(stderr, "\nxconnect: user %.*s(%d) to %s:%d\r\n", tp->namelen,
	    ((char *)tp)+XTACACSSIZE, tp->uuid,
	    hp1 ? hp1->h_name : (char *)inet_ntoa(&tp->dhost), tp->dport);
#endif
    
    
    if (logging)
	syslog(LOG_INFO, "xconnect from %s tty%o for %s (%d) to %s:%d\n",
	       hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr),
	       tp->lport, name, tp->uuid,
	       hp1 ? hp1->h_name : (char *)inet_ntoa(&tp->dhost), tp->dport);
    replyok (s, client, tp);
}

xenable (s, client, tp)
    int s;
    struct sockaddr_in *client;
    xtacacstype *tp;
{
#ifdef SYSV
    char name[SOME_ARBITRARILY_LARGE_NUMBER];
    char passwd[SOME_ARBITRARILY_LARGE_NUMBER];
#else    
    char *name, *passwd;
#endif
    struct passwd *pw;
    int expired;
    char linename[20];

#ifndef SYSV    
    name = (char *)malloc(tp->namelen+1); 
    passwd = (char *)malloc(tp->pwlen+1);
    if (name == NULL || passwd == NULL)
	return;
#endif /* not SYSV */
    
    sprintf(linename, "TTY%o", tp->lport);
    strncpy(name, ((char *)tp)+ XTACACSSIZE, tp->namelen);
    name[tp->namelen] = '\0';
    strncpy(passwd, ((char *)tp) + XTACACSSIZE + tp->namelen, tp->pwlen);
    if (tp->pwlen > PASSWD_LENGTH)
	tp->pwlen = PASSWD_LENGTH;
    passwd[tp->pwlen] = '\0';

#ifdef DEBUG1
    fprintf(stderr, "\nxenable: user %s on tty%o, host %s\r\n", name, tp->lport,
	    hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr));	    
#endif
    /*
     * Assume failure
     */
    tp->response = TA_A_REJECTED;
    tp->reason = TA_A_DENIED;
    if (pw_verify("$enable$", passwd, &pw)) {
	tp->response = XTA_A_ACCEPTED;
	tp->reason = XTA_A_NONE;
	tp->uuid = pw->pw_uid;
	tp->accesslist = pw->pw_gid;
	tp->flags = xta_getflags(pw->pw_gecos);

	/*
	 * Now check the expiration time.
	 */

	expired = check_expiration(pw->pw_shell);
	if (expired == 2) {
	    tp->response = TA_A_DENIED;
	    tp->reason = TA_A_EXPIRING;
	} else if (expired == 1)
	    tp->reason = TA_A_EXPIRING;
    }

    sprintf(linename, "TTY%o", tp->lport);

    tp->type = TA_ANSWER;
    if (logging)
	    syslog(LOG_INFO, "xenable from %s %s for %s %s\n", 
		   hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr), 
		   linename, name,
		tp->response == TA_A_ACCEPTED ? "accepted" : "rejected");
    if (sendto(s, buf, XTACACSSIZE, 0, client,
	sizeof(struct sockaddr_in)) != XTACACSSIZE)
	syslog(LOG_ERR, "write: %m\n");

#ifndef SYSV
    free(name);
    free(passwd);
#endif    
}

xlogout (s, client, tp)
    int s;
    struct sockaddr_in *client;
    xtacacstype *tp;
{
    char *name = ((char *)tp)+XTACACSSIZE;
    char linename[20];
    char *reason;

    switch(tp->reason) {
    case XTA_R_IDLE:
	reason = "Idle-timeout";
	break;
    case XTA_R_DROP:
	reason = "Carrier-Drop";
	break;
    case XTA_R_BAD:
	reason = "Bad-Passwords";
	break;
    case XTA_R_QUIT:
	reason = "";
	break;
    default:
	reason = "Unknown-reason";
	break;
    }

    name[tp->namelen] = 0;

#ifdef DEBUG1
    fprintf(stderr, "\nxlogout:  user %s(%d) line %o %s\r\n", name, tp->uuid,
	    tp->lport, reason);
#endif
    sprintf(linename, "TTY%o", tp->lport);
    if (logging)
	syslog(LOG_INFO, "xlogout from %s %s, user %s(%d) %s\n",
	       hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr), linename,
	       name, tp->uuid, reason);
    wtmp_entry(linename, "", 
	       hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr));
    replyok (s, client, tp);
}

xreload(s, client, tp)
    int s;
    struct sockaddr_in *client;
    xtacacstype *tp;
{
#ifdef DEBUG1
    fprintf(stderr, "\nxreload: host %s\r\n",
	    hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr));
#endif
    if (logging)
	syslog(LOG_INFO, "system reload from %s\n",
	       hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr));
    wtmp_entry("~", "", hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr));
    replyok (s, client, tp);
}

/*
 * xslipon() and xslipon_new()
 * For "new" functionality, return a structure xta_slip_acl in the
 * password field.
 * Be sure to set namelen = 0 and pwlen = sizeof(struct xta_slip_acl).
 */
xslipon(s, client, tp, new)
    int s;
    struct sockaddr_in *client;
    xtacacstype *tp;
    int new;		/* Is this the new SLIPON */
{
    struct hostent *hp1;
    char linename[20];
    char *name = ((char *)tp) + XTACACSSIZE;

    name[tp->namelen] = 0;

    hp1 = gethostbyaddr(&tp->dhost, sizeof (struct in_addr), AF_INET);

#ifdef DEBUG1
    fprintf(stderr, "\n%s:  user %.*s(%d) line %o slip address %s %s\r\n",
	    new ? "xslipon_new" : "xslipon",
	    tp->namelen, ((char *)tp)+XTACACSSIZE, tp->uuid, tp->lport, 
	    hp1 ? hp1->h_name : (char *)inet_ntoa(&tp->dhost),
	    (tp->flags & XTA_F_ROUTING) ? "(routing)" : " ");

#endif
    sprintf(linename, "SLIP%o", tp->lport);
    wtmp_entry(linename, name, hp1 ? hp1->h_name : (char *)inet_ntoa(&tp->dhost));
    if (logging)
	syslog(LOG_INFO, "%s from %s %s for  user %s(%d) address %s%s\n",
	       new ? "xslipon_new" : "xslipon",
	       hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr), linename,
	       name, tp->uuid,
	       hp1 ? hp1->h_name : (char *)inet_ntoa(&tp->dhost),
	       (tp->flags & XTA_F_ROUTING) ? " (routing)" : " ");

    if (new) {
	struct xta_slip_acl acl;
	int found_acl = 0;
	long acl_tmp;

	/*
	 * Add per user authorization checks here if you like.
	 */

	acl_tmp = 0;
	if (get_sup_info_long(name, XTA_SUP_IPACLIN, &acl_tmp)) {
	    acl.in = htonl(acl_tmp);
	    found_acl = 1;
	} else {
	    acl.in = 0;
	}	
	acl_tmp = 0;
	if (get_sup_info_long(name, XTA_SUP_IPACLOUT, &acl_tmp)) {
	    acl.out = htonl(acl_tmp);
	    found_acl = 1;
	} else {
	    acl.out = 0;
	}

	if (found_acl) {
	    int pak_len;
#ifdef DEBUG
	    fprintf(stderr,
		    "xslipon_new: Got ACLs: in = \"%d\", out = \"%d\"\r\n",
		    acl.in, acl.out);
#endif
	    tp->namelen = 0;
	    tp->pwlen = sizeof(acl);
	    tp->response = XTA_A_ACCEPTED;
	    tp->reason = XTA_A_NONE;
	    tp->type = TA_ANSWER;
	    bcopy((char *)&acl, ((char *)tp) + XTACACSSIZE, tp->pwlen);
	    pak_len = XTACACSSIZE + (int)tp->pwlen;
	    if (sendto(s, buf, pak_len, 0, client,
		       sizeof(struct sockaddr_in)) != pak_len) {
#ifdef DEBUG
		fprintf(stderr, "xslipon_new: Error sending packet.\r\n");
#endif
		syslog(LOG_ERR, "write: %m\n");
	    }
	} else {
	    replyok (s, client, tp);
	}
    } else {
	/* Substitute actual per-user checks here if you like */
	/* This routine just says OK as long as the user has logged in */
	if (tp->namelen != 0) {
	    replyok (s, client, tp);
	} else {
	    tp->response = XTA_A_REJECTED;
	    tp->reason = XTA_A_LOGINREQ;
	    tp->type = TA_ANSWER;
	    if (sendto(s, buf, XTACACSSIZE, 0, client,
		       sizeof(struct sockaddr_in)) != XTACACSSIZE)
		syslog(LOG_ERR, "write: %m\n");
	}
    }
}

xslipoff(s, client, tp)
    int s;
    struct sockaddr_in *client;
    xtacacstype *tp;
{
    struct hostent *hp1;
    char linename[20];
    char *name;

    hp1 = gethostbyaddr(&tp->dhost, sizeof (struct in_addr), AF_INET);


#ifdef DEBUG1
    fprintf(stderr, "\nxslipoff:  user %.*s(%d) line %o slip address %s\r\n",
	    tp->namelen, ((char *)tp)+XTACACSSIZE, tp->uuid, tp->lport,
	    hp1 ? hp1->h_name : (char *)inet_ntoa(&tp->dhost));

#endif
    sprintf(linename, "SLIP%o", tp->lport);
    wtmp_entry(linename, "",  hp1 ?
	       hp1->h_name : (char *)inet_ntoa(&tp->dhost));
    name = (char *) (((char *)tp)+XTACACSSIZE);
    name[tp->namelen] = 0;
    if (logging)
	syslog(LOG_INFO, "xslip off from %s %s for %s(%d) address %s\n",
	       hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr), linename,
	       name, tp->uuid,
	       hp1 ? hp1->h_name : (char *)inet_ntoa(&tp->dhost));

    replyok (s, client, tp);
}

xta_getflags (string)
    char * string;
{
    return(0);
}

/*
 * Send an "ok" reply to client (for things like reload, logout)
 */
replyok (s, client, tp)
    int s;
    struct sockaddr_in *client;
    xtacacstype *tp;
{
    tp->response = XTA_A_ACCEPTED;
    tp->reason = XTA_A_NONE;
    tp->type = TA_ANSWER;
    if (sendto(s, buf, XTACACSSIZE, 0, client,
	       sizeof(struct sockaddr_in)) != XTACACSSIZE)
	syslog(LOG_ERR, "write: %m\n");
}

/*
 * Routines for reading the supplementary data file.
 * Return 1 if they found valid data, otherwise return 0.
 */
int
get_sup_info_long(user, position, data)
    char *user;
    int position;
    long *data;
{
    char buf[4096], *ptr, *ptr2;

    if(get_sup_line(user, &buf[0], sizeof(buf)) == 0) {
	return(0);		/* couldn't find it */
    }

    if(get_sup_field(buf, &ptr, position) == 0) {
	return(0);
    }

    *data = strtol(ptr, &ptr2, 10);
    if((ptr2 == ptr) || !ptr2 || (*ptr2 != '\0')) {
	return(0);		/* Field was not an valid long integer */
    }

    return(1);
}

int
get_sup_info_str(user, position, field, field_len)
    char *user;
    int position;
    char *field;
    int field_len;
{
    char buf[4096], *lfield;

    if(get_sup_line(user, &buf[0], sizeof(buf)) == 0) {
	return(0);		/* couldn't find it */
    }

    if(get_sup_field(&buf[0], &lfield, position) == 0) {
	return(0);
    }

    strncpy(field, lfield, field_len);
    return(1);
}

int
get_sup_field(buf, pptr, position)
    char *buf, **pptr;
    int position;
{
    char *ptr1, *ptr2;
    int i;

    ptr1 = index(buf, ':');
    if (ptr1 == (char *)NULL) {
#ifdef DEBUG_HAIRY
	fprintf(stderr, "%s: badly formated line.\r\n", "get_sup_field");
#endif
	return(0);		/* bad format (shouldn't ever happen) */
    }
    ptr2 = ptr1;
    for (i = position; i; i--) {
	ptr1 = ptr2 + 1;
	ptr2 = index(ptr1, ':');
	if ((ptr2 == (char *)NULL) && (i != 1)) {
#ifdef DEBUG_HAIRY
	    fprintf(stderr, "%s: No entry for field %d.\r\n",
		    "get_sup_field", position);
#endif
	    return(0);		/* no entry for this position */
	}
	if (ptr2 != (char *)NULL)
	    *ptr2 = '\0';	/* terminate the string */
	else {
	    if ((ptr2 = index(ptr1, '\n')) != (char *)NULL)
		*ptr2 = '\0';
	    if ((ptr2 = index(ptr1, '\r')) != (char *)NULL)
		*ptr2 = '\0';
	}
    }

    if (!ptr1 || !*ptr1) {
#ifdef DEBUG_HAIRY
	fprintf(stderr, "%s: Null or bad entry for field %d..\r\n",
		"get_sup_field", position);
#endif
	return(0);
    }

    *pptr = ptr1;
#ifdef DEBUG_HAIRY
    fprintf(stderr, "%s: found field %d = \"%s\"\r\n",
	    "get_sup_field", position, ptr1);
#endif
    return(1);
}

int
get_sup_line(match_string, buf, bufsize)
    char *match_string, *buf;
    int bufsize;
{
    FILE *fp;
    int size;

    if (supfile == (char *)NULL)
	return(0);		/* No suplementary file on command line */
    if (!match_string || !*match_string)
        return(0);		/* NULL or empty match string */

    if((fp = fopen(supfile, "r")) == (FILE *)NULL) {
#ifdef DEBUG1
	fprintf(stderr, "get_sup_line: ERROR opening file \"%s\"\n\r",
		supfile);
#endif
	return(0);
    }
    size = strlen(match_string);
    while (fgets(buf, bufsize, fp) != (char *)NULL) {
	if (*buf == '#')
	    continue;
	if (!strncmp(match_string, buf, size) && buf[size] == ':') {
	    fclose(fp);
	    return(1);
	}
    }
    fclose(fp);
    return(0);			/* match_string not found */
}

#ifdef ARAP_SUPPORT
xarap_authenticate(s, client, tp)
    int s;
    struct sockaddr_in *client;
    xtacacstype *tp;
{
    char *name, secret[128], *chal_1, *chal_2, resp_1[8], resp_2[8];
    struct passwd *pw;
    int expired, i;

    name = (char *)malloc(tp->namelen+1);
    if(name == NULL) {
#ifdef DEBUG
	fprintf(stderr, "xarap_authenticate(): malloc failed.\r\n");
#endif /* DEBUG */
	return;
    }
    strncpy(name, ((char *)tp)+XTACACSSIZE, tp->namelen);
    name[tp->namelen] = '\0';

    chal_1 = ((char *)tp) + XTACACSSIZE + tp->namelen;
    chal_2 = ((char *)tp) + XTACACSSIZE + tp->namelen + ARAP_CHAL_SIZE;
#ifdef DEBUG_HAIRY
    fprintf(stderr, "%s: Size of ARAP challenges is %d, should be %d.\r\n",
	    "xarap_authenticate()", tp->pwlen, (ARAP_CHAL_SIZE * 2));
#endif /* DEBUG */

    /*
     * Assume failure
     */
    tp->response = TA_A_REJECTED;
    tp->reason = TA_A_DENIED;
    tp->namelen = 0;
    tp->pwlen = 0;

    pw_verify(name, NULL, &pw);			/* doesn't check passwd */
    if(pw != NULL &&
       (arap_get_user_secret(name, &secret[0], sizeof(secret)) == 0) &&
       (arap_do_des(secret, chal_1, chal_2, resp_1, resp_2) == 0)) {

	for(i = 0; i < sizeof(secret); i++) {
	    secret[i] = 0;			/* clear the secret */
	}
	tp->namelen = 0;			/* Don't send back a name */
	tp->pwlen = (ARAP_RESP_SIZE * 2);	/* send back two responses */
#ifdef DEBUG_HAIRY
	fprintf(stderr, "%s: Response values are %d %d %d %d\n",
		"xarap_authenticate()",
		*((long *)&resp_1[0]), *((long *)&resp_1[4]),
		*((long *)&resp_2[0]), *((long *)&resp_2[4]));
#endif

	bcopy(resp_1, ((char *)tp) + XTACACSSIZE, ARAP_RESP_SIZE);
	bcopy(resp_2, ((char *)tp) + XTACACSSIZE + ARAP_RESP_SIZE,
	      ARAP_RESP_SIZE);			/* load the responses */

	tp->response = XTA_A_ACCEPTED;
	tp->reason = XTA_A_NONE;
	tp->uuid = pw->pw_uid;
	if (userpwfile != 0)    /* only use GID from user defined files */
	    tp->accesslist = pw->pw_gid;
	else tp->accesslist = 0;
	tp->flags = xta_getflags(pw->pw_gecos);

	/*
	 * Now check the expiration time.
	 */

	expired = check_expiration(pw->pw_shell);
	if (expired == 2) {
	    tp->response = TA_A_DENIED;
	    tp->reason = TA_A_EXPIRING;
	} else if (expired == 1)
	    tp->reason = TA_A_EXPIRING;
    }

    if(logging) {
	syslog(LOG_INFO, "arap_auth query from %s TTY%o for %s (%s) %s%s\n",
	       hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr), 
	       tp->lport, name, pw == NULL ? "NULL user": pw->pw_gecos,
	       tp->response == TA_A_ACCEPTED ? "accepted" : "rejected");
    }

    tp->type = TA_ANSWER;
    if(sendto(s, buf, (XTACACSSIZE + tp->namelen + tp->pwlen), 0, client,
	      sizeof(struct sockaddr_in)) !=
       (XTACACSSIZE+ tp->namelen + tp->pwlen)) {
	syslog(LOG_ERR, "xarap_authenticate(): write: %m\n");
    }

    free(name);
#ifdef DEBUG
    fprintf(stderr, "xarap_authenticate(): done\r\n");
#endif
}

/*
 * Do the DES operations on the two challenge values and put the results
 * in the two responses.
 * Return 0 for success.
 */
arap_do_des(secret, chal_1, chal_2, resp_1, resp_2)
char *secret, *chal_1, *chal_2, *resp_1, *resp_2;
{
    des_pw_bitshift(secret);

    des_init(0);
    des_setkey(secret);
    des_endes(chal_1);
    des_done();

    des_init(0);
    des_setkey(secret);
    des_endes(chal_2);
    des_done();

    bcopy(chal_1, resp_1, ARAP_RESP_SIZE);
    bcopy(chal_2, resp_2, ARAP_RESP_SIZE);

    return(0);
}

/*
 * Look the ARAP user up in the ARAP secrets file and get the secret.
 * returns	0  success
 *		1  failure
 *	       -1  error	(not implemented here)
 */
arap_get_user_secret(user, secret, secret_len)
    char *user, *secret;
    int secret_len;
{
    if(get_sup_info_str(user, XTA_SUP_ARAP_SEC, secret, secret_len) == 0)
	return(1);	/* bad */
    return(0);		/* good */
}
#endif /* ARAP_SUPPORT */

#ifdef CHAP_SUPPORT
xchap_authenticate(s, client, tp)
    int s;
    struct sockaddr_in *client;
    xtacacstype *tp;
{
    char *name, secret[128], *chal, resp[MD5_LEN];
    char id;
    struct passwd *pw;
    int expired, i;
    int chal_len;

    name = (char *)malloc(tp->namelen+1);
    if(name == NULL) {
#ifdef DEBUG
	fprintf(stderr, "xchap_authenticate(): malloc failed.\r\n");
#endif /* DEBUG */
	return;
    }
    strncpy(name, ((char *)tp)+XTACACSSIZE, tp->namelen);
    name[tp->namelen] = '\0';

    id = *(((char *)tp) + XTACACSSIZE + tp->namelen);

    chal_len = tp->pwlen - 1;

    chal = (char *)malloc(chal_len);
    if(chal == NULL) {
#ifdef DEBUG
	fprintf(stderr, "xchap_authenticate(): malloc failed.\r\n");
#endif /* DEBUG */
	return;
    }
    bcopy(((char *)tp) + XTACACSSIZE + tp->namelen + 1, chal, chal_len);

#ifdef DEBUG
    fprintf(stderr,
	    "%s: User = \"%s\", Id = %d, Size of CHAP challenges is %d.\r\n",
	    "xchap_authenticate()", name, (int)id, chal_len);
#endif /* DEBUG */

    /*
     * Assume failure
     */
    tp->response = TA_A_REJECTED;
    tp->reason = TA_A_DENIED;
    tp->namelen = 0;
    tp->pwlen = 0;

    pw_verify(name, NULL, &pw);			/* doesn't check passwd */
    if(pw != NULL &&
       (chap_get_user_secret(name, &secret[0], sizeof(secret)) == 0) &&
       (chap_do_md5(id, secret, chal, chal_len, resp) == 0)) {

	for(i = 0; i < sizeof(secret); i++) {
	    secret[i] = 0;			/* clear the secret */
	}
	tp->namelen = 0;			/* Don't send back a name */
	tp->pwlen = (MD5_LEN);
#ifdef DEBUG_HAIRY
	fprintf(stderr, "%s: Response value is %d %d %d %d\n",
		"xchap_authenticate()",
		*((long *)&resp[0]), *((long *)&resp[4]),
		*((long *)&resp[8]), *((long *)&resp[12]));
#endif
	bcopy(resp, ((char *)tp) + XTACACSSIZE /* + namelen (=0) */, MD5_LEN);

	tp->response = XTA_A_ACCEPTED;
	tp->reason = XTA_A_NONE;
	tp->uuid = pw->pw_uid;
	if (userpwfile != 0)    /* only use GID from user defined files */
	    tp->accesslist = pw->pw_gid;
	else tp->accesslist = 0;
	tp->flags = xta_getflags(pw->pw_gecos);

	/*
	 * Now check the expiration time.
	 */

	expired = check_expiration(pw->pw_shell);
	if (expired == 2) {
	    tp->response = TA_A_DENIED;
	    tp->reason = TA_A_EXPIRING;
	} else if (expired == 1)
	    tp->reason = TA_A_EXPIRING;
    }

    if(logging) {
	syslog(LOG_INFO, "chap_auth query from %s TTY%o for %s (%s) %s%s\n",
	       hp ? hp->h_name : (char *)inet_ntoa(&from.sin_addr), 
	       tp->lport, name, pw == NULL ? "NULL user": pw->pw_gecos,
	       tp->response == TA_A_ACCEPTED ? "accepted" : "rejected");
    }

    tp->type = TA_ANSWER;
    if(sendto(s, buf, (XTACACSSIZE + tp->namelen + tp->pwlen), 0, client,
	      sizeof(struct sockaddr_in)) !=
       (XTACACSSIZE + tp->namelen + tp->pwlen)) {
	syslog(LOG_ERR, "xchap_authenticate(): write: %m\n");
    }

#ifdef DEBUG_HAIRY
    fprintf(stderr, "sending: size = %d = %d + %d + %d\n",
	    XTACACSSIZE + tp->namelen + tp->pwlen, XTACACSSIZE,
	    tp->namelen, tp->pwlen);
#endif

    free(name);
    free(chal);
#ifdef DEBUG
    fprintf(stderr, "xchap_authenticate(): done\r\n");
#endif
}

/*
 * chap_do_md5()
 * Does an md5 hash of the stream of the following three:
 *	"the id", "the user's secret", "the challenge"
 * Returns 0 on success.
 */
chap_do_md5(id, secret, chal, chal_len, resp)
char id, *secret, *chal;
int chal_len;
char *resp;
{
    char *md_stream;
    int md_len;
    MD5_CTX mdcontext;

    md_len = 1 + strlen(secret) + chal_len;
    md_stream = (char *)malloc(md_len);
    if(!md_stream) {
#ifdef DEBUG
	fprintf(stderr, "chap_do_md5(): malloc failed.\n");
#endif
	return(-1);
    }
#ifdef DEBUG_HAIRY
    fprintf(stderr, "chap_do_md5(): id = %d, secret = \"%s\", chal_len = %d\n",
	    (int)id, secret, chal_len);
#endif
    md_stream[0] = id;
    strcpy(&md_stream[1], secret);
    bcopy(chal, &md_stream[1 + strlen(secret)], chal_len);

    MD5Init(&mdcontext);
    MD5Update(&mdcontext, md_stream, md_len);
    MD5Final(resp, &mdcontext);
    free(md_stream);
    return(0);
}

/*
 * Look the CHAP user up in the CHAP secrets file and get the secret.
 * returns	0  success
 *		1  failure
 *	       -1  error
 */
chap_get_user_secret(user, secret, secret_len)
    char *user, *secret;
    int secret_len;
{
    if(get_sup_info_str(user, XTA_SUP_CHAP_SEC, secret, secret_len) == 0)
	return(1);	/* bad */
    return(0);		/* good */
}
#endif /* CHAP_SUPPORT */
