/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* PPPoE Server-mode daemon option parsing.
*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <netdb.h>
#include <stropts.h>
#include <netinet/if_ether.h>
#include "common.h"
#include "logging.h"
/*
* Client filter entry. These are linked in *reverse* order so that
* the DAG created by file inclusion nesting works as expected. Since
* the administrator who wrote the configuration expects "first
* match," this means that tests against the filter list must actually
* use "last match."
*/
struct filter_entry {
};
/*
* Note: I would like to make the strings and filters here const, but
* I can't because they have to be passed to free() during parsing. I
* could work around this with offsetof() or data copies, but it's not
* worth the effort.
*/
struct service_entry {
};
/*
* One of these is allocated per lower-level stream (device) that is
* referenced by the configuration files. The queries are received
* per device, and this structure allows us to find all of the
* services that correspond to that device.
*/
struct device_entry {
const char *de_name;
int de_nservices;
};
/*
* This is the parsed configuration. While a new configuration is
* being read, this is kept around until the new configuration is
* ready, and then it is discarded in one operation. It has an array
* of device entries (as above) -- one per referenced lower stream --
* and a pointer to the allocated parser information. The latter is
* kept around because we reuse pointers rather than reallocating and
* copying the data. There are thus multiple aliases to the dynamic
* data, and the "owner" (for purposes of freeing the storage) is
* considered to be this 'junk' list.
*/
struct option_state {
int os_ndevices;
};
/*
* This is the root pointer to the current parsed options.
* This cannot be const because it's passed to free() when reparsing
* options.
*/
/* Global settings for module-wide options. */
/*
* *******************************************************************
* Data structures generated during parsing.
*/
/* List of device names attached to one service */
struct device_list {
};
/* Entry for a single defined service. */
struct service_list {
};
/*
* Structure allocated for each file opened. File nesting is chained
* in reverse order so that global option scoping works as expected.
*/
struct per_file {
};
/* State of parser */
enum key_state {
};
/*
* Global parser state. There is one of these structures, and it
* exists only while actively parsing configuration files.
*/
struct parse_state {
};
/* Should be in a library somewhere. */
static char *
{
char *newstr;
return (NULL);
return (newstr);
}
/*
* Stop defining current service and revert to global definition.
* This resolves any implicit references to global options by copying
* ("inheriting") from the current global state.
*/
static void
{
/* If no current file, then nothing to close. */
return;
/*
* Fix up filter pointers to make DAG. First, locate
* the end of the filter list.
*/
} else {
break;
}
}
/*
* If we're in a global context, then we're about to
* open a new service, so it's time to fix up the
* filter list so that it's usable as a reference.
* Loop through files from which we were included, and
* link up filters. Note: closure may occur more than
* once here.
*/
/* We don't inherit from ourselves. */
}
}
} else {
/*
* Loop through default options in current and all
* enclosing include files. Inherit options.
*/
/* Inherit from global service options. */
}
}
}
}
}
}
}
}
}
}
}
}
/* Revert to global definitions. */
}
/* Discard a dynamic device list */
static void
{
}
}
/*
* Handle "service <name>" -- finish up previous service definition
* (if any) by copying from global state where necessary, and start
* defining new service.
*/
static int
{
/* Finish current service */
/* Start new service */
1);
return (-1);
}
/* Add to end of list */
else
/* Fill in initial service entry */
/* This is now the current service that we're defining. */
return (0);
}
/*
* Handle both "wildcard" and "nowildcard" options.
*/
static int
{
/* Allow global context to switch back and forth without error. */
logdbg("%s: extra \"%s\" ignored",
return (0);
}
return (0);
}
/*
* Handle "debug" option.
*/
/*ARGSUSED*/
static int
{
}
return (0);
}
/*
* Handle "nodebug" option.
*/
/*ARGSUSED*/
static int
{
}
return (0);
}
/*
* Handle all plain string options; "server", "pppd", "path", "extra",
* and "log".
*/
static int
{
char **cpp;
case ksServer:
break;
case ksPppd:
break;
case ksPath:
break;
case ksExtra:
break;
case ksLog:
break;
default:
assert(0);
return (-1);
}
return (0);
}
/*
* Handle "file <name>" option. Close out current service (if any)
* and begin parsing from new file.
*/
static int
{
mystrerror(errno));
return (-1);
}
return (-1);
}
/* Fill in new file structure. */
/* Start off in global context for this file. */
return (0);
}
/*
* Handle "device <list>" option.
*/
static int
{
const char *cp;
int len;
/* Can't use this option in the per-device files. */
return (0);
}
} else {
for (;;) {
str++;
if (*str == '\0')
break;
str++;
logerr("%s: cannot use %.*s in device list",
continue;
}
len + 1);
logerr("no memory for device name");
break;
}
/* Cannot use strcpy because cp isn't terminated. */
}
}
return (0);
}
/*
* Handle <list> portion of "client [except] <list>" option. Attach
* to list of filters in reverse order.
*/
static int
{
const char *cp;
int len;
/* Head of list. */
for (;;) {
str++;
if (*str == '\0')
break;
str++;
break;
if (*cp == '*') {
cp++;
} else {
break;
}
ucp++;
*mcp++ = 0xFF;
}
break;
cp++;
}
}
logerr("%s: illegal Ethernet address %.*s",
continue;
}
}
logerr("unable to allocate memory for filter");
break;
}
fen->fe_prevcopy = 0;
}
return (0);
}
/*
* Handle "user <name>" option.
*/
static int
{
char *cp;
logerr("%s: bad user name \"%s\"",
return (0);
}
} else {
}
if (myuid != 0) {
return (0);
logdbg("%s: not root; ignoring attempt to set UID %d (%s)",
return (0);
}
return (0);
}
/*
* Handle "group <name>" option.
*/
static int
{
char *cp;
logerr("%s: bad group name \"%s\"",
return (0);
}
} else {
}
if (getuid() != 0) {
logdbg("%s: not root; ignoring attempt to set GID %d (%s)",
return (0);
}
return (0);
}
/*
* This state machine is used to parse the configuration files. The
* "kwe_in" is the state in which the keyword is recognized. The
* "kwe_out" is the state that the keyword produces.
*/
struct kw_entry {
const char *kwe_word;
};
/* Wildcards only past this point. */
};
/*
* Produce a string for the keyword that would have gotten us into the
* current state.
*/
static const char *
{
return ("nothing");
}
/*
* Handle end-of-file processing -- close service, close file, revert
* to global context in previous include file nest level.
*/
static void
{
/* Must not be in the middle of parsing a multi-word sequence now. */
}
/* Put this file on the list of finished files. */
}
/* Back up to previous file, if any, and set global context. */
}
}
/*
* Dispatch a single keyword against the parser state machine or
* handle an environment variable assignment. The input is a string
* containing the single word to be dispatched.
*/
static int
{
int retv;
char *cp;
char *env;
char **evlist;
int len;
retv = 0;
return (retv);
}
}
return (0);
}
logerr("no memory for evlist");
return (0);
}
} else {
break;
evlist++;
}
logerr("cannot realloc evlist to %d",
return (0);
}
}
}
return (0);
}
return (-1);
}
/*
* Modified version of standard getenv; looks in locally-stored
* environment first. This function exists because we need to be able
* to revert to the original environment during a reread (SIGHUP), and
* the putenv() function overwrites that environment.
*/
static char *
{
int elen;
}
}
}
/*
* Expand an environment variable at the end of current buffer and
* return pointer to next spot in buffer for character append. psp
* context may be null.
*/
static char *
{
char *cpe;
char *cp;
*cp = '\0';
} else {
}
} else {
/* Should not occur. */
}
return (cp);
}
/*
* Given a character-at-a-time input function, get a delimited keyword
* from the input. This function handles the usual escape sequences,
* quoting, commenting, and environment variable expansion.
*
* The standard wordexp(3C) function isn't used here because the POSIX
* definition is hard to use, and the Solaris implementation is
* resource-intensive and insecure. The "hard-to-use" part is that
* wordexp expands only variables from the environment, and can't
* handle an environment overlay. Instead, the caller must use the
* environment without leaking storage is hard. The Solaris
* implementation invokes an undocumented extensions via
* the expanded result with pipe. This makes it slow to execute and
* exposes the string being expanded to users with access to "ps -f."
*
* psp may be null; it's used only for environment variable expansion.
* Input "flag" is 1 to ignore EOL, '#', and '$'; 0 for normal file parsing.
*
* Returns:
* 0 - keyword parsed.
* 1 - end of file; no keyword.
* 2 - end of file after this keyword.
*/
static int
{
char *kbp;
char *vnp;
char chr;
int ichr;
char kwstate;
const char *cp;
keymax--; /* Account for trailing NUL byte */
kwstate = '\0';
for (;;) {
switch (kwstate) {
case '\\': /* Start of unquoted escape sequence */
case '|': /* Start of escape sequence in double quotes */
case '~': /* Start of escape sequence in single quotes */
/* Convert the character if we can. */
if (chr == '\n')
chr = '\0';
/* Revert to previous state */
switch (kwstate) {
case '\\':
kwstate = 'A';
break;
case '|':
kwstate = '"';
break;
case '~':
kwstate = '\'';
break;
}
break;
case '"': /* In double-quote string */
/* Handle variable expansion. */
kwstate = '%';
chr = '\0';
break;
}
/* FALLTHROUGH */
case '\'': /* In single-quote string */
if (chr == '\\') {
/* Handle start of escape sequence */
chr = '\0';
break;
}
/* End of quoted string; revert to normal */
kwstate = 'A';
chr = '\0';
}
break;
case '$': /* Start of unquoted variable name */
case '%': /* Start of variable name in quoted string */
if (chr == '{') {
/* Variable name is bracketed. */
break;
}
/* FALLTHROUGH */
case '+': /* Gathering unquoted variable name */
case '*': /* Gathering variable name in quoted string */
if (chr == '$' &&
kwstate = '$';
chr = '\0';
break;
}
*kbp = '\0';
else
'A' : '"';
/* Go reinterpret in new context */
goto tryagain;
}
break;
case '{': /* Gathering bracketed, unquoted var name */
case '[': /* Gathering bracketed, quoted var name */
if (chr == '}') {
*kbp = '\0';
chr = '\0';
}
break;
case '#': /* Comment before word state */
case '@': /* Comment after word state */
/* At end of line, revert to previous state */
chr = '\0';
break;
}
chr = '\0';
break;
case '\0': /* Initial state; no word seen yet. */
break;
}
if (chr == '#') {
kwstate = '#';
break;
}
/* Start of keyword seen. */
kwstate = 'A';
/* FALLTHROUGH */
default: /* Middle of keyword parsing. */
break;
kwstate = ' ';
break;
}
chr = '\0';
break;
}
if (flag) /* Allow ignore; for string reparse */
break;
chr = '\0';
break;
}
if (chr == '$') {
chr = '\0';
}
break;
}
/*
* If we've reached a space at the end of the word,
* then we're done.
*/
break;
/*
* If there's a character to store and space
* available, then add it to the string
*/
}
*kbp = '\0';
}
return (0);
}
/*
* Fetch words from current file until all files are closed. Handles
* include files.
*/
static void
{
int retv;
0);
if (retv != 1)
if (retv != 0)
}
}
/*
* Open and parse named file. This is for the predefined
* these are missing.
*/
static void
{
/* It's ok if any of these files are missing. */
return;
}
/*
* Dispatch keywords from command line. Handles any files included
* from there.
*/
static void
{
/* The first argument (program name) can be null. */
if (--argc <= 0)
return;
while (--argc >= 0) {
}
}
/* Count length of dynamic device list */
static int
{
int ndevs;
ndevs = 0;
ndevs++;
return (ndevs);
}
/* Count number of devices named in entire file. */
static int
{
int ndevs = 0;
}
return (ndevs);
}
/* Write device names into linear array. */
static const char **
{
return (dnames);
}
/* Write all device names from file into a linear array. */
static const char **
{
}
return (dnames);
}
/* Compare device names; used with qsort */
static int
{
}
/*
* Get sorted list of unique device names among all defined and
* partially defined services in all files.
*/
static const char **
{
int ndevs;
const char **dnames;
const char **dnp;
const char **dnf;
/*
* Count number of explicitly referenced devices among all
* services (including duplicates).
*/
if (ndevs <= 0) {
return (NULL);
}
/* Sort and trim out duplicate devices. */
return (NULL);
}
/* Return array of pointers to names. */
return (dnames);
}
/*
* Convert data structures created by parsing process into data
* structures used by service dispatch. This gathers the unique
* device (lower stream) names and attaches the services available on
* each device to a list while triming duplicate services.
*/
static struct option_state *
{
int ndevs;
int nsvcs;
const char **dnames;
const char **dnp;
/*
* Parsing is now done.
*/
}
/* Link the services from all files together for easy referencing. */
else
}
/*
* Count up number of services per device, including
* duplicates but not including defaults.
*/
nsvcs = 0;
nsvcs++;
/*
* Get the unique devices referenced by all services.
*/
logdbg("no devices referenced by any service");
return (NULL);
}
ndevs = 0;
ndevs++;
/*
* Allocate room for main structure, device records, and
* per-device lists. Worst case is all devices having all
* services; that's why we allocate for nsvcs * ndevs.
*/
logerr("unable to allocate option state structure");
return (NULL);
}
/* We're going to succeed now, so steal these over. */
/* Loop over devices, install services, remove duplicates. */
se2pp++)
se_name) == 0)
break;
/*
* We retain a service if it's
* unique or if its serial
* number (position in the
* file) is greater than than
* any other.
*/
}
}
/* Count up the services on this device. */
/* Ignore devices having no services at all. */
if (dep->de_nservices > 0)
dep++;
}
/* Count up the devices. */
/* Free the list of device names */
return (osp);
}
/*
* Free storage unique to a given service. Pointers copied from other
* services are ignored.
*/
static void
{
}
}
}
/*
* Free a linked list of services.
*/
static void
{
}
}
/*
* Free a linked list of files and all services in those files.
*/
static void
{
}
}
/*
* Free an array of local environment variables.
*/
static void
{
char **evp;
char *env;
}
}
/*
* Add a new device (lower stream) to the list for which we're the
* PPPoE server.
*/
static void
{
dname);
} else {
}
}
/*
* Remove an existing device (lower stream) from the list for which we
* were the PPPoE server.
*/
static void
{
dname);
} else {
}
}
/*
* Get a list of all of the devices currently plumbed for PPPoE. This
* is used for supporting the "*" and "all" device aliases.
*/
static void
{
int i;
char *cp;
/* First pass; just allocate space for all *:pppoe* devices */
for (i = 0; ; i++) {
sizeof (ptn)) < 0) {
break;
}
break;
continue;
*cp = '\0';
break;
} else {
}
}
/* Second pass; eliminate improperly plumbed devices */
break;
} else {
}
}
/* Add in "*" so we can always handle dynamic plumbing. */
}
}
/*
* Set logging subsystem back to configured global default values.
*/
void
global_logging(void)
{
}
/*
* Handle SIGHUP -- reparse command line and all configuration files.
* When reparsing is complete, free old parsed data and replace with
* new.
*/
void
{
const char **dnames;
const char **dnp;
int cmpval;
/* Note that all per_file structures must be freeable */
return;
}
/* Default is 1 -- errors only */
/* Get list of all devices */
/* Parse options from command line and main configuration file. */
/*
* At this point, global options from the main configuration
* file are pointed to by ps_files, and options from command
* line are in argpf. We need to pull three special options
* from these -- wildcard, debug, and log. Note that the main
* options file overrides the command line. This is
* intentional. The semantics are such that the system
* behaves as though the main configuration file were
* "included" from the command line, and thus options there
* override the command line. This may seem odd, but at least
* it's self-consistent.
*/
newglobsvc.se_debug = 0;
}
/* Get the list of devices referenced by configuration above. */
/* Read per-device configuration files. */
}
/*
* Convert parsed data structures into per-device structures.
* (Invert the table.)
*/
/* If we're going to free the file name, then stop logging there. */
}
/*
* Unless an error has occurred, these pointers are normally
* all NULL. Nothing is freed until the file is re-read.
*/
/*
* Match up entries on device list. Detach devices no longer
* referenced. Attach ones now referenced. (The use of null
* pointers here may look fishy, but it actually works.
* NULL>=NULL is always true.)
*/
} else {
}
if (cur_options != NULL) {
} else {
}
newdep++;
} else {
if (cmpval < 0) {
/* Brand new device seen. */
newdep++;
} else if (cmpval == 0) {
/* Existing device; skip it. */
newdep++;
olddep++;
}
/* No else clause -- removal is below */
}
}
olddep++;
} else {
if (cmpval > 0) {
/* Old device is gone */
olddep++;
} else if (cmpval == 0) {
/* Existing device; skip it. */
newdep++;
olddep++;
}
/* No else clause -- insert handled above */
}
}
}
/* Discard existing parsed data storage. */
if (cur_options != NULL) {
}
/* Install new. */
}
/*
* Check if configured filters permit requesting client to use a given
* service. Note -- filters are stored in reverse order in order to
* make file-inclusion work as expected. Thus, the "first match"
* filter rule becomes "last match" here.
*/
static boolean_t
{
int i;
break;
if (i <= 0)
}
/*
* Assume reject by default if any positive-match
* (non-except) filters are given. Otherwise, if
* there are no positive-match filters, then
* non-matching means accept by default.
*/
return (!anynonexcept);
}
return (!lmatch->fe_isexcept);
}
/*
* Locate available service(s) based on client request. Assumes that
* outp points to a buffer of at least size PPPOE_MSGMAX. Creates a
* PPPoE response message in outp. Returns count of matched services
* and (through *srvp) a pointer to the last (or only) service. If
* some error is found in the request, an error string is added and -1
* is returned; the caller should just send the message without
* alteration.
*/
int
{
const char *cp;
int ttyp;
int tlen;
int nsvcs;
char *str;
if (cur_options == NULL)
return (0);
/* Search for named device (lower stream) in tables. */
else
break;
/*
* Return if interface not found. Zero-service case can't
* occur, since devices with no services aren't included in
* the list, but the code is just being safe here.
*/
return (0);
/*
* Loop over tags in client message and process them.
* Services must be matched against our list. Host-Uniq and
* Relay-Session-Id must be copied to the reply. All others
* must be discarded.
*/
nsvcs = 0;
break;
switch (ttyp) {
case POETT_SERVICE: /* Service-Name */
/*
* Allow only one. (Note that this test works
* because there's always at least one service
* per device; otherwise, the device is
* removed from the list.)
*/
if (nsvcs != -1)
"Too many Service-Name tags");
nsvcs = -1;
break;
}
if (tlen == 0) {
/*
* If config specifies "nowild" in a
* global context, then we don't
* respond to wildcard PADRs. The
* client must know the exact service
* name to get access.
*/
continue;
/*
* RFC requires that PADO includes the
* wildcard service request in response
* to PADI.
*/
nsvcs++;
/* If PADR, then one is enough */
if (!ispadi)
break;
}
/* Just for generating error messages */
if (nsvcs == 0)
} else {
/*
* Clients's requested service must appear in
* reply.
*/
/* Requested specific service; find it. */
tlen) == 0) {
nsvcs++;
}
break;
}
}
}
/*
* Allow service definition to override
* AC-Name (concentrator [server] name) field.
*/
str = "Solaris PPPoE";
}
break;
/* Ones we should discard */
case POETT_ACCESS: /* AC-Name */
case POETT_COOKIE: /* AC-Cookie */
case POETT_NAMERR: /* Service-Name-Error */
case POETT_SYSERR: /* AC-System-Error */
case POETT_GENERR: /* Generic-Error */
case POETT_HURL: /* Host-URL */
case POETT_MOTM: /* Message-Of-The-Minute */
case POETT_RTEADD: /* IP-Route-Add */
case POETT_VENDOR: /* Vendor-Specific */
case POETT_MULTI: /* Multicast-Capable */
default:
break;
/* Ones we should copy */
case POETT_UNIQ: /* Host-Uniq */
case POETT_RELAY: /* Relay-Session-Id */
break;
}
}
return (nsvcs);
}
/*
* Like fgetc, but reads from a string.
*/
static int
{
if (**cpp == '\0')
return (EOF);
return (*(*cpp)++);
}
/*
* Given a service structure, launch pppd. Called by handle_input()
* in pppoed.c if locate_service() [above] finds exactly one service
* matching a PADR.
*/
int
{
const char *path;
const char *extra;
const char *pppd;
const char *cp;
int newtun;
const char **cpp;
char *sptr;
char *spv;
int slen;
int retv;
/* Get tunnel driver connection for new PPP session. */
if (newtun == -1)
goto syserr;
/* Set this session up for standard PPP and client's address. */
0)
goto syserr;
0)
goto syserr;
/* Attach the requested lower stream. */
goto syserr;
goto syserr;
goto syserr;
/*
* Use syslog only in order to avoid mixing log messages
* in regular files.
*/
pppd = "";
/* Concatenate these. */
goto bail_out;
/* Parse out into arguments */
(void *)&spv, 1);
if (retv != 1)
if (retv != 0)
break;
}
goto bail_out;
/*
* stderr.
*/
goto bail_out;
goto bail_out;
if (newtun > 1)
if (tunfd > 1)
(void) close(2);
O_CREAT, 0600);
/*
* Change GID first, for obvious reasons. Note that
* we log any problems to syslog, not the errors file.
* The errors file is intended for problems in the
* exec'd program.
*/
reopen_log();
goto logged;
}
reopen_log();
goto logged;
}
/* Run pppd */
errno = 0;
newtun = 0;
/*
* Exec failure; attempt to log the problem and send a
* PADT to the client so that he knows the session
* went south.
*/
reopen_log();
(void) sleep(1);
mystrerror(errno));
}
exit(1);
}
/* Give session ID to client in reply. */
return (1);
/* Peer doesn't know session ID yet; hope for the best. */
if (newtun >= 0)
return (0);
}
/*
* This is pretty awful -- it uses recursion to print a simple list.
* It's just for debug, though, and does a reasonable job of printing
* the filters in the right order.
*/
static void
{
}
/*
* Write summary of parsed configuration data to given file.
*/
void
{
int i, j;
"Global debug level %d, log to %s; current level %d\n",
if (cur_options == NULL) {
return;
}
else
}
}
}
}
}