/* $NetBSD: options.c,v 1.5 2019/02/01 08:29:04 mrg Exp $ */ /* * options.c - handles option processing for PPP. * * Copyright (c) 1984-2000 Carnegie Mellon University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The name "Carnegie Mellon University" must not be used to * endorse or promote products derived from this software without * prior written permission. For permission or any legal * details, please contact * Office of Technology Transfer * Carnegie Mellon University * 5000 Forbes Avenue * Pittsburgh, PA 15213-3890 * (412) 268-4387, fax: (412) 268-7395 * tech-transfer@andrew.cmu.edu * * 4. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by Computing Services * at Carnegie Mellon University (http://www.cmu.edu/computing/)." * * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #if 0 #define RCSID "Id: options.c,v 1.102 2008/06/15 06:53:06 paulus Exp " static const char rcsid[] = RCSID; #else __RCSID("$NetBSD: options.c,v 1.5 2019/02/01 08:29:04 mrg Exp $"); #endif #include #include #include #include #include #include #include #include #include #ifdef PLUGIN #include #endif #ifdef PPP_FILTER #include /* * There have been 3 or 4 different names for this in libpcap CVS, but * this seems to be what they have settled on... * For older versions of libpcap, use DLT_PPP - but that means * we lose the inbound and outbound qualifiers. */ #ifndef DLT_PPP_PPPD #ifdef DLT_PPP_WITHDIRECTION #define DLT_PPP_PPPD DLT_PPP_WITHDIRECTION #else #define DLT_PPP_PPPD DLT_PPP #endif #endif #endif /* PPP_FILTER */ #include "pppd.h" #include "pathnames.h" #if defined(ultrix) || defined(NeXT) char *strdup __P((char *)); #endif struct option_value { struct option_value *next; const char *source; char value[1]; }; /* * Option variables and default values. */ 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 */ bool nodetach = 0; /* Don't detach from controlling tty */ bool updetach = 0; /* Detach once link is up */ bool master_detach; /* Detach when we're (only) multilink master */ 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 */ bool log_default = 1; /* log_to_fd is default (stdout) */ 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 */ bool dump_options; /* print out option values */ bool dryrun; /* print out option values and exit */ char *domain; /* domain name set by domain option */ int child_wait = 5; /* # seconds to wait for children at exit */ struct userenv *userenv_list; /* user environment variables */ #ifdef MAXOCTETS unsigned int maxoctets = 0; /* default - no limit */ int maxoctets_dir = 0; /* default - sum of traffic */ int maxoctets_timeout = 1; /* default 1 second */ #endif extern option_t auth_options[]; extern struct stat devstat; #ifdef PPP_FILTER /* Filter program for packets to pass */ struct bpf_program pass_filter_in; struct bpf_program pass_filter_out; /* Filter program for link-active packets */ struct bpf_program active_filter_in; struct bpf_program active_filter_out; #endif static option_t *curopt; /* pointer to option being processed */ 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 */ int option_priority = OPRIO_CFGFILE; /* priority of the current options */ bool devnam_fixed; /* can no longer change device name */ static int logfile_fd = -1; /* fd opened for log file */ static char logfile_name[MAXPATHLEN]; /* name of log file */ /* * Prototypes */ static int setdomain __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_in __P((char **)); static int setpassfilter_out __P((char **)); static int setactivefilter_in __P((char **)); static int setactivefilter_out __P((char **)); #endif #ifdef MAXOCTETS static int setmodir __P((char **)); #endif static int user_setenv __P((char **)); static void user_setprint __P((option_t *, printer_func, void *)); static int user_unsetenv __P((char **)); static void user_unsetprint __P((option_t *, printer_func, void *)); static option_t *find_option __P((const char *name)); static int process_option __P((option_t *, char *, 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_ALIAS | OPT_INC | OPT_NOARG | 1 }, { "kdebug", o_int, &kdebugflag, "Set kernel driver debug level", OPT_PRIO }, { "nodetach", o_bool, &nodetach, "Don't detach from controlling tty", OPT_PRIO | 1 }, { "-detach", o_bool, &nodetach, "Don't detach from controlling tty", OPT_ALIAS | OPT_PRIOSUB | 1 }, { "updetach", o_bool, &updetach, "Detach from controlling tty once link is up", OPT_PRIOSUB | OPT_A2CLR | 1, &nodetach }, { "master_detach", o_bool, &master_detach, "Detach when we're multilink master but have no link", 1 }, { "holdoff", o_int, &holdoff, "Set time in seconds before retrying connection", OPT_PRIO, &holdoff_specified }, { "idle", o_int, &idle_time_limit, "Set time in seconds before disconnecting idle link", OPT_PRIO }, { "maxconnect", o_int, &maxconnect, "Set connection time limit", OPT_PRIO | OPT_LLIMIT | OPT_NOINCR | OPT_ZEROINF }, { "domain", o_special, (void *)setdomain, "Add given domain name to hostname", OPT_PRIO | OPT_PRIV | OPT_A2STRVAL, &domain }, { "file", o_special, (void *)readfile, "Take options from a file", OPT_NOPRINT }, { "call", o_special, (void *)callfile, "Take options from a privileged file", OPT_NOPRINT }, { "persist", o_bool, &persist, "Keep on reopening connection after close", OPT_PRIO | 1 }, { "nopersist", o_bool, &persist, "Turn off persist option", OPT_PRIOSUB }, { "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", OPT_ALIAS }, { "logfile", o_special, (void *)setlogfile, "Append log messages to this file", OPT_PRIO | OPT_A2STRVAL | OPT_STATIC, &logfile_name }, { "logfd", o_int, &log_to_fd, "Send log messages to this file descriptor", OPT_PRIOSUB | OPT_A2CLR, &log_default }, { "nolog", o_int, &log_to_fd, "Don't send log messages to any file", OPT_PRIOSUB | OPT_NOARG | OPT_VAL(-1) }, { "nologfd", o_int, &log_to_fd, "Don't send log messages to any file descriptor", OPT_PRIOSUB | OPT_ALIAS | OPT_NOARG | OPT_VAL(-1) }, { "linkname", o_string, linkname, "Set logical name for link", OPT_PRIO | OPT_PRIV | OPT_STATIC, NULL, MAXPATHLEN }, { "maxfail", o_int, &maxfail, "Maximum number of unsuccessful connection attempts to allow", OPT_PRIO }, { "ktune", o_bool, &tune_kernel, "Alter kernel settings as necessary", OPT_PRIO | 1 }, { "noktune", o_bool, &tune_kernel, "Don't alter kernel settings", OPT_PRIOSUB }, { "connect-delay", o_int, &connect_delay, "Maximum time (in ms) to wait after connect script finishes", OPT_PRIO }, { "unit", o_int, &req_unit, "PPP interface unit number to use if possible", OPT_PRIO | OPT_LLIMIT, 0, 0 }, { "dump", o_bool, &dump_options, "Print out option values after parsing all options", 1 }, { "dryrun", o_bool, &dryrun, "Stop after parsing, printing, and checking options", 1 }, { "child-timeout", o_int, &child_wait, "Number of seconds to wait for child processes at exit", OPT_PRIO }, { "set", o_special, (void *)user_setenv, "Set user environment variable", OPT_A2PRINTER | OPT_NOPRINT, (void *)user_setprint }, { "unset", o_special, (void *)user_unsetenv, "Unset user environment variable", OPT_A2PRINTER | OPT_NOPRINT, (void *)user_unsetprint }, #ifdef HAVE_MULTILINK { "multilink", o_bool, &multilink, "Enable multilink operation", OPT_PRIO | 1 }, { "mp", o_bool, &multilink, "Enable multilink operation", OPT_PRIOSUB | OPT_ALIAS | 1 }, { "nomultilink", o_bool, &multilink, "Disable multilink operation", OPT_PRIOSUB | 0 }, { "nomp", o_bool, &multilink, "Disable multilink operation", OPT_PRIOSUB | OPT_ALIAS | 0 }, { "bundle", o_string, &bundle_name, "Bundle name for multilink", OPT_PRIO }, #endif /* HAVE_MULTILINK */ #ifdef PLUGIN { "plugin", o_special, (void *)loadplugin, "Load a plug-in module into pppd", OPT_PRIV | OPT_A2LIST }, #endif #ifdef PPP_FILTER { "pass-filter-in", o_special, setpassfilter_in, "set filter for packets to pass inwards", OPT_PRIO }, { "pass-filter-out", o_special, setpassfilter_out, "set filter for packets to pass outwards", OPT_PRIO }, { "active-filter-in", o_special, setactivefilter_in, "set filter for active pkts inwards", OPT_PRIO }, { "active-filter-out", o_special, setactivefilter_out, "set filter for active pkts outwards", OPT_PRIO }, #endif #ifdef MAXOCTETS { "maxoctets", o_int, &maxoctets, "Set connection traffic limit", OPT_PRIO | OPT_LLIMIT | OPT_NOINCR | OPT_ZEROINF }, { "mo", o_int, &maxoctets, "Set connection traffic limit", OPT_ALIAS | OPT_PRIO | OPT_LLIMIT | OPT_NOINCR | OPT_ZEROINF }, { "mo-direction", o_special, setmodir, "Set direction for limit traffic (sum,in,out,max)" }, { "mo-timeout", o_int, &maxoctets_timeout, "Check for traffic limit every N seconds", OPT_PRIO | OPT_LLIMIT | 1 }, #endif { NULL } }; #ifndef IMPLEMENTATION #define IMPLEMENTATION "" #endif static const char *usage_string = "\ pppd version %s\n\ Usage: %s [ options ], where options are:\n\ Communicate over the named device\n\ Set the baud rate to \n\ : Set the local and/or remote interface IP\n\ addresses. Either one may be omitted.\n\ asyncmap Set the desired async map to hex \n\ auth Require authentication from peer\n\ connect

Invoke shell command

to set up the serial line\n\ crtscts Use hardware RTS/CTS flow control\n\ cdtrcts Use hardware DTR/CTS flow control (if supported)\n\ defaultroute Add default route through interface\n\ file Take options from file \n\ modem Use modem control lines\n\ mru Set MRU value to 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 n; privileged_option = privileged; option_source = "command line"; option_priority = OPRIO_CMDLINE; while (argc > 0) { arg = *argv++; --argc; opt = find_option(arg); if (opt == NULL) { option_error("unrecognized option '%s'", arg); usage(); return 0; } n = n_arguments(opt); if (argc < n) { option_error("too few parameters for option %s", arg); return 0; } if (!process_option(opt, arg, argv)) return 0; argc -= n; argv += n; } return 1; } /* * 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, n; char *oldsource; uid_t euid; char *argv[MAXARGS]; char args[MAXARGS][MAXWORDLEN]; char cmd[MAXWORDLEN]; euid = geteuid(); if (check_prot && seteuid(getuid()) == -1) { option_error("unable to drop privileges to open %s: %m", filename); return 0; } f = fopen(filename, "r"); err = errno; if (check_prot && seteuid(euid) == -1) fatal("unable to regain privileges"); if (f == NULL) { errno = err; if (!must_exist) { if (err != ENOENT && err != ENOTDIR) warn("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); if (option_source == NULL) option_source = "file"; ret = 0; while (getword(f, cmd, &newline, filename)) { opt = find_option(cmd); if (opt == NULL) { option_error("In file %s: unrecognized option '%s'", filename, cmd); goto err; } 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]; } if (!process_option(opt, cmd, argv)) goto err; } ret = 1; err: fclose(f); privileged_option = oldpriv; option_source = oldsource; 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); option_priority = OPRIO_CFGFILE; 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; this is done by giving configuration * files a lower priority than the command line. */ int options_for_tty() { char *dev, *path, *p; int ret; size_t pl; dev = devnam; if ((p = strstr(dev, "/dev/")) != NULL) dev = p + 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 = '.'; option_priority = OPRIO_CFGFILE; ret = options_from_file(path, 0, 0, 1); 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, n, ret = 0; struct wordlist *w0; privileged_option = priv; option_source = "secrets file"; option_priority = OPRIO_SECFILE; while (w != NULL) { opt = find_option(w->word); if (opt == NULL) { option_error("In secrets file: unrecognized option '%s'", w->word); goto err; } n = n_arguments(opt); 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; } if (!process_option(opt, w0->word, argv)) goto err; w = w->next; } ret = 1; err: return ret; } /* * match_option - see if this option matches an option_t structure. */ static int match_option(const char *name, option_t *opt, int dowild) { int (*match) __P((const char *, char **, int)); if (dowild != (opt->type == o_wild)) return 0; if (!dowild) return strcmp(name, opt->name) == 0; match = (int (*) __P((const char *, char **, int))) opt->addr; return (*match)(name, NULL, 0); } /* * 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) const char *name; { option_t *opt; struct option_list *list; int i, dowild; for (dowild = 0; dowild <= 1; ++dowild) { for (opt = general_options; opt->name != NULL; ++opt) if (match_option(name, opt, dowild)) return opt; for (opt = auth_options; opt->name != NULL; ++opt) if (match_option(name, opt, dowild)) return opt; for (list = extra_options; list != NULL; list = list->next) for (opt = list->options; opt->name != NULL; ++opt) if (match_option(name, opt, dowild)) return opt; for (opt = the_channel->options; opt->name != NULL; ++opt) if (match_option(name, opt, dowild)) return opt; for (i = 0; protocols[i] != NULL; ++i) if ((opt = protocols[i]->options) != NULL) for (; opt->name != NULL; ++opt) if (match_option(name, opt, dowild)) return opt; } return NULL; } /* * process_option - process one new-style option. */ static int process_option(opt, cmd, argv) option_t *opt; char *cmd; char **argv; { u_int32_t v; int iv, a; char *sv; int (*parser) __P((char **)); int (*wildp) __P((char *, char **, int)); char *optopt = (opt->type == o_wild)? "": " option"; int prio = option_priority; option_t *mainopt = opt; current_option = opt->name; if ((opt->flags & OPT_PRIVFIX) && privileged_option) prio += OPRIO_ROOT; while (mainopt->flags & OPT_PRIOSUB) --mainopt; if (mainopt->flags & OPT_PRIO) { if (prio < mainopt->priority) { /* new value doesn't override old */ if (prio == OPRIO_CMDLINE && mainopt->priority > OPRIO_ROOT) { option_error("%s%s set in %s cannot be overridden\n", opt->name, optopt, mainopt->source); return 0; } return 1; } if (prio > OPRIO_ROOT && mainopt->priority == OPRIO_CMDLINE) warn("%s%s from %s overrides command line", opt->name, optopt, option_source); } if ((opt->flags & OPT_INITONLY) && phase != PHASE_INITIALIZE) { option_error("%s%s cannot be changed after initialization", opt->name, optopt); return 0; } if ((opt->flags & OPT_PRIV) && !privileged_option) { option_error("using the %s%s requires root privilege", opt->name, optopt); return 0; } if ((opt->flags & OPT_ENABLE) && *(bool *)(opt->addr2) == 0) { option_error("%s%s is disabled", opt->name, optopt); return 0; } if ((opt->flags & OPT_DEVEQUIV) && devnam_fixed) { option_error("the %s%s may not be changed in %s", opt->name, optopt, option_source); return 0; } 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; else if (opt->addr2 && (opt->flags & OPT_A2CLR)) *(bool *)(opt->addr2) = 0; else if (opt->addr2 && (opt->flags & OPT_A2CLRB)) *(u_char *)(opt->addr2) &= ~v; else if (opt->addr2 && (opt->flags & OPT_A2OR)) *(u_char *)(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, zok, 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; if (v & 0x80) v |= 0xffffff00U; } 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 { char **optptr = (char **)(opt->addr); sv = strdup(*argv); if (sv == NULL) novm("option argument"); if (*optptr) free(*optptr); *optptr = sv; } break; case o_special_noarg: case o_special: parser = (int (*) __P((char **))) opt->addr; curopt = opt; if (!(*parser)(argv)) return 0; if (opt->flags & OPT_A2LIST) { struct option_value *ovp, *pp; ovp = malloc(sizeof(*ovp) + strlen(*argv)); if (ovp != 0) { strcpy(ovp->value, *argv); ovp->source = option_source; ovp->next = NULL; if (opt->addr2 == NULL) { opt->addr2 = ovp; } else { for (pp = opt->addr2; pp->next != NULL; pp = pp->next) ; pp->next = ovp; } } } break; case o_wild: wildp = (int (*) __P((char *, char **, int))) opt->addr; if (!(*wildp)(cmd, argv, 1)) return 0; break; } /* * If addr2 wasn't used by any flag (OPT_A2COPY, etc.) but is set, * treat it as a bool and set/clear it based on the OPT_A2CLR bit. */ if (opt->addr2 && (opt->flags & (OPT_A2COPY|OPT_ENABLE |OPT_A2PRINTER|OPT_A2STRVAL|OPT_A2LIST|OPT_A2OR)) == 0) *(bool *)(opt->addr2) = !(opt->flags & OPT_A2CLR); mainopt->source = option_source; mainopt->priority = prio; mainopt->winner = opt - mainopt; return 1; } /* * override_value - if the option priorities would permit us to * override the value of option, return 1 and update the priority * and source of the option value. Otherwise returns 0. */ int override_value(option, priority, source) const char *option; int priority; const char *source; { option_t *opt; opt = find_option(option); if (opt == NULL) return 0; while (opt->flags & OPT_PRIOSUB) --opt; if ((opt->flags & OPT_PRIO) && priority < opt->priority) return 0; opt->priority = priority; opt->source = source; opt->winner = -1; 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; } /* * check_options - check that options are valid and consistent. */ void check_options() { if (logfile_fd >= 0 && logfile_fd != log_to_fd) close(logfile_fd); } /* * print_option - print out an option and its value */ static void print_option(option_t *opt, option_t *mainopt, printer_func printer, void *arg) { int i, v; char *p; if (opt->flags & OPT_NOPRINT) return; switch (opt->type) { case o_bool: v = opt->flags & OPT_VALUE; if (*(bool *)opt->addr != v) /* this can happen legitimately, e.g. lock option turned off for default device */ break; printer(arg, "%s", opt->name); break; case o_int: v = opt->flags & OPT_VALUE; if (v >= 128) v -= 256; i = *(int *)opt->addr; if (opt->flags & OPT_NOARG) { printer(arg, "%s", opt->name); if (i != v) { if (opt->flags & OPT_INC) { for (; i > v; i -= v) printer(arg, " %s", opt->name); } else printer(arg, " # oops: %d not %d\n", i, v); } } else { printer(arg, "%s %d", opt->name, i); } break; case o_uint32: printer(arg, "%s", opt->name); if ((opt->flags & OPT_NOARG) == 0) printer(arg, " %x", *(u_int32_t *)opt->addr); break; case o_string: if (opt->flags & OPT_HIDE) { p = "??????"; } else { p = (char *) opt->addr; if ((opt->flags & OPT_STATIC) == 0) p = *(char **)p; } printer(arg, "%s %q", opt->name, p); break; case o_special: case o_special_noarg: case o_wild: if (opt->type != o_wild) { printer(arg, "%s", opt->name); if (n_arguments(opt) == 0) break; printer(arg, " "); } if (opt->flags & OPT_A2PRINTER) { void (*oprt) __P((option_t *, printer_func, void *)); oprt = (void (*) __P((option_t *, printer_func, void *)))opt->addr2; (*oprt)(opt, printer, arg); } else if (opt->flags & OPT_A2STRVAL) { p = (char *) opt->addr2; if ((opt->flags & OPT_STATIC) == 0) p = *(char **)p; printer("%q", p); } else if (opt->flags & OPT_A2LIST) { struct option_value *ovp; ovp = (struct option_value *) opt->addr2; for (;;) { printer(arg, "%q", ovp->value); if ((ovp = ovp->next) == NULL) break; printer(arg, "\t\t# (from %s)\n%s ", ovp->source, opt->name); } } else { printer(arg, "xxx # [don't know how to print value]"); } break; default: printer(arg, "# %s value (type %d\?\?)", opt->name, opt->type); break; } printer(arg, "\t\t# (from %s)\n", mainopt->source); } /* * print_option_list - print out options in effect from an * array of options. */ static void print_option_list(option_t *opt, printer_func printer, void *arg) { while (opt->name != NULL) { if (opt->priority != OPRIO_DEFAULT && opt->winner != (short int) -1) print_option(opt + opt->winner, opt, printer, arg); do { ++opt; } while (opt->flags & OPT_PRIOSUB); } } /* * print_options - print out what options are in effect. */ void print_options(printer, arg) printer_func printer; void *arg; { struct option_list *list; int i; printer(arg, "pppd options in effect:\n"); print_option_list(general_options, printer, arg); print_option_list(auth_options, printer, arg); for (list = extra_options; list != NULL; list = list->next) print_option_list(list->options, printer, arg); print_option_list(the_channel->options, printer, arg); for (i = 0; protocols[i] != NULL; ++i) print_option_list(protocols[i]->options, printer, arg); } /* * usage - print out a message telling how to use the program. */ static void usage() { if (phase == PHASE_INITIALIZE) fprintf(stderr, usage_string, VERSION, 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\n", VERSION); 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[1024]; #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); syslog(LOG_ERR, "%s", buf); } #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 \. * \ 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; quoted = 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; } /* * 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((unsigned char)c)) { /* * \ddd octal sequence */ value = 0; for (n = 0; n < 3 && isoctal((unsigned char)c); ++n) { value = (value << 3) + (c & 07); c = getc(f); } got = 1; break; } if (c == 'x') { /* * \x sequence */ value = 0; c = getc(f); for (n = 0; n < 2 && isxdigit((unsigned char)c); ++n) { digit = toupper((unsigned char)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) { word[len] = value; ++len; } if (!got) c = getc(f); continue; } /* * Backslash starts a new escape sequence. */ if (c == '\\') { escape = 1; c = getc(f); continue; } /* * Not escaped: check for the start or end of a quoted * section and see if we've reached the end of the word. */ if (quoted) { if (c == quoted) { quoted = 0; c = getc(f); continue; } } else if (c == '"' || c == '\'') { quoted = c; c = getc(f); continue; } else if (isspace(c) || c == '#') { ungetc (c, f); break; } /* * An ordinary character: store it in the word and get another. */ if (len < MAXWORDLEN) { 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; if (quoted) option_error("warning: quoted word runs to end of file (%.20s...)", filename, word); } /* * 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 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); ok = options_from_file(fname, 1, 1, 1); free(fname); return ok; } #ifdef PPP_FILTER /* * setpassfilter_in - Set the pass filter for incoming packets */ static int setpassfilter_in(argv) char **argv; { pcap_t *pc; int ret = 1; pc = pcap_open_dead(DLT_PPP_PPPD, 65535); if (pcap_compile(pc, &pass_filter_in, *argv, 1, netmask) == -1) { option_error("error in pass-filter-in expression: %s\n", pcap_geterr(pc)); ret = 0; } pcap_close(pc); return ret; } /* * setpassfilter_out - Set the pass filter for outgoing packets */ static int setpassfilter_out(argv) char **argv; { pcap_t *pc; int ret = 1; pc = pcap_open_dead(DLT_PPP_PPPD, 65535); if (pcap_compile(pc, &pass_filter_out, *argv, 1, netmask) == -1) { option_error("error in pass-filter-out expression: %s\n", pcap_geterr(pc)); ret = 0; } pcap_close(pc); return ret; } /* * setactivefilter_in - Set the active filter for incoming packets */ static int setactivefilter_in(argv) char **argv; { pcap_t *pc; int ret = 1; pc = pcap_open_dead(DLT_PPP_PPPD, 65535); if (pcap_compile(pc, &active_filter_in, *argv, 1, netmask) == -1) { option_error("error in active-filter expression: %s\n", pcap_geterr(pc)); ret = 0; } pcap_close(pc); return ret; } /* * setactivefilter_out - Set the active filter for outgoing packets */ static int setactivefilter_out(argv) char **argv; { pcap_t *pc; int ret = 1; pc = pcap_open_dead(DLT_PPP_PPPD, 65535); if (pcap_compile(pc, &active_filter_out, *argv, 1, netmask) == -1) { option_error("error in active-filter expression: %s\n", pcap_geterr(pc)); ret = 0; } pcap_close(pc); return ret; } #endif /* * setdomain - Set domain name to append to hostname */ static int setdomain(argv) char **argv; { gethostname(hostname, MAXNAMELEN); if (**argv != 0) { if (**argv != '.') strncat(hostname, ".", MAXNAMELEN - strlen(hostname)); domain = hostname + strlen(hostname); strncat(hostname, *argv, MAXNAMELEN - strlen(hostname)); } hostname[MAXNAMELEN-1] = 0; return (1); } static int setlogfile(argv) char **argv; { int fd, err; uid_t euid; euid = geteuid(); if (!privileged_option && seteuid(getuid()) == -1) { option_error("unable to drop permissions to open %s: %m", *argv); return 0; } 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(euid) == -1) fatal("unable to regain privileges: %m"); if (fd < 0) { errno = err; option_error("Can't open log file %s: %m", *argv); return 0; } strlcpy(logfile_name, *argv, sizeof(logfile_name)); if (logfile_fd >= 0) close(logfile_fd); logfile_fd = fd; log_to_fd = fd; log_default = 0; return 1; } #ifdef MAXOCTETS static int setmodir(argv) char **argv; { if(*argv == NULL) return 0; if(!strcmp(*argv,"in")) { maxoctets_dir = PPP_OCTETS_DIRECTION_IN; } else if (!strcmp(*argv,"out")) { maxoctets_dir = PPP_OCTETS_DIRECTION_OUT; } else if (!strcmp(*argv,"max")) { maxoctets_dir = PPP_OCTETS_DIRECTION_MAXOVERAL; } else { maxoctets_dir = PPP_OCTETS_DIRECTION_SUM; } return 1; } #endif #ifdef PLUGIN static int loadplugin(argv) char **argv; { char *arg = *argv; void *handle; const char *err; void (*init) __P((void)); char *path = arg; const char *vers; if (strchr(arg, '/') == 0) { const char *base = _PATH_PLUGIN; int l = strlen(base) + strlen(arg) + 2; path = malloc(l); if (path == 0) novm("plugin file path"); strlcpy(path, base, l); strlcat(path, "/", l); strlcat(path, arg, l); } handle = dlopen(path, RTLD_GLOBAL | RTLD_NOW); if (handle == 0) { err = dlerror(); if (err != 0) option_error("%s", err); option_error("Couldn't load plugin %s", arg); goto err; } init = (void (*)(void))dlsym(handle, "plugin_init"); if (init == 0) { option_error("%s has no initialization entry point", arg); goto errclose; } vers = (const char *) dlsym(handle, "pppd_version"); if (vers == 0) { warn("Warning: plugin %s has no version information", arg); } else if (strcmp(vers, VERSION) != 0) { option_error("Plugin %s is for pppd version %s, this is %s", arg, vers, VERSION); goto errclose; } info("Plugin %s loaded.", arg); (*init)(); return 1; errclose: dlclose(handle); err: if (path != arg) free(path); return 0; } #endif /* PLUGIN */ /* * Set an environment variable specified by the user. */ static int user_setenv(argv) char **argv; { char *arg = argv[0]; char *eqp; struct userenv *uep, **insp; if ((eqp = strchr(arg, '=')) == NULL) { option_error("missing = in name=value: %s", arg); return 0; } if (eqp == arg) { option_error("missing variable name: %s", arg); return 0; } for (uep = userenv_list; uep != NULL; uep = uep->ue_next) { int nlen = strlen(uep->ue_name); if (nlen == (eqp - arg) && strncmp(arg, uep->ue_name, nlen) == 0) break; } /* Ignore attempts by unprivileged users to override privileged sources */ if (uep != NULL && !privileged_option && uep->ue_priv) return 1; /* The name never changes, so allocate it with the structure */ if (uep == NULL) { uep = malloc(sizeof (*uep) + (eqp-arg)); strncpy(uep->ue_name, arg, eqp-arg); uep->ue_name[eqp-arg] = '\0'; uep->ue_next = NULL; insp = &userenv_list; while (*insp != NULL) insp = &(*insp)->ue_next; *insp = uep; } else { struct userenv *uep2; for (uep2 = userenv_list; uep2 != NULL; uep2 = uep2->ue_next) { if (uep2 != uep && !uep2->ue_isset) break; } if (uep2 == NULL && !uep->ue_isset) find_option("unset")->flags |= OPT_NOPRINT; free(uep->ue_value); } uep->ue_isset = 1; uep->ue_priv = privileged_option; uep->ue_source = option_source; uep->ue_value = strdup(eqp + 1); curopt->flags &= ~OPT_NOPRINT; return 1; } static void user_setprint(opt, printer, arg) option_t *opt; printer_func printer; void *arg; { struct userenv *uep, *uepnext; uepnext = userenv_list; while (uepnext != NULL && !uepnext->ue_isset) uepnext = uepnext->ue_next; while ((uep = uepnext) != NULL) { uepnext = uep->ue_next; while (uepnext != NULL && !uepnext->ue_isset) uepnext = uepnext->ue_next; (*printer)(arg, "%s=%s", uep->ue_name, uep->ue_value); if (uepnext != NULL) (*printer)(arg, "\t\t# (from %s)\n%s ", uep->ue_source, opt->name); else opt->source = uep->ue_source; } } static int user_unsetenv(argv) char **argv; { struct userenv *uep, **insp; char *arg = argv[0]; if (strchr(arg, '=') != NULL) { option_error("unexpected = in name: %s", arg); return 0; } if (arg == NULL) { option_error("missing variable name for unset"); return 0; } for (uep = userenv_list; uep != NULL; uep = uep->ue_next) { if (strcmp(arg, uep->ue_name) == 0) break; } /* Ignore attempts by unprivileged users to override privileged sources */ if (uep != NULL && !privileged_option && uep->ue_priv) return 1; /* The name never changes, so allocate it with the structure */ if (uep == NULL) { uep = malloc(sizeof (*uep) + strlen(arg)); strcpy(uep->ue_name, arg); uep->ue_next = NULL; insp = &userenv_list; while (*insp != NULL) insp = &(*insp)->ue_next; *insp = uep; } else { struct userenv *uep2; for (uep2 = userenv_list; uep2 != NULL; uep2 = uep2->ue_next) { if (uep2 != uep && uep2->ue_isset) break; } if (uep2 == NULL && uep->ue_isset) find_option("set")->flags |= OPT_NOPRINT; free(uep->ue_value); } uep->ue_isset = 0; uep->ue_priv = privileged_option; uep->ue_source = option_source; uep->ue_value = NULL; curopt->flags &= ~OPT_NOPRINT; return 1; } static void user_unsetprint(opt, printer, arg) option_t *opt; printer_func printer; void *arg; { struct userenv *uep, *uepnext; uepnext = userenv_list; while (uepnext != NULL && uepnext->ue_isset) uepnext = uepnext->ue_next; while ((uep = uepnext) != NULL) { uepnext = uep->ue_next; while (uepnext != NULL && uepnext->ue_isset) uepnext = uepnext->ue_next; (*printer)(arg, "%s", uep->ue_name); if (uepnext != NULL) (*printer)(arg, "\t\t# (from %s)\n%s ", uep->ue_source, opt->name); else opt->source = uep->ue_source; } }