Implement possibility to send vendor class identifier (option 60) and
authorflorian <florian@openbsd.org>
Mon, 26 Jul 2021 09:26:36 +0000 (09:26 +0000)
committerflorian <florian@openbsd.org>
Mon, 26 Jul 2021 09:26:36 +0000 (09:26 +0000)
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)

13 files changed:
sbin/dhcpleased/Makefile
sbin/dhcpleased/control.c
sbin/dhcpleased/dhcpleased.8
sbin/dhcpleased/dhcpleased.c
sbin/dhcpleased/dhcpleased.conf.5 [new file with mode: 0644]
sbin/dhcpleased/dhcpleased.h
sbin/dhcpleased/engine.c
sbin/dhcpleased/frontend.c
sbin/dhcpleased/parse.y [new file with mode: 0644]
sbin/dhcpleased/printconf.c [new file with mode: 0644]
usr.sbin/dhcpleasectl/dhcpleasectl.c
usr.sbin/dhcpleasectl/parser.c
usr.sbin/dhcpleasectl/parser.h

index 8b18a7a..7806992 100644 (file)
@@ -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
 
index e021a67..9a713cb 100644 (file)
@@ -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 <henning@openbsd.org>
@@ -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;
index a56ea14..7ee3d8f 100644 (file)
@@ -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 <florian@openbsd.org>
 .\"
@@ -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 ,
index f4b2104..36a4a21 100644 (file)
@@ -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 <florian@openbsd.org>
@@ -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 (file)
index 0000000..c86a549
--- /dev/null
@@ -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 <florian@openbsd.org>
+.\" Copyright (c) 2005 Esben Norby <norby@openbsd.org>
+.\" Copyright (c) 2004 Claudio Jeker <claudio@openbsd.org>
+.\" Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+.\" Copyright (c) 2002 Daniel Hartmeier <dhartmei@openbsd.org>
+.\"
+.\" 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 ,
index ae81baa..e65300a 100644 (file)
@@ -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 <florian@openbsd.org>
@@ -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 */
+
index b89cf16..060d0de 100644 (file)
@@ -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 <florian@openbsd.org>
@@ -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);
index 1e5284e..9246960 100644 (file)
@@ -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 <florian@openbsd.org>
@@ -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 (file)
index 0000000..21b2383
--- /dev/null
@@ -0,0 +1,804 @@
+/*     $OpenBSD: parse.y,v 1.1 2021/07/26 09:26:36 florian Exp $       */
+
+/*
+ * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <net/if.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <vis.h>
+
+#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 <v.string>      STRING
+%token <v.number>      NUMBER
+%type  <v.string>      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 (file)
index 0000000..16b33bb
--- /dev/null
@@ -0,0 +1,116 @@
+/*     $OpenBSD: printconf.c,v 1.1 2021/07/26 09:26:36 florian Exp $   */
+
+/*
+ * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ *
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <net/if.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <arpa/inet.h>
+
+#include <event.h>
+#include <imsg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <vis.h>
+
+#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");
+       }
+}
index 6e39719..0edabf1 100644 (file)
@@ -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 <florian@openbsd.org>
@@ -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 */
index eb9a0d8..c77621e 100644 (file)
@@ -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 <norby@openbsd.org>
@@ -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},
index dad595b..4c4a89c 100644 (file)
@@ -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 <norby@openbsd.org>
@@ -23,7 +23,8 @@ enum actions {
        LOG_BRIEF,
        SHOW,
        SHOW_INTERFACE,
-       SEND_REQUEST
+       SEND_REQUEST,
+       RELOAD
 };
 
 struct parse_result {