/*
 * ppp.c - Point-to-Point Protocol.
 *
 * Copyright (c) 1989 Carnegie Mellon University.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by Carnegie Mellon University.  The name of the
 * University may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */
/*
 * Copyright (c) 1992 Purdue University
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by Purdue University.  The name of the University may not be used
 * to endorse or promote products derived * from this software without
 * specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Note: this copyright applies to portions of this software developed
 * at Purdue beyond the software covered by the original copyright.
 */

/*
 * TODO:
 * Don't prompt for passwd on /dev/tty (go into server mode).
 * Might want to choose IP addresses from config file based on user id
 * if the server, or based on remote host if tip'd client.
 * Way to set asyncmap.
 * Wait for tty queue to empty before exiting in quit.
 */

/*
 * There are three scenarios:
 * 1.  ppp used as daemon started from /etc/rc or perhaps /etc/ttys.
 *	a.  server
 *	b.  authentication necessary
 *	c.  want to use constant local ip addr
 *	d.  want to use constant remote ip addr, constant ip addr based on
 *	    authenticated user, or request ip addr
 * 2.  ppp used on /dev/tty after remote login.
 *	a.  server
 *	b.  no authentication necessary or allowed
 *	c.  want to use constant local ip addr
 *	d.  want to use constant remote ip addr, constant ip addr based on
 *	    authenticated user, or request ip addr
 * 3.  ppp used on line after tip'ing out.
 *	a.  client
 *	b.  remote end may request authentication
 *	c.  want to use constant local ip addr or request ip addr
 *	d.  want to use constant remote ip addr based on tip'd host, or
 *	    request remote ip addr
 */

#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <syslog.h>
#include <netdb.h>
#include <utmp.h>
#include <alloca.h>

#include <sys/param.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include "callout.h"

#include <sys/dlpi.h>
#include <net/if.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/termios.h>
#include <sys/conf.h>
#include <poll.h>

#define	DEVNAME_SIZE	128	/* Buffer size for /dev filenames */

#include <malloc.h>
#include <string.h>


#include "ppp.h"
#include "magic.h"
#include "fsm.h"
#include "lcp.h"
#include "ipcp.h"
#include "upap.h"
#include "chap.h"
#include "pathnames.h"
#include "patchlevel.h"

#include "dpio.h"

#define PPPHOSTS	"/etc/hosts.ppp"

#ifndef TRUE
#define TRUE (1)
#endif /*TRUE*/

#ifndef FALSE
#define FALSE (0)
#endif /*FALSE*/

#ifdef PIDPATH
static char *pidpath = PIDPATH;	/* filename in which pid will be */
				/* stored */
#else
static char *pidpath = _PATH_PIDFILE;
#endif /* PIDFILE */

static char uinfopath[DEVNAME_SIZE];

/* global vars */

int debug = 0;		        /* Debug flag */
char ifname[IFNAMSIZ];		/* Interface name */

int fd;				/* Async Device file descriptor */
int dpfd;			/* Network device file descriptor */
int dpmuxid;			/* muxid for Async attachment to dp device */

char hostname[MAX_HOSTNAME_LEN]; /* hostname */
u_char hostname_len;		/* hostname length */
u_char outpacket_buf[MTU+DLLHEADERLEN]; /* buffer for outgoing packet */

/* local vars */
static char *progname;			/* Name of this program */
static char devname[DEVNAME_SIZE] = "/dev/tty";	/* Device name */
static int ifunit = -1;			/* Interface unit number */
static char inspeed = 0;	        /* Input/Output speed */
static char outspeed = 0;	        /* Input/Output speed */
int s;				/* Socket file descriptor */
static pid_t	pid;			/* Our pid */
#ifdef sun
static pid_t	pgrpid;			/* Process Group ID */
#endif
static char user[80];			/* User name */
static char passwd[80];		/* passwd */
static char *connector = NULL;	/* holds command specified by the */
				/* "connect" command line option */
int dialup = 0;			/* Dialup mode */

static int default_device = TRUE; /* use default device (stdin/out) */

static u_char inpacket_buf[MTU+DLLHEADERLEN]; /* buffer for incoming packet */

static u_long netmask = 0;	/* netmask to use on ppp interface */
static char pidfilename[DEVNAME_SIZE];

static void hup __ARGS((int));
static void intr __ARGS((int));
static void term __ARGS((int));
static void alrm __ARGS((int));
static void io __ARGS((int));
static void incdebug __ARGS((int));
static void nodebug __ARGS((int));
static void getuserpasswd __ARGS((void));

static int setdebug __ARGS((int *, char ***));
static int setdialup __ARGS((int *, char ***));
static int setunit __ARGS((int *, char ***));
static int setpassive __ARGS((int *, char ***));
static int noopt __ARGS((int *, char ***));
static int setnovj __ARGS((int *, char ***));
static int noupap __ARGS((int *, char ***));
static int requpap __ARGS((int *, char ***));
static int nochap __ARGS((int *, char ***));
static int reqchap __ARGS((int *, char ***));
static int setspeed __ARGS((int *, char ***));
static int noaccomp __ARGS((int *, char ***));
static int noasyncmap __ARGS((int *, char ***));
static int noipaddr __ARGS((int *, char ***));
static int nomagicnumber __ARGS((int *, char ***));
static int setasyncmap __ARGS((int *, char ***));
static int setaddrmode __ARGS((int *, char ***));
static int setvjmode __ARGS((int *, char ***));
static int setmru __ARGS((int *, char ***));
static int nomru __ARGS((int *, char ***));
static int nopcomp __ARGS((int *, char ***));
static int setconnector __ARGS((int *, char ***));
static int setdomain __ARGS((int *, char ***));
static int setnetmask __ARGS((int *, char ***));
static void cleanup __ARGS((void));
static void str_restore __ARGS((void));
extern	char	*ttyname __ARGS((int));
#define	MAXMODULES	10		/* max number of module names that we can save */
static struct	modlist {
  char	modname[FMNAMESZ+1];
} str_modules[MAXMODULES];
static int	str_module_count = 0;

/*
 * Valid arguments.
 */
static struct cmd {
  char *cmd_name;
  int (*cmd_func)();
} cmds[] = {
  "-all", noopt,			/* Don't request/allow any options */
  "-ac", noaccomp,			/* Disable Address/Control compress */
  "addrmode", setaddrmode,	        /* set address negotiation mode */
  "-am", noasyncmap,			/* Disable asyncmap negotiation */
  "-as", setasyncmap,			/* set the desired async map */
  "-d", setdebug,			/* Increase debugging level */
  "debug", setdebug,			/* Increase debugging level */
  "dialup", setdialup,			/* Do special stuff for dialup */
  "-ip", noipaddr,			/* Disable IP address negotiation */
  "-mn", nomagicnumber,		        /* Disable magic number negotiation */
  "-mru", nomru,			/* Disable mru negotiation */
  "mru", setmru,			/* Set MRU value for negotiation */
  "-p", setpassive,			/* Set passive mode */
  "-pc", nopcomp,			/* Disable protocol field compress */
  "passive", setpassive,		/* Set passive mode */
  "+ua", requpap,			/* Require UPAP authentication */
  "-ua", noupap,			/* Don't allow UPAP authentication */
  "+chap", reqchap,			/* Require CHAP authentication */
  "-chap", nochap,			/* Don't allow CHAP authentication */
  "vjmode", setvjmode,		        /* set VJ compression mode */
  "-vj", setnovj,			/* disbale VJ compression */
  "connect", setconnector,              /* A program to set up a connection */
  "domain", setdomain,		        /* Add given domain name to */
					/* hostname*/
  "netmask", setnetmask,	        /* set netmask */
  "unit", setunit,			/* Set Unit request */
#if 0
  "local", setlocalipaddr,
  "remote", setremoteipaddr,
  "test", enabletestline,
#endif
  NULL
  };


/*
 * PPP Data Link Layer "protocol" table.
 * One entry per supported protocol.
 */
struct protent {
  u_short protocol;
  void (*init)();
  void (*input)();
  void (*protrej)();
} prottbl[] = {
  { LCP, lcp_init, lcp_input, lcp_protrej },
  { IPCP, ipcp_init, ipcp_input, ipcp_protrej },
  { UPAP, upap_init, upap_input, upap_protrej },
  { CHAP, ChapInit, ChapInput, ChapProtocolReject },
};


char *usage = "PPP patch level %d\n\
Usage: %s [ arguments ], where arguments are:\n\
	-all		Don't request/allow any options\n\
	-ac		Disable Address/Control compression\n\
	addrmode <m>    Address negotiation mode {old, draft (default)}\n\
	-am		Disable asyncmap negotiation\n\
	-as <n>		Set the desired async map to hex <n>\n\
	-d		Increase debugging level\n\
	debug		Increase debugging level\n\
	-ip		Disable IP address negotiation\n\
	-mn		Disable magic number negotiation\n\
	-mru		Disable mru negotiation\n\
	mru <n>		Set MRU value to <n> for negotiation\n\
	-p		Set passive mode\n\
	-pc		Disable protocol field compression\n\
	passive		Set passive mode\n\
	+ua <p>		Require UPAP authentication and use file <p> for\n\
                        remote login data\n\
	-ua		Don't allow UPAP authentication\n\
	+chap		Require CHAP authentication\n\
	-chap		Don't allow CHAP authentication\n\
	vjmode <m>      VJ compression mode {old, current (default), draft}\n\
        -vj             disable VJ compression\n\
        connect <p>     Invoke shell command <p> to set up the serial line\n\
        domain <d>      Append domain name <d> to hostname for authentication\n\
	<device>	Communicate over the named device\n\
	<speed>		Set the baud rate to <speed>\n\
	<loc>:<rem>	Set the local and/or remote interface IP\n\
			addresses.  Either one may be omitted.\n";

/* 
 * this module will attempt to reconstruct the stream with the
 * previously popped modules
 */

/*ARGSUSED*/
static void
  str_restore()
{
  /*EMPTY*/ 

  errno = 0;
  if(dpmuxid > 0 && ioctl(dpfd, I_UNLINK, dpmuxid) < 0) {
    MAINDEBUG((LOG_ERR, "str_restore: couldn't unlink from dp mux: %m"))
  }

  while(ioctl(fd, I_POP, 0) == 0);	 /* pop any we pushed */ 
  
  for(; str_module_count > 0; str_module_count--) {
    if(ioctl(fd, I_PUSH, str_modules[str_module_count-1].modname)) {
      MAINDEBUG((LOG_ERR, "str_restore: couldn't push module %s: %m",
		str_modules[str_module_count-1].modname))
    }
    else {
      MAINDEBUG((LOG_INFO, "str_restore: pushed module %s",
		str_modules[str_module_count-1].modname))
    }
  }

}

main(argc, argv)
     int argc;
     char *argv[];
{
  int i;
  sigset_t mask;
  struct termios	tios;
  struct sigaction sa;
  struct cmd *cmdp;
  char buf[DP_MAXDLBUF];
  ulong sap;

  FILE *pidfile;

  /*
   * Initialize syslog system and magic number package.
   */
  openlog("ppp", LOG_PID | LOG_NDELAY, LOG_LOCAL2);
  setlogmask(LOG_UPTO(LOG_WARNING));
  
  if (ttyname(fileno(stdin)))
    strcpy(devname, ttyname(fileno(stdin)));
  magic_init();

  if (gethostname(hostname, MAX_HOSTNAME_LEN) < 0 ) {
    syslog(LOG_ERR, "couldn't get hostname: %m");
    exit(1);
  }

  /*
   * Initialize to the standard option set and then parse the command
   * line arguments.
   */
  for (i = 0; i < sizeof (prottbl) / sizeof (struct protent); i++)
    (*prottbl[i].init)(0);
  
  progname = *argv;
  for (argc--, argv++; argc; ) {
    /*
     * First see if it's a command.
     */
    for (cmdp = cmds; cmdp->cmd_name; cmdp++)
      if (!strcmp(*argv, cmdp->cmd_name) &&
	  (*cmdp->cmd_func)(&argc, &argv))
	break;
    
    /*
     * Maybe a tty name, speed or IP address?
     */
    if (cmdp->cmd_name == NULL &&
	!setdevname(&argc, &argv) &&
	!setspeed(&argc, &argv) &&
	!setipaddr(&argc, &argv)) {
      fprintf(stderr, usage, VERSION, PATCHLEVEL, progname);
      exit(1);
    }
  }
  
  syslog(LOG_INFO, "Starting ppp daemon version %s patchlevel %d",
         VERSION, PATCHLEVEL); 
  /*
   * Initialize state.
   */

  if ((fd = open(devname, O_RDWR /*| O_NDELAY*/)) < 0) {
    syslog(LOG_ERR, "open(%s): %m", devname);
    exit(1);
  }

  if (connector) {
    if (set_up_connection(connector, fd, fd) < 0) {
      syslog(LOG_ERR, "could not set up connection");
      exit(1);
    }
  }
  
  if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    syslog(LOG_ERR, "socket : %m");
    exit(1);
  }
  
  /* if we exit, then try and restore the stream */ 
  atexit(cleanup);
  
  /* go through and save the name of all the modules, then pop em */
  while(1)	{ 
    if(!ioctl(fd, I_LOOK, str_modules[str_module_count].modname))
	MAINDEBUG((LOG_DEBUG, "popped stream module : %s",
		str_modules[str_module_count].modname))
    if(!ioctl(fd, I_POP, 0))
      str_module_count++;
    else
      break;
  }

  if(ioctl(fd, TCGETS, (caddr_t) &tios) < 0) {
    syslog(LOG_ERR, "ioctl(TCGETS): %m");
    exit(1);
  }
  
  tios.c_cflag  = CRTSCTS | (inspeed  ? inspeed : (tios.c_cflag & CBAUD));
  tios.c_cflag |= CS8|CREAD|HUPCL;
  tios.c_iflag = IGNBRK;
  
  if(ioctl(fd, TCSETS, (caddr_t) &tios) < 0) {
    syslog(LOG_ERR, "ioctl(TCSETS): %m");
    exit(1);
  }
  /*
   * Push the Async/FCS module and VJC module onto this stream.
   */
  if(ioctl(fd, I_PUSH, "pppasync") < 0) {
    syslog(LOG_ERR, "ioctl(I_PUSH, \"pppasync\"): %m");
    exit(1);
  }
  if (ioctl(fd, I_PUSH, "vjc") < 0) {
    syslog(LOG_ERR, "ioctl(I_PUSH, \"vjc\"): %m");
    exit(1);
  }

  /*
   * Now, open the dp device and set it up to accept LCP and NCP packets
   */
  if ((dpfd = open(DEVDP, O_RDWR)) < 0) {
    syslog(LOG_ERR, "Can't open \"%s\", %m", DEVDP);
    exit(1);
  }

  /*
   * Attach to the specified PPA (unit), and bind to the packets
   * of interest, namely packets directed towards the LCP and NCP
   * protocol families.
   */
  if (dlattachreq(dpfd, ifunit) < 0 ||
      dlokack(dpfd, buf) < 0) {
    syslog(LOG_ERR, "Can't attach to dp PPA %d, %m\n", ifunit);
    exit(1);
  }

  sap = DP_PROTO_SAP(PPP_LCP);
  if (dlbindreq(dpfd, sap, 0, DL_CLDLS, 0, 0) < 0 ||
      dlbindack(dpfd, buf) < 0) {
    syslog(LOG_ERR, "Can't bind to dp DLSAP %x, %m\n", DP_PROTO_SAP(PPP_LCP));
    exit(1);
  }

  sap = DP_PROTO_SAP(PPP_NCP);
  if (dlsubsbindreq(dpfd, &sap, sizeof(sap), DL_PEER_BIND) < 0 ||
      dlsubsbindack(dpfd, buf)) {
    syslog(LOG_ERR, "Can't bind to dp DLSAP %x, %m\n", DP_PROTO_SAP(PPP_NCP));
    exit(1);
  }

  /*
   * Finally, link this module to the dp device.
   */
  if((dpmuxid = ioctl(dpfd, I_LINK, fd)) < 0) {
    syslog(LOG_ERR, "ioctl(I_LINK): %m");
    exit(1);
  }

  if(fcntl(dpfd, F_SETFL, O_NDELAY) < 0) {
    syslog(LOG_ERR, "fcntl(F_SETFL, O_NDELAY): %m");
    exit(1);
  }
  /* read mode, message non-discard mode */
  if(ioctl(dpfd, I_SRDOPT, RMSGN) < 0) {
    syslog(LOG_ERR, "ioctl(I_SRDOPT, RMSGN): %m");
    exit(1);
  }
  /* Flush any waiting messages, or we'll never get SIGPOLL */
  if(ioctl(dpfd, I_FLUSH, FLUSHRW) < 0) {
    syslog(LOG_ERR, "ioctl(I_FLUSH, FLUSHRW): %m");
    exit(1);
  }

#if	0
  /*
   * Find out which interface we were given.
   */
  /* in streams, dp_if handles this ioctl */
  if (ioctl(fd, SIOCGETU, &ifunit) < 0) {
    syslog(LOG_ERR, "ioctl(SIOCGETU): %m");
    exit(1);
  }
#endif
  sprintf(ifname, dialup ? "dp%d" : "ppp%d", ifunit);
  syslog(LOG_NOTICE, "Using unit %s", ifname);
  pid = getpid();

  (void) sprintf(pidfilename, "%s/%s.pid", pidpath, ifname);

  /* write pid to file */

  if ((pidfile = fopen(pidfilename, "w")) != NULL) {
    fprintf(pidfile, "%d\n", pid);
    (void) fclose(pidfile);
  }

  SIFBRINGUP(DP_IN_PROGRESS);

  hostname_len = (u_char) strlen(hostname);

  MAINDEBUG((LOG_DEBUG, "hostname = %s", hostname))
      
#if 0
#ifdef SETSID
  if (default_device) {
    int id = tcgetpgrp(fd);
    if (id != pgrpid) {
      MAINDEBUG((LOG_WARNING, "warning: pppd is not the leader of a forground process group"))
    }
  }
  else
    if (tcsetpgrp(fd, pgrpid) < 0) {
      syslog(LOG_ERR, "tcsetpgrp(): %m");
      exit(1);
    }
#else
  if (ioctl(fd, TIOCSPGRP, &pid) < 0) {
    syslog(LOG_ERR, "ioctl(TIOCSPGRP): %m");
    exit(1);
  }
#endif
#endif
  
  /*
   * Compute mask of all interesting signals and install signal handlers
   * for each.  Only one signal handler may be active at a time.  Therefore,
   * all other signals should be masked when any handler is executing.
   */
  sigemptyset(&mask);
  sigaddset(&mask, SIGHUP);
  sigaddset(&mask, SIGINT);
  sigaddset(&mask, SIGALRM);
  sigaddset(&mask, SIGPOLL);
  /*
   * Block all signals until we get things set up here.
   */
  sigprocmask(SIG_BLOCK, &mask, NULL);	/* Block signals now */
  /*
   * Enable reception of signals generated by the streams driver.
   */
  if (ioctl(dpfd, I_SETSIG, S_INPUT|S_RDNORM|S_RDBAND|S_HIPRI|S_HANGUP) < 0) {
    syslog(LOG_ERR, "ioctl(I_SETSIG, S_RDNORM|S_RDBAND|S_HIPRI|S_HANGUP): %m");
    exit(1);
  }
#if	0
  /*
   * How do we get SIGTERMs?
   */
  if (ioctl(fd, TIOCSPGRP, &pid) < 0) {
    syslog(LOG_ERR, "ioctl(TIOCSPGRP): %m");
    exit(1);
  }
#endif

  sa.sa_handler = hup;		/* Hangup */
  sa.sa_mask = mask;
  sa.sa_flags = 0;
  if (sigaction(SIGHUP, &sa, NULL)) {
    syslog(LOG_ERR, "sigaction(SIGHUP)");
    exit(1);
  }
  sa.sa_handler = intr;		/* Interrupt */
  sa.sa_mask = mask;
  sa.sa_flags = 0;
  if (sigaction(SIGINT, &sa, NULL)) {
    syslog(LOG_ERR, "sigaction(SIGINT)");
    exit(1);
  }
  sa.sa_handler = term;		/* Terminate */
  sa.sa_mask = mask;
  sa.sa_flags = 0;
  if (sigaction(SIGTERM, &sa, NULL)) {
    syslog(LOG_ERR, "sigaction(SIGTERM)");
    exit(1);
  }
  sa.sa_handler = alrm;		/* Timeout */
  sa.sa_mask = mask;
  sa.sa_flags = 0;
  if (sigaction(SIGALRM, &sa, NULL)) {
    syslog(LOG_ERR, "sigaction(SIGALRM)");
    exit(1);
  }
  sa.sa_handler = io;			/* Input available */
  sa.sa_mask = mask;
  sa.sa_flags = 0;
  if (sigaction(SIGPOLL, &sa, NULL)) {
    syslog(LOG_ERR, "sigaction(SIGPOLL)");
    exit(1);
  }
  (void) signal(SIGUSR1, incdebug);	/* Increment debug flag */
  (void) signal(SIGUSR2, nodebug);	/* Reset debug flag */

  MAINDEBUG((LOG_INFO, "connect: %s %s", ifname, devname))
  
  
  /*
   * Start opening the connection, and  wait for
   * incoming signals (reply, timeout, etc.).
   */

  lcp_lowerup(0);			/* XXX Well, sort of... */
  if (lcp_wantoptions[0].passive)
    lcp_passiveopen(0);		/* Start protocol in passive mode */
  else
    lcp_activeopen(0);		/* Start protocol in active mode */
  for (;;) {
    sigset_t zmask;
    sigemptyset(&zmask);

    sigsuspend(&zmask);			/* Wait for next signal */
    
    /* Need to read user/passwd? */
    if (upap[0].us_flags & UPAPF_UPPENDING) {
      sigprocmask(SIG_SETMASK, &zmask, NULL);
      getuserpasswd();		/* Get user and passwd */
      upap[0].us_flags &= ~UPAPF_UPPENDING;
      upap[0].us_flags |= UPAPF_UPVALID;
      sigprocmask(SIG_SETMASK, &mask, NULL);
      upap_authwithpeer(0);
    }
  }
}


/*
 * quit - Clean up state and exit.
 */
void 
  quit()
{
#ifdef undef
  /*
   * this is probably not needed and seems to break things.  ifdef'ed
   * out for now - gmc
   */

  /*
   * the I_FLUSH ioctl should return ENXIO if carrier is missing on the 
   * line, but due to a typo, it returns EINVAL instead.  We ignore
   * both.
   */
  if((ioctl(fd, I_FLUSH, FLUSHRW) < 0) &&
     (errno != EINVAL) && (errno != ENXIO)) { 
    syslog(LOG_ERR, "quit ioctl(I_FLUSH,FLUSHRW)");
  }
#endif
  str_restore();
  /* pmg - this shouldn't close here, since on_exit calls cleanup,
   *       which really confuses matters...
   */
  /* close(fd); */
  exit(0);
}


static struct callout *callout = NULL;		/* Callout list */
static struct timeval schedtime;		/* Time last timeout was set */


/*
 * timeout - Schedule a timeout.
 *
 * Note that this timeout takes the number of seconds, NOT hz (as in
 * the kernel).
 */
void timeout(func, arg, time)
     void (*func)();
     caddr_t arg;
     int time;
{
  struct itimerval itv;
  struct callout *newp, **oldpp;
  
  MAINDEBUG((LOG_DEBUG, "Timeout %x:%x in %d seconds.",
	    (int) func, (int) arg, time))
  
  /*
   * Allocate timeout.
   */
  if ((newp = (struct callout *) malloc(sizeof(struct callout))) == NULL) {
    syslog(LOG_ERR, "Out of memory in timeout()!");
    exit(1);
  }
  newp->c_arg = arg;
  newp->c_func = func;
  
  /*
   * Find correct place to link it in and decrement its time by the
   * amount of time used by preceding timeouts.
   */
  for (oldpp = &callout;
       *oldpp && (*oldpp)->c_time <= time;
       oldpp = &(*oldpp)->c_next)
    time -= (*oldpp)->c_time;
  newp->c_time = time;
  newp->c_next = *oldpp;
  if (*oldpp)
    (*oldpp)->c_time -= time;
  *oldpp = newp;
  
  /*
   * If this is now the first callout then we have to set a new
   * itimer.
   */
  if (callout == newp) {
    itv.it_interval.tv_sec = itv.it_interval.tv_usec =
      itv.it_value.tv_usec = 0;
    itv.it_value.tv_sec = callout->c_time;
    MAINDEBUG((LOG_DEBUG, "Setting itimer for %d seconds.",
	      itv.it_value.tv_sec))
    if (setitimer(ITIMER_REAL, &itv, NULL)) {
      syslog(LOG_ERR, "setitimer(ITIMER_REAL)");
      exit(1);
    }
    if (gettimeofday(&schedtime, NULL)) {
      syslog(LOG_ERR, "gettimeofday");
      exit(1);
    }
  }
}


/*
 * untimeout - Unschedule a timeout.
 */
void untimeout(func, arg)
     void (*func)();
     caddr_t arg;
{
  
  struct itimerval itv;
  struct callout **copp, *freep;
  int reschedule = 0;
  
  MAINDEBUG((LOG_DEBUG, "Untimeout %x:%x.", (int) func, (int) arg))
  
  /*
   * If the first callout is unscheduled then we have to set a new
   * itimer.
   */
  if (callout &&
      callout->c_func == func &&
      callout->c_arg == arg)
    reschedule = 1;
  
  /*
   * Find first matching timeout.  Add its time to the next timeouts
   * time.
   */
  for (copp = &callout; *copp; copp = &(*copp)->c_next)
    if ((*copp)->c_func == func &&
	(*copp)->c_arg == arg) {
      freep = *copp;
      *copp = freep->c_next;
      if (*copp)
	(*copp)->c_time += freep->c_time;
      (void) free((char *) freep);
      break;
    }
  
  if (reschedule) {
    itv.it_interval.tv_sec = itv.it_interval.tv_usec =
      itv.it_value.tv_usec = 0;
    itv.it_value.tv_sec = callout ? callout->c_time : 0;
    MAINDEBUG((LOG_DEBUG, "Setting itimer for %d seconds.",
	      itv.it_value.tv_sec))
    if (setitimer(ITIMER_REAL, &itv, NULL)) {
      syslog(LOG_ERR, "setitimer(ITIMER_REAL)");
      exit(1);
    }
    if (gettimeofday(&schedtime, NULL)) {
      syslog(LOG_ERR, "gettimeofday");
      exit(1);
    }
  }
}


/*
 * adjtimeout - Decrement the first timeout by the amount of time since
 * it was scheduled.
 */
void adjtimeout()
{
  struct timeval tv;
  int timediff;
  
  if (callout == NULL)
    return;
  /*
   * Make sure that the clock hasn't been warped dramatically.
   * Account for recently expired, but blocked timer by adding
   * small fudge factor.
   */
  if (gettimeofday(&tv, NULL)) {
    syslog(LOG_ERR, "gettimeofday");
    exit(1);
  }
  timediff = tv.tv_sec - schedtime.tv_sec;
  if (timediff < 0 ||
      timediff > callout->c_time + 1)
    return;
  
  callout->c_time -= timediff;	/* OK, Adjust time */
}


/*
 * output - Output PPP packet.
 */
void
  output(unit, p, len)
int unit;
u_char *p;
int len;
{
  phdr_t *ph;
  u_long dlsap;
  if (unit != 0) {
    MAINDEBUG((LOG_WARNING, "output: unit != 0!"))
    abort();
  }

  ph = (phdr_t *)p;
  p += sizeof(*ph);
  len -= sizeof(*ph);
  dlsap = DP_DLSAP(ph->ph_address, ph->ph_control, ntohs(ph->ph_protocol));

  if (dlunitdatareq(dpfd, &dlsap, sizeof(dlsap), 0, 0, p, len) < 0) {
    MAINDEBUG((LOG_ERR, "dlunitdatareq, %m"));
    exit(1);
  }
  free(p);
}


/*
 * hup - Catch SIGHUP signal.
 *
 * Indicates that the physical layer has been disconnected.
 */
/*ARGSUSED*/
static void
  hup(sig)
int sig;
{
  MAINDEBUG((LOG_NOTICE, "Hangup"))
  adjtimeout();		/* Adjust timeouts */
  if (dialup)
    lcp_close(0);
  else
    lcp_lowerdown(0);		/* Reset connection */
}


/*
 * term - Catch SIGTERM signal.
 *
 * Indicates that we should initiate a graceful disconnect and exit.
 */
/*ARGSUSED*/
static void
  term(sig)
int sig;
{
  syslog(LOG_NOTICE, "Terminate signal received.");
  adjtimeout();		/* Adjust timeouts */
  lcp_close(0);		/* Close connection */
}


/*
 * intr - Catch SIGINT signal (DEL/^C).
 *
 * Indicates that we should initiate a graceful disconnect and exit.
 */
/*ARGSUSED*/
static void
  intr(sig)
int sig;
{
  syslog(LOG_NOTICE, "Interrupt received.  Exiting.");
  adjtimeout();		/* Adjust timeouts */
  lcp_close(0);		/* Close connection */
}


/*
 * alrm - Catch SIGALRM signal.
 *
 * Indicates a timeout.
 */
/*ARGSUSED*/
static void
  alrm(sig)
int sig;
{
  struct itimerval itv;
  struct callout *freep;
  
  MAINDEBUG((LOG_DEBUG, "Alarm"))
  
  /*
   * Call and free first scheduled timeout and any that were scheduled
   * for the same time.
   */
  while (callout) {
    freep = callout;	/* Remove entry before calling */
    callout = freep->c_next;
    (*freep->c_func)(freep->c_arg);
    (void) free((char *) freep);
    if (callout && callout->c_time)
      break;
  }
  
  /*
   * Set a new itimer if there are more timeouts scheduled.
   */
  if (callout) {
    itv.it_interval.tv_sec = itv.it_interval.tv_usec =
      itv.it_value.tv_usec = 0;
    itv.it_value.tv_sec = callout->c_time;
    MAINDEBUG((LOG_DEBUG, "Setting itimer for %d seconds.",
	      itv.it_value.tv_sec))
    if (setitimer(ITIMER_REAL, &itv, NULL)) {
      syslog(LOG_ERR, "setitimer(ITIMER_REAL)");
      exit(1);
    }
    if (gettimeofday(&schedtime, NULL)) {
      syslog(LOG_ERR, "gettimeofday");
      exit(1);
    }
  }
}

#if	0

#define	MAX_SAVE	128

char *malloc_save[MAX_SAVE];
char *free_save[MAX_SAVE];

int nma, nfr;

static
char *
Malloc(s)
uint s;
{
    char *p;

    p = malloc(s);
    malloc_save[nma++] = p;
    return p;
}

static void
Free(p)
char *p;
{
    free_save[nfr++] = p;
    free(p);
}
#endif

/*
 * io - Catch SIGPOLL signal.
 *
 * Indicates that incoming data is available.
 */
/*ARGSUSED*/
static void
io(sig)
int sig;
{
    int len, i;
    u_char *p, *pstart;
    u_short protocol;
    fd_set fdset;
    struct timeval notime;
    int ready;
    ulong dlsap;
    union DL_primitives *dl;
    dl_unitdata_ind_t *dui;
    dl_signal_ind_t *dsi;
    dl_uderror_ind_t *due;
    char ctlbuf[128];
    phdr_t *ph;
    struct pollfd pfd;
    int nmsgs = 0;

    struct strbuf ctl, data;

    MAINDEBUG((LOG_DEBUG, "POLL signal received"))
    adjtimeout();		/* Adjust timeouts */

    /* Yup, this is for real */
    for (;;) {			/* Read all available packets */
	/* we do this to see if the SIGPOLL handler is being invoked for input */
	/* ready, or for the socket buffer hitting the low-water mark. */

	pfd.fd = dpfd;
	pfd.events = POLLRDNORM|POLLRDBAND|POLLPRI;
	pfd.revents = 0;

	if (poll(&pfd, 1, 0) < 0) {
	    syslog(LOG_ERR, "Error in io() poll: %m");
	    exit(1);
	}

	if (!pfd.revents) {
	    if (nmsgs == 0)
		MAINDEBUG((LOG_DEBUG, "IO non-input ready SIGPOLL occured."));
	    return;
	}

	if (pfd.revents & (POLLERR|POLLNVAL)) {
	    syslog(LOG_DEBUG, "io: poll returns %s%s",
	    (pfd.revents & POLLERR)  ? "POLLERR "  : "",
	    (pfd.revents & POLLNVAL) ? "POLLNVAL " : "");
	}

	if (pfd.revents & POLLHUP) {
	    MAINDEBUG((LOG_DEBUG, "io(): POLLHUP"));
	    hup(SIGHUP);
	    return;
	}

	nmsgs++;
	pstart = (u_char *) malloc(DLLHEADERLEN + MTU);
	p = pstart + DLLHEADERLEN;

	ctl.maxlen = sizeof(ctlbuf);
	ctl.buf = ctlbuf;

	data.maxlen = MTU;
	data.buf = (caddr_t)p;

	i = 0;

	if (strgetmsg(dpfd, &ctl, &data, &i, "io") < 0) {
	    if (errno == EAGAIN || errno == EWOULDBLOCK) {
		free(pstart);
		return;
	    }
	    syslog(LOG_ERR, "strgetmsg(dpfd) %m");
	    exit(1);
	}
	MAINDEBUG((LOG_DEBUG, "io(): Received message"));

	if (ctl.len < sizeof(u_long)) {
	    MAINDEBUG((LOG_DEBUG, "io(): getmsg short return length %d",
		       ctl.len))
	    free(pstart);
	    return;
	}

	dl = (union DL_primitives *)ctl.buf;
	switch (dl->dl_primitive) {
	 case DL_UNITDATA_IND:
	    dui = &dl->unitdata_ind;
	    break;

	 case DL_UDERROR_IND:
	    due = &dl->uderror_ind;
	    errno = due->dl_unix_errno;
	    syslog(LOG_ERR, "io(): DL_UDERROR_IND: error 0x%x, %m",
		   due->dl_errno);
	    free(pstart);
	    continue;

	 case DL_SIGNAL_IND:
	    dsi = (dl_signal_ind_t *)dui;
	    if (ctl.len < sizeof(*dsi))
		syslog(LOG_ERR, "io(): DL_SIGNAL_IND: short message");
	    else
		kill(getpid(), dsi->dl_signal);
	    free(pstart);
	    continue;

	 default:
	    syslog(LOG_ERR, "io(): unknown DLPI message: 0x%x",
		   dl->dl_primitive);
	    free(pstart);
	    continue;
	}

	if (ctl.len < sizeof(*dui)) {
	    MAINDEBUG((LOG_DEBUG, "io(): getmsg short return length %d",
		      ctl.len))
	    free(pstart);
	    return;
	}

	if (dui->dl_dest_addr_length != sizeof(u_long)) {
	    MAINDEBUG((LOG_DEBUG,
		      "io(): DL_UNITDATA_IND bad dest address length %d",
		      dui->dl_dest_addr_length))
	    free(pstart);
	    return;
	}

	dlsap = *(ulong *)(((char *)dui) + dui->dl_dest_addr_offset);
	protocol = DP_DLSAP_PROTO(dlsap);

	if (data.len < 0) {
	    MAINDEBUG((LOG_DEBUG, "getmsg short return data length %d",
		       data.len))
	    free(pstart);
	    return;
	}

	len = data.len;

	/*
	* Toss all non-LCP packets unless LCP is OPEN.
	*/
	if (protocol != LCP && lcp_fsm[0].state != OPEN) {
	    MAINDEBUG((LOG_INFO,
		"io(): Received non-LCP packet and LCP is not in open state."))
	    free(pstart);
	    return;
	}

	/*
	 * Upcall the proper protocol input routine.
	 */
	for (i = 0; i < sizeof (prottbl) / sizeof (struct protent); i++)
	    if (prottbl[i].protocol == protocol) {
		(*prottbl[i].input)(0, p, len);
		break;
	    }

	if (i == sizeof (prottbl) / sizeof (struct protent)) {
	    syslog(LOG_WARNING, "input: Unknown protocol (%x) received!",
		   protocol);
	    p -= DLLHEADERLEN;
	    len += DLLHEADERLEN;
	    ph = (phdr_t *)p;
	    ph->ph_address  = DP_DLSAP_ADDR(dlsap);
	    ph->ph_control  = DP_DLSAP_CTL(dlsap);
	    ph->ph_protocol = DP_DLSAP_PROTO(dlsap);
	    lcp_sprotrej(0, p, len);
	}
	free(pstart);
    }
}

/*
 * cleanup - clean_up before we exit
 */
/* ARGSUSED */
static void
  cleanup()
{
  adjtimeout();
  lcp_lowerdown(0);
  str_restore();
  if (unlink(pidfilename) < 0) 
    syslog(LOG_WARNING, "unable to unlink pid file: %m");
}

/*
 * demuxprotrej - Demultiplex a Protocol-Reject.
 */
void
  demuxprotrej(unit, protocol)
int unit;
u_short protocol;
{
  int i;
  
  /*
   * Upcall the proper Protocol-Reject routine.
   */
  for (i = 0; i < sizeof (prottbl) / sizeof (struct protent); i++)
    if (prottbl[i].protocol == protocol) {
      (*prottbl[i].protrej)(unit);
      return;
    }
  syslog(LOG_WARNING, "demuxprotrej: Unrecognized Protocol-Reject for protocol %d!", protocol);
}


/*
 * incdebug - Catch SIGUSR1 signal.
 *
 * Increment debug flag.
 */
/*ARGSUSED*/
static void
  incdebug(sig)
int sig;
{
  MAINDEBUG((LOG_NOTICE, "Debug turned ON, Level %d",debug))
  setlogmask(LOG_UPTO(LOG_DEBUG));
  debug++;
}


/*
 * nodebug - Catch SIGUSR2 signal.
 *
 * Turn off debugging.
 */
/*ARGSUSED*/
static void
  nodebug(sig)
int sig;
{
  setlogmask(LOG_UPTO(LOG_WARNING));
  debug = 0;
}


/*
 * setdebug - Set debug (command line argument).
 */
static int
  setdebug(argcp, argvp)
int *argcp;
char ***argvp;
{
  debug++;
  setlogmask(LOG_UPTO(LOG_DEBUG));
  --*argcp, ++*argvp;
  return (1);
}

/*
 * noopt - Disable all options.
 */
static int
  noopt(argcp, argvp)
int *argcp;
char ***argvp;
{
  memset((char *) &lcp_wantoptions[0],   0, sizeof (struct lcp_options));
  memset((char *) &lcp_allowoptions[0],  0, sizeof (struct lcp_options));
  memset((char *) &ipcp_wantoptions[0],  0, sizeof (struct ipcp_options));
  memset((char *) &ipcp_allowoptions[0], 0, sizeof (struct ipcp_options));
  --*argcp, ++*argvp;
  return (1);
}


/*
 * setdialup - Set dialup (command line argument).
 */
static int
  setdialup(argcp, argvp)
int *argcp;
char ***argvp;
{
  dialup++;
  --*argcp, ++*argvp;
  return (1);
}


/*
 * setconnector - Set a program to connect to a serial line
 */
static int
  setconnector(argcp, argvp)
int *argcp;
char ***argvp;
{
  --*argcp, ++*argvp;
  
  connector = strdup(**argvp);
  if (connector == NULL) {
    syslog(LOG_ERR, "cannot allocate space for connector string");
    exit(1);
  }
  
  --*argcp, ++*argvp;
  return (1);
}


/*
 * set_up_connection - run a program to initialize the serial connector
 */
int set_up_connection(program, in, out)
     char *program;
     int in, out;
{
  int pid;
  sigset_t mask;
  int status;
  
  sigemptyset(&mask);
  sigaddset(&mask, SIGINT);
  sigaddset(&mask, SIGHUP);
  pid = fork();
  
  if (pid < 0) {
    syslog(LOG_ERR, "fork");
    exit(1);
  }
  
  if (pid == 0) {
    (void) setuid(getuid());
    (void) setgid(getgid());
    (void) sigprocmask(SIG_SETMASK, &mask, NULL);
    (void) dup2(in, 0);
    (void) dup2(out, 1);
    (void) execl("/bin/sh", "sh", "-c", program, (char *)0);
    syslog(LOG_ERR, "could not exec /bin/sh");
    _exit(99);
  }
  else {
    while (waitpid(pid, &status, 0) != pid) {
      if (errno == EINTR)
	continue;
      syslog(LOG_ERR, "waiting for connection process");
      exit(1);
    }
    (void) sigprocmask(SIG_SETMASK, &mask, NULL);
  }
  return (status == 0 ? 0 : -1);
}

/*
 * noaccomp - Disable Address/Control field compression negotiation.
 */
static int
  noaccomp(argcp, argvp)
int *argcp;
char ***argvp;
{
  lcp_wantoptions[0].neg_accompression = 0;
  lcp_allowoptions[0].neg_accompression = 0;
  --*argcp, ++*argvp;
  return (1);
}


/*
 * noasyncmap - Disable async map negotiation.
 */
static int
  noasyncmap(argcp, argvp)
int *argcp;
char ***argvp;
{
  lcp_wantoptions[0].neg_asyncmap = 0;
  lcp_allowoptions[0].neg_asyncmap = 0;
  --*argcp, ++*argvp;
  return (1);
}


/*
 * noipaddr - Disable IP address negotiation.
 */
static int
  noipaddr(argcp, argvp)
int *argcp;
char ***argvp;
{
  ipcp_wantoptions[0].neg_addrs = 0;
  ipcp_allowoptions[0].neg_addrs = 0;
  --*argcp, ++*argvp;
  return (1);
}


/*
 * nomagicnumber - Disable magic number negotiation.
 */
static int
  nomagicnumber(argcp, argvp)
int *argcp;
char ***argvp;
{
  lcp_wantoptions[0].neg_magicnumber = 0;
  lcp_allowoptions[0].neg_magicnumber = 0;
  --*argcp, ++*argvp;
  return (1);
}


/*
 * nomru - Disable mru negotiation.
 */
static int
  nomru(argcp, argvp)
int *argcp;
char ***argvp;
{
  lcp_wantoptions[0].neg_mru = 0;
  lcp_allowoptions[0].neg_mru = 0;
  --*argcp, ++*argvp;
  return (1);
}

/*
 * setmru - Set MRU for negotiation.
 */
static int
  setmru(argcp, argvp)
int *argcp;
char ***argvp;
{
  --*argcp, ++*argvp;
  lcp_wantoptions[0].mru = atoi(**argvp);
  --*argcp, ++*argvp;
  return (1);
}


/*
 * nopcomp - Disable Protocol field compression negotiation.
 */
static int
  nopcomp(argcp, argvp)
int *argcp;
char ***argvp;
{
  lcp_wantoptions[0].neg_pcompression = 0;
  lcp_allowoptions[0].neg_pcompression = 0;
  --*argcp, ++*argvp;
  return (1);
}


/*
 * setpassive - Set passive mode.
 */
static int
  setpassive(argcp, argvp)
int *argcp;
char ***argvp;
{
  lcp_wantoptions[0].passive = 1;
  --*argcp, ++*argvp;
  return (1);
}


/*
 * noupap - Disable UPAP authentication.
 */
static int
  noupap(argcp, argvp)
int *argcp;
char ***argvp;
{
  lcp_allowoptions[0].neg_upap = 0;
  --*argcp, ++*argvp;
  return (1);
}


/*
 * requpap - Require UPAP authentication.
 */
static int
  requpap(argcp, argvp)
int *argcp;
char ***argvp;
{
  FILE * ufile;
  struct stat sbuf;

  lcp_wantoptions[0].neg_upap = 1;
  lcp_allowoptions[0].neg_upap = 0;
  --*argcp, ++*argvp;
  strcpy(uinfopath, **argvp);
  --*argcp, ++*argvp;

  /* open user info file */

  if ((ufile = fopen(uinfopath, "r")) == NULL) {
    fprintf(stderr,  "unable to open user login data file %s\n", uinfopath);
    exit(1);
  };
     
  if (fstat(fileno(ufile), &sbuf) < 0) {
    perror("cannot stat user login data file!");
    exit(1);
  }
  if ((sbuf.st_mode & 077) != 0)
    syslog(LOG_WARNING, "Warning - user info file has world and/or group access!\n");

  /* get username */
  fgets(user, sizeof (user) - 1, ufile);
  if (strlen(user) == 0) {
    fprintf(stderr, "Unable to get user name from user login data file.\n");
    exit(2);
  }
  /* get rid of newline */
  user[strlen(user) - 1] = '\000';

  fgets(passwd, sizeof(passwd) - 1, ufile);

  if (strlen(passwd) == 0) {
    fprintf(stderr, "Unable to get password from user login data file.\n");
    exit(2);
  }

  passwd[strlen(passwd) - 1] = '\000';

  return (1);
}

/*
 * setunit - Set Unit
 */
static int
  setunit(argcp, argvp)
int *argcp;
char ***argvp;
{
    --*argcp, ++*argvp;
    ifunit = atoi(**argvp);
    --*argcp, ++*argvp;
    return (1);
}

/*
 * nochap - Disable CHAP authentication.
 */
static int
  nochap(argcp, argvp)
int *argcp;
char ***argvp;
{
  lcp_allowoptions[0].neg_chap = 0;
  --*argcp, ++*argvp;
  return (1);
}


/*
 * reqchap - Require CHAP authentication.
 */
static int
  reqchap(argcp, argvp)
int *argcp;
char ***argvp;
{
  lcp_wantoptions[0].neg_chap = 1;
  lcp_allowoptions[0].neg_chap = 0;
    ifunit = atoi(**argvp);
  --*argcp, ++*argvp;
  return (1);
}

/*
 *	setaddrmode - Set address negotiation mode
 */

static int
  setaddrmode(argcp, argvp)
int *argcp;
char ***argvp;
{
  extern int ipcp_negaddr_mode;
  
  --*argcp, ++*argvp;
  
  if (!strcmp(**argvp, "old")) {	/* "old" mode */
    ipcp_negaddr_mode = IPCP_NEGADDR_OLD;
  }
  
  else if (!strcmp(**argvp, "draft")) {	/* "draft" mode */
    ipcp_negaddr_mode = IPCP_NEGADDR_NEW;
  }
  else {
    MAINDEBUG((LOG_WARNING, "Unknown address negotiation mode %s. Defaulting to draft.", **argvp))
    ipcp_negaddr_mode = IPCP_NEGADDR_NEW;
  }
  --*argcp, ++*argvp;
  
  return (1);
}

/*
 *	setvjmode - Set vj compression mode
 */

static int
  setvjmode(argcp, argvp)
int *argcp;
char ***argvp;
{
  extern int ipcp_vj_mode;
  
  --*argcp, ++*argvp;
  
  if (!strcmp(**argvp, "old")) {	/* "old" mode */
    ipcp_vj_setmode(IPCP_VJMODE_OLD);
  }
  
  else if (!strcmp(**argvp, "current")) {	/* "current" mode (default)*/
    ipcp_vj_setmode(IPCP_VJMODE_CURRENT);
  }
  
  else if (!strcmp(**argvp, "draft")) {	/* "draft" mode */
    ipcp_vj_setmode(IPCP_VJMODE_DRAFT);
  }
  else {
    MAINDEBUG((LOG_WARNING, "Unknown vj compression mode %s. Defaulting to current.", **argvp))
    ipcp_vj_setmode(IPCP_VJMODE_CURRENT);
  }
  --*argcp, ++*argvp;
  
  return (1);
}
/*
 *	setnovj - diable vj compression
 */

static int
  setnovj(argcp, argvp)
int *argcp;
char ***argvp;
{
  extern int ipcp_vj_mode;
  
  --*argcp, ++*argvp;
  ipcp_wantoptions[0].neg_vj = 0;
  ipcp_allowoptions[0].neg_vj = 0;
  
  return (1);
}

/*
 * setdomain - Set domain name to append to hostname 
 */
static int
  setdomain(argcp, argvp)
int *argcp;
char ***argvp;
{
  
  --*argcp, ++*argvp;

  strcat(hostname, **argvp);
  hostname_len = strlen(hostname);

  --*argcp, ++*argvp;

  return (1);
}

/*
 * Valid speeds.
 */
struct speed {
  int speed_int, speed_val;
} speeds[] = {
#ifdef B50
  { 50, B50 },
#endif
#ifdef B75
  { 75, B75 },
#endif
#ifdef B110
  { 110, B110 },
#endif
#ifdef B150
  { 150, B150 },
#endif
#ifdef B200
  { 200, B200 },
#endif
#ifdef B300
  { 300, B300 },
#endif
#ifdef B600
  { 600, B600 },
#endif
#ifdef B1200
  { 1200, B1200 },
#endif
#ifdef B1800
  { 1800, B1800 },
#endif
#ifdef B2000
  { 2000, B2000 },
#endif
#ifdef B2400
  { 2400, B2400 },
#endif
#ifdef B3600
  { 3600, B3600 },
#endif
#ifdef B4800
  { 4800, B4800 },
#endif
#ifdef B7200
  { 7200, B7200 },
#endif
#ifdef B9600
  { 9600, B9600 },
#endif
#ifdef EXTA
  { 19200, EXTA },
#endif
#ifdef EXTB
  { 38400, EXTB },
#endif
  { 0, 0 }
};

static int
  setasyncmap(argcp, argvp) 
int	*argcp;
char	***argvp;
{
  unsigned long asyncmap;
  
  asyncmap = 0xffffffff;
  ++*argvp;
  sscanf(**argvp,"%lx",&asyncmap);
  ++*argvp;
  lcp_wantoptions[0].asyncmap = asyncmap;
  *argcp -= 2;
  return(1);
}

/*
 * setspeed - Set the speed.
 */
static int
  setspeed(argcp, argvp)
int *argcp;
char ***argvp;
{
  int speed;
  struct speed *speedp;
  
  speed = atoi(**argvp);
  for (speedp = speeds; speedp->speed_int; speedp++)
    if (speed == speedp->speed_int) {
      inspeed = speedp->speed_val;
      --*argcp, ++*argvp;
      return (1);
    }
  return (0);
}


/*
 * setdevname - Set the device name.
 */
int setdevname(argcp, argvp)
     int *argcp;
     char ***argvp;
{
  char dev[DEVNAME_SIZE];
  char *cp = **argvp;
  struct stat statbuf;
  char *tty, *ttyname();
  
  if (strncmp("/dev/", cp, sizeof ("/dev/") - 1)) {
    (void) sprintf(dev, "/dev/%s", cp);
    cp = dev;
  }
  
  /*
   * Check if there is a device by this name.
   */
  if (stat(cp, &statbuf) < 0) {
    if (errno == ENOENT)
      return (0);
    syslog(LOG_ERR, cp);
    exit(1);
  }
  
  (void) strcpy(devname, cp);
  default_device = FALSE;
  --*argcp, ++*argvp;
  
  /*
   * If we haven't already decided to require authentication,
   * or we are running ppp on the control terminal, then we can
   * allow authentication to be requested.
   */
  if ((tty = ttyname(fileno(stdin))) == NULL)
    tty = ""; /* running from init means no stdin.  Null kills strcmp -KWK */
  if (lcp_wantoptions[0].neg_upap == 0 &&
      strcmp(devname, "/dev/tty") &&
      strcmp(devname, tty)) {
    lcp_wantoptions[0].neg_upap = 0;
    lcp_allowoptions[0].neg_upap = 1;
  }
  return (1);
}


/*
 * setipaddr - Set the IP address
 */
int setipaddr(argcp, argvp)
     int *argcp;
     char ***argvp;
{
  extern int ipcp_negaddr_mode;
  u_long local, remote;
  struct hostent *hp;
  char *colon;
  
  /*
   * IP address pair separated by ":".
   */
  if ((colon = strchr(**argvp, ':')) == NULL)
    return (0);
  
  /*
   * If colon first character, then no local addr.
   */
  if (colon == **argvp) {
    local = 0l;
    ++colon;
  }
  else {
    *colon++ = '\0';
    if ((local = inet_addr(**argvp)) == -1) {
      if ((hp = gethostbyname(**argvp)) == NULL) {
	syslog(LOG_WARNING, "unknown host: %s", **argvp);
	goto ret;
      }
      memcpy((char *) &local, hp->h_addr, hp->h_length);
    }
  }
  
  /*
   * If colon last character, then no remote addr.
   */
  if (*colon == '\0')
    remote = 0l;
  else {
    if ((remote = inet_addr(colon)) == -1) {
      if ((hp = gethostbyname(colon)) == NULL) {
	syslog(LOG_WARNING,"unknown host: %s", colon);
	goto ret;
      }
      memcpy((char *) &remote, hp->h_addr, hp->h_length);
    }
  }
  
  ipcp_wantoptions[0].neg_addrs = ipcp_negaddr_mode;
  ipcp_wantoptions[0].ouraddr = local;
  ipcp_wantoptions[0].hisaddr = remote;
  
 ret:
  --*argcp, ++*argvp;
  return (1);
}

static int
  setnetmask(argcp, argvp)
int *argcp;
char ***argvp;
{
  u_long mask;
	
  --*argcp, ++*argvp;
  if ((mask = inet_addr(**argvp)) == -1) {
    fprintf(stderr, "Invalid netmask %s\n", **argvp);
    exit(1);
  }

  netmask = mask;
  --*argcp, ++*argvp;
  return (1);
}

/*
 * getuserpasswd - Get the user name and passwd.
 */
static void
  getuserpasswd()
{
  upap[0].us_user = user;
  upap[0].us_userlen = strlen(upap[0].us_user);

  upap[0].us_passwd = passwd;
  upap[0].us_passwdlen = strlen(upap[0].us_passwd);
}


/*
 * login - Check the user name and passwd and login the user.
 *
 * returns:
 *	UPAP_AUTHNAK: Login failed.
 *	UPAP_AUTHACK: Login succeeded.
 * In either case, msg points to an appropriate message.
 */
u_char
  login(user, userlen, passwd, passwdlen, msg, msglen)
char *user;
int userlen;
char *passwd;
int passwdlen;
char **msg;
int *msglen;
{
  struct passwd *pw;
  char *epasswd, *crypt();
  static int attempts = 0;
  char *tty;
  char *tmp_passwd, *tmp_user;
  
  /* why alloca.h doesn't define what alloca() returns is a mystery */

#ifdef sparc
  void *__builtin_alloca __ARGS((int));
#else
  void *alloca __ARGS((int));
#endif /*sparc*/
  tmp_passwd = (char *)alloca(passwdlen + 1);	/* we best make copies before */
  /* null terminating the string */ 
  if (tmp_passwd == NULL) {
    syslog(LOG_ERR, "alloca failed");
    exit(1);
  }
  memcpy(tmp_passwd, passwd, passwdlen);
  tmp_passwd[passwdlen] = '\0';
  
  tmp_user = alloca(userlen + 1);
  if (tmp_user == NULL) {
    syslog(LOG_ERR, "alloca failed");
    exit(1);
  }
  memcpy(tmp_user, user, userlen);
  tmp_user[userlen] = '\0';
  
  if ((pw = getpwnam(tmp_user)) == NULL) {
    *msg = "Login incorrect";
    *msglen = strlen(*msg);
    syslog(LOG_WARNING, "upap login userid '%s' incorrect",tmp_user);
    return (UPAP_AUTHNAK);
  }
  
  /*
   * XXX If no passwd, let them login without one.
   */
  if (pw->pw_passwd == '\0') {
    *msg = "Login ok";
    *msglen = strlen(*msg);
    return (UPAP_AUTHACK);
  }
  
  epasswd = crypt(tmp_passwd, pw->pw_passwd);
  if (strcmp(epasswd, pw->pw_passwd)) {
    *msg = "Login incorrect";
    *msglen = strlen(*msg);
    syslog(LOG_WARNING, "upap login password '%s' incorrect", tmp_passwd);
    /*
     * Frustrate passwd stealer programs.
     * Allow 10 tries, but start backing off after 3 (stolen from login).
     * On 10'th, drop the connection.
     */
    if (attempts++ >= 10) {
      syslog(LOG_WARNING, "%d LOGIN FAILURES ON %s, %s",
	     attempts, devname, tmp_user);
      lcp_close(0);		/* Drop DTR? */
    }
    if (attempts > 3)
      sleep((u_int) (attempts - 3) * 5);
    return (UPAP_AUTHNAK);
  }
  
  attempts = 0;			/* Reset count */
  *msg = "Login ok";
  *msglen = strlen(*msg);
  syslog(LOG_NOTICE, "user %s logged in", tmp_user);
  tty = strrchr(devname, '/');
  if (tty == NULL)
    tty = devname;
  else
    tty++;
  logwtmp(tty, tmp_user, "");		/* Add wtmp login entry */
  
  return (UPAP_AUTHACK);
}


/*
 * logout - Logout the user.
 */
void logout()
{
  char *tty;
  
  tty = strrchr(devname, '/');
  if (tty == NULL)
    tty = devname;
  else
    tty++;
  logwtmp(tty, "", "");		/* Add wtmp logout entry */
}


/*
 * getuseropt - Get the options from /etc/hosts.ppp for this user.
 */
int getuseropt(user)
     char *user;
{
  char buf[1024], *s;
  FILE *fp;
  int rc = 0;
  
  if ((fp = fopen(PPPHOSTS, "r")) == NULL)
    return (0);;
  
  /*
   * Loop till we find an entry for this user.
   */
  for (;;) {
    if (fgets(buf, sizeof (buf), fp) == NULL) {
      if (feof(fp))
	break;
      else {
	syslog(LOG_ERR, "fgets");
	exit(1);
      }
    }
    if ((s = strchr(buf, ' ')) == NULL)
      continue;
    *s++ = '\0';
    if (!strcmp(user, buf)) {
      rc = 1;
      break;
    }
  }
  fclose(fp);
  return (rc);
}
/*
 * open "secret" file and return the secret matching the given name.
 * If no secret for a given name is found, use the one for "default".
 */

void
  get_secret(name, secret, secret_len)
u_char * name;
u_char * secret;
int * secret_len;
{
  FILE * sfile;
  struct stat sbuf;
  u_char fname[256];
  int match_found, default_found;

  match_found = FALSE;
  default_found = FALSE;

  if ((sfile = fopen(_PATH_CHAPFILE, "r")) == NULL) {
    syslog(LOG_ERR, "unable to open secret file %s", _PATH_CHAPFILE);
    exit(1);
  };
     
  if (fstat(fileno(sfile), &sbuf) < 0) {
    syslog(LOG_ERR, "cannot stat secret file!: %m");
    exit(1);
  }
  if ((sbuf.st_mode & 077) != 0)
    syslog(LOG_WARNING, "Warning - secret file has world and/or group access!");

  while (!feof(sfile) && !match_found) {
    if (fscanf(sfile, "%s %s", fname, secret) == EOF)
      break;
    if (!strcasecmp(fname, name)) {
      match_found = TRUE;
    }
    if (!strcasecmp("default", name)) {
      default_found = TRUE;
    }
  }

  if (!match_found && !default_found)  {
    syslog(LOG_ERR, "No match or default entry found for %s in CHAP secret file! Aborting...", name);
    cleanup();		/* shut us down */
  }
#ifdef UNSECURE
/* while this is useful for debugging, it is a security hole as well */

    syslog(LOG_DEBUG, "get_secret: found secret %s", secret);
#endif /*UNSECURE*/
  fclose(sfile);
  *secret_len = strlen((char *)secret);
  if (*secret_len > MAX_SECRET_LEN) { /* don't let it overflow the buffer */
    syslog(LOG_ERR, "Length of secret for host %s is greater than the maximum %d characters! ", name, MAX_SECRET_LEN);
    cleanup();			/* scream and die */
  }
  return;
}
/*
 * Return user specified netmask. A value of zero means no netmask has
 * been set. 
 */
/* ARGSUSED */
u_long
  GetMask(addr)
u_long addr;
{
  return(netmask);
}
