1/*	$OpenBSD: parse.y,v 1.8 2023/11/25 12:00:39 florian Exp $	*/
2
3/*
4 * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
5 * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
6 * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
7 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
8 * Copyright (c) 2001 Markus Friedl.  All rights reserved.
9 * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
10 * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
11 *
12 * Permission to use, copy, modify, and distribute this software for any
13 * purpose with or without fee is hereby granted, provided that the above
14 * copyright notice and this permission notice appear in all copies.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
17 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
19 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
21 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
22 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 */
24
25%{
26#include <sys/types.h>
27#include <sys/queue.h>
28#include <sys/socket.h>
29#include <sys/stat.h>
30
31#include <net/if.h>
32
33#include <netinet/in.h>
34#include <netinet/if_ether.h>
35
36#include <arpa/inet.h>
37
38#include <ctype.h>
39#include <err.h>
40#include <errno.h>
41#include <event.h>
42#include <imsg.h>
43#include <limits.h>
44#include <stdarg.h>
45#include <stdio.h>
46#include <string.h>
47#include <syslog.h>
48#include <unistd.h>
49#include <vis.h>
50
51#include "log.h"
52#include "dhcpleased.h"
53#include "frontend.h"
54
55TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
56static 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;
67struct file	*pushfile(const char *, int);
68int		 popfile(void);
69int		 check_file_secrecy(int, const char *);
70int		 yyparse(void);
71int		 yylex(void);
72int		 yyerror(const char *, ...)
73    __attribute__((__format__ (printf, 1, 2)))
74    __attribute__((__nonnull__ (1)));
75int		 kw_cmp(const void *, const void *);
76int		 lookup(char *);
77int		 igetc(void);
78int		 lgetc(int);
79void		 lungetc(int);
80int		 findeol(void);
81
82TAILQ_HEAD(symhead, sym)	 symhead = TAILQ_HEAD_INITIALIZER(symhead);
83struct sym {
84	TAILQ_ENTRY(sym)	 entry;
85	int			 used;
86	int			 persist;
87	char			*nam;
88	char			*val;
89};
90
91int	 symset(const char *, const char *, int);
92char	*symget(const char *);
93
94static struct dhcpleased_conf	*conf;
95static int			 errors;
96
97static struct iface_conf	*iface_conf;
98
99struct iface_conf	*conf_get_iface(char *);
100
101typedef struct {
102	union {
103		int64_t		 number;
104		char		*string;
105	} v;
106	int lineno;
107} YYSTYPE;
108
109%}
110
111%token	DHCP_IFACE ERROR SEND VENDOR CLASS ID CLIENT IGNORE DNS ROUTES HOST NAME
112%token	NO PREFER IPV6
113
114%token	<v.string>	STRING
115%token	<v.number>	NUMBER
116%type	<v.string>	string
117
118%%
119
120grammar		: /* empty */
121		| grammar '\n'
122		| grammar varset '\n'
123		| grammar dhcp_iface '\n'
124		| grammar error '\n'		{ file->errors++; }
125		;
126
127string		: string STRING	{
128			if (asprintf(&$$, "%s %s", $1, $2) == -1) {
129				free($1);
130				free($2);
131				yyerror("string: asprintf");
132				YYERROR;
133			}
134			free($1);
135			free($2);
136		}
137		| STRING
138		;
139
140varset		: STRING '=' string		{
141			char *s = $1;
142			if (log_getverbose() == 1)
143				printf("%s = \"%s\"\n", $1, $3);
144			while (*s++) {
145				if (isspace((unsigned char)*s)) {
146					yyerror("macro name cannot contain "
147					    "whitespace");
148					free($1);
149					free($3);
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
160optnl		: '\n' optnl		/* zero or more newlines */
161		| /*empty*/
162		;
163
164nl		: '\n' optnl		/* one or more newlines */
165		;
166
167dhcp_iface	: DHCP_IFACE STRING {
168			iface_conf = conf_get_iface($2);
169		} '{' iface_block '}' {
170			iface_conf = NULL;
171		}
172		;
173
174iface_block	: optnl ifaceopts_l
175		| optnl
176		;
177
178ifaceopts_l	: ifaceopts_l ifaceoptsl nl
179		| ifaceoptsl optnl
180		;
181
182ifaceoptsl	: SEND VENDOR CLASS ID STRING {
183			ssize_t len;
184			char	buf[256];
185
186			if (iface_conf->vc_id != NULL) {
187				yyerror("vendor class id already set");
188				YYERROR;
189			}
190
191			len = strnunvis(buf, $5, sizeof(buf));
192			free($5);
193
194			if (len == -1) {
195				yyerror("invalid vendor class id");
196				YYERROR;
197			}
198			if ((size_t)len >= sizeof(buf)) {
199				yyerror("vendor class id too long");
200				YYERROR;
201			}
202
203			iface_conf->vc_id_len = 2 + strlen(buf);
204			iface_conf->vc_id = malloc(iface_conf->vc_id_len);
205			if (iface_conf->vc_id == NULL) {
206				yyerror("malloc");
207				YYERROR;
208			}
209			iface_conf->vc_id[0] = DHO_DHCP_CLASS_IDENTIFIER;
210			iface_conf->vc_id[1] = iface_conf->vc_id_len - 2;
211			memcpy(&iface_conf->vc_id[2], buf,
212			    iface_conf->vc_id_len - 2);
213		}
214		| SEND CLIENT ID STRING {
215			size_t			 i;
216			ssize_t			 len;
217			int			 not_hex = 0, val;
218			char			 buf[256], *hex, *p, excess;
219
220			if (iface_conf->c_id != NULL) {
221				yyerror("client-id already set");
222				YYERROR;
223			}
224
225			/* parse as hex string including the type byte */
226			if ((hex = strdup($4)) == NULL) {
227				free($4);
228				yyerror("malloc");
229				YYERROR;
230			}
231			for (i = 0; (p = strsep(&hex, ":")) != NULL && i <
232				 sizeof(buf); ) {
233				if (sscanf(p, "%x%c", &val, &excess) != 1 ||
234				    val < 0 || val > 0xff) {
235					not_hex = 1;
236					break;
237				}
238				buf[i++] = (val & 0xff);
239			}
240			if (p != NULL && i == sizeof(buf))
241				not_hex = 1;
242			free(hex);
243
244			if (not_hex) {
245				len = strnunvis(buf, $4, sizeof(buf));
246				free($4);
247
248				if (len == -1) {
249					yyerror("invalid client-id");
250					YYERROR;
251				}
252				if ((size_t)len >= sizeof(buf)) {
253					yyerror("client-id too long");
254					YYERROR;
255				}
256				iface_conf->c_id_len = 2 + len;
257				iface_conf->c_id = malloc(iface_conf->c_id_len);
258				if (iface_conf->c_id == NULL) {
259					yyerror("malloc");
260					YYERROR;
261				}
262				memcpy(&iface_conf->c_id[2], buf,
263				    iface_conf->c_id_len - 2);
264			} else {
265				free($4);
266				iface_conf->c_id_len = 2 + i;
267				iface_conf->c_id = malloc(iface_conf->c_id_len);
268				if (iface_conf->c_id == NULL) {
269					yyerror("malloc");
270					YYERROR;
271				}
272				memcpy(&iface_conf->c_id[2], buf,
273				    iface_conf->c_id_len - 2);
274			}
275			iface_conf->c_id[0] = DHO_DHCP_CLIENT_IDENTIFIER;
276			iface_conf->c_id[1] = iface_conf->c_id_len - 2;
277		}
278		| SEND HOST NAME STRING {
279			if (iface_conf->h_name != NULL) {
280				free($4);
281				yyerror("host name already set");
282				YYERROR;
283			}
284			if (strlen($4) > 255) {
285				free($4);
286				yyerror("host name too long");
287				YYERROR;
288			}
289			iface_conf->h_name = $4;
290		}
291		| SEND NO HOST NAME {
292			if (iface_conf->h_name != NULL) {
293				yyerror("host name already set");
294				YYERROR;
295			}
296
297			if ((iface_conf->h_name = strdup("")) == NULL) {
298				yyerror("malloc");
299				YYERROR;
300			}
301		}
302		| IGNORE ROUTES {
303			iface_conf->ignore |= IGN_ROUTES;
304		}
305		| IGNORE DNS {
306			iface_conf->ignore |= IGN_DNS;
307		}
308		| IGNORE STRING {
309			int res;
310
311			if (iface_conf->ignore_servers_len >= MAX_SERVERS) {
312				yyerror("too many servers to ignore");
313				free($2);
314				YYERROR;
315			}
316			res = inet_pton(AF_INET, $2,
317			    &iface_conf->ignore_servers[
318			    iface_conf->ignore_servers_len++]);
319
320			if (res != 1) {
321				yyerror("Invalid server IP %s", $2);
322				free($2);
323				YYERROR;
324			}
325			free($2);
326		}
327		| PREFER IPV6 {
328			iface_conf->prefer_ipv6 = 1;
329		}
330		;
331%%
332
333struct keywords {
334	const char	*k_name;
335	int		 k_val;
336};
337
338int
339yyerror(const char *fmt, ...)
340{
341	va_list		 ap;
342	char		*msg;
343
344	file->errors++;
345	va_start(ap, fmt);
346	if (vasprintf(&msg, fmt, ap) == -1)
347		fatalx("yyerror vasprintf");
348	va_end(ap);
349	logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
350	free(msg);
351	return (0);
352}
353
354int
355kw_cmp(const void *k, const void *e)
356{
357	return (strcmp(k, ((const struct keywords *)e)->k_name));
358}
359
360int
361lookup(char *s)
362{
363	/* This has to be sorted always. */
364	static const struct keywords keywords[] = {
365		{"class",		CLASS},
366		{"client",		CLIENT},
367		{"dns",			DNS},
368		{"host",		HOST},
369		{"id",			ID},
370		{"ignore",		IGNORE},
371		{"interface",		DHCP_IFACE},
372		{"ipv6",		IPV6},
373		{"name",		NAME},
374		{"no",			NO},
375		{"prefer",		PREFER},
376		{"routes",		ROUTES},
377		{"send",		SEND},
378		{"vendor",		VENDOR},
379	};
380	const struct keywords	*p;
381
382	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
383	    sizeof(keywords[0]), kw_cmp);
384
385	if (p)
386		return (p->k_val);
387	else
388		return (STRING);
389}
390
391#define START_EXPAND	1
392#define DONE_EXPAND	2
393
394static int	expanding;
395
396int
397igetc(void)
398{
399	int	c;
400
401	while (1) {
402		if (file->ungetpos > 0)
403			c = file->ungetbuf[--file->ungetpos];
404		else
405			c = getc(file->stream);
406
407		if (c == START_EXPAND)
408			expanding = 1;
409		else if (c == DONE_EXPAND)
410			expanding = 0;
411		else
412			break;
413	}
414	return (c);
415}
416
417int
418lgetc(int quotec)
419{
420	int		c, next;
421
422	if (quotec) {
423		if ((c = igetc()) == EOF) {
424			yyerror("reached end of file while parsing "
425			    "quoted string");
426			if (file == topfile || popfile() == EOF)
427				return (EOF);
428			return (quotec);
429		}
430		return (c);
431	}
432
433	while ((c = igetc()) == '\\') {
434		next = igetc();
435		if (next != '\n') {
436			c = next;
437			break;
438		}
439		yylval.lineno = file->lineno;
440		file->lineno++;
441	}
442
443	if (c == EOF) {
444		/*
445		 * Fake EOL when hit EOF for the first time. This gets line
446		 * count right if last line in included file is syntactically
447		 * invalid and has no newline.
448		 */
449		if (file->eof_reached == 0) {
450			file->eof_reached = 1;
451			return ('\n');
452		}
453		while (c == EOF) {
454			if (file == topfile || popfile() == EOF)
455				return (EOF);
456			c = igetc();
457		}
458	}
459	return (c);
460}
461
462void
463lungetc(int c)
464{
465	if (c == EOF)
466		return;
467
468	if (file->ungetpos >= file->ungetsize) {
469		void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
470		if (p == NULL)
471			err(1, "lungetc");
472		file->ungetbuf = p;
473		file->ungetsize *= 2;
474	}
475	file->ungetbuf[file->ungetpos++] = c;
476}
477
478int
479findeol(void)
480{
481	int	c;
482
483	/* Skip to either EOF or the first real EOL. */
484	while (1) {
485		c = lgetc(0);
486		if (c == '\n') {
487			file->lineno++;
488			break;
489		}
490		if (c == EOF)
491			break;
492	}
493	return (ERROR);
494}
495
496int
497yylex(void)
498{
499	char	 buf[8096];
500	char	*p, *val;
501	int	 quotec, next, c;
502	int	 token;
503
504top:
505	p = buf;
506	while ((c = lgetc(0)) == ' ' || c == '\t')
507		; /* nothing */
508
509	yylval.lineno = file->lineno;
510	if (c == '#')
511		while ((c = lgetc(0)) != '\n' && c != EOF)
512			; /* nothing */
513	if (c == '$' && !expanding) {
514		while (1) {
515			if ((c = lgetc(0)) == EOF)
516				return (0);
517
518			if (p + 1 >= buf + sizeof(buf) - 1) {
519				yyerror("string too long");
520				return (findeol());
521			}
522			if (isalnum(c) || c == '_') {
523				*p++ = c;
524				continue;
525			}
526			*p = '\0';
527			lungetc(c);
528			break;
529		}
530		val = symget(buf);
531		if (val == NULL) {
532			yyerror("macro '%s' not defined", buf);
533			return (findeol());
534		}
535		p = val + strlen(val) - 1;
536		lungetc(DONE_EXPAND);
537		while (p >= val) {
538			lungetc((unsigned char)*p);
539			p--;
540		}
541		lungetc(START_EXPAND);
542		goto top;
543	}
544
545	switch (c) {
546	case '\'':
547	case '"':
548		quotec = c;
549		while (1) {
550			if ((c = lgetc(quotec)) == EOF)
551				return (0);
552			if (c == '\n') {
553				file->lineno++;
554				continue;
555			} else if (c == '\\') {
556				if ((next = lgetc(quotec)) == EOF)
557					return (0);
558				if (next == quotec || next == ' ' ||
559				    next == '\t')
560					c = next;
561				else if (next == '\n') {
562					file->lineno++;
563					continue;
564				} else
565					lungetc(next);
566			} else if (c == quotec) {
567				*p = '\0';
568				break;
569			} else if (c == '\0') {
570				yyerror("syntax error");
571				return (findeol());
572			}
573			if (p + 1 >= buf + sizeof(buf) - 1) {
574				yyerror("string too long");
575				return (findeol());
576			}
577			*p++ = c;
578		}
579		yylval.v.string = strdup(buf);
580		if (yylval.v.string == NULL)
581			err(1, "yylex: strdup");
582		return (STRING);
583	}
584
585#define allowed_to_end_number(x) \
586	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
587
588	if (c == '-' || isdigit(c)) {
589		do {
590			*p++ = c;
591			if ((size_t)(p-buf) >= sizeof(buf)) {
592				yyerror("string too long");
593				return (findeol());
594			}
595		} while ((c = lgetc(0)) != EOF && isdigit(c));
596		lungetc(c);
597		if (p == buf + 1 && buf[0] == '-')
598			goto nodigits;
599		if (c == EOF || allowed_to_end_number(c)) {
600			const char *errstr = NULL;
601
602			*p = '\0';
603			yylval.v.number = strtonum(buf, LLONG_MIN,
604			    LLONG_MAX, &errstr);
605			if (errstr) {
606				yyerror("\"%s\" invalid number: %s",
607				    buf, errstr);
608				return (findeol());
609			}
610			return (NUMBER);
611		} else {
612nodigits:
613			while (p > buf + 1)
614				lungetc((unsigned char)*--p);
615			c = (unsigned char)*--p;
616			if (c == '-')
617				return (c);
618		}
619	}
620
621#define allowed_in_string(x) \
622	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
623	x != '{' && x != '}' && \
624	x != '!' && x != '=' && x != '#' && \
625	x != ','))
626
627	if (isalnum(c) || c == ':' || c == '_') {
628		do {
629			*p++ = c;
630			if ((size_t)(p-buf) >= sizeof(buf)) {
631				yyerror("string too long");
632				return (findeol());
633			}
634		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
635		lungetc(c);
636		*p = '\0';
637		if ((token = lookup(buf)) == STRING)
638			if ((yylval.v.string = strdup(buf)) == NULL)
639				err(1, "yylex: strdup");
640		return (token);
641	}
642	if (c == '\n') {
643		yylval.lineno = file->lineno;
644		file->lineno++;
645	}
646	if (c == EOF)
647		return (0);
648	return (c);
649}
650
651int
652check_file_secrecy(int fd, const char *fname)
653{
654	struct stat	st;
655
656	if (fstat(fd, &st)) {
657		log_warn("cannot stat %s", fname);
658		return (-1);
659	}
660	if (st.st_uid != 0 && st.st_uid != getuid()) {
661		log_warnx("%s: owner not root or current user", fname);
662		return (-1);
663	}
664	if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
665		log_warnx("%s: group writable or world read/writable", fname);
666		return (-1);
667	}
668	return (0);
669}
670
671struct file *
672pushfile(const char *name, int secret)
673{
674	struct file	*nfile;
675
676	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
677		log_warn("calloc");
678		return (NULL);
679	}
680	if ((nfile->name = strdup(name)) == NULL) {
681		log_warn("strdup");
682		free(nfile);
683		return (NULL);
684	}
685	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
686		free(nfile->name);
687		free(nfile);
688		return (NULL);
689	} else if (secret &&
690	    check_file_secrecy(fileno(nfile->stream), nfile->name)) {
691		fclose(nfile->stream);
692		free(nfile->name);
693		free(nfile);
694		return (NULL);
695	}
696	nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
697	nfile->ungetsize = 16;
698	nfile->ungetbuf = malloc(nfile->ungetsize);
699	if (nfile->ungetbuf == NULL) {
700		log_warn("malloc");
701		fclose(nfile->stream);
702		free(nfile->name);
703		free(nfile);
704		return (NULL);
705	}
706	TAILQ_INSERT_TAIL(&files, nfile, entry);
707	return (nfile);
708}
709
710int
711popfile(void)
712{
713	struct file	*prev;
714
715	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
716		prev->errors += file->errors;
717
718	TAILQ_REMOVE(&files, file, entry);
719	fclose(file->stream);
720	free(file->name);
721	free(file->ungetbuf);
722	free(file);
723	file = prev;
724	return (file ? 0 : EOF);
725}
726
727struct dhcpleased_conf *
728parse_config(const char *filename)
729{
730	extern const char	 default_conffile[];
731	struct sym		*sym, *next;
732
733	conf = config_new_empty();
734
735	file = pushfile(filename, 0);
736	if (file == NULL) {
737		/* no default config file is fine */
738		if (errno == ENOENT && filename == default_conffile)
739			return (conf);
740		log_warn("%s", filename);
741		free(conf);
742		return (NULL);
743	}
744	topfile = file;
745
746	yyparse();
747	errors = file->errors;
748	popfile();
749
750	/* Free macros and check which have not been used. */
751	TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
752		if ((log_getverbose() == 2) && !sym->used)
753			fprintf(stderr, "warning: macro '%s' not used\n",
754			    sym->nam);
755		if (!sym->persist) {
756			free(sym->nam);
757			free(sym->val);
758			TAILQ_REMOVE(&symhead, sym, entry);
759			free(sym);
760		}
761	}
762
763	if (errors) {
764		config_clear(conf);
765		return (NULL);
766	}
767
768	return (conf);
769}
770
771int
772symset(const char *nam, const char *val, int persist)
773{
774	struct sym	*sym;
775
776	TAILQ_FOREACH(sym, &symhead, entry) {
777		if (strcmp(nam, sym->nam) == 0)
778			break;
779	}
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
811int
812cmdline_symset(char *s)
813{
814	char	*sym, *val;
815	int	ret;
816
817	if ((val = strrchr(s, '=')) == NULL)
818		return (-1);
819	sym = strndup(s, val - s);
820	if (sym == NULL)
821		errx(1, "%s: strndup", __func__);
822	ret = symset(sym, val + 1, 1);
823	free(sym);
824
825	return (ret);
826}
827
828char *
829symget(const char *nam)
830{
831	struct sym	*sym;
832
833	TAILQ_FOREACH(sym, &symhead, entry) {
834		if (strcmp(nam, sym->nam) == 0) {
835			sym->used = 1;
836			return (sym->val);
837		}
838	}
839	return (NULL);
840}
841
842struct iface_conf *
843conf_get_iface(char *name)
844{
845	struct iface_conf	*iface;
846	size_t			 n;
847
848	SIMPLEQ_FOREACH(iface, &conf->iface_list, entry) {
849		if (strcmp(name, iface->name) == 0)
850			return (iface);
851	}
852
853	iface = calloc(1, sizeof(*iface));
854	if (iface == NULL)
855		errx(1, "%s: calloc", __func__);
856	n = strlcpy(iface->name, name, sizeof(iface->name));
857	if (n >= sizeof(iface->name))
858		errx(1, "%s: name too long", __func__);
859
860
861	SIMPLEQ_INSERT_TAIL(&conf->iface_list, iface, entry);
862
863	return (iface);
864}
865