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