summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/Makefile.am22
-rw-r--r--server/Makefile.in1116
-rw-r--r--server/bootp.c442
-rw-r--r--server/class.c303
-rw-r--r--server/confpars.c5606
-rw-r--r--server/db.c1195
-rw-r--r--server/ddns.c1957
-rw-r--r--server/dhcp.c4665
-rw-r--r--server/dhcpd.8805
-rw-r--r--server/dhcpd.c1542
-rw-r--r--server/dhcpd.conf.53026
-rw-r--r--server/dhcpd.conf.example104
-rw-r--r--server/dhcpd.leases.5378
-rw-r--r--server/dhcpleasequery.c1259
-rw-r--r--server/dhcpv6.c6235
-rw-r--r--server/failover.c6476
-rw-r--r--server/ldap.c2004
-rw-r--r--server/ldap_casa.c159
-rw-r--r--server/mdb.c3047
-rw-r--r--server/mdb6.c2147
-rw-r--r--server/omapi.c2565
-rw-r--r--server/salloc.c251
-rw-r--r--server/stables.c504
-rw-r--r--server/tests/Atffile5
-rw-r--r--server/tests/Makefile.am53
-rw-r--r--server/tests/Makefile.in999
-rw-r--r--server/tests/hash_unittest.c607
-rw-r--r--server/tests/load_bal_unittest.c191
-rw-r--r--server/tests/mdb6_unittest.c957
-rw-r--r--server/tests/simple_unittest.c77
30 files changed, 48697 insertions, 0 deletions
diff --git a/server/Makefile.am b/server/Makefile.am
new file mode 100644
index 0000000..dc5d4f3
--- /dev/null
+++ b/server/Makefile.am
@@ -0,0 +1,22 @@
+# 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
+
+AM_CPPFLAGS = -I.. -DLOCALSTATEDIR='"@localstatedir@"'
+
+dist_sysconf_DATA = dhcpd.conf.example
+sbin_PROGRAMS = dhcpd
+dhcpd_SOURCES = dhcpd.c dhcp.c bootp.c confpars.c db.c class.c failover.c \
+ omapi.c mdb.c stables.c salloc.c ddns.c dhcpleasequery.c \
+ dhcpv6.c mdb6.c ldap.c ldap_casa.c
+
+dhcpd_CFLAGS = $(LDAP_CFLAGS)
+dhcpd_LDADD = ../common/libdhcp.a ../omapip/libomapi.a \
+ ../dhcpctl/libdhcpctl.a ../bind/lib/libdns.a \
+ ../bind/lib/libisc.a
+
+man_MANS = dhcpd.8 dhcpd.conf.5 dhcpd.leases.5
+EXTRA_DIST = $(man_MANS)
+
diff --git a/server/Makefile.in b/server/Makefile.in
new file mode 100644
index 0000000..985a4b9
--- /dev/null
+++ b/server/Makefile.in
@@ -0,0 +1,1116 @@
+# 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@
+sbin_PROGRAMS = dhcpd$(EXEEXT)
+subdir = server
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(top_srcdir)/depcomp $(dist_sysconf_DATA)
+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 =
+am__installdirs = "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man5dir)" \
+ "$(DESTDIR)$(man8dir)" "$(DESTDIR)$(sysconfdir)"
+PROGRAMS = $(sbin_PROGRAMS)
+am_dhcpd_OBJECTS = dhcpd-dhcpd.$(OBJEXT) dhcpd-dhcp.$(OBJEXT) \
+ dhcpd-bootp.$(OBJEXT) dhcpd-confpars.$(OBJEXT) \
+ dhcpd-db.$(OBJEXT) dhcpd-class.$(OBJEXT) \
+ dhcpd-failover.$(OBJEXT) dhcpd-omapi.$(OBJEXT) \
+ dhcpd-mdb.$(OBJEXT) dhcpd-stables.$(OBJEXT) \
+ dhcpd-salloc.$(OBJEXT) dhcpd-ddns.$(OBJEXT) \
+ dhcpd-dhcpleasequery.$(OBJEXT) dhcpd-dhcpv6.$(OBJEXT) \
+ dhcpd-mdb6.$(OBJEXT) dhcpd-ldap.$(OBJEXT) \
+ dhcpd-ldap_casa.$(OBJEXT)
+dhcpd_OBJECTS = $(am_dhcpd_OBJECTS)
+dhcpd_DEPENDENCIES = ../common/libdhcp.a ../omapip/libomapi.a \
+ ../dhcpctl/libdhcpctl.a ../bind/lib/libdns.a \
+ ../bind/lib/libisc.a
+dhcpd_LINK = $(CCLD) $(dhcpd_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+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 = $(dhcpd_SOURCES)
+DIST_SOURCES = $(dhcpd_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
+man8dir = $(mandir)/man8
+NROFF = nroff
+MANS = $(man_MANS)
+DATA = $(dist_sysconf_DATA)
+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@
+
+# 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
+AM_CPPFLAGS = -I.. -DLOCALSTATEDIR='"@localstatedir@"'
+dist_sysconf_DATA = dhcpd.conf.example
+dhcpd_SOURCES = dhcpd.c dhcp.c bootp.c confpars.c db.c class.c failover.c \
+ omapi.c mdb.c stables.c salloc.c ddns.c dhcpleasequery.c \
+ dhcpv6.c mdb6.c ldap.c ldap_casa.c
+
+dhcpd_CFLAGS = $(LDAP_CFLAGS)
+dhcpd_LDADD = ../common/libdhcp.a ../omapip/libomapi.a \
+ ../dhcpctl/libdhcpctl.a ../bind/lib/libdns.a \
+ ../bind/lib/libisc.a
+
+man_MANS = dhcpd.8 dhcpd.conf.5 dhcpd.leases.5
+EXTRA_DIST = $(man_MANS)
+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 server/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign server/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):
+install-sbinPROGRAMS: $(sbin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-sbinPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(sbindir)" && rm -f $$files
+
+clean-sbinPROGRAMS:
+ -test -z "$(sbin_PROGRAMS)" || rm -f $(sbin_PROGRAMS)
+
+dhcpd$(EXEEXT): $(dhcpd_OBJECTS) $(dhcpd_DEPENDENCIES) $(EXTRA_dhcpd_DEPENDENCIES)
+ @rm -f dhcpd$(EXEEXT)
+ $(AM_V_CCLD)$(dhcpd_LINK) $(dhcpd_OBJECTS) $(dhcpd_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-bootp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-class.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-confpars.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-db.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-ddns.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-dhcp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-dhcpd.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-dhcpleasequery.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-dhcpv6.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-failover.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-ldap.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-ldap_casa.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-mdb.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-mdb6.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-omapi.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-salloc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-stables.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) '$<'`
+
+dhcpd-dhcpd.o: dhcpd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-dhcpd.o -MD -MP -MF $(DEPDIR)/dhcpd-dhcpd.Tpo -c -o dhcpd-dhcpd.o `test -f 'dhcpd.c' || echo '$(srcdir)/'`dhcpd.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-dhcpd.Tpo $(DEPDIR)/dhcpd-dhcpd.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dhcpd.c' object='dhcpd-dhcpd.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-dhcpd.o `test -f 'dhcpd.c' || echo '$(srcdir)/'`dhcpd.c
+
+dhcpd-dhcpd.obj: dhcpd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-dhcpd.obj -MD -MP -MF $(DEPDIR)/dhcpd-dhcpd.Tpo -c -o dhcpd-dhcpd.obj `if test -f 'dhcpd.c'; then $(CYGPATH_W) 'dhcpd.c'; else $(CYGPATH_W) '$(srcdir)/dhcpd.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-dhcpd.Tpo $(DEPDIR)/dhcpd-dhcpd.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dhcpd.c' object='dhcpd-dhcpd.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-dhcpd.obj `if test -f 'dhcpd.c'; then $(CYGPATH_W) 'dhcpd.c'; else $(CYGPATH_W) '$(srcdir)/dhcpd.c'; fi`
+
+dhcpd-dhcp.o: dhcp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-dhcp.o -MD -MP -MF $(DEPDIR)/dhcpd-dhcp.Tpo -c -o dhcpd-dhcp.o `test -f 'dhcp.c' || echo '$(srcdir)/'`dhcp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-dhcp.Tpo $(DEPDIR)/dhcpd-dhcp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dhcp.c' object='dhcpd-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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-dhcp.o `test -f 'dhcp.c' || echo '$(srcdir)/'`dhcp.c
+
+dhcpd-dhcp.obj: dhcp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-dhcp.obj -MD -MP -MF $(DEPDIR)/dhcpd-dhcp.Tpo -c -o dhcpd-dhcp.obj `if test -f 'dhcp.c'; then $(CYGPATH_W) 'dhcp.c'; else $(CYGPATH_W) '$(srcdir)/dhcp.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-dhcp.Tpo $(DEPDIR)/dhcpd-dhcp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dhcp.c' object='dhcpd-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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-dhcp.obj `if test -f 'dhcp.c'; then $(CYGPATH_W) 'dhcp.c'; else $(CYGPATH_W) '$(srcdir)/dhcp.c'; fi`
+
+dhcpd-bootp.o: bootp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-bootp.o -MD -MP -MF $(DEPDIR)/dhcpd-bootp.Tpo -c -o dhcpd-bootp.o `test -f 'bootp.c' || echo '$(srcdir)/'`bootp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-bootp.Tpo $(DEPDIR)/dhcpd-bootp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='bootp.c' object='dhcpd-bootp.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-bootp.o `test -f 'bootp.c' || echo '$(srcdir)/'`bootp.c
+
+dhcpd-bootp.obj: bootp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-bootp.obj -MD -MP -MF $(DEPDIR)/dhcpd-bootp.Tpo -c -o dhcpd-bootp.obj `if test -f 'bootp.c'; then $(CYGPATH_W) 'bootp.c'; else $(CYGPATH_W) '$(srcdir)/bootp.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-bootp.Tpo $(DEPDIR)/dhcpd-bootp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='bootp.c' object='dhcpd-bootp.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-bootp.obj `if test -f 'bootp.c'; then $(CYGPATH_W) 'bootp.c'; else $(CYGPATH_W) '$(srcdir)/bootp.c'; fi`
+
+dhcpd-confpars.o: confpars.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-confpars.o -MD -MP -MF $(DEPDIR)/dhcpd-confpars.Tpo -c -o dhcpd-confpars.o `test -f 'confpars.c' || echo '$(srcdir)/'`confpars.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-confpars.Tpo $(DEPDIR)/dhcpd-confpars.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='confpars.c' object='dhcpd-confpars.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-confpars.o `test -f 'confpars.c' || echo '$(srcdir)/'`confpars.c
+
+dhcpd-confpars.obj: confpars.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-confpars.obj -MD -MP -MF $(DEPDIR)/dhcpd-confpars.Tpo -c -o dhcpd-confpars.obj `if test -f 'confpars.c'; then $(CYGPATH_W) 'confpars.c'; else $(CYGPATH_W) '$(srcdir)/confpars.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-confpars.Tpo $(DEPDIR)/dhcpd-confpars.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='confpars.c' object='dhcpd-confpars.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-confpars.obj `if test -f 'confpars.c'; then $(CYGPATH_W) 'confpars.c'; else $(CYGPATH_W) '$(srcdir)/confpars.c'; fi`
+
+dhcpd-db.o: db.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-db.o -MD -MP -MF $(DEPDIR)/dhcpd-db.Tpo -c -o dhcpd-db.o `test -f 'db.c' || echo '$(srcdir)/'`db.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-db.Tpo $(DEPDIR)/dhcpd-db.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='db.c' object='dhcpd-db.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-db.o `test -f 'db.c' || echo '$(srcdir)/'`db.c
+
+dhcpd-db.obj: db.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-db.obj -MD -MP -MF $(DEPDIR)/dhcpd-db.Tpo -c -o dhcpd-db.obj `if test -f 'db.c'; then $(CYGPATH_W) 'db.c'; else $(CYGPATH_W) '$(srcdir)/db.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-db.Tpo $(DEPDIR)/dhcpd-db.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='db.c' object='dhcpd-db.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-db.obj `if test -f 'db.c'; then $(CYGPATH_W) 'db.c'; else $(CYGPATH_W) '$(srcdir)/db.c'; fi`
+
+dhcpd-class.o: class.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-class.o -MD -MP -MF $(DEPDIR)/dhcpd-class.Tpo -c -o dhcpd-class.o `test -f 'class.c' || echo '$(srcdir)/'`class.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-class.Tpo $(DEPDIR)/dhcpd-class.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='class.c' object='dhcpd-class.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-class.o `test -f 'class.c' || echo '$(srcdir)/'`class.c
+
+dhcpd-class.obj: class.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-class.obj -MD -MP -MF $(DEPDIR)/dhcpd-class.Tpo -c -o dhcpd-class.obj `if test -f 'class.c'; then $(CYGPATH_W) 'class.c'; else $(CYGPATH_W) '$(srcdir)/class.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-class.Tpo $(DEPDIR)/dhcpd-class.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='class.c' object='dhcpd-class.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-class.obj `if test -f 'class.c'; then $(CYGPATH_W) 'class.c'; else $(CYGPATH_W) '$(srcdir)/class.c'; fi`
+
+dhcpd-failover.o: failover.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-failover.o -MD -MP -MF $(DEPDIR)/dhcpd-failover.Tpo -c -o dhcpd-failover.o `test -f 'failover.c' || echo '$(srcdir)/'`failover.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-failover.Tpo $(DEPDIR)/dhcpd-failover.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='failover.c' object='dhcpd-failover.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-failover.o `test -f 'failover.c' || echo '$(srcdir)/'`failover.c
+
+dhcpd-failover.obj: failover.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-failover.obj -MD -MP -MF $(DEPDIR)/dhcpd-failover.Tpo -c -o dhcpd-failover.obj `if test -f 'failover.c'; then $(CYGPATH_W) 'failover.c'; else $(CYGPATH_W) '$(srcdir)/failover.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-failover.Tpo $(DEPDIR)/dhcpd-failover.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='failover.c' object='dhcpd-failover.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-failover.obj `if test -f 'failover.c'; then $(CYGPATH_W) 'failover.c'; else $(CYGPATH_W) '$(srcdir)/failover.c'; fi`
+
+dhcpd-omapi.o: omapi.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-omapi.o -MD -MP -MF $(DEPDIR)/dhcpd-omapi.Tpo -c -o dhcpd-omapi.o `test -f 'omapi.c' || echo '$(srcdir)/'`omapi.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-omapi.Tpo $(DEPDIR)/dhcpd-omapi.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='omapi.c' object='dhcpd-omapi.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-omapi.o `test -f 'omapi.c' || echo '$(srcdir)/'`omapi.c
+
+dhcpd-omapi.obj: omapi.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-omapi.obj -MD -MP -MF $(DEPDIR)/dhcpd-omapi.Tpo -c -o dhcpd-omapi.obj `if test -f 'omapi.c'; then $(CYGPATH_W) 'omapi.c'; else $(CYGPATH_W) '$(srcdir)/omapi.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-omapi.Tpo $(DEPDIR)/dhcpd-omapi.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='omapi.c' object='dhcpd-omapi.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-omapi.obj `if test -f 'omapi.c'; then $(CYGPATH_W) 'omapi.c'; else $(CYGPATH_W) '$(srcdir)/omapi.c'; fi`
+
+dhcpd-mdb.o: mdb.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-mdb.o -MD -MP -MF $(DEPDIR)/dhcpd-mdb.Tpo -c -o dhcpd-mdb.o `test -f 'mdb.c' || echo '$(srcdir)/'`mdb.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-mdb.Tpo $(DEPDIR)/dhcpd-mdb.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mdb.c' object='dhcpd-mdb.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-mdb.o `test -f 'mdb.c' || echo '$(srcdir)/'`mdb.c
+
+dhcpd-mdb.obj: mdb.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-mdb.obj -MD -MP -MF $(DEPDIR)/dhcpd-mdb.Tpo -c -o dhcpd-mdb.obj `if test -f 'mdb.c'; then $(CYGPATH_W) 'mdb.c'; else $(CYGPATH_W) '$(srcdir)/mdb.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-mdb.Tpo $(DEPDIR)/dhcpd-mdb.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mdb.c' object='dhcpd-mdb.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-mdb.obj `if test -f 'mdb.c'; then $(CYGPATH_W) 'mdb.c'; else $(CYGPATH_W) '$(srcdir)/mdb.c'; fi`
+
+dhcpd-stables.o: stables.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-stables.o -MD -MP -MF $(DEPDIR)/dhcpd-stables.Tpo -c -o dhcpd-stables.o `test -f 'stables.c' || echo '$(srcdir)/'`stables.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-stables.Tpo $(DEPDIR)/dhcpd-stables.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='stables.c' object='dhcpd-stables.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-stables.o `test -f 'stables.c' || echo '$(srcdir)/'`stables.c
+
+dhcpd-stables.obj: stables.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-stables.obj -MD -MP -MF $(DEPDIR)/dhcpd-stables.Tpo -c -o dhcpd-stables.obj `if test -f 'stables.c'; then $(CYGPATH_W) 'stables.c'; else $(CYGPATH_W) '$(srcdir)/stables.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-stables.Tpo $(DEPDIR)/dhcpd-stables.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='stables.c' object='dhcpd-stables.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-stables.obj `if test -f 'stables.c'; then $(CYGPATH_W) 'stables.c'; else $(CYGPATH_W) '$(srcdir)/stables.c'; fi`
+
+dhcpd-salloc.o: salloc.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-salloc.o -MD -MP -MF $(DEPDIR)/dhcpd-salloc.Tpo -c -o dhcpd-salloc.o `test -f 'salloc.c' || echo '$(srcdir)/'`salloc.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-salloc.Tpo $(DEPDIR)/dhcpd-salloc.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='salloc.c' object='dhcpd-salloc.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-salloc.o `test -f 'salloc.c' || echo '$(srcdir)/'`salloc.c
+
+dhcpd-salloc.obj: salloc.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-salloc.obj -MD -MP -MF $(DEPDIR)/dhcpd-salloc.Tpo -c -o dhcpd-salloc.obj `if test -f 'salloc.c'; then $(CYGPATH_W) 'salloc.c'; else $(CYGPATH_W) '$(srcdir)/salloc.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-salloc.Tpo $(DEPDIR)/dhcpd-salloc.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='salloc.c' object='dhcpd-salloc.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-salloc.obj `if test -f 'salloc.c'; then $(CYGPATH_W) 'salloc.c'; else $(CYGPATH_W) '$(srcdir)/salloc.c'; fi`
+
+dhcpd-ddns.o: ddns.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-ddns.o -MD -MP -MF $(DEPDIR)/dhcpd-ddns.Tpo -c -o dhcpd-ddns.o `test -f 'ddns.c' || echo '$(srcdir)/'`ddns.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-ddns.Tpo $(DEPDIR)/dhcpd-ddns.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ddns.c' object='dhcpd-ddns.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-ddns.o `test -f 'ddns.c' || echo '$(srcdir)/'`ddns.c
+
+dhcpd-ddns.obj: ddns.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-ddns.obj -MD -MP -MF $(DEPDIR)/dhcpd-ddns.Tpo -c -o dhcpd-ddns.obj `if test -f 'ddns.c'; then $(CYGPATH_W) 'ddns.c'; else $(CYGPATH_W) '$(srcdir)/ddns.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-ddns.Tpo $(DEPDIR)/dhcpd-ddns.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ddns.c' object='dhcpd-ddns.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-ddns.obj `if test -f 'ddns.c'; then $(CYGPATH_W) 'ddns.c'; else $(CYGPATH_W) '$(srcdir)/ddns.c'; fi`
+
+dhcpd-dhcpleasequery.o: dhcpleasequery.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-dhcpleasequery.o -MD -MP -MF $(DEPDIR)/dhcpd-dhcpleasequery.Tpo -c -o dhcpd-dhcpleasequery.o `test -f 'dhcpleasequery.c' || echo '$(srcdir)/'`dhcpleasequery.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-dhcpleasequery.Tpo $(DEPDIR)/dhcpd-dhcpleasequery.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dhcpleasequery.c' object='dhcpd-dhcpleasequery.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-dhcpleasequery.o `test -f 'dhcpleasequery.c' || echo '$(srcdir)/'`dhcpleasequery.c
+
+dhcpd-dhcpleasequery.obj: dhcpleasequery.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-dhcpleasequery.obj -MD -MP -MF $(DEPDIR)/dhcpd-dhcpleasequery.Tpo -c -o dhcpd-dhcpleasequery.obj `if test -f 'dhcpleasequery.c'; then $(CYGPATH_W) 'dhcpleasequery.c'; else $(CYGPATH_W) '$(srcdir)/dhcpleasequery.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-dhcpleasequery.Tpo $(DEPDIR)/dhcpd-dhcpleasequery.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dhcpleasequery.c' object='dhcpd-dhcpleasequery.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-dhcpleasequery.obj `if test -f 'dhcpleasequery.c'; then $(CYGPATH_W) 'dhcpleasequery.c'; else $(CYGPATH_W) '$(srcdir)/dhcpleasequery.c'; fi`
+
+dhcpd-dhcpv6.o: dhcpv6.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-dhcpv6.o -MD -MP -MF $(DEPDIR)/dhcpd-dhcpv6.Tpo -c -o dhcpd-dhcpv6.o `test -f 'dhcpv6.c' || echo '$(srcdir)/'`dhcpv6.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-dhcpv6.Tpo $(DEPDIR)/dhcpd-dhcpv6.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dhcpv6.c' object='dhcpd-dhcpv6.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-dhcpv6.o `test -f 'dhcpv6.c' || echo '$(srcdir)/'`dhcpv6.c
+
+dhcpd-dhcpv6.obj: dhcpv6.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-dhcpv6.obj -MD -MP -MF $(DEPDIR)/dhcpd-dhcpv6.Tpo -c -o dhcpd-dhcpv6.obj `if test -f 'dhcpv6.c'; then $(CYGPATH_W) 'dhcpv6.c'; else $(CYGPATH_W) '$(srcdir)/dhcpv6.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-dhcpv6.Tpo $(DEPDIR)/dhcpd-dhcpv6.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dhcpv6.c' object='dhcpd-dhcpv6.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-dhcpv6.obj `if test -f 'dhcpv6.c'; then $(CYGPATH_W) 'dhcpv6.c'; else $(CYGPATH_W) '$(srcdir)/dhcpv6.c'; fi`
+
+dhcpd-mdb6.o: mdb6.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-mdb6.o -MD -MP -MF $(DEPDIR)/dhcpd-mdb6.Tpo -c -o dhcpd-mdb6.o `test -f 'mdb6.c' || echo '$(srcdir)/'`mdb6.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-mdb6.Tpo $(DEPDIR)/dhcpd-mdb6.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mdb6.c' object='dhcpd-mdb6.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-mdb6.o `test -f 'mdb6.c' || echo '$(srcdir)/'`mdb6.c
+
+dhcpd-mdb6.obj: mdb6.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-mdb6.obj -MD -MP -MF $(DEPDIR)/dhcpd-mdb6.Tpo -c -o dhcpd-mdb6.obj `if test -f 'mdb6.c'; then $(CYGPATH_W) 'mdb6.c'; else $(CYGPATH_W) '$(srcdir)/mdb6.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-mdb6.Tpo $(DEPDIR)/dhcpd-mdb6.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mdb6.c' object='dhcpd-mdb6.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-mdb6.obj `if test -f 'mdb6.c'; then $(CYGPATH_W) 'mdb6.c'; else $(CYGPATH_W) '$(srcdir)/mdb6.c'; fi`
+
+dhcpd-ldap.o: ldap.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-ldap.o -MD -MP -MF $(DEPDIR)/dhcpd-ldap.Tpo -c -o dhcpd-ldap.o `test -f 'ldap.c' || echo '$(srcdir)/'`ldap.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-ldap.Tpo $(DEPDIR)/dhcpd-ldap.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ldap.c' object='dhcpd-ldap.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-ldap.o `test -f 'ldap.c' || echo '$(srcdir)/'`ldap.c
+
+dhcpd-ldap.obj: ldap.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-ldap.obj -MD -MP -MF $(DEPDIR)/dhcpd-ldap.Tpo -c -o dhcpd-ldap.obj `if test -f 'ldap.c'; then $(CYGPATH_W) 'ldap.c'; else $(CYGPATH_W) '$(srcdir)/ldap.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-ldap.Tpo $(DEPDIR)/dhcpd-ldap.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ldap.c' object='dhcpd-ldap.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-ldap.obj `if test -f 'ldap.c'; then $(CYGPATH_W) 'ldap.c'; else $(CYGPATH_W) '$(srcdir)/ldap.c'; fi`
+
+dhcpd-ldap_casa.o: ldap_casa.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-ldap_casa.o -MD -MP -MF $(DEPDIR)/dhcpd-ldap_casa.Tpo -c -o dhcpd-ldap_casa.o `test -f 'ldap_casa.c' || echo '$(srcdir)/'`ldap_casa.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-ldap_casa.Tpo $(DEPDIR)/dhcpd-ldap_casa.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ldap_casa.c' object='dhcpd-ldap_casa.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-ldap_casa.o `test -f 'ldap_casa.c' || echo '$(srcdir)/'`ldap_casa.c
+
+dhcpd-ldap_casa.obj: ldap_casa.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-ldap_casa.obj -MD -MP -MF $(DEPDIR)/dhcpd-ldap_casa.Tpo -c -o dhcpd-ldap_casa.obj `if test -f 'ldap_casa.c'; then $(CYGPATH_W) 'ldap_casa.c'; else $(CYGPATH_W) '$(srcdir)/ldap_casa.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd-ldap_casa.Tpo $(DEPDIR)/dhcpd-ldap_casa.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ldap_casa.c' object='dhcpd-ldap_casa.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) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-ldap_casa.obj `if test -f 'ldap_casa.c'; then $(CYGPATH_W) 'ldap_casa.c'; else $(CYGPATH_W) '$(srcdir)/ldap_casa.c'; fi`
+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)
+install-man8: $(man_MANS)
+ @$(NORMAL_INSTALL)
+ @list1=''; \
+ list2='$(man_MANS)'; \
+ test -n "$(man8dir)" \
+ && test -n "`echo $$list1$$list2`" \
+ || exit 0; \
+ echo " $(MKDIR_P) '$(DESTDIR)$(man8dir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(man8dir)" || 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 '/\.8[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,^[^8][0-9a-z]*$$,8,;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)$(man8dir)/$$inst'"; \
+ $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man8dir)/$$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)$(man8dir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(man8dir)" || exit $$?; }; \
+ done; }
+
+uninstall-man8:
+ @$(NORMAL_UNINSTALL)
+ @list=''; test -n "$(man8dir)" || exit 0; \
+ files=`{ for i in $$list; do echo "$$i"; done; \
+ l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \
+ sed -n '/\.8[a-z]*$$/p'; \
+ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \
+ dir='$(DESTDIR)$(man8dir)'; $(am__uninstall_files_from_dir)
+install-dist_sysconfDATA: $(dist_sysconf_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(dist_sysconf_DATA)'; test -n "$(sysconfdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sysconfdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sysconfdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(sysconfdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(sysconfdir)" || exit $$?; \
+ done
+
+uninstall-dist_sysconfDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(dist_sysconf_DATA)'; test -n "$(sysconfdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(sysconfdir)'; $(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 $(PROGRAMS) $(MANS) $(DATA)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man5dir)" "$(DESTDIR)$(man8dir)" "$(DESTDIR)$(sysconfdir)"; 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-sbinPROGRAMS 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-dist_sysconfDATA install-sbinPROGRAMS
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man: install-man5 install-man8
+
+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-dist_sysconfDATA uninstall-man \
+ uninstall-sbinPROGRAMS
+
+uninstall-man: uninstall-man5 uninstall-man8
+
+.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-sbinPROGRAMS 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-dist_sysconfDATA 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-man8 install-pdf install-pdf-am install-ps \
+ install-ps-am install-sbinPROGRAMS 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-dist_sysconfDATA uninstall-man \
+ uninstall-man5 uninstall-man8 uninstall-sbinPROGRAMS
+
+
+# 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/server/bootp.c b/server/bootp.c
new file mode 100644
index 0000000..7397cec
--- /dev/null
+++ b/server/bootp.c
@@ -0,0 +1,442 @@
+/* bootp.c
+
+ BOOTP Protocol support. */
+
+/*
+ * Copyright (c) 2009,2012-2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004,2005,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"
+#include <errno.h>
+
+#if defined (TRACING)
+# define send_packet trace_packet_send
+#endif
+
+void bootp (packet)
+ struct packet *packet;
+{
+ int result;
+ struct host_decl *hp = (struct host_decl *)0;
+ struct host_decl *host = (struct host_decl *)0;
+ struct packet outgoing;
+ struct dhcp_packet raw;
+ struct sockaddr_in to;
+ struct in_addr from;
+ struct hardware hto;
+ struct option_state *options = (struct option_state *)0;
+ struct lease *lease = (struct lease *)0;
+ unsigned i;
+ struct data_string d1;
+ struct option_cache *oc;
+ char msgbuf [1024];
+ int ignorep;
+ int peer_has_leases = 0;
+
+ if (packet -> raw -> op != BOOTREQUEST)
+ return;
+
+ /* %Audit% This is log output. %2004.06.17,Safe%
+ * If we truncate we hope the user can get a hint from the log.
+ */
+ snprintf (msgbuf, sizeof msgbuf, "BOOTREQUEST from %s via %s",
+ print_hw_addr (packet -> raw -> htype,
+ packet -> raw -> hlen,
+ packet -> raw -> chaddr),
+ packet -> raw -> giaddr.s_addr
+ ? inet_ntoa (packet -> raw -> giaddr)
+ : packet -> interface -> name);
+
+ if (!locate_network (packet)) {
+ log_info ("%s: network unknown", msgbuf);
+ return;
+ }
+
+ find_lease (&lease, packet, packet -> shared_network,
+ 0, 0, (struct lease *)0, MDL);
+
+ if (lease && lease->host)
+ host_reference(&hp, lease->host, MDL);
+
+ if (!lease || ((lease->flags & STATIC_LEASE) == 0)) {
+ struct host_decl *h;
+
+ /* We didn't find an applicable fixed-address host
+ declaration. Just in case we may be able to dynamically
+ assign an address, see if there's a host declaration
+ that doesn't have an ip address associated with it. */
+
+ if (!hp)
+ find_hosts_by_haddr(&hp, packet->raw->htype,
+ packet->raw->chaddr,
+ packet->raw->hlen, MDL);
+
+ for (h = hp; h; h = h -> n_ipaddr) {
+ if (!h -> fixed_addr) {
+ host_reference(&host, h, MDL);
+ break;
+ }
+ }
+
+ if (hp)
+ host_dereference(&hp, MDL);
+
+ if (host) {
+ host_reference(&hp, host, MDL);
+ host_dereference(&host, MDL);
+ }
+
+ /* Allocate a lease if we have not yet found one. */
+ if (!lease)
+ allocate_lease (&lease, packet,
+ packet -> shared_network -> pools,
+ &peer_has_leases);
+
+ if (lease == NULL) {
+ log_info("%s: BOOTP from dynamic client and no "
+ "dynamic leases", msgbuf);
+ goto out;
+ }
+
+#if defined(FAILOVER_PROTOCOL)
+ if ((lease->pool != NULL) &&
+ (lease->pool->failover_peer != NULL)) {
+ dhcp_failover_state_t *peer;
+
+ peer = lease->pool->failover_peer;
+
+ /* If we are in a failover state that bars us from
+ * answering, do not do so.
+ * If we are in a cooperative state, load balance
+ * (all) responses.
+ */
+ if ((peer->service_state == not_responding) ||
+ (peer->service_state == service_startup)) {
+ log_info("%s: not responding%s",
+ msgbuf, peer->nrr);
+ goto out;
+ } else if((peer->service_state == cooperating) &&
+ !load_balance_mine(packet, peer)) {
+ log_info("%s: load balance to peer %s",
+ msgbuf, peer->name);
+ goto out;
+ }
+ }
+#endif
+
+ ack_lease (packet, lease, 0, 0, msgbuf, 0, hp);
+ goto out;
+ }
+
+ /* Run the executable statements to compute the client and server
+ options. */
+ option_state_allocate (&options, MDL);
+
+ /* Execute the subnet statements. */
+ execute_statements_in_scope ((struct binding_value **)0,
+ packet, lease, (struct client_state *)0,
+ packet -> options, options,
+ &lease -> scope, lease -> subnet -> group,
+ (struct group *)0);
+
+ /* Execute statements from class scopes. */
+ for (i = packet -> class_count; i > 0; i--) {
+ execute_statements_in_scope
+ ((struct binding_value **)0,
+ packet, lease, (struct client_state *)0,
+ packet -> options, options,
+ &lease -> scope, packet -> classes [i - 1] -> group,
+ lease -> subnet -> group);
+ }
+
+ /* Execute the host statements. */
+ if (hp != NULL) {
+ execute_statements_in_scope (NULL, packet, lease, NULL,
+ packet->options, options,
+ &lease->scope,
+ hp->group, lease->subnet->group);
+ }
+
+ /* Drop the request if it's not allowed for this client. */
+ if ((oc = lookup_option (&server_universe, options, SV_ALLOW_BOOTP)) &&
+ !evaluate_boolean_option_cache(&ignorep, packet, lease,
+ NULL,
+ packet->options, options,
+ &lease->scope, oc, MDL)) {
+ if (!ignorep)
+ log_info ("%s: bootp disallowed", msgbuf);
+ goto out;
+ }
+
+ if ((oc = lookup_option(&server_universe,
+ options, SV_ALLOW_BOOTING)) &&
+ !evaluate_boolean_option_cache(&ignorep, packet, lease,
+ NULL,
+ packet->options, options,
+ &lease->scope, oc, MDL)) {
+ if (!ignorep)
+ log_info ("%s: booting disallowed", msgbuf);
+ goto out;
+ }
+
+ /* Set up the outgoing packet... */
+ memset (&outgoing, 0, sizeof outgoing);
+ memset (&raw, 0, sizeof raw);
+ outgoing.raw = &raw;
+
+ /* If we didn't get a known vendor magic number on the way in,
+ just copy the input options to the output. */
+ i = SV_ALWAYS_REPLY_RFC1048;
+ if (!packet->options_valid &&
+ !(evaluate_boolean_option_cache(&ignorep, packet, lease, NULL,
+ packet->options, options,
+ &lease->scope,
+ lookup_option (&server_universe,
+ options, i), MDL))) {
+ if (packet->packet_length > DHCP_FIXED_NON_UDP) {
+ memcpy(outgoing.raw->options, packet->raw->options,
+ packet->packet_length - DHCP_FIXED_NON_UDP);
+ }
+
+ outgoing.packet_length =
+ (packet->packet_length < BOOTP_MIN_LEN)
+ ? BOOTP_MIN_LEN
+ : packet->packet_length;
+ } else {
+
+ /* Use the subnet mask from the subnet declaration if no other
+ mask has been provided. */
+ oc = (struct option_cache *)0;
+ i = DHO_SUBNET_MASK;
+ if (!lookup_option (&dhcp_universe, options, i)) {
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data
+ (&oc -> expression,
+ lease -> subnet -> netmask.iabuf,
+ lease -> subnet -> netmask.len,
+ 0, 0, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe,
+ options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+ }
+
+ /* If use-host-decl-names is enabled and there is a hostname
+ * defined in the host delcartion, send it back in hostname
+ * option */
+ use_host_decl_name(packet, lease, options);
+
+ /* Pack the options into the buffer. Unlike DHCP, we
+ can't pack options into the filename and server
+ name buffers. */
+
+ outgoing.packet_length =
+ cons_options (packet, outgoing.raw, lease,
+ (struct client_state *)0, 0,
+ packet -> options, options,
+ &lease -> scope,
+ 0, 0, 1, (struct data_string *)0,
+ (const char *)0);
+ if (outgoing.packet_length < BOOTP_MIN_LEN)
+ outgoing.packet_length = BOOTP_MIN_LEN;
+ }
+
+ /* Take the fields that we care about... */
+ raw.op = BOOTREPLY;
+ raw.htype = packet -> raw -> htype;
+ raw.hlen = packet -> raw -> hlen;
+ memcpy (raw.chaddr, packet -> raw -> chaddr, sizeof raw.chaddr);
+ raw.hops = packet -> raw -> hops;
+ raw.xid = packet -> raw -> xid;
+ raw.secs = packet -> raw -> secs;
+ raw.flags = packet -> raw -> flags;
+ raw.ciaddr = packet -> raw -> ciaddr;
+
+ /* yiaddr is an ipv4 address, it must be 4 octets. */
+ memcpy (&raw.yiaddr, lease->ip_addr.iabuf, 4);
+
+ /* If we're always supposed to broadcast to this client, set
+ the broadcast bit in the bootp flags field. */
+ if ((oc = lookup_option (&server_universe,
+ options, SV_ALWAYS_BROADCAST)) &&
+ evaluate_boolean_option_cache (&ignorep, packet, lease,
+ (struct client_state *)0,
+ packet -> options, options,
+ &lease -> scope, oc, MDL))
+ raw.flags |= htons (BOOTP_BROADCAST);
+
+ /* Figure out the address of the next server. */
+ memset (&d1, 0, sizeof d1);
+ oc = lookup_option (&server_universe, options, SV_NEXT_SERVER);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, options,
+ &lease -> scope, oc, MDL)) {
+ /* If there was more than one answer, take the first. */
+ if (d1.len >= 4 && d1.data)
+ memcpy (&raw.siaddr, d1.data, 4);
+ data_string_forget (&d1, MDL);
+ } else {
+ if ((lease->subnet->shared_network->interface != NULL) &&
+ lease->subnet->shared_network->interface->address_count)
+ raw.siaddr =
+ lease->subnet->shared_network->interface->addresses[0];
+ else if (packet->interface->address_count)
+ raw.siaddr = packet->interface->addresses[0];
+ }
+
+ raw.giaddr = packet -> raw -> giaddr;
+
+ /* Figure out the filename. */
+ oc = lookup_option (&server_universe, options, SV_FILENAME);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, options,
+ &lease -> scope, oc, MDL)) {
+ memcpy (raw.file, d1.data,
+ d1.len > sizeof raw.file ? sizeof raw.file : d1.len);
+ if (sizeof raw.file > d1.len)
+ memset (&raw.file [d1.len],
+ 0, (sizeof raw.file) - d1.len);
+ data_string_forget (&d1, MDL);
+ } else
+ memcpy (raw.file, packet -> raw -> file, sizeof raw.file);
+
+ /* Choose a server name as above. */
+ oc = lookup_option (&server_universe, options, SV_SERVER_NAME);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, options,
+ &lease -> scope, oc, MDL)) {
+ memcpy (raw.sname, d1.data,
+ d1.len > sizeof raw.sname ? sizeof raw.sname : d1.len);
+ if (sizeof raw.sname > d1.len)
+ memset (&raw.sname [d1.len],
+ 0, (sizeof raw.sname) - d1.len);
+ data_string_forget (&d1, MDL);
+ }
+
+ /* Execute the commit statements, if there are any. */
+ execute_statements ((struct binding_value **)0,
+ packet, lease, (struct client_state *)0,
+ packet -> options,
+ options, &lease -> scope, lease -> on_commit);
+
+ /* We're done with the option state. */
+ option_state_dereference (&options, MDL);
+
+ /* Set up the hardware destination address... */
+ hto.hbuf [0] = packet -> raw -> htype;
+ hto.hlen = packet -> raw -> hlen + 1;
+ memcpy (&hto.hbuf [1], packet -> raw -> chaddr, packet -> raw -> hlen);
+
+ if (packet->interface->address_count) {
+ from = packet->interface->addresses[0];
+ } else {
+ log_error("%s: Interface %s appears to have no IPv4 "
+ "addresses, and so dhcpd cannot select a source "
+ "address.", msgbuf, packet->interface->name);
+ goto out;
+ }
+
+ /* Report what we're doing... */
+ log_info("%s", msgbuf);
+ log_info("BOOTREPLY for %s to %s (%s) via %s",
+ piaddr(lease->ip_addr),
+ ((hp != NULL) && (hp->name != NULL)) ? hp -> name : "unknown",
+ print_hw_addr (packet->raw->htype,
+ packet->raw->hlen,
+ packet->raw->chaddr),
+ packet->raw->giaddr.s_addr
+ ? inet_ntoa (packet->raw->giaddr)
+ : packet->interface->name);
+
+ /* Set up the parts of the address that are in common. */
+ to.sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+ to.sin_len = sizeof to;
+#endif
+ memset (to.sin_zero, 0, sizeof to.sin_zero);
+
+ /* If this was gatewayed, send it back to the gateway... */
+ if (raw.giaddr.s_addr) {
+ to.sin_addr = raw.giaddr;
+ to.sin_port = local_port;
+
+ if (fallback_interface) {
+ result = send_packet (fallback_interface, NULL, &raw,
+ outgoing.packet_length, from,
+ &to, &hto);
+ if (result < 0) {
+ log_error ("%s:%d: Failed to send %d byte long "
+ "packet over %s interface.", MDL,
+ outgoing.packet_length,
+ fallback_interface->name);
+ }
+
+ goto out;
+ }
+
+ /* If it comes from a client that already knows its address
+ and is not requesting a broadcast response, and we can
+ unicast to a client without using the ARP protocol, sent it
+ directly to that client. */
+ } else if (!(raw.flags & htons (BOOTP_BROADCAST)) &&
+ can_unicast_without_arp (packet -> interface)) {
+ to.sin_addr = raw.yiaddr;
+ to.sin_port = remote_port;
+
+ /* Otherwise, broadcast it on the local network. */
+ } else {
+ to.sin_addr = limited_broadcast;
+ to.sin_port = remote_port; /* XXX */
+ }
+
+ errno = 0;
+ result = send_packet(packet->interface, packet, &raw,
+ outgoing.packet_length, from, &to, &hto);
+ if (result < 0) {
+ log_error ("%s:%d: Failed to send %d byte long packet over %s"
+ " interface.", MDL, outgoing.packet_length,
+ packet->interface->name);
+ }
+
+ out:
+
+ if (options)
+ option_state_dereference (&options, MDL);
+ if (lease)
+ lease_dereference (&lease, MDL);
+ if (hp)
+ host_dereference (&hp, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+}
diff --git a/server/class.c b/server/class.c
new file mode 100644
index 0000000..1fbe7d4
--- /dev/null
+++ b/server/class.c
@@ -0,0 +1,303 @@
+/* class.c
+
+ Handling for client classes. */
+
+/*
+ * Copyright (c) 2009,2012-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"
+
+struct collection default_collection = {
+ (struct collection *)0,
+ "default",
+ (struct class *)0,
+};
+
+struct collection *collections = &default_collection;
+struct executable_statement *default_classification_rules;
+
+int have_billing_classes;
+
+/* Build the default classification rule tree. */
+
+void classification_setup ()
+{
+ /* eval ... */
+ default_classification_rules = (struct executable_statement *)0;
+ if (!executable_statement_allocate (&default_classification_rules,
+ MDL))
+ log_fatal ("Can't allocate check of default collection");
+ default_classification_rules -> op = eval_statement;
+
+ /* check-collection "default" */
+ if (!expression_allocate (&default_classification_rules -> data.eval,
+ MDL))
+ log_fatal ("Can't allocate default check expression");
+ default_classification_rules -> data.eval -> op = expr_check;
+ default_classification_rules -> data.eval -> data.check =
+ &default_collection;
+}
+
+void classify_client (packet)
+ struct packet *packet;
+{
+ execute_statements ((struct binding_value **)0, packet,
+ (struct lease *)0, (struct client_state *)0,
+ packet -> options, (struct option_state *)0,
+ &global_scope, default_classification_rules);
+}
+
+int check_collection (packet, lease, collection)
+ struct packet *packet;
+ struct lease *lease;
+ struct collection *collection;
+{
+ struct class *class, *nc;
+ struct data_string data;
+ int matched = 0;
+ int status;
+ int ignorep;
+ int classfound;
+
+ for (class = collection -> classes; class; class = class -> nic) {
+#if defined (DEBUG_CLASS_MATCHING)
+ log_info ("checking against class %s...", class -> name);
+#endif
+ memset (&data, 0, sizeof data);
+
+ /* If there is a "match if" expression, check it. If
+ we get a match, and there's no subclass expression,
+ it's a match. If we get a match and there is a subclass
+ expression, then we check the submatch. If it's not a
+ match, that's final - we don't check the submatch. */
+
+ if (class -> expr) {
+ status = (evaluate_boolean_expression_result
+ (&ignorep, packet, lease,
+ (struct client_state *)0,
+ packet -> options, (struct option_state *)0,
+ lease ? &lease -> scope : &global_scope,
+ class -> expr));
+ if (status) {
+ if (!class -> submatch) {
+ matched = 1;
+#if defined (DEBUG_CLASS_MATCHING)
+ log_info ("matches class.");
+#endif
+ classify (packet, class);
+ continue;
+ }
+ } else
+ continue;
+ }
+
+ /* Check to see if the client matches an existing subclass.
+ If it doesn't, and this is a spawning class, spawn a new
+ subclass and put the client in it. */
+ if (class -> submatch) {
+ status = (evaluate_data_expression
+ (&data, packet, lease,
+ (struct client_state *)0,
+ packet -> options, (struct option_state *)0,
+ lease ? &lease -> scope : &global_scope,
+ class -> submatch, MDL));
+ if (status && data.len) {
+ nc = (struct class *)0;
+ classfound = class_hash_lookup (&nc, class -> hash,
+ (const char *)data.data, data.len, MDL);
+
+#ifdef LDAP_CONFIGURATION
+ if (!classfound && find_subclass_in_ldap (class, &nc, &data))
+ classfound = 1;
+#endif
+
+ if (classfound) {
+#if defined (DEBUG_CLASS_MATCHING)
+ log_info ("matches subclass %s.",
+ print_hex_1 (data.len,
+ data.data, 60));
+#endif
+ data_string_forget (&data, MDL);
+ classify (packet, nc);
+ matched = 1;
+ class_dereference (&nc, MDL);
+ continue;
+ }
+ if (!class -> spawning) {
+ data_string_forget (&data, MDL);
+ continue;
+ }
+ /* XXX Write out the spawned class? */
+#if defined (DEBUG_CLASS_MATCHING)
+ log_info ("spawning subclass %s.",
+ print_hex_1 (data.len, data.data, 60));
+#endif
+ status = class_allocate (&nc, MDL);
+ group_reference (&nc -> group,
+ class -> group, MDL);
+ class_reference (&nc -> superclass,
+ class, MDL);
+ nc -> lease_limit = class -> lease_limit;
+ nc -> dirty = 1;
+ if (nc -> lease_limit) {
+ nc -> billed_leases =
+ (dmalloc
+ (nc -> lease_limit *
+ sizeof (struct lease *),
+ MDL));
+ if (!nc -> billed_leases) {
+ log_error ("no memory for%s",
+ " billing");
+ data_string_forget
+ (&nc -> hash_string,
+ MDL);
+ class_dereference (&nc, MDL);
+ data_string_forget (&data,
+ MDL);
+ continue;
+ }
+ memset (nc -> billed_leases, 0,
+ (nc -> lease_limit *
+ sizeof (struct lease *)));
+ }
+ data_string_copy (&nc -> hash_string, &data,
+ MDL);
+ data_string_forget (&data, MDL);
+ if (!class -> hash)
+ class_new_hash(&class->hash,
+ SCLASS_HASH_SIZE, MDL);
+ class_hash_add (class -> hash,
+ (const char *)
+ nc -> hash_string.data,
+ nc -> hash_string.len,
+ nc, MDL);
+ classify (packet, nc);
+ class_dereference (&nc, MDL);
+ }
+ }
+ }
+ return matched;
+}
+
+void classify (packet, class)
+ struct packet *packet;
+ struct class *class;
+{
+ if (packet -> class_count < PACKET_MAX_CLASSES)
+ class_reference (&packet -> classes [packet -> class_count++],
+ class, MDL);
+ else
+ log_error ("too many classes match %s",
+ print_hw_addr (packet -> raw -> htype,
+ packet -> raw -> hlen,
+ packet -> raw -> chaddr));
+}
+
+
+isc_result_t unlink_class(struct class **class) {
+ struct collection *lp;
+ struct class *cp, *pp;
+
+ for (lp = collections; lp; lp = lp -> next) {
+ for (pp = 0, cp = lp -> classes; cp; pp = cp, cp = cp -> nic)
+ if (cp == *class) {
+ if (pp == 0) {
+ lp->classes = cp->nic;
+ } else {
+ pp->nic = cp->nic;
+ }
+ cp->nic = 0;
+ class_dereference(class, MDL);
+
+ return ISC_R_SUCCESS;
+ }
+ }
+ return ISC_R_NOTFOUND;
+}
+
+
+isc_result_t find_class (struct class **class, const char *name,
+ const char *file, int line)
+{
+ struct collection *lp;
+ struct class *cp;
+
+ for (lp = collections; lp; lp = lp -> next) {
+ for (cp = lp -> classes; cp; cp = cp -> nic)
+ if (cp -> name && !strcmp (name, cp -> name)) {
+ return class_reference (class, cp, file, line);
+ }
+ }
+ return ISC_R_NOTFOUND;
+}
+
+int unbill_class (lease, class)
+ struct lease *lease;
+ struct class *class;
+{
+ int i;
+
+ for (i = 0; i < class -> lease_limit; i++)
+ if (class -> billed_leases [i] == lease)
+ break;
+ if (i == class -> lease_limit) {
+ log_error ("lease %s unbilled with no billing arrangement.",
+ piaddr (lease -> ip_addr));
+ return 0;
+ }
+ class_dereference (&lease -> billing_class, MDL);
+ lease_dereference (&class -> billed_leases [i], MDL);
+ class -> leases_consumed--;
+ return 1;
+}
+
+int bill_class (lease, class)
+ struct lease *lease;
+ struct class *class;
+{
+ int i;
+
+ if (lease -> billing_class) {
+ log_error ("lease billed with existing billing arrangement.");
+ unbill_class (lease, lease -> billing_class);
+ }
+
+ if (class -> leases_consumed == class -> lease_limit)
+ return 0;
+
+ for (i = 0; i < class -> lease_limit; i++)
+ if (!class -> billed_leases [i])
+ break;
+
+ if (i == class -> lease_limit) {
+ log_error ("class billing consumption disagrees with leases.");
+ return 0;
+ }
+
+ lease_reference (&class -> billed_leases [i], lease, MDL);
+ class_reference (&lease -> billing_class, class, MDL);
+ class -> leases_consumed++;
+ return 1;
+}
diff --git a/server/confpars.c b/server/confpars.c
new file mode 100644
index 0000000..89cbce5
--- /dev/null
+++ b/server/confpars.c
@@ -0,0 +1,5606 @@
+/* confpars.c
+
+ Parser 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"
+
+static unsigned char global_host_once = 1;
+static unsigned char dhcpv6_class_once = 1;
+
+static int parse_binding_value(struct parse *cfile,
+ struct binding_value *value);
+
+#if defined (TRACING)
+trace_type_t *trace_readconf_type;
+trace_type_t *trace_readleases_type;
+
+void parse_trace_setup ()
+{
+ trace_readconf_type = trace_type_register ("readconf", (void *)0,
+ trace_conf_input,
+ trace_conf_stop, MDL);
+ trace_readleases_type = trace_type_register ("readleases", (void *)0,
+ trace_conf_input,
+ trace_conf_stop, MDL);
+}
+#endif
+
+/* conf-file :== parameters declarations END_OF_FILE
+ parameters :== <nil> | parameter | parameters parameter
+ declarations :== <nil> | declaration | declarations declaration */
+
+isc_result_t readconf ()
+{
+ isc_result_t res;
+
+ res = read_conf_file (path_dhcpd_conf, root_group, ROOT_GROUP, 0);
+#if defined(LDAP_CONFIGURATION)
+ if (res != ISC_R_SUCCESS)
+ return (res);
+
+ return ldap_read_config ();
+#else
+ return (res);
+#endif
+}
+
+isc_result_t read_conf_file (const char *filename, struct group *group,
+ int group_type, int leasep)
+{
+ int file;
+ struct parse *cfile;
+ isc_result_t status;
+#if defined (TRACING)
+ char *fbuf, *dbuf;
+ off_t flen;
+ int result;
+ unsigned tflen, ulen;
+ trace_type_t *ttype;
+
+ if (leasep)
+ ttype = trace_readleases_type;
+ else
+ ttype = trace_readconf_type;
+
+ /* If we're in playback, we need to snarf the contents of the
+ named file out of the playback file rather than trying to
+ open and read it. */
+ if (trace_playback ()) {
+ dbuf = (char *)0;
+ tflen = 0;
+ status = trace_get_file (ttype, filename, &tflen, &dbuf);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ ulen = tflen;
+
+ /* What we get back is filename\0contents, where contents is
+ terminated just by the length. So we figure out the length
+ of the filename, and subtract that and the NUL from the
+ total length to get the length of the contents of the file.
+ We make fbuf a pointer to the contents of the file, and
+ leave dbuf as it is so we can free it later. */
+ tflen = strlen (dbuf);
+ ulen = ulen - tflen - 1;
+ fbuf = dbuf + tflen + 1;
+ goto memfile;
+ }
+#endif
+
+ if ((file = open (filename, O_RDONLY)) < 0) {
+ if (leasep) {
+ log_error ("Can't open lease database %s: %m --",
+ path_dhcpd_db);
+ log_error (" check for failed database %s!",
+ "rewrite attempt");
+ log_error ("Please read the dhcpd.leases manual%s",
+ " page if you");
+ log_fatal ("don't know what to do about this.");
+ } else {
+ log_fatal ("Can't open %s: %m", filename);
+ }
+ }
+
+ cfile = (struct parse *)0;
+#if defined (TRACING)
+ flen = lseek (file, (off_t)0, SEEK_END);
+ if (flen < 0) {
+ boom:
+ log_fatal ("Can't lseek on %s: %m", filename);
+ }
+ if (lseek (file, (off_t)0, SEEK_SET) < 0)
+ goto boom;
+ /* Can't handle files greater than 2^31-1. */
+ if (flen > 0x7FFFFFFFUL)
+ log_fatal ("%s: file is too long to buffer.", filename);
+ ulen = flen;
+
+ /* Allocate a buffer that will be what's written to the tracefile,
+ and also will be what we parse from. */
+ tflen = strlen (filename);
+ dbuf = dmalloc (ulen + tflen + 1, MDL);
+ if (!dbuf)
+ log_fatal ("No memory for %s (%d bytes)",
+ filename, ulen);
+
+ /* Copy the name into the beginning, nul-terminated. */
+ strcpy (dbuf, filename);
+
+ /* Load the file in after the NUL. */
+ fbuf = dbuf + tflen + 1;
+ result = read (file, fbuf, ulen);
+ if (result < 0)
+ log_fatal ("Can't read in %s: %m", filename);
+ if (result != ulen)
+ log_fatal ("%s: short read of %d bytes instead of %d.",
+ filename, ulen, result);
+ close (file);
+ memfile:
+ /* If we're recording, write out the filename and file contents. */
+ if (trace_record ())
+ trace_write_packet (ttype, ulen + tflen + 1, dbuf, MDL);
+ status = new_parse(&cfile, -1, fbuf, ulen, filename, 0); /* XXX */
+#else
+ status = new_parse(&cfile, file, NULL, 0, filename, 0);
+#endif
+ if (status != ISC_R_SUCCESS || cfile == NULL)
+ return status;
+
+ if (leasep)
+ status = lease_file_subparse (cfile);
+ else
+ status = conf_file_subparse (cfile, group, group_type);
+ end_parse (&cfile);
+#if defined (TRACING)
+ dfree (dbuf, MDL);
+#endif
+ return status;
+}
+
+#if defined (TRACING)
+void trace_conf_input (trace_type_t *ttype, unsigned len, char *data)
+{
+ char *fbuf;
+ unsigned flen;
+ unsigned tflen;
+ struct parse *cfile = (struct parse *)0;
+ static int postconf_initialized;
+ static int leaseconf_initialized;
+ isc_result_t status;
+
+ /* Do what's done above, except that we don't have to read in the
+ data, because it's already been read for us. */
+ tflen = strlen (data);
+ flen = len - tflen - 1;
+ fbuf = data + tflen + 1;
+
+ /* If we're recording, write out the filename and file contents. */
+ if (trace_record ())
+ trace_write_packet (ttype, len, data, MDL);
+
+ status = new_parse(&cfile, -1, fbuf, flen, data, 0);
+ if (status == ISC_R_SUCCESS || cfile != NULL) {
+ if (ttype == trace_readleases_type)
+ lease_file_subparse (cfile);
+ else
+ conf_file_subparse (cfile, root_group, ROOT_GROUP);
+ end_parse (&cfile);
+ }
+
+ /* Postconfiguration needs to be done after the config file
+ has been loaded. */
+ if (!postconf_initialized && ttype == trace_readconf_type) {
+ postconf_initialization (0);
+ postconf_initialized = 1;
+ }
+
+ if (!leaseconf_initialized && ttype == trace_readleases_type) {
+ db_startup (0);
+ leaseconf_initialized = 1;
+ postdb_startup ();
+ }
+}
+
+void trace_conf_stop (trace_type_t *ttype) { }
+#endif
+
+/* conf-file :== parameters declarations END_OF_FILE
+ parameters :== <nil> | parameter | parameters parameter
+ declarations :== <nil> | declaration | declarations declaration */
+
+isc_result_t conf_file_subparse (struct parse *cfile, struct group *group,
+ int group_type)
+{
+ const char *val;
+ enum dhcp_token token;
+ int declaration = 0;
+ int status;
+
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == END_OF_FILE)
+ break;
+ declaration = parse_statement (cfile, group, group_type,
+ (struct host_decl *)0,
+ declaration);
+ } while (1);
+ skip_token(&val, (unsigned *)0, cfile);
+
+ status = cfile->warnings_occurred ? DHCP_R_BADPARSE : ISC_R_SUCCESS;
+ return status;
+}
+
+/* lease-file :== lease-declarations END_OF_FILE
+ lease-statements :== <nil>
+ | lease-declaration
+ | lease-declarations lease-declaration */
+
+isc_result_t lease_file_subparse (struct parse *cfile)
+{
+ const char *val;
+ enum dhcp_token token;
+ isc_result_t status;
+
+ do {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token == END_OF_FILE)
+ break;
+ if (token == LEASE) {
+ struct lease *lease = (struct lease *)0;
+ if (parse_lease_declaration (&lease, cfile)) {
+ enter_lease (lease);
+ lease_dereference (&lease, MDL);
+ } else
+ parse_warn (cfile,
+ "possibly corrupt lease file");
+ } else if (token == IA_NA) {
+ parse_ia_na_declaration(cfile);
+ } else if (token == IA_TA) {
+ parse_ia_ta_declaration(cfile);
+ } else if (token == IA_PD) {
+ parse_ia_pd_declaration(cfile);
+ } else if (token == CLASS) {
+ parse_class_declaration(0, cfile, root_group,
+ CLASS_TYPE_CLASS);
+ } else if (token == SUBCLASS) {
+ parse_class_declaration(0, cfile, root_group,
+ CLASS_TYPE_SUBCLASS);
+ } else if (token == HOST) {
+ parse_host_declaration (cfile, root_group);
+ } else if (token == GROUP) {
+ parse_group_declaration (cfile, root_group);
+#if defined (FAILOVER_PROTOCOL)
+ } else if (token == FAILOVER) {
+ parse_failover_state_declaration
+ (cfile, (dhcp_failover_state_t *)0);
+#endif
+#ifdef DHCPv6
+ } else if (token == SERVER_DUID) {
+ parse_server_duid(cfile);
+#endif /* DHCPv6 */
+ } else {
+ log_error ("Corrupt lease file - possible data loss!");
+ skip_to_semi (cfile);
+ }
+
+ } while (1);
+
+ status = cfile->warnings_occurred ? DHCP_R_BADPARSE : ISC_R_SUCCESS;
+ return status;
+}
+
+/* statement :== parameter | declaration
+
+ parameter :== DEFAULT_LEASE_TIME lease_time
+ | MAX_LEASE_TIME lease_time
+ | DYNAMIC_BOOTP_LEASE_CUTOFF date
+ | DYNAMIC_BOOTP_LEASE_LENGTH lease_time
+ | BOOT_UNKNOWN_CLIENTS boolean
+ | ONE_LEASE_PER_CLIENT boolean
+ | GET_LEASE_HOSTNAMES boolean
+ | USE_HOST_DECL_NAME boolean
+ | NEXT_SERVER ip-addr-or-hostname SEMI
+ | option_parameter
+ | SERVER-IDENTIFIER ip-addr-or-hostname SEMI
+ | FILENAME string-parameter
+ | SERVER_NAME string-parameter
+ | hardware-parameter
+ | fixed-address-parameter
+ | ALLOW allow-deny-keyword
+ | DENY allow-deny-keyword
+ | USE_LEASE_ADDR_FOR_DEFAULT_ROUTE boolean
+ | AUTHORITATIVE
+ | NOT AUTHORITATIVE
+
+ declaration :== host-declaration
+ | group-declaration
+ | shared-network-declaration
+ | subnet-declaration
+ | VENDOR_CLASS class-declaration
+ | USER_CLASS class-declaration
+ | RANGE address-range-declaration */
+
+int parse_statement (cfile, group, type, host_decl, declaration)
+ struct parse *cfile;
+ struct group *group;
+ int type;
+ struct host_decl *host_decl;
+ int declaration;
+{
+ enum dhcp_token token;
+ const char *val;
+ struct shared_network *share;
+ char *n;
+ struct hardware hardware;
+ struct executable_statement *et, *ep;
+ struct option *option = NULL;
+ struct option_cache *cache;
+ int lose;
+ int known;
+ isc_result_t status;
+ unsigned code;
+
+ token = peek_token (&val, (unsigned *)0, cfile);
+
+ switch (token) {
+ case INCLUDE:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING) {
+ parse_warn (cfile, "filename string expected.");
+ skip_to_semi (cfile);
+ } else {
+ status = read_conf_file (val, group, type, 0);
+ if (status != ISC_R_SUCCESS)
+ parse_warn (cfile, "%s: bad parse.", val);
+ parse_semi (cfile);
+ }
+ return 1;
+
+ case HOST:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (type != HOST_DECL && type != CLASS_DECL) {
+ if (global_host_once &&
+ (type == SUBNET_DECL || type == SHARED_NET_DECL)) {
+ global_host_once = 0;
+ log_error("WARNING: Host declarations are "
+ "global. They are not limited to "
+ "the scope you declared them in.");
+ }
+
+ parse_host_declaration (cfile, group);
+ } else {
+ parse_warn (cfile,
+ "host declarations not allowed here.");
+ skip_to_semi (cfile);
+ }
+ return 1;
+
+ case GROUP:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (type != HOST_DECL && type != CLASS_DECL)
+ parse_group_declaration (cfile, group);
+ else {
+ parse_warn (cfile,
+ "group declarations not allowed here.");
+ skip_to_semi (cfile);
+ }
+ return 1;
+
+ case SHARED_NETWORK:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (type == SHARED_NET_DECL ||
+ type == HOST_DECL ||
+ type == SUBNET_DECL ||
+ type == CLASS_DECL) {
+ parse_warn (cfile, "shared-network parameters not %s.",
+ "allowed here");
+ skip_to_semi (cfile);
+ break;
+ }
+
+ parse_shared_net_declaration (cfile, group);
+ return 1;
+
+ case SUBNET:
+ case SUBNET6:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (type == HOST_DECL || type == SUBNET_DECL ||
+ type == CLASS_DECL) {
+ parse_warn (cfile,
+ "subnet declarations not allowed here.");
+ skip_to_semi (cfile);
+ return 1;
+ }
+
+ /* If we're in a subnet declaration, just do the parse. */
+ if (group->shared_network != NULL) {
+ if (token == SUBNET) {
+ parse_subnet_declaration(cfile,
+ group->shared_network);
+ } else {
+ parse_subnet6_declaration(cfile,
+ group->shared_network);
+ }
+ break;
+ }
+
+ /*
+ * Otherwise, cons up a fake shared network structure
+ * and populate it with the lone subnet...because the
+ * intention most likely is to refer to the entire link
+ * by shorthand, any configuration inside the subnet is
+ * actually placed in the shared-network's group.
+ */
+
+ share = NULL;
+ status = shared_network_allocate (&share, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't allocate shared subnet: %s",
+ isc_result_totext (status));
+ if (!clone_group (&share -> group, group, MDL))
+ log_fatal ("Can't allocate group for shared net");
+ shared_network_reference (&share -> group -> shared_network,
+ share, MDL);
+
+ /*
+ * This is an implicit shared network, not explicit in
+ * the config.
+ */
+ share->flags |= SHARED_IMPLICIT;
+
+ if (token == SUBNET) {
+ parse_subnet_declaration(cfile, share);
+ } else {
+ parse_subnet6_declaration(cfile, share);
+ }
+
+ /* share -> subnets is the subnet we just parsed. */
+ if (share->subnets) {
+ interface_reference(&share->interface,
+ share->subnets->interface,
+ MDL);
+
+ /* Make the shared network name from network number. */
+ if (token == SUBNET) {
+ n = piaddrmask(&share->subnets->net,
+ &share->subnets->netmask);
+ } else {
+ n = piaddrcidr(&share->subnets->net,
+ share->subnets->prefix_len);
+ }
+
+ share->name = strdup(n);
+
+ if (share->name == NULL)
+ log_fatal("Out of memory allocating default "
+ "shared network name (\"%s\").", n);
+
+ /* Copy the authoritative parameter from the subnet,
+ since there is no opportunity to declare it here. */
+ share->group->authoritative =
+ share->subnets->group->authoritative;
+ enter_shared_network(share);
+ }
+ shared_network_dereference(&share, MDL);
+ return 1;
+
+ case VENDOR_CLASS:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (type == CLASS_DECL) {
+ parse_warn (cfile,
+ "class declarations not allowed here.");
+ skip_to_semi (cfile);
+ break;
+ }
+ parse_class_declaration(NULL, cfile, group, CLASS_TYPE_VENDOR);
+ return 1;
+
+ case USER_CLASS:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (type == CLASS_DECL) {
+ parse_warn (cfile,
+ "class declarations not allowed here.");
+ skip_to_semi (cfile);
+ break;
+ }
+ parse_class_declaration(NULL, cfile, group, CLASS_TYPE_USER);
+ return 1;
+
+ case CLASS:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (type == CLASS_DECL) {
+ parse_warn (cfile,
+ "class declarations not allowed here.");
+ skip_to_semi (cfile);
+ break;
+ }
+ parse_class_declaration(NULL, cfile, group, CLASS_TYPE_CLASS);
+ return 1;
+
+ case SUBCLASS:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (type == CLASS_DECL) {
+ parse_warn (cfile,
+ "class declarations not allowed here.");
+ skip_to_semi (cfile);
+ break;
+ }
+ parse_class_declaration(NULL, cfile, group,
+ CLASS_TYPE_SUBCLASS);
+ return 1;
+
+ case HARDWARE:
+ skip_token(&val, (unsigned *)0, cfile);
+ memset (&hardware, 0, sizeof hardware);
+ if (host_decl && memcmp(&hardware, &(host_decl->interface),
+ sizeof(hardware)) != 0) {
+ parse_warn(cfile, "Host %s hardware address already "
+ "configured.", host_decl->name);
+ break;
+ }
+
+ parse_hardware_param (cfile, &hardware);
+ if (host_decl)
+ host_decl -> interface = hardware;
+ else
+ parse_warn (cfile, "hardware address parameter %s",
+ "not allowed here.");
+ break;
+
+ case FIXED_ADDR:
+ case FIXED_ADDR6:
+ skip_token(&val, NULL, cfile);
+ cache = NULL;
+ if (parse_fixed_addr_param(&cache, cfile, token)) {
+ if (host_decl) {
+ if (host_decl->fixed_addr) {
+ option_cache_dereference(&cache, MDL);
+ parse_warn(cfile,
+ "Only one fixed address "
+ "declaration per host.");
+ } else {
+ host_decl->fixed_addr = cache;
+ }
+ } else {
+ parse_warn(cfile,
+ "fixed-address parameter not "
+ "allowed here.");
+ option_cache_dereference(&cache, MDL);
+ }
+ }
+ break;
+
+ case POOL:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (type == POOL_DECL) {
+ parse_warn (cfile, "pool declared within pool.");
+ skip_to_semi(cfile);
+ } else if (type != SUBNET_DECL && type != SHARED_NET_DECL) {
+ parse_warn (cfile, "pool declared outside of network");
+ skip_to_semi(cfile);
+ } else
+ parse_pool_statement (cfile, group, type);
+
+ return declaration;
+
+ case RANGE:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (type != SUBNET_DECL || !group -> subnet) {
+ parse_warn (cfile,
+ "range declaration not allowed here.");
+ skip_to_semi (cfile);
+ return declaration;
+ }
+ parse_address_range (cfile, group, type, (struct pool *)0,
+ (struct lease **)0);
+ return declaration;
+
+#ifdef DHCPv6
+ case RANGE6:
+ skip_token(NULL, NULL, cfile);
+ if ((type != SUBNET_DECL) || (group->subnet == NULL)) {
+ parse_warn (cfile,
+ "range6 declaration not allowed here.");
+ skip_to_semi(cfile);
+ return declaration;
+ }
+ parse_address_range6(cfile, group);
+ return declaration;
+
+ case PREFIX6:
+ skip_token(NULL, NULL, cfile);
+ if ((type != SUBNET_DECL) || (group->subnet == NULL)) {
+ parse_warn (cfile,
+ "prefix6 declaration not allowed here.");
+ skip_to_semi(cfile);
+ return declaration;
+ }
+ parse_prefix6(cfile, group);
+ return declaration;
+
+ case FIXED_PREFIX6:
+ skip_token(&val, NULL, cfile);
+ if (!host_decl) {
+ parse_warn (cfile,
+ "fixed-prefix6 declaration not "
+ "allowed here.");
+ skip_to_semi(cfile);
+ break;
+ }
+ parse_fixed_prefix6(cfile, host_decl);
+ break;
+
+#endif /* DHCPv6 */
+
+ case TOKEN_NOT:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case AUTHORITATIVE:
+ group -> authoritative = 0;
+ goto authoritative;
+ default:
+ parse_warn (cfile, "expecting assertion");
+ skip_to_semi (cfile);
+ break;
+ }
+ break;
+ case AUTHORITATIVE:
+ skip_token(&val, (unsigned *)0, cfile);
+ group -> authoritative = 1;
+ authoritative:
+ if (type == HOST_DECL)
+ parse_warn (cfile, "authority makes no sense here.");
+ parse_semi (cfile);
+ break;
+
+ /* "server-identifier" is a special hack, equivalent to
+ "option dhcp-server-identifier". */
+ case SERVER_IDENTIFIER:
+ code = DHO_DHCP_SERVER_IDENTIFIER;
+ if (!option_code_hash_lookup(&option, dhcp_universe.code_hash,
+ &code, 0, MDL))
+ log_fatal("Server identifier not in hash (%s:%d).",
+ MDL);
+ skip_token(&val, (unsigned *)0, cfile);
+ goto finish_option;
+
+ case OPTION:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == SPACE) {
+ if (type != ROOT_GROUP) {
+ parse_warn (cfile,
+ "option space definitions %s",
+ "may not be scoped.");
+ skip_to_semi (cfile);
+ break;
+ }
+ parse_option_space_decl (cfile);
+ return declaration;
+ }
+
+ known = 0;
+ status = parse_option_name(cfile, 1, &known, &option);
+ if (status == ISC_R_SUCCESS) {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == CODE) {
+ if (type != ROOT_GROUP) {
+ parse_warn (cfile,
+ "option definitions%s",
+ " may not be scoped.");
+ skip_to_semi (cfile);
+ option_dereference(&option, MDL);
+ break;
+ }
+ skip_token(&val, (unsigned *)0, cfile);
+
+ /*
+ * If the option was known, remove it from the
+ * code and name hashes before redefining it.
+ */
+ if (known) {
+ option_name_hash_delete(
+ option->universe->name_hash,
+ option->name, 0, MDL);
+ option_code_hash_delete(
+ option->universe->code_hash,
+ &option->code, 0, MDL);
+ }
+
+ parse_option_code_definition(cfile, option);
+ option_dereference(&option, MDL);
+ return declaration;
+ }
+
+ /* If this wasn't an option code definition, don't
+ allow an unknown option. */
+ if (!known) {
+ parse_warn (cfile, "unknown option %s.%s",
+ option -> universe -> name,
+ option -> name);
+ skip_to_semi (cfile);
+ option_dereference(&option, MDL);
+ return declaration;
+ }
+
+ finish_option:
+ et = (struct executable_statement *)0;
+ if (!parse_option_statement
+ (&et, cfile, 1, option,
+ supersede_option_statement))
+ return declaration;
+ option_dereference(&option, MDL);
+ goto insert_statement;
+ } else
+ return declaration;
+
+ break;
+
+ case FAILOVER:
+ if (type != ROOT_GROUP && type != SHARED_NET_DECL) {
+ parse_warn (cfile, "failover peers may only be %s",
+ "defined in shared-network");
+ log_error ("declarations and the outer scope.");
+ skip_to_semi (cfile);
+ break;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+#if defined (FAILOVER_PROTOCOL)
+ parse_failover_peer (cfile, group, type);
+#else
+ parse_warn (cfile, "No failover support.");
+ skip_to_semi (cfile);
+#endif
+ break;
+
+#ifdef DHCPv6
+ case SERVER_DUID:
+ parse_server_duid_conf(cfile);
+ break;
+#endif /* DHCPv6 */
+
+ default:
+ et = (struct executable_statement *)0;
+ lose = 0;
+ if (!parse_executable_statement (&et, cfile, &lose,
+ context_any)) {
+ if (!lose) {
+ if (declaration)
+ parse_warn (cfile,
+ "expecting a declaration");
+ else
+ parse_warn (cfile,
+ "expecting a parameter %s",
+ "or declaration");
+ skip_to_semi (cfile);
+ }
+ return declaration;
+ }
+ if (!et)
+ return declaration;
+ insert_statement:
+ if (group -> statements) {
+ int multi = 0;
+
+ /* If this set of statements is only referenced
+ by this group, just add the current statement
+ to the end of the chain. */
+ for (ep = group -> statements; ep -> next;
+ ep = ep -> next)
+ if (ep -> refcnt > 1) /* XXX */
+ multi = 1;
+ if (!multi) {
+ executable_statement_reference (&ep -> next,
+ et, MDL);
+ executable_statement_dereference (&et, MDL);
+ return declaration;
+ }
+
+ /* Otherwise, make a parent chain, and put the
+ current group statements first and the new
+ statement in the next pointer. */
+ ep = (struct executable_statement *)0;
+ if (!executable_statement_allocate (&ep, MDL))
+ log_fatal ("No memory for statements.");
+ ep -> op = statements_statement;
+ executable_statement_reference (&ep -> data.statements,
+ group -> statements,
+ MDL);
+ executable_statement_reference (&ep -> next, et, MDL);
+ executable_statement_dereference (&group -> statements,
+ MDL);
+ executable_statement_reference (&group -> statements,
+ ep, MDL);
+ executable_statement_dereference (&ep, MDL);
+ } else {
+ executable_statement_reference (&group -> statements,
+ et, MDL);
+ }
+ executable_statement_dereference (&et, MDL);
+ return declaration;
+ }
+
+ return 0;
+}
+
+#if defined (FAILOVER_PROTOCOL)
+void parse_failover_peer (cfile, group, type)
+ struct parse *cfile;
+ struct group *group;
+ int type;
+{
+ enum dhcp_token token;
+ const char *val;
+ dhcp_failover_state_t *peer;
+ u_int32_t *tp;
+ char *name;
+ u_int32_t split;
+ u_int8_t hba [32];
+ unsigned hba_len = sizeof hba;
+ int i;
+ struct expression *expr;
+ isc_result_t status;
+ dhcp_failover_config_t *cp;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != PEER) {
+ parse_warn (cfile, "expecting \"peer\"");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (is_identifier (token) || token == STRING) {
+ name = dmalloc (strlen (val) + 1, MDL);
+ if (!name)
+ log_fatal ("no memory for peer name %s", name);
+ strcpy (name, val);
+ } else {
+ parse_warn (cfile, "expecting failover peer name.");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ /* See if there's a peer declaration by this name. */
+ peer = (dhcp_failover_state_t *)0;
+ find_failover_peer (&peer, name, MDL);
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token == SEMI) {
+ if (type != SHARED_NET_DECL)
+ parse_warn (cfile, "failover peer reference not %s",
+ "in shared-network declaration");
+ else {
+ if (!peer) {
+ parse_warn (cfile, "reference to unknown%s%s",
+ " failover peer ", name);
+ dfree (name, MDL);
+ return;
+ }
+ dhcp_failover_state_reference
+ (&group -> shared_network -> failover_peer,
+ peer, MDL);
+ }
+ dhcp_failover_state_dereference (&peer, MDL);
+ dfree (name, MDL);
+ return;
+ } else if (token == STATE) {
+ if (!peer) {
+ parse_warn (cfile, "state declaration for unknown%s%s",
+ " failover peer ", name);
+ dfree (name, MDL);
+ return;
+ }
+ parse_failover_state_declaration (cfile, peer);
+ dhcp_failover_state_dereference (&peer, MDL);
+ dfree (name, MDL);
+ return;
+ } else if (token != LBRACE) {
+ parse_warn (cfile, "expecting left brace");
+ skip_to_semi (cfile);
+ }
+
+ /* Make sure this isn't a redeclaration. */
+ if (peer) {
+ parse_warn (cfile, "redeclaration of failover peer %s", name);
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&peer, MDL);
+ dfree (name, MDL);
+ return;
+ }
+
+ status = dhcp_failover_state_allocate (&peer, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't allocate failover peer %s: %s",
+ name, isc_result_totext (status));
+
+ /* Save the name. */
+ peer -> name = name;
+
+ do {
+ cp = &peer -> me;
+ peer:
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case RBRACE:
+ break;
+
+ case PRIMARY:
+ peer -> i_am = primary;
+ break;
+
+ case SECONDARY:
+ peer -> i_am = secondary;
+ if (peer -> hba)
+ parse_warn (cfile,
+ "secondary may not define %s",
+ "load balance settings.");
+ break;
+
+ case PEER:
+ cp = &peer -> partner;
+ goto peer;
+
+ case ADDRESS:
+ expr = (struct expression *)0;
+ if (!parse_ip_addr_or_hostname (&expr, cfile, 0)) {
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&peer, MDL);
+ return;
+ }
+ option_cache (&cp -> address,
+ (struct data_string *)0, expr,
+ (struct option *)0, MDL);
+ expression_dereference (&expr, MDL);
+ break;
+
+ case PORT:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "expecting number");
+ skip_to_rbrace (cfile, 1);
+ }
+ cp -> port = atoi (val);
+ break;
+
+ case MAX_LEASE_MISBALANCE:
+ tp = &peer->max_lease_misbalance;
+ goto parse_idle;
+
+ case MAX_LEASE_OWNERSHIP:
+ tp = &peer->max_lease_ownership;
+ goto parse_idle;
+
+ case MAX_BALANCE:
+ tp = &peer->max_balance;
+ goto parse_idle;
+
+ case MIN_BALANCE:
+ tp = &peer->min_balance;
+ goto parse_idle;
+
+ case AUTO_PARTNER_DOWN:
+ tp = &peer->auto_partner_down;
+ goto parse_idle;
+
+ case MAX_RESPONSE_DELAY:
+ tp = &cp -> max_response_delay;
+ parse_idle:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "expecting number.");
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&peer, MDL);
+ return;
+ }
+ *tp = atoi (val);
+ break;
+
+ case MAX_UNACKED_UPDATES:
+ tp = &cp -> max_flying_updates;
+ goto parse_idle;
+
+ case MCLT:
+ tp = &peer -> mclt;
+ goto parse_idle;
+
+ case HBA:
+ hba_len = 32;
+ if (peer -> i_am == secondary)
+ parse_warn (cfile,
+ "secondary may not define %s",
+ "load balance settings.");
+ if (!parse_numeric_aggregate (cfile, hba, &hba_len,
+ COLON, 16, 8)) {
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&peer, MDL);
+ return;
+ }
+ if (hba_len != 32) {
+ parse_warn (cfile,
+ "HBA must be exactly 32 bytes.");
+ break;
+ }
+ make_hba:
+ peer -> hba = dmalloc (32, MDL);
+ if (!peer -> hba) {
+ dfree (peer -> name, MDL);
+ dfree (peer, MDL);
+ }
+ memcpy (peer -> hba, hba, 32);
+ break;
+
+ case SPLIT:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (peer -> i_am == secondary)
+ parse_warn (cfile,
+ "secondary may not define %s",
+ "load balance settings.");
+ if (token != NUMBER) {
+ parse_warn (cfile, "expecting number");
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&peer, MDL);
+ return;
+ }
+ split = atoi (val);
+ if (split > 256) {
+ parse_warn (cfile, "split must be between "
+ "0 and 256, inclusive");
+ } else {
+ memset (hba, 0, sizeof hba);
+ for (i = 0; i < split; i++) {
+ if (i < split)
+ hba [i / 8] |= (1 << (i & 7));
+ }
+ goto make_hba;
+ }
+ break;
+
+ case LOAD:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != BALANCE) {
+ parse_warn (cfile, "expecting 'balance'");
+ badload:
+ skip_to_rbrace (cfile, 1);
+ break;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != TOKEN_MAX) {
+ parse_warn (cfile, "expecting 'max'");
+ goto badload;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != SECONDS) {
+ parse_warn (cfile, "expecting 'secs'");
+ goto badload;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "expecting number");
+ goto badload;
+ }
+ peer -> load_balance_max_secs = atoi (val);
+ break;
+
+ default:
+ parse_warn (cfile,
+ "invalid statement in peer declaration");
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&peer, MDL);
+ return;
+ }
+ if (token != RBRACE && !parse_semi (cfile)) {
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&peer, MDL);
+ return;
+ }
+ } while (token != RBRACE);
+
+ /* me.address can be null; the failover link initiate code tries to
+ * derive a reasonable address to use.
+ */
+ if (!peer -> partner.address)
+ parse_warn (cfile, "peer address may not be omitted");
+
+ if (!peer->me.port)
+ peer->me.port = DEFAULT_FAILOVER_PORT;
+ if (!peer->partner.port)
+ peer->partner.port = DEFAULT_FAILOVER_PORT;
+
+ if (peer -> i_am == primary) {
+ if (!peer -> hba) {
+ parse_warn (cfile,
+ "primary failover server must have hba or split.");
+ } else if (!peer -> mclt) {
+ parse_warn (cfile,
+ "primary failover server must have mclt.");
+ }
+ }
+
+ if (!peer->max_lease_misbalance)
+ peer->max_lease_misbalance = DEFAULT_MAX_LEASE_MISBALANCE;
+ if (!peer->max_lease_ownership)
+ peer->max_lease_ownership = DEFAULT_MAX_LEASE_OWNERSHIP;
+ if (!peer->max_balance)
+ peer->max_balance = DEFAULT_MAX_BALANCE_TIME;
+ if (!peer->min_balance)
+ peer->min_balance = DEFAULT_MIN_BALANCE_TIME;
+ if (!peer->me.max_flying_updates)
+ peer->me.max_flying_updates = DEFAULT_MAX_FLYING_UPDATES;
+ if (!peer->me.max_response_delay)
+ peer->me.max_response_delay = DEFAULT_MAX_RESPONSE_DELAY;
+
+ if (type == SHARED_NET_DECL)
+ group->shared_network->failover_peer = peer;
+
+ /* Set the initial state. */
+ if (peer -> i_am == primary) {
+ peer -> me.state = recover;
+ peer -> me.stos = cur_time;
+ peer -> partner.state = unknown_state;
+ peer -> partner.stos = cur_time;
+ } else {
+ peer -> me.state = recover;
+ peer -> me.stos = cur_time;
+ peer -> partner.state = unknown_state;
+ peer -> partner.stos = cur_time;
+ }
+
+ status = enter_failover_peer (peer);
+ if (status != ISC_R_SUCCESS)
+ parse_warn (cfile, "failover peer %s: %s",
+ peer -> name, isc_result_totext (status));
+ dhcp_failover_state_dereference (&peer, MDL);
+}
+
+void parse_failover_state_declaration (struct parse *cfile,
+ dhcp_failover_state_t *peer)
+{
+ enum dhcp_token token;
+ const char *val;
+ char *name;
+ dhcp_failover_state_t *state;
+ dhcp_failover_config_t *cp;
+
+ if (!peer) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != PEER) {
+ parse_warn (cfile, "expecting \"peer\"");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (is_identifier (token) || token == STRING) {
+ name = dmalloc (strlen (val) + 1, MDL);
+ if (!name)
+ log_fatal ("failover peer name %s: no memory",
+ name);
+ strcpy (name, val);
+ } else {
+ parse_warn (cfile, "expecting failover peer name.");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ /* See if there's a peer declaration by this name. */
+ state = (dhcp_failover_state_t *)0;
+ find_failover_peer (&state, name, MDL);
+ if (!state) {
+ parse_warn (cfile, "unknown failover peer: %s", name);
+ skip_to_semi (cfile);
+ return;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STATE) {
+ parse_warn (cfile, "expecting 'state'");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ return;
+ }
+ } else {
+ state = (dhcp_failover_state_t *)0;
+ dhcp_failover_state_reference (&state, peer, MDL);
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LBRACE) {
+ parse_warn (cfile, "expecting left brace");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ dhcp_failover_state_dereference (&state, MDL);
+ return;
+ }
+ do {
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case RBRACE:
+ break;
+ case MY:
+ cp = &state -> me;
+ do_state:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STATE) {
+ parse_warn (cfile, "expecting 'state'");
+ goto bogus;
+ }
+ parse_failover_state (cfile,
+ &cp -> state, &cp -> stos);
+ break;
+
+ case PARTNER:
+ cp = &state -> partner;
+ goto do_state;
+
+ case MCLT:
+ if (state -> i_am == primary) {
+ parse_warn (cfile,
+ "mclt not valid for primary");
+ goto bogus;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "expecting a number.");
+ goto bogus;
+ }
+ state -> mclt = atoi (val);
+ parse_semi (cfile);
+ break;
+
+ default:
+ parse_warn (cfile, "expecting state setting.");
+ bogus:
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&state, MDL);
+ return;
+ }
+ } while (token != RBRACE);
+ dhcp_failover_state_dereference (&state, MDL);
+}
+
+void parse_failover_state (cfile, state, stos)
+ struct parse *cfile;
+ enum failover_state *state;
+ TIME *stos;
+{
+ enum dhcp_token token;
+ const char *val;
+ enum failover_state state_in;
+ TIME stos_in;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case UNKNOWN_STATE:
+ state_in = unknown_state;
+ break;
+
+ case PARTNER_DOWN:
+ state_in = partner_down;
+ break;
+
+ case NORMAL:
+ state_in = normal;
+ break;
+
+ case COMMUNICATIONS_INTERRUPTED:
+ state_in = communications_interrupted;
+ break;
+
+ case CONFLICT_DONE:
+ state_in = conflict_done;
+ break;
+
+ case RESOLUTION_INTERRUPTED:
+ state_in = resolution_interrupted;
+ break;
+
+ case POTENTIAL_CONFLICT:
+ state_in = potential_conflict;
+ break;
+
+ case RECOVER:
+ state_in = recover;
+ break;
+
+ case RECOVER_WAIT:
+ state_in = recover_wait;
+ break;
+
+ case RECOVER_DONE:
+ state_in = recover_done;
+ break;
+
+ case SHUTDOWN:
+ state_in = shut_down;
+ break;
+
+ case PAUSED:
+ state_in = paused;
+ break;
+
+ case STARTUP:
+ state_in = startup;
+ break;
+
+ default:
+ parse_warn (cfile, "unknown failover state");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token == SEMI) {
+ stos_in = cur_time;
+ } else {
+ if (token != AT) {
+ parse_warn (cfile, "expecting \"at\"");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ stos_in = parse_date (cfile);
+ if (!stos_in)
+ return;
+ }
+
+ /* Now that we've apparently gotten a clean parse, we
+ can trust that this is a state that was fully committed to
+ disk, so we can install it. */
+ *stos = stos_in;
+ *state = state_in;
+}
+#endif /* defined (FAILOVER_PROTOCOL) */
+
+/* Permit_list_match returns 1 if every element of the permit list in lhs
+ also appears in rhs. Note that this doesn't by itself mean that the
+ two lists are equal - to check for equality, permit_list_match has to
+ return 1 with (list1, list2) and with (list2, list1). */
+
+int permit_list_match (struct permit *lhs, struct permit *rhs)
+{
+ struct permit *plp, *prp;
+ int matched;
+
+ if (!lhs)
+ return 1;
+ if (!rhs)
+ return 0;
+ for (plp = lhs; plp; plp = plp -> next) {
+ matched = 0;
+ for (prp = rhs; prp; prp = prp -> next) {
+ if (prp -> type == plp -> type &&
+ (prp -> type != permit_class ||
+ prp -> class == plp -> class)) {
+ matched = 1;
+ break;
+ }
+ }
+ if (!matched)
+ return 0;
+ }
+ return 1;
+}
+
+void parse_pool_statement (cfile, group, type)
+ struct parse *cfile;
+ struct group *group;
+ int type;
+{
+ enum dhcp_token token;
+ const char *val;
+ int done = 0;
+ struct pool *pool, **p, *pp;
+ struct permit *permit;
+ struct permit **permit_head;
+ int declaration = 0;
+ isc_result_t status;
+ struct lease *lpchain = (struct lease *)0, *lp;
+ TIME t;
+ int is_allow = 0;
+
+ pool = (struct pool *)0;
+ status = pool_allocate (&pool, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("no memory for pool: %s",
+ isc_result_totext (status));
+
+ if (type == SUBNET_DECL)
+ shared_network_reference (&pool -> shared_network,
+ group -> subnet -> shared_network,
+ MDL);
+ else if (type == SHARED_NET_DECL)
+ shared_network_reference (&pool -> shared_network,
+ group -> shared_network, MDL);
+ else {
+ parse_warn(cfile, "Dynamic pools are only valid inside "
+ "subnet or shared-network statements.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ if (pool->shared_network == NULL ||
+ !clone_group(&pool->group, pool->shared_network->group, MDL))
+ log_fatal("can't clone pool group.");
+
+#if defined (FAILOVER_PROTOCOL)
+ /* Inherit the failover peer from the shared network. */
+ if (pool -> shared_network -> failover_peer)
+ dhcp_failover_state_reference
+ (&pool -> failover_peer,
+ pool -> shared_network -> failover_peer, MDL);
+#endif
+
+ if (!parse_lbrace (cfile)) {
+ pool_dereference (&pool, MDL);
+ return;
+ }
+
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case TOKEN_NO:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != FAILOVER ||
+ (token = next_token (&val, (unsigned *)0,
+ cfile)) != PEER) {
+ parse_warn (cfile,
+ "expecting \"failover peer\".");
+ skip_to_semi (cfile);
+ continue;
+ }
+#if defined (FAILOVER_PROTOCOL)
+ if (pool -> failover_peer)
+ dhcp_failover_state_dereference
+ (&pool -> failover_peer, MDL);
+#endif
+ break;
+
+#if defined (FAILOVER_PROTOCOL)
+ case FAILOVER:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != PEER) {
+ parse_warn (cfile, "expecting 'peer'.");
+ skip_to_semi (cfile);
+ break;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING) {
+ parse_warn (cfile, "expecting string.");
+ skip_to_semi (cfile);
+ break;
+ }
+ if (pool -> failover_peer)
+ dhcp_failover_state_dereference
+ (&pool -> failover_peer, MDL);
+ status = find_failover_peer (&pool -> failover_peer,
+ val, MDL);
+ if (status != ISC_R_SUCCESS)
+ parse_warn (cfile,
+ "failover peer %s: %s", val,
+ isc_result_totext (status));
+ else
+ pool -> failover_peer -> pool_count++;
+ parse_semi (cfile);
+ break;
+#endif
+
+ case RANGE:
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_address_range (cfile, group, type,
+ pool, &lpchain);
+ break;
+ case ALLOW:
+ permit_head = &pool -> permit_list;
+ /* remember the clause which leads to get_permit */
+ is_allow = 1;
+ get_permit:
+ permit = new_permit (MDL);
+ if (!permit)
+ log_fatal ("no memory for permit");
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case UNKNOWN:
+ permit -> type = permit_unknown_clients;
+ get_clients:
+ if (next_token (&val, (unsigned *)0,
+ cfile) != CLIENTS) {
+ parse_warn (cfile,
+ "expecting \"clients\"");
+ skip_to_semi (cfile);
+ free_permit (permit, MDL);
+ continue;
+ }
+ break;
+
+ case KNOWN_CLIENTS:
+ permit -> type = permit_known_clients;
+ break;
+
+ case UNKNOWN_CLIENTS:
+ permit -> type = permit_unknown_clients;
+ break;
+
+ case KNOWN:
+ permit -> type = permit_known_clients;
+ goto get_clients;
+
+ case AUTHENTICATED:
+ permit -> type = permit_authenticated_clients;
+ goto get_clients;
+
+ case UNAUTHENTICATED:
+ permit -> type =
+ permit_unauthenticated_clients;
+ goto get_clients;
+
+ case ALL:
+ permit -> type = permit_all_clients;
+ goto get_clients;
+ break;
+
+ case DYNAMIC:
+ permit -> type = permit_dynamic_bootp_clients;
+ if (next_token (&val, (unsigned *)0,
+ cfile) != TOKEN_BOOTP) {
+ parse_warn (cfile,
+ "expecting \"bootp\"");
+ skip_to_semi (cfile);
+ free_permit (permit, MDL);
+ continue;
+ }
+ goto get_clients;
+
+ case MEMBERS:
+ if (next_token (&val, (unsigned *)0,
+ cfile) != OF) {
+ parse_warn (cfile, "expecting \"of\"");
+ skip_to_semi (cfile);
+ free_permit (permit, MDL);
+ continue;
+ }
+ if (next_token (&val, (unsigned *)0,
+ cfile) != STRING) {
+ parse_warn (cfile,
+ "expecting class name.");
+ skip_to_semi (cfile);
+ free_permit (permit, MDL);
+ continue;
+ }
+ permit -> type = permit_class;
+ permit -> class = (struct class *)0;
+ find_class (&permit -> class, val, MDL);
+ if (!permit -> class)
+ parse_warn (cfile,
+ "no such class: %s", val);
+ break;
+
+ case AFTER:
+ if (pool->valid_from || pool->valid_until) {
+ parse_warn(cfile,
+ "duplicate \"after\" clause.");
+ skip_to_semi(cfile);
+ free_permit(permit, MDL);
+ continue;
+ }
+ t = parse_date_core(cfile);
+ permit->type = permit_after;
+ permit->after = t;
+ if (is_allow) {
+ pool->valid_from = t;
+ } else {
+ pool->valid_until = t;
+ }
+ break;
+
+ default:
+ parse_warn (cfile, "expecting permit type.");
+ skip_to_semi (cfile);
+ break;
+ }
+ while (*permit_head)
+ permit_head = &((*permit_head) -> next);
+ *permit_head = permit;
+ parse_semi (cfile);
+ break;
+
+ case DENY:
+ permit_head = &pool -> prohibit_list;
+ /* remember the clause which leads to get_permit */
+ is_allow = 0;
+ goto get_permit;
+
+ case RBRACE:
+ skip_token(&val, (unsigned *)0, cfile);
+ done = 1;
+ break;
+
+ case END_OF_FILE:
+ /*
+ * We can get to END_OF_FILE if, for instance,
+ * the parse_statement() reads all available tokens
+ * and leaves us at the end.
+ */
+ parse_warn(cfile, "unexpected end of file");
+ goto cleanup;
+
+ default:
+ declaration = parse_statement (cfile, pool -> group,
+ POOL_DECL,
+ (struct host_decl *)0,
+ declaration);
+ break;
+ }
+ } while (!done);
+
+ /* See if there's already a pool into which we can merge this one. */
+ for (pp = pool -> shared_network -> pools; pp; pp = pp -> next) {
+ if (pp -> group -> statements != pool -> group -> statements)
+ continue;
+#if defined (FAILOVER_PROTOCOL)
+ if (pool -> failover_peer != pp -> failover_peer)
+ continue;
+#endif
+ if (!permit_list_match (pp -> permit_list,
+ pool -> permit_list) ||
+ !permit_list_match (pool -> permit_list,
+ pp -> permit_list) ||
+ !permit_list_match (pp -> prohibit_list,
+ pool -> prohibit_list) ||
+ !permit_list_match (pool -> prohibit_list,
+ pp -> prohibit_list))
+ continue;
+
+ /* Okay, we can merge these two pools. All we have to
+ do is fix up the leases, which all point to their pool. */
+ for (lp = lpchain; lp; lp = lp -> next) {
+ pool_dereference (&lp -> pool, MDL);
+ pool_reference (&lp -> pool, pp, MDL);
+ }
+ break;
+ }
+
+ /* If we didn't succeed in merging this pool into another, put
+ it on the list. */
+ if (!pp) {
+ p = &pool -> shared_network -> pools;
+ for (; *p; p = &((*p) -> next))
+ ;
+ pool_reference (p, pool, MDL);
+ }
+
+ /* Don't allow a pool declaration with no addresses, since it is
+ probably a configuration error. */
+ if (!lpchain) {
+ parse_warn (cfile, "Pool declaration with no address range.");
+ log_error ("Pool declarations must always contain at least");
+ log_error ("one range statement.");
+ }
+
+cleanup:
+ /* Dereference the lease chain. */
+ lp = (struct lease *)0;
+ while (lpchain) {
+ lease_reference (&lp, lpchain, MDL);
+ lease_dereference (&lpchain, MDL);
+ if (lp -> next) {
+ lease_reference (&lpchain, lp -> next, MDL);
+ lease_dereference (&lp -> next, MDL);
+ lease_dereference (&lp, MDL);
+ }
+ }
+ pool_dereference (&pool, MDL);
+}
+
+/* Expect a left brace; if there isn't one, skip over the rest of the
+ statement and return zero; otherwise, return 1. */
+
+int parse_lbrace (cfile)
+ struct parse *cfile;
+{
+ enum dhcp_token token;
+ const char *val;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LBRACE) {
+ parse_warn (cfile, "expecting left brace.");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ return 1;
+}
+
+
+/* host-declaration :== hostname RBRACE parameters declarations LBRACE */
+
+void parse_host_declaration (cfile, group)
+ struct parse *cfile;
+ struct group *group;
+{
+ const char *val;
+ enum dhcp_token token;
+ struct host_decl *host;
+ char *name;
+ int declaration = 0;
+ int dynamicp = 0;
+ int deleted = 0;
+ isc_result_t status;
+ int known;
+ struct option *option;
+ struct expression *expr = NULL;
+
+ name = parse_host_name (cfile);
+ if (!name) {
+ parse_warn (cfile, "expecting a name for host declaration.");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ host = (struct host_decl *)0;
+ status = host_allocate (&host, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("can't allocate host decl struct %s: %s",
+ name, isc_result_totext (status));
+ host -> name = name;
+ if (!clone_group (&host -> group, group, MDL)) {
+ log_fatal ("can't clone group for host %s", name);
+ boom:
+ host_dereference (&host, MDL);
+ return;
+ }
+
+ if (!parse_lbrace (cfile))
+ goto boom;
+
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == RBRACE) {
+ skip_token(&val, (unsigned *)0, cfile);
+ break;
+ }
+ if (token == END_OF_FILE) {
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_warn (cfile, "unexpected end of file");
+ break;
+ }
+ /* If the host declaration was created by the server,
+ remember to save it. */
+ if (token == DYNAMIC) {
+ dynamicp = 1;
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!parse_semi (cfile))
+ break;
+ continue;
+ }
+ /* If the host declaration was created by the server,
+ remember to save it. */
+ if (token == TOKEN_DELETED) {
+ deleted = 1;
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!parse_semi (cfile))
+ break;
+ continue;
+ }
+
+ if (token == GROUP) {
+ struct group_object *go;
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING && !is_identifier (token)) {
+ parse_warn (cfile,
+ "expecting string or identifier.");
+ skip_to_rbrace (cfile, 1);
+ break;
+ }
+ go = (struct group_object *)0;
+ if (!group_hash_lookup (&go, group_name_hash,
+ val, strlen (val), MDL)) {
+ parse_warn (cfile, "unknown group %s in host %s",
+ val, host -> name);
+ } else {
+ if (host -> named_group)
+ group_object_dereference
+ (&host -> named_group, MDL);
+ group_object_reference (&host -> named_group,
+ go, MDL);
+ group_object_dereference (&go, MDL);
+ }
+ if (!parse_semi (cfile))
+ break;
+ continue;
+ }
+
+ if (token == UID) {
+ const char *s;
+ unsigned char *t = 0;
+ unsigned len;
+
+ skip_token(&val, (unsigned *)0, cfile);
+ data_string_forget (&host -> client_identifier, MDL);
+
+ if (host->client_identifier.len != 0) {
+ parse_warn(cfile, "Host %s already has a "
+ "client identifier.",
+ host->name);
+ break;
+ }
+
+ /* See if it's a string or a cshl. */
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == STRING) {
+ skip_token(&val, &len, cfile);
+ s = val;
+ host -> client_identifier.terminated = 1;
+ } else {
+ len = 0;
+ t = parse_numeric_aggregate
+ (cfile,
+ (unsigned char *)0, &len, ':', 16, 8);
+ if (!t) {
+ parse_warn (cfile,
+ "expecting hex list.");
+ skip_to_semi (cfile);
+ }
+ s = (const char *)t;
+ }
+ if (!buffer_allocate
+ (&host -> client_identifier.buffer,
+ len + host -> client_identifier.terminated, MDL))
+ log_fatal ("no memory for uid for host %s.",
+ host -> name);
+ host -> client_identifier.data =
+ host -> client_identifier.buffer -> data;
+ host -> client_identifier.len = len;
+ memcpy (host -> client_identifier.buffer -> data, s,
+ len + host -> client_identifier.terminated);
+ if (t)
+ dfree (t, MDL);
+
+ if (!parse_semi (cfile))
+ break;
+ continue;
+ }
+
+ if (token == HOST_IDENTIFIER) {
+ if (host->host_id_option != NULL) {
+ parse_warn(cfile,
+ "only one host-identifier allowed "
+ "per host");
+ skip_to_rbrace(cfile, 1);
+ break;
+ }
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != OPTION) {
+ parse_warn(cfile,
+ "host-identifier must be an option");
+ skip_to_rbrace(cfile, 1);
+ break;
+ }
+ known = 0;
+ option = NULL;
+ status = parse_option_name(cfile, 1, &known, &option);
+ if ((status != ISC_R_SUCCESS) || (option == NULL)) {
+ break;
+ }
+ if (!known) {
+ parse_warn(cfile, "unknown option %s.%s",
+ option->universe->name,
+ option->name);
+ skip_to_rbrace(cfile, 1);
+ break;
+ }
+
+ if (! parse_option_data(&expr, cfile, 1, option)) {
+ skip_to_rbrace(cfile, 1);
+ option_dereference(&option, MDL);
+ break;
+ }
+
+ if (!parse_semi(cfile)) {
+ skip_to_rbrace(cfile, 1);
+ expression_dereference(&expr, MDL);
+ option_dereference(&option, MDL);
+ break;
+ }
+
+ option_reference(&host->host_id_option, option, MDL);
+ option_dereference(&option, MDL);
+ data_string_copy(&host->host_id,
+ &expr->data.const_data, MDL);
+ expression_dereference(&expr, MDL);
+ continue;
+ }
+
+ declaration = parse_statement(cfile, host->group, HOST_DECL,
+ host, declaration);
+ } while (1);
+
+ if (deleted) {
+ struct host_decl *hp = (struct host_decl *)0;
+ if (host_hash_lookup (&hp, host_name_hash,
+ (unsigned char *)host -> name,
+ strlen (host -> name), MDL)) {
+ delete_host (hp, 0);
+ host_dereference (&hp, MDL);
+ }
+ } else {
+ if (host -> named_group && host -> named_group -> group) {
+ if (host -> group -> statements ||
+ (host -> group -> authoritative !=
+ host -> named_group -> group -> authoritative)) {
+ if (host -> group -> next)
+ group_dereference (&host -> group -> next,
+ MDL);
+ group_reference (&host -> group -> next,
+ host -> named_group -> group,
+ MDL);
+ } else {
+ group_dereference (&host -> group, MDL);
+ group_reference (&host -> group,
+ host -> named_group -> group,
+ MDL);
+ }
+ }
+
+ if (dynamicp)
+ host -> flags |= HOST_DECL_DYNAMIC;
+ else
+ host -> flags |= HOST_DECL_STATIC;
+
+ status = enter_host (host, dynamicp, 0);
+ if (status != ISC_R_SUCCESS)
+ parse_warn (cfile, "host %s: %s", host -> name,
+ isc_result_totext (status));
+ }
+ host_dereference (&host, MDL);
+}
+
+/* class-declaration :== STRING LBRACE parameters declarations RBRACE
+*/
+
+int parse_class_declaration (cp, cfile, group, type)
+ struct class **cp;
+ struct parse *cfile;
+ struct group *group;
+ int type;
+{
+ const char *val;
+ enum dhcp_token token;
+ struct class *class = (struct class *)0, *pc = (struct class *)0;
+ int declaration = 0;
+ int lose = 0;
+ struct data_string data;
+ char *name;
+ const char *tname;
+ struct executable_statement *stmt = (struct executable_statement *)0;
+ int new = 1;
+ isc_result_t status = ISC_R_FAILURE;
+ int matchedonce = 0;
+ int submatchedonce = 0;
+ unsigned code;
+
+ if (dhcpv6_class_once && local_family == AF_INET6) {
+ dhcpv6_class_once = 0;
+ log_error("WARNING: class declarations are not supported "
+ "for DHCPv6.");
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING) {
+ parse_warn (cfile, "Expecting class name");
+ skip_to_semi (cfile);
+ return 0;
+ }
+
+ /* See if there's already a class with the specified name. */
+ find_class (&pc, val, MDL);
+
+ /* If it is a class, we're updating it. If it's any of the other
+ * types (subclass, vendor or user class), the named class is a
+ * reference to the parent class so its mandatory.
+ */
+ if (pc && (type == CLASS_TYPE_CLASS)) {
+ class_reference(&class, pc, MDL);
+ new = 0;
+ class_dereference(&pc, MDL);
+ } else if (!pc && (type != CLASS_TYPE_CLASS)) {
+ parse_warn(cfile, "no class named %s", val);
+ skip_to_semi(cfile);
+ return 0;
+ }
+
+ /* The old vendor-class and user-class declarations had an implicit
+ match. We don't do the implicit match anymore. Instead, for
+ backward compatibility, we have an implicit-vendor-class and an
+ implicit-user-class. vendor-class and user-class declarations
+ are turned into subclasses of the implicit classes, and the
+ submatch expression of the implicit classes extracts the contents of
+ the vendor class or user class. */
+ if ((type == CLASS_TYPE_VENDOR) || (type == CLASS_TYPE_USER)) {
+ data.len = strlen (val);
+ data.buffer = (struct buffer *)0;
+ if (!buffer_allocate (&data.buffer, data.len + 1, MDL))
+ log_fatal ("no memory for class name.");
+ data.data = &data.buffer -> data [0];
+ data.terminated = 1;
+
+ tname = type ? "implicit-vendor-class" : "implicit-user-class";
+ } else if (type == CLASS_TYPE_CLASS) {
+ tname = val;
+ } else {
+ tname = (const char *)0;
+ }
+
+ if (tname) {
+ name = dmalloc (strlen (tname) + 1, MDL);
+ if (!name)
+ log_fatal ("No memory for class name %s.", tname);
+ strcpy (name, val);
+ } else
+ name = (char *)0;
+
+ /* If this is a straight subclass, parse the hash string. */
+ if (type == CLASS_TYPE_SUBCLASS) {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == STRING) {
+ skip_token(&val, &data.len, cfile);
+ data.buffer = (struct buffer *)0;
+ if (!buffer_allocate (&data.buffer,
+ data.len + 1, MDL)) {
+ if (pc)
+ class_dereference (&pc, MDL);
+
+ return 0;
+ }
+ data.terminated = 1;
+ data.data = &data.buffer -> data [0];
+ memcpy ((char *)data.buffer -> data, val,
+ data.len + 1);
+ } else if (token == NUMBER_OR_NAME || token == NUMBER) {
+ memset (&data, 0, sizeof data);
+ if (!parse_cshl (&data, cfile)) {
+ if (pc)
+ class_dereference (&pc, MDL);
+ return 0;
+ }
+ } else {
+ parse_warn (cfile, "Expecting string or hex list.");
+ if (pc)
+ class_dereference (&pc, MDL);
+ return 0;
+ }
+ }
+
+ /* See if there's already a class in the hash table matching the
+ hash data. */
+ if (type != CLASS_TYPE_CLASS)
+ class_hash_lookup (&class, pc -> hash,
+ (const char *)data.data, data.len, MDL);
+
+ /* If we didn't find an existing class, allocate a new one. */
+ if (!class) {
+ /* Allocate the class structure... */
+ status = class_allocate (&class, MDL);
+ if (pc) {
+ group_reference (&class -> group, pc -> group, MDL);
+ class_reference (&class -> superclass, pc, MDL);
+ class -> lease_limit = pc -> lease_limit;
+ if (class -> lease_limit) {
+ class -> billed_leases =
+ dmalloc (class -> lease_limit *
+ sizeof (struct lease *), MDL);
+ if (!class -> billed_leases)
+ log_fatal ("no memory for billing");
+ memset (class -> billed_leases, 0,
+ (class -> lease_limit *
+ sizeof (struct lease *)));
+ }
+ data_string_copy (&class -> hash_string, &data, MDL);
+ if (!pc -> hash &&
+ !class_new_hash (&pc->hash, SCLASS_HASH_SIZE, MDL))
+ log_fatal ("No memory for subclass hash.");
+ class_hash_add (pc -> hash,
+ (const char *)class -> hash_string.data,
+ class -> hash_string.len,
+ (void *)class, MDL);
+ } else {
+ if (class->group)
+ group_dereference(&class->group, MDL);
+ if (!clone_group (&class -> group, group, MDL))
+ log_fatal ("no memory to clone class group.");
+ }
+
+ /* If this is an implicit vendor or user class, add a
+ statement that causes the vendor or user class ID to
+ be sent back in the reply. */
+ if (type == CLASS_TYPE_VENDOR || type == CLASS_TYPE_USER) {
+ stmt = (struct executable_statement *)0;
+ if (!executable_statement_allocate (&stmt, MDL))
+ log_fatal ("no memory for class statement.");
+ stmt -> op = supersede_option_statement;
+ if (option_cache_allocate (&stmt -> data.option,
+ MDL)) {
+ stmt -> data.option -> data = data;
+ code = (type == CLASS_TYPE_VENDOR)
+ ? DHO_VENDOR_CLASS_IDENTIFIER
+ : DHO_USER_CLASS;
+ option_code_hash_lookup(
+ &stmt->data.option->option,
+ dhcp_universe.code_hash,
+ &code, 0, MDL);
+ }
+ class -> statements = stmt;
+ }
+
+ /* Save the name, if there is one. */
+ if (class->name != NULL)
+ dfree(class->name, MDL);
+ class->name = name;
+ }
+
+ if (type != CLASS_TYPE_CLASS)
+ data_string_forget(&data, MDL);
+
+ /* Spawned classes don't have to have their own settings. */
+ if (class -> superclass) {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == SEMI) {
+ skip_token(&val, (unsigned *)0, cfile);
+ if (cp)
+ status = class_reference (cp, class, MDL);
+ class_dereference (&class, MDL);
+ if (pc)
+ class_dereference (&pc, MDL);
+ return cp ? (status == ISC_R_SUCCESS) : 1;
+ }
+ /* Give the subclass its own group. */
+ if (!clone_group (&class -> group, class -> group, MDL))
+ log_fatal ("can't clone class group.");
+
+ }
+
+ if (!parse_lbrace (cfile)) {
+ class_dereference (&class, MDL);
+ if (pc)
+ class_dereference (&pc, MDL);
+ return 0;
+ }
+
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == RBRACE) {
+ skip_token(&val, (unsigned *)0, cfile);
+ break;
+ } else if (token == END_OF_FILE) {
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_warn (cfile, "unexpected end of file");
+ break;
+ } else if (token == DYNAMIC) {
+ class->flags |= CLASS_DECL_DYNAMIC;
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!parse_semi (cfile))
+ break;
+ continue;
+ } else if (token == TOKEN_DELETED) {
+ class->flags |= CLASS_DECL_DELETED;
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!parse_semi (cfile))
+ break;
+ continue;
+ } else if (token == MATCH) {
+ if (pc) {
+ parse_warn (cfile,
+ "invalid match in subclass.");
+ skip_to_semi (cfile);
+ break;
+ }
+ skip_token(&val, (unsigned *)0, cfile);
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token != IF)
+ goto submatch;
+ skip_token(&val, (unsigned *)0, cfile);
+ if (matchedonce) {
+ parse_warn(cfile, "A class may only have "
+ "one 'match if' clause.");
+ skip_to_semi(cfile);
+ break;
+ }
+ matchedonce = 1;
+ if (class->expr)
+ expression_dereference(&class->expr, MDL);
+ if (!parse_boolean_expression (&class->expr, cfile,
+ &lose)) {
+ if (!lose) {
+ parse_warn (cfile,
+ "expecting boolean expr.");
+ skip_to_semi (cfile);
+ }
+ } else {
+#if defined (DEBUG_EXPRESSION_PARSE)
+ print_expression ("class match",
+ class -> expr);
+#endif
+ parse_semi (cfile);
+ }
+ } else if (token == SPAWN) {
+ skip_token(&val, (unsigned *)0, cfile);
+ if (pc) {
+ parse_warn (cfile,
+ "invalid spawn in subclass.");
+ skip_to_semi (cfile);
+ break;
+ }
+ class -> spawning = 1;
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != WITH) {
+ parse_warn (cfile,
+ "expecting with after spawn");
+ skip_to_semi (cfile);
+ break;
+ }
+ submatch:
+ if (submatchedonce) {
+ parse_warn (cfile,
+ "can't override existing %s.",
+ "submatch/spawn");
+ skip_to_semi (cfile);
+ break;
+ }
+ submatchedonce = 1;
+ if (class->submatch)
+ expression_dereference(&class->submatch, MDL);
+ if (!parse_data_expression (&class -> submatch,
+ cfile, &lose)) {
+ if (!lose) {
+ parse_warn (cfile,
+ "expecting data expr.");
+ skip_to_semi (cfile);
+ }
+ } else {
+#if defined (DEBUG_EXPRESSION_PARSE)
+ print_expression ("class submatch",
+ class -> submatch);
+#endif
+ parse_semi (cfile);
+ }
+ } else if (token == LEASE) {
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LIMIT) {
+ parse_warn (cfile, "expecting \"limit\"");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ break;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "expecting a number");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ break;
+ }
+ class -> lease_limit = atoi (val);
+ if (class->billed_leases)
+ dfree(class->billed_leases, MDL);
+ class -> billed_leases =
+ dmalloc (class -> lease_limit *
+ sizeof (struct lease *), MDL);
+ if (!class -> billed_leases)
+ log_fatal ("no memory for billed leases.");
+ memset (class -> billed_leases, 0,
+ (class -> lease_limit *
+ sizeof (struct lease *)));
+ have_billing_classes = 1;
+ parse_semi (cfile);
+ } else {
+ declaration = parse_statement (cfile, class -> group,
+ CLASS_DECL,
+ (struct host_decl *)0,
+ declaration);
+ }
+ } while (1);
+
+ if (class->flags & CLASS_DECL_DELETED) {
+ if (type == CLASS_TYPE_CLASS) {
+ struct class *theclass = NULL;
+
+ status = find_class(&theclass, class->name, MDL);
+ if (status == ISC_R_SUCCESS) {
+ delete_class(theclass, 0);
+ class_dereference(&theclass, MDL);
+ }
+ } else {
+ class_hash_delete(pc->hash,
+ (char *)class->hash_string.data,
+ class->hash_string.len, MDL);
+ }
+ } else if (type == CLASS_TYPE_CLASS && new) {
+ if (!collections -> classes)
+ class_reference (&collections -> classes, class, MDL);
+ else {
+ struct class *c;
+ for (c = collections -> classes;
+ c -> nic; c = c -> nic)
+ ;
+ class_reference (&c -> nic, class, MDL);
+ }
+ }
+
+ if (cp) /* should always be 0??? */
+ status = class_reference (cp, class, MDL);
+ class_dereference (&class, MDL);
+ if (pc)
+ class_dereference (&pc, MDL);
+ return cp ? (status == ISC_R_SUCCESS) : 1;
+}
+
+/* shared-network-declaration :==
+ hostname LBRACE declarations parameters RBRACE */
+
+void parse_shared_net_declaration (cfile, group)
+ struct parse *cfile;
+ struct group *group;
+{
+ const char *val;
+ enum dhcp_token token;
+ struct shared_network *share;
+ char *name;
+ int declaration = 0;
+ isc_result_t status;
+
+ share = (struct shared_network *)0;
+ status = shared_network_allocate (&share, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't allocate shared subnet: %s",
+ isc_result_totext (status));
+ if (clone_group (&share -> group, group, MDL) == 0) {
+ log_fatal ("Can't clone group for shared net");
+ }
+ shared_network_reference (&share -> group -> shared_network,
+ share, MDL);
+
+ /* Get the name of the shared network... */
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == STRING) {
+ skip_token(&val, (unsigned *)0, cfile);
+
+ if (val [0] == 0) {
+ parse_warn (cfile, "zero-length shared network name");
+ val = "<no-name-given>";
+ }
+ name = dmalloc (strlen (val) + 1, MDL);
+ if (!name)
+ log_fatal ("no memory for shared network name");
+ strcpy (name, val);
+ } else {
+ name = parse_host_name (cfile);
+ if (!name) {
+ parse_warn (cfile,
+ "expecting a name for shared-network");
+ skip_to_semi (cfile);
+ shared_network_dereference (&share, MDL);
+ return;
+ }
+ }
+ share -> name = name;
+
+ if (!parse_lbrace (cfile)) {
+ shared_network_dereference (&share, MDL);
+ return;
+ }
+
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == RBRACE) {
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!share -> subnets)
+ parse_warn (cfile,
+ "empty shared-network decl");
+ else
+ enter_shared_network (share);
+ shared_network_dereference (&share, MDL);
+ return;
+ } else if (token == END_OF_FILE) {
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_warn (cfile, "unexpected end of file");
+ break;
+ } else if (token == INTERFACE) {
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ new_shared_network_interface (cfile, share, val);
+ if (!parse_semi (cfile))
+ break;
+ continue;
+ }
+
+ declaration = parse_statement (cfile, share -> group,
+ SHARED_NET_DECL,
+ (struct host_decl *)0,
+ declaration);
+ } while (1);
+ shared_network_dereference (&share, MDL);
+}
+
+
+static int
+common_subnet_parsing(struct parse *cfile,
+ struct shared_network *share,
+ struct subnet *subnet) {
+ enum dhcp_token token;
+ struct subnet *t, *u;
+ const char *val;
+ int declaration = 0;
+
+ enter_subnet(subnet);
+
+ if (!parse_lbrace(cfile)) {
+ subnet_dereference(&subnet, MDL);
+ return 0;
+ }
+
+ do {
+ token = peek_token(&val, NULL, cfile);
+ if (token == RBRACE) {
+ skip_token(&val, NULL, cfile);
+ break;
+ } else if (token == END_OF_FILE) {
+ skip_token(&val, NULL, cfile);
+ parse_warn (cfile, "unexpected end of file");
+ break;
+ } else if (token == INTERFACE) {
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ new_shared_network_interface(cfile, share, val);
+ if (!parse_semi(cfile))
+ break;
+ continue;
+ }
+ declaration = parse_statement(cfile, subnet->group,
+ SUBNET_DECL,
+ NULL,
+ declaration);
+ } while (1);
+
+ /* Add the subnet to the list of subnets in this shared net. */
+ if (share->subnets == NULL) {
+ subnet_reference(&share->subnets, subnet, MDL);
+ } else {
+ u = NULL;
+ for (t = share->subnets; t->next_sibling; t = t->next_sibling) {
+ if (subnet_inner_than(subnet, t, 0)) {
+ subnet_reference(&subnet->next_sibling, t, MDL);
+ if (u) {
+ subnet_dereference(&u->next_sibling,
+ MDL);
+ subnet_reference(&u->next_sibling,
+ subnet, MDL);
+ } else {
+ subnet_dereference(&share->subnets,
+ MDL);
+ subnet_reference(&share->subnets,
+ subnet, MDL);
+ }
+ subnet_dereference(&subnet, MDL);
+ return 1;
+ }
+ u = t;
+ }
+ subnet_reference(&t->next_sibling, subnet, MDL);
+ }
+ subnet_dereference(&subnet, MDL);
+ return 1;
+}
+
+/* subnet-declaration :==
+ net NETMASK netmask RBRACE parameters declarations LBRACE */
+
+void parse_subnet_declaration (cfile, share)
+ struct parse *cfile;
+ struct shared_network *share;
+{
+ const char *val;
+ enum dhcp_token token;
+ struct subnet *subnet;
+ struct iaddr iaddr;
+ unsigned char addr [4];
+ unsigned len = sizeof addr;
+ isc_result_t status;
+
+ subnet = (struct subnet *)0;
+ status = subnet_allocate (&subnet, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Allocation of new subnet failed: %s",
+ isc_result_totext (status));
+ shared_network_reference (&subnet -> shared_network, share, MDL);
+
+ /*
+ * If our parent shared network was implicitly created by the software,
+ * and not explicitly configured by the user, then we actually put all
+ * configuration scope in the parent (the shared network and subnet
+ * share the same {}-level scope).
+ *
+ * Otherwise, we clone the parent group and continue as normal.
+ */
+ if (share->flags & SHARED_IMPLICIT) {
+ group_reference(&subnet->group, share->group, MDL);
+ } else {
+ if (!clone_group(&subnet->group, share->group, MDL)) {
+ log_fatal("Allocation of group for new subnet failed.");
+ }
+ }
+ subnet_reference (&subnet -> group -> subnet, subnet, MDL);
+
+ /* Get the network number... */
+ if (!parse_numeric_aggregate (cfile, addr, &len, DOT, 10, 8)) {
+ subnet_dereference (&subnet, MDL);
+ return;
+ }
+ memcpy (iaddr.iabuf, addr, len);
+ iaddr.len = len;
+ subnet -> net = iaddr;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NETMASK) {
+ parse_warn (cfile, "Expecting netmask");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ /* Get the netmask... */
+ if (!parse_numeric_aggregate (cfile, addr, &len, DOT, 10, 8)) {
+ subnet_dereference (&subnet, MDL);
+ return;
+ }
+ memcpy (iaddr.iabuf, addr, len);
+ iaddr.len = len;
+ subnet -> netmask = iaddr;
+
+ /* Validate the network number/netmask pair. */
+ if (host_addr (subnet -> net, subnet -> netmask)) {
+ char *maskstr;
+
+ maskstr = strdup (piaddr (subnet -> netmask));
+ parse_warn (cfile,
+ "subnet %s netmask %s: bad subnet number/mask combination.",
+ piaddr (subnet -> net), maskstr);
+ free(maskstr);
+ subnet_dereference (&subnet, MDL);
+ skip_to_semi (cfile);
+ return;
+ }
+
+ common_subnet_parsing(cfile, share, subnet);
+}
+
+/* subnet6-declaration :==
+ net / bits RBRACE parameters declarations LBRACE */
+
+void
+parse_subnet6_declaration(struct parse *cfile, struct shared_network *share) {
+#if !defined(DHCPv6)
+ parse_warn(cfile, "No DHCPv6 support.");
+ skip_to_semi(cfile);
+#else /* defined(DHCPv6) */
+ struct subnet *subnet;
+ isc_result_t status;
+ enum dhcp_token token;
+ const char *val;
+ char *endp;
+ int ofs;
+ const static int mask[] = { 0x00, 0x80, 0xC0, 0xE0,
+ 0xF0, 0xF8, 0xFC, 0xFE };
+ struct iaddr iaddr;
+
+ if (local_family != AF_INET6) {
+ parse_warn(cfile, "subnet6 statement is only supported "
+ "in DHCPv6 mode.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ subnet = NULL;
+ status = subnet_allocate(&subnet, MDL);
+ if (status != ISC_R_SUCCESS) {
+ log_fatal("Allocation of new subnet failed: %s",
+ isc_result_totext(status));
+ }
+ shared_network_reference(&subnet->shared_network, share, MDL);
+
+ /*
+ * If our parent shared network was implicitly created by the software,
+ * and not explicitly configured by the user, then we actually put all
+ * configuration scope in the parent (the shared network and subnet
+ * share the same {}-level scope).
+ *
+ * Otherwise, we clone the parent group and continue as normal.
+ */
+ if (share->flags & SHARED_IMPLICIT) {
+ group_reference(&subnet->group, share->group, MDL);
+ } else {
+ if (!clone_group(&subnet->group, share->group, MDL)) {
+ log_fatal("Allocation of group for new subnet failed.");
+ }
+ }
+ subnet_reference(&subnet->group->subnet, subnet, MDL);
+
+ if (!parse_ip6_addr(cfile, &subnet->net)) {
+ subnet_dereference(&subnet, MDL);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != SLASH) {
+ parse_warn(cfile, "Expecting a '/'.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "Expecting a number.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ subnet->prefix_len = strtol(val, &endp, 10);
+ if ((subnet->prefix_len < 0) ||
+ (subnet->prefix_len > 128) ||
+ (*endp != '\0')) {
+ parse_warn(cfile, "Expecting a number between 0 and 128.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ if (!is_cidr_mask_valid(&subnet->net, subnet->prefix_len)) {
+ parse_warn(cfile, "New subnet mask too short.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * Create a netmask.
+ */
+ subnet->netmask.len = 16;
+ ofs = subnet->prefix_len / 8;
+ if (ofs < subnet->netmask.len) {
+ subnet->netmask.iabuf[ofs] = mask[subnet->prefix_len % 8];
+ }
+ while (--ofs >= 0) {
+ subnet->netmask.iabuf[ofs] = 0xFF;
+ }
+
+ /* Validate the network number/netmask pair. */
+ iaddr = subnet_number(subnet->net, subnet->netmask);
+ if (memcmp(&iaddr, &subnet->net, 16) != 0) {
+ parse_warn(cfile,
+ "subnet %s/%d: prefix not long enough for address.",
+ piaddr(subnet->net), subnet->prefix_len);
+ subnet_dereference(&subnet, MDL);
+ skip_to_semi(cfile);
+ return;
+ }
+
+ if (!common_subnet_parsing(cfile, share, subnet)) {
+ return;
+ }
+#endif /* defined(DHCPv6) */
+}
+
+/* group-declaration :== RBRACE parameters declarations LBRACE */
+
+void parse_group_declaration (cfile, group)
+ struct parse *cfile;
+ struct group *group;
+{
+ const char *val;
+ enum dhcp_token token;
+ struct group *g;
+ int declaration = 0;
+ struct group_object *t = NULL;
+ isc_result_t status;
+ char *name = NULL;
+ int deletedp = 0;
+ int dynamicp = 0;
+ int staticp = 0;
+
+ g = NULL;
+ if (!clone_group(&g, group, MDL))
+ log_fatal("no memory for explicit group.");
+
+ token = peek_token(&val, NULL, cfile);
+ if (is_identifier (token) || token == STRING) {
+ skip_token(&val, NULL, cfile);
+
+ name = dmalloc(strlen(val) + 1, MDL);
+ if (!name)
+ log_fatal("no memory for group decl name %s", val);
+ strcpy(name, val);
+ }
+
+ if (!parse_lbrace(cfile)) {
+ group_dereference(&g, MDL);
+ return;
+ }
+
+ do {
+ token = peek_token(&val, NULL, cfile);
+ if (token == RBRACE) {
+ skip_token(&val, NULL, cfile);
+ break;
+ } else if (token == END_OF_FILE) {
+ skip_token(&val, NULL, cfile);
+ parse_warn(cfile, "unexpected end of file");
+ break;
+ } else if (token == TOKEN_DELETED) {
+ skip_token(&val, NULL, cfile);
+ parse_semi(cfile);
+ deletedp = 1;
+ } else if (token == DYNAMIC) {
+ skip_token(&val, NULL, cfile);
+ parse_semi(cfile);
+ dynamicp = 1;
+ } else if (token == STATIC) {
+ skip_token(&val, NULL, cfile);
+ parse_semi(cfile);
+ staticp = 1;
+ }
+ declaration = parse_statement(cfile, g, GROUP_DECL,
+ NULL, declaration);
+ } while (1);
+
+ if (name) {
+ if (deletedp) {
+ if (group_name_hash) {
+ t = NULL;
+ if (group_hash_lookup(&t, group_name_hash,
+ name,
+ strlen(name), MDL)) {
+ delete_group(t, 0);
+ }
+ }
+ } else {
+ t = NULL;
+ status = group_object_allocate(&t, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal("no memory for group decl %s: %s",
+ val, isc_result_totext(status));
+ group_reference(&t->group, g, MDL);
+ t->name = name;
+ /* no need to include deletedp as it's handled above */
+ t->flags = ((staticp ? GROUP_OBJECT_STATIC : 0) |
+ (dynamicp ? GROUP_OBJECT_DYNAMIC : 0));
+ supersede_group(t, 0);
+ }
+ if (t != NULL)
+ group_object_dereference(&t, MDL);
+ }
+}
+
+/* fixed-addr-parameter :== ip-addrs-or-hostnames SEMI
+ ip-addrs-or-hostnames :== ip-addr-or-hostname
+ | ip-addrs-or-hostnames ip-addr-or-hostname */
+
+int
+parse_fixed_addr_param(struct option_cache **oc,
+ struct parse *cfile,
+ enum dhcp_token type) {
+ int parse_ok;
+ const char *val;
+ enum dhcp_token token;
+ struct expression *expr = NULL;
+ struct expression *tmp, *new;
+ int status;
+
+ do {
+ tmp = NULL;
+ if (type == FIXED_ADDR) {
+ parse_ok = parse_ip_addr_or_hostname(&tmp, cfile, 1);
+ } else {
+ /* INSIST(type == FIXED_ADDR6); */
+ parse_ok = parse_ip6_addr_expr(&tmp, cfile);
+ }
+ if (parse_ok) {
+ if (expr != NULL) {
+ new = NULL;
+ status = make_concat(&new, expr, tmp);
+ expression_dereference(&expr, MDL);
+ expression_dereference(&tmp, MDL);
+ if (!status) {
+ return 0;
+ }
+ expr = new;
+ } else {
+ expr = tmp;
+ }
+ } else {
+ if (expr != NULL) {
+ expression_dereference (&expr, MDL);
+ }
+ return 0;
+ }
+ token = peek_token(&val, NULL, cfile);
+ if (token == COMMA) {
+ token = next_token(&val, NULL, cfile);
+ }
+ } while (token == COMMA);
+
+ if (!parse_semi(cfile)) {
+ if (expr) {
+ expression_dereference (&expr, MDL);
+ }
+ return 0;
+ }
+
+ status = option_cache(oc, NULL, expr, NULL, MDL);
+ expression_dereference(&expr, MDL);
+ return status;
+}
+
+/* lease_declaration :== LEASE ip_address LBRACE lease_parameters RBRACE
+
+ lease_parameters :== <nil>
+ | lease_parameter
+ | lease_parameters lease_parameter
+
+ lease_parameter :== STARTS date
+ | ENDS date
+ | TIMESTAMP date
+ | HARDWARE hardware-parameter
+ | UID hex_numbers SEMI
+ | HOSTNAME hostname SEMI
+ | CLIENT_HOSTNAME hostname SEMI
+ | CLASS identifier SEMI
+ | DYNAMIC_BOOTP SEMI */
+
+int parse_lease_declaration (struct lease **lp, struct parse *cfile)
+{
+ const char *val;
+ enum dhcp_token token;
+ unsigned char addr [4];
+ unsigned len = sizeof addr;
+ int seenmask = 0;
+ int seenbit;
+ char tbuf [32];
+ struct lease *lease;
+ struct executable_statement *on;
+ int lose;
+ TIME t;
+ int noequal, newbinding;
+ struct binding *binding;
+ struct binding_value *nv;
+ isc_result_t status;
+ struct option_cache *oc;
+ pair *p;
+ binding_state_t new_state;
+ unsigned buflen = 0;
+ struct class *class;
+
+ lease = (struct lease *)0;
+ status = lease_allocate (&lease, MDL);
+ if (status != ISC_R_SUCCESS)
+ return 0;
+
+ /* Get the address for which the lease has been issued. */
+ if (!parse_numeric_aggregate (cfile, addr, &len, DOT, 10, 8)) {
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+ memcpy (lease -> ip_addr.iabuf, addr, len);
+ lease -> ip_addr.len = len;
+
+ if (!parse_lbrace (cfile)) {
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+
+ do {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token == RBRACE)
+ break;
+ else if (token == END_OF_FILE) {
+ parse_warn (cfile, "unexpected end of file");
+ break;
+ }
+ strncpy (tbuf, val, sizeof tbuf);
+ tbuf [(sizeof tbuf) - 1] = 0;
+
+ /* Parse any of the times associated with the lease. */
+ switch (token) {
+ case STARTS:
+ case ENDS:
+ case TIMESTAMP:
+ case TSTP:
+ case TSFP:
+ case ATSFP:
+ case CLTT:
+ t = parse_date (cfile);
+ switch (token) {
+ case STARTS:
+ seenbit = 1;
+ lease -> starts = t;
+ break;
+
+ case ENDS:
+ seenbit = 2;
+ lease -> ends = t;
+ break;
+
+ case TSTP:
+ seenbit = 65536;
+ lease -> tstp = t;
+ break;
+
+ case TSFP:
+ seenbit = 131072;
+ lease -> tsfp = t;
+ break;
+
+ case ATSFP:
+ seenbit = 262144;
+ lease->atsfp = t;
+ break;
+
+ case CLTT:
+ seenbit = 524288;
+ lease -> cltt = t;
+ break;
+
+ default: /* for gcc, we'll never get here. */
+ log_fatal ("Impossible error at %s:%d.", MDL);
+ return 0;
+ }
+ break;
+
+ /* Colon-separated hexadecimal octets... */
+ case UID:
+ seenbit = 8;
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == STRING) {
+ unsigned char *tuid;
+ skip_token(&val, &buflen, cfile);
+ if (buflen < sizeof lease -> uid_buf) {
+ tuid = lease -> uid_buf;
+ lease -> uid_max =
+ sizeof lease -> uid_buf;
+ } else {
+ tuid = ((unsigned char *)
+ dmalloc (buflen, MDL));
+ if (!tuid) {
+ log_error ("no space for uid");
+ lease_dereference (&lease,
+ MDL);
+ return 0;
+ }
+ lease -> uid_max = buflen;
+ }
+ lease -> uid_len = buflen;
+ memcpy (tuid, val, lease -> uid_len);
+ lease -> uid = tuid;
+ } else {
+ buflen = 0;
+ lease -> uid = (parse_numeric_aggregate
+ (cfile, (unsigned char *)0,
+ &buflen, ':', 16, 8));
+ if (!lease -> uid) {
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+ lease -> uid_len = buflen;
+ lease -> uid_max = buflen;
+ if (lease -> uid_len == 0) {
+ lease -> uid = (unsigned char *)0;
+ parse_warn (cfile, "zero-length uid");
+ seenbit = 0;
+ parse_semi (cfile);
+ break;
+ }
+ }
+ parse_semi (cfile);
+ if (!lease -> uid) {
+ log_fatal ("No memory for lease uid");
+ }
+ break;
+
+ case CLASS:
+ seenbit = 32;
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!is_identifier (token)) {
+ if (token != SEMI)
+ skip_to_rbrace (cfile, 1);
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+ parse_semi (cfile);
+ /* for now, we aren't using this. */
+ break;
+
+ case HARDWARE:
+ seenbit = 64;
+ parse_hardware_param (cfile,
+ &lease -> hardware_addr);
+ break;
+
+ case TOKEN_RESERVED:
+ seenbit = 0;
+ lease->flags |= RESERVED_LEASE;
+ parse_semi(cfile);
+ break;
+
+ case DYNAMIC_BOOTP:
+ seenbit = 0;
+ lease -> flags |= BOOTP_LEASE;
+ parse_semi (cfile);
+ break;
+
+ /* XXX: Reverse compatibility? */
+ case TOKEN_ABANDONED:
+ seenbit = 256;
+ lease -> binding_state = FTS_ABANDONED;
+ lease -> next_binding_state = FTS_ABANDONED;
+ parse_semi (cfile);
+ break;
+
+ case TOKEN_NEXT:
+ seenbit = 128;
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != BINDING) {
+ parse_warn (cfile, "expecting 'binding'");
+ skip_to_semi (cfile);
+ break;
+ }
+ goto do_binding_state;
+
+ case REWIND:
+ seenbit = 512;
+ token = next_token(&val, NULL, cfile);
+ if (token != BINDING) {
+ parse_warn(cfile, "expecting 'binding'");
+ skip_to_semi(cfile);
+ break;
+ }
+ goto do_binding_state;
+
+ case BINDING:
+ seenbit = 256;
+
+ do_binding_state:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STATE) {
+ parse_warn (cfile, "expecting 'state'");
+ skip_to_semi (cfile);
+ break;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case TOKEN_ABANDONED:
+ new_state = FTS_ABANDONED;
+ break;
+ case TOKEN_FREE:
+ new_state = FTS_FREE;
+ break;
+ case TOKEN_ACTIVE:
+ new_state = FTS_ACTIVE;
+ break;
+ case TOKEN_EXPIRED:
+ new_state = FTS_EXPIRED;
+ break;
+ case TOKEN_RELEASED:
+ new_state = FTS_RELEASED;
+ break;
+ case TOKEN_RESET:
+ new_state = FTS_RESET;
+ break;
+ case TOKEN_BACKUP:
+ new_state = FTS_BACKUP;
+ break;
+
+ /* RESERVED and BOOTP states preserved for
+ * compatibleness with older versions.
+ */
+ case TOKEN_RESERVED:
+ new_state = FTS_ACTIVE;
+ lease->flags |= RESERVED_LEASE;
+ break;
+ case TOKEN_BOOTP:
+ new_state = FTS_ACTIVE;
+ lease->flags |= BOOTP_LEASE;
+ break;
+
+ default:
+ parse_warn (cfile,
+ "%s: expecting a binding state.",
+ val);
+ skip_to_semi (cfile);
+ return 0;
+ }
+
+ if (seenbit == 256) {
+ lease -> binding_state = new_state;
+
+ /*
+ * Apply default/conservative next/rewind
+ * binding states if they haven't been set
+ * yet. These defaults will be over-ridden if
+ * they are set later in parsing.
+ */
+ if (!(seenmask & 128))
+ lease->next_binding_state = new_state;
+
+ /* The most conservative rewind state. */
+ if (!(seenmask & 512))
+ lease->rewind_binding_state = new_state;
+ } else if (seenbit == 128)
+ lease -> next_binding_state = new_state;
+ else if (seenbit == 512)
+ lease->rewind_binding_state = new_state;
+ else
+ log_fatal("Impossible condition at %s:%d.",
+ MDL);
+
+ parse_semi (cfile);
+ break;
+
+ case CLIENT_HOSTNAME:
+ seenbit = 1024;
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == STRING) {
+ if (!parse_string (cfile,
+ &lease -> client_hostname,
+ (unsigned *)0)) {
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+ } else {
+ lease -> client_hostname =
+ parse_host_name (cfile);
+ if (lease -> client_hostname)
+ parse_semi (cfile);
+ else {
+ parse_warn (cfile,
+ "expecting a hostname.");
+ skip_to_semi (cfile);
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+ }
+ break;
+
+ case BILLING:
+ seenbit = 2048;
+ class = (struct class *)0;
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token == CLASS) {
+ token = next_token (&val,
+ (unsigned *)0, cfile);
+ if (token != STRING) {
+ parse_warn (cfile, "expecting string");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ token = BILLING;
+ break;
+ }
+ if (lease -> billing_class)
+ class_dereference (&lease -> billing_class,
+ MDL);
+ find_class (&class, val, MDL);
+ if (!class)
+ parse_warn (cfile,
+ "unknown class %s", val);
+ parse_semi (cfile);
+ } else if (token == SUBCLASS) {
+ if (lease -> billing_class)
+ class_dereference (&lease -> billing_class,
+ MDL);
+ parse_class_declaration(&class, cfile, NULL,
+ CLASS_TYPE_SUBCLASS);
+ } else {
+ parse_warn (cfile, "expecting \"class\"");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ }
+ if (class) {
+ class_reference (&lease -> billing_class,
+ class, MDL);
+ class_dereference (&class, MDL);
+ }
+ break;
+
+ case ON:
+ on = (struct executable_statement *)0;
+ lose = 0;
+ if (!parse_on_statement (&on, cfile, &lose)) {
+ skip_to_rbrace (cfile, 1);
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+ seenbit = 0;
+ if ((on -> data.on.evtypes & ON_EXPIRY) &&
+ on -> data.on.statements) {
+ seenbit |= 16384;
+ executable_statement_reference
+ (&lease -> on_expiry,
+ on -> data.on.statements, MDL);
+ }
+ if ((on -> data.on.evtypes & ON_RELEASE) &&
+ on -> data.on.statements) {
+ seenbit |= 32768;
+ executable_statement_reference
+ (&lease -> on_release,
+ on -> data.on.statements, MDL);
+ }
+ executable_statement_dereference (&on, MDL);
+ break;
+
+ case OPTION:
+ case SUPERSEDE:
+ noequal = 0;
+ seenbit = 0;
+ oc = (struct option_cache *)0;
+ if (parse_option_decl (&oc, cfile)) {
+ if (oc -> option -> universe !=
+ &agent_universe) {
+ parse_warn (cfile,
+ "agent option expected.");
+ option_cache_dereference (&oc, MDL);
+ break;
+ }
+ if (!lease -> agent_options &&
+ !(option_chain_head_allocate
+ (&lease -> agent_options, MDL))) {
+ log_error ("no memory to stash agent option");
+ break;
+ }
+ for (p = &lease -> agent_options -> first;
+ *p; p = &((*p) -> cdr))
+ ;
+ *p = cons (0, 0);
+ option_cache_reference (((struct option_cache **)
+ &((*p) -> car)), oc, MDL);
+ option_cache_dereference (&oc, MDL);
+ }
+ break;
+
+ case TOKEN_SET:
+ noequal = 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);
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+
+ seenbit = 0;
+ special_set:
+ if (lease -> scope)
+ binding = find_binding (lease -> scope, val);
+ else
+ binding = (struct binding *)0;
+
+ if (!binding) {
+ if (!lease -> scope)
+ if (!(binding_scope_allocate
+ (&lease -> scope, MDL)))
+ log_fatal ("no memory for scope");
+ binding = dmalloc (sizeof *binding, MDL);
+ if (!binding)
+ log_fatal ("No memory for lease %s.",
+ "binding");
+ memset (binding, 0, sizeof *binding);
+ binding -> name =
+ dmalloc (strlen (val) + 1, MDL);
+ if (!binding -> name)
+ log_fatal ("No memory for binding %s.",
+ "name");
+ strcpy (binding -> name, val);
+ newbinding = 1;
+ } else {
+ newbinding = 0;
+ }
+
+ nv = NULL;
+ if (!binding_value_allocate(&nv, MDL))
+ log_fatal("no memory for binding value.");
+
+ if (!noequal) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != EQUAL) {
+ parse_warn (cfile,
+ "expecting '=' in set statement.");
+ goto badset;
+ }
+ }
+
+ if (!parse_binding_value(cfile, nv)) {
+ binding_value_dereference(&nv, MDL);
+ lease_dereference(&lease, MDL);
+ return 0;
+ }
+
+ if (newbinding) {
+ binding_value_reference(&binding->value,
+ nv, MDL);
+ binding->next = lease->scope->bindings;
+ lease->scope->bindings = binding;
+ } else {
+ binding_value_dereference(&binding->value, MDL);
+ binding_value_reference(&binding->value,
+ nv, MDL);
+ }
+
+ binding_value_dereference(&nv, MDL);
+ parse_semi(cfile);
+ break;
+
+ /* case NAME: */
+ default:
+ if (!strcasecmp (val, "ddns-fwd-name")) {
+ seenbit = 4096;
+ noequal = 1;
+ goto special_set;
+ } else if (!strcasecmp (val, "ddns-rev-name")) {
+ seenbit = 8192;
+ noequal = 1;
+ goto special_set;
+ } else
+ parse_warn(cfile, "Unexpected configuration "
+ "directive.");
+ skip_to_semi (cfile);
+ seenbit = 0;
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+
+ if (seenmask & seenbit) {
+ parse_warn (cfile,
+ "Too many %s parameters in lease %s\n",
+ tbuf, piaddr (lease -> ip_addr));
+ } else
+ seenmask |= seenbit;
+
+ } while (1);
+
+ /* If no binding state is specified, make one up. */
+ if (!(seenmask & 256)) {
+ if (lease -> ends > cur_time ||
+ lease -> on_expiry || lease -> on_release)
+ lease -> binding_state = FTS_ACTIVE;
+#if defined (FAILOVER_PROTOCOL)
+ else if (lease -> pool && lease -> pool -> failover_peer)
+ lease -> binding_state = FTS_EXPIRED;
+#endif
+ else
+ lease -> binding_state = FTS_FREE;
+ if (lease -> binding_state == FTS_ACTIVE) {
+#if defined (FAILOVER_PROTOCOL)
+ if (lease -> pool && lease -> pool -> failover_peer)
+ lease -> next_binding_state = FTS_EXPIRED;
+ else
+#endif
+ lease -> next_binding_state = FTS_FREE;
+ } else
+ lease -> next_binding_state = lease -> binding_state;
+
+ /* The most conservative rewind state implies no rewind. */
+ lease->rewind_binding_state = lease->binding_state;
+ }
+
+ if (!(seenmask & 65536))
+ lease -> tstp = lease -> ends;
+
+ lease_reference (lp, lease, MDL);
+ lease_dereference (&lease, MDL);
+ return 1;
+}
+
+/* Parse the right side of a 'binding value'.
+ *
+ * set foo = "bar"; is a string
+ * set foo = false; is a boolean
+ * set foo = %31; is a numeric value.
+ */
+static int
+parse_binding_value(struct parse *cfile, struct binding_value *value)
+{
+ struct data_string *data;
+ unsigned char *s;
+ const char *val;
+ unsigned buflen;
+ int token;
+
+ if ((cfile == NULL) || (value == NULL))
+ log_fatal("Invalid arguments at %s:%d.", MDL);
+
+ token = peek_token(&val, NULL, cfile);
+ if (token == STRING) {
+ skip_token(&val, &buflen, cfile);
+
+ value->type = binding_data;
+ value->value.data.len = buflen;
+
+ data = &value->value.data;
+
+ if (!buffer_allocate(&data->buffer, buflen + 1, MDL))
+ log_fatal ("No memory for binding.");
+
+ memcpy(data->buffer->data, val, buflen + 1);
+
+ data->data = data->buffer->data;
+ data->terminated = 1;
+ } else if (token == NUMBER_OR_NAME) {
+ value->type = binding_data;
+
+ data = &value->value.data;
+ s = parse_numeric_aggregate(cfile, NULL, &data->len,
+ ':', 16, 8);
+ if (s == NULL) {
+ skip_to_semi(cfile);
+ return 0;
+ }
+
+ if (data->len) {
+ if (!buffer_allocate(&data->buffer, data->len + 1,
+ MDL))
+ log_fatal("No memory for binding.");
+
+ memcpy(data->buffer->data, s, data->len);
+ data->data = data->buffer->data;
+
+ dfree (s, MDL);
+ }
+ } else if (token == PERCENT) {
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "expecting decimal number.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return 0;
+ }
+ value->type = binding_numeric;
+ value->value.intval = atol(val);
+ } else if (token == NAME) {
+ token = next_token(&val, NULL, cfile);
+ value->type = binding_boolean;
+ if (!strcasecmp(val, "true"))
+ value->value.boolean = 1;
+ else if (!strcasecmp(val, "false"))
+ value->value.boolean = 0;
+ else {
+ parse_warn(cfile, "expecting true or false");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return 0;
+ }
+ } else {
+ parse_warn (cfile, "expecting a constant value.");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ return 0;
+ }
+
+ return 1;
+}
+
+/* address-range-declaration :== ip-address ip-address SEMI
+ | DYNAMIC_BOOTP ip-address ip-address SEMI */
+
+void parse_address_range (cfile, group, type, inpool, lpchain)
+ struct parse *cfile;
+ struct group *group;
+ int type;
+ struct pool *inpool;
+ struct lease **lpchain;
+{
+ struct iaddr low, high, net;
+ unsigned char addr [4];
+ unsigned len = sizeof addr;
+ enum dhcp_token token;
+ const char *val;
+ int dynamic = 0;
+ struct subnet *subnet;
+ struct shared_network *share;
+ struct pool *pool;
+ isc_result_t status;
+
+ if ((token = peek_token (&val,
+ (unsigned *)0, cfile)) == DYNAMIC_BOOTP) {
+ skip_token(&val, (unsigned *)0, cfile);
+ dynamic = 1;
+ }
+
+ /* Get the bottom address in the range... */
+ if (!parse_numeric_aggregate (cfile, addr, &len, DOT, 10, 8))
+ return;
+ memcpy (low.iabuf, addr, len);
+ low.len = len;
+
+ /* Only one address? */
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == SEMI)
+ high = low;
+ else {
+ /* Get the top address in the range... */
+ if (!parse_numeric_aggregate (cfile, addr, &len, DOT, 10, 8))
+ return;
+ memcpy (high.iabuf, addr, len);
+ high.len = len;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != SEMI) {
+ parse_warn (cfile, "semicolon expected.");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ if (type == SUBNET_DECL) {
+ subnet = group -> subnet;
+ share = subnet -> shared_network;
+ } else {
+ share = group -> shared_network;
+ for (subnet = share -> subnets;
+ subnet; subnet = subnet -> next_sibling) {
+ net = subnet_number (low, subnet -> netmask);
+ if (addr_eq (net, subnet -> net))
+ break;
+ }
+ if (!subnet) {
+ parse_warn (cfile, "address range not on network %s",
+ group -> shared_network -> name);
+ log_error ("Be sure to place pool statement after %s",
+ "related subnet declarations.");
+ return;
+ }
+ }
+
+ if (!inpool) {
+ struct pool *last = (struct pool *)0;
+
+ /* If we're permitting dynamic bootp for this range,
+ then look for a pool with an empty prohibit list and
+ a permit list with one entry that permits all clients. */
+ for (pool = share -> pools; pool; pool = pool -> next) {
+ if ((!dynamic && !pool -> permit_list &&
+ pool -> prohibit_list &&
+ !pool -> prohibit_list -> next &&
+ (pool -> prohibit_list -> type ==
+ permit_dynamic_bootp_clients)) ||
+ (dynamic && !pool -> prohibit_list &&
+ pool -> permit_list &&
+ !pool -> permit_list -> next &&
+ (pool -> permit_list -> type ==
+ permit_all_clients))) {
+ break;
+ }
+ last = pool;
+ }
+
+ /* If we didn't get a pool, make one. */
+ if (!pool) {
+ struct permit *p;
+ status = pool_allocate (&pool, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("no memory for ad-hoc pool: %s",
+ isc_result_totext (status));
+ p = new_permit (MDL);
+ if (!p)
+ log_fatal ("no memory for ad-hoc permit.");
+
+ /* Dynamic pools permit all clients. Otherwise
+ we prohibit BOOTP clients. */
+ if (dynamic) {
+ p -> type = permit_all_clients;
+ pool -> permit_list = p;
+ } else {
+ p -> type = permit_dynamic_bootp_clients;
+ pool -> prohibit_list = p;
+ }
+
+ if (share -> pools)
+ pool_reference (&last -> next, pool, MDL);
+ else
+ pool_reference (&share -> pools, pool, MDL);
+ shared_network_reference (&pool -> shared_network,
+ share, MDL);
+ if (!clone_group (&pool -> group, share -> group, MDL))
+ log_fatal ("no memory for anon pool group.");
+ } else {
+ pool = (struct pool *)0;
+ if (last)
+ pool_reference (&pool, last, MDL);
+ else
+ pool_reference (&pool, share -> pools, MDL);
+ }
+ } else {
+ pool = (struct pool *)0;
+ pool_reference (&pool, inpool, MDL);
+ }
+
+#if defined (FAILOVER_PROTOCOL)
+ if (pool -> failover_peer && dynamic) {
+ /* Doctor, do you think I'm overly sensitive
+ about getting bug reports I can't fix? */
+ parse_warn (cfile, "dynamic-bootp flag is %s",
+ "not permitted for address");
+ log_error ("range declarations where there is a failover");
+ log_error ("peer in scope. If you wish to declare an");
+ log_error ("address range from which dynamic bootp leases");
+ log_error ("can be allocated, please declare it within a");
+ log_error ("pool declaration that also contains the \"no");
+ log_error ("failover\" statement. The failover protocol");
+ log_error ("itself does not permit dynamic bootp - this");
+ log_error ("is not a limitation specific to the ISC DHCP");
+ log_error ("server. Please don't ask me to defend this");
+ log_error ("until you have read and really tried %s",
+ "to understand");
+ log_error ("the failover protocol specification.");
+
+ /* We don't actually bomb at this point - instead,
+ we let parse_lease_file notice the error and
+ bomb at that point - it's easier. */
+ }
+#endif /* FAILOVER_PROTOCOL */
+
+ /* Create the new address range... */
+ new_address_range (cfile, low, high, subnet, pool, lpchain);
+ pool_dereference (&pool, MDL);
+}
+
+#ifdef DHCPv6
+static void
+add_ipv6_pool_to_subnet(struct subnet *subnet, u_int16_t type,
+ struct iaddr *lo_addr, int bits, int units) {
+ struct ipv6_pool *pool;
+ struct shared_network *share;
+ struct in6_addr tmp_in6_addr;
+ int num_pools;
+ struct ipv6_pool **tmp;
+
+ share = subnet->shared_network;
+
+ /*
+ * Create our pool.
+ */
+ if (lo_addr->len != sizeof(tmp_in6_addr)) {
+ log_fatal("Internal error: Attempt to add non-IPv6 address "
+ "to IPv6 shared network.");
+ }
+ memcpy(&tmp_in6_addr, lo_addr->iabuf, sizeof(tmp_in6_addr));
+ pool = NULL;
+ if (ipv6_pool_allocate(&pool, type, &tmp_in6_addr,
+ bits, units, MDL) != ISC_R_SUCCESS) {
+ log_fatal("Out of memory");
+ }
+
+ /*
+ * Add to our global IPv6 pool set.
+ */
+ if (add_ipv6_pool(pool) != ISC_R_SUCCESS) {
+ log_fatal ("Out of memory");
+ }
+
+ /*
+ * Link the pool to its network.
+ */
+ pool->subnet = NULL;
+ subnet_reference(&pool->subnet, subnet, MDL);
+ pool->shared_network = NULL;
+ shared_network_reference(&pool->shared_network, share, MDL);
+
+ /*
+ * Increase our array size for ipv6_pools in the shared_network.
+ */
+ if (share->ipv6_pools == NULL) {
+ num_pools = 0;
+ } else {
+ num_pools = 0;
+ while (share->ipv6_pools[num_pools] != NULL) {
+ num_pools++;
+ }
+ }
+ tmp = dmalloc(sizeof(struct ipv6_pool *) * (num_pools + 2), MDL);
+ if (tmp == NULL) {
+ log_fatal("Out of memory");
+ }
+ if (num_pools > 0) {
+ memcpy(tmp, share->ipv6_pools,
+ sizeof(struct ipv6_pool *) * num_pools);
+ }
+ if (share->ipv6_pools != NULL) {
+ dfree(share->ipv6_pools, MDL);
+ }
+ share->ipv6_pools = tmp;
+
+ /*
+ * Record this pool in our array of pools for this shared network.
+ */
+ ipv6_pool_reference(&share->ipv6_pools[num_pools], pool, MDL);
+ share->ipv6_pools[num_pools+1] = NULL;
+}
+
+/* address-range6-declaration :== ip-address6 ip-address6 SEMI
+ | ip-address6 SLASH number SEMI
+ | ip-address6 [SLASH number] TEMPORARY SEMI */
+
+void
+parse_address_range6(struct parse *cfile, struct group *group) {
+ struct iaddr lo, hi;
+ int bits;
+ enum dhcp_token token;
+ const char *val;
+ struct iaddrcidrnetlist *nets;
+ struct iaddrcidrnetlist *p;
+ u_int16_t type = D6O_IA_NA;
+
+ if (local_family != AF_INET6) {
+ parse_warn(cfile, "range6 statement is only supported "
+ "in DHCPv6 mode.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /* This is enforced by the caller, this is just a sanity check. */
+ if (group->subnet == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ /*
+ * Read starting address.
+ */
+ if (!parse_ip6_addr(cfile, &lo)) {
+ return;
+ }
+
+ /* Make sure starting address is within the subnet */
+ if (!addr_eq(group->subnet->net,
+ subnet_number(lo, group->subnet->netmask))) {
+ parse_warn(cfile, "range6 start address is outside the subnet");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * See if we we're using range or CIDR notation or TEMPORARY
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SLASH) {
+ /*
+ * '/' means CIDR notation, so read the bits we want.
+ */
+ skip_token(NULL, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "expecting number");
+ skip_to_semi(cfile);
+ return;
+ }
+ bits = atoi(val);
+ if ((bits < 0) || (bits > 128)) {
+ parse_warn(cfile, "networks have 0 to 128 bits");
+ skip_to_semi(cfile);
+ return;
+ }
+ if (bits < group->subnet->prefix_len) {
+ parse_warn(cfile,
+ "network mask smaller than subnet mask");
+ skip_to_semi(cfile);
+ return;
+ }
+ if (!is_cidr_mask_valid(&lo, bits)) {
+ parse_warn(cfile, "network mask too short");
+ skip_to_semi(cfile);
+ return;
+ }
+ /*
+ * can be temporary (RFC 4941 like)
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == TEMPORARY) {
+ if (bits < 64)
+ parse_warn(cfile, "temporary mask too short");
+ if (bits == 128)
+ parse_warn(cfile, "temporary singleton?");
+ skip_token(NULL, NULL, cfile);
+ type = D6O_IA_TA;
+ }
+
+ add_ipv6_pool_to_subnet(group->subnet, type, &lo,
+ bits, 128);
+
+ } else if (token == TEMPORARY) {
+ /*
+ * temporary (RFC 4941)
+ */
+ type = D6O_IA_TA;
+ skip_token(NULL, NULL, cfile);
+ bits = 64;
+ if (!is_cidr_mask_valid(&lo, bits)) {
+ parse_warn(cfile, "network mask too short");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ add_ipv6_pool_to_subnet(group->subnet, type, &lo,
+ bits, 128);
+ } else {
+ /*
+ * No '/', so we are looking for the end address of
+ * the IPv6 pool.
+ */
+ if (!parse_ip6_addr(cfile, &hi)) {
+ return;
+ }
+
+ /* Make sure ending address is within the subnet */
+ if (!addr_eq(group->subnet->net,
+ subnet_number(hi, group->subnet->netmask))) {
+ parse_warn(cfile,
+ "range6 end address is outside the subnet");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * Convert our range to a set of CIDR networks.
+ */
+ nets = NULL;
+ if (range2cidr(&nets, &lo, &hi) != ISC_R_SUCCESS) {
+ log_fatal("Error converting range to CIDR networks");
+ }
+
+ for (p=nets; p != NULL; p=p->next) {
+ add_ipv6_pool_to_subnet(group->subnet, type,
+ &p->cidrnet.lo_addr,
+ p->cidrnet.bits, 128);
+ }
+
+ free_iaddrcidrnetlist(&nets);
+ }
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "semicolon expected.");
+ skip_to_semi(cfile);
+ return;
+ }
+}
+
+/* prefix6-declaration :== ip-address6 ip-address6 SLASH number SEMI */
+
+void
+parse_prefix6(struct parse *cfile, struct group *group) {
+ struct iaddr lo, hi;
+ int bits;
+ enum dhcp_token token;
+ const char *val;
+ struct iaddrcidrnetlist *nets;
+ struct iaddrcidrnetlist *p;
+
+ if (local_family != AF_INET6) {
+ parse_warn(cfile, "prefix6 statement is only supported "
+ "in DHCPv6 mode.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /* This is enforced by the caller, so it's just a sanity check. */
+ if (group->subnet == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ /*
+ * Read starting and ending address.
+ */
+ if (!parse_ip6_addr(cfile, &lo)) {
+ return;
+ }
+
+ /* Make sure starting prefix is within the subnet */
+ if (!addr_eq(group->subnet->net,
+ subnet_number(lo, group->subnet->netmask))) {
+ parse_warn(cfile, "prefix6 start prefix"
+ " is outside the subnet");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ if (!parse_ip6_addr(cfile, &hi)) {
+ return;
+ }
+
+ /* Make sure ending prefix is within the subnet */
+ if (!addr_eq(group->subnet->net,
+ subnet_number(hi, group->subnet->netmask))) {
+ parse_warn(cfile, "prefix6 end prefix"
+ " is outside the subnet");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * Next is '/' number ';'.
+ */
+ token = next_token(NULL, NULL, cfile);
+ if (token != SLASH) {
+ parse_warn(cfile, "expecting '/'");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return;
+ }
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "expecting number");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return;
+ }
+ bits = atoi(val);
+ if ((bits <= 0) || (bits >= 128)) {
+ parse_warn(cfile, "networks have 0 to 128 bits (exclusive)");
+ return;
+ }
+ if (bits < group->subnet->prefix_len) {
+ parse_warn(cfile, "network mask smaller than subnet mask");
+ skip_to_semi(cfile);
+ return;
+ }
+ if (!is_cidr_mask_valid(&lo, bits) ||
+ !is_cidr_mask_valid(&hi, bits)) {
+ parse_warn(cfile, "network mask too short");
+ skip_to_semi(cfile);
+ return;
+ }
+ token = next_token(NULL, NULL, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "semicolon expected.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * Convert our range to a set of CIDR networks.
+ */
+ nets = NULL;
+ if (range2cidr(&nets, &lo, &hi) != ISC_R_SUCCESS) {
+ log_fatal("Error converting prefix to CIDR");
+ }
+
+ for (p = nets; p != NULL; p = p->next) {
+ /* Normalize and check. */
+ if (p->cidrnet.bits == 128) {
+ p->cidrnet.bits = bits;
+ }
+ if (p->cidrnet.bits > bits) {
+ parse_warn(cfile, "impossible mask length");
+ continue;
+ }
+ add_ipv6_pool_to_subnet(group->subnet, D6O_IA_PD,
+ &p->cidrnet.lo_addr,
+ p->cidrnet.bits, bits);
+ }
+
+ free_iaddrcidrnetlist(&nets);
+}
+
+/* fixed-prefix6 :== ip6-address SLASH number SEMI */
+
+void
+parse_fixed_prefix6(struct parse *cfile, struct host_decl *host_decl) {
+ struct iaddrcidrnetlist *ia, **h;
+ enum dhcp_token token;
+ const char *val;
+
+ /*
+ * Get the head of the fixed-prefix list.
+ */
+ h = &host_decl->fixed_prefix;
+
+ /*
+ * Walk to the end.
+ */
+ while (*h != NULL) {
+ h = &((*h)->next);
+ }
+
+ /*
+ * Allocate a new iaddrcidrnetlist structure.
+ */
+ ia = dmalloc(sizeof(*ia), MDL);
+ if (!ia) {
+ log_fatal("Out of memory");
+ }
+
+ /*
+ * Parse it.
+ */
+ if (!parse_ip6_addr(cfile, &ia->cidrnet.lo_addr)) {
+ dfree(ia, MDL);
+ return;
+ }
+ token = next_token(NULL, NULL, cfile);
+ if (token != SLASH) {
+ dfree(ia, MDL);
+ parse_warn(cfile, "expecting '/'");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return;
+ }
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ dfree(ia, MDL);
+ parse_warn(cfile, "expecting number");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return;
+ }
+ token = next_token(NULL, NULL, cfile);
+ if (token != SEMI) {
+ dfree(ia, MDL);
+ parse_warn(cfile, "semicolon expected.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * Fill it.
+ */
+ ia->cidrnet.bits = atoi(val);
+ if ((ia->cidrnet.bits < 0) || (ia->cidrnet.bits > 128)) {
+ dfree(ia, MDL);
+ parse_warn(cfile, "networks have 0 to 128 bits");
+ return;
+ }
+ if (!is_cidr_mask_valid(&ia->cidrnet.lo_addr, ia->cidrnet.bits)) {
+ dfree(ia, MDL);
+ parse_warn(cfile, "network mask too short");
+ return;
+ }
+
+ /*
+ * Store it.
+ */
+ *h = ia;
+ return;
+}
+
+#endif /* DHCPv6 */
+
+/* allow-deny-keyword :== BOOTP
+ | BOOTING
+ | DYNAMIC_BOOTP
+ | UNKNOWN_CLIENTS */
+
+int parse_allow_deny (oc, cfile, flag)
+ struct option_cache **oc;
+ struct parse *cfile;
+ int flag;
+{
+ enum dhcp_token token;
+ const char *val;
+ unsigned char rf = flag;
+ unsigned code;
+ struct option *option = NULL;
+ struct expression *data = (struct expression *)0;
+ int status;
+
+ if (!make_const_data (&data, &rf, 1, 0, 1, MDL))
+ return 0;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case TOKEN_BOOTP:
+ code = SV_ALLOW_BOOTP;
+ break;
+
+ case BOOTING:
+ code = SV_ALLOW_BOOTING;
+ break;
+
+ case DYNAMIC_BOOTP:
+ code = SV_DYNAMIC_BOOTP;
+ break;
+
+ case UNKNOWN_CLIENTS:
+ code = SV_BOOT_UNKNOWN_CLIENTS;
+ break;
+
+ case DUPLICATES:
+ code = SV_DUPLICATES;
+ break;
+
+ case DECLINES:
+ code= SV_DECLINES;
+ break;
+
+ case CLIENT_UPDATES:
+ code = SV_CLIENT_UPDATES;
+ break;
+
+ case LEASEQUERY:
+ code = SV_LEASEQUERY;
+ break;
+
+ default:
+ parse_warn (cfile, "expecting allow/deny key");
+ skip_to_semi (cfile);
+ expression_dereference (&data, MDL);
+ return 0;
+ }
+ /* Reference on option is passed to option cache. */
+ if (!option_code_hash_lookup(&option, server_universe.code_hash,
+ &code, 0, MDL))
+ log_fatal("Unable to find server option %u (%s:%d).",
+ code, MDL);
+ status = option_cache(oc, NULL, data, option, MDL);
+ expression_dereference (&data, MDL);
+ parse_semi (cfile);
+ return status;
+}
+
+void
+parse_ia_na_declaration(struct parse *cfile) {
+#if !defined(DHCPv6)
+ parse_warn(cfile, "No DHCPv6 support.");
+ skip_to_semi(cfile);
+#else /* defined(DHCPv6) */
+ enum dhcp_token token;
+ struct ia_xx *ia;
+ const char *val;
+ struct ia_xx *old_ia;
+ unsigned int len;
+ u_int32_t iaid;
+ struct iaddr iaddr;
+ binding_state_t state;
+ u_int32_t prefer;
+ u_int32_t valid;
+ TIME end_time;
+ struct iasubopt *iaaddr;
+ struct ipv6_pool *pool;
+ char addr_buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+ isc_boolean_t newbinding;
+ struct binding_scope *scope=NULL;
+ struct binding *bnd;
+ struct binding_value *nv=NULL;
+
+ if (local_family != AF_INET6) {
+ parse_warn(cfile, "IA_NA is only supported in DHCPv6 mode.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting an iaid+ia_na string");
+ skip_to_semi(cfile);
+ return;
+ }
+ if (len < 5) {
+ parse_warn(cfile, "corrupt lease file; "
+ "iaid+ia_na string too short");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ memcpy(&iaid, val, 4);
+ ia = NULL;
+ if (ia_allocate(&ia, iaid, val+4, len-4, MDL) != ISC_R_SUCCESS) {
+ log_fatal("Out of memory.");
+ }
+ ia->ia_type = D6O_IA_NA;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "corrupt lease file; expecting left brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ for (;;) {
+ token = next_token(&val, NULL, cfile);
+ if (token == RBRACE) break;
+
+ if (token == CLTT) {
+ ia->cltt = parse_date (cfile);
+ continue;
+ }
+
+ if (token != IAADDR) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting IAADDR or right brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ if (!parse_ip6_addr(cfile, &iaddr)) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting IPv6 address");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting left brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ state = FTS_LAST+1;
+ prefer = valid = 0;
+ end_time = -1;
+ for (;;) {
+ token = next_token(&val, NULL, cfile);
+ if (token == RBRACE) break;
+
+ switch(token) {
+ /* Lease binding state. */
+ case BINDING:
+ token = next_token(&val, NULL, cfile);
+ if (token != STATE) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting state");
+ skip_to_semi(cfile);
+ return;
+ }
+ token = next_token(&val, NULL, cfile);
+ switch (token) {
+ case TOKEN_ABANDONED:
+ state = FTS_ABANDONED;
+ break;
+ case TOKEN_FREE:
+ state = FTS_FREE;
+ break;
+ case TOKEN_ACTIVE:
+ state = FTS_ACTIVE;
+ break;
+ case TOKEN_EXPIRED:
+ state = FTS_EXPIRED;
+ break;
+ case TOKEN_RELEASED:
+ state = FTS_RELEASED;
+ break;
+ default:
+ parse_warn(cfile,
+ "corrupt lease "
+ "file; "
+ "expecting a "
+ "binding state.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting "
+ "semicolon.");
+ }
+ break;
+
+ /* Lease preferred lifetime. */
+ case PREFERRED_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "%s is not a valid "
+ "preferred time",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ prefer = atoi (val);
+
+ /*
+ * Currently we peek for the semi-colon to
+ * allow processing of older lease files that
+ * don't have the semi-colon. Eventually we
+ * should remove the peeking code.
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI) {
+ skip_token(&val, NULL, cfile);
+ } else {
+ parse_warn(cfile,
+ "corrupt lease file; "
+ "expecting semicolon.");
+ }
+ break;
+
+ /* Lease valid lifetime. */
+ case MAX_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "%s is not a valid "
+ "max time",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ valid = atoi (val);
+
+ /*
+ * Currently we peek for the semi-colon to
+ * allow processing of older lease files that
+ * don't have the semi-colon. Eventually we
+ * should remove the peeking code.
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI) {
+ skip_token(&val, NULL, cfile);
+ } else {
+ parse_warn(cfile,
+ "corrupt lease file; "
+ "expecting semicolon.");
+ }
+ break;
+
+ /* Lease expiration time. */
+ case ENDS:
+ end_time = parse_date(cfile);
+ break;
+
+ /* Lease binding scopes. */
+ case TOKEN_SET:
+ token = next_token(&val, NULL, cfile);
+ if ((token != NAME) &&
+ (token != NUMBER_OR_NAME)) {
+ parse_warn(cfile, "%s is not a valid "
+ "variable name",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+
+ if (scope != NULL)
+ bnd = find_binding(scope, val);
+ else {
+ if (!binding_scope_allocate(&scope,
+ MDL)) {
+ log_fatal("Out of memory for "
+ "lease binding "
+ "scope.");
+ }
+
+ bnd = NULL;
+ }
+
+ if (bnd == NULL) {
+ bnd = dmalloc(sizeof(*bnd),
+ MDL);
+ if (bnd == NULL) {
+ log_fatal("No memory for "
+ "lease binding.");
+ }
+
+ bnd->name = dmalloc(strlen(val) + 1,
+ MDL);
+ if (bnd->name == NULL) {
+ log_fatal("No memory for "
+ "binding name.");
+ }
+ strcpy(bnd->name, val);
+
+ newbinding = ISC_TRUE;
+ } else {
+ newbinding = ISC_FALSE;
+ }
+
+ if (!binding_value_allocate(&nv, MDL)) {
+ log_fatal("no memory for binding "
+ "value.");
+ }
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != EQUAL) {
+ parse_warn(cfile, "expecting '=' in "
+ "set statement.");
+ goto binding_err;
+ }
+
+ if (!parse_binding_value(cfile, nv)) {
+ binding_err:
+ binding_value_dereference(&nv, MDL);
+ binding_scope_dereference(&scope, MDL);
+ return;
+ }
+
+ if (newbinding) {
+ binding_value_reference(&bnd->value,
+ nv, MDL);
+ bnd->next = scope->bindings;
+ scope->bindings = bnd;
+ } else {
+ binding_value_dereference(&bnd->value,
+ MDL);
+ binding_value_reference(&bnd->value,
+ nv, MDL);
+ }
+
+ binding_value_dereference(&nv, MDL);
+ parse_semi(cfile);
+ break;
+
+ default:
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting ia_na contents, "
+ "got '%s'", val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ }
+
+ if (state == FTS_LAST+1) {
+ parse_warn(cfile, "corrupt lease file; "
+ "missing state in iaaddr");
+ return;
+ }
+ if (end_time == -1) {
+ parse_warn(cfile, "corrupt lease file; "
+ "missing end time in iaaddr");
+ return;
+ }
+
+ iaaddr = NULL;
+ if (iasubopt_allocate(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ log_fatal("Out of memory.");
+ }
+ memcpy(&iaaddr->addr, iaddr.iabuf, sizeof(iaaddr->addr));
+ iaaddr->plen = 0;
+ iaaddr->state = state;
+ iaaddr->prefer = prefer;
+ iaaddr->valid = valid;
+ if (iaaddr->state == FTS_RELEASED)
+ iaaddr->hard_lifetime_end_time = end_time;
+
+ if (scope != NULL) {
+ binding_scope_reference(&iaaddr->scope, scope, MDL);
+ binding_scope_dereference(&scope, MDL);
+ }
+
+ /* find the pool this address is in */
+ pool = NULL;
+ if (find_ipv6_pool(&pool, D6O_IA_NA,
+ &iaaddr->addr) != ISC_R_SUCCESS) {
+ inet_ntop(AF_INET6, &iaaddr->addr,
+ addr_buf, sizeof(addr_buf));
+ parse_warn(cfile, "no pool found for address %s",
+ addr_buf);
+ return;
+ }
+
+ /* remove old information */
+ if (cleanup_lease6(ia_na_active, pool,
+ iaaddr, ia) != ISC_R_SUCCESS) {
+ inet_ntop(AF_INET6, &iaaddr->addr,
+ addr_buf, sizeof(addr_buf));
+ parse_warn(cfile, "duplicate na lease for address %s",
+ addr_buf);
+ }
+
+ /*
+ * if we like the lease we add it to our various structues
+ * otherwise we leave it and it will get cleaned when we
+ * do the iasubopt_dereference.
+ */
+ if ((state == FTS_ACTIVE) || (state == FTS_ABANDONED)) {
+ ia_add_iasubopt(ia, iaaddr, MDL);
+ ia_reference(&iaaddr->ia, ia, MDL);
+ add_lease6(pool, iaaddr, end_time);
+ }
+
+ iasubopt_dereference(&iaaddr, MDL);
+ ipv6_pool_dereference(&pool, MDL);
+ }
+
+ /*
+ * If we have an existing record for this IA_NA, remove it.
+ */
+ old_ia = NULL;
+ if (ia_hash_lookup(&old_ia, ia_na_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, MDL)) {
+ ia_hash_delete(ia_na_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, MDL);
+ ia_dereference(&old_ia, MDL);
+ }
+
+ /*
+ * If we have addresses, add this, otherwise don't bother.
+ */
+ if (ia->num_iasubopt > 0) {
+ ia_hash_add(ia_na_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, ia, MDL);
+ }
+ ia_dereference(&ia, MDL);
+#endif /* defined(DHCPv6) */
+}
+
+void
+parse_ia_ta_declaration(struct parse *cfile) {
+#if !defined(DHCPv6)
+ parse_warn(cfile, "No DHCPv6 support.");
+ skip_to_semi(cfile);
+#else /* defined(DHCPv6) */
+ enum dhcp_token token;
+ struct ia_xx *ia;
+ const char *val;
+ struct ia_xx *old_ia;
+ unsigned int len;
+ u_int32_t iaid;
+ struct iaddr iaddr;
+ binding_state_t state;
+ u_int32_t prefer;
+ u_int32_t valid;
+ TIME end_time;
+ struct iasubopt *iaaddr;
+ struct ipv6_pool *pool;
+ char addr_buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+ isc_boolean_t newbinding;
+ struct binding_scope *scope=NULL;
+ struct binding *bnd;
+ struct binding_value *nv=NULL;
+
+ if (local_family != AF_INET6) {
+ parse_warn(cfile, "IA_TA is only supported in DHCPv6 mode.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting an iaid+ia_ta string");
+ skip_to_semi(cfile);
+ return;
+ }
+ if (len < 5) {
+ parse_warn(cfile, "corrupt lease file; "
+ "iaid+ia_ta string too short");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ memcpy(&iaid, val, 4);
+ ia = NULL;
+ if (ia_allocate(&ia, iaid, val+4, len-4, MDL) != ISC_R_SUCCESS) {
+ log_fatal("Out of memory.");
+ }
+ ia->ia_type = D6O_IA_TA;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "corrupt lease file; expecting left brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ for (;;) {
+ token = next_token(&val, NULL, cfile);
+ if (token == RBRACE) break;
+
+ if (token == CLTT) {
+ ia->cltt = parse_date (cfile);
+ continue;
+ }
+
+ if (token != IAADDR) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting IAADDR or right brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ if (!parse_ip6_addr(cfile, &iaddr)) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting IPv6 address");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting left brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ state = FTS_LAST+1;
+ prefer = valid = 0;
+ end_time = -1;
+ for (;;) {
+ token = next_token(&val, NULL, cfile);
+ if (token == RBRACE) break;
+
+ switch(token) {
+ /* Lease binding state. */
+ case BINDING:
+ token = next_token(&val, NULL, cfile);
+ if (token != STATE) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting state");
+ skip_to_semi(cfile);
+ return;
+ }
+ token = next_token(&val, NULL, cfile);
+ switch (token) {
+ case TOKEN_ABANDONED:
+ state = FTS_ABANDONED;
+ break;
+ case TOKEN_FREE:
+ state = FTS_FREE;
+ break;
+ case TOKEN_ACTIVE:
+ state = FTS_ACTIVE;
+ break;
+ case TOKEN_EXPIRED:
+ state = FTS_EXPIRED;
+ break;
+ case TOKEN_RELEASED:
+ state = FTS_RELEASED;
+ break;
+ default:
+ parse_warn(cfile,
+ "corrupt lease "
+ "file; "
+ "expecting a "
+ "binding state.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting "
+ "semicolon.");
+ }
+ break;
+
+ /* Lease preferred lifetime. */
+ case PREFERRED_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "%s is not a valid "
+ "preferred time",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ prefer = atoi (val);
+
+ /*
+ * Currently we peek for the semi-colon to
+ * allow processing of older lease files that
+ * don't have the semi-colon. Eventually we
+ * should remove the peeking code.
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI) {
+ skip_token(&val, NULL, cfile);
+ } else {
+ parse_warn(cfile,
+ "corrupt lease file; "
+ "expecting semicolon.");
+ }
+ break;
+
+ /* Lease valid lifetime. */
+ case MAX_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "%s is not a valid "
+ "max time",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ valid = atoi (val);
+
+ /*
+ * Currently we peek for the semi-colon to
+ * allow processing of older lease files that
+ * don't have the semi-colon. Eventually we
+ * should remove the peeking code.
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI) {
+ skip_token(&val, NULL, cfile);
+ } else {
+ parse_warn(cfile,
+ "corrupt lease file; "
+ "expecting semicolon.");
+ }
+ break;
+
+ /* Lease expiration time. */
+ case ENDS:
+ end_time = parse_date(cfile);
+ break;
+
+ /* Lease binding scopes. */
+ case TOKEN_SET:
+ token = next_token(&val, NULL, cfile);
+ if ((token != NAME) &&
+ (token != NUMBER_OR_NAME)) {
+ parse_warn(cfile, "%s is not a valid "
+ "variable name",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+
+ if (scope != NULL)
+ bnd = find_binding(scope, val);
+ else {
+ if (!binding_scope_allocate(&scope,
+ MDL)) {
+ log_fatal("Out of memory for "
+ "lease binding "
+ "scope.");
+ }
+
+ bnd = NULL;
+ }
+
+ if (bnd == NULL) {
+ bnd = dmalloc(sizeof(*bnd),
+ MDL);
+ if (bnd == NULL) {
+ log_fatal("No memory for "
+ "lease binding.");
+ }
+
+ bnd->name = dmalloc(strlen(val) + 1,
+ MDL);
+ if (bnd->name == NULL) {
+ log_fatal("No memory for "
+ "binding name.");
+ }
+ strcpy(bnd->name, val);
+
+ newbinding = ISC_TRUE;
+ } else {
+ newbinding = ISC_FALSE;
+ }
+
+ if (!binding_value_allocate(&nv, MDL)) {
+ log_fatal("no memory for binding "
+ "value.");
+ }
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != EQUAL) {
+ parse_warn(cfile, "expecting '=' in "
+ "set statement.");
+ goto binding_err;
+ }
+
+ if (!parse_binding_value(cfile, nv)) {
+ binding_err:
+ binding_value_dereference(&nv, MDL);
+ binding_scope_dereference(&scope, MDL);
+ return;
+ }
+
+ if (newbinding) {
+ binding_value_reference(&bnd->value,
+ nv, MDL);
+ bnd->next = scope->bindings;
+ scope->bindings = bnd;
+ } else {
+ binding_value_dereference(&bnd->value,
+ MDL);
+ binding_value_reference(&bnd->value,
+ nv, MDL);
+ }
+
+ binding_value_dereference(&nv, MDL);
+ parse_semi(cfile);
+ break;
+
+ default:
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting ia_ta contents, "
+ "got '%s'", val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ }
+
+ if (state == FTS_LAST+1) {
+ parse_warn(cfile, "corrupt lease file; "
+ "missing state in iaaddr");
+ return;
+ }
+ if (end_time == -1) {
+ parse_warn(cfile, "corrupt lease file; "
+ "missing end time in iaaddr");
+ return;
+ }
+
+ iaaddr = NULL;
+ if (iasubopt_allocate(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ log_fatal("Out of memory.");
+ }
+ memcpy(&iaaddr->addr, iaddr.iabuf, sizeof(iaaddr->addr));
+ iaaddr->plen = 0;
+ iaaddr->state = state;
+ iaaddr->prefer = prefer;
+ iaaddr->valid = valid;
+ if (iaaddr->state == FTS_RELEASED)
+ iaaddr->hard_lifetime_end_time = end_time;
+
+ if (scope != NULL) {
+ binding_scope_reference(&iaaddr->scope, scope, MDL);
+ binding_scope_dereference(&scope, MDL);
+ }
+
+ /* find the pool this address is in */
+ pool = NULL;
+ if (find_ipv6_pool(&pool, D6O_IA_TA,
+ &iaaddr->addr) != ISC_R_SUCCESS) {
+ inet_ntop(AF_INET6, &iaaddr->addr,
+ addr_buf, sizeof(addr_buf));
+ parse_warn(cfile, "no pool found for address %s",
+ addr_buf);
+ return;
+ }
+
+ /* remove old information */
+ if (cleanup_lease6(ia_ta_active, pool,
+ iaaddr, ia) != ISC_R_SUCCESS) {
+ inet_ntop(AF_INET6, &iaaddr->addr,
+ addr_buf, sizeof(addr_buf));
+ parse_warn(cfile, "duplicate ta lease for address %s",
+ addr_buf);
+ }
+
+ /*
+ * if we like the lease we add it to our various structues
+ * otherwise we leave it and it will get cleaned when we
+ * do the iasubopt_dereference.
+ */
+ if ((state == FTS_ACTIVE) || (state == FTS_ABANDONED)) {
+ ia_add_iasubopt(ia, iaaddr, MDL);
+ ia_reference(&iaaddr->ia, ia, MDL);
+ add_lease6(pool, iaaddr, end_time);
+ }
+
+ ipv6_pool_dereference(&pool, MDL);
+ iasubopt_dereference(&iaaddr, MDL);
+ }
+
+ /*
+ * If we have an existing record for this IA_TA, remove it.
+ */
+ old_ia = NULL;
+ if (ia_hash_lookup(&old_ia, ia_ta_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, MDL)) {
+ ia_hash_delete(ia_ta_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, MDL);
+ ia_dereference(&old_ia, MDL);
+ }
+
+ /*
+ * If we have addresses, add this, otherwise don't bother.
+ */
+ if (ia->num_iasubopt > 0) {
+ ia_hash_add(ia_ta_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, ia, MDL);
+ }
+ ia_dereference(&ia, MDL);
+#endif /* defined(DHCPv6) */
+}
+
+void
+parse_ia_pd_declaration(struct parse *cfile) {
+#if !defined(DHCPv6)
+ parse_warn(cfile, "No DHCPv6 support.");
+ skip_to_semi(cfile);
+#else /* defined(DHCPv6) */
+ enum dhcp_token token;
+ struct ia_xx *ia;
+ const char *val;
+ struct ia_xx *old_ia;
+ unsigned int len;
+ u_int32_t iaid;
+ struct iaddr iaddr;
+ u_int8_t plen;
+ binding_state_t state;
+ u_int32_t prefer;
+ u_int32_t valid;
+ TIME end_time;
+ struct iasubopt *iapref;
+ struct ipv6_pool *pool;
+ char addr_buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+ isc_boolean_t newbinding;
+ struct binding_scope *scope=NULL;
+ struct binding *bnd;
+ struct binding_value *nv=NULL;
+
+ if (local_family != AF_INET6) {
+ parse_warn(cfile, "IA_PD is only supported in DHCPv6 mode.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting an iaid+ia_pd string");
+ skip_to_semi(cfile);
+ return;
+ }
+ if (len < 5) {
+ parse_warn(cfile, "corrupt lease file; "
+ "iaid+ia_pd string too short");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ memcpy(&iaid, val, 4);
+ ia = NULL;
+ if (ia_allocate(&ia, iaid, val+4, len-4, MDL) != ISC_R_SUCCESS) {
+ log_fatal("Out of memory.");
+ }
+ ia->ia_type = D6O_IA_PD;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "corrupt lease file; expecting left brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ for (;;) {
+ token = next_token(&val, NULL, cfile);
+ if (token == RBRACE) break;
+
+ if (token == CLTT) {
+ ia->cltt = parse_date (cfile);
+ continue;
+ }
+
+ if (token != IAPREFIX) {
+ parse_warn(cfile, "corrupt lease file; expecting "
+ "IAPREFIX or right brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ if (!parse_ip6_prefix(cfile, &iaddr, &plen)) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting IPv6 prefix");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting left brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ state = FTS_LAST+1;
+ prefer = valid = 0;
+ end_time = -1;
+ for (;;) {
+ token = next_token(&val, NULL, cfile);
+ if (token == RBRACE) break;
+
+ switch(token) {
+ /* Prefix binding state. */
+ case BINDING:
+ token = next_token(&val, NULL, cfile);
+ if (token != STATE) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting state");
+ skip_to_semi(cfile);
+ return;
+ }
+ token = next_token(&val, NULL, cfile);
+ switch (token) {
+ case TOKEN_ABANDONED:
+ state = FTS_ABANDONED;
+ break;
+ case TOKEN_FREE:
+ state = FTS_FREE;
+ break;
+ case TOKEN_ACTIVE:
+ state = FTS_ACTIVE;
+ break;
+ case TOKEN_EXPIRED:
+ state = FTS_EXPIRED;
+ break;
+ case TOKEN_RELEASED:
+ state = FTS_RELEASED;
+ break;
+ default:
+ parse_warn(cfile,
+ "corrupt lease "
+ "file; "
+ "expecting a "
+ "binding state.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting "
+ "semicolon.");
+ }
+ break;
+
+ /* Lease preferred lifetime. */
+ case PREFERRED_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "%s is not a valid "
+ "preferred time",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ prefer = atoi (val);
+
+ /*
+ * Currently we peek for the semi-colon to
+ * allow processing of older lease files that
+ * don't have the semi-colon. Eventually we
+ * should remove the peeking code.
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI) {
+ skip_token(&val, NULL, cfile);
+ } else {
+ parse_warn(cfile,
+ "corrupt lease file; "
+ "expecting semicolon.");
+ }
+ break;
+
+ /* Lease valid lifetime. */
+ case MAX_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "%s is not a valid "
+ "max time",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ valid = atoi (val);
+
+ /*
+ * Currently we peek for the semi-colon to
+ * allow processing of older lease files that
+ * don't have the semi-colon. Eventually we
+ * should remove the peeking code.
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI) {
+ skip_token(&val, NULL, cfile);
+ } else {
+ parse_warn(cfile,
+ "corrupt lease file; "
+ "expecting semicolon.");
+ }
+ break;
+
+ /* Prefix expiration time. */
+ case ENDS:
+ end_time = parse_date(cfile);
+ break;
+
+ /* Prefix binding scopes. */
+ case TOKEN_SET:
+ token = next_token(&val, NULL, cfile);
+ if ((token != NAME) &&
+ (token != NUMBER_OR_NAME)) {
+ parse_warn(cfile, "%s is not a valid "
+ "variable name",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+
+ if (scope != NULL)
+ bnd = find_binding(scope, val);
+ else {
+ if (!binding_scope_allocate(&scope,
+ MDL)) {
+ log_fatal("Out of memory for "
+ "lease binding "
+ "scope.");
+ }
+
+ bnd = NULL;
+ }
+
+ if (bnd == NULL) {
+ bnd = dmalloc(sizeof(*bnd),
+ MDL);
+ if (bnd == NULL) {
+ log_fatal("No memory for "
+ "prefix binding.");
+ }
+
+ bnd->name = dmalloc(strlen(val) + 1,
+ MDL);
+ if (bnd->name == NULL) {
+ log_fatal("No memory for "
+ "binding name.");
+ }
+ strcpy(bnd->name, val);
+
+ newbinding = ISC_TRUE;
+ } else {
+ newbinding = ISC_FALSE;
+ }
+
+ if (!binding_value_allocate(&nv, MDL)) {
+ log_fatal("no memory for binding "
+ "value.");
+ }
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != EQUAL) {
+ parse_warn(cfile, "expecting '=' in "
+ "set statement.");
+ goto binding_err;
+ }
+
+ if (!parse_binding_value(cfile, nv)) {
+ binding_err:
+ binding_value_dereference(&nv, MDL);
+ binding_scope_dereference(&scope, MDL);
+ return;
+ }
+
+ if (newbinding) {
+ binding_value_reference(&bnd->value,
+ nv, MDL);
+ bnd->next = scope->bindings;
+ scope->bindings = bnd;
+ } else {
+ binding_value_dereference(&bnd->value,
+ MDL);
+ binding_value_reference(&bnd->value,
+ nv, MDL);
+ }
+
+ binding_value_dereference(&nv, MDL);
+ parse_semi(cfile);
+ break;
+
+ default:
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting ia_pd contents, "
+ "got '%s'", val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ }
+
+ if (state == FTS_LAST+1) {
+ parse_warn(cfile, "corrupt lease file; "
+ "missing state in iaprefix");
+ return;
+ }
+ if (end_time == -1) {
+ parse_warn(cfile, "corrupt lease file; "
+ "missing end time in iaprefix");
+ return;
+ }
+
+ iapref = NULL;
+ if (iasubopt_allocate(&iapref, MDL) != ISC_R_SUCCESS) {
+ log_fatal("Out of memory.");
+ }
+ memcpy(&iapref->addr, iaddr.iabuf, sizeof(iapref->addr));
+ iapref->plen = plen;
+ iapref->state = state;
+ iapref->prefer = prefer;
+ iapref->valid = valid;
+ if (iapref->state == FTS_RELEASED)
+ iapref->hard_lifetime_end_time = end_time;
+
+ if (scope != NULL) {
+ binding_scope_reference(&iapref->scope, scope, MDL);
+ binding_scope_dereference(&scope, MDL);
+ }
+
+ /* find the pool this address is in */
+ pool = NULL;
+ if (find_ipv6_pool(&pool, D6O_IA_PD,
+ &iapref->addr) != ISC_R_SUCCESS) {
+ inet_ntop(AF_INET6, &iapref->addr,
+ addr_buf, sizeof(addr_buf));
+ parse_warn(cfile, "no pool found for address %s",
+ addr_buf);
+ return;
+ }
+
+ /* remove old information */
+ if (cleanup_lease6(ia_pd_active, pool,
+ iapref, ia) != ISC_R_SUCCESS) {
+ inet_ntop(AF_INET6, &iapref->addr,
+ addr_buf, sizeof(addr_buf));
+ parse_warn(cfile, "duplicate pd lease for address %s",
+ addr_buf);
+ }
+
+ /*
+ * if we like the lease we add it to our various structues
+ * otherwise we leave it and it will get cleaned when we
+ * do the iasubopt_dereference.
+ */
+ if ((state == FTS_ACTIVE) || (state == FTS_ABANDONED)) {
+ ia_add_iasubopt(ia, iapref, MDL);
+ ia_reference(&iapref->ia, ia, MDL);
+ add_lease6(pool, iapref, end_time);
+ }
+
+ ipv6_pool_dereference(&pool, MDL);
+ iasubopt_dereference(&iapref, MDL);
+ }
+
+ /*
+ * If we have an existing record for this IA_PD, remove it.
+ */
+ old_ia = NULL;
+ if (ia_hash_lookup(&old_ia, ia_pd_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, MDL)) {
+ ia_hash_delete(ia_pd_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, MDL);
+ ia_dereference(&old_ia, MDL);
+ }
+
+ /*
+ * If we have prefixes, add this, otherwise don't bother.
+ */
+ if (ia->num_iasubopt > 0) {
+ ia_hash_add(ia_pd_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, ia, MDL);
+ }
+ ia_dereference(&ia, MDL);
+#endif /* defined(DHCPv6) */
+}
+
+#ifdef DHCPv6
+/*
+ * When we parse a server-duid statement in a lease file, we are
+ * looking at the saved server DUID from a previous run. In this case
+ * we expect it to be followed by the binary representation of the
+ * DUID stored in a string:
+ *
+ * server-duid "\000\001\000\001\015\221\034JRT\000\0224Y";
+ */
+void
+parse_server_duid(struct parse *cfile) {
+ enum dhcp_token token;
+ const char *val;
+ unsigned int len;
+ struct data_string duid;
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ parse_warn(cfile, "corrupt lease file; expecting a DUID");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ memset(&duid, 0, sizeof(duid));
+ duid.len = len;
+ if (!buffer_allocate(&duid.buffer, duid.len, MDL)) {
+ log_fatal("Out of memory storing DUID");
+ }
+ duid.data = (unsigned char *)duid.buffer->data;
+ memcpy(duid.buffer->data, val, len);
+
+ set_server_duid(&duid);
+
+ data_string_forget(&duid, MDL);
+
+ token = next_token(&val, &len, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "corrupt lease file; expecting a semicolon");
+ skip_to_semi(cfile);
+ return;
+ }
+}
+
+/*
+ * When we parse a server-duid statement in a config file, we will
+ * have the type of the server DUID to generate, and possibly the
+ * actual value defined.
+ *
+ * server-duid llt;
+ * server-duid llt ethernet|ieee802|fddi 213982198 00:16:6F:49:7D:9B;
+ * server-duid ll;
+ * server-duid ll ethernet|ieee802|fddi 00:16:6F:49:7D:9B;
+ * server-duid en 2495 "enterprise-specific-identifier-1234";
+ */
+void
+parse_server_duid_conf(struct parse *cfile) {
+ enum dhcp_token token;
+ const char *val;
+ unsigned int len;
+ u_int32_t enterprise_number;
+ int ll_type;
+ struct data_string ll_addr;
+ u_int32_t llt_time;
+ struct data_string duid;
+ int duid_type_num;
+
+ /*
+ * Consume the SERVER_DUID token.
+ */
+ skip_token(NULL, NULL, cfile);
+
+ /*
+ * Obtain the DUID type.
+ */
+ token = next_token(&val, NULL, cfile);
+
+ /*
+ * Enterprise is the easiest - enterprise number and raw data
+ * are required.
+ */
+ if (token == EN) {
+ /*
+ * Get enterprise number and identifier.
+ */
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "enterprise number expected");
+ skip_to_semi(cfile);
+ return;
+ }
+ enterprise_number = atoi(val);
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ parse_warn(cfile, "identifier expected");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * Save the DUID.
+ */
+ memset(&duid, 0, sizeof(duid));
+ duid.len = 2 + 4 + len;
+ if (!buffer_allocate(&duid.buffer, duid.len, MDL)) {
+ log_fatal("Out of memory storing DUID");
+ }
+ duid.data = (unsigned char *)duid.buffer->data;
+ putUShort(duid.buffer->data, DUID_EN);
+ putULong(duid.buffer->data + 2, enterprise_number);
+ memcpy(duid.buffer->data + 6, val, len);
+
+ set_server_duid(&duid);
+ data_string_forget(&duid, MDL);
+ }
+
+ /*
+ * Next easiest is the link-layer DUID. It consists only of
+ * the LL directive, or optionally the specific value to use.
+ *
+ * If we have LL only, then we set the type. If we have the
+ * value, then we set the actual DUID.
+ */
+ else if (token == LL) {
+ if (peek_token(NULL, NULL, cfile) == SEMI) {
+ set_server_duid_type(DUID_LL);
+ } else {
+ /*
+ * Get our hardware type and address.
+ */
+ token = next_token(NULL, NULL, cfile);
+ switch (token) {
+ case ETHERNET:
+ ll_type = HTYPE_ETHER;
+ break;
+ case TOKEN_RING:
+ ll_type = HTYPE_IEEE802;
+ break;
+ case TOKEN_FDDI:
+ ll_type = HTYPE_FDDI;
+ break;
+ default:
+ parse_warn(cfile, "hardware type expected");
+ skip_to_semi(cfile);
+ return;
+ }
+ memset(&ll_addr, 0, sizeof(ll_addr));
+ if (!parse_cshl(&ll_addr, cfile)) {
+ return;
+ }
+
+ /*
+ * Save the DUID.
+ */
+ memset(&duid, 0, sizeof(duid));
+ duid.len = 2 + 2 + ll_addr.len;
+ if (!buffer_allocate(&duid.buffer, duid.len, MDL)) {
+ log_fatal("Out of memory storing DUID");
+ }
+ duid.data = (unsigned char *)duid.buffer->data;
+ putUShort(duid.buffer->data, DUID_LL);
+ putUShort(duid.buffer->data + 2, ll_type);
+ memcpy(duid.buffer->data + 4,
+ ll_addr.data, ll_addr.len);
+
+ set_server_duid(&duid);
+ data_string_forget(&duid, MDL);
+ data_string_forget(&ll_addr, MDL);
+ }
+ }
+
+ /*
+ * Finally the link-layer DUID plus time. It consists only of
+ * the LLT directive, or optionally the specific value to use.
+ *
+ * If we have LLT only, then we set the type. If we have the
+ * value, then we set the actual DUID.
+ */
+ else if (token == LLT) {
+ if (peek_token(NULL, NULL, cfile) == SEMI) {
+ set_server_duid_type(DUID_LLT);
+ } else {
+ /*
+ * Get our hardware type, timestamp, and address.
+ */
+ token = next_token(NULL, NULL, cfile);
+ switch (token) {
+ case ETHERNET:
+ ll_type = HTYPE_ETHER;
+ break;
+ case TOKEN_RING:
+ ll_type = HTYPE_IEEE802;
+ break;
+ case TOKEN_FDDI:
+ ll_type = HTYPE_FDDI;
+ break;
+ default:
+ parse_warn(cfile, "hardware type expected");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "timestamp expected");
+ skip_to_semi(cfile);
+ return;
+ }
+ llt_time = atoi(val);
+
+ memset(&ll_addr, 0, sizeof(ll_addr));
+ if (!parse_cshl(&ll_addr, cfile)) {
+ return;
+ }
+
+ /*
+ * Save the DUID.
+ */
+ memset(&duid, 0, sizeof(duid));
+ duid.len = 2 + 2 + 4 + ll_addr.len;
+ if (!buffer_allocate(&duid.buffer, duid.len, MDL)) {
+ log_fatal("Out of memory storing DUID");
+ }
+ duid.data = (unsigned char *)duid.buffer->data;
+ putUShort(duid.buffer->data, DUID_LLT);
+ putUShort(duid.buffer->data + 2, ll_type);
+ putULong(duid.buffer->data + 4, llt_time);
+ memcpy(duid.buffer->data + 8,
+ ll_addr.data, ll_addr.len);
+
+ set_server_duid(&duid);
+ data_string_forget(&duid, MDL);
+ data_string_forget(&ll_addr, MDL);
+ }
+ }
+
+ /*
+ * If users want they can use a number for DUID types.
+ * This is useful for supporting future, not-yet-defined
+ * DUID types.
+ *
+ * In this case, they have to put in the complete value.
+ *
+ * This also works for existing DUID types of course.
+ */
+ else if (token == NUMBER) {
+ duid_type_num = atoi(val);
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ parse_warn(cfile, "identifier expected");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * Save the DUID.
+ */
+ memset(&duid, 0, sizeof(duid));
+ duid.len = 2 + len;
+ if (!buffer_allocate(&duid.buffer, duid.len, MDL)) {
+ log_fatal("Out of memory storing DUID");
+ }
+ duid.data = (unsigned char *)duid.buffer->data;
+ putUShort(duid.buffer->data, duid_type_num);
+ memcpy(duid.buffer->data + 2, val, len);
+
+ set_server_duid(&duid);
+ data_string_forget(&duid, MDL);
+ }
+
+ /*
+ * Anything else is an error.
+ */
+ else {
+ parse_warn(cfile, "DUID type of LLT, EN, or LL expected");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * Finally consume our trailing semicolon.
+ */
+ token = next_token(NULL, NULL, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "semicolon expected");
+ skip_to_semi(cfile);
+ }
+}
+
+#endif /* DHCPv6 */
+
diff --git a/server/db.c b/server/db.c
new file mode 100644
index 0000000..aa42af7
--- /dev/null
+++ b/server/db.c
@@ -0,0 +1,1195 @@
+/* db.c
+
+ Persistent database management routines for DHCPD... */
+
+/*
+ * Copyright (c) 2012-2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2010 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>
+#include <errno.h>
+
+#define LEASE_REWRITE_PERIOD 3600
+
+static isc_result_t write_binding_scope(FILE *db_file, struct binding *bnd,
+ char *prepend);
+
+FILE *db_file;
+
+static int counting = 0;
+static int count = 0;
+TIME write_time;
+int lease_file_is_corrupt = 0;
+
+/* Write a single binding scope value in parsable format.
+ */
+
+static isc_result_t
+write_binding_scope(FILE *db_file, struct binding *bnd, char *prepend) {
+ char *s;
+
+ if ((db_file == NULL) || (bnd == NULL) || (prepend == NULL))
+ return DHCP_R_INVALIDARG;
+
+ if (bnd->value->type == binding_data) {
+ if (bnd->value->value.data.data != NULL) {
+ s = quotify_buf(bnd->value->value.data.data,
+ bnd->value->value.data.len, MDL);
+ if (s != NULL) {
+ errno = 0;
+ fprintf(db_file, "%sset %s = \"%s\";",
+ prepend, bnd->name, s);
+ dfree(s, MDL);
+ if (errno)
+ return ISC_R_FAILURE;
+ } else {
+ return ISC_R_FAILURE;
+ }
+ }
+ } else if (bnd->value->type == binding_numeric) {
+ errno = 0;
+ fprintf(db_file, "%sset %s = %%%ld;", prepend,
+ bnd->name, bnd->value->value.intval);
+ if (errno)
+ return ISC_R_FAILURE;
+ } else if (bnd->value->type == binding_boolean) {
+ errno = 0;
+ fprintf(db_file, "%sset %s = %s;", prepend, bnd->name,
+ bnd->value->value.intval ? "true" : "false");
+ if (errno)
+ return ISC_R_FAILURE;
+ } else if (bnd->value->type == binding_dns) {
+ log_error("%s: persistent dns values not supported.",
+ bnd->name);
+ } else if (bnd->value->type == binding_function) {
+ log_error("%s: persistent functions not supported.",
+ bnd->name);
+ } else {
+ log_fatal("%s: unknown binding type %d", bnd->name,
+ bnd->value->type);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+/* Write the specified lease to the current lease database file. */
+
+int write_lease (lease)
+ struct lease *lease;
+{
+ int errors = 0;
+ struct binding *b;
+ char *s;
+ const char *tval;
+
+ /* If the lease file is corrupt, don't try to write any more leases
+ until we've written a good lease file. */
+ if (lease_file_is_corrupt)
+ if (!new_lease_file ())
+ return 0;
+
+ if (counting)
+ ++count;
+ errno = 0;
+ fprintf (db_file, "lease %s {", piaddr (lease -> ip_addr));
+ if (errno) {
+ ++errors;
+ }
+
+ if (lease->starts &&
+ ((tval = print_time(lease->starts)) == NULL ||
+ fprintf(db_file, "\n starts %s", tval) < 0))
+ ++errors;
+
+ if (lease->ends &&
+ ((tval = print_time(lease->ends)) == NULL ||
+ fprintf(db_file, "\n ends %s", tval) < 0))
+ ++errors;
+
+ if (lease->tstp &&
+ ((tval = print_time(lease->tstp)) == NULL ||
+ fprintf(db_file, "\n tstp %s", tval) < 0))
+ ++errors;
+
+ if (lease->tsfp &&
+ ((tval = print_time(lease->tsfp)) == NULL ||
+ fprintf(db_file, "\n tsfp %s", tval) < 0))
+ ++errors;
+
+ if (lease->atsfp &&
+ ((tval = print_time(lease->atsfp)) == NULL ||
+ fprintf(db_file, "\n atsfp %s", tval) < 0))
+ ++errors;
+
+ if (lease->cltt &&
+ ((tval = print_time(lease->cltt)) == NULL ||
+ fprintf(db_file, "\n cltt %s", tval) < 0))
+ ++errors;
+
+ if (fprintf (db_file, "\n binding state %s;",
+ ((lease -> binding_state > 0 &&
+ lease -> binding_state <= FTS_LAST)
+ ? binding_state_names [lease -> binding_state - 1]
+ : "abandoned")) < 0)
+ ++errors;
+
+ if (lease -> binding_state != lease -> next_binding_state)
+ if (fprintf (db_file, "\n next binding state %s;",
+ ((lease -> next_binding_state > 0 &&
+ lease -> next_binding_state <= FTS_LAST)
+ ? (binding_state_names
+ [lease -> next_binding_state - 1])
+ : "abandoned")) < 0)
+ ++errors;
+
+ /*
+ * In this case, if the rewind state is not present in the lease file,
+ * the reader will use the current binding state as the most
+ * conservative (safest) state. So if the in-memory rewind state is
+ * for some reason invalid, the best thing to do is not to write a
+ * state and let the reader take on a safe state.
+ */
+ if ((lease->binding_state != lease->rewind_binding_state) &&
+ (lease->rewind_binding_state > 0) &&
+ (lease->rewind_binding_state <= FTS_LAST) &&
+ (fprintf(db_file, "\n rewind binding state %s;",
+ binding_state_names[lease->rewind_binding_state-1])) < 0)
+ ++errors;
+
+ if (lease->flags & RESERVED_LEASE)
+ if (fprintf(db_file, "\n reserved;") < 0)
+ ++errors;
+
+ if (lease->flags & BOOTP_LEASE)
+ if (fprintf(db_file, "\n dynamic-bootp;") < 0)
+ ++errors;
+
+ /* If this lease is billed to a class and is still valid,
+ write it out. */
+ if (lease -> billing_class && lease -> ends > cur_time) {
+ if (!write_billing_class (lease -> billing_class)) {
+ log_error ("unable to write class %s",
+ lease -> billing_class -> name);
+ ++errors;
+ }
+ }
+
+ if (lease -> hardware_addr.hlen) {
+ errno = 0;
+ fprintf (db_file, "\n hardware %s %s;",
+ hardware_types [lease -> hardware_addr.hbuf [0]],
+ print_hw_addr (lease -> hardware_addr.hbuf [0],
+ lease -> hardware_addr.hlen - 1,
+ &lease -> hardware_addr.hbuf [1]));
+ if (errno)
+ ++errors;
+ }
+ if (lease -> uid_len) {
+ s = quotify_buf (lease -> uid, lease -> uid_len, MDL);
+ if (s) {
+ errno = 0;
+ fprintf (db_file, "\n uid \"%s\";", s);
+ if (errno)
+ ++errors;
+ dfree (s, MDL);
+ } else
+ ++errors;
+ }
+
+ if (lease->scope != NULL) {
+ for (b = lease->scope->bindings; b; b = b->next) {
+ if (!b->value)
+ continue;
+
+ if (write_binding_scope(db_file, b, "\n ") != ISC_R_SUCCESS)
+ ++errors;
+ }
+ }
+
+ if (lease -> agent_options) {
+ struct option_cache *oc;
+ struct data_string ds;
+ pair p;
+
+ memset (&ds, 0, sizeof ds);
+ for (p = lease -> agent_options -> first; p; p = p -> cdr) {
+ oc = (struct option_cache *)p -> car;
+ if (oc -> data.len) {
+ errno = 0;
+ fprintf (db_file, "\n option agent.%s %s;",
+ oc -> option -> name,
+ pretty_print_option (oc -> option, oc -> data.data,
+ oc -> data.len, 1, 1));
+ if (errno)
+ ++errors;
+ }
+ }
+ }
+ if (lease -> client_hostname &&
+ db_printable((unsigned char *)lease->client_hostname)) {
+ s = quotify_string (lease -> client_hostname, MDL);
+ if (s) {
+ errno = 0;
+ fprintf (db_file, "\n client-hostname \"%s\";", s);
+ if (errno)
+ ++errors;
+ dfree (s, MDL);
+ } else
+ ++errors;
+ }
+ if (lease -> on_expiry) {
+ errno = 0;
+ fprintf (db_file, "\n on expiry%s {",
+ lease -> on_expiry == lease -> on_release
+ ? " or release" : "");
+ write_statements (db_file, lease -> on_expiry, 4);
+ /* XXX */
+ fprintf (db_file, "\n }");
+ if (errno)
+ ++errors;
+ }
+ if (lease -> on_release && lease -> on_release != lease -> on_expiry) {
+ errno = 0;
+ fprintf (db_file, "\n on release {");
+ write_statements (db_file, lease -> on_release, 4);
+ /* XXX */
+ fprintf (db_file, "\n }");
+ if (errno)
+ ++errors;
+ }
+
+ errno = 0;
+ fputs ("\n}\n", db_file);
+ if (errno)
+ ++errors;
+
+ if (errors) {
+ log_info ("write_lease: unable to write lease %s",
+ piaddr (lease -> ip_addr));
+ lease_file_is_corrupt = 1;
+ }
+
+ return !errors;
+}
+
+int write_host (host)
+ struct host_decl *host;
+{
+ int errors = 0;
+ int i;
+ struct data_string ip_addrs;
+
+ /* If the lease file is corrupt, don't try to write any more leases
+ until we've written a good lease file. */
+ if (lease_file_is_corrupt)
+ if (!new_lease_file ())
+ return 0;
+
+ if (!db_printable((unsigned char *)host->name))
+ return 0;
+
+ if (counting)
+ ++count;
+
+ errno = 0;
+ fprintf (db_file, "host %s {", host -> name);
+ if (errno)
+ ++errors;
+
+ if (host -> flags & HOST_DECL_DYNAMIC) {
+ errno = 0;
+ fprintf (db_file, "\n dynamic;");
+ if (errno)
+ ++errors;
+ }
+
+ if (host -> flags & HOST_DECL_DELETED) {
+ errno = 0;
+ fprintf (db_file, "\n deleted;");
+ if (errno)
+ ++errors;
+ } else {
+ if (host -> interface.hlen) {
+ errno = 0;
+ fprintf (db_file, "\n hardware %s %s;",
+ hardware_types [host -> interface.hbuf [0]],
+ print_hw_addr (host -> interface.hbuf [0],
+ host -> interface.hlen - 1,
+ &host -> interface.hbuf [1]));
+ if (errno)
+ ++errors;
+ }
+ if (host -> client_identifier.len) {
+ int i;
+ errno = 0;
+ if (db_printable_len (host -> client_identifier.data,
+ host -> client_identifier.len)) {
+ fprintf (db_file, "\n uid \"%.*s\";",
+ (int)host -> client_identifier.len,
+ host -> client_identifier.data);
+ if (errno)
+ ++errors;
+ } else {
+ fprintf (db_file,
+ "\n uid %2.2x",
+ host -> client_identifier.data [0]);
+ if (errno)
+ ++errors;
+ for (i = 1;
+ i < host -> client_identifier.len; i++) {
+ errno = 0;
+ fprintf (db_file, ":%2.2x",
+ host ->
+ client_identifier.data [i]);
+ if (errno)
+ ++errors;
+ }
+
+ errno = 0;
+ fputc (';', db_file);
+ if (errno)
+ ++errors;
+ }
+ }
+
+ memset (&ip_addrs, 0, sizeof ip_addrs);
+ if (host -> fixed_addr &&
+ evaluate_option_cache (&ip_addrs, (struct packet *)0,
+ (struct lease *)0,
+ (struct client_state *)0,
+ (struct option_state *)0,
+ (struct option_state *)0,
+ &global_scope,
+ host -> fixed_addr, MDL)) {
+
+ errno = 0;
+ fprintf (db_file, "\n fixed-address ");
+ if (errno)
+ ++errors;
+ for (i = 0; i < ip_addrs.len - 3; i += 4) {
+
+ errno = 0;
+ fprintf (db_file, "%u.%u.%u.%u%s",
+ ip_addrs.data [i] & 0xff,
+ ip_addrs.data [i + 1] & 0xff,
+ ip_addrs.data [i + 2] & 0xff,
+ ip_addrs.data [i + 3] & 0xff,
+ i + 7 < ip_addrs.len ? "," : "");
+ if (errno)
+ ++errors;
+ }
+
+ errno = 0;
+ fputc (';', db_file);
+ if (errno)
+ ++errors;
+ }
+
+ if (host -> named_group) {
+ errno = 0;
+ fprintf (db_file, "\n group \"%s\";",
+ host -> named_group -> name);
+ if (errno)
+ ++errors;
+ }
+
+ if (host -> group &&
+ (!host -> named_group ||
+ host -> group != host -> named_group -> group) &&
+ host -> group != root_group) {
+ errno = 0;
+ write_statements (db_file,
+ host -> group -> statements, 8);
+ if (errno)
+ ++errors;
+ }
+ }
+
+ errno = 0;
+ fputs ("\n}\n", db_file);
+ if (errno)
+ ++errors;
+
+ if (errors) {
+ log_info ("write_host: unable to write host %s",
+ host -> name);
+ lease_file_is_corrupt = 1;
+ }
+
+ return !errors;
+}
+
+int write_group (group)
+ struct group_object *group;
+{
+ int errors = 0;
+
+ /* If the lease file is corrupt, don't try to write any more leases
+ until we've written a good lease file. */
+ if (lease_file_is_corrupt)
+ if (!new_lease_file ())
+ return 0;
+
+ if (!db_printable((unsigned char *)group->name))
+ return 0;
+
+ if (counting)
+ ++count;
+
+ errno = 0;
+ fprintf (db_file, "group %s {", group -> name);
+ if (errno)
+ ++errors;
+
+ if (group -> flags & GROUP_OBJECT_DYNAMIC) {
+ errno = 0;
+ fprintf (db_file, "\n dynamic;");
+ if (errno)
+ ++errors;
+ }
+
+ if (group -> flags & GROUP_OBJECT_STATIC) {
+ errno = 0;
+ fprintf (db_file, "\n static;");
+ if (errno)
+ ++errors;
+ }
+
+ if (group -> flags & GROUP_OBJECT_DELETED) {
+ errno = 0;
+ fprintf (db_file, "\n deleted;");
+ if (errno)
+ ++errors;
+ } else {
+ if (group -> group) {
+ errno = 0;
+ write_statements (db_file,
+ group -> group -> statements, 8);
+ if (errno)
+ ++errors;
+ }
+ }
+
+ errno = 0;
+ fputs ("\n}\n", db_file);
+ if (errno)
+ ++errors;
+
+ if (errors) {
+ log_info ("write_group: unable to write group %s",
+ group -> name);
+ lease_file_is_corrupt = 1;
+ }
+
+ return !errors;
+}
+
+/*
+ * Write an IA and the options it has.
+ */
+int
+write_ia(const struct ia_xx *ia) {
+ struct iasubopt *iasubopt;
+ struct binding *bnd;
+ int i;
+ char addr_buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff.255.255.255.255")];
+ const char *binding_state;
+ const char *tval;
+ char *s;
+ int fprintf_ret;
+
+ /*
+ * If the lease file is corrupt, don't try to write any more
+ * leases until we've written a good lease file.
+ */
+ if (lease_file_is_corrupt) {
+ if (!new_lease_file()) {
+ return 0;
+ }
+ }
+
+ if (counting) {
+ ++count;
+ }
+
+
+ s = quotify_buf(ia->iaid_duid.data, ia->iaid_duid.len, MDL);
+ if (s == NULL) {
+ goto error_exit;
+ }
+ switch (ia->ia_type) {
+ case D6O_IA_NA:
+ fprintf_ret = fprintf(db_file, "ia-na \"%s\" {\n", s);
+ break;
+ case D6O_IA_TA:
+ fprintf_ret = fprintf(db_file, "ia-ta \"%s\" {\n", s);
+ break;
+ case D6O_IA_PD:
+ fprintf_ret = fprintf(db_file, "ia-pd \"%s\" {\n", s);
+ break;
+ default:
+ log_error("Unknown ia type %u for \"%s\" at %s:%d",
+ (unsigned)ia->ia_type, s, MDL);
+ fprintf_ret = -1;
+ }
+ dfree(s, MDL);
+ if (fprintf_ret < 0) {
+ goto error_exit;
+ }
+ if (ia->cltt != MIN_TIME) {
+ tval = print_time(ia->cltt);
+ if (tval == NULL) {
+ goto error_exit;
+ }
+ if (fprintf(db_file, " cltt %s\n", tval) < 0) {
+ goto error_exit;
+ }
+ }
+ for (i=0; i<ia->num_iasubopt; i++) {
+ iasubopt = ia->iasubopt[i];
+
+ inet_ntop(AF_INET6, &iasubopt->addr,
+ addr_buf, sizeof(addr_buf));
+ if ((ia->ia_type != D6O_IA_PD) &&
+ (fprintf(db_file, " iaaddr %s {\n", addr_buf) < 0)) {
+ goto error_exit;
+ }
+ if ((ia->ia_type == D6O_IA_PD) &&
+ (fprintf(db_file, " iaprefix %s/%d {\n",
+ addr_buf, (int)iasubopt->plen) < 0)) {
+ goto error_exit;
+ }
+ if ((iasubopt->state <= 0) || (iasubopt->state > FTS_LAST)) {
+ log_fatal("Unknown iasubopt state %d at %s:%d",
+ iasubopt->state, MDL);
+ }
+ binding_state = binding_state_names[iasubopt->state-1];
+ if (fprintf(db_file, " binding state %s;\n",
+ binding_state) < 0) {
+ goto error_exit;
+ }
+ if (fprintf(db_file, " preferred-life %u;\n",
+ (unsigned)iasubopt->prefer) < 0) {
+ goto error_exit;
+ }
+ if (fprintf(db_file, " max-life %u;\n",
+ (unsigned)iasubopt->valid) < 0) {
+ goto error_exit;
+ }
+
+ /* Note that from here on out, the \n is prepended to the
+ * next write, rather than appended to the current write.
+ */
+ if ((iasubopt->state == FTS_ACTIVE) ||
+ (iasubopt->state == FTS_ABANDONED) ||
+ (iasubopt->hard_lifetime_end_time != 0)) {
+ tval = print_time(iasubopt->hard_lifetime_end_time);
+ } else {
+ tval = print_time(iasubopt->soft_lifetime_end_time);
+ }
+ if (tval == NULL) {
+ goto error_exit;
+ }
+ if (fprintf(db_file, " ends %s", tval) < 0) {
+ goto error_exit;
+ }
+
+ /* Write out any binding scopes: note that 'ends' above does
+ * not have \n on the end! We want that.
+ */
+ if (iasubopt->scope != NULL)
+ bnd = iasubopt->scope->bindings;
+ else
+ bnd = NULL;
+
+ for (; bnd != NULL ; bnd = bnd->next) {
+ if (bnd->value == NULL)
+ continue;
+
+ /* We don't do a regular error_exit because the
+ * lease db is not corrupt in this case.
+ */
+ if (write_binding_scope(db_file, bnd,
+ "\n ") != ISC_R_SUCCESS)
+ goto error_exit;
+
+ }
+
+ if (fprintf(db_file, "\n }\n") < 0)
+ goto error_exit;
+ }
+ if (fprintf(db_file, "}\n\n") < 0)
+ goto error_exit;
+
+ fflush(db_file);
+ return 1;
+
+error_exit:
+ log_info("write_ia: unable to write ia");
+ lease_file_is_corrupt = 1;
+ return 0;
+}
+
+#ifdef DHCPv6
+/*
+ * Put a copy of the server DUID in the leases file.
+ */
+int
+write_server_duid(void) {
+ struct data_string server_duid;
+ char *s;
+ int fprintf_ret;
+
+ /*
+ * Only write the DUID if it's been set.
+ */
+ if (!server_duid_isset()) {
+ return 1;
+ }
+
+ /*
+ * If the lease file is corrupt, don't try to write any more
+ * leases until we've written a good lease file.
+ */
+ if (lease_file_is_corrupt) {
+ if (!new_lease_file()) {
+ return 0;
+ }
+ }
+
+ /*
+ * Get a copy of our server DUID and convert to a quoted string.
+ */
+ memset(&server_duid, 0, sizeof(server_duid));
+ copy_server_duid(&server_duid, MDL);
+ s = quotify_buf(server_duid.data, server_duid.len, MDL);
+ data_string_forget(&server_duid, MDL);
+ if (s == NULL) {
+ goto error_exit;
+ }
+
+ /*
+ * Write to the leases file.
+ */
+ fprintf_ret = fprintf(db_file, "server-duid \"%s\";\n\n", s);
+ dfree(s, MDL);
+ if (fprintf_ret < 0) {
+ goto error_exit;
+ }
+
+ /*
+ * Check if we actually managed to write.
+ */
+ fflush(db_file);
+ return 1;
+
+error_exit:
+ log_info("write_server_duid: unable to write server-duid");
+ lease_file_is_corrupt = 1;
+ return 0;
+}
+#endif /* DHCPv6 */
+
+#if defined (FAILOVER_PROTOCOL)
+int write_failover_state (dhcp_failover_state_t *state)
+{
+ int errors = 0;
+ const char *tval;
+
+ if (lease_file_is_corrupt)
+ if (!new_lease_file ())
+ return 0;
+
+ errno = 0;
+ fprintf (db_file, "\nfailover peer \"%s\" state {", state -> name);
+ if (errno)
+ ++errors;
+
+ tval = print_time(state->me.stos);
+ if (tval == NULL ||
+ fprintf(db_file, "\n my state %s at %s",
+ (state->me.state == startup) ?
+ dhcp_failover_state_name_print(state->saved_state) :
+ dhcp_failover_state_name_print(state->me.state),
+ tval) < 0)
+ ++errors;
+
+ tval = print_time(state->partner.stos);
+ if (tval == NULL ||
+ fprintf(db_file, "\n partner state %s at %s",
+ dhcp_failover_state_name_print(state->partner.state),
+ tval) < 0)
+ ++errors;
+
+ if (state -> i_am == secondary) {
+ errno = 0;
+ fprintf (db_file, "\n mclt %ld;",
+ (unsigned long)state -> mclt);
+ if (errno)
+ ++errors;
+ }
+
+ errno = 0;
+ fprintf (db_file, "\n}\n");
+ if (errno)
+ ++errors;
+
+ if (errors) {
+ log_info ("write_failover_state: unable to write state %s",
+ state -> name);
+ lease_file_is_corrupt = 1;
+ return 0;
+ }
+
+ return 1;
+
+}
+#endif
+
+int db_printable (s)
+ const unsigned char *s;
+{
+ int i;
+ for (i = 0; s [i]; i++)
+ if (!isascii (s [i]) || !isprint (s [i])
+ || s [i] == '"' || s [i] == '\\')
+ return 0;
+ return 1;
+}
+
+int db_printable_len (s, len)
+ const unsigned char *s;
+ unsigned len;
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ if (!isascii (s [i]) || !isprint (s [i]) ||
+ s [i] == '"' || s [i] == '\\')
+ return 0;
+ return 1;
+}
+
+static int print_hash_string(FILE *fp, struct class *class)
+{
+ int i;
+
+ for (i = 0 ; i < class->hash_string.len ; i++)
+ if (!isascii(class->hash_string.data[i]) ||
+ !isprint(class->hash_string.data[i]))
+ break;
+
+ if (i == class->hash_string.len) {
+ if (fprintf(fp, " \"%.*s\"", (int)class->hash_string.len,
+ class->hash_string.data) <= 0) {
+ log_error("Failure writing hash string: %m");
+ return 0;
+ }
+ } else {
+ if (fprintf(fp, " %2.2x", class->hash_string.data[0]) <= 0) {
+ log_error("Failure writing hash string: %m");
+ return 0;
+ }
+ for (i = 1 ; i < class->hash_string.len ; i++) {
+ if (fprintf(fp, ":%2.2x",
+ class->hash_string.data[i]) <= 0) {
+ log_error("Failure writing hash string: %m");
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+
+
+isc_result_t
+write_named_billing_class(const void *key, unsigned len, void *object)
+{
+ const unsigned char *name = key;
+ struct class *class = object;
+
+ if (class->flags & CLASS_DECL_DYNAMIC) {
+ numclasseswritten++;
+ if (class->superclass == 0) {
+ if (fprintf(db_file, "class \"%s\" {\n", name) <= 0)
+ return ISC_R_IOERROR;
+ } else {
+ if (fprintf(db_file, "subclass \"%s\"",
+ class->superclass->name) <= 0)
+ return ISC_R_IOERROR;
+ if (!print_hash_string(db_file, class))
+ return ISC_R_IOERROR;
+ if (fprintf(db_file, " {\n") <= 0)
+ return ISC_R_IOERROR;
+ }
+
+ if ((class->flags & CLASS_DECL_DELETED) != 0) {
+ if (fprintf(db_file, " deleted;\n") <= 0)
+ return ISC_R_IOERROR;
+ } else {
+ if (fprintf(db_file, " dynamic;\n") <= 0)
+ return ISC_R_IOERROR;
+ }
+
+ if (class->lease_limit > 0) {
+ if (fprintf(db_file, " lease limit %d;\n",
+ class->lease_limit) <= 0)
+ return ISC_R_IOERROR;
+ }
+
+ if (class->expr != 0) {
+ if (fprintf(db_file, " match if ") <= 0)
+ return ISC_R_IOERROR;
+
+ errno = 0;
+ write_expression(db_file, class->expr, 5, 5, 0);
+ if (errno)
+ return ISC_R_IOERROR;
+
+ if (fprintf(db_file, ";\n") <= 0)
+ return ISC_R_IOERROR;
+ }
+
+ if (class->submatch != 0) {
+ if (class->spawning) {
+ if (fprintf(db_file, " spawn ") <= 0)
+ return ISC_R_IOERROR;
+ } else {
+ if (fprintf(db_file, " match ") <= 0)
+ return ISC_R_IOERROR;
+ }
+
+ errno = 0;
+ write_expression(db_file, class->submatch, 5, 5, 0);
+ if (errno)
+ return ISC_R_IOERROR;
+
+ if (fprintf(db_file, ";\n") <= 0)
+ return ISC_R_IOERROR;
+ }
+
+ if (class->statements != 0) {
+ errno = 0;
+ write_statements(db_file, class->statements, 8);
+ if (errno)
+ return ISC_R_IOERROR;
+ }
+
+ /* XXXJAB this isn't right, but classes read in off the
+ leases file don't get the root group assigned to them
+ (due to clone_group() call). */
+ if (class->group != 0 && class->group->authoritative != 0) {
+ errno = 0;
+ write_statements(db_file, class->group->statements, 8);
+ if (errno)
+ return ISC_R_IOERROR;
+ }
+
+ if (fprintf(db_file, "}\n\n") <= 0)
+ return ISC_R_IOERROR;
+ }
+
+ if (class->hash != NULL) { /* yep. recursive. god help us. */
+ /* XXX - cannot check error status of this...
+ * foo_hash_foreach returns a count of operations completed.
+ */
+ class_hash_foreach(class->hash, write_named_billing_class);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+void write_billing_classes ()
+{
+ struct collection *lp;
+ struct class *cp;
+
+ for (lp = collections; lp; lp = lp -> next) {
+ for (cp = lp -> classes; cp; cp = cp -> nic) {
+ if (cp -> spawning && cp -> hash) {
+ class_hash_foreach (cp -> hash, write_named_billing_class);
+ }
+ }
+ }
+}
+
+/* Write a spawned class to the database file. */
+
+int write_billing_class (class)
+ struct class *class;
+{
+ int errors = 0;
+
+ if (lease_file_is_corrupt)
+ if (!new_lease_file ())
+ return 0;
+
+ if (!class -> superclass) {
+ errno = 0;
+ fprintf (db_file, "\n billing class \"%s\";", class -> name);
+ return !errno;
+ }
+
+ if (fprintf(db_file, "\n billing subclass \"%s\"",
+ class -> superclass -> name) < 0)
+ ++errors;
+
+ if (!print_hash_string(db_file, class))
+ ++errors;
+
+ if (fprintf(db_file, ";") < 0)
+ ++errors;
+
+ class -> dirty = !errors;
+ if (errors)
+ lease_file_is_corrupt = 1;
+
+ return !errors;
+}
+
+/* Commit leases after a timeout. */
+void commit_leases_timeout (void *foo)
+{
+ commit_leases ();
+}
+
+/* Commit any leases that have been written out... */
+
+int commit_leases ()
+{
+ /* Commit any outstanding writes to the lease database file.
+ We need to do this even if we're rewriting the file below,
+ just in case the rewrite fails. */
+ if (fflush (db_file) == EOF) {
+ log_info ("commit_leases: unable to commit: %m");
+ return 0;
+ }
+ if (fsync (fileno (db_file)) < 0) {
+ log_info ("commit_leases: unable to commit: %m");
+ return 0;
+ }
+
+ /* send out all deferred ACKs now */
+ flush_ackqueue(NULL);
+
+ /* If we haven't rewritten the lease database in over an
+ hour, rewrite it now. (The length of time should probably
+ be configurable. */
+ if (count && cur_time - write_time > LEASE_REWRITE_PERIOD) {
+ count = 0;
+ write_time = cur_time;
+ new_lease_file ();
+ }
+ return 1;
+}
+
+/*
+ * rewrite the lease file about once an hour
+ * This is meant as a quick patch for ticket 24887. It allows
+ * us to rotate the v6 lease file without adding too many fsync()
+ * calls. In the future wes should revisit this area and add
+ * something similar to the delayed ack code for v4.
+ */
+int commit_leases_timed()
+{
+ if ((count != 0) && (cur_time - write_time > LEASE_REWRITE_PERIOD)) {
+ return (commit_leases());
+ }
+ return (1);
+}
+
+void db_startup (testp)
+ int testp;
+{
+ isc_result_t status;
+
+#if defined (TRACING)
+ if (!trace_playback ()) {
+#endif
+ /* Read in the existing lease file... */
+ status = read_conf_file (path_dhcpd_db,
+ (struct group *)0, 0, 1);
+ if (status != ISC_R_SUCCESS) {
+ /* XXX ignore status? */
+ ;
+ }
+
+#if defined (TRACING)
+ }
+#endif
+
+#if defined (TRACING)
+ /* If we're playing back, there is no lease file, so we can't
+ append it, so we create one immediately (maybe this isn't
+ the best solution... */
+ if (trace_playback ()) {
+ new_lease_file ();
+ }
+#endif
+ if (!testp) {
+ db_file = fopen (path_dhcpd_db, "a");
+ if (!db_file)
+ log_fatal ("Can't open %s for append.", path_dhcpd_db);
+ expire_all_pools ();
+#if defined (TRACING)
+ if (trace_playback ())
+ write_time = cur_time;
+ else
+#endif
+ time(&write_time);
+ new_lease_file ();
+ }
+
+#if defined(REPORT_HASH_PERFORMANCE)
+ log_info("Host HW hash: %s", host_hash_report(host_hw_addr_hash));
+ log_info("Host UID hash: %s", host_hash_report(host_uid_hash));
+ log_info("Lease IP hash: %s",
+ lease_ip_hash_report(lease_ip_addr_hash));
+ log_info("Lease UID hash: %s", lease_id_hash_report(lease_uid_hash));
+ log_info("Lease HW hash: %s",
+ lease_id_hash_report(lease_hw_addr_hash));
+#endif
+}
+
+int new_lease_file ()
+{
+ char newfname [512];
+ char backfname [512];
+ TIME t;
+ int db_fd;
+ int db_validity;
+ FILE *new_db_file;
+
+ /* Make a temporary lease file... */
+ time(&t);
+
+ db_validity = lease_file_is_corrupt;
+
+ /* %Audit% Truncated filename causes panic. %2004.06.17,Safe%
+ * This should never happen since the path is a configuration
+ * variable from build-time or command-line. But if it should,
+ * either by malice or ignorance, we panic, since the potential
+ * for havoc is high.
+ */
+ if (snprintf (newfname, sizeof newfname, "%s.%d",
+ path_dhcpd_db, (int)t) >= sizeof newfname)
+ log_fatal("new_lease_file: lease file path too long");
+
+ db_fd = open (newfname, O_WRONLY | O_TRUNC | O_CREAT, 0664);
+ if (db_fd < 0) {
+ log_error ("Can't create new lease file: %m");
+ return 0;
+ }
+ if ((new_db_file = fdopen(db_fd, "w")) == NULL) {
+ log_error("Can't fdopen new lease file: %m");
+ close(db_fd);
+ goto fdfail;
+ }
+
+ /* Close previous database, if any. */
+ if (db_file)
+ fclose(db_file);
+ db_file = new_db_file;
+
+ errno = 0;
+ fprintf (db_file, "# The format of this file is documented in the %s",
+ "dhcpd.leases(5) manual page.\n");
+ if (errno)
+ goto fail;
+
+ fprintf (db_file, "# This lease file was written by isc-dhcp-%s\n\n",
+ PACKAGE_VERSION);
+ if (errno)
+ goto fail;
+
+ /* At this point we have a new lease file that, so far, could not
+ * be described as either corrupt nor valid.
+ */
+ lease_file_is_corrupt = 0;
+
+ /* Write out all the leases that we know of... */
+ counting = 0;
+ if (!write_leases ())
+ goto fail;
+
+#if defined (TRACING)
+ if (!trace_playback ()) {
+#endif
+ /* %Audit% Truncated filename causes panic. %2004.06.17,Safe%
+ * This should never happen since the path is a configuration
+ * variable from build-time or command-line. But if it should,
+ * either by malice or ignorance, we panic, since the potential
+ * for havoc is too high.
+ */
+ if (snprintf (backfname, sizeof backfname, "%s~", path_dhcpd_db)
+ >= sizeof backfname)
+ log_fatal("new_lease_file: backup lease file path too long");
+
+ /* Get the old database out of the way... */
+ if (unlink (backfname) < 0 && errno != ENOENT) {
+ log_error ("Can't remove old lease database backup %s: %m",
+ backfname);
+ goto fail;
+ }
+ if (link(path_dhcpd_db, backfname) < 0) {
+ if (errno == ENOENT) {
+ log_error("%s is missing - no lease db to backup.",
+ path_dhcpd_db);
+ } else {
+ log_error("Can't backup lease database %s to %s: %m",
+ path_dhcpd_db, backfname);
+ goto fail;
+ }
+ }
+#if defined (TRACING)
+ }
+#endif
+
+ /* Move in the new file... */
+ if (rename (newfname, path_dhcpd_db) < 0) {
+ log_error ("Can't install new lease database %s to %s: %m",
+ newfname, path_dhcpd_db);
+ goto fail;
+ }
+
+ counting = 1;
+ return 1;
+
+ fail:
+ lease_file_is_corrupt = db_validity;
+ fdfail:
+ (void)unlink (newfname);
+ return 0;
+}
+
+int group_writer (struct group_object *group)
+{
+ if (!write_group (group))
+ return 0;
+ if (!commit_leases ())
+ return 0;
+ return 1;
+}
diff --git a/server/ddns.c b/server/ddns.c
new file mode 100644
index 0000000..d6ffa5a
--- /dev/null
+++ b/server/ddns.c
@@ -0,0 +1,1957 @@
+/* ddns.c
+
+ Dynamic DNS updates. */
+
+/*
+ *
+ * Copyright (c) 2009-2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2007 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2000-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 has been donated to Internet Systems Consortium
+ * by Damien Neil of Nominum, Inc.
+ *
+ * To learn more about Internet Systems Consortium, see
+ * ``https://www.isc.org/''. To learn more about Nominum, Inc., see
+ * ``http://www.nominum.com''.
+ */
+
+#include "dhcpd.h"
+#include "dst/md5.h"
+#include <dns/result.h>
+
+#ifdef NSUPDATE
+
+static void ddns_fwd_srv_connector(struct lease *lease,
+ struct iasubopt *lease6,
+ struct binding_scope **inscope,
+ dhcp_ddns_cb_t *ddns_cb,
+ isc_result_t eresult);
+
+/* DN: No way of checking that there is enough space in a data_string's
+ buffer. Be certain to allocate enough!
+ TL: This is why the expression evaluation code allocates a *new*
+ data_string. :') */
+static void data_string_append (struct data_string *ds1,
+ struct data_string *ds2)
+{
+ memcpy (ds1 -> buffer -> data + ds1 -> len,
+ ds2 -> data,
+ ds2 -> len);
+ ds1 -> len += ds2 -> len;
+}
+
+
+/* Determine what, if any, forward and reverse updates need to be
+ * performed, and carry them through.
+ */
+int
+ddns_updates(struct packet *packet, struct lease *lease, struct lease *old,
+ struct iasubopt *lease6, struct iasubopt *old6,
+ struct option_state *options)
+{
+ unsigned long ddns_ttl = DEFAULT_DDNS_TTL;
+ struct data_string ddns_hostname;
+ struct data_string ddns_domainname;
+ struct data_string old_ddns_fwd_name;
+ struct data_string ddns_fwd_name;
+ //struct data_string ddns_rev_name;
+ struct data_string ddns_dhcid;
+ struct binding_scope **scope = NULL;
+ //struct iaddr addr;
+ struct data_string d1;
+ struct option_cache *oc;
+ int s1, s2;
+ int result = 0;
+ int server_updates_a = 1;
+ //int server_updates_ptr = 1;
+ struct buffer *bp = (struct buffer *)0;
+ int ignorep = 0, client_ignorep = 0;
+ int rev_name_len;
+ int i;
+
+ dhcp_ddns_cb_t *ddns_cb;
+ int do_remove = 0;
+
+ if (ddns_update_style != 2)
+ return 0;
+
+ /*
+ * sigh, I want to cancel any previous udpates before we do anything
+ * else but this means we need to deal with the lease vs lease6
+ * question twice.
+ * If there is a ddns request already outstanding cancel it.
+ */
+
+ if (lease != NULL) {
+ if ((old != NULL) && (old->ddns_cb != NULL)) {
+ ddns_cancel(old->ddns_cb, MDL);
+ old->ddns_cb = NULL;
+ }
+ } else if (lease6 != NULL) {
+ if ((old6 != NULL) && (old6->ddns_cb != NULL)) {
+ ddns_cancel(old6->ddns_cb, MDL);
+ old6->ddns_cb = NULL;
+ }
+ } else {
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ /* Silence compiler warnings. */
+ result = 0;
+ return(0);
+ }
+
+ /* allocate our control block */
+ ddns_cb = ddns_cb_alloc(MDL);
+ if (ddns_cb == NULL) {
+ return(0);
+ }
+ /*
+ * Assume that we shall update both the A and ptr records and,
+ * as this is an update, set the active flag
+ */
+ ddns_cb->flags = DDNS_UPDATE_ADDR | DDNS_UPDATE_PTR |
+ DDNS_ACTIVE_LEASE;
+
+ /*
+ * For v4 we flag static leases so we don't try
+ * and manipulate the lease later. For v6 we don't
+ * get static leases and don't need to flag them.
+ */
+ if (lease != NULL) {
+ scope = &(lease->scope);
+ ddns_cb->address = lease->ip_addr;
+ if (lease->flags & STATIC_LEASE)
+ ddns_cb->flags |= DDNS_STATIC_LEASE;
+ } else if (lease6 != NULL) {
+ scope = &(lease6->scope);
+ memcpy(ddns_cb->address.iabuf, lease6->addr.s6_addr, 16);
+ ddns_cb->address.len = 16;
+ }
+
+ memset (&d1, 0, sizeof(d1));
+ memset (&ddns_hostname, 0, sizeof (ddns_hostname));
+ memset (&ddns_domainname, 0, sizeof (ddns_domainname));
+ memset (&old_ddns_fwd_name, 0, sizeof (ddns_fwd_name));
+ memset (&ddns_fwd_name, 0, sizeof (ddns_fwd_name));
+ //memset (&ddns_rev_name, 0, sizeof (ddns_rev_name));
+ memset (&ddns_dhcid, 0, sizeof (ddns_dhcid));
+
+ /* If we are allowed to accept the client's update of its own A
+ record, see if the client wants to update its own A record. */
+ if (!(oc = lookup_option(&server_universe, options,
+ SV_CLIENT_UPDATES)) ||
+ evaluate_boolean_option_cache(&client_ignorep, packet, lease, NULL,
+ packet->options, options, scope,
+ oc, MDL)) {
+ /* If there's no fqdn.no-client-update or if it's
+ nonzero, don't try to use the client-supplied
+ XXX */
+ if (!(oc = lookup_option (&fqdn_universe, packet -> options,
+ FQDN_SERVER_UPDATE)) ||
+ evaluate_boolean_option_cache(&ignorep, packet, lease,
+ NULL, packet->options,
+ options, scope, oc, MDL))
+ goto noclient;
+ /* Win98 and Win2k will happily claim to be willing to
+ update an unqualified domain name. */
+ if (!(oc = lookup_option (&fqdn_universe, packet -> options,
+ FQDN_DOMAINNAME)))
+ goto noclient;
+ if (!(oc = lookup_option (&fqdn_universe, packet -> options,
+ FQDN_FQDN)) ||
+ !evaluate_option_cache(&ddns_fwd_name, packet, lease,
+ NULL, packet->options,
+ options, scope, oc, MDL))
+ goto noclient;
+ ddns_cb->flags &= ~DDNS_UPDATE_ADDR;
+ server_updates_a = 0;
+ goto client_updates;
+ }
+ noclient:
+ /* If do-forward-updates is disabled, this basically means don't
+ do an update unless the client is participating, so if we get
+ here and do-forward-updates is disabled, we can stop. */
+ if ((oc = lookup_option (&server_universe, options,
+ SV_DO_FORWARD_UPDATES)) &&
+ !evaluate_boolean_option_cache(&ignorep, packet, lease,
+ NULL, packet->options,
+ options, scope, oc, MDL)) {
+ goto out;
+ }
+
+ /* If it's a static lease, then don't do the DNS update unless we're
+ specifically configured to do so. If the client asked to do its
+ own update and we allowed that, we don't do this test. */
+ /* XXX: note that we cannot detect static DHCPv6 leases. */
+ if ((lease != NULL) && (lease->flags & STATIC_LEASE)) {
+ if (!(oc = lookup_option(&server_universe, options,
+ SV_UPDATE_STATIC_LEASES)) ||
+ !evaluate_boolean_option_cache(&ignorep, packet, lease,
+ NULL, packet->options,
+ options, scope, oc, MDL))
+ goto out;
+ }
+
+ /*
+ * Compute the name for the A record.
+ */
+ oc = lookup_option(&server_universe, options, SV_DDNS_HOST_NAME);
+ if (oc)
+ s1 = evaluate_option_cache(&ddns_hostname, packet, lease,
+ NULL, packet->options,
+ options, scope, oc, MDL);
+ else
+ s1 = 0;
+
+ /* If we don't have a host name based on ddns-hostname then use
+ * the host declaration name if there is one and use-host-decl-names
+ * is turned on. */
+ if ((s1 == 0) && (lease && lease->host && lease->host->name)) {
+ oc = lookup_option(&server_universe, options,
+ SV_USE_HOST_DECL_NAMES);
+ if (evaluate_boolean_option_cache(NULL, packet, lease,
+ NULL, packet->options,
+ options, scope, oc, MDL)) {
+ s1 = ((data_string_new(&ddns_hostname,
+ lease->host->name,
+ strlen(lease->host->name),
+ MDL) && ddns_hostname.len > 0));
+ }
+ }
+
+ oc = lookup_option(&server_universe, options, SV_DDNS_DOMAIN_NAME);
+ if (oc)
+ s2 = evaluate_option_cache(&ddns_domainname, packet, lease,
+ NULL, packet->options,
+ options, scope, oc, MDL);
+ else
+ s2 = 0;
+
+ if (s1 && s2) {
+ if (ddns_hostname.len + ddns_domainname.len > 253) {
+ log_error ("ddns_update: host.domain name too long");
+
+ goto out;
+ }
+
+ if (buffer_allocate (&ddns_fwd_name.buffer,
+ ddns_hostname.len +
+ ddns_domainname.len + 2, MDL)) {
+ ddns_fwd_name.data = ddns_fwd_name.buffer->data;
+ data_string_append (&ddns_fwd_name, &ddns_hostname);
+ ddns_fwd_name.buffer->data[ddns_fwd_name.len] = '.';
+ ddns_fwd_name.len++;
+ data_string_append (&ddns_fwd_name, &ddns_domainname);
+ ddns_fwd_name.buffer->data[ddns_fwd_name.len] ='\0';
+ ddns_fwd_name.terminated = 1;
+ }
+ }
+ client_updates:
+
+ /* See if there's a name already stored on the lease. */
+ if (find_bound_string(&old_ddns_fwd_name, *scope, "ddns-fwd-name")) {
+ /* If there is, see if it's different. */
+ if (old_ddns_fwd_name.len != ddns_fwd_name.len ||
+ memcmp (old_ddns_fwd_name.data, ddns_fwd_name.data,
+ old_ddns_fwd_name.len)) {
+ /*
+ * If the name is different, mark the old record
+ * for deletion and continue getting the new info.
+ */
+ do_remove = 1;
+ goto in;
+ }
+
+ /* See if there's a DHCID on the lease, and if not
+ * then potentially look for 'on events' for ad-hoc ddns.
+ */
+ if (!find_bound_string(&ddns_dhcid, *scope, "ddns-txt") &&
+ (old != NULL)) {
+ /* If there's no DHCID, the update was probably
+ done with the old-style ad-hoc DDNS updates.
+ So if the expiry and release events look like
+ they're the same, run them. This should delete
+ the old DDNS data. */
+ if (old -> on_expiry == old -> on_release) {
+ execute_statements(NULL, NULL, lease, NULL,
+ NULL, NULL, scope,
+ old->on_expiry);
+ if (old -> on_expiry)
+ executable_statement_dereference
+ (&old -> on_expiry, MDL);
+ if (old -> on_release)
+ executable_statement_dereference
+ (&old -> on_release, MDL);
+ /* Now, install the DDNS data the new way. */
+ goto in;
+ }
+ } else
+ data_string_forget(&ddns_dhcid, MDL);
+
+ /* See if the administrator wants to do updates even
+ in cases where the update already appears to have been
+ done. */
+ if (!(oc = lookup_option(&server_universe, options,
+ SV_UPDATE_OPTIMIZATION)) ||
+ evaluate_boolean_option_cache(&ignorep, packet, lease,
+ NULL, packet->options,
+ options, scope, oc, MDL)) {
+ result = 1;
+ goto noerror;
+ }
+ /* If there's no "ddns-fwd-name" on the lease record, see if
+ * there's a ddns-client-fqdn indicating a previous client
+ * update (if it changes, we need to adjust the PTR).
+ */
+ } else if (find_bound_string(&old_ddns_fwd_name, *scope,
+ "ddns-client-fqdn")) {
+ /* If the name is not different, no need to update
+ the PTR record. */
+ if (old_ddns_fwd_name.len == ddns_fwd_name.len &&
+ !memcmp (old_ddns_fwd_name.data, ddns_fwd_name.data,
+ old_ddns_fwd_name.len) &&
+ (!(oc = lookup_option(&server_universe, options,
+ SV_UPDATE_OPTIMIZATION)) ||
+ evaluate_boolean_option_cache(&ignorep, packet, lease,
+ NULL, packet->options,
+ options, scope, oc, MDL))) {
+ goto noerror;
+ }
+ }
+ in:
+
+ /* If we don't have a name that the client has been assigned, we
+ can just skip all this. */
+
+ if ((!ddns_fwd_name.len) || (ddns_fwd_name.len > 255)) {
+ if (ddns_fwd_name.len > 255) {
+ log_error ("client provided fqdn: too long");
+ }
+
+ /* If desired do the removals */
+ if (do_remove != 0) {
+ (void) ddns_removals(lease, lease6, NULL, ISC_TRUE);
+ }
+ goto out;
+ }
+
+ /*
+ * Compute the RR TTL.
+ *
+ * We have two ways of computing the TTL.
+ * The old behavior was to allow for the customer to set up
+ * the option or to default things. For v4 this was 1/2
+ * of the lease time, for v6 this was DEFAULT_DDNS_TTL.
+ * The new behavior continues to allow the customer to set
+ * up an option but the defaults are a little different.
+ * We now use 1/2 of the (preferred) lease time for both
+ * v4 and v6 and cap them at a maximum value.
+ * If the customer chooses to use an experession that references
+ * part of the lease the v6 value will be the default as there
+ * isn't a lease available for v6.
+ */
+
+ ddns_ttl = DEFAULT_DDNS_TTL;
+ if (lease != NULL) {
+ if (lease->ends <= cur_time) {
+ ddns_ttl = 0;
+ } else {
+ ddns_ttl = (lease->ends - cur_time)/2;
+ }
+ }
+#ifndef USE_OLD_DDNS_TTL
+ else if (lease6 != NULL) {
+ ddns_ttl = lease6->prefer/2;
+ }
+
+ if (ddns_ttl > MAX_DEFAULT_DDNS_TTL) {
+ ddns_ttl = MAX_DEFAULT_DDNS_TTL;
+ }
+#endif
+
+ if ((oc = lookup_option(&server_universe, options, SV_DDNS_TTL))) {
+ if (evaluate_option_cache(&d1, packet, lease, NULL,
+ packet->options, options,
+ scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int32_t))
+ ddns_ttl = getULong (d1.data);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ ddns_cb->ttl = ddns_ttl;
+
+ /*
+ * Compute the reverse IP name, starting with the domain name.
+ */
+ oc = lookup_option(&server_universe, options, SV_DDNS_REV_DOMAIN_NAME);
+ if (oc)
+ s1 = evaluate_option_cache(&d1, packet, lease, NULL,
+ packet->options, options,
+ scope, oc, MDL);
+ else
+ s1 = 0;
+
+ /*
+ * Figure out the length of the part of the name that depends
+ * on the address.
+ */
+ if (ddns_cb->address.len == 4) {
+ char buf[17];
+ /* XXX: WOW this is gross. */
+ rev_name_len = snprintf(buf, sizeof(buf), "%u.%u.%u.%u.",
+ ddns_cb->address.iabuf[3] & 0xff,
+ ddns_cb->address.iabuf[2] & 0xff,
+ ddns_cb->address.iabuf[1] & 0xff,
+ ddns_cb->address.iabuf[0] & 0xff) + 1;
+
+ if (s1) {
+ rev_name_len += d1.len;
+
+ if (rev_name_len > 255) {
+ log_error("ddns_update: Calculated rev domain "
+ "name too long.");
+ s1 = 0;
+ data_string_forget(&d1, MDL);
+ }
+ }
+ } else if (ddns_cb->address.len == 16) {
+ /*
+ * IPv6 reverse names are always the same length, with
+ * 32 hex characters separated by dots.
+ */
+ rev_name_len = sizeof("0.1.2.3.4.5.6.7."
+ "8.9.a.b.c.d.e.f."
+ "0.1.2.3.4.5.6.7."
+ "8.9.a.b.c.d.e.f."
+ "ip6.arpa.");
+
+ /* Set s1 to make sure we gate into updates. */
+ s1 = 1;
+ } else {
+ log_fatal("invalid address length %d", ddns_cb->address.len);
+ /* Silence compiler warnings. */
+ return 0;
+ }
+
+ /* See if we are configured NOT to do reverse ptr updates */
+ if ((oc = lookup_option(&server_universe, options,
+ SV_DO_REVERSE_UPDATES)) &&
+ !evaluate_boolean_option_cache(&ignorep, packet, lease, NULL,
+ packet->options, options,
+ scope, oc, MDL)) {
+ ddns_cb->flags &= ~DDNS_UPDATE_PTR;
+ }
+
+ if (s1) {
+ if (buffer_allocate(&ddns_cb->rev_name.buffer,
+ rev_name_len, MDL)) {
+ struct data_string *rname = &ddns_cb->rev_name;
+ rname->data = rname->buffer->data;
+
+ if (ddns_cb->address.len == 4) {
+ rname->len =
+ sprintf((char *)rname->buffer->data,
+ "%u.%u.%u.%u.",
+ ddns_cb->address.iabuf[3] & 0xff,
+ ddns_cb->address.iabuf[2] & 0xff,
+ ddns_cb->address.iabuf[1] & 0xff,
+ ddns_cb->address.iabuf[0] & 0xff);
+
+ /*
+ * d1.data may be opaque, garbage bytes, from
+ * user (mis)configuration.
+ */
+ data_string_append(rname, &d1);
+ rname->buffer->data[rname->len] = '\0';
+ } else if (ddns_cb->address.len == 16) {
+ char *p = (char *)&rname->buffer->data;
+ unsigned char *a = ddns_cb->address.iabuf + 15;
+ for (i=0; i<16; i++) {
+ sprintf(p, "%x.%x.",
+ (*a & 0xF), ((*a >> 4) & 0xF));
+ p += 4;
+ a -= 1;
+ }
+ strcat(p, "ip6.arpa.");
+ rname->len = strlen((const char *)rname->data);
+ }
+
+ rname->terminated = 1;
+ }
+
+ if (d1.data != NULL)
+ data_string_forget(&d1, MDL);
+ }
+
+ /*
+ * If we are updating the A record, compute the DHCID value.
+ */
+ if ((ddns_cb->flags & DDNS_UPDATE_ADDR) != 0) {
+ if (lease6 != NULL)
+ result = get_dhcid(&ddns_cb->dhcid, 2,
+ lease6->ia->iaid_duid.data,
+ lease6->ia->iaid_duid.len);
+ else if ((lease != NULL) && (lease->uid != NULL) &&
+ (lease->uid_len != 0))
+ result = get_dhcid (&ddns_cb->dhcid,
+ DHO_DHCP_CLIENT_IDENTIFIER,
+ lease -> uid, lease -> uid_len);
+ else if (lease != NULL)
+ result = get_dhcid (&ddns_cb->dhcid, 0,
+ lease -> hardware_addr.hbuf,
+ lease -> hardware_addr.hlen);
+ else
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ if (!result)
+ goto badfqdn;
+ }
+
+ /*
+ * Perform updates.
+ */
+
+ data_string_copy(&ddns_cb->fwd_name, &ddns_fwd_name, MDL);
+
+ if (ddns_cb->flags & DDNS_UPDATE_ADDR) {
+ oc = lookup_option(&server_universe, options,
+ SV_DDNS_CONFLICT_DETECT);
+ if (oc &&
+ !evaluate_boolean_option_cache(&ignorep, packet, lease,
+ NULL, packet->options,
+ options, scope, oc, MDL))
+ ddns_cb->flags |= DDNS_CONFLICT_OVERRIDE;
+
+ }
+
+ /*
+ * Previously if we failed during the removal operations
+ * we skipped the fqdn option processing. I'm not sure
+ * if we want to continue with that if we fail before sending
+ * the ddns messages. Currently we don't.
+ */
+ if (do_remove) {
+ /*
+ * We should log a more specific error closer to the actual
+ * error if we want one. ddns_removal failure not logged here.
+ */
+ (void) ddns_removals(lease, lease6, ddns_cb, ISC_TRUE);
+ }
+ else {
+ ddns_fwd_srv_connector(lease, lease6, scope, ddns_cb,
+ ISC_R_SUCCESS);
+ }
+ ddns_cb = NULL;
+
+ noerror:
+ /*
+ * If fqdn-reply option is disabled in dhcpd.conf, then don't
+ * send the client an FQDN option at all, even if one was requested.
+ * (WinXP clients allegedly misbehave if the option is present,
+ * refusing to handle PTR updates themselves).
+ */
+ if ((oc = lookup_option (&server_universe, options, SV_FQDN_REPLY)) &&
+ !evaluate_boolean_option_cache(&ignorep, packet, lease, NULL,
+ packet->options, options,
+ scope, oc, MDL)) {
+ goto badfqdn;
+
+ /* If we're ignoring client updates, then we tell a sort of 'white
+ * lie'. We've already updated the name the server wants (per the
+ * config written by the server admin). Now let the client do as
+ * it pleases with the name they supplied (if any).
+ *
+ * We only form an FQDN option this way if the client supplied an
+ * FQDN option that had FQDN_SERVER_UPDATE set false.
+ */
+ } else if (client_ignorep &&
+ (oc = lookup_option(&fqdn_universe, packet->options,
+ FQDN_SERVER_UPDATE)) &&
+ !evaluate_boolean_option_cache(&ignorep, packet, lease, NULL,
+ packet->options, options,
+ scope, oc, MDL)) {
+ oc = lookup_option(&fqdn_universe, packet->options, FQDN_FQDN);
+ if (oc && evaluate_option_cache(&d1, packet, lease, NULL,
+ packet->options, options,
+ scope, oc, MDL)) {
+ if (d1.len == 0 ||
+ !buffer_allocate(&bp, d1.len + 5, MDL))
+ goto badfqdn;
+
+ /* Server pretends it is not updating. */
+ bp->data[0] = 0;
+ if (!save_option_buffer(&fqdn_universe, options,
+ bp, &bp->data[0], 1,
+ FQDN_SERVER_UPDATE, 0))
+ goto badfqdn;
+
+ /* Client is encouraged to update. */
+ bp->data[1] = 0;
+ if (!save_option_buffer(&fqdn_universe, options,
+ bp, &bp->data[1], 1,
+ FQDN_NO_CLIENT_UPDATE, 0))
+ goto badfqdn;
+
+ /* Use the encoding of client's FQDN option. */
+ oc = lookup_option(&fqdn_universe, packet->options,
+ FQDN_ENCODED);
+ if (oc &&
+ evaluate_boolean_option_cache(&ignorep, packet,
+ lease, NULL,
+ packet->options,
+ options, scope,
+ oc, MDL))
+ bp->data[2] = 1; /* FQDN is encoded. */
+ else
+ bp->data[2] = 0; /* FQDN is not encoded. */
+
+ if (!save_option_buffer(&fqdn_universe, options,
+ bp, &bp->data[2], 1,
+ FQDN_ENCODED, 0))
+ goto badfqdn;
+
+ /* Current FQDN drafts indicate 255 is mandatory. */
+ bp->data[3] = 255;
+ if (!save_option_buffer(&fqdn_universe, options,
+ bp, &bp->data[3], 1,
+ FQDN_RCODE1, 0))
+ goto badfqdn;
+
+ bp->data[4] = 255;
+ if (!save_option_buffer(&fqdn_universe, options,
+ bp, &bp->data[4], 1,
+ FQDN_RCODE2, 0))
+ goto badfqdn;
+
+ /* Copy in the FQDN supplied by the client. Note well
+ * that the format of this option in the cache is going
+ * to be in text format. If the fqdn supplied by the
+ * client is encoded, it is decoded into the option
+ * cache when parsed out of the packet. It will be
+ * re-encoded when the option is assembled to be
+ * transmitted if the client elects that encoding.
+ */
+ memcpy(&bp->data[5], d1.data, d1.len);
+ if (!save_option_buffer(&fqdn_universe, options,
+ bp, &bp->data[5], d1.len,
+ FQDN_FQDN, 0))
+ goto badfqdn;
+
+ data_string_forget(&d1, MDL);
+ }
+ /* Set up the outgoing FQDN option if there was an incoming
+ * FQDN option. If there's a valid FQDN option, there MUST
+ * be an FQDN_SERVER_UPDATES suboption, it's part of the fixed
+ * length head of the option contents, so we test the latter
+ * to detect the presence of the former.
+ */
+ } else if ((oc = lookup_option(&fqdn_universe, packet->options,
+ FQDN_ENCODED)) &&
+ buffer_allocate(&bp, ddns_fwd_name.len + 5, MDL)) {
+ bp -> data [0] = server_updates_a;
+ if (!save_option_buffer(&fqdn_universe, options,
+ bp, &bp->data [0], 1,
+ FQDN_SERVER_UPDATE, 0))
+ goto badfqdn;
+ bp -> data [1] = server_updates_a;
+ if (!save_option_buffer(&fqdn_universe, options,
+ bp, &bp->data [1], 1,
+ FQDN_NO_CLIENT_UPDATE, 0))
+ goto badfqdn;
+
+ /* Do the same encoding the client did. */
+ if (evaluate_boolean_option_cache(&ignorep, packet, lease,
+ NULL, packet->options,
+ options, scope, oc, MDL))
+ bp -> data [2] = 1;
+ else
+ bp -> data [2] = 0;
+ if (!save_option_buffer(&fqdn_universe, options,
+ bp, &bp->data [2], 1,
+ FQDN_ENCODED, 0))
+ goto badfqdn;
+ bp -> data [3] = 255;//isc_rcode_to_ns (rcode1);
+ if (!save_option_buffer(&fqdn_universe, options,
+ bp, &bp->data [3], 1,
+ FQDN_RCODE1, 0))
+ goto badfqdn;
+ bp -> data [4] = 255;//isc_rcode_to_ns (rcode2);
+ if (!save_option_buffer(&fqdn_universe, options,
+ bp, &bp->data [4], 1,
+ FQDN_RCODE2, 0))
+ goto badfqdn;
+ if (ddns_fwd_name.len) {
+ memcpy (&bp -> data [5],
+ ddns_fwd_name.data, ddns_fwd_name.len);
+ if (!save_option_buffer(&fqdn_universe, options,
+ bp, &bp->data [5],
+ ddns_fwd_name.len,
+ FQDN_FQDN, 0))
+ goto badfqdn;
+ }
+ }
+
+ badfqdn:
+ out:
+ /*
+ * Final cleanup.
+ */
+ if (ddns_cb != NULL) {
+ ddns_cb_free(ddns_cb, MDL);
+ }
+
+ data_string_forget(&d1, MDL);
+ data_string_forget(&ddns_hostname, MDL);
+ data_string_forget(&ddns_domainname, MDL);
+ data_string_forget(&old_ddns_fwd_name, MDL);
+ data_string_forget(&ddns_fwd_name, MDL);
+ //data_string_forget(&ddns_rev_name, MDL);
+ //data_string_forget(&ddns_dhcid, MDL);
+ if (bp)
+ buffer_dereference(&bp, MDL);
+
+ return result;
+}
+
+/*%<
+ * Utility function to update text strings within a lease.
+ *
+ * The first issue is to find the proper scope. Sometimes we shall be
+ * called with a pointer to the scope in other cases we need to find
+ * the proper lease and then get the scope. Once we have the scope we update
+ * the proper strings, as indicated by the state value in the control block.
+ * Lastly, if we needed to find the scope we write it out, if we used a
+ * scope that was passed as an argument we don't write it, assuming that
+ * our caller (or his ...) will do the write.
+ *
+ *\li ddns_cb - the control block for the DDNS request
+ *
+ *\li inscope - a pointer to the scope to update. This may be NULL
+ * in which case we use the control block to find the lease and
+ * then the scope.
+ *
+ * Returns
+ *\li ISC_R_SUCCESS
+ *
+ *\li ISC_R_FAILURE - The routine was unable to find an expected scope.
+ * In some cases (static and inactive leases) we don't expect a scope
+ * and return success.
+ */
+
+isc_result_t
+ddns_update_lease_text(dhcp_ddns_cb_t *ddns_cb,
+ struct binding_scope **inscope)
+{
+ struct binding_scope **scope = NULL;
+ struct lease *lease = NULL;
+ struct iasubopt *lease6 = NULL;
+ struct ipv6_pool *pool = NULL;
+ struct in6_addr addr;
+ struct data_string lease_dhcid;
+
+ /*
+ * If the lease was static (for a fixed address)
+ * we don't need to do any work.
+ */
+ if (ddns_cb->flags & DDNS_STATIC_LEASE)
+ return (ISC_R_SUCCESS);
+
+ /*
+ * If we are processing an expired or released v6 lease
+ * or some types of v4 leases we don't actually have a
+ * scope to update
+ */
+ if ((ddns_cb->flags & DDNS_ACTIVE_LEASE) == 0)
+ return (ISC_R_SUCCESS);
+
+ if (inscope != NULL) {
+ scope = inscope;
+ } else if (ddns_cb->address.len == 4) {
+ if (find_lease_by_ip_addr(&lease, ddns_cb->address, MDL) != 0){
+ scope = &(lease->scope);
+ }
+ } else if (ddns_cb->address.len == 16) {
+ memcpy(&addr, &ddns_cb->address.iabuf, 16);
+ if ((find_ipv6_pool(&pool, D6O_IA_TA, &addr) ==
+ ISC_R_SUCCESS) ||
+ (find_ipv6_pool(&pool, D6O_IA_NA, &addr) ==
+ ISC_R_SUCCESS)) {
+ if (iasubopt_hash_lookup(&lease6, pool->leases,
+ &addr, 16, MDL)) {
+ scope = &(lease6->scope);
+ }
+ ipv6_pool_dereference(&pool, MDL);
+ }
+ } else {
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ }
+
+ if (scope == NULL) {
+ /* If necessary get rid of the lease */
+ if (lease) {
+ lease_dereference(&lease, MDL);
+ }
+ else if (lease6) {
+ iasubopt_dereference(&lease6, MDL);
+ }
+
+ return(ISC_R_FAILURE);
+ }
+
+ /* We now have a scope and can proceed to update it */
+ switch(ddns_cb->state) {
+ case DDNS_STATE_REM_PTR:
+ unset(*scope, "ddns-rev-name");
+ if ((ddns_cb->flags & DDNS_CLIENT_DID_UPDATE) != 0) {
+ unset(*scope, "ddns-client-fqdn");
+ }
+ break;
+
+ case DDNS_STATE_ADD_PTR:
+ case DDNS_STATE_CLEANUP:
+ bind_ds_value(scope, "ddns-rev-name", &ddns_cb->rev_name);
+ if ((ddns_cb->flags & DDNS_UPDATE_ADDR) == 0) {
+ bind_ds_value(scope, "ddns-client-fqdn",
+ &ddns_cb->fwd_name);
+ }
+ break;
+
+ case DDNS_STATE_ADD_FW_YXDHCID:
+ case DDNS_STATE_ADD_FW_NXDOMAIN:
+ bind_ds_value(scope, "ddns-fwd-name", &ddns_cb->fwd_name);
+
+ /* convert from dns version to lease version of dhcid */
+ memset(&lease_dhcid, 0, sizeof(lease_dhcid));
+ dhcid_tolease(&ddns_cb->dhcid, &lease_dhcid);
+ bind_ds_value(scope, "ddns-txt", &lease_dhcid);
+ data_string_forget(&lease_dhcid, MDL);
+
+ break;
+
+ case DDNS_STATE_REM_FW_NXRR:
+ case DDNS_STATE_REM_FW_YXDHCID:
+ unset(*scope, "ddns-fwd-name");
+ unset(*scope, "ddns-txt");
+ break;
+ }
+
+ /* If necessary write it out and get rid of the lease */
+ if (lease) {
+ write_lease(lease);
+ lease_dereference(&lease, MDL);
+ } else if (lease6) {
+ write_ia(lease6->ia);
+ iasubopt_dereference(&lease6, MDL);
+ }
+
+ return(ISC_R_SUCCESS);
+}
+
+/*
+ * This function should be called when update_lease_ptr function fails.
+ * It does inform user about the condition, provides some hints how to
+ * resolve this and dies gracefully. This can happend in at least three
+ * cases (all are configuration mistakes):
+ * a) IPv4: user have duplicate fixed-address entries (the same
+ * address is defined twice). We may have found wrong lease.
+ * b) IPv6: user have overlapping pools (we tried to find
+ * a lease in a wrong pool)
+ * c) IPv6: user have duplicate fixed-address6 entires (the same
+ * address is defined twice). We may have found wrong lease.
+ *
+ * Comment: while it would be possible to recover from both cases
+ * by forcibly searching for leases in *all* following pools, that would
+ * only hide the real problem - a misconfiguration. Proper solution
+ * is to log the problem, die and let the user fix his config file.
+ */
+void
+update_lease_failed(struct lease *lease,
+ struct iasubopt *lease6,
+ dhcp_ddns_cb_t *ddns_cb,
+ dhcp_ddns_cb_t *ddns_cb_set,
+ const char * file, int line)
+{
+ char lease_address[MAX_ADDRESS_STRING_LEN + 64];
+ char reason[128]; /* likely reason */
+
+ sprintf(reason, "unknown");
+ sprintf(lease_address, "unknown");
+
+ /*
+ * let's pretend that everything is ok, so we can continue for
+ * information gathering purposes
+ */
+
+ if (ddns_cb != NULL) {
+ strncpy(lease_address, piaddr(ddns_cb->address),
+ MAX_ADDRESS_STRING_LEN);
+
+ if (ddns_cb->address.len == 4) {
+ sprintf(reason, "duplicate IPv4 fixed-address entry");
+ } else if (ddns_cb->address.len == 16) {
+ sprintf(reason, "duplicate IPv6 fixed-address6 entry "
+ "or overlapping pools");
+ } else {
+ /*
+ * Should not happen. We have non-IPv4, non-IPv6
+ * address. Something is very wrong here.
+ */
+ sprintf(reason, "corrupted ddns_cb structure (address "
+ "length is %d)", ddns_cb->address.len);
+ }
+ }
+
+ log_error("Failed to properly update internal lease structure with "
+ "DDNS");
+ log_error("control block structures. Tried to update lease for"
+ "%s address, ddns_cb=%p.", lease_address, ddns_cb);
+
+ log_error("%s", "");
+ log_error("This condition can occur, if DHCP server configuration is "
+ "inconsistent.");
+ log_error("In particular, please do check that your configuration:");
+ log_error("a) does not have overlapping pools (especially containing");
+ log_error(" %s address).", lease_address);
+ log_error("b) there are no duplicate fixed-address or fixed-address6");
+ log_error("entries for the %s address.", lease_address);
+ log_error("%s", "");
+ log_error("Possible reason for this failure: %s", reason);
+
+ log_fatal("%s(%d): Failed to update lease database with DDNS info for "
+ "address %s. Lease database inconsistent. Unable to recover."
+ " Terminating.", file, line, lease_address);
+}
+
+/*
+ * utility function to update found lease. It does extra checks
+ * that we are indeed updating the right lease. It may happen
+ * that user have duplicate fixed-address entries, so we attempt
+ * to update wrong lease. See also safe_lease6_update.
+ */
+
+void
+safe_lease_update(struct lease *lease,
+ dhcp_ddns_cb_t *oldcb,
+ dhcp_ddns_cb_t *newcb,
+ const char *file, int line)
+{
+ if (lease == NULL) {
+ /* should never get here */
+ log_fatal("Impossible condition at %s:%d (called from %s:%d).",
+ MDL, file, line);
+ }
+
+ if ( (lease->ddns_cb == NULL) && (newcb == NULL) ) {
+ /*
+ * Trying to clean up pointer that is already null. We
+ * are most likely trying to update wrong lease here.
+ */
+
+ /*
+ * Previously this error message popped out during
+ * DNS update for fixed leases. As we no longer
+ * try to update the lease for a fixed (static) lease
+ * this should not be a problem.
+ */
+ log_error("%s(%d): Invalid lease update. Tried to "
+ "clear already NULL DDNS control block "
+ "pointer for lease %s.",
+ file, line, piaddr(lease->ip_addr) );
+
+#if defined (DNS_UPDATES_MEMORY_CHECKS)
+ update_lease_failed(lease, NULL, oldcb, newcb, file, line);
+#endif
+ /*
+ * May not reach this: update_lease_failed calls
+ * log_fatal.
+ */
+ return;
+ }
+
+ if ( (lease->ddns_cb != NULL) && (lease->ddns_cb != oldcb) ) {
+ /*
+ * There is existing cb structure, but it differs from
+ * what we expected to see there. Most likely we are
+ * trying to update wrong lease.
+ */
+ log_error("%s(%d): Failed to update internal lease "
+ "structure with DDNS control block. Existing"
+ " ddns_cb structure does not match "
+ "expectations.IPv4=%s, old ddns_cb=%p, tried"
+ "to update to new ddns_cb=%p", file, line,
+ piaddr(lease->ip_addr), oldcb, newcb);
+
+#if defined (DNS_UPDATES_MEMORY_CHECKS)
+ update_lease_failed(lease, NULL, oldcb, newcb, file, line);
+#endif
+ /*
+ * May not reach this: update_lease_failed calls
+ * log_fatal.
+ */
+ return;
+ }
+
+ /* additional IPv4 specific checks may be added here */
+
+ /* update the lease */
+ lease->ddns_cb = newcb;
+}
+
+void
+safe_lease6_update(struct iasubopt *lease6,
+ dhcp_ddns_cb_t *oldcb,
+ dhcp_ddns_cb_t *newcb,
+ const char *file, int line)
+{
+ char addrbuf[MAX_ADDRESS_STRING_LEN];
+
+ if (lease6 == NULL) {
+ /* should never get here */
+ log_fatal("Impossible condition at %s:%d (called from %s:%d).",
+ MDL, file, line);
+ }
+
+ if ( (lease6->ddns_cb == NULL) && (newcb == NULL) ) {
+ inet_ntop(AF_INET6, &lease6->addr, addrbuf,
+ MAX_ADDRESS_STRING_LEN);
+ /*
+ * Trying to clean up pointer that is already null. We
+ * are most likely trying to update wrong lease here.
+ */
+ log_error("%s(%d): Failed to update internal lease "
+ "structure. Tried to clear already NULL "
+ "DDNS control block pointer for lease %s.",
+ file, line, addrbuf);
+
+#if defined (DNS_UPDATES_MEMORY_CHECKS)
+ update_lease_failed(NULL, lease6, oldcb, newcb, file, line);
+#endif
+
+ /*
+ * May not reach this: update_lease_failed calls
+ * log_fatal.
+ */
+ return;
+ }
+
+ if ( (lease6->ddns_cb != NULL) && (lease6->ddns_cb != oldcb) ) {
+ /*
+ * there is existing cb structure, but it differs from
+ * what we expected to see there. Most likely we are
+ * trying to update wrong lease.
+ */
+ inet_ntop(AF_INET6, &lease6->addr, addrbuf,
+ MAX_ADDRESS_STRING_LEN);
+
+ log_error("%s(%d): Failed to update internal lease "
+ "structure with DDNS control block. Existing"
+ " ddns_cb structure does not match "
+ "expectations.IPv6=%s, old ddns_cb=%p, tried"
+ "to update to new ddns_cb=%p", file, line,
+ addrbuf, oldcb, newcb);
+
+#if defined (DNS_UPDATES_MEMORY_CHECKS)
+ update_lease_failed(NULL, lease6, oldcb, newcb, file, line);
+#endif
+ /*
+ * May not reach this: update_lease_failed calls
+ * log_fatal.
+ */
+ return;
+ }
+ /* additional IPv6 specific checks may be added here */
+
+ /* update the lease */
+ lease6->ddns_cb = newcb;
+}
+
+/*
+ * Utility function to update the pointer to the DDNS control block
+ * in a lease.
+ * SUCCESS - able to update the pointer
+ * FAILURE - lease didn't exist or sanity checks failed
+ * lease and lease6 may be empty in which case we attempt to find
+ * the lease from the ddns_cb information.
+ * ddns_cb is the control block to use if a lookup is necessary
+ * ddns_cb_set is the pointer to insert into the lease and may be NULL
+ * The last two arguments may look odd as they will be the same much of the
+ * time, but I need an argument to tell me if I'm setting or clearing in
+ * addition to the address information from the cb to look up the lease.
+ * using the same value twice allows me more flexibility.
+ */
+
+isc_result_t
+ddns_update_lease_ptr(struct lease *lease,
+ struct iasubopt *lease6,
+ dhcp_ddns_cb_t *ddns_cb,
+ dhcp_ddns_cb_t *ddns_cb_set,
+ const char * file, int line)
+{
+ char ddns_address[MAX_ADDRESS_STRING_LEN];
+ sprintf(ddns_address, "unknown");
+ if (ddns_cb == NULL) {
+ log_info("%s(%d): No control block for lease update",
+ file, line);
+ return (ISC_R_FAILURE);
+ }
+ else {
+ strcpy(ddns_address, piaddr(ddns_cb->address));
+ }
+#if defined (DEBUG_DNS_UPDATES)
+ log_info("%s(%d): Updating lease_ptr for ddns_cp=%p (addr=%s)",
+ file, line, ddns_cb, ddns_address );
+#endif
+
+ /*
+ * If the lease was static (for a fixed address)
+ * we don't need to do any work.
+ */
+ if (ddns_cb->flags & DDNS_STATIC_LEASE) {
+#if defined (DEBUG_DNS_UPDATES)
+ log_info("lease is static, returning");
+#endif
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * If we are processing an expired or released v6 lease
+ * we don't actually have a lease to update
+ */
+ if ((ddns_cb->address.len == 16) &&
+ ((ddns_cb->flags & DDNS_ACTIVE_LEASE) == 0)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (lease != NULL) {
+ safe_lease_update(lease, ddns_cb, ddns_cb_set,
+ file, line);
+ } else if (lease6 != NULL) {
+ safe_lease6_update(lease6, ddns_cb, ddns_cb_set,
+ file, line);
+ } else if (ddns_cb->address.len == 4) {
+ struct lease *find_lease = NULL;
+ if (find_lease_by_ip_addr(&find_lease,
+ ddns_cb->address, MDL) != 0) {
+#if defined (DEBUG_DNS_UPDATES)
+ log_info("%s(%d): find_lease_by_ip_addr(%s) successful:"
+ "lease=%p", file, line, ddns_address,
+ find_lease);
+#endif
+
+ safe_lease_update(find_lease, ddns_cb,
+ ddns_cb_set, file, line);
+ lease_dereference(&find_lease, MDL);
+ }
+ else {
+ log_error("%s(%d): ddns_update_lease_ptr failed. "
+ "Lease for %s not found.",
+ file, line, piaddr(ddns_cb->address));
+
+#if defined (DNS_UPDATES_MEMORY_CHECKS)
+ update_lease_failed(NULL, NULL, ddns_cb, ddns_cb_set,
+ file, line);
+#endif
+ /*
+ * may not reach this. update_lease_failed
+ * calls log_fatal.
+ */
+ return(ISC_R_FAILURE);
+
+ }
+ } else if (ddns_cb->address.len == 16) {
+ struct iasubopt *find_lease6 = NULL;
+ struct ipv6_pool *pool = NULL;
+ struct in6_addr addr;
+ char addrbuf[MAX_ADDRESS_STRING_LEN];
+
+ memcpy(&addr, &ddns_cb->address.iabuf, 16);
+ if ((find_ipv6_pool(&pool, D6O_IA_TA, &addr) !=
+ ISC_R_SUCCESS) &&
+ (find_ipv6_pool(&pool, D6O_IA_NA, &addr) !=
+ ISC_R_SUCCESS)) {
+ inet_ntop(AF_INET6, &addr, addrbuf,
+ MAX_ADDRESS_STRING_LEN);
+ log_error("%s(%d): Pool for lease %s not found.",
+ file, line, addrbuf);
+#if defined (DNS_UPDATES_MEMORY_CHECKS)
+ update_lease_failed(NULL, NULL, ddns_cb, ddns_cb_set,
+ file, line);
+#endif
+ /*
+ * never reached. update_lease_failed
+ * calls log_fatal.
+ */
+ return(ISC_R_FAILURE);
+ }
+
+ if (iasubopt_hash_lookup(&find_lease6, pool->leases,
+ &addr, 16, MDL)) {
+ find_lease6->ddns_cb = ddns_cb_set;
+ iasubopt_dereference(&find_lease6, MDL);
+ } else {
+ inet_ntop(AF_INET6, &addr, addrbuf,
+ MAX_ADDRESS_STRING_LEN);
+ log_error("%s(%d): Lease %s not found within pool.",
+ file, line, addrbuf);
+#if defined (DNS_UPDATES_MEMORY_CHECKS)
+ update_lease_failed(NULL, NULL, ddns_cb, ddns_cb_set,
+ file, line);
+#endif
+ /*
+ * never reached. update_lease_failed
+ * calls log_fatal.
+ */
+ return(ISC_R_FAILURE);
+ }
+ ipv6_pool_dereference(&pool, MDL);
+ } else {
+ /* shouldn't get here */
+ log_fatal("Impossible condition at %s:%d, called from %s:%d.",
+ MDL, file, line);
+ }
+
+ return(ISC_R_SUCCESS);
+}
+
+void
+ddns_ptr_add(dhcp_ddns_cb_t *ddns_cb,
+ isc_result_t eresult)
+{
+ if (eresult == ISC_R_SUCCESS) {
+ log_info("Added reverse map from %.*s to %.*s",
+ (int)ddns_cb->rev_name.len,
+ (const char *)ddns_cb->rev_name.data,
+ (int)ddns_cb->fwd_name.len,
+ (const char *)ddns_cb->fwd_name.data);
+
+ ddns_update_lease_text(ddns_cb, NULL);
+ } else {
+ log_error("Unable to add reverse map from %.*s to %.*s: %s",
+ (int)ddns_cb->rev_name.len,
+ (const char *)ddns_cb->rev_name.data,
+ (int)ddns_cb->fwd_name.len,
+ (const char *)ddns_cb->fwd_name.data,
+ isc_result_totext (eresult));
+ }
+
+ ddns_update_lease_ptr(NULL, NULL, ddns_cb, NULL, MDL);
+ ddns_cb_free(ddns_cb, MDL);
+ /*
+ * A single DDNS operation may require several calls depending on
+ * the current state as the prerequisites for the first message
+ * may not succeed requiring a second operation and potentially
+ * a ptr operation after that. The commit_leases operation is
+ * invoked at the end of this set of operations in order to require
+ * a single write for all of the changes. We call commit_leases
+ * here rather than immediately after the call to update the lease
+ * text in order to save any previously written data.
+ */
+ commit_leases();
+ return;
+}
+
+/*
+ * action routine when trying to remove a pointer
+ * this will be called after the ddns queries have completed
+ * if we succeeded in removing the pointer we go to the next step (if any)
+ * if not we cleanup and leave.
+ */
+
+void
+ddns_ptr_remove(dhcp_ddns_cb_t *ddns_cb,
+ isc_result_t eresult)
+{
+ isc_result_t result = eresult;
+
+ switch(eresult) {
+ case ISC_R_SUCCESS:
+ log_info("Removed reverse map on %.*s",
+ (int)ddns_cb->rev_name.len,
+ (const char *)ddns_cb->rev_name.data);
+ /* fall through */
+ case DNS_R_NXRRSET:
+ case DNS_R_NXDOMAIN:
+ /* No entry is the same as success.
+ * Remove the information from the lease and
+ * continue with any next step */
+ ddns_update_lease_text(ddns_cb, NULL);
+
+ /* trigger any add operation */
+ result = ISC_R_SUCCESS;
+#if defined (DEBUG_DNS_UPDATES)
+ log_info("DDNS: removed map or no reverse map to remove %.*s",
+ (int)ddns_cb->rev_name.len,
+ (const char *)ddns_cb->rev_name.data);
+#endif
+ break;
+
+ default:
+ log_error("Can't remove reverse map on %.*s: %s",
+ (int)ddns_cb->rev_name.len,
+ (const char *)ddns_cb->rev_name.data,
+ isc_result_totext (eresult));
+ break;
+ }
+
+ ddns_update_lease_ptr(NULL, NULL, ddns_cb, NULL, MDL);
+ ddns_fwd_srv_connector(NULL, NULL, NULL, ddns_cb->next_op, result);
+ ddns_cb_free(ddns_cb, MDL);
+ return;
+}
+
+
+/*
+ * If the first query succeeds, the updater can conclude that it
+ * has added a new name whose only RRs are the A and DHCID RR records.
+ * The A RR update is now complete (and a client updater is finished,
+ * while a server might proceed to perform a PTR RR update).
+ * -- "Interaction between DHCP and DNS"
+ *
+ * If the second query succeeds, the updater can conclude that the current
+ * client was the last client associated with the domain name, and that
+ * the name now contains the updated A RR. The A RR update is now
+ * complete (and a client updater is finished, while a server would
+ * then proceed to perform a PTR RR update).
+ * -- "Interaction between DHCP and DNS"
+ *
+ * If the second query fails with NXRRSET, the updater must conclude
+ * that the client's desired name is in use by another host. At this
+ * juncture, the updater can decide (based on some administrative
+ * configuration outside of the scope of this document) whether to let
+ * the existing owner of the name keep that name, and to (possibly)
+ * perform some name disambiguation operation on behalf of the current
+ * client, or to replace the RRs on the name with RRs that represent
+ * the current client. If the configured policy allows replacement of
+ * existing records, the updater submits a query that deletes the
+ * existing A RR and the existing DHCID RR, adding A and DHCID RRs that
+ * represent the IP address and client-identity of the new client.
+ * -- "Interaction between DHCP and DNS"
+ */
+
+void
+ddns_fwd_srv_add2(dhcp_ddns_cb_t *ddns_cb,
+ isc_result_t eresult)
+{
+ isc_result_t result;
+ const char *logstr = NULL;
+ char ddns_address[MAX_ADDRESS_STRING_LEN];
+
+ /* Construct a printable form of the address for logging */
+ strcpy(ddns_address, piaddr(ddns_cb->address));
+
+ switch(eresult) {
+ case ISC_R_SUCCESS:
+ log_info("Added new forward map from %.*s to %s",
+ (int)ddns_cb->fwd_name.len,
+ (const char *)ddns_cb->fwd_name.data,
+ ddns_address);
+
+ ddns_update_lease_text(ddns_cb, NULL);
+
+ if ((ddns_cb->flags & DDNS_UPDATE_PTR) != 0) {
+ /* if we have zone information get rid of it */
+ if (ddns_cb->zone != NULL) {
+ ddns_cb_forget_zone(ddns_cb);
+ }
+
+ ddns_cb->state = DDNS_STATE_ADD_PTR;
+ ddns_cb->cur_func = ddns_ptr_add;
+
+ result = ddns_modify_ptr(ddns_cb, MDL);
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+ }
+ break;
+
+ case DNS_R_YXRRSET:
+ case DNS_R_YXDOMAIN:
+ logstr = "DHCID mismatch, belongs to another client.";
+ break;
+
+ case DNS_R_NXRRSET:
+ case DNS_R_NXDOMAIN:
+ logstr = "Has an address record but no DHCID, not mine.";
+ break;
+
+ default:
+ logstr = isc_result_totext(eresult);
+ break;
+ }
+
+ if (logstr != NULL) {
+ log_error("Forward map from %.*s to %s FAILED: %s",
+ (int)ddns_cb->fwd_name.len,
+ (const char *)ddns_cb->fwd_name.data,
+ ddns_address, logstr);
+ }
+
+ ddns_update_lease_ptr(NULL, NULL, ddns_cb, NULL, MDL);
+ ddns_cb_free(ddns_cb, MDL);
+ /*
+ * A single DDNS operation may require several calls depending on
+ * the current state as the prerequisites for the first message
+ * may not succeed requiring a second operation and potentially
+ * a ptr operation after that. The commit_leases operation is
+ * invoked at the end of this set of operations in order to require
+ * a single write for all of the changes. We call commit_leases
+ * here rather than immediately after the call to update the lease
+ * text in order to save any previously written data.
+ */
+ commit_leases();
+ return;
+}
+
+void
+ddns_fwd_srv_add1(dhcp_ddns_cb_t *ddns_cb,
+ isc_result_t eresult)
+{
+ isc_result_t result;
+ char ddns_address[MAX_ADDRESS_STRING_LEN];
+
+ /* Construct a printable form of the address for logging */
+ strcpy(ddns_address, piaddr(ddns_cb->address));
+
+ switch(eresult) {
+ case ISC_R_SUCCESS:
+ log_info ("Added new forward map from %.*s to %s",
+ (int)ddns_cb->fwd_name.len,
+ (const char *)ddns_cb->fwd_name.data,
+ ddns_address);
+
+ ddns_update_lease_text(ddns_cb, NULL);
+
+ if ((ddns_cb->flags & DDNS_UPDATE_PTR) != 0) {
+ /* if we have zone information get rid of it */
+ if (ddns_cb->zone != NULL) {
+ ddns_cb_forget_zone(ddns_cb);
+ }
+
+ ddns_cb->state = DDNS_STATE_ADD_PTR;
+ ddns_cb->cur_func = ddns_ptr_add;
+
+ result = ddns_modify_ptr(ddns_cb, MDL);
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+ }
+ break;
+
+ case DNS_R_YXDOMAIN:
+ /* we can reuse the zone information */
+ ddns_cb->state = DDNS_STATE_ADD_FW_YXDHCID;
+ ddns_cb->cur_func = ddns_fwd_srv_add2;
+
+ result = ddns_modify_fwd(ddns_cb, MDL);
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+ break;
+
+ default:
+ log_error ("Unable to add forward map from %.*s to %s: %s",
+ (int)ddns_cb->fwd_name.len,
+ (const char *)ddns_cb->fwd_name.data,
+ ddns_address,
+ isc_result_totext (eresult));
+ break;
+ }
+
+ ddns_update_lease_ptr(NULL, NULL, ddns_cb, NULL, MDL);
+ ddns_cb_free(ddns_cb, MDL);
+ /*
+ * A single DDNS operation may require several calls depending on
+ * the current state as the prerequisites for the first message
+ * may not succeed requiring a second operation and potentially
+ * a ptr operation after that. The commit_leases operation is
+ * invoked at the end of this set of operations in order to require
+ * a single write for all of the changes. We call commit_leases
+ * here rather than immediately after the call to update the lease
+ * text in order to save any previously written data.
+ */
+ commit_leases();
+ return;
+}
+
+static void
+ddns_fwd_srv_connector(struct lease *lease,
+ struct iasubopt *lease6,
+ struct binding_scope **inscope,
+ dhcp_ddns_cb_t *ddns_cb,
+ isc_result_t eresult)
+{
+ isc_result_t result = ISC_R_FAILURE;
+
+ if (ddns_cb == NULL) {
+ /* nothing to do */
+ return;
+ }
+
+ if (eresult == ISC_R_SUCCESS) {
+ /*
+ * If we have updates dispatch as appropriate,
+ * if not do FQDN binding if desired.
+ */
+
+ if (ddns_cb->flags & DDNS_UPDATE_ADDR) {
+ ddns_cb->state = DDNS_STATE_ADD_FW_NXDOMAIN;
+ ddns_cb->cur_func = ddns_fwd_srv_add1;
+ result = ddns_modify_fwd(ddns_cb, MDL);
+ } else if ((ddns_cb->flags & DDNS_UPDATE_PTR) &&
+ (ddns_cb->rev_name.len != 0)) {
+ ddns_cb->state = DDNS_STATE_ADD_PTR;
+ ddns_cb->cur_func = ddns_ptr_add;
+ result = ddns_modify_ptr(ddns_cb, MDL);
+ } else {
+ ddns_update_lease_text(ddns_cb, inscope);
+ }
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ ddns_update_lease_ptr(lease, lease6, ddns_cb, ddns_cb, MDL);
+ } else {
+ ddns_cb_free(ddns_cb, MDL);
+ }
+
+ return;
+}
+
+/*
+ * If the first query fails, the updater MUST NOT delete the DNS name. It
+ * may be that the host whose lease on the server has expired has moved
+ * to another network and obtained a lease from a different server,
+ * which has caused the client's A RR to be replaced. It may also be
+ * that some other client has been configured with a name that matches
+ * the name of the DHCP client, and the policy was that the last client
+ * to specify the name would get the name. In this case, the DHCID RR
+ * will no longer match the updater's notion of the client-identity of
+ * the host pointed to by the DNS name.
+ * -- "Interaction between DHCP and DNS"
+ */
+
+void
+ddns_fwd_srv_rem2(dhcp_ddns_cb_t *ddns_cb,
+ isc_result_t eresult)
+{
+ if (eresult == ISC_R_SUCCESS) {
+ ddns_update_lease_text(ddns_cb, NULL);
+
+ /* Do the next operation */
+ if ((ddns_cb->flags & DDNS_UPDATE_PTR) != 0) {
+ /* if we have zone information get rid of it */
+ if (ddns_cb->zone != NULL) {
+ ddns_cb_forget_zone(ddns_cb);
+ }
+
+ ddns_cb->state = DDNS_STATE_REM_PTR;
+ ddns_cb->cur_func = ddns_ptr_remove;
+
+ eresult = ddns_modify_ptr(ddns_cb, MDL);
+ if (eresult == ISC_R_SUCCESS) {
+ return;
+ }
+ }
+ }
+
+ ddns_update_lease_ptr(NULL, NULL, ddns_cb, NULL, MDL);
+ ddns_fwd_srv_connector(NULL, NULL, NULL, ddns_cb->next_op, eresult);
+ ddns_cb_free(ddns_cb, MDL);
+ return;
+}
+
+
+/*
+ * First action routine when trying to remove a fwd
+ * this will be called after the ddns queries have completed
+ * if we succeeded in removing the fwd we go to the next step (if any)
+ * if not we cleanup and leave.
+ */
+
+void
+ddns_fwd_srv_rem1(dhcp_ddns_cb_t *ddns_cb,
+ isc_result_t eresult)
+{
+ isc_result_t result = eresult;
+ char ddns_address[MAX_ADDRESS_STRING_LEN];
+
+ switch(eresult) {
+ case ISC_R_SUCCESS:
+ /* Construct a printable form of the address for logging */
+ strcpy(ddns_address, piaddr(ddns_cb->address));
+ log_info("Removed forward map from %.*s to %s",
+ (int)ddns_cb->fwd_name.len,
+ (const char*)ddns_cb->fwd_name.data,
+ ddns_address);
+
+ /* Do the second step of the FWD removal */
+ ddns_cb->state = DDNS_STATE_REM_FW_NXRR;
+ ddns_cb->cur_func = ddns_fwd_srv_rem2;
+ result = ddns_modify_fwd(ddns_cb, MDL);
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+ break;
+
+ case DNS_R_NXRRSET:
+ case DNS_R_NXDOMAIN:
+ ddns_update_lease_text(ddns_cb, NULL);
+
+#if defined (DEBUG_DNS_UPDATES)
+ log_info("DDNS: no forward map to remove. %p", ddns_cb);
+#endif
+
+ /* Do the next operation */
+ if ((ddns_cb->flags & DDNS_UPDATE_PTR) != 0) {
+ /* if we have zone information get rid of it */
+ if (ddns_cb->zone != NULL) {
+ ddns_cb_forget_zone(ddns_cb);
+ }
+
+ ddns_cb->state = DDNS_STATE_REM_PTR;
+ ddns_cb->cur_func = ddns_ptr_remove;
+
+ result = ddns_modify_ptr(ddns_cb, MDL);
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+ }
+ else {
+ /* Trigger the add operation */
+ eresult = ISC_R_SUCCESS;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ ddns_update_lease_ptr(NULL, NULL, ddns_cb, NULL, MDL);
+ ddns_fwd_srv_connector(NULL, NULL, NULL, ddns_cb->next_op, eresult);
+ ddns_cb_free(ddns_cb, MDL);
+}
+
+/*%<
+ * Remove relevant entries from DNS.
+ *
+ * \li lease - lease to start with if this is for v4
+ *
+ * \li lease6 - lease to start with if this is for v6
+ *
+ * \li add_ddns_cb - control block for additional DDNS work. This
+ * is used when the code is going to add a DDNS entry after removing
+ * the current entry.
+ *
+ * \li active - indication about the status of the lease. It is
+ * ISC_TRUE if the lease is still active, and FALSE if the lease
+ * is inactive. This is used to indicate if the lease is inactive or going
+ * to inactive so we can avoid trying to update the lease with cb pointers
+ * and text information if it isn't useful.
+ *
+ * Returns
+ * \li #ISC_R_FAILURE - badness occurred and we weren't able to do what was wanted
+ * \li #ISC_R_SUCCESS - we were able to do stuff but it's in progress
+ *
+ * in both cases any additional block has been passed on to it's handler
+ */
+
+isc_result_t
+ddns_removals(struct lease *lease,
+ struct iasubopt *lease6,
+ dhcp_ddns_cb_t *add_ddns_cb,
+ isc_boolean_t active)
+{
+ isc_result_t rcode, execute_add = ISC_R_FAILURE;
+ struct binding_scope **scope = NULL;
+ isc_result_t result = ISC_R_FAILURE;
+ dhcp_ddns_cb_t *ddns_cb = NULL;
+ struct data_string leaseid;
+
+ /*
+ * See if we need to cancel an outstanding request. Mostly this is
+ * used to handle the case where this routine is called twice for
+ * the same release or abandon event.
+ *
+ * When called from the dns code as part of an update request
+ * (add_ddns_cb != NULL) any outstanding requests will have already
+ * been cancelled.
+ *
+ * If the new request is just a removal and we have an outstanding
+ * request we have several options:
+ *
+ * - we are doing an update or we are doing a removal and the active
+ * flag has changed from TRUE to FALSE. In these cases we need to
+ * cancel the old request and start the new one.
+ *
+ * - other wise we are doing a removal with the active flag unchanged.
+ * In this case we can let the current removal continue and do not need
+ * to start a new one. If the old request included an update to be
+ * done after the removal we need to kill the update part of the
+ * request.
+ */
+
+ if (add_ddns_cb == NULL) {
+ if ((lease != NULL) && (lease->ddns_cb != NULL)) {
+ ddns_cb = lease->ddns_cb;
+
+ /*
+ * Is the old request an update or did the
+ * the active flag change?
+ */
+ if (((ddns_cb->state == DDNS_STATE_ADD_PTR) ||
+ (ddns_cb->state == DDNS_STATE_ADD_FW_NXDOMAIN) ||
+ (ddns_cb->state == DDNS_STATE_ADD_FW_YXDHCID)) ||
+ ((active == ISC_FALSE) &&
+ ((ddns_cb->flags & DDNS_ACTIVE_LEASE) != 0))) {
+ /* Cancel the current request */
+ ddns_cancel(lease->ddns_cb, MDL);
+ lease->ddns_cb = NULL;
+ } else {
+ /* Remvoval, check and remove updates */
+ if (ddns_cb->next_op != NULL) {
+ ddns_cb_free(ddns_cb->next_op, MDL);
+ ddns_cb->next_op = NULL;
+ }
+#if defined (DEBUG_DNS_UPDATES)
+ log_info("DDNS %s(%d): removal already in "
+ "progress new ddns_cb=%p",
+ MDL, ddns_cb);
+#endif
+ return (ISC_R_SUCCESS);
+ }
+ } else if ((lease6 != NULL) && (lease6->ddns_cb != NULL)) {
+ ddns_cb = lease6->ddns_cb;
+
+ /*
+ * Is the old request an update or did the
+ * the active flag change?
+ */
+ if (((ddns_cb->state == DDNS_STATE_ADD_PTR) ||
+ (ddns_cb->state == DDNS_STATE_ADD_FW_NXDOMAIN) ||
+ (ddns_cb->state == DDNS_STATE_ADD_FW_YXDHCID)) ||
+ ((active == ISC_FALSE) &&
+ ((ddns_cb->flags & DDNS_ACTIVE_LEASE) != 0))) {
+ /* Cancel the current request */
+ ddns_cancel(lease6->ddns_cb, MDL);
+ lease6->ddns_cb = NULL;
+ } else {
+ /* Remvoval, check and remove updates */
+ if (ddns_cb->next_op != NULL) {
+ ddns_cb_free(ddns_cb->next_op, MDL);
+ ddns_cb->next_op = NULL;
+ }
+#if defined (DEBUG_DNS_UPDATES)
+ log_info("DDNS %s(%d): removal already in "
+ "progress new ddns_cb=%p",
+ MDL, ddns_cb);
+#endif
+ return (ISC_R_SUCCESS);
+ }
+ }
+ ddns_cb = NULL;
+ }
+
+ /* allocate our control block */
+ ddns_cb = ddns_cb_alloc(MDL);
+ if (ddns_cb == NULL) {
+ goto cleanup;
+ }
+
+ /*
+ * For v4 we flag static leases so we don't try
+ * and manipulate the lease later. For v6 we don't
+ * get static leases and don't need to flag them.
+ */
+ if (lease != NULL) {
+ scope = &(lease->scope);
+ ddns_cb->address = lease->ip_addr;
+ if (lease->flags & STATIC_LEASE)
+ ddns_cb->flags |= DDNS_STATIC_LEASE;
+ } else if (lease6 != NULL) {
+ scope = &(lease6->scope);
+ memcpy(&ddns_cb->address.iabuf, lease6->addr.s6_addr, 16);
+ ddns_cb->address.len = 16;
+ } else
+ goto cleanup;
+
+ /*
+ * Set the flag bit if the lease is active, that is it isn't
+ * expired or released. This is used to determine if we need
+ * to update the scope information for both v4 and v6 and
+ * the lease information for v6 when the response
+ * from the DNS code is processed.
+ */
+ if (active == ISC_TRUE) {
+ ddns_cb->flags |= DDNS_ACTIVE_LEASE;
+ }
+
+ /* No scope implies that DDNS has not been performed for this lease. */
+ if (*scope == NULL)
+ goto cleanup;
+
+ if (ddns_update_style != 2)
+ goto cleanup;
+
+ /* Assume that we are removing both records */
+ ddns_cb->flags |= DDNS_UPDATE_ADDR | DDNS_UPDATE_PTR;
+
+ /* and that we want to do the add call */
+ execute_add = ISC_R_SUCCESS;
+
+ /*
+ * Look up stored names.
+ */
+
+ /*
+ * Find the fwd name and copy it to the control block. If we don't
+ * have it we can't delete the fwd record but we can still try to
+ * remove the ptr record and cleanup the lease information if the
+ * client did the fwd update.
+ */
+ if (!find_bound_string(&ddns_cb->fwd_name, *scope, "ddns-fwd-name")) {
+ /* don't try and delete the A, or do the add */
+ ddns_cb->flags &= ~DDNS_UPDATE_ADDR;
+ execute_add = ISC_R_FAILURE;
+
+ /* Check if client did update */
+ if (find_bound_string(&ddns_cb->fwd_name, *scope,
+ "ddns-client-fqdn")) {
+ ddns_cb->flags |= DDNS_CLIENT_DID_UPDATE;
+ }
+ }
+
+ /*
+ * Find the ptr name and copy it to the control block. If we don't
+ * have it this isn't an interim or rfc3??? record so we can't delete
+ * the A record using this mechanism but we can delete the ptr record.
+ * In this case we will attempt to do any requested next step.
+ */
+ memset(&leaseid, 0, sizeof(leaseid));
+ if (!find_bound_string (&leaseid, *scope, "ddns-txt")) {
+ ddns_cb->flags &= ~DDNS_UPDATE_ADDR;
+ } else {
+ if (dhcid_fromlease(&ddns_cb->dhcid, &leaseid) !=
+ ISC_R_SUCCESS) {
+ /* We couldn't convert the dhcid from the lease
+ * version to the dns version. We can't delete
+ * the A record but can continue to the ptr
+ */
+ ddns_cb->flags &= ~DDNS_UPDATE_ADDR;
+ }
+ data_string_forget(&leaseid, MDL);
+ }
+
+ /*
+ * Find the rev name and copy it to the control block. If we don't
+ * have it we can't get rid of it but we can try to remove the fwd
+ * pointer if desired.
+ */
+ if (!find_bound_string(&ddns_cb->rev_name, *scope, "ddns-rev-name")) {
+ ddns_cb->flags &= ~DDNS_UPDATE_PTR;
+ }
+
+ /*
+ * If we have a second control block for doing an add
+ * after the remove finished attach it to our control block.
+ */
+ ddns_cb->next_op = add_ddns_cb;
+
+ /*
+ * Now that we've collected the information we can try to process it.
+ * If necessary we call an appropriate routine to send a message and
+ * provide it with an action routine to run on the control block given
+ * the results of the message. We have three entry points from here,
+ * one for removing the A record, the next for removing the PTR and
+ * the third for doing any requested add.
+ */
+ if ((ddns_cb->flags & DDNS_UPDATE_ADDR) != 0) {
+ if (ddns_cb->fwd_name.len != 0) {
+ ddns_cb->state = DDNS_STATE_REM_FW_YXDHCID;
+ ddns_cb->cur_func = ddns_fwd_srv_rem1;
+
+ rcode = ddns_modify_fwd(ddns_cb, MDL);
+ if (rcode == ISC_R_SUCCESS) {
+ ddns_update_lease_ptr(lease, lease6, ddns_cb,
+ ddns_cb, MDL);
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * We weren't able to process the request tag the
+ * add so we won't execute it.
+ */
+ execute_add = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ else {
+ /*remove info from scope */
+ unset(*scope, "ddns-fwd-name");
+ unset(*scope, "ddns-txt");
+ }
+ }
+
+ if ((ddns_cb->flags & DDNS_UPDATE_PTR) != 0) {
+ ddns_cb->state = DDNS_STATE_REM_PTR;
+ ddns_cb->cur_func = ddns_ptr_remove;
+
+ /*
+ * if execute add isn't success remove the control block so
+ * it won't be processed when the remove completes. We
+ * also arrange to clean it up and get rid of it.
+ */
+ if (execute_add != ISC_R_SUCCESS) {
+ ddns_cb->next_op = NULL;
+ ddns_fwd_srv_connector(lease, lease6, scope,
+ add_ddns_cb, execute_add);
+ add_ddns_cb = NULL;
+ }
+ else {
+ result = ISC_R_SUCCESS;
+ }
+
+ rcode = ddns_modify_ptr(ddns_cb, MDL);
+ if (rcode == ISC_R_SUCCESS) {
+ ddns_update_lease_ptr(lease, lease6, ddns_cb, ddns_cb,
+ MDL);
+ return (result);
+ }
+
+ /* We weren't able to process the request tag the
+ * add so we won't execute it */
+ execute_add = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ cleanup:
+ /*
+ * We've gotten here because we didn't need to send a message or
+ * we failed when trying to do so. We send the additional cb
+ * off to handle sending and/or cleanup and cleanup anything
+ * we allocated here.
+ */
+ ddns_fwd_srv_connector(lease, lease6, scope, add_ddns_cb, execute_add);
+ if (ddns_cb != NULL)
+ ddns_cb_free(ddns_cb, MDL);
+
+ return (result);
+}
+
+#endif /* NSUPDATE */
diff --git a/server/dhcp.c b/server/dhcp.c
new file mode 100644
index 0000000..a724fb9
--- /dev/null
+++ b/server/dhcp.c
@@ -0,0 +1,4665 @@
+/* dhcp.c
+
+ DHCP Protocol engine. */
+
+/*
+ * 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 <errno.h>
+#include <limits.h>
+#include <sys/time.h>
+
+static void commit_leases_ackout(void *foo);
+static void maybe_return_agent_options(struct packet *packet,
+ struct option_state *options);
+
+int outstanding_pings;
+
+struct leasequeue *ackqueue_head, *ackqueue_tail;
+static struct leasequeue *free_ackqueue;
+static struct timeval max_fsync;
+
+int outstanding_acks;
+int max_outstanding_acks = DEFAULT_DELAYED_ACK;
+int max_ack_delay_secs = DEFAULT_ACK_DELAY_SECS;
+int max_ack_delay_usecs = DEFAULT_ACK_DELAY_USECS;
+int min_ack_delay_usecs = DEFAULT_MIN_ACK_DELAY_USECS;
+
+static char dhcp_message [256];
+static int site_code_min;
+
+static int find_min_site_code(struct universe *);
+static isc_result_t lowest_site_code(const void *, unsigned, void *);
+
+static const char *dhcp_type_names [] = {
+ "DHCPDISCOVER",
+ "DHCPOFFER",
+ "DHCPREQUEST",
+ "DHCPDECLINE",
+ "DHCPACK",
+ "DHCPNAK",
+ "DHCPRELEASE",
+ "DHCPINFORM",
+ "type 9",
+ "DHCPLEASEQUERY",
+ "DHCPLEASEUNASSIGNED",
+ "DHCPLEASEUNKNOWN",
+ "DHCPLEASEACTIVE"
+};
+const int dhcp_type_name_max = ((sizeof dhcp_type_names) / sizeof (char *));
+
+#if defined (TRACING)
+# define send_packet trace_packet_send
+#endif
+
+void
+dhcp (struct packet *packet) {
+ int ms_nulltp = 0;
+ struct option_cache *oc;
+ struct lease *lease = NULL;
+ const char *errmsg;
+ struct data_string data;
+
+ if (!locate_network(packet) &&
+ packet->packet_type != DHCPREQUEST &&
+ packet->packet_type != DHCPINFORM &&
+ packet->packet_type != DHCPLEASEQUERY) {
+ const char *s;
+ char typebuf[32];
+ errmsg = "unknown network segment";
+ bad_packet:
+
+ if (packet->packet_type > 0 &&
+ packet->packet_type <= dhcp_type_name_max) {
+ s = dhcp_type_names[packet->packet_type - 1];
+ } else {
+ /* %Audit% Cannot exceed 28 bytes. %2004.06.17,Safe% */
+ sprintf(typebuf, "type %d", packet->packet_type);
+ s = typebuf;
+ }
+
+ log_info("%s from %s via %s: %s", s,
+ (packet->raw->htype
+ ? print_hw_addr(packet->raw->htype,
+ packet->raw->hlen,
+ packet->raw->chaddr)
+ : "<no identifier>"),
+ packet->raw->giaddr.s_addr
+ ? inet_ntoa(packet->raw->giaddr)
+ : packet->interface->name, errmsg);
+ goto out;
+ }
+
+ /* There is a problem with the relay agent information option,
+ * which is that in order for a normal relay agent to append
+ * this option, the relay agent has to have been involved in
+ * getting the packet from the client to the server. Note
+ * that this is the software entity known as the relay agent,
+ * _not_ the hardware entity known as a router in which the
+ * relay agent may be running, so the fact that a router has
+ * forwarded a packet does not mean that the relay agent in
+ * the router was involved.
+ *
+ * So when the client broadcasts (DHCPDISCOVER, or giaddr is set),
+ * we can be sure that there are either agent options in the
+ * packet, or there aren't supposed to be. When the giaddr is not
+ * set, it's still possible that the client is on a directly
+ * attached subnet, and agent options are being appended by an l2
+ * device that has no address, and so sets no giaddr.
+ *
+ * But in either case it's possible that the packets we receive
+ * from the client in RENEW state may not include the agent options,
+ * so if they are not in the packet we must "pretend" the last values
+ * we observed were provided.
+ */
+ if (packet->packet_type == DHCPREQUEST &&
+ packet->raw->ciaddr.s_addr && !packet->raw->giaddr.s_addr &&
+ (packet->options->universe_count <= agent_universe.index ||
+ packet->options->universes[agent_universe.index] == NULL))
+ {
+ struct iaddr cip;
+
+ cip.len = sizeof packet -> raw -> ciaddr;
+ memcpy (cip.iabuf, &packet -> raw -> ciaddr,
+ sizeof packet -> raw -> ciaddr);
+ if (!find_lease_by_ip_addr (&lease, cip, MDL))
+ goto nolease;
+
+ /* If there are no agent options on the lease, it's not
+ interesting. */
+ if (!lease -> agent_options)
+ goto nolease;
+
+ /* The client should not be unicasting a renewal if its lease
+ has expired, so make it go through the process of getting
+ its agent options legally. */
+ if (lease -> ends < cur_time)
+ goto nolease;
+
+ if (lease -> uid_len) {
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_CLIENT_IDENTIFIER);
+ if (!oc)
+ goto nolease;
+
+ memset (&data, 0, sizeof data);
+ if (!evaluate_option_cache (&data,
+ packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL))
+ goto nolease;
+ if (lease -> uid_len != data.len ||
+ memcmp (lease -> uid, data.data, data.len)) {
+ data_string_forget (&data, MDL);
+ goto nolease;
+ }
+ data_string_forget (&data, MDL);
+ } else
+ if ((lease -> hardware_addr.hbuf [0] !=
+ packet -> raw -> htype) ||
+ (lease -> hardware_addr.hlen - 1 !=
+ packet -> raw -> hlen) ||
+ memcmp (&lease -> hardware_addr.hbuf [1],
+ packet -> raw -> chaddr,
+ packet -> raw -> hlen))
+ goto nolease;
+
+ /* Okay, so we found a lease that matches the client. */
+ option_chain_head_reference ((struct option_chain_head **)
+ &(packet -> options -> universes
+ [agent_universe.index]),
+ lease -> agent_options, MDL);
+
+ if (packet->options->universe_count <= agent_universe.index)
+ packet->options->universe_count =
+ agent_universe.index + 1;
+
+ packet->agent_options_stashed = ISC_TRUE;
+ }
+ nolease:
+
+ /* If a client null terminates options it sends, it probably
+ * expects the server to reciprocate.
+ */
+ if ((oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_HOST_NAME))) {
+ if (!oc -> expression)
+ ms_nulltp = oc->flags & OPTION_HAD_NULLS;
+ }
+
+ /* Classify the client. */
+ classify_client (packet);
+
+ switch (packet -> packet_type) {
+ case DHCPDISCOVER:
+ dhcpdiscover (packet, ms_nulltp);
+ break;
+
+ case DHCPREQUEST:
+ dhcprequest (packet, ms_nulltp, lease);
+ break;
+
+ case DHCPRELEASE:
+ dhcprelease (packet, ms_nulltp);
+ break;
+
+ case DHCPDECLINE:
+ dhcpdecline (packet, ms_nulltp);
+ break;
+
+ case DHCPINFORM:
+ dhcpinform (packet, ms_nulltp);
+ break;
+
+ case DHCPLEASEQUERY:
+ dhcpleasequery(packet, ms_nulltp);
+ break;
+
+ case DHCPACK:
+ case DHCPOFFER:
+ case DHCPNAK:
+ case DHCPLEASEUNASSIGNED:
+ case DHCPLEASEUNKNOWN:
+ case DHCPLEASEACTIVE:
+ break;
+
+ default:
+ errmsg = "unknown packet type";
+ goto bad_packet;
+ }
+ out:
+ if (lease)
+ lease_dereference (&lease, MDL);
+}
+
+void dhcpdiscover (packet, ms_nulltp)
+ struct packet *packet;
+ int ms_nulltp;
+{
+ struct lease *lease = (struct lease *)0;
+ char msgbuf [1024]; /* XXX */
+ TIME when;
+ const char *s;
+ int peer_has_leases = 0;
+#if defined (FAILOVER_PROTOCOL)
+ dhcp_failover_state_t *peer;
+#endif
+
+ find_lease (&lease, packet, packet -> shared_network,
+ 0, &peer_has_leases, (struct lease *)0, MDL);
+
+ if (lease && lease -> client_hostname) {
+ if ((strlen (lease -> client_hostname) <= 64) &&
+ db_printable((unsigned char *)lease->client_hostname))
+ s = lease -> client_hostname;
+ else
+ s = "Hostname Unsuitable for Printing";
+ } else
+ s = (char *)0;
+
+ /* %Audit% This is log output. %2004.06.17,Safe%
+ * If we truncate we hope the user can get a hint from the log.
+ */
+ snprintf (msgbuf, sizeof msgbuf, "DHCPDISCOVER from %s %s%s%svia %s",
+ (packet -> raw -> htype
+ ? print_hw_addr (packet -> raw -> htype,
+ packet -> raw -> hlen,
+ packet -> raw -> chaddr)
+ : (lease
+ ? print_hex_1(lease->uid_len, lease->uid, 60)
+ : "<no identifier>")),
+ s ? "(" : "", s ? s : "", s ? ") " : "",
+ packet -> raw -> giaddr.s_addr
+ ? inet_ntoa (packet -> raw -> giaddr)
+ : packet -> interface -> name);
+
+ /* Sourceless packets don't make sense here. */
+ if (!packet -> shared_network) {
+ log_info ("Packet from unknown subnet: %s",
+ inet_ntoa (packet -> raw -> giaddr));
+ goto out;
+ }
+
+#if defined (FAILOVER_PROTOCOL)
+ if (lease && lease -> pool && lease -> pool -> failover_peer) {
+ peer = lease -> pool -> failover_peer;
+
+ /*
+ * If the lease is ours to (re)allocate, then allocate it.
+ *
+ * If the lease is active, it belongs to the client. This
+ * is the right lease, if we are to offer one. We decide
+ * whether or not to offer later on.
+ *
+ * If the lease was last active, and we've reached this
+ * point, then it was last active with the same client. We
+ * can safely re-activate the lease with this client.
+ */
+ if (lease->binding_state == FTS_ACTIVE ||
+ lease->rewind_binding_state == FTS_ACTIVE ||
+ lease_mine_to_reallocate(lease)) {
+ ; /* This space intentionally left blank. */
+
+ /* Otherwise, we can't let the client have this lease. */
+ } else {
+#if defined (DEBUG_FIND_LEASE)
+ log_debug ("discarding %s - %s",
+ piaddr (lease -> ip_addr),
+ binding_state_print (lease -> binding_state));
+#endif
+ lease_dereference (&lease, MDL);
+ }
+ }
+#endif
+
+ /* If we didn't find a lease, try to allocate one... */
+ if (!lease) {
+ if (!allocate_lease (&lease, packet,
+ packet -> shared_network -> pools,
+ &peer_has_leases)) {
+ if (peer_has_leases)
+ log_error ("%s: peer holds all free leases",
+ msgbuf);
+ else
+ log_error ("%s: network %s: no free leases",
+ msgbuf,
+ packet -> shared_network -> name);
+ return;
+ }
+ }
+
+#if defined (FAILOVER_PROTOCOL)
+ if (lease && lease -> pool && lease -> pool -> failover_peer) {
+ peer = lease -> pool -> failover_peer;
+ if (peer -> service_state == not_responding ||
+ peer -> service_state == service_startup) {
+ log_info ("%s: not responding%s",
+ msgbuf, peer -> nrr);
+ goto out;
+ }
+ } else
+ peer = (dhcp_failover_state_t *)0;
+
+ /* Do load balancing if configured. */
+ if (peer && (peer -> service_state == cooperating) &&
+ !load_balance_mine (packet, peer)) {
+ if (peer_has_leases) {
+ log_debug ("%s: load balance to peer %s",
+ msgbuf, peer -> name);
+ goto out;
+ } else {
+ log_debug ("%s: cancel load balance to peer %s - %s",
+ msgbuf, peer -> name, "no free leases");
+ }
+ }
+#endif
+
+ /* If it's an expired lease, get rid of any bindings. */
+ if (lease -> ends < cur_time && lease -> scope)
+ binding_scope_dereference (&lease -> scope, MDL);
+
+ /* Set the lease to really expire in 2 minutes, unless it has
+ not yet expired, in which case leave its expiry time alone. */
+ when = cur_time + 120;
+ if (when < lease -> ends)
+ when = lease -> ends;
+
+ ack_lease (packet, lease, DHCPOFFER, when, msgbuf, ms_nulltp,
+ (struct host_decl *)0);
+ out:
+ if (lease)
+ lease_dereference (&lease, MDL);
+}
+
+void dhcprequest (packet, ms_nulltp, ip_lease)
+ struct packet *packet;
+ int ms_nulltp;
+ struct lease *ip_lease;
+{
+ struct lease *lease;
+ struct iaddr cip;
+ struct iaddr sip;
+ struct subnet *subnet;
+ int ours = 0;
+ struct option_cache *oc;
+ struct data_string data;
+ char msgbuf [1024]; /* XXX */
+ const char *s;
+ char smbuf [19];
+#if defined (FAILOVER_PROTOCOL)
+ dhcp_failover_state_t *peer;
+#endif
+ int have_requested_addr = 0;
+
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_REQUESTED_ADDRESS);
+ memset (&data, 0, sizeof data);
+ if (oc &&
+ evaluate_option_cache (&data, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ cip.len = 4;
+ memcpy (cip.iabuf, data.data, 4);
+ data_string_forget (&data, MDL);
+ have_requested_addr = 1;
+ } else {
+ oc = (struct option_cache *)0;
+ cip.len = 4;
+ memcpy (cip.iabuf, &packet -> raw -> ciaddr.s_addr, 4);
+ }
+
+ /* Find the lease that matches the address requested by the
+ client. */
+
+ subnet = (struct subnet *)0;
+ lease = (struct lease *)0;
+ if (find_subnet (&subnet, cip, MDL))
+ find_lease (&lease, packet,
+ subnet -> shared_network, &ours, 0, ip_lease, MDL);
+
+ if (lease && lease -> client_hostname) {
+ if ((strlen (lease -> client_hostname) <= 64) &&
+ db_printable((unsigned char *)lease->client_hostname))
+ s = lease -> client_hostname;
+ else
+ s = "Hostname Unsuitable for Printing";
+ } else
+ s = (char *)0;
+
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ memset (&data, 0, sizeof data);
+ if (oc &&
+ evaluate_option_cache (&data, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ sip.len = 4;
+ memcpy (sip.iabuf, data.data, 4);
+ data_string_forget (&data, MDL);
+ /* piaddr() should not return more than a 15 byte string.
+ * safe.
+ */
+ sprintf (smbuf, " (%s)", piaddr (sip));
+ } else {
+ smbuf [0] = 0;
+ sip.len = 0;
+ }
+
+ /* %Audit% This is log output. %2004.06.17,Safe%
+ * If we truncate we hope the user can get a hint from the log.
+ */
+ snprintf (msgbuf, sizeof msgbuf,
+ "DHCPREQUEST for %s%s from %s %s%s%svia %s",
+ piaddr (cip), smbuf,
+ (packet -> raw -> htype
+ ? print_hw_addr (packet -> raw -> htype,
+ packet -> raw -> hlen,
+ packet -> raw -> chaddr)
+ : (lease
+ ? print_hex_1(lease->uid_len, lease->uid, 60)
+ : "<no identifier>")),
+ s ? "(" : "", s ? s : "", s ? ") " : "",
+ packet -> raw -> giaddr.s_addr
+ ? inet_ntoa (packet -> raw -> giaddr)
+ : packet -> interface -> name);
+
+#if defined (FAILOVER_PROTOCOL)
+ if (lease && lease -> pool && lease -> pool -> failover_peer) {
+ peer = lease -> pool -> failover_peer;
+ if (peer -> service_state == not_responding ||
+ peer -> service_state == service_startup) {
+ log_info ("%s: not responding%s",
+ msgbuf, peer -> nrr);
+ goto out;
+ }
+
+ /* "load balance to peer" - is not done at all for request.
+ *
+ * If it's RENEWING, we are the only server to hear it, so
+ * we have to serve it. If it's REBINDING, it's out of
+ * communication with the other server, so there's no point
+ * in waiting to serve it. However, if the lease we're
+ * offering is not a free lease, then we may be the only
+ * server that can offer it, so we can't load balance if
+ * the lease isn't in the free or backup state. If it is
+ * in the free or backup state, then that state is what
+ * mandates one server or the other should perform the
+ * allocation, not the LBA...we know the peer cannot
+ * allocate a request for an address in our free state.
+ *
+ * So our only compass is lease_mine_to_reallocate(). This
+ * effects both load balancing, and a sanity-check that we
+ * are not going to try to allocate a lease that isn't ours.
+ */
+ if ((lease -> binding_state == FTS_FREE ||
+ lease -> binding_state == FTS_BACKUP) &&
+ !lease_mine_to_reallocate (lease)) {
+ log_debug ("%s: lease owned by peer", msgbuf);
+ goto out;
+ }
+
+ /*
+ * If the lease is in a transitional state, we can't
+ * renew it unless we can rewind it to a non-transitional
+ * state (active, free, or backup). lease_mine_to_reallocate()
+ * checks for free/backup, so we only need to check for active.
+ */
+ if ((lease->binding_state == FTS_RELEASED ||
+ lease->binding_state == FTS_EXPIRED) &&
+ lease->rewind_binding_state != FTS_ACTIVE &&
+ !lease_mine_to_reallocate(lease)) {
+ log_debug("%s: lease in transition state %s", msgbuf,
+ (lease->binding_state == FTS_RELEASED)
+ ? "released" : "expired");
+ goto out;
+ }
+
+ /* It's actually very unlikely that we'll ever get here,
+ but if we do, tell the client to stop using the lease,
+ because the administrator reset it. */
+ if (lease -> binding_state == FTS_RESET &&
+ !lease_mine_to_reallocate (lease)) {
+ log_debug ("%s: lease reset by administrator", msgbuf);
+ nak_lease (packet, &cip);
+ goto out;
+ }
+
+#if defined(SERVER_ID_CHECK)
+ /* Do a quick check on the server source address to see if
+ it is ours. sip is the incoming servrer id. To avoid
+ problems with confused clients we do some sanity checks
+ to verify sip's length and that it isn't all zeros.
+ We then get the server id we would likely use for this
+ packet and compare them. If they don't match it we assume
+ we didn't send the offer and so we don't process the request.
+ */
+
+ if ((sip.len == 4) &&
+ (memcmp(sip.iabuf, "\0\0\0\0", sip.len) != 0)) {
+ struct in_addr from;
+ setup_server_source_address(&from, NULL, packet);
+ if (memcmp(sip.iabuf, &from, sip.len) != 0) {
+ log_debug("%s: not our server id", msgbuf);
+ goto out;
+ }
+ }
+#endif /* if defined(SERVER_ID_CHECK) */
+
+ /* At this point it's possible that we will get a broadcast
+ DHCPREQUEST for a lease that we didn't offer, because
+ both we and the peer are in a position to offer it.
+ In that case, we probably shouldn't answer. In order
+ to not answer, we would have to compare the server
+ identifier sent by the client with the list of possible
+ server identifiers we can send, and if the client's
+ identifier isn't on the list, drop the DHCPREQUEST.
+ We aren't currently doing that for two reasons - first,
+ it's not clear that all clients do the right thing
+ with respect to sending the client identifier, which
+ could mean that we might simply not respond to a client
+ that is depending on us to respond. Secondly, we allow
+ the user to specify the server identifier to send, and
+ we don't enforce that the server identifier should be
+ one of our IP addresses. This is probably not a big
+ deal, but it's theoretically an issue.
+
+ The reason we care about this is that if both servers
+ send a DHCPACK to the DHCPREQUEST, they are then going
+ to send dueling BNDUPD messages, which could cause
+ trouble. I think it causes no harm, but it seems
+ wrong. */
+ } else
+ peer = (dhcp_failover_state_t *)0;
+#endif
+
+ /* If a client on a given network REQUESTs a lease on an
+ address on a different network, NAK it. If the Requested
+ Address option was used, the protocol says that it must
+ have been broadcast, so we can trust the source network
+ information.
+
+ If ciaddr was specified and Requested Address was not, then
+ we really only know for sure what network a packet came from
+ if it came through a BOOTP gateway - if it came through an
+ IP router, we'll just have to assume that it's cool.
+
+ If we don't think we know where the packet came from, it
+ came through a gateway from an unknown network, so it's not
+ from a RENEWING client. If we recognize the network it
+ *thinks* it's on, we can NAK it even though we don't
+ recognize the network it's *actually* on; otherwise we just
+ have to ignore it.
+
+ We don't currently try to take advantage of access to the
+ raw packet, because it's not available on all platforms.
+ So a packet that was unicast to us through a router from a
+ RENEWING client is going to look exactly like a packet that
+ was broadcast to us from an INIT-REBOOT client.
+
+ Since we can't tell the difference between these two kinds
+ of packets, if the packet appears to have come in off the
+ local wire, we have to treat it as if it's a RENEWING
+ client. This means that we can't NAK a RENEWING client on
+ the local wire that has a bogus address. The good news is
+ that we won't ACK it either, so it should revert to INIT
+ state and send us a DHCPDISCOVER, which we *can* work with.
+
+ Because we can't detect that a RENEWING client is on the
+ wrong wire, it's going to sit there trying to renew until
+ it gets to the REBIND state, when we *can* NAK it because
+ the packet will get to us through a BOOTP gateway. We
+ shouldn't actually see DHCPREQUEST packets from RENEWING
+ clients on the wrong wire anyway, since their idea of their
+ local router will be wrong. In any case, the protocol
+ doesn't really allow us to NAK a DHCPREQUEST from a
+ RENEWING client, so we can punt on this issue. */
+
+ if (!packet -> shared_network ||
+ (packet -> raw -> ciaddr.s_addr &&
+ packet -> raw -> giaddr.s_addr) ||
+ (have_requested_addr && !packet -> raw -> ciaddr.s_addr)) {
+
+ /* If we don't know where it came from but we do know
+ where it claims to have come from, it didn't come
+ from there. */
+ if (!packet -> shared_network) {
+ if (subnet && subnet -> group -> authoritative) {
+ log_info ("%s: wrong network.", msgbuf);
+ nak_lease (packet, &cip);
+ goto out;
+ }
+ /* Otherwise, ignore it. */
+ log_info ("%s: ignored (%s).", msgbuf,
+ (subnet
+ ? "not authoritative" : "unknown subnet"));
+ goto out;
+ }
+
+ /* If we do know where it came from and it asked for an
+ address that is not on that shared network, nak it. */
+ if (subnet)
+ subnet_dereference (&subnet, MDL);
+ if (!find_grouped_subnet (&subnet, packet -> shared_network,
+ cip, MDL)) {
+ if (packet -> shared_network -> group -> authoritative)
+ {
+ log_info ("%s: wrong network.", msgbuf);
+ nak_lease (packet, &cip);
+ goto out;
+ }
+ log_info ("%s: ignored (not authoritative).", msgbuf);
+ return;
+ }
+ }
+
+ /* If the address the client asked for is ours, but it wasn't
+ available for the client, NAK it. */
+ if (!lease && ours) {
+ log_info ("%s: lease %s unavailable.", msgbuf, piaddr (cip));
+ nak_lease (packet, &cip);
+ goto out;
+ }
+
+ /* Otherwise, send the lease to the client if we found one. */
+ if (lease) {
+ ack_lease (packet, lease, DHCPACK, 0, msgbuf, ms_nulltp,
+ (struct host_decl *)0);
+ } else
+ log_info ("%s: unknown lease %s.", msgbuf, piaddr (cip));
+
+ out:
+ if (subnet)
+ subnet_dereference (&subnet, MDL);
+ if (lease)
+ lease_dereference (&lease, MDL);
+ return;
+}
+
+void dhcprelease (packet, ms_nulltp)
+ struct packet *packet;
+ int ms_nulltp;
+{
+ struct lease *lease = (struct lease *)0, *next = (struct lease *)0;
+ struct iaddr cip;
+ struct option_cache *oc;
+ struct data_string data;
+ const char *s;
+ char msgbuf [1024], cstr[16]; /* XXX */
+
+
+ /* DHCPRELEASE must not specify address in requested-address
+ option, but old protocol specs weren't explicit about this,
+ so let it go. */
+ if ((oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_REQUESTED_ADDRESS))) {
+ log_info ("DHCPRELEASE from %s specified requested-address.",
+ print_hw_addr (packet -> raw -> htype,
+ packet -> raw -> hlen,
+ packet -> raw -> chaddr));
+ }
+
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_CLIENT_IDENTIFIER);
+ memset (&data, 0, sizeof data);
+ if (oc &&
+ evaluate_option_cache (&data, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ find_lease_by_uid (&lease, data.data, data.len, MDL);
+ data_string_forget (&data, MDL);
+
+ /* See if we can find a lease that matches the IP address
+ the client is claiming. */
+ while (lease) {
+ if (lease -> n_uid)
+ lease_reference (&next, lease -> n_uid, MDL);
+ if (!memcmp (&packet -> raw -> ciaddr,
+ lease -> ip_addr.iabuf, 4)) {
+ break;
+ }
+ lease_dereference (&lease, MDL);
+ if (next) {
+ lease_reference (&lease, next, MDL);
+ lease_dereference (&next, MDL);
+ }
+ }
+ if (next)
+ lease_dereference (&next, MDL);
+ }
+
+ /* The client is supposed to pass a valid client-identifier,
+ but the spec on this has changed historically, so try the
+ IP address in ciaddr if the client-identifier fails. */
+ if (!lease) {
+ cip.len = 4;
+ memcpy (cip.iabuf, &packet -> raw -> ciaddr, 4);
+ find_lease_by_ip_addr (&lease, cip, MDL);
+ }
+
+
+ /* If the hardware address doesn't match, don't do the release. */
+ if (lease &&
+ (lease -> hardware_addr.hlen != packet -> raw -> hlen + 1 ||
+ lease -> hardware_addr.hbuf [0] != packet -> raw -> htype ||
+ memcmp (&lease -> hardware_addr.hbuf [1],
+ packet -> raw -> chaddr, packet -> raw -> hlen)))
+ lease_dereference (&lease, MDL);
+
+ if (lease && lease -> client_hostname) {
+ if ((strlen (lease -> client_hostname) <= 64) &&
+ db_printable((unsigned char *)lease->client_hostname))
+ s = lease -> client_hostname;
+ else
+ s = "Hostname Unsuitable for Printing";
+ } else
+ s = (char *)0;
+
+ /* %Audit% Cannot exceed 16 bytes. %2004.06.17,Safe%
+ * We copy this out to stack because we actually want to log two
+ * inet_ntoa()'s in this message.
+ */
+ strncpy(cstr, inet_ntoa (packet -> raw -> ciaddr), 15);
+ cstr[15] = '\0';
+
+ /* %Audit% This is log output. %2004.06.17,Safe%
+ * If we truncate we hope the user can get a hint from the log.
+ */
+ snprintf (msgbuf, sizeof msgbuf,
+ "DHCPRELEASE of %s from %s %s%s%svia %s (%sfound)",
+ cstr,
+ (packet -> raw -> htype
+ ? print_hw_addr (packet -> raw -> htype,
+ packet -> raw -> hlen,
+ packet -> raw -> chaddr)
+ : (lease
+ ? print_hex_1(lease->uid_len, lease->uid, 60)
+ : "<no identifier>")),
+ s ? "(" : "", s ? s : "", s ? ") " : "",
+ packet -> raw -> giaddr.s_addr
+ ? inet_ntoa (packet -> raw -> giaddr)
+ : packet -> interface -> name,
+ lease ? "" : "not ");
+
+#if defined (FAILOVER_PROTOCOL)
+ if (lease && lease -> pool && lease -> pool -> failover_peer) {
+ dhcp_failover_state_t *peer = lease -> pool -> failover_peer;
+ if (peer -> service_state == not_responding ||
+ peer -> service_state == service_startup) {
+ log_info ("%s: ignored%s",
+ peer -> name, peer -> nrr);
+ goto out;
+ }
+
+ /* DHCPRELEASE messages are unicast, so if the client
+ sent the DHCPRELEASE to us, it's not going to send it
+ to the peer. Not sure why this would happen, and
+ if it does happen I think we still have to change the
+ lease state, so that's what we're doing.
+ XXX See what it says in the draft about this. */
+ }
+#endif
+
+ /* If we found a lease, release it. */
+ if (lease && lease -> ends > cur_time) {
+ release_lease (lease, packet);
+ }
+ log_info ("%s", msgbuf);
+#if defined(FAILOVER_PROTOCOL)
+ out:
+#endif
+ if (lease)
+ lease_dereference (&lease, MDL);
+}
+
+void dhcpdecline (packet, ms_nulltp)
+ struct packet *packet;
+ int ms_nulltp;
+{
+ struct lease *lease = (struct lease *)0;
+ struct option_state *options = (struct option_state *)0;
+ int ignorep = 0;
+ int i;
+ const char *status;
+ const char *s;
+ char msgbuf [1024]; /* XXX */
+ struct iaddr cip;
+ struct option_cache *oc;
+ struct data_string data;
+
+ /* DHCPDECLINE must specify address. */
+ if (!(oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_REQUESTED_ADDRESS)))
+ return;
+ memset (&data, 0, sizeof data);
+ if (!evaluate_option_cache (&data, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL))
+ return;
+
+ cip.len = 4;
+ memcpy (cip.iabuf, data.data, 4);
+ data_string_forget (&data, MDL);
+ find_lease_by_ip_addr (&lease, cip, MDL);
+
+ if (lease && lease -> client_hostname) {
+ if ((strlen (lease -> client_hostname) <= 64) &&
+ db_printable((unsigned char *)lease->client_hostname))
+ s = lease -> client_hostname;
+ else
+ s = "Hostname Unsuitable for Printing";
+ } else
+ s = (char *)0;
+
+ /* %Audit% This is log output. %2004.06.17,Safe%
+ * If we truncate we hope the user can get a hint from the log.
+ */
+ snprintf (msgbuf, sizeof msgbuf,
+ "DHCPDECLINE of %s from %s %s%s%svia %s",
+ piaddr (cip),
+ (packet -> raw -> htype
+ ? print_hw_addr (packet -> raw -> htype,
+ packet -> raw -> hlen,
+ packet -> raw -> chaddr)
+ : (lease
+ ? print_hex_1(lease->uid_len, lease->uid, 60)
+ : "<no identifier>")),
+ s ? "(" : "", s ? s : "", s ? ") " : "",
+ packet -> raw -> giaddr.s_addr
+ ? inet_ntoa (packet -> raw -> giaddr)
+ : packet -> interface -> name);
+
+ option_state_allocate (&options, MDL);
+
+ /* Execute statements in scope starting with the subnet scope. */
+ if (lease)
+ execute_statements_in_scope ((struct binding_value **)0,
+ packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, options,
+ &global_scope,
+ lease -> subnet -> group,
+ (struct group *)0);
+
+ /* Execute statements in the class scopes. */
+ for (i = packet -> class_count; i > 0; i--) {
+ execute_statements_in_scope
+ ((struct binding_value **)0, packet, (struct lease *)0,
+ (struct client_state *)0, packet -> options, options,
+ &global_scope, packet -> classes [i - 1] -> group,
+ lease ? lease -> subnet -> group : (struct group *)0);
+ }
+
+ /* Drop the request if dhcpdeclines are being ignored. */
+ oc = lookup_option (&server_universe, options, SV_DECLINES);
+ if (!oc ||
+ evaluate_boolean_option_cache (&ignorep, packet, lease,
+ (struct client_state *)0,
+ packet -> options, options,
+ &lease -> scope, oc, MDL)) {
+ /* If we found a lease, mark it as unusable and complain. */
+ if (lease) {
+#if defined (FAILOVER_PROTOCOL)
+ if (lease -> pool && lease -> pool -> failover_peer) {
+ dhcp_failover_state_t *peer =
+ lease -> pool -> failover_peer;
+ if (peer -> service_state == not_responding ||
+ peer -> service_state == service_startup) {
+ if (!ignorep)
+ log_info ("%s: ignored%s",
+ peer -> name, peer -> nrr);
+ goto out;
+ }
+
+ /* DHCPDECLINE messages are broadcast, so we can safely
+ ignore the DHCPDECLINE if the peer has the lease.
+ XXX Of course, at this point that information has been
+ lost. */
+ }
+#endif
+
+ abandon_lease (lease, "declined.");
+ status = "abandoned";
+ } else {
+ status = "not found";
+ }
+ } else
+ status = "ignored";
+
+ if (!ignorep)
+ log_info ("%s: %s", msgbuf, status);
+
+#if defined(FAILOVER_PROTOCOL)
+ out:
+#endif
+ if (options)
+ option_state_dereference (&options, MDL);
+ if (lease)
+ lease_dereference (&lease, MDL);
+}
+
+void dhcpinform (packet, ms_nulltp)
+ struct packet *packet;
+ int ms_nulltp;
+{
+ char msgbuf [1024];
+ struct data_string d1, prl;
+ struct option_cache *oc;
+ struct option_state *options = (struct option_state *)0;
+ struct dhcp_packet raw;
+ struct packet outgoing;
+ unsigned char dhcpack = DHCPACK;
+ struct subnet *subnet = NULL;
+ struct iaddr cip, gip;
+ unsigned i;
+ int nulltp;
+ struct sockaddr_in to;
+ struct in_addr from;
+ isc_boolean_t zeroed_ciaddr;
+ struct interface_info *interface;
+ int result;
+
+ /* The client should set ciaddr to its IP address, but apparently
+ it's common for clients not to do this, so we'll use their IP
+ source address if they didn't set ciaddr. */
+ if (!packet -> raw -> ciaddr.s_addr) {
+ zeroed_ciaddr = ISC_TRUE;
+ cip.len = 4;
+ memcpy (cip.iabuf, &packet -> client_addr.iabuf, 4);
+ } else {
+ zeroed_ciaddr = ISC_FALSE;
+ cip.len = 4;
+ memcpy (cip.iabuf, &packet -> raw -> ciaddr, 4);
+ }
+
+ if (packet->raw->giaddr.s_addr) {
+ gip.len = 4;
+ memcpy(gip.iabuf, &packet->raw->giaddr, 4);
+ } else
+ gip.len = 0;
+
+ /* %Audit% This is log output. %2004.06.17,Safe%
+ * If we truncate we hope the user can get a hint from the log.
+ */
+ snprintf (msgbuf, sizeof msgbuf, "DHCPINFORM from %s via %s",
+ piaddr (cip), packet->raw->giaddr.s_addr ?
+ inet_ntoa(packet->raw->giaddr) :
+ packet -> interface -> name);
+
+ /* If the IP source address is zero, don't respond. */
+ if (!memcmp (cip.iabuf, "\0\0\0", 4)) {
+ log_info ("%s: ignored (null source address).", msgbuf);
+ return;
+ }
+
+ /* Find the subnet that the client is on. */
+ if (zeroed_ciaddr && (gip.len != 0)) {
+ /* XXX - do subnet selection relay agent suboption here */
+ find_subnet(&subnet, gip, MDL);
+
+ if (subnet == NULL) {
+ log_info("%s: unknown subnet for relay address %s",
+ msgbuf, piaddr(gip));
+ return;
+ }
+ } else {
+ /* XXX - do subnet selection (not relay agent) option here */
+ find_subnet(&subnet, cip, MDL);
+
+ if (subnet == NULL) {
+ log_info("%s: unknown subnet for %s address %s",
+ msgbuf, zeroed_ciaddr ? "source" : "client",
+ piaddr(cip));
+ return;
+ }
+ }
+
+ /* We don't respond to DHCPINFORM packets if we're not authoritative.
+ It would be nice if a per-host value could override this, but
+ there's overhead involved in checking this, so let's see how people
+ react first. */
+ if (subnet && !subnet -> group -> authoritative) {
+ static int eso = 0;
+ log_info ("%s: not authoritative for subnet %s",
+ msgbuf, piaddr (subnet -> net));
+ if (!eso) {
+ log_info ("If this DHCP server is authoritative for%s",
+ " that subnet,");
+ log_info ("please write an `authoritative;' directi%s",
+ "ve either in the");
+ log_info ("subnet declaration or in some scope that%s",
+ " encloses the");
+ log_info ("subnet declaration - for example, write %s",
+ "it at the top");
+ log_info ("of the dhcpd.conf file.");
+ }
+ if (eso++ == 100)
+ eso = 0;
+ subnet_dereference (&subnet, MDL);
+ return;
+ }
+
+ option_state_allocate (&options, MDL);
+ memset (&outgoing, 0, sizeof outgoing);
+ memset (&raw, 0, sizeof raw);
+ outgoing.raw = &raw;
+
+ maybe_return_agent_options(packet, options);
+
+ /* Execute statements in scope starting with the subnet scope. */
+ if (subnet)
+ execute_statements_in_scope ((struct binding_value **)0,
+ packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, options,
+ &global_scope, subnet -> group,
+ (struct group *)0);
+
+ /* Execute statements in the class scopes. */
+ for (i = packet -> class_count; i > 0; i--) {
+ execute_statements_in_scope
+ ((struct binding_value **)0, packet, (struct lease *)0,
+ (struct client_state *)0, packet -> options, options,
+ &global_scope, packet -> classes [i - 1] -> group,
+ subnet ? subnet -> group : (struct group *)0);
+ }
+
+ /* Figure out the filename. */
+ memset (&d1, 0, sizeof d1);
+ oc = lookup_option (&server_universe, options, SV_FILENAME);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ i = d1.len;
+ if (i >= sizeof(raw.file)) {
+ log_info("file name longer than packet field "
+ "truncated - field: %lu name: %d %.*s",
+ (unsigned long)sizeof(raw.file), i, i,
+ d1.data);
+ i = sizeof(raw.file);
+ } else
+ raw.file[i] = 0;
+ memcpy (raw.file, d1.data, i);
+ data_string_forget (&d1, MDL);
+ }
+
+ /* Choose a server name as above. */
+ oc = lookup_option (&server_universe, options, SV_SERVER_NAME);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ i = d1.len;
+ if (i >= sizeof(raw.sname)) {
+ log_info("server name longer than packet field "
+ "truncated - field: %lu name: %d %.*s",
+ (unsigned long)sizeof(raw.sname), i, i,
+ d1.data);
+ i = sizeof(raw.sname);
+ } else
+ raw.sname[i] = 0;
+ memcpy (raw.sname, d1.data, i);
+ data_string_forget (&d1, MDL);
+ }
+
+ /* Set a flag if this client is a lame Microsoft client that NUL
+ terminates string options and expects us to do likewise. */
+ nulltp = 0;
+ if ((oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_HOST_NAME))) {
+ if (!oc->expression)
+ nulltp = oc->flags & OPTION_HAD_NULLS;
+ }
+
+ /* Put in DHCP-specific options. */
+ i = DHO_DHCP_MESSAGE_TYPE;
+ oc = (struct option_cache *)0;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data (&oc -> expression,
+ &dhcpack, 1, 0, 0, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe, options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+
+ get_server_source_address(&from, options, options, packet);
+
+ /* Use the subnet mask from the subnet declaration if no other
+ mask has been provided. */
+ i = DHO_SUBNET_MASK;
+ if (subnet && !lookup_option (&dhcp_universe, options, i)) {
+ oc = (struct option_cache *)0;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data (&oc -> expression,
+ subnet -> netmask.iabuf,
+ subnet -> netmask.len,
+ 0, 0, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe, options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+ }
+
+ /* If a site option space has been specified, use that for
+ site option codes. */
+ i = SV_SITE_OPTION_SPACE;
+ if ((oc = lookup_option (&server_universe, options, i)) &&
+ evaluate_option_cache (&d1, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, options,
+ &global_scope, oc, MDL)) {
+ struct universe *u = (struct universe *)0;
+
+ if (!universe_hash_lookup (&u, universe_hash,
+ (const char *)d1.data, d1.len,
+ MDL)) {
+ log_error ("unknown option space %s.", d1.data);
+ option_state_dereference (&options, MDL);
+ if (subnet)
+ subnet_dereference (&subnet, MDL);
+ return;
+ }
+
+ options -> site_universe = u -> index;
+ options->site_code_min = find_min_site_code(u);
+ data_string_forget (&d1, MDL);
+ } else {
+ options -> site_universe = dhcp_universe.index;
+ options -> site_code_min = 0; /* Trust me, it works. */
+ }
+
+ memset (&prl, 0, sizeof prl);
+
+ /* Use the parameter list from the scope if there is one. */
+ oc = lookup_option (&dhcp_universe, options,
+ DHO_DHCP_PARAMETER_REQUEST_LIST);
+
+ /* Otherwise, if the client has provided a list of options
+ that it wishes returned, use it to prioritize. Otherwise,
+ prioritize based on the default priority list. */
+
+ if (!oc)
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_PARAMETER_REQUEST_LIST);
+
+ if (oc)
+ evaluate_option_cache (&prl, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, options,
+ &global_scope, oc, MDL);
+
+#ifdef DEBUG_PACKET
+ dump_packet (packet);
+ dump_raw ((unsigned char *)packet -> raw, packet -> packet_length);
+#endif
+
+ log_info ("%s", msgbuf);
+
+ /* Figure out the address of the boot file server. */
+ if ((oc =
+ lookup_option (&server_universe, options, SV_NEXT_SERVER))) {
+ if (evaluate_option_cache (&d1, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, options,
+ &global_scope, oc, MDL)) {
+ /* If there was more than one answer,
+ take the first. */
+ if (d1.len >= 4 && d1.data)
+ memcpy (&raw.siaddr, d1.data, 4);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ /*
+ * Remove any time options, per section 3.4 RFC 2131
+ */
+ delete_option(&dhcp_universe, options, DHO_DHCP_LEASE_TIME);
+ delete_option(&dhcp_universe, options, DHO_DHCP_RENEWAL_TIME);
+ delete_option(&dhcp_universe, options, DHO_DHCP_REBINDING_TIME);
+
+ /* Set up the option buffer... */
+ outgoing.packet_length =
+ cons_options (packet, outgoing.raw, (struct lease *)0,
+ (struct client_state *)0,
+ 0, packet -> options, options, &global_scope,
+ 0, nulltp, 0,
+ prl.len ? &prl : (struct data_string *)0,
+ (char *)0);
+ option_state_dereference (&options, MDL);
+ data_string_forget (&prl, MDL);
+
+ /* Make sure that the packet is at least as big as a BOOTP packet. */
+ if (outgoing.packet_length < BOOTP_MIN_LEN)
+ outgoing.packet_length = BOOTP_MIN_LEN;
+
+ raw.giaddr = packet -> raw -> giaddr;
+ raw.ciaddr = packet -> raw -> ciaddr;
+ memcpy (raw.chaddr, packet -> raw -> chaddr, sizeof raw.chaddr);
+ raw.hlen = packet -> raw -> hlen;
+ raw.htype = packet -> raw -> htype;
+
+ raw.xid = packet -> raw -> xid;
+ raw.secs = packet -> raw -> secs;
+ raw.flags = packet -> raw -> flags;
+ raw.hops = packet -> raw -> hops;
+ raw.op = BOOTREPLY;
+
+#ifdef DEBUG_PACKET
+ dump_packet (&outgoing);
+ dump_raw ((unsigned char *)&raw, outgoing.packet_length);
+#endif
+
+ /* Set up the common stuff... */
+ to.sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+ to.sin_len = sizeof to;
+#endif
+ memset (to.sin_zero, 0, sizeof to.sin_zero);
+
+ /* RFC2131 states the server SHOULD unciast to ciaddr.
+ * There are two wrinkles - relays, and when ciaddr is zero.
+ * There's actually no mention of relays at all in rfc2131 in
+ * regard to DHCPINFORM, except to say we might get packets from
+ * clients via them. Note: relays unicast to clients to the
+ * "yiaddr" address, which servers are forbidden to set when
+ * answering an inform.
+ *
+ * The solution: If ciaddr is zero, and giaddr is set, go via the
+ * relay with the broadcast flag set to help the relay (with no
+ * yiaddr and very likely no chaddr, it will have no idea where to
+ * send the packet).
+ *
+ * If the ciaddr is zero and giaddr is not set, go via the source
+ * IP address (but you are permitted to barf on their shoes).
+ *
+ * If ciaddr is not zero, send the packet there always.
+ */
+ if (!raw.ciaddr.s_addr && gip.len) {
+ memcpy(&to.sin_addr, gip.iabuf, 4);
+ to.sin_port = local_port;
+ raw.flags |= htons(BOOTP_BROADCAST);
+ } else {
+ gip.len = 0;
+ memcpy(&to.sin_addr, cip.iabuf, 4);
+ to.sin_port = remote_port;
+ }
+
+ /* Report what we're sending. */
+ snprintf(msgbuf, sizeof msgbuf, "DHCPACK to %s (%s) via", piaddr(cip),
+ (packet->raw->htype && packet->raw->hlen) ?
+ print_hw_addr(packet->raw->htype, packet->raw->hlen,
+ packet->raw->chaddr) :
+ "<no client hardware address>");
+ log_info("%s %s", msgbuf, gip.len ? piaddr(gip) :
+ packet->interface->name);
+
+ errno = 0;
+ interface = (fallback_interface ? fallback_interface
+ : packet -> interface);
+ result = send_packet(interface, &outgoing, &raw,
+ outgoing.packet_length, from, &to, NULL);
+ if (result < 0) {
+ log_error ("%s:%d: Failed to send %d byte long packet over %s "
+ "interface.", MDL, outgoing.packet_length,
+ interface->name);
+ }
+
+
+ if (subnet)
+ subnet_dereference (&subnet, MDL);
+}
+
+void nak_lease (packet, cip)
+ struct packet *packet;
+ struct iaddr *cip;
+{
+ struct sockaddr_in to;
+ struct in_addr from;
+ int result;
+ struct dhcp_packet raw;
+ unsigned char nak = DHCPNAK;
+ struct packet outgoing;
+ unsigned i;
+ struct option_state *options = (struct option_state *)0;
+ struct option_cache *oc = (struct option_cache *)0;
+
+ option_state_allocate (&options, MDL);
+ memset (&outgoing, 0, sizeof outgoing);
+ memset (&raw, 0, sizeof raw);
+ outgoing.raw = &raw;
+
+ /* Set DHCP_MESSAGE_TYPE to DHCPNAK */
+ if (!option_cache_allocate (&oc, MDL)) {
+ log_error ("No memory for DHCPNAK message type.");
+ option_state_dereference (&options, MDL);
+ return;
+ }
+ if (!make_const_data (&oc -> expression, &nak, sizeof nak,
+ 0, 0, MDL)) {
+ log_error ("No memory for expr_const expression.");
+ option_cache_dereference (&oc, MDL);
+ option_state_dereference (&options, MDL);
+ return;
+ }
+ i = DHO_DHCP_MESSAGE_TYPE;
+ option_code_hash_lookup(&oc->option, dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe, options, oc);
+ option_cache_dereference (&oc, MDL);
+
+ /* Set DHCP_MESSAGE to whatever the message is */
+ if (!option_cache_allocate (&oc, MDL)) {
+ log_error ("No memory for DHCPNAK message type.");
+ option_state_dereference (&options, MDL);
+ return;
+ }
+ if (!make_const_data (&oc -> expression,
+ (unsigned char *)dhcp_message,
+ strlen (dhcp_message), 1, 0, MDL)) {
+ log_error ("No memory for expr_const expression.");
+ option_cache_dereference (&oc, MDL);
+ option_state_dereference (&options, MDL);
+ return;
+ }
+ i = DHO_DHCP_MESSAGE;
+ option_code_hash_lookup(&oc->option, dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe, options, oc);
+ option_cache_dereference (&oc, MDL);
+
+ /*
+ * If we are configured to do so we try to find a server id
+ * option even for NAKS by calling setup_server_source_address().
+ * This function will set up an options list from the global
+ * and subnet scopes before trying to get the source address.
+ *
+ * Otherwise we simply call get_server_source_address()
+ * directly, without a server options list, this means
+ * we'll get the source address from the interface address.
+ */
+#if defined(SERVER_ID_FOR_NAK)
+ setup_server_source_address(&from, options, packet);
+#else
+ get_server_source_address(&from, NULL, options, packet);
+#endif /* if defined(SERVER_ID_FOR_NAK) */
+
+
+ /* If there were agent options in the incoming packet, return
+ * them. We do not check giaddr to detect the presence of a
+ * relay, as this excludes "l2" relay agents which have no
+ * giaddr to set.
+ */
+ if (packet->options->universe_count > agent_universe.index &&
+ packet->options->universes [agent_universe.index]) {
+ option_chain_head_reference
+ ((struct option_chain_head **)
+ &(options -> universes [agent_universe.index]),
+ (struct option_chain_head *)
+ packet -> options -> universes [agent_universe.index],
+ MDL);
+ }
+
+ /* Do not use the client's requested parameter list. */
+ delete_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_PARAMETER_REQUEST_LIST);
+
+ /* Set up the option buffer... */
+ outgoing.packet_length =
+ cons_options (packet, outgoing.raw, (struct lease *)0,
+ (struct client_state *)0,
+ 0, packet -> options, options, &global_scope,
+ 0, 0, 0, (struct data_string *)0, (char *)0);
+ option_state_dereference (&options, MDL);
+
+/* memset (&raw.ciaddr, 0, sizeof raw.ciaddr);*/
+ if (packet->interface->address_count)
+ raw.siaddr = packet->interface->addresses[0];
+ raw.giaddr = packet -> raw -> giaddr;
+ memcpy (raw.chaddr, packet -> raw -> chaddr, sizeof raw.chaddr);
+ raw.hlen = packet -> raw -> hlen;
+ raw.htype = packet -> raw -> htype;
+
+ raw.xid = packet -> raw -> xid;
+ raw.secs = packet -> raw -> secs;
+ raw.flags = packet -> raw -> flags | htons (BOOTP_BROADCAST);
+ raw.hops = packet -> raw -> hops;
+ raw.op = BOOTREPLY;
+
+ /* Report what we're sending... */
+ log_info ("DHCPNAK on %s to %s via %s",
+ piaddr (*cip),
+ print_hw_addr (packet -> raw -> htype,
+ packet -> raw -> hlen,
+ packet -> raw -> chaddr),
+ packet -> raw -> giaddr.s_addr
+ ? inet_ntoa (packet -> raw -> giaddr)
+ : packet -> interface -> name);
+
+#ifdef DEBUG_PACKET
+ dump_packet (packet);
+ dump_raw ((unsigned char *)packet -> raw, packet -> packet_length);
+ dump_packet (&outgoing);
+ dump_raw ((unsigned char *)&raw, outgoing.packet_length);
+#endif
+
+ /* Set up the common stuff... */
+ to.sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+ to.sin_len = sizeof to;
+#endif
+ memset (to.sin_zero, 0, sizeof to.sin_zero);
+
+ /* Make sure that the packet is at least as big as a BOOTP packet. */
+ if (outgoing.packet_length < BOOTP_MIN_LEN)
+ outgoing.packet_length = BOOTP_MIN_LEN;
+
+ /* If this was gatewayed, send it back to the gateway.
+ Otherwise, broadcast it on the local network. */
+ if (raw.giaddr.s_addr) {
+ to.sin_addr = raw.giaddr;
+ if (raw.giaddr.s_addr != htonl (INADDR_LOOPBACK))
+ to.sin_port = local_port;
+ else
+ to.sin_port = remote_port; /* for testing. */
+
+ if (fallback_interface) {
+ result = send_packet(fallback_interface, packet, &raw,
+ outgoing.packet_length, from, &to,
+ NULL);
+ if (result < 0) {
+ log_error ("%s:%d: Failed to send %d byte long "
+ "packet over %s interface.", MDL,
+ outgoing.packet_length,
+ fallback_interface->name);
+ }
+
+ return;
+ }
+ } else {
+ to.sin_addr = limited_broadcast;
+ to.sin_port = remote_port;
+ }
+
+ errno = 0;
+ result = send_packet(packet->interface, packet, &raw,
+ outgoing.packet_length, from, &to, NULL);
+ if (result < 0) {
+ log_error ("%s:%d: Failed to send %d byte long packet over %s "
+ "interface.", MDL, outgoing.packet_length,
+ packet->interface->name);
+ }
+
+}
+
+void ack_lease (packet, lease, offer, when, msg, ms_nulltp, hp)
+ struct packet *packet;
+ struct lease *lease;
+ unsigned int offer;
+ TIME when;
+ char *msg;
+ int ms_nulltp;
+ struct host_decl *hp;
+{
+ struct lease *lt;
+ struct lease_state *state;
+ struct lease *next;
+ struct host_decl *host = (struct host_decl *)0;
+ TIME lease_time;
+ TIME offered_lease_time;
+ struct data_string d1;
+ TIME min_lease_time;
+ TIME max_lease_time;
+ TIME default_lease_time;
+ struct option_cache *oc;
+ isc_result_t result;
+ TIME ping_timeout;
+ TIME lease_cltt;
+ struct in_addr from;
+ TIME remaining_time;
+ struct iaddr cip;
+
+ unsigned i, j;
+ int s1;
+ int ignorep;
+ struct timeval tv;
+
+ /* If we're already acking this lease, don't do it again. */
+ if (lease -> state)
+ return;
+
+ /* Save original cltt for comparison later. */
+ lease_cltt = lease->cltt;
+
+ /* If the lease carries a host record, remember it. */
+ if (hp)
+ host_reference (&host, hp, MDL);
+ else if (lease -> host)
+ host_reference (&host, lease -> host, MDL);
+
+ /* Allocate a lease state structure... */
+ state = new_lease_state (MDL);
+ if (!state)
+ log_fatal ("unable to allocate lease state!");
+ state -> got_requested_address = packet -> got_requested_address;
+ shared_network_reference (&state -> shared_network,
+ packet -> interface -> shared_network, MDL);
+
+ /* See if we got a server identifier option. */
+ if (lookup_option (&dhcp_universe,
+ packet -> options, DHO_DHCP_SERVER_IDENTIFIER))
+ state -> got_server_identifier = 1;
+
+ maybe_return_agent_options(packet, state->options);
+
+ /* If we are offering a lease that is still currently valid, preserve
+ the events. We need to do this because if the client does not
+ REQUEST our offer, it will expire in 2 minutes, overriding the
+ expire time in the currently in force lease. We want the expire
+ events to be executed at that point. */
+ if (lease -> ends <= cur_time && offer != DHCPOFFER) {
+ /* Get rid of any old expiry or release statements - by
+ executing the statements below, we will be inserting new
+ ones if there are any to insert. */
+ if (lease -> on_expiry)
+ executable_statement_dereference (&lease -> on_expiry,
+ MDL);
+ if (lease -> on_commit)
+ executable_statement_dereference (&lease -> on_commit,
+ MDL);
+ if (lease -> on_release)
+ executable_statement_dereference (&lease -> on_release,
+ MDL);
+ }
+
+ /* Execute statements in scope starting with the subnet scope. */
+ execute_statements_in_scope ((struct binding_value **)0,
+ packet, lease, (struct client_state *)0,
+ packet -> options,
+ state -> options, &lease -> scope,
+ lease -> subnet -> group,
+ (struct group *)0);
+
+ /* If the lease is from a pool, run the pool scope. */
+ if (lease -> pool)
+ (execute_statements_in_scope
+ ((struct binding_value **)0, packet, lease,
+ (struct client_state *)0, packet -> options,
+ state -> options, &lease -> scope, lease -> pool -> group,
+ lease -> pool -> shared_network -> group));
+
+ /* Execute statements from class scopes. */
+ for (i = packet -> class_count; i > 0; i--) {
+ execute_statements_in_scope
+ ((struct binding_value **)0,
+ packet, lease, (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, packet -> classes [i - 1] -> group,
+ (lease -> pool
+ ? lease -> pool -> group
+ : lease -> subnet -> group));
+ }
+
+ /* See if the client is only supposed to have one lease at a time,
+ and if so, find its other leases and release them. We can only
+ do this on DHCPREQUEST. It's a little weird to do this before
+ looking at permissions, because the client might not actually
+ _get_ a lease after we've done the permission check, but the
+ assumption for this option is that the client has exactly one
+ network interface, and will only ever remember one lease. So
+ if it sends a DHCPREQUEST, and doesn't get the lease, it's already
+ forgotten about its old lease, so we can too. */
+ if (packet -> packet_type == DHCPREQUEST &&
+ (oc = lookup_option (&server_universe, state -> options,
+ SV_ONE_LEASE_PER_CLIENT)) &&
+ evaluate_boolean_option_cache (&ignorep,
+ packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options, &lease -> scope,
+ oc, MDL)) {
+ struct lease *seek;
+ if (lease -> uid_len) {
+ do {
+ seek = (struct lease *)0;
+ find_lease_by_uid (&seek, lease -> uid,
+ lease -> uid_len, MDL);
+ if (!seek)
+ break;
+ if (seek == lease && !seek -> n_uid) {
+ lease_dereference (&seek, MDL);
+ break;
+ }
+ next = (struct lease *)0;
+
+ /* Don't release expired leases, and don't
+ release the lease we're going to assign. */
+ next = (struct lease *)0;
+ while (seek) {
+ if (seek -> n_uid)
+ lease_reference (&next, seek -> n_uid, MDL);
+ if (seek != lease &&
+ seek -> binding_state != FTS_RELEASED &&
+ seek -> binding_state != FTS_EXPIRED &&
+ seek -> binding_state != FTS_RESET &&
+ seek -> binding_state != FTS_FREE &&
+ seek -> binding_state != FTS_BACKUP)
+ break;
+ lease_dereference (&seek, MDL);
+ if (next) {
+ lease_reference (&seek, next, MDL);
+ lease_dereference (&next, MDL);
+ }
+ }
+ if (next)
+ lease_dereference (&next, MDL);
+ if (seek) {
+ release_lease (seek, packet);
+ lease_dereference (&seek, MDL);
+ } else
+ break;
+ } while (1);
+ }
+ if (!lease -> uid_len ||
+ (host &&
+ !host -> client_identifier.len &&
+ (oc = lookup_option (&server_universe, state -> options,
+ SV_DUPLICATES)) &&
+ !evaluate_boolean_option_cache (&ignorep, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope,
+ oc, MDL))) {
+ do {
+ seek = (struct lease *)0;
+ find_lease_by_hw_addr
+ (&seek, lease -> hardware_addr.hbuf,
+ lease -> hardware_addr.hlen, MDL);
+ if (!seek)
+ break;
+ if (seek == lease && !seek -> n_hw) {
+ lease_dereference (&seek, MDL);
+ break;
+ }
+ next = (struct lease *)0;
+ while (seek) {
+ if (seek -> n_hw)
+ lease_reference (&next, seek -> n_hw, MDL);
+ if (seek != lease &&
+ seek -> binding_state != FTS_RELEASED &&
+ seek -> binding_state != FTS_EXPIRED &&
+ seek -> binding_state != FTS_RESET &&
+ seek -> binding_state != FTS_FREE &&
+ seek -> binding_state != FTS_BACKUP)
+ break;
+ lease_dereference (&seek, MDL);
+ if (next) {
+ lease_reference (&seek, next, MDL);
+ lease_dereference (&next, MDL);
+ }
+ }
+ if (next)
+ lease_dereference (&next, MDL);
+ if (seek) {
+ release_lease (seek, packet);
+ lease_dereference (&seek, MDL);
+ } else
+ break;
+ } while (1);
+ }
+ }
+
+
+ /* Make sure this packet satisfies the configured minimum
+ number of seconds. */
+ memset (&d1, 0, sizeof d1);
+ if (offer == DHCPOFFER &&
+ (oc = lookup_option (&server_universe, state -> options,
+ SV_MIN_SECS))) {
+ if (evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len &&
+ ntohs (packet -> raw -> secs) < d1.data [0]) {
+ log_info("%s: configured min-secs value (%d) "
+ "is greater than secs field (%d). "
+ "message dropped.", msg, d1.data[0],
+ ntohs(packet->raw->secs));
+ data_string_forget (&d1, MDL);
+ free_lease_state (state, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return;
+ }
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ /* Try to find a matching host declaration for this lease.
+ */
+ if (!host) {
+ struct host_decl *hp = (struct host_decl *)0;
+ struct host_decl *h;
+
+ /* Try to find a host_decl that matches the client
+ identifier or hardware address on the packet, and
+ has no fixed IP address. If there is one, hang
+ it off the lease so that its option definitions
+ can be used. */
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_CLIENT_IDENTIFIER);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL)) {
+ find_hosts_by_uid (&hp, d1.data, d1.len, MDL);
+ data_string_forget (&d1, MDL);
+ for (h = hp; h; h = h -> n_ipaddr) {
+ if (!h -> fixed_addr)
+ break;
+ }
+ if (h)
+ host_reference (&host, h, MDL);
+ if (hp != NULL)
+ host_dereference(&hp, MDL);
+ }
+ if (!host) {
+ find_hosts_by_haddr (&hp,
+ packet -> raw -> htype,
+ packet -> raw -> chaddr,
+ packet -> raw -> hlen,
+ MDL);
+ for (h = hp; h; h = h -> n_ipaddr) {
+ if (!h -> fixed_addr)
+ break;
+ }
+ if (h)
+ host_reference (&host, h, MDL);
+ if (hp != NULL)
+ host_dereference(&hp, MDL);
+ }
+ if (!host) {
+ find_hosts_by_option(&hp, packet, packet->options, MDL);
+ for (h = hp; h; h = h -> n_ipaddr) {
+ if (!h -> fixed_addr)
+ break;
+ }
+ if (h)
+ host_reference (&host, h, MDL);
+ if (hp != NULL)
+ host_dereference(&hp, MDL);
+ }
+ }
+
+ /* If we have a host_decl structure, run the options associated
+ with its group. Whether the host decl struct is old or not. */
+ if (host)
+ execute_statements_in_scope ((struct binding_value **)0,
+ packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options, &lease -> scope,
+ host -> group,
+ (lease -> pool
+ ? lease -> pool -> group
+ : lease -> subnet -> group));
+
+ /* Drop the request if it's not allowed for this client. By
+ default, unknown clients are allowed. */
+ if (!host &&
+ (oc = lookup_option (&server_universe, state -> options,
+ SV_BOOT_UNKNOWN_CLIENTS)) &&
+ !evaluate_boolean_option_cache (&ignorep,
+ packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (!ignorep)
+ log_info ("%s: unknown client", msg);
+ free_lease_state (state, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return;
+ }
+
+ /* Drop the request if it's not allowed for this client. */
+ if (!offer &&
+ (oc = lookup_option (&server_universe, state -> options,
+ SV_ALLOW_BOOTP)) &&
+ !evaluate_boolean_option_cache (&ignorep,
+ packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (!ignorep)
+ log_info ("%s: bootp disallowed", msg);
+ free_lease_state (state, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return;
+ }
+
+ /* Drop the request if booting is specifically denied. */
+ oc = lookup_option (&server_universe, state -> options,
+ SV_ALLOW_BOOTING);
+ if (oc &&
+ !evaluate_boolean_option_cache (&ignorep,
+ packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (!ignorep)
+ log_info ("%s: booting disallowed", msg);
+ free_lease_state (state, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return;
+ }
+
+ /* If we are configured to do per-class billing, do it. */
+ if (have_billing_classes && !(lease -> flags & STATIC_LEASE)) {
+ /* See if the lease is currently being billed to a
+ class, and if so, whether or not it can continue to
+ be billed to that class. */
+ if (lease -> billing_class) {
+ for (i = 0; i < packet -> class_count; i++)
+ if (packet -> classes [i] ==
+ lease -> billing_class)
+ break;
+ if (i == packet -> class_count)
+ unbill_class (lease, lease -> billing_class);
+ }
+
+ /* If we don't have an active billing, see if we need
+ one, and if we do, try to do so. */
+ if (lease->billing_class == NULL) {
+ int bill = 0;
+ for (i = 0; i < packet->class_count; i++) {
+ if (packet->classes[i]->lease_limit) {
+ bill++;
+ if (bill_class(lease,
+ packet->classes[i]))
+ break;
+ }
+ }
+ if (bill != 0 && i == packet->class_count) {
+ log_info("%s: no available billing: lease "
+ "limit reached in all matching "
+ "classes", msg);
+ free_lease_state(state, MDL);
+ if (host)
+ host_dereference(&host, MDL);
+ return;
+ }
+
+ /* If this is an offer, undo the billing. We go
+ * through all the steps above to bill a class so
+ * we can hit the 'no available billing' mark and
+ * abort without offering. But it just doesn't make
+ * sense to permanently bill a class for a non-active
+ * lease. This means on REQUEST, we will bill this
+ * lease again (if there is a REQUEST).
+ */
+ if (offer == DHCPOFFER &&
+ lease->billing_class != NULL &&
+ lease->binding_state != FTS_ACTIVE)
+ unbill_class(lease, lease->billing_class);
+ }
+ }
+
+ /* Figure out the filename. */
+ oc = lookup_option (&server_universe, state -> options, SV_FILENAME);
+ if (oc)
+ evaluate_option_cache (&state -> filename, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL);
+
+ /* Choose a server name as above. */
+ oc = lookup_option (&server_universe, state -> options,
+ SV_SERVER_NAME);
+ if (oc)
+ evaluate_option_cache (&state -> server_name, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL);
+
+ /* At this point, we have a lease that we can offer the client.
+ Now we construct a lease structure that contains what we want,
+ and call supersede_lease to do the right thing with it. */
+ lt = (struct lease *)0;
+ result = lease_allocate (&lt, MDL);
+ if (result != ISC_R_SUCCESS) {
+ log_info ("%s: can't allocate temporary lease structure: %s",
+ msg, isc_result_totext (result));
+ free_lease_state (state, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return;
+ }
+
+ /* Use the ip address of the lease that we finally found in
+ the database. */
+ lt -> ip_addr = lease -> ip_addr;
+
+ /* Start now. */
+ lt -> starts = cur_time;
+
+ /* Figure out how long a lease to assign. If this is a
+ dynamic BOOTP lease, its duration must be infinite. */
+ if (offer) {
+ lt->flags &= ~BOOTP_LEASE;
+
+ default_lease_time = DEFAULT_DEFAULT_LEASE_TIME;
+ if ((oc = lookup_option (&server_universe, state -> options,
+ SV_DEFAULT_LEASE_TIME))) {
+ if (evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int32_t))
+ default_lease_time =
+ getULong (d1.data);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ if ((oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_LEASE_TIME)))
+ s1 = evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL);
+ else
+ s1 = 0;
+
+ if (s1 && (d1.len == 4)) {
+ u_int32_t ones = 0xffffffff;
+
+ /* One potential use of reserved leases is to allow
+ * clients to signal reservation of their lease. They
+ * can kinda sorta do this, if you squint hard enough,
+ * by supplying an 'infinite' requested-lease-time
+ * option. This is generally bad practice...you want
+ * clients to return to the server on at least some
+ * period (days, months, years) to get up-to-date
+ * config state. So;
+ *
+ * 1) A client requests 0xffffffff lease-time.
+ * 2) The server reserves the lease, and assigns a
+ * <= max_lease_time lease-time to the client, which
+ * we presume is much smaller than 0xffffffff.
+ * 3) The client ultimately fails to renew its lease
+ * (all clients go offline at some point).
+ * 4) The server retains the reservation, although
+ * the lease expires and passes through those states
+ * as normal, it's placed in the 'reserved' queue,
+ * and is under no circumstances allocated to any
+ * clients.
+ *
+ * Whether the client knows its reserving its lease or
+ * not, this can be a handy tool for a sysadmin.
+ */
+ if ((memcmp(d1.data, &ones, 4) == 0) &&
+ (oc = lookup_option(&server_universe,
+ state->options,
+ SV_RESERVE_INFINITE)) &&
+ evaluate_boolean_option_cache(&ignorep, packet,
+ lease, NULL, packet->options,
+ state->options, &lease->scope,
+ oc, MDL)) {
+ lt->flags |= RESERVED_LEASE;
+ if (!ignorep)
+ log_info("Infinite-leasetime "
+ "reservation made on %s.",
+ piaddr(lt->ip_addr));
+ }
+
+ lease_time = getULong (d1.data);
+ } else
+ lease_time = default_lease_time;
+
+ if (s1)
+ data_string_forget(&d1, MDL);
+
+ /* See if there's a maximum lease time. */
+ max_lease_time = DEFAULT_MAX_LEASE_TIME;
+ if ((oc = lookup_option (&server_universe, state -> options,
+ SV_MAX_LEASE_TIME))) {
+ if (evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int32_t))
+ max_lease_time =
+ getULong (d1.data);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ /* Enforce the maximum lease length. */
+ if (lease_time < 0 /* XXX */
+ || lease_time > max_lease_time)
+ lease_time = max_lease_time;
+
+ min_lease_time = DEFAULT_MIN_LEASE_TIME;
+ if (min_lease_time > max_lease_time)
+ min_lease_time = max_lease_time;
+
+ if ((oc = lookup_option (&server_universe, state -> options,
+ SV_MIN_LEASE_TIME))) {
+ if (evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int32_t))
+ min_lease_time = getULong (d1.data);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ /* CC: If there are less than
+ adaptive-lease-time-threshold % free leases,
+ hand out only short term leases */
+
+ memset(&d1, 0, sizeof(d1));
+ if (lease->pool &&
+ (oc = lookup_option(&server_universe, state->options,
+ SV_ADAPTIVE_LEASE_TIME_THRESHOLD)) &&
+ evaluate_option_cache(&d1, packet, lease, NULL,
+ packet->options, state->options,
+ &lease->scope, oc, MDL)) {
+ if (d1.len == 1 && d1.data[0] > 0 &&
+ d1.data[0] < 100) {
+ TIME adaptive_time;
+ int poolfilled, total, count;
+
+ if (min_lease_time)
+ adaptive_time = min_lease_time;
+ else
+ adaptive_time = DEFAULT_MIN_LEASE_TIME;
+
+ /* Allow the client to keep its lease. */
+ if (lease->ends - cur_time > adaptive_time)
+ adaptive_time = lease->ends - cur_time;
+
+ count = lease->pool->lease_count;
+ total = count - (lease->pool->free_leases +
+ lease->pool->backup_leases);
+
+ poolfilled = (total > (INT_MAX / 100)) ?
+ total / (count / 100) :
+ (total * 100) / count;
+
+ log_debug("Adap-lease: Total: %d, Free: %d, "
+ "Ends: %d, Adaptive: %d, Fill: %d, "
+ "Threshold: %d",
+ lease->pool->lease_count,
+ lease->pool->free_leases,
+ (int)(lease->ends - cur_time),
+ (int)adaptive_time, poolfilled,
+ d1.data[0]);
+
+ if (poolfilled >= d1.data[0] &&
+ lease_time > adaptive_time) {
+ log_info("Pool over threshold, time "
+ "for %s reduced from %d to "
+ "%d.", piaddr(lease->ip_addr),
+ (int)lease_time,
+ (int)adaptive_time);
+
+ lease_time = adaptive_time;
+ }
+ }
+ data_string_forget(&d1, MDL);
+ }
+
+ /* a client requests an address which is not yet active*/
+ if (lease->pool && lease->pool->valid_from &&
+ cur_time < lease->pool->valid_from) {
+ /* NAK leases before pool activation date */
+ cip.len = 4;
+ memcpy (cip.iabuf, &lt->ip_addr.iabuf, 4);
+ nak_lease(packet, &cip);
+ free_lease_state (state, MDL);
+ lease_dereference (&lt, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return;
+
+ }
+
+ /* CC:
+ a) NAK current lease if past the expiration date
+ b) extend lease only up to the expiration date, but not
+ below min-lease-time
+ Setting min-lease-time is essential for this to work!
+ The value of min-lease-time determines the lenght
+ of the transition window:
+ A client renewing a second before the deadline will
+ get a min-lease-time lease. Since the current ip might not
+ be routable after the deadline, the client will
+ be offline until it DISCOVERS again. Otherwise it will
+ receive a NAK at T/2.
+ A min-lease-time of 6 seconds effectively switches over
+ all clients in this pool very quickly.
+ */
+
+ if (lease->pool && lease->pool->valid_until) {
+ if (cur_time >= lease->pool->valid_until) {
+ /* NAK leases after pool expiration date */
+ cip.len = 4;
+ memcpy (cip.iabuf, &lt->ip_addr.iabuf, 4);
+ nak_lease(packet, &cip);
+ free_lease_state (state, MDL);
+ lease_dereference (&lt, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return;
+ }
+ remaining_time = lease->pool->valid_until - cur_time;
+ if (lease_time > remaining_time)
+ lease_time = remaining_time;
+ }
+
+ if (lease_time < min_lease_time) {
+ if (min_lease_time)
+ lease_time = min_lease_time;
+ else
+ lease_time = default_lease_time;
+ }
+
+
+#if defined (FAILOVER_PROTOCOL)
+ /* Okay, we know the lease duration. Now check the
+ failover state, if any. */
+ if (lease -> pool && lease -> pool -> failover_peer) {
+ TIME new_lease_time = lease_time;
+ dhcp_failover_state_t *peer =
+ lease -> pool -> failover_peer;
+
+ /* Copy previous lease failover ack-state. */
+ lt->tsfp = lease->tsfp;
+ lt->atsfp = lease->atsfp;
+
+ /* cltt set below */
+
+ /* Lease times less than MCLT are not a concern. */
+ if (lease_time > peer->mclt) {
+ /* Each server can only offer a lease time
+ * that is either equal to MCLT (at least),
+ * or up to TSFP+MCLT. Only if the desired
+ * lease time falls within TSFP+MCLT, can
+ * the server allow it.
+ */
+ if (lt->tsfp <= cur_time)
+ new_lease_time = peer->mclt;
+ else if ((cur_time + lease_time) >
+ (lt->tsfp + peer->mclt))
+ new_lease_time = (lt->tsfp - cur_time)
+ + peer->mclt;
+ }
+
+ /* Update potential expiry. Allow for the desired
+ * lease time plus one half the actual (whether
+ * modified downward or not) lease time, which is
+ * actually an estimate of when the client will
+ * renew. This way, the client will be able to get
+ * the desired lease time upon renewal.
+ */
+ if (offer == DHCPACK) {
+ lt->tstp = cur_time + lease_time +
+ (new_lease_time / 2);
+
+ /* If we reduced the potential expiry time,
+ * make sure we don't offer an old-expiry-time
+ * lease for this lease before the change is
+ * ack'd.
+ */
+ if (lt->tstp < lt->tsfp)
+ lt->tsfp = lt->tstp;
+ } else
+ lt->tstp = lease->tstp;
+
+ /* Use failover-modified lease time. */
+ lease_time = new_lease_time;
+ }
+#endif /* FAILOVER_PROTOCOL */
+
+ /* If the lease duration causes the time value to wrap,
+ use the maximum expiry time. */
+ if (cur_time + lease_time < cur_time)
+ state -> offered_expiry = MAX_TIME - 1;
+ else
+ state -> offered_expiry = cur_time + lease_time;
+ if (when)
+ lt -> ends = when;
+ else
+ lt -> ends = state -> offered_expiry;
+
+ /* Don't make lease active until we actually get a
+ DHCPREQUEST. */
+ if (offer == DHCPACK)
+ lt -> next_binding_state = FTS_ACTIVE;
+ else
+ lt -> next_binding_state = lease -> binding_state;
+ } else {
+ lt->flags |= BOOTP_LEASE;
+
+ lease_time = MAX_TIME - cur_time;
+
+ if ((oc = lookup_option (&server_universe, state -> options,
+ SV_BOOTP_LEASE_LENGTH))) {
+ if (evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int32_t))
+ lease_time = getULong (d1.data);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ if ((oc = lookup_option (&server_universe, state -> options,
+ SV_BOOTP_LEASE_CUTOFF))) {
+ if (evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int32_t))
+ lease_time = (getULong (d1.data) -
+ cur_time);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ lt -> ends = state -> offered_expiry = cur_time + lease_time;
+ lt -> next_binding_state = FTS_ACTIVE;
+ }
+
+ /* Update Client Last Transaction Time. */
+ lt->cltt = cur_time;
+
+ /* Record the uid, if given... */
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_CLIENT_IDENTIFIER);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len <= sizeof lt -> uid_buf) {
+ memcpy (lt -> uid_buf, d1.data, d1.len);
+ lt -> uid = lt -> uid_buf;
+ lt -> uid_max = sizeof lt -> uid_buf;
+ lt -> uid_len = d1.len;
+ } else {
+ unsigned char *tuid;
+ lt -> uid_max = d1.len;
+ lt -> uid_len = d1.len;
+ tuid = (unsigned char *)dmalloc (lt -> uid_max, MDL);
+ /* XXX inelegant */
+ if (!tuid)
+ log_fatal ("no memory for large uid.");
+ memcpy (tuid, d1.data, lt -> uid_len);
+ lt -> uid = tuid;
+ }
+ data_string_forget (&d1, MDL);
+ }
+
+ if (host) {
+ host_reference (&lt -> host, host, MDL);
+ host_dereference (&host, MDL);
+ }
+ if (lease -> subnet)
+ subnet_reference (&lt -> subnet, lease -> subnet, MDL);
+ if (lease -> billing_class)
+ class_reference (&lt -> billing_class,
+ lease -> billing_class, MDL);
+
+ /* Set a flag if this client is a broken client that NUL
+ terminates string options and expects us to do likewise. */
+ if (ms_nulltp)
+ lease -> flags |= MS_NULL_TERMINATION;
+ else
+ lease -> flags &= ~MS_NULL_TERMINATION;
+
+ /* Save any bindings. */
+ if (lease -> scope) {
+ binding_scope_reference (&lt -> scope, lease -> scope, MDL);
+ binding_scope_dereference (&lease -> scope, MDL);
+ }
+ if (lease -> agent_options)
+ option_chain_head_reference (&lt -> agent_options,
+ lease -> agent_options, MDL);
+
+ /* Save the vendor-class-identifier for DHCPLEASEQUERY. */
+ oc = lookup_option(&dhcp_universe, packet->options,
+ DHO_VENDOR_CLASS_IDENTIFIER);
+ if (oc != NULL &&
+ evaluate_option_cache(&d1, packet, NULL, NULL, packet->options,
+ NULL, &lease->scope, oc, MDL)) {
+ if (d1.len != 0) {
+ bind_ds_value(&lease->scope, "vendor-class-identifier",
+ &d1);
+ }
+
+ data_string_forget(&d1, MDL);
+ }
+
+ /* If we got relay agent information options from the packet, then
+ * cache them for renewal in case the relay agent can't supply them
+ * when the client unicasts. The options may be from an addressed
+ * "l3" relay, or from an unaddressed "l2" relay which does not set
+ * giaddr.
+ */
+ if (!packet->agent_options_stashed &&
+ (packet->options != NULL) &&
+ packet->options->universe_count > agent_universe.index &&
+ packet->options->universes[agent_universe.index] != NULL) {
+ oc = lookup_option (&server_universe, state -> options,
+ SV_STASH_AGENT_OPTIONS);
+ if (!oc ||
+ evaluate_boolean_option_cache (&ignorep, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (lt -> agent_options)
+ option_chain_head_dereference (&lt -> agent_options, MDL);
+ option_chain_head_reference
+ (&lt -> agent_options,
+ (struct option_chain_head *)
+ packet -> options -> universes [agent_universe.index],
+ MDL);
+ }
+ }
+
+ /* Replace the old lease hostname with the new one, if it's changed. */
+ oc = lookup_option (&dhcp_universe, packet -> options, DHO_HOST_NAME);
+ if (oc)
+ s1 = evaluate_option_cache (&d1, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL);
+ else
+ s1 = 0;
+
+ if (oc && s1 &&
+ lease -> client_hostname &&
+ strlen (lease -> client_hostname) == d1.len &&
+ !memcmp (lease -> client_hostname, d1.data, d1.len)) {
+ /* Hasn't changed. */
+ data_string_forget (&d1, MDL);
+ lt -> client_hostname = lease -> client_hostname;
+ lease -> client_hostname = (char *)0;
+ } else if (oc && s1) {
+ lt -> client_hostname = dmalloc (d1.len + 1, MDL);
+ if (!lt -> client_hostname)
+ log_error ("no memory for client hostname.");
+ else {
+ memcpy (lt -> client_hostname, d1.data, d1.len);
+ lt -> client_hostname [d1.len] = 0;
+ }
+ data_string_forget (&d1, MDL);
+ }
+
+ /* Record the hardware address, if given... */
+ lt -> hardware_addr.hlen = packet -> raw -> hlen + 1;
+ lt -> hardware_addr.hbuf [0] = packet -> raw -> htype;
+ memcpy (&lt -> hardware_addr.hbuf [1], packet -> raw -> chaddr,
+ sizeof packet -> raw -> chaddr);
+
+ lt -> flags = lease -> flags & ~PERSISTENT_FLAGS;
+
+ /* If there are statements to execute when the lease is
+ committed, execute them. */
+ if (lease -> on_commit && (!offer || offer == DHCPACK)) {
+ execute_statements ((struct binding_value **)0,
+ packet, lt, (struct client_state *)0,
+ packet -> options,
+ state -> options, &lt -> scope,
+ lease -> on_commit);
+ if (lease -> on_commit)
+ executable_statement_dereference (&lease -> on_commit,
+ MDL);
+ }
+
+#ifdef NSUPDATE
+ /* Perform DDNS updates, if configured to. */
+ if ((!offer || offer == DHCPACK) &&
+ (!(oc = lookup_option (&server_universe, state -> options,
+ SV_DDNS_UPDATES)) ||
+ evaluate_boolean_option_cache (&ignorep, packet, lt,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lt -> scope, oc, MDL))) {
+ ddns_updates(packet, lt, lease, NULL, NULL, state->options);
+ }
+#endif /* NSUPDATE */
+
+ /* Don't call supersede_lease on a mocked-up lease. */
+ if (lease -> flags & STATIC_LEASE) {
+ /* Copy the hardware address into the static lease
+ structure. */
+ lease -> hardware_addr.hlen = packet -> raw -> hlen + 1;
+ lease -> hardware_addr.hbuf [0] = packet -> raw -> htype;
+ memcpy (&lease -> hardware_addr.hbuf [1],
+ packet -> raw -> chaddr,
+ sizeof packet -> raw -> chaddr); /* XXX */
+ } else {
+
+#if !defined(DELAYED_ACK)
+ /* Install the new information on 'lt' onto the lease at
+ * 'lease'. If this is a DHCPOFFER, it is a 'soft' promise,
+ * if it is a DHCPACK, it is a 'hard' binding, so it needs
+ * to be recorded and propogated immediately. If the update
+ * fails, don't ACK it (or BOOTREPLY) either; we may give
+ * the same lease to another client later, and that would be
+ * a conflict.
+ */
+ if (!supersede_lease(lease, lt, !offer || (offer == DHCPACK),
+ offer == DHCPACK, offer == DHCPACK, 0)) {
+#else /* defined(DELAYED_ACK) */
+ /* Install the new information on 'lt' onto the lease at
+ * 'lease'. We will not 'commit' this information to disk
+ * yet (fsync()), we will 'propogate' the information if
+ * this is BOOTP or a DHCPACK, but we will not 'pimmediate'ly
+ * transmit failover binding updates (this is delayed until
+ * after the fsync()). If the update fails, don't ACK it (or
+ * BOOTREPLY either); we may give the same lease out to a
+ * different client, and that would be a conflict.
+ */
+
+ if (!supersede_lease(lease, lt, 0, !offer || offer == DHCPACK,
+ 0, 0)) {
+#endif
+ log_info ("%s: database update failed", msg);
+ free_lease_state (state, MDL);
+ lease_dereference (&lt, MDL);
+ return;
+ }
+ }
+ lease_dereference (&lt, MDL);
+
+ /* Remember the interface on which the packet arrived. */
+ state -> ip = packet -> interface;
+
+ /* Remember the giaddr, xid, secs, flags and hops. */
+ state -> giaddr = packet -> raw -> giaddr;
+ state -> ciaddr = packet -> raw -> ciaddr;
+ state -> xid = packet -> raw -> xid;
+ state -> secs = packet -> raw -> secs;
+ state -> bootp_flags = packet -> raw -> flags;
+ state -> hops = packet -> raw -> hops;
+ state -> offer = offer;
+
+ /* If we're always supposed to broadcast to this client, set
+ the broadcast bit in the bootp flags field. */
+ if ((oc = lookup_option (&server_universe, state -> options,
+ SV_ALWAYS_BROADCAST)) &&
+ evaluate_boolean_option_cache (&ignorep, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL))
+ state -> bootp_flags |= htons (BOOTP_BROADCAST);
+
+ /* Get the Maximum Message Size option from the packet, if one
+ was sent. */
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_MAX_MESSAGE_SIZE);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int16_t))
+ state -> max_message_size = getUShort (d1.data);
+ data_string_forget (&d1, MDL);
+ } else {
+ oc = lookup_option (&dhcp_universe, state -> options,
+ DHO_DHCP_MAX_MESSAGE_SIZE);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int16_t))
+ state -> max_message_size =
+ getUShort (d1.data);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ /* Get the Subnet Selection option from the packet, if one
+ was sent. */
+ if ((oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_SUBNET_SELECTION))) {
+
+ /* Make a copy of the data. */
+ struct option_cache *noc = (struct option_cache *)0;
+ if (option_cache_allocate (&noc, MDL)) {
+ if (oc -> data.len)
+ data_string_copy (&noc -> data,
+ &oc -> data, MDL);
+ if (oc -> expression)
+ expression_reference (&noc -> expression,
+ oc -> expression, MDL);
+ if (oc -> option)
+ option_reference(&(noc->option), oc->option,
+ MDL);
+
+ save_option (&dhcp_universe, state -> options, noc);
+ option_cache_dereference (&noc, MDL);
+ }
+ }
+
+ /* Now, if appropriate, put in DHCP-specific options that
+ override those. */
+ if (state -> offer) {
+ i = DHO_DHCP_MESSAGE_TYPE;
+ oc = (struct option_cache *)0;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data (&oc -> expression,
+ &state -> offer, 1, 0, 0, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe,
+ state -> options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+
+ get_server_source_address(&from, state->options,
+ state->options, packet);
+ memcpy(state->from.iabuf, &from, sizeof(from));
+ state->from.len = sizeof(from);
+
+ offered_lease_time =
+ state -> offered_expiry - cur_time;
+
+ putULong(state->expiry, (u_int32_t)offered_lease_time);
+ i = DHO_DHCP_LEASE_TIME;
+ oc = (struct option_cache *)0;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data(&oc->expression, state->expiry,
+ 4, 0, 0, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe,
+ state -> options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+
+ /*
+ * Validate any configured renew or rebinding times against
+ * the determined lease time. Do rebinding first so that
+ * the renew time can be validated against the rebind time.
+ */
+ if ((oc = lookup_option(&dhcp_universe, state->options,
+ DHO_DHCP_REBINDING_TIME)) != NULL &&
+ evaluate_option_cache(&d1, packet, lease, NULL,
+ packet->options, state->options,
+ &lease->scope, oc, MDL)) {
+ TIME rebind_time = getULong(d1.data);
+
+ /* Drop the configured (invalid) rebinding time. */
+ if (rebind_time >= offered_lease_time)
+ delete_option(&dhcp_universe, state->options,
+ DHO_DHCP_REBINDING_TIME);
+ else /* XXX: variable is reused. */
+ offered_lease_time = rebind_time;
+
+ data_string_forget(&d1, MDL);
+ }
+
+ if ((oc = lookup_option(&dhcp_universe, state->options,
+ DHO_DHCP_RENEWAL_TIME)) != NULL &&
+ evaluate_option_cache(&d1, packet, lease, NULL,
+ packet->options, state->options,
+ &lease->scope, oc, MDL)) {
+ if (getULong(d1.data) >= offered_lease_time)
+ delete_option(&dhcp_universe, state->options,
+ DHO_DHCP_RENEWAL_TIME);
+
+ data_string_forget(&d1, MDL);
+ }
+ } else {
+ /* XXXSK: should we use get_server_source_address() here? */
+ if (state -> ip -> address_count) {
+ state -> from.len =
+ sizeof state -> ip -> addresses [0];
+ memcpy (state -> from.iabuf,
+ &state -> ip -> addresses [0],
+ state -> from.len);
+ }
+ }
+
+ /* Figure out the address of the boot file server. */
+ memset (&state -> siaddr, 0, sizeof state -> siaddr);
+ if ((oc =
+ lookup_option (&server_universe,
+ state -> options, SV_NEXT_SERVER))) {
+ if (evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL)) {
+ /* If there was more than one answer,
+ take the first. */
+ if (d1.len >= 4 && d1.data)
+ memcpy (&state -> siaddr, d1.data, 4);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ /* Use the subnet mask from the subnet declaration if no other
+ mask has been provided. */
+ i = DHO_SUBNET_MASK;
+ if (!lookup_option (&dhcp_universe, state -> options, i)) {
+ oc = (struct option_cache *)0;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data (&oc -> expression,
+ lease -> subnet -> netmask.iabuf,
+ lease -> subnet -> netmask.len,
+ 0, 0, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe,
+ state -> options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+ }
+
+ /* Use the name of the host declaration if there is one
+ and no hostname has otherwise been provided, and if the
+ use-host-decl-name flag is set. */
+ use_host_decl_name(packet, lease, state->options);
+
+ /* If we don't have a hostname yet, and we've been asked to do
+ a reverse lookup to find the hostname, do it. */
+ i = DHO_HOST_NAME;
+ j = SV_GET_LEASE_HOSTNAMES;
+ if (!lookup_option(&dhcp_universe, state->options, i) &&
+ evaluate_boolean_option_cache
+ (&ignorep, packet, lease, NULL,
+ packet->options, state->options, &lease->scope,
+ lookup_option (&server_universe, state->options, j), MDL)) {
+ struct in_addr ia;
+ struct hostent *h;
+
+ memcpy (&ia, lease -> ip_addr.iabuf, 4);
+
+ h = gethostbyaddr ((char *)&ia, sizeof ia, AF_INET);
+ if (!h)
+ log_error ("No hostname for %s", inet_ntoa (ia));
+ else {
+ oc = (struct option_cache *)0;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data (&oc -> expression,
+ ((unsigned char *)
+ h -> h_name),
+ strlen (h -> h_name) + 1,
+ 1, 1, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe,
+ state -> options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+ }
+ }
+
+ /* If so directed, use the leased IP address as the router address.
+ This supposedly makes Win95 machines ARP for all IP addresses,
+ so if the local router does proxy arp, you win. */
+
+ if (evaluate_boolean_option_cache
+ (&ignorep, packet, lease, (struct client_state *)0,
+ packet -> options, state -> options, &lease -> scope,
+ lookup_option (&server_universe, state -> options,
+ SV_USE_LEASE_ADDR_FOR_DEFAULT_ROUTE), MDL)) {
+ i = DHO_ROUTERS;
+ oc = lookup_option (&dhcp_universe, state -> options, i);
+ if (!oc) {
+ oc = (struct option_cache *)0;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data (&oc -> expression,
+ lease -> ip_addr.iabuf,
+ lease -> ip_addr.len,
+ 0, 0, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe,
+ state -> options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+ }
+ }
+
+ /* If a site option space has been specified, use that for
+ site option codes. */
+ i = SV_SITE_OPTION_SPACE;
+ if ((oc = lookup_option (&server_universe, state -> options, i)) &&
+ evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL)) {
+ struct universe *u = (struct universe *)0;
+
+ if (!universe_hash_lookup (&u, universe_hash,
+ (const char *)d1.data, d1.len,
+ MDL)) {
+ log_error ("unknown option space %s.", d1.data);
+ return;
+ }
+
+ state -> options -> site_universe = u -> index;
+ state->options->site_code_min = find_min_site_code(u);
+ data_string_forget (&d1, MDL);
+ } else {
+ state -> options -> site_code_min = 0;
+ state -> options -> site_universe = dhcp_universe.index;
+ }
+
+ /* If the client has provided a list of options that it wishes
+ returned, use it to prioritize. If there's a parameter
+ request list in scope, use that in preference. Otherwise
+ use the default priority list. */
+
+ oc = lookup_option (&dhcp_universe, state -> options,
+ DHO_DHCP_PARAMETER_REQUEST_LIST);
+
+ if (!oc)
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_PARAMETER_REQUEST_LIST);
+ if (oc)
+ evaluate_option_cache (&state -> parameter_request_list,
+ packet, lease, (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL);
+
+#ifdef DEBUG_PACKET
+ dump_packet (packet);
+ dump_raw ((unsigned char *)packet -> raw, packet -> packet_length);
+#endif
+
+ lease -> state = state;
+
+ log_info ("%s", msg);
+
+ /* Hang the packet off the lease state. */
+ packet_reference (&lease -> state -> packet, packet, MDL);
+
+ /* If this is a DHCPOFFER, ping the lease address before actually
+ sending the offer. */
+ if (offer == DHCPOFFER && !(lease -> flags & STATIC_LEASE) &&
+ ((cur_time - lease_cltt) > 60) &&
+ (!(oc = lookup_option (&server_universe, state -> options,
+ SV_PING_CHECKS)) ||
+ evaluate_boolean_option_cache (&ignorep, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL))) {
+ icmp_echorequest (&lease -> ip_addr);
+
+ /* Determine whether to use configured or default ping timeout.
+ */
+ if ((oc = lookup_option (&server_universe, state -> options,
+ SV_PING_TIMEOUT)) &&
+ evaluate_option_cache (&d1, packet, lease, NULL,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int32_t))
+ ping_timeout = getULong (d1.data);
+ else
+ ping_timeout = DEFAULT_PING_TIMEOUT;
+
+ data_string_forget (&d1, MDL);
+ } else
+ ping_timeout = DEFAULT_PING_TIMEOUT;
+
+#ifdef DEBUG
+ log_debug ("Ping timeout: %ld", (long)ping_timeout);
+#endif
+
+ /*
+ * Set a timeout for 'ping-timeout' seconds from NOW, including
+ * current microseconds. As ping-timeout defaults to 1, the
+ * exclusion of current microseconds causes a value somewhere
+ * /between/ zero and one.
+ */
+ tv.tv_sec = cur_tv.tv_sec + ping_timeout;
+ tv.tv_usec = cur_tv.tv_usec;
+ add_timeout (&tv, lease_ping_timeout, lease,
+ (tvref_t)lease_reference,
+ (tvunref_t)lease_dereference);
+ ++outstanding_pings;
+ } else {
+ lease->cltt = cur_time;
+#if defined(DELAYED_ACK)
+ if (!(lease->flags & STATIC_LEASE) &&
+ (!offer || (offer == DHCPACK)))
+ delayed_ack_enqueue(lease);
+ else
+#endif
+ dhcp_reply(lease);
+ }
+}
+
+/*
+ * CC: queue single ACK:
+ * - write the lease (but do not fsync it yet)
+ * - add to double linked list
+ * - commit if more than xx ACKs pending
+ * - if necessary set the max timer and bump the next timer
+ * but only up to the max timer value.
+ */
+
+void
+delayed_ack_enqueue(struct lease *lease)
+{
+ struct leasequeue *q;
+
+ if (!write_lease(lease))
+ return;
+ if (free_ackqueue) {
+ q = free_ackqueue;
+ free_ackqueue = q->next;
+ } else {
+ q = ((struct leasequeue *)
+ dmalloc(sizeof(struct leasequeue), MDL));
+ if (!q)
+ log_fatal("delayed_ack_enqueue: no memory!");
+ }
+ memset(q, 0, sizeof *q);
+ /* prepend to ackqueue*/
+ lease_reference(&q->lease, lease, MDL);
+ q->next = ackqueue_head;
+ ackqueue_head = q;
+ if (!ackqueue_tail)
+ ackqueue_tail = q;
+ else
+ q->next->prev = q;
+
+ outstanding_acks++;
+ if (outstanding_acks > max_outstanding_acks) {
+ commit_leases();
+
+ /* Reset max_fsync and cancel any pending timeout. */
+ memset(&max_fsync, 0, sizeof(max_fsync));
+ cancel_timeout(commit_leases_ackout, NULL);
+ } else {
+ struct timeval next_fsync;
+
+ if (max_fsync.tv_sec == 0 && max_fsync.tv_usec == 0) {
+ /* set the maximum time we'll wait */
+ max_fsync.tv_sec = cur_tv.tv_sec + max_ack_delay_secs;
+ max_fsync.tv_usec = cur_tv.tv_usec +
+ max_ack_delay_usecs;
+
+ if (max_fsync.tv_usec >= 1000000) {
+ max_fsync.tv_sec++;
+ max_fsync.tv_usec -= 1000000;
+ }
+ }
+
+ /* Set the timeout */
+ next_fsync.tv_sec = cur_tv.tv_sec;
+ next_fsync.tv_usec = cur_tv.tv_usec + min_ack_delay_usecs;
+ if (next_fsync.tv_usec >= 1000000) {
+ next_fsync.tv_sec++;
+ next_fsync.tv_usec -= 1000000;
+ }
+ /* but not more than the max */
+ if ((next_fsync.tv_sec > max_fsync.tv_sec) ||
+ ((next_fsync.tv_sec == max_fsync.tv_sec) &&
+ (next_fsync.tv_usec > max_fsync.tv_usec))) {
+ next_fsync.tv_sec = max_fsync.tv_sec;
+ next_fsync.tv_usec = max_fsync.tv_usec;
+ }
+
+ add_timeout(&next_fsync, commit_leases_ackout, NULL,
+ (tvref_t) NULL, (tvunref_t) NULL);
+ }
+}
+
+static void
+commit_leases_ackout(void *foo)
+{
+ if (outstanding_acks) {
+ commit_leases();
+
+ memset(&max_fsync, 0, sizeof(max_fsync));
+ }
+}
+
+/* CC: process the delayed ACK responses:
+ - send out the ACK packets
+ - move the queue slots to the free list
+ */
+void
+flush_ackqueue(void *foo)
+{
+ struct leasequeue *ack, *p;
+ /* process from bottom to retain packet order */
+ for (ack = ackqueue_tail ; ack ; ack = p) {
+ p = ack->prev;
+
+ /* dhcp_reply() requires that the reply state still be valid */
+ if (ack->lease->state == NULL)
+ log_error("delayed ack for %s has gone stale",
+ piaddr(ack->lease->ip_addr));
+ else
+ dhcp_reply(ack->lease);
+
+ lease_dereference(&ack->lease, MDL);
+ ack->next = free_ackqueue;
+ free_ackqueue = ack;
+ }
+ ackqueue_head = NULL;
+ ackqueue_tail = NULL;
+ outstanding_acks = 0;
+}
+
+#if defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+void
+relinquish_ackqueue(void)
+{
+ struct leasequeue *q, *n;
+
+ for (q = ackqueue_head ; q ; q = n) {
+ n = q->next;
+ dfree(q, MDL);
+ }
+ for (q = free_ackqueue ; q ; q = n) {
+ n = q->next;
+ dfree(q, MDL);
+ }
+}
+#endif
+
+void dhcp_reply (lease)
+ struct lease *lease;
+{
+ int bufs = 0;
+ unsigned packet_length;
+ struct dhcp_packet raw;
+ struct sockaddr_in to;
+ struct in_addr from;
+ struct hardware hto;
+ int result;
+ struct lease_state *state = lease -> state;
+ int nulltp, bootpp, unicastp = 1;
+ struct data_string d1;
+ const char *s;
+
+ if (!state)
+ log_fatal ("dhcp_reply was supplied lease with no state!");
+
+ /* Compose a response for the client... */
+ memset (&raw, 0, sizeof raw);
+ memset (&d1, 0, sizeof d1);
+
+ /* Copy in the filename if given; otherwise, flag the filename
+ buffer as available for options. */
+ if (state -> filename.len && state -> filename.data) {
+ memcpy (raw.file,
+ state -> filename.data,
+ state -> filename.len > sizeof raw.file
+ ? sizeof raw.file : state -> filename.len);
+ if (sizeof raw.file > state -> filename.len)
+ memset (&raw.file [state -> filename.len], 0,
+ (sizeof raw.file) - state -> filename.len);
+ else
+ log_info("file name longer than packet field "
+ "truncated - field: %lu name: %d %.*s",
+ (unsigned long)sizeof(raw.file),
+ state->filename.len, state->filename.len,
+ state->filename.data);
+ } else
+ bufs |= 1;
+
+ /* Copy in the server name if given; otherwise, flag the
+ server_name buffer as available for options. */
+ if (state -> server_name.len && state -> server_name.data) {
+ memcpy (raw.sname,
+ state -> server_name.data,
+ state -> server_name.len > sizeof raw.sname
+ ? sizeof raw.sname : state -> server_name.len);
+ if (sizeof raw.sname > state -> server_name.len)
+ memset (&raw.sname [state -> server_name.len], 0,
+ (sizeof raw.sname) - state -> server_name.len);
+ else
+ log_info("server name longer than packet field "
+ "truncated - field: %lu name: %d %.*s",
+ (unsigned long)sizeof(raw.sname),
+ state->server_name.len,
+ state->server_name.len,
+ state->server_name.data);
+ } else
+ bufs |= 2; /* XXX */
+
+ memcpy (raw.chaddr,
+ &lease -> hardware_addr.hbuf [1], sizeof raw.chaddr);
+ raw.hlen = lease -> hardware_addr.hlen - 1;
+ raw.htype = lease -> hardware_addr.hbuf [0];
+
+ /* See if this is a Microsoft client that NUL-terminates its
+ strings and expects us to do likewise... */
+ if (lease -> flags & MS_NULL_TERMINATION)
+ nulltp = 1;
+ else
+ nulltp = 0;
+
+ /* See if this is a bootp client... */
+ if (state -> offer)
+ bootpp = 0;
+ else
+ bootpp = 1;
+
+ /* Insert such options as will fit into the buffer. */
+ packet_length = cons_options (state -> packet, &raw, lease,
+ (struct client_state *)0,
+ state -> max_message_size,
+ state -> packet -> options,
+ state -> options, &global_scope,
+ bufs, nulltp, bootpp,
+ &state -> parameter_request_list,
+ (char *)0);
+
+ memcpy (&raw.ciaddr, &state -> ciaddr, sizeof raw.ciaddr);
+ memcpy (&raw.yiaddr, lease -> ip_addr.iabuf, 4);
+ raw.siaddr = state -> siaddr;
+ raw.giaddr = state -> giaddr;
+
+ raw.xid = state -> xid;
+ raw.secs = state -> secs;
+ raw.flags = state -> bootp_flags;
+ raw.hops = state -> hops;
+ raw.op = BOOTREPLY;
+
+ if (lease -> client_hostname) {
+ if ((strlen (lease -> client_hostname) <= 64) &&
+ db_printable((unsigned char *)lease->client_hostname))
+ s = lease -> client_hostname;
+ else
+ s = "Hostname Unsuitable for Printing";
+ } else
+ s = (char *)0;
+
+ /* Say what we're doing... */
+ log_info ("%s on %s to %s %s%s%svia %s",
+ (state -> offer
+ ? (state -> offer == DHCPACK ? "DHCPACK" : "DHCPOFFER")
+ : "BOOTREPLY"),
+ piaddr (lease -> ip_addr),
+ (lease -> hardware_addr.hlen
+ ? print_hw_addr (lease -> hardware_addr.hbuf [0],
+ lease -> hardware_addr.hlen - 1,
+ &lease -> hardware_addr.hbuf [1])
+ : print_hex_1(lease->uid_len, lease->uid, 60)),
+ s ? "(" : "", s ? s : "", s ? ") " : "",
+ (state -> giaddr.s_addr
+ ? inet_ntoa (state -> giaddr)
+ : state -> ip -> name));
+
+ /* Set up the hardware address... */
+ hto.hlen = lease -> hardware_addr.hlen;
+ memcpy (hto.hbuf, lease -> hardware_addr.hbuf, hto.hlen);
+
+ to.sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+ to.sin_len = sizeof to;
+#endif
+ memset (to.sin_zero, 0, sizeof to.sin_zero);
+
+#ifdef DEBUG_PACKET
+ dump_raw ((unsigned char *)&raw, packet_length);
+#endif
+
+ /* Make sure outgoing packets are at least as big
+ as a BOOTP packet. */
+ if (packet_length < BOOTP_MIN_LEN)
+ packet_length = BOOTP_MIN_LEN;
+
+ /* If this was gatewayed, send it back to the gateway... */
+ if (raw.giaddr.s_addr) {
+ to.sin_addr = raw.giaddr;
+ if (raw.giaddr.s_addr != htonl (INADDR_LOOPBACK))
+ to.sin_port = local_port;
+ else
+ to.sin_port = remote_port; /* For debugging. */
+
+ if (fallback_interface) {
+ result = send_packet(fallback_interface, NULL, &raw,
+ packet_length, raw.siaddr, &to,
+ NULL);
+ if (result < 0) {
+ log_error ("%s:%d: Failed to send %d byte long "
+ "packet over %s interface.", MDL,
+ packet_length,
+ fallback_interface->name);
+ }
+
+
+ free_lease_state (state, MDL);
+ lease -> state = (struct lease_state *)0;
+ return;
+ }
+
+ /* If the client is RENEWING, unicast to the client using the
+ regular IP stack. Some clients, particularly those that
+ follow RFC1541, are buggy, and send both ciaddr and server
+ identifier. We deal with this situation by assuming that
+ if we got both dhcp-server-identifier and ciaddr, and
+ giaddr was not set, then the client is on the local
+ network, and we can therefore unicast or broadcast to it
+ successfully. A client in REQUESTING state on another
+ network that's making this mistake will have set giaddr,
+ and will therefore get a relayed response from the above
+ code. */
+ } else if (raw.ciaddr.s_addr &&
+ !((state -> got_server_identifier ||
+ (raw.flags & htons (BOOTP_BROADCAST))) &&
+ /* XXX This won't work if giaddr isn't zero, but it is: */
+ (state -> shared_network ==
+ lease -> subnet -> shared_network)) &&
+ state -> offer == DHCPACK) {
+ to.sin_addr = raw.ciaddr;
+ to.sin_port = remote_port;
+
+ if (fallback_interface) {
+ result = send_packet(fallback_interface, NULL, &raw,
+ packet_length, raw.siaddr, &to,
+ NULL);
+ if (result < 0) {
+ log_error("%s:%d: Failed to send %d byte long"
+ " packet over %s interface.", MDL,
+ packet_length,
+ fallback_interface->name);
+ }
+
+ free_lease_state (state, MDL);
+ lease -> state = (struct lease_state *)0;
+ return;
+ }
+
+ /* If it comes from a client that already knows its address
+ and is not requesting a broadcast response, and we can
+ unicast to a client without using the ARP protocol, sent it
+ directly to that client. */
+ } else if (!(raw.flags & htons (BOOTP_BROADCAST)) &&
+ can_unicast_without_arp (state -> ip)) {
+ to.sin_addr = raw.yiaddr;
+ to.sin_port = remote_port;
+
+ /* Otherwise, broadcast it on the local network. */
+ } else {
+ to.sin_addr = limited_broadcast;
+ to.sin_port = remote_port;
+ if (!(lease -> flags & UNICAST_BROADCAST_HACK))
+ unicastp = 0;
+ }
+
+ memcpy (&from, state -> from.iabuf, sizeof from);
+
+ result = send_packet(state->ip, NULL, &raw, packet_length,
+ from, &to, unicastp ? &hto : NULL);
+ if (result < 0) {
+ log_error ("%s:%d: Failed to send %d byte long "
+ "packet over %s interface.", MDL,
+ packet_length, state->ip->name);
+ }
+
+
+ /* Free all of the entries in the option_state structure
+ now that we're done with them. */
+
+ free_lease_state (state, MDL);
+ lease -> state = (struct lease_state *)0;
+}
+
+int find_lease (struct lease **lp,
+ struct packet *packet, struct shared_network *share, int *ours,
+ int *peer_has_leases, struct lease *ip_lease_in,
+ const char *file, int line)
+{
+ struct lease *uid_lease = (struct lease *)0;
+ struct lease *ip_lease = (struct lease *)0;
+ struct lease *hw_lease = (struct lease *)0;
+ struct lease *lease = (struct lease *)0;
+ struct iaddr cip;
+ struct host_decl *hp = (struct host_decl *)0;
+ struct host_decl *host = (struct host_decl *)0;
+ struct lease *fixed_lease = (struct lease *)0;
+ struct lease *next = (struct lease *)0;
+ struct option_cache *oc;
+ struct data_string d1;
+ int have_client_identifier = 0;
+ struct data_string client_identifier;
+ struct hardware h;
+
+#if defined(FAILOVER_PROTOCOL)
+ /* Quick check to see if the peer has leases. */
+ if (peer_has_leases) {
+ struct pool *pool;
+
+ for (pool = share->pools ; pool ; pool = pool->next) {
+ dhcp_failover_state_t *peer = pool->failover_peer;
+
+ if (peer &&
+ ((peer->i_am == primary && pool->backup_leases) ||
+ (peer->i_am == secondary && pool->free_leases))) {
+ *peer_has_leases = 1;
+ break;
+ }
+ }
+ }
+#endif /* FAILOVER_PROTOCOL */
+
+ if (packet -> raw -> ciaddr.s_addr) {
+ cip.len = 4;
+ memcpy (cip.iabuf, &packet -> raw -> ciaddr, 4);
+ } else {
+ /* Look up the requested address. */
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_REQUESTED_ADDRESS);
+ memset (&d1, 0, sizeof d1);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ packet -> got_requested_address = 1;
+ cip.len = 4;
+ memcpy (cip.iabuf, d1.data, cip.len);
+ data_string_forget (&d1, MDL);
+ } else
+ cip.len = 0;
+ }
+
+ /* Try to find a host or lease that's been assigned to the
+ specified unique client identifier. */
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_CLIENT_IDENTIFIER);
+ memset (&client_identifier, 0, sizeof client_identifier);
+ if (oc &&
+ evaluate_option_cache (&client_identifier,
+ packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ /* Remember this for later. */
+ have_client_identifier = 1;
+
+ /* First, try to find a fixed host entry for the specified
+ client identifier... */
+ if (find_hosts_by_uid (&hp, client_identifier.data,
+ client_identifier.len, MDL)) {
+ /* Remember if we know of this client. */
+ packet -> known = 1;
+ mockup_lease (&fixed_lease, packet, share, hp);
+ }
+
+#if defined (DEBUG_FIND_LEASE)
+ if (fixed_lease) {
+ log_info ("Found host for client identifier: %s.",
+ piaddr (fixed_lease -> ip_addr));
+ }
+#endif
+ if (hp) {
+ if (!fixed_lease) /* Save the host if we found one. */
+ host_reference (&host, hp, MDL);
+ host_dereference (&hp, MDL);
+ }
+
+ find_lease_by_uid (&uid_lease, client_identifier.data,
+ client_identifier.len, MDL);
+ }
+
+ /* If we didn't find a fixed lease using the uid, try doing
+ it with the hardware address... */
+ if (!fixed_lease && !host) {
+ if (find_hosts_by_haddr (&hp, packet -> raw -> htype,
+ packet -> raw -> chaddr,
+ packet -> raw -> hlen, MDL)) {
+ /* Remember if we know of this client. */
+ packet -> known = 1;
+ if (host)
+ host_dereference (&host, MDL);
+ host_reference (&host, hp, MDL);
+ host_dereference (&hp, MDL);
+ mockup_lease (&fixed_lease, packet, share, host);
+#if defined (DEBUG_FIND_LEASE)
+ if (fixed_lease) {
+ log_info ("Found host for link address: %s.",
+ piaddr (fixed_lease -> ip_addr));
+ }
+#endif
+ }
+ }
+
+ /* Finally, if we haven't found anything yet try again with the
+ * host-identifier option ... */
+ if (!fixed_lease && !host) {
+ if (find_hosts_by_option(&hp, packet,
+ packet->options, MDL) == 1) {
+ packet->known = 1;
+ if (host)
+ host_dereference(&host, MDL);
+ host_reference(&host, hp, MDL);
+ host_dereference(&hp, MDL);
+ mockup_lease (&fixed_lease, packet, share, host);
+#if defined (DEBUG_FIND_LEASE)
+ if (fixed_lease) {
+ log_info ("Found host via host-identifier");
+ }
+#endif
+ }
+ }
+
+ /* If fixed_lease is present but does not match the requested
+ IP address, and this is a DHCPREQUEST, then we can't return
+ any other lease, so we might as well return now. */
+ if (packet -> packet_type == DHCPREQUEST && fixed_lease &&
+ (fixed_lease -> ip_addr.len != cip.len ||
+ memcmp (fixed_lease -> ip_addr.iabuf,
+ cip.iabuf, cip.len))) {
+ if (ours)
+ *ours = 1;
+ strcpy (dhcp_message, "requested address is incorrect");
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("Client's fixed-address %s doesn't match %s%s",
+ piaddr (fixed_lease -> ip_addr), "request ",
+ print_dotted_quads (cip.len, cip.iabuf));
+#endif
+ goto out;
+ }
+
+ /*
+ * If we found leases matching the client identifier, loop through
+ * the n_uid pointer looking for one that's actually valid. We
+ * can't do this until we get here because we depend on
+ * packet -> known, which may be set by either the uid host
+ * lookup or the haddr host lookup.
+ *
+ * Note that the n_uid lease chain is sorted in order of
+ * preference, so the first one is the best one.
+ */
+ while (uid_lease) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("trying next lease matching client id: %s",
+ piaddr (uid_lease -> ip_addr));
+#endif
+
+#if defined (FAILOVER_PROTOCOL)
+ /*
+ * When we lookup a lease by uid, we know the client identifier
+ * matches the lease's record. If it is active, or was last
+ * active with the same client, we can trivially extend it.
+ * If is not or was not active, we can allocate it to this
+ * client if it matches the usual free/backup criteria (which
+ * is contained in lease_mine_to_reallocate()).
+ */
+ if (uid_lease->binding_state != FTS_ACTIVE &&
+ uid_lease->rewind_binding_state != FTS_ACTIVE &&
+ !lease_mine_to_reallocate(uid_lease)) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info("not active or not mine to allocate: %s",
+ piaddr(uid_lease->ip_addr));
+#endif
+ goto n_uid;
+ }
+#endif
+
+ if (uid_lease -> subnet -> shared_network != share) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("wrong network segment: %s",
+ piaddr (uid_lease -> ip_addr));
+#endif
+ goto n_uid;
+ }
+
+ if ((uid_lease -> pool -> prohibit_list &&
+ permitted (packet, uid_lease -> pool -> prohibit_list)) ||
+ (uid_lease -> pool -> permit_list &&
+ !permitted (packet, uid_lease -> pool -> permit_list))) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("not permitted: %s",
+ piaddr (uid_lease -> ip_addr));
+#endif
+ n_uid:
+ if (uid_lease -> n_uid)
+ lease_reference (&next,
+ uid_lease -> n_uid, MDL);
+ if (!packet -> raw -> ciaddr.s_addr)
+ release_lease (uid_lease, packet);
+ lease_dereference (&uid_lease, MDL);
+ if (next) {
+ lease_reference (&uid_lease, next, MDL);
+ lease_dereference (&next, MDL);
+ }
+ continue;
+ }
+ break;
+ }
+#if defined (DEBUG_FIND_LEASE)
+ if (uid_lease)
+ log_info ("Found lease for client id: %s.",
+ piaddr (uid_lease -> ip_addr));
+#endif
+
+ /* Find a lease whose hardware address matches, whose client
+ * identifier matches (or equally doesn't have one), that's
+ * permitted, and that's on the correct subnet.
+ *
+ * Note that the n_hw chain is sorted in order of preference, so
+ * the first one found is the best one.
+ */
+ h.hlen = packet -> raw -> hlen + 1;
+ h.hbuf [0] = packet -> raw -> htype;
+ memcpy (&h.hbuf [1], packet -> raw -> chaddr, packet -> raw -> hlen);
+ find_lease_by_hw_addr (&hw_lease, h.hbuf, h.hlen, MDL);
+ while (hw_lease) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("trying next lease matching hw addr: %s",
+ piaddr (hw_lease -> ip_addr));
+#endif
+#if defined (FAILOVER_PROTOCOL)
+ /*
+ * When we lookup a lease by chaddr, we know the MAC address
+ * matches the lease record (we will check if the lease has a
+ * client-id the client does not next). If the lease is
+ * currently active or was last active with this client, we can
+ * trivially extend it. Otherwise, there are a set of rules
+ * that govern if we can reallocate this lease to any client
+ * ("lease_mine_to_reallocate()") including this one.
+ */
+ if (hw_lease->binding_state != FTS_ACTIVE &&
+ hw_lease->rewind_binding_state != FTS_ACTIVE &&
+ !lease_mine_to_reallocate(hw_lease)) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info("not active or not mine to allocate: %s",
+ piaddr(hw_lease->ip_addr));
+#endif
+ goto n_hw;
+ }
+#endif
+
+ /*
+ * This conditional skips "potentially active" leases (leases
+ * we think are expired may be extended by the peer, etc) that
+ * may be assigned to a differently /client-identified/ client
+ * with the same MAC address.
+ */
+ if (hw_lease -> binding_state != FTS_FREE &&
+ hw_lease -> binding_state != FTS_BACKUP &&
+ hw_lease -> uid &&
+ (!have_client_identifier ||
+ hw_lease -> uid_len != client_identifier.len ||
+ memcmp (hw_lease -> uid, client_identifier.data,
+ hw_lease -> uid_len))) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("wrong client identifier: %s",
+ piaddr (hw_lease -> ip_addr));
+#endif
+ goto n_hw;
+ }
+ if (hw_lease -> subnet -> shared_network != share) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("wrong network segment: %s",
+ piaddr (hw_lease -> ip_addr));
+#endif
+ goto n_hw;
+ }
+ if ((hw_lease -> pool -> prohibit_list &&
+ permitted (packet, hw_lease -> pool -> prohibit_list)) ||
+ (hw_lease -> pool -> permit_list &&
+ !permitted (packet, hw_lease -> pool -> permit_list))) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("not permitted: %s",
+ piaddr (hw_lease -> ip_addr));
+#endif
+ if (!packet -> raw -> ciaddr.s_addr)
+ release_lease (hw_lease, packet);
+ n_hw:
+ if (hw_lease -> n_hw)
+ lease_reference (&next, hw_lease -> n_hw, MDL);
+ lease_dereference (&hw_lease, MDL);
+ if (next) {
+ lease_reference (&hw_lease, next, MDL);
+ lease_dereference (&next, MDL);
+ }
+ continue;
+ }
+ break;
+ }
+#if defined (DEBUG_FIND_LEASE)
+ if (hw_lease)
+ log_info ("Found lease for hardware address: %s.",
+ piaddr (hw_lease -> ip_addr));
+#endif
+
+ /* Try to find a lease that's been allocated to the client's
+ IP address. */
+ if (ip_lease_in)
+ lease_reference (&ip_lease, ip_lease_in, MDL);
+ else if (cip.len)
+ find_lease_by_ip_addr (&ip_lease, cip, MDL);
+
+#if defined (DEBUG_FIND_LEASE)
+ if (ip_lease)
+ log_info ("Found lease for requested address: %s.",
+ piaddr (ip_lease -> ip_addr));
+#endif
+
+ /* If ip_lease is valid at this point, set ours to one, so that
+ even if we choose a different lease, we know that the address
+ the client was requesting was ours, and thus we can NAK it. */
+ if (ip_lease && ours)
+ *ours = 1;
+
+ /* If the requested IP address isn't on the network the packet
+ came from, don't use it. Allow abandoned leases to be matched
+ here - if the client is requesting it, there's a decent chance
+ that it's because the lease database got trashed and a client
+ that thought it had this lease answered an ARP or PING, causing the
+ lease to be abandoned. If so, this request probably came from
+ that client. */
+ if (ip_lease && (ip_lease -> subnet -> shared_network != share)) {
+ if (ours)
+ *ours = 1;
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("...but it was on the wrong shared network.");
+#endif
+ strcpy (dhcp_message, "requested address on bad subnet");
+ lease_dereference (&ip_lease, MDL);
+ }
+
+ /*
+ * If the requested address is in use (or potentially in use) by
+ * a different client, it can't be granted.
+ *
+ * This first conditional only detects if the lease is currently
+ * identified to a different client (client-id and/or chaddr
+ * mismatch). In this case we may not want to give the client the
+ * lease, if doing so may potentially be an addressing conflict.
+ */
+ if (ip_lease &&
+ (ip_lease -> uid ?
+ (!have_client_identifier ||
+ ip_lease -> uid_len != client_identifier.len ||
+ memcmp (ip_lease -> uid, client_identifier.data,
+ ip_lease -> uid_len)) :
+ (ip_lease -> hardware_addr.hbuf [0] != packet -> raw -> htype ||
+ ip_lease -> hardware_addr.hlen != packet -> raw -> hlen + 1 ||
+ memcmp (&ip_lease -> hardware_addr.hbuf [1],
+ packet -> raw -> chaddr,
+ (unsigned)(ip_lease -> hardware_addr.hlen - 1))))) {
+ /*
+ * A lease is unavailable for allocation to a new client if
+ * it is not in the FREE or BACKUP state. There may be
+ * leases that are in the expired state with a rewinding
+ * state that is free or backup, but these will be processed
+ * into the free or backup states by expiration processes, so
+ * checking for them here is superfluous.
+ */
+ if (ip_lease -> binding_state != FTS_FREE &&
+ ip_lease -> binding_state != FTS_BACKUP) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("rejecting lease for requested address.");
+#endif
+ /* If we're rejecting it because the peer has
+ it, don't set "ours", because we shouldn't NAK. */
+ if (ours && ip_lease -> binding_state != FTS_ACTIVE)
+ *ours = 0;
+ lease_dereference (&ip_lease, MDL);
+ }
+ }
+
+ /*
+ * If we got an ip_lease and a uid_lease or hw_lease, and ip_lease
+ * is/was not active, and is not ours to reallocate, forget about it.
+ */
+ if (ip_lease && (uid_lease || hw_lease) &&
+ ip_lease->binding_state != FTS_ACTIVE &&
+ ip_lease->rewind_binding_state != FTS_ACTIVE &&
+#if defined(FAILOVER_PROTOCOL)
+ !lease_mine_to_reallocate(ip_lease) &&
+#endif
+ packet->packet_type == DHCPDISCOVER) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info("ip lease not active or not ours to offer.");
+#endif
+ lease_dereference(&ip_lease, MDL);
+ }
+
+ /* If for some reason the client has more than one lease
+ on the subnet that matches its uid, pick the one that
+ it asked for and (if we can) free the other. */
+ if (ip_lease && ip_lease->binding_state == FTS_ACTIVE &&
+ ip_lease->uid && ip_lease != uid_lease) {
+ if (have_client_identifier &&
+ (ip_lease -> uid_len == client_identifier.len) &&
+ !memcmp (client_identifier.data,
+ ip_lease -> uid, ip_lease -> uid_len)) {
+ if (uid_lease) {
+ if (uid_lease->binding_state == FTS_ACTIVE) {
+ log_error ("client %s has duplicate%s on %s",
+ (print_hw_addr
+ (packet -> raw -> htype,
+ packet -> raw -> hlen,
+ packet -> raw -> chaddr)),
+ " leases",
+ (ip_lease -> subnet ->
+ shared_network -> name));
+
+ /* If the client is REQUESTing the lease,
+ it shouldn't still be using the old
+ one, so we can free it for allocation. */
+ if (uid_lease &&
+ uid_lease->binding_state == FTS_ACTIVE &&
+ !packet -> raw -> ciaddr.s_addr &&
+ (share ==
+ uid_lease -> subnet -> shared_network) &&
+ packet -> packet_type == DHCPREQUEST)
+ release_lease (uid_lease, packet);
+ }
+ lease_dereference (&uid_lease, MDL);
+ lease_reference (&uid_lease, ip_lease, MDL);
+ }
+ }
+
+ /* If we get to here and fixed_lease is not null, that means
+ that there are both a dynamic lease and a fixed-address
+ declaration for the same IP address. */
+ if (packet -> packet_type == DHCPREQUEST && fixed_lease) {
+ lease_dereference (&fixed_lease, MDL);
+ db_conflict:
+ log_error ("Dynamic and static leases present for %s.",
+ piaddr (cip));
+ log_error ("Remove host declaration %s or remove %s",
+ (fixed_lease && fixed_lease -> host
+ ? (fixed_lease -> host -> name
+ ? fixed_lease -> host -> name
+ : piaddr (cip))
+ : piaddr (cip)),
+ piaddr (cip));
+ log_error ("from the dynamic address pool for %s",
+ ip_lease -> subnet -> shared_network -> name
+ );
+ if (fixed_lease)
+ lease_dereference (&ip_lease, MDL);
+ strcpy (dhcp_message,
+ "database conflict - call for help!");
+ }
+
+ if (ip_lease && ip_lease != uid_lease) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("requested address not available.");
+#endif
+ lease_dereference (&ip_lease, MDL);
+ }
+ }
+
+ /* If we get to here with both fixed_lease and ip_lease not
+ null, then we have a configuration file bug. */
+ if (packet -> packet_type == DHCPREQUEST && fixed_lease && ip_lease)
+ goto db_conflict;
+
+ /* Toss extra pointers to the same lease... */
+ if (hw_lease && hw_lease == uid_lease) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("hardware lease and uid lease are identical.");
+#endif
+ lease_dereference (&hw_lease, MDL);
+ }
+ if (ip_lease && ip_lease == hw_lease) {
+ lease_dereference (&hw_lease, MDL);
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("hardware lease and ip lease are identical.");
+#endif
+ }
+ if (ip_lease && ip_lease == uid_lease) {
+ lease_dereference (&uid_lease, MDL);
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("uid lease and ip lease are identical.");
+#endif
+ }
+
+ /* Make sure the client is permitted to use the requested lease. */
+ if (ip_lease &&
+ ((ip_lease -> pool -> prohibit_list &&
+ permitted (packet, ip_lease -> pool -> prohibit_list)) ||
+ (ip_lease -> pool -> permit_list &&
+ !permitted (packet, ip_lease -> pool -> permit_list)))) {
+ if (!packet->raw->ciaddr.s_addr &&
+ (ip_lease->binding_state == FTS_ACTIVE))
+ release_lease (ip_lease, packet);
+
+ lease_dereference (&ip_lease, MDL);
+ }
+
+ if (uid_lease &&
+ ((uid_lease -> pool -> prohibit_list &&
+ permitted (packet, uid_lease -> pool -> prohibit_list)) ||
+ (uid_lease -> pool -> permit_list &&
+ !permitted (packet, uid_lease -> pool -> permit_list)))) {
+ if (!packet -> raw -> ciaddr.s_addr)
+ release_lease (uid_lease, packet);
+ lease_dereference (&uid_lease, MDL);
+ }
+
+ if (hw_lease &&
+ ((hw_lease -> pool -> prohibit_list &&
+ permitted (packet, hw_lease -> pool -> prohibit_list)) ||
+ (hw_lease -> pool -> permit_list &&
+ !permitted (packet, hw_lease -> pool -> permit_list)))) {
+ if (!packet -> raw -> ciaddr.s_addr)
+ release_lease (hw_lease, packet);
+ lease_dereference (&hw_lease, MDL);
+ }
+
+ /* If we've already eliminated the lease, it wasn't there to
+ begin with. If we have come up with a matching lease,
+ set the message to bad network in case we have to throw it out. */
+ if (!ip_lease) {
+ strcpy (dhcp_message, "requested address not available");
+ }
+
+ /* If this is a DHCPREQUEST, make sure the lease we're going to return
+ matches the requested IP address. If it doesn't, don't return a
+ lease at all. */
+ if (packet -> packet_type == DHCPREQUEST &&
+ !ip_lease && !fixed_lease) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("no applicable lease found for DHCPREQUEST.");
+#endif
+ goto out;
+ }
+
+ /* At this point, if fixed_lease is nonzero, we can assign it to
+ this client. */
+ if (fixed_lease) {
+ lease_reference (&lease, fixed_lease, MDL);
+ lease_dereference (&fixed_lease, MDL);
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("choosing fixed address.");
+#endif
+ }
+
+ /* If we got a lease that matched the ip address and don't have
+ a better offer, use that; otherwise, release it. */
+ if (ip_lease) {
+ if (lease) {
+ if (!packet -> raw -> ciaddr.s_addr)
+ release_lease (ip_lease, packet);
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("not choosing requested address (!).");
+#endif
+ } else {
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("choosing lease on requested address.");
+#endif
+ lease_reference (&lease, ip_lease, MDL);
+ if (lease -> host)
+ host_dereference (&lease -> host, MDL);
+ }
+ lease_dereference (&ip_lease, MDL);
+ }
+
+ /* If we got a lease that matched the client identifier, we may want
+ to use it, but if we already have a lease we like, we must free
+ the lease that matched the client identifier. */
+ if (uid_lease) {
+ if (lease) {
+ log_error("uid lease %s for client %s is duplicate "
+ "on %s",
+ piaddr(uid_lease->ip_addr),
+ print_hw_addr(packet->raw->htype,
+ packet->raw->hlen,
+ packet->raw->chaddr),
+ uid_lease->subnet->shared_network->name);
+
+ if (!packet -> raw -> ciaddr.s_addr &&
+ packet -> packet_type == DHCPREQUEST &&
+ uid_lease -> binding_state == FTS_ACTIVE)
+ release_lease(uid_lease, packet);
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("not choosing uid lease.");
+#endif
+ } else {
+ lease_reference (&lease, uid_lease, MDL);
+ if (lease -> host)
+ host_dereference (&lease -> host, MDL);
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("choosing uid lease.");
+#endif
+ }
+ lease_dereference (&uid_lease, MDL);
+ }
+
+ /* The lease that matched the hardware address is treated likewise. */
+ if (hw_lease) {
+ if (lease) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("not choosing hardware lease.");
+#endif
+ } else {
+ /* We're a little lax here - if the client didn't
+ send a client identifier and it's a bootp client,
+ but the lease has a client identifier, we still
+ let the client have a lease. */
+ if (!hw_lease -> uid_len ||
+ (have_client_identifier
+ ? (hw_lease -> uid_len ==
+ client_identifier.len &&
+ !memcmp (hw_lease -> uid,
+ client_identifier.data,
+ client_identifier.len))
+ : packet -> packet_type == 0)) {
+ lease_reference (&lease, hw_lease, MDL);
+ if (lease -> host)
+ host_dereference (&lease -> host, MDL);
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("choosing hardware lease.");
+#endif
+ } else {
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("not choosing hardware lease: %s.",
+ "uid mismatch");
+#endif
+ }
+ }
+ lease_dereference (&hw_lease, MDL);
+ }
+
+ /*
+ * If we found a host_decl but no matching address, try to
+ * find a host_decl that has no address, and if there is one,
+ * hang it off the lease so that we can use the supplied
+ * options.
+ */
+ if (lease && host && !lease->host) {
+ struct host_decl *p = NULL;
+ struct host_decl *n = NULL;
+
+ host_reference(&p, host, MDL);
+ while (p != NULL) {
+ if (!p->fixed_addr) {
+ /*
+ * If the lease is currently active, then it
+ * must be allocated to the present client.
+ * We store a reference to the host record on
+ * the lease to save a lookup later (in
+ * ack_lease()). We mustn't refer to the host
+ * record on non-active leases because the
+ * client may be denied later.
+ *
+ * XXX: Not having this reference (such as in
+ * DHCPDISCOVER/INIT) means ack_lease will have
+ * to perform this lookup a second time. This
+ * hopefully isn't a problem as DHCPREQUEST is
+ * more common than DHCPDISCOVER.
+ */
+ if (lease->binding_state == FTS_ACTIVE)
+ host_reference(&lease->host, p, MDL);
+
+ host_dereference(&p, MDL);
+ break;
+ }
+ if (p->n_ipaddr != NULL)
+ host_reference(&n, p->n_ipaddr, MDL);
+ host_dereference(&p, MDL);
+ if (n != NULL) {
+ host_reference(&p, n, MDL);
+ host_dereference(&n, MDL);
+ }
+ }
+ }
+
+ /* If we find an abandoned lease, but it's the one the client
+ requested, we assume that previous bugginess on the part
+ of the client, or a server database loss, caused the lease to
+ be abandoned, so we reclaim it and let the client have it. */
+ if (lease &&
+ (lease -> binding_state == FTS_ABANDONED) &&
+ lease == ip_lease &&
+ packet -> packet_type == DHCPREQUEST) {
+ log_error ("Reclaiming REQUESTed abandoned IP address %s.",
+ piaddr (lease -> ip_addr));
+ } else if (lease && (lease -> binding_state == FTS_ABANDONED)) {
+ /* Otherwise, if it's not the one the client requested, we do not
+ return it - instead, we claim it's ours, causing a DHCPNAK to be
+ sent if this lookup is for a DHCPREQUEST, and force the client
+ to go back through the allocation process. */
+ if (ours)
+ *ours = 1;
+ lease_dereference (&lease, MDL);
+ }
+
+ out:
+ if (have_client_identifier)
+ data_string_forget (&client_identifier, MDL);
+
+ if (fixed_lease)
+ lease_dereference (&fixed_lease, MDL);
+ if (hw_lease)
+ lease_dereference (&hw_lease, MDL);
+ if (uid_lease)
+ lease_dereference (&uid_lease, MDL);
+ if (ip_lease)
+ lease_dereference (&ip_lease, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+
+ if (lease) {
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("Returning lease: %s.",
+ piaddr (lease -> ip_addr));
+#endif
+ lease_reference (lp, lease, file, line);
+ lease_dereference (&lease, MDL);
+ return 1;
+ }
+#if defined (DEBUG_FIND_LEASE)
+ log_info ("Not returning a lease.");
+#endif
+ return 0;
+}
+
+/* Search the provided host_decl structure list for an address that's on
+ the specified shared network. If one is found, mock up and return a
+ lease structure for it; otherwise return the null pointer. */
+
+int mockup_lease (struct lease **lp, struct packet *packet,
+ struct shared_network *share, struct host_decl *hp)
+{
+ struct lease *lease = (struct lease *)0;
+ struct host_decl *rhp = (struct host_decl *)0;
+
+ if (lease_allocate (&lease, MDL) != ISC_R_SUCCESS)
+ return 0;
+ if (host_reference (&rhp, hp, MDL) != ISC_R_SUCCESS) {
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+ if (!find_host_for_network (&lease -> subnet,
+ &rhp, &lease -> ip_addr, share)) {
+ lease_dereference (&lease, MDL);
+ host_dereference (&rhp, MDL);
+ return 0;
+ }
+ host_reference (&lease -> host, rhp, MDL);
+ if (rhp -> client_identifier.len > sizeof lease -> uid_buf)
+ lease -> uid = dmalloc (rhp -> client_identifier.len, MDL);
+ else
+ lease -> uid = lease -> uid_buf;
+ if (!lease -> uid) {
+ lease_dereference (&lease, MDL);
+ host_dereference (&rhp, MDL);
+ return 0;
+ }
+ memcpy (lease -> uid, rhp -> client_identifier.data,
+ rhp -> client_identifier.len);
+ lease -> uid_len = rhp -> client_identifier.len;
+ lease -> hardware_addr = rhp -> interface;
+ lease -> starts = lease -> cltt = lease -> ends = MIN_TIME;
+ lease -> flags = STATIC_LEASE;
+ lease -> binding_state = FTS_FREE;
+
+ lease_reference (lp, lease, MDL);
+
+ lease_dereference (&lease, MDL);
+ host_dereference (&rhp, MDL);
+ return 1;
+}
+
+/* Look through all the pools in a list starting with the specified pool
+ for a free lease. We try to find a virgin lease if we can. If we
+ don't find a virgin lease, we try to find a non-virgin lease that's
+ free. If we can't find one of those, we try to reclaim an abandoned
+ lease. If all of these possibilities fail to pan out, we don't return
+ a lease at all. */
+
+int allocate_lease (struct lease **lp, struct packet *packet,
+ struct pool *pool, int *peer_has_leases)
+{
+ struct lease *lease = (struct lease *)0;
+ struct lease *candl = (struct lease *)0;
+
+ for (; pool ; pool = pool -> next) {
+ if ((pool -> prohibit_list &&
+ permitted (packet, pool -> prohibit_list)) ||
+ (pool -> permit_list &&
+ !permitted (packet, pool -> permit_list)))
+ continue;
+
+#if defined (FAILOVER_PROTOCOL)
+ /* Peer_has_leases just says that we found at least one
+ free lease. If no free lease is returned, the caller
+ can deduce that this means the peer is hogging all the
+ free leases, so we can print a better error message. */
+ /* XXX Do we need code here to ignore PEER_IS_OWNER and
+ * XXX just check tstp if we're in, e.g., PARTNER_DOWN?
+ * XXX Where do we deal with CONFLICT_DETECTED, et al? */
+ /* XXX This should be handled by the lease binding "state
+ * XXX machine" - that is, when we get here, if a lease
+ * XXX could be allocated, it will have the correct
+ * XXX binding state so that the following code will
+ * XXX result in its being allocated. */
+ /* Skip to the most expired lease in the pool that is not
+ * owned by a failover peer. */
+ if (pool->failover_peer != NULL) {
+ if (pool->failover_peer->i_am == primary) {
+ candl = pool->free;
+
+ /*
+ * In normal operation, we never want to touch
+ * the peer's leases. In partner-down
+ * operation, we need to be able to pick up
+ * the peer's leases after STOS+MCLT.
+ */
+ if (pool->backup != NULL) {
+ if (((candl == NULL) ||
+ (candl->ends >
+ pool->backup->ends)) &&
+ lease_mine_to_reallocate(
+ pool->backup)) {
+ candl = pool->backup;
+ } else {
+ *peer_has_leases = 1;
+ }
+ }
+ } else {
+ candl = pool->backup;
+
+ if (pool->free != NULL) {
+ if (((candl == NULL) ||
+ (candl->ends >
+ pool->free->ends)) &&
+ lease_mine_to_reallocate(
+ pool->free)) {
+ candl = pool->free;
+ } else {
+ *peer_has_leases = 1;
+ }
+ }
+ }
+
+ /* Try abandoned leases as a last resort. */
+ if ((candl == NULL) &&
+ (pool->abandoned != NULL) &&
+ lease_mine_to_reallocate(pool->abandoned))
+ candl = pool->abandoned;
+ } else
+#endif
+ {
+ if (pool -> free)
+ candl = pool -> free;
+ else
+ candl = pool -> abandoned;
+ }
+
+ /*
+ * XXX: This may not match with documented expectation.
+ * It's expected that when we OFFER a lease, we set its
+ * ends time forward 2 minutes so that it gets sorted to
+ * the end of its free list (avoiding a similar allocation
+ * to another client). It is not expected that we issue a
+ * "no free leases" error when the last lease has been
+ * offered, but it's not exactly broken either.
+ */
+ if (!candl || (candl -> ends > cur_time))
+ continue;
+
+ if (!lease) {
+ lease = candl;
+ continue;
+ }
+
+ /*
+ * There are tiers of lease state preference, listed here in
+ * reverse order (least to most preferential):
+ *
+ * ABANDONED
+ * FREE/BACKUP
+ *
+ * If the selected lease and candidate are both of the same
+ * state, select the oldest (longest ago) expiration time
+ * between the two. If the candidate lease is of a higher
+ * preferred grade over the selected lease, use it.
+ */
+ if ((lease -> binding_state == FTS_ABANDONED) &&
+ ((candl -> binding_state != FTS_ABANDONED) ||
+ (candl -> ends < lease -> ends))) {
+ lease = candl;
+ continue;
+ } else if (candl -> binding_state == FTS_ABANDONED)
+ continue;
+
+ if ((lease -> uid_len || lease -> hardware_addr.hlen) &&
+ ((!candl -> uid_len && !candl -> hardware_addr.hlen) ||
+ (candl -> ends < lease -> ends))) {
+ lease = candl;
+ continue;
+ } else if (candl -> uid_len || candl -> hardware_addr.hlen)
+ continue;
+
+ if (candl -> ends < lease -> ends)
+ lease = candl;
+ }
+
+ if (lease != NULL) {
+ if (lease->binding_state == FTS_ABANDONED)
+ log_error("Reclaiming abandoned lease %s.",
+ piaddr(lease->ip_addr));
+
+ /*
+ * XXX: For reliability, we go ahead and remove the host
+ * record and try to move on. For correctness, if there
+ * are any other stale host vectors, we want to find them.
+ */
+ if (lease->host != NULL) {
+ log_debug("soft impossible condition (%s:%d): stale "
+ "host \"%s\" found on lease %s", MDL,
+ lease->host->name,
+ piaddr(lease->ip_addr));
+ host_dereference(&lease->host, MDL);
+ }
+
+ lease_reference (lp, lease, MDL);
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Determine whether or not a permit exists on a particular permit list
+ that matches the specified packet, returning nonzero if so, zero if
+ not. */
+
+int permitted (packet, permit_list)
+ struct packet *packet;
+ struct permit *permit_list;
+{
+ struct permit *p;
+ int i;
+
+ for (p = permit_list; p; p = p -> next) {
+ switch (p -> type) {
+ case permit_unknown_clients:
+ if (!packet -> known)
+ return 1;
+ break;
+
+ case permit_known_clients:
+ if (packet -> known)
+ return 1;
+ break;
+
+ case permit_authenticated_clients:
+ if (packet -> authenticated)
+ return 1;
+ break;
+
+ case permit_unauthenticated_clients:
+ if (!packet -> authenticated)
+ return 1;
+ break;
+
+ case permit_all_clients:
+ return 1;
+
+ case permit_dynamic_bootp_clients:
+ if (!packet -> options_valid ||
+ !packet -> packet_type)
+ return 1;
+ break;
+
+ case permit_class:
+ for (i = 0; i < packet -> class_count; i++) {
+ if (p -> class == packet -> classes [i])
+ return 1;
+ if (packet -> classes [i] &&
+ packet -> classes [i] -> superclass &&
+ (packet -> classes [i] -> superclass ==
+ p -> class))
+ return 1;
+ }
+ break;
+
+ case permit_after:
+ if (cur_time > p->after)
+ return 1;
+ break;
+ }
+ }
+ return 0;
+}
+
+int locate_network (packet)
+ struct packet *packet;
+{
+ struct iaddr ia;
+ struct data_string data;
+ struct subnet *subnet = (struct subnet *)0;
+ struct option_cache *oc;
+
+ /* See if there's a Relay Agent Link Selection Option, or a
+ * Subnet Selection Option. The Link-Select and Subnet-Select
+ * are formatted and used precisely the same, but we must prefer
+ * the link-select over the subnet-select.
+ */
+ if ((oc = lookup_option(&agent_universe, packet->options,
+ RAI_LINK_SELECT)) == NULL)
+ oc = lookup_option(&dhcp_universe, packet->options,
+ DHO_SUBNET_SELECTION);
+
+ /* If there's no SSO and no giaddr, then use the shared_network
+ from the interface, if there is one. If not, fail. */
+ if (!oc && !packet -> raw -> giaddr.s_addr) {
+ if (packet -> interface -> shared_network) {
+ shared_network_reference
+ (&packet -> shared_network,
+ packet -> interface -> shared_network, MDL);
+ return 1;
+ }
+ return 0;
+ }
+
+ /* If there's an option indicating link connection, and it's valid,
+ * use it to figure out the subnet. If it's not valid, fail.
+ */
+ if (oc) {
+ memset (&data, 0, sizeof data);
+ if (!evaluate_option_cache (&data, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ return 0;
+ }
+ if (data.len != 4) {
+ return 0;
+ }
+ ia.len = 4;
+ memcpy (ia.iabuf, data.data, 4);
+ data_string_forget (&data, MDL);
+ } else {
+ ia.len = 4;
+ memcpy (ia.iabuf, &packet -> raw -> giaddr, 4);
+ }
+
+ /* If we know the subnet on which the IP address lives, use it. */
+ if (find_subnet (&subnet, ia, MDL)) {
+ shared_network_reference (&packet -> shared_network,
+ subnet -> shared_network, MDL);
+ subnet_dereference (&subnet, MDL);
+ return 1;
+ }
+
+ /* Otherwise, fail. */
+ return 0;
+}
+
+/*
+ * Try to figure out the source address to send packets from.
+ *
+ * from is the address structure we use to return any address
+ * we find.
+ *
+ * options is the option cache to search. This may include
+ * options from the incoming packet and configuration information.
+ *
+ * out_options is the outgoing option cache. This cache
+ * may be the same as options. If send_options isn't NULL
+ * we may save the server address option into it. We do so
+ * if send_options is different than options or if the option
+ * wasn't in options and we needed to find the address elsewhere.
+ *
+ * packet is the state structure for the incoming packet
+ *
+ * When finding the address we first check to see if it is
+ * in the options list. If it isn't we use the first address
+ * from the interface.
+ *
+ * While this is slightly more complicated than I'd like it allows
+ * us to use the same code in several different places. ack,
+ * inform and lease query use it to find the address and fill
+ * in the options if we get the address from the interface.
+ * nack uses it to find the address and copy it to the outgoing
+ * cache. dhcprequest uses it to find the address for comparison
+ * and doesn't need to add it to an outgoing list.
+ */
+
+void
+get_server_source_address(struct in_addr *from,
+ struct option_state *options,
+ struct option_state *out_options,
+ struct packet *packet) {
+ unsigned option_num;
+ struct option_cache *oc = NULL;
+ struct data_string d;
+ struct in_addr *a = NULL;
+ isc_boolean_t found = ISC_FALSE;
+ int allocate = 0;
+
+ memset(&d, 0, sizeof(d));
+ memset(from, 0, sizeof(*from));
+
+ option_num = DHO_DHCP_SERVER_IDENTIFIER;
+ oc = lookup_option(&dhcp_universe, options, option_num);
+ if (oc != NULL) {
+ if (evaluate_option_cache(&d, packet, NULL, NULL,
+ packet->options, options,
+ &global_scope, oc, MDL)) {
+ if (d.len == sizeof(*from)) {
+ found = ISC_TRUE;
+ memcpy(from, d.data, sizeof(*from));
+
+ /*
+ * Arrange to save a copy of the data
+ * to the outgoing list.
+ */
+ if ((out_options != NULL) &&
+ (options != out_options)) {
+ a = from;
+ allocate = 1;
+ }
+ }
+ data_string_forget(&d, MDL);
+ }
+ oc = NULL;
+ }
+
+ if ((found == ISC_FALSE) &&
+ (packet->interface->address_count > 0)) {
+ *from = packet->interface->addresses[0];
+
+ if (out_options != NULL) {
+ a = &packet->interface->addresses[0];
+ }
+ }
+
+ if ((a != NULL) &&
+ (option_cache_allocate(&oc, MDL))) {
+ if (make_const_data(&oc->expression,
+ (unsigned char *)a, sizeof(*a),
+ 0, allocate, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &option_num, 0, MDL);
+ save_option(&dhcp_universe, out_options, oc);
+ }
+ option_cache_dereference(&oc, MDL);
+ }
+
+ return;
+}
+
+/*
+ * Set up an option state list to try and find a server option.
+ * We don't go through all possible options - in particualr we
+ * skip the hosts and we don't include the lease to avoid
+ * making changes to it. This means that we won't get the
+ * correct server id if the admin puts them on hosts or
+ * builds the server id with information from the lease.
+ *
+ * As this is a fallback function (used to handle NAKs or
+ * sort out server id mismatch in failover) and requires
+ * configuration by the admin, it should be okay.
+ */
+
+void
+setup_server_source_address(struct in_addr *from,
+ struct option_state *options,
+ struct packet *packet) {
+
+ struct option_state *sid_options = NULL;
+ option_state_allocate (&sid_options, MDL);
+
+ if (packet->shared_network != NULL) {
+ /*
+ * If we have a subnet and group start with that else start
+ * with the shared network group. The first will recurse and
+ * include the second.
+ */
+ if ((packet->shared_network->subnets != NULL) &&
+ (packet->shared_network->subnets->group != NULL)) {
+ execute_statements_in_scope(NULL, packet, NULL, NULL,
+ packet->options, sid_options,
+ &global_scope,
+ packet->shared_network->subnets->group,
+ NULL);
+ } else {
+ execute_statements_in_scope(NULL, packet, NULL, NULL,
+ packet->options, sid_options,
+ &global_scope,
+ packet->shared_network->group,
+ NULL);
+ }
+
+ /* do the pool if there is one */
+ if (packet->shared_network->pools != NULL) {
+ execute_statements_in_scope(NULL, packet, NULL, NULL,
+ packet->options, sid_options,
+ &global_scope,
+ packet->shared_network->pools->group,
+ packet->shared_network->group);
+ }
+
+ /* currently we don't bother with classes or hosts as
+ * neither seems to be useful in this case */
+ } else {
+ /* Look for global server identity for unknown networks */
+ execute_statements_in_scope(NULL, packet, NULL, NULL,
+ packet->options, sid_options,
+ &global_scope, root_group,
+ NULL);
+ }
+
+ /* Make the call to get the server address */
+ get_server_source_address(from, sid_options, options, packet);
+
+ /* get rid of the option cache */
+ if (sid_options != NULL)
+ option_state_dereference(&sid_options, MDL);
+}
+
+/*
+ * Look for the lowest numbered site code number and
+ * apply a log warning if it is less than 224. Do not
+ * permit site codes less than 128 (old code never did).
+ *
+ * Note that we could search option codes 224 down to 128
+ * on the hash table, but the table is (probably) smaller
+ * than that if it was declared as a standalone table with
+ * defaults. So we traverse the option code hash.
+ */
+static int
+find_min_site_code(struct universe *u)
+{
+ if (u->site_code_min)
+ return u->site_code_min;
+
+ /*
+ * Note that site_code_min has to be global as we can't pass an
+ * argument through hash_foreach(). The value 224 is taken from
+ * RFC 3942.
+ */
+ site_code_min = 224;
+ option_code_hash_foreach(u->code_hash, lowest_site_code);
+
+ if (site_code_min < 224) {
+ log_error("WARNING: site-local option codes less than 224 have "
+ "been deprecated by RFC3942. You have options "
+ "listed in site local space %s that number as low as "
+ "%d. Please investigate if these should be declared "
+ "as regular options rather than site-local options, "
+ "or migrated up past 224.",
+ u->name, site_code_min);
+ }
+
+ /*
+ * don't even bother logging, this is just silly, and never worked
+ * on any old version of software.
+ */
+ if (site_code_min < 128)
+ site_code_min = 128;
+
+ /*
+ * Cache the determined minimum site code on the universe structure.
+ * Note that due to the < 128 check above, a value of zero is
+ * impossible.
+ */
+ u->site_code_min = site_code_min;
+
+ return site_code_min;
+}
+
+static isc_result_t
+lowest_site_code(const void *key, unsigned len, void *object)
+{
+ struct option *option = object;
+
+ if (option->code < site_code_min)
+ site_code_min = option->code;
+
+ return ISC_R_SUCCESS;
+}
+
+static void
+maybe_return_agent_options(struct packet *packet, struct option_state *options)
+{
+ /* If there were agent options in the incoming packet, return
+ * them. Do not return the agent options if they were stashed
+ * on the lease. We do not check giaddr to detect the presence of
+ * a relay, as this excludes "l2" relay agents which have no giaddr
+ * to set.
+ *
+ * XXX: If the user configures options for the relay agent information
+ * (state->options->universes[agent_universe.index] is not NULL),
+ * we're still required to duplicate other values provided by the
+ * relay agent. So we need to merge the old values not configured
+ * by the user into the new state, not just give up.
+ */
+ if (!packet->agent_options_stashed &&
+ (packet->options != NULL) &&
+ packet->options->universe_count > agent_universe.index &&
+ packet->options->universes[agent_universe.index] != NULL &&
+ (options->universe_count <= agent_universe.index ||
+ options->universes[agent_universe.index] == NULL)) {
+ option_chain_head_reference
+ ((struct option_chain_head **)
+ &(options->universes[agent_universe.index]),
+ (struct option_chain_head *)
+ packet->options->universes[agent_universe.index], MDL);
+
+ if (options->universe_count <= agent_universe.index)
+ options->universe_count = agent_universe.index + 1;
+ }
+}
+
+/*!
+ * \brief Adds hostname option when use-host-decl-names is enabled.
+ *
+ * Constructs a hostname option from the name of the host declaration if
+ * there is one and no hostname has otherwise been provided and the
+ * use-host-decl-names flag is set, then adds the new option to the given
+ * option_state. This funciton is used for both bootp and dhcp.
+ *
+ * \param packet inbound packet received from the client
+ * \param lease lease associated with the client
+ * \param options option state to search and update
+ */
+void use_host_decl_name(struct packet* packet,
+ struct lease *lease,
+ struct option_state *options) {
+ unsigned int ocode = SV_USE_HOST_DECL_NAMES;
+ if ((lease->host && lease->host->name) &&
+ !lookup_option(&dhcp_universe, options, DHO_HOST_NAME) &&
+ (evaluate_boolean_option_cache(NULL, packet, lease, NULL,
+ packet->options, options,
+ &lease->scope,
+ lookup_option(&server_universe,
+ options, ocode),
+ MDL))) {
+ struct option_cache *oc = NULL;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data(&oc -> expression,
+ ((unsigned char*)lease->host->name),
+ strlen(lease->host->name),
+ 1, 0, MDL)) {
+ ocode = DHO_HOST_NAME;
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &ocode, 0, MDL);
+ save_option(&dhcp_universe, options, oc);
+ }
+ option_cache_dereference(&oc, MDL);
+ }
+ }
+}
diff --git a/server/dhcpd.8 b/server/dhcpd.8
new file mode 100644
index 0000000..b9e87f2
--- /dev/null
+++ b/server/dhcpd.8
@@ -0,0 +1,805 @@
+.\" dhcpd.8
+.\"
+.\" 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/
+.\"
+.\" This software has been written for Internet Systems Consortium
+.\" by Ted Lemon in cooperation with Vixie Enterprises and Nominum, Inc.
+.\"
+.\" Support and other services are available for ISC products - see
+.\" https://www.isc.org for more information or to learn more about ISC.
+.\"
+.\" $Id: dhcpd.8,v 1.30.24.5 2011/05/20 14:33:28 tomasz Exp $
+.\"
+.TH dhcpd 8
+.SH NAME
+dhcpd - Dynamic Host Configuration Protocol Server
+.SH SYNOPSIS
+.B dhcpd
+[
+.B -p
+.I port
+]
+[
+.B -f
+]
+[
+.B -d
+]
+[
+.B -q
+]
+[
+.B -t
+|
+.B -T
+]
+[
+.B -4
+|
+.B -6
+]
+[
+.B -s
+.I server
+]
+[
+.B -cf
+.I config-file
+]
+[
+.B -lf
+.I lease-file
+]
+[
+.B -pf
+.I pid-file
+]
+[
+.B --no-pid
+]
+[
+.B -tf
+.I trace-output-file
+]
+[
+.B -play
+.I trace-playback-file
+]
+[
+.I if0
+[
+.I ...ifN
+]
+]
+
+.B dhcpd
+--version
+.SH DESCRIPTION
+The Internet Systems Consortium DHCP Server, dhcpd, implements the
+Dynamic Host Configuration Protocol (DHCP) and the Internet Bootstrap
+Protocol (BOOTP). DHCP allows hosts on a TCP/IP network to request
+and be assigned IP addresses, and also to discover information about
+the network to which they are attached. BOOTP provides similar
+functionality, with certain restrictions.
+.SH OPERATION
+.PP
+The DHCP protocol allows a host which is unknown to the network
+administrator to be automatically assigned a new IP address out of a
+pool of IP addresses for its network. In order for this to work, the
+network administrator allocates address pools in each subnet and
+enters them into the dhcpd.conf(5) file.
+.PP
+There are two versions of the DHCP protocol DHCPv4 and DHCPv6. At
+startup the server may be started for one or the other via the
+.B -4
+or
+.B -6
+arguments.
+.PP
+On startup, dhcpd reads the
+.IR dhcpd.conf
+file and stores a list of available addresses on each subnet in
+memory. When a client requests an address using the DHCP protocol,
+dhcpd allocates an address for it. Each client is assigned a lease,
+which expires after an amount of time chosen by the administrator (by
+default, one day). Before leases expire, the clients to which leases
+are assigned are expected to renew them in order to continue to use
+the addresses. Once a lease has expired, the client to which that
+lease was assigned is no longer permitted to use the leased IP
+address.
+.PP
+In order to keep track of leases across system reboots and server
+restarts, dhcpd keeps a list of leases it has assigned in the
+dhcpd.leases(5) file. Before dhcpd grants a lease to a host, it
+records the lease in this file and makes sure that the contents of the
+file are flushed to disk. This ensures that even in the event of a
+system crash, dhcpd will not forget about a lease that it has
+assigned. On startup, after reading the dhcpd.conf file, dhcpd
+reads the dhcpd.leases file to refresh its memory about what leases
+have been assigned.
+.PP
+New leases are appended to the end of the dhcpd.leases
+file. In order to prevent the file from becoming arbitrarily large,
+from time to time dhcpd creates a new dhcpd.leases file from its
+in-core lease database. Once this file has been written to disk, the
+old file is renamed
+.IR dhcpd.leases~ ,
+and the new file is renamed dhcpd.leases. If the system crashes in
+the middle of this process, whichever dhcpd.leases file remains will
+contain all the lease information, so there is no need for a special
+crash recovery process.
+.PP
+BOOTP support is also provided by this server. Unlike DHCP, the BOOTP
+protocol does not provide a protocol for recovering
+dynamically-assigned addresses once they are no longer needed. It is
+still possible to dynamically assign addresses to BOOTP clients, but
+some administrative process for reclaiming addresses is required. By
+default, leases are granted to BOOTP clients in perpetuity, although
+the network administrator may set an earlier cutoff date or a shorter
+lease length for BOOTP leases if that makes sense.
+.PP
+BOOTP clients may also be served in the old standard way, which is to
+simply provide a declaration in the dhcpd.conf file for each
+BOOTP client, permanently assigning an address to each client.
+.PP
+Whenever changes are made to the dhcpd.conf file, dhcpd must be
+restarted. To restart dhcpd, send a SIGTERM (signal 15) to the
+process ID contained in
+.IR RUNDIR/dhcpd.pid ,
+and then re-invoke dhcpd. Because the DHCP server database is not as
+lightweight as a BOOTP database, dhcpd does not automatically restart
+itself when it sees a change to the dhcpd.conf file.
+.PP
+Note: We get a lot of complaints about this. We realize that it would
+be nice if one could send a SIGHUP to the server and have it reload
+the database. This is not technically impossible, but it would
+require a great deal of work, our resources are extremely limited, and
+they can be better spent elsewhere. So please don't complain about
+this on the mailing list unless you're prepared to fund a project to
+implement this feature, or prepared to do it yourself.
+.SH COMMAND LINE
+.PP
+The names of the network interfaces on which dhcpd should listen for
+broadcasts may be specified on the command line. This should be done
+on systems where dhcpd is unable to identify non-broadcast interfaces,
+but should not be required on other systems. If no interface names
+are specified on the command line dhcpd will identify all network
+interfaces which are up, eliminating non-broadcast interfaces if
+possible, and listen for DHCP broadcasts on each interface.
+.PP
+.SH COMMAND LINE OPTIONS
+.TP
+.BI \-4
+Run as a DHCP server. This is the default and cannot be combined with
+\fB\-6\fR.
+.TP
+.BI \-6
+Run as a DHCPv6 server. This cannot be combined with \fB\-4\fR.
+.TP
+.BI \-p \ port
+The udp port number on which
+.B dhcpd
+should listen. If unspecified
+.B dhcpd
+uses the default port of 67. This is mostly useful for debugging
+purposes.
+.TP
+.BI \-s \ address
+Specify an address or host name to which
+.B dhcpd
+should send replies rather than the broadcast address (255.255.255.255).
+This option is only supported in IPv4.
+.TP
+.BI \-f
+Force
+.B dhcpd
+to run as a foreground process instead of as a daemon in the background.
+This is useful when running
+.B dhcpd
+under a debugger, or when running it
+out of inittab on System V systems.
+.TP
+.BI \-d
+Send log messages to the standard error descriptor.
+This can be useful for debugging, and also at sites where a
+complete log of all dhcp activity must be kept but syslogd is not
+reliable or otherwise cannot be used. Normally,
+.B dhcpd
+will log all
+output using the \fBsyslog(3)\fR function with the log facility set to
+LOG_DAEMON. Note that \fB\-d\fR implies \fB\-f\fR (the daemon will
+not fork itself into the background).
+.TP
+.BI \-q
+Be quiet at startup. This suppresses the printing of the entire
+copyright message during startup. This might be desirable when
+starting
+.B dhcpd
+from a system startup script (e.g., /etc/rc).
+.TP
+.BI \-t
+Test the configuration file. The server tests the configuration file
+for correct syntax, but will not attempt to perform any network
+operations. This can be used to test a new configuration file
+automatically before installing it.
+.TP
+.BI \-T
+Test the lease file. The server tests the lease file
+for correct syntax, but will not attempt to perform any network
+operations. This can be used to test a new lease file
+automatically before installing it.
+.TP
+.BI \-tf \ tracefile
+Specify a file into which the entire startup state of the server and
+all the transactions it processes are logged. This can be
+useful in submitting bug reports - if you are getting a core dump
+every so often, you can start the server with the \fB-tf\fR option and
+then, when the server dumps core, the trace file will contain all the
+transactions that led up to it dumping core, so that the problem can
+be easily debugged with \fB-play\fR.
+.TP
+.BI \-play \ playfile
+Specify a file from which the entire startup state of the server and
+all the transactions it processed are read. The \fB-play\fR option
+must be specified with an alternate lease file,
+using the \fB-lf\fR switch, so that the DHCP server doesn't wipe out
+your existing lease file with its test data. The DHCP server will
+refuse to operate in playback mode unless you specify an alternate
+lease file.
+.TP
+.BI --version
+Print version number and exit.
+.PP
+.I Modifying default file locations:
+The following options can be used to modify the locations
+.B dhcpd
+uses for its files. Because of the importance of using the same
+lease database at all times when running dhcpd in production, these
+options should be used \fBonly\fR for testing lease files or database
+files in a non-production environment.
+.TP
+.BI \-cf \ config-file
+Path to alternate configuration file.
+.TP
+.BI \-lf \ lease-file
+Path to alternate lease file.
+.TP
+.BI \-pf \ pid-file
+Path to alternate pid file.
+.TP
+.BI \--no-pid
+Option to disable writing pid files. By default the program
+will write a pid file. If the program is invoked with this
+option it will not check for an existing server process.
+.PP
+.SH CONFIGURATION
+The syntax of the dhcpd.conf(5) file is discussed separately. This
+section should be used as an overview of the configuration process,
+and the dhcpd.conf(5) documentation should be consulted for detailed
+reference information.
+.PP
+.SH Subnets
+dhcpd needs to know the subnet numbers and netmasks of all subnets for
+which it will be providing service. In addition, in order to
+dynamically allocate addresses, it must be assigned one or more ranges
+of addresses on each subnet which it can in turn assign to client
+hosts as they boot. Thus, a very simple configuration providing DHCP
+support might look like this:
+.nf
+.sp 1
+ subnet 239.252.197.0 netmask 255.255.255.0 {
+ range 239.252.197.10 239.252.197.250;
+ }
+.fi
+.PP
+Multiple address ranges may be specified like this:
+.nf
+.sp 1
+ subnet 239.252.197.0 netmask 255.255.255.0 {
+ range 239.252.197.10 239.252.197.107;
+ range 239.252.197.113 239.252.197.250;
+ }
+.fi
+.PP
+If a subnet will only be provided with BOOTP service and no dynamic
+address assignment, the range clause can be left out entirely, but the
+subnet statement must appear.
+.PP
+.SH Lease Lengths
+DHCP leases can be assigned almost any length from zero seconds to
+infinity. What lease length makes sense for any given subnet, or for
+any given installation, will vary depending on the kinds of hosts
+being served.
+.PP
+For example, in an office environment where systems are added from
+time to time and removed from time to time, but move relatively
+infrequently, it might make sense to allow lease times of a month or
+more. In a final test environment on a manufacturing floor, it may
+make more sense to assign a maximum lease length of 30 minutes -
+enough time to go through a simple test procedure on a network
+appliance before packaging it up for delivery.
+.PP
+It is possible to specify two lease lengths: the default length that
+will be assigned if a client doesn't ask for any particular lease
+length, and a maximum lease length. These are specified as clauses
+to the subnet command:
+.nf
+.sp 1
+ subnet 239.252.197.0 netmask 255.255.255.0 {
+ range 239.252.197.10 239.252.197.107;
+ default-lease-time 600;
+ max-lease-time 7200;
+ }
+.fi
+.PP
+This particular subnet declaration specifies a default lease time of
+600 seconds (ten minutes), and a maximum lease time of 7200 seconds
+(two hours). Other common values would be 86400 (one day), 604800
+(one week) and 2592000 (30 days).
+.PP
+Each subnet need not have the same lease\(emin the case of an office
+environment and a manufacturing environment served by the same DHCP
+server, it might make sense to have widely disparate values for
+default and maximum lease times on each subnet.
+.SH BOOTP Support
+Each BOOTP client must be explicitly declared in the dhcpd.conf
+file. A very basic client declaration will specify the client
+network interface's hardware address and the IP address to assign to
+that client. If the client needs to be able to load a boot file from
+the server, that file's name must be specified. A simple bootp
+client declaration might look like this:
+.nf
+.sp 1
+ host haagen {
+ hardware ethernet 08:00:2b:4c:59:23;
+ fixed-address 239.252.197.9;
+ filename "/tftpboot/haagen.boot";
+ }
+.fi
+.SH Options
+DHCP (and also BOOTP with Vendor Extensions) provide a mechanism
+whereby the server can provide the client with information about how
+to configure its network interface (e.g., subnet mask), and also how
+the client can access various network services (e.g., DNS, IP routers,
+and so on).
+.PP
+These options can be specified on a per-subnet basis, and, for BOOTP
+clients, also on a per-client basis. In the event that a BOOTP
+client declaration specifies options that are also specified in its
+subnet declaration, the options specified in the client declaration
+take precedence. A reasonably complete DHCP configuration might
+look something like this:
+.nf
+.sp 1
+ subnet 239.252.197.0 netmask 255.255.255.0 {
+ range 239.252.197.10 239.252.197.250;
+ default-lease-time 600 max-lease-time 7200;
+ option subnet-mask 255.255.255.0;
+ option broadcast-address 239.252.197.255;
+ option routers 239.252.197.1;
+ option domain-name-servers 239.252.197.2, 239.252.197.3;
+ option domain-name "isc.org";
+ }
+.fi
+.PP
+A bootp host on that subnet that needs to be in a different domain and
+use a different name server might be declared as follows:
+.nf
+.sp 1
+ host haagen {
+ hardware ethernet 08:00:2b:4c:59:23;
+ fixed-address 239.252.197.9;
+ filename "/tftpboot/haagen.boot";
+ option domain-name-servers 192.5.5.1;
+ option domain-name "vix.com";
+ }
+.fi
+.PP
+A more complete description of the dhcpd.conf file syntax is provided
+in dhcpd.conf(5).
+.SH OMAPI
+The DHCP server provides the capability to modify some of its
+configuration while it is running, without stopping it, modifying its
+database files, and restarting it. This capability is currently
+provided using OMAPI - an API for manipulating remote objects. OMAPI
+clients connect to the server using TCP/IP, authenticate, and can then
+examine the server's current status and make changes to it.
+.PP
+Rather than implementing the underlying OMAPI protocol directly, user
+programs should use the dhcpctl API or OMAPI itself. Dhcpctl is a
+wrapper that handles some of the housekeeping chores that OMAPI does
+not do automatically. Dhcpctl and OMAPI are documented in \fBdhcpctl(3)\fR
+and \fBomapi(3)\fR.
+.PP
+OMAPI exports objects, which can then be examined and modified. The
+DHCP server exports the following objects: lease, host,
+failover-state and group. Each object has a number of methods that
+are provided: lookup, create, and destroy. In addition, it is
+possible to look at attributes that are stored on objects, and in some
+cases to modify those attributes.
+.SH THE LEASE OBJECT
+Leases can't currently be created or destroyed, but they can be looked
+up to examine and modify their state.
+.PP
+Leases have the following attributes:
+.PP
+.B state \fIinteger\fR lookup, examine
+.RS 0.5i
+.nf
+1 = free
+2 = active
+3 = expired
+4 = released
+5 = abandoned
+6 = reset
+7 = backup
+8 = reserved
+9 = bootp
+.fi
+.RE
+.PP
+.B ip-address \fIdata\fR lookup, examine
+.RS 0.5i
+The IP address of the lease.
+.RE
+.PP
+.B dhcp-client-identifier \fIdata\fR lookup, examine, update
+.RS 0.5i
+The
+client identifier that the client used when it acquired the lease.
+Not all clients send client identifiers, so this may be empty.
+.RE
+.PP
+.B client-hostname \fIdata\fR examine, update
+.RS 0.5i
+The value the client sent in the host-name option.
+.RE
+.PP
+.B host \fIhandle\fR examine
+.RS 0.5i
+the host declaration associated with this lease, if any.
+.RE
+.PP
+.B subnet \fIhandle\fR examine
+.RS 0.5i
+the subnet object associated with this lease (the subnet object is not
+currently supported).
+.RE
+.PP
+.B pool \fIhandle\fR examine
+.RS 0.5i
+the pool object associated with this lease (the pool object is not
+currently supported).
+.RE
+.PP
+.B billing-class \fIhandle\fR examine
+.RS 0.5i
+the handle to the class to which this lease is currently billed, if
+any (the class object is not currently supported).
+.RE
+.PP
+.B hardware-address \fIdata\fR examine, update
+.RS 0.5i
+the hardware address (chaddr) field sent by the client when it
+acquired its lease.
+.RE
+.PP
+.B hardware-type \fIinteger\fR examine, update
+.RS 0.5i
+the type of the network interface that the client reported when it
+acquired its lease.
+.RE
+.PP
+.B ends \fItime\fR examine
+.RS 0.5i
+the time when the lease's current state ends, as understood by the
+client.
+.RE
+.PP
+.B tstp \fItime\fR examine
+.RS 0.5i
+the time when the lease's current state ends, as understood by the
+server.
+.RE
+.B tsfp \fItime\fR examine
+.RS 0.5i
+the adjusted time when the lease's current state ends, as understood by
+the failover peer (if there is no failover peer, this value is
+undefined). Generally this value is only adjusted for expired, released,
+or reset leases while the server is operating in partner-down state, and
+otherwise is simply the value supplied by the peer.
+.RE
+.B atsfp \fItime\fR examine
+.RS 0.5i
+the actual tsfp value sent from the peer. This value is forgotten when a
+lease binding state change is made, to facilitate retransmission logic.
+.RE
+.PP
+.B cltt \fItime\fR examine
+.RS 0.5i
+The time of the last transaction with the client on this lease.
+.RE
+.SH THE HOST OBJECT
+Hosts can be created, destroyed, looked up, examined and modified.
+If a host declaration is created or deleted using OMAPI, that
+information will be recorded in the dhcpd.leases file. It is
+permissible to delete host declarations that are declared in the
+dhcpd.conf file.
+.PP
+Hosts have the following attributes:
+.PP
+.B name \fIdata\fR lookup, examine, modify
+.RS 0.5i
+the name of the host declaration. This name must be unique among all
+host declarations.
+.RE
+.PP
+.B group \fIhandle\fR examine, modify
+.RS 0.5i
+the named group associated with the host declaration, if there is one.
+.RE
+.PP
+.B hardware-address \fIdata\fR lookup, examine, modify
+.RS 0.5i
+the link-layer address that will be used to match the client, if any.
+Only valid if hardware-type is also present.
+.RE
+.PP
+.B hardware-type \fIinteger\fR lookup, examine, modify
+.RS 0.5i
+the type of the network interface that will be used to match the
+client, if any. Only valid if hardware-address is also present.
+.RE
+.PP
+.B dhcp-client-identifier \fIdata\fR lookup, examine, modify
+.RS 0.5i
+the dhcp-client-identifier option that will be used to match the
+client, if any.
+.RE
+.PP
+.B ip-address \fIdata\fR examine, modify
+.RS 0.5i
+a fixed IP address which is reserved for a DHCP client that matches
+this host declaration. The IP address will only be assigned to the
+client if it is valid for the network segment to which the client is
+connected.
+.RE
+.PP
+.B statements \fIdata\fR modify
+.RS 0.5i
+a list of statements in the format of the dhcpd.conf file that will be
+executed whenever a message from the client is being processed.
+.RE
+.PP
+.B known \fIinteger\fR examine, modify
+.RS 0.5i
+if nonzero, indicates that a client matching this host declaration
+will be treated as \fIknown\fR in pool permit lists. If zero, the
+client will not be treated as known.
+.RE
+.SH THE GROUP OBJECT
+Named groups can be created, destroyed, looked up, examined and
+modified. If a group declaration is created or deleted using OMAPI,
+that information will be recorded in the dhcpd.leases file. It is
+permissible to delete group declarations that are declared in the
+dhcpd.conf file.
+.PP
+Named groups currently can only be associated with
+hosts - this allows one set of statements to be efficiently attached
+to more than one host declaration.
+.PP
+Groups have the following attributes:
+.PP
+.B name \fIdata\fR
+.RS 0.5i
+the name of the group. All groups that are created using OMAPI must
+have names, and the names must be unique among all groups.
+.RE
+.PP
+.B statements \fIdata\fR
+.RS 0.5i
+a list of statements in the format of the dhcpd.conf file that will be
+executed whenever a message from a client whose host declaration
+references this group is processed.
+.RE
+.SH THE CONTROL OBJECT
+The control object allows you to shut the server down. If the server
+is doing failover with another peer, it will make a clean transition
+into the shutdown state and notify its peer, so that the peer can go
+into partner down, and then record the "recover" state in the lease
+file so that when the server is restarted, it will automatically
+resynchronize with its peer.
+.PP
+On shutdown the server will also attempt to cleanly shut down all
+OMAPI connections. If these connections do not go down cleanly after
+five seconds, they are shut down preemptively. It can take as much
+as 25 seconds from the beginning of the shutdown process to the time
+that the server actually exits.
+.PP
+To shut the server down, open its control object and set the state
+attribute to 2.
+.SH THE FAILOVER-STATE OBJECT
+The failover-state object is the object that tracks the state of the
+failover protocol as it is being managed for a given failover peer.
+The failover object has the following attributes (please see
+.B dhcpd.conf (5)
+for explanations about what these attributes mean):
+.PP
+.B name \fIdata\fR examine
+.RS 0.5i
+Indicates the name of the failover peer relationship, as described in
+the server's \fBdhcpd.conf\fR file.
+.RE
+.PP
+.B partner-address \fIdata\fR examine
+.RS 0.5i
+Indicates the failover partner's IP address.
+.RE
+.PP
+.B local-address \fIdata\fR examine
+.RS 0.5i
+Indicates the IP address that is being used by the DHCP server for
+this failover pair.
+.RE
+.PP
+.B partner-port \fIdata\fR examine
+.RS 0.5i
+Indicates the TCP port on which the failover partner is listening for
+failover protocol connections.
+.RE
+.PP
+.B local-port \fIdata\fR examine
+.RS 0.5i
+Indicates the TCP port on which the DHCP server is listening for
+failover protocol connections for this failover pair.
+.RE
+.PP
+.B max-outstanding-updates \fIinteger\fR examine
+.RS 0.5i
+Indicates the number of updates that can be outstanding and
+unacknowledged at any given time, in this failover relationship.
+.RE
+.PP
+.B mclt \fIinteger\fR examine
+.RS 0.5i
+Indicates the maximum client lead time in this failover relationship.
+.RE
+.PP
+.B load-balance-max-secs \fIinteger\fR examine
+.RS 0.5i
+Indicates the maximum value for the secs field in a client request
+before load balancing is bypassed.
+.RE
+.PP
+.B load-balance-hba \fIdata\fR examine
+.RS 0.5i
+Indicates the load balancing hash bucket array for this failover
+relationship.
+.RE
+.PP
+.B local-state \fIinteger\fR examine, modify
+.RS 0.5i
+Indicates the present state of the DHCP server in this failover
+relationship. Possible values for state are:
+.RE
+.RS 1i
+.PP
+.nf
+1 - startup
+2 - normal
+3 - communications interrupted
+4 - partner down
+5 - potential conflict
+6 - recover
+7 - paused
+8 - shutdown
+9 - recover done
+10 - resolution interrupted
+11 - conflict done
+254 - recover wait
+.fi
+.RE
+.PP
+.RS 0.5i
+(Note that some of the above values have changed since DHCP 3.0.x.)
+.RE
+.PP
+.RS 0.5i
+In general it is not a good idea to make changes to this state.
+However, in the case that the failover partner is known to be down, it
+can be useful to set the DHCP server's failover state to partner
+down. At this point the DHCP server will take over service of the
+failover partner's leases as soon as possible, and will give out
+normal leases, not leases that are restricted by MCLT. If you do put
+the DHCP server into the partner-down when the other DHCP server is
+not in the partner-down state, but is not reachable, IP address
+assignment conflicts are possible, even likely. Once a server has
+been put into partner-down mode, its failover partner must not be
+brought back online until communication is possible between the two
+servers.
+.RE
+.PP
+.B partner-state \fIinteger\fR examine
+.RS 0.5i
+Indicates the present state of the failover partner.
+.RE
+.PP
+.B local-stos \fIinteger\fR examine
+.RS 0.5i
+Indicates the time at which the DHCP server entered its present state
+in this failover relationship.
+.RE
+.PP
+.B partner-stos \fIinteger\fR examine
+.RS 0.5i
+Indicates the time at which the failover partner entered its present state.
+.RE
+.PP
+.B hierarchy \fIinteger\fR examine
+.RS 0.5i
+Indicates whether the DHCP server is primary (0) or secondary (1) in
+this failover relationship.
+.RE
+.PP
+.B last-packet-sent \fIinteger\fR examine
+.RS 0.5i
+Indicates the time at which the most recent failover packet was sent
+by this DHCP server to its failover partner.
+.RE
+.PP
+.B last-timestamp-received \fIinteger\fR examine
+.RS 0.5i
+Indicates the timestamp that was on the failover message most recently
+received from the failover partner.
+.RE
+.PP
+.B skew \fIinteger\fR examine
+.RS 0.5i
+Indicates the skew between the failover partner's clock and this DHCP
+server's clock
+.RE
+.PP
+.B max-response-delay \fIinteger\fR examine
+.RS 0.5i
+Indicates the time in seconds after which, if no message is received
+from the failover partner, the partner is assumed to be out of
+communication.
+.RE
+.PP
+.B cur-unacked-updates \fIinteger\fR examine
+.RS 0.5i
+Indicates the number of update messages that have been received from
+the failover partner but not yet processed.
+.RE
+.SH FILES
+.B ETCDIR/dhcpd.conf, DBDIR/dhcpd.leases, RUNDIR/dhcpd.pid,
+.B DBDIR/dhcpd.leases~.
+.SH SEE ALSO
+dhclient(8), dhcrelay(8), dhcpd.conf(5), dhcpd.leases(5)
+.SH AUTHOR
+.B dhcpd(8)
+was originally written by Ted Lemon under a contract with Vixie Labs.
+Funding for this project was provided by Internet Systems
+Consortium. Version 3 of the DHCP server was funded by Nominum, Inc.
+Information about Internet Systems Consortium is available at
+.B https://www.isc.org/\fR.
diff --git a/server/dhcpd.c b/server/dhcpd.c
new file mode 100644
index 0000000..2e5ae80
--- /dev/null
+++ b/server/dhcpd.c
@@ -0,0 +1,1542 @@
+/* dhcpd.c
+
+ DHCP Server Daemon. */
+
+/*
+ * Copyright (c) 2004-2015 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/
+ *
+ */
+
+static const char copyright[] =
+"Copyright 2004-2015 Internet Systems Consortium.";
+static const char arr [] = "All rights reserved.";
+static const char message [] = "Internet Systems Consortium DHCP Server";
+static const char url [] =
+"For info, please visit https://www.isc.org/software/dhcp/";
+
+#include "dhcpd.h"
+#include <omapip/omapip_p.h>
+#include <syslog.h>
+#include <signal.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#if defined (PARANOIA)
+# include <sys/types.h>
+# include <unistd.h>
+# include <pwd.h>
+/* get around the ISC declaration of group */
+# define group real_group
+# include <grp.h>
+# undef group
+#endif /* PARANOIA */
+
+#ifndef UNIT_TEST
+static void usage(void);
+#endif
+
+struct iaddr server_identifier;
+int server_identifier_matched;
+
+#if defined (NSUPDATE)
+
+/* This stuff is always executed to figure the default values for certain
+ ddns variables. */
+char std_nsupdate [] = " \n\
+option server.ddns-hostname = \n\
+ pick (option fqdn.hostname, option host-name, config-option host-name); \n\
+option server.ddns-domainname = config-option domain-name; \n\
+option server.ddns-rev-domainname = \"in-addr.arpa.\";";
+
+/* This is the old-style name service updater that is executed
+ whenever a lease is committed. It does not follow the DHCP-DNS
+ draft at all. */
+
+char old_nsupdate [] = " \n\
+on commit { \n\
+ if (not static and \n\
+ ((config-option server.ddns-updates = null) or \n\
+ (config-option server.ddns-updates != 0))) { \n\
+ set new-ddns-fwd-name = \n\
+ concat (pick (config-option server.ddns-hostname, \n\
+ option host-name), \".\", \n\
+ pick (config-option server.ddns-domainname, \n\
+ config-option domain-name)); \n\
+ if (defined (ddns-fwd-name) and ddns-fwd-name != new-ddns-fwd-name) { \n\
+ switch (ns-update (delete (IN, A, ddns-fwd-name, leased-address))) { \n\
+ case NOERROR: \n\
+ unset ddns-fwd-name; \n\
+ on expiry or release { \n\
+ } \n\
+ } \n\
+ } \n\
+ \n\
+ if (not defined (ddns-fwd-name)) { \n\
+ set ddns-fwd-name = new-ddns-fwd-name; \n\
+ if defined (ddns-fwd-name) { \n\
+ switch (ns-update (not exists (IN, A, ddns-fwd-name, null), \n\
+ add (IN, A, ddns-fwd-name, leased-address, \n\
+ lease-time / 2))) { \n\
+ default: \n\
+ unset ddns-fwd-name; \n\
+ break; \n\
+ \n\
+ case NOERROR: \n\
+ set ddns-rev-name = \n\
+ concat (binary-to-ascii (10, 8, \".\", \n\
+ reverse (1, \n\
+ leased-address)), \".\", \n\
+ pick (config-option server.ddns-rev-domainname, \n\
+ \"in-addr.arpa.\")); \n\
+ switch (ns-update (delete (IN, PTR, ddns-rev-name, null), \n\
+ add (IN, PTR, ddns-rev-name, ddns-fwd-name, \n\
+ lease-time / 2))) \n\
+ { \n\
+ default: \n\
+ unset ddns-rev-name; \n\
+ on release or expiry { \n\
+ switch (ns-update (delete (IN, A, ddns-fwd-name, \n\
+ leased-address))) { \n\
+ case NOERROR: \n\
+ unset ddns-fwd-name; \n\
+ break; \n\
+ } \n\
+ on release or expiry; \n\
+ } \n\
+ break; \n\
+ \n\
+ case NOERROR: \n\
+ on release or expiry { \n\
+ switch (ns-update (delete (IN, PTR, ddns-rev-name, null))) {\n\
+ case NOERROR: \n\
+ unset ddns-rev-name; \n\
+ break; \n\
+ } \n\
+ switch (ns-update (delete (IN, A, ddns-fwd-name, \n\
+ leased-address))) { \n\
+ case NOERROR: \n\
+ unset ddns-fwd-name; \n\
+ break; \n\
+ } \n\
+ on release or expiry; \n\
+ } \n\
+ } \n\
+ } \n\
+ } \n\
+ } \n\
+ unset new-ddns-fwd-name; \n\
+ } \n\
+}";
+
+#endif /* NSUPDATE */
+int ddns_update_style;
+
+const char *path_dhcpd_conf = _PATH_DHCPD_CONF;
+const char *path_dhcpd_db = _PATH_DHCPD_DB;
+const char *path_dhcpd_pid = _PATH_DHCPD_PID;
+/* False (default) => we write and use a pid file */
+isc_boolean_t no_pid_file = ISC_FALSE;
+
+int dhcp_max_agent_option_packet_length = DHCP_MTU_MAX;
+
+static omapi_auth_key_t *omapi_key = (omapi_auth_key_t *)0;
+int omapi_port;
+
+#if defined (TRACING)
+trace_type_t *trace_srandom;
+#endif
+
+static isc_result_t verify_addr (omapi_object_t *l, omapi_addr_t *addr) {
+ return ISC_R_SUCCESS;
+}
+
+static isc_result_t verify_auth (omapi_object_t *p, omapi_auth_key_t *a) {
+ if (a != omapi_key)
+ return DHCP_R_INVALIDKEY;
+ return ISC_R_SUCCESS;
+}
+
+static void omapi_listener_start (void *foo)
+{
+ omapi_object_t *listener;
+ isc_result_t result;
+ struct timeval tv;
+
+ listener = (omapi_object_t *)0;
+ result = omapi_generic_new (&listener, MDL);
+ if (result != ISC_R_SUCCESS)
+ log_fatal ("Can't allocate new generic object: %s",
+ isc_result_totext (result));
+ result = omapi_protocol_listen (listener,
+ (unsigned)omapi_port, 1);
+ if (result == ISC_R_SUCCESS && omapi_key)
+ result = omapi_protocol_configure_security
+ (listener, verify_addr, verify_auth);
+ if (result != ISC_R_SUCCESS) {
+ log_error ("Can't start OMAPI protocol: %s",
+ isc_result_totext (result));
+ tv.tv_sec = cur_tv.tv_sec + 5;
+ tv.tv_usec = cur_tv.tv_usec;
+ add_timeout (&tv, omapi_listener_start, 0, 0, 0);
+ }
+ omapi_object_dereference (&listener, MDL);
+}
+
+#if defined (PARANOIA)
+/* to be used in one of two possible scenarios */
+static void setup_chroot (char *chroot_dir) {
+ if (geteuid())
+ log_fatal ("you must be root to use chroot");
+
+ if (chroot(chroot_dir)) {
+ log_fatal ("chroot(\"%s\"): %m", chroot_dir);
+ }
+ if (chdir ("/")) {
+ /* probably permission denied */
+ log_fatal ("chdir(\"/\"): %m");
+ }
+}
+#endif /* PARANOIA */
+
+#ifndef UNIT_TEST
+int
+main(int argc, char **argv) {
+ int fd;
+ int i, status;
+ struct servent *ent;
+ char *s;
+ int cftest = 0;
+ int lftest = 0;
+ int pid;
+ char pbuf [20];
+#ifndef DEBUG
+ int daemon = 1;
+#endif
+ int quiet = 0;
+ char *server = (char *)0;
+ isc_result_t result;
+ unsigned seed;
+ struct interface_info *ip;
+#if defined (NSUPDATE)
+ struct parse *parse;
+ int lose;
+#endif
+ int no_dhcpd_conf = 0;
+ int no_dhcpd_db = 0;
+ int no_dhcpd_pid = 0;
+#ifdef DHCPv6
+ int local_family_set = 0;
+#endif /* DHCPv6 */
+#if defined (TRACING)
+ char *traceinfile = (char *)0;
+ char *traceoutfile = (char *)0;
+#endif
+
+#if defined (PARANOIA)
+ char *set_user = 0;
+ char *set_group = 0;
+ char *set_chroot = 0;
+
+ uid_t set_uid = 0;
+ gid_t set_gid = 0;
+#endif /* PARANOIA */
+
+ /* Make sure that file descriptors 0 (stdin), 1, (stdout), and
+ 2 (stderr) are open. To do this, we assume that when we
+ open a file the lowest available file descriptor is used. */
+ fd = open("/dev/null", O_RDWR);
+ if (fd == 0)
+ fd = open("/dev/null", O_RDWR);
+ if (fd == 1)
+ fd = open("/dev/null", O_RDWR);
+ if (fd == 2)
+ log_perror = 0; /* No sense logging to /dev/null. */
+ else if (fd != -1)
+ close(fd);
+
+ /* Set up the isc and dns library managers */
+ status = dhcp_context_create();
+ if (status != ISC_R_SUCCESS)
+ log_fatal("Can't initialize context: %s",
+ isc_result_totext(status));
+
+ /* Set up the client classification system. */
+ classification_setup ();
+
+ /* Initialize the omapi system. */
+ result = omapi_init ();
+ if (result != ISC_R_SUCCESS)
+ log_fatal ("Can't initialize OMAPI: %s",
+ isc_result_totext (result));
+
+ /* Set up the OMAPI wrappers for common objects. */
+ dhcp_db_objects_setup ();
+ /* Set up the OMAPI wrappers for various server database internal
+ objects. */
+ dhcp_common_objects_setup ();
+
+ /* Initially, log errors to stderr as well as to syslogd. */
+ openlog ("dhcpd", DHCP_LOG_OPTIONS, DHCPD_LOG_FACILITY);
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp (argv [i], "-p")) {
+ if (++i == argc)
+ usage ();
+ local_port = validate_port (argv [i]);
+ log_debug ("binding to user-specified port %d",
+ ntohs (local_port));
+ } else if (!strcmp (argv [i], "-f")) {
+#ifndef DEBUG
+ daemon = 0;
+#endif
+ } else if (!strcmp (argv [i], "-d")) {
+#ifndef DEBUG
+ daemon = 0;
+#endif
+ log_perror = -1;
+ } else if (!strcmp (argv [i], "-s")) {
+ if (++i == argc)
+ usage ();
+ server = argv [i];
+#if defined (PARANOIA)
+ } else if (!strcmp (argv [i], "-user")) {
+ if (++i == argc)
+ usage ();
+ set_user = argv [i];
+ } else if (!strcmp (argv [i], "-group")) {
+ if (++i == argc)
+ usage ();
+ set_group = argv [i];
+ } else if (!strcmp (argv [i], "-chroot")) {
+ if (++i == argc)
+ usage ();
+ set_chroot = argv [i];
+#endif /* PARANOIA */
+ } else if (!strcmp (argv [i], "-cf")) {
+ if (++i == argc)
+ usage ();
+ path_dhcpd_conf = argv [i];
+ no_dhcpd_conf = 1;
+ } else if (!strcmp (argv [i], "-lf")) {
+ if (++i == argc)
+ usage ();
+ path_dhcpd_db = argv [i];
+ no_dhcpd_db = 1;
+ } else if (!strcmp (argv [i], "-pf")) {
+ if (++i == argc)
+ usage ();
+ path_dhcpd_pid = argv [i];
+ no_dhcpd_pid = 1;
+ } else if (!strcmp(argv[i], "--no-pid")) {
+ no_pid_file = ISC_TRUE;
+ } else if (!strcmp (argv [i], "-t")) {
+ /* test configurations only */
+#ifndef DEBUG
+ daemon = 0;
+#endif
+ cftest = 1;
+ log_perror = -1;
+ } else if (!strcmp (argv [i], "-T")) {
+ /* test configurations and lease file only */
+#ifndef DEBUG
+ daemon = 0;
+#endif
+ cftest = 1;
+ lftest = 1;
+ log_perror = -1;
+ } else if (!strcmp (argv [i], "-q")) {
+ quiet = 1;
+ quiet_interface_discovery = 1;
+#ifdef DHCPv6
+ } else if (!strcmp(argv[i], "-4")) {
+ if (local_family_set && (local_family != AF_INET)) {
+ log_fatal("Server cannot run in both IPv4 and "
+ "IPv6 mode at the same time.");
+ }
+ local_family = AF_INET;
+ local_family_set = 1;
+ } else if (!strcmp(argv[i], "-6")) {
+ if (local_family_set && (local_family != AF_INET6)) {
+ log_fatal("Server cannot run in both IPv4 and "
+ "IPv6 mode at the same time.");
+ }
+ local_family = AF_INET6;
+ local_family_set = 1;
+#endif /* DHCPv6 */
+ } else if (!strcmp (argv [i], "--version")) {
+ log_info("isc-dhcpd-%s", PACKAGE_VERSION);
+ exit (0);
+#if defined (TRACING)
+ } else if (!strcmp (argv [i], "-tf")) {
+ if (++i == argc)
+ usage ();
+ traceoutfile = argv [i];
+ } else if (!strcmp (argv [i], "-play")) {
+ if (++i == argc)
+ usage ();
+ traceinfile = argv [i];
+ trace_replay_init ();
+#endif /* TRACING */
+ } else if (argv [i][0] == '-') {
+ usage ();
+ } else {
+ struct interface_info *tmp =
+ (struct interface_info *)0;
+ if (strlen(argv[i]) >= sizeof(tmp->name))
+ log_fatal("%s: interface name too long "
+ "(is %ld)",
+ argv[i], (long)strlen(argv[i]));
+ result = interface_allocate (&tmp, MDL);
+ if (result != ISC_R_SUCCESS)
+ log_fatal ("Insufficient memory to %s %s: %s",
+ "record interface", argv [i],
+ isc_result_totext (result));
+ strcpy (tmp -> name, argv [i]);
+ if (interfaces) {
+ interface_reference (&tmp -> next,
+ interfaces, MDL);
+ interface_dereference (&interfaces, MDL);
+ }
+ interface_reference (&interfaces, tmp, MDL);
+ tmp -> flags = INTERFACE_REQUESTED;
+ }
+ }
+
+ if (!no_dhcpd_conf && (s = getenv ("PATH_DHCPD_CONF"))) {
+ path_dhcpd_conf = s;
+ }
+
+#ifdef DHCPv6
+ if (local_family == AF_INET6) {
+ /* DHCPv6: override DHCPv4 lease and pid filenames */
+ if (!no_dhcpd_db) {
+ if ((s = getenv ("PATH_DHCPD6_DB")))
+ path_dhcpd_db = s;
+ else
+ path_dhcpd_db = _PATH_DHCPD6_DB;
+ }
+ if (!no_dhcpd_pid) {
+ if ((s = getenv ("PATH_DHCPD6_PID")))
+ path_dhcpd_pid = s;
+ else
+ path_dhcpd_pid = _PATH_DHCPD6_PID;
+ }
+ } else
+#else /* !DHCPv6 */
+ {
+ if (!no_dhcpd_db && (s = getenv ("PATH_DHCPD_DB"))) {
+ path_dhcpd_db = s;
+ }
+ if (!no_dhcpd_pid && (s = getenv ("PATH_DHCPD_PID"))) {
+ path_dhcpd_pid = s;
+ }
+ }
+#endif /* DHCPv6 */
+
+ /*
+ * convert relative path names to absolute, for files that need
+ * to be reopened after chdir() has been called
+ */
+ if (path_dhcpd_db[0] != '/') {
+ char *path = dmalloc(PATH_MAX, MDL);
+ if (path == NULL)
+ log_fatal("No memory for filename\n");
+ path_dhcpd_db = realpath(path_dhcpd_db, path);
+ if (path_dhcpd_db == NULL)
+ log_fatal("%s: %s", path, strerror(errno));
+ }
+
+ if (!quiet) {
+ log_info("%s %s", message, PACKAGE_VERSION);
+ log_info (copyright);
+ log_info (arr);
+ log_info (url);
+ } else {
+ quiet = 0;
+ log_perror = 0;
+ }
+
+#if defined (TRACING)
+ trace_init (set_time, MDL);
+ if (traceoutfile) {
+ result = trace_begin (traceoutfile, MDL);
+ if (result != ISC_R_SUCCESS)
+ log_fatal ("Unable to begin trace: %s",
+ isc_result_totext (result));
+ }
+ interface_trace_setup ();
+ parse_trace_setup ();
+ trace_srandom = trace_type_register ("random-seed", (void *)0,
+ trace_seed_input,
+ trace_seed_stop, MDL);
+#if defined (NSUPDATE)
+ trace_ddns_init();
+#endif /* NSUPDATE */
+#endif
+
+#if defined (PARANOIA)
+ /* get user and group info if those options were given */
+ if (set_user) {
+ struct passwd *tmp_pwd;
+
+ if (geteuid())
+ log_fatal ("you must be root to set user");
+
+ if (!(tmp_pwd = getpwnam(set_user)))
+ log_fatal ("no such user: %s", set_user);
+
+ set_uid = tmp_pwd->pw_uid;
+
+ /* use the user's group as the default gid */
+ if (!set_group)
+ set_gid = tmp_pwd->pw_gid;
+ }
+
+ if (set_group) {
+/* get around the ISC declaration of group */
+#define group real_group
+ struct group *tmp_grp;
+
+ if (geteuid())
+ log_fatal ("you must be root to set group");
+
+ if (!(tmp_grp = getgrnam(set_group)))
+ log_fatal ("no such group: %s", set_group);
+
+ set_gid = tmp_grp->gr_gid;
+#undef group
+ }
+
+# if defined (EARLY_CHROOT)
+ if (set_chroot) setup_chroot (set_chroot);
+# endif /* EARLY_CHROOT */
+#endif /* PARANOIA */
+
+ /* Default to the DHCP/BOOTP port. */
+ if (!local_port)
+ {
+ if ((s = getenv ("DHCPD_PORT"))) {
+ local_port = validate_port (s);
+ log_debug ("binding to environment-specified port %d",
+ ntohs (local_port));
+ } else {
+ if (local_family == AF_INET) {
+ ent = getservbyname("dhcp", "udp");
+ if (ent == NULL) {
+ local_port = htons(67);
+ } else {
+ local_port = ent->s_port;
+ }
+ } else {
+ /* INSIST(local_family == AF_INET6); */
+ ent = getservbyname("dhcpv6-server", "udp");
+ if (ent == NULL) {
+ local_port = htons(547);
+ } else {
+ local_port = ent->s_port;
+ }
+ }
+#ifndef __CYGWIN32__ /* XXX */
+ endservent ();
+#endif
+ }
+ }
+
+ if (local_family == AF_INET) {
+ remote_port = htons(ntohs(local_port) + 1);
+ } else {
+ /* INSIST(local_family == AF_INET6); */
+ ent = getservbyname("dhcpv6-client", "udp");
+ if (ent == NULL) {
+ remote_port = htons(546);
+ } else {
+ remote_port = ent->s_port;
+ }
+ }
+
+ if (server) {
+ if (local_family != AF_INET) {
+ log_fatal("You can only specify address to send "
+ "replies to when running an IPv4 server.");
+ }
+ if (!inet_aton (server, &limited_broadcast)) {
+ struct hostent *he;
+ he = gethostbyname (server);
+ if (he) {
+ memcpy (&limited_broadcast,
+ he -> h_addr_list [0],
+ sizeof limited_broadcast);
+ } else
+ limited_broadcast.s_addr = INADDR_BROADCAST;
+ }
+ } else {
+ limited_broadcast.s_addr = INADDR_BROADCAST;
+ }
+
+ /* Get the current time... */
+ gettimeofday(&cur_tv, NULL);
+
+ /* Set up the initial dhcp option universe. */
+ initialize_common_option_spaces ();
+ initialize_server_option_spaces ();
+
+ /* Add the ddns update style enumeration prior to parsing. */
+ add_enumeration (&ddns_styles);
+ add_enumeration (&syslog_enum);
+#if defined (LDAP_CONFIGURATION)
+ add_enumeration (&ldap_methods);
+#if defined (LDAP_USE_SSL)
+ add_enumeration (&ldap_ssl_usage_enum);
+ add_enumeration (&ldap_tls_reqcert_enum);
+ add_enumeration (&ldap_tls_crlcheck_enum);
+#endif
+#endif
+
+ if (!group_allocate (&root_group, MDL))
+ log_fatal ("Can't allocate root group!");
+ root_group -> authoritative = 0;
+
+ /* Set up various hooks. */
+ dhcp_interface_setup_hook = dhcpd_interface_setup_hook;
+ bootp_packet_handler = do_packet;
+#ifdef DHCPv6
+ dhcpv6_packet_handler = do_packet6;
+#endif /* DHCPv6 */
+
+#if defined (NSUPDATE)
+ /* Set up the standard name service updater routine. */
+ parse = NULL;
+ status = new_parse(&parse, -1, std_nsupdate, sizeof(std_nsupdate) - 1,
+ "standard name service update routine", 0);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("can't begin parsing name service updater!");
+
+ if (parse != NULL) {
+ lose = 0;
+ if (!(parse_executable_statements(&root_group->statements,
+ parse, &lose, context_any))) {
+ end_parse(&parse);
+ log_fatal("can't parse standard name service updater!");
+ }
+ end_parse(&parse);
+ }
+#endif
+
+ /* Initialize icmp support... */
+ if (!cftest && !lftest)
+ icmp_startup (1, lease_pinged);
+
+#if defined (TRACING)
+ if (traceinfile) {
+ if (!no_dhcpd_db) {
+ log_error ("%s", "");
+ log_error ("** You must specify a lease file with -lf.");
+ log_error (" Dhcpd will not overwrite your default");
+ log_fatal (" lease file when playing back a trace. **");
+ }
+ trace_file_replay (traceinfile);
+
+#if defined (DEBUG_MEMORY_LEAKAGE) && \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ free_everything ();
+ omapi_print_dmalloc_usage_by_caller ();
+#endif
+
+ exit (0);
+ }
+#endif
+
+#ifdef DHCPv6
+ /* set up DHCPv6 hashes */
+ if (!ia_new_hash(&ia_na_active, DEFAULT_HASH_SIZE, MDL)) {
+ log_fatal("Out of memory creating hash for active IA_NA.");
+ }
+ if (!ia_new_hash(&ia_ta_active, DEFAULT_HASH_SIZE, MDL)) {
+ log_fatal("Out of memory creating hash for active IA_TA.");
+ }
+ if (!ia_new_hash(&ia_pd_active, DEFAULT_HASH_SIZE, MDL)) {
+ log_fatal("Out of memory creating hash for active IA_PD.");
+ }
+#endif /* DHCPv6 */
+
+ /* Read the dhcpd.conf file... */
+ if (readconf () != ISC_R_SUCCESS)
+ log_fatal ("Configuration file errors encountered -- exiting");
+
+ postconf_initialization (quiet);
+
+#if defined (PARANOIA) && !defined (EARLY_CHROOT)
+ if (set_chroot) setup_chroot (set_chroot);
+#endif /* PARANOIA && !EARLY_CHROOT */
+
+ /* test option should cause an early exit */
+ if (cftest && !lftest)
+ exit(0);
+
+ /*
+ * First part of dealing with pid files. Check to see if
+ * we should continue running or not. We run if:
+ * - we are testing the lease file out
+ * - we don't have a pid file to check
+ * - there is no other process running
+ */
+ if ((lftest == 0) && (no_pid_file == ISC_FALSE)) {
+ /*Read previous pid file. */
+ if ((i = open(path_dhcpd_pid, O_RDONLY)) >= 0) {
+ status = read(i, pbuf, (sizeof pbuf) - 1);
+ close(i);
+ if (status > 0) {
+ pbuf[status] = 0;
+ pid = atoi(pbuf);
+
+ /*
+ * If there was a previous server process and
+ * it is still running, abort
+ */
+ if (!pid ||
+ (pid != getpid() && kill(pid, 0) == 0))
+ log_fatal("There's already a "
+ "DHCP server running.");
+ }
+ }
+ }
+
+ group_write_hook = group_writer;
+
+ /* Start up the database... */
+ db_startup (lftest);
+
+ if (lftest)
+ exit (0);
+
+ /* Discover all the network interfaces and initialize them. */
+ discover_interfaces(DISCOVER_SERVER);
+
+#ifdef DHCPv6
+ /*
+ * Remove addresses from our pools that we should not issue
+ * to clients.
+ *
+ * We currently have no support for this in IPv4. It is not
+ * as important in IPv4, as making pools with ranges that
+ * leave out interfaces and hosts is fairly straightforward
+ * using range notation, but not so handy with CIDR notation.
+ */
+ if (local_family == AF_INET6) {
+ mark_hosts_unavailable();
+ mark_phosts_unavailable();
+ mark_interfaces_unavailable();
+ }
+#endif /* DHCPv6 */
+
+
+ /* Make up a seed for the random number generator from current
+ time plus the sum of the last four bytes of each
+ interface's hardware address interpreted as an integer.
+ Not much entropy, but we're booting, so we're not likely to
+ find anything better. */
+ seed = 0;
+ for (ip = interfaces; ip; ip = ip -> next) {
+ int junk;
+ memcpy (&junk,
+ &ip -> hw_address.hbuf [ip -> hw_address.hlen -
+ sizeof seed], sizeof seed);
+ seed += junk;
+ }
+ srandom (seed + cur_time);
+#if defined (TRACING)
+ trace_seed_stash (trace_srandom, seed + cur_time);
+#endif
+ postdb_startup ();
+
+#ifdef DHCPv6
+ /*
+ * Set server DHCPv6 identifier.
+ * See dhcpv6.c for discussion of setting DUID.
+ */
+ if (set_server_duid_from_option() == ISC_R_SUCCESS) {
+ write_server_duid();
+ } else {
+ if (!server_duid_isset()) {
+ if (generate_new_server_duid() != ISC_R_SUCCESS) {
+ log_fatal("Unable to set server identifier.");
+ }
+ write_server_duid();
+ }
+ }
+#endif /* DHCPv6 */
+
+#ifndef DEBUG
+ if (daemon) {
+ /* First part of becoming a daemon... */
+ if ((pid = fork ()) < 0)
+ log_fatal ("Can't fork daemon: %m");
+ else if (pid)
+ exit (0);
+ }
+
+#if defined (PARANOIA)
+ /* change uid to the specified one */
+
+ if (set_gid) {
+ if (setgroups (0, (void *)0))
+ log_fatal ("setgroups: %m");
+ if (setgid (set_gid))
+ log_fatal ("setgid(%d): %m", (int) set_gid);
+ }
+
+ if (set_uid) {
+ if (setuid (set_uid))
+ log_fatal ("setuid(%d): %m", (int) set_uid);
+ }
+#endif /* PARANOIA */
+
+ /*
+ * Second part of dealing with pid files. Now
+ * that we have forked we can write our pid if
+ * appropriate.
+ */
+ if (no_pid_file == ISC_FALSE) {
+ i = open(path_dhcpd_pid, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+ if (i >= 0) {
+ sprintf(pbuf, "%d\n", (int) getpid());
+ IGNORE_RET(write(i, pbuf, strlen(pbuf)));
+ close(i);
+ } else {
+ log_error("Can't create PID file %s: %m.",
+ path_dhcpd_pid);
+ }
+ }
+
+ /* If we were requested to log to stdout on the command line,
+ keep doing so; otherwise, stop. */
+ if (log_perror == -1)
+ log_perror = 1;
+ else
+ log_perror = 0;
+
+ if (daemon) {
+ /* Become session leader and get pid... */
+ (void) setsid();
+
+ /* Close standard I/O descriptors. */
+ (void) close(0);
+ (void) close(1);
+ (void) close(2);
+
+ /* Reopen them on /dev/null. */
+ (void) open("/dev/null", O_RDWR);
+ (void) open("/dev/null", O_RDWR);
+ (void) open("/dev/null", O_RDWR);
+ log_perror = 0; /* No sense logging to /dev/null. */
+
+ IGNORE_RET (chdir("/"));
+ }
+#endif /* !DEBUG */
+
+#if defined (DEBUG_MEMORY_LEAKAGE) || defined (DEBUG_MALLOC_POOL) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ dmalloc_cutoff_generation = dmalloc_generation;
+ dmalloc_longterm = dmalloc_outstanding;
+ dmalloc_outstanding = 0;
+#endif
+
+ omapi_set_int_value ((omapi_object_t *)dhcp_control_object,
+ (omapi_object_t *)0, "state", server_running);
+
+#if defined(ENABLE_GENTLE_SHUTDOWN)
+ /* no signal handlers until we deal with the side effects */
+ /* install signal handlers */
+ signal(SIGINT, dhcp_signal_handler); /* control-c */
+ signal(SIGTERM, dhcp_signal_handler); /* kill */
+#endif
+
+ /*
+ * Receive packets and dispatch them...
+ * dispatch() will never return.
+ */
+ dispatch ();
+
+ /* Let's return status code */
+ return 0;
+}
+#endif /* !UNIT_TEST */
+
+void postconf_initialization (int quiet)
+{
+ struct option_state *options = (struct option_state *)0;
+ struct data_string db;
+ struct option_cache *oc;
+ char *s;
+ isc_result_t result;
+#if defined (NSUPDATE)
+ struct parse *parse;
+#endif
+ int tmp;
+
+ /* Now try to get the lease file name. */
+ option_state_allocate (&options, MDL);
+
+ execute_statements_in_scope ((struct binding_value **)0,
+ (struct packet *)0,
+ (struct lease *)0,
+ (struct client_state *)0,
+ (struct option_state *)0,
+ options, &global_scope,
+ root_group,
+ (struct group *)0);
+ memset (&db, 0, sizeof db);
+ oc = lookup_option (&server_universe, options, SV_LEASE_FILE_NAME);
+ if (oc &&
+ evaluate_option_cache (&db, (struct packet *)0,
+ (struct lease *)0, (struct client_state *)0,
+ options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ s = dmalloc (db.len + 1, MDL);
+ if (!s)
+ log_fatal ("no memory for lease db filename.");
+ memcpy (s, db.data, db.len);
+ s [db.len] = 0;
+ data_string_forget (&db, MDL);
+ path_dhcpd_db = s;
+ }
+
+ oc = lookup_option (&server_universe, options, SV_PID_FILE_NAME);
+ if (oc &&
+ evaluate_option_cache (&db, (struct packet *)0,
+ (struct lease *)0, (struct client_state *)0,
+ options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ s = dmalloc (db.len + 1, MDL);
+ if (!s)
+ log_fatal ("no memory for pid filename.");
+ memcpy (s, db.data, db.len);
+ s [db.len] = 0;
+ data_string_forget (&db, MDL);
+ path_dhcpd_pid = s;
+ }
+
+#ifdef DHCPv6
+ if (local_family == AF_INET6) {
+ /*
+ * Override lease file name with dhcpv6 lease file name,
+ * if it was set; then, do the same with the pid file name
+ */
+ oc = lookup_option(&server_universe, options,
+ SV_DHCPV6_LEASE_FILE_NAME);
+ if (oc &&
+ evaluate_option_cache(&db, NULL, NULL, NULL,
+ options, NULL, &global_scope,
+ oc, MDL)) {
+ s = dmalloc (db.len + 1, MDL);
+ if (!s)
+ log_fatal ("no memory for lease db filename.");
+ memcpy (s, db.data, db.len);
+ s [db.len] = 0;
+ data_string_forget (&db, MDL);
+ path_dhcpd_db = s;
+ }
+
+ oc = lookup_option(&server_universe, options,
+ SV_DHCPV6_PID_FILE_NAME);
+ if (oc &&
+ evaluate_option_cache(&db, NULL, NULL, NULL,
+ options, NULL, &global_scope,
+ oc, MDL)) {
+ s = dmalloc (db.len + 1, MDL);
+ if (!s)
+ log_fatal ("no memory for pid filename.");
+ memcpy (s, db.data, db.len);
+ s [db.len] = 0;
+ data_string_forget (&db, MDL);
+ path_dhcpd_pid = s;
+ }
+ }
+#endif /* DHCPv6 */
+
+ omapi_port = -1;
+ oc = lookup_option (&server_universe, options, SV_OMAPI_PORT);
+ if (oc &&
+ evaluate_option_cache (&db, (struct packet *)0,
+ (struct lease *)0, (struct client_state *)0,
+ options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ if (db.len == 2) {
+ omapi_port = getUShort (db.data);
+ } else
+ log_fatal ("invalid omapi port data length");
+ data_string_forget (&db, MDL);
+ }
+
+ oc = lookup_option (&server_universe, options, SV_OMAPI_KEY);
+ if (oc &&
+ evaluate_option_cache (&db, (struct packet *)0,
+ (struct lease *)0, (struct client_state *)0,
+ options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ s = dmalloc (db.len + 1, MDL);
+ if (!s)
+ log_fatal ("no memory for OMAPI key filename.");
+ memcpy (s, db.data, db.len);
+ s [db.len] = 0;
+ data_string_forget (&db, MDL);
+ result = omapi_auth_key_lookup_name (&omapi_key, s);
+ dfree (s, MDL);
+ if (result != ISC_R_SUCCESS)
+ log_fatal ("OMAPI key %s: %s",
+ s, isc_result_totext (result));
+ }
+
+ oc = lookup_option (&server_universe, options, SV_LOCAL_PORT);
+ if (oc &&
+ evaluate_option_cache (&db, (struct packet *)0,
+ (struct lease *)0, (struct client_state *)0,
+ options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ if (db.len == 2) {
+ local_port = htons (getUShort (db.data));
+ } else
+ log_fatal ("invalid local port data length");
+ data_string_forget (&db, MDL);
+ }
+
+ oc = lookup_option (&server_universe, options, SV_REMOTE_PORT);
+ if (oc &&
+ evaluate_option_cache (&db, (struct packet *)0,
+ (struct lease *)0, (struct client_state *)0,
+ options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ if (db.len == 2) {
+ remote_port = htons (getUShort (db.data));
+ } else
+ log_fatal ("invalid remote port data length");
+ data_string_forget (&db, MDL);
+ }
+
+ oc = lookup_option (&server_universe, options,
+ SV_LIMITED_BROADCAST_ADDRESS);
+ if (oc &&
+ evaluate_option_cache (&db, (struct packet *)0,
+ (struct lease *)0, (struct client_state *)0,
+ options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ if (db.len == 4) {
+ memcpy (&limited_broadcast, db.data, 4);
+ } else
+ log_fatal ("invalid broadcast address data length");
+ data_string_forget (&db, MDL);
+ }
+
+ oc = lookup_option (&server_universe, options,
+ SV_LOCAL_ADDRESS);
+ if (oc &&
+ evaluate_option_cache (&db, (struct packet *)0,
+ (struct lease *)0, (struct client_state *)0,
+ options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ if (db.len == 4) {
+ memcpy (&local_address, db.data, 4);
+ } else
+ log_fatal ("invalid local address data length");
+ data_string_forget (&db, MDL);
+ }
+
+ oc = lookup_option (&server_universe, options, SV_DDNS_UPDATE_STYLE);
+ if (oc) {
+ if (evaluate_option_cache (&db, (struct packet *)0,
+ (struct lease *)0,
+ (struct client_state *)0,
+ options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ if (db.len == 1) {
+ ddns_update_style = db.data [0];
+ } else
+ log_fatal ("invalid dns update type");
+ data_string_forget (&db, MDL);
+ }
+ } else {
+ ddns_update_style = DDNS_UPDATE_STYLE_NONE;
+ }
+#if defined (NSUPDATE)
+ /* We no longer support ad_hoc, tell the user */
+ if (ddns_update_style == DDNS_UPDATE_STYLE_AD_HOC) {
+ log_fatal("ddns-update-style ad_hoc no longer supported");
+ }
+#else
+ /* If we don't have support for updates compiled in tell the user */
+ if (ddns_update_style != DDNS_UPDATE_STYLE_NONE) {
+ log_fatal("Support for ddns-update-style not compiled in");
+ }
+#endif
+
+ oc = lookup_option (&server_universe, options, SV_LOG_FACILITY);
+ if (oc) {
+ if (evaluate_option_cache (&db, (struct packet *)0,
+ (struct lease *)0,
+ (struct client_state *)0,
+ options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ if (db.len == 1) {
+ closelog ();
+ openlog("dhcpd", DHCP_LOG_OPTIONS, db.data[0]);
+ /* Log the startup banner into the new
+ log file. */
+ if (!quiet) {
+ /* Don't log to stderr twice. */
+ tmp = log_perror;
+ log_perror = 0;
+ log_info("%s %s",
+ message, PACKAGE_VERSION);
+ log_info (copyright);
+ log_info (arr);
+ log_info (url);
+ log_perror = tmp;
+ }
+ } else
+ log_fatal ("invalid log facility");
+ data_string_forget (&db, MDL);
+ }
+ }
+
+ oc = lookup_option(&server_universe, options, SV_DELAYED_ACK);
+ if (oc &&
+ evaluate_option_cache(&db, NULL, NULL, NULL, options, NULL,
+ &global_scope, oc, MDL)) {
+ if (db.len == 2) {
+ max_outstanding_acks = htons(getUShort(db.data));
+ } else {
+ log_fatal("invalid max delayed ACK count ");
+ }
+ data_string_forget(&db, MDL);
+ }
+
+ oc = lookup_option(&server_universe, options, SV_MAX_ACK_DELAY);
+ if (oc &&
+ evaluate_option_cache(&db, NULL, NULL, NULL, options, NULL,
+ &global_scope, oc, MDL)) {
+ u_int32_t timeval;
+
+ if (db.len != 4)
+ log_fatal("invalid max ack delay configuration");
+
+ timeval = getULong(db.data);
+ max_ack_delay_secs = timeval / 1000000;
+ max_ack_delay_usecs = timeval % 1000000;
+
+ data_string_forget(&db, MDL);
+ }
+
+ /* Don't need the options anymore. */
+ option_state_dereference (&options, MDL);
+
+#if defined (NSUPDATE)
+ /* If old-style ddns updates have been requested, parse the
+ old-style ddns updater. */
+ if (ddns_update_style == 1) {
+ struct executable_statement **e, *s;
+
+ if (root_group -> statements) {
+ s = (struct executable_statement *)0;
+ if (!executable_statement_allocate (&s, MDL))
+ log_fatal ("no memory for ddns updater");
+ executable_statement_reference
+ (&s -> next, root_group -> statements, MDL);
+ executable_statement_dereference
+ (&root_group -> statements, MDL);
+ executable_statement_reference
+ (&root_group -> statements, s, MDL);
+ s -> op = statements_statement;
+ e = &s -> data.statements;
+ executable_statement_dereference (&s, MDL);
+ } else {
+ e = &root_group -> statements;
+ }
+
+ /* Set up the standard name service updater routine. */
+ parse = NULL;
+ result = new_parse(&parse, -1, old_nsupdate,
+ sizeof(old_nsupdate) - 1,
+ "old name service update routine", 0);
+ if (result != ISC_R_SUCCESS)
+ log_fatal ("can't begin parsing old ddns updater!");
+
+ if (parse != NULL) {
+ tmp = 0;
+ if (!(parse_executable_statements(e, parse, &tmp,
+ context_any))) {
+ end_parse(&parse);
+ log_fatal("can't parse standard ddns updater!");
+ }
+ }
+ end_parse(&parse);
+ }
+#endif
+}
+
+void postdb_startup (void)
+{
+ /* Initialize the omapi listener state. */
+ if (omapi_port != -1) {
+ omapi_listener_start (0);
+ }
+
+#if defined (FAILOVER_PROTOCOL)
+ /* Initialize the failover listener state. */
+ dhcp_failover_startup ();
+#endif
+
+ /*
+ * Begin our lease timeout background task.
+ */
+ schedule_all_ipv6_lease_timeouts();
+}
+
+/* Print usage message. */
+#ifndef UNIT_TEST
+static void
+usage(void) {
+ log_info("%s %s", message, PACKAGE_VERSION);
+ log_info(copyright);
+ log_info(arr);
+
+ log_fatal("Usage: dhcpd [-p <UDP port #>] [-f] [-d] [-q] [-t|-T]\n"
+#ifdef DHCPv6
+ " [-4|-6] [-cf config-file] [-lf lease-file]\n"
+#else /* !DHCPv6 */
+ " [-cf config-file] [-lf lease-file]\n"
+#endif /* DHCPv6 */
+#if defined (PARANOIA)
+ /* meld into the following string */
+ " [-user user] [-group group] [-chroot dir]\n"
+#endif /* PARANOIA */
+#if defined (TRACING)
+ " [-tf trace-output-file]\n"
+ " [-play trace-input-file]\n"
+#endif /* TRACING */
+ " [-pf pid-file] [--no-pid] [-s server]\n"
+ " [if0 [...ifN]]");
+}
+#endif
+
+void lease_pinged (from, packet, length)
+ struct iaddr from;
+ u_int8_t *packet;
+ int length;
+{
+ struct lease *lp;
+
+ /* Don't try to look up a pinged lease if we aren't trying to
+ ping one - otherwise somebody could easily make us churn by
+ just forging repeated ICMP EchoReply packets for us to look
+ up. */
+ if (!outstanding_pings)
+ return;
+
+ lp = (struct lease *)0;
+ if (!find_lease_by_ip_addr (&lp, from, MDL)) {
+ log_debug ("unexpected ICMP Echo Reply from %s",
+ piaddr (from));
+ return;
+ }
+
+ if (!lp -> state) {
+#if defined (FAILOVER_PROTOCOL)
+ if (!lp -> pool ||
+ !lp -> pool -> failover_peer)
+#endif
+ log_debug ("ICMP Echo Reply for %s late or spurious.",
+ piaddr (from));
+ goto out;
+ }
+
+ if (lp -> ends > cur_time) {
+ log_debug ("ICMP Echo reply while lease %s valid.",
+ piaddr (from));
+ }
+
+ /* At this point it looks like we pinged a lease and got a
+ response, which shouldn't have happened. */
+ data_string_forget (&lp -> state -> parameter_request_list, MDL);
+ free_lease_state (lp -> state, MDL);
+ lp -> state = (struct lease_state *)0;
+
+ abandon_lease (lp, "pinged before offer");
+ cancel_timeout (lease_ping_timeout, lp);
+ --outstanding_pings;
+ out:
+ lease_dereference (&lp, MDL);
+}
+
+void lease_ping_timeout (vlp)
+ void *vlp;
+{
+ struct lease *lp = vlp;
+
+#if defined (DEBUG_MEMORY_LEAKAGE)
+ unsigned long previous_outstanding = dmalloc_outstanding;
+#endif
+
+ --outstanding_pings;
+ dhcp_reply (lp);
+
+#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);
+#endif
+#if defined (DEBUG_MEMORY_LEAKAGE)
+ dmalloc_dump_outstanding ();
+#endif
+}
+
+int dhcpd_interface_setup_hook (struct interface_info *ip, struct iaddr *ia)
+{
+ struct subnet *subnet;
+ struct shared_network *share;
+ isc_result_t status;
+
+ /* Special case for fallback network - not sure why this is
+ necessary. */
+ if (!ia) {
+ const char *fnn = "fallback-net";
+ status = shared_network_allocate (&ip -> shared_network, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("No memory for shared subnet: %s",
+ isc_result_totext (status));
+ ip -> shared_network -> name = dmalloc (strlen (fnn) + 1, MDL);
+ strcpy (ip -> shared_network -> name, fnn);
+ return 1;
+ }
+
+ /* If there's a registered subnet for this address,
+ connect it together... */
+ subnet = (struct subnet *)0;
+ if (find_subnet (&subnet, *ia, MDL)) {
+ /* If this interface has multiple aliases on the same
+ subnet, ignore all but the first we encounter. */
+ if (!subnet -> interface) {
+ interface_reference (&subnet -> interface, ip, MDL);
+ subnet -> interface_address = *ia;
+ } else if (subnet -> interface != ip) {
+ log_error ("Multiple interfaces match the %s: %s %s",
+ "same subnet",
+ subnet -> interface -> name, ip -> name);
+ }
+ share = subnet -> shared_network;
+ if (ip -> shared_network &&
+ ip -> shared_network != share) {
+ log_fatal ("Interface %s matches multiple shared %s",
+ ip -> name, "networks");
+ } else {
+ if (!ip -> shared_network)
+ shared_network_reference
+ (&ip -> shared_network, share, MDL);
+ }
+
+ if (!share -> interface) {
+ interface_reference (&share -> interface, ip, MDL);
+ } else if (share -> interface != ip) {
+ log_error ("Multiple interfaces match the %s: %s %s",
+ "same shared network",
+ share -> interface -> name, ip -> name);
+ }
+ subnet_dereference (&subnet, MDL);
+ }
+ return 1;
+}
+
+static TIME shutdown_time;
+static int omapi_connection_count;
+enum dhcp_shutdown_state shutdown_state;
+
+isc_result_t dhcp_io_shutdown (omapi_object_t *obj, void *foo)
+{
+ /* Shut down all listeners. */
+ if (shutdown_state == shutdown_listeners &&
+ obj -> type == omapi_type_listener &&
+ obj -> inner &&
+ obj -> inner -> type == omapi_type_protocol_listener) {
+ omapi_listener_destroy (obj, MDL);
+ return ISC_R_SUCCESS;
+ }
+
+ /* Shut down all existing omapi connections. */
+ if (obj -> type == omapi_type_connection &&
+ obj -> inner &&
+ obj -> inner -> type == omapi_type_protocol) {
+ if (shutdown_state == shutdown_drop_omapi_connections) {
+ omapi_disconnect (obj, 1);
+ }
+ omapi_connection_count++;
+ if (shutdown_state == shutdown_omapi_connections) {
+ omapi_disconnect (obj, 0);
+ return ISC_R_SUCCESS;
+ }
+ }
+
+ /* Shutdown all DHCP interfaces. */
+ if (obj -> type == dhcp_type_interface &&
+ shutdown_state == shutdown_dhcp) {
+ dhcp_interface_remove (obj, (omapi_object_t *)0);
+ return ISC_R_SUCCESS;
+ }
+ return ISC_R_SUCCESS;
+}
+
+static isc_result_t dhcp_io_shutdown_countdown (void *vlp)
+{
+#if defined (FAILOVER_PROTOCOL)
+ dhcp_failover_state_t *state;
+ int failover_connection_count = 0;
+#endif
+ struct timeval tv;
+
+ oncemore:
+ if (shutdown_state == shutdown_listeners ||
+ shutdown_state == shutdown_omapi_connections ||
+ shutdown_state == shutdown_drop_omapi_connections ||
+ shutdown_state == shutdown_dhcp) {
+ omapi_connection_count = 0;
+ omapi_io_state_foreach (dhcp_io_shutdown, 0);
+ }
+
+ if ((shutdown_state == shutdown_listeners ||
+ shutdown_state == shutdown_omapi_connections ||
+ shutdown_state == shutdown_drop_omapi_connections) &&
+ omapi_connection_count == 0) {
+ shutdown_state = shutdown_dhcp;
+ shutdown_time = cur_time;
+ goto oncemore;
+ } else if (shutdown_state == shutdown_listeners &&
+ cur_time - shutdown_time > 4) {
+ shutdown_state = shutdown_omapi_connections;
+ shutdown_time = cur_time;
+ } else if (shutdown_state == shutdown_omapi_connections &&
+ cur_time - shutdown_time > 4) {
+ shutdown_state = shutdown_drop_omapi_connections;
+ shutdown_time = cur_time;
+ } else if (shutdown_state == shutdown_drop_omapi_connections &&
+ cur_time - shutdown_time > 4) {
+ shutdown_state = shutdown_dhcp;
+ shutdown_time = cur_time;
+ goto oncemore;
+ } else if (shutdown_state == shutdown_dhcp &&
+ cur_time - shutdown_time > 4) {
+ shutdown_state = shutdown_done;
+ shutdown_time = cur_time;
+ }
+
+#if defined (FAILOVER_PROTOCOL)
+ /* Set all failover peers into the shutdown state. */
+ if (shutdown_state == shutdown_dhcp) {
+ for (state = failover_states; state; state = state -> next) {
+ if (state -> me.state == normal) {
+ dhcp_failover_set_state (state, shut_down);
+ failover_connection_count++;
+ }
+ if (state -> me.state == shut_down &&
+ state -> partner.state != partner_down)
+ failover_connection_count++;
+ }
+ }
+
+ if (shutdown_state == shutdown_done) {
+ for (state = failover_states; state; state = state -> next) {
+ if (state -> me.state == shut_down) {
+ if (state -> link_to_peer)
+ dhcp_failover_link_dereference (&state -> link_to_peer,
+ MDL);
+ dhcp_failover_set_state (state, recover);
+ }
+ }
+#if defined (DEBUG_MEMORY_LEAKAGE) && \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ free_everything ();
+ omapi_print_dmalloc_usage_by_caller ();
+#endif
+ exit (0);
+ }
+#else
+ if (shutdown_state == shutdown_done) {
+#if defined (DEBUG_MEMORY_LEAKAGE) && \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ free_everything ();
+ omapi_print_dmalloc_usage_by_caller ();
+#endif
+ exit (0);
+ }
+#endif
+ if (shutdown_state == shutdown_dhcp &&
+#if defined(FAILOVER_PROTOCOL)
+ !failover_connection_count &&
+#endif
+ ISC_TRUE) {
+ shutdown_state = shutdown_done;
+ shutdown_time = cur_time;
+ goto oncemore;
+ }
+ tv.tv_sec = cur_tv.tv_sec + 1;
+ tv.tv_usec = cur_tv.tv_usec;
+ add_timeout (&tv,
+ (void (*)(void *))dhcp_io_shutdown_countdown, 0, 0, 0);
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_set_control_state (control_object_state_t oldstate,
+ control_object_state_t newstate)
+{
+ struct timeval tv;
+
+ if (newstate != server_shutdown)
+ return DHCP_R_INVALIDARG;
+ /* Re-entry. */
+ if (shutdown_signal == SIGUSR1)
+ return ISC_R_SUCCESS;
+ shutdown_time = cur_time;
+ shutdown_state = shutdown_listeners;
+ /* Called by user. */
+ if (shutdown_signal == 0) {
+ shutdown_signal = SIGUSR1;
+ dhcp_io_shutdown_countdown (0);
+ return ISC_R_SUCCESS;
+ }
+ /* Called on signal. */
+ log_info("Received signal %d, initiating shutdown.", shutdown_signal);
+ shutdown_signal = SIGUSR1;
+
+ /*
+ * Prompt the shutdown event onto the timer queue
+ * and return to the dispatch loop.
+ */
+ tv.tv_sec = cur_tv.tv_sec;
+ tv.tv_usec = cur_tv.tv_usec + 1;
+ add_timeout(&tv,
+ (void (*)(void *))dhcp_io_shutdown_countdown, 0, 0, 0);
+ return ISC_R_SUCCESS;
+}
diff --git a/server/dhcpd.conf.5 b/server/dhcpd.conf.5
new file mode 100644
index 0000000..e844ce3
--- /dev/null
+++ b/server/dhcpd.conf.5
@@ -0,0 +1,3026 @@
+.\" dhcpd.conf.5
+.\"
+.\" Copyright (c) 2004-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 has been written for Internet Systems Consortium
+.\" by Ted Lemon in cooperation with Vixie Enterprises and Nominum, Inc.
+.\"
+.\" Support and other services are available for ISC products - see
+.\" https://www.isc.org for more information or to learn more about ISC.
+.\"
+.\" $Id: dhcpd.conf.5,v 1.106.18.8 2012/04/02 22:51:02 sar Exp $
+.\"
+.TH dhcpd.conf 5
+.SH NAME
+dhcpd.conf - dhcpd configuration file
+.SH DESCRIPTION
+The dhcpd.conf file contains configuration information for
+.IR dhcpd,
+the Internet Systems Consortium DHCP Server.
+.PP
+The dhcpd.conf file is a free-form ASCII text file. It is parsed by
+the recursive-descent parser built into dhcpd. The file may contain
+extra tabs and newlines for formatting purposes. Keywords in the file
+are case-insensitive. Comments may be placed anywhere within the
+file (except within quotes). Comments begin with the # character and
+end at the end of the line.
+.PP
+The file essentially consists of a list of statements. Statements
+fall into two broad categories - parameters and declarations.
+.PP
+Parameter statements either say how to do something (e.g., how long a
+lease to offer), whether to do something (e.g., should dhcpd provide
+addresses to unknown clients), or what parameters to provide to the
+client (e.g., use gateway 220.177.244.7).
+.PP
+Declarations are used to describe the topology of the
+network, to describe clients on the network, to provide addresses that
+can be assigned to clients, or to apply a group of parameters to a
+group of declarations. In any group of parameters and declarations,
+all parameters must be specified before any declarations which depend
+on those parameters may be specified.
+.PP
+Declarations about network topology include the \fIshared-network\fR
+and the \fIsubnet\fR declarations. If clients on a subnet are to be
+assigned addresses
+dynamically, a \fIrange\fR declaration must appear within the
+\fIsubnet\fR declaration. For clients with statically assigned
+addresses, or for installations where only known clients will be
+served, each such client must have a \fIhost\fR declaration. If
+parameters are to be applied to a group of declarations which are not
+related strictly on a per-subnet basis, the \fIgroup\fR declaration
+can be used.
+.PP
+For every subnet which will be served, and for every subnet
+to which the dhcp server is connected, there must be one \fIsubnet\fR
+declaration, which tells dhcpd how to recognize that an address is on
+that subnet. A \fIsubnet\fR declaration is required for each subnet
+even if no addresses will be dynamically allocated on that subnet.
+.PP
+Some installations have physical networks on which more than one IP
+subnet operates. For example, if there is a site-wide requirement
+that 8-bit subnet masks be used, but a department with a single
+physical ethernet network expands to the point where it has more than
+254 nodes, it may be necessary to run two 8-bit subnets on the same
+ethernet until such time as a new physical network can be added. In
+this case, the \fIsubnet\fR declarations for these two networks must be
+enclosed in a \fIshared-network\fR declaration.
+.PP
+Note that even when the \fIshared-network\fR declaration is absent, an
+empty one is created by the server to contain the \fIsubnet\fR (and any scoped
+parameters included in the \fIsubnet\fR). For practical purposes, this means
+that "stateless" DHCP clients, which are not tied to addresses (and therefore
+subnets) will receive the same configuration as stateful ones.
+.PP
+Some sites may have departments which have clients on more than one
+subnet, but it may be desirable to offer those clients a uniform set
+of parameters which are different than what would be offered to
+clients from other departments on the same subnet. For clients which
+will be declared explicitly with \fIhost\fR declarations, these
+declarations can be enclosed in a \fIgroup\fR declaration along with
+the parameters which are common to that department. For clients
+whose addresses will be dynamically assigned, class declarations and
+conditional declarations may be used to group parameter assignments
+based on information the client sends.
+.PP
+When a client is to be booted, its boot parameters are determined by
+consulting that client's \fIhost\fR declaration (if any), and then
+consulting any \fIclass\fR declarations matching the client,
+followed by the \fIpool\fR, \fIsubnet\fR and \fIshared-network\fR
+declarations for the IP address assigned to the client. Each of
+these declarations itself appears within a lexical scope, and all
+declarations at less specific lexical scopes are also consulted for
+client option declarations. Scopes are never considered
+twice, and if parameters are declared in more than one scope, the
+parameter declared in the most specific scope is the one that is
+used.
+.PP
+When dhcpd tries to find a \fIhost\fR declaration for a client, it
+first looks for a \fIhost\fR declaration which has a
+\fIfixed-address\fR declaration that lists an IP address that is valid
+for the subnet or shared network on which the client is booting. If
+it doesn't find any such entry, it tries to find an entry which has
+no \fIfixed-address\fR declaration.
+.SH EXAMPLES
+.PP
+A typical dhcpd.conf file will look something like this:
+.nf
+
+.I global parameters...
+
+subnet 204.254.239.0 netmask 255.255.255.224 {
+ \fIsubnet-specific parameters...\fR
+ range 204.254.239.10 204.254.239.30;
+}
+
+subnet 204.254.239.32 netmask 255.255.255.224 {
+ \fIsubnet-specific parameters...\fR
+ range 204.254.239.42 204.254.239.62;
+}
+
+subnet 204.254.239.64 netmask 255.255.255.224 {
+ \fIsubnet-specific parameters...\fR
+ range 204.254.239.74 204.254.239.94;
+}
+
+group {
+ \fIgroup-specific parameters...\fR
+ host zappo.test.isc.org {
+ \fIhost-specific parameters...\fR
+ }
+ host beppo.test.isc.org {
+ \fIhost-specific parameters...\fR
+ }
+ host harpo.test.isc.org {
+ \fIhost-specific parameters...\fR
+ }
+}
+
+.ce 1
+Figure 1
+
+.fi
+.PP
+Notice that at the beginning of the file, there's a place
+for global parameters. These might be things like the organization's
+domain name, the addresses of the name servers (if they are common to
+the entire organization), and so on. So, for example:
+.nf
+
+ option domain-name "isc.org";
+ option domain-name-servers ns1.isc.org, ns2.isc.org;
+
+.ce 1
+Figure 2
+.fi
+.PP
+As you can see in Figure 2, you can specify host addresses in
+parameters using their domain names rather than their numeric IP
+addresses. If a given hostname resolves to more than one IP address
+(for example, if that host has two ethernet interfaces), then where
+possible, both addresses are supplied to the client.
+.PP
+The most obvious reason for having subnet-specific parameters as
+shown in Figure 1 is that each subnet, of necessity, has its own
+router. So for the first subnet, for example, there should be
+something like:
+.nf
+
+ option routers 204.254.239.1;
+.fi
+.PP
+Note that the address here is specified numerically. This is not
+required - if you have a different domain name for each interface on
+your router, it's perfectly legitimate to use the domain name for that
+interface instead of the numeric address. However, in many cases
+there may be only one domain name for all of a router's IP addresses, and
+it would not be appropriate to use that name here.
+.PP
+In Figure 1 there is also a \fIgroup\fR statement, which provides
+common parameters for a set of three hosts - zappo, beppo and harpo.
+As you can see, these hosts are all in the test.isc.org domain, so it
+might make sense for a group-specific parameter to override the domain
+name supplied to these hosts:
+.nf
+
+ option domain-name "test.isc.org";
+.fi
+.PP
+Also, given the domain they're in, these are probably test machines.
+If we wanted to test the DHCP leasing mechanism, we might set the
+lease timeout somewhat shorter than the default:
+
+.nf
+ max-lease-time 120;
+ default-lease-time 120;
+.fi
+.PP
+You may have noticed that while some parameters start with the
+\fIoption\fR keyword, some do not. Parameters starting with the
+\fIoption\fR keyword correspond to actual DHCP options, while
+parameters that do not start with the option keyword either control
+the behavior of the DHCP server (e.g., how long a lease dhcpd will
+give out), or specify client parameters that are not optional in the
+DHCP protocol (for example, server-name and filename).
+.PP
+In Figure 1, each host had \fIhost-specific parameters\fR. These
+could include such things as the \fIhostname\fR option, the name of a
+file to upload (the \fIfilename\fR parameter) and the address of the
+server from which to upload the file (the \fInext-server\fR
+parameter). In general, any parameter can appear anywhere that
+parameters are allowed, and will be applied according to the scope in
+which the parameter appears.
+.PP
+Imagine that you have a site with a lot of NCD X-Terminals. These
+terminals come in a variety of models, and you want to specify the
+boot files for each model. One way to do this would be to have host
+declarations for each server and group them by model:
+.nf
+
+group {
+ filename "Xncd19r";
+ next-server ncd-booter;
+
+ host ncd1 { hardware ethernet 0:c0:c3:49:2b:57; }
+ host ncd4 { hardware ethernet 0:c0:c3:80:fc:32; }
+ host ncd8 { hardware ethernet 0:c0:c3:22:46:81; }
+}
+
+group {
+ filename "Xncd19c";
+ next-server ncd-booter;
+
+ host ncd2 { hardware ethernet 0:c0:c3:88:2d:81; }
+ host ncd3 { hardware ethernet 0:c0:c3:00:14:11; }
+}
+
+group {
+ filename "XncdHMX";
+ next-server ncd-booter;
+
+ host ncd1 { hardware ethernet 0:c0:c3:11:90:23; }
+ host ncd4 { hardware ethernet 0:c0:c3:91:a7:8; }
+ host ncd8 { hardware ethernet 0:c0:c3:cc:a:8f; }
+}
+.fi
+.SH ADDRESS POOLS
+.PP
+The
+.B pool
+declaration can be used to specify a pool of addresses that will be
+treated differently than another pool of addresses, even on the same
+network segment or subnet. For example, you may want to provide a
+large set of addresses that can be assigned to DHCP clients that are
+registered to your DHCP server, while providing a smaller set of
+addresses, possibly with short lease times, that are available for
+unknown clients. If you have a firewall, you may be able to arrange
+for addresses from one pool to be allowed access to the Internet,
+while addresses in another pool are not, thus encouraging users to
+register their DHCP clients. To do this, you would set up a pair of
+pool declarations:
+.PP
+.nf
+subnet 10.0.0.0 netmask 255.255.255.0 {
+ option routers 10.0.0.254;
+
+ # Unknown clients get this pool.
+ pool {
+ option domain-name-servers bogus.example.com;
+ max-lease-time 300;
+ range 10.0.0.200 10.0.0.253;
+ allow unknown-clients;
+ }
+
+ # Known clients get this pool.
+ pool {
+ option domain-name-servers ns1.example.com, ns2.example.com;
+ max-lease-time 28800;
+ range 10.0.0.5 10.0.0.199;
+ deny unknown-clients;
+ }
+}
+.fi
+.PP
+It is also possible to set up entirely different subnets for known and
+unknown clients - address pools exist at the level of shared networks,
+so address ranges within pool declarations can be on different
+subnets.
+.PP
+As you can see in the preceding example, pools can have permit lists
+that control which clients are allowed access to the pool and which
+aren't. Each entry in a pool's permit list is introduced with the
+.I allow
+or \fIdeny\fR keyword. If a pool has a permit list, then only those
+clients that match specific entries on the permit list will be
+eligible to be assigned addresses from the pool. If a pool has a
+deny list, then only those clients that do not match any entries on
+the deny list will be eligible. If both permit and deny lists exist
+for a pool, then only clients that match the permit list and do not
+match the deny list will be allowed access.
+.SH DYNAMIC ADDRESS ALLOCATION
+Address allocation is actually only done when a client is in the INIT
+state and has sent a DHCPDISCOVER message. If the client thinks it
+has a valid lease and sends a DHCPREQUEST to initiate or renew that
+lease, the server has only three choices - it can ignore the
+DHCPREQUEST, send a DHCPNAK to tell the client it should stop using
+the address, or send a DHCPACK, telling the client to go ahead and use
+the address for a while.
+.PP
+If the server finds the address the client is requesting, and that
+address is available to the client, the server will send a DHCPACK.
+If the address is no longer available, or the client isn't permitted
+to have it, the server will send a DHCPNAK. If the server knows
+nothing about the address, it will remain silent, unless the address
+is incorrect for the network segment to which the client has been
+attached and the server is authoritative for that network segment, in
+which case the server will send a DHCPNAK even though it doesn't know
+about the address.
+.PP
+There may be a host declaration matching the client's identification.
+If that host declaration contains a fixed-address declaration that
+lists an IP address that is valid for the network segment to which the
+client is connected. In this case, the DHCP server will never do
+dynamic address allocation. In this case, the client is \fIrequired\fR
+to take the address specified in the host declaration. If the
+client sends a DHCPREQUEST for some other address, the server will respond
+with a DHCPNAK.
+.PP
+When the DHCP server allocates a new address for a client (remember,
+this only happens if the client has sent a DHCPDISCOVER), it first
+looks to see if the client already has a valid lease on an IP address,
+or if there is an old IP address the client had before that hasn't yet
+been reassigned. In that case, the server will take that address and
+check it to see if the client is still permitted to use it. If the
+client is no longer permitted to use it, the lease is freed if the
+server thought it was still in use - the fact that the client has sent
+a DHCPDISCOVER proves to the server that the client is no longer using
+the lease.
+.PP
+If no existing lease is found, or if the client is forbidden to
+receive the existing lease, then the server will look in the list of
+address pools for the network segment to which the client is attached
+for a lease that is not in use and that the client is permitted to
+have. It looks through each pool declaration in sequence (all
+.I range
+declarations that appear outside of pool declarations are grouped into
+a single pool with no permit list). If the permit list for the pool
+allows the client to be allocated an address from that pool, the pool
+is examined to see if there is an address available. If so, then the
+client is tentatively assigned that address. Otherwise, the next
+pool is tested. If no addresses are found that can be assigned to
+the client, no response is sent to the client.
+.PP
+If an address is found that the client is permitted to have, and that
+has never been assigned to any client before, the address is
+immediately allocated to the client. If the address is available for
+allocation but has been previously assigned to a different client, the
+server will keep looking in hopes of finding an address that has never
+before been assigned to a client.
+.PP
+The DHCP server generates the list of available IP addresses from a
+hash table. This means that the addresses are not sorted in any
+particular order, and so it is not possible to predict the order in
+which the DHCP server will allocate IP addresses. Users of previous
+versions of the ISC DHCP server may have become accustomed to the DHCP
+server allocating IP addresses in ascending order, but this is no
+longer possible, and there is no way to configure this behavior with
+version 3 of the ISC DHCP server.
+.SH IP ADDRESS CONFLICT PREVENTION
+The DHCP server checks IP addresses to see if they are in use before
+allocating them to clients. It does this by sending an ICMP Echo
+request message to the IP address being allocated. If no ICMP Echo
+reply is received within a second, the address is assumed to be free.
+This is only done for leases that have been specified in range
+statements, and only when the lease is thought by the DHCP server to
+be free - i.e., the DHCP server or its failover peer has not listed
+the lease as in use.
+.PP
+If a response is received to an ICMP Echo request, the DHCP server
+assumes that there is a configuration error - the IP address is in use
+by some host on the network that is not a DHCP client. It marks the
+address as abandoned, and will not assign it to clients.
+.PP
+If a DHCP client tries to get an IP address, but none are available,
+but there are abandoned IP addresses, then the DHCP server will
+attempt to reclaim an abandoned IP address. It marks one IP address
+as free, and then does the same ICMP Echo request check described
+previously. If there is no answer to the ICMP Echo request, the
+address is assigned to the client.
+.PP
+The DHCP server does not cycle through abandoned IP addresses if the
+first IP address it tries to reclaim is free. Rather, when the next
+DHCPDISCOVER comes in from the client, it will attempt a new
+allocation using the same method described here, and will typically
+try a new IP address.
+.SH DHCP FAILOVER
+This version of the ISC DHCP server supports the DHCP failover
+protocol as documented in draft-ietf-dhc-failover-12.txt. This is
+not a final protocol document, and we have not done interoperability
+testing with other vendors' implementations of this protocol, so you
+must not assume that this implementation conforms to the standard.
+If you wish to use the failover protocol, make sure that both failover
+peers are running the same version of the ISC DHCP server.
+.PP
+The failover protocol allows two DHCP servers (and no more than two)
+to share a common address pool. Each server will have about half of
+the available IP addresses in the pool at any given time for
+allocation. If one server fails, the other server will continue to
+renew leases out of the pool, and will allocate new addresses out of
+the roughly half of available addresses that it had when
+communications with the other server were lost.
+.PP
+It is possible during a prolonged failure to tell the remaining server
+that the other server is down, in which case the remaining server will
+(over time) reclaim all the addresses the other server had available
+for allocation, and begin to reuse them. This is called putting the
+server into the PARTNER-DOWN state.
+.PP
+You can put the server into the PARTNER-DOWN state either by using the
+.B omshell (1)
+command or by stopping the server, editing the last failover state
+declaration in the lease file, and restarting the server. If you use
+this last method, change the "my state" line to:
+.PP
+.nf
+.B failover peer "\fIname\fB" state {
+.B my state partner-down;
+.B peer state \fIstate\fB at \fIdate\fB;
+.B }
+.fi
+.PP
+It is only required to change "my state" as shown above.
+.PP
+When the other server comes back online, it should automatically
+detect that it has been offline and request a complete update from the
+server that was running in the PARTNER-DOWN state, and then both
+servers will resume processing together.
+.PP
+It is possible to get into a dangerous situation: if you put one
+server into the PARTNER-DOWN state, and then *that* server goes down,
+and the other server comes back up, the other server will not know
+that the first server was in the PARTNER-DOWN state, and may issue
+addresses previously issued by the other server to different clients,
+resulting in IP address conflicts. Before putting a server into
+PARTNER-DOWN state, therefore, make
+.I sure
+that the other server will not restart automatically.
+.PP
+The failover protocol defines a primary server role and a secondary
+server role. There are some differences in how primaries and
+secondaries act, but most of the differences simply have to do with
+providing a way for each peer to behave in the opposite way from the
+other. So one server must be configured as primary, and the other
+must be configured as secondary, and it doesn't matter too much which
+one is which.
+.SH FAILOVER STARTUP
+When a server starts that has not previously communicated with its
+failover peer, it must establish communications with its failover peer
+and synchronize with it before it can serve clients. This can happen
+either because you have just configured your DHCP servers to perform
+failover for the first time, or because one of your failover servers
+has failed catastrophically and lost its database.
+.PP
+The initial recovery process is designed to ensure that when one
+failover peer loses its database and then resynchronizes, any leases
+that the failed server gave out before it failed will be honored.
+When the failed server starts up, it notices that it has no saved
+failover state, and attempts to contact its peer.
+.PP
+When it has established contact, it asks the peer for a complete copy
+its peer's lease database. The peer then sends its complete database,
+and sends a message indicating that it is done. The failed server
+then waits until MCLT has passed, and once MCLT has passed both
+servers make the transition back into normal operation. This waiting
+period ensures that any leases the failed server may have given out
+while out of contact with its partner will have expired.
+.PP
+While the failed server is recovering, its partner remains in the
+partner-down state, which means that it is serving all clients. The
+failed server provides no service at all to DHCP clients until it has
+made the transition into normal operation.
+.PP
+In the case where both servers detect that they have never before
+communicated with their partner, they both come up in this recovery
+state and follow the procedure we have just described. In this case,
+no service will be provided to DHCP clients until MCLT has expired.
+.SH CONFIGURING FAILOVER
+In order to configure failover, you need to write a peer declaration
+that configures the failover protocol, and you need to write peer
+references in each pool declaration for which you want to do
+failover. You do not have to do failover for all pools on a given
+network segment. You must not tell one server it's doing failover
+on a particular address pool and tell the other it is not. You must
+not have any common address pools on which you are not doing
+failover. A pool declaration that utilizes failover would look like this:
+.PP
+.nf
+pool {
+ failover peer "foo";
+ \fIpool specific parameters\fR
+};
+.fi
+.PP
+The server currently does very little sanity checking, so if you
+configure it wrong, it will just fail in odd ways. I would recommend
+therefore that you either do failover or don't do failover, but don't
+do any mixed pools. Also, use the same master configuration file for
+both servers, and have a separate file that contains the peer
+declaration and includes the master file. This will help you to avoid
+configuration mismatches. As our implementation evolves, this will
+become less of a problem. A basic sample dhcpd.conf file for a
+primary server might look like this:
+.PP
+.nf
+failover peer "foo" {
+ primary;
+ address anthrax.rc.vix.com;
+ port 519;
+ peer address trantor.rc.vix.com;
+ peer port 520;
+ max-response-delay 60;
+ max-unacked-updates 10;
+ mclt 3600;
+ split 128;
+ load balance max seconds 3;
+}
+
+include "/etc/dhcpd.master";
+.fi
+.PP
+The statements in the peer declaration are as follows:
+.PP
+The
+.I primary
+and
+.I secondary
+statements
+.RS 0.25i
+.PP
+[ \fBprimary\fR | \fBsecondary\fR ]\fB;\fR
+.PP
+This determines whether the server is primary or secondary, as
+described earlier under DHCP FAILOVER.
+.RE
+.PP
+The
+.I address
+statement
+.RS 0.25i
+.PP
+.B address \fIaddress\fR\fB;\fR
+.PP
+The \fBaddress\fR statement declares the IP address or DNS name on which the
+server should listen for connections from its failover peer, and also the
+value to use for the DHCP Failover Protocol server identifier. Because this
+value is used as an identifier, it may not be omitted.
+.RE
+.PP
+The
+.I peer address
+statement
+.RS 0.25i
+.PP
+.B peer address \fIaddress\fR\fB;\fR
+.PP
+The \fBpeer address\fR statement declares the IP address or DNS name to
+which the server should connect to reach its failover peer for failover
+messages.
+.RE
+.PP
+The
+.I port
+statement
+.RS 0.25i
+.PP
+.B port \fIport-number\fR\fB;\fR
+.PP
+The \fBport\fR statement declares the TCP port on which the server
+should listen for connections from its failover peer. This statement
+may be omitted, in which case the IANA assigned port number 647 will be
+used by default.
+.RE
+.PP
+The
+.I peer port
+statement
+.RS 0.25i
+.PP
+.B peer port \fIport-number\fR\fB;\fR
+.PP
+The \fBpeer port\fR statement declares the TCP port to which the
+server should connect to reach its failover peer for failover
+messages. This statement may be omitted, in which case the IANA
+assigned port number 647 will be used by default.
+.RE
+.PP
+The
+.I max-response-delay
+statement
+.RS 0.25i
+.PP
+.B max-response-delay \fIseconds\fR\fB;\fR
+.PP
+The \fBmax-response-delay\fR statement tells the DHCP server how
+many seconds may pass without receiving a message from its failover
+peer before it assumes that connection has failed. This number
+should be small enough that a transient network failure that breaks
+the connection will not result in the servers being out of
+communication for a long time, but large enough that the server isn't
+constantly making and breaking connections. This parameter must be
+specified.
+.RE
+.PP
+The
+.I max-unacked-updates
+statement
+.RS 0.25i
+.PP
+.B max-unacked-updates \fIcount\fR\fB;\fR
+.PP
+The \fBmax-unacked-updates\fR statement tells the remote DHCP server how
+many BNDUPD messages it can send before it receives a BNDACK
+from the local system. We don't have enough operational experience
+to say what a good value for this is, but 10 seems to work. This
+parameter must be specified.
+.RE
+.PP
+The
+.I mclt
+statement
+.RS 0.25i
+.PP
+.B mclt \fIseconds\fR\fB;\fR
+.PP
+The \fBmclt\fR statement defines the Maximum Client Lead Time. It
+must be specified on the primary, and may not be specified on the
+secondary. This is the length of time for which a lease may be
+renewed by either failover peer without contacting the other. The
+longer you set this, the longer it will take for the running server to
+recover IP addresses after moving into PARTNER-DOWN state. The
+shorter you set it, the more load your servers will experience when
+they are not communicating. A value of something like 3600 is
+probably reasonable, but again bear in mind that we have no real
+operational experience with this.
+.RE
+.PP
+The
+.I split
+statement
+.RS 0.25i
+.PP
+.B split \fIbits\fR\fB;\fR
+.PP
+The split statement specifies the split between the primary and
+secondary for the purposes of load balancing. Whenever a client
+makes a DHCP request, the DHCP server runs a hash on the client
+identification, resulting in value from 0 to 255. This is used as
+an index into a 256 bit field. If the bit at that index is set,
+the primary is responsible. If the bit at that index is not set,
+the secondary is responsible. The \fBsplit\fR value determines
+how many of the leading bits are set to one. So, in practice, higher
+split values will cause the primary to serve more clients than the
+secondary. Lower split values, the converse. Legal values are between
+0 and 256 inclusive, of which the most reasonable is 128. Note that
+a value of 0 makes the secondary responsible for all clients and a value
+of 256 makes the primary responsible for all clients.
+.RE
+.PP
+The
+.I hba
+statement
+.RS 0.25i
+.PP
+.B hba \fIcolon-separated-hex-list\fB;\fR
+.PP
+The hba statement specifies the split between the primary and
+secondary as a bitmap rather than a cutoff, which theoretically allows
+for finer-grained control. In practice, there is probably no need
+for such fine-grained control, however. An example hba statement:
+.PP
+.nf
+ hba ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
+ 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00;
+.fi
+.PP
+This is equivalent to a \fBsplit 128;\fR statement, and identical. The
+following two examples are also equivalent to a \fBsplit\fR of 128, but
+are not identical:
+.PP
+.nf
+ hba aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:
+ aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa;
+
+ hba 55:55:55:55:55:55:55:55:55:55:55:55:55:55:55:55:
+ 55:55:55:55:55:55:55:55:55:55:55:55:55:55:55:55;
+.fi
+.PP
+They are equivalent, because half the bits are set to 0, half are set to
+1 (0xa and 0x5 are 1010 and 0101 binary respectively) and consequently this
+would roughly divide the clients equally between the servers. They are not
+identical, because the actual peers this would load balance to each server
+are different for each example.
+.PP
+You must only have \fBsplit\fR or \fBhba\fR defined, never both. For most
+cases, the fine-grained control that \fBhba\fR offers isn't necessary, and
+\fBsplit\fR should be used.
+.RE
+.PP
+The
+.I load balance max seconds
+statement
+.RS 0.25i
+.PP
+.B load balance max seconds \fIseconds\fR\fB;\fR
+.PP
+This statement allows you to configure a cutoff after which load
+balancing is disabled. The cutoff is based on the number of seconds
+since the client sent its first DHCPDISCOVER or DHCPREQUEST message,
+and only works with clients that correctly implement the \fIsecs\fR
+field - fortunately most clients do. We recommend setting this to
+something like 3 or 5. The effect of this is that if one of the
+failover peers gets into a state where it is responding to failover
+messages but not responding to some client requests, the other
+failover peer will take over its client load automatically as the
+clients retry.
+.RE
+.PP
+The
+.I auto-partner-down
+statement
+.RS 0.25i
+.PP
+.B auto-partner-down \fIseconds\fR\fB;\fR
+.PP
+This statement instructs the server to initiate a timed delay upon entering
+the communications-interrupted state (any situation of being out-of-contact
+with the remote failover peer). At the conclusion of the timer, the server
+will automatically enter the partner-down state. This permits the server
+to allocate leases from the partner's free lease pool after an STOS+MCLT
+timer expires, which can be dangerous if the partner is in fact operating
+at the time (the two servers will give conflicting bindings).
+.PP
+Think very carefully before enabling this feature. The partner-down and
+communications-interrupted states are intentionally segregated because
+there do exist situations where a failover server can fail to communicate
+with its peer, but still has the ability to receive and reply to requests
+from DHCP clients. In general, this feature should only be used in those
+deployments where the failover servers are directly connected to one
+another, such as by a dedicated hardwired link ("a heartbeat cable").
+.PP
+A zero value disables the auto-partner-down feature (also the default), and
+any positive value indicates the time in seconds to wait before automatically
+entering partner-down.
+.RE
+.PP
+The Failover pool balance statements.
+.RS 0.25i
+.PP
+ \fBmax-lease-misbalance \fIpercentage\fR\fB;\fR
+ \fBmax-lease-ownership \fIpercentage\fR\fB;\fR
+ \fBmin-balance \fIseconds\fR\fB;\fR
+ \fBmax-balance \fIseconds\fR\fB;\fR
+.PP
+This version of the DHCP Server evaluates pool balance on a schedule,
+rather than on demand as leases are allocated. The latter approach
+proved to be slightly klunky when pool misbalanced reach total
+saturation \(em when any server ran out of leases to assign, it also lost
+its ability to notice it had run dry.
+.PP
+In order to understand pool balance, some elements of its operation
+first need to be defined. First, there are \'free\' and \'backup\' leases.
+Both of these are referred to as \'free state leases\'. \'free\' and
+\'backup\'
+are \'the free states\' for the purpose of this document. The difference
+is that only the primary may allocate from \'free\' leases unless under
+special circumstances, and only the secondary may allocate \'backup\' leases.
+.PP
+When pool balance is performed, the only plausible expectation is to
+provide a 50/50 split of the free state leases between the two servers.
+This is because no one can predict which server will fail, regardless
+of the relative load placed upon the two servers, so giving each server
+half the leases gives both servers the same amount of \'failure endurance\'.
+Therefore, there is no way to configure any different behaviour, outside of
+some very small windows we will describe shortly.
+.PP
+The first thing calculated on any pool balance run is a value referred to
+as \'lts\', or "Leases To Send". This, simply, is the difference in the
+count of free and backup leases, divided by two. For the secondary,
+it is the difference in the backup and free leases, divided by two.
+The resulting value is signed: if it is positive, the local server is
+expected to hand out leases to retain a 50/50 balance. If it is negative,
+the remote server would need to send leases to balance the pool. Once
+the lts value reaches zero, the pool is perfectly balanced (give or take
+one lease in the case of an odd number of total free state leases).
+.PP
+The current approach is still something of a hybrid of the old approach,
+marked by the presence of the \fBmax-lease-misbalance\fR statement. This
+parameter configures what used to be a 10% fixed value in previous versions:
+if lts is less than free+backup * \fBmax-lease-misbalance\fR percent, then
+the server will skip balancing a given pool (it won't bother moving any
+leases, even if some leases "should" be moved). The meaning of this value
+is also somewhat overloaded, however, in that it also governs the estimation
+of when to attempt to balance the pool (which may then also be skipped over).
+The oldest leases in the free and backup states are examined. The time
+they have resided in their respective queues is used as an estimate to
+indicate how much time it is probable it would take before the leases at
+the top of the list would be consumed (and thus, how long it would take
+to use all leases in that state). This percentage is directly multiplied
+by this time, and fit into the schedule if it falls within
+the \fBmin-balance\fR and \fBmax-balance\fR configured values. The
+scheduled pool check time is only moved in a downwards direction, it is
+never increased. Lastly, if the lts is more than double this number in
+the negative direction, the local server will \'panic\' and transmit a
+Failover protocol POOLREQ message, in the hopes that the remote system
+will be woken up into action.
+.PP
+Once the lts value exceeds the \fBmax-lease-misbalance\fR percentage of
+total free state leases as described above, leases are moved to the remote
+server. This is done in two passes.
+.PP
+In the first pass, only leases whose most recent bound client would have
+been served by the remote server - according to the Load Balance Algorithm
+(see above \fBsplit\fR and \fBhba\fR configuration statements) - are given
+away to the peer. This first pass will happily continue to give away leases,
+decrementing the lts value by one for each, until the lts value has reached
+the negative of the total number of leases multiplied by
+the \fBmax-lease-ownership\fR percentage. So it is through this value that
+you can permit a small misbalance of the lease pools - for the purpose of
+giving the peer more than a 50/50 share of leases in the hopes that their
+clients might some day return and be allocated by the peer (operating
+normally). This process is referred to as \'MAC Address Affinity\', but this
+is somewhat misnamed: it applies equally to DHCP Client Identifier options.
+Note also that affinity is applied to leases when they enter the state
+\'free\' from \'expired\' or \'released\'. In this case also, leases will not
+be moved from free to backup if the secondary already has more than its
+share.
+.PP
+The second pass is only entered into if the first pass fails to reduce
+the lts underneath the total number of free state leases multiplied by
+the \fBmax-lease-ownership\fR percentage. In this pass, the oldest
+leases are given over to the peer without second thought about the Load
+Balance Algorithm, and this continues until the lts falls under this
+value. In this way, the local server will also happily keep a small
+percentage of the leases that would normally load balance to itself.
+.PP
+So, the \fBmax-lease-misbalance\fR value acts as a behavioural gate.
+Smaller values will cause more leases to transition states to balance
+the pools over time, higher values will decrease the amount of change
+(but may lead to pool starvation if there's a run on leases).
+.PP
+The \fBmax-lease-ownership\fR value permits a small (percentage) skew
+in the lease balance of a percentage of the total number of free state
+leases.
+.PP
+Finally, the \fBmin-balance\fR and \fBmax-balance\fR make certain that a
+scheduled rebalance event happens within a reasonable timeframe (not
+to be thrown off by, for example, a 7 year old free lease).
+.PP
+Plausible values for the percentages lie between 0 and 100, inclusive, but
+values over 50 are indistinguishable from one another (once lts exceeds
+50% of the free state leases, one server must therefore have 100% of the
+leases in its respective free state). It is recommended to select
+a \fBmax-lease-ownership\fR value that is lower than the value selected
+for the \fBmax-lease-misbalance\fR value. \fBmax-lease-ownership\fR
+defaults to 10, and \fBmax-lease-misbalance\fR defaults to 15.
+.PP
+Plausible values for the \fBmin-balance\fR and \fBmax-balance\fR times also
+range from 0 to (2^32)-1 (or the limit of your local time_t value), but
+default to values 60 and 3600 respectively (to place balance events between
+1 minute and 1 hour).
+.RE
+.SH CLIENT CLASSING
+Clients can be separated into classes, and treated differently
+depending on what class they are in. This separation can be done
+either with a conditional statement, or with a match statement within
+the class declaration. It is possible to specify a limit on the
+total number of clients within a particular class or subclass that may
+hold leases at one time, and it is possible to specify automatic
+subclassing based on the contents of the client packet.
+.PP
+To add clients to classes based on conditional evaluation, you can
+specify a matching expression in the class statement:
+.PP
+.nf
+class "ras-clients" {
+ match if substring (option dhcp-client-identifier, 1, 3) = "RAS";
+}
+.fi
+.PP
+Note that whether you use matching expressions or add statements (or
+both) to classify clients, you must always write a class declaration
+for any class that you use. If there will be no match statement and
+no in-scope statements for a class, the declaration should look like
+this:
+.PP
+.nf
+class "ras-clients" {
+}
+.fi
+.SH SUBCLASSES
+.PP
+In addition to classes, it is possible to declare subclasses. A
+subclass is a class with the same name as a regular class, but with a
+specific submatch expression which is hashed for quick matching.
+This is essentially a speed hack - the main difference between five
+classes with match expressions and one class with five subclasses is
+that it will be quicker to find the subclasses. Subclasses work as
+follows:
+.PP
+.nf
+class "allocation-class-1" {
+ match pick-first-value (option dhcp-client-identifier, hardware);
+}
+
+class "allocation-class-2" {
+ match pick-first-value (option dhcp-client-identifier, hardware);
+}
+
+subclass "allocation-class-1" 1:8:0:2b:4c:39:ad;
+subclass "allocation-class-2" 1:8:0:2b:a9:cc:e3;
+subclass "allocation-class-1" 1:0:0:c4:aa:29:44;
+
+subnet 10.0.0.0 netmask 255.255.255.0 {
+ pool {
+ allow members of "allocation-class-1";
+ range 10.0.0.11 10.0.0.50;
+ }
+ pool {
+ allow members of "allocation-class-2";
+ range 10.0.0.51 10.0.0.100;
+ }
+}
+.fi
+.PP
+The data following the class name in the subclass declaration is a
+constant value to use in matching the match expression for the class.
+When class matching is done, the server will evaluate the match
+expression and then look the result up in the hash table. If it
+finds a match, the client is considered a member of both the class and
+the subclass.
+.PP
+Subclasses can be declared with or without scope. In the above
+example, the sole purpose of the subclass is to allow some clients
+access to one address pool, while other clients are given access to
+the other pool, so these subclasses are declared without scopes. If
+part of the purpose of the subclass were to define different parameter
+values for some clients, you might want to declare some subclasses
+with scopes.
+.PP
+In the above example, if you had a single client that needed some
+configuration parameters, while most didn't, you might write the
+following subclass declaration for that client:
+.PP
+.nf
+subclass "allocation-class-2" 1:08:00:2b:a1:11:31 {
+ option root-path "samsara:/var/diskless/alphapc";
+ filename "/tftpboot/netbsd.alphapc-diskless";
+}
+.fi
+.PP
+In this example, we've used subclassing as a way to control address
+allocation on a per-client basis. However, it's also possible to use
+subclassing in ways that are not specific to clients - for example, to
+use the value of the vendor-class-identifier option to determine what
+values to send in the vendor-encapsulated-options option. An example
+of this is shown under the VENDOR ENCAPSULATED OPTIONS head in the
+.B dhcp-options(5)
+manual page.
+.SH PER-CLASS LIMITS ON DYNAMIC ADDRESS ALLOCATION
+.PP
+You may specify a limit to the number of clients in a class that can
+be assigned leases. The effect of this will be to make it difficult
+for a new client in a class to get an address. Once a class with
+such a limit has reached its limit, the only way a new client in that
+class can get a lease is for an existing client to relinquish its
+lease, either by letting it expire, or by sending a DHCPRELEASE
+packet. Classes with lease limits are specified as follows:
+.PP
+.nf
+class "limited-1" {
+ lease limit 4;
+}
+.fi
+.PP
+This will produce a class in which a maximum of four members may hold
+a lease at one time.
+.SH SPAWNING CLASSES
+.PP
+It is possible to declare a
+.I spawning class\fR.
+A spawning class is a class that automatically produces subclasses
+based on what the client sends. The reason that spawning classes
+were created was to make it possible to create lease-limited classes
+on the fly. The envisioned application is a cable-modem environment
+where the ISP wishes to provide clients at a particular site with more
+than one IP address, but does not wish to provide such clients with
+their own subnet, nor give them an unlimited number of IP addresses
+from the network segment to which they are connected.
+.PP
+Many cable modem head-end systems can be configured to add a Relay
+Agent Information option to DHCP packets when relaying them to the
+DHCP server. These systems typically add a circuit ID or remote ID
+option that uniquely identifies the customer site. To take advantage
+of this, you can write a class declaration as follows:
+.PP
+.nf
+class "customer" {
+ spawn with option agent.circuit-id;
+ lease limit 4;
+}
+.fi
+.PP
+Now whenever a request comes in from a customer site, the circuit ID
+option will be checked against the class's hash table. If a subclass
+is found that matches the circuit ID, the client will be classified in
+that subclass and treated accordingly. If no subclass is found
+matching the circuit ID, a new one will be created and logged in the
+.B dhcpd.leases
+file, and the client will be classified in this new class. Once the
+client has been classified, it will be treated according to the rules
+of the class, including, in this case, being subject to the per-site
+limit of four leases.
+.PP
+The use of the subclass spawning mechanism is not restricted to relay
+agent options - this particular example is given only because it is a
+fairly straightforward one.
+.SH COMBINING MATCH, MATCH IF AND SPAWN WITH
+.PP
+In some cases, it may be useful to use one expression to assign a
+client to a particular class, and a second expression to put it into a
+subclass of that class. This can be done by combining the \fBmatch
+if\fR and \fBspawn with\fR statements, or the \fBmatch if\fR and
+\fBmatch\fR statements. For example:
+.PP
+.nf
+class "jr-cable-modems" {
+ match if option dhcp-vendor-identifier = "jrcm";
+ spawn with option agent.circuit-id;
+ lease limit 4;
+}
+
+class "dv-dsl-modems" {
+ match if option dhcp-vendor-identifier = "dvdsl";
+ spawn with option agent.circuit-id;
+ lease limit 16;
+}
+.fi
+.PP
+This allows you to have two classes that both have the same \fBspawn
+with\fR expression without getting the clients in the two classes
+confused with each other.
+.SH DYNAMIC DNS UPDATES
+.PP
+The DHCP server has the ability to dynamically update the Domain Name
+System. Within the configuration files, you can define how you want
+the Domain Name System to be updated. These updates are RFC 2136
+compliant so any DNS server supporting RFC 2136 should be able to
+accept updates from the DHCP server.
+.PP
+Two DNS update schemes are currently implemented, and another is
+planned. The two that are currently implemented are the ad-hoc DNS
+update mode and the interim DHCP-DNS interaction draft update mode.
+In the future we plan to add a third mode which will be the standard
+DNS update method based on the RFCS for DHCP-DNS interaction and DHCID
+The DHCP server must be configured to use one of the two
+currently-supported methods, or not to do dns updates.
+This can be done with the
+.I ddns-update-style
+configuration parameter.
+.SH THE AD-HOC DNS UPDATE SCHEME
+The ad-hoc Dynamic DNS update scheme is
+.B now deprecated
+and
+.B
+does not work.
+In future releases of the ISC DHCP server, this scheme will not likely be
+available. The interim scheme works, allows for failover, and should now be
+used. The following description is left here for informational purposes
+only.
+.PP
+The ad-hoc Dynamic DNS update scheme implemented in this version of
+the ISC DHCP server is a prototype design, which does not
+have much to do with the standard update method that is being
+standardized in the IETF DHC working group, but rather implements some
+very basic, yet useful, update capabilities. This mode
+.B does not work
+with the
+.I failover protocol
+because it does not account for the possibility of two different DHCP
+servers updating the same set of DNS records.
+.PP
+For the ad-hoc DNS update method, the client's FQDN is derived in two
+parts. First, the hostname is determined. Then, the domain name is
+determined, and appended to the hostname.
+.PP
+The DHCP server determines the client's hostname by first looking for
+a \fIddns-hostname\fR configuration option, and using that if it is
+present. If no such option is present, the server looks for a
+valid hostname in the FQDN option sent by the client. If one is
+found, it is used; otherwise, if the client sent a host-name option,
+that is used. Otherwise, if there is a host declaration that applies
+to the client, the name from that declaration will be used. If none
+of these applies, the server will not have a hostname for the client,
+and will not be able to do a DNS update.
+.PP
+The domain name is determined from the
+.I ddns-domainname
+configuration option. The default configuration for this option is:
+.nf
+.sp 1
+ option server.ddns-domainname = config-option domain-name;
+
+.fi
+So if this configuration option is not configured to a different
+value (over-riding the above default), or if a domain-name option
+has not been configured for the client's scope, then the server will
+not attempt to perform a DNS update.
+.PP
+The client's fully-qualified domain name, derived as we have
+described, is used as the name on which an "A" record will be stored.
+The A record will contain the IP address that the client was assigned
+in its lease. If there is already an A record with the same name in
+the DNS server, no update of either the A or PTR records will occur -
+this prevents a client from claiming that its hostname is the name of
+some network server. For example, if you have a fileserver called
+"fs.sneedville.edu", and the client claims its hostname is "fs", no
+DNS update will be done for that client, and an error message will be
+logged.
+.PP
+If the A record update succeeds, a PTR record update for the assigned
+IP address will be done, pointing to the A record. This update is
+unconditional - it will be done even if another PTR record of the same
+name exists. Since the IP address has been assigned to the DHCP
+server, this should be safe.
+.PP
+Please note that the current implementation assumes clients only have
+a single network interface. A client with two network interfaces
+will see unpredictable behavior. This is considered a bug, and will
+be fixed in a later release. It may be helpful to enable the
+.I one-lease-per-client
+parameter so that roaming clients do not trigger this same behavior.
+.PP
+The DHCP protocol normally involves a four-packet exchange - first the
+client sends a DHCPDISCOVER message, then the server sends a
+DHCPOFFER, then the client sends a DHCPREQUEST, then the server sends
+a DHCPACK. In the current version of the server, the server will do
+a DNS update after it has received the DHCPREQUEST, and before it has
+sent the DHCPACK. It only sends the DNS update if it has not sent
+one for the client's address before, in order to minimize the impact
+on the DHCP server.
+.PP
+When the client's lease expires, the DHCP server (if it is operating
+at the time, or when next it operates) will remove the client's A and
+PTR records from the DNS database. If the client releases its lease
+by sending a DHCPRELEASE message, the server will likewise remove the
+A and PTR records.
+.SH THE INTERIM DNS UPDATE SCHEME
+The interim DNS update scheme operates mostly according to several
+drafts considered by the IETF. While the drafts have since become
+RFCs the code was written before they were finalized and there are
+some differences between our code and the final RFCs. We plan to
+update our code, probably adding a standard DNS update option, at
+some time. The basic framework is similar with the main material
+difference being that a DHCID RR was assigned in the RFCs whereas
+our code continues to use an experimental TXT record. The format
+of the TXT record bears a resemblance to the DHCID RR but it is not
+equivalent (MD5 vs SHA1, field length differences etc).
+The standard RFCs are:
+.PP
+.nf
+.ce 3
+RFC 4701 (updated by RF5494)
+RFC 4702
+RFC 4703
+.fi
+.PP
+And the corresponding drafts were:
+.PP
+.nf
+.ce 3
+draft-ietf-dnsext-dhcid-rr-??.txt
+draft-ietf-dhc-fqdn-option-??.txt
+draft-ietf-dhc-ddns-resolution-??.txt
+.fi
+.PP
+Because our implementation is slightly different than the standard, we
+will briefly document the operation of this update style here.
+.PP
+The first point to understand about this style of DNS update is that
+unlike the ad-hoc style, the DHCP server does not necessarily
+always update both the A and the PTR records. The FQDN option
+includes a flag which, when sent by the client, indicates that the
+client wishes to update its own A record. In that case, the server
+can be configured either to honor the client's intentions or ignore
+them. This is done with the statement \fIallow client-updates;\fR or
+the statement \fIignore client-updates;\fR. By default, client
+updates are allowed.
+.PP
+If the server is configured to allow client updates, then if the
+client sends a fully-qualified domain name in the FQDN option, the
+server will use that name the client sent in the FQDN option to update
+the PTR record. For example, let us say that the client is a visitor
+from the "radish.org" domain, whose hostname is "jschmoe". The
+server is for the "example.org" domain. The DHCP client indicates in
+the FQDN option that its FQDN is "jschmoe.radish.org.". It also
+indicates that it wants to update its own A record. The DHCP server
+therefore does not attempt to set up an A record for the client, but
+does set up a PTR record for the IP address that it assigns the
+client, pointing at jschmoe.radish.org. Once the DHCP client has an
+IP address, it can update its own A record, assuming that the
+"radish.org" DNS server will allow it to do so.
+.PP
+If the server is configured not to allow client updates, or if the
+client doesn\'t want to do its own update, the server will simply
+choose a name for the client. By default, the server will choose
+from the following three values:
+.PP
+ 1. \fBfqdn\fR option (if present)
+ 2. hostname option (if present)
+ 3. Configured hostname option (if defined).
+.PP
+If these defaults for choosing the host name are not appropriate
+you can write your own statement to set the ddns-hostname variable
+as you wish. If none of the above are found the server will use
+the host declaration name (if one) and use-host-decl-names is on.
+.PP
+It will use its own domain name for the client. It will then update
+both the A and PTR record, using the name that it chose for the client.
+If the client sends a fully-qualified domain name in the \fBfqdn\fR option,
+the server uses only the leftmost part of the domain name - in the example
+above, "jschmoe" instead of "jschmoe.radish.org".
+.PP
+Further, if the \fIignore client-updates;\fR directive is used, then
+the server will in addition send a response in the DHCP packet, using
+the FQDN Option, that implies to the client that it should perform its
+own updates if it chooses to do so. With \fIdeny client-updates;\fR, a
+response is sent which indicates the client may not perform updates.
+.PP
+The other difference between the ad-hoc scheme and the interim
+scheme is that with the interim scheme, a method is used that
+allows more than one DHCP server to update the DNS database without
+accidentally deleting A records that shouldn't be deleted nor failing
+to add A records that should be added. The scheme works as follows:
+.PP
+When the DHCP server issues a client a new lease, it creates a text
+string that is an MD5 hash over the DHCP client's identification (see
+draft-ietf-dnsext-dhcid-rr-??.txt for details). The update adds an A
+record with the name the server chose and a TXT record containing the
+hashed identifier string (hashid). If this update succeeds, the
+server is done.
+.PP
+If the update fails because the A record already exists, then the DHCP
+server attempts to add the A record with the prerequisite that there
+must be a TXT record in the same name as the new A record, and that
+TXT record's contents must be equal to hashid. If this update
+succeeds, then the client has its A record and PTR record. If it
+fails, then the name the client has been assigned (or requested) is in
+use, and can't be used by the client. At this point the DHCP server
+gives up trying to do a DNS update for the client until the client
+chooses a new name.
+.PP
+The interim DNS update scheme is called interim for two reasons.
+First, it does not quite follow the RFCs. The RFCs call for a
+new DHCID RRtype while he interim DNS update scheme uses a TXT record.
+The ddns-resolution draft called for the DHCP server to put a DHCID RR
+on the PTR record, but the \fIinterim\fR update method does not do this.
+In the final RFC this requirement was relaxed such that a server may
+add a DHCID RR to the PTR record.
+.PP
+In addition to these differences, the server also does not update very
+aggressively. Because each DNS update involves a round trip to the
+DNS server, there is a cost associated with doing updates even if they
+do not actually modify the DNS database. So the DHCP server tracks
+whether or not it has updated the record in the past (this information
+is stored on the lease) and does not attempt to update records that it
+thinks it has already updated.
+.PP
+This can lead to cases where the DHCP server adds a record, and then
+the record is deleted through some other mechanism, but the server
+never again updates the DNS because it thinks the data is already
+there. In this case the data can be removed from the lease through
+operator intervention, and once this has been done, the DNS will be
+updated the next time the client renews.
+.SH DYNAMIC DNS UPDATE SECURITY
+.PP
+When you set your DNS server up to allow updates from the DHCP server,
+you may be exposing it to unauthorized updates. To avoid this, you
+should use TSIG signatures - a method of cryptographically signing
+updates using a shared secret key. As long as you protect the
+secrecy of this key, your updates should also be secure. Note,
+however, that the DHCP protocol itself provides no security, and that
+clients can therefore provide information to the DHCP server which the
+DHCP server will then use in its updates, with the constraints
+described previously.
+.PP
+The DNS server must be configured to allow updates for any zone that
+the DHCP server will be updating. For example, let us say that
+clients in the sneedville.edu domain will be assigned addresses on the
+10.10.17.0/24 subnet. In that case, you will need a key declaration
+for the TSIG key you will be using, and also two zone declarations -
+one for the zone containing A records that will be updates and one for
+the zone containing PTR records - for ISC BIND, something like this:
+.PP
+.nf
+key DHCP_UPDATER {
+ algorithm HMAC-MD5.SIG-ALG.REG.INT;
+ secret pRP5FapFoJ95JEL06sv4PQ==;
+};
+
+zone "example.org" {
+ type master;
+ file "example.org.db";
+ allow-update { key DHCP_UPDATER; };
+};
+
+zone "17.10.10.in-addr.arpa" {
+ type master;
+ file "10.10.17.db";
+ allow-update { key DHCP_UPDATER; };
+};
+.fi
+.PP
+You will also have to configure your DHCP server to do updates to
+these zones. To do so, you need to add something like this to your
+dhcpd.conf file:
+.PP
+.nf
+key DHCP_UPDATER {
+ algorithm HMAC-MD5.SIG-ALG.REG.INT;
+ secret pRP5FapFoJ95JEL06sv4PQ==;
+};
+
+zone EXAMPLE.ORG. {
+ primary 127.0.0.1;
+ key DHCP_UPDATER;
+}
+
+zone 17.127.10.in-addr.arpa. {
+ primary 127.0.0.1;
+ key DHCP_UPDATER;
+}
+.fi
+.PP
+The \fIprimary\fR statement specifies the IP address of the name
+server whose zone information is to be updated. In addition to
+the \fIprimary\fR statement there are also the \fIprimary6\fR ,
+\fIsecondary\fR and \fIsecondary6\fR statements. The \fIprimary6\fR
+statement specifies an IPv6 address for the name server. The
+secondaries provide for additional addresses for name servers
+to be used if the primary does not respond. The number of name
+servers the DDNS code will attempt to use before giving up
+is limited and is currently set to three.
+.PP
+Note that the zone declarations have to correspond to authority
+records in your name server - in the above example, there must be an
+SOA record for "example.org." and for "17.10.10.in-addr.arpa.". For
+example, if there were a subdomain "foo.example.org" with no separate
+SOA, you could not write a zone declaration for "foo.example.org."
+Also keep in mind that zone names in your DHCP configuration should end in a
+"."; this is the preferred syntax. If you do not end your zone name in a
+".", the DHCP server will figure it out. Also note that in the DHCP
+configuration, zone names are not encapsulated in quotes where there are in
+the DNS configuration.
+.PP
+You should choose your own secret key, of course. The ISC BIND 8 and
+9 distributions come with a program for generating secret keys called
+dnssec-keygen. The version that comes with BIND 9 is likely to produce a
+substantially more random key, so we recommend you use that one even
+if you are not using BIND 9 as your DNS server. If you are using BIND 9's
+dnssec-keygen, the above key would be created as follows:
+.PP
+.nf
+ dnssec-keygen -a HMAC-MD5 -b 128 -n USER DHCP_UPDATER
+.fi
+.PP
+If you are using the BIND 8 dnskeygen program, the following command will
+generate a key as seen above:
+.PP
+.nf
+ dnskeygen -H 128 -u -c -n DHCP_UPDATER
+.fi
+.PP
+The key name, algorithm, and secret must match that being used by the DNS
+server. The DHCP server currently supports the following algorithms:
+.nf
+
+ HMAC-MD5
+ HMAC-SHA1
+ HMAC-SHA224
+ HMAC-SHA256
+ HMAC-SHA384
+ HMAC-SHA512
+.fi
+.PP
+You may wish to enable logging of DNS updates on your DNS server.
+To do so, you might write a logging statement like the following:
+.PP
+.nf
+logging {
+ channel update_debug {
+ file "/var/log/update-debug.log";
+ severity debug 3;
+ print-category yes;
+ print-severity yes;
+ print-time yes;
+ };
+ channel security_info {
+ file "/var/log/named-auth.info";
+ severity info;
+ print-category yes;
+ print-severity yes;
+ print-time yes;
+ };
+
+ category update { update_debug; };
+ category security { security_info; };
+};
+.fi
+.PP
+You must create the /var/log/named-auth.info and
+/var/log/update-debug.log files before starting the name server. For
+more information on configuring ISC BIND, consult the documentation
+that accompanies it.
+.SH REFERENCE: EVENTS
+.PP
+There are three kinds of events that can happen regarding a lease, and
+it is possible to declare statements that occur when any of these
+events happen. These events are the commit event, when the server
+has made a commitment of a certain lease to a client, the release
+event, when the client has released the server from its commitment,
+and the expiry event, when the commitment expires.
+.PP
+To declare a set of statements to execute when an event happens, you
+must use the \fBon\fR statement, followed by the name of the event,
+followed by a series of statements to execute when the event happens,
+enclosed in braces. Events are used to implement DNS
+updates, so you should not define your own event handlers if you are
+using the built-in DNS update mechanism.
+.PP
+The built-in version of the DNS update mechanism is in a text
+string towards the top of server/dhcpd.c. If you want to use events
+for things other than DNS updates, and you also want DNS updates, you
+will have to start out by copying this code into your dhcpd.conf file
+and modifying it.
+.SH REFERENCE: DECLARATIONS
+.PP
+.B The
+.I include
+.B statement
+.PP
+.nf
+ \fBinclude\fR \fI"filename"\fR\fB;\fR
+.fi
+.PP
+The \fIinclude\fR statement is used to read in a named file, and process
+the contents of that file as though it were entered in place of the
+include statement.
+.PP
+.B The
+.I shared-network
+.B statement
+.PP
+.nf
+ \fBshared-network\fR \fIname\fR \fB{\fR
+ [ \fIparameters\fR ]
+ [ \fIdeclarations\fR ]
+ \fB}\fR
+.fi
+.PP
+The \fIshared-network\fR statement is used to inform the DHCP server
+that some IP subnets actually share the same physical network. Any
+subnets in a shared network should be declared within a
+\fIshared-network\fR statement. Parameters specified in the
+\fIshared-network\fR statement will be used when booting clients on
+those subnets unless parameters provided at the subnet or host level
+override them. If any subnet in a shared network has addresses
+available for dynamic allocation, those addresses are collected into a
+common pool for that shared network and assigned to clients as needed.
+There is no way to distinguish on which subnet of a shared network a
+client should boot.
+.PP
+.I Name
+should be the name of the shared network. This name is used when
+printing debugging messages, so it should be descriptive for the
+shared network. The name may have the syntax of a valid domain name
+(although it will never be used as such), or it may be any arbitrary
+name, enclosed in quotes.
+.PP
+.B The
+.I subnet
+.B statement
+.PP
+.nf
+ \fBsubnet\fR \fIsubnet-number\fR \fBnetmask\fR \fInetmask\fR \fB{\fR
+ [ \fIparameters\fR ]
+ [ \fIdeclarations\fR ]
+ \fB}\fR
+.fi
+.PP
+The \fIsubnet\fR statement is used to provide dhcpd with enough
+information to tell whether or not an IP address is on that subnet.
+It may also be used to provide subnet-specific parameters and to
+specify what addresses may be dynamically allocated to clients booting
+on that subnet. Such addresses are specified using the \fIrange\fR
+declaration.
+.PP
+The
+.I subnet-number
+should be an IP address or domain name which resolves to the subnet
+number of the subnet being described. The
+.I netmask
+should be an IP address or domain name which resolves to the subnet mask
+of the subnet being described. The subnet number, together with the
+netmask, are sufficient to determine whether any given IP address is
+on the specified subnet.
+.PP
+Although a netmask must be given with every subnet declaration, it is
+recommended that if there is any variance in subnet masks at a site, a
+subnet-mask option statement be used in each subnet declaration to set
+the desired subnet mask, since any subnet-mask option statement will
+override the subnet mask declared in the subnet statement.
+.PP
+.B The
+.I subnet6
+.B statement
+.PP
+.nf
+ \fBsubnet6\fR \fIsubnet6-number\fR \fB{\fR
+ [ \fIparameters\fR ]
+ [ \fIdeclarations\fR ]
+ \fB}\fR
+.fi
+.PP
+The \fIsubnet6\fR statement is used to provide dhcpd with enough
+information to tell whether or not an IPv6 address is on that subnet6.
+It may also be used to provide subnet-specific parameters and to
+specify what addresses may be dynamically allocated to clients booting
+on that subnet.
+.PP
+The
+.I subnet6-number
+should be an IPv6 network identifier, specified as ip6-address/bits.
+.PP
+.B The
+.I range
+.B statement
+.PP
+.nf
+.B range\fR [ \fBdynamic-bootp\fR ] \fIlow-address\fR [ \fIhigh-address\fR]\fB;\fR
+.fi
+.PP
+For any subnet on which addresses will be assigned dynamically, there
+must be at least one \fIrange\fR statement. The range statement
+gives the lowest and highest IP addresses in a range. All IP
+addresses in the range should be in the subnet in which the
+\fIrange\fR statement is declared. The \fIdynamic-bootp\fR flag may
+be specified if addresses in the specified range may be dynamically
+assigned to BOOTP clients as well as DHCP clients. When specifying a
+single address, \fIhigh-address\fR can be omitted.
+.PP
+.B The
+.I range6
+.B statement
+.PP
+.nf
+.B range6\fR \fIlow-address\fR \fIhigh-address\fR\fB;\fR
+.B range6\fR \fIsubnet6-number\fR\fB;\fR
+.B range6\fR \fIsubnet6-number\fR \fBtemporary\fR\fB;\fR
+.B range6\fR \fIaddress\fR \fBtemporary\fR\fB;\fR
+.fi
+.PP
+For any IPv6 subnet6 on which addresses will be assigned dynamically, there
+must be at least one \fIrange6\fR statement. The \fIrange6\fR statement
+can either be the lowest and highest IPv6 addresses in a \fIrange6\fR, or
+use CIDR notation, specified as ip6-address/bits. All IP addresses
+in the \fIrange6\fR should be in the subnet6 in which the
+\fIrange6\fR statement is declared.
+.PP
+The \fItemporary\fR variant makes the prefix (by default on 64 bits) available
+for temporary (RFC 4941) addresses. A new address per prefix in the shared
+network is computed at each request with an IA_TA option. Release and Confirm
+ignores temporary addresses.
+.PP
+Any IPv6 addresses given to hosts with \fIfixed-address6\fR are excluded
+from the \fIrange6\fR, as are IPv6 addresses on the server itself.
+.PP
+.PP
+.B The
+.I prefix6
+.B statement
+.PP
+.nf
+.B prefix6\fR \fIlow-address\fR \fIhigh-address\fR \fB/\fR \fIbits\fR\fB;\fR
+.fi
+.PP
+The \fIprefix6\fR is the \fIrange6\fR equivalent for Prefix Delegation
+(RFC 3633). Prefixes of \fIbits\fR length are assigned between
+\fIlow-address\fR and \fIhigh-address\fR.
+.PP
+Any IPv6 prefixes given to static entries (hosts) with \fIfixed-prefix6\fR
+are excluded from the \fIprefix6\fR.
+.PP
+This statement is currently global but it should have a shared-network scope.
+.PP
+.B The
+.I host
+.B statement
+.PP
+.nf
+ \fBhost\fR \fIhostname\fR {
+ [ \fIparameters\fR ]
+ [ \fIdeclarations\fR ]
+ \fB}\fR
+.fi
+.PP
+The
+.B host
+declaration provides a scope in which to provide configuration information about
+a specific client, and also provides a way to assign a client a fixed address.
+The host declaration provides a way for the DHCP server to identify a DHCP or
+BOOTP client, and also a way to assign the client a static IP address.
+.PP
+If it is desirable to be able to boot a DHCP or BOOTP client on more than one
+subnet with fixed addresses, more than one address may be specified in the
+.I fixed-address
+declaration, or more than one
+.B host
+statement may be specified matching the same client.
+.PP
+If client-specific boot parameters must change based on the network
+to which the client is attached, then multiple
+.B host
+declarations should be used. The
+.B host
+declarations will only match a client if one of their
+.I fixed-address
+statements is viable on the subnet (or shared network) where the client is
+attached. Conversely, for a
+.B host
+declaration to match a client being allocated a dynamic address, it must not
+have any
+.I fixed-address
+statements. You may therefore need a mixture of
+.B host
+declarations for any given client...some having
+.I fixed-address
+statements, others without.
+.PP
+.I hostname
+should be a name identifying the host. If a \fIhostname\fR option is
+not specified for the host, \fIhostname\fR is used.
+.PP
+\fIHost\fR declarations are matched to actual DHCP or BOOTP clients
+by matching the \fRdhcp-client-identifier\fR option specified in the
+\fIhost\fR declaration to the one supplied by the client, or, if the
+\fIhost\fR declaration or the client does not provide a
+\fRdhcp-client-identifier\fR option, by matching the \fIhardware\fR
+parameter in the \fIhost\fR declaration to the network hardware
+address supplied by the client. BOOTP clients do not normally
+provide a \fIdhcp-client-identifier\fR, so the hardware address must
+be used for all clients that may boot using the BOOTP protocol.
+.PP
+DHCPv6 servers can use the \fIhost-identifier option\fR parameter in
+the \fIhost\fR declaration, and specify any option with a fixed value
+to identify hosts.
+.PP
+Please be aware that
+.B only
+the \fIdhcp-client-identifier\fR option and the hardware address can be
+used to match a host declaration, or the \fIhost-identifier option\fR
+parameter for DHCPv6 servers. For example, it is not possible to
+match a host declaration to a \fIhost-name\fR option. This is
+because the host-name option cannot be guaranteed to be unique for any
+given client, whereas both the hardware address and
+\fIdhcp-client-identifier\fR option are at least theoretically
+guaranteed to be unique to a given client.
+.PP
+.B The
+.I group
+.B statement
+.PP
+.nf
+ \fBgroup\fR {
+ [ \fIparameters\fR ]
+ [ \fIdeclarations\fR ]
+ \fB}\fR
+.fi
+.PP
+The group statement is used simply to apply one or more parameters to
+a group of declarations. It can be used to group hosts, shared
+networks, subnets, or even other groups.
+.SH REFERENCE: ALLOW AND DENY
+The
+.I allow
+and
+.I deny
+statements can be used to control the response of the DHCP server to
+various sorts of requests. The allow and deny keywords actually have
+different meanings depending on the context. In a pool context, these
+keywords can be used to set up access lists for address allocation
+pools. In other contexts, the keywords simply control general server
+behavior with respect to clients based on scope. In a non-pool
+context, the
+.I ignore
+keyword can be used in place of the
+.I deny
+keyword to prevent logging of denied requests.
+.PP
+.SH ALLOW DENY AND IGNORE IN SCOPE
+The following usages of allow and deny will work in any scope,
+although it is not recommended that they be used in pool
+declarations.
+.PP
+.B The
+.I unknown-clients
+.B keyword
+.PP
+ \fBallow unknown-clients;\fR
+ \fBdeny unknown-clients;\fR
+ \fBignore unknown-clients;\fR
+.PP
+The \fBunknown-clients\fR flag is used to tell dhcpd whether
+or not to dynamically assign addresses to unknown clients. Dynamic
+address assignment to unknown clients is \fBallow\fRed by default.
+An unknown client is simply a client that has no host declaration.
+.PP
+The use of this option is now \fIdeprecated\fR. If you are trying to
+restrict access on your network to known clients, you should use \fBdeny
+unknown-clients;\fR inside of your address pool, as described under the
+heading ALLOW AND DENY WITHIN POOL DECLARATIONS.
+.PP
+.B The
+.I bootp
+.B keyword
+.PP
+ \fBallow bootp;\fR
+ \fBdeny bootp;\fR
+ \fBignore bootp;\fR
+.PP
+The \fBbootp\fR flag is used to tell dhcpd whether
+or not to respond to bootp queries. Bootp queries are \fBallow\fRed
+by default.
+.PP
+.B The
+.I booting
+.B keyword
+.PP
+ \fBallow booting;\fR
+ \fBdeny booting;\fR
+ \fBignore booting;\fR
+.PP
+The \fBbooting\fR flag is used to tell dhcpd whether or not to respond
+to queries from a particular client. This keyword only has meaning
+when it appears in a host declaration. By default, booting is
+\fBallow\fRed, but if it is disabled for a particular client, then
+that client will not be able to get an address from the DHCP server.
+.PP
+.B The
+.I duplicates
+.B keyword
+.PP
+ \fBallow duplicates;\fR
+ \fBdeny duplicates;\fR
+.PP
+Host declarations can match client messages based on the DHCP Client
+Identifier option or based on the client's network hardware type and
+MAC address. If the MAC address is used, the host declaration will
+match any client with that MAC address - even clients with different
+client identifiers. This doesn't normally happen, but is possible
+when one computer has more than one operating system installed on it -
+for example, Microsoft Windows and NetBSD or Linux.
+.PP
+The \fBduplicates\fR flag tells the DHCP server that if a request is
+received from a client that matches the MAC address of a host
+declaration, any other leases matching that MAC address should be
+discarded by the server, even if the UID is not the same. This is a
+violation of the DHCP protocol, but can prevent clients whose client
+identifiers change regularly from holding many leases at the same time.
+By default, duplicates are \fBallow\fRed.
+.PP
+.B The
+.I declines
+.B keyword
+.PP
+ \fBallow declines;\fR
+ \fBdeny declines;\fR
+ \fBignore declines;\fR
+.PP
+The DHCPDECLINE message is used by DHCP clients to indicate that the
+lease the server has offered is not valid. When the server receives
+a DHCPDECLINE for a particular address, it normally abandons that
+address, assuming that some unauthorized system is using it.
+Unfortunately, a malicious or buggy client can, using DHCPDECLINE
+messages, completely exhaust the DHCP server's allocation pool. The
+server will reclaim these leases, but while the client is running
+through the pool, it may cause serious thrashing in the DNS, and it
+will also cause the DHCP server to forget old DHCP client address
+allocations.
+.PP
+The \fBdeclines\fR flag tells the DHCP server whether or not to honor
+DHCPDECLINE messages. If it is set to \fBdeny\fR or \fBignore\fR in
+a particular scope, the DHCP server will not respond to DHCPDECLINE
+messages.
+.PP
+.B The
+.I client-updates
+.B keyword
+.PP
+ \fBallow client-updates;\fR
+ \fBdeny client-updates;\fR
+.PP
+The \fBclient-updates\fR flag tells the DHCP server whether or not to
+honor the client's intention to do its own update of its A record.
+This is only relevant when doing \fIinterim\fR DNS updates. See the
+documentation under the heading THE INTERIM DNS UPDATE SCHEME for
+details.
+.PP
+.B The
+.I leasequery
+.B keyword
+.PP
+ \fBallow leasequery;\fR
+ \fBdeny leasequery;\fR
+.PP
+The \fBleasequery\fR flag tells the DHCP server whether or not to
+answer DHCPLEASEQUERY packets. The answer to a DHCPLEASEQUERY packet
+includes information about a specific lease, such as when it was
+issued and when it will expire. By default, the server will not
+respond to these packets.
+.SH ALLOW AND DENY WITHIN POOL DECLARATIONS
+.PP
+The uses of the allow and deny keywords shown in the previous section
+work pretty much the same way whether the client is sending a
+DHCPDISCOVER or a DHCPREQUEST message - an address will be allocated
+to the client (either the old address it's requesting, or a new
+address) and then that address will be tested to see if it's okay to
+let the client have it. If the client requested it, and it's not
+okay, the server will send a DHCPNAK message. Otherwise, the server
+will simply not respond to the client. If it is okay to give the
+address to the client, the server will send a DHCPACK message.
+.PP
+The primary motivation behind pool declarations is to have address
+allocation pools whose allocation policies are different. A client
+may be denied access to one pool, but allowed access to another pool
+on the same network segment. In order for this to work, access
+control has to be done during address allocation, not after address
+allocation is done.
+.PP
+When a DHCPREQUEST message is processed, address allocation simply
+consists of looking up the address the client is requesting and seeing
+if it's still available for the client. If it is, then the DHCP
+server checks both the address pool permit lists and the relevant
+in-scope allow and deny statements to see if it's okay to give the
+lease to the client. In the case of a DHCPDISCOVER message, the
+allocation process is done as described previously in the ADDRESS
+ALLOCATION section.
+.PP
+When declaring permit lists for address allocation pools, the
+following syntaxes are recognized following the allow or deny keywords:
+.PP
+ \fBknown-clients;\fR
+.PP
+If specified, this statement either allows or prevents allocation from
+this pool to any client that has a host declaration (i.e., is known).
+A client is known if it has a host declaration in \fIany\fR scope, not
+just the current scope.
+.PP
+ \fBunknown-clients;\fR
+.PP
+If specified, this statement either allows or prevents allocation from
+this pool to any client that has no host declaration (i.e., is not
+known).
+.PP
+ \fBmembers of "\fRclass\fB";\fR
+.PP
+If specified, this statement either allows or prevents allocation from
+this pool to any client that is a member of the named class.
+.PP
+ \fBdynamic bootp clients;\fR
+.PP
+If specified, this statement either allows or prevents allocation from
+this pool to any bootp client.
+.PP
+ \fBauthenticated clients;\fR
+.PP
+If specified, this statement either allows or prevents allocation from
+this pool to any client that has been authenticated using the DHCP
+authentication protocol. This is not yet supported.
+.PP
+ \fBunauthenticated clients;\fR
+.PP
+If specified, this statement either allows or prevents allocation from
+this pool to any client that has not been authenticated using the DHCP
+authentication protocol. This is not yet supported.
+.PP
+ \fBall clients;\fR
+.PP
+If specified, this statement either allows or prevents allocation from
+this pool to all clients. This can be used when you want to write a
+pool declaration for some reason, but hold it in reserve, or when you
+want to renumber your network quickly, and thus want the server to
+force all clients that have been allocated addresses from this pool to
+obtain new addresses immediately when they next renew.
+.PP
+ \fBafter \fItime\fR\fB;\fR
+.PP
+If specified, this statement either allows or prevents allocation from
+this pool after a given date. This can be used when you want to move
+clients from one pool to another. The server adjusts the regular lease
+time so that the latest expiry time is at the given time+min-lease-time.
+A short min-lease-time enforces a step change, whereas a longer
+min-lease-time allows for a gradual change.
+\fItime\fR is either second since epoch, or a UTC time string e.g.
+4 2007/08/24 09:14:32 or a string with time zone offset in seconds
+e.g. 4 2007/08/24 11:14:32 -7200
+.SH REFERENCE: PARAMETERS
+The
+.I adaptive-lease-time-threshold
+statement
+.RS 0.25i
+.PP
+.B adaptive-lease-time-threshold \fIpercentage\fR\fB;\fR
+.PP
+When the number of allocated leases within a pool rises above
+the \fIpercentage\fR given in this statement, the DHCP server decreases
+the lease length for new clients within this pool to \fImin-lease-time\fR
+seconds. Clients renewing an already valid (long) leases get at least the
+remaining time from the current lease. Since the leases expire faster,
+the server may either recover more quickly or avoid pool exhaustion
+entirely. Once the number of allocated leases drop below the threshold,
+the server reverts back to normal lease times. Valid percentages are
+between 1 and 99.
+.RE
+.PP
+The
+.I always-broadcast
+statement
+.RS 0.25i
+.PP
+.B always-broadcast \fIflag\fR\fB;\fR
+.PP
+The DHCP and BOOTP protocols both require DHCP and BOOTP clients to
+set the broadcast bit in the flags field of the BOOTP message header.
+Unfortunately, some DHCP and BOOTP clients do not do this, and
+therefore may not receive responses from the DHCP server. The DHCP
+server can be made to always broadcast its responses to clients by
+setting this flag to \'on\' for the relevant scope; relevant scopes would be
+inside a conditional statement, as a parameter for a class, or as a parameter
+for a host declaration. To avoid creating excess broadcast traffic on your
+network, we recommend that you restrict the use of this option to as few
+clients as possible. For example, the Microsoft DHCP client is known not
+to have this problem, as are the OpenTransport and ISC DHCP clients.
+.RE
+.PP
+The
+.I always-reply-rfc1048
+statement
+.RS 0.25i
+.PP
+.B always-reply-rfc1048 \fIflag\fR\fB;\fR
+.PP
+Some BOOTP clients expect RFC1048-style responses, but do not follow
+RFC1048 when sending their requests. You can tell that a client is
+having this problem if it is not getting the options you have
+configured for it and if you see in the server log the message
+"(non-rfc1048)" printed with each BOOTREQUEST that is logged.
+.PP
+If you want to send rfc1048 options to such a client, you can set the
+.B always-reply-rfc1048
+option in that client's host declaration, and the DHCP server will
+respond with an RFC-1048-style vendor options field. This flag can
+be set in any scope, and will affect all clients covered by that
+scope.
+.RE
+.PP
+The
+.I authoritative
+statement
+.RS 0.25i
+.PP
+.B authoritative;
+.PP
+.B not authoritative;
+.PP
+The DHCP server will normally assume that the configuration
+information about a given network segment is not known to be correct
+and is not authoritative. This is so that if a naive user installs a
+DHCP server not fully understanding how to configure it, it does not
+send spurious DHCPNAK messages to clients that have obtained addresses
+from a legitimate DHCP server on the network.
+.PP
+Network administrators setting up authoritative DHCP servers for their
+networks should always write \fBauthoritative;\fR at the top of their
+configuration file to indicate that the DHCP server \fIshould\fR send
+DHCPNAK messages to misconfigured clients. If this is not done,
+clients will be unable to get a correct IP address after changing
+subnets until their old lease has expired, which could take quite a
+long time.
+.PP
+Usually, writing \fBauthoritative;\fR at the top level of the file
+should be sufficient. However, if a DHCP server is to be set up so
+that it is aware of some networks for which it is authoritative and
+some networks for which it is not, it may be more appropriate to
+declare authority on a per-network-segment basis.
+.PP
+Note that the most specific scope for which the concept of authority
+makes any sense is the physical network segment - either a
+shared-network statement or a subnet statement that is not contained
+within a shared-network statement. It is not meaningful to specify
+that the server is authoritative for some subnets within a shared
+network, but not authoritative for others, nor is it meaningful to
+specify that the server is authoritative for some host declarations
+and not others.
+.RE
+.PP
+The \fIboot-unknown-clients\fR statement
+.RS 0.25i
+.PP
+.B boot-unknown-clients \fIflag\fB;\fR
+.PP
+If the \fIboot-unknown-clients\fR statement is present and has a value
+of \fIfalse\fR or \fIoff\fR, then clients for which there is no
+.I host
+declaration will not be allowed to obtain IP addresses. If this
+statement is not present or has a value of \fItrue\fR or \fIon\fR,
+then clients without host declarations will be allowed to obtain IP
+addresses, as long as those addresses are not restricted by
+.I allow
+and \fIdeny\fR statements within their \fIpool\fR declarations.
+.RE
+.PP
+The \fIdb-time-format\fR statement
+.RS 0.25i
+.PP
+.B db-time-format \fR[ \fIdefault\fR | \fIlocal\fR ] \fB;\fR
+.PP
+The DHCP server software outputs several timestamps when writing leases to
+persistent storage. This configuration parameter selects one of two output
+formats. The \fIdefault\fR format prints the day, date, and time in UTC,
+while the \fIlocal\fR format prints the system seconds-since-epoch, and
+helpfully provides the day and time in the system timezone in a comment.
+The time formats are described in detail in the dhcpd.leases(5) manpage.
+.RE
+.PP
+The \fIddns-hostname\fR statement
+.RS 0.25i
+.PP
+.B ddns-hostname \fIname\fB;\fR
+.PP
+The \fIname\fR parameter should be the hostname that will be used in
+setting up the client's A and PTR records. If no \fIddns-hostname\fR is
+specified in scope, then the server will derive the hostname
+automatically, using an algorithm that varies for each of the
+different update methods.
+.RE
+.PP
+The \fIddns-domainname\fR statement
+.RS 0.25i
+.PP
+.B ddns-domainname \fIname\fB;\fR
+.PP
+The \fIname\fR parameter should be the domain name that will be
+appended to the client's hostname to form a fully-qualified
+domain-name (FQDN).
+.RE
+.PP
+The \fIddns-rev-domainname\fR statement
+.RS 0.25i
+.PP
+.B ddns-rev-domainname \fIname\fB;\fR
+The \fIname\fR parameter should be the domain name that will be
+appended to the client's reversed IP address to produce a name for use
+in the client's PTR record. By default, this is "in-addr.arpa.", but
+the default can be overridden here.
+.PP
+The reversed IP address to which this domain name is appended is
+always the IP address of the client, in dotted quad notation, reversed
+- for example, if the IP address assigned to the client is
+10.17.92.74, then the reversed IP address is 74.92.17.10. So a
+client with that IP address would, by default, be given a PTR record
+of 10.17.92.74.in-addr.arpa.
+.RE
+.PP
+The \fIddns-update-style\fR parameter
+.RS 0.25i
+.PP
+.B ddns-update-style \fIstyle\fB;\fR
+.PP
+The
+.I style
+parameter must be one of \fBad-hoc\fR, \fBinterim\fR or \fBnone\fR.
+The \fIddns-update-style\fR statement is only meaningful in the outer
+scope - it is evaluated once after reading the dhcpd.conf file, rather
+than each time a client is assigned an IP address, so there is no way
+to use different DNS update styles for different clients. The default
+is \fBnone\fR.
+.RE
+.PP
+.B The
+.I ddns-updates
+.B statement
+.RS 0.25i
+.PP
+ \fBddns-updates \fIflag\fR\fB;\fR
+.PP
+The \fIddns-updates\fR parameter controls whether or not the server will
+attempt to do a DNS update when a lease is confirmed. Set this to \fIoff\fR
+if the server should not attempt to do updates within a certain scope.
+The \fIddns-updates\fR parameter is on by default. To disable DNS
+updates in all scopes, it is preferable to use the
+\fIddns-update-style\fR statement, setting the style to \fInone\fR.
+.RE
+.PP
+The
+.I default-lease-time
+statement
+.RS 0.25i
+.PP
+.B default-lease-time \fItime\fR\fB;\fR
+.PP
+.I Time
+should be the length in seconds that will be assigned to a lease if
+the client requesting the lease does not ask for a specific expiration
+time. This is used for both DHCPv4 and DHCPv6 leases (it is also known
+as the "valid lifetime" in DHCPv6).
+The default is 43200 seconds.
+.RE
+.PP
+The
+.I delayed-ack
+and
+.I max-ack-delay
+statements
+.RS 0.25i
+.PP
+.B delayed-ack \fIcount\fR\fB;\fR
+.B max-ack-delay \fImicroseconds\fR\fB;\fR
+.PP
+.I Count
+should be an integer value from zero to 2^16-1, and defaults to 28. The
+count represents how many DHCPv4 replies maximum will be queued pending
+transmission until after a database commit event. If this number is
+reached, a database commit event (commonly resulting in fsync() and
+representing a performance penalty) will be made, and the reply packets
+will be transmitted in a batch afterwards. This preserves the RFC2131
+direction that "stable storage" be updated prior to replying to clients.
+Should the DHCPv4 sockets "go dry" (select() returns immediately with no
+read sockets), the commit is made and any queued packets are transmitted.
+.PP
+Similarly, \fImicroseconds\fR indicates how many microseconds are permitted
+to pass inbetween queuing a packet pending an fsync, and performing the
+fsync. Valid values range from 0 to 2^32-1, and defaults to 250,000 (1/4 of
+a second).
+.PP
+Please note that as delayed-ack is currently experimental, the delayed-ack
+feature is not compiled in by default, but must be enabled at compile time
+with \'./configure --enable-delayed-ack\'.
+.RE
+.PP
+The
+.I do-forward-updates
+statement
+.RS 0.25i
+.PP
+.B do-forward-updates \fIflag\fB;\fR
+.PP
+The \fIdo-forward-updates\fR statement instructs the DHCP server as
+to whether it should attempt to update a DHCP client's A record
+when the client acquires or renews a lease. This statement has no
+effect unless DNS updates are enabled and \fBddns-update-style\fR is
+set to \fBinterim\fR. Forward updates are enabled by default. If
+this statement is used to disable forward updates, the DHCP server
+will never attempt to update the client's A record, and will only ever
+attempt to update the client's PTR record if the client supplies an
+FQDN that should be placed in the PTR record using the \fBfqdn\fR option.
+If forward updates are enabled, the DHCP server will still honor the
+setting of the \fBclient-updates\fR flag.
+.RE
+.PP
+The
+.I dynamic-bootp-lease-cutoff
+statement
+.RS 0.25i
+.PP
+.B dynamic-bootp-lease-cutoff \fIdate\fB;\fR
+.PP
+The \fIdynamic-bootp-lease-cutoff\fR statement sets the ending time
+for all leases assigned dynamically to BOOTP clients. Because BOOTP
+clients do not have any way of renewing leases, and don't know that
+their leases could expire, by default dhcpd assigns infinite leases
+to all BOOTP clients. However, it may make sense in some situations
+to set a cutoff date for all BOOTP leases - for example, the end of a
+school term, or the time at night when a facility is closed and all
+machines are required to be powered off.
+.PP
+.I Date
+should be the date on which all assigned BOOTP leases will end. The
+date is specified in the form:
+.PP
+.ce 1
+W YYYY/MM/DD HH:MM:SS
+.PP
+W is the day of the week expressed as a number
+from zero (Sunday) to six (Saturday). YYYY is the year, including the
+century. MM is the month expressed as a number from 1 to 12. DD is
+the day of the month, counting from 1. HH is the hour, from zero to
+23. MM is the minute and SS is the second. The time is always in
+Coordinated Universal Time (UTC), not local time.
+.RE
+.PP
+The
+.I dynamic-bootp-lease-length
+statement
+.RS 0.25i
+.PP
+.B dynamic-bootp-lease-length\fR \fIlength\fR\fB;\fR
+.PP
+The \fIdynamic-bootp-lease-length\fR statement is used to set the
+length of leases dynamically assigned to BOOTP clients. At some
+sites, it may be possible to assume that a lease is no longer in
+use if its holder has not used BOOTP or DHCP to get its address within
+a certain time period. The period is specified in \fIlength\fR as a
+number of seconds. If a client reboots using BOOTP during the
+timeout period, the lease duration is reset to \fIlength\fR, so a
+BOOTP client that boots frequently enough will never lose its lease.
+Needless to say, this parameter should be adjusted with extreme
+caution.
+.RE
+.PP
+The
+.I filename
+statement
+.RS 0.25i
+.PP
+.B filename\fR \fB"\fR\fIfilename\fR\fB";\fR
+.PP
+The \fIfilename\fR statement can be used to specify the name of the
+initial boot file which is to be loaded by a client. The
+.I filename
+should be a filename recognizable to whatever file transfer protocol
+the client can be expected to use to load the file.
+.RE
+.PP
+The
+.I fixed-address
+declaration
+.RS 0.25i
+.PP
+.B fixed-address address\fR [\fB,\fR \fIaddress\fR ... ]\fB;\fR
+.PP
+The \fIfixed-address\fR declaration is used to assign one or more fixed
+IP addresses to a client. It should only appear in a \fIhost\fR
+declaration. If more than one address is supplied, then when the
+client boots, it will be assigned the address that corresponds to the
+network on which it is booting. If none of the addresses in the
+\fIfixed-address\fR statement are valid for the network to which the client
+is connected, that client will not match the \fIhost\fR declaration
+containing that \fIfixed-address\fR declaration. Each \fIaddress\fR
+in the \fIfixed-address\fR declaration should be either an IP address or
+a domain name that resolves to one or more IP addresses.
+.RE
+.PP
+The
+.I fixed-address6
+declaration
+.RS 0.25i
+.PP
+.B fixed-address6 ip6-address\fR ;\fR
+.PP
+The \fIfixed-address6\fR declaration is used to assign a fixed
+IPv6 addresses to a client. It should only appear in a \fIhost\fR
+declaration.
+.RE
+.PP
+The
+.I get-lease-hostnames
+statement
+.RS 0.25i
+.PP
+.B get-lease-hostnames\fR \fIflag\fR\fB;\fR
+.PP
+The \fIget-lease-hostnames\fR statement is used to tell dhcpd whether
+or not to look up the domain name corresponding to the IP address of
+each address in the lease pool and use that address for the DHCP
+\fIhostname\fR option. If \fIflag\fR is true, then this lookup is
+done for all addresses in the current scope. By default, or if
+\fIflag\fR is false, no lookups are done.
+.RE
+.PP
+The
+.I hardware
+statement
+.RS 0.25i
+.PP
+.B hardware \fIhardware-type hardware-address\fB;\fR
+.PP
+In order for a BOOTP client to be recognized, its network hardware
+address must be declared using a \fIhardware\fR clause in the
+.I host
+statement.
+.I hardware-type
+must be the name of a physical hardware interface type. Currently,
+only the
+.B ethernet
+and
+.B token-ring
+types are recognized, although support for a
+.B fddi
+hardware type (and others) would also be desirable.
+The
+.I hardware-address
+should be a set of hexadecimal octets (numbers from 0 through ff)
+separated by colons. The \fIhardware\fR statement may also be used
+for DHCP clients.
+.RE
+.PP
+The
+.I host-identifier option
+statement
+.RS 0.25i
+.PP
+.B host-identifier option \fIoption-name option-data\fB;\fR
+.PP
+This identifies a DHCPv6 client in a
+.I host
+statement.
+.I option-name
+is any option, and
+.I option-data
+is the value for the option that the client will send. The
+.I option-data
+must be a constant value.
+.RE
+.PP
+The
+.I infinite-is-reserved
+statement
+.RS 0.25i
+.PP
+.B infinite-is-reserved \fIflag\fB;\fR
+.PP
+ISC DHCP now supports \'reserved\' leases. See the section on RESERVED LEASES
+below. If this \fIflag\fR is on, the server will automatically reserve leases
+allocated to clients which requested an infinite (0xffffffff) lease-time.
+.PP
+The default is off.
+.RE
+.PP
+The
+.I lease-file-name
+statement
+.RS 0.25i
+.PP
+.B lease-file-name \fIname\fB;\fR
+.PP
+.I Name
+should be the name of the DHCP server's lease file. By default, this
+is DBDIR/dhcpd.leases. This statement \fBmust\fR appear in the outer
+scope of the configuration file - if it appears in some other scope,
+it will have no effect. Furthermore, it has no effect if overridden
+by the
+.B -lf
+flag or the
+.B PATH_DHCPD_DB
+environment variable.
+.RE
+.PP
+The
+.I limit-addrs-per-ia
+statement
+.RS 0.25i
+.PP
+.B limit-addrs-per-ia \fInumber\fB;\fR
+.PP
+By default, the DHCPv6 server will limit clients to one IAADDR per IA
+option, meaning one address. If you wish to permit clients to hang onto
+multiple addresses at a time, configure a larger \fInumber\fR here.
+.PP
+Note that there is no present method to configure the server to forcibly
+configure the client with one IP address per each subnet on a shared network.
+This is left to future work.
+.RE
+.PP
+The
+.I dhcpv6-lease-file-name
+statement
+.RS 0.25i
+.PP
+.B dhcpv6-lease-file-name \fIname\fB;\fR
+.PP
+.I Name
+is the name of the lease file to use if and only if the server is running
+in DHCPv6 mode. By default, this is DBDIR/dhcpd6.leases. This statement,
+like
+.I lease-file-name,
+\fBmust\fR appear in the outer scope of the configuration file. It
+has no effect if overridden by the
+.B -lf
+flag or the
+.B PATH_DHCPD6_DB
+environment variable. If
+.I dhcpv6-lease-file-name
+is not specified, but
+.I lease-file-name
+is, the latter value will be used.
+.RE
+.PP
+The
+.I local-port
+statement
+.RS 0.25i
+.PP
+.B local-port \fIport\fB;\fR
+.PP
+This statement causes the DHCP server to listen for DHCP requests on
+the UDP port specified in \fIport\fR, rather than on port 67.
+.RE
+.PP
+The
+.I local-address
+statement
+.RS 0.25i
+.PP
+.B local-address \fIaddress\fB;\fR
+.PP
+This statement causes the DHCP server to listen for DHCP requests sent
+to the specified \fIaddress\fR, rather than requests sent to all addresses.
+Since serving directly attached DHCP clients implies that the server must
+respond to requests sent to the all-ones IP address, this option cannot be
+used if clients are on directly attached networks; it is only realistically
+useful for a server whose only clients are reached via unicasts, such as via
+DHCP relay agents.
+.PP
+Note: This statement is only effective if the server was compiled using
+the USE_SOCKETS #define statement, which is default on a small number of
+operating systems, and must be explicitly chosen at compile-time for all
+others. You can be sure if your server is compiled with USE_SOCKETS if
+you see lines of this format at startup:
+.PP
+ Listening on Socket/eth0
+.PP
+Note also that since this bind()s all DHCP sockets to the specified
+address, that only one address may be supported in a daemon at a given
+time.
+.RE
+.PP
+The
+.I log-facility
+statement
+.RS 0.25i
+.PP
+.B log-facility \fIfacility\fB;\fR
+.PP
+This statement causes the DHCP server to do all of its logging on the
+specified log facility once the dhcpd.conf file has been read. By
+default the DHCP server logs to the daemon facility. Possible log
+facilities include auth, authpriv, cron, daemon, ftp, kern, lpr, mail,
+mark, news, ntp, security, syslog, user, uucp, and local0 through
+local7. Not all of these facilities are available on all systems,
+and there may be other facilities available on other systems.
+.PP
+In addition to setting this value, you may need to modify your
+.I syslog.conf
+file to configure logging of the DHCP server. For example, you might
+add a line like this:
+.PP
+.nf
+ local7.debug /var/log/dhcpd.log
+.fi
+.PP
+The syntax of the \fIsyslog.conf\fR file may be different on some
+operating systems - consult the \fIsyslog.conf\fR manual page to be
+sure. To get syslog to start logging to the new file, you must first
+create the file with correct ownership and permissions (usually, the
+same owner and permissions of your /var/log/messages or
+/usr/adm/messages file should be fine) and send a SIGHUP to syslogd.
+Some systems support log rollover using a shell script or program
+called newsyslog or logrotate, and you may be able to configure this
+as well so that your log file doesn't grow uncontrollably.
+.PP
+Because the \fIlog-facility\fR setting is controlled by the dhcpd.conf
+file, log messages printed while parsing the dhcpd.conf file or before
+parsing it are logged to the default log facility. To prevent this,
+see the README file included with this distribution, which describes
+BUG: where is that mentioned in README?
+how to change the default log facility. When this parameter is used,
+the DHCP server prints its startup message a second time after parsing
+the configuration file, so that the log will be as complete as
+possible.
+.RE
+.PP
+The
+.I max-lease-time
+statement
+.RS 0.25i
+.PP
+.B max-lease-time \fItime\fR\fB;\fR
+.PP
+.I Time
+should be the maximum length in seconds that will be assigned to a
+lease.
+If not defined, the default maximum lease time is 86400.
+The only exception to this is that Dynamic BOOTP lease
+lengths, which are not specified by the client, are not limited by
+this maximum.
+.RE
+.PP
+The
+.I min-lease-time
+statement
+.RS 0.25i
+.PP
+.B min-lease-time \fItime\fR\fB;\fR
+.PP
+.I Time
+should be the minimum length in seconds that will be assigned to a
+lease.
+The default is the minimum of 300 seconds or
+\fBmax-lease-time\fR.
+.RE
+.PP
+The
+.I min-secs
+statement
+.RS 0.25i
+.PP
+.B min-secs \fIseconds\fR\fB;\fR
+.PP
+.I Seconds
+should be the minimum number of seconds since a client began trying to
+acquire a new lease before the DHCP server will respond to its request.
+The number of seconds is based on what the client reports, and the maximum
+value that the client can report is 255 seconds. Generally, setting this
+to one will result in the DHCP server not responding to the client's first
+request, but always responding to its second request.
+.PP
+This can be used
+to set up a secondary DHCP server which never offers an address to a client
+until the primary server has been given a chance to do so. If the primary
+server is down, the client will bind to the secondary server, but otherwise
+clients should always bind to the primary. Note that this does not, by
+itself, permit a primary server and a secondary server to share a pool of
+dynamically-allocatable addresses.
+.RE
+.PP
+The
+.I next-server
+statement
+.RS 0.25i
+.PP
+.B next-server\fR \fIserver-name\fR\fB;\fR
+.PP
+The \fInext-server\fR statement is used to specify the host address of
+the server from which the initial boot file (specified in the
+\fIfilename\fR statement) is to be loaded. \fIServer-name\fR should
+be a numeric IP address or a domain name.
+.RE
+.PP
+The
+.I omapi-port
+statement
+.RS 0.25i
+.PP
+.B omapi-port\fR \fIport\fR\fB;\fR
+.PP
+The \fIomapi-port\fR statement causes the DHCP server to listen for
+OMAPI connections on the specified port. This statement is required
+to enable the OMAPI protocol, which is used to examine and modify the
+state of the DHCP server as it is running.
+.RE
+.PP
+The
+.I one-lease-per-client
+statement
+.RS 0.25i
+.PP
+.B one-lease-per-client \fIflag\fR\fB;\fR
+.PP
+If this flag is enabled, whenever a client sends a DHCPREQUEST for a
+particular lease, the server will automatically free any other leases
+the client holds. This presumes that when the client sends a
+DHCPREQUEST, it has forgotten any lease not mentioned in the
+DHCPREQUEST - i.e., the client has only a single network interface
+.I and
+it does not remember leases it's holding on networks to which it is
+not currently attached. Neither of these assumptions are guaranteed
+or provable, so we urge caution in the use of this statement.
+.RE
+.PP
+The
+.I pid-file-name
+statement
+.RS 0.25i
+.PP
+.B pid-file-name
+.I name\fR\fB;\fR
+.PP
+.I Name
+should be the name of the DHCP server's process ID file. This is the
+file in which the DHCP server's process ID is stored when the server
+starts. By default, this is RUNDIR/dhcpd.pid. Like the
+.I lease-file-name
+statement, this statement must appear in the outer scope
+of the configuration file. It has no effect if overridden by the
+.B -pf
+flag or the
+.B PATH_DHCPD_PID
+environment variable.
+.PP
+The
+.I dhcpv6-pid-file-name
+statement
+.RS 0.25i
+.PP
+.B dhcpv6-pid-file-name \fIname\fB;\fR
+.PP
+.I Name
+is the name of the pid file to use if and only if the server is running
+in DHCPv6 mode. By default, this is DBDIR/dhcpd6.pid. This statement,
+like
+.I pid-file-name,
+\fBmust\fR appear in the outer scope of the configuration file. It
+has no effect if overridden by the
+.B -pf
+flag or the
+.B PATH_DHCPD6_PID
+environment variable. If
+.I dhcpv6-pid-file-name
+is not specified, but
+.I pid-file-name
+is, the latter value will be used.
+.RE
+.PP
+The
+.I ping-check
+statement
+.RS 0.25i
+.PP
+.B ping-check
+.I flag\fR\fB;\fR
+.PP
+When the DHCP server is considering dynamically allocating an IP
+address to a client, it first sends an ICMP Echo request (a \fIping\fR)
+to the address being assigned. It waits for a second, and if no
+ICMP Echo response has been heard, it assigns the address. If a
+response \fIis\fR heard, the lease is abandoned, and the server does
+not respond to the client.
+.PP
+This \fIping check\fR introduces a default one-second delay in responding
+to DHCPDISCOVER messages, which can be a problem for some clients. The
+default delay of one second may be configured using the ping-timeout
+parameter. The ping-check configuration parameter can be used to control
+checking - if its value is false, no ping check is done.
+.RE
+.PP
+The
+.I ping-timeout
+statement
+.RS 0.25i
+.PP
+.B ping-timeout
+.I seconds\fR\fB;\fR
+.PP
+If the DHCP server determined it should send an ICMP echo request (a
+\fIping\fR) because the ping-check statement is true, ping-timeout allows
+you to configure how many seconds the DHCP server should wait for an
+ICMP Echo response to be heard, if no ICMP Echo response has been received
+before the timeout expires, it assigns the address. If a response \fIis\fR
+heard, the lease is abandoned, and the server does not respond to the client.
+If no value is set, ping-timeout defaults to 1 second.
+.RE
+.PP
+The
+.I preferred-lifetime
+statement
+.RS 0.25i
+.PP
+.B preferred-lifetime
+.I seconds\fR\fB;\fR
+.PP
+IPv6 addresses have \'valid\' and \'preferred\' lifetimes. The valid lifetime
+determines at what point at lease might be said to have expired, and is no
+longer useable. A preferred lifetime is an advisory condition to help
+applications move off of the address and onto currently valid addresses
+(should there still be any open TCP sockets or similar).
+.PP
+The preferred lifetime defaults to the renew+rebind timers, or 3/4 the
+default lease time if none were specified.
+.RE
+.PP
+The
+.I remote-port
+statement
+.RS 0.25i
+.PP
+.B remote-port \fIport\fB;\fR
+.PP
+This statement causes the DHCP server to transmit DHCP responses to DHCP
+clients upon the UDP port specified in \fIport\fR, rather than on port 68.
+In the event that the UDP response is transmitted to a DHCP Relay, the
+server generally uses the \fBlocal-port\fR configuration value. Should the
+DHCP Relay happen to be addressed as 127.0.0.1, however, the DHCP Server
+transmits its response to the \fBremote-port\fR configuration value. This
+is generally only useful for testing purposes, and this configuration value
+should generally not be used.
+.RE
+.PP
+The
+.I server-identifier
+statement
+.RS 0.25i
+.PP
+.B server-identifier \fIhostname\fR\fB;\fR
+.PP
+The server-identifier statement can be used to define the value that
+is sent in the DHCP Server Identifier option for a given scope. The
+value specified \fBmust\fR be an IP address for the DHCP server, and
+must be reachable by all clients served by a particular scope.
+.PP
+The use of the server-identifier statement is not recommended - the only
+reason to use it is to force a value other than the default value to be
+sent on occasions where the default value would be incorrect. The default
+value is the first IP address associated with the physical network interface
+on which the request arrived.
+.PP
+The usual case where the
+\fIserver-identifier\fR statement needs to be sent is when a physical
+interface has more than one IP address, and the one being sent by default
+isn't appropriate for some or all clients served by that interface.
+Another common case is when an alias is defined for the purpose of
+having a consistent IP address for the DHCP server, and it is desired
+that the clients use this IP address when contacting the server.
+.PP
+Supplying a value for the dhcp-server-identifier option is equivalent
+to using the server-identifier statement.
+.RE
+.PP
+The
+.I server-duid
+statement
+.RS 0.25i
+.PP
+.B server-duid \fILLT\fR [ \fIhardware-type\fR \fItimestamp\fR \fIhardware-address\fR ] \fB;\fR
+
+.B server-duid \fIEN\fR \fIenterprise-number\fR \fIenterprise-identifier\fR \fB;\fR
+
+.B server-duid \fILL\fR [ \fIhardware-type\fR \fIhardware-address\fR ] \fB;\fR
+.PP
+The server-duid statement configures the server DUID. You may pick either
+LLT (link local address plus time), EN (enterprise), or LL (link local).
+.PP
+If you choose LLT or LL, you may specify the exact contents of the DUID.
+Otherwise the server will generate a DUID of the specified type.
+.PP
+If you choose EN, you must include the enterprise number and the
+enterprise-identifier.
+.PP
+The default server-duid type is LLT.
+.RE
+.PP
+The
+.I server-name
+statement
+.RS 0.25i
+.PP
+.B server-name "\fIname\fB";\fR
+.PP
+The \fIserver-name\fR statement can be used to inform the client of
+the name of the server from which it is booting. \fIName\fR should
+be the name that will be provided to the client.
+.RE
+.PP
+The
+.I site-option-space
+statement
+.RS 0.25i
+.PP
+.B site-option-space "\fIname\fB";\fR
+.PP
+The \fIsite-option-space\fR statement can be used to determine from
+what option space site-local options will be taken. This can be used
+in much the same way as the \fIvendor-option-space\fR statement.
+Site-local options in DHCP are those options whose numeric codes are
+greater than 224. These options are intended for site-specific
+uses, but are frequently used by vendors of embedded hardware that
+contains DHCP clients. Because site-specific options are allocated
+on an ad hoc basis, it is quite possible that one vendor's DHCP client
+might use the same option code that another vendor's client uses, for
+different purposes. The \fIsite-option-space\fR option can be used
+to assign a different set of site-specific options for each such
+vendor, using conditional evaluation (see \fBdhcp-eval (5)\fR for
+details).
+.RE
+.PP
+The
+.I stash-agent-options
+statement
+.RS 0.25i
+.PP
+.B stash-agent-options \fIflag\fB;\fR
+.PP
+If the \fIstash-agent-options\fR parameter is true for a given client,
+the server will record the relay agent information options sent during
+the client's initial DHCPREQUEST message when the client was in the
+SELECTING state and behave as if those options are included in all
+subsequent DHCPREQUEST messages sent in the RENEWING state. This
+works around a problem with relay agent information options, which is
+that they usually not appear in DHCPREQUEST messages sent by the
+client in the RENEWING state, because such messages are unicast
+directly to the server and not sent through a relay agent.
+.RE
+.PP
+The
+.I update-conflict-detection
+statement
+.RS 0.25i
+.PP
+.B update-conflict-detection \fIflag\fB;\fR
+.PP
+If the \fIupdate-conflict-detection\fR parameter is true, the server will
+perform standard DHCID multiple-client, one-name conflict detection. If
+the parameter has been set false, the server will skip this check and
+instead simply tear down any previous bindings to install the new
+binding without question. The default is true.
+.RE
+.PP
+The
+.I update-optimization
+statement
+.RS 0.25i
+.PP
+.B update-optimization \fIflag\fB;\fR
+.PP
+If the \fIupdate-optimization\fR parameter is false for a given client,
+the server will attempt a DNS update for that client each time the
+client renews its lease, rather than only attempting an update when it
+appears to be necessary. This will allow the DNS to heal from
+database inconsistencies more easily, but the cost is that the DHCP
+server must do many more DNS updates. We recommend leaving this option
+enabled, which is the default. This option only affects the behavior of
+the interim DNS update scheme, and has no effect on the ad-hoc DNS update
+scheme. If this parameter is not specified, or is true, the DHCP server
+will only update when the client information changes, the client gets a
+different lease, or the client's lease expires.
+.RE
+.PP
+The
+.I update-static-leases
+statement
+.RS 0.25i
+.PP
+.B update-static-leases \fIflag\fB;\fR
+.PP
+The \fIupdate-static-leases\fR flag, if enabled, causes the DHCP
+server to do DNS updates for clients even if those clients are being
+assigned their IP address using a \fIfixed-address\fR statement - that
+is, the client is being given a static assignment. This can only
+work with the \fIinterim\fR DNS update scheme. It is not
+recommended because the DHCP server has no way to tell that the update
+has been done, and therefore will not delete the record when it is not
+in use. Also, the server must attempt the update each time the
+client renews its lease, which could have a significant performance
+impact in environments that place heavy demands on the DHCP server.
+.RE
+.PP
+The
+.I use-host-decl-names
+statement
+.RS 0.25i
+.PP
+.B use-host-decl-names \fIflag\fB;\fR
+.PP
+If the \fIuse-host-decl-names\fR parameter is true in a given scope,
+then for every host declaration within that scope, the name provided
+for the host declaration will be supplied to the client as its
+hostname. So, for example,
+.PP
+.nf
+ group {
+ use-host-decl-names on;
+
+ host joe {
+ hardware ethernet 08:00:2b:4c:29:32;
+ fixed-address joe.fugue.com;
+ }
+ }
+
+is equivalent to
+
+ host joe {
+ hardware ethernet 08:00:2b:4c:29:32;
+ fixed-address joe.fugue.com;
+ option host-name "joe";
+ }
+.fi
+.PP
+Additionally, enabling use-host-decl-names instructs the server to use
+the host declaration name in the the forward DNS name, if no other values
+are available. This value selection process is discussed in more detail
+under DNS updates.
+.PP
+An \fIoption host-name\fR statement within a host declaration will
+override the use of the name in the host declaration.
+.PP
+It should be noted here that most DHCP clients completely ignore the
+host-name option sent by the DHCP server, and there is no way to
+configure them not to do this. So you generally have a choice of
+either not having any hostname to client IP address mapping that the
+client will recognize, or doing DNS updates. It is beyond
+the scope of this document to describe how to make this
+determination.
+.RE
+.PP
+The
+.I use-lease-addr-for-default-route
+statement
+.RS 0.25i
+.PP
+.B use-lease-addr-for-default-route \fIflag\fR\fB;\fR
+.PP
+If the \fIuse-lease-addr-for-default-route\fR parameter is true in a
+given scope, then instead of sending the value specified in the
+routers option (or sending no value at all), the IP address of the
+lease being assigned is sent to the client. This supposedly causes
+Win95 machines to ARP for all IP addresses, which can be helpful if
+your router is configured for proxy ARP. The use of this feature is
+not recommended, because it won't work for many DHCP clients.
+.RE
+.PP
+The
+.I vendor-option-space
+statement
+.RS 0.25i
+.PP
+.B vendor-option-space \fIstring\fR\fB;\fR
+.PP
+The \fIvendor-option-space\fR parameter determines from what option
+space vendor options are taken. The use of this configuration
+parameter is illustrated in the \fBdhcp-options(5)\fR manual page, in
+the \fIVENDOR ENCAPSULATED OPTIONS\fR section.
+.RE
+.SH SETTING PARAMETER VALUES USING EXPRESSIONS
+Sometimes it's helpful to be able to set the value of a DHCP server
+parameter 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
+ \fImy-parameter \fB= \fIexpression \fB;\fR
+.fi
+.PP
+For example:
+.nf
+.sp 1
+ ddns-hostname = binary-to-ascii (16, 8, "-",
+ substring (hardware, 1, 6));
+.fi
+.RE
+.SH RESERVED LEASES
+It's often useful to allocate a single address to a single client, in
+approximate perpetuity. Host statements with \fBfixed-address\fR clauses
+exist to a certain extent to serve this purpose, but because host statements
+are intended to approximate \'static configuration\', they suffer from not
+being referenced in a littany of other Server Services, such as dynamic DNS,
+failover, \'on events\' and so forth.
+.PP
+If a standard dynamic lease, as from any range statement, is marked
+\'reserved\', then the server will only allocate this lease to the client it
+is identified by (be that by client identifier or hardware address).
+.PP
+In practice, this means that the lease follows the normal state engine, enters
+ACTIVE state when the client is bound to it, expires, or is released, and any
+events or services that would normally be supplied during these events are
+processed normally, as with any other dynamic lease. The only difference
+is that failover servers treat reserved leases as special when they enter
+the FREE or BACKUP states - each server applies the lease into the state it
+may allocate from - and the leases are not placed on the queue for allocation
+to other clients. Instead they may only be \'found\' by client identity. The
+result is that the lease is only offered to the returning client.
+.PP
+Care should probably be taken to ensure that the client only has one lease
+within a given subnet that it is identified by.
+.PP
+Leases may be set \'reserved\' either through OMAPI, or through the
+\'infinite-is-reserved\' configuration option (if this is applicable to your
+environment and mixture of clients).
+.PP
+It should also be noted that leases marked \'reserved\' are effectively treated
+the same as leases marked \'bootp\'.
+.RE
+.SH REFERENCE: OPTION STATEMENTS
+DHCP option statements are documented in the
+.B dhcp-options(5)
+manual page.
+.SH REFERENCE: EXPRESSIONS
+Expressions used in DHCP option statements and elsewhere are
+documented in the
+.B dhcp-eval(5)
+manual page.
+.SH SEE ALSO
+dhcpd(8), dhcpd.leases(5), dhcp-options(5), dhcp-eval(5), RFC2132, RFC2131.
+.SH AUTHOR
+.B dhcpd.conf(5)
+is maintained by ISC.
+Information about Internet Systems Consortium can be found at
+.B https://www.isc.org.
diff --git a/server/dhcpd.conf.example b/server/dhcpd.conf.example
new file mode 100644
index 0000000..5eab951
--- /dev/null
+++ b/server/dhcpd.conf.example
@@ -0,0 +1,104 @@
+# dhcpd.conf
+#
+# Sample configuration file for ISC dhcpd
+#
+
+# option definitions common to all supported networks...
+option domain-name "example.org";
+option domain-name-servers ns1.example.org, ns2.example.org;
+
+default-lease-time 600;
+max-lease-time 7200;
+
+# Use this to enble / disable dynamic dns updates globally.
+#ddns-update-style none;
+
+# If this DHCP server is the official DHCP server for the local
+# network, the authoritative directive should be uncommented.
+#authoritative;
+
+# Use this to send dhcp log messages to a different log file (you also
+# have to hack syslog.conf to complete the redirection).
+log-facility local7;
+
+# No service will be given on this subnet, but declaring it helps the
+# DHCP server to understand the network topology.
+
+subnet 10.152.187.0 netmask 255.255.255.0 {
+}
+
+# This is a very basic subnet declaration.
+
+subnet 10.254.239.0 netmask 255.255.255.224 {
+ range 10.254.239.10 10.254.239.20;
+ option routers rtr-239-0-1.example.org, rtr-239-0-2.example.org;
+}
+
+# This declaration allows BOOTP clients to get dynamic addresses,
+# which we don't really recommend.
+
+subnet 10.254.239.32 netmask 255.255.255.224 {
+ range dynamic-bootp 10.254.239.40 10.254.239.60;
+ option broadcast-address 10.254.239.31;
+ option routers rtr-239-32-1.example.org;
+}
+
+# A slightly different configuration for an internal subnet.
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ range 10.5.5.26 10.5.5.30;
+ option domain-name-servers ns1.internal.example.org;
+ option domain-name "internal.example.org";
+ option routers 10.5.5.1;
+ option broadcast-address 10.5.5.31;
+ default-lease-time 600;
+ max-lease-time 7200;
+}
+
+# Hosts which require special configuration options can be listed in
+# host statements. If no address is specified, the address will be
+# allocated dynamically (if possible), but the host-specific information
+# will still come from the host declaration.
+
+host passacaglia {
+ hardware ethernet 0:0:c0:5d:bd:95;
+ filename "vmunix.passacaglia";
+ server-name "toccata.fugue.com";
+}
+
+# Fixed IP addresses can also be specified for hosts. These addresses
+# should not also be listed as being available for dynamic assignment.
+# Hosts for which fixed IP addresses have been specified can boot using
+# BOOTP or DHCP. Hosts for which no fixed address is specified can only
+# be booted with DHCP, unless there is an address range on the subnet
+# to which a BOOTP client is connected which has the dynamic-bootp flag
+# set.
+host fantasia {
+ hardware ethernet 08:00:07:26:c0:a5;
+ fixed-address fantasia.fugue.com;
+}
+
+# You can declare a class of clients and then do address allocation
+# based on that. The example below shows a case where all clients
+# in a certain class get addresses on the 10.17.224/24 subnet, and all
+# other clients get addresses on the 10.0.29/24 subnet.
+
+class "foo" {
+ match if substring (option vendor-class-identifier, 0, 4) = "SUNW";
+}
+
+shared-network 224-29 {
+ subnet 10.17.224.0 netmask 255.255.255.0 {
+ option routers rtr-224.example.org;
+ }
+ subnet 10.0.29.0 netmask 255.255.255.0 {
+ option routers rtr-29.example.org;
+ }
+ pool {
+ allow members of "foo";
+ range 10.17.224.10 10.17.224.250;
+ }
+ pool {
+ deny members of "foo";
+ range 10.0.29.10 10.0.29.230;
+ }
+}
diff --git a/server/dhcpd.leases.5 b/server/dhcpd.leases.5
new file mode 100644
index 0000000..78c7949
--- /dev/null
+++ b/server/dhcpd.leases.5
@@ -0,0 +1,378 @@
+.\" dhcpd.leases.5
+.\"
+.\" Copyright (c) 2014-2015 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 2004,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/
+.\"
+.\" $Id: dhcpd.leases.5,v 1.14.24.3 2011/09/19 00:24:22 sar Exp $
+.\"
+.TH dhcpd.leases 5
+.SH NAME
+dhcpd.leases - DHCP client lease database
+.SH DESCRIPTION
+The Internet Systems Consortium DHCP Server keeps a persistent
+database of leases that it has assigned. This database is a free-form
+ASCII file containing a series of lease declarations. Every time a
+lease is acquired, renewed or released, its new value is recorded at
+the end of the lease file. So if more than one declaration appears
+for a given lease, the last one in the file is the current one.
+.PP
+When dhcpd is first installed, there is no lease database. However,
+dhcpd requires that a lease database be present before it will start.
+To make the initial lease database, just create an empty file called
+DBDIR/dhcpd.leases. You can do this with:
+.PP
+.nf
+ touch DBDIR/dhcpd.leases
+.fi
+.PP
+In order to prevent the lease database from growing without bound, the
+file is rewritten from time to time. First, a temporary lease
+database is created and all known leases are dumped to it. Then, the
+old lease database is renamed DBDIR/dhcpd.leases~. Finally, the
+newly written lease database is moved into place.
+.PP
+In order to process both DHCPv4 and DHCPv6 messages you will need to
+run two separate instances of the dhcpd process. Each of these
+instances will need it's own lease file. You can use the \fI-lf\fR
+option on the server's command line to specify a different lease file
+name for one or both servers.
+.SH FORMAT
+Lease descriptions are stored in a format that is parsed by the same
+recursive descent parser used to read the
+.B dhcpd.conf(5)
+and
+.B dhclient.conf(5)
+files. Lease files can contain lease declarations, and also group and
+subgroup declarations, host declarations and failover state
+declarations. Group, subgroup and host declarations are used to
+record objects created using the OMAPI protocol.
+.PP
+The lease file is a log-structured file - whenever a lease changes,
+the contents of that lease are written to the end of the file. This
+means that it is entirely possible and quite reasonable for there to
+be two or more declarations of the same lease in the lease file at the
+same time. In that case, the instance of that particular lease that
+appears last in the file is the one that is in effect.
+.PP
+Group, subgroup and host declarations in the lease file are handled in
+the same manner, except that if any of these objects are deleted, a
+\fIrubout\fR is written to the lease file. This is just the same
+declaration, with \fB{ deleted; }\fR in the scope of the
+declaration. When the lease file is rewritten, any such rubouts that
+can be eliminated are eliminated. It is possible to delete a
+declaration in the \fBdhcpd.conf\fR file; in this case, the rubout
+can never be eliminated from the \fBdhcpd.leases\fR file.
+.SH COMMON STATEMENTS FOR LEASE DECLARATIONS
+While the lease file formats for DHCPv4 and DHCPv6 are different
+they share many common statements and structures. This section
+describes the common statements while the succeeding sections
+describe the protocol specific statements.
+.PP
+.B Dates
+.PP
+A \fIdate\fR is specified in two ways, depending on the configuration
+value for the \fBdb-time-format\fR parameter. If it was set to \fIdefault\fR,
+then the \fIdate\fR fields appear as follows:
+.PP
+.I weekday year\fB/\fImonth\fB/\fIday hour\fB:\fIminute\fB:\fIsecond\fR
+.PP
+The weekday is present to make it easy for a human to tell when a
+lease expires - it's specified as a number from zero to six, with zero
+being Sunday. The day of week is ignored on input. The year is
+specified with the century, so it should generally be four digits
+except for really long leases. The month is specified as a number
+starting with 1 for January. The day of the month is likewise
+specified starting with 1. The hour is a number between 0 and 23, the
+minute a number between 0 and 59, and the second also a number between
+0 and 59.
+.PP
+Lease times are specified in Universal Coordinated Time (UTC), not in
+the local time zone. There is probably nowhere in the world where the
+times recorded on a lease are always the same as wall clock times. On
+most unix machines, you can display the current time in UTC by typing
+\fBdate -u\fR.
+.PP
+If the \fBdb-time-format\fR was configured to \fIlocal\fR, then
+the \fIdate\fR fields appear as follows:
+.PP
+ \fBepoch\fR \fI<seconds-since-epoch>\fR\fB; #\fR \fI<day-name> <month-name>
+<day-number> <hours>\fR\fB:\fR\fI<minutes>\fR\fB:\fR\fI<seconds> <year>\fR
+.PP
+The \fIseconds-since-epoch\fR is as according to the system's local clock (often
+referred to as "unix time"). The \fB#\fR symbol supplies a comment that
+describes what actual time this is as according to the system's configured
+timezone, at the time the value was written. It is provided only for human
+inspection.
+.PP
+If a lease will never expire, \fIdate\fR is \fBnever\fR instead of an
+actual date.
+.PP
+.B General Variables
+.PP
+As part of the processing of a lease information may be attached to the
+lease structure, for example the DDNS information or if you specify a
+variable in your configuration file. Some of these, like the DDNS
+information, have specific descriptions below. For others, such as
+any you might define, a generic line of the following will be included.
+.PP
+.B set \fIvariable\fB = \fIvalue\fB;
+.PP
+The \fBset\fR statement sets the value of a variable on the lease.
+For general information on variables, see the \fBdhcp-eval(5)\fR
+manual page.
+.PP
+.B DDNS Variables
+.PP
+.nf
+.B The \fIddns-text\fB variable
+.PP
+This variable is used to record the value of the client's identification
+record when the server has updated DNS for a particular lease. The text
+record is used with the interim DDNS update style.
+.PP
+.B The \fIddns-fwd-name\fB variable
+.PP
+This variable records the value of the name used in
+updating the client's A record if a DDNS update has been successfully
+done by the server. The server may also have used this name to
+update the client's PTR record.
+.PP
+.B The \fIddns-client-fqdn\fB variable
+.PP
+If the server is configured both to use the interim DDNS update
+style, and to allow clients to update their own FQDNs, then if the
+client did in fact update its own FQDN, the
+\fIddns-client-fqdn\fR variable records the name that the client has
+indicated it is using. This is the name that the server will have
+used to update the client's PTR record in this case.
+.PP
+.B The \fIddns-rev-name\fB variable
+.PP
+If the server successfully updates the client's PTR record, this
+variable will record the name that the DHCP server used for the PTR
+record. The name to which the PTR record points will be either the
+\fIddns-fwd-name\fR or the \fIddns-client-fqdn\fR.
+.PP
+.B Executable Statements
+.PP
+.B on \fIevents\fB { \fIstatements...\fB }
+The \fBon\fR statement records a list of statements to execute if a
+certain event occurs. The possible events that can occur for an
+active lease are \fBrelease\fR and \fBexpiry\fR. More than one event
+can be specified - if so, the events are separated by '|' characters.
+.PP
+.SH THE DHCPv4 LEASE DECLARATION
+.PP
+.B lease \fIip-address\fB { \fIstatements...\fB }
+.PP
+Each lease declaration includes the single IP address that has been
+leased to the client. The statements within the braces define the
+duration of the lease and to whom it is assigned.
+.PP
+.nf
+.B starts \fIdate\fB;\fR
+.B ends \fIdate\fB;\fR
+.B tstp \fIdate\fB;\fR
+.B tsfp \fIdate\fB;\fR
+.B atsfp \fIdate\fB;\fR
+.B cltt \fIdate\fB;\fR
+.fi
+.PP
+The start and end time of a lease are recorded using the \fBstarts\fR
+and \fBends\fR statements. The \fBtstp\fR statement is present if
+the failover protocol is being used, and indicates what time the peer
+has been told the lease expires. The \fBtsfp\fR statement is
+also present if the failover protocol is being used, and indicates
+the lease expiry time that the peer has acknowledged.
+The \fBatsfp\fR statement is the actual time sent from the failover
+partner.
+The \fBcltt\fR statement is the client's last transaction time.
+.PP
+See the description of dates in the section on common structures.
+.PP
+.B hardware \fIhardware-type mac-address\fB;\fR
+.PP
+The hardware statement records the MAC address of the network
+interface on which the lease will be used. It is specified as a
+series of hexadecimal octets, separated by colons.
+.PP
+.B uid \fIclient-identifier\fB;\fR
+.PP
+The \fBuid\fR statement records the client identifier used by the
+client to acquire the lease. Clients are not required to send client
+identifiers, and this statement only appears if the client did in fact
+send one. Client identifiers are normally an ARP type (1 for
+ethernet) followed by the MAC address, just like in the \fBhardware\fI
+statement, but this is not required.
+.PP
+The client identifier is recorded as a colon-separated hexadecimal
+list or as a quoted string. If it is recorded as a quoted string and
+it contains one or more non-printable characters, those characters are
+represented as octal escapes - a backslash character followed by three
+octal digits.
+.PP
+.B client-hostname "\fIhostname\fB";\fR
+.PP
+Most DHCP clients will send their hostname in the \fIhost-name\fR
+option. If a client sends its hostname in this way, the hostname is
+recorded on the lease with a \fBclient-hostname\fR statement. This
+is not required by the protocol, however, so many specialized DHCP
+clients do not send a host-name option.
+.PP
+.nf
+.B binding state \fIstate\fB;
+.B next binding state \fIstate\fB;
+.fi
+.PP
+The \fBbinding state\fR statement declares the lease's binding state.
+When the DHCP server is not configured to use the failover protocol, a
+lease's binding state may be \fBactive\fR, \fBfree\fR or \fBabandoned\fR.
+The failover protocol adds some additional transitional states, as well as
+the \fBbackup\fR state, which indicates that the lease is available
+for allocation by the failover secondary. Please see the \fBdhcpd.conf(5)\fR
+manual page for more information about abandoned leases.
+.PP
+The \fBnext binding state\fR statement indicates what state the lease
+will move to when the current state expires. The time when the
+current state expires is specified in the \fIends\fR statement.
+.PP
+.B rewind binding state \fIstate\fB;
+.PP
+This statement is part of an optimization for
+use with failover. This helps a server rewind a lease to the state most
+recently transmitted to its peer.
+.PP
+.nf
+.B option agent.circuit-id \fIstring\fR;
+.B option agent.remote-id \fIstring\fR;
+.fi
+.PP
+These statements are used to record the circuit ID and remote ID options
+sent by the relay agent, if the relay agent uses the \fIrelay agent
+information option\fR. This allows these options to be used
+consistently in conditional evaluations even when the client is
+contacting the server directly rather than through its relay agent.
+.PP
+.B The \fIvendor-class-identifier\fB variable
+.PP
+The server retains the client-supplied Vendor Class Identifier option
+for informational purposes, and to render them in DHCPLEASEQUERY responses.
+.PP
+.nf
+.B bootp;
+.B reserved;
+.fi
+.PP
+If present, they indicate that the BOOTP and RESERVED failover flags
+(respectively) should be set. BOOTP
+and RESERVED dynamic leases are treated differently than normal dynamic leases,
+as they may only be used by the client to which they are currently allocated.
+.PP
+.B Other
+Additional options or executable statements may be included, see the description
+of them in the section on common structures.
+.RE
+.PP
+.SH THE DHCPv6 LEASE (IA) DECLARATION
+.PP
+.nf
+.B ia_ta \fI IAID_DUID\fB { \fIstatements...\fB }
+.B ia_na \fI IAID_DUID\fB { \fIstatements...\fB }
+.B ia_pd \fI IAID_DUID\fB { \fIstatements...\fB }
+.fi
+.PP
+Each lease declaration starts with a tag indicating the type of the lease.
+ia_ta is for temporary addresses, ia_na is for non-temporary addresses and
+ia_pd is for prefix delegation. Following this tag is the combined IAID
+and DUID from the client for this lease.
+.PP
+The IAID_DUID value is recorded as a colon-separated hexadecimal
+list or as a quoted string. If it is recorded as a quoted string and
+it contains one or more non-printable characters, those characters are
+represented as octal escapes - a backslash character followed by three
+octal digits.
+.PP
+.B cltt \fIdate\fB;\fR
+.PP
+The \fBcltt\fR statement is the client's last transaction time.
+.PP
+See the description of dates in the section on common structures.
+.PP
+.nf
+.B iaaddr \fIipv6-address\fB { \fIstatements...\fB }
+.B iaprefix \fIipv6-address/prefix-length\fB { \fIstatements...\fB }
+.PP
+Within a given lease there can be multiple iaaddr and iaprefix statements.
+Each will have either an IPv6 address or an IPv6 prefix (an address and
+a prefix length indicating a CIDR style block of addresses). The following
+statements may occur Within each iaaddr or iaprefix.
+.PP
+.B binding state \fIstate\fB;
+.PP
+The \fBbinding state\fR statement declares the lease's binding state.
+In DHCPv6 you will normally see this as \fIactive\fR or \fIexpired\fR.
+.PP
+.B preferred-life \fIlifetime\fB;
+.PP
+The IPv6 preferred lifetime associated with this address, in seconds.
+.PP
+.B max-life \fIlifetime\fB;
+.PP
+The valid lifetime associated with this address, in seconds.
+.PP
+.B ends \fIdate\fB;\fR
+.PP
+The end time of the lease. See the description of dates in the section on
+common structures.
+.PP
+Additional options or executable statements may be included. See the description
+of them in the section on common structures.
+.PP
+.RE
+.SH THE FAILOVER PEER STATE DECLARATION
+The state of any failover peering arrangements is also recorded in the
+lease file, using the \fBfailover peer\fR statement:
+.PP
+.nf
+.B failover peer "\fIname\fB" state {
+.B my state \fIstate\fB at \fIdate\fB;
+.B peer state \fIstate\fB at \fIdate\fB;
+.B }
+.fi
+.PP
+The states of the peer named \fIname\fR is being recorded. Both the
+state of the running server (\fBmy state\fR) and the other failover
+partner (\fIpeer state\fR) are recorded. The following states are
+possible: \fBunknown-state\fR, \fBpartner-down\fR, \fBnormal\fR,
+\fBcommunications-interrupted\fR, \fBresolution-interrupted\fR,
+\fBpotential-conflict\fR, \fBrecover\fR, \fBrecover-done\fR,
+\fBshutdown\fR, \fBpaused\fR, and \fBstartup\fR.
+.RE
+.SH FILES
+.B DBDIR/dhcpd.leases DBDIR/dhcpd.leases~
+.SH SEE ALSO
+dhcpd(8), dhcp-options(5), dhcp-eval(5), dhcpd.conf(5), RFC2132, RFC2131.
+.SH AUTHOR
+.B dhcpd(8)
+is maintained by ISC.
+Information about Internet Systems Consortium can be found at:
+.B https://www.isc.org/
diff --git a/server/dhcpleasequery.c b/server/dhcpleasequery.c
new file mode 100644
index 0000000..848611b
--- /dev/null
+++ b/server/dhcpleasequery.c
@@ -0,0 +1,1259 @@
+/*
+ * Copyright (C) 2011-2012 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (C) 2006-2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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.
+ */
+
+#include "dhcpd.h"
+
+/*
+ * TODO: RFC4388 specifies that the server SHOULD return the same
+ * options it would for a DHCREQUEST message, if no Parameter
+ * Request List option (option 55) is passed. We do not do that.
+ *
+ * TODO: RFC4388 specifies the creation of a "non-sensitive options"
+ * configuration list, and that these SHOULD be returned. We
+ * have no such list.
+ *
+ * TODO: RFC4388 says the server SHOULD use RFC3118, "Authentication
+ * for DHCP Messages".
+ *
+ * TODO: RFC4388 specifies that you SHOULD insure that you cannot be
+ * DoS'ed by DHCPLEASEQUERY message.
+ */
+
+/*
+ * If you query by hardware address or by client ID, then you may have
+ * more than one IP address for your query argument. We need to do two
+ * things:
+ *
+ * 1. Find the most recent lease.
+ * 2. Find all additional IP addresses for the query argument.
+ *
+ * We do this by looking through all of the leases associated with a
+ * given hardware address or client ID. We use the cltt (client last
+ * transaction time) of the lease, which only has a resolution of one
+ * second, so we might not actually give the very latest IP.
+ */
+
+static struct lease*
+next_hw(const struct lease *lease) {
+ /* INSIST(lease != NULL); */
+ return lease->n_hw;
+}
+
+static struct lease*
+next_uid(const struct lease *lease) {
+ /* INSIST(lease != NULL); */
+ return lease->n_uid;
+}
+
+void
+get_newest_lease(struct lease **retval,
+ struct lease *lease,
+ struct lease *(*next)(const struct lease *)) {
+
+ struct lease *p;
+ struct lease *newest;
+
+ /* INSIST(newest != NULL); */
+ /* INSIST(next != NULL); */
+
+ *retval = NULL;
+
+ if (lease == NULL) {
+ return;
+ }
+
+ newest = lease;
+ for (p=next(lease); p != NULL; p=next(p)) {
+ if (newest->binding_state == FTS_ACTIVE) {
+ if ((p->binding_state == FTS_ACTIVE) &&
+ (p->cltt > newest->cltt)) {
+ newest = p;
+ }
+ } else {
+ if (p->ends > newest->ends) {
+ newest = p;
+ }
+ }
+ }
+
+ lease_reference(retval, newest, MDL);
+}
+
+static int
+get_associated_ips(const struct lease *lease,
+ struct lease *(*next)(const struct lease *),
+ const struct lease *newest,
+ u_int32_t *associated_ips,
+ unsigned int associated_ips_size) {
+
+ const struct lease *p;
+ int cnt;
+
+ /* INSIST(next != NULL); */
+ /* INSIST(associated_ips != NULL); */
+
+ if (lease == NULL) {
+ return 0;
+ }
+
+ cnt = 0;
+ for (p=lease; p != NULL; p=next(p)) {
+ if ((p->binding_state == FTS_ACTIVE) && (p != newest)) {
+ if (cnt < associated_ips_size) {
+ memcpy(&associated_ips[cnt],
+ p->ip_addr.iabuf,
+ sizeof(associated_ips[cnt]));
+ }
+ cnt++;
+ }
+ }
+ return cnt;
+}
+
+
+void
+dhcpleasequery(struct packet *packet, int ms_nulltp) {
+ char msgbuf[256];
+ char dbg_info[128];
+ struct iaddr cip;
+ struct iaddr gip;
+ struct data_string uid;
+ struct hardware h;
+ struct lease *tmp_lease;
+ struct lease *lease;
+ int want_associated_ip;
+ int assoc_ip_cnt;
+ u_int32_t assoc_ips[40]; /* XXXSK: arbitrary maximum number of IPs */
+ const int nassoc_ips = sizeof(assoc_ips) / sizeof(assoc_ips[0]);
+
+ unsigned char dhcpMsgType;
+ const char *dhcp_msg_type_name;
+ struct subnet *subnet;
+ struct group *relay_group;
+ struct option_state *options;
+ struct option_cache *oc;
+ int allow_leasequery;
+ int ignorep;
+ u_int32_t lease_duration;
+ u_int32_t time_renewal;
+ u_int32_t time_rebinding;
+ u_int32_t time_expiry;
+ u_int32_t client_last_transaction_time;
+ struct sockaddr_in to;
+ struct in_addr siaddr;
+ struct data_string prl;
+ struct data_string *prl_ptr;
+
+ int i;
+ struct interface_info *interface;
+
+ /* INSIST(packet != NULL); */
+
+ /*
+ * Prepare log information.
+ */
+ snprintf(msgbuf, sizeof(msgbuf),
+ "DHCPLEASEQUERY from %s", inet_ntoa(packet->raw->giaddr));
+
+ /*
+ * We can't reply if there is no giaddr field.
+ */
+ if (!packet->raw->giaddr.s_addr) {
+ log_info("%s: missing giaddr, ciaddr is %s, no reply sent",
+ msgbuf, inet_ntoa(packet->raw->ciaddr));
+ return;
+ }
+
+ /*
+ * Initially we use the 'giaddr' subnet options scope to determine if
+ * the giaddr-identified relay agent is permitted to perform a
+ * leasequery. The subnet is not required, and may be omitted, in
+ * which case we are essentially interrogating the root options class
+ * to find a globally permit.
+ */
+ gip.len = sizeof(packet->raw->giaddr);
+ memcpy(gip.iabuf, &packet->raw->giaddr, sizeof(packet->raw->giaddr));
+
+ subnet = NULL;
+ find_subnet(&subnet, gip, MDL);
+ if (subnet != NULL)
+ relay_group = subnet->group;
+ else
+ relay_group = root_group;
+
+ subnet_dereference(&subnet, MDL);
+
+ options = NULL;
+ if (!option_state_allocate(&options, MDL)) {
+ log_error("No memory for option state.");
+ log_info("%s: out of memory, no reply sent", msgbuf);
+ return;
+ }
+
+ execute_statements_in_scope(NULL,
+ packet,
+ NULL,
+ NULL,
+ packet->options,
+ options,
+ &global_scope,
+ relay_group,
+ NULL);
+
+ for (i=packet->class_count-1; i>=0; i--) {
+ execute_statements_in_scope(NULL,
+ packet,
+ NULL,
+ NULL,
+ packet->options,
+ options,
+ &global_scope,
+ packet->classes[i]->group,
+ relay_group);
+ }
+
+ /*
+ * Because LEASEQUERY has some privacy concerns, default to deny.
+ */
+ allow_leasequery = 0;
+
+ /*
+ * See if we are authorized to do LEASEQUERY.
+ */
+ oc = lookup_option(&server_universe, options, SV_LEASEQUERY);
+ if (oc != NULL) {
+ allow_leasequery = evaluate_boolean_option_cache(&ignorep,
+ packet, NULL, NULL, packet->options,
+ options, &global_scope, oc, MDL);
+ }
+
+ if (!allow_leasequery) {
+ log_info("%s: LEASEQUERY not allowed, query ignored", msgbuf);
+ option_state_dereference(&options, MDL);
+ return;
+ }
+
+
+ /*
+ * Copy out the client IP address.
+ */
+ cip.len = sizeof(packet->raw->ciaddr);
+ memcpy(cip.iabuf, &packet->raw->ciaddr, sizeof(packet->raw->ciaddr));
+
+ /*
+ * If the client IP address is valid (not all zero), then we
+ * are looking for information about that IP address.
+ */
+ assoc_ip_cnt = 0;
+ lease = tmp_lease = NULL;
+ if (memcmp(cip.iabuf, "\0\0\0", 4)) {
+
+ want_associated_ip = 0;
+
+ snprintf(dbg_info, sizeof(dbg_info), "IP %s", piaddr(cip));
+ find_lease_by_ip_addr(&lease, cip, MDL);
+
+
+ } else {
+
+ want_associated_ip = 1;
+
+ /*
+ * If the client IP address is all zero, then we will
+ * either look up by the client identifier (if we have
+ * one), or by the MAC address.
+ */
+
+ memset(&uid, 0, sizeof(uid));
+ if (get_option(&uid,
+ &dhcp_universe,
+ packet,
+ NULL,
+ NULL,
+ packet->options,
+ NULL,
+ packet->options,
+ &global_scope,
+ DHO_DHCP_CLIENT_IDENTIFIER,
+ MDL)) {
+
+ snprintf(dbg_info,
+ sizeof(dbg_info),
+ "client-id %s",
+ print_hex_1(uid.len, uid.data, 60));
+
+ find_lease_by_uid(&tmp_lease, uid.data, uid.len, MDL);
+ data_string_forget(&uid, MDL);
+ get_newest_lease(&lease, tmp_lease, next_uid);
+ assoc_ip_cnt = get_associated_ips(tmp_lease,
+ next_uid,
+ lease,
+ assoc_ips,
+ nassoc_ips);
+
+ } else {
+
+ if (packet->raw->hlen+1 > sizeof(h.hbuf)) {
+ log_info("%s: hardware length too long, "
+ "no reply sent", msgbuf);
+ option_state_dereference(&options, MDL);
+ return;
+ }
+
+ h.hlen = packet->raw->hlen + 1;
+ h.hbuf[0] = packet->raw->htype;
+ memcpy(&h.hbuf[1],
+ packet->raw->chaddr,
+ packet->raw->hlen);
+
+ snprintf(dbg_info,
+ sizeof(dbg_info),
+ "MAC address %s",
+ print_hw_addr(h.hbuf[0],
+ h.hlen - 1,
+ &h.hbuf[1]));
+
+ find_lease_by_hw_addr(&tmp_lease, h.hbuf, h.hlen, MDL);
+ get_newest_lease(&lease, tmp_lease, next_hw);
+ assoc_ip_cnt = get_associated_ips(tmp_lease,
+ next_hw,
+ lease,
+ assoc_ips,
+ nassoc_ips);
+
+ }
+
+ lease_dereference(&tmp_lease, MDL);
+
+ if (lease != NULL) {
+ memcpy(&packet->raw->ciaddr,
+ lease->ip_addr.iabuf,
+ sizeof(packet->raw->ciaddr));
+ }
+
+ /*
+ * Log if we have too many IP addresses associated
+ * with this client.
+ */
+ if (want_associated_ip && (assoc_ip_cnt > nassoc_ips)) {
+ log_info("%d IP addresses associated with %s, "
+ "only %d sent in reply.",
+ assoc_ip_cnt, dbg_info, nassoc_ips);
+ }
+ }
+
+ /*
+ * We now know the query target too, so can report this in
+ * our log message.
+ */
+ snprintf(msgbuf, sizeof(msgbuf),
+ "DHCPLEASEQUERY from %s for %s",
+ inet_ntoa(packet->raw->giaddr), dbg_info);
+
+ /*
+ * Figure our our return type.
+ */
+ if (lease == NULL) {
+ dhcpMsgType = DHCPLEASEUNKNOWN;
+ dhcp_msg_type_name = "DHCPLEASEUNKNOWN";
+ } else {
+ if (lease->binding_state == FTS_ACTIVE) {
+ dhcpMsgType = DHCPLEASEACTIVE;
+ dhcp_msg_type_name = "DHCPLEASEACTIVE";
+ } else {
+ dhcpMsgType = DHCPLEASEUNASSIGNED;
+ dhcp_msg_type_name = "DHCPLEASEUNASSIGNED";
+ }
+ }
+
+ /*
+ * Set options that only make sense if we have an active lease.
+ */
+
+ if (dhcpMsgType == DHCPLEASEACTIVE)
+ {
+ /*
+ * RFC 4388 uses the PRL to request options for the agent to
+ * receive that are "about" the client. It is confusing
+ * because in some cases it wants to know what was sent to
+ * the client (lease times, adjusted), and in others it wants
+ * to know information the client sent. You're supposed to
+ * know this on a case-by-case basis.
+ *
+ * "Name servers", "domain name", and the like from the relay
+ * agent's scope seems less than useful. Our options are to
+ * restart the option cache from the lease's best point of view
+ * (execute statements from the lease pool's group), or to
+ * simply restart the option cache from empty.
+ *
+ * I think restarting the option cache from empty best
+ * approaches RFC 4388's intent; specific options are included.
+ */
+ option_state_dereference(&options, MDL);
+
+ if (!option_state_allocate(&options, MDL)) {
+ log_error("%s: out of memory, no reply sent", msgbuf);
+ lease_dereference(&lease, MDL);
+ return;
+ }
+
+ /*
+ * Set the hardware address fields.
+ */
+
+ packet->raw->hlen = lease->hardware_addr.hlen - 1;
+ packet->raw->htype = lease->hardware_addr.hbuf[0];
+ memcpy(packet->raw->chaddr,
+ &lease->hardware_addr.hbuf[1],
+ sizeof(packet->raw->chaddr));
+
+ /*
+ * Set client identifier option.
+ */
+ if (lease->uid_len > 0) {
+ if (!add_option(options,
+ DHO_DHCP_CLIENT_IDENTIFIER,
+ lease->uid,
+ lease->uid_len)) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+
+
+ /*
+ * Calculate T1 and T2, the times when the client
+ * tries to extend its lease on its networking
+ * address.
+ * These seem to be hard-coded in ISC DHCP, to 0.5 and
+ * 0.875 of the lease time.
+ */
+
+ lease_duration = lease->ends - lease->starts;
+ time_renewal = lease->starts +
+ (lease_duration / 2);
+ time_rebinding = lease->starts +
+ (lease_duration / 2) +
+ (lease_duration / 4) +
+ (lease_duration / 8);
+
+ if (time_renewal > cur_time) {
+ time_renewal = htonl(time_renewal - cur_time);
+
+ if (!add_option(options,
+ DHO_DHCP_RENEWAL_TIME,
+ &time_renewal,
+ sizeof(time_renewal))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+
+ if (time_rebinding > cur_time) {
+ time_rebinding = htonl(time_rebinding - cur_time);
+
+ if (!add_option(options,
+ DHO_DHCP_REBINDING_TIME,
+ &time_rebinding,
+ sizeof(time_rebinding))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+
+ if (lease->ends > cur_time) {
+ time_expiry = htonl(lease->ends - cur_time);
+
+ if (!add_option(options,
+ DHO_DHCP_LEASE_TIME,
+ &time_expiry,
+ sizeof(time_expiry))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+
+ /* Supply the Vendor-Class-Identifier. */
+ if (lease->scope != NULL) {
+ struct data_string vendor_class;
+
+ memset(&vendor_class, 0, sizeof(vendor_class));
+
+ if (find_bound_string(&vendor_class, lease->scope,
+ "vendor-class-identifier")) {
+ if (!add_option(options,
+ DHO_VENDOR_CLASS_IDENTIFIER,
+ (void *)vendor_class.data,
+ vendor_class.len)) {
+ option_state_dereference(&options,
+ MDL);
+ lease_dereference(&lease, MDL);
+ log_error("%s: error adding vendor "
+ "class identifier, no reply "
+ "sent", msgbuf);
+ data_string_forget(&vendor_class, MDL);
+ return;
+ }
+ data_string_forget(&vendor_class, MDL);
+ }
+ }
+
+ /*
+ * Set the relay agent info.
+ *
+ * Note that because agent info is appended without regard
+ * to the PRL in cons_options(), this will be sent as the
+ * last option in the packet whether it is listed on PRL or
+ * not.
+ */
+
+ if (lease->agent_options != NULL) {
+ int idx = agent_universe.index;
+ struct option_chain_head **tmp1 =
+ (struct option_chain_head **)
+ &(options->universes[idx]);
+ struct option_chain_head *tmp2 =
+ (struct option_chain_head *)
+ lease->agent_options;
+
+ option_chain_head_reference(tmp1, tmp2, MDL);
+ }
+
+ /*
+ * Set the client last transaction time.
+ * We check to make sure we have a timestamp. For
+ * lease files that were saved before running a
+ * timestamp-aware version of the server, this may
+ * not be set.
+ */
+
+ if (lease->cltt != MIN_TIME) {
+ if (cur_time > lease->cltt) {
+ client_last_transaction_time =
+ htonl(cur_time - lease->cltt);
+ } else {
+ client_last_transaction_time = htonl(0);
+ }
+ if (!add_option(options,
+ DHO_CLIENT_LAST_TRANSACTION_TIME,
+ &client_last_transaction_time,
+ sizeof(client_last_transaction_time))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+
+ /*
+ * Set associated IPs, if requested and there are some.
+ */
+ if (want_associated_ip && (assoc_ip_cnt > 0)) {
+ if (!add_option(options,
+ DHO_ASSOCIATED_IP,
+ assoc_ips,
+ assoc_ip_cnt * sizeof(assoc_ips[0]))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+ }
+
+ /*
+ * Set the message type.
+ */
+
+ packet->raw->op = BOOTREPLY;
+
+ /*
+ * Set DHCP message type.
+ */
+ if (!add_option(options,
+ DHO_DHCP_MESSAGE_TYPE,
+ &dhcpMsgType,
+ sizeof(dhcpMsgType))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: error adding option, no reply sent", msgbuf);
+ return;
+ }
+
+ /*
+ * Log the message we've received.
+ */
+ log_info("%s", msgbuf);
+
+ /*
+ * Figure out which address to use to send from.
+ */
+ get_server_source_address(&siaddr, options, options, packet);
+
+ /*
+ * Set up the option buffer.
+ */
+
+ memset(&prl, 0, sizeof(prl));
+ oc = lookup_option(&dhcp_universe, options,
+ DHO_DHCP_PARAMETER_REQUEST_LIST);
+ if (oc != NULL) {
+ evaluate_option_cache(&prl,
+ packet,
+ NULL,
+ NULL,
+ packet->options,
+ options,
+ &global_scope,
+ oc,
+ MDL);
+ }
+ if (prl.len > 0) {
+ prl_ptr = &prl;
+ } else {
+ prl_ptr = NULL;
+ }
+
+ packet->packet_length = cons_options(packet,
+ packet->raw,
+ lease,
+ NULL,
+ 0,
+ packet->options,
+ options,
+ &global_scope,
+ 0,
+ 0,
+ 0,
+ prl_ptr,
+ NULL);
+
+ data_string_forget(&prl, MDL); /* SK: safe, even if empty */
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+
+ to.sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+ to.sin_len = sizeof(to);
+#endif
+ memset(to.sin_zero, 0, sizeof(to.sin_zero));
+
+ /*
+ * Leasequery packets are be sent to the gateway address.
+ */
+ to.sin_addr = packet->raw->giaddr;
+ if (packet->raw->giaddr.s_addr != htonl(INADDR_LOOPBACK)) {
+ to.sin_port = local_port;
+ } else {
+ to.sin_port = remote_port; /* XXXSK: For debugging. */
+ }
+
+ /*
+ * The fallback_interface lets us send with a real IP
+ * address. The packet interface sends from all-zeros.
+ */
+ if (fallback_interface != NULL) {
+ interface = fallback_interface;
+ } else {
+ interface = packet->interface;
+ }
+
+ /*
+ * Report what we're sending.
+ */
+ log_info("%s to %s for %s (%d associated IPs)",
+ dhcp_msg_type_name,
+ inet_ntoa(to.sin_addr), dbg_info, assoc_ip_cnt);
+
+ send_packet(interface,
+ NULL,
+ packet->raw,
+ packet->packet_length,
+ siaddr,
+ &to,
+ NULL);
+}
+
+#ifdef DHCPv6
+
+/*
+ * TODO: RFC5007 query-by-clientid.
+ *
+ * TODO: RFC5007 look at the pools according to the link-address.
+ *
+ * TODO: get fixed leases too.
+ *
+ * TODO: RFC5007 ORO in query-options.
+ *
+ * TODO: RFC5007 lq-relay-data.
+ *
+ * TODO: RFC5007 lq-client-link.
+ *
+ * Note: the code is still nearly compliant and usable for the target
+ * case with these missing features!
+ */
+
+/*
+ * The structure to handle a leasequery.
+ */
+struct lq6_state {
+ struct packet *packet;
+ struct data_string client_id;
+ struct data_string server_id;
+ struct data_string lq_query;
+ uint8_t query_type;
+ struct in6_addr link_addr;
+ struct option_state *query_opts;
+
+ struct option_state *reply_opts;
+ unsigned cursor;
+ union reply_buffer {
+ unsigned char data[65536];
+ struct dhcpv6_packet reply;
+ } buf;
+};
+
+/*
+ * Options that we want to send.
+ */
+static const int required_opts_lq[] = {
+ D6O_CLIENTID,
+ D6O_SERVERID,
+ D6O_STATUS_CODE,
+ D6O_CLIENT_DATA,
+ D6O_LQ_RELAY_DATA,
+ D6O_LQ_CLIENT_LINK,
+ 0
+};
+static const int required_opt_CLIENT_DATA[] = {
+ D6O_CLIENTID,
+ D6O_IAADDR,
+ D6O_IAPREFIX,
+ D6O_CLT_TIME,
+ 0
+};
+
+/*
+ * Get the lq-query option from the packet.
+ */
+static isc_result_t
+get_lq_query(struct lq6_state *lq)
+{
+ struct data_string *lq_query = &lq->lq_query;
+ struct packet *packet = lq->packet;
+ struct option_cache *oc;
+
+ /*
+ * Verify our lq_query structure is empty.
+ */
+ if ((lq_query->data != NULL) || (lq_query->len != 0)) {
+ return DHCP_R_INVALIDARG;
+ }
+
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_LQ_QUERY);
+ if (oc == NULL) {
+ return ISC_R_NOTFOUND;
+ }
+
+ if (!evaluate_option_cache(lq_query, packet, NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ return ISC_R_FAILURE;
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Message validation, RFC 5007 section 4.2.1:
+ * dhcpv6.c:valid_client_msg() - unicast + lq-query option.
+ */
+static int
+valid_query_msg(struct lq6_state *lq) {
+ struct packet *packet = lq->packet;
+ int ret_val = 0;
+ struct option_cache *oc;
+
+ /* INSIST((lq != NULL) || (packet != NULL)); */
+
+ switch (get_client_id(packet, &lq->client_id)) {
+ case ISC_R_SUCCESS:
+ break;
+ case ISC_R_NOTFOUND:
+ log_debug("Discarding %s from %s; "
+ "client identifier missing",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr));
+ goto exit;
+ default:
+ log_error("Error processing %s from %s; "
+ "unable to evaluate Client Identifier",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr));
+ goto exit;
+ }
+
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
+ if (oc != NULL) {
+ if (evaluate_option_cache(&lq->server_id, packet, NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ log_debug("Discarding %s from %s; "
+ "server identifier found "
+ "(CLIENTID %s, SERVERID %s)",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr),
+ print_hex_1(lq->client_id.len,
+ lq->client_id.data, 60),
+ print_hex_2(lq->server_id.len,
+ lq->server_id.data, 60));
+ } else {
+ log_debug("Discarding %s from %s; "
+ "server identifier found "
+ "(CLIENTID %s)",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ print_hex_1(lq->client_id.len,
+ lq->client_id.data, 60),
+ piaddr(packet->client_addr));
+ }
+ goto exit;
+ }
+
+ switch (get_lq_query(lq)) {
+ case ISC_R_SUCCESS:
+ break;
+ case ISC_R_NOTFOUND:
+ log_debug("Discarding %s from %s; lq-query missing",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr));
+ goto exit;
+ default:
+ log_error("Error processing %s from %s; "
+ "unable to evaluate LQ-Query",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr));
+ goto exit;
+ }
+
+ /* looks good */
+ ret_val = 1;
+
+exit:
+ if (!ret_val) {
+ if (lq->client_id.len > 0) {
+ data_string_forget(&lq->client_id, MDL);
+ }
+ if (lq->server_id.len > 0) {
+ data_string_forget(&lq->server_id, MDL);
+ }
+ if (lq->lq_query.len > 0) {
+ data_string_forget(&lq->lq_query, MDL);
+ }
+ }
+ return ret_val;
+}
+
+/*
+ * Set an error in a status-code option (from set_status_code).
+ */
+static int
+set_error(struct lq6_state *lq, u_int16_t code, const char *message) {
+ struct data_string d;
+ int ret_val;
+
+ memset(&d, 0, sizeof(d));
+ d.len = sizeof(code) + strlen(message);
+ if (!buffer_allocate(&d.buffer, d.len, MDL)) {
+ log_fatal("set_error: no memory for status code.");
+ }
+ d.data = d.buffer->data;
+ putUShort(d.buffer->data, code);
+ memcpy(d.buffer->data + sizeof(code), message, d.len - sizeof(code));
+ if (!save_option_buffer(&dhcpv6_universe, lq->reply_opts,
+ d.buffer, (unsigned char *)d.data, d.len,
+ D6O_STATUS_CODE, 0)) {
+ log_error("set_error: error saving status code.");
+ ret_val = 0;
+ } else {
+ ret_val = 1;
+ }
+ data_string_forget(&d, MDL);
+ return ret_val;
+}
+
+/*
+ * Process a by-address lease query.
+ */
+static int
+process_lq_by_address(struct lq6_state *lq) {
+ struct packet *packet = lq->packet;
+ struct option_cache *oc;
+ struct ipv6_pool *pool = NULL;
+ struct data_string data;
+ struct in6_addr addr;
+ struct iasubopt *iaaddr = NULL;
+ struct option_state *opt_state = NULL;
+ u_int32_t lifetime;
+ unsigned opt_cursor;
+ int ret_val = 0;
+
+ /*
+ * Get the IAADDR.
+ */
+ oc = lookup_option(&dhcpv6_universe, lq->query_opts, D6O_IAADDR);
+ if (oc == NULL) {
+ if (!set_error(lq, STATUS_MalformedQuery,
+ "No OPTION_IAADDR.")) {
+ log_error("process_lq_by_address: unable "
+ "to set MalformedQuery status code.");
+ return 0;
+ }
+ return 1;
+ }
+ memset(&data, 0, sizeof(data));
+ if (!evaluate_option_cache(&data, packet,
+ NULL, NULL,
+ lq->query_opts, NULL,
+ &global_scope, oc, MDL) ||
+ (data.len < IAADDR_OFFSET)) {
+ log_error("process_lq_by_address: error evaluating IAADDR.");
+ goto exit;
+ }
+ memcpy(&addr, data.data, sizeof(addr));
+ data_string_forget(&data, MDL);
+
+ /*
+ * Find the lease.
+ * Note the RFC 5007 says to use the link-address to find the link
+ * or the ia-aadr when it is :: but in any case the ia-addr has
+ * to be on the link, so we ignore the link-address here.
+ */
+ if (find_ipv6_pool(&pool, D6O_IA_NA, &addr) != ISC_R_SUCCESS) {
+ if (!set_error(lq, STATUS_NotConfigured,
+ "Address not in a pool.")) {
+ log_error("process_lq_by_address: unable "
+ "to set NotConfigured status code.");
+ goto exit;
+ }
+ ret_val = 1;
+ goto exit;
+ }
+ if (iasubopt_hash_lookup(&iaaddr, pool->leases, &addr,
+ sizeof(addr), MDL) == 0) {
+ ret_val = 1;
+ goto exit;
+ }
+ if ((iaaddr == NULL) || (iaaddr->state != FTS_ACTIVE) ||
+ (iaaddr->ia == NULL) || (iaaddr->ia->iaid_duid.len <= 4)) {
+ ret_val = 1;
+ goto exit;
+ }
+
+ /*
+ * Build the client-data option (with client-id, ia-addr and clt-time).
+ */
+ if (!option_state_allocate(&opt_state, MDL)) {
+ log_error("process_lq_by_address: "
+ "no memory for option state.");
+ goto exit;
+ }
+
+ data_string_copy(&data, &iaaddr->ia->iaid_duid, MDL);
+ data.data += 4;
+ data.len -= 4;
+ if (!save_option_buffer(&dhcpv6_universe, opt_state,
+ NULL, (unsigned char *)data.data, data.len,
+ D6O_CLIENTID, 0)) {
+ log_error("process_lq_by_address: error saving client ID.");
+ goto exit;
+ }
+ data_string_forget(&data, MDL);
+
+ data.len = IAADDR_OFFSET;
+ if (!buffer_allocate(&data.buffer, data.len, MDL)) {
+ log_error("process_lq_by_address: no memory for ia-addr.");
+ goto exit;
+ }
+ data.data = data.buffer->data;
+ memcpy(data.buffer->data, &iaaddr->addr, 16);
+ lifetime = iaaddr->prefer;
+ putULong(data.buffer->data + 16, lifetime);
+ lifetime = iaaddr->valid;
+ putULong(data.buffer->data + 20, lifetime);
+ if (!save_option_buffer(&dhcpv6_universe, opt_state,
+ NULL, (unsigned char *)data.data, data.len,
+ D6O_IAADDR, 0)) {
+ log_error("process_lq_by_address: error saving ia-addr.");
+ goto exit;
+ }
+ data_string_forget(&data, MDL);
+
+ lifetime = htonl(iaaddr->ia->cltt);
+ if (!save_option_buffer(&dhcpv6_universe, opt_state,
+ NULL, (unsigned char *)&lifetime, 4,
+ D6O_CLT_TIME, 0)) {
+ log_error("process_lq_by_address: error saving clt time.");
+ goto exit;
+ }
+
+ /*
+ * Store the client-data option.
+ */
+ opt_cursor = lq->cursor;
+ putUShort(lq->buf.data + lq->cursor, (unsigned)D6O_CLIENT_DATA);
+ lq->cursor += 2;
+ /* Skip option length. */
+ lq->cursor += 2;
+
+ lq->cursor += store_options6((char *)lq->buf.data + lq->cursor,
+ sizeof(lq->buf) - lq->cursor,
+ opt_state, lq->packet,
+ required_opt_CLIENT_DATA, NULL);
+ /* Reset the length. */
+ putUShort(lq->buf.data + opt_cursor + 2,
+ lq->cursor - (opt_cursor + 4));
+
+ /* Done. */
+ ret_val = 1;
+
+ exit:
+ if (data.data != NULL)
+ data_string_forget(&data, MDL);
+ if (pool != NULL)
+ ipv6_pool_dereference(&pool, MDL);
+ if (iaaddr != NULL)
+ iasubopt_dereference(&iaaddr, MDL);
+ if (opt_state != NULL)
+ option_state_dereference(&opt_state, MDL);
+ return ret_val;
+}
+
+
+/*
+ * Process a lease query.
+ */
+void
+dhcpv6_leasequery(struct data_string *reply_ret, struct packet *packet) {
+ static struct lq6_state lq;
+ struct option_cache *oc;
+ int allow_lq;
+
+ /*
+ * Initialize the lease query state.
+ */
+ lq.packet = NULL;
+ memset(&lq.client_id, 0, sizeof(lq.client_id));
+ memset(&lq.server_id, 0, sizeof(lq.server_id));
+ memset(&lq.lq_query, 0, sizeof(lq.lq_query));
+ lq.query_opts = NULL;
+ lq.reply_opts = NULL;
+ packet_reference(&lq.packet, packet, MDL);
+
+ /*
+ * Validate our input.
+ */
+ if (!valid_query_msg(&lq)) {
+ goto exit;
+ }
+
+ /*
+ * Prepare our reply.
+ */
+ if (!option_state_allocate(&lq.reply_opts, MDL)) {
+ log_error("dhcpv6_leasequery: no memory for option state.");
+ goto exit;
+ }
+ execute_statements_in_scope(NULL, lq.packet, NULL, NULL,
+ lq.packet->options, lq.reply_opts,
+ &global_scope, root_group, NULL);
+
+ lq.buf.reply.msg_type = DHCPV6_LEASEQUERY_REPLY;
+
+ memcpy(lq.buf.reply.transaction_id,
+ lq.packet->dhcpv6_transaction_id,
+ sizeof(lq.buf.reply.transaction_id));
+
+ /*
+ * Because LEASEQUERY has some privacy concerns, default to deny.
+ */
+ allow_lq = 0;
+
+ /*
+ * See if we are authorized to do LEASEQUERY.
+ */
+ oc = lookup_option(&server_universe, lq.reply_opts, SV_LEASEQUERY);
+ if (oc != NULL) {
+ allow_lq = evaluate_boolean_option_cache(NULL,
+ lq.packet,
+ NULL, NULL,
+ lq.packet->options,
+ lq.reply_opts,
+ &global_scope,
+ oc, MDL);
+ }
+
+ if (!allow_lq) {
+ log_info("dhcpv6_leasequery: not allowed, query ignored.");
+ goto exit;
+ }
+
+ /*
+ * Same than transmission of REPLY message in RFC 3315:
+ * server-id
+ * client-id
+ */
+
+ oc = lookup_option(&dhcpv6_universe, lq.reply_opts, D6O_SERVERID);
+ if (oc == NULL) {
+ /* If not already in options, get from query then global. */
+ if (lq.server_id.data == NULL)
+ copy_server_duid(&lq.server_id, MDL);
+ if (!save_option_buffer(&dhcpv6_universe,
+ lq.reply_opts,
+ NULL,
+ (unsigned char *)lq.server_id.data,
+ lq.server_id.len,
+ D6O_SERVERID,
+ 0)) {
+ log_error("dhcpv6_leasequery: "
+ "error saving server identifier.");
+ goto exit;
+ }
+ }
+
+ if (!save_option_buffer(&dhcpv6_universe,
+ lq.reply_opts,
+ lq.client_id.buffer,
+ (unsigned char *)lq.client_id.data,
+ lq.client_id.len,
+ D6O_CLIENTID,
+ 0)) {
+ log_error("dhcpv6_leasequery: "
+ "error saving client identifier.");
+ goto exit;
+ }
+
+ lq.cursor = 4;
+
+ /*
+ * Decode the lq-query option.
+ */
+
+ if (lq.lq_query.len <= LQ_QUERY_OFFSET) {
+ if (!set_error(&lq, STATUS_MalformedQuery,
+ "OPTION_LQ_QUERY too short.")) {
+ log_error("dhcpv6_leasequery: unable "
+ "to set MalformedQuery status code.");
+ goto exit;
+ }
+ goto done;
+ }
+
+ lq.query_type = lq.lq_query.data [0];
+ memcpy(&lq.link_addr, lq.lq_query.data + 1, sizeof(lq.link_addr));
+ switch (lq.query_type) {
+ case LQ6QT_BY_ADDRESS:
+ break;
+ case LQ6QT_BY_CLIENTID:
+ if (!set_error(&lq, STATUS_UnknownQueryType,
+ "QUERY_BY_CLIENTID not supported.")) {
+ log_error("dhcpv6_leasequery: unable to "
+ "set UnknownQueryType status code.");
+ goto exit;
+ }
+ goto done;
+ default:
+ if (!set_error(&lq, STATUS_UnknownQueryType,
+ "Unknown query-type.")) {
+ log_error("dhcpv6_leasequery: unable to "
+ "set UnknownQueryType status code.");
+ goto exit;
+ }
+ goto done;
+ }
+
+ if (!option_state_allocate(&lq.query_opts, MDL)) {
+ log_error("dhcpv6_leasequery: no memory for option state.");
+ goto exit;
+ }
+ if (!parse_option_buffer(lq.query_opts,
+ lq.lq_query.data + LQ_QUERY_OFFSET,
+ lq.lq_query.len - LQ_QUERY_OFFSET,
+ &dhcpv6_universe)) {
+ log_error("dhcpv6_leasequery: error parsing query-options.");
+ if (!set_error(&lq, STATUS_MalformedQuery,
+ "Bad query-options.")) {
+ log_error("dhcpv6_leasequery: unable "
+ "to set MalformedQuery status code.");
+ goto exit;
+ }
+ goto done;
+ }
+
+ /* Do it. */
+ if (!process_lq_by_address(&lq))
+ goto exit;
+
+ done:
+ /* Store the options. */
+ lq.cursor += store_options6((char *)lq.buf.data + lq.cursor,
+ sizeof(lq.buf) - lq.cursor,
+ lq.reply_opts,
+ lq.packet,
+ required_opts_lq,
+ NULL);
+
+ /* Return our reply to the caller. */
+ reply_ret->len = lq.cursor;
+ reply_ret->buffer = NULL;
+ if (!buffer_allocate(&reply_ret->buffer, lq.cursor, MDL)) {
+ log_fatal("dhcpv6_leasequery: no memory to store Reply.");
+ }
+ memcpy(reply_ret->buffer->data, lq.buf.data, lq.cursor);
+ reply_ret->data = reply_ret->buffer->data;
+
+ exit:
+ /* Cleanup. */
+ if (lq.packet != NULL)
+ packet_dereference(&lq.packet, MDL);
+ if (lq.client_id.data != NULL)
+ data_string_forget(&lq.client_id, MDL);
+ if (lq.server_id.data != NULL)
+ data_string_forget(&lq.server_id, MDL);
+ if (lq.lq_query.data != NULL)
+ data_string_forget(&lq.lq_query, MDL);
+ if (lq.query_opts != NULL)
+ option_state_dereference(&lq.query_opts, MDL);
+ if (lq.reply_opts != NULL)
+ option_state_dereference(&lq.reply_opts, MDL);
+}
+
+#endif /* DHCPv6 */
diff --git a/server/dhcpv6.c b/server/dhcpv6.c
new file mode 100644
index 0000000..c6f1cf7
--- /dev/null
+++ b/server/dhcpv6.c
@@ -0,0 +1,6235 @@
+/*
+ * Copyright (C) 2006-2014 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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.
+ */
+
+#include "dhcpd.h"
+
+#ifdef DHCPv6
+
+/*
+ * We use print_hex_1() to output DUID values. We could actually output
+ * the DUID with more information... MAC address if using type 1 or 3,
+ * and so on. However, RFC 3315 contains Grave Warnings against actually
+ * attempting to understand a DUID.
+ */
+
+/*
+ * TODO: gettext() or other method of localization for the messages
+ * for status codes (and probably for log formats eventually)
+ * TODO: refactoring (simplify, simplify, simplify)
+ * TODO: support multiple shared_networks on each interface (this
+ * will allow the server to issue multiple IPv6 addresses to
+ * a single interface)
+ */
+
+/*
+ * DHCPv6 Reply workflow assist. A Reply packet is built by various
+ * different functions; this gives us one location where we keep state
+ * regarding a reply.
+ */
+struct reply_state {
+ /* root level persistent state */
+ struct shared_network *shared;
+ struct host_decl *host;
+ struct subnet *subnet; /* Used to match fixed-addrs to subnet scopes. */
+ struct option_state *opt_state;
+ struct packet *packet;
+ struct data_string client_id;
+
+ /* IA level persistent state */
+ unsigned ia_count;
+ unsigned pd_count;
+ unsigned client_resources;
+ isc_boolean_t resources_included;
+ isc_boolean_t static_lease;
+ unsigned static_prefixes;
+ struct ia_xx *ia;
+ struct ia_xx *old_ia;
+ struct option_state *reply_ia;
+ struct data_string fixed;
+ struct iaddrcidrnet fixed_pref; /* static prefix for logging */
+
+ /* IAADDR/PREFIX level persistent state */
+ struct iasubopt *lease;
+
+ /*
+ * "t1", "t2", preferred, and valid lifetimes records for calculating
+ * t1 and t2 (min/max).
+ */
+ u_int32_t renew, rebind, prefer, valid;
+
+ /* Client-requested valid and preferred lifetimes. */
+ u_int32_t client_valid, client_prefer;
+
+ /* Chosen values to transmit for valid and preferred lifetimes. */
+ u_int32_t send_valid, send_prefer;
+
+ /* Preferred prefix length (-1 is any). */
+ int preflen;
+
+ /* Index into the data field that has been consumed. */
+ unsigned cursor;
+
+ union reply_buffer {
+ unsigned char data[65536];
+ struct dhcpv6_packet reply;
+ } buf;
+};
+
+/*
+ * Prototypes local to this file.
+ */
+static int get_encapsulated_IA_state(struct option_state **enc_opt_state,
+ struct data_string *enc_opt_data,
+ struct packet *packet,
+ struct option_cache *oc,
+ int offset);
+static void build_dhcpv6_reply(struct data_string *, struct packet *);
+static isc_result_t shared_network_from_packet6(struct shared_network **shared,
+ struct packet *packet);
+static void seek_shared_host(struct host_decl **hp,
+ struct shared_network *shared);
+static isc_boolean_t fixed_matches_shared(struct host_decl *host,
+ struct shared_network *shared);
+static isc_result_t reply_process_ia_na(struct reply_state *reply,
+ struct option_cache *ia);
+static isc_result_t reply_process_ia_ta(struct reply_state *reply,
+ struct option_cache *ia);
+static isc_result_t reply_process_addr(struct reply_state *reply,
+ struct option_cache *addr);
+static isc_boolean_t address_is_owned(struct reply_state *reply,
+ struct iaddr *addr);
+static isc_boolean_t temporary_is_available(struct reply_state *reply,
+ struct iaddr *addr);
+static isc_result_t find_client_temporaries(struct reply_state *reply);
+static isc_result_t reply_process_try_addr(struct reply_state *reply,
+ struct iaddr *addr);
+static isc_result_t find_client_address(struct reply_state *reply);
+static isc_result_t reply_process_is_addressed(struct reply_state *reply,
+ struct binding_scope **scope,
+ struct group *group);
+static isc_result_t reply_process_send_addr(struct reply_state *reply,
+ struct iaddr *addr);
+static struct iasubopt *lease_compare(struct iasubopt *alpha,
+ struct iasubopt *beta);
+static isc_result_t reply_process_ia_pd(struct reply_state *reply,
+ struct option_cache *ia_pd);
+static isc_result_t reply_process_prefix(struct reply_state *reply,
+ struct option_cache *pref);
+static isc_boolean_t prefix_is_owned(struct reply_state *reply,
+ struct iaddrcidrnet *pref);
+static isc_result_t find_client_prefix(struct reply_state *reply);
+static isc_result_t reply_process_try_prefix(struct reply_state *reply,
+ struct iaddrcidrnet *pref);
+static isc_result_t reply_process_is_prefixed(struct reply_state *reply,
+ struct binding_scope **scope,
+ struct group *group);
+static isc_result_t reply_process_send_prefix(struct reply_state *reply,
+ struct iaddrcidrnet *pref);
+static struct iasubopt *prefix_compare(struct reply_state *reply,
+ struct iasubopt *alpha,
+ struct iasubopt *beta);
+static int find_hosts_by_duid_chaddr(struct host_decl **host,
+ const struct data_string *client_id);
+static void schedule_lease_timeout_reply(struct reply_state *reply);
+
+/*
+ * Schedule lease timeouts for all of the iasubopts in the reply.
+ * This is currently used to schedule timeouts for soft leases.
+ */
+
+static void
+schedule_lease_timeout_reply(struct reply_state *reply) {
+ struct iasubopt *tmp;
+ int i;
+
+ /* sanity check the reply */
+ if ((reply == NULL) || (reply->ia == NULL) || (reply->ia->iasubopt == NULL))
+ return;
+
+ /* walk through the list, scheduling as we go */
+ for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
+ tmp = reply->ia->iasubopt[i];
+ schedule_lease_timeout(tmp->ipv6_pool);
+ }
+}
+
+/*
+ * This function returns the time since DUID time start for the
+ * given time_t value.
+ */
+static u_int32_t
+duid_time(time_t when) {
+ /*
+ * This time is modulo 2^32.
+ */
+ while ((when - DUID_TIME_EPOCH) > 4294967295u) {
+ /* use 2^31 to avoid spurious compiler warnings */
+ when -= 2147483648u;
+ when -= 2147483648u;
+ }
+
+ return when - DUID_TIME_EPOCH;
+}
+
+
+/*
+ * Server DUID.
+ *
+ * This must remain the same for the lifetime of this server, because
+ * clients return the server DUID that we sent them in Request packets.
+ *
+ * We pick the server DUID like this:
+ *
+ * 1. Check dhcpd.conf - any value the administrator has configured
+ * overrides any possible values.
+ * 2. Check the leases.txt - we want to use the previous value if
+ * possible.
+ * 3. Check if dhcpd.conf specifies a type of server DUID to use,
+ * and generate that type.
+ * 4. Generate a type 1 (time + hardware address) DUID.
+ */
+static struct data_string server_duid;
+
+/*
+ * Check if the server_duid has been set.
+ */
+isc_boolean_t
+server_duid_isset(void) {
+ return (server_duid.data != NULL);
+}
+
+/*
+ * Return the server_duid.
+ */
+void
+copy_server_duid(struct data_string *ds, const char *file, int line) {
+ data_string_copy(ds, &server_duid, file, line);
+}
+
+/*
+ * Set the server DUID to a specified value. This is used when
+ * the server DUID is stored in persistent memory (basically the
+ * leases.txt file).
+ */
+void
+set_server_duid(struct data_string *new_duid) {
+ /* INSIST(new_duid != NULL); */
+ /* INSIST(new_duid->data != NULL); */
+
+ if (server_duid_isset()) {
+ data_string_forget(&server_duid, MDL);
+ }
+ data_string_copy(&server_duid, new_duid, MDL);
+}
+
+
+/*
+ * Set the server DUID based on the D6O_SERVERID option. This handles
+ * the case where the administrator explicitly put it in the dhcpd.conf
+ * file.
+ */
+isc_result_t
+set_server_duid_from_option(void) {
+ struct option_state *opt_state;
+ struct option_cache *oc;
+ struct data_string option_duid;
+ isc_result_t ret_val;
+
+ opt_state = NULL;
+ if (!option_state_allocate(&opt_state, MDL)) {
+ log_fatal("No memory for server DUID.");
+ }
+
+ execute_statements_in_scope(NULL, NULL, NULL, NULL, NULL,
+ opt_state, &global_scope, root_group, NULL);
+
+ oc = lookup_option(&dhcpv6_universe, opt_state, D6O_SERVERID);
+ if (oc == NULL) {
+ ret_val = ISC_R_NOTFOUND;
+ } else {
+ memset(&option_duid, 0, sizeof(option_duid));
+ if (!evaluate_option_cache(&option_duid, NULL, NULL, NULL,
+ opt_state, NULL, &global_scope,
+ oc, MDL)) {
+ ret_val = ISC_R_UNEXPECTED;
+ } else {
+ set_server_duid(&option_duid);
+ data_string_forget(&option_duid, MDL);
+ ret_val = ISC_R_SUCCESS;
+ }
+ }
+
+ option_state_dereference(&opt_state, MDL);
+
+ return ret_val;
+}
+
+/*
+ * DUID layout, as defined in RFC 3315, section 9.
+ *
+ * We support type 1 (hardware address plus time) and type 3 (hardware
+ * address).
+ *
+ * We can support type 2 for specific vendors in the future, if they
+ * publish the specification. And of course there may be additional
+ * types later.
+ */
+static int server_duid_type = DUID_LLT;
+
+/*
+ * Set the DUID type.
+ */
+void
+set_server_duid_type(int type) {
+ server_duid_type = type;
+}
+
+/*
+ * Generate a new server DUID. This is done if there was no DUID in
+ * the leases.txt or in the dhcpd.conf file.
+ */
+isc_result_t
+generate_new_server_duid(void) {
+ struct interface_info *p;
+ u_int32_t time_val;
+ struct data_string generated_duid;
+
+ /*
+ * Verify we have a type that we support.
+ */
+ if ((server_duid_type != DUID_LL) && (server_duid_type != DUID_LLT)) {
+ log_error("Invalid DUID type %d specified, "
+ "only LL and LLT types supported", server_duid_type);
+ return DHCP_R_INVALIDARG;
+ }
+
+ /*
+ * Find an interface with a hardware address.
+ * Any will do. :)
+ */
+ for (p = interfaces; p != NULL; p = p->next) {
+ if (p->hw_address.hlen > 0) {
+ break;
+ }
+ }
+ if (p == NULL) {
+ return ISC_R_UNEXPECTED;
+ }
+
+ /*
+ * Build our DUID.
+ */
+ memset(&generated_duid, 0, sizeof(generated_duid));
+ if (server_duid_type == DUID_LLT) {
+ time_val = duid_time(time(NULL));
+ generated_duid.len = 8 + p->hw_address.hlen - 1;
+ if (!buffer_allocate(&generated_duid.buffer,
+ generated_duid.len, MDL)) {
+ log_fatal("No memory for server DUID.");
+ }
+ generated_duid.data = generated_duid.buffer->data;
+ putUShort(generated_duid.buffer->data, DUID_LLT);
+ putUShort(generated_duid.buffer->data + 2,
+ p->hw_address.hbuf[0]);
+ putULong(generated_duid.buffer->data + 4, time_val);
+ memcpy(generated_duid.buffer->data + 8,
+ p->hw_address.hbuf+1, p->hw_address.hlen-1);
+ } else if (server_duid_type == DUID_LL) {
+ generated_duid.len = 4 + p->hw_address.hlen - 1;
+ if (!buffer_allocate(&generated_duid.buffer,
+ generated_duid.len, MDL)) {
+ log_fatal("No memory for server DUID.");
+ }
+ generated_duid.data = generated_duid.buffer->data;
+ putUShort(generated_duid.buffer->data, DUID_LL);
+ putUShort(generated_duid.buffer->data + 2,
+ p->hw_address.hbuf[0]);
+ memcpy(generated_duid.buffer->data + 4,
+ p->hw_address.hbuf+1, p->hw_address.hlen-1);
+ } else {
+ log_fatal("Unsupported server DUID type %d.", server_duid_type);
+ }
+
+ set_server_duid(&generated_duid);
+ data_string_forget(&generated_duid, MDL);
+
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Get the client identifier from the packet.
+ */
+isc_result_t
+get_client_id(struct packet *packet, struct data_string *client_id) {
+ struct option_cache *oc;
+
+ /*
+ * Verify our client_id structure is empty.
+ */
+ if ((client_id->data != NULL) || (client_id->len != 0)) {
+ return DHCP_R_INVALIDARG;
+ }
+
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_CLIENTID);
+ if (oc == NULL) {
+ return ISC_R_NOTFOUND;
+ }
+
+ if (!evaluate_option_cache(client_id, packet, NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ return ISC_R_FAILURE;
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Message validation, defined in RFC 3315, sections 15.2, 15.5, 15.7:
+ *
+ * Servers MUST discard any Solicit messages that do not include a
+ * Client Identifier option or that do include a Server Identifier
+ * option.
+ */
+int
+valid_client_msg(struct packet *packet, struct data_string *client_id) {
+ int ret_val;
+ struct option_cache *oc;
+ struct data_string data;
+
+ ret_val = 0;
+ memset(client_id, 0, sizeof(*client_id));
+ memset(&data, 0, sizeof(data));
+
+ switch (get_client_id(packet, client_id)) {
+ case ISC_R_SUCCESS:
+ break;
+ case ISC_R_NOTFOUND:
+ log_debug("Discarding %s from %s; "
+ "client identifier missing",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr));
+ goto exit;
+ default:
+ log_error("Error processing %s from %s; "
+ "unable to evaluate Client Identifier",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr));
+ goto exit;
+ }
+
+ /*
+ * Required by RFC 3315, section 15.
+ */
+ if (packet->unicast) {
+ log_debug("Discarding %s from %s; packet sent unicast "
+ "(CLIENTID %s)",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr),
+ print_hex_1(client_id->len, client_id->data, 60));
+ goto exit;
+ }
+
+
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
+ if (oc != NULL) {
+ if (evaluate_option_cache(&data, packet, NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ log_debug("Discarding %s from %s; "
+ "server identifier found "
+ "(CLIENTID %s, SERVERID %s)",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr),
+ print_hex_1(client_id->len,
+ client_id->data, 60),
+ print_hex_2(data.len,
+ data.data, 60));
+ } else {
+ log_debug("Discarding %s from %s; "
+ "server identifier found "
+ "(CLIENTID %s)",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ print_hex_1(client_id->len,
+ client_id->data, 60),
+ piaddr(packet->client_addr));
+ }
+ goto exit;
+ }
+
+ /* looks good */
+ ret_val = 1;
+
+exit:
+ if (data.len > 0) {
+ data_string_forget(&data, MDL);
+ }
+ if (!ret_val) {
+ if (client_id->len > 0) {
+ data_string_forget(client_id, MDL);
+ }
+ }
+ return ret_val;
+}
+
+/*
+ * Response validation, defined in RFC 3315, sections 15.4, 15.6, 15.8,
+ * 15.9 (slightly different wording, but same meaning):
+ *
+ * Servers MUST discard any received Request message that meet any of
+ * the following conditions:
+ *
+ * - the message does not include a Server Identifier option.
+ * - the contents of the Server Identifier option do not match the
+ * server's DUID.
+ * - the message does not include a Client Identifier option.
+ */
+int
+valid_client_resp(struct packet *packet,
+ struct data_string *client_id,
+ struct data_string *server_id)
+{
+ int ret_val;
+ struct option_cache *oc;
+
+ /* INSIST((duid.data != NULL) && (duid.len > 0)); */
+
+ ret_val = 0;
+ memset(client_id, 0, sizeof(*client_id));
+ memset(server_id, 0, sizeof(*server_id));
+
+ switch (get_client_id(packet, client_id)) {
+ case ISC_R_SUCCESS:
+ break;
+ case ISC_R_NOTFOUND:
+ log_debug("Discarding %s from %s; "
+ "client identifier missing",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr));
+ goto exit;
+ default:
+ log_error("Error processing %s from %s; "
+ "unable to evaluate Client Identifier",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr));
+ goto exit;
+ }
+
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
+ if (oc == NULL) {
+ log_debug("Discarding %s from %s: "
+ "server identifier missing (CLIENTID %s)",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr),
+ print_hex_1(client_id->len, client_id->data, 60));
+ goto exit;
+ }
+ if (!evaluate_option_cache(server_id, packet, NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ log_error("Error processing %s from %s; "
+ "unable to evaluate Server Identifier (CLIENTID %s)",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr),
+ print_hex_1(client_id->len, client_id->data, 60));
+ goto exit;
+ }
+ if ((server_duid.len != server_id->len) ||
+ (memcmp(server_duid.data, server_id->data, server_duid.len) != 0)) {
+ log_debug("Discarding %s from %s; "
+ "not our server identifier "
+ "(CLIENTID %s, SERVERID %s, server DUID %s)",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr),
+ print_hex_1(client_id->len, client_id->data, 60),
+ print_hex_2(server_id->len, server_id->data, 60),
+ print_hex_3(server_duid.len, server_duid.data, 60));
+ goto exit;
+ }
+
+ /* looks good */
+ ret_val = 1;
+
+exit:
+ if (!ret_val) {
+ if (server_id->len > 0) {
+ data_string_forget(server_id, MDL);
+ }
+ if (client_id->len > 0) {
+ data_string_forget(client_id, MDL);
+ }
+ }
+ return ret_val;
+}
+
+/*
+ * Information request validation, defined in RFC 3315, section 15.12:
+ *
+ * Servers MUST discard any received Information-request message that
+ * meets any of the following conditions:
+ *
+ * - The message includes a Server Identifier option and the DUID in
+ * the option does not match the server's DUID.
+ *
+ * - The message includes an IA option.
+ */
+int
+valid_client_info_req(struct packet *packet, struct data_string *server_id) {
+ int ret_val;
+ struct option_cache *oc;
+ struct data_string client_id;
+ char client_id_str[80]; /* print_hex_1() uses maximum 60 characters,
+ plus a few more for extra information */
+
+ ret_val = 0;
+ memset(server_id, 0, sizeof(*server_id));
+ memset(&client_id, 0, sizeof(client_id));
+
+ /*
+ * Make a string that we can print out to give more
+ * information about the client if we need to.
+ *
+ * By RFC 3315, Section 18.1.5 clients SHOULD have a
+ * client-id on an Information-request packet, but it
+ * is not strictly necessary.
+ */
+ if (get_client_id(packet, &client_id) == ISC_R_SUCCESS) {
+ snprintf(client_id_str, sizeof(client_id_str), " (CLIENTID %s)",
+ print_hex_1(client_id.len, client_id.data, 60));
+ data_string_forget(&client_id, MDL);
+ } else {
+ client_id_str[0] = '\0';
+ }
+
+ /*
+ * Required by RFC 3315, section 15.
+ */
+ if (packet->unicast) {
+ log_debug("Discarding %s from %s; packet sent unicast%s",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr), client_id_str);
+ goto exit;
+ }
+
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
+ if (oc != NULL) {
+ log_debug("Discarding %s from %s; "
+ "IA_NA option present%s",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr), client_id_str);
+ goto exit;
+ }
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA);
+ if (oc != NULL) {
+ log_debug("Discarding %s from %s; "
+ "IA_TA option present%s",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr), client_id_str);
+ goto exit;
+ }
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
+ if (oc != NULL) {
+ log_debug("Discarding %s from %s; "
+ "IA_PD option present%s",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr), client_id_str);
+ goto exit;
+ }
+
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
+ if (oc != NULL) {
+ if (!evaluate_option_cache(server_id, packet, NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ log_error("Error processing %s from %s; "
+ "unable to evaluate Server Identifier%s",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr), client_id_str);
+ goto exit;
+ }
+ if ((server_duid.len != server_id->len) ||
+ (memcmp(server_duid.data, server_id->data,
+ server_duid.len) != 0)) {
+ log_debug("Discarding %s from %s; "
+ "not our server identifier "
+ "(SERVERID %s, server DUID %s)%s",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr),
+ print_hex_1(server_id->len,
+ server_id->data, 60),
+ print_hex_2(server_duid.len,
+ server_duid.data, 60),
+ client_id_str);
+ goto exit;
+ }
+ }
+
+ /* looks good */
+ ret_val = 1;
+
+exit:
+ if (!ret_val) {
+ if (server_id->len > 0) {
+ data_string_forget(server_id, MDL);
+ }
+ }
+ return ret_val;
+}
+
+/*
+ * Options that we want to send, in addition to what was requested
+ * via the ORO.
+ */
+static const int required_opts[] = {
+ D6O_CLIENTID,
+ D6O_SERVERID,
+ D6O_STATUS_CODE,
+ D6O_PREFERENCE,
+ 0
+};
+#if defined (RFC3315_PRE_ERRATA_2010_08)
+static const int required_opts_NAA[] = {
+ D6O_CLIENTID,
+ D6O_SERVERID,
+ D6O_STATUS_CODE,
+ 0
+};
+#endif /* defined (RFC3315_PRE_ERRATA_2010_08) */
+static const int required_opts_solicit[] = {
+ D6O_CLIENTID,
+ D6O_SERVERID,
+ D6O_IA_NA,
+ D6O_IA_TA,
+ D6O_IA_PD,
+ D6O_RAPID_COMMIT,
+ D6O_STATUS_CODE,
+ D6O_RECONF_ACCEPT,
+ D6O_PREFERENCE,
+ 0
+};
+static const int required_opts_agent[] = {
+ D6O_INTERFACE_ID,
+ D6O_RELAY_MSG,
+ 0
+};
+static const int required_opts_IA[] = {
+ D6O_IAADDR,
+ D6O_STATUS_CODE,
+ 0
+};
+static const int required_opts_IA_PD[] = {
+ D6O_IAPREFIX,
+ D6O_STATUS_CODE,
+ 0
+};
+static const int required_opts_STATUS_CODE[] = {
+ D6O_STATUS_CODE,
+ 0
+};
+
+/*
+ * Extracts from packet contents an IA_* option, storing the IA structure
+ * in its entirety in enc_opt_data, and storing any decoded DHCPv6 options
+ * in enc_opt_state for later lookup and evaluation. The 'offset' indicates
+ * where in the IA_* the DHCPv6 options commence.
+ */
+static int
+get_encapsulated_IA_state(struct option_state **enc_opt_state,
+ struct data_string *enc_opt_data,
+ struct packet *packet,
+ struct option_cache *oc,
+ int offset)
+{
+ /*
+ * Get the raw data for the encapsulated options.
+ */
+ memset(enc_opt_data, 0, sizeof(*enc_opt_data));
+ if (!evaluate_option_cache(enc_opt_data, packet,
+ NULL, NULL, packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ log_error("get_encapsulated_IA_state: "
+ "error evaluating raw option.");
+ return 0;
+ }
+ if (enc_opt_data->len < offset) {
+ log_error("get_encapsulated_IA_state: raw option too small.");
+ data_string_forget(enc_opt_data, MDL);
+ return 0;
+ }
+
+ /*
+ * Now create the option state structure, and pass it to the
+ * function that parses options.
+ */
+ *enc_opt_state = NULL;
+ if (!option_state_allocate(enc_opt_state, MDL)) {
+ log_error("get_encapsulated_IA_state: no memory for options.");
+ data_string_forget(enc_opt_data, MDL);
+ return 0;
+ }
+ if (!parse_option_buffer(*enc_opt_state,
+ enc_opt_data->data + offset,
+ enc_opt_data->len - offset,
+ &dhcpv6_universe)) {
+ log_error("get_encapsulated_IA_state: error parsing options.");
+ option_state_dereference(enc_opt_state, MDL);
+ data_string_forget(enc_opt_data, MDL);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+set_status_code(u_int16_t status_code, const char *status_message,
+ struct option_state *opt_state)
+{
+ struct data_string d;
+ int ret_val;
+
+ memset(&d, 0, sizeof(d));
+ d.len = sizeof(status_code) + strlen(status_message);
+ if (!buffer_allocate(&d.buffer, d.len, MDL)) {
+ log_fatal("set_status_code: no memory for status code.");
+ }
+ d.data = d.buffer->data;
+ putUShort(d.buffer->data, status_code);
+ memcpy(d.buffer->data + sizeof(status_code),
+ status_message, d.len - sizeof(status_code));
+ if (!save_option_buffer(&dhcpv6_universe, opt_state,
+ d.buffer, (unsigned char *)d.data, d.len,
+ D6O_STATUS_CODE, 0)) {
+ log_error("set_status_code: error saving status code.");
+ ret_val = 0;
+ } else {
+ ret_val = 1;
+ }
+ data_string_forget(&d, MDL);
+ return ret_val;
+}
+
+/*
+ * We have a set of operations we do to set up the reply packet, which
+ * is the same for many message types.
+ */
+static int
+start_reply(struct packet *packet,
+ const struct data_string *client_id,
+ const struct data_string *server_id,
+ struct option_state **opt_state,
+ struct dhcpv6_packet *reply)
+{
+ struct option_cache *oc;
+ const unsigned char *server_id_data;
+ int server_id_len;
+
+ /*
+ * Build our option state for reply.
+ */
+ *opt_state = NULL;
+ if (!option_state_allocate(opt_state, MDL)) {
+ log_error("start_reply: no memory for option_state.");
+ return 0;
+ }
+ execute_statements_in_scope(NULL, packet, NULL, NULL,
+ packet->options, *opt_state,
+ &global_scope, root_group, NULL);
+
+ /*
+ * A small bit of special handling for Solicit messages.
+ *
+ * We could move the logic into a flag, but for now just check
+ * explicitly.
+ */
+ if (packet->dhcpv6_msg_type == DHCPV6_SOLICIT) {
+ reply->msg_type = DHCPV6_ADVERTISE;
+
+ /*
+ * If:
+ * - this message type supports rapid commit (Solicit), and
+ * - the server is configured to supply a rapid commit, and
+ * - the client requests a rapid commit,
+ * Then we add a rapid commit option, and send Reply (instead
+ * of an Advertise).
+ */
+ oc = lookup_option(&dhcpv6_universe,
+ *opt_state, D6O_RAPID_COMMIT);
+ if (oc != NULL) {
+ oc = lookup_option(&dhcpv6_universe,
+ packet->options, D6O_RAPID_COMMIT);
+ if (oc != NULL) {
+ /* Rapid-commit in action. */
+ reply->msg_type = DHCPV6_REPLY;
+ } else {
+ /* Don't want a rapid-commit in advertise. */
+ delete_option(&dhcpv6_universe,
+ *opt_state, D6O_RAPID_COMMIT);
+ }
+ }
+ } else {
+ reply->msg_type = DHCPV6_REPLY;
+ /* Delete the rapid-commit from the sent options. */
+ oc = lookup_option(&dhcpv6_universe,
+ *opt_state, D6O_RAPID_COMMIT);
+ if (oc != NULL) {
+ delete_option(&dhcpv6_universe,
+ *opt_state, D6O_RAPID_COMMIT);
+ }
+ }
+
+ /*
+ * Use the client's transaction identifier for the reply.
+ */
+ memcpy(reply->transaction_id, packet->dhcpv6_transaction_id,
+ sizeof(reply->transaction_id));
+
+ /*
+ * RFC 3315, section 18.2 says we need server identifier and
+ * client identifier.
+ *
+ * If the server ID is defined via the configuration file, then
+ * it will already be present in the option state at this point,
+ * so we don't need to set it.
+ *
+ * If we have a server ID passed in from the caller,
+ * use that, otherwise use the global DUID.
+ */
+ oc = lookup_option(&dhcpv6_universe, *opt_state, D6O_SERVERID);
+ if (oc == NULL) {
+ if (server_id == NULL) {
+ server_id_data = server_duid.data;
+ server_id_len = server_duid.len;
+ } else {
+ server_id_data = server_id->data;
+ server_id_len = server_id->len;
+ }
+ if (!save_option_buffer(&dhcpv6_universe, *opt_state,
+ NULL, (unsigned char *)server_id_data,
+ server_id_len, D6O_SERVERID, 0)) {
+ log_error("start_reply: "
+ "error saving server identifier.");
+ return 0;
+ }
+ }
+
+ if (client_id->buffer != NULL) {
+ if (!save_option_buffer(&dhcpv6_universe, *opt_state,
+ client_id->buffer,
+ (unsigned char *)client_id->data,
+ client_id->len,
+ D6O_CLIENTID, 0)) {
+ log_error("start_reply: error saving "
+ "client identifier.");
+ return 0;
+ }
+ }
+
+ /*
+ * If the client accepts reconfiguration, let it know that we
+ * will send them.
+ *
+ * Note: we don't actually do this yet, but DOCSIS requires we
+ * claim to.
+ */
+ oc = lookup_option(&dhcpv6_universe, packet->options,
+ D6O_RECONF_ACCEPT);
+ if (oc != NULL) {
+ if (!save_option_buffer(&dhcpv6_universe, *opt_state,
+ NULL, (unsigned char *)"", 0,
+ D6O_RECONF_ACCEPT, 0)) {
+ log_error("start_reply: "
+ "error saving RECONF_ACCEPT option.");
+ option_state_dereference(opt_state, MDL);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/*
+ * Try to get the IPv6 address the client asked for from the
+ * pool.
+ *
+ * addr is the result (should be a pointer to NULL on entry)
+ * pool is the pool to search in
+ * requested_addr is the address the client wants
+ */
+static isc_result_t
+try_client_v6_address(struct iasubopt **addr,
+ struct ipv6_pool *pool,
+ const struct data_string *requested_addr)
+{
+ struct in6_addr tmp_addr;
+ isc_result_t result;
+
+ if (requested_addr->len < sizeof(tmp_addr)) {
+ return DHCP_R_INVALIDARG;
+ }
+ memcpy(&tmp_addr, requested_addr->data, sizeof(tmp_addr));
+ if (IN6_IS_ADDR_UNSPECIFIED(&tmp_addr)) {
+ return ISC_R_FAILURE;
+ }
+
+ /*
+ * The address is not covered by this (or possibly any) dynamic
+ * range.
+ */
+ if (!ipv6_in_pool(&tmp_addr, pool)) {
+ return ISC_R_ADDRNOTAVAIL;
+ }
+
+ if (lease6_exists(pool, &tmp_addr)) {
+ return ISC_R_ADDRINUSE;
+ }
+
+ result = iasubopt_allocate(addr, MDL);
+ if (result != ISC_R_SUCCESS) {
+ return result;
+ }
+ (*addr)->addr = tmp_addr;
+ (*addr)->plen = 0;
+
+ /* Default is soft binding for 2 minutes. */
+ result = add_lease6(pool, *addr, cur_time + 120);
+ if (result != ISC_R_SUCCESS) {
+ iasubopt_dereference(addr, MDL);
+ }
+ return result;
+}
+
+/*
+ * Get an IPv6 address for the client.
+ *
+ * addr is the result (should be a pointer to NULL on entry)
+ * packet is the information about the packet from the client
+ * requested_iaaddr is a hint from the client
+ * client_id is the DUID for the client
+ */
+static isc_result_t
+pick_v6_address(struct iasubopt **addr, struct shared_network *shared_network,
+ const struct data_string *client_id)
+{
+ struct ipv6_pool *p;
+ int i;
+ int start_pool;
+ unsigned int attempts;
+ char tmp_buf[INET6_ADDRSTRLEN];
+
+ /*
+ * No address pools, we're done.
+ */
+ if (shared_network->ipv6_pools == NULL) {
+ log_debug("Unable to pick client address: "
+ "no IPv6 pools on this shared network");
+ return ISC_R_NORESOURCES;
+ }
+ for (i = 0;; i++) {
+ p = shared_network->ipv6_pools[i];
+ if (p == NULL) {
+ log_debug("Unable to pick client address: "
+ "no IPv6 address pools "
+ "on this shared network");
+ return ISC_R_NORESOURCES;
+ }
+ if (p->pool_type == D6O_IA_NA) {
+ break;
+ }
+ }
+
+ /*
+ * Otherwise try to get a lease from the first subnet possible.
+ *
+ * We start looking at the last pool we allocated from, unless
+ * it had a collision trying to allocate an address. This will
+ * tend to move us into less-filled pools.
+ */
+ start_pool = shared_network->last_ipv6_pool;
+ i = start_pool;
+ do {
+
+ p = shared_network->ipv6_pools[i];
+ if ((p->pool_type == D6O_IA_NA) &&
+ (create_lease6(p, addr, &attempts, client_id,
+ cur_time + 120) == ISC_R_SUCCESS)) {
+ /*
+ * Record the pool used (or next one if there
+ * was a collision).
+ */
+ if (attempts > 1) {
+ i++;
+ if (shared_network->ipv6_pools[i] == NULL) {
+ i = 0;
+ }
+ }
+ shared_network->last_ipv6_pool = i;
+
+ log_debug("Picking pool address %s",
+ inet_ntop(AF_INET6, &((*addr)->addr),
+ tmp_buf, sizeof(tmp_buf)));
+ return ISC_R_SUCCESS;
+ }
+
+ i++;
+ if (shared_network->ipv6_pools[i] == NULL) {
+ i = 0;
+ }
+ } while (i != start_pool);
+
+ /*
+ * If we failed to pick an IPv6 address from any of the subnets.
+ * Presumably that means we have no addresses for the client.
+ */
+ log_debug("Unable to pick client address: no addresses available");
+ return ISC_R_NORESOURCES;
+}
+
+/*
+ * Try to get the IPv6 prefix the client asked for from the
+ * prefix pool.
+ *
+ * pref is the result (should be a pointer to NULL on entry)
+ * pool is the prefix pool to search in
+ * requested_pref is the address the client wants
+ */
+static isc_result_t
+try_client_v6_prefix(struct iasubopt **pref,
+ struct ipv6_pool *pool,
+ const struct data_string *requested_pref)
+{
+ u_int8_t tmp_plen;
+ struct in6_addr tmp_pref;
+ struct iaddr ia;
+ isc_result_t result;
+
+ if (requested_pref->len < sizeof(tmp_plen) + sizeof(tmp_pref)) {
+ return DHCP_R_INVALIDARG;
+ }
+ tmp_plen = (int) requested_pref->data[0];
+ if ((tmp_plen < 3) || (tmp_plen > 128) ||
+ ((int)tmp_plen != pool->units)) {
+ return ISC_R_FAILURE;
+ }
+ memcpy(&tmp_pref, requested_pref->data + 1, sizeof(tmp_pref));
+ if (IN6_IS_ADDR_UNSPECIFIED(&tmp_pref)) {
+ return ISC_R_FAILURE;
+ }
+ ia.len = 16;
+ memcpy(&ia.iabuf, &tmp_pref, 16);
+ if (!is_cidr_mask_valid(&ia, (int) tmp_plen)) {
+ return ISC_R_FAILURE;
+ }
+
+ if (!ipv6_in_pool(&tmp_pref, pool)) {
+ return ISC_R_ADDRNOTAVAIL;
+ }
+
+ if (prefix6_exists(pool, &tmp_pref, tmp_plen)) {
+ return ISC_R_ADDRINUSE;
+ }
+
+ result = iasubopt_allocate(pref, MDL);
+ if (result != ISC_R_SUCCESS) {
+ return result;
+ }
+ (*pref)->addr = tmp_pref;
+ (*pref)->plen = tmp_plen;
+
+ /* Default is soft binding for 2 minutes. */
+ result = add_lease6(pool, *pref, cur_time + 120);
+ if (result != ISC_R_SUCCESS) {
+ iasubopt_dereference(pref, MDL);
+ }
+ return result;
+}
+
+/*
+ * Get an IPv6 prefix for the client.
+ *
+ * pref is the result (should be a pointer to NULL on entry)
+ * packet is the information about the packet from the client
+ * requested_iaprefix is a hint from the client
+ * plen is -1 or the requested prefix length
+ * client_id is the DUID for the client
+ */
+static isc_result_t
+pick_v6_prefix(struct iasubopt **pref, int plen,
+ struct shared_network *shared_network,
+ const struct data_string *client_id)
+{
+ struct ipv6_pool *p;
+ int i;
+ unsigned int attempts;
+ char tmp_buf[INET6_ADDRSTRLEN];
+
+ /*
+ * No prefix pools, we're done.
+ */
+ if (shared_network->ipv6_pools == NULL) {
+ log_debug("Unable to pick client prefix: "
+ "no IPv6 pools on this shared network");
+ return ISC_R_NORESOURCES;
+ }
+ for (i = 0;; i++) {
+ p = shared_network->ipv6_pools[i];
+ if (p == NULL) {
+ log_debug("Unable to pick client prefix: "
+ "no IPv6 prefix pools "
+ "on this shared network");
+ return ISC_R_NORESOURCES;
+ }
+ if (p->pool_type == D6O_IA_PD) {
+ break;
+ }
+ }
+
+ /*
+ * Otherwise try to get a prefix.
+ */
+ for (i = 0;; i++) {
+ p = shared_network->ipv6_pools[i];
+ if (p == NULL) {
+ break;
+ }
+ if (p->pool_type != D6O_IA_PD) {
+ continue;
+ }
+
+ /*
+ * Try only pools with the requested prefix length if any.
+ */
+ if ((plen >= 0) && (p->units != plen)) {
+ continue;
+ }
+
+ if (create_prefix6(p, pref, &attempts, client_id,
+ cur_time + 120) == ISC_R_SUCCESS) {
+ log_debug("Picking pool prefix %s/%u",
+ inet_ntop(AF_INET6, &((*pref)->addr),
+ tmp_buf, sizeof(tmp_buf)),
+ (unsigned) (*pref)->plen);
+ return ISC_R_SUCCESS;
+ }
+ }
+
+ /*
+ * If we failed to pick an IPv6 prefix
+ * Presumably that means we have no prefixes for the client.
+ */
+ log_debug("Unable to pick client prefix: no prefixes available");
+ return ISC_R_NORESOURCES;
+}
+
+/*
+ *! \file server/dhcpv6.c
+ *
+ * \brief construct a reply containing information about a client's lease
+ *
+ * lease_to_client() is called from several messages to construct a
+ * reply that contains all that we know about the client's correct lease
+ * (or projected lease).
+ *
+ * Solicit - "Soft" binding, ignore unknown addresses or bindings, just
+ * send what we "may" give them on a request.
+ *
+ * Request - "Hard" binding, but ignore supplied addresses (just provide what
+ * the client should really use).
+ *
+ * Renew - "Hard" binding, but client-supplied addresses are 'real'. Error
+ * Rebind out any "wrong" addresses the client sends. This means we send
+ * an empty IA_NA with a status code of NoBinding or NotOnLink or
+ * possibly send the address with zeroed lifetimes.
+ *
+ * Information-Request - No binding.
+ *
+ * The basic structure is to traverse the client-supplied data first, and
+ * validate and echo back any contents that can be. If the client-supplied
+ * data does not error out (on renew/rebind as above), but we did not send
+ * any addresses, attempt to allocate one.
+ *
+ * At the end of the this function we call commit_leases_timed() to
+ * fsync and rotate the file as necessary. commit_leases_timed() will
+ * check that we have written at least one lease to the file and that
+ * some time has passed before doing any fsync or file rewrite so we
+ * don't bother tracking if we did a write_ia during this function.
+ */
+/* TODO: look at client hints for lease times */
+
+static void
+lease_to_client(struct data_string *reply_ret,
+ struct packet *packet,
+ const struct data_string *client_id,
+ const struct data_string *server_id)
+{
+ static struct reply_state reply;
+ struct option_cache *oc;
+ struct data_string packet_oro;
+#if defined (RFC3315_PRE_ERRATA_2010_08)
+ isc_boolean_t no_resources_avail = ISC_FALSE;
+#endif
+
+ memset(&packet_oro, 0, sizeof(packet_oro));
+
+ /* Locate the client. */
+ if (shared_network_from_packet6(&reply.shared,
+ packet) != ISC_R_SUCCESS)
+ goto exit;
+
+ /*
+ * Initialize the reply.
+ */
+ packet_reference(&reply.packet, packet, MDL);
+ data_string_copy(&reply.client_id, client_id, MDL);
+
+ if (!start_reply(packet, client_id, server_id, &reply.opt_state,
+ &reply.buf.reply))
+ goto exit;
+
+ /* Set the write cursor to just past the reply header. */
+ reply.cursor = REPLY_OPTIONS_INDEX;
+
+ /*
+ * Get the ORO from the packet, if any.
+ */
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_ORO);
+ if (oc != NULL) {
+ if (!evaluate_option_cache(&packet_oro, packet,
+ NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ log_error("lease_to_client: error evaluating ORO.");
+ goto exit;
+ }
+ }
+
+ /*
+ * Find a host record that matches from the packet, if any, and is
+ * valid for the shared network the client is on.
+ */
+ if (find_hosts_by_uid(&reply.host, client_id->data, client_id->len,
+ MDL))
+ seek_shared_host(&reply.host, reply.shared);
+
+ if ((reply.host == NULL) &&
+ find_hosts_by_option(&reply.host, packet, packet->options, MDL))
+ seek_shared_host(&reply.host, reply.shared);
+
+ /*
+ * Check for 'hardware' matches last, as some of the synthesis methods
+ * are not considered to be as reliable.
+ */
+ if ((reply.host == NULL) &&
+ find_hosts_by_duid_chaddr(&reply.host, client_id))
+ seek_shared_host(&reply.host, reply.shared);
+
+ /* Process the client supplied IA's onto the reply buffer. */
+ reply.ia_count = 0;
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
+
+ for (; oc != NULL ; oc = oc->next) {
+ isc_result_t status;
+
+ /* Start counting resources (addresses) offered. */
+ reply.client_resources = 0;
+ reply.resources_included = ISC_FALSE;
+
+ status = reply_process_ia_na(&reply, oc);
+
+ /*
+ * We continue to try other IA's whether we can address
+ * this one or not. Any other result is an immediate fail.
+ */
+ if ((status != ISC_R_SUCCESS) &&
+ (status != ISC_R_NORESOURCES))
+ goto exit;
+
+#if defined (RFC3315_PRE_ERRATA_2010_08)
+ /*
+ * If any address cannot be given to any IA, then set the
+ * NoAddrsAvail status code.
+ */
+ if (reply.client_resources == 0)
+ no_resources_avail = ISC_TRUE;
+#endif
+ }
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA);
+ for (; oc != NULL ; oc = oc->next) {
+ isc_result_t status;
+
+ /* Start counting resources (addresses) offered. */
+ reply.client_resources = 0;
+ reply.resources_included = ISC_FALSE;
+
+ status = reply_process_ia_ta(&reply, oc);
+
+ /*
+ * We continue to try other IA's whether we can address
+ * this one or not. Any other result is an immediate fail.
+ */
+ if ((status != ISC_R_SUCCESS) &&
+ (status != ISC_R_NORESOURCES))
+ goto exit;
+
+#if defined (RFC3315_PRE_ERRATA_2010_08)
+ /*
+ * If any address cannot be given to any IA, then set the
+ * NoAddrsAvail status code.
+ */
+ if (reply.client_resources == 0)
+ no_resources_avail = ISC_TRUE;
+#endif
+ }
+
+ /* Same for IA_PD's. */
+ reply.pd_count = 0;
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
+ for (; oc != NULL ; oc = oc->next) {
+ isc_result_t status;
+
+ /* Start counting resources (prefixes) offered. */
+ reply.client_resources = 0;
+ reply.resources_included = ISC_FALSE;
+
+ status = reply_process_ia_pd(&reply, oc);
+
+ /*
+ * We continue to try other IA_PD's whether we can address
+ * this one or not. Any other result is an immediate fail.
+ */
+ if ((status != ISC_R_SUCCESS) &&
+ (status != ISC_R_NORESOURCES))
+ goto exit;
+ }
+
+ /*
+ * Make no reply if we gave no resources and is not
+ * for Information-Request.
+ */
+ if ((reply.ia_count == 0) && (reply.pd_count == 0)) {
+ if (reply.packet->dhcpv6_msg_type !=
+ DHCPV6_INFORMATION_REQUEST)
+ goto exit;
+
+ /*
+ * Because we only execute statements on a per-IA basis,
+ * we need to execute statements in any non-IA reply to
+ * source configuration.
+ */
+ execute_statements_in_scope(NULL, reply.packet, NULL, NULL,
+ reply.packet->options,
+ reply.opt_state, &global_scope,
+ reply.shared->group, root_group);
+
+ /* Bring in any configuration from a host record. */
+ if (reply.host != NULL)
+ execute_statements_in_scope(NULL, reply.packet, NULL,
+ NULL, reply.packet->options,
+ reply.opt_state,
+ &global_scope,
+ reply.host->group,
+ reply.shared->group);
+ }
+
+ /*
+ * RFC3315 section 17.2.2 (Solicit):
+ *
+ * If the server will not assign any addresses to any IAs in a
+ * subsequent Request from the client, the server MUST send an
+ * Advertise message to the client that includes only a Status
+ * Code option with code NoAddrsAvail and a status message for
+ * the user, a Server Identifier option with the server's DUID,
+ * and a Client Identifier option with the client's DUID.
+ *
+ * Section 18.2.1 (Request):
+ *
+ * If the server cannot assign any addresses to an IA in the
+ * message from the client, the server MUST include the IA in
+ * the Reply message with no addresses in the IA and a Status
+ * Code option in the IA containing status code NoAddrsAvail.
+ *
+ * Section 18.1.8 (Client Behavior):
+ *
+ * Leave unchanged any information about addresses the client has
+ * recorded in the IA but that were not included in the IA from
+ * the server.
+ * Sends a Renew/Rebind if the IA is not in the Reply message.
+ */
+#if defined (RFC3315_PRE_ERRATA_2010_08)
+ if (no_resources_avail && (reply.ia_count != 0) &&
+ (reply.packet->dhcpv6_msg_type == DHCPV6_SOLICIT))
+ {
+ /* Set the NoAddrsAvail status code. */
+ if (!set_status_code(STATUS_NoAddrsAvail,
+ "No addresses available for this "
+ "interface.", reply.opt_state)) {
+ log_error("lease_to_client: Unable to set "
+ "NoAddrsAvail status code.");
+ goto exit;
+ }
+
+ /* Rewind the cursor to the start. */
+ reply.cursor = REPLY_OPTIONS_INDEX;
+
+ /*
+ * Produce an advertise that includes only:
+ *
+ * Status code.
+ * Server DUID.
+ * Client DUID.
+ */
+ reply.buf.reply.msg_type = DHCPV6_ADVERTISE;
+ reply.cursor += store_options6((char *)reply.buf.data +
+ reply.cursor,
+ sizeof(reply.buf) -
+ reply.cursor,
+ reply.opt_state, reply.packet,
+ required_opts_NAA,
+ NULL);
+ } else {
+ /*
+ * Having stored the client's IA's, store any options that
+ * will fit in the remaining space.
+ */
+ reply.cursor += store_options6((char *)reply.buf.data +
+ reply.cursor,
+ sizeof(reply.buf) -
+ reply.cursor,
+ reply.opt_state, reply.packet,
+ required_opts_solicit,
+ &packet_oro);
+ }
+#else /* defined (RFC3315_PRE_ERRATA_2010_08) */
+ /*
+ * Having stored the client's IA's, store any options that
+ * will fit in the remaining space.
+ */
+ reply.cursor += store_options6((char *)reply.buf.data + reply.cursor,
+ sizeof(reply.buf) - reply.cursor,
+ reply.opt_state, reply.packet,
+ required_opts_solicit,
+ &packet_oro);
+#endif /* defined (RFC3315_PRE_ERRATA_2010_08) */
+
+ /* Return our reply to the caller. */
+ reply_ret->len = reply.cursor;
+ reply_ret->buffer = NULL;
+ if (!buffer_allocate(&reply_ret->buffer, reply.cursor, MDL)) {
+ log_fatal("No memory to store Reply.");
+ }
+ memcpy(reply_ret->buffer->data, reply.buf.data, reply.cursor);
+ reply_ret->data = reply_ret->buffer->data;
+
+ /* If appropriate commit and rotate the lease file */
+ (void) commit_leases_timed();
+
+ exit:
+ /* Cleanup. */
+ if (reply.shared != NULL)
+ shared_network_dereference(&reply.shared, MDL);
+ if (reply.host != NULL)
+ host_dereference(&reply.host, MDL);
+ if (reply.opt_state != NULL)
+ option_state_dereference(&reply.opt_state, MDL);
+ if (reply.packet != NULL)
+ packet_dereference(&reply.packet, MDL);
+ if (reply.client_id.data != NULL)
+ data_string_forget(&reply.client_id, MDL);
+ if (packet_oro.buffer != NULL)
+ data_string_forget(&packet_oro, MDL);
+ reply.renew = reply.rebind = reply.prefer = reply.valid = 0;
+ reply.cursor = 0;
+}
+
+/* Process a client-supplied IA_NA. This may append options to the tail of
+ * the reply packet being built in the reply_state structure.
+ */
+static isc_result_t
+reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
+ isc_result_t status = ISC_R_SUCCESS;
+ u_int32_t iaid;
+ unsigned ia_cursor;
+ struct option_state *packet_ia;
+ struct option_cache *oc;
+ struct data_string ia_data, data;
+
+ /* Initialize values that will get cleaned up on return. */
+ packet_ia = NULL;
+ memset(&ia_data, 0, sizeof(ia_data));
+ memset(&data, 0, sizeof(data));
+ /*
+ * Note that find_client_address() may set reply->lease.
+ */
+
+ /* Make sure there is at least room for the header. */
+ if ((reply->cursor + IA_NA_OFFSET + 4) > sizeof(reply->buf)) {
+ log_error("reply_process_ia_na: Reply too long for IA.");
+ return ISC_R_NOSPACE;
+ }
+
+
+ /* Fetch the IA_NA contents. */
+ if (!get_encapsulated_IA_state(&packet_ia, &ia_data, reply->packet,
+ ia, IA_NA_OFFSET)) {
+ log_error("reply_process_ia_na: error evaluating ia");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ /* Extract IA_NA header contents. */
+ iaid = getULong(ia_data.data);
+ reply->renew = getULong(ia_data.data + 4);
+ reply->rebind = getULong(ia_data.data + 8);
+
+ /* Create an IA_NA structure. */
+ if (ia_allocate(&reply->ia, iaid, (char *)reply->client_id.data,
+ reply->client_id.len, MDL) != ISC_R_SUCCESS) {
+ log_error("reply_process_ia_na: no memory for ia.");
+ status = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ reply->ia->ia_type = D6O_IA_NA;
+
+ /* Cache pre-existing IA, if any. */
+ ia_hash_lookup(&reply->old_ia, ia_na_active,
+ (unsigned char *)reply->ia->iaid_duid.data,
+ reply->ia->iaid_duid.len, MDL);
+
+ /*
+ * Create an option cache to carry the IA_NA option contents, and
+ * execute any user-supplied values into it.
+ */
+ if (!option_state_allocate(&reply->reply_ia, MDL)) {
+ status = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ /* Check & cache the fixed host record. */
+ if ((reply->host != NULL) && (reply->host->fixed_addr != NULL)) {
+ struct iaddr tmp_addr;
+
+ if (!evaluate_option_cache(&reply->fixed, NULL, NULL, NULL,
+ NULL, NULL, &global_scope,
+ reply->host->fixed_addr, MDL)) {
+ log_error("reply_process_ia_na: unable to evaluate "
+ "fixed address.");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ if (reply->fixed.len < 16) {
+ log_error("reply_process_ia_na: invalid fixed address.");
+ status = DHCP_R_INVALIDARG;
+ goto cleanup;
+ }
+
+ /* Find the static lease's subnet. */
+ tmp_addr.len = 16;
+ memcpy(tmp_addr.iabuf, reply->fixed.data, 16);
+
+ if (find_grouped_subnet(&reply->subnet, reply->shared,
+ tmp_addr, MDL) == 0)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ reply->static_lease = ISC_TRUE;
+ } else
+ reply->static_lease = ISC_FALSE;
+
+ /*
+ * Save the cursor position at the start of the IA, so we can
+ * set length and adjust t1/t2 values later. We write a temporary
+ * header out now just in case we decide to adjust the packet
+ * within sub-process functions.
+ */
+ ia_cursor = reply->cursor;
+
+ /* Initialize the IA_NA header. First the code. */
+ putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_NA);
+ reply->cursor += 2;
+
+ /* Then option length. */
+ putUShort(reply->buf.data + reply->cursor, 0x0Cu);
+ reply->cursor += 2;
+
+ /* Then IA_NA header contents; IAID. */
+ putULong(reply->buf.data + reply->cursor, iaid);
+ reply->cursor += 4;
+
+ /* We store the client's t1 for now, and may over-ride it later. */
+ putULong(reply->buf.data + reply->cursor, reply->renew);
+ reply->cursor += 4;
+
+ /* We store the client's t2 for now, and may over-ride it later. */
+ putULong(reply->buf.data + reply->cursor, reply->rebind);
+ reply->cursor += 4;
+
+ /*
+ * For each address in this IA_NA, decide what to do about it.
+ *
+ * Guidelines:
+ *
+ * The client leaves unchanged any infomation about addresses
+ * it has recorded but are not included ("cancel/break" below).
+ * A not included IA ("cleanup" below) could give a Renew/Rebind.
+ */
+ oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAADDR);
+ reply->valid = reply->prefer = 0xffffffff;
+ reply->client_valid = reply->client_prefer = 0;
+ for (; oc != NULL ; oc = oc->next) {
+ status = reply_process_addr(reply, oc);
+
+ /*
+ * Canceled means we did not allocate addresses to the
+ * client, but we're "done" with this IA - we set a status
+ * code. So transmit this reply, e.g., move on to the next
+ * IA.
+ */
+ if (status == ISC_R_CANCELED)
+ break;
+
+ if ((status != ISC_R_SUCCESS) &&
+ (status != ISC_R_ADDRINUSE) &&
+ (status != ISC_R_ADDRNOTAVAIL))
+ goto cleanup;
+ }
+
+ reply->ia_count++;
+
+ /*
+ * If we fell through the above and never gave the client
+ * an address, give it one now.
+ */
+ if ((status != ISC_R_CANCELED) && (reply->client_resources == 0)) {
+ status = find_client_address(reply);
+
+ if (status == ISC_R_NORESOURCES) {
+ switch (reply->packet->dhcpv6_msg_type) {
+ case DHCPV6_SOLICIT:
+ /*
+ * No address for any IA is handled
+ * by the caller.
+ */
+ /* FALL THROUGH */
+
+ case DHCPV6_REQUEST:
+ /* Section 18.2.1 (Request):
+ *
+ * If the server cannot assign any addresses to
+ * an IA in the message from the client, the
+ * server MUST include the IA in the Reply
+ * message with no addresses in the IA and a
+ * Status Code option in the IA containing
+ * status code NoAddrsAvail.
+ */
+ option_state_dereference(&reply->reply_ia, MDL);
+ if (!option_state_allocate(&reply->reply_ia,
+ MDL))
+ {
+ log_error("reply_process_ia_na: No "
+ "memory for option state "
+ "wipe.");
+ status = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ if (!set_status_code(STATUS_NoAddrsAvail,
+ "No addresses available "
+ "for this interface.",
+ reply->reply_ia)) {
+ log_error("reply_process_ia_na: Unable "
+ "to set NoAddrsAvail status "
+ "code.");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ status = ISC_R_SUCCESS;
+ break;
+
+ default:
+ /*
+ * RFC 3315 does not tell us to emit a status
+ * code in this condition, or anything else.
+ *
+ * If we included non-allocated addresses
+ * (zeroed lifetimes) in an IA, then the client
+ * will deconfigure them.
+ *
+ * So we want to include the IA even if we
+ * can't give it a new address if it includes
+ * zeroed lifetime addresses.
+ *
+ * We don't want to include the IA if we
+ * provide zero addresses including zeroed
+ * lifetimes.
+ */
+ if (reply->resources_included)
+ status = ISC_R_SUCCESS;
+ else
+ goto cleanup;
+ break;
+ }
+ }
+
+ if (status != ISC_R_SUCCESS)
+ goto cleanup;
+ }
+
+ reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
+ sizeof(reply->buf) - reply->cursor,
+ reply->reply_ia, reply->packet,
+ required_opts_IA, NULL);
+
+ /* Reset the length of this IA to match what was just written. */
+ putUShort(reply->buf.data + ia_cursor + 2,
+ reply->cursor - (ia_cursor + 4));
+
+ /*
+ * T1/T2 time selection is kind of weird. We actually use DHCP
+ * (v4) scoped options as handy existing places where these might
+ * be configured by an administrator. A value of zero tells the
+ * client it may choose its own renewal time.
+ */
+ reply->renew = 0;
+ oc = lookup_option(&dhcp_universe, reply->opt_state,
+ DHO_DHCP_RENEWAL_TIME);
+ if (oc != NULL) {
+ if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
+ reply->packet->options,
+ reply->opt_state, &global_scope,
+ oc, MDL) ||
+ (data.len != 4)) {
+ log_error("Invalid renewal time.");
+ } else {
+ reply->renew = getULong(data.data);
+ }
+
+ if (data.data != NULL)
+ data_string_forget(&data, MDL);
+ }
+ putULong(reply->buf.data + ia_cursor + 8, reply->renew);
+
+ /* Now T2. */
+ reply->rebind = 0;
+ oc = lookup_option(&dhcp_universe, reply->opt_state,
+ DHO_DHCP_REBINDING_TIME);
+ if (oc != NULL) {
+ if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
+ reply->packet->options,
+ reply->opt_state, &global_scope,
+ oc, MDL) ||
+ (data.len != 4)) {
+ log_error("Invalid rebinding time.");
+ } else {
+ reply->rebind = getULong(data.data);
+ }
+
+ if (data.data != NULL)
+ data_string_forget(&data, MDL);
+ }
+ putULong(reply->buf.data + ia_cursor + 12, reply->rebind);
+
+ /*
+ * yes, goto's aren't the best but we also want to avoid extra
+ * indents
+ */
+ if (status == ISC_R_CANCELED)
+ goto cleanup;
+
+ /*
+ * Handle static leases, we always log stuff and if it's
+ * a hard binding we run any commit statements that we have
+ */
+ if (reply->static_lease) {
+#if defined(LOG_V6_ADDRESSES)
+ char tmp_addr[INET6_ADDRSTRLEN];
+ log_info("%s NA: address %s to client with duid %s iaid = %d "
+ "static",
+ dhcpv6_type_names[reply->buf.reply.msg_type],
+ inet_ntop(AF_INET6, reply->fixed.data, tmp_addr,
+ sizeof(tmp_addr)),
+ print_hex_1(reply->client_id.len,
+ reply->client_id.data, 60),
+ iaid);
+#endif
+ goto cleanup;
+ }
+
+
+#if defined(LOG_V6_ADDRESSES)
+ /*
+ * If we have any addresses log what we are doing.
+ */
+ if (reply->ia->num_iasubopt != 0) {
+ struct iasubopt *tmp;
+ int i;
+ char tmp_addr[INET6_ADDRSTRLEN];
+
+ for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
+ tmp = reply->ia->iasubopt[i];
+
+ log_info("%s NA: address %s to client with duid %s "
+ "iaid = %d valid for %d seconds",
+ dhcpv6_type_names[reply->buf.reply.msg_type],
+ inet_ntop(AF_INET6, &tmp->addr,
+ tmp_addr, sizeof(tmp_addr)),
+ print_hex_1(reply->client_id.len,
+ reply->client_id.data, 60),
+ iaid, tmp->valid);
+ }
+ }
+#endif
+
+ /*
+ * If this is not a 'soft' binding, consume the new changes into
+ * the database (if any have been attached to the ia_na).
+ *
+ * Loop through the assigned dynamic addresses, referencing the
+ * leases onto this IA_NA rather than any old ones, and updating
+ * pool timers for each (if any).
+ */
+
+ if ((reply->ia->num_iasubopt != 0) &&
+ (reply->buf.reply.msg_type == DHCPV6_REPLY)) {
+ struct iasubopt *tmp;
+ struct data_string *ia_id;
+ int i;
+
+ for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
+ tmp = reply->ia->iasubopt[i];
+
+ if (tmp->ia != NULL)
+ ia_dereference(&tmp->ia, MDL);
+ ia_reference(&tmp->ia, reply->ia, MDL);
+
+ /* Commit 'hard' bindings. */
+ renew_lease6(tmp->ipv6_pool, tmp);
+ schedule_lease_timeout(tmp->ipv6_pool);
+
+#if defined (NSUPDATE)
+ /*
+ * Perform ddns updates.
+ */
+ oc = lookup_option(&server_universe, reply->opt_state,
+ SV_DDNS_UPDATES);
+ if ((oc == NULL) ||
+ evaluate_boolean_option_cache(NULL, reply->packet,
+ NULL, NULL,
+ reply->packet->options,
+ reply->opt_state,
+ &tmp->scope,
+ oc, MDL)) {
+ ddns_updates(reply->packet, NULL, NULL,
+ tmp, NULL, reply->opt_state);
+ }
+#endif
+ }
+
+ /* Remove any old ia from the hash. */
+ if (reply->old_ia != NULL) {
+ ia_id = &reply->old_ia->iaid_duid;
+ ia_hash_delete(ia_na_active,
+ (unsigned char *)ia_id->data,
+ ia_id->len, MDL);
+ ia_dereference(&reply->old_ia, MDL);
+ }
+
+ /* Put new ia into the hash. */
+ reply->ia->cltt = cur_time;
+ ia_id = &reply->ia->iaid_duid;
+ ia_hash_add(ia_na_active, (unsigned char *)ia_id->data,
+ ia_id->len, reply->ia, MDL);
+
+ write_ia(reply->ia);
+ } else {
+ schedule_lease_timeout_reply(reply);
+ }
+
+ cleanup:
+ if (packet_ia != NULL)
+ option_state_dereference(&packet_ia, MDL);
+ if (reply->reply_ia != NULL)
+ option_state_dereference(&reply->reply_ia, MDL);
+ if (ia_data.data != NULL)
+ data_string_forget(&ia_data, MDL);
+ if (data.data != NULL)
+ data_string_forget(&data, MDL);
+ if (reply->ia != NULL)
+ ia_dereference(&reply->ia, MDL);
+ if (reply->old_ia != NULL)
+ ia_dereference(&reply->old_ia, MDL);
+ if (reply->lease != NULL)
+ iasubopt_dereference(&reply->lease, MDL);
+ if (reply->fixed.data != NULL)
+ data_string_forget(&reply->fixed, MDL);
+ if (reply->subnet != NULL)
+ subnet_dereference(&reply->subnet, MDL);
+
+ /*
+ * ISC_R_CANCELED is a status code used by the addr processing to
+ * indicate we're replying with a status code. This is still a
+ * success at higher layers.
+ */
+ return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status);
+}
+
+/*
+ * Process an IAADDR within a given IA_xA, storing any IAADDR reply contents
+ * into the reply's current ia-scoped option cache. Returns ISC_R_CANCELED
+ * in the event we are replying with a status code and do not wish to process
+ * more IAADDRs within this IA.
+ */
+static isc_result_t
+reply_process_addr(struct reply_state *reply, struct option_cache *addr) {
+ u_int32_t pref_life, valid_life;
+ struct binding_scope **scope;
+ struct group *group;
+ struct subnet *subnet;
+ struct iaddr tmp_addr;
+ struct option_cache *oc;
+ struct data_string iaaddr, data;
+ isc_result_t status = ISC_R_SUCCESS;
+
+ /* Initializes values that will be cleaned up. */
+ memset(&iaaddr, 0, sizeof(iaaddr));
+ memset(&data, 0, sizeof(data));
+ /* Note that reply->lease may be set by address_is_owned() */
+
+ /*
+ * There is no point trying to process an incoming address if there
+ * is no room for an outgoing address.
+ */
+ if ((reply->cursor + 28) > sizeof(reply->buf)) {
+ log_error("reply_process_addr: Out of room for address.");
+ return ISC_R_NOSPACE;
+ }
+
+ /* Extract this IAADDR option. */
+ if (!evaluate_option_cache(&iaaddr, reply->packet, NULL, NULL,
+ reply->packet->options, NULL, &global_scope,
+ addr, MDL) ||
+ (iaaddr.len < IAADDR_OFFSET)) {
+ log_error("reply_process_addr: error evaluating IAADDR.");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ /* The first 16 bytes are the IPv6 address. */
+ pref_life = getULong(iaaddr.data + 16);
+ valid_life = getULong(iaaddr.data + 20);
+
+ if ((reply->client_valid == 0) ||
+ (reply->client_valid > valid_life))
+ reply->client_valid = valid_life;
+
+ if ((reply->client_prefer == 0) ||
+ (reply->client_prefer > pref_life))
+ reply->client_prefer = pref_life;
+
+ /*
+ * Clients may choose to send :: as an address, with the idea to give
+ * hints about preferred-lifetime or valid-lifetime.
+ */
+ tmp_addr.len = 16;
+ memset(tmp_addr.iabuf, 0, 16);
+ if (!memcmp(iaaddr.data, tmp_addr.iabuf, 16)) {
+ /* Status remains success; we just ignore this one. */
+ goto cleanup;
+ }
+
+ /* tmp_addr len remains 16 */
+ memcpy(tmp_addr.iabuf, iaaddr.data, 16);
+
+ /*
+ * Verify that this address is on the client's network.
+ */
+ for (subnet = reply->shared->subnets ; subnet != NULL ;
+ subnet = subnet->next_sibling) {
+ if (addr_eq(subnet_number(tmp_addr, subnet->netmask),
+ subnet->net))
+ break;
+ }
+
+ /* Address not found on shared network. */
+ if (subnet == NULL) {
+ /* Ignore this address on 'soft' bindings. */
+ if (reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT) {
+ /* disable rapid commit */
+ reply->buf.reply.msg_type = DHCPV6_ADVERTISE;
+ delete_option(&dhcpv6_universe,
+ reply->opt_state,
+ D6O_RAPID_COMMIT);
+ /* status remains success */
+ goto cleanup;
+ }
+
+ /*
+ * RFC3315 section 18.2.1:
+ *
+ * If the server finds that the prefix on one or more IP
+ * addresses in any IA in the message from the client is not
+ * appropriate for the link to which the client is connected,
+ * the server MUST return the IA to the client with a Status
+ * Code option with the value NotOnLink.
+ */
+ if (reply->packet->dhcpv6_msg_type == DHCPV6_REQUEST) {
+ /* Rewind the IA_NA to empty. */
+ option_state_dereference(&reply->reply_ia, MDL);
+ if (!option_state_allocate(&reply->reply_ia, MDL)) {
+ log_error("reply_process_addr: No memory for "
+ "option state wipe.");
+ status = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ /* Append a NotOnLink status code. */
+ if (!set_status_code(STATUS_NotOnLink,
+ "Address not for use on this "
+ "link.", reply->reply_ia)) {
+ log_error("reply_process_addr: Failure "
+ "setting status code.");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ /* Fin (no more IAADDRs). */
+ status = ISC_R_CANCELED;
+ goto cleanup;
+ }
+
+ /*
+ * RFC3315 sections 18.2.3 and 18.2.4 have identical language:
+ *
+ * If the server finds that any of the addresses are not
+ * appropriate for the link to which the client is attached,
+ * the server returns the address to the client with lifetimes
+ * of 0.
+ */
+ if ((reply->packet->dhcpv6_msg_type != DHCPV6_RENEW) &&
+ (reply->packet->dhcpv6_msg_type != DHCPV6_REBIND)) {
+ log_error("It is impossible to lease a client that is "
+ "not sending a solicit, request, renew, or "
+ "rebind.");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ reply->send_prefer = reply->send_valid = 0;
+ goto send_addr;
+ }
+
+ /* Verify the address belongs to the client. */
+ if (!address_is_owned(reply, &tmp_addr)) {
+ /*
+ * For solicit and request, any addresses included are
+ * 'requested' addresses. For rebind, we actually have
+ * no direction on what to do from 3315 section 18.2.4!
+ * So I think the best bet is to try and give it out, and if
+ * we can't, zero lifetimes.
+ */
+ if ((reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT) ||
+ (reply->packet->dhcpv6_msg_type == DHCPV6_REQUEST) ||
+ (reply->packet->dhcpv6_msg_type == DHCPV6_REBIND)) {
+ status = reply_process_try_addr(reply, &tmp_addr);
+
+ /*
+ * If the address is in use, or isn't in any dynamic
+ * range, continue as normal. If any other error was
+ * found, error out.
+ */
+ if ((status != ISC_R_SUCCESS) &&
+ (status != ISC_R_ADDRINUSE) &&
+ (status != ISC_R_ADDRNOTAVAIL))
+ goto cleanup;
+
+ /*
+ * If we didn't honor this lease, for solicit and
+ * request we simply omit it from our answer. For
+ * rebind, we send it with zeroed lifetimes.
+ */
+ if (reply->lease == NULL) {
+ if (reply->packet->dhcpv6_msg_type ==
+ DHCPV6_REBIND) {
+ reply->send_prefer = 0;
+ reply->send_valid = 0;
+ goto send_addr;
+ }
+
+ /* status remains success - ignore */
+ goto cleanup;
+ }
+ /*
+ * RFC3315 section 18.2.3:
+ *
+ * If the server cannot find a client entry for the IA the
+ * server returns the IA containing no addresses with a Status
+ * Code option set to NoBinding in the Reply message.
+ *
+ * On mismatch we (ab)use this pretending we have not the IA
+ * as soon as we have not an address.
+ */
+ } else if (reply->packet->dhcpv6_msg_type == DHCPV6_RENEW) {
+ /* Rewind the IA_NA to empty. */
+ option_state_dereference(&reply->reply_ia, MDL);
+ if (!option_state_allocate(&reply->reply_ia, MDL)) {
+ log_error("reply_process_addr: No memory for "
+ "option state wipe.");
+ status = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ /* Append a NoBinding status code. */
+ if (!set_status_code(STATUS_NoBinding,
+ "Address not bound to this "
+ "interface.", reply->reply_ia)) {
+ log_error("reply_process_addr: Unable to "
+ "attach status code.");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ /* Fin (no more IAADDRs). */
+ status = ISC_R_CANCELED;
+ goto cleanup;
+ } else {
+ log_error("It is impossible to lease a client that is "
+ "not sending a solicit, request, renew, or "
+ "rebind message.");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ }
+
+ if (reply->static_lease) {
+ if (reply->host == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ scope = &global_scope;
+ group = reply->subnet->group;
+ } else {
+ if (reply->lease == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ scope = &reply->lease->scope;
+ group = reply->lease->ipv6_pool->subnet->group;
+ }
+
+ /*
+ * If client_resources is nonzero, then the reply_process_is_addressed
+ * function has executed configuration state into the reply option
+ * cache. We will use that valid cache to derive configuration for
+ * whether or not to engage in additional addresses, and similar.
+ */
+ if (reply->client_resources != 0) {
+ unsigned limit = 1;
+
+ /*
+ * Does this client have "enough" addresses already? Default
+ * to one. Everybody gets one, and one should be enough for
+ * anybody.
+ */
+ oc = lookup_option(&server_universe, reply->opt_state,
+ SV_LIMIT_ADDRS_PER_IA);
+ if (oc != NULL) {
+ if (!evaluate_option_cache(&data, reply->packet,
+ NULL, NULL,
+ reply->packet->options,
+ reply->opt_state,
+ scope, oc, MDL) ||
+ (data.len != 4)) {
+ log_error("reply_process_addr: unable to "
+ "evaluate addrs-per-ia value.");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ limit = getULong(data.data);
+ data_string_forget(&data, MDL);
+ }
+
+ /*
+ * If we wish to limit the client to a certain number of
+ * addresses, then omit the address from the reply.
+ */
+ if (reply->client_resources >= limit)
+ goto cleanup;
+ }
+
+ status = reply_process_is_addressed(reply, scope, group);
+ if (status != ISC_R_SUCCESS)
+ goto cleanup;
+
+ send_addr:
+ status = reply_process_send_addr(reply, &tmp_addr);
+
+ cleanup:
+ if (iaaddr.data != NULL)
+ data_string_forget(&iaaddr, MDL);
+ if (data.data != NULL)
+ data_string_forget(&data, MDL);
+ if (reply->lease != NULL)
+ iasubopt_dereference(&reply->lease, MDL);
+
+ return status;
+}
+
+/*
+ * Verify the address belongs to the client. If we've got a host
+ * record with a fixed address, it has to be the assigned address
+ * (fault out all else). Otherwise it's a dynamic address, so lookup
+ * that address and make sure it belongs to this DUID:IAID pair.
+ */
+static isc_boolean_t
+address_is_owned(struct reply_state *reply, struct iaddr *addr) {
+ int i;
+
+ /*
+ * This faults out addresses that don't match fixed addresses.
+ */
+ if (reply->static_lease) {
+ if (reply->fixed.data == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ if (memcmp(addr->iabuf, reply->fixed.data, 16) == 0)
+ return (ISC_TRUE);
+
+ return (ISC_FALSE);
+ }
+
+ if ((reply->old_ia == NULL) || (reply->old_ia->num_iasubopt == 0))
+ return (ISC_FALSE);
+
+ for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) {
+ struct iasubopt *tmp;
+
+ tmp = reply->old_ia->iasubopt[i];
+
+ if (memcmp(addr->iabuf, &tmp->addr, 16) == 0) {
+ if (lease6_usable(tmp) == ISC_FALSE) {
+ return (ISC_FALSE);
+ }
+ iasubopt_reference(&reply->lease, tmp, MDL);
+ return (ISC_TRUE);
+ }
+ }
+
+ return (ISC_FALSE);
+}
+
+/* Process a client-supplied IA_TA. This may append options to the tail of
+ * the reply packet being built in the reply_state structure.
+ */
+static isc_result_t
+reply_process_ia_ta(struct reply_state *reply, struct option_cache *ia) {
+ isc_result_t status = ISC_R_SUCCESS;
+ u_int32_t iaid;
+ unsigned ia_cursor;
+ struct option_state *packet_ia;
+ struct option_cache *oc;
+ struct data_string ia_data, data;
+ struct data_string iaaddr;
+ u_int32_t pref_life, valid_life;
+ struct iaddr tmp_addr;
+
+ /* Initialize values that will get cleaned up on return. */
+ packet_ia = NULL;
+ memset(&ia_data, 0, sizeof(ia_data));
+ memset(&data, 0, sizeof(data));
+ memset(&iaaddr, 0, sizeof(iaaddr));
+
+ /* Make sure there is at least room for the header. */
+ if ((reply->cursor + IA_TA_OFFSET + 4) > sizeof(reply->buf)) {
+ log_error("reply_process_ia_ta: Reply too long for IA.");
+ return ISC_R_NOSPACE;
+ }
+
+
+ /* Fetch the IA_TA contents. */
+ if (!get_encapsulated_IA_state(&packet_ia, &ia_data, reply->packet,
+ ia, IA_TA_OFFSET)) {
+ log_error("reply_process_ia_ta: error evaluating ia");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ /* Extract IA_TA header contents. */
+ iaid = getULong(ia_data.data);
+
+ /* Create an IA_TA structure. */
+ if (ia_allocate(&reply->ia, iaid, (char *)reply->client_id.data,
+ reply->client_id.len, MDL) != ISC_R_SUCCESS) {
+ log_error("reply_process_ia_ta: no memory for ia.");
+ status = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ reply->ia->ia_type = D6O_IA_TA;
+
+ /* Cache pre-existing IA, if any. */
+ ia_hash_lookup(&reply->old_ia, ia_ta_active,
+ (unsigned char *)reply->ia->iaid_duid.data,
+ reply->ia->iaid_duid.len, MDL);
+
+ /*
+ * Create an option cache to carry the IA_TA option contents, and
+ * execute any user-supplied values into it.
+ */
+ if (!option_state_allocate(&reply->reply_ia, MDL)) {
+ status = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ /*
+ * Temporary leases are dynamic by definition.
+ */
+ reply->static_lease = ISC_FALSE;
+
+ /*
+ * Save the cursor position at the start of the IA, so we can
+ * set length later. We write a temporary
+ * header out now just in case we decide to adjust the packet
+ * within sub-process functions.
+ */
+ ia_cursor = reply->cursor;
+
+ /* Initialize the IA_TA header. First the code. */
+ putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_TA);
+ reply->cursor += 2;
+
+ /* Then option length. */
+ putUShort(reply->buf.data + reply->cursor, 0x04u);
+ reply->cursor += 2;
+
+ /* Then IA_TA header contents; IAID. */
+ putULong(reply->buf.data + reply->cursor, iaid);
+ reply->cursor += 4;
+
+ /*
+ * Deal with an IAADDR for lifetimes.
+ * For all or none, process IAADDRs as hints.
+ */
+ reply->valid = reply->prefer = 0xffffffff;
+ reply->client_valid = reply->client_prefer = 0;
+ oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAADDR);
+ for (; oc != NULL; oc = oc->next) {
+ memset(&iaaddr, 0, sizeof(iaaddr));
+ if (!evaluate_option_cache(&iaaddr, reply->packet,
+ NULL, NULL,
+ reply->packet->options, NULL,
+ &global_scope, oc, MDL) ||
+ (iaaddr.len < IAADDR_OFFSET)) {
+ log_error("reply_process_ia_ta: error "
+ "evaluating IAADDR.");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ /* The first 16 bytes are the IPv6 address. */
+ pref_life = getULong(iaaddr.data + 16);
+ valid_life = getULong(iaaddr.data + 20);
+
+ if ((reply->client_valid == 0) ||
+ (reply->client_valid > valid_life))
+ reply->client_valid = valid_life;
+
+ if ((reply->client_prefer == 0) ||
+ (reply->client_prefer > pref_life))
+ reply->client_prefer = pref_life;
+
+ /* Nothing more if something has failed. */
+ if (status == ISC_R_CANCELED)
+ continue;
+
+ tmp_addr.len = 16;
+ memcpy(tmp_addr.iabuf, iaaddr.data, 16);
+ if (!temporary_is_available(reply, &tmp_addr))
+ goto bad_temp;
+ status = reply_process_is_addressed(reply,
+ &reply->lease->scope,
+ reply->shared->group);
+ if (status != ISC_R_SUCCESS)
+ goto bad_temp;
+ status = reply_process_send_addr(reply, &tmp_addr);
+ if (status != ISC_R_SUCCESS)
+ goto bad_temp;
+ if (reply->lease != NULL)
+ iasubopt_dereference(&reply->lease, MDL);
+ continue;
+
+ bad_temp:
+ /* Rewind the IA_TA to empty. */
+ option_state_dereference(&reply->reply_ia, MDL);
+ if (!option_state_allocate(&reply->reply_ia, MDL)) {
+ status = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ status = ISC_R_CANCELED;
+ reply->client_resources = 0;
+ reply->resources_included = ISC_FALSE;
+ if (reply->lease != NULL)
+ iasubopt_dereference(&reply->lease, MDL);
+ }
+ reply->ia_count++;
+
+ /*
+ * Give the client temporary addresses.
+ */
+ if (reply->client_resources != 0)
+ goto store;
+ status = find_client_temporaries(reply);
+ if (status == ISC_R_NORESOURCES) {
+ switch (reply->packet->dhcpv6_msg_type) {
+ case DHCPV6_SOLICIT:
+ /*
+ * No address for any IA is handled
+ * by the caller.
+ */
+ /* FALL THROUGH */
+
+ case DHCPV6_REQUEST:
+ /* Section 18.2.1 (Request):
+ *
+ * If the server cannot assign any addresses to
+ * an IA in the message from the client, the
+ * server MUST include the IA in the Reply
+ * message with no addresses in the IA and a
+ * Status Code option in the IA containing
+ * status code NoAddrsAvail.
+ */
+ option_state_dereference(&reply->reply_ia, MDL);
+ if (!option_state_allocate(&reply->reply_ia, MDL)) {
+ log_error("reply_process_ia_ta: No "
+ "memory for option state wipe.");
+ status = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ if (!set_status_code(STATUS_NoAddrsAvail,
+ "No addresses available "
+ "for this interface.",
+ reply->reply_ia)) {
+ log_error("reply_process_ia_ta: Unable "
+ "to set NoAddrsAvail status code.");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ status = ISC_R_SUCCESS;
+ break;
+
+ default:
+ /*
+ * We don't want to include the IA if we
+ * provide zero addresses including zeroed
+ * lifetimes.
+ */
+ if (reply->resources_included)
+ status = ISC_R_SUCCESS;
+ else
+ goto cleanup;
+ break;
+ }
+ } else if (status != ISC_R_SUCCESS)
+ goto cleanup;
+
+ store:
+ reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
+ sizeof(reply->buf) - reply->cursor,
+ reply->reply_ia, reply->packet,
+ required_opts_IA, NULL);
+
+ /* Reset the length of this IA to match what was just written. */
+ putUShort(reply->buf.data + ia_cursor + 2,
+ reply->cursor - (ia_cursor + 4));
+
+ /*
+ * yes, goto's aren't the best but we also want to avoid extra
+ * indents
+ */
+ if (status == ISC_R_CANCELED)
+ goto cleanup;
+
+#if defined(LOG_V6_ADDRESSES)
+ /*
+ * If we have any addresses log what we are doing.
+ */
+ if (reply->ia->num_iasubopt != 0) {
+ struct iasubopt *tmp;
+ int i;
+ char tmp_addr[INET6_ADDRSTRLEN];
+
+ for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
+ tmp = reply->ia->iasubopt[i];
+
+ log_info("%s TA: address %s to client with duid %s "
+ "iaid = %d valid for %d seconds",
+ dhcpv6_type_names[reply->buf.reply.msg_type],
+ inet_ntop(AF_INET6, &tmp->addr,
+ tmp_addr, sizeof(tmp_addr)),
+ print_hex_1(reply->client_id.len,
+ reply->client_id.data, 60),
+ iaid,
+ tmp->valid);
+ }
+ }
+#endif
+
+ /*
+ * For hard bindings we consume the new changes into
+ * the database (if any have been attached to the ia_ta).
+ *
+ * Loop through the assigned dynamic addresses, referencing the
+ * leases onto this IA_TA rather than any old ones, and updating
+ * pool timers for each (if any).
+ */
+ if ((reply->ia->num_iasubopt != 0) &&
+ (reply->buf.reply.msg_type == DHCPV6_REPLY)) {
+ struct iasubopt *tmp;
+ struct data_string *ia_id;
+ int i;
+
+ for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
+ tmp = reply->ia->iasubopt[i];
+
+ if (tmp->ia != NULL)
+ ia_dereference(&tmp->ia, MDL);
+ ia_reference(&tmp->ia, reply->ia, MDL);
+
+ /* Commit 'hard' bindings. */
+ renew_lease6(tmp->ipv6_pool, tmp);
+ schedule_lease_timeout(tmp->ipv6_pool);
+
+#if defined (NSUPDATE)
+ /*
+ * Perform ddns updates.
+ */
+ oc = lookup_option(&server_universe, reply->opt_state,
+ SV_DDNS_UPDATES);
+ if ((oc == NULL) ||
+ evaluate_boolean_option_cache(NULL, reply->packet,
+ NULL, NULL,
+ reply->packet->options,
+ reply->opt_state,
+ &tmp->scope,
+ oc, MDL)) {
+ ddns_updates(reply->packet, NULL, NULL,
+ tmp, NULL, reply->opt_state);
+ }
+#endif
+ }
+
+ /* Remove any old ia from the hash. */
+ if (reply->old_ia != NULL) {
+ ia_id = &reply->old_ia->iaid_duid;
+ ia_hash_delete(ia_ta_active,
+ (unsigned char *)ia_id->data,
+ ia_id->len, MDL);
+ ia_dereference(&reply->old_ia, MDL);
+ }
+
+ /* Put new ia into the hash. */
+ reply->ia->cltt = cur_time;
+ ia_id = &reply->ia->iaid_duid;
+ ia_hash_add(ia_ta_active, (unsigned char *)ia_id->data,
+ ia_id->len, reply->ia, MDL);
+
+ write_ia(reply->ia);
+ } else {
+ schedule_lease_timeout_reply(reply);
+ }
+
+ cleanup:
+ if (packet_ia != NULL)
+ option_state_dereference(&packet_ia, MDL);
+ if (iaaddr.data != NULL)
+ data_string_forget(&iaaddr, MDL);
+ if (reply->reply_ia != NULL)
+ option_state_dereference(&reply->reply_ia, MDL);
+ if (ia_data.data != NULL)
+ data_string_forget(&ia_data, MDL);
+ if (data.data != NULL)
+ data_string_forget(&data, MDL);
+ if (reply->ia != NULL)
+ ia_dereference(&reply->ia, MDL);
+ if (reply->old_ia != NULL)
+ ia_dereference(&reply->old_ia, MDL);
+ if (reply->lease != NULL)
+ iasubopt_dereference(&reply->lease, MDL);
+
+ /*
+ * ISC_R_CANCELED is a status code used by the addr processing to
+ * indicate we're replying with other addresses. This is still a
+ * success at higher layers.
+ */
+ return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status);
+}
+
+/*
+ * Verify the temporary address is available.
+ */
+static isc_boolean_t
+temporary_is_available(struct reply_state *reply, struct iaddr *addr) {
+ struct in6_addr tmp_addr;
+ struct subnet *subnet;
+ struct ipv6_pool *pool;
+ int i;
+
+ memcpy(&tmp_addr, addr->iabuf, sizeof(tmp_addr));
+ /*
+ * Clients may choose to send :: as an address, with the idea to give
+ * hints about preferred-lifetime or valid-lifetime.
+ * So this is not a request for this address.
+ */
+ if (IN6_IS_ADDR_UNSPECIFIED(&tmp_addr))
+ return ISC_FALSE;
+
+ /*
+ * Verify that this address is on the client's network.
+ */
+ for (subnet = reply->shared->subnets ; subnet != NULL ;
+ subnet = subnet->next_sibling) {
+ if (addr_eq(subnet_number(*addr, subnet->netmask),
+ subnet->net))
+ break;
+ }
+
+ /* Address not found on shared network. */
+ if (subnet == NULL)
+ return ISC_FALSE;
+
+ /*
+ * Check if this address is owned (must be before next step).
+ */
+ if (address_is_owned(reply, addr))
+ return ISC_TRUE;
+
+ /*
+ * Verify that this address is in a temporary pool and try to get it.
+ */
+ if (reply->shared->ipv6_pools == NULL)
+ return ISC_FALSE;
+ for (i = 0 ; (pool = reply->shared->ipv6_pools[i]) != NULL ; i++) {
+ if (pool->pool_type != D6O_IA_TA)
+ continue;
+ if (ipv6_in_pool(&tmp_addr, pool))
+ break;
+ }
+ if (pool == NULL)
+ return ISC_FALSE;
+ if (lease6_exists(pool, &tmp_addr))
+ return ISC_FALSE;
+ if (iasubopt_allocate(&reply->lease, MDL) != ISC_R_SUCCESS)
+ return ISC_FALSE;
+ reply->lease->addr = tmp_addr;
+ reply->lease->plen = 0;
+ /* Default is soft binding for 2 minutes. */
+ if (add_lease6(pool, reply->lease, cur_time + 120) != ISC_R_SUCCESS)
+ return ISC_FALSE;
+
+ return ISC_TRUE;
+}
+
+/*
+ * Get a temporary address per prefix.
+ */
+static isc_result_t
+find_client_temporaries(struct reply_state *reply) {
+ struct shared_network *shared;
+ int i;
+ struct ipv6_pool *p;
+ isc_result_t status;
+ unsigned int attempts;
+ struct iaddr send_addr;
+
+ /*
+ * No pools, we're done.
+ */
+ shared = reply->shared;
+ if (shared->ipv6_pools == NULL) {
+ log_debug("Unable to get client addresses: "
+ "no IPv6 pools on this shared network");
+ return ISC_R_NORESOURCES;
+ }
+
+ status = ISC_R_NORESOURCES;
+ for (i = 0;; i++) {
+ p = shared->ipv6_pools[i];
+ if (p == NULL) {
+ break;
+ }
+ if (p->pool_type != D6O_IA_TA) {
+ continue;
+ }
+
+ /*
+ * Get an address in this temporary pool.
+ */
+ status = create_lease6(p, &reply->lease, &attempts,
+ &reply->client_id, cur_time + 120);
+ if (status != ISC_R_SUCCESS) {
+ log_debug("Unable to get a temporary address.");
+ goto cleanup;
+ }
+
+ status = reply_process_is_addressed(reply,
+ &reply->lease->scope,
+ reply->lease->ipv6_pool->subnet->group);
+ if (status != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ send_addr.len = 16;
+ memcpy(send_addr.iabuf, &reply->lease->addr, 16);
+ status = reply_process_send_addr(reply, &send_addr);
+ if (status != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ /*
+ * reply->lease can't be null as we use it above
+ * add check if that changes
+ */
+ iasubopt_dereference(&reply->lease, MDL);
+ }
+
+ cleanup:
+ if (reply->lease != NULL) {
+ iasubopt_dereference(&reply->lease, MDL);
+ }
+ return status;
+}
+
+/*
+ * This function only returns failure on 'hard' failures. If it succeeds,
+ * it will leave a lease structure behind.
+ */
+static isc_result_t
+reply_process_try_addr(struct reply_state *reply, struct iaddr *addr) {
+ isc_result_t status = ISC_R_ADDRNOTAVAIL;
+ struct ipv6_pool *pool;
+ int i;
+ struct data_string data_addr;
+
+ if ((reply == NULL) || (reply->shared == NULL) ||
+ (addr == NULL) || (reply->lease != NULL))
+ return (DHCP_R_INVALIDARG);
+
+ if (reply->shared->ipv6_pools == NULL)
+ return (ISC_R_ADDRNOTAVAIL);
+
+ memset(&data_addr, 0, sizeof(data_addr));
+ data_addr.len = addr->len;
+ data_addr.data = addr->iabuf;
+
+ for (i = 0 ; (pool = reply->shared->ipv6_pools[i]) != NULL ; i++) {
+ if (pool->pool_type != D6O_IA_NA)
+ continue;
+ status = try_client_v6_address(&reply->lease, pool,
+ &data_addr);
+ if (status == ISC_R_SUCCESS)
+ break;
+ }
+
+ /* Note that this is just pedantry. There is no allocation to free. */
+ data_string_forget(&data_addr, MDL);
+ /* Return just the most recent status... */
+ return (status);
+}
+
+/* Look around for an address to give the client. First, look through the
+ * old IA for addresses we can extend. Second, try to allocate a new address.
+ * Finally, actually add that address into the current reply IA.
+ */
+static isc_result_t
+find_client_address(struct reply_state *reply) {
+ struct iaddr send_addr;
+ isc_result_t status = ISC_R_NORESOURCES;
+ struct iasubopt *lease, *best_lease = NULL;
+ struct binding_scope **scope;
+ struct group *group;
+ int i;
+
+ if (reply->static_lease) {
+ if (reply->host == NULL)
+ return DHCP_R_INVALIDARG;
+
+ send_addr.len = 16;
+ memcpy(send_addr.iabuf, reply->fixed.data, 16);
+
+ scope = &global_scope;
+ group = reply->subnet->group;
+ goto send_addr;
+ }
+
+ if (reply->old_ia != NULL) {
+ for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) {
+ struct shared_network *candidate_shared;
+
+ lease = reply->old_ia->iasubopt[i];
+ candidate_shared = lease->ipv6_pool->shared_network;
+
+ /*
+ * Look for the best lease on the client's shared
+ * network.
+ */
+ if ((candidate_shared == reply->shared) &&
+ (lease6_usable(lease) == ISC_TRUE)) {
+ best_lease = lease_compare(lease, best_lease);
+ }
+ }
+ }
+
+ /* Try to pick a new address if we didn't find one, or if we found an
+ * abandoned lease.
+ */
+ if ((best_lease == NULL) || (best_lease->state == FTS_ABANDONED)) {
+ status = pick_v6_address(&reply->lease, reply->shared,
+ &reply->ia->iaid_duid);
+ } else if (best_lease != NULL) {
+ iasubopt_reference(&reply->lease, best_lease, MDL);
+ status = ISC_R_SUCCESS;
+ }
+
+ /* Pick the abandoned lease as a last resort. */
+ if ((status == ISC_R_NORESOURCES) && (best_lease != NULL)) {
+ /* I don't see how this is supposed to be done right now. */
+ log_error("Reclaiming abandoned addresses is not yet "
+ "supported. Treating this as an out of space "
+ "condition.");
+ /* iasubopt_reference(&reply->lease, best_lease, MDL); */
+ }
+
+ /* Give up now if we didn't find a lease. */
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ if (reply->lease == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ /* Draw binding scopes from the lease's binding scope, and config
+ * from the lease's containing subnet and higher. Note that it may
+ * be desirable to place the group attachment directly in the pool.
+ */
+ scope = &reply->lease->scope;
+ group = reply->lease->ipv6_pool->subnet->group;
+
+ send_addr.len = 16;
+ memcpy(send_addr.iabuf, &reply->lease->addr, 16);
+
+ send_addr:
+ status = reply_process_is_addressed(reply, scope, group);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = reply_process_send_addr(reply, &send_addr);
+ return status;
+}
+
+/* Once an address is found for a client, perform several common functions;
+ * Calculate and store valid and preferred lease times, draw client options
+ * into the option state.
+ */
+static isc_result_t
+reply_process_is_addressed(struct reply_state *reply,
+ struct binding_scope **scope, struct group *group)
+{
+ isc_result_t status = ISC_R_SUCCESS;
+ struct data_string data;
+ struct option_cache *oc;
+
+ /* Initialize values we will cleanup. */
+ memset(&data, 0, sizeof(data));
+
+ /*
+ * Bring configured options into the root packet level cache - start
+ * with the lease's closest enclosing group (passed in by the caller
+ * as 'group').
+ */
+ execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+ reply->packet->options, reply->opt_state,
+ scope, group, root_group);
+
+ /*
+ * If there is a host record, over-ride with values configured there,
+ * without re-evaluating configuration from the previously executed
+ * group or its common enclosers.
+ */
+ if (reply->host != NULL)
+ execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+ reply->packet->options,
+ reply->opt_state, scope,
+ reply->host->group, group);
+
+ /* Determine valid lifetime. */
+ if (reply->client_valid == 0)
+ reply->send_valid = DEFAULT_DEFAULT_LEASE_TIME;
+ else
+ reply->send_valid = reply->client_valid;
+
+ oc = lookup_option(&server_universe, reply->opt_state,
+ SV_DEFAULT_LEASE_TIME);
+ if (oc != NULL) {
+ if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
+ reply->packet->options,
+ reply->opt_state,
+ scope, oc, MDL) ||
+ (data.len != 4)) {
+ log_error("reply_process_is_addressed: unable to "
+ "evaluate default lease time");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ reply->send_valid = getULong(data.data);
+ data_string_forget(&data, MDL);
+ }
+
+ if (reply->client_prefer == 0)
+ reply->send_prefer = reply->send_valid;
+ else
+ reply->send_prefer = reply->client_prefer;
+
+ if (reply->send_prefer >= reply->send_valid)
+ reply->send_prefer = (reply->send_valid / 2) +
+ (reply->send_valid / 8);
+
+ oc = lookup_option(&server_universe, reply->opt_state,
+ SV_PREFER_LIFETIME);
+ if (oc != NULL) {
+ if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
+ reply->packet->options,
+ reply->opt_state,
+ scope, oc, MDL) ||
+ (data.len != 4)) {
+ log_error("reply_process_is_addressed: unable to "
+ "evaluate preferred lease time");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ reply->send_prefer = getULong(data.data);
+ data_string_forget(&data, MDL);
+ }
+
+ /* Note lowest values for later calculation of renew/rebind times. */
+ if (reply->prefer > reply->send_prefer)
+ reply->prefer = reply->send_prefer;
+
+ if (reply->valid > reply->send_valid)
+ reply->valid = reply->send_valid;
+
+#if 0
+ /*
+ * XXX: Old 4.0.0 alpha code would change the host {} record
+ * XXX: uid upon lease assignment. This was intended to cover the
+ * XXX: case where a client first identifies itself using vendor
+ * XXX: options in a solicit, or request, but later neglects to include
+ * XXX: these options in a Renew or Rebind. It is not clear that this
+ * XXX: is required, and has some startling ramifications (such as
+ * XXX: how to recover this dynamic host {} state across restarts).
+ */
+ if (reply->host != NULL)
+ change_host_uid(host, reply->client_id->data,
+ reply->client_id->len);
+#endif /* 0 */
+
+ /* Perform dynamic lease related update work. */
+ if (reply->lease != NULL) {
+ /* Cached lifetimes */
+ reply->lease->prefer = reply->send_prefer;
+ reply->lease->valid = reply->send_valid;
+
+ /* Advance (or rewind) the valid lifetime. */
+ if (reply->buf.reply.msg_type == DHCPV6_REPLY) {
+ reply->lease->soft_lifetime_end_time =
+ cur_time + reply->send_valid;
+ /* Wait before renew! */
+ }
+
+ status = ia_add_iasubopt(reply->ia, reply->lease, MDL);
+ if (status != ISC_R_SUCCESS) {
+ log_fatal("reply_process_is_addressed: Unable to "
+ "attach lease to new IA: %s",
+ isc_result_totext(status));
+ }
+
+ /*
+ * If this is a new lease, make sure it is attached somewhere.
+ */
+ if (reply->lease->ia == NULL) {
+ ia_reference(&reply->lease->ia, reply->ia, MDL);
+ }
+ }
+
+ /* Bring a copy of the relevant options into the IA scope. */
+ execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+ reply->packet->options, reply->reply_ia,
+ scope, group, root_group);
+
+ /*
+ * And bring in host record configuration, if any, but not to overlap
+ * the previous group or its common enclosers.
+ */
+ if (reply->host != NULL)
+ execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+ reply->packet->options,
+ reply->reply_ia, scope,
+ reply->host->group, group);
+
+ cleanup:
+ if (data.data != NULL)
+ data_string_forget(&data, MDL);
+
+ if (status == ISC_R_SUCCESS)
+ reply->client_resources++;
+
+ return status;
+}
+
+/* Simply send an IAADDR within the IA scope as described. */
+static isc_result_t
+reply_process_send_addr(struct reply_state *reply, struct iaddr *addr) {
+ isc_result_t status = ISC_R_SUCCESS;
+ struct data_string data;
+
+ memset(&data, 0, sizeof(data));
+
+ /* Now append the lease. */
+ data.len = IAADDR_OFFSET;
+ if (!buffer_allocate(&data.buffer, data.len, MDL)) {
+ log_error("reply_process_send_addr: out of memory"
+ "allocating new IAADDR buffer.");
+ status = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ data.data = data.buffer->data;
+
+ memcpy(data.buffer->data, addr->iabuf, 16);
+ putULong(data.buffer->data + 16, reply->send_prefer);
+ putULong(data.buffer->data + 20, reply->send_valid);
+
+ if (!append_option_buffer(&dhcpv6_universe, reply->reply_ia,
+ data.buffer, data.buffer->data,
+ data.len, D6O_IAADDR, 0)) {
+ log_error("reply_process_send_addr: unable "
+ "to save IAADDR option");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ reply->resources_included = ISC_TRUE;
+
+ cleanup:
+ if (data.data != NULL)
+ data_string_forget(&data, MDL);
+
+ return status;
+}
+
+/* Choose the better of two leases. */
+static struct iasubopt *
+lease_compare(struct iasubopt *alpha, struct iasubopt *beta) {
+ if (alpha == NULL)
+ return beta;
+ if (beta == NULL)
+ return alpha;
+
+ switch(alpha->state) {
+ case FTS_ACTIVE:
+ switch(beta->state) {
+ case FTS_ACTIVE:
+ /* Choose the lease with the longest lifetime (most
+ * likely the most recently allocated).
+ */
+ if (alpha->hard_lifetime_end_time <
+ beta->hard_lifetime_end_time)
+ return beta;
+ else
+ return alpha;
+
+ case FTS_EXPIRED:
+ case FTS_ABANDONED:
+ return alpha;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ }
+ break;
+
+ case FTS_EXPIRED:
+ switch (beta->state) {
+ case FTS_ACTIVE:
+ return beta;
+
+ case FTS_EXPIRED:
+ /* Choose the most recently expired lease. */
+ if (alpha->hard_lifetime_end_time <
+ beta->hard_lifetime_end_time)
+ return beta;
+ else if ((alpha->hard_lifetime_end_time ==
+ beta->hard_lifetime_end_time) &&
+ (alpha->soft_lifetime_end_time <
+ beta->soft_lifetime_end_time))
+ return beta;
+ else
+ return alpha;
+
+ case FTS_ABANDONED:
+ return alpha;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ }
+ break;
+
+ case FTS_ABANDONED:
+ switch (beta->state) {
+ case FTS_ACTIVE:
+ case FTS_EXPIRED:
+ return alpha;
+
+ case FTS_ABANDONED:
+ /* Choose the lease that was abandoned longest ago. */
+ if (alpha->hard_lifetime_end_time <
+ beta->hard_lifetime_end_time)
+ return alpha;
+ else
+ return beta;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ }
+ break;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ }
+
+ log_fatal("Triple impossible condition at %s:%d.", MDL);
+ return NULL;
+}
+
+/* Process a client-supplied IA_PD. This may append options to the tail of
+ * the reply packet being built in the reply_state structure.
+ */
+static isc_result_t
+reply_process_ia_pd(struct reply_state *reply, struct option_cache *ia) {
+ isc_result_t status = ISC_R_SUCCESS;
+ u_int32_t iaid;
+ unsigned ia_cursor;
+ struct option_state *packet_ia;
+ struct option_cache *oc;
+ struct data_string ia_data, data;
+
+ /* Initialize values that will get cleaned up on return. */
+ packet_ia = NULL;
+ memset(&ia_data, 0, sizeof(ia_data));
+ memset(&data, 0, sizeof(data));
+ /*
+ * Note that find_client_prefix() may set reply->lease.
+ */
+
+ /* Make sure there is at least room for the header. */
+ if ((reply->cursor + IA_PD_OFFSET + 4) > sizeof(reply->buf)) {
+ log_error("reply_process_ia_pd: Reply too long for IA.");
+ return ISC_R_NOSPACE;
+ }
+
+
+ /* Fetch the IA_PD contents. */
+ if (!get_encapsulated_IA_state(&packet_ia, &ia_data, reply->packet,
+ ia, IA_PD_OFFSET)) {
+ log_error("reply_process_ia_pd: error evaluating ia");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ /* Extract IA_PD header contents. */
+ iaid = getULong(ia_data.data);
+ reply->renew = getULong(ia_data.data + 4);
+ reply->rebind = getULong(ia_data.data + 8);
+
+ /* Create an IA_PD structure. */
+ if (ia_allocate(&reply->ia, iaid, (char *)reply->client_id.data,
+ reply->client_id.len, MDL) != ISC_R_SUCCESS) {
+ log_error("reply_process_ia_pd: no memory for ia.");
+ status = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ reply->ia->ia_type = D6O_IA_PD;
+
+ /* Cache pre-existing IA_PD, if any. */
+ ia_hash_lookup(&reply->old_ia, ia_pd_active,
+ (unsigned char *)reply->ia->iaid_duid.data,
+ reply->ia->iaid_duid.len, MDL);
+
+ /*
+ * Create an option cache to carry the IA_PD option contents, and
+ * execute any user-supplied values into it.
+ */
+ if (!option_state_allocate(&reply->reply_ia, MDL)) {
+ status = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ /* Check & count the fixed prefix host records. */
+ reply->static_prefixes = 0;
+ if ((reply->host != NULL) && (reply->host->fixed_prefix != NULL)) {
+ struct iaddrcidrnetlist *fp;
+
+ for (fp = reply->host->fixed_prefix; fp != NULL;
+ fp = fp->next) {
+ reply->static_prefixes += 1;
+ }
+ }
+
+ /*
+ * Save the cursor position at the start of the IA_PD, so we can
+ * set length and adjust t1/t2 values later. We write a temporary
+ * header out now just in case we decide to adjust the packet
+ * within sub-process functions.
+ */
+ ia_cursor = reply->cursor;
+
+ /* Initialize the IA_PD header. First the code. */
+ putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_PD);
+ reply->cursor += 2;
+
+ /* Then option length. */
+ putUShort(reply->buf.data + reply->cursor, 0x0Cu);
+ reply->cursor += 2;
+
+ /* Then IA_PD header contents; IAID. */
+ putULong(reply->buf.data + reply->cursor, iaid);
+ reply->cursor += 4;
+
+ /* We store the client's t1 for now, and may over-ride it later. */
+ putULong(reply->buf.data + reply->cursor, reply->renew);
+ reply->cursor += 4;
+
+ /* We store the client's t2 for now, and may over-ride it later. */
+ putULong(reply->buf.data + reply->cursor, reply->rebind);
+ reply->cursor += 4;
+
+ /*
+ * For each prefix in this IA_PD, decide what to do about it.
+ */
+ oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAPREFIX);
+ reply->valid = reply->prefer = 0xffffffff;
+ reply->client_valid = reply->client_prefer = 0;
+ reply->preflen = -1;
+ for (; oc != NULL ; oc = oc->next) {
+ status = reply_process_prefix(reply, oc);
+
+ /*
+ * Canceled means we did not allocate prefixes to the
+ * client, but we're "done" with this IA - we set a status
+ * code. So transmit this reply, e.g., move on to the next
+ * IA.
+ */
+ if (status == ISC_R_CANCELED)
+ break;
+
+ if ((status != ISC_R_SUCCESS) &&
+ (status != ISC_R_ADDRINUSE) &&
+ (status != ISC_R_ADDRNOTAVAIL))
+ goto cleanup;
+ }
+
+ reply->pd_count++;
+
+ /*
+ * If we fell through the above and never gave the client
+ * a prefix, give it one now.
+ */
+ if ((status != ISC_R_CANCELED) && (reply->client_resources == 0)) {
+ status = find_client_prefix(reply);
+
+ if (status == ISC_R_NORESOURCES) {
+ switch (reply->packet->dhcpv6_msg_type) {
+ case DHCPV6_SOLICIT:
+ /*
+ * No prefix for any IA is handled
+ * by the caller.
+ */
+ /* FALL THROUGH */
+
+ case DHCPV6_REQUEST:
+ /* Same than for addresses. */
+ option_state_dereference(&reply->reply_ia, MDL);
+ if (!option_state_allocate(&reply->reply_ia,
+ MDL))
+ {
+ log_error("reply_process_ia_pd: No "
+ "memory for option state "
+ "wipe.");
+ status = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ if (!set_status_code(STATUS_NoPrefixAvail,
+ "No prefixes available "
+ "for this interface.",
+ reply->reply_ia)) {
+ log_error("reply_process_ia_pd: "
+ "Unable to set "
+ "NoPrefixAvail status "
+ "code.");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ status = ISC_R_SUCCESS;
+ break;
+
+ default:
+ if (reply->resources_included)
+ status = ISC_R_SUCCESS;
+ else
+ goto cleanup;
+ break;
+ }
+ }
+
+ if (status != ISC_R_SUCCESS)
+ goto cleanup;
+ }
+
+ reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
+ sizeof(reply->buf) - reply->cursor,
+ reply->reply_ia, reply->packet,
+ required_opts_IA_PD, NULL);
+
+ /* Reset the length of this IA_PD to match what was just written. */
+ putUShort(reply->buf.data + ia_cursor + 2,
+ reply->cursor - (ia_cursor + 4));
+
+ /*
+ * T1/T2 time selection is kind of weird. We actually use DHCP
+ * (v4) scoped options as handy existing places where these might
+ * be configured by an administrator. A value of zero tells the
+ * client it may choose its own renewal time.
+ */
+ reply->renew = 0;
+ oc = lookup_option(&dhcp_universe, reply->opt_state,
+ DHO_DHCP_RENEWAL_TIME);
+ if (oc != NULL) {
+ if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
+ reply->packet->options,
+ reply->opt_state, &global_scope,
+ oc, MDL) ||
+ (data.len != 4)) {
+ log_error("Invalid renewal time.");
+ } else {
+ reply->renew = getULong(data.data);
+ }
+
+ if (data.data != NULL)
+ data_string_forget(&data, MDL);
+ }
+ putULong(reply->buf.data + ia_cursor + 8, reply->renew);
+
+ /* Now T2. */
+ reply->rebind = 0;
+ oc = lookup_option(&dhcp_universe, reply->opt_state,
+ DHO_DHCP_REBINDING_TIME);
+ if (oc != NULL) {
+ if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
+ reply->packet->options,
+ reply->opt_state, &global_scope,
+ oc, MDL) ||
+ (data.len != 4)) {
+ log_error("Invalid rebinding time.");
+ } else {
+ reply->rebind = getULong(data.data);
+ }
+
+ if (data.data != NULL)
+ data_string_forget(&data, MDL);
+ }
+ putULong(reply->buf.data + ia_cursor + 12, reply->rebind);
+
+ /*
+ * yes, goto's aren't the best but we also want to avoid extra
+ * indents
+ */
+ if (status == ISC_R_CANCELED)
+ goto cleanup;
+
+ /*
+ * Handle static prefixes, we always log stuff and if it's
+ * a hard binding we run any commit statements that we have
+ */
+ if (reply->static_prefixes != 0) {
+#if defined(LOG_V6_ADDRESSES)
+ char tmp_addr[INET6_ADDRSTRLEN];
+ log_info("%s PD: address %s/%d to client with duid %s "
+ "iaid = %d static",
+ dhcpv6_type_names[reply->buf.reply.msg_type],
+ inet_ntop(AF_INET6, reply->fixed_pref.lo_addr.iabuf,
+ tmp_addr, sizeof(tmp_addr)),
+ reply->fixed_pref.bits,
+ print_hex_1(reply->client_id.len,
+ reply->client_id.data, 60),
+ iaid);
+#endif
+ goto cleanup;
+ }
+
+#if defined(LOG_V6_ADDRESSES)
+ /*
+ * If we have any addresses log what we are doing.
+ */
+ if (reply->ia->num_iasubopt != 0) {
+ struct iasubopt *tmp;
+ int i;
+ char tmp_addr[INET6_ADDRSTRLEN];
+
+ for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
+ tmp = reply->ia->iasubopt[i];
+
+ log_info("%s PD: address %s/%d to client with duid %s"
+ " iaid = %d valid for %d seconds",
+ dhcpv6_type_names[reply->buf.reply.msg_type],
+ inet_ntop(AF_INET6, &tmp->addr,
+ tmp_addr, sizeof(tmp_addr)),
+ (int)tmp->plen,
+ print_hex_1(reply->client_id.len,
+ reply->client_id.data, 60),
+ iaid, tmp->valid);
+ }
+ }
+#endif
+
+ /*
+ * If this is not a 'soft' binding, consume the new changes into
+ * the database (if any have been attached to the ia_pd).
+ *
+ * Loop through the assigned dynamic prefixes, referencing the
+ * prefixes onto this IA_PD rather than any old ones, and updating
+ * prefix pool timers for each (if any).
+ */
+ if ((reply->buf.reply.msg_type == DHCPV6_REPLY) &&
+ (reply->ia->num_iasubopt != 0)) {
+ struct iasubopt *tmp;
+ struct data_string *ia_id;
+ int i;
+
+ for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
+ tmp = reply->ia->iasubopt[i];
+
+ if (tmp->ia != NULL)
+ ia_dereference(&tmp->ia, MDL);
+ ia_reference(&tmp->ia, reply->ia, MDL);
+
+ /* Commit 'hard' bindings. */
+ renew_lease6(tmp->ipv6_pool, tmp);
+ schedule_lease_timeout(tmp->ipv6_pool);
+ }
+
+ /* Remove any old ia from the hash. */
+ if (reply->old_ia != NULL) {
+ ia_id = &reply->old_ia->iaid_duid;
+ ia_hash_delete(ia_pd_active,
+ (unsigned char *)ia_id->data,
+ ia_id->len, MDL);
+ ia_dereference(&reply->old_ia, MDL);
+ }
+
+ /* Put new ia into the hash. */
+ reply->ia->cltt = cur_time;
+ ia_id = &reply->ia->iaid_duid;
+ ia_hash_add(ia_pd_active, (unsigned char *)ia_id->data,
+ ia_id->len, reply->ia, MDL);
+
+ write_ia(reply->ia);
+ } else {
+ schedule_lease_timeout_reply(reply);
+ }
+
+ cleanup:
+ if (packet_ia != NULL)
+ option_state_dereference(&packet_ia, MDL);
+ if (reply->reply_ia != NULL)
+ option_state_dereference(&reply->reply_ia, MDL);
+ if (ia_data.data != NULL)
+ data_string_forget(&ia_data, MDL);
+ if (data.data != NULL)
+ data_string_forget(&data, MDL);
+ if (reply->ia != NULL)
+ ia_dereference(&reply->ia, MDL);
+ if (reply->old_ia != NULL)
+ ia_dereference(&reply->old_ia, MDL);
+ if (reply->lease != NULL)
+ iasubopt_dereference(&reply->lease, MDL);
+
+ /*
+ * ISC_R_CANCELED is a status code used by the prefix processing to
+ * indicate we're replying with a status code. This is still a
+ * success at higher layers.
+ */
+ return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status);
+}
+
+/*
+ * Process an IAPREFIX within a given IA_PD, storing any IAPREFIX reply
+ * contents into the reply's current ia_pd-scoped option cache. Returns
+ * ISC_R_CANCELED in the event we are replying with a status code and do
+ * not wish to process more IAPREFIXes within this IA_PD.
+ */
+static isc_result_t
+reply_process_prefix(struct reply_state *reply, struct option_cache *pref) {
+ u_int32_t pref_life, valid_life;
+ struct binding_scope **scope;
+ struct iaddrcidrnet tmp_pref;
+ struct option_cache *oc;
+ struct data_string iapref, data;
+ isc_result_t status = ISC_R_SUCCESS;
+
+ /* Initializes values that will be cleaned up. */
+ memset(&iapref, 0, sizeof(iapref));
+ memset(&data, 0, sizeof(data));
+ /* Note that reply->lease may be set by prefix_is_owned() */
+
+ /*
+ * There is no point trying to process an incoming prefix if there
+ * is no room for an outgoing prefix.
+ */
+ if ((reply->cursor + 29) > sizeof(reply->buf)) {
+ log_error("reply_process_prefix: Out of room for prefix.");
+ return ISC_R_NOSPACE;
+ }
+
+ /* Extract this IAPREFIX option. */
+ if (!evaluate_option_cache(&iapref, reply->packet, NULL, NULL,
+ reply->packet->options, NULL, &global_scope,
+ pref, MDL) ||
+ (iapref.len < IAPREFIX_OFFSET)) {
+ log_error("reply_process_prefix: error evaluating IAPREFIX.");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ /*
+ * Layout: preferred and valid lifetimes followed by the prefix
+ * length and the IPv6 address.
+ */
+ pref_life = getULong(iapref.data);
+ valid_life = getULong(iapref.data + 4);
+
+ if ((reply->client_valid == 0) ||
+ (reply->client_valid > valid_life))
+ reply->client_valid = valid_life;
+
+ if ((reply->client_prefer == 0) ||
+ (reply->client_prefer > pref_life))
+ reply->client_prefer = pref_life;
+
+ /*
+ * Clients may choose to send ::/0 as a prefix, with the idea to give
+ * hints about preferred-lifetime or valid-lifetime.
+ */
+ tmp_pref.lo_addr.len = 16;
+ memset(tmp_pref.lo_addr.iabuf, 0, 16);
+ if ((iapref.data[8] == 0) &&
+ (memcmp(iapref.data + 9, tmp_pref.lo_addr.iabuf, 16) == 0)) {
+ /* Status remains success; we just ignore this one. */
+ goto cleanup;
+ }
+
+ /*
+ * Clients may choose to send ::/X as a prefix to specify a
+ * preferred/requested prefix length. Note X is never zero here.
+ */
+ tmp_pref.bits = (int) iapref.data[8];
+ if (reply->preflen < 0) {
+ /* Cache the first preferred prefix length. */
+ reply->preflen = tmp_pref.bits;
+ }
+ if (memcmp(iapref.data + 9, tmp_pref.lo_addr.iabuf, 16) == 0) {
+ goto cleanup;
+ }
+
+ memcpy(tmp_pref.lo_addr.iabuf, iapref.data + 9, 16);
+
+ /* Verify the prefix belongs to the client. */
+ if (!prefix_is_owned(reply, &tmp_pref)) {
+ /* Same than for addresses. */
+ if ((reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT) ||
+ (reply->packet->dhcpv6_msg_type == DHCPV6_REQUEST) ||
+ (reply->packet->dhcpv6_msg_type == DHCPV6_REBIND)) {
+ status = reply_process_try_prefix(reply, &tmp_pref);
+
+ /* Either error out or skip this prefix. */
+ if ((status != ISC_R_SUCCESS) &&
+ (status != ISC_R_ADDRINUSE) &&
+ (status != ISC_R_ADDRNOTAVAIL))
+ goto cleanup;
+
+ if (reply->lease == NULL) {
+ if (reply->packet->dhcpv6_msg_type ==
+ DHCPV6_REBIND) {
+ reply->send_prefer = 0;
+ reply->send_valid = 0;
+ goto send_pref;
+ }
+
+ /* status remains success - ignore */
+ goto cleanup;
+ }
+ /*
+ * RFC3633 section 18.2.3:
+ *
+ * If the delegating router cannot find a binding
+ * for the requesting router's IA_PD the delegating
+ * router returns the IA_PD containing no prefixes
+ * with a Status Code option set to NoBinding in the
+ * Reply message.
+ *
+ * On mismatch we (ab)use this pretending we have not the IA
+ * as soon as we have not a prefix.
+ */
+ } else if (reply->packet->dhcpv6_msg_type == DHCPV6_RENEW) {
+ /* Rewind the IA_PD to empty. */
+ option_state_dereference(&reply->reply_ia, MDL);
+ if (!option_state_allocate(&reply->reply_ia, MDL)) {
+ log_error("reply_process_prefix: No memory "
+ "for option state wipe.");
+ status = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ /* Append a NoBinding status code. */
+ if (!set_status_code(STATUS_NoBinding,
+ "Prefix not bound to this "
+ "interface.", reply->reply_ia)) {
+ log_error("reply_process_prefix: Unable to "
+ "attach status code.");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ /* Fin (no more IAPREFIXes). */
+ status = ISC_R_CANCELED;
+ goto cleanup;
+ } else {
+ log_error("It is impossible to lease a client that is "
+ "not sending a solicit, request, renew, or "
+ "rebind message.");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ }
+
+ if (reply->static_prefixes > 0) {
+ if (reply->host == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ scope = &global_scope;
+
+ /* Copy the static prefix for logging purposes */
+ memcpy(&reply->fixed_pref, &tmp_pref, sizeof(tmp_pref));
+ } else {
+ if (reply->lease == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ scope = &reply->lease->scope;
+ }
+
+ /*
+ * If client_resources is nonzero, then the reply_process_is_prefixed
+ * function has executed configuration state into the reply option
+ * cache. We will use that valid cache to derive configuration for
+ * whether or not to engage in additional prefixes, and similar.
+ */
+ if (reply->client_resources != 0) {
+ unsigned limit = 1;
+
+ /*
+ * Does this client have "enough" prefixes already? Default
+ * to one. Everybody gets one, and one should be enough for
+ * anybody.
+ */
+ oc = lookup_option(&server_universe, reply->opt_state,
+ SV_LIMIT_PREFS_PER_IA);
+ if (oc != NULL) {
+ if (!evaluate_option_cache(&data, reply->packet,
+ NULL, NULL,
+ reply->packet->options,
+ reply->opt_state,
+ scope, oc, MDL) ||
+ (data.len != 4)) {
+ log_error("reply_process_prefix: unable to "
+ "evaluate prefs-per-ia value.");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ limit = getULong(data.data);
+ data_string_forget(&data, MDL);
+ }
+
+ /*
+ * If we wish to limit the client to a certain number of
+ * prefixes, then omit the prefix from the reply.
+ */
+ if (reply->client_resources >= limit)
+ goto cleanup;
+ }
+
+ status = reply_process_is_prefixed(reply, scope, reply->shared->group);
+ if (status != ISC_R_SUCCESS)
+ goto cleanup;
+
+ send_pref:
+ status = reply_process_send_prefix(reply, &tmp_pref);
+
+ cleanup:
+ if (iapref.data != NULL)
+ data_string_forget(&iapref, MDL);
+ if (data.data != NULL)
+ data_string_forget(&data, MDL);
+ if (reply->lease != NULL)
+ iasubopt_dereference(&reply->lease, MDL);
+
+ return status;
+}
+
+/*
+ * Verify the prefix belongs to the client. If we've got a host
+ * record with fixed prefixes, it has to be an assigned prefix
+ * (fault out all else). Otherwise it's a dynamic prefix, so lookup
+ * that prefix and make sure it belongs to this DUID:IAID pair.
+ */
+static isc_boolean_t
+prefix_is_owned(struct reply_state *reply, struct iaddrcidrnet *pref) {
+ struct iaddrcidrnetlist *l;
+ int i;
+
+ /*
+ * This faults out prefixes that don't match fixed prefixes.
+ */
+ if (reply->static_prefixes > 0) {
+ for (l = reply->host->fixed_prefix; l != NULL; l = l->next) {
+ if ((pref->bits == l->cidrnet.bits) &&
+ (memcmp(pref->lo_addr.iabuf,
+ l->cidrnet.lo_addr.iabuf, 16) == 0))
+ return (ISC_TRUE);
+ }
+ return (ISC_FALSE);
+ }
+
+ if ((reply->old_ia == NULL) ||
+ (reply->old_ia->num_iasubopt == 0))
+ return (ISC_FALSE);
+
+ for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) {
+ struct iasubopt *tmp;
+
+ tmp = reply->old_ia->iasubopt[i];
+
+ if ((pref->bits == (int) tmp->plen) &&
+ (memcmp(pref->lo_addr.iabuf, &tmp->addr, 16) == 0)) {
+ if (lease6_usable(tmp) == ISC_FALSE) {
+ return (ISC_FALSE);
+ }
+ iasubopt_reference(&reply->lease, tmp, MDL);
+ return (ISC_TRUE);
+ }
+ }
+
+ return (ISC_FALSE);
+}
+
+/*
+ * This function only returns failure on 'hard' failures. If it succeeds,
+ * it will leave a prefix structure behind.
+ */
+static isc_result_t
+reply_process_try_prefix(struct reply_state *reply,
+ struct iaddrcidrnet *pref) {
+ isc_result_t status = ISC_R_ADDRNOTAVAIL;
+ struct ipv6_pool *pool;
+ int i;
+ struct data_string data_pref;
+
+ if ((reply == NULL) || (reply->shared == NULL) ||
+ (pref == NULL) || (reply->lease != NULL))
+ return (DHCP_R_INVALIDARG);
+
+ if (reply->shared->ipv6_pools == NULL)
+ return (ISC_R_ADDRNOTAVAIL);
+
+ memset(&data_pref, 0, sizeof(data_pref));
+ data_pref.len = 17;
+ if (!buffer_allocate(&data_pref.buffer, data_pref.len, MDL)) {
+ log_error("reply_process_try_prefix: out of memory.");
+ return (ISC_R_NOMEMORY);
+ }
+ data_pref.data = data_pref.buffer->data;
+ data_pref.buffer->data[0] = (u_int8_t) pref->bits;
+ memcpy(data_pref.buffer->data + 1, pref->lo_addr.iabuf, 16);
+
+ for (i = 0 ; (pool = reply->shared->ipv6_pools[i]) != NULL ; i++) {
+ if (pool->pool_type != D6O_IA_PD)
+ continue;
+ status = try_client_v6_prefix(&reply->lease, pool,
+ &data_pref);
+ /* If we found it in this pool (either in use or available),
+ there is no need to look further. */
+ if ( (status == ISC_R_SUCCESS) || (status == ISC_R_ADDRINUSE) )
+ break;
+ }
+
+ data_string_forget(&data_pref, MDL);
+ /* Return just the most recent status... */
+ return (status);
+}
+
+/* Look around for a prefix to give the client. First, look through the old
+ * IA_PD for prefixes we can extend. Second, try to allocate a new prefix.
+ * Finally, actually add that prefix into the current reply IA_PD.
+ */
+static isc_result_t
+find_client_prefix(struct reply_state *reply) {
+ struct iaddrcidrnet send_pref;
+ isc_result_t status = ISC_R_NORESOURCES;
+ struct iasubopt *prefix, *best_prefix = NULL;
+ struct binding_scope **scope;
+ int i;
+
+ if (reply->static_prefixes > 0) {
+ struct iaddrcidrnetlist *l;
+
+ if (reply->host == NULL)
+ return DHCP_R_INVALIDARG;
+
+ for (l = reply->host->fixed_prefix; l != NULL; l = l->next) {
+ if (l->cidrnet.bits == reply->preflen)
+ break;
+ }
+ if (l == NULL) {
+ /*
+ * If no fixed prefix has the preferred length,
+ * get the first one.
+ */
+ l = reply->host->fixed_prefix;
+ }
+ memcpy(&send_pref, &l->cidrnet, sizeof(send_pref));
+
+ scope = &global_scope;
+
+ /* Copy the prefix for logging purposes */
+ memcpy(&reply->fixed_pref, &l->cidrnet, sizeof(send_pref));
+
+ goto send_pref;
+ }
+
+ if (reply->old_ia != NULL) {
+ for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) {
+ struct shared_network *candidate_shared;
+
+ prefix = reply->old_ia->iasubopt[i];
+ candidate_shared = prefix->ipv6_pool->shared_network;
+
+ /*
+ * Consider this prefix if it is in a global pool or
+ * if it is scoped in a pool under the client's shared
+ * network.
+ */
+ if (((candidate_shared == NULL) ||
+ (candidate_shared == reply->shared)) &&
+ (lease6_usable(prefix) == ISC_TRUE)) {
+ best_prefix = prefix_compare(reply, prefix,
+ best_prefix);
+ }
+ }
+ }
+
+ /* Try to pick a new prefix if we didn't find one, or if we found an
+ * abandoned prefix.
+ */
+ if ((best_prefix == NULL) || (best_prefix->state == FTS_ABANDONED)) {
+ status = pick_v6_prefix(&reply->lease, reply->preflen,
+ reply->shared, &reply->client_id);
+ } else if (best_prefix != NULL) {
+ iasubopt_reference(&reply->lease, best_prefix, MDL);
+ status = ISC_R_SUCCESS;
+ }
+
+ /* Pick the abandoned prefix as a last resort. */
+ if ((status == ISC_R_NORESOURCES) && (best_prefix != NULL)) {
+ /* I don't see how this is supposed to be done right now. */
+ log_error("Reclaiming abandoned prefixes is not yet "
+ "supported. Treating this as an out of space "
+ "condition.");
+ /* iasubopt_reference(&reply->lease, best_prefix, MDL); */
+ }
+
+ /* Give up now if we didn't find a prefix. */
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ if (reply->lease == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ scope = &reply->lease->scope;
+
+ send_pref.lo_addr.len = 16;
+ memcpy(send_pref.lo_addr.iabuf, &reply->lease->addr, 16);
+ send_pref.bits = (int) reply->lease->plen;
+
+ send_pref:
+ status = reply_process_is_prefixed(reply, scope, reply->shared->group);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = reply_process_send_prefix(reply, &send_pref);
+ return status;
+}
+
+/* Once a prefix is found for a client, perform several common functions;
+ * Calculate and store valid and preferred prefix times, draw client options
+ * into the option state.
+ */
+static isc_result_t
+reply_process_is_prefixed(struct reply_state *reply,
+ struct binding_scope **scope, struct group *group)
+{
+ isc_result_t status = ISC_R_SUCCESS;
+ struct data_string data;
+ struct option_cache *oc;
+
+ /* Initialize values we will cleanup. */
+ memset(&data, 0, sizeof(data));
+
+ /*
+ * Bring configured options into the root packet level cache - start
+ * with the lease's closest enclosing group (passed in by the caller
+ * as 'group').
+ */
+ execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+ reply->packet->options, reply->opt_state,
+ scope, group, root_group);
+
+ /*
+ * If there is a host record, over-ride with values configured there,
+ * without re-evaluating configuration from the previously executed
+ * group or its common enclosers.
+ */
+ if (reply->host != NULL)
+ execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+ reply->packet->options,
+ reply->opt_state, scope,
+ reply->host->group, group);
+
+ /* Determine valid lifetime. */
+ if (reply->client_valid == 0)
+ reply->send_valid = DEFAULT_DEFAULT_LEASE_TIME;
+ else
+ reply->send_valid = reply->client_valid;
+
+ oc = lookup_option(&server_universe, reply->opt_state,
+ SV_DEFAULT_LEASE_TIME);
+ if (oc != NULL) {
+ if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
+ reply->packet->options,
+ reply->opt_state,
+ scope, oc, MDL) ||
+ (data.len != 4)) {
+ log_error("reply_process_is_prefixed: unable to "
+ "evaluate default prefix time");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ reply->send_valid = getULong(data.data);
+ data_string_forget(&data, MDL);
+ }
+
+ if (reply->client_prefer == 0)
+ reply->send_prefer = reply->send_valid;
+ else
+ reply->send_prefer = reply->client_prefer;
+
+ if (reply->send_prefer >= reply->send_valid)
+ reply->send_prefer = (reply->send_valid / 2) +
+ (reply->send_valid / 8);
+
+ oc = lookup_option(&server_universe, reply->opt_state,
+ SV_PREFER_LIFETIME);
+ if (oc != NULL) {
+ if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
+ reply->packet->options,
+ reply->opt_state,
+ scope, oc, MDL) ||
+ (data.len != 4)) {
+ log_error("reply_process_is_prefixed: unable to "
+ "evaluate preferred prefix time");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ reply->send_prefer = getULong(data.data);
+ data_string_forget(&data, MDL);
+ }
+
+ /* Note lowest values for later calculation of renew/rebind times. */
+ if (reply->prefer > reply->send_prefer)
+ reply->prefer = reply->send_prefer;
+
+ if (reply->valid > reply->send_valid)
+ reply->valid = reply->send_valid;
+
+ /* Perform dynamic prefix related update work. */
+ if (reply->lease != NULL) {
+ /* Cached lifetimes */
+ reply->lease->prefer = reply->send_prefer;
+ reply->lease->valid = reply->send_valid;
+
+ /* Advance (or rewind) the valid lifetime. */
+ if (reply->buf.reply.msg_type == DHCPV6_REPLY) {
+ reply->lease->soft_lifetime_end_time =
+ cur_time + reply->send_valid;
+ /* Wait before renew! */
+ }
+
+ status = ia_add_iasubopt(reply->ia, reply->lease, MDL);
+ if (status != ISC_R_SUCCESS) {
+ log_fatal("reply_process_is_prefixed: Unable to "
+ "attach prefix to new IA_PD: %s",
+ isc_result_totext(status));
+ }
+
+ /*
+ * If this is a new prefix, make sure it is attached somewhere.
+ */
+ if (reply->lease->ia == NULL) {
+ ia_reference(&reply->lease->ia, reply->ia, MDL);
+ }
+ }
+
+ /* Bring a copy of the relevant options into the IA_PD scope. */
+ execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+ reply->packet->options, reply->reply_ia,
+ scope, group, root_group);
+
+ /*
+ * And bring in host record configuration, if any, but not to overlap
+ * the previous group or its common enclosers.
+ */
+ if (reply->host != NULL)
+ execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+ reply->packet->options,
+ reply->reply_ia, scope,
+ reply->host->group, group);
+
+ cleanup:
+ if (data.data != NULL)
+ data_string_forget(&data, MDL);
+
+ if (status == ISC_R_SUCCESS)
+ reply->client_resources++;
+
+ return status;
+}
+
+/* Simply send an IAPREFIX within the IA_PD scope as described. */
+static isc_result_t
+reply_process_send_prefix(struct reply_state *reply,
+ struct iaddrcidrnet *pref) {
+ isc_result_t status = ISC_R_SUCCESS;
+ struct data_string data;
+
+ memset(&data, 0, sizeof(data));
+
+ /* Now append the prefix. */
+ data.len = IAPREFIX_OFFSET;
+ if (!buffer_allocate(&data.buffer, data.len, MDL)) {
+ log_error("reply_process_send_prefix: out of memory"
+ "allocating new IAPREFIX buffer.");
+ status = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ data.data = data.buffer->data;
+
+ putULong(data.buffer->data, reply->send_prefer);
+ putULong(data.buffer->data + 4, reply->send_valid);
+ data.buffer->data[8] = pref->bits;
+ memcpy(data.buffer->data + 9, pref->lo_addr.iabuf, 16);
+
+ if (!append_option_buffer(&dhcpv6_universe, reply->reply_ia,
+ data.buffer, data.buffer->data,
+ data.len, D6O_IAPREFIX, 0)) {
+ log_error("reply_process_send_prefix: unable "
+ "to save IAPREFIX option");
+ status = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ reply->resources_included = ISC_TRUE;
+
+ cleanup:
+ if (data.data != NULL)
+ data_string_forget(&data, MDL);
+
+ return status;
+}
+
+/* Choose the better of two prefixes. */
+static struct iasubopt *
+prefix_compare(struct reply_state *reply,
+ struct iasubopt *alpha, struct iasubopt *beta) {
+ if (alpha == NULL)
+ return beta;
+ if (beta == NULL)
+ return alpha;
+
+ if (reply->preflen >= 0) {
+ if ((alpha->plen == reply->preflen) &&
+ (beta->plen != reply->preflen))
+ return alpha;
+ if ((beta->plen == reply->preflen) &&
+ (alpha->plen != reply->preflen))
+ return beta;
+ }
+
+ switch(alpha->state) {
+ case FTS_ACTIVE:
+ switch(beta->state) {
+ case FTS_ACTIVE:
+ /* Choose the prefix with the longest lifetime (most
+ * likely the most recently allocated).
+ */
+ if (alpha->hard_lifetime_end_time <
+ beta->hard_lifetime_end_time)
+ return beta;
+ else
+ return alpha;
+
+ case FTS_EXPIRED:
+ case FTS_ABANDONED:
+ return alpha;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ }
+ break;
+
+ case FTS_EXPIRED:
+ switch (beta->state) {
+ case FTS_ACTIVE:
+ return beta;
+
+ case FTS_EXPIRED:
+ /* Choose the most recently expired prefix. */
+ if (alpha->hard_lifetime_end_time <
+ beta->hard_lifetime_end_time)
+ return beta;
+ else if ((alpha->hard_lifetime_end_time ==
+ beta->hard_lifetime_end_time) &&
+ (alpha->soft_lifetime_end_time <
+ beta->soft_lifetime_end_time))
+ return beta;
+ else
+ return alpha;
+
+ case FTS_ABANDONED:
+ return alpha;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ }
+ break;
+
+ case FTS_ABANDONED:
+ switch (beta->state) {
+ case FTS_ACTIVE:
+ case FTS_EXPIRED:
+ return alpha;
+
+ case FTS_ABANDONED:
+ /* Choose the prefix that was abandoned longest ago. */
+ if (alpha->hard_lifetime_end_time <
+ beta->hard_lifetime_end_time)
+ return alpha;
+ else
+ return beta;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ }
+ break;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ }
+
+ log_fatal("Triple impossible condition at %s:%d.", MDL);
+ return NULL;
+}
+
+/*
+ * Solicit is how a client starts requesting addresses.
+ *
+ * If the client asks for rapid commit, and we support it, we will
+ * allocate the addresses and reply.
+ *
+ * Otherwise we will send an advertise message.
+ */
+
+static void
+dhcpv6_solicit(struct data_string *reply_ret, struct packet *packet) {
+ struct data_string client_id;
+
+ /*
+ * Validate our input.
+ */
+ if (!valid_client_msg(packet, &client_id)) {
+ return;
+ }
+
+ lease_to_client(reply_ret, packet, &client_id, NULL);
+
+ /*
+ * Clean up.
+ */
+ data_string_forget(&client_id, MDL);
+}
+
+/*
+ * Request is how a client actually requests addresses.
+ *
+ * Very similar to Solicit handling, except the server DUID is required.
+ */
+
+/* TODO: reject unicast messages, unless we set unicast option */
+static void
+dhcpv6_request(struct data_string *reply_ret, struct packet *packet) {
+ struct data_string client_id;
+ struct data_string server_id;
+
+ /*
+ * Validate our input.
+ */
+ if (!valid_client_resp(packet, &client_id, &server_id)) {
+ return;
+ }
+
+ /*
+ * Issue our lease.
+ */
+ lease_to_client(reply_ret, packet, &client_id, &server_id);
+
+ /*
+ * Cleanup.
+ */
+ data_string_forget(&client_id, MDL);
+ data_string_forget(&server_id, MDL);
+}
+
+/* Find a DHCPv6 packet's shared network from hints in the packet.
+ */
+static isc_result_t
+shared_network_from_packet6(struct shared_network **shared,
+ struct packet *packet)
+{
+ const struct packet *chk_packet;
+ const struct in6_addr *link_addr, *first_link_addr;
+ struct iaddr tmp_addr;
+ struct subnet *subnet;
+ isc_result_t status;
+
+ if ((shared == NULL) || (*shared != NULL) || (packet == NULL))
+ return DHCP_R_INVALIDARG;
+
+ /*
+ * First, find the link address where the packet from the client
+ * first appeared (if this packet was relayed).
+ */
+ first_link_addr = NULL;
+ chk_packet = packet->dhcpv6_container_packet;
+ while (chk_packet != NULL) {
+ link_addr = &chk_packet->dhcpv6_link_address;
+ if (!IN6_IS_ADDR_UNSPECIFIED(link_addr) &&
+ !IN6_IS_ADDR_LINKLOCAL(link_addr)) {
+ first_link_addr = link_addr;
+ break;
+ }
+ chk_packet = chk_packet->dhcpv6_container_packet;
+ }
+
+ /*
+ * If there is a relayed link address, find the subnet associated
+ * with that, and use that to get the appropriate
+ * shared_network.
+ */
+ if (first_link_addr != NULL) {
+ tmp_addr.len = sizeof(*first_link_addr);
+ memcpy(tmp_addr.iabuf,
+ first_link_addr, sizeof(*first_link_addr));
+ subnet = NULL;
+ if (!find_subnet(&subnet, tmp_addr, MDL)) {
+ log_debug("No subnet found for link-address %s.",
+ piaddr(tmp_addr));
+ return ISC_R_NOTFOUND;
+ }
+ status = shared_network_reference(shared,
+ subnet->shared_network, MDL);
+ subnet_dereference(&subnet, MDL);
+
+ /*
+ * If there is no link address, we will use the interface
+ * that this packet came in on to pick the shared_network.
+ */
+ } else if (packet->interface != NULL) {
+ status = shared_network_reference(shared,
+ packet->interface->shared_network,
+ MDL);
+ if (packet->dhcpv6_container_packet != NULL) {
+ log_info("[L2 Relay] No link address in relay packet "
+ "assuming L2 relay and using receiving "
+ "interface");
+ }
+
+ } else {
+ /*
+ * We shouldn't be able to get here but if there is no link
+ * address and no interface we don't know where to get the
+ * pool from log an error and return an error.
+ */
+ log_error("No interface and no link address "
+ "can't determine pool");
+ status = DHCP_R_INVALIDARG;
+ }
+
+ return status;
+}
+
+/*
+ * When a client thinks it might be on a new link, it sends a
+ * Confirm message.
+ *
+ * From RFC3315 section 18.2.2:
+ *
+ * When the server receives a Confirm message, the server determines
+ * whether the addresses in the Confirm message are appropriate for the
+ * link to which the client is attached. If all of the addresses in the
+ * Confirm message pass this test, the server returns a status of
+ * Success. If any of the addresses do not pass this test, the server
+ * returns a status of NotOnLink. If the server is unable to perform
+ * this test (for example, the server does not have information about
+ * prefixes on the link to which the client is connected), or there were
+ * no addresses in any of the IAs sent by the client, the server MUST
+ * NOT send a reply to the client.
+ */
+
+static void
+dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) {
+ struct shared_network *shared;
+ struct subnet *subnet;
+ struct option_cache *ia, *ta, *oc;
+ struct data_string cli_enc_opt_data, iaaddr, client_id, packet_oro;
+ struct option_state *cli_enc_opt_state, *opt_state;
+ struct iaddr cli_addr;
+ int pass;
+ isc_boolean_t inappropriate, has_addrs;
+ char reply_data[65536];
+ struct dhcpv6_packet *reply = (struct dhcpv6_packet *)reply_data;
+ int reply_ofs = (int)(offsetof(struct dhcpv6_packet, options));
+
+ /*
+ * Basic client message validation.
+ */
+ memset(&client_id, 0, sizeof(client_id));
+ if (!valid_client_msg(packet, &client_id)) {
+ return;
+ }
+
+ /*
+ * Do not process Confirms that do not have IA's we do not recognize.
+ */
+ ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
+ ta = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA);
+ if ((ia == NULL) && (ta == NULL))
+ return;
+
+ /*
+ * IA_PD's are simply ignored.
+ */
+ delete_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
+
+ /*
+ * Bit of variable initialization.
+ */
+ opt_state = cli_enc_opt_state = NULL;
+ memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
+ memset(&iaaddr, 0, sizeof(iaaddr));
+ memset(&packet_oro, 0, sizeof(packet_oro));
+
+ /* Determine what shared network the client is connected to. We
+ * must not respond if we don't have any information about the
+ * network the client is on.
+ */
+ shared = NULL;
+ if ((shared_network_from_packet6(&shared, packet) != ISC_R_SUCCESS) ||
+ (shared == NULL))
+ goto exit;
+
+ /* If there are no recorded subnets, then we have no
+ * information about this subnet - ignore Confirms.
+ */
+ subnet = shared->subnets;
+ if (subnet == NULL)
+ goto exit;
+
+ /* Are the addresses in all the IA's appropriate for that link? */
+ has_addrs = inappropriate = ISC_FALSE;
+ pass = D6O_IA_NA;
+ while(!inappropriate) {
+ /* If we've reached the end of the IA_NA pass, move to the
+ * IA_TA pass.
+ */
+ if ((pass == D6O_IA_NA) && (ia == NULL)) {
+ pass = D6O_IA_TA;
+ ia = ta;
+ }
+
+ /* If we've reached the end of all passes, we're done. */
+ if (ia == NULL)
+ break;
+
+ if (((pass == D6O_IA_NA) &&
+ !get_encapsulated_IA_state(&cli_enc_opt_state,
+ &cli_enc_opt_data,
+ packet, ia, IA_NA_OFFSET)) ||
+ ((pass == D6O_IA_TA) &&
+ !get_encapsulated_IA_state(&cli_enc_opt_state,
+ &cli_enc_opt_data,
+ packet, ia, IA_TA_OFFSET))) {
+ goto exit;
+ }
+
+ oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state,
+ D6O_IAADDR);
+
+ for ( ; oc != NULL ; oc = oc->next) {
+ if (!evaluate_option_cache(&iaaddr, packet, NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL) ||
+ (iaaddr.len < IAADDR_OFFSET)) {
+ log_error("dhcpv6_confirm: "
+ "error evaluating IAADDR.");
+ goto exit;
+ }
+
+ /* Copy out the IPv6 address for processing. */
+ cli_addr.len = 16;
+ memcpy(cli_addr.iabuf, iaaddr.data, 16);
+
+ data_string_forget(&iaaddr, MDL);
+
+ /* Record that we've processed at least one address. */
+ has_addrs = ISC_TRUE;
+
+ /* Find out if any subnets cover this address. */
+ for (subnet = shared->subnets ; subnet != NULL ;
+ subnet = subnet->next_sibling) {
+ if (addr_eq(subnet_number(cli_addr,
+ subnet->netmask),
+ subnet->net))
+ break;
+ }
+
+ /* If we reach the end of the subnet list, and no
+ * subnet matches the client address, then it must
+ * be inappropriate to the link (so far as our
+ * configuration says). Once we've found one
+ * inappropriate address, there is no reason to
+ * continue searching.
+ */
+ if (subnet == NULL) {
+ inappropriate = ISC_TRUE;
+ break;
+ }
+ }
+
+ option_state_dereference(&cli_enc_opt_state, MDL);
+ data_string_forget(&cli_enc_opt_data, MDL);
+
+ /* Advance to the next IA_*. */
+ ia = ia->next;
+ }
+
+ /* If the client supplied no addresses, do not reply. */
+ if (!has_addrs)
+ goto exit;
+
+ /*
+ * Set up reply.
+ */
+ if (!start_reply(packet, &client_id, NULL, &opt_state, reply)) {
+ goto exit;
+ }
+
+ /*
+ * Set our status.
+ */
+ if (inappropriate) {
+ if (!set_status_code(STATUS_NotOnLink,
+ "Some of the addresses are not on link.",
+ opt_state)) {
+ goto exit;
+ }
+ } else {
+ if (!set_status_code(STATUS_Success,
+ "All addresses still on link.",
+ opt_state)) {
+ goto exit;
+ }
+ }
+
+ /*
+ * Only one option: add it.
+ */
+ reply_ofs += store_options6(reply_data+reply_ofs,
+ sizeof(reply_data)-reply_ofs,
+ opt_state, packet,
+ required_opts, &packet_oro);
+
+ /*
+ * Return our reply to the caller.
+ */
+ reply_ret->len = reply_ofs;
+ reply_ret->buffer = NULL;
+ if (!buffer_allocate(&reply_ret->buffer, reply_ofs, MDL)) {
+ log_fatal("No memory to store reply.");
+ }
+ reply_ret->data = reply_ret->buffer->data;
+ memcpy(reply_ret->buffer->data, reply, reply_ofs);
+
+exit:
+ /* Cleanup any stale data strings. */
+ if (cli_enc_opt_data.buffer != NULL)
+ data_string_forget(&cli_enc_opt_data, MDL);
+ if (iaaddr.buffer != NULL)
+ data_string_forget(&iaaddr, MDL);
+ if (client_id.buffer != NULL)
+ data_string_forget(&client_id, MDL);
+ if (packet_oro.buffer != NULL)
+ data_string_forget(&packet_oro, MDL);
+
+ /* Release any stale option states. */
+ if (cli_enc_opt_state != NULL)
+ option_state_dereference(&cli_enc_opt_state, MDL);
+ if (opt_state != NULL)
+ option_state_dereference(&opt_state, MDL);
+}
+
+/*
+ * Renew is when a client wants to extend its lease/prefix, at time T1.
+ *
+ * We handle this the same as if the client wants a new lease/prefix,
+ * except for the error code of when addresses don't match.
+ */
+
+/* TODO: reject unicast messages, unless we set unicast option */
+static void
+dhcpv6_renew(struct data_string *reply, struct packet *packet) {
+ struct data_string client_id;
+ struct data_string server_id;
+
+ /*
+ * Validate the request.
+ */
+ if (!valid_client_resp(packet, &client_id, &server_id)) {
+ return;
+ }
+
+ /*
+ * Renew our lease.
+ */
+ lease_to_client(reply, packet, &client_id, &server_id);
+
+ /*
+ * Cleanup.
+ */
+ data_string_forget(&server_id, MDL);
+ data_string_forget(&client_id, MDL);
+}
+
+/*
+ * Rebind is when a client wants to extend its lease, at time T2.
+ *
+ * We handle this the same as if the client wants a new lease, except
+ * for the error code of when addresses don't match.
+ */
+
+static void
+dhcpv6_rebind(struct data_string *reply, struct packet *packet) {
+ struct data_string client_id;
+
+ if (!valid_client_msg(packet, &client_id)) {
+ return;
+ }
+
+ lease_to_client(reply, packet, &client_id, NULL);
+
+ data_string_forget(&client_id, MDL);
+}
+
+static void
+ia_na_match_decline(const struct data_string *client_id,
+ const struct data_string *iaaddr,
+ struct iasubopt *lease)
+{
+ char tmp_addr[INET6_ADDRSTRLEN];
+
+ log_error("Client %s reports address %s is "
+ "already in use by another host!",
+ print_hex_1(client_id->len, client_id->data, 60),
+ inet_ntop(AF_INET6, iaaddr->data,
+ tmp_addr, sizeof(tmp_addr)));
+ if (lease != NULL) {
+ decline_lease6(lease->ipv6_pool, lease);
+ lease->ia->cltt = cur_time;
+ write_ia(lease->ia);
+ }
+}
+
+static void
+ia_na_nomatch_decline(const struct data_string *client_id,
+ const struct data_string *iaaddr,
+ u_int32_t *ia_na_id,
+ struct packet *packet,
+ char *reply_data,
+ int *reply_ofs,
+ int reply_len)
+{
+ char tmp_addr[INET6_ADDRSTRLEN];
+ struct option_state *host_opt_state;
+ int len;
+
+ log_info("Client %s declines address %s, which is not offered to it.",
+ print_hex_1(client_id->len, client_id->data, 60),
+ inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr)));
+
+ /*
+ * Create state for this IA_NA.
+ */
+ host_opt_state = NULL;
+ if (!option_state_allocate(&host_opt_state, MDL)) {
+ log_error("ia_na_nomatch_decline: out of memory "
+ "allocating option_state.");
+ goto exit;
+ }
+
+ if (!set_status_code(STATUS_NoBinding, "Decline for unknown address.",
+ host_opt_state)) {
+ goto exit;
+ }
+
+ /*
+ * Insure we have enough space
+ */
+ if (reply_len < (*reply_ofs + 16)) {
+ log_error("ia_na_nomatch_decline: "
+ "out of space for reply packet.");
+ goto exit;
+ }
+
+ /*
+ * Put our status code into the reply packet.
+ */
+ len = store_options6(reply_data+(*reply_ofs)+16,
+ reply_len-(*reply_ofs)-16,
+ host_opt_state, packet,
+ required_opts_STATUS_CODE, NULL);
+
+ /*
+ * Store the non-encapsulated option data for this
+ * IA_NA into our reply packet. Defined in RFC 3315,
+ * section 22.4.
+ */
+ /* option number */
+ putUShort((unsigned char *)reply_data+(*reply_ofs), D6O_IA_NA);
+ /* option length */
+ putUShort((unsigned char *)reply_data+(*reply_ofs)+2, len + 12);
+ /* IA_NA, copied from the client */
+ memcpy(reply_data+(*reply_ofs)+4, ia_na_id, 4);
+ /* t1 and t2, odd that we need them, but here it is */
+ putULong((unsigned char *)reply_data+(*reply_ofs)+8, 0);
+ putULong((unsigned char *)reply_data+(*reply_ofs)+12, 0);
+
+ /*
+ * Get ready for next IA_NA.
+ */
+ *reply_ofs += (len + 16);
+
+exit:
+ option_state_dereference(&host_opt_state, MDL);
+}
+
+static void
+iterate_over_ia_na(struct data_string *reply_ret,
+ struct packet *packet,
+ const struct data_string *client_id,
+ const struct data_string *server_id,
+ const char *packet_type,
+ void (*ia_na_match)(),
+ void (*ia_na_nomatch)())
+{
+ struct option_state *opt_state;
+ struct host_decl *packet_host;
+ struct option_cache *ia;
+ struct option_cache *oc;
+ /* cli_enc_... variables come from the IA_NA/IA_TA options */
+ struct data_string cli_enc_opt_data;
+ struct option_state *cli_enc_opt_state;
+ struct host_decl *host;
+ struct option_state *host_opt_state;
+ struct data_string iaaddr;
+ struct data_string fixed_addr;
+ char reply_data[65536];
+ struct dhcpv6_packet *reply = (struct dhcpv6_packet *)reply_data;
+ int reply_ofs = (int)(offsetof(struct dhcpv6_packet, options));
+ char status_msg[32];
+ struct iasubopt *lease;
+ struct ia_xx *existing_ia_na;
+ int i;
+ struct data_string key;
+ u_int32_t iaid;
+
+ /*
+ * Initialize to empty values, in case we have to exit early.
+ */
+ opt_state = NULL;
+ memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
+ cli_enc_opt_state = NULL;
+ memset(&iaaddr, 0, sizeof(iaaddr));
+ memset(&fixed_addr, 0, sizeof(fixed_addr));
+ host_opt_state = NULL;
+ lease = NULL;
+
+ /*
+ * Find the host record that matches from the packet, if any.
+ */
+ packet_host = NULL;
+ if (!find_hosts_by_uid(&packet_host,
+ client_id->data, client_id->len, MDL)) {
+ packet_host = NULL;
+ /*
+ * Note: In general, we don't expect a client to provide
+ * enough information to match by option for these
+ * types of messages, but if we don't have a UID
+ * match we can check anyway.
+ */
+ if (!find_hosts_by_option(&packet_host,
+ packet, packet->options, MDL)) {
+ packet_host = NULL;
+
+ if (!find_hosts_by_duid_chaddr(&packet_host,
+ client_id))
+ packet_host = NULL;
+ }
+ }
+
+ /*
+ * Set our reply information.
+ */
+ reply->msg_type = DHCPV6_REPLY;
+ memcpy(reply->transaction_id, packet->dhcpv6_transaction_id,
+ sizeof(reply->transaction_id));
+
+ /*
+ * Build our option state for reply.
+ */
+ opt_state = NULL;
+ if (!option_state_allocate(&opt_state, MDL)) {
+ log_error("iterate_over_ia_na: no memory for option_state.");
+ goto exit;
+ }
+ execute_statements_in_scope(NULL, packet, NULL, NULL,
+ packet->options, opt_state,
+ &global_scope, root_group, NULL);
+
+ /*
+ * RFC 3315, section 18.2.7 tells us which options to include.
+ */
+ oc = lookup_option(&dhcpv6_universe, opt_state, D6O_SERVERID);
+ if (oc == NULL) {
+ if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL,
+ (unsigned char *)server_duid.data,
+ server_duid.len, D6O_SERVERID, 0)) {
+ log_error("iterate_over_ia_na: "
+ "error saving server identifier.");
+ goto exit;
+ }
+ }
+
+ if (!save_option_buffer(&dhcpv6_universe, opt_state,
+ client_id->buffer,
+ (unsigned char *)client_id->data,
+ client_id->len,
+ D6O_CLIENTID, 0)) {
+ log_error("iterate_over_ia_na: "
+ "error saving client identifier.");
+ goto exit;
+ }
+
+ snprintf(status_msg, sizeof(status_msg), "%s received.", packet_type);
+ if (!set_status_code(STATUS_Success, status_msg, opt_state)) {
+ goto exit;
+ }
+
+ /*
+ * Add our options that are not associated with any IA_NA or IA_TA.
+ */
+ reply_ofs += store_options6(reply_data+reply_ofs,
+ sizeof(reply_data)-reply_ofs,
+ opt_state, packet,
+ required_opts, NULL);
+
+ /*
+ * Loop through the IA_NA reported by the client, and deal with
+ * addresses reported as already in use.
+ */
+ for (ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
+ ia != NULL; ia = ia->next) {
+
+ if (!get_encapsulated_IA_state(&cli_enc_opt_state,
+ &cli_enc_opt_data,
+ packet, ia, IA_NA_OFFSET)) {
+ goto exit;
+ }
+
+ iaid = getULong(cli_enc_opt_data.data);
+
+ /*
+ * XXX: It is possible that we can get multiple addresses
+ * sent by the client. We don't send multiple
+ * addresses, so this indicates a client error.
+ * We should check for multiple IAADDR options, log
+ * if found, and set as an error.
+ */
+ oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state,
+ D6O_IAADDR);
+ if (oc == NULL) {
+ /* no address given for this IA, ignore */
+ option_state_dereference(&cli_enc_opt_state, MDL);
+ data_string_forget(&cli_enc_opt_data, MDL);
+ continue;
+ }
+
+ memset(&iaaddr, 0, sizeof(iaaddr));
+ if (!evaluate_option_cache(&iaaddr, packet, NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ log_error("iterate_over_ia_na: "
+ "error evaluating IAADDR.");
+ goto exit;
+ }
+
+ /*
+ * Now we need to figure out which host record matches
+ * this IA_NA and IAADDR (encapsulated option contents
+ * matching a host record by option).
+ *
+ * XXX: We don't currently track IA_NA separately, but
+ * we will need to do this!
+ */
+ host = NULL;
+ if (!find_hosts_by_option(&host, packet,
+ cli_enc_opt_state, MDL)) {
+ if (packet_host != NULL) {
+ host = packet_host;
+ } else {
+ host = NULL;
+ }
+ }
+ while (host != NULL) {
+ if (host->fixed_addr != NULL) {
+ if (!evaluate_option_cache(&fixed_addr, NULL,
+ NULL, NULL, NULL,
+ NULL, &global_scope,
+ host->fixed_addr,
+ MDL)) {
+ log_error("iterate_over_ia_na: error "
+ "evaluating host address.");
+ goto exit;
+ }
+ if ((iaaddr.len >= 16) &&
+ !memcmp(fixed_addr.data, iaaddr.data, 16)) {
+ data_string_forget(&fixed_addr, MDL);
+ break;
+ }
+ data_string_forget(&fixed_addr, MDL);
+ }
+ host = host->n_ipaddr;
+ }
+
+ if ((host == NULL) && (iaaddr.len >= IAADDR_OFFSET)) {
+ /*
+ * Find existing IA_NA.
+ */
+ if (ia_make_key(&key, iaid,
+ (char *)client_id->data,
+ client_id->len,
+ MDL) != ISC_R_SUCCESS) {
+ log_fatal("iterate_over_ia_na: no memory for "
+ "key.");
+ }
+
+ existing_ia_na = NULL;
+ if (ia_hash_lookup(&existing_ia_na, ia_na_active,
+ (unsigned char *)key.data,
+ key.len, MDL)) {
+ /*
+ * Make sure this address is in the IA_NA.
+ */
+ for (i=0; i<existing_ia_na->num_iasubopt; i++) {
+ struct iasubopt *tmp;
+ struct in6_addr *in6_addr;
+
+ tmp = existing_ia_na->iasubopt[i];
+ in6_addr = &tmp->addr;
+ if (memcmp(in6_addr,
+ iaaddr.data, 16) == 0) {
+ iasubopt_reference(&lease,
+ tmp, MDL);
+ break;
+ }
+ }
+ }
+
+ data_string_forget(&key, MDL);
+ }
+
+ if ((host != NULL) || (lease != NULL)) {
+ ia_na_match(client_id, &iaaddr, lease);
+ } else {
+ ia_na_nomatch(client_id, &iaaddr,
+ (u_int32_t *)cli_enc_opt_data.data,
+ packet, reply_data, &reply_ofs,
+ sizeof(reply_data));
+ }
+
+ if (lease != NULL) {
+ iasubopt_dereference(&lease, MDL);
+ }
+
+ data_string_forget(&iaaddr, MDL);
+ option_state_dereference(&cli_enc_opt_state, MDL);
+ data_string_forget(&cli_enc_opt_data, MDL);
+ }
+
+ /*
+ * Return our reply to the caller.
+ */
+ reply_ret->len = reply_ofs;
+ reply_ret->buffer = NULL;
+ if (!buffer_allocate(&reply_ret->buffer, reply_ofs, MDL)) {
+ log_fatal("No memory to store reply.");
+ }
+ reply_ret->data = reply_ret->buffer->data;
+ memcpy(reply_ret->buffer->data, reply, reply_ofs);
+
+exit:
+ if (lease != NULL) {
+ iasubopt_dereference(&lease, MDL);
+ }
+ if (host_opt_state != NULL) {
+ option_state_dereference(&host_opt_state, MDL);
+ }
+ if (fixed_addr.buffer != NULL) {
+ data_string_forget(&fixed_addr, MDL);
+ }
+ if (iaaddr.buffer != NULL) {
+ data_string_forget(&iaaddr, MDL);
+ }
+ if (cli_enc_opt_state != NULL) {
+ option_state_dereference(&cli_enc_opt_state, MDL);
+ }
+ if (cli_enc_opt_data.buffer != NULL) {
+ data_string_forget(&cli_enc_opt_data, MDL);
+ }
+ if (opt_state != NULL) {
+ option_state_dereference(&opt_state, MDL);
+ }
+}
+
+/*
+ * Decline means a client has detected that something else is using an
+ * address we gave it.
+ *
+ * Since we're only dealing with fixed leases for now, there's not
+ * much we can do, other that log the occurrence.
+ *
+ * When we start issuing addresses from pools, then we will have to
+ * record our declined addresses and issue another. In general with
+ * IPv6 there is no worry about DoS by clients exhausting space, but
+ * we still need to be aware of this possibility.
+ */
+
+/* TODO: reject unicast messages, unless we set unicast option */
+/* TODO: IA_TA */
+static void
+dhcpv6_decline(struct data_string *reply, struct packet *packet) {
+ struct data_string client_id;
+ struct data_string server_id;
+
+ /*
+ * Validate our input.
+ */
+ if (!valid_client_resp(packet, &client_id, &server_id)) {
+ return;
+ }
+
+ /*
+ * Undefined for IA_PD.
+ */
+ delete_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
+
+ /*
+ * And operate on each IA_NA in this packet.
+ */
+ iterate_over_ia_na(reply, packet, &client_id, &server_id, "Decline",
+ ia_na_match_decline, ia_na_nomatch_decline);
+
+ data_string_forget(&server_id, MDL);
+ data_string_forget(&client_id, MDL);
+}
+
+static void
+ia_na_match_release(const struct data_string *client_id,
+ const struct data_string *iaaddr,
+ struct iasubopt *lease)
+{
+ char tmp_addr[INET6_ADDRSTRLEN];
+
+ log_info("Client %s releases address %s",
+ print_hex_1(client_id->len, client_id->data, 60),
+ inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr)));
+ if (lease != NULL) {
+ release_lease6(lease->ipv6_pool, lease);
+ lease->ia->cltt = cur_time;
+ write_ia(lease->ia);
+ }
+}
+
+static void
+ia_na_nomatch_release(const struct data_string *client_id,
+ const struct data_string *iaaddr,
+ u_int32_t *ia_na_id,
+ struct packet *packet,
+ char *reply_data,
+ int *reply_ofs,
+ int reply_len)
+{
+ char tmp_addr[INET6_ADDRSTRLEN];
+ struct option_state *host_opt_state;
+ int len;
+
+ log_info("Client %s releases address %s, which is not leased to it.",
+ print_hex_1(client_id->len, client_id->data, 60),
+ inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr)));
+
+ /*
+ * Create state for this IA_NA.
+ */
+ host_opt_state = NULL;
+ if (!option_state_allocate(&host_opt_state, MDL)) {
+ log_error("ia_na_nomatch_release: out of memory "
+ "allocating option_state.");
+ goto exit;
+ }
+
+ if (!set_status_code(STATUS_NoBinding,
+ "Release for non-leased address.",
+ host_opt_state)) {
+ goto exit;
+ }
+
+ /*
+ * Insure we have enough space
+ */
+ if (reply_len < (*reply_ofs + 16)) {
+ log_error("ia_na_nomatch_release: "
+ "out of space for reply packet.");
+ goto exit;
+ }
+
+ /*
+ * Put our status code into the reply packet.
+ */
+ len = store_options6(reply_data+(*reply_ofs)+16,
+ reply_len-(*reply_ofs)-16,
+ host_opt_state, packet,
+ required_opts_STATUS_CODE, NULL);
+
+ /*
+ * Store the non-encapsulated option data for this
+ * IA_NA into our reply packet. Defined in RFC 3315,
+ * section 22.4.
+ */
+ /* option number */
+ putUShort((unsigned char *)reply_data+(*reply_ofs), D6O_IA_NA);
+ /* option length */
+ putUShort((unsigned char *)reply_data+(*reply_ofs)+2, len + 12);
+ /* IA_NA, copied from the client */
+ memcpy(reply_data+(*reply_ofs)+4, ia_na_id, 4);
+ /* t1 and t2, odd that we need them, but here it is */
+ putULong((unsigned char *)reply_data+(*reply_ofs)+8, 0);
+ putULong((unsigned char *)reply_data+(*reply_ofs)+12, 0);
+
+ /*
+ * Get ready for next IA_NA.
+ */
+ *reply_ofs += (len + 16);
+
+exit:
+ option_state_dereference(&host_opt_state, MDL);
+}
+
+static void
+ia_pd_match_release(const struct data_string *client_id,
+ const struct data_string *iapref,
+ struct iasubopt *prefix)
+{
+ char tmp_addr[INET6_ADDRSTRLEN];
+
+ log_info("Client %s releases prefix %s/%u",
+ print_hex_1(client_id->len, client_id->data, 60),
+ inet_ntop(AF_INET6, iapref->data + 9,
+ tmp_addr, sizeof(tmp_addr)),
+ (unsigned) getUChar(iapref->data + 8));
+ if (prefix != NULL) {
+ release_lease6(prefix->ipv6_pool, prefix);
+ prefix->ia->cltt = cur_time;
+ write_ia(prefix->ia);
+ }
+}
+
+static void
+ia_pd_nomatch_release(const struct data_string *client_id,
+ const struct data_string *iapref,
+ u_int32_t *ia_pd_id,
+ struct packet *packet,
+ char *reply_data,
+ int *reply_ofs,
+ int reply_len)
+{
+ char tmp_addr[INET6_ADDRSTRLEN];
+ struct option_state *host_opt_state;
+ int len;
+
+ log_info("Client %s releases prefix %s/%u, which is not leased to it.",
+ print_hex_1(client_id->len, client_id->data, 60),
+ inet_ntop(AF_INET6, iapref->data + 9,
+ tmp_addr, sizeof(tmp_addr)),
+ (unsigned) getUChar(iapref->data + 8));
+
+ /*
+ * Create state for this IA_PD.
+ */
+ host_opt_state = NULL;
+ if (!option_state_allocate(&host_opt_state, MDL)) {
+ log_error("ia_pd_nomatch_release: out of memory "
+ "allocating option_state.");
+ goto exit;
+ }
+
+ if (!set_status_code(STATUS_NoBinding,
+ "Release for non-leased prefix.",
+ host_opt_state)) {
+ goto exit;
+ }
+
+ /*
+ * Insure we have enough space
+ */
+ if (reply_len < (*reply_ofs + 16)) {
+ log_error("ia_pd_nomatch_release: "
+ "out of space for reply packet.");
+ goto exit;
+ }
+
+ /*
+ * Put our status code into the reply packet.
+ */
+ len = store_options6(reply_data+(*reply_ofs)+16,
+ reply_len-(*reply_ofs)-16,
+ host_opt_state, packet,
+ required_opts_STATUS_CODE, NULL);
+
+ /*
+ * Store the non-encapsulated option data for this
+ * IA_PD into our reply packet. Defined in RFC 3315,
+ * section 22.4.
+ */
+ /* option number */
+ putUShort((unsigned char *)reply_data+(*reply_ofs), D6O_IA_PD);
+ /* option length */
+ putUShort((unsigned char *)reply_data+(*reply_ofs)+2, len + 12);
+ /* IA_PD, copied from the client */
+ memcpy(reply_data+(*reply_ofs)+4, ia_pd_id, 4);
+ /* t1 and t2, odd that we need them, but here it is */
+ putULong((unsigned char *)reply_data+(*reply_ofs)+8, 0);
+ putULong((unsigned char *)reply_data+(*reply_ofs)+12, 0);
+
+ /*
+ * Get ready for next IA_PD.
+ */
+ *reply_ofs += (len + 16);
+
+exit:
+ option_state_dereference(&host_opt_state, MDL);
+}
+
+static void
+iterate_over_ia_pd(struct data_string *reply_ret,
+ struct packet *packet,
+ const struct data_string *client_id,
+ const struct data_string *server_id,
+ const char *packet_type,
+ void (*ia_pd_match)(),
+ void (*ia_pd_nomatch)())
+{
+ struct data_string reply_new;
+ int reply_len;
+ struct option_state *opt_state;
+ struct host_decl *packet_host;
+ struct option_cache *ia;
+ struct option_cache *oc;
+ /* cli_enc_... variables come from the IA_PD options */
+ struct data_string cli_enc_opt_data;
+ struct option_state *cli_enc_opt_state;
+ struct host_decl *host;
+ struct option_state *host_opt_state;
+ struct data_string iaprefix;
+ char reply_data[65536];
+ int reply_ofs;
+ struct iasubopt *prefix;
+ struct ia_xx *existing_ia_pd;
+ int i;
+ struct data_string key;
+ u_int32_t iaid;
+
+ /*
+ * Initialize to empty values, in case we have to exit early.
+ */
+ memset(&reply_new, 0, sizeof(reply_new));
+ opt_state = NULL;
+ memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
+ cli_enc_opt_state = NULL;
+ memset(&iaprefix, 0, sizeof(iaprefix));
+ host_opt_state = NULL;
+ prefix = NULL;
+
+ /*
+ * Compute the available length for the reply.
+ */
+ reply_len = sizeof(reply_data) - reply_ret->len;
+ reply_ofs = 0;
+
+ /*
+ * Find the host record that matches from the packet, if any.
+ */
+ packet_host = NULL;
+ if (!find_hosts_by_uid(&packet_host,
+ client_id->data, client_id->len, MDL)) {
+ packet_host = NULL;
+ /*
+ * Note: In general, we don't expect a client to provide
+ * enough information to match by option for these
+ * types of messages, but if we don't have a UID
+ * match we can check anyway.
+ */
+ if (!find_hosts_by_option(&packet_host,
+ packet, packet->options, MDL)) {
+ packet_host = NULL;
+
+ if (!find_hosts_by_duid_chaddr(&packet_host,
+ client_id))
+ packet_host = NULL;
+ }
+ }
+
+ /*
+ * Build our option state for reply.
+ */
+ opt_state = NULL;
+ if (!option_state_allocate(&opt_state, MDL)) {
+ log_error("iterate_over_ia_pd: no memory for option_state.");
+ goto exit;
+ }
+ execute_statements_in_scope(NULL, packet, NULL, NULL,
+ packet->options, opt_state,
+ &global_scope, root_group, NULL);
+
+ /*
+ * Loop through the IA_PD reported by the client, and deal with
+ * prefixes reported as already in use.
+ */
+ for (ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
+ ia != NULL; ia = ia->next) {
+
+ if (!get_encapsulated_IA_state(&cli_enc_opt_state,
+ &cli_enc_opt_data,
+ packet, ia, IA_PD_OFFSET)) {
+ goto exit;
+ }
+
+ iaid = getULong(cli_enc_opt_data.data);
+
+ oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state,
+ D6O_IAPREFIX);
+ if (oc == NULL) {
+ /* no prefix given for this IA_PD, ignore */
+ option_state_dereference(&cli_enc_opt_state, MDL);
+ data_string_forget(&cli_enc_opt_data, MDL);
+ continue;
+ }
+
+ for (; oc != NULL; oc = oc->next) {
+ memset(&iaprefix, 0, sizeof(iaprefix));
+ if (!evaluate_option_cache(&iaprefix, packet, NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ log_error("iterate_over_ia_pd: "
+ "error evaluating IAPREFIX.");
+ goto exit;
+ }
+
+ /*
+ * Now we need to figure out which host record matches
+ * this IA_PD and IAPREFIX (encapsulated option contents
+ * matching a host record by option).
+ *
+ * XXX: We don't currently track IA_PD separately, but
+ * we will need to do this!
+ */
+ host = NULL;
+ if (!find_hosts_by_option(&host, packet,
+ cli_enc_opt_state, MDL)) {
+ if (packet_host != NULL) {
+ host = packet_host;
+ } else {
+ host = NULL;
+ }
+ }
+ while (host != NULL) {
+ if (host->fixed_prefix != NULL) {
+ struct iaddrcidrnetlist *l;
+ int plen = (int) getUChar(iaprefix.data + 8);
+
+ for (l = host->fixed_prefix; l != NULL;
+ l = l->next) {
+ if (plen != l->cidrnet.bits)
+ continue;
+ if (memcmp(iaprefix.data + 9,
+ l->cidrnet.lo_addr.iabuf,
+ 16) == 0)
+ break;
+ }
+ if ((l != NULL) && (iaprefix.len >= 17))
+ break;
+ }
+ host = host->n_ipaddr;
+ }
+
+ if ((host == NULL) && (iaprefix.len >= IAPREFIX_OFFSET)) {
+ /*
+ * Find existing IA_PD.
+ */
+ if (ia_make_key(&key, iaid,
+ (char *)client_id->data,
+ client_id->len,
+ MDL) != ISC_R_SUCCESS) {
+ log_fatal("iterate_over_ia_pd: no memory for "
+ "key.");
+ }
+
+ existing_ia_pd = NULL;
+ if (ia_hash_lookup(&existing_ia_pd, ia_pd_active,
+ (unsigned char *)key.data,
+ key.len, MDL)) {
+ /*
+ * Make sure this prefix is in the IA_PD.
+ */
+ for (i = 0;
+ i < existing_ia_pd->num_iasubopt;
+ i++) {
+ struct iasubopt *tmp;
+ u_int8_t plen;
+
+ plen = getUChar(iaprefix.data + 8);
+ tmp = existing_ia_pd->iasubopt[i];
+ if ((tmp->plen == plen) &&
+ (memcmp(&tmp->addr,
+ iaprefix.data + 9,
+ 16) == 0)) {
+ iasubopt_reference(&prefix,
+ tmp, MDL);
+ break;
+ }
+ }
+ }
+
+ data_string_forget(&key, MDL);
+ }
+
+ if ((host != NULL) || (prefix != NULL)) {
+ ia_pd_match(client_id, &iaprefix, prefix);
+ } else {
+ ia_pd_nomatch(client_id, &iaprefix,
+ (u_int32_t *)cli_enc_opt_data.data,
+ packet, reply_data, &reply_ofs,
+ reply_len - reply_ofs);
+ }
+
+ if (prefix != NULL) {
+ iasubopt_dereference(&prefix, MDL);
+ }
+
+ data_string_forget(&iaprefix, MDL);
+ }
+
+ option_state_dereference(&cli_enc_opt_state, MDL);
+ data_string_forget(&cli_enc_opt_data, MDL);
+ }
+
+ /*
+ * Return our reply to the caller.
+ * The IA_NA routine has already filled at least the header.
+ */
+ reply_new.len = reply_ret->len + reply_ofs;
+ if (!buffer_allocate(&reply_new.buffer, reply_new.len, MDL)) {
+ log_fatal("No memory to store reply.");
+ }
+ reply_new.data = reply_new.buffer->data;
+ memcpy(reply_new.buffer->data,
+ reply_ret->buffer->data, reply_ret->len);
+ memcpy(reply_new.buffer->data + reply_ret->len,
+ reply_data, reply_ofs);
+ data_string_forget(reply_ret, MDL);
+ data_string_copy(reply_ret, &reply_new, MDL);
+ data_string_forget(&reply_new, MDL);
+
+exit:
+ if (prefix != NULL) {
+ iasubopt_dereference(&prefix, MDL);
+ }
+ if (host_opt_state != NULL) {
+ option_state_dereference(&host_opt_state, MDL);
+ }
+ if (iaprefix.buffer != NULL) {
+ data_string_forget(&iaprefix, MDL);
+ }
+ if (cli_enc_opt_state != NULL) {
+ option_state_dereference(&cli_enc_opt_state, MDL);
+ }
+ if (cli_enc_opt_data.buffer != NULL) {
+ data_string_forget(&cli_enc_opt_data, MDL);
+ }
+ if (opt_state != NULL) {
+ option_state_dereference(&opt_state, MDL);
+ }
+}
+
+/*
+ * Release means a client is done with the leases.
+ */
+
+/* TODO: reject unicast messages, unless we set unicast option */
+static void
+dhcpv6_release(struct data_string *reply, struct packet *packet) {
+ struct data_string client_id;
+ struct data_string server_id;
+
+ /*
+ * Validate our input.
+ */
+ if (!valid_client_resp(packet, &client_id, &server_id)) {
+ return;
+ }
+
+ /*
+ * And operate on each IA_NA in this packet.
+ */
+ iterate_over_ia_na(reply, packet, &client_id, &server_id, "Release",
+ ia_na_match_release, ia_na_nomatch_release);
+
+ /*
+ * And operate on each IA_PD in this packet.
+ */
+ iterate_over_ia_pd(reply, packet, &client_id, &server_id, "Release",
+ ia_pd_match_release, ia_pd_nomatch_release);
+
+ data_string_forget(&server_id, MDL);
+ data_string_forget(&client_id, MDL);
+}
+
+/*
+ * Information-Request is used by clients who have obtained an address
+ * from other means, but want configuration information from the server.
+ */
+
+static void
+dhcpv6_information_request(struct data_string *reply, struct packet *packet) {
+ struct data_string client_id;
+ struct data_string server_id;
+
+ /*
+ * Validate our input.
+ */
+ if (!valid_client_info_req(packet, &server_id)) {
+ return;
+ }
+
+ /*
+ * Get our client ID, if there is one.
+ */
+ memset(&client_id, 0, sizeof(client_id));
+ if (get_client_id(packet, &client_id) != ISC_R_SUCCESS) {
+ data_string_forget(&client_id, MDL);
+ }
+
+ /*
+ * Use the lease_to_client() function. This will work fine,
+ * because the valid_client_info_req() insures that we
+ * don't have any IA that would cause us to allocate
+ * resources to the client.
+ */
+ lease_to_client(reply, packet, &client_id,
+ server_id.data != NULL ? &server_id : NULL);
+
+ /*
+ * Cleanup.
+ */
+ if (client_id.data != NULL) {
+ data_string_forget(&client_id, MDL);
+ }
+ data_string_forget(&server_id, MDL);
+}
+
+/*
+ * The Relay-forw message is sent by relays. It typically contains a
+ * single option, which encapsulates an entire packet.
+ *
+ * We need to build an encapsulated reply.
+ */
+
+/* XXX: this is very, very similar to do_packet6(), and should probably
+ be combined in a clever way */
+static void
+dhcpv6_relay_forw(struct data_string *reply_ret, struct packet *packet) {
+ struct option_cache *oc;
+ struct data_string enc_opt_data;
+ struct packet *enc_packet;
+ unsigned char msg_type;
+ const struct dhcpv6_packet *msg;
+ const struct dhcpv6_relay_packet *relay;
+ struct data_string enc_reply;
+ char link_addr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+ char peer_addr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+ struct data_string a_opt, packet_ero;
+ struct option_state *opt_state;
+ static char reply_data[65536];
+ struct dhcpv6_relay_packet *reply;
+ int reply_ofs;
+
+ /*
+ * Initialize variables for early exit.
+ */
+ opt_state = NULL;
+ memset(&a_opt, 0, sizeof(a_opt));
+ memset(&packet_ero, 0, sizeof(packet_ero));
+ memset(&enc_reply, 0, sizeof(enc_reply));
+ memset(&enc_opt_data, 0, sizeof(enc_opt_data));
+ enc_packet = NULL;
+
+ /*
+ * Get our encapsulated relay message.
+ */
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_RELAY_MSG);
+ if (oc == NULL) {
+ inet_ntop(AF_INET6, &packet->dhcpv6_link_address,
+ link_addr, sizeof(link_addr));
+ inet_ntop(AF_INET6, &packet->dhcpv6_peer_address,
+ peer_addr, sizeof(peer_addr));
+ log_info("Relay-forward from %s with link address=%s and "
+ "peer address=%s missing Relay Message option.",
+ piaddr(packet->client_addr), link_addr, peer_addr);
+ goto exit;
+ }
+
+ if (!evaluate_option_cache(&enc_opt_data, NULL, NULL, NULL,
+ NULL, NULL, &global_scope, oc, MDL)) {
+ log_error("dhcpv6_forw_relay: error evaluating "
+ "relayed message.");
+ goto exit;
+ }
+
+ if (!packet6_len_okay((char *)enc_opt_data.data, enc_opt_data.len)) {
+ log_error("dhcpv6_forw_relay: encapsulated packet too short.");
+ goto exit;
+ }
+
+ /*
+ * Build a packet structure from this encapsulated packet.
+ */
+ enc_packet = NULL;
+ if (!packet_allocate(&enc_packet, MDL)) {
+ log_error("dhcpv6_forw_relay: "
+ "no memory for encapsulated packet.");
+ goto exit;
+ }
+
+ if (!option_state_allocate(&enc_packet->options, MDL)) {
+ log_error("dhcpv6_forw_relay: "
+ "no memory for encapsulated packet's options.");
+ goto exit;
+ }
+
+ enc_packet->client_port = packet->client_port;
+ enc_packet->client_addr = packet->client_addr;
+ interface_reference(&enc_packet->interface, packet->interface, MDL);
+ enc_packet->dhcpv6_container_packet = packet;
+
+ msg_type = enc_opt_data.data[0];
+ if ((msg_type == DHCPV6_RELAY_FORW) ||
+ (msg_type == DHCPV6_RELAY_REPL)) {
+ int relaylen = (int)(offsetof(struct dhcpv6_relay_packet, options));
+ relay = (struct dhcpv6_relay_packet *)enc_opt_data.data;
+ enc_packet->dhcpv6_msg_type = relay->msg_type;
+
+ /* relay-specific data */
+ enc_packet->dhcpv6_hop_count = relay->hop_count;
+ memcpy(&enc_packet->dhcpv6_link_address,
+ relay->link_address, sizeof(relay->link_address));
+ memcpy(&enc_packet->dhcpv6_peer_address,
+ relay->peer_address, sizeof(relay->peer_address));
+
+ if (!parse_option_buffer(enc_packet->options,
+ relay->options,
+ enc_opt_data.len - relaylen,
+ &dhcpv6_universe)) {
+ /* no logging here, as parse_option_buffer() logs all
+ cases where it fails */
+ goto exit;
+ }
+ } else {
+ int msglen = (int)(offsetof(struct dhcpv6_packet, options));
+ msg = (struct dhcpv6_packet *)enc_opt_data.data;
+ enc_packet->dhcpv6_msg_type = msg->msg_type;
+
+ /* message-specific data */
+ memcpy(enc_packet->dhcpv6_transaction_id,
+ msg->transaction_id,
+ sizeof(enc_packet->dhcpv6_transaction_id));
+
+ if (!parse_option_buffer(enc_packet->options,
+ msg->options,
+ enc_opt_data.len - msglen,
+ &dhcpv6_universe)) {
+ /* no logging here, as parse_option_buffer() logs all
+ cases where it fails */
+ goto exit;
+ }
+ }
+
+ /*
+ * This is recursive. It is possible to exceed maximum packet size.
+ * XXX: This will cause the packet send to fail.
+ */
+ build_dhcpv6_reply(&enc_reply, enc_packet);
+
+ /*
+ * If we got no encapsulated data, then it is discarded, and
+ * our reply-forw is also discarded.
+ */
+ if (enc_reply.data == NULL) {
+ goto exit;
+ }
+
+ /*
+ * Now we can use the reply_data buffer.
+ * Packet header stuff all comes from the forward message.
+ */
+ reply = (struct dhcpv6_relay_packet *)reply_data;
+ reply->msg_type = DHCPV6_RELAY_REPL;
+ reply->hop_count = packet->dhcpv6_hop_count;
+ memcpy(reply->link_address, &packet->dhcpv6_link_address,
+ sizeof(reply->link_address));
+ memcpy(reply->peer_address, &packet->dhcpv6_peer_address,
+ sizeof(reply->peer_address));
+ reply_ofs = (int)(offsetof(struct dhcpv6_relay_packet, options));
+
+ /*
+ * Get the reply option state.
+ */
+ opt_state = NULL;
+ if (!option_state_allocate(&opt_state, MDL)) {
+ log_error("dhcpv6_relay_forw: no memory for option state.");
+ goto exit;
+ }
+
+ /*
+ * Append the interface-id if present.
+ */
+ oc = lookup_option(&dhcpv6_universe, packet->options,
+ D6O_INTERFACE_ID);
+ if (oc != NULL) {
+ if (!evaluate_option_cache(&a_opt, packet,
+ NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ log_error("dhcpv6_relay_forw: error evaluating "
+ "Interface ID.");
+ goto exit;
+ }
+ if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL,
+ (unsigned char *)a_opt.data,
+ a_opt.len,
+ D6O_INTERFACE_ID, 0)) {
+ log_error("dhcpv6_relay_forw: error saving "
+ "Interface ID.");
+ goto exit;
+ }
+ data_string_forget(&a_opt, MDL);
+ }
+
+ /*
+ * Append our encapsulated stuff for caller.
+ */
+ if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL,
+ (unsigned char *)enc_reply.data,
+ enc_reply.len,
+ D6O_RELAY_MSG, 0)) {
+ log_error("dhcpv6_relay_forw: error saving Relay MSG.");
+ goto exit;
+ }
+
+ /*
+ * Get the ERO if any.
+ */
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_ERO);
+ if (oc != NULL) {
+ unsigned req;
+ int i;
+
+ if (!evaluate_option_cache(&packet_ero, packet,
+ NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL) ||
+ (packet_ero.len & 1)) {
+ log_error("dhcpv6_relay_forw: error evaluating ERO.");
+ goto exit;
+ }
+
+ /* Decode and apply the ERO. */
+ for (i = 0; i < packet_ero.len; i += 2) {
+ req = getUShort(packet_ero.data + i);
+ /* Already in the reply? */
+ oc = lookup_option(&dhcpv6_universe, opt_state, req);
+ if (oc != NULL)
+ continue;
+ /* Get it from the packet if present. */
+ oc = lookup_option(&dhcpv6_universe,
+ packet->options,
+ req);
+ if (oc == NULL)
+ continue;
+ if (!evaluate_option_cache(&a_opt, packet,
+ NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ log_error("dhcpv6_relay_forw: error "
+ "evaluating option %u.", req);
+ goto exit;
+ }
+ if (!save_option_buffer(&dhcpv6_universe,
+ opt_state,
+ NULL,
+ (unsigned char *)a_opt.data,
+ a_opt.len,
+ req,
+ 0)) {
+ log_error("dhcpv6_relay_forw: error saving "
+ "option %u.", req);
+ goto exit;
+ }
+ data_string_forget(&a_opt, MDL);
+ }
+ }
+
+ reply_ofs += store_options6(reply_data + reply_ofs,
+ sizeof(reply_data) - reply_ofs,
+ opt_state, packet,
+ required_opts_agent, &packet_ero);
+
+ /*
+ * Return our reply to the caller.
+ */
+ reply_ret->len = reply_ofs;
+ reply_ret->buffer = NULL;
+ if (!buffer_allocate(&reply_ret->buffer, reply_ret->len, MDL)) {
+ log_fatal("No memory to store reply.");
+ }
+ reply_ret->data = reply_ret->buffer->data;
+ memcpy(reply_ret->buffer->data, reply_data, reply_ofs);
+
+exit:
+ if (opt_state != NULL)
+ option_state_dereference(&opt_state, MDL);
+ if (a_opt.data != NULL) {
+ data_string_forget(&a_opt, MDL);
+ }
+ if (packet_ero.data != NULL) {
+ data_string_forget(&packet_ero, MDL);
+ }
+ if (enc_reply.data != NULL) {
+ data_string_forget(&enc_reply, MDL);
+ }
+ if (enc_opt_data.data != NULL) {
+ data_string_forget(&enc_opt_data, MDL);
+ }
+ if (enc_packet != NULL) {
+ packet_dereference(&enc_packet, MDL);
+ }
+}
+
+static void
+dhcpv6_discard(struct packet *packet) {
+ /* INSIST(packet->msg_type > 0); */
+ /* INSIST(packet->msg_type < dhcpv6_type_name_max); */
+
+ log_debug("Discarding %s from %s; message type not handled by server",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr));
+}
+
+static void
+build_dhcpv6_reply(struct data_string *reply, struct packet *packet) {
+ memset(reply, 0, sizeof(*reply));
+ switch (packet->dhcpv6_msg_type) {
+ case DHCPV6_SOLICIT:
+ dhcpv6_solicit(reply, packet);
+ break;
+ case DHCPV6_ADVERTISE:
+ dhcpv6_discard(packet);
+ break;
+ case DHCPV6_REQUEST:
+ dhcpv6_request(reply, packet);
+ break;
+ case DHCPV6_CONFIRM:
+ dhcpv6_confirm(reply, packet);
+ break;
+ case DHCPV6_RENEW:
+ dhcpv6_renew(reply, packet);
+ break;
+ case DHCPV6_REBIND:
+ dhcpv6_rebind(reply, packet);
+ break;
+ case DHCPV6_REPLY:
+ dhcpv6_discard(packet);
+ break;
+ case DHCPV6_RELEASE:
+ dhcpv6_release(reply, packet);
+ break;
+ case DHCPV6_DECLINE:
+ dhcpv6_decline(reply, packet);
+ break;
+ case DHCPV6_RECONFIGURE:
+ dhcpv6_discard(packet);
+ break;
+ case DHCPV6_INFORMATION_REQUEST:
+ dhcpv6_information_request(reply, packet);
+ break;
+ case DHCPV6_RELAY_FORW:
+ dhcpv6_relay_forw(reply, packet);
+ break;
+ case DHCPV6_RELAY_REPL:
+ dhcpv6_discard(packet);
+ break;
+ case DHCPV6_LEASEQUERY:
+ dhcpv6_leasequery(reply, packet);
+ break;
+ case DHCPV6_LEASEQUERY_REPLY:
+ dhcpv6_discard(packet);
+ break;
+ default:
+ /* XXX: would be nice if we had "notice" level,
+ as syslog, for this */
+ log_info("Discarding unknown DHCPv6 message type %d "
+ "from %s", packet->dhcpv6_msg_type,
+ piaddr(packet->client_addr));
+ }
+}
+
+static void
+log_packet_in(const struct packet *packet) {
+ struct data_string s;
+ u_int32_t tid;
+ char tmp_addr[INET6_ADDRSTRLEN];
+ const void *addr;
+
+ memset(&s, 0, sizeof(s));
+
+ if (packet->dhcpv6_msg_type < dhcpv6_type_name_max) {
+ data_string_sprintfa(&s, "%s message from %s port %d",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr),
+ ntohs(packet->client_port));
+ } else {
+ data_string_sprintfa(&s,
+ "Unknown message type %d from %s port %d",
+ packet->dhcpv6_msg_type,
+ piaddr(packet->client_addr),
+ ntohs(packet->client_port));
+ }
+ if ((packet->dhcpv6_msg_type == DHCPV6_RELAY_FORW) ||
+ (packet->dhcpv6_msg_type == DHCPV6_RELAY_REPL)) {
+ addr = &packet->dhcpv6_link_address;
+ data_string_sprintfa(&s, ", link address %s",
+ inet_ntop(AF_INET6, addr,
+ tmp_addr, sizeof(tmp_addr)));
+ addr = &packet->dhcpv6_peer_address;
+ data_string_sprintfa(&s, ", peer address %s",
+ inet_ntop(AF_INET6, addr,
+ tmp_addr, sizeof(tmp_addr)));
+ } else {
+ tid = 0;
+ memcpy(((char *)&tid)+1, packet->dhcpv6_transaction_id, 3);
+ data_string_sprintfa(&s, ", transaction ID 0x%06X", tid);
+
+/*
+ oc = lookup_option(&dhcpv6_universe, packet->options,
+ D6O_CLIENTID);
+ if (oc != NULL) {
+ memset(&tmp_ds, 0, sizeof(tmp_ds_));
+ if (!evaluate_option_cache(&tmp_ds, packet, NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ log_error("Error evaluating Client Identifier");
+ } else {
+ data_strint_sprintf(&s, ", client ID %s",
+
+ data_string_forget(&tmp_ds, MDL);
+ }
+ }
+*/
+
+ }
+ log_info("%s", s.data);
+
+ data_string_forget(&s, MDL);
+}
+
+void
+dhcpv6(struct packet *packet) {
+ struct data_string reply;
+ struct sockaddr_in6 to_addr;
+ int send_ret;
+
+ /*
+ * Log a message that we received this packet.
+ */
+ log_packet_in(packet);
+
+ /*
+ * Build our reply packet.
+ */
+ build_dhcpv6_reply(&reply, packet);
+
+ if (reply.data != NULL) {
+ /*
+ * Send our reply, if we have one.
+ */
+ memset(&to_addr, 0, sizeof(to_addr));
+ to_addr.sin6_family = AF_INET6;
+ if ((packet->dhcpv6_msg_type == DHCPV6_RELAY_FORW) ||
+ (packet->dhcpv6_msg_type == DHCPV6_RELAY_REPL)) {
+ to_addr.sin6_port = local_port;
+ } else {
+ to_addr.sin6_port = remote_port;
+ }
+
+#if defined (REPLY_TO_SOURCE_PORT)
+ /*
+ * This appears to have been included for testing so we would
+ * not need a root client, but was accidently left in the
+ * final code. We continue to include it in case
+ * some users have come to rely upon it, but leave
+ * it off by default as it's a bad idea.
+ */
+ to_addr.sin6_port = packet->client_port;
+#endif
+
+ memcpy(&to_addr.sin6_addr, packet->client_addr.iabuf,
+ sizeof(to_addr.sin6_addr));
+
+ log_info("Sending %s to %s port %d",
+ dhcpv6_type_names[reply.data[0]],
+ piaddr(packet->client_addr),
+ ntohs(to_addr.sin6_port));
+
+ send_ret = send_packet6(packet->interface,
+ reply.data, reply.len, &to_addr);
+ if (send_ret != reply.len) {
+ log_error("dhcpv6: send_packet6() sent %d of %d bytes",
+ send_ret, reply.len);
+ }
+ data_string_forget(&reply, MDL);
+ }
+}
+
+static void
+seek_shared_host(struct host_decl **hp, struct shared_network *shared) {
+ struct host_decl *nofixed = NULL;
+ struct host_decl *seek, *hold = NULL;
+
+ /*
+ * Seek forward through fixed addresses for the right link.
+ *
+ * Note: how to do this for fixed prefixes???
+ */
+ host_reference(&hold, *hp, MDL);
+ host_dereference(hp, MDL);
+ seek = hold;
+ while (seek != NULL) {
+ if (seek->fixed_addr == NULL)
+ nofixed = seek;
+ else if (fixed_matches_shared(seek, shared))
+ break;
+
+ seek = seek->n_ipaddr;
+ }
+
+ if ((seek == NULL) && (nofixed != NULL))
+ seek = nofixed;
+
+ if (seek != NULL)
+ host_reference(hp, seek, MDL);
+}
+
+static isc_boolean_t
+fixed_matches_shared(struct host_decl *host, struct shared_network *shared) {
+ struct subnet *subnet;
+ struct data_string addr;
+ isc_boolean_t matched;
+ struct iaddr fixed;
+
+ if (host->fixed_addr == NULL)
+ return ISC_FALSE;
+
+ memset(&addr, 0, sizeof(addr));
+ if (!evaluate_option_cache(&addr, NULL, NULL, NULL, NULL, NULL,
+ &global_scope, host->fixed_addr, MDL))
+ return ISC_FALSE;
+
+ if (addr.len < 16) {
+ data_string_forget(&addr, MDL);
+ return ISC_FALSE;
+ }
+
+ fixed.len = 16;
+ memcpy(fixed.iabuf, addr.data, 16);
+
+ matched = ISC_FALSE;
+ for (subnet = shared->subnets ; subnet != NULL ;
+ subnet = subnet->next_sibling) {
+ if (addr_eq(subnet_number(fixed, subnet->netmask),
+ subnet->net)) {
+ matched = ISC_TRUE;
+ break;
+ }
+ }
+
+ data_string_forget(&addr, MDL);
+ return matched;
+}
+
+/*
+ * find_host_by_duid_chaddr() synthesizes a DHCPv4-like 'hardware'
+ * parameter from a DHCPv6 supplied DUID (client-identifier option),
+ * and may seek to use client or relay supplied hardware addresses.
+ */
+static int
+find_hosts_by_duid_chaddr(struct host_decl **host,
+ const struct data_string *client_id) {
+ static int once_htype;
+ int htype, hlen;
+ const unsigned char *chaddr;
+
+ /*
+ * The DUID-LL and DUID-LLT must have a 2-byte DUID type and 2-byte
+ * htype.
+ */
+ if (client_id->len < 4)
+ return 0;
+
+ /*
+ * The third and fourth octets of the DUID-LL and DUID-LLT
+ * is the hardware type, but in 16 bits.
+ */
+ htype = getUShort(client_id->data + 2);
+ hlen = 0;
+ chaddr = NULL;
+
+ /* The first two octets of the DUID identify the type. */
+ switch(getUShort(client_id->data)) {
+ case DUID_LLT:
+ if (client_id->len > 8) {
+ hlen = client_id->len - 8;
+ chaddr = client_id->data + 8;
+ }
+ break;
+
+ case DUID_LL:
+ /*
+ * Note that client_id->len must be greater than or equal
+ * to four to get to this point in the function.
+ */
+ hlen = client_id->len - 4;
+ chaddr = client_id->data + 4;
+ break;
+
+ default:
+ break;
+ }
+
+ if ((hlen == 0) || (hlen > HARDWARE_ADDR_LEN))
+ return 0;
+
+ /*
+ * XXX: DHCPv6 gives a 16-bit field for the htype. DHCPv4 gives an
+ * 8-bit field. To change the semantics of the generic 'hardware'
+ * structure, we would have to adjust many DHCPv4 sources (from
+ * interface to DHCPv4 lease code), and we would have to update the
+ * 'hardware' config directive (probably being reverse compatible and
+ * providing a new upgrade/replacement primitive). This is a little
+ * too much to change for now. Hopefully we will revisit this before
+ * hardware types exceeding 8 bits are assigned.
+ */
+ if ((htype & 0xFF00) && !once_htype) {
+ once_htype = 1;
+ log_error("Attention: At least one client advertises a "
+ "hardware type of %d, which exceeds the software "
+ "limitation of 255.", htype);
+ }
+
+ return find_hosts_by_haddr(host, htype, chaddr, hlen, MDL);
+}
+
+#endif /* DHCPv6 */
+
diff --git a/server/failover.c b/server/failover.c
new file mode 100644
index 0000000..5018b8a
--- /dev/null
+++ b/server/failover.c
@@ -0,0 +1,6476 @@
+/* failover.c
+
+ Failover protocol support code... */
+
+/*
+ * Copyright (c) 2004-2014 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/
+ *
+ */
+
+#include "cdefs.h"
+#include "dhcpd.h"
+#include <omapip/omapip_p.h>
+
+#if defined (FAILOVER_PROTOCOL)
+dhcp_failover_state_t *failover_states;
+static isc_result_t do_a_failover_option (omapi_object_t *,
+ dhcp_failover_link_t *);
+dhcp_failover_listener_t *failover_listeners;
+
+static isc_result_t failover_message_reference (failover_message_t **,
+ failover_message_t *,
+ const char *file, int line);
+static isc_result_t failover_message_dereference (failover_message_t **,
+ const char *file, int line);
+
+static void dhcp_failover_pool_balance(dhcp_failover_state_t *state);
+static void dhcp_failover_pool_reqbalance(dhcp_failover_state_t *state);
+static int dhcp_failover_pool_dobalance(dhcp_failover_state_t *state,
+ isc_boolean_t *sendreq);
+static inline int secondary_not_hoarding(dhcp_failover_state_t *state,
+ struct pool *p);
+
+
+void dhcp_failover_startup ()
+{
+ dhcp_failover_state_t *state;
+ isc_result_t status;
+ struct timeval tv;
+
+ for (state = failover_states; state; state = state -> next) {
+ dhcp_failover_state_transition (state, "startup");
+
+ if (state -> pool_count == 0) {
+ log_error ("failover peer declaration with no %s",
+ "referring pools.");
+ log_error ("In order to use failover, you MUST %s",
+ "refer to your main failover declaration");
+ log_error ("in each pool declaration. You MUST %s",
+ "NOT use range declarations outside");
+ log_fatal ("of pool declarations.");
+ }
+ /* In case the peer is already running, immediately try
+ to establish a connection with it. */
+ status = dhcp_failover_link_initiate ((omapi_object_t *)state);
+ if (status != ISC_R_SUCCESS && status != DHCP_R_INCOMPLETE) {
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("add_timeout +90 dhcp_failover_reconnect");
+#endif
+ tv . tv_sec = cur_time + 90;
+ tv . tv_usec = 0;
+ add_timeout (&tv,
+ dhcp_failover_reconnect, state,
+ (tvref_t)
+ dhcp_failover_state_reference,
+ (tvunref_t)
+ dhcp_failover_state_dereference);
+ log_error ("failover peer %s: %s", state -> name,
+ isc_result_totext (status));
+ }
+
+ status = (dhcp_failover_listen
+ ((omapi_object_t *)state));
+ if (status != ISC_R_SUCCESS) {
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("add_timeout +90 %s",
+ "dhcp_failover_listener_restart");
+#endif
+ tv . tv_sec = cur_time + 90;
+ tv . tv_usec = 0;
+ add_timeout (&tv,
+ dhcp_failover_listener_restart,
+ state,
+ (tvref_t)omapi_object_reference,
+ (tvunref_t)omapi_object_dereference);
+ }
+ }
+}
+
+int dhcp_failover_write_all_states ()
+{
+ dhcp_failover_state_t *state;
+
+ for (state = failover_states; state; state = state -> next) {
+ if (!write_failover_state (state))
+ return 0;
+ }
+ return 1;
+}
+
+isc_result_t enter_failover_peer (peer)
+ dhcp_failover_state_t *peer;
+{
+ dhcp_failover_state_t *dup = (dhcp_failover_state_t *)0;
+ isc_result_t status;
+
+ status = find_failover_peer (&dup, peer -> name, MDL);
+ if (status == ISC_R_NOTFOUND) {
+ if (failover_states) {
+ dhcp_failover_state_reference (&peer -> next,
+ failover_states, MDL);
+ dhcp_failover_state_dereference (&failover_states,
+ MDL);
+ }
+ dhcp_failover_state_reference (&failover_states, peer, MDL);
+ return ISC_R_SUCCESS;
+ }
+ dhcp_failover_state_dereference (&dup, MDL);
+ if (status == ISC_R_SUCCESS)
+ return ISC_R_EXISTS;
+ return status;
+}
+
+isc_result_t find_failover_peer (peer, name, file, line)
+ dhcp_failover_state_t **peer;
+ const char *name;
+ const char *file;
+ int line;
+{
+ dhcp_failover_state_t *p;
+
+ for (p = failover_states; p; p = p -> next)
+ if (!strcmp (name, p -> name))
+ break;
+ if (p)
+ return dhcp_failover_state_reference (peer, p, file, line);
+ return ISC_R_NOTFOUND;
+}
+
+/* The failover protocol has three objects associated with it. For
+ each failover partner declaration in the dhcpd.conf file, primary
+ or secondary, there is a failover_state object. For any primary or
+ secondary state object that has a connection to its peer, there is
+ also a failover_link object, which has its own input state separate
+ from the failover protocol state for managing the actual bytes
+ coming in off the wire. Finally, there will be one listener object
+ for every distinct port number associated with a secondary
+ failover_state object. Normally all secondary failover_state
+ objects are expected to listen on the same port number, so there
+ need be only one listener object, but if different port numbers are
+ specified for each failover object, there could be as many as one
+ listener object for each secondary failover_state object. */
+
+/* This, then, is the implementation of the failover link object. */
+
+isc_result_t dhcp_failover_link_initiate (omapi_object_t *h)
+{
+ isc_result_t status;
+ dhcp_failover_link_t *obj;
+ dhcp_failover_state_t *state;
+ omapi_object_t *o;
+ int i;
+ struct data_string ds;
+ omapi_addr_list_t *addrs = (omapi_addr_list_t *)0;
+ omapi_addr_t local_addr;
+
+ /* Find the failover state in the object chain. */
+ for (o = h; o -> outer; o = o -> outer)
+ ;
+ for (; o; o = o -> inner) {
+ if (o -> type == dhcp_type_failover_state)
+ break;
+ }
+ if (!o)
+ return DHCP_R_INVALIDARG;
+ state = (dhcp_failover_state_t *)o;
+
+ obj = (dhcp_failover_link_t *)0;
+ status = dhcp_failover_link_allocate (&obj, MDL);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ option_cache_reference (&obj -> peer_address,
+ state -> partner.address, MDL);
+ obj -> peer_port = state -> partner.port;
+ dhcp_failover_state_reference (&obj -> state_object, state, MDL);
+
+ memset (&ds, 0, sizeof ds);
+ if (!evaluate_option_cache (&ds, (struct packet *)0, (struct lease *)0,
+ (struct client_state *)0,
+ (struct option_state *)0,
+ (struct option_state *)0,
+ &global_scope, obj -> peer_address, MDL)) {
+ dhcp_failover_link_dereference (&obj, MDL);
+ return ISC_R_UNEXPECTED;
+ }
+
+ /* Make an omapi address list out of a buffer containing zero or more
+ IPv4 addresses. */
+ status = omapi_addr_list_new (&addrs, ds.len / 4, MDL);
+ if (status != ISC_R_SUCCESS) {
+ dhcp_failover_link_dereference (&obj, MDL);
+ return status;
+ }
+
+ for (i = 0; i < addrs -> count; i++) {
+ addrs -> addresses [i].addrtype = AF_INET;
+ addrs -> addresses [i].addrlen = sizeof (struct in_addr);
+ memcpy (addrs -> addresses [i].address,
+ &ds.data [i * 4], sizeof (struct in_addr));
+ addrs -> addresses [i].port = obj -> peer_port;
+ }
+ data_string_forget (&ds, MDL);
+
+ /* Now figure out the local address that we're supposed to use. */
+ if (!state -> me.address ||
+ !evaluate_option_cache (&ds, (struct packet *)0,
+ (struct lease *)0,
+ (struct client_state *)0,
+ (struct option_state *)0,
+ (struct option_state *)0,
+ &global_scope, state -> me.address,
+ MDL)) {
+ memset (&local_addr, 0, sizeof local_addr);
+ local_addr.addrtype = AF_INET;
+ local_addr.addrlen = sizeof (struct in_addr);
+ if (!state -> server_identifier.len) {
+ log_fatal ("failover peer %s: no local address.",
+ state -> name);
+ }
+ } else {
+ if (ds.len != sizeof (struct in_addr)) {
+ log_error("failover peer %s: 'address' parameter "
+ "fails to resolve to an IPv4 address",
+ state->name);
+ data_string_forget (&ds, MDL);
+ dhcp_failover_link_dereference (&obj, MDL);
+ omapi_addr_list_dereference (&addrs, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+ local_addr.addrtype = AF_INET;
+ local_addr.addrlen = ds.len;
+ memcpy (local_addr.address, ds.data, ds.len);
+ if (!state -> server_identifier.len)
+ data_string_copy (&state -> server_identifier,
+ &ds, MDL);
+ data_string_forget (&ds, MDL);
+ local_addr.port = 0; /* Let the O.S. choose. */
+ }
+
+ status = omapi_connect_list ((omapi_object_t *)obj,
+ addrs, &local_addr);
+ omapi_addr_list_dereference (&addrs, MDL);
+
+ dhcp_failover_link_dereference (&obj, MDL);
+ return status;
+}
+
+isc_result_t dhcp_failover_link_signal (omapi_object_t *h,
+ const char *name, va_list ap)
+{
+ isc_result_t status;
+ dhcp_failover_link_t *link;
+ omapi_object_t *c;
+ dhcp_failover_state_t *s, *state = (dhcp_failover_state_t *)0;
+ char *sname;
+ int slen;
+ struct timeval tv;
+
+ if (h -> type != dhcp_type_failover_link) {
+ /* XXX shouldn't happen. Put an assert here? */
+ return ISC_R_UNEXPECTED;
+ }
+ link = (dhcp_failover_link_t *)h;
+
+ if (!strcmp (name, "connect")) {
+ if (link -> state_object -> i_am == primary) {
+ status = dhcp_failover_send_connect (h);
+ if (status != ISC_R_SUCCESS) {
+ log_info ("dhcp_failover_send_connect: %s",
+ isc_result_totext (status));
+ omapi_disconnect (h -> outer, 1);
+ }
+ } else
+ status = ISC_R_SUCCESS;
+ /* Allow the peer fifteen seconds to send us a
+ startup message. */
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("add_timeout +15 %s",
+ "dhcp_failover_link_startup_timeout");
+#endif
+ tv . tv_sec = cur_time + 15;
+ tv . tv_usec = 0;
+ add_timeout (&tv,
+ dhcp_failover_link_startup_timeout,
+ link,
+ (tvref_t)dhcp_failover_link_reference,
+ (tvunref_t)dhcp_failover_link_dereference);
+ return status;
+ }
+
+ if (!strcmp (name, "disconnect")) {
+ if (link -> state_object) {
+ dhcp_failover_state_reference (&state,
+ link -> state_object, MDL);
+ link -> state = dhcp_flink_disconnected;
+
+ /* Make the transition. */
+ if (state->link_to_peer == link)
+ dhcp_failover_state_transition(link->state_object, name);
+
+ /* Schedule an attempt to reconnect. */
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info("add_timeout +5 dhcp_failover_reconnect");
+#endif
+ tv.tv_sec = cur_time + 5;
+ tv.tv_usec = cur_tv.tv_usec;
+ add_timeout(&tv, dhcp_failover_reconnect, state,
+ (tvref_t)dhcp_failover_state_reference,
+ (tvunref_t)dhcp_failover_state_dereference);
+
+ dhcp_failover_state_dereference (&state, MDL);
+ }
+ return ISC_R_SUCCESS;
+ }
+
+ if (!strcmp (name, "status")) {
+ if (link -> state_object) {
+ isc_result_t status;
+
+ status = va_arg(ap, isc_result_t);
+
+ if ((status == ISC_R_HOSTUNREACH) || (status == ISC_R_TIMEDOUT)) {
+ dhcp_failover_state_reference (&state,
+ link -> state_object, MDL);
+ link -> state = dhcp_flink_disconnected;
+
+ /* Make the transition. */
+ dhcp_failover_state_transition (link -> state_object,
+ "disconnect");
+
+ /* Start trying to reconnect. */
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("add_timeout +5 %s",
+ "dhcp_failover_reconnect");
+#endif
+ tv . tv_sec = cur_time + 5;
+ tv . tv_usec = 0;
+ add_timeout (&tv, dhcp_failover_reconnect,
+ state,
+ (tvref_t)dhcp_failover_state_reference,
+ (tvunref_t)dhcp_failover_state_dereference);
+ }
+ dhcp_failover_state_dereference (&state, MDL);
+ }
+ return ISC_R_SUCCESS;
+ }
+
+ /* Not a signal we recognize? */
+ if (strcmp (name, "ready")) {
+ if (h -> inner && h -> inner -> type -> signal_handler)
+ return (*(h -> inner -> type -> signal_handler))
+ (h -> inner, name, ap);
+ return ISC_R_NOTFOUND;
+ }
+
+ if (!h -> outer || h -> outer -> type != omapi_type_connection)
+ return DHCP_R_INVALIDARG;
+ c = h -> outer;
+
+ /* We get here because we requested that we be woken up after
+ some number of bytes were read, and that number of bytes
+ has in fact been read. */
+ switch (link -> state) {
+ case dhcp_flink_start:
+ link -> state = dhcp_flink_message_length_wait;
+ if ((omapi_connection_require (c, 2)) != ISC_R_SUCCESS)
+ break;
+ case dhcp_flink_message_length_wait:
+ next_message:
+ link -> state = dhcp_flink_message_wait;
+ link -> imsg = dmalloc (sizeof (failover_message_t), MDL);
+ if (!link -> imsg) {
+ status = ISC_R_NOMEMORY;
+ dhcp_flink_fail:
+ if (link -> imsg) {
+ failover_message_dereference (&link->imsg,
+ MDL);
+ }
+ link -> state = dhcp_flink_disconnected;
+ log_info ("message length wait: %s",
+ isc_result_totext (status));
+ omapi_disconnect (c, 1);
+ /* XXX just blow away the protocol state now?
+ XXX or will disconnect blow it away? */
+ return ISC_R_UNEXPECTED;
+ }
+ memset (link -> imsg, 0, sizeof (failover_message_t));
+ link -> imsg -> refcnt = 1;
+ /* Get the length: */
+ omapi_connection_get_uint16 (c, &link -> imsg_len);
+ link -> imsg_count = 0; /* Bytes read. */
+
+ /* Ensure the message is of valid length. */
+ if (link->imsg_len < DHCP_FAILOVER_MIN_MESSAGE_SIZE ||
+ link->imsg_len > DHCP_FAILOVER_MAX_MESSAGE_SIZE) {
+ status = ISC_R_UNEXPECTED;
+ goto dhcp_flink_fail;
+ }
+
+ if ((omapi_connection_require (c, link -> imsg_len - 2U)) !=
+ ISC_R_SUCCESS)
+ break;
+ case dhcp_flink_message_wait:
+ /* Read in the message. At this point we have the
+ entire message in the input buffer. For each
+ incoming value ID, set a bit in the bitmask
+ indicating that we've gotten it. Maybe flag an
+ error message if the bit is already set. Once
+ we're done reading, we can check the bitmask to
+ make sure that the required fields for each message
+ have been included. */
+
+ link -> imsg_count += 2; /* Count the length as read. */
+
+ /* Get message type. */
+ omapi_connection_copyout (&link -> imsg -> type, c, 1);
+ link -> imsg_count++;
+
+ /* Get message payload offset. */
+ omapi_connection_copyout (&link -> imsg_payoff, c, 1);
+ link -> imsg_count++;
+
+ /* Get message time. */
+ omapi_connection_get_uint32 (c, &link -> imsg -> time);
+ link -> imsg_count += 4;
+
+ /* Get transaction ID. */
+ omapi_connection_get_uint32 (c, &link -> imsg -> xid);
+ link -> imsg_count += 4;
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+# if !defined(DEBUG_FAILOVER_CONTACT_MESSAGES)
+ if (link->imsg->type == FTM_CONTACT)
+ goto skip_contact;
+# endif
+ log_info ("link: message %s payoff %d time %ld xid %ld",
+ dhcp_failover_message_name (link -> imsg -> type),
+ link -> imsg_payoff,
+ (unsigned long)link -> imsg -> time,
+ (unsigned long)link -> imsg -> xid);
+# if !defined(DEBUG_FAILOVER_CONTACT_MESSAGES)
+ skip_contact:
+# endif
+#endif
+ /* Skip over any portions of the message header that we
+ don't understand. */
+ if (link -> imsg_payoff - link -> imsg_count) {
+ omapi_connection_copyout ((unsigned char *)0, c,
+ (link -> imsg_payoff -
+ link -> imsg_count));
+ link -> imsg_count = link -> imsg_payoff;
+ }
+
+ /* Now start sucking options off the wire. */
+ while (link -> imsg_count < link -> imsg_len) {
+ status = do_a_failover_option (c, link);
+ if (status != ISC_R_SUCCESS)
+ goto dhcp_flink_fail;
+ }
+
+ /* If it's a connect message, try to associate it with
+ a state object. */
+ /* XXX this should be authenticated! */
+ if (link -> imsg -> type == FTM_CONNECT) {
+ const char *errmsg;
+ int reason;
+
+ if (!(link->imsg->options_present &
+ FTB_RELATIONSHIP_NAME)) {
+ errmsg = "missing relationship-name";
+ reason = FTR_INVALID_PARTNER;
+ goto badconnect;
+ }
+
+ /* See if we can find a failover_state object that
+ matches this connection. This message should only
+ be received by a secondary from a primary. */
+ for (s = failover_states; s; s = s -> next) {
+ if (dhcp_failover_state_match_by_name(s,
+ &link->imsg->relationship_name))
+ state = s;
+ }
+
+ /* If we can't find a failover protocol state
+ for this remote host, drop the connection */
+ if (!state) {
+ errmsg = "unknown failover relationship name";
+ reason = FTR_INVALID_PARTNER;
+
+ badconnect:
+ /* XXX Send a refusal message first?
+ XXX Look in protocol spec for guidance. */
+
+ if (state != NULL) {
+ sname = state->name;
+ slen = strlen(sname);
+ } else if (link->imsg->options_present &
+ FTB_RELATIONSHIP_NAME) {
+ sname = (char *)link->imsg->
+ relationship_name.data;
+ slen = link->imsg->relationship_name.count;
+ } else {
+ sname = "unknown";
+ slen = strlen(sname);
+ }
+
+ log_error("Failover CONNECT from %.*s: %s",
+ slen, sname, errmsg);
+ dhcp_failover_send_connectack
+ ((omapi_object_t *)link, state,
+ reason, errmsg);
+ log_info ("failover: disconnect: %s", errmsg);
+ omapi_disconnect (c, 0);
+ link -> state = dhcp_flink_disconnected;
+ return ISC_R_SUCCESS;
+ }
+
+ if ((cur_time > link -> imsg -> time &&
+ cur_time - link -> imsg -> time > 60) ||
+ (cur_time < link -> imsg -> time &&
+ link -> imsg -> time - cur_time > 60)) {
+ errmsg = "time offset too large";
+ reason = FTR_TIMEMISMATCH;
+ goto badconnect;
+ }
+
+ if (!(link -> imsg -> options_present & FTB_HBA) ||
+ link -> imsg -> hba.count != 32) {
+ errmsg = "invalid HBA";
+ reason = FTR_HBA_CONFLICT; /* XXX */
+ goto badconnect;
+ }
+ if (state -> hba)
+ dfree (state -> hba, MDL);
+ state -> hba = dmalloc (32, MDL);
+ if (!state -> hba) {
+ errmsg = "no memory";
+ reason = FTR_MISC_REJECT;
+ goto badconnect;
+ }
+ memcpy (state -> hba, link -> imsg -> hba.data, 32);
+
+ if (!link -> state_object)
+ dhcp_failover_state_reference
+ (&link -> state_object, state, MDL);
+ if (!link -> peer_address)
+ option_cache_reference
+ (&link -> peer_address,
+ state -> partner.address, MDL);
+ }
+
+ /* If we don't have a state object at this point, it's
+ some kind of bogus situation, so just drop the
+ connection. */
+ if (!link -> state_object) {
+ log_info ("failover: connect: no matching state.");
+ omapi_disconnect (c, 1);
+ link -> state = dhcp_flink_disconnected;
+ return DHCP_R_INVALIDARG;
+ }
+
+ /* Once we have the entire message, and we've validated
+ it as best we can here, pass it to the parent. */
+ omapi_signal ((omapi_object_t *)link -> state_object,
+ "message", link);
+ link -> state = dhcp_flink_message_length_wait;
+ if (link -> imsg)
+ failover_message_dereference (&link -> imsg, MDL);
+ /* XXX This is dangerous because we could get into a tight
+ XXX loop reading input without servicing any other stuff.
+ XXX There needs to be a way to relinquish control but
+ XXX get it back immediately if there's no other work to
+ XXX do. */
+ if ((omapi_connection_require (c, 2)) == ISC_R_SUCCESS)
+ goto next_message;
+ break;
+
+ default:
+ log_fatal("Impossible case at %s:%d.", MDL);
+ break;
+ }
+ return ISC_R_SUCCESS;
+}
+
+static isc_result_t do_a_failover_option (c, link)
+ omapi_object_t *c;
+ dhcp_failover_link_t *link;
+{
+ u_int16_t option_code;
+ u_int16_t option_len;
+ unsigned char *op;
+ unsigned op_size;
+ unsigned op_count;
+ int i;
+
+ if (link -> imsg_count + 2 > link -> imsg_len) {
+ log_error ("FAILOVER: message overflow at option code.");
+ return DHCP_R_PROTOCOLERROR;
+ }
+
+ if (link->imsg->type > FTM_MAX) {
+ log_error ("FAILOVER: invalid message type: %d",
+ link->imsg->type);
+ return DHCP_R_PROTOCOLERROR;
+ }
+
+ /* Get option code. */
+ omapi_connection_get_uint16 (c, &option_code);
+ link -> imsg_count += 2;
+
+ if (link -> imsg_count + 2 > link -> imsg_len) {
+ log_error ("FAILOVER: message overflow at length.");
+ return DHCP_R_PROTOCOLERROR;
+ }
+
+ /* Get option length. */
+ omapi_connection_get_uint16 (c, &option_len);
+ link -> imsg_count += 2;
+
+ if (link -> imsg_count + option_len > link -> imsg_len) {
+ log_error ("FAILOVER: message overflow at data.");
+ return DHCP_R_PROTOCOLERROR;
+ }
+
+ /* If it's an unknown code, skip over it. */
+ if ((option_code > FTO_MAX) ||
+ (ft_options[option_code].type == FT_UNDEF)) {
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ log_debug (" option code %d (%s) len %d (not recognized)",
+ option_code,
+ dhcp_failover_option_name (option_code),
+ option_len);
+#endif
+ omapi_connection_copyout ((unsigned char *)0, c, option_len);
+ link -> imsg_count += option_len;
+ return ISC_R_SUCCESS;
+ }
+
+ /* If it's the digest, do it now. */
+ if (ft_options [option_code].type == FT_DIGEST) {
+ link -> imsg_count += option_len;
+ if (link -> imsg_count != link -> imsg_len) {
+ log_error ("FAILOVER: digest not at end of message");
+ return DHCP_R_PROTOCOLERROR;
+ }
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ log_debug (" option %s len %d",
+ ft_options [option_code].name, option_len);
+#endif
+ /* For now, just dump it. */
+ omapi_connection_copyout ((unsigned char *)0, c, option_len);
+ return ISC_R_SUCCESS;
+ }
+
+ /* Only accept an option once. */
+ if (link -> imsg -> options_present & ft_options [option_code].bit) {
+ log_error ("FAILOVER: duplicate option %s",
+ ft_options [option_code].name);
+ return DHCP_R_PROTOCOLERROR;
+ }
+
+ /* Make sure the option is appropriate for this type of message.
+ Really, any option is generally allowed for any message, and the
+ cases where this is not true are too complicated to represent in
+ this way - what this code is doing is to just avoid saving the
+ value of an option we don't have any way to use, which allows
+ us to make the failover_message structure smaller. */
+ if (ft_options [option_code].bit &&
+ !(fto_allowed [link -> imsg -> type] &
+ ft_options [option_code].bit)) {
+ omapi_connection_copyout ((unsigned char *)0, c, option_len);
+ link -> imsg_count += option_len;
+ return ISC_R_SUCCESS;
+ }
+
+ /* Figure out how many elements, how big they are, and where
+ to store them. */
+ if (ft_options [option_code].num_present) {
+ /* If this option takes a fixed number of elements,
+ we expect the space for them to be preallocated,
+ and we can just read the data in. */
+
+ op = ((unsigned char *)link -> imsg) +
+ ft_options [option_code].offset;
+ op_size = ft_sizes [ft_options [option_code].type];
+ op_count = ft_options [option_code].num_present;
+
+ if (option_len != op_size * op_count) {
+ log_error ("FAILOVER: option size (%d:%d), option %s",
+ option_len,
+ (ft_sizes [ft_options [option_code].type] *
+ ft_options [option_code].num_present),
+ ft_options [option_code].name);
+ return DHCP_R_PROTOCOLERROR;
+ }
+ } else {
+ failover_option_t *fo;
+
+ /* FT_DDNS* are special - one or two bytes of status
+ followed by the client FQDN. */
+
+ /* Note: FT_DDNS* option support appears to be incomplete.
+ ISC-Bugs #36996 has been opened to address this. */
+ if (ft_options [option_code].type == FT_DDNS ||
+ ft_options [option_code].type == FT_DDNS1) {
+ ddns_fqdn_t *ddns =
+ ((ddns_fqdn_t *)
+ (((char *)link -> imsg) +
+ ft_options [option_code].offset));
+
+ op_count = (ft_options [option_code].type == FT_DDNS1
+ ? 1 : 2);
+
+ omapi_connection_copyout (&ddns -> codes [0],
+ c, op_count);
+ link -> imsg_count += op_count;
+ if (op_count == 1)
+ ddns -> codes [1] = 0;
+ op_size = 1;
+ op_count = option_len - op_count;
+
+ ddns -> length = op_count;
+ ddns -> data = dmalloc (op_count, MDL);
+ if (!ddns -> data) {
+ log_error ("FAILOVER: no memory getting%s(%d)",
+ " DNS data ", op_count);
+
+ /* Actually, NO_MEMORY, but if we lose here
+ we have to drop the connection. */
+ return DHCP_R_PROTOCOLERROR;
+ }
+ omapi_connection_copyout (ddns -> data, c, op_count);
+ goto out;
+ }
+
+ /* A zero for num_present means that any number of
+ elements can appear, so we have to figure out how
+ many we got from the length of the option, and then
+ fill out a failover_option structure describing the
+ data. */
+ op_size = ft_sizes [ft_options [option_code].type];
+
+ /* Make sure that option data length is a multiple of the
+ size of the data type being sent. */
+ if (op_size > 1 && option_len % op_size) {
+ log_error ("FAILOVER: option_len %d not %s%d",
+ option_len, "multiple of ", op_size);
+ return DHCP_R_PROTOCOLERROR;
+ }
+
+ op_count = option_len / op_size;
+
+ fo = ((failover_option_t *)
+ (((char *)link -> imsg) +
+ ft_options [option_code].offset));
+
+ fo -> count = op_count;
+ fo -> data = dmalloc (option_len, MDL);
+ if (!fo -> data) {
+ log_error ("FAILOVER: no memory getting %s (%d)",
+ "option data", op_count);
+
+ return DHCP_R_PROTOCOLERROR;
+ }
+ op = fo -> data;
+ }
+
+ /* For single-byte message values and multi-byte values that
+ don't need swapping, just read them in all at once. */
+ if (op_size == 1 || ft_options [option_code].type == FT_IPADDR) {
+ omapi_connection_copyout ((unsigned char *)op, c, option_len);
+ link -> imsg_count += option_len;
+
+ /*
+ * As of 3.1.0, many option codes were changed to conform to
+ * draft revision 12 (which alphabetized, then renumbered all
+ * the option codes without preserving the version option code
+ * nor bumping its value). As it turns out, the message codes
+ * for CONNECT and CONNECTACK turn out the same, so it tries
+ * its darndest to connect, and falls short (when TLS_REQUEST
+ * comes up size 2 rather than size 1 as draft revision 12 also
+ * mandates).
+ *
+ * The VENDOR_CLASS code in 3.0.x was 11, which is now the HBA
+ * code. Both work out to be arbitrarily long text-or-byte
+ * strings, so they pass parsing.
+ *
+ * Note that it is possible (or intentional), if highly
+ * improbable, for the HBA bit array to exactly match
+ * isc-V3.0.x. Warning here is not an issue; if it really is
+ * 3.0.x, there will be a protocol error later on. If it isn't
+ * actually 3.0.x, then I guess the lucky user will have to
+ * live with a weird warning.
+ */
+ if ((option_code == 11) && (option_len > 9) &&
+ (strncmp((const char *)op, "isc-V3.0.", 9) == 0)) {
+ log_error("WARNING: failover as of versions 3.1.0 and "
+ "on are not reverse compatible with "
+ "versions 3.0.x.");
+ }
+
+ goto out;
+ }
+
+ /* For values that require swapping, read them in one at a time
+ using routines that swap bytes. */
+ for (i = 0; i < op_count; i++) {
+ switch (ft_options [option_code].type) {
+ case FT_UINT32:
+ omapi_connection_get_uint32 (c, (u_int32_t *)op);
+ op += 4;
+ link -> imsg_count += 4;
+ break;
+
+ case FT_UINT16:
+ omapi_connection_get_uint16 (c, (u_int16_t *)op);
+ op += 2;
+ link -> imsg_count += 2;
+ break;
+
+ default:
+ /* Everything else should have been handled
+ already. */
+ log_error ("FAILOVER: option %s: bad type %d",
+ ft_options [option_code].name,
+ ft_options [option_code].type);
+ return DHCP_R_PROTOCOLERROR;
+ }
+ }
+ out:
+ /* Remember that we got this option. */
+ link -> imsg -> options_present |= ft_options [option_code].bit;
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_failover_link_set_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_typed_data_t *value)
+{
+ if (h -> type != omapi_type_protocol)
+ return DHCP_R_INVALIDARG;
+
+ /* Never valid to set these. */
+ if (!omapi_ds_strcmp (name, "link-port") ||
+ !omapi_ds_strcmp (name, "link-name") ||
+ !omapi_ds_strcmp (name, "link-state"))
+ return ISC_R_NOPERM;
+
+ if (h -> inner && h -> inner -> type -> set_value)
+ return (*(h -> inner -> type -> set_value))
+ (h -> inner, id, name, value);
+ return ISC_R_NOTFOUND;
+}
+
+isc_result_t dhcp_failover_link_get_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_value_t **value)
+{
+ dhcp_failover_link_t *link;
+
+ if (h -> type != omapi_type_protocol)
+ return DHCP_R_INVALIDARG;
+ link = (dhcp_failover_link_t *)h;
+
+ if (!omapi_ds_strcmp (name, "link-port")) {
+ return omapi_make_int_value (value, name,
+ (int)link -> peer_port, MDL);
+ } else if (!omapi_ds_strcmp (name, "link-state")) {
+ if (link -> state >= dhcp_flink_state_max)
+ return omapi_make_string_value (value, name,
+ "invalid link state",
+ MDL);
+ return omapi_make_string_value
+ (value, name,
+ dhcp_flink_state_names [link -> state], MDL);
+ }
+
+ if (h -> inner && h -> inner -> type -> get_value)
+ return (*(h -> inner -> type -> get_value))
+ (h -> inner, id, name, value);
+ return ISC_R_NOTFOUND;
+}
+
+isc_result_t dhcp_failover_link_destroy (omapi_object_t *h,
+ const char *file, int line)
+{
+ dhcp_failover_link_t *link;
+ if (h -> type != dhcp_type_failover_link)
+ return DHCP_R_INVALIDARG;
+ link = (dhcp_failover_link_t *)h;
+
+ if (link -> peer_address)
+ option_cache_dereference (&link -> peer_address, file, line);
+ if (link -> imsg)
+ failover_message_dereference (&link -> imsg, file, line);
+ if (link -> state_object)
+ dhcp_failover_state_dereference (&link -> state_object,
+ file, line);
+ return ISC_R_SUCCESS;
+}
+
+/* Write all the published values associated with the object through the
+ specified connection. */
+
+isc_result_t dhcp_failover_link_stuff_values (omapi_object_t *c,
+ omapi_object_t *id,
+ omapi_object_t *l)
+{
+ dhcp_failover_link_t *link;
+ isc_result_t status;
+
+ if (l -> type != dhcp_type_failover_link)
+ return DHCP_R_INVALIDARG;
+ link = (dhcp_failover_link_t *)l;
+
+ status = omapi_connection_put_name (c, "link-port");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32 (c, sizeof (int));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32 (c, link -> peer_port);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "link-state");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ if (link -> state >= dhcp_flink_state_max)
+ status = omapi_connection_put_string (c, "invalid link state");
+ else
+ status = (omapi_connection_put_string
+ (c, dhcp_flink_state_names [link -> state]));
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ if (link -> inner && link -> inner -> type -> stuff_values)
+ return (*(link -> inner -> type -> stuff_values)) (c, id,
+ link -> inner);
+ return ISC_R_SUCCESS;
+}
+
+/* Set up a listener for the omapi protocol. The handle stored points to
+ a listener object, not a protocol object. */
+
+isc_result_t dhcp_failover_listen (omapi_object_t *h)
+{
+ isc_result_t status;
+ dhcp_failover_listener_t *obj, *l;
+ omapi_value_t *value = (omapi_value_t *)0;
+ omapi_addr_t local_addr;
+ unsigned long port;
+
+ status = omapi_get_value_str (h, (omapi_object_t *)0,
+ "local-port", &value);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ if (!value -> value) {
+ omapi_value_dereference (&value, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+
+ status = omapi_get_int_value (&port, value -> value);
+ omapi_value_dereference (&value, MDL);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ local_addr.port = port;
+
+ status = omapi_get_value_str (h, (omapi_object_t *)0,
+ "local-address", &value);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ if (!value -> value) {
+ nogood:
+ omapi_value_dereference (&value, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+
+ if (value -> value -> type != omapi_datatype_data ||
+ value -> value -> u.buffer.len != sizeof (struct in_addr))
+ goto nogood;
+
+ memcpy (local_addr.address, value -> value -> u.buffer.value,
+ value -> value -> u.buffer.len);
+ local_addr.addrlen = value -> value -> u.buffer.len;
+ local_addr.addrtype = AF_INET;
+
+ omapi_value_dereference (&value, MDL);
+
+ /* Are we already listening on this port and address? */
+ for (l = failover_listeners; l; l = l -> next) {
+ if (l -> address.port == local_addr.port &&
+ l -> address.addrtype == local_addr.addrtype &&
+ l -> address.addrlen == local_addr.addrlen &&
+ !memcmp (l -> address.address, local_addr.address,
+ local_addr.addrlen))
+ break;
+ }
+ /* Already listening. */
+ if (l)
+ return ISC_R_SUCCESS;
+
+ obj = (dhcp_failover_listener_t *)0;
+ status = dhcp_failover_listener_allocate (&obj, MDL);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ obj -> address = local_addr;
+
+ status = omapi_listen_addr ((omapi_object_t *)obj, &obj -> address, 1);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_object_reference (&h -> outer,
+ (omapi_object_t *)obj, MDL);
+ if (status != ISC_R_SUCCESS) {
+ dhcp_failover_listener_dereference (&obj, MDL);
+ return status;
+ }
+ status = omapi_object_reference (&obj -> inner, h, MDL);
+ if (status != ISC_R_SUCCESS) {
+ dhcp_failover_listener_dereference (&obj, MDL);
+ return status;
+ }
+
+ /* Put this listener on the list. */
+ if (failover_listeners) {
+ dhcp_failover_listener_reference (&obj -> next,
+ failover_listeners, MDL);
+ dhcp_failover_listener_dereference (&failover_listeners, MDL);
+ }
+ dhcp_failover_listener_reference (&failover_listeners, obj, MDL);
+
+ return dhcp_failover_listener_dereference (&obj, MDL);
+}
+
+/* Signal handler for protocol listener - if we get a connect signal,
+ create a new protocol connection, otherwise pass the signal down. */
+
+isc_result_t dhcp_failover_listener_signal (omapi_object_t *o,
+ const char *name, va_list ap)
+{
+ isc_result_t status;
+ omapi_connection_object_t *c;
+ dhcp_failover_link_t *obj;
+ dhcp_failover_listener_t *p;
+ dhcp_failover_state_t *s, *state = (dhcp_failover_state_t *)0;
+
+ if (!o || o -> type != dhcp_type_failover_listener)
+ return DHCP_R_INVALIDARG;
+ p = (dhcp_failover_listener_t *)o;
+
+ /* Not a signal we recognize? */
+ if (strcmp (name, "connect")) {
+ if (p -> inner && p -> inner -> type -> signal_handler)
+ return (*(p -> inner -> type -> signal_handler))
+ (p -> inner, name, ap);
+ return ISC_R_NOTFOUND;
+ }
+
+ c = va_arg (ap, omapi_connection_object_t *);
+ if (!c || c -> type != omapi_type_connection)
+ return DHCP_R_INVALIDARG;
+
+ /* See if we can find a failover_state object that
+ matches this connection. */
+ for (s = failover_states; s; s = s -> next) {
+ if (dhcp_failover_state_match
+ (s, (u_int8_t *)&c -> remote_addr.sin_addr,
+ sizeof c -> remote_addr.sin_addr)) {
+ state = s;
+ break;
+ }
+ }
+ if (!state) {
+ log_info ("failover: listener: no matching state");
+ omapi_disconnect ((omapi_object_t *)c, 1);
+ return(ISC_R_NOTFOUND);
+ }
+
+ obj = (dhcp_failover_link_t *)0;
+ status = dhcp_failover_link_allocate (&obj, MDL);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ obj -> peer_port = ntohs (c -> remote_addr.sin_port);
+
+ status = omapi_object_reference (&obj -> outer,
+ (omapi_object_t *)c, MDL);
+ if (status != ISC_R_SUCCESS) {
+ lose:
+ dhcp_failover_link_dereference (&obj, MDL);
+ log_info ("failover: listener: picayune failure.");
+ omapi_disconnect ((omapi_object_t *)c, 1);
+ return status;
+ }
+
+ status = omapi_object_reference (&c -> inner,
+ (omapi_object_t *)obj, MDL);
+ if (status != ISC_R_SUCCESS)
+ goto lose;
+
+ status = dhcp_failover_state_reference (&obj -> state_object,
+ state, MDL);
+ if (status != ISC_R_SUCCESS)
+ goto lose;
+
+ omapi_signal_in ((omapi_object_t *)obj, "connect");
+
+ return dhcp_failover_link_dereference (&obj, MDL);
+}
+
+isc_result_t dhcp_failover_listener_set_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_typed_data_t *value)
+{
+ if (h -> type != dhcp_type_failover_listener)
+ return DHCP_R_INVALIDARG;
+
+ if (h -> inner && h -> inner -> type -> set_value)
+ return (*(h -> inner -> type -> set_value))
+ (h -> inner, id, name, value);
+ return ISC_R_NOTFOUND;
+}
+
+isc_result_t dhcp_failover_listener_get_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_value_t **value)
+{
+ if (h -> type != dhcp_type_failover_listener)
+ return DHCP_R_INVALIDARG;
+
+ if (h -> inner && h -> inner -> type -> get_value)
+ return (*(h -> inner -> type -> get_value))
+ (h -> inner, id, name, value);
+ return ISC_R_NOTFOUND;
+}
+
+isc_result_t dhcp_failover_listener_destroy (omapi_object_t *h,
+ const char *file, int line)
+{
+ dhcp_failover_listener_t *l;
+
+ if (h -> type != dhcp_type_failover_listener)
+ return DHCP_R_INVALIDARG;
+ l = (dhcp_failover_listener_t *)h;
+ if (l -> next)
+ dhcp_failover_listener_dereference (&l -> next, file, line);
+
+ return ISC_R_SUCCESS;
+}
+
+/* Write all the published values associated with the object through the
+ specified connection. */
+
+isc_result_t dhcp_failover_listener_stuff (omapi_object_t *c,
+ omapi_object_t *id,
+ omapi_object_t *p)
+{
+ if (p -> type != dhcp_type_failover_listener)
+ return DHCP_R_INVALIDARG;
+
+ if (p -> inner && p -> inner -> type -> stuff_values)
+ return (*(p -> inner -> type -> stuff_values)) (c, id,
+ p -> inner);
+ return ISC_R_SUCCESS;
+}
+
+/* Set up master state machine for the failover protocol. */
+
+isc_result_t dhcp_failover_register (omapi_object_t *h)
+{
+ isc_result_t status;
+ dhcp_failover_state_t *obj;
+ unsigned long port;
+ omapi_value_t *value = (omapi_value_t *)0;
+
+ status = omapi_get_value_str (h, (omapi_object_t *)0,
+ "local-port", &value);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ if (!value -> value) {
+ omapi_value_dereference (&value, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+
+ status = omapi_get_int_value (&port, value -> value);
+ omapi_value_dereference (&value, MDL);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ obj = (dhcp_failover_state_t *)0;
+ dhcp_failover_state_allocate (&obj, MDL);
+ obj -> me.port = port;
+
+ status = omapi_listen ((omapi_object_t *)obj, port, 1);
+ if (status != ISC_R_SUCCESS) {
+ dhcp_failover_state_dereference (&obj, MDL);
+ return status;
+ }
+
+ status = omapi_object_reference (&h -> outer, (omapi_object_t *)obj,
+ MDL);
+ if (status != ISC_R_SUCCESS) {
+ dhcp_failover_state_dereference (&obj, MDL);
+ return status;
+ }
+ status = omapi_object_reference (&obj -> inner, h, MDL);
+ dhcp_failover_state_dereference (&obj, MDL);
+ return status;
+}
+
+/* Signal handler for protocol state machine. */
+
+isc_result_t dhcp_failover_state_signal (omapi_object_t *o,
+ const char *name, va_list ap)
+{
+ isc_result_t status;
+ dhcp_failover_state_t *state;
+ dhcp_failover_link_t *link;
+ struct timeval tv;
+
+ if (!o || o -> type != dhcp_type_failover_state)
+ return DHCP_R_INVALIDARG;
+ state = (dhcp_failover_state_t *)o;
+
+ /* Not a signal we recognize? */
+ if (strcmp (name, "disconnect") &&
+ strcmp (name, "message")) {
+ if (state -> inner && state -> inner -> type -> signal_handler)
+ return (*(state -> inner -> type -> signal_handler))
+ (state -> inner, name, ap);
+ return ISC_R_NOTFOUND;
+ }
+
+ /* Handle connect signals by seeing what state we're in
+ and potentially doing a state transition. */
+ if (!strcmp (name, "disconnect")) {
+ link = va_arg (ap, dhcp_failover_link_t *);
+
+ dhcp_failover_link_dereference (&state -> link_to_peer, MDL);
+ dhcp_failover_state_transition (state, "disconnect");
+ if (state -> i_am == primary) {
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("add_timeout +90 %s",
+ "dhcp_failover_reconnect");
+#endif
+ tv . tv_sec = cur_time + 90;
+ tv . tv_usec = 0;
+ add_timeout (&tv, dhcp_failover_reconnect,
+ state,
+ (tvref_t)dhcp_failover_state_reference,
+ (tvunref_t)
+ dhcp_failover_state_dereference);
+ }
+ } else if (!strcmp (name, "message")) {
+ link = va_arg (ap, dhcp_failover_link_t *);
+
+ if (link -> imsg -> type == FTM_CONNECT) {
+ /* If we already have a link to the peer, it must be
+ dead, so drop it.
+ XXX Is this the right thing to do?
+ XXX Probably not - what if both peers start at
+ XXX the same time? */
+ if (state -> link_to_peer) {
+ dhcp_failover_send_connectack
+ ((omapi_object_t *)link, state,
+ FTR_DUP_CONNECTION,
+ "already connected");
+ omapi_disconnect (link -> outer, 1);
+ return ISC_R_SUCCESS;
+ }
+ if (!(link -> imsg -> options_present & FTB_MCLT)) {
+ dhcp_failover_send_connectack
+ ((omapi_object_t *)link, state,
+ FTR_INVALID_MCLT,
+ "no MCLT provided");
+ omapi_disconnect (link -> outer, 1);
+ return ISC_R_SUCCESS;
+ }
+
+ dhcp_failover_link_reference (&state -> link_to_peer,
+ link, MDL);
+ status = (dhcp_failover_send_connectack
+ ((omapi_object_t *)link, state, 0, 0));
+ if (status != ISC_R_SUCCESS) {
+ dhcp_failover_link_dereference
+ (&state -> link_to_peer, MDL);
+ log_info ("dhcp_failover_send_connectack: %s",
+ isc_result_totext (status));
+ omapi_disconnect (link -> outer, 1);
+ return ISC_R_SUCCESS;
+ }
+ if (link -> imsg -> options_present & FTB_MAX_UNACKED)
+ state -> partner.max_flying_updates =
+ link -> imsg -> max_unacked;
+ if (link -> imsg -> options_present & FTB_RECEIVE_TIMER)
+ state -> partner.max_response_delay =
+ link -> imsg -> receive_timer;
+ state -> mclt = link -> imsg -> mclt;
+ dhcp_failover_send_state (state);
+ cancel_timeout (dhcp_failover_link_startup_timeout,
+ link);
+ } else if (link -> imsg -> type == FTM_CONNECTACK) {
+ const char *errmsg;
+ char errbuf[1024];
+ int reason;
+
+ cancel_timeout (dhcp_failover_link_startup_timeout,
+ link);
+
+ if (!(link->imsg->options_present &
+ FTB_RELATIONSHIP_NAME)) {
+ errmsg = "missing relationship-name";
+ reason = FTR_INVALID_PARTNER;
+ goto badconnectack;
+ }
+
+ if (link->imsg->options_present & FTB_REJECT_REASON) {
+ /* XXX: add message option to text output. */
+ log_error ("Failover CONNECT to %s rejected: %s",
+ state ? state->name : "unknown",
+ (dhcp_failover_reject_reason_print
+ (link -> imsg -> reject_reason)));
+ /* XXX print message from peer if peer sent message. */
+ omapi_disconnect (link -> outer, 1);
+ return ISC_R_SUCCESS;
+ }
+
+ if (!dhcp_failover_state_match_by_name(state,
+ &link->imsg->relationship_name)) {
+ /* XXX: Overflow results in log truncation, safe. */
+ snprintf(errbuf, sizeof(errbuf), "remote failover "
+ "relationship name %.*s does not match",
+ (int)link->imsg->relationship_name.count,
+ link->imsg->relationship_name.data);
+ errmsg = errbuf;
+ reason = FTR_INVALID_PARTNER;
+ badconnectack:
+ log_error("Failover CONNECTACK from %s: %s",
+ state->name, errmsg);
+ dhcp_failover_send_disconnect ((omapi_object_t *)link,
+ reason, errmsg);
+ omapi_disconnect (link -> outer, 0);
+ return ISC_R_SUCCESS;
+ }
+
+ if (state -> link_to_peer) {
+ errmsg = "already connected";
+ reason = FTR_DUP_CONNECTION;
+ goto badconnectack;
+ }
+
+ if ((cur_time > link -> imsg -> time &&
+ cur_time - link -> imsg -> time > 60) ||
+ (cur_time < link -> imsg -> time &&
+ link -> imsg -> time - cur_time > 60)) {
+ errmsg = "time offset too large";
+ reason = FTR_TIMEMISMATCH;
+ goto badconnectack;
+ }
+
+ dhcp_failover_link_reference (&state -> link_to_peer,
+ link, MDL);
+#if 0
+ /* XXX This is probably the right thing to do, but
+ XXX for release three, to make the smallest possible
+ XXX change, we are doing this when the peer state
+ XXX changes instead. */
+ if (state -> me.state == startup)
+ dhcp_failover_set_state (state,
+ state -> saved_state);
+ else
+#endif
+ dhcp_failover_send_state (state);
+
+ if (link -> imsg -> options_present & FTB_MAX_UNACKED)
+ state -> partner.max_flying_updates =
+ link -> imsg -> max_unacked;
+ if (link -> imsg -> options_present & FTB_RECEIVE_TIMER)
+ state -> partner.max_response_delay =
+ link -> imsg -> receive_timer;
+#if defined (DEBUG_FAILOVER_CONTACT_TIMING)
+ log_info ("add_timeout +%d %s",
+ (int)state -> partner.max_response_delay / 3,
+ "dhcp_failover_send_contact");
+#endif
+ tv . tv_sec = cur_time +
+ (int)state -> partner.max_response_delay / 3;
+ tv . tv_usec = 0;
+ add_timeout (&tv,
+ dhcp_failover_send_contact, state,
+ (tvref_t)dhcp_failover_state_reference,
+ (tvunref_t)dhcp_failover_state_dereference);
+#if defined (DEBUG_FAILOVER_CONTACT_TIMING)
+ log_info ("add_timeout +%d %s",
+ (int)state -> me.max_response_delay,
+ "dhcp_failover_timeout");
+#endif
+ tv . tv_sec = cur_time +
+ (int)state -> me.max_response_delay;
+ tv . tv_usec = 0;
+ add_timeout (&tv,
+ dhcp_failover_timeout, state,
+ (tvref_t)dhcp_failover_state_reference,
+ (tvunref_t)dhcp_failover_state_dereference);
+ } else if (link -> imsg -> type == FTM_DISCONNECT) {
+ if (link -> imsg -> reject_reason) {
+ log_error ("Failover DISCONNECT from %s: %s",
+ state ? state->name : "unknown",
+ (dhcp_failover_reject_reason_print
+ (link -> imsg -> reject_reason)));
+ }
+ omapi_disconnect (link -> outer, 1);
+ } else if (link -> imsg -> type == FTM_BNDUPD) {
+ dhcp_failover_process_bind_update (state,
+ link -> imsg);
+ } else if (link -> imsg -> type == FTM_BNDACK) {
+ dhcp_failover_process_bind_ack (state, link -> imsg);
+ } else if (link -> imsg -> type == FTM_UPDREQ) {
+ dhcp_failover_process_update_request (state,
+ link -> imsg);
+ } else if (link -> imsg -> type == FTM_UPDREQALL) {
+ dhcp_failover_process_update_request_all
+ (state, link -> imsg);
+ } else if (link -> imsg -> type == FTM_UPDDONE) {
+ dhcp_failover_process_update_done (state,
+ link -> imsg);
+ } else if (link -> imsg -> type == FTM_POOLREQ) {
+ dhcp_failover_pool_reqbalance(state);
+ } else if (link -> imsg -> type == FTM_POOLRESP) {
+ log_info ("pool response: %ld leases",
+ (unsigned long)
+ link -> imsg -> addresses_transferred);
+ } else if (link -> imsg -> type == FTM_STATE) {
+ dhcp_failover_peer_state_changed (state,
+ link -> imsg);
+ }
+
+ /* Add a timeout so that if the partner doesn't send
+ another message for the maximum transmit idle time
+ plus a grace of one second, we close the
+ connection. */
+ if (state -> link_to_peer &&
+ state -> link_to_peer == link &&
+ state -> link_to_peer -> state != dhcp_flink_disconnected)
+ {
+#if defined (DEBUG_FAILOVER_CONTACT_TIMING)
+ log_info ("add_timeout +%d %s",
+ (int)state -> me.max_response_delay,
+ "dhcp_failover_timeout");
+#endif
+ tv . tv_sec = cur_time +
+ (int)state -> me.max_response_delay;
+ tv . tv_usec = 0;
+ add_timeout (&tv,
+ dhcp_failover_timeout, state,
+ (tvref_t)dhcp_failover_state_reference,
+ (tvunref_t)dhcp_failover_state_dereference);
+
+ }
+ }
+
+ /* Handle all the events we care about... */
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_failover_state_transition (dhcp_failover_state_t *state,
+ const char *name)
+{
+ isc_result_t status;
+
+ /* XXX Check these state transitions against the spec! */
+ if (!strcmp (name, "disconnect")) {
+ if (state -> link_to_peer) {
+ log_info ("peer %s: disconnected", state -> name);
+ if (state -> link_to_peer -> state_object)
+ dhcp_failover_state_dereference
+ (&state -> link_to_peer -> state_object, MDL);
+ dhcp_failover_link_dereference (&state -> link_to_peer,
+ MDL);
+ }
+ cancel_timeout (dhcp_failover_send_contact, state);
+ cancel_timeout (dhcp_failover_timeout, state);
+ cancel_timeout (dhcp_failover_startup_timeout, state);
+
+ switch (state -> me.state == startup ?
+ state -> saved_state : state -> me.state) {
+ /* In these situations, we remain in the current
+ * state, or if in startup enter those states.
+ */
+ case conflict_done:
+ /* As the peer may not have received or may have
+ * lost track of updates we sent previously we
+ * rescind them, causing us to retransmit them
+ * on an update request.
+ */
+ dhcp_failover_rescind_updates(state);
+ /* fall through */
+
+ case communications_interrupted:
+ case partner_down:
+ case paused:
+ case recover:
+ case recover_done:
+ case recover_wait:
+ case resolution_interrupted:
+ case shut_down:
+ /* Already in the right state? */
+ if (state -> me.state == startup)
+ return (dhcp_failover_set_state
+ (state, state -> saved_state));
+ return ISC_R_SUCCESS;
+
+ case potential_conflict:
+ return dhcp_failover_set_state
+ (state, resolution_interrupted);
+
+ case normal:
+ return dhcp_failover_set_state
+ (state, communications_interrupted);
+
+ case unknown_state:
+ return dhcp_failover_set_state
+ (state, resolution_interrupted);
+
+ default:
+ log_fatal("Impossible case at %s:%d.", MDL);
+ break; /* can't happen. */
+ }
+ } else if (!strcmp (name, "connect")) {
+ switch (state -> me.state) {
+ case communications_interrupted:
+ status = dhcp_failover_set_state (state, normal);
+ dhcp_failover_send_updates (state);
+ return status;
+
+ case resolution_interrupted:
+ return dhcp_failover_set_state (state,
+ potential_conflict);
+
+ case conflict_done:
+ case partner_down:
+ case potential_conflict:
+ case normal:
+ case recover:
+ case shut_down:
+ case paused:
+ case unknown_state:
+ case recover_done:
+ case startup:
+ case recover_wait:
+ return dhcp_failover_send_state (state);
+
+ default:
+ log_fatal("Impossible case at %s:%d.", MDL);
+ break;
+ }
+ } else if (!strcmp (name, "startup")) {
+ dhcp_failover_set_state (state, startup);
+ return ISC_R_SUCCESS;
+ } else if (!strcmp (name, "connect-timeout")) {
+ switch (state -> me.state) {
+ case communications_interrupted:
+ case partner_down:
+ case resolution_interrupted:
+ case paused:
+ case startup:
+ case shut_down:
+ case conflict_done:
+ return ISC_R_SUCCESS;
+
+ case normal:
+ case recover:
+ case recover_wait:
+ case recover_done:
+ case unknown_state:
+ return dhcp_failover_set_state
+ (state, communications_interrupted);
+
+ case potential_conflict:
+ return dhcp_failover_set_state
+ (state, resolution_interrupted);
+
+ default:
+ log_fatal("Impossible case at %s:%d.", MDL);
+ break;
+ }
+ }
+ return DHCP_R_INVALIDARG;
+}
+
+isc_result_t dhcp_failover_set_service_state (dhcp_failover_state_t *state)
+{
+ switch (state -> me.state) {
+ case unknown_state:
+ state -> service_state = not_responding;
+ state -> nrr = " (my state unknown)";
+ break;
+
+ case partner_down:
+ state -> service_state = service_partner_down;
+ state -> nrr = "";
+ break;
+
+ case normal:
+ state -> service_state = cooperating;
+ state -> nrr = "";
+ break;
+
+ case communications_interrupted:
+ state -> service_state = not_cooperating;
+ state -> nrr = "";
+ break;
+
+ case resolution_interrupted:
+ case potential_conflict:
+ case conflict_done:
+ state -> service_state = not_responding;
+ state -> nrr = " (resolving conflicts)";
+ break;
+
+ case recover:
+ state -> service_state = not_responding;
+ state -> nrr = " (recovering)";
+ break;
+
+ case shut_down:
+ state -> service_state = not_responding;
+ state -> nrr = " (shut down)";
+ break;
+
+ case paused:
+ state -> service_state = not_responding;
+ state -> nrr = " (paused)";
+ break;
+
+ case recover_wait:
+ state -> service_state = not_responding;
+ state -> nrr = " (recover wait)";
+ break;
+
+ case recover_done:
+ state -> service_state = not_responding;
+ state -> nrr = " (recover done)";
+ break;
+
+ case startup:
+ state -> service_state = service_startup;
+ state -> nrr = " (startup)";
+ break;
+
+ default:
+ log_fatal("Impossible case at %s:%d.\n", MDL);
+ break;
+ }
+
+ /* Some peer states can require us not to respond, even if our
+ state doesn't. */
+ /* XXX hm. I suspect this isn't true anymore. */
+ if (state -> service_state != not_responding) {
+ switch (state -> partner.state) {
+ case partner_down:
+ state -> service_state = not_responding;
+ state -> nrr = " (peer demands: recovering)";
+ break;
+
+ case potential_conflict:
+ case conflict_done:
+ case resolution_interrupted:
+ state -> service_state = not_responding;
+ state -> nrr = " (peer demands: resolving conflicts)";
+ break;
+
+ /* Other peer states don't affect our behaviour. */
+ default:
+ break;
+ }
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+/*!
+ * \brief Return any leases on the ack queue back to the update queue
+ *
+ * Re-schedule any pending updates by moving them from the ack queue
+ * (update sent awaiting response) back to the update queue (need to
+ * send an update for this lease). This will result in a retransmission
+ * of the update.
+ *
+ * \param state is the state block for the failover connection we are
+ * updating.
+ */
+
+void dhcp_failover_rescind_updates (dhcp_failover_state_t *state)
+{
+ struct lease *lp;
+
+ if (state->ack_queue_tail == NULL)
+ return;
+
+ /* Zap the flags. */
+ for (lp = state->ack_queue_head; lp; lp = lp->next_pending)
+ lp->flags = ((lp->flags & ~ON_ACK_QUEUE) | ON_UPDATE_QUEUE);
+
+ /* Now hook the ack queue to the beginning of the update queue. */
+ if (state->update_queue_head) {
+ lease_reference(&state->ack_queue_tail->next_pending,
+ state->update_queue_head, MDL);
+ lease_dereference(&state->update_queue_head, MDL);
+ }
+ lease_reference(&state->update_queue_head, state->ack_queue_head, MDL);
+
+ if (!state->update_queue_tail) {
+#if defined (POINTER_DEBUG)
+ if (state->ack_queue_tail->next_pending) {
+ log_error("next pending on ack queue tail.");
+ abort();
+ }
+#endif
+ lease_reference(&state->update_queue_tail,
+ state->ack_queue_tail, MDL);
+ }
+ lease_dereference(&state->ack_queue_tail, MDL);
+ lease_dereference(&state->ack_queue_head, MDL);
+ state->cur_unacked_updates = 0;
+}
+
+isc_result_t dhcp_failover_set_state (dhcp_failover_state_t *state,
+ enum failover_state new_state)
+{
+ enum failover_state saved_state;
+ TIME saved_stos;
+ struct pool *p;
+ struct shared_network *s;
+ struct lease *l;
+ struct timeval tv;
+
+ /* If we're in certain states where we're sending updates, and the peer
+ * state changes, we need to re-schedule any pending updates just to
+ * be on the safe side. This results in retransmission.
+ */
+ switch (state -> me.state) {
+ case normal:
+ case potential_conflict:
+ case partner_down:
+ /* Move the ack queue to the update queue */
+ dhcp_failover_rescind_updates(state);
+
+ /* We will re-queue a timeout later, if applicable. */
+ cancel_timeout (dhcp_failover_keepalive, state);
+ break;
+
+ default:
+ break;
+ }
+
+ /* Tentatively make the transition. */
+ saved_state = state -> me.state;
+ saved_stos = state -> me.stos;
+
+ /* Keep the old stos if we're going into recover_wait or if we're
+ coming into or out of startup. */
+ if (new_state != recover_wait && new_state != startup &&
+ saved_state != startup)
+ state -> me.stos = cur_time;
+
+ /* If we're in shutdown, peer is in partner_down, and we're moving
+ to recover, we can skip waiting for MCLT to expire. This happens
+ when a server is moved administratively into shutdown prior to
+ actually shutting down. Of course, if there are any updates
+ pending we can't actually do this. */
+ if (new_state == recover && saved_state == shut_down &&
+ state -> partner.state == partner_down &&
+ !state -> update_queue_head && !state -> ack_queue_head)
+ state -> me.stos = cur_time - state -> mclt;
+
+ state -> me.state = new_state;
+ if (new_state == startup && saved_state != startup)
+ state -> saved_state = saved_state;
+
+ /* If we can't record the new state, we can't make a state transition. */
+ if (!write_failover_state (state) || !commit_leases ()) {
+ log_error ("Unable to record current failover state for %s",
+ state -> name);
+ state -> me.state = saved_state;
+ state -> me.stos = saved_stos;
+ return ISC_R_IOERROR;
+ }
+
+ log_info ("failover peer %s: I move from %s to %s",
+ state -> name, dhcp_failover_state_name_print (saved_state),
+ dhcp_failover_state_name_print (state -> me.state));
+
+ /* If we were in startup and we just left it, cancel the timeout. */
+ if (new_state != startup && saved_state == startup)
+ cancel_timeout (dhcp_failover_startup_timeout, state);
+
+ /*
+ * If the state changes for any reason, cancel 'delayed auto state
+ * changes' (currently there is just the one).
+ */
+ cancel_timeout(dhcp_failover_auto_partner_down, state);
+
+ /* Set our service state. */
+ dhcp_failover_set_service_state (state);
+
+ /* Tell the peer about it. */
+ if (state -> link_to_peer)
+ dhcp_failover_send_state (state);
+
+ switch (new_state) {
+ case communications_interrupted:
+ /*
+ * There is an optional feature to automatically enter partner
+ * down after a timer expires, upon entering comms-interrupted.
+ * This feature is generally not safe except in specific
+ * circumstances.
+ *
+ * A zero value (also the default) disables it.
+ */
+ if (state->auto_partner_down == 0)
+ break;
+
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info("add_timeout +%lu dhcp_failover_auto_partner_down",
+ (unsigned long)state->auto_partner_down);
+#endif
+ tv.tv_sec = cur_time + state->auto_partner_down;
+ tv.tv_usec = 0;
+ add_timeout(&tv, dhcp_failover_auto_partner_down, state,
+ (tvref_t)omapi_object_reference,
+ (tvunref_t)omapi_object_dereference);
+ break;
+
+ case normal:
+ /* Upon entering normal state, the server is expected to retransmit
+ * all pending binding updates. This is a good opportunity to
+ * rebalance the pool (potentially making new pending updates),
+ * which also schedules the next pool rebalance.
+ */
+ dhcp_failover_pool_balance(state);
+ dhcp_failover_generate_update_queue(state, 0);
+
+ if (state->update_queue_tail != NULL) {
+ dhcp_failover_send_updates(state);
+ log_info("Sending updates to %s.", state->name);
+ }
+
+ break;
+
+ case potential_conflict:
+ if ((state->i_am == primary) ||
+ ((state->i_am == secondary) &&
+ (state->partner.state == conflict_done)))
+ dhcp_failover_send_update_request (state);
+ break;
+
+ case startup:
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("add_timeout +15 %s",
+ "dhcp_failover_startup_timeout");
+#endif
+ tv . tv_sec = cur_time + 15;
+ tv . tv_usec = 0;
+ add_timeout (&tv,
+ dhcp_failover_startup_timeout,
+ state,
+ (tvref_t)omapi_object_reference,
+ (tvunref_t)
+ omapi_object_dereference);
+ break;
+
+ /* If we come back in recover_wait and there's still waiting
+ to do, set a timeout. */
+ case recover_wait:
+ if (state -> me.stos + state -> mclt > cur_time) {
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("add_timeout +%d %s",
+ (int)(cur_time -
+ state -> me.stos + state -> mclt),
+ "dhcp_failover_startup_timeout");
+#endif
+ tv . tv_sec = (int)(state -> me.stos + state -> mclt);
+ tv . tv_usec = 0;
+ add_timeout (&tv,
+ dhcp_failover_recover_done,
+ state,
+ (tvref_t)omapi_object_reference,
+ (tvunref_t)
+ omapi_object_dereference);
+ } else
+ dhcp_failover_recover_done (state);
+ break;
+
+ case recover:
+ /* XXX: We're supposed to calculate if updreq or updreqall is
+ * needed. In theory, we should only have to updreqall if we
+ * are positive we lost our stable storage.
+ */
+ if (state -> link_to_peer)
+ dhcp_failover_send_update_request_all (state);
+ break;
+
+ case partner_down:
+ /* For every expired lease, set a timeout for it to become free. */
+ for (s = shared_networks; s; s = s -> next) {
+ for (p = s -> pools; p; p = p -> next) {
+ if (p -> failover_peer == state) {
+ for (l = p->expired ; l ; l = l->next) {
+ l->tsfp = state->me.stos + state->mclt;
+ l->sort_time = (l->tsfp > l->ends) ?
+ l->tsfp : l->ends;
+ }
+ if (p->expired &&
+ (p->expired->sort_time < p->next_event_time)) {
+
+ p->next_event_time = p->expired->sort_time;
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("add_timeout +%d %s",
+ (int)(cur_time - p->next_event_time),
+ "pool_timer");
+#endif
+ tv.tv_sec = p->next_event_time;
+ tv.tv_usec = 0;
+ add_timeout(&tv, pool_timer, p,
+ (tvref_t)pool_reference,
+ (tvunref_t)pool_dereference);
+ }
+ }
+ }
+ }
+ break;
+
+
+ default:
+ break;
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_failover_peer_state_changed (dhcp_failover_state_t *state,
+ failover_message_t *msg)
+{
+ enum failover_state previous_state = state -> partner.state;
+ enum failover_state new_state;
+ int startupp;
+
+ new_state = msg -> server_state;
+ startupp = (msg -> server_flags & FTF_SERVER_STARTUP) ? 1 : 0;
+
+ if (state -> partner.state == new_state && state -> me.state) {
+ switch (state -> me.state) {
+ case startup:
+ /*
+ * If we have a peer state we must be connected.
+ * If so we should move to potential_conflict
+ * instead of resolution_interrupted, otherwise
+ * back to whereever we were before we stopped.
+ */
+ if (state->saved_state == resolution_interrupted)
+ dhcp_failover_set_state(state,
+ potential_conflict);
+ else
+ dhcp_failover_set_state(state,
+ state->saved_state);
+ return ISC_R_SUCCESS;
+
+ case unknown_state:
+ case normal:
+ case potential_conflict:
+ case recover_done:
+ case shut_down:
+ case paused:
+ case recover_wait:
+ return ISC_R_SUCCESS;
+
+ /* If we get a peer state change when we're
+ disconnected, we always process it. */
+ case partner_down:
+ case communications_interrupted:
+ case resolution_interrupted:
+ case recover:
+ case conflict_done:
+ break;
+
+ default:
+ log_fatal("Impossible case at %s:%d.", MDL);
+ break;
+ }
+ }
+
+ state -> partner.state = new_state;
+ state -> partner.stos = cur_time;
+
+ log_info ("failover peer %s: peer moves from %s to %s",
+ state -> name,
+ dhcp_failover_state_name_print (previous_state),
+ dhcp_failover_state_name_print (state -> partner.state));
+
+ if (!write_failover_state (state) || !commit_leases ()) {
+ /* This is bad, but it's not fatal. Of course, if we
+ can't write to the lease database, we're not going to
+ get much done anyway. */
+ log_error ("Unable to record current failover state for %s",
+ state -> name);
+ }
+
+ /* Quickly validate the new state as being one of the 13 known
+ * states.
+ */
+ switch (new_state) {
+ case unknown_state:
+ case startup:
+ case normal:
+ case communications_interrupted:
+ case partner_down:
+ case potential_conflict:
+ case recover:
+ case paused:
+ case shut_down:
+ case recover_done:
+ case resolution_interrupted:
+ case conflict_done:
+ case recover_wait:
+ break;
+
+ default:
+ log_error("failover peer %s: Invalid state: %d", state->name,
+ new_state);
+ dhcp_failover_set_state(state, shut_down);
+ return ISC_R_SUCCESS;
+ }
+
+ /* Do any state transitions that are required as a result of the
+ peer's state transition. */
+
+ switch (state -> me.state == startup ?
+ state -> saved_state : state -> me.state) {
+ case normal:
+ switch (new_state) {
+ case normal:
+ dhcp_failover_state_pool_check (state);
+ break;
+
+ case partner_down:
+ if (state -> me.state == startup)
+ dhcp_failover_set_state (state, recover);
+ else
+ dhcp_failover_set_state (state,
+ potential_conflict);
+ break;
+
+ case potential_conflict:
+ case resolution_interrupted:
+ case conflict_done:
+ /* None of these transitions should ever occur. */
+ log_error("Peer %s: Invalid state transition %s "
+ "to %s.", state->name,
+ dhcp_failover_state_name_print(previous_state),
+ dhcp_failover_state_name_print(new_state));
+ dhcp_failover_set_state (state, shut_down);
+ break;
+
+ case recover:
+ case shut_down:
+ dhcp_failover_set_state (state, partner_down);
+ break;
+
+ case paused:
+ dhcp_failover_set_state (state,
+ communications_interrupted);
+ break;
+
+ default:
+ /* recover_wait, recover_done, unknown_state, startup,
+ * communications_interrupted
+ */
+ break;
+ }
+ break;
+
+ case recover:
+ switch (new_state) {
+ case recover:
+ log_info ("failover peer %s: requesting %s",
+ state -> name, "full update from peer");
+ /* Don't send updreqall if we're really in the
+ startup state, because that will result in two
+ being sent. */
+ if (state -> me.state == recover)
+ dhcp_failover_send_update_request_all (state);
+ break;
+
+ case potential_conflict:
+ case resolution_interrupted:
+ case conflict_done:
+ case normal:
+ dhcp_failover_set_state (state, potential_conflict);
+ break;
+
+ case partner_down:
+ case communications_interrupted:
+ /* We're supposed to send an update request at this
+ point. */
+ /* XXX we don't currently have code here to do any
+ XXX clever detection of when we should send an
+ XXX UPDREQALL message rather than an UPDREQ
+ XXX message. What to do, what to do? */
+ /* Currently when we enter recover state, no matter
+ * the reason, we send an UPDREQALL. So, it makes
+ * the most sense to stick to that until something
+ * better is done.
+ * Furthermore, we only want to send the update
+ * request if we are not in startup state.
+ */
+ if (state -> me.state == recover)
+ dhcp_failover_send_update_request_all (state);
+ break;
+
+ case shut_down:
+ /* XXX We're not explicitly told what to do in this
+ XXX case, but this transition is consistent with
+ XXX what is elsewhere in the draft. */
+ dhcp_failover_set_state (state, partner_down);
+ break;
+
+ /* We can't really do anything in this case. */
+ default:
+ /* paused, recover_done, recover_wait, unknown_state,
+ * startup.
+ */
+ break;
+ }
+ break;
+
+ case potential_conflict:
+ switch (new_state) {
+ case normal:
+ /* This is an illegal transition. */
+ log_error("Peer %s moves to normal during conflict "
+ "resolution - panic, shutting down.",
+ state->name);
+ dhcp_failover_set_state(state, shut_down);
+ break;
+
+ case conflict_done:
+ if (previous_state == potential_conflict)
+ dhcp_failover_send_update_request (state);
+ else
+ log_error("Peer %s: Unexpected move to "
+ "conflict-done.", state->name);
+ break;
+
+ case recover_done:
+ case recover_wait:
+ case potential_conflict:
+ case partner_down:
+ case communications_interrupted:
+ case resolution_interrupted:
+ case paused:
+ break;
+
+ case recover:
+ dhcp_failover_set_state (state, recover);
+ break;
+
+ case shut_down:
+ dhcp_failover_set_state (state, partner_down);
+ break;
+
+ default:
+ /* unknown_state, startup */
+ break;
+ }
+ break;
+
+ case conflict_done:
+ switch (new_state) {
+ case normal:
+ case shut_down:
+ dhcp_failover_set_state(state, new_state);
+ break;
+
+ case potential_conflict:
+ case resolution_interrupted:
+ /*
+ * This can happen when the connection is lost and
+ * recovered after the primary has moved to
+ * conflict-done but the secondary is still in
+ * potential-conflict. In that case, we have to
+ * remain in conflict-done.
+ */
+ break;
+
+ default:
+ log_fatal("Peer %s: Invalid attempt to move from %s "
+ "to %s while local state is conflict-done.",
+ state->name,
+ dhcp_failover_state_name_print(previous_state),
+ dhcp_failover_state_name_print(new_state));
+ }
+ break;
+
+ case partner_down:
+ /* Take no action if other server is starting up. */
+ if (startupp)
+ break;
+
+ switch (new_state) {
+ /* This is where we should be. */
+ case recover:
+ case recover_wait:
+ break;
+
+ case recover_done:
+ dhcp_failover_set_state (state, normal);
+ break;
+
+ case normal:
+ case potential_conflict:
+ case partner_down:
+ case communications_interrupted:
+ case resolution_interrupted:
+ case conflict_done:
+ dhcp_failover_set_state (state, potential_conflict);
+ break;
+
+ default:
+ /* shut_down, paused, unknown_state, startup */
+ break;
+ }
+ break;
+
+ case communications_interrupted:
+ switch (new_state) {
+ case paused:
+ /* Stick with the status quo. */
+ break;
+
+ /* If we're in communications-interrupted and an
+ amnesic peer connects, go to the partner_down
+ state immediately. */
+ case recover:
+ dhcp_failover_set_state (state, partner_down);
+ break;
+
+ case normal:
+ case communications_interrupted:
+ case recover_done:
+ case recover_wait:
+ /* XXX so we don't need to do this specially in
+ XXX the CONNECT and CONNECTACK handlers. */
+ dhcp_failover_send_updates (state);
+ dhcp_failover_set_state (state, normal);
+ break;
+
+ case potential_conflict:
+ case partner_down:
+ case resolution_interrupted:
+ case conflict_done:
+ dhcp_failover_set_state (state, potential_conflict);
+ break;
+
+ case shut_down:
+ dhcp_failover_set_state (state, partner_down);
+ break;
+
+ default:
+ /* unknown_state, startup */
+ break;
+ }
+ break;
+
+ case resolution_interrupted:
+ switch (new_state) {
+ case normal:
+ case recover:
+ case potential_conflict:
+ case partner_down:
+ case communications_interrupted:
+ case resolution_interrupted:
+ case conflict_done:
+ case recover_done:
+ case recover_wait:
+ dhcp_failover_set_state (state, potential_conflict);
+ break;
+
+ case shut_down:
+ dhcp_failover_set_state (state, partner_down);
+ break;
+
+ default:
+ /* paused, unknown_state, startup */
+ break;
+ }
+ break;
+
+ /* Make no transitions while in recover_wait...just wait. */
+ case recover_wait:
+ break;
+
+ case recover_done:
+ switch (new_state) {
+ case recover_done:
+ log_error("Both servers have entered recover-done!");
+ /* Fall through and tranistion to normal anyway */
+
+ case normal:
+ dhcp_failover_set_state (state, normal);
+ break;
+
+ case shut_down:
+ dhcp_failover_set_state (state, partner_down);
+ break;
+
+ default:
+ /* potential_conflict, partner_down,
+ * communications_interrupted, resolution_interrupted,
+ * paused, recover, recover_wait, unknown_state,
+ * startup.
+ */
+ break;
+ }
+ break;
+
+ /* We are essentially dead in the water when we're in
+ either shut_down or paused states, and do not do any
+ automatic state transitions. */
+ case shut_down:
+ case paused:
+ break;
+
+ /* XXX: Shouldn't this be a fatal condition? */
+ case unknown_state:
+ break;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ break;
+
+ }
+
+ /* If we didn't make a transition out of startup as a result of
+ the peer's state change, do it now as a result of the fact that
+ we got a state change from the peer. */
+ if (state -> me.state == startup && state -> saved_state != startup)
+ dhcp_failover_set_state (state, state -> saved_state);
+
+ /* For now, just set the service state based on the peer's state
+ if necessary. */
+ dhcp_failover_set_service_state (state);
+
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Balance operation manual entry; startup, entrance to normal state. No
+ * sense sending a POOLREQ at this stage; the peer is likely about to schedule
+ * their own rebalance event upon entering normal themselves.
+ */
+static void
+dhcp_failover_pool_balance(dhcp_failover_state_t *state)
+{
+ /* Cancel pending event. */
+ cancel_timeout(dhcp_failover_pool_rebalance, state);
+ state->sched_balance = 0;
+
+ dhcp_failover_pool_dobalance(state, NULL);
+}
+
+/*
+ * Balance operation entry from timer event. Once per timer interval is
+ * the only time we want to emit POOLREQs (asserting an interrupt in our
+ * peer).
+ */
+void
+dhcp_failover_pool_rebalance(void *failover_state)
+{
+ dhcp_failover_state_t *state;
+ isc_boolean_t sendreq = ISC_FALSE;
+
+ state = (dhcp_failover_state_t *)failover_state;
+
+ /* Clear scheduled event indicator. */
+ state->sched_balance = 0;
+
+ if (dhcp_failover_pool_dobalance(state, &sendreq))
+ dhcp_failover_send_updates(state);
+
+ if (sendreq)
+ dhcp_failover_send_poolreq(state);
+}
+
+/*
+ * Balance operation entry from POOLREQ protocol message. Do not permit a
+ * POOLREQ to send back a POOLREQ. Ping pong.
+ */
+static void
+dhcp_failover_pool_reqbalance(dhcp_failover_state_t *state)
+{
+ int queued;
+
+ /* Cancel pending event. */
+ cancel_timeout(dhcp_failover_pool_rebalance, state);
+ state->sched_balance = 0;
+
+ queued = dhcp_failover_pool_dobalance(state, NULL);
+
+ dhcp_failover_send_poolresp(state, queued);
+
+ if (queued)
+ dhcp_failover_send_updates(state);
+ else
+ log_info("peer %s: Got POOLREQ, answering negatively! "
+ "Peer may be out of leases or database inconsistent.",
+ state->name);
+}
+
+/*
+ * Do the meat of the work common to all forms of pool rebalance. If the
+ * caller deems it appropriate to transmit POOLREQ messages, it can use the
+ * sendreq pointer to pass in the address of a FALSE value which this function
+ * will conditionally turn TRUE if a POOLREQ is determined to be necessary.
+ * A NULL value may be passed, in which case no action is taken.
+ */
+static int
+dhcp_failover_pool_dobalance(dhcp_failover_state_t *state,
+ isc_boolean_t *sendreq)
+{
+ int lts, total, thresh, hold, panic, pass;
+ int leases_queued = 0;
+ struct lease *lp = (struct lease *)0;
+ struct lease *next = (struct lease *)0;
+ struct shared_network *s;
+ struct pool *p;
+ binding_state_t peer_lease_state;
+ /* binding_state_t my_lease_state; */
+ /* XXX Why is this my_lease_state never used? */
+ struct lease **lq;
+ int (*log_func)(const char *, ...);
+ const char *result, *reqlog;
+
+ if (state -> me.state != normal)
+ return 0;
+
+ state->last_balance = cur_time;
+
+ for (s = shared_networks ; s ; s = s->next) {
+ for (p = s->pools ; p ; p = p->next) {
+ if (p->failover_peer != state)
+ continue;
+
+ /* Right now we're giving the peer half of the free leases.
+ If we have more leases than the peer (i.e., more than
+ half), then the number of leases we have, less the number
+ of leases the peer has, will be how many more leases we
+ have than the peer has. So if we send half that number
+ to the peer, we should be even. */
+ if (p->failover_peer->i_am == primary) {
+ lts = (p->free_leases - p->backup_leases) / 2;
+ peer_lease_state = FTS_BACKUP;
+ /* my_lease_state = FTS_FREE; */
+ lq = &p->free;
+ } else {
+ lts = (p->backup_leases - p->free_leases) / 2;
+ peer_lease_state = FTS_FREE;
+ /* my_lease_state = FTS_BACKUP; */
+ lq = &p->backup;
+ }
+
+ total = p->backup_leases + p->free_leases;
+
+ thresh = ((total * state->max_lease_misbalance) + 50) / 100;
+ hold = ((total * state->max_lease_ownership) + 50) / 100;
+
+ /*
+ * If we need leases (so lts is negative) more than negative
+ * double the thresh%, panic and send poolreq to hopefully wake
+ * up the peer (but more likely the db is inconsistent). But,
+ * if this comes out zero, switch to -1 so that the POOLREQ is
+ * sent on lts == -2 rather than right away at -1.
+ *
+ * Note that we do not subtract -1 from panic all the time
+ * because thresh% and hold% may come out to the same number,
+ * and that is correct operation...where thresh% and hold% are
+ * both -1, we want to send poolreq when lts reaches -3. So,
+ * "-3 < -2", lts < panic.
+ */
+ panic = thresh * -2;
+
+ if (panic == 0)
+ panic = -1;
+
+ if ((sendreq != NULL) && (lts < panic)) {
+ reqlog = " (requesting peer rebalance!)";
+ *sendreq = ISC_TRUE;
+ } else
+ reqlog = "";
+
+ log_info("balancing pool %lx %s total %d free %d "
+ "backup %d lts %d max-own (+/-)%d%s",
+ (unsigned long)p,
+ (p->shared_network ?
+ p->shared_network->name : ""), p->lease_count,
+ p->free_leases, p->backup_leases, lts, hold,
+ reqlog);
+
+ /* In the first pass, try to allocate leases to the
+ * peer which it would normally be responsible for (if
+ * the lease has a hardware address or client-identifier,
+ * and the load-balance-algorithm chooses the peer to
+ * answer that address), up to a hold% excess in the peer's
+ * favor. In the second pass, just send the oldest (first
+ * on the list) leases up to a hold% excess in our favor.
+ *
+ * This could make for additional pool rebalance
+ * events, but preserving MAC possession should be
+ * worth it.
+ */
+ pass = 0;
+ lease_reference(&lp, *lq, MDL);
+
+ while (lp) {
+ if (next)
+ lease_dereference(&next, MDL);
+ if (lp->next)
+ lease_reference(&next, lp->next, MDL);
+
+ /*
+ * Stop if the pool is 'balanced enough.'
+ *
+ * The pool is balanced enough if:
+ *
+ * 1) We're on the first run through and the peer has
+ * its fair share of leases already (lts reaches
+ * -hold).
+ * 2) We're on the second run through, we are shifting
+ * never-used leases, and there is a perfectly even
+ * balance (lts reaches zero).
+ * 3) Second run through, we are shifting previously
+ * used leases, and the local system has its fair
+ * share but no more (lts reaches hold).
+ *
+ * Note that this is implemented below in 3,2,1 order.
+ */
+ if (pass) {
+ if (lp->ends) {
+ if (lts <= hold)
+ break;
+ } else {
+ if (lts <= 0)
+ break;
+ }
+ } else if (lts <= -hold)
+ break;
+
+ if (pass || peer_wants_lease(lp)) {
+ --lts;
+ ++leases_queued;
+ lp->next_binding_state = peer_lease_state;
+ lp->tstp = cur_time;
+ lp->starts = cur_time;
+
+ if (!supersede_lease(lp, NULL, 0, 1, 0, 0) ||
+ !write_lease(lp))
+ log_error("can't commit lease %s on "
+ "giveaway", piaddr(lp->ip_addr));
+ }
+
+ lease_dereference(&lp, MDL);
+ if (next)
+ lease_reference(&lp, next, MDL);
+ else if (!pass) {
+ pass = 1;
+ lease_reference(&lp, *lq, MDL);
+ }
+ }
+
+ if (next)
+ lease_dereference(&next, MDL);
+ if (lp)
+ lease_dereference(&lp, MDL);
+
+ if (lts > thresh) {
+ result = "IMBALANCED";
+ log_func = log_error;
+ } else {
+ result = "balanced";
+ log_func = log_info;
+ }
+
+ log_func("%s pool %lx %s total %d free %d backup %d "
+ "lts %d max-misbal %d", result, (unsigned long)p,
+ (p->shared_network ?
+ p->shared_network->name : ""), p->lease_count,
+ p->free_leases, p->backup_leases, lts, thresh);
+
+ /* Recalculate next rebalance event timer. */
+ dhcp_failover_pool_check(p);
+ }
+ }
+
+ if (leases_queued)
+ commit_leases();
+
+ return leases_queued;
+}
+
+/* dhcp_failover_pool_check: Called whenever FREE or BACKUP leases change
+ * states, on both servers. Check the scheduled time to rebalance the pool
+ * and lower it if applicable.
+ */
+void
+dhcp_failover_pool_check(struct pool *pool)
+{
+ dhcp_failover_state_t *peer;
+ TIME est1, est2;
+ struct timeval tv;
+
+ peer = pool->failover_peer;
+
+ if(!peer || peer->me.state != normal)
+ return;
+
+ /* Estimate the time left until lease exhaustion.
+ * The first lease on the backup or free lists is also the oldest
+ * lease. It is reasonable to guess that it will take at least
+ * as much time for a pool to run out of leases, as the present
+ * age of the oldest lease (seconds since it expired).
+ *
+ * Note that this isn't so sane of an assumption if the oldest
+ * lease is a virgin (ends = 0), we wind up sending this against
+ * the max_balance bounds check.
+ */
+ if(pool->free && pool->free->ends < cur_time)
+ est1 = cur_time - pool->free->ends;
+ else
+ est1 = 0;
+
+ if(pool->backup && pool->backup->ends < cur_time)
+ est2 = cur_time - pool->backup->ends;
+ else
+ est2 = 0;
+
+ /* We don't want to schedule rebalance for when we think we'll run
+ * out of leases, we want to schedule the rebalance for when we think
+ * the disparity will be 'large enough' to warrant action.
+ */
+ est1 = ((est1 * peer->max_lease_misbalance) + 50) / 100;
+ est2 = ((est2 * peer->max_lease_misbalance) + 50) / 100;
+
+ /* Guess when the local system will begin issuing POOLREQ panic
+ * attacks because "max_lease_misbalance*2" has been exceeded.
+ */
+ if(peer->i_am == primary)
+ est1 *= 2;
+ else
+ est2 *= 2;
+
+ /* Select the smallest time. */
+ if(est1 > est2)
+ est1 = est2;
+
+ /* Bounded by the maximum configured value. */
+ if(est1 > peer->max_balance)
+ est1 = peer->max_balance;
+
+ /* Project this time into the future. */
+ est1 += cur_time;
+
+ /* Do not move the time down under the minimum. */
+ est2 = peer->last_balance + peer->min_balance;
+ if(peer->last_balance && (est1 < est2))
+ est1 = est2;
+
+ /* Introduce a random delay. */
+ est1 += random() % 5;
+
+ /* Do not move the time forward, or reset to the same time. */
+ if(peer->sched_balance) {
+ if (est1 >= peer->sched_balance)
+ return;
+
+ /* We are about to schedule the time down, cancel the
+ * current timeout.
+ */
+ cancel_timeout(dhcp_failover_pool_rebalance, peer);
+ }
+
+ /* The time is different, and lower, use it. */
+ peer->sched_balance = est1;
+
+#if defined(DEBUG_FAILOVER_TIMING)
+ log_info("add_timeout +%d dhcp_failover_pool_rebalance",
+ (int)(est1 - cur_time));
+#endif
+ tv.tv_sec = est1;
+ tv.tv_usec = 0;
+ add_timeout(&tv, dhcp_failover_pool_rebalance, peer,
+ (tvref_t)dhcp_failover_state_reference,
+ (tvunref_t)dhcp_failover_state_dereference);
+}
+
+int dhcp_failover_state_pool_check (dhcp_failover_state_t *state)
+{
+ struct shared_network *s;
+ struct pool *p;
+
+ for (s = shared_networks; s; s = s -> next) {
+ for (p = s -> pools; p; p = p -> next) {
+ if (p -> failover_peer != state)
+ continue;
+ dhcp_failover_pool_check (p);
+ }
+ }
+ return 0;
+}
+
+isc_result_t dhcp_failover_send_updates (dhcp_failover_state_t *state)
+{
+ struct lease *lp = (struct lease *)0;
+ isc_result_t status;
+
+ /* Can't update peer if we're not talking to it! */
+ if (!state -> link_to_peer)
+ return ISC_R_SUCCESS;
+
+ /* If there are acks pending, transmit them prior to potentially
+ * sending new updates for the same lease.
+ */
+ if (state->toack_queue_head != NULL)
+ dhcp_failover_send_acks(state);
+
+ while ((state -> partner.max_flying_updates >
+ state -> cur_unacked_updates) && state -> update_queue_head) {
+ /* Grab the head of the update queue. */
+ lease_reference (&lp, state -> update_queue_head, MDL);
+
+ /* Send the update to the peer. */
+ status = dhcp_failover_send_bind_update (state, lp);
+ if (status != ISC_R_SUCCESS) {
+ lease_dereference (&lp, MDL);
+ return status;
+ }
+ lp -> flags &= ~ON_UPDATE_QUEUE;
+
+ /* Take it off the head of the update queue and put the next
+ item in the update queue at the head. */
+ lease_dereference (&state -> update_queue_head, MDL);
+ if (lp -> next_pending) {
+ lease_reference (&state -> update_queue_head,
+ lp -> next_pending, MDL);
+ lease_dereference (&lp -> next_pending, MDL);
+ } else {
+ lease_dereference (&state -> update_queue_tail, MDL);
+ }
+
+ if (state -> ack_queue_head) {
+ lease_reference
+ (&state -> ack_queue_tail -> next_pending,
+ lp, MDL);
+ lease_dereference (&state -> ack_queue_tail, MDL);
+ } else {
+ lease_reference (&state -> ack_queue_head, lp, MDL);
+ }
+#if defined (POINTER_DEBUG)
+ if (lp -> next_pending) {
+ log_error ("ack_queue_tail: lp -> next_pending");
+ abort ();
+ }
+#endif
+ lease_reference (&state -> ack_queue_tail, lp, MDL);
+ lp -> flags |= ON_ACK_QUEUE;
+ lease_dereference (&lp, MDL);
+
+ /* Count the object as an unacked update. */
+ state -> cur_unacked_updates++;
+ }
+ return ISC_R_SUCCESS;
+}
+
+/* Queue an update for a lease. Always returns 1 at this point - it's
+ not an error for this to be called on a lease for which there's no
+ failover peer. */
+
+int dhcp_failover_queue_update (struct lease *lease, int immediate)
+{
+ dhcp_failover_state_t *state;
+
+ if (!lease -> pool ||
+ !lease -> pool -> failover_peer)
+ return 1;
+
+ /* If it's already on the update queue, leave it there. */
+ if (lease -> flags & ON_UPDATE_QUEUE)
+ return 1;
+
+ /* Get the failover state structure for this lease. */
+ state = lease -> pool -> failover_peer;
+
+ /* If it's on the ack queue, take it off. */
+ if (lease -> flags & ON_ACK_QUEUE)
+ dhcp_failover_ack_queue_remove (state, lease);
+
+ if (state -> update_queue_head) {
+ lease_reference (&state -> update_queue_tail -> next_pending,
+ lease, MDL);
+ lease_dereference (&state -> update_queue_tail, MDL);
+ } else {
+ lease_reference (&state -> update_queue_head, lease, MDL);
+ }
+#if defined (POINTER_DEBUG)
+ if (lease -> next_pending) {
+ log_error ("next pending on update queue lease.");
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history (lease);
+#endif
+ abort ();
+ }
+#endif
+ lease_reference (&state -> update_queue_tail, lease, MDL);
+ lease -> flags |= ON_UPDATE_QUEUE;
+ if (immediate)
+ dhcp_failover_send_updates (state);
+ return 1;
+}
+
+int dhcp_failover_send_acks (dhcp_failover_state_t *state)
+{
+ failover_message_t *msg = (failover_message_t *)0;
+
+ /* Must commit all leases prior to acking them. */
+ if (!commit_leases ())
+ return 0;
+
+ while (state -> toack_queue_head) {
+ failover_message_reference
+ (&msg, state -> toack_queue_head, MDL);
+ failover_message_dereference
+ (&state -> toack_queue_head, MDL);
+ if (msg -> next) {
+ failover_message_reference
+ (&state -> toack_queue_head, msg -> next, MDL);
+ }
+
+ dhcp_failover_send_bind_ack (state, msg, 0, (const char *)0);
+
+ failover_message_dereference (&msg, MDL);
+ }
+
+ if (state -> toack_queue_tail)
+ failover_message_dereference (&state -> toack_queue_tail, MDL);
+ state -> pending_acks = 0;
+
+ return 1;
+}
+
+void dhcp_failover_toack_queue_timeout (void *vs)
+{
+ dhcp_failover_state_t *state = vs;
+
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("dhcp_failover_toack_queue_timeout");
+#endif
+
+ dhcp_failover_send_acks (state);
+}
+
+/* Queue an ack for a message. There is currently no way to queue a
+ negative ack -- these need to be sent directly. */
+
+int dhcp_failover_queue_ack (dhcp_failover_state_t *state,
+ failover_message_t *msg)
+{
+ struct timeval tv;
+
+ if (state -> toack_queue_head) {
+ failover_message_reference
+ (&state -> toack_queue_tail -> next, msg, MDL);
+ failover_message_dereference (&state -> toack_queue_tail, MDL);
+ } else {
+ failover_message_reference (&state -> toack_queue_head,
+ msg, MDL);
+ }
+ failover_message_reference (&state -> toack_queue_tail, msg, MDL);
+
+ state -> pending_acks++;
+
+ /* Flush the toack queue whenever we exceed half the number of
+ allowed unacked updates. */
+ if (state -> pending_acks >= state -> partner.max_flying_updates / 2) {
+ dhcp_failover_send_acks (state);
+ }
+
+ /* Schedule a timeout to flush the ack queue. */
+ if (state -> pending_acks > 0) {
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("add_timeout +2 %s",
+ "dhcp_failover_toack_queue_timeout");
+#endif
+ tv . tv_sec = cur_time + 2;
+ tv . tv_usec = 0;
+ add_timeout (&tv,
+ dhcp_failover_toack_queue_timeout, state,
+ (tvref_t)dhcp_failover_state_reference,
+ (tvunref_t)dhcp_failover_state_dereference);
+ }
+
+ return 1;
+}
+
+void dhcp_failover_ack_queue_remove (dhcp_failover_state_t *state,
+ struct lease *lease)
+{
+ struct lease *lp;
+
+ if (!(lease -> flags & ON_ACK_QUEUE))
+ return;
+
+ if (state -> ack_queue_head == lease) {
+ lease_dereference (&state -> ack_queue_head, MDL);
+ if (lease -> next_pending) {
+ lease_reference (&state -> ack_queue_head,
+ lease -> next_pending, MDL);
+ lease_dereference (&lease -> next_pending, MDL);
+ } else {
+ lease_dereference (&state -> ack_queue_tail, MDL);
+ }
+ } else {
+ for (lp = state -> ack_queue_head;
+ lp && lp -> next_pending != lease;
+ lp = lp -> next_pending)
+ ;
+
+ if (!lp)
+ return;
+
+ lease_dereference (&lp -> next_pending, MDL);
+ if (lease -> next_pending) {
+ lease_reference (&lp -> next_pending,
+ lease -> next_pending, MDL);
+ lease_dereference (&lease -> next_pending, MDL);
+ } else {
+ lease_dereference (&state -> ack_queue_tail, MDL);
+ if (lp -> next_pending) {
+ log_error ("state -> ack_queue_tail");
+ abort ();
+ }
+ lease_reference (&state -> ack_queue_tail, lp, MDL);
+ }
+ }
+
+ lease -> flags &= ~ON_ACK_QUEUE;
+ /* Multiple acks on one XID is an error and may cause badness. */
+ lease->last_xid = 0;
+ /* XXX: this violates draft-failover. We can't send another
+ * update just because we forgot about an old one that hasn't
+ * been acked yet.
+ */
+ state -> cur_unacked_updates--;
+
+ /*
+ * When updating leases as a result of an ack, we defer the commit
+ * for performance reasons. When there are no more acks pending,
+ * do a commit.
+ */
+ if (state -> cur_unacked_updates == 0) {
+ commit_leases();
+ }
+}
+
+isc_result_t dhcp_failover_state_set_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_typed_data_t *value)
+{
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_failover_state)
+ return DHCP_R_INVALIDARG;
+
+ /* This list of successful returns is completely wrong, but the
+ fastest way to make dhcpctl do something vaguely sane when
+ you try to change the local state. */
+
+ if (!omapi_ds_strcmp (name, "name")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "partner-address")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "local-address")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "partner-port")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "local-port")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "max-outstanding-updates")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "mclt")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "load-balance-max-secs")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "load-balance-hba")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "partner-state")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "local-state")) {
+ unsigned long l;
+ status = omapi_get_int_value (&l, value);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ return dhcp_failover_set_state ((dhcp_failover_state_t *)h, l);
+ } else if (!omapi_ds_strcmp (name, "partner-stos")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "local-stos")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "hierarchy")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "last-packet-sent")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "last-timestamp-received")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "skew")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "max-response-delay")) {
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "cur-unacked-updates")) {
+ return ISC_R_SUCCESS;
+ }
+
+ if (h -> inner && h -> inner -> type -> set_value)
+ return (*(h -> inner -> type -> set_value))
+ (h -> inner, id, name, value);
+ return ISC_R_NOTFOUND;
+}
+
+void dhcp_failover_keepalive (void *vs)
+{
+}
+
+void dhcp_failover_reconnect (void *vs)
+{
+ dhcp_failover_state_t *state = vs;
+ isc_result_t status;
+ struct timeval tv;
+
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("dhcp_failover_reconnect");
+#endif
+ /* If we already connected the other way, let the connection
+ recovery code initiate any retry that may be required. */
+ if (state -> link_to_peer)
+ return;
+
+ status = dhcp_failover_link_initiate ((omapi_object_t *)state);
+ if (status != ISC_R_SUCCESS && status != DHCP_R_INCOMPLETE) {
+ log_info ("failover peer %s: %s", state -> name,
+ isc_result_totext (status));
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info("add_timeout +90 dhcp_failover_reconnect");
+#endif
+ tv . tv_sec = cur_time + 90;
+ tv . tv_usec = 0;
+ add_timeout(&tv, dhcp_failover_reconnect, state,
+ (tvref_t)dhcp_failover_state_reference,
+ (tvunref_t)dhcp_failover_state_dereference);
+ }
+}
+
+void dhcp_failover_startup_timeout (void *vs)
+{
+ dhcp_failover_state_t *state = vs;
+
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("dhcp_failover_startup_timeout");
+#endif
+
+ dhcp_failover_state_transition (state, "disconnect");
+}
+
+void dhcp_failover_link_startup_timeout (void *vl)
+{
+ dhcp_failover_link_t *link = vl;
+ omapi_object_t *p;
+
+ for (p = (omapi_object_t *)link; p -> inner; p = p -> inner)
+ ;
+ for (; p; p = p -> outer)
+ if (p -> type == omapi_type_connection)
+ break;
+ if (p) {
+ log_info ("failover: link startup timeout");
+ omapi_disconnect (p, 1);
+ }
+}
+
+void dhcp_failover_listener_restart (void *vs)
+{
+ dhcp_failover_state_t *state = vs;
+ isc_result_t status;
+ struct timeval tv;
+
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("dhcp_failover_listener_restart");
+#endif
+
+ status = dhcp_failover_listen ((omapi_object_t *)state);
+ if (status != ISC_R_SUCCESS) {
+ log_info ("failover peer %s: %s", state -> name,
+ isc_result_totext (status));
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("add_timeout +90 %s",
+ "dhcp_failover_listener_restart");
+#endif
+ tv . tv_sec = cur_time + 90;
+ tv . tv_usec = 0;
+ add_timeout (&tv,
+ dhcp_failover_listener_restart, state,
+ (tvref_t)dhcp_failover_state_reference,
+ (tvunref_t)dhcp_failover_state_dereference);
+ }
+}
+
+void
+dhcp_failover_auto_partner_down(void *vs)
+{
+ dhcp_failover_state_t *state = vs;
+
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info("dhcp_failover_auto_partner_down");
+#endif
+
+ dhcp_failover_set_state(state, partner_down);
+}
+
+isc_result_t dhcp_failover_state_get_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_value_t **value)
+{
+ dhcp_failover_state_t *s;
+ struct option_cache *oc;
+ struct data_string ds;
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_failover_state)
+ return DHCP_R_INVALIDARG;
+ s = (dhcp_failover_state_t *)h;
+
+ if (!omapi_ds_strcmp (name, "name")) {
+ if (s -> name)
+ return omapi_make_string_value (value,
+ name, s -> name, MDL);
+ return ISC_R_NOTFOUND;
+ } else if (!omapi_ds_strcmp (name, "partner-address")) {
+ oc = s -> partner.address;
+ getaddr:
+ memset (&ds, 0, sizeof ds);
+ if (!evaluate_option_cache (&ds, (struct packet *)0,
+ (struct lease *)0,
+ (struct client_state *)0,
+ (struct option_state *)0,
+ (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ return ISC_R_NOTFOUND;
+ }
+ status = omapi_make_const_value (value,
+ name, ds.data, ds.len, MDL);
+ /* Disgusting kludge: */
+ if (oc == s -> me.address && !s -> server_identifier.len)
+ data_string_copy (&s -> server_identifier, &ds, MDL);
+ data_string_forget (&ds, MDL);
+ return status;
+ } else if (!omapi_ds_strcmp (name, "local-address")) {
+ oc = s -> me.address;
+ goto getaddr;
+ } else if (!omapi_ds_strcmp (name, "partner-port")) {
+ return omapi_make_int_value (value, name,
+ s -> partner.port, MDL);
+ } else if (!omapi_ds_strcmp (name, "local-port")) {
+ return omapi_make_int_value (value,
+ name, s -> me.port, MDL);
+ } else if (!omapi_ds_strcmp (name, "max-outstanding-updates")) {
+ return omapi_make_uint_value (value, name,
+ s -> me.max_flying_updates,
+ MDL);
+ } else if (!omapi_ds_strcmp (name, "mclt")) {
+ return omapi_make_uint_value (value, name, s -> mclt, MDL);
+ } else if (!omapi_ds_strcmp (name, "load-balance-max-secs")) {
+ return omapi_make_int_value (value, name,
+ s -> load_balance_max_secs, MDL);
+ } else if (!omapi_ds_strcmp (name, "load-balance-hba")) {
+ if (s -> hba)
+ return omapi_make_const_value (value, name,
+ s -> hba, 32, MDL);
+ return ISC_R_NOTFOUND;
+ } else if (!omapi_ds_strcmp (name, "partner-state")) {
+ return omapi_make_uint_value (value, name,
+ s -> partner.state, MDL);
+ } else if (!omapi_ds_strcmp (name, "local-state")) {
+ return omapi_make_uint_value (value, name,
+ s -> me.state, MDL);
+ } else if (!omapi_ds_strcmp (name, "partner-stos")) {
+ return omapi_make_int_value (value, name,
+ s -> partner.stos, MDL);
+ } else if (!omapi_ds_strcmp (name, "local-stos")) {
+ return omapi_make_int_value (value, name,
+ s -> me.stos, MDL);
+ } else if (!omapi_ds_strcmp (name, "hierarchy")) {
+ return omapi_make_uint_value (value, name, s -> i_am, MDL);
+ } else if (!omapi_ds_strcmp (name, "last-packet-sent")) {
+ return omapi_make_int_value (value, name,
+ s -> last_packet_sent, MDL);
+ } else if (!omapi_ds_strcmp (name, "last-timestamp-received")) {
+ return omapi_make_int_value (value, name,
+ s -> last_timestamp_received,
+ MDL);
+ } else if (!omapi_ds_strcmp (name, "skew")) {
+ return omapi_make_int_value (value, name, s -> skew, MDL);
+ } else if (!omapi_ds_strcmp (name, "max-response-delay")) {
+ return omapi_make_uint_value (value, name,
+ s -> me.max_response_delay,
+ MDL);
+ } else if (!omapi_ds_strcmp (name, "cur-unacked-updates")) {
+ return omapi_make_int_value (value, name,
+ s -> cur_unacked_updates, MDL);
+ }
+
+ if (h -> inner && h -> inner -> type -> get_value)
+ return (*(h -> inner -> type -> get_value))
+ (h -> inner, id, name, value);
+ return ISC_R_NOTFOUND;
+}
+
+isc_result_t dhcp_failover_state_destroy (omapi_object_t *h,
+ const char *file, int line)
+{
+ dhcp_failover_state_t *s;
+
+ if (h -> type != dhcp_type_failover_state)
+ return DHCP_R_INVALIDARG;
+ s = (dhcp_failover_state_t *)h;
+
+ if (s -> link_to_peer)
+ dhcp_failover_link_dereference (&s -> link_to_peer, file, line);
+ if (s -> name) {
+ dfree (s -> name, MDL);
+ s -> name = (char *)0;
+ }
+ if (s -> partner.address)
+ option_cache_dereference (&s -> partner.address, file, line);
+ if (s -> me.address)
+ option_cache_dereference (&s -> me.address, file, line);
+ if (s -> hba) {
+ dfree (s -> hba, file, line);
+ s -> hba = (u_int8_t *)0;
+ }
+ if (s -> update_queue_head)
+ lease_dereference (&s -> update_queue_head, file, line);
+ if (s -> update_queue_tail)
+ lease_dereference (&s -> update_queue_tail, file, line);
+ if (s -> ack_queue_head)
+ lease_dereference (&s -> ack_queue_head, file, line);
+ if (s -> ack_queue_tail)
+ lease_dereference (&s -> ack_queue_tail, file, line);
+ if (s -> send_update_done)
+ lease_dereference (&s -> send_update_done, file, line);
+ if (s -> toack_queue_head)
+ failover_message_dereference (&s -> toack_queue_head,
+ file, line);
+ if (s -> toack_queue_tail)
+ failover_message_dereference (&s -> toack_queue_tail,
+ file, line);
+ return ISC_R_SUCCESS;
+}
+
+/* Write all the published values associated with the object through the
+ specified connection. */
+
+isc_result_t dhcp_failover_state_stuff (omapi_object_t *c,
+ omapi_object_t *id,
+ omapi_object_t *h)
+{
+ /* In this function c should be a (omapi_connection_object_t *) */
+
+ dhcp_failover_state_t *s;
+ isc_result_t status;
+
+ if (c -> type != omapi_type_connection)
+ return DHCP_R_INVALIDARG;
+
+ if (h -> type != dhcp_type_failover_state)
+ return DHCP_R_INVALIDARG;
+ s = (dhcp_failover_state_t *)h;
+
+ status = omapi_connection_put_name (c, "name");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_string (c, s -> name);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "partner-address");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32 (c, sizeof s -> partner.address);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_copyin (c, (u_int8_t *)&s -> partner.address,
+ sizeof s -> partner.address);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "partner-port");
+ 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, (u_int32_t)s -> partner.port);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "local-address");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32 (c, sizeof s -> me.address);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_copyin (c, (u_int8_t *)&s -> me.address,
+ sizeof s -> me.address);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "local-port");
+ 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, (u_int32_t)s -> me.port);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "max-outstanding-updates");
+ 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,
+ s -> me.max_flying_updates);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "mclt");
+ 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, s -> mclt);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "load-balance-max-secs");
+ 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, (u_int32_t)s -> load_balance_max_secs));
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+
+ if (s -> hba) {
+ status = omapi_connection_put_name (c, "load-balance-hba");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32 (c, 32);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_copyin (c, s -> hba, 32);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ }
+
+ status = omapi_connection_put_name (c, "partner-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, s -> partner.state);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "local-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, s -> me.state);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "partner-stos");
+ 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,
+ (u_int32_t)s -> partner.stos);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "local-stos");
+ 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, (u_int32_t)s -> me.stos);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "hierarchy");
+ 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, s -> i_am);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "last-packet-sent");
+ 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, (u_int32_t)s -> last_packet_sent));
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "last-timestamp-received");
+ 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, (u_int32_t)s -> last_timestamp_received));
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "skew");
+ 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, (u_int32_t)s -> skew);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "max-response-delay");
+ 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, (u_int32_t)s -> me.max_response_delay));
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "cur-unacked-updates");
+ 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, (u_int32_t)s -> cur_unacked_updates));
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ if (h -> inner && h -> inner -> type -> stuff_values)
+ return (*(h -> inner -> type -> stuff_values)) (c, id,
+ h -> inner);
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_failover_state_lookup (omapi_object_t **sp,
+ omapi_object_t *id,
+ omapi_object_t *ref)
+{
+ omapi_value_t *tv = (omapi_value_t *)0;
+ isc_result_t status;
+ dhcp_failover_state_t *s;
+
+ 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 (sp, 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 ((*sp) -> type != dhcp_type_failover_state) {
+ omapi_object_dereference (sp, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+ }
+
+ /* Look the failover state up by peer name. */
+ status = omapi_get_value_str (ref, id, "name", &tv);
+ if (status == ISC_R_SUCCESS) {
+ for (s = failover_states; s; s = s -> next) {
+ unsigned l = strlen (s -> name);
+ if (l == tv -> value -> u.buffer.len &&
+ !memcmp (s -> name,
+ tv -> value -> u.buffer.value, l))
+ break;
+ }
+ omapi_value_dereference (&tv, MDL);
+
+ /* If we already have a lease, and it's not the same one,
+ then the query was invalid. */
+ if (*sp && *sp != (omapi_object_t *)s) {
+ omapi_object_dereference (sp, MDL);
+ return DHCP_R_KEYCONFLICT;
+ } else if (!s) {
+ if (*sp)
+ omapi_object_dereference (sp, MDL);
+ return ISC_R_NOTFOUND;
+ } else if (!*sp)
+ /* XXX fix so that hash lookup itself creates
+ XXX the reference. */
+ omapi_object_reference (sp, (omapi_object_t *)s, MDL);
+ }
+
+ /* If we get to here without finding a lease, no valid key was
+ specified. */
+ if (!*sp)
+ return DHCP_R_NOKEYS;
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_failover_state_create (omapi_object_t **sp,
+ omapi_object_t *id)
+{
+ return ISC_R_NOTIMPLEMENTED;
+}
+
+isc_result_t dhcp_failover_state_remove (omapi_object_t *sp,
+ omapi_object_t *id)
+{
+ return ISC_R_NOTIMPLEMENTED;
+}
+
+int dhcp_failover_state_match (dhcp_failover_state_t *state,
+ u_int8_t *addr, unsigned addrlen)
+{
+ struct data_string ds;
+ int i;
+
+ memset (&ds, 0, sizeof ds);
+ if (evaluate_option_cache (&ds, (struct packet *)0,
+ (struct lease *)0,
+ (struct client_state *)0,
+ (struct option_state *)0,
+ (struct option_state *)0,
+ &global_scope,
+ state -> partner.address, MDL)) {
+ for (i = 0; i + addrlen - 1 < ds.len; i += addrlen) {
+ if (!memcmp (&ds.data [i],
+ addr, addrlen)) {
+ data_string_forget (&ds, MDL);
+ return 1;
+ }
+ }
+ data_string_forget (&ds, MDL);
+ }
+ return 0;
+}
+
+int
+dhcp_failover_state_match_by_name(state, name)
+ dhcp_failover_state_t *state;
+ failover_option_t *name;
+{
+ if ((strlen(state->name) == name->count) &&
+ (memcmp(state->name, name->data, name->count) == 0))
+ return 1;
+
+ return 0;
+}
+
+const char *dhcp_failover_reject_reason_print (int reason)
+{
+ static char resbuf[sizeof("Undefined-255: This reason code is not defined "
+ "in the protocol standard.")];
+
+ if ((reason > 0xff) || (reason < 0))
+ return "Reason code out of range.";
+
+ switch (reason) {
+ case FTR_ILLEGAL_IP_ADDR:
+ return "Illegal IP address (not part of any address pool).";
+
+ case FTR_FATAL_CONFLICT:
+ return "Fatal conflict exists: address in use by other client.";
+
+ case FTR_MISSING_BINDINFO:
+ return "Missing binding information.";
+
+ case FTR_TIMEMISMATCH:
+ return "Connection rejected, time mismatch too great.";
+
+ case FTR_INVALID_MCLT:
+ return "Connection rejected, invalid MCLT.";
+
+ case FTR_MISC_REJECT:
+ return "Connection rejected, unknown reason.";
+
+ case FTR_DUP_CONNECTION:
+ return "Connection rejected, duplicate connection.";
+
+ case FTR_INVALID_PARTNER:
+ return "Connection rejected, invalid failover partner.";
+
+ case FTR_TLS_UNSUPPORTED:
+ return "TLS not supported.";
+
+ case FTR_TLS_UNCONFIGURED:
+ return "TLS supported but not configured.";
+
+ case FTR_TLS_REQUIRED:
+ return "TLS required but not supported by partner.";
+
+ case FTR_DIGEST_UNSUPPORTED:
+ return "Message digest not supported.";
+
+ case FTR_DIGEST_UNCONFIGURED:
+ return "Message digest not configured.";
+
+ case FTR_VERSION_MISMATCH:
+ return "Protocol version mismatch.";
+
+ case FTR_OUTDATED_BIND_INFO:
+ return "Outdated binding information.";
+
+ case FTR_LESS_CRIT_BIND_INFO:
+ return "Less critical binding information.";
+
+ case FTR_NO_TRAFFIC:
+ return "No traffic within sufficient time.";
+
+ case FTR_HBA_CONFLICT:
+ return "Hash bucket assignment conflict.";
+
+ case FTR_IP_NOT_RESERVED:
+ return "IP not reserved on this server.";
+
+ case FTR_IP_DIGEST_FAILURE:
+ return "Message digest failed to compare.";
+
+ case FTR_IP_MISSING_DIGEST:
+ return "Missing message digest.";
+
+ case FTR_UNKNOWN:
+ return "Unknown Error.";
+
+ default:
+ sprintf(resbuf, "Undefined-%d: This reason code is not defined in the "
+ "protocol standard.", reason);
+ return resbuf;
+ }
+}
+
+const char *dhcp_failover_state_name_print (enum failover_state state)
+{
+ switch (state) {
+ default:
+ case unknown_state:
+ return "unknown-state";
+
+ case partner_down:
+ return "partner-down";
+
+ case normal:
+ return "normal";
+
+ case conflict_done:
+ return "conflict-done";
+
+ case communications_interrupted:
+ return "communications-interrupted";
+
+ case resolution_interrupted:
+ return "resolution-interrupted";
+
+ case potential_conflict:
+ return "potential-conflict";
+
+ case recover:
+ return "recover";
+
+ case recover_done:
+ return "recover-done";
+
+ case recover_wait:
+ return "recover-wait";
+
+ case shut_down:
+ return "shutdown";
+
+ case paused:
+ return "paused";
+
+ case startup:
+ return "startup";
+ }
+}
+
+const char *dhcp_failover_message_name (unsigned type)
+{
+ static char messbuf[sizeof("unknown-message-255")];
+
+ if (type > 0xff)
+ return "invalid-message";
+
+ switch (type) {
+ case FTM_POOLREQ:
+ return "pool-request";
+
+ case FTM_POOLRESP:
+ return "pool-response";
+
+ case FTM_BNDUPD:
+ return "bind-update";
+
+ case FTM_BNDACK:
+ return "bind-ack";
+
+ case FTM_CONNECT:
+ return "connect";
+
+ case FTM_CONNECTACK:
+ return "connect-ack";
+
+ case FTM_UPDREQ:
+ return "update-request";
+
+ case FTM_UPDDONE:
+ return "update-done";
+
+ case FTM_UPDREQALL:
+ return "update-request-all";
+
+ case FTM_STATE:
+ return "state";
+
+ case FTM_CONTACT:
+ return "contact";
+
+ case FTM_DISCONNECT:
+ return "disconnect";
+
+ default:
+ sprintf(messbuf, "unknown-message-%u", type);
+ return messbuf;
+ }
+}
+
+const char *dhcp_failover_option_name (unsigned type)
+{
+ static char optbuf[sizeof("unknown-option-65535")];
+
+ if (type > 0xffff)
+ return "invalid-option";
+
+ switch (type) {
+ case FTO_ADDRESSES_TRANSFERRED:
+ return "addresses-transferred";
+
+ case FTO_ASSIGNED_IP_ADDRESS:
+ return "assigned-ip-address";
+
+ case FTO_BINDING_STATUS:
+ return "binding-status";
+
+ case FTO_CLIENT_IDENTIFIER:
+ return "client-identifier";
+
+ case FTO_CHADDR:
+ return "chaddr";
+
+ case FTO_CLTT:
+ return "cltt";
+
+ case FTO_DDNS:
+ return "ddns";
+
+ case FTO_DELAYED_SERVICE:
+ return "delayed-service";
+
+ case FTO_HBA:
+ return "hba";
+
+ case FTO_IP_FLAGS:
+ return "ip-flags";
+
+ case FTO_LEASE_EXPIRY:
+ return "lease-expiry";
+
+ case FTO_MAX_UNACKED:
+ return "max-unacked";
+
+ case FTO_MCLT:
+ return "mclt";
+
+ case FTO_MESSAGE:
+ return "message";
+
+ case FTO_MESSAGE_DIGEST:
+ return "message-digest";
+
+ case FTO_POTENTIAL_EXPIRY:
+ return "potential-expiry";
+
+ case FTO_PROTOCOL_VERSION:
+ return "protocol-version";
+
+ case FTO_RECEIVE_TIMER:
+ return "receive-timer";
+
+ case FTO_REJECT_REASON:
+ return "reject-reason";
+
+ case FTO_RELATIONSHIP_NAME:
+ return "relationship-name";
+
+ case FTO_REPLY_OPTIONS:
+ return "reply-options";
+
+ case FTO_REQUEST_OPTIONS:
+ return "request-options";
+
+ case FTO_SERVER_FLAGS:
+ return "server-flags";
+
+ case FTO_SERVER_STATE:
+ return "server-state";
+
+ case FTO_STOS:
+ return "stos";
+
+ case FTO_TLS_REPLY:
+ return "tls-reply";
+
+ case FTO_TLS_REQUEST:
+ return "tls-request";
+
+ case FTO_VENDOR_CLASS:
+ return "vendor-class";
+
+ case FTO_VENDOR_OPTIONS:
+ return "vendor-options";
+
+ default:
+ sprintf(optbuf, "unknown-option-%u", type);
+ return optbuf;
+ }
+}
+
+failover_option_t *dhcp_failover_option_printf (unsigned code,
+ char *obuf,
+ unsigned *obufix,
+ unsigned obufmax,
+ const char *fmt, ...)
+{
+ va_list va;
+ char tbuf [256];
+
+ /* %Audit% Truncation causes panic. %2004.06.17,Revisit%
+ * It is unclear what the effects of truncation here are, or
+ * how that condition should be handled. It seems that this
+ * function is used for formatting messages in the failover
+ * command channel. For now the safest thing is for
+ * overflow-truncation to cause a fatal log.
+ */
+ va_start (va, fmt);
+ if (vsnprintf (tbuf, sizeof tbuf, fmt, va) >= sizeof tbuf)
+ log_fatal ("%s: vsnprintf would truncate",
+ "dhcp_failover_make_option");
+ va_end (va);
+
+ return dhcp_failover_make_option (code, obuf, obufix, obufmax,
+ strlen (tbuf), tbuf);
+}
+
+failover_option_t *dhcp_failover_make_option (unsigned code,
+ char *obuf, unsigned *obufix,
+ unsigned obufmax, ...)
+{
+ va_list va;
+ struct failover_option_info *info;
+ int i;
+ unsigned size, count;
+ unsigned val;
+ u_int8_t *iaddr;
+ unsigned ilen = 0;
+ u_int8_t *bval;
+ char *txt = NULL;
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ char tbuf [256];
+#endif
+
+ /* Note that the failover_option structure is used differently on
+ input than on output - on input, count is an element count, and
+ on output it's the number of bytes total in the option, including
+ the option code and option length. */
+ failover_option_t option, *op;
+
+
+ /* Bogus option code? */
+ if (code < 1 || code > FTO_MAX || ft_options [code].type == FT_UNDEF) {
+ return &null_failover_option;
+ }
+ info = &ft_options [code];
+
+ va_start (va, obufmax);
+
+ /* Get the number of elements and the size of the buffer we need
+ to allocate. */
+ if (info -> type == FT_DDNS || info -> type == FT_DDNS1) {
+ count = info -> type == FT_DDNS ? 1 : 2;
+ size = va_arg (va, int) + count;
+ } else {
+ /* Find out how many items in this list. */
+ if (info -> num_present)
+ count = info -> num_present;
+ else
+ count = va_arg (va, int);
+
+ /* Figure out size. */
+ switch (info -> type) {
+ case FT_UINT8:
+ case FT_BYTES:
+ case FT_DIGEST:
+ size = count;
+ break;
+
+ case FT_TEXT_OR_BYTES:
+ case FT_TEXT:
+ txt = va_arg (va, char *);
+ size = count;
+ break;
+
+ case FT_IPADDR:
+ ilen = va_arg (va, unsigned);
+ size = count * ilen;
+ break;
+
+ case FT_UINT32:
+ size = count * 4;
+ break;
+
+ case FT_UINT16:
+ size = count * 2;
+ break;
+
+ default:
+ /* shouldn't get here. */
+ log_fatal ("bogus type in failover_make_option: %d",
+ info -> type);
+ return &null_failover_option;
+ }
+ }
+
+ size += 4;
+
+ /* Allocate a buffer for the option. */
+ option.count = size;
+ option.data = dmalloc (option.count, MDL);
+ if (!option.data) {
+ va_end (va);
+ return &null_failover_option;
+ }
+
+ /* Put in the option code and option length. */
+ putUShort (option.data, code);
+ putUShort (&option.data [2], size - 4);
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ /* %Audit% Truncation causes panic. %2004.06.17,Revisit%
+ * It is unclear what the effects of truncation here are, or
+ * how that condition should be handled. It seems that this
+ * message may be sent over the failover command channel.
+ * For now the safest thing is for overflow-truncation to cause
+ * a fatal log.
+ */
+ if (snprintf (tbuf, sizeof tbuf, " (%s<%d>", info -> name,
+ option.count) >= sizeof tbuf)
+ log_fatal ("dhcp_failover_make_option: tbuf overflow");
+ failover_print (obuf, obufix, obufmax, tbuf);
+#endif
+
+ /* Now put in the data. */
+ switch (info -> type) {
+ case FT_UINT8:
+ for (i = 0; i < count; i++) {
+ val = va_arg (va, unsigned);
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ /* %Audit% Cannot exceed 24 bytes. %2004.06.17,Safe% */
+ sprintf (tbuf, " %d", val);
+ failover_print (obuf, obufix, obufmax, tbuf);
+#endif
+ option.data [i + 4] = val;
+ }
+ break;
+
+ case FT_IPADDR:
+ for (i = 0; i < count; i++) {
+ iaddr = va_arg (va, u_int8_t *);
+ if (ilen != 4) {
+ dfree (option.data, MDL);
+ log_error ("IP addrlen=%d, should be 4.",
+ ilen);
+ va_end (va);
+ return &null_failover_option;
+ }
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ /*%Audit% Cannot exceed 17 bytes. %2004.06.17,Safe%*/
+ sprintf (tbuf, " %u.%u.%u.%u",
+ iaddr [0], iaddr [1], iaddr [2], iaddr [3]);
+ failover_print (obuf, obufix, obufmax, tbuf);
+#endif
+ memcpy (&option.data [4 + i * ilen], iaddr, ilen);
+ }
+ break;
+
+ case FT_UINT32:
+ for (i = 0; i < count; i++) {
+ val = va_arg (va, unsigned);
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ /*%Audit% Cannot exceed 24 bytes. %2004.06.17,Safe%*/
+ sprintf (tbuf, " %d", val);
+ failover_print (obuf, obufix, obufmax, tbuf);
+#endif
+ putULong (&option.data [4 + i * 4], val);
+ }
+ break;
+
+ case FT_BYTES:
+ case FT_DIGEST:
+ bval = va_arg (va, u_int8_t *);
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ for (i = 0; i < count; i++) {
+ /* 23 bytes plus nul, safe. */
+ sprintf (tbuf, " %d", bval [i]);
+ failover_print (obuf, obufix, obufmax, tbuf);
+ }
+#endif
+ memcpy (&option.data [4], bval, count);
+ break;
+
+ /* On output, TEXT_OR_BYTES is _always_ text, and always NUL
+ terminated. Note that the caller should be careful not
+ to provide a format and data that amount to more than 256
+ bytes of data, since it will cause a fatal error. */
+ case FT_TEXT_OR_BYTES:
+ case FT_TEXT:
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ /* %Audit% Truncation causes panic. %2004.06.17,Revisit%
+ * It is unclear what the effects of truncation here are, or
+ * how that condition should be handled. It seems that this
+ * function is used for formatting messages in the failover
+ * command channel. For now the safest thing is for
+ * overflow-truncation to cause a fatal log.
+ */
+ if (snprintf (tbuf, sizeof tbuf, "\"%s\"", txt) >= sizeof tbuf)
+ log_fatal ("dhcp_failover_make_option: tbuf overflow");
+ failover_print (obuf, obufix, obufmax, tbuf);
+#endif
+ memcpy (&option.data [4], txt, count);
+ break;
+
+ case FT_DDNS:
+ case FT_DDNS1:
+ option.data [4] = va_arg (va, unsigned);
+ if (count == 2)
+ option.data [5] = va_arg (va, unsigned);
+ bval = va_arg (va, u_int8_t *);
+ memcpy (&option.data [4 + count], bval, size - count - 4);
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ for (i = 4; i < size; i++) {
+ /*%Audit% Cannot exceed 24 bytes. %2004.06.17,Safe%*/
+ sprintf (tbuf, " %d", option.data [i]);
+ failover_print (obuf, obufix, obufmax, tbuf);
+ }
+#endif
+ break;
+
+ case FT_UINT16:
+ for (i = 0; i < count; i++) {
+ val = va_arg (va, u_int32_t);
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ /*%Audit% Cannot exceed 24 bytes. %2004.06.17,Safe%*/
+ sprintf (tbuf, " %d", val);
+ failover_print (obuf, obufix, obufmax, tbuf);
+#endif
+ putUShort (&option.data [4 + i * 2], val);
+ }
+ break;
+
+ case FT_UNDEF:
+ default:
+ break;
+ }
+
+#if defined DEBUG_FAILOVER_MESSAGES
+ failover_print (obuf, obufix, obufmax, ")");
+#endif
+ va_end (va);
+
+ /* Now allocate a place to store what we just set up. */
+ op = dmalloc (sizeof (failover_option_t), MDL);
+ if (!op) {
+ dfree (option.data, MDL);
+ return &null_failover_option;
+ }
+
+ *op = option;
+ return op;
+}
+
+/* Send a failover message header. */
+
+isc_result_t dhcp_failover_put_message (dhcp_failover_link_t *link,
+ omapi_object_t *connection,
+ int msg_type, u_int32_t xid, ...)
+{
+ unsigned size = 0;
+ int bad_option = 0;
+ int opix = 0;
+ va_list list;
+ failover_option_t *option;
+ unsigned char *opbuf;
+ isc_result_t status = ISC_R_SUCCESS;
+ unsigned char cbuf;
+ struct timeval tv;
+
+ /* Run through the argument list once to compute the length of
+ the option portion of the message. */
+ va_start (list, xid);
+ while ((option = va_arg (list, failover_option_t *))) {
+ if (option != &skip_failover_option)
+ size += option -> count;
+ if (option == &null_failover_option)
+ bad_option = 1;
+ }
+ va_end (list);
+
+ /* Allocate an option buffer, unless we got an error. */
+ if (!bad_option && size) {
+ opbuf = dmalloc (size, MDL);
+ if (!opbuf)
+ status = ISC_R_NOMEMORY;
+ } else
+ opbuf = (unsigned char *)0;
+
+ va_start (list, xid);
+ while ((option = va_arg (list, failover_option_t *))) {
+ if (option == &skip_failover_option)
+ continue;
+ if (!bad_option && opbuf)
+ memcpy (&opbuf [opix],
+ option -> data, option -> count);
+ if (option != &null_failover_option &&
+ option != &skip_failover_option) {
+ opix += option -> count;
+ dfree (option -> data, MDL);
+ dfree (option, MDL);
+ }
+ }
+ va_end(list);
+
+ if (bad_option)
+ return DHCP_R_INVALIDARG;
+
+ /* Now send the message header. */
+
+ /* Message length. */
+ status = omapi_connection_put_uint16 (connection, size + 12);
+ if (status != ISC_R_SUCCESS)
+ goto err;
+
+ /* Message type. */
+ cbuf = msg_type;
+ status = omapi_connection_copyin (connection, &cbuf, 1);
+ if (status != ISC_R_SUCCESS)
+ goto err;
+
+ /* Payload offset. */
+ cbuf = 12;
+ status = omapi_connection_copyin (connection, &cbuf, 1);
+ if (status != ISC_R_SUCCESS)
+ goto err;
+
+ /* Current time. */
+ status = omapi_connection_put_uint32 (connection, (u_int32_t)cur_time);
+ if (status != ISC_R_SUCCESS)
+ goto err;
+
+ /* Transaction ID. */
+ status = omapi_connection_put_uint32(connection, xid);
+ if (status != ISC_R_SUCCESS)
+ goto err;
+
+ /* Payload. */
+ if (opbuf) {
+ status = omapi_connection_copyin (connection, opbuf, size);
+ if (status != ISC_R_SUCCESS)
+ goto err;
+ dfree (opbuf, MDL);
+ }
+ if (link -> state_object &&
+ link -> state_object -> link_to_peer == link) {
+#if defined (DEBUG_FAILOVER_CONTACT_TIMING)
+ log_info ("add_timeout +%d %s",
+ (int)(link -> state_object ->
+ partner.max_response_delay) / 3,
+ "dhcp_failover_send_contact");
+#endif
+ tv . tv_sec = cur_time +
+ (int)(link -> state_object ->
+ partner.max_response_delay) / 3;
+ tv . tv_usec = 0;
+ add_timeout (&tv,
+ dhcp_failover_send_contact, link -> state_object,
+ (tvref_t)dhcp_failover_state_reference,
+ (tvunref_t)dhcp_failover_state_dereference);
+ }
+ return status;
+
+ err:
+ if (opbuf)
+ dfree (opbuf, MDL);
+ log_info ("dhcp_failover_put_message: something went wrong.");
+ omapi_disconnect (connection, 1);
+ return status;
+}
+
+void dhcp_failover_timeout (void *vstate)
+{
+ dhcp_failover_state_t *state = vstate;
+ dhcp_failover_link_t *link;
+
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("dhcp_failover_timeout");
+#endif
+
+ if (!state || state -> type != dhcp_type_failover_state)
+ return;
+ link = state -> link_to_peer;
+ if (!link ||
+ !link -> outer ||
+ link -> outer -> type != omapi_type_connection)
+ return;
+
+ log_error ("timeout waiting for failover peer %s", state -> name);
+
+ /* If we haven't gotten a timely response, blow away the connection.
+ This will cause the state to change automatically. */
+ omapi_disconnect (link -> outer, 1);
+}
+
+void dhcp_failover_send_contact (void *vstate)
+{
+ dhcp_failover_state_t *state = vstate;
+ dhcp_failover_link_t *link;
+ isc_result_t status;
+
+#if defined(DEBUG_FAILOVER_MESSAGES) && \
+ defined(DEBUG_FAILOVER_CONTACT_MESSAGES)
+ char obuf [64];
+ unsigned obufix = 0;
+
+ failover_print(obuf, &obufix, sizeof(obuf), "(contact");
+#endif
+
+#if defined (DEBUG_FAILOVER_CONTACT_TIMING)
+ log_info ("dhcp_failover_send_contact");
+#endif
+
+ if (!state || state -> type != dhcp_type_failover_state)
+ return;
+ link = state -> link_to_peer;
+ if (!link ||
+ !link -> outer ||
+ link -> outer -> type != omapi_type_connection)
+ return;
+
+ status = (dhcp_failover_put_message
+ (link, link -> outer,
+ FTM_CONTACT, link->xid++,
+ (failover_option_t *)0));
+
+#if defined(DEBUG_FAILOVER_MESSAGES) && \
+ defined(DEBUG_FAILOVER_CONTACT_MESSAGES)
+ if (status != ISC_R_SUCCESS)
+ failover_print(obuf, &obufix, sizeof(obuf), " (failed)");
+ failover_print(obuf, &obufix, sizeof(obuf), ")");
+ if (obufix) {
+ log_debug ("%s", obuf);
+ }
+#else
+ IGNORE_UNUSED(status);
+#endif
+ return;
+}
+
+isc_result_t dhcp_failover_send_state (dhcp_failover_state_t *state)
+{
+ dhcp_failover_link_t *link;
+ isc_result_t status;
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ char obuf [64];
+ unsigned obufix = 0;
+
+# define FMA obuf, &obufix, sizeof obuf
+ failover_print (FMA, "(state");
+#else
+# define FMA (char *)0, (unsigned *)0, 0
+#endif
+
+ if (!state || state -> type != dhcp_type_failover_state)
+ return DHCP_R_INVALIDARG;
+ link = state -> link_to_peer;
+ if (!link ||
+ !link -> outer ||
+ link -> outer -> type != omapi_type_connection)
+ return DHCP_R_INVALIDARG;
+
+ status = (dhcp_failover_put_message
+ (link, link -> outer,
+ FTM_STATE, link->xid++,
+ dhcp_failover_make_option (FTO_SERVER_STATE, FMA,
+ (state -> me.state == startup
+ ? state -> saved_state
+ : state -> me.state)),
+ dhcp_failover_make_option
+ (FTO_SERVER_FLAGS, FMA,
+ (state -> service_state == service_startup
+ ? FTF_SERVER_STARTUP : 0)),
+ dhcp_failover_make_option (FTO_STOS, FMA, state -> me.stos),
+ (failover_option_t *)0));
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ if (status != ISC_R_SUCCESS)
+ failover_print (FMA, " (failed)");
+ failover_print (FMA, ")");
+ if (obufix) {
+ log_debug ("%s", obuf);
+ }
+#else
+ IGNORE_UNUSED(status);
+#endif
+ return ISC_R_SUCCESS;
+}
+
+/* Send a connect message. */
+
+isc_result_t dhcp_failover_send_connect (omapi_object_t *l)
+{
+ dhcp_failover_link_t *link;
+ dhcp_failover_state_t *state;
+ isc_result_t status;
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ char obuf [64];
+ unsigned obufix = 0;
+
+# define FMA obuf, &obufix, sizeof obuf
+ failover_print (FMA, "(connect");
+#else
+# define FMA (char *)0, (unsigned *)0, 0
+#endif
+
+ if (!l || l -> type != dhcp_type_failover_link)
+ return DHCP_R_INVALIDARG;
+ link = (dhcp_failover_link_t *)l;
+ state = link -> state_object;
+ if (!l -> outer || l -> outer -> type != omapi_type_connection)
+ return DHCP_R_INVALIDARG;
+
+ status =
+ (dhcp_failover_put_message
+ (link, l -> outer,
+ FTM_CONNECT, link->xid++,
+ dhcp_failover_make_option(FTO_RELATIONSHIP_NAME, FMA,
+ strlen(state->name), state->name),
+ dhcp_failover_make_option (FTO_MAX_UNACKED, FMA,
+ state -> me.max_flying_updates),
+ dhcp_failover_make_option (FTO_RECEIVE_TIMER, FMA,
+ state -> me.max_response_delay),
+ dhcp_failover_option_printf(FTO_VENDOR_CLASS, FMA,
+ "isc-%s", PACKAGE_VERSION),
+ dhcp_failover_make_option (FTO_PROTOCOL_VERSION, FMA,
+ DHCP_FAILOVER_VERSION),
+ dhcp_failover_make_option (FTO_TLS_REQUEST, FMA,
+ 0, 0),
+ dhcp_failover_make_option (FTO_MCLT, FMA,
+ state -> mclt),
+ (state -> hba
+ ? dhcp_failover_make_option (FTO_HBA, FMA, 32, state -> hba)
+ : &skip_failover_option),
+ (failover_option_t *)0));
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ if (status != ISC_R_SUCCESS)
+ failover_print (FMA, " (failed)");
+ failover_print (FMA, ")");
+ if (obufix) {
+ log_debug ("%s", obuf);
+ }
+#endif
+ return status;
+}
+
+isc_result_t dhcp_failover_send_connectack (omapi_object_t *l,
+ dhcp_failover_state_t *state,
+ int reason, const char *errmsg)
+{
+ dhcp_failover_link_t *link;
+ isc_result_t status;
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ char obuf [64];
+ unsigned obufix = 0;
+
+# define FMA obuf, &obufix, sizeof obuf
+ failover_print (FMA, "(connectack");
+#else
+# define FMA (char *)0, (unsigned *)0, 0
+#endif
+
+ if (!l || l -> type != dhcp_type_failover_link)
+ return DHCP_R_INVALIDARG;
+ link = (dhcp_failover_link_t *)l;
+ if (!l -> outer || l -> outer -> type != omapi_type_connection)
+ return DHCP_R_INVALIDARG;
+
+ status =
+ (dhcp_failover_put_message
+ (link, l -> outer,
+ FTM_CONNECTACK, link->imsg->xid,
+ state
+ ? dhcp_failover_make_option(FTO_RELATIONSHIP_NAME, FMA,
+ strlen(state->name), state->name)
+ : (link->imsg->options_present & FTB_RELATIONSHIP_NAME)
+ ? dhcp_failover_make_option(FTO_RELATIONSHIP_NAME, FMA,
+ link->imsg->relationship_name.count,
+ link->imsg->relationship_name.data)
+ : &skip_failover_option,
+ state
+ ? dhcp_failover_make_option (FTO_MAX_UNACKED, FMA,
+ state -> me.max_flying_updates)
+ : &skip_failover_option,
+ state
+ ? dhcp_failover_make_option (FTO_RECEIVE_TIMER, FMA,
+ state -> me.max_response_delay)
+ : &skip_failover_option,
+ dhcp_failover_option_printf(FTO_VENDOR_CLASS, FMA,
+ "isc-%s", PACKAGE_VERSION),
+ dhcp_failover_make_option (FTO_PROTOCOL_VERSION, FMA,
+ DHCP_FAILOVER_VERSION),
+ (link->imsg->options_present & FTB_TLS_REQUEST)
+ ? dhcp_failover_make_option(FTO_TLS_REPLY, FMA,
+ 0, 0)
+ : &skip_failover_option,
+ reason
+ ? dhcp_failover_make_option (FTO_REJECT_REASON,
+ FMA, reason)
+ : &skip_failover_option,
+ (reason && errmsg)
+ ? dhcp_failover_make_option (FTO_MESSAGE, FMA,
+ strlen (errmsg), errmsg)
+ : &skip_failover_option,
+ (failover_option_t *)0));
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ if (status != ISC_R_SUCCESS)
+ failover_print (FMA, " (failed)");
+ failover_print (FMA, ")");
+ if (obufix) {
+ log_debug ("%s", obuf);
+ }
+#endif
+ return status;
+}
+
+isc_result_t dhcp_failover_send_disconnect (omapi_object_t *l,
+ int reason,
+ const char *message)
+{
+ dhcp_failover_link_t *link;
+ isc_result_t status;
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ char obuf [64];
+ unsigned obufix = 0;
+
+# define FMA obuf, &obufix, sizeof obuf
+ failover_print (FMA, "(disconnect");
+#else
+# define FMA (char *)0, (unsigned *)0, 0
+#endif
+
+ if (!l || l -> type != dhcp_type_failover_link)
+ return DHCP_R_INVALIDARG;
+ link = (dhcp_failover_link_t *)l;
+ if (!l -> outer || l -> outer -> type != omapi_type_connection)
+ return DHCP_R_INVALIDARG;
+
+ if (!message && reason)
+ message = dhcp_failover_reject_reason_print (reason);
+
+ status = (dhcp_failover_put_message
+ (link, l -> outer,
+ FTM_DISCONNECT, link->xid++,
+ dhcp_failover_make_option (FTO_REJECT_REASON,
+ FMA, reason),
+ (message
+ ? dhcp_failover_make_option (FTO_MESSAGE, FMA,
+ strlen (message), message)
+ : &skip_failover_option),
+ (failover_option_t *)0));
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ if (status != ISC_R_SUCCESS)
+ failover_print (FMA, " (failed)");
+ failover_print (FMA, ")");
+ if (obufix) {
+ log_debug ("%s", obuf);
+ }
+#endif
+ return status;
+}
+
+/* Send a Bind Update message. */
+
+isc_result_t dhcp_failover_send_bind_update (dhcp_failover_state_t *state,
+ struct lease *lease)
+{
+ dhcp_failover_link_t *link;
+ isc_result_t status;
+ int flags = 0;
+ binding_state_t transmit_state;
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ char obuf [64];
+ unsigned obufix = 0;
+
+# define FMA obuf, &obufix, sizeof obuf
+ failover_print (FMA, "(bndupd");
+#else
+# define FMA (char *)0, (unsigned *)0, 0
+#endif
+
+ if (!state -> link_to_peer ||
+ state -> link_to_peer -> type != dhcp_type_failover_link)
+ return DHCP_R_INVALIDARG;
+ link = (dhcp_failover_link_t *)state -> link_to_peer;
+
+ if (!link -> outer || link -> outer -> type != omapi_type_connection)
+ return DHCP_R_INVALIDARG;
+
+ transmit_state = lease->desired_binding_state;
+ if (lease->flags & RESERVED_LEASE) {
+ /* If we are listing an allocable (not yet ACTIVE etc) lease
+ * as reserved, toggle to the peer's 'free state', per the
+ * draft. This gives the peer permission to alloc it to the
+ * chaddr/uid-named client.
+ */
+ if ((state->i_am == primary) && (transmit_state == FTS_FREE))
+ transmit_state = FTS_BACKUP;
+ else if ((state->i_am == secondary) &&
+ (transmit_state == FTS_BACKUP))
+ transmit_state = FTS_FREE;
+
+ flags |= FTF_IP_FLAG_RESERVE;
+ }
+ if (lease->flags & BOOTP_LEASE)
+ flags |= FTF_IP_FLAG_BOOTP;
+
+ /* last_xid == 0 is illegal, seek past zero if we hit it. */
+ if (link->xid == 0)
+ link->xid = 1;
+
+ lease->last_xid = link->xid++;
+
+ /*
+ * Our very next action is to transmit a binding update relating to
+ * this lease over the wire, and although there is a BNDACK, there is
+ * no BNDACKACK or BNDACKACKACK...the basic issue as we send a BNDUPD,
+ * we may not receive a BNDACK. This non-reception does not imply the
+ * peer did not receive and process the BNDUPD. So at this point, we
+ * must divest any state that would be dangerous to retain under the
+ * impression the peer has been updated. Normally state changes like
+ * this are processed in supersede_lease(), but in this case we need a
+ * very late binding.
+ *
+ * In failover rules, a server is permitted to work forward in certain
+ * directions from a given lease's state; active leases may be
+ * extended, so forth. There is an 'optimization' in the failover
+ * draft that permits a server to 'rewind' any work they have not
+ * informed the peer. Since we can't know if the peer received our
+ * update but was unable to acknowledge it, we make this change on
+ * transmit rather than upon receiving the acknowledgement.
+ *
+ * XXX: Frequent lease commits are undesirable. This should hopefully
+ * only trigger when a server is sending a lease /state change/, and
+ * not merely an update such as with a renewal.
+ */
+ if (lease->rewind_binding_state != lease->binding_state) {
+ lease->rewind_binding_state = lease->binding_state;
+
+ write_lease(lease);
+ commit_leases();
+ }
+
+ /* Send the update. */
+ status = (dhcp_failover_put_message
+ (link, link -> outer,
+ FTM_BNDUPD, lease->last_xid,
+ dhcp_failover_make_option (FTO_ASSIGNED_IP_ADDRESS, FMA,
+ lease -> ip_addr.len,
+ lease -> ip_addr.iabuf),
+ dhcp_failover_make_option (FTO_BINDING_STATUS, FMA,
+ lease -> desired_binding_state),
+ lease -> uid_len
+ ? dhcp_failover_make_option (FTO_CLIENT_IDENTIFIER, FMA,
+ lease -> uid_len,
+ lease -> uid)
+ : &skip_failover_option,
+ lease -> hardware_addr.hlen
+ ? dhcp_failover_make_option (FTO_CHADDR, FMA,
+ lease -> hardware_addr.hlen,
+ lease -> hardware_addr.hbuf)
+ : &skip_failover_option,
+ dhcp_failover_make_option (FTO_LEASE_EXPIRY, FMA,
+ lease -> ends),
+ dhcp_failover_make_option (FTO_POTENTIAL_EXPIRY, FMA,
+ lease -> tstp),
+ dhcp_failover_make_option (FTO_STOS, FMA,
+ lease -> starts),
+ (lease->cltt != 0) ?
+ dhcp_failover_make_option(FTO_CLTT, FMA, lease->cltt) :
+ &skip_failover_option, /* No CLTT */
+ flags ? dhcp_failover_make_option(FTO_IP_FLAGS, FMA,
+ flags) :
+ &skip_failover_option, /* No IP_FLAGS */
+ &skip_failover_option, /* XXX DDNS */
+ &skip_failover_option, /* XXX request options */
+ &skip_failover_option, /* XXX reply options */
+ (failover_option_t *)0));
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ if (status != ISC_R_SUCCESS)
+ failover_print (FMA, " (failed)");
+ failover_print (FMA, ")");
+ if (obufix) {
+ log_debug ("%s", obuf);
+ }
+#endif
+ return status;
+}
+
+/* Send a Bind ACK message. */
+
+isc_result_t dhcp_failover_send_bind_ack (dhcp_failover_state_t *state,
+ failover_message_t *msg,
+ int reason, const char *message)
+{
+ dhcp_failover_link_t *link;
+ isc_result_t status;
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ char obuf [64];
+ unsigned obufix = 0;
+
+# define FMA obuf, &obufix, sizeof obuf
+ failover_print (FMA, "(bndack");
+#else
+# define FMA (char *)0, (unsigned *)0, 0
+#endif
+
+ if (!state -> link_to_peer ||
+ state -> link_to_peer -> type != dhcp_type_failover_link)
+ return DHCP_R_INVALIDARG;
+ link = (dhcp_failover_link_t *)state -> link_to_peer;
+
+ if (!link -> outer || link -> outer -> type != omapi_type_connection)
+ return DHCP_R_INVALIDARG;
+
+ if (!message && reason)
+ message = dhcp_failover_reject_reason_print (reason);
+
+ /* Send the update. */
+ status = (dhcp_failover_put_message
+ (link, link -> outer,
+ FTM_BNDACK, msg->xid,
+ dhcp_failover_make_option (FTO_ASSIGNED_IP_ADDRESS, FMA,
+ sizeof msg -> assigned_addr,
+ &msg -> assigned_addr),
+#ifdef DO_BNDACK_SHOULD_NOT
+ dhcp_failover_make_option (FTO_BINDING_STATUS, FMA,
+ msg -> binding_status),
+ (msg -> options_present & FTB_CLIENT_IDENTIFIER)
+ ? dhcp_failover_make_option (FTO_CLIENT_IDENTIFIER, FMA,
+ msg -> client_identifier.count,
+ msg -> client_identifier.data)
+ : &skip_failover_option,
+ (msg -> options_present & FTB_CHADDR)
+ ? dhcp_failover_make_option (FTO_CHADDR, FMA,
+ msg -> chaddr.count,
+ msg -> chaddr.data)
+ : &skip_failover_option,
+ dhcp_failover_make_option (FTO_LEASE_EXPIRY, FMA,
+ msg -> expiry),
+ dhcp_failover_make_option (FTO_POTENTIAL_EXPIRY, FMA,
+ msg -> potential_expiry),
+ dhcp_failover_make_option (FTO_STOS, FMA,
+ msg -> stos),
+ (msg->options_present & FTB_CLTT) ?
+ dhcp_failover_make_option(FTO_CLTT, FMA, msg->cltt) :
+ &skip_failover_option, /* No CLTT in the msg to ack. */
+ ((msg->options_present & FTB_IP_FLAGS) && msg->ip_flags) ?
+ dhcp_failover_make_option(FTO_IP_FLAGS, FMA,
+ msg->ip_flags)
+ : &skip_failover_option,
+#endif /* DO_BNDACK_SHOULD_NOT */
+ reason
+ ? dhcp_failover_make_option(FTO_REJECT_REASON, FMA, reason)
+ : &skip_failover_option,
+ (reason && message)
+ ? dhcp_failover_make_option (FTO_MESSAGE, FMA,
+ strlen (message), message)
+ : &skip_failover_option,
+#ifdef DO_BNDACK_SHOULD_NOT
+ &skip_failover_option, /* XXX DDNS */
+ &skip_failover_option, /* XXX request options */
+ &skip_failover_option, /* XXX reply options */
+#endif /* DO_BNDACK_SHOULD_NOT */
+ (failover_option_t *)0));
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ if (status != ISC_R_SUCCESS)
+ failover_print (FMA, " (failed)");
+ failover_print (FMA, ")");
+ if (obufix) {
+ log_debug ("%s", obuf);
+ }
+#endif
+ return status;
+}
+
+isc_result_t dhcp_failover_send_poolreq (dhcp_failover_state_t *state)
+{
+ dhcp_failover_link_t *link;
+ isc_result_t status;
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ char obuf [64];
+ unsigned obufix = 0;
+
+# define FMA obuf, &obufix, sizeof obuf
+ failover_print (FMA, "(poolreq");
+#else
+# define FMA (char *)0, (unsigned *)0, 0
+#endif
+
+ if (!state -> link_to_peer ||
+ state -> link_to_peer -> type != dhcp_type_failover_link)
+ return DHCP_R_INVALIDARG;
+ link = (dhcp_failover_link_t *)state -> link_to_peer;
+
+ if (!link -> outer || link -> outer -> type != omapi_type_connection)
+ return DHCP_R_INVALIDARG;
+
+ status = (dhcp_failover_put_message
+ (link, link -> outer,
+ FTM_POOLREQ, link->xid++,
+ (failover_option_t *)0));
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ if (status != ISC_R_SUCCESS)
+ failover_print (FMA, " (failed)");
+ failover_print (FMA, ")");
+ if (obufix) {
+ log_debug ("%s", obuf);
+ }
+#endif
+ return status;
+}
+
+isc_result_t dhcp_failover_send_poolresp (dhcp_failover_state_t *state,
+ int leases)
+{
+ dhcp_failover_link_t *link;
+ isc_result_t status;
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ char obuf [64];
+ unsigned obufix = 0;
+
+# define FMA obuf, &obufix, sizeof obuf
+ failover_print (FMA, "(poolresp");
+#else
+# define FMA (char *)0, (unsigned *)0, 0
+#endif
+
+ if (!state -> link_to_peer ||
+ state -> link_to_peer -> type != dhcp_type_failover_link)
+ return DHCP_R_INVALIDARG;
+ link = (dhcp_failover_link_t *)state -> link_to_peer;
+
+ if (!link -> outer || link -> outer -> type != omapi_type_connection)
+ return DHCP_R_INVALIDARG;
+
+ status = (dhcp_failover_put_message
+ (link, link -> outer,
+ FTM_POOLRESP, link->imsg->xid,
+ dhcp_failover_make_option (FTO_ADDRESSES_TRANSFERRED, FMA,
+ leases),
+ (failover_option_t *)0));
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ if (status != ISC_R_SUCCESS)
+ failover_print (FMA, " (failed)");
+ failover_print (FMA, ")");
+ if (obufix) {
+ log_debug ("%s", obuf);
+ }
+#endif
+ return status;
+}
+
+isc_result_t dhcp_failover_send_update_request (dhcp_failover_state_t *state)
+{
+ dhcp_failover_link_t *link;
+ isc_result_t status;
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ char obuf [64];
+ unsigned obufix = 0;
+
+# define FMA obuf, &obufix, sizeof obuf
+ failover_print (FMA, "(updreq");
+#else
+# define FMA (char *)0, (unsigned *)0, 0
+#endif
+
+ if (!state->link_to_peer ||
+ state->link_to_peer->type != dhcp_type_failover_link)
+ return (DHCP_R_INVALIDARG);
+ link = (dhcp_failover_link_t *)state->link_to_peer;
+
+ if (!link->outer || link->outer->type != omapi_type_connection)
+ return (DHCP_R_INVALIDARG);
+
+ /* We allow an update to be restarted in case we requested an update
+ * and were interrupted by something. If we had an ALL going we need
+ * to restart that. Otherwise we simply continue with the request */
+ if (state->curUPD == FTM_UPDREQALL) {
+ return (dhcp_failover_send_update_request_all(state));
+ }
+
+ status = (dhcp_failover_put_message(link, link->outer, FTM_UPDREQ,
+ link->xid++, NULL));
+
+ state->curUPD = FTM_UPDREQ;
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ if (status != ISC_R_SUCCESS)
+ failover_print(FMA, " (failed)");
+ failover_print(FMA, ")");
+ if (obufix) {
+ log_debug("%s", obuf);
+ }
+#endif
+
+ if (status == ISC_R_SUCCESS) {
+ log_info("Sent update request message to %s", state->name);
+ } else {
+ log_error("Failed to send update request all message to %s: %s",
+ state->name, isc_result_totext(status));
+ }
+ return (status);
+}
+
+isc_result_t dhcp_failover_send_update_request_all (dhcp_failover_state_t
+ *state)
+{
+ dhcp_failover_link_t *link;
+ isc_result_t status;
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ char obuf [64];
+ unsigned obufix = 0;
+
+# define FMA obuf, &obufix, sizeof obuf
+ failover_print (FMA, "(updreqall");
+#else
+# define FMA (char *)0, (unsigned *)0, 0
+#endif
+
+ if (!state->link_to_peer ||
+ state->link_to_peer->type != dhcp_type_failover_link)
+ return (DHCP_R_INVALIDARG);
+ link = (dhcp_failover_link_t *)state->link_to_peer;
+
+ if (!link->outer || link->outer->type != omapi_type_connection)
+ return (DHCP_R_INVALIDARG);
+
+ /* We allow an update to be restarted in case we requested an update
+ * and were interrupted by something.
+ */
+
+ status = (dhcp_failover_put_message(link, link->outer, FTM_UPDREQALL,
+ link->xid++, NULL));
+
+ state->curUPD = FTM_UPDREQALL;
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ if (status != ISC_R_SUCCESS)
+ failover_print(FMA, " (failed)");
+ failover_print(FMA, ")");
+ if (obufix) {
+ log_debug("%s", obuf);
+ }
+#endif
+
+ if (status == ISC_R_SUCCESS) {
+ log_info("Sent update request all message to %s", state->name);
+ } else {
+ log_error("Failed to send update request all message to %s: %s",
+ state->name, isc_result_totext(status));
+ }
+ return (status);
+}
+
+isc_result_t dhcp_failover_send_update_done (dhcp_failover_state_t *state)
+{
+ dhcp_failover_link_t *link;
+ isc_result_t status;
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ char obuf [64];
+ unsigned obufix = 0;
+
+# define FMA obuf, &obufix, sizeof obuf
+ failover_print (FMA, "(upddone");
+#else
+# define FMA (char *)0, (unsigned *)0, 0
+#endif
+
+ if (!state -> link_to_peer ||
+ state -> link_to_peer -> type != dhcp_type_failover_link)
+ return DHCP_R_INVALIDARG;
+ link = (dhcp_failover_link_t *)state -> link_to_peer;
+
+ if (!link -> outer || link -> outer -> type != omapi_type_connection)
+ return DHCP_R_INVALIDARG;
+
+ status = (dhcp_failover_put_message
+ (link, link -> outer,
+ FTM_UPDDONE, state->updxid,
+ (failover_option_t *)0));
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+ if (status != ISC_R_SUCCESS)
+ failover_print (FMA, " (failed)");
+ failover_print (FMA, ")");
+ if (obufix) {
+ log_debug ("%s", obuf);
+ }
+#endif
+
+ log_info ("Sent update done message to %s", state -> name);
+
+ state->updxid--; /* Paranoia, just so it mismatches. */
+
+ /* There may be uncommitted leases at this point (since
+ dhcp_failover_process_bind_ack() doesn't commit leases);
+ commit the lease file. */
+ commit_leases();
+
+ return status;
+}
+
+/*
+ * failover_lease_is_better() compares the binding update in 'msg' with
+ * the current lease in 'lease'. If the determination is that the binding
+ * update shouldn't be allowed to update/crush more critical binding info
+ * on the lease, the lease is preferred. A value of true is returned if the
+ * local lease is preferred, or false if the remote binding update is
+ * preferred.
+ *
+ * For now this function is hopefully simplistic and trivial. It may be that
+ * a more detailed system of preferences is required, so this is something we
+ * should monitor as we gain experience with these dueling events.
+ */
+static isc_boolean_t
+failover_lease_is_better(dhcp_failover_state_t *state, struct lease *lease,
+ failover_message_t *msg)
+{
+ binding_state_t local_state;
+ TIME msg_cltt;
+
+ if (lease->binding_state != lease->desired_binding_state)
+ local_state = lease->desired_binding_state;
+ else
+ local_state = lease->binding_state;
+
+ if ((msg->options_present & FTB_CLTT) != 0)
+ msg_cltt = msg->cltt;
+ else
+ msg_cltt = 0;
+
+ switch(local_state) {
+ case FTS_ACTIVE:
+ if (msg->binding_status == FTS_ACTIVE) {
+ if (msg_cltt < lease->cltt)
+ return ISC_TRUE;
+ else if (msg_cltt > lease->cltt)
+ return ISC_FALSE;
+ else if (state->i_am == primary)
+ return ISC_TRUE;
+ else
+ return ISC_FALSE;
+ } else if (msg->binding_status == FTS_EXPIRED) {
+ return ISC_FALSE;
+ }
+ /* FALL THROUGH */
+
+ case FTS_FREE:
+ case FTS_BACKUP:
+ case FTS_EXPIRED:
+ case FTS_RELEASED:
+ case FTS_ABANDONED:
+ case FTS_RESET:
+ if (msg->binding_status == FTS_ACTIVE)
+ return ISC_FALSE;
+ else if (state->i_am == primary)
+ return ISC_TRUE;
+ else
+ return ISC_FALSE;
+ /* FALL THROUGH to impossible condition */
+
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ }
+
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ /* Silence compiler warning. */
+ return ISC_FALSE;
+}
+
+isc_result_t dhcp_failover_process_bind_update (dhcp_failover_state_t *state,
+ failover_message_t *msg)
+{
+ struct lease *lt = NULL, *lease = NULL;
+ struct iaddr ia;
+ int reason = FTR_MISC_REJECT;
+ const char *message;
+ int new_binding_state;
+ int send_to_backup = 0;
+ int required_options;
+ isc_boolean_t chaddr_changed = ISC_FALSE;
+ isc_boolean_t ident_changed = ISC_FALSE;
+
+ /* Validate the binding update. */
+ required_options = FTB_ASSIGNED_IP_ADDRESS | FTB_BINDING_STATUS;
+ if ((msg->options_present & required_options) != required_options) {
+ message = "binding update lacks required options";
+ reason = FTR_MISSING_BINDINFO;
+ goto bad;
+ }
+
+ ia.len = sizeof msg -> assigned_addr;
+ memcpy (ia.iabuf, &msg -> assigned_addr, ia.len);
+
+ if (!find_lease_by_ip_addr (&lease, ia, MDL)) {
+ message = "unknown IP address";
+ reason = FTR_ILLEGAL_IP_ADDR;
+ goto bad;
+ }
+
+ /*
+ * If this lease is covered by a different failover peering
+ * relationship, assert an error.
+ */
+ if ((lease->pool == NULL) || (lease->pool->failover_peer == NULL) ||
+ (lease->pool->failover_peer != state)) {
+ message = "IP address is covered by a different failover "
+ "relationship state";
+ reason = FTR_ILLEGAL_IP_ADDR;
+ goto bad;
+ }
+
+ /*
+ * Dueling updates: This happens when both servers send a BNDUPD
+ * at the same time. We want the best update to win, which means
+ * we reject if we think ours is better, or cancel if we think the
+ * peer's is better. We only assert a problem if the lease is on
+ * the ACK queue, not on the UPDATE queue. This means that after
+ * accepting this server's BNDUPD, we will send our own BNDUPD
+ * /after/ sending the BNDACK (this order was recently enforced in
+ * queue processing).
+ */
+ if ((lease->flags & ON_ACK_QUEUE) != 0) {
+ if (failover_lease_is_better(state, lease, msg)) {
+ message = "incoming update is less critical than "
+ "outgoing update";
+ reason = FTR_LESS_CRIT_BIND_INFO;
+ goto bad;
+ } else {
+ /* This makes it so we ignore any spurious ACKs. */
+ dhcp_failover_ack_queue_remove(state, lease);
+ }
+ }
+
+ /* Install the new info. Start by taking a copy to markup. */
+ if (!lease_copy (&lt, lease, MDL)) {
+ message = "no memory";
+ goto bad;
+ }
+
+ if (msg -> options_present & FTB_CHADDR) {
+ if (msg->binding_status == FTS_ABANDONED) {
+ message = "BNDUPD to ABANDONED with a CHADDR";
+ goto bad;
+ }
+ if (msg -> chaddr.count > sizeof lt -> hardware_addr.hbuf) {
+ message = "chaddr too long";
+ goto bad;
+ }
+
+ if ((lt->hardware_addr.hlen != msg->chaddr.count) ||
+ (memcmp(lt->hardware_addr.hbuf, msg->chaddr.data,
+ msg->chaddr.count) != 0))
+ chaddr_changed = ISC_TRUE;
+
+ lt -> hardware_addr.hlen = msg -> chaddr.count;
+ memcpy (lt -> hardware_addr.hbuf, msg -> chaddr.data,
+ msg -> chaddr.count);
+ } else if (msg->binding_status == FTS_ACTIVE ||
+ msg->binding_status == FTS_EXPIRED ||
+ msg->binding_status == FTS_RELEASED) {
+ message = "BNDUPD without CHADDR";
+ reason = FTR_MISSING_BINDINFO;
+ goto bad;
+ } else if (msg->binding_status == FTS_ABANDONED) {
+ chaddr_changed = ISC_TRUE;
+ lt->hardware_addr.hlen = 0;
+ if (lt->scope)
+ binding_scope_dereference(&lt->scope, MDL);
+ }
+
+ /* There is no explicit message content to indicate that the client
+ * supplied no client-identifier. So if we don't hear of a value,
+ * we discard the last one.
+ */
+ if (msg->options_present & FTB_CLIENT_IDENTIFIER) {
+ if (msg->binding_status == FTS_ABANDONED) {
+ message = "BNDUPD to ABANDONED with client-id";
+ goto bad;
+ }
+
+ if ((lt->uid_len != msg->client_identifier.count) ||
+ (lt->uid == NULL) || /* Sanity; should never happen. */
+ (memcmp(lt->uid, msg->client_identifier.data,
+ lt->uid_len) != 0))
+ ident_changed = ISC_TRUE;
+
+ lt->uid_len = msg->client_identifier.count;
+
+ /* Allocate the lt->uid buffer if we haven't already, or
+ * re-allocate the lt-uid buffer if we have one that is not
+ * large enough. Otherwise, just use the extant buffer.
+ */
+ if (!lt->uid || lt->uid == lt->uid_buf ||
+ lt->uid_len > lt->uid_max) {
+ if (lt->uid && lt->uid != lt->uid_buf)
+ dfree(lt->uid, MDL);
+
+ if (lt->uid_len > sizeof(lt->uid_buf)) {
+ lt->uid_max = lt->uid_len;
+ lt->uid = dmalloc(lt->uid_len, MDL);
+ if (!lt->uid) {
+ message = "no memory";
+ goto bad;
+ }
+ } else {
+ lt->uid_max = sizeof(lt->uid_buf);
+ lt->uid = lt->uid_buf;
+ }
+ }
+ memcpy (lt -> uid,
+ msg -> client_identifier.data, lt -> uid_len);
+ } else if (lt->uid && msg->binding_status != FTS_RESET &&
+ msg->binding_status != FTS_FREE &&
+ msg->binding_status != FTS_BACKUP) {
+ ident_changed = ISC_TRUE;
+ if (lt->uid != lt->uid_buf)
+ dfree (lt->uid, MDL);
+ lt->uid = NULL;
+ lt->uid_max = lt->uid_len = 0;
+ }
+
+ /*
+ * A server's configuration can assign a 'binding scope';
+ *
+ * set var = "value";
+ *
+ * The problem with these binding scopes is that they are refreshed
+ * when the server processes a client's DHCP packet. A local binding
+ * scope is trash, then, when the lease has been assigned by the
+ * partner server. There is no real way to detect this, a peer may
+ * be updating us (as through potential conflict) with a binding we
+ * sent them, but we can trivially detect the /problematic/ case;
+ *
+ * lease is free.
+ * primary allocates lease to client A, assigns ddns name A.
+ * primary fails.
+ * secondary enters partner down.
+ * lease expires, and is set free.
+ * lease is allocated to client B and given ddns name B.
+ * primary recovers.
+ *
+ * The binding update in this case will be active->active, but the
+ * client identification on the lease will have changed. The ddns
+ * update on client A will have leaked if we just remove the binding
+ * scope blindly.
+ */
+ if (msg->binding_status == FTS_ACTIVE &&
+ (chaddr_changed || ident_changed)) {
+#if defined (NSUPDATE)
+ (void) ddns_removals(lease, NULL, NULL, ISC_FALSE);
+#endif /* NSUPDATE */
+
+ if (lease->scope != NULL)
+ binding_scope_dereference(&lease->scope, MDL);
+ }
+
+ /* XXX Times may need to be adjusted based on clock skew! */
+ if (msg -> options_present & FTB_STOS) {
+ lt -> starts = msg -> stos;
+ }
+ if (msg -> options_present & FTB_LEASE_EXPIRY) {
+ lt -> ends = msg -> expiry;
+ }
+ if (msg->options_present & FTB_POTENTIAL_EXPIRY) {
+ lt->atsfp = lt->tsfp = msg->potential_expiry;
+ }
+ if (msg->options_present & FTB_IP_FLAGS) {
+ if (msg->ip_flags & FTF_IP_FLAG_RESERVE) {
+ if ((((state->i_am == primary) &&
+ (lease->binding_state == FTS_FREE)) ||
+ ((state->i_am == secondary) &&
+ (lease->binding_state == FTS_BACKUP))) &&
+ !(lease->flags & RESERVED_LEASE)) {
+ message = "Address is not reserved.";
+ reason = FTR_IP_NOT_RESERVED;
+ goto bad;
+ }
+
+ lt->flags |= RESERVED_LEASE;
+ } else
+ lt->flags &= ~RESERVED_LEASE;
+
+ if (msg->ip_flags & FTF_IP_FLAG_BOOTP) {
+ if ((((state->i_am == primary) &&
+ (lease->binding_state == FTS_FREE)) ||
+ ((state->i_am == secondary) &&
+ (lease->binding_state == FTS_BACKUP))) &&
+ !(lease->flags & BOOTP_LEASE)) {
+ message = "Address is not allocated to BOOTP.";
+ goto bad;
+ }
+ lt->flags |= BOOTP_LEASE;
+ } else
+ lt->flags &= ~BOOTP_LEASE;
+
+ if (msg->ip_flags & ~(FTF_IP_FLAG_RESERVE | FTF_IP_FLAG_BOOTP))
+ log_info("Unknown IP-flags set in BNDUPD (0x%x).",
+ msg->ip_flags);
+ } else /* Flags may only not appear if the values are zero. */
+ lt->flags &= ~(RESERVED_LEASE | BOOTP_LEASE);
+
+#if defined (DEBUG_LEASE_STATE_TRANSITIONS)
+ log_info ("processing state transition for %s: %s to %s",
+ piaddr (lease -> ip_addr),
+ binding_state_print (lease -> binding_state),
+ binding_state_print (msg -> binding_status));
+#endif
+
+ /* If we're in normal state, make sure the state transition
+ we got is valid. */
+ if (state -> me.state == normal) {
+ new_binding_state =
+ (normal_binding_state_transition_check
+ (lease, state, msg -> binding_status,
+ msg -> potential_expiry));
+ /* XXX if the transition the peer asked for isn't
+ XXX allowed, maybe we should make the transition
+ XXX into potential-conflict at this point. */
+ } else {
+ new_binding_state =
+ (conflict_binding_state_transition_check
+ (lease, state, msg -> binding_status,
+ msg -> potential_expiry));
+ }
+ if (new_binding_state != msg -> binding_status) {
+ char outbuf [100];
+
+ if (snprintf (outbuf, sizeof outbuf,
+ "%s: invalid state transition: %s to %s",
+ piaddr (lease -> ip_addr),
+ binding_state_print (lease -> binding_state),
+ binding_state_print (msg -> binding_status))
+ >= sizeof outbuf)
+ log_fatal ("%s: impossible outbuf overflow",
+ "dhcp_failover_process_bind_update");
+
+ dhcp_failover_send_bind_ack (state, msg,
+ FTR_FATAL_CONFLICT,
+ outbuf);
+ goto out;
+ }
+ if (new_binding_state == FTS_EXPIRED ||
+ new_binding_state == FTS_RELEASED ||
+ new_binding_state == FTS_RESET) {
+ lt -> next_binding_state = FTS_FREE;
+
+ /* Mac address affinity. Assign the lease to
+ * BACKUP state if we are the primary and the
+ * peer is more likely to reallocate this lease
+ * to a returning client.
+ */
+ if ((state->i_am == primary) &&
+ !(lt->flags & (RESERVED_LEASE | BOOTP_LEASE)))
+ send_to_backup = peer_wants_lease(lt);
+ } else {
+ lt -> next_binding_state = new_binding_state;
+ }
+ msg -> binding_status = lt -> next_binding_state;
+
+ /*
+ * If we accept a peer's binding update, then we can't rewind a
+ * lease behind the peer's state.
+ */
+ lease->rewind_binding_state = lt->next_binding_state;
+
+ /* Try to install the new information. */
+ if (!supersede_lease (lease, lt, 0, 0, 0, 0) ||
+ !write_lease (lease)) {
+ message = "database update failed";
+ bad:
+ dhcp_failover_send_bind_ack (state, msg, reason, message);
+ goto out;
+ } else {
+ dhcp_failover_queue_ack (state, msg);
+ }
+
+ /* If it is probably wise, assign lease to backup state if the peer
+ * is not already hoarding leases.
+ */
+ if (send_to_backup && secondary_not_hoarding(state, lease->pool)) {
+ lease->next_binding_state = FTS_BACKUP;
+ lease->tstp = cur_time;
+ lease->starts = cur_time;
+
+ if (!supersede_lease(lease, NULL, 0, 1, 0, 0) ||
+ !write_lease(lease))
+ log_error("can't commit lease %s for mac addr "
+ "affinity", piaddr(lease->ip_addr));
+
+ dhcp_failover_send_updates(state);
+ }
+
+ out:
+ if (lt)
+ lease_dereference (&lt, MDL);
+ if (lease)
+ lease_dereference (&lease, MDL);
+
+ return ISC_R_SUCCESS;
+}
+
+/* This was hairy enough I didn't want to do it all in an if statement.
+ *
+ * Returns: Truth is the secondary is allowed to get more leases based upon
+ * MAC address affinity. False otherwise.
+ */
+static inline int
+secondary_not_hoarding(dhcp_failover_state_t *state, struct pool *p) {
+ int total;
+ int hold;
+ int lts;
+
+ total = p->free_leases + p->backup_leases;
+
+ /* How many leases is one side or the other allowed to "hold"? */
+ hold = ((total * state->max_lease_ownership) + 50) / 100;
+
+ /* If we were to send leases (or if the secondary were to send us
+ * leases in the negative direction), how many would that be?
+ */
+ lts = (p->free_leases - p->backup_leases) / 2;
+
+ /* The peer is not hoarding leases if we would send them more leases
+ * (or they would take fewer leases) than the maximum they are allowed
+ * to hold (the negative hold).
+ */
+ return(lts > -hold);
+}
+
+isc_result_t dhcp_failover_process_bind_ack (dhcp_failover_state_t *state,
+ failover_message_t *msg)
+{
+ struct lease *lt = (struct lease *)0;
+ struct lease *lease = (struct lease *)0;
+ struct iaddr ia;
+ const char *message = "no memory";
+ u_int32_t pot_expire;
+ int send_to_backup = ISC_FALSE;
+ struct timeval tv;
+
+ ia.len = sizeof msg -> assigned_addr;
+ memcpy (ia.iabuf, &msg -> assigned_addr, ia.len);
+
+ if (!find_lease_by_ip_addr (&lease, ia, MDL)) {
+ message = "no such lease";
+ goto bad;
+ }
+
+ /* XXX check for conflicts. */
+ if (msg -> options_present & FTB_REJECT_REASON) {
+ log_error ("bind update on %s from %s rejected: %.*s",
+ piaddr (ia), state -> name,
+ (int)((msg -> options_present & FTB_MESSAGE)
+ ? msg -> message.count
+ : strlen (dhcp_failover_reject_reason_print
+ (msg -> reject_reason))),
+ (msg -> options_present & FTB_MESSAGE)
+ ? (const char *)(msg -> message.data)
+ : (dhcp_failover_reject_reason_print
+ (msg -> reject_reason)));
+ goto unqueue;
+ }
+
+ /* Silently discard acks for leases we did not update (or multiple
+ * acks).
+ */
+ if (!lease->last_xid)
+ goto unqueue;
+
+ if (lease->last_xid != msg->xid) {
+ message = "xid mismatch";
+ goto bad;
+ }
+
+ /* XXX Times may need to be adjusted based on clock skew! */
+ if (msg->options_present & FTO_POTENTIAL_EXPIRY)
+ pot_expire = msg->potential_expiry;
+ else
+ pot_expire = lease->tstp;
+
+ /* If the lease was desired to enter a binding state, we set
+ * such a value upon transmitting a bndupd. We do not clear it
+ * if we receive a bndupd in the meantime (or change the state
+ * of the lease again ourselves), but we do set binding_state
+ * if we get a bndupd.
+ *
+ * So desired_binding_state tells us what we sent a bndupd for,
+ * and binding_state tells us what we have since determined in
+ * the meantime.
+ */
+ if (lease->desired_binding_state == FTS_EXPIRED ||
+ lease->desired_binding_state == FTS_RESET ||
+ lease->desired_binding_state == FTS_RELEASED)
+ {
+ /* It is not a problem to do this directly as we call
+ * supersede_lease immediately after: the lease is requeued
+ * even if its sort order (tsfp) has changed.
+ */
+ lease->atsfp = lease->tsfp = pot_expire;
+ if ((state->i_am == secondary) &&
+ (lease->flags & RESERVED_LEASE))
+ lease->next_binding_state = FTS_BACKUP;
+ else
+ lease->next_binding_state = FTS_FREE;
+
+ /* Clear this condition for the next go-round. */
+ lease->desired_binding_state = lease->next_binding_state;
+
+ /* The peer will have made this state change, so set rewind. */
+ lease->rewind_binding_state = lease->next_binding_state;
+
+ supersede_lease(lease, NULL, 0, 0, 0, 0);
+ write_lease(lease);
+
+ /* Lease has returned to FREE state from the
+ * transitional states. If the lease 'belongs'
+ * to a client that would be served by the
+ * peer, process a binding update now to send
+ * the lease to backup state. But not if we
+ * think we already have.
+ */
+ if (state->i_am == primary &&
+ !(lease->flags & (RESERVED_LEASE | BOOTP_LEASE)) &&
+ peer_wants_lease(lease))
+ send_to_backup = ISC_TRUE;
+
+ if (!send_to_backup && state->me.state == normal)
+ commit_leases();
+ } else {
+ /* XXX It could be a problem to do this directly if the lease
+ * XXX is sorted by tsfp.
+ */
+ lease->atsfp = lease->tsfp = pot_expire;
+ if (lease->desired_binding_state != lease->binding_state) {
+ lease->next_binding_state =
+ lease->desired_binding_state;
+ supersede_lease(lease, NULL, 0, 0, 0, 0);
+ }
+ write_lease(lease);
+ /* Commit the lease only after a two-second timeout,
+ so that if we get a bunch of acks in quick
+ succession (e.g., when stealing leases from the
+ secondary), we do not do an immediate commit for
+ each one. */
+ tv.tv_sec = cur_time + 2;
+ tv.tv_usec = 0;
+ add_timeout(&tv, commit_leases_timeout, (void *)0, 0, 0);
+ }
+
+ unqueue:
+ dhcp_failover_ack_queue_remove (state, lease);
+
+ /* If we are supposed to send an update done after we send
+ this lease, go ahead and send it. */
+ if (state -> send_update_done == lease) {
+ lease_dereference (&state -> send_update_done, MDL);
+ dhcp_failover_send_update_done (state);
+ }
+
+ /* Now that the lease is off the ack queue, consider putting it
+ * back on the update queue for mac address affinity.
+ */
+ if (send_to_backup && secondary_not_hoarding(state, lease->pool)) {
+ lease->next_binding_state = FTS_BACKUP;
+ lease->tstp = lease->starts = cur_time;
+
+ if (!supersede_lease(lease, NULL, 0, 1, 0, 0) ||
+ !write_lease(lease))
+ log_error("can't commit lease %s for "
+ "client affinity", piaddr(lease->ip_addr));
+
+ if (state->me.state == normal)
+ commit_leases();
+ }
+
+ /* If there are updates pending, we've created space to send at
+ least one. */
+ dhcp_failover_send_updates (state);
+
+ out:
+ lease_dereference (&lease, MDL);
+ if (lt)
+ lease_dereference (&lt, MDL);
+
+ return ISC_R_SUCCESS;
+
+ bad:
+ log_info ("bind update on %s got ack from %s: %s.",
+ piaddr (ia), state -> name, message);
+ goto out;
+}
+
+isc_result_t dhcp_failover_generate_update_queue (dhcp_failover_state_t *state,
+ int everythingp)
+{
+ struct shared_network *s;
+ struct pool *p;
+ struct lease *l;
+ int i;
+#define FREE_LEASES 0
+#define ACTIVE_LEASES 1
+#define EXPIRED_LEASES 2
+#define ABANDONED_LEASES 3
+#define BACKUP_LEASES 4
+#define RESERVED_LEASES 5
+ struct lease **lptr[RESERVED_LEASES+1];
+
+ /* Loop through each pool in each shared network and call the
+ expiry routine on the pool. */
+ for (s = shared_networks; s; s = s -> next) {
+ for (p = s -> pools; p; p = p -> next) {
+ if (p->failover_peer != state)
+ continue;
+
+ lptr[FREE_LEASES] = &p->free;
+ lptr[ACTIVE_LEASES] = &p->active;
+ lptr[EXPIRED_LEASES] = &p->expired;
+ lptr[ABANDONED_LEASES] = &p->abandoned;
+ lptr[BACKUP_LEASES] = &p->backup;
+ lptr[RESERVED_LEASES] = &p->reserved;
+
+ for (i = FREE_LEASES; i <= RESERVED_LEASES; i++) {
+ for (l = *(lptr [i]); l; l = l -> next) {
+ if ((l->flags & ON_QUEUE) == 0 &&
+ (everythingp ||
+ (l->tstp > l->atsfp) ||
+ (i == EXPIRED_LEASES))) {
+ l -> desired_binding_state = l -> binding_state;
+ dhcp_failover_queue_update (l, 0);
+ }
+ }
+ }
+ }
+ }
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t
+dhcp_failover_process_update_request (dhcp_failover_state_t *state,
+ failover_message_t *msg)
+{
+ if (state->send_update_done) {
+ log_info("Received update request while old update still "
+ "flying! Silently discarding old request.");
+ lease_dereference(&state->send_update_done, MDL);
+ }
+
+ /* Generate a fresh update queue. */
+ dhcp_failover_generate_update_queue (state, 0);
+
+ state->updxid = msg->xid;
+
+ /* If there's anything on the update queue (there shouldn't be
+ anything on the ack queue), trigger an update done message
+ when we get an ack for that lease. */
+ if (state -> update_queue_tail) {
+ lease_reference (&state -> send_update_done,
+ state -> update_queue_tail, MDL);
+ dhcp_failover_send_updates (state);
+ log_info ("Update request from %s: sending update",
+ state -> name);
+ } else {
+ /* Otherwise, there are no updates to send, so we can
+ just send an UPDDONE message immediately. */
+ dhcp_failover_send_update_done (state);
+ log_info ("Update request from %s: nothing pending",
+ state -> name);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t
+dhcp_failover_process_update_request_all (dhcp_failover_state_t *state,
+ failover_message_t *msg)
+{
+ if (state->send_update_done) {
+ log_info("Received update request while old update still "
+ "flying! Silently discarding old request.");
+ lease_dereference(&state->send_update_done, MDL);
+ }
+
+ /* Generate a fresh update queue that includes every lease. */
+ dhcp_failover_generate_update_queue (state, 1);
+
+ state->updxid = msg->xid;
+
+ if (state -> update_queue_tail) {
+ lease_reference (&state -> send_update_done,
+ state -> update_queue_tail, MDL);
+ dhcp_failover_send_updates (state);
+ log_info ("Update request all from %s: sending update",
+ state -> name);
+ } else {
+ /* This should really never happen, but it could happen
+ on a server that currently has no leases configured. */
+ dhcp_failover_send_update_done (state);
+ log_info ("Update request all from %s: nothing pending",
+ state -> name);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t
+dhcp_failover_process_update_done (dhcp_failover_state_t *state,
+ failover_message_t *msg)
+{
+ struct timeval tv;
+
+ log_info ("failover peer %s: peer update completed.",
+ state -> name);
+
+ state -> curUPD = 0;
+
+ switch (state -> me.state) {
+ case unknown_state:
+ case partner_down:
+ case normal:
+ case communications_interrupted:
+ case resolution_interrupted:
+ case shut_down:
+ case paused:
+ case recover_done:
+ case startup:
+ case recover_wait:
+ break; /* shouldn't happen. */
+
+ /* We got the UPDDONE, so we can go into normal state! */
+ case potential_conflict:
+ if (state->partner.state == conflict_done) {
+ if (state->i_am == secondary) {
+ dhcp_failover_set_state (state, normal);
+ } else {
+ log_error("Secondary is in conflict_done "
+ "state after conflict resolution, "
+ "this is illegal.");
+ dhcp_failover_set_state (state, shut_down);
+ }
+ } else {
+ if (state->i_am == primary)
+ dhcp_failover_set_state (state, conflict_done);
+ else
+ log_error("Spurious update-done message.");
+ }
+
+ break;
+
+ case conflict_done:
+ log_error("Spurious update-done message.");
+ break;
+
+ case recover:
+ /* Wait for MCLT to expire before moving to recover_done,
+ except that if both peers come up in recover, there is
+ no point in waiting for MCLT to expire - this probably
+ indicates the initial startup of a newly-configured
+ failover pair. */
+ if (state -> me.stos + state -> mclt > cur_time &&
+ state -> partner.state != recover &&
+ state -> partner.state != recover_done) {
+ dhcp_failover_set_state (state, recover_wait);
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("add_timeout +%d %s",
+ (int)(cur_time -
+ state -> me.stos + state -> mclt),
+ "dhcp_failover_recover_done");
+#endif
+ tv . tv_sec = (int)(state -> me.stos + state -> mclt);
+ tv . tv_usec = 0;
+ add_timeout (&tv,
+ dhcp_failover_recover_done,
+ state,
+ (tvref_t)omapi_object_reference,
+ (tvunref_t)
+ omapi_object_dereference);
+ } else
+ dhcp_failover_recover_done (state);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+void dhcp_failover_recover_done (void *sp)
+{
+ dhcp_failover_state_t *state = sp;
+
+#if defined (DEBUG_FAILOVER_TIMING)
+ log_info ("dhcp_failover_recover_done");
+#endif
+
+ dhcp_failover_set_state (state, recover_done);
+}
+
+#if defined (DEBUG_FAILOVER_MESSAGES)
+/* Print hunks of failover messages, doing line breaks as appropriate.
+ Note that this assumes syslog is being used, rather than, e.g., the
+ Windows NT logging facility, where just dumping the whole message in
+ one hunk would be more appropriate. */
+
+void failover_print (char *obuf,
+ unsigned *obufix, unsigned obufmax, const char *s)
+{
+ int len = strlen (s);
+
+ while (len + *obufix + 1 >= obufmax) {
+ log_debug ("%s", obuf);
+ if (!*obufix) {
+ log_debug ("%s", s);
+ *obufix = 0;
+ return;
+ }
+ *obufix = 0;
+ }
+ strcpy (&obuf [*obufix], s);
+ *obufix += len;
+}
+#endif /* defined (DEBUG_FAILOVER_MESSAGES) */
+
+/* Taken from draft-ietf-dhc-loadb-01.txt: */
+/* A "mixing table" of 256 distinct values, in pseudo-random order. */
+unsigned char loadb_mx_tbl[256] = {
+ 251, 175, 119, 215, 81, 14, 79, 191, 103, 49,
+ 181, 143, 186, 157, 0, 232, 31, 32, 55, 60,
+ 152, 58, 17, 237, 174, 70, 160, 144, 220, 90,
+ 57, 223, 59, 3, 18, 140, 111, 166, 203, 196,
+ 134, 243, 124, 95, 222, 179, 197, 65, 180, 48,
+ 36, 15, 107, 46, 233, 130, 165, 30, 123, 161,
+ 209, 23, 97, 16, 40, 91, 219, 61, 100, 10,
+ 210, 109, 250, 127, 22, 138, 29, 108, 244, 67,
+ 207, 9, 178, 204, 74, 98, 126, 249, 167, 116,
+ 34, 77, 193, 200, 121, 5, 20, 113, 71, 35,
+ 128, 13, 182, 94, 25, 226, 227, 199, 75, 27,
+ 41, 245, 230, 224, 43, 225, 177, 26, 155, 150,
+ 212, 142, 218, 115, 241, 73, 88, 105, 39, 114,
+ 62, 255, 192, 201, 145, 214, 168, 158, 221, 148,
+ 154, 122, 12, 84, 82, 163, 44, 139, 228, 236,
+ 205, 242, 217, 11, 187, 146, 159, 64, 86, 239,
+ 195, 42, 106, 198, 118, 112, 184, 172, 87, 2,
+ 173, 117, 176, 229, 247, 253, 137, 185, 99, 164,
+ 102, 147, 45, 66, 231, 52, 141, 211, 194, 206,
+ 246, 238, 56, 110, 78, 248, 63, 240, 189, 93,
+ 92, 51, 53, 183, 19, 171, 72, 50, 33, 104,
+ 101, 69, 8, 252, 83, 120, 76, 135, 85, 54,
+ 202, 125, 188, 213, 96, 235, 136, 208, 162, 129,
+ 190, 132, 156, 38, 47, 1, 7, 254, 24, 4,
+ 216, 131, 89, 21, 28, 133, 37, 153, 149, 80,
+ 170, 68, 6, 169, 234, 151 };
+
+static unsigned char loadb_p_hash (const unsigned char *, unsigned);
+static unsigned char loadb_p_hash (const unsigned char *key, unsigned len)
+{
+ unsigned char hash = len;
+ int i;
+ for(i = len; i > 0; )
+ hash = loadb_mx_tbl [hash ^ (key [--i])];
+ return hash;
+}
+
+int load_balance_mine (struct packet *packet, dhcp_failover_state_t *state)
+{
+ struct option_cache *oc;
+ struct data_string ds;
+ unsigned char hbaix;
+ int hm;
+ u_int16_t ec;
+
+ ec = ntohs(packet->raw->secs);
+
+#if defined(SECS_BYTEORDER)
+ /*
+ * If desired check to see if the secs field may have been byte
+ * swapped. We assume it has if the high order byte isn't cleared
+ * while the low order byte is cleared. In this case we swap the
+ * bytes and continue processing.
+ */
+ if ((ec > 255) && ((ec & 0xff) == 0)) {
+ ec = (ec >> 8) | (ec << 8);
+ }
+#endif
+
+ if (state->load_balance_max_secs < ec) {
+ return (1);
+ }
+
+ /* If we don't have a hash bucket array, we can't tell if this
+ one's ours, so we assume it's not. */
+ if (!state->hba)
+ return (0);
+
+ oc = lookup_option(&dhcp_universe, packet->options,
+ DHO_DHCP_CLIENT_IDENTIFIER);
+ memset(&ds, 0, sizeof ds);
+ if (oc &&
+ evaluate_option_cache(&ds, packet, NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ hbaix = loadb_p_hash(ds.data, ds.len);
+
+ data_string_forget(&ds, MDL);
+ } else {
+ hbaix = loadb_p_hash(packet->raw->chaddr,
+ packet->raw->hlen);
+ }
+
+ hm = state->hba[(hbaix >> 3) & 0x1F] & (1 << (hbaix & 0x07));
+
+ if (state->i_am == primary)
+ return (hm);
+ else
+ return (!hm);
+}
+
+/* The inverse of load_balance_mine ("load balance theirs"). We can't
+ * use the regular load_balance_mine() and invert it because of the case
+ * where there might not be an HBA, and we want to indicate false here
+ * in this case only.
+ */
+int
+peer_wants_lease(struct lease *lp)
+{
+ dhcp_failover_state_t *state;
+ unsigned char hbaix;
+ int hm;
+
+ if (!lp->pool)
+ return 0;
+
+ state = lp->pool->failover_peer;
+
+ if (!state || !state->hba)
+ return 0;
+
+ if (lp->uid_len)
+ hbaix = loadb_p_hash(lp->uid, lp->uid_len);
+ else if (lp->hardware_addr.hlen > 1)
+ /* Skip the first byte, which is the hardware type, and is
+ * not included during actual load balancing checks above
+ * since it is separate from the packet header chaddr field.
+ * The remainder of the hardware address should be identical
+ * to the chaddr contents.
+ */
+ hbaix = loadb_p_hash(lp->hardware_addr.hbuf + 1,
+ lp->hardware_addr.hlen - 1);
+ else /* impossible to categorize into LBA */
+ return 0;
+
+ hm = state->hba[(hbaix >> 3) & 0x1F] & (1 << (hbaix & 0x07));
+
+ if (state->i_am == primary)
+ return !hm;
+ else
+ return hm;
+}
+
+/* This deals with what to do with bind updates when
+ we're in the normal state
+
+ Note that tsfp had better be set from the latest bind update
+ _before_ this function is called! */
+
+binding_state_t
+normal_binding_state_transition_check (struct lease *lease,
+ dhcp_failover_state_t *state,
+ binding_state_t binding_state,
+ u_int32_t tsfp)
+{
+ binding_state_t new_state;
+
+ /* If there is no transition, it's no problem. */
+ if (binding_state == lease -> binding_state)
+ return binding_state;
+
+ switch (lease -> binding_state) {
+ case FTS_FREE:
+ case FTS_ABANDONED:
+ switch (binding_state) {
+ case FTS_ACTIVE:
+ case FTS_ABANDONED:
+ case FTS_BACKUP:
+ case FTS_EXPIRED:
+ case FTS_RELEASED:
+ case FTS_RESET:
+ /* If the lease was free, and our peer is primary,
+ then it can make it active, or abandoned, or
+ backup. Abandoned is treated like free in
+ this case. */
+ if (state -> i_am == secondary)
+ return binding_state;
+
+ /* Otherwise, it can't legitimately do any sort of
+ state transition. Because the lease was free,
+ and the error has already been made, we allow the
+ peer to change its state anyway, but log a warning
+ message in hopes that the error will be fixed. */
+ case FTS_FREE: /* for compiler */
+ new_state = binding_state;
+ goto out;
+
+ default:
+ log_fatal ("Impossible case at %s:%d.", MDL);
+ return FTS_RESET;
+ }
+ case FTS_ACTIVE:
+ /* The secondary can't change the state of an active
+ lease. */
+ if (state -> i_am == primary) {
+ /* Except that the client may send the DHCPRELEASE
+ to the secondary, and we have to accept that. */
+ if (binding_state == FTS_RELEASED)
+ return binding_state;
+ new_state = lease -> binding_state;
+ goto out;
+ }
+
+ /* So this is only for transitions made by the primary: */
+ switch (binding_state) {
+ case FTS_FREE:
+ case FTS_BACKUP:
+ /* Can't set a lease to free or backup until the
+ peer agrees that it's expired. */
+ if (tsfp > cur_time) {
+ new_state = lease -> binding_state;
+ goto out;
+ }
+ return binding_state;
+
+ case FTS_EXPIRED:
+ /* XXX 65 should be the clock skew between the peers
+ XXX plus a fudge factor. This code will result
+ XXX in problems if MCLT is really short or the
+ XXX max-lease-time is really short (less than the
+ XXX fudge factor. */
+ if (lease -> ends - 65 > cur_time) {
+ new_state = lease -> binding_state;
+ goto out;
+ }
+
+ case FTS_RELEASED:
+ case FTS_ABANDONED:
+ case FTS_RESET:
+ case FTS_ACTIVE:
+ return binding_state;
+
+ default:
+ log_fatal ("Impossible case at %s:%d.", MDL);
+ return FTS_RESET;
+ }
+ break;
+ case FTS_EXPIRED:
+ switch (binding_state) {
+ case FTS_BACKUP:
+ case FTS_FREE:
+ /* Can't set a lease to free or backup until the
+ peer agrees that it's expired. */
+ if (tsfp > cur_time) {
+ new_state = lease -> binding_state;
+ goto out;
+ }
+ return binding_state;
+
+ case FTS_ACTIVE:
+ case FTS_RELEASED:
+ case FTS_ABANDONED:
+ case FTS_RESET:
+ case FTS_EXPIRED:
+ return binding_state;
+
+ default:
+ log_fatal ("Impossible case at %s:%d.", MDL);
+ return FTS_RESET;
+ }
+ case FTS_RELEASED:
+ switch (binding_state) {
+ case FTS_FREE:
+ case FTS_BACKUP:
+
+ /* These are invalid state transitions - should we
+ prevent them? */
+ case FTS_EXPIRED:
+ case FTS_ABANDONED:
+ case FTS_RESET:
+ case FTS_ACTIVE:
+ case FTS_RELEASED:
+ return binding_state;
+
+ default:
+ log_fatal ("Impossible case at %s:%d.", MDL);
+ return FTS_RESET;
+ }
+ case FTS_RESET:
+ switch (binding_state) {
+ case FTS_FREE:
+ case FTS_BACKUP:
+ /* Can't set a lease to free or backup until the
+ peer agrees that it's expired. */
+ if (tsfp > cur_time) {
+ new_state = lease -> binding_state;
+ goto out;
+ }
+ return binding_state;
+
+ case FTS_ACTIVE:
+ case FTS_EXPIRED:
+ case FTS_RELEASED:
+ case FTS_ABANDONED:
+ case FTS_RESET:
+ return binding_state;
+
+ default:
+ log_fatal ("Impossible case at %s:%d.", MDL);
+ return FTS_RESET;
+ }
+ case FTS_BACKUP:
+ switch (binding_state) {
+ case FTS_ACTIVE:
+ case FTS_ABANDONED:
+ case FTS_EXPIRED:
+ case FTS_RELEASED:
+ case FTS_RESET:
+ /* If the lease was in backup, and our peer
+ is secondary, then it can make it active
+ or abandoned. */
+ if (state -> i_am == primary)
+ return binding_state;
+
+ /* Either the primary or the secondary can
+ reasonably move a lease from the backup
+ state to the free state. */
+ case FTS_FREE:
+ return binding_state;
+
+ case FTS_BACKUP:
+ new_state = lease -> binding_state;
+ goto out;
+
+ default:
+ log_fatal ("Impossible case at %s:%d.", MDL);
+ return FTS_RESET;
+ }
+
+ default:
+ log_fatal ("Impossible case at %s:%d.", MDL);
+ return FTS_RESET;
+ }
+ out:
+ return new_state;
+}
+
+/* Determine whether the state transition is okay when we're potentially
+ in conflict with the peer. */
+binding_state_t
+conflict_binding_state_transition_check (struct lease *lease,
+ dhcp_failover_state_t *state,
+ binding_state_t binding_state,
+ u_int32_t tsfp)
+{
+ binding_state_t new_state;
+
+ /* If there is no transition, it's no problem. */
+ if (binding_state == lease -> binding_state)
+ new_state = binding_state;
+ else {
+ switch (lease -> binding_state) {
+ /* If we think the lease is not in use, then the
+ state into which the partner put it is just fine,
+ whatever it is. */
+ case FTS_FREE:
+ case FTS_ABANDONED:
+ case FTS_EXPIRED:
+ case FTS_RELEASED:
+ case FTS_RESET:
+ case FTS_BACKUP:
+ new_state = binding_state;
+ break;
+
+ /* If we think the lease *is* in use, then we're not
+ going to take the partner's change if the partner
+ thinks it's free. */
+ case FTS_ACTIVE:
+ switch (binding_state) {
+ case FTS_FREE:
+ case FTS_BACKUP:
+ new_state = lease -> binding_state;
+ break;
+
+ case FTS_EXPIRED:
+ /* If we don't agree about expiry, it's
+ * invalid. 65 should allow for max
+ * clock skew (60) plus some fudge.
+ * XXX: should we refetch cur_time?
+ */
+ if ((lease->ends - 65) > cur_time)
+ new_state = lease->binding_state;
+ else
+ new_state = binding_state;
+ break;
+
+ /* RELEASED, RESET, and ABANDONED indicate
+ * that our partner has information about
+ * this lease that we did not witness. Our
+ * partner wins.
+ */
+ case FTS_RELEASED:
+ case FTS_RESET:
+ case FTS_ABANDONED:
+ new_state = binding_state;
+ break;
+
+ default:
+ log_fatal ("Impossible case at %s:%d.", MDL);
+ return FTS_RESET;
+ }
+ break;
+
+ default:
+ log_fatal ("Impossible case at %s:%d.", MDL);
+ return FTS_RESET;
+ }
+ }
+ return new_state;
+}
+
+/* We can reallocate a lease under the following circumstances:
+
+ (1) It belongs to us - it's FTS_FREE, and we're primary, or it's
+ FTS_BACKUP, and we're secondary.
+ (2) We're in partner_down, and the lease is not active, and we
+ can be sure that the other server didn't make it active.
+ We can only be sure that the server didn't make it active
+ when we are in the partner_down state and one of the following
+ two conditions holds:
+ (a) in the case that the time sent from the peer is earlier than
+ the time we entered the partner_down state, at least MCLT has
+ gone by since we entered partner_down, or
+ (b) in the case that the time sent from the peer is later than
+ the time when we entered partner_down, the current time is
+ later than the time sent from the peer by at least MCLT. */
+
+int lease_mine_to_reallocate (struct lease *lease)
+{
+ dhcp_failover_state_t *peer;
+
+ if (lease && lease->pool &&
+ (peer = lease->pool->failover_peer)) {
+ /*
+ * In addition to the normal rules governing wether a server
+ * is allowed to operate changes on a lease, the server is
+ * allowed to operate on a lease from the standpoint of the
+ * most conservative guess of the peer's state for this lease.
+ */
+ switch (lease->binding_state) {
+ case FTS_ACTIVE:
+ /* ACTIVE leases may not be reallocated. */
+ return 0;
+
+ case FTS_FREE:
+ case FTS_ABANDONED:
+ /* FREE leases may only be allocated by the primary,
+ * unless the secondary is acting in partner_down
+ * state and stos+mclt or tsfp+mclt has expired,
+ * whichever is greater.
+ *
+ * ABANDONED are treated the same as FREE for all
+ * purposes here. Note that servers will only try
+ * for ABANDONED leases as a last resort anyway.
+ */
+ if (peer -> i_am == primary)
+ return 1;
+
+ return(peer->service_state == service_partner_down &&
+ ((lease->tsfp < peer->me.stos) ?
+ (peer->me.stos + peer->mclt < cur_time) :
+ (lease->tsfp + peer->mclt < cur_time)));
+
+ case FTS_RELEASED:
+ case FTS_EXPIRED:
+ /*
+ * These leases are generally untouchable until the
+ * peer acknowledges their state change. However, as
+ * this is impossible if the peer is offline, the
+ * failover protocol permits an 'optimization' to
+ * rewind the lease to a previous state that the server
+ * is allowed to operate on, if that was the state that
+ * was last acknowledged by the peer.
+ *
+ * So if a lease was free, was allocated by this
+ * server, and expired without ever being transmitted
+ * to the peer, it can be returned to free and given
+ * to any new client legally.
+ */
+ if ((peer->i_am == primary) &&
+ (lease->rewind_binding_state == FTS_FREE))
+ return 1;
+ if ((peer->i_am == secondary) &&
+ (lease->rewind_binding_state == FTS_BACKUP))
+ return 1;
+
+ /* FALL THROUGH (released, expired, reset) */
+ case FTS_RESET:
+ /*
+ * Released, expired, and reset leases go onto the
+ * 'expired' queue all together. Upon entry into
+ * partner-down state, this queue of leases has their
+ * tsfp values modified to equal stos+mclt, the point
+ * at which the server is allowed to remove them from
+ * these transitional states.
+ *
+ * Note that although tsfp has been possibly extended
+ * past the actual tsfp we received from the peer, we
+ * don't have to take any special action. Since tsfp
+ * will be equal to the current time when the lease
+ * transitions to free, tsfp will not be used to grant
+ * lease-times longer than the MCLT to clients, which
+ * is the only danger for this sort of modification.
+ */
+ return((peer->service_state == service_partner_down) &&
+ (lease->tsfp < cur_time));
+
+ case FTS_BACKUP:
+ /* Only the secondary may allocate BACKUP leases,
+ * unless in partner_down state in which case at
+ * least TSFP+MCLT or STOS+MCLT must have expired,
+ * whichever is greater.
+ */
+ if (peer->i_am == secondary)
+ return 1;
+
+ return((peer->service_state == service_partner_down) &&
+ ((lease->tsfp < peer->me.stos) ?
+ (peer->me.stos + peer->mclt < cur_time) :
+ (lease->tsfp + peer->mclt < cur_time)));
+
+ default:
+ /* All lease states appear above. */
+ log_fatal("Impossible case at %s:%d.", MDL);
+ break;
+ }
+ return 0;
+ }
+ if (lease)
+ return(lease->binding_state == FTS_FREE ||
+ lease->binding_state == FTS_BACKUP);
+ else
+ return 0;
+}
+
+static isc_result_t failover_message_reference (failover_message_t **mp,
+ failover_message_t *m,
+ const char *file, int line)
+{
+ *mp = m;
+ m -> refcnt++;
+ return ISC_R_SUCCESS;
+}
+
+static isc_result_t failover_message_dereference (failover_message_t **mp,
+ const char *file, int line)
+{
+ failover_message_t *m;
+ m = (*mp);
+ m -> refcnt--;
+ if (m -> refcnt == 0) {
+ if (m -> next)
+ failover_message_dereference (&m -> next,
+ file, line);
+ if (m -> chaddr.data)
+ dfree (m -> chaddr.data, file, line);
+ if (m -> client_identifier.data)
+ dfree (m -> client_identifier.data, file, line);
+ if (m -> hba.data)
+ dfree (m -> hba.data, file, line);
+ if (m -> message.data)
+ dfree (m -> message.data, file, line);
+ if (m -> relationship_name.data)
+ dfree (m -> relationship_name.data, file, line);
+ if (m -> reply_options.data)
+ dfree (m -> reply_options.data, file, line);
+ if (m -> request_options.data)
+ dfree (m -> request_options.data, file, line);
+ if (m -> vendor_class.data)
+ dfree (m -> vendor_class.data, file, line);
+ if (m -> vendor_options.data)
+ dfree (m -> vendor_options.data, file, line);
+ if (m -> ddns.data)
+ dfree (m -> ddns.data, file, line);
+ dfree (*mp, file, line);
+ }
+ *mp = 0;
+ return ISC_R_SUCCESS;
+}
+
+OMAPI_OBJECT_ALLOC (dhcp_failover_state, dhcp_failover_state_t,
+ dhcp_type_failover_state)
+OMAPI_OBJECT_ALLOC (dhcp_failover_listener, dhcp_failover_listener_t,
+ dhcp_type_failover_listener)
+OMAPI_OBJECT_ALLOC (dhcp_failover_link, dhcp_failover_link_t,
+ dhcp_type_failover_link)
+#endif /* defined (FAILOVER_PROTOCOL) */
+
+const char *binding_state_print (enum failover_state state)
+{
+ switch (state) {
+ case FTS_FREE:
+ return "free";
+ break;
+
+ case FTS_ACTIVE:
+ return "active";
+ break;
+
+ case FTS_EXPIRED:
+ return "expired";
+ break;
+
+ case FTS_RELEASED:
+ return "released";
+ break;
+
+ case FTS_ABANDONED:
+ return "abandoned";
+ break;
+
+ case FTS_RESET:
+ return "reset";
+ break;
+
+ case FTS_BACKUP:
+ return "backup";
+ break;
+
+ default:
+ return "unknown";
+ break;
+ }
+}
diff --git a/server/ldap.c b/server/ldap.c
new file mode 100644
index 0000000..8a7d695
--- /dev/null
+++ b/server/ldap.c
@@ -0,0 +1,2004 @@
+/* ldap.c
+
+ Routines for reading the configuration from LDAP */
+
+/*
+ * Copyright (c) 2003-2006 Ntelos, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of The Internet Software Consortium nor the names
+ * of its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
+ * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This LDAP module was written by Brian Masney <masneyb@ntelos.net>. Its
+ * development was sponsored by Ntelos, Inc. (www.ntelos.com).
+ */
+
+#include "dhcpd.h"
+#include <signal.h>
+#include <errno.h>
+
+#if defined(LDAP_CONFIGURATION)
+
+#if defined(LDAP_CASA_AUTH)
+#include "ldap_casa.h"
+#endif
+
+static LDAP * ld = NULL;
+static char *ldap_server = NULL,
+ *ldap_username = NULL,
+ *ldap_password = NULL,
+ *ldap_base_dn = NULL,
+ *ldap_dhcp_server_cn = NULL,
+ *ldap_debug_file = NULL;
+static int ldap_port = LDAP_PORT,
+ ldap_method = LDAP_METHOD_DYNAMIC,
+ ldap_referrals = -1,
+ ldap_debug_fd = -1;
+#if defined (LDAP_USE_SSL)
+static int ldap_use_ssl = -1, /* try TLS if possible */
+ ldap_tls_reqcert = -1,
+ ldap_tls_crlcheck = -1;
+static char *ldap_tls_ca_file = NULL,
+ *ldap_tls_ca_dir = NULL,
+ *ldap_tls_cert = NULL,
+ *ldap_tls_key = NULL,
+ *ldap_tls_ciphers = NULL,
+ *ldap_tls_randfile = NULL;
+#endif
+static struct ldap_config_stack *ldap_stack = NULL;
+
+typedef struct ldap_dn_node {
+ struct ldap_dn_node *next;
+ size_t refs;
+ char *dn;
+} ldap_dn_node;
+
+static ldap_dn_node *ldap_service_dn_head = NULL;
+static ldap_dn_node *ldap_service_dn_tail = NULL;
+
+
+static char *
+x_strncat(char *dst, const char *src, size_t dst_size)
+{
+ size_t len = strlen(dst);
+ return strncat(dst, src, dst_size > len ? dst_size - len - 1: 0);
+}
+
+static void
+ldap_parse_class (struct ldap_config_stack *item, struct parse *cfile)
+{
+ struct berval **tempbv;
+
+ if ((tempbv = ldap_get_values_len (ld, item->ldent, "cn")) == NULL ||
+ tempbv[0] == NULL)
+ {
+ if (tempbv != NULL)
+ ldap_value_free_len (tempbv);
+
+ return;
+ }
+
+ x_strncat (cfile->inbuf, "class \"", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, tempbv[0]->bv_val, LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, "\" {\n", LDAP_BUFFER_SIZE);
+
+ item->close_brace = 1;
+ ldap_value_free_len (tempbv);
+}
+
+
+static void
+ldap_parse_subclass (struct ldap_config_stack *item, struct parse *cfile)
+{
+ struct berval **tempbv, **classdata;
+
+ if ((tempbv = ldap_get_values_len (ld, item->ldent, "cn")) == NULL ||
+ tempbv[0] == NULL)
+ {
+ if (tempbv != NULL)
+ ldap_value_free_len (tempbv);
+
+ return;
+ }
+
+ if ((classdata = ldap_get_values_len (ld, item->ldent,
+ "dhcpClassData")) == NULL ||
+ classdata[0] == NULL)
+ {
+ if (classdata != NULL)
+ ldap_value_free_len (classdata);
+ ldap_value_free_len (tempbv);
+
+ return;
+ }
+
+ x_strncat (cfile->inbuf, "subclass ", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, classdata[0]->bv_val, LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, " ", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, tempbv[0]->bv_val, LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, " {\n", LDAP_BUFFER_SIZE);
+
+ item->close_brace = 1;
+ ldap_value_free_len (tempbv);
+ ldap_value_free_len (classdata);
+}
+
+
+static void
+ldap_parse_host (struct ldap_config_stack *item, struct parse *cfile)
+{
+ struct berval **tempbv, **hwaddr;
+
+ if ((tempbv = ldap_get_values_len (ld, item->ldent, "cn")) == NULL ||
+ tempbv[0] == NULL)
+ {
+ if (tempbv != NULL)
+ ldap_value_free_len (tempbv);
+
+ return;
+ }
+
+ hwaddr = ldap_get_values_len (ld, item->ldent, "dhcpHWAddress");
+
+ x_strncat (cfile->inbuf, "host ", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, tempbv[0]->bv_val, LDAP_BUFFER_SIZE);
+
+ if (hwaddr != NULL && hwaddr[0] != NULL)
+ {
+ x_strncat (cfile->inbuf, " {\nhardware ", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, hwaddr[0]->bv_val, LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, ";\n", LDAP_BUFFER_SIZE);
+ ldap_value_free_len (hwaddr);
+ }
+
+ item->close_brace = 1;
+ ldap_value_free_len (tempbv);
+}
+
+
+static void
+ldap_parse_shared_network (struct ldap_config_stack *item, struct parse *cfile)
+{
+ struct berval **tempbv;
+
+ if ((tempbv = ldap_get_values_len (ld, item->ldent, "cn")) == NULL ||
+ tempbv[0] == NULL)
+ {
+ if (tempbv != NULL)
+ ldap_value_free_len (tempbv);
+
+ return;
+ }
+
+ x_strncat (cfile->inbuf, "shared-network \"", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, tempbv[0]->bv_val, LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, "\" {\n", LDAP_BUFFER_SIZE);
+
+ item->close_brace = 1;
+ ldap_value_free_len (tempbv);
+}
+
+
+static void
+parse_netmask (int netmask, char *netmaskbuf)
+{
+ unsigned long nm;
+ int i;
+
+ nm = 0;
+ for (i=1; i <= netmask; i++)
+ {
+ nm |= 1 << (32 - i);
+ }
+
+ sprintf (netmaskbuf, "%d.%d.%d.%d", (int) (nm >> 24) & 0xff,
+ (int) (nm >> 16) & 0xff,
+ (int) (nm >> 8) & 0xff,
+ (int) nm & 0xff);
+}
+
+
+static void
+ldap_parse_subnet (struct ldap_config_stack *item, struct parse *cfile)
+{
+ struct berval **tempbv, **netmaskstr;
+ char netmaskbuf[sizeof("255.255.255.255")];
+ int i;
+
+ if ((tempbv = ldap_get_values_len (ld, item->ldent, "cn")) == NULL ||
+ tempbv[0] == NULL)
+ {
+ if (tempbv != NULL)
+ ldap_value_free_len (tempbv);
+
+ return;
+ }
+
+ if ((netmaskstr = ldap_get_values_len (ld, item->ldent,
+ "dhcpNetmask")) == NULL ||
+ netmaskstr[0] == NULL)
+ {
+ if (netmaskstr != NULL)
+ ldap_value_free_len (netmaskstr);
+ ldap_value_free_len (tempbv);
+
+ return;
+ }
+
+ x_strncat (cfile->inbuf, "subnet ", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, tempbv[0]->bv_val, LDAP_BUFFER_SIZE);
+
+ x_strncat (cfile->inbuf, " netmask ", LDAP_BUFFER_SIZE);
+ parse_netmask (strtol (netmaskstr[0]->bv_val, NULL, 10), netmaskbuf);
+ x_strncat (cfile->inbuf, netmaskbuf, LDAP_BUFFER_SIZE);
+
+ x_strncat (cfile->inbuf, " {\n", LDAP_BUFFER_SIZE);
+
+ ldap_value_free_len (tempbv);
+ ldap_value_free_len (netmaskstr);
+
+ if ((tempbv = ldap_get_values_len (ld, item->ldent, "dhcpRange")) != NULL)
+ {
+ for (i=0; tempbv[i] != NULL; i++)
+ {
+ x_strncat (cfile->inbuf, "range", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, " ", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, tempbv[i]->bv_val, LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, ";\n", LDAP_BUFFER_SIZE);
+ }
+ }
+
+ item->close_brace = 1;
+}
+
+
+static void
+ldap_parse_pool (struct ldap_config_stack *item, struct parse *cfile)
+{
+ struct berval **tempbv;
+ int i;
+
+ x_strncat (cfile->inbuf, "pool {\n", LDAP_BUFFER_SIZE);
+
+ if ((tempbv = ldap_get_values_len (ld, item->ldent, "dhcpRange")) != NULL)
+ {
+ x_strncat (cfile->inbuf, "range", LDAP_BUFFER_SIZE);
+ for (i=0; tempbv[i] != NULL; i++)
+ {
+ x_strncat (cfile->inbuf, " ", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, tempbv[i]->bv_val, LDAP_BUFFER_SIZE);
+ }
+ x_strncat (cfile->inbuf, ";\n", LDAP_BUFFER_SIZE);
+ ldap_value_free_len (tempbv);
+ }
+
+ if ((tempbv = ldap_get_values_len (ld, item->ldent, "dhcpPermitList")) != NULL)
+ {
+ for (i=0; tempbv[i] != NULL; i++)
+ {
+ x_strncat (cfile->inbuf, tempbv[i]->bv_val, LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, ";\n", LDAP_BUFFER_SIZE);
+ }
+ ldap_value_free_len (tempbv);
+ }
+
+ item->close_brace = 1;
+}
+
+
+static void
+ldap_parse_group (struct ldap_config_stack *item, struct parse *cfile)
+{
+ x_strncat (cfile->inbuf, "group {\n", LDAP_BUFFER_SIZE);
+ item->close_brace = 1;
+}
+
+
+static void
+ldap_parse_key (struct ldap_config_stack *item, struct parse *cfile)
+{
+ struct berval **tempbv;
+
+ if ((tempbv = ldap_get_values_len (ld, item->ldent, "cn")) != NULL)
+ {
+ x_strncat (cfile->inbuf, "key ", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, tempbv[0]->bv_val, LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, " {\n", LDAP_BUFFER_SIZE);
+ ldap_value_free_len (tempbv);
+ }
+
+ if ((tempbv = ldap_get_values_len (ld, item->ldent, "dhcpKeyAlgorithm")) != NULL)
+ {
+ x_strncat (cfile->inbuf, "algorithm ", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, tempbv[0]->bv_val, LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, ";\n", LDAP_BUFFER_SIZE);
+ ldap_value_free_len (tempbv);
+ }
+
+ if ((tempbv = ldap_get_values_len (ld, item->ldent, "dhcpKeySecret")) != NULL)
+ {
+ x_strncat (cfile->inbuf, "secret ", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, tempbv[0]->bv_val, LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, ";\n", LDAP_BUFFER_SIZE);
+ ldap_value_free_len (tempbv);
+ }
+
+ item->close_brace = 1;
+}
+
+
+static void
+ldap_parse_zone (struct ldap_config_stack *item, struct parse *cfile)
+{
+ char *cnFindStart, *cnFindEnd;
+ struct berval **tempbv;
+ char *keyCn;
+ size_t len;
+
+ if ((tempbv = ldap_get_values_len (ld, item->ldent, "cn")) != NULL)
+ {
+ x_strncat (cfile->inbuf, "zone ", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, tempbv[0]->bv_val, LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, " {\n", LDAP_BUFFER_SIZE);
+ ldap_value_free_len (tempbv);
+ }
+
+ if ((tempbv = ldap_get_values_len (ld, item->ldent, "dhcpDnsZoneServer")) != NULL)
+ {
+ x_strncat (cfile->inbuf, "primary ", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, tempbv[0]->bv_val, LDAP_BUFFER_SIZE);
+
+ x_strncat (cfile->inbuf, ";\n", LDAP_BUFFER_SIZE);
+ ldap_value_free_len (tempbv);
+ }
+
+ if ((tempbv = ldap_get_values_len (ld, item->ldent, "dhcpKeyDN")) != NULL)
+ {
+ cnFindStart = strchr(tempbv[0]->bv_val,'=');
+ if (cnFindStart != NULL)
+ cnFindEnd = strchr(++cnFindStart,',');
+ else
+ cnFindEnd = NULL;
+
+ if (cnFindEnd != NULL && cnFindEnd > cnFindStart)
+ {
+ len = cnFindEnd - cnFindStart;
+ keyCn = dmalloc (len + 1, MDL);
+ }
+ else
+ {
+ len = 0;
+ keyCn = NULL;
+ }
+
+ if (keyCn != NULL)
+ {
+ strncpy (keyCn, cnFindStart, len);
+ keyCn[len] = '\0';
+
+ x_strncat (cfile->inbuf, "key ", LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, keyCn, LDAP_BUFFER_SIZE);
+ x_strncat (cfile->inbuf, ";\n", LDAP_BUFFER_SIZE);
+
+ dfree (keyCn, MDL);
+ }
+
+ ldap_value_free_len (tempbv);
+ }
+
+ item->close_brace = 1;
+}
+
+
+static void
+add_to_config_stack (LDAPMessage * res, LDAPMessage * ent)
+{
+ struct ldap_config_stack *ns;
+
+ ns = dmalloc (sizeof (*ns), MDL);
+ ns->res = res;
+ ns->ldent = ent;
+ ns->close_brace = 0;
+ ns->processed = 0;
+ ns->next = ldap_stack;
+ ldap_stack = ns;
+}
+
+
+static void
+ldap_stop()
+{
+ struct sigaction old, new;
+
+ if (ld == NULL)
+ return;
+
+ /*
+ ** ldap_unbind after a LDAP_SERVER_DOWN result
+ ** causes a SIGPIPE and dhcpd gets terminated,
+ ** since it doesn't handle it...
+ */
+
+ new.sa_flags = 0;
+ new.sa_handler = SIG_IGN;
+ sigemptyset (&new.sa_mask);
+ sigaction (SIGPIPE, &new, &old);
+
+ ldap_unbind_ext_s (ld, NULL, NULL);
+ ld = NULL;
+
+ sigaction (SIGPIPE, &old, &new);
+}
+
+
+static char *
+_do_lookup_dhcp_string_option (struct option_state *options, int option_name)
+{
+ struct option_cache *oc;
+ struct data_string db;
+ char *ret;
+
+ memset (&db, 0, sizeof (db));
+ oc = lookup_option (&server_universe, options, option_name);
+ if (oc &&
+ evaluate_option_cache (&db, (struct packet*) NULL,
+ (struct lease *) NULL,
+ (struct client_state *) NULL, options,
+ (struct option_state *) NULL,
+ &global_scope, oc, MDL) &&
+ db.data != NULL && *db.data != '\0')
+
+ {
+ ret = dmalloc (db.len + 1, MDL);
+ if (ret == NULL)
+ log_fatal ("no memory for ldap option %d value", option_name);
+
+ memcpy (ret, db.data, db.len);
+ ret[db.len] = 0;
+ data_string_forget (&db, MDL);
+ }
+ else
+ ret = NULL;
+
+ return (ret);
+}
+
+
+static int
+_do_lookup_dhcp_int_option (struct option_state *options, int option_name)
+{
+ struct option_cache *oc;
+ struct data_string db;
+ int ret;
+
+ memset (&db, 0, sizeof (db));
+ oc = lookup_option (&server_universe, options, option_name);
+ if (oc &&
+ evaluate_option_cache (&db, (struct packet*) NULL,
+ (struct lease *) NULL,
+ (struct client_state *) NULL, options,
+ (struct option_state *) NULL,
+ &global_scope, oc, MDL) &&
+ db.data != NULL && *db.data != '\0')
+ {
+ ret = strtol ((const char *) db.data, NULL, 10);
+ data_string_forget (&db, MDL);
+ }
+ else
+ ret = 0;
+
+ return (ret);
+}
+
+
+static int
+_do_lookup_dhcp_enum_option (struct option_state *options, int option_name)
+{
+ struct option_cache *oc;
+ struct data_string db;
+ int ret = -1;
+
+ memset (&db, 0, sizeof (db));
+ oc = lookup_option (&server_universe, options, option_name);
+ if (oc &&
+ evaluate_option_cache (&db, (struct packet*) NULL,
+ (struct lease *) NULL,
+ (struct client_state *) NULL, options,
+ (struct option_state *) NULL,
+ &global_scope, oc, MDL) &&
+ db.data != NULL && *db.data != '\0')
+ {
+ if (db.len == 1)
+ ret = db.data [0];
+ else
+ log_fatal ("invalid option name %d", option_name);
+
+ data_string_forget (&db, MDL);
+ }
+ else
+ ret = 0;
+
+ return (ret);
+}
+
+int
+ldap_rebind_cb (LDAP *ld, LDAP_CONST char *url, ber_tag_t request, ber_int_t msgid, void *parms)
+{
+ int ret;
+ LDAPURLDesc *ldapurl = NULL;
+ char *who = NULL;
+ struct berval creds;
+
+ log_info("LDAP rebind to '%s'", url);
+ if ((ret = ldap_url_parse(url, &ldapurl)) != LDAP_SUCCESS)
+ {
+ log_error ("Error: Can not parse ldap rebind url '%s': %s",
+ url, ldap_err2string(ret));
+ return ret;
+ }
+
+
+#if defined (LDAP_USE_SSL)
+ if (strcasecmp(ldapurl->lud_scheme, "ldaps") == 0)
+ {
+ int opt = LDAP_OPT_X_TLS_HARD;
+ if ((ret = ldap_set_option (ld, LDAP_OPT_X_TLS, &opt)) != LDAP_SUCCESS)
+ {
+ log_error ("Error: Cannot init LDAPS session to %s:%d: %s",
+ ldapurl->lud_host, ldapurl->lud_port, ldap_err2string (ret));
+ return ret;
+ }
+ else
+ {
+ log_info ("LDAPS session successfully enabled to %s", ldap_server);
+ }
+ }
+ else
+ if (strcasecmp(ldapurl->lud_scheme, "ldap") == 0 &&
+ ldap_use_ssl != LDAP_SSL_OFF)
+ {
+ if ((ret = ldap_start_tls_s (ld, NULL, NULL)) != LDAP_SUCCESS)
+ {
+ log_error ("Error: Cannot start TLS session to %s:%d: %s",
+ ldapurl->lud_host, ldapurl->lud_port, ldap_err2string (ret));
+ return ret;
+ }
+ else
+ {
+ log_info ("TLS session successfully started to %s:%d",
+ ldapurl->lud_host, ldapurl->lud_port);
+ }
+ }
+#endif
+
+
+ if (ldap_username != NULL || *ldap_username != '\0')
+ {
+ who = ldap_username;
+ creds.bv_val = strdup(ldap_password);
+ creds.bv_len = strlen(ldap_password);
+ }
+
+ if ((ret = ldap_sasl_bind_s (ld, who, LDAP_SASL_SIMPLE, &creds,
+ NULL, NULL, NULL)) != LDAP_SUCCESS)
+ {
+ log_error ("Error: Cannot login into ldap server %s:%d: %s",
+ ldapurl->lud_host, ldapurl->lud_port, ldap_err2string (ret));
+ }
+ return ret;
+}
+
+static void
+ldap_start (void)
+{
+ struct option_state *options;
+ int ret, version;
+ char *uri = NULL;
+ struct berval creds;
+
+ if (ld != NULL)
+ return;
+
+ if (ldap_server == NULL)
+ {
+ options = NULL;
+ option_state_allocate (&options, MDL);
+
+ execute_statements_in_scope ((struct binding_value **) NULL,
+ (struct packet *) NULL, (struct lease *) NULL,
+ (struct client_state *) NULL, (struct option_state *) NULL,
+ options, &global_scope, root_group, (struct group *) NULL);
+
+ ldap_server = _do_lookup_dhcp_string_option (options, SV_LDAP_SERVER);
+ ldap_dhcp_server_cn = _do_lookup_dhcp_string_option (options,
+ SV_LDAP_DHCP_SERVER_CN);
+ ldap_port = _do_lookup_dhcp_int_option (options, SV_LDAP_PORT);
+ ldap_base_dn = _do_lookup_dhcp_string_option (options, SV_LDAP_BASE_DN);
+ ldap_method = _do_lookup_dhcp_enum_option (options, SV_LDAP_METHOD);
+ ldap_debug_file = _do_lookup_dhcp_string_option (options,
+ SV_LDAP_DEBUG_FILE);
+ ldap_referrals = _do_lookup_dhcp_enum_option (options, SV_LDAP_REFERRALS);
+
+#if defined (LDAP_USE_SSL)
+ ldap_use_ssl = _do_lookup_dhcp_enum_option (options, SV_LDAP_SSL);
+ if( ldap_use_ssl != LDAP_SSL_OFF)
+ {
+ ldap_tls_reqcert = _do_lookup_dhcp_enum_option (options, SV_LDAP_TLS_REQCERT);
+ ldap_tls_ca_file = _do_lookup_dhcp_string_option (options, SV_LDAP_TLS_CA_FILE);
+ ldap_tls_ca_dir = _do_lookup_dhcp_string_option (options, SV_LDAP_TLS_CA_DIR);
+ ldap_tls_cert = _do_lookup_dhcp_string_option (options, SV_LDAP_TLS_CERT);
+ ldap_tls_key = _do_lookup_dhcp_string_option (options, SV_LDAP_TLS_KEY);
+ ldap_tls_crlcheck = _do_lookup_dhcp_enum_option (options, SV_LDAP_TLS_CRLCHECK);
+ ldap_tls_ciphers = _do_lookup_dhcp_string_option (options, SV_LDAP_TLS_CIPHERS);
+ ldap_tls_randfile = _do_lookup_dhcp_string_option (options, SV_LDAP_TLS_RANDFILE);
+ }
+#endif
+
+#if defined (LDAP_CASA_AUTH)
+ if (!load_uname_pwd_from_miCASA(&ldap_username,&ldap_password))
+ {
+#if defined (DEBUG_LDAP)
+ log_info ("Authentication credential taken from file");
+#endif
+#endif
+
+ ldap_username = _do_lookup_dhcp_string_option (options, SV_LDAP_USERNAME);
+ ldap_password = _do_lookup_dhcp_string_option (options, SV_LDAP_PASSWORD);
+
+#if defined (LDAP_CASA_AUTH)
+ }
+#endif
+
+ option_state_dereference (&options, MDL);
+ }
+
+ if (ldap_server == NULL || ldap_base_dn == NULL)
+ {
+ log_info ("Not searching LDAP since ldap-server, ldap-port and ldap-base-dn were not specified in the config file");
+ ldap_method = LDAP_METHOD_STATIC;
+ return;
+ }
+
+ if (ldap_debug_file != NULL && ldap_debug_fd == -1)
+ {
+ if ((ldap_debug_fd = open (ldap_debug_file, O_CREAT | O_TRUNC | O_WRONLY,
+ S_IRUSR | S_IWUSR)) < 0)
+ log_error ("Error opening debug LDAP log file %s: %s", ldap_debug_file,
+ strerror (errno));
+ }
+
+#if defined (DEBUG_LDAP)
+ log_info ("Connecting to LDAP server %s:%d", ldap_server, ldap_port);
+#endif
+
+#if defined (LDAP_USE_SSL)
+ if (ldap_use_ssl == -1)
+ {
+ /*
+ ** There was no "ldap-ssl" option in dhcpd.conf (also not "off").
+ ** Let's try, if we can use an anonymous TLS session without to
+ ** verify the server certificate -- if not continue without TLS.
+ */
+ int opt = LDAP_OPT_X_TLS_ALLOW;
+ if ((ret = ldap_set_option (NULL, LDAP_OPT_X_TLS_REQUIRE_CERT,
+ &opt)) != LDAP_SUCCESS)
+ {
+ log_error ("Warning: Cannot set LDAP TLS require cert option to 'allow': %s",
+ ldap_err2string (ret));
+ }
+ }
+
+ if (ldap_use_ssl != LDAP_SSL_OFF)
+ {
+ if (ldap_tls_reqcert != -1)
+ {
+ if ((ret = ldap_set_option (NULL, LDAP_OPT_X_TLS_REQUIRE_CERT,
+ &ldap_tls_reqcert)) != LDAP_SUCCESS)
+ {
+ log_error ("Cannot set LDAP TLS require cert option: %s",
+ ldap_err2string (ret));
+ }
+ }
+
+ if( ldap_tls_ca_file != NULL)
+ {
+ if ((ret = ldap_set_option (NULL, LDAP_OPT_X_TLS_CACERTFILE,
+ ldap_tls_ca_file)) != LDAP_SUCCESS)
+ {
+ log_error ("Cannot set LDAP TLS CA certificate file %s: %s",
+ ldap_tls_ca_file, ldap_err2string (ret));
+ }
+ }
+ if( ldap_tls_ca_dir != NULL)
+ {
+ if ((ret = ldap_set_option (NULL, LDAP_OPT_X_TLS_CACERTDIR,
+ ldap_tls_ca_dir)) != LDAP_SUCCESS)
+ {
+ log_error ("Cannot set LDAP TLS CA certificate dir %s: %s",
+ ldap_tls_ca_dir, ldap_err2string (ret));
+ }
+ }
+ if( ldap_tls_cert != NULL)
+ {
+ if ((ret = ldap_set_option (NULL, LDAP_OPT_X_TLS_CERTFILE,
+ ldap_tls_cert)) != LDAP_SUCCESS)
+ {
+ log_error ("Cannot set LDAP TLS client certificate file %s: %s",
+ ldap_tls_cert, ldap_err2string (ret));
+ }
+ }
+ if( ldap_tls_key != NULL)
+ {
+ if ((ret = ldap_set_option (NULL, LDAP_OPT_X_TLS_KEYFILE,
+ ldap_tls_key)) != LDAP_SUCCESS)
+ {
+ log_error ("Cannot set LDAP TLS certificate key file %s: %s",
+ ldap_tls_key, ldap_err2string (ret));
+ }
+ }
+ if( ldap_tls_crlcheck != -1)
+ {
+ int opt = ldap_tls_crlcheck;
+ if ((ret = ldap_set_option (NULL, LDAP_OPT_X_TLS_CRLCHECK,
+ &opt)) != LDAP_SUCCESS)
+ {
+ log_error ("Cannot set LDAP TLS crl check option: %s",
+ ldap_err2string (ret));
+ }
+ }
+ if( ldap_tls_ciphers != NULL)
+ {
+ if ((ret = ldap_set_option (NULL, LDAP_OPT_X_TLS_CIPHER_SUITE,
+ ldap_tls_ciphers)) != LDAP_SUCCESS)
+ {
+ log_error ("Cannot set LDAP TLS cipher suite %s: %s",
+ ldap_tls_ciphers, ldap_err2string (ret));
+ }
+ }
+ if( ldap_tls_randfile != NULL)
+ {
+ if ((ret = ldap_set_option (NULL, LDAP_OPT_X_TLS_RANDOM_FILE,
+ ldap_tls_randfile)) != LDAP_SUCCESS)
+ {
+ log_error ("Cannot set LDAP TLS random file %s: %s",
+ ldap_tls_randfile, ldap_err2string (ret));
+ }
+ }
+ }
+#endif
+
+ /* enough for 'ldap://+ + hostname + ':' + port number */
+ uri = malloc(strlen(ldap_server) + 16);
+ if (uri == NULL)
+ {
+ log_error ("Cannot build ldap init URI %s:%d", ldap_server, ldap_port);
+ return;
+ }
+
+ sprintf(uri, "ldap://%s:%d", ldap_server, ldap_port);
+ ldap_initialize(&ld, uri);
+
+ if (ld == NULL)
+ {
+ log_error ("Cannot init ldap session to %s:%d", ldap_server, ldap_port);
+ return;
+ }
+
+ free(uri);
+
+ version = LDAP_VERSION3;
+ if ((ret = ldap_set_option (ld, LDAP_OPT_PROTOCOL_VERSION, &version)) != LDAP_OPT_SUCCESS)
+ {
+ log_error ("Cannot set LDAP version to %d: %s", version,
+ ldap_err2string (ret));
+ }
+
+ if (ldap_referrals != -1)
+ {
+ if ((ret = ldap_set_option (ld, LDAP_OPT_REFERRALS, ldap_referrals ?
+ LDAP_OPT_ON : LDAP_OPT_OFF)) != LDAP_OPT_SUCCESS)
+ {
+ log_error ("Cannot %s LDAP referrals option: %s",
+ (ldap_referrals ? "enable" : "disable"),
+ ldap_err2string (ret));
+ }
+ }
+
+ if ((ret = ldap_set_rebind_proc(ld, ldap_rebind_cb, NULL)) != LDAP_SUCCESS)
+ {
+ log_error ("Warning: Cannot set ldap rebind procedure: %s",
+ ldap_err2string (ret));
+ }
+
+#if defined (LDAP_USE_SSL)
+ if (ldap_use_ssl == LDAP_SSL_LDAPS ||
+ (ldap_use_ssl == LDAP_SSL_ON && ldap_port == LDAPS_PORT))
+ {
+ int opt = LDAP_OPT_X_TLS_HARD;
+ if ((ret = ldap_set_option (ld, LDAP_OPT_X_TLS, &opt)) != LDAP_SUCCESS)
+ {
+ log_error ("Error: Cannot init LDAPS session to %s:%d: %s",
+ ldap_server, ldap_port, ldap_err2string (ret));
+ ldap_stop();
+ return;
+ }
+ else
+ {
+ log_info ("LDAPS session successfully enabled to %s:%d",
+ ldap_server, ldap_port);
+ }
+ }
+ else if (ldap_use_ssl != LDAP_SSL_OFF)
+ {
+ if ((ret = ldap_start_tls_s (ld, NULL, NULL)) != LDAP_SUCCESS)
+ {
+ log_error ("Error: Cannot start TLS session to %s:%d: %s",
+ ldap_server, ldap_port, ldap_err2string (ret));
+ ldap_stop();
+ return;
+ }
+ else
+ {
+ log_info ("TLS session successfully started to %s:%d",
+ ldap_server, ldap_port);
+ }
+ }
+#endif
+
+ if (ldap_username != NULL && *ldap_username != '\0')
+ {
+ creds.bv_val = strdup(ldap_password);
+ creds.bv_len = strlen(ldap_password);
+
+ if ((ret = ldap_sasl_bind_s (ld, ldap_username, LDAP_SASL_SIMPLE,
+ &creds, NULL, NULL, NULL)) != LDAP_SUCCESS)
+ {
+ log_error ("Error: Cannot login into ldap server %s:%d: %s",
+ ldap_server, ldap_port, ldap_err2string (ret));
+ ldap_stop();
+ return;
+ }
+ }
+
+#if defined (DEBUG_LDAP)
+ log_info ("Successfully logged into LDAP server %s", ldap_server);
+#endif
+}
+
+
+static void
+parse_external_dns (LDAPMessage * ent)
+{
+ char *search[] = {"dhcpOptionsDN", "dhcpSharedNetworkDN", "dhcpSubnetDN",
+ "dhcpGroupDN", "dhcpHostDN", "dhcpClassesDN",
+ "dhcpPoolDN", NULL};
+ LDAPMessage * newres, * newent;
+ struct berval **tempbv;
+ int i, j, ret;
+#if defined (DEBUG_LDAP)
+ char *dn;
+
+ dn = ldap_get_dn (ld, ent);
+ if (dn != NULL)
+ {
+ log_info ("Parsing external DNs for '%s'", dn);
+ ldap_memfree (dn);
+ }
+#endif
+
+ if (ld == NULL)
+ ldap_start ();
+ if (ld == NULL)
+ return;
+
+ for (i=0; search[i] != NULL; i++)
+ {
+ if ((tempbv = ldap_get_values_len (ld, ent, search[i])) == NULL)
+ continue;
+
+ for (j=0; tempbv[j] != NULL; j++)
+ {
+ if (*tempbv[j]->bv_val == '\0')
+ continue;
+
+ if ((ret = ldap_search_ext_s(ld, tempbv[j]->bv_val, LDAP_SCOPE_BASE,
+ "objectClass=*", NULL, 0, NULL,
+ NULL, NULL, 0, &newres)) != LDAP_SUCCESS)
+ {
+ ldap_value_free_len (tempbv);
+ ldap_stop();
+ return;
+ }
+
+#if defined (DEBUG_LDAP)
+ log_info ("Adding contents of subtree '%s' to config stack from '%s' reference", tempbv[j], search[i]);
+#endif
+ for (newent = ldap_first_entry (ld, newres);
+ newent != NULL;
+ newent = ldap_next_entry (ld, newent))
+ {
+#if defined (DEBUG_LDAP)
+ dn = ldap_get_dn (ld, newent);
+ if (dn != NULL)
+ {
+ log_info ("Adding LDAP result set starting with '%s' to config stack", dn);
+ ldap_memfree (dn);
+ }
+#endif
+
+ add_to_config_stack (newres, newent);
+ /* don't free newres here */
+ }
+ }
+
+ ldap_value_free_len (tempbv);
+ }
+}
+
+
+static void
+free_stack_entry (struct ldap_config_stack *item)
+{
+ struct ldap_config_stack *look_ahead_pointer = item;
+ int may_free_msg = 1;
+
+ while (look_ahead_pointer->next != NULL)
+ {
+ look_ahead_pointer = look_ahead_pointer->next;
+ if (look_ahead_pointer->res == item->res)
+ {
+ may_free_msg = 0;
+ break;
+ }
+ }
+
+ if (may_free_msg)
+ ldap_msgfree (item->res);
+
+ dfree (item, MDL);
+}
+
+
+static void
+next_ldap_entry (struct parse *cfile)
+{
+ struct ldap_config_stack *temp_stack;
+
+ if (ldap_stack != NULL && ldap_stack->close_brace)
+ {
+ x_strncat (cfile->inbuf, "}\n", LDAP_BUFFER_SIZE);
+ ldap_stack->close_brace = 0;
+ }
+
+ while (ldap_stack != NULL &&
+ (ldap_stack->ldent == NULL ||
+ (ldap_stack->ldent = ldap_next_entry (ld, ldap_stack->ldent)) == NULL))
+ {
+ if (ldap_stack->close_brace)
+ {
+ x_strncat (cfile->inbuf, "}\n", LDAP_BUFFER_SIZE);
+ ldap_stack->close_brace = 0;
+ }
+
+ temp_stack = ldap_stack;
+ ldap_stack = ldap_stack->next;
+ free_stack_entry (temp_stack);
+ }
+
+ if (ldap_stack != NULL && ldap_stack->close_brace)
+ {
+ x_strncat (cfile->inbuf, "}\n", LDAP_BUFFER_SIZE);
+ ldap_stack->close_brace = 0;
+ }
+}
+
+
+static char
+check_statement_end (const char *statement)
+{
+ char *ptr;
+
+ if (statement == NULL || *statement == '\0')
+ return ('\0');
+
+ /*
+ ** check if it ends with "}", e.g.:
+ ** "zone my.domain. { ... }"
+ ** optionally followed by spaces
+ */
+ ptr = strrchr (statement, '}');
+ if (ptr != NULL)
+ {
+ /* skip following white-spaces */
+ for (++ptr; isspace ((int)*ptr); ptr++);
+
+ /* check if we reached the end */
+ if (*ptr == '\0')
+ return ('}'); /* yes, block end */
+ else
+ return (*ptr);
+ }
+
+ /*
+ ** this should not happen, but...
+ ** check if it ends with ";", e.g.:
+ ** "authoritative;"
+ ** optionally followed by spaces
+ */
+ ptr = strrchr (statement, ';');
+ if (ptr != NULL)
+ {
+ /* skip following white-spaces */
+ for (++ptr; isspace ((int)*ptr); ptr++);
+
+ /* check if we reached the end */
+ if (*ptr == '\0')
+ return (';'); /* ends with a ; */
+ else
+ return (*ptr);
+ }
+
+ return ('\0');
+}
+
+
+static isc_result_t
+ldap_parse_entry_options (LDAPMessage *ent, char *buffer, size_t size,
+ int *lease_limit)
+{
+ struct berval **tempbv;
+ int i;
+
+ if (ent == NULL || buffer == NULL || size == 0)
+ return (ISC_R_FAILURE);
+
+ if ((tempbv = ldap_get_values_len (ld, ent, "dhcpStatements")) != NULL)
+ {
+ for (i=0; tempbv[i] != NULL; i++)
+ {
+ if (lease_limit != NULL &&
+ strncasecmp ("lease limit ", tempbv[i]->bv_val, 12) == 0)
+ {
+ *lease_limit = (int) strtol ((tempbv[i]->bv_val) + 12, NULL, 10);
+ continue;
+ }
+
+ x_strncat (buffer, tempbv[i]->bv_val, size);
+
+ switch((int) check_statement_end (tempbv[i]->bv_val))
+ {
+ case '}':
+ case ';':
+ x_strncat (buffer, "\n", size);
+ break;
+ default:
+ x_strncat (buffer, ";\n", size);
+ break;
+ }
+ }
+ ldap_value_free_len (tempbv);
+ }
+
+ if ((tempbv = ldap_get_values_len (ld, ent, "dhcpOption")) != NULL)
+ {
+ for (i=0; tempbv[i] != NULL; i++)
+ {
+ x_strncat (buffer, "option ", size);
+ x_strncat (buffer, tempbv[i]->bv_val, size);
+ switch ((int) check_statement_end (tempbv[i]->bv_val))
+ {
+ case ';':
+ x_strncat (buffer, "\n", size);
+ break;
+ default:
+ x_strncat (buffer, ";\n", size);
+ break;
+ }
+ }
+ ldap_value_free_len (tempbv);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+
+static void
+ldap_generate_config_string (struct parse *cfile)
+{
+ struct berval **objectClass;
+ char *dn;
+ struct ldap_config_stack *entry;
+ LDAPMessage * ent, * res;
+ int i, ignore, found;
+ int ret;
+
+ if (ld == NULL)
+ ldap_start ();
+ if (ld == NULL)
+ return;
+
+ entry = ldap_stack;
+ if ((objectClass = ldap_get_values_len (ld, entry->ldent,
+ "objectClass")) == NULL)
+ return;
+
+ ignore = 0;
+ found = 1;
+ for (i=0; objectClass[i] != NULL; i++)
+ {
+ if (strcasecmp (objectClass[i]->bv_val, "dhcpSharedNetwork") == 0)
+ ldap_parse_shared_network (entry, cfile);
+ else if (strcasecmp (objectClass[i]->bv_val, "dhcpClass") == 0)
+ ldap_parse_class (entry, cfile);
+ else if (strcasecmp (objectClass[i]->bv_val, "dhcpSubnet") == 0)
+ ldap_parse_subnet (entry, cfile);
+ else if (strcasecmp (objectClass[i]->bv_val, "dhcpPool") == 0)
+ ldap_parse_pool (entry, cfile);
+ else if (strcasecmp (objectClass[i]->bv_val, "dhcpGroup") == 0)
+ ldap_parse_group (entry, cfile);
+ else if (strcasecmp (objectClass[i]->bv_val, "dhcpTSigKey") == 0)
+ ldap_parse_key (entry, cfile);
+ else if (strcasecmp (objectClass[i]->bv_val, "dhcpDnsZone") == 0)
+ ldap_parse_zone (entry, cfile);
+ else if (strcasecmp (objectClass[i]->bv_val, "dhcpHost") == 0)
+ {
+ if (ldap_method == LDAP_METHOD_STATIC)
+ ldap_parse_host (entry, cfile);
+ else
+ {
+ ignore = 1;
+ break;
+ }
+ }
+ else if (strcasecmp (objectClass[i]->bv_val, "dhcpSubClass") == 0)
+ {
+ if (ldap_method == LDAP_METHOD_STATIC)
+ ldap_parse_subclass (entry, cfile);
+ else
+ {
+ ignore = 1;
+ break;
+ }
+ }
+ else
+ found = 0;
+
+ if (found && cfile->inbuf[0] == '\0')
+ {
+ ignore = 1;
+ break;
+ }
+ }
+
+ ldap_value_free_len (objectClass);
+
+ if (ignore)
+ {
+ next_ldap_entry (cfile);
+ return;
+ }
+
+ ldap_parse_entry_options(entry->ldent, cfile->inbuf,
+ LDAP_BUFFER_SIZE-1, NULL);
+
+ dn = ldap_get_dn (ld, entry->ldent);
+
+#if defined(DEBUG_LDAP)
+ if (dn != NULL)
+ log_info ("Found LDAP entry '%s'", dn);
+#endif
+
+ if (dn == NULL ||
+ (ret = ldap_search_ext_s (ld, dn, LDAP_SCOPE_ONELEVEL,
+ "objectClass=*", NULL, 0, NULL, NULL,
+ NULL, 0, &res)) != LDAP_SUCCESS)
+ {
+ if (dn)
+ ldap_memfree (dn);
+
+ ldap_stop();
+ return;
+ }
+
+ ldap_memfree (dn);
+
+ if ((ent = ldap_first_entry (ld, res)) != NULL)
+ {
+ add_to_config_stack (res, ent);
+ parse_external_dns (entry->ldent);
+ }
+ else
+ {
+ ldap_msgfree (res);
+ parse_external_dns (entry->ldent);
+ next_ldap_entry (cfile);
+ }
+}
+
+
+static void
+ldap_close_debug_fd()
+{
+ if (ldap_debug_fd != -1)
+ {
+ close (ldap_debug_fd);
+ ldap_debug_fd = -1;
+ }
+}
+
+
+static void
+ldap_write_debug (const void *buff, size_t size)
+{
+ if (ldap_debug_fd != -1)
+ {
+ if (write (ldap_debug_fd, buff, size) < 0)
+ {
+ log_error ("Error writing to LDAP debug file %s: %s."
+ " Disabling log file.", ldap_debug_file,
+ strerror (errno));
+ ldap_close_debug_fd();
+ }
+ }
+}
+
+static int
+ldap_read_function (struct parse *cfile)
+{
+ cfile->inbuf[0] = '\0';
+ cfile->buflen = 0;
+
+ while (ldap_stack != NULL && *cfile->inbuf == '\0')
+ ldap_generate_config_string (cfile);
+
+ if (ldap_stack == NULL && *cfile->inbuf == '\0')
+ return (EOF);
+
+ cfile->bufix = 1;
+ cfile->buflen = strlen (cfile->inbuf) - 1;
+ if (cfile->buflen > 0)
+ ldap_write_debug (cfile->inbuf, cfile->buflen);
+
+#if defined (DEBUG_LDAP)
+ log_info ("Sending config line '%s'", cfile->inbuf);
+#endif
+
+ return (cfile->inbuf[0]);
+}
+
+
+static char *
+ldap_get_host_name (LDAPMessage * ent)
+{
+ struct berval **name;
+ char *ret;
+
+ ret = NULL;
+ if ((name = ldap_get_values_len (ld, ent, "cn")) == NULL || name[0] == NULL)
+ {
+ if (name != NULL)
+ ldap_value_free_len (name);
+
+#if defined (DEBUG_LDAP)
+ ret = ldap_get_dn (ld, ent);
+ if (ret != NULL)
+ {
+ log_info ("Cannot get cn attribute for LDAP entry %s", ret);
+ ldap_memfree(ret);
+ }
+#endif
+ return (NULL);
+ }
+
+ ret = dmalloc (strlen (name[0]->bv_val) + 1, MDL);
+ strcpy (ret, name[0]->bv_val);
+ ldap_value_free_len (name);
+
+ return (ret);
+}
+
+
+static int
+getfqhostname(char *fqhost, size_t size)
+{
+#if defined(MAXHOSTNAMELEN)
+ char hname[MAXHOSTNAMELEN];
+#else
+ char hname[65];
+#endif
+ struct hostent *hp;
+
+ if(NULL == fqhost || 1 >= size)
+ return -1;
+
+ memset(hname, 0, sizeof(hname));
+ if( gethostname(hname, sizeof(hname)-1))
+ return -1;
+
+ if(NULL == (hp = gethostbyname(hname)))
+ return -1;
+
+ strncpy(fqhost, hp->h_name, size-1);
+ fqhost[size-1] = '\0';
+ return 0;
+}
+
+
+isc_result_t
+ldap_read_config (void)
+{
+ LDAPMessage * ldres, * hostres, * ent, * hostent;
+ char hfilter[1024], sfilter[1024], fqdn[257];
+ char *buffer, *hostdn;
+ ldap_dn_node *curr = NULL;
+ struct parse *cfile;
+ struct utsname unme;
+ isc_result_t res;
+ size_t length;
+ int ret, cnt;
+ struct berval **tempbv = NULL;
+
+ if (ld == NULL)
+ ldap_start ();
+ if (ld == NULL)
+ return (ldap_server == NULL ? ISC_R_SUCCESS : ISC_R_FAILURE);
+
+ buffer = dmalloc (LDAP_BUFFER_SIZE+1, MDL);
+ if (buffer == NULL)
+ return (ISC_R_FAILURE);
+
+ cfile = (struct parse *) NULL;
+ res = new_parse (&cfile, -1, buffer, LDAP_BUFFER_SIZE, "LDAP", 0);
+ if (res != ISC_R_SUCCESS)
+ return (res);
+
+ uname (&unme);
+ if (ldap_dhcp_server_cn != NULL)
+ {
+ snprintf (hfilter, sizeof (hfilter),
+ "(&(objectClass=dhcpServer)(cn=%s))", ldap_dhcp_server_cn);
+ }
+ else
+ {
+ if(0 == getfqhostname(fqdn, sizeof(fqdn)))
+ {
+ snprintf (hfilter, sizeof (hfilter),
+ "(&(objectClass=dhcpServer)(|(cn=%s)(cn=%s)))",
+ unme.nodename, fqdn);
+ }
+ else
+ {
+ snprintf (hfilter, sizeof (hfilter),
+ "(&(objectClass=dhcpServer)(cn=%s))", unme.nodename);
+ }
+
+ }
+ hostres = NULL;
+ if ((ret = ldap_search_ext_s (ld, ldap_base_dn, LDAP_SCOPE_SUBTREE,
+ hfilter, NULL, 0, NULL, NULL, NULL, 0,
+ &hostres)) != LDAP_SUCCESS)
+ {
+ log_error ("Cannot find host LDAP entry %s %s",
+ ((ldap_dhcp_server_cn == NULL)?(unme.nodename):(ldap_dhcp_server_cn)), hfilter);
+ if(NULL != hostres)
+ ldap_msgfree (hostres);
+ ldap_stop();
+ return (ISC_R_FAILURE);
+ }
+
+ if ((hostent = ldap_first_entry (ld, hostres)) == NULL)
+ {
+ log_error ("Error: Cannot find LDAP entry matching %s", hfilter);
+ ldap_msgfree (hostres);
+ ldap_stop();
+ return (ISC_R_FAILURE);
+ }
+
+ hostdn = ldap_get_dn (ld, hostent);
+#if defined(DEBUG_LDAP)
+ if (hostdn != NULL)
+ log_info ("Found dhcpServer LDAP entry '%s'", hostdn);
+#endif
+
+ if (hostdn == NULL ||
+ (tempbv = ldap_get_values_len (ld, hostent, "dhcpServiceDN")) == NULL ||
+ tempbv[0] == NULL)
+ {
+ log_error ("Error: Cannot find LDAP entry matching %s", hfilter);
+
+ if (tempbv != NULL)
+ ldap_value_free_len (tempbv);
+
+ if (hostdn)
+ ldap_memfree (hostdn);
+ ldap_msgfree (hostres);
+ ldap_stop();
+ return (ISC_R_FAILURE);
+ }
+
+#if defined(DEBUG_LDAP)
+ log_info ("LDAP: Parsing dhcpServer options '%s' ...", hostdn);
+#endif
+
+ cfile->inbuf[0] = '\0';
+ ldap_parse_entry_options(hostent, cfile->inbuf, LDAP_BUFFER_SIZE, NULL);
+ cfile->buflen = strlen (cfile->inbuf);
+ if(cfile->buflen > 0)
+ {
+ ldap_write_debug (cfile->inbuf, cfile->buflen);
+
+ res = conf_file_subparse (cfile, root_group, ROOT_GROUP);
+ if (res != ISC_R_SUCCESS)
+ {
+ log_error ("LDAP: cannot parse dhcpServer entry '%s'", hostdn);
+ ldap_memfree (hostdn);
+ ldap_stop();
+ return res;
+ }
+ cfile->inbuf[0] = '\0';
+ }
+ ldap_msgfree (hostres);
+
+ /*
+ ** attach ldap (tree) read function now
+ */
+ cfile->bufix = cfile->buflen = 0;
+ cfile->read_function = ldap_read_function;
+
+ res = ISC_R_SUCCESS;
+ for (cnt=0; tempbv[cnt] != NULL; cnt++)
+ {
+ snprintf(sfilter, sizeof(sfilter), "(&(objectClass=dhcpService)"
+ "(|(dhcpPrimaryDN=%s)(dhcpSecondaryDN=%s)))",
+ hostdn, hostdn);
+ ldres = NULL;
+ if ((ret = ldap_search_ext_s (ld, tempbv[cnt]->bv_val, LDAP_SCOPE_BASE,
+ sfilter, NULL, 0, NULL, NULL, NULL,
+ 0, &ldres)) != LDAP_SUCCESS)
+ {
+ log_error ("Error searching for dhcpServiceDN '%s': %s. Please update the LDAP entry '%s'",
+ tempbv[cnt]->bv_val, ldap_err2string (ret), hostdn);
+ if(NULL != ldres)
+ ldap_msgfree(ldres);
+ res = ISC_R_FAILURE;
+ break;
+ }
+
+ if ((ent = ldap_first_entry (ld, ldres)) == NULL)
+ {
+ log_error ("Error: Cannot find dhcpService DN '%s' with primary or secondary server reference. Please update the LDAP server entry '%s'",
+ tempbv[cnt]->bv_val, hostdn);
+
+ ldap_msgfree(ldres);
+ res = ISC_R_FAILURE;
+ break;
+ }
+
+ /*
+ ** FIXME: how to free the remembered dn's on exit?
+ ** This should be OK if dmalloc registers the
+ ** memory it allocated and frees it on exit..
+ */
+
+ curr = dmalloc (sizeof (*curr), MDL);
+ if (curr != NULL)
+ {
+ length = strlen (tempbv[cnt]->bv_val);
+ curr->dn = dmalloc (length + 1, MDL);
+ if (curr->dn == NULL)
+ {
+ dfree (curr, MDL);
+ curr = NULL;
+ }
+ else
+ strcpy (curr->dn, tempbv[cnt]->bv_val);
+ }
+
+ if (curr != NULL)
+ {
+ curr->refs++;
+
+ /* append to service-dn list */
+ if (ldap_service_dn_tail != NULL)
+ ldap_service_dn_tail->next = curr;
+ else
+ ldap_service_dn_head = curr;
+
+ ldap_service_dn_tail = curr;
+ }
+ else
+ log_fatal ("no memory to remember ldap service dn");
+
+#if defined (DEBUG_LDAP)
+ log_info ("LDAP: Parsing dhcpService DN '%s' ...", tempbv[cnt]);
+#endif
+ add_to_config_stack (ldres, ent);
+ res = conf_file_subparse (cfile, root_group, ROOT_GROUP);
+ if (res != ISC_R_SUCCESS)
+ {
+ log_error ("LDAP: cannot parse dhcpService entry '%s'", tempbv[cnt]->bv_val);
+ break;
+ }
+ }
+
+ end_parse (&cfile);
+ ldap_close_debug_fd();
+
+ ldap_memfree (hostdn);
+ ldap_value_free_len (tempbv);
+
+ if (res != ISC_R_SUCCESS)
+ {
+ struct ldap_config_stack *temp_stack;
+
+ while ((curr = ldap_service_dn_head) != NULL)
+ {
+ ldap_service_dn_head = curr->next;
+ dfree (curr->dn, MDL);
+ dfree (curr, MDL);
+ }
+
+ ldap_service_dn_tail = NULL;
+
+ while ((temp_stack = ldap_stack) != NULL)
+ {
+ ldap_stack = temp_stack->next;
+ free_stack_entry (temp_stack);
+ }
+
+ ldap_stop();
+ }
+
+ /* Unbind from ldap immediately after reading config in static mode. */
+ if (ldap_method == LDAP_METHOD_STATIC)
+ ldap_stop();
+
+ return (res);
+}
+
+
+/* This function will parse the dhcpOption and dhcpStatements field in the LDAP
+ entry if it exists. Right now, type will be either HOST_DECL or CLASS_DECL.
+ If we are parsing a HOST_DECL, this always returns 0. If we are parsing a
+ CLASS_DECL, this will return what the current lease limit is in LDAP. If
+ there is no lease limit specified, we return 0 */
+
+static int
+ldap_parse_options (LDAPMessage * ent, struct group *group,
+ int type, struct host_decl *host,
+ struct class **class)
+{
+ int declaration, lease_limit;
+ char option_buffer[8192];
+ enum dhcp_token token;
+ struct parse *cfile;
+ isc_result_t res;
+ const char *val;
+
+ lease_limit = 0;
+ *option_buffer = '\0';
+
+ /* This block of code will try to find the parent of the host, and
+ if it is a group object, fetch the options and apply to the host. */
+ if (type == HOST_DECL)
+ {
+ char *hostdn, *basedn, *temp1, *temp2, filter[1024];
+ LDAPMessage *groupdn, *entry;
+ int ret;
+
+ hostdn = ldap_get_dn (ld, ent);
+ if( hostdn != NULL)
+ {
+ basedn = NULL;
+
+ temp1 = strchr (hostdn, '=');
+ if (temp1 != NULL)
+ temp1 = strchr (++temp1, '=');
+ if (temp1 != NULL)
+ temp2 = strchr (++temp1, ',');
+ else
+ temp2 = NULL;
+
+ if (temp2 != NULL)
+ {
+ snprintf (filter, sizeof(filter),
+ "(&(cn=%.*s)(objectClass=dhcpGroup))",
+ (int)(temp2 - temp1), temp1);
+
+ basedn = strchr (temp1, ',');
+ if (basedn != NULL)
+ ++basedn;
+ }
+
+ if (basedn != NULL && *basedn != '\0')
+ {
+ ret = ldap_search_ext_s (ld, basedn, LDAP_SCOPE_SUBTREE, filter,
+ NULL, 0, NULL, NULL, NULL, 0, &groupdn);
+ if (ret == LDAP_SUCCESS)
+ {
+ if ((entry = ldap_first_entry (ld, groupdn)) != NULL)
+ {
+ res = ldap_parse_entry_options (entry, option_buffer,
+ sizeof(option_buffer) - 1,
+ &lease_limit);
+ if (res != ISC_R_SUCCESS)
+ {
+ /* reset option buffer discarding any results */
+ *option_buffer = '\0';
+ lease_limit = 0;
+ }
+ }
+ ldap_msgfree( groupdn);
+ }
+ }
+ ldap_memfree( hostdn);
+ }
+ }
+
+ res = ldap_parse_entry_options (ent, option_buffer, sizeof(option_buffer) - 1,
+ &lease_limit);
+ if (res != ISC_R_SUCCESS)
+ return (lease_limit);
+
+ option_buffer[sizeof(option_buffer) - 1] = '\0';
+ if (*option_buffer == '\0')
+ return (lease_limit);
+
+ cfile = (struct parse *) NULL;
+ res = new_parse (&cfile, -1, option_buffer, strlen (option_buffer),
+ type == HOST_DECL ? "LDAP-HOST" : "LDAP-SUBCLASS", 0);
+ if (res != ISC_R_SUCCESS)
+ return (lease_limit);
+
+#if defined (DEBUG_LDAP)
+ log_info ("Sending the following options: '%s'", option_buffer);
+#endif
+
+ declaration = 0;
+ do
+ {
+ token = peek_token (&val, NULL, cfile);
+ if (token == END_OF_FILE)
+ break;
+ declaration = parse_statement (cfile, group, type, host, declaration);
+ } while (1);
+
+ end_parse (&cfile);
+
+ return (lease_limit);
+}
+
+
+
+int
+find_haddr_in_ldap (struct host_decl **hp, int htype, unsigned hlen,
+ const unsigned char *haddr, const char *file, int line)
+{
+ char buf[128], *type_str;
+ LDAPMessage * res, *ent;
+ struct host_decl * host;
+ isc_result_t status;
+ ldap_dn_node *curr;
+ int ret;
+
+ if (ldap_method == LDAP_METHOD_STATIC)
+ return (0);
+
+ if (ld == NULL)
+ ldap_start ();
+ if (ld == NULL)
+ return (0);
+
+ switch (htype)
+ {
+ case HTYPE_ETHER:
+ type_str = "ethernet";
+ break;
+ case HTYPE_IEEE802:
+ type_str = "token-ring";
+ break;
+ case HTYPE_FDDI:
+ type_str = "fddi";
+ break;
+ default:
+ log_info ("Ignoring unknown type %d", htype);
+ return (0);
+ }
+
+ /*
+ ** FIXME: It is not guaranteed, that the dhcpHWAddress attribute
+ ** contains _exactly_ "type addr" with one space between!
+ */
+ snprintf (buf, sizeof (buf),
+ "(&(objectClass=dhcpHost)(dhcpHWAddress=%s %s))",
+ type_str, print_hw_addr (htype, hlen, haddr));
+
+ res = ent = NULL;
+ for (curr = ldap_service_dn_head;
+ curr != NULL && *curr->dn != '\0';
+ curr = curr->next)
+ {
+#if defined (DEBUG_LDAP)
+ log_info ("Searching for %s in LDAP tree %s", buf, curr->dn);
+#endif
+ ret = ldap_search_ext_s (ld, curr->dn, LDAP_SCOPE_SUBTREE, buf, NULL, 0,
+ NULL, NULL, NULL, 0, &res);
+
+ if(ret == LDAP_SERVER_DOWN)
+ {
+ log_info ("LDAP server was down, trying to reconnect...");
+
+ ldap_stop();
+ ldap_start();
+ if(ld == NULL)
+ {
+ log_info ("LDAP reconnect failed - try again later...");
+ return (0);
+ }
+
+ ret = ldap_search_ext_s (ld, curr->dn, LDAP_SCOPE_SUBTREE, buf, NULL,
+ 0, NULL, NULL, NULL, 0, &res);
+ }
+
+ if (ret == LDAP_SUCCESS)
+ {
+ if( (ent = ldap_first_entry (ld, res)) != NULL)
+ break; /* search OK and have entry */
+
+#if defined (DEBUG_LDAP)
+ log_info ("No host entry for %s in LDAP tree %s",
+ buf, curr->dn);
+#endif
+ if(res)
+ {
+ ldap_msgfree (res);
+ res = NULL;
+ }
+ }
+ else
+ {
+ if(res)
+ {
+ ldap_msgfree (res);
+ res = NULL;
+ }
+
+ if (ret != LDAP_NO_SUCH_OBJECT && ret != LDAP_SUCCESS)
+ {
+ log_error ("Cannot search for %s in LDAP tree %s: %s", buf,
+ curr->dn, ldap_err2string (ret));
+ ldap_stop();
+ return (0);
+ }
+#if defined (DEBUG_LDAP)
+ else
+ {
+ log_info ("ldap_search_ext_s returned %s when searching for %s in %s",
+ ldap_err2string (ret), buf, curr->dn);
+ }
+#endif
+ }
+ }
+
+ if (res && ent)
+ {
+#if defined (DEBUG_LDAP)
+ char *dn = ldap_get_dn (ld, ent);
+ if (dn != NULL)
+ {
+ log_info ("Found dhcpHWAddress LDAP entry %s", dn);
+ ldap_memfree(dn);
+ }
+#endif
+
+ host = (struct host_decl *)0;
+ status = host_allocate (&host, MDL);
+ if (status != ISC_R_SUCCESS)
+ {
+ log_fatal ("can't allocate host decl struct: %s",
+ isc_result_totext (status));
+ ldap_msgfree (res);
+ return (0);
+ }
+
+ host->name = ldap_get_host_name (ent);
+ if (host->name == NULL)
+ {
+ host_dereference (&host, MDL);
+ ldap_msgfree (res);
+ return (0);
+ }
+
+ if (!clone_group (&host->group, root_group, MDL))
+ {
+ log_fatal ("can't clone group for host %s", host->name);
+ host_dereference (&host, MDL);
+ ldap_msgfree (res);
+ return (0);
+ }
+
+ ldap_parse_options (ent, host->group, HOST_DECL, host, NULL);
+
+ *hp = host;
+ ldap_msgfree (res);
+ return (1);
+ }
+
+
+ if(res) ldap_msgfree (res);
+ return (0);
+}
+
+
+int
+find_subclass_in_ldap (struct class *class, struct class **newclass,
+ struct data_string *data)
+{
+ LDAPMessage * res, * ent;
+ int ret, lease_limit;
+ isc_result_t status;
+ ldap_dn_node *curr;
+ char buf[1024];
+
+ if (ldap_method == LDAP_METHOD_STATIC)
+ return (0);
+
+ if (ld == NULL)
+ ldap_start ();
+ if (ld == NULL)
+ return (0);
+
+ snprintf (buf, sizeof (buf),
+ "(&(objectClass=dhcpSubClass)(cn=%s)(dhcpClassData=%s))",
+ print_hex_1 (data->len, data->data, 60),
+ print_hex_2 (strlen (class->name), (u_int8_t *) class->name, 60));
+#if defined (DEBUG_LDAP)
+ log_info ("Searching LDAP for %s", buf);
+#endif
+
+ res = ent = NULL;
+ for (curr = ldap_service_dn_head;
+ curr != NULL && *curr->dn != '\0';
+ curr = curr->next)
+ {
+#if defined (DEBUG_LDAP)
+ log_info ("Searching for %s in LDAP tree %s", buf, curr->dn);
+#endif
+ ret = ldap_search_ext_s (ld, curr->dn, LDAP_SCOPE_SUBTREE, buf, NULL, 0,
+ NULL, NULL, NULL, 0, &res);
+
+ if(ret == LDAP_SERVER_DOWN)
+ {
+ log_info ("LDAP server was down, trying to reconnect...");
+
+ ldap_stop();
+ ldap_start();
+
+ if(ld == NULL)
+ {
+ log_info ("LDAP reconnect failed - try again later...");
+ return (0);
+ }
+
+ ret = ldap_search_ext_s (ld, curr->dn, LDAP_SCOPE_SUBTREE, buf,
+ NULL, 0, NULL, NULL, NULL, 0, &res);
+ }
+
+ if (ret == LDAP_SUCCESS)
+ {
+ if( (ent = ldap_first_entry (ld, res)) != NULL)
+ break; /* search OK and have entry */
+
+#if defined (DEBUG_LDAP)
+ log_info ("No subclass entry for %s in LDAP tree %s",
+ buf, curr->dn);
+#endif
+ if(res)
+ {
+ ldap_msgfree (res);
+ res = NULL;
+ }
+ }
+ else
+ {
+ if(res)
+ {
+ ldap_msgfree (res);
+ res = NULL;
+ }
+
+ if (ret != LDAP_NO_SUCH_OBJECT && ret != LDAP_SUCCESS)
+ {
+ log_error ("Cannot search for %s in LDAP tree %s: %s", buf,
+ curr->dn, ldap_err2string (ret));
+ ldap_stop();
+ return (0);
+ }
+#if defined (DEBUG_LDAP)
+ else
+ {
+ log_info ("ldap_search_ext_s returned %s when searching for %s in %s",
+ ldap_err2string (ret), buf, curr->dn);
+ }
+#endif
+ }
+ }
+
+ if (res && ent)
+ {
+#if defined (DEBUG_LDAP)
+ char *dn = ldap_get_dn (ld, ent);
+ if (dn != NULL)
+ {
+ log_info ("Found subclass LDAP entry %s", dn);
+ ldap_memfree(dn);
+ }
+#endif
+
+ status = class_allocate (newclass, MDL);
+ if (status != ISC_R_SUCCESS)
+ {
+ log_error ("Cannot allocate memory for a new class");
+ ldap_msgfree (res);
+ return (0);
+ }
+
+ group_reference (&(*newclass)->group, class->group, MDL);
+ class_reference (&(*newclass)->superclass, class, MDL);
+ lease_limit = ldap_parse_options (ent, (*newclass)->group,
+ CLASS_DECL, NULL, newclass);
+ if (lease_limit == 0)
+ (*newclass)->lease_limit = class->lease_limit;
+ else
+ class->lease_limit = lease_limit;
+
+ if ((*newclass)->lease_limit)
+ {
+ (*newclass)->billed_leases =
+ dmalloc ((*newclass)->lease_limit * sizeof (struct lease *), MDL);
+ if (!(*newclass)->billed_leases)
+ {
+ log_error ("no memory for billing");
+ class_dereference (newclass, MDL);
+ ldap_msgfree (res);
+ return (0);
+ }
+ memset ((*newclass)->billed_leases, 0,
+ ((*newclass)->lease_limit * sizeof (struct lease *)));
+ }
+
+ data_string_copy (&(*newclass)->hash_string, data, MDL);
+
+ ldap_msgfree (res);
+ return (1);
+ }
+
+ if(res) ldap_msgfree (res);
+ return (0);
+}
+
+#endif
diff --git a/server/ldap_casa.c b/server/ldap_casa.c
new file mode 100644
index 0000000..952d9b9
--- /dev/null
+++ b/server/ldap_casa.c
@@ -0,0 +1,159 @@
+/* ldap_casa.c
+
+ CASA routines for DHCPD... */
+
+/* Copyright (c) 2006 Novell, Inc.
+
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1.Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2.Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3.Neither the name of ISC, ISC DHCP, nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+
+ * THIS SOFTWARE IS PROVIDED BY INTERNET SYSTEMS CONSORTIUM AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ISC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+
+ * This file was written by S Kalyanasundaram <skalyanasundaram@novell.com>
+ */
+
+/*
+ * Copyright (c) 2004-2010 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/
+ */
+
+#if defined(LDAP_CASA_AUTH)
+#include "ldap_casa.h"
+#include "dhcpd.h"
+
+int
+load_casa (void)
+{
+ if( !(casaIDK = dlopen(MICASA_LIB,RTLD_LAZY)))
+ return 0;
+ p_miCASAGetCredential = (CASA_GetCredential_T) dlsym(casaIDK, "miCASAGetCredential");
+ p_miCASASetCredential = (CASA_SetCredential_T) dlsym(casaIDK, "miCASASetCredential");
+ p_miCASARemoveCredential = (CASA_RemoveCredential_T) dlsym(casaIDK, "miCASARemoveCredential");
+
+ if((p_miCASAGetCredential == NULL) ||
+ (p_miCASASetCredential == NULL) ||
+ (p_miCASARemoveCredential == NULL))
+ {
+ if(casaIDK)
+ dlclose(casaIDK);
+ casaIDK = NULL;
+ p_miCASAGetCredential = NULL;
+ p_miCASASetCredential = NULL;
+ p_miCASARemoveCredential = NULL;
+ return 0;
+ }
+ else
+ return 1;
+}
+
+static void
+release_casa(void)
+{
+ if(casaIDK)
+ {
+ dlclose(casaIDK);
+ casaIDK = NULL;
+ }
+
+ p_miCASAGetCredential = NULL;
+ p_miCASASetCredential = NULL;
+ p_miCASARemoveCredential = NULL;
+
+}
+
+int
+load_uname_pwd_from_miCASA (char **ldap_username, char **ldap_password)
+ {
+ int result = 0;
+ uint32_t credentialtype = SSCS_CRED_TYPE_SERVER_F;
+ SSCS_BASIC_CREDENTIAL credential;
+ SSCS_SECRET_ID_T applicationSecretId;
+ char *tempVar = NULL;
+
+ const char applicationName[10] = "dhcp-ldap";
+
+ if ( load_casa() )
+ {
+ memset(&credential, 0, sizeof(SSCS_BASIC_CREDENTIAL));
+ memset(&applicationSecretId, 0, sizeof(SSCS_SECRET_ID_T));
+
+ applicationSecretId.len = strlen(applicationName) + 1;
+ memcpy (applicationSecretId.id, applicationName, applicationSecretId.len);
+
+ credential.unFlags = USERNAME_TYPE_CN_F;
+
+ result = p_miCASAGetCredential (0,
+ &applicationSecretId,NULL,&credentialtype,
+ &credential,NULL);
+
+ if(credential.unLen)
+ {
+ tempVar = dmalloc (credential.unLen + 1, MDL);
+ if (!tempVar)
+ log_fatal ("no memory for ldap_username");
+ memcpy(tempVar , credential.username, credential.unLen);
+ *ldap_username = tempVar;
+
+ tempVar = dmalloc (credential.pwordLen + 1, MDL);
+ if (!tempVar)
+ log_fatal ("no memory for ldap_password");
+ memcpy(tempVar, credential.password, credential.pwordLen);
+ *ldap_password = tempVar;
+
+#if defined (DEBUG_LDAP)
+ log_info ("Authentication credential taken from CASA");
+#endif
+
+ release_casa();
+ return 1;
+
+ }
+ else
+ {
+ release_casa();
+ return 0;
+ }
+ }
+ else
+ return 0; //casa libraries not loaded
+ }
+
+#endif /* LDAP_CASA_AUTH */
+
diff --git a/server/mdb.c b/server/mdb.c
new file mode 100644
index 0000000..5503ea0
--- /dev/null
+++ b/server/mdb.c
@@ -0,0 +1,3047 @@
+/* mdb.c
+
+ Server-specific in-memory database support. */
+
+/*
+ * Copyright (c) 2011-2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-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 "omapip/hash.h"
+
+struct subnet *subnets;
+struct shared_network *shared_networks;
+host_hash_t *host_hw_addr_hash;
+host_hash_t *host_uid_hash;
+host_hash_t *host_name_hash;
+lease_id_hash_t *lease_uid_hash;
+lease_ip_hash_t *lease_ip_addr_hash;
+lease_id_hash_t *lease_hw_addr_hash;
+
+/*
+ * We allow users to specify any option as a host identifier.
+ *
+ * Any host is uniquely identified by the combination of
+ * option type & option data.
+ *
+ * We expect people will only use a few types of options as host
+ * identifier. Because of this, we store a list with an entry for
+ * each option type. Each of these has a hash table, which contains
+ * hash of the option data.
+ */
+typedef struct host_id_info {
+ struct option *option;
+ host_hash_t *values_hash;
+ struct host_id_info *next;
+} host_id_info_t;
+
+static host_id_info_t *host_id_info = NULL;
+
+int numclasseswritten;
+
+omapi_object_type_t *dhcp_type_host;
+
+isc_result_t enter_class(cd, dynamicp, commit)
+ struct class *cd;
+ int dynamicp;
+ int commit;
+{
+ if (!collections -> classes) {
+ /* A subclass with no parent is invalid. */
+ if (cd->name == NULL)
+ return DHCP_R_INVALIDARG;
+
+ class_reference (&collections -> classes, cd, MDL);
+ } else if (cd->name != NULL) { /* regular class */
+ struct class *c = 0;
+
+ if (find_class(&c, cd->name, MDL) != ISC_R_NOTFOUND) {
+ class_dereference(&c, MDL);
+ return ISC_R_EXISTS;
+ }
+
+ /* Find the tail. */
+ for (c = collections -> classes;
+ c -> nic; c = c -> nic)
+ /* nothing */ ;
+ class_reference (&c -> nic, cd, MDL);
+ }
+
+ if (dynamicp && commit) {
+ const char *name = cd->name;
+
+ if (name == NULL) {
+ name = cd->superclass->name;
+ }
+
+ write_named_billing_class ((const unsigned char *)name, 0, cd);
+ if (!commit_leases ())
+ return ISC_R_IOERROR;
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+
+/* Variable to check if we're starting the server. The server will init as
+ * starting - but just to be safe start out as false to avoid triggering new
+ * special-case code
+ * XXX: There is actually a server_startup state...which is never entered...
+ */
+#define SS_NOSYNC 1
+#define SS_QFOLLOW 2
+static int server_starting = 0;
+
+static int find_uid_statement (struct executable_statement *esp,
+ void *vp, int condp)
+{
+ struct executable_statement **evp = vp;
+
+ if (esp -> op == supersede_option_statement &&
+ esp -> data.option &&
+ (esp -> data.option -> option -> universe ==
+ &dhcp_universe) &&
+ (esp -> data.option -> option -> code ==
+ DHO_DHCP_CLIENT_IDENTIFIER)) {
+ if (condp) {
+ log_error ("dhcp client identifier may not be %s",
+ "specified conditionally.");
+ } else if (!(*evp)) {
+ executable_statement_reference (evp, esp, MDL);
+ return 1;
+ } else {
+ log_error ("only one dhcp client identifier may be %s",
+ "specified");
+ }
+ }
+ return 0;
+}
+
+
+static host_id_info_t *
+find_host_id_info(unsigned int option_code) {
+ host_id_info_t *p;
+
+ for (p=host_id_info; p != NULL; p = p->next) {
+ if (p->option->code == option_code) {
+ break;
+ }
+ }
+ return p;
+}
+
+/* Debugging code */
+#if 0
+isc_result_t
+print_host(const void *name, unsigned len, void *value) {
+ struct host_decl *h;
+ printf("--------------\n");
+ printf("name:'%s'\n", print_hex_1(len, name, 60));
+ printf("len:%d\n", len);
+ h = (struct host_decl *)value;
+ printf("host @%p is '%s'\n", h, h->name);
+ return ISC_R_SUCCESS;
+}
+
+void
+hash_print_hosts(struct hash_table *h) {
+ hash_foreach(h, print_host);
+ printf("--------------\n");
+}
+#endif /* 0 */
+
+void
+change_host_uid(struct host_decl *host, const char *uid, int len) {
+ /* XXX: should consolidate this type of code throughout */
+ if (host_uid_hash == NULL) {
+ if (!host_new_hash(&host_uid_hash, HOST_HASH_SIZE, MDL)) {
+ log_fatal("Can't allocate host/uid hash");
+ }
+ }
+
+ /*
+ * Remove the old entry, if one exists.
+ */
+ if (host->client_identifier.data != NULL) {
+ host_hash_delete(host_uid_hash,
+ host->client_identifier.data,
+ host->client_identifier.len,
+ MDL);
+ data_string_forget(&host->client_identifier, MDL);
+ }
+
+ /*
+ * Set our new value.
+ */
+ memset(&host->client_identifier, 0, sizeof(host->client_identifier));
+ host->client_identifier.len = len;
+ if (!buffer_allocate(&host->client_identifier.buffer, len, MDL)) {
+ log_fatal("Can't allocate uid buffer");
+ }
+ host->client_identifier.data = host->client_identifier.buffer->data;
+ memcpy((char *)host->client_identifier.data, uid, len);
+
+ /*
+ * And add to hash.
+ */
+ host_hash_add(host_uid_hash, host->client_identifier.data,
+ host->client_identifier.len, host, MDL);
+}
+
+isc_result_t enter_host (hd, dynamicp, commit)
+ struct host_decl *hd;
+ int dynamicp;
+ int commit;
+{
+ struct host_decl *hp = (struct host_decl *)0;
+ struct host_decl *np = (struct host_decl *)0;
+ struct executable_statement *esp;
+ host_id_info_t *h_id_info;
+
+ if (!host_name_hash) {
+ if (!host_new_hash(&host_name_hash, HOST_HASH_SIZE, MDL))
+ log_fatal ("Can't allocate host name hash");
+ host_hash_add (host_name_hash,
+ (unsigned char *)hd -> name,
+ strlen (hd -> name), hd, MDL);
+ } else {
+ host_hash_lookup (&hp, host_name_hash,
+ (unsigned char *)hd -> name,
+ strlen (hd -> name), MDL);
+
+ /* If it's deleted, we can supersede it. */
+ if (hp && (hp -> flags & HOST_DECL_DELETED)) {
+ host_hash_delete (host_name_hash,
+ (unsigned char *)hd -> name,
+ strlen (hd -> name), MDL);
+ /* If the old entry wasn't dynamic, then we
+ always have to keep the deletion. */
+ if (hp -> flags & HOST_DECL_STATIC) {
+ hd -> flags |= HOST_DECL_STATIC;
+ }
+ host_dereference (&hp, MDL);
+ }
+
+ /* If we are updating an existing host declaration, we
+ can just delete it and add it again. */
+ if (hp && hp == hd) {
+ host_dereference (&hp, MDL);
+ delete_host (hd, 0);
+ if (!write_host (hd))
+ return ISC_R_IOERROR;
+ hd -> flags &= ~HOST_DECL_DELETED;
+ }
+
+ /* If there isn't already a host decl matching this
+ address, add it to the hash table. */
+ if (!hp) {
+ host_hash_add (host_name_hash,
+ (unsigned char *)hd -> name,
+ strlen (hd -> name), hd, MDL);
+ } else {
+ /* XXX actually, we have to delete the old one
+ XXX carefully and replace it. Not done yet. */
+ host_dereference (&hp, MDL);
+ return ISC_R_EXISTS;
+ }
+ }
+
+ if (hd -> n_ipaddr)
+ host_dereference (&hd -> n_ipaddr, MDL);
+
+ if (!hd -> type)
+ hd -> type = dhcp_type_host;
+
+ if (hd -> interface.hlen) {
+ if (!host_hw_addr_hash) {
+ if (!host_new_hash(&host_hw_addr_hash,
+ HOST_HASH_SIZE, MDL))
+ log_fatal ("Can't allocate host/hw hash");
+ } else {
+ /* If there isn't already a host decl matching this
+ address, add it to the hash table. */
+ host_hash_lookup (&hp, host_hw_addr_hash,
+ hd -> interface.hbuf,
+ hd -> interface.hlen, MDL);
+ }
+ if (!hp)
+ host_hash_add (host_hw_addr_hash, hd -> interface.hbuf,
+ hd -> interface.hlen, hd, MDL);
+ else {
+ /* If there was already a host declaration for
+ this hardware address, add this one to the
+ end of the list. */
+ for (np = hp; np -> n_ipaddr; np = np -> n_ipaddr)
+ ;
+ host_reference (&np -> n_ipaddr, hd, MDL);
+ host_dereference (&hp, MDL);
+ }
+ }
+
+ /* See if there's a statement that sets the client identifier.
+ This is a kludge - the client identifier really shouldn't be
+ set with an executable statement. */
+ esp = NULL;
+ if (executable_statement_foreach (hd->group->statements,
+ find_uid_statement, &esp, 0)) {
+ (void) evaluate_option_cache (&hd->client_identifier,
+ NULL, NULL, NULL, NULL, NULL,
+ &global_scope,
+ esp->data.option, MDL);
+ }
+
+ /* If we got a client identifier, hash this entry by
+ client identifier. */
+ if (hd -> client_identifier.len) {
+ /* If there's no uid hash, make one; otherwise, see if
+ there's already an entry in the hash for this host. */
+ if (!host_uid_hash) {
+ if (!host_new_hash(&host_uid_hash,
+ HOST_HASH_SIZE, MDL))
+ log_fatal ("Can't allocate host/uid hash");
+
+ host_hash_add (host_uid_hash,
+ hd -> client_identifier.data,
+ hd -> client_identifier.len,
+ hd, MDL);
+ } else {
+ /* If there's already a host declaration for this
+ client identifier, add this one to the end of the
+ list. Otherwise, add it to the hash table. */
+ if (host_hash_lookup (&hp, host_uid_hash,
+ hd -> client_identifier.data,
+ hd -> client_identifier.len,
+ MDL)) {
+ /* Don't link it in twice... */
+ if (!np) {
+ for (np = hp; np -> n_ipaddr;
+ np = np -> n_ipaddr) {
+ if (hd == np)
+ break;
+ }
+ if (hd != np)
+ host_reference (&np -> n_ipaddr,
+ hd, MDL);
+ }
+ host_dereference (&hp, MDL);
+ } else {
+ host_hash_add (host_uid_hash,
+ hd -> client_identifier.data,
+ hd -> client_identifier.len,
+ hd, MDL);
+ }
+ }
+ }
+
+
+ /*
+ * If we use an option as our host identifier, record it here.
+ */
+ if (hd->host_id_option != NULL) {
+ /*
+ * Look for the host identifier information for this option,
+ * and create a new entry if there is none.
+ */
+ h_id_info = find_host_id_info(hd->host_id_option->code);
+ if (h_id_info == NULL) {
+ h_id_info = dmalloc(sizeof(*h_id_info), MDL);
+ if (h_id_info == NULL) {
+ log_fatal("No memory for host-identifier "
+ "option information.");
+ }
+ option_reference(&h_id_info->option,
+ hd->host_id_option, MDL);
+ if (!host_new_hash(&h_id_info->values_hash,
+ HOST_HASH_SIZE, MDL)) {
+ log_fatal("No memory for host-identifier "
+ "option hash.");
+ }
+ h_id_info->next = host_id_info;
+ host_id_info = h_id_info;
+ }
+
+ if (host_hash_lookup(&hp, h_id_info->values_hash,
+ hd->host_id.data, hd->host_id.len, MDL)) {
+ /*
+ * If this option is already present, then add
+ * this host to the list in n_ipaddr, unless
+ * we have already done so previously.
+ *
+ * XXXSK: This seems scary to me, but I don't
+ * fully understand how these are used.
+ * Shouldn't there be multiple lists, or
+ * maybe we should just forbid duplicates?
+ */
+ if (np == NULL) {
+ np = hp;
+ while (np->n_ipaddr != NULL) {
+ np = np->n_ipaddr;
+ }
+ if (hd != np) {
+ host_reference(&np->n_ipaddr, hd, MDL);
+ }
+ }
+ host_dereference(&hp, MDL);
+ } else {
+ host_hash_add(h_id_info->values_hash,
+ hd->host_id.data,
+ hd->host_id.len,
+ hd, MDL);
+ }
+ }
+
+ if (dynamicp && commit) {
+ if (!write_host (hd))
+ return ISC_R_IOERROR;
+ if (!commit_leases ())
+ return ISC_R_IOERROR;
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+
+isc_result_t delete_class (cp, commit)
+ struct class *cp;
+ int commit;
+{
+ cp->flags |= CLASS_DECL_DELETED;
+
+ /* do the write first as we won't be leaving it in any data
+ structures, unlike the host objects */
+
+ if (commit) {
+ write_named_billing_class ((unsigned char *)cp->name, 0, cp);
+ if (!commit_leases ())
+ return ISC_R_IOERROR;
+ }
+
+ unlink_class(&cp); /* remove from collections */
+
+ class_dereference(&cp, MDL);
+
+ return ISC_R_SUCCESS;
+}
+
+
+isc_result_t delete_host (hd, commit)
+ struct host_decl *hd;
+ int commit;
+{
+ struct host_decl *hp = (struct host_decl *)0;
+ struct host_decl *np = (struct host_decl *)0;
+ struct host_decl *foo;
+ int hw_head = 0, uid_head = 1;
+
+ /* Don't need to do it twice. */
+ if (hd -> flags & HOST_DECL_DELETED)
+ return ISC_R_SUCCESS;
+
+ /* But we do need to do it once! :') */
+ hd -> flags |= HOST_DECL_DELETED;
+
+ if (hd -> interface.hlen) {
+ if (host_hw_addr_hash) {
+ if (host_hash_lookup (&hp, host_hw_addr_hash,
+ hd -> interface.hbuf,
+ hd -> interface.hlen, MDL)) {
+ if (hp == hd) {
+ host_hash_delete (host_hw_addr_hash,
+ hd -> interface.hbuf,
+ hd -> interface.hlen, MDL);
+ hw_head = 1;
+ } else {
+ np = (struct host_decl *)0;
+ foo = (struct host_decl *)0;
+ host_reference (&foo, hp, MDL);
+ while (foo) {
+ if (foo == hd)
+ break;
+ if (np)
+ host_dereference (&np, MDL);
+ host_reference (&np, foo, MDL);
+ host_dereference (&foo, MDL);
+ if (np -> n_ipaddr)
+ host_reference (&foo, np -> n_ipaddr, MDL);
+ }
+
+ if (foo) {
+ host_dereference (&np -> n_ipaddr, MDL);
+ if (hd -> n_ipaddr)
+ host_reference (&np -> n_ipaddr,
+ hd -> n_ipaddr, MDL);
+ host_dereference (&foo, MDL);
+ }
+ if (np)
+ host_dereference (&np, MDL);
+ }
+ host_dereference (&hp, MDL);
+ }
+ }
+ }
+
+ /* If we got a client identifier, hash this entry by
+ client identifier. */
+ if (hd -> client_identifier.len) {
+ if (host_uid_hash) {
+ if (host_hash_lookup (&hp, host_uid_hash,
+ hd -> client_identifier.data,
+ hd -> client_identifier.len, MDL)) {
+ if (hp == hd) {
+ host_hash_delete (host_uid_hash,
+ hd -> client_identifier.data,
+ hd -> client_identifier.len, MDL);
+ uid_head = 1;
+ } else {
+ np = (struct host_decl *)0;
+ foo = (struct host_decl *)0;
+ host_reference (&foo, hp, MDL);
+ while (foo) {
+ if (foo == hd)
+ break;
+ if (np)
+ host_dereference (&np, MDL);
+ host_reference (&np, foo, MDL);
+ host_dereference (&foo, MDL);
+ if (np -> n_ipaddr)
+ host_reference (&foo, np -> n_ipaddr, MDL);
+ }
+
+ if (foo) {
+ host_dereference (&np -> n_ipaddr, MDL);
+ if (hd -> n_ipaddr)
+ host_reference (&np -> n_ipaddr,
+ hd -> n_ipaddr, MDL);
+ host_dereference (&foo, MDL);
+ }
+ if (np)
+ host_dereference (&np, MDL);
+ }
+ host_dereference (&hp, MDL);
+ }
+ }
+ }
+
+ if (hd->host_id_option != NULL) {
+ option_dereference(&hd->host_id_option, MDL);
+ data_string_forget(&hd->host_id, MDL);
+ }
+
+ if (hd -> n_ipaddr) {
+ if (uid_head && hd -> n_ipaddr -> client_identifier.len) {
+ host_hash_add
+ (host_uid_hash,
+ hd -> n_ipaddr -> client_identifier.data,
+ hd -> n_ipaddr -> client_identifier.len,
+ hd -> n_ipaddr, MDL);
+ }
+ if (hw_head && hd -> n_ipaddr -> interface.hlen) {
+ host_hash_add (host_hw_addr_hash,
+ hd -> n_ipaddr -> interface.hbuf,
+ hd -> n_ipaddr -> interface.hlen,
+ hd -> n_ipaddr, MDL);
+ }
+ host_dereference (&hd -> n_ipaddr, MDL);
+ }
+
+ if (host_name_hash) {
+ if (host_hash_lookup (&hp, host_name_hash,
+ (unsigned char *)hd -> name,
+ strlen (hd -> name), MDL)) {
+ if (hp == hd && !(hp -> flags & HOST_DECL_STATIC)) {
+ host_hash_delete (host_name_hash,
+ (unsigned char *)hd -> name,
+ strlen (hd -> name), MDL);
+ }
+ host_dereference (&hp, MDL);
+ }
+ }
+
+ if (commit) {
+ if (!write_host (hd))
+ return ISC_R_IOERROR;
+ if (!commit_leases ())
+ return ISC_R_IOERROR;
+ }
+ return ISC_R_SUCCESS;
+}
+
+int find_hosts_by_haddr (struct host_decl **hp, int htype,
+ const unsigned char *haddr, unsigned hlen,
+ const char *file, int line)
+{
+ struct hardware h;
+#if defined(LDAP_CONFIGURATION)
+ int ret;
+
+ if ((ret = find_haddr_in_ldap (hp, htype, hlen, haddr, file, line)))
+ return ret;
+#endif
+
+ h.hlen = hlen + 1;
+ h.hbuf [0] = htype;
+ memcpy (&h.hbuf [1], haddr, hlen);
+
+ return host_hash_lookup (hp, host_hw_addr_hash,
+ h.hbuf, h.hlen, file, line);
+}
+
+int find_hosts_by_uid (struct host_decl **hp,
+ const unsigned char *data, unsigned len,
+ const char *file, int line)
+{
+ return host_hash_lookup (hp, host_uid_hash, data, len, file, line);
+}
+
+int
+find_hosts_by_option(struct host_decl **hp,
+ struct packet *packet,
+ struct option_state *opt_state,
+ const char *file, int line) {
+ host_id_info_t *p;
+ struct option_cache *oc;
+ struct data_string data;
+ int found;
+
+ for (p = host_id_info; p != NULL; p = p->next) {
+ oc = lookup_option(p->option->universe,
+ opt_state, p->option->code);
+ if (oc != NULL) {
+ memset(&data, 0, sizeof(data));
+ if (!evaluate_option_cache(&data, packet, NULL, NULL,
+ opt_state, NULL,
+ &global_scope, oc,
+ MDL)) {
+ log_error("Error evaluating option cache");
+ return 0;
+ }
+
+ found = host_hash_lookup(hp, p->values_hash,
+ data.data, data.len,
+ file, line);
+
+ data_string_forget(&data, MDL);
+
+ if (found) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+/* More than one host_decl can be returned by find_hosts_by_haddr or
+ find_hosts_by_uid, and each host_decl can have multiple addresses.
+ Loop through the list of hosts, and then for each host, through the
+ list of addresses, looking for an address that's in the same shared
+ network as the one specified. Store the matching address through
+ the addr pointer, update the host pointer to point at the host_decl
+ that matched, and return the subnet that matched. */
+
+int find_host_for_network (struct subnet **sp, struct host_decl **host,
+ struct iaddr *addr, struct shared_network *share)
+{
+ int i;
+ struct iaddr ip_address;
+ struct host_decl *hp;
+ struct data_string fixed_addr;
+
+ memset (&fixed_addr, 0, sizeof fixed_addr);
+
+ for (hp = *host; hp; hp = hp -> n_ipaddr) {
+ if (!hp -> fixed_addr)
+ continue;
+ if (!evaluate_option_cache (&fixed_addr, (struct packet *)0,
+ (struct lease *)0,
+ (struct client_state *)0,
+ (struct option_state *)0,
+ (struct option_state *)0,
+ &global_scope,
+ hp -> fixed_addr, MDL))
+ continue;
+ for (i = 0; i < fixed_addr.len; i += 4) {
+ ip_address.len = 4;
+ memcpy (ip_address.iabuf,
+ fixed_addr.data + i, 4);
+ if (find_grouped_subnet (sp, share, ip_address, MDL)) {
+ struct host_decl *tmp = (struct host_decl *)0;
+ *addr = ip_address;
+ /* This is probably not necessary, but
+ just in case *host is the only reference
+ to that host declaration, make a temporary
+ reference so that dereferencing it doesn't
+ dereference hp out from under us. */
+ host_reference (&tmp, *host, MDL);
+ host_dereference (host, MDL);
+ host_reference (host, hp, MDL);
+ host_dereference (&tmp, MDL);
+ data_string_forget (&fixed_addr, MDL);
+ return 1;
+ }
+ }
+ data_string_forget (&fixed_addr, MDL);
+ }
+ return 0;
+}
+
+void new_address_range (cfile, low, high, subnet, pool, lpchain)
+ struct parse *cfile;
+ struct iaddr low, high;
+ struct subnet *subnet;
+ struct pool *pool;
+ struct lease **lpchain;
+{
+#if defined(COMPACT_LEASES)
+ struct lease *address_range;
+#endif
+ unsigned min, max, i;
+ char lowbuf [16], highbuf [16], netbuf [16];
+ struct shared_network *share = subnet -> shared_network;
+ struct lease *lt = (struct lease *)0;
+#if !defined(COMPACT_LEASES)
+ isc_result_t status;
+#endif
+
+ /* All subnets should have attached shared network structures. */
+ if (!share) {
+ strcpy (netbuf, piaddr (subnet -> net));
+ log_fatal ("No shared network for network %s (%s)",
+ netbuf, piaddr (subnet -> netmask));
+ }
+
+ /* Initialize the hash table if it hasn't been done yet. */
+ if (!lease_uid_hash) {
+ if (!lease_id_new_hash(&lease_uid_hash, LEASE_HASH_SIZE, MDL))
+ log_fatal ("Can't allocate lease/uid hash");
+ }
+ if (!lease_ip_addr_hash) {
+ if (!lease_ip_new_hash(&lease_ip_addr_hash, LEASE_HASH_SIZE,
+ MDL))
+ log_fatal ("Can't allocate lease/ip hash");
+ }
+ if (!lease_hw_addr_hash) {
+ if (!lease_id_new_hash(&lease_hw_addr_hash, LEASE_HASH_SIZE,
+ MDL))
+ log_fatal ("Can't allocate lease/hw hash");
+ }
+
+ /* Make sure that high and low addresses are in this subnet. */
+ if (!addr_eq(subnet->net, subnet_number(low, subnet->netmask))) {
+ strcpy(lowbuf, piaddr(low));
+ strcpy(netbuf, piaddr(subnet->net));
+ log_fatal("bad range, address %s not in subnet %s netmask %s",
+ lowbuf, netbuf, piaddr(subnet->netmask));
+ }
+
+ if (!addr_eq(subnet->net, subnet_number(high, subnet->netmask))) {
+ strcpy(highbuf, piaddr(high));
+ strcpy(netbuf, piaddr(subnet->net));
+ log_fatal("bad range, address %s not in subnet %s netmask %s",
+ highbuf, netbuf, piaddr(subnet->netmask));
+ }
+
+ /* Get the high and low host addresses... */
+ max = host_addr (high, subnet -> netmask);
+ min = host_addr (low, subnet -> netmask);
+
+ /* Allow range to be specified high-to-low as well as low-to-high. */
+ if (min > max) {
+ max = min;
+ min = host_addr (high, subnet -> netmask);
+ }
+
+ /* Get a lease structure for each address in the range. */
+#if defined (COMPACT_LEASES)
+ address_range = new_leases (max - min + 1, MDL);
+ if (!address_range) {
+ strcpy (lowbuf, piaddr (low));
+ strcpy (highbuf, piaddr (high));
+ log_fatal ("No memory for address range %s-%s.",
+ lowbuf, highbuf);
+ }
+#endif
+
+ /* Fill out the lease structures with some minimal information. */
+ for (i = 0; i < max - min + 1; i++) {
+ struct lease *lp = (struct lease *)0;
+#if defined (COMPACT_LEASES)
+ omapi_object_initialize ((omapi_object_t *)&address_range [i],
+ dhcp_type_lease,
+ 0, sizeof (struct lease), MDL);
+ lease_reference (&lp, &address_range [i], MDL);
+#else
+ status = lease_allocate (&lp, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("No memory for lease %s: %s",
+ piaddr (ip_addr (subnet -> net,
+ subnet -> netmask,
+ i + min)),
+ isc_result_totext (status));
+#endif
+ lp->ip_addr = ip_addr(subnet->net, subnet->netmask, i + min);
+ lp->starts = MIN_TIME;
+ lp->ends = MIN_TIME;
+ subnet_reference(&lp->subnet, subnet, MDL);
+ pool_reference(&lp->pool, pool, MDL);
+ lp->binding_state = FTS_FREE;
+ lp->next_binding_state = FTS_FREE;
+ lp->rewind_binding_state = FTS_FREE;
+ lp->flags = 0;
+
+ /* Remember the lease in the IP address hash. */
+ if (find_lease_by_ip_addr (&lt, lp -> ip_addr, MDL)) {
+ if (lt -> pool) {
+ parse_warn (cfile,
+ "lease %s is declared twice!",
+ piaddr (lp -> ip_addr));
+ } else
+ pool_reference (&lt -> pool, pool, MDL);
+ lease_dereference (&lt, MDL);
+ } else
+ lease_ip_hash_add(lease_ip_addr_hash,
+ lp->ip_addr.iabuf, lp->ip_addr.len,
+ lp, MDL);
+ /* Put the lease on the chain for the caller. */
+ if (lpchain) {
+ if (*lpchain) {
+ lease_reference (&lp -> next, *lpchain, MDL);
+ lease_dereference (lpchain, MDL);
+ }
+ lease_reference (lpchain, lp, MDL);
+ }
+ lease_dereference (&lp, MDL);
+ }
+}
+
+int find_subnet (struct subnet **sp,
+ struct iaddr addr, const char *file, int line)
+{
+ struct subnet *rv;
+
+ for (rv = subnets; rv; rv = rv -> next_subnet) {
+ if (addr_eq (subnet_number (addr, rv -> netmask), rv -> net)) {
+ if (subnet_reference (sp, rv,
+ file, line) != ISC_R_SUCCESS)
+ return 0;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int find_grouped_subnet (struct subnet **sp,
+ struct shared_network *share, struct iaddr addr,
+ const char *file, int line)
+{
+ struct subnet *rv;
+
+ for (rv = share -> subnets; rv; rv = rv -> next_sibling) {
+ if (addr_eq (subnet_number (addr, rv -> netmask), rv -> net)) {
+ if (subnet_reference (sp, rv,
+ file, line) != ISC_R_SUCCESS)
+ return 0;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* XXX: could speed up if everyone had a prefix length */
+int
+subnet_inner_than(const struct subnet *subnet,
+ const struct subnet *scan,
+ int warnp) {
+ if (addr_eq(subnet_number(subnet->net, scan->netmask), scan->net) ||
+ addr_eq(subnet_number(scan->net, subnet->netmask), subnet->net)) {
+ char n1buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255")];
+ int i, j;
+ for (i = 0; i < 128; i++)
+ if (subnet->netmask.iabuf[3 - (i >> 3)]
+ & (1 << (i & 7)))
+ break;
+ for (j = 0; j < 128; j++)
+ if (scan->netmask.iabuf[3 - (j >> 3)] &
+ (1 << (j & 7)))
+ break;
+ if (warnp) {
+ strcpy(n1buf, piaddr(subnet->net));
+ log_error("Warning: subnet %s/%d overlaps subnet %s/%d",
+ n1buf, 32 - i,
+ piaddr(scan->net), 32 - j);
+ }
+ if (i < j)
+ return 1;
+ }
+ return 0;
+}
+
+/* Enter a new subnet into the subnet list. */
+void enter_subnet (subnet)
+ struct subnet *subnet;
+{
+ struct subnet *scan = (struct subnet *)0;
+ struct subnet *next = (struct subnet *)0;
+ struct subnet *prev = (struct subnet *)0;
+
+ /* Check for duplicates... */
+ if (subnets)
+ subnet_reference (&next, subnets, MDL);
+ while (next) {
+ subnet_reference (&scan, next, MDL);
+ subnet_dereference (&next, MDL);
+
+ /* When we find a conflict, make sure that the
+ subnet with the narrowest subnet mask comes
+ first. */
+ if (subnet_inner_than (subnet, scan, 1)) {
+ if (prev) {
+ if (prev -> next_subnet)
+ subnet_dereference (&prev -> next_subnet, MDL);
+ subnet_reference (&prev -> next_subnet, subnet, MDL);
+ subnet_dereference (&prev, MDL);
+ } else {
+ subnet_dereference (&subnets, MDL);
+ subnet_reference (&subnets, subnet, MDL);
+ }
+ subnet_reference (&subnet -> next_subnet, scan, MDL);
+ subnet_dereference (&scan, MDL);
+ return;
+ }
+ subnet_reference (&prev, scan, MDL);
+ subnet_dereference (&scan, MDL);
+ }
+ if (prev)
+ subnet_dereference (&prev, MDL);
+
+ /* XXX use the BSD radix tree code instead of a linked list. */
+ if (subnets) {
+ subnet_reference (&subnet -> next_subnet, subnets, MDL);
+ subnet_dereference (&subnets, MDL);
+ }
+ subnet_reference (&subnets, subnet, MDL);
+}
+
+/* Enter a new shared network into the shared network list. */
+
+void enter_shared_network (share)
+ struct shared_network *share;
+{
+ if (shared_networks) {
+ shared_network_reference (&share -> next,
+ shared_networks, MDL);
+ shared_network_dereference (&shared_networks, MDL);
+ }
+ shared_network_reference (&shared_networks, share, MDL);
+}
+
+void new_shared_network_interface (cfile, share, name)
+ struct parse *cfile;
+ struct shared_network *share;
+ const char *name;
+{
+ struct interface_info *ip;
+ isc_result_t status;
+
+ if (share -> interface) {
+ parse_warn (cfile,
+ "A subnet or shared network can't be connected %s",
+ "to two interfaces.");
+ return;
+ }
+
+ for (ip = interfaces; ip; ip = ip -> next)
+ if (!strcmp (ip -> name, name))
+ break;
+ if (!ip) {
+ status = interface_allocate (&ip, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("new_shared_network_interface %s: %s",
+ name, isc_result_totext (status));
+ if (strlen (name) > sizeof ip -> name) {
+ memcpy (ip -> name, name, (sizeof ip -> name) - 1);
+ ip -> name [(sizeof ip -> name) - 1] = 0;
+ } else
+ strcpy (ip -> name, name);
+ if (interfaces) {
+ interface_reference (&ip -> next, interfaces, MDL);
+ interface_dereference (&interfaces, MDL);
+ }
+ interface_reference (&interfaces, ip, MDL);
+ ip -> flags = INTERFACE_REQUESTED;
+ /* XXX this is a reference loop. */
+ shared_network_reference (&ip -> shared_network, share, MDL);
+ interface_reference (&share -> interface, ip, MDL);
+ }
+}
+
+/* Enter a lease into the system. This is called by the parser each
+ time it reads in a new lease. If the subnet for that lease has
+ already been read in (usually the case), just update that lease;
+ otherwise, allocate temporary storage for the lease and keep it around
+ until we're done reading in the config file. */
+
+void enter_lease (lease)
+ struct lease *lease;
+{
+ struct lease *comp = (struct lease *)0;
+
+ if (find_lease_by_ip_addr (&comp, lease -> ip_addr, MDL)) {
+ if (!comp -> pool) {
+ log_error ("undeclared lease found in database: %s",
+ piaddr (lease -> ip_addr));
+ } else
+ pool_reference (&lease -> pool, comp -> pool, MDL);
+
+ if (comp -> subnet)
+ subnet_reference (&lease -> subnet,
+ comp -> subnet, MDL);
+ lease_ip_hash_delete(lease_ip_addr_hash,
+ lease->ip_addr.iabuf, lease->ip_addr.len,
+ MDL);
+ lease_dereference (&comp, MDL);
+ }
+
+ /* The only way a lease can get here without a subnet is if it's in
+ the lease file, but not in the dhcpd.conf file. In this case, we
+ *should* keep it around until it's expired, but never reallocate it
+ or renew it. Currently, to maintain consistency, we are not doing
+ this.
+ XXX fix this so that the lease is kept around until it expires.
+ XXX this will be important in IPv6 with addresses that become
+ XXX non-renewable as a result of a renumbering event. */
+
+ if (!lease -> subnet) {
+ log_error ("lease %s: no subnet.", piaddr (lease -> ip_addr));
+ return;
+ }
+ lease_ip_hash_add(lease_ip_addr_hash, lease->ip_addr.iabuf,
+ lease->ip_addr.len, lease, MDL);
+}
+
+/* Replace the data in an existing lease with the data in a new lease;
+ adjust hash tables to suit, and insertion sort the lease into the
+ list of leases by expiry time so that we can always find the oldest
+ lease. */
+
+int supersede_lease (comp, lease, commit, propogate, pimmediate, from_pool)
+ struct lease *comp, *lease;
+ int commit;
+ int propogate;
+ int pimmediate;
+ int from_pool;
+{
+ struct lease *lp, **lq, *prev;
+ struct timeval tv;
+#if defined (FAILOVER_PROTOCOL)
+ int do_pool_check = 0;
+
+ /* We must commit leases before sending updates regarding them
+ to failover peers. It is, therefore, an error to set pimmediate
+ and not commit. */
+ if (pimmediate && !commit)
+ return 0;
+#endif
+
+ /* If there is no sample lease, just do the move. */
+ if (!lease)
+ goto just_move_it;
+
+ /* Static leases are not currently kept in the database... */
+ if (lease -> flags & STATIC_LEASE)
+ return 1;
+
+ /* If the existing lease hasn't expired and has a different
+ unique identifier or, if it doesn't have a unique
+ identifier, a different hardware address, then the two
+ leases are in conflict. If the existing lease has a uid
+ and the new one doesn't, but they both have the same
+ hardware address, and dynamic bootp is allowed on this
+ lease, then we allow that, in case a dynamic BOOTP lease is
+ requested *after* a DHCP lease has been assigned. */
+
+ if (lease -> binding_state != FTS_ABANDONED &&
+ lease -> next_binding_state != FTS_ABANDONED &&
+ comp -> binding_state == FTS_ACTIVE &&
+ (((comp -> uid && lease -> uid) &&
+ (comp -> uid_len != lease -> uid_len ||
+ memcmp (comp -> uid, lease -> uid, comp -> uid_len))) ||
+ (!comp -> uid &&
+ ((comp -> hardware_addr.hlen !=
+ lease -> hardware_addr.hlen) ||
+ memcmp (comp -> hardware_addr.hbuf,
+ lease -> hardware_addr.hbuf,
+ comp -> hardware_addr.hlen))))) {
+ log_error ("Lease conflict at %s",
+ piaddr (comp -> ip_addr));
+ }
+
+ /* If there's a Unique ID, dissociate it from the hash
+ table and free it if necessary. */
+ if (comp->uid) {
+ uid_hash_delete(comp);
+ if (comp->uid != comp->uid_buf) {
+ dfree(comp->uid, MDL);
+ comp->uid_max = 0;
+ comp->uid_len = 0;
+ }
+ comp -> uid = (unsigned char *)0;
+ }
+
+ /* If there's a hardware address, remove the lease from its
+ * old position in the hash bucket's ordered list.
+ */
+ if (comp->hardware_addr.hlen)
+ hw_hash_delete(comp);
+
+ /* If the lease has been billed to a class, remove the billing. */
+ if (comp -> billing_class != lease -> billing_class) {
+ if (comp -> billing_class)
+ unbill_class (comp, comp -> billing_class);
+ if (lease -> billing_class)
+ bill_class (comp, lease -> billing_class);
+ }
+
+ /* Copy the data files, but not the linkages. */
+ comp -> starts = lease -> starts;
+ if (lease -> uid) {
+ if (lease -> uid_len <= sizeof (lease -> uid_buf)) {
+ memcpy (comp -> uid_buf,
+ lease -> uid, lease -> uid_len);
+ comp -> uid = &comp -> uid_buf [0];
+ comp -> uid_max = sizeof comp -> uid_buf;
+ comp -> uid_len = lease -> uid_len;
+ } else if (lease -> uid != &lease -> uid_buf [0]) {
+ comp -> uid = lease -> uid;
+ comp -> uid_max = lease -> uid_max;
+ lease -> uid = (unsigned char *)0;
+ lease -> uid_max = 0;
+ comp -> uid_len = lease -> uid_len;
+ lease -> uid_len = 0;
+ } else {
+ log_fatal ("corrupt lease uid."); /* XXX */
+ }
+ } else {
+ comp -> uid = (unsigned char *)0;
+ comp -> uid_len = comp -> uid_max = 0;
+ }
+ if (comp -> host)
+ host_dereference (&comp -> host, MDL);
+ host_reference (&comp -> host, lease -> host, MDL);
+ comp -> hardware_addr = lease -> hardware_addr;
+ comp -> flags = ((lease -> flags & ~PERSISTENT_FLAGS) |
+ (comp -> flags & ~EPHEMERAL_FLAGS));
+ if (comp -> scope)
+ binding_scope_dereference (&comp -> scope, MDL);
+ if (lease -> scope) {
+ binding_scope_reference (&comp -> scope, lease -> scope, MDL);
+ binding_scope_dereference (&lease -> scope, MDL);
+ }
+
+ if (comp -> agent_options)
+ option_chain_head_dereference (&comp -> agent_options, MDL);
+ if (lease -> agent_options) {
+ /* Only retain the agent options if the lease is still
+ affirmatively associated with a client. */
+ if (lease -> next_binding_state == FTS_ACTIVE ||
+ lease -> next_binding_state == FTS_EXPIRED)
+ option_chain_head_reference (&comp -> agent_options,
+ lease -> agent_options,
+ MDL);
+ option_chain_head_dereference (&lease -> agent_options, MDL);
+ }
+
+ /* Record the hostname information in the lease. */
+ if (comp -> client_hostname)
+ dfree (comp -> client_hostname, MDL);
+ comp -> client_hostname = lease -> client_hostname;
+ lease -> client_hostname = (char *)0;
+
+ if (lease -> on_expiry) {
+ if (comp -> on_expiry)
+ executable_statement_dereference (&comp -> on_expiry,
+ MDL);
+ executable_statement_reference (&comp -> on_expiry,
+ lease -> on_expiry,
+ MDL);
+ }
+ if (lease -> on_commit) {
+ if (comp -> on_commit)
+ executable_statement_dereference (&comp -> on_commit,
+ MDL);
+ executable_statement_reference (&comp -> on_commit,
+ lease -> on_commit,
+ MDL);
+ }
+ if (lease -> on_release) {
+ if (comp -> on_release)
+ executable_statement_dereference (&comp -> on_release,
+ MDL);
+ executable_statement_reference (&comp -> on_release,
+ lease -> on_release, MDL);
+ }
+
+ /* Record the lease in the uid hash if necessary. */
+ if (comp->uid)
+ uid_hash_add(comp);
+
+ /* Record it in the hardware address hash if necessary. */
+ if (comp->hardware_addr.hlen)
+ hw_hash_add(comp);
+
+ comp->cltt = lease->cltt;
+#if defined (FAILOVER_PROTOCOL)
+ comp->tstp = lease->tstp;
+ comp->tsfp = lease->tsfp;
+ comp->atsfp = lease->atsfp;
+#endif /* FAILOVER_PROTOCOL */
+ comp->ends = lease->ends;
+ comp->next_binding_state = lease->next_binding_state;
+
+ /*
+ * If we have a control block pointer copy it in.
+ * We don't zero out an older ponter as it is still
+ * in use. We shouldn't need to overwrite an
+ * old pointer with a new one as the old transaction
+ * should have been cancelled before getting here.
+ */
+ if (lease->ddns_cb != NULL)
+ comp->ddns_cb = lease->ddns_cb;
+
+ just_move_it:
+#if defined (FAILOVER_PROTOCOL)
+ /*
+ * Atsfp should be cleared upon any state change that implies
+ * propagation whether supersede_lease was given a copy lease
+ * structure or not (often from the pool_timer()).
+ */
+ if (propogate)
+ comp->atsfp = 0;
+#endif /* FAILOVER_PROTOCOL */
+
+ if (!comp -> pool) {
+ log_error ("Supersede_lease: lease %s with no pool.",
+ piaddr (comp -> ip_addr));
+ return 0;
+ }
+
+ /* Figure out which queue it's on. */
+ switch (comp -> binding_state) {
+ case FTS_FREE:
+ if (comp->flags & RESERVED_LEASE)
+ lq = &comp->pool->reserved;
+ else {
+ lq = &comp->pool->free;
+ comp->pool->free_leases--;
+ }
+
+#if defined(FAILOVER_PROTOCOL)
+ do_pool_check = 1;
+#endif
+ break;
+
+ case FTS_ACTIVE:
+ lq = &comp -> pool -> active;
+ break;
+
+ case FTS_EXPIRED:
+ case FTS_RELEASED:
+ case FTS_RESET:
+ lq = &comp -> pool -> expired;
+ break;
+
+ case FTS_ABANDONED:
+ lq = &comp -> pool -> abandoned;
+ break;
+
+ case FTS_BACKUP:
+ if (comp->flags & RESERVED_LEASE)
+ lq = &comp->pool->reserved;
+ else {
+ lq = &comp->pool->backup;
+ comp->pool->backup_leases--;
+ }
+
+#if defined(FAILOVER_PROTOCOL)
+ do_pool_check = 1;
+#endif
+ break;
+
+ default:
+ log_error ("Lease with bogus binding state: %d",
+ comp -> binding_state);
+#if defined (BINDING_STATE_DEBUG)
+ abort ();
+#endif
+ return 0;
+ }
+
+ /* Remove the lease from its current place in its current
+ timer sequence. */
+ /* XXX this is horrid. */
+ prev = (struct lease *)0;
+ for (lp = *lq; lp; lp = lp -> next) {
+ if (lp == comp)
+ break;
+ prev = lp;
+ }
+
+ if (!lp) {
+ log_fatal("Lease with binding state %s not on its queue.",
+ (comp->binding_state < 1 ||
+ comp->binding_state > FTS_LAST)
+ ? "unknown"
+ : binding_state_names[comp->binding_state - 1]);
+ }
+
+ if (prev) {
+ lease_dereference (&prev -> next, MDL);
+ if (comp -> next) {
+ lease_reference (&prev -> next, comp -> next, MDL);
+ lease_dereference (&comp -> next, MDL);
+ }
+ } else {
+ lease_dereference (lq, MDL);
+ if (comp -> next) {
+ lease_reference (lq, comp -> next, MDL);
+ lease_dereference (&comp -> next, MDL);
+ }
+ }
+
+ /* Make the state transition. */
+ if (commit || !pimmediate)
+ make_binding_state_transition (comp);
+
+ /* Put the lease back on the appropriate queue. If the lease
+ is corrupt (as detected by lease_enqueue), don't go any farther. */
+ if (!lease_enqueue (comp))
+ return 0;
+
+ /* If this is the next lease that will timeout on the pool,
+ zap the old timeout and set the timeout on this pool to the
+ time that the lease's next event will happen.
+
+ We do not actually set the timeout unless commit is true -
+ we don't want to thrash the timer queue when reading the
+ lease database. Instead, the database code calls the
+ expiry event on each pool after reading in the lease file,
+ and the expiry code sets the timer if there's anything left
+ to expire after it's run any outstanding expiry events on
+ the pool. */
+ if ((commit || !pimmediate) &&
+ comp -> sort_time != MIN_TIME &&
+ comp -> sort_time > cur_time &&
+ (comp -> sort_time < comp -> pool -> next_event_time ||
+ comp -> pool -> next_event_time == MIN_TIME)) {
+ comp -> pool -> next_event_time = comp -> sort_time;
+ tv . tv_sec = comp -> pool -> next_event_time;
+ tv . tv_usec = 0;
+ add_timeout (&tv,
+ pool_timer, comp -> pool,
+ (tvref_t)pool_reference,
+ (tvunref_t)pool_dereference);
+ }
+
+ if (commit) {
+#if defined(FAILOVER_PROTOCOL)
+ /*
+ * If commit and propogate are set, then we can save a
+ * possible fsync later in BNDUPD socket transmission by
+ * stepping the rewind state forward to the new state, in
+ * case it has changed. This is only worth doing if the
+ * failover connection is currently connected, as in this
+ * case it is likely we will be transmitting to the peer very
+ * shortly.
+ */
+ if (propogate && (comp->pool->failover_peer != NULL) &&
+ ((comp->pool->failover_peer->service_state ==
+ cooperating) ||
+ (comp->pool->failover_peer->service_state ==
+ not_responding)))
+ comp->rewind_binding_state = comp->binding_state;
+#endif
+
+ if (!write_lease (comp))
+ return 0;
+ if ((server_starting & SS_NOSYNC) == 0) {
+ if (!commit_leases ())
+ return 0;
+ }
+ }
+
+#if defined (FAILOVER_PROTOCOL)
+ if (propogate) {
+ comp -> desired_binding_state = comp -> binding_state;
+ if (!dhcp_failover_queue_update (comp, pimmediate))
+ return 0;
+ }
+ if (do_pool_check && comp->pool->failover_peer)
+ dhcp_failover_pool_check(comp->pool);
+#endif
+
+ /* If the current binding state has already expired and we haven't
+ * been called from pool_timer, do an expiry event right now.
+ */
+ /* XXX At some point we should optimize this so that we don't
+ XXX write the lease twice, but this is a safe way to fix the
+ XXX problem for 3.0 (I hope!). */
+ if ((from_pool == 0) &&
+ (commit || !pimmediate) &&
+ (comp->sort_time < cur_time) &&
+ (comp->next_binding_state != comp->binding_state))
+ pool_timer(comp->pool);
+
+ return 1;
+}
+
+void make_binding_state_transition (struct lease *lease)
+{
+
+#if defined (FAILOVER_PROTOCOL)
+ dhcp_failover_state_t *peer;
+
+ if (lease -> pool && lease -> pool -> failover_peer)
+ peer = lease -> pool -> failover_peer;
+ else
+ peer = (dhcp_failover_state_t *)0;
+#endif
+
+ /* If the lease was active and is now no longer active, but isn't
+ released, then it just expired, so do the expiry event. */
+ if (lease -> next_binding_state != lease -> binding_state &&
+ ((
+#if defined (FAILOVER_PROTOCOL)
+ peer &&
+ (lease->binding_state == FTS_EXPIRED ||
+ lease->binding_state == FTS_ACTIVE) &&
+ (lease->next_binding_state == FTS_FREE ||
+ lease->next_binding_state == FTS_BACKUP)) ||
+ (!peer &&
+#endif
+ lease -> binding_state == FTS_ACTIVE &&
+ lease -> next_binding_state != FTS_RELEASED))) {
+#if defined (NSUPDATE)
+ (void) ddns_removals(lease, NULL, NULL, ISC_TRUE);
+#endif
+ if (lease -> on_expiry) {
+ execute_statements ((struct binding_value **)0,
+ (struct packet *)0, lease,
+ (struct client_state *)0,
+ (struct option_state *)0,
+ (struct option_state *)0, /* XXX */
+ &lease -> scope,
+ lease -> on_expiry);
+ if (lease -> on_expiry)
+ executable_statement_dereference
+ (&lease -> on_expiry, MDL);
+ }
+
+ /* No sense releasing a lease after it's expired. */
+ if (lease -> on_release)
+ executable_statement_dereference (&lease -> on_release,
+ MDL);
+ /* Get rid of client-specific bindings that are only
+ correct when the lease is active. */
+ if (lease -> billing_class)
+ unbill_class (lease, lease -> billing_class);
+ if (lease -> agent_options)
+ option_chain_head_dereference (&lease -> agent_options,
+ MDL);
+ if (lease -> client_hostname) {
+ dfree (lease -> client_hostname, MDL);
+ lease -> client_hostname = (char *)0;
+ }
+ if (lease -> host)
+ host_dereference (&lease -> host, MDL);
+
+ /* Send the expiry time to the peer. */
+ lease -> tstp = lease -> ends;
+ }
+
+ /* If the lease was active and is now released, do the release
+ event. */
+ if (lease -> next_binding_state != lease -> binding_state &&
+ ((
+#if defined (FAILOVER_PROTOCOL)
+ peer &&
+ lease -> binding_state == FTS_RELEASED &&
+ (lease -> next_binding_state == FTS_FREE ||
+ lease -> next_binding_state == FTS_BACKUP)) ||
+ (!peer &&
+#endif
+ lease -> binding_state == FTS_ACTIVE &&
+ lease -> next_binding_state == FTS_RELEASED))) {
+#if defined (NSUPDATE)
+ /*
+ * Note: ddns_removals() is also iterated when the lease
+ * enters state 'released' in 'release_lease()'. The below
+ * is caught when a peer receives a BNDUPD from a failover
+ * peer; it may not have received the client's release (it
+ * may have been offline).
+ *
+ * We could remove the call from release_lease() because
+ * it will also catch here on the originating server after the
+ * peer acknowledges the state change. However, there could
+ * be many hours inbetween, and in this case we /know/ the
+ * client is no longer using the lease when we receive the
+ * release message. This is not true of expiry, where the
+ * peer may have extended the lease.
+ */
+ (void) ddns_removals(lease, NULL, NULL, ISC_TRUE);
+#endif
+ if (lease -> on_release) {
+ execute_statements ((struct binding_value **)0,
+ (struct packet *)0, lease,
+ (struct client_state *)0,
+ (struct option_state *)0,
+ (struct option_state *)0, /* XXX */
+ &lease -> scope,
+ lease -> on_release);
+ executable_statement_dereference (&lease -> on_release,
+ MDL);
+ }
+
+ /* A released lease can't expire. */
+ if (lease -> on_expiry)
+ executable_statement_dereference (&lease -> on_expiry,
+ MDL);
+
+ /* Get rid of client-specific bindings that are only
+ correct when the lease is active. */
+ if (lease -> billing_class)
+ unbill_class (lease, lease -> billing_class);
+ if (lease -> agent_options)
+ option_chain_head_dereference (&lease -> agent_options,
+ MDL);
+ if (lease -> client_hostname) {
+ dfree (lease -> client_hostname, MDL);
+ lease -> client_hostname = (char *)0;
+ }
+ if (lease -> host)
+ host_dereference (&lease -> host, MDL);
+
+ /* Send the release time (should be == cur_time) to the
+ peer. */
+ lease -> tstp = lease -> ends;
+ }
+
+#if defined (DEBUG_LEASE_STATE_TRANSITIONS)
+ log_debug ("lease %s moves from %s to %s",
+ piaddr (lease -> ip_addr),
+ binding_state_print (lease -> binding_state),
+ binding_state_print (lease -> next_binding_state));
+#endif
+
+ lease -> binding_state = lease -> next_binding_state;
+ switch (lease -> binding_state) {
+ case FTS_ACTIVE:
+#if defined (FAILOVER_PROTOCOL)
+ if (lease -> pool && lease -> pool -> failover_peer)
+ lease -> next_binding_state = FTS_EXPIRED;
+ else
+#endif
+ lease -> next_binding_state = FTS_FREE;
+ break;
+
+ case FTS_EXPIRED:
+ case FTS_RELEASED:
+ case FTS_ABANDONED:
+ case FTS_RESET:
+ lease->next_binding_state = FTS_FREE;
+#if defined(FAILOVER_PROTOCOL)
+ /* If we are not in partner_down, leases don't go from
+ EXPIRED to FREE on a timeout - only on an update.
+ If we're in partner_down, they expire at mclt past
+ the time we entered partner_down. */
+ if ((lease->pool != NULL) &&
+ (lease->pool->failover_peer != NULL) &&
+ (lease->pool->failover_peer->me.state == partner_down))
+ lease->tsfp =
+ (lease->pool->failover_peer->me.stos +
+ lease->pool->failover_peer->mclt);
+#endif /* FAILOVER_PROTOCOL */
+ break;
+
+ case FTS_FREE:
+ case FTS_BACKUP:
+ lease -> next_binding_state = lease -> binding_state;
+ break;
+ }
+#if defined (DEBUG_LEASE_STATE_TRANSITIONS)
+ log_debug ("lease %s: next binding state %s",
+ piaddr (lease -> ip_addr),
+ binding_state_print (lease -> next_binding_state));
+#endif
+}
+
+/* Copy the contents of one lease into another, correctly maintaining
+ reference counts. */
+int lease_copy (struct lease **lp,
+ struct lease *lease, const char *file, int line)
+{
+ struct lease *lt = (struct lease *)0;
+ isc_result_t status;
+
+ status = lease_allocate (&lt, MDL);
+ if (status != ISC_R_SUCCESS)
+ return 0;
+
+ lt -> ip_addr = lease -> ip_addr;
+ lt -> starts = lease -> starts;
+ lt -> ends = lease -> ends;
+ lt -> uid_len = lease -> uid_len;
+ lt -> uid_max = lease -> uid_max;
+ if (lease -> uid == lease -> uid_buf) {
+ lt -> uid = lt -> uid_buf;
+ memcpy (lt -> uid_buf, lease -> uid_buf, sizeof lt -> uid_buf);
+ } else if (!lease -> uid_max) {
+ lt -> uid = (unsigned char *)0;
+ } else {
+ lt -> uid = dmalloc (lt -> uid_max, MDL);
+ if (!lt -> uid) {
+ lease_dereference (&lt, MDL);
+ return 0;
+ }
+ memcpy (lt -> uid, lease -> uid, lease -> uid_max);
+ }
+ if (lease -> client_hostname) {
+ lt -> client_hostname =
+ dmalloc (strlen (lease -> client_hostname) + 1, MDL);
+ if (!lt -> client_hostname) {
+ lease_dereference (&lt, MDL);
+ return 0;
+ }
+ strcpy (lt -> client_hostname, lease -> client_hostname);
+ }
+ if (lease -> scope)
+ binding_scope_reference (&lt -> scope, lease -> scope, MDL);
+ if (lease -> agent_options)
+ option_chain_head_reference (&lt -> agent_options,
+ lease -> agent_options, MDL);
+ host_reference (&lt -> host, lease -> host, file, line);
+ subnet_reference (&lt -> subnet, lease -> subnet, file, line);
+ pool_reference (&lt -> pool, lease -> pool, file, line);
+ class_reference (&lt -> billing_class,
+ lease -> billing_class, file, line);
+ lt -> hardware_addr = lease -> hardware_addr;
+ if (lease -> on_expiry)
+ executable_statement_reference (&lt -> on_expiry,
+ lease -> on_expiry,
+ file, line);
+ if (lease -> on_commit)
+ executable_statement_reference (&lt -> on_commit,
+ lease -> on_commit,
+ file, line);
+ if (lease -> on_release)
+ executable_statement_reference (&lt -> on_release,
+ lease -> on_release,
+ file, line);
+ lt->flags = lease->flags;
+ lt->tstp = lease->tstp;
+ lt->tsfp = lease->tsfp;
+ lt->atsfp = lease->atsfp;
+ lt->cltt = lease -> cltt;
+ lt->binding_state = lease->binding_state;
+ lt->next_binding_state = lease->next_binding_state;
+ lt->rewind_binding_state = lease->rewind_binding_state;
+ status = lease_reference(lp, lt, file, line);
+ lease_dereference(&lt, MDL);
+ return status == ISC_R_SUCCESS;
+}
+
+/* Release the specified lease and re-hash it as appropriate. */
+void release_lease (lease, packet)
+ struct lease *lease;
+ struct packet *packet;
+{
+ /* If there are statements to execute when the lease is
+ released, execute them. */
+#if defined (NSUPDATE)
+ (void) ddns_removals(lease, NULL, NULL, ISC_FALSE);
+#endif
+ if (lease -> on_release) {
+ execute_statements ((struct binding_value **)0,
+ packet, lease, (struct client_state *)0,
+ packet -> options,
+ (struct option_state *)0, /* XXX */
+ &lease -> scope, lease -> on_release);
+ if (lease -> on_release)
+ executable_statement_dereference (&lease -> on_release,
+ MDL);
+ }
+
+ /* We do either the on_release or the on_expiry events, but
+ not both (it's possible that they could be the same,
+ in any case). */
+ if (lease -> on_expiry)
+ executable_statement_dereference (&lease -> on_expiry, MDL);
+
+ if (lease -> binding_state != FTS_FREE &&
+ lease -> binding_state != FTS_BACKUP &&
+ lease -> binding_state != FTS_RELEASED &&
+ lease -> binding_state != FTS_EXPIRED &&
+ lease -> binding_state != FTS_RESET) {
+ if (lease -> on_commit)
+ executable_statement_dereference (&lease -> on_commit,
+ MDL);
+
+ /* Blow away any bindings. */
+ if (lease -> scope)
+ binding_scope_dereference (&lease -> scope, MDL);
+
+ /* Set sort times to the present. */
+ lease -> ends = cur_time;
+ /* Lower layers of muckery set tstp to ->ends. But we send
+ * protocol messages before this. So it is best to set
+ * tstp now anyway.
+ */
+ lease->tstp = cur_time;
+#if defined (FAILOVER_PROTOCOL)
+ if (lease -> pool && lease -> pool -> failover_peer) {
+ dhcp_failover_state_t *peer = NULL;
+
+ if (lease->pool != NULL)
+ peer = lease->pool->failover_peer;
+
+ if ((peer->service_state == not_cooperating) &&
+ (((peer->i_am == primary) &&
+ (lease->rewind_binding_state == FTS_FREE)) ||
+ ((peer->i_am == secondary) &&
+ (lease->rewind_binding_state == FTS_BACKUP)))) {
+ lease->next_binding_state =
+ lease->rewind_binding_state;
+ } else
+ lease -> next_binding_state = FTS_RELEASED;
+ } else {
+ lease -> next_binding_state = FTS_FREE;
+ }
+#else
+ lease -> next_binding_state = FTS_FREE;
+#endif
+ supersede_lease(lease, NULL, 1, 1, 1, 0);
+ }
+}
+
+/* Abandon the specified lease (set its timeout to infinity and its
+ particulars to zero, and re-hash it as appropriate. */
+
+void abandon_lease (lease, message)
+ struct lease *lease;
+ const char *message;
+{
+ struct lease *lt = (struct lease *)0;
+#if defined (NSUPDATE)
+ (void) ddns_removals(lease, NULL, NULL, ISC_FALSE);
+#endif
+
+ if (!lease_copy (&lt, lease, MDL))
+ return;
+
+ if (lt->scope)
+ binding_scope_dereference(&lt->scope, MDL);
+
+ lt -> ends = cur_time; /* XXX */
+ lt -> next_binding_state = FTS_ABANDONED;
+
+ log_error ("Abandoning IP address %s: %s",
+ piaddr (lease -> ip_addr), message);
+ lt -> hardware_addr.hlen = 0;
+ if (lt -> uid && lt -> uid != lt -> uid_buf)
+ dfree (lt -> uid, MDL);
+ lt -> uid = (unsigned char *)0;
+ lt -> uid_len = 0;
+ lt -> uid_max = 0;
+ supersede_lease (lease, lt, 1, 1, 1, 0);
+ lease_dereference (&lt, MDL);
+}
+
+#if 0
+/*
+ * This doesn't appear to be in use for anything anymore.
+ * I'm ifdeffing it now and if there are no complaints in
+ * the future it will be removed.
+ * SAR
+ */
+
+/* Abandon the specified lease (set its timeout to infinity and its
+ particulars to zero, and re-hash it as appropriate. */
+
+void dissociate_lease (lease)
+ struct lease *lease;
+{
+ struct lease *lt = (struct lease *)0;
+#if defined (NSUPDATE)
+ (void) ddns_removals(lease, NULL, NULL, ISC_FALSE);
+#endif
+
+ if (!lease_copy (&lt, lease, MDL))
+ return;
+
+#if defined (FAILOVER_PROTOCOL)
+ if (lease -> pool && lease -> pool -> failover_peer) {
+ lt -> next_binding_state = FTS_RESET;
+ } else {
+ lt -> next_binding_state = FTS_FREE;
+ }
+#else
+ lt -> next_binding_state = FTS_FREE;
+#endif
+ lt -> ends = cur_time; /* XXX */
+ lt -> hardware_addr.hlen = 0;
+ if (lt -> uid && lt -> uid != lt -> uid_buf)
+ dfree (lt -> uid, MDL);
+ lt -> uid = (unsigned char *)0;
+ lt -> uid_len = 0;
+ lt -> uid_max = 0;
+ supersede_lease (lease, lt, 1, 1, 1, 0);
+ lease_dereference (&lt, MDL);
+}
+#endif
+
+/* Timer called when a lease in a particular pool expires. */
+void pool_timer (vpool)
+ void *vpool;
+{
+ struct pool *pool;
+ struct lease *next = NULL;
+ struct lease *lease = NULL;
+#define FREE_LEASES 0
+#define ACTIVE_LEASES 1
+#define EXPIRED_LEASES 2
+#define ABANDONED_LEASES 3
+#define BACKUP_LEASES 4
+#define RESERVED_LEASES 5
+ struct lease **lptr[RESERVED_LEASES+1];
+ TIME next_expiry = MAX_TIME;
+ int i;
+ struct timeval tv;
+
+ pool = (struct pool *)vpool;
+
+ lptr[FREE_LEASES] = &pool->free;
+ lptr[ACTIVE_LEASES] = &pool->active;
+ lptr[EXPIRED_LEASES] = &pool->expired;
+ lptr[ABANDONED_LEASES] = &pool->abandoned;
+ lptr[BACKUP_LEASES] = &pool->backup;
+ lptr[RESERVED_LEASES] = &pool->reserved;
+
+ for (i = FREE_LEASES; i <= RESERVED_LEASES; i++) {
+ /* If there's nothing on the queue, skip it. */
+ if (!*(lptr [i]))
+ continue;
+
+#if defined (FAILOVER_PROTOCOL)
+ if (pool->failover_peer &&
+ pool->failover_peer->me.state != partner_down) {
+ /*
+ * Normally the secondary doesn't initiate expiration
+ * events (unless in partner-down), but rather relies
+ * on the primary to expire the lease. However, when
+ * disconnected from its peer, the server is allowed to
+ * rewind a lease to the previous state that the peer
+ * would have recorded it. This means there may be
+ * opportunities for active->free or active->backup
+ * expirations while out of contact.
+ *
+ * Q: Should we limit this expiration to
+ * comms-interrupt rather than not-normal?
+ */
+ if ((i == ACTIVE_LEASES) &&
+ (pool->failover_peer->i_am == secondary) &&
+ (pool->failover_peer->me.state == normal))
+ continue;
+
+ /* Leases in an expired state don't move to
+ free because of a timeout unless we're in
+ partner_down. */
+ if (i == EXPIRED_LEASES)
+ continue;
+ }
+#endif
+ lease_reference(&lease, *(lptr [i]), MDL);
+
+ while (lease) {
+ /* Remember the next lease in the list. */
+ if (next)
+ lease_dereference(&next, MDL);
+ if (lease -> next)
+ lease_reference(&next, lease->next, MDL);
+
+ /* If we've run out of things to expire on this list,
+ stop. */
+ if (lease->sort_time > cur_time) {
+ if (lease->sort_time < next_expiry)
+ next_expiry = lease->sort_time;
+ break;
+ }
+
+ /* If there is a pending state change, and
+ this lease has gotten to the time when the
+ state change should happen, just call
+ supersede_lease on it to make the change
+ happen. */
+ if (lease->next_binding_state != lease->binding_state)
+ {
+#if defined(FAILOVER_PROTOCOL)
+ dhcp_failover_state_t *peer = NULL;
+
+ if (lease->pool != NULL)
+ peer = lease->pool->failover_peer;
+
+ /* Can we rewind the lease to a free state? */
+ if (peer != NULL &&
+ peer->service_state == not_cooperating &&
+ lease->next_binding_state == FTS_EXPIRED &&
+ ((peer->i_am == primary &&
+ lease->rewind_binding_state == FTS_FREE)
+ ||
+ (peer->i_am == secondary &&
+ lease->rewind_binding_state ==
+ FTS_BACKUP)))
+ lease->next_binding_state =
+ lease->rewind_binding_state;
+#endif
+ supersede_lease(lease, NULL, 1, 1, 1, 1);
+ }
+
+ lease_dereference(&lease, MDL);
+ if (next)
+ lease_reference(&lease, next, MDL);
+ }
+ if (next)
+ lease_dereference(&next, MDL);
+ if (lease)
+ lease_dereference(&lease, MDL);
+ }
+
+ /* If we found something to expire and its expiration time
+ * is either less than the current expiration time or the
+ * current expiration time is already expired update the
+ * timer.
+ */
+ if ((next_expiry != MAX_TIME) &&
+ ((pool->next_event_time > next_expiry) ||
+ (pool->next_event_time <= cur_time))) {
+ pool->next_event_time = next_expiry;
+ tv.tv_sec = pool->next_event_time;
+ tv.tv_usec = 0;
+ add_timeout (&tv, pool_timer, pool,
+ (tvref_t)pool_reference,
+ (tvunref_t)pool_dereference);
+ } else
+ pool->next_event_time = MIN_TIME;
+
+}
+
+/* Locate the lease associated with a given IP address... */
+
+int find_lease_by_ip_addr (struct lease **lp, struct iaddr addr,
+ const char *file, int line)
+{
+ return lease_ip_hash_lookup(lp, lease_ip_addr_hash, addr.iabuf,
+ addr.len, file, line);
+}
+
+int find_lease_by_uid (struct lease **lp, const unsigned char *uid,
+ unsigned len, const char *file, int line)
+{
+ if (len == 0)
+ return 0;
+ return lease_id_hash_lookup (lp, lease_uid_hash, uid, len, file, line);
+}
+
+int find_lease_by_hw_addr (struct lease **lp,
+ const unsigned char *hwaddr, unsigned hwlen,
+ const char *file, int line)
+{
+ if (hwlen == 0)
+ return (0);
+
+ /*
+ * If it's an infiniband address don't bother
+ * as we don't have a useful address to hash.
+ */
+ if ((hwlen == 1) && (hwaddr[0] == HTYPE_INFINIBAND))
+ return (0);
+
+ return (lease_id_hash_lookup(lp, lease_hw_addr_hash, hwaddr, hwlen,
+ file, line));
+}
+
+/* If the lease is preferred over the candidate, return truth. The
+ * 'cand' and 'lease' names are retained to read more clearly against
+ * the 'uid_hash_add' and 'hw_hash_add' functions (this is common logic
+ * to those two functions).
+ *
+ * 1) ACTIVE leases are preferred. The active lease with
+ * the longest lifetime is preferred over shortest.
+ * 2) "transitional states" are next, this time with the
+ * most recent CLTT.
+ * 3) free/backup/etc states are next, again with CLTT. In truth we
+ * should never see reset leases for this.
+ * 4) Abandoned leases are always dead last.
+ */
+static isc_boolean_t
+client_lease_preferred(struct lease *cand, struct lease *lease)
+{
+ if (cand->binding_state == FTS_ACTIVE) {
+ if (lease->binding_state == FTS_ACTIVE &&
+ lease->ends >= cand->ends)
+ return ISC_TRUE;
+ } else if (cand->binding_state == FTS_EXPIRED ||
+ cand->binding_state == FTS_RELEASED) {
+ if (lease->binding_state == FTS_ACTIVE)
+ return ISC_TRUE;
+
+ if ((lease->binding_state == FTS_EXPIRED ||
+ lease->binding_state == FTS_RELEASED) &&
+ lease->cltt >= cand->cltt)
+ return ISC_TRUE;
+ } else if (cand->binding_state != FTS_ABANDONED) {
+ if (lease->binding_state == FTS_ACTIVE ||
+ lease->binding_state == FTS_EXPIRED ||
+ lease->binding_state == FTS_RELEASED)
+ return ISC_TRUE;
+
+ if (lease->binding_state != FTS_ABANDONED &&
+ lease->cltt >= cand->cltt)
+ return ISC_TRUE;
+ } else /* (cand->binding_state == FTS_ABANDONED) */ {
+ if (lease->binding_state != FTS_ABANDONED ||
+ lease->cltt >= cand->cltt)
+ return ISC_TRUE;
+ }
+
+ return ISC_FALSE;
+}
+
+/* Add the specified lease to the uid hash. */
+void
+uid_hash_add(struct lease *lease)
+{
+ struct lease *head = NULL;
+ struct lease *cand = NULL;
+ struct lease *prev = NULL;
+ struct lease *next = NULL;
+
+ /* If it's not in the hash, just add it. */
+ if (!find_lease_by_uid (&head, lease -> uid, lease -> uid_len, MDL))
+ lease_id_hash_add(lease_uid_hash, lease->uid, lease->uid_len,
+ lease, MDL);
+ else {
+ /* Otherwise, insert it into the list in order of its
+ * preference for "resuming allocation to the client."
+ *
+ * Because we don't have control of the hash bucket index
+ * directly, we have to remove and re-insert the client
+ * id into the hash if we're inserting onto the head.
+ */
+ lease_reference(&cand, head, MDL);
+ while (cand != NULL) {
+ if (client_lease_preferred(cand, lease))
+ break;
+
+ if (prev != NULL)
+ lease_dereference(&prev, MDL);
+ lease_reference(&prev, cand, MDL);
+
+ if (cand->n_uid != NULL)
+ lease_reference(&next, cand->n_uid, MDL);
+
+ lease_dereference(&cand, MDL);
+
+ if (next != NULL) {
+ lease_reference(&cand, next, MDL);
+ lease_dereference(&next, MDL);
+ }
+ }
+
+ /* If we want to insert 'before cand', and prev is NULL,
+ * then it was the head of the list. Assume that position.
+ */
+ if (prev == NULL) {
+ lease_reference(&lease->n_uid, head, MDL);
+ lease_id_hash_delete(lease_uid_hash, lease->uid,
+ lease->uid_len, MDL);
+ lease_id_hash_add(lease_uid_hash, lease->uid,
+ lease->uid_len, lease, MDL);
+ } else /* (prev != NULL) */ {
+ if(prev->n_uid != NULL) {
+ lease_reference(&lease->n_uid, prev->n_uid,
+ MDL);
+ lease_dereference(&prev->n_uid, MDL);
+ }
+ lease_reference(&prev->n_uid, lease, MDL);
+
+ lease_dereference(&prev, MDL);
+ }
+
+ if (cand != NULL)
+ lease_dereference(&cand, MDL);
+ lease_dereference(&head, MDL);
+ }
+}
+
+/* Delete the specified lease from the uid hash. */
+
+void uid_hash_delete (lease)
+ struct lease *lease;
+{
+ struct lease *head = (struct lease *)0;
+ struct lease *scan;
+
+ /* If it's not in the hash, we have no work to do. */
+ if (!find_lease_by_uid (&head, lease -> uid, lease -> uid_len, MDL)) {
+ if (lease -> n_uid)
+ lease_dereference (&lease -> n_uid, MDL);
+ return;
+ }
+
+ /* If the lease we're freeing is at the head of the list,
+ remove the hash table entry and add a new one with the
+ next lease on the list (if there is one). */
+ if (head == lease) {
+ lease_id_hash_delete(lease_uid_hash, lease->uid,
+ lease->uid_len, MDL);
+ if (lease -> n_uid) {
+ lease_id_hash_add(lease_uid_hash, lease->n_uid->uid,
+ lease->n_uid->uid_len, lease->n_uid,
+ MDL);
+ lease_dereference (&lease -> n_uid, MDL);
+ }
+ } else {
+ /* Otherwise, look for the lease in the list of leases
+ attached to the hash table entry, and remove it if
+ we find it. */
+ for (scan = head; scan -> n_uid; scan = scan -> n_uid) {
+ if (scan -> n_uid == lease) {
+ lease_dereference (&scan -> n_uid, MDL);
+ if (lease -> n_uid) {
+ lease_reference (&scan -> n_uid,
+ lease -> n_uid, MDL);
+ lease_dereference (&lease -> n_uid,
+ MDL);
+ }
+ break;
+ }
+ }
+ }
+ lease_dereference (&head, MDL);
+}
+
+/* Add the specified lease to the hardware address hash. */
+/* We don't add leases with infiniband addresses to the
+ * hash as there isn't any address to hash on. */
+
+void
+hw_hash_add(struct lease *lease)
+{
+ struct lease *head = NULL;
+ struct lease *cand = NULL;
+ struct lease *prev = NULL;
+ struct lease *next = NULL;
+
+ /*
+ * If it's an infiniband address don't bother
+ * as we don't have a useful address to hash.
+ */
+ if ((lease->hardware_addr.hlen == 1) &&
+ (lease->hardware_addr.hbuf[0] == HTYPE_INFINIBAND))
+ return;
+
+ /* If it's not in the hash, just add it. */
+ if (!find_lease_by_hw_addr (&head, lease -> hardware_addr.hbuf,
+ lease -> hardware_addr.hlen, MDL))
+ lease_id_hash_add(lease_hw_addr_hash,
+ lease->hardware_addr.hbuf,
+ lease->hardware_addr.hlen, lease, MDL);
+ else {
+ /* Otherwise, insert it into the list in order of its
+ * preference for "resuming allocation to the client."
+ *
+ * Because we don't have control of the hash bucket index
+ * directly, we have to remove and re-insert the client
+ * id into the hash if we're inserting onto the head.
+ */
+ lease_reference(&cand, head, MDL);
+ while (cand != NULL) {
+ if (client_lease_preferred(cand, lease))
+ break;
+
+ if (prev != NULL)
+ lease_dereference(&prev, MDL);
+ lease_reference(&prev, cand, MDL);
+
+ if (cand->n_hw != NULL)
+ lease_reference(&next, cand->n_hw, MDL);
+
+ lease_dereference(&cand, MDL);
+
+ if (next != NULL) {
+ lease_reference(&cand, next, MDL);
+ lease_dereference(&next, MDL);
+ }
+ }
+
+ /* If we want to insert 'before cand', and prev is NULL,
+ * then it was the head of the list. Assume that position.
+ */
+ if (prev == NULL) {
+ lease_reference(&lease->n_hw, head, MDL);
+ lease_id_hash_delete(lease_hw_addr_hash,
+ lease->hardware_addr.hbuf,
+ lease->hardware_addr.hlen, MDL);
+ lease_id_hash_add(lease_hw_addr_hash,
+ lease->hardware_addr.hbuf,
+ lease->hardware_addr.hlen,
+ lease, MDL);
+ } else /* (prev != NULL) */ {
+ if(prev->n_hw != NULL) {
+ lease_reference(&lease->n_hw, prev->n_hw,
+ MDL);
+ lease_dereference(&prev->n_hw, MDL);
+ }
+ lease_reference(&prev->n_hw, lease, MDL);
+
+ lease_dereference(&prev, MDL);
+ }
+
+ if (cand != NULL)
+ lease_dereference(&cand, MDL);
+ lease_dereference(&head, MDL);
+ }
+}
+
+/* Delete the specified lease from the hardware address hash. */
+
+void hw_hash_delete (lease)
+ struct lease *lease;
+{
+ struct lease *head = (struct lease *)0;
+ struct lease *next = (struct lease *)0;
+
+ /*
+ * If it's an infiniband address don't bother
+ * as we don't have a useful address to hash.
+ */
+ if ((lease->hardware_addr.hlen == 1) &&
+ (lease->hardware_addr.hbuf[0] == HTYPE_INFINIBAND))
+ return;
+
+ /* If it's not in the hash, we have no work to do. */
+ if (!find_lease_by_hw_addr (&head, lease -> hardware_addr.hbuf,
+ lease -> hardware_addr.hlen, MDL)) {
+ if (lease -> n_hw)
+ lease_dereference (&lease -> n_hw, MDL);
+ return;
+ }
+
+ /* If the lease we're freeing is at the head of the list,
+ remove the hash table entry and add a new one with the
+ next lease on the list (if there is one). */
+ if (head == lease) {
+ lease_id_hash_delete(lease_hw_addr_hash,
+ lease->hardware_addr.hbuf,
+ lease->hardware_addr.hlen, MDL);
+ if (lease->n_hw) {
+ lease_id_hash_add(lease_hw_addr_hash,
+ lease->n_hw->hardware_addr.hbuf,
+ lease->n_hw->hardware_addr.hlen,
+ lease->n_hw, MDL);
+ lease_dereference(&lease->n_hw, MDL);
+ }
+ } else {
+ /* Otherwise, look for the lease in the list of leases
+ attached to the hash table entry, and remove it if
+ we find it. */
+ while (head -> n_hw) {
+ if (head -> n_hw == lease) {
+ lease_dereference (&head -> n_hw, MDL);
+ if (lease -> n_hw) {
+ lease_reference (&head -> n_hw,
+ lease -> n_hw, MDL);
+ lease_dereference (&lease -> n_hw,
+ MDL);
+ }
+ break;
+ }
+ lease_reference (&next, head -> n_hw, MDL);
+ lease_dereference (&head, MDL);
+ lease_reference (&head, next, MDL);
+ lease_dereference (&next, MDL);
+ }
+ }
+ if (head)
+ lease_dereference (&head, MDL);
+}
+
+/* Write all interesting leases to permanent storage. */
+
+int write_leases ()
+{
+ struct lease *l;
+ struct shared_network *s;
+ struct pool *p;
+ struct host_decl *hp;
+ struct group_object *gp;
+ struct hash_bucket *hb;
+ struct class *cp;
+ struct collection *colp;
+ int i;
+ int num_written;
+ struct lease **lptr[RESERVED_LEASES+1];
+
+ /* write all the dynamically-created class declarations. */
+ if (collections->classes) {
+ numclasseswritten = 0;
+ for (colp = collections ; colp ; colp = colp->next) {
+ for (cp = colp->classes ; cp ; cp = cp->nic) {
+ write_named_billing_class(
+ (unsigned char *)cp->name,
+ 0, cp);
+ }
+ }
+
+ /* XXXJAB this number doesn't include subclasses... */
+ log_info ("Wrote %d class decls to leases file.",
+ numclasseswritten);
+ }
+
+
+ /* Write all the dynamically-created group declarations. */
+ if (group_name_hash) {
+ num_written = 0;
+ for (i = 0; i < group_name_hash -> hash_count; i++) {
+ for (hb = group_name_hash -> buckets [i];
+ hb; hb = hb -> next) {
+ gp = (struct group_object *)hb -> value;
+ if ((gp -> flags & GROUP_OBJECT_DYNAMIC) ||
+ ((gp -> flags & GROUP_OBJECT_STATIC) &&
+ (gp -> flags & GROUP_OBJECT_DELETED))) {
+ if (!write_group (gp))
+ return 0;
+ ++num_written;
+ }
+ }
+ }
+ log_info ("Wrote %d group decls to leases file.", num_written);
+ }
+
+ /* Write all the deleted host declarations. */
+ if (host_name_hash) {
+ num_written = 0;
+ for (i = 0; i < host_name_hash -> hash_count; i++) {
+ for (hb = host_name_hash -> buckets [i];
+ hb; hb = hb -> next) {
+ hp = (struct host_decl *)hb -> value;
+ if (((hp -> flags & HOST_DECL_STATIC) &&
+ (hp -> flags & HOST_DECL_DELETED))) {
+ if (!write_host (hp))
+ return 0;
+ ++num_written;
+ }
+ }
+ }
+ log_info ("Wrote %d deleted host decls to leases file.",
+ num_written);
+ }
+
+ /* Write all the new, dynamic host declarations. */
+ if (host_name_hash) {
+ num_written = 0;
+ for (i = 0; i < host_name_hash -> hash_count; i++) {
+ for (hb = host_name_hash -> buckets [i];
+ hb; hb = hb -> next) {
+ hp = (struct host_decl *)hb -> value;
+ if ((hp -> flags & HOST_DECL_DYNAMIC)) {
+ if (!write_host (hp))
+ ++num_written;
+ }
+ }
+ }
+ log_info ("Wrote %d new dynamic host decls to leases file.",
+ num_written);
+ }
+
+#if defined (FAILOVER_PROTOCOL)
+ /* Write all the failover states. */
+ if (!dhcp_failover_write_all_states ())
+ return 0;
+#endif
+
+ /* Write all the leases. */
+ num_written = 0;
+ for (s = shared_networks; s; s = s -> next) {
+ for (p = s -> pools; p; p = p -> next) {
+ lptr [FREE_LEASES] = &p -> free;
+ lptr [ACTIVE_LEASES] = &p -> active;
+ lptr [EXPIRED_LEASES] = &p -> expired;
+ lptr [ABANDONED_LEASES] = &p -> abandoned;
+ lptr [BACKUP_LEASES] = &p -> backup;
+ lptr [RESERVED_LEASES] = &p->reserved;
+
+ for (i = FREE_LEASES; i <= RESERVED_LEASES; i++) {
+ for (l = *(lptr [i]); l; l = l -> next) {
+#if !defined (DEBUG_DUMP_ALL_LEASES)
+ if (l->hardware_addr.hlen != 0 || l->uid_len != 0 ||
+ l->tsfp != 0 || l->binding_state != FTS_FREE)
+#endif
+ {
+ if (!write_lease (l))
+ return 0;
+ num_written++;
+ }
+ }
+ }
+ }
+ }
+ log_info ("Wrote %d leases to leases file.", num_written);
+#ifdef DHCPv6
+ if (!write_leases6()) {
+ return 0;
+ }
+#endif /* DHCPv6 */
+ if (!commit_leases ())
+ return 0;
+ return 1;
+}
+
+/* In addition to placing this lease upon a lease queue depending on its
+ * state, it also keeps track of the number of FREE and BACKUP leases in
+ * existence, and sets the sort_time on the lease.
+ *
+ * Sort_time is used in pool_timer() to determine when the lease will
+ * bubble to the top of the list and be supersede_lease()'d into its next
+ * state (possibly, if all goes well). Example, ACTIVE leases move to
+ * EXPIRED state when the 'ends' value is reached, so that is its sort
+ * time. Most queues are sorted by 'ends', since it is generally best
+ * practice to re-use the oldest lease, to reduce address collision
+ * chances.
+ */
+int lease_enqueue (struct lease *comp)
+{
+ struct lease **lq, *prev, *lp;
+ static struct lease **last_lq = NULL;
+ static struct lease *last_insert_point = NULL;
+
+ /* No queue to put it on? */
+ if (!comp -> pool)
+ return 0;
+
+ /* Figure out which queue it's going to. */
+ switch (comp -> binding_state) {
+ case FTS_FREE:
+ if (comp->flags & RESERVED_LEASE) {
+ lq = &comp->pool->reserved;
+ } else {
+ lq = &comp->pool->free;
+ comp->pool->free_leases++;
+ }
+ comp -> sort_time = comp -> ends;
+ break;
+
+ case FTS_ACTIVE:
+ lq = &comp -> pool -> active;
+ comp -> sort_time = comp -> ends;
+ break;
+
+ case FTS_EXPIRED:
+ case FTS_RELEASED:
+ case FTS_RESET:
+ lq = &comp -> pool -> expired;
+#if defined(FAILOVER_PROTOCOL)
+ /* In partner_down, tsfp is the time at which the lease
+ * may be reallocated (stos+mclt). We can do that with
+ * lease_mine_to_reallocate() anywhere between tsfp and
+ * ends. But we prefer to wait until ends before doing it
+ * automatically (choose the greater of the two). Note
+ * that 'ends' is usually a historic timestamp in the
+ * case of expired leases, is really only in the future
+ * on released leases, and if we know a lease to be released
+ * the peer might still know it to be active...in which case
+ * it's possible the peer has renewed this lease, so avoid
+ * doing that.
+ */
+ if (comp->pool->failover_peer &&
+ comp->pool->failover_peer->me.state == partner_down)
+ comp->sort_time = (comp->tsfp > comp->ends) ?
+ comp->tsfp : comp->ends;
+ else
+#endif
+ comp->sort_time = comp->ends;
+
+ break;
+
+ case FTS_ABANDONED:
+ lq = &comp -> pool -> abandoned;
+ comp -> sort_time = comp -> ends;
+ break;
+
+ case FTS_BACKUP:
+ if (comp->flags & RESERVED_LEASE) {
+ lq = &comp->pool->reserved;
+ } else {
+ lq = &comp->pool->backup;
+ comp->pool->backup_leases++;
+ }
+ comp -> sort_time = comp -> ends;
+ break;
+
+ default:
+ log_error ("Lease with bogus binding state: %d",
+ comp -> binding_state);
+#if defined (BINDING_STATE_DEBUG)
+ abort ();
+#endif
+ return 0;
+ }
+
+ /* This only works during server startup: during runtime, the last
+ * lease may be dequeued in between calls. If the queue is the same
+ * as was used previously, and the lease structure isn't (this is not
+ * a re-queue), use that as a starting point for the insertion-sort.
+ */
+ if ((server_starting & SS_QFOLLOW) && (lq == last_lq) &&
+ (comp != last_insert_point) &&
+ (last_insert_point->sort_time <= comp->sort_time)) {
+ prev = last_insert_point;
+ lp = prev->next;
+ } else {
+ prev = NULL;
+ lp = *lq;
+ }
+
+ /* Insertion sort the lease onto the appropriate queue. */
+ for (; lp ; lp = lp->next) {
+ if (lp -> sort_time >= comp -> sort_time)
+ break;
+ prev = lp;
+ }
+
+ if (prev) {
+ if (prev -> next) {
+ lease_reference (&comp -> next, prev -> next, MDL);
+ lease_dereference (&prev -> next, MDL);
+ }
+ lease_reference (&prev -> next, comp, MDL);
+ } else {
+ if (*lq) {
+ lease_reference (&comp -> next, *lq, MDL);
+ lease_dereference (lq, MDL);
+ }
+ lease_reference (lq, comp, MDL);
+ }
+ last_insert_point = comp;
+ last_lq = lq;
+ return 1;
+}
+
+/* For a given lease, sort it onto the right list in its pool and put it
+ in each appropriate hash, understanding that it's already by definition
+ in lease_ip_addr_hash. */
+
+isc_result_t
+lease_instantiate(const void *key, unsigned len, void *object)
+{
+ struct lease *lease = object;
+ struct class *class;
+ /* XXX If the lease doesn't have a pool at this point, it's an
+ XXX orphan, which we *should* keep around until it expires,
+ XXX but which right now we just forget. */
+ if (!lease -> pool) {
+ lease_ip_hash_delete(lease_ip_addr_hash, lease->ip_addr.iabuf,
+ lease->ip_addr.len, MDL);
+ return ISC_R_SUCCESS;
+ }
+
+#if defined (CONVERT_BACKUP_TO_FREE)
+#if defined (FAILOVER_PROTOCOL)
+ /* If the lease is in FTS_BACKUP but there is no peer, then the
+ * pool must have been formerly configured for failover and
+ * is now configured as standalone. This means we need to
+ * move the lease to FTS_FREE to make it available. */
+ if ((lease->binding_state == FTS_BACKUP) &&
+ (lease->pool->failover_peer == NULL)) {
+#else
+ /* We aren't compiled for failover, so just move to FTS_FREE */
+ if (lease->binding_state == FTS_BACKUP) {
+#endif
+ lease->binding_state = FTS_FREE;
+ lease->next_binding_state = FTS_FREE;
+ lease->rewind_binding_state = FTS_FREE;
+ }
+#endif
+
+ /* Put the lease on the right queue. Failure to queue is probably
+ * due to a bogus binding state. In such a case, we claim success,
+ * so that later leases in a hash_foreach are processed, but we
+ * return early as we really don't want hw address hash entries or
+ * other cruft to surround such a bogus entry.
+ */
+ if (!lease_enqueue(lease))
+ return ISC_R_SUCCESS;
+
+ /* Record the lease in the uid hash if possible. */
+ if (lease -> uid) {
+ uid_hash_add (lease);
+ }
+
+ /* Record it in the hardware address hash if possible. */
+ if (lease -> hardware_addr.hlen) {
+ hw_hash_add (lease);
+ }
+
+ /* If the lease has a billing class, set up the billing. */
+ if (lease -> billing_class) {
+ class = (struct class *)0;
+ class_reference (&class, lease -> billing_class, MDL);
+ class_dereference (&lease -> billing_class, MDL);
+ /* If the lease is available for allocation, the billing
+ is invalid, so we don't keep it. */
+ if (lease -> binding_state == FTS_ACTIVE ||
+ lease -> binding_state == FTS_EXPIRED ||
+ lease -> binding_state == FTS_RELEASED ||
+ lease -> binding_state == FTS_RESET)
+ bill_class (lease, class);
+ class_dereference (&class, MDL);
+ }
+ return ISC_R_SUCCESS;
+}
+
+/* Run expiry events on every pool. This is called on startup so that
+ any expiry events that occurred after the server stopped and before it
+ was restarted can be run. At the same time, if failover support is
+ compiled in, we compute the balance of leases for the pool. */
+
+void expire_all_pools ()
+{
+ struct shared_network *s;
+ struct pool *p;
+ int i;
+ struct lease *l;
+ struct lease **lptr[RESERVED_LEASES+1];
+
+ /* Indicate that we are in the startup phase */
+ server_starting = SS_NOSYNC | SS_QFOLLOW;
+
+ /* First, go over the hash list and actually put all the leases
+ on the appropriate lists. */
+ lease_ip_hash_foreach(lease_ip_addr_hash, lease_instantiate);
+
+ /* Loop through each pool in each shared network and call the
+ * expiry routine on the pool. It is no longer safe to follow
+ * the queue insertion point, as expiration of a lease can move
+ * it between queues (and this may be the lease that function
+ * points at).
+ */
+ server_starting &= ~SS_QFOLLOW;
+ for (s = shared_networks; s; s = s -> next) {
+ for (p = s -> pools; p; p = p -> next) {
+ pool_timer (p);
+
+ p -> lease_count = 0;
+ p -> free_leases = 0;
+ p -> backup_leases = 0;
+
+ lptr [FREE_LEASES] = &p -> free;
+ lptr [ACTIVE_LEASES] = &p -> active;
+ lptr [EXPIRED_LEASES] = &p -> expired;
+ lptr [ABANDONED_LEASES] = &p -> abandoned;
+ lptr [BACKUP_LEASES] = &p -> backup;
+ lptr [RESERVED_LEASES] = &p->reserved;
+
+ for (i = FREE_LEASES; i <= RESERVED_LEASES; i++) {
+ for (l = *(lptr [i]); l; l = l -> next) {
+ p -> lease_count++;
+ if (l -> ends <= cur_time) {
+ if (l->binding_state == FTS_FREE) {
+ if (i == FREE_LEASES)
+ p->free_leases++;
+ else if (i != RESERVED_LEASES)
+ log_fatal("Impossible case "
+ "at %s:%d.", MDL);
+ } else if (l->binding_state == FTS_BACKUP) {
+ if (i == BACKUP_LEASES)
+ p->backup_leases++;
+ else if (i != RESERVED_LEASES)
+ log_fatal("Impossible case "
+ "at %s:%d.", MDL);
+ }
+ }
+#if defined (FAILOVER_PROTOCOL)
+ if (p -> failover_peer &&
+ l -> tstp > l -> atsfp &&
+ !(l -> flags & ON_UPDATE_QUEUE)) {
+ l -> desired_binding_state = l -> binding_state;
+ dhcp_failover_queue_update (l, 1);
+ }
+#endif
+ }
+ }
+ }
+ }
+
+ /* turn off startup phase */
+ server_starting = 0;
+}
+
+void dump_subnets ()
+{
+ struct lease *l;
+ struct shared_network *s;
+ struct subnet *n;
+ struct pool *p;
+ struct lease **lptr[RESERVED_LEASES+1];
+ int i;
+
+ log_info ("Subnets:");
+ for (n = subnets; n; n = n -> next_subnet) {
+ log_debug (" Subnet %s", piaddr (n -> net));
+ log_debug (" netmask %s",
+ piaddr (n -> netmask));
+ }
+ log_info ("Shared networks:");
+ for (s = shared_networks; s; s = s -> next) {
+ log_info (" %s", s -> name);
+ for (p = s -> pools; p; p = p -> next) {
+ lptr [FREE_LEASES] = &p -> free;
+ lptr [ACTIVE_LEASES] = &p -> active;
+ lptr [EXPIRED_LEASES] = &p -> expired;
+ lptr [ABANDONED_LEASES] = &p -> abandoned;
+ lptr [BACKUP_LEASES] = &p -> backup;
+ lptr [RESERVED_LEASES] = &p->reserved;
+
+ for (i = FREE_LEASES; i <= RESERVED_LEASES; i++) {
+ for (l = *(lptr [i]); l; l = l -> next) {
+ print_lease (l);
+ }
+ }
+ }
+ }
+}
+
+HASH_FUNCTIONS(lease_ip, const unsigned char *, struct lease, lease_ip_hash_t,
+ lease_reference, lease_dereference, do_ip4_hash)
+HASH_FUNCTIONS(lease_id, const unsigned char *, struct lease, lease_id_hash_t,
+ lease_reference, lease_dereference, do_id_hash)
+HASH_FUNCTIONS (host, const unsigned char *, struct host_decl, host_hash_t,
+ host_reference, host_dereference, do_string_hash)
+HASH_FUNCTIONS (class, const char *, struct class, class_hash_t,
+ class_reference, class_dereference, do_string_hash)
+
+#if defined (DEBUG_MEMORY_LEAKAGE) && \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+extern struct hash_table *dns_zone_hash;
+extern struct interface_info **interface_vector;
+extern int interface_count;
+dhcp_control_object_t *dhcp_control_object;
+extern struct hash_table *auth_key_hash;
+struct hash_table *universe_hash;
+struct universe **universes;
+int universe_count, universe_max;
+#if 0
+extern int end;
+#endif
+
+#if defined (COMPACT_LEASES)
+extern struct lease *lease_hunks;
+#endif
+
+void free_everything(void)
+{
+ struct subnet *sc = (struct subnet *)0, *sn = (struct subnet *)0;
+ struct shared_network *nc = (struct shared_network *)0,
+ *nn = (struct shared_network *)0;
+ struct pool *pc = (struct pool *)0, *pn = (struct pool *)0;
+ struct lease *lc = (struct lease *)0, *ln = (struct lease *)0;
+ struct interface_info *ic = (struct interface_info *)0,
+ *in = (struct interface_info *)0;
+ struct class *cc = (struct class *)0, *cn = (struct class *)0;
+ struct collection *lp;
+ int i;
+
+ /* Get rid of all the hash tables. */
+ if (host_hw_addr_hash)
+ host_free_hash_table (&host_hw_addr_hash, MDL);
+ host_hw_addr_hash = 0;
+ if (host_uid_hash)
+ host_free_hash_table (&host_uid_hash, MDL);
+ host_uid_hash = 0;
+ if (lease_uid_hash)
+ lease_id_free_hash_table (&lease_uid_hash, MDL);
+ lease_uid_hash = 0;
+ if (lease_ip_addr_hash)
+ lease_ip_free_hash_table (&lease_ip_addr_hash, MDL);
+ lease_ip_addr_hash = 0;
+ if (lease_hw_addr_hash)
+ lease_id_free_hash_table (&lease_hw_addr_hash, MDL);
+ lease_hw_addr_hash = 0;
+ if (host_name_hash)
+ host_free_hash_table (&host_name_hash, MDL);
+ host_name_hash = 0;
+ if (dns_zone_hash)
+ dns_zone_free_hash_table (&dns_zone_hash, MDL);
+ dns_zone_hash = 0;
+
+ while (host_id_info != NULL) {
+ host_id_info_t *tmp;
+ option_dereference(&host_id_info->option, MDL);
+ host_free_hash_table(&host_id_info->values_hash, MDL);
+ tmp = host_id_info->next;
+ dfree(host_id_info, MDL);
+ host_id_info = tmp;
+ }
+#if 0
+ if (auth_key_hash)
+ auth_key_free_hash_table (&auth_key_hash, MDL);
+#endif
+ auth_key_hash = 0;
+
+ omapi_object_dereference ((omapi_object_t **)&dhcp_control_object,
+ MDL);
+
+ for (lp = collections; lp; lp = lp -> next) {
+ if (lp -> classes) {
+ class_reference (&cn, lp -> classes, MDL);
+ do {
+ if (cn) {
+ class_reference (&cc, cn, MDL);
+ class_dereference (&cn, MDL);
+ }
+ if (cc -> nic) {
+ class_reference (&cn, cc -> nic, MDL);
+ class_dereference (&cc -> nic, MDL);
+ }
+ group_dereference (&cc -> group, MDL);
+ if (cc -> hash) {
+ class_free_hash_table (&cc -> hash, MDL);
+ cc -> hash = (struct hash_table *)0;
+ }
+ class_dereference (&cc, MDL);
+ } while (cn);
+ class_dereference (&lp -> classes, MDL);
+ }
+ }
+
+ if (interface_vector) {
+ for (i = 0; i < interface_count; i++) {
+ if (interface_vector [i])
+ interface_dereference (&interface_vector [i], MDL);
+ }
+ dfree (interface_vector, MDL);
+ interface_vector = 0;
+ }
+
+ if (interfaces) {
+ interface_reference (&in, interfaces, MDL);
+ do {
+ if (in) {
+ interface_reference (&ic, in, MDL);
+ interface_dereference (&in, MDL);
+ }
+ if (ic -> next) {
+ interface_reference (&in, ic -> next, MDL);
+ interface_dereference (&ic -> next, MDL);
+ }
+ omapi_unregister_io_object ((omapi_object_t *)ic);
+ if (ic -> shared_network) {
+ if (ic -> shared_network -> interface)
+ interface_dereference
+ (&ic -> shared_network -> interface, MDL);
+ shared_network_dereference (&ic -> shared_network, MDL);
+ }
+ interface_dereference (&ic, MDL);
+ } while (in);
+ interface_dereference (&interfaces, MDL);
+ }
+
+ /* Subnets are complicated because of the extra links. */
+ if (subnets) {
+ subnet_reference (&sn, subnets, MDL);
+ do {
+ if (sn) {
+ subnet_reference (&sc, sn, MDL);
+ subnet_dereference (&sn, MDL);
+ }
+ if (sc -> next_subnet) {
+ subnet_reference (&sn, sc -> next_subnet, MDL);
+ subnet_dereference (&sc -> next_subnet, MDL);
+ }
+ if (sc -> next_sibling)
+ subnet_dereference (&sc -> next_sibling, MDL);
+ if (sc -> shared_network)
+ shared_network_dereference (&sc -> shared_network, MDL);
+ group_dereference (&sc -> group, MDL);
+ if (sc -> interface)
+ interface_dereference (&sc -> interface, MDL);
+ subnet_dereference (&sc, MDL);
+ } while (sn);
+ subnet_dereference (&subnets, MDL);
+ }
+
+ /* So are shared networks. */
+ /* XXX: this doesn't work presently, but i'm ok just filtering
+ * it out of the noise (you get a bigger spike on the real leaks).
+ * It would be good to fix this, but it is not a "real bug," so not
+ * today. This hack is incomplete, it doesn't trim out sub-values.
+ */
+ if (shared_networks) {
+ shared_network_dereference (&shared_networks, MDL);
+ /* This is the old method (tries to free memory twice, broken) */
+ } else if (0) {
+ shared_network_reference (&nn, shared_networks, MDL);
+ do {
+ if (nn) {
+ shared_network_reference (&nc, nn, MDL);
+ shared_network_dereference (&nn, MDL);
+ }
+ if (nc -> next) {
+ shared_network_reference (&nn, nc -> next, MDL);
+ shared_network_dereference (&nc -> next, MDL);
+ }
+
+ /* As are pools. */
+ if (nc -> pools) {
+ pool_reference (&pn, nc -> pools, MDL);
+ do {
+ struct lease **lptr[RESERVED_LEASES+1];
+
+ if (pn) {
+ pool_reference (&pc, pn, MDL);
+ pool_dereference (&pn, MDL);
+ }
+ if (pc -> next) {
+ pool_reference (&pn, pc -> next, MDL);
+ pool_dereference (&pc -> next, MDL);
+ }
+
+ lptr [FREE_LEASES] = &pc -> free;
+ lptr [ACTIVE_LEASES] = &pc -> active;
+ lptr [EXPIRED_LEASES] = &pc -> expired;
+ lptr [ABANDONED_LEASES] = &pc -> abandoned;
+ lptr [BACKUP_LEASES] = &pc -> backup;
+ lptr [RESERVED_LEASES] = &pc->reserved;
+
+ /* As (sigh) are leases. */
+ for (i = FREE_LEASES ; i <= RESERVED_LEASES ; i++) {
+ if (*lptr [i]) {
+ lease_reference (&ln, *lptr [i], MDL);
+ do {
+ if (ln) {
+ lease_reference (&lc, ln, MDL);
+ lease_dereference (&ln, MDL);
+ }
+ if (lc -> next) {
+ lease_reference (&ln, lc -> next, MDL);
+ lease_dereference (&lc -> next, MDL);
+ }
+ if (lc -> billing_class)
+ class_dereference (&lc -> billing_class,
+ MDL);
+ if (lc -> state)
+ free_lease_state (lc -> state, MDL);
+ lc -> state = (struct lease_state *)0;
+ if (lc -> n_hw)
+ lease_dereference (&lc -> n_hw, MDL);
+ if (lc -> n_uid)
+ lease_dereference (&lc -> n_uid, MDL);
+ lease_dereference (&lc, MDL);
+ } while (ln);
+ lease_dereference (lptr [i], MDL);
+ }
+ }
+ if (pc -> group)
+ group_dereference (&pc -> group, MDL);
+ if (pc -> shared_network)
+ shared_network_dereference (&pc -> shared_network,
+ MDL);
+ pool_dereference (&pc, MDL);
+ } while (pn);
+ pool_dereference (&nc -> pools, MDL);
+ }
+ /* Because of a circular reference, we need to nuke this
+ manually. */
+ group_dereference (&nc -> group, MDL);
+ shared_network_dereference (&nc, MDL);
+ } while (nn);
+ shared_network_dereference (&shared_networks, MDL);
+ }
+
+ cancel_all_timeouts ();
+ relinquish_timeouts ();
+ relinquish_ackqueue();
+ trace_free_all ();
+ group_dereference (&root_group, MDL);
+ executable_statement_dereference (&default_classification_rules, MDL);
+
+ shutdown_state = shutdown_drop_omapi_connections;
+ omapi_io_state_foreach (dhcp_io_shutdown, 0);
+ shutdown_state = shutdown_listeners;
+ omapi_io_state_foreach (dhcp_io_shutdown, 0);
+ shutdown_state = shutdown_dhcp;
+ omapi_io_state_foreach (dhcp_io_shutdown, 0);
+
+ omapi_object_dereference ((omapi_object_t **)&icmp_state, MDL);
+
+ universe_free_hash_table (&universe_hash, MDL);
+ for (i = 0; i < universe_count; i++) {
+#if 0
+ union {
+ const char *c;
+ char *s;
+ } foo;
+#endif
+ if (universes [i]) {
+ if (universes[i]->name_hash)
+ option_name_free_hash_table(
+ &universes[i]->name_hash,
+ MDL);
+ if (universes[i]->code_hash)
+ option_code_free_hash_table(
+ &universes[i]->code_hash,
+ MDL);
+#if 0
+ if (universes [i] -> name > (char *)&end) {
+ foo.c = universes [i] -> name;
+ dfree (foo.s, MDL);
+ }
+ if (universes [i] > (struct universe *)&end)
+ dfree (universes [i], MDL);
+#endif
+ }
+ }
+ dfree (universes, MDL);
+
+ relinquish_free_lease_states ();
+ relinquish_free_pairs ();
+ relinquish_free_expressions ();
+ relinquish_free_binding_values ();
+ relinquish_free_option_caches ();
+ relinquish_free_packets ();
+#if defined(COMPACT_LEASES)
+ relinquish_lease_hunks ();
+#endif
+ relinquish_hash_bucket_hunks ();
+ omapi_type_relinquish ();
+}
+#endif /* DEBUG_MEMORY_LEAKAGE_ON_EXIT */
diff --git a/server/mdb6.c b/server/mdb6.c
new file mode 100644
index 0000000..0e76264
--- /dev/null
+++ b/server/mdb6.c
@@ -0,0 +1,2147 @@
+/*
+ * Copyright (C) 2007-2012 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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.
+ */
+
+/*!
+ * \todo assert()
+ * \todo simplify functions, as pool is now in iaaddr
+ */
+
+/*! \file server/mdb6.c
+ *
+ * \page ipv6structures IPv6 Structures Overview
+ *
+ * A brief description of the IPv6 structures as reverse engineered.
+ *
+ * There are three major data strucutes involved in the database:
+ *
+ * - ipv6_pool - this contains information about a pool of addresses or prefixes
+ * that the server is using. This includes a hash table that
+ * tracks the active items and a pair of heap tables one for
+ * active items and one for non-active items. The heap tables
+ * are used to determine the next items to be modified due to
+ * timing events (expire mostly).
+ * - ia_xx - this contains information about a single IA from a request
+ * normally it will contain one pointer to a lease for the client
+ * but it may contain more in some circumstances. There are 3
+ * hash tables to aid in accessing these one each for NA, TA and PD.
+ * - iasubopt- the v6 lease structure. These are created dynamically when
+ * a client asks for something and will eventually be destroyed
+ * if the client doesn't re-ask for that item. A lease has space
+ * for backpointers to the IA and to the pool to which it belongs.
+ * The pool backpointer is always filled, the IA pointer may not be.
+ *
+ * In normal use we then have something like this:
+ *
+ * \verbatim
+ * ia hash tables
+ * ia_na_active +----------------+
+ * ia_ta_active +------------+ | pool |
+ * ia_pd_active | iasubopt |<--| active hash |
+ * +-----------------+ | aka lease |<--| active heap |
+ * | ia_xx | | pool ptr |-->| |
+ * | iasubopt array |<---| iaptr |<--| inactive heap |
+ * | lease ptr |--->| | | |
+ * +-----------------+ +------------+ +----------------+
+ * \endverbatim
+ *
+ * For the pool either the inactive heap will have a pointer
+ * or both the active heap and the active hash will have pointers.
+ *
+ * I think there are several major items to notice. The first is
+ * that as a lease moves around it will be added to and removed
+ * from the address hash table in the pool and between the active
+ * and inactive hash tables. The hash table and the active heap
+ * are used when the lease is either active or abandoned. The
+ * inactive heap is used for all other states. In particular a
+ * lease that has expired or been released will be cleaned
+ * (DDNS removal etc) and then moved to the inactive heap. After
+ * some time period (currently 1 hour) it will be freed.
+ *
+ * The second is that when a client requests specific addresses,
+ * either because it previously owned them or if the server supplied
+ * them as part of a solicit, the server will try to lookup the ia_xx
+ * associated with the client and find the addresses there. If it
+ * does find appropriate leases it moves them from the old IA to
+ * a new IA and eventually replaces the old IA with the new IA
+ * in the IA hash tables.
+ *
+ */
+#include "config.h"
+
+#include <sys/types.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <stdarg.h>
+#include "dhcpd.h"
+#include "omapip/omapip.h"
+#include "omapip/hash.h"
+#include <isc/md5.h>
+
+HASH_FUNCTIONS(ia, unsigned char *, struct ia_xx, ia_hash_t,
+ ia_reference, ia_dereference, do_string_hash)
+
+ia_hash_t *ia_na_active;
+ia_hash_t *ia_ta_active;
+ia_hash_t *ia_pd_active;
+
+HASH_FUNCTIONS(iasubopt, struct in6_addr *, struct iasubopt, iasubopt_hash_t,
+ iasubopt_reference, iasubopt_dereference, do_string_hash)
+
+struct ipv6_pool **pools;
+int num_pools;
+
+/*
+ * Create a new IAADDR/PREFIX structure.
+ *
+ * - iasubopt must be a pointer to a (struct iasubopt *) pointer previously
+ * initialized to NULL
+ */
+isc_result_t
+iasubopt_allocate(struct iasubopt **iasubopt, const char *file, int line) {
+ struct iasubopt *tmp;
+
+ if (iasubopt == NULL) {
+ log_error("%s(%d): NULL pointer reference", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+ if (*iasubopt != NULL) {
+ log_error("%s(%d): non-NULL pointer", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+
+ tmp = dmalloc(sizeof(*tmp), file, line);
+ if (tmp == NULL) {
+ return ISC_R_NOMEMORY;
+ }
+
+ tmp->refcnt = 1;
+ tmp->state = FTS_FREE;
+ tmp->heap_index = -1;
+ tmp->plen = 255;
+
+ *iasubopt = tmp;
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Reference an IAADDR/PREFIX structure.
+ *
+ * - iasubopt must be a pointer to a (struct iasubopt *) pointer previously
+ * initialized to NULL
+ */
+isc_result_t
+iasubopt_reference(struct iasubopt **iasubopt, struct iasubopt *src,
+ const char *file, int line) {
+ if (iasubopt == NULL) {
+ log_error("%s(%d): NULL pointer reference", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+ if (*iasubopt != NULL) {
+ log_error("%s(%d): non-NULL pointer", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+ if (src == NULL) {
+ log_error("%s(%d): NULL pointer reference", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+ *iasubopt = src;
+ src->refcnt++;
+ return ISC_R_SUCCESS;
+}
+
+
+/*
+ * Dereference an IAADDR/PREFIX structure.
+ *
+ * If it is the last reference, then the memory for the
+ * structure is freed.
+ */
+isc_result_t
+iasubopt_dereference(struct iasubopt **iasubopt, const char *file, int line) {
+ struct iasubopt *tmp;
+
+ if ((iasubopt == NULL) || (*iasubopt == NULL)) {
+ log_error("%s(%d): NULL pointer", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+
+ tmp = *iasubopt;
+ *iasubopt = NULL;
+
+ tmp->refcnt--;
+ if (tmp->refcnt < 0) {
+ log_error("%s(%d): negative refcnt", file, line);
+ tmp->refcnt = 0;
+ }
+ if (tmp->refcnt == 0) {
+ if (tmp->ia != NULL) {
+ ia_dereference(&(tmp->ia), file, line);
+ }
+ if (tmp->ipv6_pool != NULL) {
+ ipv6_pool_dereference(&(tmp->ipv6_pool), file, line);
+ }
+ if (tmp->scope != NULL) {
+ binding_scope_dereference(&tmp->scope, file, line);
+ }
+ dfree(tmp, file, line);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Make the key that we use for IA.
+ */
+isc_result_t
+ia_make_key(struct data_string *key, u_int32_t iaid,
+ const char *duid, unsigned int duid_len,
+ const char *file, int line) {
+
+ memset(key, 0, sizeof(*key));
+ key->len = duid_len + sizeof(iaid);
+ if (!buffer_allocate(&(key->buffer), key->len, file, line)) {
+ return ISC_R_NOMEMORY;
+ }
+ key->data = key->buffer->data;
+ memcpy((char *)key->data, &iaid, sizeof(iaid));
+ memcpy((char *)key->data + sizeof(iaid), duid, duid_len);
+
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Create a new IA structure.
+ *
+ * - ia must be a pointer to a (struct ia_xx *) pointer previously
+ * initialized to NULL
+ * - iaid and duid are values from the client
+ *
+ * XXXsk: we don't concern ourself with the byte order of the IAID,
+ * which might be a problem if we transfer this structure
+ * between machines of different byte order
+ */
+isc_result_t
+ia_allocate(struct ia_xx **ia, u_int32_t iaid,
+ const char *duid, unsigned int duid_len,
+ const char *file, int line) {
+ struct ia_xx *tmp;
+
+ if (ia == NULL) {
+ log_error("%s(%d): NULL pointer reference", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+ if (*ia != NULL) {
+ log_error("%s(%d): non-NULL pointer", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+
+ tmp = dmalloc(sizeof(*tmp), file, line);
+ if (tmp == NULL) {
+ return ISC_R_NOMEMORY;
+ }
+
+ if (ia_make_key(&tmp->iaid_duid, iaid,
+ duid, duid_len, file, line) != ISC_R_SUCCESS) {
+ dfree(tmp, file, line);
+ return ISC_R_NOMEMORY;
+ }
+
+ tmp->refcnt = 1;
+
+ *ia = tmp;
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Reference an IA structure.
+ *
+ * - ia must be a pointer to a (struct ia_xx *) pointer previously
+ * initialized to NULL
+ */
+isc_result_t
+ia_reference(struct ia_xx **ia, struct ia_xx *src,
+ const char *file, int line) {
+ if (ia == NULL) {
+ log_error("%s(%d): NULL pointer reference", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+ if (*ia != NULL) {
+ log_error("%s(%d): non-NULL pointer", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+ if (src == NULL) {
+ log_error("%s(%d): NULL pointer reference", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+ *ia = src;
+ src->refcnt++;
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Dereference an IA structure.
+ *
+ * If it is the last reference, then the memory for the
+ * structure is freed.
+ */
+isc_result_t
+ia_dereference(struct ia_xx **ia, const char *file, int line) {
+ struct ia_xx *tmp;
+ int i;
+
+ if ((ia == NULL) || (*ia == NULL)) {
+ log_error("%s(%d): NULL pointer", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+
+ tmp = *ia;
+ *ia = NULL;
+
+ tmp->refcnt--;
+ if (tmp->refcnt < 0) {
+ log_error("%s(%d): negative refcnt", file, line);
+ tmp->refcnt = 0;
+ }
+ if (tmp->refcnt == 0) {
+ if (tmp->iasubopt != NULL) {
+ for (i=0; i<tmp->num_iasubopt; i++) {
+ iasubopt_dereference(&(tmp->iasubopt[i]),
+ file, line);
+ }
+ dfree(tmp->iasubopt, file, line);
+ }
+ data_string_forget(&(tmp->iaid_duid), file, line);
+ dfree(tmp, file, line);
+ }
+ return ISC_R_SUCCESS;
+}
+
+
+/*
+ * Add an IAADDR/PREFIX entry to an IA structure.
+ */
+isc_result_t
+ia_add_iasubopt(struct ia_xx *ia, struct iasubopt *iasubopt,
+ const char *file, int line) {
+ int max;
+ struct iasubopt **new;
+
+ /*
+ * Grow our array if we need to.
+ *
+ * Note: we pick 4 as the increment, as that seems a reasonable
+ * guess as to how many addresses/prefixes we might expect
+ * on an interface.
+ */
+ if (ia->max_iasubopt <= ia->num_iasubopt) {
+ max = ia->max_iasubopt + 4;
+ new = dmalloc(max * sizeof(struct iasubopt *), file, line);
+ if (new == NULL) {
+ return ISC_R_NOMEMORY;
+ }
+ memcpy(new, ia->iasubopt,
+ ia->num_iasubopt * sizeof(struct iasubopt *));
+ ia->iasubopt = new;
+ ia->max_iasubopt = max;
+ }
+
+ iasubopt_reference(&(ia->iasubopt[ia->num_iasubopt]), iasubopt,
+ file, line);
+ ia->num_iasubopt++;
+
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Remove an IAADDR/PREFIX entry to an IA structure.
+ *
+ * Note: if a suboption appears more than once, then only ONE will be removed.
+ */
+void
+ia_remove_iasubopt(struct ia_xx *ia, struct iasubopt *iasubopt,
+ const char *file, int line) {
+ int i, j;
+ if (ia == NULL || iasubopt == NULL)
+ return;
+
+ for (i=0; i<ia->num_iasubopt; i++) {
+ if (ia->iasubopt[i] == iasubopt) {
+ /* remove this sub option */
+ iasubopt_dereference(&(ia->iasubopt[i]), file, line);
+ /* move remaining suboption pointers down one */
+ for (j=i+1; j < ia->num_iasubopt; j++) {
+ ia->iasubopt[j-1] = ia->iasubopt[j];
+ }
+ /* decrease our total count */
+ /* remove the back-reference in the suboption itself */
+ ia_dereference(&iasubopt->ia, file, line);
+ ia->num_iasubopt--;
+ return;
+ }
+ }
+ log_error("%s(%d): IAADDR/PREFIX not in IA", file, line);
+}
+
+/*
+ * Remove all addresses/prefixes from an IA.
+ */
+void
+ia_remove_all_lease(struct ia_xx *ia, const char *file, int line) {
+ int i;
+
+ for (i=0; i<ia->num_iasubopt; i++) {
+ ia_dereference(&(ia->iasubopt[i]->ia), file, line);
+ iasubopt_dereference(&(ia->iasubopt[i]), file, line);
+ }
+ ia->num_iasubopt = 0;
+}
+
+/*
+ * Compare two IA.
+ */
+isc_boolean_t
+ia_equal(const struct ia_xx *a, const struct ia_xx *b)
+{
+ isc_boolean_t found;
+ int i, j;
+
+ /*
+ * Handle cases where one or both of the inputs is NULL.
+ */
+ if (a == NULL) {
+ if (b == NULL) {
+ return ISC_TRUE;
+ } else {
+ return ISC_FALSE;
+ }
+ }
+
+ /*
+ * Check the type is the same.
+ */
+ if (a->ia_type != b->ia_type) {
+ return ISC_FALSE;
+ }
+
+ /*
+ * Check the DUID is the same.
+ */
+ if (a->iaid_duid.len != b->iaid_duid.len) {
+ return ISC_FALSE;
+ }
+ if (memcmp(a->iaid_duid.data,
+ b->iaid_duid.data, a->iaid_duid.len) != 0) {
+ return ISC_FALSE;
+ }
+
+ /*
+ * Make sure we have the same number of addresses/prefixes in each.
+ */
+ if (a->num_iasubopt != b->num_iasubopt) {
+ return ISC_FALSE;
+ }
+
+ /*
+ * Check that each address/prefix is present in both.
+ */
+ for (i=0; i<a->num_iasubopt; i++) {
+ found = ISC_FALSE;
+ for (j=0; j<a->num_iasubopt; j++) {
+ if (a->iasubopt[i]->plen != b->iasubopt[i]->plen)
+ continue;
+ if (memcmp(&(a->iasubopt[i]->addr),
+ &(b->iasubopt[j]->addr),
+ sizeof(struct in6_addr)) == 0) {
+ found = ISC_TRUE;
+ break;
+ }
+ }
+ if (!found) {
+ return ISC_FALSE;
+ }
+ }
+
+ /*
+ * These are the same in every way we care about.
+ */
+ return ISC_TRUE;
+}
+
+/*
+ * Helper function for lease heaps.
+ * Makes the top of the heap the oldest lease.
+ */
+static isc_boolean_t
+lease_older(void *a, void *b) {
+ struct iasubopt *la = (struct iasubopt *)a;
+ struct iasubopt *lb = (struct iasubopt *)b;
+
+ if (la->hard_lifetime_end_time == lb->hard_lifetime_end_time) {
+ return difftime(la->soft_lifetime_end_time,
+ lb->soft_lifetime_end_time) < 0;
+ } else {
+ return difftime(la->hard_lifetime_end_time,
+ lb->hard_lifetime_end_time) < 0;
+ }
+}
+
+/*
+ * Helper function for lease address/prefix heaps.
+ * Callback when an address's position in the heap changes.
+ */
+static void
+lease_index_changed(void *iasubopt, unsigned int new_heap_index) {
+ ((struct iasubopt *)iasubopt)-> heap_index = new_heap_index;
+}
+
+
+/*
+ * Create a new IPv6 lease pool structure.
+ *
+ * - pool must be a pointer to a (struct ipv6_pool *) pointer previously
+ * initialized to NULL
+ */
+isc_result_t
+ipv6_pool_allocate(struct ipv6_pool **pool, u_int16_t type,
+ const struct in6_addr *start_addr, int bits,
+ int units, const char *file, int line) {
+ struct ipv6_pool *tmp;
+
+ if (pool == NULL) {
+ log_error("%s(%d): NULL pointer reference", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+ if (*pool != NULL) {
+ log_error("%s(%d): non-NULL pointer", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+
+ tmp = dmalloc(sizeof(*tmp), file, line);
+ if (tmp == NULL) {
+ return ISC_R_NOMEMORY;
+ }
+
+ tmp->refcnt = 1;
+ tmp->pool_type = type;
+ tmp->start_addr = *start_addr;
+ tmp->bits = bits;
+ tmp->units = units;
+ if (!iasubopt_new_hash(&tmp->leases, DEFAULT_HASH_SIZE, file, line)) {
+ dfree(tmp, file, line);
+ return ISC_R_NOMEMORY;
+ }
+ if (isc_heap_create(dhcp_gbl_ctx.mctx, lease_older, lease_index_changed,
+ 0, &(tmp->active_timeouts)) != ISC_R_SUCCESS) {
+ iasubopt_free_hash_table(&(tmp->leases), file, line);
+ dfree(tmp, file, line);
+ return ISC_R_NOMEMORY;
+ }
+ if (isc_heap_create(dhcp_gbl_ctx.mctx, lease_older, lease_index_changed,
+ 0, &(tmp->inactive_timeouts)) != ISC_R_SUCCESS) {
+ isc_heap_destroy(&(tmp->active_timeouts));
+ iasubopt_free_hash_table(&(tmp->leases), file, line);
+ dfree(tmp, file, line);
+ return ISC_R_NOMEMORY;
+ }
+
+ *pool = tmp;
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Reference an IPv6 pool structure.
+ *
+ * - pool must be a pointer to a (struct pool *) pointer previously
+ * initialized to NULL
+ */
+isc_result_t
+ipv6_pool_reference(struct ipv6_pool **pool, struct ipv6_pool *src,
+ const char *file, int line) {
+ if (pool == NULL) {
+ log_error("%s(%d): NULL pointer reference", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+ if (*pool != NULL) {
+ log_error("%s(%d): non-NULL pointer", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+ if (src == NULL) {
+ log_error("%s(%d): NULL pointer reference", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+ *pool = src;
+ src->refcnt++;
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Note: Each IAADDR/PREFIX in a pool is referenced by the pool. This is needed
+ * to prevent the lease from being garbage collected out from under the
+ * pool.
+ *
+ * The references are made from the hash and from the heap. The following
+ * helper functions dereference these when a pool is destroyed.
+ */
+
+/*
+ * Helper function for pool cleanup.
+ * Dereference each of the hash entries in a pool.
+ */
+static isc_result_t
+dereference_hash_entry(const void *name, unsigned len, void *value) {
+ struct iasubopt *iasubopt = (struct iasubopt *)value;
+
+ iasubopt_dereference(&iasubopt, MDL);
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Helper function for pool cleanup.
+ * Dereference each of the heap entries in a pool.
+ */
+static void
+dereference_heap_entry(void *value, void *dummy) {
+ struct iasubopt *iasubopt = (struct iasubopt *)value;
+
+ iasubopt_dereference(&iasubopt, MDL);
+}
+
+
+/*
+ * Dereference an IPv6 pool structure.
+ *
+ * If it is the last reference, then the memory for the
+ * structure is freed.
+ */
+isc_result_t
+ipv6_pool_dereference(struct ipv6_pool **pool, const char *file, int line) {
+ struct ipv6_pool *tmp;
+
+ if ((pool == NULL) || (*pool == NULL)) {
+ log_error("%s(%d): NULL pointer", file, line);
+ return DHCP_R_INVALIDARG;
+ }
+
+ tmp = *pool;
+ *pool = NULL;
+
+ tmp->refcnt--;
+ if (tmp->refcnt < 0) {
+ log_error("%s(%d): negative refcnt", file, line);
+ tmp->refcnt = 0;
+ }
+ if (tmp->refcnt == 0) {
+ iasubopt_hash_foreach(tmp->leases, dereference_hash_entry);
+ iasubopt_free_hash_table(&(tmp->leases), file, line);
+ isc_heap_foreach(tmp->active_timeouts,
+ dereference_heap_entry, NULL);
+ isc_heap_destroy(&(tmp->active_timeouts));
+ isc_heap_foreach(tmp->inactive_timeouts,
+ dereference_heap_entry, NULL);
+ isc_heap_destroy(&(tmp->inactive_timeouts));
+ dfree(tmp, file, line);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Create an address by hashing the input, and using that for
+ * the non-network part.
+ */
+static void
+build_address6(struct in6_addr *addr,
+ const struct in6_addr *net_start_addr, int net_bits,
+ const struct data_string *input) {
+ isc_md5_t ctx;
+ int net_bytes;
+ int i;
+ char *str;
+ const char *net_str;
+
+ /*
+ * Use MD5 to get a nice 128 bit hash of the input.
+ * Yes, we know MD5 isn't cryptographically sound.
+ * No, we don't care.
+ */
+ isc_md5_init(&ctx);
+ isc_md5_update(&ctx, input->data, input->len);
+ isc_md5_final(&ctx, (unsigned char *)addr);
+
+ /*
+ * Copy the [0..128] network bits over.
+ */
+ str = (char *)addr;
+ net_str = (const char *)net_start_addr;
+ net_bytes = net_bits / 8;
+ for (i = 0; i < net_bytes; i++) {
+ str[i] = net_str[i];
+ }
+ switch (net_bits % 8) {
+ case 1: str[i] = (str[i] & 0x7F) | (net_str[i] & 0x80); break;
+ case 2: str[i] = (str[i] & 0x3F) | (net_str[i] & 0xC0); break;
+ case 3: str[i] = (str[i] & 0x1F) | (net_str[i] & 0xE0); break;
+ case 4: str[i] = (str[i] & 0x0F) | (net_str[i] & 0xF0); break;
+ case 5: str[i] = (str[i] & 0x07) | (net_str[i] & 0xF8); break;
+ case 6: str[i] = (str[i] & 0x03) | (net_str[i] & 0xFC); break;
+ case 7: str[i] = (str[i] & 0x01) | (net_str[i] & 0xFE); break;
+ }
+
+ /*
+ * Set the universal/local bit ("u bit") to zero for /64s. The
+ * individual/group bit ("g bit") is unchanged, because the g-bit
+ * has no meaning when the u-bit is cleared.
+ */
+ if (net_bits == 64)
+ str[8] &= ~0x02;
+}
+
+/*
+ * Create a temporary address by a variant of RFC 4941 algo.
+ * Note: this should not be used for prefixes shorter than 64 bits.
+ */
+static void
+build_temporary6(struct in6_addr *addr,
+ const struct in6_addr *net_start_addr, int net_bits,
+ const struct data_string *input) {
+ static u_int32_t history[2];
+ static u_int32_t counter = 0;
+ isc_md5_t ctx;
+ unsigned char md[16];
+
+ /*
+ * First time/time to reseed.
+ * Please use a good pseudo-random generator here!
+ */
+ if (counter == 0) {
+ isc_random_get(&history[0]);
+ isc_random_get(&history[1]);
+ }
+
+ /*
+ * Use MD5 as recommended by RFC 4941.
+ */
+ isc_md5_init(&ctx);
+ isc_md5_update(&ctx, (unsigned char *)&history[0], 8UL);
+ isc_md5_update(&ctx, input->data, input->len);
+ isc_md5_final(&ctx, md);
+
+ /*
+ * Build the address.
+ */
+ if (net_bits == 64) {
+ memcpy(&addr->s6_addr[0], &net_start_addr->s6_addr[0], 8);
+ memcpy(&addr->s6_addr[8], md, 8);
+ addr->s6_addr[8] &= ~0x02;
+ } else {
+ int net_bytes;
+ int i;
+ char *str;
+ const char *net_str;
+
+ /*
+ * Copy the [0..128] network bits over.
+ */
+ str = (char *)addr;
+ net_str = (const char *)net_start_addr;
+ net_bytes = net_bits / 8;
+ for (i = 0; i < net_bytes; i++) {
+ str[i] = net_str[i];
+ }
+ memcpy(str + net_bytes, md, 16 - net_bytes);
+ switch (net_bits % 8) {
+ case 1: str[i] = (str[i] & 0x7F) | (net_str[i] & 0x80); break;
+ case 2: str[i] = (str[i] & 0x3F) | (net_str[i] & 0xC0); break;
+ case 3: str[i] = (str[i] & 0x1F) | (net_str[i] & 0xE0); break;
+ case 4: str[i] = (str[i] & 0x0F) | (net_str[i] & 0xF0); break;
+ case 5: str[i] = (str[i] & 0x07) | (net_str[i] & 0xF8); break;
+ case 6: str[i] = (str[i] & 0x03) | (net_str[i] & 0xFC); break;
+ case 7: str[i] = (str[i] & 0x01) | (net_str[i] & 0xFE); break;
+ }
+ }
+
+
+ /*
+ * Save history for the next call.
+ */
+ memcpy((unsigned char *)&history[0], md + 8, 8);
+ counter++;
+}
+
+/* Reserved Subnet Router Anycast ::0:0:0:0. */
+static struct in6_addr rtany;
+/* Reserved Subnet Anycasts ::fdff:ffff:ffff:ff80-::fdff:ffff:ffff:ffff. */
+static struct in6_addr resany;
+
+/*
+ * Create a lease for the given address and client duid.
+ *
+ * - pool must be a pointer to a (struct pool *) pointer previously
+ * initialized to NULL
+ *
+ * Right now we simply hash the DUID, and if we get a collision, we hash
+ * again until we find a free address. We try this a fixed number of times,
+ * to avoid getting stuck in a loop (this is important on small pools
+ * where we can run out of space).
+ *
+ * We return the number of attempts that it took to find an available
+ * lease. This tells callers when a pool is are filling up, as
+ * well as an indication of how full the pool is; statistically the
+ * more full a pool is the more attempts must be made before finding
+ * a free lease. Realistically this will only happen in very full
+ * pools.
+ *
+ * We probably want different algorithms depending on the network size, in
+ * the long term.
+ */
+isc_result_t
+create_lease6(struct ipv6_pool *pool, struct iasubopt **addr,
+ unsigned int *attempts,
+ const struct data_string *uid, time_t soft_lifetime_end_time) {
+ struct data_string ds;
+ struct in6_addr tmp;
+ struct iasubopt *test_iaaddr;
+ struct data_string new_ds;
+ struct iasubopt *iaaddr;
+ isc_result_t result;
+ isc_boolean_t reserved_iid;
+ static isc_boolean_t init_resiid = ISC_FALSE;
+
+ /*
+ * Fill the reserved IIDs.
+ */
+ if (!init_resiid) {
+ memset(&rtany, 0, 16);
+ memset(&resany, 0, 8);
+ resany.s6_addr[8] = 0xfd;
+ memset(&resany.s6_addr[9], 0xff, 6);
+ init_resiid = ISC_TRUE;
+ }
+
+ /*
+ * Use the UID as our initial seed for the hash
+ */
+ memset(&ds, 0, sizeof(ds));
+ data_string_copy(&ds, (struct data_string *)uid, MDL);
+
+ *attempts = 0;
+ for (;;) {
+ /*
+ * Give up at some point.
+ */
+ if (++(*attempts) > 100) {
+ data_string_forget(&ds, MDL);
+ return ISC_R_NORESOURCES;
+ }
+
+ /*
+ * Build a resource.
+ */
+ switch (pool->pool_type) {
+ case D6O_IA_NA:
+ /* address */
+ build_address6(&tmp, &pool->start_addr,
+ pool->bits, &ds);
+ break;
+ case D6O_IA_TA:
+ /* temporary address */
+ build_temporary6(&tmp, &pool->start_addr,
+ pool->bits, &ds);
+ break;
+ case D6O_IA_PD:
+ /* prefix */
+ log_error("create_lease6: prefix pool.");
+ return DHCP_R_INVALIDARG;
+ default:
+ log_error("create_lease6: untyped pool.");
+ return DHCP_R_INVALIDARG;
+ }
+
+ /*
+ * Avoid reserved interface IDs. (cf. RFC 5453)
+ */
+ reserved_iid = ISC_FALSE;
+ if (memcmp(&tmp.s6_addr[8], &rtany.s6_addr[8], 8) == 0) {
+ reserved_iid = ISC_TRUE;
+ }
+ if (!reserved_iid &&
+ (memcmp(&tmp.s6_addr[8], &resany.s6_addr[8], 7) == 0) &&
+ ((tmp.s6_addr[15] & 0x80) == 0x80)) {
+ reserved_iid = ISC_TRUE;
+ }
+
+ /*
+ * If this address is not in use, we're happy with it
+ */
+ test_iaaddr = NULL;
+ if (!reserved_iid &&
+ (iasubopt_hash_lookup(&test_iaaddr, pool->leases,
+ &tmp, sizeof(tmp), MDL) == 0)) {
+ break;
+ }
+ if (test_iaaddr != NULL)
+ iasubopt_dereference(&test_iaaddr, MDL);
+
+ /*
+ * Otherwise, we create a new input, adding the address
+ */
+ memset(&new_ds, 0, sizeof(new_ds));
+ new_ds.len = ds.len + sizeof(tmp);
+ if (!buffer_allocate(&new_ds.buffer, new_ds.len, MDL)) {
+ data_string_forget(&ds, MDL);
+ return ISC_R_NOMEMORY;
+ }
+ new_ds.data = new_ds.buffer->data;
+ memcpy(new_ds.buffer->data, ds.data, ds.len);
+ memcpy(new_ds.buffer->data + ds.len, &tmp, sizeof(tmp));
+ data_string_forget(&ds, MDL);
+ data_string_copy(&ds, &new_ds, MDL);
+ data_string_forget(&new_ds, MDL);
+ }
+
+ data_string_forget(&ds, MDL);
+
+ /*
+ * We're happy with the address, create an IAADDR
+ * to hold it.
+ */
+ iaaddr = NULL;
+ result = iasubopt_allocate(&iaaddr, MDL);
+ if (result != ISC_R_SUCCESS) {
+ return result;
+ }
+ iaaddr->plen = 0;
+ memcpy(&iaaddr->addr, &tmp, sizeof(iaaddr->addr));
+
+ /*
+ * Add the lease to the pool (note state is free, not active?!).
+ */
+ result = add_lease6(pool, iaaddr, soft_lifetime_end_time);
+ if (result == ISC_R_SUCCESS) {
+ iasubopt_reference(addr, iaaddr, MDL);
+ }
+ iasubopt_dereference(&iaaddr, MDL);
+ return result;
+}
+
+
+/*!
+ *
+ * \brief Cleans up leases when reading from a lease file
+ *
+ * This function is only expected to be run when reading leases in from a file.
+ * It checks to see if a lease already exists for the new leases's address.
+ * We don't add expired leases to the structures when reading a lease file
+ * which limits what can happen. We have two variables the owners of the leases
+ * being the same or different and the new lease being active or non-active:
+ * Owners active
+ * same no remove old lease and its connections
+ * same yes nothing to do, other code will update the structures.
+ * diff no nothing to do
+ * diff yes this combination shouldn't happen, we should only have a
+ * single active lease per address at a time and that lease
+ * should move to non-active before any other lease can
+ * become active for that address.
+ * Currently we delete the previous lease and pass an error
+ * to the caller who should log an error.
+ *
+ * When we remove a lease we remove it from the hash table and active heap
+ * (remember only active leases are in the structures at this time) for the
+ * pool, and from the IA's array. If, after we've removed the pointer from
+ * IA's array to the lease, the IA has no more pointers we remove it from
+ * the appropriate hash table as well.
+ *
+ * \param[in] ia_table = the hash table for the IA
+ * \param[in] pool = the pool to update
+ * \param[in] lease = the new lease we want to add
+ * \param[in] ia = the new ia we are building
+ *
+ * \return
+ * ISC_R_SUCCESS = the incoming lease and any previous lease were in
+ * an expected state - one of the first 3 options above.
+ * If necessary the old lease was removed.
+ * ISC_R_FAILURE = there is already an active lease for the address in
+ * the incoming lease. This shouldn't happen if it does
+ * flag an error for the caller to log.
+ */
+
+isc_result_t
+cleanup_lease6(ia_hash_t *ia_table,
+ struct ipv6_pool *pool,
+ struct iasubopt *lease,
+ struct ia_xx *ia) {
+
+ struct iasubopt *test_iasubopt, *tmp_iasubopt;
+ struct ia_xx *old_ia;
+ isc_result_t status = ISC_R_SUCCESS;
+
+ test_iasubopt = NULL;
+ old_ia = NULL;
+
+ /*
+ * Look up the address - if we don't find a lease
+ * we don't need to do anything.
+ */
+ if (iasubopt_hash_lookup(&test_iasubopt, pool->leases,
+ &lease->addr, sizeof(lease->addr),
+ MDL) == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (test_iasubopt->ia == NULL) {
+ /* no old ia, no work to do */
+ iasubopt_dereference(&test_iasubopt, MDL);
+ return (status);
+ }
+
+ ia_reference(&old_ia, test_iasubopt->ia, MDL);
+
+ if ((old_ia->iaid_duid.len == ia->iaid_duid.len) &&
+ (memcmp((unsigned char *)ia->iaid_duid.data,
+ (unsigned char *)old_ia->iaid_duid.data,
+ ia->iaid_duid.len) == 0)) {
+ /* same IA */
+ if ((lease->state == FTS_ACTIVE) ||
+ (lease->state == FTS_ABANDONED)) {
+ /* still active, no need to delete */
+ goto cleanup;
+ }
+ } else {
+ /* different IA */
+ if ((lease->state != FTS_ACTIVE) &&
+ (lease->state != FTS_ABANDONED)) {
+ /* new lease isn't active, no work */
+ goto cleanup;
+ }
+
+ /*
+ * We appear to have two active leases, this shouldn't happen.
+ * Before a second lease can be set to active the first lease
+ * should be set to inactive (released, expired etc). For now
+ * delete the previous lease and indicate a failure to the
+ * caller so it can generate a warning.
+ * In the future we may try and determine which is the better
+ * lease to keep.
+ */
+
+ status = ISC_R_FAILURE;
+ }
+
+ /*
+ * Remove the old lease from the active heap and from the hash table
+ * then remove the lease from the IA and clean up the IA if necessary.
+ */
+ isc_heap_delete(pool->active_timeouts, test_iasubopt->heap_index);
+ pool->num_active--;
+
+ iasubopt_hash_delete(pool->leases, &test_iasubopt->addr,
+ sizeof(test_iasubopt->addr), MDL);
+ ia_remove_iasubopt(old_ia, test_iasubopt, MDL);
+ if (old_ia->num_iasubopt <= 0) {
+ ia_hash_delete(ia_table,
+ (unsigned char *)old_ia->iaid_duid.data,
+ old_ia->iaid_duid.len, MDL);
+ }
+
+ /*
+ * We derefenrece the subopt here as we've just removed it from
+ * the hash table in the pool. We need to make a copy as we
+ * need to derefernece it again later.
+ */
+ tmp_iasubopt = test_iasubopt;
+ iasubopt_dereference(&tmp_iasubopt, MDL);
+
+ cleanup:
+ ia_dereference(&old_ia, MDL);
+
+ /*
+ * Clean up the reference, this is in addition to the deference
+ * above after removing the entry from the hash table
+ */
+ iasubopt_dereference(&test_iasubopt, MDL);
+
+ return (status);
+}
+
+/*
+ * Put a lease in the pool directly. This is intended to be used when
+ * loading leases from the file.
+ */
+isc_result_t
+add_lease6(struct ipv6_pool *pool, struct iasubopt *lease,
+ time_t valid_lifetime_end_time) {
+ isc_result_t insert_result;
+ struct iasubopt *test_iasubopt;
+ struct iasubopt *tmp_iasubopt;
+
+ /* If a state was not assigned by the caller, assume active. */
+ if (lease->state == 0)
+ lease->state = FTS_ACTIVE;
+
+ ipv6_pool_reference(&lease->ipv6_pool, pool, MDL);
+
+ /*
+ * If this IAADDR/PREFIX is already in our structures, remove the
+ * old one.
+ */
+ test_iasubopt = NULL;
+ if (iasubopt_hash_lookup(&test_iasubopt, pool->leases,
+ &lease->addr, sizeof(lease->addr), MDL)) {
+ /* XXX: we should probably ask the lease what heap it is on
+ * (as a consistency check).
+ * XXX: we should probably have one function to "put this lease
+ * on its heap" rather than doing these if's everywhere. If
+ * you add more states to this list, don't.
+ */
+ if ((test_iasubopt->state == FTS_ACTIVE) ||
+ (test_iasubopt->state == FTS_ABANDONED)) {
+ isc_heap_delete(pool->active_timeouts,
+ test_iasubopt->heap_index);
+ pool->num_active--;
+ } else {
+ isc_heap_delete(pool->inactive_timeouts,
+ test_iasubopt->heap_index);
+ pool->num_inactive--;
+ }
+
+ iasubopt_hash_delete(pool->leases, &test_iasubopt->addr,
+ sizeof(test_iasubopt->addr), MDL);
+
+ /*
+ * We're going to do a bit of evil trickery here.
+ *
+ * We need to dereference the entry once to remove our
+ * current reference (in test_iasubopt), and then one
+ * more time to remove the reference left when the
+ * address was added to the pool before.
+ */
+ tmp_iasubopt = test_iasubopt;
+ iasubopt_dereference(&test_iasubopt, MDL);
+ iasubopt_dereference(&tmp_iasubopt, MDL);
+ }
+
+ /*
+ * Add IAADDR/PREFIX to our structures.
+ */
+ tmp_iasubopt = NULL;
+ iasubopt_reference(&tmp_iasubopt, lease, MDL);
+ if ((tmp_iasubopt->state == FTS_ACTIVE) ||
+ (tmp_iasubopt->state == FTS_ABANDONED)) {
+ tmp_iasubopt->hard_lifetime_end_time = valid_lifetime_end_time;
+ iasubopt_hash_add(pool->leases, &tmp_iasubopt->addr,
+ sizeof(tmp_iasubopt->addr), lease, MDL);
+ insert_result = isc_heap_insert(pool->active_timeouts,
+ tmp_iasubopt);
+ if (insert_result == ISC_R_SUCCESS)
+ pool->num_active++;
+ } else {
+ tmp_iasubopt->soft_lifetime_end_time = valid_lifetime_end_time;
+ insert_result = isc_heap_insert(pool->inactive_timeouts,
+ tmp_iasubopt);
+ if (insert_result == ISC_R_SUCCESS)
+ pool->num_inactive++;
+ }
+ if (insert_result != ISC_R_SUCCESS) {
+ iasubopt_hash_delete(pool->leases, &lease->addr,
+ sizeof(lease->addr), MDL);
+ iasubopt_dereference(&tmp_iasubopt, MDL);
+ return insert_result;
+ }
+
+ /*
+ * Note: we intentionally leave tmp_iasubopt referenced; there
+ * is a reference in the heap/hash, after all.
+ */
+
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Determine if an address is present in a pool or not.
+ */
+isc_boolean_t
+lease6_exists(const struct ipv6_pool *pool, const struct in6_addr *addr) {
+ struct iasubopt *test_iaaddr;
+
+ test_iaaddr = NULL;
+ if (iasubopt_hash_lookup(&test_iaaddr, pool->leases,
+ (void *)addr, sizeof(*addr), MDL)) {
+ iasubopt_dereference(&test_iaaddr, MDL);
+ return ISC_TRUE;
+ } else {
+ return ISC_FALSE;
+ }
+}
+
+/*!
+ *
+ * \brief Check if address is available to a lease
+ *
+ * Determine if the address in the lease is available to that
+ * lease. Either the address isn't in use or it is in use
+ * but by that lease.
+ *
+ * \param[in] lease = lease to check
+ *
+ * \return
+ * ISC_TRUE = The lease is allowed to use that address
+ * ISC_FALSE = The lease isn't allowed to use that address
+ */
+isc_boolean_t
+lease6_usable(struct iasubopt *lease) {
+ struct iasubopt *test_iaaddr;
+ isc_boolean_t status = ISC_TRUE;
+
+ test_iaaddr = NULL;
+ if (iasubopt_hash_lookup(&test_iaaddr, lease->ipv6_pool->leases,
+ (void *)&lease->addr,
+ sizeof(lease->addr), MDL)) {
+ if (test_iaaddr != lease) {
+ status = ISC_FALSE;
+ }
+ iasubopt_dereference(&test_iaaddr, MDL);
+ }
+
+ return (status);
+}
+
+/*
+ * Put the lease on our active pool.
+ */
+static isc_result_t
+move_lease_to_active(struct ipv6_pool *pool, struct iasubopt *lease) {
+ isc_result_t insert_result;
+ int old_heap_index;
+
+ old_heap_index = lease->heap_index;
+ insert_result = isc_heap_insert(pool->active_timeouts, lease);
+ if (insert_result == ISC_R_SUCCESS) {
+ iasubopt_hash_add(pool->leases, &lease->addr,
+ sizeof(lease->addr), lease, MDL);
+ isc_heap_delete(pool->inactive_timeouts, old_heap_index);
+ pool->num_active++;
+ pool->num_inactive--;
+ lease->state = FTS_ACTIVE;
+ }
+ return insert_result;
+}
+
+/*!
+ * \brief Renew a lease in the pool.
+ *
+ * The hard_lifetime_end_time of the lease should be set to
+ * the current expiration time.
+ * The soft_lifetime_end_time of the lease should be set to
+ * the desired expiration time.
+ *
+ * This routine will compare the two and call the correct
+ * heap routine to move the lease. If the lease is active
+ * and the new expiration time is greater (the normal case)
+ * then we call isc_heap_decreased() as a larger time is a
+ * lower priority. If the new expiration time is less then
+ * we call isc_heap_increased().
+ *
+ * If the lease is abandoned then it will be on the active list
+ * and we will always call isc_heap_increased() as the previous
+ * expiration would have been all 1s (as close as we can get
+ * to infinite).
+ *
+ * If the lease is moving to active we call that routine
+ * which will move it from the inactive list to the active list.
+ *
+ * \param pool a pool the lease belongs to
+ * \param lease the lease to be renewed
+ *
+ * \return result of the renew operation (ISC_R_SUCCESS if successful,
+ ISC_R_NOMEMORY when run out of memory)
+ */
+isc_result_t
+renew_lease6(struct ipv6_pool *pool, struct iasubopt *lease) {
+ time_t old_end_time = lease->hard_lifetime_end_time;
+ lease->hard_lifetime_end_time = lease->soft_lifetime_end_time;
+ lease->soft_lifetime_end_time = 0;
+
+ if (lease->state == FTS_ACTIVE) {
+ if (old_end_time <= lease->hard_lifetime_end_time) {
+ isc_heap_decreased(pool->active_timeouts,
+ lease->heap_index);
+ } else {
+ isc_heap_increased(pool->active_timeouts,
+ lease->heap_index);
+ }
+ return ISC_R_SUCCESS;
+ } else if (lease->state == FTS_ABANDONED) {
+ char tmp_addr[INET6_ADDRSTRLEN];
+ lease->state = FTS_ACTIVE;
+ isc_heap_increased(pool->active_timeouts, lease->heap_index);
+ log_info("Reclaiming previously abandoned address %s",
+ inet_ntop(AF_INET6, &(lease->addr), tmp_addr,
+ sizeof(tmp_addr)));
+ return ISC_R_SUCCESS;
+ } else {
+ return move_lease_to_active(pool, lease);
+ }
+}
+
+/*
+ * Put the lease on our inactive pool, with the specified state.
+ */
+static isc_result_t
+move_lease_to_inactive(struct ipv6_pool *pool, struct iasubopt *lease,
+ binding_state_t state) {
+ isc_result_t insert_result;
+ int old_heap_index;
+
+ old_heap_index = lease->heap_index;
+ insert_result = isc_heap_insert(pool->inactive_timeouts, lease);
+ if (insert_result == ISC_R_SUCCESS) {
+#if defined (NSUPDATE)
+ /* Process events upon expiration. */
+ if (pool->pool_type != D6O_IA_PD) {
+ (void) ddns_removals(NULL, lease, NULL, ISC_FALSE);
+ }
+#endif
+
+ /* Binding scopes are no longer valid after expiry or
+ * release.
+ */
+ if (lease->scope != NULL) {
+ binding_scope_dereference(&lease->scope, MDL);
+ }
+
+ iasubopt_hash_delete(pool->leases,
+ &lease->addr, sizeof(lease->addr), MDL);
+ isc_heap_delete(pool->active_timeouts, old_heap_index);
+ lease->state = state;
+ pool->num_active--;
+ pool->num_inactive++;
+ }
+ return insert_result;
+}
+
+/*
+ * Expire the oldest lease if it's lifetime_end_time is
+ * older than the given time.
+ *
+ * - leasep must be a pointer to a (struct iasubopt *) pointer previously
+ * initialized to NULL
+ *
+ * On return leasep has a reference to the removed entry. It is left
+ * pointing to NULL if the oldest lease has not expired.
+ */
+isc_result_t
+expire_lease6(struct iasubopt **leasep, struct ipv6_pool *pool, time_t now) {
+ struct iasubopt *tmp;
+ isc_result_t result;
+
+ if (leasep == NULL) {
+ log_error("%s(%d): NULL pointer reference", MDL);
+ return DHCP_R_INVALIDARG;
+ }
+ if (*leasep != NULL) {
+ log_error("%s(%d): non-NULL pointer", MDL);
+ return DHCP_R_INVALIDARG;
+ }
+
+ if (pool->num_active > 0) {
+ tmp = (struct iasubopt *)
+ isc_heap_element(pool->active_timeouts, 1);
+ if (now > tmp->hard_lifetime_end_time) {
+ result = move_lease_to_inactive(pool, tmp,
+ FTS_EXPIRED);
+ if (result == ISC_R_SUCCESS) {
+ iasubopt_reference(leasep, tmp, MDL);
+ }
+ return result;
+ }
+ }
+ return ISC_R_SUCCESS;
+}
+
+
+/*
+ * For a declined lease, leave it on the "active" pool, but mark
+ * it as declined. Give it an infinite (well, really long) life.
+ */
+isc_result_t
+decline_lease6(struct ipv6_pool *pool, struct iasubopt *lease) {
+ isc_result_t result;
+
+ if ((lease->state != FTS_ACTIVE) &&
+ (lease->state != FTS_ABANDONED)) {
+ result = move_lease_to_active(pool, lease);
+ if (result != ISC_R_SUCCESS) {
+ return result;
+ }
+ }
+ lease->state = FTS_ABANDONED;
+ lease->hard_lifetime_end_time = MAX_TIME;
+ isc_heap_decreased(pool->active_timeouts, lease->heap_index);
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Put the returned lease on our inactive pool.
+ */
+isc_result_t
+release_lease6(struct ipv6_pool *pool, struct iasubopt *lease) {
+ if (lease->state == FTS_ACTIVE) {
+ return move_lease_to_inactive(pool, lease, FTS_RELEASED);
+ } else {
+ return ISC_R_SUCCESS;
+ }
+}
+
+/*
+ * Create a prefix by hashing the input, and using that for
+ * the part subject to allocation.
+ */
+void
+build_prefix6(struct in6_addr *pref,
+ const struct in6_addr *net_start_pref,
+ int pool_bits, int pref_bits,
+ const struct data_string *input) {
+ isc_md5_t ctx;
+ int net_bytes;
+ int i;
+ char *str;
+ const char *net_str;
+
+ /*
+ * Use MD5 to get a nice 128 bit hash of the input.
+ * Yes, we know MD5 isn't cryptographically sound.
+ * No, we don't care.
+ */
+ isc_md5_init(&ctx);
+ isc_md5_update(&ctx, input->data, input->len);
+ isc_md5_final(&ctx, (unsigned char *)pref);
+
+ /*
+ * Copy the network bits over.
+ */
+ str = (char *)pref;
+ net_str = (const char *)net_start_pref;
+ net_bytes = pool_bits / 8;
+ for (i = 0; i < net_bytes; i++) {
+ str[i] = net_str[i];
+ }
+ i = net_bytes;
+ switch (pool_bits % 8) {
+ case 1: str[i] = (str[i] & 0x7F) | (net_str[i] & 0x80); break;
+ case 2: str[i] = (str[i] & 0x3F) | (net_str[i] & 0xC0); break;
+ case 3: str[i] = (str[i] & 0x1F) | (net_str[i] & 0xE0); break;
+ case 4: str[i] = (str[i] & 0x0F) | (net_str[i] & 0xF0); break;
+ case 5: str[i] = (str[i] & 0x07) | (net_str[i] & 0xF8); break;
+ case 6: str[i] = (str[i] & 0x03) | (net_str[i] & 0xFC); break;
+ case 7: str[i] = (str[i] & 0x01) | (net_str[i] & 0xFE); break;
+ }
+ /*
+ * Zero the remaining bits.
+ */
+ net_bytes = pref_bits / 8;
+ for (i=net_bytes+1; i<16; i++) {
+ str[i] = 0;
+ }
+ i = net_bytes;
+ switch (pref_bits % 8) {
+ case 0: str[i] &= 0; break;
+ case 1: str[i] &= 0x80; break;
+ case 2: str[i] &= 0xC0; break;
+ case 3: str[i] &= 0xE0; break;
+ case 4: str[i] &= 0xF0; break;
+ case 5: str[i] &= 0xF8; break;
+ case 6: str[i] &= 0xFC; break;
+ case 7: str[i] &= 0xFE; break;
+ }
+}
+
+/*
+ * Create a lease for the given prefix and client duid.
+ *
+ * - pool must be a pointer to a (struct pool *) pointer previously
+ * initialized to NULL
+ *
+ * Right now we simply hash the DUID, and if we get a collision, we hash
+ * again until we find a free prefix. We try this a fixed number of times,
+ * to avoid getting stuck in a loop (this is important on small pools
+ * where we can run out of space).
+ *
+ * We return the number of attempts that it took to find an available
+ * prefix. This tells callers when a pool is are filling up, as
+ * well as an indication of how full the pool is; statistically the
+ * more full a pool is the more attempts must be made before finding
+ * a free prefix. Realistically this will only happen in very full
+ * pools.
+ *
+ * We probably want different algorithms depending on the network size, in
+ * the long term.
+ */
+isc_result_t
+create_prefix6(struct ipv6_pool *pool, struct iasubopt **pref,
+ unsigned int *attempts,
+ const struct data_string *uid,
+ time_t soft_lifetime_end_time) {
+ struct data_string ds;
+ struct in6_addr tmp;
+ struct iasubopt *test_iapref;
+ struct data_string new_ds;
+ struct iasubopt *iapref;
+ isc_result_t result;
+
+ /*
+ * Use the UID as our initial seed for the hash
+ */
+ memset(&ds, 0, sizeof(ds));
+ data_string_copy(&ds, (struct data_string *)uid, MDL);
+
+ *attempts = 0;
+ for (;;) {
+ /*
+ * Give up at some point.
+ */
+ if (++(*attempts) > 10) {
+ data_string_forget(&ds, MDL);
+ return ISC_R_NORESOURCES;
+ }
+
+ /*
+ * Build a prefix
+ */
+ build_prefix6(&tmp, &pool->start_addr,
+ pool->bits, pool->units, &ds);
+
+ /*
+ * If this prefix is not in use, we're happy with it
+ */
+ test_iapref = NULL;
+ if (iasubopt_hash_lookup(&test_iapref, pool->leases,
+ &tmp, sizeof(tmp), MDL) == 0) {
+ break;
+ }
+ iasubopt_dereference(&test_iapref, MDL);
+
+ /*
+ * Otherwise, we create a new input, adding the prefix
+ */
+ memset(&new_ds, 0, sizeof(new_ds));
+ new_ds.len = ds.len + sizeof(tmp);
+ if (!buffer_allocate(&new_ds.buffer, new_ds.len, MDL)) {
+ data_string_forget(&ds, MDL);
+ return ISC_R_NOMEMORY;
+ }
+ new_ds.data = new_ds.buffer->data;
+ memcpy(new_ds.buffer->data, ds.data, ds.len);
+ memcpy(new_ds.buffer->data + ds.len, &tmp, sizeof(tmp));
+ data_string_forget(&ds, MDL);
+ data_string_copy(&ds, &new_ds, MDL);
+ data_string_forget(&new_ds, MDL);
+ }
+
+ data_string_forget(&ds, MDL);
+
+ /*
+ * We're happy with the prefix, create an IAPREFIX
+ * to hold it.
+ */
+ iapref = NULL;
+ result = iasubopt_allocate(&iapref, MDL);
+ if (result != ISC_R_SUCCESS) {
+ return result;
+ }
+ iapref->plen = (u_int8_t)pool->units;
+ memcpy(&iapref->addr, &tmp, sizeof(iapref->addr));
+
+ /*
+ * Add the prefix to the pool (note state is free, not active?!).
+ */
+ result = add_lease6(pool, iapref, soft_lifetime_end_time);
+ if (result == ISC_R_SUCCESS) {
+ iasubopt_reference(pref, iapref, MDL);
+ }
+ iasubopt_dereference(&iapref, MDL);
+ return result;
+}
+
+/*
+ * Determine if a prefix is present in a pool or not.
+ */
+isc_boolean_t
+prefix6_exists(const struct ipv6_pool *pool,
+ const struct in6_addr *pref, u_int8_t plen) {
+ struct iasubopt *test_iapref;
+
+ if ((int)plen != pool->units)
+ return ISC_FALSE;
+
+ test_iapref = NULL;
+ if (iasubopt_hash_lookup(&test_iapref, pool->leases,
+ (void *)pref, sizeof(*pref), MDL)) {
+ iasubopt_dereference(&test_iapref, MDL);
+ return ISC_TRUE;
+ } else {
+ return ISC_FALSE;
+ }
+}
+
+/*
+ * Mark an IPv6 address/prefix as unavailable from a pool.
+ *
+ * This is used for host entries and the addresses of the server itself.
+ */
+isc_result_t
+mark_lease_unavailable(struct ipv6_pool *pool, const struct in6_addr *addr) {
+ struct iasubopt *dummy_iasubopt;
+ isc_result_t result;
+
+ dummy_iasubopt = NULL;
+ result = iasubopt_allocate(&dummy_iasubopt, MDL);
+ if (result == ISC_R_SUCCESS) {
+ dummy_iasubopt->addr = *addr;
+ iasubopt_hash_add(pool->leases, &dummy_iasubopt->addr,
+ sizeof(*addr), dummy_iasubopt, MDL);
+ }
+ return result;
+}
+
+/*
+ * Add a pool.
+ */
+isc_result_t
+add_ipv6_pool(struct ipv6_pool *pool) {
+ struct ipv6_pool **new_pools;
+
+ new_pools = dmalloc(sizeof(struct ipv6_pool *) * (num_pools+1), MDL);
+ if (new_pools == NULL) {
+ return ISC_R_NOMEMORY;
+ }
+
+ if (num_pools > 0) {
+ memcpy(new_pools, pools,
+ sizeof(struct ipv6_pool *) * num_pools);
+ dfree(pools, MDL);
+ }
+ pools = new_pools;
+
+ pools[num_pools] = NULL;
+ ipv6_pool_reference(&pools[num_pools], pool, MDL);
+ num_pools++;
+ return ISC_R_SUCCESS;
+}
+
+static void
+cleanup_old_expired(struct ipv6_pool *pool) {
+ struct iasubopt *tmp;
+ struct ia_xx *ia;
+ struct ia_xx *ia_active;
+ unsigned char *tmpd;
+ time_t timeout;
+
+ while (pool->num_inactive > 0) {
+ tmp = (struct iasubopt *)
+ isc_heap_element(pool->inactive_timeouts, 1);
+ if (tmp->hard_lifetime_end_time != 0) {
+ timeout = tmp->hard_lifetime_end_time;
+ timeout += EXPIRED_IPV6_CLEANUP_TIME;
+ } else {
+ timeout = tmp->soft_lifetime_end_time;
+ }
+ if (cur_time < timeout) {
+ break;
+ }
+
+ isc_heap_delete(pool->inactive_timeouts, tmp->heap_index);
+ pool->num_inactive--;
+
+ if (tmp->ia != NULL) {
+ /*
+ * Check to see if this IA is in an active list,
+ * but has no remaining resources. If so, remove it
+ * from the active list.
+ */
+ ia = NULL;
+ ia_reference(&ia, tmp->ia, MDL);
+ ia_remove_iasubopt(ia, tmp, MDL);
+ ia_active = NULL;
+ tmpd = (unsigned char *)ia->iaid_duid.data;
+ if ((ia->ia_type == D6O_IA_NA) &&
+ (ia->num_iasubopt <= 0) &&
+ (ia_hash_lookup(&ia_active, ia_na_active, tmpd,
+ ia->iaid_duid.len, MDL) == 0) &&
+ (ia_active == ia)) {
+ ia_hash_delete(ia_na_active, tmpd,
+ ia->iaid_duid.len, MDL);
+ }
+ if ((ia->ia_type == D6O_IA_TA) &&
+ (ia->num_iasubopt <= 0) &&
+ (ia_hash_lookup(&ia_active, ia_ta_active, tmpd,
+ ia->iaid_duid.len, MDL) == 0) &&
+ (ia_active == ia)) {
+ ia_hash_delete(ia_ta_active, tmpd,
+ ia->iaid_duid.len, MDL);
+ }
+ if ((ia->ia_type == D6O_IA_PD) &&
+ (ia->num_iasubopt <= 0) &&
+ (ia_hash_lookup(&ia_active, ia_pd_active, tmpd,
+ ia->iaid_duid.len, MDL) == 0) &&
+ (ia_active == ia)) {
+ ia_hash_delete(ia_pd_active, tmpd,
+ ia->iaid_duid.len, MDL);
+ }
+ ia_dereference(&ia, MDL);
+ }
+ iasubopt_dereference(&tmp, MDL);
+ }
+}
+
+static void
+lease_timeout_support(void *vpool) {
+ struct ipv6_pool *pool;
+ struct iasubopt *lease;
+
+ pool = (struct ipv6_pool *)vpool;
+ for (;;) {
+ /*
+ * Get the next lease scheduled to expire.
+ *
+ * Note that if there are no leases in the pool,
+ * expire_lease6() will return ISC_R_SUCCESS with
+ * a NULL lease.
+ *
+ * expire_lease6() will call move_lease_to_inactive() which
+ * calls ddns_removals() do we want that on the standard
+ * expiration timer or a special 'depref' timer? Original
+ * query from DH, moved here by SAR.
+ */
+ lease = NULL;
+ if (expire_lease6(&lease, pool, cur_time) != ISC_R_SUCCESS) {
+ break;
+ }
+ if (lease == NULL) {
+ break;
+ }
+
+ write_ia(lease->ia);
+
+ iasubopt_dereference(&lease, MDL);
+ }
+
+ /*
+ * If appropriate commit and rotate the lease file
+ * As commit_leases_timed() checks to see if we've done any writes
+ * we don't bother tracking if this function called write _ia
+ */
+ (void) commit_leases_timed();
+
+ /*
+ * Do some cleanup of our expired leases.
+ */
+ cleanup_old_expired(pool);
+
+ /*
+ * Schedule next round of expirations.
+ */
+ schedule_lease_timeout(pool);
+}
+
+/*
+ * For a given pool, add a timer that will remove the next
+ * lease to expire.
+ */
+void
+schedule_lease_timeout(struct ipv6_pool *pool) {
+ struct iasubopt *tmp;
+ time_t timeout;
+ time_t next_timeout;
+ struct timeval tv;
+
+ next_timeout = MAX_TIME;
+
+ if (pool->num_active > 0) {
+ tmp = (struct iasubopt *)
+ isc_heap_element(pool->active_timeouts, 1);
+ if (tmp->hard_lifetime_end_time < next_timeout) {
+ next_timeout = tmp->hard_lifetime_end_time + 1;
+ }
+ }
+
+ if (pool->num_inactive > 0) {
+ tmp = (struct iasubopt *)
+ isc_heap_element(pool->inactive_timeouts, 1);
+ if (tmp->hard_lifetime_end_time != 0) {
+ timeout = tmp->hard_lifetime_end_time;
+ timeout += EXPIRED_IPV6_CLEANUP_TIME;
+ } else {
+ timeout = tmp->soft_lifetime_end_time + 1;
+ }
+ if (timeout < next_timeout) {
+ next_timeout = timeout;
+ }
+ }
+
+ if (next_timeout < MAX_TIME) {
+ tv.tv_sec = next_timeout;
+ tv.tv_usec = 0;
+ add_timeout(&tv, lease_timeout_support, pool,
+ (tvref_t)ipv6_pool_reference,
+ (tvunref_t)ipv6_pool_dereference);
+ }
+}
+
+/*
+ * Schedule timeouts across all pools.
+ */
+void
+schedule_all_ipv6_lease_timeouts(void) {
+ int i;
+
+ for (i=0; i<num_pools; i++) {
+ schedule_lease_timeout(pools[i]);
+ }
+}
+
+/*
+ * Given an address and the length of the network mask, return
+ * only the network portion.
+ *
+ * Examples:
+ *
+ * "fe80::216:6fff:fe49:7d9b", length 64 = "fe80::"
+ * "2001:888:1936:2:216:6fff:fe49:7d9b", length 48 = "2001:888:1936::"
+ */
+static void
+ipv6_network_portion(struct in6_addr *result,
+ const struct in6_addr *addr, int bits) {
+ unsigned char *addrp;
+ int mask_bits;
+ int bytes;
+ int extra_bits;
+ int i;
+
+ static const unsigned char bitmasks[] = {
+ 0x00, 0xFE, 0xFC, 0xF8,
+ 0xF0, 0xE0, 0xC0, 0x80,
+ };
+
+ /*
+ * Sanity check our bits. ;)
+ */
+ if ((bits < 0) || (bits > 128)) {
+ log_fatal("ipv6_network_portion: bits %d not between 0 and 128",
+ bits);
+ }
+
+ /*
+ * Copy our address portion.
+ */
+ *result = *addr;
+ addrp = ((unsigned char *)result) + 15;
+
+ /*
+ * Zero out masked portion.
+ */
+ mask_bits = 128 - bits;
+ bytes = mask_bits / 8;
+ extra_bits = mask_bits % 8;
+
+ for (i=0; i<bytes; i++) {
+ *addrp = 0;
+ addrp--;
+ }
+ if (extra_bits) {
+ *addrp &= bitmasks[extra_bits];
+ }
+}
+
+/*
+ * Determine if the given address/prefix is in the pool.
+ */
+isc_boolean_t
+ipv6_in_pool(const struct in6_addr *addr, const struct ipv6_pool *pool) {
+ struct in6_addr tmp;
+
+ ipv6_network_portion(&tmp, addr, pool->bits);
+ if (memcmp(&tmp, &pool->start_addr, sizeof(tmp)) == 0) {
+ return ISC_TRUE;
+ } else {
+ return ISC_FALSE;
+ }
+}
+
+/*
+ * Find the pool that contains the given address.
+ *
+ * - pool must be a pointer to a (struct ipv6_pool *) pointer previously
+ * initialized to NULL
+ */
+isc_result_t
+find_ipv6_pool(struct ipv6_pool **pool, u_int16_t type,
+ const struct in6_addr *addr) {
+ int i;
+
+ if (pool == NULL) {
+ log_error("%s(%d): NULL pointer reference", MDL);
+ return DHCP_R_INVALIDARG;
+ }
+ if (*pool != NULL) {
+ log_error("%s(%d): non-NULL pointer", MDL);
+ return DHCP_R_INVALIDARG;
+ }
+
+ for (i=0; i<num_pools; i++) {
+ if (pools[i]->pool_type != type)
+ continue;
+ if (ipv6_in_pool(addr, pools[i])) {
+ ipv6_pool_reference(pool, pools[i], MDL);
+ return ISC_R_SUCCESS;
+ }
+ }
+ return ISC_R_NOTFOUND;
+}
+
+/*
+ * Helper function for the various functions that act across all
+ * pools.
+ */
+static isc_result_t
+change_leases(struct ia_xx *ia,
+ isc_result_t (*change_func)(struct ipv6_pool *,
+ struct iasubopt *)) {
+ isc_result_t retval;
+ isc_result_t renew_retval;
+ struct ipv6_pool *pool;
+ struct in6_addr *addr;
+ int i;
+
+ retval = ISC_R_SUCCESS;
+ for (i=0; i<ia->num_iasubopt; i++) {
+ pool = NULL;
+ addr = &ia->iasubopt[i]->addr;
+ if (find_ipv6_pool(&pool, ia->ia_type,
+ addr) == ISC_R_SUCCESS) {
+ renew_retval = change_func(pool, ia->iasubopt[i]);
+ if (renew_retval != ISC_R_SUCCESS) {
+ retval = renew_retval;
+ }
+ }
+ /* XXXsk: should we warn if we don't find a pool? */
+ }
+ return retval;
+}
+
+/*
+ * Renew all leases in an IA from all pools.
+ *
+ * The new lifetime should be in the soft_lifetime_end_time
+ * and will be moved to hard_lifetime_end_time by renew_lease6.
+ */
+isc_result_t
+renew_leases(struct ia_xx *ia) {
+ return change_leases(ia, renew_lease6);
+}
+
+/*
+ * Release all leases in an IA from all pools.
+ */
+isc_result_t
+release_leases(struct ia_xx *ia) {
+ return change_leases(ia, release_lease6);
+}
+
+/*
+ * Decline all leases in an IA from all pools.
+ */
+isc_result_t
+decline_leases(struct ia_xx *ia) {
+ return change_leases(ia, decline_lease6);
+}
+
+#ifdef DHCPv6
+/*
+ * Helper function to output leases.
+ */
+static int write_error;
+
+static isc_result_t
+write_ia_leases(const void *name, unsigned len, void *value) {
+ struct ia_xx *ia = (struct ia_xx *)value;
+
+ if (!write_error) {
+ if (!write_ia(ia)) {
+ write_error = 1;
+ }
+ }
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Write all DHCPv6 information.
+ */
+int
+write_leases6(void) {
+ write_error = 0;
+ write_server_duid();
+ ia_hash_foreach(ia_na_active, write_ia_leases);
+ if (write_error) {
+ return 0;
+ }
+ ia_hash_foreach(ia_ta_active, write_ia_leases);
+ if (write_error) {
+ return 0;
+ }
+ ia_hash_foreach(ia_pd_active, write_ia_leases);
+ if (write_error) {
+ return 0;
+ }
+ return 1;
+}
+#endif /* DHCPv6 */
+
+static isc_result_t
+mark_hosts_unavailable_support(const void *name, unsigned len, void *value) {
+ struct host_decl *h;
+ struct data_string fixed_addr;
+ struct in6_addr addr;
+ struct ipv6_pool *p;
+
+ h = (struct host_decl *)value;
+
+ /*
+ * If the host has no address, we don't need to mark anything.
+ */
+ if (h->fixed_addr == NULL) {
+ return ISC_R_SUCCESS;
+ }
+
+ /*
+ * Evaluate the fixed address.
+ */
+ memset(&fixed_addr, 0, sizeof(fixed_addr));
+ if (!evaluate_option_cache(&fixed_addr, NULL, NULL, NULL, NULL, NULL,
+ &global_scope, h->fixed_addr, MDL)) {
+ log_error("mark_hosts_unavailable: "
+ "error evaluating host address.");
+ return ISC_R_SUCCESS;
+ }
+ if (fixed_addr.len != 16) {
+ log_error("mark_hosts_unavailable: "
+ "host address is not 128 bits.");
+ return ISC_R_SUCCESS;
+ }
+ memcpy(&addr, fixed_addr.data, 16);
+ data_string_forget(&fixed_addr, MDL);
+
+ /*
+ * Find the pool holding this host, and mark the address.
+ * (I suppose it is arguably valid to have a host that does not
+ * sit in any pool.)
+ */
+ p = NULL;
+ if (find_ipv6_pool(&p, D6O_IA_NA, &addr) == ISC_R_SUCCESS) {
+ mark_lease_unavailable(p, &addr);
+ ipv6_pool_dereference(&p, MDL);
+ }
+ if (find_ipv6_pool(&p, D6O_IA_TA, &addr) == ISC_R_SUCCESS) {
+ mark_lease_unavailable(p, &addr);
+ ipv6_pool_dereference(&p, MDL);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+void
+mark_hosts_unavailable(void) {
+ hash_foreach(host_name_hash, mark_hosts_unavailable_support);
+}
+
+static isc_result_t
+mark_phosts_unavailable_support(const void *name, unsigned len, void *value) {
+ struct host_decl *h;
+ struct iaddrcidrnetlist *l;
+ struct in6_addr pref;
+ struct ipv6_pool *p;
+
+ h = (struct host_decl *)value;
+
+ /*
+ * If the host has no prefix, we don't need to mark anything.
+ */
+ if (h->fixed_prefix == NULL) {
+ return ISC_R_SUCCESS;
+ }
+
+ /*
+ * Get the fixed prefixes.
+ */
+ for (l = h->fixed_prefix; l != NULL; l = l->next) {
+ if (l->cidrnet.lo_addr.len != 16) {
+ continue;
+ }
+ memcpy(&pref, l->cidrnet.lo_addr.iabuf, 16);
+
+ /*
+ * Find the pool holding this host, and mark the prefix.
+ * (I suppose it is arguably valid to have a host that does not
+ * sit in any pool.)
+ */
+ p = NULL;
+ if (find_ipv6_pool(&p, D6O_IA_PD, &pref) != ISC_R_SUCCESS) {
+ continue;
+ }
+ if (l->cidrnet.bits != p->units) {
+ ipv6_pool_dereference(&p, MDL);
+ continue;
+ }
+ mark_lease_unavailable(p, &pref);
+ ipv6_pool_dereference(&p, MDL);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+void
+mark_phosts_unavailable(void) {
+ hash_foreach(host_name_hash, mark_phosts_unavailable_support);
+}
+
+void
+mark_interfaces_unavailable(void) {
+ struct interface_info *ip;
+ int i;
+ struct ipv6_pool *p;
+
+ ip = interfaces;
+ while (ip != NULL) {
+ for (i=0; i<ip->v6address_count; i++) {
+ p = NULL;
+ if (find_ipv6_pool(&p, D6O_IA_NA, &ip->v6addresses[i])
+ == ISC_R_SUCCESS) {
+ mark_lease_unavailable(p,
+ &ip->v6addresses[i]);
+ ipv6_pool_dereference(&p, MDL);
+ }
+ if (find_ipv6_pool(&p, D6O_IA_TA, &ip->v6addresses[i])
+ == ISC_R_SUCCESS) {
+ mark_lease_unavailable(p,
+ &ip->v6addresses[i]);
+ ipv6_pool_dereference(&p, MDL);
+ }
+ }
+ ip = ip->next;
+ }
+}
+
+/* unittest moved to server/tests/mdb6_unittest.c */
diff --git a/server/omapi.c b/server/omapi.c
new file mode 100644
index 0000000..36e70a8
--- /dev/null
+++ b/server/omapi.c
@@ -0,0 +1,2565 @@
+/* omapi.c
+
+ OMAPI object interfaces for the DHCP server. */
+
+/*
+ * Copyright (c) 2012-2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2010 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>
+
+static isc_result_t class_lookup (omapi_object_t **,
+ omapi_object_t *, omapi_object_t *,
+ omapi_object_type_t *);
+
+omapi_object_type_t *dhcp_type_lease;
+omapi_object_type_t *dhcp_type_pool;
+omapi_object_type_t *dhcp_type_class;
+omapi_object_type_t *dhcp_type_subclass;
+omapi_object_type_t *dhcp_type_host;
+#if defined (FAILOVER_PROTOCOL)
+omapi_object_type_t *dhcp_type_failover_state;
+omapi_object_type_t *dhcp_type_failover_link;
+omapi_object_type_t *dhcp_type_failover_listener;
+#endif
+
+void dhcp_db_objects_setup ()
+{
+ isc_result_t status;
+
+ status = omapi_object_type_register (&dhcp_type_lease,
+ "lease",
+ dhcp_lease_set_value,
+ dhcp_lease_get_value,
+ dhcp_lease_destroy,
+ dhcp_lease_signal_handler,
+ dhcp_lease_stuff_values,
+ dhcp_lease_lookup,
+ dhcp_lease_create,
+ dhcp_lease_remove,
+#if defined (COMPACT_LEASES)
+ dhcp_lease_free,
+ dhcp_lease_get,
+#else
+ 0, 0,
+#endif
+ 0,
+ sizeof (struct lease),
+ 0, RC_LEASE);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register lease object type: %s",
+ isc_result_totext (status));
+
+ status = omapi_object_type_register (&dhcp_type_class,
+ "class",
+ dhcp_class_set_value,
+ dhcp_class_get_value,
+ dhcp_class_destroy,
+ dhcp_class_signal_handler,
+ dhcp_class_stuff_values,
+ dhcp_class_lookup,
+ dhcp_class_create,
+ dhcp_class_remove, 0, 0, 0,
+ sizeof (struct class), 0,
+ RC_MISC);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register class object type: %s",
+ isc_result_totext (status));
+
+ status = omapi_object_type_register (&dhcp_type_subclass,
+ "subclass",
+ dhcp_subclass_set_value,
+ dhcp_subclass_get_value,
+ dhcp_class_destroy,
+ dhcp_subclass_signal_handler,
+ dhcp_subclass_stuff_values,
+ dhcp_subclass_lookup,
+ dhcp_subclass_create,
+ dhcp_subclass_remove, 0, 0, 0,
+ sizeof (struct class), 0, RC_MISC);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register subclass object type: %s",
+ isc_result_totext (status));
+
+ status = omapi_object_type_register (&dhcp_type_pool,
+ "pool",
+ dhcp_pool_set_value,
+ dhcp_pool_get_value,
+ dhcp_pool_destroy,
+ dhcp_pool_signal_handler,
+ dhcp_pool_stuff_values,
+ dhcp_pool_lookup,
+ dhcp_pool_create,
+ dhcp_pool_remove, 0, 0, 0,
+ sizeof (struct pool), 0, RC_MISC);
+
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register pool object type: %s",
+ isc_result_totext (status));
+
+ status = omapi_object_type_register (&dhcp_type_host,
+ "host",
+ dhcp_host_set_value,
+ dhcp_host_get_value,
+ dhcp_host_destroy,
+ dhcp_host_signal_handler,
+ dhcp_host_stuff_values,
+ dhcp_host_lookup,
+ dhcp_host_create,
+ dhcp_host_remove, 0, 0, 0,
+ sizeof (struct host_decl),
+ 0, RC_MISC);
+
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register host object type: %s",
+ isc_result_totext (status));
+
+#if defined (FAILOVER_PROTOCOL)
+ status = omapi_object_type_register (&dhcp_type_failover_state,
+ "failover-state",
+ dhcp_failover_state_set_value,
+ dhcp_failover_state_get_value,
+ dhcp_failover_state_destroy,
+ dhcp_failover_state_signal,
+ dhcp_failover_state_stuff,
+ dhcp_failover_state_lookup,
+ dhcp_failover_state_create,
+ dhcp_failover_state_remove,
+ 0, 0, 0,
+ sizeof (dhcp_failover_state_t),
+ 0, RC_MISC);
+
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register failover state object type: %s",
+ isc_result_totext (status));
+
+ status = omapi_object_type_register (&dhcp_type_failover_link,
+ "failover-link",
+ dhcp_failover_link_set_value,
+ dhcp_failover_link_get_value,
+ dhcp_failover_link_destroy,
+ dhcp_failover_link_signal,
+ dhcp_failover_link_stuff_values,
+ 0, 0, 0, 0, 0, 0,
+ sizeof (dhcp_failover_link_t), 0,
+ RC_MISC);
+
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register failover link object type: %s",
+ isc_result_totext (status));
+
+ status = omapi_object_type_register (&dhcp_type_failover_listener,
+ "failover-listener",
+ dhcp_failover_listener_set_value,
+ dhcp_failover_listener_get_value,
+ dhcp_failover_listener_destroy,
+ dhcp_failover_listener_signal,
+ dhcp_failover_listener_stuff,
+ 0, 0, 0, 0, 0, 0,
+ sizeof
+ (dhcp_failover_listener_t), 0,
+ RC_MISC);
+
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't register failover listener object type: %s",
+ isc_result_totext (status));
+#endif /* FAILOVER_PROTOCOL */
+}
+
+isc_result_t dhcp_lease_set_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_typed_data_t *value)
+{
+ struct lease *lease;
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_lease)
+ return DHCP_R_INVALIDARG;
+ lease = (struct lease *)h;
+
+ /* We're skipping a lot of things it might be interesting to
+ set - for now, we just make it possible to whack the state. */
+ if (!omapi_ds_strcmp (name, "state")) {
+ unsigned long bar;
+ const char *ols, *nls;
+ status = omapi_get_int_value (&bar, value);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ if (bar < 1 || bar > FTS_LAST)
+ return DHCP_R_INVALIDARG;
+ nls = binding_state_names [bar - 1];
+ if (lease -> binding_state >= 1 &&
+ lease -> binding_state <= FTS_LAST)
+ ols = binding_state_names [lease -> binding_state - 1];
+ else
+ ols = "unknown state";
+
+ if (lease -> binding_state != bar) {
+ lease -> next_binding_state = bar;
+ if (supersede_lease (lease, NULL, 1, 1, 1, 0)) {
+ log_info ("lease %s state changed from %s to %s",
+ piaddr(lease->ip_addr), ols, nls);
+ return ISC_R_SUCCESS;
+ }
+ log_info ("lease %s state change from %s to %s failed.",
+ piaddr (lease -> ip_addr), ols, nls);
+ return ISC_R_IOERROR;
+ }
+ return DHCP_R_UNCHANGED;
+ } else if (!omapi_ds_strcmp (name, "ip-address")) {
+ return ISC_R_NOPERM;
+ } else if (!omapi_ds_strcmp (name, "dhcp-client-identifier")) {
+ return DHCP_R_UNCHANGED; /* XXX take change. */
+ } else if (!omapi_ds_strcmp (name, "hostname")) {
+ return DHCP_R_UNCHANGED; /* XXX take change. */
+ } else if (!omapi_ds_strcmp (name, "client-hostname")) {
+ return DHCP_R_UNCHANGED; /* XXX take change. */
+ } else if (!omapi_ds_strcmp (name, "host")) {
+ return DHCP_R_UNCHANGED; /* XXX take change. */
+ } else if (!omapi_ds_strcmp (name, "subnet")) {
+ return DHCP_R_INVALIDARG;
+ } else if (!omapi_ds_strcmp (name, "pool")) {
+ return ISC_R_NOPERM;
+ } else if (!omapi_ds_strcmp (name, "starts")) {
+ return ISC_R_NOPERM;
+ } else if (!omapi_ds_strcmp (name, "ends")) {
+ unsigned long lease_end, old_lease_end;
+ status = omapi_get_int_value (&lease_end, value);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ old_lease_end = lease->ends;
+ lease->ends = lease_end;
+ if (supersede_lease (lease, NULL, 1, 1, 1, 0)) {
+ log_info ("lease %s end changed from %lu to %lu",
+ piaddr(lease->ip_addr), old_lease_end, lease_end);
+ return ISC_R_SUCCESS;
+ }
+ log_info ("lease %s end change from %lu to %lu failed",
+ piaddr(lease->ip_addr), old_lease_end, lease_end);
+ return ISC_R_IOERROR;
+ } else if (!omapi_ds_strcmp(name, "flags")) {
+ u_int8_t oldflags;
+
+ if (value->type != omapi_datatype_data)
+ return DHCP_R_INVALIDARG;
+
+ oldflags = lease->flags;
+ lease->flags = (value->u.buffer.value[0] & EPHEMERAL_FLAGS) |
+ (lease->flags & ~EPHEMERAL_FLAGS);
+ if(oldflags == lease->flags)
+ return ISC_R_SUCCESS;
+ if (!supersede_lease(lease, NULL, 1, 1, 1, 0)) {
+ log_error("Failed to update flags for lease %s.",
+ piaddr(lease->ip_addr));
+ return ISC_R_IOERROR;
+ }
+ return ISC_R_SUCCESS;
+ } else if (!omapi_ds_strcmp (name, "billing-class")) {
+ return DHCP_R_UNCHANGED; /* XXX carefully allow change. */
+ } else if (!omapi_ds_strcmp (name, "hardware-address")) {
+ return DHCP_R_UNCHANGED; /* XXX take change. */
+ } else if (!omapi_ds_strcmp (name, "hardware-type")) {
+ return DHCP_R_UNCHANGED; /* XXX take change. */
+ } else if (lease -> scope) {
+ status = binding_scope_set_value (lease -> scope, 0, name, value);
+ if (status == ISC_R_SUCCESS) {
+ if (write_lease (lease) && commit_leases ())
+ return ISC_R_SUCCESS;
+ return ISC_R_IOERROR;
+ }
+ }
+
+ /* 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;
+ }
+
+ if (!lease -> scope) {
+ if (!binding_scope_allocate (&lease -> scope, MDL))
+ return ISC_R_NOMEMORY;
+ }
+ status = binding_scope_set_value (lease -> scope, 1, name, value);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ if (write_lease (lease) && commit_leases ())
+ return ISC_R_SUCCESS;
+ return ISC_R_IOERROR;
+}
+
+
+isc_result_t dhcp_lease_get_value (omapi_object_t *h, omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_value_t **value)
+{
+ struct lease *lease;
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_lease)
+ return DHCP_R_INVALIDARG;
+ lease = (struct lease *)h;
+
+ if (!omapi_ds_strcmp (name, "state"))
+ return omapi_make_int_value (value, name,
+ (int)lease -> binding_state, MDL);
+ else if (!omapi_ds_strcmp (name, "ip-address"))
+ return omapi_make_const_value (value, name,
+ lease -> ip_addr.iabuf,
+ lease -> ip_addr.len, MDL);
+ else if (!omapi_ds_strcmp (name, "dhcp-client-identifier")) {
+ return omapi_make_const_value (value, name,
+ lease -> uid,
+ lease -> uid_len, MDL);
+ } else if (!omapi_ds_strcmp (name, "client-hostname")) {
+ if (lease -> client_hostname)
+ return omapi_make_string_value
+ (value, name, lease -> client_hostname, MDL);
+ return ISC_R_NOTFOUND;
+ } else if (!omapi_ds_strcmp (name, "host")) {
+ if (lease -> host)
+ return omapi_make_handle_value
+ (value, name,
+ ((omapi_object_t *)lease -> host), MDL);
+ } else if (!omapi_ds_strcmp (name, "subnet"))
+ return omapi_make_handle_value (value, name,
+ ((omapi_object_t *)
+ lease -> subnet), MDL);
+ else if (!omapi_ds_strcmp (name, "pool"))
+ return omapi_make_handle_value (value, name,
+ ((omapi_object_t *)
+ lease -> pool), MDL);
+ else if (!omapi_ds_strcmp (name, "billing-class")) {
+ if (lease -> billing_class)
+ return omapi_make_handle_value
+ (value, name,
+ ((omapi_object_t *)lease -> billing_class),
+ MDL);
+ return ISC_R_NOTFOUND;
+ } else if (!omapi_ds_strcmp (name, "hardware-address")) {
+ if (lease -> hardware_addr.hlen)
+ return omapi_make_const_value
+ (value, name, &lease -> hardware_addr.hbuf [1],
+ (unsigned)(lease -> hardware_addr.hlen - 1),
+ MDL);
+ return ISC_R_NOTFOUND;
+ } else if (!omapi_ds_strcmp (name, "hardware-type")) {
+ if (lease -> hardware_addr.hlen)
+ return omapi_make_int_value
+ (value, name, lease -> hardware_addr.hbuf [0],
+ MDL);
+ return ISC_R_NOTFOUND;
+ } else if (lease -> scope) {
+ status = binding_scope_get_value (value, lease -> scope, name);
+ if (status != ISC_R_NOTFOUND)
+ return status;
+ }
+
+ /* 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 DHCP_R_UNKNOWNATTRIBUTE;
+}
+
+isc_result_t dhcp_lease_destroy (omapi_object_t *h, const char *file, int line)
+{
+ struct lease *lease;
+
+ if (h -> type != dhcp_type_lease)
+ return DHCP_R_INVALIDARG;
+ lease = (struct lease *)h;
+
+ if (lease -> uid)
+ uid_hash_delete (lease);
+ hw_hash_delete (lease);
+
+ if (lease -> on_release)
+ executable_statement_dereference (&lease -> on_release,
+ file, line);
+ if (lease -> on_expiry)
+ executable_statement_dereference (&lease -> on_expiry,
+ file, line);
+ if (lease -> on_commit)
+ executable_statement_dereference (&lease -> on_commit,
+ file, line);
+ if (lease -> scope)
+ binding_scope_dereference (&lease -> scope, file, line);
+
+ if (lease -> agent_options)
+ option_chain_head_dereference (&lease -> agent_options,
+ file, line);
+ if (lease -> uid && lease -> uid != lease -> uid_buf) {
+ dfree (lease -> uid, MDL);
+ lease -> uid = &lease -> uid_buf [0];
+ lease -> uid_len = 0;
+ }
+
+ if (lease -> client_hostname) {
+ dfree (lease -> client_hostname, MDL);
+ lease -> client_hostname = (char *)0;
+ }
+
+ if (lease -> host)
+ host_dereference (&lease -> host, file, line);
+ if (lease -> subnet)
+ subnet_dereference (&lease -> subnet, file, line);
+ if (lease -> pool)
+ pool_dereference (&lease -> pool, file, line);
+
+ if (lease -> state) {
+ free_lease_state (lease -> state, file, line);
+ lease -> state = (struct lease_state *)0;
+
+ cancel_timeout (lease_ping_timeout, lease);
+ --outstanding_pings; /* XXX */
+ }
+
+ if (lease -> billing_class)
+ class_dereference
+ (&lease -> billing_class, file, line);
+
+#if defined (DEBUG_MEMORY_LEAKAGE) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ /* XXX we should never be destroying a lease with a next
+ XXX pointer except on exit... */
+ if (lease -> next)
+ lease_dereference (&lease -> next, file, line);
+ if (lease -> n_hw)
+ lease_dereference (&lease -> n_hw, file, line);
+ if (lease -> n_uid)
+ lease_dereference (&lease -> n_uid, file, line);
+ if (lease -> next_pending)
+ lease_dereference (&lease -> next_pending, file, line);
+#endif
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_lease_signal_handler (omapi_object_t *h,
+ const char *name, va_list ap)
+{
+ /* h should point to (struct lease *) */
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_lease)
+ return DHCP_R_INVALIDARG;
+
+ if (!strcmp (name, "updated"))
+ return ISC_R_SUCCESS;
+
+ /* 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_lease_stuff_values (omapi_object_t *c,
+ omapi_object_t *id,
+ omapi_object_t *h)
+{
+ u_int32_t bouncer;
+ struct lease *lease;
+ isc_result_t status;
+ u_int8_t flagbuf;
+
+ if (h -> type != dhcp_type_lease)
+ return DHCP_R_INVALIDARG;
+ lease = (struct lease *)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 (int));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32 (c, lease -> binding_state);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "ip-address");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32 (c, lease -> ip_addr.len);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_copyin (c, lease -> ip_addr.iabuf,
+ lease -> ip_addr.len);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ if (lease -> uid_len) {
+ status = omapi_connection_put_name (c,
+ "dhcp-client-identifier");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32 (c, lease -> uid_len);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ if (lease -> uid_len) {
+ status = omapi_connection_copyin (c, lease -> uid,
+ lease -> uid_len);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ }
+ }
+
+ if (lease -> client_hostname) {
+ status = omapi_connection_put_name (c, "client-hostname");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status =
+ omapi_connection_put_string (c,
+ lease -> client_hostname);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ }
+
+ if (lease -> host) {
+ status = omapi_connection_put_name (c, "host");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_handle (c,
+ (omapi_object_t *)
+ lease -> host);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ }
+
+ status = omapi_connection_put_name (c, "subnet");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_handle
+ (c, (omapi_object_t *)lease -> subnet);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "pool");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_handle (c,
+ (omapi_object_t *)lease -> pool);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ if (lease -> billing_class) {
+ status = omapi_connection_put_name (c, "billing-class");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_handle
+ (c, (omapi_object_t *)lease -> billing_class);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ }
+
+ if (lease -> hardware_addr.hlen) {
+ status = omapi_connection_put_name (c, "hardware-address");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = (omapi_connection_put_uint32
+ (c,
+ (unsigned long)(lease -> hardware_addr.hlen - 1)));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = (omapi_connection_copyin
+ (c, &lease -> hardware_addr.hbuf [1],
+ (unsigned long)(lease -> hardware_addr.hlen - 1)));
+
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "hardware-type");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32 (c, sizeof (int));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32
+ (c, lease -> hardware_addr.hbuf [0]);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ }
+
+ /* TIME values may be 64-bit, depending on system architecture.
+ * OMAPI must be system independent, both in terms of transmitting
+ * bytes on the wire in network byte order, and in terms of being
+ * readable and usable by both systems.
+ *
+ * XXX: In a future feature release, a put_int64() should be made
+ * to exist, and perhaps a put_time() wrapper that selects which
+ * to use based upon sizeof(TIME). In the meantime, use existing,
+ * 32-bit, code.
+ */
+ bouncer = (u_int32_t)lease->ends;
+ status = omapi_connection_put_name(c, "ends");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32(c, sizeof(bouncer));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32(c, bouncer);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ bouncer = (u_int32_t)lease->starts;
+ status = omapi_connection_put_name(c, "starts");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32(c, sizeof(bouncer));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32(c, bouncer);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ bouncer = (u_int32_t)lease->tstp;
+ status = omapi_connection_put_name(c, "tstp");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32(c, sizeof(bouncer));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32(c, bouncer);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ bouncer = (u_int32_t)lease->tsfp;
+ status = omapi_connection_put_name(c, "tsfp");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32(c, sizeof(bouncer));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32(c, bouncer);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ bouncer = (u_int32_t)lease->atsfp;
+ status = omapi_connection_put_name(c, "atsfp");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32(c, sizeof(bouncer));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32(c, bouncer);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ bouncer = (u_int32_t)lease->cltt;
+ status = omapi_connection_put_name(c, "cltt");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32(c, sizeof(bouncer));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32(c, bouncer);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "flags");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32(c, sizeof(flagbuf));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ flagbuf = lease->flags & EPHEMERAL_FLAGS;
+ status = omapi_connection_copyin(c, &flagbuf, sizeof(flagbuf));
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ if (lease -> scope) {
+ status = binding_scope_stuff_values (c, lease -> scope);
+ 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_lease_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 lease *lease;
+
+ 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_lease) {
+ omapi_object_dereference (lp, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+ }
+
+ /* Now look for an IP address. */
+ status = omapi_get_value_str (ref, id, "ip-address", &tv);
+ if (status == ISC_R_SUCCESS) {
+ lease = (struct lease *)0;
+ lease_ip_hash_lookup(&lease, lease_ip_addr_hash,
+ tv->value->u.buffer.value,
+ tv->value->u.buffer.len, MDL);
+
+ omapi_value_dereference (&tv, MDL);
+
+ /* If we already have a lease, and it's not the same one,
+ then the query was invalid. */
+ if (*lp && *lp != (omapi_object_t *)lease) {
+ omapi_object_dereference (lp, MDL);
+ lease_dereference (&lease, MDL);
+ return DHCP_R_KEYCONFLICT;
+ } else if (!lease) {
+ if (*lp)
+ omapi_object_dereference (lp, MDL);
+ return ISC_R_NOTFOUND;
+ } else if (!*lp) {
+ /* XXX fix so that hash lookup itself creates
+ XXX the reference. */
+ omapi_object_reference (lp,
+ (omapi_object_t *)lease, MDL);
+ lease_dereference (&lease, MDL);
+ }
+ }
+
+ /* Now look for a client identifier. */
+ status = omapi_get_value_str (ref, id, "dhcp-client-identifier", &tv);
+ if (status == ISC_R_SUCCESS) {
+ lease = (struct lease *)0;
+ lease_id_hash_lookup(&lease, lease_uid_hash,
+ tv->value->u.buffer.value,
+ tv->value->u.buffer.len, MDL);
+ omapi_value_dereference (&tv, MDL);
+
+ if (*lp && *lp != (omapi_object_t *)lease) {
+ omapi_object_dereference (lp, MDL);
+ lease_dereference (&lease, MDL);
+ return DHCP_R_KEYCONFLICT;
+ } else if (!lease) {
+ if (*lp)
+ omapi_object_dereference (lp, MDL);
+ return ISC_R_NOTFOUND;
+ } else if (lease -> n_uid) {
+ if (*lp)
+ omapi_object_dereference (lp, MDL);
+ return DHCP_R_MULTIPLE;
+ } else if (!*lp) {
+ /* XXX fix so that hash lookup itself creates
+ XXX the reference. */
+ omapi_object_reference (lp,
+ (omapi_object_t *)lease, MDL);
+ lease_dereference (&lease, MDL);
+ }
+ }
+
+ /* Now look for a hardware address. */
+ status = omapi_get_value_str (ref, id, "hardware-address", &tv);
+ if (status == ISC_R_SUCCESS) {
+ unsigned char *haddr;
+ unsigned int len;
+
+ len = tv -> value -> u.buffer.len + 1;
+ haddr = dmalloc (len, MDL);
+ if (!haddr) {
+ omapi_value_dereference (&tv, MDL);
+ return ISC_R_NOMEMORY;
+ }
+
+ memcpy (haddr + 1, tv -> value -> u.buffer.value, len - 1);
+ omapi_value_dereference (&tv, MDL);
+
+ status = omapi_get_value_str (ref, id, "hardware-type", &tv);
+ if (status == ISC_R_SUCCESS) {
+ if (tv -> value -> type == omapi_datatype_data) {
+ if ((tv -> value -> u.buffer.len != 4) ||
+ (tv -> value -> u.buffer.value[0] != 0) ||
+ (tv -> value -> u.buffer.value[1] != 0) ||
+ (tv -> value -> u.buffer.value[2] != 0)) {
+ omapi_value_dereference (&tv, MDL);
+ dfree (haddr, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+
+ haddr[0] = tv -> value -> u.buffer.value[3];
+ } else if (tv -> value -> type == omapi_datatype_int) {
+ haddr[0] = (unsigned char)
+ tv -> value -> u.integer;
+ } else {
+ omapi_value_dereference (&tv, MDL);
+ dfree (haddr, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+
+ omapi_value_dereference (&tv, MDL);
+ } else {
+ /* If no hardware-type is specified, default to
+ ethernet. This may or may not be a good idea,
+ but Telus is currently relying on this behavior.
+ - DPN */
+ haddr[0] = HTYPE_ETHER;
+ }
+
+ lease = (struct lease *)0;
+ lease_id_hash_lookup(&lease, lease_hw_addr_hash, haddr, len,
+ MDL);
+ dfree (haddr, MDL);
+
+ if (*lp && *lp != (omapi_object_t *)lease) {
+ omapi_object_dereference (lp, MDL);
+ lease_dereference (&lease, MDL);
+ return DHCP_R_KEYCONFLICT;
+ } else if (!lease) {
+ if (*lp)
+ omapi_object_dereference (lp, MDL);
+ return ISC_R_NOTFOUND;
+ } else if (lease -> n_hw) {
+ if (*lp)
+ omapi_object_dereference (lp, MDL);
+ lease_dereference (&lease, MDL);
+ return DHCP_R_MULTIPLE;
+ } else if (!*lp) {
+ /* XXX fix so that hash lookup itself creates
+ XXX the reference. */
+ omapi_object_reference (lp,
+ (omapi_object_t *)lease, MDL);
+ lease_dereference (&lease, MDL);
+ }
+ }
+
+ /* If we get to here without finding a lease, no valid key was
+ specified. */
+ if (!*lp)
+ return DHCP_R_NOKEYS;
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_lease_create (omapi_object_t **lp,
+ omapi_object_t *id)
+{
+ return ISC_R_NOTIMPLEMENTED;
+}
+
+isc_result_t dhcp_lease_remove (omapi_object_t *lp,
+ omapi_object_t *id)
+{
+ return ISC_R_NOTIMPLEMENTED;
+}
+
+isc_result_t dhcp_host_set_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_typed_data_t *value)
+{
+ struct host_decl *host;
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_host)
+ return DHCP_R_INVALIDARG;
+ host = (struct host_decl *)h;
+
+ /* XXX For now, we can only set these values on new host objects.
+ XXX Soon, we need to be able to update host objects. */
+ if (!omapi_ds_strcmp (name, "name")) {
+ if (host -> name)
+ return ISC_R_EXISTS;
+ if (value && (value -> type == omapi_datatype_data ||
+ value -> type == omapi_datatype_string)) {
+ host -> name = dmalloc (value -> u.buffer.len + 1,
+ MDL);
+ if (!host -> name)
+ return ISC_R_NOMEMORY;
+ memcpy (host -> name,
+ value -> u.buffer.value,
+ value -> u.buffer.len);
+ host -> name [value -> u.buffer.len] = 0;
+ } else
+ return DHCP_R_INVALIDARG;
+ return ISC_R_SUCCESS;
+ }
+
+ if (!omapi_ds_strcmp (name, "group")) {
+ if (value && (value -> type == omapi_datatype_data ||
+ value -> type == omapi_datatype_string)) {
+ struct group_object *group;
+ group = (struct group_object *)0;
+ group_hash_lookup (&group, group_name_hash,
+ (char *)value -> u.buffer.value,
+ value -> u.buffer.len, MDL);
+ if (!group || (group -> flags & GROUP_OBJECT_DELETED))
+ return ISC_R_NOTFOUND;
+ if (host -> group)
+ group_dereference (&host -> group, MDL);
+ group_reference (&host -> group, group -> group, MDL);
+ if (host -> named_group)
+ group_object_dereference (&host -> named_group,
+ MDL);
+ group_object_reference (&host -> named_group,
+ group, MDL);
+ group_object_dereference (&group, MDL);
+ } else
+ return DHCP_R_INVALIDARG;
+ return ISC_R_SUCCESS;
+ }
+
+ if (!omapi_ds_strcmp (name, "hardware-address")) {
+ if (host -> interface.hlen)
+ return ISC_R_EXISTS;
+ if (value && (value -> type == omapi_datatype_data ||
+ value -> type == omapi_datatype_string)) {
+ if (value -> u.buffer.len >
+ (sizeof host -> interface.hbuf) - 1)
+ return DHCP_R_INVALIDARG;
+ memcpy (&host -> interface.hbuf [1],
+ value -> u.buffer.value,
+ value -> u.buffer.len);
+ host -> interface.hlen = value -> u.buffer.len + 1;
+ } else
+ return DHCP_R_INVALIDARG;
+ return ISC_R_SUCCESS;
+ }
+
+ if (!omapi_ds_strcmp (name, "hardware-type")) {
+ int type;
+ if ((value != NULL) &&
+ ((value->type == omapi_datatype_data) &&
+ (value->u.buffer.len == sizeof(type)))) {
+ if (value->u.buffer.len > sizeof(type))
+ return (DHCP_R_INVALIDARG);
+ memcpy(&type, value->u.buffer.value,
+ value->u.buffer.len);
+ type = ntohl(type);
+ } else if ((value != NULL) &&
+ (value->type == omapi_datatype_int))
+ type = value->u.integer;
+ else
+ return (DHCP_R_INVALIDARG);
+ host->interface.hbuf[0] = type;
+ return (ISC_R_SUCCESS);
+ }
+
+ if (!omapi_ds_strcmp (name, "dhcp-client-identifier")) {
+ if (host -> client_identifier.data)
+ return ISC_R_EXISTS;
+ if (value && (value -> type == omapi_datatype_data ||
+ value -> type == omapi_datatype_string)) {
+ if (!buffer_allocate (&host -> client_identifier.buffer,
+ value -> u.buffer.len, MDL))
+ return ISC_R_NOMEMORY;
+ host -> client_identifier.data =
+ &host -> client_identifier.buffer -> data [0];
+ memcpy (host -> client_identifier.buffer -> data,
+ value -> u.buffer.value,
+ value -> u.buffer.len);
+ host -> client_identifier.len = value -> u.buffer.len;
+ } else
+ return DHCP_R_INVALIDARG;
+ return ISC_R_SUCCESS;
+ }
+
+ if (!omapi_ds_strcmp (name, "ip-address")) {
+ if (host -> fixed_addr)
+ option_cache_dereference (&host -> fixed_addr, MDL);
+ if (!value)
+ return ISC_R_SUCCESS;
+ if (value && (value -> type == omapi_datatype_data ||
+ value -> type == omapi_datatype_string)) {
+ struct data_string ds;
+ memset (&ds, 0, sizeof ds);
+ ds.len = value -> u.buffer.len;
+ if (!buffer_allocate (&ds.buffer, ds.len, MDL))
+ return ISC_R_NOMEMORY;
+ ds.data = (&ds.buffer -> data [0]);
+ memcpy (ds.buffer -> data,
+ value -> u.buffer.value, ds.len);
+ if (!option_cache (&host -> fixed_addr,
+ &ds, (struct expression *)0,
+ (struct option *)0, MDL)) {
+ data_string_forget (&ds, MDL);
+ return ISC_R_NOMEMORY;
+ }
+ data_string_forget (&ds, MDL);
+ } else
+ return DHCP_R_INVALIDARG;
+ return ISC_R_SUCCESS;
+ }
+
+ if (!omapi_ds_strcmp (name, "statements")) {
+ if (!host -> group) {
+ if (!clone_group (&host -> group, root_group, MDL))
+ return ISC_R_NOMEMORY;
+ } else {
+ if (host -> group -> statements &&
+ (!host -> named_group ||
+ host -> group != host -> named_group -> group) &&
+ host -> group != root_group)
+ return ISC_R_EXISTS;
+ if (!clone_group (&host -> group, host -> group, MDL))
+ return ISC_R_NOMEMORY;
+ }
+ if (!host -> group)
+ return ISC_R_NOMEMORY;
+ if (value && (value -> type == omapi_datatype_data ||
+ value -> type == omapi_datatype_string)) {
+ struct parse *parse;
+ int lose = 0;
+ parse = (struct parse *)0;
+ 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
+ (&host -> group -> statements, parse, &lose,
+ context_any))) {
+ end_parse (&parse);
+ return DHCP_R_BADPARSE;
+ }
+ end_parse (&parse);
+ } else
+ return DHCP_R_INVALIDARG;
+ return ISC_R_SUCCESS;
+ }
+
+ /* The "known" flag isn't supported in the database yet, but it's
+ legitimate. */
+ if (!omapi_ds_strcmp (name, "known")) {
+ 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 DHCP_R_UNKNOWNATTRIBUTE;
+}
+
+
+isc_result_t dhcp_host_get_value (omapi_object_t *h, omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_value_t **value)
+{
+ struct host_decl *host;
+ isc_result_t status;
+ struct data_string ip_addrs;
+
+ if (h -> type != dhcp_type_host)
+ return DHCP_R_INVALIDARG;
+ host = (struct host_decl *)h;
+
+ if (!omapi_ds_strcmp (name, "ip-addresses")) {
+ memset (&ip_addrs, 0, sizeof ip_addrs);
+ if (host -> fixed_addr &&
+ evaluate_option_cache (&ip_addrs, (struct packet *)0,
+ (struct lease *)0,
+ (struct client_state *)0,
+ (struct option_state *)0,
+ (struct option_state *)0,
+ &global_scope,
+ host -> fixed_addr, MDL)) {
+ status = omapi_make_const_value (value, name,
+ ip_addrs.data,
+ ip_addrs.len, MDL);
+ data_string_forget (&ip_addrs, MDL);
+ return status;
+ }
+ return ISC_R_NOTFOUND;
+ }
+
+ if (!omapi_ds_strcmp (name, "dhcp-client-identifier")) {
+ if (!host -> client_identifier.len)
+ return ISC_R_NOTFOUND;
+ return omapi_make_const_value (value, name,
+ host -> client_identifier.data,
+ host -> client_identifier.len,
+ MDL);
+ }
+
+ if (!omapi_ds_strcmp (name, "name"))
+ return omapi_make_string_value (value, name, host -> name,
+ MDL);
+
+ if (!omapi_ds_strcmp (name, "hardware-address")) {
+ if (!host -> interface.hlen)
+ return ISC_R_NOTFOUND;
+ return (omapi_make_const_value
+ (value, name, &host -> interface.hbuf [1],
+ (unsigned long)(host -> interface.hlen - 1), MDL));
+ }
+
+ if (!omapi_ds_strcmp (name, "hardware-type")) {
+ if (!host -> interface.hlen)
+ return ISC_R_NOTFOUND;
+ return omapi_make_int_value (value, name,
+ host -> interface.hbuf [0], 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 DHCP_R_UNKNOWNATTRIBUTE;
+}
+
+isc_result_t dhcp_host_destroy (omapi_object_t *h, const char *file, int line)
+{
+
+ if (h -> type != dhcp_type_host)
+ return DHCP_R_INVALIDARG;
+
+#if defined (DEBUG_MEMORY_LEAKAGE) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ struct host_decl *host = (struct host_decl *)h;
+ if (host -> n_ipaddr)
+ host_dereference (&host -> n_ipaddr, file, line);
+ if (host -> n_dynamic)
+ host_dereference (&host -> n_dynamic, file, line);
+ if (host -> name) {
+ dfree (host -> name, file, line);
+ host -> name = (char *)0;
+ }
+ data_string_forget (&host -> client_identifier, file, line);
+ if (host -> fixed_addr)
+ option_cache_dereference (&host -> fixed_addr, file, line);
+ if (host -> group)
+ group_dereference (&host -> group, file, line);
+ if (host -> named_group)
+ omapi_object_dereference ((omapi_object_t **)
+ &host -> named_group, file, line);
+ data_string_forget (&host -> auth_key_id, file, line);
+#endif
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_host_signal_handler (omapi_object_t *h,
+ const char *name, va_list ap)
+{
+ struct host_decl *host;
+ isc_result_t status;
+ int updatep = 0;
+
+ if (h -> type != dhcp_type_host)
+ return DHCP_R_INVALIDARG;
+ host = (struct host_decl *)h;
+
+ if (!strcmp (name, "updated")) {
+ /* There must be a client identifier of some sort. */
+ if (host -> interface.hlen == 0 &&
+ !host -> client_identifier.len)
+ return DHCP_R_INVALIDARG;
+
+ if (!host -> name) {
+ char hnbuf [64];
+ sprintf (hnbuf, "nh%08lx%08lx",
+ (unsigned long)cur_time, (unsigned long)host);
+ host -> name = dmalloc (strlen (hnbuf) + 1, MDL);
+ if (!host -> name)
+ return ISC_R_NOMEMORY;
+ strcpy (host -> name, hnbuf);
+ }
+
+#ifdef DEBUG_OMAPI
+ log_debug ("OMAPI added host %s", host -> name);
+#endif
+ status = enter_host (host, 1, 1);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ updatep = 1;
+ }
+
+ /* 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;
+ }
+ if (updatep)
+ return ISC_R_SUCCESS;
+ return ISC_R_NOTFOUND;
+}
+
+isc_result_t dhcp_host_stuff_values (omapi_object_t *c,
+ omapi_object_t *id,
+ omapi_object_t *h)
+{
+ struct host_decl *host;
+ isc_result_t status;
+ struct data_string ip_addrs;
+
+ if (h -> type != dhcp_type_host)
+ return DHCP_R_INVALIDARG;
+ host = (struct host_decl *)h;
+
+ /* Write out all the values. */
+
+ memset (&ip_addrs, 0, sizeof ip_addrs);
+ if (host -> fixed_addr &&
+ evaluate_option_cache (&ip_addrs, (struct packet *)0,
+ (struct lease *)0,
+ (struct client_state *)0,
+ (struct option_state *)0,
+ (struct option_state *)0,
+ &global_scope,
+ host -> fixed_addr, MDL)) {
+ status = omapi_connection_put_name (c, "ip-address");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32 (c, ip_addrs.len);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_copyin (c,
+ ip_addrs.data, ip_addrs.len);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ }
+
+ if (host -> client_identifier.len) {
+ status = omapi_connection_put_name (c,
+ "dhcp-client-identifier");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = (omapi_connection_put_uint32
+ (c, host -> client_identifier.len));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = (omapi_connection_copyin
+ (c,
+ host -> client_identifier.data,
+ host -> client_identifier.len));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ }
+
+ if (host -> name) {
+ status = omapi_connection_put_name (c, "name");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_string (c, host -> name);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ }
+
+ if (host -> interface.hlen) {
+ status = omapi_connection_put_name (c, "hardware-address");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = (omapi_connection_put_uint32
+ (c, (unsigned long)(host -> interface.hlen - 1)));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = (omapi_connection_copyin
+ (c, &host -> interface.hbuf [1],
+ (unsigned long)(host -> interface.hlen - 1)));
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ status = omapi_connection_put_name (c, "hardware-type");
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_put_uint32 (c, sizeof (int));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = (omapi_connection_put_uint32
+ (c, host -> interface.hbuf [0]));
+ 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_host_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 host_decl *host;
+
+ 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_host) {
+ omapi_object_dereference (lp, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+ if (((struct host_decl *)(*lp)) -> flags & HOST_DECL_DELETED) {
+ omapi_object_dereference (lp, MDL);
+ }
+ }
+
+ /* Now look for a client identifier. */
+ status = omapi_get_value_str (ref, id, "dhcp-client-identifier", &tv);
+ if (status == ISC_R_SUCCESS) {
+ host = (struct host_decl *)0;
+ host_hash_lookup (&host, host_uid_hash,
+ tv -> value -> u.buffer.value,
+ tv -> value -> u.buffer.len, MDL);
+ omapi_value_dereference (&tv, MDL);
+
+ if (*lp && *lp != (omapi_object_t *)host) {
+ omapi_object_dereference (lp, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return DHCP_R_KEYCONFLICT;
+ } else if (!host || (host -> flags & HOST_DECL_DELETED)) {
+ if (*lp)
+ omapi_object_dereference (lp, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return ISC_R_NOTFOUND;
+ } else if (!*lp) {
+ /* XXX fix so that hash lookup itself creates
+ XXX the reference. */
+ omapi_object_reference (lp,
+ (omapi_object_t *)host, MDL);
+ host_dereference (&host, MDL);
+ }
+ }
+
+ /* Now look for a hardware address. */
+ status = omapi_get_value_str (ref, id, "hardware-address", &tv);
+ if (status == ISC_R_SUCCESS) {
+ unsigned char *haddr;
+ unsigned int len;
+
+ len = tv -> value -> u.buffer.len + 1;
+ haddr = dmalloc (len, MDL);
+ if (!haddr) {
+ omapi_value_dereference (&tv, MDL);
+ return ISC_R_NOMEMORY;
+ }
+
+ memcpy (haddr + 1, tv -> value -> u.buffer.value, len - 1);
+ omapi_value_dereference (&tv, MDL);
+
+ status = omapi_get_value_str (ref, id, "hardware-type", &tv);
+ if (status == ISC_R_SUCCESS) {
+ if (tv -> value -> type == omapi_datatype_data) {
+ if ((tv -> value -> u.buffer.len != 4) ||
+ (tv -> value -> u.buffer.value[0] != 0) ||
+ (tv -> value -> u.buffer.value[1] != 0) ||
+ (tv -> value -> u.buffer.value[2] != 0)) {
+ omapi_value_dereference (&tv, MDL);
+ dfree (haddr, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+
+ haddr[0] = tv -> value -> u.buffer.value[3];
+ } else if (tv -> value -> type == omapi_datatype_int) {
+ haddr[0] = (unsigned char)
+ tv -> value -> u.integer;
+ } else {
+ omapi_value_dereference (&tv, MDL);
+ dfree (haddr, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+
+ omapi_value_dereference (&tv, MDL);
+ } else {
+ /* If no hardware-type is specified, default to
+ ethernet. This may or may not be a good idea,
+ but Telus is currently relying on this behavior.
+ - DPN */
+ haddr[0] = HTYPE_ETHER;
+ }
+
+ host = (struct host_decl *)0;
+ host_hash_lookup (&host, host_hw_addr_hash, haddr, len, MDL);
+ dfree (haddr, MDL);
+
+ if (*lp && *lp != (omapi_object_t *)host) {
+ omapi_object_dereference (lp, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return DHCP_R_KEYCONFLICT;
+ } else if (!host || (host -> flags & HOST_DECL_DELETED)) {
+ if (*lp)
+ omapi_object_dereference (lp, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return ISC_R_NOTFOUND;
+ } else if (!*lp) {
+ /* XXX fix so that hash lookup itself creates
+ XXX the reference. */
+ omapi_object_reference (lp,
+ (omapi_object_t *)host, MDL);
+ host_dereference (&host, MDL);
+ }
+ }
+
+ /* Now look for an ip address. */
+ status = omapi_get_value_str (ref, id, "ip-address", &tv);
+ if (status == ISC_R_SUCCESS) {
+ struct lease *l;
+
+ /* first find the lease for this ip address */
+ l = (struct lease *)0;
+ lease_ip_hash_lookup(&l, lease_ip_addr_hash,
+ tv->value->u.buffer.value,
+ tv->value->u.buffer.len, MDL);
+ omapi_value_dereference (&tv, MDL);
+
+ if (!l && !*lp)
+ return ISC_R_NOTFOUND;
+
+ if (l) {
+ /* now use that to get a host */
+ host = (struct host_decl *)0;
+ host_hash_lookup (&host, host_hw_addr_hash,
+ l -> hardware_addr.hbuf,
+ l -> hardware_addr.hlen, MDL);
+
+ if (host && *lp && *lp != (omapi_object_t *)host) {
+ omapi_object_dereference (lp, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return DHCP_R_KEYCONFLICT;
+ } else if (!host || (host -> flags &
+ HOST_DECL_DELETED)) {
+ if (host)
+ host_dereference (&host, MDL);
+ if (!*lp)
+ return ISC_R_NOTFOUND;
+ } else if (!*lp) {
+ /* XXX fix so that hash lookup itself creates
+ XXX the reference. */
+ omapi_object_reference (lp, (omapi_object_t *)host,
+ MDL);
+ host_dereference (&host, MDL);
+ }
+ lease_dereference (&l, MDL);
+ }
+ }
+
+ /* Now look for a name. */
+ status = omapi_get_value_str (ref, id, "name", &tv);
+ if (status == ISC_R_SUCCESS) {
+ host = (struct host_decl *)0;
+ host_hash_lookup (&host, host_name_hash,
+ tv -> value -> u.buffer.value,
+ tv -> value -> u.buffer.len, MDL);
+ omapi_value_dereference (&tv, MDL);
+
+ if (*lp && *lp != (omapi_object_t *)host) {
+ omapi_object_dereference (lp, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return DHCP_R_KEYCONFLICT;
+ } else if (!host || (host -> flags & HOST_DECL_DELETED)) {
+ if (host)
+ host_dereference (&host, MDL);
+ return ISC_R_NOTFOUND;
+ } else if (!*lp) {
+ /* XXX fix so that hash lookup itself creates
+ XXX the reference. */
+ omapi_object_reference (lp,
+ (omapi_object_t *)host, MDL);
+ host_dereference (&host, MDL);
+ }
+ }
+
+ /* If we get to here without finding a host, no valid key was
+ specified. */
+ if (!*lp)
+ return DHCP_R_NOKEYS;
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_host_create (omapi_object_t **lp,
+ omapi_object_t *id)
+{
+ struct host_decl *hp;
+ isc_result_t status;
+ hp = (struct host_decl *)0;
+ status = host_allocate (&hp, MDL);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ group_reference (&hp -> group, root_group, MDL);
+ hp -> flags = HOST_DECL_DYNAMIC;
+ status = omapi_object_reference (lp, (omapi_object_t *)hp, MDL);
+ host_dereference (&hp, MDL);
+ return status;
+}
+
+isc_result_t dhcp_host_remove (omapi_object_t *lp,
+ omapi_object_t *id)
+{
+ struct host_decl *hp;
+ if (lp -> type != dhcp_type_host)
+ return DHCP_R_INVALIDARG;
+ hp = (struct host_decl *)lp;
+
+#ifdef DEBUG_OMAPI
+ log_debug ("OMAPI delete host %s", hp -> name);
+#endif
+ delete_host (hp, 1);
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_pool_set_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_typed_data_t *value)
+{
+ /* h should point to (struct pool *) */
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_pool)
+ 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 DHCP_R_UNKNOWNATTRIBUTE;
+}
+
+
+isc_result_t dhcp_pool_get_value (omapi_object_t *h, omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_value_t **value)
+{
+ /* h should point to (struct pool *) */
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_pool)
+ 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 DHCP_R_UNKNOWNATTRIBUTE;
+}
+
+isc_result_t dhcp_pool_destroy (omapi_object_t *h, const char *file, int line)
+{
+#if defined (DEBUG_MEMORY_LEAKAGE) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ struct permit *pc, *pn;
+#endif
+
+ if (h -> type != dhcp_type_pool)
+ return DHCP_R_INVALIDARG;
+
+#if defined (DEBUG_MEMORY_LEAKAGE) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ struct pool *pool = (struct pool *)h;
+ if (pool -> next)
+ pool_dereference (&pool -> next, file, line);
+ if (pool -> group)
+ group_dereference (&pool -> group, file, line);
+ if (pool -> shared_network)
+ shared_network_dereference (&pool -> shared_network, file, line);
+ if (pool -> active)
+ lease_dereference (&pool -> active, file, line);
+ if (pool -> expired)
+ lease_dereference (&pool -> expired, file, line);
+ if (pool -> free)
+ lease_dereference (&pool -> free, file, line);
+ if (pool -> backup)
+ lease_dereference (&pool -> backup, file, line);
+ if (pool -> abandoned)
+ lease_dereference (&pool -> abandoned, file, line);
+#if defined (FAILOVER_PROTOCOL)
+ if (pool -> failover_peer)
+ dhcp_failover_state_dereference (&pool -> failover_peer,
+ file, line);
+#endif
+ for (pc = pool -> permit_list; pc; pc = pn) {
+ pn = pc -> next;
+ free_permit (pc, file, line);
+ }
+ pool -> permit_list = (struct permit *)0;
+
+ for (pc = pool -> prohibit_list; pc; pc = pn) {
+ pn = pc -> next;
+ free_permit (pc, file, line);
+ }
+ pool -> prohibit_list = (struct permit *)0;
+#endif
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_pool_signal_handler (omapi_object_t *h,
+ const char *name, va_list ap)
+{
+ /* h should point to (struct pool *) */
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_pool)
+ return DHCP_R_INVALIDARG;
+
+ /* Can't write pools yet. */
+
+ /* 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_pool_stuff_values (omapi_object_t *c,
+ omapi_object_t *id,
+ omapi_object_t *h)
+{
+ /* h should point to (struct pool *) */
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_pool)
+ return DHCP_R_INVALIDARG;
+
+ /* Can't stuff pool 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_pool_lookup (omapi_object_t **lp,
+ omapi_object_t *id, omapi_object_t *ref)
+{
+ /* Can't look up pools yet. */
+
+ /* If we get to here without finding a pool, no valid key was
+ specified. */
+ if (!*lp)
+ return DHCP_R_NOKEYS;
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_pool_create (omapi_object_t **lp,
+ omapi_object_t *id)
+{
+ return ISC_R_NOTIMPLEMENTED;
+}
+
+isc_result_t dhcp_pool_remove (omapi_object_t *lp,
+ omapi_object_t *id)
+{
+ return ISC_R_NOTIMPLEMENTED;
+}
+
+static isc_result_t
+class_set_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_typed_data_t *value)
+{
+ struct class *class;
+ struct class *superclass = 0;
+ isc_result_t status;
+ int issubclass = (h -> type == dhcp_type_subclass);
+
+ class = (struct class *)h;
+
+ if (!omapi_ds_strcmp(name, "name")) {
+ if (class->name)
+ return ISC_R_EXISTS;
+
+ if (issubclass) {
+ char tname[value->u.buffer.len + 1];
+ memcpy(tname, value->u.buffer.value, value->u.buffer.len);
+ tname[sizeof(tname)-1] = '\0';
+ status = find_class(&superclass, tname, MDL);
+
+ if (status == ISC_R_NOTFOUND)
+ return status;
+
+ if (class->superclass != NULL)
+ class_dereference(&class->superclass, MDL);
+
+ class_reference(&class->superclass, superclass, MDL);
+ } else if (value -> type == omapi_datatype_data ||
+ value -> type == omapi_datatype_string) {
+ class->name = dmalloc(value->u.buffer.len + 1, MDL);
+ if (!class->name)
+ return ISC_R_NOMEMORY;
+
+ /* class->name is null-terminated from dmalloc() */
+ memcpy(class->name, value->u.buffer.value,
+ value->u.buffer.len);
+ } else
+ return DHCP_R_INVALIDARG;
+
+ return ISC_R_SUCCESS;
+ }
+
+
+ if (issubclass && !omapi_ds_strcmp(name, "hashstring")) {
+ if (class->hash_string.data)
+ return ISC_R_EXISTS;
+
+ if (value->type == omapi_datatype_data ||
+ value->type == omapi_datatype_string) {
+ if (!buffer_allocate(&class->hash_string.buffer,
+ value->u.buffer.len, MDL))
+ return ISC_R_NOMEMORY;
+ class->hash_string.data =
+ class->hash_string.buffer->data;
+ memcpy(class->hash_string.buffer->data,
+ value->u.buffer.value, value->u.buffer.len);
+ class->hash_string.len = value->u.buffer.len;
+ } else
+ return DHCP_R_INVALIDARG;
+
+ return ISC_R_SUCCESS;
+ }
+
+ if (!omapi_ds_strcmp(name, "group")) {
+ if (value->type == omapi_datatype_data ||
+ value->type == omapi_datatype_string) {
+ struct group_object *group = NULL;
+
+ group_hash_lookup(&group, group_name_hash,
+ (char *)value->u.buffer.value,
+ value->u.buffer.len, MDL);
+ if (!group || (group->flags & GROUP_OBJECT_DELETED))
+ return ISC_R_NOTFOUND;
+ if (class->group)
+ group_dereference(&class->group, MDL);
+ group_reference(&class->group, group->group, MDL);
+ group_object_dereference(&group, MDL);
+ } else
+ return DHCP_R_INVALIDARG;
+
+ return ISC_R_SUCCESS;
+ }
+
+
+ /* note we do not support full expressions via omapi because the
+ expressions parser needs to be re-done to support parsing from
+ strings and not just files. */
+
+ if (!omapi_ds_strcmp(name, "match")) {
+ if (value->type == omapi_datatype_data ||
+ value->type == omapi_datatype_string) {
+ unsigned minlen = (value->u.buffer.len > 8 ?
+ 8 : value->u.buffer.len);
+
+ if (!strncmp("hardware",
+ (char *)value->u.buffer.value, minlen))
+ {
+ if (!expression_allocate(&class->submatch, MDL))
+ return ISC_R_NOMEMORY;
+
+ class->submatch->op = expr_hardware;
+ } else
+ return DHCP_R_INVALIDARG;
+ } else
+ return DHCP_R_INVALIDARG;
+
+ return ISC_R_SUCCESS;
+ }
+
+
+ if (!omapi_ds_strcmp(name, "option")) {
+ if (value->type == omapi_datatype_data ||
+ value->type == omapi_datatype_string) {
+ /* XXXJAB support 'options' here. */
+ /* XXXJAB specifically 'bootfile-name' */
+ return DHCP_R_INVALIDARG; /* XXX tmp */
+ } else
+ return DHCP_R_INVALIDARG;
+
+ /*
+ * Currently no way to get here, if we update the above
+ * code so that we do get here this return needs to be
+ * uncommented.
+ * 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 DHCP_R_UNKNOWNATTRIBUTE;
+}
+
+
+
+isc_result_t dhcp_class_set_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_typed_data_t *value)
+{
+ if (h -> type != dhcp_type_class)
+ return DHCP_R_INVALIDARG;
+
+ return class_set_value(h, id, name, value);
+}
+
+isc_result_t dhcp_class_get_value (omapi_object_t *h, omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_value_t **value)
+{
+ struct class *class;
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_class)
+ return DHCP_R_INVALIDARG;
+ class = (struct class *)h;
+
+ if (!omapi_ds_strcmp (name, "name"))
+ return omapi_make_string_value (value, name, class -> name,
+ MDL);
+
+ /* 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 DHCP_R_UNKNOWNATTRIBUTE;
+}
+
+isc_result_t dhcp_class_destroy (omapi_object_t *h, const char *file, int line)
+{
+
+ if (h -> type != dhcp_type_class && h -> type != dhcp_type_subclass)
+ return DHCP_R_INVALIDARG;
+
+#if defined (DEBUG_MEMORY_LEAKAGE) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ struct class *class = (struct class *)h;
+ if (class -> nic)
+ class_dereference (&class -> nic, file, line);
+ if (class -> superclass)
+ class_dereference (&class -> superclass, file, line);
+ if (class -> name) {
+ dfree (class -> name, file, line);
+ class -> name = (char *)0;
+ }
+ if (class -> billed_leases) {
+ int i;
+ for (i = 0; i < class -> lease_limit; i++) {
+ if (class -> billed_leases [i]) {
+ lease_dereference (&class -> billed_leases [i],
+ file, line);
+ }
+ }
+ dfree (class -> billed_leases, file, line);
+ class -> billed_leases = (struct lease **)0;
+ }
+ if (class -> hash) {
+ class_free_hash_table (&class -> hash, file, line);
+ class -> hash = (class_hash_t *)0;
+ }
+ data_string_forget (&class -> hash_string, file, line);
+
+ if (class -> expr)
+ expression_dereference (&class -> expr, file, line);
+ if (class -> submatch)
+ expression_dereference (&class -> submatch, file, line);
+ if (class -> group)
+ group_dereference (&class -> group, file, line);
+ if (class -> statements)
+ executable_statement_dereference (&class -> statements,
+ file, line);
+ if (class -> superclass)
+ class_dereference (&class -> superclass, file, line);
+#endif
+
+ return ISC_R_SUCCESS;
+}
+
+static isc_result_t
+class_signal_handler(omapi_object_t *h,
+ const char *name, va_list ap)
+{
+ struct class *class = (struct class *)h;
+ isc_result_t status;
+ int updatep = 0;
+ int issubclass;
+
+ issubclass = (h -> type == dhcp_type_subclass);
+
+ if (!strcmp (name, "updated")) {
+
+ if (!issubclass) {
+ if (class -> name == 0 || strlen(class -> name) == 0) {
+ return DHCP_R_INVALIDARG;
+ }
+ } else {
+ if (class -> superclass == 0) {
+ return DHCP_R_INVALIDARG; /* didn't give name */
+ }
+
+ if (class -> hash_string.data == NULL) {
+ return DHCP_R_INVALIDARG;
+ }
+ }
+
+
+ if (issubclass) {
+ if (!class -> superclass -> hash)
+ class_new_hash(&class->superclass->hash,
+ SCLASS_HASH_SIZE, MDL);
+
+ add_hash (class -> superclass -> hash,
+ class -> hash_string.data,
+ class -> hash_string.len,
+ (void *)class, MDL);
+ }
+
+
+#ifdef DEBUG_OMAPI
+ if (issubclass) {
+ log_debug ("OMAPI added subclass %s",
+ class -> superclass -> name);
+ } else {
+ log_debug ("OMAPI added class %s", class -> name);
+ }
+#endif
+
+ status = enter_class (class, 1, 1);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ updatep = 1;
+ }
+
+ /* 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;
+ }
+
+ if (updatep)
+ return ISC_R_SUCCESS;
+
+ return ISC_R_NOTFOUND;
+}
+
+
+isc_result_t dhcp_class_signal_handler (omapi_object_t *h,
+ const char *name, va_list ap)
+{
+ if (h -> type != dhcp_type_class)
+ return DHCP_R_INVALIDARG;
+
+ return class_signal_handler(h, name, ap);
+}
+
+isc_result_t dhcp_class_stuff_values (omapi_object_t *c,
+ omapi_object_t *id,
+ omapi_object_t *h)
+{
+ /* h should point to (struct class *) */
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_class)
+ return DHCP_R_INVALIDARG;
+
+ /* Can't stuff class 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;
+}
+
+static isc_result_t class_lookup (omapi_object_t **lp,
+ omapi_object_t *id, omapi_object_t *ref,
+ omapi_object_type_t *typewanted)
+{
+ omapi_value_t *nv = (omapi_value_t *)0;
+ omapi_value_t *hv = (omapi_value_t *)0;
+ isc_result_t status;
+ struct class *class = 0;
+ struct class *subclass = 0;
+
+ *lp = NULL;
+
+ /* see if we have a name */
+ status = omapi_get_value_str (ref, id, "name", &nv);
+ if (status == ISC_R_SUCCESS) {
+ char *name = dmalloc(nv -> value -> u.buffer.len + 1, MDL);
+ memcpy (name,
+ nv -> value -> u.buffer.value,
+ nv -> value -> u.buffer.len);
+
+ omapi_value_dereference (&nv, MDL);
+
+ find_class(&class, name, MDL);
+
+ dfree(name, MDL);
+
+ if (class == NULL) {
+ return ISC_R_NOTFOUND;
+ }
+
+ if (typewanted == dhcp_type_subclass) {
+ status = omapi_get_value_str (ref, id,
+ "hashstring", &hv);
+ if (status != ISC_R_SUCCESS) {
+ class_dereference(&class, MDL);
+ return DHCP_R_NOKEYS;
+ }
+
+ if (hv -> value -> type != omapi_datatype_data &&
+ hv -> value -> type != omapi_datatype_string) {
+ class_dereference(&class, MDL);
+ omapi_value_dereference (&hv, MDL);
+ return DHCP_R_NOKEYS;
+ }
+
+ class_hash_lookup (&subclass, class -> hash,
+ (const char *)
+ hv -> value -> u.buffer.value,
+ hv -> value -> u.buffer.len, MDL);
+
+ omapi_value_dereference (&hv, MDL);
+
+ class_dereference(&class, MDL);
+
+ if (subclass == NULL) {
+ return ISC_R_NOTFOUND;
+ }
+
+ class_reference(&class, subclass, MDL);
+ class_dereference(&subclass, MDL);
+ }
+
+
+ /* Don't return the object if the type is wrong. */
+ if (class -> type != typewanted) {
+ class_dereference (&class, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+
+ if (class -> flags & CLASS_DECL_DELETED) {
+ class_dereference (&class, MDL);
+ }
+
+ omapi_object_reference(lp, (omapi_object_t *)class, MDL);
+
+ return ISC_R_SUCCESS;
+ }
+
+ return DHCP_R_NOKEYS;
+}
+
+
+isc_result_t dhcp_class_lookup (omapi_object_t **lp,
+ omapi_object_t *id, omapi_object_t *ref)
+{
+ return class_lookup(lp, id, ref, dhcp_type_class);
+}
+
+isc_result_t dhcp_class_create (omapi_object_t **lp,
+ omapi_object_t *id)
+{
+ struct class *cp = 0;
+ isc_result_t status;
+
+ status = class_allocate(&cp, MDL);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ group_reference (&cp -> group, root_group, MDL);
+ cp -> flags = CLASS_DECL_DYNAMIC;
+ status = omapi_object_reference (lp, (omapi_object_t *)cp, MDL);
+ class_dereference (&cp, MDL);
+ return status;
+}
+
+isc_result_t dhcp_class_remove (omapi_object_t *lp,
+ omapi_object_t *id)
+{
+ struct class *cp;
+ if (lp -> type != dhcp_type_class)
+ return DHCP_R_INVALIDARG;
+ cp = (struct class *)lp;
+
+#ifdef DEBUG_OMAPI
+ log_debug ("OMAPI delete class %s", cp -> name);
+#endif
+
+ delete_class (cp, 1);
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_subclass_set_value (omapi_object_t *h,
+ omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_typed_data_t *value)
+{
+ if (h -> type != dhcp_type_subclass)
+ return DHCP_R_INVALIDARG;
+
+ return class_set_value(h, id, name, value);
+}
+
+
+isc_result_t dhcp_subclass_get_value (omapi_object_t *h, omapi_object_t *id,
+ omapi_data_string_t *name,
+ omapi_value_t **value)
+{
+ struct class *subclass;
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_class)
+ return DHCP_R_INVALIDARG;
+ subclass = (struct class *)h;
+ if (subclass -> name != 0)
+ return DHCP_R_INVALIDARG;
+
+ /* XXXJAB 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 DHCP_R_UNKNOWNATTRIBUTE;
+}
+
+isc_result_t dhcp_subclass_signal_handler (omapi_object_t *h,
+ const char *name, va_list ap)
+{
+ if (h -> type != dhcp_type_subclass)
+ return DHCP_R_INVALIDARG;
+
+ return class_signal_handler(h, name, ap);
+}
+
+
+isc_result_t dhcp_subclass_stuff_values (omapi_object_t *c,
+ omapi_object_t *id,
+ omapi_object_t *h)
+{
+ struct class *subclass;
+ isc_result_t status;
+
+ if (h -> type != dhcp_type_class)
+ return DHCP_R_INVALIDARG;
+ subclass = (struct class *)h;
+ if (subclass -> name != 0)
+ return DHCP_R_INVALIDARG;
+
+
+ /* Can't stuff subclass 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_subclass_lookup (omapi_object_t **lp,
+ omapi_object_t *id, omapi_object_t *ref)
+{
+ return class_lookup(lp, id, ref, dhcp_type_subclass);
+}
+
+
+
+
+isc_result_t dhcp_subclass_create (omapi_object_t **lp,
+ omapi_object_t *id)
+{
+ struct class *cp = 0;
+ isc_result_t status;
+
+/*
+ * XXX
+ * NOTE: subclasses and classes have the same internal type, which makes it
+ * difficult to tell them apart. Specifically, in this function we need to
+ * create a class object (because there is no such thing as a subclass
+ * object), but one field of the class object is the type (which has the
+ * value dhcp_type_class), and it is from here that all the other omapi
+ * functions are accessed. So, even though there's a whole suite of
+ * subclass functions registered, they won't get used. Now we could change
+ * the type pointer after creating the class object, but I'm not certain
+ * that won't break something else.
+ */
+
+ status = subclass_allocate(&cp, MDL);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ group_reference (&cp -> group, root_group, MDL);
+
+ cp -> flags = CLASS_DECL_DYNAMIC;
+
+ status = omapi_object_reference (lp, (omapi_object_t *)cp, MDL);
+ subclass_dereference (&cp, MDL);
+ return status;
+}
+
+isc_result_t dhcp_subclass_remove (omapi_object_t *lp,
+ omapi_object_t *id)
+{
+#if 1
+
+ log_fatal("calling dhcp_subclass_set_value");
+ /* this should never be called see dhcp_subclass_create for why */
+
+#else
+
+ struct class *cp;
+ if (lp -> type != dhcp_type_subclass)
+ return DHCP_R_INVALIDARG;
+ cp = (struct class *)lp;
+
+#ifdef DEBUG_OMAPI
+ log_debug ("OMAPI delete subclass %s", cp -> name);
+#endif
+
+ delete_class (cp, 1);
+
+#endif
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t binding_scope_set_value (struct binding_scope *scope, int createp,
+ omapi_data_string_t *name,
+ omapi_typed_data_t *value)
+{
+ struct binding *bp;
+ char *nname;
+ struct binding_value *nv;
+ nname = dmalloc (name -> len + 1, MDL);
+ if (!nname)
+ return ISC_R_NOMEMORY;
+ memcpy (nname, name -> value, name -> len);
+ nname [name -> len] = 0;
+ bp = find_binding (scope, nname);
+ if (!bp && !createp) {
+ dfree (nname, MDL);
+ return DHCP_R_UNKNOWNATTRIBUTE;
+ }
+ if (!value) {
+ dfree (nname, MDL);
+ if (!bp)
+ return DHCP_R_UNKNOWNATTRIBUTE;
+ binding_value_dereference (&bp -> value, MDL);
+ return ISC_R_SUCCESS;
+ }
+
+ nv = (struct binding_value *)0;
+ if (!binding_value_allocate (&nv, MDL)) {
+ dfree (nname, MDL);
+ return ISC_R_NOMEMORY;
+ }
+ switch (value -> type) {
+ case omapi_datatype_int:
+ nv -> type = binding_numeric;
+ nv -> value.intval = value -> u.integer;
+ break;
+
+ case omapi_datatype_string:
+ case omapi_datatype_data:
+ if (!buffer_allocate (&nv -> value.data.buffer,
+ value -> u.buffer.len, MDL)) {
+ binding_value_dereference (&nv, MDL);
+ dfree (nname, MDL);
+ return ISC_R_NOMEMORY;
+ }
+ memcpy (&nv -> value.data.buffer -> data [1],
+ value -> u.buffer.value, value -> u.buffer.len);
+ nv -> value.data.len = value -> u.buffer.len;
+ break;
+
+ case omapi_datatype_object:
+ binding_value_dereference (&nv, MDL);
+ dfree (nname, MDL);
+ return DHCP_R_INVALIDARG;
+ }
+
+ if (!bp) {
+ bp = dmalloc (sizeof *bp, MDL);
+ if (!bp) {
+ binding_value_dereference (&nv, MDL);
+ dfree (nname, MDL);
+ return ISC_R_NOMEMORY;
+ }
+ memset (bp, 0, sizeof *bp);
+ bp -> name = nname;
+ bp -> next = scope -> bindings;
+ scope -> bindings = bp;
+ } else {
+ if (bp -> value)
+ binding_value_dereference (&bp -> value, MDL);
+ dfree (nname, MDL);
+ }
+ binding_value_reference (&bp -> value, nv, MDL);
+ binding_value_dereference (&nv, MDL);
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t binding_scope_get_value (omapi_value_t **value,
+ struct binding_scope *scope,
+ omapi_data_string_t *name)
+{
+ struct binding *bp;
+ omapi_typed_data_t *td;
+ isc_result_t status;
+ char *nname;
+ nname = dmalloc (name -> len + 1, MDL);
+ if (!nname)
+ return ISC_R_NOMEMORY;
+ memcpy (nname, name -> value, name -> len);
+ nname [name -> len] = 0;
+ bp = find_binding (scope, nname);
+ dfree (nname, MDL);
+ if (!bp)
+ return DHCP_R_UNKNOWNATTRIBUTE;
+ if (!bp -> value)
+ return DHCP_R_UNKNOWNATTRIBUTE;
+
+ switch (bp -> value -> type) {
+ case binding_boolean:
+ td = (omapi_typed_data_t *)0;
+ status = omapi_typed_data_new (MDL, &td, omapi_datatype_int,
+ bp -> value -> value.boolean);
+ break;
+
+ case binding_numeric:
+ td = (omapi_typed_data_t *)0;
+ status = omapi_typed_data_new (MDL, &td, omapi_datatype_int,
+ (int)
+ bp -> value -> value.intval);
+ break;
+
+ case binding_data:
+ td = (omapi_typed_data_t *)0;
+ status = omapi_typed_data_new (MDL, &td, omapi_datatype_data,
+ bp -> value -> value.data.len);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ memcpy (&td -> u.buffer.value [0],
+ bp -> value -> value.data.data,
+ bp -> value -> value.data.len);
+ break;
+
+ /* Can't return values for these two (yet?). */
+ case binding_dns:
+ case binding_function:
+ return DHCP_R_INVALIDARG;
+
+ default:
+ log_fatal ("Impossible case at %s:%d.", MDL);
+ return ISC_R_FAILURE;
+ }
+
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_value_new (value, MDL);
+ if (status != ISC_R_SUCCESS) {
+ omapi_typed_data_dereference (&td, MDL);
+ return status;
+ }
+
+ omapi_data_string_reference (&(*value) -> name, name, MDL);
+ omapi_typed_data_reference (&(*value) -> value, td, MDL);
+ omapi_typed_data_dereference (&td, MDL);
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t binding_scope_stuff_values (omapi_object_t *c,
+ struct binding_scope *scope)
+{
+ struct binding *bp;
+ unsigned len;
+ isc_result_t status;
+
+ for (bp = scope -> bindings; bp; bp = bp -> next) {
+ if (bp -> value) {
+ if (bp -> value -> type == binding_dns ||
+ bp -> value -> type == binding_function)
+ continue;
+
+ /* Stuff the name. */
+ len = strlen (bp -> name);
+ status = omapi_connection_put_uint16 (c, len);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = omapi_connection_copyin (c,
+ (unsigned char *)bp -> name,
+ len);
+ if (status != ISC_R_SUCCESS)
+ return status;
+
+ switch (bp -> value -> type) {
+ case binding_boolean:
+ status = omapi_connection_put_uint32 (c,
+ sizeof (u_int32_t));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = (omapi_connection_put_uint32
+ (c,
+ ((u_int32_t)(bp -> value -> value.boolean))));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ break;
+
+ case binding_data:
+ status = (omapi_connection_put_uint32
+ (c, bp -> value -> value.data.len));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ if (bp -> value -> value.data.len) {
+ status = (omapi_connection_copyin
+ (c, bp -> value -> value.data.data,
+ bp -> value -> value.data.len));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ }
+ break;
+
+ case binding_numeric:
+ status = (omapi_connection_put_uint32
+ (c, sizeof (u_int32_t)));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ status = (omapi_connection_put_uint32
+ (c, ((u_int32_t)
+ (bp -> value -> value.intval))));
+ if (status != ISC_R_SUCCESS)
+ return status;
+ break;
+
+
+ /* NOTREACHED */
+ case binding_dns:
+ case binding_function:
+ break;
+ }
+ }
+ }
+ return ISC_R_SUCCESS;
+}
+
+/* vim: set tabstop=8: */
diff --git a/server/salloc.c b/server/salloc.c
new file mode 100644
index 0000000..47ff7ab
--- /dev/null
+++ b/server/salloc.c
@@ -0,0 +1,251 @@
+/* salloc.c
+
+ Memory allocation for the DHCP server... */
+
+/*
+ * 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/
+ *
+ */
+
+#include "dhcpd.h"
+#include <omapip/omapip_p.h>
+
+#if defined (COMPACT_LEASES)
+struct lease *free_leases;
+
+#if defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+struct lease *lease_hunks;
+
+void relinquish_lease_hunks ()
+{
+ struct lease *c, *n, **p;
+ int i;
+
+ /* Account for all the leases on the free list. */
+ for (n = lease_hunks; n; n = n->next) {
+ for (i = 1; i < n->starts + 1; i++) {
+ p = &free_leases;
+ for (c = free_leases; c; c = c->next) {
+ if (c == &n[i]) {
+ *p = c->next;
+ n->ends++;
+ break;
+ }
+ p = &c->next;
+ }
+ if (!c) {
+ log_info("lease %s refcnt %d",
+ piaddr (n[i].ip_addr), n[i].refcnt);
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history(&n[i]);
+#endif
+ }
+ }
+ }
+
+ for (c = lease_hunks; c; c = n) {
+ n = c->next;
+ if (c->ends != c->starts) {
+ log_info("lease hunk %lx leases %ld free %ld",
+ (unsigned long)c, (unsigned long)(c->starts),
+ (unsigned long)(c->ends));
+ }
+ dfree(c, MDL);
+ }
+
+ /* Free all the rogue leases. */
+ for (c = free_leases; c; c = n) {
+ n = c->next;
+ dfree(c, MDL);
+ }
+}
+#endif
+
+struct lease *new_leases (n, file, line)
+ unsigned n;
+ const char *file;
+ int line;
+{
+ struct lease *rval;
+#if defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ rval = dmalloc ((n + 1) * sizeof (struct lease), file, line);
+ memset (rval, 0, sizeof (struct lease));
+ rval -> starts = n;
+ rval -> next = lease_hunks;
+ lease_hunks = rval;
+ rval++;
+#else
+ rval = dmalloc (n * sizeof (struct lease), file, line);
+#endif
+ return rval;
+}
+
+/* If we are allocating leases in aggregations, there's really no way
+ to free one, although perhaps we can maintain a free list. */
+
+isc_result_t dhcp_lease_free (omapi_object_t *lo,
+ const char *file, int line)
+{
+ struct lease *lease;
+ if (lo -> type != dhcp_type_lease)
+ return DHCP_R_INVALIDARG;
+ lease = (struct lease *)lo;
+ memset (lease, 0, sizeof (struct lease));
+ lease -> next = free_leases;
+ free_leases = lease;
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t dhcp_lease_get (omapi_object_t **lp,
+ const char *file, int line)
+{
+ struct lease **lease = (struct lease **)lp;
+ struct lease *lt;
+
+ if (free_leases) {
+ lt = free_leases;
+ free_leases = lt -> next;
+ *lease = lt;
+ return ISC_R_SUCCESS;
+ }
+ return ISC_R_NOMEMORY;
+}
+#endif /* COMPACT_LEASES */
+
+OMAPI_OBJECT_ALLOC (lease, struct lease, dhcp_type_lease)
+OMAPI_OBJECT_ALLOC (class, struct class, dhcp_type_class)
+OMAPI_OBJECT_ALLOC (subclass, struct class, dhcp_type_subclass)
+OMAPI_OBJECT_ALLOC (pool, struct pool, dhcp_type_pool)
+
+#if !defined (NO_HOST_FREES) /* Scary debugging mode - don't enable! */
+OMAPI_OBJECT_ALLOC (host, struct host_decl, dhcp_type_host)
+#else
+isc_result_t host_allocate (struct host_decl **p, const char *file, int line)
+{
+ return omapi_object_allocate ((omapi_object_t **)p,
+ dhcp_type_host, 0, file, line);
+}
+
+isc_result_t host_reference (struct host_decl **pptr, struct host_decl *ptr,
+ const char *file, int line)
+{
+ return omapi_object_reference ((omapi_object_t **)pptr,
+ (omapi_object_t *)ptr, file, line);
+}
+
+isc_result_t host_dereference (struct host_decl **ptr,
+ const char *file, int line)
+{
+ if ((*ptr) -> refcnt == 1) {
+ log_error ("host dereferenced with refcnt == 1.");
+#if defined (DEBUG_RC_HISTORY)
+ dump_rc_history ();
+#endif
+ abort ();
+ }
+ return omapi_object_dereference ((omapi_object_t **)ptr, file, line);
+}
+#endif
+
+struct lease_state *free_lease_states;
+
+struct lease_state *new_lease_state (file, line)
+ const char *file;
+ int line;
+{
+ struct lease_state *rval;
+
+ if (free_lease_states) {
+ rval = free_lease_states;
+ free_lease_states =
+ (struct lease_state *)(free_lease_states -> next);
+ dmalloc_reuse (rval, file, line, 0);
+ } else {
+ rval = dmalloc (sizeof (struct lease_state), file, line);
+ if (!rval)
+ return rval;
+ }
+ memset (rval, 0, sizeof *rval);
+ if (!option_state_allocate (&rval -> options, file, line)) {
+ free_lease_state (rval, file, line);
+ return (struct lease_state *)0;
+ }
+ return rval;
+}
+
+void free_lease_state (ptr, file, line)
+ struct lease_state *ptr;
+ const char *file;
+ int line;
+{
+ if (ptr -> options)
+ option_state_dereference (&ptr -> options, file, line);
+ if (ptr -> packet)
+ packet_dereference (&ptr -> packet, file, line);
+ if (ptr -> shared_network)
+ shared_network_dereference (&ptr -> shared_network,
+ file, line);
+
+ data_string_forget (&ptr -> parameter_request_list, file, line);
+ data_string_forget (&ptr -> filename, file, line);
+ data_string_forget (&ptr -> server_name, file, line);
+ ptr -> next = free_lease_states;
+ free_lease_states = ptr;
+ dmalloc_reuse (free_lease_states, (char *)0, 0, 0);
+}
+
+#if defined (DEBUG_MEMORY_LEAKAGE) || \
+ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+void relinquish_free_lease_states ()
+{
+ struct lease_state *cs, *ns;
+
+ for (cs = free_lease_states; cs; cs = ns) {
+ ns = cs -> next;
+ dfree (cs, MDL);
+ }
+ free_lease_states = (struct lease_state *)0;
+}
+#endif
+
+struct permit *new_permit (file, line)
+ const char *file;
+ int line;
+{
+ struct permit *permit = ((struct permit *)
+ dmalloc (sizeof (struct permit), file, line));
+ if (!permit)
+ return permit;
+ memset (permit, 0, sizeof *permit);
+ return permit;
+}
+
+void free_permit (permit, file, line)
+ struct permit *permit;
+ const char *file;
+ int line;
+{
+ if (permit -> type == permit_class)
+ class_dereference (&permit -> class, MDL);
+ dfree (permit, file, line);
+}
diff --git a/server/stables.c b/server/stables.c
new file mode 100644
index 0000000..da25764
--- /dev/null
+++ b/server/stables.c
@@ -0,0 +1,504 @@
+/* stables.c
+
+ Tables of information only used by server... */
+
+/*
+ * Copyright (c) 2004-2011,2013-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>
+
+#if defined (FAILOVER_PROTOCOL)
+
+/* This is used to indicate some kind of failure when generating a
+ failover option. */
+failover_option_t null_failover_option = { 0, 0 };
+failover_option_t skip_failover_option = { 0, 0 };
+
+/* Information about failover options, for printing, encoding
+ and decoding. */
+struct failover_option_info ft_options [] =
+{
+ { 0, "unused", FT_UNDEF, 0, 0, 0 },
+ { FTO_ADDRESSES_TRANSFERRED, "addresses-transferred", FT_UINT32, 1,
+ FM_OFFSET(addresses_transferred), FTB_ADDRESSES_TRANSFERRED },
+ { FTO_ASSIGNED_IP_ADDRESS, "assigned-IP-address", FT_IPADDR, 1,
+ FM_OFFSET(assigned_addr), FTB_ASSIGNED_IP_ADDRESS },
+ { FTO_BINDING_STATUS, "binding-status", FT_UINT8, 1,
+ FM_OFFSET(binding_status), FTB_BINDING_STATUS },
+ { FTO_CLIENT_IDENTIFIER, "client-identifier", FT_BYTES, 0,
+ FM_OFFSET(client_identifier), FTB_CLIENT_IDENTIFIER },
+ { FTO_CHADDR, "client-hardware-address", FT_BYTES, 0,
+ FM_OFFSET(chaddr), FTB_CHADDR },
+ { FTO_CLTT, "client-last-transaction-time", FT_UINT32, 1,
+ FM_OFFSET(cltt), FTB_CLTT },
+ { FTO_REPLY_OPTIONS, "client-reply-options", FT_BYTES, 0,
+ FM_OFFSET(reply_options), FTB_REPLY_OPTIONS },
+ { FTO_REQUEST_OPTIONS, "client-request-options", FT_BYTES, 0,
+ FM_OFFSET(request_options), FTB_REQUEST_OPTIONS },
+ { FTO_DDNS, "DDNS", FT_DDNS, 1, FM_OFFSET(ddns), FTB_DDNS },
+ { FTO_DELAYED_SERVICE, "delayed-service", FT_UINT8, 1,
+ FM_OFFSET(delayed_service), FTB_DELAYED_SERVICE },
+ { FTO_HBA, "hash-bucket-assignment", FT_BYTES, 0,
+ FM_OFFSET(hba), FTB_HBA },
+ { FTO_IP_FLAGS, "IP-flags", FT_UINT16, 1,
+ FM_OFFSET(ip_flags), FTB_IP_FLAGS },
+ { FTO_LEASE_EXPIRY, "lease-expiration-time", FT_UINT32, 1,
+ FM_OFFSET(expiry), FTB_LEASE_EXPIRY },
+ { FTO_MAX_UNACKED, "max-unacked-bndupd", FT_UINT32, 1,
+ FM_OFFSET(max_unacked), FTB_MAX_UNACKED },
+ { FTO_MCLT, "MCLT", FT_UINT32, 1, FM_OFFSET(mclt), FTB_MCLT },
+ { FTO_MESSAGE, "message", FT_TEXT, 0,
+ FM_OFFSET(message), FTB_MESSAGE },
+ { FTO_MESSAGE_DIGEST, "message-digest", FT_BYTES, 0,
+ FM_OFFSET(message_digest), FTB_MESSAGE_DIGEST },
+ { FTO_POTENTIAL_EXPIRY, "potential-expiration-time", FT_UINT32, 1,
+ FM_OFFSET(potential_expiry), FTB_POTENTIAL_EXPIRY },
+ { FTO_RECEIVE_TIMER, "receive-timer", FT_UINT32, 1,
+ FM_OFFSET(receive_timer), FTB_RECEIVE_TIMER },
+ { FTO_PROTOCOL_VERSION, "protocol-version", FT_UINT8, 1,
+ FM_OFFSET(protocol_version), FTB_PROTOCOL_VERSION },
+ { FTO_REJECT_REASON, "reject-reason", FT_UINT8, 1,
+ FM_OFFSET(reject_reason), FTB_REJECT_REASON },
+ { FTO_RELATIONSHIP_NAME, "relationship-name", FT_BYTES, 0,
+ FM_OFFSET(relationship_name), FTB_RELATIONSHIP_NAME },
+ { FTO_SERVER_FLAGS, "server-flags", FT_UINT8, 1,
+ FM_OFFSET(server_flags), FTB_SERVER_FLAGS },
+ { FTO_SERVER_STATE, "server-state", FT_UINT8, 1,
+ FM_OFFSET(server_state), FTB_SERVER_STATE },
+ { FTO_STOS, "start-time-of-state", FT_UINT32, 1,
+ FM_OFFSET(stos), FTB_STOS },
+ { FTO_TLS_REPLY, "TLS-reply", FT_UINT8, 1,
+ FM_OFFSET(tls_reply), FTB_TLS_REPLY },
+ { FTO_TLS_REQUEST, "TLS-request", FT_UINT8, 1,
+ FM_OFFSET(tls_request), FTB_TLS_REQUEST },
+ { FTO_VENDOR_CLASS, "vendor-class-identifier", FT_BYTES, 0,
+ FM_OFFSET(vendor_class), FTB_VENDOR_CLASS },
+ { FTO_VENDOR_OPTIONS, "vendor-specific-options", FT_BYTES, 0,
+ FM_OFFSET(vendor_options), FTB_VENDOR_OPTIONS }
+};
+
+/* These are really options that make sense for a particular request - if
+ some other option comes in, we're not going to use it, so we can just
+ discard it. Note that the message-digest option is allowed for all
+ message types, but is not saved - it's just used to validate the message
+ and then discarded - so it's not mentioned here. */
+
+u_int32_t fto_allowed [] = {
+ 0, /* 0 unused */
+ 0, /* 1 POOLREQ */
+ FTB_ADDRESSES_TRANSFERRED, /* 2 POOLRESP */
+ (FTB_ASSIGNED_IP_ADDRESS | FTB_BINDING_STATUS | FTB_CLIENT_IDENTIFIER |
+ FTB_CHADDR | FTB_DDNS | FTB_IP_FLAGS | FTB_LEASE_EXPIRY |
+ FTB_POTENTIAL_EXPIRY | FTB_STOS | FTB_CLTT | FTB_REQUEST_OPTIONS |
+ FTB_REPLY_OPTIONS), /* 3 BNDUPD */
+ (FTB_ASSIGNED_IP_ADDRESS | FTB_BINDING_STATUS | FTB_CLIENT_IDENTIFIER |
+ FTB_CHADDR | FTB_DDNS | FTB_IP_FLAGS | FTB_LEASE_EXPIRY |
+ FTB_POTENTIAL_EXPIRY | FTB_STOS | FTB_CLTT | FTB_REQUEST_OPTIONS |
+ FTB_REPLY_OPTIONS | FTB_REJECT_REASON | FTB_MESSAGE), /* 4 BNDACK */
+ (FTB_RELATIONSHIP_NAME | FTB_MAX_UNACKED | FTB_RECEIVE_TIMER |
+ FTB_VENDOR_CLASS | FTB_PROTOCOL_VERSION | FTB_TLS_REQUEST |
+ FTB_MCLT | FTB_HBA), /* 5 CONNECT */
+ (FTB_RELATIONSHIP_NAME | FTB_MAX_UNACKED | FTB_RECEIVE_TIMER |
+ FTB_VENDOR_CLASS | FTB_PROTOCOL_VERSION | FTB_TLS_REPLY |
+ FTB_REJECT_REASON | FTB_MESSAGE), /* CONNECTACK */
+ 0, /* 7 UPDREQALL */
+ 0, /* 8 UPDDONE */
+ 0, /* 9 UPDREQ */
+ (FTB_SERVER_STATE | FTB_SERVER_FLAGS | FTB_STOS), /* 10 STATE */
+ 0, /* 11 CONTACT */
+ (FTB_REJECT_REASON | FTB_MESSAGE) /* 12 DISCONNECT */
+};
+
+/* Sizes of the various types. */
+int ft_sizes [] = {
+ 1, /* FT_UINT8 */
+ 4, /* FT_IPADDR */
+ 4, /* FT_UINT32 */
+ 1, /* FT_BYTES */
+ 1, /* FT_TEXT_OR_BYTES */
+ 0, /* FT_DDNS */
+ 0, /* FT_DDNS1 */
+ 2, /* FT_UINT16 */
+ 1, /* FT_TEXT */
+ 0, /* FT_UNDEF */
+ 0, /* FT_DIGEST */
+};
+
+/* Names of the various failover link states. */
+const char *dhcp_flink_state_names [] = {
+ "invalid state 0",
+ "startup",
+ "message length wait",
+ "message wait",
+ "disconnected"
+};
+#endif /* FAILOVER_PROTOCOL */
+
+/* Failover binding state names. These are used even if there is no
+ failover protocol support. */
+const char *binding_state_names [] = {
+ "free", "active", "expired", "released", "abandoned",
+ "reset", "backup" };
+
+struct universe agent_universe;
+static struct option agent_options[] = {
+ { "circuit-id", "X", &agent_universe, 1, 1 },
+ { "remote-id", "X", &agent_universe, 2, 1 },
+ { "agent-id", "I", &agent_universe, 3, 1 },
+ { "DOCSIS-device-class", "L", &agent_universe, 4, 1 },
+ { "link-selection", "I", &agent_universe, 5, 1 },
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+struct universe server_universe;
+static struct option server_options[] = {
+ { "default-lease-time", "T", &server_universe, 1, 1 },
+ { "max-lease-time", "T", &server_universe, 2, 1 },
+ { "min-lease-time", "T", &server_universe, 3, 1 },
+ { "dynamic-bootp-lease-cutoff", "T", &server_universe, 4, 1 },
+ { "dynamic-bootp-lease-length", "L", &server_universe, 5, 1 },
+ { "boot-unknown-clients", "f", &server_universe, 6, 1 },
+ { "dynamic-bootp", "f", &server_universe, 7, 1 },
+ { "allow-bootp", "f", &server_universe, 8, 1 },
+ { "allow-booting", "f", &server_universe, 9, 1 },
+ { "one-lease-per-client", "f", &server_universe, 10, 1 },
+ { "get-lease-hostnames", "f", &server_universe, 11, 1 },
+ { "use-host-decl-names", "f", &server_universe, 12, 1 },
+ { "use-lease-addr-for-default-route", "f",
+ &server_universe, 13, 1 },
+ { "min-secs", "B", &server_universe, 14, 1 },
+ { "filename", "t", &server_universe, 15, 1 },
+ { "server-name", "t", &server_universe, 16, 1 },
+ { "next-server", "I", &server_universe, 17, 1 },
+ { "authoritative", "f", &server_universe, 18, 1 },
+ { "vendor-option-space", "U", &server_universe, 19, 1 },
+ { "always-reply-rfc1048", "f", &server_universe, 20, 1 },
+ { "site-option-space", "X", &server_universe, 21, 1 },
+ { "always-broadcast", "f", &server_universe, 22, 1 },
+ { "ddns-domainname", "t", &server_universe, 23, 1 },
+ { "ddns-hostname", "t", &server_universe, 24, 1 },
+ { "ddns-rev-domainname", "t", &server_universe, 25, 1 },
+ { "lease-file-name", "t", &server_universe, 26, 1 },
+ { "pid-file-name", "t", &server_universe, 27, 1 },
+ { "duplicates", "f", &server_universe, 28, 1 },
+ { "declines", "f", &server_universe, 29, 1 },
+ { "ddns-updates", "f", &server_universe, 30, 1 },
+ { "omapi-port", "S", &server_universe, 31, 1 },
+ { "local-port", "S", &server_universe, 32, 1 },
+ { "limited-broadcast-address", "I", &server_universe, 33, 1 },
+ { "remote-port", "S", &server_universe, 34, 1 },
+ { "local-address", "I", &server_universe, 35, 1 },
+ { "omapi-key", "d", &server_universe, 36, 1 },
+ { "stash-agent-options", "f", &server_universe, 37, 1 },
+ { "ddns-ttl", "T", &server_universe, 38, 1 },
+ { "ddns-update-style", "Nddns-styles.", &server_universe, 39, 1 },
+ { "client-updates", "f", &server_universe, 40, 1 },
+ { "update-optimization", "f", &server_universe, 41, 1 },
+ { "ping-check", "f", &server_universe, 42, 1 },
+ { "update-static-leases", "f", &server_universe, 43, 1 },
+ { "log-facility", "Nsyslog-facilities.",
+ &server_universe, 44, 1 },
+ { "do-forward-updates", "f", &server_universe, 45, 1 },
+ { "ping-timeout", "T", &server_universe, 46, 1 },
+ { "infinite-is-reserved", "f", &server_universe, 47, 1 },
+ { "update-conflict-detection", "f", &server_universe, 48, 1 },
+ { "leasequery", "f", &server_universe, 49, 1 },
+ { "adaptive-lease-time-threshold", "B", &server_universe, 50, 1 },
+ { "do-reverse-updates", "f", &server_universe, 51, 1 },
+ { "fqdn-reply", "f", &server_universe, 52, 1 },
+ { "preferred-lifetime", "T", &server_universe, 53, 1 },
+ { "dhcpv6-lease-file-name", "t", &server_universe, 54, 1 },
+ { "dhcpv6-pid-file-name", "t", &server_universe, 55, 1 },
+ { "limit-addrs-per-ia", "L", &server_universe, 56, 1 },
+ { "limit-prefs-per-ia", "L", &server_universe, 57, 1 },
+/* Assert a configuration parsing error if delayed-ack isn't compiled in. */
+#if defined(DELAYED_ACK)
+ { "delayed-ack", "S", &server_universe, 58, 1 },
+ { "max-ack-delay", "L", &server_universe, 59, 1 },
+#endif
+#if defined(LDAP_CONFIGURATION)
+ { "ldap-server", "t", &server_universe, 60, 1 },
+ { "ldap-port", "d", &server_universe, 61, 1 },
+ { "ldap-username", "t", &server_universe, 62, 1 },
+ { "ldap-password", "t", &server_universe, 63, 1 },
+ { "ldap-base-dn", "t", &server_universe, 64, 1 },
+ { "ldap-method", "Nldap-methods.", &server_universe, 65, 1 },
+ { "ldap-debug-file", "t", &server_universe, 66, 1 },
+ { "ldap-dhcp-server-cn", "t", &server_universe, 67, 1 },
+ { "ldap-referrals", "f", &server_universe, 68, 1 },
+#if defined(LDAP_USE_SSL)
+ { "ldap-ssl", "Nldap-ssl-usage.", &server_universe, 69, 1 },
+ { "ldap-tls-reqcert", "Nldap-tls-reqcert.", &server_universe, 70, 1 },
+ { "ldap-tls-ca-file", "t", &server_universe, 71, 1 },
+ { "ldap-tls-ca-dir", "t", &server_universe, 72, 1 },
+ { "ldap-tls-cert", "t", &server_universe, 73, 1 },
+ { "ldap-tls-key", "t", &server_universe, 74, 1 },
+ { "ldap-tls-crlcheck", "Nldap-tls-crlcheck.", &server_universe, 75, 1 },
+ { "ldap-tls-ciphers", "t", &server_universe, 76, 1 },
+ { "ldap-tls-randfile", "t", &server_universe, 77, 1 },
+#endif /* LDAP_USE_SSL */
+#endif /* LDAP_CONFIGURATION */
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+#if defined(LDAP_CONFIGURATION)
+struct enumeration_value ldap_values [] = {
+ { "static", LDAP_METHOD_STATIC },
+ { "dynamic", LDAP_METHOD_DYNAMIC },
+ { (char *) 0, 0 }
+};
+
+struct enumeration ldap_methods = {
+ (struct enumeration *)0,
+ "ldap-methods", 1,
+ ldap_values
+};
+
+#if defined(LDAP_USE_SSL)
+struct enumeration_value ldap_ssl_usage_values [] = {
+ { "off", LDAP_SSL_OFF },
+ { "on",LDAP_SSL_ON },
+ { "ldaps", LDAP_SSL_LDAPS },
+ { "start_tls", LDAP_SSL_TLS },
+ { (char *) 0, 0 }
+};
+
+struct enumeration ldap_ssl_usage_enum = {
+ (struct enumeration *)0,
+ "ldap-ssl-usage", 1,
+ ldap_ssl_usage_values
+};
+
+struct enumeration_value ldap_tls_reqcert_values [] = {
+ { "never", LDAP_OPT_X_TLS_NEVER },
+ { "hard", LDAP_OPT_X_TLS_HARD },
+ { "demand", LDAP_OPT_X_TLS_DEMAND},
+ { "allow", LDAP_OPT_X_TLS_ALLOW },
+ { "try", LDAP_OPT_X_TLS_TRY },
+ { (char *) 0, 0 }
+};
+struct enumeration ldap_tls_reqcert_enum = {
+ (struct enumeration *)0,
+ "ldap-tls-reqcert", 1,
+ ldap_tls_reqcert_values
+};
+
+struct enumeration_value ldap_tls_crlcheck_values [] = {
+ { "none", LDAP_OPT_X_TLS_CRL_NONE},
+ { "peer", LDAP_OPT_X_TLS_CRL_PEER},
+ { "all", LDAP_OPT_X_TLS_CRL_ALL },
+ { (char *) 0, 0 }
+};
+struct enumeration ldap_tls_crlcheck_enum = {
+ (struct enumeration *)0,
+ "ldap-tls-crlcheck", 1,
+ ldap_tls_crlcheck_values
+};
+#endif
+#endif
+
+struct enumeration_value ddns_styles_values [] = {
+ { "none", 0 },
+ { "ad-hoc", 1 },
+ { "interim", 2 },
+ { (char *)0, 0 }
+};
+
+struct enumeration ddns_styles = {
+ (struct enumeration *)0,
+ "ddns-styles", 1,
+ ddns_styles_values
+};
+
+struct enumeration_value syslog_values [] = {
+#if defined (LOG_KERN)
+ { "kern", LOG_KERN },
+#endif
+#if defined (LOG_USER)
+ { "user", LOG_USER },
+#endif
+#if defined (LOG_MAIL)
+ { "mail", LOG_MAIL },
+#endif
+#if defined (LOG_DAEMON)
+ { "daemon", LOG_DAEMON },
+#endif
+#if defined (LOG_AUTH)
+ { "auth", LOG_AUTH },
+#endif
+#if defined (LOG_SYSLOG)
+ { "syslog", LOG_SYSLOG },
+#endif
+#if defined (LOG_LPR)
+ { "lpr", LOG_LPR },
+#endif
+#if defined (LOG_NEWS)
+ { "news", LOG_NEWS },
+#endif
+#if defined (LOG_UUCP)
+ { "uucp", LOG_UUCP },
+#endif
+#if defined (LOG_CRON)
+ { "cron", LOG_CRON },
+#endif
+#if defined (LOG_AUTHPRIV)
+ { "authpriv", LOG_AUTHPRIV },
+#endif
+#if defined (LOG_FTP)
+ { "ftp", LOG_FTP },
+#endif
+#if defined (LOG_LOCAL0)
+ { "local0", LOG_LOCAL0 },
+#endif
+#if defined (LOG_LOCAL1)
+ { "local1", LOG_LOCAL1 },
+#endif
+#if defined (LOG_LOCAL2)
+ { "local2", LOG_LOCAL2 },
+#endif
+#if defined (LOG_LOCAL3)
+ { "local3", LOG_LOCAL3 },
+#endif
+#if defined (LOG_LOCAL4)
+ { "local4", LOG_LOCAL4 },
+#endif
+#if defined (LOG_LOCAL5)
+ { "local5", LOG_LOCAL5 },
+#endif
+#if defined (LOG_LOCAL6)
+ { "local6", LOG_LOCAL6 },
+#endif
+#if defined (LOG_LOCAL7)
+ { "local7", LOG_LOCAL7 },
+#endif
+ { (char *)0, 0 }
+};
+
+struct enumeration syslog_enum = {
+ (struct enumeration *)0,
+ "syslog-facilities", 1,
+ syslog_values
+};
+
+void initialize_server_option_spaces()
+{
+ int i;
+ unsigned code;
+
+ /* Set up the Relay Agent Information Option suboption space... */
+ agent_universe.name = "agent";
+ agent_universe.concat_duplicates = 0;
+ agent_universe.option_state_dereference =
+ linked_option_state_dereference;
+ agent_universe.lookup_func = lookup_linked_option;
+ agent_universe.save_func = save_linked_option;
+ agent_universe.delete_func = delete_linked_option;
+ agent_universe.encapsulate = linked_option_space_encapsulate;
+ agent_universe.foreach = linked_option_space_foreach;
+ agent_universe.decode = parse_option_buffer;
+ agent_universe.index = universe_count++;
+ agent_universe.length_size = 1;
+ agent_universe.tag_size = 1;
+ agent_universe.get_tag = getUChar;
+ agent_universe.store_tag = putUChar;
+ agent_universe.get_length = getUChar;
+ agent_universe.store_length = putUChar;
+ agent_universe.site_code_min = 0;
+ agent_universe.end = 0;
+ universes [agent_universe.index] = &agent_universe;
+ if (!option_name_new_hash(&agent_universe.name_hash,
+ AGENT_HASH_SIZE, MDL) ||
+ !option_code_new_hash(&agent_universe.code_hash,
+ AGENT_HASH_SIZE, MDL))
+ log_fatal ("Can't allocate agent option hash table.");
+ for (i = 0 ; agent_options[i].name ; i++) {
+ option_code_hash_add(agent_universe.code_hash,
+ &agent_options[i].code, 0,
+ &agent_options[i], MDL);
+ option_name_hash_add(agent_universe.name_hash,
+ agent_options[i].name, 0,
+ &agent_options[i], MDL);
+ }
+#if defined(REPORT_HASH_PERFORMANCE)
+ log_info("Relay Agent name hash: %s",
+ option_name_hash_report(agent_universe.name_hash));
+ log_info("Relay Agent code hash: %s",
+ option_code_hash_report(agent_universe.code_hash));
+#endif
+ code = DHO_DHCP_AGENT_OPTIONS;
+ option_code_hash_lookup(&agent_universe.enc_opt,
+ dhcp_universe.code_hash, &code, 0, MDL);
+
+ /* Set up the server option universe... */
+ server_universe.name = "server";
+ server_universe.concat_duplicates = 0;
+ server_universe.lookup_func = lookup_hashed_option;
+ server_universe.option_state_dereference =
+ hashed_option_state_dereference;
+ server_universe.save_func = save_hashed_option;
+ server_universe.delete_func = delete_hashed_option;
+ server_universe.encapsulate = hashed_option_space_encapsulate;
+ server_universe.foreach = hashed_option_space_foreach;
+ server_universe.length_size = 1; /* Never used ... */
+ server_universe.tag_size = 4;
+ server_universe.store_tag = putUChar;
+ server_universe.store_length = putUChar;
+ server_universe.site_code_min = 0;
+ server_universe.end = 0;
+ server_universe.index = universe_count++;
+ universes [server_universe.index] = &server_universe;
+ if (!option_name_new_hash(&server_universe.name_hash,
+ SERVER_HASH_SIZE, MDL) ||
+ !option_code_new_hash(&server_universe.code_hash,
+ SERVER_HASH_SIZE, MDL))
+ log_fatal ("Can't allocate server option hash table.");
+ for (i = 0 ; server_options[i].name ; i++) {
+ option_code_hash_add(server_universe.code_hash,
+ &server_options[i].code, 0,
+ &server_options[i], MDL);
+ option_name_hash_add(server_universe.name_hash,
+ server_options[i].name, 0,
+ &server_options[i], MDL);
+ }
+#if defined(REPORT_HASH_PERFORMANCE)
+ log_info("Server-Config Option name hash: %s",
+ option_name_hash_report(server_universe.name_hash));
+ log_info("Server-Config Option code hash: %s",
+ option_code_hash_report(server_universe.code_hash));
+#endif
+
+ /* Add the server and agent option spaces to the option space hash. */
+ universe_hash_add (universe_hash,
+ agent_universe.name, 0, &agent_universe, MDL);
+ universe_hash_add (universe_hash,
+ server_universe.name, 0, &server_universe, MDL);
+
+ /* Make the server universe the configuration option universe. */
+ config_universe = &server_universe;
+
+ code = SV_VENDOR_OPTION_SPACE;
+ option_code_hash_lookup(&vendor_cfg_option, server_universe.code_hash,
+ &code, 0, MDL);
+}
diff --git a/server/tests/Atffile b/server/tests/Atffile
new file mode 100644
index 0000000..b2fdc0f
--- /dev/null
+++ b/server/tests/Atffile
@@ -0,0 +1,5 @@
+Content-Type: application/X-atf-atffile; version="1"
+
+prop: test-suite = dhcp4
+
+tp-glob: *_unittests
diff --git a/server/tests/Makefile.am b/server/tests/Makefile.am
new file mode 100644
index 0000000..adde853
--- /dev/null
+++ b/server/tests/Makefile.am
@@ -0,0 +1,53 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = $(ATF_CFLAGS) -DUNIT_TEST -I$(top_srcdir)/includes
+AM_CPPFLAGS += -I$(top_srcdir)/bind/include -I$(top_srcdir)
+AM_CPPFLAGS += -DLOCALSTATEDIR='"."'
+
+EXTRA_DIST = Atffile
+
+# for autotools debugging only
+info:
+ @echo "ATF_CFLAGS=$(ATF_CFLAGS)"
+ @echo "ATF_LDFLAGS=$(ATF_LDFLAGS)"
+ @echo "ATF_LIBS=$(ATF_LIBS)"
+
+DHCPSRC = ../dhcp.c ../bootp.c ../confpars.c ../db.c ../class.c \
+ ../failover.c ../omapi.c ../mdb.c ../stables.c ../salloc.c \
+ ../ddns.c ../dhcpleasequery.c ../dhcpv6.c ../mdb6.c \
+ ../ldap.c ../ldap_casa.c ../dhcpd.c
+
+DHCPLIBS = $(top_builddir)/common/libdhcp.a $(top_builddir)/omapip/libomapi.a \
+ $(top_builddir)/dhcpctl/libdhcpctl.a $(top_builddir)/bind/lib/libdns.a \
+ $(top_builddir)/bind/lib/libisc.a
+
+ATF_TESTS =
+if HAVE_ATF
+
+ATF_TESTS += dhcpd_unittests legacy_unittests hash_unittests load_bal_unittests
+
+dhcpd_unittests_SOURCES = $(DHCPSRC)
+dhcpd_unittests_SOURCES += simple_unittest.c
+
+dhcpd_unittests_LDADD = $(ATF_LDFLAGS)
+dhcpd_unittests_LDADD += $(DHCPLIBS)
+
+dhcpd_unittests_LDFLAGS = $(AM_LDFLAGS) $(ATF_LDFLAGS)
+
+hash_unittests_SOURCES = $(DHCPSRC) hash_unittest.c
+hash_unittests_LDADD = $(DHCPLIBS) $(ATF_LDFLAGS)
+
+
+# This is a legacy unittest. It replaces main() with something that was in mdb6.c
+legacy_unittests_SOURCES = $(DHCPSRC) mdb6_unittest.c
+legacy_unittests_LDADD = $(DHCPLIBS) $(ATF_LDFLAGS)
+
+load_bal_unittests_SOURCES = $(DHCPSRC) load_bal_unittest.c
+load_bal_unittests_LDADD = $(DHCPLIBS) $(ATF_LDFLAGS)
+
+check: $(ATF_TESTS)
+ sh ${top_srcdir}/tests/unittest.sh
+
+endif
+
+check_PROGRAMS = $(ATF_TESTS)
diff --git a/server/tests/Makefile.in b/server/tests/Makefile.in
new file mode 100644
index 0000000..8019f98
--- /dev/null
+++ b/server/tests/Makefile.in
@@ -0,0 +1,999 @@
+# 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 = dhcpd_unittests legacy_unittests hash_unittests load_bal_unittests
+check_PROGRAMS = $(am__EXEEXT_2)
+subdir = server/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 = dhcpd_unittests$(EXEEXT) \
+@HAVE_ATF_TRUE@ legacy_unittests$(EXEEXT) \
+@HAVE_ATF_TRUE@ hash_unittests$(EXEEXT) \
+@HAVE_ATF_TRUE@ load_bal_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+am__dhcpd_unittests_SOURCES_DIST = ../dhcp.c ../bootp.c ../confpars.c \
+ ../db.c ../class.c ../failover.c ../omapi.c ../mdb.c \
+ ../stables.c ../salloc.c ../ddns.c ../dhcpleasequery.c \
+ ../dhcpv6.c ../mdb6.c ../ldap.c ../ldap_casa.c ../dhcpd.c \
+ simple_unittest.c
+am__objects_1 = dhcp.$(OBJEXT) bootp.$(OBJEXT) confpars.$(OBJEXT) \
+ db.$(OBJEXT) class.$(OBJEXT) failover.$(OBJEXT) \
+ omapi.$(OBJEXT) mdb.$(OBJEXT) stables.$(OBJEXT) \
+ salloc.$(OBJEXT) ddns.$(OBJEXT) dhcpleasequery.$(OBJEXT) \
+ dhcpv6.$(OBJEXT) mdb6.$(OBJEXT) ldap.$(OBJEXT) \
+ ldap_casa.$(OBJEXT) dhcpd.$(OBJEXT)
+@HAVE_ATF_TRUE@am_dhcpd_unittests_OBJECTS = $(am__objects_1) \
+@HAVE_ATF_TRUE@ simple_unittest.$(OBJEXT)
+dhcpd_unittests_OBJECTS = $(am_dhcpd_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_ATF_TRUE@dhcpd_unittests_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+@HAVE_ATF_TRUE@ $(DHCPLIBS)
+dhcpd_unittests_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(dhcpd_unittests_LDFLAGS) $(LDFLAGS) -o $@
+am__hash_unittests_SOURCES_DIST = ../dhcp.c ../bootp.c ../confpars.c \
+ ../db.c ../class.c ../failover.c ../omapi.c ../mdb.c \
+ ../stables.c ../salloc.c ../ddns.c ../dhcpleasequery.c \
+ ../dhcpv6.c ../mdb6.c ../ldap.c ../ldap_casa.c ../dhcpd.c \
+ hash_unittest.c
+@HAVE_ATF_TRUE@am_hash_unittests_OBJECTS = $(am__objects_1) \
+@HAVE_ATF_TRUE@ hash_unittest.$(OBJEXT)
+hash_unittests_OBJECTS = $(am_hash_unittests_OBJECTS)
+@HAVE_ATF_TRUE@hash_unittests_DEPENDENCIES = $(DHCPLIBS) \
+@HAVE_ATF_TRUE@ $(am__DEPENDENCIES_1)
+am__legacy_unittests_SOURCES_DIST = ../dhcp.c ../bootp.c ../confpars.c \
+ ../db.c ../class.c ../failover.c ../omapi.c ../mdb.c \
+ ../stables.c ../salloc.c ../ddns.c ../dhcpleasequery.c \
+ ../dhcpv6.c ../mdb6.c ../ldap.c ../ldap_casa.c ../dhcpd.c \
+ mdb6_unittest.c
+@HAVE_ATF_TRUE@am_legacy_unittests_OBJECTS = $(am__objects_1) \
+@HAVE_ATF_TRUE@ mdb6_unittest.$(OBJEXT)
+legacy_unittests_OBJECTS = $(am_legacy_unittests_OBJECTS)
+@HAVE_ATF_TRUE@legacy_unittests_DEPENDENCIES = $(DHCPLIBS) \
+@HAVE_ATF_TRUE@ $(am__DEPENDENCIES_1)
+am__load_bal_unittests_SOURCES_DIST = ../dhcp.c ../bootp.c \
+ ../confpars.c ../db.c ../class.c ../failover.c ../omapi.c \
+ ../mdb.c ../stables.c ../salloc.c ../ddns.c \
+ ../dhcpleasequery.c ../dhcpv6.c ../mdb6.c ../ldap.c \
+ ../ldap_casa.c ../dhcpd.c load_bal_unittest.c
+@HAVE_ATF_TRUE@am_load_bal_unittests_OBJECTS = $(am__objects_1) \
+@HAVE_ATF_TRUE@ load_bal_unittest.$(OBJEXT)
+load_bal_unittests_OBJECTS = $(am_load_bal_unittests_OBJECTS)
+@HAVE_ATF_TRUE@load_bal_unittests_DEPENDENCIES = $(DHCPLIBS) \
+@HAVE_ATF_TRUE@ $(am__DEPENDENCIES_1)
+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 = $(dhcpd_unittests_SOURCES) $(hash_unittests_SOURCES) \
+ $(legacy_unittests_SOURCES) $(load_bal_unittests_SOURCES)
+DIST_SOURCES = $(am__dhcpd_unittests_SOURCES_DIST) \
+ $(am__hash_unittests_SOURCES_DIST) \
+ $(am__legacy_unittests_SOURCES_DIST) \
+ $(am__load_bal_unittests_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) -DUNIT_TEST -I$(top_srcdir)/includes \
+ -I$(top_srcdir)/bind/include -I$(top_srcdir) \
+ -DLOCALSTATEDIR='"."'
+EXTRA_DIST = Atffile
+DHCPSRC = ../dhcp.c ../bootp.c ../confpars.c ../db.c ../class.c \
+ ../failover.c ../omapi.c ../mdb.c ../stables.c ../salloc.c \
+ ../ddns.c ../dhcpleasequery.c ../dhcpv6.c ../mdb6.c \
+ ../ldap.c ../ldap_casa.c ../dhcpd.c
+
+DHCPLIBS = $(top_builddir)/common/libdhcp.a $(top_builddir)/omapip/libomapi.a \
+ $(top_builddir)/dhcpctl/libdhcpctl.a $(top_builddir)/bind/lib/libdns.a \
+ $(top_builddir)/bind/lib/libisc.a
+
+ATF_TESTS = $(am__append_1)
+@HAVE_ATF_TRUE@dhcpd_unittests_SOURCES = $(DHCPSRC) simple_unittest.c
+@HAVE_ATF_TRUE@dhcpd_unittests_LDADD = $(ATF_LDFLAGS) $(DHCPLIBS)
+@HAVE_ATF_TRUE@dhcpd_unittests_LDFLAGS = $(AM_LDFLAGS) $(ATF_LDFLAGS)
+@HAVE_ATF_TRUE@hash_unittests_SOURCES = $(DHCPSRC) hash_unittest.c
+@HAVE_ATF_TRUE@hash_unittests_LDADD = $(DHCPLIBS) $(ATF_LDFLAGS)
+
+# This is a legacy unittest. It replaces main() with something that was in mdb6.c
+@HAVE_ATF_TRUE@legacy_unittests_SOURCES = $(DHCPSRC) mdb6_unittest.c
+@HAVE_ATF_TRUE@legacy_unittests_LDADD = $(DHCPLIBS) $(ATF_LDFLAGS)
+@HAVE_ATF_TRUE@load_bal_unittests_SOURCES = $(DHCPSRC) load_bal_unittest.c
+@HAVE_ATF_TRUE@load_bal_unittests_LDADD = $(DHCPLIBS) $(ATF_LDFLAGS)
+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 server/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign server/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)
+
+dhcpd_unittests$(EXEEXT): $(dhcpd_unittests_OBJECTS) $(dhcpd_unittests_DEPENDENCIES) $(EXTRA_dhcpd_unittests_DEPENDENCIES)
+ @rm -f dhcpd_unittests$(EXEEXT)
+ $(AM_V_CCLD)$(dhcpd_unittests_LINK) $(dhcpd_unittests_OBJECTS) $(dhcpd_unittests_LDADD) $(LIBS)
+
+hash_unittests$(EXEEXT): $(hash_unittests_OBJECTS) $(hash_unittests_DEPENDENCIES) $(EXTRA_hash_unittests_DEPENDENCIES)
+ @rm -f hash_unittests$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(hash_unittests_OBJECTS) $(hash_unittests_LDADD) $(LIBS)
+
+legacy_unittests$(EXEEXT): $(legacy_unittests_OBJECTS) $(legacy_unittests_DEPENDENCIES) $(EXTRA_legacy_unittests_DEPENDENCIES)
+ @rm -f legacy_unittests$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(legacy_unittests_OBJECTS) $(legacy_unittests_LDADD) $(LIBS)
+
+load_bal_unittests$(EXEEXT): $(load_bal_unittests_OBJECTS) $(load_bal_unittests_DEPENDENCIES) $(EXTRA_load_bal_unittests_DEPENDENCIES)
+ @rm -f load_bal_unittests$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(load_bal_unittests_OBJECTS) $(load_bal_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bootp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/class.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/confpars.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ddns.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpleasequery.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpv6.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/failover.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hash_unittest.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ldap.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ldap_casa.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/load_bal_unittest.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mdb.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mdb6.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mdb6_unittest.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/omapi.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/salloc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simple_unittest.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stables.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) '$<'`
+
+dhcp.o: ../dhcp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dhcp.o -MD -MP -MF $(DEPDIR)/dhcp.Tpo -c -o dhcp.o `test -f '../dhcp.c' || echo '$(srcdir)/'`../dhcp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp.Tpo $(DEPDIR)/dhcp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../dhcp.c' object='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 dhcp.o `test -f '../dhcp.c' || echo '$(srcdir)/'`../dhcp.c
+
+dhcp.obj: ../dhcp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dhcp.obj -MD -MP -MF $(DEPDIR)/dhcp.Tpo -c -o dhcp.obj `if test -f '../dhcp.c'; then $(CYGPATH_W) '../dhcp.c'; else $(CYGPATH_W) '$(srcdir)/../dhcp.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp.Tpo $(DEPDIR)/dhcp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../dhcp.c' object='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 dhcp.obj `if test -f '../dhcp.c'; then $(CYGPATH_W) '../dhcp.c'; else $(CYGPATH_W) '$(srcdir)/../dhcp.c'; fi`
+
+bootp.o: ../bootp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bootp.o -MD -MP -MF $(DEPDIR)/bootp.Tpo -c -o bootp.o `test -f '../bootp.c' || echo '$(srcdir)/'`../bootp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bootp.Tpo $(DEPDIR)/bootp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../bootp.c' object='bootp.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 bootp.o `test -f '../bootp.c' || echo '$(srcdir)/'`../bootp.c
+
+bootp.obj: ../bootp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bootp.obj -MD -MP -MF $(DEPDIR)/bootp.Tpo -c -o bootp.obj `if test -f '../bootp.c'; then $(CYGPATH_W) '../bootp.c'; else $(CYGPATH_W) '$(srcdir)/../bootp.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bootp.Tpo $(DEPDIR)/bootp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../bootp.c' object='bootp.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 bootp.obj `if test -f '../bootp.c'; then $(CYGPATH_W) '../bootp.c'; else $(CYGPATH_W) '$(srcdir)/../bootp.c'; fi`
+
+confpars.o: ../confpars.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT confpars.o -MD -MP -MF $(DEPDIR)/confpars.Tpo -c -o confpars.o `test -f '../confpars.c' || echo '$(srcdir)/'`../confpars.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/confpars.Tpo $(DEPDIR)/confpars.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../confpars.c' object='confpars.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 confpars.o `test -f '../confpars.c' || echo '$(srcdir)/'`../confpars.c
+
+confpars.obj: ../confpars.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT confpars.obj -MD -MP -MF $(DEPDIR)/confpars.Tpo -c -o confpars.obj `if test -f '../confpars.c'; then $(CYGPATH_W) '../confpars.c'; else $(CYGPATH_W) '$(srcdir)/../confpars.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/confpars.Tpo $(DEPDIR)/confpars.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../confpars.c' object='confpars.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 confpars.obj `if test -f '../confpars.c'; then $(CYGPATH_W) '../confpars.c'; else $(CYGPATH_W) '$(srcdir)/../confpars.c'; fi`
+
+db.o: ../db.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT db.o -MD -MP -MF $(DEPDIR)/db.Tpo -c -o db.o `test -f '../db.c' || echo '$(srcdir)/'`../db.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/db.Tpo $(DEPDIR)/db.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../db.c' object='db.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 db.o `test -f '../db.c' || echo '$(srcdir)/'`../db.c
+
+db.obj: ../db.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT db.obj -MD -MP -MF $(DEPDIR)/db.Tpo -c -o db.obj `if test -f '../db.c'; then $(CYGPATH_W) '../db.c'; else $(CYGPATH_W) '$(srcdir)/../db.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/db.Tpo $(DEPDIR)/db.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../db.c' object='db.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 db.obj `if test -f '../db.c'; then $(CYGPATH_W) '../db.c'; else $(CYGPATH_W) '$(srcdir)/../db.c'; fi`
+
+class.o: ../class.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT class.o -MD -MP -MF $(DEPDIR)/class.Tpo -c -o class.o `test -f '../class.c' || echo '$(srcdir)/'`../class.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/class.Tpo $(DEPDIR)/class.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../class.c' object='class.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 class.o `test -f '../class.c' || echo '$(srcdir)/'`../class.c
+
+class.obj: ../class.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT class.obj -MD -MP -MF $(DEPDIR)/class.Tpo -c -o class.obj `if test -f '../class.c'; then $(CYGPATH_W) '../class.c'; else $(CYGPATH_W) '$(srcdir)/../class.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/class.Tpo $(DEPDIR)/class.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../class.c' object='class.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 class.obj `if test -f '../class.c'; then $(CYGPATH_W) '../class.c'; else $(CYGPATH_W) '$(srcdir)/../class.c'; fi`
+
+failover.o: ../failover.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT failover.o -MD -MP -MF $(DEPDIR)/failover.Tpo -c -o failover.o `test -f '../failover.c' || echo '$(srcdir)/'`../failover.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/failover.Tpo $(DEPDIR)/failover.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../failover.c' object='failover.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 failover.o `test -f '../failover.c' || echo '$(srcdir)/'`../failover.c
+
+failover.obj: ../failover.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT failover.obj -MD -MP -MF $(DEPDIR)/failover.Tpo -c -o failover.obj `if test -f '../failover.c'; then $(CYGPATH_W) '../failover.c'; else $(CYGPATH_W) '$(srcdir)/../failover.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/failover.Tpo $(DEPDIR)/failover.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../failover.c' object='failover.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 failover.obj `if test -f '../failover.c'; then $(CYGPATH_W) '../failover.c'; else $(CYGPATH_W) '$(srcdir)/../failover.c'; fi`
+
+omapi.o: ../omapi.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT omapi.o -MD -MP -MF $(DEPDIR)/omapi.Tpo -c -o omapi.o `test -f '../omapi.c' || echo '$(srcdir)/'`../omapi.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/omapi.Tpo $(DEPDIR)/omapi.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../omapi.c' object='omapi.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 omapi.o `test -f '../omapi.c' || echo '$(srcdir)/'`../omapi.c
+
+omapi.obj: ../omapi.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT omapi.obj -MD -MP -MF $(DEPDIR)/omapi.Tpo -c -o omapi.obj `if test -f '../omapi.c'; then $(CYGPATH_W) '../omapi.c'; else $(CYGPATH_W) '$(srcdir)/../omapi.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/omapi.Tpo $(DEPDIR)/omapi.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../omapi.c' object='omapi.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 omapi.obj `if test -f '../omapi.c'; then $(CYGPATH_W) '../omapi.c'; else $(CYGPATH_W) '$(srcdir)/../omapi.c'; fi`
+
+mdb.o: ../mdb.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT mdb.o -MD -MP -MF $(DEPDIR)/mdb.Tpo -c -o mdb.o `test -f '../mdb.c' || echo '$(srcdir)/'`../mdb.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/mdb.Tpo $(DEPDIR)/mdb.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../mdb.c' object='mdb.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 mdb.o `test -f '../mdb.c' || echo '$(srcdir)/'`../mdb.c
+
+mdb.obj: ../mdb.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT mdb.obj -MD -MP -MF $(DEPDIR)/mdb.Tpo -c -o mdb.obj `if test -f '../mdb.c'; then $(CYGPATH_W) '../mdb.c'; else $(CYGPATH_W) '$(srcdir)/../mdb.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/mdb.Tpo $(DEPDIR)/mdb.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../mdb.c' object='mdb.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 mdb.obj `if test -f '../mdb.c'; then $(CYGPATH_W) '../mdb.c'; else $(CYGPATH_W) '$(srcdir)/../mdb.c'; fi`
+
+stables.o: ../stables.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT stables.o -MD -MP -MF $(DEPDIR)/stables.Tpo -c -o stables.o `test -f '../stables.c' || echo '$(srcdir)/'`../stables.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/stables.Tpo $(DEPDIR)/stables.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../stables.c' object='stables.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 stables.o `test -f '../stables.c' || echo '$(srcdir)/'`../stables.c
+
+stables.obj: ../stables.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT stables.obj -MD -MP -MF $(DEPDIR)/stables.Tpo -c -o stables.obj `if test -f '../stables.c'; then $(CYGPATH_W) '../stables.c'; else $(CYGPATH_W) '$(srcdir)/../stables.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/stables.Tpo $(DEPDIR)/stables.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../stables.c' object='stables.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 stables.obj `if test -f '../stables.c'; then $(CYGPATH_W) '../stables.c'; else $(CYGPATH_W) '$(srcdir)/../stables.c'; fi`
+
+salloc.o: ../salloc.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT salloc.o -MD -MP -MF $(DEPDIR)/salloc.Tpo -c -o salloc.o `test -f '../salloc.c' || echo '$(srcdir)/'`../salloc.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/salloc.Tpo $(DEPDIR)/salloc.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../salloc.c' object='salloc.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 salloc.o `test -f '../salloc.c' || echo '$(srcdir)/'`../salloc.c
+
+salloc.obj: ../salloc.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT salloc.obj -MD -MP -MF $(DEPDIR)/salloc.Tpo -c -o salloc.obj `if test -f '../salloc.c'; then $(CYGPATH_W) '../salloc.c'; else $(CYGPATH_W) '$(srcdir)/../salloc.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/salloc.Tpo $(DEPDIR)/salloc.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../salloc.c' object='salloc.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 salloc.obj `if test -f '../salloc.c'; then $(CYGPATH_W) '../salloc.c'; else $(CYGPATH_W) '$(srcdir)/../salloc.c'; fi`
+
+ddns.o: ../ddns.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ddns.o -MD -MP -MF $(DEPDIR)/ddns.Tpo -c -o ddns.o `test -f '../ddns.c' || echo '$(srcdir)/'`../ddns.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ddns.Tpo $(DEPDIR)/ddns.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../ddns.c' object='ddns.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 ddns.o `test -f '../ddns.c' || echo '$(srcdir)/'`../ddns.c
+
+ddns.obj: ../ddns.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ddns.obj -MD -MP -MF $(DEPDIR)/ddns.Tpo -c -o ddns.obj `if test -f '../ddns.c'; then $(CYGPATH_W) '../ddns.c'; else $(CYGPATH_W) '$(srcdir)/../ddns.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ddns.Tpo $(DEPDIR)/ddns.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../ddns.c' object='ddns.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 ddns.obj `if test -f '../ddns.c'; then $(CYGPATH_W) '../ddns.c'; else $(CYGPATH_W) '$(srcdir)/../ddns.c'; fi`
+
+dhcpleasequery.o: ../dhcpleasequery.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dhcpleasequery.o -MD -MP -MF $(DEPDIR)/dhcpleasequery.Tpo -c -o dhcpleasequery.o `test -f '../dhcpleasequery.c' || echo '$(srcdir)/'`../dhcpleasequery.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpleasequery.Tpo $(DEPDIR)/dhcpleasequery.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../dhcpleasequery.c' object='dhcpleasequery.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 dhcpleasequery.o `test -f '../dhcpleasequery.c' || echo '$(srcdir)/'`../dhcpleasequery.c
+
+dhcpleasequery.obj: ../dhcpleasequery.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dhcpleasequery.obj -MD -MP -MF $(DEPDIR)/dhcpleasequery.Tpo -c -o dhcpleasequery.obj `if test -f '../dhcpleasequery.c'; then $(CYGPATH_W) '../dhcpleasequery.c'; else $(CYGPATH_W) '$(srcdir)/../dhcpleasequery.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpleasequery.Tpo $(DEPDIR)/dhcpleasequery.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../dhcpleasequery.c' object='dhcpleasequery.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 dhcpleasequery.obj `if test -f '../dhcpleasequery.c'; then $(CYGPATH_W) '../dhcpleasequery.c'; else $(CYGPATH_W) '$(srcdir)/../dhcpleasequery.c'; fi`
+
+dhcpv6.o: ../dhcpv6.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dhcpv6.o -MD -MP -MF $(DEPDIR)/dhcpv6.Tpo -c -o dhcpv6.o `test -f '../dhcpv6.c' || echo '$(srcdir)/'`../dhcpv6.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpv6.Tpo $(DEPDIR)/dhcpv6.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../dhcpv6.c' object='dhcpv6.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 dhcpv6.o `test -f '../dhcpv6.c' || echo '$(srcdir)/'`../dhcpv6.c
+
+dhcpv6.obj: ../dhcpv6.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dhcpv6.obj -MD -MP -MF $(DEPDIR)/dhcpv6.Tpo -c -o dhcpv6.obj `if test -f '../dhcpv6.c'; then $(CYGPATH_W) '../dhcpv6.c'; else $(CYGPATH_W) '$(srcdir)/../dhcpv6.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpv6.Tpo $(DEPDIR)/dhcpv6.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../dhcpv6.c' object='dhcpv6.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 dhcpv6.obj `if test -f '../dhcpv6.c'; then $(CYGPATH_W) '../dhcpv6.c'; else $(CYGPATH_W) '$(srcdir)/../dhcpv6.c'; fi`
+
+mdb6.o: ../mdb6.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT mdb6.o -MD -MP -MF $(DEPDIR)/mdb6.Tpo -c -o mdb6.o `test -f '../mdb6.c' || echo '$(srcdir)/'`../mdb6.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/mdb6.Tpo $(DEPDIR)/mdb6.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../mdb6.c' object='mdb6.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 mdb6.o `test -f '../mdb6.c' || echo '$(srcdir)/'`../mdb6.c
+
+mdb6.obj: ../mdb6.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT mdb6.obj -MD -MP -MF $(DEPDIR)/mdb6.Tpo -c -o mdb6.obj `if test -f '../mdb6.c'; then $(CYGPATH_W) '../mdb6.c'; else $(CYGPATH_W) '$(srcdir)/../mdb6.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/mdb6.Tpo $(DEPDIR)/mdb6.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../mdb6.c' object='mdb6.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 mdb6.obj `if test -f '../mdb6.c'; then $(CYGPATH_W) '../mdb6.c'; else $(CYGPATH_W) '$(srcdir)/../mdb6.c'; fi`
+
+ldap.o: ../ldap.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ldap.o -MD -MP -MF $(DEPDIR)/ldap.Tpo -c -o ldap.o `test -f '../ldap.c' || echo '$(srcdir)/'`../ldap.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ldap.Tpo $(DEPDIR)/ldap.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../ldap.c' object='ldap.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 ldap.o `test -f '../ldap.c' || echo '$(srcdir)/'`../ldap.c
+
+ldap.obj: ../ldap.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ldap.obj -MD -MP -MF $(DEPDIR)/ldap.Tpo -c -o ldap.obj `if test -f '../ldap.c'; then $(CYGPATH_W) '../ldap.c'; else $(CYGPATH_W) '$(srcdir)/../ldap.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ldap.Tpo $(DEPDIR)/ldap.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../ldap.c' object='ldap.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 ldap.obj `if test -f '../ldap.c'; then $(CYGPATH_W) '../ldap.c'; else $(CYGPATH_W) '$(srcdir)/../ldap.c'; fi`
+
+ldap_casa.o: ../ldap_casa.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ldap_casa.o -MD -MP -MF $(DEPDIR)/ldap_casa.Tpo -c -o ldap_casa.o `test -f '../ldap_casa.c' || echo '$(srcdir)/'`../ldap_casa.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ldap_casa.Tpo $(DEPDIR)/ldap_casa.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../ldap_casa.c' object='ldap_casa.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 ldap_casa.o `test -f '../ldap_casa.c' || echo '$(srcdir)/'`../ldap_casa.c
+
+ldap_casa.obj: ../ldap_casa.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ldap_casa.obj -MD -MP -MF $(DEPDIR)/ldap_casa.Tpo -c -o ldap_casa.obj `if test -f '../ldap_casa.c'; then $(CYGPATH_W) '../ldap_casa.c'; else $(CYGPATH_W) '$(srcdir)/../ldap_casa.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ldap_casa.Tpo $(DEPDIR)/ldap_casa.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../ldap_casa.c' object='ldap_casa.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 ldap_casa.obj `if test -f '../ldap_casa.c'; then $(CYGPATH_W) '../ldap_casa.c'; else $(CYGPATH_W) '$(srcdir)/../ldap_casa.c'; fi`
+
+dhcpd.o: ../dhcpd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dhcpd.o -MD -MP -MF $(DEPDIR)/dhcpd.Tpo -c -o dhcpd.o `test -f '../dhcpd.c' || echo '$(srcdir)/'`../dhcpd.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd.Tpo $(DEPDIR)/dhcpd.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../dhcpd.c' object='dhcpd.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 dhcpd.o `test -f '../dhcpd.c' || echo '$(srcdir)/'`../dhcpd.c
+
+dhcpd.obj: ../dhcpd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dhcpd.obj -MD -MP -MF $(DEPDIR)/dhcpd.Tpo -c -o dhcpd.obj `if test -f '../dhcpd.c'; then $(CYGPATH_W) '../dhcpd.c'; else $(CYGPATH_W) '$(srcdir)/../dhcpd.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcpd.Tpo $(DEPDIR)/dhcpd.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../dhcpd.c' object='dhcpd.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 dhcpd.obj `if test -f '../dhcpd.c'; then $(CYGPATH_W) '../dhcpd.c'; else $(CYGPATH_W) '$(srcdir)/../dhcpd.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-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
+
+
+# for autotools debugging only
+info:
+ @echo "ATF_CFLAGS=$(ATF_CFLAGS)"
+ @echo "ATF_LDFLAGS=$(ATF_LDFLAGS)"
+ @echo "ATF_LIBS=$(ATF_LIBS)"
+
+@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/server/tests/hash_unittest.c b/server/tests/hash_unittest.c
new file mode 100644
index 0000000..565aeec
--- /dev/null
+++ b/server/tests/hash_unittest.c
@@ -0,0 +1,607 @@
+/*
+ * Copyright (c) 2012 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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 "config.h"
+#include <atf-c.h>
+#include <omapip/omapip_p.h>
+#include "dhcpd.h"
+
+/*
+ * The following structures are kept here for reference only. As hash functions
+ * are somewhat convoluted, they are copied here for the reference. Original
+ * location is specified. Keep in mind that it may change over time:
+ *
+ * copied from server/omapi.c:49 *
+ * omapi_object_type_t *dhcp_type_lease;
+ * omapi_object_type_t *dhcp_type_pool;
+ * omapi_object_type_t *dhcp_type_class;
+ * omapi_object_type_t *dhcp_type_subclass;
+ * omapi_object_type_t *dhcp_type_host;
+ *
+ * copied from server/salloc.c:138
+ * OMAPI_OBJECT_ALLOC (lease, struct lease, dhcp_type_lease)
+ * OMAPI_OBJECT_ALLOC (class, struct class, dhcp_type_class)
+ * OMAPI_OBJECT_ALLOC (subclass, struct class, dhcp_type_subclass)
+ * OMAPI_OBJECT_ALLOC (pool, struct pool, dhcp_type_pool)
+ * OMAPI_OBJECT_ALLOC (host, struct host_decl, dhcp_type_host)
+ *
+ * copied from server/mdb.c:2686
+ * HASH_FUNCTIONS(lease_ip, const unsigned char *, struct lease, lease_ip_hash_t,
+ * lease_reference, lease_dereference, do_ip4_hash)
+ * HASH_FUNCTIONS(lease_id, const unsigned char *, struct lease, lease_id_hash_t,
+ * lease_reference, lease_dereference, do_id_hash)
+ * HASH_FUNCTIONS (host, const unsigned char *, struct host_decl, host_hash_t,
+ * host_reference, host_dereference, do_string_hash)
+ * HASH_FUNCTIONS (class, const char *, struct class, class_hash_t,
+ * class_reference, class_dereference, do_string_hash)
+ *
+ * copied from server/mdb.c:46
+ * host_hash_t *host_hw_addr_hash;
+ * host_hash_t *host_uid_hash;
+ * host_hash_t *host_name_hash;
+ * lease_id_hash_t *lease_uid_hash;
+ * lease_ip_hash_t *lease_ip_addr_hash;
+ * lease_id_hash_t *lease_hw_addr_hash;
+ */
+
+/**
+ * @brief sets client-id field in host declaration
+ *
+ * @param host pointer to host declaration
+ * @param uid pointer to client-id data
+ * @param uid_len length of the client-id data
+ *
+ * @return 1 if successful, 0 otherwise
+ */
+int lease_set_clientid(struct host_decl *host, const unsigned char *uid, int uid_len) {
+
+ /* clean-up this mess and set client-identifier in a sane way */
+ int real_len = uid_len;
+ if (uid_len == 0) {
+ real_len = strlen((const char *)uid) + 1;
+ }
+
+ memset(&host->client_identifier, 0, sizeof(host->client_identifier));
+ host->client_identifier.len = uid_len;
+ if (!buffer_allocate(&host->client_identifier.buffer, real_len, MDL)) {
+ return 0;
+ }
+ host->client_identifier.data = host->client_identifier.buffer->data;
+ memcpy((char *)host->client_identifier.data, uid, real_len);
+
+ return 1;
+}
+
+/// @brief executes uid hash test for specified client-ids (2 hosts)
+///
+/// Creates two host structures, adds first host to the uid hash,
+/// then adds second host to the hash, then removes first host,
+/// then removed the second. Many checks are performed during all
+/// operations.
+///
+/// @param clientid1 client-id of the first host
+/// @param clientid1_len client-id1 length (may be 0 for strings)
+/// @param clientid2 client-id of the second host
+/// @param clientid2_len client-id2 length (may be 0 for strings)
+void lease_hash_test_2hosts(unsigned char clientid1[], size_t clientid1_len,
+ unsigned char clientid2[], size_t clientid2_len) {
+
+ printf("Checking hash operation for 2 hosts: clientid1-len=%lu"
+ "clientid2-len=%lu\n", (unsigned long) clientid1_len,
+ (unsigned long) clientid2_len);
+
+ dhcp_db_objects_setup ();
+ dhcp_common_objects_setup ();
+
+ /* check that there is actually zero hosts in the hash */
+ /* @todo: host_hash_for_each() */
+
+ struct host_decl *host1 = 0, *host2 = 0;
+ struct host_decl *check = 0;
+
+ /* === step 1: allocate hosts === */
+ ATF_CHECK_MSG(host_allocate(&host1, MDL) == ISC_R_SUCCESS,
+ "Failed to allocate host");
+ ATF_CHECK_MSG(host_allocate(&host2, MDL) == ISC_R_SUCCESS,
+ "Failed to allocate host");
+
+ ATF_CHECK_MSG(host_new_hash(&host_uid_hash, HOST_HASH_SIZE, MDL) != 0,
+ "Unable to create new hash");
+
+ ATF_CHECK_MSG(buffer_allocate(&host1->client_identifier.buffer,
+ clientid1_len, MDL) != 0,
+ "Can't allocate uid buffer for host1");
+
+ ATF_CHECK_MSG(buffer_allocate(&host2->client_identifier.buffer,
+ clientid2_len, MDL) != 0,
+ "Can't allocate uid buffer for host2");
+
+ /* setting up host1->client_identifier is actually not needed */
+ /*
+ ATF_CHECK_MSG(lease_set_clientid(host1, clientid1, actual1_len) != 0,
+ "Failed to set client-id for host1");
+
+ ATF_CHECK_MSG(lease_set_clientid(host2, clientid2, actual2_len) != 0,
+ "Failed to set client-id for host2");
+ */
+
+ ATF_CHECK_MSG(host1->refcnt == 1, "Invalid refcnt for host1");
+ ATF_CHECK_MSG(host2->refcnt == 1, "Invalid refcnt for host2");
+
+ /* verify that our hosts are not in the hash yet */
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid1,
+ clientid1_len, MDL) == 0,
+ "Host1 is not supposed to be in the uid_hash.");
+
+ ATF_CHECK_MSG(!check, "Host1 is not supposed to be in the uid_hash.");
+
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid2,
+ clientid2_len, MDL) == 0,
+ "Host2 is not supposed to be in the uid_hash.");
+ ATF_CHECK_MSG(!check, "Host2 is not supposed to be in the uid_hash.");
+
+
+ /* === step 2: add first host to the hash === */
+ host_hash_add(host_uid_hash, clientid1, clientid1_len, host1, MDL);
+
+ /* 2 pointers expected: ours (host1) and the one stored in hash */
+ ATF_CHECK_MSG(host1->refcnt == 2, "Invalid refcnt for host1");
+ /* 1 pointer expected: just ours (host2) */
+ ATF_CHECK_MSG(host2->refcnt == 1, "Invalid refcnt for host2");
+
+ /* verify that host1 is really in the hash and the we can find it */
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid1,
+ clientid1_len, MDL),
+ "Host1 was supposed to be in the uid_hash.");
+ ATF_CHECK_MSG(check, "Host1 was supposed to be in the uid_hash.");
+
+ /* Hey! That's not the host we were looking for! */
+ ATF_CHECK_MSG(check == host1, "Wrong host returned by host_hash_lookup");
+
+ /* 3 pointers: host1, (stored in hash), check */
+ ATF_CHECK_MSG(host1->refcnt == 3, "Invalid refcnt for host1");
+
+ /* reference count should be increased because we not have a pointer */
+
+ host_dereference(&check, MDL); /* we don't need it now */
+
+ ATF_CHECK_MSG(check == NULL, "check pointer is supposed to be NULL");
+
+ /* 2 pointers: host1, (stored in hash) */
+ ATF_CHECK_MSG(host1->refcnt == 2, "Invalid refcnt for host1");
+
+ /* verify that host2 is not in the hash */
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid2,
+ clientid2_len, MDL) == 0,
+ "Host2 was not supposed to be in the uid_hash[2].");
+ ATF_CHECK_MSG(check == NULL, "Host2 was not supposed to be in the hash.");
+
+
+ /* === step 3: add second hot to the hash === */
+ host_hash_add(host_uid_hash, clientid2, clientid2_len, host2, MDL);
+
+ /* 2 pointers expected: ours (host1) and the one stored in hash */
+ ATF_CHECK_MSG(host2->refcnt == 2, "Invalid refcnt for host2");
+
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid2,
+ clientid2_len, MDL),
+ "Host2 was supposed to be in the uid_hash.");
+ ATF_CHECK_MSG(check, "Host2 was supposed to be in the uid_hash.");
+
+ /* Hey! That's not the host we were looking for! */
+ ATF_CHECK_MSG(check == host2, "Wrong host returned by host_hash_lookup");
+
+ /* 3 pointers: host1, (stored in hash), check */
+ ATF_CHECK_MSG(host2->refcnt == 3, "Invalid refcnt for host1");
+
+ host_dereference(&check, MDL); /* we don't need it now */
+
+ /* now we have 2 hosts in the hash */
+
+ /* verify that host1 is still in the hash and the we can find it */
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid1,
+ clientid1_len, MDL),
+ "Host1 was supposed to be in the uid_hash.");
+ ATF_CHECK_MSG(check, "Host1 was supposed to be in the uid_hash.");
+
+ /* Hey! That's not the host we were looking for! */
+ ATF_CHECK_MSG(check == host1, "Wrong host returned by host_hash_lookup");
+
+ /* 3 pointers: host1, (stored in hash), check */
+ ATF_CHECK_MSG(host1->refcnt == 3, "Invalid refcnt for host1");
+
+ host_dereference(&check, MDL); /* we don't need it now */
+
+
+ /**
+ * @todo check that there is actually two hosts in the hash.
+ * Use host_hash_for_each() for that.
+ */
+
+ /* === step 4: remove first host from the hash === */
+
+ /* delete host from hash */
+ host_hash_delete(host_uid_hash, clientid1, clientid1_len, MDL);
+
+ ATF_CHECK_MSG(host1->refcnt == 1, "Invalid refcnt for host1");
+ ATF_CHECK_MSG(host2->refcnt == 2, "Invalid refcnt for host2");
+
+ /* verify that host1 is no longer in the hash */
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid1,
+ clientid1_len, MDL) == 0,
+ "Host1 is not supposed to be in the uid_hash.");
+ ATF_CHECK_MSG(!check, "Host1 is not supposed to be in the uid_hash.");
+
+ /* host2 should be still there, though */
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid2,
+ clientid2_len, MDL),
+ "Host2 was supposed to still be in the uid_hash.");
+ host_dereference(&check, MDL);
+
+ /* === step 5: remove second host from the hash === */
+ host_hash_delete(host_uid_hash, clientid2, clientid2_len, MDL);
+
+ ATF_CHECK_MSG(host1->refcnt == 1, "Invalid refcnt for host1");
+ ATF_CHECK_MSG(host2->refcnt == 1, "Invalid refcnt for host2");
+
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid2,
+ clientid2_len, MDL) == 0,
+ "Host2 was not supposed to be in the uid_hash anymore.");
+
+ host_dereference(&host1, MDL);
+ host_dereference(&host2, MDL);
+
+ /*
+ * No easy way to check if the host object were actually released.
+ * We could run it in valgrind and check for memory leaks.
+ */
+
+#if defined (DEBUG_MEMORY_LEAKAGE) && defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ /* @todo: Should be called in cleanup */
+ free_everything ();
+#endif
+}
+
+/// @brief executes uid hash test for specified client-ids (3 hosts)
+///
+/// Creates three host structures, adds first host to the uid hash,
+/// then adds second host to the hash, then removes first host,
+/// then removed the second. Many checks are performed during all
+/// operations.
+///
+/// @param clientid1 client-id of the first host
+/// @param clientid1_len client-id1 length (may be 0 for strings)
+/// @param clientid2 client-id of the second host
+/// @param clientid2_len client-id2 length (may be 0 for strings)
+/// @param clientid3 client-id of the second host
+/// @param clientid3_len client-id2 length (may be 0 for strings)
+void lease_hash_test_3hosts(unsigned char clientid1[], size_t clientid1_len,
+ unsigned char clientid2[], size_t clientid2_len,
+ unsigned char clientid3[], size_t clientid3_len) {
+
+ printf("Checking hash operation for 3 hosts: clientid1-len=%lu"
+ " clientid2-len=%lu clientid3-len=%lu\n",
+ (unsigned long) clientid1_len, (unsigned long) clientid2_len,
+ (unsigned long) clientid3_len);
+
+ dhcp_db_objects_setup ();
+ dhcp_common_objects_setup ();
+
+ /* check that there is actually zero hosts in the hash */
+ /* @todo: host_hash_for_each() */
+
+ struct host_decl *host1 = 0, *host2 = 0, *host3 = 0;
+ struct host_decl *check = 0;
+
+ /* === step 1: allocate hosts === */
+ ATF_CHECK_MSG(host_allocate(&host1, MDL) == ISC_R_SUCCESS,
+ "Failed to allocate host");
+ ATF_CHECK_MSG(host_allocate(&host2, MDL) == ISC_R_SUCCESS,
+ "Failed to allocate host");
+ ATF_CHECK_MSG(host_allocate(&host3, MDL) == ISC_R_SUCCESS,
+ "Failed to allocate host");
+
+ ATF_CHECK_MSG(host_new_hash(&host_uid_hash, HOST_HASH_SIZE, MDL) != 0,
+ "Unable to create new hash");
+
+ ATF_CHECK_MSG(buffer_allocate(&host1->client_identifier.buffer,
+ clientid1_len, MDL) != 0,
+ "Can't allocate uid buffer for host1");
+ ATF_CHECK_MSG(buffer_allocate(&host2->client_identifier.buffer,
+ clientid2_len, MDL) != 0,
+ "Can't allocate uid buffer for host2");
+ ATF_CHECK_MSG(buffer_allocate(&host3->client_identifier.buffer,
+ clientid3_len, MDL) != 0,
+ "Can't allocate uid buffer for host3");
+
+ /* verify that our hosts are not in the hash yet */
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid1,
+ clientid1_len, MDL) == 0,
+ "Host1 is not supposed to be in the uid_hash.");
+
+ ATF_CHECK_MSG(!check, "Host1 is not supposed to be in the uid_hash.");
+
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid2,
+ clientid2_len, MDL) == 0,
+ "Host2 is not supposed to be in the uid_hash.");
+ ATF_CHECK_MSG(!check, "Host2 is not supposed to be in the uid_hash.");
+
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid3,
+ clientid3_len, MDL) == 0,
+ "Host3 is not supposed to be in the uid_hash.");
+ ATF_CHECK_MSG(!check, "Host3 is not supposed to be in the uid_hash.");
+
+ /* === step 2: add hosts to the hash === */
+ host_hash_add(host_uid_hash, clientid1, clientid1_len, host1, MDL);
+ host_hash_add(host_uid_hash, clientid2, clientid2_len, host2, MDL);
+ host_hash_add(host_uid_hash, clientid3, clientid3_len, host3, MDL);
+
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid1,
+ clientid1_len, MDL),
+ "Host1 was supposed to be in the uid_hash.");
+ /* Hey! That's not the host we were looking for! */
+ ATF_CHECK_MSG(check == host1, "Wrong host returned by host_hash_lookup");
+ host_dereference(&check, MDL); /* we don't need it now */
+
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid2,
+ clientid2_len, MDL),
+ "Host2 was supposed to be in the uid_hash.");
+ ATF_CHECK_MSG(check, "Host2 was supposed to be in the uid_hash.");
+ /* Hey! That's not the host we were looking for! */
+ ATF_CHECK_MSG(check == host2, "Wrong host returned by host_hash_lookup");
+ host_dereference(&check, MDL); /* we don't need it now */
+
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid3,
+ clientid3_len, MDL),
+ "Host3 was supposed to be in the uid_hash.");
+ ATF_CHECK_MSG(check, "Host3 was supposed to be in the uid_hash.");
+ /* Hey! That's not the host we were looking for! */
+ ATF_CHECK_MSG(check == host3, "Wrong host returned by host_hash_lookup");
+ host_dereference(&check, MDL); /* we don't need it now */
+
+ /* === step 4: remove first host from the hash === */
+
+ /* delete host from hash */
+ host_hash_delete(host_uid_hash, clientid1, clientid1_len, MDL);
+
+ /* verify that host1 is no longer in the hash */
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid1,
+ clientid1_len, MDL) == 0,
+ "Host1 is not supposed to be in the uid_hash.");
+ ATF_CHECK_MSG(!check, "Host1 is not supposed to be in the uid_hash.");
+
+ /* host2 and host3 should be still there, though */
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid2,
+ clientid2_len, MDL),
+ "Host2 was supposed to still be in the uid_hash.");
+ host_dereference(&check, MDL);
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid3,
+ clientid3_len, MDL),
+ "Host3 was supposed to still be in the uid_hash.");
+ host_dereference(&check, MDL);
+
+ /* === step 5: remove second host from the hash === */
+ host_hash_delete(host_uid_hash, clientid2, clientid2_len, MDL);
+
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid2,
+ clientid2_len, MDL) == 0,
+ "Host2 was not supposed to be in the uid_hash anymore.");
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid3,
+ clientid3_len, MDL),
+ "Host3 was supposed to still be in the uid_hash.");
+ host_dereference(&check, MDL);
+
+ /* === step 6: remove the last (third) host from the hash === */
+ host_hash_delete(host_uid_hash, clientid3, clientid3_len, MDL);
+
+ ATF_CHECK_MSG(host_hash_lookup(&check, host_uid_hash, clientid3,
+ clientid3_len, MDL) == 0,
+ "Host3 was not supposed to be in the uid_hash anymore.");
+ host_dereference(&check, MDL);
+
+
+ host_dereference(&host1, MDL);
+ host_dereference(&host2, MDL);
+ host_dereference(&host3, MDL);
+
+ /*
+ * No easy way to check if the host object were actually released.
+ * We could run it in valgrind and check for memory leaks.
+ */
+
+#if defined (DEBUG_MEMORY_LEAKAGE) && defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+ /* @todo: Should be called in cleanup */
+ free_everything ();
+#endif
+}
+
+ATF_TC(lease_hash_basic_2hosts);
+
+ATF_TC_HEAD(lease_hash_basic_2hosts, tc) {
+ atf_tc_set_md_var(tc, "descr", "Basic lease hash tests");
+ /*
+ * The following functions are tested:
+ * host_allocate(), host_new_hash(), buffer_allocate(), host_hash_lookup()
+ * host_hash_add(), host_hash_delete()
+ */
+}
+
+ATF_TC_BODY(lease_hash_basic_2hosts, tc) {
+
+ unsigned char clientid1[] = { 0x1, 0x2, 0x3 };
+ unsigned char clientid2[] = { 0xff, 0xfe };
+
+ lease_hash_test_2hosts(clientid1, sizeof(clientid1),
+ clientid2, sizeof(clientid2));
+}
+
+
+ATF_TC(lease_hash_string_2hosts);
+
+ATF_TC_HEAD(lease_hash_string_2hosts, tc) {
+ atf_tc_set_md_var(tc, "descr", "string-based lease hash tests");
+ /*
+ * The following functions are tested:
+ * host_allocate(), host_new_hash(), buffer_allocate(), host_hash_lookup()
+ * host_hash_add(), host_hash_delete()
+ */
+}
+
+ATF_TC_BODY(lease_hash_string_2hosts, tc) {
+
+ unsigned char clientid1[] = "Alice";
+ unsigned char clientid2[] = "Bob";
+
+ lease_hash_test_2hosts(clientid1, 0, clientid2, 0);
+}
+
+
+ATF_TC(lease_hash_negative1);
+
+ATF_TC_HEAD(lease_hash_negative1, tc) {
+ atf_tc_set_md_var(tc, "descr", "Negative tests for lease hash");
+}
+
+ATF_TC_BODY(lease_hash_negative1, tc) {
+
+ unsigned char clientid1[] = { 0x1 };
+ unsigned char clientid2[] = { 0x0 };
+
+ lease_hash_test_2hosts(clientid1, 0, clientid2, 1);
+}
+
+
+
+ATF_TC(lease_hash_string_3hosts);
+ATF_TC_HEAD(lease_hash_string_3hosts, tc) {
+ atf_tc_set_md_var(tc, "descr", "string-based lease hash tests");
+ /*
+ * The following functions are tested:
+ * host_allocate(), host_new_hash(), buffer_allocate(), host_hash_lookup()
+ * host_hash_add(), host_hash_delete()
+ */
+}
+ATF_TC_BODY(lease_hash_string_3hosts, tc) {
+
+ unsigned char clientid1[] = "Alice";
+ unsigned char clientid2[] = "Bob";
+ unsigned char clientid3[] = "Charlie";
+
+ lease_hash_test_3hosts(clientid1, 0, clientid2, 0, clientid3, 0);
+}
+
+
+ATF_TC(lease_hash_basic_3hosts);
+ATF_TC_HEAD(lease_hash_basic_3hosts, tc) {
+ atf_tc_set_md_var(tc, "descr", "Basic lease hash tests");
+ /*
+ * The following functions are tested:
+ * host_allocate(), host_new_hash(), buffer_allocate(), host_hash_lookup()
+ * host_hash_add(), host_hash_delete()
+ */
+}
+ATF_TC_BODY(lease_hash_basic_3hosts, tc) {
+
+ unsigned char clientid1[] = { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9 };
+ unsigned char clientid2[] = { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8 };
+ unsigned char clientid3[] = { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 };
+
+ lease_hash_test_3hosts(clientid1, sizeof(clientid1),
+ clientid2, sizeof(clientid2),
+ clientid3, sizeof(clientid3));
+}
+
+#if 0
+/* This test is disabled as we solved the issue by prohibiting
+ the code from using an improper client id earlier and restoring
+ the hash code to its previous state. As we may choose to
+ redo the hash code again this test hasn't been deleted.
+*/
+/* this test is a direct reproduction of 29851 issue */
+ATF_TC(uid_hash_rt29851);
+
+ATF_TC_HEAD(uid_hash_rt29851, tc) {
+ atf_tc_set_md_var(tc, "descr", "Uid hash tests");
+
+ /*
+ * this test should last less than millisecond. If its execution
+ * is longer than 3 second, we hit infinite loop.
+ */
+ atf_tc_set_md_var(tc, "timeout", "3");
+}
+
+ATF_TC_BODY(uid_hash_rt29851, tc) {
+
+ unsigned char clientid1[] = { 0x0 };
+ unsigned char clientid2[] = { 0x0 };
+ unsigned char clientid3[] = { 0x0 };
+
+ int clientid1_len = 1;
+ int clientid2_len = 1;
+ int clientid3_len = 0;
+
+ struct lease *lease1 = 0, *lease2 = 0, *lease3 = 0;
+
+ dhcp_db_objects_setup ();
+ dhcp_common_objects_setup ();
+
+ ATF_CHECK(lease_id_new_hash(&lease_uid_hash, LEASE_HASH_SIZE, MDL));
+
+ ATF_CHECK(lease_allocate (&lease1, MDL) == ISC_R_SUCCESS);
+ ATF_CHECK(lease_allocate (&lease2, MDL) == ISC_R_SUCCESS);
+ ATF_CHECK(lease_allocate (&lease3, MDL) == ISC_R_SUCCESS);
+
+ lease1->uid = clientid1;
+ lease2->uid = clientid2;
+ lease3->uid = clientid3;
+
+ lease1->uid_len = clientid1_len;
+ lease2->uid_len = clientid2_len;
+ lease3->uid_len = clientid3_len;
+
+ uid_hash_add(lease1);
+ /* uid_hash_delete(lease2); // not necessary for actual issue repro */
+ uid_hash_add(lease3);
+
+ /* lease2->uid_len = 0; // not necessary for actual issue repro */
+ /* uid_hash_delete(lease2); // not necessary for actual issue repro */
+ /* uid_hash_delete(lease3); // not necessary for actual issue repro */
+ uid_hash_delete(lease1);
+
+ /* lease2->uid_len = 1; // not necessary for actual issue repro */
+ uid_hash_add(lease1);
+ uid_hash_delete(lease2);
+}
+#endif
+
+ATF_TP_ADD_TCS(tp) {
+ ATF_TP_ADD_TC(tp, lease_hash_basic_2hosts);
+ ATF_TP_ADD_TC(tp, lease_hash_basic_3hosts);
+ ATF_TP_ADD_TC(tp, lease_hash_string_2hosts);
+ ATF_TP_ADD_TC(tp, lease_hash_string_3hosts);
+ ATF_TP_ADD_TC(tp, lease_hash_negative1);
+#if 0 /* see comment in function */
+ ATF_TP_ADD_TC(tp, uid_hash_rt29851);
+#endif
+ return (atf_no_error());
+}
diff --git a/server/tests/load_bal_unittest.c b/server/tests/load_bal_unittest.c
new file mode 100644
index 0000000..1500f34
--- /dev/null
+++ b/server/tests/load_bal_unittest.c
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or 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.
+ */
+
+#include <config.h>
+
+#include "dhcpd.h"
+
+#include <atf-c.h>
+
+/*
+ * Test the load balancing code.
+ *
+ * The two main variables are:
+ * packet => the "packet" being processed
+ * state => the "state" of the failover peer
+ * We only fill in the fields necessary for our testing
+ * packet->raw->secs => amount of time the client has been trying
+ * packet->raw->hlen => the length of the mac address of the client
+ * packet->raw->chaddr => the mac address of the client
+ * To simplify the tests the mac address will be only 1 byte long and
+ * not really matter. Instead the hba will be all 1s and the tests
+ * will use the primary/secondary flag to change the expected result.
+ *
+ * state->i_am => primary or secondary
+ * state->load_balance_max_secs => maxixum time for a client to be trying
+ * before the other peer responds
+ * set to 5 for these tests
+ * state->hba = array of hash buckets assigning the hash to primary or secondary
+ * set to all ones (all primary) for theses tests
+ */
+
+ATF_TC(load_balance);
+
+ATF_TC_HEAD(load_balance, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "This test case checks that "
+ "load balancing works.");
+}
+
+ATF_TC_BODY(load_balance, tc)
+{
+ struct packet packet;
+ struct dhcp_packet raw;
+ dhcp_failover_state_t pstate, sstate;
+ u_int8_t hba[256];
+
+ memset(&packet, 0, sizeof(struct packet));
+ memset(&raw, 0, sizeof(struct dhcp_packet));
+ packet.raw = &raw;
+ raw.hlen = 1;
+ raw.chaddr[0] = 14;
+
+ memset(hba, 0xFF, 256);
+
+ /* primary state */
+ memset(&pstate, 0, sizeof(dhcp_failover_state_t));
+ pstate.i_am = primary;
+ pstate.load_balance_max_secs = 5;
+ pstate.hba = hba;
+
+ /* secondary state, we can reuse the hba as it doesn't change */
+ memset(&sstate, 0, sizeof(dhcp_failover_state_t));
+ sstate.i_am = secondary;
+ sstate.load_balance_max_secs = 5;
+ sstate.hba = hba;
+
+ /* Basic check, primary accepted, secondary not */
+ raw.secs = htons(0);
+ if (load_balance_mine(&packet, &pstate) != 1) {
+ atf_tc_fail("ERROR: primary not accepted %s:%d", MDL);
+ }
+
+ if (load_balance_mine(&packet, &sstate) != 0) {
+ atf_tc_fail("ERROR: secondary accepted %s:%d", MDL);
+ }
+
+
+ /* Timeout not exceeded, primary accepted, secondary not */
+ raw.secs = htons(2);
+ if (load_balance_mine(&packet, &pstate) != 1) {
+ atf_tc_fail("ERROR: primary not accepted %s:%d", MDL);
+ }
+
+ if (load_balance_mine(&packet, &sstate) != 0) {
+ atf_tc_fail("ERROR: secondary accepted %s:%d", MDL);
+ }
+
+ /* Timeout exceeded, both accepted */
+ raw.secs = htons(6);
+ if (load_balance_mine(&packet, &pstate) != 1) {
+ atf_tc_fail("ERROR: primary not accepted %s:%d", MDL);
+ }
+
+ if (load_balance_mine(&packet, &sstate) != 1) {
+ atf_tc_fail("ERROR: secondary not accepted %s:%d", MDL);
+ }
+
+ /* Timeout exeeded with a large value, both accepted */
+ raw.secs = htons(257);
+ if (load_balance_mine(&packet, &pstate) != 1) {
+ atf_tc_fail("ERROR: primary not accepted %s:%d", MDL);
+ }
+
+ if (load_balance_mine(&packet, &sstate) != 1) {
+ atf_tc_fail("ERROR: secondary not accepted %s:%d", MDL);
+ }
+
+}
+
+ATF_TC(load_balance_swap);
+
+ATF_TC_HEAD(load_balance_swap, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "This test case checks that "
+ "load balancing works with byteswapping.");
+}
+
+ATF_TC_BODY(load_balance_swap, tc)
+{
+#if defined(SECS_BYTEORDER)
+ struct packet packet;
+ struct dhcp_packet raw;
+ dhcp_failover_state_t pstate, sstate;
+ u_int8_t hba[256];
+
+ memset(&packet, 0, sizeof(struct packet));
+ memset(&raw, 0, sizeof(struct dhcp_packet));
+ packet.raw = &raw;
+ raw.hlen = 1;
+ raw.chaddr[0] = 14;
+
+ memset(hba, 0xFF, 256);
+
+ /* primary state */
+ memset(&pstate, 0, sizeof(dhcp_failover_state_t));
+ pstate.i_am = primary;
+ pstate.load_balance_max_secs = 5;
+ pstate.hba = hba;
+
+ /* secondary state, we can reuse the hba as it doesn't change */
+ memset(&sstate, 0, sizeof(dhcp_failover_state_t));
+ sstate.i_am = secondary;
+ sstate.load_balance_max_secs = 5;
+ sstate.hba = hba;
+
+ /* Small byteswapped timeout, primary accepted, secondary not*/
+ raw.secs = htons(256);
+ if (load_balance_mine(&packet, &pstate) != 1) {
+ atf_tc_fail("ERROR: primary not accepted %s:%d", MDL);
+ }
+
+ if (load_balance_mine(&packet, &sstate) != 0) {
+ atf_tc_fail("ERROR: secondary accepted %s:%d", MDL);
+ }
+
+ /* Large byteswapped timeout, both accepted*/
+ raw.secs = htons(256 * 6);
+ if (load_balance_mine(&packet, &pstate) != 1) {
+ atf_tc_fail("ERROR: primary not accepted %s:%d", MDL);
+ }
+
+ if (load_balance_mine(&packet, &sstate) != 1) {
+ atf_tc_fail("ERROR: secondary not accepted %s:%d", MDL);
+ }
+
+#else
+ atf_tc_skip("SECS_BYTEORDER not defined");
+#endif
+}
+
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, load_balance);
+ ATF_TP_ADD_TC(tp, load_balance_swap);
+
+ return (atf_no_error());
+}
diff --git a/server/tests/mdb6_unittest.c b/server/tests/mdb6_unittest.c
new file mode 100644
index 0000000..56b4718
--- /dev/null
+++ b/server/tests/mdb6_unittest.c
@@ -0,0 +1,957 @@
+/*
+ * Copyright (C) 2007-2012 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <stdarg.h>
+#include "dhcpd.h"
+#include "omapip/omapip.h"
+#include "omapip/hash.h"
+#include <isc/md5.h>
+
+#include <atf-c.h>
+
+#include <stdlib.h>
+
+void build_prefix6(struct in6_addr *pref, const struct in6_addr *net_start_pref,
+ int pool_bits, int pref_bits,
+ const struct data_string *input);
+
+/*
+ * Basic iaaddr manipulation.
+ * Verify construction and referencing of an iaaddr.
+ */
+
+ATF_TC(iaaddr_basic);
+ATF_TC_HEAD(iaaddr_basic, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "This test case checks that basic "
+ "IAADDR manipulation is possible.");
+}
+ATF_TC_BODY(iaaddr_basic, tc)
+{
+ struct iasubopt *iaaddr;
+ struct iasubopt *iaaddr_copy;
+
+ /* set up dhcp globals */
+ dhcp_context_create();
+
+ /* and other common arguments */
+ iaaddr = NULL;
+ iaaddr_copy = NULL;
+
+ /* tests */
+ if (iasubopt_allocate(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_allocate() %s:%d", MDL);
+ }
+ if (iaaddr->state != FTS_FREE) {
+ atf_tc_fail("ERROR: bad state %s:%d", MDL);
+ }
+ if (iaaddr->heap_index != -1) {
+ atf_tc_fail("ERROR: bad heap_index %s:%d", MDL);
+ }
+ if (iasubopt_reference(&iaaddr_copy, iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_reference() %s:%d", MDL);
+ }
+ if (iasubopt_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_reference() %s:%d", MDL);
+ }
+ if (iasubopt_dereference(&iaaddr_copy, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_reference() %s:%d", MDL);
+ }
+}
+
+/*
+ * Basic iaaddr sanity checks.
+ * Verify that the iaaddr code does some sanity checking.
+ */
+
+ATF_TC(iaaddr_negative);
+ATF_TC_HEAD(iaaddr_negative, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "This test case checks that IAADDR "
+ "option code can handle various negative scenarios.");
+}
+ATF_TC_BODY(iaaddr_negative, tc)
+{
+ struct iasubopt *iaaddr;
+ struct iasubopt *iaaddr_copy;
+
+ /* set up dhcp globals */
+ dhcp_context_create();
+
+ /* tests */
+ /* bogus allocate arguments */
+ if (iasubopt_allocate(NULL, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: iasubopt_allocate() %s:%d", MDL);
+ }
+ iaaddr = (struct iasubopt *)1;
+ if (iasubopt_allocate(&iaaddr, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: iasubopt_allocate() %s:%d", MDL);
+ }
+
+ /* bogus reference arguments */
+ iaaddr = NULL;
+ if (iasubopt_allocate(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_allocate() %s:%d", MDL);
+ }
+ if (iasubopt_reference(NULL, iaaddr, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: iasubopt_reference() %s:%d", MDL);
+ }
+ iaaddr_copy = (struct iasubopt *)1;
+ if (iasubopt_reference(&iaaddr_copy, iaaddr,
+ MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: iasubopt_reference() %s:%d", MDL);
+ }
+ iaaddr_copy = NULL;
+ if (iasubopt_reference(&iaaddr_copy, NULL, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: iasubopt_reference() %s:%d", MDL);
+ }
+ if (iasubopt_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_reference() %s:%d", MDL);
+ }
+
+ /* bogus dereference arguments */
+ if (iasubopt_dereference(NULL, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: iasubopt_dereference() %s:%d", MDL);
+ }
+ iaaddr = NULL;
+ if (iasubopt_dereference(&iaaddr, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: iasubopt_dereference() %s:%d", MDL);
+ }
+}
+
+/*
+ * Basic ia_na manipulation.
+ */
+
+ATF_TC(ia_na_basic);
+ATF_TC_HEAD(ia_na_basic, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "This test case checks that IA_NA code can "
+ "handle various basic scenarios.");
+}
+ATF_TC_BODY(ia_na_basic, tc)
+{
+ uint32_t iaid;
+ struct ia_xx *ia_na;
+ struct ia_xx *ia_na_copy;
+ struct iasubopt *iaaddr;
+
+ /* set up dhcp globals */
+ dhcp_context_create();
+
+ /* and other common arguments */
+ iaid = 666;
+ ia_na = NULL;
+ ia_na_copy = NULL;
+ iaaddr = NULL;
+
+ /* tests */
+ if (ia_allocate(&ia_na, iaid, "TestDUID", 8, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_allocate() %s:%d", MDL);
+ }
+ if (memcmp(ia_na->iaid_duid.data, &iaid, sizeof(iaid)) != 0) {
+ atf_tc_fail("ERROR: bad IAID_DUID %s:%d", MDL);
+ }
+ if (memcmp(ia_na->iaid_duid.data+sizeof(iaid), "TestDUID", 8) != 0) {
+ atf_tc_fail("ERROR: bad IAID_DUID %s:%d", MDL);
+ }
+ if (ia_na->num_iasubopt != 0) {
+ atf_tc_fail("ERROR: bad num_iasubopt %s:%d", MDL);
+ }
+ if (ia_reference(&ia_na_copy, ia_na, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_reference() %s:%d", MDL);
+ }
+ if (iasubopt_allocate(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_allocate() %s:%d", MDL);
+ }
+ if (ia_add_iasubopt(ia_na, iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_add_iasubopt() %s:%d", MDL);
+ }
+ ia_remove_iasubopt(ia_na, iaaddr, MDL);
+ if (iasubopt_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_reference() %s:%d", MDL);
+ }
+ if (ia_dereference(&ia_na, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_dereference() %s:%d", MDL);
+ }
+ if (ia_dereference(&ia_na_copy, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_dereference() %s:%d", MDL);
+ }
+}
+
+/*
+ * Lots of iaaddr in our ia_na.
+ * Create many iaaddrs and attach them to an ia_na
+ * then clean up by removing them one at a time and
+ * all at once by dereferencing the ia_na.
+ */
+
+ATF_TC(ia_na_manyaddrs);
+ATF_TC_HEAD(ia_na_manyaddrs, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "This test case checks that IA_NA can "
+ "handle lots of addresses.");
+}
+ATF_TC_BODY(ia_na_manyaddrs, tc)
+{
+ uint32_t iaid;
+ struct ia_xx *ia_na;
+ struct iasubopt *iaaddr;
+ int i;
+
+ /* set up dhcp globals */
+ dhcp_context_create();
+
+ /* tests */
+ /* lots of iaaddr that we delete */
+ iaid = 666;
+ ia_na = NULL;
+ if (ia_allocate(&ia_na, iaid, "TestDUID", 8, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_allocate() %s:%d", MDL);
+ }
+ for (i=0; i<100; i++) {
+ iaaddr = NULL;
+ if (iasubopt_allocate(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_allocate() %s:%d", MDL);
+ }
+ if (ia_add_iasubopt(ia_na, iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_add_iasubopt() %s:%d", MDL);
+ }
+ if (iasubopt_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_reference() %s:%d", MDL);
+ }
+ }
+
+#if 0
+ for (i=0; i<100; i++) {
+ iaaddr = ia_na->iasubopt[random() % ia_na->num_iasubopt];
+ ia_remove_iasubopt(ia_na, iaaddr, MDL);
+ /* TODO: valgrind reports problem here: Invalid read of size 8
+ * Address 0x51e6258 is 56 bytes inside a block of size 88 free'd */
+ }
+#endif
+ if (ia_dereference(&ia_na, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_dereference() %s:%d", MDL);
+ }
+
+ /* lots of iaaddr, let dereference cleanup */
+ iaid = 666;
+ ia_na = NULL;
+ if (ia_allocate(&ia_na, iaid, "TestDUID", 8, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_allocate() %s:%d", MDL);
+ }
+ for (i=0; i<100; i++) {
+ iaaddr = NULL;
+ if (iasubopt_allocate(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_allocate() %s:%d", MDL);
+ }
+ if (ia_add_iasubopt(ia_na, iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_add_iasubopt() %s:%d", MDL);
+ }
+ if (iasubopt_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_reference() %s:%d", MDL);
+ }
+ }
+ if (ia_dereference(&ia_na, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_dereference() %s:%d", MDL);
+ }
+}
+
+/*
+ * Basic ia_na sanity checks.
+ * Verify that the ia_na code does some sanity checking.
+ */
+
+ATF_TC(ia_na_negative);
+ATF_TC_HEAD(ia_na_negative, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "This test case checks that IA_NA option "
+ "code can handle various negative scenarios.");
+}
+ATF_TC_BODY(ia_na_negative, tc)
+{
+ uint32_t iaid;
+ struct ia_xx *ia_na;
+ struct ia_xx *ia_na_copy;
+
+ /* set up dhcp globals */
+ dhcp_context_create();
+
+ /* tests */
+ /* bogus allocate arguments */
+ if (ia_allocate(NULL, 123, "", 0, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: ia_allocate() %s:%d", MDL);
+ }
+ ia_na = (struct ia_xx *)1;
+ if (ia_allocate(&ia_na, 456, "", 0, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: ia_allocate() %s:%d", MDL);
+ }
+
+ /* bogus reference arguments */
+ iaid = 666;
+ ia_na = NULL;
+ if (ia_allocate(&ia_na, iaid, "TestDUID", 8, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_allocate() %s:%d", MDL);
+ }
+ if (ia_reference(NULL, ia_na, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: ia_reference() %s:%d", MDL);
+ }
+ ia_na_copy = (struct ia_xx *)1;
+ if (ia_reference(&ia_na_copy, ia_na, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: ia_reference() %s:%d", MDL);
+ }
+ ia_na_copy = NULL;
+ if (ia_reference(&ia_na_copy, NULL, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: ia_reference() %s:%d", MDL);
+ }
+ if (ia_dereference(&ia_na, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_dereference() %s:%d", MDL);
+ }
+
+ /* bogus dereference arguments */
+ if (ia_dereference(NULL, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: ia_dereference() %s:%d", MDL);
+ }
+
+ /* bogus remove */
+ iaid = 666;
+ ia_na = NULL;
+ if (ia_allocate(&ia_na, iaid, "TestDUID", 8, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_allocate() %s:%d", MDL);
+ }
+ ia_remove_iasubopt(ia_na, NULL, MDL);
+ if (ia_dereference(&ia_na, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_dereference() %s:%d", MDL);
+ }
+}
+
+/*
+ * Basic ipv6_pool manipulation.
+ * Verify that basic pool operations work properly.
+ * The operations include creating a pool and creating,
+ * renewing, expiring, releasing and declining addresses.
+ */
+
+ATF_TC(ipv6_pool_basic);
+ATF_TC_HEAD(ipv6_pool_basic, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "This test case checks that IPv6 pool "
+ "manipulation is possible.");
+}
+ATF_TC_BODY(ipv6_pool_basic, tc)
+{
+ struct iasubopt *iaaddr;
+ struct in6_addr addr;
+ struct ipv6_pool *pool;
+ struct ipv6_pool *pool_copy;
+ char addr_buf[INET6_ADDRSTRLEN];
+ char *uid;
+ struct data_string ds;
+ struct iasubopt *expired_iaaddr;
+ unsigned int attempts;
+
+ /* set up dhcp globals */
+ dhcp_context_create();
+
+ /* and other common arguments */
+ inet_pton(AF_INET6, "1:2:3:4::", &addr);
+
+ uid = "client0";
+ memset(&ds, 0, sizeof(ds));
+ ds.len = strlen(uid);
+ if (!buffer_allocate(&ds.buffer, ds.len, MDL)) {
+ atf_tc_fail("Out of memory");
+ }
+ ds.data = ds.buffer->data;
+ memcpy((char *)ds.data, uid, ds.len);
+
+ /* tests */
+ /* allocate, reference */
+ pool = NULL;
+ if (ipv6_pool_allocate(&pool, D6O_IA_NA, &addr,
+ 64, 128, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ipv6_pool_allocate() %s:%d", MDL);
+ }
+ if (pool->num_active != 0) {
+ atf_tc_fail("ERROR: bad num_active %s:%d", MDL);
+ }
+ if (pool->bits != 64) {
+ atf_tc_fail("ERROR: bad bits %s:%d", MDL);
+ }
+ inet_ntop(AF_INET6, &pool->start_addr, addr_buf, sizeof(addr_buf));
+ if (strcmp(inet_ntop(AF_INET6, &pool->start_addr, addr_buf,
+ sizeof(addr_buf)), "1:2:3:4::") != 0) {
+ atf_tc_fail("ERROR: bad start_addr %s:%d", MDL);
+ }
+ pool_copy = NULL;
+ if (ipv6_pool_reference(&pool_copy, pool, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ipv6_pool_reference() %s:%d", MDL);
+ }
+
+ /* create_lease6, renew_lease6, expire_lease6 */
+ iaaddr = NULL;
+ if (create_lease6(pool, &iaaddr,
+ &attempts, &ds, 1) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: create_lease6() %s:%d", MDL);
+ }
+ if (pool->num_inactive != 1) {
+ atf_tc_fail("ERROR: bad num_inactive %s:%d", MDL);
+ }
+ if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: renew_lease6() %s:%d", MDL);
+ }
+ if (pool->num_active != 1) {
+ atf_tc_fail("ERROR: bad num_active %s:%d", MDL);
+ }
+ expired_iaaddr = NULL;
+ if (expire_lease6(&expired_iaaddr, pool, 0) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: expire_lease6() %s:%d", MDL);
+ }
+ if (expired_iaaddr != NULL) {
+ atf_tc_fail("ERROR: should not have expired a lease %s:%d", MDL);
+ }
+ if (pool->num_active != 1) {
+ atf_tc_fail("ERROR: bad num_active %s:%d", MDL);
+ }
+ if (expire_lease6(&expired_iaaddr, pool, 1000) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: expire_lease6() %s:%d", MDL);
+ }
+ if (expired_iaaddr == NULL) {
+ atf_tc_fail("ERROR: should have expired a lease %s:%d", MDL);
+ }
+ if (iasubopt_dereference(&expired_iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_dereference() %s:%d", MDL);
+ }
+ if (pool->num_active != 0) {
+ atf_tc_fail("ERROR: bad num_active %s:%d", MDL);
+ }
+ if (iasubopt_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_dereference() %s:%d", MDL);
+ }
+
+ /* release_lease6, decline_lease6 */
+ if (create_lease6(pool, &iaaddr, &attempts,
+ &ds, 1) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: create_lease6() %s:%d", MDL);
+ }
+ if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: renew_lease6() %s:%d", MDL);
+ }
+ if (pool->num_active != 1) {
+ atf_tc_fail("ERROR: bad num_active %s:%d", MDL);
+ }
+ if (release_lease6(pool, iaaddr) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: decline_lease6() %s:%d", MDL);
+ }
+ if (pool->num_active != 0) {
+ atf_tc_fail("ERROR: bad num_active %s:%d", MDL);
+ }
+ if (iasubopt_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_dereference() %s:%d", MDL);
+ }
+ if (create_lease6(pool, &iaaddr, &attempts,
+ &ds, 1) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: create_lease6() %s:%d", MDL);
+ }
+ if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: renew_lease6() %s:%d", MDL);
+ }
+ if (pool->num_active != 1) {
+ atf_tc_fail("ERROR: bad num_active %s:%d", MDL);
+ }
+ if (decline_lease6(pool, iaaddr) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: decline_lease6() %s:%d", MDL);
+ }
+ if (pool->num_active != 1) {
+ atf_tc_fail("ERROR: bad num_active %s:%d", MDL);
+ }
+ if (iasubopt_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_dereference() %s:%d", MDL);
+ }
+
+ /* dereference */
+ if (ipv6_pool_dereference(&pool, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ipv6_pool_reference() %s:%d", MDL);
+ }
+ if (ipv6_pool_dereference(&pool_copy, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ipv6_pool_reference() %s:%d", MDL);
+ }
+}
+
+/*
+ * Basic ipv6_pool sanity checks.
+ * Verify that the ipv6_pool code does some sanity checking.
+ */
+
+ATF_TC(ipv6_pool_negative);
+ATF_TC_HEAD(ipv6_pool_negative, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "This test case checks that IPv6 pool "
+ "can handle negative cases.");
+}
+ATF_TC_BODY(ipv6_pool_negative, tc)
+{
+ struct in6_addr addr;
+ struct ipv6_pool *pool;
+ struct ipv6_pool *pool_copy;
+
+ /* set up dhcp globals */
+ dhcp_context_create();
+
+ /* and other common arguments */
+ inet_pton(AF_INET6, "1:2:3:4::", &addr);
+
+ /* tests */
+ if (ipv6_pool_allocate(NULL, D6O_IA_NA, &addr,
+ 64, 128, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: ipv6_pool_allocate() %s:%d", MDL);
+ }
+ pool = (struct ipv6_pool *)1;
+ if (ipv6_pool_allocate(&pool, D6O_IA_NA, &addr,
+ 64, 128, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: ipv6_pool_allocate() %s:%d", MDL);
+ }
+ if (ipv6_pool_reference(NULL, pool, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: ipv6_pool_reference() %s:%d", MDL);
+ }
+ pool_copy = (struct ipv6_pool *)1;
+ if (ipv6_pool_reference(&pool_copy, pool, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: ipv6_pool_reference() %s:%d", MDL);
+ }
+ pool_copy = NULL;
+ if (ipv6_pool_reference(&pool_copy, NULL, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: ipv6_pool_reference() %s:%d", MDL);
+ }
+ if (ipv6_pool_dereference(NULL, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: ipv6_pool_dereference() %s:%d", MDL);
+ }
+ if (ipv6_pool_dereference(&pool_copy, MDL) != DHCP_R_INVALIDARG) {
+ atf_tc_fail("ERROR: ipv6_pool_dereference() %s:%d", MDL);
+ }
+}
+
+
+/*
+ * Order of expiration.
+ * Add several addresses to a pool and check that
+ * they expire in the proper order.
+ */
+
+ATF_TC(expire_order);
+ATF_TC_HEAD(expire_order, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "This test case checks that order "
+ "of lease expiration is handled properly.");
+}
+ATF_TC_BODY(expire_order, tc)
+{
+ struct iasubopt *iaaddr;
+ struct ipv6_pool *pool;
+ struct in6_addr addr;
+ int i;
+ char *uid;
+ struct data_string ds;
+ struct iasubopt *expired_iaaddr;
+ unsigned int attempts;
+
+ /* set up dhcp globals */
+ dhcp_context_create();
+
+ /* and other common arguments */
+ inet_pton(AF_INET6, "1:2:3:4::", &addr);
+
+ uid = "client0";
+ memset(&ds, 0, sizeof(ds));
+ ds.len = strlen(uid);
+ if (!buffer_allocate(&ds.buffer, ds.len, MDL)) {
+ atf_tc_fail("Out of memory");
+ }
+ ds.data = ds.buffer->data;
+ memcpy((char *)ds.data, uid, ds.len);
+
+ iaaddr = NULL;
+ expired_iaaddr = NULL;
+
+ /* tests */
+ pool = NULL;
+ if (ipv6_pool_allocate(&pool, D6O_IA_NA, &addr,
+ 64, 128, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ipv6_pool_allocate() %s:%d", MDL);
+ }
+
+ for (i=10; i<100; i+=10) {
+ if (create_lease6(pool, &iaaddr, &attempts,
+ &ds, i) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: create_lease6() %s:%d", MDL);
+ }
+ if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: renew_lease6() %s:%d", MDL);
+ }
+ if (iasubopt_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_dereference() %s:%d", MDL);
+ }
+ if (pool->num_active != (i / 10)) {
+ atf_tc_fail("ERROR: bad num_active %s:%d", MDL);
+ }
+ }
+ if (pool->num_active != 9) {
+ atf_tc_fail("ERROR: bad num_active %s:%d", MDL);
+ }
+
+ for (i=10; i<100; i+=10) {
+ if (expire_lease6(&expired_iaaddr,
+ pool, 1000) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: expire_lease6() %s:%d", MDL);
+ }
+ if (expired_iaaddr == NULL) {
+ atf_tc_fail("ERROR: should have expired a lease %s:%d",
+ MDL);
+ }
+ if (pool->num_active != (9 - (i / 10))) {
+ atf_tc_fail("ERROR: bad num_active %s:%d", MDL);
+ }
+ if (expired_iaaddr->hard_lifetime_end_time != i) {
+ atf_tc_fail("ERROR: bad hard_lifetime_end_time %s:%d",
+ MDL);
+ }
+ if (iasubopt_dereference(&expired_iaaddr, MDL) !=
+ ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_dereference() %s:%d", MDL);
+ }
+ }
+ if (pool->num_active != 0) {
+ atf_tc_fail("ERROR: bad num_active %s:%d", MDL);
+ }
+ expired_iaaddr = NULL;
+ if (expire_lease6(&expired_iaaddr, pool, 1000) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: expire_lease6() %s:%d", MDL);
+ }
+ if (ipv6_pool_dereference(&pool, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ipv6_pool_dereference() %s:%d", MDL);
+ }
+}
+
+/*
+ * Reduce the expiration period of a lease.
+ * This test reduces the expiration period of
+ * a lease to verify we process reductions
+ * properly.
+ */
+ATF_TC(expire_order_reduce);
+ATF_TC_HEAD(expire_order_reduce, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "This test case checks that reducing "
+ "the expiration time of a lease works properly.");
+}
+ATF_TC_BODY(expire_order_reduce, tc)
+{
+ struct iasubopt *iaaddr1, *iaaddr2;
+ struct ipv6_pool *pool;
+ struct in6_addr addr;
+ char *uid;
+ struct data_string ds;
+ struct iasubopt *expired_iaaddr;
+ unsigned int attempts;
+
+ /* set up dhcp globals */
+ dhcp_context_create();
+
+ /* and other common arguments */
+ inet_pton(AF_INET6, "1:2:3:4::", &addr);
+
+ uid = "client0";
+ memset(&ds, 0, sizeof(ds));
+ ds.len = strlen(uid);
+ if (!buffer_allocate(&ds.buffer, ds.len, MDL)) {
+ atf_tc_fail("Out of memory");
+ }
+ ds.data = ds.buffer->data;
+ memcpy((char *)ds.data, uid, ds.len);
+
+ pool = NULL;
+ iaaddr1 = NULL;
+ iaaddr2 = NULL;
+ expired_iaaddr = NULL;
+
+ /*
+ * Add two leases iaaddr1 with expire time of 200
+ * and iaaddr2 with expire time of 300. Then update
+ * iaaddr2 to expire in 100 instead. This should cause
+ * iaaddr2 to move with the hash list.
+ */
+ /* create pool and add iaaddr1 and iaaddr2 */
+ if (ipv6_pool_allocate(&pool, D6O_IA_NA, &addr,
+ 64, 128, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ipv6_pool_allocate() %s:%d", MDL);
+ }
+ if (create_lease6(pool, &iaaddr1, &attempts, &ds, 200) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: create_lease6() %s:%d", MDL);
+ }
+ if (renew_lease6(pool, iaaddr1) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: renew_lease6() %s:%d", MDL);
+ }
+ if (create_lease6(pool, &iaaddr2, &attempts, &ds, 300) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: create_lease6() %s:%d", MDL);
+ }
+ if (renew_lease6(pool, iaaddr2) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: renew_lease6() %s:%d", MDL);
+ }
+
+ /* verify pool */
+ if (pool->num_active != 2) {
+ atf_tc_fail("ERROR: bad num_active %s:%d", MDL);
+ }
+
+ /* reduce lease for iaaddr2 */
+ iaaddr2->soft_lifetime_end_time = 100;
+ if (renew_lease6(pool, iaaddr2) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: renew_lease6() %s:%d", MDL);
+ }
+
+ /* expire a lease, it should be iaaddr2 with an expire time of 100 */
+ if (expire_lease6(&expired_iaaddr, pool, 1000) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: expire_lease6() %s:%d", MDL);
+ }
+ if (expired_iaaddr == NULL) {
+ atf_tc_fail("ERROR: should have expired a lease %s:%d", MDL);
+ }
+ if (expired_iaaddr != iaaddr2) {
+ atf_tc_fail("Error: incorrect lease expired %s:%d", MDL);
+ }
+ if (expired_iaaddr->hard_lifetime_end_time != 100) {
+ atf_tc_fail("ERROR: bad hard_lifetime_end_time %s:%d", MDL);
+ }
+ if (iasubopt_dereference(&expired_iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_dereference() %s:%d", MDL);
+ }
+
+ /* expire a lease, it should be iaaddr1 with an expire time of 200 */
+ if (expire_lease6(&expired_iaaddr, pool, 1000) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: expire_lease6() %s:%d", MDL);
+ }
+ if (expired_iaaddr == NULL) {
+ atf_tc_fail("ERROR: should have expired a lease %s:%d", MDL);
+ }
+ if (expired_iaaddr != iaaddr1) {
+ atf_tc_fail("Error: incorrect lease expired %s:%d", MDL);
+ }
+ if (expired_iaaddr->hard_lifetime_end_time != 200) {
+ atf_tc_fail("ERROR: bad hard_lifetime_end_time %s:%d", MDL);
+ }
+ if (iasubopt_dereference(&expired_iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_dereference() %s:%d", MDL);
+ }
+
+ /* cleanup */
+ if (iasubopt_dereference(&iaaddr1, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_dereference() %s:%d", MDL);
+ }
+ if (iasubopt_dereference(&iaaddr2, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_dereference() %s:%d", MDL);
+ }
+ if (ipv6_pool_dereference(&pool, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ipv6_pool_dereference() %s:%d", MDL);
+ }
+}
+
+/*
+ * Small pool.
+ * check that a small pool behaves properly.
+ */
+
+ATF_TC(small_pool);
+ATF_TC_HEAD(small_pool, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "This test case checks that small pool "
+ "is handled properly.");
+}
+ATF_TC_BODY(small_pool, tc)
+{
+ struct in6_addr addr;
+ struct ipv6_pool *pool;
+ struct iasubopt *iaaddr;
+ char *uid;
+ struct data_string ds;
+ unsigned int attempts;
+
+ /* set up dhcp globals */
+ dhcp_context_create();
+
+ /* and other common arguments */
+ inet_pton(AF_INET6, "1:2:3:4::", &addr);
+ addr.s6_addr[14] = 0x81;
+
+ uid = "client0";
+ memset(&ds, 0, sizeof(ds));
+ ds.len = strlen(uid);
+ if (!buffer_allocate(&ds.buffer, ds.len, MDL)) {
+ atf_tc_fail("Out of memory");
+ }
+ ds.data = ds.buffer->data;
+ memcpy((char *)ds.data, uid, ds.len);
+
+ pool = NULL;
+ iaaddr = NULL;
+
+ /* tests */
+ if (ipv6_pool_allocate(&pool, D6O_IA_NA, &addr,
+ 127, 128, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ipv6_pool_allocate() %s:%d", MDL);
+ }
+
+ if (create_lease6(pool, &iaaddr, &attempts,
+ &ds, 42) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: create_lease6() %s:%d", MDL);
+ }
+ if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: renew_lease6() %s:%d", MDL);
+ }
+ if (iasubopt_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_dereference() %s:%d", MDL);
+ }
+ if (create_lease6(pool, &iaaddr, &attempts,
+ &ds, 11) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: create_lease6() %s:%d", MDL);
+ }
+ if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: renew_lease6() %s:%d", MDL);
+ }
+ if (iasubopt_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: iasubopt_dereference() %s:%d", MDL);
+ }
+ if (create_lease6(pool, &iaaddr, &attempts,
+ &ds, 11) != ISC_R_NORESOURCES) {
+ atf_tc_fail("ERROR: create_lease6() %s:%d", MDL);
+ }
+ if (ipv6_pool_dereference(&pool, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ipv6_pool_dereference() %s:%d", MDL);
+ }
+}
+
+/*
+ * Address to pool mapping.
+ * Verify that we find the proper pool for an address
+ * or don't find a pool if we don't have one for the given
+ * address.
+ */
+ATF_TC(many_pools);
+ATF_TC_HEAD(many_pools, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "This test case checks that functions "
+ "across all pools are working correctly.");
+}
+ATF_TC_BODY(many_pools, tc)
+{
+ struct in6_addr addr;
+ struct ipv6_pool *pool;
+
+ /* set up dhcp globals */
+ dhcp_context_create();
+
+ /* and other common arguments */
+ inet_pton(AF_INET6, "1:2:3:4::", &addr);
+
+ /* tests */
+
+ pool = NULL;
+ if (ipv6_pool_allocate(&pool, D6O_IA_NA, &addr,
+ 64, 128, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ipv6_pool_allocate() %s:%d", MDL);
+ }
+ if (add_ipv6_pool(pool) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: add_ipv6_pool() %s:%d", MDL);
+ }
+ if (ipv6_pool_dereference(&pool, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ipv6_pool_dereference() %s:%d", MDL);
+ }
+ pool = NULL;
+ if (find_ipv6_pool(&pool, D6O_IA_NA, &addr) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: find_ipv6_pool() %s:%d", MDL);
+ }
+ if (ipv6_pool_dereference(&pool, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ipv6_pool_dereference() %s:%d", MDL);
+ }
+ inet_pton(AF_INET6, "1:2:3:4:ffff:ffff:ffff:ffff", &addr);
+ pool = NULL;
+ if (find_ipv6_pool(&pool, D6O_IA_NA, &addr) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: find_ipv6_pool() %s:%d", MDL);
+ }
+ if (ipv6_pool_dereference(&pool, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ipv6_pool_dereference() %s:%d", MDL);
+ }
+ inet_pton(AF_INET6, "1:2:3:5::", &addr);
+ pool = NULL;
+ if (find_ipv6_pool(&pool, D6O_IA_NA, &addr) != ISC_R_NOTFOUND) {
+ atf_tc_fail("ERROR: find_ipv6_pool() %s:%d", MDL);
+ }
+ inet_pton(AF_INET6, "1:2:3:3:ffff:ffff:ffff:ffff", &addr);
+ pool = NULL;
+ if (find_ipv6_pool(&pool, D6O_IA_NA, &addr) != ISC_R_NOTFOUND) {
+ atf_tc_fail("ERROR: find_ipv6_pool() %s:%d", MDL);
+ }
+
+/* iaid = 666;
+ ia_na = NULL;
+ if (ia_allocate(&ia_na, iaid, "TestDUID", 8, MDL) != ISC_R_SUCCESS) {
+ atf_tc_fail("ERROR: ia_allocate() %s:%d", MDL);
+ }*/
+
+ {
+ struct in6_addr r;
+ struct data_string ds;
+ u_char data[16];
+ char buf[64];
+ int i, j;
+
+ memset(&ds, 0, sizeof(ds));
+ memset(data, 0xaa, sizeof(data));
+ ds.len = 16;
+ ds.data = data;
+
+ inet_pton(AF_INET6, "3ffe:501:ffff:100::", &addr);
+ for (i = 32; i < 42; i++)
+ for (j = i + 1; j < 49; j++) {
+ memset(&r, 0, sizeof(r));
+ memset(buf, 0, 64);
+ build_prefix6(&r, &addr, i, j, &ds);
+ inet_ntop(AF_INET6, &r, buf, 64);
+ printf("%d,%d-> %s/%d\n", i, j, buf, j);
+ }
+ }
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, iaaddr_basic);
+ ATF_TP_ADD_TC(tp, iaaddr_negative);
+ ATF_TP_ADD_TC(tp, ia_na_basic);
+ ATF_TP_ADD_TC(tp, ia_na_manyaddrs);
+ ATF_TP_ADD_TC(tp, ia_na_negative);
+ ATF_TP_ADD_TC(tp, ipv6_pool_basic);
+ ATF_TP_ADD_TC(tp, ipv6_pool_negative);
+ ATF_TP_ADD_TC(tp, expire_order);
+ ATF_TP_ADD_TC(tp, expire_order_reduce);
+ ATF_TP_ADD_TC(tp, small_pool);
+ ATF_TP_ADD_TC(tp, many_pools);
+
+ return (atf_no_error());
+}
diff --git a/server/tests/simple_unittest.c b/server/tests/simple_unittest.c
new file mode 100644
index 0000000..e6d04b9
--- /dev/null
+++ b/server/tests/simple_unittest.c
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or 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.
+ */
+
+#include <config.h>
+#include <atf-c.h>
+
+/* That is an example ATF test case, tailored to ISC DHCP sources.
+ For detailed description with examples, see man 3 atf-c-api. */
+
+/* this macro defines a name of a test case. Typical test case constists
+ of an initial test declaration (ATF_TC()) followed by 3 phases:
+
+ - Initialization: ATF_TC_HEAD()
+ - Main body: ATF_TC_BODY()
+ - Cleanup: ATF_TC_CLEANUP()
+
+ In many cases initialization or cleanup are not needed. Use
+ ATF_TC_WITHOUT_HEAD() or ATF_TC_WITH_CLEANUP() as needed. */
+ATF_TC(simple_test_case);
+
+
+ATF_TC_HEAD(simple_test_case, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "This test case is a simple DHCP test.");
+}
+ATF_TC_BODY(simple_test_case, tc)
+{
+ int condition = 1;
+ int this_is_linux = 1;
+ /* Failing condition will fail the test, but the code
+ itself will continue */
+ ATF_CHECK( 2 > 1 );
+
+ /* assert style check. Test will abort if the condition is not met. */
+ ATF_REQUIRE( 5 > 4 );
+
+ ATF_CHECK_EQ(4, 2 + 2); /* Non-fatal test. */
+ ATF_REQUIRE_EQ(4, 2 + 2); /* Fatal test. */
+
+ /* tests can also explicitly report test result */
+ if (!condition) {
+ atf_tc_fail("Condition not met!"); /* Explicit failure. */
+ }
+
+ if (!this_is_linux) {
+ atf_tc_skip("Skipping test. This Linux-only test.");
+ }
+
+ if (condition && this_is_linux) {
+ /* no extra comments for pass needed. It just passed. */
+ atf_tc_pass();
+ }
+
+}
+
+/* This macro defines main() method that will call specified
+ test cases. tp and simple_test_case names can be whatever you want
+ as long as it is a valid variable identifier. */
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, simple_test_case);
+
+ return (atf_no_error());
+}