From: florian Date: Mon, 26 Jul 2021 09:26:36 +0000 (+0000) Subject: Implement possibility to send vendor class identifier (option 60) and X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=a41cc0822660d523ad2985508cda3a8c02cdede5;p=openbsd Implement possibility to send vendor class identifier (option 60) and client identifier (option 61). Some dhcp servers expect these options and refuse to hand out a lease without them. Need for vendor class identifier pointed out & tested by bket Need for client identifier pointed out by sthen Input & reads OK sthen (as part of a larger diff) OK kn (as part of a larger diff) --- diff --git a/sbin/dhcpleased/Makefile b/sbin/dhcpleased/Makefile index 8b18a7a7ff4..78069924947 100644 --- a/sbin/dhcpleased/Makefile +++ b/sbin/dhcpleased/Makefile @@ -1,9 +1,10 @@ -# $OpenBSD: Makefile,v 1.1 2021/02/26 16:16:37 florian Exp $ +# $OpenBSD: Makefile,v 1.2 2021/07/26 09:26:36 florian Exp $ PROG= dhcpleased SRCS= bpf.c checksum.c control.c dhcpleased.c engine.c frontend.c log.c +SRCS+= parse.y printconf.c -MAN= dhcpleased.8 +MAN= dhcpleased.8 dhcpleased.conf.5 #DEBUG= -g -DDEBUG=3 -O0 diff --git a/sbin/dhcpleased/control.c b/sbin/dhcpleased/control.c index e021a67a03a..9a713cb9913 100644 --- a/sbin/dhcpleased/control.c +++ b/sbin/dhcpleased/control.c @@ -1,4 +1,4 @@ -/* $OpenBSD: control.c,v 1.2 2021/03/02 04:10:07 jsg Exp $ */ +/* $OpenBSD: control.c,v 1.3 2021/07/26 09:26:36 florian Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -253,6 +253,9 @@ control_dispatch_imsg(int fd, short event, void *bula) break; switch (imsg.hdr.type) { + case IMSG_CTL_RELOAD: + frontend_imsg_compose_main(imsg.hdr.type, 0, NULL, 0); + break; case IMSG_CTL_LOG_VERBOSE: if (IMSG_DATA_SIZE(imsg) != sizeof(verbose)) break; diff --git a/sbin/dhcpleased/dhcpleased.8 b/sbin/dhcpleased/dhcpleased.8 index a56ea14c669..7ee3d8f92a1 100644 --- a/sbin/dhcpleased/dhcpleased.8 +++ b/sbin/dhcpleased/dhcpleased.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: dhcpleased.8,v 1.2 2021/02/26 17:14:25 tb Exp $ +.\" $OpenBSD: dhcpleased.8,v 1.3 2021/07/26 09:26:36 florian Exp $ .\" .\" Copyright (c) 2021 Florian Obser .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: February 26 2021 $ +.Dd $Mdocdate: July 26 2021 $ .Dt DHCPLEASED 8 .Os .Sh NAME @@ -22,7 +22,8 @@ .Nd a dynamic host configuration protocol daemon .Sh SYNOPSIS .Nm -.Op Fl dv +.Op Fl dnv +.Op Fl f Ar file .Op Fl s Ar socket .Sh DESCRIPTION .Nm @@ -57,6 +58,11 @@ If this option is specified, .Nm will run in the foreground and log to .Em stderr . +.It Fl f Ar file +Specify an alternative configuration file. +.It Fl n +Configtest mode. +Only check the configuration file for validity. .It Fl s Ar socket Use an alternate location for the default control socket. .It Fl v @@ -71,6 +77,10 @@ options increase the verbosity. .Ux Ns -domain socket used for communication with .Xr dhcpleasectl 8 . +.It Pa /etc/dhcpleased.conf +Default +.Nm +configuration file. .El .Sh SEE ALSO .Xr hostname.if 5 , diff --git a/sbin/dhcpleased/dhcpleased.c b/sbin/dhcpleased/dhcpleased.c index f4b2104a11b..36a4a21c21a 100644 --- a/sbin/dhcpleased/dhcpleased.c +++ b/sbin/dhcpleased/dhcpleased.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dhcpleased.c,v 1.17 2021/07/26 09:22:00 florian Exp $ */ +/* $OpenBSD: dhcpleased.c,v 1.18 2021/07/26 09:26:36 florian Exp $ */ /* * Copyright (c) 2017, 2021 Florian Obser @@ -86,9 +86,18 @@ static int main_imsg_send_ipc_sockets(struct imsgbuf *, struct imsgbuf *); int main_imsg_compose_frontend(int, int, void *, uint16_t); int main_imsg_compose_engine(int, int, void *, uint16_t); +#ifndef SMALL +int main_imsg_send_config(struct dhcpleased_conf *); +#endif /* SMALL */ +int main_reload(void); + static struct imsgev *iev_frontend; static struct imsgev *iev_engine; +#ifndef SMALL +struct dhcpleased_conf *main_conf; +#endif +char *conffile; pid_t frontend_pid; pid_t engine_pid; @@ -106,6 +115,14 @@ main_sig_handler(int sig, short event, void *arg) case SIGTERM: case SIGINT: main_shutdown(); + case SIGHUP: +#ifndef SMALL + if (main_reload() == -1) + log_warnx("configuration reload failed"); + else + log_debug("configuration reloaded"); +#endif /* SMALL */ + break; default: fatalx("unexpected signal"); } @@ -116,7 +133,7 @@ usage(void) { extern char *__progname; - fprintf(stderr, "usage: %s [-dv] [-s socket]\n", + fprintf(stderr, "usage: %s [-dnv] [-f file] [-s socket]\n", __progname); exit(1); } @@ -124,10 +141,10 @@ usage(void) int main(int argc, char *argv[]) { - struct event ev_sigint, ev_sigterm; + struct event ev_sigint, ev_sigterm, ev_sighup; int ch; int debug = 0, engine_flag = 0, frontend_flag = 0; - int verbose = 0; + int verbose = 0, no_action = 0; char *saved_argv0; int pipe_main2frontend[2]; int pipe_main2engine[2]; @@ -145,7 +162,7 @@ main(int argc, char *argv[]) if (saved_argv0 == NULL) saved_argv0 = "dhcpleased"; - while ((ch = getopt(argc, argv, "dEFs:v")) != -1) { + while ((ch = getopt(argc, argv, "dEFf:ns:v")) != -1) { switch (ch) { case 'd': debug = 1; @@ -156,6 +173,12 @@ main(int argc, char *argv[]) case 'F': frontend_flag = 1; break; + case 'f': + conffile = optarg; + break; + case 'n': + no_action = 1; + break; case 's': csock = optarg; break; @@ -177,6 +200,20 @@ main(int argc, char *argv[]) else if (frontend_flag) frontend(debug, verbose); +#ifndef SMALL + /* parse config file */ + if ((main_conf = parse_config(conffile)) == NULL) + exit(1); + + if (no_action) { + if (verbose) + print_config(main_conf); + else + fprintf(stderr, "configuration OK\n"); + exit(0); + } +#endif /* SMALL */ + /* Check for root privileges. */ if (geteuid()) errx(1, "need root privileges"); @@ -220,10 +257,11 @@ main(int argc, char *argv[]) /* Setup signal handler. */ signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL); signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL); + signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL); signal_add(&ev_sigint, NULL); signal_add(&ev_sigterm, NULL); + signal_add(&ev_sighup, NULL); signal(SIGPIPE, SIG_IGN); - signal(SIGHUP, SIG_IGN); /* Setup pipes to children. */ @@ -269,6 +307,13 @@ main(int argc, char *argv[]) warnx("control socket setup failed"); #endif /* SMALL */ + if (conffile != NULL) { + if (unveil(conffile, "r") == -1) + fatal("unveil %s", conffile); + } else { + if (unveil(_PATH_CONF_FILE, "r") == -1) + fatal("unveil %s", _PATH_CONF_FILE); + } if (unveil("/dev/bpf", "rw") == -1) fatal("unveil /dev/bpf"); @@ -288,6 +333,7 @@ main(int argc, char *argv[]) #ifndef SMALL if (control_fd != -1) main_imsg_compose_frontend(IMSG_CONTROLFD, control_fd, NULL, 0); + main_imsg_send_config(main_conf); #endif /* SMALL */ main_imsg_compose_frontend(IMSG_STARTUP, -1, NULL, 0); @@ -310,6 +356,10 @@ main_shutdown(void) msgbuf_clear(&iev_engine->ibuf.w); close(iev_engine->ibuf.fd); +#ifndef SMALL + config_clear(main_conf); +#endif /* SMALL */ + log_debug("waiting for children to terminate"); do { pid = wait(&status); @@ -420,6 +470,12 @@ main_dispatch_frontend(int fd, short event, void *bula) open_bpfsock(if_index); break; #ifndef SMALL + case IMSG_CTL_RELOAD: + if (main_reload() == -1) + log_warnx("configuration reload failed"); + else + log_warnx("configuration reloaded"); + break; case IMSG_CTL_LOG_VERBOSE: if (IMSG_DATA_SIZE(imsg) != sizeof(verbose)) fatalx("%s: IMSG_CTL_LOG_VERBOSE wrong length: " @@ -622,6 +678,55 @@ main_imsg_send_ipc_sockets(struct imsgbuf *frontend_buf, return (0); } +#ifndef SMALL +int +main_reload(void) +{ + struct dhcpleased_conf *xconf; + + if ((xconf = parse_config(conffile)) == NULL) + return (-1); + + if (main_imsg_send_config(xconf) == -1) + return (-1); + + merge_config(main_conf, xconf); + + return (0); +} + +int +main_imsg_send_config(struct dhcpleased_conf *xconf) +{ + struct iface_conf *iface_conf; + + main_imsg_compose_frontend(IMSG_RECONF_CONF, -1, NULL, 0); + main_imsg_compose_engine(IMSG_RECONF_CONF, -1, NULL, 0); + + /* Send the interface list to the frontend & engine. */ + SIMPLEQ_FOREACH(iface_conf, &xconf->iface_list, entry) { + main_imsg_compose_frontend(IMSG_RECONF_IFACE, -1, iface_conf, + sizeof(*iface_conf)); + main_imsg_compose_engine(IMSG_RECONF_IFACE, -1, iface_conf, + sizeof(*iface_conf)); + main_imsg_compose_frontend(IMSG_RECONF_VC_ID, -1, + iface_conf->vc_id, iface_conf->vc_id_len); + main_imsg_compose_engine(IMSG_RECONF_VC_ID, -1, + iface_conf->vc_id, iface_conf->vc_id_len); + main_imsg_compose_frontend(IMSG_RECONF_C_ID, -1, + iface_conf->c_id, iface_conf->c_id_len); + main_imsg_compose_engine(IMSG_RECONF_C_ID, -1, + iface_conf->c_id, iface_conf->c_id_len); + } + + /* Config is now complete. */ + main_imsg_compose_frontend(IMSG_RECONF_END, -1, NULL, 0); + main_imsg_compose_engine(IMSG_RECONF_END, -1, NULL, 0); + + return (0); +} +#endif /* SMALL */ + void configure_interface(struct imsg_configure_interface *imsg) { @@ -1093,3 +1198,50 @@ read_lease_file(struct imsg_ifinfo *imsg_ifinfo) read(fd, imsg_ifinfo->lease, sizeof(imsg_ifinfo->lease) - 1); close(fd); } + +#ifndef SMALL +void +merge_config(struct dhcpleased_conf *conf, struct dhcpleased_conf *xconf) +{ + struct iface_conf *iface_conf; + + /* Remove & discard existing interfaces. */ + while ((iface_conf = SIMPLEQ_FIRST(&conf->iface_list)) != NULL) { + SIMPLEQ_REMOVE_HEAD(&conf->iface_list, entry); + free(iface_conf->vc_id); + free(iface_conf->c_id); + free(iface_conf); + } + + /* Add new interfaces. */ + SIMPLEQ_CONCAT(&conf->iface_list, &xconf->iface_list); + + free(xconf); +} + +struct dhcpleased_conf * +config_new_empty(void) +{ + struct dhcpleased_conf *xconf; + + xconf = calloc(1, sizeof(*xconf)); + if (xconf == NULL) + fatal(NULL); + + SIMPLEQ_INIT(&xconf->iface_list); + + return (xconf); +} + +void +config_clear(struct dhcpleased_conf *conf) +{ + struct dhcpleased_conf *xconf; + + /* Merge current config with an empty config. */ + xconf = config_new_empty(); + merge_config(conf, xconf); + + free(conf); +} +#endif /* SMALL */ diff --git a/sbin/dhcpleased/dhcpleased.conf.5 b/sbin/dhcpleased/dhcpleased.conf.5 new file mode 100644 index 00000000000..c86a5493236 --- /dev/null +++ b/sbin/dhcpleased/dhcpleased.conf.5 @@ -0,0 +1,87 @@ +.\" $OpenBSD: dhcpleased.conf.5,v 1.1 2021/07/26 09:26:36 florian Exp $ +.\" +.\" Copyright (c) 2018, 2021 Florian Obser +.\" Copyright (c) 2005 Esben Norby +.\" Copyright (c) 2004 Claudio Jeker +.\" Copyright (c) 2003, 2004 Henning Brauer +.\" Copyright (c) 2002 Daniel Hartmeier +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: July 26 2021 $ +.Dt DHCPLEASED.CONF 5 +.Os +.Sh NAME +.Nm dhcpleased.conf +.Nd dynamic host configuration protocol client daemon configuration file +.Sh DESCRIPTION +The +.Xr dhcpleased 8 +daemon is a dynamic host configuration protocol client daemon. +.Pp +The +.Nm +config file is divided into the following main sections: +.Bl -tag -width xxxx +.It Sy Macros +User-defined variables may be defined and used later, simplifying the +configuration file. +.It Sy Interfaces +This section defines interfaces for which default options need to be overwritten. +.El +.Sh MACROS +Macros can be defined that will later be expanded in context. +Macro names must start with a letter, digit, or underscore, +and may contain any of those characters. +Macro names may not be reserved words (for example, +.Ic interface ) +Macros are not expanded inside quotes. +.Sh INTERFACES +A list of interfaces to overwrite defaults: +.Bd -unfilled -offset indent +.Ic interface Ar name { Oo option list Oc } +.Ed +.Pp +.Ic interface +options are as follows: +.Bl -tag -width Ds +.It Ic send client id Ar client-id +Send the dhcp client identifier option with a value of +.Ar client-id . +If +.Ar client-id +consists of a series of octets of two-digit hexadecimal numbers separated by +colons the first octet is used as the type and the rest as value. +The MAC address 00:53:FF:AA:BB:CC would be configured as +.Bd -literal -offset indent +send client id "01:00:53:FF:AA:BB:CC" +.Ed +.Pp +Otherwise the string +.Ar client-id +is send verbatim with type zero. +The default is to send the interface's MAC address as client identifier. +.It Ic send vendor class id Ar vendor-class-id +Send the dhcp vendor class identifier option with a value of +.Ar vendor-class-id . +The default is to not send a vendor class identifier. +.El +.Sh FILES +.Bl -tag -width /etc/dhcpleased.conf -compact +.It Pa /etc/dhcpleased.conf +.Xr dhcpleased 8 +configuration file. +.El +.Sh SEE ALSO +.Xr dhcpleasectl 8 , +.Xr dhcpleased 8 , diff --git a/sbin/dhcpleased/dhcpleased.h b/sbin/dhcpleased/dhcpleased.h index ae81baa7bb0..e65300abf29 100644 --- a/sbin/dhcpleased/dhcpleased.h +++ b/sbin/dhcpleased/dhcpleased.h @@ -1,4 +1,4 @@ -/* $OpenBSD: dhcpleased.h,v 1.7 2021/07/21 03:53:50 kn Exp $ */ +/* $OpenBSD: dhcpleased.h,v 1.8 2021/07/26 09:26:36 florian Exp $ */ /* * Copyright (c) 2017, 2021 Florian Obser @@ -19,6 +19,7 @@ */ #define _PATH_LOCKFILE "/dev/dhcpleased.lock" +#define _PATH_CONF_FILE "/etc/dhcpleased.conf" #define _PATH_DHCPLEASED_SOCKET "/dev/dhcpleased.sock" #define DHCPLEASED_USER "_dhcp" #define DHCPLEASED_RTA_LABEL "dhcpleased" @@ -42,6 +43,7 @@ #define DHCP_COOKIE {99, 130, 83, 99} /* Possible values for hardware type (htype) field. */ +#define HTYPE_NONE 0 #define HTYPE_ETHER 1 #define HTYPE_IPSEC_TUNNEL 31 @@ -189,7 +191,13 @@ enum imsg_type { IMSG_CTL_LOG_VERBOSE, IMSG_CTL_SHOW_INTERFACE_INFO, IMSG_CTL_SEND_REQUEST, + IMSG_CTL_RELOAD, IMSG_CTL_END, + IMSG_RECONF_CONF, + IMSG_RECONF_IFACE, + IMSG_RECONF_VC_ID, + IMSG_RECONF_C_ID, + IMSG_RECONF_END, #endif /* SMALL */ IMSG_SEND_DISCOVER, IMSG_SEND_REQUEST, @@ -230,6 +238,19 @@ struct ctl_engine_info { uint32_t rebinding_time; }; +struct iface_conf { + SIMPLEQ_ENTRY(iface_conf) entry; + char name[IF_NAMESIZE]; + uint8_t *vc_id; + int vc_id_len; + uint8_t *c_id; + int c_id_len; +}; + +struct dhcpleased_conf { + SIMPLEQ_HEAD(iface_conf_head, iface_conf) iface_list; +}; + #endif /* SMALL */ struct imsg_ifinfo { @@ -270,11 +291,26 @@ struct imsg_req_request { }; /* dhcpleased.c */ -void imsg_event_add(struct imsgev *); -int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t, - int, void *, uint16_t); +void imsg_event_add(struct imsgev *); +int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, + pid_t, int, void *, uint16_t); #ifndef SMALL +void config_clear(struct dhcpleased_conf *); +struct dhcpleased_conf *config_new_empty(void); +void merge_config(struct dhcpleased_conf *, struct + dhcpleased_conf *); const char *sin_to_str(struct sockaddr_in *); + +/* frontend.c */ +struct iface_conf *find_iface_conf(struct iface_conf_head *, char *); + +/* printconf.c */ +void print_config(struct dhcpleased_conf *); + +/* parse.y */ +struct dhcpleased_conf *parse_config(char *); +int cmdline_symset(char *); #else #define sin_to_str(x...) "" #endif /* SMALL */ + diff --git a/sbin/dhcpleased/engine.c b/sbin/dhcpleased/engine.c index b89cf162155..060d0de5a2c 100644 --- a/sbin/dhcpleased/engine.c +++ b/sbin/dhcpleased/engine.c @@ -1,4 +1,4 @@ -/* $OpenBSD: engine.c,v 1.21 2021/07/25 12:35:58 florian Exp $ */ +/* $OpenBSD: engine.c,v 1.22 2021/07/26 09:26:36 florian Exp $ */ /* * Copyright (c) 2017, 2021 Florian Obser @@ -145,6 +145,10 @@ int engine_imsg_compose_main(int, pid_t, void *, uint16_t); void log_dhcp_hdr(struct dhcp_hdr *); const char *dhcp_message_type2str(uint8_t); +#ifndef SMALL +struct dhcpleased_conf *engine_conf; +#endif /* SMALL */ + static struct imsgev *iev_frontend; static struct imsgev *iev_main; int64_t proposal_id; @@ -172,6 +176,10 @@ engine(int debug, int verbose) struct event ev_sigint, ev_sigterm; struct passwd *pw; +#ifndef SMALL + engine_conf = config_new_empty(); +#endif /* SMALL */ + log_init(debug, LOG_DAEMON); log_setverbose(verbose); @@ -375,12 +383,16 @@ engine_dispatch_frontend(int fd, short event, void *bula) void engine_dispatch_main(int fd, short event, void *bula) { - struct imsg imsg; - struct imsgev *iev = bula; - struct imsgbuf *ibuf = &iev->ibuf; - struct imsg_ifinfo imsg_ifinfo; - ssize_t n; - int shut = 0; +#ifndef SMALL + static struct dhcpleased_conf *nconf; + static struct iface_conf *iface_conf; +#endif /* SMALL */ + struct imsg imsg; + struct imsgev *iev = bula; + struct imsgbuf *ibuf = &iev->ibuf; + struct imsg_ifinfo imsg_ifinfo; + ssize_t n; + int shut = 0; if (event & EV_READ) { if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) @@ -439,6 +451,69 @@ engine_dispatch_main(int fd, short event, void *bula) memcpy(&imsg_ifinfo, imsg.data, sizeof(imsg_ifinfo)); engine_update_iface(&imsg_ifinfo); break; +#ifndef SMALL + case IMSG_RECONF_CONF: + if (nconf != NULL) + fatalx("%s: IMSG_RECONF_CONF already in " + "progress", __func__); + if ((nconf = malloc(sizeof(struct dhcpleased_conf))) == + NULL) + fatal(NULL); + SIMPLEQ_INIT(&nconf->iface_list); + break; + case IMSG_RECONF_IFACE: + if (IMSG_DATA_SIZE(imsg) != sizeof(struct + iface_conf)) + fatalx("%s: IMSG_RECONF_IFACE wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + if ((iface_conf = malloc(sizeof(struct iface_conf))) + == NULL) + fatal(NULL); + memcpy(iface_conf, imsg.data, sizeof(struct + iface_conf)); + iface_conf->vc_id = NULL; + iface_conf->vc_id_len = 0; + iface_conf->c_id = NULL; + iface_conf->c_id_len = 0; + SIMPLEQ_INSERT_TAIL(&nconf->iface_list, + iface_conf, entry); + break; + case IMSG_RECONF_VC_ID: + if (iface_conf == NULL) + fatal("IMSG_RECONF_VC_ID without " + "IMSG_RECONF_IFACE"); + if (IMSG_DATA_SIZE(imsg) > 255 + 2) + fatalx("%s: IMSG_RECONF_VC_ID wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + if ((iface_conf->vc_id = malloc(IMSG_DATA_SIZE(imsg))) + == NULL) + fatal(NULL); + memcpy(iface_conf->vc_id, imsg.data, + IMSG_DATA_SIZE(imsg)); + iface_conf->vc_id_len = IMSG_DATA_SIZE(imsg); + break; + case IMSG_RECONF_C_ID: + if (iface_conf == NULL) + fatal("IMSG_RECONF_C_ID without " + "IMSG_RECONF_IFACE"); + if (IMSG_DATA_SIZE(imsg) > 255 + 2) + fatalx("%s: IMSG_RECONF_C_ID wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + if ((iface_conf->c_id = malloc(IMSG_DATA_SIZE(imsg))) + == NULL) + fatal(NULL); + memcpy(iface_conf->c_id, imsg.data, + IMSG_DATA_SIZE(imsg)); + iface_conf->c_id_len = IMSG_DATA_SIZE(imsg); + break; + case IMSG_RECONF_END: + if (nconf == NULL) + fatalx("%s: IMSG_RECONF_END without " + "IMSG_RECONF_CONF", __func__); + merge_config(engine_conf, nconf); + nconf = NULL; + break; +#endif /* SMALL */ default: log_debug("%s: unexpected imsg %d", __func__, imsg.hdr.type); @@ -600,6 +675,9 @@ parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp) { static uint8_t cookie[] = DHCP_COOKIE; static struct ether_addr bcast_mac; +#ifndef SMALL + struct iface_conf *iface_conf; +#endif /* SMALL */ struct ether_header *eh; struct ether_addr ether_src, ether_dst; struct ip *ip; @@ -626,6 +704,12 @@ parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp) if (bcast_mac.ether_addr_octet[0] == 0) memset(bcast_mac.ether_addr_octet, 0xff, ETHER_ADDR_LEN); + if_name = if_indextoname(iface->if_index, ifnamebuf); + +#ifndef SMALL + iface_conf = find_iface_conf(&engine_conf->iface_list, if_name); +#endif /* SMALL*/ + memset(hbuf_src, 0, sizeof(hbuf_src)); memset(hbuf_dst, 0, sizeof(hbuf_dst)); @@ -930,17 +1014,35 @@ parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp) break; case DHO_DHCP_CLIENT_IDENTIFIER: /* the server is supposed to echo this back to us */ - if (dho_len != 1 + sizeof(iface->hw_address)) - goto wrong_length; - if (*p != HTYPE_ETHER) { - log_warn("DHO_DHCP_CLIENT_IDENTIFIER: wrong " - "type"); - return; - } - if (memcmp(p + 1, &iface->hw_address, - sizeof(iface->hw_address)) != 0) { - log_warn("wrong DHO_DHCP_CLIENT_IDENTIFIER"); - return; +#ifndef SMALL + if (iface_conf != NULL && iface_conf->c_id_len > 0) { + if (dho_len != iface_conf->c_id[1]) { + log_warnx("wrong " + "DHO_DHCP_CLIENT_IDENTIFIER"); + return; + } + if (memcmp(p, &iface_conf->c_id[2], dho_len) != + 0) { + log_warnx("wrong " + "DHO_DHCP_CLIENT_IDENTIFIER"); + return; + } + } else +#endif /* SMALL */ + { + if (dho_len != 1 + sizeof(iface->hw_address)) + goto wrong_length; + if (*p != HTYPE_ETHER) { + log_warnx("DHO_DHCP_CLIENT_IDENTIFIER: " + "wrong type"); + return; + } + if (memcmp(p + 1, &iface->hw_address, + sizeof(iface->hw_address)) != 0) { + log_warnx("wrong " + "DHO_DHCP_CLIENT_IDENTIFIER"); + return; + } } p += dho_len; rem -= dho_len; @@ -1024,7 +1126,6 @@ parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp) log_warnx("%s: %lu bytes garbage data from %s", __func__, rem, from); - if_name = if_indextoname(iface->if_index, ifnamebuf); log_debug("%s on %s from %s/%s to %s/%s", dhcp_message_type2str(dhcp_message_type), if_name == NULL ? "?" : if_name, from, hbuf_src, to, hbuf_dst); diff --git a/sbin/dhcpleased/frontend.c b/sbin/dhcpleased/frontend.c index 1e5284eaf13..9246960512e 100644 --- a/sbin/dhcpleased/frontend.c +++ b/sbin/dhcpleased/frontend.c @@ -1,4 +1,4 @@ -/* $OpenBSD: frontend.c,v 1.13 2021/07/12 15:09:18 beck Exp $ */ +/* $OpenBSD: frontend.c,v 1.14 2021/07/26 09:26:36 florian Exp $ */ /* * Copyright (c) 2017, 2021 Florian Obser @@ -90,14 +90,15 @@ int get_xflags(char *); struct iface *get_iface_by_id(uint32_t); void remove_iface(uint32_t); void set_bpfsock(int, uint32_t); -ssize_t build_packet(uint8_t, uint32_t, struct ether_addr *, struct - in_addr *, struct in_addr *); +ssize_t build_packet(uint8_t, char *, uint32_t, struct ether_addr *, + struct in_addr *, struct in_addr *); void send_discover(struct iface *); void send_request(struct iface *); void bpf_send_packet(struct iface *, uint8_t *, ssize_t); void udp_send_packet(struct iface *, uint8_t *, ssize_t); LIST_HEAD(, iface) interfaces; +struct dhcpleased_conf *frontend_conf; static struct imsgev *iev_main; static struct imsgev *iev_engine; struct event ev_route; @@ -128,6 +129,10 @@ frontend(int debug, int verbose) struct event ev_sigint, ev_sigterm; struct passwd *pw; +#ifndef SMALL + frontend_conf = config_new_empty(); +#endif /* SMALL */ + log_init(debug, LOG_DAEMON); log_setverbose(verbose); @@ -192,6 +197,10 @@ frontend_shutdown(void) msgbuf_clear(&iev_main->ibuf.w); close(iev_main->ibuf.fd); +#ifndef SMALL + config_clear(frontend_conf); +#endif /* SMALL */ + free(iev_engine); free(iev_main); @@ -218,12 +227,14 @@ frontend_imsg_compose_engine(int type, uint32_t peerid, pid_t pid, void frontend_dispatch_main(int fd, short event, void *bula) { - struct imsg imsg; - struct imsgev *iev = bula; - struct imsgbuf *ibuf = &iev->ibuf; - struct iface *iface; - ssize_t n; - int shut = 0, bpfsock, if_index, udpsock; + static struct dhcpleased_conf *nconf; + static struct iface_conf *iface_conf; + struct imsg imsg; + struct imsgev *iev = bula; + struct imsgbuf *ibuf = &iev->ibuf; + struct iface *iface; + ssize_t n; + int shut = 0, bpfsock, if_index, udpsock; if (event & EV_READ) { if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) @@ -322,7 +333,68 @@ frontend_dispatch_main(int fd, short event, void *bula) case IMSG_STARTUP: frontend_startup(); break; -#ifndef SMALL +#ifndef SMALL + case IMSG_RECONF_CONF: + if (nconf != NULL) + fatalx("%s: IMSG_RECONF_CONF already in " + "progress", __func__); + if ((nconf = malloc(sizeof(struct dhcpleased_conf))) == + NULL) + fatal(NULL); + SIMPLEQ_INIT(&nconf->iface_list); + break; + case IMSG_RECONF_IFACE: + if (IMSG_DATA_SIZE(imsg) != sizeof(struct + iface_conf)) + fatalx("%s: IMSG_RECONF_IFACE wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + if ((iface_conf = malloc(sizeof(struct iface_conf))) + == NULL) + fatal(NULL); + memcpy(iface_conf, imsg.data, sizeof(struct + iface_conf)); + iface_conf->vc_id = NULL; + iface_conf->vc_id_len = 0; + iface_conf->c_id = NULL; + iface_conf->c_id_len = 0; + SIMPLEQ_INSERT_TAIL(&nconf->iface_list, + iface_conf, entry); + break; + case IMSG_RECONF_VC_ID: + if (iface_conf == NULL) + fatal("IMSG_RECONF_VC_ID without " + "IMSG_RECONF_IFACE"); + if (IMSG_DATA_SIZE(imsg) > 255 + 2) + fatalx("%s: IMSG_RECONF_VC_ID wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + if ((iface_conf->vc_id = malloc(IMSG_DATA_SIZE(imsg))) + == NULL) + fatal(NULL); + memcpy(iface_conf->vc_id, imsg.data, + IMSG_DATA_SIZE(imsg)); + iface_conf->vc_id_len = IMSG_DATA_SIZE(imsg); + break; + case IMSG_RECONF_C_ID: + if (iface_conf == NULL) + fatal("IMSG_RECONF_C_ID without " + "IMSG_RECONF_IFACE"); + if (IMSG_DATA_SIZE(imsg) > 255 + 2) + fatalx("%s: IMSG_RECONF_C_ID wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + if ((iface_conf->c_id = malloc(IMSG_DATA_SIZE(imsg))) + == NULL) + fatal(NULL); + memcpy(iface_conf->c_id, imsg.data, + IMSG_DATA_SIZE(imsg)); + iface_conf->c_id_len = IMSG_DATA_SIZE(imsg); + break; + case IMSG_RECONF_END: + if (nconf == NULL) + fatalx("%s: IMSG_RECONF_END without " + "IMSG_RECONF_CONF", __func__); + merge_config(frontend_conf, nconf); + nconf = NULL; + break; case IMSG_CONTROLFD: if ((fd = imsg.fd) == -1) fatalx("%s: expected to receive imsg " @@ -769,8 +841,9 @@ bpf_receive(int fd, short events, void *arg) } ssize_t -build_packet(uint8_t message_type, uint32_t xid, struct ether_addr *hw_address, - struct in_addr *requested_ip, struct in_addr *server_identifier) +build_packet(uint8_t message_type, char *if_name, uint32_t xid, + struct ether_addr *hw_address, struct in_addr *requested_ip, + struct in_addr *server_identifier) { static uint8_t dhcp_cookie[] = DHCP_COOKIE; static uint8_t dhcp_message_type[] = {DHO_DHCP_MESSAGE_TYPE, 1, @@ -786,10 +859,17 @@ build_packet(uint8_t message_type, uint32_t xid, struct ether_addr *hw_address, 4, 0, 0, 0, 0}; static uint8_t dhcp_server_identifier[] = {DHO_DHCP_SERVER_IDENTIFIER, 4, 0, 0, 0, 0}; - struct dhcp_hdr *hdr; - ssize_t len; - uint8_t *p; - char *c; +#ifndef SMALL + struct iface_conf *iface_conf; +#endif /* SMALL */ + struct dhcp_hdr *hdr; + ssize_t len; + uint8_t *p; + char *c; + +#ifndef SMALL + iface_conf = find_iface_conf(&frontend_conf->iface_list, if_name); +#endif /* SMALL */ memset(dhcp_packet, 0, sizeof(dhcp_packet)); dhcp_message_type[2] = message_type; @@ -814,9 +894,26 @@ build_packet(uint8_t message_type, uint32_t xid, struct ether_addr *hw_address, memcpy(p, dhcp_hostname, dhcp_hostname[1] + 2); p += dhcp_hostname[1] + 2; } - memcpy(dhcp_client_id + 3, hw_address, sizeof(*hw_address)); - memcpy(p, dhcp_client_id, sizeof(dhcp_client_id)); - p += sizeof(dhcp_client_id); + +#ifndef SMALL + if (iface_conf != NULL) { + if (iface_conf->c_id_len > 0) { + /* XXX check space */ + memcpy(p, iface_conf->c_id, iface_conf->c_id_len); + p += iface_conf->c_id_len; + } + if (iface_conf->vc_id_len > 0) { + /* XXX check space */ + memcpy(p, iface_conf->vc_id, iface_conf->vc_id_len); + p += iface_conf->vc_id_len; + } + } else +#endif /* SMALL */ + { + memcpy(dhcp_client_id + 3, hw_address, sizeof(*hw_address)); + memcpy(p, dhcp_client_id, sizeof(dhcp_client_id)); + p += sizeof(dhcp_client_id); + } memcpy(p, dhcp_req_list, sizeof(dhcp_req_list)); p += sizeof(dhcp_req_list); @@ -851,8 +948,8 @@ build_packet(uint8_t message_type, uint32_t xid, struct ether_addr *hw_address, void send_discover(struct iface *iface) { - ssize_t pkt_len; - char ifnamebuf[IF_NAMESIZE], *if_name; + ssize_t pkt_len; + char ifnamebuf[IF_NAMESIZE], *if_name; if (!event_initialized(&iface->bpfev.ev)) { iface->send_discover = 1; @@ -863,7 +960,7 @@ send_discover(struct iface *iface) if_name = if_indextoname(iface->ifinfo.if_index, ifnamebuf); log_debug("DHCPDISCOVER on %s", if_name == NULL ? "?" : if_name); - pkt_len = build_packet(DHCPDISCOVER, iface->xid, + pkt_len = build_packet(DHCPDISCOVER, if_name, iface->xid, &iface->ifinfo.hw_address, &iface->requested_ip, NULL); bpf_send_packet(iface, dhcp_packet, pkt_len); } @@ -871,13 +968,13 @@ send_discover(struct iface *iface) void send_request(struct iface *iface) { - ssize_t pkt_len; - char ifnamebuf[IF_NAMESIZE], *if_name; + ssize_t pkt_len; + char ifnamebuf[IF_NAMESIZE], *if_name; if_name = if_indextoname(iface->ifinfo.if_index, ifnamebuf); log_debug("DHCPREQUEST on %s", if_name == NULL ? "?" : if_name); - pkt_len = build_packet(DHCPREQUEST, iface->xid, + pkt_len = build_packet(DHCPREQUEST, if_name, iface->xid, &iface->ifinfo.hw_address, &iface->requested_ip, &iface->server_identifier); if (iface->dhcp_server.s_addr != INADDR_ANY) @@ -1015,3 +1112,20 @@ set_bpfsock(int bpfsock, uint32_t if_index) send_discover(iface); } } + +#ifndef SMALL +struct iface_conf* +find_iface_conf(struct iface_conf_head *head, char *if_name) +{ + struct iface_conf *iface_conf; + + if (if_name == NULL) + return (NULL); + + SIMPLEQ_FOREACH(iface_conf, head, entry) { + if (strcmp(iface_conf->name, if_name) == 0) + return iface_conf; + } + return (NULL); +} +#endif /* SMALL */ diff --git a/sbin/dhcpleased/parse.y b/sbin/dhcpleased/parse.y new file mode 100644 index 00000000000..21b2383a44c --- /dev/null +++ b/sbin/dhcpleased/parse.y @@ -0,0 +1,804 @@ +/* $OpenBSD: parse.y,v 1.1 2021/07/26 09:26:36 florian Exp $ */ + +/* + * Copyright (c) 2018 Florian Obser + * Copyright (c) 2004, 2005 Esben Norby + * Copyright (c) 2004 Ryan McBride + * Copyright (c) 2002, 2003, 2004 Henning Brauer + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +%{ +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "dhcpleased.h" +#include "frontend.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + size_t ungetpos; + size_t ungetsize; + u_char *ungetbuf; + int eof_reached; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *, int); +int popfile(void); +int check_file_secrecy(int, const char *); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +int kw_cmp(const void *, const void *); +int lookup(char *); +int igetc(void); +int lgetc(int); +void lungetc(int); +int findeol(void); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; + +int symset(const char *, const char *, int); +char *symget(const char *); + +static struct dhcpleased_conf *conf; +static int errors; + +static struct iface_conf *iface_conf; + +struct iface_conf *conf_get_iface(char *); + +typedef struct { + union { + int64_t number; + char *string; + } v; + int lineno; +} YYSTYPE; + +%} + +%token DHCP_IFACE ERROR SEND VENDOR CLASS ID CLIENT + +%token STRING +%token NUMBER +%type string + +%% + +grammar : /* empty */ + | grammar '\n' + | grammar varset '\n' + | grammar dhcp_iface '\n' + | grammar error '\n' { file->errors++; } + ; + +string : string STRING { + if (asprintf(&$$, "%s %s", $1, $2) == -1) { + free($1); + free($2); + yyerror("string: asprintf"); + YYERROR; + } + free($1); + free($2); + } + | STRING + ; + +varset : STRING '=' string { + char *s = $1; + if (log_getverbose() == 1) + printf("%s = \"%s\"\n", $1, $3); + while (*s++) { + if (isspace((unsigned char)*s)) { + yyerror("macro name cannot contain " + "whitespace"); + free($1); + free($3); + YYERROR; + } + } + if (symset($1, $3, 0) == -1) + fatal("cannot store variable"); + free($1); + free($3); + } + ; + +optnl : '\n' optnl /* zero or more newlines */ + | /*empty*/ + ; + +nl : '\n' optnl /* one or more newlines */ + ; + +dhcp_iface : DHCP_IFACE STRING { + iface_conf = conf_get_iface($2); + } iface_block { + iface_conf = NULL; + } + ; + +iface_block : '{' optnl ifaceopts_l '}' + | '{' optnl '}' + | /* empty */ + ; + +ifaceopts_l : ifaceopts_l ifaceoptsl nl + | ifaceoptsl optnl + ; + +ifaceoptsl : SEND VENDOR CLASS ID STRING { + ssize_t len; + char buf[256]; + + if (iface_conf->vc_id != NULL) { + yyerror("vendor class id already set"); + YYERROR; + } + + len = strnunvis(buf, $5, sizeof(buf)); + free($5); + + if (len == -1) { + yyerror("invalid vendor class id"); + YYERROR; + } + if ((size_t)len >= sizeof(buf)) { + yyerror("vendor class id too long"); + YYERROR; + } + + iface_conf->vc_id_len = 2 + strlen(buf); + iface_conf->vc_id = malloc(iface_conf->vc_id_len); + if (iface_conf->vc_id == NULL) { + yyerror("malloc"); + YYERROR; + } + iface_conf->vc_id[0] = DHO_DHCP_CLASS_IDENTIFIER; + iface_conf->vc_id[1] = iface_conf->vc_id_len - 2; + memcpy(&iface_conf->vc_id[2], buf, + iface_conf->vc_id_len - 2); + } + | SEND CLIENT ID STRING { + size_t i; + ssize_t len; + int not_hex = 0, val; + char buf[256], *hex, *p, excess; + + if (iface_conf->c_id != NULL) { + yyerror("client-id already set"); + YYERROR; + } + + /* parse as hex string including the type byte */ + if ((hex = strdup($4)) == NULL) { + free($4); + yyerror("malloc"); + YYERROR; + } + for (i = 0; (p = strsep(&hex, ":")) != NULL && i < + sizeof(buf); ) { + if (sscanf(p, "%x%c", &val, &excess) != 1 || + val < 0 || val > 0xff) { + not_hex = 1; + break; + } + buf[i++] = (val & 0xff); + } + if (p != NULL && i == sizeof(buf)) + not_hex = 1; + free(hex); + + if (not_hex) { + len = strnunvis(buf, $4, sizeof(buf)); + free($4); + + if (len == -1) { + yyerror("invalid client-id"); + YYERROR; + } + if ((size_t)len >= sizeof(buf)) { + yyerror("client-id too long"); + YYERROR; + } + iface_conf->c_id_len = 3 + strlen(buf); + iface_conf->c_id = malloc(iface_conf->c_id_len); + if (iface_conf->c_id == NULL) { + yyerror("malloc"); + YYERROR; + } + iface_conf->c_id[2] = HTYPE_NONE; + memcpy(&iface_conf->c_id[3], buf, + iface_conf->c_id_len - 3); + } else { + free($4); + iface_conf->c_id_len = 2 + i; + iface_conf->c_id = malloc(iface_conf->c_id_len); + if (iface_conf->c_id == NULL) { + yyerror("malloc"); + YYERROR; + } + memcpy(&iface_conf->c_id[2], buf, + iface_conf->c_id_len - 2); + } + iface_conf->c_id[0] = DHO_DHCP_CLIENT_IDENTIFIER; + iface_conf->c_id[1] = iface_conf->c_id_len - 2; + } + ; +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + + file->errors++; + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) + fatalx("yyerror vasprintf"); + va_end(ap); + logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); + free(msg); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* This has to be sorted always. */ + static const struct keywords keywords[] = { + {"class", CLASS}, + {"client", CLIENT}, + {"id", ID}, + {"interface", DHCP_IFACE}, + {"send", SEND}, + {"vendor", VENDOR}, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define START_EXPAND 1 +#define DONE_EXPAND 2 + +static int expanding; + +int +igetc(void) +{ + int c; + + while (1) { + if (file->ungetpos > 0) + c = file->ungetbuf[--file->ungetpos]; + else + c = getc(file->stream); + + if (c == START_EXPAND) + expanding = 1; + else if (c == DONE_EXPAND) + expanding = 0; + else + break; + } + return (c); +} + +int +lgetc(int quotec) +{ + int c, next; + + if (quotec) { + if ((c = igetc()) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = igetc()) == '\\') { + next = igetc(); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + if (c == EOF) { + /* + * Fake EOL when hit EOF for the first time. This gets line + * count right if last line in included file is syntactically + * invalid and has no newline. + */ + if (file->eof_reached == 0) { + file->eof_reached = 1; + return ('\n'); + } + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = igetc(); + } + } + return (c); +} + +void +lungetc(int c) +{ + if (c == EOF) + return; + + if (file->ungetpos >= file->ungetsize) { + void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); + if (p == NULL) + err(1, "lungetc"); + file->ungetbuf = p; + file->ungetsize *= 2; + } + file->ungetbuf[file->ungetpos++] = c; +} + +int +findeol(void) +{ + int c; + + /* Skip to either EOF or the first real EOL. */ + while (1) { + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + unsigned char buf[8096]; + unsigned char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && !expanding) { + while (1) { + if ((c = lgetc(0)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + p = val + strlen(val) - 1; + lungetc(DONE_EXPAND); + while (p >= val) { + lungetc(*p); + p--; + } + lungetc(START_EXPAND); + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || next == ' ' || + next == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "yylex: strdup"); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) { + log_warn("cannot stat %s", fname); + return (-1); + } + if (st.st_uid != 0 && st.st_uid != getuid()) { + log_warnx("%s: owner not root or current user", fname); + return (-1); + } + if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { + log_warnx("%s: group writable or world read/writable", fname); + return (-1); + } + return (0); +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + log_warn("calloc"); + return (NULL); + } + if ((nfile->name = strdup(name)) == NULL) { + log_warn("strdup"); + free(nfile); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + free(nfile->name); + free(nfile); + return (NULL); + } else if (secret && + check_file_secrecy(fileno(nfile->stream), nfile->name)) { + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; + nfile->ungetsize = 16; + nfile->ungetbuf = malloc(nfile->ungetsize); + if (nfile->ungetbuf == NULL) { + log_warn("malloc"); + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file->ungetbuf); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +struct dhcpleased_conf * +parse_config(char *filename) +{ + struct sym *sym, *next; + + conf = config_new_empty(); + + file = pushfile(filename != NULL ? filename : _PATH_CONF_FILE, 0); + if (file == NULL) { + /* no default config file is fine */ + if (errno == ENOENT && filename == NULL) + return (conf); + log_warn("%s", filename); + free(conf); + return (NULL); + } + topfile = file; + + yyparse(); + errors = file->errors; + popfile(); + + /* Free macros and check which have not been used. */ + TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { + if ((log_getverbose() == 2) && !sym->used) + fprintf(stderr, "warning: macro '%s' not used\n", + sym->nam); + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + if (errors) { + config_clear(conf); + return (NULL); + } + + return (conf); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) + break; + } + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + sym = strndup(s, val - s); + if (sym == NULL) + errx(1, "%s: strndup", __func__); + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + } + return (NULL); +} + +struct iface_conf * +conf_get_iface(char *name) +{ + struct iface_conf *iface; + size_t n; + + SIMPLEQ_FOREACH(iface, &conf->iface_list, entry) { + if (strcmp(name, iface->name) == 0) + return (iface); + } + + iface = calloc(1, sizeof(*iface)); + if (iface == NULL) + errx(1, "%s: calloc", __func__); + n = strlcpy(iface->name, name, sizeof(iface->name)); + if (n >= sizeof(iface->name)) + errx(1, "%s: name too long", __func__); + + + SIMPLEQ_INSERT_TAIL(&conf->iface_list, iface, entry); + + return (iface); +} diff --git a/sbin/dhcpleased/printconf.c b/sbin/dhcpleased/printconf.c new file mode 100644 index 00000000000..16b33bbfe40 --- /dev/null +++ b/sbin/dhcpleased/printconf.c @@ -0,0 +1,116 @@ +/* $OpenBSD: printconf.c,v 1.1 2021/07/26 09:26:36 florian Exp $ */ + +/* + * Copyright (c) 2018 Florian Obser + * Copyright (c) 2004, 2005 Esben Norby + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "dhcpleased.h" +#include "log.h" + +void print_dhcp_options(char *, uint8_t *, int); + +void +print_dhcp_options(char *indent, uint8_t *p, int len) +{ + static char buf[4 * 1500 + 1]; + int rem, i; + uint8_t dho, dho_len, type; + + rem = len; + + while (rem > 0) { + dho = *p; + p += 1; + rem -= 1; + + if (rem == 0) + fatal("dhcp option too short"); + dho_len = *p; + p += 1; + rem -= 1; + if (rem < dho_len) + fatal("dhcp option too short: %d %d", rem, dho_len); + + switch (dho) { + case DHO_DHCP_CLASS_IDENTIFIER: + strvisx(buf, p, dho_len, VIS_DQ | VIS_CSTYLE); + p += dho_len; + rem -= dho_len; + printf("%ssend vendor class id \"%s\"\n", indent, buf); + break; + case DHO_DHCP_CLIENT_IDENTIFIER: + if (dho_len < 1) + fatal("dhcp option too short"); + type = *p; + p += 1; + rem -= 1; + switch (type) { + case HTYPE_NONE: + strvisx(buf, p, dho_len - 1, VIS_DQ | + VIS_CSTYLE); + printf("%ssend client id \"%s\"\n", + indent, buf); + break; + default: + printf("%ssend client id \"%02x", + indent, type); + + for (i = 0; i < dho_len - 1; i++) { + printf(":%02x", *(p + i)); + } + printf("\"\n"); + break; + } + p += dho_len - 1; + rem -= dho_len - 1; + break; + default: + fatal("unknown dhcp option: %d [%d]", *p, rem); + break; + } + } +} + +void +print_config(struct dhcpleased_conf *conf) +{ + struct iface_conf *iface; + + SIMPLEQ_FOREACH(iface, &conf->iface_list, entry) { + printf("interface %s {\n", iface->name); + print_dhcp_options("\t", iface->c_id, iface->c_id_len); + print_dhcp_options("\t", iface->vc_id, iface->vc_id_len); + printf("}\n"); + } +} diff --git a/usr.sbin/dhcpleasectl/dhcpleasectl.c b/usr.sbin/dhcpleasectl/dhcpleasectl.c index 6e397191791..0edabf1ba9e 100644 --- a/usr.sbin/dhcpleasectl/dhcpleasectl.c +++ b/usr.sbin/dhcpleasectl/dhcpleasectl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dhcpleasectl.c,v 1.4 2021/06/16 14:06:18 florian Exp $ */ +/* $OpenBSD: dhcpleasectl.c,v 1.5 2021/07/26 09:26:36 florian Exp $ */ /* * Copyright (c) 2021 Florian Obser @@ -115,6 +115,11 @@ main(int argc, char *argv[]) /* Process user request. */ switch (res->action) { + case RELOAD: + imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, -1, NULL, 0); + printf("reload request sent.\n"); + done = 1; + break; case LOG_VERBOSE: verbose = 1; /* FALLTHROUGH */ diff --git a/usr.sbin/dhcpleasectl/parser.c b/usr.sbin/dhcpleasectl/parser.c index eb9a0d88505..c77621eb3ec 100644 --- a/usr.sbin/dhcpleasectl/parser.c +++ b/usr.sbin/dhcpleasectl/parser.c @@ -1,4 +1,4 @@ -/* $OpenBSD: parser.c,v 1.1 2021/02/26 16:16:37 florian Exp $ */ +/* $OpenBSD: parser.c,v 1.2 2021/07/26 09:26:36 florian Exp $ */ /* * Copyright (c) 2004 Esben Norby @@ -61,6 +61,7 @@ static const struct token t_send[]; static const struct token t_send_request[]; static const struct token t_main[] = { + {KEYWORD, "reload", RELOAD, NULL}, {KEYWORD, "show", SHOW, t_show}, {KEYWORD, "log", NONE, t_log}, {KEYWORD, "send", NONE, t_send}, diff --git a/usr.sbin/dhcpleasectl/parser.h b/usr.sbin/dhcpleasectl/parser.h index dad595b313e..4c4a89cd4cf 100644 --- a/usr.sbin/dhcpleasectl/parser.h +++ b/usr.sbin/dhcpleasectl/parser.h @@ -1,4 +1,4 @@ -/* $OpenBSD: parser.h,v 1.1 2021/02/26 16:16:37 florian Exp $ */ +/* $OpenBSD: parser.h,v 1.2 2021/07/26 09:26:36 florian Exp $ */ /* * Copyright (c) 2004 Esben Norby @@ -23,7 +23,8 @@ enum actions { LOG_BRIEF, SHOW, SHOW_INTERFACE, - SEND_REQUEST + SEND_REQUEST, + RELOAD }; struct parse_result {