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