xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.lib/pppoe/options.c (revision 48bbca816818409505a6e214d0911fda44e622e3)
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  */
63 struct 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  */
78 struct 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  */
111 struct 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  */
128 struct 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  */
140 static struct option_state *cur_options;
141 
142 /* Global settings for module-wide options. */
143 static 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 */
151 struct 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. */
157 struct 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  */
171 struct 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 */
182 enum 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  */
191 struct 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. */
207 static char *
strsave(const char * str)208 strsave(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  */
225 static void
close_service(struct service_list * slp)226 close_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 */
346 static void
free_device_list(struct device_list * dlp)347 free_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  */
363 static int
set_service(struct service_list * slp,const char * str)364 set_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  */
405 static int
set_wildcard(struct service_list * slp,const char * str)406 set_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*/
425 static int
set_debug(struct service_list * slp,const char * str)426 set_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*/
439 static int
set_nodebug(struct service_list * slp,const char * str)440 set_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  */
454 static int
set_string(struct service_list * slp,const char * str)455 set_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  */
491 static int
set_file(struct service_list * slp,const char * str)492 set_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  */
531 static int
set_device(struct service_list * slp,const char * str)532 set_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  */
602 static int
set_client(struct service_list * slp,const char * str)603 set_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  */
681 static int
set_user(struct service_list * slp,const char * str)682 set_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  */
714 static int
set_group(struct service_list * slp,const char * str)715 set_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  */
746 struct 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 
753 static 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  */
790 static const char *
after_key(enum key_state kstate)791 after_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  */
805 static void
file_end(struct parse_state * psp)806 file_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  */
839 static int
dispatch_keyword(struct parse_state * psp,const char * keybuf)840 dispatch_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  */
914 static char *
my_getenv(struct parse_state * psp,char * estr)915 my_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  */
936 static char *
env_replace(struct parse_state * psp,char * keybuf,char kwstate)937 env_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  */
984 static int
getkeyword(struct parse_state * psp,char * keybuf,int keymax,int (* nextchr)(void *),void * arg,int flag)985 getkeyword(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  */
1165 static void
parse_from_file(struct parse_state * psp)1166 parse_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  */
1189 static void
parse_file(struct parse_state * psp,const char * fname)1190 parse_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  */
1205 static void
parse_arg_list(struct parse_state * psp,int argc,char ** argv)1206 parse_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 */
1219 static int
count_devs(struct device_list * dlp)1220 count_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. */
1231 static int
count_per_file(struct per_file * pfp)1232 count_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. */
1247 static const char **
devs_to_list(struct device_list * dlp,const char ** dnames)1248 devs_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. */
1256 static const char **
per_file_to_list(struct per_file * pfp,const char ** dnames)1257 per_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 */
1271 static int
devcmp(const void * d1,const void * d2)1272 devcmp(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  */
1281 static const char **
get_unique_devs(struct parse_state * psp)1282 get_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  */
1323 static struct option_state *
organize_state(struct parse_state * psp)1324 organize_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  */
1456 static void
free_service(struct service_list * slp)1457 free_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  */
1492 static void
free_service_list(struct service_list * slp)1493 free_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  */
1508 static void
free_file_list(struct per_file * pfp)1509 free_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  */
1525 static void
free_env_list(char ** evlist)1526 free_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  */
1542 static void
add_new_dev(int tunfd,const char * dname)1543 add_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  */
1560 static void
rem_old_dev(int tunfd,const char * dname)1561 rem_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  */
1578 static void
get_device_list(struct parse_state * psp,int tunfd)1579 get_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  */
1651 void
global_logging(void)1652 global_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  */
1662 void
parse_options(int tunfd,int argc,char ** argv)1663 parse_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  */
1837 static boolean_t
allow_service(const struct service_entry * sep,const ppptun_atype * pap)1838 allow_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  */
1882 int
locate_service(poep_t * poep,int plen,const char * iname,ppptun_atype * pap,uint32_t * outp,void ** srvp)1883 locate_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  */
2058 static int
sgetc(void * arg)2059 sgetc(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  */
2072 int
launch_service(int tunfd,poep_t * poep,void * srvp,struct ppptun_control * ptc)2073 launch_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 
2246 syserr:
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  */
2260 static void
print_filter_list(FILE * fp,struct filter_entry * fep)2261 print_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  */
2273 void
dump_configuration(FILE * fp)2274 dump_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