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