summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@baserock.org>2015-03-02 19:04:49 +0000
committer <>2015-05-08 15:30:59 +0000
commitf800382616186a5d30e28d8b2c51e97a9a8360f2 (patch)
tree0d5270190548a37223d14b54383ce8a3d3af5302 /client
downloadisc-dhcp-tarball-f800382616186a5d30e28d8b2c51e97a9a8360f2.tar.gz
Imported from /home/lorry/working-area/delta_isc-dhcp-tarball/dhcp-4.2.8.tar.gz.HEADdhcp-4.2.8master
Diffstat (limited to 'client')
-rw-r--r--client/Makefile.am18
-rw-r--r--client/Makefile.in735
-rw-r--r--client/clparse.c2235
-rw-r--r--client/dhc6.c5174
-rw-r--r--client/dhclient-script.8233
-rw-r--r--client/dhclient.8479
-rw-r--r--client/dhclient.c4401
-rw-r--r--client/dhclient.conf.5734
-rw-r--r--client/dhclient.conf.example36
-rw-r--r--client/dhclient.leases.551
-rwxr-xr-xclient/scripts/bsdos324
-rwxr-xr-xclient/scripts/freebsd392
-rwxr-xr-xclient/scripts/linux316
-rwxr-xr-xclient/scripts/macos207
-rwxr-xr-xclient/scripts/netbsd324
-rw-r--r--client/scripts/nextstep61
-rw-r--r--client/scripts/openbsd318
-rwxr-xr-xclient/scripts/openwrt285
-rwxr-xr-xclient/scripts/solaris203
19 files changed, 16526 insertions, 0 deletions
diff --git a/client/Makefile.am b/client/Makefile.am
new file mode 100644
index 0000000..39ddf6f
--- /dev/null
+++ b/client/Makefile.am
@@ -0,0 +1,18 @@
+dist_sysconf_DATA = dhclient.conf.example
+sbin_PROGRAMS = dhclient
+dhclient_SOURCES = clparse.c dhclient.c dhc6.c \
+ scripts/bsdos scripts/freebsd scripts/linux scripts/macos \
+ scripts/netbsd scripts/nextstep scripts/openbsd \
+ scripts/solaris scripts/openwrt
+dhclient_LDADD = ../common/libdhcp.a ../omapip/libomapi.a \
+ ../bind/lib/libdns.a ../bind/lib/libisc.a
+man_MANS = dhclient.8 dhclient-script.8 dhclient.conf.5 dhclient.leases.5
+EXTRA_DIST = $(man_MANS)
+
+dhclient.o: dhclient.c
+ $(COMPILE) -DCLIENT_PATH='"PATH=$(sbindir):/sbin:/bin:/usr/sbin:/usr/bin"' \
+ -DLOCALSTATEDIR='"$(localstatedir)"' -c dhclient.c
+
+dhc6.o: dhc6.c
+ $(COMPILE) -DCLIENT_PATH='"PATH=$(sbindir):/sbin:/bin:/usr/sbin:/usr/bin"' \
+ -DLOCALSTATEDIR='"$(localstatedir)"' -c dhc6.c
diff --git a/client/Makefile.in b/client/Makefile.in
new file mode 100644
index 0000000..ee11e17
--- /dev/null
+++ b/client/Makefile.in
@@ -0,0 +1,735 @@
+# 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 = dhclient$(EXEEXT)
+subdir = client
+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_dhclient_OBJECTS = clparse.$(OBJEXT) dhclient.$(OBJEXT) \
+ dhc6.$(OBJEXT)
+dhclient_OBJECTS = $(am_dhclient_OBJECTS)
+dhclient_DEPENDENCIES = ../common/libdhcp.a ../omapip/libomapi.a \
+ ../bind/lib/libdns.a ../bind/lib/libisc.a
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/includes
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+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 = $(dhclient_SOURCES)
+DIST_SOURCES = $(dhclient_SOURCES)
+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)
+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
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+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@
+dist_sysconf_DATA = dhclient.conf.example
+dhclient_SOURCES = clparse.c dhclient.c dhc6.c \
+ scripts/bsdos scripts/freebsd scripts/linux scripts/macos \
+ scripts/netbsd scripts/nextstep scripts/openbsd \
+ scripts/solaris scripts/openwrt
+
+dhclient_LDADD = ../common/libdhcp.a ../omapip/libomapi.a \
+ ../bind/lib/libdns.a ../bind/lib/libisc.a
+
+man_MANS = dhclient.8 dhclient-script.8 dhclient.conf.5 dhclient.leases.5
+EXTRA_DIST = $(man_MANS)
+all: all-am
+
+.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 client/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign client/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)
+
+dhclient$(EXEEXT): $(dhclient_OBJECTS) $(dhclient_DEPENDENCIES) $(EXTRA_dhclient_DEPENDENCIES)
+ @rm -f dhclient$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dhclient_OBJECTS) $(dhclient_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/clparse.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhc6.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhclient.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+install-man5: $(man_MANS)
+ @$(NORMAL_INSTALL)
+ @list1=''; \
+ list2='$(man_MANS)'; \
+ test -n "$(man5dir)" \
+ && test -n "`echo $$list1$$list2`" \
+ || exit 0; \
+ echo " $(MKDIR_P) '$(DESTDIR)$(man5dir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(man5dir)" || exit 1; \
+ { for i in $$list1; do echo "$$i"; done; \
+ if test -n "$$list2"; then \
+ for i in $$list2; do echo "$$i"; done \
+ | sed -n '/\.5[a-z]*$$/p'; \
+ fi; \
+ } | while read p; do \
+ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; echo "$$p"; \
+ done | \
+ sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^5][0-9a-z]*$$,5,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \
+ sed 'N;N;s,\n, ,g' | { \
+ list=; while read file base inst; do \
+ if test "$$base" = "$$inst"; then list="$$list $$file"; else \
+ echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man5dir)/$$inst'"; \
+ $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man5dir)/$$inst" || exit $$?; \
+ fi; \
+ done; \
+ for i in $$list; do echo "$$i"; done | $(am__base_list) | \
+ while read files; do \
+ test -z "$$files" || { \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man5dir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(man5dir)" || exit $$?; }; \
+ done; }
+
+uninstall-man5:
+ @$(NORMAL_UNINSTALL)
+ @list=''; test -n "$(man5dir)" || exit 0; \
+ files=`{ for i in $$list; do echo "$$i"; done; \
+ l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \
+ sed -n '/\.5[a-z]*$$/p'; \
+ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^5][0-9a-z]*$$,5,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \
+ dir='$(DESTDIR)$(man5dir)'; $(am__uninstall_files_from_dir)
+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)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(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-am
+
+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-am
+
+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
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(MANS) $(DATA)
+installdirs:
+ for dir in "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man5dir)" "$(DESTDIR)$(man8dir)" "$(DESTDIR)$(sysconfdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+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-am
+
+clean-am: clean-generic clean-sbinPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-man
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-dist_sysconfDATA install-sbinPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man: install-man5 install-man8
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-dist_sysconfDATA uninstall-man \
+ uninstall-sbinPROGRAMS
+
+uninstall-man: uninstall-man5 uninstall-man8
+
+.MAKE: install-am install-strip
+
+.PHONY: 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 \
+ 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
+
+
+dhclient.o: dhclient.c
+ $(COMPILE) -DCLIENT_PATH='"PATH=$(sbindir):/sbin:/bin:/usr/sbin:/usr/bin"' \
+ -DLOCALSTATEDIR='"$(localstatedir)"' -c dhclient.c
+
+dhc6.o: dhc6.c
+ $(COMPILE) -DCLIENT_PATH='"PATH=$(sbindir):/sbin:/bin:/usr/sbin:/usr/bin"' \
+ -DLOCALSTATEDIR='"$(localstatedir)"' -c dhc6.c
+
+# 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/client/clparse.c b/client/clparse.c
new file mode 100644
index 0000000..646229f
--- /dev/null
+++ b/client/clparse.c
@@ -0,0 +1,2235 @@
+/* clparse.c
+
+ Parser for dhclient config and lease files... */
+
+/*
+ * 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/
+ *
+ */
+
+#include "dhcpd.h"
+#include <errno.h>
+
+struct client_config top_level_config;
+
+#define NUM_DEFAULT_REQUESTED_OPTS 9
+struct option *default_requested_options[NUM_DEFAULT_REQUESTED_OPTS + 1];
+
+static void parse_client_default_duid(struct parse *cfile);
+static void parse_client6_lease_statement(struct parse *cfile);
+#ifdef DHCPv6
+static struct dhc6_ia *parse_client6_ia_na_statement(struct parse *cfile);
+static struct dhc6_ia *parse_client6_ia_ta_statement(struct parse *cfile);
+static struct dhc6_ia *parse_client6_ia_pd_statement(struct parse *cfile);
+static struct dhc6_addr *parse_client6_iaaddr_statement(struct parse *cfile);
+static struct dhc6_addr *parse_client6_iaprefix_statement(struct parse *cfile);
+#endif /* DHCPv6 */
+
+/* client-conf-file :== client-declarations END_OF_FILE
+ client-declarations :== <nil>
+ | client-declaration
+ | client-declarations client-declaration */
+
+isc_result_t read_client_conf ()
+{
+ struct client_config *config;
+ struct interface_info *ip;
+ isc_result_t status;
+ unsigned code;
+
+ /*
+ * TODO: LATER constant is very undescriptive. We should review it and
+ * change it to something more descriptive or even better remove it
+ * completely as it is currently not used.
+ */
+#ifdef LATER
+ struct parse *parse = NULL;
+#endif
+
+ /* Initialize the default request list. */
+ memset(default_requested_options, 0, sizeof(default_requested_options));
+
+ /* 1 */
+ code = DHO_SUBNET_MASK;
+ option_code_hash_lookup(&default_requested_options[0],
+ dhcp_universe.code_hash, &code, 0, MDL);
+
+ /* 2 */
+ code = DHO_BROADCAST_ADDRESS;
+ option_code_hash_lookup(&default_requested_options[1],
+ dhcp_universe.code_hash, &code, 0, MDL);
+
+ /* 3 */
+ code = DHO_TIME_OFFSET;
+ option_code_hash_lookup(&default_requested_options[2],
+ dhcp_universe.code_hash, &code, 0, MDL);
+
+ /* 4 */
+ code = DHO_ROUTERS;
+ option_code_hash_lookup(&default_requested_options[3],
+ dhcp_universe.code_hash, &code, 0, MDL);
+
+ /* 5 */
+ code = DHO_DOMAIN_NAME;
+ option_code_hash_lookup(&default_requested_options[4],
+ dhcp_universe.code_hash, &code, 0, MDL);
+
+ /* 6 */
+ code = DHO_DOMAIN_NAME_SERVERS;
+ option_code_hash_lookup(&default_requested_options[5],
+ dhcp_universe.code_hash, &code, 0, MDL);
+
+ /* 7 */
+ code = DHO_HOST_NAME;
+ option_code_hash_lookup(&default_requested_options[6],
+ dhcp_universe.code_hash, &code, 0, MDL);
+
+ /* 8 */
+ code = D6O_NAME_SERVERS;
+ option_code_hash_lookup(&default_requested_options[7],
+ dhcpv6_universe.code_hash, &code, 0, MDL);
+
+ /* 9 */
+ code = D6O_DOMAIN_SEARCH;
+ option_code_hash_lookup(&default_requested_options[8],
+ dhcpv6_universe.code_hash, &code, 0, MDL);
+
+ for (code = 0 ; code < NUM_DEFAULT_REQUESTED_OPTS ; code++) {
+ if (default_requested_options[code] == NULL)
+ log_fatal("Unable to find option definition for "
+ "index %u during default parameter request "
+ "assembly.", code);
+ }
+
+ /* Initialize the top level client configuration. */
+ memset (&top_level_config, 0, sizeof top_level_config);
+
+ /* Set some defaults... */
+ top_level_config.timeout = 60;
+ top_level_config.select_interval = 0;
+ top_level_config.reboot_timeout = 10;
+ top_level_config.retry_interval = 300;
+ top_level_config.backoff_cutoff = 15;
+ top_level_config.initial_interval = 3;
+
+ /*
+ * RFC 2131, section 4.4.1 specifies that the client SHOULD wait a
+ * random time between 1 and 10 seconds. However, we choose to not
+ * implement this default. If user is inclined to really have that
+ * delay, he is welcome to do so, using 'initial-delay X;' parameter
+ * in config file.
+ */
+ top_level_config.initial_delay = 0;
+
+ top_level_config.bootp_policy = P_ACCEPT;
+ top_level_config.script_name = path_dhclient_script;
+ top_level_config.requested_options = default_requested_options;
+ top_level_config.omapi_port = -1;
+ top_level_config.do_forward_update = 1;
+ /* Requested lease time, used by DHCPv6 (DHCPv4 uses the option cache)
+ */
+ top_level_config.requested_lease = 7200;
+
+ group_allocate (&top_level_config.on_receipt, MDL);
+ if (!top_level_config.on_receipt)
+ log_fatal ("no memory for top-level on_receipt group");
+
+ group_allocate (&top_level_config.on_transmission, MDL);
+ if (!top_level_config.on_transmission)
+ log_fatal ("no memory for top-level on_transmission group");
+
+ status = read_client_conf_file (path_dhclient_conf,
+ (struct interface_info *)0,
+ &top_level_config);
+
+ if (status != ISC_R_SUCCESS) {
+ ;
+#ifdef LATER
+ /* Set up the standard name service updater routine. */
+ status = new_parse(&parse, -1, default_client_config,
+ sizeof(default_client_config) - 1,
+ "default client configuration", 0);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("can't begin default client config!");
+ }
+
+ if (parse != NULL) {
+ do {
+ token = peek_token(&val, NULL, cfile);
+ if (token == END_OF_FILE)
+ break;
+ parse_client_statement(cfile, NULL, &top_level_config);
+ } while (1);
+ end_parse(&parse);
+#endif
+ }
+
+ /* Set up state and config structures for clients that don't
+ have per-interface configuration statements. */
+ config = (struct client_config *)0;
+ for (ip = interfaces; ip; ip = ip -> next) {
+ if (!ip -> client) {
+ ip -> client = (struct client_state *)
+ dmalloc (sizeof (struct client_state), MDL);
+ if (!ip -> client)
+ log_fatal ("no memory for client state.");
+ memset (ip -> client, 0, sizeof *(ip -> client));
+ ip -> client -> interface = ip;
+ }
+
+ if (!ip -> client -> config) {
+ if (!config) {
+ config = (struct client_config *)
+ dmalloc (sizeof (struct client_config),
+ MDL);
+ if (!config)
+ log_fatal ("no memory for client config.");
+ memcpy (config, &top_level_config,
+ sizeof top_level_config);
+ }
+ ip -> client -> config = config;
+ }
+ }
+ return status;
+}
+
+int read_client_conf_file (const char *name, struct interface_info *ip,
+ struct client_config *client)
+{
+ int file;
+ struct parse *cfile;
+ const char *val;
+ int token;
+ isc_result_t status;
+
+ if ((file = open (name, O_RDONLY)) < 0)
+ return uerr2isc (errno);
+
+ cfile = NULL;
+ status = new_parse(&cfile, file, NULL, 0, path_dhclient_conf, 0);
+ if (status != ISC_R_SUCCESS || cfile == NULL)
+ return status;
+
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == END_OF_FILE)
+ break;
+ parse_client_statement (cfile, ip, client);
+ } while (1);
+ skip_token(&val, (unsigned *)0, cfile);
+ status = (cfile -> warnings_occurred
+ ? DHCP_R_BADPARSE
+ : ISC_R_SUCCESS);
+ end_parse (&cfile);
+ return status;
+}
+
+
+/* lease-file :== client-lease-statements END_OF_FILE
+ client-lease-statements :== <nil>
+ | client-lease-statements LEASE client-lease-statement */
+
+void read_client_leases ()
+{
+ int file;
+ isc_result_t status;
+ struct parse *cfile;
+ const char *val;
+ int token;
+
+ /* Open the lease file. If we can't open it, just return -
+ we can safely trust the server to remember our state. */
+ if ((file = open (path_dhclient_db, O_RDONLY)) < 0)
+ return;
+
+ cfile = NULL;
+ status = new_parse(&cfile, file, NULL, 0, path_dhclient_db, 0);
+ if (status != ISC_R_SUCCESS || cfile == NULL)
+ return;
+
+ do {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token == END_OF_FILE)
+ break;
+
+ switch (token) {
+ case DEFAULT_DUID:
+ parse_client_default_duid(cfile);
+ break;
+
+ case LEASE:
+ parse_client_lease_statement(cfile, 0);
+ break;
+
+ case LEASE6:
+ parse_client6_lease_statement(cfile);
+ break;
+
+ default:
+ log_error ("Corrupt lease file - possible data loss!");
+ skip_to_semi (cfile);
+ break;
+ }
+ } while (1);
+
+ end_parse (&cfile);
+}
+
+/* client-declaration :==
+ SEND option-decl |
+ DEFAULT option-decl |
+ SUPERSEDE option-decl |
+ PREPEND option-decl |
+ APPEND option-decl |
+ hardware-declaration |
+ ALSO REQUEST option-list |
+ ALSO REQUIRE option-list |
+ REQUEST option-list |
+ REQUIRE option-list |
+ TIMEOUT number |
+ RETRY number |
+ REBOOT number |
+ SELECT_TIMEOUT number |
+ SCRIPT string |
+ VENDOR_SPACE string |
+ interface-declaration |
+ LEASE client-lease-statement |
+ ALIAS client-lease-statement |
+ KEY key-definition */
+
+void parse_client_statement (cfile, ip, config)
+ struct parse *cfile;
+ struct interface_info *ip;
+ struct client_config *config;
+{
+ int token;
+ const char *val;
+ struct option *option = NULL;
+ struct executable_statement *stmt;
+ int lose;
+ char *name;
+ enum policy policy;
+ int known;
+ int tmp, i;
+ isc_result_t status;
+ struct option ***append_list, **new_list, **cat_list;
+
+ switch (peek_token (&val, (unsigned *)0, cfile)) {
+ 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_client_conf_file (val, ip, config);
+ if (status != ISC_R_SUCCESS)
+ parse_warn (cfile, "%s: bad parse.", val);
+ parse_semi (cfile);
+ }
+ return;
+
+ case KEY:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (ip) {
+ /* This may seem arbitrary, but there's a reason for
+ doing it: the authentication key database is not
+ scoped. If we allow the user to declare a key other
+ than in the outer scope, the user is very likely to
+ believe that the key will only be used in that
+ scope. If the user only wants the key to be used on
+ one interface, because it's known that the other
+ interface may be connected to an insecure net and
+ the secret key is considered sensitive, we don't
+ want to lull them into believing they've gotten
+ their way. This is a bit contrived, but people
+ tend not to be entirely rational about security. */
+ parse_warn (cfile, "key definition not allowed here.");
+ skip_to_semi (cfile);
+ break;
+ }
+ parse_key (cfile);
+ return;
+
+ case TOKEN_ALSO:
+ /* consume ALSO */
+ skip_token(&val, NULL, cfile);
+
+ /* consume type of ALSO list. */
+ token = next_token(&val, NULL, cfile);
+
+ if (token == REQUEST) {
+ append_list = &config->requested_options;
+ } else if (token == REQUIRE) {
+ append_list = &config->required_options;
+ } else {
+ parse_warn(cfile, "expected REQUEST or REQUIRE list");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /* If there is no list, cut the concat short. */
+ if (*append_list == NULL) {
+ parse_option_list(cfile, append_list);
+ return;
+ }
+
+ /* Count the length of the existing list. */
+ for (i = 0 ; (*append_list)[i] != NULL ; i++)
+ ; /* This space intentionally left blank. */
+
+ /* If there's no codes on the list, cut the concat short. */
+ if (i == 0) {
+ parse_option_list(cfile, append_list);
+ return;
+ }
+
+ tmp = parse_option_list(cfile, &new_list);
+
+ if (tmp == 0 || new_list == NULL)
+ return;
+
+ /* Allocate 'i + tmp' buckets plus a terminator. */
+ cat_list = dmalloc(sizeof(struct option *) * (i + tmp + 1),
+ MDL);
+
+ if (cat_list == NULL) {
+ log_error("Unable to allocate memory for new "
+ "request list.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ for (i = 0 ; (*append_list)[i] != NULL ; i++)
+ option_reference(&cat_list[i], (*append_list)[i], MDL);
+
+ tmp = i;
+
+ for (i = 0 ; new_list[i] != 0 ; i++)
+ option_reference(&cat_list[tmp++], new_list[i], MDL);
+
+ cat_list[tmp] = 0;
+
+ /* XXX: We cannot free the old list, because it may have been
+ * XXX: assigned from an outer configuration scope (or may be
+ * XXX: the static default setting).
+ */
+ *append_list = cat_list;
+
+ return;
+
+ /* REQUIRE can either start a policy statement or a
+ comma-separated list of names of required options. */
+ case REQUIRE:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == AUTHENTICATION) {
+ policy = P_REQUIRE;
+ goto do_policy;
+ }
+ parse_option_list (cfile, &config -> required_options);
+ return;
+
+ case IGNORE:
+ skip_token(&val, (unsigned *)0, cfile);
+ policy = P_IGNORE;
+ goto do_policy;
+
+ case ACCEPT:
+ skip_token(&val, (unsigned *)0, cfile);
+ policy = P_ACCEPT;
+ goto do_policy;
+
+ case PREFER:
+ skip_token(&val, (unsigned *)0, cfile);
+ policy = P_PREFER;
+ goto do_policy;
+
+ case DONT:
+ skip_token(&val, (unsigned *)0, cfile);
+ policy = P_DONT;
+ goto do_policy;
+
+ do_policy:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token == AUTHENTICATION) {
+ if (policy != P_PREFER &&
+ policy != P_REQUIRE &&
+ policy != P_DONT) {
+ parse_warn (cfile,
+ "invalid authentication policy.");
+ skip_to_semi (cfile);
+ return;
+ }
+ config -> auth_policy = policy;
+ } else if (token != TOKEN_BOOTP) {
+ if (policy != P_PREFER &&
+ policy != P_IGNORE &&
+ policy != P_ACCEPT) {
+ parse_warn (cfile, "invalid bootp policy.");
+ skip_to_semi (cfile);
+ return;
+ }
+ config -> bootp_policy = policy;
+ } else {
+ parse_warn (cfile, "expecting a policy type.");
+ skip_to_semi (cfile);
+ return;
+ }
+ break;
+
+ case OPTION:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == SPACE) {
+ if (ip) {
+ parse_warn (cfile,
+ "option space definitions %s",
+ " may not be scoped.");
+ skip_to_semi (cfile);
+ break;
+ }
+ parse_option_space_decl (cfile);
+ return;
+ }
+
+ known = 0;
+ status = parse_option_name(cfile, 1, &known, &option);
+ if (status != ISC_R_SUCCESS || option == NULL)
+ return;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != CODE) {
+ parse_warn (cfile, "expecting \"code\" keyword.");
+ skip_to_semi (cfile);
+ option_dereference(&option, MDL);
+ return;
+ }
+ if (ip) {
+ parse_warn (cfile,
+ "option definitions may only appear in %s",
+ "the outermost scope.");
+ skip_to_semi (cfile);
+ option_dereference(&option, MDL);
+ return;
+ }
+
+ /*
+ * If the option was known, remove it from the code and name
+ * hash tables 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;
+
+ case MEDIA:
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_string_list (cfile, &config -> media, 1);
+ return;
+
+ case HARDWARE:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (ip) {
+ parse_hardware_param (cfile, &ip -> hw_address);
+ } else {
+ parse_warn (cfile, "hardware address parameter %s",
+ "not allowed here.");
+ skip_to_semi (cfile);
+ }
+ return;
+
+ case ANYCAST_MAC:
+ skip_token(&val, NULL, cfile);
+ if (ip != NULL) {
+ parse_hardware_param(cfile, &ip->anycast_mac_addr);
+ } else {
+ parse_warn(cfile, "anycast mac address parameter "
+ "not allowed here.");
+ skip_to_semi (cfile);
+ }
+ return;
+
+ case REQUEST:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (config -> requested_options == default_requested_options)
+ config -> requested_options = NULL;
+ parse_option_list (cfile, &config -> requested_options);
+ return;
+
+ case TIMEOUT:
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_lease_time (cfile, &config -> timeout);
+ return;
+
+ case RETRY:
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_lease_time (cfile, &config -> retry_interval);
+ return;
+
+ case SELECT_TIMEOUT:
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_lease_time (cfile, &config -> select_interval);
+ return;
+
+ case OMAPI:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != PORT) {
+ parse_warn (cfile,
+ "unexpected omapi subtype: %s", val);
+ skip_to_semi (cfile);
+ return;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "invalid port number: `%s'", val);
+ skip_to_semi (cfile);
+ return;
+ }
+ tmp = atoi (val);
+ if (tmp < 0 || tmp > 65535)
+ parse_warn (cfile, "invalid omapi port %d.", tmp);
+ else if (config != &top_level_config)
+ parse_warn (cfile,
+ "omapi port only works at top level.");
+ else
+ config -> omapi_port = tmp;
+ parse_semi (cfile);
+ return;
+
+ case DO_FORWARD_UPDATE:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!strcasecmp (val, "on") ||
+ !strcasecmp (val, "true"))
+ config -> do_forward_update = 1;
+ else if (!strcasecmp (val, "off") ||
+ !strcasecmp (val, "false"))
+ config -> do_forward_update = 0;
+ else {
+ parse_warn (cfile, "expecting boolean value.");
+ skip_to_semi (cfile);
+ return;
+ }
+ parse_semi (cfile);
+ return;
+
+ case REBOOT:
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_lease_time (cfile, &config -> reboot_timeout);
+ return;
+
+ case BACKOFF_CUTOFF:
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_lease_time (cfile, &config -> backoff_cutoff);
+ return;
+
+ case INITIAL_INTERVAL:
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_lease_time (cfile, &config -> initial_interval);
+ return;
+
+ case INITIAL_DELAY:
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_lease_time (cfile, &config -> initial_delay);
+ return;
+
+ case SCRIPT:
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_string (cfile, &config -> script_name, (unsigned *)0);
+ return;
+
+ case VENDOR:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != OPTION) {
+ parse_warn (cfile, "expecting 'vendor option space'");
+ skip_to_semi (cfile);
+ return;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != SPACE) {
+ parse_warn (cfile, "expecting 'vendor option space'");
+ skip_to_semi (cfile);
+ return;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!is_identifier (token)) {
+ parse_warn (cfile, "expecting an identifier.");
+ skip_to_semi (cfile);
+ return;
+ }
+ config -> vendor_space_name = dmalloc (strlen (val) + 1, MDL);
+ if (!config -> vendor_space_name)
+ log_fatal ("no memory for vendor option space name.");
+ strcpy (config -> vendor_space_name, val);
+ for (i = 0; i < universe_count; i++)
+ if (!strcmp (universes [i] -> name,
+ config -> vendor_space_name))
+ break;
+ if (i == universe_count) {
+ log_error ("vendor option space %s not found.",
+ config -> vendor_space_name);
+ }
+ parse_semi (cfile);
+ return;
+
+ case INTERFACE:
+ skip_token(&val, (unsigned *)0, cfile);
+ if (ip)
+ parse_warn (cfile, "nested interface declaration.");
+ parse_interface_declaration (cfile, config, (char *)0);
+ return;
+
+ case PSEUDO:
+ skip_token(&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ name = dmalloc (strlen (val) + 1, MDL);
+ if (!name)
+ log_fatal ("no memory for pseudo interface name");
+ strcpy (name, val);
+ parse_interface_declaration (cfile, config, name);
+ return;
+
+ case LEASE:
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_client_lease_statement (cfile, 1);
+ return;
+
+ case ALIAS:
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_client_lease_statement (cfile, 2);
+ return;
+
+ case REJECT:
+ skip_token(&val, (unsigned *)0, cfile);
+ parse_reject_statement (cfile, config);
+ return;
+
+ default:
+ lose = 0;
+ stmt = (struct executable_statement *)0;
+ if (!parse_executable_statement (&stmt,
+ cfile, &lose, context_any)) {
+ if (!lose) {
+ parse_warn (cfile, "expecting a statement.");
+ skip_to_semi (cfile);
+ }
+ } else {
+ struct executable_statement **eptr, *sptr;
+ if (stmt &&
+ (stmt -> op == send_option_statement ||
+ (stmt -> op == on_statement &&
+ (stmt -> data.on.evtypes & ON_TRANSMISSION)))) {
+ eptr = &config -> on_transmission -> statements;
+ if (stmt -> op == on_statement) {
+ sptr = (struct executable_statement *)0;
+ executable_statement_reference
+ (&sptr,
+ stmt -> data.on.statements, MDL);
+ executable_statement_dereference (&stmt,
+ MDL);
+ executable_statement_reference (&stmt,
+ sptr,
+ MDL);
+ executable_statement_dereference (&sptr,
+ MDL);
+ }
+ } else
+ eptr = &config -> on_receipt -> statements;
+
+ if (stmt) {
+ for (; *eptr; eptr = &(*eptr) -> next)
+ ;
+ executable_statement_reference (eptr,
+ stmt, MDL);
+ }
+ return;
+ }
+ break;
+ }
+ parse_semi (cfile);
+}
+
+/* option-list :== option_name |
+ option_list COMMA option_name */
+
+int
+parse_option_list(struct parse *cfile, struct option ***list)
+{
+ int ix;
+ int token;
+ const char *val;
+ pair p = (pair)0, q = (pair)0, r;
+ struct option *option = NULL;
+ isc_result_t status;
+
+ ix = 0;
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == SEMI) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ break;
+ }
+ if (!is_identifier (token)) {
+ parse_warn (cfile, "%s: expected option name.", val);
+ skip_token(&val, (unsigned *)0, cfile);
+ skip_to_semi (cfile);
+ return 0;
+ }
+ status = parse_option_name(cfile, 0, NULL, &option);
+ if (status != ISC_R_SUCCESS || option == NULL) {
+ parse_warn (cfile, "%s: expected option name.", val);
+ return 0;
+ }
+ r = new_pair (MDL);
+ if (!r)
+ log_fatal ("can't allocate pair for option code.");
+ /* XXX: we should probably carry a reference across this */
+ r->car = (caddr_t)option;
+ option_dereference(&option, MDL);
+ r -> cdr = (pair)0;
+ if (p)
+ q -> cdr = r;
+ else
+ p = r;
+ q = r;
+ ++ix;
+ token = next_token (&val, (unsigned *)0, cfile);
+ } while (token == COMMA);
+ if (token != SEMI) {
+ parse_warn (cfile, "expecting semicolon.");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ /* XXX we can't free the list here, because we may have copied
+ XXX it from an outer config state. */
+ *list = NULL;
+ if (ix) {
+ *list = dmalloc ((ix + 1) * sizeof(struct option *), MDL);
+ if (!*list)
+ log_error ("no memory for option list.");
+ else {
+ ix = 0;
+ for (q = p; q; q = q -> cdr)
+ option_reference(&(*list)[ix++],
+ (struct option *)q->car, MDL);
+ (*list)[ix] = NULL;
+ }
+ while (p) {
+ q = p -> cdr;
+ free_pair (p, MDL);
+ p = q;
+ }
+ }
+
+ return ix;
+}
+
+/* interface-declaration :==
+ INTERFACE string LBRACE client-declarations RBRACE */
+
+void parse_interface_declaration (cfile, outer_config, name)
+ struct parse *cfile;
+ struct client_config *outer_config;
+ char *name;
+{
+ int token;
+ const char *val;
+ struct client_state *client, **cp;
+ struct interface_info *ip = (struct interface_info *)0;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING) {
+ parse_warn (cfile, "expecting interface name (in quotes).");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ if (!interface_or_dummy (&ip, val))
+ log_fatal ("Can't allocate interface %s.", val);
+
+ /* If we were given a name, this is a pseudo-interface. */
+ if (name) {
+ make_client_state (&client);
+ client -> name = name;
+ client -> interface = ip;
+ for (cp = &ip -> client; *cp; cp = &((*cp) -> next))
+ ;
+ *cp = client;
+ } else {
+ if (!ip -> client) {
+ make_client_state (&ip -> client);
+ ip -> client -> interface = ip;
+ }
+ client = ip -> client;
+ }
+
+ if (!client -> config)
+ make_client_config (client, outer_config);
+
+ ip -> flags &= ~INTERFACE_AUTOMATIC;
+ interfaces_requested = 1;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LBRACE) {
+ parse_warn (cfile, "expecting left brace.");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == END_OF_FILE) {
+ parse_warn (cfile,
+ "unterminated interface declaration.");
+ return;
+ }
+ if (token == RBRACE)
+ break;
+ parse_client_statement (cfile, ip, client -> config);
+ } while (1);
+ skip_token(&val, (unsigned *)0, cfile);
+}
+
+int interface_or_dummy (struct interface_info **pi, const char *name)
+{
+ struct interface_info *i;
+ struct interface_info *ip = (struct interface_info *)0;
+ isc_result_t status;
+
+ /* Find the interface (if any) that matches the name. */
+ for (i = interfaces; i; i = i -> next) {
+ if (!strcmp (i -> name, name)) {
+ interface_reference (&ip, i, MDL);
+ break;
+ }
+ }
+
+ /* If it's not a real interface, see if it's on the dummy list. */
+ if (!ip) {
+ for (ip = dummy_interfaces; ip; ip = ip -> next) {
+ if (!strcmp (ip -> name, name)) {
+ interface_reference (&ip, i, MDL);
+ break;
+ }
+ }
+ }
+
+ /* If we didn't find an interface, make a dummy interface as
+ a placeholder. */
+ if (!ip) {
+ if ((status = interface_allocate (&ip, MDL)) != ISC_R_SUCCESS)
+ log_fatal ("Can't record interface %s: %s",
+ name, isc_result_totext (status));
+
+ if (strlen(name) >= sizeof(ip->name)) {
+ interface_dereference(&ip, MDL);
+ return 0;
+ }
+ strcpy(ip->name, name);
+
+ if (dummy_interfaces) {
+ interface_reference (&ip -> next,
+ dummy_interfaces, MDL);
+ interface_dereference (&dummy_interfaces, MDL);
+ }
+ interface_reference (&dummy_interfaces, ip, MDL);
+ }
+ if (pi)
+ status = interface_reference (pi, ip, MDL);
+ else
+ status = ISC_R_FAILURE;
+ interface_dereference (&ip, MDL);
+ if (status != ISC_R_SUCCESS)
+ return 0;
+ return 1;
+}
+
+void make_client_state (state)
+ struct client_state **state;
+{
+ *state = ((struct client_state *)dmalloc (sizeof **state, MDL));
+ if (!*state)
+ log_fatal ("no memory for client state\n");
+ memset (*state, 0, sizeof **state);
+}
+
+void make_client_config (client, config)
+ struct client_state *client;
+ struct client_config *config;
+{
+ client -> config = (((struct client_config *)
+ dmalloc (sizeof (struct client_config), MDL)));
+ if (!client -> config)
+ log_fatal ("no memory for client config\n");
+ memcpy (client -> config, config, sizeof *config);
+ if (!clone_group (&client -> config -> on_receipt,
+ config -> on_receipt, MDL) ||
+ !clone_group (&client -> config -> on_transmission,
+ config -> on_transmission, MDL))
+ log_fatal ("no memory for client state groups.");
+}
+
+/* client-lease-statement :==
+ LBRACE client-lease-declarations RBRACE
+
+ client-lease-declarations :==
+ <nil> |
+ client-lease-declaration |
+ client-lease-declarations client-lease-declaration */
+
+
+void parse_client_lease_statement (cfile, is_static)
+ struct parse *cfile;
+ int is_static;
+{
+ struct client_lease *lease, *lp, *pl, *next;
+ struct interface_info *ip = (struct interface_info *)0;
+ int token;
+ const char *val;
+ struct client_state *client = (struct client_state *)0;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LBRACE) {
+ parse_warn (cfile, "expecting left brace.");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ lease = ((struct client_lease *)
+ dmalloc (sizeof (struct client_lease), MDL));
+ if (!lease)
+ log_fatal ("no memory for lease.\n");
+ memset (lease, 0, sizeof *lease);
+ lease -> is_static = is_static;
+ if (!option_state_allocate (&lease -> options, MDL))
+ log_fatal ("no memory for lease options.\n");
+
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == END_OF_FILE) {
+ parse_warn (cfile, "unterminated lease declaration.");
+ return;
+ }
+ if (token == RBRACE)
+ break;
+ parse_client_lease_declaration (cfile, lease, &ip, &client);
+ } while (1);
+ skip_token(&val, (unsigned *)0, cfile);
+
+ /* If the lease declaration didn't include an interface
+ declaration that we recognized, it's of no use to us. */
+ if (!ip) {
+ destroy_client_lease (lease);
+ return;
+ }
+
+ /* Make sure there's a client state structure... */
+ if (!ip -> client) {
+ make_client_state (&ip -> client);
+ ip -> client -> interface = ip;
+ }
+ if (!client)
+ client = ip -> client;
+
+ /* If this is an alias lease, it doesn't need to be sorted in. */
+ if (is_static == 2) {
+ ip -> client -> alias = lease;
+ return;
+ }
+
+ /* The new lease may supersede a lease that's not the
+ active lease but is still on the lease list, so scan the
+ lease list looking for a lease with the same address, and
+ if we find it, toss it. */
+ pl = (struct client_lease *)0;
+ for (lp = client -> leases; lp; lp = next) {
+ next = lp -> next;
+ if (lp -> address.len == lease -> address.len &&
+ !memcmp (lp -> address.iabuf, lease -> address.iabuf,
+ lease -> address.len)) {
+ if (pl)
+ pl -> next = next;
+ else
+ client -> leases = next;
+ destroy_client_lease (lp);
+ break;
+ } else
+ pl = lp;
+ }
+
+ /* If this is a preloaded lease, just put it on the list of recorded
+ leases - don't make it the active lease. */
+ if (is_static) {
+ lease -> next = client -> leases;
+ client -> leases = lease;
+ return;
+ }
+
+ /* The last lease in the lease file on a particular interface is
+ the active lease for that interface. Of course, we don't know
+ what the last lease in the file is until we've parsed the whole
+ file, so at this point, we assume that the lease we just parsed
+ is the active lease for its interface. If there's already
+ an active lease for the interface, and this lease is for the same
+ ip address, then we just toss the old active lease and replace
+ it with this one. If this lease is for a different address,
+ then if the old active lease has expired, we dump it; if not,
+ we put it on the list of leases for this interface which are
+ still valid but no longer active. */
+ if (client -> active) {
+ if (client -> active -> expiry < cur_time)
+ destroy_client_lease (client -> active);
+ else if (client -> active -> address.len ==
+ lease -> address.len &&
+ !memcmp (client -> active -> address.iabuf,
+ lease -> address.iabuf,
+ lease -> address.len))
+ destroy_client_lease (client -> active);
+ else {
+ client -> active -> next = client -> leases;
+ client -> leases = client -> active;
+ }
+ }
+ client -> active = lease;
+
+ /* phew. */
+}
+
+/* client-lease-declaration :==
+ BOOTP |
+ INTERFACE string |
+ FIXED_ADDR ip_address |
+ FILENAME string |
+ SERVER_NAME string |
+ OPTION option-decl |
+ RENEW time-decl |
+ REBIND time-decl |
+ EXPIRE time-decl |
+ KEY id */
+
+void parse_client_lease_declaration (cfile, lease, ipp, clientp)
+ struct parse *cfile;
+ struct client_lease *lease;
+ struct interface_info **ipp;
+ struct client_state **clientp;
+{
+ int token;
+ const char *val;
+ struct interface_info *ip;
+ struct option_cache *oc;
+ struct client_state *client = (struct client_state *)0;
+
+ switch (next_token (&val, (unsigned *)0, cfile)) {
+ case KEY:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING && !is_identifier (token)) {
+ parse_warn (cfile, "expecting key name.");
+ skip_to_semi (cfile);
+ break;
+ }
+ if (omapi_auth_key_lookup_name (&lease -> key, val) !=
+ ISC_R_SUCCESS)
+ parse_warn (cfile, "unknown key %s", val);
+ parse_semi (cfile);
+ break;
+ case TOKEN_BOOTP:
+ lease -> is_bootp = 1;
+ break;
+
+ case INTERFACE:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING) {
+ parse_warn (cfile,
+ "expecting interface name (in quotes).");
+ skip_to_semi (cfile);
+ break;
+ }
+ if (!interface_or_dummy (ipp, val))
+ log_fatal ("Can't allocate interface %s.", val);
+ break;
+
+ case NAME:
+ token = next_token (&val, (unsigned *)0, cfile);
+ ip = *ipp;
+ if (!ip) {
+ parse_warn (cfile, "state name precedes interface.");
+ break;
+ }
+ for (client = ip -> client; client; client = client -> next)
+ if (client -> name && !strcmp (client -> name, val))
+ break;
+ if (!client)
+ parse_warn (cfile,
+ "lease specified for unknown pseudo.");
+ *clientp = client;
+ break;
+
+ case FIXED_ADDR:
+ if (!parse_ip_addr (cfile, &lease -> address))
+ return;
+ break;
+
+ case MEDIUM:
+ parse_string_list (cfile, &lease -> medium, 0);
+ return;
+
+ case FILENAME:
+ parse_string (cfile, &lease -> filename, (unsigned *)0);
+ return;
+
+ case SERVER_NAME:
+ parse_string (cfile, &lease -> server_name, (unsigned *)0);
+ return;
+
+ case RENEW:
+ lease -> renewal = parse_date (cfile);
+ return;
+
+ case REBIND:
+ lease -> rebind = parse_date (cfile);
+ return;
+
+ case EXPIRE:
+ lease -> expiry = parse_date (cfile);
+ return;
+
+ case OPTION:
+ oc = (struct option_cache *)0;
+ if (parse_option_decl (&oc, cfile)) {
+ save_option(oc->option->universe, lease->options, oc);
+ option_cache_dereference (&oc, MDL);
+ }
+ return;
+
+ default:
+ parse_warn (cfile, "expecting lease declaration.");
+ skip_to_semi (cfile);
+ break;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != SEMI) {
+ parse_warn (cfile, "expecting semicolon.");
+ skip_to_semi (cfile);
+ }
+}
+
+/* Parse a default-duid ""; statement.
+ */
+static void
+parse_client_default_duid(struct parse *cfile)
+{
+ struct data_string new_duid;
+ const char *val = NULL;
+ unsigned len;
+ int token;
+
+ memset(&new_duid, 0, sizeof(new_duid));
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ parse_warn(cfile, "Expected DUID string.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ if (len <= 2) {
+ parse_warn(cfile, "Invalid DUID contents.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ if (!buffer_allocate(&new_duid.buffer, len, MDL)) {
+ parse_warn(cfile, "Out of memory parsing default DUID.");
+ skip_to_semi(cfile);
+ return;
+ }
+ new_duid.data = new_duid.buffer->data;
+ new_duid.len = len;
+
+ memcpy(new_duid.buffer->data, val, len);
+
+ /* Rotate the last entry into place. */
+ if (default_duid.buffer != NULL)
+ data_string_forget(&default_duid, MDL);
+ data_string_copy(&default_duid, &new_duid, MDL);
+ data_string_forget(&new_duid, MDL);
+
+ parse_semi(cfile);
+}
+
+/* Parse a lease6 {} construct. The v6 client is a little different
+ * than the v4 client today, in that it only retains one lease, the
+ * active lease, and discards any less recent information. It may
+ * be useful in the future to cache additional information, but it
+ * is not worth the effort for the moment.
+ */
+static void
+parse_client6_lease_statement(struct parse *cfile)
+{
+#if !defined(DHCPv6)
+ parse_warn(cfile, "No DHCPv6 support.");
+ skip_to_semi(cfile);
+#else /* defined(DHCPv6) */
+ struct option_cache *oc = NULL;
+ struct dhc6_lease *lease;
+ struct dhc6_ia **ia;
+ struct client_state *client = NULL;
+ struct interface_info *iface = NULL;
+ struct data_string ds;
+ const char *val;
+ unsigned len;
+ int token, has_ia, no_semi, has_name;
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "Expecting open curly brace.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ lease = dmalloc(sizeof(*lease), MDL);
+ if (lease == NULL) {
+ parse_warn(cfile, "Unable to allocate lease state.");
+ skip_to_rbrace(cfile, 1);
+ return;
+ }
+
+ option_state_allocate(&lease->options, MDL);
+ if (lease->options == NULL) {
+ parse_warn(cfile, "Unable to allocate option cache.");
+ skip_to_rbrace(cfile, 1);
+ dfree(lease, MDL);
+ return;
+ }
+
+ has_ia = 0;
+ has_name = 0;
+ ia = &lease->bindings;
+ token = next_token(&val, NULL, cfile);
+ while (token != RBRACE) {
+ no_semi = 0;
+
+ switch(token) {
+ case IA_NA:
+ *ia = parse_client6_ia_na_statement(cfile);
+ if (*ia != NULL) {
+ ia = &(*ia)->next;
+ has_ia = 1;
+ }
+
+ no_semi = 1;
+
+ break;
+
+ case IA_TA:
+ *ia = parse_client6_ia_ta_statement(cfile);
+ if (*ia != NULL) {
+ ia = &(*ia)->next;
+ has_ia = 1;
+ }
+
+ no_semi = 1;
+
+ break;
+
+ case IA_PD:
+ *ia = parse_client6_ia_pd_statement(cfile);
+ if (*ia != NULL) {
+ ia = &(*ia)->next;
+ has_ia = 1;
+ }
+
+ no_semi = 1;
+
+ break;
+
+ case INTERFACE:
+ if (iface != NULL) {
+ parse_warn(cfile, "Multiple interface names?");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ break;
+ }
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ strerror:
+ parse_warn(cfile, "Expecting a string.");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ break;
+ }
+
+ for (iface = interfaces ; iface != NULL ;
+ iface = iface->next) {
+ if (strcmp(iface->name, val) == 0)
+ break;
+ }
+
+ if (iface == NULL) {
+ parse_warn(cfile, "Unknown interface.");
+ break;
+ }
+
+ break;
+
+ case NAME:
+ has_name = 1;
+
+ if (client != NULL) {
+ parse_warn(cfile, "Multiple state names?");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ break;
+ }
+
+ if (iface == NULL) {
+ parse_warn(cfile, "Client name without "
+ "interface.");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ break;
+ }
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING)
+ goto strerror;
+
+ for (client = iface->client ; client != NULL ;
+ client = client->next) {
+ if ((client->name != NULL) &&
+ (strcmp(client->name, val) == 0))
+ break;
+ }
+
+ if (client == NULL) {
+ parse_warn(cfile, "Unknown client state %s.",
+ val);
+ break;
+ }
+
+ break;
+
+ case OPTION:
+ if (parse_option_decl(&oc, cfile)) {
+ save_option(oc->option->universe,
+ lease->options, oc);
+ option_cache_dereference(&oc, MDL);
+ }
+ no_semi = 1;
+ break;
+
+ case TOKEN_RELEASED:
+ case TOKEN_ABANDONED:
+ lease->released = ISC_TRUE;
+ break;
+
+ default:
+ parse_warn(cfile, "Unexpected token, %s.", val);
+ no_semi = 1;
+ skip_to_semi(cfile);
+ break;
+ }
+
+ if (!no_semi)
+ parse_semi(cfile);
+
+ token = next_token(&val, NULL, cfile);
+
+ if (token == END_OF_FILE) {
+ parse_warn(cfile, "Unexpected end of file.");
+ break;
+ }
+ }
+
+ if (!has_ia) {
+ log_debug("Lease with no IA's discarded from lease db.");
+ dhc6_lease_destroy(&lease, MDL);
+ return;
+ }
+
+ if (iface == NULL)
+ parse_warn(cfile, "Lease has no interface designation.");
+ else if (!has_name && (client == NULL)) {
+ for (client = iface->client ; client != NULL ;
+ client = client->next) {
+ if (client->name == NULL)
+ break;
+ }
+ }
+
+ if (client == NULL) {
+ parse_warn(cfile, "No matching client state.");
+ dhc6_lease_destroy(&lease, MDL);
+ return;
+ }
+
+ /* Fetch Preference option from option cache. */
+ memset(&ds, 0, sizeof(ds));
+ oc = lookup_option(&dhcpv6_universe, lease->options, D6O_PREFERENCE);
+ if ((oc != NULL) &&
+ evaluate_option_cache(&ds, NULL, NULL, NULL, lease->options,
+ NULL, &global_scope, oc, MDL)) {
+ if (ds.len != 1) {
+ log_error("Invalid length of DHCPv6 Preference option "
+ "(%d != 1)", ds.len);
+ data_string_forget(&ds, MDL);
+ dhc6_lease_destroy(&lease, MDL);
+ return;
+ } else
+ lease->pref = ds.data[0];
+
+ data_string_forget(&ds, MDL);
+ }
+
+ /* Fetch server-id option from option cache. */
+ oc = lookup_option(&dhcpv6_universe, lease->options, D6O_SERVERID);
+ if ((oc == NULL) ||
+ !evaluate_option_cache(&lease->server_id, NULL, NULL, NULL,
+ lease->options, NULL, &global_scope, oc,
+ MDL) ||
+ (lease->server_id.len == 0)) {
+ /* This should be impossible... */
+ log_error("Invalid SERVERID option cache.");
+ dhc6_lease_destroy(&lease, MDL);
+ return;
+ }
+
+ if (client->active_lease != NULL)
+ dhc6_lease_destroy(&client->active_lease, MDL);
+
+ client->active_lease = lease;
+#endif /* defined(DHCPv6) */
+}
+
+/* Parse an ia_na object from the client lease.
+ */
+#ifdef DHCPv6
+static struct dhc6_ia *
+parse_client6_ia_na_statement(struct parse *cfile)
+{
+ struct option_cache *oc = NULL;
+ struct dhc6_ia *ia;
+ struct dhc6_addr **addr;
+ const char *val;
+ int token, no_semi, len;
+ u_int8_t buf[5];
+
+ ia = dmalloc(sizeof(*ia), MDL);
+ if (ia == NULL) {
+ parse_warn(cfile, "Out of memory allocating IA_NA state.");
+ skip_to_semi(cfile);
+ return NULL;
+ }
+ ia->ia_type = D6O_IA_NA;
+
+ /* Get IAID. */
+ len = parse_X(cfile, buf, 5);
+ if (len == 4) {
+ memcpy(ia->iaid, buf, 4);
+ } else {
+ parse_warn(cfile, "Expecting IAID of length 4, got %d.", len);
+ skip_to_semi(cfile);
+ dfree(ia, MDL);
+ return NULL;
+ }
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "Expecting open curly brace.");
+ skip_to_semi(cfile);
+ dfree(ia, MDL);
+ return NULL;
+ }
+
+ option_state_allocate(&ia->options, MDL);
+ if (ia->options == NULL) {
+ parse_warn(cfile, "Unable to allocate option state.");
+ skip_to_rbrace(cfile, 1);
+ dfree(ia, MDL);
+ return NULL;
+ }
+
+ addr = &ia->addrs;
+ token = next_token(&val, NULL, cfile);
+ while (token != RBRACE) {
+ no_semi = 0;
+
+ switch (token) {
+ case STARTS:
+ token = next_token(&val, NULL, cfile);
+ if (token == NUMBER) {
+ ia->starts = atoi(val);
+ } else {
+ parse_warn(cfile, "Expecting a number.");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ }
+ break;
+
+ case RENEW:
+ token = next_token(&val, NULL, cfile);
+ if (token == NUMBER) {
+ ia->renew = atoi(val);
+ } else {
+ parse_warn(cfile, "Expecting a number.");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ }
+ break;
+
+ case REBIND:
+ token = next_token(&val, NULL, cfile);
+ if (token == NUMBER) {
+ ia->rebind = atoi(val);
+ } else {
+ parse_warn(cfile, "Expecting a number.");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ }
+ break;
+
+ case IAADDR:
+ *addr = parse_client6_iaaddr_statement(cfile);
+
+ if (*addr != NULL)
+ addr = &(*addr)->next;
+
+ no_semi = 1;
+
+ break;
+
+ case OPTION:
+ if (parse_option_decl(&oc, cfile)) {
+ save_option(oc->option->universe,
+ ia->options, oc);
+ option_cache_dereference(&oc, MDL);
+ }
+ no_semi = 1;
+ break;
+
+ default:
+ parse_warn(cfile, "Unexpected token.");
+ no_semi = 1;
+ skip_to_semi(cfile);
+ break;
+ }
+
+ if (!no_semi)
+ parse_semi(cfile);
+
+ token = next_token(&val, NULL, cfile);
+
+ if (token == END_OF_FILE) {
+ parse_warn(cfile, "Unexpected end of file.");
+ break;
+ }
+ }
+
+ return ia;
+}
+#endif /* DHCPv6 */
+
+/* Parse an ia_ta object from the client lease.
+ */
+#ifdef DHCPv6
+static struct dhc6_ia *
+parse_client6_ia_ta_statement(struct parse *cfile)
+{
+ struct option_cache *oc = NULL;
+ struct dhc6_ia *ia;
+ struct dhc6_addr **addr;
+ const char *val;
+ int token, no_semi, len;
+ u_int8_t buf[5];
+
+ ia = dmalloc(sizeof(*ia), MDL);
+ if (ia == NULL) {
+ parse_warn(cfile, "Out of memory allocating IA_TA state.");
+ skip_to_semi(cfile);
+ return NULL;
+ }
+ ia->ia_type = D6O_IA_TA;
+
+ /* Get IAID. */
+ len = parse_X(cfile, buf, 5);
+ if (len == 4) {
+ memcpy(ia->iaid, buf, 4);
+ } else {
+ parse_warn(cfile, "Expecting IAID of length 4, got %d.", len);
+ skip_to_semi(cfile);
+ dfree(ia, MDL);
+ return NULL;
+ }
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "Expecting open curly brace.");
+ skip_to_semi(cfile);
+ dfree(ia, MDL);
+ return NULL;
+ }
+
+ option_state_allocate(&ia->options, MDL);
+ if (ia->options == NULL) {
+ parse_warn(cfile, "Unable to allocate option state.");
+ skip_to_rbrace(cfile, 1);
+ dfree(ia, MDL);
+ return NULL;
+ }
+
+ addr = &ia->addrs;
+ token = next_token(&val, NULL, cfile);
+ while (token != RBRACE) {
+ no_semi = 0;
+
+ switch (token) {
+ case STARTS:
+ token = next_token(&val, NULL, cfile);
+ if (token == NUMBER) {
+ ia->starts = atoi(val);
+ } else {
+ parse_warn(cfile, "Expecting a number.");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ }
+ break;
+
+ /* No RENEW or REBIND */
+
+ case IAADDR:
+ *addr = parse_client6_iaaddr_statement(cfile);
+
+ if (*addr != NULL)
+ addr = &(*addr)->next;
+
+ no_semi = 1;
+
+ break;
+
+ case OPTION:
+ if (parse_option_decl(&oc, cfile)) {
+ save_option(oc->option->universe,
+ ia->options, oc);
+ option_cache_dereference(&oc, MDL);
+ }
+ no_semi = 1;
+ break;
+
+ default:
+ parse_warn(cfile, "Unexpected token.");
+ no_semi = 1;
+ skip_to_semi(cfile);
+ break;
+ }
+
+ if (!no_semi)
+ parse_semi(cfile);
+
+ token = next_token(&val, NULL, cfile);
+
+ if (token == END_OF_FILE) {
+ parse_warn(cfile, "Unexpected end of file.");
+ break;
+ }
+ }
+
+ return ia;
+}
+#endif /* DHCPv6 */
+
+/* Parse an ia_pd object from the client lease.
+ */
+#ifdef DHCPv6
+static struct dhc6_ia *
+parse_client6_ia_pd_statement(struct parse *cfile)
+{
+ struct option_cache *oc = NULL;
+ struct dhc6_ia *ia;
+ struct dhc6_addr **pref;
+ const char *val;
+ int token, no_semi, len;
+ u_int8_t buf[5];
+
+ ia = dmalloc(sizeof(*ia), MDL);
+ if (ia == NULL) {
+ parse_warn(cfile, "Out of memory allocating IA_PD state.");
+ skip_to_semi(cfile);
+ return NULL;
+ }
+ ia->ia_type = D6O_IA_PD;
+
+ /* Get IAID. */
+ len = parse_X(cfile, buf, 5);
+ if (len == 4) {
+ memcpy(ia->iaid, buf, 4);
+ } else {
+ parse_warn(cfile, "Expecting IAID of length 4, got %d.", len);
+ skip_to_semi(cfile);
+ dfree(ia, MDL);
+ return NULL;
+ }
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "Expecting open curly brace.");
+ skip_to_semi(cfile);
+ dfree(ia, MDL);
+ return NULL;
+ }
+
+ option_state_allocate(&ia->options, MDL);
+ if (ia->options == NULL) {
+ parse_warn(cfile, "Unable to allocate option state.");
+ skip_to_rbrace(cfile, 1);
+ dfree(ia, MDL);
+ return NULL;
+ }
+
+ pref = &ia->addrs;
+ token = next_token(&val, NULL, cfile);
+ while (token != RBRACE) {
+ no_semi = 0;
+
+ switch (token) {
+ case STARTS:
+ token = next_token(&val, NULL, cfile);
+ if (token == NUMBER) {
+ ia->starts = atoi(val);
+ } else {
+ parse_warn(cfile, "Expecting a number.");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ }
+ break;
+
+ case RENEW:
+ token = next_token(&val, NULL, cfile);
+ if (token == NUMBER) {
+ ia->renew = atoi(val);
+ } else {
+ parse_warn(cfile, "Expecting a number.");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ }
+ break;
+
+ case REBIND:
+ token = next_token(&val, NULL, cfile);
+ if (token == NUMBER) {
+ ia->rebind = atoi(val);
+ } else {
+ parse_warn(cfile, "Expecting a number.");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ }
+ break;
+
+ case IAPREFIX:
+ *pref = parse_client6_iaprefix_statement(cfile);
+
+ if (*pref != NULL)
+ pref = &(*pref)->next;
+
+ no_semi = 1;
+
+ break;
+
+ case OPTION:
+ if (parse_option_decl(&oc, cfile)) {
+ save_option(oc->option->universe,
+ ia->options, oc);
+ option_cache_dereference(&oc, MDL);
+ }
+ no_semi = 1;
+ break;
+
+ default:
+ parse_warn(cfile, "Unexpected token.");
+ no_semi = 1;
+ skip_to_semi(cfile);
+ break;
+ }
+
+ if (!no_semi)
+ parse_semi(cfile);
+
+ token = next_token(&val, NULL, cfile);
+
+ if (token == END_OF_FILE) {
+ parse_warn(cfile, "Unexpected end of file.");
+ break;
+ }
+ }
+
+ return ia;
+}
+#endif /* DHCPv6 */
+
+/* Parse an iaaddr {} structure. */
+#ifdef DHCPv6
+static struct dhc6_addr *
+parse_client6_iaaddr_statement(struct parse *cfile)
+{
+ struct option_cache *oc = NULL;
+ struct dhc6_addr *addr;
+ const char *val;
+ int token, no_semi;
+
+ addr = dmalloc(sizeof(*addr), MDL);
+ if (addr == NULL) {
+ parse_warn(cfile, "Unable to allocate IAADDR state.");
+ skip_to_semi(cfile);
+ return NULL;
+ }
+
+ /* Get IP address. */
+ if (!parse_ip6_addr(cfile, &addr->address)) {
+ skip_to_semi(cfile);
+ dfree(addr, MDL);
+ return NULL;
+ }
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "Expecting open curly bracket.");
+ skip_to_semi(cfile);
+ dfree(addr, MDL);
+ return NULL;
+ }
+
+ option_state_allocate(&addr->options, MDL);
+ if (addr->options == NULL) {
+ parse_warn(cfile, "Unable to allocate option state.");
+ skip_to_semi(cfile);
+ dfree(addr, MDL);
+ return NULL;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ while (token != RBRACE) {
+ no_semi = 0;
+
+ switch (token) {
+ case STARTS:
+ token = next_token(&val, NULL, cfile);
+ if (token == NUMBER) {
+ addr->starts = atoi(val);
+ } else {
+ parse_warn(cfile, "Expecting a number.");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ }
+ break;
+
+ case PREFERRED_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token == NUMBER) {
+ addr->preferred_life = atoi(val);
+ } else {
+ parse_warn(cfile, "Expecting a number.");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ }
+ break;
+
+ case MAX_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token == NUMBER) {
+ addr->max_life = atoi(val);
+ } else {
+ parse_warn(cfile, "Expecting a number.");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ }
+ break;
+
+ case OPTION:
+ if (parse_option_decl(&oc, cfile)) {
+ save_option(oc->option->universe,
+ addr->options, oc);
+ option_cache_dereference(&oc, MDL);
+ }
+ no_semi = 1;
+ break;
+
+ default:
+ parse_warn(cfile, "Unexpected token.");
+ skip_to_rbrace(cfile, 1);
+ no_semi = 1;
+ break;
+ }
+
+ if (!no_semi)
+ parse_semi(cfile);
+
+ token = next_token(&val, NULL, cfile);
+ if (token == END_OF_FILE) {
+ parse_warn(cfile, "Unexpected end of file.");
+ break;
+ }
+ }
+
+ return addr;
+}
+#endif /* DHCPv6 */
+
+/* Parse an iaprefix {} structure. */
+#ifdef DHCPv6
+static struct dhc6_addr *
+parse_client6_iaprefix_statement(struct parse *cfile)
+{
+ struct option_cache *oc = NULL;
+ struct dhc6_addr *pref;
+ const char *val;
+ int token, no_semi;
+
+ pref = dmalloc(sizeof(*pref), MDL);
+ if (pref == NULL) {
+ parse_warn(cfile, "Unable to allocate IAPREFIX state.");
+ skip_to_semi(cfile);
+ return NULL;
+ }
+
+ /* Get IP prefix. */
+ if (!parse_ip6_prefix(cfile, &pref->address, &pref->plen)) {
+ skip_to_semi(cfile);
+ dfree(pref, MDL);
+ return NULL;
+ }
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "Expecting open curly bracket.");
+ skip_to_semi(cfile);
+ dfree(pref, MDL);
+ return NULL;
+ }
+
+ option_state_allocate(&pref->options, MDL);
+ if (pref->options == NULL) {
+ parse_warn(cfile, "Unable to allocate option state.");
+ skip_to_semi(cfile);
+ dfree(pref, MDL);
+ return NULL;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ while (token != RBRACE) {
+ no_semi = 0;
+
+ switch (token) {
+ case STARTS:
+ token = next_token(&val, NULL, cfile);
+ if (token == NUMBER) {
+ pref->starts = atoi(val);
+ } else {
+ parse_warn(cfile, "Expecting a number.");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ }
+ break;
+
+ case PREFERRED_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token == NUMBER) {
+ pref->preferred_life = atoi(val);
+ } else {
+ parse_warn(cfile, "Expecting a number.");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ }
+ break;
+
+ case MAX_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token == NUMBER) {
+ pref->max_life = atoi(val);
+ } else {
+ parse_warn(cfile, "Expecting a number.");
+ skip_to_semi(cfile);
+ no_semi = 1;
+ }
+ break;
+
+ case OPTION:
+ if (parse_option_decl(&oc, cfile)) {
+ save_option(oc->option->universe,
+ pref->options, oc);
+ option_cache_dereference(&oc, MDL);
+ }
+ no_semi = 1;
+ break;
+
+ default:
+ parse_warn(cfile, "Unexpected token.");
+ skip_to_rbrace(cfile, 1);
+ no_semi = 1;
+ break;
+ }
+
+ if (!no_semi)
+ parse_semi(cfile);
+
+ token = next_token(&val, NULL, cfile);
+ if (token == END_OF_FILE) {
+ parse_warn(cfile, "Unexpected end of file.");
+ break;
+ }
+ }
+
+ return pref;
+}
+#endif /* DHCPv6 */
+
+void parse_string_list (cfile, lp, multiple)
+ struct parse *cfile;
+ struct string_list **lp;
+ int multiple;
+{
+ int token;
+ const char *val;
+ struct string_list *cur, *tmp;
+
+ /* Find the last medium in the media list. */
+ if (*lp) {
+ for (cur = *lp; cur -> next; cur = cur -> next)
+ ;
+ } else {
+ cur = (struct string_list *)0;
+ }
+
+ do {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING) {
+ parse_warn (cfile, "Expecting media options.");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ tmp = ((struct string_list *)
+ dmalloc (strlen (val) + sizeof (struct string_list),
+ MDL));
+ if (!tmp)
+ log_fatal ("no memory for string list entry.");
+
+ strcpy (tmp -> string, val);
+ tmp -> next = (struct string_list *)0;
+
+ /* Store this medium at the end of the media list. */
+ if (cur)
+ cur -> next = tmp;
+ else
+ *lp = tmp;
+ cur = tmp;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ } while (multiple && token == COMMA);
+
+ if (token != SEMI) {
+ parse_warn (cfile, "expecting semicolon.");
+ skip_to_semi (cfile);
+ }
+}
+
+void parse_reject_statement (cfile, config)
+ struct parse *cfile;
+ struct client_config *config;
+{
+ int token;
+ const char *val;
+ struct iaddrmatch match;
+ struct iaddrmatchlist *list;
+ int i;
+
+ do {
+ if (!parse_ip_addr_with_subnet (cfile, &match)) {
+ /* no warn: parser will have reported what's wrong */
+ skip_to_semi (cfile);
+ return;
+ }
+
+ /* check mask is not all zeros (because that would
+ * reject EVERY address). This check could be
+ * simplified if we assume that the mask *always*
+ * represents a prefix .. but perhaps it might be
+ * useful to have a mask which is not a proper prefix
+ * (perhaps for ipv6?). The following is almost as
+ * efficient as inspection of match.mask.iabuf[0] when
+ * it IS a true prefix, and is more general when it is
+ * not.
+ */
+
+ for (i=0 ; i < match.mask.len ; i++) {
+ if (match.mask.iabuf[i]) {
+ break;
+ }
+ }
+
+ if (i == match.mask.len) {
+ /* oops we found all zeros */
+ parse_warn(cfile, "zero-length prefix is not permitted "
+ "for reject statement");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ list = dmalloc(sizeof(struct iaddrmatchlist), MDL);
+ if (!list)
+ log_fatal ("no memory for reject list!");
+
+ list->match = match;
+ list->next = config->reject_list;
+ config->reject_list = list;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ } while (token == COMMA);
+
+ if (token != SEMI) {
+ parse_warn (cfile, "expecting semicolon.");
+ skip_to_semi (cfile);
+ }
+}
+
+/* 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;
+{
+ parse_warn (cfile, "allow/deny/ignore not permitted here.");
+ skip_to_semi (cfile);
+ return 0;
+}
diff --git a/client/dhc6.c b/client/dhc6.c
new file mode 100644
index 0000000..889d186
--- /dev/null
+++ b/client/dhc6.c
@@ -0,0 +1,5174 @@
+/* dhc6.c - DHCPv6 client routines. */
+
+/*
+ * Copyright (c) 2012-2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2006-2010 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 "dhcpd.h"
+
+#ifdef DHCPv6
+
+struct sockaddr_in6 DHCPv6DestAddr;
+
+/*
+ * Option definition structures that are used by the software - declared
+ * here once and assigned at startup to save lookups.
+ */
+struct option *clientid_option = NULL;
+struct option *elapsed_option = NULL;
+struct option *ia_na_option = NULL;
+struct option *ia_ta_option = NULL;
+struct option *ia_pd_option = NULL;
+struct option *iaaddr_option = NULL;
+struct option *iaprefix_option = NULL;
+struct option *oro_option = NULL;
+struct option *irt_option = NULL;
+
+static struct dhc6_lease *dhc6_dup_lease(struct dhc6_lease *lease,
+ const char *file, int line);
+static struct dhc6_ia *dhc6_dup_ia(struct dhc6_ia *ia,
+ const char *file, int line);
+static struct dhc6_addr *dhc6_dup_addr(struct dhc6_addr *addr,
+ const char *file, int line);
+static void dhc6_ia_destroy(struct dhc6_ia **src, const char *file, int line);
+static isc_result_t dhc6_parse_ia_na(struct dhc6_ia **pia,
+ struct packet *packet,
+ struct option_state *options);
+static isc_result_t dhc6_parse_ia_ta(struct dhc6_ia **pia,
+ struct packet *packet,
+ struct option_state *options);
+static isc_result_t dhc6_parse_ia_pd(struct dhc6_ia **pia,
+ struct packet *packet,
+ struct option_state *options);
+static isc_result_t dhc6_parse_addrs(struct dhc6_addr **paddr,
+ struct packet *packet,
+ struct option_state *options);
+static isc_result_t dhc6_parse_prefixes(struct dhc6_addr **ppref,
+ struct packet *packet,
+ struct option_state *options);
+static struct dhc6_ia *find_ia(struct dhc6_ia *head,
+ u_int16_t type, const char *id);
+static struct dhc6_addr *find_addr(struct dhc6_addr *head,
+ struct iaddr *address);
+static struct dhc6_addr *find_pref(struct dhc6_addr *head,
+ struct iaddr *prefix, u_int8_t plen);
+void init_handler(struct packet *packet, struct client_state *client);
+void info_request_handler(struct packet *packet, struct client_state *client);
+void rapid_commit_handler(struct packet *packet, struct client_state *client);
+void do_init6(void *input);
+void do_info_request6(void *input);
+void do_confirm6(void *input);
+void reply_handler(struct packet *packet, struct client_state *client);
+static isc_result_t dhc6_add_ia_na(struct client_state *client,
+ struct data_string *packet,
+ struct dhc6_lease *lease,
+ u_int8_t message);
+static isc_result_t dhc6_add_ia_ta(struct client_state *client,
+ struct data_string *packet,
+ struct dhc6_lease *lease,
+ u_int8_t message);
+static isc_result_t dhc6_add_ia_pd(struct client_state *client,
+ struct data_string *packet,
+ struct dhc6_lease *lease,
+ u_int8_t message);
+static isc_boolean_t stopping_finished(void);
+static void dhc6_merge_lease(struct dhc6_lease *src, struct dhc6_lease *dst);
+void do_select6(void *input);
+void do_refresh6(void *input);
+static void do_release6(void *input);
+static void start_bound(struct client_state *client);
+static void start_informed(struct client_state *client);
+void informed_handler(struct packet *packet, struct client_state *client);
+void bound_handler(struct packet *packet, struct client_state *client);
+void start_renew6(void *input);
+void start_rebind6(void *input);
+void do_depref(void *input);
+void do_expire(void *input);
+static void make_client6_options(struct client_state *client,
+ struct option_state **op,
+ struct dhc6_lease *lease, u_int8_t message);
+static void script_write_params6(struct client_state *client,
+ const char *prefix,
+ struct option_state *options);
+static void script_write_requested6(struct client_state *client);
+static isc_boolean_t active_prefix(struct client_state *client);
+
+static int check_timing6(struct client_state *client, u_int8_t msg_type,
+ char *msg_str, struct dhc6_lease *lease,
+ struct data_string *ds);
+
+extern int onetry;
+extern int stateless;
+
+/*
+ * The "best" default DUID, since we cannot predict any information
+ * about the system (such as whether or not the hardware addresses are
+ * integrated into the motherboard or similar), is the "LLT", link local
+ * plus time, DUID. For real stateless "LL" is better.
+ *
+ * Once generated, this duid is stored into the state database, and
+ * retained across restarts.
+ *
+ * For the time being, there is probably a different state database for
+ * every daemon, so this winds up being a per-interface identifier...which
+ * is not how it is intended. Upcoming rearchitecting the client should
+ * address this "one daemon model."
+ */
+void
+form_duid(struct data_string *duid, const char *file, int line)
+{
+ struct interface_info *ip;
+ int len;
+
+ /* For now, just use the first interface on the list. */
+ ip = interfaces;
+
+ if (ip == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ if ((ip->hw_address.hlen == 0) ||
+ (ip->hw_address.hlen > sizeof(ip->hw_address.hbuf)))
+ log_fatal("Impossible hardware address length at %s:%d.", MDL);
+
+ if (duid_type == 0)
+ duid_type = stateless ? DUID_LL : DUID_LLT;
+
+ /*
+ * 2 bytes for the 'duid type' field.
+ * 2 bytes for the 'htype' field.
+ * (DUID_LLT) 4 bytes for the 'current time'.
+ * enough bytes for the hardware address (note that hw_address has
+ * the 'htype' on byte zero).
+ */
+ len = 4 + (ip->hw_address.hlen - 1);
+ if (duid_type == DUID_LLT)
+ len += 4;
+ if (!buffer_allocate(&duid->buffer, len, MDL))
+ log_fatal("no memory for default DUID!");
+ duid->data = duid->buffer->data;
+ duid->len = len;
+
+ /* Basic Link Local Address type of DUID. */
+ if (duid_type == DUID_LLT) {
+ putUShort(duid->buffer->data, DUID_LLT);
+ putUShort(duid->buffer->data + 2, ip->hw_address.hbuf[0]);
+ putULong(duid->buffer->data + 4, cur_time - DUID_TIME_EPOCH);
+ memcpy(duid->buffer->data + 8, ip->hw_address.hbuf + 1,
+ ip->hw_address.hlen - 1);
+ } else {
+ putUShort(duid->buffer->data, DUID_LL);
+ putUShort(duid->buffer->data + 2, ip->hw_address.hbuf[0]);
+ memcpy(duid->buffer->data + 4, ip->hw_address.hbuf + 1,
+ ip->hw_address.hlen - 1);
+ }
+}
+
+/*
+ * Assign DHCPv6 port numbers as a client.
+ */
+void
+dhcpv6_client_assignments(void)
+{
+ struct servent *ent;
+ unsigned code;
+
+ if (path_dhclient_pid == NULL)
+ path_dhclient_pid = _PATH_DHCLIENT6_PID;
+ if (path_dhclient_db == NULL)
+ path_dhclient_db = _PATH_DHCLIENT6_DB;
+
+ if (local_port == 0) {
+ ent = getservbyname("dhcpv6-client", "udp");
+ if (ent == NULL)
+ local_port = htons(546);
+ else
+ local_port = ent->s_port;
+ }
+
+ if (remote_port == 0) {
+ ent = getservbyname("dhcpv6-server", "udp");
+ if (ent == NULL)
+ remote_port = htons(547);
+ else
+ remote_port = ent->s_port;
+ }
+
+ memset(&DHCPv6DestAddr, 0, sizeof(DHCPv6DestAddr));
+ DHCPv6DestAddr.sin6_family = AF_INET6;
+ DHCPv6DestAddr.sin6_port = remote_port;
+ if (inet_pton(AF_INET6, All_DHCP_Relay_Agents_and_Servers,
+ &DHCPv6DestAddr.sin6_addr) <= 0) {
+ log_fatal("Bad address %s", All_DHCP_Relay_Agents_and_Servers);
+ }
+
+ code = D6O_CLIENTID;
+ if (!option_code_hash_lookup(&clientid_option,
+ dhcpv6_universe.code_hash, &code, 0, MDL))
+ log_fatal("Unable to find the CLIENTID option definition.");
+
+ code = D6O_ELAPSED_TIME;
+ if (!option_code_hash_lookup(&elapsed_option,
+ dhcpv6_universe.code_hash, &code, 0, MDL))
+ log_fatal("Unable to find the ELAPSED_TIME option definition.");
+
+ code = D6O_IA_NA;
+ if (!option_code_hash_lookup(&ia_na_option, dhcpv6_universe.code_hash,
+ &code, 0, MDL))
+ log_fatal("Unable to find the IA_NA option definition.");
+
+ code = D6O_IA_TA;
+ if (!option_code_hash_lookup(&ia_ta_option, dhcpv6_universe.code_hash,
+ &code, 0, MDL))
+ log_fatal("Unable to find the IA_TA option definition.");
+
+ code = D6O_IA_PD;
+ if (!option_code_hash_lookup(&ia_pd_option, dhcpv6_universe.code_hash,
+ &code, 0, MDL))
+ log_fatal("Unable to find the IA_PD option definition.");
+
+ code = D6O_IAADDR;
+ if (!option_code_hash_lookup(&iaaddr_option, dhcpv6_universe.code_hash,
+ &code, 0, MDL))
+ log_fatal("Unable to find the IAADDR option definition.");
+
+ code = D6O_IAPREFIX;
+ if (!option_code_hash_lookup(&iaprefix_option,
+ dhcpv6_universe.code_hash,
+ &code, 0, MDL))
+ log_fatal("Unable to find the IAPREFIX option definition.");
+
+ code = D6O_ORO;
+ if (!option_code_hash_lookup(&oro_option, dhcpv6_universe.code_hash,
+ &code, 0, MDL))
+ log_fatal("Unable to find the ORO option definition.");
+
+ code = D6O_INFORMATION_REFRESH_TIME;
+ if (!option_code_hash_lookup(&irt_option, dhcpv6_universe.code_hash,
+ &code, 0, MDL))
+ log_fatal("Unable to find the IRT option definition.");
+
+#ifndef __CYGWIN32__ /* XXX */
+ endservent();
+#endif
+}
+
+/*
+ * Instead of implementing RFC3315 RAND (section 14) as a float "between"
+ * -0.1 and 0.1 non-inclusive, we implement it as an integer.
+ *
+ * The result is expected to follow this table:
+ *
+ * split range answer
+ * - ERROR - base <= 0
+ * 0 1 0..0 1 <= base <= 10
+ * 1 3 -1..1 11 <= base <= 20
+ * 2 5 -2..2 21 <= base <= 30
+ * 3 7 -3..3 31 <= base <= 40
+ * ...
+ *
+ * XXX: For this to make sense, we really need to do timing on a
+ * XXX: usec scale...we currently can assume zero for any value less than
+ * XXX: 11, which are very common in early stages of transmission for most
+ * XXX: messages.
+ */
+static TIME
+dhc6_rand(TIME base)
+{
+ TIME rval;
+ TIME range;
+ TIME split;
+
+ /*
+ * A zero or less timeout is a bad thing...we don't want to
+ * DHCP-flood anyone.
+ */
+ if (base <= 0)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ /*
+ * The first thing we do is count how many random integers we want
+ * in either direction (best thought of as the maximum negative
+ * integer, as we will subtract this potentially from a random 0).
+ */
+ split = (base - 1) / 10;
+
+ /* Don't bother with the rest of the math if we know we'll get 0. */
+ if (split == 0)
+ return 0;
+
+ /*
+ * Then we count the total number of integers in this set. This
+ * is twice the number of integers in positive and negative
+ * directions, plus zero (-1, 0, 1 is 3, -2..2 adds 2 to 5, so forth).
+ */
+ range = (split * 2) + 1;
+
+ /* Take a random number from [0..(range-1)]. */
+ rval = random();
+ rval %= range;
+
+ /* Offset it to uncover potential negative values. */
+ rval -= split;
+
+ return rval;
+}
+
+/* Initialize message exchange timers (set RT from Initial-RT). */
+static void
+dhc6_retrans_init(struct client_state *client)
+{
+ int xid;
+
+ /* Initialize timers. */
+ client->txcount = 0;
+ client->RT = client->IRT + dhc6_rand(client->IRT);
+
+ /* Generate a new random 24-bit transaction ID for this exchange. */
+
+#if (RAND_MAX >= 0x00ffffff)
+ xid = random();
+#elif (RAND_MAX >= 0x0000ffff)
+ xid = (random() << 16) ^ random();
+#elif (RAND_MAX >= 0x000000ff)
+ xid = (random() << 16) ^ (random() << 8) ^ random();
+#else
+# error "Random number generator of less than 8 bits not supported."
+#endif
+
+ client->dhcpv6_transaction_id[0] = (xid >> 16) & 0xff;
+ client->dhcpv6_transaction_id[1] = (xid >> 8) & 0xff;
+ client->dhcpv6_transaction_id[2] = xid & 0xff;
+}
+
+/* Advance the DHCPv6 retransmission state once. */
+static void
+dhc6_retrans_advance(struct client_state *client)
+{
+ struct timeval elapsed, elapsed_plus_rt;
+
+ /* elapsed = cur - start */
+ elapsed.tv_sec = cur_tv.tv_sec - client->start_time.tv_sec;
+ elapsed.tv_usec = cur_tv.tv_usec - client->start_time.tv_usec;
+ if (elapsed.tv_usec < 0) {
+ elapsed.tv_sec -= 1;
+ elapsed.tv_usec += 1000000;
+ }
+ /* retrans_advance is called after consuming client->RT. */
+ /* elapsed += RT */
+ elapsed.tv_sec += client->RT / 100;
+ elapsed.tv_usec += (client->RT % 100) * 10000;
+ if (elapsed.tv_usec >= 1000000) {
+ elapsed.tv_sec += 1;
+ elapsed.tv_usec -= 1000000;
+ }
+ /*
+ * Save what the time will be after the current RT to determine
+ * what the delta to MRD will be.
+ */
+ elapsed_plus_rt.tv_sec = elapsed.tv_sec;
+ elapsed_plus_rt.tv_usec = elapsed.tv_usec;
+
+ /*
+ * RT for each subsequent message transmission is based on the previous
+ * value of RT:
+ *
+ * RT = 2*RTprev + RAND*RTprev
+ */
+ client->RT += client->RT + dhc6_rand(client->RT);
+
+ /*
+ * MRT specifies an upper bound on the value of RT (disregarding the
+ * randomization added by the use of RAND). If MRT has a value of 0,
+ * there is no upper limit on the value of RT. Otherwise:
+ *
+ * if (RT > MRT)
+ * RT = MRT + RAND*MRT
+ */
+ if ((client->MRT != 0) && (client->RT > client->MRT))
+ client->RT = client->MRT + dhc6_rand(client->MRT);
+
+ /*
+ * Further, if there's an MRD, we should wake up upon reaching
+ * the MRD rather than at some point after it.
+ */
+ if (client->MRD == 0) {
+ /* Done. */
+ client->txcount++;
+ return;
+ }
+ /* elapsed += client->RT */
+ elapsed.tv_sec += client->RT / 100;
+ elapsed.tv_usec += (client->RT % 100) * 10000;
+ if (elapsed.tv_usec >= 1000000) {
+ elapsed.tv_sec += 1;
+ elapsed.tv_usec -= 1000000;
+ }
+ if (elapsed.tv_sec >= client->MRD) {
+ /*
+ * The desired RT is the time that will be remaining in MRD
+ * when the current timeout finishes. We then have
+ * desired RT = MRD - (elapsed time + previous RT); or
+ * desired RT = MRD - elapsed_plut_rt;
+ */
+ client->RT = client->MRD - elapsed_plus_rt.tv_sec;
+ client->RT = (client->RT * 100) -
+ (elapsed_plus_rt.tv_usec / 10000);
+ if (client->RT < 0)
+ client->RT = 0;
+ }
+ client->txcount++;
+}
+
+/* Quick validation of DHCPv6 ADVERTISE packet contents. */
+static int
+valid_reply(struct packet *packet, struct client_state *client)
+{
+ struct data_string sid, cid;
+ struct option_cache *oc;
+ int rval = ISC_TRUE;
+
+ memset(&sid, 0, sizeof(sid));
+ memset(&cid, 0, sizeof(cid));
+
+ if (!lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID)) {
+ log_error("Response without a server identifier received.");
+ rval = ISC_FALSE;
+ }
+
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_CLIENTID);
+ if (!oc ||
+ !evaluate_option_cache(&sid, packet, NULL, client, packet->options,
+ client->sent_options, &global_scope, oc,
+ MDL)) {
+ log_error("Response without a client identifier.");
+ rval = ISC_FALSE;
+ }
+
+ oc = lookup_option(&dhcpv6_universe, client->sent_options,
+ D6O_CLIENTID);
+ if (!oc ||
+ !evaluate_option_cache(&cid, packet, NULL, client,
+ client->sent_options, NULL, &global_scope,
+ oc, MDL)) {
+ log_error("Local client identifier is missing!");
+ rval = ISC_FALSE;
+ }
+
+ if (sid.len == 0 ||
+ sid.len != cid.len ||
+ memcmp(sid.data, cid.data, sid.len)) {
+ log_error("Advertise with matching transaction ID, but "
+ "mismatching client id.");
+ rval = ISC_FALSE;
+ }
+
+ return rval;
+}
+
+/*
+ * Create a complete copy of a DHCPv6 lease structure.
+ */
+static struct dhc6_lease *
+dhc6_dup_lease(struct dhc6_lease *lease, const char *file, int line)
+{
+ struct dhc6_lease *copy;
+ struct dhc6_ia **insert_ia, *ia;
+
+ copy = dmalloc(sizeof(*copy), file, line);
+ if (copy == NULL) {
+ log_error("Out of memory for v6 lease structure.");
+ return NULL;
+ }
+
+ data_string_copy(&copy->server_id, &lease->server_id, file, line);
+ copy->pref = lease->pref;
+
+ memcpy(copy->dhcpv6_transaction_id, lease->dhcpv6_transaction_id,
+ sizeof(copy->dhcpv6_transaction_id));
+
+ option_state_reference(&copy->options, lease->options, file, line);
+
+ insert_ia = &copy->bindings;
+ for (ia = lease->bindings ; ia != NULL ; ia = ia->next) {
+ *insert_ia = dhc6_dup_ia(ia, file, line);
+
+ if (*insert_ia == NULL) {
+ dhc6_lease_destroy(&copy, file, line);
+ return NULL;
+ }
+
+ insert_ia = &(*insert_ia)->next;
+ }
+
+ return copy;
+}
+
+/*
+ * Duplicate an IA structure.
+ */
+static struct dhc6_ia *
+dhc6_dup_ia(struct dhc6_ia *ia, const char *file, int line)
+{
+ struct dhc6_ia *copy;
+ struct dhc6_addr **insert_addr, *addr;
+
+ copy = dmalloc(sizeof(*ia), file, line);
+
+ memcpy(copy->iaid, ia->iaid, sizeof(copy->iaid));
+
+ copy->ia_type = ia->ia_type;
+ copy->starts = ia->starts;
+ copy->renew = ia->renew;
+ copy->rebind = ia->rebind;
+
+ insert_addr = &copy->addrs;
+ for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
+ *insert_addr = dhc6_dup_addr(addr, file, line);
+
+ if (*insert_addr == NULL) {
+ dhc6_ia_destroy(&copy, file, line);
+ return NULL;
+ }
+
+ insert_addr = &(*insert_addr)->next;
+ }
+
+ if (ia->options != NULL)
+ option_state_reference(&copy->options, ia->options,
+ file, line);
+
+ return copy;
+}
+
+/*
+ * Duplicate an IAADDR or IAPREFIX structure.
+ */
+static struct dhc6_addr *
+dhc6_dup_addr(struct dhc6_addr *addr, const char *file, int line)
+{
+ struct dhc6_addr *copy;
+
+ copy = dmalloc(sizeof(*addr), file, line);
+
+ if (copy == NULL)
+ return NULL;
+
+ memcpy(&copy->address, &addr->address, sizeof(copy->address));
+
+ copy->plen = addr->plen;
+ copy->flags = addr->flags;
+ copy->starts = addr->starts;
+ copy->preferred_life = addr->preferred_life;
+ copy->max_life = addr->max_life;
+
+ if (addr->options != NULL)
+ option_state_reference(&copy->options, addr->options,
+ file, line);
+
+ return copy;
+}
+
+/*
+ * Form a DHCPv6 lease structure based upon packet contents. Creates and
+ * populates IA's and any IAADDR/IAPREFIX's they contain.
+ * Parsed options are deleted in order to not save them in the lease file.
+ */
+static struct dhc6_lease *
+dhc6_leaseify(struct packet *packet)
+{
+ struct data_string ds;
+ struct dhc6_lease *lease;
+ struct option_cache *oc;
+
+ lease = dmalloc(sizeof(*lease), MDL);
+ if (lease == NULL) {
+ log_error("Out of memory for v6 lease structure.");
+ return NULL;
+ }
+
+ memcpy(lease->dhcpv6_transaction_id, packet->dhcpv6_transaction_id, 3);
+ option_state_reference(&lease->options, packet->options, MDL);
+
+ memset(&ds, 0, sizeof(ds));
+
+ /* Determine preference (default zero). */
+ oc = lookup_option(&dhcpv6_universe, lease->options, D6O_PREFERENCE);
+ if (oc &&
+ evaluate_option_cache(&ds, packet, NULL, NULL, lease->options,
+ NULL, &global_scope, oc, MDL)) {
+ if (ds.len != 1) {
+ log_error("Invalid length of DHCPv6 Preference option "
+ "(%d != 1)", ds.len);
+ data_string_forget(&ds, MDL);
+ dhc6_lease_destroy(&lease, MDL);
+ return NULL;
+ } else {
+ lease->pref = ds.data[0];
+ log_debug("RCV: X-- Preference %u.",
+ (unsigned)lease->pref);
+ }
+
+ data_string_forget(&ds, MDL);
+ }
+ delete_option(&dhcpv6_universe, lease->options, D6O_PREFERENCE);
+
+ /*
+ * Dig into recursive DHCPv6 pockets for IA_NA and contained IAADDR
+ * options.
+ */
+ if (dhc6_parse_ia_na(&lease->bindings, packet,
+ lease->options) != ISC_R_SUCCESS) {
+ /* Error conditions are logged by the caller. */
+ dhc6_lease_destroy(&lease, MDL);
+ return NULL;
+ }
+ /*
+ * Dig into recursive DHCPv6 pockets for IA_TA and contained IAADDR
+ * options.
+ */
+ if (dhc6_parse_ia_ta(&lease->bindings, packet,
+ lease->options) != ISC_R_SUCCESS) {
+ /* Error conditions are logged by the caller. */
+ dhc6_lease_destroy(&lease, MDL);
+ return NULL;
+ }
+ /*
+ * Dig into recursive DHCPv6 pockets for IA_PD and contained IAPREFIX
+ * options.
+ */
+ if (dhc6_parse_ia_pd(&lease->bindings, packet,
+ lease->options) != ISC_R_SUCCESS) {
+ /* Error conditions are logged by the caller. */
+ dhc6_lease_destroy(&lease, MDL);
+ return NULL;
+ }
+
+ /*
+ * This is last because in the future we may want to make a different
+ * key based upon additional information from the packet (we may need
+ * to allow multiple leases in one client state per server, but we're
+ * not sure based on what additional keys now).
+ */
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
+ if ((oc == NULL) ||
+ !evaluate_option_cache(&lease->server_id, packet, NULL, NULL,
+ lease->options, NULL, &global_scope,
+ oc, MDL) ||
+ lease->server_id.len == 0) {
+ /* This should be impossible due to validation checks earlier.
+ */
+ log_error("Invalid SERVERID option cache.");
+ dhc6_lease_destroy(&lease, MDL);
+ return NULL;
+ } else {
+ log_debug("RCV: X-- Server ID: %s",
+ print_hex_1(lease->server_id.len,
+ lease->server_id.data, 52));
+ }
+
+ return lease;
+}
+
+static isc_result_t
+dhc6_parse_ia_na(struct dhc6_ia **pia, struct packet *packet,
+ struct option_state *options)
+{
+ struct data_string ds;
+ struct dhc6_ia *ia;
+ struct option_cache *oc;
+ isc_result_t result;
+
+ memset(&ds, 0, sizeof(ds));
+
+ oc = lookup_option(&dhcpv6_universe, options, D6O_IA_NA);
+ for ( ; oc != NULL ; oc = oc->next) {
+ ia = dmalloc(sizeof(*ia), MDL);
+ if (ia == NULL) {
+ log_error("Out of memory allocating IA_NA structure.");
+ return ISC_R_NOMEMORY;
+ } else if (evaluate_option_cache(&ds, packet, NULL, NULL,
+ options, NULL,
+ &global_scope, oc, MDL) &&
+ ds.len >= 12) {
+ memcpy(ia->iaid, ds.data, 4);
+ ia->ia_type = D6O_IA_NA;
+ ia->starts = cur_time;
+ ia->renew = getULong(ds.data + 4);
+ ia->rebind = getULong(ds.data + 8);
+
+ log_debug("RCV: X-- IA_NA %s",
+ print_hex_1(4, ia->iaid, 59));
+ /* XXX: This should be the printed time I think. */
+ log_debug("RCV: | X-- starts %u",
+ (unsigned)ia->starts);
+ log_debug("RCV: | X-- t1 - renew +%u", ia->renew);
+ log_debug("RCV: | X-- t2 - rebind +%u", ia->rebind);
+
+ /*
+ * RFC3315 section 22.4, discard IA_NA's that
+ * have t1 greater than t2, and both not zero.
+ * Since RFC3315 defines this behaviour, it is not
+ * an error - just normal operation.
+ *
+ * Note that RFC3315 says we MUST honor these values
+ * if they are not zero. So insane values are
+ * totally OK.
+ */
+ if ((ia->renew > 0) && (ia->rebind > 0) &&
+ (ia->renew > ia->rebind)) {
+ log_debug("RCV: | !-- INVALID renew/rebind "
+ "times, IA_NA discarded.");
+ dfree(ia, MDL);
+ data_string_forget(&ds, MDL);
+ continue;
+ }
+
+ if (ds.len > 12) {
+ log_debug("RCV: | X-- [Options]");
+
+ if (!option_state_allocate(&ia->options,
+ MDL)) {
+ log_error("Out of memory allocating "
+ "IA_NA option state.");
+ dfree(ia, MDL);
+ data_string_forget(&ds, MDL);
+ return ISC_R_NOMEMORY;
+ }
+
+ if (!parse_option_buffer(ia->options,
+ ds.data + 12,
+ ds.len - 12,
+ &dhcpv6_universe)) {
+ log_error("Corrupt IA_NA options.");
+ option_state_dereference(&ia->options,
+ MDL);
+ dfree(ia, MDL);
+ data_string_forget(&ds, MDL);
+ return DHCP_R_BADPARSE;
+ }
+ }
+ data_string_forget(&ds, MDL);
+
+ if (ia->options != NULL) {
+ result = dhc6_parse_addrs(&ia->addrs, packet,
+ ia->options);
+ if (result != ISC_R_SUCCESS) {
+ option_state_dereference(&ia->options,
+ MDL);
+ dfree(ia, MDL);
+ return result;
+ }
+ }
+
+ while (*pia != NULL)
+ pia = &(*pia)->next;
+ *pia = ia;
+ pia = &ia->next;
+ } else {
+ log_error("Invalid IA_NA option cache.");
+ dfree(ia, MDL);
+ if (ds.len != 0)
+ data_string_forget(&ds, MDL);
+ return ISC_R_UNEXPECTED;
+ }
+ }
+ delete_option(&dhcpv6_universe, options, D6O_IA_NA);
+
+ return ISC_R_SUCCESS;
+}
+
+static isc_result_t
+dhc6_parse_ia_ta(struct dhc6_ia **pia, struct packet *packet,
+ struct option_state *options)
+{
+ struct data_string ds;
+ struct dhc6_ia *ia;
+ struct option_cache *oc;
+ isc_result_t result;
+
+ memset(&ds, 0, sizeof(ds));
+
+ oc = lookup_option(&dhcpv6_universe, options, D6O_IA_TA);
+ for ( ; oc != NULL ; oc = oc->next) {
+ ia = dmalloc(sizeof(*ia), MDL);
+ if (ia == NULL) {
+ log_error("Out of memory allocating IA_TA structure.");
+ return ISC_R_NOMEMORY;
+ } else if (evaluate_option_cache(&ds, packet, NULL, NULL,
+ options, NULL,
+ &global_scope, oc, MDL) &&
+ ds.len >= 4) {
+ memcpy(ia->iaid, ds.data, 4);
+ ia->ia_type = D6O_IA_TA;
+ ia->starts = cur_time;
+
+ log_debug("RCV: X-- IA_TA %s",
+ print_hex_1(4, ia->iaid, 59));
+ /* XXX: This should be the printed time I think. */
+ log_debug("RCV: | X-- starts %u",
+ (unsigned)ia->starts);
+
+ if (ds.len > 4) {
+ log_debug("RCV: | X-- [Options]");
+
+ if (!option_state_allocate(&ia->options,
+ MDL)) {
+ log_error("Out of memory allocating "
+ "IA_TA option state.");
+ dfree(ia, MDL);
+ data_string_forget(&ds, MDL);
+ return ISC_R_NOMEMORY;
+ }
+
+ if (!parse_option_buffer(ia->options,
+ ds.data + 4,
+ ds.len - 4,
+ &dhcpv6_universe)) {
+ log_error("Corrupt IA_TA options.");
+ option_state_dereference(&ia->options,
+ MDL);
+ dfree(ia, MDL);
+ data_string_forget(&ds, MDL);
+ return DHCP_R_BADPARSE;
+ }
+ }
+ data_string_forget(&ds, MDL);
+
+ if (ia->options != NULL) {
+ result = dhc6_parse_addrs(&ia->addrs, packet,
+ ia->options);
+ if (result != ISC_R_SUCCESS) {
+ option_state_dereference(&ia->options,
+ MDL);
+ dfree(ia, MDL);
+ return result;
+ }
+ }
+
+ while (*pia != NULL)
+ pia = &(*pia)->next;
+ *pia = ia;
+ pia = &ia->next;
+ } else {
+ log_error("Invalid IA_TA option cache.");
+ dfree(ia, MDL);
+ if (ds.len != 0)
+ data_string_forget(&ds, MDL);
+ return ISC_R_UNEXPECTED;
+ }
+ }
+ delete_option(&dhcpv6_universe, options, D6O_IA_TA);
+
+ return ISC_R_SUCCESS;
+}
+
+static isc_result_t
+dhc6_parse_ia_pd(struct dhc6_ia **pia, struct packet *packet,
+ struct option_state *options)
+{
+ struct data_string ds;
+ struct dhc6_ia *ia;
+ struct option_cache *oc;
+ isc_result_t result;
+
+ memset(&ds, 0, sizeof(ds));
+
+ oc = lookup_option(&dhcpv6_universe, options, D6O_IA_PD);
+ for ( ; oc != NULL ; oc = oc->next) {
+ ia = dmalloc(sizeof(*ia), MDL);
+ if (ia == NULL) {
+ log_error("Out of memory allocating IA_PD structure.");
+ return ISC_R_NOMEMORY;
+ } else if (evaluate_option_cache(&ds, packet, NULL, NULL,
+ options, NULL,
+ &global_scope, oc, MDL) &&
+ ds.len >= 12) {
+ memcpy(ia->iaid, ds.data, 4);
+ ia->ia_type = D6O_IA_PD;
+ ia->starts = cur_time;
+ ia->renew = getULong(ds.data + 4);
+ ia->rebind = getULong(ds.data + 8);
+
+ log_debug("RCV: X-- IA_PD %s",
+ print_hex_1(4, ia->iaid, 59));
+ /* XXX: This should be the printed time I think. */
+ log_debug("RCV: | X-- starts %u",
+ (unsigned)ia->starts);
+ log_debug("RCV: | X-- t1 - renew +%u", ia->renew);
+ log_debug("RCV: | X-- t2 - rebind +%u", ia->rebind);
+
+ /*
+ * RFC3633 section 9, discard IA_PD's that
+ * have t1 greater than t2, and both not zero.
+ * Since RFC3633 defines this behaviour, it is not
+ * an error - just normal operation.
+ */
+ if ((ia->renew > 0) && (ia->rebind > 0) &&
+ (ia->renew > ia->rebind)) {
+ log_debug("RCV: | !-- INVALID renew/rebind "
+ "times, IA_PD discarded.");
+ dfree(ia, MDL);
+ data_string_forget(&ds, MDL);
+ continue;
+ }
+
+ if (ds.len > 12) {
+ log_debug("RCV: | X-- [Options]");
+
+ if (!option_state_allocate(&ia->options,
+ MDL)) {
+ log_error("Out of memory allocating "
+ "IA_PD option state.");
+ dfree(ia, MDL);
+ data_string_forget(&ds, MDL);
+ return ISC_R_NOMEMORY;
+ }
+
+ if (!parse_option_buffer(ia->options,
+ ds.data + 12,
+ ds.len - 12,
+ &dhcpv6_universe)) {
+ log_error("Corrupt IA_PD options.");
+ option_state_dereference(&ia->options,
+ MDL);
+ dfree(ia, MDL);
+ data_string_forget(&ds, MDL);
+ return DHCP_R_BADPARSE;
+ }
+ }
+ data_string_forget(&ds, MDL);
+
+ if (ia->options != NULL) {
+ result = dhc6_parse_prefixes(&ia->addrs,
+ packet,
+ ia->options);
+ if (result != ISC_R_SUCCESS) {
+ option_state_dereference(&ia->options,
+ MDL);
+ dfree(ia, MDL);
+ return result;
+ }
+ }
+
+ while (*pia != NULL)
+ pia = &(*pia)->next;
+ *pia = ia;
+ pia = &ia->next;
+ } else {
+ log_error("Invalid IA_PD option cache.");
+ dfree(ia, MDL);
+ if (ds.len != 0)
+ data_string_forget(&ds, MDL);
+ return ISC_R_UNEXPECTED;
+ }
+ }
+ delete_option(&dhcpv6_universe, options, D6O_IA_PD);
+
+ return ISC_R_SUCCESS;
+}
+
+
+static isc_result_t
+dhc6_parse_addrs(struct dhc6_addr **paddr, struct packet *packet,
+ struct option_state *options)
+{
+ struct data_string ds;
+ struct option_cache *oc;
+ struct dhc6_addr *addr;
+
+ memset(&ds, 0, sizeof(ds));
+
+ oc = lookup_option(&dhcpv6_universe, options, D6O_IAADDR);
+ for ( ; oc != NULL ; oc = oc->next) {
+ addr = dmalloc(sizeof(*addr), MDL);
+ if (addr == NULL) {
+ log_error("Out of memory allocating "
+ "address structure.");
+ return ISC_R_NOMEMORY;
+ } else if (evaluate_option_cache(&ds, packet, NULL, NULL,
+ options, NULL, &global_scope,
+ oc, MDL) &&
+ (ds.len >= 24)) {
+
+ addr->address.len = 16;
+ memcpy(addr->address.iabuf, ds.data, 16);
+ addr->starts = cur_time;
+ addr->preferred_life = getULong(ds.data + 16);
+ addr->max_life = getULong(ds.data + 20);
+
+ log_debug("RCV: | | X-- IAADDR %s",
+ piaddr(addr->address));
+ log_debug("RCV: | | | X-- Preferred lifetime %u.",
+ addr->preferred_life);
+ log_debug("RCV: | | | X-- Max lifetime %u.",
+ addr->max_life);
+
+ /*
+ * RFC 3315 section 22.6 says we must discard
+ * addresses whose pref is later than valid.
+ */
+ if ((addr->preferred_life > addr->max_life)) {
+ log_debug("RCV: | | | !-- INVALID lifetimes, "
+ "IAADDR discarded. Check your "
+ "server configuration.");
+ dfree(addr, MDL);
+ data_string_forget(&ds, MDL);
+ continue;
+ }
+
+ /*
+ * Fortunately this is the last recursion in the
+ * protocol.
+ */
+ if (ds.len > 24) {
+ if (!option_state_allocate(&addr->options,
+ MDL)) {
+ log_error("Out of memory allocating "
+ "IAADDR option state.");
+ dfree(addr, MDL);
+ data_string_forget(&ds, MDL);
+ return ISC_R_NOMEMORY;
+ }
+
+ if (!parse_option_buffer(addr->options,
+ ds.data + 24,
+ ds.len - 24,
+ &dhcpv6_universe)) {
+ log_error("Corrupt IAADDR options.");
+ option_state_dereference(&addr->options,
+ MDL);
+ dfree(addr, MDL);
+ data_string_forget(&ds, MDL);
+ return DHCP_R_BADPARSE;
+ }
+ }
+
+ if (addr->options != NULL)
+ log_debug("RCV: | | | X-- "
+ "[Options]");
+
+ data_string_forget(&ds, MDL);
+
+ *paddr = addr;
+ paddr = &addr->next;
+ } else {
+ log_error("Invalid IAADDR option cache.");
+ dfree(addr, MDL);
+ if (ds.len != 0)
+ data_string_forget(&ds, MDL);
+ return ISC_R_UNEXPECTED;
+ }
+ }
+ delete_option(&dhcpv6_universe, options, D6O_IAADDR);
+
+ return ISC_R_SUCCESS;
+}
+
+static isc_result_t
+dhc6_parse_prefixes(struct dhc6_addr **ppfx, struct packet *packet,
+ struct option_state *options)
+{
+ struct data_string ds;
+ struct option_cache *oc;
+ struct dhc6_addr *pfx;
+
+ memset(&ds, 0, sizeof(ds));
+
+ oc = lookup_option(&dhcpv6_universe, options, D6O_IAPREFIX);
+ for ( ; oc != NULL ; oc = oc->next) {
+ pfx = dmalloc(sizeof(*pfx), MDL);
+ if (pfx == NULL) {
+ log_error("Out of memory allocating "
+ "prefix structure.");
+ return ISC_R_NOMEMORY;
+ } else if (evaluate_option_cache(&ds, packet, NULL, NULL,
+ options, NULL, &global_scope,
+ oc, MDL) &&
+ (ds.len >= 25)) {
+
+ pfx->preferred_life = getULong(ds.data);
+ pfx->max_life = getULong(ds.data + 4);
+ pfx->plen = getUChar(ds.data + 8);
+ pfx->address.len = 16;
+ memcpy(pfx->address.iabuf, ds.data + 9, 16);
+ pfx->starts = cur_time;
+
+ log_debug("RCV: | | X-- IAPREFIX %s/%d",
+ piaddr(pfx->address), (int)pfx->plen);
+ log_debug("RCV: | | | X-- Preferred lifetime %u.",
+ pfx->preferred_life);
+ log_debug("RCV: | | | X-- Max lifetime %u.",
+ pfx->max_life);
+
+ /* Sanity check over the prefix length */
+ if ((pfx->plen < 4) || (pfx->plen > 128)) {
+ log_debug("RCV: | | | !-- INVALID prefix "
+ "length, IAPREFIX discarded. "
+ "Check your server configuration.");
+ dfree(pfx, MDL);
+ data_string_forget(&ds, MDL);
+ continue;
+ }
+ /*
+ * RFC 3633 section 10 says we must discard
+ * prefixes whose pref is later than valid.
+ */
+ if ((pfx->preferred_life > pfx->max_life)) {
+ log_debug("RCV: | | | !-- INVALID lifetimes, "
+ "IAPREFIX discarded. Check your "
+ "server configuration.");
+ dfree(pfx, MDL);
+ data_string_forget(&ds, MDL);
+ continue;
+ }
+
+ /*
+ * Fortunately this is the last recursion in the
+ * protocol.
+ */
+ if (ds.len > 25) {
+ if (!option_state_allocate(&pfx->options,
+ MDL)) {
+ log_error("Out of memory allocating "
+ "IAPREFIX option state.");
+ dfree(pfx, MDL);
+ data_string_forget(&ds, MDL);
+ return ISC_R_NOMEMORY;
+ }
+
+ if (!parse_option_buffer(pfx->options,
+ ds.data + 25,
+ ds.len - 25,
+ &dhcpv6_universe)) {
+ log_error("Corrupt IAPREFIX options.");
+ option_state_dereference(&pfx->options,
+ MDL);
+ dfree(pfx, MDL);
+ data_string_forget(&ds, MDL);
+ return DHCP_R_BADPARSE;
+ }
+ }
+
+ if (pfx->options != NULL)
+ log_debug("RCV: | | | X-- "
+ "[Options]");
+
+ data_string_forget(&ds, MDL);
+
+ *ppfx = pfx;
+ ppfx = &pfx->next;
+ } else {
+ log_error("Invalid IAPREFIX option cache.");
+ dfree(pfx, MDL);
+ if (ds.len != 0)
+ data_string_forget(&ds, MDL);
+ return ISC_R_UNEXPECTED;
+ }
+ }
+ delete_option(&dhcpv6_universe, options, D6O_IAPREFIX);
+
+ return ISC_R_SUCCESS;
+}
+
+/* Clean up a lease object, deallocate all its parts, and set it to NULL. */
+void
+dhc6_lease_destroy(struct dhc6_lease **src, const char *file, int line)
+{
+ struct dhc6_ia *ia, *nia;
+ struct dhc6_lease *lease;
+
+ if (src == NULL || *src == NULL) {
+ log_error("Attempt to destroy null lease.");
+ return;
+ }
+ lease = *src;
+
+ if (lease->server_id.len != 0)
+ data_string_forget(&lease->server_id, file, line);
+
+ for (ia = lease->bindings ; ia != NULL ; ia = nia) {
+ nia = ia->next;
+
+ dhc6_ia_destroy(&ia, file, line);
+ }
+
+ if (lease->options != NULL)
+ option_state_dereference(&lease->options, file, line);
+
+ dfree(lease, file, line);
+ *src = NULL;
+}
+
+/*
+ * Traverse the addresses list, and destroy their contents, and NULL the
+ * list pointer.
+ */
+static void
+dhc6_ia_destroy(struct dhc6_ia **src, const char *file, int line)
+{
+ struct dhc6_addr *addr, *naddr;
+ struct dhc6_ia *ia;
+
+ if (src == NULL || *src == NULL) {
+ log_error("Attempt to destroy null IA.");
+ return;
+ }
+ ia = *src;
+
+ for (addr = ia->addrs ; addr != NULL ; addr = naddr) {
+ naddr = addr->next;
+
+ if (addr->options != NULL)
+ option_state_dereference(&addr->options, file, line);
+
+ dfree(addr, file, line);
+ }
+
+ if (ia->options != NULL)
+ option_state_dereference(&ia->options, file, line);
+
+ dfree(ia, file, line);
+ *src = NULL;
+}
+
+/*
+ * For a given lease, insert it into the tail of the lease list. Upon
+ * finding a duplicate by server id, remove it and take over its position.
+ */
+static void
+insert_lease(struct dhc6_lease **head, struct dhc6_lease *new)
+{
+ while (*head != NULL) {
+ if ((*head)->server_id.len == new->server_id.len &&
+ memcmp((*head)->server_id.data, new->server_id.data,
+ new->server_id.len) == 0) {
+ new->next = (*head)->next;
+ dhc6_lease_destroy(head, MDL);
+ break;
+ }
+
+ head= &(*head)->next;
+ }
+
+ *head = new;
+ return;
+}
+
+/*
+ * Not really clear what to do here yet.
+ */
+static int
+dhc6_score_lease(struct client_state *client, struct dhc6_lease *lease)
+{
+ struct dhc6_ia *ia;
+ struct dhc6_addr *addr;
+ struct option **req;
+ int i;
+
+ if (lease->score)
+ return lease->score;
+
+ lease->score = 1;
+
+ /* If this lease lacks a required option, dump it. */
+ /* XXX: we should be able to cache the failure... */
+ req = client->config->required_options;
+ if (req != NULL) {
+ for (i = 0 ; req[i] != NULL ; i++) {
+ if (lookup_option(&dhcpv6_universe, lease->options,
+ req[i]->code) == NULL) {
+ lease->score = 0;
+ return lease->score;
+ }
+ }
+ }
+
+ /* If this lease contains a requested option, improve its score. */
+ req = client->config->requested_options;
+ if (req != NULL) {
+ for (i = 0 ; req[i] != NULL ; i++) {
+ if (lookup_option(&dhcpv6_universe, lease->options,
+ req[i]->code) != NULL)
+ lease->score++;
+ }
+ }
+
+ for (ia = lease->bindings ; ia != NULL ; ia = ia->next) {
+ lease->score += 50;
+
+ for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
+ lease->score += 100;
+ }
+ }
+
+ return lease->score;
+}
+
+/*
+ * start_init6() kicks off the process, transmitting a packet and
+ * scheduling a retransmission event.
+ */
+void
+start_init6(struct client_state *client)
+{
+ struct timeval tv;
+
+ log_debug("PRC: Soliciting for leases (INIT).");
+ client->state = S_INIT;
+
+ /* Initialize timers, RFC3315 section 17.1.2. */
+ client->IRT = SOL_TIMEOUT * 100;
+ client->MRT = SOL_MAX_RT * 100;
+ client->MRC = 0;
+ /* Default is 0 (no max) but -1 changes this. */
+ if (!onetry)
+ client->MRD = 0;
+ else
+ client->MRD = client->config->timeout;
+
+ dhc6_retrans_init(client);
+
+ /*
+ * RFC3315 section 17.1.2 goes out of its way:
+ * Also, the first RT MUST be selected to be strictly greater than IRT
+ * by choosing RAND to be strictly greater than 0.
+ */
+ /* if RAND < 0 then RAND = -RAND */
+ if (client->RT <= client->IRT)
+ client->RT = client->IRT + (client->IRT - client->RT);
+ /* if RAND == 0 then RAND = 1 */
+ if (client->RT <= client->IRT)
+ client->RT = client->IRT + 1;
+
+ client->v6_handler = init_handler;
+
+ /*
+ * RFC3315 section 17.1.2 says we MUST start the first packet
+ * between 0 and SOL_MAX_DELAY seconds. The good news is
+ * SOL_MAX_DELAY is 1.
+ */
+ tv.tv_sec = cur_tv.tv_sec;
+ tv.tv_usec = cur_tv.tv_usec;
+ tv.tv_usec += (random() % (SOL_MAX_DELAY * 100)) * 10000;
+ if (tv.tv_usec >= 1000000) {
+ tv.tv_sec += 1;
+ tv.tv_usec -= 1000000;
+ }
+ add_timeout(&tv, do_init6, client, NULL, NULL);
+
+ if (nowait)
+ go_daemon();
+}
+
+/*
+ * start_info_request6() kicks off the process, transmitting an info
+ * request packet and scheduling a retransmission event.
+ */
+void
+start_info_request6(struct client_state *client)
+{
+ struct timeval tv;
+
+ log_debug("PRC: Requesting information (INIT).");
+ client->state = S_INIT;
+
+ /* Initialize timers, RFC3315 section 18.1.5. */
+ client->IRT = INF_TIMEOUT * 100;
+ client->MRT = INF_MAX_RT * 100;
+ client->MRC = 0;
+ /* Default is 0 (no max) but -1 changes this. */
+ if (!onetry)
+ client->MRD = 0;
+ else
+ client->MRD = client->config->timeout;
+
+ dhc6_retrans_init(client);
+
+ client->v6_handler = info_request_handler;
+
+ /*
+ * RFC3315 section 18.1.5 says we MUST start the first packet
+ * between 0 and INF_MAX_DELAY seconds. The good news is
+ * INF_MAX_DELAY is 1.
+ */
+ tv.tv_sec = cur_tv.tv_sec;
+ tv.tv_usec = cur_tv.tv_usec;
+ tv.tv_usec += (random() % (INF_MAX_DELAY * 100)) * 10000;
+ if (tv.tv_usec >= 1000000) {
+ tv.tv_sec += 1;
+ tv.tv_usec -= 1000000;
+ }
+ add_timeout(&tv, do_info_request6, client, NULL, NULL);
+
+ if (nowait)
+ go_daemon();
+}
+
+/*
+ * start_confirm6() kicks off an "init-reboot" version of the process, at
+ * startup to find out if old bindings are 'fair' and at runtime whenever
+ * a link cycles state we'll eventually want to do this.
+ */
+void
+start_confirm6(struct client_state *client)
+{
+ struct timeval tv;
+
+ /* If there is no active lease, there is nothing to check. */
+ if ((client->active_lease == NULL) ||
+ !active_prefix(client) ||
+ client->active_lease->released) {
+ start_init6(client);
+ return;
+ }
+
+ log_debug("PRC: Confirming active lease (INIT-REBOOT).");
+ client->state = S_REBOOTING;
+
+ /* Initialize timers, RFC3315 section 17.1.3. */
+ client->IRT = CNF_TIMEOUT * 100;
+ client->MRT = CNF_MAX_RT * 100;
+ client->MRC = 0;
+ client->MRD = CNF_MAX_RD;
+
+ dhc6_retrans_init(client);
+
+ client->v6_handler = reply_handler;
+
+ /*
+ * RFC3315 section 18.1.2 says we MUST start the first packet
+ * between 0 and CNF_MAX_DELAY seconds. The good news is
+ * CNF_MAX_DELAY is 1.
+ */
+ tv.tv_sec = cur_tv.tv_sec;
+ tv.tv_usec = cur_tv.tv_usec;
+ tv.tv_usec += (random() % (CNF_MAX_DELAY * 100)) * 10000;
+ if (tv.tv_usec >= 1000000) {
+ tv.tv_sec += 1;
+ tv.tv_usec -= 1000000;
+ }
+ if (wanted_ia_pd != 0) {
+ client->state = S_REBINDING;
+ client->refresh_type = DHCPV6_REBIND;
+ add_timeout(&tv, do_refresh6, client, NULL, NULL);
+ } else
+ add_timeout(&tv, do_confirm6, client, NULL, NULL);
+}
+
+/*
+ * check_timing6() check on the timing for sending a v6 message
+ * and then do the basic initialization for a v6 message.
+ */
+#define CHK_TIM_SUCCESS 0
+#define CHK_TIM_MRC_EXCEEDED 1
+#define CHK_TIM_MRD_EXCEEDED 2
+#define CHK_TIM_ALLOC_FAILURE 3
+
+int
+check_timing6 (struct client_state *client, u_int8_t msg_type,
+ char *msg_str, struct dhc6_lease *lease,
+ struct data_string *ds)
+{
+ struct timeval elapsed;
+
+ /*
+ * Start_time starts at the first transmission.
+ */
+ if (client->txcount == 0) {
+ client->start_time.tv_sec = cur_tv.tv_sec;
+ client->start_time.tv_usec = cur_tv.tv_usec;
+ } else if ((client->MRC != 0) && (client->txcount > client->MRC)) {
+ log_info("Max retransmission count exceeded.");
+ return(CHK_TIM_MRC_EXCEEDED);
+ }
+
+ /* elapsed = cur - start */
+ elapsed.tv_sec = cur_tv.tv_sec - client->start_time.tv_sec;
+ elapsed.tv_usec = cur_tv.tv_usec - client->start_time.tv_usec;
+ if (elapsed.tv_usec < 0) {
+ elapsed.tv_sec -= 1;
+ elapsed.tv_usec += 1000000;
+ }
+
+ /* Check if finished (-1 argument). */
+ if ((client->MRD != 0) && (elapsed.tv_sec >= client->MRD)) {
+ log_info("Max retransmission duration exceeded.");
+ return(CHK_TIM_MRD_EXCEEDED);
+ }
+
+ memset(ds, 0, sizeof(*ds));
+ if (!buffer_allocate(&(ds->buffer), 4, MDL)) {
+ log_error("Unable to allocate memory for %s.", msg_str);
+ return(CHK_TIM_ALLOC_FAILURE);
+ }
+ ds->data = ds->buffer->data;
+ ds->len = 4;
+
+ ds->buffer->data[0] = msg_type;
+ memcpy(ds->buffer->data + 1, client->dhcpv6_transaction_id, 3);
+
+ /* Form an elapsed option. */
+ /* Maximum value is 65535 1/100s coded as 0xffff. */
+ if ((elapsed.tv_sec < 0) || (elapsed.tv_sec > 655) ||
+ ((elapsed.tv_sec == 655) && (elapsed.tv_usec > 350000))) {
+ client->elapsed = 0xffff;
+ } else {
+ client->elapsed = elapsed.tv_sec * 100;
+ client->elapsed += elapsed.tv_usec / 10000;
+ }
+
+ if (client->elapsed == 0)
+ log_debug("XMT: Forming %s, 0 ms elapsed.", msg_str);
+ else
+ log_debug("XMT: Forming %s, %u0 ms elapsed.", msg_str,
+ (unsigned)client->elapsed);
+
+ client->elapsed = htons(client->elapsed);
+
+ make_client6_options(client, &client->sent_options, lease, msg_type);
+
+ return(CHK_TIM_SUCCESS);
+}
+
+/*
+ * do_init6() marshals and transmits a solicit.
+ */
+void
+do_init6(void *input)
+{
+ struct client_state *client;
+ struct dhc6_ia *old_ia;
+ struct dhc6_addr *old_addr;
+ struct data_string ds;
+ struct data_string ia;
+ struct data_string addr;
+ struct timeval tv;
+ u_int32_t t1, t2;
+ int i, idx, len, send_ret;
+
+ client = input;
+
+ /*
+ * In RFC3315 section 17.1.2, the retransmission timer is
+ * used as the selecting timer.
+ */
+ if (client->advertised_leases != NULL) {
+ start_selecting6(client);
+ return;
+ }
+
+ switch(check_timing6(client, DHCPV6_SOLICIT, "Solicit", NULL, &ds)) {
+ case CHK_TIM_MRC_EXCEEDED:
+ case CHK_TIM_ALLOC_FAILURE:
+ return;
+ case CHK_TIM_MRD_EXCEEDED:
+ client->state = S_STOPPED;
+ if (client->active_lease != NULL) {
+ dhc6_lease_destroy(&client->active_lease, MDL);
+ client->active_lease = NULL;
+ }
+ /* Stop if and only if this is the last client. */
+ if (stopping_finished())
+ exit(2);
+ return;
+ }
+
+ /*
+ * Fetch any configured 'sent' options (includes DUID) in wire format.
+ */
+ dhcpv6_universe.encapsulate(&ds, NULL, NULL, client,
+ NULL, client->sent_options, &global_scope,
+ &dhcpv6_universe);
+
+ /* Use a specific handler with rapid-commit. */
+ if (lookup_option(&dhcpv6_universe, client->sent_options,
+ D6O_RAPID_COMMIT) != NULL) {
+ client->v6_handler = rapid_commit_handler;
+ }
+
+ /* Append IA_NA. */
+ for (i = 0; i < wanted_ia_na; i++) {
+ /*
+ * XXX: maybe the IA_NA('s) should be put into the sent_options
+ * cache. They'd have to be pulled down as they also contain
+ * different option caches in the same universe...
+ */
+ memset(&ia, 0, sizeof(ia));
+ if (!buffer_allocate(&ia.buffer, 12, MDL)) {
+ log_error("Unable to allocate memory for IA_NA.");
+ data_string_forget(&ds, MDL);
+ return;
+ }
+ ia.data = ia.buffer->data;
+ ia.len = 12;
+
+ /*
+ * A simple IAID is the last 4 bytes
+ * of the hardware address.
+ */
+ if (client->interface->hw_address.hlen > 4) {
+ idx = client->interface->hw_address.hlen - 4;
+ len = 4;
+ } else {
+ idx = 0;
+ len = client->interface->hw_address.hlen;
+ }
+ memcpy(ia.buffer->data,
+ client->interface->hw_address.hbuf + idx,
+ len);
+ if (i)
+ ia.buffer->data[3] += i;
+
+ t1 = client->config->requested_lease / 2;
+ t2 = t1 + (t1 / 2);
+ putULong(ia.buffer->data + 4, t1);
+ putULong(ia.buffer->data + 8, t2);
+
+ log_debug("XMT: X-- IA_NA %s",
+ print_hex_1(4, ia.buffer->data, 55));
+ log_debug("XMT: | X-- Request renew in +%u", (unsigned)t1);
+ log_debug("XMT: | X-- Request rebind in +%u", (unsigned)t2);
+
+ if ((client->active_lease != NULL) &&
+ ((old_ia = find_ia(client->active_lease->bindings,
+ D6O_IA_NA,
+ (char *)ia.buffer->data)) != NULL)) {
+ /*
+ * For each address in the old IA_NA,
+ * request a binding.
+ */
+ memset(&addr, 0, sizeof(addr));
+ for (old_addr = old_ia->addrs ; old_addr != NULL ;
+ old_addr = old_addr->next) {
+ if (old_addr->address.len != 16) {
+ log_error("Invalid IPv6 address "
+ "length %d. "
+ "Ignoring. (%s:%d)",
+ old_addr->address.len,
+ MDL);
+ continue;
+ }
+
+ if (!buffer_allocate(&addr.buffer, 24, MDL)) {
+ log_error("Unable to allocate memory "
+ "for IAADDR.");
+ data_string_forget(&ia, MDL);
+ data_string_forget(&ds, MDL);
+ return;
+ }
+ addr.data = addr.buffer->data;
+ addr.len = 24;
+
+ memcpy(addr.buffer->data,
+ old_addr->address.iabuf,
+ 16);
+
+ t1 = client->config->requested_lease;
+ t2 = t1 + (t1 / 2);
+ putULong(addr.buffer->data + 16, t1);
+ putULong(addr.buffer->data + 20, t2);
+
+ log_debug("XMT: | X-- Request address %s.",
+ piaddr(old_addr->address));
+ log_debug("XMT: | | X-- Request "
+ "preferred in +%u",
+ (unsigned)t1);
+ log_debug("XMT: | | X-- Request valid "
+ "in +%u",
+ (unsigned)t2);
+
+ append_option(&ia, &dhcpv6_universe,
+ iaaddr_option,
+ &addr);
+
+ data_string_forget(&addr, MDL);
+ }
+ }
+
+ append_option(&ds, &dhcpv6_universe, ia_na_option, &ia);
+ data_string_forget(&ia, MDL);
+ }
+
+ /* Append IA_TA. */
+ for (i = 0; i < wanted_ia_ta; i++) {
+ /*
+ * XXX: maybe the IA_TA('s) should be put into the sent_options
+ * cache. They'd have to be pulled down as they also contain
+ * different option caches in the same universe...
+ */
+ memset(&ia, 0, sizeof(ia));
+ if (!buffer_allocate(&ia.buffer, 4, MDL)) {
+ log_error("Unable to allocate memory for IA_TA.");
+ data_string_forget(&ds, MDL);
+ return;
+ }
+ ia.data = ia.buffer->data;
+ ia.len = 4;
+
+ /*
+ * A simple IAID is the last 4 bytes
+ * of the hardware address.
+ */
+ if (client->interface->hw_address.hlen > 4) {
+ idx = client->interface->hw_address.hlen - 4;
+ len = 4;
+ } else {
+ idx = 0;
+ len = client->interface->hw_address.hlen;
+ }
+ memcpy(ia.buffer->data,
+ client->interface->hw_address.hbuf + idx,
+ len);
+ if (i)
+ ia.buffer->data[3] += i;
+
+ log_debug("XMT: X-- IA_TA %s",
+ print_hex_1(4, ia.buffer->data, 55));
+
+ if ((client->active_lease != NULL) &&
+ ((old_ia = find_ia(client->active_lease->bindings,
+ D6O_IA_TA,
+ (char *)ia.buffer->data)) != NULL)) {
+ /*
+ * For each address in the old IA_TA,
+ * request a binding.
+ */
+ memset(&addr, 0, sizeof(addr));
+ for (old_addr = old_ia->addrs ; old_addr != NULL ;
+ old_addr = old_addr->next) {
+ if (old_addr->address.len != 16) {
+ log_error("Invalid IPv6 address "
+ "length %d. "
+ "Ignoring. (%s:%d)",
+ old_addr->address.len,
+ MDL);
+ continue;
+ }
+
+ if (!buffer_allocate(&addr.buffer, 24, MDL)) {
+ log_error("Unable to allocate memory "
+ "for IAADDR.");
+ data_string_forget(&ia, MDL);
+ data_string_forget(&ds, MDL);
+ return;
+ }
+ addr.data = addr.buffer->data;
+ addr.len = 24;
+
+ memcpy(addr.buffer->data,
+ old_addr->address.iabuf,
+ 16);
+
+ t1 = client->config->requested_lease;
+ t2 = t1 + (t1 / 2);
+ putULong(addr.buffer->data + 16, t1);
+ putULong(addr.buffer->data + 20, t2);
+
+ log_debug("XMT: | X-- Request address %s.",
+ piaddr(old_addr->address));
+ log_debug("XMT: | | X-- Request "
+ "preferred in +%u",
+ (unsigned)t1);
+ log_debug("XMT: | | X-- Request valid "
+ "in +%u",
+ (unsigned)t2);
+
+ append_option(&ia, &dhcpv6_universe,
+ iaaddr_option,
+ &addr);
+
+ data_string_forget(&addr, MDL);
+ }
+ }
+
+ append_option(&ds, &dhcpv6_universe, ia_ta_option, &ia);
+ data_string_forget(&ia, MDL);
+ }
+
+ /* Append IA_PD. */
+ for (i = 0; i < wanted_ia_pd; i++) {
+ /*
+ * XXX: maybe the IA_PD('s) should be put into the sent_options
+ * cache. They'd have to be pulled down as they also contain
+ * different option caches in the same universe...
+ */
+ memset(&ia, 0, sizeof(ia));
+ if (!buffer_allocate(&ia.buffer, 12, MDL)) {
+ log_error("Unable to allocate memory for IA_PD.");
+ data_string_forget(&ds, MDL);
+ return;
+ }
+ ia.data = ia.buffer->data;
+ ia.len = 12;
+
+ /*
+ * A simple IAID is the last 4 bytes
+ * of the hardware address.
+ */
+ if (client->interface->hw_address.hlen > 4) {
+ idx = client->interface->hw_address.hlen - 4;
+ len = 4;
+ } else {
+ idx = 0;
+ len = client->interface->hw_address.hlen;
+ }
+ memcpy(ia.buffer->data,
+ client->interface->hw_address.hbuf + idx,
+ len);
+ if (i)
+ ia.buffer->data[3] += i;
+
+ t1 = client->config->requested_lease / 2;
+ t2 = t1 + (t1 / 2);
+ putULong(ia.buffer->data + 4, t1);
+ putULong(ia.buffer->data + 8, t2);
+
+ log_debug("XMT: X-- IA_PD %s",
+ print_hex_1(4, ia.buffer->data, 55));
+ log_debug("XMT: | X-- Request renew in +%u", (unsigned)t1);
+ log_debug("XMT: | X-- Request rebind in +%u", (unsigned)t2);
+
+ if ((client->active_lease != NULL) &&
+ ((old_ia = find_ia(client->active_lease->bindings,
+ D6O_IA_PD,
+ (char *)ia.buffer->data)) != NULL)) {
+ /*
+ * For each prefix in the old IA_PD,
+ * request a binding.
+ */
+ memset(&addr, 0, sizeof(addr));
+ for (old_addr = old_ia->addrs ; old_addr != NULL ;
+ old_addr = old_addr->next) {
+ if (old_addr->address.len != 16) {
+ log_error("Invalid IPv6 prefix, "
+ "Ignoring. (%s:%d)",
+ MDL);
+ continue;
+ }
+
+ if (!buffer_allocate(&addr.buffer, 25, MDL)) {
+ log_error("Unable to allocate memory "
+ "for IAPREFIX.");
+ data_string_forget(&ia, MDL);
+ data_string_forget(&ds, MDL);
+ return;
+ }
+ addr.data = addr.buffer->data;
+ addr.len = 25;
+
+ t1 = client->config->requested_lease;
+ t2 = t1 + (t1 / 2);
+ putULong(addr.buffer->data, t1);
+ putULong(addr.buffer->data + 4, t2);
+
+ putUChar(addr.buffer->data + 8,
+ old_addr->plen);
+ memcpy(addr.buffer->data + 9,
+ old_addr->address.iabuf,
+ 16);
+
+ log_debug("XMT: | X-- Request prefix %s/%u.",
+ piaddr(old_addr->address),
+ (unsigned) old_addr->plen);
+ log_debug("XMT: | | X-- Request "
+ "preferred in +%u",
+ (unsigned)t1);
+ log_debug("XMT: | | X-- Request valid "
+ "in +%u",
+ (unsigned)t2);
+
+ append_option(&ia, &dhcpv6_universe,
+ iaprefix_option,
+ &addr);
+
+ data_string_forget(&addr, MDL);
+ }
+ }
+
+ append_option(&ds, &dhcpv6_universe, ia_pd_option, &ia);
+ data_string_forget(&ia, MDL);
+ }
+
+ /* Transmit and wait. */
+
+ log_info("XMT: Solicit on %s, interval %ld0ms.",
+ client->name ? client->name : client->interface->name,
+ (long int)client->RT);
+
+ send_ret = send_packet6(client->interface,
+ ds.data, ds.len, &DHCPv6DestAddr);
+ if (send_ret != ds.len) {
+ log_error("dhc6: send_packet6() sent %d of %d bytes",
+ send_ret, ds.len);
+ }
+
+ data_string_forget(&ds, MDL);
+
+ /* Wait RT */
+ tv.tv_sec = cur_tv.tv_sec + client->RT / 100;
+ tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000;
+ if (tv.tv_usec >= 1000000) {
+ tv.tv_sec += 1;
+ tv.tv_usec -= 1000000;
+ }
+ add_timeout(&tv, do_init6, client, NULL, NULL);
+
+ dhc6_retrans_advance(client);
+}
+
+/* do_info_request6() marshals and transmits an information-request. */
+void
+do_info_request6(void *input)
+{
+ struct client_state *client;
+ struct data_string ds;
+ struct timeval tv;
+ int send_ret;
+
+ client = input;
+
+ switch(check_timing6(client, DHCPV6_INFORMATION_REQUEST,
+ "Info-Request", NULL, &ds)) {
+ case CHK_TIM_MRC_EXCEEDED:
+ case CHK_TIM_ALLOC_FAILURE:
+ return;
+ case CHK_TIM_MRD_EXCEEDED:
+ exit(2);
+ case CHK_TIM_SUCCESS:
+ break;
+ }
+
+ /* Fetch any configured 'sent' options (includes DUID) in wire format.
+ */
+ dhcpv6_universe.encapsulate(&ds, NULL, NULL, client,
+ NULL, client->sent_options, &global_scope,
+ &dhcpv6_universe);
+
+ /* Transmit and wait. */
+
+ log_info("XMT: Info-Request on %s, interval %ld0ms.",
+ client->name ? client->name : client->interface->name,
+ (long int)client->RT);
+
+ send_ret = send_packet6(client->interface,
+ ds.data, ds.len, &DHCPv6DestAddr);
+ if (send_ret != ds.len) {
+ log_error("dhc6: send_packet6() sent %d of %d bytes",
+ send_ret, ds.len);
+ }
+
+ data_string_forget(&ds, MDL);
+
+ /* Wait RT */
+ tv.tv_sec = cur_tv.tv_sec + client->RT / 100;
+ tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000;
+ if (tv.tv_usec >= 1000000) {
+ tv.tv_sec += 1;
+ tv.tv_usec -= 1000000;
+ }
+ add_timeout(&tv, do_info_request6, client, NULL, NULL);
+
+ dhc6_retrans_advance(client);
+}
+
+/* do_confirm6() creates a Confirm packet and transmits it. This function
+ * is called on every timeout to (re)transmit.
+ */
+void
+do_confirm6(void *input)
+{
+ struct client_state *client;
+ struct data_string ds;
+ int send_ret;
+ struct timeval tv;
+
+ client = input;
+
+ if (client->active_lease == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ /* In section 17.1.3, it is said:
+ *
+ * If the client receives no responses before the message
+ * transmission process terminates, as described in section 14,
+ * the client SHOULD continue to use any IP addresses, using the
+ * last known lifetimes for those addresses, and SHOULD continue
+ * to use any other previously obtained configuration parameters.
+ *
+ * So if confirm times out, we go active.
+ *
+ * XXX: Should we reduce all IA's t1 to 0, so that we renew and
+ * stick there until we get a reply?
+ */
+
+ switch(check_timing6(client, DHCPV6_CONFIRM, "Confirm",
+ client->active_lease, &ds)) {
+ case CHK_TIM_MRC_EXCEEDED:
+ case CHK_TIM_MRD_EXCEEDED:
+ start_bound(client);
+ return;
+ case CHK_TIM_ALLOC_FAILURE:
+ return;
+ case CHK_TIM_SUCCESS:
+ break;
+ }
+
+ /* Fetch any configured 'sent' options (includes DUID') in wire format.
+ */
+ dhcpv6_universe.encapsulate(&ds, NULL, NULL, client, NULL,
+ client->sent_options, &global_scope,
+ &dhcpv6_universe);
+
+ /* Append IA's. */
+ if (wanted_ia_na &&
+ dhc6_add_ia_na(client, &ds, client->active_lease,
+ DHCPV6_CONFIRM) != ISC_R_SUCCESS) {
+ data_string_forget(&ds, MDL);
+ return;
+ }
+ if (wanted_ia_ta &&
+ dhc6_add_ia_ta(client, &ds, client->active_lease,
+ DHCPV6_CONFIRM) != ISC_R_SUCCESS) {
+ data_string_forget(&ds, MDL);
+ return;
+ }
+
+ /* Transmit and wait. */
+
+ log_info("XMT: Confirm on %s, interval %ld0ms.",
+ client->name ? client->name : client->interface->name,
+ (long int)client->RT);
+
+ send_ret = send_packet6(client->interface, ds.data, ds.len,
+ &DHCPv6DestAddr);
+ if (send_ret != ds.len) {
+ log_error("dhc6: sendpacket6() sent %d of %d bytes",
+ send_ret, ds.len);
+ }
+
+ data_string_forget(&ds, MDL);
+
+ /* Wait RT */
+ tv.tv_sec = cur_tv.tv_sec + client->RT / 100;
+ tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000;
+ if (tv.tv_usec >= 1000000) {
+ tv.tv_sec += 1;
+ tv.tv_usec -= 1000000;
+ }
+ add_timeout(&tv, do_confirm6, client, NULL, NULL);
+
+ dhc6_retrans_advance(client);
+}
+
+/*
+ * Release addresses.
+ */
+void
+start_release6(struct client_state *client)
+{
+ /* Cancel any pending transmissions */
+ cancel_timeout(do_confirm6, client);
+ cancel_timeout(do_select6, client);
+ cancel_timeout(do_refresh6, client);
+ cancel_timeout(do_release6, client);
+ client->state = S_STOPPED;
+
+ /*
+ * It is written: "The client MUST NOT use any of the addresses it
+ * is releasing as the source address in the Release message or in
+ * any subsequently transmitted message." So unconfigure now.
+ */
+ unconfigure6(client, "RELEASE6");
+
+ /* Note this in the lease file. */
+ if (client->active_lease == NULL)
+ return;
+ client->active_lease->released = ISC_TRUE;
+ write_client6_lease(client, client->active_lease, 0, 1);
+
+ /* Set timers per RFC3315 section 18.1.6. */
+ client->IRT = REL_TIMEOUT * 100;
+ client->MRT = 0;
+ client->MRC = REL_MAX_RC;
+ client->MRD = 0;
+
+ dhc6_retrans_init(client);
+ client->v6_handler = reply_handler;
+
+ do_release6(client);
+}
+/*
+ * do_release6() creates a Release packet and transmits it.
+ */
+static void
+do_release6(void *input)
+{
+ struct client_state *client;
+ struct data_string ds;
+ int send_ret;
+ struct timeval tv;
+
+ client = input;
+
+ if ((client->active_lease == NULL) || !active_prefix(client))
+ return;
+
+ switch(check_timing6(client, DHCPV6_RELEASE, "Release",
+ client->active_lease, &ds)) {
+ case CHK_TIM_MRC_EXCEEDED:
+ case CHK_TIM_ALLOC_FAILURE:
+ case CHK_TIM_MRD_EXCEEDED:
+ goto release_done;
+ case CHK_TIM_SUCCESS:
+ break;
+ }
+
+ /*
+ * Don't use unicast as we don't know if we still have an
+ * available address with enough scope.
+ */
+
+ dhcpv6_universe.encapsulate(&ds, NULL, NULL, client, NULL,
+ client->sent_options, &global_scope,
+ &dhcpv6_universe);
+
+ /* Append IA's (but don't release temporary addresses). */
+ if (wanted_ia_na &&
+ dhc6_add_ia_na(client, &ds, client->active_lease,
+ DHCPV6_RELEASE) != ISC_R_SUCCESS) {
+ data_string_forget(&ds, MDL);
+ goto release_done;
+ }
+ if (wanted_ia_pd &&
+ dhc6_add_ia_pd(client, &ds, client->active_lease,
+ DHCPV6_RELEASE) != ISC_R_SUCCESS) {
+ data_string_forget(&ds, MDL);
+ goto release_done;
+ }
+
+ /* Transmit and wait. */
+ log_info("XMT: Release on %s, interval %ld0ms.",
+ client->name ? client->name : client->interface->name,
+ (long int)client->RT);
+
+ send_ret = send_packet6(client->interface, ds.data, ds.len,
+ &DHCPv6DestAddr);
+ if (send_ret != ds.len) {
+ log_error("dhc6: sendpacket6() sent %d of %d bytes",
+ send_ret, ds.len);
+ }
+
+ data_string_forget(&ds, MDL);
+
+ /* Wait RT */
+ tv.tv_sec = cur_tv.tv_sec + client->RT / 100;
+ tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000;
+ if (tv.tv_usec >= 1000000) {
+ tv.tv_sec += 1;
+ tv.tv_usec -= 1000000;
+ }
+ add_timeout(&tv, do_release6, client, NULL, NULL);
+ dhc6_retrans_advance(client);
+ return;
+
+ release_done:
+ dhc6_lease_destroy(&client->active_lease, MDL);
+ client->active_lease = NULL;
+ if (stopping_finished())
+ exit(0);
+}
+
+/* status_log() just puts a status code into displayable form and logs it
+ * to info level.
+ */
+static void
+status_log(int code, const char *scope, const char *additional, int len)
+{
+ const char *msg = NULL;
+
+ switch(code) {
+ case STATUS_Success:
+ msg = "Success";
+ break;
+
+ case STATUS_UnspecFail:
+ msg = "UnspecFail";
+ break;
+
+ case STATUS_NoAddrsAvail:
+ msg = "NoAddrsAvail";
+ break;
+
+ case STATUS_NoBinding:
+ msg = "NoBinding";
+ break;
+
+ case STATUS_NotOnLink:
+ msg = "NotOnLink";
+ break;
+
+ case STATUS_UseMulticast:
+ msg = "UseMulticast";
+ break;
+
+ case STATUS_NoPrefixAvail:
+ msg = "NoPrefixAvail";
+ break;
+
+ default:
+ msg = "UNKNOWN";
+ break;
+ }
+
+ if (len > 0)
+ log_info("%s status code %s: %s", scope, msg,
+ print_hex_1(len,
+ (const unsigned char *)additional, 50));
+ else
+ log_info("%s status code %s.", scope, msg);
+}
+
+/* Acquire a status code.
+ */
+static isc_result_t
+dhc6_get_status_code(struct option_state *options, unsigned *code,
+ struct data_string *msg)
+{
+ struct option_cache *oc;
+ struct data_string ds;
+ isc_result_t rval = ISC_R_SUCCESS;
+
+ if ((options == NULL) || (code == NULL))
+ return DHCP_R_INVALIDARG;
+
+ if ((msg != NULL) && (msg->len != 0))
+ return DHCP_R_INVALIDARG;
+
+ memset(&ds, 0, sizeof(ds));
+
+ /* Assume success if there is no option. */
+ *code = STATUS_Success;
+
+ oc = lookup_option(&dhcpv6_universe, options, D6O_STATUS_CODE);
+ if ((oc != NULL) &&
+ evaluate_option_cache(&ds, NULL, NULL, NULL, options,
+ NULL, &global_scope, oc, MDL)) {
+ if (ds.len < 2) {
+ log_error("Invalid status code length %d.", ds.len);
+ rval = DHCP_R_FORMERR;
+ } else
+ *code = getUShort(ds.data);
+
+ if ((msg != NULL) && (ds.len > 2)) {
+ data_string_copy(msg, &ds, MDL);
+ msg->data += 2;
+ msg->len -= 2;
+ }
+
+ data_string_forget(&ds, MDL);
+ return rval;
+ }
+
+ return ISC_R_NOTFOUND;
+}
+
+/* Look at status codes in an advertise, and reform the return value.
+ */
+static isc_result_t
+dhc6_check_status(isc_result_t rval, struct option_state *options,
+ const char *scope, unsigned *code)
+{
+ struct data_string msg;
+ isc_result_t status;
+
+ if ((scope == NULL) || (code == NULL))
+ return DHCP_R_INVALIDARG;
+
+ /* If we don't find a code, we assume success. */
+ *code = STATUS_Success;
+
+ /* If there is no options cache, then there is no code. */
+ if (options != NULL) {
+ memset(&msg, 0, sizeof(msg));
+ status = dhc6_get_status_code(options, code, &msg);
+
+ if (status == ISC_R_SUCCESS) {
+ status_log(*code, scope, (char *)msg.data, msg.len);
+ data_string_forget(&msg, MDL);
+
+ if (*code != STATUS_Success)
+ rval = ISC_R_FAILURE;
+
+ } else if (status != ISC_R_NOTFOUND)
+ rval = status;
+ }
+
+ return rval;
+}
+
+/* Look in the packet, any IA's, and any IAADDR's within those IA's to find
+ * status code options that are not SUCCESS.
+ */
+static isc_result_t
+dhc6_check_advertise(struct dhc6_lease *lease)
+{
+ struct dhc6_ia *ia;
+ struct dhc6_addr *addr;
+ isc_result_t rval = ISC_R_SUCCESS;
+ int have_addrs = ISC_FALSE;
+ unsigned code;
+ const char *scope;
+
+ rval = dhc6_check_status(rval, lease->options, "message", &code);
+
+ for (ia = lease->bindings ; ia != NULL ; ia = ia->next) {
+ switch (ia->ia_type) {
+ case D6O_IA_NA:
+ scope = "IA_NA";
+ break;
+ case D6O_IA_TA:
+ scope = "IA_TA";
+ break;
+ case D6O_IA_PD:
+ scope = "IA_PD";
+ break;
+ default:
+ log_error("dhc6_check_advertise: no type.");
+ return ISC_R_FAILURE;
+ }
+ rval = dhc6_check_status(rval, ia->options, scope, &code);
+
+ for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
+ if (ia->ia_type != D6O_IA_PD)
+ scope = "IAADDR";
+ else
+ scope = "IAPREFIX";
+ rval = dhc6_check_status(rval, addr->options,
+ scope, &code);
+ have_addrs = ISC_TRUE;
+ }
+ }
+
+ if (have_addrs != ISC_TRUE)
+ rval = ISC_R_ADDRNOTAVAIL;
+
+ return rval;
+}
+
+/* status code <-> action matrix for the client in INIT state
+ * (rapid/commit). Returns always false as no action is defined.
+ */
+static isc_boolean_t
+dhc6_init_action(struct client_state *client, isc_result_t *rvalp,
+ unsigned code)
+{
+ if (rvalp == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ if (client == NULL) {
+ *rvalp = DHCP_R_INVALIDARG;
+ return ISC_FALSE;
+ }
+
+ if (*rvalp == ISC_R_SUCCESS)
+ return ISC_FALSE;
+
+ /* No possible action in any case... */
+ return ISC_FALSE;
+}
+
+/* status code <-> action matrix for the client in SELECT state
+ * (request/reply). Returns true if action was taken (and the
+ * packet should be ignored), or false if no action was taken.
+ */
+static isc_boolean_t
+dhc6_select_action(struct client_state *client, isc_result_t *rvalp,
+ unsigned code)
+{
+ struct dhc6_lease *lease;
+ isc_result_t rval;
+
+ if (rvalp == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ if (client == NULL) {
+ *rvalp = DHCP_R_INVALIDARG;
+ return ISC_FALSE;
+ }
+ rval = *rvalp;
+
+ if (rval == ISC_R_SUCCESS)
+ return ISC_FALSE;
+
+ switch (code) {
+ /* We may have an earlier failure status code (so no
+ * success rval), and a success code now. This
+ * doesn't upgrade the rval to success, but it does
+ * mean we take no action here.
+ */
+ case STATUS_Success:
+ /* Gimpy server, or possibly an attacker. */
+ case STATUS_NoBinding:
+ case STATUS_UseMulticast:
+ /* Take no action. */
+ return ISC_FALSE;
+
+ /* If the server can't deal with us, either try the
+ * next advertised server, or continue retrying if there
+ * weren't any.
+ */
+ default:
+ case STATUS_UnspecFail:
+ if (client->advertised_leases != NULL) {
+ dhc6_lease_destroy(&client->selected_lease, MDL);
+ client->selected_lease = NULL;
+
+ start_selecting6(client);
+
+ break;
+ } else /* Take no action - continue to retry. */
+ return ISC_FALSE;
+
+ /* If the server has no addresses, try other servers if
+ * we got some, otherwise go to INIT to hope for more
+ * servers.
+ */
+ case STATUS_NoAddrsAvail:
+ case STATUS_NoPrefixAvail:
+ if (client->state == S_REBOOTING)
+ return ISC_FALSE;
+
+ if (client->selected_lease == NULL)
+ log_fatal("Impossible case at %s:%d.", MDL);
+
+ dhc6_lease_destroy(&client->selected_lease, MDL);
+ client->selected_lease = NULL;
+
+ if (client->advertised_leases != NULL)
+ start_selecting6(client);
+ else
+ start_init6(client);
+
+ break;
+
+ /* If we got a NotOnLink from a Confirm, then we're not
+ * on link. Kill the old-active binding and start over.
+ *
+ * If we got a NotOnLink from our Request, something weird
+ * happened. Start over from scratch anyway.
+ */
+ case STATUS_NotOnLink:
+ if (client->state == S_REBOOTING) {
+ if (client->active_lease == NULL)
+ log_fatal("Impossible case at %s:%d.", MDL);
+
+ dhc6_lease_destroy(&client->active_lease, MDL);
+ } else {
+ if (client->selected_lease == NULL)
+ log_fatal("Impossible case at %s:%d.", MDL);
+
+ dhc6_lease_destroy(&client->selected_lease, MDL);
+ client->selected_lease = NULL;
+
+ while (client->advertised_leases != NULL) {
+ lease = client->advertised_leases;
+ client->advertised_leases = lease->next;
+
+ dhc6_lease_destroy(&lease, MDL);
+ }
+ }
+
+ start_init6(client);
+ break;
+ }
+
+ return ISC_TRUE;
+}
+
+static void
+dhc6_withdraw_lease(struct client_state *client)
+{
+ struct dhc6_ia *ia;
+ struct dhc6_addr *addr;
+
+ if ((client == NULL) || (client->active_lease == NULL))
+ return;
+
+ for (ia = client->active_lease->bindings ; ia != NULL ;
+ ia = ia->next) {
+ for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
+ addr->max_life = addr->preferred_life = 0;
+ }
+ }
+
+ /* Perform expiry. */
+ do_expire(client);
+}
+
+/* status code <-> action matrix for the client in BOUND state
+ * (request/reply). Returns true if action was taken (and the
+ * packet should be ignored), or false if no action was taken.
+ */
+static isc_boolean_t
+dhc6_reply_action(struct client_state *client, isc_result_t *rvalp,
+ unsigned code)
+{
+ isc_result_t rval;
+
+ if (rvalp == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ if (client == NULL) {
+ *rvalp = DHCP_R_INVALIDARG;
+ return ISC_FALSE;
+ }
+ rval = *rvalp;
+
+ if (rval == ISC_R_SUCCESS)
+ return ISC_FALSE;
+
+ switch (code) {
+ /* It's possible an earlier status code set rval to a failure
+ * code, and we've encountered a later success.
+ */
+ case STATUS_Success:
+ /* In "refreshes" (where we get replies), we probably
+ * still have a valid lease. So "take no action" and
+ * the upper levels will keep retrying until the lease
+ * expires (or we rebind).
+ */
+ case STATUS_UnspecFail:
+ /* For unknown codes...it's a soft (retryable) error. */
+ default:
+ return ISC_FALSE;
+
+ /* The server is telling us to use a multicast address, so
+ * we have to delete the unicast option from the active
+ * lease, then allow retransmission to occur normally.
+ * (XXX: It might be preferable in this case to retransmit
+ * sooner than the current interval, but for now we don't.)
+ */
+ case STATUS_UseMulticast:
+ if (client->active_lease != NULL)
+ delete_option(&dhcp_universe,
+ client->active_lease->options,
+ D6O_UNICAST);
+ return ISC_FALSE;
+
+ /* "When the client receives a NotOnLink status from the
+ * server in response to a Request, the client can either
+ * re-issue the Request without specifying any addresses
+ * or restart the DHCP server discovery process."
+ *
+ * This is strange. If competing server evaluation is
+ * useful (and therefore in the protocol), then why would
+ * a client's first reaction be to request from the same
+ * server on a different link? Surely you'd want to
+ * re-evaluate your server selection.
+ *
+ * Well, I guess that's the answer.
+ */
+ case STATUS_NotOnLink:
+ /* In this case, we need to rescind all current active
+ * bindings (just 'expire' them all normally, if early).
+ * They're no use to us on the wrong link. Then head back
+ * to init, redo server selection and get new addresses.
+ */
+ dhc6_withdraw_lease(client);
+ break;
+
+ /* "If the status code is NoAddrsAvail, the client has
+ * received no usable addresses in the IA and may choose
+ * to try obtaining addresses for the IA from another
+ * server."
+ */
+ case STATUS_NoAddrsAvail:
+ case STATUS_NoPrefixAvail:
+ /* Head back to init, keeping any active bindings (!). */
+ start_init6(client);
+ break;
+
+ /* - sends a Request message if the IA contained a Status
+ * Code option with the NoBinding status (and does not
+ * send any additional Renew/Rebind messages)
+ */
+ case STATUS_NoBinding:
+ if (client->advertised_leases != NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ client->advertised_leases =
+ dhc6_dup_lease(client->active_lease, MDL);
+ start_selecting6(client);
+ break;
+ }
+
+ return ISC_TRUE;
+}
+
+/* status code <-> action matrix for the client in STOPPED state
+ * (release/decline). Returns true if action was taken (and the
+ * packet should be ignored), or false if no action was taken.
+ * NoBinding is translated into Success.
+ */
+static isc_boolean_t
+dhc6_stop_action(struct client_state *client, isc_result_t *rvalp,
+ unsigned code)
+{
+ isc_result_t rval;
+
+ if (rvalp == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ if (client == NULL) {
+ *rvalp = DHCP_R_INVALIDARG;
+ return ISC_FALSE;
+ }
+ rval = *rvalp;
+
+ if (rval == ISC_R_SUCCESS)
+ return ISC_FALSE;
+
+ switch (code) {
+ /* It's possible an earlier status code set rval to a failure
+ * code, and we've encountered a later success.
+ */
+ case STATUS_Success:
+ /* For unknown codes...it's a soft (retryable) error. */
+ case STATUS_UnspecFail:
+ default:
+ return ISC_FALSE;
+
+ /* NoBinding is not an error */
+ case STATUS_NoBinding:
+ if (rval == ISC_R_FAILURE)
+ *rvalp = ISC_R_SUCCESS;
+ return ISC_FALSE;
+
+ /* Should not happen */
+ case STATUS_NoAddrsAvail:
+ case STATUS_NoPrefixAvail:
+ break;
+
+ /* Give up on it */
+ case STATUS_NotOnLink:
+ break;
+
+ /* The server is telling us to use a multicast address, so
+ * we have to delete the unicast option from the active
+ * lease, then allow retransmission to occur normally.
+ * (XXX: It might be preferable in this case to retransmit
+ * sooner than the current interval, but for now we don't.)
+ */
+ case STATUS_UseMulticast:
+ if (client->active_lease != NULL)
+ delete_option(&dhcp_universe,
+ client->active_lease->options,
+ D6O_UNICAST);
+ return ISC_FALSE;
+ }
+
+ return ISC_TRUE;
+}
+
+/* Look at a new and old lease, and make sure the new information is not
+ * losing us any state.
+ */
+static isc_result_t
+dhc6_check_reply(struct client_state *client, struct dhc6_lease *new)
+{
+ isc_boolean_t (*action)(struct client_state *,
+ isc_result_t *, unsigned);
+ struct dhc6_ia *ia;
+ struct dhc6_addr *addr;
+ isc_result_t rval = ISC_R_SUCCESS;
+ unsigned code;
+ const char *scope;
+ int nscore, sscore;
+
+ if ((client == NULL) || (new == NULL))
+ return DHCP_R_INVALIDARG;
+
+ switch (client->state) {
+ case S_INIT:
+ action = dhc6_init_action;
+ break;
+
+ case S_SELECTING:
+ case S_REBOOTING:
+ action = dhc6_select_action;
+ break;
+
+ case S_RENEWING:
+ case S_REBINDING:
+ action = dhc6_reply_action;
+ break;
+
+ case S_STOPPED:
+ action = dhc6_stop_action;
+ break;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ return ISC_R_CANCELED;
+ }
+
+ /* If there is a code to extract, and if there is some
+ * action to take based on that code, then take the action
+ * and do not continue.
+ */
+ rval = dhc6_check_status(rval, new->options, "message", &code);
+ if (action(client, &rval, code))
+ return ISC_R_CANCELED;
+
+ for (ia = new->bindings ; ia != NULL ; ia = ia->next) {
+ switch (ia->ia_type) {
+ case D6O_IA_NA:
+ scope = "IA_NA";
+ break;
+ case D6O_IA_TA:
+ scope = "IA_TA";
+ break;
+ case D6O_IA_PD:
+ scope = "IA_PD";
+ break;
+ default:
+ log_error("dhc6_check_reply: no type.");
+ return DHCP_R_INVALIDARG;
+ }
+ rval = dhc6_check_status(rval, ia->options,
+ scope, &code);
+ if (action(client, &rval, code))
+ return ISC_R_CANCELED;
+
+ for (addr = ia->addrs ; addr != NULL ;
+ addr = addr->next) {
+ if (ia->ia_type != D6O_IA_PD)
+ scope = "IAADDR";
+ else
+ scope = "IAPREFIX";
+ rval = dhc6_check_status(rval, addr->options,
+ scope, &code);
+ if (action(client, &rval, code))
+ return ISC_R_CANCELED;
+ }
+ }
+
+ /* A Confirm->Reply is unsuitable for comparison to the old lease. */
+ if (client->state == S_REBOOTING)
+ return rval;
+
+ /* No old lease in rapid-commit. */
+ if (client->state == S_INIT)
+ return rval;
+
+ switch (client->state) {
+ case S_SELECTING:
+ /* Compare the new lease with the selected lease to make
+ * sure there is no risky business.
+ */
+ nscore = dhc6_score_lease(client, new);
+ sscore = dhc6_score_lease(client, client->selected_lease);
+ if ((client->advertised_leases != NULL) &&
+ (nscore < (sscore / 2))) {
+ /* XXX: An attacker might reply this way to make
+ * XXX: sure we latch onto their configuration.
+ * XXX: We might want to ignore the packet and
+ * XXX: schedule re-selection at the next timeout?
+ */
+ log_error("PRC: BAIT AND SWITCH detected. Score of "
+ "supplied lease (%d) is substantially "
+ "smaller than the advertised score (%d). "
+ "Trying other servers.",
+ nscore, sscore);
+
+ dhc6_lease_destroy(&client->selected_lease, MDL);
+ client->selected_lease = NULL;
+
+ start_selecting6(client);
+
+ return ISC_R_CANCELED;
+ }
+ break;
+
+ case S_RENEWING:
+ case S_REBINDING:
+ /* This leaves one RFC3315 status check unimplemented:
+ *
+ * - sends a Renew/Rebind if the IA is not in the Reply
+ * message
+ *
+ * We rely on the scheduling system to note that the IA has
+ * not left Renewal/Rebinding/whatever since it still carries
+ * old times from the last successful binding. So this is
+ * implemented actually, just not explicitly.
+ */
+ break;
+
+ case S_STOPPED:
+ /* Nothing critical to do at this stage. */
+ break;
+
+ default:
+ log_fatal("REALLY impossible condition at %s:%d.", MDL);
+ return ISC_R_CANCELED;
+ }
+
+ return rval;
+}
+
+/* While in init state, we only collect advertisements. If there happens
+ * to be an advertisement with a preference option of 255, that's an
+ * automatic exit. Otherwise, we collect advertisements until our timeout
+ * expires (client->RT).
+ */
+void
+init_handler(struct packet *packet, struct client_state *client)
+{
+ struct dhc6_lease *lease;
+
+ /* In INIT state, we send solicits, we only expect to get
+ * advertises (rapid commit has its own handler).
+ */
+ if (packet->dhcpv6_msg_type != DHCPV6_ADVERTISE)
+ return;
+
+ /* RFC3315 section 15.3 validation (same as 15.10 since we
+ * always include a client id).
+ */
+ if (!valid_reply(packet, client)) {
+ log_error("Invalid Advertise - rejecting.");
+ return;
+ }
+
+ lease = dhc6_leaseify(packet);
+
+ if (dhc6_check_advertise(lease) != ISC_R_SUCCESS) {
+ log_debug("PRC: Lease failed to satisfy.");
+ dhc6_lease_destroy(&lease, MDL);
+ return;
+ }
+
+ insert_lease(&client->advertised_leases, lease);
+
+ /* According to RFC3315 section 17.1.2, the client MUST wait for
+ * the first RT before selecting a lease. But on the 400th RT,
+ * we dont' want to wait the full timeout if we finally get an
+ * advertise. We could probably wait a second, but ohwell,
+ * RFC3315 doesn't say so.
+ *
+ * If the lease is highest possible preference, 255, RFC3315 claims
+ * we should continue immediately even on the first RT. We probably
+ * should not if the advertise contains less than one IA and address.
+ */
+ if ((client->txcount > 1) ||
+ ((lease->pref == 255) &&
+ (dhc6_score_lease(client, lease) > 150))) {
+ log_debug("RCV: Advertisement immediately selected.");
+ cancel_timeout(do_init6, client);
+ start_selecting6(client);
+ } else
+ log_debug("RCV: Advertisement recorded.");
+}
+
+/* info_request_handler() accepts a Reply to an Info-request.
+ */
+void
+info_request_handler(struct packet *packet, struct client_state *client)
+{
+ isc_result_t check_status;
+ unsigned code;
+
+ if (packet->dhcpv6_msg_type != DHCPV6_REPLY)
+ return;
+
+ /* RFC3315 section 15.10 validation (same as 15.3 since we
+ * always include a client id).
+ */
+ if (!valid_reply(packet, client)) {
+ log_error("Invalid Reply - rejecting.");
+ return;
+ }
+
+ check_status = dhc6_check_status(ISC_R_SUCCESS, packet->options,
+ "message", &code);
+ if (check_status != ISC_R_SUCCESS) {
+ /* If no action was taken, but there is an error, then
+ * we wait for a retransmission.
+ */
+ if (check_status != ISC_R_CANCELED)
+ return;
+ }
+
+ /* We're done retransmitting at this point. */
+ cancel_timeout(do_info_request6, client);
+
+ /* Action was taken, so now that we've torn down our scheduled
+ * retransmissions, return.
+ */
+ if (check_status == ISC_R_CANCELED)
+ return;
+
+ /* Cleanup if a previous attempt to go bound failed. */
+ if (client->old_lease != NULL) {
+ dhc6_lease_destroy(&client->old_lease, MDL);
+ client->old_lease = NULL;
+ }
+
+ /* Cache options in the active_lease. */
+ if (client->active_lease != NULL)
+ client->old_lease = client->active_lease;
+ client->active_lease = dmalloc(sizeof(struct dhc6_lease), MDL);
+ if (client->active_lease == NULL)
+ log_fatal("Out of memory for v6 lease structure.");
+ option_state_reference(&client->active_lease->options,
+ packet->options, MDL);
+
+ start_informed(client);
+}
+
+/* Specific version of init_handler() for rapid-commit.
+ */
+void
+rapid_commit_handler(struct packet *packet, struct client_state *client)
+{
+ struct dhc6_lease *lease;
+ isc_result_t check_status;
+
+ /* On ADVERTISE just fall back to the init_handler().
+ */
+ if (packet->dhcpv6_msg_type == DHCPV6_ADVERTISE) {
+ init_handler(packet, client);
+ return;
+ } else if (packet->dhcpv6_msg_type != DHCPV6_REPLY)
+ return;
+
+ /* RFC3315 section 15.10 validation (same as 15.3 since we
+ * always include a client id).
+ */
+ if (!valid_reply(packet, client)) {
+ log_error("Invalid Reply - rejecting.");
+ return;
+ }
+
+ /* A rapid-commit option MUST be here. */
+ if (lookup_option(&dhcpv6_universe, packet->options,
+ D6O_RAPID_COMMIT) == 0) {
+ log_error("Reply without Rapid-Commit - rejecting.");
+ return;
+ }
+
+ lease = dhc6_leaseify(packet);
+
+ /* This is an out of memory condition...hopefully a temporary
+ * problem. Returning now makes us try to retransmit later.
+ */
+ if (lease == NULL)
+ return;
+
+ check_status = dhc6_check_reply(client, lease);
+ if (check_status != ISC_R_SUCCESS) {
+ dhc6_lease_destroy(&lease, MDL);
+ return;
+ }
+
+ /* Jump to the selecting state. */
+ cancel_timeout(do_init6, client);
+ client->state = S_SELECTING;
+
+ /* Merge any bindings in the active lease (if there is one) into
+ * the new active lease.
+ */
+ dhc6_merge_lease(client->active_lease, lease);
+
+ /* Cleanup if a previous attempt to go bound failed. */
+ if (client->old_lease != NULL) {
+ dhc6_lease_destroy(&client->old_lease, MDL);
+ client->old_lease = NULL;
+ }
+
+ /* Make this lease active and BIND to it. */
+ if (client->active_lease != NULL)
+ client->old_lease = client->active_lease;
+ client->active_lease = lease;
+
+ /* We're done with the ADVERTISEd leases, if any. */
+ while(client->advertised_leases != NULL) {
+ lease = client->advertised_leases;
+ client->advertised_leases = lease->next;
+
+ dhc6_lease_destroy(&lease, MDL);
+ }
+
+ start_bound(client);
+}
+
+/* Find the 'best' lease in the cache of advertised leases (usually). From
+ * RFC3315 Section 17.1.3:
+ *
+ * Upon receipt of one or more valid Advertise messages, the client
+ * selects one or more Advertise messages based upon the following
+ * criteria.
+ *
+ * - Those Advertise messages with the highest server preference value
+ * are preferred over all other Advertise messages.
+ *
+ * - Within a group of Advertise messages with the same server
+ * preference value, a client MAY select those servers whose
+ * Advertise messages advertise information of interest to the
+ * client. For example, the client may choose a server that returned
+ * an advertisement with configuration options of interest to the
+ * client.
+ *
+ * - The client MAY choose a less-preferred server if that server has a
+ * better set of advertised parameters, such as the available
+ * addresses advertised in IAs.
+ *
+ * Note that the first and third contradict each other. The third should
+ * probably be taken to mean that the client should prefer answers that
+ * offer bindings, even if that violates the preference rule.
+ *
+ * The above also isn't deterministic where there are ties. So the final
+ * tiebreaker we add, if all other values are equal, is to compare the
+ * server identifiers and to select the numerically lower one.
+ */
+static struct dhc6_lease *
+dhc6_best_lease(struct client_state *client, struct dhc6_lease **head)
+{
+ struct dhc6_lease **rpos, *rval, **candp, *cand;
+ int cscore, rscore;
+
+ if (head == NULL || *head == NULL)
+ return NULL;
+
+ rpos = head;
+ rval = *rpos;
+ rscore = dhc6_score_lease(client, rval);
+ candp = &rval->next;
+ cand = *candp;
+
+ log_debug("PRC: Considering best lease.");
+ log_debug("PRC: X-- Initial candidate %s (s: %d, p: %u).",
+ print_hex_1(rval->server_id.len,
+ rval->server_id.data, 48),
+ rscore, (unsigned)rval->pref);
+
+ for (; cand != NULL ; candp = &cand->next, cand = *candp) {
+ cscore = dhc6_score_lease(client, cand);
+
+ log_debug("PRC: X-- Candidate %s (s: %d, p: %u).",
+ print_hex_1(cand->server_id.len,
+ cand->server_id.data, 48),
+ cscore, (unsigned)cand->pref);
+
+ /* Above you'll find quoted RFC3315 Section 17.1.3.
+ *
+ * The third clause tells us to give up on leases that
+ * have no bindings even if their preference is better.
+ * So where our 'selected' lease's score is less than 150
+ * (1 ia + 1 addr), choose any candidate >= 150.
+ *
+ * The first clause tells us to make preference the primary
+ * deciding factor. So if it's lower, reject, if it's
+ * higher, select.
+ *
+ * The second clause tells us where the preference is
+ * equal, we should use 'our judgement' of what we like
+ * to see in an advertisement primarily.
+ *
+ * But there can still be a tie. To make this deterministic,
+ * we compare the server identifiers and select the binary
+ * lowest.
+ *
+ * Since server id's are unique in this list, there is
+ * no further tie to break.
+ */
+ if ((rscore < 150) && (cscore >= 150)) {
+ log_debug("PRC: | X-- Selected, has bindings.");
+ } else if (cand->pref < rval->pref) {
+ log_debug("PRC: | X-- Rejected, lower preference.");
+ continue;
+ } else if (cand->pref > rval->pref) {
+ log_debug("PRC: | X-- Selected, higher preference.");
+ } else if (cscore > rscore) {
+ log_debug("PRC: | X-- Selected, equal preference, "
+ "higher score.");
+ } else if (cscore < rscore) {
+ log_debug("PRC: | X-- Rejected, equal preference, "
+ "lower score.");
+ continue;
+ } else if ((cand->server_id.len < rval->server_id.len) ||
+ ((cand->server_id.len == rval->server_id.len) &&
+ (memcmp(cand->server_id.data,
+ rval->server_id.data,
+ cand->server_id.len) < 0))) {
+ log_debug("PRC: | X-- Selected, equal preference, "
+ "equal score, binary lesser server ID.");
+ } else {
+ log_debug("PRC: | X-- Rejected, equal preference, "
+ "equal score, binary greater server ID.");
+ continue;
+ }
+
+ rpos = candp;
+ rval = cand;
+ rscore = cscore;
+ }
+
+ /* Remove the selected lease from the chain. */
+ *rpos = rval->next;
+
+ return rval;
+}
+
+/* Select a lease out of the advertised leases and setup state to try and
+ * acquire that lease.
+ */
+void
+start_selecting6(struct client_state *client)
+{
+ struct dhc6_lease *lease;
+
+ if (client->advertised_leases == NULL) {
+ log_error("Can not enter DHCPv6 SELECTING state with no "
+ "leases to select from!");
+ return;
+ }
+
+ log_debug("PRC: Selecting best advertised lease.");
+ client->state = S_SELECTING;
+
+ lease = dhc6_best_lease(client, &client->advertised_leases);
+
+ if (lease == NULL)
+ log_fatal("Impossible error at %s:%d.", MDL);
+
+ client->selected_lease = lease;
+
+ /* Set timers per RFC3315 section 18.1.1. */
+ client->IRT = REQ_TIMEOUT * 100;
+ client->MRT = REQ_MAX_RT * 100;
+ client->MRC = REQ_MAX_RC;
+ client->MRD = 0;
+
+ dhc6_retrans_init(client);
+
+ client->v6_handler = reply_handler;
+
+ /* ("re")transmit the first packet. */
+ do_select6(client);
+}
+
+/* Transmit a Request to select a lease offered in Advertisements. In
+ * the event of failure, either move on to the next-best advertised lease,
+ * or head back to INIT state if there are none.
+ */
+void
+do_select6(void *input)
+{
+ struct client_state *client;
+ struct dhc6_lease *lease;
+ struct data_string ds;
+ struct timeval tv;
+ int send_ret;
+
+ client = input;
+
+ /* 'lease' is fewer characters to type. */
+ lease = client->selected_lease;
+ if (lease == NULL || lease->bindings == NULL) {
+ log_error("Illegal to attempt selection without selecting "
+ "a lease.");
+ return;
+ }
+
+ switch(check_timing6(client, DHCPV6_REQUEST, "Request", lease, &ds)) {
+ case CHK_TIM_MRC_EXCEEDED:
+ case CHK_TIM_MRD_EXCEEDED:
+ log_debug("PRC: Lease %s failed.",
+ print_hex_1(lease->server_id.len,
+ lease->server_id.data, 56));
+
+ /* Get rid of the lease that timed/counted out. */
+ dhc6_lease_destroy(&lease, MDL);
+ client->selected_lease = NULL;
+
+ /* If there are more leases great. If not, get more. */
+ if (client->advertised_leases != NULL)
+ start_selecting6(client);
+ else
+ start_init6(client);
+ return;
+ case CHK_TIM_ALLOC_FAILURE:
+ return;
+ case CHK_TIM_SUCCESS:
+ break;
+ }
+
+ /* Now make a packet that looks suspiciously like the one we
+ * got from the server. But different.
+ *
+ * XXX: I guess IAID is supposed to be something the client
+ * indicates and uses as a key to its internal state. It is
+ * kind of odd to ask the server for IA's whose IAID the client
+ * did not manufacture. We first need a formal dhclient.conf
+ * construct for the iaid, then we can delve into this matter
+ * more properly. In the time being, this will work.
+ */
+
+ /* Fetch any configured 'sent' options (includes DUID) in wire format.
+ */
+ dhcpv6_universe.encapsulate(&ds, NULL, NULL, client,
+ NULL, client->sent_options, &global_scope,
+ &dhcpv6_universe);
+
+ /* Now append any IA's, and within them any IAADDR/IAPREFIXs. */
+ if (wanted_ia_na &&
+ dhc6_add_ia_na(client, &ds, lease,
+ DHCPV6_REQUEST) != ISC_R_SUCCESS) {
+ data_string_forget(&ds, MDL);
+ return;
+ }
+ if (wanted_ia_ta &&
+ dhc6_add_ia_ta(client, &ds, lease,
+ DHCPV6_REQUEST) != ISC_R_SUCCESS) {
+ data_string_forget(&ds, MDL);
+ return;
+ }
+ if (wanted_ia_pd &&
+ dhc6_add_ia_pd(client, &ds, lease,
+ DHCPV6_REQUEST) != ISC_R_SUCCESS) {
+ data_string_forget(&ds, MDL);
+ return;
+ }
+
+ log_info("XMT: Request on %s, interval %ld0ms.",
+ client->name ? client->name : client->interface->name,
+ (long int)client->RT);
+
+ send_ret = send_packet6(client->interface,
+ ds.data, ds.len, &DHCPv6DestAddr);
+ if (send_ret != ds.len) {
+ log_error("dhc6: send_packet6() sent %d of %d bytes",
+ send_ret, ds.len);
+ }
+
+ data_string_forget(&ds, MDL);
+
+ /* Wait RT */
+ tv.tv_sec = cur_tv.tv_sec + client->RT / 100;
+ tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000;
+ if (tv.tv_usec >= 1000000) {
+ tv.tv_sec += 1;
+ tv.tv_usec -= 1000000;
+ }
+ add_timeout(&tv, do_select6, client, NULL, NULL);
+
+ dhc6_retrans_advance(client);
+}
+
+/* For each IA_NA in the lease, for each address in the IA_NA,
+ * append that information onto the packet-so-far.
+ */
+static isc_result_t
+dhc6_add_ia_na(struct client_state *client, struct data_string *packet,
+ struct dhc6_lease *lease, u_int8_t message)
+{
+ struct data_string iads;
+ struct data_string addrds;
+ struct dhc6_addr *addr;
+ struct dhc6_ia *ia;
+ isc_result_t rval = ISC_R_SUCCESS;
+ TIME t1, t2;
+
+ memset(&iads, 0, sizeof(iads));
+ memset(&addrds, 0, sizeof(addrds));
+ for (ia = lease->bindings;
+ ia != NULL && rval == ISC_R_SUCCESS;
+ ia = ia->next) {
+ if (ia->ia_type != D6O_IA_NA)
+ continue;
+
+ if (!buffer_allocate(&iads.buffer, 12, MDL)) {
+ log_error("Unable to allocate memory for IA_NA.");
+ rval = ISC_R_NOMEMORY;
+ break;
+ }
+
+ /* Copy the IAID into the packet buffer. */
+ memcpy(iads.buffer->data, ia->iaid, 4);
+ iads.data = iads.buffer->data;
+ iads.len = 12;
+
+ switch (message) {
+ case DHCPV6_REQUEST:
+ case DHCPV6_RENEW:
+ case DHCPV6_REBIND:
+
+ t1 = client->config->requested_lease / 2;
+ t2 = t1 + (t1 / 2);
+#if MAX_TIME > 0xffffffff
+ if (t1 > 0xffffffff)
+ t1 = 0xffffffff;
+ if (t2 > 0xffffffff)
+ t2 = 0xffffffff;
+#endif
+ putULong(iads.buffer->data + 4, t1);
+ putULong(iads.buffer->data + 8, t2);
+
+ log_debug("XMT: X-- IA_NA %s",
+ print_hex_1(4, iads.data, 59));
+ log_debug("XMT: | X-- Requested renew +%u",
+ (unsigned) t1);
+ log_debug("XMT: | X-- Requested rebind +%u",
+ (unsigned) t2);
+ break;
+
+ case DHCPV6_CONFIRM:
+ case DHCPV6_RELEASE:
+ case DHCPV6_DECLINE:
+ /* Set t1 and t2 to zero; server will ignore them */
+ memset(iads.buffer->data + 4, 0, 8);
+ log_debug("XMT: X-- IA_NA %s",
+ print_hex_1(4, iads.buffer->data, 55));
+
+ break;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ }
+
+ for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
+ /*
+ * Do not confirm expired addresses, do not request
+ * expired addresses (but we keep them around for
+ * solicit).
+ */
+ if (addr->flags & DHC6_ADDR_EXPIRED)
+ continue;
+
+ if (addr->address.len != 16) {
+ log_error("Illegal IPv6 address length (%d), "
+ "ignoring. (%s:%d)",
+ addr->address.len, MDL);
+ continue;
+ }
+
+ if (!buffer_allocate(&addrds.buffer, 24, MDL)) {
+ log_error("Unable to allocate memory for "
+ "IAADDR.");
+ rval = ISC_R_NOMEMORY;
+ break;
+ }
+
+ addrds.data = addrds.buffer->data;
+ addrds.len = 24;
+
+ /* Copy the address into the packet buffer. */
+ memcpy(addrds.buffer->data, addr->address.iabuf, 16);
+
+ /* Copy in additional information as appropriate */
+ switch (message) {
+ case DHCPV6_REQUEST:
+ case DHCPV6_RENEW:
+ case DHCPV6_REBIND:
+ t1 = client->config->requested_lease;
+ t2 = t1 + 300;
+ putULong(addrds.buffer->data + 16, t1);
+ putULong(addrds.buffer->data + 20, t2);
+
+ log_debug("XMT: | | X-- IAADDR %s",
+ piaddr(addr->address));
+ log_debug("XMT: | | | X-- Preferred "
+ "lifetime +%u", (unsigned)t1);
+ log_debug("XMT: | | | X-- Max lifetime +%u",
+ (unsigned)t2);
+
+ break;
+
+ case DHCPV6_CONFIRM:
+ /*
+ * Set preferred and max life to zero,
+ * per 17.1.3.
+ */
+ memset(addrds.buffer->data + 16, 0, 8);
+ log_debug("XMT: | X-- Confirm Address %s",
+ piaddr(addr->address));
+ break;
+
+ case DHCPV6_RELEASE:
+ /* Preferred and max life are irrelevant */
+ memset(addrds.buffer->data + 16, 0, 8);
+ log_debug("XMT: | X-- Release Address %s",
+ piaddr(addr->address));
+ break;
+
+ case DHCPV6_DECLINE:
+ /* Preferred and max life are irrelevant */
+ memset(addrds.buffer->data + 16, 0, 8);
+ log_debug("XMT: | X-- Decline Address %s",
+ piaddr(addr->address));
+ break;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.",
+ MDL);
+ }
+
+ append_option(&iads, &dhcpv6_universe, iaaddr_option,
+ &addrds);
+ data_string_forget(&addrds, MDL);
+ }
+
+ /*
+ * It doesn't make sense to make a request without an
+ * address.
+ */
+ if (ia->addrs == NULL) {
+ log_debug("!!!: V IA_NA has no IAADDRs - removed.");
+ rval = ISC_R_FAILURE;
+ } else if (rval == ISC_R_SUCCESS) {
+ log_debug("XMT: V IA_NA appended.");
+ append_option(packet, &dhcpv6_universe, ia_na_option,
+ &iads);
+ }
+
+ data_string_forget(&iads, MDL);
+ }
+
+ return rval;
+}
+
+/* For each IA_TA in the lease, for each address in the IA_TA,
+ * append that information onto the packet-so-far.
+ */
+static isc_result_t
+dhc6_add_ia_ta(struct client_state *client, struct data_string *packet,
+ struct dhc6_lease *lease, u_int8_t message)
+{
+ struct data_string iads;
+ struct data_string addrds;
+ struct dhc6_addr *addr;
+ struct dhc6_ia *ia;
+ isc_result_t rval = ISC_R_SUCCESS;
+ TIME t1, t2;
+
+ memset(&iads, 0, sizeof(iads));
+ memset(&addrds, 0, sizeof(addrds));
+ for (ia = lease->bindings;
+ ia != NULL && rval == ISC_R_SUCCESS;
+ ia = ia->next) {
+ if (ia->ia_type != D6O_IA_TA)
+ continue;
+
+ if (!buffer_allocate(&iads.buffer, 4, MDL)) {
+ log_error("Unable to allocate memory for IA_TA.");
+ rval = ISC_R_NOMEMORY;
+ break;
+ }
+
+ /* Copy the IAID into the packet buffer. */
+ memcpy(iads.buffer->data, ia->iaid, 4);
+ iads.data = iads.buffer->data;
+ iads.len = 4;
+
+ log_debug("XMT: X-- IA_TA %s",
+ print_hex_1(4, iads.buffer->data, 55));
+
+ for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
+ /*
+ * Do not confirm expired addresses, do not request
+ * expired addresses (but we keep them around for
+ * solicit).
+ */
+ if (addr->flags & DHC6_ADDR_EXPIRED)
+ continue;
+
+ if (addr->address.len != 16) {
+ log_error("Illegal IPv6 address length (%d), "
+ "ignoring. (%s:%d)",
+ addr->address.len, MDL);
+ continue;
+ }
+
+ if (!buffer_allocate(&addrds.buffer, 24, MDL)) {
+ log_error("Unable to allocate memory for "
+ "IAADDR.");
+ rval = ISC_R_NOMEMORY;
+ break;
+ }
+
+ addrds.data = addrds.buffer->data;
+ addrds.len = 24;
+
+ /* Copy the address into the packet buffer. */
+ memcpy(addrds.buffer->data, addr->address.iabuf, 16);
+
+ /* Copy in additional information as appropriate */
+ switch (message) {
+ case DHCPV6_REQUEST:
+ case DHCPV6_RENEW:
+ case DHCPV6_REBIND:
+ t1 = client->config->requested_lease;
+ t2 = t1 + 300;
+ putULong(addrds.buffer->data + 16, t1);
+ putULong(addrds.buffer->data + 20, t2);
+
+ log_debug("XMT: | | X-- IAADDR %s",
+ piaddr(addr->address));
+ log_debug("XMT: | | | X-- Preferred "
+ "lifetime +%u", (unsigned)t1);
+ log_debug("XMT: | | | X-- Max lifetime +%u",
+ (unsigned)t2);
+
+ break;
+
+ case DHCPV6_CONFIRM:
+ /*
+ * Set preferred and max life to zero,
+ * per 17.1.3.
+ */
+ memset(addrds.buffer->data + 16, 0, 8);
+ log_debug("XMT: | X-- Confirm Address %s",
+ piaddr(addr->address));
+ break;
+
+ case DHCPV6_RELEASE:
+ /* Preferred and max life are irrelevant */
+ memset(addrds.buffer->data + 16, 0, 8);
+ log_debug("XMT: | X-- Release Address %s",
+ piaddr(addr->address));
+ break;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.",
+ MDL);
+ }
+
+ append_option(&iads, &dhcpv6_universe, iaaddr_option,
+ &addrds);
+ data_string_forget(&addrds, MDL);
+ }
+
+ /*
+ * It doesn't make sense to make a request without an
+ * address.
+ */
+ if (ia->addrs == NULL) {
+ log_debug("!!!: V IA_TA has no IAADDRs - removed.");
+ rval = ISC_R_FAILURE;
+ } else if (rval == ISC_R_SUCCESS) {
+ log_debug("XMT: V IA_TA appended.");
+ append_option(packet, &dhcpv6_universe, ia_ta_option,
+ &iads);
+ }
+
+ data_string_forget(&iads, MDL);
+ }
+
+ return rval;
+}
+
+/* For each IA_PD in the lease, for each prefix in the IA_PD,
+ * append that information onto the packet-so-far.
+ */
+static isc_result_t
+dhc6_add_ia_pd(struct client_state *client, struct data_string *packet,
+ struct dhc6_lease *lease, u_int8_t message)
+{
+ struct data_string iads;
+ struct data_string prefds;
+ struct dhc6_addr *pref;
+ struct dhc6_ia *ia;
+ isc_result_t rval = ISC_R_SUCCESS;
+ TIME t1, t2;
+
+ memset(&iads, 0, sizeof(iads));
+ memset(&prefds, 0, sizeof(prefds));
+ for (ia = lease->bindings;
+ ia != NULL && rval == ISC_R_SUCCESS;
+ ia = ia->next) {
+ if (ia->ia_type != D6O_IA_PD)
+ continue;
+
+ if (!buffer_allocate(&iads.buffer, 12, MDL)) {
+ log_error("Unable to allocate memory for IA_PD.");
+ rval = ISC_R_NOMEMORY;
+ break;
+ }
+
+ /* Copy the IAID into the packet buffer. */
+ memcpy(iads.buffer->data, ia->iaid, 4);
+ iads.data = iads.buffer->data;
+ iads.len = 12;
+
+ switch (message) {
+ case DHCPV6_REQUEST:
+ case DHCPV6_RENEW:
+ case DHCPV6_REBIND:
+
+ t1 = client->config->requested_lease / 2;
+ t2 = t1 + (t1 / 2);
+#if MAX_TIME > 0xffffffff
+ if (t1 > 0xffffffff)
+ t1 = 0xffffffff;
+ if (t2 > 0xffffffff)
+ t2 = 0xffffffff;
+#endif
+ putULong(iads.buffer->data + 4, t1);
+ putULong(iads.buffer->data + 8, t2);
+
+ log_debug("XMT: X-- IA_PD %s",
+ print_hex_1(4, iads.data, 59));
+ log_debug("XMT: | X-- Requested renew +%u",
+ (unsigned) t1);
+ log_debug("XMT: | X-- Requested rebind +%u",
+ (unsigned) t2);
+ break;
+
+ case DHCPV6_RELEASE:
+ /* Set t1 and t2 to zero; server will ignore them */
+ memset(iads.buffer->data + 4, 0, 8);
+ log_debug("XMT: X-- IA_PD %s",
+ print_hex_1(4, iads.buffer->data, 55));
+
+ break;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ }
+
+ for (pref = ia->addrs ; pref != NULL ; pref = pref->next) {
+ /*
+ * Do not confirm expired prefixes, do not request
+ * expired prefixes (but we keep them around for
+ * solicit).
+ */
+ if (pref->flags & DHC6_ADDR_EXPIRED)
+ continue;
+
+ if (pref->address.len != 16) {
+ log_error("Illegal IPv6 prefix "
+ "ignoring. (%s:%d)",
+ MDL);
+ continue;
+ }
+
+ if (pref->plen == 0) {
+ log_info("Null IPv6 prefix, "
+ "ignoring. (%s:%d)",
+ MDL);
+ }
+
+ if (!buffer_allocate(&prefds.buffer, 25, MDL)) {
+ log_error("Unable to allocate memory for "
+ "IAPREFIX.");
+ rval = ISC_R_NOMEMORY;
+ break;
+ }
+
+ prefds.data = prefds.buffer->data;
+ prefds.len = 25;
+
+ /* Copy the prefix into the packet buffer. */
+ putUChar(prefds.buffer->data + 8, pref->plen);
+ memcpy(prefds.buffer->data + 9,
+ pref->address.iabuf,
+ 16);
+
+ /* Copy in additional information as appropriate */
+ switch (message) {
+ case DHCPV6_REQUEST:
+ case DHCPV6_RENEW:
+ case DHCPV6_REBIND:
+ t1 = client->config->requested_lease;
+ t2 = t1 + 300;
+ putULong(prefds.buffer->data, t1);
+ putULong(prefds.buffer->data + 4, t2);
+
+ log_debug("XMT: | | X-- IAPREFIX %s/%u",
+ piaddr(pref->address),
+ (unsigned) pref->plen);
+ log_debug("XMT: | | | X-- Preferred "
+ "lifetime +%u", (unsigned)t1);
+ log_debug("XMT: | | | X-- Max lifetime +%u",
+ (unsigned)t2);
+
+ break;
+
+ case DHCPV6_RELEASE:
+ /* Preferred and max life are irrelevant */
+ memset(prefds.buffer->data, 0, 8);
+ log_debug("XMT: | X-- Release Prefix %s/%u",
+ piaddr(pref->address),
+ (unsigned) pref->plen);
+ break;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.",
+ MDL);
+ }
+
+ append_option(&iads, &dhcpv6_universe,
+ iaprefix_option, &prefds);
+ data_string_forget(&prefds, MDL);
+ }
+
+ /*
+ * It doesn't make sense to make a request without an
+ * address.
+ */
+ if (ia->addrs == NULL) {
+ log_debug("!!!: V IA_PD has no IAPREFIXs - removed.");
+ rval = ISC_R_FAILURE;
+ } else if (rval == ISC_R_SUCCESS) {
+ log_debug("XMT: V IA_PD appended.");
+ append_option(packet, &dhcpv6_universe,
+ ia_pd_option, &iads);
+ }
+
+ data_string_forget(&iads, MDL);
+ }
+
+ return rval;
+}
+
+/* stopping_finished() checks if there is a remaining work to do.
+ */
+static isc_boolean_t
+stopping_finished(void)
+{
+ struct interface_info *ip;
+ struct client_state *client;
+
+ for (ip = interfaces; ip; ip = ip -> next) {
+ for (client = ip -> client; client; client = client -> next) {
+ if (client->state != S_STOPPED)
+ return ISC_FALSE;
+ if (client->active_lease != NULL)
+ return ISC_FALSE;
+ }
+ }
+ return ISC_TRUE;
+}
+
+/* reply_handler() accepts a Reply while we're attempting Select or Renew or
+ * Rebind. Basically any Reply packet.
+ */
+void
+reply_handler(struct packet *packet, struct client_state *client)
+{
+ struct dhc6_lease *lease;
+ isc_result_t check_status;
+
+ if (packet->dhcpv6_msg_type != DHCPV6_REPLY)
+ return;
+
+ /* RFC3315 section 15.10 validation (same as 15.3 since we
+ * always include a client id).
+ */
+ if (!valid_reply(packet, client)) {
+ log_error("Invalid Reply - rejecting.");
+ return;
+ }
+
+ lease = dhc6_leaseify(packet);
+
+ /* This is an out of memory condition...hopefully a temporary
+ * problem. Returning now makes us try to retransmit later.
+ */
+ if (lease == NULL)
+ return;
+
+ check_status = dhc6_check_reply(client, lease);
+ if (check_status != ISC_R_SUCCESS) {
+ dhc6_lease_destroy(&lease, MDL);
+
+ /* If no action was taken, but there is an error, then
+ * we wait for a retransmission.
+ */
+ if (check_status != ISC_R_CANCELED)
+ return;
+ }
+
+ /* We're done retransmitting at this point. */
+ cancel_timeout(do_confirm6, client);
+ cancel_timeout(do_select6, client);
+ cancel_timeout(do_refresh6, client);
+ cancel_timeout(do_release6, client);
+
+ /* If this is in response to a Release/Decline, clean up and return. */
+ if (client->state == S_STOPPED) {
+ if (client->active_lease == NULL)
+ return;
+
+ dhc6_lease_destroy(&client->active_lease, MDL);
+ client->active_lease = NULL;
+ /* We should never wait for nothing!? */
+ if (stopping_finished())
+ exit(0);
+ return;
+ }
+
+ /* Action was taken, so now that we've torn down our scheduled
+ * retransmissions, return.
+ */
+ if (check_status == ISC_R_CANCELED)
+ return;
+
+ if (client->selected_lease != NULL) {
+ dhc6_lease_destroy(&client->selected_lease, MDL);
+ client->selected_lease = NULL;
+ }
+
+ /* If this is in response to a confirm, we use the lease we've
+ * already got, not the reply we were sent.
+ */
+ if (client->state == S_REBOOTING) {
+ if (client->active_lease == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ dhc6_lease_destroy(&lease, MDL);
+ start_bound(client);
+ return;
+ }
+
+ /* Merge any bindings in the active lease (if there is one) into
+ * the new active lease.
+ */
+ dhc6_merge_lease(client->active_lease, lease);
+
+ /* Cleanup if a previous attempt to go bound failed. */
+ if (client->old_lease != NULL) {
+ dhc6_lease_destroy(&client->old_lease, MDL);
+ client->old_lease = NULL;
+ }
+
+ /* Make this lease active and BIND to it. */
+ if (client->active_lease != NULL)
+ client->old_lease = client->active_lease;
+ client->active_lease = lease;
+
+ /* We're done with the ADVERTISEd leases, if any. */
+ while(client->advertised_leases != NULL) {
+ lease = client->advertised_leases;
+ client->advertised_leases = lease->next;
+
+ dhc6_lease_destroy(&lease, MDL);
+ }
+
+ start_bound(client);
+}
+
+/* DHCPv6 packets are a little sillier than they needed to be - the root
+ * packet contains options, then IA's which contain options, then within
+ * that IAADDR's which contain options.
+ *
+ * To sort this out at dhclient-script time (which fetches config parameters
+ * in environment variables), start_bound() iterates over each IAADDR, and
+ * calls this function to marshall an environment variable set that includes
+ * the most-specific option values related to that IAADDR in particular.
+ *
+ * To achieve this, we load environment variables for the root options space,
+ * then the IA, then the IAADDR. Any duplicate option names will be
+ * over-written by the later versions.
+ */
+static void
+dhc6_marshall_values(const char *prefix, struct client_state *client,
+ struct dhc6_lease *lease, struct dhc6_ia *ia,
+ struct dhc6_addr *addr)
+{
+ /* Option cache contents, in descending order of
+ * scope.
+ */
+ if ((lease != NULL) && (lease->options != NULL))
+ script_write_params6(client, prefix, lease->options);
+ if ((ia != NULL) && (ia->options != NULL))
+ script_write_params6(client, prefix, ia->options);
+ if ((addr != NULL) && (addr->options != NULL))
+ script_write_params6(client, prefix, addr->options);
+
+ /* addr fields. */
+ if (addr != NULL) {
+ if ((ia != NULL) && (ia->ia_type == D6O_IA_PD)) {
+ client_envadd(client, prefix,
+ "ip6_prefix", "%s/%u",
+ piaddr(addr->address),
+ (unsigned) addr->plen);
+ } else {
+ client_envadd(client, prefix, "ip6_prefixlen",
+ "%d", DHCLIENT_DEFAULT_PREFIX_LEN);
+ client_envadd(client, prefix, "ip6_address",
+ "%s", piaddr(addr->address));
+ }
+ if ((ia != NULL) && (ia->ia_type == D6O_IA_TA)) {
+ client_envadd(client, prefix,
+ "ip6_type", "temporary");
+ }
+ client_envadd(client, prefix, "life_starts", "%d",
+ (int)(addr->starts));
+ client_envadd(client, prefix, "preferred_life", "%u",
+ addr->preferred_life);
+ client_envadd(client, prefix, "max_life", "%u",
+ addr->max_life);
+ }
+
+ /* ia fields. */
+ if (ia != NULL) {
+ client_envadd(client, prefix, "iaid", "%s",
+ print_hex_1(4, ia->iaid, 12));
+ client_envadd(client, prefix, "starts", "%d",
+ (int)(ia->starts));
+ client_envadd(client, prefix, "renew", "%u", ia->renew);
+ client_envadd(client, prefix, "rebind", "%u", ia->rebind);
+ }
+}
+
+/* Look at where the client's active lease is sitting. If it's looking to
+ * time out on renew, rebind, depref, or expiration, do those things.
+ */
+static void
+dhc6_check_times(struct client_state *client)
+{
+ struct dhc6_lease *lease;
+ struct dhc6_ia *ia;
+ struct dhc6_addr *addr;
+ TIME renew=MAX_TIME, rebind=MAX_TIME, depref=MAX_TIME,
+ lo_expire=MAX_TIME, hi_expire=0, tmp;
+ int has_addrs = ISC_FALSE;
+ struct timeval tv;
+
+ lease = client->active_lease;
+
+ /* Bit spammy. We should probably keep record of scheduled
+ * events instead.
+ */
+ cancel_timeout(start_renew6, client);
+ cancel_timeout(start_rebind6, client);
+ cancel_timeout(do_depref, client);
+ cancel_timeout(do_expire, client);
+
+ for(ia = lease->bindings ; ia != NULL ; ia = ia->next) {
+ TIME this_ia_lo_expire, this_ia_hi_expire, use_expire;
+
+ this_ia_lo_expire = MAX_TIME;
+ this_ia_hi_expire = 0;
+
+ for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
+ if(!(addr->flags & DHC6_ADDR_DEPREFFED)) {
+ if (addr->preferred_life == 0xffffffff)
+ tmp = MAX_TIME;
+ else
+ tmp = addr->starts +
+ addr->preferred_life;
+
+ if (tmp < depref)
+ depref = tmp;
+ }
+
+ if (!(addr->flags & DHC6_ADDR_EXPIRED)) {
+ /* Find EPOCH-relative expiration. */
+ if (addr->max_life == 0xffffffff)
+ tmp = MAX_TIME;
+ else
+ tmp = addr->starts + addr->max_life;
+
+ /* Make the times ia->starts relative. */
+ tmp -= ia->starts;
+
+ if (tmp > this_ia_hi_expire)
+ this_ia_hi_expire = tmp;
+ if (tmp < this_ia_lo_expire)
+ this_ia_lo_expire = tmp;
+
+ has_addrs = ISC_TRUE;
+ }
+ }
+
+ /* These times are ia->starts relative. */
+ if (this_ia_lo_expire <= (this_ia_hi_expire / 2))
+ use_expire = this_ia_hi_expire;
+ else
+ use_expire = this_ia_lo_expire;
+
+ /*
+ * If the auto-selected expiration time is "infinite", or
+ * zero, assert a reasonable default.
+ */
+ if ((use_expire == MAX_TIME) || (use_expire <= 1))
+ use_expire = client->config->requested_lease / 2;
+ else
+ use_expire /= 2;
+
+ /* Don't renew/rebind temporary addresses. */
+ if (ia->ia_type != D6O_IA_TA) {
+
+ if (ia->renew == 0) {
+ tmp = ia->starts + use_expire;
+ } else if (ia->renew == 0xffffffff)
+ tmp = MAX_TIME;
+ else
+ tmp = ia->starts + ia->renew;
+
+ if (tmp < renew)
+ renew = tmp;
+
+ if (ia->rebind == 0) {
+ /* Set rebind to 3/4 expiration interval. */
+ tmp = ia->starts;
+ tmp += use_expire + (use_expire / 2);
+ } else if (ia->rebind == 0xffffffff)
+ tmp = MAX_TIME;
+ else
+ tmp = ia->starts + ia->rebind;
+
+ if (tmp < rebind)
+ rebind = tmp;
+ }
+
+ /*
+ * Return expiration ranges to EPOCH relative for event
+ * scheduling (add_timeout()).
+ */
+ this_ia_hi_expire += ia->starts;
+ this_ia_lo_expire += ia->starts;
+
+ if (this_ia_hi_expire > hi_expire)
+ hi_expire = this_ia_hi_expire;
+ if (this_ia_lo_expire < lo_expire)
+ lo_expire = this_ia_lo_expire;
+ }
+
+ /* If there are no addresses, give up, go to INIT.
+ * Note that if an address is unexpired with a date in the past,
+ * we're scheduling an expiration event to ocurr in the past. We
+ * could probably optimize this to expire now (but then there's
+ * recursion).
+ *
+ * In the future, we may decide that we're done here, or to
+ * schedule a future request (using 4-pkt info-request model).
+ */
+ if (has_addrs == ISC_FALSE) {
+ dhc6_lease_destroy(&client->active_lease, MDL);
+ client->active_lease = NULL;
+
+ /* Go back to the beginning. */
+ start_init6(client);
+ return;
+ }
+
+ switch(client->state) {
+ case S_BOUND:
+ /* We'd like to hit renewing, but if rebinding has already
+ * passed (time warp), head straight there.
+ */
+ if ((rebind > cur_time) && (renew < rebind)) {
+ log_debug("PRC: Renewal event scheduled in %d seconds, "
+ "to run for %u seconds.",
+ (int)(renew - cur_time),
+ (unsigned)(rebind - renew));
+ client->next_MRD = rebind;
+ tv.tv_sec = renew;
+ tv.tv_usec = 0;
+ add_timeout(&tv, start_renew6, client, NULL, NULL);
+
+ break;
+ }
+ /* FALL THROUGH */
+ case S_RENEWING:
+ /* While actively renewing, MRD is bounded by the time
+ * we stop renewing and start rebinding. This helps us
+ * process the state change on time.
+ */
+ client->MRD = rebind - cur_time;
+ if (rebind != MAX_TIME) {
+ log_debug("PRC: Rebind event scheduled in %d seconds, "
+ "to run for %d seconds.",
+ (int)(rebind - cur_time),
+ (int)(hi_expire - rebind));
+ client->next_MRD = hi_expire;
+ tv.tv_sec = rebind;
+ tv.tv_usec = 0;
+ add_timeout(&tv, start_rebind6, client, NULL, NULL);
+ }
+ break;
+
+ case S_REBINDING:
+ /* For now, we rebind up until the last lease expires. In
+ * the future, we might want to start SOLICITing when we've
+ * depreffed an address.
+ */
+ client->MRD = hi_expire - cur_time;
+ break;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ }
+
+ /* Separately, set a time at which we will depref and expire
+ * leases. This might happen with multiple addresses while we
+ * keep trying to refresh.
+ */
+ if (depref != MAX_TIME) {
+ log_debug("PRC: Depreference scheduled in %d seconds.",
+ (int)(depref - cur_time));
+ tv.tv_sec = depref;
+ tv.tv_usec = 0;
+ add_timeout(&tv, do_depref, client, NULL, NULL);
+ }
+ if (lo_expire != MAX_TIME) {
+ log_debug("PRC: Expiration scheduled in %d seconds.",
+ (int)(lo_expire - cur_time));
+ tv.tv_sec = lo_expire;
+ tv.tv_usec = 0;
+ add_timeout(&tv, do_expire, client, NULL, NULL);
+ }
+}
+
+/* In a given IA chain, find the IA with the same type and 'iaid'. */
+static struct dhc6_ia *
+find_ia(struct dhc6_ia *head, u_int16_t type, const char *id)
+{
+ struct dhc6_ia *ia;
+
+ for (ia = head ; ia != NULL ; ia = ia->next) {
+ if (ia->ia_type != type)
+ continue;
+ if (memcmp(ia->iaid, id, 4) == 0)
+ return ia;
+ }
+
+ return NULL;
+}
+
+/* In a given address chain, find a matching address. */
+static struct dhc6_addr *
+find_addr(struct dhc6_addr *head, struct iaddr *address)
+{
+ struct dhc6_addr *addr;
+
+ for (addr = head ; addr != NULL ; addr = addr->next) {
+ if ((addr->address.len == address->len) &&
+ (memcmp(addr->address.iabuf, address->iabuf,
+ address->len) == 0))
+ return addr;
+ }
+
+ return NULL;
+}
+
+/* In a given prefix chain, find a matching prefix. */
+static struct dhc6_addr *
+find_pref(struct dhc6_addr *head, struct iaddr *prefix, u_int8_t plen)
+{
+ struct dhc6_addr *pref;
+
+ for (pref = head ; pref != NULL ; pref = pref->next) {
+ if ((pref->address.len == prefix->len) &&
+ (pref->plen == plen) &&
+ (memcmp(pref->address.iabuf, prefix->iabuf,
+ prefix->len) == 0))
+ return pref;
+ }
+
+ return NULL;
+}
+
+/* Merge the bindings from the source lease into the destination lease
+ * structure, where they are missing. We have to copy the stateful
+ * objects rather than move them over, because later code needs to be
+ * able to compare new versus old if they contain any bindings.
+ */
+static void
+dhc6_merge_lease(struct dhc6_lease *src, struct dhc6_lease *dst)
+{
+ struct dhc6_ia *sia, *dia, *tia;
+ struct dhc6_addr *saddr, *daddr, *taddr;
+ int changes = 0;
+
+ if ((dst == NULL) || (src == NULL))
+ return;
+
+ for (sia = src->bindings ; sia != NULL ; sia = sia->next) {
+ dia = find_ia(dst->bindings, sia->ia_type, (char *)sia->iaid);
+
+ if (dia == NULL) {
+ tia = dhc6_dup_ia(sia, MDL);
+
+ if (tia == NULL)
+ log_fatal("Out of memory merging lease - "
+ "Unable to continue without losing "
+ "state! (%s:%d)", MDL);
+
+ /* XXX: consider sorting? */
+ tia->next = dst->bindings;
+ dst->bindings = tia;
+ changes = 1;
+ } else {
+ for (saddr = sia->addrs ; saddr != NULL ;
+ saddr = saddr->next) {
+ if (sia->ia_type != D6O_IA_PD)
+ daddr = find_addr(dia->addrs,
+ &saddr->address);
+ else
+ daddr = find_pref(dia->addrs,
+ &saddr->address,
+ saddr->plen);
+
+ if (daddr == NULL) {
+ taddr = dhc6_dup_addr(saddr, MDL);
+
+ if (taddr == NULL)
+ log_fatal("Out of memory "
+ "merging lease - "
+ "Unable to continue "
+ "without losing "
+ "state! (%s:%d)",
+ MDL);
+
+ /* XXX: consider sorting? */
+ taddr->next = dia->addrs;
+ dia->addrs = taddr;
+ changes = 1;
+ }
+ }
+ }
+ }
+
+ /* If we made changes, reset the score to 0 so it is recalculated. */
+ if (changes)
+ dst->score = 0;
+}
+
+/* We've either finished selecting or succeeded in Renew or Rebinding our
+ * lease. In all cases we got a Reply. Give dhclient-script a tickle
+ * to inform it about the new values, and then lay in wait for the next
+ * event.
+ */
+static void
+start_bound(struct client_state *client)
+{
+ struct dhc6_ia *ia, *oldia;
+ struct dhc6_addr *addr, *oldaddr;
+ struct dhc6_lease *lease, *old;
+ const char *reason;
+#if defined (NSUPDATE)
+ TIME dns_update_offset = 1;
+#endif
+
+ lease = client->active_lease;
+ if (lease == NULL) {
+ log_error("Cannot enter bound state unless an active lease "
+ "is selected.");
+ return;
+ }
+ lease->released = ISC_FALSE;
+ old = client->old_lease;
+
+ client->v6_handler = bound_handler;
+
+ switch (client->state) {
+ case S_SELECTING:
+ case S_REBOOTING: /* Pretend we got bound. */
+ reason = "BOUND6";
+ break;
+
+ case S_RENEWING:
+ reason = "RENEW6";
+ break;
+
+ case S_REBINDING:
+ reason = "REBIND6";
+ break;
+
+ default:
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ /* Silence compiler warnings. */
+ return;
+ }
+
+ log_debug("PRC: Bound to lease %s.",
+ print_hex_1(client->active_lease->server_id.len,
+ client->active_lease->server_id.data, 55));
+ client->state = S_BOUND;
+
+ write_client6_lease(client, lease, 0, 1);
+
+ oldia = NULL;
+ for (ia = lease->bindings ; ia != NULL ; ia = ia->next) {
+ if (old != NULL)
+ oldia = find_ia(old->bindings,
+ ia->ia_type,
+ (char *)ia->iaid);
+ else
+ oldia = NULL;
+
+ for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
+ if (oldia != NULL) {
+ if (ia->ia_type != D6O_IA_PD)
+ oldaddr = find_addr(oldia->addrs,
+ &addr->address);
+ else
+ oldaddr = find_pref(oldia->addrs,
+ &addr->address,
+ addr->plen);
+ } else
+ oldaddr = NULL;
+
+#if defined (NSUPDATE)
+ if ((oldaddr == NULL) && (ia->ia_type == D6O_IA_NA))
+ dhclient_schedule_updates(client,
+ &addr->address,
+ dns_update_offset++);
+#endif
+
+ /* Shell out to setup the new binding. */
+ script_init(client, reason, NULL);
+
+ if (old != NULL)
+ dhc6_marshall_values("old_", client, old,
+ oldia, oldaddr);
+ dhc6_marshall_values("new_", client, lease, ia, addr);
+ script_write_requested6(client);
+
+ script_go(client);
+ }
+
+ /* XXX: maybe we should loop on the old values instead? */
+ if (ia->addrs == NULL) {
+ script_init(client, reason, NULL);
+
+ if (old != NULL)
+ dhc6_marshall_values("old_", client, old,
+ oldia,
+ oldia != NULL ?
+ oldia->addrs : NULL);
+
+ dhc6_marshall_values("new_", client, lease, ia,
+ NULL);
+ script_write_requested6(client);
+
+ script_go(client);
+ }
+ }
+
+ /* XXX: maybe we should loop on the old values instead? */
+ if (lease->bindings == NULL) {
+ script_init(client, reason, NULL);
+
+ if (old != NULL)
+ dhc6_marshall_values("old_", client, old,
+ old->bindings,
+ (old->bindings != NULL) ?
+ old->bindings->addrs : NULL);
+
+ dhc6_marshall_values("new_", client, lease, NULL, NULL);
+ script_write_requested6(client);
+
+ script_go(client);
+ }
+
+ go_daemon();
+
+ if (client->old_lease != NULL) {
+ dhc6_lease_destroy(&client->old_lease, MDL);
+ client->old_lease = NULL;
+ }
+
+ /* Schedule events. */
+ dhc6_check_times(client);
+}
+
+/* While bound, ignore packets. In the future we'll want to answer
+ * Reconfigure-Request messages and the like.
+ */
+void
+bound_handler(struct packet *packet, struct client_state *client)
+{
+ log_debug("RCV: Input packets are ignored once bound.");
+}
+
+/* start_renew6() gets us all ready to go to start transmitting Renew packets.
+ * Note that client->next_MRD must be set before entering this function -
+ * it must be set to the time at which the client should start Rebinding.
+ */
+void
+start_renew6(void *input)
+{
+ struct client_state *client;
+
+ client = (struct client_state *)input;
+
+ log_info("PRC: Renewing lease on %s.",
+ client->name ? client->name : client->interface->name);
+ client->state = S_RENEWING;
+
+ client->v6_handler = reply_handler;
+
+ /* Times per RFC3315 section 18.1.3. */
+ client->IRT = REN_TIMEOUT * 100;
+ client->MRT = REN_MAX_RT * 100;
+ client->MRC = 0;
+ /* MRD is special in renew - we need to set it by checking timer
+ * state.
+ */
+ client->MRD = client->next_MRD - cur_time;
+
+ dhc6_retrans_init(client);
+
+ client->refresh_type = DHCPV6_RENEW;
+ do_refresh6(client);
+}
+
+/* do_refresh6() transmits one DHCPv6 packet, be it a Renew or Rebind, and
+ * gives the retransmission state a bump for the next time. Note that
+ * client->refresh_type must be set before entering this function.
+ */
+void
+do_refresh6(void *input)
+{
+ struct option_cache *oc;
+ struct sockaddr_in6 unicast, *dest_addr = &DHCPv6DestAddr;
+ struct data_string ds;
+ struct client_state *client;
+ struct dhc6_lease *lease;
+ struct timeval elapsed, tv;
+ int send_ret;
+
+ client = (struct client_state *)input;
+ memset(&ds, 0, sizeof(ds));
+
+ lease = client->active_lease;
+ if (lease == NULL) {
+ log_error("Cannot renew without an active binding.");
+ return;
+ }
+
+ /* Ensure we're emitting a valid message type. */
+ switch (client->refresh_type) {
+ case DHCPV6_RENEW:
+ case DHCPV6_REBIND:
+ break;
+
+ default:
+ log_fatal("Internal inconsistency (%d) at %s:%d.",
+ client->refresh_type, MDL);
+ }
+
+ /*
+ * Start_time starts at the first transmission.
+ */
+ if (client->txcount == 0) {
+ client->start_time.tv_sec = cur_tv.tv_sec;
+ client->start_time.tv_usec = cur_tv.tv_usec;
+ }
+
+ /* elapsed = cur - start */
+ elapsed.tv_sec = cur_tv.tv_sec - client->start_time.tv_sec;
+ elapsed.tv_usec = cur_tv.tv_usec - client->start_time.tv_usec;
+ if (elapsed.tv_usec < 0) {
+ elapsed.tv_sec -= 1;
+ elapsed.tv_usec += 1000000;
+ }
+ if (((client->MRC != 0) && (client->txcount > client->MRC)) ||
+ ((client->MRD != 0) && (elapsed.tv_sec >= client->MRD))) {
+ /* We're done. Move on to the next phase, if any. */
+ dhc6_check_times(client);
+ return;
+ }
+
+ /*
+ * Check whether the server has sent a unicast option; if so, we can
+ * use the address it specified for RENEWs.
+ */
+ oc = lookup_option(&dhcpv6_universe, lease->options, D6O_UNICAST);
+ if (oc && evaluate_option_cache(&ds, NULL, NULL, NULL,
+ lease->options, NULL, &global_scope,
+ oc, MDL)) {
+ if (ds.len < 16) {
+ log_error("Invalid unicast option length %d.", ds.len);
+ } else {
+ memset(&unicast, 0, sizeof(DHCPv6DestAddr));
+ unicast.sin6_family = AF_INET6;
+ unicast.sin6_port = remote_port;
+ memcpy(&unicast.sin6_addr, ds.data, 16);
+ if (client->refresh_type == DHCPV6_RENEW) {
+ dest_addr = &unicast;
+ }
+ }
+
+ data_string_forget(&ds, MDL);
+ }
+
+ /* Commence forming a renew packet. */
+ memset(&ds, 0, sizeof(ds));
+ if (!buffer_allocate(&ds.buffer, 4, MDL)) {
+ log_error("Unable to allocate memory for packet.");
+ return;
+ }
+ ds.data = ds.buffer->data;
+ ds.len = 4;
+
+ ds.buffer->data[0] = client->refresh_type;
+ memcpy(ds.buffer->data + 1, client->dhcpv6_transaction_id, 3);
+
+ /* Form an elapsed option. */
+ /* Maximum value is 65535 1/100s coded as 0xffff. */
+ if ((elapsed.tv_sec < 0) || (elapsed.tv_sec > 655) ||
+ ((elapsed.tv_sec == 655) && (elapsed.tv_usec > 350000))) {
+ client->elapsed = 0xffff;
+ } else {
+ client->elapsed = elapsed.tv_sec * 100;
+ client->elapsed += elapsed.tv_usec / 10000;
+ }
+
+ if (client->elapsed == 0)
+ log_debug("XMT: Forming %s, 0 ms elapsed.",
+ dhcpv6_type_names[client->refresh_type]);
+ else
+ log_debug("XMT: Forming %s, %u0 ms elapsed.",
+ dhcpv6_type_names[client->refresh_type],
+ (unsigned)client->elapsed);
+
+ client->elapsed = htons(client->elapsed);
+
+ make_client6_options(client, &client->sent_options, lease,
+ client->refresh_type);
+
+ /* Put in any options from the sent cache. */
+ dhcpv6_universe.encapsulate(&ds, NULL, NULL, client, NULL,
+ client->sent_options, &global_scope,
+ &dhcpv6_universe);
+
+ /* Append IA's */
+ if (wanted_ia_na &&
+ dhc6_add_ia_na(client, &ds, lease,
+ client->refresh_type) != ISC_R_SUCCESS) {
+ data_string_forget(&ds, MDL);
+ return;
+ }
+ if (wanted_ia_pd &&
+ dhc6_add_ia_pd(client, &ds, lease,
+ client->refresh_type) != ISC_R_SUCCESS) {
+ data_string_forget(&ds, MDL);
+ return;
+ }
+
+ log_info("XMT: %s on %s, interval %ld0ms.",
+ dhcpv6_type_names[client->refresh_type],
+ client->name ? client->name : client->interface->name,
+ (long int)client->RT);
+
+ send_ret = send_packet6(client->interface, ds.data, ds.len, dest_addr);
+
+ if (send_ret != ds.len) {
+ log_error("dhc6: send_packet6() sent %d of %d bytes",
+ send_ret, ds.len);
+ }
+
+ data_string_forget(&ds, MDL);
+
+ /* Wait RT */
+ tv.tv_sec = cur_tv.tv_sec + client->RT / 100;
+ tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000;
+ if (tv.tv_usec >= 1000000) {
+ tv.tv_sec += 1;
+ tv.tv_usec -= 1000000;
+ }
+ add_timeout(&tv, do_refresh6, client, NULL, NULL);
+
+ dhc6_retrans_advance(client);
+}
+
+/* start_rebind6() gets us all set up to go and rebind a lease. Note that
+ * client->next_MRD must be set before entering this function. In this case,
+ * MRD must be set to the maximum time any address in the packet will
+ * expire.
+ */
+void
+start_rebind6(void *input)
+{
+ struct client_state *client;
+
+ client = (struct client_state *)input;
+
+ log_info("PRC: Rebinding lease on %s.",
+ client->name ? client->name : client->interface->name);
+ client->state = S_REBINDING;
+
+ client->v6_handler = reply_handler;
+
+ /* Times per RFC3315 section 18.1.4. */
+ client->IRT = REB_TIMEOUT * 100;
+ client->MRT = REB_MAX_RT * 100;
+ client->MRC = 0;
+ /* MRD is special in rebind - it's determined by the timer
+ * state.
+ */
+ client->MRD = client->next_MRD - cur_time;
+
+ dhc6_retrans_init(client);
+
+ client->refresh_type = DHCPV6_REBIND;
+ do_refresh6(client);
+}
+
+/* do_depref() runs through a given lease's addresses, for each that has
+ * not yet been depreffed, shells out to the dhclient-script to inform it
+ * of the status change. The dhclient-script should then do...something...
+ * to encourage applications to move off the address and onto one of the
+ * remaining 'preferred' addresses.
+ */
+void
+do_depref(void *input)
+{
+ struct client_state *client;
+ struct dhc6_lease *lease;
+ struct dhc6_ia *ia;
+ struct dhc6_addr *addr;
+
+ client = (struct client_state *)input;
+
+ lease = client->active_lease;
+ if (lease == NULL)
+ return;
+
+ for (ia = lease->bindings ; ia != NULL ; ia = ia->next) {
+ for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
+ if (addr->flags & DHC6_ADDR_DEPREFFED)
+ continue;
+
+ if (addr->starts + addr->preferred_life <= cur_time) {
+ script_init(client, "DEPREF6", NULL);
+ dhc6_marshall_values("cur_", client, lease,
+ ia, addr);
+ script_write_requested6(client);
+ script_go(client);
+
+ addr->flags |= DHC6_ADDR_DEPREFFED;
+
+ if (ia->ia_type != D6O_IA_PD)
+ log_info("PRC: Address %s depreferred.",
+ piaddr(addr->address));
+ else
+ log_info("PRC: Prefix %s/%u depreferred.",
+ piaddr(addr->address),
+ (unsigned) addr->plen);
+
+#if defined (NSUPDATE)
+ /* Remove DDNS bindings at depref time. */
+ if ((ia->ia_type == D6O_IA_NA) &&
+ client->config->do_forward_update)
+ client_dns_remove(client,
+ &addr->address);
+#endif
+ }
+ }
+ }
+
+ dhc6_check_times(client);
+}
+
+/* do_expire() searches through all the addresses on a given lease, and
+ * expires/removes any addresses that are no longer valid.
+ */
+void
+do_expire(void *input)
+{
+ struct client_state *client;
+ struct dhc6_lease *lease;
+ struct dhc6_ia *ia;
+ struct dhc6_addr *addr;
+ int has_addrs = ISC_FALSE;
+
+ client = (struct client_state *)input;
+
+ lease = client->active_lease;
+ if (lease == NULL)
+ return;
+
+ for (ia = lease->bindings ; ia != NULL ; ia = ia->next) {
+ for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
+ if (addr->flags & DHC6_ADDR_EXPIRED)
+ continue;
+
+ if (addr->starts + addr->max_life <= cur_time) {
+ script_init(client, "EXPIRE6", NULL);
+ dhc6_marshall_values("old_", client, lease,
+ ia, addr);
+ script_write_requested6(client);
+ script_go(client);
+
+ addr->flags |= DHC6_ADDR_EXPIRED;
+
+ if (ia->ia_type != D6O_IA_PD)
+ log_info("PRC: Address %s expired.",
+ piaddr(addr->address));
+ else
+ log_info("PRC: Prefix %s/%u expired.",
+ piaddr(addr->address),
+ (unsigned) addr->plen);
+
+#if defined (NSUPDATE)
+ /* We remove DNS records at depref time, but
+ * it is possible that we might get here
+ * without depreffing.
+ */
+ if ((ia->ia_type == D6O_IA_NA) &&
+ client->config->do_forward_update &&
+ !(addr->flags & DHC6_ADDR_DEPREFFED))
+ client_dns_remove(client,
+ &addr->address);
+#endif
+
+ continue;
+ }
+
+ has_addrs = ISC_TRUE;
+ }
+ }
+
+ /* Clean up empty leases. */
+ if (has_addrs == ISC_FALSE) {
+ log_info("PRC: Bound lease is devoid of active addresses."
+ " Re-initializing.");
+
+ dhc6_lease_destroy(&lease, MDL);
+ client->active_lease = NULL;
+
+ start_init6(client);
+ return;
+ }
+
+ /* Schedule the next run through. */
+ dhc6_check_times(client);
+}
+
+/*
+ * Run client script to unconfigure interface.
+ * Called with reason STOP6 when dhclient -x is run, or with reason
+ * RELEASE6 when server has replied to a Release message.
+ * Stateless is a special case.
+ */
+void
+unconfigure6(struct client_state *client, const char *reason)
+{
+ struct dhc6_ia *ia;
+ struct dhc6_addr *addr;
+
+ if (stateless) {
+ script_init(client, reason, NULL);
+ if (client->active_lease != NULL)
+ script_write_params6(client, "old_",
+ client->active_lease->options);
+ script_write_requested6(client);
+ script_go(client);
+ return;
+ }
+
+ if (client->active_lease == NULL)
+ return;
+
+ for (ia = client->active_lease->bindings ; ia != NULL ; ia = ia->next) {
+ if (ia->ia_type == D6O_IA_TA)
+ continue;
+
+ for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
+ script_init(client, reason, NULL);
+ dhc6_marshall_values("old_", client,
+ client->active_lease, ia, addr);
+ script_write_requested6(client);
+ script_go(client);
+
+#if defined (NSUPDATE)
+ if ((ia->ia_type == D6O_IA_NA) &&
+ client->config->do_forward_update)
+ client_dns_remove(client, &addr->address);
+#endif
+ }
+ }
+}
+
+void
+refresh_info_request6(void *input)
+{
+ struct client_state *client;
+
+ client = (struct client_state *)input;
+ start_info_request6(client);
+}
+
+/* Timeout for Information-Request (using the IRT option).
+ */
+static void
+dhc6_check_irt(struct client_state *client)
+{
+ struct option **req;
+ struct option_cache *oc;
+ TIME expire = MAX_TIME;
+ struct timeval tv;
+ int i;
+ isc_boolean_t found = ISC_FALSE;
+
+ cancel_timeout(refresh_info_request6, client);
+
+ req = client->config->requested_options;
+ for (i = 0; req[i] != NULL; i++) {
+ if (req[i] == irt_option) {
+ found = ISC_TRUE;
+ break;
+ }
+ }
+ /* Simply return gives a endless loop waiting for nothing. */
+ if (!found)
+ exit(0);
+
+ oc = lookup_option(&dhcpv6_universe, client->active_lease->options,
+ D6O_INFORMATION_REFRESH_TIME);
+ if (oc != NULL) {
+ struct data_string irt;
+
+ memset(&irt, 0, sizeof(irt));
+ if (!evaluate_option_cache(&irt, NULL, NULL, client,
+ client->active_lease->options,
+ NULL, &global_scope, oc, MDL) ||
+ (irt.len < 4)) {
+ log_error("Can't evaluate IRT.");
+ } else {
+ expire = getULong(irt.data);
+ if (expire < IRT_MINIMUM)
+ expire = IRT_MINIMUM;
+ if (expire == 0xffffffff)
+ expire = MAX_TIME;
+ }
+ data_string_forget(&irt, MDL);
+ } else
+ expire = IRT_DEFAULT;
+
+ if (expire != MAX_TIME) {
+ log_debug("PRC: Refresh event scheduled in %u seconds.",
+ (unsigned) expire);
+ tv.tv_sec = cur_time + expire;
+ tv.tv_usec = 0;
+ add_timeout(&tv, refresh_info_request6, client, NULL, NULL);
+ }
+}
+
+/* We got a Reply. Give dhclient-script a tickle to inform it about
+ * the new values, and then lay in wait for the next event.
+ */
+static void
+start_informed(struct client_state *client)
+{
+ client->v6_handler = informed_handler;
+
+ log_debug("PRC: Done.");
+
+ client->state = S_BOUND;
+
+ script_init(client, "RENEW6", NULL);
+ if (client->old_lease != NULL)
+ script_write_params6(client, "old_",
+ client->old_lease->options);
+ script_write_params6(client, "new_", client->active_lease->options);
+ script_write_requested6(client);
+ script_go(client);
+
+ go_daemon();
+
+ if (client->old_lease != NULL) {
+ dhc6_lease_destroy(&client->old_lease, MDL);
+ client->old_lease = NULL;
+ }
+
+ /* Schedule events. */
+ dhc6_check_irt(client);
+}
+
+/* While informed, ignore packets.
+ */
+void
+informed_handler(struct packet *packet, struct client_state *client)
+{
+ log_debug("RCV: Input packets are ignored once bound.");
+}
+
+/* make_client6_options() fetches option caches relevant to the client's
+ * scope and places them into the sent_options cache. This cache is later
+ * used to populate DHCPv6 output packets with options.
+ */
+static void
+make_client6_options(struct client_state *client, struct option_state **op,
+ struct dhc6_lease *lease, u_int8_t message)
+{
+ struct option_cache *oc;
+ struct option **req;
+ struct buffer *buffer;
+ int buflen, i, oro_len;
+
+ if ((op == NULL) || (client == NULL))
+ return;
+
+ if (*op)
+ option_state_dereference(op, MDL);
+
+ /* Create a cache to carry options to transmission. */
+ option_state_allocate(op, MDL);
+
+ /* Create and store an 'elapsed time' option in the cache. */
+ oc = NULL;
+ if (option_cache_allocate(&oc, MDL)) {
+ const unsigned char *cdata;
+
+ cdata = (unsigned char *)&client->elapsed;
+
+ if (make_const_data(&oc->expression, cdata, 2, 0, 0, MDL)) {
+ option_reference(&oc->option, elapsed_option, MDL);
+ save_option(&dhcpv6_universe, *op, oc);
+ }
+
+ option_cache_dereference(&oc, MDL);
+ }
+
+ /* Bring in any configured options to send. */
+ if (client->config->on_transmission)
+ execute_statements_in_scope(NULL, NULL, NULL, client,
+ lease ? lease->options : NULL,
+ *op, &global_scope,
+ client->config->on_transmission,
+ NULL);
+
+ /* Rapid-commit is only for SOLICITs. */
+ if (message != DHCPV6_SOLICIT)
+ delete_option(&dhcpv6_universe, *op, D6O_RAPID_COMMIT);
+
+ /* See if the user configured a DUID in a relevant scope. If not,
+ * introduce our default manufactured id.
+ */
+ if ((oc = lookup_option(&dhcpv6_universe, *op,
+ D6O_CLIENTID)) == NULL) {
+ if (!option_cache(&oc, &default_duid, NULL, clientid_option,
+ MDL))
+ log_fatal("Failure assembling a DUID.");
+
+ save_option(&dhcpv6_universe, *op, oc);
+ option_cache_dereference(&oc, MDL);
+ }
+
+ /* In cases where we're responding to a single server, put the
+ * server's id in the response.
+ *
+ * Note that lease is NULL for SOLICIT or INFO request messages,
+ * and otherwise MUST be present.
+ */
+ if (lease == NULL) {
+ if ((message != DHCPV6_SOLICIT) &&
+ (message != DHCPV6_INFORMATION_REQUEST))
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ } else if ((message != DHCPV6_REBIND) &&
+ (message != DHCPV6_CONFIRM)) {
+ oc = lookup_option(&dhcpv6_universe, lease->options,
+ D6O_SERVERID);
+ if (oc != NULL)
+ save_option(&dhcpv6_universe, *op, oc);
+ }
+
+ /* 'send dhcp6.oro foo;' syntax we used in 4.0.0a1/a2 has been
+ * deprecated by adjustments to the 'request' syntax also used for
+ * DHCPv4.
+ */
+ if (lookup_option(&dhcpv6_universe, *op, D6O_ORO) != NULL)
+ log_error("'send dhcp6.oro' syntax is deprecated, please "
+ "use the 'request' syntax (\"man dhclient.conf\").");
+
+ /* Construct and store an ORO (Option Request Option). It is a
+ * fatal error to fail to send an ORO (of at least zero length).
+ *
+ * Discussion: RFC3315 appears to be inconsistent in its statements
+ * of whether or not the ORO is mandatory. In section 18.1.1
+ * ("Creation and Transmission of Request Messages"):
+ *
+ * The client MUST include an Option Request option (see section
+ * 22.7) to indicate the options the client is interested in
+ * receiving. The client MAY include options with data values as
+ * hints to the server about parameter values the client would like
+ * to have returned.
+ *
+ * This MUST is missing from the creation/transmission of other
+ * messages (such as Renew and Rebind), and the section 22.7 ("Option
+ * Request Option" format and definition):
+ *
+ * A client MAY include an Option Request option in a Solicit,
+ * Request, Renew, Rebind, Confirm or Information-request message to
+ * inform the server about options the client wants the server to
+ * send to the client. A server MAY include an Option Request
+ * option in a Reconfigure option to indicate which options the
+ * client should request from the server.
+ *
+ * seems to relax the requirement from MUST to MAY (and still other
+ * language in RFC3315 supports this).
+ *
+ * In lieu of a clarification of RFC3315, we will conform with the
+ * MUST. Instead of an absent ORO, we will if there are no options
+ * to request supply an empty ORO. Theoretically, an absent ORO is
+ * difficult to interpret (does the client want all options or no
+ * options?). A zero-length ORO is intuitively clear: requesting
+ * nothing.
+ */
+ buffer = NULL;
+ oro_len = 0;
+ buflen = 32;
+ if (!buffer_allocate(&buffer, buflen, MDL))
+ log_fatal("Out of memory constructing DHCPv6 ORO.");
+ req = client->config->requested_options;
+ if (req != NULL) {
+ for (i = 0 ; req[i] != NULL ; i++) {
+ if (buflen == oro_len) {
+ struct buffer *tmpbuf = NULL;
+
+ buflen += 32;
+
+ /* Shell game. */
+ buffer_reference(&tmpbuf, buffer, MDL);
+ buffer_dereference(&buffer, MDL);
+
+ if (!buffer_allocate(&buffer, buflen, MDL))
+ log_fatal("Out of memory resizing "
+ "DHCPv6 ORO buffer.");
+
+ memcpy(buffer->data, tmpbuf->data, oro_len);
+
+ buffer_dereference(&tmpbuf, MDL);
+ }
+
+ if (req[i]->universe == &dhcpv6_universe) {
+ /* Append the code to the ORO. */
+ putUShort(buffer->data + oro_len,
+ req[i]->code);
+ oro_len += 2;
+ }
+ }
+ }
+
+ oc = NULL;
+ if (make_const_option_cache(&oc, &buffer, NULL, oro_len,
+ oro_option, MDL)) {
+ save_option(&dhcpv6_universe, *op, oc);
+ } else {
+ log_fatal("Unable to create ORO option cache.");
+ }
+
+ /*
+ * Note: make_const_option_cache() consumes the buffer, we do not
+ * need to dereference it (XXX).
+ */
+ option_cache_dereference(&oc, MDL);
+}
+
+/* A clone of the DHCPv4 script_write_params() minus the DHCPv4-specific
+ * filename, server-name, etc specifics.
+ *
+ * Simply, store all values present in all universes of the option state
+ * (probably derived from a DHCPv6 packet) into environment variables
+ * named after the option names (and universe names) but with the 'prefix'
+ * prepended.
+ *
+ * Later, dhclient-script may compare for example "new_time_servers" and
+ * "old_time_servers" for differences, and only upon detecting a change
+ * bother to rewrite ntp.conf and restart it. Or something along those
+ * generic lines.
+ */
+static void
+script_write_params6(struct client_state *client, const char *prefix,
+ struct option_state *options)
+{
+ struct envadd_state es;
+ int i;
+
+ if (options == NULL)
+ return;
+
+ es.client = client;
+ es.prefix = prefix;
+
+ for (i = 0 ; i < options->universe_count ; i++) {
+ option_space_foreach(NULL, NULL, client, NULL, options,
+ &global_scope, universes[i], &es,
+ client_option_envadd);
+ }
+}
+
+/*
+ * A clone of the DHCPv4 routine.
+ * Write out the environment variables for the objects that the
+ * client requested. If the object was requested the variable will be:
+ * requested_<option_name>=1
+ * If it wasn't requested there won't be a variable.
+ */
+static void script_write_requested6(client)
+ struct client_state *client;
+{
+ int i;
+ struct option **req;
+ char name[256];
+ req = client->config->requested_options;
+
+ if (req == NULL)
+ return;
+
+ for (i = 0 ; req[i] != NULL ; i++) {
+ if ((req[i]->universe == &dhcpv6_universe) &&
+ dhcp_option_ev_name (name, sizeof(name), req[i])) {
+ client_envadd(client, "requested_", name, "%d", 1);
+ }
+ }
+}
+
+/*
+ * Check if there is something not fully defined in the active lease.
+ */
+static isc_boolean_t
+active_prefix(struct client_state *client)
+{
+ struct dhc6_lease *lease;
+ struct dhc6_ia *ia;
+ struct dhc6_addr *pref;
+ char zeros[16];
+
+ lease = client->active_lease;
+ if (lease == NULL)
+ return ISC_FALSE;
+ memset(zeros, 0, 16);
+ for (ia = lease->bindings; ia != NULL; ia = ia->next) {
+ if (ia->ia_type != D6O_IA_PD)
+ continue;
+ for (pref = ia->addrs; pref != NULL; pref = pref->next) {
+ if (pref->plen == 0)
+ return ISC_FALSE;
+ if (pref->address.len != 16)
+ return ISC_FALSE;
+ if (memcmp(pref->address.iabuf, zeros, 16) == 0)
+ return ISC_FALSE;
+ }
+ }
+ return ISC_TRUE;
+}
+#endif /* DHCPv6 */
diff --git a/client/dhclient-script.8 b/client/dhclient-script.8
new file mode 100644
index 0000000..5df832b
--- /dev/null
+++ b/client/dhclient-script.8
@@ -0,0 +1,233 @@
+.\" dhclient-script.8
+.\"
+.\" Copyright (c) 2012,2014 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 2009-2010 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 2004-2005 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 1996-2003 by Internet Software Consortium
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+.\" OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.\" Internet Systems Consortium, Inc.
+.\" 950 Charter Street
+.\" Redwood City, CA 94063
+.\" <info@isc.org>
+.\" https://www.isc.org/
+.\"
+.\" Support and other services are available for ISC products - see
+.\" https://www.isc.org for more information or to learn more about ISC.
+.\"
+.\" $Id: dhclient-script.8,v 1.12.24.2 2010/07/06 19:03:11 sar Exp $
+.\"
+.TH dhclient-script 8
+.SH NAME
+dhclient-script - DHCP client network configuration script
+.SH DESCRIPTION
+The DHCP client network configuration script is invoked from time to
+time by \fBdhclient(8)\fR. This script is used by the dhcp client to
+set each interface's initial configuration prior to requesting an
+address, to test the address once it has been offered, and to set the
+interface's final configuration once a lease has been acquired. If no
+lease is acquired, the script is used to test predefined leases, if
+any, and also called once if no valid lease can be identified.
+.PP
+This script is not meant to be customized by the end user. If local
+customizations are needed, they should be possible using the enter and
+exit hooks provided (see HOOKS for details). These hooks will allow the
+user to override the default behaviour of the client in creating a
+.B /etc/resolv.conf
+file.
+.PP
+No standard client script exists for some operating systems, even though
+the actual client may work, so a pioneering user may well need to create
+a new script or modify an existing one. In general, customizations specific
+to a particular computer should be done in the
+.B ETCDIR/dhclient.conf
+file. If you find that you can't make such a customization without
+customizing
+.B ETCDIR/dhclient.conf
+or using the enter and exit hooks, please submit a bug report.
+.SH HOOKS
+When it starts, the client script first defines a shell function,
+.B make_resolv_conf ,
+which is later used to create the
+.B /etc/resolv.conf
+file. To override the default behaviour, redefine this function in
+the enter hook script.
+.PP
+On after defining the make_resolv_conf function, the client script checks
+for the presence of an executable
+.B ETCDIR/dhclient-enter-hooks
+script, and if present, it invokes the script inline, using the Bourne
+shell \'.\' command. The entire environment documented under OPERATION
+is available to this script, which may modify the environment if needed
+to change the behaviour of the script. If an error occurs during the
+execution of the script, it can set the exit_status variable to a nonzero
+value, and
+.B CLIENTBINDIR/dhclient-script
+will exit with that error code immediately after the client script exits.
+.PP
+After all processing has completed,
+.B CLIENTBINDIR/dhclient-script
+checks for the presence of an executable
+.B ETCDIR/dhclient-exit-hooks
+script, which if present is invoked using the \'.\' command. The exit
+status of dhclient-script will be passed to dhclient-exit-hooks in the
+exit_status shell variable, and will always be zero if the script
+succeeded at the task for which it was invoked. The rest of the
+environment as described previously for dhclient-enter-hooks is also
+present. The
+.B ETCDIR/dhclient-exit-hooks
+script can modify the valid of exit_status to change the exit status
+of dhclient-script.
+.SH OPERATION
+When dhclient needs to invoke the client configuration script, it
+defines a set of variables in the environment, and then invokes
+.B CLIENTBINDIR/dhclient-script.
+In all cases, $reason is set to the name of the reason why the script
+has been invoked. The following reasons are currently defined:
+MEDIUM, PREINIT, BOUND, RENEW, REBIND, REBOOT, EXPIRE, FAIL, STOP, RELEASE,
+NBI and TIMEOUT.
+.PP
+.SH MEDIUM
+The DHCP client is requesting that an interface's media type
+be set. The interface name is passed in $interface, and the media
+type is passed in $medium.
+.SH PREINIT
+The DHCP client is requesting that an interface be configured as
+required in order to send packets prior to receiving an actual
+address. For clients which use the BSD socket library, this means
+configuring the interface with an IP address of 0.0.0.0 and a
+broadcast address of 255.255.255.255. For other clients, it may be
+possible to simply configure the interface up without actually giving
+it an IP address at all. The interface name is passed in $interface,
+and the media type in $medium.
+.PP
+If an IP alias has been declared in dhclient.conf, its address will be
+passed in $alias_ip_address, and that ip alias should be deleted from
+the interface, along with any routes to it.
+.SH BOUND
+The DHCP client has done an initial binding to a new address. The
+new ip address is passed in $new_ip_address, and the interface name is
+passed in $interface. The media type is passed in $medium. Any
+options acquired from the server are passed using the option name
+described in \fBdhcp-options\fR, except that dashes (\'-\') are replaced
+by underscores (\'_\') in order to make valid shell variables, and the
+variable names start with new_. So for example, the new subnet mask
+would be passed in $new_subnet_mask. The options that the client
+explicitly requested via a PRL or ORO option are passed with the same
+option name as above but prepended with requested_ and with a value of 1,
+or example requested_subnet_mask=1. No such variable is defined for
+options not requested by the client or options that don't require a
+request option, such as the ip address (*_ip_address) or expiration
+time (*_expiry).
+.PP
+Before actually configuring the address, dhclient-script should
+somehow ARP for it and exit with a nonzero status if it receives a
+reply. In this case, the client will send a DHCPDECLINE message to
+the server and acquire a different address. This may also be done in
+the RENEW, REBIND, or REBOOT states, but is not required, and indeed
+may not be desirable.
+.PP
+When a binding has been completed, a lot of network parameters are
+likely to need to be set up. A new /etc/resolv.conf needs to be
+created, using the values of $new_domain_name and
+$new_domain_name_servers (which may list more than one server,
+separated by spaces). A default route should be set using
+$new_routers, and static routes may need to be set up using
+$new_static_routes.
+.PP
+If an IP alias has been declared, it must be set up here. The alias
+IP address will be written as $alias_ip_address, and other DHCP
+options that are set for the alias (e.g., subnet mask) will be passed
+in variables named as described previously except starting with
+$alias_ instead of $new_. Care should be taken that the alias IP
+address not be used if it is identical to the bound IP address
+($new_ip_address), since the other alias parameters may be incorrect
+in this case.
+.SH RENEW
+When a binding has been renewed, the script is called as in BOUND,
+except that in addition to all the variables starting with $new_, and
+$requested_ there is another set of variables starting with $old_.
+Persistent settings that may have changed need to be deleted - for
+example, if a local route to the bound address is being configured,
+the old local route should be deleted. If the default route has changed,
+the old default route should be deleted. If the static routes have changed,
+the old ones should be deleted. Otherwise, processing can be done as with
+BOUND.
+.SH REBIND
+The DHCP client has rebound to a new DHCP server. This can be handled
+as with RENEW, except that if the IP address has changed, the ARP
+table should be cleared.
+.SH REBOOT
+The DHCP client has successfully reacquired its old address after a
+reboot. This can be processed as with BOUND.
+.SH EXPIRE
+The DHCP client has failed to renew its lease or acquire a new one,
+and the lease has expired. The IP address must be relinquished, and
+all related parameters should be deleted, as in RENEW and REBIND.
+.SH FAIL
+The DHCP client has been unable to contact any DHCP servers, and any
+leases that have been tested have not proved to be valid. The
+parameters from the last lease tested should be deconfigured. This
+can be handled in the same way as EXPIRE.
+.SH STOP
+The dhclient has been informed to shut down gracefully, the
+dhclient-script should unconfigure or shutdown the interface as
+appropriate.
+.SH RELEASE
+The dhclient has been executed using the -r flag, indicating that the
+administrator wishes it to release its lease(s). dhclient-script should
+unconfigure or shutdown the interface.
+.SH NBI
+No-Broadcast-Interfaces...dhclient was unable to find any interfaces
+upon which it believed it should commence DHCP. What dhclient-script
+should do in this situation is entirely up to the implementor.
+.SH TIMEOUT
+The DHCP client has been unable to contact any DHCP servers.
+However, an old lease has been identified, and its parameters have
+been passed in as with BOUND. The client configuration script should
+test these parameters and, if it has reason to believe they are valid,
+should exit with a value of zero. If not, it should exit with a
+nonzero value.
+.PP
+The usual way to test a lease is to set up the network as with REBIND
+(since this may be called to test more than one lease) and then ping
+the first router defined in $routers. If a response is received, the
+lease must be valid for the network to which the interface is
+currently connected. It would be more complete to try to ping all of
+the routers listed in $new_routers, as well as those listed in
+$new_static_routes, but current scripts do not do this.
+.SH FILES
+Each operating system should generally have its own script file,
+although the script files for similar operating systems may be similar
+or even identical. The script files included in Internet
+Systems Consortium DHCP distribution appear in the distribution tree
+under client/scripts, and bear the names of the operating systems on
+which they are intended to work.
+.SH BUGS
+If more than one interface is being used, there's no obvious way to
+avoid clashes between server-supplied configuration parameters - for
+example, the stock dhclient-script rewrites /etc/resolv.conf. If
+more than one interface is being configured, /etc/resolv.conf will be
+repeatedly initialized to the values provided by one server, and then
+the other. Assuming the information provided by both servers is
+valid, this shouldn't cause any real problems, but it could be
+confusing.
+.SH SEE ALSO
+dhclient(8), dhcpd(8), dhcrelay(8), dhclient.conf(5) and
+dhclient.leases(5).
+.SH AUTHOR
+.B dhclient-script(8)
+To learn more about Internet Systems Consortium,
+see
+.B https://www.isc.org.
diff --git a/client/dhclient.8 b/client/dhclient.8
new file mode 100644
index 0000000..0aa1119
--- /dev/null
+++ b/client/dhclient.8
@@ -0,0 +1,479 @@
+.\" $Id: dhclient.8,v 1.32.24.4 2011/04/15 22:12:50 sar Exp $
+.\"
+.\" Copyright (c) 2004,2007-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/
+.\"
+.\" Support and other services are available for ISC products - see
+.\" https://www.isc.org for more information or to learn more about ISC.
+.\"
+.TH dhclient 8
+.SH NAME
+dhclient - Dynamic Host Configuration Protocol Client
+.SH SYNOPSIS
+.B dhclient
+[
+.B -4
+|
+.B -6
+]
+[
+.B -S
+]
+[
+.B -N
+[
+.B -N...
+]
+]
+[
+.B -T
+[
+.B -T...
+]
+]
+[
+.B -P
+[
+.B -P...
+]
+]
+[
+.B -D
+.I LL|LLT
+]
+[
+.B -p
+.I port-number
+]
+[
+.B -d
+]
+[
+.B -e
+.I VAR=value
+]
+[
+.B -q
+]
+[
+.B -1
+]
+[
+.B -r
+|
+.B -x
+]
+[
+.B -lf
+.I lease-file
+]
+[
+.B -pf
+.I pid-file
+]
+[
+.B --no-pid
+]
+[
+.B -cf
+.I config-file
+]
+[
+.B -sf
+.I script-file
+]
+[
+.B -s
+.I server-addr
+]
+[
+.B -g
+.I relay
+]
+[
+.B -n
+]
+[
+.B -nw
+]
+[
+.B -w
+]
+[
+.B -v
+]
+[
+.B --version
+]
+[
+.I if0
+[
+.I ...ifN
+]
+]
+.SH DESCRIPTION
+The Internet Systems Consortium DHCP Client, \fBdhclient\fR, provides a
+means for configuring one or more network interfaces using the Dynamic
+Host Configuration Protocol, BOOTP protocol, or if these protocols
+fail, by statically assigning an address.
+.SH OPERATION
+.PP
+The DHCP protocol allows a host to contact a central server which
+maintains a list of IP addresses which may be assigned on one or more
+subnets. A DHCP client may request an address from this pool, and
+then use it on a temporary basis for communication on network. The
+DHCP protocol also provides a mechanism whereby a client can learn
+important details about the network to which it is attached, such as
+the location of a default router, the location of a name server, and
+so on.
+.PP
+There are two versions of the DHCP protocol DHCPv4 and DHCPv6. At
+startup the client may be started for one or the other via the
+.B -4
+or
+.B -6
+options.
+.PP
+On startup, \fBdhclient\fR reads the dhclient.conf
+for configuration instructions. It then gets a list of all the
+network interfaces that are configured in the current system. For
+each interface, it attempts to configure the interface using the DHCP
+protocol.
+.PP
+In order to keep track of leases across system reboots and server
+restarts, \fBdhclient\fR keeps a list of leases it has been assigned in the
+dhclient.leases file. On startup, after reading the dhclient.conf
+file, \fBdhclient\fR reads the dhclient.leases file to refresh its memory
+about what leases it has been assigned.
+.PP
+When a new lease is acquired, it is appended to the end of the
+dhclient.leases file. In order to prevent the file from becoming
+arbitrarily large, from time to time \fBdhclient\fR creates a new
+dhclient.leases file from its in-core lease database. The old version
+of the dhclient.leases file is retained under the name
+.IR dhclient.leases~
+until the next time \fBdhclient\fR rewrites the database.
+.PP
+Old leases are kept around in case the DHCP server is unavailable when
+\fBdhclient\fR is first invoked (generally during the initial system boot
+process). In that event, old leases from the dhclient.leases file
+which have not yet expired are tested, and if they are determined to
+be valid, they are used until either they expire or the DHCP server
+becomes available.
+.PP
+A mobile host which may sometimes need to access a network on which no
+DHCP server exists may be preloaded with a lease for a fixed
+address on that network. When all attempts to contact a DHCP server
+have failed, \fBdhclient\fR will try to validate the static lease, and if it
+succeeds, will use that lease until it is restarted.
+.PP
+A mobile host may also travel to some networks on which DHCP is not
+available but BOOTP is. In that case, it may be advantageous to
+arrange with the network administrator for an entry on the BOOTP
+database, so that the host can boot quickly on that network rather
+than cycling through the list of old leases.
+.SH COMMAND LINE
+.PP
+The names of the network interfaces that \fBdhclient\fR should attempt to
+configure may be specified on the command line. If no interface names
+are specified on the command line \fBdhclient\fR will normally identify all
+network interfaces, eliminating non-broadcast interfaces if
+possible, and attempt to configure each interface.
+.PP
+It is also possible to specify interfaces by name in the dhclient.conf
+file. If interfaces are specified in this way, then the client will
+only configure interfaces that are either specified in the
+configuration file or on the command line, and will ignore all other
+interfaces.
+.PP
+The client normally prints no output during its startup sequence. It
+can be made to emit verbose messages displaying the startup sequence events
+until it has acquired an address by supplying the
+.B -v
+command line argument. In either case, the client logs messages using
+the
+.B syslog(3)
+facility.
+.SH OPTIONS
+.TP
+.BI \-4
+Use the DHCPv4 protocol to obtain an IPv4 address and configuration
+parameters. This is the default and cannot be combined with
+\fB\-6\fR.
+.TP
+.BI \-6
+Use the DHCPv6 protocol to obtain whatever IPv6 addresses are available
+along with configuration parameters. It cannot be combined with
+\fB\-4\fR. The \fB\-S -T -P -N\fR and
+\fB\-D\fR arguments provide more control over aspects of the DHCPv6
+processing. Note: it is not recommended to mix queries of different
+types together or even to share the lease file between them.
+.TP
+.BI \-1
+Try to get a lease once. On failure exit with code 2. In DHCPv6 this
+sets the maximum duration of the initial exchange to
+.I timeout
+(from dhclient.conf with a default of sixty seconds).
+.TP
+.BI \-d
+.\" This is not intuitive.
+Force
+.B dhclient
+to run as a foreground process. Normally the DHCP client will run
+in the foreground until is has configured an interface at which time
+it will revert to running in the background. This option is useful
+when running the client under a debugger, or when running it out of
+inittab on System V systems. This implies \fB-v\fR.
+.TP
+.BI \-nw
+Become a daemon immediately (nowait) rather than waiting until an
+IP address has been acquired.
+.TP
+.BI \-q
+Be quiet at startup, this is the default.
+.TP
+.BI \-v
+Enable verbose log messages.
+.\" This prints the version, copyright and URL.
+.TP
+.BI \-w
+Continue running even if no broadcast interfaces were found. Normally
+DHCP client will exit if it isn't able to identify any network interfaces
+to configure. On laptop computers and other computers with
+hot-swappable I/O buses, it is possible that a broadcast interface may
+be added after system startup. This flag can be used to cause the client
+not to exit when it doesn't find any such interfaces. The
+.B omshell(1)
+program can then be used to notify the client when a network interface
+has been added or removed, so that the client can attempt to configure an IP
+address on that interface.
+.TP
+.BI \-n
+Do not configure any interfaces. This is most likely to be useful in
+combination with the
+.B -w
+flag.
+.TP
+.BI \-e \ VAR=value
+Define additional environment variables for the environment where
+.B dhclient-script
+executes. You may specify multiple
+.B \-e
+options on the command line.
+.TP
+.BI \-r
+Release the current lease and stop the running DHCP client as previously
+recorded in the PID file. When shutdown via this method
+.B dhclient-script
+will be executed with the specific reason for calling the script set.
+The client normally doesn't release the current lease as this is not
+required by the DHCP protocol but some cable ISPs require their clients
+to notify the server if they wish to release an assigned IP address.
+.\" TODO what dhclient-script argument?
+.\" When released,
+.TP
+.BI \-x
+Stop the running DHCP client without releasing the current lease.
+Kills existing \fBdhclient\fR process as previously recorded in the
+PID file. When shutdown via this method
+.B dhclient-script
+will be executed with the specific reason for calling the script set.
+.TP
+.BI \-p \ port-number
+The UDP port number on which the DHCP client should listen and transmit.
+If unspecified,
+.B dhclient
+uses the default port of 68. This is mostly useful for debugging purposes.
+If a different port is specified on which the client should listen and
+transmit, the client will also use a different destination port -
+one less than the specified port.
+.TP
+.BI \-s \ server-addr
+Specify the server IP address or fully qualified domain name to use as
+a destination for DHCP protocol messages before
+.B dhclient
+has acquired an IP address. Normally,
+.B dhclient
+transmits these messages to 255.255.255.255 (the IP limited broadcast
+address). Overriding this is mostly useful for debugging purposes. This
+feature is not supported in DHCPv6 (\fB-6\fR) mode.
+.TP
+.BI \-g \ relay
+.\" mockup relay
+Set the giaddr field of all packets to the \fIrelay\fR IP address
+simulating a relay agent. This is for testing pruposes only and
+should not be expected to work in any consistent or useful way.
+.TP
+.BI \--version
+Print version number and exit.
+.PP
+.I Options available for DHCPv6 mode:
+.TP
+.BI \-S
+.\" TODO: mention DUID?
+Use Information-request to get only stateless configuration parameters
+(i.e., without address). This implies \fB\-6\fR. It also doesn't
+rewrite the lease database.
+.\" TODO: May not be used with -N -P or -T. ??
+.TP
+.BI \-T
+.\" TODO wanted_ia_ta++
+Ask for IPv6 temporary addresses, one set per \fB\-T\fR flag. This
+implies \fB\-6\fR and also disables the normal address query.
+See \fB\-N\fR to restore it.
+.TP
+.BI \-P
+Enable IPv6 prefix delegation. This implies \fB\-6\fR and also
+disables the normal address query. See \fB\-N\fR to restore it.
+Note only one requested interface is allowed.
+.TP
+.BI \-D \ LL\ or\ LLT
+Override the default when selecting the type of DUID to use. By default,
+DHCPv6 \fBdhclient\fR creates an identifier based on the link-layer address
+(DUID-LL) if it is running in stateless mode (with \fB\-S\fR, not
+requesting an address), or it creates an identifier based on the
+link-layer address plus a timestamp (DUID-LLT) if it is running in
+stateful mode (without \fB\-S\fR, requesting an address). \fB\-D\fR
+overrides this default, with a value of either \fILL\fR or \fILLT\fR.
+.TP
+.BI \-N
+.\" TODO: is this for telling an already running dhclient?
+Restore normal address query for IPv6. This implies \fB-6\fR.
+It is used to restore normal operation after using \fB-T\fR or \fB-P\fR.
+.PP
+.I Modifying default file locations:
+The following options can be used to modify the locations a client uses
+for its files. They can be particularly useful if, for example,
+.B DBDIR
+or
+.B RUNDIR
+have not been mounted when the DHCP client is started.
+.TP
+.BI \-cf \ config-file
+Path to the client configuration file. If unspecified, the default
+.B ETCDIR/dhclient.conf
+is used. See \fBdhclient.conf(5)\fR for a description of this file.
+.TP
+.BI \-lf \ lease-file
+Path to the lease database file. If unspecified, the default
+.B DBDIR/dhclient.leases
+is used. See \fBdhclient.leases(5)\fR for a description of this file.
+.TP
+.BI \-pf \ pid-file
+Path to the process ID file. If unspecified, the default
+.B RUNDIR/dhclient.pid
+is used.
+.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 attempt to kill any existing client processes
+even if invoked with \fB-r\fR or \fB-x\fR.
+.TP
+.BI \-sf \ script-file
+Path to the network configuration script invoked by
+.B dhclient
+when it gets a lease. If unspecified, the default
+.B CLIENTBINDIR/dhclient-script
+is used. See \fBdhclient-script(8)\fR for a description of this file.
+
+
+.PP
+.SH CONFIGURATION
+The syntax of the \fBdhclient.conf(5)\fR file is discussed separately.
+.SH OMAPI
+The DHCP client provides some ability to control it while it is
+running, without stopping it. This capability is provided using OMAPI,
+an API for manipulating remote objects. OMAPI clients connect to the
+client using TCP/IP, authenticate, and can then examine the client'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. Most things you'd want to do with the client can
+be done directly using the \fBomshell(1)\fR command, rather than
+having to write a special program.
+.SH THE CONTROL OBJECT
+The control object allows you to shut the client down, releasing all
+leases that it holds and deleting any DNS records it may have added.
+It also allows you to pause the client - this unconfigures any
+interfaces the client is using. You can then restart it, which
+causes it to reconfigure those interfaces. You would normally pause
+the client prior to going into hibernation or sleep on a laptop
+computer. You would then resume it after the power comes back.
+This allows PC cards to be shut down while the computer is hibernating
+or sleeping, and then reinitialized to their previous state once the
+computer comes out of hibernation or sleep.
+.PP
+The control object has one attribute - the state attribute. To shut
+the client down, set its state attribute to 2. It will automatically
+do a DHCPRELEASE. To pause it, set its state attribute to 3. To
+resume it, set its state attribute to 4.
+.PP
+.SH ENVIRONMENT VARIABLES
+.PP
+The following environment variables may be defined
+to override the builtin defaults for file locations.
+Note that use of the related command-line options
+will ignore the corresponding environment variable settings.
+.TP
+.B PATH_DHCLIENT_CONF
+The dhclient.conf configuration file.
+.TP
+.B PATH_DHCLIENT_DB
+The dhclient.leases database.
+.TP
+.B PATH_DHCLIENT_PID
+The dhclient PID file.
+.TP
+.B PATH_DHCLIENT_SCRIPT
+The dhclient-script file.
+.PP
+.SH FILES
+.B CLIENTBINDIR/dhclient-script,
+.B ETCDIR/dhclient.conf, DBDIR/dhclient.leases, RUNDIR/dhclient.pid,
+.B DBDIR/dhclient.leases~.
+.SH SEE ALSO
+dhcpd(8), dhcrelay(8), dhclient-script(8), dhclient.conf(5),
+dhclient.leases(5), dhcp-eval(5).
+.SH AUTHOR
+.B dhclient(8)
+To learn more about Internet Systems Consortium,
+see
+.B https://www.isc.org
+.PP
+This client was substantially modified and enhanced by Elliot Poger
+for use on Linux while he was working on the MosquitoNet project at
+Stanford.
+.PP
+The current version owes much to Elliot's Linux enhancements, but
+was substantially reorganized and partially rewritten by Ted Lemon
+so as to use the same networking framework that the Internet Systems
+Consortium DHCP server uses. Much system-specific configuration code
+was moved into a shell script so that as support for more operating
+systems is added, it will not be necessary to port and maintain
+system-specific configuration code to these operating systems - instead,
+the shell script can invoke the native tools to accomplish the same
+purpose.
+.PP
diff --git a/client/dhclient.c b/client/dhclient.c
new file mode 100644
index 0000000..0625296
--- /dev/null
+++ b/client/dhclient.c
@@ -0,0 +1,4401 @@
+/* dhclient.c
+
+ DHCP Client. */
+
+/*
+ * Copyright (c) 2004-2015 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ * This code is based on the original client state machine that was
+ * written by Elliot Poger. The code has been extensively hacked on
+ * by Ted Lemon since then, so any mistakes you find are probably his
+ * fault and not Elliot's.
+ */
+
+#include "dhcpd.h"
+#include <syslog.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <limits.h>
+#include <dns/result.h>
+
+TIME default_lease_time = 43200; /* 12 hours... */
+TIME max_lease_time = 86400; /* 24 hours... */
+
+const char *path_dhclient_conf = _PATH_DHCLIENT_CONF;
+const char *path_dhclient_db = NULL;
+const char *path_dhclient_pid = NULL;
+static char path_dhclient_script_array[] = _PATH_DHCLIENT_SCRIPT;
+char *path_dhclient_script = path_dhclient_script_array;
+
+/* False (default) => we write and use a pid file */
+isc_boolean_t no_pid_file = ISC_FALSE;
+
+int dhcp_max_agent_option_packet_length = 0;
+
+int interfaces_requested = 0;
+
+struct iaddr iaddr_broadcast = { 4, { 255, 255, 255, 255 } };
+struct iaddr iaddr_any = { 4, { 0, 0, 0, 0 } };
+struct in_addr inaddr_any;
+struct sockaddr_in sockaddr_broadcast;
+struct in_addr giaddr;
+struct data_string default_duid;
+int duid_type = 0;
+
+/* ASSERT_STATE() does nothing now; it used to be
+ assert (state_is == state_shouldbe). */
+#define ASSERT_STATE(state_is, state_shouldbe) {}
+
+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 Client";
+static const char url [] =
+"For info, please visit https://www.isc.org/software/dhcp/";
+
+u_int16_t local_port = 0;
+u_int16_t remote_port = 0;
+int no_daemon = 0;
+struct string_list *client_env = NULL;
+int client_env_count = 0;
+int onetry = 0;
+int quiet = 1;
+int nowait = 0;
+int stateless = 0;
+int wanted_ia_na = -1; /* the absolute value is the real one. */
+int wanted_ia_ta = 0;
+int wanted_ia_pd = 0;
+char *mockup_relay = NULL;
+
+void run_stateless(int exit_mode);
+
+static void usage(void);
+
+static isc_result_t write_duid(struct data_string *duid);
+static void add_reject(struct packet *packet);
+
+static int check_domain_name(const char *ptr, size_t len, int dots);
+static int check_domain_name_list(const char *ptr, size_t len, int dots);
+static int check_option_values(struct universe *universe, unsigned int opt,
+ const char *ptr, size_t len);
+
+int
+main(int argc, char **argv) {
+ int fd;
+ int i;
+ struct interface_info *ip;
+ struct client_state *client;
+ unsigned seed;
+ char *server = NULL;
+ isc_result_t status;
+ int exit_mode = 0;
+ int release_mode = 0;
+ struct timeval tv;
+ omapi_object_t *listener;
+ isc_result_t result;
+ int persist = 0;
+ int no_dhclient_conf = 0;
+ int no_dhclient_db = 0;
+ int no_dhclient_pid = 0;
+ int no_dhclient_script = 0;
+#ifdef DHCPv6
+ int local_family_set = 0;
+#endif /* DHCPv6 */
+ char *s;
+
+ /* Initialize client globals. */
+ memset(&default_duid, 0, sizeof(default_duid));
+
+ /* 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);
+
+ openlog("dhclient", DHCP_LOG_OPTIONS, LOG_DAEMON);
+
+#if !(defined(DEBUG) || defined(__CYGWIN32__))
+ setlogmask(LOG_UPTO(LOG_INFO));
+#endif
+
+ /* 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 OMAPI. */
+ status = omapi_init();
+ if (status != ISC_R_SUCCESS)
+ log_fatal("Can't initialize OMAPI: %s",
+ isc_result_totext(status));
+
+ /* Set up the OMAPI wrappers for various server database internal
+ objects. */
+ dhcp_common_objects_setup();
+
+ dhcp_interface_discovery_hook = dhclient_interface_discovery_hook;
+ dhcp_interface_shutdown_hook = dhclient_interface_shutdown_hook;
+ dhcp_interface_startup_hook = dhclient_interface_startup_hook;
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-r")) {
+ release_mode = 1;
+ no_daemon = 1;
+#ifdef DHCPv6
+ } else if (!strcmp(argv[i], "-4")) {
+ if (local_family_set && local_family != AF_INET)
+ log_fatal("Client can only do v4 or v6, not "
+ "both.");
+ local_family_set = 1;
+ local_family = AF_INET;
+ } else if (!strcmp(argv[i], "-6")) {
+ if (local_family_set && local_family != AF_INET6)
+ log_fatal("Client can only do v4 or v6, not "
+ "both.");
+ local_family_set = 1;
+ local_family = AF_INET6;
+#endif /* DHCPv6 */
+ } else if (!strcmp(argv[i], "-x")) { /* eXit, no release */
+ release_mode = 0;
+ no_daemon = 0;
+ exit_mode = 1;
+ } else 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], "-d")) {
+ no_daemon = 1;
+ quiet = 0;
+ } else if (!strcmp(argv[i], "-pf")) {
+ if (++i == argc)
+ usage();
+ path_dhclient_pid = argv[i];
+ no_dhclient_pid = 1;
+ } else if (!strcmp(argv[i], "--no-pid")) {
+ no_pid_file = ISC_TRUE;
+ } else if (!strcmp(argv[i], "-cf")) {
+ if (++i == argc)
+ usage();
+ path_dhclient_conf = argv[i];
+ no_dhclient_conf = 1;
+ } else if (!strcmp(argv[i], "-lf")) {
+ if (++i == argc)
+ usage();
+ path_dhclient_db = argv[i];
+ no_dhclient_db = 1;
+ } else if (!strcmp(argv[i], "-sf")) {
+ if (++i == argc)
+ usage();
+ path_dhclient_script = argv[i];
+ no_dhclient_script = 1;
+ } else if (!strcmp(argv[i], "-1")) {
+ onetry = 1;
+ } else if (!strcmp(argv[i], "-q")) {
+ quiet = 1;
+ } else if (!strcmp(argv[i], "-s")) {
+ if (++i == argc)
+ usage();
+ server = argv[i];
+ } else if (!strcmp(argv[i], "-g")) {
+ if (++i == argc)
+ usage();
+ mockup_relay = argv[i];
+ } else if (!strcmp(argv[i], "-nw")) {
+ nowait = 1;
+ } else if (!strcmp(argv[i], "-n")) {
+ /* do not start up any interfaces */
+ interfaces_requested = -1;
+ } else if (!strcmp(argv[i], "-w")) {
+ /* do not exit if there are no broadcast interfaces. */
+ persist = 1;
+ } else if (!strcmp(argv[i], "-e")) {
+ struct string_list *tmp;
+ if (++i == argc)
+ usage();
+ tmp = dmalloc(strlen(argv[i]) + sizeof *tmp, MDL);
+ if (!tmp)
+ log_fatal("No memory for %s", argv[i]);
+ strcpy(tmp->string, argv[i]);
+ tmp->next = client_env;
+ client_env = tmp;
+ client_env_count++;
+#ifdef DHCPv6
+ } else if (!strcmp(argv[i], "-S")) {
+ if (local_family_set && (local_family == AF_INET)) {
+ usage();
+ }
+ local_family_set = 1;
+ local_family = AF_INET6;
+ wanted_ia_na = 0;
+ stateless = 1;
+ } else if (!strcmp(argv[i], "-N")) {
+ if (local_family_set && (local_family == AF_INET)) {
+ usage();
+ }
+ local_family_set = 1;
+ local_family = AF_INET6;
+ if (wanted_ia_na < 0) {
+ wanted_ia_na = 0;
+ }
+ wanted_ia_na++;
+ } else if (!strcmp(argv[i], "-T")) {
+ if (local_family_set && (local_family == AF_INET)) {
+ usage();
+ }
+ local_family_set = 1;
+ local_family = AF_INET6;
+ if (wanted_ia_na < 0) {
+ wanted_ia_na = 0;
+ }
+ wanted_ia_ta++;
+ } else if (!strcmp(argv[i], "-P")) {
+ if (local_family_set && (local_family == AF_INET)) {
+ usage();
+ }
+ local_family_set = 1;
+ local_family = AF_INET6;
+ if (wanted_ia_na < 0) {
+ wanted_ia_na = 0;
+ }
+ wanted_ia_pd++;
+ } else if (!strcmp(argv[i], "-D")) {
+ if (local_family_set && (local_family == AF_INET)) {
+ usage();
+ }
+ local_family_set = 1;
+ local_family = AF_INET6;
+ if (++i == argc)
+ usage();
+ if (!strcasecmp(argv[i], "LL")) {
+ duid_type = DUID_LL;
+ } else if (!strcasecmp(argv[i], "LLT")) {
+ duid_type = DUID_LLT;
+ } else {
+ usage();
+ }
+#endif /* DHCPv6 */
+ } else if (!strcmp(argv[i], "-v")) {
+ quiet = 0;
+ } else if (!strcmp(argv[i], "--version")) {
+ const char vstring[] = "isc-dhclient-";
+ IGNORE_RET(write(STDERR_FILENO, vstring,
+ strlen(vstring)));
+ IGNORE_RET(write(STDERR_FILENO,
+ PACKAGE_VERSION,
+ strlen(PACKAGE_VERSION)));
+ IGNORE_RET(write(STDERR_FILENO, "\n", 1));
+ exit(0);
+ } else if (argv[i][0] == '-') {
+ usage();
+ } else if (interfaces_requested < 0) {
+ usage();
+ } else {
+ struct interface_info *tmp = NULL;
+
+ status = interface_allocate(&tmp, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal("Can't record interface %s:%s",
+ argv[i], isc_result_totext(status));
+ if (strlen(argv[i]) >= sizeof(tmp->name))
+ log_fatal("%s: interface name too long (is %ld)",
+ argv[i], (long)strlen(argv[i]));
+ 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;
+ interfaces_requested++;
+ }
+ }
+
+ if (wanted_ia_na < 0) {
+ wanted_ia_na = 1;
+ }
+
+ /* Support only one (requested) interface for Prefix Delegation. */
+ if (wanted_ia_pd && (interfaces_requested != 1)) {
+ usage();
+ }
+
+ if (!no_dhclient_conf && (s = getenv("PATH_DHCLIENT_CONF"))) {
+ path_dhclient_conf = s;
+ }
+ if (!no_dhclient_db && (s = getenv("PATH_DHCLIENT_DB"))) {
+ path_dhclient_db = s;
+ }
+ if (!no_dhclient_pid && (s = getenv("PATH_DHCLIENT_PID"))) {
+ path_dhclient_pid = s;
+ }
+ if (!no_dhclient_script && (s = getenv("PATH_DHCLIENT_SCRIPT"))) {
+ path_dhclient_script = s;
+ }
+
+ /* Set up the initial dhcp option universe. */
+ initialize_common_option_spaces();
+
+ /* Assign v4 or v6 specific running parameters. */
+ if (local_family == AF_INET)
+ dhcpv4_client_assignments();
+#ifdef DHCPv6
+ else if (local_family == AF_INET6)
+ dhcpv6_client_assignments();
+#endif /* DHCPv6 */
+ else
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ /*
+ * convert relative path names to absolute, for files that need
+ * to be reopened after chdir() has been called
+ */
+ if (path_dhclient_db[0] != '/') {
+ char *path = dmalloc(PATH_MAX, MDL);
+ if (path == NULL)
+ log_fatal("No memory for filename\n");
+ path_dhclient_db = realpath(path_dhclient_db, path);
+ if (path_dhclient_db == NULL)
+ log_fatal("%s: %s", path, strerror(errno));
+ }
+
+ if (path_dhclient_script[0] != '/') {
+ char *path = dmalloc(PATH_MAX, MDL);
+ if (path == NULL)
+ log_fatal("No memory for filename\n");
+ path_dhclient_script = realpath(path_dhclient_script, path);
+ if (path_dhclient_script == NULL)
+ log_fatal("%s: %s", path, strerror(errno));
+ }
+
+ /*
+ * See if we should kill off any currently running client
+ * we don't try to kill it off if the user told us not
+ * to write a pid file - we assume they are controlling
+ * the process in some other fashion.
+ */
+ if ((release_mode || exit_mode) && (no_pid_file == ISC_FALSE)) {
+ FILE *pidfd;
+ pid_t oldpid;
+ long temp;
+ int e;
+
+ if ((pidfd = fopen(path_dhclient_pid, "r")) != NULL) {
+ e = fscanf(pidfd, "%ld\n", &temp);
+ oldpid = (pid_t)temp;
+
+ if (e != 0 && e != EOF) {
+ if (oldpid && (kill(oldpid, SIGTERM) == 0)) {
+ /*
+ * wait for the old process to
+ * cleanly terminate.
+ * Note kill() with sig=0 could
+ * detect termination but only
+ * the parent can be signaled...
+ */
+ sleep(1);
+ }
+ }
+ fclose(pidfd);
+ }
+ }
+
+ if (!quiet) {
+ log_info("%s %s", message, PACKAGE_VERSION);
+ log_info(copyright);
+ log_info(arr);
+ log_info(url);
+ log_info("%s", "");
+ } else {
+ log_perror = 0;
+ quiet_interface_discovery = 1;
+ }
+
+ /* If we're given a relay agent address to insert, for testing
+ purposes, figure out what it is. */
+ if (mockup_relay) {
+ if (!inet_aton(mockup_relay, &giaddr)) {
+ struct hostent *he;
+ he = gethostbyname(mockup_relay);
+ if (he) {
+ memcpy(&giaddr, he->h_addr_list[0],
+ sizeof giaddr);
+ } else {
+ log_fatal("%s: no such host", mockup_relay);
+ }
+ }
+ }
+
+ /* Get the current time... */
+ gettimeofday(&cur_tv, NULL);
+
+ sockaddr_broadcast.sin_family = AF_INET;
+ sockaddr_broadcast.sin_port = remote_port;
+ if (server) {
+ if (!inet_aton(server, &sockaddr_broadcast.sin_addr)) {
+ struct hostent *he;
+ he = gethostbyname(server);
+ if (he) {
+ memcpy(&sockaddr_broadcast.sin_addr,
+ he->h_addr_list[0],
+ sizeof sockaddr_broadcast.sin_addr);
+ } else
+ sockaddr_broadcast.sin_addr.s_addr =
+ INADDR_BROADCAST;
+ }
+ } else {
+ sockaddr_broadcast.sin_addr.s_addr = INADDR_BROADCAST;
+ }
+
+ inaddr_any.s_addr = INADDR_ANY;
+
+ /* Stateless special case. */
+ if (stateless) {
+ if (release_mode || (wanted_ia_na > 0) ||
+ wanted_ia_ta || wanted_ia_pd ||
+ (interfaces_requested != 1)) {
+ usage();
+ }
+ run_stateless(exit_mode);
+ return 0;
+ }
+
+ /* Discover all the network interfaces. */
+ discover_interfaces(DISCOVER_UNCONFIGURED);
+
+ /* Parse the dhclient.conf file. */
+ read_client_conf();
+
+ /* Parse the lease database. */
+ read_client_leases();
+
+ /* Rewrite the lease database... */
+ rewrite_client_leases();
+
+ /* XXX */
+/* config_counter(&snd_counter, &rcv_counter); */
+
+ /*
+ * If no broadcast interfaces were discovered, call the script
+ * and tell it so.
+ */
+ if (!interfaces) {
+ /*
+ * Call dhclient-script with the NBI flag,
+ * in case somebody cares.
+ */
+ script_init(NULL, "NBI", NULL);
+ script_go(NULL);
+
+ /*
+ * If we haven't been asked to persist, waiting for new
+ * interfaces, then just exit.
+ */
+ if (!persist) {
+ /* Nothing more to do. */
+ log_info("No broadcast interfaces found - exiting.");
+ exit(0);
+ }
+ } else if (!release_mode && !exit_mode) {
+ /* Call the script with the list of interfaces. */
+ for (ip = interfaces; ip; ip = ip->next) {
+ /*
+ * If interfaces were specified, don't configure
+ * interfaces that weren't specified!
+ */
+ if ((interfaces_requested > 0) &&
+ ((ip->flags & (INTERFACE_REQUESTED |
+ INTERFACE_AUTOMATIC)) !=
+ INTERFACE_REQUESTED))
+ continue;
+
+ if (local_family == AF_INET6) {
+ script_init(ip->client, "PREINIT6", NULL);
+ } else {
+ script_init(ip->client, "PREINIT", NULL);
+ if (ip->client->alias != NULL)
+ script_write_params(ip->client,
+ "alias_",
+ ip->client->alias);
+ }
+ script_go(ip->client);
+ }
+ }
+
+ /* At this point, all the interfaces that the script thinks
+ are relevant should be running, so now we once again call
+ discover_interfaces(), and this time ask it to actually set
+ up the interfaces. */
+ discover_interfaces(interfaces_requested != 0
+ ? DISCOVER_REQUESTED
+ : DISCOVER_RUNNING);
+
+ /* 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 + (unsigned)getpid());
+
+ /* Start a configuration state machine for each interface. */
+#ifdef DHCPv6
+ if (local_family == AF_INET6) {
+ /* Establish a default DUID. This may be moved to the
+ * DHCPv4 area later.
+ */
+ if (default_duid.len == 0) {
+ if (default_duid.buffer != NULL)
+ data_string_forget(&default_duid, MDL);
+
+ form_duid(&default_duid, MDL);
+ write_duid(&default_duid);
+ }
+
+ for (ip = interfaces ; ip != NULL ; ip = ip->next) {
+ for (client = ip->client ; client != NULL ;
+ client = client->next) {
+ if (release_mode) {
+ start_release6(client);
+ continue;
+ } else if (exit_mode) {
+ unconfigure6(client, "STOP6");
+ continue;
+ }
+
+ /* If we have a previous binding, Confirm
+ * that we can (or can't) still use it.
+ */
+ if ((client->active_lease != NULL) &&
+ !client->active_lease->released)
+ start_confirm6(client);
+ else
+ start_init6(client);
+ }
+ }
+ } else
+#endif /* DHCPv6 */
+ {
+ for (ip = interfaces ; ip ; ip = ip->next) {
+ ip->flags |= INTERFACE_RUNNING;
+ for (client = ip->client ; client ;
+ client = client->next) {
+ if (exit_mode)
+ state_stop(client);
+ else if (release_mode)
+ do_release(client);
+ else {
+ client->state = S_INIT;
+
+ if (top_level_config.initial_delay>0)
+ {
+ tv.tv_sec = 0;
+ if (top_level_config.
+ initial_delay>1)
+ tv.tv_sec = cur_time
+ + random()
+ % (top_level_config.
+ initial_delay-1);
+ tv.tv_usec = random()
+ % 1000000;
+ /*
+ * this gives better
+ * distribution than just
+ *whole seconds
+ */
+ add_timeout(&tv, state_reboot,
+ client, 0, 0);
+ } else {
+ state_reboot(client);
+ }
+ }
+ }
+ }
+ }
+
+ if (exit_mode)
+ return 0;
+ if (release_mode) {
+#ifndef DHCPv6
+ return 0;
+#else
+ if (local_family == AF_INET6) {
+ if (onetry)
+ return 0;
+ } else
+ return 0;
+#endif /* DHCPv6 */
+ }
+
+ /* Start up a listener for the object management API protocol. */
+ if (top_level_config.omapi_port != -1) {
+ listener = NULL;
+ result = omapi_generic_new(&listener, MDL);
+ if (result != ISC_R_SUCCESS)
+ log_fatal("Can't allocate new generic object: %s\n",
+ isc_result_totext(result));
+ result = omapi_protocol_listen(listener,
+ (unsigned)
+ top_level_config.omapi_port,
+ 1);
+ if (result != ISC_R_SUCCESS)
+ log_fatal("Can't start OMAPI protocol: %s",
+ isc_result_totext (result));
+ }
+
+ /* Set up the bootp packet handler... */
+ bootp_packet_handler = do_packet;
+#ifdef DHCPv6
+ dhcpv6_packet_handler = do_packet6;
+#endif /* DHCPv6 */
+
+#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
+
+#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
+
+ /* If we're not supposed to wait before getting the address,
+ don't. */
+ if (nowait)
+ go_daemon();
+
+ /* If we're not going to daemonize, write the pid file
+ now. */
+ if (no_daemon || nowait)
+ write_client_pid_file();
+
+ /* Start dispatching packets and timeouts... */
+ dispatch();
+
+ /* In fact dispatch() never returns. */
+ return 0;
+}
+
+static void usage()
+{
+ log_info("%s %s", message, PACKAGE_VERSION);
+ log_info(copyright);
+ log_info(arr);
+ log_info(url);
+
+
+ log_fatal("Usage: dhclient "
+#ifdef DHCPv6
+ "[-4|-6] [-SNTP1dvrx] [-nw] [-p <port>] [-D LL|LLT]\n"
+#else /* DHCPv6 */
+ "[-1dvrx] [-nw] [-p <port>]\n"
+#endif /* DHCPv6 */
+ " [-s server-addr] [-cf config-file] "
+ "[-lf lease-file]\n"
+ " [-pf pid-file] [--no-pid] [-e VAR=val]\n"
+ " [-sf script-file] [interface]");
+}
+
+void run_stateless(int exit_mode)
+{
+#ifdef DHCPv6
+ struct client_state *client;
+ omapi_object_t *listener;
+ isc_result_t result;
+
+ /* Discover the network interface. */
+ discover_interfaces(DISCOVER_REQUESTED);
+
+ if (!interfaces)
+ usage();
+
+ /* Parse the dhclient.conf file. */
+ read_client_conf();
+
+ /* Parse the lease database. */
+ read_client_leases();
+
+ /* Establish a default DUID. */
+ if (default_duid.len == 0) {
+ if (default_duid.buffer != NULL)
+ data_string_forget(&default_duid, MDL);
+
+ form_duid(&default_duid, MDL);
+ }
+
+ /* Start a configuration state machine. */
+ for (client = interfaces->client ;
+ client != NULL ;
+ client = client->next) {
+ if (exit_mode) {
+ unconfigure6(client, "STOP6");
+ continue;
+ }
+ start_info_request6(client);
+ }
+ if (exit_mode)
+ return;
+
+ /* Start up a listener for the object management API protocol. */
+ if (top_level_config.omapi_port != -1) {
+ listener = NULL;
+ result = omapi_generic_new(&listener, MDL);
+ if (result != ISC_R_SUCCESS)
+ log_fatal("Can't allocate new generic object: %s\n",
+ isc_result_totext(result));
+ result = omapi_protocol_listen(listener,
+ (unsigned)
+ top_level_config.omapi_port,
+ 1);
+ if (result != ISC_R_SUCCESS)
+ log_fatal("Can't start OMAPI protocol: %s",
+ isc_result_totext(result));
+ }
+
+ /* Set up the packet handler... */
+ dhcpv6_packet_handler = do_packet6;
+
+#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
+
+ /* If we're not supposed to wait before getting the address,
+ don't. */
+ if (nowait)
+ go_daemon();
+
+ /* If we're not going to daemonize, write the pid file
+ now. */
+ if (no_daemon || nowait)
+ write_client_pid_file();
+
+ /* Start dispatching packets and timeouts... */
+ dispatch();
+
+#endif /* DHCPv6 */
+ return;
+}
+
+isc_result_t find_class (struct class **c,
+ const char *s, const char *file, int line)
+{
+ return 0;
+}
+
+int check_collection (packet, lease, collection)
+ struct packet *packet;
+ struct lease *lease;
+ struct collection *collection;
+{
+ return 0;
+}
+
+void classify (packet, class)
+ struct packet *packet;
+ struct class *class;
+{
+}
+
+int unbill_class (lease, class)
+ struct lease *lease;
+ struct class *class;
+{
+ return 0;
+}
+
+int find_subnet (struct subnet **sp,
+ struct iaddr addr, const char *file, int line)
+{
+ return 0;
+}
+
+/* Individual States:
+ *
+ * Each routine is called from the dhclient_state_machine() in one of
+ * these conditions:
+ * -> entering INIT state
+ * -> recvpacket_flag == 0: timeout in this state
+ * -> otherwise: received a packet in this state
+ *
+ * Return conditions as handled by dhclient_state_machine():
+ * Returns 1, sendpacket_flag = 1: send packet, reset timer.
+ * Returns 1, sendpacket_flag = 0: just reset the timer (wait for a milestone).
+ * Returns 0: finish the nap which was interrupted for no good reason.
+ *
+ * Several per-interface variables are used to keep track of the process:
+ * active_lease: the lease that is being used on the interface
+ * (null pointer if not configured yet).
+ * offered_leases: leases corresponding to DHCPOFFER messages that have
+ * been sent to us by DHCP servers.
+ * acked_leases: leases corresponding to DHCPACK messages that have been
+ * sent to us by DHCP servers.
+ * sendpacket: DHCP packet we're trying to send.
+ * destination: IP address to send sendpacket to
+ * In addition, there are several relevant per-lease variables.
+ * T1_expiry, T2_expiry, lease_expiry: lease milestones
+ * In the active lease, these control the process of renewing the lease;
+ * In leases on the acked_leases list, this simply determines when we
+ * can no longer legitimately use the lease.
+ */
+
+void state_reboot (cpp)
+ void *cpp;
+{
+ struct client_state *client = cpp;
+
+ /* If we don't remember an active lease, go straight to INIT. */
+ if (!client -> active ||
+ client -> active -> is_bootp ||
+ client -> active -> expiry <= cur_time) {
+ state_init (client);
+ return;
+ }
+
+ /* We are in the rebooting state. */
+ client -> state = S_REBOOTING;
+
+ /*
+ * make_request doesn't initialize xid because it normally comes
+ * from the DHCPDISCOVER, but we haven't sent a DHCPDISCOVER,
+ * so pick an xid now.
+ */
+ client -> xid = random ();
+
+ /*
+ * Make a DHCPREQUEST packet, and set
+ * appropriate per-interface flags.
+ */
+ make_request (client, client -> active);
+ client -> destination = iaddr_broadcast;
+ client -> first_sending = cur_time;
+ client -> interval = client -> config -> initial_interval;
+
+ /* Zap the medium list... */
+ client -> medium = NULL;
+
+ /* Send out the first DHCPREQUEST packet. */
+ send_request (client);
+}
+
+/* Called when a lease has completely expired and we've been unable to
+ renew it. */
+
+void state_init (cpp)
+ void *cpp;
+{
+ struct client_state *client = cpp;
+
+ ASSERT_STATE(state, S_INIT);
+
+ /* Make a DHCPDISCOVER packet, and set appropriate per-interface
+ flags. */
+ make_discover (client, client -> active);
+ client -> xid = client -> packet.xid;
+ client -> destination = iaddr_broadcast;
+ client -> state = S_SELECTING;
+ client -> first_sending = cur_time;
+ client -> interval = client -> config -> initial_interval;
+
+ /* Add an immediate timeout to cause the first DHCPDISCOVER packet
+ to go out. */
+ send_discover (client);
+}
+
+/*
+ * state_selecting is called when one or more DHCPOFFER packets have been
+ * received and a configurable period of time has passed.
+ */
+
+void state_selecting (cpp)
+ void *cpp;
+{
+ struct client_state *client = cpp;
+ struct client_lease *lp, *next, *picked;
+
+
+ ASSERT_STATE(state, S_SELECTING);
+
+ /*
+ * Cancel state_selecting and send_discover timeouts, since either
+ * one could have got us here.
+ */
+ cancel_timeout (state_selecting, client);
+ cancel_timeout (send_discover, client);
+
+ /*
+ * We have received one or more DHCPOFFER packets. Currently,
+ * the only criterion by which we judge leases is whether or
+ * not we get a response when we arp for them.
+ */
+ picked = NULL;
+ for (lp = client -> offered_leases; lp; lp = next) {
+ next = lp -> next;
+
+ /*
+ * Check to see if we got an ARPREPLY for the address
+ * in this particular lease.
+ */
+ if (!picked) {
+ picked = lp;
+ picked -> next = NULL;
+ } else {
+ destroy_client_lease (lp);
+ }
+ }
+ client -> offered_leases = NULL;
+
+ /*
+ * If we just tossed all the leases we were offered, go back
+ * to square one.
+ */
+ if (!picked) {
+ client -> state = S_INIT;
+ state_init (client);
+ return;
+ }
+
+ /* If it was a BOOTREPLY, we can just take the address right now. */
+ if (picked -> is_bootp) {
+ client -> new = picked;
+
+ /* Make up some lease expiry times
+ XXX these should be configurable. */
+ client -> new -> expiry = cur_time + 12000;
+ client -> new -> renewal += cur_time + 8000;
+ client -> new -> rebind += cur_time + 10000;
+
+ client -> state = S_REQUESTING;
+
+ /* Bind to the address we received. */
+ bind_lease (client);
+ return;
+ }
+
+ /* Go to the REQUESTING state. */
+ client -> destination = iaddr_broadcast;
+ client -> state = S_REQUESTING;
+ client -> first_sending = cur_time;
+ client -> interval = client -> config -> initial_interval;
+
+ /* Make a DHCPREQUEST packet from the lease we picked. */
+ make_request (client, picked);
+ client -> xid = client -> packet.xid;
+
+ /* Toss the lease we picked - we'll get it back in a DHCPACK. */
+ destroy_client_lease (picked);
+
+ /* Add an immediate timeout to send the first DHCPREQUEST packet. */
+ send_request (client);
+}
+
+/* state_requesting is called when we receive a DHCPACK message after
+ having sent out one or more DHCPREQUEST packets. */
+
+void dhcpack (packet)
+ struct packet *packet;
+{
+ struct interface_info *ip = packet -> interface;
+ struct client_state *client;
+ struct client_lease *lease;
+ struct option_cache *oc;
+ struct data_string ds;
+
+ /* If we're not receptive to an offer right now, or if the offer
+ has an unrecognizable transaction id, then just drop it. */
+ for (client = ip -> client; client; client = client -> next) {
+ if (client -> xid == packet -> raw -> xid)
+ break;
+ }
+ if (!client ||
+ (packet -> interface -> hw_address.hlen - 1 !=
+ packet -> raw -> hlen) ||
+ (memcmp (&packet -> interface -> hw_address.hbuf [1],
+ packet -> raw -> chaddr, packet -> raw -> hlen))) {
+#if defined (DEBUG)
+ log_debug ("DHCPACK in wrong transaction.");
+#endif
+ return;
+ }
+
+ if (client -> state != S_REBOOTING &&
+ client -> state != S_REQUESTING &&
+ client -> state != S_RENEWING &&
+ client -> state != S_REBINDING) {
+#if defined (DEBUG)
+ log_debug ("DHCPACK in wrong state.");
+#endif
+ return;
+ }
+
+ log_info ("DHCPACK from %s", piaddr (packet -> client_addr));
+
+ lease = packet_to_lease (packet, client);
+ if (!lease) {
+ log_info ("packet_to_lease failed.");
+ return;
+ }
+
+ client -> new = lease;
+
+ /* Stop resending DHCPREQUEST. */
+ cancel_timeout (send_request, client);
+
+ /* Figure out the lease time. */
+ oc = lookup_option (&dhcp_universe, client -> new -> options,
+ DHO_DHCP_LEASE_TIME);
+ memset (&ds, 0, sizeof ds);
+ if (oc &&
+ evaluate_option_cache (&ds, packet, (struct lease *)0, client,
+ packet -> options, client -> new -> options,
+ &global_scope, oc, MDL)) {
+ if (ds.len > 3)
+ client -> new -> expiry = getULong (ds.data);
+ else
+ client -> new -> expiry = 0;
+ data_string_forget (&ds, MDL);
+ } else
+ client -> new -> expiry = 0;
+
+ if (client->new->expiry == 0) {
+ struct timeval tv;
+
+ log_error ("no expiry time on offered lease.");
+
+ /* Quench this (broken) server. Return to INIT to reselect. */
+ add_reject(packet);
+
+ /* 1/2 second delay to restart at INIT. */
+ tv.tv_sec = cur_tv.tv_sec;
+ tv.tv_usec = cur_tv.tv_usec + 500000;
+
+ if (tv.tv_usec >= 1000000) {
+ tv.tv_sec++;
+ tv.tv_usec -= 1000000;
+ }
+
+ add_timeout(&tv, state_init, client, 0, 0);
+ return;
+ }
+
+ /*
+ * A number that looks negative here is really just very large,
+ * because the lease expiry offset is unsigned.
+ */
+ if (client->new->expiry < 0)
+ client->new->expiry = TIME_MAX;
+
+ /* Take the server-provided renewal time if there is one. */
+ oc = lookup_option (&dhcp_universe, client -> new -> options,
+ DHO_DHCP_RENEWAL_TIME);
+ if (oc &&
+ evaluate_option_cache (&ds, packet, (struct lease *)0, client,
+ packet -> options, client -> new -> options,
+ &global_scope, oc, MDL)) {
+ if (ds.len > 3)
+ client -> new -> renewal = getULong (ds.data);
+ else
+ client -> new -> renewal = 0;
+ data_string_forget (&ds, MDL);
+ } else
+ client -> new -> renewal = 0;
+
+ /* If it wasn't specified by the server, calculate it. */
+ if (!client -> new -> renewal)
+ client -> new -> renewal = client -> new -> expiry / 2 + 1;
+
+ if (client -> new -> renewal <= 0)
+ client -> new -> renewal = TIME_MAX;
+
+ /* Now introduce some randomness to the renewal time: */
+ if (client->new->renewal <= ((TIME_MAX / 3) - 3))
+ client->new->renewal = (((client->new->renewal * 3) + 3) / 4) +
+ (((random() % client->new->renewal) + 3) / 4);
+
+ /* Same deal with the rebind time. */
+ oc = lookup_option (&dhcp_universe, client -> new -> options,
+ DHO_DHCP_REBINDING_TIME);
+ if (oc &&
+ evaluate_option_cache (&ds, packet, (struct lease *)0, client,
+ packet -> options, client -> new -> options,
+ &global_scope, oc, MDL)) {
+ if (ds.len > 3)
+ client -> new -> rebind = getULong (ds.data);
+ else
+ client -> new -> rebind = 0;
+ data_string_forget (&ds, MDL);
+ } else
+ client -> new -> rebind = 0;
+
+ if (client -> new -> rebind <= 0) {
+ if (client -> new -> expiry <= TIME_MAX / 7)
+ client -> new -> rebind =
+ client -> new -> expiry * 7 / 8;
+ else
+ client -> new -> rebind =
+ client -> new -> expiry / 8 * 7;
+ }
+
+ /* Make sure our randomness didn't run the renewal time past the
+ rebind time. */
+ if (client -> new -> renewal > client -> new -> rebind) {
+ if (client -> new -> rebind <= TIME_MAX / 3)
+ client -> new -> renewal =
+ client -> new -> rebind * 3 / 4;
+ else
+ client -> new -> renewal =
+ client -> new -> rebind / 4 * 3;
+ }
+
+ client -> new -> expiry += cur_time;
+ /* Lease lengths can never be negative. */
+ if (client -> new -> expiry < cur_time)
+ client -> new -> expiry = TIME_MAX;
+ client -> new -> renewal += cur_time;
+ if (client -> new -> renewal < cur_time)
+ client -> new -> renewal = TIME_MAX;
+ client -> new -> rebind += cur_time;
+ if (client -> new -> rebind < cur_time)
+ client -> new -> rebind = TIME_MAX;
+
+ bind_lease (client);
+}
+
+void bind_lease (client)
+ struct client_state *client;
+{
+ struct timeval tv;
+
+ /* Remember the medium. */
+ client->new->medium = client->medium;
+
+ /* Run the client script with the new parameters. */
+ script_init(client, (client->state == S_REQUESTING ? "BOUND" :
+ (client->state == S_RENEWING ? "RENEW" :
+ (client->state == S_REBOOTING ? "REBOOT" :
+ "REBIND"))),
+ client->new->medium);
+ if (client->active && client->state != S_REBOOTING)
+ script_write_params(client, "old_", client->active);
+ script_write_params (client, "new_", client->new);
+ script_write_requested(client);
+ if (client->alias)
+ script_write_params(client, "alias_", client->alias);
+
+ /* If the BOUND/RENEW code detects another machine using the
+ offered address, it exits nonzero. We need to send a
+ DHCPDECLINE and toss the lease. */
+ if (script_go(client)) {
+ make_decline(client, client->new);
+ send_decline(client);
+ destroy_client_lease(client->new);
+ client->new = NULL;
+ if (onetry) {
+ if (!quiet)
+ log_info("Unable to obtain a lease on first "
+ "try (declined). Exiting.");
+ exit(2);
+ } else {
+ state_init(client);
+ return;
+ }
+ }
+
+ /* Write out the new lease if it has been long enough. */
+ if (!client->last_write ||
+ (cur_time - client->last_write) >= MIN_LEASE_WRITE)
+ write_client_lease(client, client->new, 0, 1);
+
+ /* Replace the old active lease with the new one. */
+ if (client->active)
+ destroy_client_lease(client->active);
+ client->active = client->new;
+ client->new = NULL;
+
+ /* Set up a timeout to start the renewal process. */
+ tv.tv_sec = client->active->renewal;
+ tv.tv_usec = ((client->active->renewal - cur_tv.tv_sec) > 1) ?
+ random() % 1000000 : cur_tv.tv_usec;
+ add_timeout(&tv, state_bound, client, 0, 0);
+
+ log_info("bound to %s -- renewal in %ld seconds.",
+ piaddr(client->active->address),
+ (long)(client->active->renewal - cur_time));
+ client->state = S_BOUND;
+ reinitialize_interfaces();
+ go_daemon();
+#if defined (NSUPDATE)
+ if (client->config->do_forward_update)
+ dhclient_schedule_updates(client, &client->active->address, 1);
+#endif
+}
+
+/* state_bound is called when we've successfully bound to a particular
+ lease, but the renewal time on that lease has expired. We are
+ expected to unicast a DHCPREQUEST to the server that gave us our
+ original lease. */
+
+void state_bound (cpp)
+ void *cpp;
+{
+ struct client_state *client = cpp;
+ struct option_cache *oc;
+ struct data_string ds;
+
+ ASSERT_STATE(state, S_BOUND);
+
+ /* T1 has expired. */
+ make_request (client, client -> active);
+ client -> xid = client -> packet.xid;
+
+ memset (&ds, 0, sizeof ds);
+ oc = lookup_option (&dhcp_universe, client -> active -> options,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ if (oc &&
+ evaluate_option_cache (&ds, (struct packet *)0, (struct lease *)0,
+ client, (struct option_state *)0,
+ client -> active -> options,
+ &global_scope, oc, MDL)) {
+ if (ds.len > 3) {
+ memcpy (client -> destination.iabuf, ds.data, 4);
+ client -> destination.len = 4;
+ } else
+ client -> destination = iaddr_broadcast;
+
+ data_string_forget (&ds, MDL);
+ } else
+ client -> destination = iaddr_broadcast;
+
+ client -> first_sending = cur_time;
+ client -> interval = client -> config -> initial_interval;
+ client -> state = S_RENEWING;
+
+ /* Send the first packet immediately. */
+ send_request (client);
+}
+
+/* state_stop is called when we've been told to shut down. We unconfigure
+ the interfaces, and then stop operating until told otherwise. */
+
+void state_stop (cpp)
+ void *cpp;
+{
+ struct client_state *client = cpp;
+
+ /* Cancel all timeouts. */
+ cancel_timeout(state_selecting, client);
+ cancel_timeout(send_discover, client);
+ cancel_timeout(send_request, client);
+ cancel_timeout(state_bound, client);
+
+ /* If we have an address, unconfigure it. */
+ if (client->active) {
+ script_init(client, "STOP", client->active->medium);
+ script_write_params(client, "old_", client->active);
+ script_write_requested(client);
+ if (client->alias)
+ script_write_params(client, "alias_", client->alias);
+ script_go(client);
+ }
+}
+
+int commit_leases ()
+{
+ return 0;
+}
+
+int write_lease (lease)
+ struct lease *lease;
+{
+ return 0;
+}
+
+int write_host (host)
+ struct host_decl *host;
+{
+ return 0;
+}
+
+void db_startup (testp)
+ int testp;
+{
+}
+
+void bootp (packet)
+ struct packet *packet;
+{
+ struct iaddrmatchlist *ap;
+ char addrbuf[4*16];
+ char maskbuf[4*16];
+
+ if (packet -> raw -> op != BOOTREPLY)
+ return;
+
+ /* If there's a reject list, make sure this packet's sender isn't
+ on it. */
+ for (ap = packet -> interface -> client -> config -> reject_list;
+ ap; ap = ap -> next) {
+ if (addr_match(&packet->client_addr, &ap->match)) {
+
+ /* piaddr() returns its result in a static
+ buffer sized 4*16 (see common/inet.c). */
+
+ strcpy(addrbuf, piaddr(ap->match.addr));
+ strcpy(maskbuf, piaddr(ap->match.mask));
+
+ log_info("BOOTREPLY from %s rejected by rule %s "
+ "mask %s.", piaddr(packet->client_addr),
+ addrbuf, maskbuf);
+ return;
+ }
+ }
+
+ dhcpoffer (packet);
+
+}
+
+void dhcp (packet)
+ struct packet *packet;
+{
+ struct iaddrmatchlist *ap;
+ void (*handler) (struct packet *);
+ const char *type;
+ char addrbuf[4*16];
+ char maskbuf[4*16];
+
+ switch (packet -> packet_type) {
+ case DHCPOFFER:
+ handler = dhcpoffer;
+ type = "DHCPOFFER";
+ break;
+
+ case DHCPNAK:
+ handler = dhcpnak;
+ type = "DHCPNACK";
+ break;
+
+ case DHCPACK:
+ handler = dhcpack;
+ type = "DHCPACK";
+ break;
+
+ default:
+ return;
+ }
+
+ /* If there's a reject list, make sure this packet's sender isn't
+ on it. */
+ for (ap = packet -> interface -> client -> config -> reject_list;
+ ap; ap = ap -> next) {
+ if (addr_match(&packet->client_addr, &ap->match)) {
+
+ /* piaddr() returns its result in a static
+ buffer sized 4*16 (see common/inet.c). */
+
+ strcpy(addrbuf, piaddr(ap->match.addr));
+ strcpy(maskbuf, piaddr(ap->match.mask));
+
+ log_info("%s from %s rejected by rule %s mask %s.",
+ type, piaddr(packet->client_addr),
+ addrbuf, maskbuf);
+ return;
+ }
+ }
+ (*handler) (packet);
+}
+
+#ifdef DHCPv6
+void
+dhcpv6(struct packet *packet) {
+ struct iaddrmatchlist *ap;
+ struct client_state *client;
+ char addrbuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")];
+
+ /* Silently drop bogus messages. */
+ if (packet->dhcpv6_msg_type >= dhcpv6_type_name_max)
+ return;
+
+ /* Discard, with log, packets from quenched sources. */
+ for (ap = packet->interface->client->config->reject_list ;
+ ap ; ap = ap->next) {
+ if (addr_match(&packet->client_addr, &ap->match)) {
+ strcpy(addrbuf, piaddr(packet->client_addr));
+ log_info("%s from %s rejected by rule %s",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ addrbuf,
+ piaddrmask(&ap->match.addr, &ap->match.mask));
+ return;
+ }
+ }
+
+ /* Screen out nonsensical messages. */
+ switch(packet->dhcpv6_msg_type) {
+ case DHCPV6_ADVERTISE:
+ case DHCPV6_RECONFIGURE:
+ if (stateless)
+ return;
+ /* Falls through */
+ case DHCPV6_REPLY:
+ log_info("RCV: %s message on %s from %s.",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ packet->interface->name, piaddr(packet->client_addr));
+ break;
+
+ default:
+ return;
+ }
+
+ /* Find a client state that matches the incoming XID. */
+ for (client = packet->interface->client ; client ;
+ client = client->next) {
+ if (memcmp(&client->dhcpv6_transaction_id,
+ packet->dhcpv6_transaction_id, 3) == 0) {
+ client->v6_handler(packet, client);
+ return;
+ }
+ }
+
+ /* XXX: temporary log for debugging */
+ log_info("Packet received, but nothing done with it.");
+}
+#endif /* DHCPv6 */
+
+void dhcpoffer (packet)
+ struct packet *packet;
+{
+ struct interface_info *ip = packet -> interface;
+ struct client_state *client;
+ struct client_lease *lease, *lp;
+ struct option **req;
+ int i;
+ int stop_selecting;
+ const char *name = packet -> packet_type ? "DHCPOFFER" : "BOOTREPLY";
+ char obuf [1024];
+ struct timeval tv;
+
+#ifdef DEBUG_PACKET
+ dump_packet (packet);
+#endif
+
+ /* Find a client state that matches the xid... */
+ for (client = ip -> client; client; client = client -> next)
+ if (client -> xid == packet -> raw -> xid)
+ break;
+
+ /* If we're not receptive to an offer right now, or if the offer
+ has an unrecognizable transaction id, then just drop it. */
+ if (!client ||
+ client -> state != S_SELECTING ||
+ (packet -> interface -> hw_address.hlen - 1 !=
+ packet -> raw -> hlen) ||
+ (memcmp (&packet -> interface -> hw_address.hbuf [1],
+ packet -> raw -> chaddr, packet -> raw -> hlen))) {
+#if defined (DEBUG)
+ log_debug ("%s in wrong transaction.", name);
+#endif
+ return;
+ }
+
+ sprintf (obuf, "%s from %s", name, piaddr (packet -> client_addr));
+
+
+ /* If this lease doesn't supply the minimum required DHCPv4 parameters,
+ * ignore it.
+ */
+ req = client->config->required_options;
+ if (req != NULL) {
+ for (i = 0 ; req[i] != NULL ; i++) {
+ if ((req[i]->universe == &dhcp_universe) &&
+ !lookup_option(&dhcp_universe, packet->options,
+ req[i]->code)) {
+ struct option *option = NULL;
+ unsigned code = req[i]->code;
+
+ option_code_hash_lookup(&option,
+ dhcp_universe.code_hash,
+ &code, 0, MDL);
+
+ if (option)
+ log_info("%s: no %s option.", obuf,
+ option->name);
+ else
+ log_info("%s: no unknown-%u option.",
+ obuf, code);
+
+ option_dereference(&option, MDL);
+
+ return;
+ }
+ }
+ }
+
+ /* If we've already seen this lease, don't record it again. */
+ for (lease = client -> offered_leases; lease; lease = lease -> next) {
+ if (lease -> address.len == sizeof packet -> raw -> yiaddr &&
+ !memcmp (lease -> address.iabuf,
+ &packet -> raw -> yiaddr, lease -> address.len)) {
+ log_debug ("%s: already seen.", obuf);
+ return;
+ }
+ }
+
+ lease = packet_to_lease (packet, client);
+ if (!lease) {
+ log_info ("%s: packet_to_lease failed.", obuf);
+ return;
+ }
+
+ /* If this lease was acquired through a BOOTREPLY, record that
+ fact. */
+ if (!packet -> options_valid || !packet -> packet_type)
+ lease -> is_bootp = 1;
+
+ /* Record the medium under which this lease was offered. */
+ lease -> medium = client -> medium;
+
+ /* Figure out when we're supposed to stop selecting. */
+ stop_selecting = (client -> first_sending +
+ client -> config -> select_interval);
+
+ /* If this is the lease we asked for, put it at the head of the
+ list, and don't mess with the arp request timeout. */
+ if (lease -> address.len == client -> requested_address.len &&
+ !memcmp (lease -> address.iabuf,
+ client -> requested_address.iabuf,
+ client -> requested_address.len)) {
+ lease -> next = client -> offered_leases;
+ client -> offered_leases = lease;
+ } else {
+ /* Put the lease at the end of the list. */
+ lease -> next = (struct client_lease *)0;
+ if (!client -> offered_leases)
+ client -> offered_leases = lease;
+ else {
+ for (lp = client -> offered_leases; lp -> next;
+ lp = lp -> next)
+ ;
+ lp -> next = lease;
+ }
+ }
+
+ /* If the selecting interval has expired, go immediately to
+ state_selecting(). Otherwise, time out into
+ state_selecting at the select interval. */
+ if (stop_selecting <= cur_tv.tv_sec)
+ state_selecting (client);
+ else {
+ tv.tv_sec = stop_selecting;
+ tv.tv_usec = cur_tv.tv_usec;
+ add_timeout(&tv, state_selecting, client, 0, 0);
+ cancel_timeout(send_discover, client);
+ }
+ log_info("%s", obuf);
+}
+
+/* Allocate a client_lease structure and initialize it from the parameters
+ in the specified packet. */
+
+struct client_lease *packet_to_lease (packet, client)
+ struct packet *packet;
+ struct client_state *client;
+{
+ struct client_lease *lease;
+ unsigned i;
+ struct option_cache *oc;
+ struct option *option = NULL;
+ struct data_string data;
+
+ lease = (struct client_lease *)new_client_lease (MDL);
+
+ if (!lease) {
+ log_error("packet_to_lease: no memory to record lease.\n");
+ return NULL;
+ }
+
+ memset(lease, 0, sizeof(*lease));
+
+ /* Copy the lease options. */
+ option_state_reference(&lease->options, packet->options, MDL);
+
+ lease->address.len = sizeof(packet->raw->yiaddr);
+ memcpy(lease->address.iabuf, &packet->raw->yiaddr,
+ lease->address.len);
+
+ lease->next_srv_addr.len = sizeof(packet->raw->siaddr);
+ memcpy(lease->next_srv_addr.iabuf, &packet->raw->siaddr,
+ lease->next_srv_addr.len);
+
+ memset(&data, 0, sizeof(data));
+
+ if (client -> config -> vendor_space_name) {
+ i = DHO_VENDOR_ENCAPSULATED_OPTIONS;
+
+ /* See if there was a vendor encapsulation option. */
+ oc = lookup_option (&dhcp_universe, lease -> options, i);
+ if (oc &&
+ client -> config -> vendor_space_name &&
+ evaluate_option_cache (&data, packet,
+ (struct lease *)0, client,
+ packet -> options, lease -> options,
+ &global_scope, oc, MDL)) {
+ if (data.len) {
+ if (!option_code_hash_lookup(&option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL))
+ log_fatal("Unable to find VENDOR "
+ "option (%s:%d).", MDL);
+ parse_encapsulated_suboptions
+ (packet -> options, option,
+ data.data, data.len, &dhcp_universe,
+ client -> config -> vendor_space_name
+ );
+
+ option_dereference(&option, MDL);
+ }
+ data_string_forget (&data, MDL);
+ }
+ } else
+ i = 0;
+
+ /* Figure out the overload flag. */
+ oc = lookup_option (&dhcp_universe, lease -> options,
+ DHO_DHCP_OPTION_OVERLOAD);
+ if (oc &&
+ evaluate_option_cache (&data, packet, (struct lease *)0, client,
+ packet -> options, lease -> options,
+ &global_scope, oc, MDL)) {
+ if (data.len > 0)
+ i = data.data [0];
+ else
+ i = 0;
+ data_string_forget (&data, MDL);
+ } else
+ i = 0;
+
+ /* If the server name was filled out, copy it. */
+ if (!(i & 2) && packet -> raw -> sname [0]) {
+ unsigned len;
+ /* Don't count on the NUL terminator. */
+ for (len = 0; len < DHCP_SNAME_LEN; len++)
+ if (!packet -> raw -> sname [len])
+ break;
+ lease -> server_name = dmalloc (len + 1, MDL);
+ if (!lease -> server_name) {
+ log_error ("dhcpoffer: no memory for server name.\n");
+ destroy_client_lease (lease);
+ return (struct client_lease *)0;
+ } else {
+ memcpy (lease -> server_name,
+ packet -> raw -> sname, len);
+ lease -> server_name [len] = 0;
+ }
+ }
+
+ /* Ditto for the filename. */
+ if (!(i & 1) && packet -> raw -> file [0]) {
+ unsigned len;
+ /* Don't count on the NUL terminator. */
+ for (len = 0; len < DHCP_FILE_LEN; len++)
+ if (!packet -> raw -> file [len])
+ break;
+ lease -> filename = dmalloc (len + 1, MDL);
+ if (!lease -> filename) {
+ log_error ("dhcpoffer: no memory for filename.\n");
+ destroy_client_lease (lease);
+ return (struct client_lease *)0;
+ } else {
+ memcpy (lease -> filename,
+ packet -> raw -> file, len);
+ lease -> filename [len] = 0;
+ }
+ }
+
+ execute_statements_in_scope ((struct binding_value **)0,
+ (struct packet *)packet,
+ (struct lease *)0, client,
+ lease -> options, lease -> options,
+ &global_scope,
+ client -> config -> on_receipt,
+ (struct group *)0);
+
+ return lease;
+}
+
+void dhcpnak (packet)
+ struct packet *packet;
+{
+ struct interface_info *ip = packet -> interface;
+ struct client_state *client;
+
+ /* Find a client state that matches the xid... */
+ for (client = ip -> client; client; client = client -> next)
+ if (client -> xid == packet -> raw -> xid)
+ break;
+
+ /* If we're not receptive to an offer right now, or if the offer
+ has an unrecognizable transaction id, then just drop it. */
+ if (!client ||
+ (packet -> interface -> hw_address.hlen - 1 !=
+ packet -> raw -> hlen) ||
+ (memcmp (&packet -> interface -> hw_address.hbuf [1],
+ packet -> raw -> chaddr, packet -> raw -> hlen))) {
+#if defined (DEBUG)
+ log_debug ("DHCPNAK in wrong transaction.");
+#endif
+ return;
+ }
+
+ if (client -> state != S_REBOOTING &&
+ client -> state != S_REQUESTING &&
+ client -> state != S_RENEWING &&
+ client -> state != S_REBINDING) {
+#if defined (DEBUG)
+ log_debug ("DHCPNAK in wrong state.");
+#endif
+ return;
+ }
+
+ log_info ("DHCPNAK from %s", piaddr (packet -> client_addr));
+
+ if (!client -> active) {
+#if defined (DEBUG)
+ log_info ("DHCPNAK with no active lease.\n");
+#endif
+ return;
+ }
+
+ /* If we get a DHCPNAK, we use the EXPIRE dhclient-script state
+ * to indicate that we want all old bindings to be removed. (It
+ * is possible that we may get a NAK while in the RENEW state,
+ * so we might have bindings active at that time)
+ */
+ script_init(client, "EXPIRE", NULL);
+ script_write_params(client, "old_", client->active);
+ script_write_requested(client);
+ if (client->alias)
+ script_write_params(client, "alias_", client->alias);
+ script_go(client);
+
+ destroy_client_lease (client -> active);
+ client -> active = (struct client_lease *)0;
+
+ /* Stop sending DHCPREQUEST packets... */
+ cancel_timeout (send_request, client);
+
+ /* On some scripts, 'EXPIRE' causes the interface to be ifconfig'd
+ * down (this expunges any routes and arp cache). This makes the
+ * interface unusable by state_init(), which we call next. So, we
+ * need to 'PREINIT' the interface to bring it back up.
+ */
+ script_init(client, "PREINIT", NULL);
+ if (client->alias)
+ script_write_params(client, "alias_", client->alias);
+ script_go(client);
+
+ client -> state = S_INIT;
+ state_init (client);
+}
+
+/* Send out a DHCPDISCOVER packet, and set a timeout to send out another
+ one after the right interval has expired. If we don't get an offer by
+ the time we reach the panic interval, call the panic function. */
+
+void send_discover (cpp)
+ void *cpp;
+{
+ struct client_state *client = cpp;
+
+ int result;
+ int interval;
+ int increase = 1;
+ struct timeval tv;
+
+ /* Figure out how long it's been since we started transmitting. */
+ interval = cur_time - client -> first_sending;
+
+ /* If we're past the panic timeout, call the script and tell it
+ we haven't found anything for this interface yet. */
+ if (interval > client -> config -> timeout) {
+ state_panic (client);
+ return;
+ }
+
+ /* If we're selecting media, try the whole list before doing
+ the exponential backoff, but if we've already received an
+ offer, stop looping, because we obviously have it right. */
+ if (!client -> offered_leases &&
+ client -> config -> media) {
+ int fail = 0;
+ again:
+ if (client -> medium) {
+ client -> medium = client -> medium -> next;
+ increase = 0;
+ }
+ if (!client -> medium) {
+ if (fail)
+ log_fatal ("No valid media types for %s!",
+ client -> interface -> name);
+ client -> medium =
+ client -> config -> media;
+ increase = 1;
+ }
+
+ log_info ("Trying medium \"%s\" %d",
+ client -> medium -> string, increase);
+ script_init (client, "MEDIUM", client -> medium);
+ if (script_go (client)) {
+ fail = 1;
+ goto again;
+ }
+ }
+
+ /* If we're supposed to increase the interval, do so. If it's
+ currently zero (i.e., we haven't sent any packets yet), set
+ it to initial_interval; otherwise, add to it a random number
+ between zero and two times itself. On average, this means
+ that it will double with every transmission. */
+ if (increase) {
+ if (!client->interval)
+ client->interval = client->config->initial_interval;
+ else
+ client->interval += random() % (2 * client->interval);
+
+ /* Don't backoff past cutoff. */
+ if (client->interval > client->config->backoff_cutoff)
+ client->interval = (client->config->backoff_cutoff / 2)
+ + (random() % client->config->backoff_cutoff);
+ } else if (!client->interval)
+ client->interval = client->config->initial_interval;
+
+ /* If the backoff would take us to the panic timeout, just use that
+ as the interval. */
+ if (cur_time + client -> interval >
+ client -> first_sending + client -> config -> timeout)
+ client -> interval =
+ (client -> first_sending +
+ client -> config -> timeout) - cur_time + 1;
+
+ /* Record the number of seconds since we started sending. */
+ if (interval < 65536)
+ client -> packet.secs = htons (interval);
+ else
+ client -> packet.secs = htons (65535);
+ client -> secs = client -> packet.secs;
+
+ log_info ("DHCPDISCOVER on %s to %s port %d interval %ld",
+ client -> name ? client -> name : client -> interface -> name,
+ inet_ntoa (sockaddr_broadcast.sin_addr),
+ ntohs (sockaddr_broadcast.sin_port), (long)(client -> interval));
+
+ /* Send out a packet. */
+ result = send_packet(client->interface, NULL, &client->packet,
+ client->packet_length, inaddr_any,
+ &sockaddr_broadcast, NULL);
+ if (result < 0) {
+ log_error("%s:%d: Failed to send %d byte long packet over %s "
+ "interface.", MDL, client->packet_length,
+ client->interface->name);
+ }
+
+ /*
+ * If we used 0 microseconds here, and there were other clients on the
+ * same network with a synchronized local clock (ntp), and a similar
+ * zero-microsecond-scheduler behavior, then we could be participating
+ * in a sub-second DOS ttck.
+ */
+ tv.tv_sec = cur_tv.tv_sec + client->interval;
+ tv.tv_usec = client->interval > 1 ? random() % 1000000 : cur_tv.tv_usec;
+ add_timeout(&tv, send_discover, client, 0, 0);
+}
+
+/* state_panic gets called if we haven't received any offers in a preset
+ amount of time. When this happens, we try to use existing leases that
+ haven't yet expired, and failing that, we call the client script and
+ hope it can do something. */
+
+void state_panic (cpp)
+ void *cpp;
+{
+ struct client_state *client = cpp;
+ struct client_lease *loop;
+ struct client_lease *lp;
+ struct timeval tv;
+
+ loop = lp = client -> active;
+
+ log_info ("No DHCPOFFERS received.");
+
+ /* We may not have an active lease, but we may have some
+ predefined leases that we can try. */
+ if (!client -> active && client -> leases)
+ goto activate_next;
+
+ /* Run through the list of leases and see if one can be used. */
+ while (client -> active) {
+ if (client -> active -> expiry > cur_time) {
+ log_info ("Trying recorded lease %s",
+ piaddr (client -> active -> address));
+ /* Run the client script with the existing
+ parameters. */
+ script_init (client, "TIMEOUT",
+ client -> active -> medium);
+ script_write_params (client, "new_", client -> active);
+ script_write_requested(client);
+ if (client -> alias)
+ script_write_params (client, "alias_",
+ client -> alias);
+
+ /* If the old lease is still good and doesn't
+ yet need renewal, go into BOUND state and
+ timeout at the renewal time. */
+ if (!script_go (client)) {
+ if (cur_time < client -> active -> renewal) {
+ client -> state = S_BOUND;
+ log_info ("bound: renewal in %ld %s.",
+ (long)(client -> active -> renewal -
+ cur_time), "seconds");
+ tv.tv_sec = client->active->renewal;
+ tv.tv_usec = ((client->active->renewal -
+ cur_time) > 1) ?
+ random() % 1000000 :
+ cur_tv.tv_usec;
+ add_timeout(&tv, state_bound, client, 0, 0);
+ } else {
+ client -> state = S_BOUND;
+ log_info ("bound: immediate renewal.");
+ state_bound (client);
+ }
+ reinitialize_interfaces ();
+ go_daemon ();
+ return;
+ }
+ }
+
+ /* If there are no other leases, give up. */
+ if (!client -> leases) {
+ client -> leases = client -> active;
+ client -> active = (struct client_lease *)0;
+ break;
+ }
+
+ activate_next:
+ /* Otherwise, put the active lease at the end of the
+ lease list, and try another lease.. */
+ for (lp = client -> leases; lp -> next; lp = lp -> next)
+ ;
+ lp -> next = client -> active;
+ if (lp -> next) {
+ lp -> next -> next = (struct client_lease *)0;
+ }
+ client -> active = client -> leases;
+ client -> leases = client -> leases -> next;
+
+ /* If we already tried this lease, we've exhausted the
+ set of leases, so we might as well give up for
+ now. */
+ if (client -> active == loop)
+ break;
+ else if (!loop)
+ loop = client -> active;
+ }
+
+ /* No leases were available, or what was available didn't work, so
+ tell the shell script that we failed to allocate an address,
+ and try again later. */
+ if (onetry) {
+ if (!quiet)
+ log_info ("Unable to obtain a lease on first try.%s",
+ " Exiting.");
+ exit (2);
+ }
+
+ log_info ("No working leases in persistent database - sleeping.");
+ script_init (client, "FAIL", (struct string_list *)0);
+ if (client -> alias)
+ script_write_params (client, "alias_", client -> alias);
+ script_go (client);
+ client -> state = S_INIT;
+ tv.tv_sec = cur_tv.tv_sec + ((client->config->retry_interval + 1) / 2 +
+ (random() % client->config->retry_interval));
+ tv.tv_usec = ((tv.tv_sec - cur_tv.tv_sec) > 1) ?
+ random() % 1000000 : cur_tv.tv_usec;
+ add_timeout(&tv, state_init, client, 0, 0);
+ go_daemon ();
+}
+
+void send_request (cpp)
+ void *cpp;
+{
+ struct client_state *client = cpp;
+
+ int result;
+ int interval;
+ struct sockaddr_in destination;
+ struct in_addr from;
+ struct timeval tv;
+
+ /* Figure out how long it's been since we started transmitting. */
+ interval = cur_time - client -> first_sending;
+
+ /* If we're in the INIT-REBOOT or REQUESTING state and we're
+ past the reboot timeout, go to INIT and see if we can
+ DISCOVER an address... */
+ /* XXX In the INIT-REBOOT state, if we don't get an ACK, it
+ means either that we're on a network with no DHCP server,
+ or that our server is down. In the latter case, assuming
+ that there is a backup DHCP server, DHCPDISCOVER will get
+ us a new address, but we could also have successfully
+ reused our old address. In the former case, we're hosed
+ anyway. This is not a win-prone situation. */
+ if ((client -> state == S_REBOOTING ||
+ client -> state == S_REQUESTING) &&
+ interval > client -> config -> reboot_timeout) {
+ cancel:
+ client -> state = S_INIT;
+ cancel_timeout (send_request, client);
+ state_init (client);
+ return;
+ }
+
+ /* If we're in the reboot state, make sure the media is set up
+ correctly. */
+ if (client -> state == S_REBOOTING &&
+ !client -> medium &&
+ client -> active -> medium ) {
+ script_init (client, "MEDIUM", client -> active -> medium);
+
+ /* If the medium we chose won't fly, go to INIT state. */
+ if (script_go (client))
+ goto cancel;
+
+ /* Record the medium. */
+ client -> medium = client -> active -> medium;
+ }
+
+ /* If the lease has expired, relinquish the address and go back
+ to the INIT state. */
+ if (client -> state != S_REQUESTING &&
+ cur_time > client -> active -> expiry) {
+ /* Run the client script with the new parameters. */
+ script_init (client, "EXPIRE", (struct string_list *)0);
+ script_write_params (client, "old_", client -> active);
+ script_write_requested(client);
+ if (client -> alias)
+ script_write_params (client, "alias_",
+ client -> alias);
+ script_go (client);
+
+ /* Now do a preinit on the interface so that we can
+ discover a new address. */
+ script_init (client, "PREINIT", (struct string_list *)0);
+ if (client -> alias)
+ script_write_params (client, "alias_",
+ client -> alias);
+ script_go (client);
+
+ client -> state = S_INIT;
+ state_init (client);
+ return;
+ }
+
+ /* Do the exponential backoff... */
+ if (!client -> interval)
+ client -> interval = client -> config -> initial_interval;
+ else {
+ client -> interval += ((random () >> 2) %
+ (2 * client -> interval));
+ }
+
+ /* Don't backoff past cutoff. */
+ if (client -> interval >
+ client -> config -> backoff_cutoff)
+ client -> interval =
+ ((client -> config -> backoff_cutoff / 2)
+ + ((random () >> 2) %
+ client -> config -> backoff_cutoff));
+
+ /* If the backoff would take us to the expiry time, just set the
+ timeout to the expiry time. */
+ if (client -> state != S_REQUESTING &&
+ cur_time + client -> interval > client -> active -> expiry)
+ client -> interval =
+ client -> active -> expiry - cur_time + 1;
+
+ /* If the lease T2 time has elapsed, or if we're not yet bound,
+ broadcast the DHCPREQUEST rather than unicasting. */
+ if (client -> state == S_REQUESTING ||
+ client -> state == S_REBOOTING ||
+ cur_time > client -> active -> rebind)
+ destination.sin_addr = sockaddr_broadcast.sin_addr;
+ else
+ memcpy (&destination.sin_addr.s_addr,
+ client -> destination.iabuf,
+ sizeof destination.sin_addr.s_addr);
+ destination.sin_port = remote_port;
+ destination.sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+ destination.sin_len = sizeof destination;
+#endif
+
+ if (client -> state == S_RENEWING ||
+ client -> state == S_REBINDING)
+ memcpy (&from, client -> active -> address.iabuf,
+ sizeof from);
+ else
+ from.s_addr = INADDR_ANY;
+
+ /* Record the number of seconds since we started sending. */
+ if (client -> state == S_REQUESTING)
+ client -> packet.secs = client -> secs;
+ else {
+ if (interval < 65536)
+ client -> packet.secs = htons (interval);
+ else
+ client -> packet.secs = htons (65535);
+ }
+
+ log_info ("DHCPREQUEST on %s to %s port %d",
+ client -> name ? client -> name : client -> interface -> name,
+ inet_ntoa (destination.sin_addr),
+ ntohs (destination.sin_port));
+
+ if (destination.sin_addr.s_addr != INADDR_BROADCAST &&
+ fallback_interface) {
+ result = send_packet(fallback_interface, NULL, &client->packet,
+ client->packet_length, from, &destination,
+ NULL);
+ if (result < 0) {
+ log_error("%s:%d: Failed to send %d byte long packet "
+ "over %s interface.", MDL,
+ client->packet_length,
+ fallback_interface->name);
+ }
+ }
+ else {
+ /* Send out a packet. */
+ result = send_packet(client->interface, NULL, &client->packet,
+ client->packet_length, from, &destination,
+ NULL);
+ if (result < 0) {
+ log_error("%s:%d: Failed to send %d byte long packet"
+ " over %s interface.", MDL,
+ client->packet_length,
+ client->interface->name);
+ }
+ }
+
+ tv.tv_sec = cur_tv.tv_sec + client->interval;
+ tv.tv_usec = ((tv.tv_sec - cur_tv.tv_sec) > 1) ?
+ random() % 1000000 : cur_tv.tv_usec;
+ add_timeout(&tv, send_request, client, 0, 0);
+}
+
+void send_decline (cpp)
+ void *cpp;
+{
+ struct client_state *client = cpp;
+
+ int result;
+
+ log_info ("DHCPDECLINE on %s to %s port %d",
+ client->name ? client->name : client->interface->name,
+ inet_ntoa(sockaddr_broadcast.sin_addr),
+ ntohs(sockaddr_broadcast.sin_port));
+
+ /* Send out a packet. */
+ result = send_packet(client->interface, NULL, &client->packet,
+ client->packet_length, inaddr_any,
+ &sockaddr_broadcast, NULL);
+ if (result < 0) {
+ log_error("%s:%d: Failed to send %d byte long packet over %s"
+ " interface.", MDL, client->packet_length,
+ client->interface->name);
+ }
+}
+
+void send_release (cpp)
+ void *cpp;
+{
+ struct client_state *client = cpp;
+
+ int result;
+ struct sockaddr_in destination;
+ struct in_addr from;
+
+ memcpy (&from, client -> active -> address.iabuf,
+ sizeof from);
+ memcpy (&destination.sin_addr.s_addr,
+ client -> destination.iabuf,
+ sizeof destination.sin_addr.s_addr);
+ destination.sin_port = remote_port;
+ destination.sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+ destination.sin_len = sizeof destination;
+#endif
+
+ /* Set the lease to end now, so that we don't accidentally
+ reuse it if we restart before the old expiry time. */
+ client -> active -> expiry =
+ client -> active -> renewal =
+ client -> active -> rebind = cur_time;
+ if (!write_client_lease (client, client -> active, 1, 1)) {
+ log_error ("Can't release lease: lease write failed.");
+ return;
+ }
+
+ log_info ("DHCPRELEASE on %s to %s port %d",
+ client -> name ? client -> name : client -> interface -> name,
+ inet_ntoa (destination.sin_addr),
+ ntohs (destination.sin_port));
+
+ if (fallback_interface) {
+ result = send_packet(fallback_interface, NULL, &client->packet,
+ client->packet_length, from, &destination,
+ NULL);
+ if (result < 0) {
+ log_error("%s:%d: Failed to send %d byte long packet"
+ " over %s interface.", MDL,
+ client->packet_length,
+ fallback_interface->name);
+ }
+ } else {
+ /* Send out a packet. */
+ result = send_packet(client->interface, NULL, &client->packet,
+ client->packet_length, from, &destination,
+ NULL);
+ if (result < 0) {
+ log_error ("%s:%d: Failed to send %d byte long packet"
+ " over %s interface.", MDL,
+ client->packet_length,
+ client->interface->name);
+ }
+
+ }
+}
+
+void
+make_client_options(struct client_state *client, struct client_lease *lease,
+ u_int8_t *type, struct option_cache *sid,
+ struct iaddr *rip, struct option **prl,
+ struct option_state **op)
+{
+ unsigned i;
+ struct option_cache *oc;
+ struct option *option = NULL;
+ struct buffer *bp = (struct buffer *)0;
+
+ /* If there are any leftover options, get rid of them. */
+ if (*op)
+ option_state_dereference (op, MDL);
+
+ /* Allocate space for options. */
+ option_state_allocate (op, MDL);
+
+ /* Send the server identifier if provided. */
+ if (sid)
+ save_option (&dhcp_universe, *op, sid);
+
+ oc = (struct option_cache *)0;
+
+ /* Send the requested address if provided. */
+ if (rip) {
+ client -> requested_address = *rip;
+ i = DHO_DHCP_REQUESTED_ADDRESS;
+ if (!(option_code_hash_lookup(&option, dhcp_universe.code_hash,
+ &i, 0, MDL) &&
+ make_const_option_cache(&oc, NULL, rip->iabuf, rip->len,
+ option, MDL)))
+ log_error ("can't make requested address cache.");
+ else {
+ save_option (&dhcp_universe, *op, oc);
+ option_cache_dereference (&oc, MDL);
+ }
+ option_dereference(&option, MDL);
+ } else {
+ client -> requested_address.len = 0;
+ }
+
+ i = DHO_DHCP_MESSAGE_TYPE;
+ if (!(option_code_hash_lookup(&option, dhcp_universe.code_hash, &i, 0,
+ MDL) &&
+ make_const_option_cache(&oc, NULL, type, 1, option, MDL)))
+ log_error ("can't make message type.");
+ else {
+ save_option (&dhcp_universe, *op, oc);
+ option_cache_dereference (&oc, MDL);
+ }
+ option_dereference(&option, MDL);
+
+ if (prl) {
+ int len;
+
+ /* Probe the length of the list. */
+ len = 0;
+ for (i = 0 ; prl[i] != NULL ; i++)
+ if (prl[i]->universe == &dhcp_universe)
+ len++;
+
+ if (!buffer_allocate (&bp, len, MDL))
+ log_error ("can't make parameter list buffer.");
+ else {
+ unsigned code = DHO_DHCP_PARAMETER_REQUEST_LIST;
+
+ len = 0;
+ for (i = 0 ; prl[i] != NULL ; i++)
+ if (prl[i]->universe == &dhcp_universe)
+ bp->data[len++] = prl[i]->code;
+
+ if (!(option_code_hash_lookup(&option,
+ dhcp_universe.code_hash,
+ &code, 0, MDL) &&
+ make_const_option_cache(&oc, &bp, NULL, len,
+ option, MDL))) {
+ if (bp != NULL)
+ buffer_dereference(&bp, MDL);
+ log_error ("can't make option cache");
+ } else {
+ save_option (&dhcp_universe, *op, oc);
+ option_cache_dereference (&oc, MDL);
+ }
+ option_dereference(&option, MDL);
+ }
+ }
+
+ /* Run statements that need to be run on transmission. */
+ if (client -> config -> on_transmission)
+ execute_statements_in_scope
+ ((struct binding_value **)0,
+ (struct packet *)0, (struct lease *)0, client,
+ (lease ? lease -> options : (struct option_state *)0),
+ *op, &global_scope,
+ client -> config -> on_transmission,
+ (struct group *)0);
+}
+
+void make_discover (client, lease)
+ struct client_state *client;
+ struct client_lease *lease;
+{
+ unsigned char discover = DHCPDISCOVER;
+ struct option_state *options = (struct option_state *)0;
+
+ memset (&client -> packet, 0, sizeof (client -> packet));
+
+ make_client_options (client,
+ lease, &discover, (struct option_cache *)0,
+ lease ? &lease -> address : (struct iaddr *)0,
+ client -> config -> requested_options,
+ &options);
+
+ /* Set up the option buffer... */
+ client -> packet_length =
+ cons_options ((struct packet *)0, &client -> packet,
+ (struct lease *)0, client,
+ /* maximum packet size */1500,
+ (struct option_state *)0,
+ options,
+ /* scope */ &global_scope,
+ /* overload */ 0,
+ /* terminate */0,
+ /* bootpp */0,
+ (struct data_string *)0,
+ client -> config -> vendor_space_name);
+
+ option_state_dereference (&options, MDL);
+ if (client -> packet_length < BOOTP_MIN_LEN)
+ client -> packet_length = BOOTP_MIN_LEN;
+
+ client -> packet.op = BOOTREQUEST;
+ client -> packet.htype = client -> interface -> hw_address.hbuf [0];
+ client -> packet.hlen = client -> interface -> hw_address.hlen - 1;
+ client -> packet.hops = 0;
+ client -> packet.xid = random ();
+ client -> packet.secs = 0; /* filled in by send_discover. */
+
+ if (can_receive_unicast_unconfigured (client -> interface))
+ client -> packet.flags = 0;
+ else
+ client -> packet.flags = htons (BOOTP_BROADCAST);
+
+ memset (&(client -> packet.ciaddr),
+ 0, sizeof client -> packet.ciaddr);
+ memset (&(client -> packet.yiaddr),
+ 0, sizeof client -> packet.yiaddr);
+ memset (&(client -> packet.siaddr),
+ 0, sizeof client -> packet.siaddr);
+ client -> packet.giaddr = giaddr;
+ if (client -> interface -> hw_address.hlen > 0)
+ memcpy (client -> packet.chaddr,
+ &client -> interface -> hw_address.hbuf [1],
+ (unsigned)(client -> interface -> hw_address.hlen - 1));
+
+#ifdef DEBUG_PACKET
+ dump_raw ((unsigned char *)&client -> packet, client -> packet_length);
+#endif
+}
+
+
+void make_request (client, lease)
+ struct client_state *client;
+ struct client_lease *lease;
+{
+ unsigned char request = DHCPREQUEST;
+ struct option_cache *oc;
+
+ memset (&client -> packet, 0, sizeof (client -> packet));
+
+ if (client -> state == S_REQUESTING)
+ oc = lookup_option (&dhcp_universe, lease -> options,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ else
+ oc = (struct option_cache *)0;
+
+ if (client -> sent_options)
+ option_state_dereference (&client -> sent_options, MDL);
+
+ make_client_options (client, lease, &request, oc,
+ ((client -> state == S_REQUESTING ||
+ client -> state == S_REBOOTING)
+ ? &lease -> address
+ : (struct iaddr *)0),
+ client -> config -> requested_options,
+ &client -> sent_options);
+
+ /* Set up the option buffer... */
+ client -> packet_length =
+ cons_options ((struct packet *)0, &client -> packet,
+ (struct lease *)0, client,
+ /* maximum packet size */1500,
+ (struct option_state *)0,
+ client -> sent_options,
+ /* scope */ &global_scope,
+ /* overload */ 0,
+ /* terminate */0,
+ /* bootpp */0,
+ (struct data_string *)0,
+ client -> config -> vendor_space_name);
+
+ if (client -> packet_length < BOOTP_MIN_LEN)
+ client -> packet_length = BOOTP_MIN_LEN;
+
+ client -> packet.op = BOOTREQUEST;
+ client -> packet.htype = client -> interface -> hw_address.hbuf [0];
+ client -> packet.hlen = client -> interface -> hw_address.hlen - 1;
+ client -> packet.hops = 0;
+ client -> packet.xid = client -> xid;
+ client -> packet.secs = 0; /* Filled in by send_request. */
+
+ /* If we own the address we're requesting, put it in ciaddr;
+ otherwise set ciaddr to zero. */
+ if (client -> state == S_BOUND ||
+ client -> state == S_RENEWING ||
+ client -> state == S_REBINDING) {
+ memcpy (&client -> packet.ciaddr,
+ lease -> address.iabuf, lease -> address.len);
+ client -> packet.flags = 0;
+ } else {
+ memset (&client -> packet.ciaddr, 0,
+ sizeof client -> packet.ciaddr);
+ if (can_receive_unicast_unconfigured (client -> interface))
+ client -> packet.flags = 0;
+ else
+ client -> packet.flags = htons (BOOTP_BROADCAST);
+ }
+
+ memset (&client -> packet.yiaddr, 0,
+ sizeof client -> packet.yiaddr);
+ memset (&client -> packet.siaddr, 0,
+ sizeof client -> packet.siaddr);
+ if (client -> state != S_BOUND &&
+ client -> state != S_RENEWING)
+ client -> packet.giaddr = giaddr;
+ else
+ memset (&client -> packet.giaddr, 0,
+ sizeof client -> packet.giaddr);
+ if (client -> interface -> hw_address.hlen > 0)
+ memcpy (client -> packet.chaddr,
+ &client -> interface -> hw_address.hbuf [1],
+ (unsigned)(client -> interface -> hw_address.hlen - 1));
+
+#ifdef DEBUG_PACKET
+ dump_raw ((unsigned char *)&client -> packet, client -> packet_length);
+#endif
+}
+
+void make_decline (client, lease)
+ struct client_state *client;
+ struct client_lease *lease;
+{
+ unsigned char decline = DHCPDECLINE;
+ struct option_cache *oc;
+
+ struct option_state *options = (struct option_state *)0;
+
+ /* Create the options cache. */
+ oc = lookup_option (&dhcp_universe, lease -> options,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ make_client_options(client, lease, &decline, oc, &lease->address,
+ NULL, &options);
+
+ /* Consume the options cache into the option buffer. */
+ memset (&client -> packet, 0, sizeof (client -> packet));
+ client -> packet_length =
+ cons_options ((struct packet *)0, &client -> packet,
+ (struct lease *)0, client, 0,
+ (struct option_state *)0, options,
+ &global_scope, 0, 0, 0, (struct data_string *)0,
+ client -> config -> vendor_space_name);
+
+ /* Destroy the options cache. */
+ option_state_dereference (&options, MDL);
+
+ if (client -> packet_length < BOOTP_MIN_LEN)
+ client -> packet_length = BOOTP_MIN_LEN;
+
+ client -> packet.op = BOOTREQUEST;
+ client -> packet.htype = client -> interface -> hw_address.hbuf [0];
+ client -> packet.hlen = client -> interface -> hw_address.hlen - 1;
+ client -> packet.hops = 0;
+ client -> packet.xid = client -> xid;
+ client -> packet.secs = 0; /* Filled in by send_request. */
+ if (can_receive_unicast_unconfigured (client -> interface))
+ client -> packet.flags = 0;
+ else
+ client -> packet.flags = htons (BOOTP_BROADCAST);
+
+ /* ciaddr must always be zero. */
+ memset (&client -> packet.ciaddr, 0,
+ sizeof client -> packet.ciaddr);
+ memset (&client -> packet.yiaddr, 0,
+ sizeof client -> packet.yiaddr);
+ memset (&client -> packet.siaddr, 0,
+ sizeof client -> packet.siaddr);
+ client -> packet.giaddr = giaddr;
+ memcpy (client -> packet.chaddr,
+ &client -> interface -> hw_address.hbuf [1],
+ client -> interface -> hw_address.hlen);
+
+#ifdef DEBUG_PACKET
+ dump_raw ((unsigned char *)&client -> packet, client -> packet_length);
+#endif
+}
+
+void make_release (client, lease)
+ struct client_state *client;
+ struct client_lease *lease;
+{
+ unsigned char request = DHCPRELEASE;
+ struct option_cache *oc;
+
+ struct option_state *options = (struct option_state *)0;
+
+ memset (&client -> packet, 0, sizeof (client -> packet));
+
+ oc = lookup_option (&dhcp_universe, lease -> options,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ make_client_options(client, lease, &request, oc, NULL, NULL, &options);
+
+ /* Set up the option buffer... */
+ client -> packet_length =
+ cons_options ((struct packet *)0, &client -> packet,
+ (struct lease *)0, client,
+ /* maximum packet size */1500,
+ (struct option_state *)0,
+ options,
+ /* scope */ &global_scope,
+ /* overload */ 0,
+ /* terminate */0,
+ /* bootpp */0,
+ (struct data_string *)0,
+ client -> config -> vendor_space_name);
+
+ if (client -> packet_length < BOOTP_MIN_LEN)
+ client -> packet_length = BOOTP_MIN_LEN;
+ option_state_dereference (&options, MDL);
+
+ client -> packet.op = BOOTREQUEST;
+ client -> packet.htype = client -> interface -> hw_address.hbuf [0];
+ client -> packet.hlen = client -> interface -> hw_address.hlen - 1;
+ client -> packet.hops = 0;
+ client -> packet.xid = random ();
+ client -> packet.secs = 0;
+ client -> packet.flags = 0;
+ memcpy (&client -> packet.ciaddr,
+ lease -> address.iabuf, lease -> address.len);
+ memset (&client -> packet.yiaddr, 0,
+ sizeof client -> packet.yiaddr);
+ memset (&client -> packet.siaddr, 0,
+ sizeof client -> packet.siaddr);
+ client -> packet.giaddr = giaddr;
+ memcpy (client -> packet.chaddr,
+ &client -> interface -> hw_address.hbuf [1],
+ client -> interface -> hw_address.hlen);
+
+#ifdef DEBUG_PACKET
+ dump_raw ((unsigned char *)&client -> packet, client -> packet_length);
+#endif
+}
+
+void destroy_client_lease (lease)
+ struct client_lease *lease;
+{
+ if (lease -> server_name)
+ dfree (lease -> server_name, MDL);
+ if (lease -> filename)
+ dfree (lease -> filename, MDL);
+ option_state_dereference (&lease -> options, MDL);
+ free_client_lease (lease, MDL);
+}
+
+FILE *leaseFile = NULL;
+int leases_written = 0;
+
+void rewrite_client_leases ()
+{
+ struct interface_info *ip;
+ struct client_state *client;
+ struct client_lease *lp;
+
+ if (leaseFile != NULL)
+ fclose (leaseFile);
+ leaseFile = fopen (path_dhclient_db, "w");
+ if (leaseFile == NULL) {
+ log_error ("can't create %s: %m", path_dhclient_db);
+ return;
+ }
+
+ /* If there is a default duid, write it out. */
+ if (default_duid.len != 0)
+ write_duid(&default_duid);
+
+ /* Write out all the leases attached to configured interfaces that
+ we know about. */
+ for (ip = interfaces; ip; ip = ip -> next) {
+ for (client = ip -> client; client; client = client -> next) {
+ for (lp = client -> leases; lp; lp = lp -> next) {
+ write_client_lease (client, lp, 1, 0);
+ }
+ if (client -> active)
+ write_client_lease (client,
+ client -> active, 1, 0);
+
+ if (client->active_lease != NULL)
+ write_client6_lease(client,
+ client->active_lease,
+ 1, 0);
+
+ /* Reset last_write after rewrites. */
+ client->last_write = 0;
+ }
+ }
+
+ /* Write out any leases that are attached to interfaces that aren't
+ currently configured. */
+ for (ip = dummy_interfaces; ip; ip = ip -> next) {
+ for (client = ip -> client; client; client = client -> next) {
+ for (lp = client -> leases; lp; lp = lp -> next) {
+ write_client_lease (client, lp, 1, 0);
+ }
+ if (client -> active)
+ write_client_lease (client,
+ client -> active, 1, 0);
+
+ if (client->active_lease != NULL)
+ write_client6_lease(client,
+ client->active_lease,
+ 1, 0);
+
+ /* Reset last_write after rewrites. */
+ client->last_write = 0;
+ }
+ }
+ fflush (leaseFile);
+}
+
+void write_lease_option (struct option_cache *oc,
+ struct packet *packet, struct lease *lease,
+ struct client_state *client_state,
+ struct option_state *in_options,
+ struct option_state *cfg_options,
+ struct binding_scope **scope,
+ struct universe *u, void *stuff)
+{
+ const char *name, *dot;
+ struct data_string ds;
+ char *preamble = stuff;
+
+ memset (&ds, 0, sizeof ds);
+
+ if (u != &dhcp_universe) {
+ name = u -> name;
+ dot = ".";
+ } else {
+ name = "";
+ dot = "";
+ }
+ if (evaluate_option_cache (&ds, packet, lease, client_state,
+ in_options, cfg_options, scope, oc, MDL)) {
+ /* The option name */
+ fprintf(leaseFile, "%soption %s%s%s", preamble,
+ name, dot, oc->option->name);
+
+ /* The option value if there is one */
+ if ((oc->option->format == NULL) ||
+ (oc->option->format[0] != 'Z')) {
+ fprintf(leaseFile, " %s",
+ pretty_print_option(oc->option, ds.data,
+ ds.len, 1, 1));
+ }
+
+ /* The closing semi-colon and newline */
+ fprintf(leaseFile, ";\n");
+
+ data_string_forget (&ds, MDL);
+ }
+}
+
+/* Write an option cache to the lease store. */
+static void
+write_options(struct client_state *client, struct option_state *options,
+ const char *preamble)
+{
+ int i;
+
+ for (i = 0; i < options->universe_count; i++) {
+ option_space_foreach(NULL, NULL, client, NULL, options,
+ &global_scope, universes[i],
+ (char *)preamble, write_lease_option);
+ }
+}
+
+/* Write the default DUID to the lease store. */
+static isc_result_t
+write_duid(struct data_string *duid)
+{
+ char *str;
+ int stat;
+
+ if ((duid == NULL) || (duid->len <= 2))
+ return DHCP_R_INVALIDARG;
+
+ if (leaseFile == NULL) { /* XXX? */
+ leaseFile = fopen(path_dhclient_db, "w");
+ if (leaseFile == NULL) {
+ log_error("can't create %s: %m", path_dhclient_db);
+ return ISC_R_IOERROR;
+ }
+ }
+
+ /* It would make more sense to write this as a hex string,
+ * but our function to do that (print_hex_n) uses a fixed
+ * length buffer...and we can't guarantee a duid would be
+ * less than the fixed length.
+ */
+ str = quotify_buf(duid->data, duid->len, MDL);
+ if (str == NULL)
+ return ISC_R_NOMEMORY;
+
+ stat = fprintf(leaseFile, "default-duid \"%s\";\n", str);
+ dfree(str, MDL);
+ if (stat <= 0)
+ return ISC_R_IOERROR;
+
+ if (fflush(leaseFile) != 0)
+ return ISC_R_IOERROR;
+
+ return ISC_R_SUCCESS;
+}
+
+/* Write a DHCPv6 lease to the store. */
+isc_result_t
+write_client6_lease(struct client_state *client, struct dhc6_lease *lease,
+ int rewrite, int sync)
+{
+ struct dhc6_ia *ia;
+ struct dhc6_addr *addr;
+ int stat;
+ const char *ianame;
+
+ /* This should include the current lease. */
+ if (!rewrite && (leases_written++ > 20)) {
+ rewrite_client_leases();
+ leases_written = 0;
+ return ISC_R_SUCCESS;
+ }
+
+ if (client == NULL || lease == NULL)
+ return DHCP_R_INVALIDARG;
+
+ if (leaseFile == NULL) { /* XXX? */
+ leaseFile = fopen(path_dhclient_db, "w");
+ if (leaseFile == NULL) {
+ log_error("can't create %s: %m", path_dhclient_db);
+ return ISC_R_IOERROR;
+ }
+ }
+
+ stat = fprintf(leaseFile, "lease6 {\n");
+ if (stat <= 0)
+ return ISC_R_IOERROR;
+
+ stat = fprintf(leaseFile, " interface \"%s\";\n",
+ client->interface->name);
+ if (stat <= 0)
+ return ISC_R_IOERROR;
+
+ for (ia = lease->bindings ; ia != NULL ; ia = ia->next) {
+ switch (ia->ia_type) {
+ case D6O_IA_NA:
+ default:
+ ianame = "ia-na";
+ break;
+ case D6O_IA_TA:
+ ianame = "ia-ta";
+ break;
+ case D6O_IA_PD:
+ ianame = "ia-pd";
+ break;
+ }
+ stat = fprintf(leaseFile, " %s %s {\n",
+ ianame, print_hex_1(4, ia->iaid, 12));
+ if (stat <= 0)
+ return ISC_R_IOERROR;
+
+ if (ia->ia_type != D6O_IA_TA)
+ stat = fprintf(leaseFile, " starts %d;\n"
+ " renew %u;\n"
+ " rebind %u;\n",
+ (int)ia->starts, ia->renew, ia->rebind);
+ else
+ stat = fprintf(leaseFile, " starts %d;\n",
+ (int)ia->starts);
+ if (stat <= 0)
+ return ISC_R_IOERROR;
+
+ for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
+ if (ia->ia_type != D6O_IA_PD)
+ stat = fprintf(leaseFile,
+ " iaaddr %s {\n",
+ piaddr(addr->address));
+ else
+ stat = fprintf(leaseFile,
+ " iaprefix %s/%d {\n",
+ piaddr(addr->address),
+ (int)addr->plen);
+ if (stat <= 0)
+ return ISC_R_IOERROR;
+
+ stat = fprintf(leaseFile, " starts %d;\n"
+ " preferred-life %u;\n"
+ " max-life %u;\n",
+ (int)addr->starts, addr->preferred_life,
+ addr->max_life);
+ if (stat <= 0)
+ return ISC_R_IOERROR;
+
+ if (addr->options != NULL)
+ write_options(client, addr->options, " ");
+
+ stat = fprintf(leaseFile, " }\n");
+ if (stat <= 0)
+ return ISC_R_IOERROR;
+ }
+
+ if (ia->options != NULL)
+ write_options(client, ia->options, " ");
+
+ stat = fprintf(leaseFile, " }\n");
+ if (stat <= 0)
+ return ISC_R_IOERROR;
+ }
+
+ if (lease->released) {
+ stat = fprintf(leaseFile, " released;\n");
+ if (stat <= 0)
+ return ISC_R_IOERROR;
+ }
+
+ if (lease->options != NULL)
+ write_options(client, lease->options, " ");
+
+ stat = fprintf(leaseFile, "}\n");
+ if (stat <= 0)
+ return ISC_R_IOERROR;
+
+ if (fflush(leaseFile) != 0)
+ return ISC_R_IOERROR;
+
+ if (sync) {
+ if (fsync(fileno(leaseFile)) < 0) {
+ log_error("write_client_lease: fsync(): %m");
+ return ISC_R_IOERROR;
+ }
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+int write_client_lease (client, lease, rewrite, makesure)
+ struct client_state *client;
+ struct client_lease *lease;
+ int rewrite;
+ int makesure;
+{
+ struct data_string ds;
+ int errors = 0;
+ char *s;
+ const char *tval;
+
+ if (!rewrite) {
+ if (leases_written++ > 20) {
+ rewrite_client_leases ();
+ leases_written = 0;
+ }
+ }
+
+ /* If the lease came from the config file, we don't need to stash
+ a copy in the lease database. */
+ if (lease -> is_static)
+ return 1;
+
+ if (leaseFile == NULL) { /* XXX */
+ leaseFile = fopen (path_dhclient_db, "w");
+ if (leaseFile == NULL) {
+ log_error ("can't create %s: %m", path_dhclient_db);
+ return 0;
+ }
+ }
+
+ errno = 0;
+ fprintf (leaseFile, "lease {\n");
+ if (lease -> is_bootp) {
+ fprintf (leaseFile, " bootp;\n");
+ if (errno) {
+ ++errors;
+ errno = 0;
+ }
+ }
+ fprintf (leaseFile, " interface \"%s\";\n",
+ client -> interface -> name);
+ if (errno) {
+ ++errors;
+ errno = 0;
+ }
+ if (client -> name) {
+ fprintf (leaseFile, " name \"%s\";\n", client -> name);
+ if (errno) {
+ ++errors;
+ errno = 0;
+ }
+ }
+ fprintf (leaseFile, " fixed-address %s;\n",
+ piaddr (lease -> address));
+ if (errno) {
+ ++errors;
+ errno = 0;
+ }
+ if (lease -> filename) {
+ s = quotify_string (lease -> filename, MDL);
+ if (s) {
+ fprintf (leaseFile, " filename \"%s\";\n", s);
+ if (errno) {
+ ++errors;
+ errno = 0;
+ }
+ dfree (s, MDL);
+ } else
+ errors++;
+
+ }
+ if (lease->server_name != NULL) {
+ s = quotify_string(lease->server_name, MDL);
+ if (s != NULL) {
+ fprintf(leaseFile, " server-name \"%s\";\n", s);
+ if (errno) {
+ ++errors;
+ errno = 0;
+ }
+ dfree(s, MDL);
+ } else
+ ++errors;
+ }
+ if (lease -> medium) {
+ s = quotify_string (lease -> medium -> string, MDL);
+ if (s) {
+ fprintf (leaseFile, " medium \"%s\";\n", s);
+ if (errno) {
+ ++errors;
+ errno = 0;
+ }
+ dfree (s, MDL);
+ } else
+ errors++;
+ }
+ if (errno != 0) {
+ errors++;
+ errno = 0;
+ }
+
+ memset (&ds, 0, sizeof ds);
+
+ write_options(client, lease->options, " ");
+
+ tval = print_time(lease->renewal);
+ if (tval == NULL ||
+ fprintf(leaseFile, " renew %s\n", tval) < 0)
+ errors++;
+
+ tval = print_time(lease->rebind);
+ if (tval == NULL ||
+ fprintf(leaseFile, " rebind %s\n", tval) < 0)
+ errors++;
+
+ tval = print_time(lease->expiry);
+ if (tval == NULL ||
+ fprintf(leaseFile, " expire %s\n", tval) < 0)
+ errors++;
+
+ if (fprintf(leaseFile, "}\n") < 0)
+ errors++;
+
+ if (fflush(leaseFile) != 0)
+ errors++;
+
+ client->last_write = cur_time;
+
+ if (!errors && makesure) {
+ if (fsync (fileno (leaseFile)) < 0) {
+ log_info ("write_client_lease: %m");
+ return 0;
+ }
+ }
+
+ return errors ? 0 : 1;
+}
+
+/* Variables holding name of script and file pointer for writing to
+ script. Needless to say, this is not reentrant - only one script
+ can be invoked at a time. */
+char scriptName [256];
+FILE *scriptFile;
+
+void script_init (client, reason, medium)
+ struct client_state *client;
+ const char *reason;
+ struct string_list *medium;
+{
+ struct string_list *sl, *next;
+
+ if (client) {
+ for (sl = client -> env; sl; sl = next) {
+ next = sl -> next;
+ dfree (sl, MDL);
+ }
+ client -> env = (struct string_list *)0;
+ client -> envc = 0;
+
+ if (client -> interface) {
+ client_envadd (client, "", "interface", "%s",
+ client -> interface -> name);
+ }
+ if (client -> name)
+ client_envadd (client,
+ "", "client", "%s", client -> name);
+ if (medium)
+ client_envadd (client,
+ "", "medium", "%s", medium -> string);
+
+ client_envadd (client, "", "reason", "%s", reason);
+ client_envadd (client, "", "pid", "%ld", (long int)getpid ());
+ }
+}
+
+void client_option_envadd (struct option_cache *oc,
+ struct packet *packet, struct lease *lease,
+ struct client_state *client_state,
+ struct option_state *in_options,
+ struct option_state *cfg_options,
+ struct binding_scope **scope,
+ struct universe *u, void *stuff)
+{
+ struct envadd_state *es = stuff;
+ struct data_string data;
+ memset (&data, 0, sizeof data);
+
+ if (evaluate_option_cache (&data, packet, lease, client_state,
+ in_options, cfg_options, scope, oc, MDL)) {
+ if (data.len) {
+ char name [256];
+ if (dhcp_option_ev_name (name, sizeof name,
+ oc->option)) {
+ const char *value;
+ size_t length;
+ value = pretty_print_option(oc->option,
+ data.data,
+ data.len, 0, 0);
+ length = strlen(value);
+
+ if (check_option_values(oc->option->universe,
+ oc->option->code,
+ value, length) == 0) {
+ client_envadd(es->client, es->prefix,
+ name, "%s", value);
+ } else {
+ log_error("suspect value in %s "
+ "option - discarded",
+ name);
+ }
+ data_string_forget (&data, MDL);
+ }
+ }
+ }
+}
+
+void script_write_params (client, prefix, lease)
+ struct client_state *client;
+ const char *prefix;
+ struct client_lease *lease;
+{
+ int i;
+ struct data_string data;
+ struct option_cache *oc;
+ struct envadd_state es;
+
+ es.client = client;
+ es.prefix = prefix;
+
+ client_envadd (client,
+ prefix, "ip_address", "%s", piaddr (lease -> address));
+
+ /* If we've set the next server address in the lease structure
+ put it into an environment variable for the script */
+ if (lease->next_srv_addr.len != 0) {
+ client_envadd(client, prefix, "next_server", "%s",
+ piaddr(lease->next_srv_addr));
+ }
+
+ /* For the benefit of Linux (and operating systems which may
+ have similar needs), compute the network address based on
+ the supplied ip address and netmask, if provided. Also
+ compute the broadcast address (the host address all ones
+ broadcast address, not the host address all zeroes
+ broadcast address). */
+
+ memset (&data, 0, sizeof data);
+ oc = lookup_option (&dhcp_universe, lease -> options, DHO_SUBNET_MASK);
+ if (oc && evaluate_option_cache (&data, (struct packet *)0,
+ (struct lease *)0, client,
+ (struct option_state *)0,
+ lease -> options,
+ &global_scope, oc, MDL)) {
+ if (data.len > 3) {
+ struct iaddr netmask, subnet, broadcast;
+
+ /*
+ * No matter the length of the subnet-mask option,
+ * use only the first four octets. Note that
+ * subnet-mask options longer than 4 octets are not
+ * in conformance with RFC 2132, but servers with this
+ * flaw do exist.
+ */
+ memcpy(netmask.iabuf, data.data, 4);
+ netmask.len = 4;
+ data_string_forget (&data, MDL);
+
+ subnet = subnet_number (lease -> address, netmask);
+ if (subnet.len) {
+ client_envadd (client, prefix, "network_number",
+ "%s", piaddr (subnet));
+
+ oc = lookup_option (&dhcp_universe,
+ lease -> options,
+ DHO_BROADCAST_ADDRESS);
+ if (!oc ||
+ !(evaluate_option_cache
+ (&data, (struct packet *)0,
+ (struct lease *)0, client,
+ (struct option_state *)0,
+ lease -> options,
+ &global_scope, oc, MDL))) {
+ broadcast = broadcast_addr (subnet, netmask);
+ if (broadcast.len) {
+ client_envadd (client,
+ prefix, "broadcast_address",
+ "%s", piaddr (broadcast));
+ }
+ }
+ }
+ }
+ data_string_forget (&data, MDL);
+ }
+
+ if (lease->filename) {
+ if (check_option_values(NULL, DHO_ROOT_PATH,
+ lease->filename,
+ strlen(lease->filename)) == 0) {
+ client_envadd(client, prefix, "filename",
+ "%s", lease->filename);
+ } else {
+ log_error("suspect value in %s "
+ "option - discarded",
+ lease->filename);
+ }
+ }
+
+ if (lease->server_name) {
+ if (check_option_values(NULL, DHO_HOST_NAME,
+ lease->server_name,
+ strlen(lease->server_name)) == 0 ) {
+ client_envadd (client, prefix, "server_name",
+ "%s", lease->server_name);
+ } else {
+ log_error("suspect value in %s "
+ "option - discarded",
+ lease->server_name);
+ }
+ }
+
+ for (i = 0; i < lease -> options -> universe_count; i++) {
+ option_space_foreach ((struct packet *)0, (struct lease *)0,
+ client, (struct option_state *)0,
+ lease -> options, &global_scope,
+ universes [i],
+ &es, client_option_envadd);
+ }
+ client_envadd (client, prefix, "expiry", "%d", (int)(lease -> expiry));
+}
+
+/*
+ * Write out the environment variables for the objects that the
+ * client requested. If the object was requested the variable will be:
+ * requested_<option_name>=1
+ * If it wasn't requested there won't be a variable.
+ */
+void script_write_requested(client)
+ struct client_state *client;
+{
+ int i;
+ struct option **req;
+ char name[256];
+ req = client->config->requested_options;
+
+ if (req == NULL)
+ return;
+
+ for (i = 0 ; req[i] != NULL ; i++) {
+ if ((req[i]->universe == &dhcp_universe) &&
+ dhcp_option_ev_name(name, sizeof(name), req[i])) {
+ client_envadd(client, "requested_", name, "%d", 1);
+ }
+ }
+}
+
+int script_go (client)
+ struct client_state *client;
+{
+ char *scriptName;
+ char *argv [2];
+ char **envp;
+ char reason [] = "REASON=NBI";
+ static char client_path [] = CLIENT_PATH;
+ int i;
+ struct string_list *sp, *next;
+ int pid, wpid, wstatus;
+
+ if (client)
+ scriptName = client -> config -> script_name;
+ else
+ scriptName = top_level_config.script_name;
+
+ envp = dmalloc (((client ? client -> envc : 2) +
+ client_env_count + 2) * sizeof (char *), MDL);
+ if (!envp) {
+ log_error ("No memory for client script environment.");
+ return 0;
+ }
+ i = 0;
+ /* Copy out the environment specified on the command line,
+ if any. */
+ for (sp = client_env; sp; sp = sp -> next) {
+ envp [i++] = sp -> string;
+ }
+ /* Copy out the environment specified by dhclient. */
+ if (client) {
+ for (sp = client -> env; sp; sp = sp -> next) {
+ envp [i++] = sp -> string;
+ }
+ } else {
+ envp [i++] = reason;
+ }
+ /* Set $PATH. */
+ envp [i++] = client_path;
+ envp [i] = (char *)0;
+
+ argv [0] = scriptName;
+ argv [1] = (char *)0;
+
+ pid = fork ();
+ if (pid < 0) {
+ log_error ("fork: %m");
+ wstatus = 0;
+ } else if (pid) {
+ do {
+ wpid = wait (&wstatus);
+ } while (wpid != pid && wpid > 0);
+ if (wpid < 0) {
+ log_error ("wait: %m");
+ wstatus = 0;
+ }
+ } else {
+ /* We don't want to pass an open file descriptor for
+ * dhclient.leases when executing dhclient-script.
+ */
+ if (leaseFile != NULL)
+ fclose(leaseFile);
+ execve (scriptName, argv, envp);
+ log_error ("execve (%s, ...): %m", scriptName);
+ exit (0);
+ }
+
+ if (client) {
+ for (sp = client -> env; sp; sp = next) {
+ next = sp -> next;
+ dfree (sp, MDL);
+ }
+ client -> env = (struct string_list *)0;
+ client -> envc = 0;
+ }
+ dfree (envp, MDL);
+ gettimeofday(&cur_tv, NULL);
+ return (WIFEXITED (wstatus) ?
+ WEXITSTATUS (wstatus) : -WTERMSIG (wstatus));
+}
+
+void client_envadd (struct client_state *client,
+ const char *prefix, const char *name, const char *fmt, ...)
+{
+ char spbuf [1024];
+ char *s;
+ unsigned len;
+ struct string_list *val;
+ va_list list;
+
+ va_start (list, fmt);
+ len = vsnprintf (spbuf, sizeof spbuf, fmt, list);
+ va_end (list);
+
+ val = dmalloc (strlen (prefix) + strlen (name) + 1 /* = */ +
+ len + sizeof *val, MDL);
+ if (!val)
+ return;
+ s = val -> string;
+ strcpy (s, prefix);
+ strcat (s, name);
+ s += strlen (s);
+ *s++ = '=';
+ if (len >= sizeof spbuf) {
+ va_start (list, fmt);
+ vsnprintf (s, len + 1, fmt, list);
+ va_end (list);
+ } else
+ strcpy (s, spbuf);
+ val -> next = client -> env;
+ client -> env = val;
+ client -> envc++;
+}
+
+int dhcp_option_ev_name (buf, buflen, option)
+ char *buf;
+ size_t buflen;
+ struct option *option;
+{
+ int i, j;
+ const char *s;
+
+ j = 0;
+ if (option -> universe != &dhcp_universe) {
+ s = option -> universe -> name;
+ i = 0;
+ } else {
+ s = option -> name;
+ i = 1;
+ }
+
+ do {
+ while (*s) {
+ if (j + 1 == buflen)
+ return 0;
+ if (*s == '-')
+ buf [j++] = '_';
+ else
+ buf [j++] = *s;
+ ++s;
+ }
+ if (!i) {
+ s = option -> name;
+ if (j + 1 == buflen)
+ return 0;
+ buf [j++] = '_';
+ }
+ ++i;
+ } while (i != 2);
+
+ buf [j] = 0;
+ return 1;
+}
+
+void go_daemon ()
+{
+ static int state = 0;
+ int pid;
+
+ /* Don't become a daemon if the user requested otherwise. */
+ if (no_daemon) {
+ write_client_pid_file ();
+ return;
+ }
+
+ /* Only do it once. */
+ if (state)
+ return;
+ state = 1;
+
+ /* Stop logging to stderr... */
+ log_perror = 0;
+
+ /* Become a daemon... */
+ if ((pid = fork ()) < 0)
+ log_fatal ("Can't fork daemon: %m");
+ else if (pid)
+ exit (0);
+ /* 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);
+
+ write_client_pid_file ();
+
+ IGNORE_RET (chdir("/"));
+}
+
+void write_client_pid_file ()
+{
+ FILE *pf;
+ int pfdesc;
+
+ /* nothing to do if the user doesn't want a pid file */
+ if (no_pid_file == ISC_TRUE) {
+ return;
+ }
+
+ pfdesc = open (path_dhclient_pid, O_CREAT | O_TRUNC | O_WRONLY, 0644);
+
+ if (pfdesc < 0) {
+ log_error ("Can't create %s: %m", path_dhclient_pid);
+ return;
+ }
+
+ pf = fdopen (pfdesc, "w");
+ if (!pf) {
+ close(pfdesc);
+ log_error ("Can't fdopen %s: %m", path_dhclient_pid);
+ } else {
+ fprintf (pf, "%ld\n", (long)getpid ());
+ fclose (pf);
+ }
+}
+
+void client_location_changed ()
+{
+ struct interface_info *ip;
+ struct client_state *client;
+
+ for (ip = interfaces; ip; ip = ip -> next) {
+ for (client = ip -> client; client; client = client -> next) {
+ switch (client -> state) {
+ case S_SELECTING:
+ cancel_timeout (send_discover, client);
+ break;
+
+ case S_BOUND:
+ cancel_timeout (state_bound, client);
+ break;
+
+ case S_REBOOTING:
+ case S_REQUESTING:
+ case S_RENEWING:
+ cancel_timeout (send_request, client);
+ break;
+
+ case S_INIT:
+ case S_REBINDING:
+ case S_STOPPED:
+ break;
+ }
+ client -> state = S_INIT;
+ state_reboot (client);
+ }
+ }
+}
+
+void do_release(client)
+ struct client_state *client;
+{
+ struct data_string ds;
+ struct option_cache *oc;
+
+ /* Pick a random xid. */
+ client -> xid = random ();
+
+ /* is there even a lease to release? */
+ if (client -> active) {
+ /* Make a DHCPRELEASE packet, and set appropriate per-interface
+ flags. */
+ make_release (client, client -> active);
+
+ memset (&ds, 0, sizeof ds);
+ oc = lookup_option (&dhcp_universe,
+ client -> active -> options,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ if (oc &&
+ evaluate_option_cache (&ds, (struct packet *)0,
+ (struct lease *)0, client,
+ (struct option_state *)0,
+ client -> active -> options,
+ &global_scope, oc, MDL)) {
+ if (ds.len > 3) {
+ memcpy (client -> destination.iabuf,
+ ds.data, 4);
+ client -> destination.len = 4;
+ } else
+ client -> destination = iaddr_broadcast;
+
+ data_string_forget (&ds, MDL);
+ } else
+ client -> destination = iaddr_broadcast;
+ client -> first_sending = cur_time;
+ client -> interval = client -> config -> initial_interval;
+
+ /* Zap the medium list... */
+ client -> medium = (struct string_list *)0;
+
+ /* Send out the first and only DHCPRELEASE packet. */
+ send_release (client);
+
+ /* Do the client script RELEASE operation. */
+ script_init (client,
+ "RELEASE", (struct string_list *)0);
+ if (client -> alias)
+ script_write_params (client, "alias_",
+ client -> alias);
+ script_write_params (client, "old_", client -> active);
+ script_write_requested(client);
+ script_go (client);
+ }
+
+ /* Cancel any timeouts. */
+ cancel_timeout (state_bound, client);
+ cancel_timeout (send_discover, client);
+ cancel_timeout (state_init, client);
+ cancel_timeout (send_request, client);
+ cancel_timeout (state_reboot, client);
+ client -> state = S_STOPPED;
+}
+
+int dhclient_interface_shutdown_hook (struct interface_info *interface)
+{
+ do_release (interface -> client);
+
+ return 1;
+}
+
+int dhclient_interface_discovery_hook (struct interface_info *tmp)
+{
+ struct interface_info *last, *ip;
+ /* See if we can find the client from dummy_interfaces */
+ last = 0;
+ for (ip = dummy_interfaces; ip; ip = ip -> next) {
+ if (!strcmp (ip -> name, tmp -> name)) {
+ /* Remove from dummy_interfaces */
+ if (last) {
+ ip = (struct interface_info *)0;
+ interface_reference (&ip, last -> next, MDL);
+ interface_dereference (&last -> next, MDL);
+ if (ip -> next) {
+ interface_reference (&last -> next,
+ ip -> next, MDL);
+ interface_dereference (&ip -> next,
+ MDL);
+ }
+ } else {
+ ip = (struct interface_info *)0;
+ interface_reference (&ip,
+ dummy_interfaces, MDL);
+ interface_dereference (&dummy_interfaces, MDL);
+ if (ip -> next) {
+ interface_reference (&dummy_interfaces,
+ ip -> next, MDL);
+ interface_dereference (&ip -> next,
+ MDL);
+ }
+ }
+ /* Copy "client" to tmp */
+ if (ip -> client) {
+ tmp -> client = ip -> client;
+ tmp -> client -> interface = tmp;
+ }
+ interface_dereference (&ip, MDL);
+ break;
+ }
+ last = ip;
+ }
+ return 1;
+}
+
+isc_result_t dhclient_interface_startup_hook (struct interface_info *interface)
+{
+ struct interface_info *ip;
+ struct client_state *client;
+
+ /* This code needs some rethinking. It doesn't test against
+ a signal name, and it just kind of bulls into doing something
+ that may or may not be appropriate. */
+
+ if (interfaces) {
+ interface_reference (&interface -> next, interfaces, MDL);
+ interface_dereference (&interfaces, MDL);
+ }
+ interface_reference (&interfaces, interface, MDL);
+
+ discover_interfaces (DISCOVER_UNCONFIGURED);
+
+ for (ip = interfaces; ip; ip = ip -> next) {
+ /* If interfaces were specified, don't configure
+ interfaces that weren't specified! */
+ if (ip -> flags & INTERFACE_RUNNING ||
+ (ip -> flags & (INTERFACE_REQUESTED |
+ INTERFACE_AUTOMATIC)) !=
+ INTERFACE_REQUESTED)
+ continue;
+ script_init (ip -> client,
+ "PREINIT", (struct string_list *)0);
+ if (ip -> client -> alias)
+ script_write_params (ip -> client, "alias_",
+ ip -> client -> alias);
+ script_go (ip -> client);
+ }
+
+ discover_interfaces (interfaces_requested != 0
+ ? DISCOVER_REQUESTED
+ : DISCOVER_RUNNING);
+
+ for (ip = interfaces; ip; ip = ip -> next) {
+ if (ip -> flags & INTERFACE_RUNNING)
+ continue;
+ ip -> flags |= INTERFACE_RUNNING;
+ for (client = ip->client ; client ; client = client->next) {
+ client->state = S_INIT;
+ state_reboot(client);
+ }
+ }
+ return ISC_R_SUCCESS;
+}
+
+/* The client should never receive a relay agent information option,
+ so if it does, log it and discard it. */
+
+int parse_agent_information_option (packet, len, data)
+ struct packet *packet;
+ int len;
+ u_int8_t *data;
+{
+ return 1;
+}
+
+/* The client never sends relay agent information options. */
+
+unsigned cons_agent_information_options (cfg_options, outpacket,
+ agentix, length)
+ struct option_state *cfg_options;
+ struct dhcp_packet *outpacket;
+ unsigned agentix;
+ unsigned length;
+{
+ return length;
+}
+
+static void shutdown_exit (void *foo)
+{
+ exit (0);
+}
+
+#if defined (NSUPDATE)
+/*
+ * 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"
+ */
+
+/* The first and second stages are pretty similar so we combine them */
+void
+client_dns_remove_action(dhcp_ddns_cb_t *ddns_cb,
+ isc_result_t eresult)
+{
+
+ isc_result_t result;
+
+ if ((eresult == ISC_R_SUCCESS) &&
+ (ddns_cb->state == DDNS_STATE_REM_FW_YXDHCID)) {
+ /* Do the second stage of the FWD removal */
+ ddns_cb->state = DDNS_STATE_REM_FW_NXRR;
+
+ result = ddns_modify_fwd(ddns_cb, MDL);
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+ }
+
+ /* If we are done or have an error clean up */
+ ddns_cb_free(ddns_cb, MDL);
+ return;
+}
+
+void
+client_dns_remove(struct client_state *client,
+ struct iaddr *addr)
+{
+ dhcp_ddns_cb_t *ddns_cb;
+ isc_result_t result;
+
+ /* if we have an old ddns request for this client, cancel it */
+ if (client->ddns_cb != NULL) {
+ ddns_cancel(client->ddns_cb, MDL);
+ client->ddns_cb = NULL;
+ }
+
+ ddns_cb = ddns_cb_alloc(MDL);
+ if (ddns_cb != NULL) {
+ ddns_cb->address = *addr;
+ ddns_cb->timeout = 0;
+
+ ddns_cb->state = DDNS_STATE_REM_FW_YXDHCID;
+ ddns_cb->flags = DDNS_UPDATE_ADDR;
+ ddns_cb->cur_func = client_dns_remove_action;
+
+ result = client_dns_update(client, ddns_cb);
+
+ if (result != ISC_R_TIMEDOUT) {
+ ddns_cb_free(ddns_cb, MDL);
+ }
+ }
+}
+#endif
+
+isc_result_t dhcp_set_control_state (control_object_state_t oldstate,
+ control_object_state_t newstate)
+{
+ struct interface_info *ip;
+ struct client_state *client;
+ struct timeval tv;
+
+ if (newstate == server_shutdown) {
+ /* Re-entry */
+ if (shutdown_signal == SIGUSR1)
+ return ISC_R_SUCCESS;
+ /* Log shutdown on signal. */
+ if ((shutdown_signal == SIGINT) ||
+ (shutdown_signal == SIGTERM)) {
+ log_info("Received signal %d, initiating shutdown.",
+ shutdown_signal);
+ }
+ /* Mark it was called. */
+ shutdown_signal = SIGUSR1;
+ }
+
+ /* Do the right thing for each interface. */
+ for (ip = interfaces; ip; ip = ip -> next) {
+ for (client = ip -> client; client; client = client -> next) {
+ switch (newstate) {
+ case server_startup:
+ return ISC_R_SUCCESS;
+
+ case server_running:
+ return ISC_R_SUCCESS;
+
+ case server_shutdown:
+ if (client -> active &&
+ client -> active -> expiry > cur_time) {
+#if defined (NSUPDATE)
+ if (client->config->do_forward_update) {
+ client_dns_remove(client,
+ &client->active->address);
+ }
+#endif
+ do_release (client);
+ }
+ break;
+
+ case server_hibernate:
+ state_stop (client);
+ break;
+
+ case server_awaken:
+ state_reboot (client);
+ break;
+ }
+ }
+ }
+
+ if (newstate == server_shutdown) {
+ tv.tv_sec = cur_tv.tv_sec;
+ tv.tv_usec = cur_tv.tv_usec + 1;
+ add_timeout(&tv, shutdown_exit, 0, 0, 0);
+ }
+ return ISC_R_SUCCESS;
+}
+
+#if defined (NSUPDATE)
+/*
+ * Called after a timeout if the DNS update failed on the previous try.
+ * Starts the retry process. If the retry times out it will schedule
+ * this routine to run again after a 10x wait.
+ */
+void
+client_dns_update_timeout (void *cp)
+{
+ dhcp_ddns_cb_t *ddns_cb = (dhcp_ddns_cb_t *)cp;
+ struct client_state *client = (struct client_state *)ddns_cb->lease;
+ isc_result_t status = ISC_R_FAILURE;
+
+ if ((client != NULL) &&
+ ((client->active != NULL) ||
+ (client->active_lease != NULL)))
+ status = client_dns_update(client, ddns_cb);
+
+ /*
+ * A status of timedout indicates that we started the update and
+ * have released control of the control block. Any other status
+ * indicates that we should clean up the control block. We either
+ * got a success which indicates that we didn't really need to
+ * send an update or some other error in which case we weren't able
+ * to start the update process. In both cases we still own
+ * the control block and should free it.
+ */
+ if (status != ISC_R_TIMEDOUT) {
+ if (client != NULL) {
+ client->ddns_cb = NULL;
+ }
+ ddns_cb_free(ddns_cb, MDL);
+ }
+}
+
+/*
+ * 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"
+ */
+
+/* The first and second stages are pretty similar so we combine them */
+void
+client_dns_update_action(dhcp_ddns_cb_t *ddns_cb,
+ isc_result_t eresult)
+{
+ isc_result_t result;
+ struct timeval tv;
+
+ switch(eresult) {
+ case ISC_R_SUCCESS:
+ default:
+ /* Either we succeeded or broke in a bad way, clean up */
+ break;
+
+ case DNS_R_YXRRSET:
+ /*
+ * This is the only difference between the two stages,
+ * check to see if it is the first stage, in which case
+ * start the second stage
+ */
+ if (ddns_cb->state == DDNS_STATE_ADD_FW_NXDOMAIN) {
+ ddns_cb->state = DDNS_STATE_ADD_FW_YXDHCID;
+ ddns_cb->cur_func = client_dns_update_action;
+
+ result = ddns_modify_fwd(ddns_cb, MDL);
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+ }
+ break;
+
+ case ISC_R_TIMEDOUT:
+ /*
+ * We got a timeout response from the DNS module. Schedule
+ * another attempt for later. We forget the name, dhcid and
+ * zone so if it gets changed we will get the new information.
+ */
+ data_string_forget(&ddns_cb->fwd_name, MDL);
+ data_string_forget(&ddns_cb->dhcid, MDL);
+ if (ddns_cb->zone != NULL) {
+ forget_zone((struct dns_zone **)&ddns_cb->zone);
+ }
+
+ /* Reset to doing the first stage */
+ ddns_cb->state = DDNS_STATE_ADD_FW_NXDOMAIN;
+ ddns_cb->cur_func = client_dns_update_action;
+
+ /* and update our timer */
+ if (ddns_cb->timeout < 3600)
+ ddns_cb->timeout *= 10;
+ tv.tv_sec = cur_tv.tv_sec + ddns_cb->timeout;
+ tv.tv_usec = cur_tv.tv_usec;
+ add_timeout(&tv, client_dns_update_timeout,
+ ddns_cb, NULL, NULL);
+ return;
+ }
+
+ ddns_cb_free(ddns_cb, MDL);
+ return;
+}
+
+/* See if we should do a DNS update, and if so, do it. */
+
+isc_result_t
+client_dns_update(struct client_state *client, dhcp_ddns_cb_t *ddns_cb)
+{
+ struct data_string client_identifier;
+ struct option_cache *oc;
+ int ignorep;
+ int result;
+ isc_result_t rcode;
+
+ /* If we didn't send an FQDN option, we certainly aren't going to
+ be doing an update. */
+ if (!client -> sent_options)
+ return ISC_R_SUCCESS;
+
+ /* If we don't have a lease, we can't do an update. */
+ if ((client->active == NULL) && (client->active_lease == NULL))
+ return ISC_R_SUCCESS;
+
+ /* If we set the no client update flag, don't do the update. */
+ if ((oc = lookup_option (&fqdn_universe, client -> sent_options,
+ FQDN_NO_CLIENT_UPDATE)) &&
+ evaluate_boolean_option_cache (&ignorep, (struct packet *)0,
+ (struct lease *)0, client,
+ client -> sent_options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL))
+ return ISC_R_SUCCESS;
+
+ /* If we set the "server, please update" flag, or didn't set it
+ to false, don't do the update. */
+ if (!(oc = lookup_option (&fqdn_universe, client -> sent_options,
+ FQDN_SERVER_UPDATE)) ||
+ evaluate_boolean_option_cache (&ignorep, (struct packet *)0,
+ (struct lease *)0, client,
+ client -> sent_options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL))
+ return ISC_R_SUCCESS;
+
+ /* If no FQDN option was supplied, don't do the update. */
+ if (!(oc = lookup_option (&fqdn_universe, client -> sent_options,
+ FQDN_FQDN)) ||
+ !evaluate_option_cache (&ddns_cb->fwd_name, (struct packet *)0,
+ (struct lease *)0, client,
+ client -> sent_options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL))
+ return ISC_R_SUCCESS;
+
+ /* If this is a DHCPv6 client update, make a dhcid string out of
+ * the DUID. If this is a DHCPv4 client update, choose either
+ * the client identifier, if there is one, or the interface's
+ * MAC address.
+ */
+ result = 0;
+ POST(result);
+ memset(&client_identifier, 0, sizeof(client_identifier));
+ if (client->active_lease != NULL) {
+ if (((oc =
+ lookup_option(&dhcpv6_universe, client->sent_options,
+ D6O_CLIENTID)) != NULL) &&
+ evaluate_option_cache(&client_identifier, NULL, NULL,
+ client, client->sent_options, NULL,
+ &global_scope, oc, MDL)) {
+ /* RFC4701 defines type '2' as being for the DUID
+ * field. We aren't using RFC4701 DHCID RR's yet,
+ * but this is as good a value as any.
+ */
+ result = get_dhcid(&ddns_cb->dhcid, 2,
+ client_identifier.data,
+ client_identifier.len);
+ data_string_forget(&client_identifier, MDL);
+ } else
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ } else {
+ if (((oc =
+ lookup_option(&dhcp_universe, client->sent_options,
+ DHO_DHCP_CLIENT_IDENTIFIER)) != NULL) &&
+ evaluate_option_cache(&client_identifier, NULL, NULL,
+ client, client->sent_options, NULL,
+ &global_scope, oc, MDL)) {
+ result = get_dhcid(&ddns_cb->dhcid,
+ DHO_DHCP_CLIENT_IDENTIFIER,
+ client_identifier.data,
+ client_identifier.len);
+ data_string_forget(&client_identifier, MDL);
+ } else
+ result = get_dhcid(&ddns_cb->dhcid, 0,
+ client->interface->hw_address.hbuf,
+ client->interface->hw_address.hlen);
+ }
+ if (!result) {
+ return ISC_R_SUCCESS;
+ }
+
+ /*
+ * Perform updates.
+ */
+ if (ddns_cb->fwd_name.len && ddns_cb->dhcid.len) {
+ rcode = ddns_modify_fwd(ddns_cb, MDL);
+ } else
+ rcode = ISC_R_FAILURE;
+
+ /*
+ * A success from the modify routine means we are performing
+ * async processing, for which we use the timedout error message.
+ */
+ if (rcode == ISC_R_SUCCESS) {
+ rcode = ISC_R_TIMEDOUT;
+ }
+
+ return rcode;
+}
+
+
+/*
+ * Schedule the first update. They will continue to retry occasionally
+ * until they no longer time out (or fail).
+ */
+void
+dhclient_schedule_updates(struct client_state *client,
+ struct iaddr *addr,
+ int offset)
+{
+ dhcp_ddns_cb_t *ddns_cb;
+ struct timeval tv;
+
+ if (!client->config->do_forward_update)
+ return;
+
+ /* cancel any outstanding ddns requests */
+ if (client->ddns_cb != NULL) {
+ ddns_cancel(client->ddns_cb, MDL);
+ client->ddns_cb = NULL;
+ }
+
+ ddns_cb = ddns_cb_alloc(MDL);
+
+ if (ddns_cb != NULL) {
+ ddns_cb->lease = (void *)client;
+ ddns_cb->address = *addr;
+ ddns_cb->timeout = 1;
+
+ /*
+ * XXX: DNS TTL is a problem we need to solve properly.
+ * Until that time, 300 is a placeholder default for
+ * something that is less insane than a value scaled
+ * by lease timeout.
+ */
+ ddns_cb->ttl = 300;
+
+ ddns_cb->state = DDNS_STATE_ADD_FW_NXDOMAIN;
+ ddns_cb->cur_func = client_dns_update_action;
+ ddns_cb->flags = DDNS_UPDATE_ADDR | DDNS_INCLUDE_RRSET;
+
+ client->ddns_cb = ddns_cb;
+
+ tv.tv_sec = cur_tv.tv_sec + offset;
+ tv.tv_usec = cur_tv.tv_usec;
+ add_timeout(&tv, client_dns_update_timeout,
+ ddns_cb, NULL, NULL);
+ } else {
+ log_error("Unable to allocate dns update state for %s",
+ piaddr(*addr));
+ }
+}
+#endif
+
+void
+dhcpv4_client_assignments(void)
+{
+ struct servent *ent;
+
+ if (path_dhclient_pid == NULL)
+ path_dhclient_pid = _PATH_DHCLIENT_PID;
+ if (path_dhclient_db == NULL)
+ path_dhclient_db = _PATH_DHCLIENT_DB;
+
+ /* Default to the DHCP/BOOTP port. */
+ if (!local_port) {
+ /* If we're faking a relay agent, and we're not using loopback,
+ use the server port, not the client port. */
+ if (mockup_relay && giaddr.s_addr != htonl(INADDR_LOOPBACK)) {
+ local_port = htons(67);
+ } else {
+ ent = getservbyname("dhcpc", "udp");
+ if (ent == NULL)
+ ent = getservbyname("bootpc", "udp");
+ if (ent == NULL)
+ local_port = htons(68);
+ else
+ local_port = ent->s_port;
+#ifndef __CYGWIN32__
+ endservent ();
+#endif
+ }
+ }
+
+ /* If we're faking a relay agent, and we're not using loopback,
+ we're using the server port, not the client port. */
+ if (mockup_relay && giaddr.s_addr != htonl(INADDR_LOOPBACK)) {
+ remote_port = local_port;
+ } else
+ remote_port = htons(ntohs(local_port) - 1); /* XXX */
+}
+
+/*
+ * The following routines are used to check that certain
+ * strings are reasonable before we pass them to the scripts.
+ * This avoids some problems with scripts treating the strings
+ * as commands - see ticket 23722
+ * The domain checking code should be done as part of assembling
+ * the string but we are doing it here for now due to time
+ * constraints.
+ */
+
+static int check_domain_name(const char *ptr, size_t len, int dots)
+{
+ const char *p;
+
+ /* not empty or complete length not over 255 characters */
+ if ((len == 0) || (len > 256))
+ return(-1);
+
+ /* consists of [[:alnum:]-]+ labels separated by [.] */
+ /* a [_] is against RFC but seems to be "widely used"... */
+ for (p=ptr; (*p != 0) && (len-- > 0); p++) {
+ if ((*p == '-') || (*p == '_')) {
+ /* not allowed at begin or end of a label */
+ if (((p - ptr) == 0) || (len == 0) || (p[1] == '.'))
+ return(-1);
+ } else if (*p == '.') {
+ /* each label has to be 1-63 characters;
+ we allow [.] at the end ('foo.bar.') */
+ size_t d = p - ptr;
+ if ((d <= 0) || (d >= 64))
+ return(-1);
+ ptr = p + 1; /* jump to the next label */
+ if ((dots > 0) && (len > 0))
+ dots--;
+ } else if (isalnum((unsigned char)*p) == 0) {
+ /* also numbers at the begin are fine */
+ return(-1);
+ }
+ }
+ return(dots ? -1 : 0);
+}
+
+static int check_domain_name_list(const char *ptr, size_t len, int dots)
+{
+ const char *p;
+ int ret = -1; /* at least one needed */
+
+ if ((ptr == NULL) || (len == 0))
+ return(-1);
+
+ for (p=ptr; (*p != 0) && (len > 0); p++, len--) {
+ if (*p != ' ')
+ continue;
+ if (p > ptr) {
+ if (check_domain_name(ptr, p - ptr, dots) != 0)
+ return(-1);
+ ret = 0;
+ }
+ ptr = p + 1;
+ }
+ if (p > ptr)
+ return(check_domain_name(ptr, p - ptr, dots));
+ else
+ return(ret);
+}
+
+static int check_option_values(struct universe *universe,
+ unsigned int opt,
+ const char *ptr,
+ size_t len)
+{
+ if (ptr == NULL)
+ return(-1);
+
+ /* just reject options we want to protect, will be escaped anyway */
+ if ((universe == NULL) || (universe == &dhcp_universe)) {
+ switch(opt) {
+ case DHO_DOMAIN_NAME:
+#ifdef ACCEPT_LIST_IN_DOMAIN_NAME
+ return check_domain_name_list(ptr, len, 0);
+#else
+ return check_domain_name(ptr, len, 0);
+#endif
+ case DHO_HOST_NAME:
+ case DHO_NIS_DOMAIN:
+ case DHO_NETBIOS_SCOPE:
+ return check_domain_name(ptr, len, 0);
+ break;
+ case DHO_DOMAIN_SEARCH:
+ return check_domain_name_list(ptr, len, 0);
+ break;
+ case DHO_ROOT_PATH:
+ if (len == 0)
+ return(-1);
+ for (; (*ptr != 0) && (len-- > 0); ptr++) {
+ if(!(isalnum((unsigned char)*ptr) ||
+ *ptr == '#' || *ptr == '%' ||
+ *ptr == '+' || *ptr == '-' ||
+ *ptr == '_' || *ptr == ':' ||
+ *ptr == '.' || *ptr == ',' ||
+ *ptr == '@' || *ptr == '~' ||
+ *ptr == '\\' || *ptr == '/' ||
+ *ptr == '[' || *ptr == ']' ||
+ *ptr == '=' || *ptr == ' '))
+ return(-1);
+ }
+ return(0);
+ break;
+ }
+ }
+
+#ifdef DHCPv6
+ if (universe == &dhcpv6_universe) {
+ switch(opt) {
+ case D6O_SIP_SERVERS_DNS:
+ case D6O_DOMAIN_SEARCH:
+ case D6O_NIS_DOMAIN_NAME:
+ case D6O_NISP_DOMAIN_NAME:
+ return check_domain_name_list(ptr, len, 0);
+ break;
+ }
+ }
+#endif
+
+ return(0);
+}
+
+static void
+add_reject(struct packet *packet) {
+ struct iaddrmatchlist *list;
+
+ list = dmalloc(sizeof(struct iaddrmatchlist), MDL);
+ if (!list)
+ log_fatal ("no memory for reject list!");
+
+ /*
+ * client_addr is misleading - it is set to source address in common
+ * code.
+ */
+ list->match.addr = packet->client_addr;
+ /* Set mask to indicate host address. */
+ list->match.mask.len = list->match.addr.len;
+ memset(list->match.mask.iabuf, 0xff, sizeof(list->match.mask.iabuf));
+
+ /* Append to reject list for the source interface. */
+ list->next = packet->interface->client->config->reject_list;
+ packet->interface->client->config->reject_list = list;
+
+ /*
+ * We should inform user that we won't be accepting this server
+ * anymore.
+ */
+ log_info("Server added to list of rejected servers.");
+}
diff --git a/client/dhclient.conf.5 b/client/dhclient.conf.5
new file mode 100644
index 0000000..0f3beaf
--- /dev/null
+++ b/client/dhclient.conf.5
@@ -0,0 +1,734 @@
+.\" $Id: dhclient.conf.5,v 1.25.24.10 2012/04/16 17:17:48 sar Exp $
+.\"
+.\" Copyright (c) 2009-2012,2014 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 2004,2007 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 1996-2003 by Internet Software Consortium
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+.\" OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.\" Internet Systems Consortium, Inc.
+.\" 950 Charter Street
+.\" Redwood City, CA 94063
+.\" <info@isc.org>
+.\" https://www.isc.org/
+.\"
+.\" Support and other services are available for ISC products - see
+.\" https://www.isc.org for more information or to learn more about ISC.
+.\"
+.TH dhclient.conf 5
+.SH NAME
+dhclient.conf - DHCP client configuration file
+.SH DESCRIPTION
+The dhclient.conf file contains configuration information for
+.IR dhclient,
+the Internet Systems Consortium DHCP Client.
+.PP
+The dhclient.conf file is a free-form ASCII text file. It is parsed by
+the recursive-descent parser built into dhclient. 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 dhclient.conf file can be used to configure the behaviour of the
+client in a wide variety of ways: protocol timing, information
+requested from the server, information required of the server,
+defaults to use if the server does not provide certain information,
+values with which to override information provided by the server, or
+values to prepend or append to information provided by the server.
+The configuration file can also be preinitialized with addresses to
+use on networks that don't have DHCP servers.
+.SH PROTOCOL TIMING
+The timing behaviour of the client need not be configured by the user.
+If no timing configuration is provided by the user, a fairly
+reasonable timing behaviour will be used by default - one which
+results in fairly timely updates without placing an inordinate load on
+the server.
+.PP
+The following statements can be used to adjust the timing behaviour of
+the DHCP client if required, however:
+.PP
+.I The
+.B timeout
+.I statement
+.PP
+.B timeout
+.I time
+.B ;
+.PP
+The
+.I timeout
+statement determines the amount of time that must pass between the
+time that the client begins to try to determine its address and the
+time that it decides that it's not going to be able to contact a
+server. By default, this timeout is sixty seconds. After the
+timeout has passed, if there are any static leases defined in the
+configuration file, or any leases remaining in the lease database that
+have not yet expired, the client will loop through these leases
+attempting to validate them, and if it finds one that appears to be
+valid, it will use that lease's address. If there are no valid
+static leases or unexpired leases in the lease database, the client
+will restart the protocol after the defined retry interval.
+.PP
+.I The
+.B retry
+.I statement
+.PP
+ \fBretry \fItime\fR\fB;\fR
+.PP
+The
+.I retry
+statement determines the time that must pass after the client has
+determined that there is no DHCP server present before it tries again
+to contact a DHCP server. By default, this is five minutes.
+.PP
+.I The
+.B select-timeout
+.I statement
+.PP
+ \fBselect-timeout \fItime\fR\fB;\fR
+.PP
+It is possible (some might say desirable) for there to be more than
+one DHCP server serving any given network. In this case, it is
+possible that a client may be sent more than one offer in response to
+its initial lease discovery message. It may be that one of these
+offers is preferable to the other (e.g., one offer may have the
+address the client previously used, and the other may not).
+.PP
+The
+.I select-timeout
+is the time after the client sends its first lease discovery request
+at which it stops waiting for offers from servers, assuming that it
+has received at least one such offer. If no offers have been
+received by the time the
+.I select-timeout
+has expired, the client will accept the first offer that arrives.
+.PP
+By default, the select-timeout is zero seconds - that is, the client
+will take the first offer it sees.
+.PP
+.I The
+.B reboot
+.I statement
+.PP
+ \fBreboot \fItime\fR\fB;\fR
+.PP
+When the client is restarted, it first tries to reacquire the last
+address it had. This is called the INIT-REBOOT state. If it is
+still attached to the same network it was attached to when it last
+ran, this is the quickest way to get started. The
+.I reboot
+statement sets the time that must elapse after the client first tries
+to reacquire its old address before it gives up and tries to discover
+a new address. By default, the reboot timeout is ten seconds.
+.PP
+.I The
+.B backoff-cutoff
+.I statement
+.PP
+ \fBbackoff-cutoff \fItime\fR\fB;\fR
+.PP
+The client uses an exponential backoff algorithm with some randomness,
+so that if many clients try to configure themselves at the same time,
+they will not make their requests in lockstep. The
+.I backoff-cutoff
+statement determines the maximum amount of time that the client is
+allowed to back off, the actual value will be evaluated randomly between
+1/2 to 1 1/2 times the \fItime\fR specified. It defaults to fifteen
+seconds.
+.PP
+.I The
+.B initial-interval
+.I statement
+.PP
+ \fBinitial-interval \fItime\fR\fB;\fR
+.PP
+The
+.I initial-interval
+statement sets the amount of time between the first attempt to reach a
+server and the second attempt to reach a server. Each time a message
+is sent, the interval between messages is incremented by twice the
+current interval multiplied by a random number between zero and one.
+If it is greater than the backoff-cutoff amount, it is set to that
+amount. It defaults to ten seconds.
+.PP
+.I The initial-delay
+.I statement
+.PP
+ \fBinitial-delay \fItime\fR\fB;\fR
+.PP
+.I initial-delay
+parameter sets the maximum time client can wait after start before
+commencing first transmission.
+According to RFC2131 Section 4.4.1, client should wait a random time between
+startup and the actual first transmission. Previous versions of ISC DHCP
+client used to wait random time up to 5 seconds, but that was unwanted
+due to impact on startup time. As such, new versions have the default
+initial delay set to 0. To restore old behavior, please set initial-delay
+to 5.
+.SH LEASE REQUIREMENTS AND REQUESTS
+The DHCP protocol allows the client to request that the server send it
+specific information, and not send it other information that it is not
+prepared to accept. The protocol also allows the client to reject
+offers from servers if they don't contain information the client
+needs, or if the information provided is not satisfactory.
+.PP
+There is a variety of data contained in offers that DHCP servers send
+to DHCP clients. The data that can be specifically requested is what
+are called \fIDHCP Options\fR. DHCP Options are defined in
+ \fBdhcp-options(5)\fR.
+.PP
+.I The
+.B request
+.I statement
+.PP
+ \fB[ also ] request [ [ \fIoption-space\fR . ] \fIoption\fR ] [\fB,\fI ... ]\fB;\fR
+.PP
+The request statement causes the client to request that any server
+responding to the client send the client its values for the specified
+options. Only the option names should be specified in the request
+statement - not option parameters. By default, the DHCPv4 client
+requests the subnet-mask, broadcast-address, time-offset, routers,
+domain-name, domain-name-servers and host-name options while the DHCPv6
+client requests the dhcp6 name-servers and domain-search options. Note
+that if you enter a \'request\' statement, you over-ride these defaults
+and these options will not be requested.
+.PP
+In some cases, it may be desirable to send no parameter request list
+at all. To do this, simply write the request statement but specify
+no parameters:
+.PP
+.nf
+ request;
+.fi
+.PP
+In most cases, it is desirable to simply add one option to the request
+list which is of interest to the client in question. In this case, it
+is best to \'also request\' the additional options:
+.PP
+.nf
+ also request domain-search, dhcp6.sip-servers-addresses;
+.fi
+.PP
+.I The
+.B require
+.I statement
+.PP
+ \fB[ also ] require [ [ \fIoption-space\fR . ] \fIoption\fR ] [\fB,\fI ... ]\fB;\fR
+.PP
+The require statement lists options that must be sent in order for an
+offer to be accepted. Offers that do not contain all the listed
+options will be ignored. There is no default require list.
+.PP
+.nf
+ require name-servers;
+
+ interface eth0 {
+ also require domain-search;
+ }
+.PP
+.I The
+.B send
+.I statement
+.PP
+ \fBsend { [ \fIoption declaration\fR ]
+[\fB,\fI ... \fIoption declaration\fR ]\fB}\fR
+.PP
+The send statement causes the client to send the specified options to
+the server with the specified values. These are full option
+declarations as described in \fBdhcp-options(5)\fR. Options that are
+always sent in the DHCP protocol should not be specified here, except
+that the client can specify a requested \fBdhcp-lease-time\fR option other
+than the default requested lease time, which is two hours. The other
+obvious use for this statement is to send information to the server
+that will allow it to differentiate between this client and other
+clients or kinds of clients.
+.SH DYNAMIC DNS
+The client now has some very limited support for doing DNS updates
+when a lease is acquired. This is prototypical, and probably doesn't
+do what you want. It also only works if you happen to have control
+over your DNS server, which isn't very likely.
+.PP
+Note that everything in this section is true whether you are using DHCPv4
+or DHCPv6. The exact same syntax is used for both.
+.PP
+To make it work, you have to declare a key and zone as in the DHCP
+server (see \fBdhcpd.conf\fR(5) for details). You also need to
+configure the \fIfqdn\fR option on the client, as follows:
+.PP
+.nf
+ send fqdn.fqdn "grosse.fugue.com.";
+ send fqdn.encoded on;
+ send fqdn.server-update off;
+ also request fqdn, dhcp6.fqdn;
+.fi
+.PP
+The \fIfqdn.fqdn\fR option \fBMUST\fR be a fully-qualified domain
+name. You \fBMUST\fR define a zone statement for the zone to be
+updated. The \fIfqdn.encoded\fR option may need to be set to
+\fIon\fR or \fIoff\fR, depending on the DHCP server you are using.
+.PP
+.I The
+.B do-forward-updates
+.I statement
+.PP
+ \fBdo-forward-updates [ \fIflag\fR ] \fB;\fR
+.PP
+If you want to do DNS updates in the DHCP client
+script (see \fBdhclient-script(8)\fR) rather than having the
+DHCP client do the update directly (for example, if you want to
+use SIG(0) authentication, which is not supported directly by the
+DHCP client, you can instruct the client not to do the update using
+the \fBdo-forward-updates\fR statement. \fIFlag\fR should be \fBtrue\fR
+if you want the DHCP client to do the update, and \fBfalse\fR if
+you don't want the DHCP client to do the update. By default, the DHCP
+client will do the DNS update.
+.SH OPTION MODIFIERS
+In some cases, a client may receive option data from the server which
+is not really appropriate for that client, or may not receive
+information that it needs, and for which a useful default value
+exists. It may also receive information which is useful, but which
+needs to be supplemented with local information. To handle these
+needs, several option modifiers are available.
+.PP
+.I The
+.B default
+.I statement
+.PP
+ \fBdefault [ \fIoption declaration\fR ] \fB;\fR
+.PP
+If for some option the client should use the value supplied by
+the server, but needs to use some default value if no value was supplied
+by the server, these values can be defined in the
+.B default
+statement.
+.PP
+.I The
+.B supersede
+.I statement
+.PP
+ \fBsupersede [ \fIoption declaration\fR ] \fB;\fR
+.PP
+If for some option the client should always use a locally-configured
+value or values rather than whatever is supplied by the server, these
+values can be defined in the
+.B supersede
+statement.
+.PP
+.I The
+.B prepend
+.I statement
+.PP
+ \fBprepend [ \fIoption declaration\fR ] \fB;\fR
+.PP
+If for some set of options the client should use a value you
+supply, and then use the values supplied by
+the server, if any, these values can be defined in the
+.B prepend
+statement. The
+.B prepend
+statement can only be used for options which
+allow more than one value to be given. This restriction is not
+enforced - if you ignore it, the behaviour will be unpredictable.
+.PP
+.I The
+.B append
+.I statement
+.PP
+ \fBappend [ \fIoption declaration\fR ] \fB;\fR
+.PP
+If for some set of options the client should first use the values
+supplied by the server, if any, and then use values you supply, these
+values can be defined in the
+.B append
+statement. The
+.B append
+statement can only be used for options which
+allow more than one value to be given. This restriction is not
+enforced - if you ignore it, the behaviour will be unpredictable.
+.SH LEASE DECLARATIONS
+.PP
+.I The
+.B lease
+.I declaration
+.PP
+ \fBlease {\fR \fIlease-declaration\fR [ ... \fIlease-declaration ] \fB}\fR
+.PP
+The DHCP client may decide after some period of time (see \fBPROTOCOL
+TIMING\fR) that it is not going to succeed in contacting a
+server. At that time, it consults its own database of old leases and
+tests each one that has not yet timed out by pinging the listed router
+for that lease to see if that lease could work. It is possible to
+define one or more \fIfixed\fR leases in the client configuration file
+for networks where there is no DHCP or BOOTP service, so that the
+client can still automatically configure its address. This is done
+with the
+.B lease
+statement.
+.PP
+NOTE: the lease statement is also used in the dhclient.leases file in
+order to record leases that have been received from DHCP servers.
+Some of the syntax for leases as described below is only needed in the
+dhclient.leases file. Such syntax is documented here for
+completeness.
+.PP
+A lease statement consists of the lease keyword, followed by a left
+curly brace, followed by one or more lease declaration statements,
+followed by a right curly brace. The following lease declarations
+are possible:
+.PP
+ \fBbootp;\fR
+.PP
+The
+.B bootp
+statement is used to indicate that the lease was acquired using the
+BOOTP protocol rather than the DHCP protocol. It is never necessary
+to specify this in the client configuration file. The client uses
+this syntax in its lease database file.
+.PP
+ \fBinterface\fR \fB"\fR\fIstring\fR\fB";\fR
+.PP
+The
+.B interface
+lease statement is used to indicate the interface on which the lease
+is valid. If set, this lease will only be tried on a particular
+interface. When the client receives a lease from a server, it always
+records the interface number on which it received that lease.
+If predefined leases are specified in the dhclient.conf file, the
+interface should also be specified, although this is not required.
+.PP
+ \fBfixed-address\fR \fIip-address\fR\fB;\fR
+.PP
+The
+.B fixed-address
+statement is used to set the ip address of a particular lease. This
+is required for all lease statements. The IP address must be
+specified as a dotted quad (e.g., 12.34.56.78).
+.PP
+ \fBfilename "\fR\fIstring\fR\fB";\fR
+.PP
+The
+.B filename
+statement specifies the name of the boot filename to use. This is
+not used by the standard client configuration script, but is included
+for completeness.
+.PP
+ \fBserver-name "\fR\fIstring\fR\fB";\fR
+.PP
+The
+.B server-name
+statement specifies the name of the boot server name to use. This is
+also not used by the standard client configuration script.
+.PP
+ \fBoption\fR \fIoption-declaration\fR\fB;\fR
+.PP
+The
+.B option
+statement is used to specify the value of an option supplied by the
+server, or, in the case of predefined leases declared in
+dhclient.conf, the value that the user wishes the client configuration
+script to use if the predefined lease is used.
+.PP
+ \fBscript "\fIscript-name\fB";\fR
+.PP
+The
+.B script
+statement is used to specify the pathname of the dhcp client
+configuration script. This script is used by the dhcp client to set
+each interface's initial configuration prior to requesting an address,
+to test the address once it has been offered, and to set the
+interface's final configuration once a lease has been acquired. If
+no lease is acquired, the script is used to test predefined leases, if
+any, and also called once if no valid lease can be identified. For
+more information, see
+.B dhclient-script(8).
+.PP
+ \fBvendor option space "\fIname\fB";\fR
+.PP
+The
+.B vendor option space
+statement is used to specify which option space should be used for
+decoding the vendor-encapsulate-options option if one is received.
+The \fIdhcp-vendor-identifier\fR can be used to request a specific
+class of vendor options from the server. See
+.B dhcp-options(5)
+for details.
+.PP
+ \fBmedium "\fImedia setup\fB";\fR
+.PP
+The
+.B medium
+statement can be used on systems where network interfaces cannot
+automatically determine the type of network to which they are
+connected. The media setup string is a system-dependent parameter
+which is passed to the dhcp client configuration script when
+initializing the interface. On Unix and Unix-like systems, the
+argument is passed on the ifconfig command line when configuring the
+interface.
+.PP
+The dhcp client automatically declares this parameter if it uses a
+media type (see the
+.B media
+statement) when configuring the interface in order to obtain a lease.
+This statement should be used in predefined leases only if the network
+interface requires media type configuration.
+.PP
+ \fBrenew\fR \fIdate\fB;\fR
+.PP
+ \fBrebind\fR \fIdate\fB;\fR
+.PP
+ \fBexpire\fR \fIdate\fB;\fR
+.PP
+The \fBrenew\fR statement defines the time at which the dhcp client
+should begin trying to contact its server to renew a lease that it is
+using. The \fBrebind\fR statement defines the time at which the dhcp
+client should begin to try to contact \fIany\fR dhcp server in order
+to renew its lease. The \fBexpire\fR statement defines the time at
+which the dhcp client must stop using a lease if it has not been able
+to contact a server in order to renew it.
+.PP
+These declarations are automatically set in leases acquired by the
+DHCP client, but must also be configured in predefined leases - a
+predefined lease whose expiry time has passed will not be used by the
+DHCP client.
+.PP
+Dates are specified in one of two ways. The software will output times in
+these two formats depending on if the \fBdb-time-format\fR configuration
+parameter has been set to \fIdefault\fR or \fIlocal\fR.
+.PP
+If it is set to \fIdefault\fR, then \fIdate\fR values appear as follows:
+.PP
+ \fI<weekday> <year>\fB/\fI<month>\fB/\fI<day>
+<hour>\fB:\fI<minute>\fB:\fI<second>\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. When declaring a predefined lease, it can always be
+specified as zero. 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
+If the \fBdb-time-format\fR configuration was set to \fIlocal\fR, then
+the \fIdate\fR values 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, the epoch time is the only recommended value for machine
+inspection.
+.PP
+Note that when defining a static lease, one may use either time format one
+wishes, and need not include the comment or values after it.
+.PP
+If the time is infinite in duration, then the \fIdate\fR is \fBnever\fR
+instead of an actual date.
+.SH ALIAS DECLARATIONS
+ \fBalias { \fI declarations ... \fB}\fR
+.PP
+Some DHCP clients running TCP/IP roaming protocols may require that in
+addition to the lease they may acquire via DHCP, their interface also
+be configured with a predefined IP alias so that they can have a
+permanent IP address even while roaming. The Internet Systems
+Consortium DHCP client doesn't support roaming with fixed addresses
+directly, but in order to facilitate such experimentation, the dhcp
+client can be set up to configure an IP alias using the
+.B alias
+declaration.
+.PP
+The alias declaration resembles a lease declaration, except that
+options other than the subnet-mask option are ignored by the standard
+client configuration script, and expiry times are ignored. A typical
+alias declaration includes an interface declaration, a fixed-address
+declaration for the IP alias address, and a subnet-mask option
+declaration. A medium statement should never be included in an alias
+declaration.
+.SH OTHER DECLARATIONS
+ \fBdb-time-format\fR [ \fIdefault\fR | \fIlocal\fR ] \fB;\fR
+.PP
+The \fBdb-time-format\fR option determines which of two output methods are
+used for printing times in leases files. The \fIdefault\fR format provides
+day-and-time in UTC, whereas \fIlocal\fR uses a seconds-since-epoch to store
+the time value, and helpfully places a local timezone time in a comment on
+the same line. The formats are described in detail in this manpage, within
+the LEASE DECLARATIONS section.
+.PP
+ \fBreject \fIcidr-ip-address\fR [\fB,\fR \fI...\fB \fIcidr-ip-address\fR ] \fB;\fR
+.PP
+The
+.B reject
+statement causes the DHCP client to reject offers from
+servers whose server identifier matches any of the specified hosts or
+subnets. This can be used to avoid being configured by rogue or
+misconfigured dhcp servers, although it should be a last resort -
+better to track down the bad DHCP server and fix it.
+.PP
+The \fIcidr-ip-address\fR configuration type is of the
+form \fIip-address\fR[\fB/\fIprefixlen\fR], where \fIip-address\fR is a
+dotted quad IP address, and \fRprefixlen\fR is the CIDR prefix length of
+the subnet, counting the number of significant bits in the netmask starting
+from the leftmost end. Example configuration syntax:
+.PP
+.I \fIreject\fR 192.168.0.0\fB/\fR16\fB,\fR 10.0.0.5\fB;\fR
+.PP
+The above example would cause offers from any server identifier in the
+entire RFC 1918 "Class C" network 192.168.0.0/16, or the specific
+single address 10.0.0.5, to be rejected.
+.PP
+ \fBinterface "\fIname\fB" { \fIdeclarations ... \fB }
+.PP
+A client with more than one network interface may require different
+behaviour depending on which interface is being configured. All
+timing parameters and declarations other than lease and alias
+declarations can be enclosed in an interface declaration, and those
+parameters will then be used only for the interface that matches the
+specified name. Interfaces for which there is no interface
+declaration will use the parameters declared outside of any interface
+declaration, or the default settings.
+.PP
+.B Note well:
+ISC dhclient only maintains one list of interfaces, which is either
+determined at startup from command line arguments, or otherwise is
+autodetected. If you supplied the list of interfaces on the command
+line, this configuration clause will add the named interface to the
+list in such a way that will cause it to be configured by DHCP. Which
+may not be the result you had intended. This is an undesirable side
+effect that will be addressed in a future release.
+.PP
+ \fBpseudo "\fIname\fR" "\fIreal-name\fB" { \fIdeclarations ... \fB }
+.PP
+Under some circumstances it can be useful to declare a pseudo-interface
+and have the DHCP client acquire a configuration for that interface.
+Each interface that the DHCP client is supporting normally has a DHCP
+client state machine running on it to acquire and maintain its lease.
+A pseudo-interface is just another state machine running on the
+interface named \fIreal-name\fR, with its own lease and its own
+state. If you use this feature, you must provide a client identifier
+for both the pseudo-interface and the actual interface, and the two
+identifiers must be different. You must also provide a separate
+client script for the pseudo-interface to do what you want with the IP
+address. For example:
+.PP
+.nf
+ interface "ep0" {
+ send dhcp-client-identifier "my-client-ep0";
+ }
+ pseudo "secondary" "ep0" {
+ send dhcp-client-identifier "my-client-ep0-secondary";
+ script "/etc/dhclient-secondary";
+ }
+.fi
+.PP
+The client script for the pseudo-interface should not configure the
+interface up or down - essentially, all it needs to handle are the
+states where a lease has been acquired or renewed, and the states
+where a lease has expired. See \fBdhclient-script(8)\fR for more
+information.
+.PP
+ \fBmedia "\fImedia setup\fB"\fI [ \fB, "\fImedia setup\fB", \fI... ]\fB;\fR
+.PP
+The
+.B media
+statement defines one or more media configuration parameters which may
+be tried while attempting to acquire an IP address. The dhcp client
+will cycle through each media setup string on the list, configuring
+the interface using that setup and attempting to boot, and then trying
+the next one. This can be used for network interfaces which aren't
+capable of sensing the media type unaided - whichever media type
+succeeds in getting a request to the server and hearing the reply is
+probably right (no guarantees).
+.PP
+The media setup is only used for the initial phase of address
+acquisition (the DHCPDISCOVER and DHCPOFFER packets). Once an
+address has been acquired, the dhcp client will record it in its lease
+database and will record the media type used to acquire the address.
+Whenever the client tries to renew the lease, it will use that same
+media type. The lease must expire before the client will go back to
+cycling through media types.
+.PP
+ \fBhardware\fR \fIlink-type mac-address\fR\fB;\fR
+.PP
+The
+.B hardware
+statement defines the hardware MAC address to use for this interface,
+for DHCP servers or relays to direct their replies. dhclient will determine
+the interface's MAC address automatically, so use of this parameter
+is not recommended. The \fIlink-type\fR corresponds to the interface's
+link layer type (example: \'ethernet\'), while the \fImac-address\fR is
+a string of colon-separated hexadecimal values for octets.
+.PP
+ \fBanycast-mac\fR \fIlink-type mac-address\fR\fB;\fR
+.PP
+The
+.B anycast-mac
+statement over-rides the all-ones broadcast MAC address dhclient will use
+when it is transmitting packets to the all-ones limited broadcast IPv4
+address. This configuration parameter is useful to reduce the number of
+broadcast packets transmitted by DHCP clients, but is only useful if you
+know the DHCP service(s) anycast MAC address prior to configuring your
+client. The \fIlink-type\fR and \fImac-address\fR parameters are configured
+in a similar manner to the \fBhardware\fR statement.
+.PP
+.SH SAMPLE
+The following configuration file is used on a laptop running NetBSD
+1.3. The laptop has an IP alias of 192.5.5.213, and has one
+interface, ep0 (a 3com 3C589C). Booting intervals have been
+shortened somewhat from the default, because the client is known to
+spend most of its time on networks with little DHCP activity. The
+laptop does roam to multiple networks.
+
+.nf
+
+timeout 60;
+retry 60;
+reboot 10;
+select-timeout 5;
+initial-interval 2;
+reject 192.33.137.209;
+
+interface "ep0" {
+ send host-name "andare.fugue.com";
+ hardware ethernet 00:a0:24:ab:fb:9c;
+ send dhcp-client-identifier 1:0:a0:24:ab:fb:9c;
+ send dhcp-lease-time 3600;
+ supersede domain-search "fugue.com", "rc.vix.com", "home.vix.com";
+ prepend domain-name-servers 127.0.0.1;
+ request subnet-mask, broadcast-address, time-offset, routers,
+ domain-name, domain-name-servers, host-name;
+ require subnet-mask, domain-name-servers;
+ script "CLIENTBINDIR/dhclient-script";
+ media "media 10baseT/UTP", "media 10base2/BNC";
+}
+
+alias {
+ interface "ep0";
+ fixed-address 192.5.5.213;
+ option subnet-mask 255.255.255.255;
+}
+.fi
+This is a very complicated dhclient.conf file - in general, yours
+should be much simpler. In many cases, it's sufficient to just
+create an empty dhclient.conf file - the defaults are usually fine.
+.SH SEE ALSO
+dhcp-options(5), dhcp-eval(5), dhclient.leases(5), dhcpd(8), dhcpd.conf(5),
+RFC2132, RFC2131.
+.SH AUTHOR
+.B dhclient(8)
+Information about Internet Systems Consortium can be found at
+.B https://www.isc.org.
diff --git a/client/dhclient.conf.example b/client/dhclient.conf.example
new file mode 100644
index 0000000..b8354de
--- /dev/null
+++ b/client/dhclient.conf.example
@@ -0,0 +1,36 @@
+send host-name = pick-first-value(gethostname(), "ISC-dhclient");
+send dhcp-client-identifier 1:0:a0:24:ab:fb:9c;
+send dhcp-lease-time 3600;
+supersede domain-search "fugue.com", "home.vix.com";
+prepend domain-name-servers 127.0.0.1;
+request subnet-mask, broadcast-address, time-offset, routers,
+ domain-name, domain-name-servers, host-name;
+require subnet-mask, domain-name-servers;
+timeout 60;
+retry 60;
+reboot 10;
+select-timeout 5;
+initial-interval 2;
+script "/etc/dhclient-script";
+media "-link0 -link1 -link2", "link0 link1";
+reject 192.33.137.209;
+
+alias {
+ interface "ep0";
+ fixed-address 192.5.5.213;
+ option subnet-mask 255.255.255.255;
+}
+
+lease {
+ interface "ep0";
+ fixed-address 192.33.137.200;
+ medium "link0 link1";
+ option host-name "andare.swiftmedia.com";
+ option subnet-mask 255.255.255.0;
+ option broadcast-address 192.33.137.255;
+ option routers 192.33.137.250;
+ option domain-name-servers 127.0.0.1;
+ renew 2 2000/1/12 00:00:01;
+ rebind 2 2000/1/12 00:00:01;
+ expire 2 2000/1/12 00:00:01;
+}
diff --git a/client/dhclient.leases.5 b/client/dhclient.leases.5
new file mode 100644
index 0000000..8c175a2
--- /dev/null
+++ b/client/dhclient.leases.5
@@ -0,0 +1,51 @@
+.\" $Id: dhclient.leases.5,v 1.5.24.3 2011/02/23 23:52:21 sar Exp $
+.\"
+.\" Copyright (c) 2009-2011 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 1997-2003 by Internet Software Consortium
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+.\" OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.\" Internet Systems Consortium, Inc.
+.\" 950 Charter Street
+.\" Redwood City, CA 94063
+.\" <info@isc.org>
+.\" https://www.isc.org/
+.\"
+.\" Support and other services are available for ISC products - see
+.\" https://www.isc.org for more information or to learn more about ISC.
+.\"
+.\" $Id: dhclient.leases.5,v 1.5.24.3 2011/02/23 23:52:21 sar Exp $
+.\"
+.TH dhclient.leases 5
+.SH NAME
+dhclient.leases - DHCP client lease database
+.SH DESCRIPTION
+The Internet Systems Consortium DHCP client keeps a persistent
+database of leases that it has acquired that are still valid. The
+database is a free-form ASCII file containing one valid declaration
+per lease. If more than one declaration appears for a given lease,
+the last one in the file is used. The file is written as a log, so
+this is not an unusual occurrence.
+.PP
+The format of the lease declarations is described in
+.B dhclient.conf(5).
+.SH FILES
+.B DBDIR/dhclient.leases
+.SH SEE ALSO
+dhclient(8), dhcp-options(5), dhclient.conf(5), dhcpd(8),
+dhcpd.conf(5), RFC2132, RFC2131.
+.SH AUTHOR
+.B dhclient(8)
+Information about Internet Systems Consortium can be found at
+.B https://www.isc.org.
diff --git a/client/scripts/bsdos b/client/scripts/bsdos
new file mode 100755
index 0000000..d69d0d8
--- /dev/null
+++ b/client/scripts/bsdos
@@ -0,0 +1,324 @@
+#!/bin/sh
+
+make_resolv_conf() {
+ if [ x"$new_domain_name_servers" != x ]; then
+ cat /dev/null > /etc/resolv.conf.dhclient
+ if [ "x$new_domain_search" != x ]; then
+ echo search $new_domain_search >> /etc/resolv.conf.dhclient
+ elif [ "x$new_domain_name" != x ]; then
+ # Note that the DHCP 'Domain Name Option' is really just a domain
+ # name, and that this practice of using the domain name option as
+ # a search path is both nonstandard and deprecated.
+ echo search $new_domain_name >> /etc/resolv.conf.dhclient
+ fi
+ for nameserver in $new_domain_name_servers; do
+ echo nameserver $nameserver >> /etc/resolv.conf.dhclient
+ done
+
+ mv /etc/resolv.conf.dhclient /etc/resolv.conf
+ elif [ "x${new_dhcp6_name_servers}" != x ] ; then
+ cat /dev/null > /etc/resolv.conf.dhclient6
+ chmod 644 /etc/resolv.conf.dhclient6
+
+ if [ "x${new_dhcp6_domain_search}" != x ] ; then
+ echo search ${new_dhcp6_domain_search} >> /etc/resolv.conf.dhclient6
+ fi
+ for nameserver in ${new_dhcp6_name_servers} ; do
+ # If the nameserver has a link-local address
+ # add a <zone_id> (interface name) to it.
+ case $nameserver in
+ fe80:*) zone_id="%$interface";;
+ FE80:*) zone_id="%$interface";;
+ *) zone_id="";;
+ esac
+ echo nameserver ${nameserver}$zone_id >> /etc/resolv.conf.dhclient6
+ done
+
+ mv /etc/resolv.conf.dhclient6 /etc/resolv.conf
+ fi
+}
+
+# Must be used on exit. Invokes the local dhcp client exit hooks, if any.
+exit_with_hooks() {
+ exit_status=$1
+ if [ -f /etc/dhclient-exit-hooks ]; then
+ . /etc/dhclient-exit-hooks
+ fi
+# probably should do something with exit status of the local script
+ exit $exit_status
+}
+
+# Invoke the local dhcp client enter hooks, if they exist.
+if [ -f /etc/dhclient-enter-hooks ]; then
+ exit_status=0
+ . /etc/dhclient-enter-hooks
+ # allow the local script to abort processing of this state
+ # local script must set exit_status variable to nonzero.
+ if [ $exit_status -ne 0 ]; then
+ exit $exit_status
+ fi
+fi
+
+if [ x$new_network_number != x ]; then
+ echo New Network Number: $new_network_number
+fi
+
+if [ x$new_broadcast_address != x ]; then
+ echo New Broadcast Address: $new_broadcast_address
+ new_broadcast_arg="broadcast $new_broadcast_address"
+fi
+if [ x$old_broadcast_address != x ]; then
+ old_broadcast_arg="broadcast $old_broadcast_address"
+fi
+if [ x$new_subnet_mask != x ]; then
+ new_netmask_arg="netmask $new_subnet_mask"
+fi
+if [ x$old_subnet_mask != x ]; then
+ old_netmask_arg="netmask $old_subnet_mask"
+fi
+if [ x$alias_subnet_mask != x ]; then
+ alias_subnet_arg="netmask $alias_subnet_mask"
+fi
+if [ x$new_interface_mtu != x ]; then
+ mtu_arg="mtu $new_interface_mtu"
+fi
+if [ x$IF_METRIC != x ]; then
+ metric_arg="metric $IF_METRIC"
+fi
+
+if [ x$reason = xMEDIUM ]; then
+ eval "ifconfig $interface $medium"
+ eval "ifconfig $interface inet -alias 0.0.0.0 $medium" >/dev/null 2>&1
+ sleep 1
+ exit_with_hooks 0
+fi
+
+###
+### DHCPv4 Handlers
+###
+
+if [ x$reason = xPREINIT ]; then
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ ifconfig $interface inet 0.0.0.0 netmask 0.0.0.0 \
+ broadcast 255.255.255.255 up
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xARPCHECK ] || [ x$reason = xARPSEND ]; then
+ exit_with_hooks 0;
+fi
+
+if [ x$reason = xBOUND ] || [ x$reason = xRENEW ] || \
+ [ x$reason = xREBIND ] || [ x$reason = xREBOOT ]; then
+ current_hostname=`hostname`
+ if [ x$current_hostname = x ] || \
+ [ x$current_hostname = x$old_host_name ]; then
+ if [ x$current_hostname = x ] || \
+ [ x$new_host_name != x$old_host_name ]; then
+ hostname $new_host_name
+ fi
+ fi
+
+ if [ x$old_ip_address != x ] && [ x$alias_ip_address != x ] && \
+ [ x$alias_ip_address != x$old_ip_address ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ if [ x$old_ip_address != x ] && [ x$old_ip_address != x$new_ip_address ]
+ then
+ eval "ifconfig $interface inet -alias $old_ip_address $medium"
+ route delete $old_ip_address 127.1 >/dev/null 2>&1
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ if [ "$old_static_routes" != "" ]; then
+ set $old_static_routes
+ while [ $# -gt 1 ]; do
+ route delete $1 $2
+ shift; shift
+ done
+ fi
+ arp -n -a | sed -n -e 's/^.*(\(.*\)) at .*$/arp -n -d \1/p' |sh
+ fi
+ if [ x$old_ip_address = x ] || [ x$old_ip_address != x$new_ip_address ] || \
+ [ x$reason = xBOUND ] || [ x$reason = xREBOOT ]; then
+ eval "ifconfig $interface inet $new_ip_address $new_netmask_arg \
+ $new_broadcast_arg $mtu_arg $metric_arg $medium"
+ route add $new_ip_address 127.1 >/dev/null 2>&1
+ for router in $new_routers; do
+ route add default $router >/dev/null 2>&1
+ done
+ if [ "$new_static_routes" != "" ]; then
+ set $new_static_routes
+ while [ $# -gt 1 ]; do
+ route add $1 $2
+ shift; shift
+ done
+ fi
+ else
+ # we haven't changed the address, have we changed other options
+ # that we wish to update?
+ if [ x$new_routers != x ] && [ x$new_routers != x$old_routers ] ; then
+ # if we've changed routers delete the old and add the new.
+ $LOGGER "New Routers: $new_routers"
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ for router in $new_routers; do
+ route add default $router >/dev/null 2>&1
+ done
+ fi
+ fi
+ if [ x$new_ip_address != x$alias_ip_address ] && [ x$alias_ip_address != x ];
+ then
+ ifconfig $interface inet alias $alias_ip_address $alias_subnet_arg
+ route add $alias_ip_address 127.0.0.1
+ fi
+ make_resolv_conf
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xEXPIRE ] || [ x$reason = xFAIL ] || [ x$reason = xRELEASE ] \
+ || [ x$reason = xSTOP ]; then
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ if [ x$old_ip_address != x ]; then
+ eval "ifconfig $interface inet -alias $old_ip_address $medium"
+ route delete $old_ip_address 127.1 >/dev/null 2>&1
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ if [ "$old_static_routes" != "" ]; then
+ set $old_static_routes
+ while [ $# -gt 1 ]; do
+ route delete $1 $2
+ shift; shift
+ done
+ fi
+ arp -n -a | sed -n -e 's/^.*(\(.*\)) at .*$/arp -n -d \1/p' \
+ |sh >/dev/null 2>&1
+ fi
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet alias $alias_ip_address $alias_subnet_arg
+ route add $alias_ip_address 127.0.0.1
+ fi
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xTIMEOUT ]; then
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ eval "ifconfig $interface inet $new_ip_address $new_netmask_arg \
+ $new_broadcast_arg $mtu_arg $metric_arg $medium"
+ sleep 1
+ if [ "$new_routers" != "" ]; then
+ set $new_routers
+ if ping -q -c 1 -w 1 $1; then
+ if [ x$new_ip_address != x$alias_ip_address ] && \
+ [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet alias $alias_ip_address $alias_subnet_arg
+ route add $alias_ip_address 127.0.0.1
+ fi
+ route add $new_ip_address 127.1 >/dev/null 2>&1
+ for router in $new_routers; do
+ route add default $router >/dev/null 2>&1
+ done
+ set $new_static_routes
+ while [ $# -gt 1 ]; do
+ route add $0 $1
+ shift; shift
+ done
+ make_resolv_conf
+ exit_with_hooks 0
+ fi
+ fi
+ eval "ifconfig $interface inet -alias $new_ip_address $medium"
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ if [ "$old_static_routes" != "" ]; then
+ set $old_static_routes
+ while [ $# -gt 1 ]; do
+ route delete $1 $2
+ shift; shift
+ done
+ fi
+ arp -n -a | sed -n -e 's/^.*(\(.*\)) at .*$/arp -n -d \1/p' \
+ |sh >/dev/null 2>&1
+ exit_with_hooks 1
+fi
+
+###
+### DHCPv6 Handlers
+###
+
+if [ ${reason} = PREINIT6 ] ; then
+ # Ensure interface is up.
+ ifconfig ${interface} up
+
+ # XXX: Remove any stale addresses from aborted clients.
+
+ exit_with_hooks 0
+fi
+
+if [ x${old_ip6_prefix} != x ] || [ x${new_ip6_prefix} != x ] ; then
+ echo Prefix ${reason} old=${old_ip6_prefix} new=${new_ip6_prefix}
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = BOUND6 ] ; then
+ if [ x${new_ip6_address} = x ] || [ x${new_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ ifconfig ${interface} inet6 add ${new_ip6_address}/${new_ip6_prefixlen}
+
+ # Check for nameserver options.
+ make_resolv_conf
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = RENEW6 ] || [ ${reason} = REBIND6 ] ; then
+ # Make sure nothing has moved around on us.
+
+ # Nameservers/domains/etc.
+ if [ "x${new_dhcp6_name_servers}" != "x${old_dhcp6_name_servers}" ] ||
+ [ "x${new_dhcp6_domain_search}" != "x${old_dhcp6_domain_search}" ] ; then
+ make_resolv_conf
+ fi
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = DEPREF6 ] ; then
+ if [ x${new_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ # XXX:
+ # There doesn't appear to be a way to update an addr to indicate
+ # preference.
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = EXPIRE6 -o ${reason} = RELEASE6 -o ${reason} = STOP6 ] ; then
+ if [ x${old_ip6_address} = x ] || [ x${old_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ ifconfig ${interface} inet6 delete ${old_ip6_address}/${old_ip6_prefixlen}
+
+ exit_with_hooks 0
+fi
+
+exit_with_hooks 0
diff --git a/client/scripts/freebsd b/client/scripts/freebsd
new file mode 100755
index 0000000..dd939f7
--- /dev/null
+++ b/client/scripts/freebsd
@@ -0,0 +1,392 @@
+#!/bin/sh
+#
+# $Id: freebsd,v 1.23.54.1 2011/05/18 20:01:54 sar Exp $
+#
+# $FreeBSD$
+
+if [ -x /usr/bin/logger ]; then
+ LOGGER="/usr/bin/logger -s -p user.notice -t dhclient"
+else
+ LOGGER=echo
+fi
+
+make_resolv_conf() {
+ if [ x"$new_domain_name_servers" != x ]; then
+ ( cat /dev/null > /etc/resolv.conf.dhclient )
+ exit_status=$?
+ if [ $exit_status -ne 0 ]; then
+ $LOGGER "Unable to create /etc/resolv.conf.dhclient: Error $exit_status"
+ else
+ if [ "x$new_domain_search" != x ]; then
+ ( echo search $new_domain_search >> /etc/resolv.conf.dhclient )
+ exit_status=$?
+ elif [ "x$new_domain_name" != x ]; then
+ # Note that the DHCP 'Domain Name Option' is really just a domain
+ # name, and that this practice of using the domain name option as
+ # a search path is both nonstandard and deprecated.
+ ( echo search $new_domain_name >> /etc/resolv.conf.dhclient )
+ exit_status=$?
+ fi
+ for nameserver in $new_domain_name_servers; do
+ if [ $exit_status -ne 0 ]; then
+ break
+ fi
+ ( echo nameserver $nameserver >>/etc/resolv.conf.dhclient )
+ exit_status=$?
+ done
+
+ # If there were no errors, attempt to mv the new file into place.
+ if [ $exit_status -eq 0 ]; then
+ ( mv /etc/resolv.conf.dhclient /etc/resolv.conf )
+ exit_status=$?
+ fi
+
+ if [ $exit_status -ne 0 ]; then
+ $LOGGER "Error while writing new /etc/resolv.conf."
+ fi
+ fi
+ elif [ "x${new_dhcp6_name_servers}" != x ] ; then
+ ( cat /dev/null > /etc/resolv.conf.dhclient6 )
+ exit_status=$?
+ if [ $exit_status -ne 0 ] ; then
+ $LOGGER "Unable to create /etc/resolv.conf.dhclient6: Error $exit_status"
+ else
+ if [ "x${new_dhcp6_domain_search}" != x ] ; then
+ ( echo search ${new_dhcp6_domain_search} >> /etc/resolv.conf.dhclient6 )
+ exit_status=$?
+ fi
+ for nameserver in ${new_dhcp6_name_servers} ; do
+ if [ $exit_status -ne 0 ] ; then
+ break
+ fi
+ # If the nameserver has a link-local address
+ # add a <zone_id> (interface name) to it.
+ case $nameserver in
+ fe80:*) zone_id="%$interface";;
+ FE80:*) zone_id="%$interface";;
+ *) zone_id="";;
+ esac
+ ( echo nameserver ${nameserver}$zone_id >> /etc/resolv.conf.dhclient6 )
+ exit_status=$?
+ done
+
+ if [ $exit_status -eq 0 ] ; then
+ ( mv /etc/resolv.conf.dhclient6 /etc/resolv.conf )
+ exit_status=$?
+ fi
+
+ if [ $exit_status -ne 0 ] ; then
+ $LOGGER "Error while writing new /etc/resolv.conf."
+ fi
+ fi
+ fi
+}
+
+# Must be used on exit. Invokes the local dhcp client exit hooks, if any.
+exit_with_hooks() {
+ exit_status=$1
+ if [ -f /etc/dhclient-exit-hooks ]; then
+ . /etc/dhclient-exit-hooks
+ fi
+# probably should do something with exit status of the local script
+ exit $exit_status
+}
+
+# Invoke the local dhcp client enter hooks, if they exist.
+if [ -f /etc/dhclient-enter-hooks ]; then
+ exit_status=0
+ . /etc/dhclient-enter-hooks
+ # allow the local script to abort processing of this state
+ # local script must set exit_status variable to nonzero.
+ if [ $exit_status -ne 0 ]; then
+ exit $exit_status
+ fi
+fi
+
+if [ x$new_network_number != x ]; then
+ $LOGGER New Network Number: $new_network_number
+fi
+
+if [ x$new_broadcast_address != x ]; then
+ $LOGGER New Broadcast Address: $new_broadcast_address
+ new_broadcast_arg="broadcast $new_broadcast_address"
+fi
+if [ x$old_broadcast_address != x ]; then
+ old_broadcast_arg="broadcast $old_broadcast_address"
+fi
+if [ x$new_subnet_mask != x ]; then
+ new_netmask_arg="netmask $new_subnet_mask"
+fi
+if [ x$old_subnet_mask != x ]; then
+ old_netmask_arg="netmask $old_subnet_mask"
+fi
+if [ x$alias_subnet_mask != x ]; then
+ alias_subnet_arg="netmask $alias_subnet_mask"
+fi
+if [ x$new_interface_mtu != x ]; then
+ mtu_arg="mtu $new_interface_mtu"
+fi
+if [ x$IF_METRIC != x ]; then
+ metric_arg="metric $IF_METRIC"
+fi
+
+if [ x$reason = xMEDIUM ]; then
+ eval "ifconfig $interface $medium"
+ eval "ifconfig $interface inet -alias 0.0.0.0 $medium" >/dev/null 2>&1
+ sleep 1
+ exit_with_hooks 0
+fi
+
+###
+### DHCPv4 Handlers
+###
+
+if [ x$reason = xPREINIT ]; then
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ ifconfig $interface inet 0.0.0.0 netmask 0.0.0.0 \
+ broadcast 255.255.255.255 up
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xARPCHECK ] || [ x$reason = xARPSEND ]; then
+ exit_with_hooks 0;
+fi
+
+if [ x$reason = xBOUND ] || [ x$reason = xRENEW ] || \
+ [ x$reason = xREBIND ] || [ x$reason = xREBOOT ]; then
+ current_hostname=`/bin/hostname`
+ if [ x$current_hostname = x ] || \
+ [ x$current_hostname = x$old_host_name ]; then
+ if [ x$current_hostname = x ] || \
+ [ x$new_host_name != x$old_host_name ]; then
+ $LOGGER "New Hostname: $new_host_name"
+ hostname $new_host_name
+ fi
+ fi
+ if [ x$old_ip_address != x ] && [ x$alias_ip_address != x ] && \
+ [ x$alias_ip_address != x$old_ip_address ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ if [ x$old_ip_address != x ] && [ x$old_ip_address != x$new_ip_address ]
+ then
+ eval "ifconfig $interface inet -alias $old_ip_address $medium"
+ route delete $old_ip_address 127.1 >/dev/null 2>&1
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ if [ -n "$old_static_routes" ]; then
+ set -- $old_static_routes
+ while [ $# -gt 1 ]; do
+ route delete $1 $2
+ shift; shift
+ done
+ fi
+ arp -n -a | sed -n -e 's/^.*(\(.*\)) at .*$/arp -d \1/p' |sh
+ fi
+ if [ x$old_ip_address = x ] || [ x$old_ip_address != x$new_ip_address ] || \
+ [ x$reason = xBOUND ] || [ x$reason = xREBOOT ]; then
+ eval "ifconfig $interface inet $new_ip_address $new_netmask_arg \
+ $new_broadcast_arg $mtu_arg $metric_arg $medium"
+ $LOGGER "New IP Address ($interface): $new_ip_address"
+ $LOGGER "New Subnet Mask ($interface): $new_subnet_mask"
+ $LOGGER "New Broadcast Address ($interface): $new_broadcast_address"
+ if [ -n "$new_routers" ]; then
+ $LOGGER "New Routers: $new_routers"
+ fi
+ route add $new_ip_address 127.1 >/dev/null 2>&1
+ for router in $new_routers; do
+ # If the subnet is captive, eg the netmask is /32 but the default
+ # gateway is (obviously) outside of this, then we need to produce a
+ # host route to reach the gateway.
+ if [ "x$new_subnet_mask" = "x255.255.255.255" ] ; then
+ route add -host $router -interface $interface
+ fi
+ route add default $router >/dev/null 2>&1
+ done
+ if [ -n "$new_static_routes" ]; then
+ $LOGGER "New Static Routes: $new_static_routes"
+ set -- $new_static_routes
+ while [ $# -gt 1 ]; do
+ route add $1 $2
+ shift; shift
+ done
+ fi
+ else
+ # we haven't changed the address, have we changed other options
+ # that we wish to update?
+ if [ x$new_routers != x ] && [ x$new_routers != x$old_routers ] ; then
+ # if we've changed routers delete the old and add the new.
+ $LOGGER "New Routers: $new_routers"
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ for router in $new_routers; do
+ # If the subnet is captive, eg the netmask is /32 but the default
+ # gateway is (obviously) outside of this, then we need to produce a
+ # host route to reach the gateway.
+ if [ "x$new_subnet_mask" = "x255.255.255.255" ] ; then
+ route add -host $router -interface $interface
+ fi
+ route add default $router >/dev/null 2>&1
+ done
+ fi
+ fi
+ if [ x$new_ip_address != x$alias_ip_address ] && [ x$alias_ip_address != x ];
+ then
+ ifconfig $interface inet alias $alias_ip_address $alias_subnet_arg
+ route add $alias_ip_address 127.0.0.1
+ fi
+ make_resolv_conf
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xEXPIRE ] || [ x$reason = xFAIL ] || [ x$reason = xRELEASE ] \
+ || [ x$reason = xSTOP ]; then
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ if [ x$old_ip_address != x ]; then
+ eval "ifconfig $interface inet -alias $old_ip_address $medium"
+ route delete $old_ip_address 127.1 >/dev/null 2>&1
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ if [ -n "$old_static_routes" ]; then
+ set -- $old_static_routes
+ while [ $# -gt 1 ]; do
+ route delete $1 $2
+ shift; shift
+ done
+ fi
+ arp -n -a | sed -n -e 's/^.*(\(.*\)) at .*$/arp -d \1/p' \
+ |sh >/dev/null 2>&1
+ fi
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet alias $alias_ip_address $alias_subnet_arg
+ route add $alias_ip_address 127.0.0.1
+ fi
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xTIMEOUT ]; then
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ eval "ifconfig $interface inet $new_ip_address $new_netmask_arg \
+ $new_broadcast_arg $mtu_arg $metric_arg $medium"
+ $LOGGER "New IP Address ($interface): $new_ip_address"
+ $LOGGER "New Subnet Mask ($interface): $new_subnet_mask"
+ $LOGGER "New Broadcast Address ($interface): $new_broadcast_address"
+ sleep 1
+ if [ -n "$new_routers" ]; then
+ $LOGGER "New Routers: $new_routers"
+ set -- $new_routers
+ if ping -q -c 1 $1; then
+ if [ x$new_ip_address != x$alias_ip_address ] && \
+ [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet alias $alias_ip_address $alias_subnet_arg
+ route add $alias_ip_address 127.0.0.1
+ fi
+ route add $new_ip_address 127.1 >/dev/null 2>&1
+ for router in $new_routers; do
+ if [ "x$new_subnet_mask" = "x255.255.255.255" ] ; then
+ route add -host $router -interface $interface
+ fi
+ route add default $router >/dev/null 2>&1
+ done
+ set -- $new_static_routes
+ while [ $# -gt 1 ]; do
+ route add $1 $2
+ shift; shift
+ done
+ make_resolv_conf
+ exit_with_hooks 0
+ fi
+ fi
+ eval "ifconfig $interface inet -alias $new_ip_address $medium"
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ if [ -n "$old_static_routes" ]; then
+ set -- $old_static_routes
+ while [ $# -gt 1 ]; do
+ route delete $1 $2
+ shift; shift
+ done
+ fi
+ arp -n -a | sed -n -e 's/^.*(\(.*\)) at .*$/arp -d \1/p' \
+ |sh >/dev/null 2>&1
+ exit_with_hooks 1
+fi
+
+###
+### DHCPv6 Handlers
+###
+
+if [ ${reason} = PREINIT6 ] ; then
+ # Ensure interface is up.
+ ifconfig ${interface} up
+
+ # XXX: Remove any stale addresses from aborted clients.
+
+ exit_with_hooks 0
+fi
+
+if [ x${old_ip6_prefix} != x ] || [ x${new_ip6_prefix} != x ] ; then
+ echo Prefix ${reason} old=${old_ip6_prefix} new=${new_ip6_prefix}
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = BOUND6 ] ; then
+ if [ x${new_ip6_address} = x ] || [ x${new_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ ifconfig ${interface} inet6 ${new_ip6_address}/${new_ip6_prefixlen} alias
+
+ # Check for nameserver options.
+ make_resolv_conf
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = RENEW6 ] || [ ${reason} = REBIND6 ] ; then
+ # Make sure nothing has moved around on us.
+
+ # Nameservers/domains/etc.
+ if [ "x${new_dhcp6_name_servers}" != "x${old_dhcp6_name_servers}" ] ||
+ [ "x${new_dhcp6_domain_search}" != "x${old_dhcp6_domain_search}" ] ; then
+ make_resolv_conf
+ fi
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = DEPREF6 ] ; then
+ if [ x${new_ip6_address} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ ifconfig ${interface} inet6 ${new_ip6_address} deprecated
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = EXPIRE6 -o ${reason} = RELEASE6 -o ${reason} = STOP6 ] ; then
+ if [ x${old_ip6_address} = x ] || [ x${old_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ ifconfig ${interface} inet6 ${old_ip6_address}/${old_ip6_prefixlen} -alias
+
+ exit_with_hooks 0
+fi
+
+exit_with_hooks 0
diff --git a/client/scripts/linux b/client/scripts/linux
new file mode 100755
index 0000000..e6792c6
--- /dev/null
+++ b/client/scripts/linux
@@ -0,0 +1,316 @@
+#!/bin/bash
+# dhclient-script for Linux. Dan Halbert, March, 1997.
+# Updated for Linux 2.[12] by Brian J. Murrell, January 1999.
+# No guarantees about this. I'm a novice at the details of Linux
+# networking.
+
+# Notes:
+
+# 0. This script is based on the netbsd script supplied with dhcp-970306.
+
+# 1. ifconfig down apparently deletes all relevant routes and flushes
+# the arp cache, so this doesn't need to be done explicitly.
+
+# 2. The alias address handling here has not been tested AT ALL.
+# I'm just going by the doc of modern Linux ip aliasing, which uses
+# notations like eth0:0, eth0:1, for each alias.
+
+# 3. I have to calculate the network address, and calculate the broadcast
+# address if it is not supplied. This might be much more easily done
+# by the dhclient C code, and passed on.
+
+# 4. TIMEOUT not tested. ping has a flag I don't know, and I'm suspicious
+# of the $1 in its args.
+
+# 'ip' just looks too weird. /sbin/ip looks less weird.
+ip=/sbin/ip
+
+make_resolv_conf() {
+ if [ x"$new_domain_name_servers" != x ]; then
+ cat /dev/null > /etc/resolv.conf.dhclient
+ chmod 644 /etc/resolv.conf.dhclient
+ if [ x"$new_domain_search" != x ]; then
+ echo search $new_domain_search >> /etc/resolv.conf.dhclient
+ elif [ x"$new_domain_name" != x ]; then
+ # Note that the DHCP 'Domain Name Option' is really just a domain
+ # name, and that this practice of using the domain name option as
+ # a search path is both nonstandard and deprecated.
+ echo search $new_domain_name >> /etc/resolv.conf.dhclient
+ fi
+ for nameserver in $new_domain_name_servers; do
+ echo nameserver $nameserver >>/etc/resolv.conf.dhclient
+ done
+
+ mv /etc/resolv.conf.dhclient /etc/resolv.conf
+ elif [ "x${new_dhcp6_name_servers}" != x ] ; then
+ cat /dev/null > /etc/resolv.conf.dhclient6
+ chmod 644 /etc/resolv.conf.dhclient6
+
+ if [ "x${new_dhcp6_domain_search}" != x ] ; then
+ echo search ${new_dhcp6_domain_search} >> /etc/resolv.conf.dhclient6
+ fi
+ shopt -s nocasematch
+ for nameserver in ${new_dhcp6_name_servers} ; do
+ # If the nameserver has a link-local address
+ # add a <zone_id> (interface name) to it.
+ if [[ "$nameserver" =~ ^fe80:: ]]
+ then
+ zone_id="%$interface"
+ else
+ zone_id=
+ fi
+ echo nameserver ${nameserver}$zone_id >> /etc/resolv.conf.dhclient6
+ done
+ shopt -u nocasematch
+
+ mv /etc/resolv.conf.dhclient6 /etc/resolv.conf
+ fi
+}
+
+# Must be used on exit. Invokes the local dhcp client exit hooks, if any.
+exit_with_hooks() {
+ exit_status=$1
+ if [ -f /etc/dhclient-exit-hooks ]; then
+ . /etc/dhclient-exit-hooks
+ fi
+# probably should do something with exit status of the local script
+ exit $exit_status
+}
+
+# Invoke the local dhcp client enter hooks, if they exist.
+if [ -f /etc/dhclient-enter-hooks ]; then
+ exit_status=0
+ . /etc/dhclient-enter-hooks
+ # allow the local script to abort processing of this state
+ # local script must set exit_status variable to nonzero.
+ if [ $exit_status -ne 0 ]; then
+ exit $exit_status
+ fi
+fi
+
+###
+### DHCPv4 Handlers
+###
+
+if [ x$new_broadcast_address != x ]; then
+ new_broadcast_arg="broadcast $new_broadcast_address"
+fi
+if [ x$old_broadcast_address != x ]; then
+ old_broadcast_arg="broadcast $old_broadcast_address"
+fi
+if [ x$new_subnet_mask != x ]; then
+ new_subnet_arg="netmask $new_subnet_mask"
+fi
+if [ x$old_subnet_mask != x ]; then
+ old_subnet_arg="netmask $old_subnet_mask"
+fi
+if [ x$alias_subnet_mask != x ]; then
+ alias_subnet_arg="netmask $alias_subnet_mask"
+fi
+if [ x$new_interface_mtu != x ]; then
+ mtu_arg="mtu $new_interface_mtu"
+fi
+if [ x$IF_METRIC != x ]; then
+ metric_arg="metric $IF_METRIC"
+fi
+
+if [ x$reason = xMEDIUM ]; then
+ # Linux doesn't do mediums (ok, ok, media).
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xPREINIT ]; then
+ if [ x$alias_ip_address != x ]; then
+ # Bring down alias interface. Its routes will disappear too.
+ ifconfig $interface:0- inet 0
+ fi
+ ifconfig $interface 0 up
+
+ # We need to give the kernel some time to get the interface up.
+ sleep 1
+
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xARPCHECK ] || [ x$reason = xARPSEND ]; then
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xBOUND ] || [ x$reason = xRENEW ] || \
+ [ x$reason = xREBIND ] || [ x$reason = xREBOOT ]; then
+ current_hostname=`hostname`
+ if [ x$current_hostname = x ] || \
+ [ x$current_hostname = "x(none)" ] || \
+ [ x$current_hostname = xlocalhost ] || \
+ [ x$current_hostname = x$old_host_name ]; then
+ if [ x$new_host_name != x$old_host_name ]; then
+ hostname "$new_host_name"
+ fi
+ fi
+
+ if [ x$old_ip_address != x ] && [ x$alias_ip_address != x ] && \
+ [ x$alias_ip_address != x$old_ip_address ]; then
+ # Possible new alias. Remove old alias.
+ ifconfig $interface:0- inet 0
+ fi
+ if [ x$old_ip_address != x ] && [ x$old_ip_address != x$new_ip_address ]; then
+ # IP address changed. Bringing down the interface will delete all routes,
+ # and clear the ARP cache.
+ ifconfig $interface inet 0 down
+
+ fi
+ if [ x$old_ip_address = x ] || [ x$old_ip_address != x$new_ip_address ] || \
+ [ x$reason = xBOUND ] || [ x$reason = xREBOOT ]; then
+
+ ifconfig $interface inet $new_ip_address $new_subnet_arg \
+ $new_broadcast_arg $mtu_arg
+ # Add a network route to the computed network address.
+ for router in $new_routers; do
+ if [ "x$new_subnet_mask" = "x255.255.255.255" ] ; then
+ route add -host $router dev $interface
+ fi
+ route add default gw $router $metric_arg dev $interface
+ done
+ else
+ # we haven't changed the address, have we changed other options
+ # that we wish to update?
+ if [ x$new_routers != x ] && [ x$new_routers != x$old_routers ] ; then
+ # if we've changed routers delete the old and add the new.
+ for router in $old_routers; do
+ route del default gw $router
+ done
+ for router in $new_routers; do
+ if [ "x$new_subnet_mask" = "x255.255.255.255" ] ; then
+ route add -host $router dev $interface
+ fi
+ route add default gw $router $metric_arg dev $interface
+ done
+ fi
+ fi
+ if [ x$new_ip_address != x$alias_ip_address ] && [ x$alias_ip_address != x ];
+ then
+ ifconfig $interface:0- inet 0
+ ifconfig $interface:0 inet $alias_ip_address $alias_subnet_arg
+ route add -host $alias_ip_address $interface:0
+ fi
+ make_resolv_conf
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xEXPIRE ] || [ x$reason = xFAIL ] || [ x$reason = xRELEASE ] \
+ || [ x$reason = xSTOP ]; then
+ if [ x$alias_ip_address != x ]; then
+ # Turn off alias interface.
+ ifconfig $interface:0- inet 0
+ fi
+ if [ x$old_ip_address != x ]; then
+ # Shut down interface, which will delete routes and clear arp cache.
+ ifconfig $interface inet 0 down
+ fi
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface:0 inet $alias_ip_address $alias_subnet_arg
+ route add -host $alias_ip_address $interface:0
+ fi
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xTIMEOUT ]; then
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface:0- inet 0
+ fi
+ ifconfig $interface inet $new_ip_address $new_subnet_arg \
+ $new_broadcast_arg $mtu_arg
+ set $new_routers
+ if ping -q -c 1 $1; then
+ if [ x$new_ip_address != x$alias_ip_address ] && \
+ [ x$alias_ip_address != x ]; then
+ ifconfig $interface:0 inet $alias_ip_address $alias_subnet_arg
+ route add -host $alias_ip_address dev $interface:0
+ fi
+ for router in $new_routers; do
+ if [ "x$new_subnet_mask" = "x255.255.255.255" ] ; then
+ route add -host $router dev $interface
+ fi
+ route add default gw $router $metric_arg dev $interface
+ done
+ make_resolv_conf
+ exit_with_hooks 0
+ fi
+ ifconfig $interface inet 0 down
+ exit_with_hooks 1
+fi
+
+###
+### DHCPv6 Handlers
+###
+
+if [ x$reason = xPREINIT6 ] ; then
+ # Ensure interface is up.
+ ${ip} link set ${interface} up
+
+ # Remove any stale addresses from aborted clients.
+ ${ip} -f inet6 addr flush dev ${interface} scope global permanent
+
+ exit_with_hooks 0
+fi
+
+if [ x${old_ip6_prefix} != x ] || [ x${new_ip6_prefix} != x ] ; then
+ echo Prefix ${reason} old=${old_ip6_prefix} new=${new_ip6_prefix}
+
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xBOUND6 ] ; then
+ if [ x${new_ip6_address} = x ] || [ x${new_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ ${ip} -f inet6 addr add ${new_ip6_address}/${new_ip6_prefixlen} \
+ dev ${interface} scope global
+
+ # Check for nameserver options.
+ make_resolv_conf
+
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xRENEW6 ] || [ x$reason = xREBIND6 ] ; then
+ if [ x${new_ip6_address} != x ] && [ x${new_ip6_prefixlen} != x ] ; then
+ ${ip} -f inet6 addr add ${new_ip6_address}/${new_ip6_prefixlen} \
+ dev ${interface} scope global
+ fi
+
+ # Make sure nothing has moved around on us.
+
+ # Nameservers/domains/etc.
+ if [ "x${new_dhcp6_name_servers}" != "x${old_dhcp6_name_servers}" ] ||
+ [ "x${new_dhcp6_domain_search}" != "x${old_dhcp6_domain_search}" ] ; then
+ make_resolv_conf
+ fi
+
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xDEPREF6 ] ; then
+ if [ x${new_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ ${ip} -f inet6 addr change ${new_ip6_address}/${new_ip6_prefixlen} \
+ dev ${interface} scope global preferred_lft 0
+
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xEXPIRE6 -o x$reason = xRELEASE6 -o x$reason = xSTOP6 ] ; then
+ if [ x${old_ip6_address} = x ] || [ x${old_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ ${ip} -f inet6 addr del ${old_ip6_address}/${old_ip6_prefixlen} \
+ dev ${interface}
+
+ exit_with_hooks 0
+fi
+
+exit_with_hooks 0
diff --git a/client/scripts/macos b/client/scripts/macos
new file mode 100755
index 0000000..22360bd
--- /dev/null
+++ b/client/scripts/macos
@@ -0,0 +1,207 @@
+#!/bin/sh
+#
+# $Id: macos,v 1.2.108.2 2011/09/20 17:04:03 sar Exp $
+#
+# automous run of this script will commit the DNS setting
+#
+
+if [ -x /usr/bin/logger ]; then
+ LOGGER="/usr/bin/logger -s -p user.notice -t dhclient"
+else
+ LOGGER=echo
+fi
+
+to_commit="yes"
+
+make_resolv_conf() {
+ to_commit="no"
+ if [ "x${new_dhcp6_name_servers}" != x ]; then
+ ( cat /dev/null > /var/run/resolv.conf.dhclient6 )
+ exit_status=$?
+ if [ $exit_status -ne 0 ]; then
+ $LOGGER "Unable to create /var/run/resolv.conf.dhclient6: Error $exit_status"
+ else
+ if [ "x${new_dhcp6_domain_search}" != x ]; then
+ ( echo search ${new_dhcp6_domain_search} >> /var/run/resolv.conf.dhclient6 )
+ exit_status=$?
+ fi
+ for nameserver in ${new_dhcp6_name_servers} ; do
+ if [ $exit_status -ne 0 ]; then
+ break
+ fi
+ # If the nameserver has a link-local address
+ # add a <zone_id> (interface name) to it.
+ case $nameserver in
+ fe80:*) zone_id="%$interface";;
+ FE80:*) zone_id="%$interface";;
+ *) zone_id="";;
+ esac
+ ( echo nameserver ${nameserver}$zone_id >> /etc/resolv.conf.dhclient6 )
+ exit_status=$?
+ done
+
+ if [ $exit_status -eq 0 ]; then
+ to_commit="force"
+ commit_resolv_conf
+ fi
+ fi
+ fi
+}
+
+# Try to commit /var/run/resolv.conf.dhclient6 contents to
+# System Configuration framework's Dynamic Store.
+# Note this will be cleared by the next location change
+# or preempted by IPv4.
+#
+# The System Configuration agent "IPMonitor" gets the DNS configuration
+# from the IPv4 or IPv6 primary service in the Dynamic Store
+# (managed by configd).
+commit_resolv_conf() {
+ if [ -f /var/run/resolv.conf.dhclient6 ]; then
+ if [ -x /usr/sbin/scutil ]; then
+ serviceID=`echo show State:/Network/Global/IPv6 | \
+ /usr/sbin/scutil | \
+ awk '/PrimaryService/ { print $3 }'`
+ echo $serviceID
+ if [ x$serviceID = x ]; then
+ $LOGGER "Can't find the primary IPv6 service"
+ else
+ tmp=`mktemp SC_dhclient6.XXXXXXXXXX`
+ echo list | /usr/sbin/scutil > /tmp/$tmp
+ grep -q State:/Network/Service/$serviceID/DNS /tmp/$tmp
+ grep_status=$?
+ if [ $grep_status -eq 0 ]; then
+ $LOGGER "DNS service already set in primary IPv6 service"
+ rm /tmp/$tmp
+ else
+ res=/var/run/resolv.conf.dhclient6
+ cp /dev/null /tmp/$tmp
+ grep -q '^nameserver' $res
+ grep_status=$?
+ if [ $grep_status -eq 0 ]; then
+ echo d.add ServerAddresses '*' \
+ `awk 'BEGIN { n="" } \
+ /^nameserver/ { n=n " " $2 } \
+ END { print n}' < $res` >> /tmp/$tmp
+ fi
+ grep -q '^search' $res
+ grep_status=$?
+ if [ $grep_status -eq 0 ]; then
+ echo d.add SearchDomains '*' \
+ `sed 's/^search//' < $res` >> /tmp/$tmp
+ fi
+ echo set State:/Network/Service/$serviceID/DNS >> /tmp/$tmp
+ echo quit >> /tmp/$tmp
+ cat /tmp/$tmp
+ /usr/sbin/scutil < /tmp/$tmp
+ rm /tmp/$tmp
+ fi
+ fi
+ else
+ $LOGGER "Can't find SystemConfiguration tools."
+ fi
+ else
+ if [ $to_commit = force ]; then
+ $LOGGER "Can't find /var/run/resolv.conf.dhclient6"
+ fi
+ fi
+ to_commit="done"
+}
+
+# Must be used on exit. Invokes the local dhcp client exit hooks, if any.
+exit_with_hooks() {
+ exit_status=$1
+ if [ -f /etc/dhclient-exit-hooks ]; then
+ . /etc/dhclient-exit-hooks
+ fi
+# probably should do something with exit status of the local script
+ exit $exit_status
+}
+
+# Invoke the local dhcp client enter hooks, if they exist.
+if [ -f /etc/dhclient-enter-hooks ]; then
+ exit_status=0
+ . /etc/dhclient-enter-hooks
+ # allow the local script to abort processing of this state
+ # local script must set exit_status variable to nonzero.
+ if [ $exit_status -ne 0 ]; then
+ exit $exit_status
+ fi
+fi
+
+if [ x$reason = xMEDIUM ]; then
+ eval "ifconfig $interface $medium"
+ eval "ifconfig $interface inet -alias 0.0.0.0 $medium" >/dev/null 2>&1
+ sleep 1
+ exit_with_hooks 0
+fi
+
+###
+### DHCPv6 Handlers
+###
+
+if [ x$reason = xPREINIT6 ]; then
+ # Ensure interface is up.
+ ifconfig ${interface} up
+
+ # XXX: Remove any stale addresses from aborted clients.
+
+ exit_with_hooks 0
+fi
+
+if [ x${old_ip6_prefix} != x ] || [ x${new_ip6_prefix} != x ]; then
+ echo Prefix $reason old=${old_ip6_prefix} new=${new_ip6_prefix}
+
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xBOUND6 ]; then
+ if [ x${new_ip6_address} = x ] || [ x${new_ip6_prefixlen} = x ]; then
+ exit_with_hooks 2;
+ fi
+
+ ifconfig ${interface} inet6 ${new_ip6_address}/${new_ip6_prefixlen} alias
+
+ # Check for nameserver options.
+ make_resolv_conf
+
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xRENEW6 ] || [ x$reason = xREBIND6 ]; then
+ # Make sure nothing has moved around on us.
+
+ # Nameservers/domains/etc.
+ if [ "x${new_dhcp6_name_servers}" != "x${old_dhcp6_name_servers}" ] ||
+ [ "x${new_dhcp6_domain_search}" != "x${old_dhcp6_domain_search}" ]; then
+ make_resolv_conf
+ fi
+
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xDEPREF6 ]; then
+ if [ x${new_ip6_address} = x ]; then
+ exit_with_hooks 2;
+ fi
+
+ ifconfig ${interface} inet6 ${new_ip6_address} deprecated
+
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xEXPIRE6 -o x$reason = xRELEASE6 -o x$reason = xSTOP6 ]; then
+ if [ x${old_ip6_address} = x ] || [ x${old_ip6_prefixlen} = x ]; then
+ exit_with_hooks 2;
+ fi
+
+ ifconfig ${interface} inet6 ${old_ip6_address}/${old_ip6_prefixlen} -alias
+
+ exit_with_hooks 0
+fi
+
+if [ $to_commit = yes ]; then
+ commit_resolv_conf
+fi
+
+exit_with_hooks 0
diff --git a/client/scripts/netbsd b/client/scripts/netbsd
new file mode 100755
index 0000000..8a5007e
--- /dev/null
+++ b/client/scripts/netbsd
@@ -0,0 +1,324 @@
+#!/bin/sh
+
+make_resolv_conf() {
+ if [ "x$new_domain_name" != x ] && [ x"$new_domain_name_servers" != x ]; then
+ cat /dev/null > /etc/resolv.conf.dhclient
+ if [ "x$new_domain_search" != x ]; then
+ echo search $new_domain_search >> /etc/resolv.conf.dhclient
+ elif [ "x$new_domain_name" != x ]; then
+ # Note that the DHCP 'Domain Name Option' is really just a domain
+ # name, and that this practice of using the domain name option as
+ # a search path is both nonstandard and deprecated.
+ echo search $new_domain_name >> /etc/resolv.conf.dhclient
+ fi
+ for nameserver in $new_domain_name_servers; do
+ echo nameserver $nameserver >>/etc/resolv.conf.dhclient
+ done
+
+ mv /etc/resolv.conf.dhclient /etc/resolv.conf
+ elif [ "x${new_dhcp6_name_servers}" != x ] ; then
+ cat /dev/null > /etc/resolv.conf.dhclient6
+ chmod 644 /etc/resolv.conf.dhclient6
+
+ if [ "x${new_dhcp6_domain_search}" != x ] ; then
+ echo search ${new_dhcp6_domain_search} >> /etc/resolv.conf.dhclient6
+ fi
+ for nameserver in ${new_dhcp6_name_servers} ; do
+ # If the nameserver has a link-local address
+ # add a <zone_id> (interface name) to it.
+ case $nameserver in
+ fe80:*) zone_id="%$interface";;
+ FE80:*) zone_id="%$interface";;
+ *) zone_id="";;
+ esac
+ echo nameserver ${nameserver}$zone_id >> /etc/resolv.conf.dhclient6
+ done
+
+ mv /etc/resolv.conf.dhclient6 /etc/resolv.conf
+ fi
+}
+
+# Must be used on exit. Invokes the local dhcp client exit hooks, if any.
+exit_with_hooks() {
+ exit_status=$1
+ if [ -f /etc/dhclient-exit-hooks ]; then
+ . /etc/dhclient-exit-hooks
+ fi
+# probably should do something with exit status of the local script
+ exit $exit_status
+}
+
+# Invoke the local dhcp client enter hooks, if they exist.
+if [ -f /etc/dhclient-enter-hooks ]; then
+ exit_status=0
+ . /etc/dhclient-enter-hooks
+ # allow the local script to abort processing of this state
+ # local script must set exit_status variable to nonzero.
+ if [ $exit_status -ne 0 ]; then
+ exit $exit_status
+ fi
+fi
+
+if [ x$new_network_number != x ]; then
+ echo New Network Number: $new_network_number
+fi
+
+if [ x$new_broadcast_address != x ]; then
+ echo New Broadcast Address: $new_broadcast_address
+ new_broadcast_arg="broadcast $new_broadcast_address"
+fi
+if [ x$old_broadcast_address != x ]; then
+ old_broadcast_arg="broadcast $old_broadcast_address"
+fi
+if [ x$new_subnet_mask != x ]; then
+ new_netmask_arg="netmask $new_subnet_mask"
+fi
+if [ x$old_subnet_mask != x ]; then
+ old_netmask_arg="netmask $old_subnet_mask"
+fi
+if [ x$alias_subnet_mask != x ]; then
+ alias_subnet_arg="netmask $alias_subnet_mask"
+fi
+ if [ x$new_interface_mtu != x ]; then
+ mtu_arg="mtu $new_interface_mtu"
+ fi
+if [ x$IF_METRIC != x ]; then
+ metric_arg="metric $IF_METRIC"
+fi
+
+if [ x$reason = xMEDIUM ]; then
+ eval "ifconfig $interface $medium"
+ eval "ifconfig $interface inet -alias 0.0.0.0 $medium" >/dev/null 2>&1
+ sleep 1
+ exit_with_hooks 0
+fi
+
+###
+### DHCPv4 Handlers
+###
+
+if [ x$reason = xPREINIT ]; then
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ ifconfig $interface inet 0.0.0.0 netmask 0.0.0.0 \
+ broadcast 255.255.255.255 up
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xARPCHECK ] || [ x$reason = xARPSEND ]; then
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xBOUND ] || [ x$reason = xRENEW ] || \
+ [ x$reason = xREBIND ] || [ x$reason = xREBOOT ]; then
+ current_hostname=`hostname`
+ if [ x$current_hostname = x ] || \
+ [ x$current_hostname = x$old_host_name ]; then
+ if [ x$current_hostname = x ] || \
+ [ x$new_host_name != x$old_host_name ]; then
+ hostname $new_host_name
+ fi
+ fi
+
+ if [ x$old_ip_address != x ] && [ x$alias_ip_address != x ] && \
+ [ x$alias_ip_address != x$old_ip_address ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ if [ x$old_ip_address != x ] && [ x$old_ip_address != x$new_ip_address ]
+ then
+ eval "ifconfig $interface inet -alias $old_ip_address $medium"
+ route delete $old_ip_address 127.1 >/dev/null 2>&1
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ if [ "$old_static_routes" != "" ]; then
+ set $old_static_routes
+ while [ $# -gt 1 ]; do
+ route delete $1 $2
+ shift; shift
+ done
+ fi
+ arp -n -a | sed -n -e 's/^.*(\(.*\)) at .*$/arp -n -d \1/p' |sh
+ fi
+ if [ x$old_ip_address = x ] || [ x$old_ip_address != x$new_ip_address ] || \
+ [ x$reason = xBOUND ] || [ x$reason = xREBOOT ]; then
+ eval "ifconfig $interface inet $new_ip_address $new_netmask_arg \
+ $new_broadcast_arg $mtu_arg $metric_arg $medium"
+ route add $new_ip_address 127.1 >/dev/null 2>&1
+ for router in $new_routers; do
+ route add default $router >/dev/null 2>&1
+ done
+ if [ "$new_static_routes" != "" ]; then
+ set $new_static_routes
+ while [ $# -gt 1 ]; do
+ route add $1 $2
+ shift; shift
+ done
+ fi
+ else
+ # we haven't changed the address, have we changed other options
+ # that we wish to update?
+ if [ x$new_routers != x ] && [ x$new_routers != x$old_routers ] ; then
+ # if we've changed routers delete the old and add the new.
+ $LOGGER "New Routers: $new_routers"
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ for router in $new_routers; do
+ route add default $router >/dev/null 2>&1
+ done
+ fi
+ fi
+ if [ x$new_ip_address != x$alias_ip_address ] && [ x$alias_ip_address != x ];
+ then
+ ifconfig $interface inet alias $alias_ip_address $alias_subnet_arg
+ route add $alias_ip_address 127.0.0.1
+ fi
+ make_resolv_conf
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xEXPIRE ] || [ x$reason = xFAIL ] || [ x$reason = xRELEASE ] \
+ || [ x$reason = xSTOP ]; then
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ if [ x$old_ip_address != x ]; then
+ eval "ifconfig $interface inet -alias $old_ip_address $medium"
+ route delete $old_ip_address 127.1 >/dev/null 2>&1
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ if [ "$old_static_routes" != "" ]; then
+ set $old_static_routes
+ while [ $# -gt 1 ]; do
+ route delete $1 $2
+ shift; shift
+ done
+ fi
+ arp -n -a | sed -n -e 's/^.*(\(.*\)) at .*$/arp -n -d \1/p' \
+ |sh >/dev/null 2>&1
+ fi
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet alias $alias_ip_address $alias_subnet_arg
+ route add $alias_ip_address 127.0.0.1
+ fi
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xTIMEOUT ]; then
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ eval "ifconfig $interface inet $new_ip_address $new_netmask_arg \
+ $new_broadcast_arg $mtu_arg $metric_arg $medium"
+ sleep 1
+ if [ "$new_routers" != "" ]; then
+ set $new_routers
+ if ping -q -c 1 -w 1 $1; then
+ if [ x$new_ip_address != x$alias_ip_address ] && \
+ [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet alias $alias_ip_address $alias_subnet_arg
+ route add $alias_ip_address 127.0.0.1
+ fi
+ route add $new_ip_address 127.1 >/dev/null 2>&1
+ for router in $new_routers; do
+ route add default $router >/dev/null 2>&1
+ done
+ set $new_static_routes
+ while [ $# -gt 1 ]; do
+ route add $0 $1
+ shift; shift
+ done
+ make_resolv_conf
+ exit_with_hooks 0
+ fi
+ fi
+ eval "ifconfig $interface inet -alias $new_ip_address $medium"
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ if [ "$old_static_routes" != "" ]; then
+ set $old_static_routes
+ while [ $# -gt 1 ]; do
+ route delete $1 $2
+ shift; shift
+ done
+ fi
+ arp -n -a | sed -n -e 's/^.*(\(.*\)) at .*$/arp -n -d \1/p' \
+ |sh >/dev/null 2>&1
+ exit_with_hooks 1
+fi
+
+###
+### DHCPv6 Handlers
+###
+
+if [ ${reason} = PREINIT6 ] ; then
+ # Ensure interface is up.
+ ifconfig ${interface} up
+
+ # XXX: Remove any stale addresses from aborted clients.
+
+ exit_with_hooks 0
+fi
+
+if [ x${old_ip6_prefix} != x ] || [ x${new_ip6_prefix} != x ] ; then
+ echo Prefix ${reason} old=${old_ip6_prefix} new=${new_ip6_prefix}
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = BOUND6 ] ; then
+ if [ x${new_ip6_address} = x ] || [ x${new_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ ifconfig ${interface} inet6 add ${new_ip6_address}/${new_ip6_prefixlen}
+
+ # Check for nameserver options.
+ make_resolv_conf
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = RENEW6 ] || [ ${reason} = REBIND6 ] ; then
+ # Make sure nothing has moved around on us.
+
+ # Nameservers/domains/etc.
+ if [ "x${new_dhcp6_name_servers}" != "x${old_dhcp6_name_servers}" ] ||
+ [ "x${new_dhcp6_domain_search}" != "x${old_dhcp6_domain_search}" ] ; then
+ make_resolv_conf
+ fi
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = DEPREF6 ] ; then
+ if [ x${new_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ # XXX:
+ # There doesn't appear to be a way to update an addr to indicate
+ # preference.
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = EXPIRE6 -o ${reason} = RELEASE6 -o ${reason} = STOP6 ] ; then
+ if [ x${old_ip6_address} = x ] || [ x${old_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ ifconfig ${interface} inet6 delete ${old_ip6_address}/${old_ip6_prefixlen}
+
+ exit_with_hooks 0
+fi
+
+exit_with_hooks 0
diff --git a/client/scripts/nextstep b/client/scripts/nextstep
new file mode 100644
index 0000000..9273c5a
--- /dev/null
+++ b/client/scripts/nextstep
@@ -0,0 +1,61 @@
+#!/bin/sh
+#
+# simplified dhclient-script for NeXTSTEP/OPENSTEP
+#
+# removed a lot of the cruft from the netbsd version since NeXTSTEP doesn't
+# support aliases and lots of things were breaking for no good reason
+#
+# 14 Sep 1997, David W. Young
+#
+if [ x$reason = xPREINIT ]; then
+ ifconfig $interface inet 0.0.0.0 netmask 0.0.0.0 up >/dev/null 2>&1
+ exit 0
+fi
+if [ x$reason = xBOUND ] || [ x$reason = xRENEW ] || \
+ [ x$reason = xREBIND ] || [ x$reason = xREBOOT ]; then
+ current_hostname=`hostname`
+ if [ x$current_hostname = x ] || \
+ [ x$current_hostname = x$old_host_name ]; then
+ if [ x$current_hostname = x ] || \
+ [ x$new_host_name != x$old_host_name ]; then
+ hostname $new_host_name
+ fi
+ fi
+
+ if [ x$old_ip_address != x ] && [ x$old_ip_address != x$new_ip_address ]
+ then
+ ifconfig $interface $new_ip_address netmask $new_subnet_mask \
+ >/dev/null 2>&1
+ route add $new_ip_address 127.1 0 >/dev/null 2>&1
+ for router in $new_routers ; do
+ route add default $router 1 >/dev/null 2>&1
+ done
+ fi
+ if [ x"$new_domain_name_servers" != x ]; then
+ cat /dev/null > /etc/resolv.conf.dhclient
+ if [ "x$new_domain_search" != x ]; then
+ echo search $new_domain_search >> /etc/resolv.conf.dhclient
+ elif [ "x$new_domain_name" != x ]; then
+ # Note that the DHCP 'Domain Name Option' is really just a domain
+ # name, and that this practice of using the domain name option as
+ # a search path is both nonstandard and deprecated.
+ echo search $new_domain_name >> /etc/resolv.conf.dhclient
+ fi
+ for nameserver in $new_domain_name_servers; do
+ echo nameserver $nameserver >>/etc/resolv.conf.dhclient
+ done
+
+ mv /etc/resolv.conf.dhclient /etc/resolv.conf
+ fi
+ exit 0
+fi
+if [ x$reason = xEXPIRE ] || [ x$reason = xFAIL ] || [ x$reason = xRELEASE ] \
+ || [ x$reason = xSTOP ]; then
+ if [ x$old_ip_address != x ]; then
+ route delete $old_ip_address 127.1 >/dev/null 2>&1
+ for $router in $old_routers ; do
+ route delete default $router >/dev/null 2>&1
+ done
+ fi
+ exit 0
+fi
diff --git a/client/scripts/openbsd b/client/scripts/openbsd
new file mode 100644
index 0000000..f20d0ff
--- /dev/null
+++ b/client/scripts/openbsd
@@ -0,0 +1,318 @@
+#!/bin/sh
+
+make_resolv_conf() {
+ if [ x"$new_domain_name_servers" != x ]; then
+ cat /dev/null > /etc/resolv.conf.dhclient
+ if [ x"$new_domain_search" != x ]; then
+ echo search $new_domain_search >> /etc/resolv.conf.dhclient
+ elif [ x"$new_domain_name" != x ]; then
+ # Note that the DHCP 'Domain Name Option' is really just a domain
+ # name, and that this practice of using the domain name option as
+ # a search path is both nonstandard and deprecated.
+ echo search $new_domain_name >> /etc/resolv.conf.dhclient
+ fi
+ for nameserver in $new_domain_name_servers; do
+ echo nameserver $nameserver >>/etc/resolv.conf.dhclient
+ done
+
+ mv /etc/resolv.conf.dhclient /etc/resolv.conf
+ elif [ "x${new_dhcp6_name_servers}" != x ] ; then
+ cat /dev/null > /etc/resolv.conf.dhclient6
+ chmod 644 /etc/resolv.conf.dhclient6
+
+ if [ "x${new_dhcp6_domain_search}" != x ] ; then
+ echo search ${new_dhcp6_domain_search} >> /etc/resolv.conf.dhclient6
+ fi
+ for nameserver in ${new_dhcp6_name_servers} ; do
+ # If the nameserver has a link-local address
+ # add a <zone_id> (interface name) to it.
+ case $nameserver in
+ fe80:*) zone_id="%$interface";;
+ FE80:*) zone_id="%$interface";;
+ *) zone_id="";;
+ esac
+ echo nameserver ${nameserver}$zone_id >> /etc/resolv.conf.dhclient6
+ done
+
+ mv /etc/resolv.conf.dhclient6 /etc/resolv.conf
+ fi
+}
+
+# Must be used on exit. Invokes the local dhcp client exit hooks, if any.
+exit_with_hooks() {
+ exit_status=$1
+ if [ -f /etc/dhclient-exit-hooks ]; then
+ . /etc/dhclient-exit-hooks
+ fi
+# probably should do something with exit status of the local script
+ exit $exit_status
+}
+
+# Invoke the local dhcp client enter hooks, if they exist.
+if [ -f /etc/dhclient-enter-hooks ]; then
+ exit_status=0
+ . /etc/dhclient-enter-hooks
+ # allow the local script to abort processing of this state
+ # local script must set exit_status variable to nonzero.
+ if [ $exit_status -ne 0 ]; then
+ exit $exit_status
+ fi
+fi
+
+if [ x$new_network_number != x ]; then
+ echo New Network Number: $new_network_number
+fi
+
+if [ x$new_broadcast_address != x ]; then
+ echo New Broadcast Address: $new_broadcast_address
+ new_broadcast_arg="broadcast $new_broadcast_address"
+fi
+if [ x$old_broadcast_address != x ]; then
+ old_broadcast_arg="broadcast $old_broadcast_address"
+fi
+if [ x$new_subnet_mask != x ]; then
+ new_netmask_arg="netmask $new_subnet_mask"
+fi
+if [ x$old_subnet_mask != x ]; then
+ old_netmask_arg="netmask $old_subnet_mask"
+fi
+if [ x$alias_subnet_mask != x ]; then
+ alias_subnet_arg="netmask $alias_subnet_mask"
+fi
+
+if [ x$reason = xMEDIUM ]; then
+ eval "ifconfig $interface $medium"
+ eval "ifconfig $interface inet -alias 0.0.0.0 $medium" >/dev/null 2>&1
+ sleep 1
+ exit_with_hooks 0
+fi
+
+###
+### DHCPv4 Handlers
+###
+
+if [ x$reason = xPREINIT ]; then
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ ifconfig $interface inet 0.0.0.0 netmask 0.0.0.0 \
+ broadcast 255.255.255.255 up
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xARPCHECK ] || [ x$reason = xARPSEND ]; then
+ exit_with_hooks 0;
+fi
+
+if [ x$reason = xBOUND ] || [ x$reason = xRENEW ] || \
+ [ x$reason = xREBIND ] || [ x$reason = xREBOOT ]; then
+ current_hostname=`hostname`
+ if [ x$current_hostname = x ] || \
+ [ x$current_hostname = x$old_host_name ]; then
+ if [ x$current_hostname = x ] || \
+ [ x$new_host_name != x$old_host_name ]; then
+ hostname $new_host_name
+ fi
+ fi
+
+ if [ x$old_ip_address != x ] && [ x$alias_ip_address != x ] && \
+ [ x$alias_ip_address != x$old_ip_address ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ if [ x$old_ip_address != x ] && [ x$old_ip_address != x$new_ip_address ]
+ then
+ eval "ifconfig $interface inet -alias $old_ip_address $medium"
+ route delete $old_ip_address 127.1 >/dev/null 2>&1
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ if [ "$old_static_routes" != "" ]; then
+ set $old_static_routes
+ while [ $# -gt 1 ]; do
+ route delete $1 $2
+ shift; shift
+ done
+ fi
+ arp -n -a | sed -n -e 's/^.*(\(.*\)) at .*$/arp -n -d \1/p' |sh
+ fi
+ if [ x$old_ip_address = x ] || [ x$old_ip_address != x$new_ip_address ] || \
+ [ x$reason = xBOUND ] || [ x$reason = xREBOOT ]; then
+ eval "ifconfig $interface inet $new_ip_address $new_netmask_arg \
+ $new_broadcast_arg $medium"
+ route add $new_ip_address 127.1 >/dev/null 2>&1
+ for router in $new_routers; do
+ route add default $router >/dev/null 2>&1
+ done
+ if [ "$new_static_routes" != "" ]; then
+ set $new_static_routes
+ while [ $# -gt 1 ]; do
+ route add $1 $2
+ shift; shift
+ done
+ fi
+ else
+ # we haven't changed the address, have we changed other options
+ # that we wish to update?
+ if [ x$new_routers != x ] && [ x$new_routers != x$old_routers ] ; then
+ # if we've changed routers delete the old and add the new.
+ $LOGGER "New Routers: $new_routers"
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ for router in $new_routers; do
+ route add default $router >/dev/null 2>&1
+ done
+ fi
+ fi
+ if [ x$new_ip_address != x$alias_ip_address ] && [ x$alias_ip_address != x ];
+ then
+ ifconfig $interface inet alias $alias_ip_address $alias_subnet_arg
+ route add $alias_ip_address 127.0.0.1
+ fi
+ make_resolv_conf
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xEXPIRE ] || [ x$reason = xFAIL ] || [ x$reason = xRELEASE ] \
+ || [ x$reason = xSTOP ]; then
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ if [ x$old_ip_address != x ]; then
+ eval "ifconfig $interface inet -alias $old_ip_address $medium"
+ route delete $old_ip_address 127.1 >/dev/null 2>&1
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ if [ "$old_static_routes" != "" ]; then
+ set $old_static_routes
+ while [ $# -gt 1 ]; do
+ route delete $1 $2
+ shift; shift
+ done
+ fi
+ arp -n -a | sed -n -e 's/^.*(\(.*\)) at .*$/arp -n -d \1/p' \
+ |sh >/dev/null 2>&1
+ fi
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet alias $alias_ip_address $alias_subnet_arg
+ route add $alias_ip_address 127.0.0.1
+ fi
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xTIMEOUT ]; then
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet -alias $alias_ip_address > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ eval "ifconfig $interface inet $new_ip_address $new_netmask_arg \
+ $new_broadcast_arg $medium"
+ sleep 1
+ if [ "$new_routers" != "" ]; then
+ set $new_routers
+ if ping -q -c 1 -w 1 $1; then
+ if [ x$new_ip_address != x$alias_ip_address ] && \
+ [ x$alias_ip_address != x ]; then
+ ifconfig $interface inet alias $alias_ip_address $alias_subnet_arg
+ route add $alias_ip_address 127.0.0.1
+ fi
+ route add $new_ip_address 127.1 >/dev/null 2>&1
+ for router in $new_routers; do
+ route add default $router >/dev/null 2>&1
+ done
+ set $new_static_routes
+ while [ $# -gt 1 ]; do
+ route add $0 $1
+ shift; shift
+ done
+ make_resolv_conf
+ exit_with_hooks 0
+ fi
+ fi
+ eval "ifconfig $interface inet -alias $new_ip_address $medium"
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ if [ "$old_static_routes" != "" ]; then
+ set $old_static_routes
+ while [ $# -gt 1 ]; do
+ route delete $1 $2
+ shift; shift
+ done
+ fi
+ arp -n -a | sed -n -e 's/^.*(\(.*\)) at .*$/arp -n -d \1/p' \
+ |sh >/dev/null 2>&1
+ exit_with_hooks 1
+fi
+
+###
+### DHCPv6 Handlers
+###
+
+if [ ${reason} = PREINIT6 ] ; then
+ # Ensure interface is up.
+ ifconfig ${interface} up
+
+ # XXX: Remove any stale addresses from aborted clients.
+
+ exit_with_hooks 0
+fi
+
+if [ x${old_ip6_prefix} != x ] || [ x${new_ip6_prefix} != x ] ; then
+ echo Prefix ${reason} old=${old_ip6_prefix} new=${new_ip6_prefix}
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = BOUND6 ] ; then
+ if [ x${new_ip6_address} = x ] || [ x${new_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ ifconfig ${interface} inet6 add ${new_ip6_address}/${new_ip6_prefixlen}
+
+ # Check for nameserver options.
+ make_resolv_conf
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = RENEW6 ] || [ ${reason} = REBIND6 ] ; then
+ # Make sure nothing has moved around on us.
+
+ # Nameservers/domains/etc.
+ if [ "x${new_dhcp6_name_servers}" != "x${old_dhcp6_name_servers}" ] ||
+ [ "x${new_dhcp6_domain_search}" != "x${old_dhcp6_domain_search}" ] ; then
+ make_resolv_conf
+ fi
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = DEPREF6 ] ; then
+ if [ x${new_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ # XXX:
+ # There doesn't appear to be a way to update an addr to indicate
+ # preference.
+
+ exit_with_hooks 0
+fi
+
+if [ ${reason} = EXPIRE6 -o ${reason} = RELEASE6 -o ${reason} = STOP6 ] ; then
+ if [ x${old_ip6_address} = x ] || [ x${old_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ ifconfig ${interface} inet6 delete ${old_ip6_address}/${old_ip6_prefixlen}
+
+ exit_with_hooks 0
+fi
+
+exit_with_hooks 0
diff --git a/client/scripts/openwrt b/client/scripts/openwrt
new file mode 100755
index 0000000..55a4aa9
--- /dev/null
+++ b/client/scripts/openwrt
@@ -0,0 +1,285 @@
+#!/bin/sh
+
+# 'ip' just looks too weird. /sbin/ip looks less weird.
+ip=/usr/sbin/ip
+
+make_resolv_conf() {
+ if [ x"$new_domain_name_servers" != x ]; then
+ cat /dev/null > /etc/resolv.conf.dhclient
+ chmod 644 /etc/resolv.conf.dhclient
+ if [ x"$new_domain_search" != x ]; then
+ echo search $new_domain_search >> /etc/resolv.conf.dhclient
+ elif [ x"$new_domain_name" != x ]; then
+ # Note that the DHCP 'Domain Name Option' is really just a domain
+ # name, and that this practice of using the domain name option as
+ # a search path is both nonstandard and deprecated.
+ echo search $new_domain_name >> /etc/resolv.conf.dhclient
+ fi
+ for nameserver in $new_domain_name_servers; do
+ echo nameserver $nameserver >>/etc/resolv.conf.dhclient
+ done
+
+ mv /etc/resolv.conf.dhclient /etc/resolv.conf
+ elif [ "x${new_dhcp6_name_servers}" != x ] ; then
+ cat /dev/null > /etc/resolv.conf.dhclient6
+ chmod 644 /etc/resolv.conf.dhclient6
+
+ if [ "x${new_dhcp6_domain_search}" != x ] ; then
+ echo search ${new_dhcp6_domain_search} >> /etc/resolv.conf.dhclient6
+ fi
+ for nameserver in ${new_dhcp6_name_servers} ; do
+ # If the nameserver has a link-local address
+ # add a <zone_id> (interface name) to it.
+ case $nameserver in
+ fe80:*) zone_id="%$interface";;
+ FE80:*) zone_id="%$interface";;
+ *) zone_id="";;
+ esac
+ echo nameserver ${nameserver}$zone_id >> /etc/resolv.conf.dhclient6
+ done
+
+ mv /etc/resolv.conf.dhclient6 /etc/resolv.conf
+ fi
+}
+
+# Must be used on exit. Invokes the local dhcp client exit hooks, if any.
+exit_with_hooks() {
+ exit_status=$1
+ if [ -f /etc/dhclient-exit-hooks ]; then
+ . /etc/dhclient-exit-hooks
+ fi
+# probably should do something with exit status of the local script
+ exit $exit_status
+}
+
+# Invoke the local dhcp client enter hooks, if they exist.
+if [ -f /etc/dhclient-enter-hooks ]; then
+ exit_status=0
+ . /etc/dhclient-enter-hooks
+ # allow the local script to abort processing of this state
+ # local script must set exit_status variable to nonzero.
+ if [ $exit_status -ne 0 ]; then
+ exit $exit_status
+ fi
+fi
+
+###
+### DHCPv4 Handlers
+###
+
+if [ x$new_broadcast_address != x ]; then
+ new_broadcast_arg="broadcast $new_broadcast_address"
+fi
+if [ x$new_subnet_mask != x ]; then
+ new_subnet_arg="netmask $new_subnet_mask"
+fi
+if [ x$alias_subnet_mask != x ]; then
+ alias_subnet_arg="netmask $alias_subnet_mask"
+fi
+if [ x$new_interface_mtu != x ]; then
+ mtu_arg="mtu $new_interface_mtu"
+fi
+if [ x$IF_METRIC != x ]; then
+ metric_arg="metric $IF_METRIC"
+fi
+
+if [ x$reason = xMEDIUM ]; then
+ # Linux doesn't do mediums (ok, ok, media).
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xPREINIT ]; then
+ if [ x$alias_ip_address != x ]; then
+ # Bring down alias interface. Its routes will disappear too.
+ ifconfig $interface:0- 0.0.0.0
+ fi
+ ifconfig $interface 0.0.0.0 up
+
+ # We need to give the kernel some time to get the interface up.
+ sleep 1
+
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xARPCHECK ] || [ x$reason = xARPSEND ]; then
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xBOUND ] || [ x$reason = xRENEW ] || \
+ [ x$reason = xREBIND ] || [ x$reason = xREBOOT ]; then
+ current_hostname=`hostname`
+ if [ x$current_hostname = x ] || \
+ [ x$current_hostname = x$old_host_name ]; then
+ if [ x$current_hostname = x ] || \
+ [ x$new_host_name != x$old_host_name ]; then
+ hostname $new_host_name
+ fi
+ fi
+
+ if [ x$old_ip_address != x ] && [ x$alias_ip_address != x ] && \
+ [ x$alias_ip_address != x$old_ip_address ]; then
+ # Possible new alias. Remove old alias.
+ ifconfig $interface:0- 0.0.0.0
+ fi
+ if [ x$old_ip_address != x ] && \
+ [ x$old_ip_address != x$new_ip_address ]; then
+ # IP address changed. Bringing down the interface will delete all routes,
+ # and clear the ARP cache.
+ ifconfig $interface 0.0.0.0 down
+
+ fi
+ if [ x$old_ip_address = x ] || [ x$old_ip_address != x$new_ip_address ] || \
+ [ x$reason = xBOUND ] || [ x$reason = xREBOOT ]; then
+
+ ifconfig $interface $new_ip_address $new_subnet_arg \
+ $new_broadcast_arg $mtu_arg
+ for router in $new_routers; do
+ if [ "x$new_subnet_mask" = "x255.255.255.255" ] ; then
+ route add -host $router dev $interface
+ fi
+ route add default gw $router $metric_arg dev $interface
+ done
+ else
+ # we haven't changed the address, have we changed other options
+ # that we wish to update?
+ if [ x$new_routers != x ] && [ x$new_routers != x$old_routers ] ; then
+ # if we've changed routers delete the old and add the new.
+ $LOGGER "New Routers: $new_routers"
+ for router in $old_routers; do
+ route del default gw $router
+ done
+ for router in $new_routers; do
+ if [ "x$new_subnet_mask" = "x255.255.255.255" ] ; then
+ route add -host $router dev $interface
+ fi
+ route add default gw $router $metric_arg dev $interface
+ done
+ fi
+ fi
+ if [ x$new_ip_address != x$alias_ip_address ] && [ x$alias_ip_address != x ];
+ then
+ ifconfig $interface:0- 0.0.0.0
+ ifconfig $interface:0 $alias_ip_address $alias_subnet_arg
+ route add -host $alias_ip_address $interface:0
+ fi
+ make_resolv_conf
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xEXPIRE ] || [ x$reason = xFAIL ] || [ x$reason = xRELEASE ] \
+ || [ x$reason = xSTOP ]; then
+ if [ x$alias_ip_address != x ]; then
+ # Turn off alias interface.
+ ifconfig $interface:0- 0.0.0.0
+ fi
+ if [ x$old_ip_address != x ]; then
+ # Shut down interface, which will delete routes and clear arp cache.
+ ifconfig $interface 0.0.0.0 down
+ fi
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface:0 $alias_ip_address $alias_subnet_arg
+ route add -host $alias_ip_address $interface:0
+ fi
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xTIMEOUT ]; then
+ if [ x$alias_ip_address != x ]; then
+ ifconfig $interface:0- 0.0.0.0
+ fi
+ ifconfig $interface $new_ip_address $new_subnet_arg \
+ $new_broadcast_arg $mtu_arg
+ set $new_routers
+ if ping -q -c 1 $1; then
+ if [ x$new_ip_address != x$alias_ip_address ] && \
+ [ x$alias_ip_address != x ]; then
+ ifconfig $interface:0 $alias_ip_address $alias_subnet_arg
+ route add -host $alias_ip_address dev $interface:0
+ fi
+ for router in $new_routers; do
+ if [ "x$new_subnet_mask" = "x255.255.255.255" ] ; then
+ route add -host $router dev $interface
+ fi
+ route add default gw $router $metric_arg dev $interface
+ done
+ make_resolv_conf
+ exit_with_hooks 0
+ fi
+ ifconfig $interface 0.0.0.0 down
+ exit_with_hooks 1
+fi
+
+###
+### DHCPv6 Handlers
+###
+
+if [ x$reason = xPREINIT6 ]; then
+ # Ensure interface is up.
+ ${ip} link set ${interface} up
+
+ # Remove any stale addresses from aborted clients.
+ ${ip} -f inet6 addr flush dev ${interface} scope global permanent
+
+ exit_with_hooks 0
+fi
+
+if [ x${old_ip6_prefix} != x ] || [ x${new_ip6_prefix} != x ] ; then
+ echo Prefix ${reason} old=${old_ip6_prefix} new=${new_ip6_prefix}
+
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xBOUND6 ]; then
+ if [ x${new_ip6_address} = x ] || [ x${new_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ ${ip} -f inet6 addr add ${new_ip6_address}/${new_ip6_prefixlen} \
+ dev ${interface} scope global
+
+ # Check for nameserver options.
+ make_resolv_conf
+
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xRENEW6 ] || [ x$reason = xREBIND6 ]; then
+ if [ x${new_ip6_address} != x ] && [ x${new_ip6_prefixlen} != x ] ; then
+ ${ip} -f inet6 addr add ${new_ip6_address}/${new_ip6_prefixlen} \
+ dev ${interface} scope global
+ fi
+
+ # Make sure nothing has moved around on us.
+
+ # Nameservers/domains/etc.
+ if [ "x${new_dhcp6_name_servers}" != "x${old_dhcp6_name_servers}" ] ||
+ [ "x${new_dhcp6_domain_search}" != "x${old_dhcp6_domain_search}" ] ; then
+ make_resolv_conf
+ fi
+
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xDEPREF6 ]; then
+ if [ x${new_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ ${ip} -f inet6 addr change ${new_ip6_address}/${new_ip6_prefixlen} \
+ dev ${interface} scope global preferred_lft 0
+
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xEXPIRE6 -o x$reason = xRELEASE6 -o x$reason = xSTOP6 ]; then
+ if [ x${old_ip6_address} = x ] || [ x${old_ip6_prefixlen} = x ] ; then
+ exit_with_hooks 2;
+ fi
+
+ ${ip} -f inet6 addr del ${old_ip6_address}/${old_ip6_prefixlen} \
+ dev ${interface}
+
+ exit_with_hooks 0
+fi
+
+exit_with_hooks 0
diff --git a/client/scripts/solaris b/client/scripts/solaris
new file mode 100755
index 0000000..af553b9
--- /dev/null
+++ b/client/scripts/solaris
@@ -0,0 +1,203 @@
+#!/bin/sh
+
+make_resolv_conf() {
+ if [ x"$new_domain_name_servers" != x ]; then
+ cat /dev/null > /etc/resolv.conf.dhclient
+ if [ x"$new_domain_search" != x ]; then
+ echo search $new_domain_search >> /etc/resolv.conf.dhclient
+ elif [ x"$new_domain_name" != x ]; then
+ # Note that the DHCP 'Domain Name Option' is really just a domain
+ # name, and that this practice of using the domain name option as
+ # a search path is both nonstandard and deprecated.
+ echo search $new_domain_name >> /etc/resolv.conf.dhclient
+ fi
+ for nameserver in $new_domain_name_servers; do
+ echo nameserver $nameserver >>/etc/resolv.conf.dhclient
+ done
+
+ mv /etc/resolv.conf.dhclient /etc/resolv.conf
+ fi
+}
+
+# Must be used on exit. Invokes the local dhcp client exit hooks, if any.
+exit_with_hooks() {
+ exit_status=$1
+ if [ -f /etc/dhclient-exit-hooks ]; then
+ . /etc/dhclient-exit-hooks
+ fi
+# probably should do something with exit status of the local script
+ exit $exit_status
+}
+
+# Invoke the local dhcp client enter hooks, if they exist.
+if [ -f /etc/dhclient-enter-hooks ]; then
+ exit_status=0
+ . /etc/dhclient-enter-hooks
+ # allow the local script to abort processing of this state
+ # local script must set exit_status variable to nonzero.
+ if [ $exit_status -ne 0 ]; then
+ exit $exit_status
+ fi
+fi
+
+if [ x$new_broadcast_address != x ]; then
+ new_broadcast_arg="broadcast $new_broadcast_address"
+fi
+if [ x$old_broadcast_address != x ]; then
+ old_broadcast_arg="broadcast $old_broadcast_address"
+fi
+if [ x$new_subnet_mask != x ]; then
+ new_netmask_arg="netmask $new_subnet_mask"
+fi
+if [ x$old_subnet_mask != x ]; then
+ old_netmask_arg="netmask $old_subnet_mask"
+fi
+if [ x$alias_subnet_mask != x ]; then
+ alias_subnet_arg="netmask $alias_subnet_mask"
+fi
+ if [ x$new_interface_mtu != x ]; then
+ mtu_arg="mtu $new_interface_mtu"
+ fi
+if [ x$IF_METRIC != x ]; then
+ metric_arg="metric $IF_METRIC"
+fi
+
+ifconfig=/sbin/ifconfig
+
+release=`uname -r`
+release=`expr $release : '\(.*\)\..*'`
+relmajor=`echo $release |sed -e 's/^\([^\.]*\)\..*$/\1/'`
+relminor=`echo $release |sed -e 's/^.*\.\([^\.]*\)$/\1/'`
+
+if [ x$reason = xMEDIUM ]; then
+ eval "$ifconfig $interface $medium"
+ $ifconfig $interface
+ sleep 1
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xPREINIT ]; then
+ if [ x$alias_ip_address != x ]; then
+ $ifconfig ${interface}:1 0 down > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ if [ $relmajor -gt 5 ] || ( [ $relmajor -eq 5 ] && [ $relminor -ge 5 ] )
+ then
+ # Turn the interface on
+ $ifconfig $interface plumb
+ $ifconfig $interface up
+ else
+ $ifconfig $interface inet 0.0.0.0 netmask 0.0.0.0 \
+ broadcast 255.255.255.255 up
+ fi
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xARPCHECK ] || [ x$reason = xARPSEND ]; then
+ exit_with_hooks 0;
+fi
+
+if [ x$reason = xBOUND ] || [ x$reason = xRENEW ] || \
+ [ x$reason = xREBIND ] || [ x$reason = xREBOOT ]; then
+ current_hostname=`hostname`
+ if [ x$current_hostname = x ] || \
+ [ x$current_hostname = x$old_host_name ]; then
+ if [ x$current_hostname = x ] || \
+ [ x$new_host_name != x$old_host_name ]; then
+ hostname $new_host_name
+ fi
+ fi
+
+ if [ x$old_ip_address != x ] && [ x$alias_ip_address != x ] && \
+ [ x$alias_ip_address != x$old_ip_address ]; then
+ $ifconfig ${interface}:1 inet 0 down > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ if [ x$old_ip_address != x ] && [ x$old_ip_address != x$new_ip_address ]; then
+ $ifconfig ${interface} inet 0 down
+ route delete $old_ip_address 127.1 >/dev/null 2>&1
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ fi
+ if [ x$old_ip_address = x ] || [ x$old_ip_address != x$new_ip_address ] || \
+ [ x$reason = xBOUND ] || [ x$reason = xREBOOT ]; then
+ eval "$ifconfig $interface inet $new_ip_address $new_netmask_arg \
+ $new_broadcast_arg $mtu_arg $metric_arg $medium"
+ route add $new_ip_address 127.1 1 >/dev/null 2>&1
+ for router in $new_routers; do
+ route add default $router 1 >/dev/null 2>&1
+ done
+ else
+ # we haven't changed the address, have we changed other options
+ # that we wish to update?
+ if [ x$new_routers != x ] && [ x$new_routers != x$old_routers ] ; then
+ # if we've changed routers delete the old and add the new.
+ $LOGGER "New Routers: $new_routers"
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ for router in $new_routers; do
+ route add default $router 1 >/dev/null 2>&1
+ done
+ fi
+ fi
+ if [ x$new_ip_address != x$alias_ip_address ] && [ x$alias_ip_address != x ];
+ then
+ $ifconfig ${interface}:1 inet $alias_ip_address $alias_subnet_arg
+ route add $alias_ip_address 127.0.0.1 1
+ fi
+ make_resolv_conf
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xEXPIRE ] || [ x$reason = xFAIL ] || [ x$reason = xRELEASE ] \
+ || [ x$reason = xSTOP ]; then
+ if [ x$alias_ip_address != x ]; then
+ $ifconfig ${interface}:1 0 down > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ if [ x$old_ip_address != x ]; then
+ $ifconfig $interface inet 0 down
+ route delete $old_ip_address 127.1 >/dev/null 2>&1
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ fi
+ if [ x$alias_ip_address != x ]; then
+ $ifconfig ${interface}:1 inet $alias_ip_address $alias_subnet_arg
+ route add $alias_ip_address 127.0.0.1 1
+ fi
+ exit_with_hooks 0
+fi
+
+if [ x$reason = xTIMEOUT ]; then
+ if [ x$alias_ip_address != x ]; then
+ $ifconfig ${interface}:1 0 down > /dev/null 2>&1
+ route delete $alias_ip_address 127.0.0.1 > /dev/null 2>&1
+ fi
+ eval "$ifconfig $interface inet $new_ip_address $new_netmask_arg \
+ $new_broadcast_arg $mtu_arg $metric_arg $medium"
+ sleep 1
+ set $new_routers
+ if ping -s -n -I 1 $1 64 1; then
+ if [ x$new_ip_address != x$alias_ip_address ] && \
+ [ x$alias_ip_address != x ]; then
+ $ifconfig ${interface}:1 inet $alias_ip_address $alias_subnet_arg
+ route add $alias_ip_address 127.0.0.1 1
+ fi
+ route add $new_ip_address 127.1 1 >/dev/null 2>&1
+ for router in $new_routers; do
+ route add default $router 1 >/dev/null 2>&1
+ done
+ make_resolv_conf
+ exit_with_hooks 0
+ fi
+ $ifconfig $interface inet 0 down
+ for router in $old_routers; do
+ route delete default $router >/dev/null 2>&1
+ done
+ exit_with_hooks 1
+fi
+
+exit_with_hooks 0