xref: /freebsd/usr.sbin/ypldap/parse.y (revision fe75646a0234a261c0013bf1840fdac4acaf0cec)
1 /*	$OpenBSD: parse.y,v 1.18 2015/01/16 06:40:22 deraadt Exp $	*/
2 
3 /*
4  * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
5  * Copyright (c) 2007, 2008 Reyk Floeter <reyk@openbsd.org>
6  * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
7  * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
8  * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
9  * Copyright (c) 2001 Markus Friedl.  All rights reserved.
10  * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
11  * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
12  *
13  * Permission to use, copy, modify, and distribute this software for any
14  * purpose with or without fee is hereby granted, provided that the above
15  * copyright notice and this permission notice appear in all copies.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
18  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
19  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
20  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
21  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
22  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
23  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24  */
25 
26 %{
27 #include <sys/types.h>
28 #include <sys/param.h>
29 #include <sys/time.h>
30 #include <sys/queue.h>
31 #include <sys/tree.h>
32 #include <sys/socket.h>
33 #include <sys/stat.h>
34 
35 #include <netinet/in.h>
36 #include <arpa/inet.h>
37 
38 #include <ctype.h>
39 #include <err.h>
40 #include <errno.h>
41 #include <event.h>
42 #include <fcntl.h>
43 #include <limits.h>
44 #include <netdb.h>
45 #include <pwd.h>
46 #include <stdarg.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <syslog.h>
51 #include <unistd.h>
52 
53 #include "ypldap.h"
54 
55 TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
56 static struct file {
57 	TAILQ_ENTRY(file)	 entry;
58 	FILE			*stream;
59 	char			*name;
60 	int			 lineno;
61 	int			 errors;
62 } *file, *topfile;
63 struct file	*pushfile(const char *, int);
64 int		 popfile(void);
65 int		 check_file_secrecy(int, const char *);
66 int		 yyparse(void);
67 int		 yylex(void);
68 int		 yyerror(const char *, ...)
69     __attribute__((__format__ (printf, 1, 2)))
70     __attribute__((__nonnull__ (1)));
71 int		 kw_cmp(const void *, const void *);
72 int		 lookup(char *);
73 int		 lgetc(int);
74 int		 lungetc(int);
75 int		 findeol(void);
76 
77 TAILQ_HEAD(symhead, sym)	 symhead = TAILQ_HEAD_INITIALIZER(symhead);
78 struct sym {
79 	TAILQ_ENTRY(sym)	 entry;
80 	int			 used;
81 	int			 persist;
82 	char			*nam;
83 	char			*val;
84 };
85 int		 symset(const char *, const char *, int);
86 char		*symget(const char *);
87 
88 struct env		*conf = NULL;
89 struct idm		*idm = NULL;
90 static int		 errors = 0;
91 
92 typedef struct {
93 	union {
94 		int64_t		 number;
95 		char		*string;
96 	} v;
97 	int lineno;
98 } YYSTYPE;
99 
100 %}
101 
102 %token	SERVER FILTER ATTRIBUTE BASEDN BINDDN GROUPDN BINDCRED MAPS CHANGE DOMAIN PROVIDE
103 %token	USER GROUP TO EXPIRE HOME SHELL GECOS UID GID INTERVAL
104 %token	PASSWD NAME FIXED LIST GROUPNAME GROUPPASSWD GROUPGID MAP
105 %token	INCLUDE DIRECTORY CLASS PORT ERROR GROUPMEMBERS
106 %token	<v.string>	STRING
107 %token  <v.number>	NUMBER
108 %type	<v.number>	opcode attribute
109 %type	<v.string>	port
110 
111 %%
112 
113 grammar		: /* empty */
114 		| grammar '\n'
115 		| grammar include '\n'
116 		| grammar varset '\n'
117 		| grammar directory '\n'
118 		| grammar main '\n'
119 		| grammar error '\n'			{ file->errors++; }
120 		;
121 
122 nl		: '\n' optnl
123 		;
124 
125 optnl		: '\n' optnl
126 		| /* empty */
127 		;
128 
129 
130 include		: INCLUDE STRING			{
131 			struct file	*nfile;
132 
133 			if ((nfile = pushfile($2, 0)) == NULL) {
134 				yyerror("failed to include file %s", $2);
135 				free($2);
136 				YYERROR;
137 			}
138 			free($2);
139 
140 			file = nfile;
141 			lungetc('\n');
142 		}
143 		;
144 
145 varset		: STRING '=' STRING			{
146 			char *s = $1;
147 			while (*s++) {
148 				if (isspace((unsigned char) *s)) {
149 					yyerror("macro name cannot contain "
150 					  "whitespace");
151 					YYERROR;
152 				}
153 			}
154 			if (symset($1, $3, 0) == -1)
155 				fatal("cannot store variable");
156 			free($1);
157 			free($3);
158 		}
159 		;
160 
161 port		: /* empty */	{ $$ = NULL; }
162 		| PORT STRING	{ $$ = $2; }
163 		;
164 
165 opcode		: GROUP					{ $$ = 0; }
166 		| PASSWD				{ $$ = 1; }
167 		;
168 
169 
170 attribute	: NAME					{ $$ = 0; }
171 		| PASSWD				{ $$ = 1; }
172 		| UID					{ $$ = 2; }
173 		| GID					{ $$ = 3; }
174 		| CLASS					{ $$ = 4; }
175 		| CHANGE				{ $$ = 5; }
176 		| EXPIRE				{ $$ = 6; }
177 		| GECOS					{ $$ = 7; }
178 		| HOME					{ $$ = 8; }
179 		| SHELL					{ $$ = 9; }
180 		| GROUPNAME				{ $$ = 10; }
181 		| GROUPPASSWD				{ $$ = 11; }
182 		| GROUPGID				{ $$ = 12; }
183 		| GROUPMEMBERS				{ $$ = 13; }
184 		;
185 
186 diropt		: BINDDN STRING				{
187 			idm->idm_flags |= F_NEEDAUTH;
188 			if (strlcpy(idm->idm_binddn, $2,
189 			    sizeof(idm->idm_binddn)) >=
190 			    sizeof(idm->idm_binddn)) {
191 				yyerror("directory binddn truncated");
192 				free($2);
193 				YYERROR;
194 			}
195 			free($2);
196 		}
197 		| BINDCRED STRING			{
198 			idm->idm_flags |= F_NEEDAUTH;
199 			if (strlcpy(idm->idm_bindcred, $2,
200 			    sizeof(idm->idm_bindcred)) >=
201 			    sizeof(idm->idm_bindcred)) {
202 				yyerror("directory bindcred truncated");
203 				free($2);
204 				YYERROR;
205 			}
206 			free($2);
207 		}
208 		| BASEDN STRING			{
209 			if (strlcpy(idm->idm_basedn, $2,
210 			    sizeof(idm->idm_basedn)) >=
211 			    sizeof(idm->idm_basedn)) {
212 				yyerror("directory basedn truncated");
213 				free($2);
214 				YYERROR;
215 			}
216 			free($2);
217 		}
218 		| GROUPDN STRING		{
219 			if(strlcpy(idm->idm_groupdn, $2,
220 			    sizeof(idm->idm_groupdn)) >=
221 			    sizeof(idm->idm_groupdn)) {
222 				yyerror("directory groupdn truncated");
223 				free($2);
224 				YYERROR;
225 			}
226 			free($2);
227 		}
228 		| opcode FILTER STRING			{
229 			if (strlcpy(idm->idm_filters[$1], $3,
230 			    sizeof(idm->idm_filters[$1])) >=
231 			    sizeof(idm->idm_filters[$1])) {
232 				yyerror("filter truncated");
233 				free($3);
234 				YYERROR;
235 			}
236 			free($3);
237 		}
238 		| ATTRIBUTE attribute MAPS TO STRING	{
239 			if (strlcpy(idm->idm_attrs[$2], $5,
240 			    sizeof(idm->idm_attrs[$2])) >=
241 			    sizeof(idm->idm_attrs[$2])) {
242 				yyerror("attribute truncated");
243 				free($5);
244 				YYERROR;
245 			}
246 			free($5);
247 		}
248 		| FIXED ATTRIBUTE attribute STRING	{
249 			if (strlcpy(idm->idm_attrs[$3], $4,
250 			    sizeof(idm->idm_attrs[$3])) >=
251 			    sizeof(idm->idm_attrs[$3])) {
252 				yyerror("attribute truncated");
253 				free($4);
254 				YYERROR;
255 			}
256 			idm->idm_flags |= F_FIXED_ATTR($3);
257 			free($4);
258 		}
259 		| LIST attribute MAPS TO STRING	{
260 			if (strlcpy(idm->idm_attrs[$2], $5,
261 			    sizeof(idm->idm_attrs[$2])) >=
262 			    sizeof(idm->idm_attrs[$2])) {
263 				yyerror("attribute truncated");
264 				free($5);
265 				YYERROR;
266 			}
267 			idm->idm_list |= F_LIST($2);
268 			free($5);
269 		}
270 		;
271 
272 directory	: DIRECTORY STRING port {
273 			if ((idm = calloc(1, sizeof(*idm))) == NULL)
274 				fatal(NULL);
275 			idm->idm_id = conf->sc_maxid++;
276 
277 			if (strlcpy(idm->idm_name, $2,
278 			    sizeof(idm->idm_name)) >=
279 			    sizeof(idm->idm_name)) {
280 				yyerror("attribute truncated");
281 				free($2);
282 				YYERROR;
283 			}
284 
285 			free($2);
286 		} '{' optnl diropts '}'			{
287 			TAILQ_INSERT_TAIL(&conf->sc_idms, idm, idm_entry);
288 			idm = NULL;
289 		}
290 		;
291 
292 main		: INTERVAL NUMBER			{
293 			conf->sc_conf_tv.tv_sec = $2;
294 			conf->sc_conf_tv.tv_usec = 0;
295 		}
296 		| DOMAIN STRING				{
297 			if (strlcpy(conf->sc_domainname, $2,
298 			    sizeof(conf->sc_domainname)) >=
299 			    sizeof(conf->sc_domainname)) {
300 				yyerror("domainname truncated");
301 				free($2);
302 				YYERROR;
303 			}
304 			free($2);
305 		}
306 		| PROVIDE MAP STRING			{
307 			if (strcmp($3, "passwd.byname") == 0)
308 				conf->sc_flags |= YPMAP_PASSWD_BYNAME;
309 			else if (strcmp($3, "passwd.byuid") == 0)
310 				conf->sc_flags |= YPMAP_PASSWD_BYUID;
311 			else if (strcmp($3, "master.passwd.byname") == 0)
312 				conf->sc_flags |= YPMAP_MASTER_PASSWD_BYNAME;
313 			else if (strcmp($3, "master.passwd.byuid") == 0)
314 				conf->sc_flags |= YPMAP_MASTER_PASSWD_BYUID;
315 			else if (strcmp($3, "group.byname") == 0)
316 				conf->sc_flags |= YPMAP_GROUP_BYNAME;
317 			else if (strcmp($3, "group.bygid") == 0)
318 				conf->sc_flags |= YPMAP_GROUP_BYGID;
319 			else if (strcmp($3, "netid.byname") == 0)
320 				conf->sc_flags |= YPMAP_NETID_BYNAME;
321 			else {
322 				yyerror("unsupported map type: %s", $3);
323 				free($3);
324 				YYERROR;
325 			}
326 			free($3);
327 		}
328 		;
329 
330 diropts		: diropts diropt nl
331 		| diropt optnl
332 		;
333 
334 %%
335 
336 struct keywords {
337 	const char	*k_name;
338 	int		 k_val;
339 };
340 
341 int
342 yyerror(const char *fmt, ...)
343 {
344 	va_list		 ap;
345 	char		*msg;
346 
347 	file->errors++;
348 	va_start(ap, fmt);
349 	if (vasprintf(&msg, fmt, ap) == -1)
350 		fatalx("yyerror vasprintf");
351 	va_end(ap);
352 	logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
353 	free(msg);
354 	return (0);
355 }
356 
357 int
358 kw_cmp(const void *k, const void *e)
359 {
360 	return (strcmp(k, ((const struct keywords *)e)->k_name));
361 }
362 
363 int
364 lookup(char *s)
365 {
366 	/* this has to be sorted always */
367 	static const struct keywords keywords[] = {
368 		{ "attribute",		ATTRIBUTE },
369 		{ "basedn",		BASEDN },
370 		{ "bindcred",		BINDCRED },
371 		{ "binddn",		BINDDN },
372 		{ "change",		CHANGE },
373 		{ "class",		CLASS },
374 		{ "directory",		DIRECTORY },
375 		{ "domain",		DOMAIN },
376 		{ "expire",		EXPIRE },
377 		{ "filter",		FILTER },
378 		{ "fixed",		FIXED },
379 		{ "gecos",		GECOS },
380 		{ "gid",		GID },
381 		{ "group",		GROUP },
382 		{ "groupdn",		GROUPDN },
383 		{ "groupgid",		GROUPGID },
384 		{ "groupmembers",	GROUPMEMBERS },
385 		{ "groupname",		GROUPNAME },
386 		{ "grouppasswd",	GROUPPASSWD },
387 		{ "home",		HOME },
388 		{ "include",		INCLUDE },
389 		{ "interval",		INTERVAL },
390 		{ "list",		LIST },
391 		{ "map",		MAP },
392 		{ "maps",		MAPS },
393 		{ "name",		NAME },
394 		{ "passwd",		PASSWD },
395 		{ "port",		PORT },
396 		{ "provide",		PROVIDE },
397 		{ "server",		SERVER },
398 		{ "shell",		SHELL },
399 		{ "to",			TO },
400 		{ "uid",		UID },
401 		{ "user",		USER },
402 	};
403 	const struct keywords	*p;
404 
405 	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
406 	    sizeof(keywords[0]), kw_cmp);
407 
408 	if (p)
409 		return (p->k_val);
410 	else
411 		return (STRING);
412 }
413 
414 #define MAXPUSHBACK	128
415 
416 u_char	*parsebuf;
417 int	 parseindex;
418 u_char	 pushback_buffer[MAXPUSHBACK];
419 int	 pushback_index = 0;
420 
421 int
422 lgetc(int quotec)
423 {
424 	int		c, next;
425 
426 	if (parsebuf) {
427 		/* Read character from the parsebuffer instead of input. */
428 		if (parseindex >= 0) {
429 			c = parsebuf[parseindex++];
430 			if (c != '\0')
431 				return (c);
432 			parsebuf = NULL;
433 		} else
434 			parseindex++;
435 	}
436 
437 	if (pushback_index)
438 		return (pushback_buffer[--pushback_index]);
439 
440 	if (quotec) {
441 		if ((c = getc(file->stream)) == EOF) {
442 			yyerror("reached end of file while parsing "
443 			    "quoted string");
444 			if (file == topfile || popfile() == EOF)
445 				return (EOF);
446 			return (quotec);
447 		}
448 		return (c);
449 	}
450 
451 	while ((c = getc(file->stream)) == '\\') {
452 		next = getc(file->stream);
453 		if (next != '\n') {
454 			c = next;
455 			break;
456 		}
457 		yylval.lineno = file->lineno;
458 		file->lineno++;
459 	}
460 
461 	while (c == EOF) {
462 		if (file == topfile || popfile() == EOF)
463 			return (EOF);
464 		c = getc(file->stream);
465 	}
466 	return (c);
467 }
468 
469 int
470 lungetc(int c)
471 {
472 	if (c == EOF)
473 		return (EOF);
474 	if (parsebuf) {
475 		parseindex--;
476 		if (parseindex >= 0)
477 			return (c);
478 	}
479 	if (pushback_index < MAXPUSHBACK-1)
480 		return (pushback_buffer[pushback_index++] = c);
481 	else
482 		return (EOF);
483 }
484 
485 int
486 findeol(void)
487 {
488 	int	c;
489 
490 	parsebuf = NULL;
491 
492 	/* skip to either EOF or the first real EOL */
493 	while (1) {
494 		if (pushback_index)
495 			c = pushback_buffer[--pushback_index];
496 		else
497 			c = lgetc(0);
498 		if (c == '\n') {
499 			file->lineno++;
500 			break;
501 		}
502 		if (c == EOF)
503 			break;
504 	}
505 	return (ERROR);
506 }
507 
508 int
509 yylex(void)
510 {
511 	u_char	 buf[8096];
512 	u_char	*p, *val;
513 	int	 quotec, next, c;
514 	int	 token;
515 
516 top:
517 	p = buf;
518 	while ((c = lgetc(0)) == ' ' || c == '\t')
519 		; /* nothing */
520 
521 	yylval.lineno = file->lineno;
522 	if (c == '#')
523 		while ((c = lgetc(0)) != '\n' && c != EOF)
524 			; /* nothing */
525 	if (c == '$' && parsebuf == NULL) {
526 		while (1) {
527 			if ((c = lgetc(0)) == EOF)
528 				return (0);
529 
530 			if (p + 1 >= buf + sizeof(buf) - 1) {
531 				yyerror("string too long");
532 				return (findeol());
533 			}
534 			if (isalnum(c) || c == '_') {
535 				*p++ = c;
536 				continue;
537 			}
538 			*p = '\0';
539 			lungetc(c);
540 			break;
541 		}
542 		val = symget(buf);
543 		if (val == NULL) {
544 			yyerror("macro '%s' not defined", buf);
545 			return (findeol());
546 		}
547 		parsebuf = val;
548 		parseindex = 0;
549 		goto top;
550 	}
551 
552 	switch (c) {
553 	case '\'':
554 	case '"':
555 		quotec = c;
556 		while (1) {
557 			if ((c = lgetc(quotec)) == EOF)
558 				return (0);
559 			if (c == '\n') {
560 				file->lineno++;
561 				continue;
562 			} else if (c == '\\') {
563 				if ((next = lgetc(quotec)) == EOF)
564 					return (0);
565 				if (next == quotec || c == ' ' || c == '\t')
566 					c = next;
567 				else if (next == '\n') {
568 					file->lineno++;
569 					continue;
570 				} else
571 					lungetc(next);
572 			} else if (c == quotec) {
573 				*p = '\0';
574 				break;
575 			} else if (c == '\0') {
576 				yyerror("syntax error");
577 				return (findeol());
578 			}
579 			if (p + 1 >= buf + sizeof(buf) - 1) {
580 				yyerror("string too long");
581 				return (findeol());
582 			}
583 			*p++ = c;
584 		}
585 		yylval.v.string = strdup(buf);
586 		if (yylval.v.string == NULL)
587 			err(1, "yylex: strdup");
588 		return (STRING);
589 	}
590 
591 #define allowed_to_end_number(x) \
592 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
593 
594 	if (c == '-' || isdigit(c)) {
595 		do {
596 			*p++ = c;
597 			if ((unsigned)(p-buf) >= sizeof(buf)) {
598 				yyerror("string too long");
599 				return (findeol());
600 			}
601 		} while ((c = lgetc(0)) != EOF && isdigit(c));
602 		lungetc(c);
603 		if (p == buf + 1 && buf[0] == '-')
604 			goto nodigits;
605 		if (c == EOF || allowed_to_end_number(c)) {
606 			const char *errstr = NULL;
607 
608 			*p = '\0';
609 			yylval.v.number = strtonum(buf, LLONG_MIN,
610 			    LLONG_MAX, &errstr);
611 			if (errstr) {
612 				yyerror("\"%s\" invalid number: %s",
613 				    buf, errstr);
614 				return (findeol());
615 			}
616 			return (NUMBER);
617 		} else {
618 nodigits:
619 			while (p > buf + 1)
620 				lungetc(*--p);
621 			c = *--p;
622 			if (c == '-')
623 				return (c);
624 		}
625 	}
626 
627 #define allowed_in_string(x) \
628 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
629 	x != '{' && x != '}' && x != '<' && x != '>' && \
630 	x != '!' && x != '=' && x != '#' && \
631 	x != ','))
632 
633 	if (isalnum(c) || c == ':' || c == '_') {
634 		do {
635 			*p++ = c;
636 			if ((unsigned)(p-buf) >= sizeof(buf)) {
637 				yyerror("string too long");
638 				return (findeol());
639 			}
640 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
641 		lungetc(c);
642 		*p = '\0';
643 		if ((token = lookup(buf)) == STRING)
644 			if ((yylval.v.string = strdup(buf)) == NULL)
645 				err(1, "yylex: strdup");
646 		return (token);
647 	}
648 	if (c == '\n') {
649 		yylval.lineno = file->lineno;
650 		file->lineno++;
651 	}
652 	if (c == EOF)
653 		return (0);
654 	return (c);
655 }
656 
657 int
658 check_file_secrecy(int fd, const char *fname)
659 {
660 	struct stat	st;
661 
662 	if (fstat(fd, &st)) {
663 		log_warn("cannot stat %s", fname);
664 		return (-1);
665 	}
666 	if (st.st_uid != 0 && st.st_uid != getuid()) {
667 		log_warnx("%s: owner not root or current user", fname);
668 		return (-1);
669 	}
670 	if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
671 		log_warnx("%s: group writable or world read/writable", fname);
672 		return (-1);
673 	}
674 	return (0);
675 }
676 
677 struct file *
678 pushfile(const char *name, int secret)
679 {
680 	struct file	*nfile;
681 
682 	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
683 		log_warn("malloc");
684 		return (NULL);
685 	}
686 	if ((nfile->name = strdup(name)) == NULL) {
687 		log_warn("malloc");
688 		free(nfile);
689 		return (NULL);
690 	}
691 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
692 		log_warn("%s", nfile->name);
693 		free(nfile->name);
694 		free(nfile);
695 		return (NULL);
696 	} else if (secret &&
697 	    check_file_secrecy(fileno(nfile->stream), nfile->name)) {
698 		fclose(nfile->stream);
699 		free(nfile->name);
700 		free(nfile);
701 		return (NULL);
702 	}
703 	nfile->lineno = 1;
704 	TAILQ_INSERT_TAIL(&files, nfile, entry);
705 	return (nfile);
706 }
707 
708 int
709 popfile(void)
710 {
711 	struct file	*prev;
712 
713 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
714 		prev->errors += file->errors;
715 
716 	TAILQ_REMOVE(&files, file, entry);
717 	fclose(file->stream);
718 	free(file->name);
719 	free(file);
720 	file = prev;
721 	return (file ? 0 : EOF);
722 }
723 
724 int
725 parse_config(struct env *x_conf, const char *filename, int opts)
726 {
727 	struct sym	*sym, *next;
728 
729 	conf = x_conf;
730 	bzero(conf, sizeof(*conf));
731 
732 	TAILQ_INIT(&conf->sc_idms);
733 	conf->sc_conf_tv.tv_sec = DEFAULT_INTERVAL;
734 	conf->sc_conf_tv.tv_usec = 0;
735 
736 	errors = 0;
737 
738 	if ((file = pushfile(filename, 1)) == NULL) {
739 		return (-1);
740 	}
741 	topfile = file;
742 
743 	/*
744 	 * parse configuration
745 	 */
746 	setservent(1);
747 	yyparse();
748 	endservent();
749 	errors = file->errors;
750 	popfile();
751 
752 	/* Free macros and check which have not been used. */
753 	for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
754 		next = TAILQ_NEXT(sym, entry);
755 		if ((opts & YPLDAP_OPT_VERBOSE) && !sym->used)
756 			fprintf(stderr, "warning: macro '%s' not "
757 			    "used\n", sym->nam);
758 		if (!sym->persist) {
759 			free(sym->nam);
760 			free(sym->val);
761 			TAILQ_REMOVE(&symhead, sym, entry);
762 			free(sym);
763 		}
764 	}
765 
766 	if (errors) {
767 		return (-1);
768 	}
769 
770 	return (0);
771 }
772 
773 int
774 symset(const char *nam, const char *val, int persist)
775 {
776 	struct sym	*sym;
777 
778 	for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
779 	    sym = TAILQ_NEXT(sym, entry))
780 		;	/* nothing */
781 
782 	if (sym != NULL) {
783 		if (sym->persist == 1)
784 			return (0);
785 		else {
786 			free(sym->nam);
787 			free(sym->val);
788 			TAILQ_REMOVE(&symhead, sym, entry);
789 			free(sym);
790 		}
791 	}
792 	if ((sym = calloc(1, sizeof(*sym))) == NULL)
793 		return (-1);
794 
795 	sym->nam = strdup(nam);
796 	if (sym->nam == NULL) {
797 		free(sym);
798 		return (-1);
799 	}
800 	sym->val = strdup(val);
801 	if (sym->val == NULL) {
802 		free(sym->nam);
803 		free(sym);
804 		return (-1);
805 	}
806 	sym->used = 0;
807 	sym->persist = persist;
808 	TAILQ_INSERT_TAIL(&symhead, sym, entry);
809 	return (0);
810 }
811 
812 int
813 cmdline_symset(char *s)
814 {
815 	char	*sym, *val;
816 	int	ret;
817 	size_t	len;
818 
819 	if ((val = strrchr(s, '=')) == NULL)
820 		return (-1);
821 
822 	len = strlen(s) - strlen(val) + 1;
823 	if ((sym = malloc(len)) == NULL)
824 		errx(1, "cmdline_symset: malloc");
825 
826 	(void)strlcpy(sym, s, len);
827 
828 	ret = symset(sym, val + 1, 1);
829 	free(sym);
830 
831 	return (ret);
832 }
833 
834 char *
835 symget(const char *nam)
836 {
837 	struct sym	*sym;
838 
839 	TAILQ_FOREACH(sym, &symhead, entry)
840 		if (strcmp(nam, sym->nam) == 0) {
841 			sym->used = 1;
842 			return (sym->val);
843 		}
844 	return (NULL);
845 }
846