1030 lines
24 KiB
C
1030 lines
24 KiB
C
/* $OpenBSD: clparse.c,v 1.203 2021/02/27 17:44:58 krw Exp $ */
|
|
|
|
/* Parser for dhclient config and lease files. */
|
|
|
|
/*
|
|
* Copyright (c) 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''.
|
|
*/
|
|
|
|
#include <sys/queue.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_arp.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/if_ether.h>
|
|
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "dhcp.h"
|
|
#include "dhcpd.h"
|
|
#include "dhctoken.h"
|
|
#include "log.h"
|
|
|
|
void parse_conf_decl(FILE *, char *);
|
|
int parse_hex_octets(FILE *, unsigned int *, uint8_t **);
|
|
int parse_domain_list(FILE *, int *, char **);
|
|
int parse_option_list(FILE *, int *, uint8_t *);
|
|
int parse_interface(FILE *, char *);
|
|
int parse_lease(FILE *, struct client_lease **);
|
|
void parse_lease_decl(FILE *, struct client_lease *);
|
|
int parse_option(FILE *, int *, struct option_data *);
|
|
int parse_reject_statement(FILE *);
|
|
|
|
void apply_actions(uint8_t *);
|
|
void set_default_client_identifier(struct ether_addr *);
|
|
void set_default_hostname(void);
|
|
|
|
void
|
|
init_config(void)
|
|
{
|
|
struct option_data *option;
|
|
uint32_t expiry;
|
|
|
|
config = calloc(1, sizeof(*config));
|
|
if (config == NULL)
|
|
fatal("config");
|
|
|
|
TAILQ_INIT(&config->reject_list);
|
|
|
|
/* Set some defaults. */
|
|
config->link_interval = 10; /* secs before going daemon w/o lease */
|
|
config->select_interval = 0; /* secs to wait for other OFFERs */
|
|
config->retry_interval = 1; /* secs before asking for OFFER */
|
|
#ifdef SMALL
|
|
config->backoff_cutoff = 2; /* max secs between packet retries */
|
|
config->reboot_interval = 5; /* secs before giving up on reboot */
|
|
config->offer_interval = 10; /* secs to wait for an OFFER */
|
|
#else
|
|
config->backoff_cutoff = 10; /* max secs between packet retries */
|
|
config->reboot_interval = 1; /* secs before giving up on reboot */
|
|
config->offer_interval = 30; /* secs to wait for an OFFER */
|
|
#endif
|
|
config->initial_interval = 1; /* secs before 1st retry */
|
|
|
|
/* All leases must supply a subnet mask. Classful defaults are dead, Jim. */
|
|
config->required_options[config->required_option_count++] = DHO_SUBNET_MASK;
|
|
|
|
/*
|
|
* Set default lease length, which will determine default renewal
|
|
* and rebind times.
|
|
*
|
|
* XXX Thus applies to both BOOTP and DHCP leases.
|
|
*
|
|
* DHO_DHCP_LEASE_TIME (12 hours == 43200 seconds),
|
|
*/
|
|
option = &config->defaults[DHO_DHCP_LEASE_TIME];
|
|
option->data = malloc(4);
|
|
if (option->data == NULL)
|
|
fatal("default lease length");
|
|
|
|
config->default_actions[DHO_DHCP_LEASE_TIME] = ACTION_DEFAULT;
|
|
option->len = 4;
|
|
expiry = htonl(43200);
|
|
memcpy(option->data, &expiry, 4);
|
|
|
|
config->requested_options
|
|
[config->requested_option_count++] = DHO_SUBNET_MASK;
|
|
config->requested_options
|
|
[config->requested_option_count++] = DHO_BROADCAST_ADDRESS;
|
|
config->requested_options
|
|
[config->requested_option_count++] = DHO_TIME_OFFSET;
|
|
/* RFC 3442 says CLASSLESS_STATIC_ROUTES must be before ROUTERS! */
|
|
config->requested_options
|
|
[config->requested_option_count++] = DHO_CLASSLESS_STATIC_ROUTES;
|
|
config->requested_options
|
|
[config->requested_option_count++] = DHO_ROUTERS;
|
|
config->requested_options
|
|
[config->requested_option_count++] = DHO_DOMAIN_NAME;
|
|
config->requested_options
|
|
[config->requested_option_count++] = DHO_DOMAIN_SEARCH;
|
|
config->requested_options
|
|
[config->requested_option_count++] = DHO_DOMAIN_NAME_SERVERS;
|
|
config->requested_options
|
|
[config->requested_option_count++] = DHO_HOST_NAME;
|
|
config->requested_options
|
|
[config->requested_option_count++] = DHO_BOOTFILE_NAME;
|
|
config->requested_options
|
|
[config->requested_option_count++] = DHO_TFTP_SERVER;
|
|
}
|
|
|
|
/*
|
|
* conf-decls :==
|
|
* <nil>
|
|
* | conf-decl
|
|
* | conf-decls conf-decl
|
|
*/
|
|
void
|
|
read_conf(char *name, uint8_t *actions, struct ether_addr *hwaddr)
|
|
{
|
|
FILE *cfile;
|
|
int token;
|
|
|
|
init_config();
|
|
|
|
if (path_dhclient_conf != NULL) {
|
|
cfile = fopen(path_dhclient_conf, "r");
|
|
if (cfile == NULL)
|
|
fatal("fopen(%s)", path_dhclient_conf);
|
|
new_parse(path_dhclient_conf);
|
|
for (;;) {
|
|
token = peek_token(NULL, cfile);
|
|
if (token == EOF)
|
|
break;
|
|
parse_conf_decl(cfile, name);
|
|
}
|
|
fclose(cfile);
|
|
}
|
|
|
|
set_default_client_identifier(hwaddr);
|
|
set_default_hostname();
|
|
apply_actions(actions);
|
|
}
|
|
|
|
/*
|
|
* leases :==
|
|
* <nil>
|
|
* | lease
|
|
* | leases lease
|
|
*/
|
|
void
|
|
read_lease_db(struct client_lease_tq *lease_db)
|
|
{
|
|
struct client_lease *lease, *lp, *nlp;
|
|
FILE *cfile;
|
|
int i;
|
|
|
|
TAILQ_INIT(lease_db);
|
|
|
|
if ((cfile = fopen(path_lease_db, "r")) == NULL)
|
|
return;
|
|
|
|
new_parse(path_lease_db);
|
|
|
|
i = DHO_DHCP_CLIENT_IDENTIFIER;
|
|
while (feof(cfile) == 0) {
|
|
if (parse_lease(cfile, &lease) == 0)
|
|
continue;
|
|
|
|
/*
|
|
* The new lease will supersede a lease with the same
|
|
* ssid AND the same Client Identifier AND the same
|
|
* IP address.
|
|
*/
|
|
TAILQ_FOREACH_SAFE(lp, lease_db, next, nlp) {
|
|
if (lp->ssid_len != lease->ssid_len)
|
|
continue;
|
|
if (memcmp(lp->ssid, lease->ssid, lp->ssid_len) != 0)
|
|
continue;
|
|
if ((lease->options[i].len != 0) &&
|
|
((lp->options[i].len != lease->options[i].len) ||
|
|
memcmp(lp->options[i].data, lease->options[i].data,
|
|
lp->options[i].len) != 0))
|
|
continue;
|
|
if (lp->address.s_addr != lease->address.s_addr)
|
|
continue;
|
|
|
|
TAILQ_REMOVE(lease_db, lp, next);
|
|
free_client_lease(lp);
|
|
}
|
|
|
|
if (lease->epoch == 0)
|
|
time(&lease->epoch);
|
|
TAILQ_INSERT_TAIL(lease_db, lease, next);
|
|
}
|
|
|
|
fclose(cfile);
|
|
}
|
|
|
|
/*
|
|
* conf-decl :==
|
|
* APPEND option SEMI
|
|
* | BACKOFF_CUTOFF number SEMI
|
|
* | DEFAULT option SEMI
|
|
* | FILENAME string SEMI
|
|
* | FIXED_ADDR ip-address SEMI
|
|
* | IGNORE option-name-list SEMI
|
|
* | INITIAL_INTERVAL number SEMI
|
|
* | INTERFACE interface
|
|
* | LINK_TIMEOUT number SEMI
|
|
* | NEXT_SERVER string SEMI
|
|
* | PREPEND option SEMI
|
|
* | REBOOT number SEMI
|
|
* | REJECT ip-address SEMI
|
|
* | REQUEST option-name-list SEMI
|
|
* | REQUIRE option-name-list SEMI
|
|
* | RETRY number SEMI
|
|
* | SELECT_TIMEOUT number SEMI
|
|
* | SEND option SEMI
|
|
* | SERVER_NAME string SEMI
|
|
* | SUPERSEDE option SEMI
|
|
* | TIMEOUT number SEMI
|
|
*/
|
|
void
|
|
parse_conf_decl(FILE *cfile, char *name)
|
|
{
|
|
uint8_t list[DHO_COUNT];
|
|
char *val;
|
|
enum actions *p;
|
|
int action, count, i, token;
|
|
|
|
token = next_token(NULL, cfile);
|
|
|
|
switch (token) {
|
|
case TOK_APPEND:
|
|
if (parse_option(cfile, &i, config->defaults) == 0)
|
|
return;
|
|
action = code_to_action(i, ACTION_APPEND);
|
|
if (action == ACTION_DEFAULT)
|
|
parse_warn("'append' treated as 'default'");
|
|
config->default_actions[i] = action;
|
|
break;
|
|
case TOK_BACKOFF_CUTOFF:
|
|
if (parse_number(cfile, &config->backoff_cutoff, 0, INT32_MAX)
|
|
== 0)
|
|
return;
|
|
break;
|
|
case TOK_DEFAULT:
|
|
if (parse_option(cfile, &i, config->defaults) == 0)
|
|
return;
|
|
config->default_actions[i] = ACTION_DEFAULT;
|
|
break;
|
|
case TOK_FILENAME:
|
|
if (parse_string(cfile, &val) == 0)
|
|
return;
|
|
free(config->filename);
|
|
config->filename = val;
|
|
break;
|
|
case TOK_FIXED_ADDR:
|
|
if (parse_ip_addr(cfile, &config->address) == 0)
|
|
return;
|
|
break;
|
|
case TOK_IGNORE:
|
|
memset(list, 0, sizeof(list));
|
|
count = 0;
|
|
if (parse_option_list(cfile, &count, list) == 0)
|
|
return;
|
|
p = config->default_actions;
|
|
if (count == 0) {
|
|
for (i = 0; i < DHO_COUNT; i++)
|
|
if (p[i] == ACTION_IGNORE)
|
|
p[i] = ACTION_USELEASE;
|
|
} else {
|
|
for (i = 0; i < count; i++)
|
|
p[list[i]] = ACTION_IGNORE;
|
|
}
|
|
break;
|
|
case TOK_INITIAL_INTERVAL:
|
|
if (parse_number(cfile, &config->initial_interval, 0, INT32_MAX)
|
|
== 0)
|
|
return;
|
|
break;
|
|
case TOK_INTERFACE:
|
|
parse_interface(cfile, name);
|
|
return;
|
|
case TOK_LINK_TIMEOUT:
|
|
if (parse_number(cfile, &config->link_interval, 0, INT32_MAX)
|
|
== 0)
|
|
return;
|
|
break;
|
|
case TOK_NEXT_SERVER:
|
|
if (parse_ip_addr(cfile, &config->next_server) == 0)
|
|
return;
|
|
break;
|
|
case TOK_PREPEND:
|
|
if (parse_option(cfile, &i, config->defaults) == 0)
|
|
return;
|
|
action = code_to_action(i, ACTION_PREPEND);
|
|
if (action == ACTION_SUPERSEDE)
|
|
parse_warn("'prepend' treated as 'supersede'");
|
|
config->default_actions[i] = action;
|
|
break;
|
|
case TOK_REBOOT:
|
|
if (parse_number(cfile, &config->reboot_interval, 0, INT32_MAX)
|
|
== 0)
|
|
return;
|
|
break;
|
|
case TOK_REJECT:
|
|
if (parse_reject_statement(cfile) == 0)
|
|
return;
|
|
break;
|
|
case TOK_REQUEST:
|
|
if (parse_option_list(cfile, &config->requested_option_count,
|
|
config->requested_options) == 0)
|
|
return;
|
|
break;
|
|
case TOK_REQUIRE:
|
|
if (parse_option_list(cfile, &config->required_option_count,
|
|
config->required_options) == 0)
|
|
return;
|
|
break;
|
|
case TOK_RETRY:
|
|
if (parse_number(cfile, &config->retry_interval, 0, INT32_MAX)
|
|
== 0)
|
|
return;
|
|
break;
|
|
case TOK_SELECT_TIMEOUT:
|
|
if (parse_number(cfile, &config->select_interval, 0, INT32_MAX)
|
|
== 0)
|
|
return;
|
|
break;
|
|
case TOK_SEND:
|
|
if (parse_option(cfile, &i, config->send_options) == 0)
|
|
return;
|
|
break;
|
|
case TOK_SERVER_NAME:
|
|
if (parse_string(cfile, &val) == 0)
|
|
return;
|
|
free(config->server_name);
|
|
config->server_name = val;
|
|
break;
|
|
case TOK_SUPERSEDE:
|
|
if (parse_option(cfile, &i, config->defaults) == 0)
|
|
return;
|
|
config->default_actions[i] = ACTION_SUPERSEDE;
|
|
break;
|
|
case TOK_TIMEOUT:
|
|
if (parse_number(cfile, &config->offer_interval, 0, INT32_MAX)
|
|
== 0)
|
|
return;
|
|
break;
|
|
case TOK_USELEASE:
|
|
memset(list, 0, sizeof(list));
|
|
count = 0;
|
|
if (parse_option_list(cfile, &count, list) == 0)
|
|
return;
|
|
p = config->default_actions;
|
|
if (count == 0) {
|
|
for (i = 0; i < DHO_COUNT; i++) {
|
|
free(config->defaults[i].data);
|
|
config->defaults[i].data = NULL;
|
|
config->defaults[i].len = 0;
|
|
p[i] = ACTION_USELEASE;
|
|
}
|
|
} else {
|
|
for (i = 0; i < count; i++) {
|
|
free(config->defaults[list[i]].data);
|
|
config->defaults[list[i]].data = NULL;
|
|
config->defaults[list[i]].len = 0;
|
|
p[list[i]] = ACTION_USELEASE;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
parse_warn("expecting statement.");
|
|
skip_to_semi(cfile);
|
|
return;
|
|
}
|
|
|
|
parse_semi(cfile);
|
|
}
|
|
|
|
int
|
|
parse_hex_octets(FILE *cfile, unsigned int *len, uint8_t **buf)
|
|
{
|
|
static uint8_t octets[1500];
|
|
char *val, *ep;
|
|
unsigned long ulval;
|
|
unsigned int i;
|
|
int token;
|
|
|
|
i = 0;
|
|
do {
|
|
token = next_token(&val, cfile);
|
|
|
|
errno = 0;
|
|
ulval = strtoul(val, &ep, 16);
|
|
if ((val[0] == '\0' || *ep != '\0') ||
|
|
(errno == ERANGE && ulval == ULONG_MAX) ||
|
|
(ulval > UINT8_MAX))
|
|
break;
|
|
octets[i++] = ulval;
|
|
|
|
if (peek_token(NULL, cfile) == ';') {
|
|
*buf = malloc(i);
|
|
if (*buf == NULL)
|
|
break;
|
|
memcpy(*buf, octets, i);
|
|
*len = i;
|
|
return 1;
|
|
}
|
|
if (i == sizeof(octets))
|
|
break;
|
|
token = next_token(NULL, cfile);
|
|
} while (token == ':');
|
|
|
|
parse_warn("expecting colon delimited list of hex octets.");
|
|
|
|
if (token != ';')
|
|
skip_to_semi(cfile);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
parse_domain_list(FILE *cfile, int *len, char **dp)
|
|
{
|
|
uint8_t buf[DHCP_DOMAIN_SEARCH_LEN];
|
|
char *domain;
|
|
int count, token;
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
count = 0;
|
|
|
|
do {
|
|
if (parse_string(cfile, &domain) == 0)
|
|
return 0;
|
|
|
|
count++;
|
|
if (count > DHCP_DOMAIN_SEARCH_CNT) {
|
|
parse_warn("more than 6 search domains");
|
|
break;
|
|
}
|
|
|
|
if (count > 1)
|
|
strlcat(buf, " ", sizeof(buf));
|
|
if (strlcat(buf, domain, sizeof(buf)) >= sizeof(buf)) {
|
|
parse_warn("domain list too long");
|
|
break;
|
|
}
|
|
|
|
token = peek_token(NULL, cfile);
|
|
if (token == ';') {
|
|
*dp = strdup(buf);
|
|
if (*dp == NULL)
|
|
fatal("domain name list");
|
|
*len = strlen(*dp);
|
|
return 1;
|
|
}
|
|
token = next_token(NULL, cfile);
|
|
if (token != ',')
|
|
parse_warn("';' or ',' expected");
|
|
} while (token == ',');
|
|
|
|
if (token != ';')
|
|
skip_to_semi(cfile);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* option-list :==
|
|
* <nil>
|
|
* | option-name
|
|
* | option-list COMMA option-name
|
|
*/
|
|
int
|
|
parse_option_list(FILE *cfile, int *count, uint8_t *optlist)
|
|
{
|
|
uint8_t list[DHO_COUNT];
|
|
unsigned int ix, j;
|
|
int i;
|
|
int token;
|
|
char *val;
|
|
|
|
/* Empty list of option names is allowed, to re-init optlist. */
|
|
if (peek_token(NULL, cfile) == ';') {
|
|
memset(optlist, DHO_PAD, sizeof(list));
|
|
*count = 0;
|
|
return 1;
|
|
}
|
|
|
|
memset(list, 0, sizeof(list));
|
|
memcpy(list, optlist, *count);
|
|
ix = *count;
|
|
do {
|
|
/* Next token must be an option name. */
|
|
token = next_token(&val, cfile);
|
|
i = name_to_code(val);
|
|
if (i == DHO_END)
|
|
break;
|
|
|
|
/* Avoid storing duplicate options in the list. */
|
|
for (j = 0; j < ix && list[j] != i; j++)
|
|
;
|
|
if (j == ix)
|
|
list[ix++] = i;
|
|
|
|
if (peek_token(NULL, cfile) == ';') {
|
|
memcpy(optlist, list, sizeof(list));
|
|
*count = ix;
|
|
return 1;
|
|
}
|
|
token = next_token(NULL, cfile);
|
|
} while (token == ',');
|
|
|
|
parse_warn("expecting comma delimited list of option names.");
|
|
|
|
if (token != ';')
|
|
skip_to_semi(cfile);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* interface :==
|
|
* string LBRACE conf-decls RBRACE
|
|
*/
|
|
int
|
|
parse_interface(FILE *cfile, char *name)
|
|
{
|
|
char *val;
|
|
int token;
|
|
|
|
token = next_token(&val, cfile);
|
|
if (token != TOK_STRING) {
|
|
parse_warn("expecting string.");
|
|
if (token != ';')
|
|
skip_to_semi(cfile);
|
|
return 0;
|
|
}
|
|
|
|
if (strcmp(name, val) != 0) {
|
|
skip_to_semi(cfile);
|
|
return 1;
|
|
}
|
|
|
|
token = next_token(&val, cfile);
|
|
if (token != '{') {
|
|
parse_warn("expecting '{'.");
|
|
if (token != ';')
|
|
skip_to_semi(cfile);
|
|
return 0;
|
|
}
|
|
|
|
for (;;) {
|
|
token = peek_token(&val, cfile);
|
|
if (token == EOF) {
|
|
parse_warn("unterminated interface declaration.");
|
|
return 0;
|
|
}
|
|
if (token == '}') {
|
|
token = next_token(NULL, cfile);
|
|
return 1;
|
|
}
|
|
parse_conf_decl(cfile, name);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* lease :== LEASE RBRACE lease-decls LBRACE
|
|
*
|
|
* lease-decls :==
|
|
* <nil>
|
|
* | lease-decl
|
|
* | lease-decls lease-decl
|
|
*/
|
|
int
|
|
parse_lease(FILE *cfile, struct client_lease **lp)
|
|
{
|
|
struct client_lease *lease;
|
|
int token;
|
|
|
|
token = next_token(NULL, cfile);
|
|
if (token == EOF)
|
|
return 0;
|
|
if (token != TOK_LEASE) {
|
|
parse_warn("expecting lease");
|
|
if (token != ';')
|
|
skip_to_semi(cfile);
|
|
return 0;
|
|
}
|
|
|
|
token = next_token(NULL, cfile);
|
|
if (token != '{') {
|
|
parse_warn("expecting '{'.");
|
|
if (token != ';')
|
|
skip_to_semi(cfile);
|
|
return 0;
|
|
}
|
|
|
|
lease = calloc(1, sizeof(*lease));
|
|
if (lease == NULL)
|
|
fatal("lease");
|
|
|
|
for (;;) {
|
|
token = peek_token(NULL, cfile);
|
|
if (token == EOF) {
|
|
parse_warn("unterminated lease.");
|
|
free_client_lease(lease);
|
|
break;
|
|
}
|
|
if (token == '}') {
|
|
token = next_token(NULL, cfile);
|
|
*lp = lease;
|
|
return 1;
|
|
}
|
|
parse_lease_decl(cfile, lease);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* lease-decl :==
|
|
* BOOTP SEMI
|
|
* | EPOCH number SEMI
|
|
* | EXPIRE <skip to semi> SEMI
|
|
* | FILENAME string SEMI
|
|
* | FIXED_ADDR ip_address SEMI
|
|
* | INTERFACE string SEMI
|
|
* | NEXT_SERVER string SEMI
|
|
* | OPTION option SEMI
|
|
* | REBIND <skip to semi> SEMI
|
|
* | RENEW <skip to semi> SEMI
|
|
* | SERVER_NAME string SEMI
|
|
* | SSID string SEMI
|
|
*/
|
|
void
|
|
parse_lease_decl(FILE *cfile, struct client_lease *lease)
|
|
{
|
|
char *val;
|
|
unsigned int len;
|
|
int i, token;
|
|
|
|
token = next_token(&val, cfile);
|
|
|
|
switch (token) {
|
|
case TOK_BOOTP:
|
|
/* 'bootp' is just a comment. See BOOTP_LEASE(). */
|
|
break;
|
|
case TOK_EPOCH:
|
|
if (parse_number(cfile, &lease->epoch, INT64_MIN, INT64_MAX)
|
|
== 0)
|
|
return;
|
|
break;
|
|
case TOK_EXPIRE:
|
|
/* 'expire' is just a comment. See 'epoch'. */
|
|
skip_to_semi(cfile);
|
|
return;
|
|
case TOK_FILENAME:
|
|
if (parse_string(cfile, &val) == 0)
|
|
return;
|
|
free(lease->filename);
|
|
lease->filename = val;
|
|
break;
|
|
case TOK_FIXED_ADDR:
|
|
if (parse_ip_addr(cfile, &lease->address) == 0)
|
|
return;
|
|
break;
|
|
case TOK_INTERFACE:
|
|
/* 'interface' is just a comment. */
|
|
skip_to_semi(cfile);
|
|
return;
|
|
case TOK_NEXT_SERVER:
|
|
if (parse_ip_addr(cfile, &lease->next_server) == 0)
|
|
return;
|
|
break;
|
|
case TOK_OPTION:
|
|
if (parse_option(cfile, &i, lease->options) == 0)
|
|
return;
|
|
break;
|
|
case TOK_REBIND:
|
|
case TOK_RENEW:
|
|
/* 'rebind' & 'renew' are just comments. See 'epoch'. */
|
|
skip_to_semi(cfile);
|
|
return;
|
|
case TOK_SERVER_NAME:
|
|
if (parse_string(cfile, &val) == 0)
|
|
return;
|
|
free(lease->server_name);
|
|
lease->server_name = val;
|
|
break;
|
|
case TOK_SSID:
|
|
if (parse_string(cfile, &val) == 0)
|
|
return;
|
|
len = strlen(val);
|
|
if (len > sizeof(lease->ssid)) {
|
|
free(val);
|
|
parse_warn("ssid > 32 bytes");
|
|
skip_to_semi(cfile);
|
|
return;
|
|
}
|
|
memset(lease->ssid, 0, sizeof(lease->ssid));
|
|
memcpy(lease->ssid, val, len);
|
|
free(val);
|
|
lease->ssid_len = len;
|
|
break;
|
|
default:
|
|
parse_warn("expecting lease declaration.");
|
|
skip_to_semi(cfile);
|
|
return;
|
|
}
|
|
|
|
parse_semi(cfile);
|
|
}
|
|
|
|
/*
|
|
* option :==
|
|
* option-name option-value
|
|
*
|
|
* option-value :==
|
|
* text
|
|
* | hex-octets
|
|
* | signed-32
|
|
* | unsigned-32
|
|
* | unsigned-16
|
|
* | unsigned-8
|
|
* | flag
|
|
* | ip-address
|
|
* | ip-address-array
|
|
* | ip-address-pair-array
|
|
* | uint16-array
|
|
* | cidr-ip-address-array
|
|
*/
|
|
int
|
|
parse_option(FILE *cfile, int *code, struct option_data *options)
|
|
{
|
|
uint8_t hunkbuf[1024], cidr[5], buf[4];
|
|
struct in_addr ip_addr;
|
|
uint8_t *dp;
|
|
char *fmt, *val;
|
|
long long number;
|
|
unsigned int hunkix = 0;
|
|
int i, freedp, len, token;
|
|
|
|
token = next_token(&val, cfile);
|
|
i = name_to_code(val);
|
|
if (i == DHO_END) {
|
|
parse_warn("expecting option name.");
|
|
skip_to_semi(cfile);
|
|
return 0;
|
|
}
|
|
|
|
/* Parse the option data. */
|
|
do {
|
|
for (fmt = code_to_format(i); *fmt != '\0'; fmt++) {
|
|
if (*fmt == 'A')
|
|
break;
|
|
freedp = 0;
|
|
switch (*fmt) {
|
|
case 'X':
|
|
if (peek_token(NULL, cfile) == TOK_STRING) {
|
|
if (parse_string(cfile, (char **)&dp)
|
|
== 0)
|
|
return 0;
|
|
len = strlen(dp);
|
|
} else if (parse_hex_octets(cfile, &len, &dp)
|
|
== 0)
|
|
return 0;
|
|
freedp = 1;
|
|
break;
|
|
case 't': /* Text string. */
|
|
if (parse_string(cfile, (char **)&dp) == 0)
|
|
return 0;
|
|
len = strlen(dp);
|
|
freedp = 1;
|
|
break;
|
|
case 'I': /* IP address. */
|
|
if (parse_ip_addr(cfile, &ip_addr) == 0)
|
|
return 0;
|
|
len = sizeof(ip_addr);
|
|
dp = (uint8_t *)&ip_addr;
|
|
break;
|
|
case 'l': /* Signed 32-bit integer. */
|
|
if (parse_number(cfile, &number, INT32_MIN,
|
|
INT32_MAX) == 0)
|
|
return 0;
|
|
number = htobe64(number);
|
|
len = sizeof(int32_t);
|
|
memcpy(buf, (char *)&number + (sizeof(number) - len), len);
|
|
dp = buf;
|
|
break;
|
|
case 'L': /* Unsigned 32-bit integer. */
|
|
if (parse_number(cfile, &number, 0, UINT32_MAX) == 0)
|
|
return 0;
|
|
number = htobe64(number);
|
|
len = sizeof(uint32_t);
|
|
memcpy(buf, (char *)&number + (sizeof(number) - len), len);
|
|
dp = buf;
|
|
break;
|
|
case 'S': /* Unsigned 16-bit integer. */
|
|
if (parse_number(cfile, &number, 0, UINT16_MAX) == 0)
|
|
return 0;
|
|
number = htobe64(number);
|
|
len = sizeof(uint16_t);
|
|
memcpy(buf, (char *)&number + (sizeof(number) - len), len);
|
|
dp = buf;
|
|
break;
|
|
case 'B': /* Unsigned 8-bit integer. */
|
|
if (parse_number(cfile, &number, 0, UINT8_MAX) == 0)
|
|
return 0;
|
|
buf[0] = number;
|
|
len = 1;
|
|
dp = buf;
|
|
break;
|
|
case 'f': /* Boolean flag. */
|
|
if (parse_boolean(cfile, buf) == 0)
|
|
return 0;
|
|
len = 1;
|
|
dp = buf;
|
|
break;
|
|
case 'C':
|
|
if (parse_cidr(cfile, cidr) == 0)
|
|
return 0;
|
|
len = 1 + (cidr[0] + 7) / 8;
|
|
dp = cidr;
|
|
break;
|
|
case 'D':
|
|
if (peek_token(NULL, cfile) == TOK_STRING) {
|
|
if (parse_domain_list(cfile, &len,
|
|
(char **)&dp) == 0)
|
|
return 0;
|
|
} else {
|
|
if (parse_hex_octets(cfile, &len, &dp)
|
|
== 0)
|
|
return 0;
|
|
val = rfc1035_as_string(dp, len);
|
|
free(dp);
|
|
dp = strdup(val);
|
|
if (dp == NULL)
|
|
fatal("RFC1035 hex octets");
|
|
len = strlen(dp);
|
|
}
|
|
freedp = 1;
|
|
break;
|
|
default:
|
|
log_warnx("%s: bad format %c in "
|
|
"parse_option_param", log_procname, *fmt);
|
|
skip_to_semi(cfile);
|
|
return 0;
|
|
}
|
|
if (dp != NULL && len > 0) {
|
|
if (hunkix + len > sizeof(hunkbuf)) {
|
|
if (freedp == 1)
|
|
free(dp);
|
|
parse_warn("option data buffer "
|
|
"overflow");
|
|
skip_to_semi(cfile);
|
|
return 0;
|
|
}
|
|
memcpy(&hunkbuf[hunkix], dp, len);
|
|
hunkix += len;
|
|
if (freedp == 1)
|
|
free(dp);
|
|
}
|
|
}
|
|
token = peek_token(NULL, cfile);
|
|
if (*fmt == 'A' && token == ',')
|
|
token = next_token(NULL, cfile);
|
|
} while (*fmt == 'A' && token == ',');
|
|
|
|
free(options[i].data);
|
|
options[i].data = malloc(hunkix);
|
|
if (options[i].data == NULL)
|
|
fatal("option data");
|
|
memcpy(options[i].data, hunkbuf, hunkix);
|
|
options[i].len = hunkix;
|
|
|
|
*code = i;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
parse_reject_statement(FILE *cfile)
|
|
{
|
|
struct in_addr addr;
|
|
struct reject_elem *elem;
|
|
|
|
if (parse_ip_addr(cfile, &addr) == 0)
|
|
return 0;
|
|
|
|
TAILQ_FOREACH(elem, &config->reject_list, next) {
|
|
if (elem->addr.s_addr == addr.s_addr)
|
|
return 1;
|
|
}
|
|
|
|
elem = malloc(sizeof(*elem));
|
|
if (elem == NULL)
|
|
fatal("reject address");
|
|
elem->addr = addr;
|
|
TAILQ_INSERT_TAIL(&config->reject_list, elem, next);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
apply_actions(uint8_t *actions)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < DHO_END; i++) {
|
|
switch (actions[i]) {
|
|
case ACTION_IGNORE:
|
|
config->default_actions[i] = ACTION_IGNORE;
|
|
free(config->defaults[i].data);
|
|
config->defaults[i].data = NULL;
|
|
config->defaults[i].len = 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
set_default_client_identifier(struct ether_addr *hwaddr)
|
|
{
|
|
struct option_data *opt;
|
|
|
|
/*
|
|
* Check both len && data so
|
|
*
|
|
* send dhcp-client-identifier "";
|
|
*
|
|
* can be used to suppress sending the default client
|
|
* identifier.
|
|
*/
|
|
opt = &config->send_options[DHO_DHCP_CLIENT_IDENTIFIER];
|
|
if (opt->len == 0 && opt->data == NULL) {
|
|
opt->data = calloc(1, ETHER_ADDR_LEN + 1);
|
|
if (opt->data == NULL)
|
|
fatal("default client identifier");
|
|
opt->data[0] = HTYPE_ETHER;
|
|
memcpy(&opt->data[1], hwaddr->ether_addr_octet,
|
|
ETHER_ADDR_LEN);
|
|
opt->len = ETHER_ADDR_LEN + 1;
|
|
}
|
|
}
|
|
|
|
void
|
|
set_default_hostname(void)
|
|
{
|
|
char hn[HOST_NAME_MAX + 1], *p;
|
|
struct option_data *opt;
|
|
int rslt;
|
|
|
|
/*
|
|
* Check both len && data so
|
|
*
|
|
* send host-name "";
|
|
*
|
|
* can be used to suppress sending the default host
|
|
* name.
|
|
*/
|
|
opt = &config->send_options[DHO_HOST_NAME];
|
|
if (opt->len == 0 && opt->data == NULL) {
|
|
rslt = gethostname(hn, sizeof(hn));
|
|
if (rslt == -1) {
|
|
log_warn("host-name");
|
|
return;
|
|
}
|
|
p = strchr(hn, '.');
|
|
if (p != NULL)
|
|
*p = '\0';
|
|
opt->data = strdup(hn);
|
|
if (opt->data == NULL)
|
|
fatal("default host-name");
|
|
opt->len = strlen(opt->data);
|
|
}
|
|
}
|