xref: /openbsd/usr.sbin/dhcpd/parse.c (revision a15cd296)
1 /*	$OpenBSD: parse.c,v 1.29 2024/06/27 16:39:31 florian Exp $	*/
2 
3 /*
4  * Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of The Internet Software Consortium nor the names
17  *    of its contributors may be used to endorse or promote products derived
18  *    from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
21  * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
22  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
23  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24  * DISCLAIMED.  IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
25  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  * This software has been written for the Internet Software Consortium
35  * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
36  * Enterprises.  To learn more about the Internet Software Consortium,
37  * see ``http://www.vix.com/isc''.  To learn more about Vixie
38  * Enterprises, see ``http://www.vix.com''.
39  */
40 
41 #include <sys/types.h>
42 #include <sys/socket.h>
43 
44 #include <net/if.h>
45 
46 #include <netinet/in.h>
47 #include <netinet/if_ether.h>
48 
49 #include <ctype.h>
50 #include <errno.h>
51 #include <stdarg.h>
52 #include <stdint.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <syslog.h>
57 #include <time.h>
58 #include <unistd.h>
59 
60 #include "dhcp.h"
61 #include "tree.h"
62 #include "dhcpd.h"
63 #include "dhctoken.h"
64 #include "log.h"
65 
66 /*
67  * Skip to the semicolon ending the current statement.   If we encounter
68  * braces, the matching closing brace terminates the statement.   If we
69  * encounter a right brace but haven't encountered a left brace, return
70  * leaving the brace in the token buffer for the caller.   If we see a
71  * semicolon and haven't seen a left brace, return.   This lets us skip
72  * over:
73  *
74  *	statement;
75  *	statement foo bar { }
76  *	statement foo bar { statement { } }
77  *	statement}
78  *
79  *	...et cetera.
80  */
81 void
skip_to_semi(FILE * cfile)82 skip_to_semi(FILE *cfile)
83 {
84 	int		 token;
85 	char		*val;
86 	int		 brace_count = 0;
87 
88 	do {
89 		token = peek_token(&val, cfile);
90 		if (token == '}') {
91 			if (brace_count) {
92 				token = next_token(&val, cfile);
93 				if (!--brace_count)
94 					return;
95 			} else
96 				return;
97 		} else if (token == '{') {
98 			brace_count++;
99 		} else if (token == ';' && !brace_count) {
100 			token = next_token(&val, cfile);
101 			return;
102 		} else if (token == '\n') {
103 			/*
104 			 * EOL only happens when parsing
105 			 * /etc/resolv.conf, and we treat it like a
106 			 * semicolon because the resolv.conf file is
107 			 * line-oriented.
108 			 */
109 			token = next_token(&val, cfile);
110 			return;
111 		}
112 		token = next_token(&val, cfile);
113 	} while (token != EOF);
114 }
115 
116 int
parse_semi(FILE * cfile)117 parse_semi(FILE *cfile)
118 {
119 	int token;
120 	char *val;
121 
122 	token = next_token(&val, cfile);
123 	if (token != ';') {
124 		parse_warn("semicolon expected.");
125 		skip_to_semi(cfile);
126 		return (0);
127 	}
128 	return (1);
129 }
130 
131 /*
132  * string-parameter :== STRING SEMI
133  */
134 char *
parse_string(FILE * cfile)135 parse_string(FILE *cfile)
136 {
137 	char *val, *s;
138 	int token;
139 
140 	token = next_token(&val, cfile);
141 	if (token != TOK_STRING) {
142 		parse_warn("filename must be a string");
143 		skip_to_semi(cfile);
144 		return (NULL);
145 	}
146 	s = strdup(val);
147 	if (s == NULL)
148 		fatalx("no memory for string %s.", val);
149 
150 	if (!parse_semi(cfile)) {
151 		free(s);
152 		return (NULL);
153 	}
154 	return (s);
155 }
156 
157 /*
158  * hostname :== identifier | hostname DOT identifier
159  */
160 char *
parse_host_name(FILE * cfile)161 parse_host_name(FILE *cfile)
162 {
163 	char *val, *s, *t;
164 	int token, len = 0;
165 	pair c = NULL;
166 
167 	/* Read a dotted hostname... */
168 	do {
169 		/* Read a token, which should be an identifier. */
170 		token = next_token(&val, cfile);
171 		if (!is_identifier(token)) {
172 			parse_warn("expecting an identifier in hostname");
173 			skip_to_semi(cfile);
174 			return (NULL);
175 		}
176 		/* Store this identifier... */
177 		s = strdup(val);
178 		if (s == NULL)
179 			fatalx("can't allocate temp space for hostname.");
180 		c = cons((caddr_t) s, c);
181 		len += strlen(s) + 1;
182 		/*
183 		 * Look for a dot; if it's there, keep going, otherwise
184 		 * we're done.
185 		 */
186 		token = peek_token(&val, cfile);
187 		if (token == '.')
188 			token = next_token(&val, cfile);
189 	} while (token == '.');
190 
191 	/* Assemble the hostname together into a string. */
192 	if (!(s = malloc(len)))
193 		fatalx("can't allocate space for hostname.");
194 	t = s + len;
195 	*--t = '\0';
196 	while (c) {
197 		pair cdr = c->cdr;
198 		int l = strlen((char *)c->car);
199 
200 		t -= l;
201 		memcpy(t, (char *)c->car, l);
202 		/* Free up temp space. */
203 		free(c->car);
204 		free(c);
205 		c = cdr;
206 		if (t != s)
207 			*--t = '.';
208 	}
209 	return (s);
210 }
211 
212 /*
213  * hardware-parameter :== HARDWARE ETHERNET csns SEMI
214  * csns :== NUMBER | csns COLON NUMBER
215  */
216 void
parse_hardware_param(FILE * cfile,struct hardware * hardware)217 parse_hardware_param(FILE *cfile, struct hardware *hardware)
218 {
219 	char *val;
220 	int token, hlen = 0;
221 	unsigned char *e, *t = NULL;
222 
223 	token = next_token(&val, cfile);
224 	switch (token) {
225 	case TOK_ETHERNET:
226 		hardware->htype = HTYPE_ETHER;
227 		break;
228 	case TOK_IPSEC_TUNNEL:
229 		hardware->htype = HTYPE_IPSEC_TUNNEL;
230 		break;
231 	default:
232 		parse_warn("expecting a network hardware type");
233 		skip_to_semi(cfile);
234 		return;
235 	}
236 
237 
238 	/* Try looking up in /etc/ethers first. */
239 	if (hardware->htype == HTYPE_ETHER) {
240 		token = peek_token(&val, cfile);
241 		hlen = sizeof(struct ether_addr);
242 		if ((e = malloc(hlen)) == NULL)
243 			fatalx("can't allocate space for ethernet address.");
244 		if (ether_hostton(val, (struct ether_addr *)e) == 0) {
245 			(void)next_token(&val, cfile); /* consume token */
246 			t = e;
247 		} else
248 			free(e);
249 	}
250 
251 	/*
252 	 * Parse the hardware address information.   Technically, it
253 	 * would make a lot of sense to restrict the length of the data
254 	 * we'll accept here to the length of a particular hardware
255 	 * address type.   Unfortunately, there are some broken clients
256 	 * out there that put bogus data in the chaddr buffer, and we
257 	 * accept that data in the lease file rather than simply failing
258 	 * on such clients.   Yuck.
259 	 */
260 	if (!t)
261 		t = parse_numeric_aggregate(cfile, NULL, &hlen, ':', 16, 8);
262 
263 	if (!t)
264 		return;
265 	if (hlen > sizeof(hardware->haddr)) {
266 		free(t);
267 		parse_warn("hardware address too long");
268 	} else {
269 		hardware->hlen = hlen;
270 		memcpy((unsigned char *)&hardware->haddr[0], t,
271 		    hardware->hlen);
272 		if (hlen < sizeof(hardware->haddr))
273 			memset(&hardware->haddr[hlen], 0,
274 			    sizeof(hardware->haddr) - hlen);
275 		free(t);
276 	}
277 
278 	token = next_token(&val, cfile);
279 	if (token != ';') {
280 		parse_warn("expecting semicolon.");
281 		skip_to_semi(cfile);
282 	}
283 }
284 
285 /*
286  * lease-time :== NUMBER SEMI
287  */
288 void
parse_lease_time(FILE * cfile,time_t * timep)289 parse_lease_time(FILE *cfile, time_t *timep)
290 {
291 	const char *errstr;
292 	char *val;
293 	uint32_t value;
294 
295 	next_token(&val, cfile);
296 
297 	value = strtonum(val, 0, UINT32_MAX, &errstr);
298 	if (errstr) {
299 		parse_warn("lease time is %s: %s", errstr, val);
300 		skip_to_semi(cfile);
301 		return;
302 	}
303 
304 	*timep = value;
305 
306 	parse_semi(cfile);
307 }
308 
309 /*
310  * No BNF for numeric aggregates - that's defined by the caller.  What
311  * this function does is to parse a sequence of numbers separated by the
312  * token specified in separator.  If max is zero, any number of numbers
313  * will be parsed; otherwise, exactly max numbers are expected.  Base
314  * and size tell us how to internalize the numbers once they've been
315  * tokenized.
316  */
317 unsigned char *
parse_numeric_aggregate(FILE * cfile,unsigned char * buf,int * max,int separator,int base,int size)318 parse_numeric_aggregate(FILE *cfile, unsigned char *buf, int *max,
319     int separator, int base, int size)
320 {
321 	char *val, *t;
322 	int token, count = 0;
323 	unsigned char *bufp = buf, *s = NULL;
324 	pair c = NULL;
325 
326 	if (!bufp && *max) {
327 		bufp = malloc(*max * size / 8);
328 		if (!bufp)
329 			fatalx("can't allocate space for numeric aggregate");
330 	} else
331 		s = bufp;
332 
333 	do {
334 		if (count) {
335 			token = peek_token(&val, cfile);
336 			if (token != separator) {
337 				if (!*max)
338 					break;
339 				if (token != '{' && token != '}')
340 					token = next_token(&val, cfile);
341 				parse_warn("too few numbers.");
342 				if (token != ';')
343 					skip_to_semi(cfile);
344 				return (NULL);
345 			}
346 			token = next_token(&val, cfile);
347 		}
348 		token = next_token(&val, cfile);
349 
350 		if (token == EOF) {
351 			parse_warn("unexpected end of file");
352 			break;
353 		}
354 		if (token != TOK_NUMBER && token != TOK_NUMBER_OR_NAME) {
355 			parse_warn("expecting numeric value.");
356 			skip_to_semi(cfile);
357 			return (NULL);
358 		}
359 		/*
360 		 * If we can, convert the number now; otherwise, build a
361 		 * linked list of all the numbers.
362 		 */
363 		if (s) {
364 			convert_num(s, val, base, size);
365 			s += size / 8;
366 		} else {
367 			t = strdup(val);
368 			if (t == NULL)
369 				fatalx("no temp space for number.");
370 			c = cons(t, c);
371 		}
372 	} while (++count != *max);
373 
374 	/* If we had to cons up a list, convert it now. */
375 	if (c) {
376 		bufp = malloc(count * size / 8);
377 		if (!bufp)
378 			fatalx("can't allocate space for numeric aggregate.");
379 		s = bufp + count - size / 8;
380 		*max = count;
381 	}
382 	while (c) {
383 		pair		cdr = c->cdr;
384 		convert_num(s, (char *)c->car, base, size);
385 		s -= size / 8;
386 		/* Free up temp space. */
387 		free(c->car);
388 		free(c);
389 		c = cdr;
390 	}
391 	return (bufp);
392 }
393 
394 void
convert_num(unsigned char * buf,char * str,int base,int size)395 convert_num(unsigned char *buf, char *str, int base, int size)
396 {
397 	int negative = 0, tval, max;
398 	u_int32_t val = 0;
399 	char *ptr = str;
400 
401 	if (*ptr == '-') {
402 		negative = 1;
403 		ptr++;
404 	}
405 
406 	/* If base wasn't specified, figure it out from the data. */
407 	if (!base) {
408 		if (ptr[0] == '0') {
409 			if (ptr[1] == 'x') {
410 				base = 16;
411 				ptr += 2;
412 			} else if (isascii((unsigned char)ptr[1]) &&
413 			    isdigit((unsigned char)ptr[1])) {
414 				base = 8;
415 				ptr += 1;
416 			} else
417 				base = 10;
418 		} else
419 			base = 10;
420 	}
421 
422 	do {
423 		tval = *ptr++;
424 		/* XXX assumes ASCII... */
425 		if (tval >= 'a')
426 			tval = tval - 'a' + 10;
427 		else if (tval >= 'A')
428 			tval = tval - 'A' + 10;
429 		else if (tval >= '0')
430 			tval -= '0';
431 		else {
432 			log_warnx("Bogus number: %s.", str);
433 			break;
434 		}
435 		if (tval >= base) {
436 			log_warnx("Bogus number: %s: digit %d not in base %d",
437 			    str, tval, base);
438 			break;
439 		}
440 		val = val * base + tval;
441 	} while (*ptr);
442 
443 	if (negative)
444 		max = (1 << (size - 1));
445 	else
446 		max = (1 << (size - 1)) + ((1 << (size - 1)) - 1);
447 	if (val > max) {
448 		switch (base) {
449 		case 8:
450 			log_warnx("value %s%o exceeds max (%d) for precision.",
451 			    negative ? "-" : "", val, max);
452 			break;
453 		case 16:
454 			log_warnx("value %s%x exceeds max (%d) for precision.",
455 			    negative ? "-" : "", val, max);
456 			break;
457 		default:
458 			log_warnx("value %s%u exceeds max (%d) for precision.",
459 			    negative ? "-" : "", val, max);
460 			break;
461 		}
462 	}
463 
464 	if (negative) {
465 		switch (size) {
466 		case 8:
467 			*buf = -(unsigned long)val;
468 			break;
469 		case 16:
470 			putShort(buf, -(unsigned long)val);
471 			break;
472 		case 32:
473 			putLong(buf, -(unsigned long)val);
474 			break;
475 		default:
476 			log_warnx("Unexpected integer size: %d", size);
477 			break;
478 		}
479 	} else {
480 		switch (size) {
481 		case 8:
482 			*buf = (u_int8_t)val;
483 			break;
484 		case 16:
485 			putUShort(buf, (u_int16_t)val);
486 			break;
487 		case 32:
488 			putULong(buf, val);
489 			break;
490 		default:
491 			log_warnx("Unexpected integer size: %d", size);
492 			break;
493 		}
494 	}
495 }
496 
497 /*
498  * date :== NUMBER NUMBER SLASH NUMBER SLASH NUMBER
499  *		NUMBER COLON NUMBER COLON NUMBER UTC SEMI
500  *
501  * Dates are always in UTC; first number is day of week; next is
502  * year/month/day; next is hours:minutes:seconds on a 24-hour
503  * clock.
504  */
505 time_t
parse_date(FILE * cfile)506 parse_date(FILE *cfile)
507 {
508 	struct tm tm;
509 	char timestr[26]; /* "w yyyy/mm/dd hh:mm:ss UTC" */
510 	char *val, *p;
511 	size_t n;
512 	time_t guess;
513 	int token;
514 
515 	memset(timestr, 0, sizeof(timestr));
516 
517 	do {
518 		token = peek_token(NULL, cfile);
519 		switch (token) {
520 		case TOK_NAME:
521 		case TOK_NUMBER:
522 		case TOK_NUMBER_OR_NAME:
523 		case '/':
524 		case ':':
525 			token = next_token(&val, cfile);
526 			n = strlcat(timestr, val, sizeof(timestr));
527 			if (n >= sizeof(timestr)) {
528 				/* XXX Will break after year 9999! */
529 				parse_warn("time string too long");
530 				skip_to_semi(cfile);
531 				return (0);
532 			}
533 			break;
534 		case';':
535 			break;
536 		default:
537 			parse_warn("invalid time string");
538 			skip_to_semi(cfile);
539 			return (0);
540 		}
541 	} while (token != ';');
542 
543 	parse_semi(cfile);
544 
545 	memset(&tm, 0, sizeof(tm));	/* 'cuz strptime ignores tm_isdt. */
546 	p = strptime(timestr, DB_TIMEFMT, &tm);
547 	if (p == NULL || *p != '\0') {
548 		p = strptime(timestr, OLD_DB_TIMEFMT, &tm);
549 		if (p == NULL || *p != '\0') {
550 			parse_warn("unparseable time string");
551 			return (0);
552 		}
553 	}
554 
555 	guess = timegm(&tm);
556 	if (guess == -1) {
557 		parse_warn("time could not be represented");
558 		return (0);
559 	}
560 
561 	return (guess);
562 }
563 
564 int warnings_occurred;
565 
566 int
parse_warn(char * fmt,...)567 parse_warn(char *fmt, ...)
568 {
569 	static char fbuf[1024];
570 	static char mbuf[1024];
571 	static char spaces[81];
572 	va_list list;
573 	int i;
574 
575 	snprintf(fbuf, sizeof(fbuf), "%s line %d: %s", tlname, lexline, mbuf);
576 	va_start(list, fmt);
577 	vsnprintf(mbuf, sizeof(mbuf), fbuf, list);
578 	va_end(list);
579 
580 	log_warnx("%s", mbuf);
581 	log_warnx("%s", token_line);
582 	if (lexchar < sizeof(spaces)) {
583 		memset(spaces, 0, sizeof(spaces));
584 		for (i = 0; i < lexchar - 1; i++) {
585 			if (token_line[i] == '\t')
586 				spaces[i] = '\t';
587 			else
588 				spaces[i] = ' ';
589 		}
590 	}
591 	log_warnx("%s^", spaces);
592 
593 	warnings_occurred = 1;
594 
595 	return (0);
596 }
597