]> git.ipfire.org Git - thirdparty/dhcp.git/commitdiff
Parser for DHCP client config files
authorTed Lemon <source@isc.org>
Tue, 18 Feb 1997 14:27:53 +0000 (14:27 +0000)
committerTed Lemon <source@isc.org>
Tue, 18 Feb 1997 14:27:53 +0000 (14:27 +0000)
client/clparse.c [new file with mode: 0644]
clparse.c [new file with mode: 0644]

diff --git a/client/clparse.c b/client/clparse.c
new file mode 100644 (file)
index 0000000..7d4b1e4
--- /dev/null
@@ -0,0 +1,781 @@
+/* clparse.c
+
+   Parser for dhclient config and lease files... */
+
+/*
+ * Copyright (c) 1995, 1996, 1997 The Internet Software Consortium.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of The Internet Software Consortium nor the names
+ *    of its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
+ * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises.  To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''.  To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#ifndef lint
+static char copyright[] =
+"$Id: clparse.c,v 1.1 1997/02/18 14:27:53 mellon Exp $ Copyright (c) 1995, 1996 The Internet Software Consortium.  All rights reserved.\n";
+#endif /* not lint */
+
+#include "dhcpd.h"
+#include "dhctoken.h"
+
+static TIME parsed_time;
+
+/* client-conf-file :== client-declarations EOF
+   client-declarations :== <nil>
+                        | client-declaration
+                        | client-declarations client-declaration */
+
+int read_client_conf ()
+{
+       FILE *cfile;
+       char *val;
+       int token;
+       int declaration = 0;
+       struct client_config *config;
+       struct client_state *state;
+       struct interface_info *ip;
+
+       new_parse (path_dhclient_conf);
+
+       /* Set up the initial dhcp option universe. */
+       initialize_universes ();
+
+       /* Initialize the top level client configuration. */
+       memset (&top_level_config, 0, sizeof top_level_config);
+
+       if ((cfile = fopen (path_dhclient_conf, "r")) == NULL)
+               error ("Can't open %s: %m", path_dhclient_conf);
+       do {
+               token = peek_token (&val, cfile);
+               if (token == EOF)
+                       break;
+               parse_client_statement (cfile, (struct interface_info *)0,
+                                       &top_level_config);
+       } while (1);
+       token = next_token (&val, cfile); /* Clear the peek buffer */
+
+       /* Set up state and config structures for clients that don't
+          have per-interface configuration declarations. */
+       config = (struct client_config *)0;
+       for (ip = interfaces; ip; ip = ip -> next) {
+               if (!ip -> client) {
+                       ip -> client = (struct client_state *)
+                               malloc (sizeof (struct client_state));
+                       if (!ip -> client)
+                               error ("no memory for client state.");
+                       memset (ip -> client, 0, sizeof *(ip -> client));
+                       if (!config) {
+                               config = (struct client_config *)
+                                       malloc (sizeof (struct client_config));
+                               if (!config)
+                                       error ("no memory for client config.");
+                               memcpy (config, &top_level_config,
+                                       sizeof top_level_config);
+                       }
+                       ip -> client -> config = config;
+               }
+       }
+
+       return !warnings_occurred;
+}
+
+/* lease-file :== client-lease-statements EOF
+   client-lease-statements :== <nil>
+                    | client-lease-statements client-lease-statement */
+
+void read_client_leases ()
+{
+       FILE *cfile;
+       char *val;
+       int token;
+
+       new_parse (path_dhclient_db);
+
+       /* Open the lease file.   If we can't open it, just return -
+          we can safely trust the server to remember our state. */
+       if ((cfile = fopen (path_dhclient_db, "r")) == NULL)
+               return;
+       do {
+               token = next_token (&val, cfile);
+               if (token == EOF)
+                       break;
+               if (token != LEASE) {
+                       warn ("Corrupt lease file - possible data loss!");
+                       skip_to_semi (cfile);
+                       break;
+               } else
+                       parse_client_lease_statement (cfile);
+
+       } while (1);
+}
+
+/* client-declaration :== 
+       HOSTNAME string |
+       hardware-declaration |
+       client-identifier-declaration |
+       REQUEST option-list |
+       REQUIRE option-list |
+       TIMEOUT number |
+       RETRY number |
+       SELECT_TIMEOUT number |
+       SCRIPT string |
+       interface-declaration |
+       client-lease-statement */
+
+void parse_client_statement (cfile, ip, config)
+       FILE *cfile;
+       struct interface_info *ip;
+       struct client_config *config;
+{
+       int token;
+       char *val;
+       char *t, *n;
+       struct hardware hardware;
+
+       switch (next_token (&val, cfile)) {
+             case HOSTNAME:
+               config -> dns_hostname = parse_string (cfile);
+               return;
+
+             case CLIENT_IDENTIFIER:
+               config -> cid_len = parse_X (cfile,
+                                            &config -> client_identifier);
+               break;
+
+             case HARDWARE:
+               if (ip) {
+                       parse_hardware_param (cfile, &ip -> hw_address);
+               } else {
+                       parse_warn ("hardware address parameter %s",
+                                   "not allowed here.");
+                       skip_to_semi (cfile);
+               }
+               return;
+
+             case REQUEST:
+               config -> requested_option_count =
+                       parse_option_list (cfile, config -> requested_options);
+               return;
+
+             case REQUIRE:
+               memset (config -> required_options, 0,
+                       sizeof config -> required_options);
+               parse_option_list (cfile, config -> required_options);
+               return;
+
+             case TIMEOUT:
+               parse_lease_time (cfile, &config -> timeout);
+               return;
+
+             case RETRY:
+               parse_lease_time (cfile, &config -> retry_interval);
+               return;
+
+             case SELECT_TIMEOUT:
+               parse_lease_time (cfile, &config -> select_interval);
+               return;
+
+             case SCRIPT:
+               config -> script_name = parse_string (cfile);
+               return;
+
+             case INTERFACE:
+               if (ip)
+                       parse_warn ("nested interface declaration.");
+               parse_interface_declaration (cfile, config);
+               return;
+
+             case LEASE:
+               parse_client_lease_statement (cfile);
+               return;
+
+             default:
+               parse_warn ("expecting a statement.");
+               skip_to_semi (cfile);
+               break;
+       }
+       token = next_token (&val, cfile);
+       if (token != SEMI) {
+               parse_warn ("semicolon expected.");
+               skip_to_semi (cfile);
+       }
+}
+
+int parse_X (cfile, p)
+       FILE *cfile;
+       u_int8_t **p;
+{
+       int token;
+       char *val;
+       unsigned char buf [1024];
+       int buflen;
+       unsigned char *s;
+
+       token = peek_token (&val, cfile);
+       if (token == NUMBER_OR_NAME || token == NUMBER) {
+               do {
+                       token = next_token (&val, cfile);
+                       if (token != NUMBER && token != NUMBER_OR_NAME) {
+                               parse_warn ("expecting hexadecimal constant.");
+                               skip_to_semi (cfile);
+                               *p = (u_int8_t *)0;
+                               return 0;
+                       }
+                       convert_num (&buf [buflen], val, 16, 8);
+                       if (buflen++ > sizeof buf) {
+                               parse_warn ("hexadecimal constant too long.");
+                               skip_to_semi (cfile);
+                               *p = (u_int8_t *)0;
+                               return 0;
+                       }
+                       token = peek_token (&val, cfile);
+                       if (token == COLON)
+                               token = next_token (&val, cfile);
+               } while (token == COLON);
+               val = buf;
+       } else if (token == STRING) {
+               token = next_token (&val, cfile);
+               buflen = strlen (val) + 1;
+       } else {
+               parse_warn ("expecting string or hexadecimal data");
+               skip_to_semi (cfile);
+               *p = (u_int8_t *)0;
+               return 0;
+       }
+       *p = malloc (buflen);
+       if (!*p)
+               error ("out of memory allocating client id.\n");
+       memcpy (*p, val, buflen);
+       return buflen;
+}
+
+/* option-list :== option_name |
+                  option_list COMMA option_name */
+
+int parse_option_list (cfile, list)
+       FILE *cfile;
+       u_int8_t *list;
+{
+       int ix, i;
+       int token;
+       char *val;
+
+       ix = 0;
+       do {
+               token = next_token (&val, cfile);
+               if (!is_identifier (token)) {
+                       parse_warn ("expected option name.");
+                       skip_to_semi (cfile);
+                       return 0;
+               }
+               for (i = 0; i < 256; i++) {
+                       if (!strcasecmp (dhcp_options [i].name, val))
+                               break;
+               }
+               if (i == 256) {
+                       parse_warn ("%s: expected option name.");
+                       skip_to_semi (cfile);
+                       return 0;
+               }
+               list [ix++] = i;
+               if (ix == 256) {
+                       parse_warn ("%s: too many options.", val);
+                       skip_to_semi (cfile);
+                       return 0;
+               }
+               token = next_token (&val, cfile);
+       } while (token == COMMA);
+       if (token != SEMI) {
+               parse_warn ("expecting semicolon.");
+               skip_to_semi (cfile);
+               return 0;
+       }
+       return ix;
+}
+
+/* interface-declaration :==
+       INTERFACE string LBRACE client-declarations RBRACE */
+
+void parse_interface_declaration (cfile, outer_config)
+       FILE *cfile;
+       struct client_config *outer_config;
+{
+       int token;
+       char *val;
+
+       struct interface_info dummy_interface, *ip;
+       struct client_state dummy_state;
+       struct client_config dummy_config;
+
+       token = next_token (&val, cfile);
+       if (token != STRING) {
+               parse_warn ("expecting interface name (in quotes).");
+               skip_to_semi (cfile);
+               return;
+       }
+
+       /* Find the interface (if any) that matches the name. */
+       for (ip = interfaces; ip; ip = ip -> next) {
+               if (!strcmp (ip -> name, val))
+                       break;
+       }
+
+       /* If we didn't find an interface, put up a dummy interface so
+          that we have some place to parse the bogus data.   Otherwise,
+          allocate a client state and client config structure for the
+          interface. */
+       if (!ip) {
+               parse_warn ("interface %s not found.", val);
+               ip = &dummy_interface;
+               memset (ip, 0, sizeof dummy_interface);
+               ip -> client = &dummy_state;
+               memset (ip -> client, 0, sizeof dummy_state);
+               ip -> client -> config = &dummy_config;
+       } else {
+               ip -> client = ((struct client_state *)
+                               malloc (sizeof (struct client_state)));
+               if (!ip -> client)
+                       error ("no memory for state for %s\n", val);
+               memset (ip -> client, 0, sizeof *(ip -> client));
+               ip -> client -> config =
+                       ((struct client_config *)
+                        malloc (sizeof (struct client_config)));
+               if (!ip -> client -> config)
+                       error ("no memory for config for %s\n", val);
+               memset (ip -> client -> config, 0,
+                       sizeof *(ip -> client -> config));
+       }
+       memcpy (ip -> client -> config, outer_config, sizeof *outer_config);
+
+       token = next_token (&val, cfile);
+       if (token != LBRACE) {
+               parse_warn ("expecting left brace.");
+               skip_to_semi (cfile);
+               return;
+       }
+
+       do {
+               token = peek_token (&val, cfile);
+               if (token == EOF) {
+                       parse_warn ("unterminated interface declaration.");
+                       return;
+               }
+               if (token == RBRACE)
+                       break;
+               parse_client_statement (cfile, ip, ip -> client -> config);
+       } while (1);
+       token = next_token (&val, cfile);
+}
+
+/* client-lease-statement :==
+       LEASE RBRACE client-lease-declarations LBRACE
+
+       client-lease-declarations :==
+               <nil> |
+               client-lease-declaration |
+               client-lease-declarations client-lease-declaration */
+
+
+void parse_client_lease_statement (cfile)
+       FILE *cfile;
+{
+       struct client_lease *lease, *lp, *pl;
+       struct interface_info *ip;
+       int token;
+       char *val;
+
+       token = next_token (&val, cfile);
+       if (token != LBRACE) {
+               parse_warn ("expecting left brace.");
+               skip_to_semi (cfile);
+               return;
+       }
+
+       lease = (struct client_lease *)malloc (sizeof (struct client_lease));
+       if (!lease)
+               error ("no memory for lease.\n");
+       memset (lease, 0, sizeof *lease);
+
+       ip = (struct interface_info *)0;
+
+       do {
+               token = peek_token (&val, cfile);
+               if (token == EOF) {
+                       parse_warn ("unterminated lease declaration.");
+                       return;
+               }
+               if (token == RBRACE)
+                       break;
+               parse_client_lease_declaration (cfile, lease, &ip);
+       } while (1);
+       token = next_token (&val, cfile);
+
+       /* If the lease declaration didn't include an interface
+          declaration that we recognized, it's of no use to us. */
+       if (!ip) {
+               free_client_lease (lease);
+               return;
+       }
+
+       /* The last lease in the lease file on a particular interface is
+          the active lease for that interface.    Of course, we don't know
+          what the last lease in the file is until we've parsed the whole
+          file, so at this point, we assume that the lease we just parsed
+          is the active lease for its interface.   If there's already
+          an active lease for the interface, and this lease is for the same
+          ip address, then we just toss the old active lease and replace
+          it with this one.   If this lease is for a different address,
+          then if the old active lease has expired, we dump it; if not,
+          we put it on the list of leases for this interface which are
+          still valid but no longer active. */
+       if (ip -> client -> active) {
+               if (ip -> client -> active -> expiry < cur_time)
+                       free_client_lease (ip -> client -> active);
+               else if (ip -> client -> active -> address.len ==
+                        lease -> address.len &&
+                        !memcmp (ip -> client -> active -> address.iabuf,
+                                 lease -> address.iabuf,
+                                 lease -> address.len))
+                       free_client_lease (ip -> client -> active);
+               else {
+                       ip -> client -> active -> next =
+                               ip -> client -> leases;
+                       ip -> client -> leases = ip -> client -> active;
+               }
+       }
+       ip -> client -> active = lease;
+
+       /* The current lease may supersede a lease that's not the
+          active lease but is still on the lease list, so scan the
+          lease list looking for a lease with the same address, and
+          if we find it, toss it. */
+       pl = (struct client_lease *)0;
+       for (lp = ip -> client -> leases; lp; lp = lp -> next) {
+               if (lp -> address.len == lease -> address.len &&
+                   !memcmp (lp -> address.iabuf, lease -> address.iabuf,
+                            lease -> address.len)) {
+                       if (pl)
+                               pl -> next = lp -> next;
+                       else
+                               ip -> client -> leases = lp -> next;
+                       free_client_lease (lp);
+                       break;
+               }
+       }
+       /* phew. */
+}
+
+/* client-lease-declaration :==
+       INTERFACE string |
+       FIXED_ADDR ip_address |
+       FILENAME string |
+       SERVER_NAME string |
+       option-decl |
+       RENEW time-decl |
+       REBIND time-decl |
+       EXPIRE time-decl */
+
+void parse_client_lease_declaration (cfile, lease, ipp)
+       FILE *cfile;
+       struct client_lease *lease;
+       struct interface_info **ipp;
+{
+       int token;
+       char *val;
+       char *t, *n;
+       struct interface_info *ip;
+
+       switch (next_token (&val, cfile)) {
+             case INTERFACE:
+               token = next_token (&val, cfile);
+               if (token != STRING) {
+                       parse_warn ("expecting interface name (in quotes).");
+                       skip_to_semi (cfile);
+                       break;
+               }
+               for (ip = interfaces; ip; ip = ip -> next) {
+                       if (!strcmp (ip -> name, val))
+                               break;
+               }
+               *ipp = ip;
+               break;
+
+             case FIXED_ADDR:
+               parse_ip_addr (cfile, &lease -> address);
+               break;
+
+             case FILENAME:
+               lease -> filename = parse_string (cfile);
+               return;
+
+             case SERVER_NAME:
+               lease -> server_name = parse_string (cfile);
+               return;
+
+             case RENEW:
+               lease -> renewal = parse_date (cfile);
+               return;
+
+             case REBIND:
+               lease -> rebind = parse_date (cfile);
+               return;
+
+             case EXPIRE:
+               lease -> expiry = parse_date (cfile);
+               return;
+
+             case OPTION:
+               parse_option_decl (cfile, lease -> options);
+               return;
+
+             default:
+               parse_warn ("expecting lease declaration.");
+               skip_to_semi (cfile);
+               break;
+       }
+       token = next_token (&val, cfile);
+       if (token != SEMI) {
+               parse_warn ("expecting semicolon.");
+               skip_to_semi (cfile);
+       }
+}
+
+void parse_ip_addr (cfile, addr)
+       FILE *cfile;
+       struct iaddr *addr;
+{
+       char *val;
+       int token;
+
+       addr -> len = 4;
+       parse_numeric_aggregate (cfile, addr -> iabuf,
+                                &addr -> len, DOT, 10, 8);
+}      
+
+void parse_option_decl (cfile, options)
+       FILE *cfile;
+       struct option_data *options;
+{
+       char *val;
+       int token;
+       unsigned char buf [4];
+       char *vendor;
+       char *fmt;
+       struct universe *universe;
+       struct option *option;
+       struct iaddr ip_addr;
+       char *dp;
+       int len;
+
+       token = next_token (&val, cfile);
+       if (!is_identifier (token)) {
+               parse_warn ("expecting identifier after option keyword.");
+               if (token != SEMI)
+                       skip_to_semi (cfile);
+               return;
+       }
+       vendor = malloc (strlen (val) + 1);
+       if (!vendor)
+               error ("no memory for vendor information.");
+       strcpy (vendor, val);
+       token = peek_token (&val, cfile);
+       if (token == DOT) {
+               /* Go ahead and take the DOT token... */
+               token = next_token (&val, cfile);
+
+               /* The next token should be an identifier... */
+               token = next_token (&val, cfile);
+               if (!is_identifier (token)) {
+                       parse_warn ("expecting identifier after '.'");
+                       if (token != SEMI)
+                               skip_to_semi (cfile);
+                       return;
+               }
+
+               /* Look up the option name hash table for the specified
+                  vendor. */
+               universe = (struct universe *)hash_lookup (&universe_hash,
+                                                          vendor, 0);
+               /* If it's not there, we can't parse the rest of the
+                  declaration. */
+               if (!universe) {
+                       parse_warn ("no vendor named %s.", vendor);
+                       skip_to_semi (cfile);
+                       return;
+               }
+       } else {
+               /* Use the default hash table, which contains all the
+                  standard dhcp option names. */
+               val = vendor;
+               universe = &dhcp_universe;
+       }
+
+       /* Look up the actual option info... */
+       option = (struct option *)hash_lookup (universe -> hash, val, 0);
+
+       /* If we didn't get an option structure, it's an undefined option. */
+       if (!option) {
+               if (val == vendor)
+                       parse_warn ("no option named %s", val);
+               else
+                       parse_warn ("no option named %s for vendor %s",
+                                   val, vendor);
+               skip_to_semi (cfile);
+               return;
+       }
+
+       /* Free the initial identifier token. */
+       free (vendor);
+
+       /* Parse the option data... */
+       do {
+               /* Set a flag if this is an array of a simple type (i.e.,
+                  not an array of pairs of IP addresses, or something
+                  like that. */
+               int uniform = option -> format [1] == 'A';
+
+               for (fmt = option -> format; *fmt; fmt++) {
+                       if (*fmt == 'A')
+                               break;
+                       switch (*fmt) {
+                             case 'X':
+                               options [option -> code].len =
+                                       parse_X (cfile,
+                                                (&options
+                                                 [option -> code].data));
+                               break;
+                                       
+                             case 't': /* Text string... */
+                               options [option -> code].data =
+                                       parse_string (cfile);
+                               options [option -> code].len =
+                                       strlen (options [option -> code].data);
+                               break;
+
+                             case 'I': /* IP address. */
+                               parse_ip_addr (cfile, &ip_addr);
+                               len = ip_addr.len;
+                               dp = ip_addr.iabuf;
+
+                             alloc:
+                               options [option -> code].data = malloc (len);
+                               if (!options [option -> code].data)
+                                       error ("no memory for option data.");
+                               memcpy (options [option -> code].data,
+                                       dp, len);
+                               options [option -> code].len = len;
+                               break;
+
+                             case 'L': /* Unsigned 32-bit integer... */
+                             case 'l': /* Signed 32-bit integer... */
+                               token = next_token (&val, cfile);
+                               if (token != NUMBER) {
+                                     need_number:
+                                       parse_warn ("expecting number.");
+                                       if (token != SEMI)
+                                               skip_to_semi (cfile);
+                                       return;
+                               }
+                               convert_num (buf, val, 0, 32);
+                               len = 4;
+                               dp = buf;
+                               goto alloc;
+
+                             case 's': /* Signed 16-bit integer. */
+                             case 'S': /* Unsigned 16-bit integer. */
+                               token = next_token (&val, cfile);
+                               if (token != NUMBER)
+                                       goto need_number;
+                               convert_num (buf, val, 0, 16);
+                               len = 2;
+                               dp = buf;
+                               goto alloc;
+
+                             case 'b': /* Signed 8-bit integer. */
+                             case 'B': /* Unsigned 8-bit integer. */
+                               token = next_token (&val, cfile);
+                               if (token != NUMBER)
+                                       goto need_number;
+                               convert_num (buf, val, 0, 8);
+                               len = 1;
+                               dp = buf;
+                               goto alloc;
+
+                             case 'f': /* Boolean flag. */
+                               token = next_token (&val, cfile);
+                               if (!is_identifier (token)) {
+                                       parse_warn ("expecting identifier.");
+                                     bad_flag:
+                                       if (token != SEMI)
+                                               skip_to_semi (cfile);
+                                       return;
+                               }
+                               if (!strcasecmp (val, "true")
+                                   || !strcasecmp (val, "on"))
+                                       buf [0] = 1;
+                               else if (!strcasecmp (val, "false")
+                                        || !strcasecmp (val, "off"))
+                                       buf [0] = 0;
+                               else {
+                                       parse_warn ("expecting boolean.");
+                                       goto bad_flag;
+                               }
+                               len = 1;
+                               dp = buf;
+                               goto alloc;
+
+                             default:
+                               warn ("Bad format %c in parse_option_param.",
+                                     *fmt);
+                               skip_to_semi (cfile);
+                               return;
+                       }
+               }
+               if (*fmt == 'A') {
+                       token = peek_token (&val, cfile);
+                       if (token == COMMA) {
+                               token = next_token (&val, cfile);
+                               continue;
+                       }
+                       break;
+               }
+       } while (*fmt == 'A');
+
+       token = next_token (&val, cfile);
+       if (token != SEMI) {
+               parse_warn ("semicolon expected.");
+               skip_to_semi (cfile);
+               return;
+       }
+}
+
diff --git a/clparse.c b/clparse.c
new file mode 100644 (file)
index 0000000..7d4b1e4
--- /dev/null
+++ b/clparse.c
@@ -0,0 +1,781 @@
+/* clparse.c
+
+   Parser for dhclient config and lease files... */
+
+/*
+ * Copyright (c) 1995, 1996, 1997 The Internet Software Consortium.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of The Internet Software Consortium nor the names
+ *    of its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
+ * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises.  To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''.  To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#ifndef lint
+static char copyright[] =
+"$Id: clparse.c,v 1.1 1997/02/18 14:27:53 mellon Exp $ Copyright (c) 1995, 1996 The Internet Software Consortium.  All rights reserved.\n";
+#endif /* not lint */
+
+#include "dhcpd.h"
+#include "dhctoken.h"
+
+static TIME parsed_time;
+
+/* client-conf-file :== client-declarations EOF
+   client-declarations :== <nil>
+                        | client-declaration
+                        | client-declarations client-declaration */
+
+int read_client_conf ()
+{
+       FILE *cfile;
+       char *val;
+       int token;
+       int declaration = 0;
+       struct client_config *config;
+       struct client_state *state;
+       struct interface_info *ip;
+
+       new_parse (path_dhclient_conf);
+
+       /* Set up the initial dhcp option universe. */
+       initialize_universes ();
+
+       /* Initialize the top level client configuration. */
+       memset (&top_level_config, 0, sizeof top_level_config);
+
+       if ((cfile = fopen (path_dhclient_conf, "r")) == NULL)
+               error ("Can't open %s: %m", path_dhclient_conf);
+       do {
+               token = peek_token (&val, cfile);
+               if (token == EOF)
+                       break;
+               parse_client_statement (cfile, (struct interface_info *)0,
+                                       &top_level_config);
+       } while (1);
+       token = next_token (&val, cfile); /* Clear the peek buffer */
+
+       /* Set up state and config structures for clients that don't
+          have per-interface configuration declarations. */
+       config = (struct client_config *)0;
+       for (ip = interfaces; ip; ip = ip -> next) {
+               if (!ip -> client) {
+                       ip -> client = (struct client_state *)
+                               malloc (sizeof (struct client_state));
+                       if (!ip -> client)
+                               error ("no memory for client state.");
+                       memset (ip -> client, 0, sizeof *(ip -> client));
+                       if (!config) {
+                               config = (struct client_config *)
+                                       malloc (sizeof (struct client_config));
+                               if (!config)
+                                       error ("no memory for client config.");
+                               memcpy (config, &top_level_config,
+                                       sizeof top_level_config);
+                       }
+                       ip -> client -> config = config;
+               }
+       }
+
+       return !warnings_occurred;
+}
+
+/* lease-file :== client-lease-statements EOF
+   client-lease-statements :== <nil>
+                    | client-lease-statements client-lease-statement */
+
+void read_client_leases ()
+{
+       FILE *cfile;
+       char *val;
+       int token;
+
+       new_parse (path_dhclient_db);
+
+       /* Open the lease file.   If we can't open it, just return -
+          we can safely trust the server to remember our state. */
+       if ((cfile = fopen (path_dhclient_db, "r")) == NULL)
+               return;
+       do {
+               token = next_token (&val, cfile);
+               if (token == EOF)
+                       break;
+               if (token != LEASE) {
+                       warn ("Corrupt lease file - possible data loss!");
+                       skip_to_semi (cfile);
+                       break;
+               } else
+                       parse_client_lease_statement (cfile);
+
+       } while (1);
+}
+
+/* client-declaration :== 
+       HOSTNAME string |
+       hardware-declaration |
+       client-identifier-declaration |
+       REQUEST option-list |
+       REQUIRE option-list |
+       TIMEOUT number |
+       RETRY number |
+       SELECT_TIMEOUT number |
+       SCRIPT string |
+       interface-declaration |
+       client-lease-statement */
+
+void parse_client_statement (cfile, ip, config)
+       FILE *cfile;
+       struct interface_info *ip;
+       struct client_config *config;
+{
+       int token;
+       char *val;
+       char *t, *n;
+       struct hardware hardware;
+
+       switch (next_token (&val, cfile)) {
+             case HOSTNAME:
+               config -> dns_hostname = parse_string (cfile);
+               return;
+
+             case CLIENT_IDENTIFIER:
+               config -> cid_len = parse_X (cfile,
+                                            &config -> client_identifier);
+               break;
+
+             case HARDWARE:
+               if (ip) {
+                       parse_hardware_param (cfile, &ip -> hw_address);
+               } else {
+                       parse_warn ("hardware address parameter %s",
+                                   "not allowed here.");
+                       skip_to_semi (cfile);
+               }
+               return;
+
+             case REQUEST:
+               config -> requested_option_count =
+                       parse_option_list (cfile, config -> requested_options);
+               return;
+
+             case REQUIRE:
+               memset (config -> required_options, 0,
+                       sizeof config -> required_options);
+               parse_option_list (cfile, config -> required_options);
+               return;
+
+             case TIMEOUT:
+               parse_lease_time (cfile, &config -> timeout);
+               return;
+
+             case RETRY:
+               parse_lease_time (cfile, &config -> retry_interval);
+               return;
+
+             case SELECT_TIMEOUT:
+               parse_lease_time (cfile, &config -> select_interval);
+               return;
+
+             case SCRIPT:
+               config -> script_name = parse_string (cfile);
+               return;
+
+             case INTERFACE:
+               if (ip)
+                       parse_warn ("nested interface declaration.");
+               parse_interface_declaration (cfile, config);
+               return;
+
+             case LEASE:
+               parse_client_lease_statement (cfile);
+               return;
+
+             default:
+               parse_warn ("expecting a statement.");
+               skip_to_semi (cfile);
+               break;
+       }
+       token = next_token (&val, cfile);
+       if (token != SEMI) {
+               parse_warn ("semicolon expected.");
+               skip_to_semi (cfile);
+       }
+}
+
+int parse_X (cfile, p)
+       FILE *cfile;
+       u_int8_t **p;
+{
+       int token;
+       char *val;
+       unsigned char buf [1024];
+       int buflen;
+       unsigned char *s;
+
+       token = peek_token (&val, cfile);
+       if (token == NUMBER_OR_NAME || token == NUMBER) {
+               do {
+                       token = next_token (&val, cfile);
+                       if (token != NUMBER && token != NUMBER_OR_NAME) {
+                               parse_warn ("expecting hexadecimal constant.");
+                               skip_to_semi (cfile);
+                               *p = (u_int8_t *)0;
+                               return 0;
+                       }
+                       convert_num (&buf [buflen], val, 16, 8);
+                       if (buflen++ > sizeof buf) {
+                               parse_warn ("hexadecimal constant too long.");
+                               skip_to_semi (cfile);
+                               *p = (u_int8_t *)0;
+                               return 0;
+                       }
+                       token = peek_token (&val, cfile);
+                       if (token == COLON)
+                               token = next_token (&val, cfile);
+               } while (token == COLON);
+               val = buf;
+       } else if (token == STRING) {
+               token = next_token (&val, cfile);
+               buflen = strlen (val) + 1;
+       } else {
+               parse_warn ("expecting string or hexadecimal data");
+               skip_to_semi (cfile);
+               *p = (u_int8_t *)0;
+               return 0;
+       }
+       *p = malloc (buflen);
+       if (!*p)
+               error ("out of memory allocating client id.\n");
+       memcpy (*p, val, buflen);
+       return buflen;
+}
+
+/* option-list :== option_name |
+                  option_list COMMA option_name */
+
+int parse_option_list (cfile, list)
+       FILE *cfile;
+       u_int8_t *list;
+{
+       int ix, i;
+       int token;
+       char *val;
+
+       ix = 0;
+       do {
+               token = next_token (&val, cfile);
+               if (!is_identifier (token)) {
+                       parse_warn ("expected option name.");
+                       skip_to_semi (cfile);
+                       return 0;
+               }
+               for (i = 0; i < 256; i++) {
+                       if (!strcasecmp (dhcp_options [i].name, val))
+                               break;
+               }
+               if (i == 256) {
+                       parse_warn ("%s: expected option name.");
+                       skip_to_semi (cfile);
+                       return 0;
+               }
+               list [ix++] = i;
+               if (ix == 256) {
+                       parse_warn ("%s: too many options.", val);
+                       skip_to_semi (cfile);
+                       return 0;
+               }
+               token = next_token (&val, cfile);
+       } while (token == COMMA);
+       if (token != SEMI) {
+               parse_warn ("expecting semicolon.");
+               skip_to_semi (cfile);
+               return 0;
+       }
+       return ix;
+}
+
+/* interface-declaration :==
+       INTERFACE string LBRACE client-declarations RBRACE */
+
+void parse_interface_declaration (cfile, outer_config)
+       FILE *cfile;
+       struct client_config *outer_config;
+{
+       int token;
+       char *val;
+
+       struct interface_info dummy_interface, *ip;
+       struct client_state dummy_state;
+       struct client_config dummy_config;
+
+       token = next_token (&val, cfile);
+       if (token != STRING) {
+               parse_warn ("expecting interface name (in quotes).");
+               skip_to_semi (cfile);
+               return;
+       }
+
+       /* Find the interface (if any) that matches the name. */
+       for (ip = interfaces; ip; ip = ip -> next) {
+               if (!strcmp (ip -> name, val))
+                       break;
+       }
+
+       /* If we didn't find an interface, put up a dummy interface so
+          that we have some place to parse the bogus data.   Otherwise,
+          allocate a client state and client config structure for the
+          interface. */
+       if (!ip) {
+               parse_warn ("interface %s not found.", val);
+               ip = &dummy_interface;
+               memset (ip, 0, sizeof dummy_interface);
+               ip -> client = &dummy_state;
+               memset (ip -> client, 0, sizeof dummy_state);
+               ip -> client -> config = &dummy_config;
+       } else {
+               ip -> client = ((struct client_state *)
+                               malloc (sizeof (struct client_state)));
+               if (!ip -> client)
+                       error ("no memory for state for %s\n", val);
+               memset (ip -> client, 0, sizeof *(ip -> client));
+               ip -> client -> config =
+                       ((struct client_config *)
+                        malloc (sizeof (struct client_config)));
+               if (!ip -> client -> config)
+                       error ("no memory for config for %s\n", val);
+               memset (ip -> client -> config, 0,
+                       sizeof *(ip -> client -> config));
+       }
+       memcpy (ip -> client -> config, outer_config, sizeof *outer_config);
+
+       token = next_token (&val, cfile);
+       if (token != LBRACE) {
+               parse_warn ("expecting left brace.");
+               skip_to_semi (cfile);
+               return;
+       }
+
+       do {
+               token = peek_token (&val, cfile);
+               if (token == EOF) {
+                       parse_warn ("unterminated interface declaration.");
+                       return;
+               }
+               if (token == RBRACE)
+                       break;
+               parse_client_statement (cfile, ip, ip -> client -> config);
+       } while (1);
+       token = next_token (&val, cfile);
+}
+
+/* client-lease-statement :==
+       LEASE RBRACE client-lease-declarations LBRACE
+
+       client-lease-declarations :==
+               <nil> |
+               client-lease-declaration |
+               client-lease-declarations client-lease-declaration */
+
+
+void parse_client_lease_statement (cfile)
+       FILE *cfile;
+{
+       struct client_lease *lease, *lp, *pl;
+       struct interface_info *ip;
+       int token;
+       char *val;
+
+       token = next_token (&val, cfile);
+       if (token != LBRACE) {
+               parse_warn ("expecting left brace.");
+               skip_to_semi (cfile);
+               return;
+       }
+
+       lease = (struct client_lease *)malloc (sizeof (struct client_lease));
+       if (!lease)
+               error ("no memory for lease.\n");
+       memset (lease, 0, sizeof *lease);
+
+       ip = (struct interface_info *)0;
+
+       do {
+               token = peek_token (&val, cfile);
+               if (token == EOF) {
+                       parse_warn ("unterminated lease declaration.");
+                       return;
+               }
+               if (token == RBRACE)
+                       break;
+               parse_client_lease_declaration (cfile, lease, &ip);
+       } while (1);
+       token = next_token (&val, cfile);
+
+       /* If the lease declaration didn't include an interface
+          declaration that we recognized, it's of no use to us. */
+       if (!ip) {
+               free_client_lease (lease);
+               return;
+       }
+
+       /* The last lease in the lease file on a particular interface is
+          the active lease for that interface.    Of course, we don't know
+          what the last lease in the file is until we've parsed the whole
+          file, so at this point, we assume that the lease we just parsed
+          is the active lease for its interface.   If there's already
+          an active lease for the interface, and this lease is for the same
+          ip address, then we just toss the old active lease and replace
+          it with this one.   If this lease is for a different address,
+          then if the old active lease has expired, we dump it; if not,
+          we put it on the list of leases for this interface which are
+          still valid but no longer active. */
+       if (ip -> client -> active) {
+               if (ip -> client -> active -> expiry < cur_time)
+                       free_client_lease (ip -> client -> active);
+               else if (ip -> client -> active -> address.len ==
+                        lease -> address.len &&
+                        !memcmp (ip -> client -> active -> address.iabuf,
+                                 lease -> address.iabuf,
+                                 lease -> address.len))
+                       free_client_lease (ip -> client -> active);
+               else {
+                       ip -> client -> active -> next =
+                               ip -> client -> leases;
+                       ip -> client -> leases = ip -> client -> active;
+               }
+       }
+       ip -> client -> active = lease;
+
+       /* The current lease may supersede a lease that's not the
+          active lease but is still on the lease list, so scan the
+          lease list looking for a lease with the same address, and
+          if we find it, toss it. */
+       pl = (struct client_lease *)0;
+       for (lp = ip -> client -> leases; lp; lp = lp -> next) {
+               if (lp -> address.len == lease -> address.len &&
+                   !memcmp (lp -> address.iabuf, lease -> address.iabuf,
+                            lease -> address.len)) {
+                       if (pl)
+                               pl -> next = lp -> next;
+                       else
+                               ip -> client -> leases = lp -> next;
+                       free_client_lease (lp);
+                       break;
+               }
+       }
+       /* phew. */
+}
+
+/* client-lease-declaration :==
+       INTERFACE string |
+       FIXED_ADDR ip_address |
+       FILENAME string |
+       SERVER_NAME string |
+       option-decl |
+       RENEW time-decl |
+       REBIND time-decl |
+       EXPIRE time-decl */
+
+void parse_client_lease_declaration (cfile, lease, ipp)
+       FILE *cfile;
+       struct client_lease *lease;
+       struct interface_info **ipp;
+{
+       int token;
+       char *val;
+       char *t, *n;
+       struct interface_info *ip;
+
+       switch (next_token (&val, cfile)) {
+             case INTERFACE:
+               token = next_token (&val, cfile);
+               if (token != STRING) {
+                       parse_warn ("expecting interface name (in quotes).");
+                       skip_to_semi (cfile);
+                       break;
+               }
+               for (ip = interfaces; ip; ip = ip -> next) {
+                       if (!strcmp (ip -> name, val))
+                               break;
+               }
+               *ipp = ip;
+               break;
+
+             case FIXED_ADDR:
+               parse_ip_addr (cfile, &lease -> address);
+               break;
+
+             case FILENAME:
+               lease -> filename = parse_string (cfile);
+               return;
+
+             case SERVER_NAME:
+               lease -> server_name = parse_string (cfile);
+               return;
+
+             case RENEW:
+               lease -> renewal = parse_date (cfile);
+               return;
+
+             case REBIND:
+               lease -> rebind = parse_date (cfile);
+               return;
+
+             case EXPIRE:
+               lease -> expiry = parse_date (cfile);
+               return;
+
+             case OPTION:
+               parse_option_decl (cfile, lease -> options);
+               return;
+
+             default:
+               parse_warn ("expecting lease declaration.");
+               skip_to_semi (cfile);
+               break;
+       }
+       token = next_token (&val, cfile);
+       if (token != SEMI) {
+               parse_warn ("expecting semicolon.");
+               skip_to_semi (cfile);
+       }
+}
+
+void parse_ip_addr (cfile, addr)
+       FILE *cfile;
+       struct iaddr *addr;
+{
+       char *val;
+       int token;
+
+       addr -> len = 4;
+       parse_numeric_aggregate (cfile, addr -> iabuf,
+                                &addr -> len, DOT, 10, 8);
+}      
+
+void parse_option_decl (cfile, options)
+       FILE *cfile;
+       struct option_data *options;
+{
+       char *val;
+       int token;
+       unsigned char buf [4];
+       char *vendor;
+       char *fmt;
+       struct universe *universe;
+       struct option *option;
+       struct iaddr ip_addr;
+       char *dp;
+       int len;
+
+       token = next_token (&val, cfile);
+       if (!is_identifier (token)) {
+               parse_warn ("expecting identifier after option keyword.");
+               if (token != SEMI)
+                       skip_to_semi (cfile);
+               return;
+       }
+       vendor = malloc (strlen (val) + 1);
+       if (!vendor)
+               error ("no memory for vendor information.");
+       strcpy (vendor, val);
+       token = peek_token (&val, cfile);
+       if (token == DOT) {
+               /* Go ahead and take the DOT token... */
+               token = next_token (&val, cfile);
+
+               /* The next token should be an identifier... */
+               token = next_token (&val, cfile);
+               if (!is_identifier (token)) {
+                       parse_warn ("expecting identifier after '.'");
+                       if (token != SEMI)
+                               skip_to_semi (cfile);
+                       return;
+               }
+
+               /* Look up the option name hash table for the specified
+                  vendor. */
+               universe = (struct universe *)hash_lookup (&universe_hash,
+                                                          vendor, 0);
+               /* If it's not there, we can't parse the rest of the
+                  declaration. */
+               if (!universe) {
+                       parse_warn ("no vendor named %s.", vendor);
+                       skip_to_semi (cfile);
+                       return;
+               }
+       } else {
+               /* Use the default hash table, which contains all the
+                  standard dhcp option names. */
+               val = vendor;
+               universe = &dhcp_universe;
+       }
+
+       /* Look up the actual option info... */
+       option = (struct option *)hash_lookup (universe -> hash, val, 0);
+
+       /* If we didn't get an option structure, it's an undefined option. */
+       if (!option) {
+               if (val == vendor)
+                       parse_warn ("no option named %s", val);
+               else
+                       parse_warn ("no option named %s for vendor %s",
+                                   val, vendor);
+               skip_to_semi (cfile);
+               return;
+       }
+
+       /* Free the initial identifier token. */
+       free (vendor);
+
+       /* Parse the option data... */
+       do {
+               /* Set a flag if this is an array of a simple type (i.e.,
+                  not an array of pairs of IP addresses, or something
+                  like that. */
+               int uniform = option -> format [1] == 'A';
+
+               for (fmt = option -> format; *fmt; fmt++) {
+                       if (*fmt == 'A')
+                               break;
+                       switch (*fmt) {
+                             case 'X':
+                               options [option -> code].len =
+                                       parse_X (cfile,
+                                                (&options
+                                                 [option -> code].data));
+                               break;
+                                       
+                             case 't': /* Text string... */
+                               options [option -> code].data =
+                                       parse_string (cfile);
+                               options [option -> code].len =
+                                       strlen (options [option -> code].data);
+                               break;
+
+                             case 'I': /* IP address. */
+                               parse_ip_addr (cfile, &ip_addr);
+                               len = ip_addr.len;
+                               dp = ip_addr.iabuf;
+
+                             alloc:
+                               options [option -> code].data = malloc (len);
+                               if (!options [option -> code].data)
+                                       error ("no memory for option data.");
+                               memcpy (options [option -> code].data,
+                                       dp, len);
+                               options [option -> code].len = len;
+                               break;
+
+                             case 'L': /* Unsigned 32-bit integer... */
+                             case 'l': /* Signed 32-bit integer... */
+                               token = next_token (&val, cfile);
+                               if (token != NUMBER) {
+                                     need_number:
+                                       parse_warn ("expecting number.");
+                                       if (token != SEMI)
+                                               skip_to_semi (cfile);
+                                       return;
+                               }
+                               convert_num (buf, val, 0, 32);
+                               len = 4;
+                               dp = buf;
+                               goto alloc;
+
+                             case 's': /* Signed 16-bit integer. */
+                             case 'S': /* Unsigned 16-bit integer. */
+                               token = next_token (&val, cfile);
+                               if (token != NUMBER)
+                                       goto need_number;
+                               convert_num (buf, val, 0, 16);
+                               len = 2;
+                               dp = buf;
+                               goto alloc;
+
+                             case 'b': /* Signed 8-bit integer. */
+                             case 'B': /* Unsigned 8-bit integer. */
+                               token = next_token (&val, cfile);
+                               if (token != NUMBER)
+                                       goto need_number;
+                               convert_num (buf, val, 0, 8);
+                               len = 1;
+                               dp = buf;
+                               goto alloc;
+
+                             case 'f': /* Boolean flag. */
+                               token = next_token (&val, cfile);
+                               if (!is_identifier (token)) {
+                                       parse_warn ("expecting identifier.");
+                                     bad_flag:
+                                       if (token != SEMI)
+                                               skip_to_semi (cfile);
+                                       return;
+                               }
+                               if (!strcasecmp (val, "true")
+                                   || !strcasecmp (val, "on"))
+                                       buf [0] = 1;
+                               else if (!strcasecmp (val, "false")
+                                        || !strcasecmp (val, "off"))
+                                       buf [0] = 0;
+                               else {
+                                       parse_warn ("expecting boolean.");
+                                       goto bad_flag;
+                               }
+                               len = 1;
+                               dp = buf;
+                               goto alloc;
+
+                             default:
+                               warn ("Bad format %c in parse_option_param.",
+                                     *fmt);
+                               skip_to_semi (cfile);
+                               return;
+                       }
+               }
+               if (*fmt == 'A') {
+                       token = peek_token (&val, cfile);
+                       if (token == COMMA) {
+                               token = next_token (&val, cfile);
+                               continue;
+                       }
+                       break;
+               }
+       } while (*fmt == 'A');
+
+       token = next_token (&val, cfile);
+       if (token != SEMI) {
+               parse_warn ("semicolon expected.");
+               skip_to_semi (cfile);
+               return;
+       }
+}
+