xref: /openbsd/usr.sbin/ntpd/parse.y (revision 8932bfb7)
1 /*	$OpenBSD: parse.y,v 1.47 2010/08/03 18:42:40 henning Exp $ */
2 
3 /*
4  * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
5  * Copyright (c) 2001 Markus Friedl.  All rights reserved.
6  * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
7  * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
8  *
9  * Permission to use, copy, modify, and distribute this software for any
10  * purpose with or without fee is hereby granted, provided that the above
11  * copyright notice and this permission notice appear in all copies.
12  *
13  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20  */
21 
22 %{
23 #include <sys/types.h>
24 #include <sys/socket.h>
25 #include <netinet/in.h>
26 #include <arpa/inet.h>
27 
28 #include <ctype.h>
29 #include <errno.h>
30 #include <limits.h>
31 #include <stdarg.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <syslog.h>
36 
37 #include "ntpd.h"
38 
39 TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
40 static struct file {
41 	TAILQ_ENTRY(file)	 entry;
42 	FILE			*stream;
43 	char			*name;
44 	int			 lineno;
45 	int			 errors;
46 } *file, *topfile;
47 struct file	*pushfile(const char *);
48 int		 popfile(void);
49 int		 yyparse(void);
50 int		 yylex(void);
51 int		 yyerror(const char *, ...);
52 int		 kw_cmp(const void *, const void *);
53 int		 lookup(char *);
54 int		 lgetc(int);
55 int		 lungetc(int);
56 int		 findeol(void);
57 
58 struct ntpd_conf		*conf;
59 
60 struct opts {
61 	int		weight;
62 	int		correction;
63 	char		*refstr;
64 } opts;
65 void		opts_default(void);
66 
67 typedef struct {
68 	union {
69 		int64_t			 number;
70 		char			*string;
71 		struct ntp_addr_wrap	*addr;
72 		struct opts		 opts;
73 	} v;
74 	int lineno;
75 } YYSTYPE;
76 
77 %}
78 
79 %token	LISTEN ON
80 %token	SERVER SERVERS SENSOR CORRECTION REFID WEIGHT
81 %token	ERROR
82 %token	<v.string>		STRING
83 %token	<v.number>		NUMBER
84 %type	<v.addr>		address
85 %type	<v.opts>		server_opts server_opts_l server_opt
86 %type	<v.opts>		sensor_opts sensor_opts_l sensor_opt
87 %type	<v.opts>		correction
88 %type	<v.opts>		refid
89 %type	<v.opts>		weight
90 %%
91 
92 grammar		: /* empty */
93 		| grammar '\n'
94 		| grammar main '\n'
95 		| grammar error '\n'		{ file->errors++; }
96 		;
97 
98 main		: LISTEN ON address	{
99 			struct listen_addr	*la;
100 			struct ntp_addr		*h, *next;
101 
102 			if ((h = $3->a) == NULL &&
103 			    (host_dns($3->name, &h) == -1 || !h)) {
104 				yyerror("could not resolve \"%s\"", $3->name);
105 				free($3->name);
106 				free($3);
107 				YYERROR;
108 			}
109 
110 			for (; h != NULL; h = next) {
111 				next = h->next;
112 				if (h->ss.ss_family == AF_UNSPEC) {
113 					conf->listen_all = 1;
114 					free(h);
115 					continue;
116 				}
117 				la = calloc(1, sizeof(struct listen_addr));
118 				if (la == NULL)
119 					fatal("listen on calloc");
120 				la->fd = -1;
121 				memcpy(&la->sa, &h->ss,
122 				    sizeof(struct sockaddr_storage));
123 				TAILQ_INSERT_TAIL(&conf->listen_addrs, la,
124 				    entry);
125 				free(h);
126 			}
127 			free($3->name);
128 			free($3);
129 		}
130 		| SERVERS address server_opts	{
131 			struct ntp_peer		*p;
132 			struct ntp_addr		*h, *next;
133 
134 			h = $2->a;
135 			do {
136 				if (h != NULL) {
137 					next = h->next;
138 					if (h->ss.ss_family != AF_INET &&
139 					    h->ss.ss_family != AF_INET6) {
140 						yyerror("IPv4 or IPv6 address "
141 						    "or hostname expected");
142 						free(h);
143 						free($2->name);
144 						free($2);
145 						YYERROR;
146 					}
147 					h->next = NULL;
148 				} else
149 					next = NULL;
150 
151 				p = new_peer();
152 				p->weight = $3.weight;
153 				p->addr = h;
154 				p->addr_head.a = h;
155 				p->addr_head.pool = 1;
156 				p->addr_head.name = strdup($2->name);
157 				if (p->addr_head.name == NULL)
158 					fatal(NULL);
159 				if (p->addr != NULL)
160 					p->state = STATE_DNS_DONE;
161 				TAILQ_INSERT_TAIL(&conf->ntp_peers, p, entry);
162 
163 				h = next;
164 			} while (h != NULL);
165 
166 			free($2->name);
167 			free($2);
168 		}
169 		| SERVER address server_opts {
170 			struct ntp_peer		*p;
171 			struct ntp_addr		*h, *next;
172 
173 			p = new_peer();
174 			for (h = $2->a; h != NULL; h = next) {
175 				next = h->next;
176 				if (h->ss.ss_family != AF_INET &&
177 				    h->ss.ss_family != AF_INET6) {
178 					yyerror("IPv4 or IPv6 address "
179 					    "or hostname expected");
180 					free(h);
181 					free(p);
182 					free($2->name);
183 					free($2);
184 					YYERROR;
185 				}
186 				h->next = p->addr;
187 				p->addr = h;
188 			}
189 
190 			p->weight = $3.weight;
191 			p->addr_head.a = p->addr;
192 			p->addr_head.pool = 0;
193 			p->addr_head.name = strdup($2->name);
194 			if (p->addr_head.name == NULL)
195 				fatal(NULL);
196 			if (p->addr != NULL)
197 				p->state = STATE_DNS_DONE;
198 			TAILQ_INSERT_TAIL(&conf->ntp_peers, p, entry);
199 			free($2->name);
200 			free($2);
201 		}
202 		| SENSOR STRING	sensor_opts {
203 			struct ntp_conf_sensor	*s;
204 
205 			s = new_sensor($2);
206 			s->weight = $3.weight;
207 			s->correction = $3.correction;
208 			s->refstr = $3.refstr;
209 			free($2);
210 			TAILQ_INSERT_TAIL(&conf->ntp_conf_sensors, s, entry);
211 		}
212 		;
213 
214 address		: STRING		{
215 			if (($$ = calloc(1, sizeof(struct ntp_addr_wrap))) ==
216 			    NULL)
217 				fatal(NULL);
218 			if (host($1, &$$->a) == -1) {
219 				yyerror("could not parse address spec \"%s\"",
220 				    $1);
221 				free($1);
222 				free($$);
223 				YYERROR;
224 			}
225 			$$->name = $1;
226 		}
227 		;
228 
229 server_opts	:	{ opts_default(); }
230 		  server_opts_l
231 			{ $$ = opts; }
232 		|	{ opts_default(); $$ = opts; }
233 		;
234 server_opts_l	: server_opts_l server_opt
235 		| server_opt
236 		;
237 server_opt	: weight
238 		;
239 
240 sensor_opts	:	{ opts_default(); }
241 		  sensor_opts_l
242 			{ $$ = opts; }
243 		|	{ opts_default(); $$ = opts; }
244 		;
245 sensor_opts_l	: sensor_opts_l sensor_opt
246 		| sensor_opt
247 		;
248 sensor_opt	: correction
249 		| refid
250 		| weight
251 		;
252 
253 correction	: CORRECTION NUMBER {
254 			if ($2 < -127000000 || $2 > 127000000) {
255 				yyerror("correction must be between "
256 				    "-127000000 and 127000000 microseconds");
257 				YYERROR;
258 			}
259 			opts.correction = $2;
260 		}
261 		;
262 
263 refid		: REFID STRING {
264 			size_t l = strlen($2);
265 
266 			if (l < 1 || l > 4) {
267 				yyerror("refid must be 1 to 4 characters");
268 				free($2);
269 				YYERROR;
270 			}
271 			opts.refstr = $2;
272 		}
273 		;
274 
275 weight		: WEIGHT NUMBER	{
276 			if ($2 < 1 || $2 > 10) {
277 				yyerror("weight must be between 1 and 10");
278 				YYERROR;
279 			}
280 			opts.weight = $2;
281 		}
282 		;
283 
284 %%
285 
286 void
287 opts_default(void)
288 {
289 	bzero(&opts, sizeof opts);
290 	opts.weight = 1;
291 }
292 
293 struct keywords {
294 	const char	*k_name;
295 	int		 k_val;
296 };
297 
298 int
299 yyerror(const char *fmt, ...)
300 {
301 	va_list		 ap;
302 	char		*nfmt;
303 
304 	file->errors++;
305 	va_start(ap, fmt);
306 	if (asprintf(&nfmt, "%s:%d: %s", file->name, yylval.lineno, fmt) == -1)
307 		fatalx("yyerror asprintf");
308 	vlog(LOG_CRIT, nfmt, ap);
309 	va_end(ap);
310 	free(nfmt);
311 	return (0);
312 }
313 
314 int
315 kw_cmp(const void *k, const void *e)
316 {
317 	return (strcmp(k, ((const struct keywords *)e)->k_name));
318 }
319 
320 int
321 lookup(char *s)
322 {
323 	/* this has to be sorted always */
324 	static const struct keywords keywords[] = {
325 		{ "correction",		CORRECTION},
326 		{ "listen",		LISTEN},
327 		{ "on",			ON},
328 		{ "refid",		REFID},
329 		{ "sensor",		SENSOR},
330 		{ "server",		SERVER},
331 		{ "servers",		SERVERS},
332 		{ "weight",		WEIGHT}
333 	};
334 	const struct keywords	*p;
335 
336 	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
337 	    sizeof(keywords[0]), kw_cmp);
338 
339 	if (p)
340 		return (p->k_val);
341 	else
342 		return (STRING);
343 }
344 
345 #define MAXPUSHBACK	128
346 
347 char	*parsebuf;
348 int	 parseindex;
349 char	 pushback_buffer[MAXPUSHBACK];
350 int	 pushback_index = 0;
351 
352 int
353 lgetc(int quotec)
354 {
355 	int		c, next;
356 
357 	if (parsebuf) {
358 		/* Read character from the parsebuffer instead of input. */
359 		if (parseindex >= 0) {
360 			c = parsebuf[parseindex++];
361 			if (c != '\0')
362 				return (c);
363 			parsebuf = NULL;
364 		} else
365 			parseindex++;
366 	}
367 
368 	if (pushback_index)
369 		return (pushback_buffer[--pushback_index]);
370 
371 	if (quotec) {
372 		if ((c = getc(file->stream)) == EOF) {
373 			yyerror("reached end of file while parsing "
374 			    "quoted string");
375 			if (file == topfile || popfile() == EOF)
376 				return (EOF);
377 			return (quotec);
378 		}
379 		return (c);
380 	}
381 
382 	while ((c = getc(file->stream)) == '\\') {
383 		next = getc(file->stream);
384 		if (next != '\n') {
385 			c = next;
386 			break;
387 		}
388 		yylval.lineno = file->lineno;
389 		file->lineno++;
390 	}
391 
392 	while (c == EOF) {
393 		if (file == topfile || popfile() == EOF)
394 			return (EOF);
395 		c = getc(file->stream);
396 	}
397 	return (c);
398 }
399 
400 int
401 lungetc(int c)
402 {
403 	if (c == EOF)
404 		return (EOF);
405 	if (parsebuf) {
406 		parseindex--;
407 		if (parseindex >= 0)
408 			return (c);
409 	}
410 	if (pushback_index < MAXPUSHBACK-1)
411 		return (pushback_buffer[pushback_index++] = c);
412 	else
413 		return (EOF);
414 }
415 
416 int
417 findeol(void)
418 {
419 	int	c;
420 
421 	parsebuf = NULL;
422 
423 	/* skip to either EOF or the first real EOL */
424 	while (1) {
425 		if (pushback_index)
426 			c = pushback_buffer[--pushback_index];
427 		else
428 			c = lgetc(0);
429 		if (c == '\n') {
430 			file->lineno++;
431 			break;
432 		}
433 		if (c == EOF)
434 			break;
435 	}
436 	return (ERROR);
437 }
438 
439 int
440 yylex(void)
441 {
442 	char	 buf[8096];
443 	char	*p;
444 	int	 quotec, next, c;
445 	int	 token;
446 
447 	p = buf;
448 	while ((c = lgetc(0)) == ' ' || c == '\t')
449 		; /* nothing */
450 
451 	yylval.lineno = file->lineno;
452 	if (c == '#')
453 		while ((c = lgetc(0)) != '\n' && c != EOF)
454 			; /* nothing */
455 
456 	switch (c) {
457 	case '\'':
458 	case '"':
459 		quotec = c;
460 		while (1) {
461 			if ((c = lgetc(quotec)) == EOF)
462 				return (0);
463 			if (c == '\n') {
464 				file->lineno++;
465 				continue;
466 			} else if (c == '\\') {
467 				if ((next = lgetc(quotec)) == EOF)
468 					return (0);
469 				if (next == quotec || c == ' ' || c == '\t')
470 					c = next;
471 				else if (next == '\n') {
472 					file->lineno++;
473 					continue;
474 				} else
475 					lungetc(next);
476 			} else if (c == quotec) {
477 				*p = '\0';
478 				break;
479 			}
480 			if (p + 1 >= buf + sizeof(buf) - 1) {
481 				yyerror("string too long");
482 				return (findeol());
483 			}
484 			*p++ = (char)c;
485 		}
486 		yylval.v.string = strdup(buf);
487 		if (yylval.v.string == NULL)
488 			fatal("yylex: strdup");
489 		return (STRING);
490 	}
491 
492 #define allowed_to_end_number(x) \
493 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
494 
495 	if (c == '-' || isdigit(c)) {
496 		do {
497 			*p++ = c;
498 			if ((unsigned)(p-buf) >= sizeof(buf)) {
499 				yyerror("string too long");
500 				return (findeol());
501 			}
502 		} while ((c = lgetc(0)) != EOF && isdigit(c));
503 		lungetc(c);
504 		if (p == buf + 1 && buf[0] == '-')
505 			goto nodigits;
506 		if (c == EOF || allowed_to_end_number(c)) {
507 			const char *errstr = NULL;
508 
509 			*p = '\0';
510 			yylval.v.number = strtonum(buf, LLONG_MIN,
511 			    LLONG_MAX, &errstr);
512 			if (errstr) {
513 				yyerror("\"%s\" invalid number: %s",
514 				    buf, errstr);
515 				return (findeol());
516 			}
517 			return (NUMBER);
518 		} else {
519 nodigits:
520 			while (p > buf + 1)
521 				lungetc(*--p);
522 			c = *--p;
523 			if (c == '-')
524 				return (c);
525 		}
526 	}
527 
528 #define allowed_in_string(x) \
529 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
530 	x != '{' && x != '}' && x != '<' && x != '>' && \
531 	x != '!' && x != '=' && x != '/' && x != '#' && \
532 	x != ','))
533 
534 	if (isalnum(c) || c == ':' || c == '_' || c == '*') {
535 		do {
536 			*p++ = c;
537 			if ((unsigned)(p-buf) >= sizeof(buf)) {
538 				yyerror("string too long");
539 				return (findeol());
540 			}
541 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
542 		lungetc(c);
543 		*p = '\0';
544 		if ((token = lookup(buf)) == STRING)
545 			if ((yylval.v.string = strdup(buf)) == NULL)
546 				fatal("yylex: strdup");
547 		return (token);
548 	}
549 	if (c == '\n') {
550 		yylval.lineno = file->lineno;
551 		file->lineno++;
552 	}
553 	if (c == EOF)
554 		return (0);
555 	return (c);
556 }
557 
558 struct file *
559 pushfile(const char *name)
560 {
561 	struct file	*nfile;
562 
563 	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
564 		log_warn("malloc");
565 		return (NULL);
566 	}
567 	if ((nfile->name = strdup(name)) == NULL) {
568 		log_warn("malloc");
569 		free(nfile);
570 		return (NULL);
571 	}
572 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
573 		log_warn("%s", nfile->name);
574 		free(nfile->name);
575 		free(nfile);
576 		return (NULL);
577 	}
578 	nfile->lineno = 1;
579 	TAILQ_INSERT_TAIL(&files, nfile, entry);
580 	return (nfile);
581 }
582 
583 int
584 popfile(void)
585 {
586 	struct file	*prev;
587 
588 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
589 		prev->errors += file->errors;
590 
591 	TAILQ_REMOVE(&files, file, entry);
592 	fclose(file->stream);
593 	free(file->name);
594 	free(file);
595 	file = prev;
596 	return (file ? 0 : EOF);
597 }
598 
599 int
600 parse_config(const char *filename, struct ntpd_conf *xconf)
601 {
602 	int		 errors = 0;
603 
604 	conf = xconf;
605 	TAILQ_INIT(&conf->listen_addrs);
606 	TAILQ_INIT(&conf->ntp_peers);
607 	TAILQ_INIT(&conf->ntp_conf_sensors);
608 
609 	if ((file = pushfile(filename)) == NULL) {
610 		return (-1);
611 	}
612 	topfile = file;
613 
614 	yyparse();
615 	errors = file->errors;
616 	popfile();
617 
618 	return (errors ? -1 : 0);
619 }
620