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