1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * PPPoE Server-mode daemon option parsing.
23 *
24 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
25 * Use is subject to license terms.
26 * Copyright (c) 2016 by Delphix. All rights reserved.
27 */
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <unistd.h>
32#include <assert.h>
33#include <ctype.h>
34#include <string.h>
35#include <sys/types.h>
36#include <fcntl.h>
37#include <pwd.h>
38#include <grp.h>
39#include <errno.h>
40#include <netdb.h>
41#include <stropts.h>
42#include <sys/stat.h>
43#include <sys/socket.h>
44#include <net/if.h>
45#include <netinet/in.h>
46#include <netinet/if_ether.h>
47#include <net/sppptun.h>
48
49#include "common.h"
50#include "logging.h"
51
52#define	MAX_KEYWORD	4096	/* Maximum token length */
53#define	MAX_NEST	32	/* Maximum ${$sub} nesting */
54#define	MAXARGS		256	/* Maximum number of pppd arguments */
55
56/*
57 * Client filter entry.  These are linked in *reverse* order so that
58 * the DAG created by file inclusion nesting works as expected.  Since
59 * the administrator who wrote the configuration expects "first
60 * match," this means that tests against the filter list must actually
61 * use "last match."
62 */
63struct filter_entry {
64	struct filter_entry *fe_prev;	/* Previous filter in list */
65	struct ether_addr fe_mac;	/* MAC address */
66	struct ether_addr fe_mask;	/* Mask for above address test */
67	uchar_t fe_isexcept;	/* invert sense; exclude matching clients */
68	uchar_t fe_prevcopy;		/* fe_prev points to copied list */
69	uchar_t fe_unused[2];		/* padding */
70};
71
72/*
73 * Note: I would like to make the strings and filters here const, but
74 * I can't because they have to be passed to free() during parsing.  I
75 * could work around this with offsetof() or data copies, but it's not
76 * worth the effort.
77 */
78struct service_entry {
79	const char *se_name;		/* Name of service */
80	struct filter_entry *se_flist;	/* Pointer to list of client filters */
81	uint_t se_flags;		/* SEF_* flags (below) */
82	int se_debug;			/* Debug level (0=nodebug) */
83	char *se_server;		/* Server (AC) name */
84	char *se_pppd;			/* Options for pppd */
85	char *se_path;			/* Path to pppd executable */
86	char *se_extra;			/* Extra options */
87	char *se_log;			/* Log file */
88	uid_t se_uid;			/* User ID */
89	gid_t se_gid;			/* Group ID */
90};
91
92#define	SEF_WILD	0x00000001	/* Offer in wildcard reply */
93#define	SEF_NOWILD	0x00000002	/* Don't offer in wildcard */
94#define	SEF_CFLIST	0x00000004	/* se_flist copied from global */
95#define	SEF_CSERVER	0x00000008	/* se_server copied from global */
96#define	SEF_CPPPD	0x00000010	/* se_pppd copied from global */
97#define	SEF_CPATH	0x00000020	/* se_path copied from global */
98#define	SEF_CEXTRA	0x00000040	/* se_extra copied from global */
99#define	SEF_CLOG	0x00000080	/* se_log copied from global */
100#define	SEF_UIDSET	0x00000100	/* se_uid has been set */
101#define	SEF_GIDSET	0x00000200	/* se_gid has been set */
102#define	SEF_DEBUGCLR	0x00000400	/* do not add se_debug from global */
103#define	SEF_CDEV	0x00000800	/* copied devs (parse only) */
104
105/*
106 * One of these is allocated per lower-level stream (device) that is
107 * referenced by the configuration files.  The queries are received
108 * per device, and this structure allows us to find all of the
109 * services that correspond to that device.
110 */
111struct device_entry {
112	const char *de_name;
113	const struct service_entry **de_services;
114	int de_nservices;
115};
116
117/*
118 * This is the parsed configuration.  While a new configuration is
119 * being read, this is kept around until the new configuration is
120 * ready, and then it is discarded in one operation.  It has an array
121 * of device entries (as above) -- one per referenced lower stream --
122 * and a pointer to the allocated parser information.  The latter is
123 * kept around because we reuse pointers rather than reallocating and
124 * copying the data.  There are thus multiple aliases to the dynamic
125 * data, and the "owner" (for purposes of freeing the storage) is
126 * considered to be this 'junk' list.
127 */
128struct option_state {
129	const struct device_entry *os_devices;
130	int os_ndevices;
131	struct per_file *os_pfjunk;	/* Kept for deallocation */
132	char **os_evjunk;		/* ditto */
133};
134
135/*
136 * This is the root pointer to the current parsed options.
137 * This cannot be const because it's passed to free() when reparsing
138 * options.
139 */
140static struct option_state *cur_options;
141
142/* Global settings for module-wide options. */
143static struct service_entry glob_svc;
144
145/*
146 * *******************************************************************
147 * Data structures generated during parsing.
148 */
149
150/* List of device names attached to one service */
151struct device_list {
152	struct device_list *dl_next;
153	const char *dl_name;		/* Name of one device */
154};
155
156/* Entry for a single defined service. */
157struct service_list {
158	struct service_entry sl_entry;	/* Parsed service data */
159	struct service_list *sl_next;	/* Next service entry */
160	struct parse_state *sl_parse;	/* Back pointer to state */
161	struct device_list *sl_dev;	/* List of devices */
162	int sl_serial;			/* Serial number (conflict resolve) */
163};
164#define	SESERIAL(x)	((struct service_list *)&(x))->sl_serial
165#define	ISGLOBAL(x)	((x) == &(x)->sl_parse->ps_cfile->pf_global)
166
167/*
168 * Structure allocated for each file opened.  File nesting is chained
169 * in reverse order so that global option scoping works as expected.
170 */
171struct per_file {
172	struct per_file *pf_prev;	/* Back chain */
173	struct service_list pf_global;	/* Global (default) service context */
174	struct service_list *pf_svc;	/* List of services */
175	struct service_list *pf_svc_last;
176	FILE *pf_input;			/* File for input */
177	const char *pf_name;		/* File name */
178	int pf_nsvc;			/* Count of services */
179};
180
181/* State of parser */
182enum key_state {
183	ksDefault, ksService, ksDevice, ksClient, ksClientE, ksServer,
184	ksPppd, ksFile, ksPath, ksExtra, ksLog, ksUser, ksGroup
185};
186
187/*
188 * Global parser state.  There is one of these structures, and it
189 * exists only while actively parsing configuration files.
190 */
191struct parse_state {
192	enum key_state ps_state;	/* Parser state */
193	int ps_serial;			/* Service serial number */
194	struct per_file *ps_files;	/* Parsed files */
195	struct per_file *ps_cfile;	/* Current file */
196	struct service_list *ps_csvc;	/* Current service */
197	struct device_list *ps_star;	/* Wildcard device */
198	int ps_flags;			/* PSF_* below */
199	char **ps_evlist;		/* allocated environment variables */
200	int ps_evsize;			/* max length; for realloc */
201};
202
203#define	PSF_PERDEV	0x0001		/* In a per-device file */
204#define	PSF_SETLEVEL	0x0002		/* Set log level along the way */
205
206/* Should be in a library somewhere. */
207static char *
208strsave(const char *str)
209{
210	char *newstr;
211
212	if (str == NULL)
213		return (NULL);
214	newstr = (char *)malloc(strlen(str) + 1);
215	if (newstr != NULL)
216		(void) strcpy(newstr, str);
217	return (newstr);
218}
219
220/*
221 * Stop defining current service and revert to global definition.
222 * This resolves any implicit references to global options by copying
223 * ("inheriting") from the current global state.
224 */
225static void
226close_service(struct service_list *slp)
227{
228	struct parse_state *psp;
229	struct per_file *cfile;
230	struct service_entry *sep;
231	struct service_entry *sedefp;
232	struct filter_entry *fep;
233
234	assert(slp != NULL);
235	psp = slp->sl_parse;
236	cfile = psp->ps_cfile;
237
238	/* If no current file, then nothing to close. */
239	if (cfile == NULL)
240		return;
241
242	sep = &slp->sl_entry;
243
244	/*
245	 * Fix up filter pointers to make DAG.  First, locate
246	 * the end of the filter list.
247	 */
248	if (sep->se_flags & SEF_CFLIST) {
249		sep->se_flist = fep = NULL;
250	} else {
251		for (fep = sep->se_flist; fep != NULL; fep = fep->fe_prev)
252			if (fep->fe_prev == NULL || fep->fe_prevcopy) {
253				fep->fe_prev = NULL;
254				break;
255			}
256	}
257	if (slp == &cfile->pf_global) {
258		/*
259		 * If we're in a global context, then we're about to
260		 * open a new service, so it's time to fix up the
261		 * filter list so that it's usable as a reference.
262		 * Loop through files from which we were included, and
263		 * link up filters.  Note: closure may occur more than
264		 * once here.
265		 */
266		/* We don't inherit from ourselves. */
267		cfile = cfile->pf_prev;
268		while (cfile != NULL) {
269			if (fep == NULL) {
270				sep->se_flist = fep =
271				    cfile->pf_global.sl_entry.se_flist;
272				sep->se_flags |= SEF_CFLIST;
273			} else if (fep->fe_prev == NULL) {
274				fep->fe_prev =
275				    cfile->pf_global.sl_entry.se_flist;
276				fep->fe_prevcopy = 1;
277			}
278			cfile = cfile->pf_prev;
279		}
280	} else {
281		/*
282		 * Loop through default options in current and all
283		 * enclosing include files.  Inherit options.
284		 */
285		logdbg("service %s ends", slp->sl_entry.se_name);
286		while (cfile != NULL) {
287			/* Inherit from global service options. */
288			if (slp->sl_dev == NULL) {
289				slp->sl_dev = cfile->pf_global.sl_dev;
290				sep->se_flags |= SEF_CDEV;
291			}
292			sedefp = &cfile->pf_global.sl_entry;
293			if (fep == NULL) {
294				sep->se_flist = fep = sedefp->se_flist;
295				sep->se_flags |= SEF_CFLIST;
296			} else if (fep->fe_prev == NULL) {
297				fep->fe_prev = sedefp->se_flist;
298				fep->fe_prevcopy = 1;
299			}
300			if (sep->se_server == NULL) {
301				sep->se_server = sedefp->se_server;
302				sep->se_flags |= SEF_CSERVER;
303			}
304			if (sep->se_pppd == NULL) {
305				sep->se_pppd = sedefp->se_pppd;
306				sep->se_flags |= SEF_CPPPD;
307			}
308			if (sep->se_path == NULL) {
309				sep->se_path = sedefp->se_path;
310				sep->se_flags |= SEF_CPATH;
311			}
312			if (sep->se_extra == NULL) {
313				sep->se_extra = sedefp->se_extra;
314				sep->se_flags |= SEF_CEXTRA;
315			}
316			if (sep->se_log == NULL) {
317				sep->se_log = sedefp->se_log;
318				sep->se_flags |= SEF_CLOG;
319			}
320			if (!(sep->se_flags & SEF_UIDSET) &&
321			    (sedefp->se_flags & SEF_UIDSET)) {
322				sep->se_uid = sedefp->se_uid;
323				sep->se_flags |= SEF_UIDSET;
324			}
325			if (!(sep->se_flags & SEF_GIDSET) &&
326			    (sedefp->se_flags & SEF_GIDSET)) {
327				sep->se_gid = sedefp->se_gid;
328				sep->se_flags |= SEF_GIDSET;
329			}
330			if (!(sep->se_flags & (SEF_WILD|SEF_NOWILD)))
331				sep->se_flags |= sedefp->se_flags &
332				    (SEF_WILD|SEF_NOWILD);
333			if (!(sep->se_flags & SEF_DEBUGCLR)) {
334				sep->se_debug += sedefp->se_debug;
335				sep->se_flags |= sedefp->se_flags &
336				    SEF_DEBUGCLR;
337			}
338			cfile = cfile->pf_prev;
339		}
340	}
341	/* Revert to global definitions. */
342	psp->ps_csvc = &psp->ps_cfile->pf_global;
343}
344
345/* Discard a dynamic device list */
346static void
347free_device_list(struct device_list *dlp)
348{
349	struct device_list *dln;
350
351	while (dlp != NULL) {
352		dln = dlp->dl_next;
353		free(dlp);
354		dlp = dln;
355	}
356}
357
358/*
359 * Handle "service <name>" -- finish up previous service definition
360 * (if any) by copying from global state where necessary, and start
361 * defining new service.
362 */
363static int
364set_service(struct service_list *slp, const char *str)
365{
366	struct parse_state *psp;
367	struct per_file *cfile;
368
369	/* Finish current service */
370	close_service(slp);
371
372	/* Start new service */
373	psp = slp->sl_parse;
374	slp = (struct service_list *)calloc(sizeof (*slp) + strlen(str) + 1,
375	    1);
376	if (slp == NULL) {
377		logerr("no memory for service \"%s\"", str);
378		return (-1);
379	}
380
381	/* Add to end of list */
382	cfile = psp->ps_cfile;
383	if (cfile->pf_svc_last == NULL)
384		cfile->pf_svc = slp;
385	else
386		cfile->pf_svc_last->sl_next = slp;
387	cfile->pf_svc_last = slp;
388	cfile->pf_nsvc++;
389
390	/* Fill in initial service entry */
391	slp->sl_entry.se_name = (const char *)(slp+1);
392	(void) strcpy((char *)(slp+1), str);
393	logdbg("service %s begins", slp->sl_entry.se_name);
394	slp->sl_serial = psp->ps_serial++;
395	slp->sl_parse = psp;
396
397	/* This is now the current service that we're defining. */
398	psp->ps_csvc = slp;
399	return (0);
400}
401
402/*
403 * Handle both "wildcard" and "nowildcard" options.
404 */
405static int
406set_wildcard(struct service_list *slp, const char *str)
407{
408	/* Allow global context to switch back and forth without error. */
409	if (!ISGLOBAL(slp) &&
410	    (slp->sl_entry.se_flags & (SEF_WILD|SEF_NOWILD))) {
411		logdbg("%s: extra \"%s\" ignored",
412		    slp->sl_parse->ps_cfile->pf_name, str);
413		return (0);
414	}
415	slp->sl_entry.se_flags =
416	    (slp->sl_entry.se_flags & ~(SEF_WILD|SEF_NOWILD)) |
417	    (*str == 'n' ? SEF_NOWILD : SEF_WILD);
418	return (0);
419}
420
421/*
422 * Handle "debug" option.
423 */
424/*ARGSUSED*/
425static int
426set_debug(struct service_list *slp, const char *str)
427{
428	slp->sl_entry.se_debug++;
429	if (ISGLOBAL(slp) && (slp->sl_parse->ps_flags & PSF_SETLEVEL)) {
430		log_level = slp->sl_entry.se_debug;
431	}
432	return (0);
433}
434
435/*
436 * Handle "nodebug" option.
437 */
438/*ARGSUSED*/
439static int
440set_nodebug(struct service_list *slp, const char *str)
441{
442	slp->sl_entry.se_flags |= SEF_DEBUGCLR;
443	slp->sl_entry.se_debug = 0;
444	if (ISGLOBAL(slp) && (slp->sl_parse->ps_flags & PSF_SETLEVEL)) {
445		log_level = slp->sl_entry.se_debug;
446	}
447	return (0);
448}
449
450/*
451 * Handle all plain string options; "server", "pppd", "path", "extra",
452 * and "log".
453 */
454static int
455set_string(struct service_list *slp, const char *str)
456{
457	char **cpp;
458
459	assert(!(slp->sl_entry.se_flags &
460	    (SEF_CSERVER|SEF_CPPPD|SEF_CPATH|SEF_CEXTRA|SEF_CLOG)));
461	switch (slp->sl_parse->ps_state) {
462	case ksServer:
463		cpp = &slp->sl_entry.se_server;
464		break;
465	case ksPppd:
466		cpp = &slp->sl_entry.se_pppd;
467		break;
468	case ksPath:
469		cpp = &slp->sl_entry.se_path;
470		break;
471	case ksExtra:
472		cpp = &slp->sl_entry.se_extra;
473		break;
474	case ksLog:
475		cpp = &slp->sl_entry.se_log;
476		break;
477	default:
478		assert(0);
479		return (-1);
480	}
481	if (*cpp != NULL)
482		free(*cpp);
483	*cpp = strsave(str);
484	return (0);
485}
486
487/*
488 * Handle "file <name>" option.  Close out current service (if any)
489 * and begin parsing from new file.
490 */
491static int
492set_file(struct service_list *slp, const char *str)
493{
494	FILE *fp;
495	struct per_file *pfp;
496	struct parse_state *psp;
497
498	close_service(slp);
499
500	if ((fp = fopen(str, "r")) == NULL) {
501		logwarn("%s: %s: %s", slp->sl_parse->ps_cfile->pf_name, str,
502		    mystrerror(errno));
503		return (-1);
504	}
505	pfp = (struct per_file *)calloc(sizeof (*pfp) + strlen(str) + 1, 1);
506	if (pfp == NULL) {
507		logerr("no memory for parsing file %s", str);
508		(void) fclose(fp);
509		return (-1);
510	}
511	logdbg("config file %s open", str);
512
513	/* Fill in new file structure. */
514	pfp->pf_name = (const char *)(pfp+1);
515	(void) strcpy((char *)(pfp+1), str);
516	pfp->pf_input = fp;
517	psp = slp->sl_parse;
518	pfp->pf_prev = psp->ps_cfile;
519	psp->ps_cfile = pfp;
520
521	/* Start off in global context for this file. */
522	psp->ps_csvc = &pfp->pf_global;
523	pfp->pf_global.sl_parse = psp;
524	pfp->pf_global.sl_entry.se_name = "<global>";
525	return (0);
526}
527
528/*
529 * Handle "device <list>" option.
530 */
531static int
532set_device(struct service_list *slp, const char *str)
533{
534	struct parse_state *psp = slp->sl_parse;
535	struct device_list *dlp;
536	struct device_list *dln;
537	struct device_list **dlpp;
538	const char *cp;
539	int len;
540
541	/* Can't use this option in the per-device files. */
542	if (psp->ps_flags & PSF_PERDEV) {
543		logerr("\"device %s\" ignored in %s", str,
544		    psp->ps_cfile->pf_name);
545		return (0);
546	}
547
548	if (strcmp(str, "*") == 0 || strcmp(str, "all") == 0) {
549		if (!(slp->sl_entry.se_flags & SEF_CDEV))
550			free_device_list(slp->sl_dev);
551		slp->sl_dev = psp->ps_star;
552		slp->sl_entry.se_flags |= SEF_CDEV;
553	} else {
554		dlpp = &dlp;
555		for (;;) {
556			while (isspace(*str) || *str == ',')
557				str++;
558			if (*str == '\0')
559				break;
560			cp = str;
561			while (*str != '\0' && !isspace(*str) && *str != ',')
562				str++;
563			len = str - cp;
564			if ((len == 1 && *cp == '*') ||
565			    (len == 3 && strncmp(cp, "all", 3) == 0)) {
566				logerr("%s: cannot use %.*s in device list",
567				    psp->ps_cfile->pf_name, len, cp);
568				continue;
569			}
570			dln = (struct device_list *)malloc(sizeof (*dln) +
571			    len + 1);
572			if (dln == NULL) {
573				logerr("no memory for device name");
574				break;
575			}
576			dln->dl_name = (const char *)(dln + 1);
577			/* Cannot use strcpy because cp isn't terminated. */
578			(void) memcpy(dln + 1, cp, len);
579			((char *)(dln + 1))[len] = '\0';
580			logdbg("%s: device %s", psp->ps_cfile->pf_name,
581			    dln->dl_name);
582			*dlpp = dln;
583			dlpp = &dln->dl_next;
584		}
585		*dlpp = NULL;
586
587		dlpp = &slp->sl_dev;
588		if (!(slp->sl_entry.se_flags & SEF_CDEV))
589			while (*dlpp != NULL)
590				dlpp = &(*dlpp)->dl_next;
591		*dlpp = dlp;
592		slp->sl_entry.se_flags &= ~SEF_CDEV;
593	}
594
595	return (0);
596}
597
598/*
599 * Handle <list> portion of "client [except] <list>" option.  Attach
600 * to list of filters in reverse order.
601 */
602static int
603set_client(struct service_list *slp, const char *str)
604{
605	struct parse_state *psp = slp->sl_parse;
606	struct filter_entry *fep;
607	struct filter_entry *fen;
608	const char *cp;
609	int len;
610	char hbuf[MAXHOSTNAMELEN];
611	struct ether_addr ea;
612	struct ether_addr mask;
613	uchar_t *ucp;
614	uchar_t *mcp;
615
616	/* Head of list. */
617	fep = slp->sl_entry.se_flist;
618	for (;;) {
619		while (isspace(*str) || *str == ',')
620			str++;
621		if (*str == '\0')
622			break;
623		cp = str;
624		while (*str != '\0' && !isspace(*str) && *str != ',')
625			str++;
626		len = str - cp;
627		(void) memcpy(hbuf, cp, len);
628		hbuf[len] = '\0';
629		mcp = mask.ether_addr_octet;
630		mcp[0] = mcp[1] = mcp[2] = mcp[3] = mcp[4] = mcp[5] = 0xFF;
631		if (ether_hostton(hbuf, &ea) != 0) {
632			ucp = ea.ether_addr_octet;
633			while (cp < str) {
634				if (ucp >= ea.ether_addr_octet + sizeof (ea))
635					break;
636				if (*cp == '*') {
637					*mcp++ = *ucp++ = 0;
638					cp++;
639				} else {
640					if (!isxdigit(*cp))
641						break;
642					*ucp = hexdecode(*cp++);
643					if (cp < str && isxdigit(*cp)) {
644						*ucp = (*ucp << 4) |
645						    hexdecode(*cp++);
646					}
647					ucp++;
648					*mcp++ = 0xFF;
649				}
650				if (cp < str) {
651					if (*cp != ':' || cp + 1 == str)
652						break;
653					cp++;
654				}
655			}
656			if (cp < str) {
657				logerr("%s: illegal Ethernet address %.*s",
658				    psp->ps_cfile->pf_name, len, cp);
659				continue;
660			}
661		}
662		fen = (struct filter_entry *)malloc(sizeof (*fen));
663		if (fen == NULL) {
664			logerr("unable to allocate memory for filter");
665			break;
666		}
667		fen->fe_isexcept = psp->ps_state == ksClientE;
668		fen->fe_prevcopy = 0;
669		(void) memcpy(&fen->fe_mac, &ea, sizeof (fen->fe_mac));
670		(void) memcpy(&fen->fe_mask, &mask, sizeof (fen->fe_mask));
671		fen->fe_prev = fep;
672		fep = fen;
673	}
674	slp->sl_entry.se_flist = fep;
675	return (0);
676}
677
678/*
679 * Handle "user <name>" option.
680 */
681static int
682set_user(struct service_list *slp, const char *str)
683{
684	struct passwd *pw;
685	char *cp;
686	uid_t myuid, uid;
687
688	if ((pw = getpwnam(str)) == NULL) {
689		uid = (uid_t)strtol(str, &cp, 0);
690		if (str == cp || *cp != '\0') {
691			logerr("%s:  bad user name \"%s\"",
692			    slp->sl_parse->ps_cfile->pf_name, str);
693			return (0);
694		}
695	} else {
696		uid = pw->pw_uid;
697	}
698	slp->sl_entry.se_uid = uid;
699	myuid = getuid();
700	if (myuid != 0) {
701		if (myuid == uid)
702			return (0);
703		logdbg("%s:  not root; ignoring attempt to set UID %d (%s)",
704		    slp->sl_parse->ps_cfile->pf_name, uid, str);
705		return (0);
706	}
707	slp->sl_entry.se_flags |= SEF_UIDSET;
708	return (0);
709}
710
711/*
712 * Handle "group <name>" option.
713 */
714static int
715set_group(struct service_list *slp, const char *str)
716{
717	struct group *gr;
718	char *cp;
719	gid_t gid;
720
721	if ((gr = getgrnam(str)) == NULL) {
722		gid = (gid_t)strtol(str, &cp, 0);
723		if (str == cp || *cp != '\0') {
724			logerr("%s:  bad group name \"%s\"",
725			    slp->sl_parse->ps_cfile->pf_name, str);
726			return (0);
727		}
728	} else {
729		gid = gr->gr_gid;
730	}
731	slp->sl_entry.se_gid = gid;
732	if (getuid() != 0) {
733		logdbg("%s:  not root; ignoring attempt to set GID %d (%s)",
734		    slp->sl_parse->ps_cfile->pf_name, gid, str);
735		return (0);
736	}
737	slp->sl_entry.se_flags |= SEF_GIDSET;
738	return (0);
739}
740
741/*
742 * This state machine is used to parse the configuration files.  The
743 * "kwe_in" is the state in which the keyword is recognized.  The
744 * "kwe_out" is the state that the keyword produces.
745 */
746struct kw_entry {
747	const char *kwe_word;
748	enum key_state kwe_in;
749	enum key_state kwe_out;
750	int (*kwe_func)(struct service_list *slp, const char *str);
751};
752
753static const struct kw_entry key_list[] = {
754	{ "service",	ksDefault,	ksService,	NULL },
755	{ "device",	ksDefault,	ksDevice,	NULL },
756	{ "client",	ksDefault,	ksClient,	NULL },
757	{ "except",	ksClient,	ksClientE,	NULL },
758	{ "wildcard",	ksDefault,	ksDefault,	set_wildcard },
759	{ "nowildcard",	ksDefault,	ksDefault,	set_wildcard },
760	{ "server",	ksDefault,	ksServer,	NULL },
761	{ "pppd",	ksDefault,	ksPppd,		NULL },
762	{ "debug",	ksDefault,	ksDefault,	set_debug },
763	{ "nodebug",	ksDefault,	ksDefault,	set_nodebug },
764	{ "file",	ksDefault,	ksFile,		NULL },
765	{ "path",	ksDefault,	ksPath,		NULL },
766	{ "extra",	ksDefault,	ksExtra,	NULL },
767	{ "log",	ksDefault,	ksLog,		NULL },
768	{ "user",	ksDefault,	ksUser,		NULL },
769	{ "group",	ksDefault,	ksGroup,	NULL },
770	/* Wildcards only past this point. */
771	{ "",		ksService,	ksDefault,	set_service },
772	{ "",		ksDevice,	ksDefault,	set_device },
773	{ "",		ksClient,	ksDefault,	set_client },
774	{ "",		ksClientE,	ksDefault,	set_client },
775	{ "",		ksServer,	ksDefault,	set_string },
776	{ "",		ksPppd,		ksDefault,	set_string },
777	{ "",		ksFile,		ksDefault,	set_file },
778	{ "",		ksPath,		ksDefault,	set_string },
779	{ "",		ksExtra,	ksDefault,	set_string },
780	{ "",		ksLog,		ksDefault,	set_string },
781	{ "",		ksUser,		ksDefault,	set_user },
782	{ "",		ksGroup,	ksDefault,	set_group },
783	{ NULL, ksDefault, ksDefault, NULL }
784};
785
786/*
787 * Produce a string for the keyword that would have gotten us into the
788 * current state.
789 */
790static const char *
791after_key(enum key_state kstate)
792{
793	const struct kw_entry *kep;
794
795	for (kep = key_list; kep->kwe_word != NULL; kep++)
796		if (kep->kwe_out == kstate)
797			return (kep->kwe_word);
798	return ("nothing");
799}
800
801/*
802 * Handle end-of-file processing -- close service, close file, revert
803 * to global context in previous include file nest level.
804 */
805static void
806file_end(struct parse_state *psp)
807{
808	struct per_file *pfp;
809
810	/* Must not be in the middle of parsing a multi-word sequence now. */
811	if (psp->ps_state != ksDefault) {
812		logerr("%s ends with \"%s\"", psp->ps_cfile->pf_name,
813		    after_key(psp->ps_state));
814		psp->ps_state = ksDefault;
815	}
816	close_service(psp->ps_csvc);
817	if ((pfp = psp->ps_cfile) != NULL) {
818		/* Put this file on the list of finished files. */
819		psp->ps_cfile = pfp->pf_prev;
820		pfp->pf_prev = psp->ps_files;
821		psp->ps_files = pfp;
822		if (pfp->pf_input != NULL) {
823			logdbg("file %s closed", pfp->pf_name);
824			(void) fclose(pfp->pf_input);
825			pfp->pf_input = NULL;
826		}
827
828		/* Back up to previous file, if any, and set global context. */
829		if ((pfp = psp->ps_cfile) != NULL)
830			psp->ps_csvc = &pfp->pf_global;
831	}
832}
833
834/*
835 * Dispatch a single keyword against the parser state machine or
836 * handle an environment variable assignment.  The input is a string
837 * containing the single word to be dispatched.
838 */
839static int
840dispatch_keyword(struct parse_state *psp, const char *keybuf)
841{
842	const struct kw_entry *kep;
843	int retv;
844	char *cp;
845	char *env;
846	char **evlist;
847	int len;
848
849	retv = 0;
850	for (kep = key_list; kep->kwe_word != NULL; kep++) {
851		if (kep->kwe_in == psp->ps_state &&
852		    (*kep->kwe_word == '\0' ||
853		    strcasecmp(kep->kwe_word, keybuf) == 0)) {
854			if (kep->kwe_func != NULL)
855				retv = (*kep->kwe_func)(psp->ps_csvc, keybuf);
856			psp->ps_state = kep->kwe_out;
857			return (retv);
858		}
859	}
860	if (strchr(keybuf, '=') != NULL) {
861		if ((cp = strsave(keybuf)) == NULL) {
862			logerr("no memory to save %s", keybuf);
863			return (0);
864		}
865		len = (strchr(cp, '=') - cp) + 1;
866		if ((evlist = psp->ps_evlist) == NULL) {
867			psp->ps_evlist = evlist =
868			    (char **)malloc(8 * sizeof (*evlist));
869			if (evlist == NULL) {
870				logerr("no memory for evlist");
871				free(cp);
872				return (0);
873			}
874			psp->ps_evsize = 8;
875			evlist[0] = evlist[1] = NULL;
876		} else {
877			while ((env = *evlist) != NULL) {
878				if (strncmp(cp, env, len) == 0)
879					break;
880				evlist++;
881			}
882			if (env == NULL &&
883			    evlist-psp->ps_evlist >= psp->ps_evsize-1) {
884				evlist = (char **)realloc(psp->ps_evlist,
885				    (psp->ps_evsize + 8) * sizeof (*evlist));
886				if (evlist == NULL) {
887					logerr("cannot realloc evlist to %d",
888					    psp->ps_evsize + 8);
889					free(cp);
890					return (0);
891				}
892				psp->ps_evlist = evlist;
893				evlist += psp->ps_evsize - 1;
894				psp->ps_evsize += 8;
895				evlist[1] = NULL;
896			}
897		}
898		logdbg("setenv \"%s\"", cp);
899		if (*evlist != NULL)
900			free(*evlist);
901		*evlist = cp;
902		return (0);
903	}
904	logerr("%s: unknown keyword '%s'", psp->ps_cfile->pf_name, keybuf);
905	return (-1);
906}
907
908/*
909 * Modified version of standard getenv; looks in locally-stored
910 * environment first.  This function exists because we need to be able
911 * to revert to the original environment during a reread (SIGHUP), and
912 * the putenv() function overwrites that environment.
913 */
914static char *
915my_getenv(struct parse_state *psp, char *estr)
916{
917	char **evlist, *ent;
918	int elen;
919
920	if (psp != NULL && (evlist = psp->ps_evlist) != NULL) {
921		elen = strlen(estr);
922		while ((ent = *evlist++) != NULL) {
923			if (strncmp(ent, estr, elen) == 0 &&
924			    ent[elen] == '=')
925				return (ent + elen + 1);
926		}
927	}
928	return (getenv(estr));
929}
930
931/*
932 * Expand an environment variable at the end of current buffer and
933 * return pointer to next spot in buffer for character append.  psp
934 * context may be null.
935 */
936static char *
937env_replace(struct parse_state *psp, char *keybuf, char kwstate)
938{
939	char *cpe;
940	char *cp;
941
942	if ((cp = strrchr(keybuf, kwstate)) != NULL) {
943		if ((cpe = my_getenv(psp, cp + 1)) != NULL) {
944			*cp = '\0';
945			(void) strncat(cp, cpe,
946			    MAX_KEYWORD - (cp - keybuf) - 1);
947			keybuf[MAX_KEYWORD - 1] = '\0';
948			cp += strlen(cp);
949		} else {
950			logerr("unknown variable \"%s\"", cp + 1);
951		}
952	} else {
953		/* Should not occur. */
954		cp = keybuf + strlen(keybuf);
955	}
956	return (cp);
957}
958
959/*
960 * Given a character-at-a-time input function, get a delimited keyword
961 * from the input.  This function handles the usual escape sequences,
962 * quoting, commenting, and environment variable expansion.
963 *
964 * The standard wordexp(3C) function isn't used here because the POSIX
965 * definition is hard to use, and the Solaris implementation is
966 * resource-intensive and insecure.  The "hard-to-use" part is that
967 * wordexp expands only variables from the environment, and can't
968 * handle an environment overlay.  Instead, the caller must use the
969 * feeble putenv/getenv interface, and rewinding to the initial
970 * environment without leaking storage is hard.  The Solaris
971 * implementation invokes an undocumented extensions via
972 * fork/exec("/bin/ksh -\005 %s") for every invocation, and gathers
973 * the expanded result with pipe.  This makes it slow to execute and
974 * exposes the string being expanded to users with access to "ps -f."
975 *
976 * psp may be null; it's used only for environment variable expansion.
977 * Input "flag" is 1 to ignore EOL, '#', and '$'; 0 for normal file parsing.
978 *
979 * Returns:
980 *	0 - keyword parsed.
981 *	1 - end of file; no keyword.
982 *	2 - end of file after this keyword.
983 */
984static int
985getkeyword(struct parse_state *psp, char *keybuf, int keymax,
986    int (*nextchr)(void *), void *arg, int flag)
987{
988	char varnest[MAX_NEST];
989	char *kbp;
990	char *vnp;
991	char chr;
992	int ichr;
993	char kwstate;
994	static const char escstr[] = "a\ab\bf\fn\nr\r";
995	const char *cp;
996
997	keymax--;	/* Account for trailing NUL byte */
998
999	kwstate = '\0';
1000	kbp = keybuf;
1001	vnp = varnest;
1002	for (;;) {
1003		ichr = (*nextchr)(arg);
1004		chr = (char)ichr;
1005	tryagain:
1006		switch (kwstate) {
1007		case '\\':	/* Start of unquoted escape sequence */
1008		case '|':	/* Start of escape sequence in double quotes */
1009		case '~':	/* Start of escape sequence in single quotes */
1010			/* Convert the character if we can. */
1011			if (chr == '\n')
1012				chr = '\0';
1013			else if (isalpha(chr) &&
1014			    (cp = strchr(escstr, chr)) != NULL)
1015				chr = cp[1];
1016			/* Revert to previous state */
1017			switch (kwstate) {
1018			case '\\':
1019				kwstate = 'A';
1020				break;
1021			case '|':
1022				kwstate = '"';
1023				break;
1024			case '~':
1025				kwstate = '\'';
1026				break;
1027			}
1028			break;
1029		case '"':	/* In double-quote string */
1030			if (!flag && chr == '$') {
1031				/* Handle variable expansion. */
1032				kwstate = '%';
1033				chr = '\0';
1034				break;
1035			}
1036				/* FALLTHROUGH */
1037		case '\'':	/* In single-quote string */
1038			if (chr == '\\') {
1039				/* Handle start of escape sequence */
1040				kwstate = kwstate == '"' ? '|' : '~';
1041				chr = '\0';
1042				break;
1043			}
1044			if (chr == kwstate) {
1045				/* End of quoted string; revert to normal */
1046				kwstate = 'A';
1047				chr = '\0';
1048			}
1049			break;
1050		case '$':	/* Start of unquoted variable name */
1051		case '%':	/* Start of variable name in quoted string */
1052			if (chr == '{') {
1053				/* Variable name is bracketed. */
1054				kwstate = chr =
1055				    kwstate == '$' ? '{' : '[';
1056				break;
1057			}
1058			*kbp++ = kwstate = kwstate == '$' ? '+' : '*';
1059				/* FALLTHROUGH */
1060		case '+':	/* Gathering unquoted variable name */
1061		case '*':	/* Gathering variable name in quoted string */
1062			if (chr == '$' &&
1063			    vnp < varnest + sizeof (varnest)) {
1064				*vnp++ = kwstate;
1065				kwstate = '$';
1066				chr = '\0';
1067				break;
1068			}
1069			if (!isalnum(chr) && chr != '_' &&
1070			    chr != '.' && chr != '-') {
1071				*kbp = '\0';
1072				kbp = env_replace(psp, keybuf, kwstate);
1073				if (vnp > varnest)
1074					kwstate = *--vnp;
1075				else
1076					kwstate = kwstate == '+' ?
1077					    'A' : '"';
1078				/* Go reinterpret in new context */
1079				goto tryagain;
1080			}
1081			break;
1082		case '{':	/* Gathering bracketed, unquoted var name */
1083		case '[':	/* Gathering bracketed, quoted var name */
1084			if (chr == '}') {
1085				*kbp = '\0';
1086				kbp = env_replace(psp, keybuf, kwstate);
1087				kwstate = kwstate == '{' ? 'A' : '"';
1088				chr = '\0';
1089			}
1090			break;
1091		case '#':	/* Comment before word state */
1092		case '@':	/* Comment after word state */
1093			if (chr == '\n' || chr == '\r' || ichr == EOF) {
1094				/* At end of line, revert to previous state */
1095				kwstate = kwstate == '#' ? '\0' : ' ';
1096				chr = '\0';
1097				break;
1098			}
1099			chr = '\0';
1100			break;
1101		case '\0':	/* Initial state; no word seen yet. */
1102			if (ichr == EOF || isspace(chr)) {
1103				chr = '\0';	/* Skip over leading spaces */
1104				break;
1105			}
1106			if (chr == '#') {
1107				kwstate = '#';
1108				chr = '\0';	/* Skip over comments */
1109				break;
1110			}
1111			/* Start of keyword seen. */
1112			kwstate = 'A';
1113			/* FALLTHROUGH */
1114		default:	/* Middle of keyword parsing. */
1115			if (ichr == EOF)
1116				break;
1117			if (isspace(chr)) {	/* Space terminates word */
1118				kwstate = ' ';
1119				break;
1120			}
1121			if (chr == '"' || chr == '\'' || chr == '\\') {
1122				kwstate = chr;	/* Begin quote or escape */
1123				chr = '\0';
1124				break;
1125			}
1126			if (flag)	/* Allow ignore; for string reparse */
1127				break;
1128			if (chr == '#') {	/* Comment terminates word */
1129				kwstate = '@';	/* Must consume comment also */
1130				chr = '\0';
1131				break;
1132			}
1133			if (chr == '$') {
1134				kwstate = '$';	/* Begin variable expansion */
1135				chr = '\0';
1136			}
1137			break;
1138		}
1139		/*
1140		 * If we've reached a space at the end of the word,
1141		 * then we're done.
1142		 */
1143		if (ichr == EOF || kwstate == ' ')
1144			break;
1145		/*
1146		 * If there's a character to store and space
1147		 * available, then add it to the string
1148		 */
1149		if (chr != '\0' && kbp < keybuf + keymax)
1150			*kbp++ = (char)chr;
1151	}
1152
1153	*kbp = '\0';
1154
1155	if (ichr == EOF) {
1156		return (kwstate == '\0' ? 1 : 2);
1157	}
1158	return (0);
1159}
1160
1161/*
1162 * Fetch words from current file until all files are closed.  Handles
1163 * include files.
1164 */
1165static void
1166parse_from_file(struct parse_state *psp)
1167{
1168	char keybuf[MAX_KEYWORD];
1169	int retv;
1170
1171	while (psp->ps_cfile != NULL && psp->ps_cfile->pf_input != NULL) {
1172		retv = getkeyword(psp, keybuf, sizeof (keybuf),
1173		    (int (*)(void *))fgetc, (void *)psp->ps_cfile->pf_input,
1174		    0);
1175
1176		if (retv != 1)
1177			(void) dispatch_keyword(psp, keybuf);
1178
1179		if (retv != 0)
1180			file_end(psp);
1181	}
1182}
1183
1184/*
1185 * Open and parse named file.  This is for the predefined
1186 * configuration files in /etc/ppp -- it's not an error if any of
1187 * these are missing.
1188 */
1189static void
1190parse_file(struct parse_state *psp, const char *fname)
1191{
1192	struct stat sb;
1193
1194	/* It's ok if any of these files are missing. */
1195	if (stat(fname, &sb) == -1 && errno == ENOENT)
1196		return;
1197	if (set_file(psp->ps_csvc, fname) == 0)
1198		parse_from_file(psp);
1199}
1200
1201/*
1202 * Dispatch keywords from command line.  Handles any files included
1203 * from there.
1204 */
1205static void
1206parse_arg_list(struct parse_state *psp, int argc, char **argv)
1207{
1208	/* The first argument (program name) can be null. */
1209	if (--argc <= 0)
1210		return;
1211	while (--argc >= 0) {
1212		(void) dispatch_keyword(psp, *++argv);
1213		if (psp->ps_cfile->pf_input != NULL)
1214			parse_from_file(psp);
1215	}
1216}
1217
1218/* Count length of dynamic device list */
1219static int
1220count_devs(struct device_list *dlp)
1221{
1222	int ndevs;
1223
1224	ndevs = 0;
1225	for (; dlp != NULL; dlp = dlp->dl_next)
1226		ndevs++;
1227	return (ndevs);
1228}
1229
1230/* Count number of devices named in entire file. */
1231static int
1232count_per_file(struct per_file *pfp)
1233{
1234	struct service_list *slp;
1235	int ndevs = 0;
1236
1237	for (; pfp != NULL; pfp = pfp->pf_prev) {
1238		ndevs += count_devs(pfp->pf_global.sl_dev);
1239		for (slp = pfp->pf_svc; slp != NULL; slp = slp->sl_next)
1240			if (!(slp->sl_entry.se_flags & SEF_CDEV))
1241				ndevs += count_devs(slp->sl_dev);
1242	}
1243	return (ndevs);
1244}
1245
1246/* Write device names into linear array. */
1247static const char **
1248devs_to_list(struct device_list *dlp, const char **dnames)
1249{
1250	for (; dlp != NULL; dlp = dlp->dl_next)
1251		*dnames++ = dlp->dl_name;
1252	return (dnames);
1253}
1254
1255/* Write all device names from file into a linear array. */
1256static const char **
1257per_file_to_list(struct per_file *pfp, const char **dnames)
1258{
1259	struct service_list *slp;
1260
1261	for (; pfp != NULL; pfp = pfp->pf_prev) {
1262		dnames = devs_to_list(pfp->pf_global.sl_dev, dnames);
1263		for (slp = pfp->pf_svc; slp != NULL; slp = slp->sl_next)
1264			if (!(slp->sl_entry.se_flags & SEF_CDEV))
1265				dnames = devs_to_list(slp->sl_dev, dnames);
1266	}
1267	return (dnames);
1268}
1269
1270/* Compare device names; used with qsort */
1271static int
1272devcmp(const void *d1, const void *d2)
1273{
1274	return (strcmp(*(const char **)d1, *(const char **)d2));
1275}
1276
1277/*
1278 * Get sorted list of unique device names among all defined and
1279 * partially defined services in all files.
1280 */
1281static const char **
1282get_unique_devs(struct parse_state *psp)
1283{
1284	int ndevs;
1285	const char **dnames;
1286	const char **dnp;
1287	const char **dnf;
1288
1289	/*
1290	 * Count number of explicitly referenced devices among all
1291	 * services (including duplicates).
1292	 */
1293	ndevs = count_per_file(psp->ps_files);
1294	ndevs += count_per_file(psp->ps_cfile);
1295	if (ndevs <= 0) {
1296		return (NULL);
1297	}
1298
1299	/* Sort and trim out duplicate devices. */
1300	dnames = (const char **)malloc((ndevs+1) * sizeof (const char *));
1301	if (dnames == NULL) {
1302		logerr("unable to allocate space for %d devices", ndevs + 1);
1303		return (NULL);
1304	}
1305	dnp = per_file_to_list(psp->ps_files, dnames);
1306	(void) per_file_to_list(psp->ps_cfile, dnp);
1307	qsort(dnames, ndevs, sizeof (const char *), devcmp);
1308	for (dnf = (dnp = dnames) + 1; dnf < dnames+ndevs; dnf++)
1309		if (strcmp(*dnf, *dnp) != 0)
1310			*++dnp = *dnf;
1311	*++dnp = NULL;
1312
1313	/* Return array of pointers to names. */
1314	return (dnames);
1315}
1316
1317/*
1318 * Convert data structures created by parsing process into data
1319 * structures used by service dispatch.  This gathers the unique
1320 * device (lower stream) names and attaches the services available on
1321 * each device to a list while triming duplicate services.
1322 */
1323static struct option_state *
1324organize_state(struct parse_state *psp)
1325{
1326	struct per_file *pfp;
1327	struct per_file *pftopp;
1328	struct service_list *slp;
1329	struct device_list *dlp;
1330	int ndevs;
1331	int nsvcs;
1332	const char **dnames;
1333	const char **dnp;
1334	struct device_entry *dep;
1335	struct option_state *osp;
1336	struct service_entry **sepp;
1337	struct service_entry **sebpp;
1338	struct service_entry **se2pp;
1339
1340	/*
1341	 * Parsing is now done.
1342	 */
1343	close_service(psp->ps_csvc);
1344	psp->ps_csvc = NULL;
1345	if ((pfp = psp->ps_cfile) != NULL) {
1346		pfp->pf_prev = psp->ps_files;
1347		psp->ps_files = pfp;
1348		psp->ps_cfile = NULL;
1349	}
1350
1351	/* Link the services from all files together for easy referencing. */
1352	pftopp = psp->ps_files;
1353	for (pfp = pftopp->pf_prev; pfp != NULL; pfp = pfp->pf_prev)
1354		if (pfp->pf_svc != NULL) {
1355			if (pftopp->pf_svc_last == NULL)
1356				pftopp->pf_svc = pfp->pf_svc;
1357			else
1358				pftopp->pf_svc_last->sl_next = pfp->pf_svc;
1359			pftopp->pf_svc_last = pfp->pf_svc_last;
1360			pfp->pf_svc = pfp->pf_svc_last = NULL;
1361		}
1362
1363	/*
1364	 * Count up number of services per device, including
1365	 * duplicates but not including defaults.
1366	 */
1367	nsvcs = 0;
1368	for (slp = psp->ps_files->pf_svc; slp != NULL; slp = slp->sl_next)
1369		for (dlp = slp->sl_dev; dlp != NULL; dlp = dlp->dl_next)
1370			nsvcs++;
1371
1372	/*
1373	 * Get the unique devices referenced by all services.
1374	 */
1375	dnames = get_unique_devs(psp);
1376	if (dnames == NULL) {
1377		logdbg("no devices referenced by any service");
1378		return (NULL);
1379	}
1380	ndevs = 0;
1381	for (dnp = dnames; *dnp != NULL; dnp++)
1382		ndevs++;
1383
1384	/*
1385	 * Allocate room for main structure, device records, and
1386	 * per-device lists.  Worst case is all devices having all
1387	 * services; that's why we allocate for nsvcs * ndevs.
1388	 */
1389	osp = (struct option_state *)malloc(sizeof (*osp) +
1390	    ndevs * sizeof (*dep) + nsvcs * ndevs * sizeof (*sepp));
1391	if (osp == NULL) {
1392		logerr("unable to allocate option state structure");
1393		free(dnames);
1394		return (NULL);
1395	}
1396
1397	/* We're going to succeed now, so steal these over. */
1398	osp->os_devices = dep = (struct device_entry *)(osp+1);
1399	osp->os_pfjunk = psp->ps_files;
1400	psp->ps_files = NULL;
1401	osp->os_evjunk = psp->ps_evlist;
1402	psp->ps_evlist = NULL;
1403
1404	/* Loop over devices, install services, remove duplicates. */
1405	sepp = (struct service_entry **)(dep + ndevs);
1406	for (dnp = dnames; *dnp != NULL; dnp++) {
1407		dep->de_name = *dnp;
1408		dep->de_services = (const struct service_entry **)sepp;
1409		sebpp = sepp;
1410		for (slp = osp->os_pfjunk->pf_svc; slp != NULL;
1411		    slp = slp->sl_next)
1412			for (dlp = slp->sl_dev; dlp != NULL;
1413			    dlp = dlp->dl_next) {
1414				if (dlp->dl_name == *dnp ||
1415				    strcmp(dlp->dl_name, *dnp) == 0) {
1416					for (se2pp = sebpp; se2pp < sepp;
1417					    se2pp++)
1418						if ((*se2pp)->se_name ==
1419						    slp->sl_entry.se_name ||
1420						    strcmp((*se2pp)->
1421						    se_name, slp->sl_entry.
1422						    se_name) == 0)
1423							break;
1424					/*
1425					 * We retain a service if it's
1426					 * unique or if its serial
1427					 * number (position in the
1428					 * file) is greater than than
1429					 * any other.
1430					 */
1431					if (se2pp >= sepp)
1432						*sepp++ = &slp->sl_entry;
1433					else if (SESERIAL(**se2pp) <
1434					    SESERIAL(slp->sl_entry))
1435						*se2pp = &slp->sl_entry;
1436				}
1437			}
1438		/* Count up the services on this device. */
1439		dep->de_nservices = (const struct service_entry **)sepp -
1440		    dep->de_services;
1441		/* Ignore devices having no services at all. */
1442		if (dep->de_nservices > 0)
1443			dep++;
1444	}
1445	/* Count up the devices. */
1446	osp->os_ndevices = dep - osp->os_devices;
1447	/* Free the list of device names */
1448	free(dnames);
1449	return (osp);
1450}
1451
1452/*
1453 * Free storage unique to a given service.  Pointers copied from other
1454 * services are ignored.
1455 */
1456static void
1457free_service(struct service_list *slp)
1458{
1459	struct filter_entry *fep;
1460	struct filter_entry *fen;
1461
1462	if (!(slp->sl_entry.se_flags & SEF_CDEV))
1463		free_device_list(slp->sl_dev);
1464	if (!(slp->sl_entry.se_flags & SEF_CFLIST)) {
1465		fep = slp->sl_entry.se_flist;
1466		while (fep != NULL) {
1467			fen = fep->fe_prevcopy ? NULL : fep->fe_prev;
1468			free(fep);
1469			fep = fen;
1470		}
1471	}
1472	if (!(slp->sl_entry.se_flags & SEF_CPPPD) &&
1473	    slp->sl_entry.se_pppd != NULL)
1474		free(slp->sl_entry.se_pppd);
1475	if (!(slp->sl_entry.se_flags & SEF_CSERVER) &&
1476	    slp->sl_entry.se_server != NULL)
1477		free(slp->sl_entry.se_server);
1478	if (!(slp->sl_entry.se_flags & SEF_CPATH) &&
1479	    slp->sl_entry.se_path != NULL)
1480		free(slp->sl_entry.se_path);
1481	if (!(slp->sl_entry.se_flags & SEF_CEXTRA) &&
1482	    slp->sl_entry.se_extra != NULL)
1483		free(slp->sl_entry.se_extra);
1484	if (!(slp->sl_entry.se_flags & SEF_CLOG) &&
1485	    slp->sl_entry.se_log != NULL)
1486		free(slp->sl_entry.se_log);
1487}
1488
1489/*
1490 * Free a linked list of services.
1491 */
1492static void
1493free_service_list(struct service_list *slp)
1494{
1495	struct service_list *sln;
1496
1497	while (slp != NULL) {
1498		free_service(slp);
1499		sln = slp->sl_next;
1500		free(slp);
1501		slp = sln;
1502	}
1503}
1504
1505/*
1506 * Free a linked list of files and all services in those files.
1507 */
1508static void
1509free_file_list(struct per_file *pfp)
1510{
1511	struct per_file *pfn;
1512
1513	while (pfp != NULL) {
1514		free_service(&pfp->pf_global);
1515		free_service_list(pfp->pf_svc);
1516		pfn = pfp->pf_prev;
1517		free(pfp);
1518		pfp = pfn;
1519	}
1520}
1521
1522/*
1523 * Free an array of local environment variables.
1524 */
1525static void
1526free_env_list(char **evlist)
1527{
1528	char **evp;
1529	char *env;
1530
1531	if ((evp = evlist) != NULL) {
1532		while ((env = *evp++) != NULL)
1533			free(env);
1534		free(evlist);
1535	}
1536}
1537
1538/*
1539 * Add a new device (lower stream) to the list for which we're the
1540 * PPPoE server.
1541 */
1542static void
1543add_new_dev(int tunfd, const char *dname)
1544{
1545	union ppptun_name ptn;
1546
1547	(void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%s:pppoed",
1548	    dname);
1549	if (strioctl(tunfd, PPPTUN_SCTL, &ptn, sizeof (ptn), 0) < 0) {
1550		logerr("PPPTUN_SCTL %s: %s", ptn.ptn_name, mystrerror(errno));
1551	} else {
1552		logdbg("added %s", ptn.ptn_name);
1553	}
1554}
1555
1556/*
1557 * Remove an existing device (lower stream) from the list for which we
1558 * were the PPPoE server.
1559 */
1560static void
1561rem_old_dev(int tunfd, const char *dname)
1562{
1563	union ppptun_name ptn;
1564
1565	(void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%s:pppoed",
1566	    dname);
1567	if (strioctl(tunfd, PPPTUN_DCTL, &ptn, sizeof (ptn), 0) < 0) {
1568		logerr("PPPTUN_DCTL %s: %s", ptn.ptn_name, mystrerror(errno));
1569	} else {
1570		logdbg("removed %s", ptn.ptn_name);
1571	}
1572}
1573
1574/*
1575 * Get a list of all of the devices currently plumbed for PPPoE.  This
1576 * is used for supporting the "*" and "all" device aliases.
1577 */
1578static void
1579get_device_list(struct parse_state *psp, int tunfd)
1580{
1581	struct device_list *dlp;
1582	struct device_list **dlpp;
1583	struct device_list *dlalt;
1584	struct device_list **dl2pp;
1585	struct device_list *dla;
1586	int i;
1587	union ppptun_name ptn;
1588	char *cp;
1589
1590	/* First pass; just allocate space for all *:pppoe* devices */
1591	dlpp = &psp->ps_star;
1592	dl2pp = &dlalt;
1593	for (i = 0; ; i++) {
1594		ptn.ptn_index = i;
1595		if (strioctl(tunfd, PPPTUN_GNNAME, &ptn, sizeof (ptn),
1596		    sizeof (ptn)) < 0) {
1597			logerr("PPPTUN_GNNAME %d: %s", i, mystrerror(errno));
1598			break;
1599		}
1600		if (ptn.ptn_name[0] == '\0')
1601			break;
1602		if ((cp = strchr(ptn.ptn_name, ':')) == NULL ||
1603		    strncmp(cp, ":pppoe", 6) != 0 ||
1604		    (cp[6] != '\0' && strcmp(cp+6, "d") != 0))
1605			continue;
1606		*cp = '\0';
1607		dlp = (struct device_list *)malloc(sizeof (*dlp) +
1608		    strlen(ptn.ptn_name) + 1);
1609		if (dlp == NULL)
1610			break;
1611		dlp->dl_name = (const char *)(dlp + 1);
1612		(void) strcpy((char *)(dlp + 1), ptn.ptn_name);
1613		if (cp[6] == '\0') {
1614			*dlpp = dlp;
1615			dlpp = &dlp->dl_next;
1616		} else {
1617			*dl2pp = dlp;
1618			dl2pp = &dlp->dl_next;
1619		}
1620	}
1621	*dlpp = NULL;
1622	*dl2pp = NULL;
1623
1624	/* Second pass; eliminate improperly plumbed devices */
1625	for (dlpp = &psp->ps_star; (dlp = *dlpp) != NULL; ) {
1626		for (dla = dlalt; dla != NULL; dla = dla->dl_next)
1627			if (strcmp(dla->dl_name, dlp->dl_name) == 0)
1628				break;
1629		if (dla == NULL) {
1630			*dlpp = dlp->dl_next;
1631			free(dlp);
1632		} else {
1633			dlpp = &dlp->dl_next;
1634		}
1635	}
1636	free_device_list(dlalt);
1637
1638	/* Add in "*" so we can always handle dynamic plumbing. */
1639	dlp = (struct device_list *)malloc(sizeof (*dlp) + 2);
1640	if (dlp != NULL) {
1641		dlp->dl_name = (const char *)(dlp + 1);
1642		(void) strcpy((char *)(dlp + 1), "*");
1643		dlp->dl_next = psp->ps_star;
1644		psp->ps_star = dlp;
1645	}
1646}
1647
1648/*
1649 * Set logging subsystem back to configured global default values.
1650 */
1651void
1652global_logging(void)
1653{
1654	log_for_service(glob_svc.se_log, glob_svc.se_debug);
1655}
1656
1657/*
1658 * Handle SIGHUP -- reparse command line and all configuration files.
1659 * When reparsing is complete, free old parsed data and replace with
1660 * new.
1661 */
1662void
1663parse_options(int tunfd, int argc, char **argv)
1664{
1665	struct parse_state pstate;
1666	struct per_file *argpf;
1667	struct option_state *newopt;
1668	const char **dnames;
1669	const char **dnp;
1670	const struct device_entry *newdep, *newmax;
1671	const struct device_entry *olddep, *oldmax;
1672	int cmpval;
1673	struct service_entry newglobsvc, *mainsvc;
1674
1675	/* Note that all per_file structures must be freeable */
1676	argpf = (struct per_file *)calloc(sizeof (*argpf), 1);
1677	if (argpf == NULL) {
1678		return;
1679	}
1680	(void) memset(&pstate, '\0', sizeof (pstate));
1681	pstate.ps_state = ksDefault;
1682	pstate.ps_cfile = argpf;
1683	pstate.ps_csvc = &argpf->pf_global;
1684	argpf->pf_global.sl_parse = &pstate;
1685	argpf->pf_name = "command line";
1686
1687	/* Default is 1 -- errors only */
1688	argpf->pf_global.sl_entry.se_debug++;
1689	argpf->pf_global.sl_entry.se_name = "<global>";
1690
1691	/* Get list of all devices */
1692	get_device_list(&pstate, tunfd);
1693
1694	/* Parse options from command line and main configuration file. */
1695	pstate.ps_flags |= PSF_SETLEVEL;
1696	parse_arg_list(&pstate, argc, argv);
1697	parse_file(&pstate, "/etc/ppp/pppoe");
1698	pstate.ps_flags &= ~PSF_SETLEVEL;
1699
1700	/*
1701	 * At this point, global options from the main configuration
1702	 * file are pointed to by ps_files, and options from command
1703	 * line are in argpf.  We need to pull three special options
1704	 * from these -- wildcard, debug, and log.  Note that the main
1705	 * options file overrides the command line.  This is
1706	 * intentional.  The semantics are such that the system
1707	 * behaves as though the main configuration file were
1708	 * "included" from the command line, and thus options there
1709	 * override the command line.  This may seem odd, but at least
1710	 * it's self-consistent.
1711	 */
1712	newglobsvc = argpf->pf_global.sl_entry;
1713	if (pstate.ps_files != NULL) {
1714		mainsvc = &pstate.ps_files->pf_global.sl_entry;
1715		if (mainsvc->se_log != NULL)
1716			newglobsvc.se_log = mainsvc->se_log;
1717		if (mainsvc->se_flags & (SEF_WILD|SEF_NOWILD))
1718			newglobsvc.se_flags =
1719			    (newglobsvc.se_flags & ~(SEF_WILD|SEF_NOWILD)) |
1720			    (mainsvc->se_flags & (SEF_WILD|SEF_NOWILD));
1721		if (mainsvc->se_flags & SEF_DEBUGCLR)
1722			newglobsvc.se_debug = 0;
1723		newglobsvc.se_debug += mainsvc->se_debug;
1724	}
1725	glob_svc = newglobsvc;
1726	global_logging();
1727
1728	/* Get the list of devices referenced by configuration above. */
1729	dnames = get_unique_devs(&pstate);
1730	if (dnames != NULL) {
1731		/* Read per-device configuration files. */
1732		pstate.ps_flags |= PSF_PERDEV;
1733		for (dnp = dnames; *dnp != NULL; dnp++)
1734			parse_file(&pstate, *dnp);
1735		pstate.ps_flags &= ~PSF_PERDEV;
1736		free(dnames);
1737	}
1738	file_end(&pstate);
1739
1740	/*
1741	 * Convert parsed data structures into per-device structures.
1742	 * (Invert the table.)
1743	 */
1744	newopt = organize_state(&pstate);
1745
1746	/* If we're going to free the file name, then stop logging there. */
1747	if (newopt == NULL && glob_svc.se_log != NULL) {
1748		glob_svc.se_log = NULL;
1749		global_logging();
1750	}
1751
1752	/*
1753	 * Unless an error has occurred, these pointers are normally
1754	 * all NULL.  Nothing is freed until the file is re-read.
1755	 */
1756	free_file_list(pstate.ps_files);
1757	free_file_list(pstate.ps_cfile);
1758	free_device_list(pstate.ps_star);
1759	free_env_list(pstate.ps_evlist);
1760
1761	/*
1762	 * Match up entries on device list.  Detach devices no longer
1763	 * referenced.  Attach ones now referenced.  (The use of null
1764	 * pointers here may look fishy, but it actually works.
1765	 * NULL>=NULL is always true.)
1766	 */
1767	if (newopt != NULL) {
1768		newdep = newopt->os_devices;
1769		newmax = newdep + newopt->os_ndevices;
1770	} else {
1771		newdep = newmax = NULL;
1772	}
1773	if (cur_options != NULL) {
1774		olddep = cur_options->os_devices;
1775		oldmax = olddep + cur_options->os_ndevices;
1776	} else {
1777		olddep = oldmax = NULL;
1778	}
1779	while ((newdep != NULL && newdep < newmax) ||
1780	    (olddep != NULL && olddep < oldmax)) {
1781		if (newdep < newmax) {
1782			if (olddep >= oldmax) {
1783				add_new_dev(tunfd, newdep->de_name);
1784				newdep++;
1785			} else {
1786				cmpval = strcmp(newdep->de_name,
1787				    olddep->de_name);
1788				if (cmpval < 0) {
1789					/* Brand new device seen. */
1790					add_new_dev(tunfd, newdep->de_name);
1791					newdep++;
1792				} else if (cmpval == 0) {
1793					/* Existing device; skip it. */
1794					newdep++;
1795					olddep++;
1796				}
1797				/* No else clause -- removal is below */
1798			}
1799		}
1800		if (olddep < oldmax) {
1801			if (newdep >= newmax) {
1802				rem_old_dev(tunfd, olddep->de_name);
1803				olddep++;
1804			} else {
1805				cmpval = strcmp(newdep->de_name,
1806				    olddep->de_name);
1807				if (cmpval > 0) {
1808					/* Old device is gone */
1809					rem_old_dev(tunfd, olddep->de_name);
1810					olddep++;
1811				} else if (cmpval == 0) {
1812					/* Existing device; skip it. */
1813					newdep++;
1814					olddep++;
1815				}
1816				/* No else clause -- insert handled above */
1817			}
1818		}
1819	}
1820
1821	/* Discard existing parsed data storage. */
1822	if (cur_options != NULL) {
1823		free_file_list(cur_options->os_pfjunk);
1824		free_env_list(cur_options->os_evjunk);
1825		free(cur_options);
1826	}
1827	/* Install new. */
1828	cur_options = newopt;
1829}
1830
1831/*
1832 * Check if configured filters permit requesting client to use a given
1833 * service.  Note -- filters are stored in reverse order in order to
1834 * make file-inclusion work as expected.  Thus, the "first match"
1835 * filter rule becomes "last match" here.
1836 */
1837static boolean_t
1838allow_service(const struct service_entry *sep, const ppptun_atype *pap)
1839{
1840	const struct filter_entry *fep;
1841	const struct filter_entry *lmatch;
1842	boolean_t anynonexcept = B_FALSE;
1843	const uchar_t *upt;
1844	const uchar_t *macp;
1845	const uchar_t *maskp;
1846	int i;
1847
1848	lmatch = NULL;
1849	for (fep = sep->se_flist; fep != NULL; fep = fep->fe_prev) {
1850		anynonexcept |= !fep->fe_isexcept;
1851		upt = pap->pta_pppoe.ptma_mac;
1852		macp = fep->fe_mac.ether_addr_octet;
1853		maskp = fep->fe_mask.ether_addr_octet;
1854		for (i = sizeof (pap->pta_pppoe.ptma_mac); i > 0; i--)
1855			if (((*macp++ ^ *upt++) & *maskp++) != 0)
1856				break;
1857		if (i <= 0)
1858			lmatch = fep;
1859	}
1860
1861	if (lmatch == NULL) {
1862		/*
1863		 * Assume reject by default if any positive-match
1864		 * (non-except) filters are given.  Otherwise, if
1865		 * there are no positive-match filters, then
1866		 * non-matching means accept by default.
1867		 */
1868		return (!anynonexcept);
1869	}
1870	return (!lmatch->fe_isexcept);
1871}
1872
1873/*
1874 * Locate available service(s) based on client request.  Assumes that
1875 * outp points to a buffer of at least size PPPOE_MSGMAX.  Creates a
1876 * PPPoE response message in outp.  Returns count of matched services
1877 * and (through *srvp) a pointer to the last (or only) service.  If
1878 * some error is found in the request, an error string is added and -1
1879 * is returned; the caller should just send the message without
1880 * alteration.
1881 */
1882int
1883locate_service(poep_t *poep, int plen, const char *iname, ppptun_atype *pap,
1884    uint32_t *outp, void **srvp)
1885{
1886	poep_t *opoe;
1887	const uint8_t *tagp;
1888	const char *cp;
1889	int ttyp;
1890	int tlen;
1891	int nsvcs;
1892	const struct device_entry *dep, *depe;
1893	const struct device_entry *wdep;
1894	const struct service_entry **sepp, **seppe;
1895	const struct service_entry *sep;
1896	char *str;
1897	boolean_t ispadi;
1898
1899	ispadi = poep->poep_code == POECODE_PADI;
1900	opoe = poe_mkheader(outp, ispadi ? POECODE_PADO : POECODE_PADS, 0);
1901
1902	*srvp = NULL;
1903	if (cur_options == NULL)
1904		return (0);
1905
1906	/* Search for named device (lower stream) in tables. */
1907	dep = cur_options->os_devices;
1908	depe = dep + cur_options->os_ndevices;
1909	wdep = NULL;
1910	if ((cp = strchr(iname, ':')) != NULL)
1911		tlen = cp - iname;
1912	else
1913		tlen = strlen(iname);
1914	for (; dep < depe; dep++)
1915		if (strncmp(iname, dep->de_name, tlen) == 0 &&
1916		    dep->de_name[tlen] == '\0')
1917			break;
1918		else if (dep->de_name[0] == '*' && dep->de_name[1] == '\0')
1919			wdep = dep;
1920	if (dep >= depe)
1921		dep = wdep;
1922	/*
1923	 * Return if interface not found.  Zero-service case can't
1924	 * occur, since devices with no services aren't included in
1925	 * the list, but the code is just being safe here.
1926	 */
1927	if (dep == NULL || dep->de_services == NULL || dep->de_nservices <= 0)
1928		return (0);
1929
1930	/*
1931	 * Loop over tags in client message and process them.
1932	 * Services must be matched against our list.  Host-Uniq and
1933	 * Relay-Session-Id must be copied to the reply.  All others
1934	 * must be discarded.
1935	 */
1936	nsvcs = 0;
1937	sepp = dep->de_services;
1938	tagp = (const uint8_t *)(poep + 1);
1939	while (poe_tagcheck(poep, plen, tagp)) {
1940		ttyp = POET_GET_TYPE(tagp);
1941		if (ttyp == POETT_END)
1942			break;
1943		tlen = POET_GET_LENG(tagp);
1944		switch (ttyp) {
1945		case POETT_SERVICE:	/* Service-Name */
1946			/*
1947			 * Allow only one.  (Note that this test works
1948			 * because there's always at least one service
1949			 * per device; otherwise, the device is
1950			 * removed from the list.)
1951			 */
1952			if (sepp != dep->de_services) {
1953				if (nsvcs != -1)
1954					(void) poe_add_str(opoe, POETT_NAMERR,
1955					    "Too many Service-Name tags");
1956				nsvcs = -1;
1957				break;
1958			}
1959			seppe = sepp + dep->de_nservices;
1960			if (tlen == 0) {
1961				/*
1962				 * If config specifies "nowild" in a
1963				 * global context, then we don't
1964				 * respond to wildcard PADRs.  The
1965				 * client must know the exact service
1966				 * name to get access.
1967				 */
1968
1969				if (!ispadi && (glob_svc.se_flags & SEF_NOWILD))
1970					sepp = seppe;
1971				while (sepp < seppe) {
1972					sep = *sepp++;
1973					if (sep->se_name[0] == '\0' ||
1974					    (sep->se_flags & SEF_NOWILD) ||
1975					    !allow_service(sep, pap))
1976						continue;
1977					*srvp = (void *)sep;
1978					/*
1979					 * RFC requires that PADO includes the
1980					 * wildcard service request in response
1981					 * to PADI.
1982					 */
1983					if (ispadi && nsvcs == 0 &&
1984					    !(glob_svc.se_flags & SEF_NOWILD))
1985						(void) poe_tag_copy(opoe, tagp);
1986					nsvcs++;
1987					(void) poe_add_str(opoe, POETT_SERVICE,
1988					    sep->se_name);
1989					/* If PADR, then one is enough */
1990					if (!ispadi)
1991						break;
1992				}
1993				/* Just for generating error messages */
1994				if (nsvcs == 0)
1995					(void) poe_tag_copy(opoe, tagp);
1996			} else {
1997				/*
1998				 * Clients's requested service must appear in
1999				 * reply.
2000				 */
2001				(void) poe_tag_copy(opoe, tagp);
2002
2003				/* Requested specific service; find it. */
2004				cp = (char *)POET_DATA(tagp);
2005				while (sepp < seppe) {
2006					sep = *sepp++;
2007					if (strlen(sep->se_name) == tlen &&
2008					    strncasecmp(sep->se_name, cp,
2009					    tlen) == 0) {
2010						if (allow_service(sep, pap)) {
2011							nsvcs++;
2012							*srvp = (void *)sep;
2013						}
2014						break;
2015					}
2016				}
2017			}
2018			/*
2019			 * Allow service definition to override
2020			 * AC-Name (concentrator [server] name) field.
2021			 */
2022			if (*srvp != NULL) {
2023				sep = (const struct service_entry *)*srvp;
2024				log_for_service(sep->se_log, sep->se_debug);
2025				str = "Solaris PPPoE";
2026				if (sep->se_server != NULL)
2027					str = sep->se_server;
2028				(void) poe_add_str(opoe, POETT_ACCESS, str);
2029			}
2030			break;
2031		/* Ones we should discard */
2032		case POETT_ACCESS:	/* AC-Name */
2033		case POETT_COOKIE:	/* AC-Cookie */
2034		case POETT_NAMERR:	/* Service-Name-Error */
2035		case POETT_SYSERR:	/* AC-System-Error */
2036		case POETT_GENERR:	/* Generic-Error */
2037		case POETT_HURL:	/* Host-URL */
2038		case POETT_MOTM:	/* Message-Of-The-Minute */
2039		case POETT_RTEADD:	/* IP-Route-Add */
2040		case POETT_VENDOR:	/* Vendor-Specific */
2041		case POETT_MULTI:	/* Multicast-Capable */
2042		default:
2043			break;
2044		/* Ones we should copy */
2045		case POETT_UNIQ:	/* Host-Uniq */
2046		case POETT_RELAY:	/* Relay-Session-Id */
2047			(void) poe_tag_copy(opoe, tagp);
2048			break;
2049		}
2050		tagp = POET_NEXT(tagp);
2051	}
2052	return (nsvcs);
2053}
2054
2055/*
2056 * Like fgetc, but reads from a string.
2057 */
2058static int
2059sgetc(void *arg)
2060{
2061	char **cpp = (char **)arg;
2062	if (**cpp == '\0')
2063		return (EOF);
2064	return (*(*cpp)++);
2065}
2066
2067/*
2068 * Given a service structure, launch pppd.  Called by handle_input()
2069 * in pppoed.c if locate_service() [above] finds exactly one service
2070 * matching a PADR.
2071 */
2072int
2073launch_service(int tunfd, poep_t *poep, void *srvp, struct ppptun_control *ptc)
2074{
2075	const struct service_entry *sep = (const struct service_entry *)srvp;
2076	const char *path;
2077	const char *extra;
2078	const char *pppd;
2079	const char *cp;
2080	pid_t pidv;
2081	int newtun;
2082	struct ppptun_peer ptp;
2083	union ppptun_name ptn;
2084	const char *args[MAXARGS];
2085	struct strbuf ctrl;
2086	struct strbuf data;
2087	const char **cpp;
2088	char *sptr;
2089	char *spv;
2090	int slen;
2091	int retv;
2092	char keybuf[MAX_KEYWORD];
2093
2094	assert(sep != NULL);
2095
2096	/* Get tunnel driver connection for new PPP session. */
2097	newtun = open(tunnam, O_RDWR);
2098	if (newtun == -1)
2099		goto syserr;
2100
2101	/* Set this session up for standard PPP and client's address. */
2102	(void) memset(&ptp, '\0', sizeof (ptp));
2103	ptp.ptp_style = PTS_PPPOE;
2104	ptp.ptp_address = ptc->ptc_address;
2105	if (strioctl(newtun, PPPTUN_SPEER, &ptp, sizeof (ptp), sizeof (ptp)) <
2106	    0)
2107		goto syserr;
2108	ptp.ptp_rsessid = ptp.ptp_lsessid;
2109	if (strioctl(newtun, PPPTUN_SPEER, &ptp, sizeof (ptp), sizeof (ptp)) <
2110	    0)
2111		goto syserr;
2112
2113	/* Attach the requested lower stream. */
2114	cp = strchr(ptc->ptc_name, ':');
2115	if (cp == NULL)
2116		cp = ptc->ptc_name + strlen(ptc->ptc_name);
2117	(void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%.*s:pppoe",
2118	    cp-ptc->ptc_name, ptc->ptc_name);
2119	if (strioctl(newtun, PPPTUN_SDATA, &ptn, sizeof (ptn), 0) < 0)
2120		goto syserr;
2121	(void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%.*s:pppoed",
2122	    cp-ptc->ptc_name, ptc->ptc_name);
2123	if (strioctl(newtun, PPPTUN_SCTL, &ptn, sizeof (ptn), 0) < 0)
2124		goto syserr;
2125
2126	pidv = fork();
2127	if (pidv == (pid_t)-1)
2128		goto syserr;
2129
2130	if (pidv == (pid_t)0) {
2131		/*
2132		 * Use syslog only in order to avoid mixing log messages
2133		 * in regular files.
2134		 */
2135		close_log_files();
2136
2137		if ((path = sep->se_path) == NULL)
2138			path = "/usr/bin/pppd";
2139		if ((extra = sep->se_extra) == NULL)
2140			extra = "plugin pppoe.so directtty";
2141		if ((pppd = sep->se_pppd) == NULL)
2142			pppd = "";
2143
2144		/* Concatenate these. */
2145		slen = strlen(path) + strlen(extra) + strlen(pppd) + 3;
2146		if ((sptr = (char *)malloc(slen)) == NULL)
2147			goto bail_out;
2148		(void) strcpy(sptr, path);
2149		(void) strcat(sptr, " ");
2150		(void) strcat(sptr, extra);
2151		(void) strcat(sptr, " ");
2152		(void) strcat(sptr, pppd);
2153
2154		/* Parse out into arguments */
2155		cpp = args;
2156		spv = sptr;
2157		while (cpp < args + MAXARGS - 1) {
2158			retv = getkeyword(NULL, keybuf, sizeof (keybuf), sgetc,
2159			    (void *)&spv, 1);
2160			if (retv != 1)
2161				*cpp++ = strsave(keybuf);
2162			if (retv != 0)
2163				break;
2164		}
2165		*cpp = NULL;
2166		if (cpp == args)
2167			goto bail_out;
2168
2169		/*
2170		 * Fix tunnel device on stdin/stdout and error file on
2171		 * stderr.
2172		 */
2173		if (newtun != 0 && dup2(newtun, 0) < 0)
2174			goto bail_out;
2175		if (newtun != 1 && dup2(newtun, 1) < 0)
2176			goto bail_out;
2177		if (newtun > 1)
2178			(void) close(newtun);
2179		if (tunfd > 1)
2180			(void) close(tunfd);
2181		(void) close(2);
2182		(void) open("/etc/ppp/pppoe-errors", O_WRONLY | O_APPEND |
2183		    O_CREAT, 0600);
2184
2185		/*
2186		 * Change GID first, for obvious reasons.  Note that
2187		 * we log any problems to syslog, not the errors file.
2188		 * The errors file is intended for problems in the
2189		 * exec'd program.
2190		 */
2191		if ((sep->se_flags & SEF_GIDSET) &&
2192		    setgid(sep->se_gid) == -1) {
2193			cp = mystrerror(errno);
2194			reopen_log();
2195			logerr("setgid(%d): %s", sep->se_gid, cp);
2196			goto logged;
2197		}
2198		if ((sep->se_flags & SEF_UIDSET) &&
2199		    setuid(sep->se_uid) == -1) {
2200			cp = mystrerror(errno);
2201			reopen_log();
2202			logerr("setuid(%d): %s", sep->se_uid, cp);
2203			goto logged;
2204		}
2205
2206		/* Run pppd */
2207		path = args[0];
2208		cp = strrchr(args[0], '/');
2209		if (cp != NULL && cp[1] != '\0')
2210			args[0] = cp+1;
2211		errno = 0;
2212		(void) execv(path, (char * const *)args);
2213		newtun = 0;
2214
2215		/*
2216		 * Exec failure; attempt to log the problem and send a
2217		 * PADT to the client so that it knows the session
2218		 * went south.
2219		 */
2220	bail_out:
2221		cp = mystrerror(errno);
2222		reopen_log();
2223		logerr("\"%s\": %s", (sptr == NULL ? path : sptr), cp);
2224	logged:
2225		poep = poe_mkheader(pkt_output, POECODE_PADT, ptp.ptp_lsessid);
2226		poep->poep_session_id = htons(ptp.ptp_lsessid);
2227		(void) poe_add_str(poep, POETT_SYSERR, cp);
2228		(void) sleep(1);
2229		ctrl.len = sizeof (*ptc);
2230		ctrl.buf = (caddr_t)ptc;
2231		data.len = poe_length(poep) + sizeof (*poep);
2232		data.buf = (caddr_t)poep;
2233		if (putmsg(newtun, &ctrl, &data, 0) < 0) {
2234			logerr("putmsg %s: %s", ptc->ptc_name,
2235			    mystrerror(errno));
2236		}
2237		exit(1);
2238	}
2239
2240	(void) close(newtun);
2241
2242	/* Give session ID to client in reply. */
2243	poep->poep_session_id = htons(ptp.ptp_lsessid);
2244	return (1);
2245
2246syserr:
2247	/* Peer doesn't know session ID yet; hope for the best. */
2248	retv = errno;
2249	if (newtun >= 0)
2250		(void) close(newtun);
2251	(void) poe_add_str(poep, POETT_SYSERR, mystrerror(retv));
2252	return (0);
2253}
2254
2255/*
2256 * This is pretty awful -- it uses recursion to print a simple list.
2257 * It's just for debug, though, and does a reasonable job of printing
2258 * the filters in the right order.
2259 */
2260static void
2261print_filter_list(FILE *fp, struct filter_entry *fep)
2262{
2263	if (fep->fe_prev != NULL)
2264		print_filter_list(fp, fep->fe_prev);
2265	(void) fprintf(fp, "\t\t    MAC %s", ehost2(&fep->fe_mac));
2266	(void) fprintf(fp, ", mask %s%s\n", ehost2(&fep->fe_mask),
2267	    (fep->fe_isexcept ? ", except" : ""));
2268}
2269
2270/*
2271 * Write summary of parsed configuration data to given file.
2272 */
2273void
2274dump_configuration(FILE *fp)
2275{
2276	const struct device_entry *dep;
2277	const struct service_entry *sep, **sepp;
2278	struct per_file *pfp;
2279	int i, j;
2280
2281	(void) fprintf(fp, "Will%s respond to wildcard queries.\n",
2282	    (glob_svc.se_flags & SEF_NOWILD) ? " not" : "");
2283	(void) fprintf(fp,
2284	    "Global debug level %d, log to %s; current level %d\n",
2285	    glob_svc.se_debug,
2286	    ((glob_svc.se_log == NULL || *glob_svc.se_log == '\0') ?
2287	    "syslog" : glob_svc.se_log),
2288	    log_level);
2289	if (cur_options == NULL) {
2290		(void) fprintf(fp, "No current configuration.\n");
2291		return;
2292	}
2293	(void) fprintf(fp, "Current configuration:\n");
2294	(void) fprintf(fp, "    %d device(s):\n", cur_options->os_ndevices);
2295	dep = cur_options->os_devices;
2296	for (i = 0; i < cur_options->os_ndevices; i++, dep++) {
2297		(void) fprintf(fp, "\t%s: %d service(s):\n",
2298		    dep->de_name, dep->de_nservices);
2299		sepp = dep->de_services;
2300		for (j = 0; j < dep->de_nservices; j++, sepp++) {
2301			sep = *sepp;
2302			(void) fprintf(fp, "\t    %s: debug level %d",
2303			    sep->se_name, sep->se_debug);
2304			if (sep->se_flags & SEF_UIDSET)
2305				(void) fprintf(fp, ", UID %u", sep->se_uid);
2306			if (sep->se_flags & SEF_GIDSET)
2307				(void) fprintf(fp, ", GID %u", sep->se_gid);
2308			if (sep->se_flags & SEF_WILD)
2309				(void) fprintf(fp, ", wildcard");
2310			else if (sep->se_flags & SEF_NOWILD)
2311				(void) fprintf(fp, ", nowildcard");
2312			else
2313				(void) fprintf(fp, ", wildcard (default)");
2314			(void) putc('\n', fp);
2315			if (sep->se_server != NULL)
2316				(void) fprintf(fp, "\t\tserver \"%s\"\n",
2317				    sep->se_server);
2318			if (sep->se_pppd != NULL)
2319				(void) fprintf(fp, "\t\tpppd \"%s\"\n",
2320				    sep->se_pppd);
2321			if (sep->se_path != NULL)
2322				(void) fprintf(fp, "\t\tpath \"%s\"\n",
2323				    sep->se_path);
2324			if (sep->se_extra != NULL)
2325				(void) fprintf(fp, "\t\textra \"%s\"\n",
2326				    sep->se_extra);
2327			if (sep->se_log != NULL)
2328				(void) fprintf(fp, "\t\tlog \"%s\"\n",
2329				    sep->se_log);
2330			if (sep->se_flist != NULL) {
2331				(void) fprintf(fp, "\t\tfilter list:\n");
2332				print_filter_list(fp, sep->se_flist);
2333			}
2334		}
2335	}
2336	(void) fprintf(fp, "\nConfiguration read from:\n");
2337	for (pfp = cur_options->os_pfjunk; pfp != NULL; pfp = pfp->pf_prev) {
2338		(void) fprintf(fp, "    %s: %d service(s)\n", pfp->pf_name,
2339		    pfp->pf_nsvc);
2340	}
2341}
2342