diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/Makefile.am | 22 | ||||
-rw-r--r-- | server/Makefile.in | 1116 | ||||
-rw-r--r-- | server/bootp.c | 442 | ||||
-rw-r--r-- | server/class.c | 303 | ||||
-rw-r--r-- | server/confpars.c | 5606 | ||||
-rw-r--r-- | server/db.c | 1195 | ||||
-rw-r--r-- | server/ddns.c | 1957 | ||||
-rw-r--r-- | server/dhcp.c | 4665 | ||||
-rw-r--r-- | server/dhcpd.8 | 805 | ||||
-rw-r--r-- | server/dhcpd.c | 1542 | ||||
-rw-r--r-- | server/dhcpd.conf.5 | 3026 | ||||
-rw-r--r-- | server/dhcpd.conf.example | 104 | ||||
-rw-r--r-- | server/dhcpd.leases.5 | 378 | ||||
-rw-r--r-- | server/dhcpleasequery.c | 1259 | ||||
-rw-r--r-- | server/dhcpv6.c | 6235 | ||||
-rw-r--r-- | server/failover.c | 6476 | ||||
-rw-r--r-- | server/ldap.c | 2004 | ||||
-rw-r--r-- | server/ldap_casa.c | 159 | ||||
-rw-r--r-- | server/mdb.c | 3047 | ||||
-rw-r--r-- | server/mdb6.c | 2147 | ||||
-rw-r--r-- | server/omapi.c | 2565 | ||||
-rw-r--r-- | server/salloc.c | 251 | ||||
-rw-r--r-- | server/stables.c | 504 | ||||
-rw-r--r-- | server/tests/Atffile | 5 | ||||
-rw-r--r-- | server/tests/Makefile.am | 53 | ||||
-rw-r--r-- | server/tests/Makefile.in | 999 | ||||
-rw-r--r-- | server/tests/hash_unittest.c | 607 | ||||
-rw-r--r-- | server/tests/load_bal_unittest.c | 191 | ||||
-rw-r--r-- | server/tests/mdb6_unittest.c | 957 | ||||
-rw-r--r-- | server/tests/simple_unittest.c | 77 |
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 (<, 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, <->ip_addr.iabuf, 4); + nak_lease(packet, &cip); + free_lease_state (state, MDL); + lease_dereference (<, 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, <->ip_addr.iabuf, 4); + nak_lease(packet, &cip); + free_lease_state (state, MDL); + lease_dereference (<, 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 (< -> host, host, MDL); + host_dereference (&host, MDL); + } + if (lease -> subnet) + subnet_reference (< -> subnet, lease -> subnet, MDL); + if (lease -> billing_class) + class_reference (< -> 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 (< -> scope, lease -> scope, MDL); + binding_scope_dereference (&lease -> scope, MDL); + } + if (lease -> agent_options) + option_chain_head_reference (< -> 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 (< -> agent_options, MDL); + option_chain_head_reference + (< -> 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 (< -> 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, < -> 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, + < -> 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 (<, MDL); + return; + } + } + lease_dereference (<, 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 (<, 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(<->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 (<, 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 (<, 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 (<, lp -> ip_addr, MDL)) { + if (lt -> pool) { + parse_warn (cfile, + "lease %s is declared twice!", + piaddr (lp -> ip_addr)); + } else + pool_reference (< -> pool, pool, MDL); + lease_dereference (<, 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 (<, 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 (<, 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 (<, MDL); + return 0; + } + strcpy (lt -> client_hostname, lease -> client_hostname); + } + if (lease -> scope) + binding_scope_reference (< -> scope, lease -> scope, MDL); + if (lease -> agent_options) + option_chain_head_reference (< -> agent_options, + lease -> agent_options, MDL); + host_reference (< -> host, lease -> host, file, line); + subnet_reference (< -> subnet, lease -> subnet, file, line); + pool_reference (< -> pool, lease -> pool, file, line); + class_reference (< -> billing_class, + lease -> billing_class, file, line); + lt -> hardware_addr = lease -> hardware_addr; + if (lease -> on_expiry) + executable_statement_reference (< -> on_expiry, + lease -> on_expiry, + file, line); + if (lease -> on_commit) + executable_statement_reference (< -> on_commit, + lease -> on_commit, + file, line); + if (lease -> on_release) + executable_statement_reference (< -> 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(<, 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 (<, lease, MDL)) + return; + + if (lt->scope) + binding_scope_dereference(<->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 (<, 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 (<, 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 (<, 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()); +} |