Implement lease files.
authorflorian <florian@openbsd.org>
Wed, 5 Jun 2024 16:15:47 +0000 (16:15 +0000)
committerflorian <florian@openbsd.org>
Wed, 5 Jun 2024 16:15:47 +0000 (16:15 +0000)
sbin/dhcp6leased/Makefile
sbin/dhcp6leased/control.c
sbin/dhcp6leased/dhcp6leased.c
sbin/dhcp6leased/dhcp6leased.h
sbin/dhcp6leased/engine.c
sbin/dhcp6leased/parse.y
sbin/dhcp6leased/parse_lease.y [new file with mode: 0644]

index 4574a9a..73effc9 100644 (file)
@@ -1,8 +1,8 @@
-#      $OpenBSD: Makefile,v 1.1 2024/06/02 12:28:05 florian Exp $
+#      $OpenBSD: Makefile,v 1.2 2024/06/05 16:15:47 florian Exp $
 
 PROG=  dhcp6leased
 SRCS=  control.c dhcp6leased.c engine.c frontend.c log.c
-SRCS+= parse.y printconf.c
+SRCS+= parse.y printconf.c parse_lease.y
 
 MAN=   dhcp6leased.8 dhcp6leased.conf.5
 
@@ -17,6 +17,9 @@ YFLAGS=
 LDADD+=        -levent -lutil
 DPADD+= ${LIBEVENT} ${LIBUTIL}
 
+parse_lease.c: parse_lease.y
+       ${YACC.y} -ppl -o ${.TARGET} ${.IMPSRC}
+
 .include <bsd.prog.mk>
 
 # Don't compile dhcp6leased as static binary by default
index 5bc63c1..364b9ad 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: control.c,v 1.2 2024/06/02 13:35:52 florian Exp $     */
+/*     $OpenBSD: control.c,v 1.3 2024/06/05 16:15:47 florian Exp $     */
 
 /*
  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -28,6 +28,7 @@
 #include <errno.h>
 #include <event.h>
 #include <imsg.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
index 160ceb4..61fbd18 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: dhcp6leased.c,v 1.10 2024/06/05 16:11:26 florian Exp $        */
+/*     $OpenBSD: dhcp6leased.c,v 1.11 2024/06/05 16:15:47 florian Exp $        */
 
 /*
  * Copyright (c) 2017, 2021, 2024 Florian Obser <florian@openbsd.org>
@@ -78,6 +78,7 @@ void   configure_address(struct imsg_configure_address *);
 void    deconfigure_address(struct imsg_configure_address *);
 void    read_lease_file(struct imsg_ifinfo *);
 uint8_t        *get_uuid(void);
+void    write_lease_file(struct imsg_lease_info *);
 
 int     main_imsg_send_ipc_sockets(struct imsgbuf *, struct imsgbuf *);
 int     main_imsg_compose_frontend(int, int, void *, uint16_t);
@@ -309,7 +310,8 @@ main(int argc, char *argv[])
        if (unveil(NULL, NULL) == -1)
                fatal("unveil");
 
-       if (pledge("stdio inet rpath wpath sendfd wroute", NULL) == -1)
+       if (pledge("stdio inet rpath wpath cpath fattr sendfd wroute", NULL)
+           == -1)
                fatal("pledge");
 
        main_imsg_compose_frontend(IMSG_ROUTESOCK, frontend_routesock, NULL, 0);
@@ -543,6 +545,17 @@ main_dispatch_engine(int fd, short event, void *bula)
                        deconfigure_address(&imsg_configure_address);
                        break;
                }
+               case IMSG_WRITE_LEASE:  {
+                       struct imsg_lease_info imsg_lease_info;
+                       if (IMSG_DATA_SIZE(imsg) !=
+                           sizeof(imsg_lease_info))
+                               fatalx("%s: IMSG_WRITE_LEASE wrong length: %lu",
+                                   __func__, IMSG_DATA_SIZE(imsg));
+                       memcpy(&imsg_lease_info, imsg.data,
+                           sizeof(imsg_lease_info));
+                       write_lease_file(&imsg_lease_info);
+                       break;
+               }
                default:
                        log_debug("%s: error handling imsg %d", __func__,
                            imsg.hdr.type);
@@ -858,17 +871,99 @@ open_udpsock(uint32_t if_index)
        freeifaddrs(ifap);
 }
 
+void
+write_lease_file(struct imsg_lease_info *imsg_lease_info)
+{
+       struct iface_conf       *iface_conf;
+       uint32_t                 i;
+       int                      len, fd, rem;
+       char                     if_name[IF_NAMESIZE];
+       char                     lease_buf[LEASE_SIZE];
+       char                     lease_file_buf[sizeof(_PATH_LEASE) +
+           IF_NAMESIZE];
+       char                     tmpl[] = _PATH_LEASE"XXXXXXXXXX";
+       char                     ntopbuf[INET6_ADDRSTRLEN];
+       char                    *p;
+
+       if (no_lease_files)
+               return;
+
+       if (if_indextoname(imsg_lease_info->if_index, if_name) == NULL) {
+               log_warnx("%s: cannot find interface %d", __func__,
+                   imsg_lease_info->if_index);
+               return;
+       }
+
+       if ((iface_conf = find_iface_conf(&main_conf->iface_list, if_name))
+           == NULL) {
+               log_debug("%s: no interface configuration for %s", __func__,
+                   if_name);
+               return;
+       }
+
+       len = snprintf(lease_file_buf, sizeof(lease_file_buf), "%s%s",
+           _PATH_LEASE, if_name);
+       if (len == -1 || (size_t) len >= sizeof(lease_file_buf)) {
+               log_warnx("%s: failed to encode lease path for %s", __func__,
+                   if_name);
+               return;
+       }
+
+       p = lease_buf;
+       rem = sizeof(lease_buf);
+
+       for (i = 0; i < iface_conf->ia_count; i++) {
+               len = snprintf(p, rem, "%s%d %s %d\n", LEASE_IA_PD_PREFIX,
+                   i, inet_ntop(AF_INET6, &imsg_lease_info->pds[i].prefix,
+                   ntopbuf, INET6_ADDRSTRLEN),
+                   imsg_lease_info->pds[i].prefix_len);
+               if (len == -1 || len >= rem) {
+                       log_warnx("%s: failed to encode lease for %s", __func__,
+                           if_name);
+                       return;
+               }
+               p += len;
+               rem -= len;
+       }
+
+       len = sizeof(lease_buf) - rem;
+
+       if ((fd = mkstemp(tmpl)) == -1) {
+               log_warn("%s: mkstemp", __func__);
+               return;
+       }
+
+       if (write(fd, lease_buf, len) < len)
+               goto err;
+
+       if (fchmod(fd, 0644) == -1)
+               goto err;
+
+       if (close(fd) == -1)
+               goto err;
+       fd = -1;
+
+       if (rename(tmpl, lease_file_buf) == -1)
+               goto err;
+       return;
+ err:
+       log_warn("%s", __func__);
+       if (fd != -1)
+               close(fd);
+       unlink(tmpl);
+}
+
 void
 read_lease_file(struct imsg_ifinfo *imsg_ifinfo)
 {
-       int      len, fd;
+       int      len;
        char     if_name[IF_NAMESIZE];
        char     lease_file_buf[sizeof(_PATH_LEASE) + IF_NAMESIZE];
 
        if (no_lease_files)
                return;
 
-       memset(imsg_ifinfo->lease, 0, sizeof(imsg_ifinfo->lease));
+       memset(imsg_ifinfo->pds, 0, sizeof(imsg_ifinfo->pds));
 
        if (if_indextoname(imsg_ifinfo->if_index, if_name) == NULL) {
                log_warnx("%s: cannot find interface %d", __func__,
@@ -883,13 +978,22 @@ read_lease_file(struct imsg_ifinfo *imsg_ifinfo)
                    if_name);
                return;
        }
+       parse_lease(lease_file_buf, imsg_ifinfo);
 
-       if ((fd = open(lease_file_buf, O_RDONLY)) == -1)
-               return;
+       if (log_getverbose() > 1) {
+               int      i;
+               char     ntopbuf[INET6_ADDRSTRLEN];
+
+               for (i = 0; i < MAX_IA; i++) {
+                       if (imsg_ifinfo->pds[i].prefix_len == 0)
+                               continue;
 
-       /* no need for error handling, we'll just do a DHCP discover */
-       read(fd, imsg_ifinfo->lease, sizeof(imsg_ifinfo->lease) - 1);
-       close(fd);
+                       log_debug("%s: %s: %d %s/%d", __func__, if_name, i,
+                           inet_ntop(AF_INET6, &imsg_ifinfo->pds[i].prefix,
+                           ntopbuf, INET6_ADDRSTRLEN),
+                           imsg_ifinfo->pds[i].prefix_len);
+               }
+       }
 }
 
 void
index 35f29cc..040cf8c 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: dhcp6leased.h,v 1.6 2024/06/05 16:14:12 florian Exp $ */
+/*     $OpenBSD: dhcp6leased.h,v 1.7 2024/06/05 16:15:47 florian Exp $ */
 
 /*
  * Copyright (c) 2017, 2021 Florian Obser <florian@openbsd.org>
 #define        XID_SIZE                3
 #define        SERVERID_SIZE           130 /* 2 octet type, max 128 octets data */
 #define        MAX_IA                  32
-#define        LEASE_VERSION           "version: 2"
-#define        LEASE_IP_PREFIX         "ip: "
-#define        LEASE_NEXTSERVER_PREFIX "next-server: "
-#define        LEASE_BOOTFILE_PREFIX   "filename: "
-#define        LEASE_HOSTNAME_PREFIX   "host-name: "
-#define        LEASE_DOMAIN_PREFIX     "domain-name: "
 #define        LEASE_SIZE              4096
+#define        LEASE_IA_PD_PREFIX      "ia_pd "
 /* MAXDNAME from arpa/namesr.h */
 #define        DHCP6LEASED_MAX_DNSSL   1025
 #define        MAX_RDNS_COUNT          8 /* max nameserver in a RTM_PROPOSAL */
@@ -177,6 +172,7 @@ enum imsg_type {
        IMSG_CONFIGURE_ADDRESS,
        IMSG_DECONFIGURE_ADDRESS,
        IMSG_REQUEST_REBOOT,
+       IMSG_WRITE_LEASE,
 };
 
 struct ctl_engine_info {
@@ -217,12 +213,19 @@ struct dhcp6leased_conf {
        int                                             rapid_commit;
 };
 
+struct prefix {
+       struct in6_addr  prefix;
+       int              prefix_len;
+       uint32_t         vltime;
+       uint32_t         pltime;
+};
+
 struct imsg_ifinfo {
        uint32_t                if_index;
        int                     rdomain;
        int                     running;
        int                     link_state;
-       char                    lease[LEASE_SIZE];
+       struct prefix           pds[MAX_IA];
 };
 
 struct imsg_dhcp {
@@ -231,13 +234,6 @@ struct imsg_dhcp {
        uint8_t                 packet[1500];
 };
 
-struct prefix {
-       struct in6_addr  prefix;
-       int              prefix_len;
-       uint32_t         vltime;
-       uint32_t         pltime;
-};
-
 struct imsg_req_dhcp {
        uint32_t                 if_index;
        int                      elapsed_time;
@@ -247,6 +243,11 @@ struct imsg_req_dhcp {
        struct prefix            pds[MAX_IA];
 };
 
+struct imsg_lease_info {
+       uint32_t                 if_index;
+       struct prefix            pds[MAX_IA];
+};
+
 /* dhcp6leased.c */
 void                    imsg_event_add(struct imsgev *);
 int                     imsg_compose_event(struct imsgev *, uint16_t, uint32_t,
@@ -268,6 +269,25 @@ int                        *changed_ifaces(struct dhcp6leased_conf *, struct
 void   print_config(struct dhcp6leased_conf *, int);
 
 /* parse.y */
-struct dhcp6leased_conf        *parse_config(const char *);
-int                     cmdline_symset(char *);
+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;
+};
 
+struct dhcp6leased_conf        *parse_config(const char *);
+struct file            *pushfile(const char *, int);
+int                     popfile(void);
+int                     kw_cmp(const void *, const void *);
+int                     lgetc(int);
+void                    lungetc(int);
+int                     findeol(void);
+
+/* parse_lease.y */
+void   parse_lease(const char*, struct imsg_ifinfo *);
index 1e0bb1f..6f9ccf1 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: engine.c,v 1.11 2024/06/05 16:12:09 florian Exp $     */
+/*     $OpenBSD: engine.c,v 1.12 2024/06/05 16:15:47 florian Exp $     */
 
 /*
  * Copyright (c) 2017, 2021, 2024 Florian Obser <florian@openbsd.org>
@@ -136,7 +136,7 @@ void                         configure_interfaces(struct dhcp6leased_iface *);
 void                    deconfigure_interfaces(struct dhcp6leased_iface *);
 void                    send_reconfigure_interface(struct iface_pd_conf *,
                             struct prefix *, enum reconfigure_action);
-void                    parse_lease(struct dhcp6leased_iface *,
+void                    parse_lease_xxx(struct dhcp6leased_iface *,
                             struct imsg_ifinfo *);
 int                     engine_imsg_compose_main(int, pid_t, void *, uint16_t);
 const char             *dhcp_message_type2str(uint8_t);
@@ -445,8 +445,6 @@ engine_dispatch_main(int fd, short event, void *bula)
                                fatalx("%s: IMSG_UPDATE_IF wrong length: %lu",
                                    __func__, IMSG_DATA_SIZE(imsg));
                        memcpy(&imsg_ifinfo, imsg.data, sizeof(imsg_ifinfo));
-                       if (imsg_ifinfo.lease[LEASE_SIZE - 1] != '\0')
-                               fatalx("Invalid lease");
                        engine_update_iface(&imsg_ifinfo);
                        break;
                case IMSG_RECONF_CONF:
@@ -601,7 +599,9 @@ void
 engine_update_iface(struct imsg_ifinfo *imsg_ifinfo)
 {
        struct dhcp6leased_iface        *iface;
-       int                      need_refresh = 0;
+       struct iface_conf               *iface_conf;
+       int                              need_refresh = 0;
+       char                             ifnamebuf[IF_NAMESIZE], *if_name;
 
        iface = get_dhcp6leased_iface_by_id(imsg_ifinfo->if_index);
 
@@ -637,18 +637,38 @@ engine_update_iface(struct imsg_ifinfo *imsg_ifinfo)
        if (!need_refresh)
                return;
 
+       if ((if_name = if_indextoname(iface->if_index, ifnamebuf)) == NULL) {
+               log_debug("%s: unknown interface %d", __func__,
+                   iface->if_index);
+               return;
+       }
+
+       if ((iface_conf = find_iface_conf(&engine_conf->iface_list, if_name))
+           == NULL) {
+               log_debug("%s: no interface configuration for %d", __func__,
+                   iface->if_index);
+               return;
+       }
+
        if (iface->running && LINK_STATE_IS_UP(iface->link_state)) {
-#if 0
-XXXX
-               if (iface->requested_ip.s_addr == INADDR_ANY)
-                       parse_lease(iface, imsg_ifinfo);
+               uint32_t         i;
+               int              got_lease;
 
-               if (iface->requested_ip.s_addr == INADDR_ANY)
-                       state_transition(iface, IF_INIT);
-               else
+               if (iface->pds[0].prefix_len == 0)
+                       memcpy(iface->pds, imsg_ifinfo->pds,
+                           sizeof(iface->pds));
+
+               got_lease = 0;
+               for (i = 0; i < iface_conf->ia_count; i++) {
+                       if (iface->pds[i].prefix_len > 0) {
+                               got_lease = 1;
+                               break;
+                       }
+               }
+               if (got_lease)
                        state_transition(iface, IF_REBOOTING);
-#endif
-               state_transition(iface, IF_INIT);
+               else
+                       state_transition(iface, IF_INIT);
        } else
                state_transition(iface, IF_DOWN);
 }
@@ -884,6 +904,7 @@ parse_dhcp(struct dhcp6leased_iface *iface, struct imsg_dhcp *dhcp)
                case IF_REQUESTING:
                case IF_RENEWING:
                case IF_REBINDING:
+               case IF_REBOOTING:
                        break;
                case IF_INIT:
                        if (rapid_commit && engine_conf->rapid_commit)
@@ -1232,8 +1253,6 @@ request_dhcp_request(struct dhcp6leased_iface *iface)
                fatalx("invalid state IF_BOUND in %s", __func__);
                break;
        case IF_REBOOTING:
-               fatalx("XXX state IF_REBOOTING in %s not IMPL", __func__);
-               break;
        case IF_REQUESTING:
        case IF_RENEWING:
        case IF_REBINDING:
@@ -1251,6 +1270,7 @@ request_dhcp_request(struct dhcp6leased_iface *iface)
                engine_imsg_compose_frontend(IMSG_SEND_RENEW, 0, &imsg,
                    sizeof(imsg));
                break;
+       case IF_REBOOTING:
        case IF_REBINDING:
                engine_imsg_compose_frontend(IMSG_SEND_REBIND, 0, &imsg,
                    sizeof(imsg));
@@ -1267,6 +1287,7 @@ configure_interfaces(struct dhcp6leased_iface *iface)
        struct iface_conf       *iface_conf;
        struct iface_ia_conf    *ia_conf;
        struct iface_pd_conf    *pd_conf;
+       struct imsg_lease_info   imsg_lease_info;
        char                     ifnamebuf[IF_NAMESIZE], *if_name;
 
 
@@ -1282,6 +1303,12 @@ configure_interfaces(struct dhcp6leased_iface *iface)
                return;
        }
 
+       memset(&imsg_lease_info, 0, sizeof(imsg_lease_info));
+       imsg_lease_info.if_index = iface->if_index;
+       memcpy(imsg_lease_info.pds, iface->pds, sizeof(iface->pds));
+       engine_imsg_compose_main(IMSG_WRITE_LEASE, 0, &imsg_lease_info,
+           sizeof(imsg_lease_info));
+
        SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list, entry) {
                struct prefix   *pd = &iface->pds[ia_conf->id];
 
@@ -1371,12 +1398,6 @@ send_reconfigure_interface(struct iface_pd_conf *pd_conf, struct prefix *pd,
                    sizeof(address));
 }
 
-void
-parse_lease(struct dhcp6leased_iface *iface, struct imsg_ifinfo *imsg_ifinfo)
-{
-       fatalx("%s: not implemented", __func__); /* XXX */
-}
-
 const char *
 dhcp_message_type2str(uint8_t type)
 {
index 940ec15..3b60e77 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: parse.y,v 1.7 2024/06/04 15:48:47 florian Exp $       */
+/*     $OpenBSD: parse.y,v 1.8 2024/06/05 16:15:47 florian Exp $       */
 
 /*
  * Copyright (c) 2018, 2024 Florian Obser <florian@openbsd.org>
 #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);
+struct file     *file, *topfile;
 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 {
@@ -699,23 +683,6 @@ symset(const char *nam, const char *val, int persist)
        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)
 {
diff --git a/sbin/dhcp6leased/parse_lease.y b/sbin/dhcp6leased/parse_lease.y
new file mode 100644 (file)
index 0000000..767e0de
--- /dev/null
@@ -0,0 +1,289 @@
+/*     $OpenBSD: parse_lease.y,v 1.1 2024/06/05 16:15:47 florian Exp $ */
+
+/*
+ * Copyright (c) 2018, 2024 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 <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 "dhcp6leased.h"
+#include "frontend.h"
+
+extern TAILQ_HEAD(files, file)   files;
+extern struct file              *file, *topfile;
+int             yyparse(void);
+int             yylex(void);
+int             yyerror(const char *, ...)
+    __attribute__((__format__ (printf, 1, 2)))
+    __attribute__((__nonnull__ (1)));
+int             pllookup(char *);
+
+struct imsg_ifinfo             *ifinfo;
+static int                      errors;
+
+typedef struct {
+       union {
+               int64_t          number;
+               char            *string;
+       } v;
+       int lineno;
+} YYSTYPE;
+
+%}
+
+%token ERROR IAPD
+
+%token <v.string>      STRING
+%token <v.number>      NUMBER
+
+%%
+
+grammar                : /* empty */
+               | grammar '\n'
+               | grammar ia_pd '\n'
+               | grammar error '\n'            { file->errors++; }
+               ;
+
+ia_pd          : IAPD NUMBER STRING NUMBER {
+                       if ($2 < 0 || $2 > MAX_IA) {
+                               yyerror("invalid IA_ID %lld", $2);
+                               free($3);
+                               YYERROR;
+                       }
+                       if ($4 < 1 || $4 > 128) {
+                               yyerror("invalid prefix length %lld", $4);
+                               free($3);
+                               ifinfo->pds[$2].prefix_len = 0;
+                               YYERROR;
+                       } else
+                               ifinfo->pds[$2].prefix_len = $4;
+
+                       if (inet_pton(AF_INET6, $3, &ifinfo->pds[$2].prefix)
+                           != 1) {
+                               yyerror("invalid prefix %s", $3);
+                               free($3);
+                               ifinfo->pds[$2].prefix_len = 0;
+                               YYERROR;
+                       }
+                       free($3);
+               }
+               ;
+%%
+
+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
+pllookup(char *s)
+{
+       /* This has to be sorted always. */
+       static const struct keywords keywords[] = {
+               {"ia_pd",       IAPD},
+       };
+       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);
+}
+
+int
+yylex(void)
+{
+       char     buf[8096];
+       char    *p;
+       int      quotec, next, c;
+       int      token;
+
+       p = buf;
+       while ((c = lgetc(0)) == ' ' || c == '\t')
+               ; /* nothing */
+
+       yylval.lineno = file->lineno;
+       if (c == '#')
+               while ((c = lgetc(0)) != '\n' && c != EOF)
+                       ; /* nothing */
+       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((unsigned char)*--p);
+                       c = (unsigned char)*--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 = pllookup(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);
+}
+
+void
+parse_lease(const char *filename, struct imsg_ifinfo *imsg)
+{
+       ifinfo = imsg;
+       file = pushfile(filename, 0);
+       if (file == NULL)
+               return;
+
+       topfile = file;
+
+       yyparse();
+       errors = file->errors;
+       popfile();
+}