dhclient - Tweak parsing.
[dragonfly.git] / sbin / dhclient / clparse.c
1 /*      $OpenBSD: src/sbin/dhclient/clparse.c,v 1.38 2011/12/10 17:15:27 krw Exp $      */
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  */
52 int
53 read_client_conf(void)
54 {
55         FILE *cfile;
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 {
87                         token = peek_token(NULL, cfile);
88                         if (token == EOF)
89                                 break;
90                         parse_client_statement(cfile);
91                 } while (1);
92                 token = next_token(NULL, cfile); /* Clear the peek buffer */
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  */
104 void
105 read_client_leases(void)
106 {
107         FILE    *cfile;
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 {
117                 token = next_token(NULL, cfile);
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  */
154 void
155 parse_client_statement(FILE *cfile)
156 {
157         int token, code;
158
159         switch (next_token(NULL, cfile)) {
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:
184                 skip_to_semi(cfile);
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:
229                 skip_to_semi(cfile);
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         }
239         token = next_token(NULL, cfile);
240         if (token != ';') {
241                 parse_warn("semicolon expected.");
242                 skip_to_semi(cfile);
243         }
244 }
245
246 int
247 parse_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  */
295 int
296 parse_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  */
339 void
340 parse_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  */
386 void
387 parse_client_lease_statement(FILE *cfile, int is_static)
388 {
389         struct client_lease     *lease, *lp, *pl;
390         int                      token;
391
392         token = next_token(NULL, cfile);
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 {
406                 token = peek_token(NULL, cfile);
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);
415         token = next_token(NULL, cfile);
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
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) {
433                 if (addr_eq(lp->address, lease->address)) {
434                         if (pl)
435                                 pl->next = lp->next;
436                         else
437                                 client->leases = lp->next;
438                         free_client_lease(lp);
439                         break;
440                 } else
441                         pl = lp;
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);
471                 else if (addr_eq(client->active->address, lease->address))
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  */
495 void
496 parse_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:
524                 skip_to_semi(cfile);
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
556 int
557 parse_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;
624 alloc:
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) {
638 need_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.");
670 bad_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
712 void
713 parse_reject_statement(FILE *cfile)
714 {
715         struct iaddrlist *list;
716         struct iaddr addr;
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
734                 token = next_token(NULL, cfile);
735         } while (token == ',');
736
737         if (token != ';') {
738                 parse_warn("expecting semicolon.");
739                 skip_to_semi(cfile);
740         }
741 }