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