summaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@baserock.org>2015-03-02 19:04:49 +0000
committer <>2015-05-08 15:30:59 +0000
commitf800382616186a5d30e28d8b2c51e97a9a8360f2 (patch)
tree0d5270190548a37223d14b54383ce8a3d3af5302 /common
downloadisc-dhcp-tarball-f800382616186a5d30e28d8b2c51e97a9a8360f2.tar.gz
Imported from /home/lorry/working-area/delta_isc-dhcp-tarball/dhcp-4.2.8.tar.gz.HEADdhcp-4.2.8master
Diffstat (limited to 'common')
-rw-r--r--common/Makefile.am17
-rw-r--r--common/Makefile.in778
-rw-r--r--common/alloc.c1362
-rw-r--r--common/bpf.c607
-rw-r--r--common/comapi.c921
-rw-r--r--common/conflex.c1535
-rw-r--r--common/ctrace.c291
-rw-r--r--common/dhcp-eval.5569
-rw-r--r--common/dhcp-options.52157
-rw-r--r--common/discover.c1862
-rw-r--r--common/dispatch.c435
-rw-r--r--common/dlpi.c1414
-rw-r--r--common/dns.c1785
-rw-r--r--common/ethernet.c87
-rw-r--r--common/execute.c1157
-rw-r--r--common/fddi.c86
-rw-r--r--common/icmp.c300
-rw-r--r--common/inet.c624
-rw-r--r--common/lpf.c537
-rw-r--r--common/memory.c148
-rw-r--r--common/nit.c416
-rw-r--r--common/ns_name.c789
-rw-r--r--common/options.c4227
-rw-r--r--common/packet.c359
-rw-r--r--common/parse.c6007
-rw-r--r--common/print.c1519
-rw-r--r--common/raw.c133
-rw-r--r--common/resolv.c189
-rw-r--r--common/socket.c1225
-rw-r--r--common/tables.c1410
-rw-r--r--common/tests/Atffile5
-rw-r--r--common/tests/Makefile.am30
-rw-r--r--common/tests/Makefile.in694
-rw-r--r--common/tests/ns_name_test.c160
-rw-r--r--common/tests/test_alloc.c501
-rw-r--r--common/tr.c331
-rw-r--r--common/tree.c4643
-rw-r--r--common/upf.c367
38 files changed, 39677 insertions, 0 deletions
diff --git a/common/Makefile.am b/common/Makefile.am
new file mode 100644
index 0000000..eddef05
--- /dev/null
+++ b/common/Makefile.am
@@ -0,0 +1,17 @@
+AM_CPPFLAGS = -I.. -DLOCALSTATEDIR='"@localstatedir@"'
+AM_CFLAGS = $(LDAP_CFLAGS)
+
+noinst_LIBRARIES = libdhcp.a
+libdhcp_a_SOURCES = alloc.c bpf.c comapi.c conflex.c ctrace.c discover.c \
+ dispatch.c dlpi.c dns.c ethernet.c execute.c fddi.c \
+ icmp.c inet.c lpf.c memory.c nit.c ns_name.c options.c \
+ packet.c parse.c print.c raw.c resolv.c socket.c \
+ tables.c tr.c tree.c upf.c
+man_MANS = dhcp-eval.5 dhcp-options.5
+EXTRA_DIST = $(man_MANS)
+
+# We want to build this directory first, before descending into tests subdir.
+# The reason is that ideally the tests should link existing objects from this
+# directory. That eliminates any discrepancies between tested code and
+# production code. Sadly, we are not there yet.
+SUBDIRS = . tests
diff --git a/common/Makefile.in b/common/Makefile.in
new file mode 100644
index 0000000..51e7818
--- /dev/null
+++ b/common/Makefile.in
@@ -0,0 +1,778 @@
+# Makefile.in generated by automake 1.14 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = common
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(top_srcdir)/depcomp
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/includes/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+AR = ar
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_@AM_V@)
+am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@)
+am__v_AR_0 = @echo " AR " $@;
+am__v_AR_1 =
+libdhcp_a_AR = $(AR) $(ARFLAGS)
+libdhcp_a_LIBADD =
+am_libdhcp_a_OBJECTS = alloc.$(OBJEXT) bpf.$(OBJEXT) comapi.$(OBJEXT) \
+ conflex.$(OBJEXT) ctrace.$(OBJEXT) discover.$(OBJEXT) \
+ dispatch.$(OBJEXT) dlpi.$(OBJEXT) dns.$(OBJEXT) \
+ ethernet.$(OBJEXT) execute.$(OBJEXT) fddi.$(OBJEXT) \
+ icmp.$(OBJEXT) inet.$(OBJEXT) lpf.$(OBJEXT) memory.$(OBJEXT) \
+ nit.$(OBJEXT) ns_name.$(OBJEXT) options.$(OBJEXT) \
+ packet.$(OBJEXT) parse.$(OBJEXT) print.$(OBJEXT) raw.$(OBJEXT) \
+ resolv.$(OBJEXT) socket.$(OBJEXT) tables.$(OBJEXT) \
+ tr.$(OBJEXT) tree.$(OBJEXT) upf.$(OBJEXT)
+libdhcp_a_OBJECTS = $(am_libdhcp_a_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/includes
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libdhcp_a_SOURCES)
+DIST_SOURCES = $(libdhcp_a_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+man5dir = $(mandir)/man5
+am__installdirs = "$(DESTDIR)$(man5dir)"
+NROFF = nroff
+MANS = $(man_MANS)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+ATF_BIN = @ATF_BIN@
+ATF_CFLAGS = @ATF_CFLAGS@
+ATF_LDFLAGS = @ATF_LDFLAGS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDAP_CFLAGS = @LDAP_CFLAGS@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_prefix_program = @ac_prefix_program@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+byte_order = @byte_order@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = -I.. -DLOCALSTATEDIR='"@localstatedir@"'
+AM_CFLAGS = $(LDAP_CFLAGS)
+noinst_LIBRARIES = libdhcp.a
+libdhcp_a_SOURCES = alloc.c bpf.c comapi.c conflex.c ctrace.c discover.c \
+ dispatch.c dlpi.c dns.c ethernet.c execute.c fddi.c \
+ icmp.c inet.c lpf.c memory.c nit.c ns_name.c options.c \
+ packet.c parse.c print.c raw.c resolv.c socket.c \
+ tables.c tr.c tree.c upf.c
+
+man_MANS = dhcp-eval.5 dhcp-options.5
+EXTRA_DIST = $(man_MANS)
+
+# We want to build this directory first, before descending into tests subdir.
+# The reason is that ideally the tests should link existing objects from this
+# directory. That eliminates any discrepancies between tested code and
+# production code. Sadly, we are not there yet.
+SUBDIRS = . tests
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign common/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign common/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+libdhcp.a: $(libdhcp_a_OBJECTS) $(libdhcp_a_DEPENDENCIES) $(EXTRA_libdhcp_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libdhcp.a
+ $(AM_V_AR)$(libdhcp_a_AR) libdhcp.a $(libdhcp_a_OBJECTS) $(libdhcp_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libdhcp.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/alloc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bpf.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/comapi.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/conflex.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ctrace.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/discover.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dispatch.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlpi.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dns.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ethernet.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/execute.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fddi.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/icmp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/inet.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lpf.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/memory.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nit.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ns_name.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/options.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/packet.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/print.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/raw.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/resolv.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socket.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tables.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tr.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tree.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/upf.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+install-man5: $(man_MANS)
+ @$(NORMAL_INSTALL)
+ @list1=''; \
+ list2='$(man_MANS)'; \
+ test -n "$(man5dir)" \
+ && test -n "`echo $$list1$$list2`" \
+ || exit 0; \
+ echo " $(MKDIR_P) '$(DESTDIR)$(man5dir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(man5dir)" || exit 1; \
+ { for i in $$list1; do echo "$$i"; done; \
+ if test -n "$$list2"; then \
+ for i in $$list2; do echo "$$i"; done \
+ | sed -n '/\.5[a-z]*$$/p'; \
+ fi; \
+ } | while read p; do \
+ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; echo "$$p"; \
+ done | \
+ sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^5][0-9a-z]*$$,5,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \
+ sed 'N;N;s,\n, ,g' | { \
+ list=; while read file base inst; do \
+ if test "$$base" = "$$inst"; then list="$$list $$file"; else \
+ echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man5dir)/$$inst'"; \
+ $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man5dir)/$$inst" || exit $$?; \
+ fi; \
+ done; \
+ for i in $$list; do echo "$$i"; done | $(am__base_list) | \
+ while read files; do \
+ test -z "$$files" || { \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man5dir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(man5dir)" || exit $$?; }; \
+ done; }
+
+uninstall-man5:
+ @$(NORMAL_UNINSTALL)
+ @list=''; test -n "$(man5dir)" || exit 0; \
+ files=`{ for i in $$list; do echo "$$i"; done; \
+ l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \
+ sed -n '/\.5[a-z]*$$/p'; \
+ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^5][0-9a-z]*$$,5,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \
+ dir='$(DESTDIR)$(man5dir)'; $(am__uninstall_files_from_dir)
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LIBRARIES) $(MANS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(man5dir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-man
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man: install-man5
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-man
+
+uninstall-man: uninstall-man5
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-noinstLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-man5 install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-man \
+ uninstall-man5
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/common/alloc.c b/common/alloc.c
new file mode 100644
index 0000000..a55f471
--- /dev/null
+++ b/common/alloc.c
@@ -0,0 +1,1362 @@
+/* alloc.c
+
+ Memory allocation... */
+
+/*
+ * Copyright (c) 2009,2013-2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2007 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+#include <omapip/omapip_p.h>
+
+struct dhcp_packet *dhcp_free_list;
+struct packet *packet_free_list;
+
+int option_chain_head_allocate (ptr, file, line)
+ struct option_chain_head **ptr;
+ const char *file;
+ int line;
+{
+ struct option_chain_head *h;
+
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct option_chain_head *)0;
+#endif
+ }
+
+ h = dmalloc (sizeof *h, file, line);
+ if (h) {
+ memset (h, 0, sizeof *h);
+ return option_chain_head_reference (ptr, h, file, line);
+ }
+ return 0;
+}
+
+int option_chain_head_reference (ptr, bp, file, line)
+ struct option_chain_head **ptr;
+ struct option_chain_head *bp;
+ const char *file;
+ int line;
+{
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct option_chain_head *)0;
+#endif
+ }
+ *ptr = bp;
+ bp -> refcnt++;
+ rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC);
+ return 1;
+}
+
+int option_chain_head_dereference (ptr, file, line)
+ struct option_chain_head **ptr;
+ const char *file;
+ int line;
+{
+ struct option_chain_head *option_chain_head;
+ pair car, cdr;
+
+ if (!ptr || !*ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ option_chain_head = *ptr;
+ *ptr = (struct option_chain_head *)0;
+ --option_chain_head -> refcnt;
+ rc_register (file, line, ptr, option_chain_head,
+ option_chain_head -> refcnt, 1, RC_MISC);
+ if (option_chain_head -> refcnt > 0)
+ return 1;
+
+ if (option_chain_head -> refcnt < 0) {
+ log_error ("%s(%d): negative refcnt!", file, line);
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history (option_chain_head);
+#endif
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ /* If there are any options on this head, free them. */
+ for (car = option_chain_head -> first; car; car = cdr) {
+ cdr = car -> cdr;
+ if (car -> car)
+ option_cache_dereference ((struct option_cache **)
+ (&car -> car), MDL);
+ dfree (car, MDL);
+ }
+
+ dfree (option_chain_head, file, line);
+ return 1;
+}
+
+int group_allocate (ptr, file, line)
+ struct group **ptr;
+ const char *file;
+ int line;
+{
+ struct group *g;
+
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct group *)0;
+#endif
+ }
+
+ g = dmalloc (sizeof *g, file, line);
+ if (g) {
+ memset (g, 0, sizeof *g);
+ return group_reference (ptr, g, file, line);
+ }
+ return 0;
+}
+
+int group_reference (ptr, bp, file, line)
+ struct group **ptr;
+ struct group *bp;
+ const char *file;
+ int line;
+{
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct group *)0;
+#endif
+ }
+ *ptr = bp;
+ bp -> refcnt++;
+ rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC);
+ return 1;
+}
+
+int group_dereference (ptr, file, line)
+ struct group **ptr;
+ const char *file;
+ int line;
+{
+ struct group *group;
+
+ if (!ptr || !*ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ group = *ptr;
+ *ptr = (struct group *)0;
+ --group -> refcnt;
+ rc_register (file, line, ptr, group, group -> refcnt, 1, RC_MISC);
+ if (group -> refcnt > 0)
+ return 1;
+
+ if (group -> refcnt < 0) {
+ log_error ("%s(%d): negative refcnt!", file, line);
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history (group);
+#endif
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ if (group -> object)
+ group_object_dereference (&group -> object, file, line);
+ if (group -> subnet)
+ subnet_dereference (&group -> subnet, file, line);
+ if (group -> shared_network)
+ shared_network_dereference (&group -> shared_network,
+ file, line);
+ if (group -> statements)
+ executable_statement_dereference (&group -> statements,
+ file, line);
+ if (group -> next)
+ group_dereference (&group -> next, file, line);
+ dfree (group, file, line);
+ return 1;
+}
+
+struct dhcp_packet *new_dhcp_packet (file, line)
+ const char *file;
+ int line;
+{
+ struct dhcp_packet *rval;
+ rval = (struct dhcp_packet *)dmalloc (sizeof (struct dhcp_packet),
+ file, line);
+ return rval;
+}
+
+struct protocol *new_protocol (file, line)
+ const char *file;
+ int line;
+{
+ struct protocol *rval = dmalloc (sizeof (struct protocol), file, line);
+ return rval;
+}
+
+struct domain_search_list *new_domain_search_list (file, line)
+ const char *file;
+ int line;
+{
+ struct domain_search_list *rval =
+ dmalloc (sizeof (struct domain_search_list), file, line);
+ return rval;
+}
+
+struct name_server *new_name_server (file, line)
+ const char *file;
+ int line;
+{
+ struct name_server *rval =
+ dmalloc (sizeof (struct name_server), file, line);
+ return rval;
+}
+
+void free_name_server (ptr, file, line)
+ struct name_server *ptr;
+ const char *file;
+ int line;
+{
+ dfree ((void *)ptr, file, line);
+}
+
+struct option *new_option (name, file, line)
+ const char *name;
+ const char *file;
+ int line;
+{
+ struct option *rval;
+ int len;
+
+ len = strlen(name);
+
+ rval = dmalloc(sizeof(struct option) + len + 1, file, line);
+
+ if(rval) {
+ memcpy(rval + 1, name, len);
+ rval->name = (char *)(rval + 1);
+ }
+
+ return rval;
+}
+
+struct universe *new_universe (file, line)
+ const char *file;
+ int line;
+{
+ struct universe *rval =
+ dmalloc (sizeof (struct universe), file, line);
+ return rval;
+}
+
+void free_universe (ptr, file, line)
+ struct universe *ptr;
+ const char *file;
+ int line;
+{
+ dfree ((void *)ptr, file, line);
+}
+
+void free_domain_search_list (ptr, file, line)
+ struct domain_search_list *ptr;
+ const char *file;
+ int line;
+{
+ dfree ((void *)ptr, file, line);
+}
+
+void free_protocol (ptr, file, line)
+ struct protocol *ptr;
+ const char *file;
+ int line;
+{
+ dfree ((void *)ptr, file, line);
+}
+
+void free_dhcp_packet (ptr, file, line)
+ struct dhcp_packet *ptr;
+ const char *file;
+ int line;
+{
+ dfree ((void *)ptr, file, line);
+}
+
+struct client_lease *new_client_lease (file, line)
+ const char *file;
+ int line;
+{
+ return (struct client_lease *)dmalloc (sizeof (struct client_lease),
+ file, line);
+}
+
+void free_client_lease (lease, file, line)
+ struct client_lease *lease;
+ const char *file;
+ int line;
+{
+ dfree (lease, file, line);
+}
+
+pair free_pairs;
+
+pair new_pair (file, line)
+ const char *file;
+ int line;
+{
+ pair foo;
+
+ if (free_pairs) {
+ foo = free_pairs;
+ free_pairs = foo -> cdr;
+ memset (foo, 0, sizeof *foo);
+ dmalloc_reuse (foo, file, line, 0);
+ return foo;
+ }
+
+ foo = dmalloc (sizeof *foo, file, line);
+ if (!foo)
+ return foo;
+ memset (foo, 0, sizeof *foo);
+ return foo;
+}
+
+void free_pair (foo, file, line)
+ pair foo;
+ const char *file;
+ int line;
+{
+ foo -> cdr = free_pairs;
+ free_pairs = foo;
+ dmalloc_reuse (free_pairs, __FILE__, __LINE__, 0);
+}
+
+#if defined (DEBUG_MEMORY_LEAKAGE) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+void relinquish_free_pairs ()
+{
+ pair pf, pc;
+
+ for (pf = free_pairs; pf; pf = pc) {
+ pc = pf -> cdr;
+ dfree (pf, MDL);
+ }
+ free_pairs = (pair)0;
+}
+#endif
+
+struct expression *free_expressions;
+
+int expression_allocate (cptr, file, line)
+ struct expression **cptr;
+ const char *file;
+ int line;
+{
+ struct expression *rval;
+
+ if (free_expressions) {
+ rval = free_expressions;
+ free_expressions = rval -> data.not;
+ dmalloc_reuse (rval, file, line, 1);
+ } else {
+ rval = dmalloc (sizeof (struct expression), file, line);
+ if (!rval)
+ return 0;
+ }
+ memset (rval, 0, sizeof *rval);
+ return expression_reference (cptr, rval, file, line);
+}
+
+int expression_reference (ptr, src, file, line)
+ struct expression **ptr;
+ struct expression *src;
+ const char *file;
+ int line;
+{
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct expression *)0;
+#endif
+ }
+ *ptr = src;
+ src -> refcnt++;
+ rc_register (file, line, ptr, src, src -> refcnt, 0, RC_MISC);
+ return 1;
+}
+
+void free_expression (expr, file, line)
+ struct expression *expr;
+ const char *file;
+ int line;
+{
+ expr -> data.not = free_expressions;
+ free_expressions = expr;
+ dmalloc_reuse (free_expressions, __FILE__, __LINE__, 0);
+}
+
+#if defined (DEBUG_MEMORY_LEAKAGE) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+void relinquish_free_expressions ()
+{
+ struct expression *e, *n;
+
+ for (e = free_expressions; e; e = n) {
+ n = e -> data.not;
+ dfree (e, MDL);
+ }
+ free_expressions = (struct expression *)0;
+}
+#endif
+
+struct binding_value *free_binding_values;
+
+int binding_value_allocate (cptr, file, line)
+ struct binding_value **cptr;
+ const char *file;
+ int line;
+{
+ struct binding_value *rval;
+
+ if (free_binding_values) {
+ rval = free_binding_values;
+ free_binding_values = rval -> value.bv;
+ dmalloc_reuse (rval, file, line, 1);
+ } else {
+ rval = dmalloc (sizeof (struct binding_value), file, line);
+ if (!rval)
+ return 0;
+ }
+ memset (rval, 0, sizeof *rval);
+ return binding_value_reference (cptr, rval, file, line);
+}
+
+int binding_value_reference (ptr, src, file, line)
+ struct binding_value **ptr;
+ struct binding_value *src;
+ const char *file;
+ int line;
+{
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct binding_value *)0;
+#endif
+ }
+ *ptr = src;
+ src -> refcnt++;
+ rc_register (file, line, ptr, src, src -> refcnt, 0, RC_MISC);
+ return 1;
+}
+
+void free_binding_value (bv, file, line)
+ struct binding_value *bv;
+ const char *file;
+ int line;
+{
+ bv -> value.bv = free_binding_values;
+ free_binding_values = bv;
+ dmalloc_reuse (free_binding_values, (char *)0, 0, 0);
+}
+
+#if defined (DEBUG_MEMORY_LEAKAGE) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+void relinquish_free_binding_values ()
+{
+ struct binding_value *b, *n;
+
+ for (b = free_binding_values; b; b = n) {
+ n = b -> value.bv;
+ dfree (b, MDL);
+ }
+ free_binding_values = (struct binding_value *)0;
+}
+#endif
+
+int fundef_allocate (cptr, file, line)
+ struct fundef **cptr;
+ const char *file;
+ int line;
+{
+ struct fundef *rval;
+
+ rval = dmalloc (sizeof (struct fundef), file, line);
+ if (!rval)
+ return 0;
+ memset (rval, 0, sizeof *rval);
+ return fundef_reference (cptr, rval, file, line);
+}
+
+int fundef_reference (ptr, src, file, line)
+ struct fundef **ptr;
+ struct fundef *src;
+ const char *file;
+ int line;
+{
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct fundef *)0;
+#endif
+ }
+ *ptr = src;
+ src -> refcnt++;
+ rc_register (file, line, ptr, src, src -> refcnt, 0, RC_MISC);
+ return 1;
+}
+
+struct option_cache *free_option_caches;
+
+#if defined (DEBUG_MEMORY_LEAKAGE) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+void relinquish_free_option_caches ()
+{
+ struct option_cache *o, *n;
+
+ for (o = free_option_caches; o; o = n) {
+ n = (struct option_cache *)(o -> expression);
+ dfree (o, MDL);
+ }
+ free_option_caches = (struct option_cache *)0;
+}
+#endif
+
+int option_cache_allocate (cptr, file, line)
+ struct option_cache **cptr;
+ const char *file;
+ int line;
+{
+ struct option_cache *rval;
+
+ if (free_option_caches) {
+ rval = free_option_caches;
+ free_option_caches =
+ (struct option_cache *)(rval -> expression);
+ dmalloc_reuse (rval, file, line, 0);
+ } else {
+ rval = dmalloc (sizeof (struct option_cache), file, line);
+ if (!rval)
+ return 0;
+ }
+ memset (rval, 0, sizeof *rval);
+ return option_cache_reference (cptr, rval, file, line);
+}
+
+int option_cache_reference (ptr, src, file, line)
+ struct option_cache **ptr;
+ struct option_cache *src;
+ const char *file;
+ int line;
+{
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct option_cache *)0;
+#endif
+ }
+ *ptr = src;
+ src -> refcnt++;
+ rc_register (file, line, ptr, src, src -> refcnt, 0, RC_MISC);
+ return 1;
+}
+
+int buffer_allocate (ptr, len, file, line)
+ struct buffer **ptr;
+ unsigned len;
+ const char *file;
+ int line;
+{
+ struct buffer *bp;
+
+ /* XXXSK: should check for bad ptr values, otherwise we
+ leak memory if they are wrong */
+ bp = dmalloc (len + sizeof *bp, file, line);
+ if (!bp)
+ return 0;
+ /* XXXSK: both of these initializations are unnecessary */
+ memset (bp, 0, sizeof *bp);
+ bp -> refcnt = 0;
+ return buffer_reference (ptr, bp, file, line);
+}
+
+int buffer_reference (ptr, bp, file, line)
+ struct buffer **ptr;
+ struct buffer *bp;
+ const char *file;
+ int line;
+{
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct buffer *)0;
+#endif
+ }
+ *ptr = bp;
+ bp -> refcnt++;
+ rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC);
+ return 1;
+}
+
+int buffer_dereference (ptr, file, line)
+ struct buffer **ptr;
+ const char *file;
+ int line;
+{
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ if (!*ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ (*ptr) -> refcnt--;
+ rc_register (file, line, ptr, *ptr, (*ptr) -> refcnt, 1, RC_MISC);
+ if (!(*ptr) -> refcnt) {
+ dfree ((*ptr), file, line);
+ } else if ((*ptr) -> refcnt < 0) {
+ log_error ("%s(%d): negative refcnt!", file, line);
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history (*ptr);
+#endif
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ *ptr = (struct buffer *)0;
+ return 1;
+}
+
+int dns_host_entry_allocate (ptr, hostname, file, line)
+ struct dns_host_entry **ptr;
+ const char *hostname;
+ const char *file;
+ int line;
+{
+ struct dns_host_entry *bp;
+
+ bp = dmalloc (strlen (hostname) + sizeof *bp, file, line);
+ if (!bp)
+ return 0;
+ memset (bp, 0, sizeof *bp);
+ bp -> refcnt = 0;
+ strcpy (bp -> hostname, hostname);
+ return dns_host_entry_reference (ptr, bp, file, line);
+}
+
+int dns_host_entry_reference (ptr, bp, file, line)
+ struct dns_host_entry **ptr;
+ struct dns_host_entry *bp;
+ const char *file;
+ int line;
+{
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct dns_host_entry *)0;
+#endif
+ }
+ *ptr = bp;
+ bp -> refcnt++;
+ rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC);
+ return 1;
+}
+
+int dns_host_entry_dereference (ptr, file, line)
+ struct dns_host_entry **ptr;
+ const char *file;
+ int line;
+{
+ if (!ptr || !*ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ (*ptr)->refcnt--;
+ rc_register (file, line, ptr, *ptr, (*ptr)->refcnt, 1, RC_MISC);
+ if ((*ptr)->refcnt == 0) {
+ dfree ((*ptr), file, line);
+ } else if ((*ptr)->refcnt < 0) {
+ log_error ("%s(%d): negative refcnt!", file, line);
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history (*ptr);
+#endif
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ *ptr = (struct dns_host_entry *)0;
+ return 1;
+}
+
+int option_state_allocate (ptr, file, line)
+ struct option_state **ptr;
+ const char *file;
+ int line;
+{
+ unsigned size;
+
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct option_state *)0;
+#endif
+ }
+
+ size = sizeof **ptr + (universe_count - 1) * sizeof (void *);
+ *ptr = dmalloc (size, file, line);
+ if (*ptr) {
+ memset (*ptr, 0, size);
+ (*ptr) -> universe_count = universe_count;
+ (*ptr) -> refcnt = 1;
+ rc_register (file, line,
+ ptr, *ptr, (*ptr) -> refcnt, 0, RC_MISC);
+ return 1;
+ }
+ return 0;
+}
+
+int option_state_reference (ptr, bp, file, line)
+ struct option_state **ptr;
+ struct option_state *bp;
+ const char *file;
+ int line;
+{
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct option_state *)0;
+#endif
+ }
+ *ptr = bp;
+ bp -> refcnt++;
+ rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC);
+ return 1;
+}
+
+int option_state_dereference (ptr, file, line)
+ struct option_state **ptr;
+ const char *file;
+ int line;
+{
+ int i;
+ struct option_state *options;
+
+ if (!ptr || !*ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ options = *ptr;
+ *ptr = (struct option_state *)0;
+ --options -> refcnt;
+ rc_register (file, line, ptr, options, options -> refcnt, 1, RC_MISC);
+ if (options -> refcnt > 0)
+ return 1;
+
+ if (options -> refcnt < 0) {
+ log_error ("%s(%d): negative refcnt!", file, line);
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history (options);
+#endif
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ /* Loop through the per-universe state. */
+ for (i = 0; i < options -> universe_count; i++)
+ if (options -> universes [i] &&
+ universes [i] -> option_state_dereference)
+ ((*(universes [i] -> option_state_dereference))
+ (universes [i], options, file, line));
+
+ dfree (options, file, line);
+ return 1;
+}
+
+int executable_statement_allocate (ptr, file, line)
+ struct executable_statement **ptr;
+ const char *file;
+ int line;
+{
+ struct executable_statement *bp;
+
+ bp = dmalloc (sizeof *bp, file, line);
+ if (!bp)
+ return 0;
+ memset (bp, 0, sizeof *bp);
+ return executable_statement_reference (ptr, bp, file, line);
+}
+
+int executable_statement_reference (ptr, bp, file, line)
+ struct executable_statement **ptr;
+ struct executable_statement *bp;
+ const char *file;
+ int line;
+{
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct executable_statement *)0;
+#endif
+ }
+ *ptr = bp;
+ bp -> refcnt++;
+ rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC);
+ return 1;
+}
+
+static struct packet *free_packets;
+
+#if defined (DEBUG_MEMORY_LEAKAGE) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+void relinquish_free_packets ()
+{
+ struct packet *p, *n;
+ for (p = free_packets; p; p = n) {
+ n = (struct packet *)(p -> raw);
+ dfree (p, MDL);
+ }
+ free_packets = (struct packet *)0;
+}
+#endif
+
+int packet_allocate (ptr, file, line)
+ struct packet **ptr;
+ const char *file;
+ int line;
+{
+ struct packet *p;
+
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct packet *)0;
+#endif
+ }
+
+ if (free_packets) {
+ p = free_packets;
+ free_packets = (struct packet *)(p -> raw);
+ dmalloc_reuse (p, file, line, 1);
+ } else {
+ p = dmalloc (sizeof *p, file, line);
+ }
+ if (p) {
+ memset (p, 0, sizeof *p);
+ return packet_reference (ptr, p, file, line);
+ }
+ return 0;
+}
+
+int packet_reference (ptr, bp, file, line)
+ struct packet **ptr;
+ struct packet *bp;
+ const char *file;
+ int line;
+{
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct packet *)0;
+#endif
+ }
+ *ptr = bp;
+ bp -> refcnt++;
+ rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC);
+ return 1;
+}
+
+int packet_dereference (ptr, file, line)
+ struct packet **ptr;
+ const char *file;
+ int line;
+{
+ int i;
+ struct packet *packet;
+
+ if (!ptr || !*ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ packet = *ptr;
+ *ptr = (struct packet *)0;
+ --packet -> refcnt;
+ rc_register (file, line, ptr, packet, packet -> refcnt, 1, RC_MISC);
+ if (packet -> refcnt > 0)
+ return 1;
+
+ if (packet -> refcnt < 0) {
+ log_error ("%s(%d): negative refcnt!", file, line);
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history (packet);
+#endif
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ if (packet -> options)
+ option_state_dereference (&packet -> options, file, line);
+ if (packet -> interface)
+ interface_dereference (&packet -> interface, MDL);
+ if (packet -> shared_network)
+ shared_network_dereference (&packet -> shared_network, MDL);
+ for (i = 0; i < packet -> class_count && i < PACKET_MAX_CLASSES; i++) {
+ if (packet -> classes [i])
+ omapi_object_dereference ((omapi_object_t **)
+ &packet -> classes [i], MDL);
+ }
+ packet -> raw = (struct dhcp_packet *)free_packets;
+ free_packets = packet;
+ dmalloc_reuse (free_packets, __FILE__, __LINE__, 0);
+ return 1;
+}
+
+int dns_zone_allocate (ptr, file, line)
+ struct dns_zone **ptr;
+ const char *file;
+ int line;
+{
+ struct dns_zone *d;
+
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct dns_zone *)0;
+#endif
+ }
+
+ d = dmalloc (sizeof *d, file, line);
+ if (d) {
+ memset (d, 0, sizeof *d);
+ return dns_zone_reference (ptr, d, file, line);
+ }
+ return 0;
+}
+
+int dns_zone_reference (ptr, bp, file, line)
+ struct dns_zone **ptr;
+ struct dns_zone *bp;
+ const char *file;
+ int line;
+{
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct dns_zone *)0;
+#endif
+ }
+ *ptr = bp;
+ bp -> refcnt++;
+ rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC);
+ return 1;
+}
+
+int binding_scope_allocate (ptr, file, line)
+ struct binding_scope **ptr;
+ const char *file;
+ int line;
+{
+ struct binding_scope *bp;
+
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ bp = dmalloc (sizeof *bp, file, line);
+ if (!bp)
+ return 0;
+ memset (bp, 0, sizeof *bp);
+ binding_scope_reference (ptr, bp, file, line);
+ return 1;
+}
+
+int binding_scope_reference (ptr, bp, file, line)
+ struct binding_scope **ptr;
+ struct binding_scope *bp;
+ const char *file;
+ int line;
+{
+ if (!ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (*ptr) {
+ log_error ("%s(%d): non-null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct binding_scope *)0;
+#endif
+ }
+ *ptr = bp;
+ bp -> refcnt++;
+ rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC);
+ return 1;
+}
+
+/*!
+ * \brief Constructs a null-terminated data_string from a char* and length.
+ *
+ * Allocates a data_string and copies into it the given length of bytes
+ * from the given source, adding a terminating null if not present in the source
+ * at length-1.
+ *
+ * \param new_string pointer to the data_string to construct. Cannot be
+ * NULL. Note that its contents will be overwritten. Passing in the address
+ * of an allocated data_string will result in memory leaks.
+ * \param src data to be copied. Cannot be NULL.
+ * \param len length of the data to copied
+ *
+ * \return 1 - if the data_string is constructed successfully, 0 if
+ * target data_struct is NULL or the buffer allocation fails.
+ */
+int
+data_string_new(struct data_string *new_string,
+ const char *src, unsigned int len,
+ const char *file, int line)
+{
+ unsigned int copy_len = 0;
+
+ if (new_string == NULL) {
+ log_error("data_string_new: new_string cannot be NULL %s(%d)",
+ file, line);
+ return (0);
+ }
+
+ if (src == NULL) {
+ log_error("data_string_new: src cannot be NULL %s(%d)",
+ file, line);
+ return (0);
+ }
+
+ memset(new_string, 0, sizeof (struct data_string));
+
+ /* If we already have a NULL back off length by one. This lets
+ * us always just add a NULL at the end. */
+ copy_len = (len > 0 && src[len - 1 ] == 0) ? len - 1 : len;
+
+ /* Allocate the buffer, accounting for terminating null */
+ if (!buffer_allocate(&(new_string->buffer), copy_len + 1, MDL)) {
+ log_error("data_string_new: No memory %s(%d)", file, line);
+ return (0);
+ }
+
+ /* Only copy if there's something to copy */
+ if (copy_len > 0) {
+ memcpy(new_string->buffer->data, src, copy_len);
+ }
+
+ /* Always tack on the null */
+ new_string->buffer->data[copy_len + 1] = 0;
+
+ /* Update data_string accessor values. Note len does NOT include
+ * the NULL. */
+ new_string->data = new_string->buffer->data;
+ new_string->len = copy_len;
+ new_string->terminated = 1;
+
+ return (1);
+}
+
+/* Make a copy of the data in data_string, upping the buffer reference
+ count if there's a buffer. */
+
+void
+data_string_copy(struct data_string *dest, const struct data_string *src,
+ const char *file, int line)
+{
+ if (src -> buffer) {
+ buffer_reference (&dest -> buffer, src -> buffer, file, line);
+ } else {
+ dest->buffer = NULL;
+ }
+ dest -> data = src -> data;
+ dest -> terminated = src -> terminated;
+ dest -> len = src -> len;
+}
+
+/* Release the reference count to a data string's buffer (if any) and
+ zero out the other information, yielding the null data string. */
+
+void data_string_forget (data, file, line)
+ struct data_string *data;
+ const char *file;
+ int line;
+{
+ if (data -> buffer)
+ buffer_dereference (&data -> buffer, file, line);
+ memset (data, 0, sizeof *data);
+}
+
+/* If the data_string is larger than the specified length, reduce
+ the data_string to the specified size. */
+
+void data_string_truncate (dp, len)
+ struct data_string *dp;
+ int len;
+{
+ /* XXX: do we need to consider the "terminated" flag in the check? */
+ if (len < dp -> len) {
+ dp -> terminated = 0;
+ dp -> len = len;
+ }
+}
diff --git a/common/bpf.c b/common/bpf.c
new file mode 100644
index 0000000..27e55aa
--- /dev/null
+++ b/common/bpf.c
@@ -0,0 +1,607 @@
+/* bpf.c
+
+ BPF socket interface code, originally contributed by Archie Cobbs. */
+
+/*
+ * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ * This software was contributed to Internet Systems Consortium
+ * by Archie Cobbs.
+ *
+ * Patches for FDDI support on Digital Unix were written by Bill
+ * Stapleton, and maintained for a while by Mike Meredith before he
+ * managed to get me to integrate them.
+ */
+
+#include "dhcpd.h"
+#if defined (USE_BPF_SEND) || defined (USE_BPF_RECEIVE) \
+ || defined (USE_LPF_RECEIVE)
+# if defined (USE_LPF_RECEIVE)
+# include <asm/types.h>
+# include <linux/filter.h>
+# define bpf_insn sock_filter /* Linux: dare to be gratuitously different. */
+# else
+# include <sys/ioctl.h>
+# include <sys/uio.h>
+# include <net/bpf.h>
+# include <net/if_types.h>
+# if defined (NEED_OSF_PFILT_HACKS)
+# include <net/pfilt.h>
+# endif
+# endif
+
+#include <netinet/in_systm.h>
+#include "includes/netinet/ip.h"
+#include "includes/netinet/udp.h"
+#include "includes/netinet/if_ether.h"
+#endif
+
+#ifdef USE_BPF_RECEIVE
+#include <ifaddrs.h>
+#endif
+
+#include <errno.h>
+
+/* Reinitializes the specified interface after an address change. This
+ is not required for packet-filter APIs. */
+
+#ifdef USE_BPF_SEND
+void if_reinitialize_send (info)
+ struct interface_info *info;
+{
+}
+#endif
+
+#ifdef USE_BPF_RECEIVE
+void if_reinitialize_receive (info)
+ struct interface_info *info;
+{
+}
+#endif
+
+/* Called by get_interface_list for each interface that's discovered.
+ Opens a packet filter for each interface and adds it to the select
+ mask. */
+
+#if defined (USE_BPF_SEND) || defined (USE_BPF_RECEIVE)
+int if_register_bpf (info)
+ struct interface_info *info;
+{
+ int sock;
+ char filename[50];
+ int b;
+
+ /* Open a BPF device */
+ for (b = 0; 1; b++) {
+ /* %Audit% 31 bytes max. %2004.06.17,Safe% */
+ sprintf(filename, BPF_FORMAT, b);
+ sock = open (filename, O_RDWR, 0);
+ if (sock < 0) {
+ if (errno == EBUSY) {
+ continue;
+ } else {
+ if (!b)
+ log_fatal ("No bpf devices.%s%s%s",
+ " Please read the README",
+ " section for your operating",
+ " system.");
+ log_fatal ("Can't find free bpf: %m");
+ }
+ } else {
+ break;
+ }
+ }
+
+ /* Set the BPF device to point at this interface. */
+ if (ioctl (sock, BIOCSETIF, info -> ifp) < 0)
+ log_fatal ("Can't attach interface %s to bpf device %s: %m",
+ info -> name, filename);
+
+ get_hw_addr(info->name, &info->hw_address);
+
+ return sock;
+}
+#endif /* USE_BPF_SEND || USE_BPF_RECEIVE */
+
+#ifdef USE_BPF_SEND
+void if_register_send (info)
+ struct interface_info *info;
+{
+ /* If we're using the bpf API for sending and receiving,
+ we don't need to register this interface twice. */
+#ifndef USE_BPF_RECEIVE
+ info -> wfdesc = if_register_bpf (info, interface);
+#else
+ info -> wfdesc = info -> rfdesc;
+#endif
+ if (!quiet_interface_discovery)
+ log_info ("Sending on BPF/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+
+void if_deregister_send (info)
+ struct interface_info *info;
+{
+ /* If we're using the bpf API for sending and receiving,
+ we don't need to register this interface twice. */
+#ifndef USE_BPF_RECEIVE
+ close (info -> wfdesc);
+#endif
+ info -> wfdesc = -1;
+
+ if (!quiet_interface_discovery)
+ log_info ("Disabling output on BPF/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+#endif /* USE_BPF_SEND */
+
+#if defined (USE_BPF_RECEIVE) || defined (USE_LPF_RECEIVE)
+/* Packet filter program...
+ XXX Changes to the filter program may require changes to the constant
+ offsets used in if_register_send to patch the BPF program! XXX */
+
+struct bpf_insn dhcp_bpf_filter [] = {
+ /* Make sure this is an IP packet... */
+ BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 12),
+ BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8),
+
+ /* Make sure it's a UDP packet... */
+ BPF_STMT (BPF_LD + BPF_B + BPF_ABS, 23),
+ BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
+
+ /* Make sure this isn't a fragment... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20),
+ BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+
+ /* Get the IP header length... */
+ BPF_STMT (BPF_LDX + BPF_B + BPF_MSH, 14),
+
+ /* Make sure it's to the right port... */
+ BPF_STMT (BPF_LD + BPF_H + BPF_IND, 16),
+ BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, 67, 0, 1), /* patch */
+
+ /* If we passed all the tests, ask for the whole packet. */
+ BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
+
+ /* Otherwise, drop it. */
+ BPF_STMT(BPF_RET+BPF_K, 0),
+};
+
+#if defined (DEC_FDDI)
+struct bpf_insn *bpf_fddi_filter;
+#endif
+
+int dhcp_bpf_filter_len = sizeof dhcp_bpf_filter / sizeof (struct bpf_insn);
+#if defined (HAVE_TR_SUPPORT)
+struct bpf_insn dhcp_bpf_tr_filter [] = {
+ /* accept all token ring packets due to variable length header */
+ /* if we want to get clever, insert the program here */
+
+ /* If we passed all the tests, ask for the whole packet. */
+ BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
+
+ /* Otherwise, drop it. */
+ BPF_STMT(BPF_RET+BPF_K, 0),
+};
+
+int dhcp_bpf_tr_filter_len = (sizeof dhcp_bpf_tr_filter /
+ sizeof (struct bpf_insn));
+#endif /* HAVE_TR_SUPPORT */
+#endif /* USE_LPF_RECEIVE || USE_BPF_RECEIVE */
+
+#if defined (USE_BPF_RECEIVE)
+void if_register_receive (info)
+ struct interface_info *info;
+{
+ int flag = 1;
+ struct bpf_version v;
+ struct bpf_program p;
+#ifdef NEED_OSF_PFILT_HACKS
+ u_int32_t bits;
+#endif
+#ifdef DEC_FDDI
+ int link_layer;
+#endif /* DEC_FDDI */
+
+ /* Open a BPF device and hang it on this interface... */
+ info -> rfdesc = if_register_bpf (info);
+
+ /* Make sure the BPF version is in range... */
+ if (ioctl (info -> rfdesc, BIOCVERSION, &v) < 0)
+ log_fatal ("Can't get BPF version: %m");
+
+ if (v.bv_major != BPF_MAJOR_VERSION ||
+ v.bv_minor < BPF_MINOR_VERSION)
+ log_fatal ("BPF version mismatch - recompile DHCP!");
+
+ /* Set immediate mode so that reads return as soon as a packet
+ comes in, rather than waiting for the input buffer to fill with
+ packets. */
+ if (ioctl (info -> rfdesc, BIOCIMMEDIATE, &flag) < 0)
+ log_fatal ("Can't set immediate mode on bpf device: %m");
+
+#ifdef NEED_OSF_PFILT_HACKS
+ /* Allow the copyall flag to be set... */
+ if (ioctl(info -> rfdesc, EIOCALLOWCOPYALL, &flag) < 0)
+ log_fatal ("Can't set ALLOWCOPYALL: %m");
+
+ /* Clear all the packet filter mode bits first... */
+ bits = 0;
+ if (ioctl (info -> rfdesc, EIOCMBIS, &bits) < 0)
+ log_fatal ("Can't clear pfilt bits: %m");
+
+ /* Set the ENBATCH, ENCOPYALL, ENBPFHDR bits... */
+ bits = ENBATCH | ENCOPYALL | ENBPFHDR;
+ if (ioctl (info -> rfdesc, EIOCMBIS, &bits) < 0)
+ log_fatal ("Can't set ENBATCH|ENCOPYALL|ENBPFHDR: %m");
+#endif
+ /* Get the required BPF buffer length from the kernel. */
+ if (ioctl (info -> rfdesc, BIOCGBLEN, &info -> rbuf_max) < 0)
+ log_fatal ("Can't get bpf buffer length: %m");
+ info -> rbuf = dmalloc (info -> rbuf_max, MDL);
+ if (!info -> rbuf)
+ log_fatal ("Can't allocate %ld bytes for bpf input buffer.",
+ (long)(info -> rbuf_max));
+ info -> rbuf_offset = 0;
+ info -> rbuf_len = 0;
+
+ /* Set up the bpf filter program structure. */
+ p.bf_len = dhcp_bpf_filter_len;
+
+#ifdef DEC_FDDI
+ /* See if this is an FDDI interface, flag it for later. */
+ if (ioctl(info -> rfdesc, BIOCGDLT, &link_layer) >= 0 &&
+ link_layer == DLT_FDDI) {
+ if (!bpf_fddi_filter) {
+ bpf_fddi_filter = dmalloc (sizeof bpf_fddi_filter,
+ MDL);
+ if (!bpf_fddi_filter)
+ log_fatal ("No memory for FDDI filter.");
+ memcpy (bpf_fddi_filter,
+ dhcp_bpf_filter, sizeof dhcp_bpf_filter);
+ /* Patch the BPF program to account for the difference
+ in length between ethernet headers (14), FDDI and
+ 802.2 headers (16 +8=24, +10).
+ XXX changes to filter program may require changes to
+ XXX the insn number(s) used below! */
+ bpf_fddi_filter[0].k += 10;
+ bpf_fddi_filter[2].k += 10;
+ bpf_fddi_filter[4].k += 10;
+ bpf_fddi_filter[6].k += 10;
+ bpf_fddi_filter[7].k += 10;
+ }
+ p.bf_insns = bpf_fddi_filter;
+ } else
+#endif /* DEC_FDDI */
+ p.bf_insns = dhcp_bpf_filter;
+
+ /* Patch the server port into the BPF program...
+ XXX changes to filter program may require changes
+ to the insn number(s) used below! XXX */
+ dhcp_bpf_filter [8].k = ntohs (local_port);
+
+ if (ioctl (info -> rfdesc, BIOCSETF, &p) < 0)
+ log_fatal ("Can't install packet filter program: %m");
+ if (!quiet_interface_discovery)
+ log_info ("Listening on BPF/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+
+void if_deregister_receive (info)
+ struct interface_info *info;
+{
+ close (info -> rfdesc);
+ info -> rfdesc = -1;
+
+ if (!quiet_interface_discovery)
+ log_info ("Disabling input on BPF/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+#endif /* USE_BPF_RECEIVE */
+
+#ifdef USE_BPF_SEND
+ssize_t send_packet (interface, packet, raw, len, from, to, hto)
+ struct interface_info *interface;
+ struct packet *packet;
+ struct dhcp_packet *raw;
+ size_t len;
+ struct in_addr from;
+ struct sockaddr_in *to;
+ struct hardware *hto;
+{
+ unsigned hbufp = 0, ibufp = 0;
+ double hw [4];
+ double ip [32];
+ struct iovec iov [3];
+ int result;
+
+ if (!strcmp (interface -> name, "fallback"))
+ return send_fallback (interface, packet, raw,
+ len, from, to, hto);
+
+ if (hto == NULL && interface->anycast_mac_addr.hlen)
+ hto = &interface->anycast_mac_addr;
+
+ /* Assemble the headers... */
+ assemble_hw_header (interface, (unsigned char *)hw, &hbufp, hto);
+ assemble_udp_ip_header (interface,
+ (unsigned char *)ip, &ibufp, from.s_addr,
+ to -> sin_addr.s_addr, to -> sin_port,
+ (unsigned char *)raw, len);
+
+ /* Fire it off */
+ iov [0].iov_base = ((char *)hw);
+ iov [0].iov_len = hbufp;
+ iov [1].iov_base = ((char *)ip);
+ iov [1].iov_len = ibufp;
+ iov [2].iov_base = (char *)raw;
+ iov [2].iov_len = len;
+
+ result = writev(interface -> wfdesc, iov, 3);
+ if (result < 0)
+ log_error ("send_packet: %m");
+ return result;
+}
+#endif /* USE_BPF_SEND */
+
+#ifdef USE_BPF_RECEIVE
+ssize_t receive_packet (interface, buf, len, from, hfrom)
+ struct interface_info *interface;
+ unsigned char *buf;
+ size_t len;
+ struct sockaddr_in *from;
+ struct hardware *hfrom;
+{
+ int length = 0;
+ int offset = 0;
+ struct bpf_hdr hdr;
+ unsigned paylen;
+
+ /* All this complexity is because BPF doesn't guarantee
+ that only one packet will be returned at a time. We're
+ getting what we deserve, though - this is a terrible abuse
+ of the BPF interface. Sigh. */
+
+ /* Process packets until we get one we can return or until we've
+ done a read and gotten nothing we can return... */
+
+ /* If the buffer is empty, fill it. */
+ if (interface->rbuf_offset >= interface->rbuf_len) {
+ length = read(interface->rfdesc, interface->rbuf,
+ (size_t)interface->rbuf_max);
+ if (length <= 0) {
+#ifdef __FreeBSD__
+ if (errno == ENXIO) {
+#else
+ if (errno == EIO) {
+#endif
+ dhcp_interface_remove
+ ((omapi_object_t *)interface, NULL);
+ }
+ return (length);
+ }
+ interface->rbuf_offset = 0;
+ interface->rbuf_len = BPF_WORDALIGN(length);
+ }
+
+ do {
+ /* If there isn't room for a whole bpf header, something went
+ wrong, but we'll ignore it and hope it goes away... XXX */
+ if (interface->rbuf_len -
+ interface->rbuf_offset < sizeof hdr) {
+ interface->rbuf_offset = interface->rbuf_len;
+ continue;
+ }
+
+ /* Copy out a bpf header... */
+ memcpy(&hdr, &interface->rbuf[interface->rbuf_offset],
+ sizeof hdr);
+
+ /* If the bpf header plus data doesn't fit in what's left
+ of the buffer, stick head in sand yet again... */
+ if (interface->rbuf_offset +
+ hdr.bh_hdrlen + hdr.bh_caplen > interface->rbuf_len) {
+ interface->rbuf_offset = interface->rbuf_len;
+ continue;
+ }
+
+ /* If the captured data wasn't the whole packet, or if
+ the packet won't fit in the input buffer, all we
+ can do is drop it. */
+ if (hdr.bh_caplen != hdr.bh_datalen) {
+ interface->rbuf_offset =
+ BPF_WORDALIGN(interface->rbuf_offset +
+ hdr.bh_hdrlen + hdr.bh_caplen);
+ continue;
+ }
+
+ /* Skip over the BPF header... */
+ interface->rbuf_offset += hdr.bh_hdrlen;
+
+ /* Decode the physical header... */
+ offset = decode_hw_header(interface, interface->rbuf,
+ interface->rbuf_offset, hfrom);
+
+ /* If a physical layer checksum failed (dunno of any
+ physical layer that supports this, but WTH), skip this
+ packet. */
+ if (offset < 0) {
+ interface->rbuf_offset =
+ BPF_WORDALIGN(interface->rbuf_offset +
+ hdr.bh_caplen);
+ continue;
+ }
+ interface->rbuf_offset += offset;
+ hdr.bh_caplen -= offset;
+
+ /* Decode the IP and UDP headers... */
+ offset = decode_udp_ip_header(interface, interface->rbuf,
+ interface->rbuf_offset,
+ from, hdr.bh_caplen, &paylen, 1);
+
+ /* If the IP or UDP checksum was bad, skip the packet... */
+ if (offset < 0) {
+ interface->rbuf_offset =
+ BPF_WORDALIGN(interface->rbuf_offset +
+ hdr.bh_caplen);
+ continue;
+ }
+ interface->rbuf_offset = interface->rbuf_offset + offset;
+ hdr.bh_caplen -= offset;
+
+ /* If there's not enough room to stash the packet data,
+ we have to skip it (this shouldn't happen in real
+ life, though). */
+ if (hdr.bh_caplen > len) {
+ interface->rbuf_offset =
+ BPF_WORDALIGN(interface->rbuf_offset +
+ hdr.bh_caplen);
+ continue;
+ }
+
+ /* Copy out the data in the packet... */
+ memcpy(buf, interface->rbuf + interface->rbuf_offset, paylen);
+ interface->rbuf_offset =
+ BPF_WORDALIGN(interface->rbuf_offset + hdr.bh_caplen);
+ return paylen;
+ } while (interface->rbuf_offset < interface->rbuf_len);
+
+ return (0);
+}
+
+int can_unicast_without_arp (ip)
+ struct interface_info *ip;
+{
+ return 1;
+}
+
+int can_receive_unicast_unconfigured (ip)
+ struct interface_info *ip;
+{
+ return 1;
+}
+
+int supports_multiple_interfaces (ip)
+ struct interface_info *ip;
+{
+ return 1;
+}
+
+void maybe_setup_fallback ()
+{
+ isc_result_t status;
+ struct interface_info *fbi = (struct interface_info *)0;
+ if (setup_fallback (&fbi, MDL)) {
+ if_register_fallback (fbi);
+ status = omapi_register_io_object ((omapi_object_t *)fbi,
+ if_readsocket, 0,
+ fallback_discard, 0, 0);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register I/O handle for %s: %s",
+ fbi -> name, isc_result_totext (status));
+ interface_dereference (&fbi, MDL);
+ }
+}
+
+void
+get_hw_addr(const char *name, struct hardware *hw) {
+ struct ifaddrs *ifa;
+ struct ifaddrs *p;
+ struct sockaddr_dl *sa;
+
+ if (getifaddrs(&ifa) != 0) {
+ log_fatal("Error getting interface information; %m");
+ }
+
+ /*
+ * Loop through our interfaces finding a match.
+ */
+ sa = NULL;
+ for (p=ifa; (p != NULL) && (sa == NULL); p = p->ifa_next) {
+ if ((p->ifa_addr->sa_family == AF_LINK) &&
+ !strcmp(p->ifa_name, name)) {
+ sa = (struct sockaddr_dl *)p->ifa_addr;
+ }
+ }
+ if (sa == NULL) {
+ log_fatal("No interface called '%s'", name);
+ }
+
+ /*
+ * Pull out the appropriate information.
+ */
+ switch (sa->sdl_type) {
+ case IFT_ETHER:
+#if defined (IFT_L2VLAN)
+ case IFT_L2VLAN:
+#endif
+ hw->hlen = sa->sdl_alen + 1;
+ hw->hbuf[0] = HTYPE_ETHER;
+ memcpy(&hw->hbuf[1], LLADDR(sa), sa->sdl_alen);
+ break;
+ case IFT_ISO88023:
+ case IFT_ISO88024: /* "token ring" */
+ case IFT_ISO88025:
+ case IFT_ISO88026:
+ hw->hlen = sa->sdl_alen + 1;
+ hw->hbuf[0] = HTYPE_IEEE802;
+ memcpy(&hw->hbuf[1], LLADDR(sa), sa->sdl_alen);
+ break;
+#ifdef IFT_FDDI
+ case IFT_FDDI:
+ hw->hlen = sa->sdl_alen + 1;
+ hw->hbuf[0] = HTYPE_FDDI;
+ memcpy(&hw->hbuf[1], LLADDR(sa), sa->sdl_alen);
+ break;
+#endif /* IFT_FDDI */
+ default:
+ log_fatal("Unsupported device type %d for \"%s\"",
+ sa->sdl_type, name);
+ }
+
+ freeifaddrs(ifa);
+}
+#endif
diff --git a/common/comapi.c b/common/comapi.c
new file mode 100644
index 0000000..ee1c7ec
--- /dev/null
+++ b/common/comapi.c
@@ -0,0 +1,921 @@
+/* omapi.c
+
+ OMAPI object interfaces for the DHCP server. */
+
+/*
+ * Copyright (c) 2012,2014 Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1999-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+/* Many, many thanks to Brian Murrell and BCtel for this code - BCtel
+ provided the funding that resulted in this code and the entire
+ OMAPI support library being written, and Brian helped brainstorm
+ and refine the requirements. To the extent that this code is
+ useful, you have Brian and BCtel to thank. Any limitations in the
+ code are a result of mistakes on my part. -- Ted Lemon */
+
+#include "dhcpd.h"
+#include <omapip/omapip_p.h>
+
+OMAPI_OBJECT_ALLOC (subnet, struct subnet, dhcp_type_subnet)
+OMAPI_OBJECT_ALLOC (shared_network, struct shared_network,
+ dhcp_type_shared_network)
+OMAPI_OBJECT_ALLOC (group_object, struct group_object, dhcp_type_group)
+OMAPI_OBJECT_ALLOC (dhcp_control, dhcp_control_object_t, dhcp_type_control)
+
+omapi_object_type_t *dhcp_type_interface;
+omapi_object_type_t *dhcp_type_group;
+omapi_object_type_t *dhcp_type_shared_network;
+omapi_object_type_t *dhcp_type_subnet;
+omapi_object_type_t *dhcp_type_control;
+dhcp_control_object_t *dhcp_control_object;
+
+void dhcp_common_objects_setup ()
+{
+ isc_result_t status;
+
+ status = omapi_object_type_register (&dhcp_type_control,
+ "control",
+ dhcp_control_set_value,
+ dhcp_control_get_value,
+ dhcp_control_destroy,
+ dhcp_control_signal_handler,
+ dhcp_control_stuff_values,
+ dhcp_control_lookup,
+ dhcp_control_create,
+ dhcp_control_remove, 0, 0, 0,
+ sizeof (dhcp_control_object_t),
+ 0, RC_MISC);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register control object type: %s",
+ isc_result_totext (status));
+ status = dhcp_control_allocate (&dhcp_control_object, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't make initial control object: %s",
+ isc_result_totext (status));
+ dhcp_control_object -> state = server_startup;
+
+ status = omapi_object_type_register (&dhcp_type_group,
+ "group",
+ dhcp_group_set_value,
+ dhcp_group_get_value,
+ dhcp_group_destroy,
+ dhcp_group_signal_handler,
+ dhcp_group_stuff_values,
+ dhcp_group_lookup,
+ dhcp_group_create,
+ dhcp_group_remove, 0, 0, 0,
+ sizeof (struct group_object), 0,
+ RC_MISC);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register group object type: %s",
+ isc_result_totext (status));
+
+ status = omapi_object_type_register (&dhcp_type_subnet,
+ "subnet",
+ dhcp_subnet_set_value,
+ dhcp_subnet_get_value,
+ dhcp_subnet_destroy,
+ dhcp_subnet_signal_handler,
+ dhcp_subnet_stuff_values,
+ dhcp_subnet_lookup,
+ dhcp_subnet_create,
+ dhcp_subnet_remove, 0, 0, 0,
+ sizeof (struct subnet), 0,
+ RC_MISC);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register subnet object type: %s",
+ isc_result_totext (status));
+
+ status = omapi_object_type_register
+ (&dhcp_type_shared_network,
+ "shared-network",
+ dhcp_shared_network_set_value,
+ dhcp_shared_network_get_value,
+ dhcp_shared_network_destroy,
+ dhcp_shared_network_signal_handler,
+ dhcp_shared_network_stuff_values,
+ dhcp_shared_network_lookup,
+ dhcp_shared_network_create,
+ dhcp_shared_network_remove, 0, 0, 0,
+ sizeof (struct shared_network), 0, RC_MISC);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register shared network object type: %s",
+ isc_result_totext (status));
+
+ interface_setup ();
+}
+
+isc_result_t dhcp_group_set_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_typed_data_t *value)
+{
+ struct group_object *group;
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_group)
+ return DHCP_R_INVALIDARG;
+ group = (struct group_object *)h;
+
+ /* XXX For now, we can only set these values on new group objects.
+ XXX Soon, we need to be able to update group objects. */
+ if (!omapi_ds_strcmp (name, "name")) {
+ if (group -> name)
+ return ISC_R_EXISTS;
+ if (value -> type == omapi_datatype_data ||
+ value -> type == omapi_datatype_string) {
+ group -> name = dmalloc (value -> u.buffer.len + 1,
+ MDL);
+ if (!group -> name)
+ return ISC_R_NOMEMORY;
+ memcpy (group -> name,
+ value -> u.buffer.value,
+ value -> u.buffer.len);
+ group -> name [value -> u.buffer.len] = 0;
+ } else
+ return DHCP_R_INVALIDARG;
+ return ISC_R_SUCCESS;
+ }
+
+ if (!omapi_ds_strcmp (name, "statements")) {
+ if (group -> group && group -> group -> statements)
+ return ISC_R_EXISTS;
+ if (!group -> group) {
+ if (!clone_group (&group -> group, root_group, MDL))
+ return ISC_R_NOMEMORY;
+ }
+ if (value -> type == omapi_datatype_data ||
+ value -> type == omapi_datatype_string) {
+ struct parse *parse;
+ int lose = 0;
+ parse = NULL;
+ status = new_parse(&parse, -1,
+ (char *) value->u.buffer.value,
+ value->u.buffer.len,
+ "network client", 0);
+ if (status != ISC_R_SUCCESS || parse == NULL)
+ return status;
+ if (!(parse_executable_statements
+ (&group -> group -> statements, parse, &lose,
+ context_any))) {
+ end_parse (&parse);
+ return DHCP_R_BADPARSE;
+ }
+ end_parse (&parse);
+ return ISC_R_SUCCESS;
+ } else
+ return DHCP_R_INVALIDARG;
+ }
+
+ /* Try to find some inner object that can take the value. */
+ if (h -> inner && h -> inner -> type -> set_value) {
+ status = ((*(h -> inner -> type -> set_value))
+ (h -> inner, id, name, value));
+ if (status == ISC_R_SUCCESS || status == DHCP_R_UNCHANGED)
+ return status;
+ }
+
+ return ISC_R_NOTFOUND;
+}
+
+
+isc_result_t dhcp_group_get_value (omapi_object_t *h, omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_value_t **value)
+{
+ struct group_object *group;
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_group)
+ return DHCP_R_INVALIDARG;
+ group = (struct group_object *)h;
+
+ if (!omapi_ds_strcmp (name, "name"))
+ return omapi_make_string_value (value,
+ name, group -> name, MDL);
+
+ /* Try to find some inner object that can take the value. */
+ if (h -> inner && h -> inner -> type -> get_value) {
+ status = ((*(h -> inner -> type -> get_value))
+ (h -> inner, id, name, value));
+ if (status == ISC_R_SUCCESS)
+ return status;
+ }
+ return ISC_R_NOTFOUND;
+}
+
+isc_result_t dhcp_group_destroy (omapi_object_t *h, const char *file, int line)
+{
+ struct group_object *group, *t;
+
+ if (h -> type != dhcp_type_group)
+ return DHCP_R_INVALIDARG;
+ group = (struct group_object *)h;
+
+ if (group -> name) {
+ if (group_name_hash) {
+ t = (struct group_object *)0;
+ if (group_hash_lookup (&t, group_name_hash,
+ group -> name,
+ strlen (group -> name), MDL)) {
+ group_hash_delete (group_name_hash,
+ group -> name,
+ strlen (group -> name),
+ MDL);
+ group_object_dereference (&t, MDL);
+ }
+ }
+ dfree (group -> name, file, line);
+ group -> name = (char *)0;
+ }
+ if (group -> group)
+ group_dereference (&group -> group, MDL);
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_group_signal_handler (omapi_object_t *h,
+ const char *name, va_list ap)
+{
+ struct group_object *group;
+ isc_result_t status;
+ int updatep = 0;
+
+ if (h -> type != dhcp_type_group)
+ return DHCP_R_INVALIDARG;
+ group = (struct group_object *)h;
+
+ if (!strcmp (name, "updated")) {
+ /* A group object isn't valid if a subgroup hasn't yet been
+ associated with it. */
+ if (!group -> group)
+ return DHCP_R_INVALIDARG;
+
+ /* Group objects always have to have names. */
+ if (!group -> name) {
+ char hnbuf [64];
+ sprintf (hnbuf, "ng%08lx%08lx",
+ (unsigned long)cur_time,
+ (unsigned long)group);
+ group -> name = dmalloc (strlen (hnbuf) + 1, MDL);
+ if (!group -> name)
+ return ISC_R_NOMEMORY;
+ strcpy (group -> name, hnbuf);
+ }
+
+ supersede_group (group, 1);
+ updatep = 1;
+ }
+
+ /* Try to find some inner object that can take the value. */
+ if (h -> inner && h -> inner -> type -> get_value) {
+ status = ((*(h -> inner -> type -> signal_handler))
+ (h -> inner, name, ap));
+ if (status == ISC_R_SUCCESS)
+ return status;
+ }
+ if (updatep)
+ return ISC_R_SUCCESS;
+ return ISC_R_NOTFOUND;
+}
+
+isc_result_t dhcp_group_stuff_values (omapi_object_t *c,
+ omapi_object_t *id,
+ omapi_object_t *h)
+{
+ struct group_object *group;
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_group)
+ return DHCP_R_INVALIDARG;
+ group = (struct group_object *)h;
+
+ /* Write out all the values. */
+ if (group -> name) {
+ status = omapi_connection_put_name (c, "name");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_string (c, group -> name);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ }
+
+ /* Write out the inner object, if any. */
+ if (h -> inner && h -> inner -> type -> stuff_values) {
+ status = ((*(h -> inner -> type -> stuff_values))
+ (c, id, h -> inner));
+ if (status == ISC_R_SUCCESS)
+ return status;
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_group_lookup (omapi_object_t **lp,
+ omapi_object_t *id, omapi_object_t *ref)
+{
+ omapi_value_t *tv = (omapi_value_t *)0;
+ isc_result_t status;
+ struct group_object *group;
+
+ if (!ref)
+ return DHCP_R_NOKEYS;
+
+ /* First see if we were sent a handle. */
+ status = omapi_get_value_str (ref, id, "handle", &tv);
+ if (status == ISC_R_SUCCESS) {
+ status = omapi_handle_td_lookup (lp, tv -> value);
+
+ omapi_value_dereference (&tv, MDL);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ /* Don't return the object if the type is wrong. */
+ if ((*lp) -> type != dhcp_type_group) {
+ omapi_object_dereference (lp, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+ }
+
+ /* Now look for a name. */
+ status = omapi_get_value_str (ref, id, "name", &tv);
+ if (status == ISC_R_SUCCESS) {
+ group = (struct group_object *)0;
+ if (group_name_hash &&
+ group_hash_lookup (&group, group_name_hash,
+ (const char *)
+ tv -> value -> u.buffer.value,
+ tv -> value -> u.buffer.len, MDL)) {
+ omapi_value_dereference (&tv, MDL);
+
+ if (*lp && *lp != (omapi_object_t *)group) {
+ group_object_dereference (&group, MDL);
+ omapi_object_dereference (lp, MDL);
+ return DHCP_R_KEYCONFLICT;
+ } else if (!*lp) {
+ /* XXX fix so that hash lookup itself creates
+ XXX the reference. */
+ omapi_object_reference (lp,
+ (omapi_object_t *)group,
+ MDL);
+ group_object_dereference (&group, MDL);
+ }
+ } else if (!*lp)
+ return ISC_R_NOTFOUND;
+ }
+
+ /* If we get to here without finding a group, no valid key was
+ specified. */
+ if (!*lp)
+ return DHCP_R_NOKEYS;
+
+ if (((struct group_object *)(*lp)) -> flags & GROUP_OBJECT_DELETED) {
+ omapi_object_dereference (lp, MDL);
+ return ISC_R_NOTFOUND;
+ }
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_group_create (omapi_object_t **lp,
+ omapi_object_t *id)
+{
+ struct group_object *group;
+ isc_result_t status;
+ group = (struct group_object *)0;
+
+ status = group_object_allocate (&group, MDL);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ group -> flags = GROUP_OBJECT_DYNAMIC;
+ status = omapi_object_reference (lp, (omapi_object_t *)group, MDL);
+ group_object_dereference (&group, MDL);
+ return status;
+}
+
+isc_result_t dhcp_group_remove (omapi_object_t *lp,
+ omapi_object_t *id)
+{
+ struct group_object *group;
+ isc_result_t status;
+ if (lp -> type != dhcp_type_group)
+ return DHCP_R_INVALIDARG;
+ group = (struct group_object *)lp;
+
+ group -> flags |= GROUP_OBJECT_DELETED;
+ if (group_write_hook) {
+ if (!(*group_write_hook) (group))
+ return ISC_R_IOERROR;
+ }
+
+ status = dhcp_group_destroy ((omapi_object_t *)group, MDL);
+
+ return status;
+}
+
+isc_result_t dhcp_control_set_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_typed_data_t *value)
+{
+ dhcp_control_object_t *control;
+ isc_result_t status;
+ unsigned long newstate;
+
+ if (h -> type != dhcp_type_control)
+ return DHCP_R_INVALIDARG;
+ control = (dhcp_control_object_t *)h;
+
+ if (!omapi_ds_strcmp (name, "state")) {
+ status = omapi_get_int_value (&newstate, value);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = dhcp_set_control_state (control -> state, newstate);
+ if (status == ISC_R_SUCCESS)
+ control -> state = value -> u.integer;
+ return status;
+ }
+
+ /* Try to find some inner object that can take the value. */
+ if (h -> inner && h -> inner -> type -> set_value) {
+ status = ((*(h -> inner -> type -> set_value))
+ (h -> inner, id, name, value));
+ if (status == ISC_R_SUCCESS || status == DHCP_R_UNCHANGED)
+ return status;
+ }
+
+ return ISC_R_NOTFOUND;
+}
+
+
+isc_result_t dhcp_control_get_value (omapi_object_t *h, omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_value_t **value)
+{
+ dhcp_control_object_t *control;
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_control)
+ return DHCP_R_INVALIDARG;
+ control = (dhcp_control_object_t *)h;
+
+ if (!omapi_ds_strcmp (name, "state"))
+ return omapi_make_int_value (value,
+ name, (int)control -> state, MDL);
+
+ /* Try to find some inner object that can take the value. */
+ if (h -> inner && h -> inner -> type -> get_value) {
+ status = ((*(h -> inner -> type -> get_value))
+ (h -> inner, id, name, value));
+ if (status == ISC_R_SUCCESS)
+ return status;
+ }
+ return ISC_R_NOTFOUND;
+}
+
+isc_result_t dhcp_control_destroy (omapi_object_t *h,
+ const char *file, int line)
+{
+ if (h -> type != dhcp_type_control)
+ return DHCP_R_INVALIDARG;
+
+ /* Can't destroy the control object. */
+ return ISC_R_NOPERM;
+}
+
+isc_result_t dhcp_control_signal_handler (omapi_object_t *h,
+ const char *name, va_list ap)
+{
+ /* In this function h should be a (dhcp_control_object_t *) */
+
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_control)
+ return DHCP_R_INVALIDARG;
+
+ /* Try to find some inner object that can take the value. */
+ if (h -> inner && h -> inner -> type -> get_value) {
+ status = ((*(h -> inner -> type -> signal_handler))
+ (h -> inner, name, ap));
+ if (status == ISC_R_SUCCESS)
+ return status;
+ }
+ return ISC_R_NOTFOUND;
+}
+
+isc_result_t dhcp_control_stuff_values (omapi_object_t *c,
+ omapi_object_t *id,
+ omapi_object_t *h)
+{
+ dhcp_control_object_t *control;
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_control)
+ return DHCP_R_INVALIDARG;
+ control = (dhcp_control_object_t *)h;
+
+ /* Write out all the values. */
+ status = omapi_connection_put_name (c, "state");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32 (c, sizeof (u_int32_t));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32 (c, control -> state);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ /* Write out the inner object, if any. */
+ if (h -> inner && h -> inner -> type -> stuff_values) {
+ status = ((*(h -> inner -> type -> stuff_values))
+ (c, id, h -> inner));
+ if (status == ISC_R_SUCCESS)
+ return status;
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_control_lookup (omapi_object_t **lp,
+ omapi_object_t *id, omapi_object_t *ref)
+{
+ omapi_value_t *tv = (omapi_value_t *)0;
+ isc_result_t status;
+
+ /* First see if we were sent a handle. */
+ if (ref) {
+ status = omapi_get_value_str (ref, id, "handle", &tv);
+ if (status == ISC_R_SUCCESS) {
+ status = omapi_handle_td_lookup (lp, tv -> value);
+
+ omapi_value_dereference (&tv, MDL);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ /* Don't return the object if the type is wrong. */
+ if ((*lp) -> type != dhcp_type_control) {
+ omapi_object_dereference (lp, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+ }
+ }
+
+ /* Otherwise, stop playing coy - there's only one control object,
+ so we can just return it. */
+ dhcp_control_reference ((dhcp_control_object_t **)lp,
+ dhcp_control_object, MDL);
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_control_create (omapi_object_t **lp,
+ omapi_object_t *id)
+{
+ /* Can't create a control object - there can be only one. */
+ return ISC_R_NOPERM;
+}
+
+isc_result_t dhcp_control_remove (omapi_object_t *lp,
+ omapi_object_t *id)
+{
+ /* Form is emptiness; emptiness form. The control object
+ cannot go out of existance. */
+ return ISC_R_NOPERM;
+}
+
+isc_result_t dhcp_subnet_set_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_typed_data_t *value)
+{
+ /* In this function h should be a (struct subnet *) */
+
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_subnet)
+ return DHCP_R_INVALIDARG;
+
+ /* No values to set yet. */
+
+ /* Try to find some inner object that can take the value. */
+ if (h -> inner && h -> inner -> type -> set_value) {
+ status = ((*(h -> inner -> type -> set_value))
+ (h -> inner, id, name, value));
+ if (status == ISC_R_SUCCESS || status == DHCP_R_UNCHANGED)
+ return status;
+ }
+
+ return ISC_R_NOTFOUND;
+}
+
+
+isc_result_t dhcp_subnet_get_value (omapi_object_t *h, omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_value_t **value)
+{
+ /* In this function h should be a (struct subnet *) */
+
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_subnet)
+ return DHCP_R_INVALIDARG;
+
+ /* No values to get yet. */
+
+ /* Try to find some inner object that can provide the value. */
+ if (h -> inner && h -> inner -> type -> get_value) {
+ status = ((*(h -> inner -> type -> get_value))
+ (h -> inner, id, name, value));
+ if (status == ISC_R_SUCCESS)
+ return status;
+ }
+ return ISC_R_NOTFOUND;
+}
+
+isc_result_t dhcp_subnet_destroy (omapi_object_t *h, const char *file, int line)
+{
+#if defined (DEBUG_MEMORY_LEAKAGE) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ struct subnet *subnet;
+#endif
+
+ if (h -> type != dhcp_type_subnet)
+ return DHCP_R_INVALIDARG;
+
+#if defined (DEBUG_MEMORY_LEAKAGE) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ subnet = (struct subnet *)h;
+ if (subnet -> next_subnet)
+ subnet_dereference (&subnet -> next_subnet, file, line);
+ if (subnet -> next_sibling)
+ subnet_dereference (&subnet -> next_sibling, file, line);
+ if (subnet -> shared_network)
+ shared_network_dereference (&subnet -> shared_network,
+ file, line);
+ if (subnet -> interface)
+ interface_dereference (&subnet -> interface, file, line);
+ if (subnet -> group)
+ group_dereference (&subnet -> group, file, line);
+#endif
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_subnet_signal_handler (omapi_object_t *h,
+ const char *name, va_list ap)
+{
+ /* In this function h should be a (struct subnet *) */
+
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_subnet)
+ return DHCP_R_INVALIDARG;
+
+ /* Can't write subnets yet. */
+
+ /* Try to find some inner object that can take the value. */
+ if (h -> inner && h -> inner -> type -> get_value) {
+ status = ((*(h -> inner -> type -> signal_handler))
+ (h -> inner, name, ap));
+ if (status == ISC_R_SUCCESS)
+ return status;
+ }
+
+ return ISC_R_NOTFOUND;
+}
+
+isc_result_t dhcp_subnet_stuff_values (omapi_object_t *c,
+ omapi_object_t *id,
+ omapi_object_t *h)
+{
+ /* In this function h should be a (struct subnet *) */
+
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_subnet)
+ return DHCP_R_INVALIDARG;
+
+ /* Can't stuff subnet values yet. */
+
+ /* Write out the inner object, if any. */
+ if (h -> inner && h -> inner -> type -> stuff_values) {
+ status = ((*(h -> inner -> type -> stuff_values))
+ (c, id, h -> inner));
+ if (status == ISC_R_SUCCESS)
+ return status;
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_subnet_lookup (omapi_object_t **lp,
+ omapi_object_t *id,
+ omapi_object_t *ref)
+{
+ /* Can't look up subnets yet. */
+
+ /* If we get to here without finding a subnet, no valid key was
+ specified. */
+ if (!*lp)
+ return DHCP_R_NOKEYS;
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_subnet_create (omapi_object_t **lp,
+ omapi_object_t *id)
+{
+ return ISC_R_NOTIMPLEMENTED;
+}
+
+isc_result_t dhcp_subnet_remove (omapi_object_t *lp,
+ omapi_object_t *id)
+{
+ return ISC_R_NOTIMPLEMENTED;
+}
+
+isc_result_t dhcp_shared_network_set_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_typed_data_t *value)
+{
+ /* In this function h should be a (struct shared_network *) */
+
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_shared_network)
+ return DHCP_R_INVALIDARG;
+
+ /* No values to set yet. */
+
+ /* Try to find some inner object that can take the value. */
+ if (h -> inner && h -> inner -> type -> set_value) {
+ status = ((*(h -> inner -> type -> set_value))
+ (h -> inner, id, name, value));
+ if (status == ISC_R_SUCCESS || status == DHCP_R_UNCHANGED)
+ return status;
+ }
+
+ return ISC_R_NOTFOUND;
+}
+
+
+isc_result_t dhcp_shared_network_get_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_value_t **value)
+{
+ /* In this function h should be a (struct shared_network *) */
+
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_shared_network)
+ return DHCP_R_INVALIDARG;
+
+ /* No values to get yet. */
+
+ /* Try to find some inner object that can provide the value. */
+ if (h -> inner && h -> inner -> type -> get_value) {
+ status = ((*(h -> inner -> type -> get_value))
+ (h -> inner, id, name, value));
+ if (status == ISC_R_SUCCESS)
+ return status;
+ }
+ return ISC_R_NOTFOUND;
+}
+
+isc_result_t dhcp_shared_network_destroy (omapi_object_t *h,
+ const char *file, int line)
+{
+ /* In this function h should be a (struct shared_network *) */
+
+#if defined (DEBUG_MEMORY_LEAKAGE) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ struct shared_network *shared_network;
+#endif
+
+ if (h -> type != dhcp_type_shared_network)
+ return DHCP_R_INVALIDARG;
+
+#if defined (DEBUG_MEMORY_LEAKAGE) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ shared_network = (struct shared_network *)h;
+ if (shared_network -> next)
+ shared_network_dereference (&shared_network -> next,
+ file, line);
+ if (shared_network -> name) {
+ dfree (shared_network -> name, file, line);
+ shared_network -> name = 0;
+ }
+ if (shared_network -> subnets)
+ subnet_dereference (&shared_network -> subnets, file, line);
+ if (shared_network -> interface)
+ interface_dereference (&shared_network -> interface,
+ file, line);
+ if (shared_network -> pools)
+ omapi_object_dereference ((omapi_object_t **)
+ &shared_network -> pools, file, line);
+ if (shared_network -> group)
+ group_dereference (&shared_network -> group, file, line);
+#if defined (FAILOVER_PROTOCOL)
+ if (shared_network -> failover_peer)
+ omapi_object_dereference ((omapi_object_t **)
+ &shared_network -> failover_peer,
+ file, line);
+#endif
+#endif /* DEBUG_MEMORY_LEAKAGE */
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_shared_network_signal_handler (omapi_object_t *h,
+ const char *name,
+ va_list ap)
+{
+ /* In this function h should be a (struct shared_network *) */
+
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_shared_network)
+ return DHCP_R_INVALIDARG;
+
+ /* Can't write shared_networks yet. */
+
+ /* Try to find some inner object that can take the value. */
+ if (h -> inner && h -> inner -> type -> get_value) {
+ status = ((*(h -> inner -> type -> signal_handler))
+ (h -> inner, name, ap));
+ if (status == ISC_R_SUCCESS)
+ return status;
+ }
+
+ return ISC_R_NOTFOUND;
+}
+
+isc_result_t dhcp_shared_network_stuff_values (omapi_object_t *c,
+ omapi_object_t *id,
+ omapi_object_t *h)
+{
+ /* In this function h should be a (struct shared_network *) */
+
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_shared_network)
+ return DHCP_R_INVALIDARG;
+
+ /* Can't stuff shared_network values yet. */
+
+ /* Write out the inner object, if any. */
+ if (h -> inner && h -> inner -> type -> stuff_values) {
+ status = ((*(h -> inner -> type -> stuff_values))
+ (c, id, h -> inner));
+ if (status == ISC_R_SUCCESS)
+ return status;
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_shared_network_lookup (omapi_object_t **lp,
+ omapi_object_t *id,
+ omapi_object_t *ref)
+{
+ /* Can't look up shared_networks yet. */
+
+ /* If we get to here without finding a shared_network, no valid key was
+ specified. */
+ if (!*lp)
+ return DHCP_R_NOKEYS;
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_shared_network_create (omapi_object_t **lp,
+ omapi_object_t *id)
+{
+ return ISC_R_NOTIMPLEMENTED;
+}
+
+isc_result_t dhcp_shared_network_remove (omapi_object_t *lp,
+ omapi_object_t *id)
+{
+ return ISC_R_NOTIMPLEMENTED;
+}
+
diff --git a/common/conflex.c b/common/conflex.c
new file mode 100644
index 0000000..f634121
--- /dev/null
+++ b/common/conflex.c
@@ -0,0 +1,1535 @@
+/* conflex.c
+
+ Lexical scanner for dhcpd config file... */
+
+/*
+ * Copyright (c) 2004-2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+#include <ctype.h>
+
+static int get_char (struct parse *);
+static void unget_char(struct parse *, int);
+static void skip_to_eol (struct parse *);
+static enum dhcp_token read_whitespace(int c, struct parse *cfile);
+static enum dhcp_token read_string (struct parse *);
+static enum dhcp_token read_number (int, struct parse *);
+static enum dhcp_token read_num_or_name (int, struct parse *);
+static enum dhcp_token intern (char *, enum dhcp_token);
+
+isc_result_t new_parse (cfile, file, inbuf, buflen, name, eolp)
+ struct parse **cfile;
+ int file;
+ char *inbuf;
+ unsigned buflen;
+ const char *name;
+ int eolp;
+{
+ isc_result_t status = ISC_R_SUCCESS;
+ struct parse *tmp;
+
+ tmp = dmalloc(sizeof(struct parse), MDL);
+ if (tmp == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ /*
+ * We don't need to initialize things to zero here, since
+ * dmalloc() returns memory that is set to zero.
+ */
+ tmp->tlname = name;
+ tmp->lpos = tmp -> line = 1;
+ tmp->cur_line = tmp->line1;
+ tmp->prev_line = tmp->line2;
+ tmp->token_line = tmp->cur_line;
+ tmp->cur_line[0] = tmp->prev_line[0] = 0;
+ tmp->file = file;
+ tmp->eol_token = eolp;
+
+ if (inbuf != NULL) {
+ tmp->inbuf = inbuf;
+ tmp->buflen = buflen;
+ tmp->bufsiz = 0;
+ } else {
+ struct stat sb;
+
+ if (fstat(file, &sb) < 0) {
+ status = ISC_R_IOERROR;
+ goto cleanup;
+ }
+
+ if (sb.st_size == 0)
+ goto cleanup;
+
+ tmp->bufsiz = tmp->buflen = (size_t) sb.st_size;
+ tmp->inbuf = mmap(NULL, tmp->bufsiz, PROT_READ, MAP_SHARED,
+ file, 0);
+
+ if (tmp->inbuf == MAP_FAILED) {
+ status = ISC_R_IOERROR;
+ goto cleanup;
+ }
+ }
+
+ *cfile = tmp;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ dfree(tmp, MDL);
+ return (status);
+}
+
+isc_result_t end_parse (cfile)
+ struct parse **cfile;
+{
+ /* "Memory" config files have no file. */
+ if ((*cfile)->file != -1) {
+ munmap((*cfile)->inbuf, (*cfile)->bufsiz);
+ close((*cfile)->file);
+ }
+
+ if ((*cfile)->saved_state != NULL) {
+ dfree((*cfile)->saved_state, MDL);
+ }
+
+ dfree(*cfile, MDL);
+ *cfile = NULL;
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Save the current state of the parser.
+ *
+ * Only one state may be saved. Any previous saved state is
+ * lost.
+ */
+isc_result_t
+save_parse_state(struct parse *cfile) {
+ /*
+ * Free any previous saved state.
+ */
+ if (cfile->saved_state != NULL) {
+ dfree(cfile->saved_state, MDL);
+ }
+
+ /*
+ * Save our current state.
+ */
+ cfile->saved_state = dmalloc(sizeof(struct parse), MDL);
+ if (cfile->saved_state == NULL) {
+ return ISC_R_NOMEMORY;
+ }
+ memcpy(cfile->saved_state, cfile, sizeof(*cfile));
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Return the parser to the previous saved state.
+ *
+ * You must call save_parse_state() before calling
+ * restore_parse_state(), but you can call restore_parse_state() any
+ * number of times after that.
+ */
+isc_result_t
+restore_parse_state(struct parse *cfile) {
+ struct parse *saved_state;
+
+ if (cfile->saved_state == NULL) {
+ return DHCP_R_NOTYET;
+ }
+
+ saved_state = cfile->saved_state;
+ memcpy(cfile, saved_state, sizeof(*cfile));
+ cfile->saved_state = saved_state;
+ return ISC_R_SUCCESS;
+}
+
+static int get_char (cfile)
+ struct parse *cfile;
+{
+ /* My kingdom for WITH... */
+ int c;
+
+ if (cfile->bufix == cfile->buflen) {
+#if !defined(LDAP_CONFIGURATION)
+ c = EOF;
+#else /* defined(LDAP_CONFIGURATION) */
+ if (cfile->read_function != NULL)
+ c = cfile->read_function(cfile);
+ else
+ c = EOF;
+#endif
+ } else {
+ c = cfile->inbuf [cfile->bufix];
+ cfile->bufix++;
+ }
+
+ if (!cfile->ugflag) {
+ if (c == EOL) {
+ if (cfile->cur_line == cfile->line1) {
+ cfile->cur_line = cfile->line2;
+ cfile->prev_line = cfile->line1;
+ } else {
+ cfile->cur_line = cfile->line1;
+ cfile->prev_line = cfile->line2;
+ }
+ cfile->line++;
+ cfile->lpos = 1;
+ cfile->cur_line [0] = 0;
+ } else if (c != EOF) {
+ if (cfile->lpos <= 80) {
+ cfile->cur_line [cfile->lpos - 1] = c;
+ cfile->cur_line [cfile->lpos] = 0;
+ }
+ cfile->lpos++;
+ }
+ } else
+ cfile->ugflag = 0;
+ return c;
+}
+
+/*
+ * Return a character to our input buffer.
+ */
+static void
+unget_char(struct parse *cfile, int c) {
+ if (c != EOF) {
+ cfile->bufix--;
+ cfile->ugflag = 1; /* do not put characters into
+ our error buffer on the next
+ call to get_char() */
+ }
+}
+
+/*
+ * GENERAL NOTE ABOUT TOKENS
+ *
+ * We normally only want non-whitespace tokens. There are some
+ * circumstances where we *do* want to see whitespace (for example
+ * when parsing IPv6 addresses).
+ *
+ * Generally we use the next_token() function to read tokens. This
+ * in turn calls get_next_token, which does *not* return tokens for
+ * whitespace. Rather, it skips these.
+ *
+ * When we need to see whitespace, we us next_raw_token(), which also
+ * returns the WHITESPACE token.
+ *
+ * The peek_token() and peek_raw_token() functions work as expected.
+ *
+ * Warning: if you invoke peek_token(), then if there is a whitespace
+ * token, it will be lost, and subsequent use of next_raw_token() or
+ * peek_raw_token() will NOT see it.
+ */
+
+static enum dhcp_token
+get_raw_token(struct parse *cfile) {
+ int c;
+ enum dhcp_token ttok;
+ static char tb [2];
+ int l, p;
+
+ do {
+ l = cfile -> line;
+ p = cfile -> lpos;
+
+ c = get_char (cfile);
+ if (!((c == '\n') && cfile->eol_token) &&
+ isascii(c) && isspace(c)) {
+ ttok = read_whitespace(c, cfile);
+ break;
+ }
+ if (c == '#') {
+ skip_to_eol (cfile);
+ continue;
+ }
+ if (c == '"') {
+ cfile -> lexline = l;
+ cfile -> lexchar = p;
+ ttok = read_string (cfile);
+ break;
+ }
+ if ((isascii (c) && isdigit (c)) || c == '-') {
+ cfile -> lexline = l;
+ cfile -> lexchar = p;
+ ttok = read_number (c, cfile);
+ break;
+ } else if (isascii (c) && isalpha (c)) {
+ cfile -> lexline = l;
+ cfile -> lexchar = p;
+ ttok = read_num_or_name (c, cfile);
+ break;
+ } else if (c == EOF) {
+ ttok = END_OF_FILE;
+ cfile -> tlen = 0;
+ break;
+ } else {
+ cfile -> lexline = l;
+ cfile -> lexchar = p;
+ tb [0] = c;
+ tb [1] = 0;
+ cfile -> tval = tb;
+ cfile -> tlen = 1;
+ ttok = c;
+ break;
+ }
+ } while (1);
+ return ttok;
+}
+
+/*
+ * The get_next_token() function consumes the next token and
+ * returns it to the caller.
+ *
+ * Since the code is almost the same for "normal" and "raw"
+ * input, we pass a flag to alter the way it works.
+ */
+
+static enum dhcp_token
+get_next_token(const char **rval, unsigned *rlen,
+ struct parse *cfile, isc_boolean_t raw) {
+ int rv;
+
+ if (cfile -> token) {
+ if (cfile -> lexline != cfile -> tline)
+ cfile -> token_line = cfile -> cur_line;
+ cfile -> lexchar = cfile -> tlpos;
+ cfile -> lexline = cfile -> tline;
+ rv = cfile -> token;
+ cfile -> token = 0;
+ } else {
+ rv = get_raw_token(cfile);
+ cfile -> token_line = cfile -> cur_line;
+ }
+
+ if (!raw) {
+ while (rv == WHITESPACE) {
+ rv = get_raw_token(cfile);
+ cfile->token_line = cfile->cur_line;
+ }
+ }
+
+ if (rval)
+ *rval = cfile -> tval;
+ if (rlen)
+ *rlen = cfile -> tlen;
+#ifdef DEBUG_TOKENS
+ fprintf (stderr, "%s:%d ", cfile -> tval, rv);
+#endif
+ return rv;
+}
+
+
+/*
+ * Get the next token from cfile and return it.
+ *
+ * If rval is non-NULL, set the pointer it contains to
+ * the contents of the token.
+ *
+ * If rlen is non-NULL, set the integer it contains to
+ * the length of the token.
+ */
+
+enum dhcp_token
+next_token(const char **rval, unsigned *rlen, struct parse *cfile) {
+ return get_next_token(rval, rlen, cfile, ISC_FALSE);
+}
+
+
+/*
+ * The same as the next_token() function above, but will return space
+ * as the WHITESPACE token.
+ */
+
+enum dhcp_token
+next_raw_token(const char **rval, unsigned *rlen, struct parse *cfile) {
+ return get_next_token(rval, rlen, cfile, ISC_TRUE);
+}
+
+
+/*
+ * The do_peek_token() function checks the next token without
+ * consuming it, and returns it to the caller.
+ *
+ * Since the code is almost the same for "normal" and "raw"
+ * input, we pass a flag to alter the way it works. (See the
+ * warning in the GENERAL NOTES ABOUT TOKENS above though.)
+ */
+
+enum dhcp_token
+do_peek_token(const char **rval, unsigned int *rlen,
+ struct parse *cfile, isc_boolean_t raw) {
+ int x;
+
+ if (!cfile->token || (!raw && (cfile->token == WHITESPACE))) {
+ cfile -> tlpos = cfile -> lexchar;
+ cfile -> tline = cfile -> lexline;
+
+ do {
+ cfile->token = get_raw_token(cfile);
+ } while (!raw && (cfile->token == WHITESPACE));
+
+ if (cfile -> lexline != cfile -> tline)
+ cfile -> token_line = cfile -> prev_line;
+
+ x = cfile -> lexchar;
+ cfile -> lexchar = cfile -> tlpos;
+ cfile -> tlpos = x;
+
+ x = cfile -> lexline;
+ cfile -> lexline = cfile -> tline;
+ cfile -> tline = x;
+ }
+ if (rval)
+ *rval = cfile -> tval;
+ if (rlen)
+ *rlen = cfile -> tlen;
+#ifdef DEBUG_TOKENS
+ fprintf (stderr, "(%s:%d) ", cfile -> tval, cfile -> token);
+#endif
+ return cfile -> token;
+}
+
+
+/*
+ * Get the next token from cfile and return it, leaving it for a
+ * subsequent call to next_token().
+ *
+ * Note that it WILL consume whitespace tokens.
+ *
+ * If rval is non-NULL, set the pointer it contains to
+ * the contents of the token.
+ *
+ * If rlen is non-NULL, set the integer it contains to
+ * the length of the token.
+ */
+
+enum dhcp_token
+peek_token(const char **rval, unsigned *rlen, struct parse *cfile) {
+ return do_peek_token(rval, rlen, cfile, ISC_FALSE);
+}
+
+
+/*
+ * The same as the peek_token() function above, but will return space
+ * as the WHITESPACE token.
+ */
+
+enum dhcp_token
+peek_raw_token(const char **rval, unsigned *rlen, struct parse *cfile) {
+ return do_peek_token(rval, rlen, cfile, ISC_TRUE);
+}
+
+static void skip_to_eol (cfile)
+ struct parse *cfile;
+{
+ int c;
+ do {
+ c = get_char (cfile);
+ if (c == EOF)
+ return;
+ if (c == EOL) {
+ return;
+ }
+ } while (1);
+}
+
+static enum dhcp_token
+read_whitespace(int c, struct parse *cfile) {
+ int ofs;
+
+ /*
+ * Read as much whitespace as we have available.
+ */
+ ofs = 0;
+ do {
+ if (ofs >= (sizeof(cfile->tokbuf) - 1)) {
+ /*
+ * As the file includes a huge amount of whitespace,
+ * it's probably broken.
+ * Print out a warning and bail out.
+ */
+ parse_warn(cfile,
+ "whitespace too long, buffer overflow.");
+ log_fatal("Exiting");
+ }
+ cfile->tokbuf[ofs++] = c;
+ c = get_char(cfile);
+ } while (!((c == '\n') && cfile->eol_token) &&
+ isascii(c) && isspace(c));
+
+ /*
+ * Put the last (non-whitespace) character back.
+ */
+ unget_char(cfile, c);
+
+ /*
+ * Return our token.
+ */
+ cfile->tokbuf[ofs] = '\0';
+ cfile->tlen = ofs;
+ cfile->tval = cfile->tokbuf;
+ return WHITESPACE;
+}
+
+static enum dhcp_token read_string (cfile)
+ struct parse *cfile;
+{
+ int i;
+ int bs = 0;
+ int c;
+ int value = 0;
+ int hex = 0;
+
+ for (i = 0; i < sizeof cfile -> tokbuf; i++) {
+ again:
+ c = get_char (cfile);
+ if (c == EOF) {
+ parse_warn (cfile, "eof in string constant");
+ break;
+ }
+ if (bs == 1) {
+ switch (c) {
+ case 't':
+ cfile -> tokbuf [i] = '\t';
+ break;
+ case 'r':
+ cfile -> tokbuf [i] = '\r';
+ break;
+ case 'n':
+ cfile -> tokbuf [i] = '\n';
+ break;
+ case 'b':
+ cfile -> tokbuf [i] = '\b';
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ hex = 0;
+ value = c - '0';
+ ++bs;
+ goto again;
+ case 'x':
+ hex = 1;
+ value = 0;
+ ++bs;
+ goto again;
+ default:
+ cfile -> tokbuf [i] = c;
+ break;
+ }
+ bs = 0;
+ } else if (bs > 1) {
+ if (hex) {
+ if (c >= '0' && c <= '9') {
+ value = value * 16 + (c - '0');
+ } else if (c >= 'a' && c <= 'f') {
+ value = value * 16 + (c - 'a' + 10);
+ } else if (c >= 'A' && c <= 'F') {
+ value = value * 16 + (c - 'A' + 10);
+ } else {
+ parse_warn (cfile,
+ "invalid hex digit: %x",
+ c);
+ bs = 0;
+ continue;
+ }
+ if (++bs == 4) {
+ cfile -> tokbuf [i] = value;
+ bs = 0;
+ } else
+ goto again;
+ } else {
+ if (c >= '0' && c <= '7') {
+ value = value * 8 + (c - '0');
+ } else {
+ if (value != 0) {
+ parse_warn (cfile,
+ "invalid octal digit %x",
+ c);
+ continue;
+ } else
+ cfile -> tokbuf [i] = 0;
+ bs = 0;
+ }
+ if (++bs == 4) {
+ cfile -> tokbuf [i] = value;
+ bs = 0;
+ } else
+ goto again;
+ }
+ } else if (c == '\\') {
+ bs = 1;
+ goto again;
+ } else if (c == '"')
+ break;
+ else
+ cfile -> tokbuf [i] = c;
+ }
+ /* Normally, I'd feel guilty about this, but we're talking about
+ strings that'll fit in a DHCP packet here... */
+ if (i == sizeof cfile -> tokbuf) {
+ parse_warn (cfile,
+ "string constant larger than internal buffer");
+ --i;
+ }
+ cfile -> tokbuf [i] = 0;
+ cfile -> tlen = i;
+ cfile -> tval = cfile -> tokbuf;
+ return STRING;
+}
+
+static enum dhcp_token read_number (c, cfile)
+ int c;
+ struct parse *cfile;
+{
+ int i = 0;
+ int token = NUMBER;
+
+ cfile -> tokbuf [i++] = c;
+ for (; i < sizeof cfile -> tokbuf; i++) {
+ c = get_char (cfile);
+
+ /* Promote NUMBER -> NUMBER_OR_NAME -> NAME, never demote.
+ * Except in the case of '0x' syntax hex, which gets called
+ * a NAME at '0x', and returned to NUMBER_OR_NAME once it's
+ * verified to be at least 0xf or less.
+ */
+ switch(isascii(c) ? token : BREAK) {
+ case NUMBER:
+ if(isdigit(c))
+ break;
+ /* FALLTHROUGH */
+ case NUMBER_OR_NAME:
+ if(isxdigit(c)) {
+ token = NUMBER_OR_NAME;
+ break;
+ }
+ /* FALLTHROUGH */
+ case NAME:
+ if((i == 2) && isxdigit(c) &&
+ (cfile->tokbuf[0] == '0') &&
+ ((cfile->tokbuf[1] == 'x') ||
+ (cfile->tokbuf[1] == 'X'))) {
+ token = NUMBER_OR_NAME;
+ break;
+ } else if(((c == '-') || (c == '_') || isalnum(c))) {
+ token = NAME;
+ break;
+ }
+ /* FALLTHROUGH */
+ case BREAK:
+ /* At this point c is either EOF or part of the next
+ * token. If not EOF, rewind the file one byte so
+ * the next token is read from there.
+ */
+ unget_char(cfile, c);
+ goto end_read;
+
+ default:
+ log_fatal("read_number():%s:%d: impossible case", MDL);
+ }
+
+ cfile -> tokbuf [i] = c;
+ }
+
+ if (i == sizeof cfile -> tokbuf) {
+ parse_warn (cfile,
+ "numeric token larger than internal buffer");
+ --i;
+ }
+
+ end_read:
+ cfile -> tokbuf [i] = 0;
+ cfile -> tlen = i;
+ cfile -> tval = cfile -> tokbuf;
+
+ /*
+ * If this entire token from start to finish was "-", such as
+ * the middle parameter in "42 - 7", return just the MINUS token.
+ */
+ if ((i == 1) && (cfile->tokbuf[i] == '-'))
+ return MINUS;
+ else
+ return token;
+}
+
+static enum dhcp_token read_num_or_name (c, cfile)
+ int c;
+ struct parse *cfile;
+{
+ int i = 0;
+ enum dhcp_token rv = NUMBER_OR_NAME;
+ cfile -> tokbuf [i++] = c;
+ for (; i < sizeof cfile -> tokbuf; i++) {
+ c = get_char (cfile);
+ if (!isascii (c) ||
+ (c != '-' && c != '_' && !isalnum (c))) {
+ unget_char(cfile, c);
+ break;
+ }
+ if (!isxdigit (c))
+ rv = NAME;
+ cfile -> tokbuf [i] = c;
+ }
+ if (i == sizeof cfile -> tokbuf) {
+ parse_warn (cfile, "token larger than internal buffer");
+ --i;
+ }
+ cfile -> tokbuf [i] = 0;
+ cfile -> tlen = i;
+ cfile -> tval = cfile -> tokbuf;
+ return intern(cfile->tval, rv);
+}
+
+static enum dhcp_token
+intern(char *atom, enum dhcp_token dfv) {
+ if (!isascii(atom[0]))
+ return dfv;
+
+ switch (tolower((unsigned char)atom[0])) {
+ case '-':
+ if (atom [1] == 0)
+ return MINUS;
+ break;
+
+ case 'a':
+ if (!strcasecmp(atom + 1, "bandoned"))
+ return TOKEN_ABANDONED;
+ if (!strcasecmp(atom + 1, "ctive"))
+ return TOKEN_ACTIVE;
+ if (!strncasecmp(atom + 1, "dd", 2)) {
+ if (atom[3] == '\0')
+ return TOKEN_ADD;
+ else if (!strcasecmp(atom + 3, "ress"))
+ return ADDRESS;
+ break;
+ }
+ if (!strcasecmp(atom + 1, "fter"))
+ return AFTER;
+ if (isascii(atom[1]) &&
+ (tolower((unsigned char)atom[1]) == 'l')) {
+ if (!strcasecmp(atom + 2, "gorithm"))
+ return ALGORITHM;
+ if (!strcasecmp(atom + 2, "ias"))
+ return ALIAS;
+ if (isascii(atom[2]) &&
+ (tolower((unsigned char)atom[2]) == 'l')) {
+ if (atom[3] == '\0')
+ return ALL;
+ else if (!strcasecmp(atom + 3, "ow"))
+ return ALLOW;
+ break;
+ }
+ if (!strcasecmp(atom + 2, "so"))
+ return TOKEN_ALSO;
+ break;
+ }
+ if (isascii(atom[1]) &&
+ (tolower((unsigned char)atom[1]) == 'n')) {
+ if (!strcasecmp(atom + 2, "d"))
+ return AND;
+ if (!strcasecmp(atom + 2, "ycast-mac"))
+ return ANYCAST_MAC;
+ break;
+ }
+ if (!strcasecmp(atom + 1, "ppend"))
+ return APPEND;
+ if (!strcasecmp(atom + 1, "rray"))
+ return ARRAY;
+ if (isascii(atom[1]) &&
+ (tolower((unsigned char)atom[1]) == 't')) {
+ if (atom[2] == '\0')
+ return AT;
+ if (!strcasecmp(atom + 2, "sfp"))
+ return ATSFP;
+ break;
+ }
+ if (!strncasecmp(atom + 1, "ut", 2)) {
+ if (isascii(atom[3]) &&
+ (tolower((unsigned char)atom[3]) == 'h')) {
+ if (!strncasecmp(atom + 4, "enticat", 7)) {
+ if (!strcasecmp(atom + 11, "ed"))
+ return AUTHENTICATED;
+ if (!strcasecmp(atom + 11, "ion"))
+ return AUTHENTICATION;
+ break;
+ }
+ if (!strcasecmp(atom + 4, "oritative"))
+ return AUTHORITATIVE;
+ break;
+ }
+ if (!strcasecmp(atom + 3, "o-partner-down"))
+ return AUTO_PARTNER_DOWN;
+ break;
+ }
+ break;
+ case 'b':
+ if (!strcasecmp (atom + 1, "ackup"))
+ return TOKEN_BACKUP;
+ if (!strcasecmp (atom + 1, "ootp"))
+ return TOKEN_BOOTP;
+ if (!strcasecmp (atom + 1, "inding"))
+ return BINDING;
+ if (!strcasecmp (atom + 1, "inary-to-ascii"))
+ return BINARY_TO_ASCII;
+ if (!strcasecmp (atom + 1, "ackoff-cutoff"))
+ return BACKOFF_CUTOFF;
+ if (!strcasecmp (atom + 1, "ooting"))
+ return BOOTING;
+ if (!strcasecmp (atom + 1, "oot-unknown-clients"))
+ return BOOT_UNKNOWN_CLIENTS;
+ if (!strcasecmp (atom + 1, "reak"))
+ return BREAK;
+ if (!strcasecmp (atom + 1, "illing"))
+ return BILLING;
+ if (!strcasecmp (atom + 1, "oolean"))
+ return BOOLEAN;
+ if (!strcasecmp (atom + 1, "alance"))
+ return BALANCE;
+ if (!strcasecmp (atom + 1, "ound"))
+ return BOUND;
+ break;
+ case 'c':
+ if (!strcasecmp(atom + 1, "ase"))
+ return CASE;
+ if (!strcasecmp(atom + 1, "heck"))
+ return CHECK;
+ if (!strcasecmp(atom + 1, "iaddr"))
+ return CIADDR;
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 'l') {
+ if (!strcasecmp(atom + 2, "ass"))
+ return CLASS;
+ if (!strncasecmp(atom + 2, "ient", 4)) {
+ if (!strcasecmp(atom + 6, "s"))
+ return CLIENTS;
+ if (atom[6] == '-') {
+ if (!strcasecmp(atom + 7, "hostname"))
+ return CLIENT_HOSTNAME;
+ if (!strcasecmp(atom + 7, "identifier"))
+ return CLIENT_IDENTIFIER;
+ if (!strcasecmp(atom + 7, "state"))
+ return CLIENT_STATE;
+ if (!strcasecmp(atom + 7, "updates"))
+ return CLIENT_UPDATES;
+ break;
+ }
+ break;
+ }
+ if (!strcasecmp(atom + 2, "ose"))
+ return TOKEN_CLOSE;
+ if (!strcasecmp(atom + 2, "tt"))
+ return CLTT;
+ break;
+ }
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 'o') {
+ if (!strcasecmp(atom + 2, "de"))
+ return CODE;
+ if (isascii(atom[2]) &&
+ tolower((unsigned char)atom[2]) == 'm') {
+ if (!strcasecmp(atom + 3, "mit"))
+ return COMMIT;
+ if (!strcasecmp(atom + 3,
+ "munications-interrupted"))
+ return COMMUNICATIONS_INTERRUPTED;
+ if (!strcasecmp(atom + 3, "pressed"))
+ return COMPRESSED;
+ break;
+ }
+ if (isascii(atom[2]) &&
+ tolower((unsigned char)atom[2]) == 'n') {
+ if (!strcasecmp(atom + 3, "cat"))
+ return CONCAT;
+ if (!strcasecmp(atom + 3, "fig-option"))
+ return CONFIG_OPTION;
+ if (!strcasecmp(atom + 3, "flict-done"))
+ return CONFLICT_DONE;
+ if (!strcasecmp(atom + 3, "nect"))
+ return CONNECT;
+ break;
+ }
+ break;
+ }
+ if (!strcasecmp(atom + 1, "reate"))
+ return TOKEN_CREATE;
+ break;
+ case 'd':
+ if (!strcasecmp(atom + 1, "b-time-format"))
+ return DB_TIME_FORMAT;
+ if (!strcasecmp (atom + 1, "ns-update"))
+ return DNS_UPDATE;
+ if (!strcasecmp (atom + 1, "ns-delete"))
+ return DNS_DELETE;
+ if (!strcasecmp (atom + 1, "omain"))
+ return DOMAIN;
+ if (!strncasecmp (atom + 1, "omain-", 6)) {
+ if (!strcasecmp(atom + 7, "name"))
+ return DOMAIN_NAME;
+ if (!strcasecmp(atom + 7, "list"))
+ return DOMAIN_LIST;
+ }
+ if (!strcasecmp (atom + 1, "o-forward-updates"))
+ return DO_FORWARD_UPDATE;
+ /* do-forward-update is included for historical reasons */
+ if (!strcasecmp (atom + 1, "o-forward-update"))
+ return DO_FORWARD_UPDATE;
+ if (!strcasecmp (atom + 1, "ebug"))
+ return TOKEN_DEBUG;
+ if (!strcasecmp (atom + 1, "eny"))
+ return DENY;
+ if (!strcasecmp (atom + 1, "eleted"))
+ return TOKEN_DELETED;
+ if (!strcasecmp (atom + 1, "elete"))
+ return TOKEN_DELETE;
+ if (!strncasecmp (atom + 1, "efault", 6)) {
+ if (!atom [7])
+ return DEFAULT;
+ if (!strcasecmp(atom + 7, "-duid"))
+ return DEFAULT_DUID;
+ if (!strcasecmp (atom + 7, "-lease-time"))
+ return DEFAULT_LEASE_TIME;
+ break;
+ }
+ if (!strncasecmp (atom + 1, "ynamic", 6)) {
+ if (!atom [7])
+ return DYNAMIC;
+ if (!strncasecmp (atom + 7, "-bootp", 6)) {
+ if (!atom [13])
+ return DYNAMIC_BOOTP;
+ if (!strcasecmp (atom + 13, "-lease-cutoff"))
+ return DYNAMIC_BOOTP_LEASE_CUTOFF;
+ if (!strcasecmp (atom + 13, "-lease-length"))
+ return DYNAMIC_BOOTP_LEASE_LENGTH;
+ break;
+ }
+ }
+ if (!strcasecmp (atom + 1, "uplicates"))
+ return DUPLICATES;
+ if (!strcasecmp (atom + 1, "eclines"))
+ return DECLINES;
+ if (!strncasecmp (atom + 1, "efine", 5)) {
+ if (!strcasecmp (atom + 6, "d"))
+ return DEFINED;
+ if (!atom [6])
+ return DEFINE;
+ }
+ break;
+ case 'e':
+ if (isascii (atom [1]) &&
+ tolower((unsigned char)atom[1]) == 'x') {
+ if (!strcasecmp (atom + 2, "tract-int"))
+ return EXTRACT_INT;
+ if (!strcasecmp (atom + 2, "ists"))
+ return EXISTS;
+ if (!strcasecmp (atom + 2, "piry"))
+ return EXPIRY;
+ if (!strcasecmp (atom + 2, "pire"))
+ return EXPIRE;
+ if (!strcasecmp (atom + 2, "pired"))
+ return TOKEN_EXPIRED;
+ }
+ if (!strcasecmp (atom + 1, "ncode-int"))
+ return ENCODE_INT;
+ if (!strcasecmp(atom + 1, "poch"))
+ return EPOCH;
+ if (!strcasecmp (atom + 1, "thernet"))
+ return ETHERNET;
+ if (!strcasecmp (atom + 1, "nds"))
+ return ENDS;
+ if (!strncasecmp (atom + 1, "ls", 2)) {
+ if (!strcasecmp (atom + 3, "e"))
+ return ELSE;
+ if (!strcasecmp (atom + 3, "if"))
+ return ELSIF;
+ break;
+ }
+ if (!strcasecmp (atom + 1, "rror"))
+ return ERROR;
+ if (!strcasecmp (atom + 1, "val"))
+ return EVAL;
+ if (!strcasecmp (atom + 1, "ncapsulate"))
+ return ENCAPSULATE;
+ if (!strcasecmp(atom + 1, "xecute"))
+ return EXECUTE;
+ if (!strcasecmp(atom+1, "n")) {
+ return EN;
+ }
+ break;
+ case 'f':
+ if (!strcasecmp (atom + 1, "atal"))
+ return FATAL;
+ if (!strcasecmp (atom + 1, "ilename"))
+ return FILENAME;
+ if (!strcasecmp (atom + 1, "ixed-address"))
+ return FIXED_ADDR;
+ if (!strcasecmp (atom + 1, "ixed-address6"))
+ return FIXED_ADDR6;
+ if (!strcasecmp (atom + 1, "ixed-prefix6"))
+ return FIXED_PREFIX6;
+ if (!strcasecmp (atom + 1, "ddi"))
+ return TOKEN_FDDI;
+ if (!strcasecmp (atom + 1, "ormerr"))
+ return NS_FORMERR;
+ if (!strcasecmp (atom + 1, "unction"))
+ return FUNCTION;
+ if (!strcasecmp (atom + 1, "ailover"))
+ return FAILOVER;
+ if (!strcasecmp (atom + 1, "ree"))
+ return TOKEN_FREE;
+ break;
+ case 'g':
+ if (!strncasecmp(atom + 1, "et", 2)) {
+ if (!strcasecmp(atom + 3, "-lease-hostnames"))
+ return GET_LEASE_HOSTNAMES;
+ if (!strcasecmp(atom + 3, "hostbyname"))
+ return GETHOSTBYNAME;
+ if (!strcasecmp(atom + 3, "hostname"))
+ return GETHOSTNAME;
+ break;
+ }
+ if (!strcasecmp (atom + 1, "iaddr"))
+ return GIADDR;
+ if (!strcasecmp (atom + 1, "roup"))
+ return GROUP;
+ break;
+ case 'h':
+ if (!strcasecmp(atom + 1, "ash"))
+ return HASH;
+ if (!strcasecmp (atom + 1, "ba"))
+ return HBA;
+ if (!strcasecmp (atom + 1, "ost"))
+ return HOST;
+ if (!strcasecmp (atom + 1, "ost-decl-name"))
+ return HOST_DECL_NAME;
+ if (!strcasecmp(atom + 1, "ost-identifier"))
+ return HOST_IDENTIFIER;
+ if (!strcasecmp (atom + 1, "ardware"))
+ return HARDWARE;
+ if (!strcasecmp (atom + 1, "ostname"))
+ return HOSTNAME;
+ if (!strcasecmp (atom + 1, "elp"))
+ return TOKEN_HELP;
+ break;
+ case 'i':
+ if (!strcasecmp(atom+1, "a-na"))
+ return IA_NA;
+ if (!strcasecmp(atom+1, "a-ta"))
+ return IA_TA;
+ if (!strcasecmp(atom+1, "a-pd"))
+ return IA_PD;
+ if (!strcasecmp(atom+1, "aaddr"))
+ return IAADDR;
+ if (!strcasecmp(atom+1, "aprefix"))
+ return IAPREFIX;
+ if (!strcasecmp (atom + 1, "nclude"))
+ return INCLUDE;
+ if (!strcasecmp (atom + 1, "nteger"))
+ return INTEGER;
+ if (!strcasecmp (atom + 1, "nfiniband"))
+ return TOKEN_INFINIBAND;
+ if (!strcasecmp (atom + 1, "nfinite"))
+ return INFINITE;
+ if (!strcasecmp (atom + 1, "nfo"))
+ return INFO;
+ if (!strcasecmp (atom + 1, "p-address"))
+ return IP_ADDRESS;
+ if (!strcasecmp (atom + 1, "p6-address"))
+ return IP6_ADDRESS;
+ if (!strcasecmp (atom + 1, "nitial-interval"))
+ return INITIAL_INTERVAL;
+ if (!strcasecmp (atom + 1, "nitial-delay"))
+ return INITIAL_DELAY;
+ if (!strcasecmp (atom + 1, "nterface"))
+ return INTERFACE;
+ if (!strcasecmp (atom + 1, "dentifier"))
+ return IDENTIFIER;
+ if (!strcasecmp (atom + 1, "f"))
+ return IF;
+ if (!strcasecmp (atom + 1, "s"))
+ return IS;
+ if (!strcasecmp (atom + 1, "gnore"))
+ return IGNORE;
+ break;
+ case 'k':
+ if (!strncasecmp (atom + 1, "nown", 4)) {
+ if (!strcasecmp (atom + 5, "-clients"))
+ return KNOWN_CLIENTS;
+ if (!atom[5])
+ return KNOWN;
+ break;
+ }
+ if (!strcasecmp (atom + 1, "ey"))
+ return KEY;
+ break;
+ case 'l':
+ if (!strcasecmp (atom + 1, "case"))
+ return LCASE;
+ if (!strcasecmp (atom + 1, "ease"))
+ return LEASE;
+ if (!strcasecmp(atom + 1, "ease6"))
+ return LEASE6;
+ if (!strcasecmp (atom + 1, "eased-address"))
+ return LEASED_ADDRESS;
+ if (!strcasecmp (atom + 1, "ease-time"))
+ return LEASE_TIME;
+ if (!strcasecmp(atom + 1, "easequery"))
+ return LEASEQUERY;
+ if (!strcasecmp(atom + 1, "ength"))
+ return LENGTH;
+ if (!strcasecmp (atom + 1, "imit"))
+ return LIMIT;
+ if (!strcasecmp (atom + 1, "et"))
+ return LET;
+ if (!strcasecmp (atom + 1, "oad"))
+ return LOAD;
+ if (!strcasecmp(atom + 1, "ocal"))
+ return LOCAL;
+ if (!strcasecmp (atom + 1, "og"))
+ return LOG;
+ if (!strcasecmp(atom+1, "lt")) {
+ return LLT;
+ }
+ if (!strcasecmp(atom+1, "l")) {
+ return LL;
+ }
+ break;
+ case 'm':
+ if (!strncasecmp (atom + 1, "ax", 2)) {
+ if (!atom [3])
+ return TOKEN_MAX;
+ if (!strcasecmp (atom + 3, "-balance"))
+ return MAX_BALANCE;
+ if (!strncasecmp (atom + 3, "-lease-", 7)) {
+ if (!strcasecmp(atom + 10, "misbalance"))
+ return MAX_LEASE_MISBALANCE;
+ if (!strcasecmp(atom + 10, "ownership"))
+ return MAX_LEASE_OWNERSHIP;
+ if (!strcasecmp(atom + 10, "time"))
+ return MAX_LEASE_TIME;
+ }
+ if (!strcasecmp(atom + 3, "-life"))
+ return MAX_LIFE;
+ if (!strcasecmp (atom + 3, "-transmit-idle"))
+ return MAX_TRANSMIT_IDLE;
+ if (!strcasecmp (atom + 3, "-response-delay"))
+ return MAX_RESPONSE_DELAY;
+ if (!strcasecmp (atom + 3, "-unacked-updates"))
+ return MAX_UNACKED_UPDATES;
+ }
+ if (!strncasecmp (atom + 1, "in-", 3)) {
+ if (!strcasecmp (atom + 4, "balance"))
+ return MIN_BALANCE;
+ if (!strcasecmp (atom + 4, "lease-time"))
+ return MIN_LEASE_TIME;
+ if (!strcasecmp (atom + 4, "secs"))
+ return MIN_SECS;
+ break;
+ }
+ if (!strncasecmp (atom + 1, "edi", 3)) {
+ if (!strcasecmp (atom + 4, "a"))
+ return MEDIA;
+ if (!strcasecmp (atom + 4, "um"))
+ return MEDIUM;
+ break;
+ }
+ if (!strcasecmp (atom + 1, "atch"))
+ return MATCH;
+ if (!strcasecmp (atom + 1, "embers"))
+ return MEMBERS;
+ if (!strcasecmp (atom + 1, "y"))
+ return MY;
+ if (!strcasecmp (atom + 1, "clt"))
+ return MCLT;
+ break;
+ case 'n':
+ if (!strcasecmp (atom + 1, "ormal"))
+ return NORMAL;
+ if (!strcasecmp (atom + 1, "ameserver"))
+ return NAMESERVER;
+ if (!strcasecmp (atom + 1, "etmask"))
+ return NETMASK;
+ if (!strcasecmp (atom + 1, "ever"))
+ return NEVER;
+ if (!strcasecmp (atom + 1, "ext-server"))
+ return NEXT_SERVER;
+ if (!strcasecmp (atom + 1, "ot"))
+ return TOKEN_NOT;
+ if (!strcasecmp (atom + 1, "o"))
+ return TOKEN_NO;
+ if (!strcasecmp (atom + 1, "s-update"))
+ return NS_UPDATE;
+ if (!strcasecmp (atom + 1, "oerror"))
+ return NS_NOERROR;
+ if (!strcasecmp (atom + 1, "otauth"))
+ return NS_NOTAUTH;
+ if (!strcasecmp (atom + 1, "otimp"))
+ return NS_NOTIMP;
+ if (!strcasecmp (atom + 1, "otzone"))
+ return NS_NOTZONE;
+ if (!strcasecmp (atom + 1, "xdomain"))
+ return NS_NXDOMAIN;
+ if (!strcasecmp (atom + 1, "xrrset"))
+ return NS_NXRRSET;
+ if (!strcasecmp (atom + 1, "ull"))
+ return TOKEN_NULL;
+ if (!strcasecmp (atom + 1, "ext"))
+ return TOKEN_NEXT;
+ if (!strcasecmp (atom + 1, "ew"))
+ return TOKEN_NEW;
+ break;
+ case 'o':
+ if (!strcasecmp (atom + 1, "mapi"))
+ return OMAPI;
+ if (!strcasecmp (atom + 1, "r"))
+ return OR;
+ if (!strcasecmp (atom + 1, "n"))
+ return ON;
+ if (!strcasecmp (atom + 1, "pen"))
+ return TOKEN_OPEN;
+ if (!strcasecmp (atom + 1, "ption"))
+ return OPTION;
+ if (!strcasecmp (atom + 1, "ne-lease-per-client"))
+ return ONE_LEASE_PER_CLIENT;
+ if (!strcasecmp (atom + 1, "f"))
+ return OF;
+ if (!strcasecmp (atom + 1, "wner"))
+ return OWNER;
+ break;
+ case 'p':
+ if (!strcasecmp (atom + 1, "repend"))
+ return PREPEND;
+ if (!strcasecmp(atom + 1, "referred-life"))
+ return PREFERRED_LIFE;
+ if (!strcasecmp (atom + 1, "acket"))
+ return PACKET;
+ if (!strcasecmp (atom + 1, "ool"))
+ return POOL;
+ if (!strcasecmp (atom + 1, "refix6"))
+ return PREFIX6;
+ if (!strcasecmp (atom + 1, "seudo"))
+ return PSEUDO;
+ if (!strcasecmp (atom + 1, "eer"))
+ return PEER;
+ if (!strcasecmp (atom + 1, "rimary"))
+ return PRIMARY;
+ if (!strcasecmp (atom + 1, "rimary6"))
+ return PRIMARY6;
+ if (!strncasecmp (atom + 1, "artner", 6)) {
+ if (!atom [7])
+ return PARTNER;
+ if (!strcasecmp (atom + 7, "-down"))
+ return PARTNER_DOWN;
+ }
+ if (!strcasecmp (atom + 1, "ort"))
+ return PORT;
+ if (!strcasecmp (atom + 1, "otential-conflict"))
+ return POTENTIAL_CONFLICT;
+ if (!strcasecmp (atom + 1, "ick-first-value") ||
+ !strcasecmp (atom + 1, "ick"))
+ return PICK;
+ if (!strcasecmp (atom + 1, "aused"))
+ return PAUSED;
+ break;
+ case 'r':
+ if (!strcasecmp(atom + 1, "ange"))
+ return RANGE;
+ if (!strcasecmp(atom + 1, "ange6"))
+ return RANGE6;
+ if (isascii(atom[1]) &&
+ (tolower((unsigned char)atom[1]) == 'e')) {
+ if (!strcasecmp(atom + 2, "bind"))
+ return REBIND;
+ if (!strcasecmp(atom + 2, "boot"))
+ return REBOOT;
+ if (!strcasecmp(atom + 2, "contact-interval"))
+ return RECONTACT_INTERVAL;
+ if (!strncasecmp(atom + 2, "cover", 5)) {
+ if (atom[7] == '\0')
+ return RECOVER;
+ if (!strcasecmp(atom + 7, "-done"))
+ return RECOVER_DONE;
+ if (!strcasecmp(atom + 7, "-wait"))
+ return RECOVER_WAIT;
+ break;
+ }
+ if (!strcasecmp(atom + 2, "fresh"))
+ return REFRESH;
+ if (!strcasecmp(atom + 2, "fused"))
+ return NS_REFUSED;
+ if (!strcasecmp(atom + 2, "ject"))
+ return REJECT;
+ if (!strcasecmp(atom + 2, "lease"))
+ return RELEASE;
+ if (!strcasecmp(atom + 2, "leased"))
+ return TOKEN_RELEASED;
+ if (!strcasecmp(atom + 2, "move"))
+ return REMOVE;
+ if (!strcasecmp(atom + 2, "new"))
+ return RENEW;
+ if (!strcasecmp(atom + 2, "quest"))
+ return REQUEST;
+ if (!strcasecmp(atom + 2, "quire"))
+ return REQUIRE;
+ if (isascii(atom[2]) &&
+ (tolower((unsigned char)atom[2]) == 's')) {
+ if (!strcasecmp(atom + 3, "erved"))
+ return TOKEN_RESERVED;
+ if (!strcasecmp(atom + 3, "et"))
+ return TOKEN_RESET;
+ if (!strcasecmp(atom + 3,
+ "olution-interrupted"))
+ return RESOLUTION_INTERRUPTED;
+ break;
+ }
+ if (!strcasecmp(atom + 2, "try"))
+ return RETRY;
+ if (!strcasecmp(atom + 2, "turn"))
+ return RETURN;
+ if (!strcasecmp(atom + 2, "verse"))
+ return REVERSE;
+ if (!strcasecmp(atom + 2, "wind"))
+ return REWIND;
+ break;
+ }
+ break;
+ case 's':
+ if (!strcasecmp(atom + 1, "cript"))
+ return SCRIPT;
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 'e') {
+ if (!strcasecmp(atom + 2, "arch"))
+ return SEARCH;
+ if (isascii(atom[2]) &&
+ tolower((unsigned char)atom[2]) == 'c') {
+ if (!strncasecmp(atom + 3, "ond", 3)) {
+ if (!strcasecmp(atom + 6, "ary"))
+ return SECONDARY;
+ if (!strcasecmp(atom + 6, "ary6"))
+ return SECONDARY6;
+ if (!strcasecmp(atom + 6, "s"))
+ return SECONDS;
+ break;
+ }
+ if (!strcasecmp(atom + 3, "ret"))
+ return SECRET;
+ break;
+ }
+ if (!strncasecmp(atom + 2, "lect", 4)) {
+ if (atom[6] == '\0')
+ return SELECT;
+ if (!strcasecmp(atom + 6, "-timeout"))
+ return SELECT_TIMEOUT;
+ break;
+ }
+ if (!strcasecmp(atom + 2, "nd"))
+ return SEND;
+ if (!strncasecmp(atom + 2, "rv", 2)) {
+ if (!strncasecmp(atom + 4, "er", 2)) {
+ if (atom[6] == '\0')
+ return TOKEN_SERVER;
+ if (atom[6] == '-') {
+ if (!strcasecmp(atom + 7,
+ "duid"))
+ return SERVER_DUID;
+ if (!strcasecmp(atom + 7,
+ "name"))
+ return SERVER_NAME;
+ if (!strcasecmp(atom + 7,
+ "identifier"))
+ return SERVER_IDENTIFIER;
+ break;
+ }
+ break;
+ }
+ if (!strcasecmp(atom + 4, "fail"))
+ return NS_SERVFAIL;
+ break;
+ }
+ if (!strcasecmp(atom + 2, "t"))
+ return TOKEN_SET;
+ break;
+ }
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 'h') {
+ if (!strcasecmp(atom + 2, "ared-network"))
+ return SHARED_NETWORK;
+ if (!strcasecmp(atom + 2, "utdown"))
+ return SHUTDOWN;
+ break;
+ }
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 'i') {
+ if (!strcasecmp(atom + 2, "addr"))
+ return SIADDR;
+ if (!strcasecmp(atom + 2, "gned"))
+ return SIGNED;
+ if (!strcasecmp(atom + 2, "ze"))
+ return SIZE;
+ break;
+ }
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 'p') {
+ if (isascii(atom[2]) &&
+ tolower((unsigned char)atom[2]) == 'a') {
+ if (!strcasecmp(atom + 3, "ce"))
+ return SPACE;
+ if (!strcasecmp(atom + 3, "wn"))
+ return SPAWN;
+ break;
+ }
+ if (!strcasecmp(atom + 2, "lit"))
+ return SPLIT;
+ break;
+ }
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 't') {
+ if (isascii(atom[2]) &&
+ tolower((unsigned char)atom[2]) == 'a') {
+ if(!strncasecmp(atom + 3, "rt", 2)) {
+ if (!strcasecmp(atom + 5, "s"))
+ return STARTS;
+ if (!strcasecmp(atom + 5, "up"))
+ return STARTUP;
+ break;
+ }
+ if (isascii(atom[3]) &&
+ tolower((unsigned char)atom[3]) == 't') {
+ if (!strcasecmp(atom + 4, "e"))
+ return STATE;
+ if (!strcasecmp(atom + 4, "ic"))
+ return STATIC;
+ break;
+ }
+ }
+ if (!strcasecmp(atom + 2, "ring"))
+ return STRING_TOKEN;
+ break;
+ }
+ if (!strncasecmp(atom + 1, "ub", 2)) {
+ if (!strcasecmp(atom + 3, "class"))
+ return SUBCLASS;
+ if (!strcasecmp(atom + 3, "net"))
+ return SUBNET;
+ if (!strcasecmp(atom + 3, "net6"))
+ return SUBNET6;
+ if (!strcasecmp(atom + 3, "string"))
+ return SUBSTRING;
+ break;
+ }
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 'u') {
+ if (!strcasecmp(atom + 2, "ffix"))
+ return SUFFIX;
+ if (!strcasecmp(atom + 2, "persede"))
+ return SUPERSEDE;
+ }
+ if (!strcasecmp(atom + 1, "witch"))
+ return SWITCH;
+ break;
+ case 't':
+ if (!strcasecmp (atom + 1, "imestamp"))
+ return TIMESTAMP;
+ if (!strcasecmp (atom + 1, "imeout"))
+ return TIMEOUT;
+ if (!strcasecmp (atom + 1, "oken-ring"))
+ return TOKEN_RING;
+ if (!strcasecmp (atom + 1, "ext"))
+ return TEXT;
+ if (!strcasecmp (atom + 1, "stp"))
+ return TSTP;
+ if (!strcasecmp (atom + 1, "sfp"))
+ return TSFP;
+ if (!strcasecmp (atom + 1, "ransmission"))
+ return TRANSMISSION;
+ if (!strcasecmp(atom + 1, "emporary"))
+ return TEMPORARY;
+ break;
+ case 'u':
+ if (!strcasecmp (atom + 1, "case"))
+ return UCASE;
+ if (!strcasecmp (atom + 1, "nset"))
+ return UNSET;
+ if (!strcasecmp (atom + 1, "nsigned"))
+ return UNSIGNED;
+ if (!strcasecmp (atom + 1, "id"))
+ return UID;
+ if (!strncasecmp (atom + 1, "se", 2)) {
+ if (!strcasecmp (atom + 3, "r-class"))
+ return USER_CLASS;
+ if (!strcasecmp (atom + 3, "-host-decl-names"))
+ return USE_HOST_DECL_NAMES;
+ if (!strcasecmp (atom + 3,
+ "-lease-addr-for-default-route"))
+ return USE_LEASE_ADDR_FOR_DEFAULT_ROUTE;
+ break;
+ }
+ if (!strncasecmp (atom + 1, "nknown", 6)) {
+ if (!strcasecmp (atom + 7, "-clients"))
+ return UNKNOWN_CLIENTS;
+ if (!strcasecmp (atom + 7, "-state"))
+ return UNKNOWN_STATE;
+ if (!atom [7])
+ return UNKNOWN;
+ break;
+ }
+ if (!strcasecmp (atom + 1, "nauthenticated"))
+ return UNAUTHENTICATED;
+ if (!strcasecmp (atom + 1, "pdated-dns-rr"))
+ return UPDATED_DNS_RR;
+ if (!strcasecmp (atom + 1, "pdate"))
+ return UPDATE;
+ break;
+ case 'v':
+ if (!strcasecmp (atom + 1, "endor-class"))
+ return VENDOR_CLASS;
+ if (!strcasecmp (atom + 1, "endor"))
+ return VENDOR;
+ break;
+ case 'w':
+ if (!strcasecmp (atom + 1, "ith"))
+ return WITH;
+ if (!strcasecmp(atom + 1, "idth"))
+ return WIDTH;
+ break;
+ case 'y':
+ if (!strcasecmp (atom + 1, "iaddr"))
+ return YIADDR;
+ if (!strcasecmp (atom + 1, "xdomain"))
+ return NS_YXDOMAIN;
+ if (!strcasecmp (atom + 1, "xrrset"))
+ return NS_YXRRSET;
+ break;
+ case 'z':
+ if (!strcasecmp (atom + 1, "erolen"))
+ return ZEROLEN;
+ if (!strcasecmp (atom + 1, "one"))
+ return ZONE;
+ break;
+ }
+ return dfv;
+}
+
diff --git a/common/ctrace.c b/common/ctrace.c
new file mode 100644
index 0000000..578ea5e
--- /dev/null
+++ b/common/ctrace.c
@@ -0,0 +1,291 @@
+/* trace.c
+
+ Subroutines that support dhcp tracing... */
+
+/*
+ * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2001-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+
+#if defined (TRACING)
+void trace_interface_register (trace_type_t *ttype, struct interface_info *ip)
+{
+ trace_interface_packet_t tipkt;
+
+ if (trace_record ()) {
+ memset (&tipkt, 0, sizeof tipkt);
+ memcpy (&tipkt.hw_address,
+ &ip -> hw_address, sizeof ip -> hw_address);
+ if (ip->address_count)
+ memcpy(&tipkt.primary_address,
+ ip->addresses, sizeof(*ip->addresses));
+ memcpy (tipkt.name, ip -> name, sizeof ip -> name);
+ tipkt.index = htonl (ip -> index);
+
+ trace_write_packet (ttype, sizeof tipkt, (char *)&tipkt, MDL);
+ }
+}
+
+void trace_interface_input (trace_type_t *ttype, unsigned len, char *buf)
+{
+ trace_interface_packet_t *tipkt;
+ struct interface_info *ip;
+ struct sockaddr_in *sin;
+ struct iaddr addr;
+ isc_result_t status;
+
+ if (len != sizeof *tipkt) {
+ log_error ("trace interface packet size mismatch: %ld != %d",
+ (long)(sizeof *tipkt), len);
+ return;
+ }
+ tipkt = (trace_interface_packet_t *)buf;
+
+ ip = (struct interface_info *)0;
+ status = interface_allocate (&ip, MDL);
+ if (status != ISC_R_SUCCESS) {
+ foo:
+ log_error ("trace_interface_input: %s.",
+ isc_result_totext (status));
+ return;
+ }
+ ip -> ifp = dmalloc (sizeof *(ip -> ifp), MDL);
+ if (!ip -> ifp) {
+ interface_dereference (&ip, MDL);
+ status = ISC_R_NOMEMORY;
+ goto foo;
+ }
+
+ memcpy (&ip -> hw_address, &tipkt -> hw_address,
+ sizeof ip -> hw_address);
+ /* XXX: Without the full addresses state it's not quite a full
+ * trace.
+ */
+ ip->address_count = ip->address_max = 1;
+ ip->addresses = dmalloc(sizeof(*ip->addresses), MDL);
+ memcpy(ip->addresses, &tipkt->primary_address, sizeof(*ip->addresses));
+ memcpy (ip -> name, tipkt -> name, sizeof ip -> name);
+ ip -> index = ntohl (tipkt -> index);
+
+ interface_snorf (ip, 0);
+ if (dhcp_interface_discovery_hook)
+ (*dhcp_interface_discovery_hook) (ip);
+
+ /* Fake up an ifp. */
+ memcpy (ip -> ifp -> ifr_name, ip -> name, sizeof ip -> name);
+#ifdef HAVE_SA_LEN
+ ip -> ifp -> ifr_addr.sa_len = sizeof (struct sockaddr_in);
+#endif
+ sin = (struct sockaddr_in *)&ip -> ifp -> ifr_addr;
+ sin->sin_addr = ip->addresses[0];
+
+ addr.len = 4;
+ memcpy (addr.iabuf, &sin -> sin_addr.s_addr, addr.len);
+ if (dhcp_interface_setup_hook)
+ (*dhcp_interface_setup_hook) (ip, &addr);
+ interface_stash (ip);
+
+ if (!quiet_interface_discovery) {
+ log_info ("Listening on Trace/%s/%s%s%s",
+ ip -> name,
+ print_hw_addr (ip -> hw_address.hbuf [0],
+ ip -> hw_address.hlen - 1,
+ &ip -> hw_address.hbuf [1]),
+ (ip -> shared_network ? "/" : ""),
+ (ip -> shared_network ?
+ ip -> shared_network -> name : ""));
+ if (strcmp (ip -> name, "fallback")) {
+ log_info ("Sending on Trace/%s/%s%s%s",
+ ip -> name,
+ print_hw_addr (ip -> hw_address.hbuf [0],
+ ip -> hw_address.hlen - 1,
+ &ip -> hw_address.hbuf [1]),
+ (ip -> shared_network ? "/" : ""),
+ (ip -> shared_network ?
+ ip -> shared_network -> name : ""));
+ }
+ }
+ interface_dereference (&ip, MDL);
+}
+
+void trace_interface_stop (trace_type_t *ttype) {
+ /* XXX */
+}
+
+void trace_inpacket_stash (struct interface_info *interface,
+ struct dhcp_packet *packet,
+ unsigned len,
+ unsigned int from_port,
+ struct iaddr from,
+ struct hardware *hfrom)
+{
+ trace_inpacket_t tip;
+ trace_iov_t iov [2];
+
+ if (!trace_record ())
+ return;
+ tip.from_port = from_port;
+ tip.from = from;
+ tip.from.len = htonl (tip.from.len);
+ if (hfrom) {
+ tip.hfrom = *hfrom;
+ tip.havehfrom = 1;
+ } else {
+ memset (&tip.hfrom, 0, sizeof tip.hfrom);
+ tip.havehfrom = 0;
+ }
+ tip.index = htonl (interface -> index);
+
+ iov [0].buf = (char *)&tip;
+ iov [0].len = sizeof tip;
+ iov [1].buf = (char *)packet;
+ iov [1].len = len;
+ trace_write_packet_iov (inpacket_trace, 2, iov, MDL);
+}
+
+void trace_inpacket_input (trace_type_t *ttype, unsigned len, char *buf)
+{
+ trace_inpacket_t *tip;
+ int index;
+
+ if (len < sizeof *tip) {
+ log_error ("trace_input_packet: too short - %d", len);
+ return;
+ }
+ tip = (trace_inpacket_t *)buf;
+ index = ntohl (tip -> index);
+ tip -> from.len = ntohl (tip -> from.len);
+
+ if (index > interface_count ||
+ index < 0 ||
+ !interface_vector [index]) {
+ log_error ("trace_input_packet: unknown interface index %d",
+ index);
+ return;
+ }
+
+ if (!bootp_packet_handler) {
+ log_error ("trace_input_packet: no bootp packet handler.");
+ return;
+ }
+
+ (*bootp_packet_handler) (interface_vector [index],
+ (struct dhcp_packet *)(tip + 1),
+ len - sizeof *tip,
+ tip -> from_port,
+ tip -> from,
+ (tip -> havehfrom ?
+ &tip -> hfrom
+ : (struct hardware *)0));
+}
+
+void trace_inpacket_stop (trace_type_t *ttype) { }
+
+ssize_t trace_packet_send (struct interface_info *interface,
+ struct packet *packet,
+ struct dhcp_packet *raw,
+ size_t len,
+ struct in_addr from,
+ struct sockaddr_in *to,
+ struct hardware *hto)
+{
+ trace_outpacket_t tip;
+ trace_iov_t iov [2];
+
+ if (trace_record ()) {
+ if (hto) {
+ tip.hto = *hto;
+ tip.havehto = 1;
+ } else {
+ memset (&tip.hto, 0, sizeof tip.hto);
+ tip.havehto = 0;
+ }
+ tip.from.len = 4;
+ memcpy (tip.from.iabuf, &from, 4);
+ tip.to.len = 4;
+ memcpy (tip.to.iabuf, &to -> sin_addr, 4);
+ tip.to_port = to -> sin_port;
+ tip.index = htonl (interface -> index);
+
+ iov [0].buf = (char *)&tip;
+ iov [0].len = sizeof tip;
+ iov [1].buf = (char *)raw;
+ iov [1].len = len;
+ trace_write_packet_iov (outpacket_trace, 2, iov, MDL);
+ }
+ if (!trace_playback ()) {
+ return send_packet (interface, packet, raw, len,
+ from, to, hto);
+ }
+ return len;
+}
+
+void trace_outpacket_input (trace_type_t *ttype, unsigned len, char *buf)
+{
+ trace_outpacket_t *tip;
+ int index;
+
+ if (len < sizeof *tip) {
+ log_error ("trace_input_packet: too short - %d", len);
+ return;
+ }
+ tip = (trace_outpacket_t *)buf;
+ index = ntohl (tip -> index);
+
+ if (index > interface_count ||
+ index < 0 ||
+ !interface_vector [index]) {
+ log_error ("trace_input_packet: unknown interface index %d",
+ index);
+ return;
+ }
+
+ /* XXX would be nice to somehow take notice of these. */
+}
+
+void trace_outpacket_stop (trace_type_t *ttype) { }
+
+void trace_seed_stash (trace_type_t *ttype, unsigned seed)
+{
+ u_int32_t outseed;
+ if (!trace_record ())
+ return;
+ outseed = htonl (seed);
+ trace_write_packet (ttype, sizeof outseed, (char *)&outseed, MDL);
+ return;
+}
+
+void trace_seed_input (trace_type_t *ttype, unsigned length, char *buf)
+{
+ u_int32_t *seed;
+
+ if (length != sizeof seed) {
+ log_error ("trace_seed_input: wrong size (%d)", length);
+ }
+ seed = (u_int32_t *)buf;
+ srandom (ntohl (*seed));
+}
+
+void trace_seed_stop (trace_type_t *ttype) { }
+#endif /* TRACING */
diff --git a/common/dhcp-eval.5 b/common/dhcp-eval.5
new file mode 100644
index 0000000..8e95530
--- /dev/null
+++ b/common/dhcp-eval.5
@@ -0,0 +1,569 @@
+.\" $Id: dhcp-eval.5,v 1.29.24.4 2012/05/17 15:51:20 sar Exp $
+.\"
+.\" Copyright (c) 2009-2012,2014 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 2004,2007 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 1996-2003 by Internet Software Consortium
+.\"
+.\" 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 ISC DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+.\"
+.\" Internet Systems Consortium, Inc.
+.\" 950 Charter Street
+.\" Redwood City, CA 94063
+.\" <info@isc.org>
+.\" https://www.isc.org/
+.\"
+.\" Support and other services are available for ISC products - see
+.\" https://www.isc.org for more information or to learn more about ISC.
+.\"
+.TH dhcp-eval 5
+.SH NAME
+dhcp-eval - ISC DHCP conditional evaluation
+.SH DESCRIPTION
+The Internet Systems Consortium DHCP client and server both provide
+the ability to perform conditional behavior depending on the contents
+of packets they receive. The syntax for specifying this conditional
+behaviour is documented here.
+.SH REFERENCE: CONDITIONAL BEHAVIOUR
+Conditional behaviour may be specified using the if statement and the else
+or elsif statements or the switch and case statements.
+A conditional statement can appear anywhere
+that a regular statement (e.g., an option statement) can appear, and
+can enclose one or more such statements.
+.PP
+.B CONDITIONAL BEHAVIOUR: IF
+.PP
+A typical conditional if statement in a server might be:
+.PP
+.nf
+if option dhcp-user-class = "accounting" {
+ max-lease-time 17600;
+ option domain-name "accounting.example.org";
+ option domain-name-servers ns1.accounting.example.org,
+ ns2.accounting.example.org;
+} elsif option dhcp-user-class = "sales" {
+ max-lease-time 17600;
+ option domain-name "sales.example.org";
+ option domain-name-servers ns1.sales.example.org,
+ ns2.sales.example.org;
+} elsif option dhcp-user-class = "engineering" {
+ max-lease-time 17600;
+ option domain-name "engineering.example.org";
+ option domain-name-servers ns1.engineering.example.org,
+ ns2.engineering.example.org;
+} else {
+ max-lease-time 600;
+ option domain-name "misc.example.org";
+ option domain-name-servers ns1.misc.example.org,
+ ns2.misc.example.org;
+}
+.fi
+.PP
+On the client side, an example of conditional evaluation might be:
+.PP
+.nf
+# example.org filters DNS at its firewall, so we have to use their DNS
+# servers when we connect to their network. If we are not at
+# example.org, prefer our own DNS server.
+if not option domain-name = "example.org" {
+ prepend domain-name-servers 127.0.0.1;
+}
+.fi
+.PP
+The
+.B if
+statement and the
+.B elsif
+continuation statement both take boolean expressions as their
+arguments. That is, they take expressions that, when evaluated,
+produce a boolean result. If the expression evaluates to true, then
+the statements enclosed in braces following the
+.B if
+statement are executed, and all subsequent
+.B elsif
+and
+.B else
+clauses are skipped. Otherwise, each subsequent
+.B elsif
+clause's expression is checked, until an elsif clause is encountered
+whose test evaluates to true. If such a clause is found, the
+statements in braces following it are executed, and then any
+subsequent
+.B elsif
+and
+.B else
+clauses are skipped. If all the
+.B if
+and
+.B elsif
+clauses are checked but none
+of their expressions evaluate true, then if there is an
+.B else
+clause, the statements enclosed in braces following the
+.B else
+are evaluated. Boolean expressions that evaluate to null are
+treated as false in conditionals.
+.PP
+.B CONDITIONAL BEHAVIOUR: SWITCH
+.PP
+The above example can be rewritten using a switch construct as well.
+.PP
+.nf
+switch (option dhcp-user-class) {
+ case "accounting":
+ max-lease-time 17600;
+ option domain-name "accounting.example.org";
+ option domain-name-servers ns1.accounting.example.org,
+ ns2.accounting.example.org;
+ case "sales":
+ max-lease-time 17600;
+ option domain-name "sales.example.org";
+ option domain-name-servers ns1.sales.example.org,
+ ns2.sales.example.org;
+ break;
+ case "engineering":
+ max-lease-time 17600;
+ option domain-name "engineering.example.org";
+ option domain-name-servers ns1.engineering.example.org,
+ ns2.engineering.example.org;
+ break;
+ default:
+ max-lease-time 600;
+ option domain-name "misc.example.org";
+ option domain-name-servers ns1.misc.example.org,
+ ns2.misc.example.org;
+ break;
+}
+.fi
+.PP
+The
+.B switch
+statement and the
+.B case
+statements can both be data expressions or numeric expressions. Within
+a switch statement they all must be the same type. The server
+evaluates the expression from the switch statement and then it evaluates
+the expressions from the case statements until it finds a match.
+.PP
+If it finds a match it starts executing statements from that case
+until the next break statement. If it doesn't find a match it
+starts from the default statement and again proceeds to the next
+break statement. If there is no match and no default it does nothing.
+.PP
+.SH BOOLEAN EXPRESSIONS
+The following is the current list of boolean expressions that are
+supported by the DHCP distribution.
+.PP
+.I data-expression-1 \fB=\fR \fIdata-expression-2\fR
+.RS 0.25i
+.PP
+The \fB=\fR operator compares the values of two data expressions,
+returning true if they are the same, false if they are not. If
+either the left-hand side or the right-hand side are null, the
+result is also null.
+.RE
+.PP
+.I data-expression-1 \fB~=\fR \fIdata-expression-2\fR
+.I data-expression-1 \fB~~\fR \fIdata-expression-2\fR
+.RS 0.25i
+.PP
+The \fB~=\fR and \fB~~\fR operators (not available on all systems) perform
+extended regex(7) matching of the values of two data expressions, returning
+true if \fIdata-expression-1\fR matches against the regular expression
+evaluated by \fIdata-expression-2\fR, or false if it does not match or
+encounters some error. If either the left-hand side or the right-hand side
+are null or empty strings, the result is also false. The \fB~~\fR operator
+differs from the \fB~=\fR operator in that it is case-insensitive.
+.RE
+.PP
+.I boolean-expression-1 \fBand\fR \fIboolean-expression-2\fR
+.PP
+.RS 0.25i
+The \fBand\fR operator evaluates to true if the boolean expression on
+the left-hand side and the boolean expression on the right-hand side
+both evaluate to true. Otherwise, it evaluates to false. If either
+the expression on the left-hand side or the expression on the
+right-hand side are null, the result is null.
+.RE
+.PP
+.I boolean-expression-1 \fBor\fR \fIboolean-expression-2\fR
+.PP
+.RS 0.25i
+The \fBor\fR operator evaluates to true if either the boolean
+expression on the left-hand side or the boolean expression on the
+right-hand side evaluate to true. Otherwise, it evaluates to false.
+If either the expression on the left-hand side or the expression on
+the right-hand side are null, the result is null.
+.RE
+.PP
+.B not \fIboolean-expression
+.PP
+.RS 0.25i
+The \fBnot\fR operator evaluates to true if \fIboolean-expression\fR
+evaluates to false, and returns false if \fIboolean-expression\fR evaluates
+to true. If \fIboolean-expression\fR evaluates to null, the result
+is also null.
+.RE
+.PP
+.B exists \fIoption-name\fR
+.PP
+.RS 0.25i
+The \fBexists\fR expression returns true if the specified option
+exists in the incoming DHCP packet being processed.
+.RE
+.B known
+.PP
+.RS 0.25i
+The \fBknown\fR expression returns true if the client whose request is
+currently being processed is known - that is, if there's a host
+declaration for it.
+.RE
+.B static
+.PP
+.RS 0.25i
+The \fBstatic\fR expression returns true if the lease assigned to the
+client whose request is currently being processed is derived from a static
+address assignment.
+.RE
+.SH DATA EXPRESSIONS
+Several of the boolean expressions above depend on the results of
+evaluating data expressions. A list of these expressions is provided
+here.
+.PP
+.B substring (\fIdata-expr\fB, \fIoffset\fB, \fIlength\fB)\fR
+.PP
+.RS 0.25i
+The \fBsubstring\fR operator evaluates the data expression and returns
+the substring of the result of that evaluation that starts
+\fIoffset\fR bytes from the beginning, continuing for \fIlength\fR
+bytes. \fIOffset\fR and \fIlength\fR are both numeric expressions.
+If \fIdata-expr\fR, \fIoffset\fR or \fIlength\fR evaluate to null,
+then the result is also null. If \fIoffset\fR is greater than or
+equal to the length of the evaluated data, then a zero-length data
+string is returned. If \fIlength\fI is greater then the remaining
+length of the evaluated data after \fIoffset\fR, then a data string
+containing all data from \fIoffset\fR to the end of the evaluated data
+is returned.
+.RE
+.PP
+.B suffix (\fIdata-expr\fB, \fIlength\fB)\fR
+.PP
+.RS 0.25i
+The \fBsuffix\fR operator evaluates \fIdata-expr\fR and returns the
+last \fIlength\fR bytes of the result of that evaluation. \fILength\fR
+is a numeric expression. If \fIdata-expr\fR or \fIlength\fR evaluate
+to null, then the result is also null. If \fIsuffix\fR evaluates to a
+number greater than the length of the evaluated data, then the
+evaluated data is returned.
+.RE
+.PP
+.B lcase (\fIdata-expr\fB)\fR
+.PP
+.RS 0.25i
+The \fBlcase\fR function returns the result of evaluating
+\fIdata-expr\fR converted to lower case. If \fIdata-expr\fR evaluates
+to null, then the result is also null.
+.RE
+.PP
+.B ucase (\fIdata-expr\fB)\fR
+.PP
+.RS 0.25i
+The \fBucase\fR function returns the result of evaluating
+\fIdata-expr\fR converted to upper case. If \fIdata-expr\fR evaluates
+to null, then the result is also null.
+.RE
+.PP
+.B option \fIoption-name\fR
+.PP
+.RS 0.25i
+The \fBoption\fR operator returns the contents of the specified option in
+the packet to which the server is responding.
+.RE
+.PP
+.B config-option \fIoption-name\fR
+.PP
+.RS 0.25i
+The \fBconfig-option\fR operator returns the value for the specified option
+that the DHCP client or server has been configured to send.
+.RE
+.PP
+.B gethostname()
+.PP
+.RS 0.25i
+The \fBgethostname()\fR function returns a data string whose contents are a
+character string, the results of calling gethostname() on the local
+system with a size limit of 255 bytes (not including NULL terminator). This
+can be used for example to configure dhclient to send the local hostname
+without knowing the local hostname at the time dhclient.conf is written.
+.RE
+.PP
+.B hardware
+.PP
+.RS 0.25i
+The \fBhardware\fR operator returns a data string whose first element
+is the type of network interface indicated in packet being considered,
+and whose subsequent elements are client's link-layer address. If
+there is no packet, or if the RFC2131 \fIhlen\fR field is invalid,
+then the result is null. Hardware types include ethernet (1),
+token-ring (6), and fddi (8). Hardware types are specified by the
+IETF, and details on how the type numbers are defined can be found in
+RFC2131 (in the ISC DHCP distribution, this is included in the doc/
+subdirectory).
+.RE
+.PP
+.B packet (\fIoffset\fB, \fIlength\fB)\fR
+.PP
+.RS 0.25i
+The \fBpacket\fR operator returns the specified portion of the packet
+being considered, or null in contexts where no packet is being
+considered. \fIOffset\fR and \fIlength\fR are applied to the
+contents packet as in the \fBsubstring\fR operator.
+.RE
+.PP
+.I string
+.PP
+.RS 0.25i
+A string, enclosed in quotes, may be specified as a data expression,
+and returns the text between the quotes, encoded in ASCII. The
+backslash ('\\') character is treated specially, as in C programming: '\\t'
+means TAB, '\\r' means carriage return, '\\n' means newline, and '\\b' means
+bell. Any octal value can be specified with '\\nnn', where nnn is any
+positive octal number less than 0400. Any hexadecimal value can be
+specified with '\\xnn', where nn is any positive hexadecimal number less
+than or equal to 0xff.
+.RE
+.PP
+.I colon-separated hexadecimal list
+.PP
+.RS 0.25i
+A list of hexadecimal octet values, separated by colons, may be
+specified as a data expression.
+.RE
+.PP
+.B concat (\fIdata-expr1\fB, ..., \fIdata-exprN\fB)\fR
+.RS 0.25i
+The expressions are evaluated, and the results of each evaluation are
+concatenated in the sequence that the subexpressions are listed. If
+any subexpression evaluates to null, the result of the concatenation
+is null.
+.RE
+.PP
+.B reverse (\fInumeric-expr1\fB, \fIdata-expr2\fB)\fR
+.RS 0.25i
+The two expressions are evaluated, and then the result of evaluating
+the data expression is reversed in place, using hunks of the size
+specified in the numeric expression. For example, if the numeric
+expression evaluates to four, and the data expression evaluates to
+twelve bytes of data, then the reverse expression will evaluate to
+twelve bytes of data, consisting of the last four bytes of the
+input data, followed by the middle four bytes, followed by the first
+four bytes.
+.RE
+.PP
+.B leased-address
+.RS 0.25i
+In any context where the client whose request is being processed has
+been assigned an IP address, this data expression returns that IP
+address. In any context where the client whose request is being
+processed has not been assigned an ip address, if this data expression
+is found in executable statements executed on that client's behalf,
+a log message indicating "there is no lease associated with this client"
+is syslogged to the debug level (this is considered dhcpd.conf debugging
+information).
+.RE
+.PP
+.B binary-to-ascii (\fInumeric-expr1\fB, \fInumeric-expr2\fB,
+.B \fIdata-expr1\fB,\fR \fIdata-expr2\fB)\fR
+.RS 0.25i
+Converts the result of evaluating data-expr2 into a text string
+containing one number for each element of the result of evaluating
+data-expr2. Each number is separated from the other by the result of
+evaluating data-expr1. The result of evaluating numeric-expr1
+specifies the base (2 through 16) into which the numbers should be
+converted. The result of evaluating numeric-expr2 specifies the
+width in bits of each number, which may be either 8, 16 or 32.
+.PP
+As an example of the preceding three types of expressions, to produce
+the name of a PTR record for the IP address being assigned to a
+client, one could write the following expression:
+.RE
+.PP
+.nf
+ concat (binary-to-ascii (10, 8, ".",
+ reverse (1, leased-address)),
+ ".in-addr.arpa.");
+
+.fi
+.RE
+.PP
+.B encode-int (\fInumeric-expr\fB, \fIwidth\fB)\fR
+.RS 0.25i
+Numeric-expr is evaluated and encoded as a data string of the
+specified width, in network byte order (most significant byte first).
+If the numeric expression evaluates to the null value, the result is
+also null.
+.RE
+.PP
+.B pick-first-value (\fIdata-expr1\fR [ ... \fIexpr\fRn ] \fB)\fR
+.RS 0.25i
+The pick-first-value function takes any number of data expressions as
+its arguments. Each expression is evaluated, starting with the first
+in the list, until an expression is found that does not evaluate to a
+null value. That expression is returned, and none of the subsequent
+expressions are evaluated. If all expressions evaluate to a null
+value, the null value is returned.
+.RE
+.PP
+.B host-decl-name
+.RS 0.25i
+The host-decl-name function returns the name of the host declaration
+that matched the client whose request is currently being processed, if
+any. If no host declaration matched, the result is the null value.
+.RE
+.SH NUMERIC EXPRESSIONS
+Numeric expressions are expressions that evaluate to an integer. In
+general, the maximum size of such an integer should not be assumed to
+be representable in fewer than 32 bits, but the precision of such
+integers may be more than 32 bits.
+.PP
+In addition to the following operators several standard math functions
+are available. They are:
+.nf
+operation symbol
+add \fB+\fR
+subtract \fB-\fR
+divide \fB/\fR
+multiply \fB*\fR
+modulus \fB%\fR
+bitwise and \fB&\fR
+bitwise or \fB|\fR
+bitwise xor \fB^\fR
+.fi
+.PP
+.B extract-int (\fIdata-expr\fB, \fIwidth\fB)\fR
+.PP
+.RS 0.25i
+The \fBextract-int\fR operator extracts an integer value in network
+byte order from the result of evaluating the specified data
+expression. Width is the width in bits of the integer to extract.
+Currently, the only supported widths are 8, 16 and 32. If the
+evaluation of the data expression doesn't provide sufficient bits to
+extract an integer of the specified size, the null value is returned.
+.RE
+.PP
+.B lease-time
+.PP
+.RS 0.25i
+The duration of the current lease - that is, the difference between
+the current time and the time that the lease expires.
+.RE
+.PP
+.I number
+.PP
+.RS 0.25i
+Any number between zero and the maximum representable size may be
+specified as a numeric expression.
+.RE
+.PP
+.B client-state
+.PP
+.RS 0.25i
+The current state of the client instance being processed. This is
+only useful in DHCP client configuration files. Possible values are:
+.TP 2
+.I \(bu
+Booting - DHCP client is in the INIT state, and does not yet have an
+IP address. The next message transmitted will be a DHCPDISCOVER,
+which will be broadcast.
+.TP
+.I \(bu
+Reboot - DHCP client is in the INIT-REBOOT state. It has an IP
+address, but is not yet using it. The next message to be transmitted
+will be a DHCPREQUEST, which will be broadcast. If no response is
+heard, the client will bind to its address and move to the BOUND state.
+.TP
+.I \(bu
+Select - DHCP client is in the SELECTING state - it has received at
+least one DHCPOFFER message, but is waiting to see if it may receive
+other DHCPOFFER messages from other servers. No messages are sent in
+the SELECTING state.
+.TP
+.I \(bu
+Request - DHCP client is in the REQUESTING state - it has received at
+least one DHCPOFFER message, and has chosen which one it will
+request. The next message to be sent will be a DHCPREQUEST message,
+which will be broadcast.
+.TP
+.I \(bu
+Bound - DHCP client is in the BOUND state - it has an IP address. No
+messages are transmitted in this state.
+.TP
+.I \(bu
+Renew - DHCP client is in the RENEWING state - it has an IP address,
+and is trying to contact the server to renew it. The next message to
+be sent will be a DHCPREQUEST message, which will be unicast directly
+to the server.
+.TP
+.I \(bu
+Rebind - DHCP client is in the REBINDING state - it has an IP address,
+and is trying to contact any server to renew it. The next message to
+be sent will be a DHCPREQUEST, which will be broadcast.
+.RE
+.SH REFERENCE: ACTION EXPRESSIONS
+.PP
+.B log (\fIpriority\fB, \fIdata-expr\fB)\fR
+.RS 0.25i
+.PP
+Logging statements may be used to send information to the standard logging
+channels. A logging statement includes an optional priority (\fBfatal\fR,
+\fBerror\fR, \fBinfo\fR, or \fBdebug\fR), and a data expression.
+.PP
+Logging statements take only a single data expression argument, so if you
+want to output multiple data values, you will need to use the \fBconcat\fR
+operator to concatenate them.
+.RE
+.PP
+.B execute (\fIcommand-path\fB [, \fIdata-expr1\fB, ... \fIdata-exprN\fB]);\fR
+.RS 0.25i
+.PP
+The \fBexecute\fR statement runs an external command. The first argument
+is a string literal containing the name or path of the command to run.
+The other arguments, if present, are either string literals or data-
+expressions which evaluate to text strings, to be passed as command-line
+arguments to the command.
+.PP
+\fBexecute\fR is synchronous; the program will block until the external
+command being run has finished. Please note that lengthy program
+execution (for example, in an "on commit" in dhcpd.conf) may result in
+bad performance and timeouts. Only external applications with very short
+execution times are suitable for use.
+.PP
+Passing user-supplied data to an external application might be dangerous.
+Make sure the external application checks input buffers for validity.
+Non-printable ASCII characters will be converted into dhcpd.conf language
+octal escapes ("\\nnn"), make sure your external command handles them as
+such.
+.PP
+It is possible to use the execute statement in any context, not only
+on events. If you put it in a regular scope in the configuration file
+you will execute that command every time a scope is evaluated.
+.RE
+.SH REFERENCE: DYNAMIC DNS UPDATES
+.PP
+See the dhcpd.conf and dhclient.conf man pages for more information
+about DDNS.
+.SH SEE ALSO
+dhcpd.conf(5), dhcpd.leases(5), dhclient.conf(5), dhcp-options(5), dhcpd(8),
+dhclient(8), RFC2132, RFC2131.
+.SH AUTHOR
+Information about Internet Systems Consortium can be found at
+.B https://www.isc.org.
diff --git a/common/dhcp-options.5 b/common/dhcp-options.5
new file mode 100644
index 0000000..1e418ca
--- /dev/null
+++ b/common/dhcp-options.5
@@ -0,0 +1,2157 @@
+.\" $Id: dhcp-options.5,v 1.45.18.6 2011/05/20 14:33:26 tomasz Exp $
+.\"
+.\" Copyright (c) 2012-2014 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 2004-2010 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 1996-2003 by Internet Software Consortium
+.\"
+.\" 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 ISC DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+.\"
+.\" Internet Systems Consortium, Inc.
+.\" 950 Charter Street
+.\" Redwood City, CA 94063
+.\" <info@isc.org>
+.\" https://www.isc.org/
+.\"
+.\" Support and other services are available for ISC products - see
+.\" https://www.isc.org for more information or to learn more about ISC.
+.\"
+.TH dhcp-options 5
+.SH NAME
+dhcp-options - Dynamic Host Configuration Protocol options
+.SH DESCRIPTION
+The Dynamic Host Configuration protocol allows the client to receive
+.B options
+from the DHCP server describing the network configuration and various
+services that are available on the network. When configuring
+.B dhcpd(8)
+or
+.B dhclient(8) ,
+options must often be declared. The syntax for declaring options,
+and the names and formats of the options that can be declared, are
+documented here.
+.SH REFERENCE: OPTION STATEMENTS
+.PP
+DHCP \fIoption\fR statements always start with the \fIoption\fR
+keyword, followed by an option name, followed by option data. The
+option names and data formats are described below. It is not
+necessary to exhaustively specify all DHCP options - only those
+options which are needed by clients must be specified.
+.PP
+Option data comes in a variety of formats, as defined below:
+.PP
+The
+.B ip-address
+data type can be entered either as an explicit IP
+address (e.g., 239.254.197.10) or as a domain name (e.g.,
+haagen.isc.org). When entering a domain name, be sure that that
+domain name resolves to a single IP address.
+.PP
+The
+.B ip6-address
+data specifies an IPv6 address, like ::1 or 3ffe:bbbb:aaaa:aaaa::1.
+.PP
+The
+.B int32
+data type specifies a signed 32-bit integer. The
+.B uint32
+data type specifies an unsigned 32-bit integer. The
+.B int16
+and
+.B uint16
+data types specify signed and unsigned 16-bit integers. The
+.B int8
+and
+.B uint8
+data types specify signed and unsigned 8-bit integers.
+Unsigned 8-bit integers are also sometimes referred to as octets.
+.PP
+The
+.B text
+data type specifies an NVT ASCII string, which must be
+enclosed in double quotes - for example, to specify a root-path
+option, the syntax would be
+.nf
+.sp 1
+option root-path "10.0.1.4:/var/tmp/rootfs";
+.fi
+.PP
+The
+.B domain-name
+data type specifies a domain name, which must not be
+enclosed in double quotes. This data type is not used for any
+existing DHCP options. The domain name is stored just as if it were
+a text option.
+.PP
+The
+.B domain-list
+data type specifies a list of domain names, enclosed in double quotes and
+separated by commas ("example.com", "foo.example.com").
+.PP
+The
+.B flag
+data type specifies a boolean value. Booleans can be either true or
+false (or on or off, if that makes more sense to you).
+.PP
+The
+.B string
+data type specifies either an NVT ASCII string
+enclosed in double quotes, or a series of octets specified in
+hexadecimal, separated by colons. For example:
+.nf
+.sp 1
+ option dhcp-client-identifier "CLIENT-FOO";
+or
+ option dhcp-client-identifier 43:4c:49:45:54:2d:46:4f:4f;
+.fi
+.SH SETTING OPTION VALUES USING EXPRESSIONS
+Sometimes it's helpful to be able to set the value of a DHCP option
+based on some value that the client has sent. To do this, you can
+use expression evaluation. The
+.B dhcp-eval(5)
+manual page describes how to write expressions. To assign the result
+of an evaluation to an option, define the option as follows:
+.nf
+.sp 1
+ \fBoption \fImy-option \fB= \fIexpression \fB;\fR
+.fi
+.PP
+For example:
+.nf
+.sp 1
+ option hostname = binary-to-ascii (16, 8, "-",
+ substring (hardware, 1, 6));
+.fi
+.SH STANDARD DHCPV4 OPTIONS
+The documentation for the various options mentioned below is taken
+from the latest IETF draft document on DHCP options. Options not
+listed below may not yet be implemented, but it is possible to use
+such options by defining them in the configuration file. Please see
+the DEFINING NEW OPTIONS heading later in this document for more
+information.
+.PP
+Some of the options documented here are automatically generated by
+the DHCP server or by clients, and cannot be configured by the user.
+The value of such an option can be used in the configuration file of
+the receiving DHCP protocol agent (server or client), for example in
+conditional expressions. However, the value of the option cannot be
+used in the configuration file of the sending agent, because the value
+is determined only \fIafter\fR the configuration file has been
+processed. In the following documentation, such options will be shown
+as "not user configurable"
+.PP
+The standard options are:
+.PP
+.B option \fBall-subnets-local\fR \fIflag\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies whether or not the client may assume that all
+subnets of the IP network to which the client is connected use the
+same MTU as the subnet of that network to which the client is
+directly connected. A value of true indicates that all subnets share
+the same MTU. A value of false means that the client should assume that
+some subnets of the directly connected network may have smaller MTUs.
+.RE
+.PP
+.B option \fBarp-cache-timeout\fR \fIuint32\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the timeout in seconds for ARP cache entries.
+.RE
+.PP
+.B option \fBassociated-ip\fR \fIip-address\fR [\fB,\fR
+\fIip-address\fR... ]\fB;\fR
+.RS 0.25i
+.PP
+This option is part of lease query. It is used to
+return all of the IP addresses associated with a given DHCP client.
+.PP
+This option is not user configurable.
+.RE
+.PP
+.B option \fBbcms-controller-address\fR \fIip-address\fR [\fB,\fR
+\fIip-address\fR... ]\fB;\fR
+.RS 0.25i
+.PP
+This option configures a list of IPv4 addresses for use as Broadcast and
+Multicast Controller Servers ("BCMS").
+.RE
+.PP
+.B option \fBbcms-controller-names\fR \fIdomain-list\fR\fB;\fR
+.RS 0.25i
+.PP
+This option contains the domain names of local Broadcast and
+Multicast Controller Servers ("BCMS") controllers which the client
+may use.
+.RE
+.PP
+.B option \fBbootfile-name\fR \fItext\fR\fB;\fR
+.RS 0.25i
+.PP
+This option is used to identify a bootstrap file. If supported by the
+client, it should have the same effect as the \fBfilename\fR
+declaration. BOOTP clients are unlikely to support this option. Some
+DHCP clients will support it, and others actually require it.
+.RE
+.PP
+.B option \fBboot-size\fR \fIuint16\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the length in 512-octet blocks of the default
+boot image for the client.
+.RE
+.PP
+.B option \fBbroadcast-address\fR \fIip-address\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the broadcast address in use on the client's
+subnet. Legal values for broadcast addresses are specified in
+section 3.2.1.3 of STD 3 (RFC1122).
+.RE
+.PP
+.B option \fBclient-last-transaction-time\fR \fIuint32\fR\fB;\fR
+.RS 0.25i
+.PP
+This option is part of lease query. It allows the
+receiver to determine the time of the most recent access by the
+client. The value is a duration in seconds from when the client
+last communicated with the DHCP server.
+.PP
+This option is not user configurable.
+.RE
+.PP
+.B option \fBcookie-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR...
+]\fB;\fR
+.RS 0.25i
+.PP
+The cookie server option specifies a list of RFC 865 cookie
+servers available to the client. Servers should be listed in order
+of preference.
+.RE
+.PP
+.B option \fBdefault-ip-ttl\fR \fIuint8;\fR
+.RS 0.25i
+.PP
+This option specifies the default time-to-live that the client should
+use on outgoing datagrams.
+.RE
+.PP
+.B option \fBdefault-tcp-ttl\fR \fIuint8\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the default TTL that the client should use when
+sending TCP segments. The minimum value is 1.
+.RE
+.PP
+.B option \fBdefault-url\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The format and meaning of this option is not described in any standards
+document, but is claimed to be in use by Apple Computer. It is not known
+what clients may reasonably do if supplied with this option. Use at your
+own risk.
+.RE
+.PP
+.B option \fBdhcp-client-identifier\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+This option can be used to specify a DHCP client identifier in a
+host declaration, so that dhcpd can find the host record by matching
+against the client identifier.
+.PP
+Please be aware that some DHCP clients, when configured with client
+identifiers that are ASCII text, will prepend a zero to the ASCII
+text. So you may need to write:
+.nf
+
+ option dhcp-client-identifier "\\0foo";
+
+rather than:
+
+ option dhcp-client-identifier "foo";
+.fi
+.RE
+.PP
+.B option \fBdhcp-lease-time\fR \fIuint32\fR\fB;\fR
+.RS 0.25i
+.PP
+This option is used in a client request (DHCPDISCOVER or DHCPREQUEST)
+to allow the client to request a lease time for the IP address. In a
+server reply (DHCPOFFER), a DHCP server uses this option to specify
+the lease time it is willing to offer.
+.PP
+This option is not directly user configurable in the server; refer to the
+\fImax-lease-time\fR and \fIdefault-lease-time\fR server options in
+.B dhcpd.conf(5).
+.RE
+.PP
+.B option \fBdhcp-max-message-size\fR \fIuint16\fR\fB;\fR
+.RS 0.25i
+.PP
+This option, when sent by the client, specifies the maximum size of
+any response that the server sends to the client. When specified on
+the server, if the client did not send a dhcp-max-message-size option,
+the size specified on the server is used. This works for BOOTP as
+well as DHCP responses.
+.RE
+.PP
+.B option \fBdhcp-message\fR \fItext\fR\fB;\fR
+.RS 0.25i
+.PP
+This option is used by a DHCP server to provide an error message to a
+DHCP client in a DHCPNAK message in the event of a failure. A client
+may use this option in a DHCPDECLINE message to indicate why the
+client declined the offered parameters.
+.PP
+This option is not user configurable.
+.RE
+.PP
+.B option \fBdhcp-message-type\fR \fIuint8\fR\fB;\fR
+.RS 0.25i
+.PP
+This option, sent by both client and server, specifies the type of DHCP
+message contained in the DHCP packet. Possible values (taken directly from
+RFC2132) are:
+.PP
+.nf
+ 1 DHCPDISCOVER
+ 2 DHCPOFFER
+ 3 DHCPREQUEST
+ 4 DHCPDECLINE
+ 5 DHCPACK
+ 6 DHCPNAK
+ 7 DHCPRELEASE
+ 8 DHCPINFORM
+.fi
+.PP
+This option is not user configurable.
+.PP
+.RE
+.B option \fBdhcp-option-overload\fR \fIuint8\fR\fB;\fR
+.RS 0.25i
+.PP
+This option is used to indicate that the DHCP \'sname\' or \'file\'
+fields are being overloaded by using them to carry DHCP options. A
+DHCP server inserts this option if the returned parameters will
+exceed the usual space allotted for options.
+.PP
+If this option is present, the client interprets the specified
+additional fields after it concludes interpretation of the standard
+option fields.
+.PP
+Legal values for this option are:
+.PP
+.nf
+ 1 the \'file\' field is used to hold options
+ 2 the \'sname\' field is used to hold options
+ 3 both fields are used to hold options
+.fi
+.PP
+This option is not user configurable.
+.PP
+.RE
+.PP
+.B option \fBdhcp-parameter-request-list\fR \fIuint8\fR [\fB,\fR
+\fIuint8\fR... ]\fB;\fR
+.RS 0.25i
+.PP
+This option, when sent by the client, specifies which options the
+client wishes the server to return. Normally, in the ISC DHCP
+client, this is done using the \fIrequest\fR statement. If this
+option is not specified by the client, the DHCP server will normally
+return every option that is valid in scope and that fits into the
+reply. When this option is specified on the server, the server
+returns the specified options. This can be used to force a client to
+take options that it hasn't requested, and it can also be used to
+tailor the response of the DHCP server for clients that may need a
+more limited set of options than those the server would normally
+return.
+.RE
+.PP
+.B option \fBdhcp-rebinding-time\fR \fIuint32\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the number of seconds from the time a client gets
+an address until the client transitions to the REBINDING state.
+.PP
+This option is user configurable, but it will be ignored if the value is
+greater than or equal to the lease time.
+.PP
+To make DHCPv4+DHCPv6 migration easier in the future, any value configured
+in this option is also used as a DHCPv6 "T1" (renew) time.
+.PP
+.RE
+.PP
+.B option \fBdhcp-renewal-time\fR \fIuint32\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the number of seconds from the time a client gets
+an address until the client transitions to the RENEWING state.
+.PP
+This option is user configurable, but it will be ignored if the value is
+greater than or equal to the rebinding time, or lease time.
+.PP
+To make DHCPv4+DHCPv6 migration easier in the future, any value configured
+in this option is also used as a DHCPv6 "T2" (rebind) time.
+.PP
+.RE
+.PP
+.B option \fBdhcp-requested-address\fR \fIip-address\fR\fB;\fR
+.RS 0.25i
+.PP
+This option is used by the client in a DHCPDISCOVER to
+request that a particular IP address be assigned.
+.PP
+This option is not user configurable.
+.PP
+.RE
+.PP
+.B option \fBdhcp-server-identifier\fR \fIip-address\fR\fB;\fR
+.RS 0.25i
+.PP
+This option is used in DHCPOFFER and DHCPREQUEST messages, and may
+optionally be included in the DHCPACK and DHCPNAK messages. DHCP
+servers include this option in the DHCPOFFER in order to allow the
+client to distinguish between lease offers. DHCP clients use the
+contents of the \'server identifier\' field as the destination address
+for any DHCP messages unicast to the DHCP server. DHCP clients also
+indicate which of several lease offers is being accepted by including
+this option in a DHCPREQUEST message.
+.PP
+The value of this option is the IP address of the server.
+.PP
+This option is not directly user configurable. See the
+\fIserver-identifier\fR server option in
+.B \fIdhcpd.conf(5).
+.PP
+.RE
+.PP
+.B option \fBdomain-name\fR \fItext\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the domain name that client should use when
+resolving hostnames via the Domain Name System.
+.RE
+.PP
+.B option \fBdomain-name-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR...
+]\fB;\fR
+.RS 0.25i
+.PP
+The domain-name-servers option specifies a list of Domain Name System
+(STD 13, RFC 1035) name servers available to the client. Servers
+should be listed in order of preference.
+.RE
+.PP
+.B option \fBdomain-search\fR \fIdomain-list\fR\fB;\fR
+.RS 0.25i
+.PP
+The domain-search option specifies a \'search list\' of Domain Names to be
+used by the client to locate not-fully-qualified domain names. The difference
+between this option and historic use of the domain-name option for the same
+ends is that this option is encoded in RFC1035 compressed labels on the wire.
+For example:
+.nf
+.sp 1
+ option domain-search "example.com", "sales.example.com",
+ "eng.example.com";
+.fi
+.RE
+.PP
+.B option \fBextensions-path\fR \fItext\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the name of a file containing additional options
+to be interpreted according to the DHCP option format as specified in
+RFC2132.
+.RE
+.PP
+.B option \fBfinger-server\fR \fIip-address\fR [\fB,\fR
+\fIip-address\fR... ]\fB;\fR
+.RS 0.25i
+.PP
+The Finger server option specifies a list of Finger servers available
+to the client. Servers should be listed in order of preference.
+.RE
+.PP
+.B option \fBfont-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR...
+]\fB;\fR
+.RS 0.25i
+.PP
+This option specifies a list of X Window System Font servers available
+to the client. Servers should be listed in order of preference.
+.RE
+.PP
+.B option \fBhost-name\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the name of the client. The name may or may
+not be qualified with the local domain name (it is preferable to use
+the domain-name option to specify the domain name). See RFC 1035 for
+character set restrictions. This option is only honored by
+.B dhclient-script(8)
+if the hostname for the client machine is not set.
+.RE
+.PP
+.B option \fBieee802-3-encapsulation\fR \fIflag\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies whether or not the client should use Ethernet
+Version 2 (RFC 894) or IEEE 802.3 (RFC 1042) encapsulation if the
+interface is an Ethernet. A value of false indicates that the client
+should use RFC 894 encapsulation. A value of true means that the client
+should use RFC 1042 encapsulation.
+.RE
+.PP
+.B option \fBien116-name-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR...
+];
+.RS 0.25i
+.PP
+The ien116-name-servers option specifies a list of IEN 116 name servers
+available to the client. Servers should be listed in order of
+preference.
+.RE
+.PP
+.B option \fBimpress-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR...
+]\fB;\fR
+.RS 0.25i
+.PP
+The impress-server option specifies a list of Imagen Impress servers
+available to the client. Servers should be listed in order of
+preference.
+.RE
+.PP
+.B option \fBinterface-mtu\fR \fIuint16\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the MTU to use on this interface. The minimum
+legal value for the MTU is 68.
+.RE
+.PP
+.B option \fBip-forwarding\fR \fIflag\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies whether the client should configure its IP
+layer for packet forwarding. A value of false means disable IP
+forwarding, and a value of true means enable IP forwarding.
+.RE
+.PP
+.B option \fBirc-server\fR \fIip-address\fR [\fB,\fR
+\fIip-address\fR... ]\fB;\fR
+.RS 0.25i
+.PP
+The IRC server option specifies a list of IRC servers available
+to the client. Servers should be listed in order of preference.
+.RE
+.PP
+.B option \fBlog-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR...
+]\fB;\fR
+.RS 0.25i
+.PP
+The log-server option specifies a list of MIT-LCS UDP log servers
+available to the client. Servers should be listed in order of
+preference.
+.RE
+.PP
+.B option \fBlpr-servers\fR \fIip-address \fR [\fB,\fR \fIip-address\fR...
+]\fB;\fR
+.RS 0.25i
+.PP
+The LPR server option specifies a list of RFC 1179 line printer
+servers available to the client. Servers should be listed in order
+of preference.
+.RE
+.PP
+.B option \fBmask-supplier\fR \fIflag\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies whether or not the client should respond to
+subnet mask requests using ICMP. A value of false indicates that the
+client should not respond. A value of true means that the client should
+respond.
+.RE
+.PP
+.B option \fBmax-dgram-reassembly\fR \fIuint16\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the maximum size datagram that the client
+should be prepared to reassemble. The minimum legal value is
+576.
+.RE
+.PP
+.B option \fBmerit-dump\fR \fItext\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the path-name of a file to which the client's
+core image should be dumped in the event the client crashes. The
+path is formatted as a character string consisting of characters from
+the NVT ASCII character set.
+.RE
+.PP
+.B option \fBmobile-ip-home-agent\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... ]\fB;\fR
+.RS 0.25i
+.PP
+This option specifies a list of IP addresses indicating mobile IP
+home agents available to the client. Agents should be listed in
+order of preference, although normally there will be only one such
+agent.
+.RE
+.PP
+.B option \fBnds-context\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The nds-context option specifies the name of the initial Netware
+Directory Service for an NDS client.
+.RE
+.PP
+.B option \fBnds-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... ]\fB;\fR
+.RS 0.25i
+.PP
+The nds-servers option specifies a list of IP addresses of NDS servers.
+.RE
+.PP
+.B option \fBnds-tree-name\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The nds-tree-name option specifies NDS tree name that the NDS client
+should use.
+.RE
+.PP
+.B option \fBnetbios-dd-server\fR \fIip-address\fR [\fB,\fR \fIip-address\fR...
+]\fB;\fR
+.RS 0.25i
+.PP
+The NetBIOS datagram distribution server (NBDD) option specifies a
+list of RFC 1001/1002 NBDD servers listed in order of preference.
+.RE
+.PP
+.B option \fBnetbios-name-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR...]\fB;\fR
+.RS 0.25i
+.PP
+The NetBIOS name server (NBNS) option specifies a list of RFC
+1001/1002 NBNS name servers listed in order of preference. NetBIOS
+Name Service is currently more commonly referred to as WINS. WINS
+servers can be specified using the netbios-name-servers option.
+.RE
+.PP
+.B option \fBnetbios-node-type\fR \fIuint8\fR\fB;\fR
+.RS 0.25i
+.PP
+The NetBIOS node type option allows NetBIOS over TCP/IP clients which
+are configurable to be configured as described in RFC 1001/1002. The
+value is specified as a single octet which identifies the client type.
+.PP
+Possible node types are:
+.PP
+.TP 5
+.I 1
+B-node: Broadcast - no WINS
+.TP
+.I 2
+P-node: Peer - WINS only
+.TP
+.I 4
+M-node: Mixed - broadcast, then WINS
+.TP
+.I 8
+H-node: Hybrid - WINS, then broadcast
+.RE
+.PP
+.B option \fBnetbios-scope\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The NetBIOS scope option specifies the NetBIOS over TCP/IP scope
+parameter for the client as specified in RFC 1001/1002. See RFC1001,
+RFC1002, and RFC1035 for character-set restrictions.
+.RE
+.PP
+.B option \fBnetinfo-server-address\fR \fIip-address\fR [\fB,\fR
+\fIip-address\fR... ]\fB;\fR
+.RS 0.25i
+.PP
+The \fBnetinfo-server-address\fR option has not been described in any
+RFC, but has been allocated (and is claimed to be in use) by Apple
+Computers. It's hard to say if the above is the correct format, or
+what clients might be expected to do if values were configured. Use
+at your own risk.
+.RE
+.PP
+.B option \fBnetinfo-server-tag\fR \fItext\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBnetinfo-server-tag\fR option has not been described in any
+RFC, but has been allocated (and is claimed to be in use) by Apple
+Computers. It's hard to say if the above is the correct format,
+or what clients might be expected to do if values were configured. Use
+at your own risk.
+.RE
+.PP
+.B option \fBnis-domain\fR \fItext\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the name of the client's NIS (Sun Network
+Information Services) domain. The domain is formatted as a character
+string consisting of characters from the NVT ASCII character set.
+.RE
+.PP
+.B option \fBnis-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR...
+]\fB;\fR
+.RS 0.25i
+.PP
+This option specifies a list of IP addresses indicating NIS servers
+available to the client. Servers should be listed in order of
+preference.
+.RE
+.PP
+.B option \fBnisplus-domain\fR \fItext\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the name of the client's NIS+ domain. The
+domain is formatted as a character string consisting of characters
+from the NVT ASCII character set.
+.RE
+.PP
+.B option \fBnisplus-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR...
+]\fB;\fR
+.RS 0.25i
+.PP
+This option specifies a list of IP addresses indicating NIS+ servers
+available to the client. Servers should be listed in order of
+preference.
+.RE
+.PP
+.B option \fBnntp-server\fR \fIip-address\fR [\fB,\fR
+\fIip-address\fR... ]\fB;\fR
+.RS 0.25i
+.PP
+The NNTP server option specifies a list of NNTP servesr available
+to the client. Servers should be listed in order of preference.
+.RE
+.PP
+.B option \fBnon-local-source-routing\fR \fIflag\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies whether the client should configure its IP
+layer to allow forwarding of datagrams with non-local source routes
+(see Section 3.3.5 of [4] for a discussion of this topic). A value
+of false means disallow forwarding of such datagrams, and a value of true
+means allow forwarding.
+.RE
+.PP
+.B option \fBntp-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR...
+]\fB;\fR
+.RS 0.25i
+.PP
+This option specifies a list of IP addresses indicating NTP (RFC 5905)
+servers available to the client. Servers should be listed in order
+of preference.
+.RE
+.PP
+.B option \fBnwip-domain\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The name of the NetWare/IP domain that a NetWare/IP client should
+use.
+.RE
+.PP
+.B option \fBnwip-suboptions\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+A sequence of suboptions for NetWare/IP clients - see RFC2242 for
+details. Normally this option is set by specifying specific
+NetWare/IP suboptions - see the NETWARE/IP SUBOPTIONS section for more
+information.
+.RE
+.PP
+.B option \fBpath-mtu-aging-timeout\fR \fIuint32\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the timeout (in seconds) to use when aging Path
+MTU values discovered by the mechanism defined in RFC 1191.
+.RE
+.PP
+.B option \fBpath-mtu-plateau-table\fR \fIuint16\fR [\fB,\fR \fIuint16\fR...
+]\fB;\fR
+.RS 0.25i
+.PP
+This option specifies a table of MTU sizes to use when performing
+Path MTU Discovery as defined in RFC 1191. The table is formatted as
+a list of 16-bit unsigned integers, ordered from smallest to largest.
+The minimum MTU value cannot be smaller than 68.
+.RE
+.PP
+.B option \fBperform-mask-discovery\fR \fIflag\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies whether or not the client should perform subnet
+mask discovery using ICMP. A value of false indicates that the client
+should not perform mask discovery. A value of true means that the
+client should perform mask discovery.
+.RE
+.PP
+.nf
+.B option \fBpolicy-filter\fR \fIip-address ip-address\fR
+ [\fB,\fR \fIip-address ip-address\fR...]\fB;\fR
+.RE
+.fi
+.RS 0.25i
+.PP
+This option specifies policy filters for non-local source routing.
+The filters consist of a list of IP addresses and masks which specify
+destination/mask pairs with which to filter incoming source routes.
+.PP
+Any source routed datagram whose next-hop address does not match one
+of the filters should be discarded by the client.
+.PP
+See STD 3 (RFC1122) for further information.
+.RE
+.PP
+.B option \fBpop-server\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... ]\fB;\fR
+.RS 0.25i
+.PP
+The POP3 server option specifies a list of POP3 servers available
+to the client. Servers should be listed in order of preference.
+.RE
+.PP
+.B option \fBresource-location-servers\fR \fIip-address\fR
+ [\fB, \fR\fIip-address\fR...]\fB;\fR
+.fi
+.RS 0.25i
+.PP
+This option specifies a list of RFC 887 Resource Location
+servers available to the client. Servers should be listed in order
+of preference.
+.RE
+.PP
+.B option \fBroot-path\fR \fItext\fB;\fR\fR
+.RS 0.25i
+.PP
+This option specifies the path-name that contains the client's root
+disk. The path is formatted as a character string consisting of
+characters from the NVT ASCII character set.
+.RE
+.PP
+.B option \fBrouter-discovery\fR \fIflag\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies whether or not the client should solicit
+routers using the Router Discovery mechanism defined in RFC 1256.
+A value of false indicates that the client should not perform
+router discovery. A value of true means that the client should perform
+router discovery.
+.RE
+.PP
+.B option \fBrouter-solicitation-address\fR \fIip-address\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the address to which the client should transmit
+router solicitation requests.
+.RE
+.PP
+.B option routers \fIip-address\fR [\fB,\fR \fIip-address\fR...
+]\fB;\fR
+.RS 0.25i
+.PP
+The routers option specifies a list of IP addresses for routers on the
+client's subnet. Routers should be listed in order of preference.
+.RE
+.PP
+.B option slp-directory-agent \fIboolean ip-address
+[\fB,\fR \fIip-address\fR... ]\fB;\fR
+.RS 0.25i
+.PP
+This option specifies two things: the IP addresses of one or more
+Service Location Protocol Directory Agents, and whether the use of
+these addresses is mandatory. If the initial boolean value is true,
+the SLP agent should just use the IP addresses given. If the value
+is false, the SLP agent may additionally do active or passive
+multicast discovery of SLP agents (see RFC2165 for details).
+.PP
+Please note that in this option and the slp-service-scope option, the
+term "SLP Agent" is being used to refer to a Service Location Protocol
+agent running on a machine that is being configured using the DHCP
+protocol.
+.PP
+Also, please be aware that some companies may refer to SLP as NDS.
+If you have an NDS directory agent whose address you need to
+configure, the slp-directory-agent option should work.
+.RE
+.PP
+.B option slp-service-scope \fIboolean text\fR\fB;\fR
+.RS 0.25i
+.PP
+The Service Location Protocol Service Scope Option specifies two
+things: a list of service scopes for SLP, and whether the use of this
+list is mandatory. If the initial boolean value is true, the SLP
+agent should only use the list of scopes provided in this option;
+otherwise, it may use its own static configuration in preference to
+the list provided in this option.
+.PP
+The text string should be a comma-separated list of scopes that the
+SLP agent should use. It may be omitted, in which case the SLP Agent
+will use the aggregated list of scopes of all directory agents known
+to the SLP agent.
+.RE
+.PP
+.B option \fBsmtp-server\fR \fIip-address\fR [\fB,\fR
+\fIip-address\fR... ]\fB;\fR
+.RS 0.25i
+.PP
+The SMTP server option specifies a list of SMTP servers available to
+the client. Servers should be listed in order of preference.
+.RE
+.PP
+.nf
+.B option \fBstatic-routes\fR \fIip-address ip-address\fR
+ [\fB,\fR \fIip-address ip-address\fR...]\fB;\fR
+.fi
+.RS 0.25i
+.PP
+This option specifies a list of static routes that the client should
+install in its routing cache. If multiple routes to the same
+destination are specified, they are listed in descending order of
+priority.
+.PP
+The routes consist of a list of IP address pairs. The first address
+is the destination address, and the second address is the router for
+the destination.
+.PP
+The default route (0.0.0.0) is an illegal destination for a static
+route. To specify the default route, use the
+.B routers
+option. Also, please note that this option is not intended for
+classless IP routing - it does not include a subnet mask. Since
+classless IP routing is now the most widely deployed routing standard,
+this option is virtually useless, and is not implemented by any of the
+popular DHCP clients, for example the Microsoft DHCP client.
+.RE
+.PP
+.nf
+.B option \fBstreettalk-directory-assistance-server\fR \fIip-address\fR
+ [\fB,\fR \fIip-address\fR...]\fB;\fR
+.fi
+.RS 0.25i
+.PP
+The StreetTalk Directory Assistance (STDA) server option specifies a
+list of STDA servers available to the client. Servers should be
+listed in order of preference.
+.RE
+.PP
+.B option \fBstreettalk-server\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... ]\fB;\fR
+.RS 0.25i
+.PP
+The StreetTalk server option specifies a list of StreetTalk servers
+available to the client. Servers should be listed in order of
+preference.
+.RE
+.PP
+.B option subnet-mask \fIip-address\fR\fB;\fR
+.RS 0.25i
+.PP
+The subnet mask option specifies the client's subnet mask as per RFC
+950. If no subnet mask option is provided anywhere in scope, as a
+last resort dhcpd will use the subnet mask from the subnet declaration
+for the network on which an address is being assigned. However,
+.I any
+subnet-mask option declaration that is in scope for the address being
+assigned will override the subnet mask specified in the subnet
+declaration.
+.RE
+.PP
+.B option \fBsubnet-selection\fR \fIip-address\fR\fB;\fR
+.RS 0.25i
+.PP
+Sent by the client if an address is required in a subnet other than the one
+that would normally be selected (based on the relaying address of the
+connected subnet the request is obtained from). See RFC3011. Note that the
+option number used by this server is 118; this has not always been the
+defined number, and some clients may use a different value. Use of this
+option should be regarded as slightly experimental!
+.RE
+.PP
+This option is not user configurable in the server.
+.PP
+.PP
+.B option \fBswap-server\fR \fIip-address\fR\fB;\fR
+.RS 0.25i
+.PP
+This specifies the IP address of the client's swap server.
+.RE
+.PP
+.B option \fBtcp-keepalive-garbage\fR \fIflag\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies whether or not the client should send TCP
+keepalive messages with an octet of garbage for compatibility with
+older implementations. A value of false indicates that a garbage octet
+should not be sent. A value of true indicates that a garbage octet
+should be sent.
+.RE
+.PP
+.B option \fBtcp-keepalive-interval\fR \fIuint32\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the interval (in seconds) that the client TCP
+should wait before sending a keepalive message on a TCP connection.
+The time is specified as a 32-bit unsigned integer. A value of zero
+indicates that the client should not generate keepalive messages on
+connections unless specifically requested by an application.
+.RE
+.PP
+.B option \fBtftp-server-name\fR \fItext\fR\fB;\fR
+.RS 0.25i
+.PP
+This option is used to identify a TFTP server and, if supported by the
+client, should have the same effect as the \fBserver-name\fR
+declaration. BOOTP clients are unlikely to support this option.
+Some DHCP clients will support it, and others actually require it.
+.RE
+.PP
+.B option time-offset \fIint32\fR\fB;\fR
+.RS 0.25i
+.PP
+The time-offset option specifies the offset of the client's subnet in
+seconds from Coordinated Universal Time (UTC).
+.RE
+.PP
+.B option time-servers \fIip-address\fR [, \fIip-address\fR...
+]\fB;\fR
+.RS 0.25i
+.PP
+The time-server option specifies a list of RFC 868 time servers
+available to the client. Servers should be listed in order of
+preference.
+.RE
+.PP
+.B option \fBtrailer-encapsulation\fR \fIflag\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies whether or not the client should negotiate the
+use of trailers (RFC 893 [14]) when using the ARP protocol. A value
+of false indicates that the client should not attempt to use trailers. A
+value of true means that the client should attempt to use trailers.
+.RE
+.PP
+.B option \fBuap-servers\fR \fItext\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies a list of URLs, each pointing to a user
+authentication service that is capable of processing authentication
+requests encapsulated in the User Authentication Protocol (UAP). UAP
+servers can accept either HTTP 1.1 or SSLv3 connections. If the list
+includes a URL that does not contain a port component, the normal
+default port is assumed (i.e., port 80 for http and port 443 for
+https). If the list includes a URL that does not contain a path
+component, the path /uap is assumed. If more than one URL is
+specified in this list, the URLs are separated by spaces.
+.RE
+.PP
+.B option \fBuser-class\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+This option is used by some DHCP clients as a way for users to
+specify identifying information to the client. This can be used in a
+similar way to the vendor-class-identifier option, but the value of
+the option is specified by the user, not the vendor. Most recent
+DHCP clients have a way in the user interface to specify the value for
+this identifier, usually as a text string.
+.RE
+.PP
+.B option \fBvendor-class-identifier\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+This option is used by some DHCP clients to identify the vendor
+type and possibly the configuration of a DHCP client. The information
+is a string of bytes whose contents are specific to the vendor and are
+not specified in a standard. To see what vendor class identifier
+clients are sending, you can write the following in your DHCP server
+configuration file:
+.nf
+.PP
+set vendor-string = option vendor-class-identifier;
+.fi
+.PP
+This will result in all entries in the DHCP server lease database file
+for clients that sent vendor-class-identifier options having a set
+statement that looks something like this:
+.nf
+.PP
+set vendor-string = "SUNW.Ultra-5_10";
+.fi
+.PP
+The vendor-class-identifier option is normally used by the DHCP server
+to determine the options that are returned in the
+.B vendor-encapsulated-options
+option. Please see the VENDOR ENCAPSULATED OPTIONS section later in this
+manual page for further information.
+.RE
+.PP
+.B option \fBvendor-encapsulated-options\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBvendor-encapsulated-options\fR option can contain either a
+single vendor-specific value or one or more vendor-specific
+suboptions. This option is not normally specified in the DHCP server
+configuration file - instead, a vendor class is defined for each
+vendor, vendor class suboptions are defined, values for those
+suboptions are defined, and the DHCP server makes up a response on
+that basis.
+.PP
+Some default behaviours for well-known DHCP client vendors (currently,
+the Microsoft Windows 2000 DHCP client) are configured automatically,
+but otherwise this must be configured manually - see the VENDOR
+ENCAPSULATED OPTIONS section later in this manual page for details.
+.RE
+.PP
+.B option \fBvivso\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBvivso\fR option can contain multiple separate options, one for
+each 32-bit Enterprise ID. Each Enterprise-ID discriminated option then
+contains additional options whose format is defined by the vendor who
+holds that ID. This option is usually not configured manually, but
+rather is configured via intervening option definitions. Please also
+see the VENDOR ENCAPSULATED OPTIONS section later in this manual page
+for details.
+.RE
+.PP
+.B option \fBwww-server\fR \fIip-address\fR [\fB,\fR
+\fIip-address\fR... ]\fB;\fR
+.RS 0.25i
+.PP
+The WWW server option specifies a list of WWW servers available
+to the client. Servers should be listed in order of preference.
+.RE
+.PP
+.B option \fBx-display-manager\fR \fIip-address\fR [\fB,\fR \fIip-address\fR...
+]\fB;\fR
+.RS 0.25i
+.PP
+This option specifies a list of systems that are running the X Window
+System Display Manager and are available to the client. Addresses
+should be listed in order of preference.
+.RE
+.SH RELAY AGENT INFORMATION OPTION
+An IETF draft, draft-ietf-dhc-agent-options-11.txt, defines a series
+of encapsulated options that a relay agent can add to a DHCP packet
+when relaying it to the DHCP server. The server can then make
+address allocation decisions (or whatever other decisions it wants)
+based on these options. The server also returns these options in any
+replies it sends through the relay agent, so that the relay agent can
+use the information in these options for delivery or accounting
+purposes.
+.PP
+The current draft defines two options. To reference
+these options in the dhcp server, specify the option space name,
+"agent", followed by a period, followed by the option name. It is
+not normally useful to define values for these options in the server,
+although it is permissible. These options are not supported in the
+client.
+.PP
+.B option \fBagent.circuit-id\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The circuit-id suboption encodes an agent-local identifier of the
+circuit from which a DHCP client-to-server packet was received. It is
+intended for use by agents in relaying DHCP responses back to the
+proper circuit. The format of this option is currently defined to be
+vendor-dependent, and will probably remain that way, although the
+current draft allows for the possibility of standardizing the
+format in the future.
+.RE
+.PP
+.B option \fBagent.remote-id\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The remote-id suboption encodes information about the remote host end
+of a circuit. Examples of what it might contain include caller ID
+information, username information, remote ATM address, cable modem ID,
+and similar things. In principal, the meaning is not well-specified,
+and it should generally be assumed to be an opaque object that is
+administratively guaranteed to be unique to a particular remote end of
+a circuit.
+.RE
+.PP
+.B option \fBagent.DOCSIS-device-class\fR \fIuint32\fR\fB;\fR
+.RS 0.25i
+.PP
+The DOCSIS-device-class suboption is intended to convey information about
+the host endpoint, hardware, and software, that either the host operating
+system or the DHCP server may not otherwise be aware of (but the relay is
+able to distinguish). This is implemented as a 32-bit field (4 octets),
+each bit representing a flag describing the host in one of these ways.
+So far, only bit zero (being the least significant bit) is defined in
+RFC3256. If this bit is set to one, the host is considered a CPE
+Controlled Cable Modem (CCCM). All other bits are reserved.
+.RE
+.PP
+.B option \fBagent.link-selection\fR \fIip-address\fR\fB;\fR
+.RS 0.25i
+.PP
+The link-selection suboption is provided by relay agents to inform servers
+what subnet the client is actually attached to. This is useful in those
+cases where the giaddr (where responses must be sent to the relay agent)
+is not on the same subnet as the client. When this option is present in
+a packet from a relay agent, the DHCP server will use its contents to find
+a subnet declared in configuration, and from here take one step further
+backwards to any shared-network the subnet may be defined within; the
+client may be given any address within that shared network, as normally
+appropriate.
+.RE
+.SH THE CLIENT FQDN SUBOPTIONS
+The Client FQDN option, currently defined in the Internet Draft
+draft-ietf-dhc-fqdn-option-00.txt is not a standard yet, but is in
+sufficiently wide use already that we have implemented it. Due to
+the complexity of the option format, we have implemented it as a
+suboption space rather than a single option. In general this
+option should not be configured by the user - instead it should be
+used as part of an automatic DNS update system.
+.PP
+.B option fqdn.no-client-update \fIflag\fB;
+.RS 0.25i
+.PP
+When the client sends this, if it is true, it means the client will not
+attempt to update its A record. When sent by the server to the client,
+it means that the client \fIshould not\fR update its own A record.
+.RE
+.PP
+.B option fqdn.server-update \fIflag\fB;
+.RS 0.25i
+.PP
+When the client sends this to the server, it is requesting that the server
+update its A record. When sent by the server, it means that the server
+has updated (or is about to update) the client's A record.
+.RE
+.PP
+.B option fqdn.encoded \fIflag\fB;
+.RS 0.25i
+.PP
+If true, this indicates that the domain name included in the option is
+encoded in DNS wire format, rather than as plain ASCII text. The client
+normally sets this to false if it doesn't support DNS wire format in the
+FQDN option. The server should always send back the same value that the
+client sent. When this value is set on the configuration side, it controls
+the format in which the \fIfqdn.fqdn\fR suboption is encoded.
+.RE
+.PP
+.B option fqdn.rcode1 \fIflag\fB;
+.PP
+.B option fqdn.rcode2 \fIflag\fB;
+.RS 0.25i
+.PP
+These options specify the result of the updates of the A and PTR records,
+respectively, and are only sent by the DHCP server to the DHCP client.
+The values of these fields are those defined in the DNS protocol specification.
+.RE
+.PP
+.B option fqdn.fqdn \fItext\fB;
+.RS 0.25i
+.PP
+Specifies the domain name that the client wishes to use. This can be a
+fully-qualified domain name, or a single label. If there is no trailing
+\'.\' character in the name, it is not fully-qualified, and the server will
+generally update that name in some locally-defined domain.
+.RE
+.PP
+.B option fqdn.hostname \fI--never set--\fB;
+.RS 0.25i
+.PP
+This option should never be set, but it can be read back using the \fBoption\fR
+and \fBconfig-option\fR operators in an expression, in which case it returns
+the first label in the \fBfqdn.fqdn\fR suboption - for example, if
+the value of \fBfqdn.fqdn\fR is "foo.example.com.", then \fBfqdn.hostname\fR
+will be "foo".
+.RE
+.PP
+.B option fqdn.domainname \fI--never set--\fB;
+.RS 0.25i
+.PP
+This option should never be set, but it can be read back using the \fBoption\fR
+and \fBconfig-option\fR operators in an expression, in which case it returns
+all labels after the first label in the \fBfqdn.fqdn\fR suboption - for
+example, if the value of \fBfqdn.fqdn\fR is "foo.example.com.",
+then \fBfqdn.domainname\fR will be "example.com.". If this suboption value
+is not set, it means that an unqualified name was sent in the \fBfqdn\fR option,
+or that no \fBfqdn\fR option was sent at all.
+.RE
+.PP
+If you wish to use any of these suboptions, we strongly recommend that you
+refer to the Client FQDN option draft (or standard, when it becomes a
+standard) - the documentation here is sketchy and incomplete in comparison,
+and is just intended for reference by people who already understand the
+Client FQDN option specification.
+.SH THE NETWARE/IP SUBOPTIONS
+RFC2242 defines a set of encapsulated options for Novell NetWare/IP
+clients. To use these options in the dhcp server, specify the option
+space name, "nwip", followed by a period, followed by the option name.
+The following options can be specified:
+.PP
+.B option \fBnwip.nsq-broadcast\fR \fIflag\fR\fB;\fR
+.RS 0.25i
+.PP
+If true, the client should use the NetWare Nearest Server Query to
+locate a NetWare/IP server. The behaviour of the Novell client if
+this suboption is false, or is not present, is not specified.
+.PP
+.RE
+.B option \fBnwip.preferred-dss\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... ]\fR\fB;\fR
+.RS 0.25i
+.PP
+This suboption specifies a list of up to five IP addresses, each of
+which should be the IP address of a NetWare Domain SAP/RIP server
+(DSS).
+.RE
+.PP
+.B option \fBnwip.nearest-nwip-server\fR \fI\fIip-address\fR
+ [\fB,\fR \fIip-address\fR...]\fR\fB;\fR
+.RS 0.25i
+.PP
+This suboption specifies a list of up to five IP addresses, each of
+which should be the IP address of a Nearest NetWare IP server.
+.RE
+.PP
+.B option \fBnwip.autoretries\fR \fIuint8\fR\fB;\fR
+.RS 0.25i
+.PP
+Specifies the number of times that a NetWare/IP client should attempt
+to communicate with a given DSS server at startup.
+.RE
+.PP
+.B option \fBnwip.autoretry-secs\fR \fIuint8\fR\fB;\fR
+.RS 0.25i
+.PP
+Specifies the number of seconds that a Netware/IP client should wait
+between retries when attempting to establish communications with a DSS
+server at startup.
+.RE
+.PP
+.B option \fBnwip.nwip-1-1\fR \fIuint8\fR\fB;\fR
+.RS 0.25i
+.PP
+If true, the NetWare/IP client should support NetWare/IP version 1.1
+compatibility. This is only needed if the client will be contacting
+Netware/IP version 1.1 servers.
+.RE
+.PP
+.B option \fBnwip.primary-dss\fR \fIip-address\fR\fB;\fR
+.RS 0.25i
+.PP
+Specifies the IP address of the Primary Domain SAP/RIP Service server
+(DSS) for this NetWare/IP domain. The NetWare/IP administration
+utility uses this value as Primary DSS server when configuring a
+secondary DSS server.
+.RE
+.SH STANDARD DHCPV6 OPTIONS
+DHCPv6 options differ from DHCPv4 options partially due to using
+16-bit code and length tags, but semantically zero-length options
+are legal in DHCPv6, and multiple options are treated differently.
+Whereas in DHCPv4 multiple options would be concatenated to form one
+option, in DHCPv6 they are expected to be individual instantiations.
+Understandably, many options are not "allowed" to have multiple
+instances in a packet - normally these are options which are digested
+by the DHCP protocol software, and not by users or applications.
+.PP
+.B option \fBdhcp6.client-id\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the client's DUID identifier. DUIDs are similar
+but different from DHCPv4 client identifiers - there are documented duid
+types:
+.PP
+.I duid-llt
+.PP
+.I duid-en
+.PP
+.I duid-ll
+.PP
+This value should not be configured, but rather is provided by clients
+and treated as an opaque identifier key blob by servers.
+.RE
+.PP
+.B option \fBdhcp6.server-id\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+This option specifies the server's DUID identifier. One may use this
+option to configure an opaque binary blob for your server's identifier.
+.RE
+.PP
+.B option \fBdhcp6.ia-na\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The Identity Association for Non-temporary Addresses (ia-na) carries
+assigned addresses that are not temporary addresses for use by the
+DHCPv6 client. This option is produced by the DHCPv6 server software,
+and should not be configured.
+.RE
+.PP
+.B option \fBdhcp6.ia-ta\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The Identity Association for Temporary Addresses (ia-ta) carries
+temporary addresses, which may change upon every renewal. There is
+no support for this in the current DHCPv6 software.
+.RE
+.PP
+.B option \fBdhcp6.ia-addr\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The Identity Association Address option is encapsulated inside ia-na
+or ia-ta options in order to represent addresses associated with those
+IA's. These options are manufactured by the software, so should not
+be configured.
+.RE
+.PP
+.B option \fBdhcp6.oro\fR \fIuint16\fR [ \fB,\fR \fIuint16\fR\fB,\fR ... ]\fB;\fR
+.RS 0.25i
+.PP
+The Option Request Option ("ORO") is the DHCPv6 equivalent of the
+parameter-request-list. Clients supply this option to ask servers
+to reply with options relevant to their needs and use. This option
+must not be directly configured, the request syntax in dhclient.conf (5)
+should be used instead.
+.RE
+.PP
+.B option \fBdhcp6.preference\fR \fIuint8\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBpreference\fR option informs a DHCPv6 client which server is
+\'preferred\' for use on a given subnet. This preference is only
+applied during the initial stages of configuration - once a client
+is bound to an IA, it will remain bound to that IA until it is no
+longer valid or has expired. This value may be configured on the
+server, and is digested by the client software.
+.RE
+.PP
+.B option \fBdhcp6.elapsed-time\fR \fIuint16\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBelapsed-time\fR option is constructed by the DHCPv6 client
+software, and is potentially consumed by intermediaries. This
+option should not be configured.
+.RE
+.PP
+.B option \fBdhcp6.relay-msg\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBrelay-msg\fR option is constructed by intervening DHCPv6
+relay agent software. This option is entirely used by protocol
+software, and is not meant for user configuration.
+.RE
+.PP
+.B option \fBdhcp6.unicast\fR \fIip6-address\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBunicast\fR option is provided by DHCPv6 servers which are
+willing (or prefer) to receive Renew packets from their clients
+by exchanging UDP unicasts with them. Normally, DHCPv6 clients
+will multicast their Renew messages. This may be configured on
+the server, and should be configured as an address the server
+is ready to reply to.
+.RE
+.PP
+.B option \fBdhcp6.status-code\fR \fIstatus-code\fR [ \fIstring\fR ] \fB;\fR
+.RS 0.25i
+.PP
+The \fBstatus-code\fR option is provided by DHCPv6 servers to inform
+clients of error conditions during protocol communication. This option
+is manufactured and digested by protocol software, and should not be
+configured.
+.RE
+.PP
+.B option \fBdhcp6.rapid-commit\fR \fB;\fR
+.RS 0.25i
+.PP
+The \fBrapid-commit\fR option is a zero-length option that clients use
+to indicate their desire to enter into rapid-commit with the server.
+.RE
+.PP
+.B option \fBdhcp6.vendor-opts\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBvendor-opts\fR option is actually an encapsulated sub-option space,
+in which each Vendor-specific Information Option (VSIO) is identified by
+a 32-bit Enterprise-ID number. The encapsulated option spaces within these
+options are defined by the vendors.
+.PP
+To make use of this option, the best way is to examine the section
+titled VENDOR ENCAPSULATED OPTIONS below, in particular the bits about
+the "vsio" option space.
+.RE
+.PP
+.B option \fBdhcp6.interface-id\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBinterface-id\fR option is manufactured by relay agents, and may
+be used to guide configuration differentiating clients by the interface
+they are remotely attached to. It does not make sense to configure a
+value for this option, but it may make sense to inspect its contents.
+.RE
+.PP
+.B option \fBdhcp6.reconf-msg\fR \fIdhcpv6-message\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBreconf-msg\fR option is manufactured by servers, and sent to
+clients in Reconfigure messages to inform them of what message
+the client should Reconfigure using. There is no support for
+DHCPv6 Reconfigure extensions, and this option is documented
+informationally only.
+.RE
+.PP
+.B option \fBdhcp6.reconf-accept ;\fR
+.RS 0.25i
+.PP
+The \fBreconf-accept\fR option is included by DHCPv6 clients that
+support the Reconfigure extentions, advertising that they will
+respond if the server were to ask them to Reconfigure. There is
+no support for DHCPv6 Reconfigure extensions, and this option is
+documented informationally only.
+.RE
+.PP
+.B option \fBdhcp6.sip-servers-names\fR \fIdomain-list\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBsip-servers-names\fR option allows SIP clients to locate a
+local SIP server that is to be used for all outbound SIP requests, a
+so-called"outbound proxy server." If you wish to use manually entered
+IPv6 addresses instead, please see the \fBsip-servers-addresses\fR option
+below.
+.RE
+.PP
+.B option
+.B dhcp6.sip-servers-addresses
+.I ip6-address \fR[\fB,\fR
+.I ip6-address \fR... ]
+.B ;
+.RS 0.25i
+.PP
+The \fBsip-servers-addresses\fR option allows SIP clients to locate
+a local SIP server that is to be used for all outbound SIP requests,
+a so-called "outbound proxy servers." If you wish to use domain names
+rather than IPv6 addresses, please see the \fBsip-servers-names\fR option
+above.
+.RE
+.PP
+.B option
+.B dhcp6.name-servers
+.I ip6-address \fR[\fB,\fR
+.I ip6-address \fR... ]
+.B ;
+.RS 0.25i
+.PP
+The \fBname-servers\fR option instructs clients about locally available
+recursive DNS servers. It is easiest to describe this as the "nameserver"
+line in /etc/resolv.conf.
+.RE
+.PP
+.B option \fBdhcp6.domain-search\fR \fIdomain-list\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBdomain-search\fR option specifies the client's domain search path
+to be applied to recursive DNS queries. It is easiest to describe this as
+the "search" line in /etc/resolv.conf.
+.RE
+.PP
+.B option \fBdhcp6.ia-pd\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBia-pd\fR option is manufactured by clients and servers to create a
+Prefix Delegation binding - to delegate an IPv6 prefix to the client. It is
+not directly edited in dhcpd.conf(5) or dhclient.conf(5), but rather is
+manufactured and consumed by the software.
+.RE
+.PP
+.B option \fBdhcp6.ia-prefix\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBia-prefix\fR option is placed inside \fBia-pd\fR options in order
+to identify the prefix(es) allocated to the client. It is not directly
+edited in dhcpd.conf(5) or dhclient.conf(5), but rather is
+manufactured and consumed by the software.
+.RE
+.PP
+.B option
+.B dhcp6.nis-servers
+.I ip6-address \fR[\fB,
+.I ip6-address \fR... ]
+.B ;
+.RS 0.25i
+.PP
+The \fBnis-servers\fR option identifies, in order, NIS servers available
+to the client.
+.RE
+.PP
+.B option
+.B dhcp6.nisp-servers
+.I ip6-address \fR[\fB,
+.I ip6-address \fR... ]
+.B ;
+.RS 0.25i
+.PP
+The \fBnisp-servers\fR option identifies, in order, NIS+ servers available
+to the client.
+.RE
+.PP
+.B option \fBnis-domain-name\fR \fIdomain-list\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBnis-domain-name\fR option specifies the NIS domain name the client is
+expected to use, and is related to the \fBnis-servers\fR option.
+.RE
+.PP
+.B option \fBdhcp6.nis-domain-name\fR \fIdomain-name\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBdhcp6.nis-domain-name\fR option specifies NIS domain name the
+client is expected to use, and is related to \fBdhcp6.nis-servers\fR option.
+.RE
+.PP
+.B option \fBnisp-domain-name\fR \fIdomain-list\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBnisp-domain-name\fR option specifies the NIS+ domain name the client
+is expected to use, and is related to the \fBnisp-servers\fR option.
+.RE
+.PP
+.B option \fBdhcp6.nisp-domain-name\fR \fIdomain-name\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBdhcp6.nis-domain-name\fR option specifies NIS+ domain name the
+client is expected to use, and is related to \fBdhcp6.nisp-servers\fR option.
+.RE
+.PP
+.B option
+.B dhcp6.sntp-servers
+.I ip6-address \fR[\fB,
+.I ip6-address \fR... ]
+.B ;
+.RS 0.25i
+.PP
+The \fBsntp-servers\fR option specifies a list of local SNTP servers
+available for the client to synchronize their clocks.
+.RE
+.PP
+.B option \fBdhcp6.info-refresh-time\fR \fIuint32\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBinfo-refresh-time\fR option gives DHCPv6 clients using
+Information-request messages a hint as to how long they should between
+refreshing the information they were given. Note that this option will
+only be delivered to the client, and be likely to affect the client's
+behaviour, if the client requested the option.
+.RE
+.PP
+.B option \fBdhcp6.bcms-server-d\fR \fIdomain-list\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBbcms-server-d\fR option contains the domain names of local BCMS
+(Broadcast and Multicast Control Services) controllers which the client
+may use.
+.RE
+.PP
+.B option
+.B dhcp6.bcms-server-a
+.I ip6-address \fR[\fB,
+.I ip6-address \fR... ]
+.B ;
+.RS 0.25i
+.PP
+The \fBbcms-server-a\fR option contains the IPv6 addresses of local BCMS
+(Broadcast and Multicast Control Services) controllers which the client
+may use.
+.RE
+.PP
+.B option \fBdhcp6.remote-id\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBremote-id\fR option is constructed by relay agents, to inform the
+server of details pertaining to what the relay knows about the client (such
+as what port it is attached to, and so forth). The contents of this option
+have some vendor-specific structure (similar to VSIO), but we have chosen
+to treat this option as an opaque field.
+.RE
+.PP
+.B option \fBdhcp6.subscriber-id\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBsubscriber-id\fR option is an opaque field provided by the relay agent,
+which provides additional information about the subscriber in question. The
+exact contents of this option depend upon the vendor and/or the operator's
+configuration of the remote device, and as such is an opaque field.
+.RE
+.PP
+.B option \fBdhcp6.fqdn\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBfqdn\fR option is normally constructed by the client or server,
+and negotiates the client's Fully Qualified Domain Name, as well as which
+party is responsible for Dynamic DNS Updates. See the section on the
+Client FQDN SubOptions for full details (the DHCPv4 and DHCPv6 FQDN options
+use the same "fqdn." encapsulated space, so are in all ways identical).
+.RE
+.PP
+.B option \fBdhcp6.lq-query\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBlq-query\fR option is used internally by for lease query.
+.RE
+.PP
+.B option \fBdhcp6.client-data\fR \fIstring\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBclient-data\fR option is used internally by for lease query.
+.RE
+.PP
+.B option \fBdhcp6.clt-time\fR \fIuint32\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBclt-time\fR option is used internally by for lease query.
+.RE
+.PP
+.B option \fBdhcp6.lq-relay-data\fR \fIip6-address string\fR\fB;\fR
+.RS 0.25i
+.PP
+The \fBlq-relay-data\fR option is used internally by for lease query.
+.RE
+.PP
+.B option
+.B dhcp6.lq-client-link
+.I ip6-address \fR[\fB,\fR
+.I ip6-address \fR... ]
+.B ;
+.RS 0.25i
+.PP
+The \fBlq-client-link\fR option is used internally by for lease query.
+.RE
+.PP
+.RE
+.SH DEFINING NEW OPTIONS
+The Internet Systems Consortium DHCP client and server provide the
+capability to define new options. Each DHCP option has a name, a
+code, and a structure. The name is used by you to refer to the
+option. The code is a number, used by the DHCP server and client to
+refer to an option. The structure describes what the contents of an
+option looks like.
+.PP
+To define a new option, you need to choose a name for it that is not
+in use for some other option - for example, you can't use "host-name"
+because the DHCP protocol already defines a host-name option, which is
+documented earlier in this manual page. If an option name doesn't
+appear in this manual page, you can use it, but it's probably a good
+idea to put some kind of unique string at the beginning so you can be
+sure that future options don't take your name. For example, you
+might define an option, "local-host-name", feeling some confidence
+that no official DHCP option name will ever start with "local".
+.PP
+Once you have chosen a name, you must choose a code. All codes between
+224 and 254 are reserved as \'site-local\' DHCP options, so you can pick
+any one of these for your site (not for your product/application). In
+RFC3942, site-local space was moved from starting at 128 to starting at
+224. In practice, some vendors have interpreted the protocol rather
+loosely and have used option code values greater than 128 themselves.
+There's no real way to avoid this problem, and it was thought to be
+unlikely to cause too much trouble in practice. If you come across
+a vendor-documented option code in either the new or old site-local
+spaces, please contact your vendor and inform them about rfc3942.
+.PP
+The structure of an option is simply the format in which the option
+data appears. The ISC DHCP server currently supports a few simple
+types, like integers, booleans, strings and IP addresses, and it also
+supports the ability to define arrays of single types or arrays of
+fixed sequences of types.
+.PP
+New options are declared as follows:
+.PP
+.B option
+.I new-name
+.B code
+.I new-code
+.B =
+.I definition
+.B ;
+.PP
+The values of
+.I new-name
+and
+.I new-code
+should be the name you have chosen for the new option and the code you
+have chosen. The
+.I definition
+should be the definition of the structure of the option.
+.PP
+The following simple option type definitions are supported:
+.PP
+.B BOOLEAN
+.PP
+.B option
+.I new-name
+.B code
+.I new-code
+.B =
+.B boolean
+.B ;
+.PP
+An option of type boolean is a flag with a value of either on or off
+(or true or false). So an example use of the boolean type would be:
+.nf
+
+option use-zephyr code 180 = boolean;
+option use-zephyr on;
+
+.fi
+.B INTEGER
+.PP
+.B option
+.I new-name
+.B code
+.I new-code
+.B =
+.I sign
+.B integer
+.I width
+.B ;
+.PP
+The \fIsign\fR token should either be blank, \fIunsigned\fR
+or \fIsigned\fR. The width can be either 8, 16 or 32, and refers to
+the number of bits in the integer. So for example, the following two
+lines show a definition of the sql-connection-max option and its use:
+.nf
+
+option sql-connection-max code 192 = unsigned integer 16;
+option sql-connection-max 1536;
+
+.fi
+.B IP-ADDRESS
+.PP
+.B option
+.I new-name
+.B code
+.I new-code
+.B =
+.B ip-address
+.B ;
+.PP
+An option whose structure is an IP address can be expressed either as
+a domain name or as a dotted quad. So the following is an example use
+of the ip-address type:
+.nf
+
+option sql-server-address code 193 = ip-address;
+option sql-server-address sql.example.com;
+
+.fi
+.B IP6-ADDRESS
+.PP
+.B option
+.I new-name
+.B code
+.I new-code
+.B =
+.B ip6-address
+.B ;
+.PP
+An option whose structure is an IPv6 address must be expressed as
+a valid IPv6 address. The following is an example use of the
+ip6-address type:
+.nf
+
+option dhcp6.some-server code 1234 = array of ip6-address;
+option dhcp6.some-server 3ffe:bbbb:aaaa:aaaa::1, 3ffe:bbbb:aaaa:aaaa::2;
+
+.fi
+.PP
+.B TEXT
+.PP
+.B option
+.I new-name
+.B code
+.I new-code
+.B =
+.B text
+.B ;
+.PP
+An option whose type is text will encode an ASCII text string. For
+example:
+.nf
+
+option sql-default-connection-name code 194 = text;
+option sql-default-connection-name "PRODZA";
+
+.fi
+.PP
+.B DATA STRING
+.PP
+.B option
+.I new-name
+.B code
+.I new-code
+.B =
+.B string
+.B ;
+.PP
+An option whose type is a data string is essentially just a collection
+of bytes, and can be specified either as quoted text, like the text
+type, or as a list of hexadecimal contents separated by colons whose
+values must be between 0 and FF. For example:
+.nf
+
+option sql-identification-token code 195 = string;
+option sql-identification-token 17:23:19:a6:42:ea:99:7c:22;
+
+.fi
+.PP
+.B DOMAIN-LIST
+.PP
+.B option
+.I new-name
+.B code
+.I new-code
+.B =
+.B domain-list
+.B [compressed]
+.B ;
+.PP
+An option whose type is \fBdomain-list\fR is an RFC1035 formatted (on the
+wire, "DNS Format") list of domain names, separated by root labels. The
+optional \fBcompressed\fR keyword indicates if the option should be
+compressed relative to the start of the option contents (not the packet
+contents).
+.PP
+When in doubt, omit the \fBcompressed\fR keyword. When the software receives
+an option that is compressed and the \fBcompressed\fR keyword is omitted, it
+will still decompress the option (relative to the option contents field). The
+keyword only controls whether or not transmitted packets are compressed.
+.PP
+Note that when
+.B domain-list
+formatted options are output as environment variables to
+.B dhclient-script(8),
+the standard DNS \-escape mechanism is used: they are decimal. This is
+appropriate for direct use in eg /etc/resolv.conf.
+.nf
+
+.fi
+.PP
+.B ENCAPSULATION
+.PP
+.B option
+.I new-name
+.B code
+.I new-code
+.B =
+.B encapsulate
+.I identifier
+.B ;
+.PP
+An option whose type is \fBencapsulate\fR will encapsulate the
+contents of the option space specified in \fIidentifier\fR. Examples
+of encapsulated options in the DHCP protocol as it currently exists
+include the vendor-encapsulated-options option, the netware-suboptions
+option and the relay-agent-information option.
+.nf
+
+option space local;
+option local.demo code 1 = text;
+option local-encapsulation code 197 = encapsulate local;
+option local.demo "demo";
+
+.fi
+.PP
+.B ARRAYS
+.PP
+Options can contain arrays of any of the above types except for the
+text and data string types, which aren't currently supported in
+arrays. An example of an array definition is as follows:
+.nf
+
+option kerberos-servers code 200 = array of ip-address;
+option kerberos-servers 10.20.10.1, 10.20.11.1;
+
+.fi
+.B RECORDS
+.PP
+Options can also contain data structures consisting of a sequence of
+data types, which is sometimes called a record type. For example:
+.nf
+
+option contrived-001 code 201 = { boolean, integer 32, text };
+option contrived-001 on 1772 "contrivance";
+
+.fi
+It's also possible to have options that are arrays of records, for
+example:
+.nf
+
+option new-static-routes code 201 = array of {
+ ip-address, ip-address, ip-address, integer 8 };
+option static-routes
+ 10.0.0.0 255.255.255.0 net-0-rtr.example.com 1,
+ 10.0.1.0 255.255.255.0 net-1-rtr.example.com 1,
+ 10.2.0.0 255.255.224.0 net-2-0-rtr.example.com 3;
+
+.fi
+.SH VENDOR ENCAPSULATED OPTIONS
+The DHCP protocol defines the \fBvendor-encapsulated-options\fR
+option, which allows vendors to define their own options that will be
+sent encapsulated in a standard DHCP option. It also defines
+the \fBVendor Identified Vendor Sub Options\fR option ("VIVSO"), and the
+DHCPv6 protocol defines the \fBVendor-specific Information Option\fR
+("VSIO"). The format of all of these options is usually internally a
+string of options, similarly to other normal DHCP options. The VIVSO
+and VSIO options differ in that they contain options that correspond
+to vendor Enterprise-ID numbers (assigned by IANA), which then contain
+options according to each Vendor's specifications. You will need to refer
+to your vendor's documentation in order to form options to their
+specification.
+.PP
+The value of these options can be set in one of two ways. The first
+way is to simply specify the data directly, using a text string or a
+colon-separated list of hexadecimal values. For help in forming these
+strings, please refer to \fBRFC2132\fR for the DHCPv4 \fBVendor Specific
+Information Option\fR, \fBRFC3925\fR for the DHCPv4 \fBVendor Identified Vendor
+Sub Options\fR, or \fBRFC3315\fR for the DHCPv6 \fBVendor-specific Information
+Option\fR. For example:
+.PP
+.nf
+option vendor-encapsulated-options
+ 2:4:
+ AC:11:41:1:
+ 3:12:
+ 73:75:6e:64:68:63:70:2d:73:65:72:76:65:72:31:37:2d:31:
+ 4:12:
+ 2f:65:78:70:6f:72:74:2f:72:6f:6f:74:2f:69:38:36:70:63;
+option vivso
+ 00:00:09:bf:0E:
+ 01:0c:
+ 48:65:6c:6c:6f:20:77:6f:72:6c:64:21;
+option dhcp6.vendor-opts
+ 00:00:09:bf:
+ 00:01:00:0c:
+ 48:65:6c:6c:6f:20:77:6f:72:6c:64:21;
+.fi
+.PP
+The second way of setting the value of these options is to have the DHCP
+server generate a vendor-specific option buffer. To do this, you
+must do four things: define an option space, define some options in
+that option space, provide values for them, and specify that that
+option space should be used to generate the relevant option.
+.PP
+To define a new option space in which vendor options can be stored,
+use the \fRoption space\fP statement:
+.PP
+.B option
+.B space
+.I name
+.B [ [ code width
+.I number
+.B ] [ length width
+.I number
+.B ] [ hash size
+.I number
+.B ] ] ;
+.PP
+Where the numbers following \fBcode width\fR, \fBlength width\fR,
+and \fBhash size\fR respectively identify the number of bytes used to
+describe option codes, option lengths, and the size in buckets of the
+hash tables to hold options in this space (most DHCPv4 option spaces
+use 1 byte codes and lengths, which is the default, whereas most
+DHCPv6 option spaces use 2 byte codes and lengths).
+.PP
+The code and length widths are used in DHCP protocol - you must configure
+these numbers to match the applicable option space you are configuring.
+They each default to 1. Valid values for code widths are 1, 2 or 4.
+Valid values for length widths are 0, 1 or 2. Most DHCPv4 option spaces
+use 1 byte codes and lengths, which is the default, whereas most DHCPv6
+option spaces use 2 byte codes and lengths. A zero-byte length produces
+options similar to the DHCPv6 Vendor-specific Information Option - but
+not their contents!
+.PP
+The hash size defaults depend upon the \fBcode width\fR selected, and
+may be 254 or 1009. Valid values range between 1 and 65535. Note
+that the higher you configure this value, the more memory will be used. It
+is considered good practice to configure a value that is slightly larger
+than the estimated number of options you plan to configure within the
+space. Previous versions of ISC DHCP (up to and including DHCP 3.0.*),
+this value was fixed at 9973.
+.PP
+The name can then be used in option definitions, as described earlier in
+this document. For example:
+.nf
+
+option space SUNW code width 1 length width 1 hash size 3;
+option SUNW.server-address code 2 = ip-address;
+option SUNW.server-name code 3 = text;
+option SUNW.root-path code 4 = text;
+
+option space ISC code width 1 length width 1 hash size 3;
+option ISC.sample code 1 = text;
+option vendor.ISC code 2495 = encapsulate vivso-sample;
+option vendor-class.ISC code 2495 = text;
+
+option ISC.sample "configuration text here";
+option vendor-class.ISC "vendor class here";
+
+option space docsis code width 2 length width 2 hash size 17;
+option docsis.tftp-servers code 32 = array of ip6-address;
+option docsis.cablelabs-configuration-file code 33 = text;
+option docsis.cablelabs-syslog-servers code 34 = array of ip6-address;
+option docsis.device-id code 36 = string;
+option docsis.time-servers code 37 = array of ip6-address;
+option docsis.time-offset code 38 = signed integer 32;
+option vsio.docsis code 4491 = encapsulate docsis;
+
+.fi
+Once you have defined an option space and the format of some options,
+you can set up scopes that define values for those options, and you
+can say when to use them. For example, suppose you want to handle
+two different classes of clients. Using the option space definition
+shown in the previous example, you can send different option values to
+different clients based on the vendor-class-identifier option that the
+clients send, as follows:
+.PP
+.nf
+class "vendor-classes" {
+ match option vendor-class-identifier;
+}
+
+subclass "vendor-classes" "SUNW.Ultra-5_10" {
+ vendor-option-space SUNW;
+ option SUNW.root-path "/export/root/sparc";
+}
+
+subclass "vendor-classes" "SUNW.i86pc" {
+ vendor-option-space SUNW;
+ option SUNW.root-path "/export/root/i86pc";
+}
+
+option SUNW.server-address 172.17.65.1;
+option SUNW.server-name "sundhcp-server17-1";
+
+option vivso-sample.sample "Hello world!";
+
+option docsis.tftp-servers ::1;
+
+.fi
+.PP
+As you can see in the preceding example, regular scoping rules apply,
+so you can define values that are global in the global scope, and only
+define values that are specific to a particular class in the local
+scope. The \fBvendor-option-space\fR declaration tells the DHCP
+server to use options in the SUNW option space to construct the DHCPv4
+.B vendor-encapsulated-options
+option. This is a limitation of that option - the DHCPv4 VIVSO and the
+DHCPv6 VSIO options can have multiple vendor definitions all at once (even
+transmitted to the same client), so it is not necessary to configure this.
+.SH SEE ALSO
+dhcpd.conf(5), dhcpd.leases(5), dhclient.conf(5), dhcp-eval(5), dhcpd(8),
+dhclient(8), RFC2132, RFC2131, RFC3046, RFC3315.
+.SH AUTHOR
+Information about Internet Systems Consortium can be found at
+.B https://www.isc.org.
diff --git a/common/discover.c b/common/discover.c
new file mode 100644
index 0000000..3cd64a7
--- /dev/null
+++ b/common/discover.c
@@ -0,0 +1,1862 @@
+/* discover.c
+
+ Find and identify the network interfaces. */
+
+/*
+ * Copyright (c) 2013-2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2009,2011 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+
+/* length of line we can read from the IF file, 256 is too small in some cases */
+#define IF_LINE_LENGTH 1024
+
+#define BSD_COMP /* needed on Solaris for SIOCGLIFNUM */
+#include <sys/ioctl.h>
+#include <errno.h>
+
+#ifdef HAVE_NET_IF6_H
+# include <net/if6.h>
+#endif
+
+struct interface_info *interfaces, *dummy_interfaces, *fallback_interface;
+int interfaces_invalidated;
+int quiet_interface_discovery;
+u_int16_t local_port;
+u_int16_t remote_port;
+int (*dhcp_interface_setup_hook) (struct interface_info *, struct iaddr *);
+int (*dhcp_interface_discovery_hook) (struct interface_info *);
+isc_result_t (*dhcp_interface_startup_hook) (struct interface_info *);
+int (*dhcp_interface_shutdown_hook) (struct interface_info *);
+
+struct in_addr limited_broadcast;
+
+int local_family = AF_INET;
+struct in_addr local_address;
+
+void (*bootp_packet_handler) (struct interface_info *,
+ struct dhcp_packet *, unsigned,
+ unsigned int,
+ struct iaddr, struct hardware *);
+
+#ifdef DHCPv6
+void (*dhcpv6_packet_handler)(struct interface_info *,
+ const char *, int,
+ int, const struct iaddr *,
+ isc_boolean_t);
+#endif /* DHCPv6 */
+
+
+omapi_object_type_t *dhcp_type_interface;
+#if defined (TRACING)
+trace_type_t *interface_trace;
+trace_type_t *inpacket_trace;
+trace_type_t *outpacket_trace;
+#endif
+struct interface_info **interface_vector;
+int interface_count;
+int interface_max;
+
+OMAPI_OBJECT_ALLOC (interface, struct interface_info, dhcp_type_interface)
+
+isc_result_t interface_setup ()
+{
+ isc_result_t status;
+ status = omapi_object_type_register (&dhcp_type_interface,
+ "interface",
+ dhcp_interface_set_value,
+ dhcp_interface_get_value,
+ dhcp_interface_destroy,
+ dhcp_interface_signal_handler,
+ dhcp_interface_stuff_values,
+ dhcp_interface_lookup,
+ dhcp_interface_create,
+ dhcp_interface_remove,
+ 0, 0, 0,
+ sizeof (struct interface_info),
+ interface_initialize, RC_MISC);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register interface object type: %s",
+ isc_result_totext (status));
+
+ return status;
+}
+
+#if defined (TRACING)
+void interface_trace_setup ()
+{
+ interface_trace = trace_type_register ("interface", (void *)0,
+ trace_interface_input,
+ trace_interface_stop, MDL);
+ inpacket_trace = trace_type_register ("inpacket", (void *)0,
+ trace_inpacket_input,
+ trace_inpacket_stop, MDL);
+ outpacket_trace = trace_type_register ("outpacket", (void *)0,
+ trace_outpacket_input,
+ trace_outpacket_stop, MDL);
+}
+#endif
+
+isc_result_t interface_initialize (omapi_object_t *ipo,
+ const char *file, int line)
+{
+ struct interface_info *ip = (struct interface_info *)ipo;
+ ip -> rfdesc = ip -> wfdesc = -1;
+ return ISC_R_SUCCESS;
+}
+
+
+/*
+ * Scanning for Interfaces
+ * -----------------------
+ *
+ * To find interfaces, we create an iterator that abstracts out most
+ * of the platform specifics. Use is fairly straightforward:
+ *
+ * - begin_iface_scan() starts the process.
+ * - Use next_iface() until it returns 0.
+ * - end_iface_scan() performs any necessary cleanup.
+ *
+ * We check for errors on each call to next_iface(), which returns a
+ * description of the error as a string if any occurs.
+ *
+ * We currently have code for Solaris and Linux. Other systems need
+ * to have code written.
+ *
+ * NOTE: the long-term goal is to use the interface code from BIND 9.
+ */
+
+#if defined(SIOCGLIFCONF) && defined(SIOCGLIFNUM) && defined(SIOCGLIFFLAGS)
+
+/* HP/UX doesn't define struct lifconf, instead they define struct
+ * if_laddrconf. Similarly, 'struct lifreq' and 'struct lifaddrreq'.
+ */
+#ifdef ISC_PLATFORM_HAVEIF_LADDRCONF
+# define lifc_len iflc_len
+# define lifc_buf iflc_buf
+# define lifc_req iflc_req
+# define LIFCONF if_laddrconf
+#else
+# define ISC_HAVE_LIFC_FAMILY 1
+# define ISC_HAVE_LIFC_FLAGS 1
+# define LIFCONF lifconf
+#endif
+
+#ifdef ISC_PLATFORM_HAVEIF_LADDRREQ
+# define lifr_addr iflr_addr
+# define lifr_name iflr_name
+# define lifr_dstaddr iflr_dstaddr
+# define lifr_flags iflr_flags
+# define sockaddr_storage sockaddr_ext
+# define ss_family sa_family
+# define LIFREQ if_laddrreq
+#else
+# define LIFREQ lifreq
+#endif
+
+#ifndef IF_NAMESIZE
+# if defined(LIFNAMSIZ)
+# define IF_NAMESIZE LIFNAMSIZ
+# elif defined(IFNAMSIZ)
+# define IF_NAMESIZE IFNAMSIZ
+# else
+# define IF_NAMESIZE 16
+# endif
+#endif
+#elif !defined(__linux) && !defined(HAVE_IFADDRS_H)
+# define SIOCGLIFCONF SIOCGIFCONF
+# define SIOCGLIFFLAGS SIOCGIFFLAGS
+# define LIFREQ ifreq
+# define LIFCONF ifconf
+# define lifr_name ifr_name
+# define lifr_addr ifr_addr
+# define lifr_flags ifr_flags
+# define lifc_len ifc_len
+# define lifc_buf ifc_buf
+# define lifc_req ifc_req
+#ifdef _AIX
+# define ss_family __ss_family
+#endif
+#endif
+
+#if defined(SIOCGLIFCONF) && defined(SIOCGLIFFLAGS)
+/*
+ * Solaris support
+ * ---------------
+ *
+ * The SIOCGLIFCONF ioctl() are the extension that you need to use
+ * on Solaris to get information about IPv6 addresses.
+ *
+ * Solaris' extended interface is documented in the if_tcp man page.
+ */
+
+/*
+ * Structure holding state about the scan.
+ */
+struct iface_conf_list {
+ int sock; /* file descriptor used to get information */
+ int num; /* total number of interfaces */
+ struct LIFCONF conf; /* structure used to get information */
+ int next; /* next interface to retrieve when iterating */
+};
+
+/*
+ * Structure used to return information about a specific interface.
+ */
+struct iface_info {
+ char name[IF_NAMESIZE+1]; /* name of the interface, e.g. "bge0" */
+ struct sockaddr_storage addr; /* address information */
+ isc_uint64_t flags; /* interface flags, e.g. IFF_LOOPBACK */
+};
+
+/*
+ * Start a scan of interfaces.
+ *
+ * The iface_conf_list structure maintains state for this process.
+ */
+int
+begin_iface_scan(struct iface_conf_list *ifaces) {
+#ifdef ISC_PLATFORM_HAVELIFNUM
+ struct lifnum lifnum;
+#else
+ int lifnum;
+#endif
+
+ ifaces->sock = socket(local_family, SOCK_DGRAM, IPPROTO_UDP);
+ if (ifaces->sock < 0) {
+ log_error("Error creating socket to list interfaces; %m");
+ return 0;
+ }
+
+ memset(&lifnum, 0, sizeof(lifnum));
+#ifdef ISC_PLATFORM_HAVELIFNUM
+ lifnum.lifn_family = AF_UNSPEC;
+#endif
+#ifdef SIOCGLIFNUM
+ if (ioctl(ifaces->sock, SIOCGLIFNUM, &lifnum) < 0) {
+ log_error("Error finding total number of interfaces; %m");
+ close(ifaces->sock);
+ ifaces->sock = -1;
+ return 0;
+ }
+
+#ifdef ISC_PLATFORM_HAVELIFNUM
+ ifaces->num = lifnum.lifn_count;
+#else
+ ifaces->num = lifnum;
+#endif
+#else
+ ifaces->num = 64;
+#endif /* SIOCGLIFNUM */
+
+ memset(&ifaces->conf, 0, sizeof(ifaces->conf));
+#ifdef ISC_HAVE_LIFC_FAMILY
+ ifaces->conf.lifc_family = AF_UNSPEC;
+#endif
+ ifaces->conf.lifc_len = ifaces->num * sizeof(struct LIFREQ);
+ ifaces->conf.lifc_buf = dmalloc(ifaces->conf.lifc_len, MDL);
+ if (ifaces->conf.lifc_buf == NULL) {
+ log_fatal("Out of memory getting interface list.");
+ }
+
+ if (ioctl(ifaces->sock, SIOCGLIFCONF, &ifaces->conf) < 0) {
+ log_error("Error getting interfaces configuration list; %m");
+ dfree(ifaces->conf.lifc_buf, MDL);
+ close(ifaces->sock);
+ ifaces->sock = -1;
+ return 0;
+ }
+
+ ifaces->next = 0;
+
+ return 1;
+}
+
+/*
+ * Retrieve the next interface.
+ *
+ * Returns information in the info structure.
+ * Sets err to 1 if there is an error, otherwise 0.
+ */
+int
+next_iface(struct iface_info *info, int *err, struct iface_conf_list *ifaces) {
+ struct LIFREQ *p;
+ struct LIFREQ tmp;
+ isc_boolean_t foundif;
+#if defined(sun) || defined(__linux)
+ /* Pointer used to remove interface aliases. */
+ char *s;
+#endif
+
+ do {
+ foundif = ISC_FALSE;
+
+ if (ifaces->next >= ifaces->num) {
+ *err = 0;
+ return 0;
+ }
+
+ p = ifaces->conf.lifc_req;
+ p += ifaces->next;
+
+ if (strlen(p->lifr_name) >= sizeof(info->name)) {
+ *err = 1;
+ log_error("Interface name '%s' too long", p->lifr_name);
+ return 0;
+ }
+
+ /* Reject if interface address family does not match */
+ if (p->lifr_addr.ss_family != local_family) {
+ ifaces->next++;
+ continue;
+ }
+
+ strcpy(info->name, p->lifr_name);
+ memset(&info->addr, 0, sizeof(info->addr));
+ memcpy(&info->addr, &p->lifr_addr, sizeof(p->lifr_addr));
+
+#if defined(sun) || defined(__linux)
+ /* interface aliases look like "eth0:1" or "wlan1:3" */
+ s = strchr(info->name, ':');
+ if (s != NULL) {
+ *s = '\0';
+ }
+#endif /* defined(sun) || defined(__linux) */
+
+ foundif = ISC_TRUE;
+ } while ((foundif == ISC_FALSE) ||
+ (strncmp(info->name, "dummy", 5) == 0));
+
+ memset(&tmp, 0, sizeof(tmp));
+ strcpy(tmp.lifr_name, info->name);
+ if (ioctl(ifaces->sock, SIOCGLIFFLAGS, &tmp) < 0) {
+ log_error("Error getting interface flags for '%s'; %m",
+ p->lifr_name);
+ *err = 1;
+ return 0;
+ }
+ info->flags = tmp.lifr_flags;
+
+ ifaces->next++;
+ *err = 0;
+ return 1;
+}
+
+/*
+ * End scan of interfaces.
+ */
+void
+end_iface_scan(struct iface_conf_list *ifaces) {
+ dfree(ifaces->conf.lifc_buf, MDL);
+ close(ifaces->sock);
+ ifaces->sock = -1;
+}
+
+#elif __linux /* !HAVE_SIOCGLIFCONF */
+/*
+ * Linux support
+ * -------------
+ *
+ * In Linux, we use the /proc pseudo-filesystem to get information
+ * about interfaces, along with selected ioctl() calls.
+ *
+ * Linux low level access is documented in the netdevice man page.
+ */
+
+/*
+ * Structure holding state about the scan.
+ */
+struct iface_conf_list {
+ int sock; /* file descriptor used to get information */
+ FILE *fp; /* input from /proc/net/dev */
+#ifdef DHCPv6
+ FILE *fp6; /* input from /proc/net/if_inet6 */
+#endif
+};
+
+/*
+ * Structure used to return information about a specific interface.
+ */
+struct iface_info {
+ char name[IFNAMSIZ]; /* name of the interface, e.g. "eth0" */
+ struct sockaddr_storage addr; /* address information */
+ isc_uint64_t flags; /* interface flags, e.g. IFF_LOOPBACK */
+};
+
+/*
+ * Start a scan of interfaces.
+ *
+ * The iface_conf_list structure maintains state for this process.
+ */
+int
+begin_iface_scan(struct iface_conf_list *ifaces) {
+ char buf[IF_LINE_LENGTH];
+ int len;
+ int i;
+
+ ifaces->fp = fopen("/proc/net/dev", "r");
+ if (ifaces->fp == NULL) {
+ log_error("Error opening '/proc/net/dev' to list interfaces");
+ return 0;
+ }
+
+ /*
+ * The first 2 lines are header information, so read and ignore them.
+ */
+ for (i=0; i<2; i++) {
+ if (fgets(buf, sizeof(buf), ifaces->fp) == NULL) {
+ log_error("Error reading headers from '/proc/net/dev'");
+ fclose(ifaces->fp);
+ ifaces->fp = NULL;
+ return 0;
+ }
+ len = strlen(buf);
+ if ((len <= 0) || (buf[len-1] != '\n')) {
+ log_error("Bad header line in '/proc/net/dev'");
+ fclose(ifaces->fp);
+ ifaces->fp = NULL;
+ return 0;
+ }
+ }
+
+ ifaces->sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (ifaces->sock < 0) {
+ log_error("Error creating socket to list interfaces; %m");
+ fclose(ifaces->fp);
+ ifaces->fp = NULL;
+ return 0;
+ }
+
+#ifdef DHCPv6
+ if (local_family == AF_INET6) {
+ ifaces->fp6 = fopen("/proc/net/if_inet6", "r");
+ if (ifaces->fp6 == NULL) {
+ log_error("Error opening '/proc/net/if_inet6' to "
+ "list IPv6 interfaces; %m");
+ close(ifaces->sock);
+ ifaces->sock = -1;
+ fclose(ifaces->fp);
+ ifaces->fp = NULL;
+ return 0;
+ }
+ }
+#endif
+
+ return 1;
+}
+
+/*
+ * Read our IPv4 interfaces from /proc/net/dev.
+ *
+ * The file looks something like this:
+ *
+ * Inter-| Receive ...
+ * face |bytes packets errs drop fifo frame ...
+ * lo: 1580562 4207 0 0 0 0 ...
+ * eth0: 0 0 0 0 0 0 ...
+ * eth1:1801552440 37895 0 14 0 ...
+ *
+ * We only care about the interface name, which is at the start of
+ * each line.
+ *
+ * We use an ioctl() to get the address and flags for each interface.
+ */
+static int
+next_iface4(struct iface_info *info, int *err, struct iface_conf_list *ifaces) {
+ char buf[IF_LINE_LENGTH];
+ int len;
+ char *p;
+ char *name;
+ struct ifreq tmp;
+
+ /*
+ * Loop exits when we find an interface that has an address, or
+ * when we run out of interfaces.
+ */
+ for (;;) {
+ do {
+ /*
+ * Read the next line in the file.
+ */
+ if (fgets(buf, sizeof(buf), ifaces->fp) == NULL) {
+ if (ferror(ifaces->fp)) {
+ *err = 1;
+ log_error("Error reading interface "
+ "information");
+ } else {
+ *err = 0;
+ }
+ return 0;
+ }
+
+ /*
+ * Make sure the line is a nice,
+ * newline-terminated line.
+ */
+ len = strlen(buf);
+ if ((len <= 0) || (buf[len-1] != '\n')) {
+ log_error("Bad line reading interface "
+ "information");
+ *err = 1;
+ return 0;
+ }
+
+ /*
+ * Figure out our name.
+ */
+ p = strrchr(buf, ':');
+ if (p == NULL) {
+ log_error("Bad line reading interface "
+ "information (no colon)");
+ *err = 1;
+ return 0;
+ }
+ *p = '\0';
+ name = buf;
+ while (isspace(*name)) {
+ name++;
+ }
+
+ /*
+ * Copy our name into our interface structure.
+ */
+ len = p - name;
+ if (len >= sizeof(info->name)) {
+ *err = 1;
+ log_error("Interface name '%s' too long", name);
+ return 0;
+ }
+ strncpy(info->name, name, sizeof(info->name) - 1);
+
+#ifdef ALIAS_NAMED_PERMUTED
+ /* interface aliases look like "eth0:1" or "wlan1:3" */
+ s = strchr(info->name, ':');
+ if (s != NULL) {
+ *s = '\0';
+ }
+#endif
+
+#ifdef SKIP_DUMMY_INTERFACES
+ } while (strncmp(info->name, "dummy", 5) == 0);
+#else
+ } while (0);
+#endif
+
+ memset(&tmp, 0, sizeof(tmp));
+ strncpy(tmp.ifr_name, name, sizeof(tmp.ifr_name) - 1);
+ if (ioctl(ifaces->sock, SIOCGIFADDR, &tmp) < 0) {
+ if (errno == EADDRNOTAVAIL) {
+ continue;
+ }
+ log_error("Error getting interface address "
+ "for '%s'; %m", name);
+ *err = 1;
+ return 0;
+ }
+ memcpy(&info->addr, &tmp.ifr_addr, sizeof(tmp.ifr_addr));
+
+ memset(&tmp, 0, sizeof(tmp));
+ strncpy(tmp.ifr_name, name, sizeof(tmp.ifr_name) - 1);
+ if (ioctl(ifaces->sock, SIOCGIFFLAGS, &tmp) < 0) {
+ log_error("Error getting interface flags for '%s'; %m",
+ name);
+ *err = 1;
+ return 0;
+ }
+ info->flags = tmp.ifr_flags;
+
+ *err = 0;
+ return 1;
+ }
+}
+
+#ifdef DHCPv6
+/*
+ * Read our IPv6 interfaces from /proc/net/if_inet6.
+ *
+ * The file looks something like this:
+ *
+ * fe80000000000000025056fffec00008 05 40 20 80 vmnet8
+ * 00000000000000000000000000000001 01 80 10 80 lo
+ * fe80000000000000025056fffec00001 06 40 20 80 vmnet1
+ * 200108881936000202166ffffe497d9b 03 40 00 00 eth1
+ * fe8000000000000002166ffffe497d9b 03 40 20 80 eth1
+ *
+ * We get IPv6 address from the start, the interface name from the end,
+ * and ioctl() to get flags.
+ */
+static int
+next_iface6(struct iface_info *info, int *err, struct iface_conf_list *ifaces) {
+ char buf[IF_LINE_LENGTH];
+ int len;
+ char *p;
+ char *name;
+ int i;
+ struct sockaddr_in6 addr;
+ struct ifreq tmp;
+
+ do {
+ /*
+ * Read the next line in the file.
+ */
+ if (fgets(buf, sizeof(buf), ifaces->fp6) == NULL) {
+ if (ferror(ifaces->fp6)) {
+ *err = 1;
+ log_error("Error reading IPv6 "
+ "interface information");
+ } else {
+ *err = 0;
+ }
+ return 0;
+ }
+
+ /*
+ * Make sure the line is a nice, newline-terminated line.
+ */
+ len = strlen(buf);
+ if ((len <= 0) || (buf[len-1] != '\n')) {
+ log_error("Bad line reading IPv6 "
+ "interface information");
+ *err = 1;
+ return 0;
+ }
+
+ /*
+ * Figure out our name.
+ */
+ buf[--len] = '\0';
+ p = strrchr(buf, ' ');
+ if (p == NULL) {
+ log_error("Bad line reading IPv6 interface "
+ "information (no space)");
+ *err = 1;
+ return 0;
+ }
+ name = p+1;
+
+ /*
+ * Copy our name into our interface structure.
+ */
+ len = strlen(name);
+ if (len >= sizeof(info->name)) {
+ *err = 1;
+ log_error("IPv6 interface name '%s' too long", name);
+ return 0;
+ }
+ strcpy(info->name, name);
+
+#ifdef SKIP_DUMMY_INTERFACES
+ } while (strncmp(info->name, "dummy", 5) == 0);
+#else
+ } while (0);
+#endif
+
+ /*
+ * Double-check we start with the IPv6 address.
+ */
+ for (i=0; i<32; i++) {
+ if (!isxdigit(buf[i]) || isupper(buf[i])) {
+ *err = 1;
+ log_error("Bad line reading IPv6 interface address "
+ "for '%s'", name);
+ return 0;
+ }
+ }
+
+ /*
+ * Load our socket structure.
+ */
+ memset(&addr, 0, sizeof(addr));
+ addr.sin6_family = AF_INET6;
+ for (i=0; i<16; i++) {
+ unsigned char byte;
+ static const char hex[] = "0123456789abcdef";
+ byte = ((index(hex, buf[i * 2]) - hex) << 4) |
+ (index(hex, buf[i * 2 + 1]) - hex);
+ addr.sin6_addr.s6_addr[i] = byte;
+ }
+ memcpy(&info->addr, &addr, sizeof(addr));
+
+ /*
+ * Get our flags.
+ */
+ memset(&tmp, 0, sizeof(tmp));
+ strcpy(tmp.ifr_name, name);
+ if (ioctl(ifaces->sock, SIOCGIFFLAGS, &tmp) < 0) {
+ log_error("Error getting interface flags for '%s'; %m", name);
+ *err = 1;
+ return 0;
+ }
+ info->flags = tmp.ifr_flags;
+
+ *err = 0;
+ return 1;
+}
+#endif /* DHCPv6 */
+
+/*
+ * Retrieve the next interface.
+ *
+ * Returns information in the info structure.
+ * Sets err to 1 if there is an error, otherwise 0.
+ */
+int
+next_iface(struct iface_info *info, int *err, struct iface_conf_list *ifaces) {
+ if (next_iface4(info, err, ifaces)) {
+ return 1;
+ }
+#ifdef DHCPv6
+ if (!(*err)) {
+ if (local_family == AF_INET6)
+ return next_iface6(info, err, ifaces);
+ }
+#endif
+ return 0;
+}
+
+/*
+ * End scan of interfaces.
+ */
+void
+end_iface_scan(struct iface_conf_list *ifaces) {
+ fclose(ifaces->fp);
+ ifaces->fp = NULL;
+ close(ifaces->sock);
+ ifaces->sock = -1;
+#ifdef DHCPv6
+ if (local_family == AF_INET6) {
+ fclose(ifaces->fp6);
+ ifaces->fp6 = NULL;
+ }
+#endif
+}
+#else
+
+/*
+ * BSD support
+ * -----------
+ *
+ * FreeBSD, NetBSD, OpenBSD, and OS X all have the getifaddrs()
+ * function.
+ *
+ * The getifaddrs() man page describes the use.
+ */
+
+#include <ifaddrs.h>
+
+/*
+ * Structure holding state about the scan.
+ */
+struct iface_conf_list {
+ struct ifaddrs *head; /* beginning of the list */
+ struct ifaddrs *next; /* current position in the list */
+};
+
+/*
+ * Structure used to return information about a specific interface.
+ */
+struct iface_info {
+ char name[IFNAMSIZ]; /* name of the interface, e.g. "bge0" */
+ struct sockaddr_storage addr; /* address information */
+ isc_uint64_t flags; /* interface flags, e.g. IFF_LOOPBACK */
+};
+
+/*
+ * Start a scan of interfaces.
+ *
+ * The iface_conf_list structure maintains state for this process.
+ */
+int
+begin_iface_scan(struct iface_conf_list *ifaces) {
+ if (getifaddrs(&ifaces->head) != 0) {
+ log_error("Error getting interfaces; %m");
+ return 0;
+ }
+ ifaces->next = ifaces->head;
+ return 1;
+}
+
+/*
+ * Retrieve the next interface.
+ *
+ * Returns information in the info structure.
+ * Sets err to 1 if there is an error, otherwise 0.
+ */
+int
+next_iface(struct iface_info *info, int *err, struct iface_conf_list *ifaces) {
+ if (ifaces->next == NULL) {
+ *err = 0;
+ return 0;
+ }
+ if (strlen(ifaces->next->ifa_name) >= sizeof(info->name)) {
+ log_error("Interface name '%s' too long",
+ ifaces->next->ifa_name);
+ *err = 1;
+ return 0;
+ }
+ strcpy(info->name, ifaces->next->ifa_name);
+ memcpy(&info->addr, ifaces->next->ifa_addr,
+ ifaces->next->ifa_addr->sa_len);
+ info->flags = ifaces->next->ifa_flags;
+ ifaces->next = ifaces->next->ifa_next;
+ *err = 0;
+ return 1;
+}
+
+/*
+ * End scan of interfaces.
+ */
+void
+end_iface_scan(struct iface_conf_list *ifaces) {
+ freeifaddrs(ifaces->head);
+ ifaces->head = NULL;
+ ifaces->next = NULL;
+}
+#endif
+
+/* XXX: perhaps create drealloc() rather than do it manually */
+void
+add_ipv4_addr_to_interface(struct interface_info *iface,
+ const struct in_addr *addr) {
+ /*
+ * We don't expect a lot of addresses per IPv4 interface, so
+ * we use 4, as our "chunk size" for collecting addresses.
+ */
+ if (iface->addresses == NULL) {
+ iface->addresses = dmalloc(4 * sizeof(struct in_addr), MDL);
+ if (iface->addresses == NULL) {
+ log_fatal("Out of memory saving IPv4 address "
+ "on interface.");
+ }
+ iface->address_count = 0;
+ iface->address_max = 4;
+ } else if (iface->address_count >= iface->address_max) {
+ struct in_addr *tmp;
+ int new_max;
+
+ new_max = iface->address_max + 4;
+ tmp = dmalloc(new_max * sizeof(struct in_addr), MDL);
+ if (tmp == NULL) {
+ log_fatal("Out of memory saving IPv4 address "
+ "on interface.");
+ }
+ memcpy(tmp,
+ iface->addresses,
+ iface->address_max * sizeof(struct in_addr));
+ dfree(iface->addresses, MDL);
+ iface->addresses = tmp;
+ iface->address_max = new_max;
+ }
+ iface->addresses[iface->address_count++] = *addr;
+}
+
+#ifdef DHCPv6
+/* XXX: perhaps create drealloc() rather than do it manually */
+void
+add_ipv6_addr_to_interface(struct interface_info *iface,
+ const struct in6_addr *addr) {
+ /*
+ * Each IPv6 interface will have at least two IPv6 addresses,
+ * and likely quite a few more. So we use 8, as our "chunk size" for
+ * collecting addresses.
+ */
+ if (iface->v6addresses == NULL) {
+ iface->v6addresses = dmalloc(8 * sizeof(struct in6_addr), MDL);
+ if (iface->v6addresses == NULL) {
+ log_fatal("Out of memory saving IPv6 address "
+ "on interface.");
+ }
+ iface->v6address_count = 0;
+ iface->v6address_max = 8;
+ } else if (iface->v6address_count >= iface->v6address_max) {
+ struct in6_addr *tmp;
+ int new_max;
+
+ new_max = iface->v6address_max + 8;
+ tmp = dmalloc(new_max * sizeof(struct in6_addr), MDL);
+ if (tmp == NULL) {
+ log_fatal("Out of memory saving IPv6 address "
+ "on interface.");
+ }
+ memcpy(tmp,
+ iface->v6addresses,
+ iface->v6address_max * sizeof(struct in6_addr));
+ dfree(iface->v6addresses, MDL);
+ iface->v6addresses = tmp;
+ iface->v6address_max = new_max;
+ }
+ iface->v6addresses[iface->v6address_count++] = *addr;
+}
+#endif /* DHCPv6 */
+
+/* Use the SIOCGIFCONF ioctl to get a list of all the attached interfaces.
+ For each interface that's of type INET and not the loopback interface,
+ register that interface with the network I/O software, figure out what
+ subnet it's on, and add it to the list of interfaces. */
+
+void
+discover_interfaces(int state) {
+ struct iface_conf_list ifaces;
+ struct iface_info info;
+ int err;
+
+ struct interface_info *tmp;
+ struct interface_info *last, *next;
+
+#ifdef DHCPv6
+ char abuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+#endif /* DHCPv6 */
+
+
+ struct subnet *subnet;
+ int ir;
+ isc_result_t status;
+ int wifcount = 0;
+
+ static int setup_fallback = 0;
+
+ if (!begin_iface_scan(&ifaces)) {
+ log_fatal("Can't get list of interfaces.");
+ }
+
+ /* If we already have a list of interfaces, and we're running as
+ a DHCP server, the interfaces were requested. */
+ if (interfaces && (state == DISCOVER_SERVER ||
+ state == DISCOVER_RELAY ||
+ state == DISCOVER_REQUESTED))
+ ir = 0;
+ else if (state == DISCOVER_UNCONFIGURED)
+ ir = INTERFACE_REQUESTED | INTERFACE_AUTOMATIC;
+ else
+ ir = INTERFACE_REQUESTED;
+
+ /* Cycle through the list of interfaces looking for IP addresses. */
+ while (next_iface(&info, &err, &ifaces)) {
+
+ /* See if we've seen an interface that matches this one. */
+ for (tmp = interfaces; tmp; tmp = tmp->next) {
+ if (!strcmp(tmp->name, info.name))
+ break;
+ }
+
+ /* Skip non broadcast interfaces (plus loopback and
+ point-to-point in case an OS incorrectly marks them
+ as broadcast). Also skip down interfaces unless we're
+ trying to get a list of configurable interfaces. */
+ if ((((local_family == AF_INET &&
+ !(info.flags & IFF_BROADCAST)) ||
+#ifdef DHCPv6
+ (local_family == AF_INET6 &&
+ !(info.flags & IFF_MULTICAST)) ||
+#endif
+ info.flags & IFF_LOOPBACK ||
+ info.flags & IFF_POINTOPOINT) && !tmp) ||
+ (!(info.flags & IFF_UP) &&
+ state != DISCOVER_UNCONFIGURED))
+ continue;
+
+ /* If there isn't already an interface by this name,
+ allocate one. */
+ if (tmp == NULL) {
+ status = interface_allocate(&tmp, MDL);
+ if (status != ISC_R_SUCCESS) {
+ log_fatal("Error allocating interface %s: %s",
+ info.name, isc_result_totext(status));
+ }
+ strcpy(tmp->name, info.name);
+ interface_snorf(tmp, ir);
+ interface_dereference(&tmp, MDL);
+ tmp = interfaces; /* XXX */
+ }
+
+ if (dhcp_interface_discovery_hook) {
+ (*dhcp_interface_discovery_hook)(tmp);
+ }
+
+ if ((info.addr.ss_family == AF_INET) &&
+ (local_family == AF_INET)) {
+ struct sockaddr_in *a = (struct sockaddr_in*)&info.addr;
+ struct iaddr addr;
+
+ /* We don't want the loopback interface. */
+ if (a->sin_addr.s_addr == htonl(INADDR_LOOPBACK) &&
+ ((tmp->flags & INTERFACE_AUTOMATIC) &&
+ state == DISCOVER_SERVER))
+ continue;
+
+ /* If the only address we have is 0.0.0.0, we
+ shouldn't consider the interface configured. */
+ if (a->sin_addr.s_addr != htonl(INADDR_ANY))
+ tmp->configured = 1;
+
+ add_ipv4_addr_to_interface(tmp, &a->sin_addr);
+
+ /* invoke the setup hook */
+ addr.len = 4;
+ memcpy(addr.iabuf, &a->sin_addr.s_addr, addr.len);
+ if (dhcp_interface_setup_hook) {
+ (*dhcp_interface_setup_hook)(tmp, &addr);
+ }
+ }
+#ifdef DHCPv6
+ else if ((info.addr.ss_family == AF_INET6) &&
+ (local_family == AF_INET6)) {
+ struct sockaddr_in6 *a =
+ (struct sockaddr_in6*)&info.addr;
+ struct iaddr addr;
+
+ /* We don't want the loopback interface. */
+ if (IN6_IS_ADDR_LOOPBACK(&a->sin6_addr) &&
+ ((tmp->flags & INTERFACE_AUTOMATIC) &&
+ state == DISCOVER_SERVER))
+ continue;
+
+ /* If the only address we have is 0.0.0.0, we
+ shouldn't consider the interface configured. */
+ if (IN6_IS_ADDR_UNSPECIFIED(&a->sin6_addr))
+ tmp->configured = 1;
+
+ add_ipv6_addr_to_interface(tmp, &a->sin6_addr);
+
+ /* invoke the setup hook */
+ addr.len = 16;
+ memcpy(addr.iabuf, &a->sin6_addr, addr.len);
+ if (dhcp_interface_setup_hook) {
+ (*dhcp_interface_setup_hook)(tmp, &addr);
+ }
+ }
+#endif /* DHCPv6 */
+ }
+
+ if (err) {
+ log_fatal("Error getting interface information.");
+ }
+
+ end_iface_scan(&ifaces);
+
+
+ /* Mock-up an 'ifp' structure which is no longer used in the
+ * new interface-sensing code, but is used in higher layers
+ * (for example to sense fallback interfaces).
+ */
+ for (tmp = interfaces ; tmp != NULL ; tmp = tmp->next) {
+ if (tmp->ifp == NULL) {
+ struct ifreq *tif;
+
+ tif = (struct ifreq *)dmalloc(sizeof(struct ifreq),
+ MDL);
+ if (tif == NULL)
+ log_fatal("no space for ifp mockup.");
+ strcpy(tif->ifr_name, tmp->name);
+ tmp->ifp = tif;
+ }
+ }
+
+
+ /* If we're just trying to get a list of interfaces that we might
+ be able to configure, we can quit now. */
+ if (state == DISCOVER_UNCONFIGURED) {
+ return;
+ }
+
+ /* Weed out the interfaces that did not have IP addresses. */
+ tmp = last = next = NULL;
+ if (interfaces)
+ interface_reference (&tmp, interfaces, MDL);
+ while (tmp) {
+ if (next)
+ interface_dereference (&next, MDL);
+ if (tmp -> next)
+ interface_reference (&next, tmp -> next, MDL);
+ /* skip interfaces that are running already */
+ if (tmp -> flags & INTERFACE_RUNNING) {
+ interface_dereference(&tmp, MDL);
+ if(next)
+ interface_reference(&tmp, next, MDL);
+ continue;
+ }
+ if ((tmp -> flags & INTERFACE_AUTOMATIC) &&
+ state == DISCOVER_REQUESTED)
+ tmp -> flags &= ~(INTERFACE_AUTOMATIC |
+ INTERFACE_REQUESTED);
+
+#ifdef DHCPv6
+ if (!(tmp->flags & INTERFACE_REQUESTED)) {
+#else
+ if (!tmp -> ifp || !(tmp -> flags & INTERFACE_REQUESTED)) {
+#endif /* DHCPv6 */
+ if ((tmp -> flags & INTERFACE_REQUESTED) != ir)
+ log_fatal ("%s: not found", tmp -> name);
+ if (!last) {
+ if (interfaces)
+ interface_dereference (&interfaces,
+ MDL);
+ if (next)
+ interface_reference (&interfaces, next, MDL);
+ } else {
+ interface_dereference (&last -> next, MDL);
+ if (next)
+ interface_reference (&last -> next,
+ next, MDL);
+ }
+ if (tmp -> next)
+ interface_dereference (&tmp -> next, MDL);
+
+ /* Remember the interface in case we need to know
+ about it later. */
+ if (dummy_interfaces) {
+ interface_reference (&tmp -> next,
+ dummy_interfaces, MDL);
+ interface_dereference (&dummy_interfaces, MDL);
+ }
+ interface_reference (&dummy_interfaces, tmp, MDL);
+ interface_dereference (&tmp, MDL);
+ if (next)
+ interface_reference (&tmp, next, MDL);
+ continue;
+ }
+ last = tmp;
+
+ /* We must have a subnet declaration for each interface. */
+ if (!tmp->shared_network && (state == DISCOVER_SERVER)) {
+ log_error("%s", "");
+ if (local_family == AF_INET) {
+ log_error("No subnet declaration for %s (%s).",
+ tmp->name,
+ (tmp->addresses == NULL) ?
+ "no IPv4 addresses" :
+ inet_ntoa(tmp->addresses[0]));
+#ifdef DHCPv6
+ } else {
+ if (tmp->v6addresses != NULL) {
+ inet_ntop(AF_INET6,
+ &tmp->v6addresses[0],
+ abuf,
+ sizeof(abuf));
+ } else {
+ strcpy(abuf, "no IPv6 addresses");
+ }
+ log_error("No subnet6 declaration for %s (%s).",
+ tmp->name,
+ abuf);
+#endif /* DHCPv6 */
+ }
+ if (supports_multiple_interfaces(tmp)) {
+ log_error ("** Ignoring requests on %s. %s",
+ tmp -> name, "If this is not what");
+ log_error (" you want, please write %s",
+#ifdef DHCPv6
+ (local_family != AF_INET) ?
+ "a subnet6 declaration" :
+#endif
+ "a subnet declaration");
+ log_error (" in your dhcpd.conf file %s",
+ "for the network segment");
+ log_error (" to %s %s %s",
+ "which interface",
+ tmp -> name, "is attached. **");
+ log_error ("%s", "");
+ goto next;
+ } else {
+ log_error ("You must write a %s",
+#ifdef DHCPv6
+ (local_family != AF_INET) ?
+ "subnet6 declaration for this" :
+#endif
+ "subnet declaration for this");
+ log_error ("subnet. You cannot prevent %s",
+ "the DHCP server");
+ log_error ("from listening on this subnet %s",
+ "because your");
+ log_fatal ("operating system does not %s.",
+ "support this capability");
+ }
+ }
+
+ /* Find subnets that don't have valid interface
+ addresses... */
+ for (subnet = (tmp -> shared_network
+ ? tmp -> shared_network -> subnets
+ : (struct subnet *)0);
+ subnet; subnet = subnet -> next_sibling) {
+ /* Set the interface address for this subnet
+ to the first address we found. */
+ if (subnet->interface_address.len == 0) {
+ if (tmp->address_count > 0) {
+ subnet->interface_address.len = 4;
+ memcpy(subnet->interface_address.iabuf,
+ &tmp->addresses[0].s_addr, 4);
+ } else if (tmp->v6address_count > 0) {
+ subnet->interface_address.len = 16;
+ memcpy(subnet->interface_address.iabuf,
+ &tmp->v6addresses[0].s6_addr,
+ 16);
+ } else {
+ /* XXX: should be one */
+ log_error("%s missing an interface "
+ "address", tmp->name);
+ continue;
+ }
+ }
+ }
+
+ /* Flag the index as not having been set, so that the
+ interface registerer can set it or not as it chooses. */
+ tmp -> index = -1;
+
+ /* Register the interface... */
+ if (local_family == AF_INET) {
+ if_register_receive(tmp);
+ if_register_send(tmp);
+#ifdef DHCPv6
+ } else {
+ if ((state == DISCOVER_SERVER) ||
+ (state == DISCOVER_RELAY)) {
+ if_register6(tmp, 1);
+ } else {
+ if_register_linklocal6(tmp);
+ }
+#endif /* DHCPv6 */
+ }
+
+ interface_stash (tmp);
+ wifcount++;
+#if defined (F_SETFD)
+ if (fcntl (tmp -> rfdesc, F_SETFD, 1) < 0)
+ log_error ("Can't set close-on-exec on %s: %m",
+ tmp -> name);
+ if (tmp -> rfdesc != tmp -> wfdesc) {
+ if (fcntl (tmp -> wfdesc, F_SETFD, 1) < 0)
+ log_error ("Can't set close-on-exec on %s: %m",
+ tmp -> name);
+ }
+#endif
+ next:
+ interface_dereference (&tmp, MDL);
+ if (next)
+ interface_reference (&tmp, next, MDL);
+ }
+
+ /*
+ * Now register all the remaining interfaces as protocols.
+ * We register with omapi to allow for control of the interface,
+ * we've already registered the fd or socket with the socket
+ * manager as part of if_register_receive().
+ */
+ for (tmp = interfaces; tmp; tmp = tmp -> next) {
+ /* not if it's been registered before */
+ if (tmp -> flags & INTERFACE_RUNNING)
+ continue;
+ if (tmp -> rfdesc == -1)
+ continue;
+ switch (local_family) {
+#ifdef DHCPv6
+ case AF_INET6:
+ status = omapi_register_io_object((omapi_object_t *)tmp,
+ if_readsocket,
+ 0, got_one_v6, 0, 0);
+ break;
+#endif /* DHCPv6 */
+ case AF_INET:
+ default:
+ status = omapi_register_io_object((omapi_object_t *)tmp,
+ if_readsocket,
+ 0, got_one, 0, 0);
+ break;
+ }
+
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register I/O handle for %s: %s",
+ tmp -> name, isc_result_totext (status));
+
+#if defined(DHCPv6)
+ /* Only register the first interface for V6, since
+ * servers and relays all use the same socket.
+ * XXX: This has some messy side effects if we start
+ * dynamically adding and removing interfaces, but
+ * we're well beyond that point in terms of mess.
+ */
+ if (((state == DISCOVER_SERVER) || (state == DISCOVER_RELAY)) &&
+ (local_family == AF_INET6))
+ break;
+#endif
+ } /* for (tmp = interfaces; ... */
+
+ if (state == DISCOVER_SERVER && wifcount == 0) {
+ log_info ("%s", "");
+ log_fatal ("Not configured to listen on any interfaces!");
+ }
+
+ if ((local_family == AF_INET) && !setup_fallback) {
+ setup_fallback = 1;
+ maybe_setup_fallback();
+ }
+
+#if defined (F_SETFD)
+ if (fallback_interface) {
+ if (fcntl (fallback_interface -> rfdesc, F_SETFD, 1) < 0)
+ log_error ("Can't set close-on-exec on fallback: %m");
+ if (fallback_interface -> rfdesc != fallback_interface -> wfdesc) {
+ if (fcntl (fallback_interface -> wfdesc, F_SETFD, 1) < 0)
+ log_error ("Can't set close-on-exec on fallback: %m");
+ }
+ }
+#endif /* F_SETFD */
+}
+
+int if_readsocket (h)
+ omapi_object_t *h;
+{
+ struct interface_info *ip;
+
+ if (h -> type != dhcp_type_interface)
+ return -1;
+ ip = (struct interface_info *)h;
+ return ip -> rfdesc;
+}
+
+int setup_fallback (struct interface_info **fp, const char *file, int line)
+{
+ isc_result_t status;
+
+ status = interface_allocate (&fallback_interface, file, line);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Error allocating fallback interface: %s",
+ isc_result_totext (status));
+ strcpy (fallback_interface -> name, "fallback");
+ if (dhcp_interface_setup_hook)
+ (*dhcp_interface_setup_hook) (fallback_interface,
+ (struct iaddr *)0);
+ status = interface_reference (fp, fallback_interface, file, line);
+
+ fallback_interface -> index = -1;
+ interface_stash (fallback_interface);
+ return status == ISC_R_SUCCESS;
+}
+
+void reinitialize_interfaces ()
+{
+ struct interface_info *ip;
+
+ for (ip = interfaces; ip; ip = ip -> next) {
+ if_reinitialize_receive (ip);
+ if_reinitialize_send (ip);
+ }
+
+ if (fallback_interface)
+ if_reinitialize_send (fallback_interface);
+
+ interfaces_invalidated = 1;
+}
+
+isc_result_t got_one (h)
+ omapi_object_t *h;
+{
+ struct sockaddr_in from;
+ struct hardware hfrom;
+ struct iaddr ifrom;
+ int result;
+ union {
+ unsigned char packbuf [4095]; /* Packet input buffer.
+ Must be as large as largest
+ possible MTU. */
+ struct dhcp_packet packet;
+ } u;
+ struct interface_info *ip;
+
+ if (h -> type != dhcp_type_interface)
+ return DHCP_R_INVALIDARG;
+ ip = (struct interface_info *)h;
+
+ again:
+ if ((result =
+ receive_packet (ip, u.packbuf, sizeof u, &from, &hfrom)) < 0) {
+ log_error ("receive_packet failed on %s: %m", ip -> name);
+ return ISC_R_UNEXPECTED;
+ }
+ if (result == 0)
+ return ISC_R_UNEXPECTED;
+
+ /*
+ * If we didn't at least get the fixed portion of the BOOTP
+ * packet, drop the packet.
+ * Previously we allowed packets with no sname or filename
+ * as we were aware of at least one client that did. But
+ * a bug caused short packets to not work and nobody has
+ * complained, it seems rational to tighten up that
+ * restriction.
+ */
+ if (result < DHCP_FIXED_NON_UDP)
+ return ISC_R_UNEXPECTED;
+
+#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO)
+ {
+ /* We retrieve the ifindex from the unused hfrom variable */
+ unsigned int ifindex;
+
+ memcpy(&ifindex, hfrom.hbuf, sizeof (ifindex));
+
+ /*
+ * Seek forward from the first interface to find the matching
+ * source interface by interface index.
+ */
+ ip = interfaces;
+ while ((ip != NULL) && (if_nametoindex(ip->name) != ifindex))
+ ip = ip->next;
+ if (ip == NULL)
+ return ISC_R_NOTFOUND;
+ }
+#endif
+
+ if (bootp_packet_handler) {
+ ifrom.len = 4;
+ memcpy (ifrom.iabuf, &from.sin_addr, ifrom.len);
+
+ (*bootp_packet_handler) (ip, &u.packet, (unsigned)result,
+ from.sin_port, ifrom, &hfrom);
+ }
+
+ /* If there is buffered data, read again. This is for, e.g.,
+ bpf, which may return two packets at once. */
+ if (ip -> rbuf_offset != ip -> rbuf_len)
+ goto again;
+ return ISC_R_SUCCESS;
+}
+
+#ifdef DHCPv6
+isc_result_t
+got_one_v6(omapi_object_t *h) {
+ struct sockaddr_in6 from;
+ struct in6_addr to;
+ struct iaddr ifrom;
+ int result;
+ char buf[65536]; /* maximum size for a UDP packet is 65536 */
+ struct interface_info *ip;
+ int is_unicast;
+ unsigned int if_idx = 0;
+
+ if (h->type != dhcp_type_interface) {
+ return DHCP_R_INVALIDARG;
+ }
+ ip = (struct interface_info *)h;
+
+ result = receive_packet6(ip, (unsigned char *)buf, sizeof(buf),
+ &from, &to, &if_idx);
+ if (result < 0) {
+ log_error("receive_packet6() failed on %s: %m", ip->name);
+ return ISC_R_UNEXPECTED;
+ }
+
+ /* 0 is 'any' interface. */
+ if (if_idx == 0)
+ return ISC_R_NOTFOUND;
+
+ if (dhcpv6_packet_handler != NULL) {
+ /*
+ * If a packet is not multicast, we assume it is unicast.
+ */
+ if (IN6_IS_ADDR_MULTICAST(&to)) {
+ is_unicast = ISC_FALSE;
+ } else {
+ is_unicast = ISC_TRUE;
+ }
+
+ ifrom.len = 16;
+ memcpy(ifrom.iabuf, &from.sin6_addr, ifrom.len);
+
+ /* Seek forward to find the matching source interface. */
+ ip = interfaces;
+ while ((ip != NULL) && (if_nametoindex(ip->name) != if_idx))
+ ip = ip->next;
+
+ if (ip == NULL)
+ return ISC_R_NOTFOUND;
+
+ (*dhcpv6_packet_handler)(ip, buf,
+ result, from.sin6_port,
+ &ifrom, is_unicast);
+ }
+
+ return ISC_R_SUCCESS;
+}
+#endif /* DHCPv6 */
+
+isc_result_t dhcp_interface_set_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_typed_data_t *value)
+{
+ struct interface_info *interface;
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_interface)
+ return DHCP_R_INVALIDARG;
+ interface = (struct interface_info *)h;
+
+ if (!omapi_ds_strcmp (name, "name")) {
+ if ((value -> type == omapi_datatype_data ||
+ value -> type == omapi_datatype_string) &&
+ value -> u.buffer.len < sizeof interface -> name) {
+ memcpy (interface -> name,
+ value -> u.buffer.value,
+ value -> u.buffer.len);
+ interface -> name [value -> u.buffer.len] = 0;
+ } else
+ return DHCP_R_INVALIDARG;
+ return ISC_R_SUCCESS;
+ }
+
+ /* Try to find some inner object that can take the value. */
+ if (h -> inner && h -> inner -> type -> set_value) {
+ status = ((*(h -> inner -> type -> set_value))
+ (h -> inner, id, name, value));
+ if (status == ISC_R_SUCCESS || status == DHCP_R_UNCHANGED)
+ return status;
+ }
+
+ return ISC_R_NOTFOUND;
+}
+
+
+isc_result_t dhcp_interface_get_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_value_t **value)
+{
+ return ISC_R_NOTIMPLEMENTED;
+}
+
+isc_result_t dhcp_interface_destroy (omapi_object_t *h,
+ const char *file, int line)
+{
+ struct interface_info *interface;
+
+ if (h -> type != dhcp_type_interface)
+ return DHCP_R_INVALIDARG;
+ interface = (struct interface_info *)h;
+
+ if (interface -> ifp) {
+ dfree (interface -> ifp, file, line);
+ interface -> ifp = 0;
+ }
+ if (interface -> next)
+ interface_dereference (&interface -> next, file, line);
+ if (interface -> rbuf) {
+ dfree (interface -> rbuf, file, line);
+ interface -> rbuf = (unsigned char *)0;
+ }
+ if (interface -> client)
+ interface -> client = (struct client_state *)0;
+
+ if (interface -> shared_network)
+ omapi_object_dereference ((omapi_object_t **)
+ &interface -> shared_network, MDL);
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_interface_signal_handler (omapi_object_t *h,
+ const char *name, va_list ap)
+{
+ struct interface_info *ip, *interface;
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_interface)
+ return DHCP_R_INVALIDARG;
+ interface = (struct interface_info *)h;
+
+ /* If it's an update signal, see if the interface is dead right
+ now, or isn't known at all, and if that's the case, revive it. */
+ if (!strcmp (name, "update")) {
+ for (ip = dummy_interfaces; ip; ip = ip -> next)
+ if (ip == interface)
+ break;
+ if (ip && dhcp_interface_startup_hook)
+ return (*dhcp_interface_startup_hook) (ip);
+
+ for (ip = interfaces; ip; ip = ip -> next)
+ if (ip == interface)
+ break;
+ if (!ip && dhcp_interface_startup_hook)
+ return (*dhcp_interface_startup_hook) (ip);
+ }
+
+ /* Try to find some inner object that can take the value. */
+ if (h -> inner && h -> inner -> type -> signal_handler) {
+ status = ((*(h -> inner -> type -> signal_handler))
+ (h -> inner, name, ap));
+ if (status == ISC_R_SUCCESS)
+ return status;
+ }
+ return ISC_R_NOTFOUND;
+}
+
+isc_result_t dhcp_interface_stuff_values (omapi_object_t *c,
+ omapi_object_t *id,
+ omapi_object_t *h)
+{
+ struct interface_info *interface;
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_interface)
+ return DHCP_R_INVALIDARG;
+ interface = (struct interface_info *)h;
+
+ /* Write out all the values. */
+
+ status = omapi_connection_put_name (c, "state");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ if ((interface->flags & INTERFACE_REQUESTED) != 0)
+ status = omapi_connection_put_string (c, "up");
+ else
+ status = omapi_connection_put_string (c, "down");
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ /* Write out the inner object, if any. */
+ if (h -> inner && h -> inner -> type -> stuff_values) {
+ status = ((*(h -> inner -> type -> stuff_values))
+ (c, id, h -> inner));
+ if (status == ISC_R_SUCCESS)
+ return status;
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_interface_lookup (omapi_object_t **ip,
+ omapi_object_t *id,
+ omapi_object_t *ref)
+{
+ omapi_value_t *tv = (omapi_value_t *)0;
+ isc_result_t status;
+ struct interface_info *interface;
+
+ if (!ref)
+ return DHCP_R_NOKEYS;
+
+ /* First see if we were sent a handle. */
+ status = omapi_get_value_str (ref, id, "handle", &tv);
+ if (status == ISC_R_SUCCESS) {
+ status = omapi_handle_td_lookup (ip, tv -> value);
+
+ omapi_value_dereference (&tv, MDL);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ /* Don't return the object if the type is wrong. */
+ if ((*ip) -> type != dhcp_type_interface) {
+ omapi_object_dereference (ip, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+ }
+
+ /* Now look for an interface name. */
+ status = omapi_get_value_str (ref, id, "name", &tv);
+ if (status == ISC_R_SUCCESS) {
+ char *s;
+ unsigned len;
+ for (interface = interfaces; interface;
+ interface = interface -> next) {
+ s = memchr (interface -> name, 0, IFNAMSIZ);
+ if (s)
+ len = s - &interface -> name [0];
+ else
+ len = IFNAMSIZ;
+ if ((tv -> value -> u.buffer.len == len &&
+ !memcmp (interface -> name,
+ (char *)tv -> value -> u.buffer.value,
+ len)))
+ break;
+ }
+ if (!interface) {
+ for (interface = dummy_interfaces;
+ interface; interface = interface -> next) {
+ s = memchr (interface -> name, 0, IFNAMSIZ);
+ if (s)
+ len = s - &interface -> name [0];
+ else
+ len = IFNAMSIZ;
+ if ((tv -> value -> u.buffer.len == len &&
+ !memcmp (interface -> name,
+ (char *)
+ tv -> value -> u.buffer.value,
+ len)))
+ break;
+ }
+ }
+
+ omapi_value_dereference (&tv, MDL);
+ if (*ip && *ip != (omapi_object_t *)interface) {
+ omapi_object_dereference (ip, MDL);
+ return DHCP_R_KEYCONFLICT;
+ } else if (!interface) {
+ if (*ip)
+ omapi_object_dereference (ip, MDL);
+ return ISC_R_NOTFOUND;
+ } else if (!*ip)
+ omapi_object_reference (ip,
+ (omapi_object_t *)interface,
+ MDL);
+ }
+
+ /* If we get to here without finding an interface, no valid key was
+ specified. */
+ if (!*ip)
+ return DHCP_R_NOKEYS;
+ return ISC_R_SUCCESS;
+}
+
+/* actually just go discover the interface */
+isc_result_t dhcp_interface_create (omapi_object_t **lp,
+ omapi_object_t *id)
+{
+ struct interface_info *hp;
+ isc_result_t status;
+
+ hp = (struct interface_info *)0;
+ status = interface_allocate (&hp, MDL);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ hp -> flags = INTERFACE_REQUESTED;
+ status = interface_reference ((struct interface_info **)lp, hp, MDL);
+ interface_dereference (&hp, MDL);
+ return status;
+}
+
+isc_result_t dhcp_interface_remove (omapi_object_t *lp,
+ omapi_object_t *id)
+{
+ struct interface_info *interface, *ip, *last;
+
+ interface = (struct interface_info *)lp;
+
+ /* remove from interfaces */
+ last = 0;
+ for (ip = interfaces; ip; ip = ip -> next) {
+ if (ip == interface) {
+ if (last) {
+ interface_dereference (&last -> next, MDL);
+ if (ip -> next)
+ interface_reference (&last -> next,
+ ip -> next, MDL);
+ } else {
+ interface_dereference (&interfaces, MDL);
+ if (ip -> next)
+ interface_reference (&interfaces,
+ ip -> next, MDL);
+ }
+ if (ip -> next)
+ interface_dereference (&ip -> next, MDL);
+ break;
+ }
+ last = ip;
+ }
+ if (!ip)
+ return ISC_R_NOTFOUND;
+
+ /* add the interface to the dummy_interface list */
+ if (dummy_interfaces) {
+ interface_reference (&interface -> next,
+ dummy_interfaces, MDL);
+ interface_dereference (&dummy_interfaces, MDL);
+ }
+ interface_reference (&dummy_interfaces, interface, MDL);
+
+ /* do a DHCPRELEASE */
+ if (dhcp_interface_shutdown_hook)
+ (*dhcp_interface_shutdown_hook) (interface);
+
+ /* remove the io object */
+ omapi_unregister_io_object ((omapi_object_t *)interface);
+
+ switch(local_family) {
+#ifdef DHCPv6
+ case AF_INET6:
+ if_deregister6(interface);
+ break;
+#endif /* DHCPv6 */
+ case AF_INET:
+ default:
+ if_deregister_send(interface);
+ if_deregister_receive(interface);
+ break;
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+void interface_stash (struct interface_info *tptr)
+{
+ struct interface_info **vec;
+ int delta;
+
+ /* If the registerer didn't assign an index, assign one now. */
+ if (tptr -> index == -1) {
+ tptr -> index = interface_count++;
+ while (tptr -> index < interface_max &&
+ interface_vector [tptr -> index])
+ tptr -> index = interface_count++;
+ }
+
+ if (interface_max <= tptr -> index) {
+ delta = tptr -> index - interface_max + 10;
+ vec = dmalloc ((interface_max + delta) *
+ sizeof (struct interface_info *), MDL);
+ if (!vec)
+ return;
+ memset (&vec [interface_max], 0,
+ (sizeof (struct interface_info *)) * delta);
+ interface_max += delta;
+ if (interface_vector) {
+ memcpy (vec, interface_vector,
+ (interface_count *
+ sizeof (struct interface_info *)));
+ dfree (interface_vector, MDL);
+ }
+ interface_vector = vec;
+ }
+ interface_reference (&interface_vector [tptr -> index], tptr, MDL);
+ if (tptr -> index >= interface_count)
+ interface_count = tptr -> index + 1;
+#if defined (TRACING)
+ trace_interface_register (interface_trace, tptr);
+#endif
+}
+
+void interface_snorf (struct interface_info *tmp, int ir)
+{
+ tmp -> circuit_id = (u_int8_t *)tmp -> name;
+ tmp -> circuit_id_len = strlen (tmp -> name);
+ tmp -> remote_id = 0;
+ tmp -> remote_id_len = 0;
+ tmp -> flags = ir;
+ if (interfaces) {
+ interface_reference (&tmp -> next,
+ interfaces, MDL);
+ interface_dereference (&interfaces, MDL);
+ }
+ interface_reference (&interfaces, tmp, MDL);
+}
diff --git a/common/dispatch.c b/common/dispatch.c
new file mode 100644
index 0000000..c75a003
--- /dev/null
+++ b/common/dispatch.c
@@ -0,0 +1,435 @@
+/* dispatch.c
+
+ Network input dispatcher... */
+
+/*
+ * Copyright (c) 2004-2011,2013 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+
+#include <sys/time.h>
+
+struct timeout *timeouts;
+static struct timeout *free_timeouts;
+
+void set_time(TIME t)
+{
+ /* Do any outstanding timeouts. */
+ if (cur_tv . tv_sec != t) {
+ cur_tv . tv_sec = t;
+ cur_tv . tv_usec = 0;
+ process_outstanding_timeouts ((struct timeval *)0);
+ }
+}
+
+struct timeval *process_outstanding_timeouts (struct timeval *tvp)
+{
+ /* Call any expired timeouts, and then if there's
+ still a timeout registered, time out the select
+ call then. */
+ another:
+ if (timeouts) {
+ struct timeout *t;
+ if ((timeouts -> when . tv_sec < cur_tv . tv_sec) ||
+ ((timeouts -> when . tv_sec == cur_tv . tv_sec) &&
+ (timeouts -> when . tv_usec <= cur_tv . tv_usec))) {
+ t = timeouts;
+ timeouts = timeouts -> next;
+ (*(t -> func)) (t -> what);
+ if (t -> unref)
+ (*t -> unref) (&t -> what, MDL);
+ t -> next = free_timeouts;
+ free_timeouts = t;
+ goto another;
+ }
+ if (tvp) {
+ tvp -> tv_sec = timeouts -> when . tv_sec;
+ tvp -> tv_usec = timeouts -> when . tv_usec;
+ }
+ return tvp;
+ } else
+ return (struct timeval *)0;
+}
+
+/* Wait for packets to come in using select(). When one does, call
+ receive_packet to receive the packet and possibly strip hardware
+ addressing information from it, and then call through the
+ bootp_packet_handler hook to try to do something with it. */
+
+/*
+ * Use the DHCP timeout list as a place to store DHCP specific
+ * information, but use the ISC timer system to actually dispatch
+ * the events.
+ *
+ * There are several things that the DHCP timer code does that the
+ * ISC code doesn't:
+ * 1) It allows for negative times
+ * 2) The cancel arguments are different. The DHCP code uses the
+ * function and data to find the proper timer to cancel while the
+ * ISC code uses a pointer to the timer.
+ * 3) The DHCP code includes provision for incrementing and decrementing
+ * a reference counter associated with the data.
+ * The first one is fairly easy to fix but will take some time to go throuh
+ * the callers and update them. The second is also not all that difficult
+ * in concept - add a pointer to the appropriate structures to hold a pointer
+ * to the timer and use that. The complications arise in trying to ensure
+ * that all of the corner cases are covered. The last one is potentially
+ * more painful and requires more investigation.
+ *
+ * The plan is continue with the older DHCP calls and timer list. The
+ * calls will continue to manipulate the list but will also pass a
+ * timer to the ISC timer code for the actual dispatch. Later, if desired,
+ * we can go back and modify the underlying calls to use the ISC
+ * timer functions directly without requiring all of the code to change
+ * at the same time.
+ */
+
+void
+dispatch(void)
+{
+ isc_result_t status;
+
+ do {
+ status = isc_app_ctxrun(dhcp_gbl_ctx.actx);
+
+ /*
+ * isc_app_ctxrun can be stopped by receiving a
+ * signal. It will return ISC_R_RELOAD in that
+ * case. That is a normal behavior.
+ */
+
+ if (status == ISC_R_RELOAD) {
+ /*
+ * dhcp_set_control_state() will do the job.
+ * Note its first argument is ignored.
+ */
+ status = dhcp_set_control_state(server_shutdown,
+ server_shutdown);
+ if (status == ISC_R_SUCCESS)
+ status = ISC_R_RELOAD;
+ }
+ } while (status == ISC_R_RELOAD);
+
+ log_fatal ("Dispatch routine failed: %s -- exiting",
+ isc_result_totext (status));
+}
+
+void
+isclib_timer_callback(isc_task_t *taskp,
+ isc_event_t *eventp)
+{
+ struct timeout *t = (struct timeout *)eventp->ev_arg;
+ struct timeout *q, *r;
+
+ /* Get the current time... */
+ gettimeofday (&cur_tv, (struct timezone *)0);
+
+ /*
+ * Find the timeout on the dhcp list and remove it.
+ * As the list isn't ordered we search the entire list
+ */
+
+ r = NULL;
+ for (q = timeouts; q; q = q->next) {
+ if (q == t) {
+ if (r)
+ r->next = q->next;
+ else
+ timeouts = q->next;
+ break;
+ }
+ r = q;
+ }
+
+ /*
+ * The timer should always be on the list. If it is we do
+ * the work and detach the timer block, if not we log an error.
+ * In both cases we attempt free the ISC event and continue
+ * processing.
+ */
+
+ if (q != NULL) {
+ /* call the callback function */
+ (*(q->func)) (q->what);
+ if (q->unref) {
+ (*q->unref) (&q->what, MDL);
+ }
+ q->next = free_timeouts;
+ isc_timer_detach(&q->isc_timeout);
+ free_timeouts = q;
+ } else {
+ /*
+ * Hmm, we should clean up the timer structure but aren't
+ * sure about the pointer to the timer block we got so
+ * don't try to - may change this to a log_fatal
+ */
+ log_error("Error finding timer structure");
+ }
+
+ isc_event_free(&eventp);
+ return;
+}
+
+/* maximum value for usec */
+#define USEC_MAX 1000000
+#define DHCP_SEC_MAX 0xFFFFFFFF
+
+void add_timeout (when, where, what, ref, unref)
+ struct timeval *when;
+ void (*where) (void *);
+ void *what;
+ tvref_t ref;
+ tvunref_t unref;
+{
+ struct timeout *t, *q;
+ int usereset = 0;
+ isc_result_t status;
+ int64_t sec;
+ int usec;
+ isc_interval_t interval;
+ isc_time_t expires;
+
+ /* See if this timeout supersedes an existing timeout. */
+ t = (struct timeout *)0;
+ for (q = timeouts; q; q = q->next) {
+ if ((where == NULL || q->func == where) &&
+ q->what == what) {
+ if (t)
+ t->next = q->next;
+ else
+ timeouts = q->next;
+ usereset = 1;
+ break;
+ }
+ t = q;
+ }
+
+ /* If we didn't supersede a timeout, allocate a timeout
+ structure now. */
+ if (!q) {
+ if (free_timeouts) {
+ q = free_timeouts;
+ free_timeouts = q->next;
+ } else {
+ q = ((struct timeout *)
+ dmalloc(sizeof(struct timeout), MDL));
+ if (!q) {
+ log_fatal("add_timeout: no memory!");
+ }
+ }
+ memset(q, 0, sizeof *q);
+ q->func = where;
+ q->ref = ref;
+ q->unref = unref;
+ if (q->ref)
+ (*q->ref)(&q->what, what, MDL);
+ else
+ q->what = what;
+ }
+
+ /*
+ * The value passed in is a time from an epoch but we need a relative
+ * time so we need to do some math to try and recover the period.
+ * This is complicated by the fact that not all of the calls cared
+ * about the usec value, if it's zero we assume the caller didn't care.
+ *
+ * The ISC timer library doesn't seem to like negative values
+ * and can't accept any values above 4G-1 seconds so we limit
+ * the values to 0 <= value < 4G-1. We do it before
+ * checking the trace option so that both the trace code and
+ * the working code use the same values.
+ */
+
+ sec = when->tv_sec - cur_tv.tv_sec;
+ usec = when->tv_usec - cur_tv.tv_usec;
+
+ if ((when->tv_usec != 0) && (usec < 0)) {
+ sec--;
+ usec += USEC_MAX;
+ }
+
+ if (sec < 0) {
+ sec = 0;
+ usec = 0;
+ } else if (sec > DHCP_SEC_MAX) {
+ log_error("Timeout requested too large "
+ "reducing to 2^^32-1");
+ sec = DHCP_SEC_MAX;
+ usec = 0;
+ } else if (usec < 0) {
+ usec = 0;
+ } else if (usec >= USEC_MAX) {
+ usec = USEC_MAX - 1;
+ }
+
+ /*
+ * This is necessary for the tracing code but we put it
+ * here in case we want to compare timing information
+ * for some reason, like debugging.
+ */
+ q->when.tv_sec = cur_tv.tv_sec + (sec & DHCP_SEC_MAX);
+ q->when.tv_usec = usec;
+
+#if defined (TRACING)
+ if (trace_playback()) {
+ /*
+ * If we are doing playback we need to handle the timers
+ * within this code rather than having the isclib handle
+ * them for us. We need to keep the timer list in order
+ * to allow us to find the ones to timeout.
+ *
+ * By using a different timer setup in the playback we may
+ * have variations between the orginal and the playback but
+ * it's the best we can do for now.
+ */
+
+ /* Beginning of list? */
+ if (!timeouts || (timeouts->when.tv_sec > q-> when.tv_sec) ||
+ ((timeouts->when.tv_sec == q->when.tv_sec) &&
+ (timeouts->when.tv_usec > q->when.tv_usec))) {
+ q->next = timeouts;
+ timeouts = q;
+ return;
+ }
+
+ /* Middle of list? */
+ for (t = timeouts; t->next; t = t->next) {
+ if ((t->next->when.tv_sec > q->when.tv_sec) ||
+ ((t->next->when.tv_sec == q->when.tv_sec) &&
+ (t->next->when.tv_usec > q->when.tv_usec))) {
+ q->next = t->next;
+ t->next = q;
+ return;
+ }
+ }
+
+ /* End of list. */
+ t->next = q;
+ q->next = (struct timeout *)0;
+ return;
+ }
+#endif
+ /*
+ * Don't bother sorting the DHCP list, just add it to the front.
+ * Eventually the list should be removed as we migrate the callers
+ * to the native ISC timer functions, if it becomes a performance
+ * problem before then we may need to order the list.
+ */
+ q->next = timeouts;
+ timeouts = q;
+
+ isc_interval_set(&interval, sec & DHCP_SEC_MAX, usec * 1000);
+ status = isc_time_nowplusinterval(&expires, &interval);
+ if (status != ISC_R_SUCCESS) {
+ /*
+ * The system time function isn't happy or returned
+ * a value larger than isc_time_t can hold.
+ */
+ log_fatal("Unable to set up timer: %s",
+ isc_result_totext(status));
+ }
+
+ if (usereset == 0) {
+ status = isc_timer_create(dhcp_gbl_ctx.timermgr,
+ isc_timertype_once, &expires,
+ NULL, dhcp_gbl_ctx.task,
+ isclib_timer_callback,
+ (void *)q, &q->isc_timeout);
+ } else {
+ status = isc_timer_reset(q->isc_timeout,
+ isc_timertype_once, &expires,
+ NULL, 0);
+ }
+
+ /* If it fails log an error and die */
+ if (status != ISC_R_SUCCESS) {
+ log_fatal("Unable to add timeout to isclib\n");
+ }
+
+ return;
+}
+
+void cancel_timeout (where, what)
+ void (*where) (void *);
+ void *what;
+{
+ struct timeout *t, *q;
+
+ /* Look for this timeout on the list, and unlink it if we find it. */
+ t = (struct timeout *)0;
+ for (q = timeouts; q; q = q -> next) {
+ if (q->func == where && q->what == what) {
+ if (t)
+ t->next = q->next;
+ else
+ timeouts = q->next;
+ break;
+ }
+ t = q;
+ }
+
+ /*
+ * If we found the timeout, cancel it and put it on the free list.
+ * The TRACING stuff is ugly but we don't add a timer when doing
+ * playback so we don't want to remove them then either.
+ */
+ if (q) {
+#if defined (TRACING)
+ if (!trace_playback()) {
+#endif
+ isc_timer_detach(&q->isc_timeout);
+#if defined (TRACING)
+ }
+#endif
+
+ if (q->unref)
+ (*q->unref) (&q->what, MDL);
+ q->next = free_timeouts;
+ free_timeouts = q;
+ }
+}
+
+#if defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+void cancel_all_timeouts ()
+{
+ struct timeout *t, *n;
+ for (t = timeouts; t; t = n) {
+ n = t->next;
+ isc_timer_detach(&t->isc_timeout);
+ if (t->unref && t->what)
+ (*t->unref) (&t->what, MDL);
+ t->next = free_timeouts;
+ free_timeouts = t;
+ }
+}
+
+void relinquish_timeouts ()
+{
+ struct timeout *t, *n;
+ for (t = free_timeouts; t; t = n) {
+ n = t->next;
+ dfree(t, MDL);
+ }
+}
+#endif
diff --git a/common/dlpi.c b/common/dlpi.c
new file mode 100644
index 0000000..c34adc3
--- /dev/null
+++ b/common/dlpi.c
@@ -0,0 +1,1414 @@
+/* dlpi.c
+
+ Data Link Provider Interface (DLPI) network interface code. */
+
+/*
+ * Copyright (c) 2009-2011,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004,2007 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ * This software was written for Internet Systems Consortium
+ * by Eric James Negaard, <lmdejn@lmd.ericsson.se>. To learn more about
+ * Internet Systems Consortium, see ``https://www.isc.org''.
+ *
+ * Joost Mulders has also done considerable work in debugging the DLPI API
+ * support on Solaris and getting this code to work properly on a variety
+ * of different Solaris platforms.
+ */
+
+/*
+ * Based largely in part to the existing NIT code in nit.c.
+ *
+ * This code has been developed and tested on sparc-based machines running
+ * SunOS 5.5.1, with le and hme network interfaces. It should be pretty
+ * generic, though.
+ */
+
+/*
+ * Implementation notes:
+ *
+ * I first tried to write this code to the "vanilla" DLPI 2.0 API.
+ * It worked on a Sun Ultra-1 with a hme interface, but didn't work
+ * on Sun SparcStation 5's with "le" interfaces (the packets sent out
+ * via dlpiunitdatareq contained an Ethernet type of 0x0000 instead
+ * of the expected 0x0800).
+ *
+ * Therefore I added the "DLPI_RAW" code which is a Sun extension to
+ * the DLPI standard. This code works on both of the above machines.
+ * This is configurable in the OS-dependent include file by defining
+ * USE_DLPI_RAW.
+ *
+ * It quickly became apparant that I should also use the "pfmod"
+ * STREAMS module to cut down on the amount of user level packet
+ * processing. I don't know how widely available "pfmod" is, so it's
+ * use is conditionally included. This is configurable in the
+ * OS-dependent include file by defining USE_DLPI_PFMOD.
+ *
+ * A major quirk on the Sun's at least, is that no packets seem to get
+ * sent out the interface until six seconds after the interface is
+ * first "attached" to [per system reboot] (it's actually from when
+ * the interface is attached, not when it is plumbed, so putting a
+ * sleep into the dhclient-script at PREINIT time doesn't help). I
+ * HAVE tried, without success to poll the fd to see when it is ready
+ * for writing. This doesn't help at all. If the sleeps are not done,
+ * the initial DHCPREQUEST or DHCPDISCOVER never gets sent out, so
+ * I've put them here, when register_send and register_receive are
+ * called (split up into two three-second sleeps between the notices,
+ * so that it doesn't seem like so long when you're watching :-). The
+ * amount of time to sleep is configurable in the OS-dependent include
+ * file by defining DLPI_FIRST_SEND_WAIT to be the number of seconds
+ * to sleep.
+ */
+
+/*
+ * The Open Group Technical Standard can be found here:
+ * http://www.opengroup.org/onlinepubs/009618899/index.htm
+ *
+ * The HP DLPI Programmer's Guide can be found here:
+ * http://docs.hp.com/en/B2355-90139/index.html
+ */
+
+#include "dhcpd.h"
+
+#if defined (USE_DLPI_SEND) || defined (USE_DLPI_RECEIVE) || \
+ defined(USE_DLPI_HWADDR)
+
+# include <sys/ioctl.h>
+# include <sys/time.h>
+# include <sys/dlpi.h>
+# include <stropts.h>
+# ifdef USE_DLPI_PFMOD
+# include <sys/pfmod.h>
+# endif
+#include <poll.h>
+#include <errno.h>
+
+# include <netinet/in_systm.h>
+# include "includes/netinet/ip.h"
+# include "includes/netinet/udp.h"
+# include "includes/netinet/if_ether.h"
+
+# ifdef USE_DLPI_PFMOD
+# ifdef USE_DLPI_RAW
+# define DLPI_MODNAME "DLPI+RAW+PFMOD"
+# else
+# define DLPI_MODNAME "DLPI+PFMOD"
+# endif
+# else
+# ifdef USE_DLPI_RAW
+# define DLPI_MODNAME "DLPI+RAW"
+# else
+# define DLPI_MODNAME "DLPI"
+# endif
+# endif
+
+# ifndef ABS
+# define ABS(x) ((x) >= 0 ? (x) : 0-(x))
+# endif
+
+#if defined(USE_DLPI_PFMOD) || defined(USE_DLPI_RAW)
+static int strioctl (int fd, int cmd, int timeout, int len, char *dp);
+#endif
+
+#define DLPI_MAXDLBUF 8192 /* Buffer size */
+#define DLPI_MAXDLADDR 1024 /* Max address size */
+#define DLPI_DEVDIR "/dev/" /* Device directory */
+
+static int dlpiopen(const char *ifname);
+static int dlpiunit (char *ifname);
+static int dlpiinforeq (int fd);
+static int dlpiphysaddrreq (int fd, unsigned long addrtype);
+static int dlpiattachreq (int fd, unsigned long ppa);
+static int dlpibindreq (int fd, unsigned long sap, unsigned long max_conind,
+ unsigned long service_mode, unsigned long conn_mgmt,
+ unsigned long xidtest);
+#if defined(UNUSED_DLPI_INTERFACE)
+/* These functions are unused at present, but may be used at a later date.
+ * defined out to avoid compiler warnings about unused static functions.
+ */
+static int dlpidetachreq (int fd);
+static int dlpiunbindreq (int fd);
+#endif
+static int dlpiokack (int fd, char *bufp);
+static int dlpiinfoack (int fd, char *bufp);
+static int dlpiphysaddrack (int fd, char *bufp);
+static int dlpibindack (int fd, char *bufp);
+#if defined(USE_DLPI_SEND) || defined(USE_DLPI_RECEIVE)
+/* These functions are not used if we're only sourcing the get_hw_addr()
+ * function (for USE_SOCKETS).
+ */
+static int dlpiunitdatareq (int fd, unsigned char *addr, int addrlen,
+ unsigned long minpri, unsigned long maxpri,
+ unsigned char *data, int datalen);
+static int dlpiunitdataind (int fd,
+ unsigned char *dstaddr,
+ unsigned long *dstaddrlen,
+ unsigned char *srcaddr,
+ unsigned long *srcaddrlen,
+ unsigned long *grpaddr,
+ unsigned char *data,
+ int datalen);
+#endif /* !USE_DLPI_HWADDR: USE_DLPI_SEND || USE_DLPI_RECEIVE */
+static int expected (unsigned long prim, union DL_primitives *dlp,
+ int msgflags);
+static int strgetmsg (int fd, struct strbuf *ctlp, struct strbuf *datap,
+ int *flagsp, char *caller);
+
+/* Reinitializes the specified interface after an address change. This
+ is not required for packet-filter APIs. */
+
+#ifdef USE_DLPI_SEND
+void if_reinitialize_send (info)
+ struct interface_info *info;
+{
+}
+#endif
+
+#ifdef USE_DLPI_RECEIVE
+void if_reinitialize_receive (info)
+ struct interface_info *info;
+{
+}
+#endif
+
+/* Called by get_interface_list for each interface that's discovered.
+ Opens a packet filter for each interface and adds it to the select
+ mask. */
+
+int if_register_dlpi (info)
+ struct interface_info *info;
+{
+ int sock;
+ int unit;
+ long buf [DLPI_MAXDLBUF];
+ union DL_primitives *dlp;
+
+ dlp = (union DL_primitives *)buf;
+
+ /* Open a DLPI device */
+ if ((sock = dlpiopen (info -> name)) < 0) {
+ log_fatal ("Can't open DLPI device for %s: %m", info -> name);
+ }
+
+ /*
+ * Submit a DL_INFO_REQ request, to find the dl_mac_type and
+ * dl_provider_style
+ */
+ if (dlpiinforeq(sock) < 0 || dlpiinfoack(sock, (char *)buf) < 0) {
+ log_fatal ("Can't get DLPI MAC type for %s: %m", info -> name);
+ } else {
+ switch (dlp -> info_ack.dl_mac_type) {
+ case DL_CSMACD: /* IEEE 802.3 */
+ case DL_ETHER:
+ info -> hw_address.hbuf [0] = HTYPE_ETHER;
+ break;
+ /* adding token ring 5/1999 - mayer@ping.at */
+ case DL_TPR:
+ info -> hw_address.hbuf [0] = HTYPE_IEEE802;
+ break;
+ case DL_FDDI:
+ info -> hw_address.hbuf [0] = HTYPE_FDDI;
+ break;
+ default:
+ log_fatal("%s: unsupported DLPI MAC type %lu", info->name,
+ (unsigned long)dlp->info_ack.dl_mac_type);
+ break;
+ }
+ /*
+ * copy the sap length and broadcast address of this interface
+ * to interface_info. This fixes nothing but seemed nicer than to
+ * assume -2 and ffffff.
+ */
+ info -> dlpi_sap_length = dlp -> info_ack.dl_sap_length;
+ info -> dlpi_broadcast_addr.hlen =
+ dlp -> info_ack.dl_brdcst_addr_length;
+ memcpy (info -> dlpi_broadcast_addr.hbuf,
+ (char *)dlp + dlp -> info_ack.dl_brdcst_addr_offset,
+ dlp -> info_ack.dl_brdcst_addr_length);
+ }
+
+ if (dlp -> info_ack.dl_provider_style == DL_STYLE2) {
+ /*
+ * Attach to the device. If this fails, the device
+ * does not exist.
+ */
+ unit = dlpiunit (info -> name);
+
+ if (dlpiattachreq (sock, unit) < 0
+ || dlpiokack (sock, (char *)buf) < 0) {
+ log_fatal ("Can't attach DLPI device for %s: %m", info -> name);
+ }
+ }
+
+ /*
+ * Bind to the IP service access point (SAP), connectionless (CLDLS).
+ */
+ if (dlpibindreq (sock, ETHERTYPE_IP, 0, DL_CLDLS, 0, 0) < 0
+ || dlpibindack (sock, (char *)buf) < 0) {
+ log_fatal ("Can't bind DLPI device for %s: %m", info -> name);
+ }
+
+ /*
+ * Submit a DL_PHYS_ADDR_REQ request, to find
+ * the hardware address
+ */
+ if (dlpiphysaddrreq (sock, DL_CURR_PHYS_ADDR) < 0
+ || dlpiphysaddrack (sock, (char *)buf) < 0) {
+ log_fatal ("Can't get DLPI hardware address for %s: %m",
+ info -> name);
+ }
+
+ info -> hw_address.hlen = dlp -> physaddr_ack.dl_addr_length + 1;
+ memcpy (&info -> hw_address.hbuf [1],
+ (char *)buf + dlp -> physaddr_ack.dl_addr_offset,
+ dlp -> physaddr_ack.dl_addr_length);
+
+#ifdef USE_DLPI_RAW
+ if (strioctl (sock, DLIOCRAW, INFTIM, 0, 0) < 0) {
+ log_fatal ("Can't set DLPI RAW mode for %s: %m",
+ info -> name);
+ }
+#endif
+
+#ifdef USE_DLPI_PFMOD
+ if (ioctl (sock, I_PUSH, "pfmod") < 0) {
+ log_fatal ("Can't push packet filter onto DLPI for %s: %m",
+ info -> name);
+ }
+#endif
+
+ return sock;
+}
+
+#if defined(USE_DLPI_PFMOD) || defined(USE_DLPI_RAW)
+static int
+strioctl (fd, cmd, timeout, len, dp)
+int fd;
+int cmd;
+int timeout;
+int len;
+char *dp;
+{
+ struct strioctl sio;
+ int rslt;
+
+ sio.ic_cmd = cmd;
+ sio.ic_timout = timeout;
+ sio.ic_len = len;
+ sio.ic_dp = dp;
+
+ if ((rslt = ioctl (fd, I_STR, &sio)) < 0) {
+ return rslt;
+ } else {
+ return sio.ic_len;
+ }
+}
+#endif /* USE_DPI_PFMOD || USE_DLPI_RAW */
+
+#ifdef USE_DLPI_SEND
+void if_register_send (info)
+ struct interface_info *info;
+{
+ /* If we're using the DLPI API for sending and receiving,
+ we don't need to register this interface twice. */
+#ifndef USE_DLPI_RECEIVE
+# ifdef USE_DLPI_PFMOD
+ struct packetfilt pf;
+# endif
+
+ info -> wfdesc = if_register_dlpi (info);
+
+# ifdef USE_DLPI_PFMOD
+ /* Set up an PFMOD filter that rejects everything... */
+ pf.Pf_Priority = 0;
+ pf.Pf_FilterLen = 1;
+ pf.Pf_Filter [0] = ENF_PUSHZERO;
+
+ /* Install the filter */
+ if (strioctl (info -> wfdesc, PFIOCSETF, INFTIM,
+ sizeof (pf), (char *)&pf) < 0) {
+ log_fatal ("Can't set PFMOD send filter on %s: %m", info -> name);
+ }
+
+# endif /* USE_DLPI_PFMOD */
+#else /* !defined (USE_DLPI_RECEIVE) */
+ /*
+ * If using DLPI for both send and receive, simply re-use
+ * the read file descriptor that was set up earlier.
+ */
+ info -> wfdesc = info -> rfdesc;
+#endif
+
+ if (!quiet_interface_discovery)
+ log_info ("Sending on DLPI/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+
+#ifdef DLPI_FIRST_SEND_WAIT
+/* See the implementation notes at the beginning of this file */
+# ifdef USE_DLPI_RECEIVE
+ sleep (DLPI_FIRST_SEND_WAIT - (DLPI_FIRST_SEND_WAIT / 2));
+# else
+ sleep (DLPI_FIRST_SEND_WAIT);
+# endif
+#endif
+}
+
+void if_deregister_send (info)
+ struct interface_info *info;
+{
+ /* If we're using the DLPI API for sending and receiving,
+ we don't need to register this interface twice. */
+#ifndef USE_DLPI_RECEIVE
+ close (info -> wfdesc);
+#endif
+ info -> wfdesc = -1;
+
+ if (!quiet_interface_discovery)
+ log_info ("Disabling output on DLPI/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+#endif /* USE_DLPI_SEND */
+
+#ifdef USE_DLPI_RECEIVE
+/* Packet filter program...
+ XXX Changes to the filter program may require changes to the constant
+ offsets used in if_register_send to patch the NIT program! XXX */
+
+void if_register_receive (info)
+ struct interface_info *info;
+{
+#ifdef USE_DLPI_PFMOD
+ struct packetfilt pf;
+ struct ip iphdr;
+ u_int16_t offset;
+#endif
+
+ /* Open a DLPI device and hang it on this interface... */
+ info -> rfdesc = if_register_dlpi (info);
+
+#ifdef USE_DLPI_PFMOD
+ /* Set up the PFMOD filter program. */
+ /* XXX Unlike the BPF filter program, this one won't work if the
+ XXX IP packet is fragmented or if there are options on the IP
+ XXX header. */
+ pf.Pf_Priority = 0;
+ pf.Pf_FilterLen = 0;
+
+#if defined (USE_DLPI_RAW)
+# define ETHER_H_PREFIX (14) /* sizeof (ethernet_header) */
+ /*
+ * ethertype == ETHERTYPE_IP
+ */
+ offset = 12;
+ pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHWORD + (offset / 2);
+ pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT | ENF_CAND;
+ pf.Pf_Filter [pf.Pf_FilterLen++] = htons (ETHERTYPE_IP);
+# else
+# define ETHER_H_PREFIX (0)
+# endif /* USE_DLPI_RAW */
+ /*
+ * The packets that will be received on this file descriptor
+ * will be IP packets (due to the SAP that was specified in
+ * the dlbind call). There will be no ethernet header.
+ * Therefore, setup the packet filter to check the protocol
+ * field for UDP, and the destination port number equal
+ * to the local port. All offsets are relative to the start
+ * of an IP packet.
+ */
+
+ /*
+ * BOOTPS destination port
+ */
+ offset = ETHER_H_PREFIX + sizeof (iphdr) + sizeof (u_int16_t);
+ pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHWORD + (offset / 2);
+ pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT | ENF_CAND;
+ pf.Pf_Filter [pf.Pf_FilterLen++] = local_port;
+
+ /*
+ * protocol should be udp. this is a byte compare, test for
+ * endianess.
+ */
+ offset = ETHER_H_PREFIX + ((u_int8_t *)&(iphdr.ip_p) - (u_int8_t *)&iphdr);
+ pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHWORD + (offset / 2);
+ pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT | ENF_AND;
+ pf.Pf_Filter [pf.Pf_FilterLen++] = htons (0x00FF);
+ pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT | ENF_CAND;
+ pf.Pf_Filter [pf.Pf_FilterLen++] = htons (IPPROTO_UDP);
+
+ /* Install the filter... */
+ if (strioctl (info -> rfdesc, PFIOCSETF, INFTIM,
+ sizeof (pf), (char *)&pf) < 0) {
+ log_fatal ("Can't set PFMOD receive filter on %s: %m", info -> name);
+ }
+#endif /* USE_DLPI_PFMOD */
+
+ if (!quiet_interface_discovery)
+ log_info ("Listening on DLPI/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+
+#ifdef DLPI_FIRST_SEND_WAIT
+/* See the implementation notes at the beginning of this file */
+# ifdef USE_DLPI_SEND
+ sleep (DLPI_FIRST_SEND_WAIT / 2);
+# else
+ sleep (DLPI_FIRST_SEND_WAIT);
+# endif
+#endif
+}
+
+void if_deregister_receive (info)
+ struct interface_info *info;
+{
+ /* If we're using the DLPI API for sending and receiving,
+ we don't need to register this interface twice. */
+#ifndef USE_DLPI_SEND
+ close (info -> rfdesc);
+#endif
+ info -> rfdesc = -1;
+
+ if (!quiet_interface_discovery)
+ log_info ("Disabling input on DLPI/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+#endif /* USE_DLPI_RECEIVE */
+
+#ifdef USE_DLPI_SEND
+ssize_t send_packet (interface, packet, raw, len, from, to, hto)
+ struct interface_info *interface;
+ struct packet *packet;
+ struct dhcp_packet *raw;
+ size_t len;
+ struct in_addr from;
+ struct sockaddr_in *to;
+ struct hardware *hto;
+{
+#ifdef USE_DLPI_RAW
+ double hh [32];
+ int fudge;
+#endif
+ double ih [1536 / sizeof (double)];
+ unsigned char *dbuf = (unsigned char *)ih;
+ unsigned dbuflen;
+ unsigned char dstaddr [DLPI_MAXDLADDR];
+ unsigned addrlen;
+ int result;
+
+ if (!strcmp (interface -> name, "fallback"))
+ return send_fallback (interface, packet, raw,
+ len, from, to, hto);
+
+ if (hto == NULL && interface->anycast_mac_addr.hlen)
+ hto = &interface->anycast_mac_addr;
+
+ dbuflen = 0;
+
+ /* Assemble the headers... */
+#ifdef USE_DLPI_RAW
+ assemble_hw_header (interface, (unsigned char *)hh, &dbuflen, hto);
+ if (dbuflen > sizeof hh)
+ log_fatal ("send_packet: hh buffer too small.\n");
+ fudge = dbuflen % 4; /* IP header must be word-aligned. */
+ memcpy (dbuf + fudge, (unsigned char *)hh, dbuflen);
+ dbuflen += fudge;
+#endif
+ assemble_udp_ip_header (interface, dbuf, &dbuflen, from.s_addr,
+ to -> sin_addr.s_addr, to -> sin_port,
+ (unsigned char *)raw, len);
+
+ /* Copy the data into the buffer (yuk). */
+ memcpy (dbuf + dbuflen, raw, len);
+ dbuflen += len;
+
+#ifdef USE_DLPI_RAW
+ result = write (interface -> wfdesc, dbuf + fudge, dbuflen - fudge);
+#else
+
+ /*
+ * Setup the destination address (DLSAP) in dstaddr
+ *
+ * If sap_length < 0 we must deliver the DLSAP as phys+sap.
+ * If sap_length > 0 we must deliver the DLSAP as sap+phys.
+ *
+ * sap = Service Access Point == ETHERTYPE_IP
+ * sap + datalink address is called DLSAP in dlpi speak.
+ */
+ { /* ENCODE DLSAP */
+ unsigned char phys [DLPI_MAXDLADDR];
+ unsigned char sap [4];
+ int sap_len = interface -> dlpi_sap_length;
+ int phys_len = interface -> hw_address.hlen - 1;
+
+ /* sap = htons (ETHERTYPE_IP) kludge */
+ memset (sap, 0, sizeof (sap));
+# if (BYTE_ORDER == LITTLE_ENDIAN)
+ sap [0] = 0x00;
+ sap [1] = 0x08;
+# else
+ sap [0] = 0x08;
+ sap [1] = 0x00;
+# endif
+
+ if (hto && hto -> hlen == interface -> hw_address.hlen)
+ memcpy ( phys, (char *) &hto -> hbuf [1], phys_len);
+ else
+ memcpy ( phys, interface -> dlpi_broadcast_addr.hbuf,
+ interface -> dlpi_broadcast_addr.hlen);
+
+ if (sap_len < 0) {
+ memcpy ( dstaddr, phys, phys_len);
+ memcpy ( (char *) &dstaddr [phys_len], sap, ABS (sap_len));
+ }
+ else {
+ memcpy ( dstaddr, (void *) sap, sap_len);
+ memcpy ( (char *) &dstaddr [sap_len], phys, phys_len);
+ }
+ addrlen = phys_len + ABS (sap_len);
+ } /* ENCODE DLSAP */
+
+ result = dlpiunitdatareq (interface -> wfdesc, dstaddr, addrlen,
+ 0, 0, dbuf, dbuflen);
+#endif /* USE_DLPI_RAW */
+ if (result < 0)
+ log_error ("send_packet: %m");
+ return result;
+}
+#endif /* USE_DLPI_SEND */
+
+#ifdef USE_DLPI_RECEIVE
+ssize_t receive_packet (interface, buf, len, from, hfrom)
+ struct interface_info *interface;
+ unsigned char *buf;
+ size_t len;
+ struct sockaddr_in *from;
+ struct hardware *hfrom;
+{
+ unsigned char dbuf [1536];
+ unsigned char srcaddr [DLPI_MAXDLADDR];
+ unsigned long srcaddrlen;
+ int length = 0;
+ int offset = 0;
+ int bufix = 0;
+ unsigned paylen;
+
+#ifdef USE_DLPI_RAW
+ length = read (interface -> rfdesc, dbuf, sizeof (dbuf));
+#else
+ length = dlpiunitdataind (interface -> rfdesc, (unsigned char *)NULL,
+ (unsigned long *)NULL, srcaddr, &srcaddrlen,
+ (unsigned long *)NULL, dbuf, sizeof (dbuf));
+#endif
+
+ if (length <= 0) {
+ log_error("receive_packet: %m");
+ return length;
+ }
+
+# if !defined (USE_DLPI_RAW)
+ /*
+ * Copy the sender's hw address into hfrom
+ * If sap_len < 0 the DLSAP is as phys+sap.
+ * If sap_len > 0 the DLSAP is as sap+phys.
+ *
+ * sap is discarded here.
+ */
+ { /* DECODE DLSAP */
+ int sap_len = interface -> dlpi_sap_length;
+ int phys_len = interface -> hw_address.hlen - 1;
+
+ if (hfrom && (srcaddrlen == ABS (sap_len) + phys_len )) {
+ hfrom -> hbuf [0] = interface -> hw_address.hbuf [0];
+ hfrom -> hlen = interface -> hw_address.hlen;
+
+ if (sap_len < 0) {
+ memcpy ((char *) &hfrom -> hbuf [1], srcaddr, phys_len);
+ }
+ else {
+ memcpy((char *)&hfrom->hbuf[1], srcaddr + sap_len, phys_len);
+ }
+ }
+ else if (hfrom) {
+ memset (hfrom, '\0', sizeof *hfrom);
+ }
+ } /* DECODE_DLSAP */
+
+# endif /* !defined (USE_DLPI_RAW) */
+
+ /* Decode the IP and UDP headers... */
+ bufix = 0;
+#ifdef USE_DLPI_RAW
+ /* Decode the physical header... */
+ offset = decode_hw_header (interface, dbuf, bufix, hfrom);
+
+ /* If a physical layer checksum failed (dunno of any
+ physical layer that supports this, but WTH), skip this
+ packet. */
+ if (offset < 0) {
+ return 0;
+ }
+ bufix += offset;
+ length -= offset;
+#endif
+ offset = decode_udp_ip_header (interface, dbuf, bufix,
+ from, length, &paylen, 1);
+
+ /*
+ * If the IP or UDP checksum was bad, skip the packet...
+ *
+ * Note: this happens all the time when writing packets via the
+ * fallback socket. The packet received by streams does not have
+ * the IP or UDP checksums filled in, as those are calculated by
+ * the hardware.
+ */
+ if (offset < 0) {
+ return 0;
+ }
+
+ bufix += offset;
+ length -= offset;
+
+ if (length < paylen)
+ log_fatal("Internal inconsistency at %s:%d.", MDL);
+
+ /* Copy out the data in the packet... */
+ memcpy(buf, &dbuf [bufix], paylen);
+ return paylen;
+}
+#endif
+
+/* Common DLPI routines ...
+ *
+ * Written by Eric James Negaard, <lmdejn@lmd.ericsson.se>
+ *
+ * Based largely in part to the example code contained in the document
+ * "How to Use the STREAMS Data Link Provider Interface (DLPI)", written
+ * by Neal Nuckolls of SunSoft Internet Engineering.
+ *
+ * This code has been developed and tested on sparc-based machines running
+ * SunOS 5.5.1, with le and hme network interfaces. It should be pretty
+ * generic, though.
+ *
+ * The usual disclaimers apply. This code works for me. Don't blame me
+ * if it makes your machine or network go down in flames. That taken
+ * into consideration, use this code as you wish. If you make usefull
+ * modifications I'd appreciate hearing about it.
+ */
+
+#define DLPI_MAXWAIT 15 /* Max timeout */
+
+
+/*
+ * Parse an interface name and extract the unit number
+ */
+
+static int dlpiunit (ifname)
+ char *ifname;
+{
+ char *cp;
+ int unit;
+
+ if (!ifname) {
+ return 0;
+ }
+
+ /* Advance to the end of the name */
+ cp = ifname;
+ while (*cp) cp++;
+ /* Back up to the start of the first digit */
+ while ((*(cp-1) >= '0' && *(cp-1) <= '9') || *(cp - 1) == ':') cp--;
+
+ /* Convert the unit number */
+ unit = 0;
+ while (*cp >= '0' && *cp <= '9') {
+ unit *= 10;
+ unit += (*cp++ - '0');
+ }
+
+ return unit;
+}
+
+/*
+ * dlpiopen - open the DLPI device for a given interface name
+ */
+static int
+dlpiopen(const char *ifname) {
+ char devname [50];
+ char *dp;
+ const char *cp, *ep;
+
+ if (!ifname) {
+ return -1;
+ }
+
+ /* Open a DLPI device */
+ if (*ifname == '/') {
+ dp = devname;
+ } else {
+ /* Prepend the device directory */
+ memcpy (devname, DLPI_DEVDIR, strlen (DLPI_DEVDIR));
+ dp = &devname [strlen (DLPI_DEVDIR)];
+ }
+
+ /* Find the end of the interface name */
+ ep = cp = ifname;
+ while (*ep)
+ ep++;
+ /* And back up to the first digit (unit number) */
+ while ((*(ep - 1) >= '0' && *(ep - 1) <= '9') || *(ep - 1) == ':')
+ ep--;
+
+ /* Copy everything up to the unit number */
+ while (cp < ep) {
+ *dp++ = *cp++;
+ }
+ *dp = '\0';
+
+ return open (devname, O_RDWR, 0);
+}
+
+/*
+ * dlpiinforeq - request information about the data link provider.
+ */
+
+static int dlpiinforeq (fd)
+ int fd;
+{
+ dl_info_req_t info_req;
+ struct strbuf ctl;
+ int flags;
+
+ info_req.dl_primitive = DL_INFO_REQ;
+
+ ctl.maxlen = 0;
+ ctl.len = sizeof (info_req);
+ ctl.buf = (char *)&info_req;
+
+ flags = RS_HIPRI;
+
+ return putmsg (fd, &ctl, (struct strbuf *)NULL, flags);
+}
+
+/*
+ * dlpiphysaddrreq - request the current physical address.
+ */
+static int dlpiphysaddrreq (fd, addrtype)
+ int fd;
+ unsigned long addrtype;
+{
+ dl_phys_addr_req_t physaddr_req;
+ struct strbuf ctl;
+ int flags;
+
+ physaddr_req.dl_primitive = DL_PHYS_ADDR_REQ;
+ physaddr_req.dl_addr_type = addrtype;
+
+ ctl.maxlen = 0;
+ ctl.len = sizeof (physaddr_req);
+ ctl.buf = (char *)&physaddr_req;
+
+ flags = RS_HIPRI;
+
+ return putmsg (fd, &ctl, (struct strbuf *)NULL, flags);
+}
+
+/*
+ * dlpiattachreq - send a request to attach to a specific unit.
+ */
+static int dlpiattachreq (fd, ppa)
+ unsigned long ppa;
+ int fd;
+{
+ dl_attach_req_t attach_req;
+ struct strbuf ctl;
+ int flags;
+
+ attach_req.dl_primitive = DL_ATTACH_REQ;
+ attach_req.dl_ppa = ppa;
+
+ ctl.maxlen = 0;
+ ctl.len = sizeof (attach_req);
+ ctl.buf = (char *)&attach_req;
+
+ flags = 0;
+
+ return putmsg (fd, &ctl, (struct strbuf*)NULL, flags);
+}
+
+/*
+ * dlpibindreq - send a request to bind to a specific SAP address.
+ */
+static int dlpibindreq (fd, sap, max_conind, service_mode, conn_mgmt, xidtest)
+ unsigned long sap;
+ unsigned long max_conind;
+ unsigned long service_mode;
+ unsigned long conn_mgmt;
+ unsigned long xidtest;
+ int fd;
+{
+ dl_bind_req_t bind_req;
+ struct strbuf ctl;
+ int flags;
+
+ bind_req.dl_primitive = DL_BIND_REQ;
+ bind_req.dl_sap = sap;
+ bind_req.dl_max_conind = max_conind;
+ bind_req.dl_service_mode = service_mode;
+ bind_req.dl_conn_mgmt = conn_mgmt;
+ bind_req.dl_xidtest_flg = xidtest;
+
+ ctl.maxlen = 0;
+ ctl.len = sizeof (bind_req);
+ ctl.buf = (char *)&bind_req;
+
+ flags = 0;
+
+ return putmsg (fd, &ctl, (struct strbuf*)NULL, flags);
+}
+
+#if defined(UNUSED_DLPI_INTERFACE)
+/*
+ * dlpiunbindreq - send a request to unbind. This function is not actually
+ * used by ISC DHCP, but is included for completeness in case it is
+ * ever required for new work.
+ */
+static int dlpiunbindreq (fd)
+ int fd;
+{
+ dl_unbind_req_t unbind_req;
+ struct strbuf ctl;
+ int flags;
+
+ unbind_req.dl_primitive = DL_UNBIND_REQ;
+
+ ctl.maxlen = 0;
+ ctl.len = sizeof (unbind_req);
+ ctl.buf = (char *)&unbind_req;
+
+ flags = 0;
+
+ return putmsg (fd, &ctl, (struct strbuf*)NULL, flags);
+}
+
+
+/*
+ * dlpidetachreq - send a request to detach. This function is not actually
+ * used by ISC DHCP, but is included for completeness in case it is
+ * ever required for new work.
+ */
+static int dlpidetachreq (fd)
+ int fd;
+{
+ dl_detach_req_t detach_req;
+ struct strbuf ctl;
+ int flags;
+
+ detach_req.dl_primitive = DL_DETACH_REQ;
+
+ ctl.maxlen = 0;
+ ctl.len = sizeof (detach_req);
+ ctl.buf = (char *)&detach_req;
+
+ flags = 0;
+
+ return putmsg (fd, &ctl, (struct strbuf*)NULL, flags);
+}
+#endif /* UNUSED_DLPI_INTERFACE */
+
+
+/*
+ * dlpibindack - receive an ack to a dlbindreq.
+ */
+static int dlpibindack (fd, bufp)
+ char *bufp;
+ int fd;
+{
+ union DL_primitives *dlp;
+ struct strbuf ctl;
+ int flags;
+
+ ctl.maxlen = DLPI_MAXDLBUF;
+ ctl.len = 0;
+ ctl.buf = bufp;
+
+ if (strgetmsg (fd, &ctl,
+ (struct strbuf*)NULL, &flags, "dlpibindack") < 0) {
+ return -1;
+ }
+
+ dlp = (union DL_primitives *)ctl.buf;
+
+ if (expected (DL_BIND_ACK, dlp, flags) == -1) {
+ return -1;
+ }
+
+ if (ctl.len < sizeof (dl_bind_ack_t)) {
+ /* Returned structure is too short */
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * dlpiokack - general acknowledgement reception.
+ */
+static int dlpiokack (fd, bufp)
+ char *bufp;
+ int fd;
+{
+ union DL_primitives *dlp;
+ struct strbuf ctl;
+ int flags;
+
+ ctl.maxlen = DLPI_MAXDLBUF;
+ ctl.len = 0;
+ ctl.buf = bufp;
+
+ if (strgetmsg (fd, &ctl,
+ (struct strbuf*)NULL, &flags, "dlpiokack") < 0) {
+ return -1;
+ }
+
+ dlp = (union DL_primitives *)ctl.buf;
+
+ if (expected (DL_OK_ACK, dlp, flags) == -1) {
+ return -1;
+ }
+
+ if (ctl.len < sizeof (dl_ok_ack_t)) {
+ /* Returned structure is too short */
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * dlpiinfoack - receive an ack to a dlinforeq.
+ */
+static int dlpiinfoack (fd, bufp)
+ char *bufp;
+ int fd;
+{
+ union DL_primitives *dlp;
+ struct strbuf ctl;
+ int flags;
+
+ ctl.maxlen = DLPI_MAXDLBUF;
+ ctl.len = 0;
+ ctl.buf = bufp;
+
+ if (strgetmsg (fd, &ctl, (struct strbuf *)NULL, &flags,
+ "dlpiinfoack") < 0) {
+ return -1;
+ }
+
+ dlp = (union DL_primitives *) ctl.buf;
+
+ if (expected (DL_INFO_ACK, dlp, flags) == -1) {
+ return -1;
+ }
+
+ if (ctl.len < sizeof (dl_info_ack_t)) {
+ /* Returned structure is too short */
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * dlpiphysaddrack - receive an ack to a dlpiphysaddrreq.
+ */
+int dlpiphysaddrack (fd, bufp)
+ char *bufp;
+ int fd;
+{
+ union DL_primitives *dlp;
+ struct strbuf ctl;
+ int flags;
+
+ ctl.maxlen = DLPI_MAXDLBUF;
+ ctl.len = 0;
+ ctl.buf = bufp;
+
+ if (strgetmsg (fd, &ctl, (struct strbuf *)NULL, &flags,
+ "dlpiphysaddrack") < 0) {
+ return -1;
+ }
+
+ dlp = (union DL_primitives *)ctl.buf;
+
+ if (expected (DL_PHYS_ADDR_ACK, dlp, flags) == -1) {
+ return -1;
+ }
+
+ if (ctl.len < sizeof (dl_phys_addr_ack_t)) {
+ /* Returned structure is too short */
+ return -1;
+ }
+
+ return 0;
+}
+
+#if defined(USE_DLPI_SEND) || defined(USE_DLPI_RECEIVE)
+int dlpiunitdatareq (fd, addr, addrlen, minpri, maxpri, dbuf, dbuflen)
+ int fd;
+ unsigned char *addr;
+ int addrlen;
+ unsigned long minpri;
+ unsigned long maxpri;
+ unsigned char *dbuf;
+ int dbuflen;
+{
+ long buf [DLPI_MAXDLBUF];
+ union DL_primitives *dlp;
+ struct strbuf ctl, data;
+
+ /* Set up the control information... */
+ dlp = (union DL_primitives *)buf;
+ dlp -> unitdata_req.dl_primitive = DL_UNITDATA_REQ;
+ dlp -> unitdata_req.dl_dest_addr_length = addrlen;
+ dlp -> unitdata_req.dl_dest_addr_offset = sizeof (dl_unitdata_req_t);
+ dlp -> unitdata_req.dl_priority.dl_min = minpri;
+ dlp -> unitdata_req.dl_priority.dl_max = maxpri;
+
+ /* Append the destination address */
+ memcpy ((char *)buf + dlp -> unitdata_req.dl_dest_addr_offset,
+ addr, addrlen);
+
+ ctl.maxlen = 0;
+ ctl.len = dlp -> unitdata_req.dl_dest_addr_offset + addrlen;
+ ctl.buf = (char *)buf;
+
+ data.maxlen = 0;
+ data.buf = (char *)dbuf;
+ data.len = dbuflen;
+
+ /* Send the packet down the wire... */
+ return putmsg (fd, &ctl, &data, 0);
+}
+
+static int dlpiunitdataind (fd, daddr, daddrlen,
+ saddr, saddrlen, grpaddr, dbuf, dlen)
+ int fd;
+ unsigned char *daddr;
+ unsigned long *daddrlen;
+ unsigned char *saddr;
+ unsigned long *saddrlen;
+ unsigned long *grpaddr;
+ unsigned char *dbuf;
+ int dlen;
+{
+ long buf [DLPI_MAXDLBUF];
+ union DL_primitives *dlp;
+ struct strbuf ctl, data;
+ int flags = 0;
+ int result;
+
+ /* Set up the msg_buf structure... */
+ dlp = (union DL_primitives *)buf;
+ dlp -> unitdata_ind.dl_primitive = DL_UNITDATA_IND;
+
+ ctl.maxlen = DLPI_MAXDLBUF;
+ ctl.len = 0;
+ ctl.buf = (char *)buf;
+
+ data.maxlen = dlen;
+ data.len = 0;
+ data.buf = (char *)dbuf;
+
+ result = getmsg (fd, &ctl, &data, &flags);
+
+ if (result < 0) {
+ log_debug("dlpiunitdataind: %m");
+ return -1;
+ }
+
+ if (ctl.len < sizeof (dl_unitdata_ind_t) ||
+ dlp -> unitdata_ind.dl_primitive != DL_UNITDATA_IND) {
+ return -1;
+ }
+
+ if (data.len <= 0) {
+ return data.len;
+ }
+
+ /* Copy sender info */
+ if (saddr) {
+ memcpy (saddr,
+ (char *)buf + dlp -> unitdata_ind.dl_src_addr_offset,
+ dlp -> unitdata_ind.dl_src_addr_length);
+ }
+ if (saddrlen) {
+ *saddrlen = dlp -> unitdata_ind.dl_src_addr_length;
+ }
+
+ /* Copy destination info */
+ if (daddr) {
+ memcpy (daddr,
+ (char *)buf + dlp -> unitdata_ind.dl_dest_addr_offset,
+ dlp -> unitdata_ind.dl_dest_addr_length);
+ }
+ if (daddrlen) {
+ *daddrlen = dlp -> unitdata_ind.dl_dest_addr_length;
+ }
+
+ if (grpaddr) {
+ *grpaddr = dlp -> unitdata_ind.dl_group_address;
+ }
+
+ return data.len;
+}
+#endif /* !USE_DLPI_HWADDR: USE_DLPI_RECEIVE || USE_DLPI_SEND */
+
+/*
+ * expected - see if we got what we wanted.
+ */
+static int expected (prim, dlp, msgflags)
+ unsigned long prim;
+ union DL_primitives *dlp;
+ int msgflags;
+{
+ if (msgflags != RS_HIPRI) {
+ /* Message was not M_PCPROTO */
+ return -1;
+ }
+
+ if (dlp->dl_primitive != prim) {
+ /* Incorrect/unexpected return message */
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * strgetmsg - get a message from a stream, with timeout.
+ */
+static int strgetmsg (fd, ctlp, datap, flagsp, caller)
+ struct strbuf *ctlp, *datap;
+ char *caller;
+ int *flagsp;
+ int fd;
+{
+ int result;
+ struct pollfd pfd;
+ int count;
+ time_t now;
+ time_t starttime;
+ int to_msec;
+
+ pfd.fd = fd;
+ pfd.events = POLLPRI; /* We're only interested in knowing
+ * when we can receive the next high
+ * priority message.
+ */
+ pfd.revents = 0;
+
+ now = time (&starttime);
+ while (now <= starttime + DLPI_MAXWAIT) {
+ to_msec = ((starttime + DLPI_MAXWAIT) - now) * 1000;
+ count = poll (&pfd, 1, to_msec);
+
+ if (count == 0) {
+ /* log_fatal ("strgetmsg: timeout"); */
+ return -1;
+ } else if (count < 0) {
+ if (errno == EAGAIN || errno == EINTR) {
+ time (&now);
+ continue;
+ } else {
+ /* log_fatal ("poll: %m"); */
+ return -1;
+ }
+ } else {
+ break;
+ }
+ }
+
+ /*
+ * Set flags argument and issue getmsg ().
+ */
+ *flagsp = 0;
+ if ((result = getmsg (fd, ctlp, datap, flagsp)) < 0) {
+ return result;
+ }
+
+ /*
+ * Check for MOREDATA and/or MORECTL.
+ */
+ if (result & (MORECTL|MOREDATA)) {
+ return -1;
+ }
+
+ /*
+ * Check for at least sizeof (long) control data portion.
+ */
+ if (ctlp -> len < sizeof (long)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+#if defined(USE_DLPI_SEND)
+int can_unicast_without_arp (ip)
+ struct interface_info *ip;
+{
+ return 1;
+}
+
+int can_receive_unicast_unconfigured (ip)
+ struct interface_info *ip;
+{
+ return 1;
+}
+
+int supports_multiple_interfaces (ip)
+ struct interface_info *ip;
+{
+ return 1;
+}
+
+void maybe_setup_fallback ()
+{
+ isc_result_t status;
+ struct interface_info *fbi = (struct interface_info *)0;
+ if (setup_fallback (&fbi, MDL)) {
+ if_register_fallback (fbi);
+ status = omapi_register_io_object ((omapi_object_t *)fbi,
+ if_readsocket, 0,
+ fallback_discard, 0, 0);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register I/O handle for %s: %s",
+ fbi -> name, isc_result_totext (status));
+ interface_dereference (&fbi, MDL);
+ }
+}
+#endif /* USE_DLPI_SEND */
+
+void
+get_hw_addr(const char *name, struct hardware *hw) {
+ int sock, unit;
+ long buf[DLPI_MAXDLBUF];
+ union DL_primitives *dlp;
+
+ dlp = (union DL_primitives *)buf;
+
+ /*
+ * Open a DLPI device.
+ */
+ sock = dlpiopen(name);
+ if (sock < 0) {
+ log_fatal("Can't open DLPI device for %s: %m", name);
+ }
+
+ /*
+ * Submit a DL_INFO_REQ request, to find the dl_mac_type and
+ * dl_provider_style
+ */
+ if (dlpiinforeq(sock) < 0) {
+ log_fatal("Can't request DLPI MAC type for %s: %m", name);
+ }
+ if (dlpiinfoack(sock, (char *)buf) < 0) {
+ log_fatal("Can't get DLPI MAC type for %s: %m", name);
+ }
+ switch (dlp->info_ack.dl_mac_type) {
+ case DL_CSMACD: /* IEEE 802.3 */
+ case DL_ETHER:
+ hw->hbuf[0] = HTYPE_ETHER;
+ break;
+ case DL_TPR:
+ hw->hbuf[0] = HTYPE_IEEE802;
+ break;
+ case DL_FDDI:
+ hw->hbuf[0] = HTYPE_FDDI;
+ break;
+ default:
+ log_fatal("%s: unsupported DLPI MAC type %lu", name,
+ (unsigned long)dlp->info_ack.dl_mac_type);
+ }
+
+ if (dlp->info_ack.dl_provider_style == DL_STYLE2) {
+ /*
+ * Attach to the device. If this fails, the device
+ * does not exist.
+ */
+ unit = dlpiunit((char *)name);
+
+ if (dlpiattachreq(sock, unit) < 0 ||
+ dlpiokack(sock, (char *)buf) < 0) {
+ log_fatal("Can't attach DLPI device for %s: %m",
+ name);
+ }
+ }
+
+ /*
+ * Submit a DL_PHYS_ADDR_REQ request, to find
+ * the hardware address.
+ */
+ if (dlpiphysaddrreq(sock, DL_CURR_PHYS_ADDR) < 0) {
+ log_fatal("Can't request DLPI hardware address for %s: %m",
+ name);
+ }
+ if (dlpiphysaddrack(sock, (char *)buf) < 0) {
+ log_fatal("Can't get DLPI hardware address for %s: %m",
+ name);
+ }
+ if (dlp->physaddr_ack.dl_addr_length < sizeof(hw->hbuf)) {
+ memcpy(hw->hbuf+1,
+ (char *)buf + dlp->physaddr_ack.dl_addr_offset,
+ dlp->physaddr_ack.dl_addr_length);
+ hw->hlen = dlp->physaddr_ack.dl_addr_length + 1;
+ } else {
+ memcpy(hw->hbuf+1,
+ (char *)buf + dlp->physaddr_ack.dl_addr_offset,
+ sizeof(hw->hbuf)-1);
+ hw->hlen = sizeof(hw->hbuf);
+ }
+
+ close(sock);
+}
+#endif /* USE_DLPI_SEND || USE_DLPI_RECEIVE || USE_DLPI_HWADDR */
diff --git a/common/dns.c b/common/dns.c
new file mode 100644
index 0000000..ebf6212
--- /dev/null
+++ b/common/dns.c
@@ -0,0 +1,1785 @@
+/* dns.c
+
+ Domain Name Service subroutines. */
+
+/*
+ * Copyright (c) 2009-2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2007 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2001-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+#include "arpa/nameser.h"
+#include <isc/md5.h>
+
+#include <dns/result.h>
+
+/*
+ * This file contains code to connect the DHCP code to the libdns modules.
+ * As part of that function it maintains a database of zone cuts that can
+ * be used to figure out which server should be contacted to update any
+ * given domain name. Included in the zone information may be a pointer
+ * to a key in which case that key is used for the update. If no zone
+ * is found then the DNS code determines the zone on its own.
+ *
+ * The way this works is that you define the domain name to which an
+ * SOA corresponds, and the addresses of some primaries for that domain name:
+ *
+ * zone FOO.COM {
+ * primary 10.0.17.1;
+ * secondary 10.0.22.1, 10.0.23.1;
+ * key "FOO.COM Key";
+ * }
+ *
+ * If an update is requested for GAZANGA.TOPANGA.FOO.COM, then the name
+ * server looks in its database for a zone record for "GAZANGA.TOPANGA.FOO.COM",
+ * doesn't find it, looks for one for "TOPANGA.FOO.COM", doesn't find *that*,
+ * looks for "FOO.COM", finds it. So it
+ * attempts the update to the primary for FOO.COM. If that times out, it
+ * tries the secondaries. You can list multiple primaries if you have some
+ * kind of magic name server that supports that. You shouldn't list
+ * secondaries that don't know how to forward updates (e.g., BIND 8 doesn't
+ * support update forwarding, AFAIK). If no TSIG key is listed, the update
+ * is attempted without TSIG.
+ *
+ * You can also include IPv6 addresses via the primary6 and secondary6
+ * options. The search order for the addresses is primary, primary6,
+ * secondary and lastly secondary6, with a limit on the number of
+ * addresses used. Currently this limit is 3.
+ *
+ * The DHCP server tries to find an existing zone for any given name by
+ * trying to look up a local zone structure for each domain containing
+ * that name, all the way up to '.'. If it finds one cached, it tries
+ * to use that one to do the update. That's why it tries to update
+ * "FOO.COM" above, even though theoretically it should try GAZANGA...
+ * and TOPANGA... first.
+ *
+ * If the update fails with a predefined zone the zone is marked as bad
+ * and another search of the predefined zones is done. If no predefined
+ * zone is found finding a zone is left to the DNS module via examination
+ * of SOA records. If the DNS module finds a zone it may cache the zone
+ * but the zone won't be cached here.
+ *
+ * TSIG updates are not performed on zones found by the DNS module - if
+ * you want TSIG updates you _must_ write a zone definition linking the
+ * key to the zone. In cases where you know for sure what the key is
+ * but do not want to hardcode the IP addresses of the primary or
+ * secondaries, a zone declaration can be made that doesn't include any
+ * primary or secondary declarations. When the DHCP server encounters
+ * this while hunting up a matching zone for a name, it looks up the SOA,
+ * fills in the IP addresses, and uses that record for the update.
+ * If the SOA lookup returns NXRRSET, a warning is printed and the zone is
+ * discarded, TSIG key and all. The search for the zone then continues
+ * as if the zone record hadn't been found. Zones without IP addresses
+ * don't match when initially hunting for a zone to update.
+ *
+ * When an update is attempted and no predefined zone is found
+ * that matches any enclosing domain of the domain being updated, the DHCP
+ * server goes through the same process that is done when the update to a
+ * predefined zone fails - starting with the most specific domain
+ * name (GAZANGA.TOPANGA.FOO.COM) and moving to the least specific (the root),
+ * it tries to look up an SOA record.
+ *
+ * TSIG keys are defined like this:
+ *
+ * key "FOO.COM Key" {
+ * algorithm HMAC-MD5.SIG-ALG.REG.INT;
+ * secret <Base64>;
+ * }
+ *
+ * <Base64> is a number expressed in base64 that represents the key.
+ * It's also permissible to use a quoted string here - this will be
+ * translated as the ASCII bytes making up the string, and will not
+ * include any NUL termination. The key name can be any text string,
+ * and the key type must be one of the key types defined in the draft
+ * or by the IANA. Currently only the HMAC-MD5... key type is
+ * supported.
+ *
+ * The DDNS processing has been split into two areas. One is the
+ * control code that determines what should be done. That code is found
+ * in the client or server directories. The other is the common code
+ * that performs functions such as properly formatting the arguments.
+ * That code is found in this file. The basic processing flow for a
+ * DDNS update is:
+ * In the client or server code determine what needs to be done and
+ * collect the necesary information then pass it to a function from
+ * this file.
+ * In this code lookup the zone and extract the zone and key information
+ * (if available) and prepare the arguments for the DNS module.
+ * When the DNS module completes its work (times out or gets a reply)
+ * it will trigger another function here which does generic processing
+ * and then passes control back to the code from the server or client.
+ * The server or client code then determines the next step which may
+ * result in another call to this module in which case the process repeats.
+ */
+
+dns_zone_hash_t *dns_zone_hash;
+
+/*
+ * DHCP dns structures
+ * Normally the relationship between these structures isn't one to one
+ * but in the DHCP case it (mostly) is. To make the allocations, frees,
+ * and passing of the memory easier we make a single structure with all
+ * the pieces.
+ *
+ * The maximum size of the data buffer should be large enough for any
+ * items DHCP will generate
+ */
+
+typedef struct dhcp_ddns_rdata {
+ dns_rdata_t rdata;
+ dns_rdatalist_t rdatalist;
+ dns_rdataset_t rdataset;
+} dhcp_ddns_data_t;
+
+#if defined (NSUPDATE)
+
+void ddns_interlude(isc_task_t *, isc_event_t *);
+
+
+#if defined (TRACING)
+/*
+ * Code to support tracing DDNS packets. We trace packets going to and
+ * coming from the libdns code but don't try to track the packets
+ * exchanged between the libdns code and the dns server(s) it contacts.
+ *
+ * The code is split into two sets of routines
+ * input refers to messages received from the dns module
+ * output refers to messages sent to the dns module
+ * Currently there are three routines in each set
+ * write is used to write information about the message to the trace file
+ * this routine is called directly from the proper place in the code.
+ * read is used to read information about a message from the trace file
+ * this routine is called from the trace loop as it reads through
+ * the file and is registered via the trace_type_register routine.
+ * When playing back a trace file we shall absorb records of output
+ * messages as part of processing the write function, therefore
+ * any output messages we encounter are flagged as errors.
+ * stop isn't currently used in this code but is needed for the register
+ * routine.
+ *
+ * We pass a pointer to a control block to the dns module which it returns
+ * to use as part of the result. As the pointer may vary between traces
+ * we need to map between those from the trace file and the new ones during
+ * playback.
+ *
+ * The mapping is complicated a little as a pointer could be 4 or 8 bytes
+ * long. We treat the old pointer as an 8 byte quantity and pad and compare
+ * as necessary.
+ */
+
+/*
+ * Structure used to map old pointers to new pointers.
+ * Old pointers are 8 bytes long as we don't know if the trace was
+ * done on a 64 bit or 32 bit machine.
+ */
+#define TRACE_PTR_LEN 8
+
+typedef struct dhcp_ddns_map {
+ char old_pointer[TRACE_PTR_LEN];
+ void *new_pointer;
+ struct dhcp_ddns_map *next;
+} dhcp_ddns_map_t;
+
+/* The starting point for the map structure */
+static dhcp_ddns_map_t *ddns_map;
+
+trace_type_t *trace_ddns_input;
+trace_type_t *trace_ddns_output;
+
+/*
+ * The data written to the trace file is:
+ * 32 bits result from dns
+ * 64 bits pointer of cb
+ */
+
+void
+trace_ddns_input_write(dhcp_ddns_cb_t *ddns_cb, isc_result_t result)
+{
+ trace_iov_t iov[2];
+ u_int32_t old_result;
+ char old_pointer[TRACE_PTR_LEN];
+
+ old_result = htonl((u_int32_t)result);
+ memset(old_pointer, 0, TRACE_PTR_LEN);
+ memcpy(old_pointer, &ddns_cb, sizeof(ddns_cb));
+
+ iov[0].len = sizeof(old_result);
+ iov[0].buf = (char *)&old_result;
+ iov[1].len = TRACE_PTR_LEN;
+ iov[1].buf = old_pointer;
+ trace_write_packet_iov(trace_ddns_input, 2, iov, MDL);
+}
+
+/*
+ * Process the result and pointer from the trace file.
+ * We use the pointer map to find the proper pointer for this instance.
+ * Then we need to construct an event to pass along to the interlude
+ * function.
+ */
+static void
+trace_ddns_input_read(trace_type_t *ttype, unsigned length,
+ char *buf)
+{
+ u_int32_t old_result;
+ char old_pointer[TRACE_PTR_LEN];
+ dns_clientupdateevent_t *eventp;
+ void *new_pointer;
+ dhcp_ddns_map_t *ddns_map_ptr;
+
+ if (length < (sizeof(old_result) + TRACE_PTR_LEN)) {
+ log_error("trace_ddns_input_read: data too short");
+ return;
+ }
+
+ memcpy(&old_result, buf, sizeof(old_result));
+ memcpy(old_pointer, buf + sizeof(old_result), TRACE_PTR_LEN);
+
+ /* map the old pointer to a new pointer */
+ for (ddns_map_ptr = ddns_map;
+ ddns_map_ptr != NULL;
+ ddns_map_ptr = ddns_map_ptr->next) {
+ if ((ddns_map_ptr->new_pointer != NULL) &&
+ memcmp(ddns_map_ptr->old_pointer,
+ old_pointer, TRACE_PTR_LEN) == 0) {
+ new_pointer = ddns_map_ptr->new_pointer;
+ ddns_map_ptr->new_pointer = NULL;
+ memset(ddns_map_ptr->old_pointer, 0, TRACE_PTR_LEN);
+ break;
+ }
+ }
+ if (ddns_map_ptr == NULL) {
+ log_error("trace_dns_input_read: unable to map cb pointer");
+ return;
+ }
+
+ eventp = (dns_clientupdateevent_t *)
+ isc_event_allocate(dhcp_gbl_ctx.mctx,
+ dhcp_gbl_ctx.task,
+ 0,
+ ddns_interlude,
+ new_pointer,
+ sizeof(dns_clientupdateevent_t));
+ if (eventp == NULL) {
+ log_error("trace_ddns_input_read: unable to allocate event");
+ return;
+ }
+ eventp->result = ntohl(old_result);
+
+
+ ddns_interlude(dhcp_gbl_ctx.task, (isc_event_t *)eventp);
+
+ return;
+}
+
+static void
+trace_ddns_input_stop(trace_type_t *ttype)
+{
+}
+
+/*
+ * We use the same arguments as for the dns startupdate function to
+ * allows us to choose between the two via a macro. If tracing isn't
+ * in use we simply call the dns function directly.
+ *
+ * If we are doing playback we read the next packet from the file
+ * and compare the type. If it matches we extract the results and pointer
+ * from the trace file. The results are returned to the caller as if
+ * they had called the dns routine. The pointer is used to construct a
+ * map for when the "reply" is processed.
+ *
+ * The data written to trace file is:
+ * 32 bits result
+ * 64 bits pointer of cb (DDNS Control block)
+ * contents of cb
+ */
+
+isc_result_t
+trace_ddns_output_write(dns_client_t *client, dns_rdataclass_t rdclass,
+ dns_name_t *zonename, dns_namelist_t *prerequisites,
+ dns_namelist_t *updates, isc_sockaddrlist_t *servers,
+ dns_tsec_t *tsec, unsigned int options,
+ isc_task_t *task, isc_taskaction_t action, void *arg,
+ dns_clientupdatetrans_t **transp)
+{
+ isc_result_t result;
+ u_int32_t old_result;
+ char old_pointer[TRACE_PTR_LEN];
+ dhcp_ddns_map_t *ddns_map_ptr;
+
+ if (trace_playback() != 0) {
+ /* We are doing playback, extract the entry from the file */
+ unsigned buflen = 0;
+ char *inbuf = NULL;
+
+ result = trace_get_packet(&trace_ddns_output,
+ &buflen, &inbuf);
+ if (result != ISC_R_SUCCESS) {
+ log_error("trace_ddns_output_write: no input found");
+ return (ISC_R_FAILURE);
+ }
+ if (buflen < (sizeof(old_result) + TRACE_PTR_LEN)) {
+ log_error("trace_ddns_output_write: data too short");
+ dfree(inbuf, MDL);
+ return (ISC_R_FAILURE);
+ }
+ memcpy(&old_result, inbuf, sizeof(old_result));
+ result = ntohl(old_result);
+ memcpy(old_pointer, inbuf + sizeof(old_result), TRACE_PTR_LEN);
+ dfree(inbuf, MDL);
+
+ /* add the pointer to the pointer map */
+ for (ddns_map_ptr = ddns_map;
+ ddns_map_ptr != NULL;
+ ddns_map_ptr = ddns_map_ptr->next) {
+ if (ddns_map_ptr->new_pointer == NULL) {
+ break;
+ }
+ }
+
+ /*
+ * If we didn't find an empty entry, allocate an entry and
+ * link it into the list. The list isn't ordered.
+ */
+ if (ddns_map_ptr == NULL) {
+ ddns_map_ptr = dmalloc(sizeof(*ddns_map_ptr), MDL);
+ if (ddns_map_ptr == NULL) {
+ log_error("trace_ddns_output_write: "
+ "unable to allocate map entry");
+ return(ISC_R_FAILURE);
+ }
+ ddns_map_ptr->next = ddns_map;
+ ddns_map = ddns_map_ptr;
+ }
+
+ memcpy(ddns_map_ptr->old_pointer, old_pointer, TRACE_PTR_LEN);
+ ddns_map_ptr->new_pointer = arg;
+ }
+ else {
+ /* We aren't doing playback, make the actual call */
+ result = dns_client_startupdate(client, rdclass, zonename,
+ prerequisites, updates,
+ servers, tsec, options,
+ task, action, arg, transp);
+ }
+
+ if (trace_record() != 0) {
+ /* We are recording, save the information to the file */
+ trace_iov_t iov[3];
+ old_result = htonl((u_int32_t)result);
+ memset(old_pointer, 0, TRACE_PTR_LEN);
+ memcpy(old_pointer, &arg, sizeof(arg));
+ iov[0].len = sizeof(old_result);
+ iov[0].buf = (char *)&old_result;
+ iov[1].len = TRACE_PTR_LEN;
+ iov[1].buf = old_pointer;
+
+ /* Write out the entire cb, in case we want to look at it */
+ iov[2].len = sizeof(dhcp_ddns_cb_t);
+ iov[2].buf = (char *)arg;
+
+ trace_write_packet_iov(trace_ddns_output, 3, iov, MDL);
+ }
+
+ return(result);
+}
+
+static void
+trace_ddns_output_read(trace_type_t *ttype, unsigned length,
+ char *buf)
+{
+ log_error("unaccounted for ddns output.");
+}
+
+static void
+trace_ddns_output_stop(trace_type_t *ttype)
+{
+}
+
+void
+trace_ddns_init()
+{
+ trace_ddns_output = trace_type_register("ddns-output", NULL,
+ trace_ddns_output_read,
+ trace_ddns_output_stop, MDL);
+ trace_ddns_input = trace_type_register("ddns-input", NULL,
+ trace_ddns_input_read,
+ trace_ddns_input_stop, MDL);
+ ddns_map = NULL;
+}
+
+#define ddns_update trace_ddns_output_write
+#else
+#define ddns_update dns_client_startupdate
+#endif /* TRACING */
+
+/*
+ * Code to allocate and free a dddns control block. This block is used
+ * to pass and track the information associated with a DDNS update request.
+ */
+dhcp_ddns_cb_t *
+ddns_cb_alloc(const char *file, int line)
+{
+ dhcp_ddns_cb_t *ddns_cb;
+ int i;
+
+ ddns_cb = dmalloc(sizeof(*ddns_cb), file, line);
+ if (ddns_cb != NULL) {
+ ISC_LIST_INIT(ddns_cb->zone_server_list);
+ for (i = 0; i < DHCP_MAXNS; i++) {
+ ISC_LINK_INIT(&ddns_cb->zone_addrs[i], link);
+ }
+ }
+
+#if defined (DEBUG_DNS_UPDATES)
+ log_info("%s(%d): Allocating ddns_cb=%p", file, line, ddns_cb);
+#endif
+
+ return(ddns_cb);
+}
+
+void
+ddns_cb_free(dhcp_ddns_cb_t *ddns_cb, const char *file, int line)
+{
+#if defined (DEBUG_DNS_UPDATES)
+ log_info("%s(%d): freeing ddns_cb=%p", file, line, ddns_cb);
+#endif
+
+ data_string_forget(&ddns_cb->fwd_name, file, line);
+ data_string_forget(&ddns_cb->rev_name, file, line);
+ data_string_forget(&ddns_cb->dhcid, file, line);
+
+ if (ddns_cb->zone != NULL) {
+ forget_zone((struct dns_zone **)&ddns_cb->zone);
+ }
+
+ /* Should be freed by now, check just in case. */
+ if (ddns_cb->transaction != NULL)
+ log_error("Impossible memory leak at %s:%d (attempt to free "
+ "DDNS Control Block before transaction).", MDL);
+
+ dfree(ddns_cb, file, line);
+}
+
+void
+ddns_cb_forget_zone(dhcp_ddns_cb_t *ddns_cb)
+{
+ int i;
+
+ forget_zone(&ddns_cb->zone);
+ ddns_cb->zone_name[0] = 0;
+ ISC_LIST_INIT(ddns_cb->zone_server_list);
+ for (i = 0; i < DHCP_MAXNS; i++) {
+ ISC_LINK_INIT(&ddns_cb->zone_addrs[i], link);
+ }
+}
+
+isc_result_t find_tsig_key (ns_tsig_key **key, const char *zname,
+ struct dns_zone *zone)
+{
+ ns_tsig_key *tkey;
+
+ if (!zone)
+ return ISC_R_NOTFOUND;
+
+ if (!zone -> key) {
+ return DHCP_R_KEY_UNKNOWN;
+ }
+
+ if ((!zone -> key -> name ||
+ strlen (zone -> key -> name) > NS_MAXDNAME) ||
+ (!zone -> key -> algorithm ||
+ strlen (zone -> key -> algorithm) > NS_MAXDNAME) ||
+ (!zone -> key) ||
+ (!zone -> key -> key) ||
+ (zone -> key -> key -> len == 0)) {
+ return DHCP_R_INVALIDKEY;
+ }
+ tkey = dmalloc (sizeof *tkey, MDL);
+ if (!tkey) {
+ nomem:
+ return ISC_R_NOMEMORY;
+ }
+ memset (tkey, 0, sizeof *tkey);
+ tkey -> data = dmalloc (zone -> key -> key -> len, MDL);
+ if (!tkey -> data) {
+ dfree (tkey, MDL);
+ goto nomem;
+ }
+ strcpy (tkey -> name, zone -> key -> name);
+ strcpy (tkey -> alg, zone -> key -> algorithm);
+ memcpy (tkey -> data,
+ zone -> key -> key -> value, zone -> key -> key -> len);
+ tkey -> len = zone -> key -> key -> len;
+ *key = tkey;
+ return ISC_R_SUCCESS;
+}
+
+void tkey_free (ns_tsig_key **key)
+{
+ if ((*key) -> data)
+ dfree ((*key) -> data, MDL);
+ dfree ((*key), MDL);
+ *key = (ns_tsig_key *)0;
+}
+#endif
+
+isc_result_t enter_dns_zone (struct dns_zone *zone)
+{
+ struct dns_zone *tz = (struct dns_zone *)0;
+
+ if (dns_zone_hash) {
+ dns_zone_hash_lookup (&tz,
+ dns_zone_hash, zone -> name, 0, MDL);
+ if (tz == zone) {
+ dns_zone_dereference (&tz, MDL);
+ return ISC_R_SUCCESS;
+ }
+ if (tz) {
+ dns_zone_hash_delete (dns_zone_hash,
+ zone -> name, 0, MDL);
+ dns_zone_dereference (&tz, MDL);
+ }
+ } else {
+ if (!dns_zone_new_hash(&dns_zone_hash, DNS_HASH_SIZE, MDL))
+ return ISC_R_NOMEMORY;
+ }
+
+ dns_zone_hash_add (dns_zone_hash, zone -> name, 0, zone, MDL);
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dns_zone_lookup (struct dns_zone **zone, const char *name)
+{
+ int len;
+ char *tname = (char *)0;
+ isc_result_t status;
+
+ if (!dns_zone_hash)
+ return ISC_R_NOTFOUND;
+
+ len = strlen (name);
+ if (name [len - 1] != '.') {
+ tname = dmalloc ((unsigned)len + 2, MDL);
+ if (!tname)
+ return ISC_R_NOMEMORY;
+ strcpy (tname, name);
+ tname [len] = '.';
+ tname [len + 1] = 0;
+ name = tname;
+ }
+ if (!dns_zone_hash_lookup (zone, dns_zone_hash, name, 0, MDL))
+ status = ISC_R_NOTFOUND;
+ else
+ status = ISC_R_SUCCESS;
+
+ if (tname)
+ dfree (tname, MDL);
+ return status;
+}
+
+int dns_zone_dereference (ptr, file, line)
+ struct dns_zone **ptr;
+ const char *file;
+ int line;
+{
+ struct dns_zone *dns_zone;
+
+ if ((ptr == NULL) || (*ptr == NULL)) {
+ log_error("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort();
+#else
+ return (0);
+#endif
+ }
+
+ dns_zone = *ptr;
+ *ptr = NULL;
+ --dns_zone->refcnt;
+ rc_register(file, line, ptr, dns_zone, dns_zone->refcnt, 1, RC_MISC);
+ if (dns_zone->refcnt > 0)
+ return (1);
+
+ if (dns_zone->refcnt < 0) {
+ log_error("%s(%d): negative refcnt!", file, line);
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history(dns_zone);
+#endif
+#if defined (POINTER_DEBUG)
+ abort();
+#else
+ return (0);
+#endif
+ }
+
+ if (dns_zone->name)
+ dfree(dns_zone->name, file, line);
+ if (dns_zone->key)
+ omapi_auth_key_dereference(&dns_zone->key, file, line);
+ if (dns_zone->primary)
+ option_cache_dereference(&dns_zone->primary, file, line);
+ if (dns_zone->secondary)
+ option_cache_dereference(&dns_zone->secondary, file, line);
+ if (dns_zone->primary6)
+ option_cache_dereference(&dns_zone->primary6, file, line);
+ if (dns_zone->secondary6)
+ option_cache_dereference(&dns_zone->secondary6, file, line);
+ dfree(dns_zone, file, line);
+ return (1);
+}
+
+#if defined (NSUPDATE)
+isc_result_t
+find_cached_zone(dhcp_ddns_cb_t *ddns_cb, int direction)
+{
+ isc_result_t status = ISC_R_NOTFOUND;
+ const char *np;
+ struct dns_zone *zone = NULL;
+ struct data_string nsaddrs;
+ struct in_addr zone_addr;
+ struct in6_addr zone_addr6;
+ int ix;
+
+ if (direction == FIND_FORWARD) {
+ np = (const char *)ddns_cb->fwd_name.data;
+ } else {
+ np = (const char *)ddns_cb->rev_name.data;
+ }
+
+ /* We can't look up a null zone. */
+ if ((np == NULL) || (*np == '\0')) {
+ return (DHCP_R_INVALIDARG);
+ }
+
+ /*
+ * For each subzone, try to find a cached zone.
+ */
+ for (;;) {
+ status = dns_zone_lookup(&zone, np);
+ if (status == ISC_R_SUCCESS)
+ break;
+
+ np = strchr(np, '.');
+ if (np == NULL)
+ break;
+ np++;
+ }
+
+ if (status != ISC_R_SUCCESS)
+ return (status);
+
+ /* Make sure the zone is valid. */
+ if (zone->timeout && zone->timeout < cur_time) {
+ dns_zone_dereference(&zone, MDL);
+ return (ISC_R_CANCELED);
+ }
+
+ /* Make sure the zone name will fit. */
+ if (strlen(zone->name) > sizeof(ddns_cb->zone_name)) {
+ dns_zone_dereference(&zone, MDL);
+ return (ISC_R_NOSPACE);
+ }
+ strcpy((char *)&ddns_cb->zone_name[0], zone->name);
+
+ memset (&nsaddrs, 0, sizeof nsaddrs);
+ ix = 0;
+
+ if (zone->primary) {
+ if (evaluate_option_cache(&nsaddrs, NULL, NULL, NULL,
+ NULL, NULL, &global_scope,
+ zone->primary, MDL)) {
+ int ip = 0;
+ while (ix < DHCP_MAXNS) {
+ if (ip + 4 > nsaddrs.len)
+ break;
+ memcpy(&zone_addr, &nsaddrs.data[ip], 4);
+ isc_sockaddr_fromin(&ddns_cb->zone_addrs[ix],
+ &zone_addr,
+ NS_DEFAULTPORT);
+ ISC_LIST_APPEND(ddns_cb->zone_server_list,
+ &ddns_cb->zone_addrs[ix],
+ link);
+ ip += 4;
+ ix++;
+ }
+ data_string_forget(&nsaddrs, MDL);
+ }
+ }
+
+ if (zone->primary6) {
+ if (evaluate_option_cache(&nsaddrs, NULL, NULL, NULL,
+ NULL, NULL, &global_scope,
+ zone->primary6, MDL)) {
+ int ip = 0;
+ while (ix < DHCP_MAXNS) {
+ if (ip + 16 > nsaddrs.len)
+ break;
+ memcpy(&zone_addr6, &nsaddrs.data[ip], 16);
+ isc_sockaddr_fromin6(&ddns_cb->zone_addrs[ix],
+ &zone_addr6,
+ NS_DEFAULTPORT);
+ ISC_LIST_APPEND(ddns_cb->zone_server_list,
+ &ddns_cb->zone_addrs[ix],
+ link);
+ ip += 16;
+ ix++;
+ }
+ data_string_forget(&nsaddrs, MDL);
+ }
+ }
+
+ if (zone->secondary) {
+ if (evaluate_option_cache(&nsaddrs, NULL, NULL, NULL,
+ NULL, NULL, &global_scope,
+ zone->secondary, MDL)) {
+ int ip = 0;
+ while (ix < DHCP_MAXNS) {
+ if (ip + 4 > nsaddrs.len)
+ break;
+ memcpy(&zone_addr, &nsaddrs.data[ip], 4);
+ isc_sockaddr_fromin(&ddns_cb->zone_addrs[ix],
+ &zone_addr,
+ NS_DEFAULTPORT);
+ ISC_LIST_APPEND(ddns_cb->zone_server_list,
+ &ddns_cb->zone_addrs[ix],
+ link);
+ ip += 4;
+ ix++;
+ }
+ data_string_forget (&nsaddrs, MDL);
+ }
+ }
+
+ if (zone->secondary6) {
+ if (evaluate_option_cache(&nsaddrs, NULL, NULL, NULL,
+ NULL, NULL, &global_scope,
+ zone->secondary6, MDL)) {
+ int ip = 0;
+ while (ix < DHCP_MAXNS) {
+ if (ip + 16 > nsaddrs.len)
+ break;
+ memcpy(&zone_addr6, &nsaddrs.data[ip], 16);
+ isc_sockaddr_fromin6(&ddns_cb->zone_addrs[ix],
+ &zone_addr6,
+ NS_DEFAULTPORT);
+ ISC_LIST_APPEND(ddns_cb->zone_server_list,
+ &ddns_cb->zone_addrs[ix],
+ link);
+ ip += 16;
+ ix++;
+ }
+ data_string_forget (&nsaddrs, MDL);
+ }
+ }
+
+ dns_zone_reference(&ddns_cb->zone, zone, MDL);
+ dns_zone_dereference (&zone, MDL);
+ return ISC_R_SUCCESS;
+}
+
+void forget_zone (struct dns_zone **zone)
+{
+ dns_zone_dereference (zone, MDL);
+}
+
+void repudiate_zone (struct dns_zone **zone)
+{
+ /* XXX Currently we're not differentiating between a cached
+ XXX zone and a zone that's been repudiated, which means
+ XXX that if we reap cached zones, we blow away repudiated
+ XXX zones. This isn't a big problem since we're not yet
+ XXX caching zones... :'} */
+
+ /* verify that we have a pointer at least */
+ if ((zone == NULL) || (*zone == NULL)) {
+ log_info("Null argument to repudiate zone");
+ return;
+ }
+
+ (*zone) -> timeout = cur_time - 1;
+ dns_zone_dereference (zone, MDL);
+}
+
+/* Have to use TXT records for now. */
+#define T_DHCID T_TXT
+
+int get_dhcid (struct data_string *id,
+ int type, const u_int8_t *data, unsigned len)
+{
+ unsigned char buf[ISC_MD5_DIGESTLENGTH];
+ isc_md5_t md5;
+ int i;
+
+ /* Types can only be 0..(2^16)-1. */
+ if (type < 0 || type > 65535)
+ return 0;
+
+ /*
+ * Hexadecimal MD5 digest plus two byte type, NUL,
+ * and one byte for length for dns.
+ */
+ if (!buffer_allocate (&id -> buffer,
+ (ISC_MD5_DIGESTLENGTH * 2) + 4, MDL))
+ return 0;
+ id -> data = id -> buffer -> data;
+
+ /*
+ * DHCP clients and servers should use the following forms of client
+ * identification, starting with the most preferable, and finishing
+ * with the least preferable. If the client does not send any of these
+ * forms of identification, the DHCP/DDNS interaction is not defined by
+ * this specification. The most preferable form of identification is
+ * the Globally Unique Identifier Option [TBD]. Next is the DHCP
+ * Client Identifier option. Last is the client's link-layer address,
+ * as conveyed in its DHCPREQUEST message. Implementors should note
+ * that the link-layer address cannot be used if there are no
+ * significant bytes in the chaddr field of the DHCP client's request,
+ * because this does not constitute a unique identifier.
+ * -- "Interaction between DHCP and DNS"
+ * <draft-ietf-dhc-dhcp-dns-12.txt>
+ * M. Stapp, Y. Rekhter
+ *
+ * We put the length into the first byte to turn
+ * this into a dns text string. This avoid needing to
+ * copy the string to add the byte later.
+ */
+ id->buffer->data[0] = ISC_MD5_DIGESTLENGTH * 2 + 2;
+
+ /* Put the type in the next two bytes. */
+ id->buffer->data[1] = "0123456789abcdef"[(type >> 4) & 0xf];
+ /* This should have been [type & 0xf] but now that
+ * it is in use we need to leave it this way in order
+ * to avoid disturbing customer's lease files
+ */
+ id->buffer->data[2] = "0123456789abcdef"[type % 15];
+
+ /* Mash together an MD5 hash of the identifier. */
+ isc_md5_init(&md5);
+ isc_md5_update(&md5, data, len);
+ isc_md5_final(&md5, buf);
+
+ /* Convert into ASCII. */
+ for (i = 0; i < ISC_MD5_DIGESTLENGTH; i++) {
+ id->buffer->data[i * 2 + 3] =
+ "0123456789abcdef"[(buf[i] >> 4) & 0xf];
+ id->buffer->data[i * 2 + 4] =
+ "0123456789abcdef"[buf[i] & 0xf];
+ }
+
+ id->len = ISC_MD5_DIGESTLENGTH * 2 + 3;
+ id->buffer->data[id->len] = 0;
+ id->terminated = 1;
+
+ return 1;
+}
+
+/*
+ * The dhcid (text version) that we pass to DNS includes a length byte
+ * at the start but the text we store in the lease doesn't include the
+ * length byte. The following routines are to convert between the two
+ * styles.
+ *
+ * When converting from a dhcid to a leaseid we reuse the buffer and
+ * simply adjust the data pointer and length fields in the data string.
+ * This avoids any prolems with allocating space.
+ */
+
+void
+dhcid_tolease(struct data_string *dhcid,
+ struct data_string *leaseid)
+{
+ /* copy the data string then update the fields */
+ data_string_copy(leaseid, dhcid, MDL);
+ leaseid->data++;
+ leaseid->len--;
+}
+
+isc_result_t
+dhcid_fromlease(struct data_string *dhcid,
+ struct data_string *leaseid)
+{
+ if (!buffer_allocate(&dhcid->buffer, leaseid->len + 2, MDL)) {
+ return(ISC_R_FAILURE);
+ }
+
+ dhcid->data = dhcid->buffer->data;
+
+ dhcid->buffer->data[0] = leaseid->len;
+ memcpy(dhcid->buffer->data + 1, leaseid->data, leaseid->len);
+ dhcid->len = leaseid->len + 1;
+ if (leaseid->terminated == 1) {
+ dhcid->buffer->data[dhcid->len] = 0;
+ dhcid->terminated = 1;
+ }
+
+ return(ISC_R_SUCCESS);
+}
+
+/*
+ * Construct the dataset for this item.
+ * This is a fairly simple arrangement as the operations we do are simple.
+ * If there is data we simply have the rdata point to it - the formatting
+ * must be correct already. We then link the rdatalist to the rdata and
+ * create a rdataset from the rdatalist.
+ */
+
+static isc_result_t
+make_dns_dataset(dns_rdataclass_t dataclass,
+ dns_rdatatype_t datatype,
+ dhcp_ddns_data_t *dataspace,
+ unsigned char *data,
+ int datalen,
+ int ttl)
+{
+ dns_rdata_t *rdata = &dataspace->rdata;
+ dns_rdatalist_t *rdatalist = &dataspace->rdatalist;
+ dns_rdataset_t *rdataset = &dataspace->rdataset;
+
+ isc_region_t region;
+
+ /* set up the rdata */
+ dns_rdata_init(rdata);
+
+ if (data == NULL) {
+ /* No data, set up the rdata fields we care about */
+ rdata->flags = DNS_RDATA_UPDATE;
+ rdata->type = datatype;
+ rdata->rdclass = dataclass;
+ } else {
+ switch(datatype) {
+ case dns_rdatatype_a:
+ case dns_rdatatype_aaaa:
+ case dns_rdatatype_txt:
+ case dns_rdatatype_dhcid:
+ case dns_rdatatype_ptr:
+ /* The data must be in the right format we simply
+ * need to supply it via the correct structure */
+ region.base = data;
+ region.length = datalen;
+ dns_rdata_fromregion(rdata, dataclass, datatype,
+ &region);
+ break;
+ default:
+ return(DHCP_R_INVALIDARG);
+ break;
+ }
+ }
+
+ /* setup the datalist and attach the rdata to it */
+ dns_rdatalist_init(rdatalist);
+ rdatalist->type = datatype;
+ rdatalist->rdclass = dataclass;
+ rdatalist->ttl = ttl;
+ ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
+
+ /* convert the datalist to a dataset */
+ dns_rdataset_init(rdataset);
+ dns_rdatalist_tordataset(rdatalist, rdataset);
+
+ return(ISC_R_SUCCESS);
+}
+
+/*
+ * When a DHCP client or server intends to update an A RR, it first
+ * prepares a DNS UPDATE query which includes as a prerequisite the
+ * assertion that the name does not exist. The update section of the
+ * query attempts to add the new name and its IP address mapping (an A
+ * RR), and the DHCID RR with its unique client-identity.
+ * -- "Interaction between DHCP and DNS"
+ *
+ * There are two cases, one for the server and one for the client.
+ *
+ * For the server the first step will have a request of:
+ * The name is not in use
+ * Add an A RR
+ * Add a DHCID RR (currently txt)
+ *
+ * For the client the first step will have a request of:
+ * The A RR does not exist
+ * Add an A RR
+ * Add a DHCID RR (currently txt)
+ */
+
+static isc_result_t
+ddns_modify_fwd_add1(dhcp_ddns_cb_t *ddns_cb,
+ dhcp_ddns_data_t *dataspace,
+ dns_name_t *pname,
+ dns_name_t *uname)
+{
+ isc_result_t result;
+
+ /* Construct the prerequisite list */
+ if ((ddns_cb->flags & DDNS_INCLUDE_RRSET) != 0) {
+ /* The A RR shouldn't exist */
+ result = make_dns_dataset(dns_rdataclass_none,
+ ddns_cb->address_type,
+ dataspace, NULL, 0, 0);
+ } else {
+ /* The name is not in use */
+ result = make_dns_dataset(dns_rdataclass_none,
+ dns_rdatatype_any,
+ dataspace, NULL, 0, 0);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return(result);
+ }
+ ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link);
+ dataspace++;
+
+ /* Construct the update list */
+ /* Add the A RR */
+ result = make_dns_dataset(dns_rdataclass_in, ddns_cb->address_type,
+ dataspace,
+ (unsigned char *)ddns_cb->address.iabuf,
+ ddns_cb->address.len, ddns_cb->ttl);
+ if (result != ISC_R_SUCCESS) {
+ return(result);
+ }
+ ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link);
+ dataspace++;
+
+ /* Add the DHCID RR */
+ result = make_dns_dataset(dns_rdataclass_in, dns_rdatatype_txt,
+ dataspace,
+ (unsigned char *)ddns_cb->dhcid.data,
+ ddns_cb->dhcid.len, ddns_cb->ttl);
+ if (result != ISC_R_SUCCESS) {
+ return(result);
+ }
+ ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link);
+
+ return(ISC_R_SUCCESS);
+}
+
+/*
+ * If the first update operation fails with YXDOMAIN, the updater can
+ * conclude that the intended name is in use. The updater then
+ * attempts to confirm that the DNS name is not being used by some
+ * other host. The updater prepares a second UPDATE query in which the
+ * prerequisite is that the desired name has attached to it a DHCID RR
+ * whose contents match the client identity. The update section of
+ * this query deletes the existing A records on the name, and adds the
+ * A record that matches the DHCP binding and the DHCID RR with the
+ * client identity.
+ * -- "Interaction between DHCP and DNS"
+ *
+ * The message for the second step depends on if we are doing conflict
+ * resolution. If we are we include a prerequisite. If not we delete
+ * the DHCID in addition to all A rrsets.
+ *
+ * Conflict resolution:
+ * DHCID RR exists, and matches client identity.
+ * Delete A RRset.
+ * Add A RR.
+ *
+ * Conflict override:
+ * Delete DHCID RRs.
+ * Add DHCID RR
+ * Delete A RRset.
+ * Add A RR.
+ */
+
+static isc_result_t
+ddns_modify_fwd_add2(dhcp_ddns_cb_t *ddns_cb,
+ dhcp_ddns_data_t *dataspace,
+ dns_name_t *pname,
+ dns_name_t *uname)
+{
+ isc_result_t result;
+
+ /*
+ * If we are doing conflict resolution (unset) we use a prereq list.
+ * If not we delete the DHCID in addition to all A rrsets.
+ */
+ if ((ddns_cb->flags & DDNS_CONFLICT_OVERRIDE) == 0) {
+ /* Construct the prereq list */
+ /* The DHCID RR exists and matches the client identity */
+ result = make_dns_dataset(dns_rdataclass_in, dns_rdatatype_txt,
+ dataspace,
+ (unsigned char *)ddns_cb->dhcid.data,
+ ddns_cb->dhcid.len, 0);
+ if (result != ISC_R_SUCCESS) {
+ return(result);
+ }
+ ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link);
+ dataspace++;
+ } else {
+ /* Start constructing the update list.
+ * Conflict detection override: delete DHCID RRs */
+ result = make_dns_dataset(dns_rdataclass_any,
+ dns_rdatatype_txt,
+ dataspace, NULL, 0, 0);
+ if (result != ISC_R_SUCCESS) {
+ return(result);
+ }
+ ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link);
+ dataspace++;
+
+ /* Add current DHCID RR */
+ result = make_dns_dataset(dns_rdataclass_in, dns_rdatatype_txt,
+ dataspace,
+ (unsigned char *)ddns_cb->dhcid.data,
+ ddns_cb->dhcid.len, ddns_cb->ttl);
+ if (result != ISC_R_SUCCESS) {
+ return(result);
+ }
+ ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link);
+ dataspace++;
+ }
+
+ /* Start or continue constructing the update list */
+ /* Delete the A RRset */
+ result = make_dns_dataset(dns_rdataclass_any, ddns_cb->address_type,
+ dataspace, NULL, 0, 0);
+ if (result != ISC_R_SUCCESS) {
+ return(result);
+ }
+ ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link);
+ dataspace++;
+
+ /* Add the A RR */
+ result = make_dns_dataset(dns_rdataclass_in, ddns_cb->address_type,
+ dataspace,
+ (unsigned char *)ddns_cb->address.iabuf,
+ ddns_cb->address.len, ddns_cb->ttl);
+ if (result != ISC_R_SUCCESS) {
+ return(result);
+ }
+ ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link);
+
+ return(ISC_R_SUCCESS);
+}
+
+/*
+ * The entity chosen to handle the A record for this client (either the
+ * client or the server) SHOULD delete the A record that was added when
+ * the lease was made to the client.
+ *
+ * In order to perform this delete, the updater prepares an UPDATE
+ * query which contains two prerequisites. The first prerequisite
+ * asserts that the DHCID RR exists whose data is the client identity
+ * described in Section 4.3. The second prerequisite asserts that the
+ * data in the A RR contains the IP address of the lease that has
+ * expired or been released.
+ * -- "Interaction between DHCP and DNS"
+ *
+ * RFC 4703 has relaxed the prereqisites to only checking the DHCID RR
+ * and we have adopted that to minizmie problems due to interruptions
+ * when doing a deletion.
+ *
+ * First try has:
+ * DHCID RR exists, and matches client identity.
+ * Delete appropriate A RR.
+ */
+
+static isc_result_t
+ddns_modify_fwd_rem1(dhcp_ddns_cb_t *ddns_cb,
+ dhcp_ddns_data_t *dataspace,
+ dns_name_t *pname,
+ dns_name_t *uname)
+{
+ isc_result_t result;
+
+ /* Consruct the prereq list */
+ /* The DHCID RR exists and matches the client identity */
+ result = make_dns_dataset(dns_rdataclass_in, dns_rdatatype_txt,
+ dataspace,
+ (unsigned char *)ddns_cb->dhcid.data,
+ ddns_cb->dhcid.len, 0);
+ if (result != ISC_R_SUCCESS) {
+ return(result);
+ }
+ ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link);
+ dataspace++;
+
+ /* Construct the update list */
+ /* Delete A RRset */
+ result = make_dns_dataset(dns_rdataclass_none, ddns_cb->address_type,
+ dataspace,
+ (unsigned char *)ddns_cb->address.iabuf,
+ ddns_cb->address.len, 0);
+ if (result != ISC_R_SUCCESS) {
+ return(result);
+ }
+ ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link);
+
+ return(ISC_R_SUCCESS);
+}
+
+/*
+ * If the deletion of the A succeeded, and there are no A or AAAA
+ * records left for this domain, then we can blow away the DHCID
+ * record as well. We can't blow away the DHCID record above
+ * because it's possible that more than one record has been added
+ * to this domain name.
+ *
+ * Second query has:
+ * A RR does not exist.
+ * AAAA RR does not exist.
+ * Delete appropriate DHCID RR.
+ */
+
+static isc_result_t
+ddns_modify_fwd_rem2(dhcp_ddns_cb_t *ddns_cb,
+ dhcp_ddns_data_t *dataspace,
+ dns_name_t *pname,
+ dns_name_t *uname)
+{
+ isc_result_t result;
+
+ /* Construct the prereq list */
+ /* The A RR does not exist */
+ result = make_dns_dataset(dns_rdataclass_none, dns_rdatatype_a,
+ dataspace, NULL, 0, 0);
+ if (result != ISC_R_SUCCESS) {
+ return(result);
+ }
+ ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link);
+ dataspace++;
+
+ /* The AAAA RR does not exist */
+ result = make_dns_dataset(dns_rdataclass_none, dns_rdatatype_aaaa,
+ dataspace, NULL, 0, 0);
+ if (result != ISC_R_SUCCESS) {
+ return(result);
+ }
+ ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link);
+ dataspace++;
+
+ /* Construct the update list */
+ /* Delete DHCID RR */
+ result = make_dns_dataset(dns_rdataclass_none, dns_rdatatype_txt,
+ dataspace,
+ (unsigned char *)ddns_cb->dhcid.data,
+ ddns_cb->dhcid.len, 0);
+ if (result != ISC_R_SUCCESS) {
+ return(result);
+ }
+ ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link);
+
+ return(ISC_R_SUCCESS);
+}
+
+/*
+ * This routine converts from the task action call into something
+ * easier to work with. It also handles the common case of a signature
+ * or zone not being correct.
+ */
+void ddns_interlude(isc_task_t *taskp,
+ isc_event_t *eventp)
+{
+ dhcp_ddns_cb_t *ddns_cb = (dhcp_ddns_cb_t *)eventp->ev_arg;
+ dns_clientupdateevent_t *ddns_event = (dns_clientupdateevent_t *)eventp;
+ isc_result_t eresult = ddns_event->result;
+ isc_result_t result;
+
+ /* We've extracted the information we want from it, get rid of
+ * the event block.*/
+ isc_event_free(&eventp);
+
+#if defined (TRACING)
+ if (trace_record()) {
+ trace_ddns_input_write(ddns_cb, eresult);
+ }
+#endif
+
+#if defined (DEBUG_DNS_UPDATES)
+ print_dns_status(DDNS_PRINT_INBOUND, ddns_cb, eresult);
+#endif
+
+ /* This transaction is complete, clear the value */
+ dns_client_destroyupdatetrans(&ddns_cb->transaction);
+
+ /* If we cancelled or tried to cancel the operation we just
+ * need to clean up. */
+ if ((eresult == ISC_R_CANCELED) ||
+ ((ddns_cb->flags & DDNS_ABORT) != 0)) {
+#if defined (DEBUG_DNS_UPDATES)
+ log_info("DDNS: completeing transaction cancellation cb=%p, "
+ "flags=%x, %s",
+ ddns_cb, ddns_cb->flags, isc_result_totext(eresult));
+#endif
+ if ((ddns_cb->flags & DDNS_ABORT) == 0) {
+ log_info("DDNS: cleaning up lease pointer for a cancel "
+ "cb=%p", ddns_cb);
+ /*
+ * We shouldn't actually be able to get here but
+ * we are. This means we haven't cleaned up
+ * the lease pointer so we need to do that before
+ * freeing the cb.
+ */
+ ddns_cb->cur_func(ddns_cb, eresult);
+ return;
+ }
+
+ if (ddns_cb->next_op != NULL) {
+ /* if necessary cleanup up next op block */
+ ddns_cb_free(ddns_cb->next_op, MDL);
+ }
+ ddns_cb_free(ddns_cb, MDL);
+ return;
+ }
+
+ /* If we had a problem with our key or zone try again */
+ if ((eresult == DNS_R_NOTAUTH) ||
+ (eresult == DNS_R_NOTZONE)) {
+ int i;
+ /* Our zone information was questionable,
+ * repudiate it and try again */
+ log_error("DDNS: bad zone information, repudiating zone %s",
+ ddns_cb->zone_name);
+ repudiate_zone(&ddns_cb->zone);
+ ddns_cb->zone_name[0] = 0;
+ ISC_LIST_INIT(ddns_cb->zone_server_list);
+ for (i = 0; i < DHCP_MAXNS; i++) {
+ ISC_LINK_INIT(&ddns_cb->zone_addrs[i], link);
+ }
+
+ if ((ddns_cb->state == DDNS_STATE_ADD_PTR) ||
+ (ddns_cb->state == DDNS_STATE_REM_PTR)) {
+ result = ddns_modify_ptr(ddns_cb, MDL);
+ } else {
+ result = ddns_modify_fwd(ddns_cb, MDL);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ /* if we couldn't redo the query log it and
+ * let the next function clean it up */
+ log_info("DDNS: Failed to retry after zone failure");
+ ddns_cb->cur_func(ddns_cb, result);
+ }
+ return;
+ } else {
+ /* pass it along to be processed */
+ ddns_cb->cur_func(ddns_cb, eresult);
+ }
+
+ return;
+}
+
+/*
+ * This routine does the generic work for sending a ddns message to
+ * modify the forward record (A or AAAA) and calls one of a set of
+ * routines to build the specific message.
+ */
+
+isc_result_t
+ddns_modify_fwd(dhcp_ddns_cb_t *ddns_cb, const char *file, int line)
+{
+ isc_result_t result;
+ dns_tsec_t *tsec_key = NULL;
+
+ unsigned char *clientname;
+ dhcp_ddns_data_t *dataspace = NULL;
+ dns_namelist_t prereqlist, updatelist;
+ dns_fixedname_t zname0, pname0, uname0;
+ dns_name_t *zname = NULL, *pname, *uname;
+
+ isc_sockaddrlist_t *zlist = NULL;
+
+ /* Get a pointer to the clientname to make things easier. */
+ clientname = (unsigned char *)ddns_cb->fwd_name.data;
+
+ /* Extract and validate the type of the address. */
+ if (ddns_cb->address.len == 4) {
+ ddns_cb->address_type = dns_rdatatype_a;
+ } else if (ddns_cb->address.len == 16) {
+ ddns_cb->address_type = dns_rdatatype_aaaa;
+ } else {
+ return DHCP_R_INVALIDARG;
+ }
+
+ /*
+ * If we already have a zone use it, otherwise try to lookup the
+ * zone in our cache. If we find one we will have a pointer to
+ * the zone that needs to be dereferenced when we are done with it.
+ * If we don't find one that is okay we'll let the DNS code try and
+ * find the information for us.
+ */
+
+ if (ddns_cb->zone == NULL) {
+ result = find_cached_zone(ddns_cb, FIND_FORWARD);
+ }
+
+ /*
+ * If we have a zone try to get any information we need
+ * from it - name, addresses and the key. The address
+ * and key may be empty the name can't be.
+ */
+ if (ddns_cb->zone) {
+ /* Set up the zone name for use by DNS */
+ result = dhcp_isc_name(ddns_cb->zone_name, &zname0, &zname);
+ if (result != ISC_R_SUCCESS) {
+ log_error("Unable to build name for zone for "
+ "fwd update: %s %s",
+ ddns_cb->zone_name,
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ if (!(ISC_LIST_EMPTY(ddns_cb->zone_server_list))) {
+ /* If we have any addresses get them */
+ zlist = &ddns_cb->zone_server_list;
+ }
+
+
+ if (ddns_cb->zone->key != NULL) {
+ /*
+ * Not having a key is fine, having a key
+ * but not a tsec is odd so we warn the user.
+ */
+ /*sar*/
+ /* should we do the warning? */
+ tsec_key = ddns_cb->zone->key->tsec_key;
+ if (tsec_key == NULL) {
+ log_error("No tsec for use with key %s",
+ ddns_cb->zone->key->name);
+ }
+ }
+ }
+
+ /* Set up the DNS names for the prereq and update lists */
+ if (((result = dhcp_isc_name(clientname, &pname0, &pname))
+ != ISC_R_SUCCESS) ||
+ ((result = dhcp_isc_name(clientname, &uname0, &uname))
+ != ISC_R_SUCCESS)) {
+ log_error("Unable to build name for fwd update: %s %s",
+ clientname, isc_result_totext(result));
+ goto cleanup;
+ }
+
+ /* Allocate the various isc dns library structures we may require. */
+ dataspace = isc_mem_get(dhcp_gbl_ctx.mctx, sizeof(*dataspace) * 4);
+ if (dataspace == NULL) {
+ log_error("Unable to allocate memory for fwd update");
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ ISC_LIST_INIT(prereqlist);
+ ISC_LIST_INIT(updatelist);
+
+ switch(ddns_cb->state) {
+ case DDNS_STATE_ADD_FW_NXDOMAIN:
+ result = ddns_modify_fwd_add1(ddns_cb, dataspace,
+ pname, uname);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ ISC_LIST_APPEND(prereqlist, pname, link);
+ break;
+ case DDNS_STATE_ADD_FW_YXDHCID:
+ result = ddns_modify_fwd_add2(ddns_cb, dataspace,
+ pname, uname);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /* If we aren't doing conflict override we have entries
+ * in the pname list and we need to attach it to the
+ * prereqlist */
+
+ if ((ddns_cb->flags & DDNS_CONFLICT_OVERRIDE) == 0) {
+ ISC_LIST_APPEND(prereqlist, pname, link);
+ }
+
+ break;
+ case DDNS_STATE_REM_FW_YXDHCID:
+ result = ddns_modify_fwd_rem1(ddns_cb, dataspace,
+ pname, uname);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ ISC_LIST_APPEND(prereqlist, pname, link);
+ break;
+ case DDNS_STATE_REM_FW_NXRR:
+ result = ddns_modify_fwd_rem2(ddns_cb, dataspace,
+ pname, uname);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ ISC_LIST_APPEND(prereqlist, pname, link);
+ break;
+
+ default:
+ log_error("Invalid operation in ddns code.");
+ result = DHCP_R_INVALIDARG;
+ goto cleanup;
+ break;
+ }
+
+ /*
+ * We always have an update list but may not have a prereqlist
+ * if we are doing conflict override.
+ */
+ ISC_LIST_APPEND(updatelist, uname, link);
+
+ /* send the message, cleanup and return the result */
+ result = ddns_update(dhcp_gbl_ctx.dnsclient,
+ dns_rdataclass_in, zname,
+ &prereqlist, &updatelist,
+ zlist, tsec_key,
+ DNS_CLIENTRESOPT_ALLOWRUN,
+ dhcp_gbl_ctx.task,
+ ddns_interlude,
+ (void *)ddns_cb,
+ &ddns_cb->transaction);
+ if (result == ISC_R_FAMILYNOSUPPORT) {
+ log_info("Unable to perform DDNS update, "
+ "address family not supported");
+ }
+
+#if defined (DEBUG_DNS_UPDATES)
+ print_dns_status(DDNS_PRINT_OUTBOUND, ddns_cb, result);
+#endif
+
+ cleanup:
+#if defined (DEBUG_DNS_UPDATES)
+ if (result != ISC_R_SUCCESS) {
+ log_info("DDNS: %s(%d): error in ddns_modify_fwd %s for %p",
+ file, line, isc_result_totext(result), ddns_cb);
+ }
+#endif
+
+ if (dataspace != NULL) {
+ isc_mem_put(dhcp_gbl_ctx.mctx, dataspace,
+ sizeof(*dataspace) * 4);
+ }
+ return(result);
+}
+
+
+isc_result_t
+ddns_modify_ptr(dhcp_ddns_cb_t *ddns_cb, const char *file, int line)
+{
+ isc_result_t result;
+ dns_tsec_t *tsec_key = NULL;
+ unsigned char *ptrname;
+ dhcp_ddns_data_t *dataspace = NULL;
+ dns_namelist_t updatelist;
+ dns_fixedname_t zname0, uname0;
+ dns_name_t *zname = NULL, *uname;
+ isc_sockaddrlist_t *zlist = NULL;
+ unsigned char buf[256];
+ int buflen;
+
+ /*
+ * Try to lookup the zone in the zone cache. As with the forward
+ * case it's okay if we don't have one, the DNS code will try to
+ * find something also if we succeed we will need to dereference
+ * the zone later. Unlike with the forward case we assume we won't
+ * have a pre-existing zone.
+ */
+ result = find_cached_zone(ddns_cb, FIND_REVERSE);
+ if ((result == ISC_R_SUCCESS) &&
+ !(ISC_LIST_EMPTY(ddns_cb->zone_server_list))) {
+ /* Set up the zone name for use by DNS */
+ result = dhcp_isc_name(ddns_cb->zone_name, &zname0, &zname);
+ if (result != ISC_R_SUCCESS) {
+ log_error("Unable to build name for zone for "
+ "fwd update: %s %s",
+ ddns_cb->zone_name,
+ isc_result_totext(result));
+ goto cleanup;
+ }
+ /* If we have any addresses get them */
+ if (!(ISC_LIST_EMPTY(ddns_cb->zone_server_list))) {
+ zlist = &ddns_cb->zone_server_list;
+ }
+
+ /*
+ * If we now have a zone try to get the key, NULL is okay,
+ * having a key but not a tsec is odd so we warn.
+ */
+ /*sar*/
+ /* should we do the warning if we have a key but no tsec? */
+ if ((ddns_cb->zone != NULL) && (ddns_cb->zone->key != NULL)) {
+ tsec_key = ddns_cb->zone->key->tsec_key;
+ if (tsec_key == NULL) {
+ log_error("No tsec for use with key %s",
+ ddns_cb->zone->key->name);
+ }
+ }
+ }
+
+ /* We must have a name for the update list */
+ /* Get a pointer to the ptrname to make things easier. */
+ ptrname = (unsigned char *)ddns_cb->rev_name.data;
+
+ if ((result = dhcp_isc_name(ptrname, &uname0, &uname))
+ != ISC_R_SUCCESS) {
+ log_error("Unable to build name for fwd update: %s %s",
+ ptrname, isc_result_totext(result));
+ goto cleanup;
+ }
+
+ /*
+ * Allocate the various isc dns library structures we may require.
+ * Allocating one blob avoids being halfway through the process
+ * and being unable to allocate as well as making the free easy.
+ */
+ dataspace = isc_mem_get(dhcp_gbl_ctx.mctx, sizeof(*dataspace) * 2);
+ if (dataspace == NULL) {
+ log_error("Unable to allocate memory for fwd update");
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ ISC_LIST_INIT(updatelist);
+
+ /*
+ * Construct the update list
+ * We always delete what's currently there
+ * Delete PTR RR.
+ */
+ result = make_dns_dataset(dns_rdataclass_any, dns_rdatatype_ptr,
+ &dataspace[0], NULL, 0, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ ISC_LIST_APPEND(uname->list, &dataspace[0].rdataset, link);
+
+ /*
+ * If we are updating the pointer we then add the new one
+ * Add PTR RR.
+ */
+ if (ddns_cb->state == DDNS_STATE_ADD_PTR) {
+#if 0
+ /*
+ * I've left this dead code in the file for now in case
+ * we decide to try and get rid of the ns_name functions.
+ * sar
+ */
+
+ /*
+ * Need to convert pointer into on the wire representation
+ * We replace the '.' characters with the lengths of the
+ * next name and add a length to the beginning for the first
+ * name.
+ */
+ if (ddns_cb->fwd_name.len == 1) {
+ /* the root */
+ buf[0] = 0;
+ buflen = 1;
+ } else {
+ unsigned char *cp;
+ buf[0] = '.';
+ memcpy(&buf[1], ddns_cb->fwd_name.data,
+ ddns_cb->fwd_name.len);
+ for(cp = buf + ddns_cb->fwd_name.len, buflen = 0;
+ cp != buf;
+ cp--) {
+ if (*cp == '.') {
+ *cp = buflen;
+ buflen = 0;
+ } else {
+ buflen++;
+ }
+ }
+ *cp = buflen;
+ buflen = ddns_cb->fwd_name.len + 1;
+ }
+#endif
+ /*
+ * Need to convert pointer into on the wire representation
+ */
+ if (MRns_name_pton((char *)ddns_cb->fwd_name.data,
+ buf, 256) == -1) {
+ goto cleanup;
+ }
+ buflen = 0;
+ while (buf[buflen] != 0) {
+ buflen += buf[buflen] + 1;
+ }
+ buflen++;
+
+ result = make_dns_dataset(dns_rdataclass_in,
+ dns_rdatatype_ptr,
+ &dataspace[1],
+ buf, buflen, ddns_cb->ttl);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ ISC_LIST_APPEND(uname->list, &dataspace[1].rdataset, link);
+ }
+
+ ISC_LIST_APPEND(updatelist, uname, link);
+
+ /*sar*/
+ /*
+ * for now I'll cleanup the dataset immediately, it would be
+ * more efficient to keep it around in case the signaturure failed
+ * and we wanted to retry it.
+ */
+ /* send the message, cleanup and return the result */
+ result = ddns_update((dns_client_t *)dhcp_gbl_ctx.dnsclient,
+ dns_rdataclass_in, zname,
+ NULL, &updatelist,
+ zlist, tsec_key,
+ DNS_CLIENTRESOPT_ALLOWRUN,
+ dhcp_gbl_ctx.task,
+ ddns_interlude, (void *)ddns_cb,
+ &ddns_cb->transaction);
+ if (result == ISC_R_FAMILYNOSUPPORT) {
+ log_info("Unable to perform DDNS update, "
+ "address family not supported");
+ }
+
+#if defined (DEBUG_DNS_UPDATES)
+ print_dns_status(DDNS_PRINT_OUTBOUND, ddns_cb, result);
+#endif
+
+ cleanup:
+#if defined (DEBUG_DNS_UPDATES)
+ if (result != ISC_R_SUCCESS) {
+ log_info("DDNS: %s(%d): error in ddns_modify_ptr %s for %p",
+ file, line, isc_result_totext(result), ddns_cb);
+ }
+#endif
+
+ if (dataspace != NULL) {
+ isc_mem_put(dhcp_gbl_ctx.mctx, dataspace,
+ sizeof(*dataspace) * 2);
+ }
+ return(result);
+}
+
+void
+ddns_cancel(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) {
+ ddns_cb->flags |= DDNS_ABORT;
+ if (ddns_cb->transaction != NULL) {
+ dns_client_cancelupdate((dns_clientupdatetrans_t *)
+ ddns_cb->transaction);
+ }
+ ddns_cb->lease = NULL;
+
+#if defined (DEBUG_DNS_UPDATES)
+ log_info("DDNS: %s(%d): cancelling transaction for %p",
+ file, line, ddns_cb);
+#endif
+}
+
+#endif /* NSUPDATE */
+
+HASH_FUNCTIONS (dns_zone, const char *, struct dns_zone, dns_zone_hash_t,
+ dns_zone_reference, dns_zone_dereference, do_case_hash)
diff --git a/common/ethernet.c b/common/ethernet.c
new file mode 100644
index 0000000..cd67ae2
--- /dev/null
+++ b/common/ethernet.c
@@ -0,0 +1,87 @@
+/* ethernet.c
+
+ Packet assembly code, originally contributed by Archie Cobbs. */
+
+/*
+ * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+
+#if defined (PACKET_ASSEMBLY) || defined (PACKET_DECODING)
+#include "includes/netinet/if_ether.h"
+#endif /* PACKET_ASSEMBLY || PACKET_DECODING */
+
+#if defined (PACKET_ASSEMBLY)
+/* Assemble an hardware header... */
+
+void assemble_ethernet_header (interface, buf, bufix, to)
+ struct interface_info *interface;
+ unsigned char *buf;
+ unsigned *bufix;
+ struct hardware *to;
+{
+ struct isc_ether_header eh;
+
+ if (to && to -> hlen == 7) /* XXX */
+ memcpy (eh.ether_dhost, &to -> hbuf [1],
+ sizeof eh.ether_dhost);
+ else
+ memset (eh.ether_dhost, 0xff, sizeof (eh.ether_dhost));
+ if (interface -> hw_address.hlen - 1 == sizeof (eh.ether_shost))
+ memcpy (eh.ether_shost, &interface -> hw_address.hbuf [1],
+ sizeof (eh.ether_shost));
+ else
+ memset (eh.ether_shost, 0x00, sizeof (eh.ether_shost));
+
+ eh.ether_type = htons (ETHERTYPE_IP);
+
+ memcpy (&buf [*bufix], &eh, ETHER_HEADER_SIZE);
+ *bufix += ETHER_HEADER_SIZE;
+}
+#endif /* PACKET_ASSEMBLY */
+
+#ifdef PACKET_DECODING
+/* Decode a hardware header... */
+
+ssize_t decode_ethernet_header (interface, buf, bufix, from)
+ struct interface_info *interface;
+ unsigned char *buf;
+ unsigned bufix;
+ struct hardware *from;
+{
+ struct isc_ether_header eh;
+
+ memcpy (&eh, buf + bufix, ETHER_HEADER_SIZE);
+
+#ifdef USERLAND_FILTER
+ if (ntohs (eh.ether_type) != ETHERTYPE_IP)
+ return -1;
+#endif
+ memcpy (&from -> hbuf [1], eh.ether_shost, sizeof (eh.ether_shost));
+ from -> hbuf [0] = ARPHRD_ETHER;
+ from -> hlen = (sizeof eh.ether_shost) + 1;
+
+ return ETHER_HEADER_SIZE;
+}
+#endif /* PACKET_DECODING */
diff --git a/common/execute.c b/common/execute.c
new file mode 100644
index 0000000..d031392
--- /dev/null
+++ b/common/execute.c
@@ -0,0 +1,1157 @@
+/* execute.c
+
+ Support for executable statements. */
+
+/*
+ * Copyright (c) 2009,2013,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2007 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1998-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+#include <omapip/omapip_p.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+int execute_statements (result, packet, lease, client_state,
+ in_options, out_options, scope, statements)
+ struct binding_value **result;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *out_options;
+ struct binding_scope **scope;
+ struct executable_statement *statements;
+{
+ struct executable_statement *r, *e, *next;
+ int rc;
+ int status;
+ struct binding *binding;
+ struct data_string ds;
+ struct binding_scope *ns;
+
+ if (!statements)
+ return 1;
+
+ r = (struct executable_statement *)0;
+ next = (struct executable_statement *)0;
+ e = (struct executable_statement *)0;
+ executable_statement_reference (&r, statements, MDL);
+ while (r && !(result && *result)) {
+ if (r -> next)
+ executable_statement_reference (&next, r -> next, MDL);
+ switch (r -> op) {
+ case statements_statement:
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: statements");
+#endif
+ status = execute_statements (result, packet, lease,
+ client_state, in_options,
+ out_options, scope,
+ r -> data.statements);
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: statements returns %d", status);
+#endif
+ if (!status)
+ return 0;
+ break;
+
+ case on_statement:
+ if (lease) {
+ if (r -> data.on.evtypes & ON_EXPIRY) {
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: on expiry");
+#endif
+ if (lease -> on_expiry)
+ executable_statement_dereference
+ (&lease -> on_expiry, MDL);
+ if (r -> data.on.statements)
+ executable_statement_reference
+ (&lease -> on_expiry,
+ r -> data.on.statements, MDL);
+ }
+ if (r -> data.on.evtypes & ON_RELEASE) {
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: on release");
+#endif
+ if (lease -> on_release)
+ executable_statement_dereference
+ (&lease -> on_release, MDL);
+ if (r -> data.on.statements)
+ executable_statement_reference
+ (&lease -> on_release,
+ r -> data.on.statements, MDL);
+ }
+ if (r -> data.on.evtypes & ON_COMMIT) {
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: on commit");
+#endif
+ if (lease -> on_commit)
+ executable_statement_dereference
+ (&lease -> on_commit, MDL);
+ if (r -> data.on.statements)
+ executable_statement_reference
+ (&lease -> on_commit,
+ r -> data.on.statements, MDL);
+ }
+ }
+ break;
+
+ case switch_statement:
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: switch");
+#endif
+ status = (find_matching_case
+ (&e, packet, lease, client_state,
+ in_options, out_options, scope,
+ r -> data.s_switch.expr,
+ r -> data.s_switch.statements));
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: switch: case %lx", (unsigned long)e);
+#endif
+ if (status) {
+ if (!(execute_statements
+ (result, packet, lease, client_state,
+ in_options, out_options, scope, e))) {
+ executable_statement_dereference
+ (&e, MDL);
+ return 0;
+ }
+ executable_statement_dereference (&e, MDL);
+ }
+ break;
+
+ /* These have no effect when executed. */
+ case case_statement:
+ case default_statement:
+ break;
+
+ case if_statement:
+ status = (evaluate_boolean_expression
+ (&rc, packet,
+ lease, client_state, in_options,
+ out_options, scope, r -> data.ie.expr));
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: if %s", (status
+ ? (rc ? "true" : "false")
+ : "NULL"));
+#endif
+ /* XXX Treat NULL as false */
+ if (!status)
+ rc = 0;
+ if (!execute_statements
+ (result, packet, lease, client_state,
+ in_options, out_options, scope,
+ rc ? r -> data.ie.tc : r -> data.ie.fc))
+ return 0;
+ break;
+
+ case eval_statement:
+ status = evaluate_expression
+ ((struct binding_value **)0,
+ packet, lease, client_state, in_options,
+ out_options, scope, r -> data.eval, MDL);
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: evaluate: %s",
+ (status ? "succeeded" : "failed"));
+#else
+ POST(status);
+#endif
+ break;
+
+ case execute_statement: {
+#ifdef ENABLE_EXECUTE
+ struct expression *expr;
+ char **argv;
+ int i, argc = r->data.execute.argc;
+ pid_t p;
+
+ /* save room for the command and the NULL terminator */
+ argv = dmalloc((argc + 2) * sizeof(*argv), MDL);
+ if (!argv)
+ break;
+
+ argv[0] = dmalloc(strlen(r->data.execute.command) + 1,
+ MDL);
+ if (argv[0]) {
+ strcpy(argv[0], r->data.execute.command);
+ } else {
+ goto execute_out;
+ }
+
+ log_debug("execute_statement argv[0] = %s", argv[0]);
+
+ for (i = 1, expr = r->data.execute.arglist; expr;
+ expr = expr->data.arg.next, i++) {
+ memset (&ds, 0, sizeof(ds));
+ status = (evaluate_data_expression
+ (&ds, packet,
+ lease, client_state, in_options,
+ out_options, scope,
+ expr->data.arg.val, MDL));
+ if (status) {
+ argv[i] = dmalloc(ds.len + 1, MDL);
+ if (argv[i]) {
+ memcpy(argv[i], ds.data,
+ ds.len);
+ argv[i][ds.len] = 0;
+ log_debug("execute_statement argv[%d] = %s", i, argv[i]);
+ }
+ data_string_forget (&ds, MDL);
+ if (!argv[i]) {
+ log_debug("execute_statement failed argv[%d]", i);
+ goto execute_out;
+ }
+ } else {
+ log_debug("execute: bad arg %d", i);
+ goto execute_out;
+ }
+ }
+ argv[i] = NULL;
+
+ if ((p = fork()) > 0) {
+ int status;
+ waitpid(p, &status, 0);
+
+ if (status) {
+ log_error("execute: %s exit status %d",
+ argv[0], status);
+ }
+ } else if (p == 0) {
+ execvp(argv[0], argv);
+ log_error("Unable to execute %s: %m", argv[0]);
+ _exit(127);
+ } else {
+ log_error("execute: fork() failed");
+ }
+
+ execute_out:
+ for (i = 0; i <= argc; i++) {
+ if(argv[i])
+ dfree(argv[i], MDL);
+ }
+
+ dfree(argv, MDL);
+#else /* !ENABLE_EXECUTE */
+ log_fatal("Impossible case at %s:%d (ENABLE_EXECUTE "
+ "is not defined).", MDL);
+#endif /* ENABLE_EXECUTE */
+ break;
+ }
+
+ case return_statement:
+ status = evaluate_expression
+ (result, packet,
+ lease, client_state, in_options,
+ out_options, scope, r -> data.retval, MDL);
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: return: %s",
+ (status ? "succeeded" : "failed"));
+#else
+ POST(status);
+#endif
+ break;
+
+ case add_statement:
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: add %s", (r -> data.add -> name
+ ? r -> data.add -> name
+ : "<unnamed class>"));
+#endif
+ classify (packet, r -> data.add);
+ break;
+
+ case break_statement:
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: break");
+#endif
+ return 1;
+
+ case supersede_option_statement:
+ case send_option_statement:
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: %s option %s.%s",
+ (r -> op == supersede_option_statement
+ ? "supersede" : "send"),
+ r -> data.option -> option -> universe -> name,
+ r -> data.option -> option -> name);
+ goto option_statement;
+#endif
+ case default_option_statement:
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: default option %s.%s",
+ r -> data.option -> option -> universe -> name,
+ r -> data.option -> option -> name);
+ goto option_statement;
+#endif
+ case append_option_statement:
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: append option %s.%s",
+ r -> data.option -> option -> universe -> name,
+ r -> data.option -> option -> name);
+ goto option_statement;
+#endif
+ case prepend_option_statement:
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: prepend option %s.%s",
+ r -> data.option -> option -> universe -> name,
+ r -> data.option -> option -> name);
+ option_statement:
+#endif
+ set_option (r -> data.option -> option -> universe,
+ out_options, r -> data.option, r -> op);
+ break;
+
+ case set_statement:
+ case define_statement:
+ status = 1;
+ if (!scope) {
+ log_error("set %s: no scope",
+ r->data.set.name);
+ break;
+ }
+ if (!*scope) {
+ if (!binding_scope_allocate(scope, MDL)) {
+ log_error("set %s: can't allocate scope",
+ r->data.set.name);
+ break;
+ }
+ }
+ binding = find_binding(*scope, r->data.set.name);
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug("exec: set %s", r->data.set.name);
+#else
+ POST(status);
+#endif
+ if (binding == NULL) {
+ binding = dmalloc(sizeof(*binding), MDL);
+ if (binding != NULL) {
+ memset(binding, 0, sizeof(*binding));
+ binding->name =
+ dmalloc(strlen
+ (r->data.set.name) + 1,
+ MDL);
+ if (binding->name != NULL) {
+ strcpy(binding->name, r->data.set.name);
+ binding->next = (*scope)->bindings;
+ (*scope)->bindings = binding;
+ } else {
+ dfree(binding, MDL);
+ binding = NULL;
+ }
+ }
+ }
+ if (binding != NULL) {
+ if (binding->value != NULL)
+ binding_value_dereference
+ (&binding->value, MDL);
+ if (r->op == set_statement) {
+ status = (evaluate_expression
+ (&binding->value, packet,
+ lease, client_state,
+ in_options, out_options,
+ scope, r->data.set.expr,
+ MDL));
+ } else {
+ if (!(binding_value_allocate
+ (&binding->value, MDL))) {
+ dfree(binding, MDL);
+ binding = NULL;
+ }
+ if ((binding != NULL) &&
+ (binding->value != NULL)) {
+ binding->value->type =
+ binding_function;
+ (fundef_reference
+ (&binding->value->value.fundef,
+ r->data.set.expr->data.func,
+ MDL));
+ }
+ }
+ }
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: set %s%s", r -> data.set.name,
+ (binding && status ? "" : " (failed)"));
+#else
+ POST(status);
+#endif
+ break;
+
+ case unset_statement:
+ if (!scope || !*scope)
+ break;
+ binding = find_binding (*scope, r -> data.unset);
+ if (binding) {
+ if (binding -> value)
+ binding_value_dereference
+ (&binding -> value, MDL);
+ status = 1;
+ } else
+ status = 0;
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: unset %s: %s", r -> data.unset,
+ (status ? "found" : "not found"));
+#else
+ POST(status);
+#endif
+ break;
+
+ case let_statement:
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug("exec: let %s", r->data.let.name);
+#endif
+ status = 0;
+ ns = NULL;
+ binding_scope_allocate (&ns, MDL);
+ e = r;
+
+ next_let:
+ if (ns) {
+ binding = dmalloc(sizeof(*binding), MDL);
+ memset(binding, 0, sizeof(*binding));
+ if (!binding) {
+ blb:
+ binding_scope_dereference(&ns, MDL);
+ } else {
+ binding->name =
+ dmalloc(strlen
+ (e->data.let.name + 1),
+ MDL);
+ if (binding->name)
+ strcpy(binding->name,
+ e->data.let.name);
+ else {
+ dfree(binding, MDL);
+ binding = NULL;
+ goto blb;
+ }
+ }
+ } else
+ binding = NULL;
+
+ if (ns && binding) {
+ status = (evaluate_expression
+ (&binding->value, packet, lease,
+ client_state,
+ in_options, out_options,
+ scope, e->data.set.expr, MDL));
+ binding->next = ns->bindings;
+ ns->bindings = binding;
+ }
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug("exec: let %s%s", e->data.let.name,
+ (binding && status ? "" : "failed"));
+#else
+ POST(status);
+#endif
+ if (!e->data.let.statements) {
+ } else if (e->data.let.statements->op ==
+ let_statement) {
+ e = e->data.let.statements;
+ goto next_let;
+ } else if (ns) {
+ if (scope && *scope)
+ binding_scope_reference(&ns->outer,
+ *scope, MDL);
+ execute_statements
+ (result, packet, lease,
+ client_state,
+ in_options, out_options,
+ &ns, e->data.let.statements);
+ }
+ if (ns)
+ binding_scope_dereference(&ns, MDL);
+ break;
+
+ case log_statement:
+ memset (&ds, 0, sizeof ds);
+ status = (evaluate_data_expression
+ (&ds, packet,
+ lease, client_state, in_options,
+ out_options, scope, r -> data.log.expr,
+ MDL));
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("exec: log");
+#endif
+
+ if (status) {
+ switch (r -> data.log.priority) {
+ case log_priority_fatal:
+ log_fatal ("%.*s", (int)ds.len,
+ ds.data);
+ break;
+ case log_priority_error:
+ log_error ("%.*s", (int)ds.len,
+ ds.data);
+ break;
+ case log_priority_debug:
+ log_debug ("%.*s", (int)ds.len,
+ ds.data);
+ break;
+ case log_priority_info:
+ log_info ("%.*s", (int)ds.len,
+ ds.data);
+ break;
+ }
+ data_string_forget (&ds, MDL);
+ }
+
+ break;
+
+ default:
+ log_error ("bogus statement type %d", r -> op);
+ break;
+ }
+ executable_statement_dereference (&r, MDL);
+ if (next) {
+ executable_statement_reference (&r, next, MDL);
+ executable_statement_dereference (&next, MDL);
+ }
+ }
+
+ return 1;
+}
+
+/* Execute all the statements in a particular scope, and all statements in
+ scopes outer from that scope, but if a particular limiting scope is
+ reached, do not execute statements in that scope or in scopes outer
+ from it. More specific scopes need to take precedence over less
+ specific scopes, so we recursively traverse the scope list, executing
+ the most outer scope first. */
+
+void execute_statements_in_scope (result, packet,
+ lease, client_state, in_options, out_options,
+ scope, group, limiting_group)
+ struct binding_value **result;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *out_options;
+ struct binding_scope **scope;
+ struct group *group;
+ struct group *limiting_group;
+{
+ struct group *limit;
+
+ /* If we've recursed as far as we can, return. */
+ if (!group)
+ return;
+
+ /* As soon as we get to a scope that is outer than the limiting
+ scope, we are done. This is so that if somebody does something
+ like this, it does the expected thing:
+
+ domain-name "fugue.com";
+ shared-network FOO {
+ host bar {
+ domain-name "othello.fugue.com";
+ fixed-address 10.20.30.40;
+ }
+ subnet 10.20.30.0 netmask 255.255.255.0 {
+ domain-name "manhattan.fugue.com";
+ }
+ }
+
+ The problem with the above arrangement is that the host's
+ group nesting will be host -> shared-network -> top-level,
+ and the limiting scope when we evaluate the host's scope
+ will be the subnet -> shared-network -> top-level, so we need
+ to know when we evaluate the host's scope to stop before we
+ evaluate the shared-networks scope, because it's outer than
+ the limiting scope, which means we've already evaluated it. */
+
+ for (limit = limiting_group; limit; limit = limit -> next) {
+ if (group == limit)
+ return;
+ }
+
+ if (group -> next)
+ execute_statements_in_scope (result, packet,
+ lease, client_state,
+ in_options, out_options, scope,
+ group -> next, limiting_group);
+ execute_statements (result, packet, lease, client_state, in_options,
+ out_options, scope, group -> statements);
+}
+
+/* Dereference or free any subexpressions of a statement being freed. */
+
+int executable_statement_dereference (ptr, file, line)
+ struct executable_statement **ptr;
+ const char *file;
+ int line;
+{
+ if (!ptr || !*ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ (*ptr) -> refcnt--;
+ rc_register (file, line, ptr, *ptr, (*ptr) -> refcnt, 1, RC_MISC);
+ if ((*ptr) -> refcnt > 0) {
+ *ptr = (struct executable_statement *)0;
+ return 1;
+ }
+
+ if ((*ptr) -> refcnt < 0) {
+ log_error ("%s(%d): negative refcnt!", file, line);
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history (*ptr);
+#endif
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ if ((*ptr) -> next)
+ executable_statement_dereference (&(*ptr) -> next, file, line);
+
+ switch ((*ptr) -> op) {
+ case statements_statement:
+ if ((*ptr) -> data.statements)
+ executable_statement_dereference
+ (&(*ptr) -> data.statements, file, line);
+ break;
+
+ case on_statement:
+ if ((*ptr) -> data.on.statements)
+ executable_statement_dereference
+ (&(*ptr) -> data.on.statements, file, line);
+ break;
+
+ case switch_statement:
+ if ((*ptr) -> data.s_switch.statements)
+ executable_statement_dereference
+ (&(*ptr) -> data.on.statements, file, line);
+ if ((*ptr) -> data.s_switch.expr)
+ expression_dereference (&(*ptr) -> data.s_switch.expr,
+ file, line);
+ break;
+
+ case case_statement:
+ if ((*ptr) -> data.s_switch.expr)
+ expression_dereference (&(*ptr) -> data.c_case,
+ file, line);
+ break;
+
+ case if_statement:
+ if ((*ptr) -> data.ie.expr)
+ expression_dereference (&(*ptr) -> data.ie.expr,
+ file, line);
+ if ((*ptr) -> data.ie.tc)
+ executable_statement_dereference
+ (&(*ptr) -> data.ie.tc, file, line);
+ if ((*ptr) -> data.ie.fc)
+ executable_statement_dereference
+ (&(*ptr) -> data.ie.fc, file, line);
+ break;
+
+ case eval_statement:
+ if ((*ptr) -> data.eval)
+ expression_dereference (&(*ptr) -> data.eval,
+ file, line);
+ break;
+
+ case return_statement:
+ if ((*ptr) -> data.eval)
+ expression_dereference (&(*ptr) -> data.eval,
+ file, line);
+ break;
+
+ case set_statement:
+ if ((*ptr)->data.set.name)
+ dfree ((*ptr)->data.set.name, file, line);
+ if ((*ptr)->data.set.expr)
+ expression_dereference (&(*ptr) -> data.set.expr,
+ file, line);
+ break;
+
+ case unset_statement:
+ if ((*ptr)->data.unset)
+ dfree ((*ptr)->data.unset, file, line);
+ break;
+
+ case execute_statement:
+ if ((*ptr)->data.execute.command)
+ dfree ((*ptr)->data.execute.command, file, line);
+ if ((*ptr)->data.execute.arglist)
+ expression_dereference (&(*ptr) -> data.execute.arglist,
+ file, line);
+ break;
+
+ case supersede_option_statement:
+ case send_option_statement:
+ case default_option_statement:
+ case append_option_statement:
+ case prepend_option_statement:
+ if ((*ptr) -> data.option)
+ option_cache_dereference (&(*ptr) -> data.option,
+ file, line);
+ break;
+
+ default:
+ /* Nothing to do. */
+ break;
+ }
+
+ dfree ((*ptr), file, line);
+ *ptr = (struct executable_statement *)0;
+ return 1;
+}
+
+void write_statements (file, statements, indent)
+ FILE *file;
+ struct executable_statement *statements;
+ int indent;
+{
+#if defined ENABLE_EXECUTE
+ struct expression *expr;
+#endif
+ struct executable_statement *r, *x;
+ const char *s, *t, *dot;
+ int col;
+
+ if (!statements)
+ return;
+
+ for (r = statements; r; r = r -> next) {
+ switch (r -> op) {
+ case statements_statement:
+ write_statements (file, r -> data.statements, indent);
+ break;
+
+ case on_statement:
+ indent_spaces (file, indent);
+ fprintf (file, "on ");
+ s = "";
+ if (r -> data.on.evtypes & ON_EXPIRY) {
+ fprintf (file, "%sexpiry", s);
+ s = " or ";
+ }
+ if (r -> data.on.evtypes & ON_COMMIT) {
+ fprintf (file, "%scommit", s);
+ s = " or ";
+ }
+ if (r -> data.on.evtypes & ON_RELEASE) {
+ fprintf (file, "%srelease", s);
+ /* s = " or "; */
+ }
+ if (r -> data.on.statements) {
+ fprintf (file, " {");
+ write_statements (file,
+ r -> data.on.statements,
+ indent + 2);
+ indent_spaces (file, indent);
+ fprintf (file, "}");
+ } else {
+ fprintf (file, ";");
+ }
+ break;
+
+ case switch_statement:
+ indent_spaces (file, indent);
+ fprintf (file, "switch (");
+ col = write_expression (file,
+ r -> data.s_switch.expr,
+ indent + 7, indent + 7, 1);
+ col = token_print_indent (file, col, indent + 7,
+ "", "", ")");
+ token_print_indent (file,
+ col, indent, " ", "", "{");
+ write_statements (file, r -> data.s_switch.statements,
+ indent + 2);
+ indent_spaces (file, indent);
+ fprintf (file, "}");
+ break;
+
+ case case_statement:
+ indent_spaces (file, indent - 1);
+ fprintf (file, "case ");
+ col = write_expression (file,
+ r -> data.s_switch.expr,
+ indent + 5, indent + 5, 1);
+ token_print_indent (file, col, indent + 5,
+ "", "", ":");
+ break;
+
+ case default_statement:
+ indent_spaces (file, indent - 1);
+ fprintf (file, "default: ");
+ break;
+
+ case if_statement:
+ indent_spaces (file, indent);
+ fprintf (file, "if ");
+ x = r;
+ col = write_expression (file,
+ x -> data.ie.expr,
+ indent + 3, indent + 3, 1);
+ else_if:
+ token_print_indent (file, col, indent, " ", "", "{");
+ write_statements (file, x -> data.ie.tc, indent + 2);
+ if (x -> data.ie.fc &&
+ x -> data.ie.fc -> op == if_statement &&
+ !x -> data.ie.fc -> next) {
+ indent_spaces (file, indent);
+ fprintf (file, "} elsif ");
+ x = x -> data.ie.fc;
+ col = write_expression (file,
+ x -> data.ie.expr,
+ indent + 6,
+ indent + 6, 1);
+ goto else_if;
+ }
+ if (x -> data.ie.fc) {
+ indent_spaces (file, indent);
+ fprintf (file, "} else {");
+ write_statements (file, x -> data.ie.fc,
+ indent + 2);
+ }
+ indent_spaces (file, indent);
+ fprintf (file, "}");
+ break;
+
+ case eval_statement:
+ indent_spaces (file, indent);
+ fprintf (file, "eval ");
+ (void) write_expression (file, r -> data.eval,
+ indent + 5, indent + 5, 1);
+ fprintf (file, ";");
+ break;
+
+ case return_statement:
+ indent_spaces (file, indent);
+ fprintf (file, "return;");
+ break;
+
+ case add_statement:
+ indent_spaces (file, indent);
+ fprintf (file, "add \"%s\"", r -> data.add -> name);
+ break;
+
+ case break_statement:
+ indent_spaces (file, indent);
+ fprintf (file, "break;");
+ break;
+
+ case supersede_option_statement:
+ case send_option_statement:
+ s = "supersede";
+ goto option_statement;
+
+ case default_option_statement:
+ s = "default";
+ goto option_statement;
+
+ case append_option_statement:
+ s = "append";
+ goto option_statement;
+
+ case prepend_option_statement:
+ s = "prepend";
+ option_statement:
+ /* Note: the reason we don't try to pretty print
+ the option here is that the format of the option
+ may change in dhcpd.conf, and then when this
+ statement was read back, it would cause a syntax
+ error. */
+ if (r -> data.option -> option -> universe ==
+ &dhcp_universe) {
+ t = "";
+ dot = "";
+ } else {
+ t = (r -> data.option -> option ->
+ universe -> name);
+ dot = ".";
+ }
+ indent_spaces (file, indent);
+ fprintf (file, "%s %s%s%s = ", s, t, dot,
+ r -> data.option -> option -> name);
+ col = (indent + strlen (s) + strlen (t) +
+ strlen (dot) + strlen (r -> data.option ->
+ option -> name) + 4);
+ if (r -> data.option -> expression)
+ write_expression
+ (file,
+ r -> data.option -> expression,
+ col, indent + 8, 1);
+ else
+ token_indent_data_string
+ (file, col, indent + 8, "", "",
+ &r -> data.option -> data);
+
+ fprintf (file, ";"); /* XXX */
+ break;
+
+ case set_statement:
+ indent_spaces (file, indent);
+ fprintf (file, "set ");
+ col = token_print_indent (file, indent + 4, indent + 4,
+ "", "", r -> data.set.name);
+ (void) token_print_indent (file, col, indent + 4,
+ " ", " ", "=");
+ col = write_expression (file, r -> data.set.expr,
+ indent + 3, indent + 3, 0);
+ (void) token_print_indent (file, col, indent + 4,
+ " ", "", ";");
+ break;
+
+ case unset_statement:
+ indent_spaces (file, indent);
+ fprintf (file, "unset ");
+ col = token_print_indent (file, indent + 6, indent + 6,
+ "", "", r -> data.set.name);
+ (void) token_print_indent (file, col, indent + 6,
+ " ", "", ";");
+ break;
+
+ case log_statement:
+ indent_spaces (file, indent);
+ fprintf (file, "log ");
+ col = token_print_indent (file, indent + 4, indent + 4,
+ "", "", "(");
+ switch (r -> data.log.priority) {
+ case log_priority_fatal:
+ (void) token_print_indent
+ (file, col, indent + 4, "",
+ " ", "fatal,");
+ break;
+ case log_priority_error:
+ (void) token_print_indent
+ (file, col, indent + 4, "",
+ " ", "error,");
+ break;
+ case log_priority_debug:
+ (void) token_print_indent
+ (file, col, indent + 4, "",
+ " ", "debug,");
+ break;
+ case log_priority_info:
+ (void) token_print_indent
+ (file, col, indent + 4, "",
+ " ", "info,");
+ break;
+ }
+ col = write_expression (file, r -> data.log.expr,
+ indent + 4, indent + 4, 0);
+ (void) token_print_indent (file, col, indent + 4,
+ "", "", ");");
+
+ break;
+
+ case execute_statement:
+#ifdef ENABLE_EXECUTE
+ indent_spaces (file, indent);
+ col = token_print_indent(file, indent + 4, indent + 4,
+ "", "", "execute");
+ col = token_print_indent(file, col, indent + 4, " ", "",
+ "(");
+ col = token_print_indent(file, col, indent + 4, "\"", "\"", r->data.execute.command);
+ for (expr = r->data.execute.arglist; expr; expr = expr->data.arg.next) {
+ col = token_print_indent(file, col, indent + 4, "", " ", ",");
+ col = write_expression (file, expr->data.arg.val, col, indent + 4, 0);
+ }
+ (void) token_print_indent(file, col, indent + 4, "", "", ");");
+#else /* !ENABLE_EXECUTE */
+ log_fatal("Impossible case at %s:%d (ENABLE_EXECUTE "
+ "is not defined).", MDL);
+#endif /* ENABLE_EXECUTE */
+ break;
+
+ default:
+ log_fatal ("bogus statement type %d\n", r -> op);
+ }
+ }
+}
+
+/* Find a case statement in the sequence of executable statements that
+ matches the expression, and if found, return the following statement.
+ If no case statement matches, try to find a default statement and
+ return that (the default statement can precede all the case statements).
+ Otherwise, return the null statement. */
+
+int find_matching_case (struct executable_statement **ep,
+ struct packet *packet, struct lease *lease,
+ struct client_state *client_state,
+ struct option_state *in_options,
+ struct option_state *out_options,
+ struct binding_scope **scope,
+ struct expression *expr,
+ struct executable_statement *stmt)
+{
+ int status, sub;
+ struct executable_statement *s;
+
+ if (is_data_expression (expr)) {
+ struct data_string cd, ds;
+ memset (&ds, 0, sizeof ds);
+ memset (&cd, 0, sizeof cd);
+
+ status = (evaluate_data_expression (&ds, packet, lease,
+ client_state, in_options,
+ out_options, scope, expr,
+ MDL));
+ if (status) {
+ for (s = stmt; s; s = s -> next) {
+ if (s -> op == case_statement) {
+ sub = (evaluate_data_expression
+ (&cd, packet, lease, client_state,
+ in_options, out_options,
+ scope, s -> data.c_case, MDL));
+ if (sub && cd.len == ds.len &&
+ !memcmp (cd.data, ds.data, cd.len))
+ {
+ data_string_forget (&cd, MDL);
+ data_string_forget (&ds, MDL);
+ executable_statement_reference
+ (ep, s -> next, MDL);
+ return 1;
+ }
+ data_string_forget (&cd, MDL);
+ }
+ }
+ data_string_forget (&ds, MDL);
+ }
+ } else {
+ unsigned long n, c;
+ status = evaluate_numeric_expression (&n, packet, lease,
+ client_state,
+ in_options, out_options,
+ scope, expr);
+
+ if (status) {
+ for (s = stmt; s; s = s -> next) {
+ if (s -> op == case_statement) {
+ sub = (evaluate_numeric_expression
+ (&c, packet, lease, client_state,
+ in_options, out_options,
+ scope, s -> data.c_case));
+ if (sub && n == c) {
+ executable_statement_reference
+ (ep, s -> next, MDL);
+ return 1;
+ }
+ }
+ }
+ }
+ }
+
+ /* If we didn't find a matching case statement, look for a default
+ statement and return the statement following it. */
+ for (s = stmt; s; s = s -> next)
+ if (s -> op == default_statement)
+ break;
+ if (s) {
+ executable_statement_reference (ep, s -> next, MDL);
+ return 1;
+ }
+ return 0;
+}
+
+int executable_statement_foreach (struct executable_statement *stmt,
+ int (*callback) (struct
+ executable_statement *,
+ void *, int),
+ void *vp, int condp)
+{
+ struct executable_statement *foo;
+ int ok = 0;
+
+ for (foo = stmt; foo; foo = foo -> next) {
+ if ((*callback) (foo, vp, condp) != 0)
+ ok = 1;
+ switch (foo -> op) {
+ case null_statement:
+ break;
+ case if_statement:
+ if (executable_statement_foreach (foo -> data.ie.tc,
+ callback, vp, 1))
+ ok = 1;
+ if (executable_statement_foreach (foo -> data.ie.fc,
+ callback, vp, 1))
+ ok = 1;
+ break;
+ case add_statement:
+ break;
+ case eval_statement:
+ break;
+ case break_statement:
+ break;
+ case default_option_statement:
+ break;
+ case supersede_option_statement:
+ break;
+ case append_option_statement:
+ break;
+ case prepend_option_statement:
+ break;
+ case send_option_statement:
+ break;
+ case statements_statement:
+ if ((executable_statement_foreach
+ (foo -> data.statements, callback, vp, condp)))
+ ok = 1;
+ break;
+ case on_statement:
+ if ((executable_statement_foreach
+ (foo -> data.on.statements, callback, vp, 1)))
+ ok = 1;
+ break;
+ case switch_statement:
+ if ((executable_statement_foreach
+ (foo -> data.s_switch.statements, callback, vp, 1)))
+ ok = 1;
+ break;
+ case case_statement:
+ break;
+ case default_statement:
+ break;
+ case set_statement:
+ break;
+ case unset_statement:
+ break;
+ case let_statement:
+ if ((executable_statement_foreach
+ (foo -> data.let.statements, callback, vp, 0)))
+ ok = 1;
+ break;
+ case define_statement:
+ break;
+ case log_statement:
+ case return_statement:
+ case execute_statement:
+ break;
+ }
+ }
+ return ok;
+}
diff --git a/common/fddi.c b/common/fddi.c
new file mode 100644
index 0000000..d998193
--- /dev/null
+++ b/common/fddi.c
@@ -0,0 +1,86 @@
+/* fddi.c
+
+ Packet assembly code, originally contributed by Archie Cobbs. */
+
+/*
+ * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+
+#if defined (DEC_FDDI)
+#include <netinet/if_fddi.h>
+#include <net/if_llc.h>
+
+#if defined (PACKET_ASSEMBLY) || defined (PACKET_DECODING)
+#include "includes/netinet/if_ether.h"
+#endif /* PACKET_ASSEMBLY || PACKET_DECODING */
+
+#if defined (PACKET_ASSEMBLY)
+/* Assemble an hardware header... */
+
+void assemble_fddi_header (interface, buf, bufix, to)
+ struct interface_info *interface;
+ unsigned char *buf;
+ unsigned *bufix;
+ struct hardware *to;
+{
+ struct fddi_header fh;
+ struct llc lh;
+
+ if (to && to -> hlen == 7)
+ memcpy (fh.fddi_dhost, &to -> hbuf [1],
+ sizeof (fh.fddi_dhost));
+ memcpy (fh.fddi_shost,
+ &interface -> hw_address.hbuf [1], sizeof (fh.fddi_shost));
+ fh.fddi_fc = FDDIFC_LLC_ASYNC;
+ memcpy (&buf [*bufix], &fh, sizeof fh);
+ *bufix += sizeof fh;
+
+ lh.llc_dsap = LLC_SNAP_LSAP;
+ lh.llc_ssap = LLC_SNAP_LSAP;
+ lh.llc_un.type_snap.control = LLC_UI;
+ lh.llc_un.type_snap.ether_type = htons (ETHERTYPE_IP);
+ memcpy (&buf [*bufix], &lh, LLC_SNAP_LEN);
+ *bufix += LLC_SNAP_LEN;
+}
+#endif /* PACKET_ASSEMBLY */
+
+#ifdef PACKET_DECODING
+/* Decode a hardware header... */
+
+ssize_t decode_fddi_header (interface, buf, bufix, from)
+ struct interface_info *interface;
+ unsigned char *buf;
+ unsigned bufix;
+ struct hardware *from;
+{
+ struct fddi_header fh;
+ struct llc lh;
+
+ from -> hbuf [0] = HTYPE_FDDI;
+ memcpy (&from -> hbuf [1], fh.fddi_shost, sizeof fh.fddi_shost);
+ return FDDI_HEADER_SIZE + LLC_SNAP_LEN;
+}
+#endif /* PACKET_DECODING */
+#endif /* DEC_FDDI */
diff --git a/common/icmp.c b/common/icmp.c
new file mode 100644
index 0000000..6f97f67
--- /dev/null
+++ b/common/icmp.c
@@ -0,0 +1,300 @@
+/* dhcp.c
+
+ ICMP Protocol engine - for sending out pings and receiving
+ responses. */
+
+/*
+ * Copyright (c) 2011,2013,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004,2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+#include "netinet/ip.h"
+#include "netinet/ip_icmp.h"
+
+struct icmp_state *icmp_state;
+static omapi_object_type_t *dhcp_type_icmp;
+static int no_icmp;
+
+OMAPI_OBJECT_ALLOC (icmp_state, struct icmp_state, dhcp_type_icmp)
+
+#if defined (TRACING)
+trace_type_t *trace_icmp_input;
+trace_type_t *trace_icmp_output;
+#endif
+
+/* Initialize the ICMP protocol. */
+
+void icmp_startup (routep, handler)
+ int routep;
+ void (*handler) (struct iaddr, u_int8_t *, int);
+{
+ struct protoent *proto;
+ int protocol = 1;
+ int state;
+ isc_result_t result;
+
+ /* Only initialize icmp once. */
+ if (dhcp_type_icmp)
+ log_fatal ("attempted to reinitialize icmp protocol");
+
+ result = omapi_object_type_register (&dhcp_type_icmp, "icmp",
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ sizeof (struct icmp_state),
+ 0, RC_MISC);
+
+ if (result != ISC_R_SUCCESS)
+ log_fatal ("Can't register icmp object type: %s",
+ isc_result_totext (result));
+
+ icmp_state_allocate (&icmp_state, MDL);
+ icmp_state -> icmp_handler = handler;
+
+#if defined (TRACING)
+ trace_icmp_input = trace_type_register ("icmp-input", (void *)0,
+ trace_icmp_input_input,
+ trace_icmp_input_stop, MDL);
+ trace_icmp_output = trace_type_register ("icmp-output", (void *)0,
+ trace_icmp_output_input,
+ trace_icmp_output_stop, MDL);
+
+ /* If we're playing back a trace file, don't create the socket
+ or set up the callback. */
+ if (!trace_playback ()) {
+#endif
+ /* Get the protocol number (should be 1). */
+ proto = getprotobyname ("icmp");
+ if (proto)
+ protocol = proto -> p_proto;
+
+ /* Get a raw socket for the ICMP protocol. */
+ icmp_state -> socket = socket (AF_INET, SOCK_RAW, protocol);
+ if (icmp_state -> socket < 0) {
+ no_icmp = 1;
+ log_error ("unable to create icmp socket: %m");
+ return;
+ }
+
+#if defined (HAVE_SETFD)
+ if (fcntl (icmp_state -> socket, F_SETFD, 1) < 0)
+ log_error ("Can't set close-on-exec on icmp: %m");
+#endif
+
+ /* Make sure it does routing... */
+ state = 0;
+ if (setsockopt (icmp_state -> socket, SOL_SOCKET, SO_DONTROUTE,
+ (char *)&state, sizeof state) < 0)
+ log_fatal ("Can't disable SO_DONTROUTE on ICMP: %m");
+
+ result = (omapi_register_io_object
+ ((omapi_object_t *)icmp_state,
+ icmp_readsocket, 0, icmp_echoreply, 0, 0));
+ if (result != ISC_R_SUCCESS)
+ log_fatal ("Can't register icmp handle: %s",
+ isc_result_totext (result));
+#if defined (TRACING)
+ }
+#endif
+}
+
+int icmp_readsocket (h)
+ omapi_object_t *h;
+{
+ struct icmp_state *state;
+
+ state = (struct icmp_state *)h;
+ return state -> socket;
+}
+
+int icmp_echorequest (addr)
+ struct iaddr *addr;
+{
+ struct sockaddr_in to;
+ struct icmp icmp;
+ int status;
+#if defined (TRACING)
+ trace_iov_t iov [2];
+#endif
+
+ if (no_icmp)
+ return 1;
+ if (!icmp_state)
+ log_fatal ("ICMP protocol used before initialization.");
+
+ memset (&to, 0, sizeof(to));
+#ifdef HAVE_SA_LEN
+ to.sin_len = sizeof to;
+#endif
+ to.sin_family = AF_INET;
+ to.sin_port = 0; /* unused. */
+ memcpy (&to.sin_addr, addr -> iabuf, sizeof to.sin_addr); /* XXX */
+
+ icmp.icmp_type = ICMP_ECHO;
+ icmp.icmp_code = 0;
+ icmp.icmp_cksum = 0;
+ icmp.icmp_seq = 0;
+#if SIZEOF_STRUCT_IADDR_P == 8
+ icmp.icmp_id = (((u_int32_t)(u_int64_t)addr) ^
+ (u_int32_t)(((u_int64_t)addr) >> 32));
+#else
+ icmp.icmp_id = (u_int32_t)addr;
+#endif
+ memset (&icmp.icmp_dun, 0, sizeof icmp.icmp_dun);
+
+ icmp.icmp_cksum = wrapsum (checksum ((unsigned char *)&icmp,
+ sizeof icmp, 0));
+
+#if defined (TRACING)
+ if (trace_playback ()) {
+ char *buf = (char *)0;
+ unsigned buflen = 0;
+
+ /* Consume the ICMP event. */
+ status = trace_get_packet (&trace_icmp_output, &buflen, &buf);
+ if (status != ISC_R_SUCCESS)
+ log_error ("icmp_echorequest: %s",
+ isc_result_totext (status));
+ if (buf)
+ dfree (buf, MDL);
+ } else {
+ if (trace_record ()) {
+ iov [0].buf = (char *)addr;
+ iov [0].len = sizeof *addr;
+ iov [1].buf = (char *)&icmp;
+ iov [1].len = sizeof icmp;
+ trace_write_packet_iov (trace_icmp_output,
+ 2, iov, MDL);
+ }
+#endif
+ /* Send the ICMP packet... */
+ status = sendto (icmp_state -> socket,
+ (char *)&icmp, sizeof icmp, 0,
+ (struct sockaddr *)&to, sizeof to);
+ if (status < 0)
+ log_error ("icmp_echorequest %s: %m",
+ inet_ntoa(to.sin_addr));
+
+ if (status != sizeof icmp)
+ return 0;
+#if defined (TRACING)
+ }
+#endif
+ return 1;
+}
+
+isc_result_t icmp_echoreply (h)
+ omapi_object_t *h;
+{
+ struct icmp *icfrom;
+ struct ip *ip;
+ struct sockaddr_in from;
+ u_int8_t icbuf [1500];
+ int status;
+ SOCKLEN_T sl;
+ int hlen, len;
+ struct iaddr ia;
+ struct icmp_state *state;
+#if defined (TRACING)
+ trace_iov_t iov [2];
+#endif
+
+ state = (struct icmp_state *)h;
+
+ sl = sizeof from;
+ status = recvfrom (state -> socket, (char *)icbuf, sizeof icbuf, 0,
+ (struct sockaddr *)&from, &sl);
+ if (status < 0) {
+ log_error ("icmp_echoreply: %m");
+ return ISC_R_UNEXPECTED;
+ }
+
+ /* Find the IP header length... */
+ ip = (struct ip *)icbuf;
+ hlen = IP_HL (ip);
+
+ /* Short packet? */
+ if (status < hlen + (sizeof *icfrom)) {
+ return ISC_R_SUCCESS;
+ }
+
+ len = status - hlen;
+ icfrom = (struct icmp *)(icbuf + hlen);
+
+ /* Silently discard ICMP packets that aren't echoreplies. */
+ if (icfrom -> icmp_type != ICMP_ECHOREPLY) {
+ return ISC_R_SUCCESS;
+ }
+
+ /* If we were given a second-stage handler, call it. */
+ if (state -> icmp_handler) {
+ memcpy (ia.iabuf, &from.sin_addr, sizeof from.sin_addr);
+ ia.len = sizeof from.sin_addr;
+
+#if defined (TRACING)
+ if (trace_record ()) {
+ ia.len = htonl(ia.len);
+ iov [0].buf = (char *)&ia;
+ iov [0].len = sizeof ia;
+ iov [1].buf = (char *)icbuf;
+ iov [1].len = len;
+ trace_write_packet_iov (trace_icmp_input, 2, iov, MDL);
+ ia.len = ntohl(ia.len);
+ }
+#endif
+ (*state -> icmp_handler) (ia, icbuf, len);
+ }
+ return ISC_R_SUCCESS;
+}
+
+#if defined (TRACING)
+void trace_icmp_input_input (trace_type_t *ttype, unsigned length, char *buf)
+{
+ struct iaddr *ia;
+ u_int8_t *icbuf;
+ ia = (struct iaddr *)buf;
+ ia->len = ntohl(ia->len);
+ icbuf = (u_int8_t *)(ia + 1);
+ if (icmp_state -> icmp_handler)
+ (*icmp_state -> icmp_handler) (*ia, icbuf,
+ (int)(length - sizeof ia));
+}
+
+void trace_icmp_input_stop (trace_type_t *ttype) { }
+
+void trace_icmp_output_input (trace_type_t *ttype, unsigned length, char *buf)
+{
+ struct iaddr ia;
+
+ if (length != (sizeof (struct icmp) + sizeof (ia))) {
+ log_error ("trace_icmp_output_input: data size mismatch %d:%d",
+ length, (int)(sizeof (struct icmp) + sizeof (ia)));
+ return;
+ }
+ ia.len = 4;
+ memcpy (ia.iabuf, buf, 4);
+
+ log_error ("trace_icmp_output_input: unsent ping to %s", piaddr (ia));
+}
+
+void trace_icmp_output_stop (trace_type_t *ttype) { }
+#endif /* TRACING */
diff --git a/common/inet.c b/common/inet.c
new file mode 100644
index 0000000..0cff19d
--- /dev/null
+++ b/common/inet.c
@@ -0,0 +1,624 @@
+/* inet.c
+
+ Subroutines to manipulate internet addresses and ports in a safely portable
+ way... */
+
+/*
+ * Copyright (c) 2011,2013,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2007-2009 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004,2005 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+
+/* Return just the network number of an internet address... */
+
+struct iaddr subnet_number (addr, mask)
+ struct iaddr addr;
+ struct iaddr mask;
+{
+ int i;
+ struct iaddr rv;
+
+ if (addr.len > sizeof(addr.iabuf))
+ log_fatal("subnet_number():%s:%d: Invalid addr length.", MDL);
+ if (addr.len != mask.len)
+ log_fatal("subnet_number():%s:%d: Addr/mask length mismatch.",
+ MDL);
+
+ rv.len = 0;
+
+ /* Both addresses must have the same length... */
+ if (addr.len != mask.len)
+ return rv;
+
+ rv.len = addr.len;
+ for (i = 0; i < rv.len; i++)
+ rv.iabuf [i] = addr.iabuf [i] & mask.iabuf [i];
+ return rv;
+}
+
+/* Combine a network number and a integer to produce an internet address.
+ This won't work for subnets with more than 32 bits of host address, but
+ maybe this isn't a problem. */
+
+struct iaddr ip_addr (subnet, mask, host_address)
+ struct iaddr subnet;
+ struct iaddr mask;
+ u_int32_t host_address;
+{
+ int i, j, k;
+ u_int32_t swaddr;
+ struct iaddr rv;
+ unsigned char habuf [sizeof swaddr];
+
+ if (subnet.len > sizeof(subnet.iabuf))
+ log_fatal("ip_addr():%s:%d: Invalid addr length.", MDL);
+ if (subnet.len != mask.len)
+ log_fatal("ip_addr():%s:%d: Addr/mask length mismatch.",
+ MDL);
+
+ swaddr = htonl (host_address);
+ memcpy (habuf, &swaddr, sizeof swaddr);
+
+ /* Combine the subnet address and the host address. If
+ the host address is bigger than can fit in the subnet,
+ return a zero-length iaddr structure. */
+ rv = subnet;
+ j = rv.len - sizeof habuf;
+ for (i = sizeof habuf - 1; i >= 0; i--) {
+ if (mask.iabuf [i + j]) {
+ if (habuf [i] > (mask.iabuf [i + j] ^ 0xFF)) {
+ rv.len = 0;
+ return rv;
+ }
+ for (k = i - 1; k >= 0; k--) {
+ if (habuf [k]) {
+ rv.len = 0;
+ return rv;
+ }
+ }
+ rv.iabuf [i + j] |= habuf [i];
+ break;
+ } else
+ rv.iabuf [i + j] = habuf [i];
+ }
+
+ return rv;
+}
+
+/* Given a subnet number and netmask, return the address on that subnet
+ for which the host portion of the address is all ones (the standard
+ broadcast address). */
+
+struct iaddr broadcast_addr (subnet, mask)
+ struct iaddr subnet;
+ struct iaddr mask;
+{
+ int i;
+ struct iaddr rv;
+
+ if (subnet.len > sizeof(subnet.iabuf))
+ log_fatal("broadcast_addr():%s:%d: Invalid addr length.", MDL);
+ if (subnet.len != mask.len)
+ log_fatal("broadcast_addr():%s:%d: Addr/mask length mismatch.",
+ MDL);
+
+ if (subnet.len != mask.len) {
+ rv.len = 0;
+ return rv;
+ }
+
+ for (i = 0; i < subnet.len; i++) {
+ rv.iabuf [i] = subnet.iabuf [i] | (~mask.iabuf [i] & 255);
+ }
+ rv.len = subnet.len;
+
+ return rv;
+}
+
+u_int32_t host_addr (addr, mask)
+ struct iaddr addr;
+ struct iaddr mask;
+{
+ int i;
+ u_int32_t swaddr;
+ struct iaddr rv;
+
+ if (addr.len > sizeof(addr.iabuf))
+ log_fatal("host_addr():%s:%d: Invalid addr length.", MDL);
+ if (addr.len != mask.len)
+ log_fatal("host_addr():%s:%d: Addr/mask length mismatch.",
+ MDL);
+
+ rv.len = 0;
+
+ /* Mask out the network bits... */
+ rv.len = addr.len;
+ for (i = 0; i < rv.len; i++)
+ rv.iabuf [i] = addr.iabuf [i] & ~mask.iabuf [i];
+
+ /* Copy out up to 32 bits... */
+ memcpy (&swaddr, &rv.iabuf [rv.len - sizeof swaddr], sizeof swaddr);
+
+ /* Swap it and return it. */
+ return ntohl (swaddr);
+}
+
+int addr_eq (addr1, addr2)
+ struct iaddr addr1, addr2;
+{
+ if (addr1.len > sizeof(addr1.iabuf))
+ log_fatal("addr_eq():%s:%d: Invalid addr length.", MDL);
+
+ if (addr1.len != addr2.len)
+ return 0;
+ return memcmp (addr1.iabuf, addr2.iabuf, addr1.len) == 0;
+}
+
+/* addr_match
+ *
+ * compares an IP address against a network/mask combination
+ * by ANDing the IP with the mask and seeing whether the result
+ * matches the masked network value.
+ */
+int
+addr_match(addr, match)
+ struct iaddr *addr;
+ struct iaddrmatch *match;
+{
+ int i;
+
+ if (addr->len != match->addr.len)
+ return 0;
+
+ for (i = 0 ; i < addr->len ; i++) {
+ if ((addr->iabuf[i] & match->mask.iabuf[i]) !=
+ match->addr.iabuf[i])
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Compares the addresses a1 and a2.
+ *
+ * If a1 < a2, returns -1.
+ * If a1 == a2, returns 0.
+ * If a1 > a2, returns 1.
+ *
+ * WARNING: if a1 and a2 differ in length, returns 0.
+ */
+int
+addr_cmp(const struct iaddr *a1, const struct iaddr *a2) {
+ int i;
+
+ if (a1->len != a2->len) {
+ return 0;
+ }
+
+ for (i=0; i<a1->len; i++) {
+ if (a1->iabuf[i] < a2->iabuf[i]) {
+ return -1;
+ }
+ if (a1->iabuf[i] > a2->iabuf[i]) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Performs a bitwise-OR of two addresses.
+ *
+ * Returns 1 if the result is non-zero, or 0 otherwise.
+ *
+ * WARNING: if a1 and a2 differ in length, returns 0.
+ */
+int
+addr_or(struct iaddr *result, const struct iaddr *a1, const struct iaddr *a2) {
+ int i;
+ int all_zero;
+
+ if (a1->len != a2->len) {
+ return 0;
+ }
+
+ all_zero = 1;
+
+ result->len = a1->len;
+ for (i=0; i<a1->len; i++) {
+ result->iabuf[i] = a1->iabuf[i] | a2->iabuf[i];
+ if (result->iabuf[i] != 0) {
+ all_zero = 0;
+ }
+ }
+
+ return !all_zero;
+}
+
+/*
+ * Performs a bitwise-AND of two addresses.
+ *
+ * Returns 1 if the result is non-zero, or 0 otherwise.
+ *
+ * WARNING: if a1 and a2 differ in length, returns 0.
+ */
+int
+addr_and(struct iaddr *result, const struct iaddr *a1, const struct iaddr *a2) {
+ int i;
+ int all_zero;
+
+ if (a1->len != a2->len) {
+ return 0;
+ }
+
+ all_zero = 1;
+
+ result->len = a1->len;
+ for (i=0; i<a1->len; i++) {
+ result->iabuf[i] = a1->iabuf[i] & a2->iabuf[i];
+ if (result->iabuf[i] != 0) {
+ all_zero = 0;
+ }
+ }
+
+ return !all_zero;
+}
+
+/*
+ * Check if a bitmask of the given length is valid for the address.
+ * This is not the case if any bits longer than the bitmask are 1.
+ *
+ * So, this is valid:
+ *
+ * 127.0.0.0/8
+ *
+ * But this is not:
+ *
+ * 127.0.0.1/8
+ *
+ * Because the final ".1" would get masked out by the /8.
+ */
+isc_boolean_t
+is_cidr_mask_valid(const struct iaddr *addr, int bits) {
+ int zero_bits;
+ int zero_bytes;
+ int i;
+ char byte;
+ int shift_bits;
+
+ /*
+ * Check our bit boundaries.
+ */
+ if (bits < 0) {
+ return ISC_FALSE;
+ }
+ if (bits > (addr->len * 8)) {
+ return ISC_FALSE;
+ }
+
+ /*
+ * Figure out how many low-order bits need to be zero.
+ */
+ zero_bits = (addr->len * 8) - bits;
+ zero_bytes = zero_bits / 8;
+
+ /*
+ * Check to make sure the low-order bytes are zero.
+ */
+ for (i=1; i<=zero_bytes; i++) {
+ if (addr->iabuf[addr->len-i] != 0) {
+ return ISC_FALSE;
+ }
+ }
+
+ /*
+ * Look to see if any bits not in right-hand bytes are
+ * non-zero, by making a byte that has these bits set to zero
+ * comparing to the original byte. If these two values are
+ * equal, then the right-hand bits are zero, and we are
+ * happy.
+ */
+ shift_bits = zero_bits % 8;
+ if (shift_bits == 0) return ISC_TRUE;
+ byte = addr->iabuf[addr->len-zero_bytes-1];
+ return (((byte >> shift_bits) << shift_bits) == byte);
+}
+
+/*
+ * range2cidr
+ *
+ * Converts a range of IP addresses to a set of CIDR networks.
+ *
+ * Examples:
+ * 192.168.0.0 - 192.168.0.255 = 192.168.0.0/24
+ * 10.0.0.0 - 10.0.1.127 = 10.0.0.0/24, 10.0.1.0/25
+ * 255.255.255.32 - 255.255.255.255 = 255.255.255.32/27, 255.255.255.64/26,
+ * 255.255.255.128/25
+ */
+isc_result_t
+range2cidr(struct iaddrcidrnetlist **result,
+ const struct iaddr *lo, const struct iaddr *hi) {
+ struct iaddr addr;
+ struct iaddr mask;
+ int bit;
+ struct iaddr end_addr;
+ struct iaddr dummy;
+ int ofs, val;
+ struct iaddrcidrnetlist *net;
+ int tmp;
+
+ if (result == NULL) {
+ return DHCP_R_INVALIDARG;
+ }
+ if (*result != NULL) {
+ return DHCP_R_INVALIDARG;
+ }
+ if ((lo == NULL) || (hi == NULL) || (lo->len != hi->len)) {
+ return DHCP_R_INVALIDARG;
+ }
+
+ /*
+ * Put our start and end in the right order, if reversed.
+ */
+ if (addr_cmp(lo, hi) > 0) {
+ const struct iaddr *tmp;
+ tmp = lo;
+ lo = hi;
+ hi = tmp;
+ }
+
+ /*
+ * Theory of operation:
+ *
+ * -------------------
+ * Start at the low end, and keep trying larger networks
+ * until we get one that is too big (explained below).
+ *
+ * We keep a "mask", which is the ones-complement of a
+ * normal netmask. So, a /23 has a netmask of 255.255.254.0,
+ * and a mask of 0.0.1.255.
+ *
+ * We know when a network is too big when we bitwise-AND the
+ * mask with the starting address and we get a non-zero
+ * result, like this:
+ *
+ * addr: 192.168.1.0, mask: 0.0.1.255
+ * bitwise-AND: 0.0.1.0
+ *
+ * A network is also too big if the bitwise-OR of the mask
+ * with the starting address is larger than the end address,
+ * like this:
+ *
+ * start: 192.168.1.0, mask: 0.0.1.255, end: 192.168.0.255
+ * bitwise-OR: 192.168.1.255
+ *
+ * -------------------
+ * Once we have found a network that is too big, we add the
+ * appropriate CIDR network to our list of found networks.
+ *
+ * We then use the next IP address as our low address, and
+ * begin the process of searching for a network that is
+ * too big again, starting with an empty mask.
+ */
+ addr = *lo;
+ bit = 0;
+ memset(&mask, 0, sizeof(mask));
+ mask.len = addr.len;
+ while (addr_cmp(&addr, hi) <= 0) {
+ /*
+ * Bitwise-OR mask with (1 << bit)
+ */
+ ofs = addr.len - (bit / 8) - 1;
+ val = 1 << (bit % 8);
+ if (ofs >= 0) {
+ mask.iabuf[ofs] |= val;
+ }
+
+ /*
+ * See if we're too big, and save this network if so.
+ */
+ addr_or(&end_addr, &addr, &mask);
+ if ((ofs < 0) ||
+ (addr_cmp(&end_addr, hi) > 0) ||
+ addr_and(&dummy, &addr, &mask)) {
+ /*
+ * Add a new prefix to our list.
+ */
+ net = dmalloc(sizeof(*net), MDL);
+ if (net == NULL) {
+ while (*result != NULL) {
+ net = (*result)->next;
+ dfree(*result, MDL);
+ *result = net;
+ }
+ return ISC_R_NOMEMORY;
+ }
+ net->cidrnet.lo_addr = addr;
+ net->cidrnet.bits = (addr.len * 8) - bit;
+ net->next = *result;
+ *result = net;
+
+ /*
+ * Figure out our new starting address,
+ * by adding (1 << bit) to our previous
+ * starting address.
+ */
+ tmp = addr.iabuf[ofs] + val;
+ while ((ofs >= 0) && (tmp > 255)) {
+ addr.iabuf[ofs] = tmp - 256;
+ ofs--;
+ tmp = addr.iabuf[ofs] + 1;
+ }
+ if (ofs < 0) {
+ /* Gone past last address, we're done. */
+ break;
+ }
+ addr.iabuf[ofs] = tmp;
+
+ /*
+ * Reset our bit and mask.
+ */
+ bit = 0;
+ memset(mask.iabuf, 0, sizeof(mask.iabuf));
+ memset(end_addr.iabuf, 0, sizeof(end_addr.iabuf));
+ } else {
+ /*
+ * If we're not too big, increase our network size.
+ */
+ bit++;
+ }
+ }
+
+ /*
+ * We're done.
+ */
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Free a list of CIDR networks, such as returned from range2cidr().
+ */
+isc_result_t
+free_iaddrcidrnetlist(struct iaddrcidrnetlist **result) {
+ struct iaddrcidrnetlist *p;
+
+ if (result == NULL) {
+ return DHCP_R_INVALIDARG;
+ }
+ if (*result == NULL) {
+ return DHCP_R_INVALIDARG;
+ }
+
+ while (*result != NULL) {
+ p = *result;
+ *result = p->next;
+ dfree(p, MDL);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+/* piaddr() turns an iaddr structure into a printable address. */
+/* XXX: should use a const pointer rather than passing the structure */
+const char *
+piaddr(const struct iaddr addr) {
+ static char
+ pbuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+ /* "255.255.255.255" */
+
+ /* INSIST((addr.len == 0) || (addr.len == 4) || (addr.len == 16)); */
+
+ if (addr.len == 0) {
+ return "<null address>";
+ }
+ if (addr.len == 4) {
+ return inet_ntop(AF_INET, addr.iabuf, pbuf, sizeof(pbuf));
+ }
+ if (addr.len == 16) {
+ return inet_ntop(AF_INET6, addr.iabuf, pbuf, sizeof(pbuf));
+ }
+
+ log_fatal("piaddr():%s:%d: Invalid address length %d.", MDL,
+ addr.len);
+ /* quell compiler warnings */
+ return NULL;
+}
+
+/* piaddrmask takes an iaddr structure mask, determines the bitlength of
+ * the mask, and then returns the printable CIDR notation of the two.
+ */
+char *
+piaddrmask(struct iaddr *addr, struct iaddr *mask) {
+ int mw;
+ unsigned int oct, bit;
+
+ if ((addr->len != 4) && (addr->len != 16))
+ log_fatal("piaddrmask():%s:%d: Address length %d invalid",
+ MDL, addr->len);
+ if (addr->len != mask->len)
+ log_fatal("piaddrmask():%s:%d: Address and mask size mismatch",
+ MDL);
+
+ /* Determine netmask width in bits. */
+ for (mw = (mask->len * 8) ; mw > 0 ; ) {
+ oct = (mw - 1) / 8;
+ bit = 0x80 >> ((mw - 1) % 8);
+ if (!mask->iabuf[oct])
+ mw -= 8;
+ else if (mask->iabuf[oct] & bit)
+ break;
+ else
+ mw--;
+ }
+
+ if (mw < 0)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ return piaddrcidr(addr, mw);
+}
+
+/* Format an address and mask-length into printable CIDR notation. */
+char *
+piaddrcidr(const struct iaddr *addr, unsigned int bits) {
+ static char
+ ret[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255/128")];
+ /* "255.255.255.255/32" */
+
+ /* INSIST(addr != NULL); */
+ /* INSIST((addr->len == 4) || (addr->len == 16)); */
+ /* INSIST(bits <= (addr->len * 8)); */
+
+ if (bits > (addr->len * 8))
+ return NULL;
+
+ sprintf(ret, "%s/%d", piaddr(*addr), bits);
+
+ return ret;
+}
+
+/* Validate that the string represents a valid port number and
+ * return it in network byte order
+ */
+
+u_int16_t
+validate_port(char *port) {
+ long local_port = 0;
+ long lower = 1;
+ long upper = 65535;
+ char *endptr;
+
+ errno = 0;
+ local_port = strtol(port, &endptr, 10);
+
+ if ((*endptr != '\0') || (errno == ERANGE) || (errno == EINVAL))
+ log_fatal ("Invalid port number specification: %s", port);
+
+ if (local_port < lower || local_port > upper)
+ log_fatal("Port number specified is out of range (%ld-%ld).",
+ lower, upper);
+
+ return htons((u_int16_t)local_port);
+}
diff --git a/common/lpf.c b/common/lpf.c
new file mode 100644
index 0000000..6fe4954
--- /dev/null
+++ b/common/lpf.c
@@ -0,0 +1,537 @@
+/* lpf.c
+
+ Linux packet filter code, contributed by Brian Murrel at Interlinx
+ Support Services in Vancouver, B.C. */
+
+/*
+ * Copyright (c) 2014-2015 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2009,2012 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004,2007 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ */
+
+#include "dhcpd.h"
+#if defined (USE_LPF_SEND) || defined (USE_LPF_RECEIVE)
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+#include <errno.h>
+
+#include <asm/types.h>
+#include <linux/filter.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <netinet/in_systm.h>
+#include "includes/netinet/ip.h"
+#include "includes/netinet/udp.h"
+#include "includes/netinet/if_ether.h"
+#include <net/if.h>
+
+/* Reinitializes the specified interface after an address change. This
+ is not required for packet-filter APIs. */
+
+#ifdef USE_LPF_SEND
+void if_reinitialize_send (info)
+ struct interface_info *info;
+{
+}
+#endif
+
+#ifdef USE_LPF_RECEIVE
+void if_reinitialize_receive (info)
+ struct interface_info *info;
+{
+}
+#endif
+
+/* Called by get_interface_list for each interface that's discovered.
+ Opens a packet filter for each interface and adds it to the select
+ mask. */
+
+int if_register_lpf (info)
+ struct interface_info *info;
+{
+ int sock;
+ union {
+ struct sockaddr_ll ll;
+ struct sockaddr common;
+ } sa;
+ struct ifreq ifr;
+
+ /* Make an LPF socket. */
+ if ((sock = socket(PF_PACKET, SOCK_RAW,
+ htons((short)ETH_P_ALL))) < 0) {
+ if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT ||
+ errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT ||
+ errno == EAFNOSUPPORT || errno == EINVAL) {
+ log_error ("socket: %m - make sure");
+ log_error ("CONFIG_PACKET (Packet socket) %s",
+ "and CONFIG_FILTER");
+ log_error ("(Socket Filtering) are enabled %s",
+ "in your kernel");
+ log_fatal ("configuration!");
+ }
+ log_fatal ("Open a socket for LPF: %m");
+ }
+
+ memset (&ifr, 0, sizeof ifr);
+ strncpy (ifr.ifr_name, (const char *)info -> ifp, sizeof ifr.ifr_name);
+ ifr.ifr_name[IFNAMSIZ-1] = '\0';
+ if (ioctl (sock, SIOCGIFINDEX, &ifr))
+ log_fatal ("Failed to get interface index: %m");
+
+ /* Bind to the interface name */
+ memset (&sa, 0, sizeof sa);
+ sa.ll.sll_family = AF_PACKET;
+ sa.ll.sll_ifindex = ifr.ifr_ifindex;
+ if (bind (sock, &sa.common, sizeof sa)) {
+ if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT ||
+ errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT ||
+ errno == EAFNOSUPPORT || errno == EINVAL) {
+ log_error ("socket: %m - make sure");
+ log_error ("CONFIG_PACKET (Packet socket) %s",
+ "and CONFIG_FILTER");
+ log_error ("(Socket Filtering) are enabled %s",
+ "in your kernel");
+ log_fatal ("configuration!");
+ }
+ log_fatal ("Bind socket to interface: %m");
+
+ }
+
+ get_hw_addr(info->name, &info->hw_address);
+
+ return sock;
+}
+#endif /* USE_LPF_SEND || USE_LPF_RECEIVE */
+
+#ifdef USE_LPF_SEND
+void if_register_send (info)
+ struct interface_info *info;
+{
+ /* If we're using the lpf API for sending and receiving,
+ we don't need to register this interface twice. */
+#ifndef USE_LPF_RECEIVE
+ info -> wfdesc = if_register_lpf (info);
+#else
+ info -> wfdesc = info -> rfdesc;
+#endif
+ if (!quiet_interface_discovery)
+ log_info ("Sending on LPF/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+
+void if_deregister_send (info)
+ struct interface_info *info;
+{
+ /* don't need to close twice if we are using lpf for sending and
+ receiving */
+#ifndef USE_LPF_RECEIVE
+ /* for LPF this is simple, packet filters are removed when sockets
+ are closed */
+ close (info -> wfdesc);
+#endif
+ info -> wfdesc = -1;
+ if (!quiet_interface_discovery)
+ log_info ("Disabling output on LPF/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+#endif /* USE_LPF_SEND */
+
+#ifdef USE_LPF_RECEIVE
+/* Defined in bpf.c. We can't extern these in dhcpd.h without pulling
+ in bpf includes... */
+extern struct sock_filter dhcp_bpf_filter [];
+extern int dhcp_bpf_filter_len;
+
+#if defined (HAVE_TR_SUPPORT)
+extern struct sock_filter dhcp_bpf_tr_filter [];
+extern int dhcp_bpf_tr_filter_len;
+static void lpf_tr_filter_setup (struct interface_info *);
+#endif
+
+static void lpf_gen_filter_setup (struct interface_info *);
+
+void if_register_receive (info)
+ struct interface_info *info;
+{
+ /* Open a LPF device and hang it on this interface... */
+ info -> rfdesc = if_register_lpf (info);
+
+#ifdef PACKET_AUXDATA
+ {
+ int val = 1;
+
+ if (setsockopt(info->rfdesc, SOL_PACKET, PACKET_AUXDATA,
+ &val, sizeof(val)) < 0) {
+ if (errno != ENOPROTOOPT) {
+ log_fatal ("Failed to set auxiliary packet data: %m");
+ }
+ }
+ }
+#endif
+
+
+#if defined (HAVE_TR_SUPPORT)
+ if (info -> hw_address.hbuf [0] == HTYPE_IEEE802)
+ lpf_tr_filter_setup (info);
+ else
+#endif
+ lpf_gen_filter_setup (info);
+
+ if (!quiet_interface_discovery)
+ log_info ("Listening on LPF/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+
+void if_deregister_receive (info)
+ struct interface_info *info;
+{
+ /* for LPF this is simple, packet filters are removed when sockets
+ are closed */
+ close (info -> rfdesc);
+ info -> rfdesc = -1;
+ if (!quiet_interface_discovery)
+ log_info ("Disabling input on LPF/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+
+static void lpf_gen_filter_setup (info)
+ struct interface_info *info;
+{
+ struct sock_fprog p;
+
+ memset(&p, 0, sizeof(p));
+
+ /* Set up the bpf filter program structure. This is defined in
+ bpf.c */
+ p.len = dhcp_bpf_filter_len;
+ p.filter = dhcp_bpf_filter;
+
+ /* Patch the server port into the LPF program...
+ XXX changes to filter program may require changes
+ to the insn number(s) used below! XXX */
+ dhcp_bpf_filter [8].k = ntohs ((short)local_port);
+
+ if (setsockopt (info -> rfdesc, SOL_SOCKET, SO_ATTACH_FILTER, &p,
+ sizeof p) < 0) {
+ if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT ||
+ errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT ||
+ errno == EAFNOSUPPORT) {
+ log_error ("socket: %m - make sure");
+ log_error ("CONFIG_PACKET (Packet socket) %s",
+ "and CONFIG_FILTER");
+ log_error ("(Socket Filtering) are enabled %s",
+ "in your kernel");
+ log_fatal ("configuration!");
+ }
+ log_fatal ("Can't install packet filter program: %m");
+ }
+}
+
+#if defined (HAVE_TR_SUPPORT)
+static void lpf_tr_filter_setup (info)
+ struct interface_info *info;
+{
+ struct sock_fprog p;
+
+ memset(&p, 0, sizeof(p));
+
+ /* Set up the bpf filter program structure. This is defined in
+ bpf.c */
+ p.len = dhcp_bpf_tr_filter_len;
+ p.filter = dhcp_bpf_tr_filter;
+
+ /* Patch the server port into the LPF program...
+ XXX changes to filter program may require changes
+ XXX to the insn number(s) used below!
+ XXX Token ring filter is null - when/if we have a filter
+ XXX that's not, we'll need this code.
+ XXX dhcp_bpf_filter [?].k = ntohs (local_port); */
+
+ if (setsockopt (info -> rfdesc, SOL_SOCKET, SO_ATTACH_FILTER, &p,
+ sizeof p) < 0) {
+ if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT ||
+ errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT ||
+ errno == EAFNOSUPPORT) {
+ log_error ("socket: %m - make sure");
+ log_error ("CONFIG_PACKET (Packet socket) %s",
+ "and CONFIG_FILTER");
+ log_error ("(Socket Filtering) are enabled %s",
+ "in your kernel");
+ log_fatal ("configuration!");
+ }
+ log_fatal ("Can't install packet filter program: %m");
+ }
+}
+#endif /* HAVE_TR_SUPPORT */
+#endif /* USE_LPF_RECEIVE */
+
+#ifdef USE_LPF_SEND
+ssize_t send_packet (interface, packet, raw, len, from, to, hto)
+ struct interface_info *interface;
+ struct packet *packet;
+ struct dhcp_packet *raw;
+ size_t len;
+ struct in_addr from;
+ struct sockaddr_in *to;
+ struct hardware *hto;
+{
+ unsigned hbufp = 0, ibufp = 0;
+ double hh [16];
+ double ih [1536 / sizeof (double)];
+ unsigned char *buf = (unsigned char *)ih;
+ int result;
+ int fudge;
+
+ if (!strcmp (interface -> name, "fallback"))
+ return send_fallback (interface, packet, raw,
+ len, from, to, hto);
+
+ if (hto == NULL && interface->anycast_mac_addr.hlen)
+ hto = &interface->anycast_mac_addr;
+
+ /* Assemble the headers... */
+ assemble_hw_header (interface, (unsigned char *)hh, &hbufp, hto);
+ fudge = hbufp % 4; /* IP header must be word-aligned. */
+ memcpy (buf + fudge, (unsigned char *)hh, hbufp);
+ ibufp = hbufp + fudge;
+ assemble_udp_ip_header (interface, buf, &ibufp, from.s_addr,
+ to -> sin_addr.s_addr, to -> sin_port,
+ (unsigned char *)raw, len);
+ memcpy (buf + ibufp, raw, len);
+ result = write(interface->wfdesc, buf + fudge, ibufp + len - fudge);
+ if (result < 0)
+ log_error ("send_packet: %m");
+ return result;
+}
+#endif /* USE_LPF_SEND */
+
+#ifdef USE_LPF_RECEIVE
+ssize_t receive_packet (interface, buf, len, from, hfrom)
+ struct interface_info *interface;
+ unsigned char *buf;
+ size_t len;
+ struct sockaddr_in *from;
+ struct hardware *hfrom;
+{
+ int length = 0;
+ int offset = 0;
+ int csum_ready = 1;
+ unsigned char ibuf [1536];
+ unsigned bufix = 0;
+ unsigned paylen;
+ unsigned char cmsgbuf[CMSG_LEN(sizeof(struct tpacket_auxdata))];
+ struct iovec iov = {
+ .iov_base = ibuf,
+ .iov_len = sizeof ibuf,
+ };
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = cmsgbuf,
+ .msg_controllen = sizeof(cmsgbuf),
+ };
+
+ length = recvmsg (interface->rfdesc, &msg, 0);
+ if (length <= 0)
+ return length;
+
+#ifdef PACKET_AUXDATA
+ {
+ /* Use auxiliary packet data to:
+ *
+ * a. Weed out extraneous VLAN-tagged packets - If the NIC driver is
+ * handling VLAN encapsulation (i.e. stripping/adding VLAN tags),
+ * then an inbound VLAN packet will be seen twice: Once by
+ * the parent interface (e.g. eth0) with a VLAN tag != 0; and once
+ * by the vlan interface (e.g. eth0.n) with a VLAN tag of 0 (i.e none).
+ * We want to discard the packet sent to the parent and thus respond
+ * only over the vlan interface. (Drivers for Intel PRO/1000 series
+ * NICs perform VLAN encapsulation, while drivers for PCnet series
+ * do not, for example. The linux kernel makes stripped vlan info
+ * visible to user space via CMSG/auxdata, this appears to not be
+ * true for BSD OSs.). NOTE: this is only supported on linux flavors
+ * which define the tpacket_auxdata.tp_vlan_tci.
+ *
+ * b. Determine if checksum is valid for use. It may not be if
+ * checksum offloading is enabled on the interface. */
+ struct cmsghdr *cmsg;
+
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level == SOL_PACKET &&
+ cmsg->cmsg_type == PACKET_AUXDATA) {
+ struct tpacket_auxdata *aux = (void *)CMSG_DATA(cmsg);
+ /* Discard packets with stripped vlan id */
+
+#ifdef VLAN_TCI_PRESENT
+ if (aux->tp_vlan_tci != 0)
+ return 0;
+#endif
+
+ csum_ready = ((aux->tp_status & TP_STATUS_CSUMNOTREADY)
+ ? 0 : 1);
+ }
+ }
+
+ }
+#endif
+
+ bufix = 0;
+ /* Decode the physical header... */
+ offset = decode_hw_header (interface, ibuf, bufix, hfrom);
+
+ /* If a physical layer checksum failed (dunno of any
+ physical layer that supports this, but WTH), skip this
+ packet. */
+ if (offset < 0) {
+ return 0;
+ }
+
+ bufix += offset;
+ length -= offset;
+
+ /* Decode the IP and UDP headers... */
+ offset = decode_udp_ip_header (interface, ibuf, bufix, from,
+ (unsigned)length, &paylen, csum_ready);
+
+ /* If the IP or UDP checksum was bad, skip the packet... */
+ if (offset < 0)
+ return 0;
+
+ bufix += offset;
+ length -= offset;
+
+ if (length < paylen)
+ log_fatal("Internal inconsistency at %s:%d.", MDL);
+
+ /* Copy out the data in the packet... */
+ memcpy(buf, &ibuf[bufix], paylen);
+ return paylen;
+}
+
+int can_unicast_without_arp (ip)
+ struct interface_info *ip;
+{
+ return 1;
+}
+
+int can_receive_unicast_unconfigured (ip)
+ struct interface_info *ip;
+{
+ return 1;
+}
+
+int supports_multiple_interfaces (ip)
+ struct interface_info *ip;
+{
+ return 1;
+}
+
+void maybe_setup_fallback ()
+{
+ isc_result_t status;
+ struct interface_info *fbi = (struct interface_info *)0;
+ if (setup_fallback (&fbi, MDL)) {
+ if_register_fallback (fbi);
+ status = omapi_register_io_object ((omapi_object_t *)fbi,
+ if_readsocket, 0,
+ fallback_discard, 0, 0);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register I/O handle for \"%s\": %s",
+ fbi -> name, isc_result_totext (status));
+ interface_dereference (&fbi, MDL);
+ }
+}
+
+void
+get_hw_addr(const char *name, struct hardware *hw) {
+ int sock;
+ struct ifreq tmp;
+ struct sockaddr *sa;
+
+ if (strlen(name) >= sizeof(tmp.ifr_name)) {
+ log_fatal("Device name too long: \"%s\"", name);
+ }
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ log_fatal("Can't create socket for \"%s\": %m", name);
+ }
+
+ memset(&tmp, 0, sizeof(tmp));
+ strcpy(tmp.ifr_name, name);
+ if (ioctl(sock, SIOCGIFHWADDR, &tmp) < 0) {
+ log_fatal("Error getting hardware address for \"%s\": %m",
+ name);
+ }
+
+ sa = &tmp.ifr_hwaddr;
+ switch (sa->sa_family) {
+ case ARPHRD_ETHER:
+ hw->hlen = 7;
+ hw->hbuf[0] = HTYPE_ETHER;
+ memcpy(&hw->hbuf[1], sa->sa_data, 6);
+ break;
+ case ARPHRD_IEEE802:
+#ifdef ARPHRD_IEEE802_TR
+ case ARPHRD_IEEE802_TR:
+#endif /* ARPHRD_IEEE802_TR */
+ hw->hlen = 7;
+ hw->hbuf[0] = HTYPE_IEEE802;
+ memcpy(&hw->hbuf[1], sa->sa_data, 6);
+ break;
+ case ARPHRD_FDDI:
+ hw->hlen = 7;
+ hw->hbuf[0] = HTYPE_FDDI;
+ memcpy(&hw->hbuf[1], sa->sa_data, 6);
+ break;
+ default:
+ log_fatal("Unsupported device type %ld for \"%s\"",
+ (long int)sa->sa_family, name);
+ }
+
+ close(sock);
+}
+#endif
diff --git a/common/memory.c b/common/memory.c
new file mode 100644
index 0000000..03a0d2e
--- /dev/null
+++ b/common/memory.c
@@ -0,0 +1,148 @@
+/* memory.c
+
+ Memory-resident database... */
+
+/*
+ * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+
+struct group *root_group;
+group_hash_t *group_name_hash;
+int (*group_write_hook) (struct group_object *);
+
+isc_result_t delete_group (struct group_object *group, int writep)
+{
+ struct group_object *d;
+
+ /* The group should exist and be hashed - if not, it's invalid. */
+ if (group_name_hash) {
+ d = (struct group_object *)0;
+ group_hash_lookup (&d, group_name_hash, group -> name,
+ strlen (group -> name), MDL);
+ } else
+ return DHCP_R_INVALIDARG;
+ if (!d)
+ return DHCP_R_INVALIDARG;
+
+ /* Also not okay to delete a group that's not the one in
+ the hash table. */
+ if (d != group)
+ return DHCP_R_INVALIDARG;
+
+ /* If it's dynamic, and we're deleting it, we can just blow away the
+ hash table entry. */
+ if ((group -> flags & GROUP_OBJECT_DYNAMIC) &&
+ !(group -> flags & GROUP_OBJECT_STATIC)) {
+ group_hash_delete (group_name_hash,
+ group -> name, strlen (group -> name), MDL);
+ } else {
+ group -> flags |= GROUP_OBJECT_DELETED;
+ if (group -> group)
+ group_dereference (&group -> group, MDL);
+ }
+
+ /* Store the group declaration in the lease file. */
+ if (writep && group_write_hook) {
+ if (!(*group_write_hook) (group))
+ return ISC_R_IOERROR;
+ }
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t supersede_group (struct group_object *group, int writep)
+{
+ struct group_object *t;
+
+ /* Register the group in the group name hash table,
+ so we can look it up later. */
+ if (group_name_hash) {
+ t = (struct group_object *)0;
+ group_hash_lookup (&t, group_name_hash,
+ group -> name,
+ strlen (group -> name), MDL);
+ if (t && t != group) {
+ /* If this isn't a dynamic entry, then we need to flag
+ the replacement as not dynamic either - otherwise,
+ if the dynamic entry is deleted later, the static
+ entry will come back next time the server is stopped
+ and restarted. */
+ if (!(t -> flags & GROUP_OBJECT_DYNAMIC))
+ group -> flags |= GROUP_OBJECT_STATIC;
+
+ /* Delete the old object if it hasn't already been
+ deleted. If it has already been deleted, get rid of
+ the hash table entry. This is a legitimate
+ situation - a deleted static object needs to be kept
+ around so we remember it's deleted. */
+ if (!(t -> flags & GROUP_OBJECT_DELETED))
+ delete_group (t, 0);
+ else {
+ group_hash_delete (group_name_hash,
+ group -> name,
+ strlen (group -> name),
+ MDL);
+ group_object_dereference (&t, MDL);
+ }
+ }
+ } else {
+ group_new_hash(&group_name_hash, GROUP_HASH_SIZE, MDL);
+ t = (struct group_object *)0;
+ }
+
+ /* Add the group to the group name hash if it's not
+ already there, and also thread it into the list of
+ dynamic groups if appropriate. */
+ if (!t) {
+ group_hash_add (group_name_hash, group -> name,
+ strlen (group -> name), group, MDL);
+ }
+
+ /* Store the group declaration in the lease file. */
+ if (writep && group_write_hook) {
+ if (!(*group_write_hook) (group))
+ return ISC_R_IOERROR;
+ }
+ return ISC_R_SUCCESS;
+}
+
+int clone_group (struct group **gp, struct group *group,
+ const char *file, int line)
+{
+ struct group *g = (struct group *)0;
+
+ /* Normally gp should contain the null pointer, but for convenience
+ it's permissible to clone a group into itself. */
+ if (*gp && *gp != group)
+ return 0;
+ if (!group_allocate (&g, file, line))
+ return 0;
+ if (group == *gp)
+ *gp = (struct group *)0;
+ group_reference (gp, g, file, line);
+ g -> authoritative = group -> authoritative;
+ group_reference (&g -> next, group, file, line);
+ group_dereference (&g, file, line);
+ return 1;
+}
diff --git a/common/nit.c b/common/nit.c
new file mode 100644
index 0000000..316e85f
--- /dev/null
+++ b/common/nit.c
@@ -0,0 +1,416 @@
+/* nit.c
+
+ Network Interface Tap (NIT) network interface code, by Ted Lemon
+ with one crucial tidbit of help from Stu Grossmen. */
+
+/*
+ * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+#if defined (USE_NIT_SEND) || defined (USE_NIT_RECEIVE)
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+
+#include <sys/time.h>
+#include <net/nit.h>
+#include <net/nit_if.h>
+#include <net/nit_pf.h>
+#include <net/nit_buf.h>
+#include <sys/stropts.h>
+#include <net/packetfilt.h>
+
+#include <netinet/in_systm.h>
+#include "includes/netinet/ip.h"
+#include "includes/netinet/udp.h"
+#include "includes/netinet/if_ether.h"
+
+/* Reinitializes the specified interface after an address change. This
+ is not required for packet-filter APIs. */
+
+#ifdef USE_NIT_SEND
+void if_reinitialize_send (info)
+ struct interface_info *info;
+{
+}
+#endif
+
+#ifdef USE_NIT_RECEIVE
+void if_reinitialize_receive (info)
+ struct interface_info *info;
+{
+}
+#endif
+
+/* Called by get_interface_list for each interface that's discovered.
+ Opens a packet filter for each interface and adds it to the select
+ mask. */
+
+int if_register_nit (info)
+ struct interface_info *info;
+{
+ int sock;
+ char filename[50];
+ struct ifreq ifr;
+ struct strioctl sio;
+
+ /* Open a NIT device */
+ sock = open ("/dev/nit", O_RDWR);
+ if (sock < 0)
+ log_fatal ("Can't open NIT device for %s: %m", info -> name);
+
+ /* Set the NIT device to point at this interface. */
+ sio.ic_cmd = NIOCBIND;
+ sio.ic_len = sizeof *(info -> ifp);
+ sio.ic_dp = (char *)(info -> ifp);
+ sio.ic_timout = INFTIM;
+ if (ioctl (sock, I_STR, &sio) < 0)
+ log_fatal ("Can't attach interface %s to nit device: %m",
+ info -> name);
+
+ /* Get the low-level address... */
+ sio.ic_cmd = SIOCGIFADDR;
+ sio.ic_len = sizeof ifr;
+ sio.ic_dp = (char *)&ifr;
+ sio.ic_timout = INFTIM;
+ if (ioctl (sock, I_STR, &sio) < 0)
+ log_fatal ("Can't get physical layer address for %s: %m",
+ info -> name);
+
+ /* XXX code below assumes ethernet interface! */
+ info -> hw_address.hlen = 7;
+ info -> hw_address.hbuf [0] = ARPHRD_ETHER;
+ memcpy (&info -> hw_address.hbuf [1],
+ ifr.ifr_ifru.ifru_addr.sa_data, 6);
+
+ if (ioctl (sock, I_PUSH, "pf") < 0)
+ log_fatal ("Can't push packet filter onto NIT for %s: %m",
+ info -> name);
+
+ return sock;
+}
+#endif /* USE_NIT_SEND || USE_NIT_RECEIVE */
+
+#ifdef USE_NIT_SEND
+void if_register_send (info)
+ struct interface_info *info;
+{
+ /* If we're using the nit API for sending and receiving,
+ we don't need to register this interface twice. */
+#ifndef USE_NIT_RECEIVE
+ struct packetfilt pf;
+ struct strioctl sio;
+
+ info -> wfdesc = if_register_nit (info);
+
+ pf.Pf_Priority = 0;
+ pf.Pf_FilterLen = 1;
+ pf.Pf_Filter [0] = ENF_PUSHZERO;
+
+ /* Set up an NIT filter that rejects everything... */
+ sio.ic_cmd = NIOCSETF;
+ sio.ic_len = sizeof pf;
+ sio.ic_dp = (char *)&pf;
+ sio.ic_timout = INFTIM;
+ if (ioctl (info -> wfdesc, I_STR, &sio) < 0)
+ log_fatal ("Can't set NIT filter: %m");
+#else
+ info -> wfdesc = info -> rfdesc;
+#endif
+ if (!quiet_interface_discovery)
+ log_info ("Sending on NIT/%s%s%s",
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+
+void if_deregister_send (info)
+ struct interface_info *info;
+{
+ /* If we're using the nit API for sending and receiving,
+ we don't need to register this interface twice. */
+#ifndef USE_NIT_RECEIVE
+ close (info -> wfdesc);
+#endif
+ info -> wfdesc = -1;
+ if (!quiet_interface_discovery)
+ log_info ("Disabling output on NIT/%s%s%s",
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+#endif /* USE_NIT_SEND */
+
+#ifdef USE_NIT_RECEIVE
+/* Packet filter program...
+ XXX Changes to the filter program may require changes to the constant
+ offsets used in if_register_send to patch the NIT program! XXX */
+
+void if_register_receive (info)
+ struct interface_info *info;
+{
+ int flag = 1;
+ u_int32_t x;
+ struct packetfilt pf;
+ struct strioctl sio;
+ u_int16_t addr [2];
+ struct timeval t;
+
+ /* Open a NIT device and hang it on this interface... */
+ info -> rfdesc = if_register_nit (info);
+
+ /* Set the snap length to 0, which means always take the whole
+ packet. */
+ x = 0;
+ if (ioctl (info -> rfdesc, NIOCSSNAP, &x) < 0)
+ log_fatal ("Can't set NIT snap length on %s: %m", info -> name);
+
+ /* Set the stream to byte stream mode */
+ if (ioctl (info -> rfdesc, I_SRDOPT, RMSGN) != 0)
+ log_info ("I_SRDOPT failed on %s: %m", info -> name);
+
+#if 0
+ /* Push on the chunker... */
+ if (ioctl (info -> rfdesc, I_PUSH, "nbuf") < 0)
+ log_fatal ("Can't push chunker onto NIT STREAM: %m");
+
+ /* Set the timeout to zero. */
+ t.tv_sec = 0;
+ t.tv_usec = 0;
+ if (ioctl (info -> rfdesc, NIOCSTIME, &t) < 0)
+ log_fatal ("Can't set chunk timeout: %m");
+#endif
+
+ /* Ask for no header... */
+ x = 0;
+ if (ioctl (info -> rfdesc, NIOCSFLAGS, &x) < 0)
+ log_fatal ("Can't set NIT flags on %s: %m", info -> name);
+
+ /* Set up the NIT filter program. */
+ /* XXX Unlike the BPF filter program, this one won't work if the
+ XXX IP packet is fragmented or if there are options on the IP
+ XXX header. */
+ pf.Pf_Priority = 0;
+ pf.Pf_FilterLen = 0;
+
+ pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHWORD + 6;
+ pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT + ENF_CAND;
+ pf.Pf_Filter [pf.Pf_FilterLen++] = htons (ETHERTYPE_IP);
+ pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT;
+ pf.Pf_Filter [pf.Pf_FilterLen++] = htons (IPPROTO_UDP);
+ pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHWORD + 11;
+ pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT + ENF_AND;
+ pf.Pf_Filter [pf.Pf_FilterLen++] = htons (0xFF);
+ pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_CAND;
+ pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHWORD + 18;
+ pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT + ENF_CAND;
+ pf.Pf_Filter [pf.Pf_FilterLen++] = local_port;
+
+ /* Install the filter... */
+ sio.ic_cmd = NIOCSETF;
+ sio.ic_len = sizeof pf;
+ sio.ic_dp = (char *)&pf;
+ sio.ic_timout = INFTIM;
+ if (ioctl (info -> rfdesc, I_STR, &sio) < 0)
+ log_fatal ("Can't set NIT filter on %s: %m", info -> name);
+
+ if (!quiet_interface_discovery)
+ log_info ("Listening on NIT/%s%s%s",
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+
+void if_deregister_receive (info)
+ struct interface_info *info;
+{
+ /* If we're using the nit API for sending and receiving,
+ we don't need to register this interface twice. */
+ close (info -> rfdesc);
+ info -> rfdesc = -1;
+
+ if (!quiet_interface_discovery)
+ log_info ("Disabling input on NIT/%s%s%s",
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+#endif /* USE_NIT_RECEIVE */
+
+#ifdef USE_NIT_SEND
+ssize_t send_packet (interface, packet, raw, len, from, to, hto)
+ struct interface_info *interface;
+ struct packet *packet;
+ struct dhcp_packet *raw;
+ size_t len;
+ struct in_addr from;
+ struct sockaddr_in *to;
+ struct hardware *hto;
+{
+ unsigned hbufp, ibufp;
+ double hh [16];
+ double ih [1536 / sizeof (double)];
+ unsigned char *buf = (unsigned char *)ih;
+ struct sockaddr *junk;
+ struct strbuf ctl, data;
+ struct sockaddr_in foo;
+ int result;
+
+ if (!strcmp (interface -> name, "fallback"))
+ return send_fallback (interface, packet, raw,
+ len, from, to, hto);
+
+ if (hto == NULL && interface->anycast_mac_addr.hlen)
+ hto = &interface->anycast_mac_addr;
+
+ /* Start with the sockaddr struct... */
+ junk = (struct sockaddr *)&hh [0];
+ hbufp = (((unsigned char *)&junk -> sa_data [0]) -
+ (unsigned char *)&hh[0]);
+ ibufp = 0;
+
+ /* Assemble the headers... */
+ assemble_hw_header (interface, (unsigned char *)junk, &hbufp, hto);
+ assemble_udp_ip_header (interface, buf, &ibufp,
+ from.s_addr, to -> sin_addr.s_addr,
+ to -> sin_port, (unsigned char *)raw, len);
+
+ /* Copy the data into the buffer (yuk). */
+ memcpy (buf + ibufp, raw, len);
+
+ /* Set up the sockaddr structure... */
+#if USE_SIN_LEN
+ junk -> sa_len = hbufp - 2; /* XXX */
+#endif
+ junk -> sa_family = AF_UNSPEC;
+
+ /* Set up the msg_buf structure... */
+ ctl.buf = (char *)&hh [0];
+ ctl.maxlen = ctl.len = hbufp;
+ data.buf = (char *)&ih [0];
+ data.maxlen = data.len = ibufp + len;
+
+ result = putmsg (interface -> wfdesc, &ctl, &data, 0);
+ if (result < 0)
+ log_error ("send_packet: %m");
+ return result;
+}
+#endif /* USE_NIT_SEND */
+
+#ifdef USE_NIT_RECEIVE
+ssize_t receive_packet (interface, buf, len, from, hfrom)
+ struct interface_info *interface;
+ unsigned char *buf;
+ size_t len;
+ struct sockaddr_in *from;
+ struct hardware *hfrom;
+{
+ int nread;
+ int length = 0;
+ int offset = 0;
+ unsigned char ibuf [1536];
+ int bufix = 0;
+ unsigned paylen;
+
+ length = read (interface -> rfdesc, ibuf, sizeof ibuf);
+ if (length <= 0)
+ return length;
+
+ /* Decode the physical header... */
+ offset = decode_hw_header (interface, ibuf, bufix, hfrom);
+
+ /* If a physical layer checksum failed (dunno of any
+ physical layer that supports this, but WTH), skip this
+ packet. */
+ if (offset < 0) {
+ return 0;
+ }
+
+ bufix += offset;
+ length -= offset;
+
+ /* Decode the IP and UDP headers... */
+ offset = decode_udp_ip_header (interface, ibuf, bufix,
+ from, length, &paylen, 1);
+
+ /* If the IP or UDP checksum was bad, skip the packet... */
+ if (offset < 0)
+ return 0;
+
+ bufix += offset;
+ length -= offset;
+
+ if (length < paylen)
+ log_fatal("Internal inconsistency at %s:%d.", MDL);
+
+ /* Copy out the data in the packet... */
+ memcpy(buf, &ibuf[bufix], paylen);
+ return paylen;
+}
+
+int can_unicast_without_arp (ip)
+ struct interface_info *ip;
+{
+ return 1;
+}
+
+int can_receive_unicast_unconfigured (ip)
+ struct interface_info *ip;
+{
+ return 1;
+}
+
+int supports_multiple_interfaces (ip)
+ struct interface_info *ip;
+{
+ return 1;
+}
+
+void maybe_setup_fallback ()
+{
+ isc_result_t status;
+ struct interface_info *fbi = (struct interface_info *)0;
+ if (setup_fallback (&fbi, MDL)) {
+ if_register_fallback (fbi);
+ status = omapi_register_io_object ((omapi_object_t *)fbi,
+ if_readsocket, 0,
+ fallback_discard, 0, 0);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register I/O handle for %s: %s",
+ fbi -> name, isc_result_totext (status));
+ interface_dereference (&fbi, MDL);
+ }
+}
+#endif
diff --git a/common/ns_name.c b/common/ns_name.c
new file mode 100644
index 0000000..829fe14
--- /dev/null
+++ b/common/ns_name.c
@@ -0,0 +1,789 @@
+/*
+ * Copyright (c) 2004,2009,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * http://www.isc.org/
+ */
+
+#include <sys/types.h>
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "minires.h"
+#include "arpa/nameser.h"
+
+/* Data. */
+
+static const char digits[] = "0123456789";
+
+/* Forward. */
+
+static int special(int);
+static int printable(int);
+static int dn_find(const u_char *, const u_char *,
+ const u_char * const *,
+ const u_char * const *);
+
+/* Public. */
+
+/*
+ * MRns_name_ntop(src, dst, dstsiz)
+ * Convert an encoded domain name to printable ascii as per RFC1035.
+ * return:
+ * Number of bytes written to buffer, or -1 (with errno set)
+ * notes:
+ * The root is returned as "."
+ * All other domains are returned in non absolute form
+ */
+int
+MRns_name_ntop(const u_char *src, char *dst, size_t dstsiz) {
+ const u_char *cp;
+ char *dn, *eom;
+ u_char c;
+ u_int n;
+
+ cp = src;
+ dn = dst;
+ eom = dst + dstsiz;
+
+ while ((n = *cp++) != 0) {
+ if ((n & NS_CMPRSFLGS) != 0) {
+ /* Some kind of compression pointer. */
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ if (dn != dst) {
+ if (dn >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ *dn++ = '.';
+ }
+ if (dn + n >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ for ((void)NULL; n > 0; n--) {
+ c = *cp++;
+ if (special(c)) {
+ if (dn + 1 >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ *dn++ = '\\';
+ *dn++ = (char)c;
+ } else if (!printable(c)) {
+ if (dn + 3 >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ *dn++ = '\\';
+ *dn++ = digits[c / 100];
+ *dn++ = digits[(c % 100) / 10];
+ *dn++ = digits[c % 10];
+ } else {
+ if (dn >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ *dn++ = (char)c;
+ }
+ }
+ }
+ if (dn == dst) {
+ if (dn >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ *dn++ = '.';
+ }
+ if (dn >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ *dn++ = '\0';
+ return (dn - dst);
+}
+
+/*
+ * MRns_name_pton(src, dst, dstsiz)
+ * Convert a ascii string into an encoded domain name as per RFC1035.
+ * return:
+ * -1 if it fails
+ * 1 if string was fully qualified
+ * 0 is string was not fully qualified
+ * notes:
+ * Enforces label and domain length limits.
+ */
+
+int
+MRns_name_pton(const char *src, u_char *dst, size_t dstsiz) {
+ u_char *label, *bp, *eom;
+ int c, n, escaped;
+ char *cp;
+
+ escaped = 0;
+ bp = dst;
+ eom = dst + dstsiz;
+ label = bp++;
+
+ while ((c = *src++) != 0) {
+ if (escaped) {
+ if ((cp = strchr(digits, c)) != NULL) {
+ n = (cp - digits) * 100;
+ if ((c = *src++) == 0 ||
+ (cp = strchr(digits, c)) == NULL) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ n += (cp - digits) * 10;
+ if ((c = *src++) == 0 ||
+ (cp = strchr(digits, c)) == NULL) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ n += (cp - digits);
+ if (n > 255) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ c = n;
+ }
+ escaped = 0;
+ } else if (c == '\\') {
+ escaped = 1;
+ continue;
+ } else if (c == '.') {
+ c = (bp - label - 1);
+ if ((c & NS_CMPRSFLGS) != 0) { /* Label too big. */
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ if (label >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ *label = c;
+ /* Fully qualified ? */
+ if (*src == '\0') {
+ if (c != 0) {
+ if (bp >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ *bp++ = '\0';
+ }
+ if ((bp - dst) > MAXCDNAME) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ return (1);
+ }
+ if (c == 0 || *src == '.') {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ label = bp++;
+ continue;
+ }
+ if (bp >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ *bp++ = (u_char)c;
+ }
+ c = (bp - label - 1);
+ if ((c & NS_CMPRSFLGS) != 0) { /* Label too big. */
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ if (label >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ *label = c;
+ if (c != 0) {
+ if (bp >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ *bp++ = 0;
+ }
+ if ((bp - dst) > MAXCDNAME) { /* src too big */
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * MRns_name_ntol(src, dst, dstsiz)
+ * Convert a network strings labels into all lowercase.
+ * return:
+ * Number of bytes written to buffer, or -1 (with errno set)
+ * notes:
+ * Enforces label and domain length limits.
+ */
+
+int
+MRns_name_ntol(const u_char *src, u_char *dst, size_t dstsiz) {
+ const u_char *cp;
+ u_char *dn, *eom;
+ u_char c;
+ u_int n;
+
+ cp = src;
+ dn = dst;
+ eom = dst + dstsiz;
+
+ if (dn >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ while ((n = *cp++) != 0) {
+ if ((n & NS_CMPRSFLGS) != 0) {
+ /* Some kind of compression pointer. */
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ *dn++ = n;
+ if (dn + n >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ for ((void)NULL; n > 0; n--) {
+ c = *cp++;
+ if (isupper(c))
+ *dn++ = tolower(c);
+ else
+ *dn++ = c;
+ }
+ }
+ *dn++ = '\0';
+ return (dn - dst);
+}
+
+/*
+ * MRns_name_unpack(msg, eom, src, dst, dstsiz)
+ * Unpack a domain name from a message, source may be compressed.
+ * return:
+ * -1 if it fails, or consumed octets if it succeeds.
+ */
+int
+MRns_name_unpack(const u_char *msg, const u_char *eom, const u_char *src,
+ u_char *dst, size_t dstsiz)
+{
+ const u_char *srcp, *dstlim;
+ u_char *dstp;
+ unsigned n;
+ int len;
+ int checked;
+
+ len = -1;
+ checked = 0;
+ dstp = dst;
+ srcp = src;
+ dstlim = dst + dstsiz;
+ if (srcp < msg || srcp >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ /* Fetch next label in domain name. */
+ while ((n = *srcp++) != 0) {
+ /* Check for indirection. */
+ switch (n & NS_CMPRSFLGS) {
+ case 0:
+ /* Limit checks. */
+ if (dstp + n + 1 >= dstlim || srcp + n >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ checked += n + 1;
+ *dstp++ = n;
+ memcpy(dstp, srcp, n);
+ dstp += n;
+ srcp += n;
+ break;
+
+ case NS_CMPRSFLGS:
+ if (srcp >= eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ if (len < 0)
+ len = srcp - src + 1;
+ srcp = msg + (((n & 0x3f) << 8) | (*srcp & 0xff));
+ if (srcp < msg || srcp >= eom) { /* Out of range. */
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ checked += 2;
+ /*
+ * Check for loops in the compressed name;
+ * if we've looked at the whole message,
+ * there must be a loop.
+ */
+ if (checked >= eom - msg) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ break;
+
+ default:
+ errno = EMSGSIZE;
+ return (-1); /* flag error */
+ }
+ }
+ *dstp = '\0';
+ if (len < 0)
+ len = srcp - src;
+ return (len);
+}
+
+/*
+ * MRns_name_pack(src, dst, dstsiz, dnptrs, lastdnptr)
+ * Pack domain name 'domain' into 'comp_dn'.
+ * return:
+ * Size of the compressed name, or -1.
+ * notes:
+ * 'dnptrs' is an array of pointers to previous compressed names.
+ * dnptrs[0] is a pointer to the beginning of the message. The array
+ * ends with NULL.
+ * 'lastdnptr' is a pointer to the end of the array pointed to
+ * by 'dnptrs'.
+ * Side effects:
+ * The list of pointers in dnptrs is updated for labels inserted into
+ * the message as we compress the name. If 'dnptr' is NULL, we don't
+ * try to compress names. If 'lastdnptr' is NULL, we don't update the
+ * list.
+ */
+int
+MRns_name_pack(const u_char *src, u_char *dst, unsigned dstsiz,
+ const u_char **dnptrs, const u_char **lastdnptr)
+{
+ u_char *dstp;
+ const u_char **cpp, **lpp, *eob, *msg;
+ const u_char *srcp;
+ unsigned n;
+ int l;
+
+ srcp = src;
+ dstp = dst;
+ eob = dstp + dstsiz;
+ lpp = cpp = NULL;
+ if (dnptrs != NULL) {
+ if ((msg = *dnptrs++) != NULL) {
+ for (cpp = dnptrs; *cpp != NULL; cpp++)
+ (void)NULL;
+ lpp = cpp; /* end of list to search */
+ }
+ } else
+ msg = NULL;
+
+ /* make sure the domain we are about to add is legal */
+ l = 0;
+ do {
+ n = *srcp;
+ if ((n & NS_CMPRSFLGS) != 0) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ l += n + 1;
+ if (l > MAXCDNAME) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ srcp += n + 1;
+ } while (n != 0);
+
+ /* from here on we need to reset compression pointer array on error */
+ srcp = src;
+ do {
+ /* Look to see if we can use pointers. */
+ n = *srcp;
+ if (n != 0 && msg != NULL) {
+ l = dn_find(srcp, msg, (const u_char * const *)dnptrs,
+ (const u_char * const *)lpp);
+ if (l >= 0) {
+ if (dstp + 1 >= eob) {
+ goto cleanup;
+ }
+ *dstp++ = (l >> 8) | NS_CMPRSFLGS;
+ *dstp++ = l % 256;
+ return (dstp - dst);
+ }
+ /* Not found, save it. */
+ if (lastdnptr != NULL && cpp < lastdnptr - 1 &&
+ (dstp - msg) < 0x4000) {
+ *cpp++ = dstp;
+ *cpp = NULL;
+ }
+ }
+ /* copy label to buffer */
+ if (n & NS_CMPRSFLGS) { /* Should not happen. */
+ goto cleanup;
+ }
+ if (dstp + 1 + n >= eob) {
+ goto cleanup;
+ }
+ memcpy(dstp, srcp, n + 1);
+ srcp += n + 1;
+ dstp += n + 1;
+ } while (n != 0);
+
+ if (dstp > eob) {
+cleanup:
+ if (msg != NULL)
+ *lpp = NULL;
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ return (dstp - dst);
+}
+
+/*
+ * MRns_name_uncompress(msg, eom, src, dst, dstsiz)
+ * Expand compressed domain name to presentation format.
+ * return:
+ * Number of bytes read out of `src', or -1 (with errno set).
+ * note:
+ * Root domain returns as "." not "".
+ */
+int
+MRns_name_uncompress(const u_char *msg, const u_char *eom, const u_char *src,
+ char *dst, size_t dstsiz)
+{
+ u_char tmp[NS_MAXCDNAME];
+ int n;
+
+ if ((n = MRns_name_unpack(msg, eom, src, tmp, sizeof tmp)) == -1)
+ return (-1);
+ if (MRns_name_ntop(tmp, dst, dstsiz) == -1)
+ return (-1);
+ return (n);
+}
+
+/*
+ * MRns_name_compress(src, dst, dstsiz, dnptrs, lastdnptr)
+ * Compress a domain name into wire format, using compression pointers.
+ * return:
+ * Number of bytes consumed in `dst' or -1 (with errno set).
+ * notes:
+ * 'dnptrs' is an array of pointers to previous compressed names.
+ * dnptrs[0] is a pointer to the beginning of the message.
+ * The list ends with NULL. 'lastdnptr' is a pointer to the end of the
+ * array pointed to by 'dnptrs'. Side effect is to update the list of
+ * pointers for labels inserted into the message as we compress the name.
+ * If 'dnptr' is NULL, we don't try to compress names. If 'lastdnptr'
+ * is NULL, we don't update the list.
+ */
+int
+MRns_name_compress(const char *src, u_char *dst, size_t dstsiz,
+ const u_char **dnptrs, const u_char **lastdnptr)
+{
+ u_char tmp[NS_MAXCDNAME];
+
+ if (MRns_name_pton(src, tmp, sizeof tmp) == -1)
+ return (-1);
+ return (MRns_name_pack(tmp, dst, dstsiz, dnptrs, lastdnptr));
+}
+
+/*
+ * MRns_name_skip(ptrptr, eom)
+ * Advance *ptrptr to skip over the compressed name it points at.
+ * return:
+ * 0 on success, -1 (with errno set) on failure.
+ */
+int
+MRns_name_skip(const u_char **ptrptr, const u_char *eom) {
+ const u_char *cp;
+ u_int n;
+
+ cp = *ptrptr;
+ while (cp < eom && (n = *cp++) != 0) {
+ /* Check for indirection. */
+ switch (n & NS_CMPRSFLGS) {
+ case 0: /* normal case, n == len */
+ cp += n;
+ continue;
+ case NS_CMPRSFLGS: /* indirection */
+ cp++;
+ break;
+ default: /* illegal type */
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ break;
+ }
+ if (cp > eom) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ *ptrptr = cp;
+ return (0);
+}
+
+/* Private. */
+
+/*
+ * special(ch)
+ * Thinking in noninternationalized USASCII (per the DNS spec),
+ * is this characted special ("in need of quoting") ?
+ * return:
+ * boolean.
+ */
+static int
+special(int ch) {
+ switch (ch) {
+ case 0x22: /* '"' */
+ case 0x2E: /* '.' */
+ case 0x3B: /* ';' */
+ case 0x5C: /* '\\' */
+ /* Special modifiers in zone files. */
+ case 0x40: /* '@' */
+ case 0x24: /* '$' */
+ return (1);
+ default:
+ return (0);
+ }
+}
+
+/*
+ * printable(ch)
+ * Thinking in noninternationalized USASCII (per the DNS spec),
+ * is this character visible and not a space when printed ?
+ * return:
+ * boolean.
+ */
+static int
+printable(int ch) {
+ return (ch > 0x20 && ch < 0x7f);
+}
+
+/*
+ * Thinking in noninternationalized USASCII (per the DNS spec),
+ * convert this character to lower case if it's upper case.
+ */
+static int
+mklower(int ch) {
+ if (ch >= 0x41 && ch <= 0x5A)
+ return (ch + 0x20);
+ return (ch);
+}
+
+/*
+ * dn_find(domain, msg, dnptrs, lastdnptr)
+ * Search for the counted-label name in an array of compressed names.
+ * return:
+ * offset from msg if found, or -1.
+ * notes:
+ * dnptrs is the pointer to the first name on the list,
+ * not the pointer to the start of the message.
+ */
+static int
+dn_find(const u_char *domain, const u_char *msg,
+ const u_char * const *dnptrs,
+ const u_char * const *lastdnptr)
+{
+ const u_char *dn, *cp, *sp;
+ const u_char * const *cpp;
+ u_int n;
+
+ for (cpp = dnptrs; cpp < lastdnptr; cpp++) {
+ dn = domain;
+ sp = cp = *cpp;
+ while ((n = *cp++) != 0) {
+ /*
+ * check for indirection
+ */
+ switch (n & NS_CMPRSFLGS) {
+ case 0: /* normal case, n == len */
+ if (n != *dn++)
+ goto next;
+ for ((void)NULL; n > 0; n--)
+ if (mklower(*dn++) != mklower(*cp++))
+ goto next;
+ /* Is next root for both ? */
+ if (*dn == '\0' && *cp == '\0')
+ return (sp - msg);
+ if (*dn)
+ continue;
+ goto next;
+
+ case NS_CMPRSFLGS: /* indirection */
+ cp = msg + (((n & 0x3f) << 8) | *cp);
+ break;
+
+ default: /* illegal type */
+ errno = EMSGSIZE;
+ return (-1);
+ }
+ }
+ next: ;
+ }
+ errno = ENOENT;
+ return (-1);
+}
+
+/*!
+ * \brief Creates a string of comma-separated domain-names from a
+ * compressed list
+ *
+ * Produces a null-terminated string of comma-separated domain-names from
+ * a buffer containing a compressed list of domain-names. The names will
+ * be dotted and without enclosing quotes. For example:
+ * If a compressed list contains the follwoing two domain names:
+ *
+ * a. one.two.com
+ * b. three.four.com
+ *
+ * The compressed data will look like this:
+ *
+ * 03 6f 6e 65 03 74 77 6f 03 63 6f 6d 00 05 74 68
+ * 72 65 65 04 66 6f 75 72 c0 08
+ *
+ * and will decompress into:
+ *
+ * one.two.com,three.four.com
+ *
+ * \param buf - buffer containing the compressed list of domain-names
+ * \param buflen - length of compressed list of domain-names
+ * \param dst_buf - buffer to receive the decompressed list
+ * \param dst_size - size of the destination buffer
+ *
+ * \return the length of the decompressed string when successful, -1 on
+ * error.
+ */
+int MRns_name_uncompress_list(const unsigned char* buf, int buflen,
+ char* dst_buf, size_t dst_size)
+{
+ const unsigned char* src = buf;
+ char* dst = dst_buf;
+ int consumed = 1;
+ int dst_remaining = dst_size;
+ int added_len = 0;
+ int first_pass = 1;
+
+ if (!buf || buflen == 0 || *buf == 0x00) {
+ /* nothing to do */
+ *dst = 0;
+ return (0);
+ }
+
+ while ((consumed > 0) && (src < (buf + buflen)))
+ {
+ if (dst_remaining <= 0) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+
+ if (!first_pass) {
+ *dst++ = ',';
+ *dst = '\0';
+ dst_remaining--;
+ }
+
+ consumed = MRns_name_uncompress(buf, buf + buflen, src,
+ dst, dst_remaining);
+ if (consumed < 0) {
+ return (-1);
+ }
+
+ src += consumed;
+ added_len = strlen(dst);
+ dst_remaining -= added_len;
+ dst += added_len;
+ first_pass = 0;
+ }
+ *dst='\0';
+
+ /* return the length of the uncompressed list string */
+ return (strlen(dst_buf));
+}
+
+/*!
+ * \brief Creates a compressed list from a string of comma-separated
+ * domain-names
+ *
+ * Produces a buffer containing a compressed data version of a list of
+ * domain-names extracted from a comma-separated string. Given a string
+ * containing:
+ *
+ * one.two.com,three.four.com
+ *
+ * It will compress this into:
+ *
+ * 03 6f 6e 65 03 74 77 6f 03 63 6f 6d 00 05 74 68
+ * 72 65 65 04 66 6f 75 72 c0 08
+ *
+ * \param buf - buffer containing the uncompressed string of domain-names
+ * \param buflen - length of uncompressed string of domain-names
+ * \param compbuf - buffer to receive the compressed list
+ * \param compbuf_size - size of the compression buffer
+ *
+ * \return the length of the compressed data when successful, -1 on error.
+ */
+int MRns_name_compress_list(const char* buf, int buflen,
+ unsigned char* compbuf, size_t compbuf_size)
+{
+ char cur_name[NS_MAXCDNAME];
+ const unsigned char *dnptrs[256], **lastdnptr;
+ const char* src;
+ const char* src_end;
+ unsigned clen = 0;
+ int result = 0;
+
+ memset(compbuf, 0, compbuf_size);
+ memset(dnptrs, 0, sizeof(dnptrs));
+ dnptrs[0] = compbuf;
+ lastdnptr = &dnptrs[255];
+
+ src = buf;
+ src_end = buf + buflen;
+ while (src < src_end) {
+ char *comma = strchr(src, ',');
+ int copylen = ((comma != NULL) ? comma - src : strlen(src));
+ if (copylen > (sizeof(cur_name) - 1)) {
+ errno = EMSGSIZE;
+ return (-1);
+ }
+
+ memcpy(cur_name, src, copylen);
+ cur_name[copylen] = '\0';
+ src += copylen + 1;
+
+ result = MRns_name_compress(cur_name, compbuf + clen,
+ compbuf_size - clen,
+ dnptrs, lastdnptr);
+
+ if (result < 0) {
+ return (-1);
+ }
+
+ clen += result;
+ }
+
+ /* return size of compressed list */
+ return(clen);
+}
diff --git a/common/options.c b/common/options.c
new file mode 100644
index 0000000..2e51bd4
--- /dev/null
+++ b/common/options.c
@@ -0,0 +1,4227 @@
+/* options.c
+
+ DHCP options parsing and reassembly. */
+
+/*
+ * Copyright (c) 2004-2012,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#define DHCP_OPTION_DATA
+#include "dhcpd.h"
+#include <omapip/omapip_p.h>
+#include <limits.h>
+
+struct option *vendor_cfg_option;
+
+static int pretty_text(char **, char *, const unsigned char **,
+ const unsigned char *, int);
+static int pretty_domain(char **, char *, const unsigned char **,
+ const unsigned char *);
+static int prepare_option_buffer(struct universe *universe, struct buffer *bp,
+ unsigned char *buffer, unsigned length,
+ unsigned code, int terminatep,
+ struct option_cache **opp);
+
+/* Parse all available options out of the specified packet. */
+/* Note, the caller is responsible for allocating packet->options. */
+int parse_options (packet)
+ struct packet *packet;
+{
+ struct option_cache *op = NULL;
+
+ /* If we don't see the magic cookie, there's nothing to parse. */
+ if (memcmp (packet -> raw -> options, DHCP_OPTIONS_COOKIE, 4)) {
+ packet -> options_valid = 0;
+ return 1;
+ }
+
+ /* Go through the options field, up to the end of the packet
+ or the End field. */
+ if (!parse_option_buffer (packet -> options,
+ &packet -> raw -> options [4],
+ (packet -> packet_length -
+ DHCP_FIXED_NON_UDP - 4),
+ &dhcp_universe)) {
+
+ /* STSN servers have a bug where they send a mangled
+ domain-name option, and whatever is beyond that in
+ the packet is junk. Microsoft clients accept this,
+ which is probably why whoever implemented the STSN
+ server isn't aware of the problem yet. To work around
+ this, we will accept corrupt packets from the server if
+ they contain a valid DHCP_MESSAGE_TYPE option, but
+ will not accept any corrupt client packets (the ISC DHCP
+ server is sufficiently widely used that it is probably
+ beneficial for it to be picky) and will not accept
+ packets whose type can't be determined. */
+
+ if ((op = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_MESSAGE_TYPE))) {
+ if (!op -> data.data ||
+ (op -> data.data [0] != DHCPOFFER &&
+ op -> data.data [0] != DHCPACK &&
+ op -> data.data [0] != DHCPNAK))
+ return 0;
+ } else
+ return 0;
+ }
+
+ /* If we parsed a DHCP Option Overload option, parse more
+ options out of the buffer(s) containing them. */
+ if ((op = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_OPTION_OVERLOAD))) {
+ if (op -> data.data [0] & 1) {
+ if (!parse_option_buffer
+ (packet -> options,
+ (unsigned char *)packet -> raw -> file,
+ sizeof packet -> raw -> file,
+ &dhcp_universe))
+ return 0;
+ }
+ if (op -> data.data [0] & 2) {
+ if (!parse_option_buffer
+ (packet -> options,
+ (unsigned char *)packet -> raw -> sname,
+ sizeof packet -> raw -> sname,
+ &dhcp_universe))
+ return 0;
+ }
+ }
+ packet -> options_valid = 1;
+ return 1;
+}
+
+/* Parse options out of the specified buffer, storing addresses of option
+ * values in packet->options.
+ */
+int parse_option_buffer (options, buffer, length, universe)
+ struct option_state *options;
+ const unsigned char *buffer;
+ unsigned length;
+ struct universe *universe;
+{
+ unsigned len, offset;
+ unsigned code;
+ struct option_cache *op = NULL, *nop = NULL;
+ struct buffer *bp = (struct buffer *)0;
+ struct option *option = NULL;
+ char *reason = "general failure";
+
+ if (!buffer_allocate (&bp, length, MDL)) {
+ log_error ("no memory for option buffer.");
+ return 0;
+ }
+ memcpy (bp -> data, buffer, length);
+
+ for (offset = 0;
+ (offset + universe->tag_size) <= length &&
+ (code = universe->get_tag(buffer + offset)) != universe->end; ) {
+ offset += universe->tag_size;
+
+ /* Pad options don't have a length - just skip them. */
+ if (code == DHO_PAD)
+ continue;
+
+ /* Don't look for length if the buffer isn't that big. */
+ if ((offset + universe->length_size) > length) {
+ reason = "code tag at end of buffer - missing "
+ "length field";
+ goto bogus;
+ }
+
+ /* All other fields (except PAD and END handled above)
+ * have a length field, unless it's a DHCPv6 zero-length
+ * options space (eg any of the enterprise-id'd options).
+ *
+ * Zero-length-size option spaces basically consume the
+ * entire options buffer, so have at it.
+ */
+ if (universe->get_length != NULL)
+ len = universe->get_length(buffer + offset);
+ else if (universe->length_size == 0)
+ len = length - universe->tag_size;
+ else {
+ log_fatal("Improperly configured option space(%s): "
+ "may not have a nonzero length size "
+ "AND a NULL get_length function.",
+ universe->name);
+
+ /* Silence compiler warnings. */
+ return 0;
+ }
+
+ offset += universe->length_size;
+
+ option_code_hash_lookup(&option, universe->code_hash, &code,
+ 0, MDL);
+
+ /* If the length is outrageous, the options are bad. */
+ if (offset + len > length) {
+ reason = "option length exceeds option buffer length";
+ bogus:
+ log_error("parse_option_buffer: malformed option "
+ "%s.%s (code %u): %s.", universe->name,
+ option ? option->name : "<unknown>",
+ code, reason);
+ buffer_dereference (&bp, MDL);
+ return 0;
+ }
+
+ /* If the option contains an encapsulation, parse it. If
+ the parse fails, or the option isn't an encapsulation (by
+ far the most common case), or the option isn't entirely
+ an encapsulation, keep the raw data as well. */
+ if (!(option &&
+ (option->format[0] == 'e' ||
+ option->format[0] == 'E') &&
+ (parse_encapsulated_suboptions(options, option,
+ bp->data + offset, len,
+ universe, NULL)))) {
+ op = lookup_option(universe, options, code);
+
+ if (op != NULL && universe->concat_duplicates) {
+ struct data_string new;
+ memset(&new, 0, sizeof new);
+ if (!buffer_allocate(&new.buffer,
+ op->data.len + len,
+ MDL)) {
+ log_error("parse_option_buffer: "
+ "No memory.");
+ buffer_dereference(&bp, MDL);
+ return 0;
+ }
+ /* Copy old option to new data object. */
+ memcpy(new.buffer->data, op->data.data,
+ op->data.len);
+ /* Concat new option behind old. */
+ memcpy(new.buffer->data + op->data.len,
+ bp->data + offset, len);
+ new.len = op->data.len + len;
+ new.data = new.buffer->data;
+ /* Save new concat'd object. */
+ data_string_forget(&op->data, MDL);
+ data_string_copy(&op->data, &new, MDL);
+ data_string_forget(&new, MDL);
+ } else if (op != NULL) {
+ /* We must append this statement onto the
+ * end of the list.
+ */
+ while (op->next != NULL)
+ op = op->next;
+
+ if (!option_cache_allocate(&nop, MDL)) {
+ log_error("parse_option_buffer: "
+ "No memory.");
+ buffer_dereference(&bp, MDL);
+ return 0;
+ }
+
+ option_reference(&nop->option, op->option, MDL);
+
+ nop->data.buffer = NULL;
+ buffer_reference(&nop->data.buffer, bp, MDL);
+ nop->data.data = bp->data + offset;
+ nop->data.len = len;
+
+ option_cache_reference(&op->next, nop, MDL);
+ option_cache_dereference(&nop, MDL);
+ } else {
+ if (save_option_buffer(universe, options, bp,
+ bp->data + offset, len,
+ code, 1) == 0) {
+ log_error("parse_option_buffer: "
+ "save_option_buffer failed");
+ buffer_dereference(&bp, MDL);
+ return 0;
+ }
+ }
+ }
+ option_dereference(&option, MDL);
+ offset += len;
+ }
+ buffer_dereference (&bp, MDL);
+ return 1;
+}
+
+/* If an option in an option buffer turns out to be an encapsulation,
+ figure out what to do. If we don't know how to de-encapsulate it,
+ or it's not well-formed, return zero; otherwise, return 1, indicating
+ that we succeeded in de-encapsulating it. */
+
+struct universe *find_option_universe (struct option *eopt, const char *uname)
+{
+ int i;
+ char *s, *t;
+ struct universe *universe = (struct universe *)0;
+
+ /* Look for the E option in the option format. */
+ s = strchr (eopt -> format, 'E');
+ if (!s) {
+ log_error ("internal encapsulation format error 1.");
+ return 0;
+ }
+ /* Look for the universe name in the option format. */
+ t = strchr (++s, '.');
+ /* If there was no trailing '.', or there's something after the
+ trailing '.', the option is bogus and we can't use it. */
+ if (!t || t [1]) {
+ log_error ("internal encapsulation format error 2.");
+ return 0;
+ }
+ if (t == s && uname) {
+ for (i = 0; i < universe_count; i++) {
+ if (!strcmp (universes [i] -> name, uname)) {
+ universe = universes [i];
+ break;
+ }
+ }
+ } else if (t != s) {
+ for (i = 0; i < universe_count; i++) {
+ if (strlen (universes [i] -> name) == t - s &&
+ !memcmp (universes [i] -> name,
+ s, (unsigned)(t - s))) {
+ universe = universes [i];
+ break;
+ }
+ }
+ }
+ return universe;
+}
+
+/* If an option in an option buffer turns out to be an encapsulation,
+ figure out what to do. If we don't know how to de-encapsulate it,
+ or it's not well-formed, return zero; otherwise, return 1, indicating
+ that we succeeded in de-encapsulating it. */
+
+int parse_encapsulated_suboptions (struct option_state *options,
+ struct option *eopt,
+ const unsigned char *buffer,
+ unsigned len, struct universe *eu,
+ const char *uname)
+{
+ int i;
+ struct universe *universe = find_option_universe (eopt, uname);
+
+ /* If we didn't find the universe, we can't do anything with it
+ right now (e.g., we can't decode vendor options until we've
+ decoded the packet and executed the scopes that it matches). */
+ if (!universe)
+ return 0;
+
+ /* If we don't have a decoding function for it, we can't decode
+ it. */
+ if (!universe -> decode)
+ return 0;
+
+ i = (*universe -> decode) (options, buffer, len, universe);
+
+ /* If there is stuff before the suboptions, we have to keep it. */
+ if (eopt -> format [0] != 'E')
+ return 0;
+ /* Otherwise, return the status of the decode function. */
+ return i;
+}
+
+int fqdn_universe_decode (struct option_state *options,
+ const unsigned char *buffer,
+ unsigned length, struct universe *u)
+{
+ struct buffer *bp = (struct buffer *)0;
+
+ /* FQDN options have to be at least four bytes long. */
+ if (length < 3)
+ return 0;
+
+ /* Save the contents of the option in a buffer. */
+ if (!buffer_allocate (&bp, length + 4, MDL)) {
+ log_error ("no memory for option buffer.");
+ return 0;
+ }
+ memcpy (&bp -> data [3], buffer + 1, length - 1);
+
+ if (buffer [0] & 4) /* encoded */
+ bp -> data [0] = 1;
+ else
+ bp -> data [0] = 0;
+ if (!save_option_buffer(&fqdn_universe, options, bp,
+ bp->data, 1, FQDN_ENCODED, 0)) {
+ bad:
+ buffer_dereference (&bp, MDL);
+ return 0;
+ }
+
+ if (buffer [0] & 1) /* server-update */
+ bp -> data [2] = 1;
+ else
+ bp -> data [2] = 0;
+ if (buffer [0] & 2) /* no-client-update */
+ bp -> data [1] = 1;
+ else
+ bp -> data [1] = 0;
+
+ /* XXX Ideally we should store the name in DNS format, so if the
+ XXX label isn't in DNS format, we convert it to DNS format,
+ XXX rather than converting labels specified in DNS format to
+ XXX the plain ASCII representation. But that's hard, so
+ XXX not now. */
+
+ /* Not encoded using DNS format? */
+ if (!bp -> data [0]) {
+ unsigned i;
+
+ /* Some broken clients NUL-terminate this option. */
+ if (buffer [length - 1] == 0) {
+ --length;
+ bp -> data [1] = 1;
+ }
+
+ /* Determine the length of the hostname component of the
+ name. If the name contains no '.' character, it
+ represents a non-qualified label. */
+ for (i = 3; i < length && buffer [i] != '.'; i++);
+ i -= 3;
+
+ /* Note: If the client sends a FQDN, the first '.' will
+ be used as a NUL terminator for the hostname. */
+ if (i && (!save_option_buffer(&fqdn_universe, options, bp,
+ &bp->data[5], i,
+ FQDN_HOSTNAME, 0)))
+ goto bad;
+ /* Note: If the client sends a single label, the
+ FQDN_DOMAINNAME option won't be set. */
+ if (length > 4 + i &&
+ (!save_option_buffer(&fqdn_universe, options, bp,
+ &bp -> data[6 + i], length - 4 - i,
+ FQDN_DOMAINNAME, 1)))
+ goto bad;
+ /* Also save the whole name. */
+ if (length > 3) {
+ if (!save_option_buffer(&fqdn_universe, options, bp,
+ &bp -> data [5], length - 3,
+ FQDN_FQDN, 1))
+ goto bad;
+ }
+ } else {
+ unsigned len;
+ unsigned total_len = 0;
+ unsigned first_len = 0;
+ int terminated = 0;
+ unsigned char *s;
+
+ s = &bp -> data[5];
+
+ while (s < &bp -> data[0] + length + 2) {
+ len = *s;
+ if (len > 63) {
+ log_info ("fancy bits in fqdn option");
+ return 0;
+ }
+ if (len == 0) {
+ terminated = 1;
+ break;
+ }
+ if (s + len > &bp -> data [0] + length + 3) {
+ log_info ("fqdn tag longer than buffer");
+ return 0;
+ }
+
+ if (first_len == 0) {
+ first_len = len;
+ }
+
+ *s = '.';
+ s += len + 1;
+ total_len += len + 1;
+ }
+
+ /* We wind up with a length that's one too many because
+ we shouldn't increment for the last label, but there's
+ no way to tell we're at the last label until we exit
+ the loop. :'*/
+ if (total_len > 0)
+ total_len--;
+
+ if (!terminated) {
+ first_len = total_len;
+ }
+
+ if (first_len > 0 &&
+ !save_option_buffer(&fqdn_universe, options, bp,
+ &bp -> data[6], first_len,
+ FQDN_HOSTNAME, 0))
+ goto bad;
+ if (total_len > 0 && first_len != total_len) {
+ if (!save_option_buffer(&fqdn_universe, options, bp,
+ &bp->data[6 + first_len],
+ total_len - first_len,
+ FQDN_DOMAINNAME, 1))
+ goto bad;
+ }
+ if (total_len > 0)
+ if (!save_option_buffer (&fqdn_universe, options, bp,
+ &bp -> data [6], total_len,
+ FQDN_FQDN, 1))
+ goto bad;
+ }
+
+ if (!save_option_buffer (&fqdn_universe, options, bp,
+ &bp -> data [1], 1,
+ FQDN_NO_CLIENT_UPDATE, 0))
+ goto bad;
+ if (!save_option_buffer (&fqdn_universe, options, bp,
+ &bp -> data [2], 1,
+ FQDN_SERVER_UPDATE, 0))
+ goto bad;
+
+ if (!save_option_buffer (&fqdn_universe, options, bp,
+ &bp -> data [3], 1,
+ FQDN_RCODE1, 0))
+ goto bad;
+ if (!save_option_buffer (&fqdn_universe, options, bp,
+ &bp -> data [4], 1,
+ FQDN_RCODE2, 0))
+ goto bad;
+
+ buffer_dereference (&bp, MDL);
+ return 1;
+}
+
+/*
+ * Load all options into a buffer, and then split them out into the three
+ * separate fields in the dhcp packet (options, file, and sname) where
+ * options can be stored.
+ *
+ * returns 0 on error, length of packet on success
+ */
+int
+cons_options(struct packet *inpacket, struct dhcp_packet *outpacket,
+ struct lease *lease, struct client_state *client_state,
+ int mms, struct option_state *in_options,
+ struct option_state *cfg_options,
+ struct binding_scope **scope,
+ int overload_avail, int terminate, int bootpp,
+ struct data_string *prl, const char *vuname)
+{
+#define PRIORITY_COUNT 300
+ unsigned priority_list[PRIORITY_COUNT];
+ int priority_len;
+ unsigned char buffer[4096], agentopts[1024];
+ unsigned index = 0;
+ unsigned mb_size = 0, mb_max = 0;
+ unsigned option_size = 0, agent_size = 0;
+ unsigned length;
+ int i;
+ struct option_cache *op;
+ struct data_string ds;
+ pair pp, *hash;
+ int overload_used = 0;
+ int of1 = 0, of2 = 0;
+
+ memset(&ds, 0, sizeof ds);
+
+ /*
+ * If there's a Maximum Message Size option in the incoming packet
+ * and no alternate maximum message size has been specified, or
+ * if the one specified in the packet is shorter than the
+ * alternative, take the one in the packet.
+ */
+
+ if (inpacket &&
+ (op = lookup_option(&dhcp_universe, inpacket->options,
+ DHO_DHCP_MAX_MESSAGE_SIZE)) &&
+ (evaluate_option_cache(&ds, inpacket, lease,
+ client_state, in_options,
+ cfg_options, scope, op, MDL) != 0)) {
+ if (ds.len >= sizeof (u_int16_t)) {
+ i = getUShort(ds.data);
+ if(!mms || (i < mms))
+ mms = i;
+ }
+ data_string_forget(&ds, MDL);
+ }
+
+ /*
+ * If the client has provided a maximum DHCP message size,
+ * use that, up to the MTU limit. Otherwise, if it's BOOTP,
+ * only 64 bytes; otherwise use up to the minimum IP MTU size
+ * (576 bytes).
+ *
+ * XXX if a BOOTP client specifies a max message size, we will
+ * honor it.
+ */
+ if (mms) {
+ if (mms < DHCP_MTU_MIN)
+ /* Enforce minimum packet size, per RFC 2132 */
+ mb_size = DHCP_MIN_OPTION_LEN;
+ else if (mms > DHCP_MTU_MAX)
+ /*
+ * TODO: Packets longer than 1500 bytes really
+ * should be allowed, but it requires upstream
+ * changes to the way the packet is allocated. For
+ * now, we forbid them. They won't be needed very
+ * often anyway.
+ */
+ mb_size = DHCP_MAX_OPTION_LEN;
+ else
+ mb_size = mms - DHCP_FIXED_LEN;
+ } else if (bootpp) {
+ mb_size = 64;
+ if (inpacket != NULL &&
+ (inpacket->packet_length >= 64 + DHCP_FIXED_NON_UDP))
+ mb_size = inpacket->packet_length - DHCP_FIXED_NON_UDP;
+ } else
+ mb_size = DHCP_MIN_OPTION_LEN;
+
+ /*
+ * If answering a client message, see whether any relay agent
+ * options were included with the message. If so, save them
+ * to copy back in later, and make space in the main buffer
+ * to accommodate them
+ */
+ if (client_state == NULL) {
+ priority_list[0] = DHO_DHCP_AGENT_OPTIONS;
+ priority_len = 1;
+ agent_size = store_options(NULL, agentopts, 0,
+ sizeof(agentopts),
+ inpacket, lease, client_state,
+ in_options, cfg_options, scope,
+ priority_list, priority_len,
+ 0, 0, 0, NULL);
+
+ mb_size += agent_size;
+ if (mb_size > DHCP_MAX_OPTION_LEN)
+ mb_size = DHCP_MAX_OPTION_LEN;
+ }
+
+ /*
+ * Set offsets for buffer data to be copied into filename
+ * and servername fields
+ */
+ if (mb_size > agent_size)
+ mb_max = mb_size - agent_size;
+ else
+ mb_max = mb_size;
+
+ if (overload_avail & 1) {
+ of1 = mb_max;
+ mb_max += DHCP_FILE_LEN;
+ }
+
+ if (overload_avail & 2) {
+ of2 = mb_max;
+ mb_max += DHCP_SNAME_LEN;
+ }
+
+ /*
+ * Preload the option priority list with protocol-mandatory options.
+ * This effectively gives these options the highest priority.
+ * This provides the order for any available options, the option
+ * must be in the option cache in order to actually be included.
+ */
+ priority_len = 0;
+ priority_list[priority_len++] = DHO_DHCP_MESSAGE_TYPE;
+ priority_list[priority_len++] = DHO_DHCP_SERVER_IDENTIFIER;
+ priority_list[priority_len++] = DHO_DHCP_LEASE_TIME;
+ priority_list[priority_len++] = DHO_DHCP_RENEWAL_TIME;
+ priority_list[priority_len++] = DHO_DHCP_REBINDING_TIME;
+ priority_list[priority_len++] = DHO_DHCP_MESSAGE;
+ priority_list[priority_len++] = DHO_DHCP_REQUESTED_ADDRESS;
+ priority_list[priority_len++] = DHO_ASSOCIATED_IP;
+
+ if (prl != NULL && prl->len > 0) {
+ if ((op = lookup_option(&dhcp_universe, cfg_options,
+ DHO_SUBNET_SELECTION))) {
+ if (priority_len < PRIORITY_COUNT)
+ priority_list[priority_len++] =
+ DHO_SUBNET_SELECTION;
+ }
+
+ data_string_truncate(prl, (PRIORITY_COUNT - priority_len));
+
+ /*
+ * Copy the client's PRL onto the priority_list after our high
+ * priority header.
+ */
+ for (i = 0; i < prl->len; i++) {
+ /*
+ * Prevent client from changing order of delivery
+ * of relay agent information option.
+ */
+ if (prl->data[i] != DHO_DHCP_AGENT_OPTIONS)
+ priority_list[priority_len++] = prl->data[i];
+ }
+
+ /*
+ * If the client doesn't request the FQDN option explicitly,
+ * to indicate priority, consider it lowest priority. Fit
+ * in the packet if there is space. Note that the option
+ * may only be included if the client supplied one.
+ */
+ if ((inpacket != NULL) && (priority_len < PRIORITY_COUNT) &&
+ (lookup_option(&fqdn_universe, inpacket->options,
+ FQDN_ENCODED) != NULL))
+ priority_list[priority_len++] = DHO_FQDN;
+
+ /*
+ * Some DHCP Servers will give the subnet-mask option if
+ * it is not on the parameter request list - so some client
+ * implementations have come to rely on this - so we will
+ * also make sure we supply this, at lowest priority.
+ *
+ * This is only done in response to DHCPDISCOVER or
+ * DHCPREQUEST messages, to avoid providing the option on
+ * DHCPINFORM or DHCPLEASEQUERY responses (if the client
+ * didn't request it).
+ */
+ if ((inpacket != NULL) && (priority_len < PRIORITY_COUNT) &&
+ ((inpacket->packet_type == DHCPDISCOVER) ||
+ (inpacket->packet_type == DHCPREQUEST)))
+ priority_list[priority_len++] = DHO_SUBNET_MASK;
+ } else {
+ /*
+ * First, hardcode some more options that ought to be
+ * sent first...these are high priority to have in the
+ * packet.
+ */
+ priority_list[priority_len++] = DHO_SUBNET_MASK;
+ priority_list[priority_len++] = DHO_ROUTERS;
+ priority_list[priority_len++] = DHO_DOMAIN_NAME_SERVERS;
+ priority_list[priority_len++] = DHO_HOST_NAME;
+ priority_list[priority_len++] = DHO_FQDN;
+
+ /*
+ * Append a list of the standard DHCP options from the
+ * standard DHCP option space. Actually, if a site
+ * option space hasn't been specified, we wind up
+ * treating the dhcp option space as the site option
+ * space, and the first for loop is skipped, because
+ * it's slightly more general to do it this way,
+ * taking the 1Q99 DHCP futures work into account.
+ */
+ if (cfg_options->site_code_min) {
+ for (i = 0; i < OPTION_HASH_SIZE; i++) {
+ hash = cfg_options->universes[dhcp_universe.index];
+ if (hash) {
+ for (pp = hash[i]; pp; pp = pp->cdr) {
+ op = (struct option_cache *)(pp->car);
+ if (op->option->code <
+ cfg_options->site_code_min &&
+ priority_len < PRIORITY_COUNT &&
+ op->option->code != DHO_DHCP_AGENT_OPTIONS)
+ priority_list[priority_len++] =
+ op->option->code;
+ }
+ }
+ }
+ }
+
+ /*
+ * Now cycle through the site option space, or if there
+ * is no site option space, we'll be cycling through the
+ * dhcp option space.
+ */
+ for (i = 0; i < OPTION_HASH_SIZE; i++) {
+ hash = cfg_options->universes[cfg_options->site_universe];
+ if (hash != NULL)
+ for (pp = hash[i]; pp; pp = pp->cdr) {
+ op = (struct option_cache *)(pp->car);
+ if (op->option->code >=
+ cfg_options->site_code_min &&
+ priority_len < PRIORITY_COUNT &&
+ op->option->code != DHO_DHCP_AGENT_OPTIONS)
+ priority_list[priority_len++] =
+ op->option->code;
+ }
+ }
+
+ /*
+ * Put any spaces that are encapsulated on the list,
+ * sort out whether they contain values later.
+ */
+ for (i = 0; i < cfg_options->universe_count; i++) {
+ if (universes[i]->enc_opt &&
+ priority_len < PRIORITY_COUNT &&
+ universes[i]->enc_opt->universe == &dhcp_universe) {
+ if (universes[i]->enc_opt->code !=
+ DHO_DHCP_AGENT_OPTIONS)
+ priority_list[priority_len++] =
+ universes[i]->enc_opt->code;
+ }
+ }
+
+ /*
+ * The vendor option space can't stand on its own, so always
+ * add it to the list.
+ */
+ if (priority_len < PRIORITY_COUNT)
+ priority_list[priority_len++] =
+ DHO_VENDOR_ENCAPSULATED_OPTIONS;
+ }
+
+ /* Put the cookie up front... */
+ memcpy(buffer, DHCP_OPTIONS_COOKIE, 4);
+ index += 4;
+
+ /* Copy the options into the big buffer... */
+ option_size = store_options(&overload_used, buffer, index, mb_max,
+ inpacket, lease, client_state,
+ in_options, cfg_options, scope,
+ priority_list, priority_len,
+ of1, of2, terminate, vuname);
+
+ /* If store_options() failed */
+ if (option_size == 0)
+ return 0;
+
+ /* How much was stored in the main buffer? */
+ index += option_size;
+
+ /*
+ * If we're going to have to overload, store the overload
+ * option first.
+ */
+ if (overload_used) {
+ if (mb_size - agent_size - index < 3)
+ return 0;
+
+ buffer[index++] = DHO_DHCP_OPTION_OVERLOAD;
+ buffer[index++] = 1;
+ buffer[index++] = overload_used;
+
+ if (overload_used & 1)
+ memcpy(outpacket->file, &buffer[of1], DHCP_FILE_LEN);
+
+ if (overload_used & 2)
+ memcpy(outpacket->sname, &buffer[of2], DHCP_SNAME_LEN);
+ }
+
+ /* Now copy in preserved agent options, if any */
+ if (agent_size) {
+ if (mb_size - index >= agent_size) {
+ memcpy(&buffer[index], agentopts, agent_size);
+ index += agent_size;
+ } else
+ log_error("Unable to store relay agent information "
+ "in reply packet.");
+ }
+
+ /* Tack a DHO_END option onto the packet if we need to. */
+ if (index < mb_size)
+ buffer[index++] = DHO_END;
+
+ /* Copy main buffer into the options buffer of the packet */
+ memcpy(outpacket->options, buffer, index);
+
+ /* Figure out the length. */
+ length = DHCP_FIXED_NON_UDP + index;
+ return length;
+}
+
+/*
+ * XXX: We currently special case collecting VSIO options.
+ * We should be able to handle this in a more generic fashion, by
+ * including any encapsulated options that are present and desired.
+ * This will look something like the VSIO handling VSIO code.
+ * We may also consider handling the ORO-like options within
+ * encapsulated spaces.
+ */
+
+struct vsio_state {
+ char *buf;
+ int buflen;
+ int bufpos;
+};
+
+static void
+vsio_options(struct option_cache *oc,
+ struct packet *packet,
+ struct lease *dummy_lease,
+ struct client_state *dummy_client_state,
+ struct option_state *dummy_opt_state,
+ struct option_state *opt_state,
+ struct binding_scope **dummy_binding_scope,
+ struct universe *universe,
+ void *void_vsio_state) {
+ struct vsio_state *vs = (struct vsio_state *)void_vsio_state;
+ struct data_string ds;
+ int total_len;
+
+ memset(&ds, 0, sizeof(ds));
+ if (evaluate_option_cache(&ds, packet, NULL,
+ NULL, opt_state, NULL,
+ &global_scope, oc, MDL)) {
+ total_len = ds.len + universe->tag_size + universe->length_size;
+ if (total_len <= (vs->buflen - vs->bufpos)) {
+ if (universe->tag_size == 1) {
+ vs->buf[vs->bufpos++] = oc->option->code;
+ } else if (universe->tag_size == 2) {
+ putUShort((unsigned char *)vs->buf+vs->bufpos,
+ oc->option->code);
+ vs->bufpos += 2;
+ } else if (universe->tag_size == 4) {
+ putULong((unsigned char *)vs->buf+vs->bufpos,
+ oc->option->code);
+ vs->bufpos += 4;
+ }
+ if (universe->length_size == 1) {
+ vs->buf[vs->bufpos++] = ds.len;
+ } else if (universe->length_size == 2) {
+ putUShort((unsigned char *)vs->buf+vs->bufpos,
+ ds.len);
+ vs->bufpos += 2;
+ } else if (universe->length_size == 4) {
+ putULong((unsigned char *)vs->buf+vs->bufpos,
+ ds.len);
+ vs->bufpos += 4;
+ }
+ memcpy(vs->buf + vs->bufpos, ds.data, ds.len);
+ vs->bufpos += ds.len;
+ } else {
+ log_debug("No space for option %d in VSIO space %s.",
+ oc->option->code, universe->name);
+ }
+ data_string_forget(&ds, MDL);
+ } else {
+ log_error("Error evaluating option %d in VSIO space %s.",
+ oc->option->code, universe->name);
+ }
+}
+
+/*
+ * Stores the options from the DHCPv6 universe into the buffer given.
+ *
+ * Required options are given as a 0-terminated list of option codes.
+ * Once those are added, the ORO is consulted.
+ */
+
+int
+store_options6(char *buf, int buflen,
+ struct option_state *opt_state,
+ struct packet *packet,
+ const int *required_opts,
+ struct data_string *oro) {
+ int i, j;
+ struct option_cache *oc;
+ struct option *o;
+ struct data_string ds;
+ int bufpos;
+ int oro_size;
+ u_int16_t code;
+ int in_required_opts;
+ int vsio_option_code;
+ int vsio_wanted;
+ struct vsio_state vs;
+ unsigned char *tmp;
+
+ bufpos = 0;
+ vsio_wanted = 0;
+
+ /*
+ * Find the option code for the VSIO universe.
+ */
+ vsio_option_code = 0;
+ o = vsio_universe.enc_opt;
+ while (o != NULL) {
+ if (o->universe == &dhcpv6_universe) {
+ vsio_option_code = o->code;
+ break;
+ }
+ o = o->universe->enc_opt;
+ }
+ if (vsio_option_code == 0) {
+ log_fatal("No VSIO option code found.");
+ }
+
+ if (required_opts != NULL) {
+ for (i=0; required_opts[i] != 0; i++) {
+ if (required_opts[i] == vsio_option_code) {
+ vsio_wanted = 1;
+ }
+
+ oc = lookup_option(&dhcpv6_universe,
+ opt_state, required_opts[i]);
+ if (oc == NULL) {
+ continue;
+ }
+ memset(&ds, 0, sizeof(ds));
+ for (; oc != NULL ; oc = oc->next) {
+ if (evaluate_option_cache(&ds, packet, NULL,
+ NULL, opt_state,
+ NULL, &global_scope,
+ oc, MDL)) {
+ if ((ds.len + 4) <=
+ (buflen - bufpos)) {
+ tmp = (unsigned char *)buf;
+ tmp += bufpos;
+ /* option tag */
+ putUShort(tmp,
+ required_opts[i]);
+ /* option length */
+ putUShort(tmp+2, ds.len);
+ /* option data */
+ memcpy(tmp+4, ds.data, ds.len);
+ /* update position */
+ bufpos += (4 + ds.len);
+ } else {
+ log_debug("No space for "
+ "option %d",
+ required_opts[i]);
+ }
+ data_string_forget(&ds, MDL);
+ } else {
+ log_error("Error evaluating option %d",
+ required_opts[i]);
+ }
+ }
+ }
+ }
+
+ if (oro == NULL) {
+ oro_size = 0;
+ } else {
+ oro_size = oro->len / 2;
+ }
+ for (i=0; i<oro_size; i++) {
+ memcpy(&code, oro->data+(i*2), 2);
+ code = ntohs(code);
+
+ /*
+ * See if we've already included this option because
+ * it is required.
+ */
+ in_required_opts = 0;
+ if (required_opts != NULL) {
+ for (j=0; required_opts[j] != 0; j++) {
+ if (required_opts[j] == code) {
+ in_required_opts = 1;
+ break;
+ }
+ }
+ }
+ if (in_required_opts) {
+ continue;
+ }
+
+ /*
+ * See if this is the VSIO option.
+ */
+ if (code == vsio_option_code) {
+ vsio_wanted = 1;
+ }
+
+ /*
+ * Not already added, find this option.
+ */
+ oc = lookup_option(&dhcpv6_universe, opt_state, code);
+ memset(&ds, 0, sizeof(ds));
+ for (; oc != NULL ; oc = oc->next) {
+ if (evaluate_option_cache(&ds, packet, NULL, NULL,
+ opt_state, NULL,
+ &global_scope, oc, MDL)) {
+ if ((ds.len + 4) <= (buflen - bufpos)) {
+ tmp = (unsigned char *)buf + bufpos;
+ /* option tag */
+ putUShort(tmp, code);
+ /* option length */
+ putUShort(tmp+2, ds.len);
+ /* option data */
+ memcpy(tmp+4, ds.data, ds.len);
+ /* update position */
+ bufpos += (4 + ds.len);
+ } else {
+ log_debug("No space for option %d",
+ code);
+ }
+ data_string_forget(&ds, MDL);
+ } else {
+ log_error("Error evaluating option %d", code);
+ }
+ }
+ }
+
+ if (vsio_wanted) {
+ for (i=0; i < opt_state->universe_count; i++) {
+ if (opt_state->universes[i] != NULL) {
+ o = universes[i]->enc_opt;
+ if ((o != NULL) &&
+ (o->universe == &vsio_universe)) {
+ /*
+ * Add the data from this VSIO option.
+ */
+ vs.buf = buf;
+ vs.buflen = buflen;
+ vs.bufpos = bufpos+8;
+ option_space_foreach(packet, NULL,
+ NULL,
+ NULL, opt_state,
+ NULL,
+ universes[i],
+ (void *)&vs,
+ vsio_options);
+
+ /*
+ * If there was actually data here,
+ * add the "header".
+ */
+ if (vs.bufpos > bufpos+8) {
+ tmp = (unsigned char *)buf +
+ bufpos;
+ putUShort(tmp,
+ vsio_option_code);
+ putUShort(tmp+2,
+ vs.bufpos-bufpos-4);
+ putULong(tmp+4, o->code);
+
+ bufpos = vs.bufpos;
+ }
+ }
+ }
+ }
+ }
+
+ return bufpos;
+}
+
+/*
+ * Store all the requested options into the requested buffer.
+ * XXX: ought to be static
+ */
+int
+store_options(int *ocount,
+ unsigned char *buffer, unsigned index, unsigned buflen,
+ struct packet *packet, struct lease *lease,
+ struct client_state *client_state,
+ struct option_state *in_options,
+ struct option_state *cfg_options,
+ struct binding_scope **scope,
+ unsigned *priority_list, int priority_len,
+ unsigned first_cutoff, int second_cutoff, int terminate,
+ const char *vuname)
+{
+ int bufix = 0, six = 0, tix = 0;
+ int i;
+ int ix;
+ int tto;
+ int bufend, sbufend;
+ struct data_string od;
+ struct option_cache *oc;
+ struct option *option = NULL;
+ unsigned code;
+
+ /*
+ * These arguments are relative to the start of the buffer, so
+ * reduce them by the current buffer index, and advance the
+ * buffer pointer to where we're going to start writing.
+ */
+ buffer = &buffer[index];
+ buflen -= index;
+ if (first_cutoff)
+ first_cutoff -= index;
+ if (second_cutoff)
+ second_cutoff -= index;
+
+ /* Calculate the start and end of each section of the buffer */
+ bufend = sbufend = buflen;
+ if (first_cutoff) {
+ if (first_cutoff >= buflen)
+ log_fatal("%s:%d:store_options: Invalid first cutoff.", MDL);
+ bufend = first_cutoff;
+
+ if (second_cutoff) {
+ if (second_cutoff >= buflen)
+ log_fatal("%s:%d:store_options: Invalid second cutoff.",
+ MDL);
+ sbufend = second_cutoff;
+ }
+ } else if (second_cutoff) {
+ if (second_cutoff >= buflen)
+ log_fatal("%s:%d:store_options: Invalid second cutoff.", MDL);
+ bufend = second_cutoff;
+ }
+
+ memset (&od, 0, sizeof od);
+
+ /* Eliminate duplicate options from the parameter request list.
+ * Enforce RFC-mandated ordering of options that are present.
+ */
+ for (i = 0; i < priority_len; i++) {
+ /* Eliminate duplicates. */
+ tto = 0;
+ for (ix = i + 1; ix < priority_len + tto; ix++) {
+ if (tto)
+ priority_list [ix - tto] =
+ priority_list [ix];
+ if (priority_list [i] == priority_list [ix]) {
+ tto++;
+ priority_len--;
+ }
+ }
+
+ /* Enforce ordering of SUBNET_MASK options, according to
+ * RFC2132 Section 3.3:
+ *
+ * If both the subnet mask and the router option are
+ * specified in a DHCP reply, the subnet mask option MUST
+ * be first.
+ *
+ * This guidance does not specify what to do if the client
+ * PRL explicitly requests the options out of order, it is
+ * a general statement.
+ */
+ if (priority_list[i] == DHO_SUBNET_MASK) {
+ for (ix = i - 1 ; ix >= 0 ; ix--) {
+ if (priority_list[ix] == DHO_ROUTERS) {
+ /* swap */
+ priority_list[ix] = DHO_SUBNET_MASK;
+ priority_list[i] = DHO_ROUTERS;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Copy out the options in the order that they appear in the
+ priority list... */
+ for (i = 0; i < priority_len; i++) {
+ /* Number of bytes left to store (some may already
+ have been stored by a previous pass). */
+ unsigned length;
+ int optstart, soptstart, toptstart;
+ struct universe *u;
+ int have_encapsulation = 0;
+ struct data_string encapsulation;
+ int splitup;
+
+ memset (&encapsulation, 0, sizeof encapsulation);
+ have_encapsulation = 0;
+
+ if (option != NULL)
+ option_dereference(&option, MDL);
+
+ /* Code for next option to try to store. */
+ code = priority_list [i];
+
+ /* Look up the option in the site option space if the code
+ is above the cutoff, otherwise in the DHCP option space. */
+ if (code >= cfg_options -> site_code_min)
+ u = universes [cfg_options -> site_universe];
+ else
+ u = &dhcp_universe;
+
+ oc = lookup_option (u, cfg_options, code);
+
+ if (oc && oc->option)
+ option_reference(&option, oc->option, MDL);
+ else
+ option_code_hash_lookup(&option, u->code_hash, &code, 0, MDL);
+
+ /* If it's a straight encapsulation, and the user supplied a
+ * value for the entire option, use that. Otherwise, search
+ * the encapsulated space.
+ *
+ * If it's a limited encapsulation with preceding data, and the
+ * user supplied values for the preceding bytes, search the
+ * encapsulated space.
+ */
+ if ((option != NULL) &&
+ (((oc == NULL) && (option->format[0] == 'E')) ||
+ ((oc != NULL) && (option->format[0] == 'e')))) {
+ static char *s, *t;
+ struct option_cache *tmp;
+ struct data_string name;
+
+ s = strchr (option->format, 'E');
+ if (s)
+ t = strchr (++s, '.');
+ if (s && t) {
+ memset (&name, 0, sizeof name);
+
+ /* A zero-length universe name means the vendor
+ option space, if one is defined. */
+ if (t == s) {
+ if (vendor_cfg_option) {
+ tmp = lookup_option (vendor_cfg_option -> universe,
+ cfg_options,
+ vendor_cfg_option -> code);
+ if (tmp)
+ /* No need to check the return as we check name.len below */
+ (void) evaluate_option_cache (&name, packet, lease,
+ client_state,
+ in_options,
+ cfg_options,
+ scope, tmp, MDL);
+ } else if (vuname) {
+ name.data = (unsigned char *)s;
+ name.len = strlen (s);
+ }
+ } else {
+ name.data = (unsigned char *)s;
+ name.len = t - s;
+ }
+
+ /* If we found a universe, and there are options configured
+ for that universe, try to encapsulate it. */
+ if (name.len) {
+ have_encapsulation =
+ (option_space_encapsulate
+ (&encapsulation, packet, lease, client_state,
+ in_options, cfg_options, scope, &name));
+ data_string_forget (&name, MDL);
+ }
+ }
+ }
+
+ /* In order to avoid memory leaks, we have to get to here
+ with any option cache that we allocated in tmp not being
+ referenced by tmp, and whatever option cache is referenced
+ by oc being an actual reference. lookup_option doesn't
+ generate a reference (this needs to be fixed), so the
+ preceding goop ensures that if we *didn't* generate a new
+ option cache, oc still winds up holding an actual reference. */
+
+ /* If no data is available for this option, skip it. */
+ if (!oc && !have_encapsulation) {
+ continue;
+ }
+
+ /* Find the value of the option... */
+ od.len = 0;
+ if (oc) {
+ /* No need to check the return as we check od.len below */
+ (void) evaluate_option_cache (&od, packet,
+ lease, client_state, in_options,
+ cfg_options, scope, oc, MDL);
+
+ /* If we have encapsulation for this option, and an oc
+ * lookup succeeded, but the evaluation failed, it is
+ * either because this is a complex atom (atoms before
+ * E on format list) and the top half of the option is
+ * not configured, or this is a simple encapsulated
+ * space and the evaluator is giving us a NULL. Prefer
+ * the evaluator's opinion over the subspace.
+ */
+ if (!od.len) {
+ data_string_forget (&encapsulation, MDL);
+ data_string_forget (&od, MDL);
+ continue;
+ }
+ }
+
+ /* We should now have a constant length for the option. */
+ length = od.len;
+ if (have_encapsulation) {
+ length += encapsulation.len;
+
+ /* od.len can be nonzero if we got here without an
+ * oc (cache lookup failed), but did have an encapsulated
+ * simple encapsulation space.
+ */
+ if (!od.len) {
+ data_string_copy (&od, &encapsulation, MDL);
+ data_string_forget (&encapsulation, MDL);
+ } else {
+ struct buffer *bp = (struct buffer *)0;
+ if (!buffer_allocate (&bp, length, MDL)) {
+ option_cache_dereference (&oc, MDL);
+ data_string_forget (&od, MDL);
+ data_string_forget (&encapsulation, MDL);
+ continue;
+ }
+ memcpy (&bp -> data [0], od.data, od.len);
+ memcpy (&bp -> data [od.len], encapsulation.data,
+ encapsulation.len);
+ data_string_forget (&od, MDL);
+ data_string_forget (&encapsulation, MDL);
+ od.data = &bp -> data [0];
+ buffer_reference (&od.buffer, bp, MDL);
+ buffer_dereference (&bp, MDL);
+ od.len = length;
+ od.terminated = 0;
+ }
+ }
+
+ /* Do we add a NUL? */
+ if (terminate && option && format_has_text(option->format)) {
+ length++;
+ tto = 1;
+ } else {
+ tto = 0;
+ }
+
+ /* Try to store the option. */
+
+ /* If the option's length is more than 255, we must store it
+ in multiple hunks. Store 255-byte hunks first. However,
+ in any case, if the option data will cross a buffer
+ boundary, split it across that boundary. */
+
+ if (length > 255)
+ splitup = 1;
+ else
+ splitup = 0;
+
+ ix = 0;
+ optstart = bufix;
+ soptstart = six;
+ toptstart = tix;
+ while (length) {
+ unsigned incr = length;
+ int *pix;
+ unsigned char *base;
+
+ /* Try to fit it in the options buffer. */
+ if (!splitup &&
+ ((!six && !tix && (i == priority_len - 1) &&
+ (bufix + 2 + length < bufend)) ||
+ (bufix + 5 + length < bufend))) {
+ base = buffer;
+ pix = &bufix;
+ /* Try to fit it in the second buffer. */
+ } else if (!splitup && first_cutoff &&
+ (first_cutoff + six + 3 + length < sbufend)) {
+ base = &buffer[first_cutoff];
+ pix = &six;
+ /* Try to fit it in the third buffer. */
+ } else if (!splitup && second_cutoff &&
+ (second_cutoff + tix + 3 + length < buflen)) {
+ base = &buffer[second_cutoff];
+ pix = &tix;
+ /* Split the option up into the remaining space. */
+ } else {
+ splitup = 1;
+
+ /* Use any remaining options space. */
+ if (bufix + 6 < bufend) {
+ incr = bufend - bufix - 5;
+ base = buffer;
+ pix = &bufix;
+ /* Use any remaining first_cutoff space. */
+ } else if (first_cutoff &&
+ (first_cutoff + six + 4 < sbufend)) {
+ incr = sbufend - (first_cutoff + six) - 3;
+ base = &buffer[first_cutoff];
+ pix = &six;
+ /* Use any remaining second_cutoff space. */
+ } else if (second_cutoff &&
+ (second_cutoff + tix + 4 < buflen)) {
+ incr = buflen - (second_cutoff + tix) - 3;
+ base = &buffer[second_cutoff];
+ pix = &tix;
+ /* Give up, roll back this option. */
+ } else {
+ bufix = optstart;
+ six = soptstart;
+ tix = toptstart;
+ break;
+ }
+ }
+
+ if (incr > length)
+ incr = length;
+ if (incr > 255)
+ incr = 255;
+
+ /* Everything looks good - copy it in! */
+ base [*pix] = code;
+ base [*pix + 1] = (unsigned char)incr;
+ if (tto && incr == length) {
+ if (incr > 1)
+ memcpy (base + *pix + 2,
+ od.data + ix, (unsigned)(incr - 1));
+ base [*pix + 2 + incr - 1] = 0;
+ } else {
+ memcpy (base + *pix + 2,
+ od.data + ix, (unsigned)incr);
+ }
+ length -= incr;
+ ix += incr;
+ *pix += 2 + incr;
+ }
+ data_string_forget (&od, MDL);
+ }
+
+ if (option != NULL)
+ option_dereference(&option, MDL);
+
+ /* If we can overload, and we have, then PAD and END those spaces. */
+ if (first_cutoff && six) {
+ if ((first_cutoff + six + 1) < sbufend)
+ memset (&buffer[first_cutoff + six + 1], DHO_PAD,
+ sbufend - (first_cutoff + six + 1));
+ else if (first_cutoff + six >= sbufend)
+ log_fatal("Second buffer overflow in overloaded options.");
+
+ buffer[first_cutoff + six] = DHO_END;
+ if (ocount != NULL)
+ *ocount |= 1; /* So that caller knows there's data there. */
+ }
+
+ if (second_cutoff && tix) {
+ if (second_cutoff + tix + 1 < buflen) {
+ memset (&buffer[second_cutoff + tix + 1], DHO_PAD,
+ buflen - (second_cutoff + tix + 1));
+ } else if (second_cutoff + tix >= buflen)
+ log_fatal("Third buffer overflow in overloaded options.");
+
+ buffer[second_cutoff + tix] = DHO_END;
+ if (ocount != NULL)
+ *ocount |= 2; /* So that caller knows there's data there. */
+ }
+
+ if ((six || tix) && (bufix + 3 > bufend))
+ log_fatal("Not enough space for option overload option.");
+
+ return bufix;
+}
+
+/* Return true if the format string has a variable length text option
+ * ("t"), return false otherwise.
+ */
+
+int
+format_has_text(format)
+ const char *format;
+{
+ const char *p;
+
+ p = format;
+ while (*p != '\0') {
+ switch (*p++) {
+ case 'd':
+ case 't':
+ return 1;
+
+ /* These symbols are arbitrary, not fixed or
+ * determinable length...text options with them is
+ * invalid (whatever the case, they are never NULL
+ * terminated).
+ */
+ case 'A':
+ case 'a':
+ case 'X':
+ case 'x':
+ case 'D':
+ return 0;
+
+ case 'c':
+ /* 'c' only follows 'D' atoms, and indicates that
+ * compression may be used. If there was a 'D'
+ * atom already, we would have returned. So this
+ * is an error, but continue looking for 't' anyway.
+ */
+ log_error("format_has_text(%s): 'c' atoms are illegal "
+ "except after 'D' atoms.", format);
+ break;
+
+ /* 'E' is variable length, but not arbitrary...you
+ * can find its length if you can find an END option.
+ * N is (n)-byte in length but trails a name of a
+ * space defining the enumeration values. So treat
+ * both the same - valid, fixed-length fields.
+ */
+ case 'E':
+ case 'N':
+ /* Consume the space name. */
+ while ((*p != '\0') && (*p++ != '.'))
+ ;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/* Determine the minimum length of a DHCP option prior to any variable
+ * or inconsistent length formats, according to its configured format
+ * variable (and possibly from supplied option cache contents for variable
+ * length format symbols).
+ */
+
+int
+format_min_length(format, oc)
+ const char *format;
+ struct option_cache *oc;
+{
+ const char *p, *name;
+ int min_len = 0;
+ int last_size = 0;
+ struct enumeration *espace;
+
+ p = format;
+ while (*p != '\0') {
+ switch (*p++) {
+ case '6': /* IPv6 Address */
+ min_len += 16;
+ last_size = 16;
+ break;
+
+ case 'I': /* IPv4 Address */
+ case 'l': /* int32_t */
+ case 'L': /* uint32_t */
+ case 'T': /* Lease Time, uint32_t equivalent */
+ min_len += 4;
+ last_size = 4;
+ break;
+
+ case 's': /* int16_t */
+ case 'S': /* uint16_t */
+ min_len += 2;
+ last_size = 2;
+ break;
+
+ case 'N': /* Enumeration value. */
+ /* Consume space name. */
+ name = p;
+ p = strchr(p, '.');
+ if (p == NULL)
+ log_fatal("Corrupt format: %s", format);
+
+ espace = find_enumeration(name, p - name);
+ if (espace == NULL) {
+ log_error("Unknown enumeration: %s", format);
+ /* Max is safest value to return. */
+ return INT_MAX;
+ }
+
+ min_len += espace->width;
+ last_size = espace->width;
+ p++;
+
+ break;
+
+ case 'b': /* int8_t */
+ case 'B': /* uint8_t */
+ case 'F': /* Flag that is always true. */
+ case 'f': /* Flag */
+ min_len++;
+ last_size = 1;
+ break;
+
+ case 'o': /* Last argument is optional. */
+ min_len -= last_size;
+
+ /* XXX: It MAY be possible to sense the end of an
+ * encapsulated space, but right now this is too
+ * hard to support. Return a safe value.
+ */
+ case 'e': /* Encapsulation hint (there is an 'E' later). */
+ case 'E': /* Encapsulated options. */
+ return min_len;
+
+ case 'd': /* "Domain name" */
+ case 'D': /* "rfc1035 formatted names" */
+ case 't': /* "ASCII Text" */
+ case 'X': /* "ASCII or Hex Conditional */
+ case 'x': /* "Hex" */
+ case 'A': /* Array of all that precedes. */
+ case 'a': /* Array of preceding symbol. */
+ case 'Z': /* nothing. */
+ return min_len;
+
+ case 'c': /* Compress flag for D atom. */
+ log_error("format_min_length(%s): 'c' atom is illegal "
+ "except after 'D' atom.", format);
+ return INT_MAX;
+
+ default:
+ /* No safe value is known. */
+ log_error("format_min_length(%s): No safe value "
+ "for unknown format symbols.", format);
+ return INT_MAX;
+ }
+ }
+
+ return min_len;
+}
+
+
+/* Format the specified option so that a human can easily read it. */
+
+const char *pretty_print_option (option, data, len, emit_commas, emit_quotes)
+ struct option *option;
+ const unsigned char *data;
+ unsigned len;
+ int emit_commas;
+ int emit_quotes;
+{
+ static char optbuf [32768]; /* XXX */
+ static char *endbuf = &optbuf[sizeof(optbuf)];
+ int hunksize = 0;
+ int opthunk = 0;
+ int hunkinc = 0;
+ int numhunk = -1;
+ int numelem = 0;
+ int count;
+ int i, j, k, l;
+ char fmtbuf[32] = "";
+ struct iaddr iaddr;
+ struct enumeration *enumbuf[32]; /* MUST be same as fmtbuf */
+ char *op = optbuf;
+ const unsigned char *dp = data;
+ char comma;
+ unsigned long tval;
+ isc_boolean_t a_array = ISC_FALSE;
+ int len_used;
+
+ if (emit_commas)
+ comma = ',';
+ else
+ comma = ' ';
+
+ memset (enumbuf, 0, sizeof enumbuf);
+
+ /* Figure out the size of the data. */
+ for (l = i = 0; option -> format [i]; i++, l++) {
+ if (l >= sizeof(fmtbuf) - 1)
+ log_fatal("Bounds failure on internal buffer at "
+ "%s:%d", MDL);
+
+ if (!numhunk) {
+ log_error ("%s: Extra codes in format string: %s",
+ option -> name,
+ &(option -> format [i]));
+ break;
+ }
+ numelem++;
+ fmtbuf [l] = option -> format [i];
+ switch (option -> format [i]) {
+ case 'a':
+ a_array = ISC_TRUE;
+ /* Fall through */
+ case 'A':
+ --numelem;
+ fmtbuf [l] = 0;
+ numhunk = 0;
+ break;
+ case 'E':
+ /* Skip the universe name. */
+ while (option -> format [i] &&
+ option -> format [i] != '.')
+ i++;
+ /* Fall Through! */
+ case 'X':
+ for (k = 0; k < len; k++) {
+ if (!isascii (data [k]) ||
+ !isprint (data [k]))
+ break;
+ }
+ /* If we found no bogus characters, or the bogus
+ character we found is a trailing NUL, it's
+ okay to print this option as text. */
+ if (k == len || (k + 1 == len && data [k] == 0)) {
+ fmtbuf [l] = 't';
+ numhunk = -2;
+ } else {
+ fmtbuf [l] = 'x';
+ hunksize++;
+ comma = ':';
+ numhunk = 0;
+ a_array = ISC_TRUE;
+ hunkinc = 1;
+ }
+ fmtbuf [l + 1] = 0;
+ break;
+ case 'c':
+ /* The 'c' atom is a 'D' modifier only. */
+ log_error("'c' atom not following D atom in format "
+ "string: %s", option->format);
+ break;
+ case 'D':
+ /*
+ * Skip the 'c' atom, if present. It does not affect
+ * how we convert wire->text format (if compression is
+ * present either way, we still process it).
+ */
+ if (option->format[i+1] == 'c')
+ i++;
+ fmtbuf[l + 1] = 0;
+ numhunk = -2;
+ break;
+ case 'd':
+ fmtbuf[l] = 't';
+ /* Fall Through ! */
+ case 't':
+ fmtbuf[l + 1] = 0;
+ numhunk = -2;
+ break;
+ case 'N':
+ k = i;
+ while (option -> format [i] &&
+ option -> format [i] != '.')
+ i++;
+ enumbuf [l] =
+ find_enumeration (&option -> format [k] + 1,
+ i - k - 1);
+ if (enumbuf[l] == NULL) {
+ hunksize += 1;
+ hunkinc = 1;
+ } else {
+ hunksize += enumbuf[l]->width;
+ hunkinc = enumbuf[l]->width;
+ }
+ break;
+ case '6':
+ hunksize += 16;
+ hunkinc = 16;
+ break;
+ case 'I':
+ case 'l':
+ case 'L':
+ case 'T':
+ hunksize += 4;
+ hunkinc = 4;
+ break;
+ case 's':
+ case 'S':
+ hunksize += 2;
+ hunkinc = 2;
+ break;
+ case 'b':
+ case 'B':
+ case 'f':
+ case 'F':
+ hunksize++;
+ hunkinc = 1;
+ break;
+ case 'e':
+ case 'Z':
+ break;
+ case 'o':
+ opthunk += hunkinc;
+ break;
+ default:
+ log_error ("%s: garbage in format string: %s",
+ option -> name,
+ &(option -> format [i]));
+ break;
+ }
+ }
+
+ /* Check for too few bytes... */
+ if (hunksize - opthunk > len) {
+ log_error ("%s: expecting at least %d bytes; got %d",
+ option -> name,
+ hunksize, len);
+ return "<error>";
+ }
+ /* Check for too many bytes... */
+ if (numhunk == -1 && hunksize < len)
+ log_error ("%s: %d extra bytes",
+ option -> name,
+ len - hunksize);
+
+ /* If this is an array, compute its size. */
+ if (numhunk == 0) {
+ if (a_array == ISC_TRUE) {
+ /*
+ * It is an 'a' type array - we repeat the
+ * last format type. A binary string for 'X'
+ * is also like this. hunkinc is the size
+ * of the last format type and we add 1 to
+ * cover the entire first record.
+ */
+
+ /* If format string had no valid entries prior to
+ * 'a' hunkinc will be 0. Ex: "a", "oa", "aA" */
+ if (hunkinc == 0) {
+ log_error ("%s: invalid 'a' format: %s",
+ option->name, option->format);
+ return ("<error>");
+ }
+
+ numhunk = ((len - hunksize) / hunkinc) + 1;
+ len_used = hunksize + ((numhunk - 1) * hunkinc);
+ } else {
+ /*
+ * It is an 'A' type array - we repeat the
+ * entire record
+ */
+
+ /* If format string had no valid entries prior to
+ * 'A' hunksize will be 0. Ex: "A", "oA", "foA" */
+ if (hunksize == 0) {
+ log_error ("%s: invalid 'A' format: %s",
+ option->name, option->format);
+ return ("<error>");
+ }
+
+ numhunk = len / hunksize;
+ len_used = numhunk * hunksize;
+ }
+
+ /* See if we got an exact number of hunks. */
+ if (len_used < len) {
+ log_error ("%s: %d extra bytes at end of array\n",
+ option -> name,
+ len - len_used);
+ }
+ }
+
+
+ /* A one-hunk array prints the same as a single hunk. */
+ if (numhunk < 0)
+ numhunk = 1;
+
+ /* Cycle through the array (or hunk) printing the data. */
+ for (i = 0; i < numhunk; i++) {
+ if ((a_array == ISC_TRUE) && (i != 0) && (numelem > 0)) {
+ /*
+ * For 'a' type of arrays we repeat
+ * only the last format character
+ * We should never hit the case of numelem == 0
+ * but let's include the check to be safe.
+ */
+ j = numelem - 1;
+ } else {
+ /*
+ * for other types of arrays or the first
+ * time through for 'a' types, we go through
+ * the entire set of format characters.
+ */
+ j = 0;
+ }
+
+ for (; j < numelem; j++) {
+ switch (fmtbuf [j]) {
+ case 't':
+ /* endbuf-1 leaves room for NULL. */
+ k = pretty_text(&op, endbuf - 1, &dp,
+ data + len, emit_quotes);
+ if (k == -1) {
+ log_error("Error printing text.");
+ break;
+ }
+ *op = 0;
+ break;
+ case 'D': /* RFC1035 format name list */
+ for( ; dp < (data + len) ; dp += k) {
+ unsigned char nbuff[NS_MAXCDNAME];
+ const unsigned char *nbp, *nend;
+
+ nend = &nbuff[sizeof(nbuff)];
+
+ /* If this is for ISC DHCP consumption
+ * (emit_quotes), lay it out as a list
+ * of STRING tokens. Otherwise, it is
+ * a space-separated list of DNS-
+ * escaped names as /etc/resolv.conf
+ * might digest.
+ */
+ if (dp != data) {
+ if (op + 2 > endbuf)
+ break;
+
+ if (emit_quotes)
+ *op++ = ',';
+ *op++ = ' ';
+ }
+
+ /* XXX: if fmtbuf[j+1] != 'c', we
+ * should warn if the data was
+ * compressed anyway.
+ */
+ k = MRns_name_unpack(data,
+ data + len,
+ dp, nbuff,
+ sizeof(nbuff));
+
+ if (k == -1) {
+ log_error("Invalid domain "
+ "list.");
+ break;
+ }
+
+ /* If emit_quotes, then use ISC DHCP
+ * escapes. Otherwise, rely only on
+ * ns_name_ntop().
+ */
+ if (emit_quotes) {
+ nbp = nbuff;
+ pretty_domain(&op, endbuf-1,
+ &nbp, nend);
+ } else {
+ /* ns_name_ntop() includes
+ * a trailing NUL in its
+ * count.
+ */
+ count = MRns_name_ntop(
+ nbuff, op,
+ (endbuf-op)-1);
+
+ if (count <= 0) {
+ log_error("Invalid "
+ "domain name.");
+ break;
+ }
+
+ /* Consume all but the trailing
+ * NUL.
+ */
+ op += count - 1;
+
+ /* Replace the trailing NUL
+ * with the implicit root
+ * (in the unlikely event the
+ * domain name /is/ the root).
+ */
+ *op++ = '.';
+ }
+ }
+ *op = '\0';
+ break;
+ /* pretty-printing an array of enums is
+ going to get ugly. */
+ case 'N':
+ if (!enumbuf [j]) {
+ tval = *dp++;
+ goto enum_as_num;
+ }
+
+ switch (enumbuf[j]->width) {
+ case 1:
+ tval = getUChar(dp);
+ break;
+
+ case 2:
+ tval = getUShort(dp);
+ break;
+
+ case 4:
+ tval = getULong(dp);
+ break;
+
+ default:
+ log_fatal("Impossible case at %s:%d.",
+ MDL);
+ return "<double impossible condition>";
+ }
+
+ for (i = 0; ;i++) {
+ if (!enumbuf [j] -> values [i].name)
+ goto enum_as_num;
+ if (enumbuf [j] -> values [i].value ==
+ tval)
+ break;
+ }
+ strcpy (op, enumbuf [j] -> values [i].name);
+ dp += enumbuf[j]->width;
+ break;
+
+ enum_as_num:
+ sprintf(op, "%lu", tval);
+ break;
+
+ case 'I':
+ iaddr.len = 4;
+ memcpy(iaddr.iabuf, dp, 4);
+ strcpy(op, piaddr(iaddr));
+ dp += 4;
+ break;
+ case '6':
+ iaddr.len = 16;
+ memcpy(iaddr.iabuf, dp, 16);
+ strcpy(op, piaddr(iaddr));
+ dp += 16;
+ break;
+ case 'l':
+ sprintf (op, "%ld", (long)getLong (dp));
+ dp += 4;
+ break;
+ case 'T':
+ tval = getULong (dp);
+ if (tval == -1)
+ sprintf (op, "%s", "infinite");
+ else
+ sprintf(op, "%lu", tval);
+ break;
+ case 'L':
+ sprintf(op, "%lu",
+ (unsigned long)getULong(dp));
+ dp += 4;
+ break;
+ case 's':
+ sprintf (op, "%d", (int)getShort (dp));
+ dp += 2;
+ break;
+ case 'S':
+ sprintf(op, "%u", (unsigned)getUShort(dp));
+ dp += 2;
+ break;
+ case 'b':
+ sprintf (op, "%d", *(const char *)dp++);
+ break;
+ case 'B':
+ sprintf (op, "%d", *dp++);
+ break;
+ case 'X':
+ case 'x':
+ sprintf (op, "%x", *dp++);
+ break;
+ case 'f':
+ strcpy (op, *dp++ ? "true" : "false");
+ break;
+ case 'F':
+ strcpy (op, "true");
+ break;
+ case 'e':
+ case 'Z':
+ *op = '\0';
+ break;
+ default:
+ log_error ("Unexpected format code %c",
+ fmtbuf [j]);
+ }
+ op += strlen (op);
+ if (dp == data + len)
+ break;
+ if (j + 1 < numelem && comma != ':')
+ *op++ = ' ';
+ }
+ if (i + 1 < numhunk) {
+ *op++ = comma;
+ }
+ if (dp == data + len)
+ break;
+ }
+ return optbuf;
+}
+
+int get_option (result, universe, packet, lease, client_state,
+ in_options, cfg_options, options, scope, code, file, line)
+ struct data_string *result;
+ struct universe *universe;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *cfg_options;
+ struct option_state *options;
+ struct binding_scope **scope;
+ unsigned code;
+ const char *file;
+ int line;
+{
+ struct option_cache *oc;
+
+ if (!universe -> lookup_func)
+ return 0;
+ oc = ((*universe -> lookup_func) (universe, options, code));
+ if (!oc)
+ return 0;
+ if (!evaluate_option_cache (result, packet, lease, client_state,
+ in_options, cfg_options, scope, oc,
+ file, line))
+ return 0;
+ return 1;
+}
+
+void set_option (universe, options, option, op)
+ struct universe *universe;
+ struct option_state *options;
+ struct option_cache *option;
+ enum statement_op op;
+{
+ struct option_cache *oc, *noc;
+
+ switch (op) {
+ case if_statement:
+ case add_statement:
+ case eval_statement:
+ case break_statement:
+ default:
+ log_error ("bogus statement type in set_option.");
+ break;
+
+ case default_option_statement:
+ oc = lookup_option (universe, options,
+ option -> option -> code);
+ if (oc)
+ break;
+ save_option (universe, options, option);
+ break;
+
+ case supersede_option_statement:
+ case send_option_statement:
+ /* Install the option, replacing any existing version. */
+ save_option (universe, options, option);
+ break;
+
+ case append_option_statement:
+ case prepend_option_statement:
+ oc = lookup_option (universe, options,
+ option -> option -> code);
+ if (!oc) {
+ save_option (universe, options, option);
+ break;
+ }
+ /* If it's not an expression, make it into one. */
+ if (!oc -> expression && oc -> data.len) {
+ if (!expression_allocate (&oc -> expression, MDL)) {
+ log_error ("Can't allocate const expression.");
+ break;
+ }
+ oc -> expression -> op = expr_const_data;
+ data_string_copy
+ (&oc -> expression -> data.const_data,
+ &oc -> data, MDL);
+ data_string_forget (&oc -> data, MDL);
+ }
+ noc = (struct option_cache *)0;
+ if (!option_cache_allocate (&noc, MDL))
+ break;
+ if (op == append_option_statement) {
+ if (!make_concat (&noc -> expression,
+ oc -> expression,
+ option -> expression)) {
+ option_cache_dereference (&noc, MDL);
+ break;
+ }
+ } else {
+ if (!make_concat (&noc -> expression,
+ option -> expression,
+ oc -> expression)) {
+ option_cache_dereference (&noc, MDL);
+ break;
+ }
+ }
+
+ /* If we are trying to combine compressed domain-lists then
+ * we need to change the expression opcode. The lists must
+ * be decompressed, combined, and then recompressed to work
+ * correctly. You cannot simply add two compressed lists
+ * together. */
+ switch (((memcmp(option->option->format, "Dc", 2) == 0) +
+ (memcmp(oc->option->format, "Dc", 2) == 0))) {
+ case 1:
+ /* Only one is "Dc", this won't work
+ * Not sure if you can make this occur, but just
+ * in case. */
+ log_error ("Both options must be Dc format");
+ option_cache_dereference (&noc, MDL);
+ return;
+ case 2:
+ /* Both are "Dc", change the code */
+ noc->expression->op = expr_concat_dclist;
+ break;
+ default:
+ /* Neither are "Dc", so as you were */
+ break;
+ }
+
+ option_reference(&(noc->option), oc->option, MDL);
+ save_option (universe, options, noc);
+ option_cache_dereference (&noc, MDL);
+ break;
+ }
+}
+
+struct option_cache *lookup_option (universe, options, code)
+ struct universe *universe;
+ struct option_state *options;
+ unsigned code;
+{
+ if (!options)
+ return (struct option_cache *)0;
+ if (universe -> lookup_func)
+ return (*universe -> lookup_func) (universe, options, code);
+ else
+ log_error ("can't look up options in %s space.",
+ universe -> name);
+ return (struct option_cache *)0;
+}
+
+struct option_cache *lookup_hashed_option (universe, options, code)
+ struct universe *universe;
+ struct option_state *options;
+ unsigned code;
+{
+ int hashix;
+ pair bptr;
+ pair *hash;
+
+ /* Make sure there's a hash table. */
+ if (universe -> index >= options -> universe_count ||
+ !(options -> universes [universe -> index]))
+ return (struct option_cache *)0;
+
+ hash = options -> universes [universe -> index];
+
+ hashix = compute_option_hash (code);
+ for (bptr = hash [hashix]; bptr; bptr = bptr -> cdr) {
+ if (((struct option_cache *)(bptr -> car)) -> option -> code ==
+ code)
+ return (struct option_cache *)(bptr -> car);
+ }
+ return (struct option_cache *)0;
+}
+
+/* Save a specified buffer into an option cache. */
+int
+save_option_buffer(struct universe *universe, struct option_state *options,
+ struct buffer *bp, unsigned char *buffer, unsigned length,
+ unsigned code, int terminatep)
+{
+ struct option_cache *op = NULL;
+ int status = 1;
+
+ status = prepare_option_buffer(universe, bp, buffer, length, code,
+ terminatep, &op);
+
+ if (status == 0)
+ goto cleanup;
+
+ save_option(universe, options, op);
+
+ cleanup:
+ if (op != NULL)
+ option_cache_dereference(&op, MDL);
+
+ return status;
+}
+
+/* Append a specified buffer onto the tail of an option cache. */
+int
+append_option_buffer(struct universe *universe, struct option_state *options,
+ struct buffer *bp, unsigned char *buffer, unsigned length,
+ unsigned code, int terminatep)
+{
+ struct option_cache *op = NULL;
+ int status = 1;
+
+ status = prepare_option_buffer(universe, bp, buffer, length, code,
+ terminatep, &op);
+
+ if (status == 0)
+ goto cleanup;
+
+ also_save_option(universe, options, op);
+
+ cleanup:
+ if (op != NULL)
+ option_cache_dereference(&op, MDL);
+
+ return status;
+}
+
+/* Create/copy a buffer into a new option cache. */
+static int
+prepare_option_buffer(struct universe *universe, struct buffer *bp,
+ unsigned char *buffer, unsigned length, unsigned code,
+ int terminatep, struct option_cache **opp)
+{
+ struct buffer *lbp = NULL;
+ struct option *option = NULL;
+ struct option_cache *op;
+ int status = 1;
+
+ /* Code sizes of 8, 16, and 32 bits are allowed. */
+ switch(universe->tag_size) {
+ case 1:
+ if (code > 0xff)
+ return 0;
+ break;
+ case 2:
+ if (code > 0xffff)
+ return 0;
+ break;
+ case 4:
+ if (code > 0xffffffff)
+ return 0;
+ break;
+
+ default:
+ log_fatal("Inconsistent universe tag size at %s:%d.", MDL);
+ }
+
+ option_code_hash_lookup(&option, universe->code_hash, &code, 0, MDL);
+
+ /* If we created an option structure for each option a client
+ * supplied, it's possible we may create > 2^32 option structures.
+ * That's not feasible. So by failing to enter these option
+ * structures into the code and name hash tables, references will
+ * never be more than 1 - when the option cache is destroyed, this
+ * will be cleaned up.
+ */
+ if (!option) {
+ char nbuf[sizeof("unknown-4294967295")];
+
+ sprintf(nbuf, "unknown-%u", code);
+
+ option = new_option(nbuf, MDL);
+
+ if (!option)
+ return 0;
+
+ option->format = default_option_format;
+ option->universe = universe;
+ option->code = code;
+
+ /* new_option() doesn't set references, pretend. */
+ option->refcnt = 1;
+ }
+
+ if (!option_cache_allocate (opp, MDL)) {
+ log_error("No memory for option code %s.%s.",
+ universe->name, option->name);
+ status = 0;
+ goto cleanup;
+ }
+
+ /* Pointer rather than double pointer makes for less parens. */
+ op = *opp;
+
+ option_reference(&op->option, option, MDL);
+
+ /* If we weren't passed a buffer in which the data are saved and
+ refcounted, allocate one now. */
+ if (!bp) {
+ if (!buffer_allocate (&lbp, length + terminatep, MDL)) {
+ log_error ("no memory for option buffer.");
+
+ status = 0;
+ goto cleanup;
+ }
+ memcpy (lbp -> data, buffer, length + terminatep);
+ bp = lbp;
+ buffer = &bp -> data [0]; /* Refer to saved buffer. */
+ }
+
+ /* Reference buffer copy to option cache. */
+ op -> data.buffer = (struct buffer *)0;
+ buffer_reference (&op -> data.buffer, bp, MDL);
+
+ /* Point option cache into buffer. */
+ op -> data.data = buffer;
+ op -> data.len = length;
+
+ if (terminatep) {
+ /* NUL terminate (we can get away with this because we (or
+ the caller!) allocated one more than the buffer size, and
+ because the byte following the end of an option is always
+ the code of the next option, which the caller is getting
+ out of the *original* buffer. */
+ buffer [length] = 0;
+ op -> data.terminated = 1;
+ } else
+ op -> data.terminated = 0;
+
+ /* If this option is ultimately a text option, null determinate to
+ * comply with RFC2132 section 2. Mark a flag so this can be sensed
+ * later to echo NULLs back to clients that supplied them (they
+ * probably expect them).
+ */
+ if (format_has_text(option->format)) {
+ int min_len = format_min_length(option->format, op);
+
+ while ((op->data.len > min_len) &&
+ (op->data.data[op->data.len-1] == '\0')) {
+ op->data.len--;
+ op->flags |= OPTION_HAD_NULLS;
+ }
+ }
+
+ /* And let go of our references. */
+ cleanup:
+ if (lbp != NULL)
+ buffer_dereference(&lbp, MDL);
+ option_dereference(&option, MDL);
+
+ return status;
+}
+
+static void
+count_options(struct option_cache *dummy_oc,
+ struct packet *dummy_packet,
+ struct lease *dummy_lease,
+ struct client_state *dummy_client_state,
+ struct option_state *dummy_opt_state,
+ struct option_state *opt_state,
+ struct binding_scope **dummy_binding_scope,
+ struct universe *dummy_universe,
+ void *void_accumulator) {
+ int *accumulator = (int *)void_accumulator;
+
+ *accumulator += 1;
+}
+
+static void
+collect_oro(struct option_cache *oc,
+ struct packet *dummy_packet,
+ struct lease *dummy_lease,
+ struct client_state *dummy_client_state,
+ struct option_state *dummy_opt_state,
+ struct option_state *opt_state,
+ struct binding_scope **dummy_binding_scope,
+ struct universe *dummy_universe,
+ void *void_oro) {
+ struct data_string *oro = (struct data_string *)void_oro;
+
+ putUShort(oro->buffer->data + oro->len, oc->option->code);
+ oro->len += 2;
+}
+
+/* build_server_oro() is presently unusued, but may be used at a future date
+ * with support for Reconfigure messages (as a hint to the client about new
+ * option value contents).
+ */
+void
+build_server_oro(struct data_string *server_oro,
+ struct option_state *options,
+ const char *file, int line) {
+ int num_opts;
+ int i;
+ struct option *o;
+
+ /*
+ * Count the number of options, so we can allocate enough memory.
+ * We want to mention sub-options too, so check all universes.
+ */
+ num_opts = 0;
+ option_space_foreach(NULL, NULL, NULL, NULL, options,
+ NULL, &dhcpv6_universe, (void *)&num_opts,
+ count_options);
+ for (i=0; i < options->universe_count; i++) {
+ if (options->universes[i] != NULL) {
+ o = universes[i]->enc_opt;
+ while (o != NULL) {
+ if (o->universe == &dhcpv6_universe) {
+ num_opts++;
+ break;
+ }
+ o = o->universe->enc_opt;
+ }
+ }
+ }
+
+ /*
+ * Allocate space.
+ */
+ memset(server_oro, 0, sizeof(*server_oro));
+ if (!buffer_allocate(&server_oro->buffer, num_opts * 2, MDL)) {
+ log_fatal("no memory to build server ORO");
+ }
+ server_oro->data = server_oro->buffer->data;
+
+ /*
+ * Copy the data in.
+ * We want to mention sub-options too, so check all universes.
+ */
+ server_oro->len = 0; /* gets set in collect_oro */
+ option_space_foreach(NULL, NULL, NULL, NULL, options,
+ NULL, &dhcpv6_universe, (void *)server_oro,
+ collect_oro);
+ for (i=0; i < options->universe_count; i++) {
+ if (options->universes[i] != NULL) {
+ o = universes[i]->enc_opt;
+ while (o != NULL) {
+ if (o->universe == &dhcpv6_universe) {
+ unsigned char *tmp;
+ tmp = server_oro->buffer->data;
+ putUShort(tmp + server_oro->len,
+ o->code);
+ server_oro->len += 2;
+ break;
+ }
+ o = o->universe->enc_opt;
+ }
+ }
+ }
+}
+
+/* Wrapper function to put an option cache into an option state. */
+void
+save_option(struct universe *universe, struct option_state *options,
+ struct option_cache *oc)
+{
+ if (universe->save_func)
+ (*universe->save_func)(universe, options, oc, ISC_FALSE);
+ else
+ log_error("can't store options in %s space.", universe->name);
+}
+
+/* Wrapper function to append an option cache into an option state's list. */
+void
+also_save_option(struct universe *universe, struct option_state *options,
+ struct option_cache *oc)
+{
+ if (universe->save_func)
+ (*universe->save_func)(universe, options, oc, ISC_TRUE);
+ else
+ log_error("can't store options in %s space.", universe->name);
+}
+
+void
+save_hashed_option(struct universe *universe, struct option_state *options,
+ struct option_cache *oc, isc_boolean_t appendp)
+{
+ int hashix;
+ pair bptr;
+ pair *hash = options -> universes [universe -> index];
+ struct option_cache **ocloc;
+
+ if (oc -> refcnt == 0)
+ abort ();
+
+ /* Compute the hash. */
+ hashix = compute_option_hash (oc -> option -> code);
+
+ /* If there's no hash table, make one. */
+ if (!hash) {
+ hash = (pair *)dmalloc (OPTION_HASH_SIZE * sizeof *hash, MDL);
+ if (!hash) {
+ log_error ("no memory to store %s.%s",
+ universe -> name, oc -> option -> name);
+ return;
+ }
+ memset (hash, 0, OPTION_HASH_SIZE * sizeof *hash);
+ options -> universes [universe -> index] = (void *)hash;
+ } else {
+ /* Try to find an existing option matching the new one. */
+ for (bptr = hash [hashix]; bptr; bptr = bptr -> cdr) {
+ if (((struct option_cache *)
+ (bptr -> car)) -> option -> code ==
+ oc -> option -> code)
+ break;
+ }
+
+ /* Deal with collisions on the hash list. */
+ if (bptr) {
+ ocloc = (struct option_cache **)&bptr->car;
+
+ /*
+ * If appendp is set, append it onto the tail of the
+ * ->next list. If it is not set, rotate it into
+ * position at the head of the list.
+ */
+ if (appendp) {
+ do {
+ ocloc = &(*ocloc)->next;
+ } while (*ocloc != NULL);
+ } else {
+ option_cache_dereference(ocloc, MDL);
+ }
+
+ option_cache_reference(ocloc, oc, MDL);
+ return;
+ }
+ }
+
+ /* Otherwise, just put the new one at the head of the list. */
+ bptr = new_pair (MDL);
+ if (!bptr) {
+ log_error ("No memory for option_cache reference.");
+ return;
+ }
+ bptr -> cdr = hash [hashix];
+ bptr -> car = 0;
+ option_cache_reference ((struct option_cache **)&bptr -> car, oc, MDL);
+ hash [hashix] = bptr;
+}
+
+void delete_option (universe, options, code)
+ struct universe *universe;
+ struct option_state *options;
+ int code;
+{
+ if (universe -> delete_func)
+ (*universe -> delete_func) (universe, options, code);
+ else
+ log_error ("can't delete options from %s space.",
+ universe -> name);
+}
+
+void delete_hashed_option (universe, options, code)
+ struct universe *universe;
+ struct option_state *options;
+ int code;
+{
+ int hashix;
+ pair bptr, prev = (pair)0;
+ pair *hash = options -> universes [universe -> index];
+
+ /* There may not be any options in this space. */
+ if (!hash)
+ return;
+
+ /* Try to find an existing option matching the new one. */
+ hashix = compute_option_hash (code);
+ for (bptr = hash [hashix]; bptr; bptr = bptr -> cdr) {
+ if (((struct option_cache *)(bptr -> car)) -> option -> code
+ == code)
+ break;
+ prev = bptr;
+ }
+ /* If we found one, wipe it out... */
+ if (bptr) {
+ if (prev)
+ prev -> cdr = bptr -> cdr;
+ else
+ hash [hashix] = bptr -> cdr;
+ option_cache_dereference
+ ((struct option_cache **)(&bptr -> car), MDL);
+ free_pair (bptr, MDL);
+ }
+}
+
+extern struct option_cache *free_option_caches; /* XXX */
+
+int option_cache_dereference (ptr, file, line)
+ struct option_cache **ptr;
+ const char *file;
+ int line;
+{
+ if (!ptr || !*ptr) {
+ log_error ("Null pointer in option_cache_dereference: %s(%d)",
+ file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ (*ptr) -> refcnt--;
+ rc_register (file, line, ptr, *ptr, (*ptr) -> refcnt, 1, RC_MISC);
+ if (!(*ptr) -> refcnt) {
+ if ((*ptr) -> data.buffer)
+ data_string_forget (&(*ptr) -> data, file, line);
+ if ((*ptr)->option)
+ option_dereference(&(*ptr)->option, MDL);
+ if ((*ptr) -> expression)
+ expression_dereference (&(*ptr) -> expression,
+ file, line);
+ if ((*ptr) -> next)
+ option_cache_dereference (&((*ptr) -> next),
+ file, line);
+ /* Put it back on the free list... */
+ (*ptr) -> expression = (struct expression *)free_option_caches;
+ free_option_caches = *ptr;
+ dmalloc_reuse (free_option_caches, (char *)0, 0, 0);
+ }
+ if ((*ptr) -> refcnt < 0) {
+ log_error ("%s(%d): negative refcnt!", file, line);
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history (*ptr);
+#endif
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ *ptr = (struct option_cache *)0;
+ return 0;
+#endif
+ }
+ *ptr = (struct option_cache *)0;
+ return 1;
+
+}
+
+int hashed_option_state_dereference (universe, state, file, line)
+ struct universe *universe;
+ struct option_state *state;
+ const char *file;
+ int line;
+{
+ pair *heads;
+ pair cp, next;
+ int i;
+
+ /* Get the pointer to the array of hash table bucket heads. */
+ heads = (pair *)(state -> universes [universe -> index]);
+ if (!heads)
+ return 0;
+
+ /* For each non-null head, loop through all the buckets dereferencing
+ the attached option cache structures and freeing the buckets. */
+ for (i = 0; i < OPTION_HASH_SIZE; i++) {
+ for (cp = heads [i]; cp; cp = next) {
+ next = cp -> cdr;
+ option_cache_dereference
+ ((struct option_cache **)&cp -> car,
+ file, line);
+ free_pair (cp, file, line);
+ }
+ }
+
+ dfree (heads, file, line);
+ state -> universes [universe -> index] = (void *)0;
+ return 1;
+}
+
+/* The 'data_string' primitive doesn't have an appension mechanism.
+ * This function must then append a new option onto an existing buffer
+ * by first duplicating the original buffer and appending the desired
+ * values, followed by coping the new value into place.
+ */
+int
+append_option(struct data_string *dst, struct universe *universe,
+ struct option *option, struct data_string *src)
+{
+ struct data_string tmp;
+
+ if (src->len == 0 && option->format[0] != 'Z')
+ return 0;
+
+ memset(&tmp, 0, sizeof(tmp));
+
+ /* Allocate a buffer to hold existing data, the current option's
+ * tag and length, and the option's content.
+ */
+ if (!buffer_allocate(&tmp.buffer,
+ (dst->len + universe->length_size +
+ universe->tag_size + src->len), MDL)) {
+ /* XXX: This kills all options presently stored in the
+ * destination buffer. This is the way the original code
+ * worked, and assumes an 'all or nothing' approach to
+ * eg encapsulated option spaces. It may or may not be
+ * desirable.
+ */
+ data_string_forget(dst, MDL);
+ return 0;
+ }
+ tmp.data = tmp.buffer->data;
+
+ /* Copy the existing data off the destination. */
+ if (dst->len != 0)
+ memcpy(tmp.buffer->data, dst->data, dst->len);
+ tmp.len = dst->len;
+
+ /* Place the new option tag and length. */
+ (*universe->store_tag)(tmp.buffer->data + tmp.len, option->code);
+ tmp.len += universe->tag_size;
+ (*universe->store_length)(tmp.buffer->data + tmp.len, src->len);
+ tmp.len += universe->length_size;
+
+ /* Copy the option contents onto the end. */
+ memcpy(tmp.buffer->data + tmp.len, src->data, src->len);
+ tmp.len += src->len;
+
+ /* Play the shell game. */
+ data_string_forget(dst, MDL);
+ data_string_copy(dst, &tmp, MDL);
+ data_string_forget(&tmp, MDL);
+ return 1;
+}
+
+int
+store_option(struct data_string *result, struct universe *universe,
+ struct packet *packet, struct lease *lease,
+ struct client_state *client_state,
+ struct option_state *in_options, struct option_state *cfg_options,
+ struct binding_scope **scope, struct option_cache *oc)
+{
+ struct data_string tmp;
+ struct universe *subu=NULL;
+ int status;
+ char *start, *end;
+
+ memset(&tmp, 0, sizeof(tmp));
+
+ if (evaluate_option_cache(&tmp, packet, lease, client_state,
+ in_options, cfg_options, scope, oc, MDL)) {
+ /* If the option is an extended 'e'ncapsulation (not a
+ * direct 'E'ncapsulation), append the encapsulated space
+ * onto the currently prepared value.
+ */
+ do {
+ if (oc->option->format &&
+ oc->option->format[0] == 'e') {
+ /* Skip forward to the universe name. */
+ start = strchr(oc->option->format, 'E');
+ if (start == NULL)
+ break;
+
+ /* Locate the name-terminating '.'. */
+ end = strchr(++start, '.');
+
+ /* A zero-length name is not allowed in
+ * these kinds of encapsulations.
+ */
+ if (end == NULL || start == end)
+ break;
+
+ universe_hash_lookup(&subu, universe_hash,
+ start, end - start, MDL);
+
+ if (subu == NULL) {
+ log_error("store_option: option %d "
+ "refers to unknown "
+ "option space '%.*s'.",
+ oc->option->code,
+ (int)(end - start), start);
+ break;
+ }
+
+ /* Append encapsulations, if any. We
+ * already have the prepended values, so
+ * we send those even if there are no
+ * encapsulated options (and ->encapsulate()
+ * returns zero).
+ */
+ subu->encapsulate(&tmp, packet, lease,
+ client_state, in_options,
+ cfg_options, scope, subu);
+ subu = NULL;
+ }
+ } while (ISC_FALSE);
+
+ status = append_option(result, universe, oc->option, &tmp);
+ data_string_forget(&tmp, MDL);
+
+ return status;
+ }
+
+ return 0;
+}
+
+int option_space_encapsulate (result, packet, lease, client_state,
+ in_options, cfg_options, scope, name)
+ struct data_string *result;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *cfg_options;
+ struct binding_scope **scope;
+ struct data_string *name;
+{
+ struct universe *u = NULL;
+ int status = 0;
+
+ universe_hash_lookup(&u, universe_hash,
+ (const char *)name->data, name->len, MDL);
+ if (u == NULL) {
+ log_error("option_space_encapsulate: option space '%.*s' does "
+ "not exist, but is configured.",
+ (int)name->len, name->data);
+ return status;
+ }
+
+ if (u->encapsulate != NULL) {
+ if (u->encapsulate(result, packet, lease, client_state,
+ in_options, cfg_options, scope, u))
+ status = 1;
+ } else
+ log_error("encapsulation requested for '%s' with no support.",
+ name->data);
+
+ return status;
+}
+
+/* Attempt to store any 'E'ncapsulated options that have not yet been
+ * placed on the option buffer by the above (configuring a value in
+ * the space over-rides any values in the child universe).
+ *
+ * Note that there are far fewer universes than there will ever be
+ * options in any universe. So it is faster to traverse the
+ * configured universes, checking if each is encapsulated in the
+ * current universe, and if so attempting to do so.
+ *
+ * For each configured universe for this configuration option space,
+ * which is encapsulated within the current universe, can not be found
+ * by the lookup function (the universe-specific encapsulation
+ * functions would already have stored such a value), and encapsulates
+ * at least one option, append it.
+ */
+static int
+search_subencapsulation(struct data_string *result, struct packet *packet,
+ struct lease *lease, struct client_state *client_state,
+ struct option_state *in_options,
+ struct option_state *cfg_options,
+ struct binding_scope **scope,
+ struct universe *universe)
+{
+ struct data_string sub;
+ struct universe *subu;
+ int i, status = 0;
+
+ memset(&sub, 0, sizeof(sub));
+ for (i = 0 ; i < cfg_options->universe_count ; i++) {
+ subu = universes[i];
+
+ if (subu == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ if (subu->enc_opt != NULL &&
+ subu->enc_opt->universe == universe &&
+ subu->enc_opt->format != NULL &&
+ subu->enc_opt->format[0] == 'E' &&
+ lookup_option(universe, cfg_options,
+ subu->enc_opt->code) == NULL &&
+ subu->encapsulate(&sub, packet, lease, client_state,
+ in_options, cfg_options,
+ scope, subu)) {
+ if (append_option(result, universe,
+ subu->enc_opt, &sub))
+ status = 1;
+
+ data_string_forget(&sub, MDL);
+ }
+ }
+
+ return status;
+}
+
+int hashed_option_space_encapsulate (result, packet, lease, client_state,
+ in_options, cfg_options, scope, universe)
+ struct data_string *result;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *cfg_options;
+ struct binding_scope **scope;
+ struct universe *universe;
+{
+ pair p, *hash;
+ int status;
+ int i;
+
+ if (universe -> index >= cfg_options -> universe_count)
+ return 0;
+
+ hash = cfg_options -> universes [universe -> index];
+ if (!hash)
+ return 0;
+
+ /* For each hash bucket, and each configured option cache within
+ * that bucket, append the option onto the buffer in encapsulated
+ * format appropriate to the universe.
+ */
+ status = 0;
+ for (i = 0; i < OPTION_HASH_SIZE; i++) {
+ for (p = hash [i]; p; p = p -> cdr) {
+ if (store_option(result, universe, packet, lease,
+ client_state, in_options, cfg_options,
+ scope, (struct option_cache *)p->car))
+ status = 1;
+ }
+ }
+
+ if (search_subencapsulation(result, packet, lease, client_state,
+ in_options, cfg_options, scope, universe))
+ status = 1;
+
+ return status;
+}
+
+int nwip_option_space_encapsulate (result, packet, lease, client_state,
+ in_options, cfg_options, scope, universe)
+ struct data_string *result;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *cfg_options;
+ struct binding_scope **scope;
+ struct universe *universe;
+{
+ pair ocp;
+ int status;
+ static struct option_cache *no_nwip;
+ struct data_string ds;
+ struct option_chain_head *head;
+
+ if (universe -> index >= cfg_options -> universe_count)
+ return 0;
+ head = ((struct option_chain_head *)
+ cfg_options -> universes [nwip_universe.index]);
+ if (!head)
+ return 0;
+
+ status = 0;
+ for (ocp = head -> first; ocp; ocp = ocp -> cdr) {
+ if (store_option (result, universe, packet,
+ lease, client_state, in_options,
+ cfg_options, scope,
+ (struct option_cache *)ocp -> car))
+ status = 1;
+ }
+
+ /* If there's no data, the nwip suboption is supposed to contain
+ a suboption saying there's no data. */
+ if (!status) {
+ if (!no_nwip) {
+ unsigned one = 1;
+ static unsigned char nni [] = { 1, 0 };
+
+ memset (&ds, 0, sizeof ds);
+ ds.data = nni;
+ ds.len = 2;
+ if (option_cache_allocate (&no_nwip, MDL))
+ data_string_copy (&no_nwip -> data, &ds, MDL);
+ if (!option_code_hash_lookup(&no_nwip->option,
+ nwip_universe.code_hash,
+ &one, 0, MDL))
+ log_fatal("Nwip option hash does not contain "
+ "1 (%s:%d).", MDL);
+ }
+ if (no_nwip) {
+ if (store_option (result, universe, packet, lease,
+ client_state, in_options,
+ cfg_options, scope, no_nwip))
+ status = 1;
+ }
+ } else {
+ memset (&ds, 0, sizeof ds);
+
+ /* If we have nwip options, the first one has to be the
+ nwip-exists-in-option-area option. */
+ if (!buffer_allocate (&ds.buffer, result -> len + 2, MDL)) {
+ data_string_forget (result, MDL);
+ return 0;
+ }
+ ds.data = &ds.buffer -> data [0];
+ ds.buffer -> data [0] = 2;
+ ds.buffer -> data [1] = 0;
+ memcpy (&ds.buffer -> data [2], result -> data, result -> len);
+ data_string_forget (result, MDL);
+ data_string_copy (result, &ds, MDL);
+ data_string_forget (&ds, MDL);
+ }
+
+ return status;
+}
+
+/* We don't want to use ns_name_pton()...it doesn't tell us how many bytes
+ * it has consumed, and it plays havoc with our escapes.
+ *
+ * So this function does DNS encoding, and returns either the number of
+ * octects consumed (on success), or -1 on failure.
+ */
+static int
+fqdn_encode(unsigned char *dst, int dstlen, const unsigned char *src,
+ int srclen)
+{
+ unsigned char *out;
+ int i, j, len, outlen=0;
+
+ out = dst;
+ for (i = 0, j = 0 ; i < srclen ; i = j) {
+ while ((j < srclen) && (src[j] != '.') && (src[j] != '\0'))
+ j++;
+
+ len = j - i;
+ if ((outlen + 1 + len) > dstlen)
+ return -1;
+
+ *out++ = len;
+ outlen++;
+
+ /* We only do one FQDN, ending in one root label. */
+ if (len == 0)
+ return outlen;
+
+ memcpy(out, src + i, len);
+ out += len;
+ outlen += len;
+
+ /* Advance past the root label. */
+ j++;
+ }
+
+ if ((outlen + 1) > dstlen)
+ return -1;
+
+ /* Place the root label. */
+ *out++ = 0;
+ outlen++;
+
+ return outlen;
+}
+
+int fqdn_option_space_encapsulate (result, packet, lease, client_state,
+ in_options, cfg_options, scope, universe)
+ struct data_string *result;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *cfg_options;
+ struct binding_scope **scope;
+ struct universe *universe;
+{
+ pair ocp;
+ struct data_string results [FQDN_SUBOPTION_COUNT + 1];
+ int status = 1;
+ int i;
+ unsigned len;
+ struct buffer *bp = (struct buffer *)0;
+ struct option_chain_head *head;
+
+ /* If there's no FQDN universe, don't encapsulate. */
+ if (fqdn_universe.index >= cfg_options -> universe_count)
+ return 0;
+ head = ((struct option_chain_head *)
+ cfg_options -> universes [fqdn_universe.index]);
+ if (!head)
+ return 0;
+
+ /* Figure out the values of all the suboptions. */
+ memset (results, 0, sizeof results);
+ for (ocp = head -> first; ocp; ocp = ocp -> cdr) {
+ struct option_cache *oc = (struct option_cache *)(ocp -> car);
+ if (oc -> option -> code > FQDN_SUBOPTION_COUNT)
+ continue;
+ /* No need to check the return code, we check the length later */
+ (void) evaluate_option_cache (&results[oc->option->code],
+ packet, lease, client_state,
+ in_options, cfg_options, scope,
+ oc, MDL);
+ }
+ /* We add a byte for the flags field.
+ * We add two bytes for the two RCODE fields.
+ * We add a byte because we will prepend a label count.
+ * We add a byte because the input len doesn't count null termination,
+ * and we will add a root label.
+ */
+ len = 5 + results [FQDN_FQDN].len;
+ /* Save the contents of the option in a buffer. */
+ if (!buffer_allocate (&bp, len, MDL)) {
+ log_error ("no memory for option buffer.");
+ status = 0;
+ goto exit;
+ }
+ buffer_reference (&result -> buffer, bp, MDL);
+ result -> len = 3;
+ result -> data = &bp -> data [0];
+
+ memset (&bp -> data [0], 0, len);
+ /* XXX: The server should set bit 4 (yes, 4, not 3) to 1 if it is
+ * not going to perform any ddns updates. The client should set the
+ * bit if it doesn't want the server to perform any updates.
+ * The problem is at this layer of abstraction we have no idea if
+ * the caller is a client or server.
+ *
+ * See RFC4702, Section 3.1, 'The "N" bit'.
+ *
+ * if (?)
+ * bp->data[0] |= 8;
+ */
+ if (results [FQDN_NO_CLIENT_UPDATE].len &&
+ results [FQDN_NO_CLIENT_UPDATE].data [0])
+ bp -> data [0] |= 2;
+ if (results [FQDN_SERVER_UPDATE].len &&
+ results [FQDN_SERVER_UPDATE].data [0])
+ bp -> data [0] |= 1;
+ if (results [FQDN_RCODE1].len)
+ bp -> data [1] = results [FQDN_RCODE1].data [0];
+ if (results [FQDN_RCODE2].len)
+ bp -> data [2] = results [FQDN_RCODE2].data [0];
+
+ if (results [FQDN_ENCODED].len &&
+ results [FQDN_ENCODED].data [0]) {
+ bp->data[0] |= 4;
+ if (results [FQDN_FQDN].len) {
+ i = fqdn_encode(&bp->data[3], len - 3,
+ results[FQDN_FQDN].data,
+ results[FQDN_FQDN].len);
+
+ if (i < 0) {
+ status = 0;
+ goto exit;
+ }
+
+ result->len += i;
+ result->terminated = 0;
+ }
+ } else {
+ if (results [FQDN_FQDN].len) {
+ memcpy (&bp -> data [3], results [FQDN_FQDN].data,
+ results [FQDN_FQDN].len);
+ result -> len += results [FQDN_FQDN].len;
+ result -> terminated = 0;
+ }
+ }
+ exit:
+ for (i = 1; i <= FQDN_SUBOPTION_COUNT; i++) {
+ if (results [i].len)
+ data_string_forget (&results [i], MDL);
+ }
+ buffer_dereference (&bp, MDL);
+ if (!status)
+ data_string_forget(result, MDL);
+ return status;
+}
+
+/*
+ * Trap invalid attempts to inspect FQND6 contents.
+ */
+struct option_cache *
+lookup_fqdn6_option(struct universe *universe, struct option_state *options,
+ unsigned code)
+{
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ return NULL;
+}
+
+/*
+ * Trap invalid attempts to save options directly to FQDN6 rather than FQDN.
+ */
+void
+save_fqdn6_option(struct universe *universe, struct option_state *options,
+ struct option_cache *oc, isc_boolean_t appendp)
+{
+ log_fatal("Impossible condition at %s:%d.", MDL);
+}
+
+/*
+ * Trap invalid attempts to delete an option out of the FQDN6 universe.
+ */
+void
+delete_fqdn6_option(struct universe *universe, struct option_state *options,
+ int code)
+{
+ log_fatal("Impossible condition at %s:%d.", MDL);
+}
+
+/* Shill to the DHCPv4 fqdn option cache any attempts to traverse the
+ * V6's option cache entry.
+ *
+ * This function is called speculatively by dhclient to setup
+ * environment variables. But it would have already called the
+ * foreach on the normal fqdn universe, so this is superfluous.
+ */
+void
+fqdn6_option_space_foreach(struct packet *packet, struct lease *lease,
+ struct client_state *client_state,
+ struct option_state *in_options,
+ struct option_state *cfg_options,
+ struct binding_scope **scope,
+ struct universe *u, void *stuff,
+ void (*func)(struct option_cache *,
+ struct packet *,
+ struct lease *,
+ struct client_state *,
+ struct option_state *,
+ struct option_state *,
+ struct binding_scope **,
+ struct universe *, void *))
+{
+ /* Pretend it is empty. */
+ return;
+}
+
+/* Turn the FQDN option space into a DHCPv6 FQDN option buffer.
+ */
+int
+fqdn6_option_space_encapsulate(struct data_string *result,
+ struct packet *packet, struct lease *lease,
+ struct client_state *client_state,
+ struct option_state *in_options,
+ struct option_state *cfg_options,
+ struct binding_scope **scope,
+ struct universe *universe)
+{
+ pair ocp;
+ struct option_chain_head *head;
+ struct option_cache *oc;
+ unsigned char *data;
+ int i, len, rval = 0, count;
+ struct data_string results[FQDN_SUBOPTION_COUNT + 1];
+
+ if (fqdn_universe.index >= cfg_options->universe_count)
+ return 0;
+ head = ((struct option_chain_head *)
+ cfg_options->universes[fqdn_universe.index]);
+ if (head == NULL)
+ return 0;
+
+ memset(results, 0, sizeof(results));
+ for (ocp = head->first ; ocp != NULL ; ocp = ocp->cdr) {
+ oc = (struct option_cache *)(ocp->car);
+ if (oc->option->code > FQDN_SUBOPTION_COUNT)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ /* No need to check the return code, we check the length later */
+ (void) evaluate_option_cache(&results[oc->option->code], packet,
+ lease, client_state, in_options,
+ cfg_options, scope, oc, MDL);
+ }
+
+ /* We add a byte for the flags field at the start of the option.
+ * We add a byte because we will prepend a label count.
+ * We add a byte because the input length doesn't include a trailing
+ * NULL, and we will add a root label.
+ */
+ len = results[FQDN_FQDN].len + 3;
+ if (!buffer_allocate(&result->buffer, len, MDL)) {
+ log_error("No memory for virtual option buffer.");
+ goto exit;
+ }
+ data = result->buffer->data;
+ result->data = data;
+
+ /* The first byte is the flags field. */
+ result->len = 1;
+ data[0] = 0;
+ /* XXX: The server should set bit 3 (yes, 3, not 4) to 1 if we
+ * are not going to perform any DNS updates. The problem is
+ * that at this layer of abstraction, we do not know if the caller
+ * is the client or the server.
+ *
+ * See RFC4704 Section 4.1, 'The "N" bit'.
+ *
+ * if (?)
+ * data[0] |= 4;
+ */
+ if (results[FQDN_NO_CLIENT_UPDATE].len &&
+ results[FQDN_NO_CLIENT_UPDATE].data[0])
+ data[0] |= 2;
+ if (results[FQDN_SERVER_UPDATE].len &&
+ results[FQDN_SERVER_UPDATE].data[0])
+ data[0] |= 1;
+
+ /* If there is no name, we're done. */
+ if (results[FQDN_FQDN].len == 0) {
+ rval = 1;
+ goto exit;
+ }
+
+ /* Convert textual representation to DNS format. */
+ count = fqdn_encode(data + 1, len - 1,
+ results[FQDN_FQDN].data, results[FQDN_FQDN].len);
+
+ if (count < 0) {
+ rval = 0;
+ data_string_forget(result, MDL);
+ goto exit;
+ }
+
+ result->len += count;
+ result->terminated = 0;
+
+ /* Success! */
+ rval = 1;
+
+ exit:
+ for (i = 1 ; i <= FQDN_SUBOPTION_COUNT ; i++) {
+ if (result[i].len)
+ data_string_forget(&results[i], MDL);
+ }
+
+ return rval;
+}
+
+/* Read the DHCPv6 FQDN option's contents into the FQDN virtual space.
+ */
+int
+fqdn6_universe_decode(struct option_state *options,
+ const unsigned char *buffer, unsigned length,
+ struct universe *u)
+{
+ struct buffer *bp = NULL;
+ unsigned char *first_dot;
+ int len, hlen, dlen;
+
+ /* The FQDN option has to be at least 1 byte long. */
+ if (length < 1)
+ return 0;
+
+ /* Save the contents of the option in a buffer. There are 3
+ * one-byte values we record from the packet, so we go ahead
+ * and allocate a bigger buffer to accommodate them. But the
+ * 'length' we got (because it is a DNS encoded string) is
+ * one longer than we need...so we only add two extra octets.
+ */
+ if (!buffer_allocate(&bp, length + 2, MDL)) {
+ log_error("No memory for dhcp6.fqdn option buffer.");
+ return 0;
+ }
+
+ /* The v6 FQDN is always 'encoded' per DNS. */
+ bp->data[0] = 1;
+ if (!save_option_buffer(&fqdn_universe, options, bp,
+ bp->data, 1, FQDN_ENCODED, 0))
+ goto error;
+
+ /* XXX: We need to process 'The "N" bit'. */
+
+ if (buffer[0] & 1) /* server-update. */
+ bp->data[2] = 1;
+ else
+ bp->data[2] = 0;
+
+ if (!save_option_buffer(&fqdn_universe, options, bp, bp->data + 2, 1,
+ FQDN_SERVER_UPDATE, 0))
+ goto error;
+
+ if (buffer[0] & 2) /* no-client-update. */
+ bp->data[1] = 1;
+ else
+ bp->data[1] = 0;
+
+ if (!save_option_buffer(&fqdn_universe, options, bp, bp->data + 1, 1,
+ FQDN_NO_CLIENT_UPDATE, 0))
+ goto error;
+
+ /* Convert the domain name to textual representation for config. */
+ len = MRns_name_ntop(buffer + 1, (char *)bp->data + 3, length - 1);
+ if (len == -1) {
+ log_error("Unable to convert dhcp6.fqdn domain name to "
+ "printable form.");
+ goto error;
+ }
+
+ /* Save the domain name. */
+ if (len > 0) {
+ unsigned char *fqdn_start = bp->data + 3;
+
+ if (!save_option_buffer(&fqdn_universe, options, bp,
+ fqdn_start, len, FQDN_FQDN, 1))
+ goto error;
+
+ first_dot = (unsigned char *)strchr((char *)fqdn_start, '.');
+
+ if (first_dot != NULL) {
+ hlen = first_dot - fqdn_start;
+ dlen = len - hlen;
+ } else {
+ hlen = len;
+ dlen = 0;
+ }
+
+ if (!save_option_buffer(&fqdn_universe, options, bp,
+ fqdn_start, len, FQDN_FQDN, 1) ||
+ ((hlen > 0) &&
+ !save_option_buffer(&fqdn_universe, options, bp,
+ fqdn_start, hlen,
+ FQDN_HOSTNAME, 0)) ||
+ ((dlen > 0) &&
+ !save_option_buffer(&fqdn_universe, options, bp,
+ first_dot, dlen, FQDN_DOMAINNAME, 0)))
+ goto error;
+ }
+
+ buffer_dereference(&bp, MDL);
+ return 1;
+
+ error:
+ buffer_dereference(&bp, MDL);
+ return 0;
+}
+
+void option_space_foreach (struct packet *packet, struct lease *lease,
+ struct client_state *client_state,
+ struct option_state *in_options,
+ struct option_state *cfg_options,
+ struct binding_scope **scope,
+ struct universe *u, void *stuff,
+ void (*func) (struct option_cache *,
+ struct packet *,
+ struct lease *, struct client_state *,
+ struct option_state *,
+ struct option_state *,
+ struct binding_scope **,
+ struct universe *, void *))
+{
+ if (u -> foreach)
+ (*u -> foreach) (packet, lease, client_state, in_options,
+ cfg_options, scope, u, stuff, func);
+}
+
+void suboption_foreach (struct packet *packet, struct lease *lease,
+ struct client_state *client_state,
+ struct option_state *in_options,
+ struct option_state *cfg_options,
+ struct binding_scope **scope,
+ struct universe *u, void *stuff,
+ void (*func) (struct option_cache *,
+ struct packet *,
+ struct lease *, struct client_state *,
+ struct option_state *,
+ struct option_state *,
+ struct binding_scope **,
+ struct universe *, void *),
+ struct option_cache *oc,
+ const char *vsname)
+{
+ struct universe *universe = find_option_universe (oc -> option,
+ vsname);
+ if (universe -> foreach)
+ (*universe -> foreach) (packet, lease, client_state,
+ in_options, cfg_options,
+ scope, universe, stuff, func);
+}
+
+void hashed_option_space_foreach (struct packet *packet, struct lease *lease,
+ struct client_state *client_state,
+ struct option_state *in_options,
+ struct option_state *cfg_options,
+ struct binding_scope **scope,
+ struct universe *u, void *stuff,
+ void (*func) (struct option_cache *,
+ struct packet *,
+ struct lease *,
+ struct client_state *,
+ struct option_state *,
+ struct option_state *,
+ struct binding_scope **,
+ struct universe *, void *))
+{
+ pair *hash;
+ int i;
+ struct option_cache *oc;
+
+ if (cfg_options -> universe_count <= u -> index)
+ return;
+
+ hash = cfg_options -> universes [u -> index];
+ if (!hash)
+ return;
+ for (i = 0; i < OPTION_HASH_SIZE; i++) {
+ pair p;
+ /* XXX save _all_ options! XXX */
+ for (p = hash [i]; p; p = p -> cdr) {
+ oc = (struct option_cache *)p -> car;
+ (*func) (oc, packet, lease, client_state,
+ in_options, cfg_options, scope, u, stuff);
+ }
+ }
+}
+
+void
+save_linked_option(struct universe *universe, struct option_state *options,
+ struct option_cache *oc, isc_boolean_t appendp)
+{
+ pair *tail;
+ struct option_chain_head *head;
+ struct option_cache **ocloc;
+
+ if (universe -> index >= options -> universe_count)
+ return;
+ head = ((struct option_chain_head *)
+ options -> universes [universe -> index]);
+ if (!head) {
+ if (!option_chain_head_allocate (((struct option_chain_head **)
+ &options -> universes
+ [universe -> index]), MDL))
+ return;
+ head = ((struct option_chain_head *)
+ options -> universes [universe -> index]);
+ }
+
+ /* Find the tail of the list. */
+ for (tail = &head -> first; *tail; tail = &((*tail) -> cdr)) {
+ ocloc = (struct option_cache **)&(*tail)->car;
+
+ if (oc->option->code == (*ocloc)->option->code) {
+ if (appendp) {
+ do {
+ ocloc = &(*ocloc)->next;
+ } while (*ocloc != NULL);
+ } else {
+ option_cache_dereference(ocloc, MDL);
+ }
+ option_cache_reference(ocloc, oc, MDL);
+ return;
+ }
+ }
+
+ *tail = cons (0, 0);
+ if (*tail) {
+ option_cache_reference ((struct option_cache **)
+ (&(*tail) -> car), oc, MDL);
+ }
+}
+
+int linked_option_space_encapsulate (result, packet, lease, client_state,
+ in_options, cfg_options, scope, universe)
+ struct data_string *result;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *cfg_options;
+ struct binding_scope **scope;
+ struct universe *universe;
+{
+ int status = 0;
+ pair oc;
+ struct option_chain_head *head;
+
+ if (universe -> index >= cfg_options -> universe_count)
+ return status;
+ head = ((struct option_chain_head *)
+ cfg_options -> universes [universe -> index]);
+ if (!head)
+ return status;
+
+ for (oc = head -> first; oc; oc = oc -> cdr) {
+ if (store_option (result, universe, packet,
+ lease, client_state, in_options, cfg_options,
+ scope, (struct option_cache *)(oc -> car)))
+ status = 1;
+ }
+
+ if (search_subencapsulation(result, packet, lease, client_state,
+ in_options, cfg_options, scope, universe))
+ status = 1;
+
+ return status;
+}
+
+void delete_linked_option (universe, options, code)
+ struct universe *universe;
+ struct option_state *options;
+ int code;
+{
+ pair *tail, tmp = (pair)0;
+ struct option_chain_head *head;
+
+ if (universe -> index >= options -> universe_count)
+ return;
+ head = ((struct option_chain_head *)
+ options -> universes [universe -> index]);
+ if (!head)
+ return;
+
+ for (tail = &head -> first; *tail; tail = &((*tail) -> cdr)) {
+ if (code ==
+ ((struct option_cache *)(*tail) -> car) -> option -> code)
+ {
+ tmp = (*tail) -> cdr;
+ option_cache_dereference ((struct option_cache **)
+ (&(*tail) -> car), MDL);
+ dfree (*tail, MDL);
+ (*tail) = tmp;
+ break;
+ }
+ }
+}
+
+struct option_cache *lookup_linked_option (universe, options, code)
+ struct universe *universe;
+ struct option_state *options;
+ unsigned code;
+{
+ pair oc;
+ struct option_chain_head *head;
+
+ if (universe -> index >= options -> universe_count)
+ return 0;
+ head = ((struct option_chain_head *)
+ options -> universes [universe -> index]);
+ if (!head)
+ return 0;
+
+ for (oc = head -> first; oc; oc = oc -> cdr) {
+ if (code ==
+ ((struct option_cache *)(oc -> car)) -> option -> code) {
+ return (struct option_cache *)(oc -> car);
+ }
+ }
+
+ return (struct option_cache *)0;
+}
+
+int linked_option_state_dereference (universe, state, file, line)
+ struct universe *universe;
+ struct option_state *state;
+ const char *file;
+ int line;
+{
+ return (option_chain_head_dereference
+ ((struct option_chain_head **)
+ (&state -> universes [universe -> index]), MDL));
+}
+
+void linked_option_space_foreach (struct packet *packet, struct lease *lease,
+ struct client_state *client_state,
+ struct option_state *in_options,
+ struct option_state *cfg_options,
+ struct binding_scope **scope,
+ struct universe *u, void *stuff,
+ void (*func) (struct option_cache *,
+ struct packet *,
+ struct lease *,
+ struct client_state *,
+ struct option_state *,
+ struct option_state *,
+ struct binding_scope **,
+ struct universe *, void *))
+{
+ pair car;
+ struct option_chain_head *head;
+
+ if (u -> index >= cfg_options -> universe_count)
+ return;
+ head = ((struct option_chain_head *)
+ cfg_options -> universes [u -> index]);
+ if (!head)
+ return;
+ for (car = head -> first; car; car = car -> cdr) {
+ (*func) ((struct option_cache *)(car -> car),
+ packet, lease, client_state,
+ in_options, cfg_options, scope, u, stuff);
+ }
+}
+
+void do_packet (interface, packet, len, from_port, from, hfrom)
+ struct interface_info *interface;
+ struct dhcp_packet *packet;
+ unsigned len;
+ unsigned int from_port;
+ struct iaddr from;
+ struct hardware *hfrom;
+{
+ struct option_cache *op;
+ struct packet *decoded_packet;
+#if defined (DEBUG_MEMORY_LEAKAGE)
+ unsigned long previous_outstanding = dmalloc_outstanding;
+#endif
+
+#if defined (TRACING)
+ trace_inpacket_stash(interface, packet, len, from_port, from, hfrom);
+#endif
+
+ decoded_packet = NULL;
+ if (!packet_allocate(&decoded_packet, MDL)) {
+ log_error("do_packet: no memory for incoming packet!");
+ return;
+ }
+ decoded_packet->raw = packet;
+ decoded_packet->packet_length = len;
+ decoded_packet->client_port = from_port;
+ decoded_packet->client_addr = from;
+ interface_reference(&decoded_packet->interface, interface, MDL);
+ decoded_packet->haddr = hfrom;
+
+ if (packet->hlen > sizeof packet->chaddr) {
+ packet_dereference(&decoded_packet, MDL);
+ log_info("Discarding packet with bogus hlen.");
+ return;
+ }
+
+ /* Allocate packet->options now so it is non-null for all packets */
+ decoded_packet->options_valid = 0;
+ if (!option_state_allocate (&decoded_packet->options, MDL)) {
+ return;
+ }
+
+ /* If there's an option buffer, try to parse it. */
+ if (decoded_packet->packet_length >= DHCP_FIXED_NON_UDP + 4) {
+ if (!parse_options(decoded_packet)) {
+ packet_dereference (&decoded_packet, MDL);
+ return;
+ }
+
+ if (decoded_packet->options_valid &&
+ (op = lookup_option(&dhcp_universe,
+ decoded_packet->options,
+ DHO_DHCP_MESSAGE_TYPE))) {
+ struct data_string dp;
+ memset(&dp, 0, sizeof dp);
+ evaluate_option_cache(&dp, decoded_packet, NULL, NULL,
+ decoded_packet->options, NULL,
+ NULL, op, MDL);
+ if (dp.len > 0)
+ decoded_packet->packet_type = dp.data[0];
+ else
+ decoded_packet->packet_type = 0;
+ data_string_forget(&dp, MDL);
+ }
+ }
+
+ if (validate_packet(decoded_packet) != 0) {
+ if (decoded_packet->packet_type)
+ dhcp(decoded_packet);
+ else
+ bootp(decoded_packet);
+ }
+
+ /* If the caller kept the packet, they'll have upped the refcnt. */
+ packet_dereference(&decoded_packet, MDL);
+
+#if defined (DEBUG_MEMORY_LEAKAGE)
+ log_info("generation %ld: %ld new, %ld outstanding, %ld long-term",
+ dmalloc_generation,
+ dmalloc_outstanding - previous_outstanding,
+ dmalloc_outstanding, dmalloc_longterm);
+ dmalloc_dump_outstanding();
+#endif
+#if defined (DEBUG_RC_HISTORY_EXHAUSTIVELY)
+ dump_rc_history(0);
+#endif
+}
+
+int
+packet6_len_okay(const char *packet, int len) {
+ if (len < 1) {
+ return 0;
+ }
+ if ((packet[0] == DHCPV6_RELAY_FORW) ||
+ (packet[0] == DHCPV6_RELAY_REPL)) {
+ if (len >= offsetof(struct dhcpv6_relay_packet, options)) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else {
+ if (len >= offsetof(struct dhcpv6_packet, options)) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+}
+
+#ifdef DHCPv6
+void
+do_packet6(struct interface_info *interface, const char *packet,
+ int len, int from_port, const struct iaddr *from,
+ isc_boolean_t was_unicast) {
+ unsigned char msg_type;
+ const struct dhcpv6_packet *msg;
+ const struct dhcpv6_relay_packet *relay;
+ struct packet *decoded_packet;
+#if defined (DEBUG_MEMORY_LEAKAGE)
+ unsigned long previous_outstanding = dmalloc_outstanding;
+#endif
+
+ if (!packet6_len_okay(packet, len)) {
+ log_info("do_packet6: "
+ "short packet from %s port %d, len %d, dropped",
+ piaddr(*from), from_port, len);
+ return;
+ }
+
+ decoded_packet = NULL;
+ if (!packet_allocate(&decoded_packet, MDL)) {
+ log_error("do_packet6: no memory for incoming packet.");
+ return;
+ }
+
+ if (!option_state_allocate(&decoded_packet->options, MDL)) {
+ log_error("do_packet6: no memory for options.");
+ packet_dereference(&decoded_packet, MDL);
+ return;
+ }
+
+ /* IPv4 information, already set to 0 */
+ /* decoded_packet->packet_type = 0; */
+ /* memset(&decoded_packet->haddr, 0, sizeof(decoded_packet->haddr)); */
+ /* decoded_packet->circuit_id = NULL; */
+ /* decoded_packet->circuit_id_len = 0; */
+ /* decoded_packet->remote_id = NULL; */
+ /* decoded_packet->remote_id_len = 0; */
+ decoded_packet->raw = (struct dhcp_packet *)packet;
+ decoded_packet->packet_length = (unsigned)len;
+ decoded_packet->client_port = from_port;
+ decoded_packet->client_addr = *from;
+ interface_reference(&decoded_packet->interface, interface, MDL);
+
+ decoded_packet->unicast = was_unicast;
+
+ msg_type = packet[0];
+ if ((msg_type == DHCPV6_RELAY_FORW) ||
+ (msg_type == DHCPV6_RELAY_REPL)) {
+ int relaylen = (int)(offsetof(struct dhcpv6_relay_packet, options));
+ relay = (const struct dhcpv6_relay_packet *)packet;
+ decoded_packet->dhcpv6_msg_type = relay->msg_type;
+
+ /* relay-specific data */
+ decoded_packet->dhcpv6_hop_count = relay->hop_count;
+ memcpy(&decoded_packet->dhcpv6_link_address,
+ relay->link_address, sizeof(relay->link_address));
+ memcpy(&decoded_packet->dhcpv6_peer_address,
+ relay->peer_address, sizeof(relay->peer_address));
+
+ if (!parse_option_buffer(decoded_packet->options,
+ relay->options, len - relaylen,
+ &dhcpv6_universe)) {
+ /* no logging here, as parse_option_buffer() logs all
+ cases where it fails */
+ packet_dereference(&decoded_packet, MDL);
+ return;
+ }
+ } else {
+ int msglen = (int)(offsetof(struct dhcpv6_packet, options));
+ msg = (const struct dhcpv6_packet *)packet;
+ decoded_packet->dhcpv6_msg_type = msg->msg_type;
+
+ /* message-specific data */
+ memcpy(decoded_packet->dhcpv6_transaction_id,
+ msg->transaction_id,
+ sizeof(decoded_packet->dhcpv6_transaction_id));
+
+ if (!parse_option_buffer(decoded_packet->options,
+ msg->options, len - msglen,
+ &dhcpv6_universe)) {
+ /* no logging here, as parse_option_buffer() logs all
+ cases where it fails */
+ packet_dereference(&decoded_packet, MDL);
+ return;
+ }
+ }
+
+ dhcpv6(decoded_packet);
+
+ packet_dereference(&decoded_packet, MDL);
+
+#if defined (DEBUG_MEMORY_LEAKAGE)
+ log_info("generation %ld: %ld new, %ld outstanding, %ld long-term",
+ dmalloc_generation,
+ dmalloc_outstanding - previous_outstanding,
+ dmalloc_outstanding, dmalloc_longterm);
+ dmalloc_dump_outstanding();
+#endif
+#if defined (DEBUG_RC_HISTORY_EXHAUSTIVELY)
+ dump_rc_history(0);
+#endif
+}
+#endif /* DHCPv6 */
+
+int
+pretty_escape(char **dst, char *dend, const unsigned char **src,
+ const unsigned char *send)
+{
+ int count = 0;
+
+ /* If there aren't as many bytes left as there are in the source
+ * buffer, don't even bother entering the loop.
+ */
+ if (dst == NULL || dend == NULL || src == NULL || send == NULL ||
+ *dst == NULL || *src == NULL || (*dst >= dend) || (*src > send) ||
+ ((send - *src) > (dend - *dst)))
+ return -1;
+
+ for ( ; *src < send ; (*src)++) {
+ if (!isascii (**src) || !isprint (**src)) {
+ /* Skip trailing NUL. */
+ if ((*src + 1) != send || **src != '\0') {
+ if (*dst + 4 > dend)
+ return -1;
+
+ sprintf(*dst, "\\%03o",
+ **src);
+ (*dst) += 4;
+ count += 4;
+ }
+ } else if (**src == '"' || **src == '\'' || **src == '$' ||
+ **src == '`' || **src == '\\' || **src == '|' ||
+ **src == '&') {
+ if (*dst + 2 > dend)
+ return -1;
+
+ **dst = '\\';
+ (*dst)++;
+ **dst = **src;
+ (*dst)++;
+ count += 2;
+ } else {
+ if (*dst + 1 > dend)
+ return -1;
+
+ **dst = **src;
+ (*dst)++;
+ count++;
+ }
+ }
+
+ return count;
+}
+
+static int
+pretty_text(char **dst, char *dend, const unsigned char **src,
+ const unsigned char *send, int emit_quotes)
+{
+ int count;
+
+ if (dst == NULL || dend == NULL || src == NULL || send == NULL ||
+ *dst == NULL || *src == NULL ||
+ ((*dst + (emit_quotes ? 2 : 0)) > dend) || (*src > send))
+ return -1;
+
+ if (emit_quotes) {
+ **dst = '"';
+ (*dst)++;
+ }
+
+ /* dend-1 leaves 1 byte for the closing quote. */
+ count = pretty_escape(dst, dend - (emit_quotes ? 1 : 0), src, send);
+
+ if (count == -1)
+ return -1;
+
+ if (emit_quotes && (*dst < dend)) {
+ **dst = '"';
+ (*dst)++;
+
+ /* Includes quote prior to pretty_escape(); */
+ count += 2;
+ }
+
+ return count;
+}
+
+static int
+pretty_domain(char **dst, char *dend, const unsigned char **src,
+ const unsigned char *send)
+{
+ const unsigned char *tend;
+ int count = 2;
+ int tsiz, status;
+
+ if (dst == NULL || dend == NULL || src == NULL || send == NULL ||
+ *dst == NULL || *src == NULL ||
+ ((*dst + 2) > dend) || (*src >= send))
+ return -1;
+
+ **dst = '"';
+ (*dst)++;
+
+ do {
+ /* Continue loop until end of src buffer. */
+ if (*src >= send)
+ break;
+
+ /* Consume tag size. */
+ tsiz = **src;
+ (*src)++;
+
+ /* At root, finis. */
+ if (tsiz == 0)
+ break;
+
+ tend = (*src) + tsiz;
+
+ /* If the tag exceeds the source buffer, it's illegal.
+ * This should also trap compression pointers (which should
+ * not be in these buffers).
+ */
+ if (tend > send)
+ return -1;
+
+ /* dend-2 leaves room for a trailing dot and quote. */
+ status = pretty_escape(dst, dend-2, src, tend);
+
+ if ((status == -1) || ((*dst + 2) > dend))
+ return -1;
+
+ **dst = '.';
+ (*dst)++;
+ count += status + 1;
+ }
+ while(1);
+
+ **dst = '"';
+ (*dst)++;
+
+ return count;
+}
+
+/*
+ * Add the option identified with the option number and data to the
+ * options state.
+ */
+int
+add_option(struct option_state *options,
+ unsigned int option_num,
+ void *data,
+ unsigned int data_len)
+{
+ struct option_cache *oc;
+ struct option *option;
+
+ /* INSIST(options != NULL); */
+ /* INSIST(data != NULL); */
+
+ option = NULL;
+ if (!option_code_hash_lookup(&option, dhcp_universe.code_hash,
+ &option_num, 0, MDL)) {
+ log_error("Attempting to add unknown option %d.", option_num);
+ return 0;
+ }
+
+ oc = NULL;
+ if (!option_cache_allocate(&oc, MDL)) {
+ log_error("No memory for option cache adding %s (option %d).",
+ option->name, option_num);
+ return 0;
+ }
+
+ if (!make_const_data(&oc->expression,
+ data,
+ data_len,
+ 0,
+ 0,
+ MDL)) {
+ log_error("No memory for constant data adding %s (option %d).",
+ option->name, option_num);
+ option_cache_dereference(&oc, MDL);
+ return 0;
+ }
+
+ option_reference(&(oc->option), option, MDL);
+ save_option(&dhcp_universe, options, oc);
+ option_cache_dereference(&oc, MDL);
+
+ return 1;
+}
+
+/**
+ * Checks if received BOOTP/DHCPv4 packet is sane
+ *
+ * @param packet received, decoded packet
+ *
+ * @return 1 if packet is sane, 0 if it is not
+ */
+int validate_packet(struct packet *packet)
+{
+ struct option_cache *oc = NULL;
+
+ oc = lookup_option (&dhcp_universe, packet->options,
+ DHO_DHCP_CLIENT_IDENTIFIER);
+ if (oc) {
+ /* Let's check if client-identifier is sane */
+ if (oc->data.len == 0) {
+ log_debug("Dropped DHCPv4 packet with zero-length client-id");
+ return (0);
+
+ } else if (oc->data.len == 1) {
+ /*
+ * RFC2132, section 9.14 states that minimum length of client-id
+ * is 2. We will allow single-character client-ids for now (for
+ * backwards compatibility), but warn the user that support for
+ * this is against the standard.
+ */
+ log_debug("Accepted DHCPv4 packet with one-character client-id - "
+ "a future version of ISC DHCP will reject this");
+ }
+ } else {
+ /*
+ * If hlen is 0 we don't have any identifier, we warn the user
+ * but continue processing the packet as we can.
+ */
+ if (packet->raw->hlen == 0) {
+ log_debug("Received DHCPv4 packet without client-id"
+ " option and empty hlen field.");
+ }
+ }
+
+ /* @todo: Add checks for other received options */
+
+ return (1);
+}
diff --git a/common/packet.c b/common/packet.c
new file mode 100644
index 0000000..e4f390e
--- /dev/null
+++ b/common/packet.c
@@ -0,0 +1,359 @@
+/* packet.c
+
+ Packet assembly code, originally contributed by Archie Cobbs. */
+
+/*
+ * Copyright (c) 2009,2012,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004,2005,2007 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ * This code was originally contributed by Archie Cobbs, and is still
+ * very similar to that contribution, although the packet checksum code
+ * has been hacked significantly with the help of quite a few ISC DHCP
+ * users, without whose gracious and thorough help the checksum code would
+ * still be disabled.
+ */
+
+#include "dhcpd.h"
+
+#if defined (PACKET_ASSEMBLY) || defined (PACKET_DECODING)
+#include "includes/netinet/ip.h"
+#include "includes/netinet/udp.h"
+#include "includes/netinet/if_ether.h"
+#endif /* PACKET_ASSEMBLY || PACKET_DECODING */
+
+/* Compute the easy part of the checksum on a range of bytes. */
+
+u_int32_t checksum (buf, nbytes, sum)
+ unsigned char *buf;
+ unsigned nbytes;
+ u_int32_t sum;
+{
+ unsigned i;
+
+#ifdef DEBUG_CHECKSUM
+ log_debug ("checksum (%x %d %x)", buf, nbytes, sum);
+#endif
+
+ /* Checksum all the pairs of bytes first... */
+ for (i = 0; i < (nbytes & ~1U); i += 2) {
+#ifdef DEBUG_CHECKSUM_VERBOSE
+ log_debug ("sum = %x", sum);
+#endif
+ sum += (u_int16_t) ntohs(*((u_int16_t *)(buf + i)));
+ /* Add carry. */
+ if (sum > 0xFFFF)
+ sum -= 0xFFFF;
+ }
+
+ /* If there's a single byte left over, checksum it, too. Network
+ byte order is big-endian, so the remaining byte is the high byte. */
+ if (i < nbytes) {
+#ifdef DEBUG_CHECKSUM_VERBOSE
+ log_debug ("sum = %x", sum);
+#endif
+ sum += buf [i] << 8;
+ /* Add carry. */
+ if (sum > 0xFFFF)
+ sum -= 0xFFFF;
+ }
+
+ return sum;
+}
+
+/* Finish computing the checksum, and then put it into network byte order. */
+
+u_int32_t wrapsum (sum)
+ u_int32_t sum;
+{
+#ifdef DEBUG_CHECKSUM
+ log_debug ("wrapsum (%x)", sum);
+#endif
+
+ sum = ~sum & 0xFFFF;
+#ifdef DEBUG_CHECKSUM_VERBOSE
+ log_debug ("sum = %x", sum);
+#endif
+
+#ifdef DEBUG_CHECKSUM
+ log_debug ("wrapsum returns %x", htons (sum));
+#endif
+ return htons(sum);
+}
+
+#ifdef PACKET_ASSEMBLY
+void assemble_hw_header (interface, buf, bufix, to)
+ struct interface_info *interface;
+ unsigned char *buf;
+ unsigned *bufix;
+ struct hardware *to;
+{
+ switch (interface->hw_address.hbuf[0]) {
+#if defined(HAVE_TR_SUPPORT)
+ case HTYPE_IEEE802:
+ assemble_tr_header(interface, buf, bufix, to);
+ break;
+#endif
+#if defined (DEC_FDDI)
+ case HTYPE_FDDI:
+ assemble_fddi_header(interface, buf, bufix, to);
+ break;
+#endif
+ case HTYPE_INFINIBAND:
+ log_error("Attempt to assemble hw header for infiniband");
+ break;
+ case HTYPE_ETHER:
+ default:
+ assemble_ethernet_header(interface, buf, bufix, to);
+ break;
+ }
+}
+
+/* UDP header and IP header assembled together for convenience. */
+
+void assemble_udp_ip_header (interface, buf, bufix,
+ from, to, port, data, len)
+ struct interface_info *interface;
+ unsigned char *buf;
+ unsigned *bufix;
+ u_int32_t from;
+ u_int32_t to;
+ u_int32_t port;
+ unsigned char *data;
+ unsigned len;
+{
+ struct ip ip;
+ struct udphdr udp;
+
+ memset (&ip, 0, sizeof ip);
+
+ /* Fill out the IP header */
+ IP_V_SET (&ip, 4);
+ IP_HL_SET (&ip, 20);
+ ip.ip_tos = IPTOS_LOWDELAY;
+ ip.ip_len = htons(sizeof(ip) + sizeof(udp) + len);
+ ip.ip_id = 0;
+ ip.ip_off = 0;
+ ip.ip_ttl = 128;
+ ip.ip_p = IPPROTO_UDP;
+ ip.ip_sum = 0;
+ ip.ip_src.s_addr = from;
+ ip.ip_dst.s_addr = to;
+
+ /* Checksum the IP header... */
+ ip.ip_sum = wrapsum (checksum ((unsigned char *)&ip, sizeof ip, 0));
+
+ /* Copy the ip header into the buffer... */
+ memcpy (&buf [*bufix], &ip, sizeof ip);
+ *bufix += sizeof ip;
+
+ /* Fill out the UDP header */
+ udp.uh_sport = local_port; /* XXX */
+ udp.uh_dport = port; /* XXX */
+ udp.uh_ulen = htons(sizeof(udp) + len);
+ memset (&udp.uh_sum, 0, sizeof udp.uh_sum);
+
+ /* Compute UDP checksums, including the ``pseudo-header'', the UDP
+ header and the data. */
+
+ udp.uh_sum =
+ wrapsum (checksum ((unsigned char *)&udp, sizeof udp,
+ checksum (data, len,
+ checksum ((unsigned char *)
+ &ip.ip_src,
+ 2 * sizeof ip.ip_src,
+ IPPROTO_UDP +
+ (u_int32_t)
+ ntohs (udp.uh_ulen)))));
+
+ /* Copy the udp header into the buffer... */
+ memcpy (&buf [*bufix], &udp, sizeof udp);
+ *bufix += sizeof udp;
+}
+#endif /* PACKET_ASSEMBLY */
+
+#ifdef PACKET_DECODING
+/* Decode a hardware header... */
+/* Support for ethernet, TR and FDDI
+ * Doesn't support infiniband yet as the supported oses shouldn't get here
+ */
+
+ssize_t decode_hw_header (interface, buf, bufix, from)
+ struct interface_info *interface;
+ unsigned char *buf;
+ unsigned bufix;
+ struct hardware *from;
+{
+ switch(interface->hw_address.hbuf[0]) {
+#if defined (HAVE_TR_SUPPORT)
+ case HTYPE_IEEE802:
+ return (decode_tr_header(interface, buf, bufix, from));
+#endif
+#if defined (DEC_FDDI)
+ case HTYPE_FDDI:
+ return (decode_fddi_header(interface, buf, bufix, from));
+#endif
+ case HTYPE_INFINIBAND:
+ log_error("Attempt to decode hw header for infiniband");
+ return (0);
+ case HTYPE_ETHER:
+ default:
+ return (decode_ethernet_header(interface, buf, bufix, from));
+ }
+}
+
+/* UDP header and IP header decoded together for convenience. */
+
+ssize_t
+decode_udp_ip_header(struct interface_info *interface,
+ unsigned char *buf, unsigned bufix,
+ struct sockaddr_in *from, unsigned buflen,
+ unsigned *rbuflen, int csum_ready)
+{
+ unsigned char *data;
+ struct ip ip;
+ struct udphdr udp;
+ unsigned char *upp, *endbuf;
+ u_int32_t ip_len, ulen, pkt_len;
+ static unsigned int ip_packets_seen = 0;
+ static unsigned int ip_packets_bad_checksum = 0;
+ static unsigned int udp_packets_seen = 0;
+ static unsigned int udp_packets_bad_checksum = 0;
+ static unsigned int udp_packets_length_checked = 0;
+ static unsigned int udp_packets_length_overflow = 0;
+ unsigned len;
+
+ /* Designate the end of the input buffer for bounds checks. */
+ endbuf = buf + bufix + buflen;
+
+ /* Assure there is at least an IP header there. */
+ if ((buf + bufix + sizeof(ip)) > endbuf)
+ return -1;
+
+ /* Copy the IP header into a stack aligned structure for inspection.
+ * There may be bits in the IP header that we're not decoding, so we
+ * copy out the bits we grok and skip ahead by ip.ip_hl * 4.
+ */
+ upp = buf + bufix;
+ memcpy(&ip, upp, sizeof(ip));
+ ip_len = (*upp & 0x0f) << 2;
+ upp += ip_len;
+
+ /* Check the IP packet length. */
+ pkt_len = ntohs(ip.ip_len);
+ if (pkt_len > buflen)
+ return -1;
+
+ /* Assure after ip_len bytes that there is enough room for a UDP header. */
+ if ((upp + sizeof(udp)) > endbuf)
+ return -1;
+
+ /* Copy the UDP header into a stack aligned structure for inspection. */
+ memcpy(&udp, upp, sizeof(udp));
+
+#ifdef USERLAND_FILTER
+ /* Is it a UDP packet? */
+ if (ip.ip_p != IPPROTO_UDP)
+ return -1;
+
+ /* Is it to the port we're serving? */
+ if (udp.uh_dport != local_port)
+ return -1;
+#endif /* USERLAND_FILTER */
+
+ ulen = ntohs(udp.uh_ulen);
+ if (ulen < sizeof(udp))
+ return -1;
+
+ udp_packets_length_checked++;
+ if ((upp + ulen) > endbuf) {
+ udp_packets_length_overflow++;
+ if (((udp_packets_length_checked > 4) &&
+ (udp_packets_length_overflow != 0)) &&
+ ((udp_packets_length_checked / udp_packets_length_overflow) < 2)) {
+ log_info("%u udp packets in %u too long - dropped",
+ udp_packets_length_overflow,
+ udp_packets_length_checked);
+ udp_packets_length_overflow = 0;
+ udp_packets_length_checked = 0;
+ }
+ return -1;
+ }
+
+ /* Check the IP header checksum - it should be zero. */
+ ip_packets_seen++;
+ if (wrapsum (checksum (buf + bufix, ip_len, 0))) {
+ ++ip_packets_bad_checksum;
+ if (((ip_packets_seen > 4) && (ip_packets_bad_checksum != 0)) &&
+ ((ip_packets_seen / ip_packets_bad_checksum) < 2)) {
+ log_info ("%u bad IP checksums seen in %u packets",
+ ip_packets_bad_checksum, ip_packets_seen);
+ ip_packets_seen = ip_packets_bad_checksum = 0;
+ }
+ return -1;
+ }
+
+ /* Copy out the IP source address... */
+ memcpy(&from->sin_addr, &ip.ip_src, 4);
+
+ data = upp + sizeof(udp);
+ len = ulen - sizeof(udp);
+
+ /* UDP check sum may be optional (udp.uh_sum == 0) or not ready if checksum
+ * offloading is in use */
+ udp_packets_seen++;
+ if (udp.uh_sum && csum_ready) {
+ /* Check the UDP header checksum - since the received packet header
+ * contains the UDP checksum calculated by the transmitter, calculating
+ * it now should come out to zero. */
+ if (wrapsum(checksum((unsigned char *)&udp, sizeof(udp),
+ checksum(data, len,
+ checksum((unsigned char *)&ip.ip_src,
+ 8, IPPROTO_UDP + ulen))))) {
+ udp_packets_bad_checksum++;
+ if (((udp_packets_seen > 4) && (udp_packets_bad_checksum != 0))
+ && ((udp_packets_seen / udp_packets_bad_checksum) < 2)) {
+ log_info ("%u bad udp checksums in %u packets",
+ udp_packets_bad_checksum, udp_packets_seen);
+ udp_packets_seen = udp_packets_bad_checksum = 0;
+ }
+
+ return -1;
+ }
+ }
+
+ /* If at least 5 with less than 50% bad, start over */
+ if (udp_packets_seen > 4) {
+ udp_packets_bad_checksum = 0;
+ udp_packets_seen = 0;
+ }
+
+ /* Copy out the port... */
+ memcpy (&from -> sin_port, &udp.uh_sport, sizeof udp.uh_sport);
+
+ /* Save the length of the UDP payload. */
+ if (rbuflen != NULL)
+ *rbuflen = len;
+
+ /* Return the index to the UDP payload. */
+ return ip_len + sizeof udp;
+}
+#endif /* PACKET_DECODING */
diff --git a/common/parse.c b/common/parse.c
new file mode 100644
index 0000000..8f1518f
--- /dev/null
+++ b/common/parse.c
@@ -0,0 +1,6007 @@
+/* parse.c
+
+ Common parser code for dhcpd and dhclient. */
+
+/*
+ * Copyright (c) 2004-2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+#include <syslog.h>
+
+/* Enumerations can be specified in option formats, and are used for
+ parsing, so we define the routines that manage them here. */
+
+struct enumeration *enumerations;
+
+void add_enumeration (struct enumeration *enumeration)
+{
+ enumeration -> next = enumerations;
+ enumerations = enumeration;
+}
+
+struct enumeration *find_enumeration (const char *name, int length)
+{
+ struct enumeration *e;
+
+ for (e = enumerations; e; e = e -> next)
+ if (strlen (e -> name) == length &&
+ !memcmp (e -> name, name, (unsigned)length))
+ return e;
+ return (struct enumeration *)0;
+}
+
+struct enumeration_value *find_enumeration_value (const char *name,
+ int length,
+ unsigned *widthp,
+ const char *value)
+{
+ struct enumeration *e;
+ int i;
+
+ e = find_enumeration (name, length);
+ if (e) {
+ if (widthp != NULL)
+ *widthp = e->width;
+ for (i = 0; e -> values [i].name; i++) {
+ if (!strcmp (value, e -> values [i].name))
+ return &e -> values [i];
+ }
+ }
+ return (struct enumeration_value *)0;
+}
+
+/* Skip to the semicolon ending the current statement. If we encounter
+ braces, the matching closing brace terminates the statement.
+*/
+void skip_to_semi (cfile)
+ struct parse *cfile;
+{
+ skip_to_rbrace(cfile, 0);
+}
+
+/* Skips everything from the current point upto (and including) the given
+ number of right braces. If we encounter a semicolon but haven't seen a
+ left brace, consume it and return.
+ This lets us skip over:
+
+ statement;
+ statement foo bar { }
+ statement foo bar { statement { } }
+ statement}
+
+ ...et cetera. */
+void skip_to_rbrace (cfile, brace_count)
+ struct parse *cfile;
+ int brace_count;
+{
+ enum dhcp_token token;
+ const char *val;
+
+#if defined (DEBUG_TOKEN)
+ log_error("skip_to_rbrace: %d\n", brace_count);
+#endif
+ do {
+ token = peek_token(&val, NULL, cfile);
+ if (token == RBRACE) {
+ if (brace_count > 0) {
+ --brace_count;
+ }
+
+ if (brace_count == 0) {
+ /* Eat the brace and return. */
+ skip_token(&val, NULL, cfile);
+ return;
+ }
+ } else if (token == LBRACE) {
+ brace_count++;
+ } else if (token == SEMI && (brace_count == 0)) {
+ /* Eat the semicolon and return. */
+ skip_token(&val, NULL, cfile);
+ return;
+ } else if (token == EOL) {
+ /* EOL only happens when parsing /etc/resolv.conf,
+ and we treat it like a semicolon because the
+ resolv.conf file is line-oriented. */
+ skip_token(&val, NULL, cfile);
+ return;
+ }
+
+ /* Eat the current token */
+ token = next_token(&val, NULL, cfile);
+ } while (token != END_OF_FILE);
+}
+
+int parse_semi (cfile)
+ struct parse *cfile;
+{
+ enum dhcp_token token;
+ const char *val;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != SEMI) {
+ parse_warn (cfile, "semicolon expected.");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ return 1;
+}
+
+/* string-parameter :== STRING SEMI */
+
+int parse_string (cfile, sptr, lptr)
+ struct parse *cfile;
+ char **sptr;
+ unsigned *lptr;
+{
+ const char *val;
+ enum dhcp_token token;
+ char *s;
+ unsigned len;
+
+ token = next_token (&val, &len, cfile);
+ if (token != STRING) {
+ parse_warn (cfile, "expecting a string");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ s = (char *)dmalloc (len + 1, MDL);
+ if (!s)
+ log_fatal ("no memory for string %s.", val);
+ memcpy (s, val, len + 1);
+
+ if (!parse_semi (cfile)) {
+ dfree (s, MDL);
+ return 0;
+ }
+ if (sptr)
+ *sptr = s;
+ else
+ dfree (s, MDL);
+ if (lptr)
+ *lptr = len;
+ return 1;
+}
+
+/*
+ * hostname :== IDENTIFIER
+ * | IDENTIFIER DOT
+ * | hostname DOT IDENTIFIER
+ */
+
+char *parse_host_name (cfile)
+ struct parse *cfile;
+{
+ const char *val;
+ enum dhcp_token token;
+ unsigned len = 0;
+ char *s;
+ char *t;
+ pair c = (pair)0;
+ int ltid = 0;
+
+ /* Read a dotted hostname... */
+ do {
+ /* Read a token, which should be an identifier. */
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (!is_identifier (token) && token != NUMBER)
+ break;
+ skip_token(&val, (unsigned *)0, cfile);
+
+ /* Store this identifier... */
+ if (!(s = (char *)dmalloc (strlen (val) + 1, MDL)))
+ log_fatal ("can't allocate temp space for hostname.");
+ strcpy (s, val);
+ c = cons ((caddr_t)s, c);
+ len += strlen (s) + 1;
+ /* Look for a dot; if it's there, keep going, otherwise
+ we're done. */
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == DOT) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ ltid = 1;
+ } else
+ ltid = 0;
+ } while (token == DOT);
+
+ /* Should be at least one token. */
+ if (!len)
+ return (char *)0;
+
+ /* Assemble the hostname together into a string. */
+ if (!(s = (char *)dmalloc (len + ltid, MDL)))
+ log_fatal ("can't allocate space for hostname.");
+ t = s + len + ltid;
+ *--t = 0;
+ if (ltid)
+ *--t = '.';
+ while (c) {
+ pair cdr = c -> cdr;
+ unsigned l = strlen ((char *)(c -> car));
+ t -= l;
+ memcpy (t, (char *)(c -> car), l);
+ /* Free up temp space. */
+ dfree (c -> car, MDL);
+ dfree (c, MDL);
+ c = cdr;
+ if (t != s)
+ *--t = '.';
+ }
+ return s;
+}
+
+/* ip-addr-or-hostname :== ip-address | hostname
+ ip-address :== NUMBER DOT NUMBER DOT NUMBER DOT NUMBER
+
+ Parse an ip address or a hostname. If uniform is zero, put in
+ an expr_substring node to limit hostnames that evaluate to more
+ than one IP address.
+
+ Note that RFC1123 permits hostnames to consist of all digits,
+ making it difficult to quickly disambiguate them from ip addresses.
+*/
+
+int parse_ip_addr_or_hostname (expr, cfile, uniform)
+ struct expression **expr;
+ struct parse *cfile;
+ int uniform;
+{
+ const char *val;
+ enum dhcp_token token;
+ unsigned char addr [4];
+ unsigned len = sizeof addr;
+ char *name;
+ struct expression *x = (struct expression *)0;
+ int ipaddr = 0;
+
+ token = peek_token (&val, (unsigned *)0, cfile);
+
+ if (token == NUMBER) {
+ /*
+ * a hostname may be numeric, but domain names must
+ * start with a letter, so we can disambiguate by
+ * looking ahead a few tokens. we save the parse
+ * context first, and restore it after we know what
+ * we're dealing with.
+ */
+ save_parse_state(cfile);
+ skip_token(NULL, NULL, cfile);
+ if (next_token(NULL, NULL, cfile) == DOT &&
+ next_token(NULL, NULL, cfile) == NUMBER)
+ ipaddr = 1;
+ restore_parse_state(cfile);
+
+ if (ipaddr &&
+ parse_numeric_aggregate (cfile, addr, &len, DOT, 10, 8))
+ return make_const_data (expr, addr, len, 0, 1, MDL);
+
+ }
+
+ if (is_identifier (token) || token == NUMBER) {
+ name = parse_host_name (cfile);
+ if (!name)
+ return 0;
+ if (!make_host_lookup (expr, name)) {
+ dfree(name, MDL);
+ return 0;
+ }
+ dfree(name, MDL);
+ if (!uniform) {
+ if (!make_limit (&x, *expr, 4))
+ return 0;
+ expression_dereference (expr, MDL);
+ *expr = x;
+ }
+ } else {
+ if (token != RBRACE && token != LBRACE)
+ token = next_token (&val, (unsigned *)0, cfile);
+ parse_warn (cfile, "%s (%d): expecting IP address or hostname",
+ val, token);
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * ip-address :== NUMBER DOT NUMBER DOT NUMBER DOT NUMBER
+ */
+
+int parse_ip_addr (cfile, addr)
+ struct parse *cfile;
+ struct iaddr *addr;
+{
+ addr -> len = 4;
+ if (parse_numeric_aggregate (cfile, addr -> iabuf,
+ &addr -> len, DOT, 10, 8))
+ return 1;
+ return 0;
+}
+
+/*
+ * Return true if every character in the string is hexadecimal.
+ */
+static int
+is_hex_string(const char *s) {
+ while (*s != '\0') {
+ if (!isxdigit((int)*s)) {
+ return 0;
+ }
+ s++;
+ }
+ return 1;
+}
+
+/*
+ * ip-address6 :== (complicated set of rules)
+ *
+ * See section 2.2 of RFC 1884 for details.
+ *
+ * We are lazy for this. We pull numbers, names, colons, and dots
+ * together and then throw the resulting string at the inet_pton()
+ * function.
+ */
+
+int
+parse_ip6_addr(struct parse *cfile, struct iaddr *addr) {
+ enum dhcp_token token;
+ const char *val;
+ int val_len;
+
+ char v6[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+ int v6_len;
+
+ /*
+ * First token is non-raw. This way we eat any whitespace before
+ * our IPv6 address begins, like one would expect.
+ */
+ token = peek_token(&val, NULL, cfile);
+
+ /*
+ * Gather symbols.
+ */
+ v6_len = 0;
+ for (;;) {
+ if ((((token == NAME) || (token == NUMBER_OR_NAME)) &&
+ is_hex_string(val)) ||
+ (token == NUMBER) ||
+ (token == DOT) ||
+ (token == COLON)) {
+
+ next_raw_token(&val, NULL, cfile);
+ val_len = strlen(val);
+ if ((v6_len + val_len) >= sizeof(v6)) {
+ parse_warn(cfile, "Invalid IPv6 address.");
+ skip_to_semi(cfile);
+ return 0;
+ }
+ memcpy(v6+v6_len, val, val_len);
+ v6_len += val_len;
+
+ } else {
+ break;
+ }
+ token = peek_raw_token(&val, NULL, cfile);
+ }
+ v6[v6_len] = '\0';
+
+ /*
+ * Use inet_pton() for actual work.
+ */
+ if (inet_pton(AF_INET6, v6, addr->iabuf) <= 0) {
+ parse_warn(cfile, "Invalid IPv6 address.");
+ skip_to_semi(cfile);
+ return 0;
+ }
+ addr->len = 16;
+ return 1;
+}
+
+/*
+ * Same as parse_ip6_addr() above, but returns the value in the
+ * expression rather than in an address structure.
+ */
+int
+parse_ip6_addr_expr(struct expression **expr,
+ struct parse *cfile) {
+ struct iaddr addr;
+
+ if (!parse_ip6_addr(cfile, &addr)) {
+ return 0;
+ }
+ return make_const_data(expr, addr.iabuf, addr.len, 0, 1, MDL);
+}
+
+/*
+ * ip6-prefix :== ip6-address "/" NUMBER
+ */
+int
+parse_ip6_prefix(struct parse *cfile, struct iaddr *addr, u_int8_t *plen) {
+ enum dhcp_token token;
+ const char *val;
+ int n;
+
+ if (!parse_ip6_addr(cfile, addr)) {
+ return 0;
+ }
+ token = next_token(&val, NULL, cfile);
+ if (token != SLASH) {
+ parse_warn(cfile, "Slash expected.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return 0;
+ }
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "Number expected.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return 0;
+ }
+ n = atoi(val);
+ if ((n < 0) || (n > 128)) {
+ parse_warn(cfile, "Invalid IPv6 prefix length.");
+ skip_to_semi(cfile);
+ return 0;
+ }
+ if (!is_cidr_mask_valid(addr, n)) {
+ parse_warn(cfile, "network mask too short.");
+ skip_to_semi(cfile);
+ return 0;
+ }
+ *plen = n;
+ return 1;
+}
+
+/*
+ * ip-address-with-subnet :== ip-address |
+ * ip-address "/" NUMBER
+ */
+
+int
+parse_ip_addr_with_subnet(cfile, match)
+ struct parse *cfile;
+ struct iaddrmatch *match;
+{
+ const char *val, *orig;
+ enum dhcp_token token;
+ int prefixlen;
+ int fflen;
+ unsigned char newval, warnmask=0;
+
+ if (parse_ip_addr(cfile, &match->addr)) {
+ /* default to host mask */
+ prefixlen = match->addr.len * 8;
+
+ token = peek_token(&val, NULL, cfile);
+
+ if (token == SLASH) {
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+
+ if (token != NUMBER) {
+ parse_warn(cfile, "Invalid CIDR prefix length:"
+ " expecting a number.");
+ return 0;
+ }
+
+ prefixlen = atoi(val);
+
+ if (prefixlen < 0 ||
+ prefixlen > (match->addr.len * 8)) {
+ parse_warn(cfile, "subnet prefix is out of "
+ "range [0..%d].",
+ match->addr.len * 8);
+ return 0;
+ }
+ }
+
+ /* construct a suitable mask field */
+
+ /* copy length */
+ match->mask.len = match->addr.len;
+
+ /* count of 0xff bytes in mask */
+ fflen = prefixlen / 8;
+
+ /* set leading mask */
+ memset(match->mask.iabuf, 0xff, fflen);
+
+ /* set zeroes */
+ if (fflen < match->mask.len) {
+ match->mask.iabuf[fflen] =
+ "\x00\x80\xc0\xe0\xf0\xf8\xfc\xfe"[prefixlen % 8];
+
+ memset(match->mask.iabuf+fflen+1, 0x00,
+ match->mask.len - fflen - 1);
+
+ /* AND-out insignificant bits from supplied netmask. */
+ orig = piaddr(match->addr);
+ do {
+ newval = match->addr.iabuf[fflen] &
+ match->mask.iabuf[fflen];
+
+ if (newval != match->addr.iabuf[fflen]) {
+ warnmask = 1;
+ match->addr.iabuf[fflen] = newval;
+ }
+ } while (++fflen < match->mask.len);
+
+ if (warnmask) {
+ log_error("Warning: Extraneous bits removed "
+ "in address component of %s/%d.",
+ orig, prefixlen);
+ log_error("New value: %s/%d.",
+ piaddr(match->addr), prefixlen);
+ }
+ }
+
+ return 1;
+ }
+
+ parse_warn(cfile,
+ "expecting ip-address or ip-address/prefixlen");
+
+ return 0; /* let caller pick up pieces */
+}
+
+/*
+ * hardware-parameter :== HARDWARE hardware-type colon-separated-hex-list SEMI
+ * hardware-type :== ETHERNET | TOKEN_RING | TOKEN_FDDI | INFINIBAND
+ * Note that INFINIBAND may not be useful for some items, such as classification
+ * as the hardware address won't always be available.
+ */
+
+void parse_hardware_param (cfile, hardware)
+ struct parse *cfile;
+ struct hardware *hardware;
+{
+ const char *val;
+ enum dhcp_token token;
+ unsigned hlen;
+ unsigned char *t;
+
+ token = next_token(&val, NULL, cfile);
+ switch (token) {
+ case ETHERNET:
+ hardware->hbuf[0] = HTYPE_ETHER;
+ break;
+ case TOKEN_RING:
+ hardware->hbuf[0] = HTYPE_IEEE802;
+ break;
+ case TOKEN_FDDI:
+ hardware->hbuf[0] = HTYPE_FDDI;
+ break;
+ case TOKEN_INFINIBAND:
+ hardware->hbuf[0] = HTYPE_INFINIBAND;
+ break;
+ default:
+ if (!strncmp(val, "unknown-", 8)) {
+ hardware->hbuf[0] = atoi(&val[8]);
+ } else {
+ parse_warn(cfile,
+ "expecting a network hardware type");
+ skip_to_semi(cfile);
+
+ return;
+ }
+ }
+
+ /* Parse the hardware address information. Technically,
+ it would make a lot of sense to restrict the length of the
+ data we'll accept here to the length of a particular hardware
+ address type. Unfortunately, there are some broken clients
+ out there that put bogus data in the chaddr buffer, and we accept
+ that data in the lease file rather than simply failing on such
+ clients. Yuck. */
+ hlen = 0;
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI) {
+ hardware->hlen = 1;
+ goto out;
+ }
+ t = parse_numeric_aggregate(cfile, NULL, &hlen, COLON, 16, 8);
+ if (t == NULL) {
+ hardware->hlen = 1;
+ return;
+ }
+ if (hlen + 1 > sizeof(hardware->hbuf)) {
+ dfree(t, MDL);
+ parse_warn(cfile, "hardware address too long");
+ } else {
+ hardware->hlen = hlen + 1;
+ memcpy((unsigned char *)&hardware->hbuf[1], t, hlen);
+ if (hlen + 1 < sizeof(hardware->hbuf))
+ memset(&hardware->hbuf[hlen + 1], 0,
+ (sizeof(hardware->hbuf)) - hlen - 1);
+ dfree(t, MDL);
+ }
+
+ out:
+ token = next_token(&val, NULL, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "expecting semicolon.");
+ skip_to_semi(cfile);
+ }
+}
+
+/* lease-time :== NUMBER SEMI */
+
+void parse_lease_time (cfile, timep)
+ struct parse *cfile;
+ TIME *timep;
+{
+ const char *val;
+ enum dhcp_token token;
+ u_int32_t num;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "Expecting numeric lease time");
+ skip_to_semi (cfile);
+ return;
+ }
+ convert_num(cfile, (unsigned char *)&num, val, 10, 32);
+ /* Unswap the number - convert_num returns stuff in NBO. */
+ *timep = ntohl(num);
+
+ parse_semi (cfile);
+}
+
+/* No BNF for numeric aggregates - that's defined by the caller. What
+ this function does is to parse a sequence of numbers separated by
+ the token specified in separator. If max is zero, any number of
+ numbers will be parsed; otherwise, exactly max numbers are
+ expected. Base and size tell us how to internalize the numbers
+ once they've been tokenized.
+
+ buf - A pointer to space to return the parsed value, if it is null
+ then the function will allocate space for the return.
+
+ max - The maximum number of items to store. If zero there is no
+ maximum. When buf is null and the function needs to allocate space
+ it will do an allocation of max size at the beginning if max is non
+ zero. If max is zero then the allocation will be done later, after
+ the function has determined the size necessary for the incoming
+ string.
+
+ returns NULL on errors or a pointer to the value string on success.
+ The pointer will either be buf if it was non-NULL or newly allocated
+ space if buf was NULL
+ */
+
+
+unsigned char *parse_numeric_aggregate (cfile, buf,
+ max, separator, base, size)
+ struct parse *cfile;
+ unsigned char *buf;
+ unsigned *max;
+ int separator;
+ int base;
+ unsigned size;
+{
+ const char *val;
+ enum dhcp_token token;
+ unsigned char *bufp = buf, *s, *t;
+ unsigned count = 0;
+ pair c = (pair)0;
+
+ if (!bufp && *max) {
+ bufp = (unsigned char *)dmalloc (*max * size / 8, MDL);
+ if (!bufp)
+ log_fatal ("no space for numeric aggregate");
+ }
+ s = bufp;
+
+ do {
+ if (count) {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token != separator) {
+ if (!*max)
+ break;
+ if (token != RBRACE && token != LBRACE)
+ token = next_token (&val,
+ (unsigned *)0,
+ cfile);
+ parse_warn (cfile, "too few numbers.");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ /* free bufp if it was allocated */
+ if ((bufp != NULL) && (bufp != buf))
+ dfree(bufp, MDL);
+ return (unsigned char *)0;
+ }
+ skip_token(&val, (unsigned *)0, cfile);
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+
+ if (token == END_OF_FILE) {
+ parse_warn (cfile, "unexpected end of file");
+ break;
+ }
+
+ /* Allow NUMBER_OR_NAME if base is 16. */
+ if (token != NUMBER &&
+ (base != 16 || token != NUMBER_OR_NAME)) {
+ parse_warn (cfile, "expecting numeric value.");
+ skip_to_semi (cfile);
+ /* free bufp if it was allocated */
+ if ((bufp != NULL) && (bufp != buf))
+ dfree(bufp, MDL);
+ /* free any linked numbers we may have allocated */
+ while (c) {
+ pair cdr = c->cdr;
+ dfree(c->car, MDL);
+ dfree(c, MDL);
+ c = cdr;
+ }
+ return (NULL);
+ }
+ /* If we can, convert the number now; otherwise, build
+ a linked list of all the numbers. */
+ if (s) {
+ convert_num (cfile, s, val, base, size);
+ s += size / 8;
+ } else {
+ t = (unsigned char *)dmalloc (strlen (val) + 1, MDL);
+ if (!t)
+ log_fatal ("no temp space for number.");
+ strcpy ((char *)t, val);
+ c = cons ((caddr_t)t, c);
+ }
+ } while (++count != *max);
+
+ /* If we had to cons up a list, convert it now. */
+ if (c) {
+ /*
+ * No need to cleanup bufp, to get here we didn't allocate
+ * bufp above
+ */
+ bufp = (unsigned char *)dmalloc (count * size / 8, MDL);
+ if (!bufp)
+ log_fatal ("no space for numeric aggregate.");
+ s = bufp + count - size / 8;
+ *max = count;
+ }
+ while (c) {
+ pair cdr = c -> cdr;
+ convert_num (cfile, s, (char *)(c -> car), base, size);
+ s -= size / 8;
+ /* Free up temp space. */
+ dfree (c -> car, MDL);
+ dfree (c, MDL);
+ c = cdr;
+ }
+ return bufp;
+}
+
+void convert_num (cfile, buf, str, base, size)
+ struct parse *cfile;
+ unsigned char *buf;
+ const char *str;
+ int base;
+ unsigned size;
+{
+ const unsigned char *ptr = (const unsigned char *)str;
+ int negative = 0;
+ u_int32_t val = 0;
+ int tval;
+ int max;
+
+ if (*ptr == '-') {
+ negative = 1;
+ ++ptr;
+ }
+
+ /* If base wasn't specified, figure it out from the data. */
+ if (!base) {
+ if (ptr [0] == '0') {
+ if (ptr [1] == 'x') {
+ base = 16;
+ ptr += 2;
+ } else if (isascii (ptr [1]) && isdigit (ptr [1])) {
+ base = 8;
+ ptr += 1;
+ } else {
+ base = 10;
+ }
+ } else {
+ base = 10;
+ }
+ }
+
+ do {
+ tval = *ptr++;
+ /* XXX assumes ASCII... */
+ if (tval >= 'a')
+ tval = tval - 'a' + 10;
+ else if (tval >= 'A')
+ tval = tval - 'A' + 10;
+ else if (tval >= '0')
+ tval -= '0';
+ else {
+ parse_warn (cfile, "Bogus number: %s.", str);
+ break;
+ }
+ if (tval >= base) {
+ parse_warn (cfile,
+ "Bogus number %s: digit %d not in base %d",
+ str, tval, base);
+ break;
+ }
+ val = val * base + tval;
+ } while (*ptr);
+
+ if (negative)
+ max = (1 << (size - 1));
+ else
+ max = (1 << (size - 1)) + ((1 << (size - 1)) - 1);
+ if (val > max) {
+ switch (base) {
+ case 8:
+ parse_warn (cfile,
+ "%s%lo exceeds max (%d) for precision.",
+ negative ? "-" : "",
+ (unsigned long)val, max);
+ break;
+ case 16:
+ parse_warn (cfile,
+ "%s%lx exceeds max (%d) for precision.",
+ negative ? "-" : "",
+ (unsigned long)val, max);
+ break;
+ default:
+ parse_warn (cfile,
+ "%s%lu exceeds max (%d) for precision.",
+ negative ? "-" : "",
+ (unsigned long)val, max);
+ break;
+ }
+ }
+
+ if (negative) {
+ switch (size) {
+ case 8:
+ *buf = -(unsigned long)val;
+ break;
+ case 16:
+ putShort (buf, -(long)val);
+ break;
+ case 32:
+ putLong (buf, -(long)val);
+ break;
+ default:
+ parse_warn (cfile,
+ "Unexpected integer size: %d\n", size);
+ break;
+ }
+ } else {
+ switch (size) {
+ case 8:
+ *buf = (u_int8_t)val;
+ break;
+ case 16:
+ putUShort (buf, (u_int16_t)val);
+ break;
+ case 32:
+ putULong (buf, val);
+ break;
+ default:
+ parse_warn (cfile,
+ "Unexpected integer size: %d\n", size);
+ break;
+ }
+ }
+}
+
+/*
+ * date :== NUMBER NUMBER SLASH NUMBER SLASH NUMBER
+ * NUMBER COLON NUMBER COLON NUMBER |
+ * NUMBER NUMBER SLASH NUMBER SLASH NUMBER
+ * NUMBER COLON NUMBER COLON NUMBER NUMBER |
+ * EPOCH NUMBER |
+ * NEVER
+ *
+ * Dates are stored in UTC or with a timezone offset; first number is day
+ * of week; next is year/month/day; next is hours:minutes:seconds on a
+ * 24-hour clock, followed by the timezone offset in seconds, which is
+ * optional.
+ */
+
+/*
+ * just parse the date
+ * any trailing semi must be consumed by the caller of this routine
+ */
+TIME
+parse_date_core(cfile)
+ struct parse *cfile;
+{
+ int guess;
+ int tzoff, year, mon, mday, hour, min, sec;
+ const char *val;
+ enum dhcp_token token;
+ static int months[11] = { 31, 59, 90, 120, 151, 181,
+ 212, 243, 273, 304, 334 };
+
+ /* "never", "epoch" or day of week */
+ token = peek_token(&val, NULL, cfile);
+ if (token == NEVER) {
+ skip_token(&val, NULL, cfile); /* consume NEVER */
+ return(MAX_TIME);
+ }
+
+ /* This indicates 'local' time format. */
+ if (token == EPOCH) {
+ skip_token(&val, NULL, cfile); /* consume EPOCH */
+ token = peek_token(&val, NULL, cfile);
+
+ if (token != NUMBER) {
+ if (token != SEMI)
+ skip_token(&val, NULL, cfile);
+ parse_warn(cfile, "Seconds since epoch expected.");
+ return((TIME)0);
+ }
+
+ skip_token(&val, NULL, cfile); /* consume number */
+ guess = atoi(val);
+
+ return((TIME)guess);
+ }
+
+ if (token != NUMBER) {
+ if (token != SEMI)
+ skip_token(&val, NULL, cfile);
+ parse_warn(cfile, "numeric day of week expected.");
+ return((TIME)0);
+ }
+ skip_token(&val, NULL, cfile); /* consume day of week */
+ /* we are not using this for anything */
+
+ /* Year... */
+ token = peek_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ if (token != SEMI)
+ skip_token(&val, NULL, cfile);
+ parse_warn(cfile, "numeric year expected.");
+ return((TIME)0);
+ }
+ skip_token(&val, NULL, cfile); /* consume year */
+
+ /* Note: the following is not a Y2K bug - it's a Y1.9K bug. Until
+ somebody invents a time machine, I think we can safely disregard
+ it. This actually works around a stupid Y2K bug that was present
+ in a very early beta release of dhcpd. */
+ year = atoi(val);
+ if (year > 1900)
+ year -= 1900;
+
+ /* Slash separating year from month... */
+ token = peek_token(&val, NULL, cfile);
+ if (token != SLASH) {
+ if (token != SEMI)
+ skip_token(&val, NULL, cfile);
+ parse_warn(cfile,
+ "expected slash separating year from month.");
+ return((TIME)0);
+ }
+ skip_token(&val, NULL, cfile); /* consume SLASH */
+
+ /* Month... */
+ token = peek_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ if (token != SEMI)
+ skip_token(&val, NULL, cfile);
+ parse_warn(cfile, "numeric month expected.");
+ return((TIME)0);
+ }
+ skip_token(&val, NULL, cfile); /* consume month */
+ mon = atoi(val) - 1;
+
+ /* Slash separating month from day... */
+ token = peek_token(&val, NULL, cfile);
+ if (token != SLASH) {
+ if (token != SEMI)
+ skip_token(&val, NULL, cfile);
+ parse_warn(cfile,
+ "expected slash separating month from day.");
+ return((TIME)0);
+ }
+ skip_token(&val, NULL, cfile); /* consume SLASH */
+
+ /* Day of month... */
+ token = peek_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ if (token != SEMI)
+ skip_token(&val, NULL, cfile);
+ parse_warn(cfile, "numeric day of month expected.");
+ return((TIME)0);
+ }
+ skip_token(&val, NULL, cfile); /* consume day of month */
+ mday = atoi(val);
+
+ /* Hour... */
+ token = peek_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ if (token != SEMI)
+ skip_token(&val, NULL, cfile);
+ parse_warn(cfile, "numeric hour expected.");
+ return((TIME)0);
+ }
+ skip_token(&val, NULL, cfile); /* consume hour */
+ hour = atoi(val);
+
+ /* Colon separating hour from minute... */
+ token = peek_token(&val, NULL, cfile);
+ if (token != COLON) {
+ if (token != SEMI)
+ skip_token(&val, NULL, cfile);
+ parse_warn(cfile,
+ "expected colon separating hour from minute.");
+ return((TIME)0);
+ }
+ skip_token(&val, NULL, cfile); /* consume colon */
+
+ /* Minute... */
+ token = peek_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ if (token != SEMI)
+ skip_token(&val, NULL, cfile);
+ parse_warn(cfile, "numeric minute expected.");
+ return((TIME)0);
+ }
+ skip_token(&val, NULL, cfile); /* consume minute */
+ min = atoi(val);
+
+ /* Colon separating minute from second... */
+ token = peek_token(&val, NULL, cfile);
+ if (token != COLON) {
+ if (token != SEMI)
+ skip_token(&val, NULL, cfile);
+ parse_warn(cfile,
+ "expected colon separating minute from second.");
+ return((TIME)0);
+ }
+ skip_token(&val, NULL, cfile); /* consume colon */
+
+ /* Second... */
+ token = peek_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ if (token != SEMI)
+ skip_token(&val, NULL, cfile);
+ parse_warn(cfile, "numeric second expected.");
+ return((TIME)0);
+ }
+ skip_token(&val, NULL, cfile); /* consume second */
+ sec = atoi(val);
+
+ tzoff = 0;
+ token = peek_token(&val, NULL, cfile);
+ if (token == NUMBER) {
+ skip_token(&val, NULL, cfile); /* consume tzoff */
+ tzoff = atoi(val);
+ } else if (token != SEMI) {
+ skip_token(&val, NULL, cfile);
+ parse_warn(cfile,
+ "Time zone offset or semicolon expected.");
+ return((TIME)0);
+ }
+
+ /* Guess the time value... */
+ guess = ((((((365 * (year - 70) + /* Days in years since '70 */
+ (year - 69) / 4 + /* Leap days since '70 */
+ (mon /* Days in months this year */
+ ? months [mon - 1]
+ : 0) +
+ (mon > 1 && /* Leap day this year */
+ !((year - 72) & 3)) +
+ mday - 1) * 24) + /* Day of month */
+ hour) * 60) +
+ min) * 60) + sec + tzoff;
+
+ /* This guess could be wrong because of leap seconds or other
+ weirdness we don't know about that the system does. For
+ now, we're just going to accept the guess, but at some point
+ it might be nice to do a successive approximation here to
+ get an exact value. Even if the error is small, if the
+ server is restarted frequently (and thus the lease database
+ is reread), the error could accumulate into something
+ significant. */
+
+ return((TIME)guess);
+}
+
+/*
+ * Wrapper to consume the semicolon after the date
+ * :== date semi
+ */
+
+TIME
+parse_date(cfile)
+ struct parse *cfile;
+{
+ TIME guess;
+ guess = parse_date_core(cfile);
+
+ /* Make sure the date ends in a semicolon... */
+ if (!parse_semi(cfile))
+ return((TIME)0);
+ return(guess);
+}
+
+
+
+/*
+ * option-name :== IDENTIFIER |
+ IDENTIFIER . IDENTIFIER
+ */
+
+isc_result_t
+parse_option_name (cfile, allocate, known, opt)
+ struct parse *cfile;
+ int allocate;
+ int *known;
+ struct option **opt;
+{
+ const char *val;
+ enum dhcp_token token;
+ char *uname;
+ struct universe *universe;
+ struct option *option;
+ unsigned code;
+
+ if (opt == NULL)
+ return DHCP_R_INVALIDARG;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!is_identifier (token)) {
+ parse_warn (cfile,
+ "expecting identifier after option keyword.");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ return DHCP_R_BADPARSE;
+ }
+ uname = dmalloc (strlen (val) + 1, MDL);
+ if (!uname)
+ log_fatal ("no memory for uname information.");
+ strcpy (uname, val);
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == DOT) {
+ /* Go ahead and take the DOT token... */
+ skip_token(&val, (unsigned *)0, cfile);
+
+ /* The next token should be an identifier... */
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!is_identifier (token)) {
+ parse_warn (cfile, "expecting identifier after '.'");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ return DHCP_R_BADPARSE;
+ }
+
+ /* Look up the option name hash table for the specified
+ uname. */
+ universe = (struct universe *)0;
+ if (!universe_hash_lookup (&universe, universe_hash,
+ uname, 0, MDL)) {
+ parse_warn (cfile, "no option space named %s.", uname);
+ skip_to_semi (cfile);
+ return ISC_R_NOTFOUND;
+ }
+ } else {
+ /* Use the default hash table, which contains all the
+ standard dhcp option names. */
+ val = uname;
+ universe = &dhcp_universe;
+ }
+
+ /* Look up the actual option info... */
+ option_name_hash_lookup(opt, universe->name_hash, val, 0, MDL);
+ option = *opt;
+
+ /* If we didn't get an option structure, it's an undefined option. */
+ if (option) {
+ if (known)
+ *known = 1;
+ /* If the option name is of the form unknown-[decimal], use
+ * the trailing decimal value to find the option definition.
+ * If there is no definition, construct one. This is to
+ * support legacy use of unknown options in config files or
+ * lease databases.
+ */
+ } else if (strncasecmp(val, "unknown-", 8) == 0) {
+ code = atoi(val+8);
+
+ /* Option code 0 is always illegal for us, thanks
+ * to the option decoder.
+ */
+ if (code == 0 || code == universe->end) {
+ parse_warn(cfile, "Option codes 0 and %u are illegal "
+ "in the %s space.", universe->end,
+ universe->name);
+ skip_to_semi(cfile);
+ dfree(uname, MDL);
+ return ISC_R_FAILURE;
+ }
+
+ /* It's odd to think of unknown option codes as
+ * being known, but this means we know what the
+ * parsed name is talking about.
+ */
+ if (known)
+ *known = 1;
+
+ option_code_hash_lookup(opt, universe->code_hash,
+ &code, 0, MDL);
+ option = *opt;
+
+ /* If we did not find an option of that code,
+ * manufacture an unknown-xxx option definition.
+ * Its single reference will ensure that it is
+ * deleted once the option is recycled out of
+ * existence (by the parent).
+ */
+ if (option == NULL) {
+ option = new_option(val, MDL);
+ option->universe = universe;
+ option->code = code;
+ option->format = default_option_format;
+ option_reference(opt, option, MDL);
+ } else
+ log_info("option %s has been redefined as option %s. "
+ "Please update your configs if neccessary.",
+ val, option->name);
+ /* If we've been told to allocate, that means that this
+ * (might) be an option code definition, so we'll create
+ * an option structure and return it for the parent to
+ * decide.
+ */
+ } else if (allocate) {
+ option = new_option(val, MDL);
+ option -> universe = universe;
+ option_reference(opt, option, MDL);
+ } else {
+ parse_warn(cfile, "no option named %s in space %s",
+ val, universe->name);
+ skip_to_semi (cfile);
+ dfree(uname, MDL);
+ return ISC_R_NOTFOUND;
+ }
+
+ /* Free the initial identifier token. */
+ dfree (uname, MDL);
+ return ISC_R_SUCCESS;
+}
+
+/* IDENTIFIER [WIDTHS] SEMI
+ * WIDTHS ~= LENGTH WIDTH NUMBER
+ * CODE WIDTH NUMBER
+ */
+
+void parse_option_space_decl (cfile)
+ struct parse *cfile;
+{
+ int token;
+ const char *val;
+ struct universe **ua, *nu;
+ char *nu_name;
+ int tsize=1, lsize=1, hsize = 0;
+
+ skip_token(&val, (unsigned *)0, cfile); /* Discard the SPACE token,
+ which was checked by the
+ caller. */
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!is_identifier (token)) {
+ parse_warn (cfile, "expecting identifier.");
+ skip_to_semi (cfile);
+ return;
+ }
+ nu = new_universe (MDL);
+ if (!nu)
+ log_fatal ("No memory for new option space.");
+
+ /* Set up the server option universe... */
+ nu_name = dmalloc (strlen (val) + 1, MDL);
+ if (!nu_name)
+ log_fatal ("No memory for new option space name.");
+ strcpy (nu_name, val);
+ nu -> name = nu_name;
+
+ do {
+ token = next_token(&val, NULL, cfile);
+ switch(token) {
+ case SEMI:
+ break;
+
+ case CODE:
+ token = next_token(&val, NULL, cfile);
+ if (token != WIDTH) {
+ parse_warn(cfile, "expecting width token.");
+ goto bad;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "expecting number 1, 2, 4.");
+ goto bad;
+ }
+
+ tsize = atoi(val);
+
+
+ switch (tsize) {
+ case 1:
+ if (!hsize)
+ hsize = BYTE_NAME_HASH_SIZE;
+ break;
+ case 2:
+ if (!hsize)
+ hsize = WORD_NAME_HASH_SIZE;
+ break;
+ case 4:
+ if (!hsize)
+ hsize = QUAD_NAME_HASH_SIZE;
+ break;
+ default:
+ parse_warn(cfile, "invalid code width (%d), "
+ "expecting a 1, 2 or 4.",
+ tsize);
+ goto bad;
+ }
+ break;
+
+ case LENGTH:
+ token = next_token(&val, NULL, cfile);
+ if (token != WIDTH) {
+ parse_warn(cfile, "expecting width token.");
+ goto bad;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "expecting number 1 or 2.");
+ goto bad;
+ }
+
+ lsize = atoi(val);
+ if (lsize != 1 && lsize != 2) {
+ parse_warn(cfile, "invalid length width (%d) "
+ "expecting 1 or 2.", lsize);
+ goto bad;
+ }
+
+ break;
+
+ case HASH:
+ token = next_token(&val, NULL, cfile);
+ if (token != SIZE) {
+ parse_warn(cfile, "expecting size token.");
+ goto bad;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "expecting a 10base number");
+ goto bad;
+ }
+
+ /* (2^31)-1 is the highest Mersenne prime we should
+ * probably allow...
+ */
+ hsize = atoi(val);
+ if (hsize < 0 || hsize > 0x7FFFFFFF) {
+ parse_warn(cfile, "invalid hash length: %d",
+ hsize);
+ goto bad;
+ }
+
+ break;
+
+ default:
+ parse_warn(cfile, "Unexpected token.");
+ }
+ } while (token != SEMI);
+
+ if (!hsize)
+ hsize = DEFAULT_SPACE_HASH_SIZE;
+
+ nu -> lookup_func = lookup_hashed_option;
+ nu -> option_state_dereference = hashed_option_state_dereference;
+ nu -> foreach = hashed_option_space_foreach;
+ nu -> save_func = save_hashed_option;
+ nu -> delete_func = delete_hashed_option;
+ nu -> encapsulate = hashed_option_space_encapsulate;
+ nu -> decode = parse_option_buffer;
+ nu -> length_size = lsize;
+ nu -> tag_size = tsize;
+ switch(tsize) {
+ case 1:
+ nu->get_tag = getUChar;
+ nu->store_tag = putUChar;
+ break;
+ case 2:
+ nu->get_tag = getUShort;
+ nu->store_tag = putUShort;
+ break;
+ case 4:
+ nu->get_tag = getULong;
+ nu->store_tag = putULong;
+ break;
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ }
+ switch(lsize) {
+ case 0:
+ nu->get_length = NULL;
+ nu->store_length = NULL;
+ break;
+ case 1:
+ nu->get_length = getUChar;
+ nu->store_length = putUChar;
+ break;
+ case 2:
+ nu->get_length = getUShort;
+ nu->store_length = putUShort;
+ break;
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ }
+ nu -> index = universe_count++;
+ if (nu -> index >= universe_max) {
+ ua = dmalloc (universe_max * 2 * sizeof *ua, MDL);
+ if (!ua)
+ log_fatal ("No memory to expand option space array.");
+ memcpy (ua, universes, universe_max * sizeof *ua);
+ universe_max *= 2;
+ dfree (universes, MDL);
+ universes = ua;
+ }
+ universes [nu -> index] = nu;
+ if (!option_name_new_hash(&nu->name_hash, hsize, MDL) ||
+ !option_code_new_hash(&nu->code_hash, hsize, MDL))
+ log_fatal("Can't allocate %s option hash table.", nu->name);
+ universe_hash_add (universe_hash, nu -> name, 0, nu, MDL);
+ return;
+
+ bad:
+ dfree(nu_name, MDL);
+ dfree(nu, MDL);
+}
+
+/* This is faked up to look good right now. Ideally, this should do a
+ recursive parse and allow arbitrary data structure definitions, but for
+ now it just allows you to specify a single type, an array of single types,
+ a sequence of types, or an array of sequences of types.
+
+ ocd :== NUMBER EQUALS ocsd SEMI
+
+ ocsd :== ocsd_type |
+ ocsd_type_sequence |
+ ARRAY OF ocsd_simple_type_sequence
+
+ ocsd_type_sequence :== LBRACE ocsd_types RBRACE
+
+ ocsd_simple_type_sequence :== LBRACE ocsd_simple_types RBRACE
+
+ ocsd_types :== ocsd_type |
+ ocsd_types ocsd_type
+
+ ocsd_type :== ocsd_simple_type |
+ ARRAY OF ocsd_simple_type
+
+ ocsd_simple_types :== ocsd_simple_type |
+ ocsd_simple_types ocsd_simple_type
+
+ ocsd_simple_type :== BOOLEAN |
+ INTEGER NUMBER |
+ SIGNED INTEGER NUMBER |
+ UNSIGNED INTEGER NUMBER |
+ IP-ADDRESS |
+ TEXT |
+ STRING |
+ ENCAPSULATE identifier */
+
+int parse_option_code_definition (cfile, option)
+ struct parse *cfile;
+ struct option *option;
+{
+ const char *val;
+ enum dhcp_token token;
+ struct option *oldopt;
+ unsigned arrayp = 0;
+ int recordp = 0;
+ int no_more_in_record = 0;
+ char tokbuf [128];
+ unsigned tokix = 0;
+ char type;
+ int is_signed;
+ char *s;
+ int has_encapsulation = 0;
+ struct universe *encapsulated;
+
+ /* Parse the option code. */
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "expecting option code number.");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ option -> code = atoi (val);
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != EQUAL) {
+ parse_warn (cfile, "expecting \"=\"");
+ skip_to_semi (cfile);
+ return 0;
+ }
+
+ /* See if this is an array. */
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token == ARRAY) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != OF) {
+ parse_warn (cfile, "expecting \"of\".");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ arrayp = 1;
+ token = next_token (&val, (unsigned *)0, cfile);
+ }
+
+ if (token == LBRACE) {
+ recordp = 1;
+ token = next_token (&val, (unsigned *)0, cfile);
+ }
+
+ /* At this point we're expecting a data type. */
+ next_type:
+ if (has_encapsulation) {
+ parse_warn (cfile,
+ "encapsulate must always be the last item.");
+ skip_to_semi (cfile);
+ return 0;
+ }
+
+ switch (token) {
+ case ARRAY:
+ if (arrayp) {
+ parse_warn (cfile, "no nested arrays.");
+ skip_to_rbrace (cfile, recordp);
+ if (recordp)
+ skip_to_semi (cfile);
+ return 0;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != OF) {
+ parse_warn (cfile, "expecting \"of\".");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ arrayp = recordp + 1;
+ token = next_token (&val, (unsigned *)0, cfile);
+ if ((recordp) && (token == LBRACE)) {
+ parse_warn (cfile,
+ "only uniform array inside record.");
+ skip_to_rbrace (cfile, recordp + 1);
+ skip_to_semi (cfile);
+ return 0;
+ }
+ goto next_type;
+ case BOOLEAN:
+ type = 'f';
+ break;
+ case INTEGER:
+ is_signed = 1;
+ parse_integer:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "expecting number.");
+ skip_to_rbrace (cfile, recordp);
+ if (recordp)
+ skip_to_semi (cfile);
+ return 0;
+ }
+ switch (atoi (val)) {
+ case 8:
+ type = is_signed ? 'b' : 'B';
+ break;
+ case 16:
+ type = is_signed ? 's' : 'S';
+ break;
+ case 32:
+ type = is_signed ? 'l' : 'L';
+ break;
+ default:
+ parse_warn (cfile,
+ "%s bit precision is not supported.", val);
+ skip_to_rbrace (cfile, recordp);
+ if (recordp)
+ skip_to_semi (cfile);
+ return 0;
+ }
+ break;
+ case SIGNED:
+ is_signed = 1;
+ parse_signed:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != INTEGER) {
+ parse_warn (cfile, "expecting \"integer\" keyword.");
+ skip_to_rbrace (cfile, recordp);
+ if (recordp)
+ skip_to_semi (cfile);
+ return 0;
+ }
+ goto parse_integer;
+ case UNSIGNED:
+ is_signed = 0;
+ goto parse_signed;
+
+ case IP_ADDRESS:
+ type = 'I';
+ break;
+ case IP6_ADDRESS:
+ type = '6';
+ break;
+ case DOMAIN_NAME:
+ type = 'd';
+ goto no_arrays;
+ case DOMAIN_LIST:
+ /* Consume optional compression indicator. */
+ token = peek_token(&val, NULL, cfile);
+ if (token == COMPRESSED) {
+ skip_token(&val, NULL, cfile);
+ tokbuf[tokix++] = 'D';
+ type = 'c';
+ } else
+ type = 'D';
+ goto no_arrays;
+ case TEXT:
+ type = 't';
+ no_arrays:
+ if (arrayp) {
+ parse_warn (cfile, "arrays of text strings not %s",
+ "yet supported.");
+ skip_to_rbrace (cfile, recordp);
+ if (recordp)
+ skip_to_semi (cfile);
+ return 0;
+ }
+ no_more_in_record = 1;
+ break;
+ case STRING_TOKEN:
+ type = 'X';
+ goto no_arrays;
+
+ case ENCAPSULATE:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!is_identifier (token)) {
+ parse_warn (cfile,
+ "expecting option space identifier");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ encapsulated = NULL;
+ if (!universe_hash_lookup(&encapsulated, universe_hash,
+ val, strlen(val), MDL)) {
+ parse_warn(cfile, "unknown option space %s", val);
+ skip_to_semi (cfile);
+ return 0;
+ }
+ if (strlen (val) + tokix + 2 > sizeof (tokbuf))
+ goto toobig;
+ tokbuf [tokix++] = 'E';
+ strcpy (&tokbuf [tokix], val);
+ tokix += strlen (val);
+ type = '.';
+ has_encapsulation = 1;
+ break;
+
+ case ZEROLEN:
+ type = 'Z';
+ if (arrayp) {
+ parse_warn (cfile, "array incompatible with zerolen.");
+ skip_to_rbrace (cfile, recordp);
+ if (recordp)
+ skip_to_semi (cfile);
+ return 0;
+ }
+ no_more_in_record = 1;
+ break;
+
+ default:
+ parse_warn (cfile, "unknown data type %s", val);
+ skip_to_rbrace (cfile, recordp);
+ if (recordp)
+ skip_to_semi (cfile);
+ return 0;
+ }
+
+ if (tokix == sizeof tokbuf) {
+ toobig:
+ parse_warn (cfile, "too many types in record.");
+ skip_to_rbrace (cfile, recordp);
+ if (recordp)
+ skip_to_semi (cfile);
+ return 0;
+ }
+ tokbuf [tokix++] = type;
+
+ if (recordp) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (arrayp > recordp) {
+ if (tokix == sizeof tokbuf) {
+ parse_warn (cfile,
+ "too many types in record.");
+ skip_to_rbrace (cfile, 1);
+ skip_to_semi (cfile);
+ return 0;
+ }
+ arrayp = 0;
+ tokbuf[tokix++] = 'a';
+ }
+ if (token == COMMA) {
+ if (no_more_in_record) {
+ parse_warn (cfile,
+ "%s must be at end of record.",
+ type == 't' ? "text" : "string");
+ skip_to_rbrace (cfile, 1);
+ if (recordp)
+ skip_to_semi (cfile);
+ return 0;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ goto next_type;
+ }
+ if (token != RBRACE) {
+ parse_warn (cfile, "expecting right brace.");
+ skip_to_rbrace (cfile, 1);
+ if (recordp)
+ skip_to_semi (cfile);
+ return 0;
+ }
+ }
+ if (!parse_semi (cfile)) {
+ parse_warn (cfile, "semicolon expected.");
+ skip_to_semi (cfile);
+ if (recordp)
+ skip_to_semi (cfile);
+ return 0;
+ }
+ if (has_encapsulation && arrayp) {
+ parse_warn (cfile,
+ "Arrays of encapsulations don't make sense.");
+ return 0;
+ }
+ s = dmalloc(tokix + (arrayp ? 1 : 0) + 1, MDL);
+ if (s == NULL) {
+ log_fatal("no memory for option format.");
+ }
+ memcpy(s, tokbuf, tokix);
+ if (arrayp) {
+ s[tokix++] = (arrayp > recordp) ? 'a' : 'A';
+ }
+ s[tokix] = '\0';
+
+ option -> format = s;
+
+ oldopt = NULL;
+ option_code_hash_lookup(&oldopt, option->universe->code_hash,
+ &option->code, 0, MDL);
+ if (oldopt != NULL) {
+ /*
+ * XXX: This illegalizes a configuration syntax that was
+ * valid in 3.0.x, where multiple name->code mappings are
+ * given, but only one code->name mapping survives. It is
+ * unclear what can or should be done at this point, but it
+ * seems best to retain 3.0.x behaviour for upgrades to go
+ * smoothly.
+ *
+ option_name_hash_delete(option->universe->name_hash,
+ oldopt->name, 0, MDL);
+ */
+ option_code_hash_delete(option->universe->code_hash,
+ &oldopt->code, 0, MDL);
+
+ option_dereference(&oldopt, MDL);
+ }
+ option_code_hash_add(option->universe->code_hash, &option->code, 0,
+ option, MDL);
+ option_name_hash_add(option->universe->name_hash, option->name, 0,
+ option, MDL);
+ if (has_encapsulation) {
+ /* INSIST(tokbuf[0] == 'E'); */
+ /* INSIST(encapsulated != NULL); */
+ if (!option_code_hash_lookup(&encapsulated->enc_opt,
+ option->universe->code_hash,
+ &option->code, 0, MDL)) {
+ log_fatal("error finding encapsulated option (%s:%d)",
+ MDL);
+ }
+ }
+ return 1;
+}
+
+/*
+ * base64 :== NUMBER_OR_STRING
+ */
+
+int parse_base64 (data, cfile)
+ struct data_string *data;
+ struct parse *cfile;
+{
+ const char *val;
+ int i, j, k;
+ unsigned acc = 0;
+ static unsigned char
+ from64 [] = {64, 64, 64, 64, 64, 64, 64, 64, /* \"#$%&' */
+ 64, 64, 64, 62, 64, 64, 64, 63, /* ()*+,-./ */
+ 52, 53, 54, 55, 56, 57, 58, 59, /* 01234567 */
+ 60, 61, 64, 64, 64, 64, 64, 64, /* 89:;<=>? */
+ 64, 0, 1, 2, 3, 4, 5, 6, /* @ABCDEFG */
+ 7, 8, 9, 10, 11, 12, 13, 14, /* HIJKLMNO */
+ 15, 16, 17, 18, 19, 20, 21, 22, /* PQRSTUVW */
+ 23, 24, 25, 64, 64, 64, 64, 64, /* XYZ[\]^_ */
+ 64, 26, 27, 28, 29, 30, 31, 32, /* 'abcdefg */
+ 33, 34, 35, 36, 37, 38, 39, 40, /* hijklmno */
+ 41, 42, 43, 44, 45, 46, 47, 48, /* pqrstuvw */
+ 49, 50, 51, 64, 64, 64, 64, 64}; /* xyz{|}~ */
+ struct string_list *bufs = NULL,
+ *last = NULL,
+ *t;
+ int cc = 0;
+ int terminated = 0;
+ int valid_base64;
+
+ /* It's possible for a + or a / to cause a base64 quantity to be
+ tokenized into more than one token, so we have to parse them all
+ in before decoding. */
+ do {
+ unsigned l;
+
+ (void)next_token(&val, &l, cfile);
+ t = dmalloc(l + sizeof(*t), MDL);
+ if (t == NULL)
+ log_fatal("no memory for base64 buffer.");
+ memset(t, 0, (sizeof(*t)) - 1);
+ memcpy(t->string, val, l + 1);
+ cc += l;
+ if (last)
+ last->next = t;
+ else
+ bufs = t;
+ last = t;
+ (void)peek_token(&val, NULL, cfile);
+ valid_base64 = 1;
+ for (i = 0; val[i]; i++) {
+ /* Check to see if the character is valid. It
+ may be out of range or within the right range
+ but not used in the mapping */
+ if (((val[i] < ' ') || (val[i] > 'z')) ||
+ ((from64[val[i] - ' '] > 63) && (val[i] != '='))) {
+ valid_base64 = 0;
+ break; /* no need to continue for loop */
+ }
+ }
+ } while (valid_base64);
+
+ data->len = cc;
+ data->len = (data->len * 3) / 4;
+ if (!buffer_allocate(&data->buffer, data->len, MDL)) {
+ parse_warn (cfile, "can't allocate buffer for base64 data.");
+ data->len = 0;
+ data->data = NULL;
+ goto out;
+ }
+
+ j = k = 0;
+ for (t = bufs; t; t = t->next) {
+ for (i = 0; t->string[i]; i++) {
+ unsigned foo = t->string[i];
+ if (terminated && foo != '=') {
+ parse_warn(cfile,
+ "stuff after base64 '=' terminator: %s.",
+ &t->string[i]);
+ goto bad;
+ }
+ if ((foo < ' ') || (foo > 'z')) {
+ bad64:
+ parse_warn(cfile,
+ "invalid base64 character %d.",
+ t->string[i]);
+ bad:
+ data_string_forget(data, MDL);
+ goto out;
+ }
+ if (foo == '=')
+ terminated = 1;
+ else {
+ foo = from64[foo - ' '];
+ if (foo == 64)
+ goto bad64;
+ acc = (acc << 6) + foo;
+ switch (k % 4) {
+ case 0:
+ break;
+ case 1:
+ data->buffer->data[j++] = (acc >> 4);
+ acc = acc & 0x0f;
+ break;
+
+ case 2:
+ data->buffer->data[j++] = (acc >> 2);
+ acc = acc & 0x03;
+ break;
+ case 3:
+ data->buffer->data[j++] = acc;
+ acc = 0;
+ break;
+ }
+ }
+ k++;
+ }
+ }
+ if (k % 4) {
+ if (acc) {
+ parse_warn(cfile,
+ "partial base64 value left over: %d.",
+ acc);
+ }
+ }
+ data->len = j;
+ data->data = data->buffer->data;
+ out:
+ for (t = bufs; t; t = last) {
+ last = t->next;
+ dfree(t, MDL);
+ }
+ if (data->len)
+ return 1;
+ else
+ return 0;
+}
+
+
+/*
+ * colon-separated-hex-list :== NUMBER |
+ * NUMBER COLON colon-separated-hex-list
+ */
+
+int parse_cshl (data, cfile)
+ struct data_string *data;
+ struct parse *cfile;
+{
+ u_int8_t ibuf [128];
+ unsigned ilen = 0;
+ unsigned tlen = 0;
+ struct option_tag *sl = (struct option_tag *)0;
+ struct option_tag *next, **last = &sl;
+ enum dhcp_token token;
+ const char *val;
+ unsigned char *rvp;
+
+ do {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER && token != NUMBER_OR_NAME) {
+ parse_warn (cfile, "expecting hexadecimal number.");
+ skip_to_semi (cfile);
+ for (; sl; sl = next) {
+ next = sl -> next;
+ dfree (sl, MDL);
+ }
+ return 0;
+ }
+ if (ilen == sizeof ibuf) {
+ next = (struct option_tag *)
+ dmalloc (ilen - 1 +
+ sizeof (struct option_tag), MDL);
+ if (!next)
+ log_fatal ("no memory for string list.");
+ memcpy (next -> data, ibuf, ilen);
+ *last = next;
+ last = &next -> next;
+ tlen += ilen;
+ ilen = 0;
+ }
+ convert_num (cfile, &ibuf [ilen++], val, 16, 8);
+
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token != COLON)
+ break;
+ skip_token(&val, (unsigned *)0, cfile);
+ } while (1);
+
+ if (!buffer_allocate (&data -> buffer, tlen + ilen, MDL))
+ log_fatal ("no memory to store octet data.");
+ data -> data = &data -> buffer -> data [0];
+ data -> len = tlen + ilen;
+ data -> terminated = 0;
+
+ rvp = &data -> buffer -> data [0];
+ while (sl) {
+ next = sl -> next;
+ memcpy (rvp, sl -> data, sizeof ibuf);
+ rvp += sizeof ibuf;
+ dfree (sl, MDL);
+ sl = next;
+ }
+
+ memcpy (rvp, ibuf, ilen);
+ return 1;
+}
+
+/*
+ * executable-statements :== executable-statement executable-statements |
+ * executable-statement
+ *
+ * executable-statement :==
+ * IF if-statement |
+ * ADD class-name SEMI |
+ * BREAK SEMI |
+ * OPTION option-parameter SEMI |
+ * SUPERSEDE option-parameter SEMI |
+ * PREPEND option-parameter SEMI |
+ * APPEND option-parameter SEMI
+ */
+
+int parse_executable_statements (statements, cfile, lose, case_context)
+ struct executable_statement **statements;
+ struct parse *cfile;
+ int *lose;
+ enum expression_context case_context;
+{
+ struct executable_statement **next;
+
+ next = statements;
+ while (parse_executable_statement (next, cfile, lose, case_context))
+ next = &((*next) -> next);
+ if (!*lose)
+ return 1;
+ return 0;
+}
+
+int parse_executable_statement (result, cfile, lose, case_context)
+ struct executable_statement **result;
+ struct parse *cfile;
+ int *lose;
+ enum expression_context case_context;
+{
+#if defined(ENABLE_EXECUTE)
+ unsigned len;
+ struct expression **ep;
+#endif
+ enum dhcp_token token;
+ const char *val;
+ struct class *cta;
+ struct option *option=NULL;
+ struct option_cache *cache;
+ int known;
+ int flag;
+ int i;
+ struct dns_zone *zone;
+ isc_result_t status;
+ char *s;
+
+ token = peek_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case DB_TIME_FORMAT:
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token == DEFAULT) {
+ db_time_format = DEFAULT_TIME_FORMAT;
+ } else if (token == LOCAL) {
+ db_time_format = LOCAL_TIME_FORMAT;
+ } else {
+ parse_warn(cfile, "Expecting 'local' or 'default'.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ *lose = 1;
+ return 0;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "Expecting a semicolon.");
+ *lose = 1;
+ return 0;
+ }
+
+ /* We're done here. */
+ return 1;
+
+ case IF:
+ skip_token(&val, (unsigned *)0, cfile);
+ return parse_if_statement (result, cfile, lose);
+
+ case TOKEN_ADD:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING) {
+ parse_warn (cfile, "expecting class name.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ return 0;
+ }
+ cta = (struct class *)0;
+ status = find_class (&cta, val, MDL);
+ if (status != ISC_R_SUCCESS) {
+ parse_warn (cfile, "class %s: %s",
+ val, isc_result_totext (status));
+ skip_to_semi (cfile);
+ *lose = 1;
+ return 0;
+ }
+ if (!parse_semi (cfile)) {
+ *lose = 1;
+ return 0;
+ }
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for new statement.");
+ (*result) -> op = add_statement;
+ (*result) -> data.add = cta;
+ break;
+
+ case BREAK:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!parse_semi (cfile)) {
+ *lose = 1;
+ return 0;
+ }
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for new statement.");
+ (*result) -> op = break_statement;
+ break;
+
+ case SEND:
+ skip_token(&val, (unsigned *)0, cfile);
+ known = 0;
+ status = parse_option_name (cfile, 0, &known, &option);
+ if (status != ISC_R_SUCCESS || option == NULL) {
+ *lose = 1;
+ return 0;
+ }
+ status = parse_option_statement(result, cfile, 1, option,
+ send_option_statement);
+ option_dereference(&option, MDL);
+ return status;
+
+ case SUPERSEDE:
+ case OPTION:
+ skip_token(&val, (unsigned *)0, cfile);
+ known = 0;
+ status = parse_option_name (cfile, 0, &known, &option);
+ if (status != ISC_R_SUCCESS || option == NULL) {
+ *lose = 1;
+ return 0;
+ }
+ status = parse_option_statement(result, cfile, 1, option,
+ supersede_option_statement);
+ option_dereference(&option, MDL);
+ return status;
+
+ case ALLOW:
+ flag = 1;
+ goto pad;
+ case DENY:
+ flag = 0;
+ goto pad;
+ case IGNORE:
+ flag = 2;
+ pad:
+ skip_token(&val, (unsigned *)0, cfile);
+ cache = (struct option_cache *)0;
+ if (!parse_allow_deny (&cache, cfile, flag))
+ return 0;
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for new statement.");
+ (*result) -> op = supersede_option_statement;
+ (*result) -> data.option = cache;
+ break;
+
+ case DEFAULT:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == COLON)
+ goto switch_default;
+ known = 0;
+ status = parse_option_name (cfile, 0, &known, &option);
+ if (status != ISC_R_SUCCESS || option == NULL) {
+ *lose = 1;
+ return 0;
+ }
+ status = parse_option_statement(result, cfile, 1, option,
+ default_option_statement);
+ option_dereference(&option, MDL);
+ return status;
+
+ case PREPEND:
+ skip_token(&val, (unsigned *)0, cfile);
+ known = 0;
+ status = parse_option_name (cfile, 0, &known, &option);
+ if (status != ISC_R_SUCCESS || option == NULL) {
+ *lose = 1;
+ return 0;
+ }
+ status = parse_option_statement(result, cfile, 1, option,
+ prepend_option_statement);
+ option_dereference(&option, MDL);
+ return status;
+
+ case APPEND:
+ skip_token(&val, (unsigned *)0, cfile);
+ known = 0;
+ status = parse_option_name (cfile, 0, &known, &option);
+ if (status != ISC_R_SUCCESS || option == NULL) {
+ *lose = 1;
+ return 0;
+ }
+ status = parse_option_statement(result, cfile, 1, option,
+ append_option_statement);
+ option_dereference(&option, MDL);
+ return status;
+
+ case ON:
+ skip_token(&val, (unsigned *)0, cfile);
+ return parse_on_statement (result, cfile, lose);
+
+ case SWITCH:
+ skip_token(&val, (unsigned *)0, cfile);
+ return parse_switch_statement (result, cfile, lose);
+
+ case CASE:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (case_context == context_any) {
+ parse_warn (cfile,
+ "case statement in inappropriate scope.");
+ *lose = 1;
+ skip_to_semi (cfile);
+ return 0;
+ }
+ return parse_case_statement (result,
+ cfile, lose, case_context);
+
+ switch_default:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (case_context == context_any) {
+ parse_warn (cfile, "switch default statement in %s",
+ "inappropriate scope.");
+
+ *lose = 1;
+ return 0;
+ } else {
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for default statement.");
+ (*result) -> op = default_statement;
+ return 1;
+ }
+
+ case DEFINE:
+ case TOKEN_SET:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (token == DEFINE)
+ flag = 1;
+ else
+ flag = 0;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NAME && token != NUMBER_OR_NAME) {
+ parse_warn (cfile,
+ "%s can't be a variable name", val);
+ badset:
+ skip_to_semi (cfile);
+ *lose = 1;
+ return 0;
+ }
+
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for set statement.");
+ (*result) -> op = flag ? define_statement : set_statement;
+ (*result) -> data.set.name = dmalloc (strlen (val) + 1, MDL);
+ if (!(*result)->data.set.name)
+ log_fatal ("can't allocate variable name");
+ strcpy ((*result) -> data.set.name, val);
+ token = next_token (&val, (unsigned *)0, cfile);
+
+ if (token == LPAREN) {
+ struct string_list *head, *cur, *new;
+ struct expression *expr;
+ head = cur = (struct string_list *)0;
+ do {
+ token = next_token (&val,
+ (unsigned *)0, cfile);
+ if (token == RPAREN)
+ break;
+ if (token != NAME && token != NUMBER_OR_NAME) {
+ parse_warn (cfile,
+ "expecting argument name");
+ skip_to_rbrace (cfile, 0);
+ *lose = 1;
+ executable_statement_dereference
+ (result, MDL);
+ return 0;
+ }
+ new = ((struct string_list *)
+ dmalloc (sizeof (struct string_list) +
+ strlen (val), MDL));
+ if (!new)
+ log_fatal ("can't allocate string.");
+ memset (new, 0, sizeof *new);
+ strcpy (new -> string, val);
+ if (cur) {
+ cur -> next = new;
+ cur = new;
+ } else {
+ head = cur = new;
+ }
+ token = next_token (&val,
+ (unsigned *)0, cfile);
+ } while (token == COMMA);
+
+ if (token != RPAREN) {
+ parse_warn (cfile, "expecting right paren.");
+ badx:
+ skip_to_semi (cfile);
+ *lose = 1;
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LBRACE) {
+ parse_warn (cfile, "expecting left brace.");
+ goto badx;
+ }
+
+ expr = (struct expression *)0;
+ if (!(expression_allocate (&expr, MDL)))
+ log_fatal ("can't allocate expression.");
+ expr -> op = expr_function;
+ if (!fundef_allocate (&expr -> data.func, MDL))
+ log_fatal ("can't allocate fundef.");
+ expr -> data.func -> args = head;
+ (*result) -> data.set.expr = expr;
+
+ if (!(parse_executable_statements
+ (&expr -> data.func -> statements, cfile, lose,
+ case_context))) {
+ if (*lose)
+ goto badx;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RBRACE) {
+ parse_warn (cfile, "expecting rigt brace.");
+ goto badx;
+ }
+ } else {
+ if (token != EQUAL) {
+ parse_warn (cfile,
+ "expecting '=' in %s statement.",
+ flag ? "define" : "set");
+ goto badset;
+ }
+
+ if (!parse_expression (&(*result) -> data.set.expr,
+ cfile, lose, context_any,
+ (struct expression **)0,
+ expr_none)) {
+ if (!*lose)
+ parse_warn (cfile,
+ "expecting expression.");
+ else
+ *lose = 1;
+ skip_to_semi (cfile);
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ if (!parse_semi (cfile)) {
+ *lose = 1;
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ }
+ break;
+
+ case UNSET:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NAME && token != NUMBER_OR_NAME) {
+ parse_warn (cfile,
+ "%s can't be a variable name", val);
+ skip_to_semi (cfile);
+ *lose = 1;
+ return 0;
+ }
+
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for set statement.");
+ (*result) -> op = unset_statement;
+ (*result) -> data.unset = dmalloc (strlen (val) + 1, MDL);
+ if (!(*result)->data.unset)
+ log_fatal ("can't allocate variable name");
+ strcpy ((*result) -> data.unset, val);
+ if (!parse_semi (cfile)) {
+ *lose = 1;
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ break;
+
+ case EVAL:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for eval statement.");
+ (*result) -> op = eval_statement;
+
+ if (!parse_expression (&(*result) -> data.eval,
+ cfile, lose, context_data, /* XXX */
+ (struct expression **)0, expr_none)) {
+ if (!*lose)
+ parse_warn (cfile,
+ "expecting data expression.");
+ else
+ *lose = 1;
+ skip_to_semi (cfile);
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ if (!parse_semi (cfile)) {
+ *lose = 1;
+ executable_statement_dereference (result, MDL);
+ }
+ break;
+
+ case EXECUTE:
+#ifdef ENABLE_EXECUTE
+ skip_token(&val, NULL, cfile);
+
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for execute statement.");
+ (*result)->op = execute_statement;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN) {
+ parse_warn(cfile, "left parenthesis expected.");
+ skip_to_semi(cfile);
+ *lose = 1;
+ return 0;
+ }
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ parse_warn(cfile, "Expecting a quoted string.");
+ skip_to_semi(cfile);
+ *lose = 1;
+ return 0;
+ }
+
+ (*result)->data.execute.command = dmalloc(len + 1, MDL);
+ if ((*result)->data.execute.command == NULL)
+ log_fatal("can't allocate command name");
+ strcpy((*result)->data.execute.command, val);
+
+ ep = &(*result)->data.execute.arglist;
+ (*result)->data.execute.argc = 0;
+
+ while((token = next_token(&val, NULL, cfile)) == COMMA) {
+ if (!expression_allocate(ep, MDL))
+ log_fatal ("can't allocate expression");
+
+ if (!parse_data_expression (&(*ep) -> data.arg.val,
+ cfile, lose)) {
+ if (!*lose) {
+ parse_warn (cfile,
+ "expecting expression.");
+ *lose = 1;
+ }
+ skip_to_semi(cfile);
+ *lose = 1;
+ return 0;
+ }
+ ep = &(*ep)->data.arg.next;
+ (*result)->data.execute.argc++;
+ }
+
+ if (token != RPAREN) {
+ parse_warn(cfile, "right parenthesis expected.");
+ skip_to_semi(cfile);
+ *lose = 1;
+ return 0;
+ }
+
+ if (!parse_semi (cfile)) {
+ *lose = 1;
+ executable_statement_dereference (result, MDL);
+ }
+#else /* ! ENABLE_EXECUTE */
+ parse_warn(cfile, "define ENABLE_EXECUTE in site.h to "
+ "enable execute(); expressions.");
+ skip_to_semi(cfile);
+ *lose = 1;
+ return 0;
+#endif /* ENABLE_EXECUTE */
+ break;
+
+ case RETURN:
+ skip_token(&val, (unsigned *)0, cfile);
+
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for return statement.");
+ (*result) -> op = return_statement;
+
+ if (!parse_expression (&(*result) -> data.retval,
+ cfile, lose, context_data,
+ (struct expression **)0, expr_none)) {
+ if (!*lose)
+ parse_warn (cfile,
+ "expecting data expression.");
+ else
+ *lose = 1;
+ skip_to_semi (cfile);
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ if (!parse_semi (cfile)) {
+ *lose = 1;
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ break;
+
+ case LOG:
+ skip_token(&val, (unsigned *)0, cfile);
+
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for log statement.");
+ (*result) -> op = log_statement;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN) {
+ parse_warn (cfile, "left parenthesis expected.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ return 0;
+ }
+
+ token = peek_token (&val, (unsigned *)0, cfile);
+ i = 1;
+ if (token == FATAL) {
+ (*result) -> data.log.priority = log_priority_fatal;
+ } else if (token == ERROR) {
+ (*result) -> data.log.priority = log_priority_error;
+ } else if (token == TOKEN_DEBUG) {
+ (*result) -> data.log.priority = log_priority_debug;
+ } else if (token == INFO) {
+ (*result) -> data.log.priority = log_priority_info;
+ } else {
+ (*result) -> data.log.priority = log_priority_debug;
+ i = 0;
+ }
+ if (i) {
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA) {
+ parse_warn (cfile, "comma expected.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ return 0;
+ }
+ }
+
+ if (!(parse_data_expression
+ (&(*result) -> data.log.expr, cfile, lose))) {
+ skip_to_semi (cfile);
+ *lose = 1;
+ return 0;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RPAREN) {
+ parse_warn (cfile, "right parenthesis expected.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ return 0;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != SEMI) {
+ parse_warn (cfile, "semicolon expected.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ return 0;
+ }
+ break;
+
+ /* Not really a statement, but we parse it here anyway
+ because it's appropriate for all DHCP agents with
+ parsers. */
+ case ZONE:
+ skip_token(&val, (unsigned *)0, cfile);
+ zone = (struct dns_zone *)0;
+ if (!dns_zone_allocate (&zone, MDL))
+ log_fatal ("no memory for new zone.");
+ zone -> name = parse_host_name (cfile);
+ if (!zone -> name) {
+ parse_warn (cfile, "expecting hostname.");
+ badzone:
+ *lose = 1;
+ skip_to_semi (cfile);
+ dns_zone_dereference (&zone, MDL);
+ return 0;
+ }
+ i = strlen (zone -> name);
+ if (zone -> name [i - 1] != '.') {
+ s = dmalloc ((unsigned)i + 2, MDL);
+ if (!s) {
+ parse_warn (cfile, "no trailing '.' on zone");
+ goto badzone;
+ }
+ strcpy (s, zone -> name);
+ s [i] = '.';
+ s [i + 1] = 0;
+ dfree (zone -> name, MDL);
+ zone -> name = s;
+ }
+ if (!parse_zone (zone, cfile))
+ goto badzone;
+ status = enter_dns_zone (zone);
+ if (status != ISC_R_SUCCESS) {
+ parse_warn (cfile, "dns zone key %s: %s",
+ zone -> name, isc_result_totext (status));
+ dns_zone_dereference (&zone, MDL);
+ return 0;
+ }
+ dns_zone_dereference (&zone, MDL);
+ return 1;
+
+ /* Also not really a statement, but same idea as above. */
+ case KEY:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!parse_key (cfile)) {
+ *lose = 1;
+ return 0;
+ }
+ return 1;
+
+ default:
+ if (config_universe && is_identifier (token)) {
+ option = (struct option *)0;
+ option_name_hash_lookup(&option,
+ config_universe->name_hash,
+ val, 0, MDL);
+ if (option) {
+ skip_token(&val, (unsigned *)0, cfile);
+ status = parse_option_statement
+ (result, cfile, 1, option,
+ supersede_option_statement);
+ option_dereference(&option, MDL);
+ return status;
+ }
+ }
+
+ if (token == NUMBER_OR_NAME || token == NAME) {
+ /* This is rather ugly. Since function calls are
+ data expressions, fake up an eval statement. */
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for eval statement.");
+ (*result) -> op = eval_statement;
+
+ if (!parse_expression (&(*result) -> data.eval,
+ cfile, lose, context_data,
+ (struct expression **)0,
+ expr_none)) {
+ if (!*lose)
+ parse_warn (cfile, "expecting "
+ "function call.");
+ else
+ *lose = 1;
+ skip_to_semi (cfile);
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ if (!parse_semi (cfile)) {
+ *lose = 1;
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ break;
+ }
+
+ *lose = 0;
+ return 0;
+ }
+
+ return 1;
+}
+
+/* zone-statements :== zone-statement |
+ zone-statement zone-statements
+ zone-statement :==
+ PRIMARY ip-addresses SEMI |
+ SECONDARY ip-addresses SEMI |
+ PRIMARY6 ip-address6 SEMI |
+ SECONDARY6 ip-address6 SEMI |
+ key-reference SEMI
+ ip-addresses :== ip-addr-or-hostname |
+ ip-addr-or-hostname COMMA ip-addresses
+ key-reference :== KEY STRING |
+ KEY identifier */
+
+int parse_zone (struct dns_zone *zone, struct parse *cfile)
+{
+ int token;
+ const char *val;
+ char *key_name;
+ struct option_cache *oc;
+ int done = 0;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LBRACE) {
+ parse_warn (cfile, "expecting left brace");
+ return 0;
+ }
+
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case PRIMARY:
+ if (zone -> primary) {
+ parse_warn (cfile,
+ "more than one primary.");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ if (!option_cache_allocate (&zone -> primary, MDL))
+ log_fatal ("can't allocate primary option cache.");
+ oc = zone -> primary;
+ goto consemup;
+
+ case SECONDARY:
+ if (zone -> secondary) {
+ parse_warn (cfile, "more than one secondary.");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ if (!option_cache_allocate (&zone -> secondary, MDL))
+ log_fatal ("can't allocate secondary.");
+ oc = zone -> secondary;
+ consemup:
+ skip_token(&val, (unsigned *)0, cfile);
+ do {
+ struct expression *expr = (struct expression *)0;
+ if (!parse_ip_addr_or_hostname (&expr, cfile, 0)) {
+ parse_warn (cfile,
+ "expecting IP addr or hostname.");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ if (oc -> expression) {
+ struct expression *old =
+ (struct expression *)0;
+ expression_reference (&old,
+ oc -> expression,
+ MDL);
+ expression_dereference (&oc -> expression,
+ MDL);
+ if (!make_concat (&oc -> expression,
+ old, expr))
+ log_fatal ("no memory for concat.");
+ expression_dereference (&expr, MDL);
+ expression_dereference (&old, MDL);
+ } else {
+ expression_reference (&oc -> expression,
+ expr, MDL);
+ expression_dereference (&expr, MDL);
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ } while (token == COMMA);
+ if (token != SEMI) {
+ parse_warn (cfile, "expecting semicolon.");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ break;
+
+ case PRIMARY6:
+ if (zone->primary6) {
+ parse_warn(cfile, "more than one primary6.");
+ skip_to_semi(cfile);
+ return (0);
+ }
+ if (!option_cache_allocate (&zone->primary6, MDL))
+ log_fatal("can't allocate primary6 option cache.");
+ oc = zone->primary6;
+ goto consemup6;
+
+ case SECONDARY6:
+ if (zone->secondary6) {
+ parse_warn(cfile, "more than one secondary6.");
+ skip_to_semi(cfile);
+ return (0);
+ }
+ if (!option_cache_allocate (&zone->secondary6, MDL))
+ log_fatal("can't allocate secondary6 "
+ "option cache.");
+ oc = zone->secondary6;
+ consemup6:
+ skip_token(&val, NULL, cfile);
+ do {
+ struct expression *expr = NULL;
+ if (parse_ip6_addr_expr(&expr, cfile) == 0) {
+ parse_warn(cfile, "expecting IPv6 addr.");
+ skip_to_semi(cfile);
+ return (0);
+ }
+ if (oc->expression) {
+ struct expression *old = NULL;
+ expression_reference(&old, oc->expression,
+ MDL);
+ expression_dereference(&oc->expression,
+ MDL);
+ if (!make_concat(&oc->expression,
+ old, expr))
+ log_fatal("no memory for concat.");
+ expression_dereference(&expr, MDL);
+ expression_dereference(&old, MDL);
+ } else {
+ expression_reference(&oc->expression,
+ expr, MDL);
+ expression_dereference(&expr, MDL);
+ }
+ token = next_token(&val, NULL, cfile);
+ } while (token == COMMA);
+ if (token != SEMI) {
+ parse_warn(cfile, "expecting semicolon.");
+ skip_to_semi(cfile);
+ return (0);
+ }
+ break;
+
+ case KEY:
+ skip_token(&val, NULL, cfile);
+ token = peek_token(&val, NULL, cfile);
+ if (token == STRING) {
+ skip_token(&val, NULL, cfile);
+ key_name = NULL;
+ } else {
+ key_name = parse_host_name(cfile);
+ if (!key_name) {
+ parse_warn(cfile, "expecting key name.");
+ skip_to_semi(cfile);
+ return (0);
+ }
+ val = key_name;
+ }
+ if (zone->key) {
+ log_fatal("Multiple key definitions for zone %s.",
+ zone->name);
+ }
+ if (omapi_auth_key_lookup_name(&zone->key, val) !=
+ ISC_R_SUCCESS)
+ parse_warn(cfile, "unknown key %s", val);
+ if (key_name)
+ dfree(key_name, MDL);
+ if (!parse_semi(cfile))
+ return (0);
+ break;
+
+ default:
+ done = 1;
+ break;
+ }
+ } while (!done);
+
+ token = next_token(&val, NULL, cfile);
+ if (token != RBRACE) {
+ parse_warn(cfile, "expecting right brace.");
+ return (0);
+ }
+ return (1);
+}
+
+/* key-statements :== key-statement |
+ key-statement key-statements
+ key-statement :==
+ ALGORITHM host-name SEMI |
+ secret-definition SEMI
+ secret-definition :== SECRET base64val |
+ SECRET STRING */
+
+int parse_key (struct parse *cfile)
+{
+ int token;
+ const char *val;
+ int done = 0;
+ struct auth_key *key;
+ struct data_string ds;
+ isc_result_t status;
+ char *s;
+
+ key = (struct auth_key *)0;
+ if (omapi_auth_key_new (&key, MDL) != ISC_R_SUCCESS)
+ log_fatal ("no memory for key");
+
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == STRING) {
+ skip_token(&val, (unsigned *)0, cfile);
+ key -> name = dmalloc (strlen (val) + 1, MDL);
+ if (!key -> name)
+ log_fatal ("no memory for key name.");
+ strcpy (key -> name, val);
+
+ } else {
+ key -> name = parse_host_name (cfile);
+ if (!key -> name) {
+ parse_warn (cfile, "expecting key name.");
+ skip_to_semi (cfile);
+ goto bad;
+ }
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LBRACE) {
+ parse_warn (cfile, "expecting left brace");
+ goto bad;
+ }
+
+ do {
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case ALGORITHM:
+ if (key -> algorithm) {
+ parse_warn (cfile,
+ "key %s: too many algorithms",
+ key -> name);
+ goto rbad;
+ }
+ key -> algorithm = parse_host_name (cfile);
+ if (!key -> algorithm) {
+ parse_warn (cfile,
+ "expecting key algorithm name.");
+ goto rbad;
+ }
+ if (!parse_semi (cfile))
+ goto rbad;
+ /* If the algorithm name isn't an FQDN, tack on
+ the .SIG-ALG.REG.NET. domain. */
+ s = strrchr (key -> algorithm, '.');
+ if (!s) {
+ static char add [] = ".SIG-ALG.REG.INT.";
+ s = dmalloc (strlen (key -> algorithm) +
+ sizeof (add), MDL);
+ if (!s) {
+ log_error ("no memory for key %s.",
+ "algorithm");
+ goto rbad;
+ }
+ strcpy (s, key -> algorithm);
+ strcat (s, add);
+ dfree (key -> algorithm, MDL);
+ key -> algorithm = s;
+ } else if (s [1]) {
+ /* If there is no trailing '.', hack one in. */
+ s = dmalloc (strlen (key -> algorithm) + 2, MDL);
+ if (!s) {
+ log_error ("no memory for key %s.",
+ key -> algorithm);
+ goto rbad;
+ }
+ strcpy (s, key -> algorithm);
+ strcat (s, ".");
+ dfree (key -> algorithm, MDL);
+ key -> algorithm = s;
+ }
+ break;
+
+ case SECRET:
+ if (key -> key) {
+ parse_warn (cfile, "key %s: too many secrets",
+ key -> name);
+ goto rbad;
+ }
+
+ memset (&ds, 0, sizeof(ds));
+ if (!parse_base64 (&ds, cfile))
+ goto rbad;
+ status = omapi_data_string_new (&key -> key, ds.len,
+ MDL);
+ if (status != ISC_R_SUCCESS)
+ goto rbad;
+ memcpy (key -> key -> value,
+ ds.buffer -> data, ds.len);
+ data_string_forget (&ds, MDL);
+
+ if (!parse_semi (cfile))
+ goto rbad;
+ break;
+
+ default:
+ done = 1;
+ break;
+ }
+ } while (!done);
+ if (token != RBRACE) {
+ parse_warn (cfile, "expecting right brace.");
+ goto rbad;
+ }
+ /* Allow the BIND 8 syntax, which has a semicolon after each
+ closing brace. */
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == SEMI) {
+ skip_token(&val, (unsigned *)0, cfile);
+ }
+
+ /* Remember the key. */
+ status = omapi_auth_key_enter (key);
+ if (status != ISC_R_SUCCESS) {
+ parse_warn (cfile, "tsig key %s: %s",
+ key -> name, isc_result_totext (status));
+ goto bad;
+ }
+ omapi_auth_key_dereference (&key, MDL);
+ return 1;
+
+ rbad:
+ skip_to_rbrace (cfile, 1);
+ bad:
+ omapi_auth_key_dereference (&key, MDL);
+ return 0;
+}
+
+/*
+ * on-statement :== event-types LBRACE executable-statements RBRACE
+ * event-types :== event-type OR event-types |
+ * event-type
+ * event-type :== EXPIRY | COMMIT | RELEASE
+ */
+
+int parse_on_statement (result, cfile, lose)
+ struct executable_statement **result;
+ struct parse *cfile;
+ int *lose;
+{
+ enum dhcp_token token;
+ const char *val;
+
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for new statement.");
+ (*result) -> op = on_statement;
+
+ do {
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case EXPIRY:
+ (*result) -> data.on.evtypes |= ON_EXPIRY;
+ break;
+
+ case COMMIT:
+ (*result) -> data.on.evtypes |= ON_COMMIT;
+ break;
+
+ case RELEASE:
+ (*result) -> data.on.evtypes |= ON_RELEASE;
+ break;
+
+ case TRANSMISSION:
+ (*result) -> data.on.evtypes |= ON_TRANSMISSION;
+ break;
+
+ default:
+ parse_warn (cfile, "expecting a lease event type");
+ skip_to_semi (cfile);
+ *lose = 1;
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ } while (token == OR);
+
+ /* Semicolon means no statements. */
+ if (token == SEMI)
+ return 1;
+
+ if (token != LBRACE) {
+ parse_warn (cfile, "left brace expected.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ if (!parse_executable_statements (&(*result) -> data.on.statements,
+ cfile, lose, context_any)) {
+ if (*lose) {
+ /* Try to even things up. */
+ do {
+ token = next_token (&val,
+ (unsigned *)0, cfile);
+ } while (token != END_OF_FILE && token != RBRACE);
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RBRACE) {
+ parse_warn (cfile, "right brace expected.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * switch-statement :== LPAREN expr RPAREN LBRACE executable-statements RBRACE
+ *
+ */
+
+int parse_switch_statement (result, cfile, lose)
+ struct executable_statement **result;
+ struct parse *cfile;
+ int *lose;
+{
+ enum dhcp_token token;
+ const char *val;
+
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for new statement.");
+ (*result) -> op = switch_statement;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN) {
+ parse_warn (cfile, "expecting left brace.");
+ pfui:
+ *lose = 1;
+ skip_to_semi (cfile);
+ gnorf:
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+
+ if (!parse_expression (&(*result) -> data.s_switch.expr,
+ cfile, lose, context_data_or_numeric,
+ (struct expression **)0, expr_none)) {
+ if (!*lose) {
+ parse_warn (cfile,
+ "expecting data or numeric expression.");
+ goto pfui;
+ }
+ goto gnorf;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RPAREN) {
+ parse_warn (cfile, "right paren expected.");
+ goto pfui;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LBRACE) {
+ parse_warn (cfile, "left brace expected.");
+ goto pfui;
+ }
+ if (!(parse_executable_statements
+ (&(*result) -> data.s_switch.statements, cfile, lose,
+ (is_data_expression ((*result) -> data.s_switch.expr)
+ ? context_data : context_numeric)))) {
+ if (*lose) {
+ skip_to_rbrace (cfile, 1);
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RBRACE) {
+ parse_warn (cfile, "right brace expected.");
+ goto pfui;
+ }
+ return 1;
+}
+
+/*
+ * case-statement :== CASE expr COLON
+ *
+ */
+
+int parse_case_statement (result, cfile, lose, case_context)
+ struct executable_statement **result;
+ struct parse *cfile;
+ int *lose;
+ enum expression_context case_context;
+{
+ enum dhcp_token token;
+ const char *val;
+
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for new statement.");
+ (*result) -> op = case_statement;
+
+ if (!parse_expression (&(*result) -> data.c_case,
+ cfile, lose, case_context,
+ (struct expression **)0, expr_none))
+ {
+ if (!*lose) {
+ parse_warn (cfile, "expecting %s expression.",
+ (case_context == context_data
+ ? "data" : "numeric"));
+ }
+ pfui:
+ *lose = 1;
+ skip_to_semi (cfile);
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COLON) {
+ parse_warn (cfile, "colon expected.");
+ goto pfui;
+ }
+ return 1;
+}
+
+/*
+ * if-statement :== boolean-expression LBRACE executable-statements RBRACE
+ * else-statement
+ *
+ * else-statement :== <null> |
+ * ELSE LBRACE executable-statements RBRACE |
+ * ELSE IF if-statement |
+ * ELSIF if-statement
+ */
+
+int parse_if_statement (result, cfile, lose)
+ struct executable_statement **result;
+ struct parse *cfile;
+ int *lose;
+{
+ enum dhcp_token token;
+ const char *val;
+ int parenp;
+
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for if statement.");
+
+ (*result) -> op = if_statement;
+
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == LPAREN) {
+ parenp = 1;
+ skip_token(&val, (unsigned *)0, cfile);
+ } else
+ parenp = 0;
+
+
+ if (!parse_boolean_expression (&(*result) -> data.ie.expr,
+ cfile, lose)) {
+ if (!*lose)
+ parse_warn (cfile, "boolean expression expected.");
+ executable_statement_dereference (result, MDL);
+ *lose = 1;
+ return 0;
+ }
+#if defined (DEBUG_EXPRESSION_PARSE)
+ print_expression ("if condition", (*result) -> data.ie.expr);
+#endif
+ if (parenp) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RPAREN) {
+ parse_warn (cfile, "expecting right paren.");
+ *lose = 1;
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LBRACE) {
+ parse_warn (cfile, "left brace expected.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ if (!parse_executable_statements (&(*result) -> data.ie.tc,
+ cfile, lose, context_any)) {
+ if (*lose) {
+ /* Try to even things up. */
+ do {
+ token = next_token (&val,
+ (unsigned *)0, cfile);
+ } while (token != END_OF_FILE && token != RBRACE);
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RBRACE) {
+ parse_warn (cfile, "right brace expected.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == ELSE) {
+ skip_token(&val, (unsigned *)0, cfile);
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == IF) {
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!parse_if_statement (&(*result) -> data.ie.fc,
+ cfile, lose)) {
+ if (!*lose)
+ parse_warn (cfile,
+ "expecting if statement");
+ executable_statement_dereference (result, MDL);
+ *lose = 1;
+ return 0;
+ }
+ } else if (token != LBRACE) {
+ parse_warn (cfile, "left brace or if expected.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ executable_statement_dereference (result, MDL);
+ return 0;
+ } else {
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!(parse_executable_statements
+ (&(*result) -> data.ie.fc,
+ cfile, lose, context_any))) {
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RBRACE) {
+ parse_warn (cfile, "right brace expected.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ }
+ } else if (token == ELSIF) {
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!parse_if_statement (&(*result) -> data.ie.fc,
+ cfile, lose)) {
+ if (!*lose)
+ parse_warn (cfile,
+ "expecting conditional.");
+ executable_statement_dereference (result, MDL);
+ *lose = 1;
+ return 0;
+ }
+ } else
+ (*result) -> data.ie.fc = (struct executable_statement *)0;
+
+ return 1;
+}
+
+/*
+ * boolean_expression :== CHECK STRING |
+ * NOT boolean-expression |
+ * data-expression EQUAL data-expression |
+ * data-expression BANG EQUAL data-expression |
+ * data-expression REGEX_MATCH data-expression |
+ * boolean-expression AND boolean-expression |
+ * boolean-expression OR boolean-expression
+ * EXISTS OPTION-NAME
+ */
+
+int parse_boolean_expression (expr, cfile, lose)
+ struct expression **expr;
+ struct parse *cfile;
+ int *lose;
+{
+ /* Parse an expression... */
+ if (!parse_expression (expr, cfile, lose, context_boolean,
+ (struct expression **)0, expr_none))
+ return 0;
+
+ if (!is_boolean_expression (*expr) &&
+ (*expr) -> op != expr_variable_reference &&
+ (*expr) -> op != expr_funcall) {
+ parse_warn (cfile, "Expecting a boolean expression.");
+ *lose = 1;
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+ return 1;
+}
+
+/* boolean :== ON SEMI | OFF SEMI | TRUE SEMI | FALSE SEMI */
+
+int parse_boolean (cfile)
+ struct parse *cfile;
+{
+ const char *val;
+ int rv;
+
+ (void)next_token(&val, NULL, cfile);
+ if (!strcasecmp (val, "true")
+ || !strcasecmp (val, "on"))
+ rv = 1;
+ else if (!strcasecmp (val, "false")
+ || !strcasecmp (val, "off"))
+ rv = 0;
+ else {
+ parse_warn (cfile,
+ "boolean value (true/false/on/off) expected");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ parse_semi (cfile);
+ return rv;
+}
+
+
+/*
+ * data_expression :== SUBSTRING LPAREN data-expression COMMA
+ * numeric-expression COMMA
+ * numeric-expression RPAREN |
+ * CONCAT LPAREN data-expression COMMA
+ * data-expression RPAREN
+ * SUFFIX LPAREN data_expression COMMA
+ * numeric-expression RPAREN |
+ * LCASE LPAREN data_expression RPAREN |
+ * UCASE LPAREN data_expression RPAREN |
+ * OPTION option_name |
+ * HARDWARE |
+ * PACKET LPAREN numeric-expression COMMA
+ * numeric-expression RPAREN |
+ * STRING |
+ * colon_separated_hex_list
+ */
+
+int parse_data_expression (expr, cfile, lose)
+ struct expression **expr;
+ struct parse *cfile;
+ int *lose;
+{
+ /* Parse an expression... */
+ if (!parse_expression (expr, cfile, lose, context_data,
+ (struct expression **)0, expr_none))
+ return 0;
+
+ if (!is_data_expression (*expr) &&
+ (*expr) -> op != expr_variable_reference &&
+ (*expr) -> op != expr_funcall) {
+ expression_dereference (expr, MDL);
+ parse_warn (cfile, "Expecting a data expression.");
+ *lose = 1;
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * numeric-expression :== EXTRACT_INT LPAREN data-expression
+ * COMMA number RPAREN |
+ * NUMBER
+ */
+
+int parse_numeric_expression (expr, cfile, lose)
+ struct expression **expr;
+ struct parse *cfile;
+ int *lose;
+{
+ /* Parse an expression... */
+ if (!parse_expression (expr, cfile, lose, context_numeric,
+ (struct expression **)0, expr_none))
+ return 0;
+
+ if (!is_numeric_expression (*expr) &&
+ (*expr) -> op != expr_variable_reference &&
+ (*expr) -> op != expr_funcall) {
+ expression_dereference (expr, MDL);
+ parse_warn (cfile, "Expecting a numeric expression.");
+ *lose = 1;
+ return 0;
+ }
+ return 1;
+}
+#if defined (NSUPDATE_OLD)
+/*
+ * dns-expression :==
+ * UPDATE LPAREN ns-class COMMA ns-type COMMA data-expression COMMA
+ * data-expression COMMA numeric-expression RPAREN
+ * DELETE LPAREN ns-class COMMA ns-type COMMA data-expression COMMA
+ * data-expression RPAREN
+ * EXISTS LPAREN ns-class COMMA ns-type COMMA data-expression COMMA
+ * data-expression RPAREN
+ * NOT EXISTS LPAREN ns-class COMMA ns-type COMMA data-expression COMMA
+ * data-expression RPAREN
+ * ns-class :== IN | CHAOS | HS | NUMBER
+ * ns-type :== A | PTR | MX | TXT | NUMBER
+ */
+
+int parse_dns_expression (expr, cfile, lose)
+ struct expression **expr;
+ struct parse *cfile;
+ int *lose;
+{
+ /* Parse an expression... */
+ if (!parse_expression (expr, cfile, lose, context_dns,
+ (struct expression **)0, expr_none))
+ return 0;
+
+ if (!is_dns_expression (*expr) &&
+ (*expr) -> op != expr_variable_reference &&
+ (*expr) -> op != expr_funcall) {
+ expression_dereference (expr, MDL);
+ parse_warn (cfile, "Expecting a dns update subexpression.");
+ *lose = 1;
+ return 0;
+ }
+ return 1;
+}
+#endif /* NSUPDATE_OLD */
+/* Parse a subexpression that does not contain a binary operator. */
+
+int parse_non_binary (expr, cfile, lose, context)
+ struct expression **expr;
+ struct parse *cfile;
+ int *lose;
+ enum expression_context context;
+{
+ enum dhcp_token token;
+ const char *val;
+ struct collection *col;
+ struct expression *nexp, **ep;
+ int known;
+ char *cptr;
+#if defined (NSUPDATE_OLD)
+ enum expr_op opcode;
+ const char *s;
+ unsigned long u;
+#endif
+ isc_result_t status;
+ unsigned len;
+
+ token = peek_token (&val, (unsigned *)0, cfile);
+
+ /* Check for unary operators... */
+ switch (token) {
+ case CHECK:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING) {
+ parse_warn (cfile, "string expected.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ return 0;
+ }
+ for (col = collections; col; col = col -> next)
+ if (!strcmp (col -> name, val))
+ break;
+ if (!col) {
+ parse_warn (cfile, "unknown collection.");
+ *lose = 1;
+ return 0;
+ }
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_check;
+ (*expr) -> data.check = col;
+ break;
+
+ case TOKEN_NOT:
+ skip_token(&val, (unsigned *)0, cfile);
+#if defined(NSUPDATE_OLD)
+ if (context == context_dns) {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ goto not_exists;
+ }
+#endif
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_not;
+ if (!parse_non_binary (&(*expr) -> data.not,
+ cfile, lose, context_boolean)) {
+ if (!*lose) {
+ parse_warn (cfile, "expression expected");
+ skip_to_semi (cfile);
+ }
+ *lose = 1;
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+ if (!is_boolean_expression ((*expr) -> data.not)) {
+ *lose = 1;
+ parse_warn (cfile, "boolean expression expected");
+ skip_to_semi (cfile);
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+ break;
+
+ case LPAREN:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!parse_expression (expr, cfile, lose, context,
+ (struct expression **)0, expr_none)) {
+ if (!*lose) {
+ parse_warn (cfile, "expression expected");
+ skip_to_semi (cfile);
+ }
+ *lose = 1;
+ return 0;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RPAREN) {
+ *lose = 1;
+ parse_warn (cfile, "right paren expected");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ break;
+
+ case EXISTS:
+#if defined(NSUPDATE_OLD)
+ if (context == context_dns)
+ goto ns_exists;
+#endif
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_exists;
+ known = 0;
+ /* Pass reference directly to expression structure. */
+ status = parse_option_name(cfile, 0, &known,
+ &(*expr)->data.option);
+ if (status != ISC_R_SUCCESS ||
+ (*expr)->data.option == NULL) {
+ *lose = 1;
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+ break;
+
+ case STATIC:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_static;
+ break;
+
+ case KNOWN:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_known;
+ break;
+
+ case SUBSTRING:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_substring;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN) {
+ nolparen:
+ expression_dereference (expr, MDL);
+ parse_warn (cfile, "left parenthesis expected.");
+ *lose = 1;
+ return 0;
+ }
+
+ if (!parse_data_expression (&(*expr) -> data.substring.expr,
+ cfile, lose)) {
+ nodata:
+ expression_dereference (expr, MDL);
+ if (!*lose) {
+ parse_warn (cfile,
+ "expecting data expression.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ }
+ return 0;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA) {
+ nocomma:
+ expression_dereference (expr, MDL);
+ parse_warn (cfile, "comma expected.");
+ *lose = 1;
+
+ return 0;
+ }
+
+ if (!parse_numeric_expression
+ (&(*expr) -> data.substring.offset,cfile, lose)) {
+ nonum:
+ if (!*lose) {
+ parse_warn (cfile,
+ "expecting numeric expression.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ }
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ if (!parse_numeric_expression
+ (&(*expr) -> data.substring.len, cfile, lose))
+ goto nonum;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RPAREN) {
+ norparen:
+ parse_warn (cfile, "right parenthesis expected.");
+ *lose = 1;
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+ break;
+
+ case SUFFIX:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_suffix;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ if (!parse_data_expression (&(*expr) -> data.suffix.expr,
+ cfile, lose))
+ goto nodata;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ if (!parse_numeric_expression (&(*expr) -> data.suffix.len,
+ cfile, lose))
+ goto nonum;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case LCASE:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate(expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr)->op = expr_lcase;
+
+ token = next_token(&val, (unsigned *)0, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ if (!parse_data_expression(&(*expr)->data.lcase, cfile, lose))
+ goto nodata;
+
+ token = next_token(&val, (unsigned *)0, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case UCASE:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate(expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr)->op = expr_ucase;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ if (!parse_data_expression(&(*expr)->data.ucase,
+ cfile, lose))
+ goto nodata;
+
+ token = next_token(&val, (unsigned *)0, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case CONCAT:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_concat;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ if (!parse_data_expression (&(*expr) -> data.concat [0],
+ cfile, lose))
+ goto nodata;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ concat_another:
+ if (!parse_data_expression (&(*expr) -> data.concat [1],
+ cfile, lose))
+ goto nodata;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+
+ if (token == COMMA) {
+ nexp = (struct expression *)0;
+ if (!expression_allocate (&nexp, MDL))
+ log_fatal ("can't allocate at CONCAT2");
+ nexp -> op = expr_concat;
+ expression_reference (&nexp -> data.concat [0],
+ *expr, MDL);
+ expression_dereference (expr, MDL);
+ expression_reference (expr, nexp, MDL);
+ expression_dereference (&nexp, MDL);
+ goto concat_another;
+ }
+
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case BINARY_TO_ASCII:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_binary_to_ascii;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ if (!parse_numeric_expression (&(*expr) -> data.b2a.base,
+ cfile, lose))
+ goto nodata;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ if (!parse_numeric_expression (&(*expr) -> data.b2a.width,
+ cfile, lose))
+ goto nodata;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ if (!parse_data_expression (&(*expr) -> data.b2a.separator,
+ cfile, lose))
+ goto nodata;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ if (!parse_data_expression (&(*expr) -> data.b2a.buffer,
+ cfile, lose))
+ goto nodata;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case REVERSE:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_reverse;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ if (!(parse_numeric_expression
+ (&(*expr) -> data.reverse.width, cfile, lose)))
+ goto nodata;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ if (!(parse_data_expression
+ (&(*expr) -> data.reverse.buffer, cfile, lose)))
+ goto nodata;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case PICK:
+ /* pick (a, b, c) actually produces an internal representation
+ that looks like pick (a, pick (b, pick (c, nil))). */
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!(expression_allocate (expr, MDL)))
+ log_fatal ("can't allocate expression");
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ nexp = (struct expression *)0;
+ expression_reference (&nexp, *expr, MDL);
+ do {
+ nexp -> op = expr_pick_first_value;
+ if (!(parse_data_expression
+ (&nexp -> data.pick_first_value.car,
+ cfile, lose)))
+ goto nodata;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token == COMMA) {
+ struct expression *foo = (struct expression *)0;
+ if (!expression_allocate (&foo, MDL))
+ log_fatal ("can't allocate expr");
+ expression_reference
+ (&nexp -> data.pick_first_value.cdr, foo, MDL);
+ expression_dereference (&nexp, MDL);
+ expression_reference (&nexp, foo, MDL);
+ expression_dereference (&foo, MDL);
+ }
+ } while (token == COMMA);
+ expression_dereference (&nexp, MDL);
+
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+#if defined(NSUPDATE_OLD)
+ /* dns-update and dns-delete are present for historical
+ purposes, but are deprecated in favor of ns-update
+ in combination with update, delete, exists and not
+ exists. */
+ case DNS_UPDATE:
+ case DNS_DELETE:
+#if !defined (NSUPDATE)
+ parse_warn (cfile,
+ "Please rebuild dhcpd with --with-nsupdate.");
+#endif
+ skip_token(&val, (unsigned *)0, cfile);
+ if (token == DNS_UPDATE)
+ opcode = expr_ns_add;
+ else
+ opcode = expr_ns_delete;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING) {
+ parse_warn (cfile,
+ "parse_expression: expecting string.");
+ badnsupdate:
+ skip_to_semi (cfile);
+ *lose = 1;
+ return 0;
+ }
+
+ if (!strcasecmp (val, "a"))
+ u = T_A;
+ else if (!strcasecmp (val, "aaaa"))
+ u = T_AAAA;
+ else if (!strcasecmp (val, "ptr"))
+ u = T_PTR;
+ else if (!strcasecmp (val, "mx"))
+ u = T_MX;
+ else if (!strcasecmp (val, "cname"))
+ u = T_CNAME;
+ else if (!strcasecmp (val, "TXT"))
+ u = T_TXT;
+ else {
+ parse_warn (cfile, "unexpected rrtype: %s", val);
+ goto badnsupdate;
+ }
+
+ s = (opcode == expr_ns_add
+ ? "old-dns-update"
+ : "old-dns-delete");
+ cptr = dmalloc (strlen (s) + 1, MDL);
+ if (!cptr)
+ log_fatal ("can't allocate name for %s", s);
+ strcpy (cptr, s);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_funcall;
+ (*expr) -> data.funcall.name = cptr;
+
+ /* Fake up a function call. */
+ ep = &(*expr) -> data.funcall.arglist;
+ if (!expression_allocate (ep, MDL))
+ log_fatal ("can't allocate expression");
+ (*ep) -> op = expr_arg;
+ if (!make_const_int (&(*ep) -> data.arg.val, u))
+ log_fatal ("can't allocate rrtype value.");
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA)
+ goto nocomma;
+ ep = &((*ep) -> data.arg.next);
+ if (!expression_allocate (ep, MDL))
+ log_fatal ("can't allocate expression");
+ (*ep) -> op = expr_arg;
+ if (!(parse_data_expression (&(*ep) -> data.arg.val,
+ cfile, lose)))
+ goto nodata;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ ep = &((*ep) -> data.arg.next);
+ if (!expression_allocate (ep, MDL))
+ log_fatal ("can't allocate expression");
+ (*ep) -> op = expr_arg;
+ if (!(parse_data_expression (&(*ep) -> data.arg.val,
+ cfile, lose)))
+ goto nodata;
+
+ if (opcode == expr_ns_add) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ ep = &((*ep) -> data.arg.next);
+ if (!expression_allocate (ep, MDL))
+ log_fatal ("can't allocate expression");
+ (*ep) -> op = expr_arg;
+ if (!(parse_numeric_expression (&(*ep) -> data.arg.val,
+ cfile, lose))) {
+ parse_warn (cfile,
+ "expecting numeric expression.");
+ goto badnsupdate;
+ }
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case NS_UPDATE:
+#if !defined (NSUPDATE)
+ parse_warn (cfile,
+ "Please rebuild dhcpd with --with-nsupdate.");
+#endif
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ nexp = *expr;
+ do {
+ nexp -> op = expr_dns_transaction;
+ if (!(parse_dns_expression
+ (&nexp -> data.dns_transaction.car,
+ cfile, lose)))
+ {
+ if (!*lose)
+ parse_warn
+ (cfile,
+ "expecting dns expression.");
+ expression_dereference (expr, MDL);
+ *lose = 1;
+ return 0;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+
+ if (token == COMMA) {
+ if (!(expression_allocate
+ (&nexp -> data.dns_transaction.cdr,
+ MDL)))
+ log_fatal
+ ("can't allocate expression");
+ nexp = nexp -> data.dns_transaction.cdr;
+ }
+ } while (token == COMMA);
+
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ /* NOT EXISTS is special cased above... */
+ not_exists:
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token != EXISTS) {
+ parse_warn (cfile, "expecting DNS prerequisite.");
+ *lose = 1;
+ return 0;
+ }
+ opcode = expr_ns_not_exists;
+ goto nsupdatecode;
+ case TOKEN_ADD:
+ opcode = expr_ns_add;
+ goto nsupdatecode;
+ case TOKEN_DELETE:
+ opcode = expr_ns_delete;
+ goto nsupdatecode;
+ ns_exists:
+ opcode = expr_ns_exists;
+ nsupdatecode:
+ token = next_token (&val, (unsigned *)0, cfile);
+
+#if !defined (NSUPDATE)
+ parse_warn (cfile,
+ "Please rebuild dhcpd with --with-nsupdate.");
+#endif
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = opcode;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!is_identifier (token) && token != NUMBER) {
+ parse_warn (cfile, "expecting identifier or number.");
+ badnsop:
+ expression_dereference (expr, MDL);
+ skip_to_semi (cfile);
+ *lose = 1;
+ return 0;
+ }
+
+ if (token == NUMBER)
+ (*expr) -> data.ns_add.rrclass = atoi (val);
+ else if (!strcasecmp (val, "in"))
+ (*expr) -> data.ns_add.rrclass = C_IN;
+ else if (!strcasecmp (val, "chaos"))
+ (*expr) -> data.ns_add.rrclass = C_CHAOS;
+ else if (!strcasecmp (val, "hs"))
+ (*expr) -> data.ns_add.rrclass = C_HS;
+ else {
+ parse_warn (cfile, "unexpected rrclass: %s", val);
+ goto badnsop;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!is_identifier (token) && token != NUMBER) {
+ parse_warn (cfile, "expecting identifier or number.");
+ goto badnsop;
+ }
+
+ if (token == NUMBER)
+ (*expr) -> data.ns_add.rrtype = atoi (val);
+ else if (!strcasecmp (val, "a"))
+ (*expr) -> data.ns_add.rrtype = T_A;
+ else if (!strcasecmp (val, "aaaa"))
+ (*expr) -> data.ns_add.rrtype = T_AAAA;
+ else if (!strcasecmp (val, "ptr"))
+ (*expr) -> data.ns_add.rrtype = T_PTR;
+ else if (!strcasecmp (val, "mx"))
+ (*expr) -> data.ns_add.rrtype = T_MX;
+ else if (!strcasecmp (val, "cname"))
+ (*expr) -> data.ns_add.rrtype = T_CNAME;
+ else if (!strcasecmp (val, "TXT"))
+ (*expr) -> data.ns_add.rrtype = T_TXT;
+ else {
+ parse_warn (cfile, "unexpected rrtype: %s", val);
+ goto badnsop;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ if (!(parse_data_expression
+ (&(*expr) -> data.ns_add.rrname, cfile, lose)))
+ goto nodata;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ if (!(parse_data_expression
+ (&(*expr) -> data.ns_add.rrdata, cfile, lose)))
+ goto nodata;
+
+ if (opcode == expr_ns_add) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ if (!(parse_numeric_expression
+ (&(*expr) -> data.ns_add.ttl, cfile,
+ lose))) {
+ if (!*lose)
+ parse_warn (cfile,
+ "expecting numeric expression.");
+ goto badnsupdate;
+ }
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+#endif /* NSUPDATE_OLD */
+ case OPTION:
+ case CONFIG_OPTION:
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = (token == OPTION
+ ? expr_option
+ : expr_config_option);
+ skip_token(&val, (unsigned *)0, cfile);
+ known = 0;
+ /* Pass reference directly to expression structure. */
+ status = parse_option_name(cfile, 0, &known,
+ &(*expr)->data.option);
+ if (status != ISC_R_SUCCESS ||
+ (*expr)->data.option == NULL) {
+ *lose = 1;
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+ break;
+
+ case HARDWARE:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_hardware;
+ break;
+
+ case LEASED_ADDRESS:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_leased_address;
+ break;
+
+ case CLIENT_STATE:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_client_state;
+ break;
+
+ case FILENAME:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_filename;
+ break;
+
+ case SERVER_NAME:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_sname;
+ break;
+
+ case LEASE_TIME:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_lease_time;
+ break;
+
+ case TOKEN_NULL:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_null;
+ break;
+
+ case HOST_DECL_NAME:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_host_decl_name;
+ break;
+
+#if defined(NSUPDATE_OLD)
+ case UPDATED_DNS_RR:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING) {
+ parse_warn (cfile, "expecting string.");
+ bad_rrtype:
+ *lose = 1;
+ return 0;
+ }
+ if (!strcasecmp (val, "a"))
+ s = "ddns-fwd-name";
+ else if (!strcasecmp (val, "ptr"))
+ s = "ddns-rev-name";
+ else {
+ parse_warn (cfile, "invalid DNS rrtype: %s", val);
+ goto bad_rrtype;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RPAREN)
+ goto norparen;
+
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_variable_reference;
+ (*expr) -> data.variable =
+ dmalloc (strlen (s) + 1, MDL);
+ if (!(*expr) -> data.variable)
+ log_fatal ("can't allocate variable name.");
+ strcpy ((*expr) -> data.variable, s);
+ break;
+#endif /* NSUPDATE_OLD */
+ case PACKET:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_packet;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ if (!parse_numeric_expression (&(*expr) -> data.packet.offset,
+ cfile, lose))
+ goto nonum;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ if (!parse_numeric_expression (&(*expr) -> data.packet.len,
+ cfile, lose))
+ goto nonum;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case STRING:
+ skip_token(&val, &len, cfile);
+ if (!make_const_data (expr, (const unsigned char *)val,
+ len, 1, 1, MDL))
+ log_fatal ("can't make constant string expression.");
+ break;
+
+ case EXTRACT_INT:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN) {
+ parse_warn (cfile, "left parenthesis expected.");
+ *lose = 1;
+ return 0;
+ }
+
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+
+ if (!parse_data_expression (&(*expr) -> data.extract_int,
+ cfile, lose)) {
+ if (!*lose) {
+ parse_warn (cfile,
+ "expecting data expression.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ }
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA) {
+ parse_warn (cfile, "comma expected.");
+ *lose = 1;
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "number expected.");
+ *lose = 1;
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+ switch (atoi (val)) {
+ case 8:
+ (*expr) -> op = expr_extract_int8;
+ break;
+
+ case 16:
+ (*expr) -> op = expr_extract_int16;
+ break;
+
+ case 32:
+ (*expr) -> op = expr_extract_int32;
+ break;
+
+ default:
+ parse_warn (cfile,
+ "unsupported integer size %d", atoi (val));
+ *lose = 1;
+ skip_to_semi (cfile);
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RPAREN) {
+ parse_warn (cfile, "right parenthesis expected.");
+ *lose = 1;
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+ break;
+
+ case ENCODE_INT:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN) {
+ parse_warn (cfile, "left parenthesis expected.");
+ *lose = 1;
+ return 0;
+ }
+
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+
+ if (!parse_numeric_expression (&(*expr) -> data.encode_int,
+ cfile, lose)) {
+ parse_warn (cfile, "expecting numeric expression.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != COMMA) {
+ parse_warn (cfile, "comma expected.");
+ *lose = 1;
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "number expected.");
+ *lose = 1;
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+ switch (atoi (val)) {
+ case 8:
+ (*expr) -> op = expr_encode_int8;
+ break;
+
+ case 16:
+ (*expr) -> op = expr_encode_int16;
+ break;
+
+ case 32:
+ (*expr) -> op = expr_encode_int32;
+ break;
+
+ default:
+ parse_warn (cfile,
+ "unsupported integer size %d", atoi (val));
+ *lose = 1;
+ skip_to_semi (cfile);
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RPAREN) {
+ parse_warn (cfile, "right parenthesis expected.");
+ *lose = 1;
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+ break;
+
+ case NUMBER:
+ /* If we're in a numeric context, this should just be a
+ number, by itself. */
+ if (context == context_numeric ||
+ context == context_data_or_numeric) {
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_const_int;
+ (*expr) -> data.const_int = atoi (val);
+ break;
+ }
+
+ case NUMBER_OR_NAME:
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+
+ (*expr) -> op = expr_const_data;
+ if (!parse_cshl (&(*expr) -> data.const_data, cfile)) {
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+ break;
+
+ case NS_FORMERR:
+ known = FORMERR;
+ goto ns_const;
+ ns_const:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_const_int;
+ (*expr) -> data.const_int = known;
+ break;
+
+ case NS_NOERROR:
+ known = ISC_R_SUCCESS;
+ goto ns_const;
+
+ case NS_NOTAUTH:
+ known = DHCP_R_NOTAUTH;
+ goto ns_const;
+
+ case NS_NOTIMP:
+ known = ISC_R_NOTIMPLEMENTED;
+ goto ns_const;
+
+ case NS_NOTZONE:
+ known = DHCP_R_NOTZONE;
+ goto ns_const;
+
+ case NS_NXDOMAIN:
+ known = DHCP_R_NXDOMAIN;
+ goto ns_const;
+
+ case NS_NXRRSET:
+ known = DHCP_R_NXRRSET;
+ goto ns_const;
+
+ case NS_REFUSED:
+ known = DHCP_R_REFUSED;
+ goto ns_const;
+
+ case NS_SERVFAIL:
+ known = DHCP_R_SERVFAIL;
+ goto ns_const;
+
+ case NS_YXDOMAIN:
+ known = DHCP_R_YXDOMAIN;
+ goto ns_const;
+
+ case NS_YXRRSET:
+ known = DHCP_R_YXRRSET;
+ goto ns_const;
+
+ case BOOTING:
+ known = S_INIT;
+ goto ns_const;
+
+ case REBOOT:
+ known = S_REBOOTING;
+ goto ns_const;
+
+ case SELECT:
+ known = S_SELECTING;
+ goto ns_const;
+
+ case REQUEST:
+ known = S_REQUESTING;
+ goto ns_const;
+
+ case BOUND:
+ known = S_BOUND;
+ goto ns_const;
+
+ case RENEW:
+ known = S_RENEWING;
+ goto ns_const;
+
+ case REBIND:
+ known = S_REBINDING;
+ goto ns_const;
+
+ case DEFINED:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NAME && token != NUMBER_OR_NAME) {
+ parse_warn (cfile, "%s can't be a variable name", val);
+ skip_to_semi (cfile);
+ *lose = 1;
+ return 0;
+ }
+
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_variable_exists;
+ (*expr) -> data.variable = dmalloc (strlen (val) + 1, MDL);
+ if (!(*expr)->data.variable)
+ log_fatal ("can't allocate variable name");
+ strcpy ((*expr) -> data.variable, val);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ /* This parses 'gethostname()'. */
+ case GETHOSTNAME:
+ skip_token(&val, NULL, cfile);
+ if (!expression_allocate(expr, MDL))
+ log_fatal("can't allocate expression");
+ (*expr)->op = expr_gethostname;
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case GETHOSTBYNAME:
+ skip_token(&val, NULL, cfile);
+ token = next_token(NULL, NULL, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ /* The argument is a quoted string. */
+ token = next_token(&val, NULL, cfile);
+ if (token != STRING) {
+ parse_warn(cfile, "Expecting quoted literal: "
+ "\"foo.example.com\"");
+ skip_to_semi(cfile);
+ *lose = 1;
+ return 0;
+ }
+ if (!make_host_lookup(expr, val))
+ log_fatal("Error creating gethostbyname() internal "
+ "record. (%s:%d)", MDL);
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ /* Not a valid start to an expression... */
+ default:
+ if (token != NAME && token != NUMBER_OR_NAME)
+ return 0;
+
+ skip_token(&val, (unsigned *)0, cfile);
+
+ /* Save the name of the variable being referenced. */
+ cptr = dmalloc (strlen (val) + 1, MDL);
+ if (!cptr)
+ log_fatal ("can't allocate variable name");
+ strcpy (cptr, val);
+
+ /* Simple variable reference, as far as we can tell. */
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN) {
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_variable_reference;
+ (*expr) -> data.variable = cptr;
+ break;
+ }
+
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_funcall;
+ (*expr) -> data.funcall.name = cptr;
+
+ /* Now parse the argument list. */
+ ep = &(*expr) -> data.funcall.arglist;
+ do {
+ if (!expression_allocate (ep, MDL))
+ log_fatal ("can't allocate expression");
+ (*ep) -> op = expr_arg;
+ if (!parse_expression (&(*ep) -> data.arg.val,
+ cfile, lose, context_any,
+ (struct expression **)0,
+ expr_none)) {
+ if (!*lose) {
+ parse_warn (cfile,
+ "expecting expression.");
+ *lose = 1;
+ }
+ skip_to_semi (cfile);
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+ ep = &((*ep) -> data.arg.next);
+ token = next_token (&val, (unsigned *)0, cfile);
+ } while (token == COMMA);
+ if (token != RPAREN) {
+ parse_warn (cfile, "Right parenthesis expected.");
+ skip_to_semi (cfile);
+ *lose = 1;
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+ break;
+ }
+ return 1;
+}
+
+/* Parse an expression. */
+
+int parse_expression (expr, cfile, lose, context, plhs, binop)
+ struct expression **expr;
+ struct parse *cfile;
+ int *lose;
+ enum expression_context context;
+ struct expression **plhs;
+ enum expr_op binop;
+{
+ enum dhcp_token token;
+ const char *val;
+ struct expression *rhs = (struct expression *)0, *tmp;
+ struct expression *lhs = (struct expression *)0;
+ enum expr_op next_op;
+ enum expression_context
+ lhs_context = context_any,
+ rhs_context = context_any;
+
+ /* Consume the left hand side we were passed. */
+ if (plhs) {
+ expression_reference (&lhs, *plhs, MDL);
+ expression_dereference (plhs, MDL);
+ }
+
+ new_rhs:
+ if (!parse_non_binary (&rhs, cfile, lose, context)) {
+ /* If we already have a left-hand side, then it's not
+ okay for there not to be a right-hand side here, so
+ we need to flag it as an error. */
+ if (lhs) {
+ if (!*lose) {
+ parse_warn (cfile,
+ "expecting right-hand side.");
+ *lose = 1;
+ skip_to_semi (cfile);
+ }
+ expression_dereference (&lhs, MDL);
+ }
+ return 0;
+ }
+
+ /* At this point, rhs contains either an entire subexpression,
+ or at least a left-hand-side. If we do not see a binary token
+ as the next token, we're done with the expression. */
+
+ token = peek_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case BANG:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token != EQUAL) {
+ parse_warn (cfile, "! in boolean context without =");
+ *lose = 1;
+ skip_to_semi (cfile);
+ if (lhs)
+ expression_dereference (&lhs, MDL);
+ return 0;
+ }
+ next_op = expr_not_equal;
+ context = expression_context (rhs);
+ break;
+
+ case EQUAL:
+ next_op = expr_equal;
+ context = expression_context (rhs);
+ break;
+
+ case TILDE:
+#ifdef HAVE_REGEX_H
+ skip_token(&val, NULL, cfile);
+ token = peek_token(&val, NULL, cfile);
+
+ if (token == TILDE)
+ next_op = expr_iregex_match;
+ else if (token == EQUAL)
+ next_op = expr_regex_match;
+ else {
+ parse_warn(cfile, "expecting ~= or ~~ operator");
+ *lose = 1;
+ skip_to_semi(cfile);
+ if (lhs)
+ expression_dereference(&lhs, MDL);
+ return 0;
+ }
+
+ context = expression_context(rhs);
+#else
+ parse_warn(cfile, "No support for regex operator.");
+ *lose = 1;
+ skip_to_semi(cfile);
+ if (lhs != NULL)
+ expression_dereference(&lhs, MDL);
+ return 0;
+#endif
+ break;
+
+ case AND:
+ next_op = expr_and;
+ context = expression_context (rhs);
+ break;
+
+ case OR:
+ next_op = expr_or;
+ context = expression_context (rhs);
+ break;
+
+ case PLUS:
+ next_op = expr_add;
+ context = expression_context (rhs);
+ break;
+
+ case MINUS:
+ next_op = expr_subtract;
+ context = expression_context (rhs);
+ break;
+
+ case SLASH:
+ next_op = expr_divide;
+ context = expression_context (rhs);
+ break;
+
+ case ASTERISK:
+ next_op = expr_multiply;
+ context = expression_context (rhs);
+ break;
+
+ case PERCENT:
+ next_op = expr_remainder;
+ context = expression_context (rhs);
+ break;
+
+ case AMPERSAND:
+ next_op = expr_binary_and;
+ context = expression_context (rhs);
+ break;
+
+ case PIPE:
+ next_op = expr_binary_or;
+ context = expression_context (rhs);
+ break;
+
+ case CARET:
+ next_op = expr_binary_xor;
+ context = expression_context (rhs);
+ break;
+
+ default:
+ next_op = expr_none;
+ }
+
+ /* If we have no lhs yet, we just parsed it. */
+ if (!lhs) {
+ /* If there was no operator following what we just parsed,
+ then we're done - return it. */
+ if (next_op == expr_none) {
+ *expr = rhs;
+ return 1;
+ }
+ lhs = rhs;
+ rhs = (struct expression *)0;
+ binop = next_op;
+ skip_token(&val, (unsigned *)0, cfile);
+ goto new_rhs;
+ }
+
+ /* If the next binary operator is of greater precedence than the
+ * current operator, then rhs we have parsed so far is actually
+ * the lhs of the next operator. To get this value, we have to
+ * recurse.
+ */
+ if (binop != expr_none && next_op != expr_none &&
+ op_precedence (binop, next_op) < 0) {
+
+ /* Eat the subexpression operator token, which we pass to
+ * parse_expression...we only peek()'d earlier.
+ */
+ skip_token(&val, (unsigned *)0, cfile);
+
+ /* Continue parsing of the right hand side with that token. */
+ tmp = rhs;
+ rhs = (struct expression *)0;
+ if (!parse_expression (&rhs, cfile, lose, op_context (next_op),
+ &tmp, next_op)) {
+ if (!*lose) {
+ parse_warn (cfile,
+ "expecting a subexpression");
+ *lose = 1;
+ }
+ return 0;
+ }
+ next_op = expr_none;
+ }
+
+ if (binop != expr_none) {
+ rhs_context = expression_context(rhs);
+ lhs_context = expression_context(lhs);
+
+ if ((rhs_context != context_any) && (lhs_context != context_any) &&
+ (rhs_context != lhs_context)) {
+ parse_warn (cfile, "illegal expression relating different types");
+ skip_to_semi (cfile);
+ expression_dereference (&rhs, MDL);
+ expression_dereference (&lhs, MDL);
+ *lose = 1;
+ return 0;
+ }
+
+ switch(binop) {
+ case expr_not_equal:
+ case expr_equal:
+ if ((rhs_context != context_data_or_numeric) &&
+ (rhs_context != context_data) &&
+ (rhs_context != context_numeric) &&
+ (rhs_context != context_any)) {
+ parse_warn (cfile, "expecting data/numeric expression");
+ skip_to_semi (cfile);
+ expression_dereference (&rhs, MDL);
+ *lose = 1;
+ return 0;
+ }
+ break;
+
+ case expr_regex_match:
+#ifdef HAVE_REGEX_H
+ if (expression_context(rhs) != context_data) {
+ parse_warn(cfile, "expecting data expression");
+ skip_to_semi(cfile);
+ expression_dereference(&rhs, MDL);
+ *lose = 1;
+ return 0;
+ }
+#else
+ /* It should not be possible to attempt to parse the right
+ * hand side of an operator there is no support for.
+ */
+ log_fatal("Impossible condition at %s:%d.", MDL);
+#endif
+ break;
+
+ case expr_and:
+ case expr_or:
+ if ((rhs_context != context_boolean) &&
+ (rhs_context != context_any)) {
+ parse_warn (cfile, "expecting boolean expressions");
+ skip_to_semi (cfile);
+ expression_dereference (&rhs, MDL);
+ *lose = 1;
+ return 0;
+ }
+ break;
+
+ case expr_add:
+ case expr_subtract:
+ case expr_divide:
+ case expr_multiply:
+ case expr_remainder:
+ case expr_binary_and:
+ case expr_binary_or:
+ case expr_binary_xor:
+ if ((rhs_context != context_numeric) &&
+ (rhs_context != context_any)) {
+ parse_warn (cfile, "expecting numeric expressions");
+ skip_to_semi (cfile);
+ expression_dereference (&rhs, MDL);
+ *lose = 1;
+ return 0;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Now, if we didn't find a binary operator, we're done parsing
+ this subexpression, so combine it with the preceding binary
+ operator and return the result. */
+ if (next_op == expr_none) {
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("Can't allocate expression!");
+
+ (*expr) -> op = binop;
+ /* All the binary operators' data union members
+ are the same, so we'll cheat and use the member
+ for the equals operator. */
+ (*expr) -> data.equal [0] = lhs;
+ (*expr) -> data.equal [1] = rhs;
+ return 1;
+ }
+
+ /* Eat the operator token - we now know it was a binary operator... */
+ skip_token(&val, (unsigned *)0, cfile);
+
+ /* Now combine the LHS and the RHS using binop. */
+ tmp = (struct expression *)0;
+ if (!expression_allocate (&tmp, MDL))
+ log_fatal ("No memory for equal precedence combination.");
+
+ /* Store the LHS and RHS. */
+ tmp -> data.equal [0] = lhs;
+ tmp -> data.equal [1] = rhs;
+ tmp -> op = binop;
+
+ lhs = tmp;
+ tmp = (struct expression *)0;
+ rhs = (struct expression *)0;
+
+ binop = next_op;
+ goto new_rhs;
+}
+
+
+int parse_option_data (expr, cfile, lookups, option)
+struct expression **expr;
+struct parse *cfile;
+int lookups;
+struct option *option;
+{
+ const char *val;
+ const char *fmt = NULL;
+ struct expression *tmp;
+ enum dhcp_token token;
+
+ do {
+ /*
+ * Set a flag if this is an array of a simple type (i.e.,
+ * not an array of pairs of IP addresses, or something like
+ * that.
+ */
+ int uniform = 0;
+
+ and_again:
+ /* Set fmt to start of format for 'A' and one char back
+ * for 'a'.
+ */
+ if ((fmt != NULL) && (fmt != option->format) && (*fmt == 'a'))
+ fmt -= 1;
+ else if ((fmt == NULL) || (*fmt == 'A'))
+ fmt = option->format;
+
+ /* 'a' means always uniform */
+ if ((fmt[0] != 'Z') && (tolower((unsigned char)fmt[1]) == 'a'))
+ uniform = 1;
+
+ do {
+ if ((*fmt == 'A') || (*fmt == 'a'))
+ break;
+ if (*fmt == 'o') {
+ /* consume the optional flag */
+ fmt++;
+ continue;
+ }
+
+ if (fmt[1] == 'o') {
+ /*
+ * A value for the current format is
+ * optional - check to see if the next
+ * token is a semi-colon if so we don't
+ * need to parse it and doing so would
+ * consume the semi-colon which our
+ * caller is expecting to parse
+ */
+ token = peek_token(&val, (unsigned *)0,
+ cfile);
+ if (token == SEMI) {
+ fmt++;
+ continue;
+ }
+ }
+
+ tmp = *expr;
+ *expr = NULL;
+
+ if (!parse_option_token(expr, cfile, &fmt, tmp,
+ uniform, lookups)) {
+ if (fmt [1] != 'o') {
+ if (tmp)
+ expression_dereference (&tmp,
+ MDL);
+ return 0;
+ }
+ *expr = tmp;
+ tmp = NULL;
+ }
+ if (tmp)
+ expression_dereference (&tmp, MDL);
+
+ fmt++;
+ } while (*fmt != '\0');
+
+ if ((*fmt == 'A') || (*fmt == 'a')) {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ /* Comma means: continue with next element in array */
+ if (token == COMMA) {
+ skip_token(&val, (unsigned *)0, cfile);
+ continue;
+ }
+ /* no comma: end of array.
+ 'A' or end of string means: leave the loop */
+ if ((*fmt == 'A') || (fmt[1] == '\0'))
+ break;
+ /* 'a' means: go on with next char */
+ if (*fmt == 'a') {
+ fmt++;
+ goto and_again;
+ }
+ }
+ } while ((*fmt == 'A') || (*fmt == 'a'));
+
+ return 1;
+}
+
+/* option-statement :== identifier DOT identifier <syntax> SEMI
+ | identifier <syntax> SEMI
+
+ Option syntax is handled specially through format strings, so it
+ would be painful to come up with BNF for it. However, it always
+ starts as above and ends in a SEMI. */
+
+int parse_option_statement (result, cfile, lookups, option, op)
+ struct executable_statement **result;
+ struct parse *cfile;
+ int lookups;
+ struct option *option;
+ enum statement_op op;
+{
+ const char *val;
+ enum dhcp_token token;
+ struct expression *expr = (struct expression *)0;
+ int lose;
+
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if ((token == SEMI) && (option->format[0] != 'Z')) {
+ /* Eat the semicolon... */
+ /*
+ * XXXSK: I'm not sure why we should ever get here, but we
+ * do during our startup. This confuses things if
+ * we are parsing a zero-length option, so don't
+ * eat the semicolon token in that case.
+ */
+ skip_token(&val, (unsigned *)0, cfile);
+ } else if (token == EQUAL) {
+ /* Eat the equals sign. */
+ skip_token(&val, (unsigned *)0, cfile);
+
+ /* Parse a data expression and use its value for the data. */
+ if (!parse_data_expression (&expr, cfile, &lose)) {
+ /* In this context, we must have an executable
+ statement, so if we found something else, it's
+ still an error. */
+ if (!lose) {
+ parse_warn (cfile,
+ "expecting a data expression.");
+ skip_to_semi (cfile);
+ }
+ return 0;
+ }
+ } else {
+ if (! parse_option_data(&expr, cfile, lookups, option))
+ return 0;
+ }
+
+ if (!parse_semi (cfile))
+ return 0;
+ if (!executable_statement_allocate (result, MDL))
+ log_fatal ("no memory for option statement.");
+
+ (*result)->op = op;
+ if (expr && !option_cache (&(*result)->data.option,
+ NULL, expr, option, MDL))
+ log_fatal ("no memory for option cache");
+
+ if (expr)
+ expression_dereference (&expr, MDL);
+
+ return 1;
+}
+
+int parse_option_token (rv, cfile, fmt, expr, uniform, lookups)
+ struct expression **rv;
+ struct parse *cfile;
+ const char **fmt;
+ struct expression *expr;
+ int uniform;
+ int lookups;
+{
+ const char *val;
+ enum dhcp_token token;
+ struct expression *t = (struct expression *)0;
+ unsigned char buf [4];
+ unsigned len;
+ struct iaddr addr;
+ int compress;
+ isc_boolean_t freeval = ISC_FALSE;
+ const char *f, *g;
+ struct enumeration_value *e;
+
+ switch (**fmt) {
+ case 'U':
+ token = next_token (&val, &len, cfile);
+ if (!is_identifier (token)) {
+ if ((*fmt) [1] != 'o') {
+ parse_warn (cfile, "expecting identifier.");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ }
+ return 0;
+ }
+ if (!make_const_data (&t, (const unsigned char *)val,
+ len, 1, 1, MDL))
+ log_fatal ("No memory for %s", val);
+ break;
+
+ case 'E':
+ g = strchr (*fmt, '.');
+ if (!g) {
+ parse_warn (cfile,
+ "malformed encapsulation format (bug!)");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ *fmt = g;
+ /* FALL THROUGH */
+ /* to get string value for the option */
+ case 'X':
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == NUMBER_OR_NAME || token == NUMBER) {
+ if (!expression_allocate (&t, MDL))
+ return 0;
+ if (!parse_cshl (&t -> data.const_data, cfile)) {
+ expression_dereference (&t, MDL);
+ return 0;
+ }
+ t -> op = expr_const_data;
+ } else {
+ token = next_token (&val, &len, cfile);
+
+ if(token == STRING) {
+ if (!make_const_data (&t,
+ (const unsigned char *)val,
+ len, 1, 1, MDL))
+ log_fatal ("No memory for \"%s\"", val);
+ } else {
+ if ((*fmt) [1] != 'o') {
+ parse_warn (cfile, "expecting string "
+ "or hexadecimal data.");
+ skip_to_semi (cfile);
+ }
+ return 0;
+ }
+ }
+ break;
+
+ case 'D': /* Domain list... */
+ if ((*fmt)[1] == 'c') {
+ compress = 1;
+ /* Skip the compress-flag atom. */
+ (*fmt)++;
+ } else
+ compress = 0;
+
+ t = parse_domain_list(cfile, compress);
+
+ if (!t) {
+ if ((*fmt)[1] != 'o')
+ skip_to_semi(cfile);
+ return 0;
+ }
+
+ break;
+
+ case 'd': /* Domain name... */
+ val = parse_host_name (cfile);
+ if (!val) {
+ parse_warn (cfile, "not a valid domain name.");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ len = strlen (val);
+ freeval = ISC_TRUE;
+ goto make_string;
+
+ case 't': /* Text string... */
+ token = next_token (&val, &len, cfile);
+ if (token != STRING && !is_identifier (token)) {
+ if ((*fmt) [1] != 'o') {
+ parse_warn (cfile, "expecting string.");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ }
+ return 0;
+ }
+ make_string:
+ if (!make_const_data (&t, (const unsigned char *)val,
+ len, 1, 1, MDL))
+ log_fatal ("No memory for concatenation");
+ if (freeval == ISC_TRUE) {
+ dfree((char *)val, MDL);
+ freeval = ISC_FALSE;
+ POST(freeval);
+ }
+ break;
+
+ case 'N':
+ f = (*fmt) + 1;
+ g = strchr (*fmt, '.');
+ if (!g) {
+ parse_warn (cfile, "malformed %s (bug!)",
+ "enumeration format");
+ foo:
+ skip_to_semi (cfile);
+ return 0;
+ }
+ *fmt = g;
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!is_identifier (token)) {
+ parse_warn (cfile,
+ "identifier expected");
+ goto foo;
+ }
+ e = find_enumeration_value (f, (*fmt) - f, &len, val);
+ if (!e) {
+ parse_warn (cfile, "unknown value");
+ goto foo;
+ }
+ if (!make_const_data (&t, &e -> value, len, 0, 1, MDL))
+ return 0;
+ break;
+
+ case 'I': /* IP address or hostname. */
+ if (lookups) {
+ if (!parse_ip_addr_or_hostname (&t, cfile, uniform))
+ return 0;
+ } else {
+ if (!parse_ip_addr (cfile, &addr))
+ return 0;
+ if (!make_const_data (&t, addr.iabuf, addr.len,
+ 0, 1, MDL))
+ return 0;
+ }
+ break;
+
+ case '6': /* IPv6 address. */
+ if (!parse_ip6_addr(cfile, &addr)) {
+ return 0;
+ }
+ if (!make_const_data(&t, addr.iabuf, addr.len, 0, 1, MDL)) {
+ return 0;
+ }
+ break;
+
+ case 'T': /* Lease interval. */
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != INFINITE)
+ goto check_number;
+ putLong (buf, -1);
+ if (!make_const_data (&t, buf, 4, 0, 1, MDL))
+ return 0;
+ break;
+
+ case 'L': /* Unsigned 32-bit integer... */
+ case 'l': /* Signed 32-bit integer... */
+ token = next_token (&val, (unsigned *)0, cfile);
+ check_number:
+ if ((token != NUMBER) && (token != NUMBER_OR_NAME)) {
+ need_number:
+ if ((*fmt) [1] != 'o') {
+ parse_warn (cfile, "expecting number.");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ }
+ return 0;
+ }
+ convert_num (cfile, buf, val, 0, 32);
+ if (!make_const_data (&t, buf, 4, 0, 1, MDL))
+ return 0;
+ break;
+
+ case 's': /* Signed 16-bit integer. */
+ case 'S': /* Unsigned 16-bit integer. */
+ token = next_token (&val, (unsigned *)0, cfile);
+ if ((token != NUMBER) && (token != NUMBER_OR_NAME))
+ goto need_number;
+ convert_num (cfile, buf, val, 0, 16);
+ if (!make_const_data (&t, buf, 2, 0, 1, MDL))
+ return 0;
+ break;
+
+ case 'b': /* Signed 8-bit integer. */
+ case 'B': /* Unsigned 8-bit integer. */
+ token = next_token (&val, (unsigned *)0, cfile);
+ if ((token != NUMBER) && (token != NUMBER_OR_NAME))
+ goto need_number;
+ convert_num (cfile, buf, val, 0, 8);
+ if (!make_const_data (&t, buf, 1, 0, 1, MDL))
+ return 0;
+ break;
+
+ case 'f': /* Boolean flag. */
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!is_identifier (token)) {
+ if ((*fmt) [1] != 'o')
+ parse_warn (cfile, "expecting identifier.");
+ bad_flag:
+ if ((*fmt) [1] != 'o') {
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ }
+ return 0;
+ }
+ if (!strcasecmp (val, "true")
+ || !strcasecmp (val, "on"))
+ buf [0] = 1;
+ else if (!strcasecmp (val, "false")
+ || !strcasecmp (val, "off"))
+ buf [0] = 0;
+ else if (!strcasecmp (val, "ignore"))
+ buf [0] = 2;
+ else {
+ if ((*fmt) [1] != 'o')
+ parse_warn (cfile, "expecting boolean.");
+ goto bad_flag;
+ }
+ if (!make_const_data (&t, buf, 1, 0, 1, MDL))
+ return 0;
+ break;
+
+ case 'Z': /* Zero-length option. */
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "semicolon expected.");
+ skip_to_semi(cfile);
+ }
+ buf[0] = '\0';
+ if (!make_const_data(&t, /* expression */
+ buf, /* buffer */
+ 0, /* length */
+ 0, /* terminated */
+ 1, /* allocate */
+ MDL))
+ return 0;
+ break;
+
+ default:
+ parse_warn (cfile, "Bad format '%c' in parse_option_token.",
+ **fmt);
+ skip_to_semi (cfile);
+ return 0;
+ }
+ if (expr) {
+ if (!make_concat (rv, expr, t))
+ return 0;
+ } else
+ expression_reference (rv, t, MDL);
+ expression_dereference (&t, MDL);
+ return 1;
+}
+
+int parse_option_decl (oc, cfile)
+ struct option_cache **oc;
+ struct parse *cfile;
+{
+ const char *val;
+ int token;
+ u_int8_t buf [4];
+ u_int8_t hunkbuf [1024];
+ unsigned hunkix = 0;
+ const char *fmt, *f;
+ struct option *option=NULL;
+ struct iaddr ip_addr;
+ u_int8_t *dp;
+ const u_int8_t *cdp;
+ unsigned len;
+ int nul_term = 0;
+ struct buffer *bp;
+ int known = 0;
+ int compress;
+ struct expression *express = NULL;
+ struct enumeration_value *e;
+ isc_result_t status;
+
+ status = parse_option_name (cfile, 0, &known, &option);
+ if (status != ISC_R_SUCCESS || option == NULL)
+ return 0;
+
+ fmt = option->format;
+
+ /* Parse the option data... */
+ do {
+ for (; *fmt; fmt++) {
+ if (*fmt == 'A') {
+ /* 'A' is an array of records, start at
+ * the beginning
+ */
+ fmt = option->format;
+ break;
+ }
+
+ if (*fmt == 'a') {
+ /* 'a' is an array of the last field,
+ * back up one format character
+ */
+ fmt--;
+ break;
+ }
+ if (*fmt == 'o' && fmt != option -> format)
+ continue;
+ switch (*fmt) {
+ case 'E':
+ fmt = strchr (fmt, '.');
+ if (!fmt) {
+ parse_warn (cfile,
+ "malformed %s (bug!)",
+ "encapsulation format");
+ goto parse_exit;
+ }
+ /* FALL THROUGH */
+ /* to get string value for the option */
+ case 'X':
+ len = parse_X (cfile, &hunkbuf [hunkix],
+ sizeof hunkbuf - hunkix);
+ hunkix += len;
+ break;
+
+ case 't': /* Text string... */
+ token = peek_token (&val,
+ &len, cfile);
+ if (token == SEMI && fmt[1] == 'o') {
+ fmt++;
+ break;
+ }
+ token = next_token (&val,
+ &len, cfile);
+ if (token != STRING) {
+ parse_warn (cfile,
+ "expecting string.");
+ goto parse_exit;
+ }
+ if (hunkix + len + 1 > sizeof hunkbuf) {
+ parse_warn (cfile,
+ "option data buffer %s",
+ "overflow");
+ goto parse_exit;
+ }
+ memcpy (&hunkbuf [hunkix], val, len + 1);
+ nul_term = 1;
+ hunkix += len;
+ break;
+
+ case 'D':
+ if (fmt[1] == 'c') {
+ compress = 1;
+ fmt++;
+ } else
+ compress = 0;
+
+ express = parse_domain_list(cfile, compress);
+
+ if (express == NULL)
+ goto exit;
+
+ if (express->op != expr_const_data) {
+ parse_warn(cfile, "unexpected "
+ "expression");
+ goto parse_exit;
+ }
+
+ len = express->data.const_data.len;
+ cdp = express->data.const_data.data;
+
+ if ((hunkix + len) > sizeof(hunkbuf)) {
+ parse_warn(cfile, "option data buffer "
+ "overflow");
+ goto parse_exit;
+ }
+ memcpy(&hunkbuf[hunkix], cdp, len);
+ hunkix += len;
+
+ expression_dereference(&express, MDL);
+ break;
+
+ case 'N':
+ f = fmt + 1;
+ fmt = strchr (fmt, '.');
+ if (!fmt) {
+ parse_warn (cfile,
+ "malformed %s (bug!)",
+ "enumeration format");
+ goto parse_exit;
+ }
+ token = next_token (&val,
+ (unsigned *)0, cfile);
+ if (!is_identifier (token)) {
+ parse_warn (cfile,
+ "identifier expected");
+ goto parse_exit;
+ }
+ e = find_enumeration_value (f, fmt - f,
+ &len, val);
+ if (!e) {
+ parse_warn (cfile,
+ "unknown value");
+ goto parse_exit;
+ }
+ dp = &e -> value;
+ goto alloc;
+
+ case '6':
+ if (!parse_ip6_addr(cfile, &ip_addr))
+ goto exit;
+ len = ip_addr.len;
+ dp = ip_addr.iabuf;
+ goto alloc;
+
+ case 'I': /* IP address. */
+ if (!parse_ip_addr (cfile, &ip_addr))
+ goto exit;
+ len = ip_addr.len;
+ dp = ip_addr.iabuf;
+
+ alloc:
+ if (hunkix + len > sizeof hunkbuf) {
+ parse_warn (cfile,
+ "option data buffer %s",
+ "overflow");
+ goto parse_exit;
+ }
+ memcpy (&hunkbuf [hunkix], dp, len);
+ hunkix += len;
+ break;
+
+ case 'L': /* Unsigned 32-bit integer... */
+ case 'l': /* Signed 32-bit integer... */
+ token = next_token (&val,
+ (unsigned *)0, cfile);
+ if ((token != NUMBER) &&
+ (token != NUMBER_OR_NAME)) {
+ need_number:
+ parse_warn (cfile,
+ "expecting number.");
+ if (token != SEMI)
+ goto parse_exit;
+ else
+ goto exit;
+ }
+ convert_num (cfile, buf, val, 0, 32);
+ len = 4;
+ dp = buf;
+ goto alloc;
+
+ case 's': /* Signed 16-bit integer. */
+ case 'S': /* Unsigned 16-bit integer. */
+ token = next_token (&val,
+ (unsigned *)0, cfile);
+ if ((token != NUMBER) &&
+ (token != NUMBER_OR_NAME))
+ goto need_number;
+ convert_num (cfile, buf, val, 0, 16);
+ len = 2;
+ dp = buf;
+ goto alloc;
+
+ case 'b': /* Signed 8-bit integer. */
+ case 'B': /* Unsigned 8-bit integer. */
+ token = next_token (&val,
+ (unsigned *)0, cfile);
+ if ((token != NUMBER) &&
+ (token != NUMBER_OR_NAME))
+ goto need_number;
+ convert_num (cfile, buf, val, 0, 8);
+ len = 1;
+ dp = buf;
+ goto alloc;
+
+ case 'f': /* Boolean flag. */
+ token = next_token (&val,
+ (unsigned *)0, cfile);
+ if (!is_identifier (token)) {
+ parse_warn (cfile,
+ "expecting identifier.");
+ bad_flag:
+ if (token != SEMI)
+ goto parse_exit;
+ else
+ goto exit;
+ }
+ if (!strcasecmp (val, "true")
+ || !strcasecmp (val, "on"))
+ buf [0] = 1;
+ else if (!strcasecmp (val, "false")
+ || !strcasecmp (val, "off"))
+ buf [0] = 0;
+ else {
+ parse_warn (cfile,
+ "expecting boolean.");
+ goto bad_flag;
+ }
+ len = 1;
+ dp = buf;
+ goto alloc;
+
+ case 'Z': /* Zero-length option */
+ token = peek_token(&val, (unsigned *)0, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile,
+ "semicolon expected.");
+ goto parse_exit;
+ }
+ len = 0;
+ buf[0] = '\0';
+ break;
+
+ default:
+ log_error ("parse_option_param: Bad format %c",
+ *fmt);
+ goto parse_exit;
+ }
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ } while (*fmt && token == COMMA);
+
+ if (token != SEMI) {
+ parse_warn (cfile, "semicolon expected.");
+ goto parse_exit;
+ }
+
+ bp = (struct buffer *)0;
+ if (!buffer_allocate (&bp, hunkix + nul_term, MDL))
+ log_fatal ("no memory to store option declaration.");
+ memcpy (bp -> data, hunkbuf, hunkix + nul_term);
+
+ if (!option_cache_allocate (oc, MDL))
+ log_fatal ("out of memory allocating option cache.");
+
+ (*oc) -> data.buffer = bp;
+ (*oc) -> data.data = &bp -> data [0];
+ (*oc) -> data.terminated = nul_term;
+ (*oc) -> data.len = hunkix;
+ option_reference(&(*oc)->option, option, MDL);
+ option_dereference(&option, MDL);
+ return 1;
+
+parse_exit:
+ if (express != NULL)
+ expression_dereference(&express, MDL);
+ skip_to_semi (cfile);
+exit:
+ option_dereference(&option, MDL);
+
+ return 0;
+}
+
+/* Consider merging parse_cshl into this. */
+
+int parse_X (cfile, buf, max)
+ struct parse *cfile;
+ u_int8_t *buf;
+ unsigned max;
+{
+ int token;
+ const char *val;
+ unsigned len;
+
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == NUMBER_OR_NAME || token == NUMBER) {
+ len = 0;
+ do {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER && token != NUMBER_OR_NAME) {
+ parse_warn (cfile,
+ "expecting hexadecimal constant.");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ convert_num (cfile, &buf [len], val, 16, 8);
+ if (len++ > max) {
+ parse_warn (cfile,
+ "hexadecimal constant too long.");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == COLON)
+ token = next_token (&val,
+ (unsigned *)0, cfile);
+ } while (token == COLON);
+ val = (char *)buf;
+ } else if (token == STRING) {
+ skip_token(&val, &len, cfile);
+ if (len + 1 > max) {
+ parse_warn (cfile, "string constant too long.");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ memcpy (buf, val, len + 1);
+ } else {
+ parse_warn (cfile, "expecting string or hexadecimal data");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ return len;
+}
+
+int parse_warn (struct parse *cfile, const char *fmt, ...)
+{
+ va_list list;
+ char lexbuf [256];
+ char mbuf [1024];
+ char fbuf [1024];
+ unsigned i, lix;
+
+ do_percentm (mbuf, fmt);
+ /* %Audit% This is log output. %2004.06.17,Safe%
+ * If we truncate we hope the user can get a hint from the log.
+ */
+ snprintf (fbuf, sizeof fbuf, "%s line %d: %s",
+ cfile -> tlname, cfile -> lexline, mbuf);
+
+ va_start (list, fmt);
+ vsnprintf (mbuf, sizeof mbuf, fbuf, list);
+ va_end (list);
+
+ lix = 0;
+ for (i = 0;
+ cfile -> token_line [i] && i < (cfile -> lexchar - 1); i++) {
+ if (lix < (sizeof lexbuf) - 1)
+ lexbuf [lix++] = ' ';
+ if (cfile -> token_line [i] == '\t') {
+ for (; lix < (sizeof lexbuf) - 1 && (lix & 7); lix++)
+ lexbuf [lix] = ' ';
+ }
+ }
+ lexbuf [lix] = 0;
+
+#ifndef DEBUG
+ syslog (LOG_ERR, "%s", mbuf);
+ syslog (LOG_ERR, "%s", cfile -> token_line);
+ if (cfile -> lexchar < 81)
+ syslog (LOG_ERR, "%s^", lexbuf);
+#endif
+
+ if (log_perror) {
+ IGNORE_RET (write (STDERR_FILENO, mbuf, strlen (mbuf)));
+ IGNORE_RET (write (STDERR_FILENO, "\n", 1));
+ IGNORE_RET (write (STDERR_FILENO, cfile -> token_line,
+ strlen (cfile -> token_line)));
+ IGNORE_RET (write (STDERR_FILENO, "\n", 1));
+ if (cfile -> lexchar < 81)
+ IGNORE_RET (write (STDERR_FILENO, lexbuf, lix));
+ IGNORE_RET (write (STDERR_FILENO, "^\n", 2));
+ }
+
+ cfile -> warnings_occurred = 1;
+
+ return 0;
+}
+
+struct expression *
+parse_domain_list(struct parse *cfile, int compress)
+{
+ const char *val;
+ enum dhcp_token token = SEMI;
+ struct expression *t = NULL;
+ unsigned len, clen = 0;
+ int result;
+ unsigned char compbuf[256 * NS_MAXCDNAME];
+ const unsigned char *dnptrs[256], **lastdnptr;
+
+ memset(compbuf, 0, sizeof(compbuf));
+ memset(dnptrs, 0, sizeof(dnptrs));
+ dnptrs[0] = compbuf;
+ lastdnptr = &dnptrs[255];
+
+ do {
+ /* Consume the COMMA token if peeked. */
+ if (token == COMMA)
+ skip_token(&val, NULL, cfile);
+
+ /* Get next (or first) value. */
+ token = next_token(&val, &len, cfile);
+
+ if (token != STRING) {
+ parse_warn(cfile, "Expecting a domain string.");
+ return NULL;
+ }
+
+ /* If compression pointers are enabled, compress. If not,
+ * just pack the names in series into the buffer.
+ */
+ if (compress) {
+ result = MRns_name_compress(val, compbuf + clen,
+ sizeof(compbuf) - clen,
+ dnptrs, lastdnptr);
+
+ if (result < 0) {
+ parse_warn(cfile, "Error compressing domain "
+ "list: %m");
+ return NULL;
+ }
+
+ clen += result;
+ } else {
+ result = MRns_name_pton(val, compbuf + clen,
+ sizeof(compbuf) - clen);
+
+ /* result == 1 means the input was fully qualified.
+ * result == 0 means the input wasn't.
+ * result == -1 means bad things.
+ */
+ if (result < 0) {
+ parse_warn(cfile, "Error assembling domain "
+ "list: %m");
+ return NULL;
+ }
+
+ /*
+ * We need to figure out how many bytes to increment
+ * our buffer pointer since pton doesn't tell us.
+ */
+ while (compbuf[clen] != 0)
+ clen += compbuf[clen] + 1;
+
+ /* Count the last label (0). */
+ clen++;
+ }
+
+ if (clen > sizeof(compbuf))
+ log_fatal("Impossible error at %s:%d", MDL);
+
+ token = peek_token(&val, NULL, cfile);
+ } while (token == COMMA);
+
+ if (!make_const_data(&t, compbuf, clen, 1, 1, MDL))
+ log_fatal("No memory for domain list object.");
+
+ return t;
+}
+
diff --git a/common/print.c b/common/print.c
new file mode 100644
index 0000000..dfe0593
--- /dev/null
+++ b/common/print.c
@@ -0,0 +1,1519 @@
+/* print.c
+
+ Turn data structures into printable text. */
+
+/*
+ * Copyright (c) 2009-2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2007 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+
+int db_time_format = DEFAULT_TIME_FORMAT;
+
+char *quotify_string (const char *s, const char *file, int line)
+{
+ unsigned len = 0;
+ const char *sp;
+ char *buf, *nsp;
+
+ for (sp = s; sp && *sp; sp++) {
+ if (*sp == ' ')
+ len++;
+ else if (!isascii ((int)*sp) || !isprint ((int)*sp))
+ len += 4;
+ else if (*sp == '"' || *sp == '\\')
+ len += 2;
+ else
+ len++;
+ }
+
+ buf = dmalloc (len + 1, file, line);
+ if (buf) {
+ nsp = buf;
+ for (sp = s; sp && *sp; sp++) {
+ if (*sp == ' ')
+ *nsp++ = ' ';
+ else if (!isascii ((int)*sp) || !isprint ((int)*sp)) {
+ sprintf (nsp, "\\%03o",
+ *(const unsigned char *)sp);
+ nsp += 4;
+ } else if (*sp == '"' || *sp == '\\') {
+ *nsp++ = '\\';
+ *nsp++ = *sp;
+ } else
+ *nsp++ = *sp;
+ }
+ *nsp++ = 0;
+ }
+ return buf;
+}
+
+char *quotify_buf (const unsigned char *s, unsigned len,
+ const char *file, int line)
+{
+ unsigned nulen = 0;
+ char *buf, *nsp;
+ int i;
+
+ for (i = 0; i < len; i++) {
+ if (s [i] == ' ')
+ nulen++;
+ else if (!isascii (s [i]) || !isprint (s [i]))
+ nulen += 4;
+ else if (s [i] == '"' || s [i] == '\\')
+ nulen += 2;
+ else
+ nulen++;
+ }
+
+ buf = dmalloc (nulen + 1, MDL);
+ if (buf) {
+ nsp = buf;
+ for (i = 0; i < len; i++) {
+ if (s [i] == ' ')
+ *nsp++ = ' ';
+ else if (!isascii (s [i]) || !isprint (s [i])) {
+ sprintf (nsp, "\\%03o", s [i]);
+ nsp += 4;
+ } else if (s [i] == '"' || s [i] == '\\') {
+ *nsp++ = '\\';
+ *nsp++ = s [i];
+ } else
+ *nsp++ = s [i];
+ }
+ *nsp++ = 0;
+ }
+ return buf;
+}
+
+char *print_base64 (const unsigned char *buf, unsigned len,
+ const char *file, int line)
+{
+ char *s, *b;
+ unsigned bl;
+ int i;
+ unsigned val, extra;
+ static char to64 [] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ bl = ((len * 4 + 2) / 3) + 1;
+ b = dmalloc (bl + 1, file, line);
+ if (!b)
+ return (char *)0;
+
+ i = 0;
+ s = b;
+ while (i != len) {
+ val = buf [i++];
+ extra = val & 3;
+ val = val >> 2;
+ *s++ = to64 [val];
+ if (i == len) {
+ *s++ = to64 [extra << 4];
+ *s++ = '=';
+ break;
+ }
+ val = (extra << 8) + buf [i++];
+ extra = val & 15;
+ val = val >> 4;
+ *s++ = to64 [val];
+ if (i == len) {
+ *s++ = to64 [extra << 2];
+ *s++ = '=';
+ break;
+ }
+ val = (extra << 8) + buf [i++];
+ extra = val & 0x3f;
+ val = val >> 6;
+ *s++ = to64 [val];
+ *s++ = to64 [extra];
+ }
+ if (!len)
+ *s++ = '=';
+ *s++ = 0;
+ if (s > b + bl + 1)
+ abort ();
+ return b;
+}
+
+char *print_hw_addr (htype, hlen, data)
+ const int htype;
+ const int hlen;
+ const unsigned char *data;
+{
+ static char habuf [49];
+ char *s;
+ int i;
+
+ if (hlen <= 0)
+ habuf [0] = 0;
+ else {
+ s = habuf;
+ for (i = 0; i < hlen; i++) {
+ sprintf (s, "%02x", data [i]);
+ s += strlen (s);
+ *s++ = ':';
+ }
+ *--s = 0;
+ }
+ return habuf;
+}
+
+void print_lease (lease)
+ struct lease *lease;
+{
+ struct tm *t;
+ char tbuf [32];
+
+ log_debug (" Lease %s",
+ piaddr (lease -> ip_addr));
+
+ t = gmtime (&lease -> starts);
+ strftime (tbuf, sizeof tbuf, "%Y/%m/%d %H:%M:%S", t);
+ log_debug (" start %s", tbuf);
+
+ t = gmtime (&lease -> ends);
+ strftime (tbuf, sizeof tbuf, "%Y/%m/%d %H:%M:%S", t);
+ log_debug (" end %s", tbuf);
+
+ if (lease -> hardware_addr.hlen)
+ log_debug (" hardware addr = %s",
+ print_hw_addr (lease -> hardware_addr.hbuf [0],
+ lease -> hardware_addr.hlen - 1,
+ &lease -> hardware_addr.hbuf [1]));
+ log_debug (" host %s ",
+ lease -> host ? lease -> host -> name : "<none>");
+}
+
+#if defined (DEBUG_PACKET)
+void dump_packet_option (struct option_cache *oc,
+ struct packet *packet,
+ struct lease *lease,
+ struct client_state *client,
+ struct option_state *in_options,
+ struct option_state *cfg_options,
+ struct binding_scope **scope,
+ struct universe *u, void *foo)
+{
+ const char *name, *dot;
+ struct data_string ds;
+ memset (&ds, 0, sizeof ds);
+
+ if (u != &dhcp_universe) {
+ name = u -> name;
+ dot = ".";
+ } else {
+ name = "";
+ dot = "";
+ }
+ if (evaluate_option_cache (&ds, packet, lease, client,
+ in_options, cfg_options, scope, oc, MDL)) {
+ log_debug (" option %s%s%s %s;\n",
+ name, dot, oc -> option -> name,
+ pretty_print_option (oc -> option,
+ ds.data, ds.len, 1, 1));
+ data_string_forget (&ds, MDL);
+ }
+}
+
+void dump_packet (tp)
+ struct packet *tp;
+{
+ struct dhcp_packet *tdp = tp -> raw;
+
+ log_debug ("packet length %d", tp -> packet_length);
+ log_debug ("op = %d htype = %d hlen = %d hops = %d",
+ tdp -> op, tdp -> htype, tdp -> hlen, tdp -> hops);
+ log_debug ("xid = %x secs = %ld flags = %x",
+ tdp -> xid, (unsigned long)tdp -> secs, tdp -> flags);
+ log_debug ("ciaddr = %s", inet_ntoa (tdp -> ciaddr));
+ log_debug ("yiaddr = %s", inet_ntoa (tdp -> yiaddr));
+ log_debug ("siaddr = %s", inet_ntoa (tdp -> siaddr));
+ log_debug ("giaddr = %s", inet_ntoa (tdp -> giaddr));
+ log_debug ("chaddr = %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x",
+ ((unsigned char *)(tdp -> chaddr)) [0],
+ ((unsigned char *)(tdp -> chaddr)) [1],
+ ((unsigned char *)(tdp -> chaddr)) [2],
+ ((unsigned char *)(tdp -> chaddr)) [3],
+ ((unsigned char *)(tdp -> chaddr)) [4],
+ ((unsigned char *)(tdp -> chaddr)) [5]);
+ log_debug ("filename = %s", tdp -> file);
+ log_debug ("server_name = %s", tdp -> sname);
+ if (tp -> options_valid) {
+ int i;
+
+ for (i = 0; i < tp -> options -> universe_count; i++) {
+ if (tp -> options -> universes [i]) {
+ option_space_foreach (tp, (struct lease *)0,
+ (struct client_state *)0,
+ (struct option_state *)0,
+ tp -> options,
+ &global_scope,
+ universes [i], 0,
+ dump_packet_option);
+ }
+ }
+ }
+ log_debug ("%s", "");
+}
+#endif
+
+void dump_raw (buf, len)
+ const unsigned char *buf;
+ unsigned len;
+{
+ int i;
+ char lbuf [80];
+ int lbix = 0;
+
+/*
+ 1 2 3 4 5 6 7
+01234567890123456789012345678901234567890123456789012345678901234567890123
+280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .................
+*/
+
+ memset(lbuf, ' ', 79);
+ lbuf [79] = 0;
+
+ for (i = 0; i < len; i++) {
+ if ((i & 15) == 0) {
+ if (lbix) {
+ lbuf[53]=' ';
+ lbuf[54]=' ';
+ lbuf[55]=' ';
+ lbuf[73]='\0';
+ log_info ("%s", lbuf);
+ }
+ memset(lbuf, ' ', 79);
+ lbuf [79] = 0;
+ sprintf (lbuf, "%03x:", i);
+ lbix = 4;
+ } else if ((i & 7) == 0)
+ lbuf [lbix++] = ' ';
+
+ if(isprint(buf[i])) {
+ lbuf[56+(i%16)]=buf[i];
+ } else {
+ lbuf[56+(i%16)]='.';
+ }
+
+ sprintf (&lbuf [lbix], " %02x", buf [i]);
+ lbix += 3;
+ lbuf[lbix]=' ';
+
+ }
+ lbuf[53]=' ';
+ lbuf[54]=' ';
+ lbuf[55]=' ';
+ lbuf[73]='\0';
+ log_info ("%s", lbuf);
+}
+
+void hash_dump (table)
+ struct hash_table *table;
+{
+ int i;
+ struct hash_bucket *bp;
+
+ if (!table)
+ return;
+
+ for (i = 0; i < table -> hash_count; i++) {
+ if (!table -> buckets [i])
+ continue;
+ log_info ("hash bucket %d:", i);
+ for (bp = table -> buckets [i]; bp; bp = bp -> next) {
+ if (bp -> len)
+ dump_raw (bp -> name, bp -> len);
+ else
+ log_info ("%s", (const char *)bp -> name);
+ }
+ }
+}
+
+/*
+ * print a string as hex. This only outputs
+ * colon separated hex list no matter what
+ * the input looks like. See print_hex
+ * for a function that prints either cshl
+ * or a string if all bytes are printible
+ * It only uses limit characters from buf
+ * and doesn't do anything if buf == NULL
+ *
+ * len - length of data
+ * data - input data
+ * limit - length of buf to use
+ * buf - output buffer
+ */
+void print_hex_only (len, data, limit, buf)
+ unsigned len;
+ const u_int8_t *data;
+ unsigned limit;
+ char *buf;
+{
+ unsigned i;
+
+ if ((buf == NULL) || (limit < 3))
+ return;
+
+ for (i = 0; (i < limit / 3) && (i < len); i++) {
+ sprintf(&buf[i*3], "%02x:", data[i]);
+ }
+ buf[(i * 3) - 1] = 0;
+ return;
+}
+
+/*
+ * print a string as either text if all the characters
+ * are printable or colon separated hex if they aren't
+ *
+ * len - length of data
+ * data - input data
+ * limit - length of buf to use
+ * buf - output buffer
+ */
+void print_hex_or_string (len, data, limit, buf)
+ unsigned len;
+ const u_int8_t *data;
+ unsigned limit;
+ char *buf;
+{
+ unsigned i;
+ if ((buf == NULL) || (limit < 3))
+ return;
+
+ for (i = 0; (i < (limit - 3)) && (i < len); i++) {
+ if (!isascii(data[i]) || !isprint(data[i])) {
+ print_hex_only(len, data, limit, buf);
+ return;
+ }
+ }
+
+ buf[0] = '"';
+ i = len;
+ if (i > (limit - 3))
+ i = limit - 3;
+ memcpy(&buf[1], data, i);
+ buf[i + 1] = '"';
+ buf[i + 2] = 0;
+ return;
+}
+
+/*
+ * print a string as either hex or text
+ * using static buffers to hold the output
+ *
+ * len - length of data
+ * data - input data
+ * limit - length of buf
+ * buf_num - the output buffer to use
+ */
+#define HBLEN 1024
+char *print_hex(len, data, limit, buf_num)
+ unsigned len;
+ const u_int8_t *data;
+ unsigned limit;
+ unsigned buf_num;
+{
+ static char hex_buf_1[HBLEN + 1];
+ static char hex_buf_2[HBLEN + 1];
+ static char hex_buf_3[HBLEN + 1];
+ char *hex_buf;
+
+ switch(buf_num) {
+ case 0:
+ hex_buf = hex_buf_1;
+ if (limit >= sizeof(hex_buf_1))
+ limit = sizeof(hex_buf_1);
+ break;
+ case 1:
+ hex_buf = hex_buf_2;
+ if (limit >= sizeof(hex_buf_2))
+ limit = sizeof(hex_buf_2);
+ break;
+ case 2:
+ hex_buf = hex_buf_3;
+ if (limit >= sizeof(hex_buf_3))
+ limit = sizeof(hex_buf_3);
+ break;
+ default:
+ return(NULL);
+ }
+
+ print_hex_or_string(len, data, limit, hex_buf);
+ return(hex_buf);
+}
+
+#define DQLEN 80
+
+char *print_dotted_quads (len, data)
+ unsigned len;
+ const u_int8_t *data;
+{
+ static char dq_buf [DQLEN + 1];
+ int i;
+ char *s;
+
+ s = &dq_buf [0];
+
+ i = 0;
+
+ /* %Audit% Loop bounds checks to 21 bytes. %2004.06.17,Safe%
+ * The sprintf can't exceed 18 bytes, and since the loop enforces
+ * 21 bytes of space per iteration at no time can we exit the
+ * loop without at least 3 bytes spare.
+ */
+ do {
+ sprintf (s, "%u.%u.%u.%u, ",
+ data [i], data [i + 1], data [i + 2], data [i + 3]);
+ s += strlen (s);
+ i += 4;
+ } while ((s - &dq_buf [0] > DQLEN - 21) &&
+ i + 3 < len);
+ if (i == len)
+ s [-2] = 0;
+ else
+ strcpy (s, "...");
+ return dq_buf;
+}
+
+char *print_dec_1 (val)
+ unsigned long val;
+{
+ static char vbuf [32];
+ sprintf (vbuf, "%lu", val);
+ return vbuf;
+}
+
+char *print_dec_2 (val)
+ unsigned long val;
+{
+ static char vbuf [32];
+ sprintf (vbuf, "%lu", val);
+ return vbuf;
+}
+
+static unsigned print_subexpression (struct expression *, char *, unsigned);
+
+static unsigned print_subexpression (expr, buf, len)
+ struct expression *expr;
+ char *buf;
+ unsigned len;
+{
+ unsigned rv, left;
+ const char *s;
+
+ switch (expr -> op) {
+ case expr_none:
+ if (len > 3) {
+ strcpy (buf, "nil");
+ return 3;
+ }
+ break;
+
+ case expr_match:
+ if (len > 7) {
+ strcpy (buf, "(match)");
+ return 7;
+ }
+ break;
+
+ case expr_check:
+ rv = 10 + strlen (expr -> data.check -> name);
+ if (len > rv) {
+ sprintf (buf, "(check %s)",
+ expr -> data.check -> name);
+ return rv;
+ }
+ break;
+
+ case expr_equal:
+ if (len > 6) {
+ rv = 4;
+ strcpy (buf, "(eq ");
+ rv += print_subexpression (expr -> data.equal [0],
+ buf + rv, len - rv - 2);
+ buf [rv++] = ' ';
+ rv += print_subexpression (expr -> data.equal [1],
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_not_equal:
+ if (len > 7) {
+ rv = 5;
+ strcpy (buf, "(neq ");
+ rv += print_subexpression (expr -> data.equal [0],
+ buf + rv, len - rv - 2);
+ buf [rv++] = ' ';
+ rv += print_subexpression (expr -> data.equal [1],
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_regex_match:
+ if (len > 10) {
+ rv = 4;
+ strcpy(buf, "(regex ");
+ rv += print_subexpression(expr->data.equal[0],
+ buf + rv, len - rv - 2);
+ buf[rv++] = ' ';
+ rv += print_subexpression(expr->data.equal[1],
+ buf + rv, len - rv - 1);
+ buf[rv++] = ')';
+ buf[rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_substring:
+ if (len > 11) {
+ rv = 8;
+ strcpy (buf, "(substr ");
+ rv += print_subexpression (expr -> data.substring.expr,
+ buf + rv, len - rv - 3);
+ buf [rv++] = ' ';
+ rv += print_subexpression
+ (expr -> data.substring.offset,
+ buf + rv, len - rv - 2);
+ buf [rv++] = ' ';
+ rv += print_subexpression (expr -> data.substring.len,
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_suffix:
+ if (len > 10) {
+ rv = 8;
+ strcpy (buf, "(suffix ");
+ rv += print_subexpression (expr -> data.suffix.expr,
+ buf + rv, len - rv - 2);
+ if (len > rv)
+ buf [rv++] = ' ';
+ rv += print_subexpression (expr -> data.suffix.len,
+ buf + rv, len - rv - 1);
+ if (len > rv)
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_lcase:
+ if (len > 9) {
+ rv = 7;
+ strcpy(buf, "(lcase ");
+ rv += print_subexpression(expr->data.lcase,
+ buf + rv, len - rv - 1);
+ buf[rv++] = ')';
+ buf[rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_ucase:
+ if (len > 9) {
+ rv = 7;
+ strcpy(buf, "(ucase ");
+ rv += print_subexpression(expr->data.ucase,
+ buf + rv, len - rv - 1);
+ buf[rv++] = ')';
+ buf[rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_concat:
+ if (len > 10) {
+ rv = 8;
+ strcpy (buf, "(concat ");
+ rv += print_subexpression (expr -> data.concat [0],
+ buf + rv, len - rv - 2);
+ buf [rv++] = ' ';
+ rv += print_subexpression (expr -> data.concat [1],
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_pick_first_value:
+ if (len > 8) {
+ rv = 6;
+ strcpy (buf, "(pick1st ");
+ rv += print_subexpression
+ (expr -> data.pick_first_value.car,
+ buf + rv, len - rv - 2);
+ buf [rv++] = ' ';
+ rv += print_subexpression
+ (expr -> data.pick_first_value.cdr,
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_host_lookup:
+ rv = 15 + strlen (expr -> data.host_lookup -> hostname);
+ if (len > rv) {
+ sprintf (buf, "(dns-lookup %s)",
+ expr -> data.host_lookup -> hostname);
+ return rv;
+ }
+ break;
+
+ case expr_and:
+ s = "and";
+ binop:
+ rv = strlen (s);
+ if (len > rv + 4) {
+ buf [0] = '(';
+ strcpy (&buf [1], s);
+ rv += 1;
+ buf [rv++] = ' ';
+ rv += print_subexpression (expr -> data.and [0],
+ buf + rv, len - rv - 2);
+ buf [rv++] = ' ';
+ rv += print_subexpression (expr -> data.and [1],
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_or:
+ s = "or";
+ goto binop;
+
+ case expr_add:
+ s = "+";
+ goto binop;
+
+ case expr_subtract:
+ s = "-";
+ goto binop;
+
+ case expr_multiply:
+ s = "*";
+ goto binop;
+
+ case expr_divide:
+ s = "/";
+ goto binop;
+
+ case expr_remainder:
+ s = "%";
+ goto binop;
+
+ case expr_binary_and:
+ s = "&";
+ goto binop;
+
+ case expr_binary_or:
+ s = "|";
+ goto binop;
+
+ case expr_binary_xor:
+ s = "^";
+ goto binop;
+
+ case expr_not:
+ if (len > 6) {
+ rv = 5;
+ strcpy (buf, "(not ");
+ rv += print_subexpression (expr -> data.not,
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_config_option:
+ s = "cfg-option";
+ goto dooption;
+
+ case expr_option:
+ s = "option";
+ dooption:
+ rv = strlen (s) + 2 + (strlen (expr -> data.option -> name) +
+ strlen (expr -> data.option -> universe -> name));
+ if (len > rv) {
+ sprintf (buf, "(option %s.%s)",
+ expr -> data.option -> universe -> name,
+ expr -> data.option -> name);
+ return rv;
+ }
+ break;
+
+ case expr_hardware:
+ if (len > 10) {
+ strcpy (buf, "(hardware)");
+ return 10;
+ }
+ break;
+
+ case expr_packet:
+ if (len > 10) {
+ rv = 8;
+ strcpy (buf, "(substr ");
+ rv += print_subexpression (expr -> data.packet.offset,
+ buf + rv, len - rv - 2);
+ buf [rv++] = ' ';
+ rv += print_subexpression (expr -> data.packet.len,
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_const_data:
+ s = print_hex_1 (expr -> data.const_data.len,
+ expr -> data.const_data.data, len);
+ rv = strlen (s);
+ if (rv >= len)
+ rv = len - 1;
+ strncpy (buf, s, rv);
+ buf [rv] = 0;
+ return rv;
+
+ case expr_encapsulate:
+ rv = 13;
+ strcpy (buf, "(encapsulate ");
+ rv += expr -> data.encapsulate.len;
+ if (rv + 2 > len)
+ rv = len - 2;
+ strncpy (buf,
+ (const char *)expr -> data.encapsulate.data, rv - 13);
+ buf [rv++] = ')';
+ buf [rv++] = 0;
+ break;
+
+ case expr_extract_int8:
+ if (len > 7) {
+ rv = 6;
+ strcpy (buf, "(int8 ");
+ rv += print_subexpression (expr -> data.extract_int,
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_extract_int16:
+ if (len > 8) {
+ rv = 7;
+ strcpy (buf, "(int16 ");
+ rv += print_subexpression (expr -> data.extract_int,
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_extract_int32:
+ if (len > 8) {
+ rv = 7;
+ strcpy (buf, "(int32 ");
+ rv += print_subexpression (expr -> data.extract_int,
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_encode_int8:
+ if (len > 7) {
+ rv = 6;
+ strcpy (buf, "(to-int8 ");
+ rv += print_subexpression (expr -> data.encode_int,
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_encode_int16:
+ if (len > 8) {
+ rv = 7;
+ strcpy (buf, "(to-int16 ");
+ rv += print_subexpression (expr -> data.encode_int,
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_encode_int32:
+ if (len > 8) {
+ rv = 7;
+ strcpy (buf, "(to-int32 ");
+ rv += print_subexpression (expr -> data.encode_int,
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_const_int:
+ s = print_dec_1 (expr -> data.const_int);
+ rv = strlen (s);
+ if (len > rv) {
+ strcpy (buf, s);
+ return rv;
+ }
+ break;
+
+ case expr_exists:
+ rv = 10 + (strlen (expr -> data.option -> name) +
+ strlen (expr -> data.option -> universe -> name));
+ if (len > rv) {
+ sprintf (buf, "(exists %s.%s)",
+ expr -> data.option -> universe -> name,
+ expr -> data.option -> name);
+ return rv;
+ }
+ break;
+
+ case expr_variable_exists:
+ rv = 10 + strlen (expr -> data.variable);
+ if (len > rv) {
+ sprintf (buf, "(defined %s)", expr -> data.variable);
+ return rv;
+ }
+ break;
+
+ case expr_variable_reference:
+ rv = strlen (expr -> data.variable);
+ if (len > rv) {
+ sprintf (buf, "%s", expr -> data.variable);
+ return rv;
+ }
+ break;
+
+ case expr_known:
+ s = "known";
+ astring:
+ rv = strlen (s);
+ if (len > rv) {
+ strcpy (buf, s);
+ return rv;
+ }
+ break;
+
+ case expr_leased_address:
+ s = "leased-address";
+ goto astring;
+
+ case expr_client_state:
+ s = "client-state";
+ goto astring;
+
+ case expr_host_decl_name:
+ s = "host-decl-name";
+ goto astring;
+
+ case expr_lease_time:
+ s = "lease-time";
+ goto astring;
+
+ case expr_static:
+ s = "static";
+ goto astring;
+
+ case expr_filename:
+ s = "filename";
+ goto astring;
+
+ case expr_sname:
+ s = "server-name";
+ goto astring;
+
+ case expr_reverse:
+ if (len > 11) {
+ rv = 13;
+ strcpy (buf, "(reverse ");
+ rv += print_subexpression (expr -> data.reverse.width,
+ buf + rv, len - rv - 2);
+ buf [rv++] = ' ';
+ rv += print_subexpression (expr -> data.reverse.buffer,
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_binary_to_ascii:
+ if (len > 5) {
+ rv = 9;
+ strcpy (buf, "(b2a ");
+ rv += print_subexpression (expr -> data.b2a.base,
+ buf + rv, len - rv - 4);
+ buf [rv++] = ' ';
+ rv += print_subexpression (expr -> data.b2a.width,
+ buf + rv, len - rv - 3);
+ buf [rv++] = ' ';
+ rv += print_subexpression (expr -> data.b2a.separator,
+ buf + rv, len - rv - 2);
+ buf [rv++] = ' ';
+ rv += print_subexpression (expr -> data.b2a.buffer,
+ buf + rv, len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_dns_transaction:
+ rv = 10;
+ if (len < rv + 2) {
+ buf [0] = '(';
+ strcpy (&buf [1], "ns-update ");
+ while (len < rv + 2) {
+ rv += print_subexpression
+ (expr -> data.dns_transaction.car,
+ buf + rv, len - rv - 2);
+ buf [rv++] = ' ';
+ expr = expr -> data.dns_transaction.cdr;
+ }
+ buf [rv - 1] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ return 0;
+
+ case expr_ns_delete:
+ s = "delete";
+ left = 4;
+ goto dodnsupd;
+ case expr_ns_exists:
+ s = "exists";
+ left = 4;
+ goto dodnsupd;
+ case expr_ns_not_exists:
+ s = "not_exists";
+ left = 4;
+ goto dodnsupd;
+ case expr_ns_add:
+ s = "update";
+ left = 5;
+ dodnsupd:
+ rv = strlen (s);
+ if (len > strlen (s) + 1) {
+ buf [0] = '(';
+ strcpy (buf + 1, s);
+ rv++;
+ buf [rv++] = ' ';
+ s = print_dec_1 (expr -> data.ns_add.rrclass);
+ if (len > rv + strlen (s) + left) {
+ strcpy (&buf [rv], s);
+ rv += strlen (&buf [rv]);
+ }
+ buf [rv++] = ' ';
+ left--;
+ s = print_dec_1 (expr -> data.ns_add.rrtype);
+ if (len > rv + strlen (s) + left) {
+ strcpy (&buf [rv], s);
+ rv += strlen (&buf [rv]);
+ }
+ buf [rv++] = ' ';
+ left--;
+ rv += print_subexpression
+ (expr -> data.ns_add.rrname,
+ buf + rv, len - rv - left);
+ buf [rv++] = ' ';
+ left--;
+ rv += print_subexpression
+ (expr -> data.ns_add.rrdata,
+ buf + rv, len - rv - left);
+ buf [rv++] = ' ';
+ left--;
+ rv += print_subexpression
+ (expr -> data.ns_add.ttl,
+ buf + rv, len - rv - left);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_null:
+ if (len > 6) {
+ strcpy (buf, "(null)");
+ return 6;
+ }
+ break;
+ case expr_funcall:
+ rv = 12 + strlen (expr -> data.funcall.name);
+ if (len > rv + 1) {
+ strcpy (buf, "(funcall ");
+ strcpy (buf + 9, expr -> data.funcall.name);
+ buf [rv++] = ' ';
+ rv += print_subexpression
+ (expr -> data.funcall.arglist, buf + rv,
+ len - rv - 1);
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_arg:
+ rv = print_subexpression (expr -> data.arg.val, buf, len);
+ if (expr -> data.arg.next && rv + 2 < len) {
+ buf [rv++] = ' ';
+ rv += print_subexpression (expr -> data.arg.next,
+ buf, len);
+ if (rv + 1 < len)
+ buf [rv++] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_function:
+ rv = 9;
+ if (len > rv + 1) {
+ struct string_list *foo;
+ strcpy (buf, "(function");
+ for (foo = expr -> data.func -> args;
+ foo; foo = foo -> next) {
+ if (len > rv + 2 + strlen (foo -> string)) {
+ buf [rv - 1] = ' ';
+ strcpy (&buf [rv], foo -> string);
+ rv += strlen (foo -> string);
+ }
+ }
+ buf [rv++] = ')';
+ buf [rv] = 0;
+ return rv;
+ }
+ break;
+
+ case expr_gethostname:
+ if (len > 13) {
+ strcpy(buf, "(gethostname)");
+ return 13;
+ }
+ break;
+
+ default:
+ log_fatal("Impossible case at %s:%d (undefined expression "
+ "%d).", MDL, expr->op);
+ break;
+ }
+ return 0;
+}
+
+void print_expression (name, expr)
+ const char *name;
+ struct expression *expr;
+{
+ char buf [1024];
+
+ print_subexpression (expr, buf, sizeof buf);
+ log_info ("%s: %s", name, buf);
+}
+
+int token_print_indent_concat (FILE *file, int col, int indent,
+ const char *prefix,
+ const char *suffix, ...)
+{
+ va_list list;
+ unsigned len;
+ char *s, *t, *u;
+
+ va_start (list, suffix);
+ s = va_arg (list, char *);
+ len = 0;
+ while (s) {
+ len += strlen (s);
+ s = va_arg (list, char *);
+ }
+ va_end (list);
+
+ t = dmalloc (len + 1, MDL);
+ if (!t)
+ log_fatal ("token_print_indent: no memory for copy buffer");
+
+ va_start (list, suffix);
+ s = va_arg (list, char *);
+ u = t;
+ while (s) {
+ len = strlen (s);
+ strcpy (u, s);
+ u += len;
+ s = va_arg (list, char *);
+ }
+ va_end (list);
+
+ col = token_print_indent (file, col, indent,
+ prefix, suffix, t);
+ dfree (t, MDL);
+ return col;
+}
+
+int token_indent_data_string (FILE *file, int col, int indent,
+ const char *prefix, const char *suffix,
+ struct data_string *data)
+{
+ int i;
+ char *buf;
+ char obuf [3];
+
+ /* See if this is just ASCII. */
+ for (i = 0; i < data -> len; i++)
+ if (!isascii (data -> data [i]) ||
+ !isprint (data -> data [i]))
+ break;
+
+ /* If we have a purely ASCII string, output it as text. */
+ if (i == data -> len) {
+ buf = dmalloc (data -> len + 3, MDL);
+ if (buf) {
+ buf [0] = '"';
+ memcpy (buf + 1, data -> data, data -> len);
+ buf [data -> len + 1] = '"';
+ buf [data -> len + 2] = 0;
+ i = token_print_indent (file, col, indent,
+ prefix, suffix, buf);
+ dfree (buf, MDL);
+ return i;
+ }
+ }
+
+ for (i = 0; i < data -> len; i++) {
+ sprintf (obuf, "%2.2x", data -> data [i]);
+ col = token_print_indent (file, col, indent,
+ i == 0 ? prefix : "",
+ (i + 1 == data -> len
+ ? suffix
+ : ""), obuf);
+ if (i + 1 != data -> len)
+ col = token_print_indent (file, col, indent,
+ prefix, suffix, ":");
+ }
+ return col;
+}
+
+int token_print_indent (FILE *file, int col, int indent,
+ const char *prefix,
+ const char *suffix, const char *buf)
+{
+ int len = 0;
+ if (prefix != NULL)
+ len += strlen (prefix);
+ if (buf != NULL)
+ len += strlen (buf);
+
+ if (col + len > 79) {
+ if (indent + len < 79) {
+ indent_spaces (file, indent);
+ col = indent;
+ } else {
+ indent_spaces (file, col);
+ col = len > 79 ? 0 : 79 - len - 1;
+ }
+ } else if (prefix && *prefix) {
+ fputs (prefix, file);
+ col += strlen (prefix);
+ }
+ if ((buf != NULL) && (*buf != 0)) {
+ fputs (buf, file);
+ col += strlen(buf);
+ }
+ if (suffix && *suffix) {
+ if (col + strlen (suffix) > 79) {
+ indent_spaces (file, indent);
+ col = indent;
+ } else {
+ fputs (suffix, file);
+ col += strlen (suffix);
+ }
+ }
+ return col;
+}
+
+void indent_spaces (FILE *file, int indent)
+{
+ int i;
+ fputc ('\n', file);
+ for (i = 0; i < indent; i++)
+ fputc (' ', file);
+}
+
+#if defined (NSUPDATE)
+#if defined (DEBUG_DNS_UPDATES)
+/*
+ * direction outbound (messages to the dns server)
+ * inbound (messages from the dns server)
+ * ddns_cb is the control block associated with the message
+ * result is the result from the dns code. For outbound calls
+ * it is from the call to pass the message to the dns library.
+ * For inbound calls it is from the event returned by the library.
+ *
+ * For outbound messages we print whatever we think is interesting
+ * from the control block.
+ * For inbound messages we only print the transaction id pointer
+ * and the result and expect that the user will match them up as
+ * necessary. Note well: the transaction information is opaque to
+ * us so we simply print the pointer to it. This should be sufficient
+ * to match requests and replys in a short sequence but is awkward
+ * when trying to use it for longer sequences.
+ */
+void
+print_dns_status (int direction,
+ struct dhcp_ddns_cb *ddns_cb,
+ isc_result_t result)
+{
+ char obuf[1024];
+ char *s = obuf, *end = &obuf[sizeof(obuf)-2];
+ char *en;
+ const char *result_str;
+ char ddns_address[
+ sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+
+ if (direction == DDNS_PRINT_INBOUND) {
+ log_info("DDNS reply: id ptr %p, result: %s",
+ ddns_cb->transaction, isc_result_totext(result));
+ return;
+ }
+
+ /*
+ * To avoid having to figure out if any of the strings
+ * aren't NULL terminated, just 0 the whole string
+ */
+ memset(obuf, 0, 1024);
+
+ en = "DDNS request: id ptr ";
+ if (s + strlen(en) + 16 < end) {
+ sprintf(s, "%s%p", en, ddns_cb->transaction);
+ s += strlen(s);
+ } else {
+ goto bailout;
+ }
+
+ switch (ddns_cb->state) {
+ case DDNS_STATE_ADD_FW_NXDOMAIN:
+ en = " add forward ";
+ break;
+ case DDNS_STATE_ADD_FW_YXDHCID:
+ en = " modify forward ";
+ break;
+
+ case DDNS_STATE_ADD_PTR:
+ en = " add reverse ";
+ break;
+
+ case DDNS_STATE_REM_FW_YXDHCID:
+ en = " remove forward ";
+ break;
+
+ case DDNS_STATE_REM_FW_NXRR:
+ en = " remove rrset ";
+ break;
+
+ case DDNS_STATE_REM_PTR:
+ en = " remove reverse ";
+ break;
+
+ case DDNS_STATE_CLEANUP:
+ en = " cleanup ";
+ break;
+
+ default:
+ en = " unknown state ";
+ break;
+ }
+
+ switch (ddns_cb->state) {
+ case DDNS_STATE_ADD_FW_NXDOMAIN:
+ case DDNS_STATE_ADD_FW_YXDHCID:
+ case DDNS_STATE_REM_FW_YXDHCID:
+ case DDNS_STATE_REM_FW_NXRR:
+ strcpy(ddns_address, piaddr(ddns_cb->address));
+ if (s + strlen(en) + strlen(ddns_address) +
+ ddns_cb->fwd_name.len + 5 < end) {
+ sprintf(s, "%s%s for %.*s", en, ddns_address,
+ ddns_cb->fwd_name.len,
+ ddns_cb->fwd_name.data);
+ s += strlen(s);
+ } else {
+ goto bailout;
+ }
+ break;
+
+ case DDNS_STATE_ADD_PTR:
+ case DDNS_STATE_REM_PTR:
+ if (s + strlen(en) + ddns_cb->fwd_name.len +
+ ddns_cb->rev_name.len + 5 < end) {
+ sprintf(s, "%s%.*s for %.*s", en,
+ ddns_cb->fwd_name.len,
+ ddns_cb->fwd_name.data,
+ ddns_cb->rev_name.len,
+ ddns_cb->rev_name.data);
+ s += strlen(s);
+ } else {
+ goto bailout;
+ }
+ break;
+
+ case DDNS_STATE_CLEANUP:
+ default:
+ if (s + strlen(en) < end) {
+ sprintf(s, "%s", en);
+ s += strlen(s);
+ } else {
+ goto bailout;
+ }
+ break;
+ }
+
+ en = " zone: ";
+ if (s + strlen(en) + strlen((char *)ddns_cb->zone_name) < end) {
+ sprintf(s, "%s%s", en, ddns_cb->zone_name);
+ s += strlen(s);
+ } else {
+ goto bailout;
+ }
+
+ en = " dhcid: ";
+ if (ddns_cb->dhcid.len > 0) {
+ if (s + strlen(en) + ddns_cb->dhcid.len-1 < end) {
+ strcpy(s, en);
+ s += strlen(s);
+ strncpy(s, (char *)ddns_cb->dhcid.data+1,
+ ddns_cb->dhcid.len-1);
+ s += strlen(s);
+ } else {
+ goto bailout;
+ }
+ } else {
+ en = " dhcid: <empty>";
+ if (s + strlen(en) < end) {
+ strcpy(s, en);
+ s += strlen(s);
+ } else {
+ goto bailout;
+ }
+ }
+
+ en = " ttl: ";
+ if (s + strlen(en) + 10 < end) {
+ sprintf(s, "%s%ld", en, ddns_cb->ttl);
+ s += strlen(s);
+ } else {
+ goto bailout;
+ }
+
+ en = " result: ";
+ result_str = isc_result_totext(result);
+ if (s + strlen(en) + strlen(result_str) < end) {
+ sprintf(s, "%s%s", en, result_str);
+ s += strlen(s);
+ } else {
+ goto bailout;
+ }
+
+ bailout:
+ /*
+ * We either finished building the string or ran out
+ * of space, print whatever we have in case it is useful
+ */
+ log_info("%s", obuf);
+
+ return;
+}
+#endif
+#endif /* NSUPDATE */
+
+/* Format the given time as "A; # B", where A is the format
+ * used by the parser, and B is the local time, for humans.
+ */
+const char *
+print_time(TIME t)
+{
+ static char buf[sizeof("epoch 9223372036854775807; "
+ "# Wed Jun 30 21:49:08 2147483647")];
+ static char buf1[sizeof("# Wed Jun 30 21:49:08 2147483647")];
+ time_t since_epoch;
+ /* The string: "6 2147483647/12/31 23:59:60;"
+ * is smaller than the other, used to declare the buffer size, so
+ * we can use one buffer for both.
+ */
+
+ if (t == MAX_TIME)
+ return "never;";
+
+ if (t < 0)
+ return NULL;
+
+ /* For those lucky enough to have a 128-bit time_t, ensure that
+ * whatever (corrupt) value we're given doesn't exceed the static
+ * buffer.
+ */
+#if (MAX_TIME > 0x7fffffffffffffff)
+ if (t > 0x7fffffffffffffff)
+ return NULL;
+#endif
+
+ if (db_time_format == LOCAL_TIME_FORMAT) {
+ since_epoch = mktime(localtime(&t));
+ if ((strftime(buf1, sizeof(buf1),
+ "# %a %b %d %H:%M:%S %Y",
+ localtime(&t)) == 0) ||
+ (snprintf(buf, sizeof(buf), "epoch %lu; %s",
+ (unsigned long)since_epoch, buf1) >= sizeof(buf)))
+ return NULL;
+
+ } else {
+ /* No bounds check for the year is necessary - in this case,
+ * strftime() will run out of space and assert an error.
+ */
+ if (strftime(buf, sizeof(buf), "%w %Y/%m/%d %H:%M:%S;",
+ gmtime(&t)) == 0)
+ return NULL;
+ }
+
+ return buf;
+}
diff --git a/common/raw.c b/common/raw.c
new file mode 100644
index 0000000..a15f8ee
--- /dev/null
+++ b/common/raw.c
@@ -0,0 +1,133 @@
+/* raw.c
+
+ BSD raw socket interface code... */
+
+/* XXX
+
+ It's not clear how this should work, and that lack of clarity is
+ terribly detrimental to the NetBSD 1.1 kernel - it crashes and
+ burns.
+
+ Using raw sockets ought to be a big win over using BPF or something
+ like it, because you don't need to deal with the complexities of
+ the physical layer, but it appears not to be possible with existing
+ raw socket implementations. This may be worth revisiting in the
+ future. For now, this code can probably be considered a curiosity.
+ Sigh. */
+
+/*
+ * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+
+#if defined (USE_RAW_SEND)
+#include <sys/uio.h>
+
+/* Generic interface registration routine... */
+void if_register_send (info)
+ struct interface_info *info;
+{
+ struct sockaddr_in name;
+ int sock;
+ struct socklist *tmp;
+ int flag;
+
+ /* Set up the address we're going to connect to. */
+ name.sin_family = AF_INET;
+ name.sin_port = local_port;
+ name.sin_addr.s_addr = htonl (INADDR_BROADCAST);
+ memset (name.sin_zero, 0, sizeof (name.sin_zero));
+
+ /* List addresses on which we're listening. */
+ if (!quiet_interface_discovery)
+ log_info ("Sending on %s, port %d",
+ piaddr (info -> address), htons (local_port));
+ if ((sock = socket (AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
+ log_fatal ("Can't create dhcp socket: %m");
+
+ /* Set the BROADCAST option so that we can broadcast DHCP responses. */
+ flag = 1;
+ if (setsockopt (sock, SOL_SOCKET, SO_BROADCAST,
+ &flag, sizeof flag) < 0)
+ log_fatal ("Can't set SO_BROADCAST option on dhcp socket: %m");
+
+ /* Set the IP_HDRINCL flag so that we can supply our own IP
+ headers... */
+ if (setsockopt (sock, IPPROTO_IP, IP_HDRINCL, &flag, sizeof flag) < 0)
+ log_fatal ("Can't set IP_HDRINCL flag: %m");
+
+ info -> wfdesc = sock;
+ if (!quiet_interface_discovery)
+ log_info ("Sending on Raw/%s%s%s",
+ info -> name,
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+
+void if_deregister_send (info)
+ struct interface_info *info;
+{
+ close (info -> wfdesc);
+ info -> wfdesc = -1;
+
+ if (!quiet_interface_discovery)
+ log_info ("Disabling output on Raw/%s%s%s",
+ info -> name,
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+
+size_t send_packet (interface, packet, raw, len, from, to, hto)
+ struct interface_info *interface;
+ struct packet *packet;
+ struct dhcp_packet *raw;
+ size_t len;
+ struct in_addr from;
+ struct sockaddr_in *to;
+ struct hardware *hto;
+{
+ unsigned char buf [256];
+ int bufp = 0;
+ struct iovec iov [2];
+ int result;
+
+ /* Assemble the headers... */
+ assemble_udp_ip_header (interface, buf, &bufp, from.s_addr,
+ to -> sin_addr.s_addr, to -> sin_port,
+ (unsigned char *)raw, len);
+
+ /* Fire it off */
+ iov [0].iov_base = (char *)buf;
+ iov [0].iov_len = bufp;
+ iov [1].iov_base = (char *)raw;
+ iov [1].iov_len = len;
+
+ result = writev(interface -> wfdesc, iov, 2);
+ if (result < 0)
+ log_error ("send_packet: %m");
+ return result;
+}
+#endif /* USE_SOCKET_SEND */
diff --git a/common/resolv.c b/common/resolv.c
new file mode 100644
index 0000000..526cebf
--- /dev/null
+++ b/common/resolv.c
@@ -0,0 +1,189 @@
+/* resolv.c
+
+ Parser for /etc/resolv.conf file. */
+
+/*
+ * Copyright (c) 2009,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2007 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+
+struct name_server *name_servers;
+struct domain_search_list *domains;
+char path_resolv_conf [] = _PATH_RESOLV_CONF;
+
+void read_resolv_conf (parse_time)
+ TIME parse_time;
+{
+ int file;
+ struct parse *cfile;
+ const char *val;
+ int token;
+ struct name_server *sp, *sl, *ns;
+ struct domain_search_list *dp, *dl, *nd;
+ isc_result_t status;
+
+ if ((file = open (path_resolv_conf, O_RDONLY)) < 0) {
+ log_error ("Can't open %s: %m", path_resolv_conf);
+ return;
+ }
+
+ cfile = NULL;
+ status = new_parse(&cfile, file, NULL, 0, path_resolv_conf, 1);
+ if (status != ISC_R_SUCCESS || cfile == NULL)
+ return;
+
+ do {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token == END_OF_FILE)
+ break;
+ else if (token == EOL)
+ continue;
+ else if (token == DOMAIN || token == SEARCH) {
+ do {
+ struct domain_search_list *nd, **dp;
+ char *dn;
+
+ dn = parse_host_name (cfile);
+ if (!dn)
+ break;
+
+ dp = &domains;
+ for (nd = domains; nd; nd = nd -> next) {
+ dp = &nd -> next;
+ if (!strcmp (nd -> domain, dn))
+ break;
+ }
+ if (!nd) {
+ nd = new_domain_search_list (MDL);
+ if (!nd)
+ log_fatal ("No memory for %s",
+ dn);
+ nd -> next =
+ (struct domain_search_list *)0;
+ *dp = nd;
+ nd -> domain = dn;
+ }
+ nd -> rcdate = parse_time;
+ token = peek_token (&val,
+ (unsigned *)0, cfile);
+ } while (token != EOL);
+ if (token != EOL) {
+ parse_warn (cfile,
+ "junk after domain declaration");
+ skip_to_semi (cfile);
+ }
+ skip_token(&val, (unsigned *)0, cfile);
+ } else if (token == NAMESERVER) {
+ struct name_server *ns, **sp;
+ struct iaddr iaddr;
+
+ parse_ip_addr (cfile, &iaddr);
+
+ sp = &name_servers;
+ for (ns = name_servers; ns; ns = ns -> next) {
+ sp = &ns -> next;
+ if (!memcmp (&ns -> addr.sin_addr,
+ iaddr.iabuf, iaddr.len))
+ break;
+ }
+ if (!ns) {
+ ns = new_name_server (MDL);
+ if (!ns)
+ log_fatal ("No memory for nameserver %s",
+ piaddr (iaddr));
+ ns -> next = (struct name_server *)0;
+ *sp = ns;
+ memcpy (&ns -> addr.sin_addr,
+ iaddr.iabuf, iaddr.len);
+#ifdef HAVE_SA_LEN
+ ns -> addr.sin_len = sizeof ns -> addr;
+#endif
+ ns -> addr.sin_family = AF_INET;
+ ns -> addr.sin_port = htons (53);
+ memset (ns -> addr.sin_zero, 0,
+ sizeof ns -> addr.sin_zero);
+ }
+ ns -> rcdate = parse_time;
+ skip_to_semi (cfile);
+ } else
+ skip_to_semi (cfile); /* Ignore what we don't grok. */
+ } while (1);
+ skip_token(&val, (unsigned *)0, cfile);
+
+ /* Lose servers that are no longer in /etc/resolv.conf. */
+ sl = (struct name_server *)0;
+ for (sp = name_servers; sp; sp = ns) {
+ ns = sp -> next;
+ if (sp -> rcdate != parse_time) {
+ if (sl)
+ sl -> next = sp -> next;
+ else
+ name_servers = sp -> next;
+ /* We can't actually free the name server structure,
+ because somebody might be hanging on to it. If
+ your /etc/resolv.conf file changes a lot, this
+ could be a noticeable memory leak. */
+ } else
+ sl = sp;
+ }
+
+ /* Lose domains that are no longer in /etc/resolv.conf. */
+ dl = (struct domain_search_list *)0;
+ for (dp = domains; dp; dp = nd) {
+ nd = dp -> next;
+ if (dp -> rcdate != parse_time) {
+ if (dl)
+ dl -> next = dp -> next;
+ else
+ domains = dp -> next;
+ free_domain_search_list (dp, MDL);
+ } else
+ dl = dp;
+ }
+ end_parse (&cfile);
+}
+
+/* Pick a name server from the /etc/resolv.conf file. */
+
+struct name_server *first_name_server ()
+{
+ static TIME rcdate;
+ struct stat st;
+
+ /* Check /etc/resolv.conf and reload it if it's changed. */
+ if (cur_time > rcdate) {
+ if (stat (path_resolv_conf, &st) < 0) {
+ log_error ("Can't stat %s", path_resolv_conf);
+ return (struct name_server *)0;
+ }
+ if (st.st_mtime > rcdate) {
+ rcdate = cur_time + 1;
+
+ read_resolv_conf (rcdate);
+ }
+ }
+
+ return name_servers;
+}
diff --git a/common/socket.c b/common/socket.c
new file mode 100644
index 0000000..5467a35
--- /dev/null
+++ b/common/socket.c
@@ -0,0 +1,1225 @@
+/* socket.c
+
+ BSD socket interface code... */
+
+/*
+ * Copyright (c) 2004-2015 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+/* SO_BINDTODEVICE support added by Elliot Poger (poger@leland.stanford.edu).
+ * This sockopt allows a socket to be bound to a particular interface,
+ * thus enabling the use of DHCPD on a multihomed host.
+ * If SO_BINDTODEVICE is defined in your system header files, the use of
+ * this sockopt will be automatically enabled.
+ * I have implemented it under Linux; other systems should be doable also.
+ */
+
+#include "dhcpd.h"
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+#include <sys/uio.h>
+
+#if defined(sun) && defined(USE_V4_PKTINFO)
+#include <sys/sysmacros.h>
+#include <net/if.h>
+#include <sys/sockio.h>
+#include <net/if_dl.h>
+#include <sys/dlpi.h>
+#endif
+
+#ifdef USE_SOCKET_FALLBACK
+# if !defined (USE_SOCKET_SEND)
+# define if_register_send if_register_fallback
+# define send_packet send_fallback
+# define if_reinitialize_send if_reinitialize_fallback
+# endif
+#endif
+
+#if defined(DHCPv6)
+/*
+ * XXX: this is gross. we need to go back and overhaul the API for socket
+ * handling.
+ */
+static int no_global_v6_socket = 0;
+static unsigned int global_v6_socket_references = 0;
+static int global_v6_socket = -1;
+
+static void if_register_multicast(struct interface_info *info);
+#endif
+
+/*
+ * We can use a single socket for AF_INET (similar to AF_INET6) on all
+ * interfaces configured for DHCP if the system has support for IP_PKTINFO
+ * and IP_RECVPKTINFO (for example Solaris 11).
+ */
+#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO)
+static unsigned int global_v4_socket_references = 0;
+static int global_v4_socket = -1;
+#endif
+
+/*
+ * If we can't bind() to a specific interface, then we can only have
+ * a single socket. This variable insures that we don't try to listen
+ * on two sockets.
+ */
+#if !defined(SO_BINDTODEVICE) && !defined(USE_FALLBACK)
+static int once = 0;
+#endif /* !defined(SO_BINDTODEVICE) && !defined(USE_FALLBACK) */
+
+/* Reinitializes the specified interface after an address change. This
+ is not required for packet-filter APIs. */
+
+#if defined (USE_SOCKET_SEND) || defined (USE_SOCKET_FALLBACK)
+void if_reinitialize_send (info)
+ struct interface_info *info;
+{
+#if 0
+#ifndef USE_SOCKET_RECEIVE
+ once = 0;
+ close (info -> wfdesc);
+#endif
+ if_register_send (info);
+#endif
+}
+#endif
+
+#ifdef USE_SOCKET_RECEIVE
+void if_reinitialize_receive (info)
+ struct interface_info *info;
+{
+#if 0
+ once = 0;
+ close (info -> rfdesc);
+ if_register_receive (info);
+#endif
+}
+#endif
+
+#if defined (USE_SOCKET_SEND) || \
+ defined (USE_SOCKET_RECEIVE) || \
+ defined (USE_SOCKET_FALLBACK)
+/* Generic interface registration routine... */
+int
+if_register_socket(struct interface_info *info, int family,
+ int *do_multicast, struct in6_addr *linklocal6)
+{
+ struct sockaddr_storage name;
+ int name_len;
+ int sock;
+ int flag;
+ int domain;
+#ifdef DHCPv6
+ struct sockaddr_in6 *addr6;
+#endif
+ struct sockaddr_in *addr;
+
+ /* INSIST((family == AF_INET) || (family == AF_INET6)); */
+
+#if !defined(SO_BINDTODEVICE) && !defined(USE_FALLBACK)
+ /* Make sure only one interface is registered. */
+ if (once) {
+ log_fatal ("The standard socket API can only support %s",
+ "hosts with a single network interface.");
+ }
+ once = 1;
+#endif
+
+ /*
+ * Set up the address we're going to bind to, depending on the
+ * address family.
+ */
+ memset(&name, 0, sizeof(name));
+ switch (family) {
+#ifdef DHCPv6
+ case AF_INET6:
+ addr6 = (struct sockaddr_in6 *)&name;
+ addr6->sin6_family = AF_INET6;
+ addr6->sin6_port = local_port;
+ if (linklocal6) {
+ memcpy(&addr6->sin6_addr,
+ linklocal6,
+ sizeof(addr6->sin6_addr));
+ addr6->sin6_scope_id = if_nametoindex(info->name);
+ }
+#ifdef HAVE_SA_LEN
+ addr6->sin6_len = sizeof(*addr6);
+#endif
+ name_len = sizeof(*addr6);
+ domain = PF_INET6;
+ if ((info->flags & INTERFACE_STREAMS) == INTERFACE_UPSTREAM) {
+ *do_multicast = 0;
+ }
+ break;
+#endif /* DHCPv6 */
+
+ case AF_INET:
+ default:
+ addr = (struct sockaddr_in *)&name;
+ addr->sin_family = AF_INET;
+ addr->sin_port = local_port;
+ memcpy(&addr->sin_addr,
+ &local_address,
+ sizeof(addr->sin_addr));
+#ifdef HAVE_SA_LEN
+ addr->sin_len = sizeof(*addr);
+#endif
+ name_len = sizeof(*addr);
+ domain = PF_INET;
+ break;
+ }
+
+ /* Make a socket... */
+ sock = socket(domain, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock < 0) {
+ log_fatal("Can't create dhcp socket: %m");
+ }
+
+ /* Set the REUSEADDR option so that we don't fail to start if
+ we're being restarted. */
+ flag = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+ (char *)&flag, sizeof(flag)) < 0) {
+ log_fatal("Can't set SO_REUSEADDR option on dhcp socket: %m");
+ }
+
+ /* Set the BROADCAST option so that we can broadcast DHCP responses.
+ We shouldn't do this for fallback devices, and we can detect that
+ a device is a fallback because it has no ifp structure. */
+ if (info->ifp &&
+ (setsockopt(sock, SOL_SOCKET, SO_BROADCAST,
+ (char *)&flag, sizeof(flag)) < 0)) {
+ log_fatal("Can't set SO_BROADCAST option on dhcp socket: %m");
+ }
+
+#if defined(DHCPv6) && defined(SO_REUSEPORT)
+ /*
+ * We only set SO_REUSEPORT on AF_INET6 sockets, so that multiple
+ * daemons can bind to their own sockets and get data for their
+ * respective interfaces. This does not (and should not) affect
+ * DHCPv4 sockets; we can't yet support BSD sockets well, much
+ * less multiple sockets. Make sense only with multicast.
+ */
+ if (local_family == AF_INET6) {
+ flag = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
+ (char *)&flag, sizeof(flag)) < 0) {
+ log_fatal("Can't set SO_REUSEPORT option on dhcp "
+ "socket: %m");
+ }
+ }
+#endif
+
+ /* Bind the socket to this interface's IP address. */
+ if (bind(sock, (struct sockaddr *)&name, name_len) < 0) {
+ log_error("Can't bind to dhcp address: %m");
+ log_error("Please make sure there is no other dhcp server");
+ log_error("running and that there's no entry for dhcp or");
+ log_error("bootp in /etc/inetd.conf. Also make sure you");
+ log_error("are not running HP JetAdmin software, which");
+ log_fatal("includes a bootp server.");
+ }
+
+#if defined(SO_BINDTODEVICE)
+ /* Bind this socket to this interface. */
+ if ((local_family != AF_INET6) && (info->ifp != NULL) &&
+ setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE,
+ (char *)(info -> ifp), sizeof(*(info -> ifp))) < 0) {
+ log_fatal("setsockopt: SO_BINDTODEVICE: %m");
+ }
+#endif
+
+ /* IP_BROADCAST_IF instructs the kernel which interface to send
+ * IP packets whose destination address is 255.255.255.255. These
+ * will be treated as subnet broadcasts on the interface identified
+ * by ip address (info -> primary_address). This is only known to
+ * be defined in SCO system headers, and may not be defined in all
+ * releases.
+ */
+#if defined(SCO) && defined(IP_BROADCAST_IF)
+ if (info->address_count &&
+ setsockopt(sock, IPPROTO_IP, IP_BROADCAST_IF, &info->addresses[0],
+ sizeof(info->addresses[0])) < 0)
+ log_fatal("Can't set IP_BROADCAST_IF on dhcp socket: %m");
+#endif
+
+#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO)
+ /*
+ * If we turn on IP_RECVPKTINFO we will be able to receive
+ * the interface index information of the received packet.
+ */
+ if (family == AF_INET) {
+ int on = 1;
+ if (setsockopt(sock, IPPROTO_IP, IP_RECVPKTINFO,
+ &on, sizeof(on)) != 0) {
+ log_fatal("setsockopt: IPV_RECVPKTINFO: %m");
+ }
+ }
+#endif
+
+#ifdef DHCPv6
+ /*
+ * If we turn on IPV6_PKTINFO, we will be able to receive
+ * additional information, such as the destination IP address.
+ * We need this to spot unicast packets.
+ */
+ if (family == AF_INET6) {
+ int on = 1;
+#ifdef IPV6_RECVPKTINFO
+ /* RFC3542 */
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ &on, sizeof(on)) != 0) {
+ log_fatal("setsockopt: IPV6_RECVPKTINFO: %m");
+ }
+#else
+ /* RFC2292 */
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO,
+ &on, sizeof(on)) != 0) {
+ log_fatal("setsockopt: IPV6_PKTINFO: %m");
+ }
+#endif
+ }
+
+#endif /* DHCPv6 */
+
+ return sock;
+}
+
+#ifdef DHCPv6
+void set_multicast_hop_limit(struct interface_info* info, int hop_limit) {
+ if (setsockopt(info->wfdesc, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+ &hop_limit, sizeof(int)) < 0) {
+ log_fatal("setMulticaseHopLimit: IPV6_MULTICAST_HOPS: %m");
+ }
+
+ log_debug("Setting hop count limit to %d for interface %s",
+ hop_limit, info->name);
+
+}
+#endif /* DHCPv6 */
+
+#endif /* USE_SOCKET_SEND || USE_SOCKET_RECEIVE || USE_SOCKET_FALLBACK */
+
+#if defined (USE_SOCKET_SEND) || defined (USE_SOCKET_FALLBACK)
+void if_register_send (info)
+ struct interface_info *info;
+{
+#ifndef USE_SOCKET_RECEIVE
+ info->wfdesc = if_register_socket(info, AF_INET, 0, NULL);
+ /* If this is a normal IPv4 address, get the hardware address. */
+ if (strcmp(info->name, "fallback") != 0)
+ get_hw_addr(info->name, &info->hw_address);
+#if defined (USE_SOCKET_FALLBACK)
+ /* Fallback only registers for send, but may need to receive as
+ well. */
+ info->rfdesc = info->wfdesc;
+#endif
+#else
+ info->wfdesc = info->rfdesc;
+#endif
+ if (!quiet_interface_discovery)
+ log_info ("Sending on Socket/%s%s%s",
+ info->name,
+ (info->shared_network ? "/" : ""),
+ (info->shared_network ?
+ info->shared_network->name : ""));
+}
+
+#if defined (USE_SOCKET_SEND)
+void if_deregister_send (info)
+ struct interface_info *info;
+{
+#ifndef USE_SOCKET_RECEIVE
+ close (info -> wfdesc);
+#endif
+ info -> wfdesc = -1;
+
+ if (!quiet_interface_discovery)
+ log_info ("Disabling output on Socket/%s%s%s",
+ info -> name,
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+#endif /* USE_SOCKET_SEND */
+#endif /* USE_SOCKET_SEND || USE_SOCKET_FALLBACK */
+
+#ifdef USE_SOCKET_RECEIVE
+void if_register_receive (info)
+ struct interface_info *info;
+{
+
+#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO)
+ if (global_v4_socket_references == 0) {
+ global_v4_socket = if_register_socket(info, AF_INET, 0, NULL);
+ if (global_v4_socket < 0) {
+ /*
+ * if_register_socket() fatally logs if it fails to
+ * create a socket, this is just a sanity check.
+ */
+ log_fatal("Failed to create AF_INET socket %s:%d",
+ MDL);
+ }
+ }
+
+ info->rfdesc = global_v4_socket;
+ global_v4_socket_references++;
+#else
+ /* If we're using the socket API for sending and receiving,
+ we don't need to register this interface twice. */
+ info->rfdesc = if_register_socket(info, AF_INET, 0, NULL);
+#endif /* IP_PKTINFO... */
+ /* If this is a normal IPv4 address, get the hardware address. */
+ if (strcmp(info->name, "fallback") != 0)
+ get_hw_addr(info->name, &info->hw_address);
+
+ if (!quiet_interface_discovery)
+ log_info ("Listening on Socket/%s%s%s",
+ info->name,
+ (info->shared_network ? "/" : ""),
+ (info->shared_network ?
+ info->shared_network->name : ""));
+}
+
+void if_deregister_receive (info)
+ struct interface_info *info;
+{
+#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO)
+ /* Dereference the global v4 socket. */
+ if ((info->rfdesc == global_v4_socket) &&
+ (info->wfdesc == global_v4_socket) &&
+ (global_v4_socket_references > 0)) {
+ global_v4_socket_references--;
+ info->rfdesc = -1;
+ } else {
+ log_fatal("Impossible condition at %s:%d", MDL);
+ }
+
+ if (global_v4_socket_references == 0) {
+ close(global_v4_socket);
+ global_v4_socket = -1;
+ }
+#else
+ close(info->rfdesc);
+ info->rfdesc = -1;
+#endif /* IP_PKTINFO... */
+ if (!quiet_interface_discovery)
+ log_info ("Disabling input on Socket/%s%s%s",
+ info -> name,
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+#endif /* USE_SOCKET_RECEIVE */
+
+
+#ifdef DHCPv6
+/*
+ * This function joins the interface to DHCPv6 multicast groups so we will
+ * receive multicast messages.
+ */
+static void
+if_register_multicast(struct interface_info *info) {
+ int sock = info->rfdesc;
+ struct ipv6_mreq mreq;
+
+ if (inet_pton(AF_INET6, All_DHCP_Relay_Agents_and_Servers,
+ &mreq.ipv6mr_multiaddr) <= 0) {
+ log_fatal("inet_pton: unable to convert '%s'",
+ All_DHCP_Relay_Agents_and_Servers);
+ }
+ mreq.ipv6mr_interface = if_nametoindex(info->name);
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq, sizeof(mreq)) < 0) {
+ log_fatal("setsockopt: IPV6_JOIN_GROUP: %m");
+ }
+
+ /*
+ * The relay agent code sets the streams so you know which way
+ * is up and down. But a relay agent shouldn't join to the
+ * Server address, or else you get fun loops. So up or down
+ * doesn't matter, we're just using that config to sense this is
+ * a relay agent.
+ */
+ if ((info->flags & INTERFACE_STREAMS) == 0) {
+ if (inet_pton(AF_INET6, All_DHCP_Servers,
+ &mreq.ipv6mr_multiaddr) <= 0) {
+ log_fatal("inet_pton: unable to convert '%s'",
+ All_DHCP_Servers);
+ }
+ mreq.ipv6mr_interface = if_nametoindex(info->name);
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq, sizeof(mreq)) < 0) {
+ log_fatal("setsockopt: IPV6_JOIN_GROUP: %m");
+ }
+ }
+}
+
+void
+if_register6(struct interface_info *info, int do_multicast) {
+ /* Bounce do_multicast to a stack variable because we may change it. */
+ int req_multi = do_multicast;
+
+ if (no_global_v6_socket) {
+ log_fatal("Impossible condition at %s:%d", MDL);
+ }
+
+ if (global_v6_socket_references == 0) {
+ global_v6_socket = if_register_socket(info, AF_INET6,
+ &req_multi, NULL);
+ if (global_v6_socket < 0) {
+ /*
+ * if_register_socket() fatally logs if it fails to
+ * create a socket, this is just a sanity check.
+ */
+ log_fatal("Impossible condition at %s:%d", MDL);
+ } else {
+ log_info("Bound to *:%d", ntohs(local_port));
+ }
+ }
+
+ info->rfdesc = global_v6_socket;
+ info->wfdesc = global_v6_socket;
+ global_v6_socket_references++;
+
+ if (req_multi)
+ if_register_multicast(info);
+
+ get_hw_addr(info->name, &info->hw_address);
+
+ if (!quiet_interface_discovery) {
+ if (info->shared_network != NULL) {
+ log_info("Listening on Socket/%d/%s/%s",
+ global_v6_socket, info->name,
+ info->shared_network->name);
+ log_info("Sending on Socket/%d/%s/%s",
+ global_v6_socket, info->name,
+ info->shared_network->name);
+ } else {
+ log_info("Listening on Socket/%s", info->name);
+ log_info("Sending on Socket/%s", info->name);
+ }
+ }
+}
+
+/*
+ * Register an IPv6 socket bound to the link-local address of
+ * the argument interface (used by clients on a multiple interface box,
+ * vs. a server or a relay using the global IPv6 socket and running
+ * *only* in a single instance).
+ */
+void
+if_register_linklocal6(struct interface_info *info) {
+ int sock;
+ int count;
+ struct in6_addr *addr6 = NULL;
+ int req_multi = 0;
+
+ if (global_v6_socket >= 0) {
+ log_fatal("Impossible condition at %s:%d", MDL);
+ }
+
+ no_global_v6_socket = 1;
+
+ /* get the (?) link-local address */
+ for (count = 0; count < info->v6address_count; count++) {
+ addr6 = &info->v6addresses[count];
+ if (IN6_IS_ADDR_LINKLOCAL(addr6))
+ break;
+ }
+
+ if (!addr6) {
+ log_fatal("no link-local IPv6 address for %s", info->name);
+ }
+
+ sock = if_register_socket(info, AF_INET6, &req_multi, addr6);
+
+ if (sock < 0) {
+ log_fatal("if_register_socket for %s fails", info->name);
+ }
+
+ info->rfdesc = sock;
+ info->wfdesc = sock;
+
+ get_hw_addr(info->name, &info->hw_address);
+
+ if (!quiet_interface_discovery) {
+ if (info->shared_network != NULL) {
+ log_info("Listening on Socket/%d/%s/%s",
+ global_v6_socket, info->name,
+ info->shared_network->name);
+ log_info("Sending on Socket/%d/%s/%s",
+ global_v6_socket, info->name,
+ info->shared_network->name);
+ } else {
+ log_info("Listening on Socket/%s", info->name);
+ log_info("Sending on Socket/%s", info->name);
+ }
+ }
+}
+
+void
+if_deregister6(struct interface_info *info) {
+ /* client case */
+ if (no_global_v6_socket) {
+ close(info->rfdesc);
+ info->rfdesc = -1;
+ info->wfdesc = -1;
+ } else if ((info->rfdesc == global_v6_socket) &&
+ (info->wfdesc == global_v6_socket) &&
+ (global_v6_socket_references > 0)) {
+ /* Dereference the global v6 socket. */
+ global_v6_socket_references--;
+ info->rfdesc = -1;
+ info->wfdesc = -1;
+ } else {
+ log_fatal("Impossible condition at %s:%d", MDL);
+ }
+
+ if (!quiet_interface_discovery) {
+ if (info->shared_network != NULL) {
+ log_info("Disabling input on Socket/%s/%s", info->name,
+ info->shared_network->name);
+ log_info("Disabling output on Socket/%s/%s", info->name,
+ info->shared_network->name);
+ } else {
+ log_info("Disabling input on Socket/%s", info->name);
+ log_info("Disabling output on Socket/%s", info->name);
+ }
+ }
+
+ if (!no_global_v6_socket &&
+ (global_v6_socket_references == 0)) {
+ close(global_v6_socket);
+ global_v6_socket = -1;
+
+ log_info("Unbound from *:%d", ntohs(local_port));
+ }
+}
+#endif /* DHCPv6 */
+
+#if defined (USE_SOCKET_SEND) || defined (USE_SOCKET_FALLBACK)
+ssize_t send_packet (interface, packet, raw, len, from, to, hto)
+ struct interface_info *interface;
+ struct packet *packet;
+ struct dhcp_packet *raw;
+ size_t len;
+ struct in_addr from;
+ struct sockaddr_in *to;
+ struct hardware *hto;
+{
+ int result;
+#ifdef IGNORE_HOSTUNREACH
+ int retry = 0;
+ do {
+#endif
+#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO)
+ struct in_pktinfo pktinfo;
+
+ if (interface->ifp != NULL) {
+ memset(&pktinfo, 0, sizeof (pktinfo));
+ pktinfo.ipi_ifindex = interface->ifp->ifr_index;
+ if (setsockopt(interface->wfdesc, IPPROTO_IP,
+ IP_PKTINFO, (char *)&pktinfo,
+ sizeof(pktinfo)) < 0)
+ log_fatal("setsockopt: IP_PKTINFO: %m");
+ }
+#endif
+ result = sendto (interface -> wfdesc, (char *)raw, len, 0,
+ (struct sockaddr *)to, sizeof *to);
+#ifdef IGNORE_HOSTUNREACH
+ } while (to -> sin_addr.s_addr == htonl (INADDR_BROADCAST) &&
+ result < 0 &&
+ (errno == EHOSTUNREACH ||
+ errno == ECONNREFUSED) &&
+ retry++ < 10);
+#endif
+ if (result < 0) {
+ log_error ("send_packet: %m");
+ if (errno == ENETUNREACH)
+ log_error ("send_packet: please consult README file%s",
+ " regarding broadcast address.");
+ }
+ return result;
+}
+
+#endif /* USE_SOCKET_SEND || USE_SOCKET_FALLBACK */
+
+#ifdef DHCPv6
+/*
+ * Solaris 9 is missing the CMSG_LEN and CMSG_SPACE macros, so we will
+ * synthesize them (based on the BIND 9 technique).
+ */
+
+#ifndef CMSG_LEN
+static size_t CMSG_LEN(size_t len) {
+ size_t hdrlen;
+ /*
+ * Cast NULL so that any pointer arithmetic performed by CMSG_DATA
+ * is correct.
+ */
+ hdrlen = (size_t)CMSG_DATA(((struct cmsghdr *)NULL));
+ return hdrlen + len;
+}
+#endif /* !CMSG_LEN */
+
+#ifndef CMSG_SPACE
+static size_t CMSG_SPACE(size_t len) {
+ struct msghdr msg;
+ struct cmsghdr *cmsgp;
+
+ /*
+ * XXX: The buffer length is an ad-hoc value, but should be enough
+ * in a practical sense.
+ */
+ union {
+ struct cmsghdr cmsg_sizer;
+ u_int8_t pktinfo_sizer[sizeof(struct cmsghdr) + 1024];
+ } dummybuf;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_control = &dummybuf;
+ msg.msg_controllen = sizeof(dummybuf);
+
+ cmsgp = (struct cmsghdr *)&dummybuf;
+ cmsgp->cmsg_len = CMSG_LEN(len);
+
+ cmsgp = CMSG_NXTHDR(&msg, cmsgp);
+ if (cmsgp != NULL) {
+ return (char *)cmsgp - (char *)msg.msg_control;
+ } else {
+ return 0;
+ }
+}
+#endif /* !CMSG_SPACE */
+
+#endif /* DHCPv6 */
+
+#if defined(DHCPv6) || \
+ (defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && \
+ defined(USE_V4_PKTINFO))
+/*
+ * For both send_packet6() and receive_packet6() we need to allocate
+ * space for the cmsg header information. We do this once and reuse
+ * the buffer. We also need the control buf for send_packet() and
+ * receive_packet() when we use a single socket and IP_PKTINFO to
+ * send the packet out the correct interface.
+ */
+static void *control_buf = NULL;
+static size_t control_buf_len = 0;
+
+static void
+allocate_cmsg_cbuf(void) {
+ control_buf_len = CMSG_SPACE(sizeof(struct in6_pktinfo));
+ control_buf = dmalloc(control_buf_len, MDL);
+ return;
+}
+#endif /* DHCPv6, IP_PKTINFO ... */
+
+#ifdef DHCPv6
+/*
+ * For both send_packet6() and receive_packet6() we need to use the
+ * sendmsg()/recvmsg() functions rather than the simpler send()/recv()
+ * functions.
+ *
+ * In the case of send_packet6(), we need to do this in order to insure
+ * that the reply packet leaves on the same interface that it arrived
+ * on.
+ *
+ * In the case of receive_packet6(), we need to do this in order to
+ * get the IP address the packet was sent to. This is used to identify
+ * whether a packet is multicast or unicast.
+ *
+ * Helpful man pages: recvmsg, readv (talks about the iovec stuff), cmsg.
+ *
+ * Also see the sections in RFC 3542 about IPV6_PKTINFO.
+ */
+
+/* Send an IPv6 packet */
+ssize_t send_packet6(struct interface_info *interface,
+ const unsigned char *raw, size_t len,
+ struct sockaddr_in6 *to) {
+ struct msghdr m;
+ struct iovec v;
+ struct sockaddr_in6 dst;
+ int result;
+ struct in6_pktinfo *pktinfo;
+ struct cmsghdr *cmsg;
+ unsigned int ifindex;
+
+ /*
+ * If necessary allocate space for the control message header.
+ * The space is common between send and receive.
+ */
+
+ if (control_buf == NULL) {
+ allocate_cmsg_cbuf();
+ if (control_buf == NULL) {
+ log_error("send_packet6: unable to allocate cmsg header");
+ return(ENOMEM);
+ }
+ }
+ memset(control_buf, 0, control_buf_len);
+
+ /*
+ * Initialize our message header structure.
+ */
+ memset(&m, 0, sizeof(m));
+
+ /*
+ * Set the target address we're sending to.
+ * Enforce the scope ID for bogus BSDs.
+ */
+ memcpy(&dst, to, sizeof(dst));
+ m.msg_name = &dst;
+ m.msg_namelen = sizeof(dst);
+ ifindex = if_nametoindex(interface->name);
+ if (no_global_v6_socket)
+ dst.sin6_scope_id = ifindex;
+
+ /*
+ * Set the data buffer we're sending. (Using this wacky
+ * "scatter-gather" stuff... we only have a single chunk
+ * of data to send, so we declare a single vector entry.)
+ */
+ v.iov_base = (char *)raw;
+ v.iov_len = len;
+ m.msg_iov = &v;
+ m.msg_iovlen = 1;
+
+ /*
+ * Setting the interface is a bit more involved.
+ *
+ * We have to create a "control message", and set that to
+ * define the IPv6 packet information. We could set the
+ * source address if we wanted, but we can safely let the
+ * kernel decide what that should be.
+ */
+ m.msg_control = control_buf;
+ m.msg_controllen = control_buf_len;
+ cmsg = CMSG_FIRSTHDR(&m);
+ INSIST(cmsg != NULL);
+ cmsg->cmsg_level = IPPROTO_IPV6;
+ cmsg->cmsg_type = IPV6_PKTINFO;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo));
+ pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+ memset(pktinfo, 0, sizeof(*pktinfo));
+ pktinfo->ipi6_ifindex = ifindex;
+
+ result = sendmsg(interface->wfdesc, &m, 0);
+ if (result < 0) {
+ log_error("send_packet6: %m");
+ }
+ return result;
+}
+#endif /* DHCPv6 */
+
+#ifdef USE_SOCKET_RECEIVE
+ssize_t receive_packet (interface, buf, len, from, hfrom)
+ struct interface_info *interface;
+ unsigned char *buf;
+ size_t len;
+ struct sockaddr_in *from;
+ struct hardware *hfrom;
+{
+#if !(defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO))
+ SOCKLEN_T flen = sizeof *from;
+#endif
+ int result;
+
+ /*
+ * The normal Berkeley socket interface doesn't give us any way
+ * to know what hardware interface we received the message on,
+ * but we should at least make sure the structure is emptied.
+ */
+ memset(hfrom, 0, sizeof(*hfrom));
+
+#ifdef IGNORE_HOSTUNREACH
+ int retry = 0;
+ do {
+#endif
+
+#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO)
+ struct msghdr m;
+ struct iovec v;
+ struct cmsghdr *cmsg;
+ struct in_pktinfo *pktinfo;
+ unsigned int ifindex;
+
+ /*
+ * If necessary allocate space for the control message header.
+ * The space is common between send and receive.
+ */
+ if (control_buf == NULL) {
+ allocate_cmsg_cbuf();
+ if (control_buf == NULL) {
+ log_error("receive_packet: unable to allocate cmsg "
+ "header");
+ return(ENOMEM);
+ }
+ }
+ memset(control_buf, 0, control_buf_len);
+
+ /*
+ * Initialize our message header structure.
+ */
+ memset(&m, 0, sizeof(m));
+
+ /*
+ * Point so we can get the from address.
+ */
+ m.msg_name = from;
+ m.msg_namelen = sizeof(*from);
+
+ /*
+ * Set the data buffer we're receiving. (Using this wacky
+ * "scatter-gather" stuff... but we that doesn't really make
+ * sense for us, so we use a single vector entry.)
+ */
+ v.iov_base = buf;
+ v.iov_len = len;
+ m.msg_iov = &v;
+ m.msg_iovlen = 1;
+
+ /*
+ * Getting the interface is a bit more involved.
+ *
+ * We set up some space for a "control message". We have
+ * previously asked the kernel to give us packet
+ * information (when we initialized the interface), so we
+ * should get the interface index from that.
+ */
+ m.msg_control = control_buf;
+ m.msg_controllen = control_buf_len;
+
+ result = recvmsg(interface->rfdesc, &m, 0);
+
+ if (result >= 0) {
+ /*
+ * If we did read successfully, then we need to loop
+ * through the control messages we received and
+ * find the one with our inteface index.
+ */
+ cmsg = CMSG_FIRSTHDR(&m);
+ while (cmsg != NULL) {
+ if ((cmsg->cmsg_level == IPPROTO_IP) &&
+ (cmsg->cmsg_type == IP_PKTINFO)) {
+ pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg);
+ ifindex = pktinfo->ipi_ifindex;
+ /*
+ * We pass the ifindex back to the caller
+ * using the unused hfrom parameter avoiding
+ * interface changes between sockets and
+ * the discover code.
+ */
+ memcpy(hfrom->hbuf, &ifindex, sizeof(ifindex));
+ return (result);
+ }
+ cmsg = CMSG_NXTHDR(&m, cmsg);
+ }
+
+ /*
+ * We didn't find the necessary control message
+ * flag it as an error
+ */
+ result = -1;
+ errno = EIO;
+ }
+#else
+ result = recvfrom(interface -> rfdesc, (char *)buf, len, 0,
+ (struct sockaddr *)from, &flen);
+#endif /* IP_PKTINFO ... */
+#ifdef IGNORE_HOSTUNREACH
+ } while (result < 0 &&
+ (errno == EHOSTUNREACH ||
+ errno == ECONNREFUSED) &&
+ retry++ < 10);
+#endif
+ return (result);
+}
+
+#endif /* USE_SOCKET_RECEIVE */
+
+#ifdef DHCPv6
+ssize_t
+receive_packet6(struct interface_info *interface,
+ unsigned char *buf, size_t len,
+ struct sockaddr_in6 *from, struct in6_addr *to_addr,
+ unsigned int *if_idx)
+{
+ struct msghdr m;
+ struct iovec v;
+ int result;
+ struct cmsghdr *cmsg;
+ struct in6_pktinfo *pktinfo;
+
+ /*
+ * If necessary allocate space for the control message header.
+ * The space is common between send and receive.
+ */
+ if (control_buf == NULL) {
+ allocate_cmsg_cbuf();
+ if (control_buf == NULL) {
+ log_error("receive_packet6: unable to allocate cmsg "
+ "header");
+ return(ENOMEM);
+ }
+ }
+ memset(control_buf, 0, control_buf_len);
+
+ /*
+ * Initialize our message header structure.
+ */
+ memset(&m, 0, sizeof(m));
+
+ /*
+ * Point so we can get the from address.
+ */
+ m.msg_name = from;
+ m.msg_namelen = sizeof(*from);
+
+ /*
+ * Set the data buffer we're receiving. (Using this wacky
+ * "scatter-gather" stuff... but we that doesn't really make
+ * sense for us, so we use a single vector entry.)
+ */
+ v.iov_base = buf;
+ v.iov_len = len;
+ m.msg_iov = &v;
+ m.msg_iovlen = 1;
+
+ /*
+ * Getting the interface is a bit more involved.
+ *
+ * We set up some space for a "control message". We have
+ * previously asked the kernel to give us packet
+ * information (when we initialized the interface), so we
+ * should get the destination address from that.
+ */
+ m.msg_control = control_buf;
+ m.msg_controllen = control_buf_len;
+
+ result = recvmsg(interface->rfdesc, &m, 0);
+
+ if (result >= 0) {
+ /*
+ * If we did read successfully, then we need to loop
+ * through the control messages we received and
+ * find the one with our destination address.
+ */
+ cmsg = CMSG_FIRSTHDR(&m);
+ while (cmsg != NULL) {
+ if ((cmsg->cmsg_level == IPPROTO_IPV6) &&
+ (cmsg->cmsg_type == IPV6_PKTINFO)) {
+ pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+ *to_addr = pktinfo->ipi6_addr;
+ *if_idx = pktinfo->ipi6_ifindex;
+
+ return (result);
+ }
+ cmsg = CMSG_NXTHDR(&m, cmsg);
+ }
+
+ /*
+ * We didn't find the necessary control message
+ * flag is as an error
+ */
+ result = -1;
+ errno = EIO;
+ }
+
+ return (result);
+}
+#endif /* DHCPv6 */
+
+#if defined (USE_SOCKET_FALLBACK)
+/* This just reads in a packet and silently discards it. */
+
+isc_result_t fallback_discard (object)
+ omapi_object_t *object;
+{
+ char buf [1540];
+ struct sockaddr_in from;
+ SOCKLEN_T flen = sizeof from;
+ int status;
+ struct interface_info *interface;
+
+ if (object -> type != dhcp_type_interface)
+ return DHCP_R_INVALIDARG;
+ interface = (struct interface_info *)object;
+
+ status = recvfrom (interface -> wfdesc, buf, sizeof buf, 0,
+ (struct sockaddr *)&from, &flen);
+#if defined (DEBUG)
+ /* Only report fallback discard errors if we're debugging. */
+ if (status < 0) {
+ log_error ("fallback_discard: %m");
+ return ISC_R_UNEXPECTED;
+ }
+#else
+ /* ignore the fact that status value is never used */
+ IGNORE_UNUSED(status);
+#endif
+ return ISC_R_SUCCESS;
+}
+#endif /* USE_SOCKET_FALLBACK */
+
+#if defined (USE_SOCKET_SEND)
+int can_unicast_without_arp (ip)
+ struct interface_info *ip;
+{
+ return 0;
+}
+
+int can_receive_unicast_unconfigured (ip)
+ struct interface_info *ip;
+{
+#if defined (SOCKET_CAN_RECEIVE_UNICAST_UNCONFIGURED)
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+int supports_multiple_interfaces (ip)
+ struct interface_info *ip;
+{
+#if defined(SO_BINDTODEVICE) || \
+ (defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && \
+ defined(USE_V4_PKTINFO))
+ return(1);
+#else
+ return(0);
+#endif
+}
+
+/* If we have SO_BINDTODEVICE, set up a fallback interface; otherwise,
+ do not. */
+
+void maybe_setup_fallback ()
+{
+#if defined (USE_SOCKET_FALLBACK)
+ isc_result_t status;
+ struct interface_info *fbi = (struct interface_info *)0;
+ if (setup_fallback (&fbi, MDL)) {
+ fbi -> wfdesc = if_register_socket (fbi, AF_INET, 0, NULL);
+ fbi -> rfdesc = fbi -> wfdesc;
+ log_info ("Sending on Socket/%s%s%s",
+ fbi -> name,
+ (fbi -> shared_network ? "/" : ""),
+ (fbi -> shared_network ?
+ fbi -> shared_network -> name : ""));
+
+ status = omapi_register_io_object ((omapi_object_t *)fbi,
+ if_readsocket, 0,
+ fallback_discard, 0, 0);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register I/O handle for %s: %s",
+ fbi -> name, isc_result_totext (status));
+ interface_dereference (&fbi, MDL);
+ }
+#endif
+}
+
+
+#if defined(sun) && defined(USE_V4_PKTINFO)
+/* This code assumes the existence of SIOCGLIFHWADDR */
+void
+get_hw_addr(const char *name, struct hardware *hw) {
+ struct sockaddr_dl *dladdrp;
+ int sock, i;
+ struct lifreq lifr;
+
+ memset(&lifr, 0, sizeof (lifr));
+ (void) strlcpy(lifr.lifr_name, name, sizeof (lifr.lifr_name));
+ /*
+ * Check if the interface is a virtual or IPMP interface - in those
+ * cases it has no hw address, so generate a random one.
+ */
+ if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ||
+ ioctl(sock, SIOCGLIFFLAGS, &lifr) < 0) {
+ if (sock != -1)
+ (void) close(sock);
+
+#ifdef DHCPv6
+ /*
+ * If approrpriate try this with an IPv6 socket
+ */
+ if ((sock = socket(AF_INET6, SOCK_DGRAM, 0)) >= 0 &&
+ ioctl(sock, SIOCGLIFFLAGS, &lifr) >= 0) {
+ goto flag_check;
+ }
+ if (sock != -1)
+ (void) close(sock);
+#endif
+ log_fatal("Couldn't get interface flags for %s: %m", name);
+
+ }
+
+ flag_check:
+ if (lifr.lifr_flags & (IFF_VIRTUAL|IFF_IPMP)) {
+ hw->hlen = sizeof (hw->hbuf);
+ srandom((long)gethrtime());
+
+ hw->hbuf[0] = HTYPE_IPMP;
+ for (i = 1; i < hw->hlen; ++i) {
+ hw->hbuf[i] = random() % 256;
+ }
+
+ if (sock != -1)
+ (void) close(sock);
+ return;
+ }
+
+ if (ioctl(sock, SIOCGLIFHWADDR, &lifr) < 0)
+ log_fatal("Couldn't get interface hardware address for %s: %m",
+ name);
+ dladdrp = (struct sockaddr_dl *)&lifr.lifr_addr;
+ hw->hlen = dladdrp->sdl_alen+1;
+ switch (dladdrp->sdl_type) {
+ case DL_CSMACD: /* IEEE 802.3 */
+ case DL_ETHER:
+ hw->hbuf[0] = HTYPE_ETHER;
+ break;
+ case DL_TPR:
+ hw->hbuf[0] = HTYPE_IEEE802;
+ break;
+ case DL_FDDI:
+ hw->hbuf[0] = HTYPE_FDDI;
+ break;
+ case DL_IB:
+ hw->hbuf[0] = HTYPE_INFINIBAND;
+ break;
+ default:
+ log_fatal("%s: unsupported DLPI MAC type %lu", name,
+ (unsigned long)dladdrp->sdl_type);
+ }
+
+ memcpy(hw->hbuf+1, LLADDR(dladdrp), hw->hlen-1);
+
+ if (sock != -1)
+ (void) close(sock);
+}
+#endif /* defined(sun) */
+
+#endif /* USE_SOCKET_SEND */
diff --git a/common/tables.c b/common/tables.c
new file mode 100644
index 0000000..ccd9701
--- /dev/null
+++ b/common/tables.c
@@ -0,0 +1,1410 @@
+/* tables.c
+
+ Tables of information... */
+
+/*
+ * Copyright (c) 2011-2012 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2009 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+
+/* XXXDPN: Moved here from hash.c, when it moved to libomapi. Not sure
+ where these really belong. */
+HASH_FUNCTIONS (group, const char *, struct group_object, group_hash_t,
+ group_reference, group_dereference, do_string_hash)
+HASH_FUNCTIONS (universe, const char *, struct universe, universe_hash_t, 0, 0,
+ do_case_hash)
+HASH_FUNCTIONS (option_name, const char *, struct option, option_name_hash_t,
+ option_reference, option_dereference, do_case_hash)
+HASH_FUNCTIONS (option_code, const unsigned *, struct option,
+ option_code_hash_t, option_reference, option_dereference,
+ do_number_hash)
+
+/* DHCP Option names, formats and codes, from RFC1533.
+
+ Format codes:
+
+ I - IPv4 address
+ 6 - IPv6 address
+ l - 32-bit signed integer
+ L - 32-bit unsigned integer
+ s - 16-bit signed integer
+ S - 16-bit unsigned integer
+ b - 8-bit signed integer
+ B - 8-bit unsigned integer
+ t - ASCII text
+ T - Lease Time, 32-bit unsigned integer implying a number of seconds from
+ some event. The special all-ones value means 'infinite'. May either
+ be printed as a decimal, eg, "3600", or as this name, eg, "infinite".
+ f - flag (true or false)
+ A - array of all that precedes (e.g., fIA means array of records of
+ a flag and an IP address)
+ a - array of the preceding character (e.g., fIa means a single flag
+ followed by an array of IP addresses)
+ U - name of an option space (universe)
+ F - implicit flag - the presence of the option indicates that the
+ flag is true.
+ o - the preceding value is optional.
+ E - encapsulation, string or colon-separated hex list (the latter
+ two for parsing). E is followed by a text string containing
+ the name of the option space to encapsulate, followed by a '.'.
+ If the E is immediately followed by '.', the applicable vendor
+ option space is used if one is defined.
+ e - If an encapsulation directive is not the first thing in the string,
+ the option scanner requires an efficient way to find the encapsulation.
+ This is done by placing a 'e' at the beginning of the option. The
+ 'e' has no other purpose, and is not required if 'E' is the first
+ thing in the option.
+ X - either an ASCII string or binary data. On output, the string is
+ scanned to see if it's printable ASCII and, if so, output as a
+ quoted string. If not, it's output as colon-separated hex. On
+ input, the option can be specified either as a quoted string or as
+ a colon-separated hex list.
+ N - enumeration. N is followed by a text string containing
+ the name of the set of enumeration values to parse or emit,
+ followed by a '.'. The width of the data is specified in the
+ named enumeration. Named enumerations are tracked in parse.c.
+ d - Domain name (i.e., FOO or FOO.BAR).
+ D - Domain list (i.e., example.com eng.example.com)
+ c - When following a 'D' atom, enables compression pointers.
+ Z - Zero-length option
+*/
+
+struct universe dhcp_universe;
+static struct option dhcp_options[] = {
+ { "subnet-mask", "I", &dhcp_universe, 1, 1 },
+ { "time-offset", "l", &dhcp_universe, 2, 1 },
+ { "routers", "IA", &dhcp_universe, 3, 1 },
+ { "time-servers", "IA", &dhcp_universe, 4, 1 },
+ { "ien116-name-servers", "IA", &dhcp_universe, 5, 1 },
+ { "domain-name-servers", "IA", &dhcp_universe, 6, 1 },
+ { "log-servers", "IA", &dhcp_universe, 7, 1 },
+ { "cookie-servers", "IA", &dhcp_universe, 8, 1 },
+ { "lpr-servers", "IA", &dhcp_universe, 9, 1 },
+ { "impress-servers", "IA", &dhcp_universe, 10, 1 },
+ { "resource-location-servers", "IA", &dhcp_universe, 11, 1 },
+ { "host-name", "t", &dhcp_universe, 12, 1 },
+ { "boot-size", "S", &dhcp_universe, 13, 1 },
+ { "merit-dump", "t", &dhcp_universe, 14, 1 },
+ { "domain-name", "t", &dhcp_universe, 15, 1 },
+ { "swap-server", "I", &dhcp_universe, 16, 1 },
+ { "root-path", "t", &dhcp_universe, 17, 1 },
+ { "extensions-path", "t", &dhcp_universe, 18, 1 },
+ { "ip-forwarding", "f", &dhcp_universe, 19, 1 },
+ { "non-local-source-routing", "f", &dhcp_universe, 20, 1 },
+ { "policy-filter", "IIA", &dhcp_universe, 21, 1 },
+ { "max-dgram-reassembly", "S", &dhcp_universe, 22, 1 },
+ { "default-ip-ttl", "B", &dhcp_universe, 23, 1 },
+ { "path-mtu-aging-timeout", "L", &dhcp_universe, 24, 1 },
+ { "path-mtu-plateau-table", "SA", &dhcp_universe, 25, 1 },
+ { "interface-mtu", "S", &dhcp_universe, 26, 1 },
+ { "all-subnets-local", "f", &dhcp_universe, 27, 1 },
+ { "broadcast-address", "I", &dhcp_universe, 28, 1 },
+ { "perform-mask-discovery", "f", &dhcp_universe, 29, 1 },
+ { "mask-supplier", "f", &dhcp_universe, 30, 1 },
+ { "router-discovery", "f", &dhcp_universe, 31, 1 },
+ { "router-solicitation-address", "I", &dhcp_universe, 32, 1 },
+ { "static-routes", "IIA", &dhcp_universe, 33, 1 },
+ { "trailer-encapsulation", "f", &dhcp_universe, 34, 1 },
+ { "arp-cache-timeout", "L", &dhcp_universe, 35, 1 },
+ { "ieee802-3-encapsulation", "f", &dhcp_universe, 36, 1 },
+ { "default-tcp-ttl", "B", &dhcp_universe, 37, 1 },
+ { "tcp-keepalive-interval", "L", &dhcp_universe, 38, 1 },
+ { "tcp-keepalive-garbage", "f", &dhcp_universe, 39, 1 },
+ { "nis-domain", "t", &dhcp_universe, 40, 1 },
+ { "nis-servers", "IA", &dhcp_universe, 41, 1 },
+ { "ntp-servers", "IA", &dhcp_universe, 42, 1 },
+ { "vendor-encapsulated-options", "E.", &dhcp_universe, 43, 1 },
+ { "netbios-name-servers", "IA", &dhcp_universe, 44, 1 },
+ { "netbios-dd-server", "IA", &dhcp_universe, 45, 1 },
+ { "netbios-node-type", "B", &dhcp_universe, 46, 1 },
+ { "netbios-scope", "t", &dhcp_universe, 47, 1 },
+ { "font-servers", "IA", &dhcp_universe, 48, 1 },
+ { "x-display-manager", "IA", &dhcp_universe, 49, 1 },
+ { "dhcp-requested-address", "I", &dhcp_universe, 50, 1 },
+ { "dhcp-lease-time", "L", &dhcp_universe, 51, 1 },
+ { "dhcp-option-overload", "B", &dhcp_universe, 52, 1 },
+ { "dhcp-message-type", "B", &dhcp_universe, 53, 1 },
+ { "dhcp-server-identifier", "I", &dhcp_universe, 54, 1 },
+ { "dhcp-parameter-request-list", "BA", &dhcp_universe, 55, 1 },
+ { "dhcp-message", "t", &dhcp_universe, 56, 1 },
+ { "dhcp-max-message-size", "S", &dhcp_universe, 57, 1 },
+ { "dhcp-renewal-time", "L", &dhcp_universe, 58, 1 },
+ { "dhcp-rebinding-time", "L", &dhcp_universe, 59, 1 },
+ { "vendor-class-identifier", "X", &dhcp_universe, 60, 1 },
+ { "dhcp-client-identifier", "X", &dhcp_universe, 61, 1 },
+ { "nwip-domain", "t", &dhcp_universe, 62, 1 },
+ { "nwip-suboptions", "Enwip.", &dhcp_universe, 63, 1 },
+ { "nisplus-domain", "t", &dhcp_universe, 64, 1 },
+ { "nisplus-servers", "IA", &dhcp_universe, 65, 1 },
+ { "tftp-server-name", "t", &dhcp_universe, 66, 1 },
+ { "bootfile-name", "t", &dhcp_universe, 67, 1 },
+ { "mobile-ip-home-agent", "IA", &dhcp_universe, 68, 1 },
+ { "smtp-server", "IA", &dhcp_universe, 69, 1 },
+ { "pop-server", "IA", &dhcp_universe, 70, 1 },
+ { "nntp-server", "IA", &dhcp_universe, 71, 1 },
+ { "www-server", "IA", &dhcp_universe, 72, 1 },
+ { "finger-server", "IA", &dhcp_universe, 73, 1 },
+ { "irc-server", "IA", &dhcp_universe, 74, 1 },
+ { "streettalk-server", "IA", &dhcp_universe, 75, 1 },
+ { "streettalk-directory-assistance-server", "IA",
+ &dhcp_universe, 76, 1 },
+ { "user-class", "t", &dhcp_universe, 77, 1 },
+ { "slp-directory-agent", "fIa", &dhcp_universe, 78, 1 },
+ { "slp-service-scope", "fto", &dhcp_universe, 79, 1 },
+ /* 80 is the zero-length rapid-commit (RFC 4039) */
+ { "fqdn", "Efqdn.", &dhcp_universe, 81, 1 },
+ { "relay-agent-information", "Eagent.", &dhcp_universe, 82, 1 },
+ /* 83 is iSNS (RFC 4174) */
+ /* 84 is unassigned */
+ { "nds-servers", "IA", &dhcp_universe, 85, 1 },
+ { "nds-tree-name", "t", &dhcp_universe, 86, 1 },
+ { "nds-context", "t", &dhcp_universe, 87, 1 },
+
+ /* Note: RFC4280 fails to identify if the DHCPv4 option is to use
+ * compression pointers or not. Assume not.
+ */
+ { "bcms-controller-names", "D", &dhcp_universe, 88, 1 },
+ { "bcms-controller-address", "Ia", &dhcp_universe, 89, 1 },
+
+ /* 90 is the authentication option (RFC 3118) */
+
+ { "client-last-transaction-time", "L", &dhcp_universe, 91, 1 },
+ { "associated-ip", "Ia", &dhcp_universe, 92, 1 },
+#if 0
+ /* Defined by RFC 4578 */
+ { "pxe-system-type", "S", &dhcp_universe, 93, 1 },
+ { "pxe-interface-id", "BBB", &dhcp_universe, 94, 1 },
+ { "pxe-client-id", "BX", &dhcp_universe, 97, 1 },
+#endif
+ { "uap-servers", "t", &dhcp_universe, 98, 1 },
+ { "netinfo-server-address", "Ia", &dhcp_universe, 112, 1 },
+ { "netinfo-server-tag", "t", &dhcp_universe, 113, 1 },
+ { "default-url", "t", &dhcp_universe, 114, 1 },
+ { "subnet-selection", "I", &dhcp_universe, 118, 1 },
+ { "domain-search", "Dc", &dhcp_universe, 119, 1 },
+ { "vivco", "Evendor-class.", &dhcp_universe, 124, 1 },
+ { "vivso", "Evendor.", &dhcp_universe, 125, 1 },
+#if 0
+ /* Referenced by RFC 4578.
+ * DO NOT UNCOMMENT THESE DEFINITIONS: these names are placeholders
+ * and will not be used in future versions of the software.
+ */
+ { "pxe-undefined-1", "X", &dhcp_universe, 128, 1 },
+ { "pxe-undefined-2", "X", &dhcp_universe, 129, 1 },
+ { "pxe-undefined-3", "X", &dhcp_universe, 130, 1 },
+ { "pxe-undefined-4", "X", &dhcp_universe, 131, 1 },
+ { "pxe-undefined-5", "X", &dhcp_universe, 132, 1 },
+ { "pxe-undefined-6", "X", &dhcp_universe, 133, 1 },
+ { "pxe-undefined-7", "X", &dhcp_universe, 134, 1 },
+ { "pxe-undefined-8", "X", &dhcp_universe, 135, 1 },
+#endif
+#if 0
+ /* Not defined by RFC yet */
+ { "tftp-server-address", "Ia", &dhcp_universe, 150, 1 },
+#endif
+#if 0
+ /* PXELINUX options: defined by RFC 5071 */
+ { "pxelinux-magic", "BBBB", &dhcp_universe, 208, 1 },
+ { "loader-configfile", "t", &dhcp_universe, 209, 1 },
+ { "loader-pathprefix", "t", &dhcp_universe, 210, 1 },
+ { "loader-reboottime", "L", &dhcp_universe, 211, 1 },
+#endif
+#if 0
+ /* Not defined by RFC yet */
+ { "vss-info", "BX", &dhcp_universe, 221, 1 },
+#endif
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+struct universe nwip_universe;
+static struct option nwip_options[] = {
+ { "illegal-1", "", &nwip_universe, 1, 1 },
+ { "illegal-2", "", &nwip_universe, 2, 1 },
+ { "illegal-3", "", &nwip_universe, 3, 1 },
+ { "illegal-4", "", &nwip_universe, 4, 1 },
+ { "nsq-broadcast", "f", &nwip_universe, 5, 1 },
+ { "preferred-dss", "IA", &nwip_universe, 6, 1 },
+ { "nearest-nwip-server", "IA", &nwip_universe, 7, 1 },
+ { "autoretries", "B", &nwip_universe, 8, 1 },
+ { "autoretry-secs", "B", &nwip_universe, 9, 1 },
+ { "nwip-1-1", "f", &nwip_universe, 10, 1 },
+ { "primary-dss", "I", &nwip_universe, 11, 1 },
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+/* Note that the "FQDN suboption space" does not reflect the FQDN option
+ * format - rather, this is a handy "virtualization" of a flat option
+ * which makes manual configuration and presentation of some of its
+ * contents easier (each of these suboptions is a fixed-space field within
+ * the fqdn contents - domain and host names are derived from a common field,
+ * and differ in the left and right hand side of the leftmost dot, fqdn is
+ * the combination of the two).
+ *
+ * Note further that the DHCPv6 and DHCPv4 'fqdn' options use the same
+ * virtualized option space to store their work.
+ */
+
+struct universe fqdn_universe;
+struct universe fqdn6_universe;
+static struct option fqdn_options[] = {
+ { "no-client-update", "f", &fqdn_universe, 1, 1 },
+ { "server-update", "f", &fqdn_universe, 2, 1 },
+ { "encoded", "f", &fqdn_universe, 3, 1 },
+ { "rcode1", "B", &fqdn_universe, 4, 1 },
+ { "rcode2", "B", &fqdn_universe, 5, 1 },
+ { "hostname", "t", &fqdn_universe, 6, 1 },
+ { "domainname", "t", &fqdn_universe, 7, 1 },
+ { "fqdn", "t", &fqdn_universe, 8, 1 },
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+struct universe vendor_class_universe;
+static struct option vendor_class_options[] = {
+ { "isc", "X", &vendor_class_universe, 2495, 1 },
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+struct universe vendor_universe;
+static struct option vendor_options[] = {
+ { "isc", "Eisc.", &vendor_universe, 2495, 1 },
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+struct universe isc_universe;
+static struct option isc_options [] = {
+ { "media", "t", &isc_universe, 1, 1 },
+ { "update-assist", "X", &isc_universe, 2, 1 },
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+struct universe dhcpv6_universe;
+static struct option dhcpv6_options[] = {
+
+ /* RFC3315 OPTIONS */
+
+ /* Client and server DUIDs are opaque fields, but marking them
+ * up somewhat makes configuration easier.
+ */
+ { "client-id", "X", &dhcpv6_universe, 1, 1 },
+ { "server-id", "X", &dhcpv6_universe, 2, 1 },
+
+ /* ia-* options actually have at their ends a space for options
+ * that are specific to this instance of the option. We can not
+ * handle this yet at this stage of development, so the encoding
+ * of these options is unspecified ("X").
+ */
+ { "ia-na", "X", &dhcpv6_universe, 3, 1 },
+ { "ia-ta", "X", &dhcpv6_universe, 4, 1 },
+ { "ia-addr", "X", &dhcpv6_universe, 5, 1 },
+
+ /* "oro" is DHCPv6 speak for "parameter-request-list" */
+ { "oro", "SA", &dhcpv6_universe, 6, 1 },
+
+ { "preference", "B", &dhcpv6_universe, 7, 1 },
+ { "elapsed-time", "S", &dhcpv6_universe, 8, 1 },
+ { "relay-msg", "X", &dhcpv6_universe, 9, 1 },
+
+ /* Option code 10 is curiously unassigned. */
+ /*
+ * In draft-ietf-dhc-dhcpv6-25 there were two OPTION_CLIENT_MSG and
+ * OPTION_SERVER_MSG options. They were eventually unified as
+ * OPTION_RELAY_MSG, hence no option with value of 10.
+ */
+#if 0
+ /* XXX: missing suitable atoms for the auth option. We may want
+ * to 'virtually encapsulate' this option a la the fqdn option
+ * seeing as it is processed explicitly by the server and unlikely
+ * to be configured by hand by users as such.
+ */
+ { "auth", "Nauth-protocol.Nauth-algorithm.Nrdm-type.LLX",
+ &dhcpv6_universe, 11, 1 },
+#endif
+ { "unicast", "6", &dhcpv6_universe, 12, 1 },
+ { "status-code", "Nstatus-codes.to", &dhcpv6_universe, 13, 1 },
+ { "rapid-commit", "Z", &dhcpv6_universe, 14, 1 },
+#if 0
+ /* XXX: user-class contents are of the form "StA" where the
+ * integer describes the length of the text field. We don't have
+ * an atom for pre-determined-length octet strings yet, so we
+ * can't quite do these two.
+ */
+ { "user-class", "X", &dhcpv6_universe, 15, 1 },
+ { "vendor-class", "X", &dhcpv6_universe, 16, 1 },
+#endif
+ { "vendor-opts", "Evsio.", &dhcpv6_universe, 17, 1 },
+ { "interface-id", "X", &dhcpv6_universe, 18, 1 },
+ { "reconf-msg", "Ndhcpv6-messages.", &dhcpv6_universe, 19, 1 },
+ { "reconf-accept", "Z", &dhcpv6_universe, 20, 1 },
+
+ /* RFC3319 OPTIONS */
+
+ /* Of course: we would HAVE to have a different atom for
+ * domain names without compression. Typical.
+ */
+ { "sip-servers-names", "D", &dhcpv6_universe, 21, 1 },
+ { "sip-servers-addresses", "6A", &dhcpv6_universe, 22, 1 },
+
+ /* RFC3646 OPTIONS */
+
+ { "name-servers", "6A", &dhcpv6_universe, 23, 1 },
+ { "domain-search", "D", &dhcpv6_universe, 24, 1 },
+
+ /* RFC3633 OPTIONS */
+
+ { "ia-pd", "X", &dhcpv6_universe, 25, 1 },
+ { "ia-prefix", "X", &dhcpv6_universe, 26, 1 },
+
+ /* RFC3898 OPTIONS */
+
+ { "nis-servers", "6A", &dhcpv6_universe, 27, 1 },
+ { "nisp-servers", "6A", &dhcpv6_universe, 28, 1 },
+ { "nis-domain-name", "D", &dhcpv6_universe, 29, 1 },
+ { "nisp-domain-name", "D", &dhcpv6_universe, 30, 1 },
+
+ /* RFC4075 OPTIONS */
+ { "sntp-servers", "6A", &dhcpv6_universe, 31, 1 },
+
+ /* RFC4242 OPTIONS */
+
+ { "info-refresh-time", "T", &dhcpv6_universe, 32, 1 },
+
+ /* RFC4280 OPTIONS */
+
+ { "bcms-server-d", "D", &dhcpv6_universe, 33, 1 },
+ { "bcms-server-a", "6A", &dhcpv6_universe, 34, 1 },
+
+ /* Note that 35 is not assigned. */
+
+ /* Not yet considering for inclusion. */
+#if 0
+ /* RFC4776 OPTIONS */
+
+ { "geoconf-civic", "X", &dhcpv6_universe, 36, 1 },
+#endif
+
+ /* RFC4649 OPTIONS */
+
+ /* The remote-id option looks like the VSIO option, but for all
+ * intents and purposes we only need to treat the entire field
+ * like a globally unique identifier (and if we create such an
+ * option, ensure the first 4 bytes are our enterprise-id followed
+ * by a globally unique ID so long as you're within that enterprise
+ * id). So we'll use "X" for now unless someone grumbles.
+ */
+ { "remote-id", "X", &dhcpv6_universe, 37, 1 },
+
+ /* RFC4580 OPTIONS */
+
+ { "subscriber-id", "X", &dhcpv6_universe, 38, 1 },
+
+ /* RFC4704 OPTIONS */
+
+ /* The DHCPv6 FQDN option is...weird.
+ *
+ * We use the same "virtual" encapsulated space as DHCPv4's FQDN
+ * option, so it can all be configured in one place. Since the
+ * options system does not support multiple inheritance, we use
+ * a 'shill' layer to perform the different protocol conversions,
+ * and to redirect any queries in the DHCPv4 FQDN's space.
+ */
+ { "fqdn", "Efqdn6-if-you-see-me-its-a-bug-bug-bug.",
+ &dhcpv6_universe, 39, 1 },
+
+ /* Not yet considering for inclusion. */
+#if 0
+ /* draft-ietf-dhc-paa-option-05 */
+ { "pana-agent", "6A", &dhcpv6_universe, 40, 1 },
+
+ /* RFC4833 OPTIONS */
+
+ { "new-posix-timezone", "t", &dhcpv6_universe, 41, 1 },
+ { "new-tzdb-timezone", "t", &dhcpv6_universe, 42, 1 },
+
+ /* RFC4994 OPTIONS */
+
+ { "ero", "SA", &dhcpv6_universe, 43, 1 },
+#endif
+
+ /* RFC5007 OPTIONS */
+
+ { "lq-query", "X", &dhcpv6_universe, 44, 1 },
+ { "client-data", "X", &dhcpv6_universe, 45, 1 },
+ { "clt-time", "L", &dhcpv6_universe, 46, 1 },
+ { "lq-relay-data", "6X", &dhcpv6_universe, 47, 1 },
+ { "lq-client-link", "6A", &dhcpv6_universe, 48, 1 },
+
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+struct enumeration_value dhcpv6_duid_type_values[] = {
+ { "duid-llt", DUID_LLT }, /* Link-Local Plus Time */
+ { "duid-en", DUID_EN }, /* DUID based upon enterprise-ID. */
+ { "duid-ll", DUID_LL }, /* DUID from Link Local address only. */
+ { NULL, 0 }
+};
+
+struct enumeration dhcpv6_duid_types = {
+ NULL,
+ "duid-types", 2,
+ dhcpv6_duid_type_values
+};
+
+struct enumeration_value dhcpv6_status_code_values[] = {
+ { "success", 0 }, /* Success */
+ { "UnspecFail", 1 }, /* Failure, for unspecified reasons. */
+ { "NoAddrsAvail", 2 }, /* Server has no addresses to assign. */
+ { "NoBinding", 3 }, /* Client record (binding) unavailable. */
+ { "NotOnLink", 4 }, /* Bad prefix for the link. */
+ { "UseMulticast", 5 }, /* Not just good advice. It's the law. */
+ { "NoPrefixAvail", 6 }, /* Server has no prefixes to assign. */
+ { "UnknownQueryType", 7 }, /* Query-type unknown/unsupported. */
+ { "MalformedQuery", 8 }, /* Leasequery not valid. */
+ { "NotConfigured", 9 }, /* The target address is not in config. */
+ { "NotAllowed", 10 }, /* Server doesn't allow the leasequery. */
+ { NULL, 0 }
+};
+
+struct enumeration dhcpv6_status_codes = {
+ NULL,
+ "status-codes", 2,
+ dhcpv6_status_code_values
+};
+
+struct enumeration_value lq6_query_type_values[] = {
+ { "query-by-address", 1 },
+ { "query-by-clientid", 2 },
+ { NULL, 0 }
+};
+
+struct enumeration lq6_query_types = {
+ NULL,
+ "query-types", 2,
+ lq6_query_type_values
+};
+
+struct enumeration_value dhcpv6_message_values[] = {
+ { "SOLICIT", 1 },
+ { "ADVERTISE", 2 },
+ { "REQUEST", 3 },
+ { "CONFIRM", 4 },
+ { "RENEW", 5 },
+ { "REBIND", 6 },
+ { "REPLY", 7 },
+ { "RELEASE", 8 },
+ { "DECLINE", 9 },
+ { "RECONFIGURE", 10 },
+ { "INFORMATION-REQUEST", 11 },
+ { "RELAY-FORW", 12 },
+ { "RELAY-REPL", 13 },
+ { "LEASEQUERY", 14 },
+ { "LEASEQUERY-REPLY", 15 },
+ { NULL, 0 }
+};
+
+/* Some code refers to a different table. */
+const char *dhcpv6_type_names[] = {
+ NULL,
+ "Solicit",
+ "Advertise",
+ "Request",
+ "Confirm",
+ "Renew",
+ "Rebind",
+ "Reply",
+ "Release",
+ "Decline",
+ "Reconfigure",
+ "Information-request",
+ "Relay-forward",
+ "Relay-reply",
+ "Leasequery",
+ "Leasequery-reply"
+};
+const int dhcpv6_type_name_max =
+ (sizeof(dhcpv6_type_names) / sizeof(dhcpv6_type_names[0]));
+
+struct enumeration dhcpv6_messages = {
+ NULL,
+ "dhcpv6-messages", 1,
+ dhcpv6_message_values
+};
+
+struct universe vsio_universe;
+static struct option vsio_options[] = {
+ { "isc", "Eisc6.", &vsio_universe, 2495, 1 },
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+struct universe isc6_universe;
+static struct option isc6_options[] = {
+ { "media", "t", &isc6_universe, 1, 1 },
+ { "update-assist", "X", &isc6_universe, 2, 1 },
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+const char *hardware_types [] = {
+ "unknown-0",
+ "ethernet",
+ "unknown-2",
+ "unknown-3",
+ "unknown-4",
+ "unknown-5",
+ "token-ring",
+ "unknown-7",
+ "fddi",
+ "unknown-9",
+ "unknown-10",
+ "unknown-11",
+ "unknown-12",
+ "unknown-13",
+ "unknown-14",
+ "unknown-15",
+ "unknown-16",
+ "unknown-17",
+ "unknown-18",
+ "unknown-19",
+ "unknown-20",
+ "unknown-21",
+ "unknown-22",
+ "unknown-23",
+ "unknown-24",
+ "unknown-25",
+ "unknown-26",
+ "unknown-27",
+ "unknown-28",
+ "unknown-29",
+ "unknown-30",
+ "unknown-31",
+ "infiniband",
+ "unknown-33",
+ "unknown-34",
+ "unknown-35",
+ "unknown-36",
+ "unknown-37",
+ "unknown-38",
+ "unknown-39",
+ "unknown-40",
+ "unknown-41",
+ "unknown-42",
+ "unknown-43",
+ "unknown-44",
+ "unknown-45",
+ "unknown-46",
+ "unknown-47",
+ "unknown-48",
+ "unknown-49",
+ "unknown-50",
+ "unknown-51",
+ "unknown-52",
+ "unknown-53",
+ "unknown-54",
+ "unknown-55",
+ "unknown-56",
+ "unknown-57",
+ "unknown-58",
+ "unknown-59",
+ "unknown-60",
+ "unknown-61",
+ "unknown-62",
+ "unknown-63",
+ "unknown-64",
+ "unknown-65",
+ "unknown-66",
+ "unknown-67",
+ "unknown-68",
+ "unknown-69",
+ "unknown-70",
+ "unknown-71",
+ "unknown-72",
+ "unknown-73",
+ "unknown-74",
+ "unknown-75",
+ "unknown-76",
+ "unknown-77",
+ "unknown-78",
+ "unknown-79",
+ "unknown-80",
+ "unknown-81",
+ "unknown-82",
+ "unknown-83",
+ "unknown-84",
+ "unknown-85",
+ "unknown-86",
+ "unknown-87",
+ "unknown-88",
+ "unknown-89",
+ "unknown-90",
+ "unknown-91",
+ "unknown-92",
+ "unknown-93",
+ "unknown-94",
+ "unknown-95",
+ "unknown-96",
+ "unknown-97",
+ "unknown-98",
+ "unknown-99",
+ "unknown-100",
+ "unknown-101",
+ "unknown-102",
+ "unknown-103",
+ "unknown-104",
+ "unknown-105",
+ "unknown-106",
+ "unknown-107",
+ "unknown-108",
+ "unknown-109",
+ "unknown-110",
+ "unknown-111",
+ "unknown-112",
+ "unknown-113",
+ "unknown-114",
+ "unknown-115",
+ "unknown-116",
+ "unknown-117",
+ "unknown-118",
+ "unknown-119",
+ "unknown-120",
+ "unknown-121",
+ "unknown-122",
+ "unknown-123",
+ "unknown-124",
+ "unknown-125",
+ "unknown-126",
+ "unknown-127",
+ "unknown-128",
+ "unknown-129",
+ "unknown-130",
+ "unknown-131",
+ "unknown-132",
+ "unknown-133",
+ "unknown-134",
+ "unknown-135",
+ "unknown-136",
+ "unknown-137",
+ "unknown-138",
+ "unknown-139",
+ "unknown-140",
+ "unknown-141",
+ "unknown-142",
+ "unknown-143",
+ "unknown-144",
+ "unknown-145",
+ "unknown-146",
+ "unknown-147",
+ "unknown-148",
+ "unknown-149",
+ "unknown-150",
+ "unknown-151",
+ "unknown-152",
+ "unknown-153",
+ "unknown-154",
+ "unknown-155",
+ "unknown-156",
+ "unknown-157",
+ "unknown-158",
+ "unknown-159",
+ "unknown-160",
+ "unknown-161",
+ "unknown-162",
+ "unknown-163",
+ "unknown-164",
+ "unknown-165",
+ "unknown-166",
+ "unknown-167",
+ "unknown-168",
+ "unknown-169",
+ "unknown-170",
+ "unknown-171",
+ "unknown-172",
+ "unknown-173",
+ "unknown-174",
+ "unknown-175",
+ "unknown-176",
+ "unknown-177",
+ "unknown-178",
+ "unknown-179",
+ "unknown-180",
+ "unknown-181",
+ "unknown-182",
+ "unknown-183",
+ "unknown-184",
+ "unknown-185",
+ "unknown-186",
+ "unknown-187",
+ "unknown-188",
+ "unknown-189",
+ "unknown-190",
+ "unknown-191",
+ "unknown-192",
+ "unknown-193",
+ "unknown-194",
+ "unknown-195",
+ "unknown-196",
+ "unknown-197",
+ "unknown-198",
+ "unknown-199",
+ "unknown-200",
+ "unknown-201",
+ "unknown-202",
+ "unknown-203",
+ "unknown-204",
+ "unknown-205",
+ "unknown-206",
+ "unknown-207",
+ "unknown-208",
+ "unknown-209",
+ "unknown-210",
+ "unknown-211",
+ "unknown-212",
+ "unknown-213",
+ "unknown-214",
+ "unknown-215",
+ "unknown-216",
+ "unknown-217",
+ "unknown-218",
+ "unknown-219",
+ "unknown-220",
+ "unknown-221",
+ "unknown-222",
+ "unknown-223",
+ "unknown-224",
+ "unknown-225",
+ "unknown-226",
+ "unknown-227",
+ "unknown-228",
+ "unknown-229",
+ "unknown-230",
+ "unknown-231",
+ "unknown-232",
+ "unknown-233",
+ "unknown-234",
+ "unknown-235",
+ "unknown-236",
+ "unknown-237",
+ "unknown-238",
+ "unknown-239",
+ "unknown-240",
+ "unknown-241",
+ "unknown-242",
+ "unknown-243",
+ "unknown-244",
+ "unknown-245",
+ "unknown-246",
+ "unknown-247",
+ "unknown-248",
+ "unknown-249",
+ "unknown-250",
+ "unknown-251",
+ "unknown-252",
+ "unknown-253",
+ "unknown-254",
+ "unknown-255" };
+
+universe_hash_t *universe_hash;
+struct universe **universes;
+int universe_count, universe_max;
+
+/* Universe containing names of configuration options, which, rather than
+ writing "option universe-name.option-name ...;", can be set by writing
+ "option-name ...;". */
+
+struct universe *config_universe;
+
+/* XXX: omapi must die...all the below keeps us from having to make the
+ * option structures omapi typed objects, which is a bigger headache.
+ */
+
+char *default_option_format = (char *) "X";
+
+/* Must match hash_reference/dereference types in omapip/hash.h. */
+int
+option_reference(struct option **dest, struct option *src,
+ const char * file, int line)
+{
+ if (!dest || !src)
+ return DHCP_R_INVALIDARG;
+
+ if (*dest) {
+#if defined(POINTER_DEBUG)
+ log_fatal("%s(%d): reference store into non-null pointer!",
+ file, line);
+#else
+ return DHCP_R_INVALIDARG;
+#endif
+ }
+
+ *dest = src;
+ src->refcnt++;
+ rc_register(file, line, dest, src, src->refcnt, 0, RC_MISC);
+ return(ISC_R_SUCCESS);
+}
+
+int
+option_dereference(struct option **dest, const char *file, int line)
+{
+ if (!dest)
+ return DHCP_R_INVALIDARG;
+
+ if (!*dest) {
+#if defined (POINTER_DEBUG)
+ log_fatal("%s(%d): dereference of null pointer!", file, line);
+#else
+ return DHCP_R_INVALIDARG;
+#endif
+ }
+
+ if ((*dest)->refcnt <= 0) {
+#if defined (POINTER_DEBUG)
+ log_fatal("%s(%d): dereference of <= 0 refcnt!", file, line);
+#else
+ return DHCP_R_INVALIDARG;
+#endif
+ }
+
+ (*dest)->refcnt--;
+
+ rc_register(file, line, dest, (*dest), (*dest)->refcnt, 1, RC_MISC);
+
+ if ((*dest)->refcnt == 0) {
+ /* The option name may be packed in the same alloc as the
+ * option structure.
+ */
+ if ((char *) (*dest)->name != (char *) ((*dest) + 1))
+ dfree((char *) (*dest)->name, file, line);
+
+ /* It's either a user-configured format (allocated), or the
+ * default static format.
+ */
+ if (((*dest)->format != NULL) &&
+ ((*dest)->format != default_option_format)) {
+ dfree((char *) (*dest)->format, file, line);
+ }
+
+ dfree(*dest, file, line);
+ }
+
+ *dest = NULL;
+ return ISC_R_SUCCESS;
+}
+
+void initialize_common_option_spaces()
+{
+ unsigned code;
+ int i;
+
+ /* The 'universes' table is dynamically grown to contain
+ * universe as they're configured - except during startup.
+ * Since we know how many we put down in .c files, we can
+ * allocate a more-than-right-sized buffer now, leaving some
+ * space for user-configured option spaces.
+ *
+ * 1: dhcp_universe (dhcpv4 options)
+ * 2: nwip_universe (dhcpv4 NWIP option)
+ * 3: fqdn_universe (dhcpv4 fqdn option - reusable for v6)
+ * 4: vendor_class_universe (VIVCO)
+ * 5: vendor_universe (VIVSO)
+ * 6: isc_universe (dhcpv4 isc config space)
+ * 7: dhcpv6_universe (dhcpv6 options)
+ * 8: vsio_universe (DHCPv6 Vendor-Identified space)
+ * 9: isc6_universe (ISC's Vendor universe in DHCPv6 VSIO)
+ * 10: fqdn6_universe (dhcpv6 fqdn option shill to v4)
+ * 11: agent_universe (dhcpv4 relay agent - see server/stables.c)
+ * 12: server_universe (server's config, see server/stables.c)
+ * 13: user-config
+ * 14: more user-config
+ * 15: more user-config
+ * 16: more user-config
+ */
+ universe_max = 16;
+ i = universe_max * sizeof(struct universe *);
+ if (i <= 0)
+ log_fatal("Ludicrous initial size option space table.");
+ universes = dmalloc(i, MDL);
+ if (universes == NULL)
+ log_fatal("Can't allocate option space table.");
+ memset(universes, 0, i);
+
+ /* Set up the DHCP option universe... */
+ dhcp_universe.name = "dhcp";
+ dhcp_universe.concat_duplicates = 1;
+ dhcp_universe.lookup_func = lookup_hashed_option;
+ dhcp_universe.option_state_dereference =
+ hashed_option_state_dereference;
+ dhcp_universe.save_func = save_hashed_option;
+ dhcp_universe.delete_func = delete_hashed_option;
+ dhcp_universe.encapsulate = hashed_option_space_encapsulate;
+ dhcp_universe.foreach = hashed_option_space_foreach;
+ dhcp_universe.decode = parse_option_buffer;
+ dhcp_universe.length_size = 1;
+ dhcp_universe.tag_size = 1;
+ dhcp_universe.get_tag = getUChar;
+ dhcp_universe.store_tag = putUChar;
+ dhcp_universe.get_length = getUChar;
+ dhcp_universe.store_length = putUChar;
+ dhcp_universe.site_code_min = 0;
+ dhcp_universe.end = DHO_END;
+ dhcp_universe.index = universe_count++;
+ universes [dhcp_universe.index] = &dhcp_universe;
+ if (!option_name_new_hash(&dhcp_universe.name_hash,
+ BYTE_NAME_HASH_SIZE, MDL) ||
+ !option_code_new_hash(&dhcp_universe.code_hash,
+ BYTE_CODE_HASH_SIZE, MDL))
+ log_fatal ("Can't allocate dhcp option hash table.");
+ for (i = 0 ; dhcp_options[i].name ; i++) {
+ option_code_hash_add(dhcp_universe.code_hash,
+ &dhcp_options[i].code, 0,
+ &dhcp_options[i], MDL);
+ option_name_hash_add(dhcp_universe.name_hash,
+ dhcp_options [i].name, 0,
+ &dhcp_options [i], MDL);
+ }
+#if defined(REPORT_HASH_PERFORMANCE)
+ log_info("DHCP name hash: %s",
+ option_name_hash_report(dhcp_universe.name_hash));
+ log_info("DHCP code hash: %s",
+ option_code_hash_report(dhcp_universe.code_hash));
+#endif
+
+ /* Set up the Novell option universe (for option 63)... */
+ nwip_universe.name = "nwip";
+ nwip_universe.concat_duplicates = 0; /* XXX: reference? */
+ nwip_universe.lookup_func = lookup_linked_option;
+ nwip_universe.option_state_dereference =
+ linked_option_state_dereference;
+ nwip_universe.save_func = save_linked_option;
+ nwip_universe.delete_func = delete_linked_option;
+ nwip_universe.encapsulate = nwip_option_space_encapsulate;
+ nwip_universe.foreach = linked_option_space_foreach;
+ nwip_universe.decode = parse_option_buffer;
+ nwip_universe.length_size = 1;
+ nwip_universe.tag_size = 1;
+ nwip_universe.get_tag = getUChar;
+ nwip_universe.store_tag = putUChar;
+ nwip_universe.get_length = getUChar;
+ nwip_universe.store_length = putUChar;
+ nwip_universe.site_code_min = 0;
+ nwip_universe.end = 0;
+ code = DHO_NWIP_SUBOPTIONS;
+ nwip_universe.enc_opt = NULL;
+ if (!option_code_hash_lookup(&nwip_universe.enc_opt,
+ dhcp_universe.code_hash, &code, 0, MDL))
+ log_fatal("Unable to find NWIP parent option (%s:%d).", MDL);
+ nwip_universe.index = universe_count++;
+ universes [nwip_universe.index] = &nwip_universe;
+ if (!option_name_new_hash(&nwip_universe.name_hash,
+ NWIP_HASH_SIZE, MDL) ||
+ !option_code_new_hash(&nwip_universe.code_hash,
+ NWIP_HASH_SIZE, MDL))
+ log_fatal ("Can't allocate nwip option hash table.");
+ for (i = 0 ; nwip_options[i].name ; i++) {
+ option_code_hash_add(nwip_universe.code_hash,
+ &nwip_options[i].code, 0,
+ &nwip_options[i], MDL);
+ option_name_hash_add(nwip_universe.name_hash,
+ nwip_options[i].name, 0,
+ &nwip_options[i], MDL);
+ }
+#if defined(REPORT_HASH_PERFORMANCE)
+ log_info("NWIP name hash: %s",
+ option_name_hash_report(nwip_universe.name_hash));
+ log_info("NWIP code hash: %s",
+ option_code_hash_report(nwip_universe.code_hash));
+#endif
+
+ /* Set up the FQDN option universe... */
+ fqdn_universe.name = "fqdn";
+ fqdn_universe.concat_duplicates = 0;
+ fqdn_universe.lookup_func = lookup_linked_option;
+ fqdn_universe.option_state_dereference =
+ linked_option_state_dereference;
+ fqdn_universe.save_func = save_linked_option;
+ fqdn_universe.delete_func = delete_linked_option;
+ fqdn_universe.encapsulate = fqdn_option_space_encapsulate;
+ fqdn_universe.foreach = linked_option_space_foreach;
+ fqdn_universe.decode = fqdn_universe_decode;
+ fqdn_universe.length_size = 1;
+ fqdn_universe.tag_size = 1;
+ fqdn_universe.get_tag = getUChar;
+ fqdn_universe.store_tag = putUChar;
+ fqdn_universe.get_length = getUChar;
+ fqdn_universe.store_length = putUChar;
+ fqdn_universe.site_code_min = 0;
+ fqdn_universe.end = 0;
+ fqdn_universe.index = universe_count++;
+ code = DHO_FQDN;
+ fqdn_universe.enc_opt = NULL;
+ if (!option_code_hash_lookup(&fqdn_universe.enc_opt,
+ dhcp_universe.code_hash, &code, 0, MDL))
+ log_fatal("Unable to find FQDN parent option (%s:%d).", MDL);
+ universes [fqdn_universe.index] = &fqdn_universe;
+ if (!option_name_new_hash(&fqdn_universe.name_hash,
+ FQDN_HASH_SIZE, MDL) ||
+ !option_code_new_hash(&fqdn_universe.code_hash,
+ FQDN_HASH_SIZE, MDL))
+ log_fatal ("Can't allocate fqdn option hash table.");
+ for (i = 0 ; fqdn_options[i].name ; i++) {
+ option_code_hash_add(fqdn_universe.code_hash,
+ &fqdn_options[i].code, 0,
+ &fqdn_options[i], MDL);
+ option_name_hash_add(fqdn_universe.name_hash,
+ fqdn_options[i].name, 0,
+ &fqdn_options[i], MDL);
+ }
+#if defined(REPORT_HASH_PERFORMANCE)
+ log_info("FQDN name hash: %s",
+ option_name_hash_report(fqdn_universe.name_hash));
+ log_info("FQDN code hash: %s",
+ option_code_hash_report(fqdn_universe.code_hash));
+#endif
+
+ /* Set up the Vendor Identified Vendor Class options (for option
+ * 125)...
+ */
+ vendor_class_universe.name = "vendor-class";
+ vendor_class_universe.concat_duplicates = 0; /* XXX: reference? */
+ vendor_class_universe.lookup_func = lookup_hashed_option;
+ vendor_class_universe.option_state_dereference =
+ hashed_option_state_dereference;
+ vendor_class_universe.save_func = save_hashed_option;
+ vendor_class_universe.delete_func = delete_hashed_option;
+ vendor_class_universe.encapsulate = hashed_option_space_encapsulate;
+ vendor_class_universe.foreach = hashed_option_space_foreach;
+ vendor_class_universe.decode = parse_option_buffer;
+ vendor_class_universe.length_size = 1;
+ vendor_class_universe.tag_size = 4;
+ vendor_class_universe.get_tag = getULong;
+ vendor_class_universe.store_tag = putULong;
+ vendor_class_universe.get_length = getUChar;
+ vendor_class_universe.store_length = putUChar;
+ vendor_class_universe.site_code_min = 0;
+ vendor_class_universe.end = 0;
+ code = DHO_VIVCO_SUBOPTIONS;
+ vendor_class_universe.enc_opt = NULL;
+ if (!option_code_hash_lookup(&vendor_class_universe.enc_opt,
+ dhcp_universe.code_hash, &code, 0, MDL))
+ log_fatal("Unable to find VIVCO parent option (%s:%d).", MDL);
+ vendor_class_universe.index = universe_count++;
+ universes[vendor_class_universe.index] = &vendor_class_universe;
+ if (!option_name_new_hash(&vendor_class_universe.name_hash,
+ VIVCO_HASH_SIZE, MDL) ||
+ !option_code_new_hash(&vendor_class_universe.code_hash,
+ VIVCO_HASH_SIZE, MDL))
+ log_fatal("Can't allocate Vendor Identified Vendor Class "
+ "option hash table.");
+ for (i = 0 ; vendor_class_options[i].name ; i++) {
+ option_code_hash_add(vendor_class_universe.code_hash,
+ &vendor_class_options[i].code, 0,
+ &vendor_class_options[i], MDL);
+ option_name_hash_add(vendor_class_universe.name_hash,
+ vendor_class_options[i].name, 0,
+ &vendor_class_options[i], MDL);
+ }
+#if defined(REPORT_HASH_PERFORMANCE)
+ log_info("VIVCO name hash: %s",
+ option_name_hash_report(vendor_class_universe.name_hash));
+ log_info("VIVCO code hash: %s",
+ option_code_hash_report(vendor_class_universe.code_hash));
+#endif
+
+ /* Set up the Vendor Identified Vendor Sub-options (option 126)... */
+ vendor_universe.name = "vendor";
+ vendor_universe.concat_duplicates = 0; /* XXX: reference? */
+ vendor_universe.lookup_func = lookup_hashed_option;
+ vendor_universe.option_state_dereference =
+ hashed_option_state_dereference;
+ vendor_universe.save_func = save_hashed_option;
+ vendor_universe.delete_func = delete_hashed_option;
+ vendor_universe.encapsulate = hashed_option_space_encapsulate;
+ vendor_universe.foreach = hashed_option_space_foreach;
+ vendor_universe.decode = parse_option_buffer;
+ vendor_universe.length_size = 1;
+ vendor_universe.tag_size = 4;
+ vendor_universe.get_tag = getULong;
+ vendor_universe.store_tag = putULong;
+ vendor_universe.get_length = getUChar;
+ vendor_universe.store_length = putUChar;
+ vendor_universe.site_code_min = 0;
+ vendor_universe.end = 0;
+ code = DHO_VIVSO_SUBOPTIONS;
+ vendor_universe.enc_opt = NULL;
+ if (!option_code_hash_lookup(&vendor_universe.enc_opt,
+ dhcp_universe.code_hash, &code, 0, MDL))
+ log_fatal("Unable to find VIVSO parent option (%s:%d).", MDL);
+ vendor_universe.index = universe_count++;
+ universes[vendor_universe.index] = &vendor_universe;
+ if (!option_name_new_hash(&vendor_universe.name_hash,
+ VIVSO_HASH_SIZE, MDL) ||
+ !option_code_new_hash(&vendor_universe.code_hash,
+ VIVSO_HASH_SIZE, MDL))
+ log_fatal("Can't allocate Vendor Identified Vendor Sub-"
+ "options hash table.");
+ for (i = 0 ; vendor_options[i].name ; i++) {
+ option_code_hash_add(vendor_universe.code_hash,
+ &vendor_options[i].code, 0,
+ &vendor_options[i], MDL);
+ option_name_hash_add(vendor_universe.name_hash,
+ vendor_options[i].name, 0,
+ &vendor_options[i], MDL);
+ }
+#if defined(REPORT_HASH_PERFORMANCE)
+ log_info("VIVSO name hash: %s",
+ option_name_hash_report(vendor_universe.name_hash));
+ log_info("VIVSO code hash: %s",
+ option_code_hash_report(vendor_universe.code_hash));
+#endif
+
+ /* Set up the ISC Vendor-option universe (for option 125.2495)... */
+ isc_universe.name = "isc";
+ isc_universe.concat_duplicates = 0; /* XXX: check VIVSO ref */
+ isc_universe.lookup_func = lookup_linked_option;
+ isc_universe.option_state_dereference =
+ linked_option_state_dereference;
+ isc_universe.save_func = save_linked_option;
+ isc_universe.delete_func = delete_linked_option;
+ isc_universe.encapsulate = linked_option_space_encapsulate;
+ isc_universe.foreach = linked_option_space_foreach;
+ isc_universe.decode = parse_option_buffer;
+ isc_universe.length_size = 2;
+ isc_universe.tag_size = 2;
+ isc_universe.get_tag = getUShort;
+ isc_universe.store_tag = putUShort;
+ isc_universe.get_length = getUShort;
+ isc_universe.store_length = putUShort;
+ isc_universe.site_code_min = 0;
+ isc_universe.end = 0;
+ code = VENDOR_ISC_SUBOPTIONS;
+ isc_universe.enc_opt = NULL;
+ if (!option_code_hash_lookup(&isc_universe.enc_opt,
+ vendor_universe.code_hash, &code, 0, MDL))
+ log_fatal("Unable to find ISC parent option (%s:%d).", MDL);
+ isc_universe.index = universe_count++;
+ universes[isc_universe.index] = &isc_universe;
+ if (!option_name_new_hash(&isc_universe.name_hash,
+ VIV_ISC_HASH_SIZE, MDL) ||
+ !option_code_new_hash(&isc_universe.code_hash,
+ VIV_ISC_HASH_SIZE, MDL))
+ log_fatal("Can't allocate ISC Vendor options hash table.");
+ for (i = 0 ; isc_options[i].name ; i++) {
+ option_code_hash_add(isc_universe.code_hash,
+ &isc_options[i].code, 0,
+ &isc_options[i], MDL);
+ option_name_hash_add(isc_universe.name_hash,
+ isc_options[i].name, 0,
+ &isc_options[i], MDL);
+ }
+#if defined(REPORT_HASH_PERFORMANCE)
+ log_info("ISC name hash: %s",
+ option_name_hash_report(isc_universe.name_hash));
+ log_info("ISC code hash: %s",
+ option_code_hash_report(isc_universe.code_hash));
+#endif
+
+ /* Set up the DHCPv6 root universe. */
+ dhcpv6_universe.name = "dhcp6";
+ dhcpv6_universe.concat_duplicates = 0;
+ dhcpv6_universe.lookup_func = lookup_hashed_option;
+ dhcpv6_universe.option_state_dereference =
+ hashed_option_state_dereference;
+ dhcpv6_universe.save_func = save_hashed_option;
+ dhcpv6_universe.delete_func = delete_hashed_option;
+ dhcpv6_universe.encapsulate = hashed_option_space_encapsulate;
+ dhcpv6_universe.foreach = hashed_option_space_foreach;
+ dhcpv6_universe.decode = parse_option_buffer;
+ dhcpv6_universe.length_size = 2;
+ dhcpv6_universe.tag_size = 2;
+ dhcpv6_universe.get_tag = getUShort;
+ dhcpv6_universe.store_tag = putUShort;
+ dhcpv6_universe.get_length = getUShort;
+ dhcpv6_universe.store_length = putUShort;
+ dhcpv6_universe.site_code_min = 0;
+ /* DHCPv6 has no END option. */
+ dhcpv6_universe.end = 0x00;
+ dhcpv6_universe.index = universe_count++;
+ universes[dhcpv6_universe.index] = &dhcpv6_universe;
+ if (!option_name_new_hash(&dhcpv6_universe.name_hash,
+ WORD_NAME_HASH_SIZE, MDL) ||
+ !option_code_new_hash(&dhcpv6_universe.code_hash,
+ WORD_CODE_HASH_SIZE, MDL))
+ log_fatal("Can't allocate dhcpv6 option hash tables.");
+ for (i = 0 ; dhcpv6_options[i].name ; i++) {
+ option_code_hash_add(dhcpv6_universe.code_hash,
+ &dhcpv6_options[i].code, 0,
+ &dhcpv6_options[i], MDL);
+ option_name_hash_add(dhcpv6_universe.name_hash,
+ dhcpv6_options[i].name, 0,
+ &dhcpv6_options[i], MDL);
+ }
+
+ /* Add DHCPv6 protocol enumeration sets. */
+ add_enumeration(&dhcpv6_duid_types);
+ add_enumeration(&dhcpv6_status_codes);
+ add_enumeration(&dhcpv6_messages);
+
+ /* Set up DHCPv6 VSIO universe. */
+ vsio_universe.name = "vsio";
+ vsio_universe.concat_duplicates = 0;
+ vsio_universe.lookup_func = lookup_hashed_option;
+ vsio_universe.option_state_dereference =
+ hashed_option_state_dereference;
+ vsio_universe.save_func = save_hashed_option;
+ vsio_universe.delete_func = delete_hashed_option;
+ vsio_universe.encapsulate = hashed_option_space_encapsulate;
+ vsio_universe.foreach = hashed_option_space_foreach;
+ vsio_universe.decode = parse_option_buffer;
+ vsio_universe.length_size = 0;
+ vsio_universe.tag_size = 4;
+ vsio_universe.get_tag = getULong;
+ vsio_universe.store_tag = putULong;
+ vsio_universe.get_length = NULL;
+ vsio_universe.store_length = NULL;
+ vsio_universe.site_code_min = 0;
+ /* No END option. */
+ vsio_universe.end = 0x00;
+ code = D6O_VENDOR_OPTS;
+ if (!option_code_hash_lookup(&vsio_universe.enc_opt,
+ dhcpv6_universe.code_hash, &code, 0, MDL))
+ log_fatal("Unable to find VSIO parent option (%s:%d).", MDL);
+ vsio_universe.index = universe_count++;
+ universes[vsio_universe.index] = &vsio_universe;
+ if (!option_name_new_hash(&vsio_universe.name_hash,
+ VSIO_HASH_SIZE, MDL) ||
+ !option_code_new_hash(&vsio_universe.code_hash,
+ VSIO_HASH_SIZE, MDL))
+ log_fatal("Can't allocate Vendor Specific Information "
+ "Options space.");
+ for (i = 0 ; vsio_options[i].name != NULL ; i++) {
+ option_code_hash_add(vsio_universe.code_hash,
+ &vsio_options[i].code, 0,
+ &vsio_options[i], MDL);
+ option_name_hash_add(vsio_universe.name_hash,
+ vsio_options[i].name, 0,
+ &vsio_options[i], MDL);
+ }
+
+ /* Add ISC VSIO sub-sub-option space. */
+ isc6_universe.name = "isc6";
+ isc6_universe.concat_duplicates = 0;
+ isc6_universe.lookup_func = lookup_hashed_option;
+ isc6_universe.option_state_dereference =
+ hashed_option_state_dereference;
+ isc6_universe.save_func = save_hashed_option;
+ isc6_universe.delete_func = delete_hashed_option;
+ isc6_universe.encapsulate = hashed_option_space_encapsulate;
+ isc6_universe.foreach = hashed_option_space_foreach;
+ isc6_universe.decode = parse_option_buffer;
+ isc6_universe.length_size = 0;
+ isc6_universe.tag_size = 4;
+ isc6_universe.get_tag = getULong;
+ isc6_universe.store_tag = putULong;
+ isc6_universe.get_length = NULL;
+ isc6_universe.store_length = NULL;
+ isc6_universe.site_code_min = 0;
+ /* No END option. */
+ isc6_universe.end = 0x00;
+ code = 2495;
+ if (!option_code_hash_lookup(&isc6_universe.enc_opt,
+ vsio_universe.code_hash, &code, 0, MDL))
+ log_fatal("Unable to find ISC parent option (%s:%d).", MDL);
+ isc6_universe.index = universe_count++;
+ universes[isc6_universe.index] = &isc6_universe;
+ if (!option_name_new_hash(&isc6_universe.name_hash,
+ VIV_ISC_HASH_SIZE, MDL) ||
+ !option_code_new_hash(&isc6_universe.code_hash,
+ VIV_ISC_HASH_SIZE, MDL))
+ log_fatal("Can't allocate Vendor Specific Information "
+ "Options space.");
+ for (i = 0 ; isc6_options[i].name != NULL ; i++) {
+ option_code_hash_add(isc6_universe.code_hash,
+ &isc6_options[i].code, 0,
+ &isc6_options[i], MDL);
+ option_name_hash_add(isc6_universe.name_hash,
+ isc6_options[i].name, 0,
+ &isc6_options[i], MDL);
+ }
+
+ /* The fqdn6 option space is a protocol-wrapper shill for the
+ * old DHCPv4 space.
+ */
+ fqdn6_universe.name = "fqdn6-if-you-see-me-its-a-bug-bug-bug";
+ fqdn6_universe.lookup_func = lookup_fqdn6_option;
+ fqdn6_universe.option_state_dereference = NULL; /* Covered by v4. */
+ fqdn6_universe.save_func = save_fqdn6_option;
+ fqdn6_universe.delete_func = delete_fqdn6_option;
+ fqdn6_universe.encapsulate = fqdn6_option_space_encapsulate;
+ fqdn6_universe.foreach = fqdn6_option_space_foreach;
+ fqdn6_universe.decode = fqdn6_universe_decode;
+ /* This is not a 'normal' encapsulated space, so these values are
+ * meaningless.
+ */
+ fqdn6_universe.length_size = 0;
+ fqdn6_universe.tag_size = 0;
+ fqdn6_universe.get_tag = NULL;
+ fqdn6_universe.store_tag = NULL;
+ fqdn6_universe.get_length = NULL;
+ fqdn6_universe.store_length = NULL;
+ fqdn6_universe.site_code_min = 0;
+ fqdn6_universe.end = 0;
+ fqdn6_universe.index = universe_count++;
+ code = D6O_CLIENT_FQDN;
+ fqdn6_universe.enc_opt = NULL;
+ if (!option_code_hash_lookup(&fqdn6_universe.enc_opt,
+ dhcpv6_universe.code_hash, &code, 0, MDL))
+ log_fatal("Unable to find FQDN v6 parent option. (%s:%d).",
+ MDL);
+ universes[fqdn6_universe.index] = &fqdn6_universe;
+ /* The fqdn6 space shares the same option space as the v4 space.
+ * So there are no name or code hashes on the v6 side.
+ */
+ fqdn6_universe.name_hash = NULL;
+ fqdn6_universe.code_hash = NULL;
+
+
+ /* Set up the hash of DHCPv4 universes. */
+ universe_new_hash(&universe_hash, UNIVERSE_HASH_SIZE, MDL);
+ universe_hash_add(universe_hash, dhcp_universe.name, 0,
+ &dhcp_universe, MDL);
+ universe_hash_add(universe_hash, nwip_universe.name, 0,
+ &nwip_universe, MDL);
+ universe_hash_add(universe_hash, fqdn_universe.name, 0,
+ &fqdn_universe, MDL);
+ universe_hash_add(universe_hash, vendor_class_universe.name, 0,
+ &vendor_class_universe, MDL);
+ universe_hash_add(universe_hash, vendor_universe.name, 0,
+ &vendor_universe, MDL);
+ universe_hash_add(universe_hash, isc_universe.name, 0,
+ &isc_universe, MDL);
+
+ /* Set up hashes for DHCPv6 universes. */
+ universe_hash_add(universe_hash, dhcpv6_universe.name, 0,
+ &dhcpv6_universe, MDL);
+ universe_hash_add(universe_hash, vsio_universe.name, 0,
+ &vsio_universe, MDL);
+ universe_hash_add(universe_hash, isc6_universe.name, 0,
+ &isc6_universe, MDL);
+/* This should not be necessary. Listing here just for consistency.
+ * universe_hash_add(universe_hash, fqdn6_universe.name, 0,
+ * &fqdn6_universe, MDL);
+ */
+}
diff --git a/common/tests/Atffile b/common/tests/Atffile
new file mode 100644
index 0000000..10402ce
--- /dev/null
+++ b/common/tests/Atffile
@@ -0,0 +1,5 @@
+Content-Type: application/X-atf-atffile; version="1"
+
+prop: test-suite = dhcp4
+
+tp-glob: *_unittest
diff --git a/common/tests/Makefile.am b/common/tests/Makefile.am
new file mode 100644
index 0000000..cfe3d83
--- /dev/null
+++ b/common/tests/Makefile.am
@@ -0,0 +1,30 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = $(ATF_CFLAGS) -I$(top_srcdir)/includes
+
+EXTRA_DIST = Atffile
+
+ATF_TESTS =
+
+if HAVE_ATF
+
+ATF_TESTS += alloc_unittest ns_name_unittest
+
+alloc_unittest_SOURCES = test_alloc.c $(top_srcdir)/tests/t_api_dhcp.c
+alloc_unittest_LDADD = $(ATF_LDFLAGS)
+alloc_unittest_LDADD += ../libdhcp.a \
+ ../../omapip/libomapi.a ../../bind/lib/libdns.a \
+ ../../bind/lib/libisc.a
+
+ns_name_unittest_SOURCES = ns_name_test.c $(top_srcdir)/tests/t_api_dhcp.c
+ns_name_unittest_LDADD = $(ATF_LDFLAGS)
+ns_name_unittest_LDADD += ../libdhcp.a \
+ ../../omapip/libomapi.a ../../bind/lib/libdns.a \
+ ../../bind/lib/libisc.a
+
+check: $(ATF_TESTS)
+ sh ${top_srcdir}/tests/unittest.sh
+
+endif
+
+check_PROGRAMS = $(ATF_TESTS)
diff --git a/common/tests/Makefile.in b/common/tests/Makefile.in
new file mode 100644
index 0000000..67d5865
--- /dev/null
+++ b/common/tests/Makefile.in
@@ -0,0 +1,694 @@
+# Makefile.in generated by automake 1.14 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@HAVE_ATF_TRUE@am__append_1 = alloc_unittest ns_name_unittest
+check_PROGRAMS = $(am__EXEEXT_2)
+subdir = common/tests
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(top_srcdir)/depcomp
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/includes/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@HAVE_ATF_TRUE@am__EXEEXT_1 = alloc_unittest$(EXEEXT) \
+@HAVE_ATF_TRUE@ ns_name_unittest$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+am__alloc_unittest_SOURCES_DIST = test_alloc.c \
+ $(top_srcdir)/tests/t_api_dhcp.c
+@HAVE_ATF_TRUE@am_alloc_unittest_OBJECTS = test_alloc.$(OBJEXT) \
+@HAVE_ATF_TRUE@ t_api_dhcp.$(OBJEXT)
+alloc_unittest_OBJECTS = $(am_alloc_unittest_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_ATF_TRUE@alloc_unittest_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+@HAVE_ATF_TRUE@ ../libdhcp.a ../../omapip/libomapi.a \
+@HAVE_ATF_TRUE@ ../../bind/lib/libdns.a ../../bind/lib/libisc.a
+am__ns_name_unittest_SOURCES_DIST = ns_name_test.c \
+ $(top_srcdir)/tests/t_api_dhcp.c
+@HAVE_ATF_TRUE@am_ns_name_unittest_OBJECTS = ns_name_test.$(OBJEXT) \
+@HAVE_ATF_TRUE@ t_api_dhcp.$(OBJEXT)
+ns_name_unittest_OBJECTS = $(am_ns_name_unittest_OBJECTS)
+@HAVE_ATF_TRUE@ns_name_unittest_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+@HAVE_ATF_TRUE@ ../libdhcp.a ../../omapip/libomapi.a \
+@HAVE_ATF_TRUE@ ../../bind/lib/libdns.a ../../bind/lib/libisc.a
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/includes
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(alloc_unittest_SOURCES) $(ns_name_unittest_SOURCES)
+DIST_SOURCES = $(am__alloc_unittest_SOURCES_DIST) \
+ $(am__ns_name_unittest_SOURCES_DIST)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+ATF_BIN = @ATF_BIN@
+ATF_CFLAGS = @ATF_CFLAGS@
+ATF_LDFLAGS = @ATF_LDFLAGS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDAP_CFLAGS = @LDAP_CFLAGS@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_prefix_program = @ac_prefix_program@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+byte_order = @byte_order@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+AM_CPPFLAGS = $(ATF_CFLAGS) -I$(top_srcdir)/includes
+EXTRA_DIST = Atffile
+ATF_TESTS = $(am__append_1)
+@HAVE_ATF_TRUE@alloc_unittest_SOURCES = test_alloc.c $(top_srcdir)/tests/t_api_dhcp.c
+@HAVE_ATF_TRUE@alloc_unittest_LDADD = $(ATF_LDFLAGS) ../libdhcp.a \
+@HAVE_ATF_TRUE@ ../../omapip/libomapi.a ../../bind/lib/libdns.a \
+@HAVE_ATF_TRUE@ ../../bind/lib/libisc.a
+@HAVE_ATF_TRUE@ns_name_unittest_SOURCES = ns_name_test.c $(top_srcdir)/tests/t_api_dhcp.c
+@HAVE_ATF_TRUE@ns_name_unittest_LDADD = $(ATF_LDFLAGS) ../libdhcp.a \
+@HAVE_ATF_TRUE@ ../../omapip/libomapi.a ../../bind/lib/libdns.a \
+@HAVE_ATF_TRUE@ ../../bind/lib/libisc.a
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign common/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign common/tests/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-checkPROGRAMS:
+ -test -z "$(check_PROGRAMS)" || rm -f $(check_PROGRAMS)
+
+alloc_unittest$(EXEEXT): $(alloc_unittest_OBJECTS) $(alloc_unittest_DEPENDENCIES) $(EXTRA_alloc_unittest_DEPENDENCIES)
+ @rm -f alloc_unittest$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(alloc_unittest_OBJECTS) $(alloc_unittest_LDADD) $(LIBS)
+
+ns_name_unittest$(EXEEXT): $(ns_name_unittest_OBJECTS) $(ns_name_unittest_DEPENDENCIES) $(EXTRA_ns_name_unittest_DEPENDENCIES)
+ @rm -f ns_name_unittest$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(ns_name_unittest_OBJECTS) $(ns_name_unittest_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ns_name_test.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_api_dhcp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_alloc.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+t_api_dhcp.o: $(top_srcdir)/tests/t_api_dhcp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT t_api_dhcp.o -MD -MP -MF $(DEPDIR)/t_api_dhcp.Tpo -c -o t_api_dhcp.o `test -f '$(top_srcdir)/tests/t_api_dhcp.c' || echo '$(srcdir)/'`$(top_srcdir)/tests/t_api_dhcp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_api_dhcp.Tpo $(DEPDIR)/t_api_dhcp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(top_srcdir)/tests/t_api_dhcp.c' object='t_api_dhcp.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o t_api_dhcp.o `test -f '$(top_srcdir)/tests/t_api_dhcp.c' || echo '$(srcdir)/'`$(top_srcdir)/tests/t_api_dhcp.c
+
+t_api_dhcp.obj: $(top_srcdir)/tests/t_api_dhcp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT t_api_dhcp.obj -MD -MP -MF $(DEPDIR)/t_api_dhcp.Tpo -c -o t_api_dhcp.obj `if test -f '$(top_srcdir)/tests/t_api_dhcp.c'; then $(CYGPATH_W) '$(top_srcdir)/tests/t_api_dhcp.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/tests/t_api_dhcp.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_api_dhcp.Tpo $(DEPDIR)/t_api_dhcp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(top_srcdir)/tests/t_api_dhcp.c' object='t_api_dhcp.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o t_api_dhcp.obj `if test -f '$(top_srcdir)/tests/t_api_dhcp.c'; then $(CYGPATH_W) '$(top_srcdir)/tests/t_api_dhcp.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/tests/t_api_dhcp.c'; fi`
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS)
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-checkPROGRAMS clean-generic mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-checkPROGRAMS clean-generic cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic pdf pdf-am \
+ ps ps-am tags tags-am uninstall uninstall-am
+
+
+@HAVE_ATF_TRUE@check: $(ATF_TESTS)
+@HAVE_ATF_TRUE@ sh ${top_srcdir}/tests/unittest.sh
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/common/tests/ns_name_test.c b/common/tests/ns_name_test.c
new file mode 100644
index 0000000..93ffa76
--- /dev/null
+++ b/common/tests/ns_name_test.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2014 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Tests the newly added functions: MRns_name_compress_list and
+ * MRns_name_uncompress_list. These two functions rely on most of
+ * the other functions in ns_name.c. If these tests pass, then the
+ * majority of those functions work.
+ *
+ * This is not exhaustive test of these functions, much more could be
+ * done.
+ */
+#include "config.h"
+#include <atf-c.h>
+#include "dhcpd.h"
+
+ATF_TC(MRns_name_list_funcs);
+
+ATF_TC_HEAD(MRns_name_list_funcs, tc) {
+ atf_tc_set_md_var(tc, "descr", "MRns_name list funcs test, "
+ "compress from text, decompress to text");
+}
+
+void concat_lists (const char* label, struct data_string* list1,
+ struct data_string* list2, const char *refbuf, size_t reflen);
+
+ATF_TC_BODY(MRns_name_list_funcs, tc) {
+
+ const char text_list[] = "one.two.com,three.two.com,four.two.com";
+ unsigned char comp_list[] = {
+ 0x03,0x6f,0x6e,0x65,0x03,0x74,0x77,0x6f,0x03,0x63,0x6f,
+ 0x6d,0x00,0x05,0x74,0x68,0x72,0x65,0x65,0xc0,0x04,0x04,
+ 0x66,0x6f,0x75,0x72,0xc0,0x04};
+ unsigned char compbuf[sizeof(comp_list)];
+ char textbuf[sizeof(text_list)];
+ int ret;
+
+ memset(compbuf, 0x00, sizeof(compbuf));
+
+ /* Compress the reference text list */
+ ret = MRns_name_compress_list(text_list, sizeof(text_list),
+ compbuf, sizeof(compbuf));
+
+ /* Verify compressed length is correct */
+ ATF_REQUIRE_MSG((ret == sizeof(compbuf)), "compressed len %d wrong", ret);
+
+ /* Verify compressed content is correct */
+ ATF_REQUIRE_MSG((memcmp(comp_list, compbuf, sizeof(compbuf)) == 0),
+ "compressed buffer content wrong");
+
+ /* Decompress the new compressed list */
+ ret = MRns_name_uncompress_list(compbuf, ret, textbuf, sizeof(textbuf));
+
+ /* Verify decompressed length is correct */
+ ATF_REQUIRE_MSG((ret == strlen(text_list)),
+ "uncompressed len %d wrong", ret);
+
+ /* Verify decompressed content is correct */
+ ATF_REQUIRE_MSG((memcmp(textbuf, text_list, sizeof(textbuf)) == 0),
+ "uncompressed buffer content wrong");
+}
+
+ATF_TC(concat_dclists);
+
+ATF_TC_HEAD(concat_dclists, tc) {
+ atf_tc_set_md_var(tc, "descr", "concat_dclists function test, "
+ "permutate concating empty and non-empty lists");
+}
+
+ATF_TC_BODY(concat_dclists, tc) {
+ /* Compressed list version of "booya.com" */
+ const char data[] =
+ {0x05, 0x62, 0x6f, 0x6f, 0x79, 0x61, 0x03, 0x63, 0x6f, 0x6d, 0x00 };
+
+ /* Concatenation of data with itself */
+ const char data2[] =
+ {0x05, 0x62, 0x6f, 0x6f, 0x79, 0x61, 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ 0xc0, 0x00 };
+
+ struct data_string nonempty;
+ struct data_string empty;
+
+ /* Make a non-empty compressed list from data[] */
+ nonempty.len = sizeof(data);
+ buffer_allocate(&(nonempty.buffer), nonempty.len, MDL);
+ memcpy(nonempty.buffer->data, data, nonempty.len);
+ nonempty.data = nonempty.buffer->data;
+
+ /* Permutate NULL with non-empty list */
+ concat_lists("null + null", NULL, NULL, "", 1);
+ concat_lists("null + nonempty", NULL, &nonempty, data, sizeof(data));
+ concat_lists("nonempty + null", &nonempty, NULL, data, sizeof(data));
+
+ /* Permutate zeroed-out list with non-empty list */
+ memset (&empty, 0x00, sizeof(struct data_string));
+ concat_lists("zero-list + zero-list", &empty, &empty, "", 1);
+ concat_lists("zero-list + nonempty", &empty, &nonempty, data, sizeof(data));
+ concat_lists("nonempty + zero-list", &nonempty, &empty, data, sizeof(data));
+
+ /* Create an empty list which is a buffer with 1 null in it */
+ /* Make sure those work the same as zeroed out data_strings */
+ buffer_allocate (&empty.buffer, 1, MDL);
+ empty.len = 1;
+ empty.data = empty.buffer->data;
+
+ /* Permutate with empty list */
+ concat_lists("empty + empty", &empty, &empty, "", 1);
+ concat_lists("empty + nonempty", &empty, &nonempty, data, sizeof(data));
+ concat_lists("nonempty + empty", &nonempty, &empty, data, sizeof(data));
+
+ /* Permutate with list len of 0 */
+ empty.len = 0;
+ concat_lists("zero-len + zero-len", &empty, &empty, "", 1);
+ concat_lists("zero-len + nonempty", &empty, &nonempty, data, sizeof(data));
+ concat_lists("nonempty + zero-len", &nonempty, &empty, data, sizeof(data));
+
+ /* Lastly, make sure two non-empty lists concat correctly */
+ concat_lists("nonempty + nonempty", &nonempty, &nonempty,
+ data2, sizeof(data2));
+
+ data_string_forget(&empty, MDL);
+ data_string_forget(&nonempty, MDL);
+}
+
+/* Helper function which tests concatenating two compressed lists
+*
+* \param label text to print in error message
+* \param list1 data_string containing first list
+* \param list2 data_string containing second list
+* \param refbuf buffer containing the expected concatentated data
+* \param reflen length of the expected data
+*/
+void concat_lists (const char* label, struct data_string* list1,
+ struct data_string* list2, const char *refbuf, size_t reflen)
+{
+ struct data_string result;
+ int rc;
+
+ memset (&result, 0x00, sizeof(struct data_string));
+ rc = concat_dclists (&result, list1, list2);
+ ATF_REQUIRE_MSG((rc >= 0), "%s concat_dclists call failed %d",
+ label, rc);
+
+ ATF_REQUIRE_MSG(result.len == reflen, "%s result len: %d incorrect",
+ label, result.len);
+
+ if (refbuf != NULL) {
+ ATF_REQUIRE_MSG((memcmp(result.data, refbuf, reflen) == 0),
+ "%s content is incorrect", label);
+ }
+
+ data_string_forget(&result, MDL);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, MRns_name_list_funcs);
+ ATF_TP_ADD_TC(tp, concat_dclists);
+
+ return (atf_no_error());
+}
diff --git a/common/tests/test_alloc.c b/common/tests/test_alloc.c
new file mode 100644
index 0000000..9e08213
--- /dev/null
+++ b/common/tests/test_alloc.c
@@ -0,0 +1,501 @@
+/*
+ * Copyright (c) 2007,2009-2014 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * We test the functions provided in alloc.c here. These are very
+ * basic functions, and it is very important that they work correctly.
+ *
+ * You can see two different styles of testing:
+ *
+ * - In the first, we have a single test for each function that tests
+ * all of the possible ways it can operate. (This is the case for
+ * the buffer tests.)
+ *
+ * - In the second, we have a separate test for each of the ways a
+ * function can operate. (This is the case for the data_string
+ * tests.)
+ *
+ * The advantage of a single test per function is that you have fewer
+ * tests, and less duplicated and extra code. The advantage of having
+ * a separate test is that each test is simpler. Plus if you need to
+ * allow certain tests to fail for some reason (known bugs that are
+ * hard to fix for example), then
+ */
+
+/** @TODO: dmalloc() test */
+
+#include "config.h"
+#include <atf-c.h>
+#include "dhcpd.h"
+
+static const char* checkString (struct data_string* ds, const char *src);
+
+ATF_TC(buffer_allocate);
+
+ATF_TC_HEAD(buffer_allocate, tc) {
+ atf_tc_set_md_var(tc, "descr", "buffer_allocate basic test");
+}
+
+ATF_TC_BODY(buffer_allocate, tc) {
+ struct buffer *buf = 0;
+
+ /*
+ * Check a 0-length buffer.
+ */
+ buf = NULL;
+ if (!buffer_allocate(&buf, 0, MDL)) {
+ atf_tc_fail("failed on 0-len buffer");
+ }
+ if (!buffer_dereference(&buf, MDL)) {
+ atf_tc_fail("buffer_dereference() failed");
+ }
+ if (buf != NULL) {
+ atf_tc_fail("buffer_dereference() did not NULL-out buffer");
+ }
+
+ /*
+ * Check an actual buffer.
+ */
+ buf = NULL;
+ if (!buffer_allocate(&buf, 100, MDL)) {
+ atf_tc_fail("failed on allocate 100 bytes\n");
+ }
+ if (!buffer_dereference(&buf, MDL)) {
+ atf_tc_fail("buffer_dereference() failed");
+ }
+ if (buf != NULL) {
+ atf_tc_fail("buffer_dereference() did not NULL-out buffer");
+ }
+
+ /*
+ * Okay, we're happy.
+ */
+ atf_tc_pass();
+}
+
+ATF_TC(buffer_reference);
+
+ATF_TC_HEAD(buffer_reference, tc) {
+ atf_tc_set_md_var(tc, "descr", "buffer_reference basic test");
+}
+
+ATF_TC_BODY(buffer_reference, tc) {
+
+ struct buffer *a, *b;
+
+ /*
+ * Create a buffer.
+ */
+ a = NULL;
+ if (!buffer_allocate(&a, 100, MDL)) {
+ atf_tc_fail("failed on allocate 100 bytes");
+ }
+
+ /**
+ * Confirm buffer_reference() doesn't work if we pass in NULL.
+ *
+ * @TODO: we should confirm we get an error message here.
+ */
+ if (buffer_reference(NULL, a, MDL)) {
+ atf_tc_fail("succeeded on an error input");
+ }
+
+ /**
+ * @TODO: we should confirm we get an error message if we pass
+ * a non-NULL target.
+ */
+
+ /*
+ * Confirm we work under normal circumstances.
+ */
+ b = NULL;
+ if (!buffer_reference(&b, a, MDL)) {
+ atf_tc_fail("buffer_reference() failed");
+ }
+
+ if (b != a) {
+ atf_tc_fail("incorrect pointer returned");
+ }
+
+ if (b->refcnt != 2) {
+ atf_tc_fail("incorrect refcnt");
+ }
+
+ /*
+ * Clean up.
+ */
+ if (!buffer_dereference(&b, MDL)) {
+ atf_tc_fail("buffer_dereference() failed");
+ }
+ if (!buffer_dereference(&a, MDL)) {
+ atf_tc_fail("buffer_dereference() failed");
+ }
+
+}
+
+
+ATF_TC(buffer_dereference);
+
+ATF_TC_HEAD(buffer_dereference, tc) {
+ atf_tc_set_md_var(tc, "descr", "buffer_dereference basic test");
+}
+
+ATF_TC_BODY(buffer_dereference, tc) {
+ struct buffer *a, *b;
+
+ /**
+ * Confirm buffer_dereference() doesn't work if we pass in NULL.
+ *
+ * TODO: we should confirm we get an error message here.
+ */
+ if (buffer_dereference(NULL, MDL)) {
+ atf_tc_fail("succeeded on an error input");
+ }
+
+ /**
+ * Confirm buffer_dereference() doesn't work if we pass in
+ * a pointer to NULL.
+ *
+ * @TODO: we should confirm we get an error message here.
+ */
+ a = NULL;
+ if (buffer_dereference(&a, MDL)) {
+ atf_tc_fail("succeeded on an error input");
+ }
+
+ /*
+ * Confirm we work under normal circumstances.
+ */
+ a = NULL;
+ if (!buffer_allocate(&a, 100, MDL)) {
+ atf_tc_fail("failed on allocate");
+ }
+ if (!buffer_dereference(&a, MDL)) {
+ atf_tc_fail("buffer_dereference() failed");
+ }
+ if (a != NULL) {
+ atf_tc_fail("non-null buffer after buffer_dereference()");
+ }
+
+ /**
+ * Confirm we get an error from negative refcnt.
+ *
+ * @TODO: we should confirm we get an error message here.
+ */
+ a = NULL;
+ if (!buffer_allocate(&a, 100, MDL)) {
+ atf_tc_fail("failed on allocate");
+ }
+ b = NULL;
+ if (!buffer_reference(&b, a, MDL)) {
+ atf_tc_fail("buffer_reference() failed");
+ }
+ a->refcnt = 0; /* purposely set to invalid value */
+ if (buffer_dereference(&a, MDL)) {
+ atf_tc_fail("buffer_dereference() succeeded on error input");
+ }
+ a->refcnt = 2;
+ if (!buffer_dereference(&b, MDL)) {
+ atf_tc_fail("buffer_dereference() failed");
+ }
+ if (!buffer_dereference(&a, MDL)) {
+ atf_tc_fail("buffer_dereference() failed");
+ }
+}
+
+ATF_TC(data_string_forget);
+
+ATF_TC_HEAD(data_string_forget, tc) {
+ atf_tc_set_md_var(tc, "descr", "data_string_forget basic test");
+}
+
+ATF_TC_BODY(data_string_forget, tc) {
+ struct buffer *buf;
+ struct data_string a;
+ const char *str = "Lorem ipsum dolor sit amet turpis duis.";
+
+ /*
+ * Create the string we want to forget.
+ */
+ memset(&a, 0, sizeof(a));
+ a.len = strlen(str);
+ buf = NULL;
+ if (!buffer_allocate(&buf, a.len, MDL)) {
+ atf_tc_fail("out of memory");
+ }
+ if (!buffer_reference(&a.buffer, buf, MDL)) {
+ atf_tc_fail("buffer_reference() failed");
+ }
+ a.data = a.buffer->data;
+ memcpy(a.buffer->data, str, a.len);
+
+ /*
+ * Forget and confirm we've forgotten.
+ */
+ data_string_forget(&a, MDL);
+
+ if (a.len != 0) {
+ atf_tc_fail("incorrect length");
+ }
+
+ if (a.data != NULL) {
+ atf_tc_fail("incorrect data");
+ }
+ if (a.terminated) {
+ atf_tc_fail("incorrect terminated");
+ }
+ if (a.buffer != NULL) {
+ atf_tc_fail("incorrect buffer");
+ }
+ if (buf->refcnt != 1) {
+ atf_tc_fail("too many references to buf");
+ }
+
+ /*
+ * Clean up buffer.
+ */
+ if (!buffer_dereference(&buf, MDL)) {
+ atf_tc_fail("buffer_reference() failed");
+ }
+}
+
+ATF_TC(data_string_forget_nobuf);
+
+ATF_TC_HEAD(data_string_forget_nobuf, tc) {
+ atf_tc_set_md_var(tc, "descr", "data_string_forget test, "
+ "data_string without buffer");
+}
+
+ATF_TC_BODY(data_string_forget_nobuf, tc) {
+ struct data_string a;
+ const char *str = "Lorem ipsum dolor sit amet massa nunc.";
+
+ /*
+ * Create the string we want to forget.
+ */
+ memset(&a, 0, sizeof(a));
+ a.len = strlen(str);
+ a.data = (const unsigned char *)str;
+ a.terminated = 1;
+
+ /*
+ * Forget and confirm we've forgotten.
+ */
+ data_string_forget(&a, MDL);
+
+ if (a.len != 0) {
+ atf_tc_fail("incorrect length");
+ }
+ if (a.data != NULL) {
+ atf_tc_fail("incorrect data");
+ }
+ if (a.terminated) {
+ atf_tc_fail("incorrect terminated");
+ }
+ if (a.buffer != NULL) {
+ atf_tc_fail("incorrect buffer");
+ }
+}
+
+ATF_TC(data_string_copy);
+
+ATF_TC_HEAD(data_string_copy, tc) {
+ atf_tc_set_md_var(tc, "descr", "data_string_copy basic test");
+}
+
+ATF_TC_BODY(data_string_copy, tc) {
+ struct data_string a, b;
+ const char *str = "Lorem ipsum dolor sit amet orci aliquam.";
+
+ /*
+ * Create the string we want to copy.
+ */
+ memset(&a, 0, sizeof(a));
+ a.len = strlen(str);
+ if (!buffer_allocate(&a.buffer, a.len, MDL)) {
+ atf_tc_fail("out of memory");
+ }
+ a.data = a.buffer->data;
+ memcpy(a.buffer->data, str, a.len);
+
+ /*
+ * Copy the string, and confirm it works.
+ */
+ memset(&b, 0, sizeof(b));
+ data_string_copy(&b, &a, MDL);
+
+ if (b.len != a.len) {
+ atf_tc_fail("incorrect length");
+ }
+ if (b.data != a.data) {
+ atf_tc_fail("incorrect data");
+ }
+ if (b.terminated != a.terminated) {
+ atf_tc_fail("incorrect terminated");
+ }
+ if (b.buffer != a.buffer) {
+ atf_tc_fail("incorrect buffer");
+ }
+
+ /*
+ * Clean up.
+ */
+ data_string_forget(&b, MDL);
+ data_string_forget(&a, MDL);
+}
+
+ATF_TC(data_string_copy_nobuf);
+
+ATF_TC_HEAD(data_string_copy_nobuf, tc) {
+ atf_tc_set_md_var(tc, "descr", "data_string_copy test, "
+ "data_string without buffer");
+}
+
+ATF_TC_BODY(data_string_copy_nobuf, tc) {
+ struct data_string a, b;
+ const char *str = "Lorem ipsum dolor sit amet cras amet.";
+
+ /*
+ * Create the string we want to copy.
+ */
+ memset(&a, 0, sizeof(a));
+ a.len = strlen(str);
+ a.data = (const unsigned char *)str;
+ a.terminated = 1;
+
+ /*
+ * Copy the string, and confirm it works.
+ */
+ memset(&b, 0, sizeof(b));
+ data_string_copy(&b, &a, MDL);
+
+ if (b.len != a.len) {
+ atf_tc_fail("incorrect length");
+ }
+ if (b.data != a.data) {
+ atf_tc_fail("incorrect data");
+ }
+ if (b.terminated != a.terminated) {
+ atf_tc_fail("incorrect terminated");
+ }
+ if (b.buffer != a.buffer) {
+ atf_tc_fail("incorrect buffer");
+ }
+
+ /*
+ * Clean up.
+ */
+ data_string_forget(&b, MDL);
+ data_string_forget(&a, MDL);
+
+}
+
+
+ATF_TC(data_string_new);
+
+ATF_TC_HEAD(data_string_new, tc) {
+ atf_tc_set_md_var(tc, "descr", "data_string_new test, "
+ "exercises data_string_new function");
+}
+
+ATF_TC_BODY(data_string_new, tc) {
+ struct data_string new_string;
+ const char *src = "Really? Latin? ... geeks";
+ int len_arg = 0;
+ const char *error;
+
+ /* Case 1: Call with an invalid data_string pointer, should fail */
+ if (data_string_new(NULL, src, len_arg, MDL)) {
+ atf_tc_fail("case 1: call should have failed");
+ }
+
+ /* Case 2: Passing in NULL src should fail */
+ if (data_string_new(&new_string, NULL, 10, MDL)) {
+ atf_tc_fail("case 2: did not return success");
+ }
+
+ /* Case 3: Call with valid params, length includes NULL */
+ len_arg = strlen(src) + 1;
+ if (data_string_new(&new_string, src, len_arg, MDL) == 0) {
+ atf_tc_fail("case 3: did not return success");
+ }
+
+ error = checkString(&new_string, src);
+ ATF_REQUIRE_MSG((error == NULL), "case 3: %s", error);
+ data_string_forget(&new_string, MDL);
+
+
+ /* Case 4: Call with valid params, length does not include NULL */
+ len_arg = 7;
+ if (data_string_new(&new_string, src, len_arg, MDL) == 0) {
+ atf_tc_fail("case 4: did not return success");
+ }
+
+ error = checkString(&new_string, "Really?");
+ ATF_REQUIRE_MSG((error == NULL), "case 4: %s", error);
+ data_string_forget(&new_string, MDL);
+
+
+ /* Case 5: Call with valid params, source string is "" */
+ len_arg = 0;
+ if (data_string_new(&new_string, "", len_arg, MDL) == 0) {
+ atf_tc_fail("case 5: did not return success");
+ }
+
+ error = checkString(&new_string, "");
+ ATF_REQUIRE_MSG((error == NULL), "case 4: %s", error);
+ data_string_forget(&new_string, MDL);
+
+
+}
+
+/* Helper function which tests validity of a data_string
+*
+* Verifies that the given data_string contains a null-terminated string
+* equal to a given string.
+*
+* \param string data_string to test
+* \param src text content string should contain
+* \return returns NULL if data_string is validate or an error message
+* describing why it is invalid
+*/
+const char* checkString (struct data_string* string,
+ const char* src) {
+ int src_len = strlen(src);
+
+ if (string->buffer == NULL) {
+ return ("buffer is NULL");
+ }
+
+ if (string->data != string->buffer->data) {
+ return ("data not set to buffer->data");
+ }
+
+ if (string->len != src_len) {
+ return ("len is wrong ");
+ }
+
+ if (string->terminated != 1) {
+ return ("terminated flag not set");
+ }
+
+ if (memcmp(string->data, src, src_len + 1)) {
+ return ("data content wrong");
+ }
+
+ return (NULL);
+}
+
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, buffer_allocate);
+ ATF_TP_ADD_TC(tp, buffer_reference);
+ ATF_TP_ADD_TC(tp, buffer_dereference);
+ ATF_TP_ADD_TC(tp, data_string_forget);
+ ATF_TP_ADD_TC(tp, data_string_forget_nobuf);
+ ATF_TP_ADD_TC(tp, data_string_copy);
+ ATF_TP_ADD_TC(tp, data_string_copy_nobuf);
+ ATF_TP_ADD_TC(tp, data_string_new);
+
+ return (atf_no_error());
+}
diff --git a/common/tr.c b/common/tr.c
new file mode 100644
index 0000000..6e05faf
--- /dev/null
+++ b/common/tr.c
@@ -0,0 +1,331 @@
+/* tr.c
+
+ token ring interface support
+ Contributed in May of 1999 by Andrew Chittenden */
+
+/*
+ * Copyright (c) 2011 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ */
+
+#include "dhcpd.h"
+
+#if defined (HAVE_TR_SUPPORT) && \
+ (defined (PACKET_ASSEMBLY) || defined (PACKET_DECODING))
+#include "includes/netinet/ip.h"
+#include "includes/netinet/udp.h"
+#include "includes/netinet/if_ether.h"
+#include "netinet/if_tr.h"
+#include <sys/time.h>
+
+/*
+ * token ring device handling subroutines. These are required as token-ring
+ * does not have a simple on-the-wire header but requires the use of
+ * source routing
+ */
+
+static int insert_source_routing (struct trh_hdr *trh, struct interface_info* interface);
+static void save_source_routing (struct trh_hdr *trh, struct interface_info* interface);
+static void expire_routes (void);
+
+/*
+ * As we keep a list of interesting routing information only, a singly
+ * linked list is all we need
+ */
+struct routing_entry {
+ struct routing_entry *next;
+ unsigned char addr[TR_ALEN];
+ unsigned char iface[5];
+ u_int16_t rcf; /* route control field */
+ u_int16_t rseg[8]; /* routing registers */
+ unsigned long access_time; /* time we last used this entry */
+};
+
+static struct routing_entry *routing_info = NULL;
+
+static int routing_timeout = 10;
+static struct timeval routing_timer;
+
+void assemble_tr_header (interface, buf, bufix, to)
+ struct interface_info *interface;
+ unsigned char *buf;
+ unsigned *bufix;
+ struct hardware *to;
+{
+ struct trh_hdr *trh;
+ int hdr_len;
+ struct trllc *llc;
+
+
+ /* set up the token header */
+ trh = (struct trh_hdr *) &buf[*bufix];
+ if (interface -> hw_address.hlen - 1 == sizeof (trh->saddr))
+ memcpy (trh->saddr, &interface -> hw_address.hbuf [1],
+ sizeof (trh->saddr));
+ else
+ memset (trh->saddr, 0x00, sizeof (trh->saddr));
+
+ if (to && to -> hlen == 7) /* XXX */
+ memcpy (trh->daddr, &to -> hbuf [1], sizeof trh->daddr);
+ else
+ memset (trh->daddr, 0xff, sizeof (trh->daddr));
+
+ hdr_len = insert_source_routing (trh, interface);
+
+ trh->ac = AC;
+ trh->fc = LLC_FRAME;
+
+ /* set up the llc header for snap encoding after the tr header */
+ llc = (struct trllc *)(buf + *bufix + hdr_len);
+ llc->dsap = EXTENDED_SAP;
+ llc->ssap = EXTENDED_SAP;
+ llc->llc = UI_CMD;
+ llc->protid[0] = 0;
+ llc->protid[1] = 0;
+ llc->protid[2] = 0;
+ llc->ethertype = htons(ETHERTYPE_IP);
+
+ hdr_len += sizeof(struct trllc);
+
+ *bufix += hdr_len;
+}
+
+
+static unsigned char tr_broadcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+/*
+ * decoding the token header is a bit complex as you can see here. It is
+ * further complicated by the linux kernel stripping off some valuable
+ * information (see comment below) even though we've asked for the raw
+ * packets.
+ */
+ssize_t decode_tr_header (interface, buf, bufix, from)
+ struct interface_info *interface;
+ unsigned char *buf;
+ unsigned bufix;
+ struct hardware *from;
+{
+ struct trh_hdr *trh = (struct trh_hdr *) buf + bufix;
+ struct trllc *llc;
+ struct ip *ip;
+ struct udphdr *udp;
+ unsigned int route_len = 0;
+ ssize_t hdr_len;
+ struct timeval now;
+
+ /* see whether any source routing information has expired */
+ gettimeofday(&now, NULL);
+
+ if (routing_timer.tv_sec == 0)
+ routing_timer.tv_sec = now.tv_sec + routing_timeout;
+ else if ((now.tv_sec - routing_timer.tv_sec) > 0)
+ expire_routes();
+
+ /* the kernel might have stripped off the source
+ * routing bit. We try a heuristic to determine whether
+ * this is the case and put it back on if so
+ */
+ route_len = (ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8;
+ llc = (struct trllc *)(buf + bufix + sizeof(struct trh_hdr)-TR_MAXRIFLEN+route_len);
+ if (llc->dsap == EXTENDED_SAP
+ && llc->ssap == EXTENDED_SAP
+ && llc->llc == UI_CMD
+ && llc->protid[0] == 0
+ && llc->protid[1] == 0
+ && llc->protid[2] == 0) {
+ /* say there is source routing information present */
+ trh->saddr[0] |= TR_RII;
+ }
+
+ if (trh->saddr[0] & TR_RII)
+ route_len = (ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8;
+ else
+ route_len = 0;
+
+ hdr_len = sizeof (struct trh_hdr) - TR_MAXRIFLEN + route_len;
+
+ /* now filter out unwanted packets: this is based on the packet
+ * filter code in bpf.c */
+ llc = (struct trllc *)(buf + bufix + hdr_len);
+ ip = (struct ip *) (llc + 1);
+ udp = (struct udphdr *) ((unsigned char*) ip + IP_HL (ip));
+
+ /* make sure it is a snap encoded, IP, UDP, unfragmented packet sent
+ * to our port */
+ if (llc->dsap != EXTENDED_SAP
+ || ntohs(llc->ethertype) != ETHERTYPE_IP
+ || ip->ip_p != IPPROTO_UDP
+ || (ntohs (ip->ip_off) & IP_OFFMASK) != 0
+ || udp->uh_dport != local_port)
+ return -1;
+
+ /* only save source routing information for packets from valued hosts */
+ save_source_routing(trh, interface);
+
+ return hdr_len + sizeof (struct trllc);
+}
+
+/* insert_source_routing inserts source route information into the token ring
+ * header
+ */
+static int insert_source_routing (trh, interface)
+ struct trh_hdr *trh;
+ struct interface_info* interface;
+{
+ struct routing_entry *rover;
+ struct timeval now;
+ unsigned int route_len = 0;
+
+ gettimeofday(&now, NULL);
+
+ /* single route broadcasts as per rfc 1042 */
+ if (memcmp(trh->daddr, tr_broadcast,TR_ALEN) == 0) {
+ trh->saddr[0] |= TR_RII;
+ trh->rcf = ((sizeof(trh->rcf)) << 8) & TR_RCF_LEN_MASK;
+ trh->rcf |= (TR_RCF_FRAME2K | TR_RCF_LIMITED_BROADCAST);
+ trh->rcf = htons(trh->rcf);
+ } else {
+ /* look for a routing entry */
+ for (rover = routing_info; rover != NULL; rover = rover->next) {
+ if (memcmp(rover->addr, trh->daddr, TR_ALEN) == 0)
+ break;
+ }
+
+ if (rover != NULL) {
+ /* success: route that frame */
+ if ((rover->rcf & TR_RCF_LEN_MASK) >> 8) {
+ u_int16_t rcf = rover->rcf;
+ memcpy(trh->rseg,rover->rseg,sizeof(trh->rseg));
+ rcf ^= TR_RCF_DIR_BIT;
+ rcf &= ~TR_RCF_BROADCAST_MASK;
+ trh->rcf = htons(rcf);
+ trh->saddr[0] |= TR_RII;
+ }
+ rover->access_time = now.tv_sec;
+ } else {
+ /* we don't have any routing information so send a
+ * limited broadcast */
+ trh->saddr[0] |= TR_RII;
+ trh->rcf = ((sizeof(trh->rcf)) << 8) & TR_RCF_LEN_MASK;
+ trh->rcf |= (TR_RCF_FRAME2K | TR_RCF_LIMITED_BROADCAST);
+ trh->rcf = htons(trh->rcf);
+ }
+ }
+
+ /* return how much of the header we've actually used */
+ if (trh->saddr[0] & TR_RII)
+ route_len = (ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8;
+ else
+ route_len = 0;
+
+ return sizeof (struct trh_hdr) - TR_MAXRIFLEN + route_len;
+}
+
+/*
+ * save any source routing information
+ */
+static void save_source_routing(trh, interface)
+ struct trh_hdr *trh;
+ struct interface_info *interface;
+{
+ struct routing_entry *rover;
+ struct timeval now;
+ unsigned char saddr[TR_ALEN];
+ u_int16_t rcf = 0;
+
+ gettimeofday(&now, NULL);
+
+ memcpy(saddr, trh->saddr, sizeof(saddr));
+ saddr[0] &= 0x7f; /* strip off source routing present flag */
+
+ /* scan our table to see if we've got it */
+ for (rover = routing_info; rover != NULL; rover = rover->next) {
+ if (memcmp(&rover->addr[0], &saddr[0], TR_ALEN) == 0)
+ break;
+ }
+
+ /* found an entry so update it with fresh information */
+ if (rover != NULL) {
+ if ((trh->saddr[0] & TR_RII) &&
+ ((ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8) > 2) {
+ rcf = ntohs(trh->rcf);
+ rcf &= ~TR_RCF_BROADCAST_MASK;
+ memcpy(rover->rseg, trh->rseg, sizeof(rover->rseg));
+ }
+ rover->rcf = rcf;
+ rover->access_time = now.tv_sec;
+ return; /* that's all folks */
+ }
+
+ /* no entry found, so create one */
+ rover = dmalloc (sizeof (struct routing_entry), MDL);
+ if (rover == NULL) {
+ fprintf(stderr,
+ "%s: unable to save source routing information\n",
+ __FILE__);
+ return;
+ }
+
+ memcpy(rover->addr, saddr, sizeof(rover->addr));
+ memcpy(rover->iface, interface->name, 5);
+ rover->access_time = now.tv_sec;
+ if (trh->saddr[0] & TR_RII) {
+ if (((ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8) > 2) {
+ rcf = ntohs(trh->rcf);
+ rcf &= ~TR_RCF_BROADCAST_MASK;
+ memcpy(rover->rseg, trh->rseg, sizeof(rover->rseg));
+ }
+ rover->rcf = rcf;
+ }
+
+ /* insert into list */
+ rover->next = routing_info;
+ routing_info = rover;
+
+ return;
+}
+
+/*
+ * get rid of old routes
+ */
+static void expire_routes()
+{
+ struct routing_entry *rover;
+ struct routing_entry **prover = &routing_info;
+ struct timeval now;
+
+ gettimeofday(&now, NULL);
+
+ while((rover = *prover) != NULL) {
+ if ((now.tv_sec - rover->access_time) > routing_timeout) {
+ *prover = rover->next;
+ dfree (rover, MDL);
+ } else
+ prover = &rover->next;
+ }
+
+ /* Reset the timer */
+ routing_timer.tv_sec = now.tv_sec + routing_timeout;
+ routing_timer.tv_usec = now.tv_usec;
+}
+
+#endif
diff --git a/common/tree.c b/common/tree.c
new file mode 100644
index 0000000..23e86fa
--- /dev/null
+++ b/common/tree.c
@@ -0,0 +1,4643 @@
+/* tree.c
+
+ Routines for manipulating parse trees... */
+
+/*
+ * Copyright (c) 2011-2013,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+#include <omapip/omapip_p.h>
+#include <ctype.h>
+#include <sys/wait.h>
+
+#ifdef HAVE_REGEX_H
+# include <regex.h>
+#endif
+
+struct binding_scope *global_scope;
+
+static int do_host_lookup (struct data_string *, struct dns_host_entry *);
+
+#define DS_SPRINTF_SIZE 128
+
+/*
+ * If we are using a data_string structure to hold a NUL-terminated
+ * ASCII string, this function can be used to append a printf-formatted
+ * string to the end of it. The data_string structure will be resized to
+ * be big enough to hold the new string.
+ *
+ * If the append works, then 1 is returned.
+ *
+ * If it is not possible to allocate a buffer big enough to hold the
+ * new value, then the old data_string is unchanged, and 0 is returned.
+ */
+int
+data_string_sprintfa(struct data_string *ds, const char *fmt, ...) {
+ va_list args;
+ int cur_strlen;
+ int max;
+ int vsnprintf_ret;
+ int new_len;
+ struct buffer *tmp_buffer;
+
+ /*
+ * If the data_string is empty, then initialize it.
+ */
+ if (ds->data == NULL) {
+ /* INSIST(ds.buffer == NULL); */
+ if (!buffer_allocate(&ds->buffer, DS_SPRINTF_SIZE, MDL)) {
+ return 0;
+ }
+ ds->data = ds->buffer->data;
+ ds->len = DS_SPRINTF_SIZE;
+ *((char *)ds->data) = '\0';
+ }
+
+ /*
+ * Get the length of the string, and figure out how much space
+ * is left.
+ */
+ cur_strlen = strlen((char *)ds->data);
+ max = ds->len - cur_strlen;
+
+ /*
+ * Use vsnprintf(), which won't write past our space, but will
+ * tell us how much space it wants.
+ */
+ va_start(args, fmt);
+ vsnprintf_ret = vsnprintf((char *)ds->data+cur_strlen, max, fmt, args);
+ va_end(args);
+ /* INSIST(vsnprintf_ret >= 0); */
+
+ /*
+ * If our buffer is not big enough, we need a new buffer.
+ */
+ if (vsnprintf_ret >= max) {
+ /*
+ * Figure out a size big enough.
+ */
+ new_len = ds->len * 2;
+ while (new_len <= cur_strlen + vsnprintf_ret) {
+ new_len *= 2;
+ }
+
+ /*
+ * Create a new buffer and fill it.
+ */
+ tmp_buffer = NULL;
+ if (!buffer_allocate(&tmp_buffer, new_len, MDL)) {
+ /*
+ * If we can't create a big enough buffer,
+ * we should remove any truncated output that we had.
+ */
+ *((char *)ds->data+cur_strlen) = '\0';
+ va_end(args);
+ return 0;
+ }
+ memcpy(tmp_buffer->data, ds->data, cur_strlen);
+
+ /* Rerun the vsprintf. */
+ va_start(args, fmt);
+ vsprintf((char *)tmp_buffer->data + cur_strlen, fmt, args);
+ va_end(args);
+
+ /*
+ * Replace our old buffer with the new buffer.
+ */
+ buffer_dereference(&ds->buffer, MDL);
+ buffer_reference(&ds->buffer, tmp_buffer, MDL);
+ buffer_dereference(&tmp_buffer, MDL);
+ ds->data = ds->buffer->data;
+ ds->len = new_len;
+ }
+ return 1;
+}
+
+pair cons (car, cdr)
+ caddr_t car;
+ pair cdr;
+{
+ pair foo = (pair)dmalloc (sizeof *foo, MDL);
+ if (!foo)
+ log_fatal ("no memory for cons.");
+ foo -> car = car;
+ foo -> cdr = cdr;
+ return foo;
+}
+
+int make_const_option_cache (oc, buffer, data, len, option, file, line)
+ struct option_cache **oc;
+ struct buffer **buffer;
+ u_int8_t *data;
+ unsigned len;
+ struct option *option;
+ const char *file;
+ int line;
+{
+ struct buffer *bp;
+
+ if (buffer) {
+ bp = *buffer;
+ *buffer = 0;
+ } else {
+ bp = (struct buffer *)0;
+ if (!buffer_allocate (&bp, len, file, line)) {
+ log_error ("%s(%d): can't allocate buffer.",
+ file, line);
+ return 0;
+ }
+ }
+
+ if (!option_cache_allocate (oc, file, line)) {
+ log_error ("%s(%d): can't allocate option cache.", file, line);
+ buffer_dereference (&bp, file, line);
+ return 0;
+ }
+
+ (*oc) -> data.len = len;
+ (*oc) -> data.buffer = bp;
+ (*oc) -> data.data = &bp -> data [0];
+ (*oc) -> data.terminated = 0;
+ if (data)
+ memcpy (&bp -> data [0], data, len);
+ option_reference(&((*oc)->option), option, MDL);
+ return 1;
+}
+
+int make_host_lookup (expr, name)
+ struct expression **expr;
+ const char *name;
+{
+ if (!expression_allocate (expr, MDL)) {
+ log_error ("No memory for host lookup tree node.");
+ return 0;
+ }
+ (*expr) -> op = expr_host_lookup;
+ if (!enter_dns_host (&((*expr) -> data.host_lookup), name)) {
+ expression_dereference (expr, MDL);
+ return 0;
+ }
+ return 1;
+}
+
+int enter_dns_host (dh, name)
+ struct dns_host_entry **dh;
+ const char *name;
+{
+ /* XXX This should really keep a hash table of hostnames
+ XXX and just add a new reference to a hostname that
+ XXX already exists, if possible, rather than creating
+ XXX a new structure. */
+ if (!dns_host_entry_allocate (dh, name, MDL)) {
+ log_error ("Can't allocate space for new host.");
+ return 0;
+ }
+ return 1;
+}
+
+int make_const_data (struct expression **expr, const unsigned char *data,
+ unsigned len, int terminated, int allocate,
+ const char *file, int line)
+{
+ struct expression *nt;
+
+ if (!expression_allocate (expr, file, line)) {
+ log_error ("No memory for make_const_data tree node.");
+ return 0;
+ }
+ nt = *expr;
+
+ if (len) {
+ if (allocate) {
+ if (!buffer_allocate (&nt -> data.const_data.buffer,
+ len + terminated, file, line)) {
+ log_error ("Can't allocate const_data buffer");
+ expression_dereference (expr, file, line);
+ return 0;
+ }
+ nt -> data.const_data.data =
+ &nt -> data.const_data.buffer -> data [0];
+ memcpy (nt -> data.const_data.buffer -> data,
+ data, len + terminated);
+ } else
+ nt -> data.const_data.data = data;
+ nt -> data.const_data.terminated = terminated;
+ } else
+ nt -> data.const_data.data = 0;
+
+ nt -> op = expr_const_data;
+ nt -> data.const_data.len = len;
+ return 1;
+}
+
+int make_const_int (expr, val)
+ struct expression **expr;
+ unsigned long val;
+{
+ if (!expression_allocate (expr, MDL)) {
+ log_error ("No memory for make_const_int tree node.");
+ return 0;
+ }
+
+ (*expr) -> op = expr_const_int;
+ (*expr) -> data.const_int = val;
+ return 1;
+}
+
+int make_concat (expr, left, right)
+ struct expression **expr;
+ struct expression *left, *right;
+{
+ /* If we're concatenating a null tree to a non-null tree, just
+ return the non-null tree; if both trees are null, return
+ a null tree. */
+ if (!left) {
+ if (!right)
+ return 0;
+ expression_reference (expr, right, MDL);
+ return 1;
+ }
+ if (!right) {
+ expression_reference (expr, left, MDL);
+ return 1;
+ }
+
+ /* Otherwise, allocate a new node to concatenate the two. */
+ if (!expression_allocate (expr, MDL)) {
+ log_error ("No memory for concatenation expression node.");
+ return 0;
+ }
+
+ (*expr) -> op = expr_concat;
+ expression_reference (&(*expr) -> data.concat [0], left, MDL);
+ expression_reference (&(*expr) -> data.concat [1], right, MDL);
+ return 1;
+}
+
+int make_encapsulation (expr, name)
+ struct expression **expr;
+ struct data_string *name;
+{
+ /* Allocate a new node to store the encapsulation. */
+ if (!expression_allocate (expr, MDL)) {
+ log_error ("No memory for encapsulation expression node.");
+ return 0;
+ }
+
+ (*expr) -> op = expr_encapsulate;
+ data_string_copy (&(*expr) -> data.encapsulate, name, MDL);
+ return 1;
+}
+
+int make_substring (new, expr, offset, length)
+ struct expression **new;
+ struct expression *expr;
+ struct expression *offset;
+ struct expression *length;
+{
+ /* Allocate an expression node to compute the substring. */
+ if (!expression_allocate (new, MDL)) {
+ log_error ("no memory for substring expression.");
+ return 0;
+ }
+ (*new) -> op = expr_substring;
+ expression_reference (&(*new) -> data.substring.expr, expr, MDL);
+ expression_reference (&(*new) -> data.substring.offset, offset, MDL);
+ expression_reference (&(*new) -> data.substring.len, length, MDL);
+ return 1;
+}
+
+int make_limit (new, expr, limit)
+ struct expression **new;
+ struct expression *expr;
+ int limit;
+{
+ /* Allocate a node to enforce a limit on evaluation. */
+ if (!expression_allocate (new, MDL))
+ log_error ("no memory for limit expression");
+ (*new) -> op = expr_substring;
+ expression_reference (&(*new) -> data.substring.expr, expr, MDL);
+
+ /* Offset is a constant 0. */
+ if (!expression_allocate (&(*new) -> data.substring.offset, MDL)) {
+ log_error ("no memory for limit offset expression");
+ expression_dereference (new, MDL);
+ return 0;
+ }
+ (*new) -> data.substring.offset -> op = expr_const_int;
+ (*new) -> data.substring.offset -> data.const_int = 0;
+
+ /* Length is a constant: the specified limit. */
+ if (!expression_allocate (&(*new) -> data.substring.len, MDL)) {
+ log_error ("no memory for limit length expression");
+ expression_dereference (new, MDL);
+ return 0;
+ }
+ (*new) -> data.substring.len -> op = expr_const_int;
+ (*new) -> data.substring.len -> data.const_int = limit;
+
+ return 1;
+}
+
+int option_cache (struct option_cache **oc, struct data_string *dp,
+ struct expression *expr, struct option *option,
+ const char *file, int line)
+{
+ if (!option_cache_allocate (oc, file, line))
+ return 0;
+ if (dp)
+ data_string_copy (&(*oc) -> data, dp, file, line);
+ if (expr)
+ expression_reference (&(*oc) -> expression, expr, file, line);
+ option_reference(&(*oc)->option, option, MDL);
+ return 1;
+}
+
+int make_let (result, name)
+ struct executable_statement **result;
+ const char *name;
+{
+ if (!(executable_statement_allocate (result, MDL)))
+ return 0;
+
+ (*result) -> op = let_statement;
+ (*result) -> data.let.name = dmalloc (strlen (name) + 1, MDL);
+ if (!(*result) -> data.let.name) {
+ executable_statement_dereference (result, MDL);
+ return 0;
+ }
+ strcpy ((*result) -> data.let.name, name);
+ return 1;
+}
+
+static int do_host_lookup (result, dns)
+ struct data_string *result;
+ struct dns_host_entry *dns;
+{
+ struct hostent *h;
+ unsigned i, count;
+ unsigned new_len;
+
+#ifdef DEBUG_EVAL
+ log_debug ("time: now = %d dns = %d diff = %d",
+ cur_time, dns -> timeout, cur_time - dns -> timeout);
+#endif
+
+ /* If the record hasn't timed out, just copy the data and return. */
+ if (cur_time <= dns -> timeout) {
+#ifdef DEBUG_EVAL
+ log_debug ("easy copy: %d %s",
+ dns -> data.len,
+ (dns -> data.len > 4
+ ? inet_ntoa (*(struct in_addr *)(dns -> data.data))
+ : 0));
+#endif
+ data_string_copy (result, &dns -> data, MDL);
+ return 1;
+ }
+#ifdef DEBUG_EVAL
+ log_debug ("Looking up %s", dns -> hostname);
+#endif
+
+ /* Otherwise, look it up... */
+ h = gethostbyname (dns -> hostname);
+ if (!h) {
+#ifndef NO_H_ERRNO
+ switch (h_errno) {
+ case HOST_NOT_FOUND:
+#endif
+ log_error ("%s: host unknown.", dns -> hostname);
+#ifndef NO_H_ERRNO
+ break;
+ case TRY_AGAIN:
+ log_error ("%s: temporary name server failure",
+ dns -> hostname);
+ break;
+ case NO_RECOVERY:
+ log_error ("%s: name server failed", dns -> hostname);
+ break;
+ case NO_DATA:
+ log_error ("%s: no A record associated with address",
+ dns -> hostname);
+ }
+#endif /* !NO_H_ERRNO */
+
+ /* Okay to try again after a minute. */
+ dns -> timeout = cur_time + 60;
+ data_string_forget (&dns -> data, MDL);
+ return 0;
+ }
+
+#ifdef DEBUG_EVAL
+ log_debug ("Lookup succeeded; first address is %s",
+ inet_ntoa (h -> h_addr_list [0]));
+#endif
+
+ /* Count the number of addresses we got... */
+ for (count = 0; h -> h_addr_list [count]; count++)
+ ;
+
+ /* Dereference the old data, if any. */
+ data_string_forget (&dns -> data, MDL);
+
+ /* Do we need to allocate more memory? */
+ new_len = count * h -> h_length;
+ if (!buffer_allocate (&dns -> data.buffer, new_len, MDL))
+ {
+ log_error ("No memory for %s.", dns -> hostname);
+ return 0;
+ }
+
+ dns -> data.data = &dns -> data.buffer -> data [0];
+ dns -> data.len = new_len;
+ dns -> data.terminated = 0;
+
+ /* Addresses are conveniently stored one to the buffer, so we
+ have to copy them out one at a time... :'( */
+ for (i = 0; i < count; i++) {
+ memcpy (&dns -> data.buffer -> data [h -> h_length * i],
+ h -> h_addr_list [i], (unsigned)(h -> h_length));
+ }
+#ifdef DEBUG_EVAL
+ log_debug ("dns -> data: %x h -> h_addr_list [0]: %x",
+ *(int *)(dns -> buffer), h -> h_addr_list [0]);
+#endif
+
+ /* XXX Set the timeout for an hour from now.
+ XXX This should really use the time on the DNS reply. */
+ dns -> timeout = cur_time + 3600;
+
+#ifdef DEBUG_EVAL
+ log_debug ("hard copy: %d %s", dns -> data.len,
+ (dns -> data.len > 4
+ ? inet_ntoa (*(struct in_addr *)(dns -> data.data)) : 0));
+#endif
+ data_string_copy (result, &dns -> data, MDL);
+ return 1;
+}
+
+int evaluate_expression (result, packet, lease, client_state,
+ in_options, cfg_options, scope, expr, file, line)
+ struct binding_value **result;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *cfg_options;
+ struct binding_scope **scope;
+ struct expression *expr;
+ const char *file;
+ int line;
+{
+ struct binding_value *bv;
+ int status;
+ struct binding *binding;
+
+ bv = (struct binding_value *)0;
+
+ if (expr -> op == expr_variable_reference) {
+ if (!scope || !*scope)
+ return 0;
+
+ binding = find_binding (*scope, expr -> data.variable);
+
+ if (binding && binding -> value) {
+ if (result)
+ binding_value_reference (result,
+ binding -> value,
+ file, line);
+ return 1;
+ } else
+ return 0;
+ } else if (expr -> op == expr_funcall) {
+ struct string_list *s;
+ struct expression *arg;
+ struct binding_scope *ns;
+ struct binding *nb;
+
+ if (!scope || !*scope) {
+ log_error ("%s: no such function.",
+ expr -> data.funcall.name);
+ return 0;
+ }
+
+ binding = find_binding (*scope, expr -> data.funcall.name);
+
+ if (!binding || !binding -> value) {
+ log_error ("%s: no such function.",
+ expr -> data.funcall.name);
+ return 0;
+ }
+ if (binding -> value -> type != binding_function) {
+ log_error ("%s: not a function.",
+ expr -> data.funcall.name);
+ return 0;
+ }
+
+ /* Create a new binding scope in which to define
+ the arguments to the function. */
+ ns = (struct binding_scope *)0;
+ if (!binding_scope_allocate (&ns, MDL)) {
+ log_error ("%s: can't allocate argument scope.",
+ expr -> data.funcall.name);
+ return 0;
+ }
+
+ arg = expr -> data.funcall.arglist;
+ s = binding -> value -> value.fundef -> args;
+ while (arg && s) {
+ nb = dmalloc (sizeof *nb, MDL);
+ if (!nb) {
+ blb:
+ binding_scope_dereference (&ns, MDL);
+ return 0;
+ } else {
+ memset (nb, 0, sizeof *nb);
+ nb -> name = dmalloc (strlen (s -> string) + 1,
+ MDL);
+ if (nb -> name)
+ strcpy (nb -> name, s -> string);
+ else {
+ dfree (nb, MDL);
+ goto blb;
+ }
+ }
+ evaluate_expression (&nb -> value, packet, lease,
+ client_state,
+ in_options, cfg_options, scope,
+ arg -> data.arg.val, file, line);
+ nb -> next = ns -> bindings;
+ ns -> bindings = nb;
+ arg = arg -> data.arg.next;
+ s = s -> next;
+ }
+ if (arg) {
+ log_error ("%s: too many arguments.",
+ expr -> data.funcall.name);
+ binding_scope_dereference (&ns, MDL);
+ return 0;
+ }
+ if (s) {
+ log_error ("%s: too few arguments.",
+ expr -> data.funcall.name);
+ binding_scope_dereference (&ns, MDL);
+ return 0;
+ }
+
+ if (scope && *scope)
+ binding_scope_reference (&ns -> outer, *scope, MDL);
+
+ status = (execute_statements
+ (&bv, packet,
+ lease, client_state, in_options, cfg_options, &ns,
+ binding -> value -> value.fundef -> statements));
+ binding_scope_dereference (&ns, MDL);
+
+ if (!bv)
+ return 1;
+ } else if (is_boolean_expression (expr)) {
+ if (!binding_value_allocate (&bv, MDL))
+ return 0;
+ bv -> type = binding_boolean;
+ status = (evaluate_boolean_expression
+ (&bv -> value.boolean, packet, lease, client_state,
+ in_options, cfg_options, scope, expr));
+ } else if (is_numeric_expression (expr)) {
+ if (!binding_value_allocate (&bv, MDL))
+ return 0;
+ bv -> type = binding_numeric;
+ status = (evaluate_numeric_expression
+ (&bv -> value.intval, packet, lease, client_state,
+ in_options, cfg_options, scope, expr));
+ } else if (is_data_expression (expr)) {
+ if (!binding_value_allocate (&bv, MDL))
+ return 0;
+ bv -> type = binding_data;
+ status = (evaluate_data_expression
+ (&bv -> value.data, packet, lease, client_state,
+ in_options, cfg_options, scope, expr, MDL));
+#if defined (NSUPDATE_OLD)
+ } else if (is_dns_expression (expr)) {
+ if (!binding_value_allocate (&bv, MDL))
+ return 0;
+ bv -> type = binding_dns;
+ status = (evaluate_dns_expression
+ (&bv -> value.dns, packet, lease, client_state,
+ in_options, cfg_options, scope, expr));
+#endif
+ } else {
+ log_error ("%s: invalid expression type: %d",
+ "evaluate_expression", expr -> op);
+ return 0;
+ }
+ if (result && status)
+ binding_value_reference (result, bv, file, line);
+ binding_value_dereference (&bv, MDL);
+
+ return status;
+}
+
+int binding_value_dereference (struct binding_value **v,
+ const char *file, int line)
+{
+ struct binding_value *bv = *v;
+
+ *v = (struct binding_value *)0;
+
+ /* Decrement the reference count. If it's nonzero, we're
+ done. */
+ --(bv -> refcnt);
+ rc_register (file, line, v, bv, bv -> refcnt, 1, RC_MISC);
+ if (bv -> refcnt > 0)
+ return 1;
+ if (bv -> refcnt < 0) {
+ log_error ("%s(%d): negative refcnt!", file, line);
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history (bv);
+#endif
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ switch (bv -> type) {
+ case binding_boolean:
+ case binding_numeric:
+ break;
+ case binding_data:
+ if (bv -> value.data.buffer)
+ data_string_forget (&bv -> value.data, file, line);
+ break;
+ case binding_dns:
+#if defined (NSUPDATE_OLD)
+ if (bv -> value.dns) {
+ if (bv -> value.dns -> r_data) {
+ dfree (bv -> value.dns -> r_data_ephem, MDL);
+ bv -> value.dns -> r_data = (unsigned char *)0;
+ bv -> value.dns -> r_data_ephem =
+ (unsigned char *)0;
+ }
+ minires_freeupdrec (bv -> value.dns);
+ }
+ break;
+#endif
+ default:
+ log_error ("%s(%d): invalid binding type: %d",
+ file, line, bv -> type);
+ return 0;
+ }
+ free_binding_value(bv, file, line);
+ return 1;
+}
+
+#if defined (NSUPDATE_OLD)
+int evaluate_dns_expression (result, packet, lease, client_state, in_options,
+ cfg_options, scope, expr)
+ ns_updrec **result;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *cfg_options;
+ struct binding_scope **scope;
+ struct expression *expr;
+{
+ unsigned long ttl = 0;
+ char *tname;
+ struct data_string name, data;
+ int r0, r1, r2;
+
+ if (!result || *result) {
+ log_error ("evaluate_dns_expression called with non-null %s",
+ "result pointer");
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ switch (expr -> op) {
+#if defined (NSUPDATE)
+ case expr_ns_add:
+ r0 = evaluate_numeric_expression (&ttl, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.ns_add.ttl);
+ goto nsfinish;
+
+ case expr_ns_exists:
+ ttl = 1;
+
+ case expr_ns_delete:
+ case expr_ns_not_exists:
+ r0 = 1;
+ nsfinish:
+ memset (&name, 0, sizeof name);
+ r1 = evaluate_data_expression (&name, packet, lease,
+ client_state,
+ in_options, cfg_options, scope,
+ expr -> data.ns_add.rrname,
+ MDL);
+ if (r1) {
+ /* The result of the evaluation may or may not
+ be NUL-terminated, but we need it
+ terminated for sure, so we have to allocate
+ a buffer and terminate it. */
+ tname = dmalloc (name.len + 1, MDL);
+ if (!tname) {
+ r2 = 0;
+ r1 = 0;
+ data_string_forget (&name, MDL);
+ } else {
+ memcpy (tname, name.data, name.len);
+ tname [name.len] = 0;
+ memset (&data, 0, sizeof data);
+ r2 = evaluate_data_expression
+ (&data, packet, lease, client_state,
+ in_options, cfg_options, scope,
+ expr -> data.ns_add.rrdata, MDL);
+ }
+ } else {
+ r2 = 0;
+ tname = NULL;
+ }
+ if (r0 && r1 && (r2 || expr -> op != expr_ns_add)) {
+ *result = minires_mkupdrec (((expr -> op == expr_ns_add ||
+ expr -> op == expr_ns_delete)
+ ? S_UPDATE : S_PREREQ),
+ tname,
+ expr -> data.ns_add.rrclass,
+ expr -> data.ns_add.rrtype,
+ ttl);
+ if (!*result) {
+ ngood:
+ if (r2) {
+ data_string_forget (&data, MDL);
+ r2 = 0;
+ }
+ } else {
+ if (data.len) {
+ /* As a special case, if we get exactly
+ four bytes of data, it's an IP address
+ represented as a 32-bit quantity, which
+ is actually what we *should* be getting
+ here. Because res_mkupdrec is currently
+ broken and expects a dotted quad, convert
+ it. This should be fixed when the new
+ resolver is merged. */
+ if (data.len == 4) {
+ (*result) -> r_data_ephem =
+ dmalloc (16, MDL);
+ if (!(*result) -> r_data_ephem)
+ goto dpngood;
+ (*result) -> r_data =
+ (*result) -> r_data_ephem;
+ /*%Audit% 16 bytes max. %2004.06.17,Safe%*/
+ sprintf ((char *)(*result) -> r_data_ephem,
+ "%u.%u.%u.%u",
+ data.data [0] & 0xff,
+ data.data [1] & 0xff,
+ data.data [2] & 0xff,
+ data.data [3] & 0xff);
+ (*result) -> r_size =
+ strlen ((const char *)
+ (*result) -> r_data);
+ } else {
+ (*result) -> r_size = data.len;
+ (*result) -> r_data_ephem =
+ dmalloc (data.len, MDL);
+ if (!(*result) -> r_data_ephem) {
+ dpngood: /* double plus ungood. */
+ minires_freeupdrec (*result);
+ *result = 0;
+ goto ngood;
+ }
+ (*result) -> r_data =
+ (*result) -> r_data_ephem;
+ memcpy ((*result) -> r_data_ephem,
+ data.data, data.len);
+ }
+ } else {
+ (*result) -> r_data = 0;
+ (*result) -> r_size = 0;
+ }
+ switch (expr -> op) {
+ case expr_ns_add:
+ (*result) -> r_opcode = ADD;
+ break;
+ case expr_ns_delete:
+ (*result) -> r_opcode = DELETE;
+ break;
+ case expr_ns_exists:
+ (*result) -> r_opcode = YXRRSET;
+ break;
+ case expr_ns_not_exists:
+ (*result) -> r_opcode = NXRRSET;
+ break;
+
+ /* Can't happen, but satisfy gcc. */
+ default:
+ break;
+ }
+ }
+ }
+ if (r1) {
+ data_string_forget (&name, MDL);
+ dfree (tname, MDL);
+ }
+ if (r2)
+ data_string_forget (&data, MDL);
+ /* One flaw in the thinking here: an IP address and an
+ ASCII string both look like data expressions, but
+ for A records, we want an ASCII string, not a
+ binary IP address. Do I need to turn binary IP
+ addresses into a separate type? */
+ return (r0 && r1 &&
+ (r2 || expr -> op != expr_ns_add) && *result);
+
+#else
+ case expr_ns_add:
+ case expr_ns_delete:
+ case expr_ns_exists:
+ case expr_ns_not_exists:
+ return 0;
+#endif
+ case expr_funcall:
+ log_error ("%s: dns values for functions not supported.",
+ expr -> data.funcall.name);
+ break;
+
+ case expr_variable_reference:
+ log_error ("%s: dns values for variables not supported.",
+ expr -> data.variable);
+ break;
+
+ case expr_check:
+ case expr_equal:
+ case expr_not_equal:
+ case expr_regex_match:
+ case expr_iregex_match:
+ case expr_and:
+ case expr_or:
+ case expr_not:
+ case expr_match:
+ case expr_static:
+ case expr_known:
+ case expr_exists:
+ case expr_variable_exists:
+ log_error ("Boolean opcode in evaluate_dns_expression: %d",
+ expr -> op);
+ return 0;
+
+ case expr_none:
+ case expr_substring:
+ case expr_suffix:
+ case expr_lcase:
+ case expr_ucase:
+ case expr_option:
+ case expr_hardware:
+ case expr_const_data:
+ case expr_packet:
+ case expr_concat:
+ case expr_encapsulate:
+ case expr_host_lookup:
+ case expr_encode_int8:
+ case expr_encode_int16:
+ case expr_encode_int32:
+ case expr_binary_to_ascii:
+ case expr_reverse:
+ case expr_filename:
+ case expr_sname:
+ case expr_pick_first_value:
+ case expr_host_decl_name:
+ case expr_config_option:
+ case expr_leased_address:
+ case expr_null:
+ case expr_gethostname:
+ log_error ("Data opcode in evaluate_dns_expression: %d",
+ expr -> op);
+ return 0;
+
+ case expr_extract_int8:
+ case expr_extract_int16:
+ case expr_extract_int32:
+ case expr_const_int:
+ case expr_lease_time:
+ case expr_dns_transaction:
+ case expr_add:
+ case expr_subtract:
+ case expr_multiply:
+ case expr_divide:
+ case expr_remainder:
+ case expr_binary_and:
+ case expr_binary_or:
+ case expr_binary_xor:
+ case expr_client_state:
+ log_error ("Numeric opcode in evaluate_dns_expression: %d",
+ expr -> op);
+ return 0;
+
+ case expr_function:
+ log_error ("Function opcode in evaluate_dns_expression: %d",
+ expr -> op);
+ return 0;
+
+ case expr_arg:
+ break;
+ }
+
+ log_error ("Bogus opcode in evaluate_dns_expression: %d",
+ expr -> op);
+ return 0;
+}
+#endif /* defined (NSUPDATE_OLD) */
+
+int evaluate_boolean_expression (result, packet, lease, client_state,
+ in_options, cfg_options, scope, expr)
+ int *result;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *cfg_options;
+ struct binding_scope **scope;
+ struct expression *expr;
+{
+ struct data_string left, right;
+ int bleft, bright;
+ int sleft, sright;
+ struct binding *binding;
+ struct binding_value *bv, *obv;
+#ifdef HAVE_REGEX_H
+ int regflags = REG_EXTENDED | REG_NOSUB;
+ regex_t re;
+#endif
+
+ switch (expr -> op) {
+ case expr_check:
+ *result = check_collection (packet, lease,
+ expr -> data.check);
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("bool: check (%s) returns %s",
+ expr -> data.check -> name,
+ *result ? "true" : "false");
+#endif
+ return 1;
+
+ case expr_equal:
+ case expr_not_equal:
+ bv = obv = (struct binding_value *)0;
+ sleft = evaluate_expression (&bv, packet, lease, client_state,
+ in_options, cfg_options, scope,
+ expr -> data.equal [0], MDL);
+ sright = evaluate_expression (&obv, packet, lease,
+ client_state, in_options,
+ cfg_options, scope,
+ expr -> data.equal [1], MDL);
+ if (sleft && sright) {
+ if (bv -> type != obv -> type)
+ *result = expr -> op == expr_not_equal;
+ else {
+ switch (obv -> type) {
+ case binding_boolean:
+ if (bv -> value.boolean == obv -> value.boolean)
+ *result = expr -> op == expr_equal;
+ else
+ *result = expr -> op == expr_not_equal;
+ break;
+
+ case binding_data:
+ if ((bv -> value.data.len ==
+ obv -> value.data.len) &&
+ !memcmp (bv -> value.data.data,
+ obv -> value.data.data,
+ obv -> value.data.len))
+ *result = expr -> op == expr_equal;
+ else
+ *result = expr -> op == expr_not_equal;
+ break;
+
+ case binding_numeric:
+ if (bv -> value.intval == obv -> value.intval)
+ *result = expr -> op == expr_equal;
+ else
+ *result = expr -> op == expr_not_equal;
+ break;
+#if defined (NSUPDATE_OLD)
+ case binding_dns:
+#if defined (NSUPDATE)
+ /* XXX This should be a comparison for equal
+ XXX values, not for identity. */
+ if (bv -> value.dns == obv -> value.dns)
+ *result = expr -> op == expr_equal;
+ else
+ *result = expr -> op == expr_not_equal;
+#else
+ *result = expr -> op == expr_not_equal;
+#endif
+ break;
+#endif /* NSUPDATE_OLD */
+ case binding_function:
+ if (bv -> value.fundef == obv -> value.fundef)
+ *result = expr -> op == expr_equal;
+ else
+ *result = expr -> op == expr_not_equal;
+ break;
+ default:
+ *result = expr -> op == expr_not_equal;
+ break;
+ }
+ }
+ } else if (!sleft && !sright)
+ *result = expr -> op == expr_equal;
+ else
+ *result = expr -> op == expr_not_equal;
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("bool: %sequal = %s",
+ expr -> op == expr_not_equal ? "not" : "",
+ (*result ? "true" : "false"));
+#endif
+ if (sleft)
+ binding_value_dereference (&bv, MDL);
+ if (sright)
+ binding_value_dereference (&obv, MDL);
+ return 1;
+
+ case expr_iregex_match:
+#ifdef HAVE_REGEX_H
+ regflags |= REG_ICASE;
+#endif
+ /* FALL THROUGH */
+ case expr_regex_match:
+#ifdef HAVE_REGEX_H
+ memset(&left, 0, sizeof left);
+ bleft = evaluate_data_expression(&left, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr->data.equal[0], MDL);
+ memset(&right, 0, sizeof right);
+ bright = evaluate_data_expression(&right, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr->data.equal[1], MDL);
+
+ *result = 0;
+ memset(&re, 0, sizeof(re));
+ if (bleft && bright &&
+ (left.data != NULL) && (right.data != NULL) &&
+ (regcomp(&re, (char *)right.data, regflags) == 0) &&
+ (regexec(&re, (char *)left.data, (size_t)0, NULL, 0) == 0))
+ *result = 1;
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug("bool: %s ~= %s yields %s",
+ bleft ? print_hex_1(left.len, left.data, 20)
+ : "NULL",
+ bright ? print_hex_2 (right.len, right.data, 20)
+ : "NULL",
+ *result ? "true" : "false");
+#endif
+
+ if (bleft)
+ data_string_forget(&left, MDL);
+ if (bright)
+ data_string_forget(&right, MDL);
+
+ regfree(&re);
+
+ /*
+ * If we have bleft and bright then we have a good
+ * syntax, otherwise not.
+ *
+ * XXX: we don't warn on invalid regular expression
+ * syntax, should we?
+ */
+ return bleft && bright;
+#else
+ /* It shouldn't be possible to configure a regex operator
+ * when there's no support.
+ */
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ break;
+#endif
+
+ case expr_and:
+ sleft = evaluate_boolean_expression (&bleft, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [0]);
+ if (sleft && bleft)
+ sright = evaluate_boolean_expression
+ (&bright, packet, lease, client_state,
+ in_options, cfg_options,
+ scope, expr -> data.and [1]);
+ else
+ sright = bright = 0;
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("bool: and (%s, %s) = %s",
+ sleft ? (bleft ? "true" : "false") : "NULL",
+ sright ? (bright ? "true" : "false") : "NULL",
+ ((sleft && sright)
+ ? (bleft && bright ? "true" : "false") : "NULL"));
+#endif
+ if (sleft && sright) {
+ *result = bleft && bright;
+ return 1;
+ }
+ return 0;
+
+ case expr_or:
+ bleft = bright = 0;
+ sleft = evaluate_boolean_expression (&bleft, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.or [0]);
+ if (!sleft || !bleft)
+ sright = evaluate_boolean_expression
+ (&bright, packet, lease, client_state,
+ in_options, cfg_options,
+ scope, expr -> data.or [1]);
+ else
+ sright = 0;
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("bool: or (%s, %s) = %s",
+ sleft ? (bleft ? "true" : "false") : "NULL",
+ sright ? (bright ? "true" : "false") : "NULL",
+ ((sleft || sright)
+ ? (bleft || bright ? "true" : "false") : "NULL"));
+#endif
+ if (sleft || sright) {
+ *result = bleft || bright;
+ return 1;
+ }
+ return 0;
+
+ case expr_not:
+ sleft = evaluate_boolean_expression(&bleft, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr->data.not);
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug("bool: not (%s) = %s",
+ sleft ? (bleft ? "true" : "false") : "NULL",
+ sleft ? (!bleft ? "true" : "false") : "NULL");
+#endif
+ if (sleft) {
+ *result = !bleft;
+ return 1;
+ }
+ return 0;
+
+ case expr_exists:
+ memset (&left, 0, sizeof left);
+ if (!in_options ||
+ !get_option (&left, expr -> data.exists -> universe,
+ packet, lease, client_state,
+ in_options, cfg_options, in_options,
+ scope, expr -> data.exists -> code, MDL))
+ *result = 0;
+ else {
+ *result = 1;
+ data_string_forget (&left, MDL);
+ }
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("bool: exists %s.%s = %s",
+ expr -> data.option -> universe -> name,
+ expr -> data.option -> name,
+ *result ? "true" : "false");
+#endif
+ return 1;
+
+ case expr_known:
+ if (!packet) {
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("bool: known = NULL");
+#endif
+ return 0;
+ }
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("bool: known = %s",
+ packet -> known ? "true" : "false");
+#endif
+ *result = packet -> known;
+ return 1;
+
+ case expr_static:
+ if (!lease || !(lease -> flags & STATIC_LEASE)) {
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("bool: static = false (%s %s %s %d)",
+ lease ? "y" : "n",
+ (lease && (lease -> flags & STATIC_LEASE)
+ ? "y" : "n"),
+ piaddr (lease -> ip_addr),
+ lease ? lease -> flags : 0);
+#endif
+ *result = 0;
+ return 1;
+ }
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("bool: static = true");
+#endif
+ *result = 1;
+ return 1;
+
+ case expr_variable_exists:
+ if (scope && *scope) {
+ binding = find_binding (*scope, expr -> data.variable);
+
+ if (binding) {
+ if (binding -> value)
+ *result = 1;
+ else
+ *result = 0;
+ } else
+ *result = 0;
+ } else
+ *result = 0;
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("boolean: %s? = %s", expr -> data.variable,
+ *result ? "true" : "false");
+#endif
+ return 1;
+
+ case expr_variable_reference:
+ if (scope && *scope) {
+ binding = find_binding (*scope, expr -> data.variable);
+
+ if (binding && binding -> value) {
+ if (binding -> value -> type ==
+ binding_boolean) {
+ *result = binding -> value -> value.boolean;
+ sleft = 1;
+ } else {
+ log_error ("binding type %d in %s.",
+ binding -> value -> type,
+ "evaluate_boolean_expression");
+ sleft = 0;
+ }
+ } else
+ sleft = 0;
+ } else
+ sleft = 0;
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("boolean: %s = %s", expr -> data.variable,
+ sleft ? (*result ? "true" : "false") : "NULL");
+#endif
+ return sleft;
+
+ case expr_funcall:
+ bv = (struct binding_value *)0;
+ sleft = evaluate_expression (&bv, packet, lease, client_state,
+ in_options, cfg_options,
+ scope, expr, MDL);
+ if (sleft) {
+ if (bv -> type != binding_boolean)
+ log_error ("%s() returned type %d in %s.",
+ expr -> data.funcall.name,
+ bv -> type,
+ "evaluate_boolean_expression");
+ else
+ *result = bv -> value.boolean;
+ binding_value_dereference (&bv, MDL);
+ }
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("boolean: %s() = %s", expr -> data.funcall.name,
+ sleft ? (*result ? "true" : "false") : "NULL");
+#endif
+ break;
+
+ case expr_none:
+ case expr_match:
+ case expr_substring:
+ case expr_suffix:
+ case expr_lcase:
+ case expr_ucase:
+ case expr_option:
+ case expr_hardware:
+ case expr_const_data:
+ case expr_packet:
+ case expr_concat:
+ case expr_encapsulate:
+ case expr_host_lookup:
+ case expr_encode_int8:
+ case expr_encode_int16:
+ case expr_encode_int32:
+ case expr_binary_to_ascii:
+ case expr_reverse:
+ case expr_pick_first_value:
+ case expr_host_decl_name:
+ case expr_config_option:
+ case expr_leased_address:
+ case expr_null:
+ case expr_filename:
+ case expr_sname:
+ case expr_gethostname:
+ case expr_concat_dclist:
+ log_error ("Data opcode in evaluate_boolean_expression: %d",
+ expr -> op);
+ return 0;
+
+ case expr_extract_int8:
+ case expr_extract_int16:
+ case expr_extract_int32:
+ case expr_const_int:
+ case expr_lease_time:
+ case expr_dns_transaction:
+ case expr_add:
+ case expr_subtract:
+ case expr_multiply:
+ case expr_divide:
+ case expr_remainder:
+ case expr_binary_and:
+ case expr_binary_or:
+ case expr_binary_xor:
+ case expr_client_state:
+ log_error ("Numeric opcode in evaluate_boolean_expression: %d",
+ expr -> op);
+ return 0;
+
+ case expr_ns_add:
+ case expr_ns_delete:
+ case expr_ns_exists:
+ case expr_ns_not_exists:
+ log_error ("dns opcode in evaluate_boolean_expression: %d",
+ expr -> op);
+ return 0;
+
+ case expr_function:
+ log_error ("function definition in evaluate_boolean_expr");
+ return 0;
+
+ case expr_arg:
+ break;
+ }
+
+ log_error ("Bogus opcode in evaluate_boolean_expression: %d",
+ expr -> op);
+ return 0;
+}
+
+int evaluate_data_expression (result, packet, lease, client_state,
+ in_options, cfg_options, scope, expr, file, line)
+ struct data_string *result;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *cfg_options;
+ struct binding_scope **scope;
+ struct expression *expr;
+ const char *file;
+ int line;
+{
+ struct data_string data, other;
+ unsigned long offset, len, i;
+ int s0, s1, s2, s3;
+ int status;
+ struct binding *binding;
+ unsigned char *s;
+ struct binding_value *bv;
+
+ switch (expr -> op) {
+ /* Extract N bytes starting at byte M of a data string. */
+ case expr_substring:
+ memset (&data, 0, sizeof data);
+ s0 = evaluate_data_expression (&data, packet, lease,
+ client_state,
+ in_options, cfg_options, scope,
+ expr -> data.substring.expr,
+ MDL);
+
+ /* Evaluate the offset and length. */
+ s1 = evaluate_numeric_expression
+ (&offset, packet, lease, client_state, in_options,
+ cfg_options, scope, expr -> data.substring.offset);
+ s2 = evaluate_numeric_expression (&len, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.substring.len);
+
+ if (s0 && s1 && s2) {
+ /* If the offset is after end of the string,
+ return an empty string. Otherwise, do the
+ adjustments and return what's left. */
+ if (data.len > offset) {
+ data_string_copy (result, &data, file, line);
+ result -> len -= offset;
+ if (result -> len > len) {
+ result -> len = len;
+ result -> terminated = 0;
+ }
+ result -> data += offset;
+ }
+ s3 = 1;
+ } else
+ s3 = 0;
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: substring (%s, %s, %s) = %s",
+ s0 ? print_hex_1 (data.len, data.data, 30) : "NULL",
+ s1 ? print_dec_1 (offset) : "NULL",
+ s2 ? print_dec_2 (len) : "NULL",
+ (s3 ? print_hex_2 (result -> len, result -> data, 30)
+ : "NULL"));
+#endif
+ if (s0)
+ data_string_forget (&data, MDL);
+ if (s3)
+ return 1;
+ return 0;
+
+ /* Extract the last N bytes of a data string. */
+ case expr_suffix:
+ memset (&data, 0, sizeof data);
+ s0 = evaluate_data_expression (&data, packet, lease,
+ client_state,
+ in_options, cfg_options, scope,
+ expr -> data.suffix.expr, MDL);
+ /* Evaluate the length. */
+ s1 = evaluate_numeric_expression (&len, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.suffix.len);
+ if (s0 && s1) {
+ data_string_copy (result, &data, file, line);
+
+ /* If we are returning the last N bytes of a
+ string whose length is <= N, just return
+ the string - otherwise, compute a new
+ starting address and decrease the
+ length. */
+ if (data.len > len) {
+ result -> data += data.len - len;
+ result -> len = len;
+ }
+ data_string_forget (&data, MDL);
+ }
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: suffix (%s, %s) = %s",
+ s0 ? print_hex_1 (data.len, data.data, 30) : "NULL",
+ s1 ? print_dec_1 (len) : "NULL",
+ ((s0 && s1)
+ ? print_hex_2 (result -> len, result -> data, 30)
+ : "NULL"));
+#endif
+ return s0 && s1;
+
+ /* Convert string to lowercase. */
+ case expr_lcase:
+ memset(&data, 0, sizeof data);
+ s0 = evaluate_data_expression(&data, packet, lease,
+ client_state,
+ in_options, cfg_options, scope,
+ expr->data.lcase, MDL);
+ s1 = 0;
+ if (s0) {
+ result->len = data.len;
+ if (buffer_allocate(&result->buffer,
+ result->len + data.terminated,
+ MDL)) {
+ result->data = &result->buffer->data[0];
+ memcpy(result->buffer->data, data.data,
+ data.len + data.terminated);
+ result->terminated = data.terminated;
+ s = (unsigned char *)result->data;
+ for (i = 0; i < result->len; i++, s++)
+ *s = tolower(*s);
+ s1 = 1;
+ } else {
+ log_error("data: lcase: no buffer memory.");
+ }
+ }
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug("data: lcase (%s) = %s",
+ s0 ? print_hex_1(data.len, data.data, 30) : "NULL",
+ s1 ? print_hex_2(result->len, result->data, 30)
+ : "NULL");
+#endif
+ if (s0)
+ data_string_forget(&data, MDL);
+ return s1;
+
+ /* Convert string to uppercase. */
+ case expr_ucase:
+ memset(&data, 0, sizeof data);
+ s0 = evaluate_data_expression(&data, packet, lease,
+ client_state,
+ in_options, cfg_options, scope,
+ expr->data.lcase, MDL);
+ s1 = 0;
+ if (s0) {
+ result->len = data.len;
+ if (buffer_allocate(&result->buffer,
+ result->len + data.terminated,
+ file, line)) {
+ result->data = &result->buffer->data[0];
+ memcpy(result->buffer->data, data.data,
+ data.len + data.terminated);
+ result->terminated = data.terminated;
+ s = (unsigned char *)result->data;
+ for (i = 0; i < result->len; i++, s++)
+ *s = toupper(*s);
+ s1 = 1;
+ } else {
+ log_error("data: lcase: no buffer memory.");
+ }
+ }
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug("data: ucase (%s) = %s",
+ s0 ? print_hex_1(data.len, data.data, 30) : "NULL",
+ s1 ? print_hex_2(result->len, result->data, 30)
+ : "NULL");
+#endif
+ if (s0)
+ data_string_forget(&data, MDL);
+ return s1;
+
+ /* Extract an option. */
+ case expr_option:
+ if (in_options)
+ s0 = get_option (result,
+ expr -> data.option -> universe,
+ packet, lease, client_state,
+ in_options, cfg_options, in_options,
+ scope, expr -> data.option -> code,
+ file, line);
+ else
+ s0 = 0;
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: option %s.%s = %s",
+ expr -> data.option -> universe -> name,
+ expr -> data.option -> name,
+ s0 ? print_hex_1 (result -> len, result -> data, 60)
+ : "NULL");
+#endif
+ return s0;
+
+ case expr_config_option:
+ if (cfg_options)
+ s0 = get_option (result,
+ expr -> data.option -> universe,
+ packet, lease, client_state,
+ in_options, cfg_options, cfg_options,
+ scope, expr -> data.option -> code,
+ file, line);
+ else
+ s0 = 0;
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: config-option %s.%s = %s",
+ expr -> data.option -> universe -> name,
+ expr -> data.option -> name,
+ s0 ? print_hex_1 (result -> len, result -> data, 60)
+ : "NULL");
+#endif
+ return s0;
+
+ /* Combine the hardware type and address. */
+ case expr_hardware:
+ /* On the client, hardware is our hardware. */
+ if (client_state) {
+ memset (result, 0, sizeof *result);
+ result -> data =
+ client_state -> interface -> hw_address.hbuf;
+ result -> len =
+ client_state -> interface -> hw_address.hlen;
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: hardware = %s",
+ print_hex_1 (result -> len,
+ result -> data, 60));
+#endif
+ return 1;
+ }
+
+ /* The server cares about the client's hardware address,
+ so only in the case where we are examining a packet can
+ we return anything. */
+ if (!packet || !packet -> raw) {
+ log_error ("data: hardware: raw packet not available");
+ return 0;
+ }
+ if (packet -> raw -> hlen > sizeof packet -> raw -> chaddr) {
+ log_error ("data: hardware: invalid hlen (%d)\n",
+ packet -> raw -> hlen);
+ return 0;
+ }
+ result -> len = packet -> raw -> hlen + 1;
+ if (buffer_allocate (&result -> buffer, result -> len,
+ file, line)) {
+ result -> data = &result -> buffer -> data [0];
+ result -> buffer -> data [0] = packet -> raw -> htype;
+ memcpy (&result -> buffer -> data [1],
+ packet -> raw -> chaddr,
+ packet -> raw -> hlen);
+ result -> terminated = 0;
+ } else {
+ log_error ("data: hardware: no memory for buffer.");
+ return 0;
+ }
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: hardware = %s",
+ print_hex_1 (result -> len, result -> data, 60));
+#endif
+ return 1;
+
+ /* Extract part of the raw packet. */
+ case expr_packet:
+ if (!packet || !packet -> raw) {
+ log_error ("data: packet: raw packet not available");
+ return 0;
+ }
+
+ s0 = evaluate_numeric_expression (&offset, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.packet.offset);
+ s1 = evaluate_numeric_expression (&len,
+ packet, lease, client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.packet.len);
+ if (s0 && s1 && offset < packet -> packet_length) {
+ if (offset + len > packet -> packet_length)
+ result -> len =
+ packet -> packet_length - offset;
+ else
+ result -> len = len;
+ if (buffer_allocate (&result -> buffer,
+ result -> len, file, line)) {
+ result -> data = &result -> buffer -> data [0];
+ memcpy (result -> buffer -> data,
+ (((unsigned char *)(packet -> raw))
+ + offset), result -> len);
+ result -> terminated = 0;
+ } else {
+ log_error ("data: packet: no buffer memory.");
+ return 0;
+ }
+ s2 = 1;
+ } else
+ s2 = 0;
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: packet (%ld, %ld) = %s",
+ offset, len,
+ s2 ? print_hex_1 (result -> len,
+ result -> data, 60) : NULL);
+#endif
+ return s2;
+
+ /* The encapsulation of all defined options in an
+ option space... */
+ case expr_encapsulate:
+ if (cfg_options)
+ s0 = option_space_encapsulate
+ (result, packet, lease, client_state,
+ in_options, cfg_options, scope,
+ &expr -> data.encapsulate);
+ else
+ s0 = 0;
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: encapsulate (%s) = %s",
+ expr -> data.encapsulate.data,
+ s0 ? print_hex_1 (result -> len,
+ result -> data, 60) : "NULL");
+#endif
+ return s0;
+
+ /* Some constant data... */
+ case expr_const_data:
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: const = %s",
+ print_hex_1 (expr -> data.const_data.len,
+ expr -> data.const_data.data, 60));
+#endif
+ data_string_copy (result,
+ &expr -> data.const_data, file, line);
+ return 1;
+
+ /* Hostname lookup... */
+ case expr_host_lookup:
+ s0 = do_host_lookup (result, expr -> data.host_lookup);
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: DNS lookup (%s) = %s",
+ expr -> data.host_lookup -> hostname,
+ (s0
+ ? print_dotted_quads (result -> len, result -> data)
+ : "NULL"));
+#endif
+ return s0;
+
+ /* Concatenation... */
+ case expr_concat:
+ memset (&data, 0, sizeof data);
+ s0 = evaluate_data_expression (&data, packet, lease,
+ client_state,
+ in_options, cfg_options, scope,
+ expr -> data.concat [0], MDL);
+ memset (&other, 0, sizeof other);
+ s1 = evaluate_data_expression (&other, packet, lease,
+ client_state,
+ in_options, cfg_options, scope,
+ expr -> data.concat [1], MDL);
+
+ if (s0 && s1) {
+ result -> len = data.len + other.len;
+ if (!buffer_allocate (&result -> buffer,
+ (result -> len + other.terminated),
+ file, line)) {
+ log_error ("data: concat: no memory");
+ result -> len = 0;
+ data_string_forget (&data, MDL);
+ data_string_forget (&other, MDL);
+ return 0;
+ }
+ result -> data = &result -> buffer -> data [0];
+ memcpy (result -> buffer -> data, data.data, data.len);
+ memcpy (&result -> buffer -> data [data.len],
+ other.data, other.len + other.terminated);
+ }
+
+ if (s0)
+ data_string_forget (&data, MDL);
+ if (s1)
+ data_string_forget (&other, MDL);
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: concat (%s, %s) = %s",
+ s0 ? print_hex_1 (data.len, data.data, 20) : "NULL",
+ s1 ? print_hex_2 (other.len, other.data, 20) : "NULL",
+ ((s0 && s1)
+ ? print_hex_3 (result -> len, result -> data, 30)
+ : "NULL"));
+#endif
+ return s0 && s1;
+
+ case expr_encode_int8:
+ s0 = evaluate_numeric_expression (&len, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.encode_int);
+ if (s0) {
+ result -> len = 1;
+ if (!buffer_allocate (&result -> buffer,
+ 1, file, line)) {
+ log_error ("data: encode_int8: no memory");
+ result -> len = 0;
+ s0 = 0;
+ } else {
+ result -> data = &result -> buffer -> data [0];
+ result -> buffer -> data [0] = len;
+ }
+ } else
+ result -> len = 0;
+
+#if defined (DEBUG_EXPRESSIONS)
+ if (!s0)
+ log_debug ("data: encode_int8 (NULL) = NULL");
+ else
+ log_debug ("data: encode_int8 (%ld) = %s", len,
+ print_hex_2 (result -> len,
+ result -> data, 20));
+#endif
+ return s0;
+
+
+ case expr_encode_int16:
+ s0 = evaluate_numeric_expression (&len, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.encode_int);
+ if (s0) {
+ result -> len = 2;
+ if (!buffer_allocate (&result -> buffer, 2,
+ file, line)) {
+ log_error ("data: encode_int16: no memory");
+ result -> len = 0;
+ s0 = 0;
+ } else {
+ result -> data = &result -> buffer -> data [0];
+ putUShort (result -> buffer -> data, len);
+ }
+ } else
+ result -> len = 0;
+
+#if defined (DEBUG_EXPRESSIONS)
+ if (!s0)
+ log_debug ("data: encode_int16 (NULL) = NULL");
+ else
+ log_debug ("data: encode_int16 (%ld) = %s", len,
+ print_hex_2 (result -> len,
+ result -> data, 20));
+#endif
+ return s0;
+
+ case expr_encode_int32:
+ s0 = evaluate_numeric_expression (&len, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.encode_int);
+ if (s0) {
+ result -> len = 4;
+ if (!buffer_allocate (&result -> buffer, 4,
+ file, line)) {
+ log_error ("data: encode_int32: no memory");
+ result -> len = 0;
+ s0 = 0;
+ } else {
+ result -> data = &result -> buffer -> data [0];
+ putULong (result -> buffer -> data, len);
+ }
+ } else
+ result -> len = 0;
+
+#if defined (DEBUG_EXPRESSIONS)
+ if (!s0)
+ log_debug ("data: encode_int32 (NULL) = NULL");
+ else
+ log_debug ("data: encode_int32 (%ld) = %s", len,
+ print_hex_2 (result -> len,
+ result -> data, 20));
+#endif
+ return s0;
+
+ case expr_binary_to_ascii:
+ /* Evaluate the base (offset) and width (len): */
+ s0 = evaluate_numeric_expression
+ (&offset, packet, lease, client_state, in_options,
+ cfg_options, scope, expr -> data.b2a.base);
+ s1 = evaluate_numeric_expression (&len, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.b2a.width);
+
+ /* Evaluate the separator string. */
+ memset (&data, 0, sizeof data);
+ s2 = evaluate_data_expression (&data, packet, lease,
+ client_state,
+ in_options, cfg_options, scope,
+ expr -> data.b2a.separator,
+ MDL);
+
+ /* Evaluate the data to be converted. */
+ memset (&other, 0, sizeof other);
+ s3 = evaluate_data_expression (&other, packet, lease,
+ client_state,
+ in_options, cfg_options, scope,
+ expr -> data.b2a.buffer, MDL);
+
+ if (s0 && s1 && s2 && s3) {
+ unsigned buflen, i;
+
+ if (len != 8 && len != 16 && len != 32) {
+ log_info ("binary_to_ascii: %s %ld!",
+ "invalid width", len);
+ status = 0;
+ goto b2a_out;
+ }
+ len /= 8;
+
+ /* The buffer must be a multiple of the number's
+ width. */
+ if (other.len % len) {
+ log_info ("binary-to-ascii: %s %d %s %ld!",
+ "length of buffer", other.len,
+ "not a multiple of width", len);
+ status = 0;
+ goto b2a_out;
+ }
+
+ /* Count the width of the output. */
+ buflen = 0;
+ for (i = 0; i < other.len; i += len) {
+ if (len == 1) {
+ if (offset == 8) {
+ if (other.data [i] < 8)
+ buflen++;
+ else if (other.data [i] < 64)
+ buflen += 2;
+ else
+ buflen += 3;
+ } else if (offset == 10) {
+ if (other.data [i] < 10)
+ buflen++;
+ else if (other.data [i] < 100)
+ buflen += 2;
+ else
+ buflen += 3;
+ } else if (offset == 16) {
+ if (other.data [i] < 16)
+ buflen++;
+ else
+ buflen += 2;
+ } else
+ buflen += (converted_length
+ (&other.data [i],
+ offset, 1));
+ } else
+ buflen += (converted_length
+ (&other.data [i],
+ offset, len));
+ if (i + len != other.len)
+ buflen += data.len;
+ }
+
+ if (!buffer_allocate (&result -> buffer,
+ buflen + 1, file, line)) {
+ log_error ("data: binary-to-ascii: no memory");
+ status = 0;
+ goto b2a_out;
+ }
+ result -> data = &result -> buffer -> data [0];
+ result -> len = buflen;
+ result -> terminated = 1;
+
+ buflen = 0;
+ for (i = 0; i < other.len; i += len) {
+ buflen += (binary_to_ascii
+ (&result -> buffer -> data [buflen],
+ &other.data [i], offset, len));
+ if (i + len != other.len) {
+ memcpy (&result ->
+ buffer -> data [buflen],
+ data.data, data.len);
+ buflen += data.len;
+ }
+ }
+ /* NUL terminate. */
+ result -> buffer -> data [buflen] = 0;
+ status = 1;
+ } else
+ status = 0;
+
+ b2a_out:
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: binary-to-ascii (%s, %s, %s, %s) = %s",
+ s0 ? print_dec_1 (offset) : "NULL",
+ s1 ? print_dec_2 (len) : "NULL",
+ s2 ? print_hex_1 (data.len, data.data, 30) : "NULL",
+ s3 ? print_hex_2 (other.len, other.data, 30) : "NULL",
+ (status ? print_hex_3 (result -> len, result -> data, 30)
+ : "NULL"));
+#endif
+ if (s2)
+ data_string_forget (&data, MDL);
+ if (s3)
+ data_string_forget (&other, MDL);
+ if (status)
+ return 1;
+ return 0;
+
+ case expr_reverse:
+ /* Evaluate the width (len): */
+ s0 = evaluate_numeric_expression
+ (&len, packet, lease, client_state, in_options,
+ cfg_options, scope, expr -> data.reverse.width);
+
+ /* Evaluate the data. */
+ memset (&data, 0, sizeof data);
+ s1 = evaluate_data_expression (&data, packet, lease,
+ client_state,
+ in_options, cfg_options, scope,
+ expr -> data.reverse.buffer,
+ MDL);
+
+ if (s0 && s1) {
+ int i;
+
+ /* The buffer must be a multiple of the number's
+ width. */
+ if (data.len % len) {
+ log_info ("reverse: %s %d %s %ld!",
+ "length of buffer", data.len,
+ "not a multiple of width", len);
+ status = 0;
+ goto reverse_out;
+ }
+
+ /* XXX reverse in place? I don't think we can. */
+ if (!buffer_allocate (&result -> buffer,
+ data.len, file, line)) {
+ log_error ("data: reverse: no memory");
+ status = 0;
+ goto reverse_out;
+ }
+ result -> data = &result -> buffer -> data [0];
+ result -> len = data.len;
+ result -> terminated = 0;
+
+ for (i = 0; i < data.len; i += len) {
+ memcpy (&result -> buffer -> data [i],
+ &data.data [data.len - i - len], len);
+ }
+ status = 1;
+ } else
+ status = 0;
+
+ reverse_out:
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: reverse (%s, %s) = %s",
+ s0 ? print_dec_1 (len) : "NULL",
+ s1 ? print_hex_1 (data.len, data.data, 30) : "NULL",
+ (status ? print_hex_3 (result -> len, result -> data, 30)
+ : "NULL"));
+#endif
+ if (s0)
+ data_string_forget (&data, MDL);
+ if (status)
+ return 1;
+ return 0;
+
+ case expr_leased_address:
+ if (!lease) {
+ log_debug("data: \"leased-address\" configuration "
+ "directive: there is no lease associated "
+ "with this client.");
+ return 0;
+ }
+ result -> len = lease -> ip_addr.len;
+ if (buffer_allocate (&result -> buffer, result -> len,
+ file, line)) {
+ result -> data = &result -> buffer -> data [0];
+ memcpy (&result -> buffer -> data [0],
+ lease -> ip_addr.iabuf, lease -> ip_addr.len);
+ result -> terminated = 0;
+ } else {
+ log_error ("data: leased-address: no memory.");
+ return 0;
+ }
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: leased-address = %s",
+ print_hex_1 (result -> len, result -> data, 60));
+#endif
+ return 1;
+
+ case expr_pick_first_value:
+ memset (&data, 0, sizeof data);
+ if ((evaluate_data_expression
+ (result, packet,
+ lease, client_state, in_options, cfg_options,
+ scope, expr -> data.pick_first_value.car, MDL))) {
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: pick_first_value (%s, xxx)",
+ print_hex_1 (result -> len,
+ result -> data, 40));
+#endif
+ return 1;
+ }
+
+ if (expr -> data.pick_first_value.cdr &&
+ (evaluate_data_expression
+ (result, packet,
+ lease, client_state, in_options, cfg_options,
+ scope, expr -> data.pick_first_value.cdr, MDL))) {
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: pick_first_value (NULL, %s)",
+ print_hex_1 (result -> len,
+ result -> data, 40));
+#endif
+ return 1;
+ }
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: pick_first_value (NULL, NULL) = NULL");
+#endif
+ return 0;
+
+ case expr_host_decl_name:
+ if (!lease || !lease -> host) {
+ log_error ("data: host_decl_name: not available");
+ return 0;
+ }
+ result -> len = strlen (lease -> host -> name);
+ if (buffer_allocate (&result -> buffer,
+ result -> len + 1, file, line)) {
+ result -> data = &result -> buffer -> data [0];
+ strcpy ((char *)&result -> buffer -> data [0],
+ lease -> host -> name);
+ result -> terminated = 1;
+ } else {
+ log_error ("data: host-decl-name: no memory.");
+ return 0;
+ }
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: host-decl-name = %s", lease -> host -> name);
+#endif
+ return 1;
+
+ case expr_null:
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: null = NULL");
+#endif
+ return 0;
+
+ case expr_variable_reference:
+ if (scope && *scope) {
+ binding = find_binding (*scope, expr -> data.variable);
+
+ if (binding && binding -> value) {
+ if (binding -> value -> type == binding_data) {
+ data_string_copy (result,
+ &binding -> value -> value.data,
+ file, line);
+ s0 = 1;
+ } else if (binding -> value -> type != binding_data) {
+ log_error ("binding type %d in %s.",
+ binding -> value -> type,
+ "evaluate_data_expression");
+ s0 = 0;
+ } else
+ s0 = 0;
+ } else
+ s0 = 0;
+ } else
+ s0 = 0;
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: %s = %s", expr -> data.variable,
+ s0 ? print_hex_1 (result -> len,
+ result -> data, 50) : "NULL");
+#endif
+ return s0;
+
+ case expr_funcall:
+ bv = (struct binding_value *)0;
+ s0 = evaluate_expression (&bv, packet, lease, client_state,
+ in_options, cfg_options,
+ scope, expr, MDL);
+ if (s0) {
+ if (bv -> type != binding_data)
+ log_error ("%s() returned type %d in %s.",
+ expr -> data.funcall.name,
+ bv -> type,
+ "evaluate_data_expression");
+ else
+ data_string_copy (result, &bv -> value.data,
+ file, line);
+ binding_value_dereference (&bv, MDL);
+ }
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: %s = %s", expr -> data.funcall.name,
+ s0 ? print_hex_1 (result -> len,
+ result -> data, 50) : "NULL");
+#endif
+ break;
+
+ /* Extract the filename. */
+ case expr_filename:
+ if (packet && packet -> raw -> file [0]) {
+ char *fn =
+ memchr (packet -> raw -> file, 0,
+ sizeof packet -> raw -> file);
+ if (!fn)
+ fn = ((char *)packet -> raw -> file +
+ sizeof packet -> raw -> file);
+ result -> len = fn - &(packet -> raw -> file [0]);
+ if (buffer_allocate (&result -> buffer,
+ result -> len + 1, file, line)) {
+ result -> data = &result -> buffer -> data [0];
+ memcpy (&result -> buffer -> data [0],
+ packet -> raw -> file,
+ result -> len);
+ result -> buffer -> data [result -> len] = 0;
+ result -> terminated = 1;
+ s0 = 1;
+ } else {
+ log_error ("data: filename: no memory.");
+ s0 = 0;
+ }
+ } else
+ s0 = 0;
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_info ("data: filename = \"%s\"",
+ s0 ? (const char *)(result -> data) : "NULL");
+#endif
+ return s0;
+
+ /* Extract the server name. */
+ case expr_sname:
+ if (packet && packet -> raw -> sname [0]) {
+ char *fn =
+ memchr (packet -> raw -> sname, 0,
+ sizeof packet -> raw -> sname);
+ if (!fn)
+ fn = ((char *)packet -> raw -> sname +
+ sizeof packet -> raw -> sname);
+ result -> len = fn - &packet -> raw -> sname [0];
+ if (buffer_allocate (&result -> buffer,
+ result -> len + 1, file, line)) {
+ result -> data = &result -> buffer -> data [0];
+ memcpy (&result -> buffer -> data [0],
+ packet -> raw -> sname,
+ result -> len);
+ result -> buffer -> data [result -> len] = 0;
+ result -> terminated = 1;
+ s0 = 1;
+ } else {
+ log_error ("data: sname: no memory.");
+ s0 = 0;
+ }
+ } else
+ s0 = 0;
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_info ("data: sname = \"%s\"",
+ s0 ? (const char *)(result -> data) : "NULL");
+#endif
+ return s0;
+
+ /* Provide the system's local hostname as a return value. */
+ case expr_gethostname:
+ /*
+ * Allocate a buffer to return.
+ *
+ * The largest valid hostname is maybe 64 octets at a single
+ * label, or 255 octets if you think a hostname is allowed
+ * to contain labels (plus termination).
+ */
+ memset(result, 0, sizeof(*result));
+ if (!buffer_allocate(&result->buffer, 255, file, line)) {
+ log_error("data: gethostname(): no memory for buffer");
+ return 0;
+ }
+ result->data = result->buffer->data;
+
+ /*
+ * On successful completion, gethostname() resturns 0. It may
+ * not null-terminate the string if there was insufficient
+ * space.
+ */
+ if (!gethostname((char *)result->buffer->data, 255)) {
+ if (result->buffer->data[255] == '\0')
+ result->len =
+ strlen((char *)result->buffer->data);
+ else
+ result->len = 255;
+ return 1;
+ }
+
+ data_string_forget(result, MDL);
+ return 0;
+
+ case expr_concat_dclist: {
+ /* Operands are compressed domain-name lists ("Dc" format)
+ * Fetch both compressed lists then call concat_dclists which
+ * combines them into a single compressed list. */
+ memset(&data, 0, sizeof data);
+ int outcome = 0;
+ s0 = evaluate_data_expression(&data, packet, lease,
+ client_state,
+ in_options, cfg_options, scope,
+ expr->data.concat[0], MDL);
+
+ memset (&other, 0, sizeof other);
+ s1 = evaluate_data_expression (&other, packet, lease,
+ client_state,
+ in_options, cfg_options, scope,
+ expr->data.concat[1], MDL);
+
+ if (s0 && s1) {
+ outcome = concat_dclists(result, &data, &other);
+ if (outcome == 0) {
+ log_error ("data: concat_dclist failed");
+ }
+ }
+
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: concat_dclists (%s, %s) = %s",
+ (s0 ? print_hex_1(data.len, data.data, data.len)
+ : "NULL"),
+ (s1 ? print_hex_2(other.len, other.data, other.len)
+ : "NULL"),
+ (((s0 && s1) && result->len > 0)
+ ? print_hex_3 (result->len, result->data, result->len)
+ : "NULL"));
+#endif
+ if (s0)
+ data_string_forget (&data, MDL);
+
+ if (s1)
+ data_string_forget (&other, MDL);
+
+ return (outcome);
+ } /* expr_concat_dclist */
+
+ case expr_check:
+ case expr_equal:
+ case expr_not_equal:
+ case expr_regex_match:
+ case expr_iregex_match:
+ case expr_and:
+ case expr_or:
+ case expr_not:
+ case expr_match:
+ case expr_static:
+ case expr_known:
+ case expr_none:
+ case expr_exists:
+ case expr_variable_exists:
+ log_error ("Boolean opcode in evaluate_data_expression: %d",
+ expr -> op);
+ return 0;
+
+ case expr_extract_int8:
+ case expr_extract_int16:
+ case expr_extract_int32:
+ case expr_const_int:
+ case expr_lease_time:
+ case expr_dns_transaction:
+ case expr_add:
+ case expr_subtract:
+ case expr_multiply:
+ case expr_divide:
+ case expr_remainder:
+ case expr_binary_and:
+ case expr_binary_or:
+ case expr_binary_xor:
+ case expr_client_state:
+ log_error ("Numeric opcode in evaluate_data_expression: %d",
+ expr -> op);
+ return 0;
+
+ case expr_ns_add:
+ case expr_ns_delete:
+ case expr_ns_exists:
+ case expr_ns_not_exists:
+ log_error ("dns update opcode in evaluate_data_expression: %d",
+ expr -> op);
+ return 0;
+
+ case expr_function:
+ log_error ("function definition in evaluate_data_expression");
+ return 0;
+
+ case expr_arg:
+ break;
+ }
+
+ log_error ("Bogus opcode in evaluate_data_expression: %d", expr -> op);
+ return 0;
+}
+
+int evaluate_numeric_expression (result, packet, lease, client_state,
+ in_options, cfg_options, scope, expr)
+ unsigned long *result;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *cfg_options;
+ struct binding_scope **scope;
+ struct expression *expr;
+{
+ struct data_string data;
+ int status, sleft, sright;
+#if defined (NSUPDATE_OLD)
+ ns_updrec *nut;
+ ns_updque uq;
+ struct expression *cur, *next;
+#endif
+
+ struct binding *binding;
+ struct binding_value *bv;
+ unsigned long ileft, iright;
+ int rc = 0;
+
+ switch (expr -> op) {
+ case expr_check:
+ case expr_equal:
+ case expr_not_equal:
+ case expr_regex_match:
+ case expr_iregex_match:
+ case expr_and:
+ case expr_or:
+ case expr_not:
+ case expr_match:
+ case expr_static:
+ case expr_known:
+ case expr_none:
+ case expr_exists:
+ case expr_variable_exists:
+ log_error ("Boolean opcode in evaluate_numeric_expression: %d",
+ expr -> op);
+ return 0;
+
+ case expr_substring:
+ case expr_suffix:
+ case expr_lcase:
+ case expr_ucase:
+ case expr_option:
+ case expr_hardware:
+ case expr_const_data:
+ case expr_packet:
+ case expr_concat:
+ case expr_encapsulate:
+ case expr_host_lookup:
+ case expr_encode_int8:
+ case expr_encode_int16:
+ case expr_encode_int32:
+ case expr_binary_to_ascii:
+ case expr_reverse:
+ case expr_filename:
+ case expr_sname:
+ case expr_pick_first_value:
+ case expr_host_decl_name:
+ case expr_config_option:
+ case expr_leased_address:
+ case expr_null:
+ case expr_gethostname:
+ log_error ("Data opcode in evaluate_numeric_expression: %d",
+ expr -> op);
+ return 0;
+
+ case expr_extract_int8:
+ memset (&data, 0, sizeof data);
+ status = evaluate_data_expression
+ (&data, packet, lease, client_state, in_options,
+ cfg_options, scope, expr -> data.extract_int, MDL);
+ if (status)
+ *result = data.data [0];
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("num: extract_int8 (%s) = %s",
+ status ? print_hex_1 (data.len, data.data, 60) : "NULL",
+ status ? print_dec_1 (*result) : "NULL" );
+#endif
+ if (status) data_string_forget (&data, MDL);
+ return status;
+
+ case expr_extract_int16:
+ memset(&data, 0, sizeof(data));
+ status = (evaluate_data_expression
+ (&data, packet, lease, client_state, in_options,
+ cfg_options, scope, expr->data.extract_int, MDL));
+ if (status && data.len >= 2) {
+ *result = getUShort(data.data);
+ rc = 1;
+ }
+#if defined (DEBUG_EXPRESSIONS)
+ if (rc == 1) {
+ log_debug("num: extract_int16 (%s) = %ld",
+ print_hex_1(data.len, data.data, 60),
+ *result);
+ } else {
+ log_debug("num: extract_int16 (NULL) = NULL");
+ }
+#endif
+ if (status)
+ data_string_forget(&data, MDL);
+
+ return (rc);
+
+ case expr_extract_int32:
+ memset (&data, 0, sizeof data);
+ status = (evaluate_data_expression
+ (&data, packet, lease, client_state, in_options,
+ cfg_options, scope, expr -> data.extract_int, MDL));
+ if (status && data.len >= 4) {
+ *result = getULong (data.data);
+ rc = 1;
+ }
+#if defined (DEBUG_EXPRESSIONS)
+ if (rc == 1) {
+ log_debug ("num: extract_int32 (%s) = %ld",
+ print_hex_1 (data.len, data.data, 60),
+ *result);
+ } else {
+ log_debug ("num: extract_int32 (NULL) = NULL");
+ }
+#endif
+ if (status) data_string_forget (&data, MDL);
+ return (rc);
+
+ case expr_const_int:
+ *result = expr -> data.const_int;
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("number: CONSTANT = %ld", *result);
+#endif
+ return 1;
+
+ case expr_lease_time:
+ if (!lease) {
+ log_error("data: leased_lease: not available");
+ return (0);
+ }
+ if (lease->ends < cur_time) {
+ log_error("%s %lu when it is now %lu",
+ "data: lease_time: lease ends at",
+ (long)(lease->ends), (long)cur_time);
+ return (0);
+ }
+ *result = lease->ends - cur_time;
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug("number: lease-time = (%lu - %lu) = %ld",
+ (long unsigned)lease->ends,
+ (long unsigned)cur_time, *result);
+#endif
+ return (1);
+
+ case expr_dns_transaction:
+#if !defined (NSUPDATE_OLD)
+ return 0;
+#else
+ if (!resolver_inited) {
+ minires_ninit (&resolver_state);
+ resolver_inited = 1;
+ resolver_state.retrans = 1;
+ resolver_state.retry = 1;
+ }
+ ISC_LIST_INIT (uq);
+ cur = expr;
+ do {
+ next = cur -> data.dns_transaction.cdr;
+ nut = 0;
+ status = (evaluate_dns_expression
+ (&nut, packet,
+ lease, client_state, in_options, cfg_options,
+ scope, cur -> data.dns_transaction.car));
+ if (!status)
+ goto dns_bad;
+ ISC_LIST_APPEND (uq, nut, r_link);
+ cur = next;
+ } while (next);
+
+ /* Do the update and record the error code, if there was
+ an error; otherwise set it to NOERROR. */
+ *result = minires_nupdate (&resolver_state,
+ ISC_LIST_HEAD (uq));
+ status = 1;
+
+ print_dns_status ((int)*result, &uq);
+
+ dns_bad:
+ while (!ISC_LIST_EMPTY (uq)) {
+ ns_updrec *tmp = ISC_LIST_HEAD (uq);
+ ISC_LIST_UNLINK (uq, tmp, r_link);
+ if (tmp -> r_data_ephem) {
+ dfree (tmp -> r_data_ephem, MDL);
+ tmp -> r_data = (unsigned char *)0;
+ tmp -> r_data_ephem = (unsigned char *)0;
+ }
+ minires_freeupdrec (tmp);
+ }
+ return status;
+#endif /* NSUPDATE_OLD */
+
+ case expr_variable_reference:
+ if (scope && *scope) {
+ binding = find_binding (*scope, expr -> data.variable);
+
+ if (binding && binding -> value) {
+ if (binding -> value -> type == binding_numeric) {
+ *result = binding -> value -> value.intval;
+ status = 1;
+ } else {
+ log_error ("binding type %d in %s.",
+ binding -> value -> type,
+ "evaluate_numeric_expression");
+ status = 0;
+ }
+ } else
+ status = 0;
+ } else
+ status = 0;
+#if defined (DEBUG_EXPRESSIONS)
+ if (status)
+ log_debug ("numeric: %s = %ld",
+ expr -> data.variable, *result);
+ else
+ log_debug ("numeric: %s = NULL",
+ expr -> data.variable);
+#endif
+ return status;
+
+ case expr_funcall:
+ bv = (struct binding_value *)0;
+ status = evaluate_expression (&bv, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope, expr, MDL);
+ if (status) {
+ if (bv -> type != binding_numeric)
+ log_error ("%s() returned type %d in %s.",
+ expr -> data.funcall.name,
+ bv -> type,
+ "evaluate_numeric_expression");
+ else
+ *result = bv -> value.intval;
+ binding_value_dereference (&bv, MDL);
+ }
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("data: %s = %ld", expr -> data.funcall.name,
+ status ? *result : 0);
+#endif
+ break;
+
+ case expr_add:
+ sleft = evaluate_numeric_expression (&ileft, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [0]);
+ sright = evaluate_numeric_expression (&iright, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [1]);
+
+#if defined (DEBUG_EXPRESSIONS)
+ if (sleft && sright)
+ log_debug ("num: %ld + %ld = %ld",
+ ileft, iright, ileft + iright);
+ else if (sleft)
+ log_debug ("num: %ld + NULL = NULL", ileft);
+ else
+ log_debug ("num: NULL + %ld = NULL", iright);
+#endif
+ if (sleft && sright) {
+ *result = ileft + iright;
+ return 1;
+ }
+ return 0;
+
+ case expr_subtract:
+ sleft = evaluate_numeric_expression (&ileft, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [0]);
+ sright = evaluate_numeric_expression (&iright, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [1]);
+
+#if defined (DEBUG_EXPRESSIONS)
+ if (sleft && sright)
+ log_debug ("num: %ld - %ld = %ld",
+ ileft, iright, ileft - iright);
+ else if (sleft)
+ log_debug ("num: %ld - NULL = NULL", ileft);
+ else
+ log_debug ("num: NULL - %ld = NULL", iright);
+#endif
+ if (sleft && sright) {
+ *result = ileft - iright;
+ return 1;
+ }
+ return 0;
+
+ case expr_multiply:
+ sleft = evaluate_numeric_expression (&ileft, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [0]);
+ sright = evaluate_numeric_expression (&iright, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [1]);
+
+#if defined (DEBUG_EXPRESSIONS)
+ if (sleft && sright)
+ log_debug ("num: %ld * %ld = %ld",
+ ileft, iright, ileft * iright);
+ else if (sleft)
+ log_debug ("num: %ld * NULL = NULL", ileft);
+ else
+ log_debug ("num: NULL * %ld = NULL", iright);
+#endif
+ if (sleft && sright) {
+ *result = ileft * iright;
+ return 1;
+ }
+ return 0;
+
+ case expr_divide:
+ sleft = evaluate_numeric_expression (&ileft, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [0]);
+ sright = evaluate_numeric_expression (&iright, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [1]);
+
+#if defined (DEBUG_EXPRESSIONS)
+ if (sleft && sright) {
+ if (iright != 0)
+ log_debug ("num: %ld / %ld = %ld",
+ ileft, iright, ileft / iright);
+ else
+ log_debug ("num: %ld / %ld = NULL",
+ ileft, iright);
+ } else if (sleft)
+ log_debug ("num: %ld / NULL = NULL", ileft);
+ else
+ log_debug ("num: NULL / %ld = NULL", iright);
+#endif
+ if (sleft && sright && iright) {
+ *result = ileft / iright;
+ return 1;
+ }
+ return 0;
+
+ case expr_remainder:
+ sleft = evaluate_numeric_expression (&ileft, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [0]);
+ sright = evaluate_numeric_expression (&iright, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [1]);
+
+#if defined (DEBUG_EXPRESSIONS)
+ if (sleft && sright) {
+ if (iright != 0)
+ log_debug ("num: %ld %% %ld = %ld",
+ ileft, iright, ileft % iright);
+ else
+ log_debug ("num: %ld %% %ld = NULL",
+ ileft, iright);
+ } else if (sleft)
+ log_debug ("num: %ld %% NULL = NULL", ileft);
+ else
+ log_debug ("num: NULL %% %ld = NULL", iright);
+#endif
+ if (sleft && sright && iright) {
+ *result = ileft % iright;
+ return 1;
+ }
+ return 0;
+
+ case expr_binary_and:
+ sleft = evaluate_numeric_expression (&ileft, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [0]);
+ sright = evaluate_numeric_expression (&iright, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [1]);
+
+#if defined (DEBUG_EXPRESSIONS)
+ if (sleft && sright)
+ log_debug ("num: %ld | %ld = %ld",
+ ileft, iright, ileft & iright);
+ else if (sleft)
+ log_debug ("num: %ld & NULL = NULL", ileft);
+ else
+ log_debug ("num: NULL & %ld = NULL", iright);
+#endif
+ if (sleft && sright) {
+ *result = ileft & iright;
+ return 1;
+ }
+ return 0;
+
+ case expr_binary_or:
+ sleft = evaluate_numeric_expression (&ileft, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [0]);
+ sright = evaluate_numeric_expression (&iright, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [1]);
+
+#if defined (DEBUG_EXPRESSIONS)
+ if (sleft && sright)
+ log_debug ("num: %ld | %ld = %ld",
+ ileft, iright, ileft | iright);
+ else if (sleft)
+ log_debug ("num: %ld | NULL = NULL", ileft);
+ else
+ log_debug ("num: NULL | %ld = NULL", iright);
+#endif
+ if (sleft && sright) {
+ *result = ileft | iright;
+ return 1;
+ }
+ return 0;
+
+ case expr_binary_xor:
+ sleft = evaluate_numeric_expression (&ileft, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [0]);
+ sright = evaluate_numeric_expression (&iright, packet, lease,
+ client_state,
+ in_options, cfg_options,
+ scope,
+ expr -> data.and [1]);
+
+#if defined (DEBUG_EXPRESSIONS)
+ if (sleft && sright)
+ log_debug ("num: %ld ^ %ld = %ld",
+ ileft, iright, ileft ^ iright);
+ else if (sleft)
+ log_debug ("num: %ld ^ NULL = NULL", ileft);
+ else
+ log_debug ("num: NULL ^ %ld = NULL", iright);
+#endif
+ if (sleft && sright) {
+ *result = ileft ^ iright;
+ return 1;
+ }
+ return 0;
+
+ case expr_client_state:
+ if (client_state) {
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("num: client-state = %d",
+ client_state -> state);
+#endif
+ *result = client_state -> state;
+ return 1;
+ } else {
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug ("num: client-state = NULL");
+#endif
+ return 0;
+ }
+
+ case expr_ns_add:
+ case expr_ns_delete:
+ case expr_ns_exists:
+ case expr_ns_not_exists:
+ log_error ("dns opcode in evaluate_numeric_expression: %d",
+ expr -> op);
+ return 0;
+
+ case expr_function:
+ log_error ("function definition in evaluate_numeric_expr");
+ return 0;
+
+ case expr_arg:
+ break;
+
+ default:
+ log_fatal("Impossible case at %s:%d. Undefined operator "
+ "%d.", MDL, expr->op);
+ break;
+ }
+
+ log_error ("evaluate_numeric_expression: bogus opcode %d", expr -> op);
+ return 0;
+}
+
+/*
+ * Return data hanging off of an option cache structure, or if there
+ * isn't any, evaluate the expression hanging off of it and return the
+ * result of that evaluation. There should never be both an expression
+ * and a valid data_string.
+ *
+ * returns 0 if there wasn't an expression or it couldn't be evaluated
+ * returns non-zero if there was an expression or string that was evaluated
+ * When it returns zero the arguements, in particualr resutl, should not
+ * be modified
+ */
+
+int evaluate_option_cache (result, packet, lease, client_state,
+ in_options, cfg_options, scope, oc, file, line)
+ struct data_string *result;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *cfg_options;
+ struct binding_scope **scope;
+ struct option_cache *oc;
+ const char *file;
+ int line;
+{
+ if (oc->data.data != NULL) {
+ data_string_copy (result, &oc -> data, file, line);
+ return 1;
+ }
+ if (!oc -> expression)
+ return 0;
+ return evaluate_data_expression (result, packet, lease, client_state,
+ in_options, cfg_options, scope,
+ oc -> expression, file, line);
+}
+
+/* Evaluate an option cache and extract a boolean from the result.
+ * The boolean option cache is actually a trinary value where:
+ *
+ * 0 = return 0, ignore parameter 0 (also the case for no data)
+ * 1 = return 1, ignore parameter 0
+ * 2 = return 0, ignore parameter 1
+ *
+ * This supports both classic boolean flags on/off as well as the
+ * allow/deny/ignore keywords
+*/
+int evaluate_boolean_option_cache (ignorep, packet,
+ lease, client_state, in_options,
+ cfg_options, scope, oc, file, line)
+ int *ignorep;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *cfg_options;
+ struct binding_scope **scope;
+ struct option_cache *oc;
+ const char *file;
+ int line;
+{
+ int result = 0;
+ if (ignorep)
+ *ignorep = 0;
+
+ /* Only attempt to evaluate if option_cache is not null. This permits
+ * us to be called with option_lookup() as an argument. */
+ if (oc && in_options) {
+ struct data_string ds;
+
+ memset(&ds, 0, sizeof ds);
+ if (evaluate_option_cache(&ds, packet,
+ lease, client_state, in_options,
+ cfg_options, scope, oc, file,
+ line)) {
+ /* We have a value for the option set result and
+ * ignore parameter accordingly. */
+ if (ds.len) {
+ if (ds.data[0] == 1)
+ result = 1;
+ else if ((ds.data[0] == 2) && (ignorep != NULL))
+ *ignorep = 1;
+ }
+
+ data_string_forget(&ds, MDL);
+ }
+ }
+
+ return (result);
+}
+
+/* Evaluate a boolean expression and return the result of the evaluation,
+ or FALSE if it failed. */
+
+int evaluate_boolean_expression_result (ignorep, packet, lease, client_state,
+ in_options, cfg_options, scope, expr)
+ int *ignorep;
+ struct packet *packet;
+ struct lease *lease;
+ struct client_state *client_state;
+ struct option_state *in_options;
+ struct option_state *cfg_options;
+ struct binding_scope **scope;
+ struct expression *expr;
+{
+ int result;
+
+ /* So that we can be called with option_lookup as an argument. */
+ if (!expr)
+ return 0;
+
+ if (!evaluate_boolean_expression (&result, packet, lease, client_state,
+ in_options, cfg_options,
+ scope, expr))
+ return 0;
+
+ if (result == 2) {
+ *ignorep = 1;
+ result = 0;
+ } else
+ *ignorep = 0;
+ return result;
+}
+
+
+/* Dereference an expression node, and if the reference count goes to zero,
+ dereference any data it refers to, and then free it. */
+void expression_dereference (eptr, file, line)
+ struct expression **eptr;
+ const char *file;
+ int line;
+{
+ struct expression *expr = *eptr;
+
+ /* Zero the pointer. */
+ *eptr = (struct expression *)0;
+
+ /* Decrement the reference count. If it's nonzero, we're
+ done. */
+ --(expr -> refcnt);
+ rc_register (file, line, eptr, expr, expr -> refcnt, 1, RC_MISC);
+ if (expr -> refcnt > 0)
+ return;
+ if (expr -> refcnt < 0) {
+ log_error ("%s(%d): negative refcnt!", file, line);
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history (expr);
+#endif
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return;
+#endif
+ }
+
+ /* Dereference subexpressions. */
+ switch (expr -> op) {
+ /* All the binary operators can be handled the same way. */
+ case expr_equal:
+ case expr_not_equal:
+ case expr_regex_match:
+ case expr_iregex_match:
+ case expr_concat:
+ case expr_and:
+ case expr_or:
+ case expr_add:
+ case expr_subtract:
+ case expr_multiply:
+ case expr_divide:
+ case expr_remainder:
+ case expr_binary_and:
+ case expr_binary_or:
+ case expr_binary_xor:
+ case expr_client_state:
+ if (expr -> data.equal [0])
+ expression_dereference (&expr -> data.equal [0],
+ file, line);
+ if (expr -> data.equal [1])
+ expression_dereference (&expr -> data.equal [1],
+ file, line);
+ break;
+
+ case expr_substring:
+ if (expr -> data.substring.expr)
+ expression_dereference (&expr -> data.substring.expr,
+ file, line);
+ if (expr -> data.substring.offset)
+ expression_dereference (&expr -> data.substring.offset,
+ file, line);
+ if (expr -> data.substring.len)
+ expression_dereference (&expr -> data.substring.len,
+ file, line);
+ break;
+
+ case expr_suffix:
+ if (expr -> data.suffix.expr)
+ expression_dereference (&expr -> data.suffix.expr,
+ file, line);
+ if (expr -> data.suffix.len)
+ expression_dereference (&expr -> data.suffix.len,
+ file, line);
+ break;
+
+ case expr_lcase:
+ if (expr->data.lcase)
+ expression_dereference(&expr->data.lcase, MDL);
+ break;
+
+ case expr_ucase:
+ if (expr->data.ucase)
+ expression_dereference(&expr->data.ucase, MDL);
+ break;
+
+ case expr_not:
+ if (expr -> data.not)
+ expression_dereference (&expr -> data.not, file, line);
+ break;
+
+ case expr_packet:
+ if (expr -> data.packet.offset)
+ expression_dereference (&expr -> data.packet.offset,
+ file, line);
+ if (expr -> data.packet.len)
+ expression_dereference (&expr -> data.packet.len,
+ file, line);
+ break;
+
+ case expr_extract_int8:
+ case expr_extract_int16:
+ case expr_extract_int32:
+ if (expr -> data.extract_int)
+ expression_dereference (&expr -> data.extract_int,
+ file, line);
+ break;
+
+ case expr_encode_int8:
+ case expr_encode_int16:
+ case expr_encode_int32:
+ if (expr -> data.encode_int)
+ expression_dereference (&expr -> data.encode_int,
+ file, line);
+ break;
+
+ case expr_encapsulate:
+ case expr_const_data:
+ data_string_forget (&expr -> data.const_data, file, line);
+ break;
+
+ case expr_host_lookup:
+ if (expr -> data.host_lookup)
+ dns_host_entry_dereference (&expr -> data.host_lookup,
+ file, line);
+ break;
+
+ case expr_binary_to_ascii:
+ if (expr -> data.b2a.base)
+ expression_dereference (&expr -> data.b2a.base,
+ file, line);
+ if (expr -> data.b2a.width)
+ expression_dereference (&expr -> data.b2a.width,
+ file, line);
+ if (expr -> data.b2a.separator)
+ expression_dereference (&expr -> data.b2a.separator,
+ file, line);
+ if (expr -> data.b2a.buffer)
+ expression_dereference (&expr -> data.b2a.buffer,
+ file, line);
+ break;
+
+ case expr_pick_first_value:
+ if (expr -> data.pick_first_value.car)
+ expression_dereference (&expr -> data.pick_first_value.car,
+ file, line);
+ if (expr -> data.pick_first_value.cdr)
+ expression_dereference (&expr -> data.pick_first_value.cdr,
+ file, line);
+ break;
+
+ case expr_reverse:
+ if (expr -> data.reverse.width)
+ expression_dereference (&expr -> data.reverse.width,
+ file, line);
+ if (expr -> data.reverse.buffer)
+ expression_dereference
+ (&expr -> data.reverse.buffer, file, line);
+ break;
+
+ case expr_dns_transaction:
+ if (expr -> data.dns_transaction.car)
+ expression_dereference (&expr -> data.dns_transaction.car,
+ file, line);
+ if (expr -> data.dns_transaction.cdr)
+ expression_dereference (&expr -> data.dns_transaction.cdr,
+ file, line);
+ break;
+
+ case expr_ns_add:
+ if (expr -> data.ns_add.rrname)
+ expression_dereference (&expr -> data.ns_add.rrname,
+ file, line);
+ if (expr -> data.ns_add.rrdata)
+ expression_dereference (&expr -> data.ns_add.rrdata,
+ file, line);
+ if (expr -> data.ns_add.ttl)
+ expression_dereference (&expr -> data.ns_add.ttl,
+ file, line);
+ break;
+
+ case expr_ns_delete:
+ case expr_ns_exists:
+ case expr_ns_not_exists:
+ if (expr -> data.ns_delete.rrname)
+ expression_dereference (&expr -> data.ns_delete.rrname,
+ file, line);
+ if (expr -> data.ns_delete.rrdata)
+ expression_dereference (&expr -> data.ns_delete.rrdata,
+ file, line);
+ break;
+
+ case expr_variable_reference:
+ case expr_variable_exists:
+ if (expr -> data.variable)
+ dfree (expr -> data.variable, file, line);
+ break;
+
+ case expr_funcall:
+ if (expr -> data.funcall.name)
+ dfree (expr -> data.funcall.name, file, line);
+ if (expr -> data.funcall.arglist)
+ expression_dereference (&expr -> data.funcall.arglist,
+ file, line);
+ break;
+
+ case expr_arg:
+ if (expr -> data.arg.val)
+ expression_dereference (&expr -> data.arg.val,
+ file, line);
+ if (expr -> data.arg.next)
+ expression_dereference (&expr -> data.arg.next,
+ file, line);
+ break;
+
+ case expr_function:
+ fundef_dereference (&expr -> data.func, file, line);
+ break;
+
+ /* No subexpressions. */
+ case expr_leased_address:
+ case expr_lease_time:
+ case expr_filename:
+ case expr_sname:
+ case expr_const_int:
+ case expr_check:
+ case expr_option:
+ case expr_hardware:
+ case expr_exists:
+ case expr_known:
+ case expr_null:
+ case expr_gethostname:
+ break;
+
+ default:
+ break;
+ }
+ free_expression (expr, MDL);
+}
+
+int is_dns_expression (expr)
+ struct expression *expr;
+{
+ return (expr -> op == expr_ns_add ||
+ expr -> op == expr_ns_delete ||
+ expr -> op == expr_ns_exists ||
+ expr -> op == expr_ns_not_exists);
+}
+
+int is_boolean_expression (expr)
+ struct expression *expr;
+{
+ return (expr -> op == expr_check ||
+ expr -> op == expr_exists ||
+ expr -> op == expr_variable_exists ||
+ expr -> op == expr_equal ||
+ expr -> op == expr_not_equal ||
+ expr->op == expr_regex_match ||
+ expr->op == expr_iregex_match ||
+ expr -> op == expr_and ||
+ expr -> op == expr_or ||
+ expr -> op == expr_not ||
+ expr -> op == expr_known ||
+ expr -> op == expr_static);
+}
+
+int is_data_expression (expr)
+ struct expression *expr;
+{
+ return (expr->op == expr_substring ||
+ expr->op == expr_suffix ||
+ expr->op == expr_lcase ||
+ expr->op == expr_ucase ||
+ expr->op == expr_option ||
+ expr->op == expr_hardware ||
+ expr->op == expr_const_data ||
+ expr->op == expr_packet ||
+ expr->op == expr_concat ||
+ expr->op == expr_encapsulate ||
+ expr->op == expr_encode_int8 ||
+ expr->op == expr_encode_int16 ||
+ expr->op == expr_encode_int32 ||
+ expr->op == expr_host_lookup ||
+ expr->op == expr_binary_to_ascii ||
+ expr->op == expr_filename ||
+ expr->op == expr_sname ||
+ expr->op == expr_reverse ||
+ expr->op == expr_pick_first_value ||
+ expr->op == expr_host_decl_name ||
+ expr->op == expr_leased_address ||
+ expr->op == expr_config_option ||
+ expr->op == expr_null ||
+ expr->op == expr_gethostname);
+}
+
+int is_numeric_expression (expr)
+ struct expression *expr;
+{
+ return (expr -> op == expr_extract_int8 ||
+ expr -> op == expr_extract_int16 ||
+ expr -> op == expr_extract_int32 ||
+ expr -> op == expr_const_int ||
+ expr -> op == expr_lease_time ||
+ expr -> op == expr_dns_transaction ||
+ expr -> op == expr_add ||
+ expr -> op == expr_subtract ||
+ expr -> op == expr_multiply ||
+ expr -> op == expr_divide ||
+ expr -> op == expr_remainder ||
+ expr -> op == expr_binary_and ||
+ expr -> op == expr_binary_or ||
+ expr -> op == expr_binary_xor ||
+ expr -> op == expr_client_state);
+}
+
+int is_compound_expression (expr)
+ struct expression *expr;
+{
+ return (expr -> op == expr_ns_add ||
+ expr -> op == expr_ns_delete ||
+ expr -> op == expr_ns_exists ||
+ expr -> op == expr_ns_not_exists ||
+ expr -> op == expr_substring ||
+ expr -> op == expr_suffix ||
+ expr -> op == expr_option ||
+ expr -> op == expr_concat ||
+ expr -> op == expr_encode_int8 ||
+ expr -> op == expr_encode_int16 ||
+ expr -> op == expr_encode_int32 ||
+ expr -> op == expr_binary_to_ascii ||
+ expr -> op == expr_reverse ||
+ expr -> op == expr_pick_first_value ||
+ expr -> op == expr_config_option ||
+ expr -> op == expr_extract_int8 ||
+ expr -> op == expr_extract_int16 ||
+ expr -> op == expr_extract_int32 ||
+ expr -> op == expr_dns_transaction);
+}
+
+static int op_val (enum expr_op);
+
+static int op_val (op)
+ enum expr_op op;
+{
+ switch (op) {
+ case expr_none:
+ case expr_match:
+ case expr_static:
+ case expr_check:
+ case expr_substring:
+ case expr_suffix:
+ case expr_lcase:
+ case expr_ucase:
+ case expr_concat:
+ case expr_encapsulate:
+ case expr_host_lookup:
+ case expr_not:
+ case expr_option:
+ case expr_hardware:
+ case expr_packet:
+ case expr_const_data:
+ case expr_extract_int8:
+ case expr_extract_int16:
+ case expr_extract_int32:
+ case expr_encode_int8:
+ case expr_encode_int16:
+ case expr_encode_int32:
+ case expr_const_int:
+ case expr_exists:
+ case expr_variable_exists:
+ case expr_known:
+ case expr_binary_to_ascii:
+ case expr_reverse:
+ case expr_filename:
+ case expr_sname:
+ case expr_pick_first_value:
+ case expr_host_decl_name:
+ case expr_config_option:
+ case expr_leased_address:
+ case expr_lease_time:
+ case expr_dns_transaction:
+ case expr_null:
+ case expr_variable_reference:
+ case expr_ns_add:
+ case expr_ns_delete:
+ case expr_ns_exists:
+ case expr_ns_not_exists:
+ case expr_arg:
+ case expr_funcall:
+ case expr_function:
+ /* XXXDPN: Need to assign sane precedences to these. */
+ case expr_binary_and:
+ case expr_binary_or:
+ case expr_binary_xor:
+ case expr_client_state:
+ case expr_gethostname:
+ case expr_concat_dclist:
+ return 100;
+
+ case expr_equal:
+ case expr_not_equal:
+ case expr_regex_match:
+ case expr_iregex_match:
+ return 4;
+
+ case expr_or:
+ case expr_and:
+ return 3;
+
+ case expr_add:
+ case expr_subtract:
+ return 2;
+
+ case expr_multiply:
+ case expr_divide:
+ case expr_remainder:
+ return 1;
+ }
+ return 100;
+}
+
+int op_precedence (op1, op2)
+ enum expr_op op1, op2;
+{
+ return op_val (op1) - op_val (op2);
+}
+
+enum expression_context expression_context (struct expression *expr)
+{
+ if (is_data_expression (expr))
+ return context_data;
+ if (is_numeric_expression (expr))
+ return context_numeric;
+ if (is_boolean_expression (expr))
+ return context_boolean;
+ if (is_dns_expression (expr))
+ return context_dns;
+ return context_any;
+}
+
+enum expression_context op_context (op)
+ enum expr_op op;
+{
+ switch (op) {
+/* XXX Why aren't these specific? */
+ case expr_none:
+ case expr_match:
+ case expr_static:
+ case expr_check:
+ case expr_substring:
+ case expr_suffix:
+ case expr_lcase:
+ case expr_ucase:
+ case expr_concat:
+ case expr_encapsulate:
+ case expr_host_lookup:
+ case expr_not:
+ case expr_option:
+ case expr_hardware:
+ case expr_packet:
+ case expr_const_data:
+ case expr_extract_int8:
+ case expr_extract_int16:
+ case expr_extract_int32:
+ case expr_encode_int8:
+ case expr_encode_int16:
+ case expr_encode_int32:
+ case expr_const_int:
+ case expr_exists:
+ case expr_variable_exists:
+ case expr_known:
+ case expr_binary_to_ascii:
+ case expr_reverse:
+ case expr_filename:
+ case expr_sname:
+ case expr_pick_first_value:
+ case expr_host_decl_name:
+ case expr_config_option:
+ case expr_leased_address:
+ case expr_lease_time:
+ case expr_null:
+ case expr_variable_reference:
+ case expr_ns_add:
+ case expr_ns_delete:
+ case expr_ns_exists:
+ case expr_ns_not_exists:
+ case expr_dns_transaction:
+ case expr_arg:
+ case expr_funcall:
+ case expr_function:
+ case expr_gethostname:
+ case expr_concat_dclist:
+ return context_any;
+
+ case expr_equal:
+ case expr_not_equal:
+ case expr_regex_match:
+ case expr_iregex_match:
+ return context_data;
+
+ case expr_and:
+ return context_boolean;
+
+ case expr_or:
+ return context_boolean;
+
+ case expr_add:
+ case expr_subtract:
+ case expr_multiply:
+ case expr_divide:
+ case expr_remainder:
+ case expr_binary_and:
+ case expr_binary_or:
+ case expr_binary_xor:
+ case expr_client_state:
+ return context_numeric;
+ }
+ return context_any;
+}
+
+int write_expression (file, expr, col, indent, firstp)
+ FILE *file;
+ struct expression *expr;
+ int col;
+ int indent;
+ int firstp;
+{
+ struct expression *e;
+ const char *s;
+ char obuf [65];
+ int scol;
+ int width;
+
+ /* If this promises to be a fat expression, start a new line. */
+ if (!firstp && is_compound_expression (expr)) {
+ indent_spaces (file, indent);
+ col = indent;
+ }
+
+ switch (expr -> op) {
+ case expr_none:
+ col = token_print_indent (file, col, indent, "", "", "null");
+ break;
+
+ case expr_check:
+ col = token_print_indent (file, col, indent, "", "", "check");
+ col = token_print_indent_concat (file, col, indent,
+ " ", "", "\"",
+ expr -> data.check -> name,
+ "\"", (char *)0);
+ break;
+
+ case expr_regex_match:
+ s = "~=";
+ goto binary;
+
+ case expr_iregex_match:
+ s = "~~";
+ goto binary;
+
+ case expr_not_equal:
+ s = "!=";
+ goto binary;
+
+ case expr_equal:
+ s = "=";
+ binary:
+ col = write_expression (file, expr -> data.equal [0],
+ col, indent, 1);
+ col = token_print_indent (file, col, indent, " ", " ", s);
+ col = write_expression (file, expr -> data.equal [1],
+ col, indent + 2, 0);
+ break;
+
+ case expr_substring:
+ col = token_print_indent (file, col, indent, "", "",
+ "substring");
+ col = token_print_indent (file, col, indent, " ", "", "(");
+ scol = col;
+ col = write_expression (file, expr -> data.substring.expr,
+ col, scol, 1);
+ col = token_print_indent (file, col, indent, "", " ", ",");
+ col = write_expression (file, expr -> data.substring.offset,
+ col, indent, 0);
+ col = token_print_indent (file, col, scol, "", " ", ",");
+ col = write_expression (file, expr -> data.substring.len,
+ col, scol, 0);
+ col = token_print_indent (file, col, indent, "", "", ")");
+ break;
+
+ case expr_suffix:
+ col = token_print_indent (file, col, indent, "", "", "suffix");
+ col = token_print_indent (file, col, indent, " ", "", "(");
+ scol = col;
+ col = write_expression (file, expr -> data.suffix.expr,
+ col, scol, 1);
+ col = token_print_indent (file, col, scol, "", " ", ",");
+ col = write_expression (file, expr -> data.suffix.len,
+ col, scol, 0);
+ col = token_print_indent (file, col, indent, "", "", ")");
+ break;
+
+ case expr_lcase:
+ col = token_print_indent(file, col, indent, "", "", "lcase");
+ col = token_print_indent(file, col, indent, " ", "", "(");
+ scol = col;
+ col = write_expression(file, expr->data.lcase, col, scol, 1);
+ col = token_print_indent(file, col, indent, "", "", ")");
+ break;
+
+ case expr_ucase:
+ col = token_print_indent(file, col, indent, "", "", "ucase");
+ col = token_print_indent(file, col, indent, " ", "", "(");
+ scol = col;
+ col = write_expression(file, expr->data.ucase, col, scol, 1);
+ col = token_print_indent(file, col, indent, "", "", ")");
+ break;
+
+ case expr_concat:
+ e = expr;
+ col = token_print_indent (file, col, indent, "", "",
+ "concat");
+ col = token_print_indent (file, col, indent, " ", "", "(");
+ scol = col;
+ firstp = 1;
+ concat_again:
+ col = write_expression (file, e -> data.concat [0],
+ col, scol, firstp);
+ firstp = 0;
+ if (!e -> data.concat [1])
+ goto no_concat_cdr;
+ col = token_print_indent (file, col, scol, "", " ", ",");
+ if (e -> data.concat [1] -> op == expr_concat) {
+ e = e -> data.concat [1];
+ goto concat_again;
+ }
+ col = write_expression (file, e -> data.concat [1],
+ col, scol, 0);
+ no_concat_cdr:
+ col = token_print_indent (file, col, indent, "", "", ")");
+ break;
+
+ case expr_host_lookup:
+ col = token_print_indent (file, col, indent, "", "",
+ "gethostbyname");
+ col = token_print_indent (file, col, indent, " ", "", "(");
+ col = token_print_indent_concat
+ (file, col, indent, "", "",
+ "\"", expr -> data.host_lookup -> hostname, "\"",
+ (char *)0);
+ col = token_print_indent (file, col, indent, "", "", ")");
+ break;
+
+ case expr_add:
+ s = "+";
+ goto binary;
+
+ case expr_subtract:
+ s = "-";
+ goto binary;
+
+ case expr_multiply:
+ s = "*";
+ goto binary;
+
+ case expr_divide:
+ s = "/";
+ goto binary;
+
+ case expr_remainder:
+ s = "%";
+ goto binary;
+
+ case expr_binary_and:
+ s = "&";
+ goto binary;
+
+ case expr_binary_or:
+ s = "|";
+ goto binary;
+
+ case expr_binary_xor:
+ s = "^";
+ goto binary;
+
+ case expr_and:
+ s = "and";
+ goto binary;
+
+ case expr_or:
+ s = "or";
+ goto binary;
+
+ case expr_not:
+ col = token_print_indent (file, col, indent, "", " ", "not");
+ col = write_expression (file,
+ expr -> data.not, col, indent + 2, 1);
+ break;
+
+ case expr_option:
+ s = "option";
+
+ print_option_name:
+ col = token_print_indent (file, col, indent, "", "", s);
+
+ if (expr -> data.option -> universe != &dhcp_universe) {
+ col = token_print_indent (file, col, indent,
+ " ", "",
+ (expr -> data.option ->
+ universe -> name));
+ col = token_print_indent (file, col, indent, "", "",
+ ".");
+ col = token_print_indent (file, col, indent, "", "",
+ expr -> data.option -> name);
+ } else {
+ col = token_print_indent (file, col, indent, " ", "",
+ expr -> data.option -> name);
+ }
+ break;
+
+ case expr_hardware:
+ col = token_print_indent (file, col, indent, "", "",
+ "hardware");
+ break;
+
+ case expr_packet:
+ col = token_print_indent (file, col, indent, "", "",
+ "packet");
+ col = token_print_indent (file, col, indent, " ", "", "(");
+ scol = col;
+ col = write_expression (file, expr -> data.packet.offset,
+ col, indent, 1);
+ col = token_print_indent (file, col, scol, "", " ", ",");
+ col = write_expression (file, expr -> data.packet.len,
+ col, scol, 0);
+ col = token_print_indent (file, col, indent, "", "", ")");
+ break;
+
+ case expr_const_data:
+ col = token_indent_data_string (file, col, indent, "", "",
+ &expr -> data.const_data);
+ break;
+
+ case expr_extract_int8:
+ width = 8;
+ extract_int:
+ col = token_print_indent (file, col, indent, "", "",
+ "extract-int");
+ col = token_print_indent (file, col, indent, " ", "", "(");
+ scol = col;
+ col = write_expression (file, expr -> data.extract_int,
+ col, indent, 1);
+ col = token_print_indent (file, col, scol, "", " ", ",");
+ sprintf (obuf, "%d", width);
+ col = token_print_indent (file, col, scol, " ", "", obuf);
+ col = token_print_indent (file, col, indent, "", "", ")");
+ break;
+
+ case expr_extract_int16:
+ width = 16;
+ goto extract_int;
+
+ case expr_extract_int32:
+ width = 32;
+ goto extract_int;
+
+ case expr_encode_int8:
+ width = 8;
+ encode_int:
+ col = token_print_indent (file, col, indent, "", "",
+ "encode-int");
+ col = token_print_indent (file, col, indent, " ", "", "(");
+ scol = col;
+ col = write_expression (file, expr -> data.extract_int,
+ col, indent, 1);
+ col = token_print_indent (file, col, scol, "", " ", ",");
+ sprintf (obuf, "%d", width);
+ col = token_print_indent (file, col, scol, " ", "", obuf);
+ col = token_print_indent (file, col, indent, "", "",
+ ")");
+ break;
+
+ case expr_encode_int16:
+ width = 16;
+ goto encode_int;
+
+ case expr_encode_int32:
+ width = 32;
+ goto encode_int;
+
+ case expr_const_int:
+ sprintf (obuf, "%lu", expr -> data.const_int);
+ col = token_print_indent (file, col, indent, "", "", obuf);
+ break;
+
+ case expr_exists:
+ s = "exists";
+ goto print_option_name;
+
+ case expr_encapsulate:
+ col = token_print_indent (file, col, indent, "", "",
+ "encapsulate");
+ col = token_indent_data_string (file, col, indent, " ", "",
+ &expr -> data.encapsulate);
+ break;
+
+ case expr_known:
+ col = token_print_indent (file, col, indent, "", "", "known");
+ break;
+
+ case expr_reverse:
+ col = token_print_indent (file, col, indent, "", "",
+ "reverse");
+ col = token_print_indent (file, col, indent, " ", "", "(");
+ scol = col;
+ col = write_expression (file, expr -> data.reverse.width,
+ col, scol, 1);
+ col = token_print_indent (file, col, scol, "", " ", ",");
+ col = write_expression (file, expr -> data.reverse.buffer,
+ col, scol, 0);
+ col = token_print_indent (file, col, indent, "", "",
+ ")");
+ break;
+
+ case expr_leased_address:
+ col = token_print_indent (file, col, indent, "", "",
+ "leased-address");
+ break;
+
+ case expr_client_state:
+ col = token_print_indent (file, col, indent, "", "",
+ "client-state");
+ break;
+
+ case expr_binary_to_ascii:
+ col = token_print_indent (file, col, indent, "", "",
+ "binary-to-ascii");
+ col = token_print_indent (file, col, indent, " ", "",
+ "(");
+ scol = col;
+ col = write_expression (file, expr -> data.b2a.base,
+ col, scol, 1);
+ col = token_print_indent (file, col, scol, "", " ",
+ ",");
+ col = write_expression (file, expr -> data.b2a.width,
+ col, scol, 0);
+ col = token_print_indent (file, col, scol, "", " ",
+ ",");
+ col = write_expression (file, expr -> data.b2a.separator,
+ col, scol, 0);
+ col = token_print_indent (file, col, scol, "", " ",
+ ",");
+ col = write_expression (file, expr -> data.b2a.buffer,
+ col, scol, 0);
+ col = token_print_indent (file, col, indent, "", "",
+ ")");
+ break;
+
+ case expr_config_option:
+ s = "config-option";
+ goto print_option_name;
+
+ case expr_host_decl_name:
+ col = token_print_indent (file, col, indent, "", "",
+ "host-decl-name");
+ break;
+
+ case expr_pick_first_value:
+ e = expr;
+ col = token_print_indent (file, col, indent, "", "",
+ "concat");
+ col = token_print_indent (file, col, indent, " ", "",
+ "(");
+ scol = col;
+ firstp = 1;
+ pick_again:
+ col = write_expression (file,
+ e -> data.pick_first_value.car,
+ col, scol, firstp);
+ firstp = 0;
+ /* We're being very lisp-like right now - instead of
+ representing this expression as (first middle . last) we're
+ representing it as (first middle last), which means that the
+ tail cdr is always nil. Apologies to non-wisp-lizards - may
+ this obscure way of describing the problem motivate you to
+ learn more about the one true computing language. */
+ if (!e -> data.pick_first_value.cdr)
+ goto no_pick_cdr;
+ col = token_print_indent (file, col, scol, "", " ",
+ ",");
+ if (e -> data.pick_first_value.cdr -> op ==
+ expr_pick_first_value) {
+ e = e -> data.pick_first_value.cdr;
+ goto pick_again;
+ }
+ col = write_expression (file,
+ e -> data.pick_first_value.cdr,
+ col, scol, 0);
+ no_pick_cdr:
+ col = token_print_indent (file, col, indent, "", "",
+ ")");
+ break;
+
+ case expr_lease_time:
+ col = token_print_indent (file, col, indent, "", "",
+ "lease-time");
+ break;
+
+ case expr_dns_transaction:
+ col = token_print_indent (file, col, indent, "", "",
+ "ns-update");
+ col = token_print_indent (file, col, indent, " ", "",
+ "(");
+ scol = 0;
+ for (e = expr;
+ e && e -> op == expr_dns_transaction;
+ e = e -> data.dns_transaction.cdr) {
+ if (!scol) {
+ scol = col;
+ firstp = 1;
+ } else
+ firstp = 0;
+ col = write_expression (file,
+ e -> data.dns_transaction.car,
+ col, scol, firstp);
+ if (e -> data.dns_transaction.cdr)
+ col = token_print_indent (file, col, scol,
+ "", " ", ",");
+ }
+ if (e)
+ col = write_expression (file, e, col, scol, 0);
+ col = token_print_indent (file, col, indent, "", "", ")");
+ break;
+
+ case expr_ns_add:
+ col = token_print_indent (file, col, indent, "", "",
+ "update");
+ col = token_print_indent (file, col, indent, " ", "",
+ "(");
+ scol = col;
+ sprintf (obuf, "%d", expr -> data.ns_add.rrclass);
+ col = token_print_indent (file, col, scol, "", "", obuf);
+ col = token_print_indent (file, col, scol, "", " ",
+ ",");
+ sprintf (obuf, "%d", expr -> data.ns_add.rrtype);
+ col = token_print_indent (file, col, scol, "", "", obuf);
+ col = token_print_indent (file, col, scol, "", " ",
+ ",");
+ col = write_expression (file, expr -> data.ns_add.rrname,
+ col, scol, 0);
+ col = token_print_indent (file, col, scol, "", " ",
+ ",");
+ col = write_expression (file, expr -> data.ns_add.rrdata,
+ col, scol, 0);
+ col = token_print_indent (file, col, scol, "", " ",
+ ",");
+ col = write_expression (file, expr -> data.ns_add.ttl,
+ col, scol, 0);
+ col = token_print_indent (file, col, indent, "", "",
+ ")");
+ break;
+
+ case expr_ns_delete:
+ col = token_print_indent (file, col, indent, "", "",
+ "delete");
+ col = token_print_indent (file, col, indent, " ", "",
+ "(");
+ finish_ns_small:
+ scol = col;
+ sprintf (obuf, "%d", expr -> data.ns_add.rrclass);
+ col = token_print_indent (file, col, scol, "", "", obuf);
+ col = token_print_indent (file, col, scol, "", " ",
+ ",");
+ sprintf (obuf, "%d", expr -> data.ns_add.rrtype);
+ col = token_print_indent (file, col, scol, "", "", obuf);
+ col = token_print_indent (file, col, scol, "", " ",
+ ",");
+ col = write_expression (file, expr -> data.ns_add.rrname,
+ col, scol, 0);
+ col = token_print_indent (file, col, scol, "", " ",
+ ",");
+ col = write_expression (file, expr -> data.ns_add.rrdata,
+ col, scol, 0);
+ col = token_print_indent (file, col, indent, "", "",
+ ")");
+ break;
+
+ case expr_ns_exists:
+ col = token_print_indent (file, col, indent, "", "",
+ "exists");
+ col = token_print_indent (file, col, indent, " ", "",
+ "(");
+ goto finish_ns_small;
+
+ case expr_ns_not_exists:
+ col = token_print_indent (file, col, indent, "", "",
+ "not exists");
+ col = token_print_indent (file, col, indent, " ", "",
+ "(");
+ goto finish_ns_small;
+
+ case expr_static:
+ col = token_print_indent (file, col, indent, "", "",
+ "static");
+ break;
+
+ case expr_null:
+ col = token_print_indent (file, col, indent, "", "", "null");
+ break;
+
+ case expr_variable_reference:
+ col = token_print_indent (file, indent, indent, "", "",
+ expr -> data.variable);
+ break;
+
+ case expr_variable_exists:
+ col = token_print_indent (file, indent, indent, "", "",
+ "defined");
+ col = token_print_indent (file, col, indent, " ", "", "(");
+ col = token_print_indent (file, col, indent, "", "",
+ expr -> data.variable);
+ col = token_print_indent (file, col, indent, "", "", ")");
+ break;
+
+ case expr_gethostname:
+ col = token_print_indent(file, col, indent, "", "",
+ "gethostname()");
+ break;
+
+ case expr_funcall:
+ col = token_print_indent(file, indent, indent, "", "",
+ expr->data.funcall.name);
+ col = token_print_indent(file, col, indent, " ", "", "(");
+
+ firstp = 1;
+ e = expr->data.funcall.arglist;
+ while (e != NULL) {
+ if (!firstp)
+ col = token_print_indent(file, col, indent,
+ "", " ", ",");
+
+ col = write_expression(file, e->data.arg.val, col,
+ indent, firstp);
+ firstp = 0;
+ e = e->data.arg.next;
+ }
+
+ col = token_print_indent(file, col, indent, "", "", ")");
+ break;
+
+ default:
+ log_fatal ("invalid expression type in print_expression: %d",
+ expr -> op);
+ }
+ return col;
+}
+
+struct binding *find_binding (struct binding_scope *scope, const char *name)
+{
+ struct binding *bp;
+ struct binding_scope *s;
+
+ for (s = scope; s; s = s -> outer) {
+ for (bp = s -> bindings; bp; bp = bp -> next) {
+ if (!strcasecmp (name, bp -> name)) {
+ return bp;
+ }
+ }
+ }
+ return (struct binding *)0;
+}
+
+int free_bindings (struct binding_scope *scope, const char *file, int line)
+{
+ struct binding *bp, *next;
+
+ for (bp = scope -> bindings; bp; bp = next) {
+ next = bp -> next;
+ if (bp -> name)
+ dfree (bp -> name, file, line);
+ if (bp -> value)
+ binding_value_dereference (&bp -> value, file, line);
+ dfree (bp, file, line);
+ }
+ scope -> bindings = (struct binding *)0;
+ return 1;
+}
+
+int binding_scope_dereference (ptr, file, line)
+ struct binding_scope **ptr;
+ const char *file;
+ int line;
+{
+ struct binding_scope *binding_scope;
+
+ if (!ptr || !*ptr) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ binding_scope = *ptr;
+ *ptr = (struct binding_scope *)0;
+ --binding_scope -> refcnt;
+ rc_register (file, line, ptr,
+ binding_scope, binding_scope -> refcnt, 1, RC_MISC);
+ if (binding_scope -> refcnt > 0)
+ return 1;
+
+ if (binding_scope -> refcnt < 0) {
+ log_error ("%s(%d): negative refcnt!", file, line);
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history (binding_scope);
+#endif
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ free_bindings (binding_scope, file, line);
+ if (binding_scope -> outer)
+ binding_scope_dereference (&binding_scope -> outer, MDL);
+ dfree (binding_scope, file, line);
+ return 1;
+}
+
+int fundef_dereference (ptr, file, line)
+ struct fundef **ptr;
+ const char *file;
+ int line;
+{
+ struct fundef *bp;
+ struct string_list *sp, *next;
+
+ if ((ptr == NULL) || (*ptr == NULL)) {
+ log_error ("%s(%d): null pointer", file, line);
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+
+ bp = *ptr;
+ bp -> refcnt--;
+ rc_register (file, line, ptr, bp, bp -> refcnt, 1, RC_MISC);
+ if (bp -> refcnt < 0) {
+ log_error ("%s(%d): negative refcnt!", file, line);
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history (bp);
+#endif
+#if defined (POINTER_DEBUG)
+ abort ();
+#else
+ return 0;
+#endif
+ }
+ if (!bp -> refcnt) {
+ for (sp = bp -> args; sp; sp = next) {
+ next = sp -> next;
+ dfree (sp, file, line);
+ }
+ if (bp -> statements)
+ executable_statement_dereference (&bp -> statements,
+ file, line);
+ dfree (bp, file, line);
+ }
+ *ptr = (struct fundef *)0;
+ return 1;
+}
+
+#if defined (NOTYET) /* Post 3.0 final. */
+int data_subexpression_length (int *rv,
+ struct expression *expr)
+{
+ int crhs, clhs, llhs, lrhs;
+ switch (expr -> op) {
+ case expr_substring:
+ if (expr -> data.substring.len &&
+ expr -> data.substring.len -> op == expr_const_int) {
+ (*rv =
+ (int)expr -> data.substring.len -> data.const_int);
+ return 1;
+ }
+ return 0;
+
+ case expr_packet:
+ case expr_suffix:
+ if (expr -> data.suffix.len &&
+ expr -> data.suffix.len -> op == expr_const_int) {
+ (*rv =
+ (int)expr -> data.suffix.len -> data.const_int);
+ return 1;
+ }
+ return 0;
+
+ case expr_lcase:
+ return data_subexpression_length(rv, expr->data.lcase);
+
+ case expr_ucase:
+ return data_subexpression_length(rv, expr->data.ucase);
+
+ case expr_concat:
+ clhs = data_subexpression_length (&llhs,
+ expr -> data.concat [0]);
+ crhs = data_subexpression_length (&lrhs,
+ expr -> data.concat [1]);
+ if (crhs == 0 || clhs == 0)
+ return 0;
+ *rv = llhs + lrhs;
+ return 1;
+ break;
+
+ case expr_hardware:
+ return 0;
+
+ case expr_const_data:
+ *rv = expr -> data.const_data.len;
+ return 2;
+
+ case expr_reverse:
+ return data_subexpression_length (rv,
+ expr -> data.reverse.buffer);
+
+ case expr_leased_address:
+ case expr_lease_time:
+ *rv = 4;
+ return 2;
+
+ case expr_pick_first_value:
+ clhs = data_subexpression_length (&llhs,
+ expr -> data.concat [0]);
+ crhs = data_subexpression_length (&lrhs,
+ expr -> data.concat [1]);
+ if (crhs == 0 || clhs == 0)
+ return 0;
+ if (llhs > lrhs)
+ *rv = llhs;
+ else
+ *rv = lrhs;
+ return 1;
+
+ case expr_binary_to_ascii:
+ case expr_config_option:
+ case expr_host_decl_name:
+ case expr_encapsulate:
+ case expr_filename:
+ case expr_sname:
+ case expr_host_lookup:
+ case expr_option:
+ case expr_none:
+ case expr_match:
+ case expr_check:
+ case expr_equal:
+ case expr_regex_match:
+ case expr_iregex_match:
+ case expr_and:
+ case expr_or:
+ case expr_not:
+ case expr_extract_int8:
+ case expr_extract_int16:
+ case expr_extract_int32:
+ case expr_encode_int8:
+ case expr_encode_int16:
+ case expr_encode_int32:
+ case expr_const_int:
+ case expr_exists:
+ case expr_known:
+ case expr_dns_transaction:
+ case expr_static:
+ case expr_ns_add:
+ case expr_ns_delete:
+ case expr_ns_exists:
+ case expr_ns_not_exists:
+ case expr_not_equal:
+ case expr_null:
+ case expr_variable_exists:
+ case expr_variable_reference:
+ case expr_arg:
+ case expr_funcall:
+ case expr_function:
+ case expr_add:
+ case expr_subtract:
+ case expr_multiply:
+ case expr_divide:
+ case expr_remainder:
+ case expr_binary_and:
+ case expr_binary_or:
+ case expr_binary_xor:
+ case expr_client_state:
+ case expr_gethostname:
+ return 0;
+ }
+ return 0;
+}
+
+int expr_valid_for_context (struct expression *expr,
+ enum expression_context context)
+{
+ /* We don't know at parse time what type of value a function may
+ return, so we can't flag an error on it. */
+ if (expr -> op == expr_funcall ||
+ expr -> op == expr_variable_reference)
+ return 1;
+
+ switch (context) {
+ case context_any:
+ return 1;
+
+ case context_boolean:
+ if (is_boolean_expression (expr))
+ return 1;
+ return 0;
+
+ case context_data:
+ if (is_data_expression (expr))
+ return 1;
+ return 0;
+
+ case context_numeric:
+ if (is_numeric_expression (expr))
+ return 1;
+ return 0;
+
+ case context_dns:
+ if (is_dns_expression (expr)) {
+ return 1;
+ }
+ return 0;
+
+ case context_data_or_numeric:
+ if (is_numeric_expression (expr) ||
+ is_data_expression (expr)) {
+ return 1;
+ }
+ return 0;
+
+ case context_function:
+ if (expr -> op == expr_function)
+ return 1;
+ return 0;
+ }
+ return 0;
+}
+#endif /* NOTYET */
+
+struct binding *create_binding (struct binding_scope **scope, const char *name)
+{
+ struct binding *binding;
+
+ if (!*scope) {
+ if (!binding_scope_allocate (scope, MDL))
+ return (struct binding *)0;
+ }
+
+ binding = find_binding (*scope, name);
+ if (!binding) {
+ binding = dmalloc (sizeof *binding, MDL);
+ if (!binding)
+ return (struct binding *)0;
+
+ memset (binding, 0, sizeof *binding);
+ binding -> name = dmalloc (strlen (name) + 1, MDL);
+ if (!binding -> name) {
+ dfree (binding, MDL);
+ return (struct binding *)0;
+ }
+ strcpy (binding -> name, name);
+
+ binding -> next = (*scope) -> bindings;
+ (*scope) -> bindings = binding;
+ }
+
+ return binding;
+}
+
+
+int bind_ds_value (struct binding_scope **scope,
+ const char *name,
+ struct data_string *value)
+{
+ struct binding *binding;
+
+ binding = create_binding (scope, name);
+ if (!binding)
+ return 0;
+
+ if (binding -> value)
+ binding_value_dereference (&binding -> value, MDL);
+
+ if (!binding_value_allocate (&binding -> value, MDL))
+ return 0;
+
+ data_string_copy (&binding -> value -> value.data, value, MDL);
+ binding -> value -> type = binding_data;
+
+ return 1;
+}
+
+
+int find_bound_string (struct data_string *value,
+ struct binding_scope *scope,
+ const char *name)
+{
+ struct binding *binding;
+
+ binding = find_binding (scope, name);
+ if (!binding ||
+ !binding -> value ||
+ binding -> value -> type != binding_data)
+ return 0;
+
+ if (binding -> value -> value.data.terminated) {
+ data_string_copy (value, &binding -> value -> value.data, MDL);
+ } else {
+ if (buffer_allocate (&value->buffer,
+ binding->value->value.data.len,
+ MDL) == 0) {
+ return 0;
+ }
+
+ memcpy (value -> buffer -> data,
+ binding -> value -> value.data.data,
+ binding -> value -> value.data.len);
+ value -> data = value -> buffer -> data;
+ value -> len = binding -> value -> value.data.len;
+ }
+
+ return 1;
+}
+
+int unset (struct binding_scope *scope, const char *name)
+{
+ struct binding *binding;
+
+ binding = find_binding (scope, name);
+ if (binding) {
+ if (binding -> value)
+ binding_value_dereference
+ (&binding -> value, MDL);
+ return 1;
+ }
+ return 0;
+}
+
+/*!
+ * \brief Adds two Dc-formatted lists into a single Dc-formatted list
+ *
+ * Given two data_strings containing compressed lists, it constructs a
+ * third data_string containing a single compressed list:
+ *
+ * 1. Decompressing the first list into a buffer
+ * 2. Decompressing the second list onto the end of the buffer
+ * 3. Compressing the buffer into the result
+ *
+ * If either list is empty, the result will be the equal to the compressed
+ * content of the non-empty list. If both lists are empty, the result will
+ * be an "empty" list: a 1 byte buffer containing 0x00.
+ *
+ * It relies on two functions to decompress and compress:
+ *
+ * - MRns_name_uncompress_list() - produces a null-terminated string of
+ * comma-separated domain-names from a buffer containing "Dc" formatted
+ * data
+ *
+ * - MRns_name_compress_list() - produces a buffer containing "Dc" formatted
+ * data from a null-terminated string containing comma-separated domain-names
+ *
+ * \param result data_string which will contain the combined list
+ * in Dc format
+ * \param list1 data_string containing first Dc formatted list
+ * \param list2 data_string containing second Dc formatted list
+ * \return 0 if there is an error, the length of the new list when successful
+ */
+int concat_dclists (struct data_string* result,
+ struct data_string* list1,
+ struct data_string* list2)
+{
+ char uncompbuf[32*NS_MAXCDNAME];
+ char *uncomp = uncompbuf;
+ int uncomp_len = 0;
+ int compbuf_max = 0;
+ int list_len = 0;
+ int i;
+
+ /* If not empty, uncompress first list into the uncompressed buffer */
+ if (list1 && (list1->data) && (list1->len)) {
+ list_len = MRns_name_uncompress_list(list1->data,
+ list1->len, uncomp,
+ sizeof(uncompbuf));
+ if (list_len < 0) {
+ log_error ("concat_dclists:"
+ " error decompressing domain list 1");
+ return (0);
+ }
+
+ uncomp_len = list_len;
+ uncomp += list_len;
+ }
+
+ /* If not empty, uncompress second list into the uncompressed buffer */
+ if (list2 && (list2->data) && (list2->len)) {
+ /* If first list wasn't empty, add a comma */
+ if (uncomp_len > 0) {
+ *uncomp++ = ',';
+ uncomp_len++;
+ }
+
+ list_len = MRns_name_uncompress_list(list2->data, list2->len,
+ uncomp, (sizeof(uncompbuf)
+ - uncomp_len));
+ if (list_len < 0) {
+ log_error ("concat_dclists:"
+ " error decompressing domain list 2");
+ return (0);
+ }
+
+ uncomp_len += list_len;
+ uncomp += list_len;
+ }
+
+ /* If both lists were empty, return an "empty" result */
+ if (uncomp_len == 0) {
+ if (!buffer_allocate (&result->buffer, 1, MDL)) {
+ log_error ("concat_dclists: empty list allocate fail");
+ result->len = 0;
+ return (0);
+ }
+
+ result->len = 1;
+ result->data = result->buffer->data;
+ return (1);
+ }
+
+ /* Estimate the buffer size needed for decompression. The largest
+ * decompression would if one where there are no repeated portions,
+ * (i.e. no compressions). Therefore that size should be the
+ * decompressed string length + 2 for each comma + a final null. Each
+ * dot gets replaced with a length byte and is accounted for in string
+ * length. Mininum length is * uncomp_len + 3. */
+ compbuf_max = uncomp_len + 3;
+ uncomp = uncompbuf;
+ for (i = 0; i < uncomp_len; i++)
+ if (*uncomp++ == ',')
+ compbuf_max += 2;
+
+ /* Allocate compression buffer based on estimated max */
+ if (!buffer_allocate (&result->buffer, compbuf_max, MDL)) {
+ log_error ("concat_dclists: No memory for result");
+ result->len = 0;
+ return (0);
+ }
+
+ /* Compress the combined list into result */
+ list_len = MRns_name_compress_list(uncompbuf, uncomp_len,
+ result->buffer->data, compbuf_max);
+
+ if (list_len <= 0) {
+ log_error ("concat_dlists: error compressing result");
+ data_string_forget(result, MDL);
+ result->len = 0;
+ return (0);
+ }
+
+ /* Update result length to actual size */
+ result->len = list_len;
+ result->data = result->buffer->data;
+ return (list_len);
+}
+
+/* vim: set tabstop=8: */
diff --git a/common/upf.c b/common/upf.c
new file mode 100644
index 0000000..34011eb
--- /dev/null
+++ b/common/upf.c
@@ -0,0 +1,367 @@
+/* upf.c
+
+ Ultrix PacketFilter interface code. */
+
+/*
+ * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-2003 by Internet Software Consortium
+ *
+ * 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 ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+#if defined (USE_UPF_SEND) || defined (USE_UPF_RECEIVE)
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+
+#include <net/pfilt.h>
+#include <netinet/in_systm.h>
+#include "includes/netinet/ip.h"
+#include "includes/netinet/udp.h"
+#include "includes/netinet/if_ether.h"
+
+/* Reinitializes the specified interface after an address change. This
+ is not required for packet-filter APIs. */
+
+#ifdef USE_UPF_SEND
+void if_reinitialize_send (info)
+ struct interface_info *info;
+{
+}
+#endif
+
+#ifdef USE_UPF_RECEIVE
+void if_reinitialize_receive (info)
+ struct interface_info *info;
+{
+}
+#endif
+
+/* Called by get_interface_list for each interface that's discovered.
+ Opens a packet filter for each interface and adds it to the select
+ mask. */
+
+int if_register_upf (info)
+ struct interface_info *info;
+{
+ int sock;
+ char filename[50];
+ int b;
+ struct endevp param;
+
+ /* Open a UPF device */
+ for (b = 0; 1; b++) {
+ /* %Audit% Cannot exceed 36 bytes. %2004.06.17,Safe% */
+ sprintf(filename, "/dev/pf/pfilt%d", b);
+
+ sock = open (filename, O_RDWR, 0);
+ if (sock < 0) {
+ if (errno == EBUSY) {
+ continue;
+ } else {
+ log_fatal ("Can't find free upf: %m");
+ }
+ } else {
+ break;
+ }
+ }
+
+ /* Set the UPF device to point at this interface. */
+ if (ioctl (sock, EIOCSETIF, info -> ifp) < 0)
+ log_fatal ("Can't attach interface %s to upf device %s: %m",
+ info -> name, filename);
+
+ /* Get the hardware address. */
+ if (ioctl (sock, EIOCDEVP, &param) < 0)
+ log_fatal ("Can't get interface %s hardware address: %m",
+ info -> name);
+
+ /* We only know how to do ethernet. */
+ if (param.end_dev_type != ENDT_10MB)
+ log_fatal ("Invalid device type on network interface %s: %d",
+ info -> name, param.end_dev_type);
+
+ if (param.end_addr_len != 6)
+ log_fatal ("Invalid hardware address length on %s: %d",
+ info -> name, param.end_addr_len);
+
+ info -> hw_address.hlen = 7;
+ info -> hw_address.hbuf [0] = ARPHRD_ETHER;
+ memcpy (&info -> hw_address.hbuf [1], param.end_addr, 6);
+
+ return sock;
+}
+#endif /* USE_UPF_SEND || USE_UPF_RECEIVE */
+
+#ifdef USE_UPF_SEND
+void if_register_send (info)
+ struct interface_info *info;
+{
+ /* If we're using the upf API for sending and receiving,
+ we don't need to register this interface twice. */
+#ifndef USE_UPF_RECEIVE
+ info -> wfdesc = if_register_upf (info, interface);
+#else
+ info -> wfdesc = info -> rfdesc;
+#endif
+ if (!quiet_interface_discovery)
+ log_info ("Sending on UPF/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+
+void if_deregister_send (info)
+ struct interface_info *info;
+{
+#ifndef USE_UPF_RECEIVE
+ close (info -> wfdesc);
+#endif
+ info -> wfdesc = -1;
+ if (!quiet_interface_discovery)
+ log_info ("Disabling output on UPF/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+#endif /* USE_UPF_SEND */
+
+#ifdef USE_UPF_RECEIVE
+/* Packet filter program...
+ XXX Changes to the filter program may require changes to the constant
+ offsets used in if_register_send to patch the UPF program! XXX */
+
+
+void if_register_receive (info)
+ struct interface_info *info;
+{
+ int flag = 1;
+ u_int32_t addr;
+ struct enfilter pf;
+ u_int32_t bits;
+
+ /* Open a UPF device and hang it on this interface... */
+ info -> rfdesc = if_register_upf (info);
+
+ /* Allow the copyall flag to be set... */
+ if (ioctl(info -> rfdesc, EIOCALLOWCOPYALL, &flag) < 0)
+ log_fatal ("Can't set ALLOWCOPYALL: %m");
+
+ /* Clear all the packet filter mode bits first... */
+ flag = (ENHOLDSIG | ENBATCH | ENTSTAMP | ENPROMISC |
+ ENNONEXCL | ENCOPYALL);
+ if (ioctl (info -> rfdesc, EIOCMBIC, &flag) < 0)
+ log_fatal ("Can't clear pfilt bits: %m");
+
+ /* Set the ENBATCH and ENCOPYALL bits... */
+ bits = ENBATCH | ENCOPYALL;
+ if (ioctl (info -> rfdesc, EIOCMBIS, &bits) < 0)
+ log_fatal ("Can't set ENBATCH|ENCOPYALL: %m");
+
+ /* Set up the UPF filter program. */
+ /* XXX Unlike the BPF filter program, this one won't work if the
+ XXX IP packet is fragmented or if there are options on the IP
+ XXX header. */
+ pf.enf_Priority = 0;
+ pf.enf_FilterLen = 0;
+
+ pf.enf_Filter [pf.enf_FilterLen++] = ENF_PUSHWORD + 6;
+ pf.enf_Filter [pf.enf_FilterLen++] = ENF_PUSHLIT + ENF_CAND;
+ pf.enf_Filter [pf.enf_FilterLen++] = htons (ETHERTYPE_IP);
+ pf.enf_Filter [pf.enf_FilterLen++] = ENF_PUSHLIT;
+ pf.enf_Filter [pf.enf_FilterLen++] = htons (IPPROTO_UDP);
+ pf.enf_Filter [pf.enf_FilterLen++] = ENF_PUSHWORD + 11;
+ pf.enf_Filter [pf.enf_FilterLen++] = ENF_PUSHLIT + ENF_AND;
+ pf.enf_Filter [pf.enf_FilterLen++] = htons (0xFF);
+ pf.enf_Filter [pf.enf_FilterLen++] = ENF_CAND;
+ pf.enf_Filter [pf.enf_FilterLen++] = ENF_PUSHWORD + 18;
+ pf.enf_Filter [pf.enf_FilterLen++] = ENF_PUSHLIT + ENF_CAND;
+ pf.enf_Filter [pf.enf_FilterLen++] = local_port;
+
+ if (ioctl (info -> rfdesc, EIOCSETF, &pf) < 0)
+ log_fatal ("Can't install packet filter program: %m");
+ if (!quiet_interface_discovery)
+ log_info ("Listening on UPF/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+
+void if_deregister_receive (info)
+ struct interface_info *info;
+{
+ close (info -> rfdesc);
+ info -> rfdesc = -1;
+ if (!quiet_interface_discovery)
+ log_info ("Disabling input on UPF/%s/%s%s%s",
+ info -> name,
+ print_hw_addr (info -> hw_address.hbuf [0],
+ info -> hw_address.hlen - 1,
+ &info -> hw_address.hbuf [1]),
+ (info -> shared_network ? "/" : ""),
+ (info -> shared_network ?
+ info -> shared_network -> name : ""));
+}
+#endif /* USE_UPF_RECEIVE */
+
+#ifdef USE_UPF_SEND
+ssize_t send_packet (interface, packet, raw, len, from, to, hto)
+ struct interface_info *interface;
+ struct packet *packet;
+ struct dhcp_packet *raw;
+ size_t len;
+ struct in_addr from;
+ struct sockaddr_in *to;
+ struct hardware *hto;
+{
+ unsigned hbufp = 0, ibufp = 0;
+ double hw [4];
+ double ip [32];
+ struct iovec iov [3];
+ int result;
+ int fudge;
+
+ if (!strcmp (interface -> name, "fallback"))
+ return send_fallback (interface, packet, raw,
+ len, from, to, hto);
+
+ if (hto == NULL && interface->anycast_mac_addr.hlen)
+ hto = &interface->anycast_mac_addr;
+
+ /* Assemble the headers... */
+ assemble_hw_header (interface, (unsigned char *)hw, &hbufp, hto);
+ assemble_udp_ip_header (interface,
+ (unsigned char *)ip, &ibufp, from.s_addr,
+ to -> sin_addr.s_addr, to -> sin_port,
+ (unsigned char *)raw, len);
+
+ /* Fire it off */
+ iov [0].iov_base = ((char *)hw);
+ iov [0].iov_len = hbufp;
+ iov [1].iov_base = ((char *)ip);
+ iov [1].iov_len = ibufp;
+ iov [2].iov_base = (char *)raw;
+ iov [2].iov_len = len;
+
+ result = writev(interface -> wfdesc, iov, 3);
+ if (result < 0)
+ log_error ("send_packet: %m");
+ return result;
+}
+#endif /* USE_UPF_SEND */
+
+#ifdef USE_UPF_RECEIVE
+ssize_t receive_packet (interface, buf, len, from, hfrom)
+ struct interface_info *interface;
+ unsigned char *buf;
+ size_t len;
+ struct sockaddr_in *from;
+ struct hardware *hfrom;
+{
+ int nread;
+ int length = 0;
+ int offset = 0;
+ unsigned char ibuf [1500 + sizeof (struct enstamp)];
+ int bufix = 0;
+ unsigned paylen;
+
+ length = read (interface -> rfdesc, ibuf, sizeof ibuf);
+ if (length <= 0)
+ return length;
+
+ bufix = sizeof (struct enstamp);
+ /* Decode the physical header... */
+ offset = decode_hw_header (interface, ibuf, bufix, hfrom);
+
+ /* If a physical layer checksum failed (dunno of any
+ physical layer that supports this, but WTH), skip this
+ packet. */
+ if (offset < 0) {
+ return 0;
+ }
+
+ bufix += offset;
+ length -= offset;
+
+ /* Decode the IP and UDP headers... */
+ offset = decode_udp_ip_header (interface, ibuf, bufix,
+ from, length, &paylen, 1);
+
+ /* If the IP or UDP checksum was bad, skip the packet... */
+ if (offset < 0)
+ return 0;
+
+ bufix += offset;
+ length -= offset;
+
+ if (length < paylen)
+ log_fatal("Internal inconsistency at %s:%d.", MDL);
+
+ /* Copy out the data in the packet... */
+ memcpy (buf, &ibuf[bufix], paylen);
+ return paylen;
+}
+
+int can_unicast_without_arp (ip)
+ struct interface_info *ip;
+{
+ return 1;
+}
+
+int can_receive_unicast_unconfigured (ip)
+ struct interface_info *ip;
+{
+ return 1;
+}
+
+int supports_multiple_interfaces (ip)
+ struct interface_info *ip;
+{
+ return 1;
+}
+
+void maybe_setup_fallback ()
+{
+ isc_result_t status;
+ struct interface_info *fbi = (struct interface_info *)0;
+ if (setup_fallback (&fbi, MDL)) {
+ if_register_fallback (fbi);
+ status = omapi_register_io_object ((omapi_object_t *)fbi,
+ if_readsocket, 0,
+ fallback_discard, 0, 0);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register I/O handle for %s: %s",
+ fbi -> name, isc_result_totext (status));
+ interface_dereference (&fbi, MDL);
+ }
+}
+#endif