From: Ted Lemon Date: Tue, 18 Feb 1997 14:27:53 +0000 (+0000) Subject: Parser for DHCP client config files X-Git-Tag: DHCP_970222~64 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7d5f3f4a40794cf250b2dc6588200303d015f899;p=thirdparty%2Fdhcp.git Parser for DHCP client config files --- diff --git a/client/clparse.c b/client/clparse.c new file mode 100644 index 000000000..7d4b1e454 --- /dev/null +++ b/client/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 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 :== + | 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 :== + | 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 :== + | + 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 index 000000000..7d4b1e454 --- /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 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 :== + | 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 :== + | 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 :== + | + 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; + } +} +