diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2015-03-02 19:04:49 +0000 |
---|---|---|
committer | <> | 2015-05-08 15:30:59 +0000 |
commit | f800382616186a5d30e28d8b2c51e97a9a8360f2 (patch) | |
tree | 0d5270190548a37223d14b54383ce8a3d3af5302 /client | |
download | isc-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.am | 18 | ||||
-rw-r--r-- | client/Makefile.in | 735 | ||||
-rw-r--r-- | client/clparse.c | 2235 | ||||
-rw-r--r-- | client/dhc6.c | 5174 | ||||
-rw-r--r-- | client/dhclient-script.8 | 233 | ||||
-rw-r--r-- | client/dhclient.8 | 479 | ||||
-rw-r--r-- | client/dhclient.c | 4401 | ||||
-rw-r--r-- | client/dhclient.conf.5 | 734 | ||||
-rw-r--r-- | client/dhclient.conf.example | 36 | ||||
-rw-r--r-- | client/dhclient.leases.5 | 51 | ||||
-rwxr-xr-x | client/scripts/bsdos | 324 | ||||
-rwxr-xr-x | client/scripts/freebsd | 392 | ||||
-rwxr-xr-x | client/scripts/linux | 316 | ||||
-rwxr-xr-x | client/scripts/macos | 207 | ||||
-rwxr-xr-x | client/scripts/netbsd | 324 | ||||
-rw-r--r-- | client/scripts/nextstep | 61 | ||||
-rw-r--r-- | client/scripts/openbsd | 318 | ||||
-rwxr-xr-x | client/scripts/openwrt | 285 | ||||
-rwxr-xr-x | client/scripts/solaris | 203 |
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(©->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(©->options, lease->options, file, line); + + insert_ia = ©->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(©, 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 = ©->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(©, file, line); + return NULL; + } + + insert_addr = &(*insert_addr)->next; + } + + if (ia->options != NULL) + option_state_reference(©->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(©->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(©->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 |