options.c   [plain text]


/*
 * options.c - handles option processing for PPP.
 *
 * 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.
 */

#define RCSID	"$Id: options.c,v 1.3 2002/05/23 01:09:19 callie Exp $"

#include <ctype.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <termios.h>
#include <syslog.h>
#include <string.h>
#include <netdb.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifdef PLUGIN
#ifdef __APPLE__
#else
#include <dlfcn.h>
#endif
#endif
#ifdef PPP_FILTER
#include <pcap.h>
#include <pcap-int.h>	/* XXX: To get struct pcap */
#endif

#include "pppd.h"
#include "pathnames.h"
#include "patchlevel.h"
#include "fsm.h"
#include "lcp.h"
#include "ipcp.h"
#include "upap.h"
#include "chap.h"
#include "ccp.h"

//#include <net/ppp-comp.h>
#include <ppp_comp.h>

#if defined(ultrix) || defined(NeXT)
char *strdup __P((char *));
#endif

static const char rcsid[] = RCSID;

/*
 * Option variables and default values.
 */
#ifdef PPP_FILTER
int	dflag = 0;		/* Tell libpcap we want debugging */
#endif
int	debug = 0;		/* Debug flag */
int	kdebugflag = 0;		/* Tell kernel to print debug messages */
int	default_device = 1;	/* Using /dev/tty or equivalent */
char	devnam[MAXPATHLEN];	/* Device name */
u_int32_t netmask = 0;		/* IP netmask to set on interface */
bool	nodetach = 0;		/* Don't detach from controlling tty */
bool	updetach = 0;		/* Detach once link is up */
int	maxconnect = 0;		/* Maximum connect time */
char	user[MAXNAMELEN];	/* Username for PAP */
char	passwd[MAXSECRETLEN];	/* Password for PAP */
bool	persist = 0;		/* Reopen link after it goes down */
char	our_name[MAXNAMELEN];	/* Our name for authentication purposes */
bool	demand = 0;		/* do dial-on-demand */
char	*ipparam = NULL;	/* Extra parameter for ip up/down scripts */
int	idle_time_limit = 0;	/* Disconnect if idle for this many seconds */
int	holdoff = 30;		/* # seconds to pause before reconnecting */
bool	holdoff_specified;	/* true if a holdoff value has been given */
int	log_to_fd = 1;		/* send log messages to this fd too */
int	maxfail = 10;		/* max # of unsuccessful connection attempts */
char	linkname[MAXPATHLEN];	/* logical name for link */
bool	tune_kernel;		/* may alter kernel settings */
int	connect_delay = 1000;	/* wait this many ms after connect script */
int	req_unit = -1;		/* requested interface unit */
bool	multilink = 0;		/* Enable multilink operation */
char	*bundle_name = NULL;	/* bundle name for multilink */
#ifdef __APPLE__
char 	*device = NULL; 	/* device we are using (can be use as a generic device container) */
char 	*remoteaddress = NULL; 	/* remoteaddress we are connecting to (can be use as a generic address container) */
char 	*altremoteaddress = NULL; /* alternate remoteaddress we are connecting to */
int 	redialcount = 0;	/* number of time to redial */
int 	redialtimer = 30;	/* delay in seconds to wait before to redial */
bool 	redialalternate = 0;  	/* do we redial alternate number */
int 	busycode = -1;		/* busy error code that triggers the redial */
int 	cancelcode = -1;	/* cancel error code for connectors */
#endif

extern option_t auth_options[];
extern struct stat devstat;

struct option_info initializer_info;
struct option_info connect_script_info;
struct option_info disconnect_script_info;
#ifdef __APPLE__
struct option_info altconnect_script_info;
struct option_info terminal_script_info;
#endif
struct option_info welcomer_info;
struct option_info devnam_info;
struct option_info ptycommand_info;

#ifdef PPP_FILTER
struct	bpf_program pass_filter;/* Filter program for packets to pass */
struct	bpf_program active_filter; /* Filter program for link-active pkts */
pcap_t  pc;			/* Fake struct pcap so we can compile expr */
#endif

char *current_option;		/* the name of the option being parsed */
int  privileged_option;		/* set iff the current option came from root */
char *option_source;		/* string saying where the option came from */
bool log_to_file;		/* log_to_fd is a file opened by us */
bool log_to_specific_fd;	/* log_to_fd was specified by user option */
bool no_override;		/* don't override previously-set options */

/*
 * Prototypes
 */
static int setdevname __P((char *));
static int setipaddr __P((char *));
static int setspeed __P((char *));
static int noopt __P((char **));
static int setdomain __P((char **));
static int setnetmask __P((char **));
static int readfile __P((char **));
static int callfile __P((char **));
static int showversion __P((char **));
static int showhelp __P((char **));
static void usage __P((void));
static int setlogfile __P((char **));
#ifdef PLUGIN
static int loadplugin __P((char **));
#endif

#ifdef PPP_FILTER
static int setpassfilter __P((char **));
static int setactivefilter __P((char **));
#endif

static option_t *find_option __P((char *name));
static int process_option __P((option_t *, char **));
static int n_arguments __P((option_t *));
static int number_option __P((char *, u_int32_t *, int));

/*
 * Structure to store extra lists of options.
 */
struct option_list {
    option_t *options;
    struct option_list *next;
};

static struct option_list *extra_options = NULL;

/*
 * Valid arguments.
 */
option_t general_options[] = {
    { "debug", o_int, &debug,
      "Increase debugging level", OPT_INC|OPT_NOARG|1 },
    { "-d", o_int, &debug,
      "Increase debugging level", OPT_INC|OPT_NOARG|1 },
    { "kdebug", o_int, &kdebugflag,
      "Set kernel driver debug level" },
    { "nodetach", o_bool, &nodetach,
      "Don't detach from controlling tty", 1 },
    { "-detach", o_bool, &nodetach,
      "Don't detach from controlling tty", 1 },
    { "updetach", o_bool, &updetach,
      "Detach from controlling tty once link is up", 1 },
    { "holdoff", o_int, &holdoff,
      "Set time in seconds before retrying connection" },
    { "idle", o_int, &idle_time_limit,
      "Set time in seconds before disconnecting idle link" },
    { "-all", o_special_noarg, (void *)noopt,
      "Don't request/allow any LCP or IPCP options (useless)" },
    { "maxconnect", o_int, &maxconnect,
      "Set connection time limit", OPT_LLIMIT|OPT_NOINCR|OPT_ZEROINF },
    { "domain", o_special, (void *)setdomain,
      "Add given domain name to hostname" },
    { "mtu", o_int, &lcp_allowoptions[0].mru,
      "Set our MTU", OPT_LIMITS, NULL, MAXMRU, MINMRU },
    { "netmask", o_special, (void *)setnetmask,
      "set netmask" },
    { "file", o_special, (void *)readfile,
      "Take options from a file", OPT_PREPASS },
    { "call", o_special, (void *)callfile,
      "Take options from a privileged file", OPT_PREPASS },
    { "persist", o_bool, &persist,
      "Keep on reopening connection after close", 1 },
    { "nopersist", o_bool, &persist,
      "Turn off persist option" },
    { "demand", o_bool, &demand,
      "Dial on demand", OPT_INITONLY | 1, &persist },
    { "--version", o_special_noarg, (void *)showversion,
      "Show version number" },
    { "--help", o_special_noarg, (void *)showhelp,
      "Show brief listing of options" },
    { "-h", o_special_noarg, (void *)showhelp,
      "Show brief listing of options" },
    { "logfd", o_int, &log_to_fd,
      "Send log messages to this file descriptor",
      0, &log_to_specific_fd },
    { "logfile", o_special, (void *)setlogfile,
      "Append log messages to this file" },
    { "nolog", o_int, &log_to_fd,
      "Don't send log messages to any file",
      OPT_NOARG | OPT_VAL(-1) },
    { "nologfd", o_int, &log_to_fd,
      "Don't send log messages to any file descriptor",
      OPT_NOARG | OPT_VAL(-1) },
    { "linkname", o_string, linkname,
      "Set logical name for link",
      OPT_PRIV|OPT_STATIC, NULL, MAXPATHLEN },
    { "maxfail", o_int, &maxfail,
      "Maximum number of unsuccessful connection attempts to allow" },
    { "ktune", o_bool, &tune_kernel,
      "Alter kernel settings as necessary", 1 },
    { "noktune", o_bool, &tune_kernel,
      "Don't alter kernel settings", 0 },
    { "connect-delay", o_int, &connect_delay,
      "Maximum time (in ms) to wait after connect script finishes" },
    { "unit", o_int, &req_unit,
      "PPP interface unit number to use if possible", OPT_LLIMIT, 0, 0 },
#ifdef __APPLE__
    { "device", o_string, &device,
      "Device we are using"},
    { "remoteaddress", o_string, &remoteaddress,
      "Remote address we are connecting to"},
    { "altremoteaddress", o_string, &altremoteaddress,
      "Alternate remote address we are connecting to"},
    { "redialcount", o_int, &redialcount,
      "Number of times to redial" },
    { "redialtimer", o_int, &redialtimer,
      "Delay in seconds to wait before to redial"},
    { "redialalternate", o_bool, &redialalternate,
      "Redial also atlernate number", 1},
    { "busycode", o_int, &busycode,
      "Busy signal error code that will trigger the redial"},
    { "cancelcode", o_int, &cancelcode,
      "Cancel error code that for connectors"},
#endif
#ifdef HAVE_MULTILINK
    { "multilink", o_bool, &multilink,
      "Enable multilink operation", 1 },
    { "nomultilink", o_bool, &multilink,
      "Disable multilink operation", 0 },
    { "mp", o_bool, &multilink,
      "Enable multilink operation", 1 },
    { "nomp", o_bool, &multilink,
      "Disable multilink operation", 0 },
    { "bundle", o_string, &bundle_name,
      "Bundle name for multilink" },
#endif /* HAVE_MULTILINK */
#ifdef PLUGIN
    { "plugin", o_special, (void *)loadplugin,
      "Load a plug-in module into pppd", OPT_PRIV },
#endif

#ifdef PPP_FILTER
    { "pdebug", o_int, &dflag,
      "libpcap debugging" },
    { "pass-filter", 1, setpassfilter,
      "set filter for packets to pass" },
    { "active-filter", 1, setactivefilter,
      "set filter for active pkts" },
#endif

    { NULL }
};

#ifndef IMPLEMENTATION
#define IMPLEMENTATION ""
#endif

static char *usage_string = "\
pppd version %s.%d%s\n\
Usage: %s [ options ], where options are:\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\
	asyncmap <n>	Set the desired async map to hex <n>\n\
	auth		Require authentication from peer\n\
        connect <p>     Invoke shell command <p> to set up the serial line\n\
	crtscts		Use hardware RTS/CTS flow control\n\
	defaultroute	Add default route through interface\n\
	file <f>	Take options from file <f>\n\
	modem		Use modem control lines\n\
	mru <n>		Set MRU value to <n> for negotiation\n\
See pppd(8) for more options.\n\
";

/*
 * parse_args - parse a string of arguments from the command line.
 */
int
parse_args(argc, argv)
    int argc;
    char **argv;
{
    char *arg;
    option_t *opt;
    int ret;

    privileged_option = privileged;
    option_source = "command line";
    while (argc > 0) {
	arg = *argv++;
	--argc;

	/*
	 * First see if it's an option in the new option list.
	 */
	opt = find_option(arg);
	if (opt != NULL) {
	    int n = n_arguments(opt);
	    if (argc < n) {
		option_error("too few parameters for option %s", arg);
		return 0;
	    }
	    current_option = arg;
	    if (!process_option(opt, argv))
		return 0;
	    argc -= n;
	    argv += n;
	    continue;
	}

	/*
	 * Maybe a tty name, speed or IP address?
	 */
	if ((ret = setdevname(arg)) == 0
	    && (ret = setspeed(arg)) == 0
	    && (ret = setipaddr(arg)) == 0) {
	    option_error("unrecognized option '%s'", arg);
	    usage();
	    return 0;
	}
	if (ret < 0)	/* error */
	    return 0;
    }
    return 1;
}

#if 0
/*
 * scan_args - scan the command line arguments to get the tty name,
 * if specified.  Also checks whether the notty or pty option was given.
 */
void
scan_args(argc, argv)
    int argc;
    char **argv;
{
    char *arg;
    option_t *opt;

    privileged_option = privileged;
    while (argc > 0) {
	arg = *argv++;
	--argc;

	if (strcmp(arg, "notty") == 0 || strcmp(arg, "pty") == 0)
	    using_pty = 1;

	/* Skip options and their arguments */
	opt = find_option(arg);
	if (opt != NULL) {
	    int n = n_arguments(opt);
	    argc -= n;
	    argv += n;
	    continue;
	}

	/* Check if it's a tty name and copy it if so */
	(void) setdevname(arg, 1);
    }
}
#endif

/*
 * options_from_file - Read a string of options from a file,
 * and interpret them.
 */
int
options_from_file(filename, must_exist, check_prot, priv)
    char *filename;
    int must_exist;
    int check_prot;
    int priv;
{
    FILE *f;
    int i, newline, ret, err;
    option_t *opt;
    int oldpriv;
    char *oldsource;
    char *argv[MAXARGS];
    char args[MAXARGS][MAXWORDLEN];
    char cmd[MAXWORDLEN];
#ifdef __APPLE__
    char *tofree;
#endif

    if (check_prot)
	seteuid(getuid());
    f = fopen(filename, "r");
    err = errno;
    if (check_prot)
	seteuid(0);
    if (f == NULL) {
	errno = err;
	if (!must_exist) {
	    if (err != ENOENT && err != ENOTDIR)
		warning("Warning: can't open options file %s: %m", filename);
	    return 1;
	}
	option_error("Can't open options file %s: %m", filename);
	return 0;
    }

    oldpriv = privileged_option;
    privileged_option = priv;
    oldsource = option_source;
    option_source = strdup(filename);
#ifdef __APPLE__
    tofree = option_source;
#endif
    if (option_source == NULL)
	option_source = "file";
    ret = 0;
    while (getword(f, cmd, &newline, filename)) {
	/*
	 * First see if it's a command.
	 */
	opt = find_option(cmd);
	if (opt != NULL) {
	    int n = n_arguments(opt);
	    for (i = 0; i < n; ++i) {
		if (!getword(f, args[i], &newline, filename)) {
		    option_error(
			"In file %s: too few parameters for option '%s'",
			filename, cmd);
		    goto err;
		}
		argv[i] = args[i];
	    }
	    current_option = cmd;
	    if ((opt->flags & OPT_DEVEQUIV) && no_override) {
		option_error("the %s option may not be used in the %s file",
			     cmd, filename);
		goto err;
	    }
	    if (!process_option(opt, argv))
		goto err;
	    continue;
	}

	/*
	 * Maybe a tty name, speed or IP address?
	 */
	if ((i = setdevname(cmd)) == 0
	    && (i = setspeed(cmd)) == 0
	    && (i = setipaddr(cmd)) == 0) {
	    option_error("In file %s: unrecognized option '%s'",
			 filename, cmd);
	    goto err;
	}
	if (i < 0)		/* error */
	    goto err;
    }
    ret = 1;

err:
    fclose(f);
    privileged_option = oldpriv;
    option_source = oldsource;
#ifdef __APPLE__
    if (tofree)
        free(tofree);
#endif
    return ret;
}

/*
 * options_from_user - See if the use has a ~/.ppprc file,
 * and if so, interpret options from it.
 */
int
options_from_user()
{
    char *user, *path, *file;
    int ret;
    struct passwd *pw;
    size_t pl;

    pw = getpwuid(getuid());
    if (pw == NULL || (user = pw->pw_dir) == NULL || user[0] == 0)
	return 1;
    file = _PATH_USEROPT;
    pl = strlen(user) + strlen(file) + 2;
    path = malloc(pl);
    if (path == NULL)
	novm("init file name");
    slprintf(path, pl, "%s/%s", user, file);
    ret = options_from_file(path, 0, 1, privileged);
    free(path);
    return ret;
}

/*
 * options_for_tty - See if an options file exists for the serial
 * device, and if so, interpret options from it.
 * We only allow the per-tty options file to override anything from
 * the command line if it is something that the user can't override
 * once it has been set by root.
 */
int
options_for_tty()
{
    char *dev, *path, *p;
    int ret;
    size_t pl;

    dev = devnam;
    if (strncmp(dev, "/dev/", 5) == 0)
	dev += 5;
    if (dev[0] == 0 || strcmp(dev, "tty") == 0)
	return 1;		/* don't look for /etc/ppp/options.tty */
    pl = strlen(_PATH_TTYOPT) + strlen(dev) + 1;
    path = malloc(pl);
    if (path == NULL)
	novm("tty init file name");
    slprintf(path, pl, "%s%s", _PATH_TTYOPT, dev);
    /* Turn slashes into dots, for Solaris case (e.g. /dev/term/a) */
    for (p = path + strlen(_PATH_TTYOPT); *p != 0; ++p)
	if (*p == '/')
	    *p = '.';
    no_override = 1;
    ret = options_from_file(path, 0, 0, 1);
    no_override = 0;
    free(path);
    return ret;
}

/*
 * options_from_list - process a string of options in a wordlist.
 */
int
options_from_list(w, priv)
    struct wordlist *w;
    int priv;
{
    char *argv[MAXARGS];
    option_t *opt;
    int i, ret = 0;

    privileged_option = priv;
    option_source = "secrets file";

    while (w != NULL) {
	/*
	 * First see if it's a command.
	 */
	opt = find_option(w->word);
	if (opt != NULL) {
	    int n = n_arguments(opt);
	    struct wordlist *w0 = w;
	    for (i = 0; i < n; ++i) {
		w = w->next;
		if (w == NULL) {
		    option_error(
			"In secrets file: too few parameters for option '%s'",
			w0->word);
		    goto err;
		}
		argv[i] = w->word;
	    }
	    current_option = w0->word;
	    if (!process_option(opt, argv))
		goto err;
	    continue;
	}

	/*
	 * Maybe a tty name, speed or IP address?
	 */
	if ((i = setdevname(w->word)) == 0
	    && (i = setspeed(w->word)) == 0
	    && (i = setipaddr(w->word)) == 0) {
	    option_error("In secrets file: unrecognized option '%s'",
			 w->word);
	    goto err;
	}
	if (i < 0)		/* error */
	    goto err;
    }
    ret = 1;

err:
    return ret;
}

/*
 * find_option - scan the option lists for the various protocols
 * looking for an entry with the given name.
 * This could be optimized by using a hash table.
 */
static option_t *
find_option(name)
    char *name;
{
    option_t *opt;
    struct option_list *list;
    int i;

    for (list = extra_options; list != NULL; list = list->next)
	for (opt = list->options; opt->name != NULL; ++opt)
	    if (strcmp(name, opt->name) == 0)
		return opt;
    for (opt = general_options; opt->name != NULL; ++opt)
	if (strcmp(name, opt->name) == 0)
	    return opt;
    for (opt = auth_options; opt->name != NULL; ++opt)
	if (strcmp(name, opt->name) == 0)
	    return opt;
    for (i = 0; protocols[i] != NULL; ++i)
	if ((opt = protocols[i]->options) != NULL)
	    for (; opt->name != NULL; ++opt)
		if (strcmp(name, opt->name) == 0)
		    return opt;
    return NULL;
}

/*
 * process_option - process one new-style option.
 */
static int
process_option(opt, argv)
    option_t *opt;
    char **argv;
{
    u_int32_t v;
    int iv, a;
#ifdef __APPLE__
    int i, len;
 #endif
    char *sv;
    int (*parser) __P((char **));

    if (no_override && (opt->flags & OPT_SEENIT)) {
	struct option_info *ip = (struct option_info *) opt->addr2;
	if (!(privileged && (opt->flags & OPT_PRIVFIX)))
	    return 1;
	if (!ip || ip->priv)
	    return 1;
	warning("%s option from per-tty file overrides command line", opt->name);
    }
    if ((opt->flags & OPT_INITONLY) && phase != PHASE_INITIALIZE) {
	option_error("it's too late to use the %s option", opt->name);
	return 0;
    }
    if ((opt->flags & OPT_PRIV) && !privileged_option) {
	option_error("using the %s option requires root privilege", opt->name);
	return 0;
    }
    if ((opt->flags & OPT_ENABLE) && *(bool *)(opt->addr2) == 0) {
	option_error("%s option is disabled", opt->name);
	return 0;
    }
    if ((opt->flags & OPT_PRIVFIX) && !privileged_option) {
	struct option_info *ip = (struct option_info *) opt->addr2;
	if (ip && ip->priv) {
	    option_error("%s option cannot be overridden", opt->name);
	    return 0;
	}
    }
    opt->flags |= OPT_SEENIT;

    switch (opt->type) {
    case o_bool:
	v = opt->flags & OPT_VALUE;
	*(bool *)(opt->addr) = v;
	if (opt->addr2 && (opt->flags & OPT_A2COPY))
	    *(bool *)(opt->addr2) = v;
	break;

    case o_int:
	iv = 0;
	if ((opt->flags & OPT_NOARG) == 0) {
	    if (!int_option(*argv, &iv))
		return 0;
	    if ((((opt->flags & OPT_LLIMIT) && iv < opt->lower_limit)
		 || ((opt->flags & OPT_ULIMIT) && iv > opt->upper_limit))
		&& !((opt->flags & OPT_ZEROOK && iv == 0))) {
		char *zok = (opt->flags & OPT_ZEROOK)? " zero or": "";
		switch (opt->flags & OPT_LIMITS) {
		case OPT_LLIMIT:
		    option_error("%s value must be%s >= %d",
				 opt->name, zok, opt->lower_limit);
		    break;
		case OPT_ULIMIT:
		    option_error("%s value must be%s <= %d",
				 opt->name, zok, opt->upper_limit);
		    break;
		case OPT_LIMITS:
		    option_error("%s value must be%s between %d and %d",
				opt->name, opt->lower_limit, opt->upper_limit);
		    break;
		}
		return 0;
	    }
	}
	a = opt->flags & OPT_VALUE;
	if (a >= 128)
	    a -= 256;		/* sign extend */
	iv += a;
	if (opt->flags & OPT_INC)
	    iv += *(int *)(opt->addr);
	if ((opt->flags & OPT_NOINCR) && !privileged_option) {
	    int oldv = *(int *)(opt->addr);
	    if ((opt->flags & OPT_ZEROINF) ?
		(oldv != 0 && (iv == 0 || iv > oldv)) : (iv > oldv)) {
		option_error("%s value cannot be increased", opt->name);
		return 0;
	    }
	}
	*(int *)(opt->addr) = iv;
	if (opt->addr2 && (opt->flags & OPT_A2COPY))
	    *(int *)(opt->addr2) = iv;
	break;

    case o_uint32:
	if (opt->flags & OPT_NOARG) {
	    v = opt->flags & OPT_VALUE;
	} else if (!number_option(*argv, &v, 16))
	    return 0;
	if (opt->flags & OPT_OR)
	    v |= *(u_int32_t *)(opt->addr);
	*(u_int32_t *)(opt->addr) = v;
	if (opt->addr2 && (opt->flags & OPT_A2COPY))
	    *(u_int32_t *)(opt->addr2) = v;
	break;

    case o_string:
	if (opt->flags & OPT_STATIC) {
	    strlcpy((char *)(opt->addr), *argv, opt->upper_limit);
	} else {
	    sv = strdup(*argv);
	    if (sv == NULL)
		novm("option argument");
	    *(char **)(opt->addr) = sv;
	}
	break;

    case o_special_noarg:
    case o_special:
	parser = (int (*) __P((char **))) opt->addr;
	if (!(*parser)(argv))
	    return 0;
	break;
    }

    if (opt->addr2) {
	if (opt->flags & OPT_A2INFO) {
	    struct option_info *ip = (struct option_info *) opt->addr2;
	    ip->priv = privileged_option;
	    ip->source = option_source;
	} else if ((opt->flags & (OPT_A2COPY|OPT_ENABLE)) == 0)
	    *(bool *)(opt->addr2) = 1;
    }
 
#ifdef __APPLE__
    if (opt->flags & OPT_HIDE)
        for (i = 0, len = strlen(*argv); i < len; (*argv)[i++] = '*');
 #endif
    return 1;
}

/*
 * n_arguments - tell how many arguments an option takes
 */
static int
n_arguments(opt)
    option_t *opt;
{
    return (opt->type == o_bool || opt->type == o_special_noarg
	    || (opt->flags & OPT_NOARG))? 0: 1;
}

/*
 * add_options - add a list of options to the set we grok.
 */
void
add_options(opt)
    option_t *opt;
{
    struct option_list *list;

    list = malloc(sizeof(*list));
    if (list == 0)
	novm("option list entry");
    list->options = opt;
    list->next = extra_options;
    extra_options = list;
}

/*
 * usage - print out a message telling how to use the program.
 */
static void
usage()
{
    if (phase == PHASE_INITIALIZE)
	fprintf(stderr, usage_string, VERSION, PATCHLEVEL, IMPLEMENTATION,
		progname);
}

/*
 * showhelp - print out usage message and exit.
 */
static int
showhelp(argv)
    char **argv;
{
    if (phase == PHASE_INITIALIZE) {
	usage();
	exit(0);
    }
    return 0;
}

/*
 * showversion - print out the version number and exit.
 */
static int
showversion(argv)
    char **argv;
{
    if (phase == PHASE_INITIALIZE) {
	fprintf(stderr, "pppd version %s.%d%s\n",
		VERSION, PATCHLEVEL, IMPLEMENTATION);
	exit(0);
    }
    return 0;
}

/*
 * option_error - print a message about an error in an option.
 * The message is logged, and also sent to
 * stderr if phase == PHASE_INITIALIZE.
 */
void
option_error __V((char *fmt, ...))
{
    va_list args;
    char buf[256];

#if defined(__STDC__)
    va_start(args, fmt);
#else
    char *fmt;
    va_start(args);
    fmt = va_arg(args, char *);
#endif
    vslprintf(buf, sizeof(buf), fmt, args);
    va_end(args);
    if (phase == PHASE_INITIALIZE)
	fprintf(stderr, "%s: %s\n", progname, buf);
#ifdef __APPLE__
    error(buf);
#else
    syslog(LOG_ERR, "%s", buf);
#endif
}

#if 0
/*
 * readable - check if a file is readable by the real user.
 */
int
readable(fd)
    int fd;
{
    uid_t uid;
    int i;
    struct stat sbuf;

    uid = getuid();
    if (uid == 0)
	return 1;
    if (fstat(fd, &sbuf) != 0)
	return 0;
    if (sbuf.st_uid == uid)
	return sbuf.st_mode & S_IRUSR;
    if (sbuf.st_gid == getgid())
	return sbuf.st_mode & S_IRGRP;
    for (i = 0; i < ngroups; ++i)
	if (sbuf.st_gid == groups[i])
	    return sbuf.st_mode & S_IRGRP;
    return sbuf.st_mode & S_IROTH;
}
#endif

/*
 * Read a word from a file.
 * Words are delimited by white-space or by quotes (" or ').
 * Quotes, white-space and \ may be escaped with \.
 * \<newline> is ignored.
 */
int
getword(f, word, newlinep, filename)
    FILE *f;
    char *word;
    int *newlinep;
    char *filename;
{
    int c, len, escape;
    int quoted, comment;
    int value, digit, got, n;

#define isoctal(c) ((c) >= '0' && (c) < '8')

    *newlinep = 0;
    len = 0;
    escape = 0;
    comment = 0;

    /*
     * First skip white-space and comments.
     */
    for (;;) {
	c = getc(f);
	if (c == EOF)
	    break;

	/*
	 * A newline means the end of a comment; backslash-newline
	 * is ignored.  Note that we cannot have escape && comment.
	 */
	if (c == '\n') {
	    if (!escape) {
		*newlinep = 1;
		comment = 0;
	    } else
		escape = 0;
	    continue;
	}

	/*
	 * Ignore characters other than newline in a comment.
	 */
	if (comment)
	    continue;

	/*
	 * If this character is escaped, we have a word start.
	 */
	if (escape)
	    break;

	/*
	 * If this is the escape character, look at the next character.
	 */
	if (c == '\\') {
	    escape = 1;
	    continue;
	}

	/*
	 * If this is the start of a comment, ignore the rest of the line.
	 */
	if (c == '#') {
	    comment = 1;
	    continue;
	}

	/*
	 * A non-whitespace character is the start of a word.
	 */
	if (!isspace(c))
	    break;
    }

    /*
     * Save the delimiter for quoted strings.
     */
    if (!escape && (c == '"' || c == '\'')) {
        quoted = c;
	c = getc(f);
    } else
        quoted = 0;

    /*
     * Process characters until the end of the word.
     */
    while (c != EOF) {
	if (escape) {
	    /*
	     * This character is escaped: backslash-newline is ignored,
	     * various other characters indicate particular values
	     * as for C backslash-escapes.
	     */
	    escape = 0;
	    if (c == '\n') {
	        c = getc(f);
		continue;
	    }

	    got = 0;
	    switch (c) {
	    case 'a':
		value = '\a';
		break;
	    case 'b':
		value = '\b';
		break;
	    case 'f':
		value = '\f';
		break;
	    case 'n':
		value = '\n';
		break;
	    case 'r':
		value = '\r';
		break;
	    case 's':
		value = ' ';
		break;
	    case 't':
		value = '\t';
		break;

	    default:
		if (isoctal(c)) {
		    /*
		     * \ddd octal sequence
		     */
		    value = 0;
		    for (n = 0; n < 3 && isoctal(c); ++n) {
			value = (value << 3) + (c & 07);
			c = getc(f);
		    }
		    got = 1;
		    break;
		}

		if (c == 'x') {
		    /*
		     * \x<hex_string> sequence
		     */
		    value = 0;
		    c = getc(f);
		    for (n = 0; n < 2 && isxdigit(c); ++n) {
			digit = toupper(c) - '0';
			if (digit > 10)
			    digit += '0' + 10 - 'A';
			value = (value << 4) + digit;
			c = getc (f);
		    }
		    got = 1;
		    break;
		}

		/*
		 * Otherwise the character stands for itself.
		 */
		value = c;
		break;
	    }

	    /*
	     * Store the resulting character for the escape sequence.
	     */
	    if (len < MAXWORDLEN-1)
		word[len] = value;
	    ++len;

	    if (!got)
		c = getc(f);
	    continue;

	}

	/*
	 * Not escaped: see if we've reached the end of the word.
	 */
	if (quoted) {
	    if (c == quoted)
		break;
	} else {
	    if (isspace(c) || c == '#') {
		ungetc (c, f);
		break;
	    }
	}

	/*
	 * Backslash starts an escape sequence.
	 */
	if (c == '\\') {
	    escape = 1;
	    c = getc(f);
	    continue;
	}

	/*
	 * An ordinary character: store it in the word and get another.
	 */
	if (len < MAXWORDLEN-1)
	    word[len] = c;
	++len;

	c = getc(f);
    }

    /*
     * End of the word: check for errors.
     */
    if (c == EOF) {
	if (ferror(f)) {
	    if (errno == 0)
		errno = EIO;
	    option_error("Error reading %s: %m", filename);
	    die(1);
	}
	/*
	 * If len is zero, then we didn't find a word before the
	 * end of the file.
	 */
	if (len == 0)
	    return 0;
    }

    /*
     * Warn if the word was too long, and append a terminating null.
     */
    if (len >= MAXWORDLEN) {
	option_error("warning: word in file %s too long (%.20s...)",
		     filename, word);
	len = MAXWORDLEN - 1;
    }
    word[len] = 0;

    return 1;

#undef isoctal

}

/*
 * number_option - parse an unsigned numeric parameter for an option.
 */
static int
number_option(str, valp, base)
    char *str;
    u_int32_t *valp;
    int base;
{
    char *ptr;

    *valp = strtoul(str, &ptr, base);
    if (ptr == str) {
	option_error("invalid numeric parameter '%s' for %s option",
		     str, current_option);
	return 0;
    }
    return 1;
}


/*
 * int_option - like number_option, but valp is int *,
 * the base is assumed to be 0, and *valp is not changed
 * if there is an error.
 */
int
int_option(str, valp)
    char *str;
    int *valp;
{
    u_int32_t v;

    if (!number_option(str, &v, 0))
	return 0;
    *valp = (int) v;
    return 1;
}


/*
 * The following procedures parse options.
 */

/*
 * readfile - take commands from a file.
 */
static int
readfile(argv)
    char **argv;
{
    return options_from_file(*argv, 1, 1, privileged_option);
}

/*
 * callfile - take commands from /etc/ppp/peers/<name>.
 * Name may not contain /../, start with / or ../, or end in /..
 */
static int
callfile(argv)
    char **argv;
{
    char *fname, *arg, *p;
    int l, ok;

    arg = *argv;
    ok = 1;
    if (arg[0] == '/' || arg[0] == 0)
	ok = 0;
    else {
	for (p = arg; *p != 0; ) {
	    if (p[0] == '.' && p[1] == '.' && (p[2] == '/' || p[2] == 0)) {
		ok = 0;
		break;
	    }
	    while (*p != '/' && *p != 0)
		++p;
	    if (*p == '/')
		++p;
	}
    }
    if (!ok) {
	option_error("call option value may not contain .. or start with /");
	return 0;
    }

    l = strlen(arg) + strlen(_PATH_PEERFILES) + 1;
    if ((fname = (char *) malloc(l)) == NULL)
	novm("call file name");
    slprintf(fname, l, "%s%s", _PATH_PEERFILES, arg);

#ifdef __APPLE__
    ok = options_from_file(fname, 0, 1, 1);
#else
    ok = options_from_file(fname, 1, 1, 1);
#endif

    free(fname);
    return ok;
}

#ifdef PPP_FILTER
/*
 * setpassfilter - Set the pass filter for packets
 */
static int
setpassfilter(argv)
    char **argv;
{
    pc.linktype = DLT_PPP;
    pc.snapshot = PPP_HDRLEN;
 
    if (pcap_compile(&pc, &pass_filter, *argv, 1, netmask) == 0)
	return 1;
    option_error("error in pass-filter expression: %s\n", pcap_geterr(&pc));
    return 0;
}

/*
 * setactivefilter - Set the active filter for packets
 */
static int
setactivefilter(argv)
    char **argv;
{
    pc.linktype = DLT_PPP;
    pc.snapshot = PPP_HDRLEN;
 
    if (pcap_compile(&pc, &active_filter, *argv, 1, netmask) == 0)
	return 1;
    option_error("error in active-filter expression: %s\n", pcap_geterr(&pc));
    return 0;
}
#endif

/*
 * noopt - Disable all options.
 */
static int
noopt(argv)
    char **argv;
{
    BZERO((char *) &lcp_wantoptions[0], sizeof (struct lcp_options));
    BZERO((char *) &lcp_allowoptions[0], sizeof (struct lcp_options));
    BZERO((char *) &ipcp_wantoptions[0], sizeof (struct ipcp_options));
    BZERO((char *) &ipcp_allowoptions[0], sizeof (struct ipcp_options));

    return (1);
}

/*
 * setdomain - Set domain name to append to hostname 
 */
static int
setdomain(argv)
    char **argv;
{
    if (!privileged_option) {
	option_error("using the domain option requires root privilege");
	return 0;
    }
    gethostname(hostname, MAXNAMELEN);
    if (**argv != 0) {
	if (**argv != '.')
	    strncat(hostname, ".", MAXNAMELEN - strlen(hostname));
	strncat(hostname, *argv, MAXNAMELEN - strlen(hostname));
    }
    hostname[MAXNAMELEN-1] = 0;
    return (1);
}


/*
 * setspeed - Set the speed.
 */
static int
setspeed(arg)
    char *arg;
{
    char *ptr;
    int spd;

    if (no_override && inspeed != 0)
	return 1;
    spd = strtol(arg, &ptr, 0);
    if (ptr == arg || *ptr != 0 || spd == 0)
	return 0;
    if (!no_override || inspeed == 0)
	inspeed = spd;
    return 1;
}


/*
 * setdevname - Set the device name.
 */
static int
setdevname(cp)
    char *cp;
{
    struct stat statbuf;
    char dev[MAXPATHLEN];

    if (*cp == 0)
	return 0;

    if (strncmp("/dev/", cp, 5) != 0) {
	strlcpy(dev, "/dev/", sizeof(dev));
	strlcat(dev, cp, sizeof(dev));
	cp = dev;
    }

    /*
     * Check if there is a character device by this name.
     */
    if (stat(cp, &statbuf) < 0) {
	if (errno == ENOENT)
	    return 0;
	option_error("Couldn't stat %s: %m", cp);
	return -1;
    }
    if (!S_ISCHR(statbuf.st_mode)) {
	option_error("%s is not a character device", cp);
	return -1;
    }

    if (phase != PHASE_INITIALIZE) {
	option_error("device name cannot be changed after initialization");
	return -1;
    }
    if (no_override) {
	option_error("per-tty options file may not specify device name");
	return -1;
    }

    if (devnam_info.priv && !privileged_option) {
	option_error("device name cannot be overridden");
	return -1;
    }

    strlcpy(devnam, cp, sizeof(devnam));
    devstat = statbuf;
    default_device = 0;
    devnam_info.priv = privileged_option;
    devnam_info.source = option_source;
  
    return 1;
}


/*
 * setipaddr - Set the IP address
 */
static int
setipaddr(arg)
    char *arg;
{
    struct hostent *hp;
    char *colon;
    u_int32_t local, remote;
    ipcp_options *wo = &ipcp_wantoptions[0];
    static int seen_local = 0, seen_remote = 0;

    /*
     * IP address pair separated by ":".
     */
    if ((colon = strchr(arg, ':')) == NULL)
	return 0;
  
    /*
     * If colon first character, then no local addr.
     */
    if (colon != arg && !(no_override && seen_local)) {
	*colon = '\0';
	if ((local = inet_addr(arg)) == (u_int32_t) -1) {
	    if ((hp = gethostbyname(arg)) == NULL) {
		option_error("unknown host: %s", arg);
		return -1;
	    } else {
		local = *(u_int32_t *)hp->h_addr;
	    }
	}
	if (bad_ip_adrs(local)) {
	    option_error("bad local IP address %s", ip_ntoa(local));
	    return -1;
	}
	if (local != 0)
	    wo->ouraddr = local;
	*colon = ':';
	seen_local = 1;
    }
  
    /*
     * If colon last character, then no remote addr.
     */
    if (*++colon != '\0' && !(no_override && seen_remote)) {
	if ((remote = inet_addr(colon)) == (u_int32_t) -1) {
	    if ((hp = gethostbyname(colon)) == NULL) {
		option_error("unknown host: %s", colon);
		return -1;
	    } else {
		remote = *(u_int32_t *)hp->h_addr;
		if (remote_name[0] == 0)
		    strlcpy(remote_name, colon, sizeof(remote_name));
	    }
	}
	if (bad_ip_adrs(remote)) {
	    option_error("bad remote IP address %s", ip_ntoa(remote));
	    return -1;
	}
	if (remote != 0)
	    wo->hisaddr = remote;
	seen_remote = 1;
    }

    return 1;
}


/*
 * setnetmask - set the netmask to be used on the interface.
 */
static int
setnetmask(argv)
    char **argv;
{
    u_int32_t mask;
    int n;
    char *p;

    /*
     * Unfortunately, if we use inet_addr, we can't tell whether
     * a result of all 1s is an error or a valid 255.255.255.255.
     */
    p = *argv;
    n = parse_dotted_ip(p, &mask);

    mask = htonl(mask);

    if (n == 0 || p[n] != 0 || (netmask & ~mask) != 0) {
	option_error("invalid netmask value '%s'", *argv);
	return 0;
    }

    netmask = mask;
    return (1);
}

int
parse_dotted_ip(p, vp)
    char *p;
    u_int32_t *vp;
{
    int n;
    u_int32_t v, b;
    char *endp, *p0 = p;

    v = 0;
    for (n = 3;; --n) {
	b = strtoul(p, &endp, 0);
	if (endp == p)
	    return 0;
	if (b > 255) {
	    if (n < 3)
		return 0;
	    /* accept e.g. 0xffffff00 */
	    *vp = b;
	    return endp - p0;
	}
	v |= b << (n * 8);
	p = endp;
	if (n == 0)
	    break;
	if (*p != '.')
	    return 0;
	++p;
    }
    *vp = v;
    return p - p0;
}

static int
setlogfile(argv)
    char **argv;
{
    int fd, err;

    if (!privileged_option)
	seteuid(getuid());
    fd = open(*argv, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0644);
    if (fd < 0 && errno == EEXIST)
	fd = open(*argv, O_WRONLY | O_APPEND);
    err = errno;
    if (!privileged_option)
	seteuid(0);
    if (fd < 0) {
	errno = err;
	option_error("Can't open log file %s: %m", *argv);
	return 0;
    }
    if (log_to_file && log_to_fd >= 0)
	close(log_to_fd);
    log_to_fd = fd;
    log_to_file = 1;
    return 1;
}

#ifdef PLUGIN

#ifdef __APPLE__
static int
loadplugin(argv)
    char **argv;
{
    char *arg = *argv;
    int err;
    
    err = sys_loadplugin(*argv);
    if (err) {
	option_error("Couldn't load plugin %s", arg);
        return 0;
    }

    //info("Plugin %s loaded.", arg);

    return 1;
}

#else
static int
loadplugin(argv)
    char **argv;
{
    char *arg = *argv;
    void *handle;
    const char *err;
    void (*init) __P((void));

    handle = dlopen(arg, RTLD_GLOBAL | RTLD_NOW);
    if (handle == 0) {
	err = dlerror();
	if (err != 0)
	    option_error("%s", err);
	option_error("Couldn't load plugin %s", arg);
	return 0;
    }
    init = (void (*)(void))dlsym(handle, "plugin_init");
    if (init == 0) {
	option_error("%s has no initialization entry point", arg);
	dlclose(handle);
	return 0;
    }
    info("Plugin %s loaded.", arg);
    (*init)();
    return 1;
}
#endif
#endif /* PLUGIN */