dhclient - Tweak parsing.
[dragonfly.git] / sbin / dhclient / clparse.c
CommitLineData
54dfa84a 1/* $OpenBSD: src/sbin/dhclient/clparse.c,v 1.38 2011/12/10 17:15:27 krw Exp $ */
846204b6
HT
2
3/* Parser for dhclient config and lease files... */
4
5/*
6 * Copyright (c) 1997 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 "dhcpd.h"
44#include "dhctoken.h"
45
46/*
47 * client-conf-file :== client-declarations EOF
48 * client-declarations :== <nil>
49 * | client-declaration
50 * | client-declarations client-declaration
51 */
52int
53read_client_conf(void)
54{
55 FILE *cfile;
846204b6
HT
56 int token;
57
58 new_parse(path_dhclient_conf);
59
60 /* Set some defaults... */
61 config->link_timeout = 10;
62 config->timeout = 60;
63 config->select_interval = 0;
64 config->reboot_timeout = 10;
65 config->retry_interval = 300;
66 config->backoff_cutoff = 15;
67 config->initial_interval = 3;
68 config->bootp_policy = ACCEPT;
69 config->script_name = _PATH_DHCLIENT_SCRIPT;
70 config->requested_options
71 [config->requested_option_count++] = DHO_SUBNET_MASK;
72 config->requested_options
73 [config->requested_option_count++] = DHO_BROADCAST_ADDRESS;
74 config->requested_options
75 [config->requested_option_count++] = DHO_TIME_OFFSET;
76 config->requested_options
77 [config->requested_option_count++] = DHO_ROUTERS;
78 config->requested_options
79 [config->requested_option_count++] = DHO_DOMAIN_NAME;
80 config->requested_options
81 [config->requested_option_count++] = DHO_DOMAIN_NAME_SERVERS;
82 config->requested_options
83 [config->requested_option_count++] = DHO_HOST_NAME;
84
85 if ((cfile = fopen(path_dhclient_conf, "r")) != NULL) {
86 do {
54dfa84a 87 token = peek_token(NULL, cfile);
846204b6
HT
88 if (token == EOF)
89 break;
90 parse_client_statement(cfile);
91 } while (1);
54dfa84a 92 token = next_token(NULL, cfile); /* Clear the peek buffer */
846204b6
HT
93 fclose(cfile);
94 }
95
96 return (!warnings_occurred);
97}
98
99/*
100 * lease-file :== client-lease-statements EOF
101 * client-lease-statements :== <nil>
102 * | client-lease-statements LEASE client-lease-statement
103 */
104void
105read_client_leases(void)
106{
107 FILE *cfile;
846204b6
HT
108 int token;
109
110 new_parse(path_dhclient_db);
111
112 /* Open the lease file. If we can't open it, just return -
113 we can safely trust the server to remember our state. */
114 if ((cfile = fopen(path_dhclient_db, "r")) == NULL)
115 return;
116 do {
54dfa84a 117 token = next_token(NULL, cfile);
846204b6
HT
118 if (token == EOF)
119 break;
120 if (token != TOK_LEASE) {
121 warning("Corrupt lease file - possible data loss!");
122 skip_to_semi(cfile);
123 break;
124 } else
125 parse_client_lease_statement(cfile, 0);
126
127 } while (1);
128 fclose(cfile);
129}
130
131/*
132 * client-declaration :==
133 * TOK_SEND option-decl |
134 * TOK_DEFAULT option-decl |
135 * TOK_SUPERSEDE option-decl |
136 * TOK_APPEND option-decl |
137 * TOK_PREPEND option-decl |
138 * TOK_MEDIA string-list |
139 * hardware-declaration |
140 * TOK_REQUEST option-list |
141 * TOK_REQUIRE option-list |
142 * TOK_TIMEOUT number |
143 * TOK_RETRY number |
144 * TOK_SELECT_TIMEOUT number |
145 * TOK_REBOOT number |
146 * TOK_BACKOFF_CUTOFF number |
147 * TOK_INITIAL_INTERVAL number |
148 * TOK_SCRIPT string |
149 * interface-declaration |
150 * TOK_LEASE client-lease-statement |
151 * TOK_ALIAS client-lease-statement |
152 * TOK_REJECT reject-statement
153 */
154void
155parse_client_statement(FILE *cfile)
156{
846204b6
HT
157 int token, code;
158
54dfa84a 159 switch (next_token(NULL, cfile)) {
846204b6
HT
160 case TOK_SEND:
161 parse_option_decl(cfile, &config->send_options[0]);
162 return;
163 case TOK_DEFAULT:
164 code = parse_option_decl(cfile, &config->defaults[0]);
165 if (code != -1)
166 config->default_actions[code] = ACTION_DEFAULT;
167 return;
168 case TOK_SUPERSEDE:
169 code = parse_option_decl(cfile, &config->defaults[0]);
170 if (code != -1)
171 config->default_actions[code] = ACTION_SUPERSEDE;
172 return;
173 case TOK_APPEND:
174 code = parse_option_decl(cfile, &config->defaults[0]);
175 if (code != -1)
176 config->default_actions[code] = ACTION_APPEND;
177 return;
178 case TOK_PREPEND:
179 code = parse_option_decl(cfile, &config->defaults[0]);
180 if (code != -1)
181 config->default_actions[code] = ACTION_PREPEND;
182 return;
183 case TOK_MEDIA:
7cbe3601 184 skip_to_semi(cfile);
846204b6
HT
185 return;
186 case TOK_HARDWARE:
187 parse_hardware_param(cfile, &ifi->hw_address);
188 return;
189 case TOK_REQUEST:
190 config->requested_option_count =
191 parse_option_list(cfile, config->requested_options);
192 return;
193 case TOK_REQUIRE:
194 memset(config->required_options, 0,
195 sizeof(config->required_options));
196 parse_option_list(cfile, config->required_options);
197 return;
198 case TOK_LINK_TIMEOUT:
199 parse_lease_time(cfile, &config->link_timeout);
200 return;
201 case TOK_TIMEOUT:
202 parse_lease_time(cfile, &config->timeout);
203 return;
204 case TOK_RETRY:
205 parse_lease_time(cfile, &config->retry_interval);
206 return;
207 case TOK_SELECT_TIMEOUT:
208 parse_lease_time(cfile, &config->select_interval);
209 return;
210 case TOK_REBOOT:
211 parse_lease_time(cfile, &config->reboot_timeout);
212 return;
213 case TOK_BACKOFF_CUTOFF:
214 parse_lease_time(cfile, &config->backoff_cutoff);
215 return;
216 case TOK_INITIAL_INTERVAL:
217 parse_lease_time(cfile, &config->initial_interval);
218 return;
219 case TOK_SCRIPT:
220 config->script_name = parse_string(cfile);
221 return;
222 case TOK_INTERFACE:
223 parse_interface_declaration(cfile);
224 return;
225 case TOK_LEASE:
226 parse_client_lease_statement(cfile, 1);
227 return;
228 case TOK_ALIAS:
7cbe3601 229 skip_to_semi(cfile);
846204b6
HT
230 return;
231 case TOK_REJECT:
232 parse_reject_statement(cfile);
233 return;
234 default:
235 parse_warn("expecting a statement.");
236 skip_to_semi(cfile);
237 break;
238 }
54dfa84a 239 token = next_token(NULL, cfile);
846204b6
HT
240 if (token != ';') {
241 parse_warn("semicolon expected.");
242 skip_to_semi(cfile);
243 }
244}
245
246int
247parse_X(FILE *cfile, u_int8_t *buf, int max)
248{
249 int token;
250 char *val;
251 int len;
252
253 token = peek_token(&val, cfile);
254 if (token == TOK_NUMBER_OR_NAME || token == TOK_NUMBER) {
255 len = 0;
256 do {
257 token = next_token(&val, cfile);
258 if (token != TOK_NUMBER && token != TOK_NUMBER_OR_NAME) {
259 parse_warn("expecting hexadecimal constant.");
260 skip_to_semi(cfile);
261 return (0);
262 }
263 convert_num(&buf[len], val, 16, 8);
264 if (len++ > max) {
265 parse_warn("hexadecimal constant too long.");
266 skip_to_semi(cfile);
267 return (0);
268 }
269 token = peek_token(&val, cfile);
270 if (token == ':')
271 token = next_token(&val, cfile);
272 } while (token == ':');
273 val = (char *)buf;
274 } else if (token == TOK_STRING) {
275 token = next_token(&val, cfile);
276 len = strlen(val);
277 if (len + 1 > max) {
278 parse_warn("string constant too long.");
279 skip_to_semi(cfile);
280 return (0);
281 }
282 memcpy(buf, val, len + 1);
283 } else {
284 parse_warn("expecting string or hexadecimal data");
285 skip_to_semi(cfile);
286 return (0);
287 }
288 return (len);
289}
290
291/*
292 * option-list :== option_name |
293 * option_list COMMA option_name
294 */
295int
296parse_option_list(FILE *cfile, u_int8_t *list)
297{
298 int ix, i;
299 int token;
300 char *val;
301
302 ix = 0;
303 do {
304 token = next_token(&val, cfile);
305 if (!is_identifier(token)) {
306 parse_warn("expected option name.");
307 skip_to_semi(cfile);
308 return (0);
309 }
310 for (i = 0; i < 256; i++)
311 if (!strcasecmp(dhcp_options[i].name, val))
312 break;
313
314 if (i == 256) {
315 parse_warn("%s: unexpected option name.", val);
316 skip_to_semi(cfile);
317 return (0);
318 }
319 list[ix++] = i;
320 if (ix == 256) {
321 parse_warn("%s: too many options.", val);
322 skip_to_semi(cfile);
323 return (0);
324 }
325 token = next_token(&val, cfile);
326 } while (token == ',');
327 if (token != ';') {
328 parse_warn("expecting semicolon.");
329 skip_to_semi(cfile);
330 return (0);
331 }
332 return (ix);
333}
334
335/*
336 * interface-declaration :==
337 * INTERFACE string LBRACE client-declarations RBRACE
338 */
339void
340parse_interface_declaration(FILE *cfile)
341{
342 char *val;
343 int token;
344
345 token = next_token(&val, cfile);
346 if (token != TOK_STRING) {
347 parse_warn("expecting interface name (in quotes).");
348 skip_to_semi(cfile);
349 return;
350 }
351
352 if (strcmp(ifi->name, val) != 0) {
353 skip_to_semi(cfile);
354 return;
355 }
356
357 token = next_token(&val, cfile);
358 if (token != '{') {
359 parse_warn("expecting left brace.");
360 skip_to_semi(cfile);
361 return;
362 }
363
364 do {
365 token = peek_token(&val, cfile);
366 if (token == EOF) {
367 parse_warn("unterminated interface declaration.");
368 return;
369 }
370 if (token == '}')
371 break;
372 parse_client_statement(cfile);
373 } while (1);
374 token = next_token(&val, cfile);
375}
376
377/*
378 * client-lease-statement :==
379 * RBRACE client-lease-declarations LBRACE
380 *
381 * client-lease-declarations :==
382 * <nil> |
383 * client-lease-declaration |
384 * client-lease-declarations client-lease-declaration
385 */
386void
387parse_client_lease_statement(FILE *cfile, int is_static)
388{
389 struct client_lease *lease, *lp, *pl;
390 int token;
846204b6 391
54dfa84a 392 token = next_token(NULL, cfile);
846204b6
HT
393 if (token != '{') {
394 parse_warn("expecting left brace.");
395 skip_to_semi(cfile);
396 return;
397 }
398
399 lease = malloc(sizeof(struct client_lease));
400 if (!lease)
401 error("no memory for lease.");
402 memset(lease, 0, sizeof(*lease));
403 lease->is_static = is_static;
404
405 do {
54dfa84a 406 token = peek_token(NULL, cfile);
846204b6
HT
407 if (token == EOF) {
408 parse_warn("unterminated lease declaration.");
409 return;
410 }
411 if (token == '}')
412 break;
413 parse_client_lease_declaration(cfile, lease);
414 } while (1);
54dfa84a 415 token = next_token(NULL, cfile);
846204b6
HT
416
417 /* If the lease declaration didn't include an interface
418 * declaration that we recognized, it's of no use to us.
419 */
420 if (!ifi) {
421 free_client_lease(lease);
422 return;
423 }
424
846204b6
HT
425 /*
426 * The new lease may supersede a lease that's not the active
427 * lease but is still on the lease list, so scan the lease list
428 * looking for a lease with the same address, and if we find it,
429 * toss it.
430 */
431 pl = NULL;
432 for (lp = client->leases; lp; lp = lp->next) {
c3b31e60 433 if (addr_eq(lp->address, lease->address)) {
846204b6
HT
434 if (pl)
435 pl->next = lp->next;
436 else
437 client->leases = lp->next;
438 free_client_lease(lp);
439 break;
44967127
AHJ
440 } else
441 pl = lp;
846204b6
HT
442 }
443
444 /*
445 * If this is a preloaded lease, just put it on the list of
446 * recorded leases - don't make it the active lease.
447 */
448 if (is_static) {
449 lease->next = client->leases;
450 client->leases = lease;
451 return;
452 }
453
454 /*
455 * The last lease in the lease file on a particular interface is
456 * the active lease for that interface. Of course, we don't
457 * know what the last lease in the file is until we've parsed
458 * the whole file, so at this point, we assume that the lease we
459 * just parsed is the active lease for its interface. If
460 * there's already an active lease for the interface, and this
461 * lease is for the same ip address, then we just toss the old
462 * active lease and replace it with this one. If this lease is
463 * for a different address, then if the old active lease has
464 * expired, we dump it; if not, we put it on the list of leases
465 * for this interface which are still valid but no longer
466 * active.
467 */
468 if (client->active) {
469 if (client->active->expiry < cur_time)
470 free_client_lease(client->active);
c3b31e60 471 else if (addr_eq(client->active->address, lease->address))
846204b6
HT
472 free_client_lease(client->active);
473 else {
474 client->active->next = client->leases;
475 client->leases = client->active;
476 }
477 }
478 client->active = lease;
479
480 /* Phew. */
481}
482
483/*
484 * client-lease-declaration :==
485 * BOOTP |
486 * INTERFACE string |
487 * FIXED_ADDR ip_address |
488 * FILENAME string |
489 * SERVER_NAME string |
490 * OPTION option-decl |
491 * RENEW time-decl |
492 * REBIND time-decl |
493 * EXPIRE time-decl
494 */
495void
496parse_client_lease_declaration(FILE *cfile, struct client_lease *lease)
497{
498 char *val;
499 int token;
500
501 switch (next_token(&val, cfile)) {
502 case TOK_BOOTP:
503 lease->is_bootp = 1;
504 break;
505 case TOK_INTERFACE:
506 token = next_token(&val, cfile);
507 if (token != TOK_STRING) {
508 parse_warn("expecting interface name (in quotes).");
509 skip_to_semi(cfile);
510 break;
511 }
512 if (strcmp(ifi->name, val) != 0) {
513 parse_warn("wrong interface name. Expecting '%s'.",
514 ifi->name);
515 skip_to_semi(cfile);
516 break;
517 }
518 break;
519 case TOK_FIXED_ADDR:
520 if (!parse_ip_addr(cfile, &lease->address))
521 return;
522 break;
523 case TOK_MEDIUM:
7cbe3601 524 skip_to_semi(cfile);
846204b6
HT
525 return;
526 case TOK_FILENAME:
527 lease->filename = parse_string(cfile);
528 return;
529 case TOK_SERVER_NAME:
530 lease->server_name = parse_string(cfile);
531 return;
532 case TOK_RENEW:
533 lease->renewal = parse_date(cfile);
534 return;
535 case TOK_REBIND:
536 lease->rebind = parse_date(cfile);
537 return;
538 case TOK_EXPIRE:
539 lease->expiry = parse_date(cfile);
540 return;
541 case TOK_OPTION:
542 parse_option_decl(cfile, lease->options);
543 return;
544 default:
545 parse_warn("expecting lease declaration.");
546 skip_to_semi(cfile);
547 break;
548 }
549 token = next_token(&val, cfile);
550 if (token != ';') {
551 parse_warn("expecting semicolon.");
552 skip_to_semi(cfile);
553 }
554}
555
556int
557parse_option_decl(FILE *cfile, struct option_data *options)
558{
559 char *val;
560 int token;
561 u_int8_t buf[4];
562 u_int8_t hunkbuf[1024];
563 int hunkix = 0;
564 char *fmt;
565 struct iaddr ip_addr;
566 u_int8_t *dp;
567 int len, code;
568 int nul_term = 0;
569
570 token = next_token(&val, cfile);
571 if (!is_identifier(token)) {
572 parse_warn("expecting identifier after option keyword.");
573 if (token != ';')
574 skip_to_semi(cfile);
575 return (-1);
576 }
577
578 /* Look up the actual option info. */
579 fmt = NULL;
580 for (code = 0; code < 256; code++)
581 if (strcmp(dhcp_options[code].name, val) == 0)
582 break;
583
584 if (code > 255) {
585 parse_warn("no option named %s", val);
586 skip_to_semi(cfile);
587 return (-1);
588 }
589
590 /* Parse the option data... */
591 do {
592 for (fmt = dhcp_options[code].format; *fmt; fmt++) {
593 if (*fmt == 'A')
594 break;
595 switch (*fmt) {
596 case 'X':
597 len = parse_X(cfile, &hunkbuf[hunkix],
598 sizeof(hunkbuf) - hunkix);
599 hunkix += len;
600 break;
601 case 't': /* Text string... */
602 token = next_token(&val, cfile);
603 if (token != TOK_STRING) {
604 parse_warn("expecting string.");
605 skip_to_semi(cfile);
606 return (-1);
607 }
608 len = strlen(val);
609 if (hunkix + len + 1 > sizeof(hunkbuf)) {
610 parse_warn("option data buffer %s",
611 "overflow");
612 skip_to_semi(cfile);
613 return (-1);
614 }
615 memcpy(&hunkbuf[hunkix], val, len + 1);
616 nul_term = 1;
617 hunkix += len;
618 break;
619 case 'I': /* IP address. */
620 if (!parse_ip_addr(cfile, &ip_addr))
621 return (-1);
622 len = ip_addr.len;
623 dp = ip_addr.iabuf;
624alloc:
625 if (hunkix + len > sizeof(hunkbuf)) {
626 parse_warn("option data buffer "
627 "overflow");
628 skip_to_semi(cfile);
629 return (-1);
630 }
631 memcpy(&hunkbuf[hunkix], dp, len);
632 hunkix += len;
633 break;
634 case 'L': /* Unsigned 32-bit integer... */
635 case 'l': /* Signed 32-bit integer... */
636 token = next_token(&val, cfile);
637 if (token != TOK_NUMBER) {
638need_number:
639 parse_warn("expecting number.");
640 if (token != ';')
641 skip_to_semi(cfile);
642 return (-1);
643 }
644 convert_num(buf, val, 0, 32);
645 len = 4;
646 dp = buf;
647 goto alloc;
648 case 's': /* Signed 16-bit integer. */
649 case 'S': /* Unsigned 16-bit integer. */
650 token = next_token(&val, cfile);
651 if (token != TOK_NUMBER)
652 goto need_number;
653 convert_num(buf, val, 0, 16);
654 len = 2;
655 dp = buf;
656 goto alloc;
657 case 'b': /* Signed 8-bit integer. */
658 case 'B': /* Unsigned 8-bit integer. */
659 token = next_token(&val, cfile);
660 if (token != TOK_NUMBER)
661 goto need_number;
662 convert_num(buf, val, 0, 8);
663 len = 1;
664 dp = buf;
665 goto alloc;
666 case 'f': /* Boolean flag. */
667 token = next_token(&val, cfile);
668 if (!is_identifier(token)) {
669 parse_warn("expecting identifier.");
670bad_flag:
671 if (token != ';')
672 skip_to_semi(cfile);
673 return (-1);
674 }
675 if (!strcasecmp(val, "true") ||
676 !strcasecmp(val, "on"))
677 buf[0] = 1;
678 else if (!strcasecmp(val, "false") ||
679 !strcasecmp(val, "off"))
680 buf[0] = 0;
681 else {
682 parse_warn("expecting boolean.");
683 goto bad_flag;
684 }
685 len = 1;
686 dp = buf;
687 goto alloc;
688 default:
689 warning("Bad format %c in parse_option_param.",
690 *fmt);
691 skip_to_semi(cfile);
692 return (-1);
693 }
694 }
695 token = next_token(&val, cfile);
696 } while (*fmt == 'A' && token == ',');
697
698 if (token != ';') {
699 parse_warn("semicolon expected.");
700 skip_to_semi(cfile);
701 return (-1);
702 }
703
704 options[code].data = malloc(hunkix + nul_term);
705 if (!options[code].data)
706 error("out of memory allocating option data.");
707 memcpy(options[code].data, hunkbuf, hunkix + nul_term);
708 options[code].len = hunkix;
709 return (code);
710}
711
712void
846204b6
HT
713parse_reject_statement(FILE *cfile)
714{
715 struct iaddrlist *list;
716 struct iaddr addr;
846204b6
HT
717 int token;
718
719 do {
720 if (!parse_ip_addr(cfile, &addr)) {
721 parse_warn("expecting IP address.");
722 skip_to_semi(cfile);
723 return;
724 }
725
726 list = malloc(sizeof(struct iaddrlist));
727 if (!list)
728 error("no memory for reject list!");
729
730 list->addr = addr;
731 list->next = config->reject_list;
732 config->reject_list = list;
733
54dfa84a 734 token = next_token(NULL, cfile);
846204b6
HT
735 } while (token == ',');
736
737 if (token != ';') {
738 parse_warn("expecting semicolon.");
739 skip_to_semi(cfile);
740 }
741}