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 /common | |
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 'common')
38 files changed, 39677 insertions, 0 deletions
diff --git a/common/Makefile.am b/common/Makefile.am new file mode 100644 index 0000000..eddef05 --- /dev/null +++ b/common/Makefile.am @@ -0,0 +1,17 @@ +AM_CPPFLAGS = -I.. -DLOCALSTATEDIR='"@localstatedir@"' +AM_CFLAGS = $(LDAP_CFLAGS) + +noinst_LIBRARIES = libdhcp.a +libdhcp_a_SOURCES = alloc.c bpf.c comapi.c conflex.c ctrace.c discover.c \ + dispatch.c dlpi.c dns.c ethernet.c execute.c fddi.c \ + icmp.c inet.c lpf.c memory.c nit.c ns_name.c options.c \ + packet.c parse.c print.c raw.c resolv.c socket.c \ + tables.c tr.c tree.c upf.c +man_MANS = dhcp-eval.5 dhcp-options.5 +EXTRA_DIST = $(man_MANS) + +# We want to build this directory first, before descending into tests subdir. +# The reason is that ideally the tests should link existing objects from this +# directory. That eliminates any discrepancies between tested code and +# production code. Sadly, we are not there yet. +SUBDIRS = . tests diff --git a/common/Makefile.in b/common/Makefile.in new file mode 100644 index 0000000..51e7818 --- /dev/null +++ b/common/Makefile.in @@ -0,0 +1,778 @@ +# Makefile.in generated by automake 1.14 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = common +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/depcomp +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/includes/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LIBRARIES = $(noinst_LIBRARIES) +AR = ar +ARFLAGS = cru +AM_V_AR = $(am__v_AR_@AM_V@) +am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@) +am__v_AR_0 = @echo " AR " $@; +am__v_AR_1 = +libdhcp_a_AR = $(AR) $(ARFLAGS) +libdhcp_a_LIBADD = +am_libdhcp_a_OBJECTS = alloc.$(OBJEXT) bpf.$(OBJEXT) comapi.$(OBJEXT) \ + conflex.$(OBJEXT) ctrace.$(OBJEXT) discover.$(OBJEXT) \ + dispatch.$(OBJEXT) dlpi.$(OBJEXT) dns.$(OBJEXT) \ + ethernet.$(OBJEXT) execute.$(OBJEXT) fddi.$(OBJEXT) \ + icmp.$(OBJEXT) inet.$(OBJEXT) lpf.$(OBJEXT) memory.$(OBJEXT) \ + nit.$(OBJEXT) ns_name.$(OBJEXT) options.$(OBJEXT) \ + packet.$(OBJEXT) parse.$(OBJEXT) print.$(OBJEXT) raw.$(OBJEXT) \ + resolv.$(OBJEXT) socket.$(OBJEXT) tables.$(OBJEXT) \ + tr.$(OBJEXT) tree.$(OBJEXT) upf.$(OBJEXT) +libdhcp_a_OBJECTS = $(am_libdhcp_a_OBJECTS) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/includes +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libdhcp_a_SOURCES) +DIST_SOURCES = $(libdhcp_a_SOURCES) +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +man5dir = $(mandir)/man5 +am__installdirs = "$(DESTDIR)$(man5dir)" +NROFF = nroff +MANS = $(man_MANS) +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +ATF_BIN = @ATF_BIN@ +ATF_CFLAGS = @ATF_CFLAGS@ +ATF_LDFLAGS = @ATF_LDFLAGS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDAP_CFLAGS = @LDAP_CFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_prefix_program = @ac_prefix_program@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +byte_order = @byte_order@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = -I.. -DLOCALSTATEDIR='"@localstatedir@"' +AM_CFLAGS = $(LDAP_CFLAGS) +noinst_LIBRARIES = libdhcp.a +libdhcp_a_SOURCES = alloc.c bpf.c comapi.c conflex.c ctrace.c discover.c \ + dispatch.c dlpi.c dns.c ethernet.c execute.c fddi.c \ + icmp.c inet.c lpf.c memory.c nit.c ns_name.c options.c \ + packet.c parse.c print.c raw.c resolv.c socket.c \ + tables.c tr.c tree.c upf.c + +man_MANS = dhcp-eval.5 dhcp-options.5 +EXTRA_DIST = $(man_MANS) + +# We want to build this directory first, before descending into tests subdir. +# The reason is that ideally the tests should link existing objects from this +# directory. That eliminates any discrepancies between tested code and +# production code. Sadly, we are not there yet. +SUBDIRS = . tests +all: all-recursive + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign common/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign common/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) + +libdhcp.a: $(libdhcp_a_OBJECTS) $(libdhcp_a_DEPENDENCIES) $(EXTRA_libdhcp_a_DEPENDENCIES) + $(AM_V_at)-rm -f libdhcp.a + $(AM_V_AR)$(libdhcp_a_AR) libdhcp.a $(libdhcp_a_OBJECTS) $(libdhcp_a_LIBADD) + $(AM_V_at)$(RANLIB) libdhcp.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/alloc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bpf.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/comapi.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/conflex.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ctrace.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/discover.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dispatch.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlpi.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dns.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ethernet.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/execute.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fddi.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/icmp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/inet.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lpf.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/memory.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nit.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ns_name.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/options.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/packet.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/print.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/raw.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/resolv.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socket.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tables.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tr.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tree.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/upf.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` +install-man5: $(man_MANS) + @$(NORMAL_INSTALL) + @list1=''; \ + list2='$(man_MANS)'; \ + test -n "$(man5dir)" \ + && test -n "`echo $$list1$$list2`" \ + || exit 0; \ + echo " $(MKDIR_P) '$(DESTDIR)$(man5dir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(man5dir)" || exit 1; \ + { for i in $$list1; do echo "$$i"; done; \ + if test -n "$$list2"; then \ + for i in $$list2; do echo "$$i"; done \ + | sed -n '/\.5[a-z]*$$/p'; \ + fi; \ + } | while read p; do \ + if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; echo "$$p"; \ + done | \ + sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^5][0-9a-z]*$$,5,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ + sed 'N;N;s,\n, ,g' | { \ + list=; while read file base inst; do \ + if test "$$base" = "$$inst"; then list="$$list $$file"; else \ + echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man5dir)/$$inst'"; \ + $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man5dir)/$$inst" || exit $$?; \ + fi; \ + done; \ + for i in $$list; do echo "$$i"; done | $(am__base_list) | \ + while read files; do \ + test -z "$$files" || { \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man5dir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(man5dir)" || exit $$?; }; \ + done; } + +uninstall-man5: + @$(NORMAL_UNINSTALL) + @list=''; test -n "$(man5dir)" || exit 0; \ + files=`{ for i in $$list; do echo "$$i"; done; \ + l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ + sed -n '/\.5[a-z]*$$/p'; \ + } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^5][0-9a-z]*$$,5,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ + dir='$(DESTDIR)$(man5dir)'; $(am__uninstall_files_from_dir) + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile $(LIBRARIES) $(MANS) +installdirs: installdirs-recursive +installdirs-am: + for dir in "$(DESTDIR)$(man5dir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am + +distclean: distclean-recursive + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: install-man + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: install-man5 + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: uninstall-man + +uninstall-man: uninstall-man5 + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \ + check-am clean clean-generic clean-noinstLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-man5 install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs installdirs-am \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-man \ + uninstall-man5 + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/common/alloc.c b/common/alloc.c new file mode 100644 index 0000000..a55f471 --- /dev/null +++ b/common/alloc.c @@ -0,0 +1,1362 @@ +/* alloc.c + + Memory allocation... */ + +/* + * Copyright (c) 2009,2013-2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2007 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" +#include <omapip/omapip_p.h> + +struct dhcp_packet *dhcp_free_list; +struct packet *packet_free_list; + +int option_chain_head_allocate (ptr, file, line) + struct option_chain_head **ptr; + const char *file; + int line; +{ + struct option_chain_head *h; + + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct option_chain_head *)0; +#endif + } + + h = dmalloc (sizeof *h, file, line); + if (h) { + memset (h, 0, sizeof *h); + return option_chain_head_reference (ptr, h, file, line); + } + return 0; +} + +int option_chain_head_reference (ptr, bp, file, line) + struct option_chain_head **ptr; + struct option_chain_head *bp; + const char *file; + int line; +{ + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct option_chain_head *)0; +#endif + } + *ptr = bp; + bp -> refcnt++; + rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC); + return 1; +} + +int option_chain_head_dereference (ptr, file, line) + struct option_chain_head **ptr; + const char *file; + int line; +{ + struct option_chain_head *option_chain_head; + pair car, cdr; + + if (!ptr || !*ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + option_chain_head = *ptr; + *ptr = (struct option_chain_head *)0; + --option_chain_head -> refcnt; + rc_register (file, line, ptr, option_chain_head, + option_chain_head -> refcnt, 1, RC_MISC); + if (option_chain_head -> refcnt > 0) + return 1; + + if (option_chain_head -> refcnt < 0) { + log_error ("%s(%d): negative refcnt!", file, line); +#if defined (DEBUG_RC_HISTORY) + dump_rc_history (option_chain_head); +#endif +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + /* If there are any options on this head, free them. */ + for (car = option_chain_head -> first; car; car = cdr) { + cdr = car -> cdr; + if (car -> car) + option_cache_dereference ((struct option_cache **) + (&car -> car), MDL); + dfree (car, MDL); + } + + dfree (option_chain_head, file, line); + return 1; +} + +int group_allocate (ptr, file, line) + struct group **ptr; + const char *file; + int line; +{ + struct group *g; + + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct group *)0; +#endif + } + + g = dmalloc (sizeof *g, file, line); + if (g) { + memset (g, 0, sizeof *g); + return group_reference (ptr, g, file, line); + } + return 0; +} + +int group_reference (ptr, bp, file, line) + struct group **ptr; + struct group *bp; + const char *file; + int line; +{ + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct group *)0; +#endif + } + *ptr = bp; + bp -> refcnt++; + rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC); + return 1; +} + +int group_dereference (ptr, file, line) + struct group **ptr; + const char *file; + int line; +{ + struct group *group; + + if (!ptr || !*ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + group = *ptr; + *ptr = (struct group *)0; + --group -> refcnt; + rc_register (file, line, ptr, group, group -> refcnt, 1, RC_MISC); + if (group -> refcnt > 0) + return 1; + + if (group -> refcnt < 0) { + log_error ("%s(%d): negative refcnt!", file, line); +#if defined (DEBUG_RC_HISTORY) + dump_rc_history (group); +#endif +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + if (group -> object) + group_object_dereference (&group -> object, file, line); + if (group -> subnet) + subnet_dereference (&group -> subnet, file, line); + if (group -> shared_network) + shared_network_dereference (&group -> shared_network, + file, line); + if (group -> statements) + executable_statement_dereference (&group -> statements, + file, line); + if (group -> next) + group_dereference (&group -> next, file, line); + dfree (group, file, line); + return 1; +} + +struct dhcp_packet *new_dhcp_packet (file, line) + const char *file; + int line; +{ + struct dhcp_packet *rval; + rval = (struct dhcp_packet *)dmalloc (sizeof (struct dhcp_packet), + file, line); + return rval; +} + +struct protocol *new_protocol (file, line) + const char *file; + int line; +{ + struct protocol *rval = dmalloc (sizeof (struct protocol), file, line); + return rval; +} + +struct domain_search_list *new_domain_search_list (file, line) + const char *file; + int line; +{ + struct domain_search_list *rval = + dmalloc (sizeof (struct domain_search_list), file, line); + return rval; +} + +struct name_server *new_name_server (file, line) + const char *file; + int line; +{ + struct name_server *rval = + dmalloc (sizeof (struct name_server), file, line); + return rval; +} + +void free_name_server (ptr, file, line) + struct name_server *ptr; + const char *file; + int line; +{ + dfree ((void *)ptr, file, line); +} + +struct option *new_option (name, file, line) + const char *name; + const char *file; + int line; +{ + struct option *rval; + int len; + + len = strlen(name); + + rval = dmalloc(sizeof(struct option) + len + 1, file, line); + + if(rval) { + memcpy(rval + 1, name, len); + rval->name = (char *)(rval + 1); + } + + return rval; +} + +struct universe *new_universe (file, line) + const char *file; + int line; +{ + struct universe *rval = + dmalloc (sizeof (struct universe), file, line); + return rval; +} + +void free_universe (ptr, file, line) + struct universe *ptr; + const char *file; + int line; +{ + dfree ((void *)ptr, file, line); +} + +void free_domain_search_list (ptr, file, line) + struct domain_search_list *ptr; + const char *file; + int line; +{ + dfree ((void *)ptr, file, line); +} + +void free_protocol (ptr, file, line) + struct protocol *ptr; + const char *file; + int line; +{ + dfree ((void *)ptr, file, line); +} + +void free_dhcp_packet (ptr, file, line) + struct dhcp_packet *ptr; + const char *file; + int line; +{ + dfree ((void *)ptr, file, line); +} + +struct client_lease *new_client_lease (file, line) + const char *file; + int line; +{ + return (struct client_lease *)dmalloc (sizeof (struct client_lease), + file, line); +} + +void free_client_lease (lease, file, line) + struct client_lease *lease; + const char *file; + int line; +{ + dfree (lease, file, line); +} + +pair free_pairs; + +pair new_pair (file, line) + const char *file; + int line; +{ + pair foo; + + if (free_pairs) { + foo = free_pairs; + free_pairs = foo -> cdr; + memset (foo, 0, sizeof *foo); + dmalloc_reuse (foo, file, line, 0); + return foo; + } + + foo = dmalloc (sizeof *foo, file, line); + if (!foo) + return foo; + memset (foo, 0, sizeof *foo); + return foo; +} + +void free_pair (foo, file, line) + pair foo; + const char *file; + int line; +{ + foo -> cdr = free_pairs; + free_pairs = foo; + dmalloc_reuse (free_pairs, __FILE__, __LINE__, 0); +} + +#if defined (DEBUG_MEMORY_LEAKAGE) || \ + defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT) +void relinquish_free_pairs () +{ + pair pf, pc; + + for (pf = free_pairs; pf; pf = pc) { + pc = pf -> cdr; + dfree (pf, MDL); + } + free_pairs = (pair)0; +} +#endif + +struct expression *free_expressions; + +int expression_allocate (cptr, file, line) + struct expression **cptr; + const char *file; + int line; +{ + struct expression *rval; + + if (free_expressions) { + rval = free_expressions; + free_expressions = rval -> data.not; + dmalloc_reuse (rval, file, line, 1); + } else { + rval = dmalloc (sizeof (struct expression), file, line); + if (!rval) + return 0; + } + memset (rval, 0, sizeof *rval); + return expression_reference (cptr, rval, file, line); +} + +int expression_reference (ptr, src, file, line) + struct expression **ptr; + struct expression *src; + const char *file; + int line; +{ + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct expression *)0; +#endif + } + *ptr = src; + src -> refcnt++; + rc_register (file, line, ptr, src, src -> refcnt, 0, RC_MISC); + return 1; +} + +void free_expression (expr, file, line) + struct expression *expr; + const char *file; + int line; +{ + expr -> data.not = free_expressions; + free_expressions = expr; + dmalloc_reuse (free_expressions, __FILE__, __LINE__, 0); +} + +#if defined (DEBUG_MEMORY_LEAKAGE) || \ + defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT) +void relinquish_free_expressions () +{ + struct expression *e, *n; + + for (e = free_expressions; e; e = n) { + n = e -> data.not; + dfree (e, MDL); + } + free_expressions = (struct expression *)0; +} +#endif + +struct binding_value *free_binding_values; + +int binding_value_allocate (cptr, file, line) + struct binding_value **cptr; + const char *file; + int line; +{ + struct binding_value *rval; + + if (free_binding_values) { + rval = free_binding_values; + free_binding_values = rval -> value.bv; + dmalloc_reuse (rval, file, line, 1); + } else { + rval = dmalloc (sizeof (struct binding_value), file, line); + if (!rval) + return 0; + } + memset (rval, 0, sizeof *rval); + return binding_value_reference (cptr, rval, file, line); +} + +int binding_value_reference (ptr, src, file, line) + struct binding_value **ptr; + struct binding_value *src; + const char *file; + int line; +{ + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct binding_value *)0; +#endif + } + *ptr = src; + src -> refcnt++; + rc_register (file, line, ptr, src, src -> refcnt, 0, RC_MISC); + return 1; +} + +void free_binding_value (bv, file, line) + struct binding_value *bv; + const char *file; + int line; +{ + bv -> value.bv = free_binding_values; + free_binding_values = bv; + dmalloc_reuse (free_binding_values, (char *)0, 0, 0); +} + +#if defined (DEBUG_MEMORY_LEAKAGE) || \ + defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT) +void relinquish_free_binding_values () +{ + struct binding_value *b, *n; + + for (b = free_binding_values; b; b = n) { + n = b -> value.bv; + dfree (b, MDL); + } + free_binding_values = (struct binding_value *)0; +} +#endif + +int fundef_allocate (cptr, file, line) + struct fundef **cptr; + const char *file; + int line; +{ + struct fundef *rval; + + rval = dmalloc (sizeof (struct fundef), file, line); + if (!rval) + return 0; + memset (rval, 0, sizeof *rval); + return fundef_reference (cptr, rval, file, line); +} + +int fundef_reference (ptr, src, file, line) + struct fundef **ptr; + struct fundef *src; + const char *file; + int line; +{ + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct fundef *)0; +#endif + } + *ptr = src; + src -> refcnt++; + rc_register (file, line, ptr, src, src -> refcnt, 0, RC_MISC); + return 1; +} + +struct option_cache *free_option_caches; + +#if defined (DEBUG_MEMORY_LEAKAGE) || \ + defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT) +void relinquish_free_option_caches () +{ + struct option_cache *o, *n; + + for (o = free_option_caches; o; o = n) { + n = (struct option_cache *)(o -> expression); + dfree (o, MDL); + } + free_option_caches = (struct option_cache *)0; +} +#endif + +int option_cache_allocate (cptr, file, line) + struct option_cache **cptr; + const char *file; + int line; +{ + struct option_cache *rval; + + if (free_option_caches) { + rval = free_option_caches; + free_option_caches = + (struct option_cache *)(rval -> expression); + dmalloc_reuse (rval, file, line, 0); + } else { + rval = dmalloc (sizeof (struct option_cache), file, line); + if (!rval) + return 0; + } + memset (rval, 0, sizeof *rval); + return option_cache_reference (cptr, rval, file, line); +} + +int option_cache_reference (ptr, src, file, line) + struct option_cache **ptr; + struct option_cache *src; + const char *file; + int line; +{ + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct option_cache *)0; +#endif + } + *ptr = src; + src -> refcnt++; + rc_register (file, line, ptr, src, src -> refcnt, 0, RC_MISC); + return 1; +} + +int buffer_allocate (ptr, len, file, line) + struct buffer **ptr; + unsigned len; + const char *file; + int line; +{ + struct buffer *bp; + + /* XXXSK: should check for bad ptr values, otherwise we + leak memory if they are wrong */ + bp = dmalloc (len + sizeof *bp, file, line); + if (!bp) + return 0; + /* XXXSK: both of these initializations are unnecessary */ + memset (bp, 0, sizeof *bp); + bp -> refcnt = 0; + return buffer_reference (ptr, bp, file, line); +} + +int buffer_reference (ptr, bp, file, line) + struct buffer **ptr; + struct buffer *bp; + const char *file; + int line; +{ + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct buffer *)0; +#endif + } + *ptr = bp; + bp -> refcnt++; + rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC); + return 1; +} + +int buffer_dereference (ptr, file, line) + struct buffer **ptr; + const char *file; + int line; +{ + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + if (!*ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + (*ptr) -> refcnt--; + rc_register (file, line, ptr, *ptr, (*ptr) -> refcnt, 1, RC_MISC); + if (!(*ptr) -> refcnt) { + dfree ((*ptr), file, line); + } else if ((*ptr) -> refcnt < 0) { + log_error ("%s(%d): negative refcnt!", file, line); +#if defined (DEBUG_RC_HISTORY) + dump_rc_history (*ptr); +#endif +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + *ptr = (struct buffer *)0; + return 1; +} + +int dns_host_entry_allocate (ptr, hostname, file, line) + struct dns_host_entry **ptr; + const char *hostname; + const char *file; + int line; +{ + struct dns_host_entry *bp; + + bp = dmalloc (strlen (hostname) + sizeof *bp, file, line); + if (!bp) + return 0; + memset (bp, 0, sizeof *bp); + bp -> refcnt = 0; + strcpy (bp -> hostname, hostname); + return dns_host_entry_reference (ptr, bp, file, line); +} + +int dns_host_entry_reference (ptr, bp, file, line) + struct dns_host_entry **ptr; + struct dns_host_entry *bp; + const char *file; + int line; +{ + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct dns_host_entry *)0; +#endif + } + *ptr = bp; + bp -> refcnt++; + rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC); + return 1; +} + +int dns_host_entry_dereference (ptr, file, line) + struct dns_host_entry **ptr; + const char *file; + int line; +{ + if (!ptr || !*ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + (*ptr)->refcnt--; + rc_register (file, line, ptr, *ptr, (*ptr)->refcnt, 1, RC_MISC); + if ((*ptr)->refcnt == 0) { + dfree ((*ptr), file, line); + } else if ((*ptr)->refcnt < 0) { + log_error ("%s(%d): negative refcnt!", file, line); +#if defined (DEBUG_RC_HISTORY) + dump_rc_history (*ptr); +#endif +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + *ptr = (struct dns_host_entry *)0; + return 1; +} + +int option_state_allocate (ptr, file, line) + struct option_state **ptr; + const char *file; + int line; +{ + unsigned size; + + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct option_state *)0; +#endif + } + + size = sizeof **ptr + (universe_count - 1) * sizeof (void *); + *ptr = dmalloc (size, file, line); + if (*ptr) { + memset (*ptr, 0, size); + (*ptr) -> universe_count = universe_count; + (*ptr) -> refcnt = 1; + rc_register (file, line, + ptr, *ptr, (*ptr) -> refcnt, 0, RC_MISC); + return 1; + } + return 0; +} + +int option_state_reference (ptr, bp, file, line) + struct option_state **ptr; + struct option_state *bp; + const char *file; + int line; +{ + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct option_state *)0; +#endif + } + *ptr = bp; + bp -> refcnt++; + rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC); + return 1; +} + +int option_state_dereference (ptr, file, line) + struct option_state **ptr; + const char *file; + int line; +{ + int i; + struct option_state *options; + + if (!ptr || !*ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + options = *ptr; + *ptr = (struct option_state *)0; + --options -> refcnt; + rc_register (file, line, ptr, options, options -> refcnt, 1, RC_MISC); + if (options -> refcnt > 0) + return 1; + + if (options -> refcnt < 0) { + log_error ("%s(%d): negative refcnt!", file, line); +#if defined (DEBUG_RC_HISTORY) + dump_rc_history (options); +#endif +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + /* Loop through the per-universe state. */ + for (i = 0; i < options -> universe_count; i++) + if (options -> universes [i] && + universes [i] -> option_state_dereference) + ((*(universes [i] -> option_state_dereference)) + (universes [i], options, file, line)); + + dfree (options, file, line); + return 1; +} + +int executable_statement_allocate (ptr, file, line) + struct executable_statement **ptr; + const char *file; + int line; +{ + struct executable_statement *bp; + + bp = dmalloc (sizeof *bp, file, line); + if (!bp) + return 0; + memset (bp, 0, sizeof *bp); + return executable_statement_reference (ptr, bp, file, line); +} + +int executable_statement_reference (ptr, bp, file, line) + struct executable_statement **ptr; + struct executable_statement *bp; + const char *file; + int line; +{ + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct executable_statement *)0; +#endif + } + *ptr = bp; + bp -> refcnt++; + rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC); + return 1; +} + +static struct packet *free_packets; + +#if defined (DEBUG_MEMORY_LEAKAGE) || \ + defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT) +void relinquish_free_packets () +{ + struct packet *p, *n; + for (p = free_packets; p; p = n) { + n = (struct packet *)(p -> raw); + dfree (p, MDL); + } + free_packets = (struct packet *)0; +} +#endif + +int packet_allocate (ptr, file, line) + struct packet **ptr; + const char *file; + int line; +{ + struct packet *p; + + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct packet *)0; +#endif + } + + if (free_packets) { + p = free_packets; + free_packets = (struct packet *)(p -> raw); + dmalloc_reuse (p, file, line, 1); + } else { + p = dmalloc (sizeof *p, file, line); + } + if (p) { + memset (p, 0, sizeof *p); + return packet_reference (ptr, p, file, line); + } + return 0; +} + +int packet_reference (ptr, bp, file, line) + struct packet **ptr; + struct packet *bp; + const char *file; + int line; +{ + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct packet *)0; +#endif + } + *ptr = bp; + bp -> refcnt++; + rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC); + return 1; +} + +int packet_dereference (ptr, file, line) + struct packet **ptr; + const char *file; + int line; +{ + int i; + struct packet *packet; + + if (!ptr || !*ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + packet = *ptr; + *ptr = (struct packet *)0; + --packet -> refcnt; + rc_register (file, line, ptr, packet, packet -> refcnt, 1, RC_MISC); + if (packet -> refcnt > 0) + return 1; + + if (packet -> refcnt < 0) { + log_error ("%s(%d): negative refcnt!", file, line); +#if defined (DEBUG_RC_HISTORY) + dump_rc_history (packet); +#endif +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + if (packet -> options) + option_state_dereference (&packet -> options, file, line); + if (packet -> interface) + interface_dereference (&packet -> interface, MDL); + if (packet -> shared_network) + shared_network_dereference (&packet -> shared_network, MDL); + for (i = 0; i < packet -> class_count && i < PACKET_MAX_CLASSES; i++) { + if (packet -> classes [i]) + omapi_object_dereference ((omapi_object_t **) + &packet -> classes [i], MDL); + } + packet -> raw = (struct dhcp_packet *)free_packets; + free_packets = packet; + dmalloc_reuse (free_packets, __FILE__, __LINE__, 0); + return 1; +} + +int dns_zone_allocate (ptr, file, line) + struct dns_zone **ptr; + const char *file; + int line; +{ + struct dns_zone *d; + + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct dns_zone *)0; +#endif + } + + d = dmalloc (sizeof *d, file, line); + if (d) { + memset (d, 0, sizeof *d); + return dns_zone_reference (ptr, d, file, line); + } + return 0; +} + +int dns_zone_reference (ptr, bp, file, line) + struct dns_zone **ptr; + struct dns_zone *bp; + const char *file; + int line; +{ + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct dns_zone *)0; +#endif + } + *ptr = bp; + bp -> refcnt++; + rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC); + return 1; +} + +int binding_scope_allocate (ptr, file, line) + struct binding_scope **ptr; + const char *file; + int line; +{ + struct binding_scope *bp; + + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + bp = dmalloc (sizeof *bp, file, line); + if (!bp) + return 0; + memset (bp, 0, sizeof *bp); + binding_scope_reference (ptr, bp, file, line); + return 1; +} + +int binding_scope_reference (ptr, bp, file, line) + struct binding_scope **ptr; + struct binding_scope *bp; + const char *file; + int line; +{ + if (!ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (*ptr) { + log_error ("%s(%d): non-null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct binding_scope *)0; +#endif + } + *ptr = bp; + bp -> refcnt++; + rc_register (file, line, ptr, bp, bp -> refcnt, 0, RC_MISC); + return 1; +} + +/*! + * \brief Constructs a null-terminated data_string from a char* and length. + * + * Allocates a data_string and copies into it the given length of bytes + * from the given source, adding a terminating null if not present in the source + * at length-1. + * + * \param new_string pointer to the data_string to construct. Cannot be + * NULL. Note that its contents will be overwritten. Passing in the address + * of an allocated data_string will result in memory leaks. + * \param src data to be copied. Cannot be NULL. + * \param len length of the data to copied + * + * \return 1 - if the data_string is constructed successfully, 0 if + * target data_struct is NULL or the buffer allocation fails. + */ +int +data_string_new(struct data_string *new_string, + const char *src, unsigned int len, + const char *file, int line) +{ + unsigned int copy_len = 0; + + if (new_string == NULL) { + log_error("data_string_new: new_string cannot be NULL %s(%d)", + file, line); + return (0); + } + + if (src == NULL) { + log_error("data_string_new: src cannot be NULL %s(%d)", + file, line); + return (0); + } + + memset(new_string, 0, sizeof (struct data_string)); + + /* If we already have a NULL back off length by one. This lets + * us always just add a NULL at the end. */ + copy_len = (len > 0 && src[len - 1 ] == 0) ? len - 1 : len; + + /* Allocate the buffer, accounting for terminating null */ + if (!buffer_allocate(&(new_string->buffer), copy_len + 1, MDL)) { + log_error("data_string_new: No memory %s(%d)", file, line); + return (0); + } + + /* Only copy if there's something to copy */ + if (copy_len > 0) { + memcpy(new_string->buffer->data, src, copy_len); + } + + /* Always tack on the null */ + new_string->buffer->data[copy_len + 1] = 0; + + /* Update data_string accessor values. Note len does NOT include + * the NULL. */ + new_string->data = new_string->buffer->data; + new_string->len = copy_len; + new_string->terminated = 1; + + return (1); +} + +/* Make a copy of the data in data_string, upping the buffer reference + count if there's a buffer. */ + +void +data_string_copy(struct data_string *dest, const struct data_string *src, + const char *file, int line) +{ + if (src -> buffer) { + buffer_reference (&dest -> buffer, src -> buffer, file, line); + } else { + dest->buffer = NULL; + } + dest -> data = src -> data; + dest -> terminated = src -> terminated; + dest -> len = src -> len; +} + +/* Release the reference count to a data string's buffer (if any) and + zero out the other information, yielding the null data string. */ + +void data_string_forget (data, file, line) + struct data_string *data; + const char *file; + int line; +{ + if (data -> buffer) + buffer_dereference (&data -> buffer, file, line); + memset (data, 0, sizeof *data); +} + +/* If the data_string is larger than the specified length, reduce + the data_string to the specified size. */ + +void data_string_truncate (dp, len) + struct data_string *dp; + int len; +{ + /* XXX: do we need to consider the "terminated" flag in the check? */ + if (len < dp -> len) { + dp -> terminated = 0; + dp -> len = len; + } +} diff --git a/common/bpf.c b/common/bpf.c new file mode 100644 index 0000000..27e55aa --- /dev/null +++ b/common/bpf.c @@ -0,0 +1,607 @@ +/* bpf.c + + BPF socket interface code, originally contributed by Archie Cobbs. */ + +/* + * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + * This software was contributed to Internet Systems Consortium + * by Archie Cobbs. + * + * Patches for FDDI support on Digital Unix were written by Bill + * Stapleton, and maintained for a while by Mike Meredith before he + * managed to get me to integrate them. + */ + +#include "dhcpd.h" +#if defined (USE_BPF_SEND) || defined (USE_BPF_RECEIVE) \ + || defined (USE_LPF_RECEIVE) +# if defined (USE_LPF_RECEIVE) +# include <asm/types.h> +# include <linux/filter.h> +# define bpf_insn sock_filter /* Linux: dare to be gratuitously different. */ +# else +# include <sys/ioctl.h> +# include <sys/uio.h> +# include <net/bpf.h> +# include <net/if_types.h> +# if defined (NEED_OSF_PFILT_HACKS) +# include <net/pfilt.h> +# endif +# endif + +#include <netinet/in_systm.h> +#include "includes/netinet/ip.h" +#include "includes/netinet/udp.h" +#include "includes/netinet/if_ether.h" +#endif + +#ifdef USE_BPF_RECEIVE +#include <ifaddrs.h> +#endif + +#include <errno.h> + +/* Reinitializes the specified interface after an address change. This + is not required for packet-filter APIs. */ + +#ifdef USE_BPF_SEND +void if_reinitialize_send (info) + struct interface_info *info; +{ +} +#endif + +#ifdef USE_BPF_RECEIVE +void if_reinitialize_receive (info) + struct interface_info *info; +{ +} +#endif + +/* Called by get_interface_list for each interface that's discovered. + Opens a packet filter for each interface and adds it to the select + mask. */ + +#if defined (USE_BPF_SEND) || defined (USE_BPF_RECEIVE) +int if_register_bpf (info) + struct interface_info *info; +{ + int sock; + char filename[50]; + int b; + + /* Open a BPF device */ + for (b = 0; 1; b++) { + /* %Audit% 31 bytes max. %2004.06.17,Safe% */ + sprintf(filename, BPF_FORMAT, b); + sock = open (filename, O_RDWR, 0); + if (sock < 0) { + if (errno == EBUSY) { + continue; + } else { + if (!b) + log_fatal ("No bpf devices.%s%s%s", + " Please read the README", + " section for your operating", + " system."); + log_fatal ("Can't find free bpf: %m"); + } + } else { + break; + } + } + + /* Set the BPF device to point at this interface. */ + if (ioctl (sock, BIOCSETIF, info -> ifp) < 0) + log_fatal ("Can't attach interface %s to bpf device %s: %m", + info -> name, filename); + + get_hw_addr(info->name, &info->hw_address); + + return sock; +} +#endif /* USE_BPF_SEND || USE_BPF_RECEIVE */ + +#ifdef USE_BPF_SEND +void if_register_send (info) + struct interface_info *info; +{ + /* If we're using the bpf API for sending and receiving, + we don't need to register this interface twice. */ +#ifndef USE_BPF_RECEIVE + info -> wfdesc = if_register_bpf (info, interface); +#else + info -> wfdesc = info -> rfdesc; +#endif + if (!quiet_interface_discovery) + log_info ("Sending on BPF/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} + +void if_deregister_send (info) + struct interface_info *info; +{ + /* If we're using the bpf API for sending and receiving, + we don't need to register this interface twice. */ +#ifndef USE_BPF_RECEIVE + close (info -> wfdesc); +#endif + info -> wfdesc = -1; + + if (!quiet_interface_discovery) + log_info ("Disabling output on BPF/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} +#endif /* USE_BPF_SEND */ + +#if defined (USE_BPF_RECEIVE) || defined (USE_LPF_RECEIVE) +/* Packet filter program... + XXX Changes to the filter program may require changes to the constant + offsets used in if_register_send to patch the BPF program! XXX */ + +struct bpf_insn dhcp_bpf_filter [] = { + /* Make sure this is an IP packet... */ + BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8), + + /* Make sure it's a UDP packet... */ + BPF_STMT (BPF_LD + BPF_B + BPF_ABS, 23), + BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6), + + /* Make sure this isn't a fragment... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20), + BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0), + + /* Get the IP header length... */ + BPF_STMT (BPF_LDX + BPF_B + BPF_MSH, 14), + + /* Make sure it's to the right port... */ + BPF_STMT (BPF_LD + BPF_H + BPF_IND, 16), + BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, 67, 0, 1), /* patch */ + + /* If we passed all the tests, ask for the whole packet. */ + BPF_STMT(BPF_RET+BPF_K, (u_int)-1), + + /* Otherwise, drop it. */ + BPF_STMT(BPF_RET+BPF_K, 0), +}; + +#if defined (DEC_FDDI) +struct bpf_insn *bpf_fddi_filter; +#endif + +int dhcp_bpf_filter_len = sizeof dhcp_bpf_filter / sizeof (struct bpf_insn); +#if defined (HAVE_TR_SUPPORT) +struct bpf_insn dhcp_bpf_tr_filter [] = { + /* accept all token ring packets due to variable length header */ + /* if we want to get clever, insert the program here */ + + /* If we passed all the tests, ask for the whole packet. */ + BPF_STMT(BPF_RET+BPF_K, (u_int)-1), + + /* Otherwise, drop it. */ + BPF_STMT(BPF_RET+BPF_K, 0), +}; + +int dhcp_bpf_tr_filter_len = (sizeof dhcp_bpf_tr_filter / + sizeof (struct bpf_insn)); +#endif /* HAVE_TR_SUPPORT */ +#endif /* USE_LPF_RECEIVE || USE_BPF_RECEIVE */ + +#if defined (USE_BPF_RECEIVE) +void if_register_receive (info) + struct interface_info *info; +{ + int flag = 1; + struct bpf_version v; + struct bpf_program p; +#ifdef NEED_OSF_PFILT_HACKS + u_int32_t bits; +#endif +#ifdef DEC_FDDI + int link_layer; +#endif /* DEC_FDDI */ + + /* Open a BPF device and hang it on this interface... */ + info -> rfdesc = if_register_bpf (info); + + /* Make sure the BPF version is in range... */ + if (ioctl (info -> rfdesc, BIOCVERSION, &v) < 0) + log_fatal ("Can't get BPF version: %m"); + + if (v.bv_major != BPF_MAJOR_VERSION || + v.bv_minor < BPF_MINOR_VERSION) + log_fatal ("BPF version mismatch - recompile DHCP!"); + + /* Set immediate mode so that reads return as soon as a packet + comes in, rather than waiting for the input buffer to fill with + packets. */ + if (ioctl (info -> rfdesc, BIOCIMMEDIATE, &flag) < 0) + log_fatal ("Can't set immediate mode on bpf device: %m"); + +#ifdef NEED_OSF_PFILT_HACKS + /* Allow the copyall flag to be set... */ + if (ioctl(info -> rfdesc, EIOCALLOWCOPYALL, &flag) < 0) + log_fatal ("Can't set ALLOWCOPYALL: %m"); + + /* Clear all the packet filter mode bits first... */ + bits = 0; + if (ioctl (info -> rfdesc, EIOCMBIS, &bits) < 0) + log_fatal ("Can't clear pfilt bits: %m"); + + /* Set the ENBATCH, ENCOPYALL, ENBPFHDR bits... */ + bits = ENBATCH | ENCOPYALL | ENBPFHDR; + if (ioctl (info -> rfdesc, EIOCMBIS, &bits) < 0) + log_fatal ("Can't set ENBATCH|ENCOPYALL|ENBPFHDR: %m"); +#endif + /* Get the required BPF buffer length from the kernel. */ + if (ioctl (info -> rfdesc, BIOCGBLEN, &info -> rbuf_max) < 0) + log_fatal ("Can't get bpf buffer length: %m"); + info -> rbuf = dmalloc (info -> rbuf_max, MDL); + if (!info -> rbuf) + log_fatal ("Can't allocate %ld bytes for bpf input buffer.", + (long)(info -> rbuf_max)); + info -> rbuf_offset = 0; + info -> rbuf_len = 0; + + /* Set up the bpf filter program structure. */ + p.bf_len = dhcp_bpf_filter_len; + +#ifdef DEC_FDDI + /* See if this is an FDDI interface, flag it for later. */ + if (ioctl(info -> rfdesc, BIOCGDLT, &link_layer) >= 0 && + link_layer == DLT_FDDI) { + if (!bpf_fddi_filter) { + bpf_fddi_filter = dmalloc (sizeof bpf_fddi_filter, + MDL); + if (!bpf_fddi_filter) + log_fatal ("No memory for FDDI filter."); + memcpy (bpf_fddi_filter, + dhcp_bpf_filter, sizeof dhcp_bpf_filter); + /* Patch the BPF program to account for the difference + in length between ethernet headers (14), FDDI and + 802.2 headers (16 +8=24, +10). + XXX changes to filter program may require changes to + XXX the insn number(s) used below! */ + bpf_fddi_filter[0].k += 10; + bpf_fddi_filter[2].k += 10; + bpf_fddi_filter[4].k += 10; + bpf_fddi_filter[6].k += 10; + bpf_fddi_filter[7].k += 10; + } + p.bf_insns = bpf_fddi_filter; + } else +#endif /* DEC_FDDI */ + p.bf_insns = dhcp_bpf_filter; + + /* Patch the server port into the BPF program... + XXX changes to filter program may require changes + to the insn number(s) used below! XXX */ + dhcp_bpf_filter [8].k = ntohs (local_port); + + if (ioctl (info -> rfdesc, BIOCSETF, &p) < 0) + log_fatal ("Can't install packet filter program: %m"); + if (!quiet_interface_discovery) + log_info ("Listening on BPF/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} + +void if_deregister_receive (info) + struct interface_info *info; +{ + close (info -> rfdesc); + info -> rfdesc = -1; + + if (!quiet_interface_discovery) + log_info ("Disabling input on BPF/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} +#endif /* USE_BPF_RECEIVE */ + +#ifdef USE_BPF_SEND +ssize_t send_packet (interface, packet, raw, len, from, to, hto) + struct interface_info *interface; + struct packet *packet; + struct dhcp_packet *raw; + size_t len; + struct in_addr from; + struct sockaddr_in *to; + struct hardware *hto; +{ + unsigned hbufp = 0, ibufp = 0; + double hw [4]; + double ip [32]; + struct iovec iov [3]; + int result; + + if (!strcmp (interface -> name, "fallback")) + return send_fallback (interface, packet, raw, + len, from, to, hto); + + if (hto == NULL && interface->anycast_mac_addr.hlen) + hto = &interface->anycast_mac_addr; + + /* Assemble the headers... */ + assemble_hw_header (interface, (unsigned char *)hw, &hbufp, hto); + assemble_udp_ip_header (interface, + (unsigned char *)ip, &ibufp, from.s_addr, + to -> sin_addr.s_addr, to -> sin_port, + (unsigned char *)raw, len); + + /* Fire it off */ + iov [0].iov_base = ((char *)hw); + iov [0].iov_len = hbufp; + iov [1].iov_base = ((char *)ip); + iov [1].iov_len = ibufp; + iov [2].iov_base = (char *)raw; + iov [2].iov_len = len; + + result = writev(interface -> wfdesc, iov, 3); + if (result < 0) + log_error ("send_packet: %m"); + return result; +} +#endif /* USE_BPF_SEND */ + +#ifdef USE_BPF_RECEIVE +ssize_t receive_packet (interface, buf, len, from, hfrom) + struct interface_info *interface; + unsigned char *buf; + size_t len; + struct sockaddr_in *from; + struct hardware *hfrom; +{ + int length = 0; + int offset = 0; + struct bpf_hdr hdr; + unsigned paylen; + + /* All this complexity is because BPF doesn't guarantee + that only one packet will be returned at a time. We're + getting what we deserve, though - this is a terrible abuse + of the BPF interface. Sigh. */ + + /* Process packets until we get one we can return or until we've + done a read and gotten nothing we can return... */ + + /* If the buffer is empty, fill it. */ + if (interface->rbuf_offset >= interface->rbuf_len) { + length = read(interface->rfdesc, interface->rbuf, + (size_t)interface->rbuf_max); + if (length <= 0) { +#ifdef __FreeBSD__ + if (errno == ENXIO) { +#else + if (errno == EIO) { +#endif + dhcp_interface_remove + ((omapi_object_t *)interface, NULL); + } + return (length); + } + interface->rbuf_offset = 0; + interface->rbuf_len = BPF_WORDALIGN(length); + } + + do { + /* If there isn't room for a whole bpf header, something went + wrong, but we'll ignore it and hope it goes away... XXX */ + if (interface->rbuf_len - + interface->rbuf_offset < sizeof hdr) { + interface->rbuf_offset = interface->rbuf_len; + continue; + } + + /* Copy out a bpf header... */ + memcpy(&hdr, &interface->rbuf[interface->rbuf_offset], + sizeof hdr); + + /* If the bpf header plus data doesn't fit in what's left + of the buffer, stick head in sand yet again... */ + if (interface->rbuf_offset + + hdr.bh_hdrlen + hdr.bh_caplen > interface->rbuf_len) { + interface->rbuf_offset = interface->rbuf_len; + continue; + } + + /* If the captured data wasn't the whole packet, or if + the packet won't fit in the input buffer, all we + can do is drop it. */ + if (hdr.bh_caplen != hdr.bh_datalen) { + interface->rbuf_offset = + BPF_WORDALIGN(interface->rbuf_offset + + hdr.bh_hdrlen + hdr.bh_caplen); + continue; + } + + /* Skip over the BPF header... */ + interface->rbuf_offset += hdr.bh_hdrlen; + + /* Decode the physical header... */ + offset = decode_hw_header(interface, interface->rbuf, + interface->rbuf_offset, hfrom); + + /* If a physical layer checksum failed (dunno of any + physical layer that supports this, but WTH), skip this + packet. */ + if (offset < 0) { + interface->rbuf_offset = + BPF_WORDALIGN(interface->rbuf_offset + + hdr.bh_caplen); + continue; + } + interface->rbuf_offset += offset; + hdr.bh_caplen -= offset; + + /* Decode the IP and UDP headers... */ + offset = decode_udp_ip_header(interface, interface->rbuf, + interface->rbuf_offset, + from, hdr.bh_caplen, &paylen, 1); + + /* If the IP or UDP checksum was bad, skip the packet... */ + if (offset < 0) { + interface->rbuf_offset = + BPF_WORDALIGN(interface->rbuf_offset + + hdr.bh_caplen); + continue; + } + interface->rbuf_offset = interface->rbuf_offset + offset; + hdr.bh_caplen -= offset; + + /* If there's not enough room to stash the packet data, + we have to skip it (this shouldn't happen in real + life, though). */ + if (hdr.bh_caplen > len) { + interface->rbuf_offset = + BPF_WORDALIGN(interface->rbuf_offset + + hdr.bh_caplen); + continue; + } + + /* Copy out the data in the packet... */ + memcpy(buf, interface->rbuf + interface->rbuf_offset, paylen); + interface->rbuf_offset = + BPF_WORDALIGN(interface->rbuf_offset + hdr.bh_caplen); + return paylen; + } while (interface->rbuf_offset < interface->rbuf_len); + + return (0); +} + +int can_unicast_without_arp (ip) + struct interface_info *ip; +{ + return 1; +} + +int can_receive_unicast_unconfigured (ip) + struct interface_info *ip; +{ + return 1; +} + +int supports_multiple_interfaces (ip) + struct interface_info *ip; +{ + return 1; +} + +void maybe_setup_fallback () +{ + isc_result_t status; + struct interface_info *fbi = (struct interface_info *)0; + if (setup_fallback (&fbi, MDL)) { + if_register_fallback (fbi); + status = omapi_register_io_object ((omapi_object_t *)fbi, + if_readsocket, 0, + fallback_discard, 0, 0); + if (status != ISC_R_SUCCESS) + log_fatal ("Can't register I/O handle for %s: %s", + fbi -> name, isc_result_totext (status)); + interface_dereference (&fbi, MDL); + } +} + +void +get_hw_addr(const char *name, struct hardware *hw) { + struct ifaddrs *ifa; + struct ifaddrs *p; + struct sockaddr_dl *sa; + + if (getifaddrs(&ifa) != 0) { + log_fatal("Error getting interface information; %m"); + } + + /* + * Loop through our interfaces finding a match. + */ + sa = NULL; + for (p=ifa; (p != NULL) && (sa == NULL); p = p->ifa_next) { + if ((p->ifa_addr->sa_family == AF_LINK) && + !strcmp(p->ifa_name, name)) { + sa = (struct sockaddr_dl *)p->ifa_addr; + } + } + if (sa == NULL) { + log_fatal("No interface called '%s'", name); + } + + /* + * Pull out the appropriate information. + */ + switch (sa->sdl_type) { + case IFT_ETHER: +#if defined (IFT_L2VLAN) + case IFT_L2VLAN: +#endif + hw->hlen = sa->sdl_alen + 1; + hw->hbuf[0] = HTYPE_ETHER; + memcpy(&hw->hbuf[1], LLADDR(sa), sa->sdl_alen); + break; + case IFT_ISO88023: + case IFT_ISO88024: /* "token ring" */ + case IFT_ISO88025: + case IFT_ISO88026: + hw->hlen = sa->sdl_alen + 1; + hw->hbuf[0] = HTYPE_IEEE802; + memcpy(&hw->hbuf[1], LLADDR(sa), sa->sdl_alen); + break; +#ifdef IFT_FDDI + case IFT_FDDI: + hw->hlen = sa->sdl_alen + 1; + hw->hbuf[0] = HTYPE_FDDI; + memcpy(&hw->hbuf[1], LLADDR(sa), sa->sdl_alen); + break; +#endif /* IFT_FDDI */ + default: + log_fatal("Unsupported device type %d for \"%s\"", + sa->sdl_type, name); + } + + freeifaddrs(ifa); +} +#endif diff --git a/common/comapi.c b/common/comapi.c new file mode 100644 index 0000000..ee1c7ec --- /dev/null +++ b/common/comapi.c @@ -0,0 +1,921 @@ +/* omapi.c + + OMAPI object interfaces for the DHCP server. */ + +/* + * Copyright (c) 2012,2014 Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2007,2009 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1999-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +/* Many, many thanks to Brian Murrell and BCtel for this code - BCtel + provided the funding that resulted in this code and the entire + OMAPI support library being written, and Brian helped brainstorm + and refine the requirements. To the extent that this code is + useful, you have Brian and BCtel to thank. Any limitations in the + code are a result of mistakes on my part. -- Ted Lemon */ + +#include "dhcpd.h" +#include <omapip/omapip_p.h> + +OMAPI_OBJECT_ALLOC (subnet, struct subnet, dhcp_type_subnet) +OMAPI_OBJECT_ALLOC (shared_network, struct shared_network, + dhcp_type_shared_network) +OMAPI_OBJECT_ALLOC (group_object, struct group_object, dhcp_type_group) +OMAPI_OBJECT_ALLOC (dhcp_control, dhcp_control_object_t, dhcp_type_control) + +omapi_object_type_t *dhcp_type_interface; +omapi_object_type_t *dhcp_type_group; +omapi_object_type_t *dhcp_type_shared_network; +omapi_object_type_t *dhcp_type_subnet; +omapi_object_type_t *dhcp_type_control; +dhcp_control_object_t *dhcp_control_object; + +void dhcp_common_objects_setup () +{ + isc_result_t status; + + status = omapi_object_type_register (&dhcp_type_control, + "control", + dhcp_control_set_value, + dhcp_control_get_value, + dhcp_control_destroy, + dhcp_control_signal_handler, + dhcp_control_stuff_values, + dhcp_control_lookup, + dhcp_control_create, + dhcp_control_remove, 0, 0, 0, + sizeof (dhcp_control_object_t), + 0, RC_MISC); + if (status != ISC_R_SUCCESS) + log_fatal ("Can't register control object type: %s", + isc_result_totext (status)); + status = dhcp_control_allocate (&dhcp_control_object, MDL); + if (status != ISC_R_SUCCESS) + log_fatal ("Can't make initial control object: %s", + isc_result_totext (status)); + dhcp_control_object -> state = server_startup; + + status = omapi_object_type_register (&dhcp_type_group, + "group", + dhcp_group_set_value, + dhcp_group_get_value, + dhcp_group_destroy, + dhcp_group_signal_handler, + dhcp_group_stuff_values, + dhcp_group_lookup, + dhcp_group_create, + dhcp_group_remove, 0, 0, 0, + sizeof (struct group_object), 0, + RC_MISC); + if (status != ISC_R_SUCCESS) + log_fatal ("Can't register group object type: %s", + isc_result_totext (status)); + + status = omapi_object_type_register (&dhcp_type_subnet, + "subnet", + dhcp_subnet_set_value, + dhcp_subnet_get_value, + dhcp_subnet_destroy, + dhcp_subnet_signal_handler, + dhcp_subnet_stuff_values, + dhcp_subnet_lookup, + dhcp_subnet_create, + dhcp_subnet_remove, 0, 0, 0, + sizeof (struct subnet), 0, + RC_MISC); + if (status != ISC_R_SUCCESS) + log_fatal ("Can't register subnet object type: %s", + isc_result_totext (status)); + + status = omapi_object_type_register + (&dhcp_type_shared_network, + "shared-network", + dhcp_shared_network_set_value, + dhcp_shared_network_get_value, + dhcp_shared_network_destroy, + dhcp_shared_network_signal_handler, + dhcp_shared_network_stuff_values, + dhcp_shared_network_lookup, + dhcp_shared_network_create, + dhcp_shared_network_remove, 0, 0, 0, + sizeof (struct shared_network), 0, RC_MISC); + if (status != ISC_R_SUCCESS) + log_fatal ("Can't register shared network object type: %s", + isc_result_totext (status)); + + interface_setup (); +} + +isc_result_t dhcp_group_set_value (omapi_object_t *h, + omapi_object_t *id, + omapi_data_string_t *name, + omapi_typed_data_t *value) +{ + struct group_object *group; + isc_result_t status; + + if (h -> type != dhcp_type_group) + return DHCP_R_INVALIDARG; + group = (struct group_object *)h; + + /* XXX For now, we can only set these values on new group objects. + XXX Soon, we need to be able to update group objects. */ + if (!omapi_ds_strcmp (name, "name")) { + if (group -> name) + return ISC_R_EXISTS; + if (value -> type == omapi_datatype_data || + value -> type == omapi_datatype_string) { + group -> name = dmalloc (value -> u.buffer.len + 1, + MDL); + if (!group -> name) + return ISC_R_NOMEMORY; + memcpy (group -> name, + value -> u.buffer.value, + value -> u.buffer.len); + group -> name [value -> u.buffer.len] = 0; + } else + return DHCP_R_INVALIDARG; + return ISC_R_SUCCESS; + } + + if (!omapi_ds_strcmp (name, "statements")) { + if (group -> group && group -> group -> statements) + return ISC_R_EXISTS; + if (!group -> group) { + if (!clone_group (&group -> group, root_group, MDL)) + return ISC_R_NOMEMORY; + } + if (value -> type == omapi_datatype_data || + value -> type == omapi_datatype_string) { + struct parse *parse; + int lose = 0; + parse = NULL; + status = new_parse(&parse, -1, + (char *) value->u.buffer.value, + value->u.buffer.len, + "network client", 0); + if (status != ISC_R_SUCCESS || parse == NULL) + return status; + if (!(parse_executable_statements + (&group -> group -> statements, parse, &lose, + context_any))) { + end_parse (&parse); + return DHCP_R_BADPARSE; + } + end_parse (&parse); + return ISC_R_SUCCESS; + } else + return DHCP_R_INVALIDARG; + } + + /* Try to find some inner object that can take the value. */ + if (h -> inner && h -> inner -> type -> set_value) { + status = ((*(h -> inner -> type -> set_value)) + (h -> inner, id, name, value)); + if (status == ISC_R_SUCCESS || status == DHCP_R_UNCHANGED) + return status; + } + + return ISC_R_NOTFOUND; +} + + +isc_result_t dhcp_group_get_value (omapi_object_t *h, omapi_object_t *id, + omapi_data_string_t *name, + omapi_value_t **value) +{ + struct group_object *group; + isc_result_t status; + + if (h -> type != dhcp_type_group) + return DHCP_R_INVALIDARG; + group = (struct group_object *)h; + + if (!omapi_ds_strcmp (name, "name")) + return omapi_make_string_value (value, + name, group -> name, MDL); + + /* Try to find some inner object that can take the value. */ + if (h -> inner && h -> inner -> type -> get_value) { + status = ((*(h -> inner -> type -> get_value)) + (h -> inner, id, name, value)); + if (status == ISC_R_SUCCESS) + return status; + } + return ISC_R_NOTFOUND; +} + +isc_result_t dhcp_group_destroy (omapi_object_t *h, const char *file, int line) +{ + struct group_object *group, *t; + + if (h -> type != dhcp_type_group) + return DHCP_R_INVALIDARG; + group = (struct group_object *)h; + + if (group -> name) { + if (group_name_hash) { + t = (struct group_object *)0; + if (group_hash_lookup (&t, group_name_hash, + group -> name, + strlen (group -> name), MDL)) { + group_hash_delete (group_name_hash, + group -> name, + strlen (group -> name), + MDL); + group_object_dereference (&t, MDL); + } + } + dfree (group -> name, file, line); + group -> name = (char *)0; + } + if (group -> group) + group_dereference (&group -> group, MDL); + + return ISC_R_SUCCESS; +} + +isc_result_t dhcp_group_signal_handler (omapi_object_t *h, + const char *name, va_list ap) +{ + struct group_object *group; + isc_result_t status; + int updatep = 0; + + if (h -> type != dhcp_type_group) + return DHCP_R_INVALIDARG; + group = (struct group_object *)h; + + if (!strcmp (name, "updated")) { + /* A group object isn't valid if a subgroup hasn't yet been + associated with it. */ + if (!group -> group) + return DHCP_R_INVALIDARG; + + /* Group objects always have to have names. */ + if (!group -> name) { + char hnbuf [64]; + sprintf (hnbuf, "ng%08lx%08lx", + (unsigned long)cur_time, + (unsigned long)group); + group -> name = dmalloc (strlen (hnbuf) + 1, MDL); + if (!group -> name) + return ISC_R_NOMEMORY; + strcpy (group -> name, hnbuf); + } + + supersede_group (group, 1); + updatep = 1; + } + + /* Try to find some inner object that can take the value. */ + if (h -> inner && h -> inner -> type -> get_value) { + status = ((*(h -> inner -> type -> signal_handler)) + (h -> inner, name, ap)); + if (status == ISC_R_SUCCESS) + return status; + } + if (updatep) + return ISC_R_SUCCESS; + return ISC_R_NOTFOUND; +} + +isc_result_t dhcp_group_stuff_values (omapi_object_t *c, + omapi_object_t *id, + omapi_object_t *h) +{ + struct group_object *group; + isc_result_t status; + + if (h -> type != dhcp_type_group) + return DHCP_R_INVALIDARG; + group = (struct group_object *)h; + + /* Write out all the values. */ + if (group -> name) { + status = omapi_connection_put_name (c, "name"); + if (status != ISC_R_SUCCESS) + return status; + status = omapi_connection_put_string (c, group -> name); + if (status != ISC_R_SUCCESS) + return status; + } + + /* Write out the inner object, if any. */ + if (h -> inner && h -> inner -> type -> stuff_values) { + status = ((*(h -> inner -> type -> stuff_values)) + (c, id, h -> inner)); + if (status == ISC_R_SUCCESS) + return status; + } + + return ISC_R_SUCCESS; +} + +isc_result_t dhcp_group_lookup (omapi_object_t **lp, + omapi_object_t *id, omapi_object_t *ref) +{ + omapi_value_t *tv = (omapi_value_t *)0; + isc_result_t status; + struct group_object *group; + + if (!ref) + return DHCP_R_NOKEYS; + + /* First see if we were sent a handle. */ + status = omapi_get_value_str (ref, id, "handle", &tv); + if (status == ISC_R_SUCCESS) { + status = omapi_handle_td_lookup (lp, tv -> value); + + omapi_value_dereference (&tv, MDL); + if (status != ISC_R_SUCCESS) + return status; + + /* Don't return the object if the type is wrong. */ + if ((*lp) -> type != dhcp_type_group) { + omapi_object_dereference (lp, MDL); + return DHCP_R_INVALIDARG; + } + } + + /* Now look for a name. */ + status = omapi_get_value_str (ref, id, "name", &tv); + if (status == ISC_R_SUCCESS) { + group = (struct group_object *)0; + if (group_name_hash && + group_hash_lookup (&group, group_name_hash, + (const char *) + tv -> value -> u.buffer.value, + tv -> value -> u.buffer.len, MDL)) { + omapi_value_dereference (&tv, MDL); + + if (*lp && *lp != (omapi_object_t *)group) { + group_object_dereference (&group, MDL); + omapi_object_dereference (lp, MDL); + return DHCP_R_KEYCONFLICT; + } else if (!*lp) { + /* XXX fix so that hash lookup itself creates + XXX the reference. */ + omapi_object_reference (lp, + (omapi_object_t *)group, + MDL); + group_object_dereference (&group, MDL); + } + } else if (!*lp) + return ISC_R_NOTFOUND; + } + + /* If we get to here without finding a group, no valid key was + specified. */ + if (!*lp) + return DHCP_R_NOKEYS; + + if (((struct group_object *)(*lp)) -> flags & GROUP_OBJECT_DELETED) { + omapi_object_dereference (lp, MDL); + return ISC_R_NOTFOUND; + } + return ISC_R_SUCCESS; +} + +isc_result_t dhcp_group_create (omapi_object_t **lp, + omapi_object_t *id) +{ + struct group_object *group; + isc_result_t status; + group = (struct group_object *)0; + + status = group_object_allocate (&group, MDL); + if (status != ISC_R_SUCCESS) + return status; + group -> flags = GROUP_OBJECT_DYNAMIC; + status = omapi_object_reference (lp, (omapi_object_t *)group, MDL); + group_object_dereference (&group, MDL); + return status; +} + +isc_result_t dhcp_group_remove (omapi_object_t *lp, + omapi_object_t *id) +{ + struct group_object *group; + isc_result_t status; + if (lp -> type != dhcp_type_group) + return DHCP_R_INVALIDARG; + group = (struct group_object *)lp; + + group -> flags |= GROUP_OBJECT_DELETED; + if (group_write_hook) { + if (!(*group_write_hook) (group)) + return ISC_R_IOERROR; + } + + status = dhcp_group_destroy ((omapi_object_t *)group, MDL); + + return status; +} + +isc_result_t dhcp_control_set_value (omapi_object_t *h, + omapi_object_t *id, + omapi_data_string_t *name, + omapi_typed_data_t *value) +{ + dhcp_control_object_t *control; + isc_result_t status; + unsigned long newstate; + + if (h -> type != dhcp_type_control) + return DHCP_R_INVALIDARG; + control = (dhcp_control_object_t *)h; + + if (!omapi_ds_strcmp (name, "state")) { + status = omapi_get_int_value (&newstate, value); + if (status != ISC_R_SUCCESS) + return status; + status = dhcp_set_control_state (control -> state, newstate); + if (status == ISC_R_SUCCESS) + control -> state = value -> u.integer; + return status; + } + + /* Try to find some inner object that can take the value. */ + if (h -> inner && h -> inner -> type -> set_value) { + status = ((*(h -> inner -> type -> set_value)) + (h -> inner, id, name, value)); + if (status == ISC_R_SUCCESS || status == DHCP_R_UNCHANGED) + return status; + } + + return ISC_R_NOTFOUND; +} + + +isc_result_t dhcp_control_get_value (omapi_object_t *h, omapi_object_t *id, + omapi_data_string_t *name, + omapi_value_t **value) +{ + dhcp_control_object_t *control; + isc_result_t status; + + if (h -> type != dhcp_type_control) + return DHCP_R_INVALIDARG; + control = (dhcp_control_object_t *)h; + + if (!omapi_ds_strcmp (name, "state")) + return omapi_make_int_value (value, + name, (int)control -> state, MDL); + + /* Try to find some inner object that can take the value. */ + if (h -> inner && h -> inner -> type -> get_value) { + status = ((*(h -> inner -> type -> get_value)) + (h -> inner, id, name, value)); + if (status == ISC_R_SUCCESS) + return status; + } + return ISC_R_NOTFOUND; +} + +isc_result_t dhcp_control_destroy (omapi_object_t *h, + const char *file, int line) +{ + if (h -> type != dhcp_type_control) + return DHCP_R_INVALIDARG; + + /* Can't destroy the control object. */ + return ISC_R_NOPERM; +} + +isc_result_t dhcp_control_signal_handler (omapi_object_t *h, + const char *name, va_list ap) +{ + /* In this function h should be a (dhcp_control_object_t *) */ + + isc_result_t status; + + if (h -> type != dhcp_type_control) + return DHCP_R_INVALIDARG; + + /* Try to find some inner object that can take the value. */ + if (h -> inner && h -> inner -> type -> get_value) { + status = ((*(h -> inner -> type -> signal_handler)) + (h -> inner, name, ap)); + if (status == ISC_R_SUCCESS) + return status; + } + return ISC_R_NOTFOUND; +} + +isc_result_t dhcp_control_stuff_values (omapi_object_t *c, + omapi_object_t *id, + omapi_object_t *h) +{ + dhcp_control_object_t *control; + isc_result_t status; + + if (h -> type != dhcp_type_control) + return DHCP_R_INVALIDARG; + control = (dhcp_control_object_t *)h; + + /* Write out all the values. */ + status = omapi_connection_put_name (c, "state"); + if (status != ISC_R_SUCCESS) + return status; + status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); + if (status != ISC_R_SUCCESS) + return status; + status = omapi_connection_put_uint32 (c, control -> state); + if (status != ISC_R_SUCCESS) + return status; + + /* Write out the inner object, if any. */ + if (h -> inner && h -> inner -> type -> stuff_values) { + status = ((*(h -> inner -> type -> stuff_values)) + (c, id, h -> inner)); + if (status == ISC_R_SUCCESS) + return status; + } + + return ISC_R_SUCCESS; +} + +isc_result_t dhcp_control_lookup (omapi_object_t **lp, + omapi_object_t *id, omapi_object_t *ref) +{ + omapi_value_t *tv = (omapi_value_t *)0; + isc_result_t status; + + /* First see if we were sent a handle. */ + if (ref) { + status = omapi_get_value_str (ref, id, "handle", &tv); + if (status == ISC_R_SUCCESS) { + status = omapi_handle_td_lookup (lp, tv -> value); + + omapi_value_dereference (&tv, MDL); + if (status != ISC_R_SUCCESS) + return status; + + /* Don't return the object if the type is wrong. */ + if ((*lp) -> type != dhcp_type_control) { + omapi_object_dereference (lp, MDL); + return DHCP_R_INVALIDARG; + } + } + } + + /* Otherwise, stop playing coy - there's only one control object, + so we can just return it. */ + dhcp_control_reference ((dhcp_control_object_t **)lp, + dhcp_control_object, MDL); + return ISC_R_SUCCESS; +} + +isc_result_t dhcp_control_create (omapi_object_t **lp, + omapi_object_t *id) +{ + /* Can't create a control object - there can be only one. */ + return ISC_R_NOPERM; +} + +isc_result_t dhcp_control_remove (omapi_object_t *lp, + omapi_object_t *id) +{ + /* Form is emptiness; emptiness form. The control object + cannot go out of existance. */ + return ISC_R_NOPERM; +} + +isc_result_t dhcp_subnet_set_value (omapi_object_t *h, + omapi_object_t *id, + omapi_data_string_t *name, + omapi_typed_data_t *value) +{ + /* In this function h should be a (struct subnet *) */ + + isc_result_t status; + + if (h -> type != dhcp_type_subnet) + return DHCP_R_INVALIDARG; + + /* No values to set yet. */ + + /* Try to find some inner object that can take the value. */ + if (h -> inner && h -> inner -> type -> set_value) { + status = ((*(h -> inner -> type -> set_value)) + (h -> inner, id, name, value)); + if (status == ISC_R_SUCCESS || status == DHCP_R_UNCHANGED) + return status; + } + + return ISC_R_NOTFOUND; +} + + +isc_result_t dhcp_subnet_get_value (omapi_object_t *h, omapi_object_t *id, + omapi_data_string_t *name, + omapi_value_t **value) +{ + /* In this function h should be a (struct subnet *) */ + + isc_result_t status; + + if (h -> type != dhcp_type_subnet) + return DHCP_R_INVALIDARG; + + /* No values to get yet. */ + + /* Try to find some inner object that can provide the value. */ + if (h -> inner && h -> inner -> type -> get_value) { + status = ((*(h -> inner -> type -> get_value)) + (h -> inner, id, name, value)); + if (status == ISC_R_SUCCESS) + return status; + } + return ISC_R_NOTFOUND; +} + +isc_result_t dhcp_subnet_destroy (omapi_object_t *h, const char *file, int line) +{ +#if defined (DEBUG_MEMORY_LEAKAGE) || \ + defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT) + struct subnet *subnet; +#endif + + if (h -> type != dhcp_type_subnet) + return DHCP_R_INVALIDARG; + +#if defined (DEBUG_MEMORY_LEAKAGE) || \ + defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT) + subnet = (struct subnet *)h; + if (subnet -> next_subnet) + subnet_dereference (&subnet -> next_subnet, file, line); + if (subnet -> next_sibling) + subnet_dereference (&subnet -> next_sibling, file, line); + if (subnet -> shared_network) + shared_network_dereference (&subnet -> shared_network, + file, line); + if (subnet -> interface) + interface_dereference (&subnet -> interface, file, line); + if (subnet -> group) + group_dereference (&subnet -> group, file, line); +#endif + + return ISC_R_SUCCESS; +} + +isc_result_t dhcp_subnet_signal_handler (omapi_object_t *h, + const char *name, va_list ap) +{ + /* In this function h should be a (struct subnet *) */ + + isc_result_t status; + + if (h -> type != dhcp_type_subnet) + return DHCP_R_INVALIDARG; + + /* Can't write subnets yet. */ + + /* Try to find some inner object that can take the value. */ + if (h -> inner && h -> inner -> type -> get_value) { + status = ((*(h -> inner -> type -> signal_handler)) + (h -> inner, name, ap)); + if (status == ISC_R_SUCCESS) + return status; + } + + return ISC_R_NOTFOUND; +} + +isc_result_t dhcp_subnet_stuff_values (omapi_object_t *c, + omapi_object_t *id, + omapi_object_t *h) +{ + /* In this function h should be a (struct subnet *) */ + + isc_result_t status; + + if (h -> type != dhcp_type_subnet) + return DHCP_R_INVALIDARG; + + /* Can't stuff subnet values yet. */ + + /* Write out the inner object, if any. */ + if (h -> inner && h -> inner -> type -> stuff_values) { + status = ((*(h -> inner -> type -> stuff_values)) + (c, id, h -> inner)); + if (status == ISC_R_SUCCESS) + return status; + } + + return ISC_R_SUCCESS; +} + +isc_result_t dhcp_subnet_lookup (omapi_object_t **lp, + omapi_object_t *id, + omapi_object_t *ref) +{ + /* Can't look up subnets yet. */ + + /* If we get to here without finding a subnet, no valid key was + specified. */ + if (!*lp) + return DHCP_R_NOKEYS; + return ISC_R_SUCCESS; +} + +isc_result_t dhcp_subnet_create (omapi_object_t **lp, + omapi_object_t *id) +{ + return ISC_R_NOTIMPLEMENTED; +} + +isc_result_t dhcp_subnet_remove (omapi_object_t *lp, + omapi_object_t *id) +{ + return ISC_R_NOTIMPLEMENTED; +} + +isc_result_t dhcp_shared_network_set_value (omapi_object_t *h, + omapi_object_t *id, + omapi_data_string_t *name, + omapi_typed_data_t *value) +{ + /* In this function h should be a (struct shared_network *) */ + + isc_result_t status; + + if (h -> type != dhcp_type_shared_network) + return DHCP_R_INVALIDARG; + + /* No values to set yet. */ + + /* Try to find some inner object that can take the value. */ + if (h -> inner && h -> inner -> type -> set_value) { + status = ((*(h -> inner -> type -> set_value)) + (h -> inner, id, name, value)); + if (status == ISC_R_SUCCESS || status == DHCP_R_UNCHANGED) + return status; + } + + return ISC_R_NOTFOUND; +} + + +isc_result_t dhcp_shared_network_get_value (omapi_object_t *h, + omapi_object_t *id, + omapi_data_string_t *name, + omapi_value_t **value) +{ + /* In this function h should be a (struct shared_network *) */ + + isc_result_t status; + + if (h -> type != dhcp_type_shared_network) + return DHCP_R_INVALIDARG; + + /* No values to get yet. */ + + /* Try to find some inner object that can provide the value. */ + if (h -> inner && h -> inner -> type -> get_value) { + status = ((*(h -> inner -> type -> get_value)) + (h -> inner, id, name, value)); + if (status == ISC_R_SUCCESS) + return status; + } + return ISC_R_NOTFOUND; +} + +isc_result_t dhcp_shared_network_destroy (omapi_object_t *h, + const char *file, int line) +{ + /* In this function h should be a (struct shared_network *) */ + +#if defined (DEBUG_MEMORY_LEAKAGE) || \ + defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT) + struct shared_network *shared_network; +#endif + + if (h -> type != dhcp_type_shared_network) + return DHCP_R_INVALIDARG; + +#if defined (DEBUG_MEMORY_LEAKAGE) || \ + defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT) + shared_network = (struct shared_network *)h; + if (shared_network -> next) + shared_network_dereference (&shared_network -> next, + file, line); + if (shared_network -> name) { + dfree (shared_network -> name, file, line); + shared_network -> name = 0; + } + if (shared_network -> subnets) + subnet_dereference (&shared_network -> subnets, file, line); + if (shared_network -> interface) + interface_dereference (&shared_network -> interface, + file, line); + if (shared_network -> pools) + omapi_object_dereference ((omapi_object_t **) + &shared_network -> pools, file, line); + if (shared_network -> group) + group_dereference (&shared_network -> group, file, line); +#if defined (FAILOVER_PROTOCOL) + if (shared_network -> failover_peer) + omapi_object_dereference ((omapi_object_t **) + &shared_network -> failover_peer, + file, line); +#endif +#endif /* DEBUG_MEMORY_LEAKAGE */ + + return ISC_R_SUCCESS; +} + +isc_result_t dhcp_shared_network_signal_handler (omapi_object_t *h, + const char *name, + va_list ap) +{ + /* In this function h should be a (struct shared_network *) */ + + isc_result_t status; + + if (h -> type != dhcp_type_shared_network) + return DHCP_R_INVALIDARG; + + /* Can't write shared_networks yet. */ + + /* Try to find some inner object that can take the value. */ + if (h -> inner && h -> inner -> type -> get_value) { + status = ((*(h -> inner -> type -> signal_handler)) + (h -> inner, name, ap)); + if (status == ISC_R_SUCCESS) + return status; + } + + return ISC_R_NOTFOUND; +} + +isc_result_t dhcp_shared_network_stuff_values (omapi_object_t *c, + omapi_object_t *id, + omapi_object_t *h) +{ + /* In this function h should be a (struct shared_network *) */ + + isc_result_t status; + + if (h -> type != dhcp_type_shared_network) + return DHCP_R_INVALIDARG; + + /* Can't stuff shared_network values yet. */ + + /* Write out the inner object, if any. */ + if (h -> inner && h -> inner -> type -> stuff_values) { + status = ((*(h -> inner -> type -> stuff_values)) + (c, id, h -> inner)); + if (status == ISC_R_SUCCESS) + return status; + } + + return ISC_R_SUCCESS; +} + +isc_result_t dhcp_shared_network_lookup (omapi_object_t **lp, + omapi_object_t *id, + omapi_object_t *ref) +{ + /* Can't look up shared_networks yet. */ + + /* If we get to here without finding a shared_network, no valid key was + specified. */ + if (!*lp) + return DHCP_R_NOKEYS; + return ISC_R_SUCCESS; +} + +isc_result_t dhcp_shared_network_create (omapi_object_t **lp, + omapi_object_t *id) +{ + return ISC_R_NOTIMPLEMENTED; +} + +isc_result_t dhcp_shared_network_remove (omapi_object_t *lp, + omapi_object_t *id) +{ + return ISC_R_NOTIMPLEMENTED; +} + diff --git a/common/conflex.c b/common/conflex.c new file mode 100644 index 0000000..f634121 --- /dev/null +++ b/common/conflex.c @@ -0,0 +1,1535 @@ +/* conflex.c + + Lexical scanner for dhcpd config file... */ + +/* + * Copyright (c) 2004-2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" +#include <ctype.h> + +static int get_char (struct parse *); +static void unget_char(struct parse *, int); +static void skip_to_eol (struct parse *); +static enum dhcp_token read_whitespace(int c, struct parse *cfile); +static enum dhcp_token read_string (struct parse *); +static enum dhcp_token read_number (int, struct parse *); +static enum dhcp_token read_num_or_name (int, struct parse *); +static enum dhcp_token intern (char *, enum dhcp_token); + +isc_result_t new_parse (cfile, file, inbuf, buflen, name, eolp) + struct parse **cfile; + int file; + char *inbuf; + unsigned buflen; + const char *name; + int eolp; +{ + isc_result_t status = ISC_R_SUCCESS; + struct parse *tmp; + + tmp = dmalloc(sizeof(struct parse), MDL); + if (tmp == NULL) { + return (ISC_R_NOMEMORY); + } + + /* + * We don't need to initialize things to zero here, since + * dmalloc() returns memory that is set to zero. + */ + tmp->tlname = name; + tmp->lpos = tmp -> line = 1; + tmp->cur_line = tmp->line1; + tmp->prev_line = tmp->line2; + tmp->token_line = tmp->cur_line; + tmp->cur_line[0] = tmp->prev_line[0] = 0; + tmp->file = file; + tmp->eol_token = eolp; + + if (inbuf != NULL) { + tmp->inbuf = inbuf; + tmp->buflen = buflen; + tmp->bufsiz = 0; + } else { + struct stat sb; + + if (fstat(file, &sb) < 0) { + status = ISC_R_IOERROR; + goto cleanup; + } + + if (sb.st_size == 0) + goto cleanup; + + tmp->bufsiz = tmp->buflen = (size_t) sb.st_size; + tmp->inbuf = mmap(NULL, tmp->bufsiz, PROT_READ, MAP_SHARED, + file, 0); + + if (tmp->inbuf == MAP_FAILED) { + status = ISC_R_IOERROR; + goto cleanup; + } + } + + *cfile = tmp; + return (ISC_R_SUCCESS); + +cleanup: + dfree(tmp, MDL); + return (status); +} + +isc_result_t end_parse (cfile) + struct parse **cfile; +{ + /* "Memory" config files have no file. */ + if ((*cfile)->file != -1) { + munmap((*cfile)->inbuf, (*cfile)->bufsiz); + close((*cfile)->file); + } + + if ((*cfile)->saved_state != NULL) { + dfree((*cfile)->saved_state, MDL); + } + + dfree(*cfile, MDL); + *cfile = NULL; + return ISC_R_SUCCESS; +} + +/* + * Save the current state of the parser. + * + * Only one state may be saved. Any previous saved state is + * lost. + */ +isc_result_t +save_parse_state(struct parse *cfile) { + /* + * Free any previous saved state. + */ + if (cfile->saved_state != NULL) { + dfree(cfile->saved_state, MDL); + } + + /* + * Save our current state. + */ + cfile->saved_state = dmalloc(sizeof(struct parse), MDL); + if (cfile->saved_state == NULL) { + return ISC_R_NOMEMORY; + } + memcpy(cfile->saved_state, cfile, sizeof(*cfile)); + return ISC_R_SUCCESS; +} + +/* + * Return the parser to the previous saved state. + * + * You must call save_parse_state() before calling + * restore_parse_state(), but you can call restore_parse_state() any + * number of times after that. + */ +isc_result_t +restore_parse_state(struct parse *cfile) { + struct parse *saved_state; + + if (cfile->saved_state == NULL) { + return DHCP_R_NOTYET; + } + + saved_state = cfile->saved_state; + memcpy(cfile, saved_state, sizeof(*cfile)); + cfile->saved_state = saved_state; + return ISC_R_SUCCESS; +} + +static int get_char (cfile) + struct parse *cfile; +{ + /* My kingdom for WITH... */ + int c; + + if (cfile->bufix == cfile->buflen) { +#if !defined(LDAP_CONFIGURATION) + c = EOF; +#else /* defined(LDAP_CONFIGURATION) */ + if (cfile->read_function != NULL) + c = cfile->read_function(cfile); + else + c = EOF; +#endif + } else { + c = cfile->inbuf [cfile->bufix]; + cfile->bufix++; + } + + if (!cfile->ugflag) { + if (c == EOL) { + if (cfile->cur_line == cfile->line1) { + cfile->cur_line = cfile->line2; + cfile->prev_line = cfile->line1; + } else { + cfile->cur_line = cfile->line1; + cfile->prev_line = cfile->line2; + } + cfile->line++; + cfile->lpos = 1; + cfile->cur_line [0] = 0; + } else if (c != EOF) { + if (cfile->lpos <= 80) { + cfile->cur_line [cfile->lpos - 1] = c; + cfile->cur_line [cfile->lpos] = 0; + } + cfile->lpos++; + } + } else + cfile->ugflag = 0; + return c; +} + +/* + * Return a character to our input buffer. + */ +static void +unget_char(struct parse *cfile, int c) { + if (c != EOF) { + cfile->bufix--; + cfile->ugflag = 1; /* do not put characters into + our error buffer on the next + call to get_char() */ + } +} + +/* + * GENERAL NOTE ABOUT TOKENS + * + * We normally only want non-whitespace tokens. There are some + * circumstances where we *do* want to see whitespace (for example + * when parsing IPv6 addresses). + * + * Generally we use the next_token() function to read tokens. This + * in turn calls get_next_token, which does *not* return tokens for + * whitespace. Rather, it skips these. + * + * When we need to see whitespace, we us next_raw_token(), which also + * returns the WHITESPACE token. + * + * The peek_token() and peek_raw_token() functions work as expected. + * + * Warning: if you invoke peek_token(), then if there is a whitespace + * token, it will be lost, and subsequent use of next_raw_token() or + * peek_raw_token() will NOT see it. + */ + +static enum dhcp_token +get_raw_token(struct parse *cfile) { + int c; + enum dhcp_token ttok; + static char tb [2]; + int l, p; + + do { + l = cfile -> line; + p = cfile -> lpos; + + c = get_char (cfile); + if (!((c == '\n') && cfile->eol_token) && + isascii(c) && isspace(c)) { + ttok = read_whitespace(c, cfile); + break; + } + if (c == '#') { + skip_to_eol (cfile); + continue; + } + if (c == '"') { + cfile -> lexline = l; + cfile -> lexchar = p; + ttok = read_string (cfile); + break; + } + if ((isascii (c) && isdigit (c)) || c == '-') { + cfile -> lexline = l; + cfile -> lexchar = p; + ttok = read_number (c, cfile); + break; + } else if (isascii (c) && isalpha (c)) { + cfile -> lexline = l; + cfile -> lexchar = p; + ttok = read_num_or_name (c, cfile); + break; + } else if (c == EOF) { + ttok = END_OF_FILE; + cfile -> tlen = 0; + break; + } else { + cfile -> lexline = l; + cfile -> lexchar = p; + tb [0] = c; + tb [1] = 0; + cfile -> tval = tb; + cfile -> tlen = 1; + ttok = c; + break; + } + } while (1); + return ttok; +} + +/* + * The get_next_token() function consumes the next token and + * returns it to the caller. + * + * Since the code is almost the same for "normal" and "raw" + * input, we pass a flag to alter the way it works. + */ + +static enum dhcp_token +get_next_token(const char **rval, unsigned *rlen, + struct parse *cfile, isc_boolean_t raw) { + int rv; + + if (cfile -> token) { + if (cfile -> lexline != cfile -> tline) + cfile -> token_line = cfile -> cur_line; + cfile -> lexchar = cfile -> tlpos; + cfile -> lexline = cfile -> tline; + rv = cfile -> token; + cfile -> token = 0; + } else { + rv = get_raw_token(cfile); + cfile -> token_line = cfile -> cur_line; + } + + if (!raw) { + while (rv == WHITESPACE) { + rv = get_raw_token(cfile); + cfile->token_line = cfile->cur_line; + } + } + + if (rval) + *rval = cfile -> tval; + if (rlen) + *rlen = cfile -> tlen; +#ifdef DEBUG_TOKENS + fprintf (stderr, "%s:%d ", cfile -> tval, rv); +#endif + return rv; +} + + +/* + * Get the next token from cfile and return it. + * + * If rval is non-NULL, set the pointer it contains to + * the contents of the token. + * + * If rlen is non-NULL, set the integer it contains to + * the length of the token. + */ + +enum dhcp_token +next_token(const char **rval, unsigned *rlen, struct parse *cfile) { + return get_next_token(rval, rlen, cfile, ISC_FALSE); +} + + +/* + * The same as the next_token() function above, but will return space + * as the WHITESPACE token. + */ + +enum dhcp_token +next_raw_token(const char **rval, unsigned *rlen, struct parse *cfile) { + return get_next_token(rval, rlen, cfile, ISC_TRUE); +} + + +/* + * The do_peek_token() function checks the next token without + * consuming it, and returns it to the caller. + * + * Since the code is almost the same for "normal" and "raw" + * input, we pass a flag to alter the way it works. (See the + * warning in the GENERAL NOTES ABOUT TOKENS above though.) + */ + +enum dhcp_token +do_peek_token(const char **rval, unsigned int *rlen, + struct parse *cfile, isc_boolean_t raw) { + int x; + + if (!cfile->token || (!raw && (cfile->token == WHITESPACE))) { + cfile -> tlpos = cfile -> lexchar; + cfile -> tline = cfile -> lexline; + + do { + cfile->token = get_raw_token(cfile); + } while (!raw && (cfile->token == WHITESPACE)); + + if (cfile -> lexline != cfile -> tline) + cfile -> token_line = cfile -> prev_line; + + x = cfile -> lexchar; + cfile -> lexchar = cfile -> tlpos; + cfile -> tlpos = x; + + x = cfile -> lexline; + cfile -> lexline = cfile -> tline; + cfile -> tline = x; + } + if (rval) + *rval = cfile -> tval; + if (rlen) + *rlen = cfile -> tlen; +#ifdef DEBUG_TOKENS + fprintf (stderr, "(%s:%d) ", cfile -> tval, cfile -> token); +#endif + return cfile -> token; +} + + +/* + * Get the next token from cfile and return it, leaving it for a + * subsequent call to next_token(). + * + * Note that it WILL consume whitespace tokens. + * + * If rval is non-NULL, set the pointer it contains to + * the contents of the token. + * + * If rlen is non-NULL, set the integer it contains to + * the length of the token. + */ + +enum dhcp_token +peek_token(const char **rval, unsigned *rlen, struct parse *cfile) { + return do_peek_token(rval, rlen, cfile, ISC_FALSE); +} + + +/* + * The same as the peek_token() function above, but will return space + * as the WHITESPACE token. + */ + +enum dhcp_token +peek_raw_token(const char **rval, unsigned *rlen, struct parse *cfile) { + return do_peek_token(rval, rlen, cfile, ISC_TRUE); +} + +static void skip_to_eol (cfile) + struct parse *cfile; +{ + int c; + do { + c = get_char (cfile); + if (c == EOF) + return; + if (c == EOL) { + return; + } + } while (1); +} + +static enum dhcp_token +read_whitespace(int c, struct parse *cfile) { + int ofs; + + /* + * Read as much whitespace as we have available. + */ + ofs = 0; + do { + if (ofs >= (sizeof(cfile->tokbuf) - 1)) { + /* + * As the file includes a huge amount of whitespace, + * it's probably broken. + * Print out a warning and bail out. + */ + parse_warn(cfile, + "whitespace too long, buffer overflow."); + log_fatal("Exiting"); + } + cfile->tokbuf[ofs++] = c; + c = get_char(cfile); + } while (!((c == '\n') && cfile->eol_token) && + isascii(c) && isspace(c)); + + /* + * Put the last (non-whitespace) character back. + */ + unget_char(cfile, c); + + /* + * Return our token. + */ + cfile->tokbuf[ofs] = '\0'; + cfile->tlen = ofs; + cfile->tval = cfile->tokbuf; + return WHITESPACE; +} + +static enum dhcp_token read_string (cfile) + struct parse *cfile; +{ + int i; + int bs = 0; + int c; + int value = 0; + int hex = 0; + + for (i = 0; i < sizeof cfile -> tokbuf; i++) { + again: + c = get_char (cfile); + if (c == EOF) { + parse_warn (cfile, "eof in string constant"); + break; + } + if (bs == 1) { + switch (c) { + case 't': + cfile -> tokbuf [i] = '\t'; + break; + case 'r': + cfile -> tokbuf [i] = '\r'; + break; + case 'n': + cfile -> tokbuf [i] = '\n'; + break; + case 'b': + cfile -> tokbuf [i] = '\b'; + break; + case '0': + case '1': + case '2': + case '3': + hex = 0; + value = c - '0'; + ++bs; + goto again; + case 'x': + hex = 1; + value = 0; + ++bs; + goto again; + default: + cfile -> tokbuf [i] = c; + break; + } + bs = 0; + } else if (bs > 1) { + if (hex) { + if (c >= '0' && c <= '9') { + value = value * 16 + (c - '0'); + } else if (c >= 'a' && c <= 'f') { + value = value * 16 + (c - 'a' + 10); + } else if (c >= 'A' && c <= 'F') { + value = value * 16 + (c - 'A' + 10); + } else { + parse_warn (cfile, + "invalid hex digit: %x", + c); + bs = 0; + continue; + } + if (++bs == 4) { + cfile -> tokbuf [i] = value; + bs = 0; + } else + goto again; + } else { + if (c >= '0' && c <= '7') { + value = value * 8 + (c - '0'); + } else { + if (value != 0) { + parse_warn (cfile, + "invalid octal digit %x", + c); + continue; + } else + cfile -> tokbuf [i] = 0; + bs = 0; + } + if (++bs == 4) { + cfile -> tokbuf [i] = value; + bs = 0; + } else + goto again; + } + } else if (c == '\\') { + bs = 1; + goto again; + } else if (c == '"') + break; + else + cfile -> tokbuf [i] = c; + } + /* Normally, I'd feel guilty about this, but we're talking about + strings that'll fit in a DHCP packet here... */ + if (i == sizeof cfile -> tokbuf) { + parse_warn (cfile, + "string constant larger than internal buffer"); + --i; + } + cfile -> tokbuf [i] = 0; + cfile -> tlen = i; + cfile -> tval = cfile -> tokbuf; + return STRING; +} + +static enum dhcp_token read_number (c, cfile) + int c; + struct parse *cfile; +{ + int i = 0; + int token = NUMBER; + + cfile -> tokbuf [i++] = c; + for (; i < sizeof cfile -> tokbuf; i++) { + c = get_char (cfile); + + /* Promote NUMBER -> NUMBER_OR_NAME -> NAME, never demote. + * Except in the case of '0x' syntax hex, which gets called + * a NAME at '0x', and returned to NUMBER_OR_NAME once it's + * verified to be at least 0xf or less. + */ + switch(isascii(c) ? token : BREAK) { + case NUMBER: + if(isdigit(c)) + break; + /* FALLTHROUGH */ + case NUMBER_OR_NAME: + if(isxdigit(c)) { + token = NUMBER_OR_NAME; + break; + } + /* FALLTHROUGH */ + case NAME: + if((i == 2) && isxdigit(c) && + (cfile->tokbuf[0] == '0') && + ((cfile->tokbuf[1] == 'x') || + (cfile->tokbuf[1] == 'X'))) { + token = NUMBER_OR_NAME; + break; + } else if(((c == '-') || (c == '_') || isalnum(c))) { + token = NAME; + break; + } + /* FALLTHROUGH */ + case BREAK: + /* At this point c is either EOF or part of the next + * token. If not EOF, rewind the file one byte so + * the next token is read from there. + */ + unget_char(cfile, c); + goto end_read; + + default: + log_fatal("read_number():%s:%d: impossible case", MDL); + } + + cfile -> tokbuf [i] = c; + } + + if (i == sizeof cfile -> tokbuf) { + parse_warn (cfile, + "numeric token larger than internal buffer"); + --i; + } + + end_read: + cfile -> tokbuf [i] = 0; + cfile -> tlen = i; + cfile -> tval = cfile -> tokbuf; + + /* + * If this entire token from start to finish was "-", such as + * the middle parameter in "42 - 7", return just the MINUS token. + */ + if ((i == 1) && (cfile->tokbuf[i] == '-')) + return MINUS; + else + return token; +} + +static enum dhcp_token read_num_or_name (c, cfile) + int c; + struct parse *cfile; +{ + int i = 0; + enum dhcp_token rv = NUMBER_OR_NAME; + cfile -> tokbuf [i++] = c; + for (; i < sizeof cfile -> tokbuf; i++) { + c = get_char (cfile); + if (!isascii (c) || + (c != '-' && c != '_' && !isalnum (c))) { + unget_char(cfile, c); + break; + } + if (!isxdigit (c)) + rv = NAME; + cfile -> tokbuf [i] = c; + } + if (i == sizeof cfile -> tokbuf) { + parse_warn (cfile, "token larger than internal buffer"); + --i; + } + cfile -> tokbuf [i] = 0; + cfile -> tlen = i; + cfile -> tval = cfile -> tokbuf; + return intern(cfile->tval, rv); +} + +static enum dhcp_token +intern(char *atom, enum dhcp_token dfv) { + if (!isascii(atom[0])) + return dfv; + + switch (tolower((unsigned char)atom[0])) { + case '-': + if (atom [1] == 0) + return MINUS; + break; + + case 'a': + if (!strcasecmp(atom + 1, "bandoned")) + return TOKEN_ABANDONED; + if (!strcasecmp(atom + 1, "ctive")) + return TOKEN_ACTIVE; + if (!strncasecmp(atom + 1, "dd", 2)) { + if (atom[3] == '\0') + return TOKEN_ADD; + else if (!strcasecmp(atom + 3, "ress")) + return ADDRESS; + break; + } + if (!strcasecmp(atom + 1, "fter")) + return AFTER; + if (isascii(atom[1]) && + (tolower((unsigned char)atom[1]) == 'l')) { + if (!strcasecmp(atom + 2, "gorithm")) + return ALGORITHM; + if (!strcasecmp(atom + 2, "ias")) + return ALIAS; + if (isascii(atom[2]) && + (tolower((unsigned char)atom[2]) == 'l')) { + if (atom[3] == '\0') + return ALL; + else if (!strcasecmp(atom + 3, "ow")) + return ALLOW; + break; + } + if (!strcasecmp(atom + 2, "so")) + return TOKEN_ALSO; + break; + } + if (isascii(atom[1]) && + (tolower((unsigned char)atom[1]) == 'n')) { + if (!strcasecmp(atom + 2, "d")) + return AND; + if (!strcasecmp(atom + 2, "ycast-mac")) + return ANYCAST_MAC; + break; + } + if (!strcasecmp(atom + 1, "ppend")) + return APPEND; + if (!strcasecmp(atom + 1, "rray")) + return ARRAY; + if (isascii(atom[1]) && + (tolower((unsigned char)atom[1]) == 't')) { + if (atom[2] == '\0') + return AT; + if (!strcasecmp(atom + 2, "sfp")) + return ATSFP; + break; + } + if (!strncasecmp(atom + 1, "ut", 2)) { + if (isascii(atom[3]) && + (tolower((unsigned char)atom[3]) == 'h')) { + if (!strncasecmp(atom + 4, "enticat", 7)) { + if (!strcasecmp(atom + 11, "ed")) + return AUTHENTICATED; + if (!strcasecmp(atom + 11, "ion")) + return AUTHENTICATION; + break; + } + if (!strcasecmp(atom + 4, "oritative")) + return AUTHORITATIVE; + break; + } + if (!strcasecmp(atom + 3, "o-partner-down")) + return AUTO_PARTNER_DOWN; + break; + } + break; + case 'b': + if (!strcasecmp (atom + 1, "ackup")) + return TOKEN_BACKUP; + if (!strcasecmp (atom + 1, "ootp")) + return TOKEN_BOOTP; + if (!strcasecmp (atom + 1, "inding")) + return BINDING; + if (!strcasecmp (atom + 1, "inary-to-ascii")) + return BINARY_TO_ASCII; + if (!strcasecmp (atom + 1, "ackoff-cutoff")) + return BACKOFF_CUTOFF; + if (!strcasecmp (atom + 1, "ooting")) + return BOOTING; + if (!strcasecmp (atom + 1, "oot-unknown-clients")) + return BOOT_UNKNOWN_CLIENTS; + if (!strcasecmp (atom + 1, "reak")) + return BREAK; + if (!strcasecmp (atom + 1, "illing")) + return BILLING; + if (!strcasecmp (atom + 1, "oolean")) + return BOOLEAN; + if (!strcasecmp (atom + 1, "alance")) + return BALANCE; + if (!strcasecmp (atom + 1, "ound")) + return BOUND; + break; + case 'c': + if (!strcasecmp(atom + 1, "ase")) + return CASE; + if (!strcasecmp(atom + 1, "heck")) + return CHECK; + if (!strcasecmp(atom + 1, "iaddr")) + return CIADDR; + if (isascii(atom[1]) && + tolower((unsigned char)atom[1]) == 'l') { + if (!strcasecmp(atom + 2, "ass")) + return CLASS; + if (!strncasecmp(atom + 2, "ient", 4)) { + if (!strcasecmp(atom + 6, "s")) + return CLIENTS; + if (atom[6] == '-') { + if (!strcasecmp(atom + 7, "hostname")) + return CLIENT_HOSTNAME; + if (!strcasecmp(atom + 7, "identifier")) + return CLIENT_IDENTIFIER; + if (!strcasecmp(atom + 7, "state")) + return CLIENT_STATE; + if (!strcasecmp(atom + 7, "updates")) + return CLIENT_UPDATES; + break; + } + break; + } + if (!strcasecmp(atom + 2, "ose")) + return TOKEN_CLOSE; + if (!strcasecmp(atom + 2, "tt")) + return CLTT; + break; + } + if (isascii(atom[1]) && + tolower((unsigned char)atom[1]) == 'o') { + if (!strcasecmp(atom + 2, "de")) + return CODE; + if (isascii(atom[2]) && + tolower((unsigned char)atom[2]) == 'm') { + if (!strcasecmp(atom + 3, "mit")) + return COMMIT; + if (!strcasecmp(atom + 3, + "munications-interrupted")) + return COMMUNICATIONS_INTERRUPTED; + if (!strcasecmp(atom + 3, "pressed")) + return COMPRESSED; + break; + } + if (isascii(atom[2]) && + tolower((unsigned char)atom[2]) == 'n') { + if (!strcasecmp(atom + 3, "cat")) + return CONCAT; + if (!strcasecmp(atom + 3, "fig-option")) + return CONFIG_OPTION; + if (!strcasecmp(atom + 3, "flict-done")) + return CONFLICT_DONE; + if (!strcasecmp(atom + 3, "nect")) + return CONNECT; + break; + } + break; + } + if (!strcasecmp(atom + 1, "reate")) + return TOKEN_CREATE; + break; + case 'd': + if (!strcasecmp(atom + 1, "b-time-format")) + return DB_TIME_FORMAT; + if (!strcasecmp (atom + 1, "ns-update")) + return DNS_UPDATE; + if (!strcasecmp (atom + 1, "ns-delete")) + return DNS_DELETE; + if (!strcasecmp (atom + 1, "omain")) + return DOMAIN; + if (!strncasecmp (atom + 1, "omain-", 6)) { + if (!strcasecmp(atom + 7, "name")) + return DOMAIN_NAME; + if (!strcasecmp(atom + 7, "list")) + return DOMAIN_LIST; + } + if (!strcasecmp (atom + 1, "o-forward-updates")) + return DO_FORWARD_UPDATE; + /* do-forward-update is included for historical reasons */ + if (!strcasecmp (atom + 1, "o-forward-update")) + return DO_FORWARD_UPDATE; + if (!strcasecmp (atom + 1, "ebug")) + return TOKEN_DEBUG; + if (!strcasecmp (atom + 1, "eny")) + return DENY; + if (!strcasecmp (atom + 1, "eleted")) + return TOKEN_DELETED; + if (!strcasecmp (atom + 1, "elete")) + return TOKEN_DELETE; + if (!strncasecmp (atom + 1, "efault", 6)) { + if (!atom [7]) + return DEFAULT; + if (!strcasecmp(atom + 7, "-duid")) + return DEFAULT_DUID; + if (!strcasecmp (atom + 7, "-lease-time")) + return DEFAULT_LEASE_TIME; + break; + } + if (!strncasecmp (atom + 1, "ynamic", 6)) { + if (!atom [7]) + return DYNAMIC; + if (!strncasecmp (atom + 7, "-bootp", 6)) { + if (!atom [13]) + return DYNAMIC_BOOTP; + if (!strcasecmp (atom + 13, "-lease-cutoff")) + return DYNAMIC_BOOTP_LEASE_CUTOFF; + if (!strcasecmp (atom + 13, "-lease-length")) + return DYNAMIC_BOOTP_LEASE_LENGTH; + break; + } + } + if (!strcasecmp (atom + 1, "uplicates")) + return DUPLICATES; + if (!strcasecmp (atom + 1, "eclines")) + return DECLINES; + if (!strncasecmp (atom + 1, "efine", 5)) { + if (!strcasecmp (atom + 6, "d")) + return DEFINED; + if (!atom [6]) + return DEFINE; + } + break; + case 'e': + if (isascii (atom [1]) && + tolower((unsigned char)atom[1]) == 'x') { + if (!strcasecmp (atom + 2, "tract-int")) + return EXTRACT_INT; + if (!strcasecmp (atom + 2, "ists")) + return EXISTS; + if (!strcasecmp (atom + 2, "piry")) + return EXPIRY; + if (!strcasecmp (atom + 2, "pire")) + return EXPIRE; + if (!strcasecmp (atom + 2, "pired")) + return TOKEN_EXPIRED; + } + if (!strcasecmp (atom + 1, "ncode-int")) + return ENCODE_INT; + if (!strcasecmp(atom + 1, "poch")) + return EPOCH; + if (!strcasecmp (atom + 1, "thernet")) + return ETHERNET; + if (!strcasecmp (atom + 1, "nds")) + return ENDS; + if (!strncasecmp (atom + 1, "ls", 2)) { + if (!strcasecmp (atom + 3, "e")) + return ELSE; + if (!strcasecmp (atom + 3, "if")) + return ELSIF; + break; + } + if (!strcasecmp (atom + 1, "rror")) + return ERROR; + if (!strcasecmp (atom + 1, "val")) + return EVAL; + if (!strcasecmp (atom + 1, "ncapsulate")) + return ENCAPSULATE; + if (!strcasecmp(atom + 1, "xecute")) + return EXECUTE; + if (!strcasecmp(atom+1, "n")) { + return EN; + } + break; + case 'f': + if (!strcasecmp (atom + 1, "atal")) + return FATAL; + if (!strcasecmp (atom + 1, "ilename")) + return FILENAME; + if (!strcasecmp (atom + 1, "ixed-address")) + return FIXED_ADDR; + if (!strcasecmp (atom + 1, "ixed-address6")) + return FIXED_ADDR6; + if (!strcasecmp (atom + 1, "ixed-prefix6")) + return FIXED_PREFIX6; + if (!strcasecmp (atom + 1, "ddi")) + return TOKEN_FDDI; + if (!strcasecmp (atom + 1, "ormerr")) + return NS_FORMERR; + if (!strcasecmp (atom + 1, "unction")) + return FUNCTION; + if (!strcasecmp (atom + 1, "ailover")) + return FAILOVER; + if (!strcasecmp (atom + 1, "ree")) + return TOKEN_FREE; + break; + case 'g': + if (!strncasecmp(atom + 1, "et", 2)) { + if (!strcasecmp(atom + 3, "-lease-hostnames")) + return GET_LEASE_HOSTNAMES; + if (!strcasecmp(atom + 3, "hostbyname")) + return GETHOSTBYNAME; + if (!strcasecmp(atom + 3, "hostname")) + return GETHOSTNAME; + break; + } + if (!strcasecmp (atom + 1, "iaddr")) + return GIADDR; + if (!strcasecmp (atom + 1, "roup")) + return GROUP; + break; + case 'h': + if (!strcasecmp(atom + 1, "ash")) + return HASH; + if (!strcasecmp (atom + 1, "ba")) + return HBA; + if (!strcasecmp (atom + 1, "ost")) + return HOST; + if (!strcasecmp (atom + 1, "ost-decl-name")) + return HOST_DECL_NAME; + if (!strcasecmp(atom + 1, "ost-identifier")) + return HOST_IDENTIFIER; + if (!strcasecmp (atom + 1, "ardware")) + return HARDWARE; + if (!strcasecmp (atom + 1, "ostname")) + return HOSTNAME; + if (!strcasecmp (atom + 1, "elp")) + return TOKEN_HELP; + break; + case 'i': + if (!strcasecmp(atom+1, "a-na")) + return IA_NA; + if (!strcasecmp(atom+1, "a-ta")) + return IA_TA; + if (!strcasecmp(atom+1, "a-pd")) + return IA_PD; + if (!strcasecmp(atom+1, "aaddr")) + return IAADDR; + if (!strcasecmp(atom+1, "aprefix")) + return IAPREFIX; + if (!strcasecmp (atom + 1, "nclude")) + return INCLUDE; + if (!strcasecmp (atom + 1, "nteger")) + return INTEGER; + if (!strcasecmp (atom + 1, "nfiniband")) + return TOKEN_INFINIBAND; + if (!strcasecmp (atom + 1, "nfinite")) + return INFINITE; + if (!strcasecmp (atom + 1, "nfo")) + return INFO; + if (!strcasecmp (atom + 1, "p-address")) + return IP_ADDRESS; + if (!strcasecmp (atom + 1, "p6-address")) + return IP6_ADDRESS; + if (!strcasecmp (atom + 1, "nitial-interval")) + return INITIAL_INTERVAL; + if (!strcasecmp (atom + 1, "nitial-delay")) + return INITIAL_DELAY; + if (!strcasecmp (atom + 1, "nterface")) + return INTERFACE; + if (!strcasecmp (atom + 1, "dentifier")) + return IDENTIFIER; + if (!strcasecmp (atom + 1, "f")) + return IF; + if (!strcasecmp (atom + 1, "s")) + return IS; + if (!strcasecmp (atom + 1, "gnore")) + return IGNORE; + break; + case 'k': + if (!strncasecmp (atom + 1, "nown", 4)) { + if (!strcasecmp (atom + 5, "-clients")) + return KNOWN_CLIENTS; + if (!atom[5]) + return KNOWN; + break; + } + if (!strcasecmp (atom + 1, "ey")) + return KEY; + break; + case 'l': + if (!strcasecmp (atom + 1, "case")) + return LCASE; + if (!strcasecmp (atom + 1, "ease")) + return LEASE; + if (!strcasecmp(atom + 1, "ease6")) + return LEASE6; + if (!strcasecmp (atom + 1, "eased-address")) + return LEASED_ADDRESS; + if (!strcasecmp (atom + 1, "ease-time")) + return LEASE_TIME; + if (!strcasecmp(atom + 1, "easequery")) + return LEASEQUERY; + if (!strcasecmp(atom + 1, "ength")) + return LENGTH; + if (!strcasecmp (atom + 1, "imit")) + return LIMIT; + if (!strcasecmp (atom + 1, "et")) + return LET; + if (!strcasecmp (atom + 1, "oad")) + return LOAD; + if (!strcasecmp(atom + 1, "ocal")) + return LOCAL; + if (!strcasecmp (atom + 1, "og")) + return LOG; + if (!strcasecmp(atom+1, "lt")) { + return LLT; + } + if (!strcasecmp(atom+1, "l")) { + return LL; + } + break; + case 'm': + if (!strncasecmp (atom + 1, "ax", 2)) { + if (!atom [3]) + return TOKEN_MAX; + if (!strcasecmp (atom + 3, "-balance")) + return MAX_BALANCE; + if (!strncasecmp (atom + 3, "-lease-", 7)) { + if (!strcasecmp(atom + 10, "misbalance")) + return MAX_LEASE_MISBALANCE; + if (!strcasecmp(atom + 10, "ownership")) + return MAX_LEASE_OWNERSHIP; + if (!strcasecmp(atom + 10, "time")) + return MAX_LEASE_TIME; + } + if (!strcasecmp(atom + 3, "-life")) + return MAX_LIFE; + if (!strcasecmp (atom + 3, "-transmit-idle")) + return MAX_TRANSMIT_IDLE; + if (!strcasecmp (atom + 3, "-response-delay")) + return MAX_RESPONSE_DELAY; + if (!strcasecmp (atom + 3, "-unacked-updates")) + return MAX_UNACKED_UPDATES; + } + if (!strncasecmp (atom + 1, "in-", 3)) { + if (!strcasecmp (atom + 4, "balance")) + return MIN_BALANCE; + if (!strcasecmp (atom + 4, "lease-time")) + return MIN_LEASE_TIME; + if (!strcasecmp (atom + 4, "secs")) + return MIN_SECS; + break; + } + if (!strncasecmp (atom + 1, "edi", 3)) { + if (!strcasecmp (atom + 4, "a")) + return MEDIA; + if (!strcasecmp (atom + 4, "um")) + return MEDIUM; + break; + } + if (!strcasecmp (atom + 1, "atch")) + return MATCH; + if (!strcasecmp (atom + 1, "embers")) + return MEMBERS; + if (!strcasecmp (atom + 1, "y")) + return MY; + if (!strcasecmp (atom + 1, "clt")) + return MCLT; + break; + case 'n': + if (!strcasecmp (atom + 1, "ormal")) + return NORMAL; + if (!strcasecmp (atom + 1, "ameserver")) + return NAMESERVER; + if (!strcasecmp (atom + 1, "etmask")) + return NETMASK; + if (!strcasecmp (atom + 1, "ever")) + return NEVER; + if (!strcasecmp (atom + 1, "ext-server")) + return NEXT_SERVER; + if (!strcasecmp (atom + 1, "ot")) + return TOKEN_NOT; + if (!strcasecmp (atom + 1, "o")) + return TOKEN_NO; + if (!strcasecmp (atom + 1, "s-update")) + return NS_UPDATE; + if (!strcasecmp (atom + 1, "oerror")) + return NS_NOERROR; + if (!strcasecmp (atom + 1, "otauth")) + return NS_NOTAUTH; + if (!strcasecmp (atom + 1, "otimp")) + return NS_NOTIMP; + if (!strcasecmp (atom + 1, "otzone")) + return NS_NOTZONE; + if (!strcasecmp (atom + 1, "xdomain")) + return NS_NXDOMAIN; + if (!strcasecmp (atom + 1, "xrrset")) + return NS_NXRRSET; + if (!strcasecmp (atom + 1, "ull")) + return TOKEN_NULL; + if (!strcasecmp (atom + 1, "ext")) + return TOKEN_NEXT; + if (!strcasecmp (atom + 1, "ew")) + return TOKEN_NEW; + break; + case 'o': + if (!strcasecmp (atom + 1, "mapi")) + return OMAPI; + if (!strcasecmp (atom + 1, "r")) + return OR; + if (!strcasecmp (atom + 1, "n")) + return ON; + if (!strcasecmp (atom + 1, "pen")) + return TOKEN_OPEN; + if (!strcasecmp (atom + 1, "ption")) + return OPTION; + if (!strcasecmp (atom + 1, "ne-lease-per-client")) + return ONE_LEASE_PER_CLIENT; + if (!strcasecmp (atom + 1, "f")) + return OF; + if (!strcasecmp (atom + 1, "wner")) + return OWNER; + break; + case 'p': + if (!strcasecmp (atom + 1, "repend")) + return PREPEND; + if (!strcasecmp(atom + 1, "referred-life")) + return PREFERRED_LIFE; + if (!strcasecmp (atom + 1, "acket")) + return PACKET; + if (!strcasecmp (atom + 1, "ool")) + return POOL; + if (!strcasecmp (atom + 1, "refix6")) + return PREFIX6; + if (!strcasecmp (atom + 1, "seudo")) + return PSEUDO; + if (!strcasecmp (atom + 1, "eer")) + return PEER; + if (!strcasecmp (atom + 1, "rimary")) + return PRIMARY; + if (!strcasecmp (atom + 1, "rimary6")) + return PRIMARY6; + if (!strncasecmp (atom + 1, "artner", 6)) { + if (!atom [7]) + return PARTNER; + if (!strcasecmp (atom + 7, "-down")) + return PARTNER_DOWN; + } + if (!strcasecmp (atom + 1, "ort")) + return PORT; + if (!strcasecmp (atom + 1, "otential-conflict")) + return POTENTIAL_CONFLICT; + if (!strcasecmp (atom + 1, "ick-first-value") || + !strcasecmp (atom + 1, "ick")) + return PICK; + if (!strcasecmp (atom + 1, "aused")) + return PAUSED; + break; + case 'r': + if (!strcasecmp(atom + 1, "ange")) + return RANGE; + if (!strcasecmp(atom + 1, "ange6")) + return RANGE6; + if (isascii(atom[1]) && + (tolower((unsigned char)atom[1]) == 'e')) { + if (!strcasecmp(atom + 2, "bind")) + return REBIND; + if (!strcasecmp(atom + 2, "boot")) + return REBOOT; + if (!strcasecmp(atom + 2, "contact-interval")) + return RECONTACT_INTERVAL; + if (!strncasecmp(atom + 2, "cover", 5)) { + if (atom[7] == '\0') + return RECOVER; + if (!strcasecmp(atom + 7, "-done")) + return RECOVER_DONE; + if (!strcasecmp(atom + 7, "-wait")) + return RECOVER_WAIT; + break; + } + if (!strcasecmp(atom + 2, "fresh")) + return REFRESH; + if (!strcasecmp(atom + 2, "fused")) + return NS_REFUSED; + if (!strcasecmp(atom + 2, "ject")) + return REJECT; + if (!strcasecmp(atom + 2, "lease")) + return RELEASE; + if (!strcasecmp(atom + 2, "leased")) + return TOKEN_RELEASED; + if (!strcasecmp(atom + 2, "move")) + return REMOVE; + if (!strcasecmp(atom + 2, "new")) + return RENEW; + if (!strcasecmp(atom + 2, "quest")) + return REQUEST; + if (!strcasecmp(atom + 2, "quire")) + return REQUIRE; + if (isascii(atom[2]) && + (tolower((unsigned char)atom[2]) == 's')) { + if (!strcasecmp(atom + 3, "erved")) + return TOKEN_RESERVED; + if (!strcasecmp(atom + 3, "et")) + return TOKEN_RESET; + if (!strcasecmp(atom + 3, + "olution-interrupted")) + return RESOLUTION_INTERRUPTED; + break; + } + if (!strcasecmp(atom + 2, "try")) + return RETRY; + if (!strcasecmp(atom + 2, "turn")) + return RETURN; + if (!strcasecmp(atom + 2, "verse")) + return REVERSE; + if (!strcasecmp(atom + 2, "wind")) + return REWIND; + break; + } + break; + case 's': + if (!strcasecmp(atom + 1, "cript")) + return SCRIPT; + if (isascii(atom[1]) && + tolower((unsigned char)atom[1]) == 'e') { + if (!strcasecmp(atom + 2, "arch")) + return SEARCH; + if (isascii(atom[2]) && + tolower((unsigned char)atom[2]) == 'c') { + if (!strncasecmp(atom + 3, "ond", 3)) { + if (!strcasecmp(atom + 6, "ary")) + return SECONDARY; + if (!strcasecmp(atom + 6, "ary6")) + return SECONDARY6; + if (!strcasecmp(atom + 6, "s")) + return SECONDS; + break; + } + if (!strcasecmp(atom + 3, "ret")) + return SECRET; + break; + } + if (!strncasecmp(atom + 2, "lect", 4)) { + if (atom[6] == '\0') + return SELECT; + if (!strcasecmp(atom + 6, "-timeout")) + return SELECT_TIMEOUT; + break; + } + if (!strcasecmp(atom + 2, "nd")) + return SEND; + if (!strncasecmp(atom + 2, "rv", 2)) { + if (!strncasecmp(atom + 4, "er", 2)) { + if (atom[6] == '\0') + return TOKEN_SERVER; + if (atom[6] == '-') { + if (!strcasecmp(atom + 7, + "duid")) + return SERVER_DUID; + if (!strcasecmp(atom + 7, + "name")) + return SERVER_NAME; + if (!strcasecmp(atom + 7, + "identifier")) + return SERVER_IDENTIFIER; + break; + } + break; + } + if (!strcasecmp(atom + 4, "fail")) + return NS_SERVFAIL; + break; + } + if (!strcasecmp(atom + 2, "t")) + return TOKEN_SET; + break; + } + if (isascii(atom[1]) && + tolower((unsigned char)atom[1]) == 'h') { + if (!strcasecmp(atom + 2, "ared-network")) + return SHARED_NETWORK; + if (!strcasecmp(atom + 2, "utdown")) + return SHUTDOWN; + break; + } + if (isascii(atom[1]) && + tolower((unsigned char)atom[1]) == 'i') { + if (!strcasecmp(atom + 2, "addr")) + return SIADDR; + if (!strcasecmp(atom + 2, "gned")) + return SIGNED; + if (!strcasecmp(atom + 2, "ze")) + return SIZE; + break; + } + if (isascii(atom[1]) && + tolower((unsigned char)atom[1]) == 'p') { + if (isascii(atom[2]) && + tolower((unsigned char)atom[2]) == 'a') { + if (!strcasecmp(atom + 3, "ce")) + return SPACE; + if (!strcasecmp(atom + 3, "wn")) + return SPAWN; + break; + } + if (!strcasecmp(atom + 2, "lit")) + return SPLIT; + break; + } + if (isascii(atom[1]) && + tolower((unsigned char)atom[1]) == 't') { + if (isascii(atom[2]) && + tolower((unsigned char)atom[2]) == 'a') { + if(!strncasecmp(atom + 3, "rt", 2)) { + if (!strcasecmp(atom + 5, "s")) + return STARTS; + if (!strcasecmp(atom + 5, "up")) + return STARTUP; + break; + } + if (isascii(atom[3]) && + tolower((unsigned char)atom[3]) == 't') { + if (!strcasecmp(atom + 4, "e")) + return STATE; + if (!strcasecmp(atom + 4, "ic")) + return STATIC; + break; + } + } + if (!strcasecmp(atom + 2, "ring")) + return STRING_TOKEN; + break; + } + if (!strncasecmp(atom + 1, "ub", 2)) { + if (!strcasecmp(atom + 3, "class")) + return SUBCLASS; + if (!strcasecmp(atom + 3, "net")) + return SUBNET; + if (!strcasecmp(atom + 3, "net6")) + return SUBNET6; + if (!strcasecmp(atom + 3, "string")) + return SUBSTRING; + break; + } + if (isascii(atom[1]) && + tolower((unsigned char)atom[1]) == 'u') { + if (!strcasecmp(atom + 2, "ffix")) + return SUFFIX; + if (!strcasecmp(atom + 2, "persede")) + return SUPERSEDE; + } + if (!strcasecmp(atom + 1, "witch")) + return SWITCH; + break; + case 't': + if (!strcasecmp (atom + 1, "imestamp")) + return TIMESTAMP; + if (!strcasecmp (atom + 1, "imeout")) + return TIMEOUT; + if (!strcasecmp (atom + 1, "oken-ring")) + return TOKEN_RING; + if (!strcasecmp (atom + 1, "ext")) + return TEXT; + if (!strcasecmp (atom + 1, "stp")) + return TSTP; + if (!strcasecmp (atom + 1, "sfp")) + return TSFP; + if (!strcasecmp (atom + 1, "ransmission")) + return TRANSMISSION; + if (!strcasecmp(atom + 1, "emporary")) + return TEMPORARY; + break; + case 'u': + if (!strcasecmp (atom + 1, "case")) + return UCASE; + if (!strcasecmp (atom + 1, "nset")) + return UNSET; + if (!strcasecmp (atom + 1, "nsigned")) + return UNSIGNED; + if (!strcasecmp (atom + 1, "id")) + return UID; + if (!strncasecmp (atom + 1, "se", 2)) { + if (!strcasecmp (atom + 3, "r-class")) + return USER_CLASS; + if (!strcasecmp (atom + 3, "-host-decl-names")) + return USE_HOST_DECL_NAMES; + if (!strcasecmp (atom + 3, + "-lease-addr-for-default-route")) + return USE_LEASE_ADDR_FOR_DEFAULT_ROUTE; + break; + } + if (!strncasecmp (atom + 1, "nknown", 6)) { + if (!strcasecmp (atom + 7, "-clients")) + return UNKNOWN_CLIENTS; + if (!strcasecmp (atom + 7, "-state")) + return UNKNOWN_STATE; + if (!atom [7]) + return UNKNOWN; + break; + } + if (!strcasecmp (atom + 1, "nauthenticated")) + return UNAUTHENTICATED; + if (!strcasecmp (atom + 1, "pdated-dns-rr")) + return UPDATED_DNS_RR; + if (!strcasecmp (atom + 1, "pdate")) + return UPDATE; + break; + case 'v': + if (!strcasecmp (atom + 1, "endor-class")) + return VENDOR_CLASS; + if (!strcasecmp (atom + 1, "endor")) + return VENDOR; + break; + case 'w': + if (!strcasecmp (atom + 1, "ith")) + return WITH; + if (!strcasecmp(atom + 1, "idth")) + return WIDTH; + break; + case 'y': + if (!strcasecmp (atom + 1, "iaddr")) + return YIADDR; + if (!strcasecmp (atom + 1, "xdomain")) + return NS_YXDOMAIN; + if (!strcasecmp (atom + 1, "xrrset")) + return NS_YXRRSET; + break; + case 'z': + if (!strcasecmp (atom + 1, "erolen")) + return ZEROLEN; + if (!strcasecmp (atom + 1, "one")) + return ZONE; + break; + } + return dfv; +} + diff --git a/common/ctrace.c b/common/ctrace.c new file mode 100644 index 0000000..578ea5e --- /dev/null +++ b/common/ctrace.c @@ -0,0 +1,291 @@ +/* trace.c + + Subroutines that support dhcp tracing... */ + +/* + * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2001-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" + +#if defined (TRACING) +void trace_interface_register (trace_type_t *ttype, struct interface_info *ip) +{ + trace_interface_packet_t tipkt; + + if (trace_record ()) { + memset (&tipkt, 0, sizeof tipkt); + memcpy (&tipkt.hw_address, + &ip -> hw_address, sizeof ip -> hw_address); + if (ip->address_count) + memcpy(&tipkt.primary_address, + ip->addresses, sizeof(*ip->addresses)); + memcpy (tipkt.name, ip -> name, sizeof ip -> name); + tipkt.index = htonl (ip -> index); + + trace_write_packet (ttype, sizeof tipkt, (char *)&tipkt, MDL); + } +} + +void trace_interface_input (trace_type_t *ttype, unsigned len, char *buf) +{ + trace_interface_packet_t *tipkt; + struct interface_info *ip; + struct sockaddr_in *sin; + struct iaddr addr; + isc_result_t status; + + if (len != sizeof *tipkt) { + log_error ("trace interface packet size mismatch: %ld != %d", + (long)(sizeof *tipkt), len); + return; + } + tipkt = (trace_interface_packet_t *)buf; + + ip = (struct interface_info *)0; + status = interface_allocate (&ip, MDL); + if (status != ISC_R_SUCCESS) { + foo: + log_error ("trace_interface_input: %s.", + isc_result_totext (status)); + return; + } + ip -> ifp = dmalloc (sizeof *(ip -> ifp), MDL); + if (!ip -> ifp) { + interface_dereference (&ip, MDL); + status = ISC_R_NOMEMORY; + goto foo; + } + + memcpy (&ip -> hw_address, &tipkt -> hw_address, + sizeof ip -> hw_address); + /* XXX: Without the full addresses state it's not quite a full + * trace. + */ + ip->address_count = ip->address_max = 1; + ip->addresses = dmalloc(sizeof(*ip->addresses), MDL); + memcpy(ip->addresses, &tipkt->primary_address, sizeof(*ip->addresses)); + memcpy (ip -> name, tipkt -> name, sizeof ip -> name); + ip -> index = ntohl (tipkt -> index); + + interface_snorf (ip, 0); + if (dhcp_interface_discovery_hook) + (*dhcp_interface_discovery_hook) (ip); + + /* Fake up an ifp. */ + memcpy (ip -> ifp -> ifr_name, ip -> name, sizeof ip -> name); +#ifdef HAVE_SA_LEN + ip -> ifp -> ifr_addr.sa_len = sizeof (struct sockaddr_in); +#endif + sin = (struct sockaddr_in *)&ip -> ifp -> ifr_addr; + sin->sin_addr = ip->addresses[0]; + + addr.len = 4; + memcpy (addr.iabuf, &sin -> sin_addr.s_addr, addr.len); + if (dhcp_interface_setup_hook) + (*dhcp_interface_setup_hook) (ip, &addr); + interface_stash (ip); + + if (!quiet_interface_discovery) { + log_info ("Listening on Trace/%s/%s%s%s", + ip -> name, + print_hw_addr (ip -> hw_address.hbuf [0], + ip -> hw_address.hlen - 1, + &ip -> hw_address.hbuf [1]), + (ip -> shared_network ? "/" : ""), + (ip -> shared_network ? + ip -> shared_network -> name : "")); + if (strcmp (ip -> name, "fallback")) { + log_info ("Sending on Trace/%s/%s%s%s", + ip -> name, + print_hw_addr (ip -> hw_address.hbuf [0], + ip -> hw_address.hlen - 1, + &ip -> hw_address.hbuf [1]), + (ip -> shared_network ? "/" : ""), + (ip -> shared_network ? + ip -> shared_network -> name : "")); + } + } + interface_dereference (&ip, MDL); +} + +void trace_interface_stop (trace_type_t *ttype) { + /* XXX */ +} + +void trace_inpacket_stash (struct interface_info *interface, + struct dhcp_packet *packet, + unsigned len, + unsigned int from_port, + struct iaddr from, + struct hardware *hfrom) +{ + trace_inpacket_t tip; + trace_iov_t iov [2]; + + if (!trace_record ()) + return; + tip.from_port = from_port; + tip.from = from; + tip.from.len = htonl (tip.from.len); + if (hfrom) { + tip.hfrom = *hfrom; + tip.havehfrom = 1; + } else { + memset (&tip.hfrom, 0, sizeof tip.hfrom); + tip.havehfrom = 0; + } + tip.index = htonl (interface -> index); + + iov [0].buf = (char *)&tip; + iov [0].len = sizeof tip; + iov [1].buf = (char *)packet; + iov [1].len = len; + trace_write_packet_iov (inpacket_trace, 2, iov, MDL); +} + +void trace_inpacket_input (trace_type_t *ttype, unsigned len, char *buf) +{ + trace_inpacket_t *tip; + int index; + + if (len < sizeof *tip) { + log_error ("trace_input_packet: too short - %d", len); + return; + } + tip = (trace_inpacket_t *)buf; + index = ntohl (tip -> index); + tip -> from.len = ntohl (tip -> from.len); + + if (index > interface_count || + index < 0 || + !interface_vector [index]) { + log_error ("trace_input_packet: unknown interface index %d", + index); + return; + } + + if (!bootp_packet_handler) { + log_error ("trace_input_packet: no bootp packet handler."); + return; + } + + (*bootp_packet_handler) (interface_vector [index], + (struct dhcp_packet *)(tip + 1), + len - sizeof *tip, + tip -> from_port, + tip -> from, + (tip -> havehfrom ? + &tip -> hfrom + : (struct hardware *)0)); +} + +void trace_inpacket_stop (trace_type_t *ttype) { } + +ssize_t trace_packet_send (struct interface_info *interface, + struct packet *packet, + struct dhcp_packet *raw, + size_t len, + struct in_addr from, + struct sockaddr_in *to, + struct hardware *hto) +{ + trace_outpacket_t tip; + trace_iov_t iov [2]; + + if (trace_record ()) { + if (hto) { + tip.hto = *hto; + tip.havehto = 1; + } else { + memset (&tip.hto, 0, sizeof tip.hto); + tip.havehto = 0; + } + tip.from.len = 4; + memcpy (tip.from.iabuf, &from, 4); + tip.to.len = 4; + memcpy (tip.to.iabuf, &to -> sin_addr, 4); + tip.to_port = to -> sin_port; + tip.index = htonl (interface -> index); + + iov [0].buf = (char *)&tip; + iov [0].len = sizeof tip; + iov [1].buf = (char *)raw; + iov [1].len = len; + trace_write_packet_iov (outpacket_trace, 2, iov, MDL); + } + if (!trace_playback ()) { + return send_packet (interface, packet, raw, len, + from, to, hto); + } + return len; +} + +void trace_outpacket_input (trace_type_t *ttype, unsigned len, char *buf) +{ + trace_outpacket_t *tip; + int index; + + if (len < sizeof *tip) { + log_error ("trace_input_packet: too short - %d", len); + return; + } + tip = (trace_outpacket_t *)buf; + index = ntohl (tip -> index); + + if (index > interface_count || + index < 0 || + !interface_vector [index]) { + log_error ("trace_input_packet: unknown interface index %d", + index); + return; + } + + /* XXX would be nice to somehow take notice of these. */ +} + +void trace_outpacket_stop (trace_type_t *ttype) { } + +void trace_seed_stash (trace_type_t *ttype, unsigned seed) +{ + u_int32_t outseed; + if (!trace_record ()) + return; + outseed = htonl (seed); + trace_write_packet (ttype, sizeof outseed, (char *)&outseed, MDL); + return; +} + +void trace_seed_input (trace_type_t *ttype, unsigned length, char *buf) +{ + u_int32_t *seed; + + if (length != sizeof seed) { + log_error ("trace_seed_input: wrong size (%d)", length); + } + seed = (u_int32_t *)buf; + srandom (ntohl (*seed)); +} + +void trace_seed_stop (trace_type_t *ttype) { } +#endif /* TRACING */ diff --git a/common/dhcp-eval.5 b/common/dhcp-eval.5 new file mode 100644 index 0000000..8e95530 --- /dev/null +++ b/common/dhcp-eval.5 @@ -0,0 +1,569 @@ +.\" $Id: dhcp-eval.5,v 1.29.24.4 2012/05/17 15:51:20 sar Exp $ +.\" +.\" Copyright (c) 2009-2012,2014 by Internet Systems Consortium, Inc. ("ISC") +.\" Copyright (c) 2004,2007 by Internet Systems Consortium, Inc. ("ISC") +.\" Copyright (c) 1996-2003 by Internet Software Consortium +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +.\" OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.\" Internet Systems Consortium, Inc. +.\" 950 Charter Street +.\" Redwood City, CA 94063 +.\" <info@isc.org> +.\" https://www.isc.org/ +.\" +.\" Support and other services are available for ISC products - see +.\" https://www.isc.org for more information or to learn more about ISC. +.\" +.TH dhcp-eval 5 +.SH NAME +dhcp-eval - ISC DHCP conditional evaluation +.SH DESCRIPTION +The Internet Systems Consortium DHCP client and server both provide +the ability to perform conditional behavior depending on the contents +of packets they receive. The syntax for specifying this conditional +behaviour is documented here. +.SH REFERENCE: CONDITIONAL BEHAVIOUR +Conditional behaviour may be specified using the if statement and the else +or elsif statements or the switch and case statements. +A conditional statement can appear anywhere +that a regular statement (e.g., an option statement) can appear, and +can enclose one or more such statements. +.PP +.B CONDITIONAL BEHAVIOUR: IF +.PP +A typical conditional if statement in a server might be: +.PP +.nf +if option dhcp-user-class = "accounting" { + max-lease-time 17600; + option domain-name "accounting.example.org"; + option domain-name-servers ns1.accounting.example.org, + ns2.accounting.example.org; +} elsif option dhcp-user-class = "sales" { + max-lease-time 17600; + option domain-name "sales.example.org"; + option domain-name-servers ns1.sales.example.org, + ns2.sales.example.org; +} elsif option dhcp-user-class = "engineering" { + max-lease-time 17600; + option domain-name "engineering.example.org"; + option domain-name-servers ns1.engineering.example.org, + ns2.engineering.example.org; +} else { + max-lease-time 600; + option domain-name "misc.example.org"; + option domain-name-servers ns1.misc.example.org, + ns2.misc.example.org; +} +.fi +.PP +On the client side, an example of conditional evaluation might be: +.PP +.nf +# example.org filters DNS at its firewall, so we have to use their DNS +# servers when we connect to their network. If we are not at +# example.org, prefer our own DNS server. +if not option domain-name = "example.org" { + prepend domain-name-servers 127.0.0.1; +} +.fi +.PP +The +.B if +statement and the +.B elsif +continuation statement both take boolean expressions as their +arguments. That is, they take expressions that, when evaluated, +produce a boolean result. If the expression evaluates to true, then +the statements enclosed in braces following the +.B if +statement are executed, and all subsequent +.B elsif +and +.B else +clauses are skipped. Otherwise, each subsequent +.B elsif +clause's expression is checked, until an elsif clause is encountered +whose test evaluates to true. If such a clause is found, the +statements in braces following it are executed, and then any +subsequent +.B elsif +and +.B else +clauses are skipped. If all the +.B if +and +.B elsif +clauses are checked but none +of their expressions evaluate true, then if there is an +.B else +clause, the statements enclosed in braces following the +.B else +are evaluated. Boolean expressions that evaluate to null are +treated as false in conditionals. +.PP +.B CONDITIONAL BEHAVIOUR: SWITCH +.PP +The above example can be rewritten using a switch construct as well. +.PP +.nf +switch (option dhcp-user-class) { + case "accounting": + max-lease-time 17600; + option domain-name "accounting.example.org"; + option domain-name-servers ns1.accounting.example.org, + ns2.accounting.example.org; + case "sales": + max-lease-time 17600; + option domain-name "sales.example.org"; + option domain-name-servers ns1.sales.example.org, + ns2.sales.example.org; + break; + case "engineering": + max-lease-time 17600; + option domain-name "engineering.example.org"; + option domain-name-servers ns1.engineering.example.org, + ns2.engineering.example.org; + break; + default: + max-lease-time 600; + option domain-name "misc.example.org"; + option domain-name-servers ns1.misc.example.org, + ns2.misc.example.org; + break; +} +.fi +.PP +The +.B switch +statement and the +.B case +statements can both be data expressions or numeric expressions. Within +a switch statement they all must be the same type. The server +evaluates the expression from the switch statement and then it evaluates +the expressions from the case statements until it finds a match. +.PP +If it finds a match it starts executing statements from that case +until the next break statement. If it doesn't find a match it +starts from the default statement and again proceeds to the next +break statement. If there is no match and no default it does nothing. +.PP +.SH BOOLEAN EXPRESSIONS +The following is the current list of boolean expressions that are +supported by the DHCP distribution. +.PP +.I data-expression-1 \fB=\fR \fIdata-expression-2\fR +.RS 0.25i +.PP +The \fB=\fR operator compares the values of two data expressions, +returning true if they are the same, false if they are not. If +either the left-hand side or the right-hand side are null, the +result is also null. +.RE +.PP +.I data-expression-1 \fB~=\fR \fIdata-expression-2\fR +.I data-expression-1 \fB~~\fR \fIdata-expression-2\fR +.RS 0.25i +.PP +The \fB~=\fR and \fB~~\fR operators (not available on all systems) perform +extended regex(7) matching of the values of two data expressions, returning +true if \fIdata-expression-1\fR matches against the regular expression +evaluated by \fIdata-expression-2\fR, or false if it does not match or +encounters some error. If either the left-hand side or the right-hand side +are null or empty strings, the result is also false. The \fB~~\fR operator +differs from the \fB~=\fR operator in that it is case-insensitive. +.RE +.PP +.I boolean-expression-1 \fBand\fR \fIboolean-expression-2\fR +.PP +.RS 0.25i +The \fBand\fR operator evaluates to true if the boolean expression on +the left-hand side and the boolean expression on the right-hand side +both evaluate to true. Otherwise, it evaluates to false. If either +the expression on the left-hand side or the expression on the +right-hand side are null, the result is null. +.RE +.PP +.I boolean-expression-1 \fBor\fR \fIboolean-expression-2\fR +.PP +.RS 0.25i +The \fBor\fR operator evaluates to true if either the boolean +expression on the left-hand side or the boolean expression on the +right-hand side evaluate to true. Otherwise, it evaluates to false. +If either the expression on the left-hand side or the expression on +the right-hand side are null, the result is null. +.RE +.PP +.B not \fIboolean-expression +.PP +.RS 0.25i +The \fBnot\fR operator evaluates to true if \fIboolean-expression\fR +evaluates to false, and returns false if \fIboolean-expression\fR evaluates +to true. If \fIboolean-expression\fR evaluates to null, the result +is also null. +.RE +.PP +.B exists \fIoption-name\fR +.PP +.RS 0.25i +The \fBexists\fR expression returns true if the specified option +exists in the incoming DHCP packet being processed. +.RE +.B known +.PP +.RS 0.25i +The \fBknown\fR expression returns true if the client whose request is +currently being processed is known - that is, if there's a host +declaration for it. +.RE +.B static +.PP +.RS 0.25i +The \fBstatic\fR expression returns true if the lease assigned to the +client whose request is currently being processed is derived from a static +address assignment. +.RE +.SH DATA EXPRESSIONS +Several of the boolean expressions above depend on the results of +evaluating data expressions. A list of these expressions is provided +here. +.PP +.B substring (\fIdata-expr\fB, \fIoffset\fB, \fIlength\fB)\fR +.PP +.RS 0.25i +The \fBsubstring\fR operator evaluates the data expression and returns +the substring of the result of that evaluation that starts +\fIoffset\fR bytes from the beginning, continuing for \fIlength\fR +bytes. \fIOffset\fR and \fIlength\fR are both numeric expressions. +If \fIdata-expr\fR, \fIoffset\fR or \fIlength\fR evaluate to null, +then the result is also null. If \fIoffset\fR is greater than or +equal to the length of the evaluated data, then a zero-length data +string is returned. If \fIlength\fI is greater then the remaining +length of the evaluated data after \fIoffset\fR, then a data string +containing all data from \fIoffset\fR to the end of the evaluated data +is returned. +.RE +.PP +.B suffix (\fIdata-expr\fB, \fIlength\fB)\fR +.PP +.RS 0.25i +The \fBsuffix\fR operator evaluates \fIdata-expr\fR and returns the +last \fIlength\fR bytes of the result of that evaluation. \fILength\fR +is a numeric expression. If \fIdata-expr\fR or \fIlength\fR evaluate +to null, then the result is also null. If \fIsuffix\fR evaluates to a +number greater than the length of the evaluated data, then the +evaluated data is returned. +.RE +.PP +.B lcase (\fIdata-expr\fB)\fR +.PP +.RS 0.25i +The \fBlcase\fR function returns the result of evaluating +\fIdata-expr\fR converted to lower case. If \fIdata-expr\fR evaluates +to null, then the result is also null. +.RE +.PP +.B ucase (\fIdata-expr\fB)\fR +.PP +.RS 0.25i +The \fBucase\fR function returns the result of evaluating +\fIdata-expr\fR converted to upper case. If \fIdata-expr\fR evaluates +to null, then the result is also null. +.RE +.PP +.B option \fIoption-name\fR +.PP +.RS 0.25i +The \fBoption\fR operator returns the contents of the specified option in +the packet to which the server is responding. +.RE +.PP +.B config-option \fIoption-name\fR +.PP +.RS 0.25i +The \fBconfig-option\fR operator returns the value for the specified option +that the DHCP client or server has been configured to send. +.RE +.PP +.B gethostname() +.PP +.RS 0.25i +The \fBgethostname()\fR function returns a data string whose contents are a +character string, the results of calling gethostname() on the local +system with a size limit of 255 bytes (not including NULL terminator). This +can be used for example to configure dhclient to send the local hostname +without knowing the local hostname at the time dhclient.conf is written. +.RE +.PP +.B hardware +.PP +.RS 0.25i +The \fBhardware\fR operator returns a data string whose first element +is the type of network interface indicated in packet being considered, +and whose subsequent elements are client's link-layer address. If +there is no packet, or if the RFC2131 \fIhlen\fR field is invalid, +then the result is null. Hardware types include ethernet (1), +token-ring (6), and fddi (8). Hardware types are specified by the +IETF, and details on how the type numbers are defined can be found in +RFC2131 (in the ISC DHCP distribution, this is included in the doc/ +subdirectory). +.RE +.PP +.B packet (\fIoffset\fB, \fIlength\fB)\fR +.PP +.RS 0.25i +The \fBpacket\fR operator returns the specified portion of the packet +being considered, or null in contexts where no packet is being +considered. \fIOffset\fR and \fIlength\fR are applied to the +contents packet as in the \fBsubstring\fR operator. +.RE +.PP +.I string +.PP +.RS 0.25i +A string, enclosed in quotes, may be specified as a data expression, +and returns the text between the quotes, encoded in ASCII. The +backslash ('\\') character is treated specially, as in C programming: '\\t' +means TAB, '\\r' means carriage return, '\\n' means newline, and '\\b' means +bell. Any octal value can be specified with '\\nnn', where nnn is any +positive octal number less than 0400. Any hexadecimal value can be +specified with '\\xnn', where nn is any positive hexadecimal number less +than or equal to 0xff. +.RE +.PP +.I colon-separated hexadecimal list +.PP +.RS 0.25i +A list of hexadecimal octet values, separated by colons, may be +specified as a data expression. +.RE +.PP +.B concat (\fIdata-expr1\fB, ..., \fIdata-exprN\fB)\fR +.RS 0.25i +The expressions are evaluated, and the results of each evaluation are +concatenated in the sequence that the subexpressions are listed. If +any subexpression evaluates to null, the result of the concatenation +is null. +.RE +.PP +.B reverse (\fInumeric-expr1\fB, \fIdata-expr2\fB)\fR +.RS 0.25i +The two expressions are evaluated, and then the result of evaluating +the data expression is reversed in place, using hunks of the size +specified in the numeric expression. For example, if the numeric +expression evaluates to four, and the data expression evaluates to +twelve bytes of data, then the reverse expression will evaluate to +twelve bytes of data, consisting of the last four bytes of the +input data, followed by the middle four bytes, followed by the first +four bytes. +.RE +.PP +.B leased-address +.RS 0.25i +In any context where the client whose request is being processed has +been assigned an IP address, this data expression returns that IP +address. In any context where the client whose request is being +processed has not been assigned an ip address, if this data expression +is found in executable statements executed on that client's behalf, +a log message indicating "there is no lease associated with this client" +is syslogged to the debug level (this is considered dhcpd.conf debugging +information). +.RE +.PP +.B binary-to-ascii (\fInumeric-expr1\fB, \fInumeric-expr2\fB, +.B \fIdata-expr1\fB,\fR \fIdata-expr2\fB)\fR +.RS 0.25i +Converts the result of evaluating data-expr2 into a text string +containing one number for each element of the result of evaluating +data-expr2. Each number is separated from the other by the result of +evaluating data-expr1. The result of evaluating numeric-expr1 +specifies the base (2 through 16) into which the numbers should be +converted. The result of evaluating numeric-expr2 specifies the +width in bits of each number, which may be either 8, 16 or 32. +.PP +As an example of the preceding three types of expressions, to produce +the name of a PTR record for the IP address being assigned to a +client, one could write the following expression: +.RE +.PP +.nf + concat (binary-to-ascii (10, 8, ".", + reverse (1, leased-address)), + ".in-addr.arpa."); + +.fi +.RE +.PP +.B encode-int (\fInumeric-expr\fB, \fIwidth\fB)\fR +.RS 0.25i +Numeric-expr is evaluated and encoded as a data string of the +specified width, in network byte order (most significant byte first). +If the numeric expression evaluates to the null value, the result is +also null. +.RE +.PP +.B pick-first-value (\fIdata-expr1\fR [ ... \fIexpr\fRn ] \fB)\fR +.RS 0.25i +The pick-first-value function takes any number of data expressions as +its arguments. Each expression is evaluated, starting with the first +in the list, until an expression is found that does not evaluate to a +null value. That expression is returned, and none of the subsequent +expressions are evaluated. If all expressions evaluate to a null +value, the null value is returned. +.RE +.PP +.B host-decl-name +.RS 0.25i +The host-decl-name function returns the name of the host declaration +that matched the client whose request is currently being processed, if +any. If no host declaration matched, the result is the null value. +.RE +.SH NUMERIC EXPRESSIONS +Numeric expressions are expressions that evaluate to an integer. In +general, the maximum size of such an integer should not be assumed to +be representable in fewer than 32 bits, but the precision of such +integers may be more than 32 bits. +.PP +In addition to the following operators several standard math functions +are available. They are: +.nf +operation symbol +add \fB+\fR +subtract \fB-\fR +divide \fB/\fR +multiply \fB*\fR +modulus \fB%\fR +bitwise and \fB&\fR +bitwise or \fB|\fR +bitwise xor \fB^\fR +.fi +.PP +.B extract-int (\fIdata-expr\fB, \fIwidth\fB)\fR +.PP +.RS 0.25i +The \fBextract-int\fR operator extracts an integer value in network +byte order from the result of evaluating the specified data +expression. Width is the width in bits of the integer to extract. +Currently, the only supported widths are 8, 16 and 32. If the +evaluation of the data expression doesn't provide sufficient bits to +extract an integer of the specified size, the null value is returned. +.RE +.PP +.B lease-time +.PP +.RS 0.25i +The duration of the current lease - that is, the difference between +the current time and the time that the lease expires. +.RE +.PP +.I number +.PP +.RS 0.25i +Any number between zero and the maximum representable size may be +specified as a numeric expression. +.RE +.PP +.B client-state +.PP +.RS 0.25i +The current state of the client instance being processed. This is +only useful in DHCP client configuration files. Possible values are: +.TP 2 +.I \(bu +Booting - DHCP client is in the INIT state, and does not yet have an +IP address. The next message transmitted will be a DHCPDISCOVER, +which will be broadcast. +.TP +.I \(bu +Reboot - DHCP client is in the INIT-REBOOT state. It has an IP +address, but is not yet using it. The next message to be transmitted +will be a DHCPREQUEST, which will be broadcast. If no response is +heard, the client will bind to its address and move to the BOUND state. +.TP +.I \(bu +Select - DHCP client is in the SELECTING state - it has received at +least one DHCPOFFER message, but is waiting to see if it may receive +other DHCPOFFER messages from other servers. No messages are sent in +the SELECTING state. +.TP +.I \(bu +Request - DHCP client is in the REQUESTING state - it has received at +least one DHCPOFFER message, and has chosen which one it will +request. The next message to be sent will be a DHCPREQUEST message, +which will be broadcast. +.TP +.I \(bu +Bound - DHCP client is in the BOUND state - it has an IP address. No +messages are transmitted in this state. +.TP +.I \(bu +Renew - DHCP client is in the RENEWING state - it has an IP address, +and is trying to contact the server to renew it. The next message to +be sent will be a DHCPREQUEST message, which will be unicast directly +to the server. +.TP +.I \(bu +Rebind - DHCP client is in the REBINDING state - it has an IP address, +and is trying to contact any server to renew it. The next message to +be sent will be a DHCPREQUEST, which will be broadcast. +.RE +.SH REFERENCE: ACTION EXPRESSIONS +.PP +.B log (\fIpriority\fB, \fIdata-expr\fB)\fR +.RS 0.25i +.PP +Logging statements may be used to send information to the standard logging +channels. A logging statement includes an optional priority (\fBfatal\fR, +\fBerror\fR, \fBinfo\fR, or \fBdebug\fR), and a data expression. +.PP +Logging statements take only a single data expression argument, so if you +want to output multiple data values, you will need to use the \fBconcat\fR +operator to concatenate them. +.RE +.PP +.B execute (\fIcommand-path\fB [, \fIdata-expr1\fB, ... \fIdata-exprN\fB]);\fR +.RS 0.25i +.PP +The \fBexecute\fR statement runs an external command. The first argument +is a string literal containing the name or path of the command to run. +The other arguments, if present, are either string literals or data- +expressions which evaluate to text strings, to be passed as command-line +arguments to the command. +.PP +\fBexecute\fR is synchronous; the program will block until the external +command being run has finished. Please note that lengthy program +execution (for example, in an "on commit" in dhcpd.conf) may result in +bad performance and timeouts. Only external applications with very short +execution times are suitable for use. +.PP +Passing user-supplied data to an external application might be dangerous. +Make sure the external application checks input buffers for validity. +Non-printable ASCII characters will be converted into dhcpd.conf language +octal escapes ("\\nnn"), make sure your external command handles them as +such. +.PP +It is possible to use the execute statement in any context, not only +on events. If you put it in a regular scope in the configuration file +you will execute that command every time a scope is evaluated. +.RE +.SH REFERENCE: DYNAMIC DNS UPDATES +.PP +See the dhcpd.conf and dhclient.conf man pages for more information +about DDNS. +.SH SEE ALSO +dhcpd.conf(5), dhcpd.leases(5), dhclient.conf(5), dhcp-options(5), dhcpd(8), +dhclient(8), RFC2132, RFC2131. +.SH AUTHOR +Information about Internet Systems Consortium can be found at +.B https://www.isc.org. diff --git a/common/dhcp-options.5 b/common/dhcp-options.5 new file mode 100644 index 0000000..1e418ca --- /dev/null +++ b/common/dhcp-options.5 @@ -0,0 +1,2157 @@ +.\" $Id: dhcp-options.5,v 1.45.18.6 2011/05/20 14:33:26 tomasz Exp $ +.\" +.\" Copyright (c) 2012-2014 by Internet Systems Consortium, Inc. ("ISC") +.\" Copyright (c) 2004-2010 by Internet Systems Consortium, Inc. ("ISC") +.\" Copyright (c) 1996-2003 by Internet Software Consortium +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +.\" OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.\" Internet Systems Consortium, Inc. +.\" 950 Charter Street +.\" Redwood City, CA 94063 +.\" <info@isc.org> +.\" https://www.isc.org/ +.\" +.\" Support and other services are available for ISC products - see +.\" https://www.isc.org for more information or to learn more about ISC. +.\" +.TH dhcp-options 5 +.SH NAME +dhcp-options - Dynamic Host Configuration Protocol options +.SH DESCRIPTION +The Dynamic Host Configuration protocol allows the client to receive +.B options +from the DHCP server describing the network configuration and various +services that are available on the network. When configuring +.B dhcpd(8) +or +.B dhclient(8) , +options must often be declared. The syntax for declaring options, +and the names and formats of the options that can be declared, are +documented here. +.SH REFERENCE: OPTION STATEMENTS +.PP +DHCP \fIoption\fR statements always start with the \fIoption\fR +keyword, followed by an option name, followed by option data. The +option names and data formats are described below. It is not +necessary to exhaustively specify all DHCP options - only those +options which are needed by clients must be specified. +.PP +Option data comes in a variety of formats, as defined below: +.PP +The +.B ip-address +data type can be entered either as an explicit IP +address (e.g., 239.254.197.10) or as a domain name (e.g., +haagen.isc.org). When entering a domain name, be sure that that +domain name resolves to a single IP address. +.PP +The +.B ip6-address +data specifies an IPv6 address, like ::1 or 3ffe:bbbb:aaaa:aaaa::1. +.PP +The +.B int32 +data type specifies a signed 32-bit integer. The +.B uint32 +data type specifies an unsigned 32-bit integer. The +.B int16 +and +.B uint16 +data types specify signed and unsigned 16-bit integers. The +.B int8 +and +.B uint8 +data types specify signed and unsigned 8-bit integers. +Unsigned 8-bit integers are also sometimes referred to as octets. +.PP +The +.B text +data type specifies an NVT ASCII string, which must be +enclosed in double quotes - for example, to specify a root-path +option, the syntax would be +.nf +.sp 1 +option root-path "10.0.1.4:/var/tmp/rootfs"; +.fi +.PP +The +.B domain-name +data type specifies a domain name, which must not be +enclosed in double quotes. This data type is not used for any +existing DHCP options. The domain name is stored just as if it were +a text option. +.PP +The +.B domain-list +data type specifies a list of domain names, enclosed in double quotes and +separated by commas ("example.com", "foo.example.com"). +.PP +The +.B flag +data type specifies a boolean value. Booleans can be either true or +false (or on or off, if that makes more sense to you). +.PP +The +.B string +data type specifies either an NVT ASCII string +enclosed in double quotes, or a series of octets specified in +hexadecimal, separated by colons. For example: +.nf +.sp 1 + option dhcp-client-identifier "CLIENT-FOO"; +or + option dhcp-client-identifier 43:4c:49:45:54:2d:46:4f:4f; +.fi +.SH SETTING OPTION VALUES USING EXPRESSIONS +Sometimes it's helpful to be able to set the value of a DHCP option +based on some value that the client has sent. To do this, you can +use expression evaluation. The +.B dhcp-eval(5) +manual page describes how to write expressions. To assign the result +of an evaluation to an option, define the option as follows: +.nf +.sp 1 + \fBoption \fImy-option \fB= \fIexpression \fB;\fR +.fi +.PP +For example: +.nf +.sp 1 + option hostname = binary-to-ascii (16, 8, "-", + substring (hardware, 1, 6)); +.fi +.SH STANDARD DHCPV4 OPTIONS +The documentation for the various options mentioned below is taken +from the latest IETF draft document on DHCP options. Options not +listed below may not yet be implemented, but it is possible to use +such options by defining them in the configuration file. Please see +the DEFINING NEW OPTIONS heading later in this document for more +information. +.PP +Some of the options documented here are automatically generated by +the DHCP server or by clients, and cannot be configured by the user. +The value of such an option can be used in the configuration file of +the receiving DHCP protocol agent (server or client), for example in +conditional expressions. However, the value of the option cannot be +used in the configuration file of the sending agent, because the value +is determined only \fIafter\fR the configuration file has been +processed. In the following documentation, such options will be shown +as "not user configurable" +.PP +The standard options are: +.PP +.B option \fBall-subnets-local\fR \fIflag\fR\fB;\fR +.RS 0.25i +.PP +This option specifies whether or not the client may assume that all +subnets of the IP network to which the client is connected use the +same MTU as the subnet of that network to which the client is +directly connected. A value of true indicates that all subnets share +the same MTU. A value of false means that the client should assume that +some subnets of the directly connected network may have smaller MTUs. +.RE +.PP +.B option \fBarp-cache-timeout\fR \fIuint32\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the timeout in seconds for ARP cache entries. +.RE +.PP +.B option \fBassociated-ip\fR \fIip-address\fR [\fB,\fR +\fIip-address\fR... ]\fB;\fR +.RS 0.25i +.PP +This option is part of lease query. It is used to +return all of the IP addresses associated with a given DHCP client. +.PP +This option is not user configurable. +.RE +.PP +.B option \fBbcms-controller-address\fR \fIip-address\fR [\fB,\fR +\fIip-address\fR... ]\fB;\fR +.RS 0.25i +.PP +This option configures a list of IPv4 addresses for use as Broadcast and +Multicast Controller Servers ("BCMS"). +.RE +.PP +.B option \fBbcms-controller-names\fR \fIdomain-list\fR\fB;\fR +.RS 0.25i +.PP +This option contains the domain names of local Broadcast and +Multicast Controller Servers ("BCMS") controllers which the client +may use. +.RE +.PP +.B option \fBbootfile-name\fR \fItext\fR\fB;\fR +.RS 0.25i +.PP +This option is used to identify a bootstrap file. If supported by the +client, it should have the same effect as the \fBfilename\fR +declaration. BOOTP clients are unlikely to support this option. Some +DHCP clients will support it, and others actually require it. +.RE +.PP +.B option \fBboot-size\fR \fIuint16\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the length in 512-octet blocks of the default +boot image for the client. +.RE +.PP +.B option \fBbroadcast-address\fR \fIip-address\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the broadcast address in use on the client's +subnet. Legal values for broadcast addresses are specified in +section 3.2.1.3 of STD 3 (RFC1122). +.RE +.PP +.B option \fBclient-last-transaction-time\fR \fIuint32\fR\fB;\fR +.RS 0.25i +.PP +This option is part of lease query. It allows the +receiver to determine the time of the most recent access by the +client. The value is a duration in seconds from when the client +last communicated with the DHCP server. +.PP +This option is not user configurable. +.RE +.PP +.B option \fBcookie-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... +]\fB;\fR +.RS 0.25i +.PP +The cookie server option specifies a list of RFC 865 cookie +servers available to the client. Servers should be listed in order +of preference. +.RE +.PP +.B option \fBdefault-ip-ttl\fR \fIuint8;\fR +.RS 0.25i +.PP +This option specifies the default time-to-live that the client should +use on outgoing datagrams. +.RE +.PP +.B option \fBdefault-tcp-ttl\fR \fIuint8\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the default TTL that the client should use when +sending TCP segments. The minimum value is 1. +.RE +.PP +.B option \fBdefault-url\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The format and meaning of this option is not described in any standards +document, but is claimed to be in use by Apple Computer. It is not known +what clients may reasonably do if supplied with this option. Use at your +own risk. +.RE +.PP +.B option \fBdhcp-client-identifier\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +This option can be used to specify a DHCP client identifier in a +host declaration, so that dhcpd can find the host record by matching +against the client identifier. +.PP +Please be aware that some DHCP clients, when configured with client +identifiers that are ASCII text, will prepend a zero to the ASCII +text. So you may need to write: +.nf + + option dhcp-client-identifier "\\0foo"; + +rather than: + + option dhcp-client-identifier "foo"; +.fi +.RE +.PP +.B option \fBdhcp-lease-time\fR \fIuint32\fR\fB;\fR +.RS 0.25i +.PP +This option is used in a client request (DHCPDISCOVER or DHCPREQUEST) +to allow the client to request a lease time for the IP address. In a +server reply (DHCPOFFER), a DHCP server uses this option to specify +the lease time it is willing to offer. +.PP +This option is not directly user configurable in the server; refer to the +\fImax-lease-time\fR and \fIdefault-lease-time\fR server options in +.B dhcpd.conf(5). +.RE +.PP +.B option \fBdhcp-max-message-size\fR \fIuint16\fR\fB;\fR +.RS 0.25i +.PP +This option, when sent by the client, specifies the maximum size of +any response that the server sends to the client. When specified on +the server, if the client did not send a dhcp-max-message-size option, +the size specified on the server is used. This works for BOOTP as +well as DHCP responses. +.RE +.PP +.B option \fBdhcp-message\fR \fItext\fR\fB;\fR +.RS 0.25i +.PP +This option is used by a DHCP server to provide an error message to a +DHCP client in a DHCPNAK message in the event of a failure. A client +may use this option in a DHCPDECLINE message to indicate why the +client declined the offered parameters. +.PP +This option is not user configurable. +.RE +.PP +.B option \fBdhcp-message-type\fR \fIuint8\fR\fB;\fR +.RS 0.25i +.PP +This option, sent by both client and server, specifies the type of DHCP +message contained in the DHCP packet. Possible values (taken directly from +RFC2132) are: +.PP +.nf + 1 DHCPDISCOVER + 2 DHCPOFFER + 3 DHCPREQUEST + 4 DHCPDECLINE + 5 DHCPACK + 6 DHCPNAK + 7 DHCPRELEASE + 8 DHCPINFORM +.fi +.PP +This option is not user configurable. +.PP +.RE +.B option \fBdhcp-option-overload\fR \fIuint8\fR\fB;\fR +.RS 0.25i +.PP +This option is used to indicate that the DHCP \'sname\' or \'file\' +fields are being overloaded by using them to carry DHCP options. A +DHCP server inserts this option if the returned parameters will +exceed the usual space allotted for options. +.PP +If this option is present, the client interprets the specified +additional fields after it concludes interpretation of the standard +option fields. +.PP +Legal values for this option are: +.PP +.nf + 1 the \'file\' field is used to hold options + 2 the \'sname\' field is used to hold options + 3 both fields are used to hold options +.fi +.PP +This option is not user configurable. +.PP +.RE +.PP +.B option \fBdhcp-parameter-request-list\fR \fIuint8\fR [\fB,\fR +\fIuint8\fR... ]\fB;\fR +.RS 0.25i +.PP +This option, when sent by the client, specifies which options the +client wishes the server to return. Normally, in the ISC DHCP +client, this is done using the \fIrequest\fR statement. If this +option is not specified by the client, the DHCP server will normally +return every option that is valid in scope and that fits into the +reply. When this option is specified on the server, the server +returns the specified options. This can be used to force a client to +take options that it hasn't requested, and it can also be used to +tailor the response of the DHCP server for clients that may need a +more limited set of options than those the server would normally +return. +.RE +.PP +.B option \fBdhcp-rebinding-time\fR \fIuint32\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the number of seconds from the time a client gets +an address until the client transitions to the REBINDING state. +.PP +This option is user configurable, but it will be ignored if the value is +greater than or equal to the lease time. +.PP +To make DHCPv4+DHCPv6 migration easier in the future, any value configured +in this option is also used as a DHCPv6 "T1" (renew) time. +.PP +.RE +.PP +.B option \fBdhcp-renewal-time\fR \fIuint32\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the number of seconds from the time a client gets +an address until the client transitions to the RENEWING state. +.PP +This option is user configurable, but it will be ignored if the value is +greater than or equal to the rebinding time, or lease time. +.PP +To make DHCPv4+DHCPv6 migration easier in the future, any value configured +in this option is also used as a DHCPv6 "T2" (rebind) time. +.PP +.RE +.PP +.B option \fBdhcp-requested-address\fR \fIip-address\fR\fB;\fR +.RS 0.25i +.PP +This option is used by the client in a DHCPDISCOVER to +request that a particular IP address be assigned. +.PP +This option is not user configurable. +.PP +.RE +.PP +.B option \fBdhcp-server-identifier\fR \fIip-address\fR\fB;\fR +.RS 0.25i +.PP +This option is used in DHCPOFFER and DHCPREQUEST messages, and may +optionally be included in the DHCPACK and DHCPNAK messages. DHCP +servers include this option in the DHCPOFFER in order to allow the +client to distinguish between lease offers. DHCP clients use the +contents of the \'server identifier\' field as the destination address +for any DHCP messages unicast to the DHCP server. DHCP clients also +indicate which of several lease offers is being accepted by including +this option in a DHCPREQUEST message. +.PP +The value of this option is the IP address of the server. +.PP +This option is not directly user configurable. See the +\fIserver-identifier\fR server option in +.B \fIdhcpd.conf(5). +.PP +.RE +.PP +.B option \fBdomain-name\fR \fItext\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the domain name that client should use when +resolving hostnames via the Domain Name System. +.RE +.PP +.B option \fBdomain-name-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... +]\fB;\fR +.RS 0.25i +.PP +The domain-name-servers option specifies a list of Domain Name System +(STD 13, RFC 1035) name servers available to the client. Servers +should be listed in order of preference. +.RE +.PP +.B option \fBdomain-search\fR \fIdomain-list\fR\fB;\fR +.RS 0.25i +.PP +The domain-search option specifies a \'search list\' of Domain Names to be +used by the client to locate not-fully-qualified domain names. The difference +between this option and historic use of the domain-name option for the same +ends is that this option is encoded in RFC1035 compressed labels on the wire. +For example: +.nf +.sp 1 + option domain-search "example.com", "sales.example.com", + "eng.example.com"; +.fi +.RE +.PP +.B option \fBextensions-path\fR \fItext\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the name of a file containing additional options +to be interpreted according to the DHCP option format as specified in +RFC2132. +.RE +.PP +.B option \fBfinger-server\fR \fIip-address\fR [\fB,\fR +\fIip-address\fR... ]\fB;\fR +.RS 0.25i +.PP +The Finger server option specifies a list of Finger servers available +to the client. Servers should be listed in order of preference. +.RE +.PP +.B option \fBfont-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... +]\fB;\fR +.RS 0.25i +.PP +This option specifies a list of X Window System Font servers available +to the client. Servers should be listed in order of preference. +.RE +.PP +.B option \fBhost-name\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the name of the client. The name may or may +not be qualified with the local domain name (it is preferable to use +the domain-name option to specify the domain name). See RFC 1035 for +character set restrictions. This option is only honored by +.B dhclient-script(8) +if the hostname for the client machine is not set. +.RE +.PP +.B option \fBieee802-3-encapsulation\fR \fIflag\fR\fB;\fR +.RS 0.25i +.PP +This option specifies whether or not the client should use Ethernet +Version 2 (RFC 894) or IEEE 802.3 (RFC 1042) encapsulation if the +interface is an Ethernet. A value of false indicates that the client +should use RFC 894 encapsulation. A value of true means that the client +should use RFC 1042 encapsulation. +.RE +.PP +.B option \fBien116-name-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... +]; +.RS 0.25i +.PP +The ien116-name-servers option specifies a list of IEN 116 name servers +available to the client. Servers should be listed in order of +preference. +.RE +.PP +.B option \fBimpress-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... +]\fB;\fR +.RS 0.25i +.PP +The impress-server option specifies a list of Imagen Impress servers +available to the client. Servers should be listed in order of +preference. +.RE +.PP +.B option \fBinterface-mtu\fR \fIuint16\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the MTU to use on this interface. The minimum +legal value for the MTU is 68. +.RE +.PP +.B option \fBip-forwarding\fR \fIflag\fR\fB;\fR +.RS 0.25i +.PP +This option specifies whether the client should configure its IP +layer for packet forwarding. A value of false means disable IP +forwarding, and a value of true means enable IP forwarding. +.RE +.PP +.B option \fBirc-server\fR \fIip-address\fR [\fB,\fR +\fIip-address\fR... ]\fB;\fR +.RS 0.25i +.PP +The IRC server option specifies a list of IRC servers available +to the client. Servers should be listed in order of preference. +.RE +.PP +.B option \fBlog-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... +]\fB;\fR +.RS 0.25i +.PP +The log-server option specifies a list of MIT-LCS UDP log servers +available to the client. Servers should be listed in order of +preference. +.RE +.PP +.B option \fBlpr-servers\fR \fIip-address \fR [\fB,\fR \fIip-address\fR... +]\fB;\fR +.RS 0.25i +.PP +The LPR server option specifies a list of RFC 1179 line printer +servers available to the client. Servers should be listed in order +of preference. +.RE +.PP +.B option \fBmask-supplier\fR \fIflag\fR\fB;\fR +.RS 0.25i +.PP +This option specifies whether or not the client should respond to +subnet mask requests using ICMP. A value of false indicates that the +client should not respond. A value of true means that the client should +respond. +.RE +.PP +.B option \fBmax-dgram-reassembly\fR \fIuint16\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the maximum size datagram that the client +should be prepared to reassemble. The minimum legal value is +576. +.RE +.PP +.B option \fBmerit-dump\fR \fItext\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the path-name of a file to which the client's +core image should be dumped in the event the client crashes. The +path is formatted as a character string consisting of characters from +the NVT ASCII character set. +.RE +.PP +.B option \fBmobile-ip-home-agent\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... ]\fB;\fR +.RS 0.25i +.PP +This option specifies a list of IP addresses indicating mobile IP +home agents available to the client. Agents should be listed in +order of preference, although normally there will be only one such +agent. +.RE +.PP +.B option \fBnds-context\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The nds-context option specifies the name of the initial Netware +Directory Service for an NDS client. +.RE +.PP +.B option \fBnds-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... ]\fB;\fR +.RS 0.25i +.PP +The nds-servers option specifies a list of IP addresses of NDS servers. +.RE +.PP +.B option \fBnds-tree-name\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The nds-tree-name option specifies NDS tree name that the NDS client +should use. +.RE +.PP +.B option \fBnetbios-dd-server\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... +]\fB;\fR +.RS 0.25i +.PP +The NetBIOS datagram distribution server (NBDD) option specifies a +list of RFC 1001/1002 NBDD servers listed in order of preference. +.RE +.PP +.B option \fBnetbios-name-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR...]\fB;\fR +.RS 0.25i +.PP +The NetBIOS name server (NBNS) option specifies a list of RFC +1001/1002 NBNS name servers listed in order of preference. NetBIOS +Name Service is currently more commonly referred to as WINS. WINS +servers can be specified using the netbios-name-servers option. +.RE +.PP +.B option \fBnetbios-node-type\fR \fIuint8\fR\fB;\fR +.RS 0.25i +.PP +The NetBIOS node type option allows NetBIOS over TCP/IP clients which +are configurable to be configured as described in RFC 1001/1002. The +value is specified as a single octet which identifies the client type. +.PP +Possible node types are: +.PP +.TP 5 +.I 1 +B-node: Broadcast - no WINS +.TP +.I 2 +P-node: Peer - WINS only +.TP +.I 4 +M-node: Mixed - broadcast, then WINS +.TP +.I 8 +H-node: Hybrid - WINS, then broadcast +.RE +.PP +.B option \fBnetbios-scope\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The NetBIOS scope option specifies the NetBIOS over TCP/IP scope +parameter for the client as specified in RFC 1001/1002. See RFC1001, +RFC1002, and RFC1035 for character-set restrictions. +.RE +.PP +.B option \fBnetinfo-server-address\fR \fIip-address\fR [\fB,\fR +\fIip-address\fR... ]\fB;\fR +.RS 0.25i +.PP +The \fBnetinfo-server-address\fR option has not been described in any +RFC, but has been allocated (and is claimed to be in use) by Apple +Computers. It's hard to say if the above is the correct format, or +what clients might be expected to do if values were configured. Use +at your own risk. +.RE +.PP +.B option \fBnetinfo-server-tag\fR \fItext\fR\fB;\fR +.RS 0.25i +.PP +The \fBnetinfo-server-tag\fR option has not been described in any +RFC, but has been allocated (and is claimed to be in use) by Apple +Computers. It's hard to say if the above is the correct format, +or what clients might be expected to do if values were configured. Use +at your own risk. +.RE +.PP +.B option \fBnis-domain\fR \fItext\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the name of the client's NIS (Sun Network +Information Services) domain. The domain is formatted as a character +string consisting of characters from the NVT ASCII character set. +.RE +.PP +.B option \fBnis-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... +]\fB;\fR +.RS 0.25i +.PP +This option specifies a list of IP addresses indicating NIS servers +available to the client. Servers should be listed in order of +preference. +.RE +.PP +.B option \fBnisplus-domain\fR \fItext\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the name of the client's NIS+ domain. The +domain is formatted as a character string consisting of characters +from the NVT ASCII character set. +.RE +.PP +.B option \fBnisplus-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... +]\fB;\fR +.RS 0.25i +.PP +This option specifies a list of IP addresses indicating NIS+ servers +available to the client. Servers should be listed in order of +preference. +.RE +.PP +.B option \fBnntp-server\fR \fIip-address\fR [\fB,\fR +\fIip-address\fR... ]\fB;\fR +.RS 0.25i +.PP +The NNTP server option specifies a list of NNTP servesr available +to the client. Servers should be listed in order of preference. +.RE +.PP +.B option \fBnon-local-source-routing\fR \fIflag\fR\fB;\fR +.RS 0.25i +.PP +This option specifies whether the client should configure its IP +layer to allow forwarding of datagrams with non-local source routes +(see Section 3.3.5 of [4] for a discussion of this topic). A value +of false means disallow forwarding of such datagrams, and a value of true +means allow forwarding. +.RE +.PP +.B option \fBntp-servers\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... +]\fB;\fR +.RS 0.25i +.PP +This option specifies a list of IP addresses indicating NTP (RFC 5905) +servers available to the client. Servers should be listed in order +of preference. +.RE +.PP +.B option \fBnwip-domain\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The name of the NetWare/IP domain that a NetWare/IP client should +use. +.RE +.PP +.B option \fBnwip-suboptions\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +A sequence of suboptions for NetWare/IP clients - see RFC2242 for +details. Normally this option is set by specifying specific +NetWare/IP suboptions - see the NETWARE/IP SUBOPTIONS section for more +information. +.RE +.PP +.B option \fBpath-mtu-aging-timeout\fR \fIuint32\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the timeout (in seconds) to use when aging Path +MTU values discovered by the mechanism defined in RFC 1191. +.RE +.PP +.B option \fBpath-mtu-plateau-table\fR \fIuint16\fR [\fB,\fR \fIuint16\fR... +]\fB;\fR +.RS 0.25i +.PP +This option specifies a table of MTU sizes to use when performing +Path MTU Discovery as defined in RFC 1191. The table is formatted as +a list of 16-bit unsigned integers, ordered from smallest to largest. +The minimum MTU value cannot be smaller than 68. +.RE +.PP +.B option \fBperform-mask-discovery\fR \fIflag\fR\fB;\fR +.RS 0.25i +.PP +This option specifies whether or not the client should perform subnet +mask discovery using ICMP. A value of false indicates that the client +should not perform mask discovery. A value of true means that the +client should perform mask discovery. +.RE +.PP +.nf +.B option \fBpolicy-filter\fR \fIip-address ip-address\fR + [\fB,\fR \fIip-address ip-address\fR...]\fB;\fR +.RE +.fi +.RS 0.25i +.PP +This option specifies policy filters for non-local source routing. +The filters consist of a list of IP addresses and masks which specify +destination/mask pairs with which to filter incoming source routes. +.PP +Any source routed datagram whose next-hop address does not match one +of the filters should be discarded by the client. +.PP +See STD 3 (RFC1122) for further information. +.RE +.PP +.B option \fBpop-server\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... ]\fB;\fR +.RS 0.25i +.PP +The POP3 server option specifies a list of POP3 servers available +to the client. Servers should be listed in order of preference. +.RE +.PP +.B option \fBresource-location-servers\fR \fIip-address\fR + [\fB, \fR\fIip-address\fR...]\fB;\fR +.fi +.RS 0.25i +.PP +This option specifies a list of RFC 887 Resource Location +servers available to the client. Servers should be listed in order +of preference. +.RE +.PP +.B option \fBroot-path\fR \fItext\fB;\fR\fR +.RS 0.25i +.PP +This option specifies the path-name that contains the client's root +disk. The path is formatted as a character string consisting of +characters from the NVT ASCII character set. +.RE +.PP +.B option \fBrouter-discovery\fR \fIflag\fR\fB;\fR +.RS 0.25i +.PP +This option specifies whether or not the client should solicit +routers using the Router Discovery mechanism defined in RFC 1256. +A value of false indicates that the client should not perform +router discovery. A value of true means that the client should perform +router discovery. +.RE +.PP +.B option \fBrouter-solicitation-address\fR \fIip-address\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the address to which the client should transmit +router solicitation requests. +.RE +.PP +.B option routers \fIip-address\fR [\fB,\fR \fIip-address\fR... +]\fB;\fR +.RS 0.25i +.PP +The routers option specifies a list of IP addresses for routers on the +client's subnet. Routers should be listed in order of preference. +.RE +.PP +.B option slp-directory-agent \fIboolean ip-address +[\fB,\fR \fIip-address\fR... ]\fB;\fR +.RS 0.25i +.PP +This option specifies two things: the IP addresses of one or more +Service Location Protocol Directory Agents, and whether the use of +these addresses is mandatory. If the initial boolean value is true, +the SLP agent should just use the IP addresses given. If the value +is false, the SLP agent may additionally do active or passive +multicast discovery of SLP agents (see RFC2165 for details). +.PP +Please note that in this option and the slp-service-scope option, the +term "SLP Agent" is being used to refer to a Service Location Protocol +agent running on a machine that is being configured using the DHCP +protocol. +.PP +Also, please be aware that some companies may refer to SLP as NDS. +If you have an NDS directory agent whose address you need to +configure, the slp-directory-agent option should work. +.RE +.PP +.B option slp-service-scope \fIboolean text\fR\fB;\fR +.RS 0.25i +.PP +The Service Location Protocol Service Scope Option specifies two +things: a list of service scopes for SLP, and whether the use of this +list is mandatory. If the initial boolean value is true, the SLP +agent should only use the list of scopes provided in this option; +otherwise, it may use its own static configuration in preference to +the list provided in this option. +.PP +The text string should be a comma-separated list of scopes that the +SLP agent should use. It may be omitted, in which case the SLP Agent +will use the aggregated list of scopes of all directory agents known +to the SLP agent. +.RE +.PP +.B option \fBsmtp-server\fR \fIip-address\fR [\fB,\fR +\fIip-address\fR... ]\fB;\fR +.RS 0.25i +.PP +The SMTP server option specifies a list of SMTP servers available to +the client. Servers should be listed in order of preference. +.RE +.PP +.nf +.B option \fBstatic-routes\fR \fIip-address ip-address\fR + [\fB,\fR \fIip-address ip-address\fR...]\fB;\fR +.fi +.RS 0.25i +.PP +This option specifies a list of static routes that the client should +install in its routing cache. If multiple routes to the same +destination are specified, they are listed in descending order of +priority. +.PP +The routes consist of a list of IP address pairs. The first address +is the destination address, and the second address is the router for +the destination. +.PP +The default route (0.0.0.0) is an illegal destination for a static +route. To specify the default route, use the +.B routers +option. Also, please note that this option is not intended for +classless IP routing - it does not include a subnet mask. Since +classless IP routing is now the most widely deployed routing standard, +this option is virtually useless, and is not implemented by any of the +popular DHCP clients, for example the Microsoft DHCP client. +.RE +.PP +.nf +.B option \fBstreettalk-directory-assistance-server\fR \fIip-address\fR + [\fB,\fR \fIip-address\fR...]\fB;\fR +.fi +.RS 0.25i +.PP +The StreetTalk Directory Assistance (STDA) server option specifies a +list of STDA servers available to the client. Servers should be +listed in order of preference. +.RE +.PP +.B option \fBstreettalk-server\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... ]\fB;\fR +.RS 0.25i +.PP +The StreetTalk server option specifies a list of StreetTalk servers +available to the client. Servers should be listed in order of +preference. +.RE +.PP +.B option subnet-mask \fIip-address\fR\fB;\fR +.RS 0.25i +.PP +The subnet mask option specifies the client's subnet mask as per RFC +950. If no subnet mask option is provided anywhere in scope, as a +last resort dhcpd will use the subnet mask from the subnet declaration +for the network on which an address is being assigned. However, +.I any +subnet-mask option declaration that is in scope for the address being +assigned will override the subnet mask specified in the subnet +declaration. +.RE +.PP +.B option \fBsubnet-selection\fR \fIip-address\fR\fB;\fR +.RS 0.25i +.PP +Sent by the client if an address is required in a subnet other than the one +that would normally be selected (based on the relaying address of the +connected subnet the request is obtained from). See RFC3011. Note that the +option number used by this server is 118; this has not always been the +defined number, and some clients may use a different value. Use of this +option should be regarded as slightly experimental! +.RE +.PP +This option is not user configurable in the server. +.PP +.PP +.B option \fBswap-server\fR \fIip-address\fR\fB;\fR +.RS 0.25i +.PP +This specifies the IP address of the client's swap server. +.RE +.PP +.B option \fBtcp-keepalive-garbage\fR \fIflag\fR\fB;\fR +.RS 0.25i +.PP +This option specifies whether or not the client should send TCP +keepalive messages with an octet of garbage for compatibility with +older implementations. A value of false indicates that a garbage octet +should not be sent. A value of true indicates that a garbage octet +should be sent. +.RE +.PP +.B option \fBtcp-keepalive-interval\fR \fIuint32\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the interval (in seconds) that the client TCP +should wait before sending a keepalive message on a TCP connection. +The time is specified as a 32-bit unsigned integer. A value of zero +indicates that the client should not generate keepalive messages on +connections unless specifically requested by an application. +.RE +.PP +.B option \fBtftp-server-name\fR \fItext\fR\fB;\fR +.RS 0.25i +.PP +This option is used to identify a TFTP server and, if supported by the +client, should have the same effect as the \fBserver-name\fR +declaration. BOOTP clients are unlikely to support this option. +Some DHCP clients will support it, and others actually require it. +.RE +.PP +.B option time-offset \fIint32\fR\fB;\fR +.RS 0.25i +.PP +The time-offset option specifies the offset of the client's subnet in +seconds from Coordinated Universal Time (UTC). +.RE +.PP +.B option time-servers \fIip-address\fR [, \fIip-address\fR... +]\fB;\fR +.RS 0.25i +.PP +The time-server option specifies a list of RFC 868 time servers +available to the client. Servers should be listed in order of +preference. +.RE +.PP +.B option \fBtrailer-encapsulation\fR \fIflag\fR\fB;\fR +.RS 0.25i +.PP +This option specifies whether or not the client should negotiate the +use of trailers (RFC 893 [14]) when using the ARP protocol. A value +of false indicates that the client should not attempt to use trailers. A +value of true means that the client should attempt to use trailers. +.RE +.PP +.B option \fBuap-servers\fR \fItext\fR\fB;\fR +.RS 0.25i +.PP +This option specifies a list of URLs, each pointing to a user +authentication service that is capable of processing authentication +requests encapsulated in the User Authentication Protocol (UAP). UAP +servers can accept either HTTP 1.1 or SSLv3 connections. If the list +includes a URL that does not contain a port component, the normal +default port is assumed (i.e., port 80 for http and port 443 for +https). If the list includes a URL that does not contain a path +component, the path /uap is assumed. If more than one URL is +specified in this list, the URLs are separated by spaces. +.RE +.PP +.B option \fBuser-class\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +This option is used by some DHCP clients as a way for users to +specify identifying information to the client. This can be used in a +similar way to the vendor-class-identifier option, but the value of +the option is specified by the user, not the vendor. Most recent +DHCP clients have a way in the user interface to specify the value for +this identifier, usually as a text string. +.RE +.PP +.B option \fBvendor-class-identifier\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +This option is used by some DHCP clients to identify the vendor +type and possibly the configuration of a DHCP client. The information +is a string of bytes whose contents are specific to the vendor and are +not specified in a standard. To see what vendor class identifier +clients are sending, you can write the following in your DHCP server +configuration file: +.nf +.PP +set vendor-string = option vendor-class-identifier; +.fi +.PP +This will result in all entries in the DHCP server lease database file +for clients that sent vendor-class-identifier options having a set +statement that looks something like this: +.nf +.PP +set vendor-string = "SUNW.Ultra-5_10"; +.fi +.PP +The vendor-class-identifier option is normally used by the DHCP server +to determine the options that are returned in the +.B vendor-encapsulated-options +option. Please see the VENDOR ENCAPSULATED OPTIONS section later in this +manual page for further information. +.RE +.PP +.B option \fBvendor-encapsulated-options\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The \fBvendor-encapsulated-options\fR option can contain either a +single vendor-specific value or one or more vendor-specific +suboptions. This option is not normally specified in the DHCP server +configuration file - instead, a vendor class is defined for each +vendor, vendor class suboptions are defined, values for those +suboptions are defined, and the DHCP server makes up a response on +that basis. +.PP +Some default behaviours for well-known DHCP client vendors (currently, +the Microsoft Windows 2000 DHCP client) are configured automatically, +but otherwise this must be configured manually - see the VENDOR +ENCAPSULATED OPTIONS section later in this manual page for details. +.RE +.PP +.B option \fBvivso\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The \fBvivso\fR option can contain multiple separate options, one for +each 32-bit Enterprise ID. Each Enterprise-ID discriminated option then +contains additional options whose format is defined by the vendor who +holds that ID. This option is usually not configured manually, but +rather is configured via intervening option definitions. Please also +see the VENDOR ENCAPSULATED OPTIONS section later in this manual page +for details. +.RE +.PP +.B option \fBwww-server\fR \fIip-address\fR [\fB,\fR +\fIip-address\fR... ]\fB;\fR +.RS 0.25i +.PP +The WWW server option specifies a list of WWW servers available +to the client. Servers should be listed in order of preference. +.RE +.PP +.B option \fBx-display-manager\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... +]\fB;\fR +.RS 0.25i +.PP +This option specifies a list of systems that are running the X Window +System Display Manager and are available to the client. Addresses +should be listed in order of preference. +.RE +.SH RELAY AGENT INFORMATION OPTION +An IETF draft, draft-ietf-dhc-agent-options-11.txt, defines a series +of encapsulated options that a relay agent can add to a DHCP packet +when relaying it to the DHCP server. The server can then make +address allocation decisions (or whatever other decisions it wants) +based on these options. The server also returns these options in any +replies it sends through the relay agent, so that the relay agent can +use the information in these options for delivery or accounting +purposes. +.PP +The current draft defines two options. To reference +these options in the dhcp server, specify the option space name, +"agent", followed by a period, followed by the option name. It is +not normally useful to define values for these options in the server, +although it is permissible. These options are not supported in the +client. +.PP +.B option \fBagent.circuit-id\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The circuit-id suboption encodes an agent-local identifier of the +circuit from which a DHCP client-to-server packet was received. It is +intended for use by agents in relaying DHCP responses back to the +proper circuit. The format of this option is currently defined to be +vendor-dependent, and will probably remain that way, although the +current draft allows for the possibility of standardizing the +format in the future. +.RE +.PP +.B option \fBagent.remote-id\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The remote-id suboption encodes information about the remote host end +of a circuit. Examples of what it might contain include caller ID +information, username information, remote ATM address, cable modem ID, +and similar things. In principal, the meaning is not well-specified, +and it should generally be assumed to be an opaque object that is +administratively guaranteed to be unique to a particular remote end of +a circuit. +.RE +.PP +.B option \fBagent.DOCSIS-device-class\fR \fIuint32\fR\fB;\fR +.RS 0.25i +.PP +The DOCSIS-device-class suboption is intended to convey information about +the host endpoint, hardware, and software, that either the host operating +system or the DHCP server may not otherwise be aware of (but the relay is +able to distinguish). This is implemented as a 32-bit field (4 octets), +each bit representing a flag describing the host in one of these ways. +So far, only bit zero (being the least significant bit) is defined in +RFC3256. If this bit is set to one, the host is considered a CPE +Controlled Cable Modem (CCCM). All other bits are reserved. +.RE +.PP +.B option \fBagent.link-selection\fR \fIip-address\fR\fB;\fR +.RS 0.25i +.PP +The link-selection suboption is provided by relay agents to inform servers +what subnet the client is actually attached to. This is useful in those +cases where the giaddr (where responses must be sent to the relay agent) +is not on the same subnet as the client. When this option is present in +a packet from a relay agent, the DHCP server will use its contents to find +a subnet declared in configuration, and from here take one step further +backwards to any shared-network the subnet may be defined within; the +client may be given any address within that shared network, as normally +appropriate. +.RE +.SH THE CLIENT FQDN SUBOPTIONS +The Client FQDN option, currently defined in the Internet Draft +draft-ietf-dhc-fqdn-option-00.txt is not a standard yet, but is in +sufficiently wide use already that we have implemented it. Due to +the complexity of the option format, we have implemented it as a +suboption space rather than a single option. In general this +option should not be configured by the user - instead it should be +used as part of an automatic DNS update system. +.PP +.B option fqdn.no-client-update \fIflag\fB; +.RS 0.25i +.PP +When the client sends this, if it is true, it means the client will not +attempt to update its A record. When sent by the server to the client, +it means that the client \fIshould not\fR update its own A record. +.RE +.PP +.B option fqdn.server-update \fIflag\fB; +.RS 0.25i +.PP +When the client sends this to the server, it is requesting that the server +update its A record. When sent by the server, it means that the server +has updated (or is about to update) the client's A record. +.RE +.PP +.B option fqdn.encoded \fIflag\fB; +.RS 0.25i +.PP +If true, this indicates that the domain name included in the option is +encoded in DNS wire format, rather than as plain ASCII text. The client +normally sets this to false if it doesn't support DNS wire format in the +FQDN option. The server should always send back the same value that the +client sent. When this value is set on the configuration side, it controls +the format in which the \fIfqdn.fqdn\fR suboption is encoded. +.RE +.PP +.B option fqdn.rcode1 \fIflag\fB; +.PP +.B option fqdn.rcode2 \fIflag\fB; +.RS 0.25i +.PP +These options specify the result of the updates of the A and PTR records, +respectively, and are only sent by the DHCP server to the DHCP client. +The values of these fields are those defined in the DNS protocol specification. +.RE +.PP +.B option fqdn.fqdn \fItext\fB; +.RS 0.25i +.PP +Specifies the domain name that the client wishes to use. This can be a +fully-qualified domain name, or a single label. If there is no trailing +\'.\' character in the name, it is not fully-qualified, and the server will +generally update that name in some locally-defined domain. +.RE +.PP +.B option fqdn.hostname \fI--never set--\fB; +.RS 0.25i +.PP +This option should never be set, but it can be read back using the \fBoption\fR +and \fBconfig-option\fR operators in an expression, in which case it returns +the first label in the \fBfqdn.fqdn\fR suboption - for example, if +the value of \fBfqdn.fqdn\fR is "foo.example.com.", then \fBfqdn.hostname\fR +will be "foo". +.RE +.PP +.B option fqdn.domainname \fI--never set--\fB; +.RS 0.25i +.PP +This option should never be set, but it can be read back using the \fBoption\fR +and \fBconfig-option\fR operators in an expression, in which case it returns +all labels after the first label in the \fBfqdn.fqdn\fR suboption - for +example, if the value of \fBfqdn.fqdn\fR is "foo.example.com.", +then \fBfqdn.domainname\fR will be "example.com.". If this suboption value +is not set, it means that an unqualified name was sent in the \fBfqdn\fR option, +or that no \fBfqdn\fR option was sent at all. +.RE +.PP +If you wish to use any of these suboptions, we strongly recommend that you +refer to the Client FQDN option draft (or standard, when it becomes a +standard) - the documentation here is sketchy and incomplete in comparison, +and is just intended for reference by people who already understand the +Client FQDN option specification. +.SH THE NETWARE/IP SUBOPTIONS +RFC2242 defines a set of encapsulated options for Novell NetWare/IP +clients. To use these options in the dhcp server, specify the option +space name, "nwip", followed by a period, followed by the option name. +The following options can be specified: +.PP +.B option \fBnwip.nsq-broadcast\fR \fIflag\fR\fB;\fR +.RS 0.25i +.PP +If true, the client should use the NetWare Nearest Server Query to +locate a NetWare/IP server. The behaviour of the Novell client if +this suboption is false, or is not present, is not specified. +.PP +.RE +.B option \fBnwip.preferred-dss\fR \fIip-address\fR [\fB,\fR \fIip-address\fR... ]\fR\fB;\fR +.RS 0.25i +.PP +This suboption specifies a list of up to five IP addresses, each of +which should be the IP address of a NetWare Domain SAP/RIP server +(DSS). +.RE +.PP +.B option \fBnwip.nearest-nwip-server\fR \fI\fIip-address\fR + [\fB,\fR \fIip-address\fR...]\fR\fB;\fR +.RS 0.25i +.PP +This suboption specifies a list of up to five IP addresses, each of +which should be the IP address of a Nearest NetWare IP server. +.RE +.PP +.B option \fBnwip.autoretries\fR \fIuint8\fR\fB;\fR +.RS 0.25i +.PP +Specifies the number of times that a NetWare/IP client should attempt +to communicate with a given DSS server at startup. +.RE +.PP +.B option \fBnwip.autoretry-secs\fR \fIuint8\fR\fB;\fR +.RS 0.25i +.PP +Specifies the number of seconds that a Netware/IP client should wait +between retries when attempting to establish communications with a DSS +server at startup. +.RE +.PP +.B option \fBnwip.nwip-1-1\fR \fIuint8\fR\fB;\fR +.RS 0.25i +.PP +If true, the NetWare/IP client should support NetWare/IP version 1.1 +compatibility. This is only needed if the client will be contacting +Netware/IP version 1.1 servers. +.RE +.PP +.B option \fBnwip.primary-dss\fR \fIip-address\fR\fB;\fR +.RS 0.25i +.PP +Specifies the IP address of the Primary Domain SAP/RIP Service server +(DSS) for this NetWare/IP domain. The NetWare/IP administration +utility uses this value as Primary DSS server when configuring a +secondary DSS server. +.RE +.SH STANDARD DHCPV6 OPTIONS +DHCPv6 options differ from DHCPv4 options partially due to using +16-bit code and length tags, but semantically zero-length options +are legal in DHCPv6, and multiple options are treated differently. +Whereas in DHCPv4 multiple options would be concatenated to form one +option, in DHCPv6 they are expected to be individual instantiations. +Understandably, many options are not "allowed" to have multiple +instances in a packet - normally these are options which are digested +by the DHCP protocol software, and not by users or applications. +.PP +.B option \fBdhcp6.client-id\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the client's DUID identifier. DUIDs are similar +but different from DHCPv4 client identifiers - there are documented duid +types: +.PP +.I duid-llt +.PP +.I duid-en +.PP +.I duid-ll +.PP +This value should not be configured, but rather is provided by clients +and treated as an opaque identifier key blob by servers. +.RE +.PP +.B option \fBdhcp6.server-id\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +This option specifies the server's DUID identifier. One may use this +option to configure an opaque binary blob for your server's identifier. +.RE +.PP +.B option \fBdhcp6.ia-na\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The Identity Association for Non-temporary Addresses (ia-na) carries +assigned addresses that are not temporary addresses for use by the +DHCPv6 client. This option is produced by the DHCPv6 server software, +and should not be configured. +.RE +.PP +.B option \fBdhcp6.ia-ta\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The Identity Association for Temporary Addresses (ia-ta) carries +temporary addresses, which may change upon every renewal. There is +no support for this in the current DHCPv6 software. +.RE +.PP +.B option \fBdhcp6.ia-addr\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The Identity Association Address option is encapsulated inside ia-na +or ia-ta options in order to represent addresses associated with those +IA's. These options are manufactured by the software, so should not +be configured. +.RE +.PP +.B option \fBdhcp6.oro\fR \fIuint16\fR [ \fB,\fR \fIuint16\fR\fB,\fR ... ]\fB;\fR +.RS 0.25i +.PP +The Option Request Option ("ORO") is the DHCPv6 equivalent of the +parameter-request-list. Clients supply this option to ask servers +to reply with options relevant to their needs and use. This option +must not be directly configured, the request syntax in dhclient.conf (5) +should be used instead. +.RE +.PP +.B option \fBdhcp6.preference\fR \fIuint8\fR\fB;\fR +.RS 0.25i +.PP +The \fBpreference\fR option informs a DHCPv6 client which server is +\'preferred\' for use on a given subnet. This preference is only +applied during the initial stages of configuration - once a client +is bound to an IA, it will remain bound to that IA until it is no +longer valid or has expired. This value may be configured on the +server, and is digested by the client software. +.RE +.PP +.B option \fBdhcp6.elapsed-time\fR \fIuint16\fR\fB;\fR +.RS 0.25i +.PP +The \fBelapsed-time\fR option is constructed by the DHCPv6 client +software, and is potentially consumed by intermediaries. This +option should not be configured. +.RE +.PP +.B option \fBdhcp6.relay-msg\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The \fBrelay-msg\fR option is constructed by intervening DHCPv6 +relay agent software. This option is entirely used by protocol +software, and is not meant for user configuration. +.RE +.PP +.B option \fBdhcp6.unicast\fR \fIip6-address\fR\fB;\fR +.RS 0.25i +.PP +The \fBunicast\fR option is provided by DHCPv6 servers which are +willing (or prefer) to receive Renew packets from their clients +by exchanging UDP unicasts with them. Normally, DHCPv6 clients +will multicast their Renew messages. This may be configured on +the server, and should be configured as an address the server +is ready to reply to. +.RE +.PP +.B option \fBdhcp6.status-code\fR \fIstatus-code\fR [ \fIstring\fR ] \fB;\fR +.RS 0.25i +.PP +The \fBstatus-code\fR option is provided by DHCPv6 servers to inform +clients of error conditions during protocol communication. This option +is manufactured and digested by protocol software, and should not be +configured. +.RE +.PP +.B option \fBdhcp6.rapid-commit\fR \fB;\fR +.RS 0.25i +.PP +The \fBrapid-commit\fR option is a zero-length option that clients use +to indicate their desire to enter into rapid-commit with the server. +.RE +.PP +.B option \fBdhcp6.vendor-opts\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The \fBvendor-opts\fR option is actually an encapsulated sub-option space, +in which each Vendor-specific Information Option (VSIO) is identified by +a 32-bit Enterprise-ID number. The encapsulated option spaces within these +options are defined by the vendors. +.PP +To make use of this option, the best way is to examine the section +titled VENDOR ENCAPSULATED OPTIONS below, in particular the bits about +the "vsio" option space. +.RE +.PP +.B option \fBdhcp6.interface-id\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The \fBinterface-id\fR option is manufactured by relay agents, and may +be used to guide configuration differentiating clients by the interface +they are remotely attached to. It does not make sense to configure a +value for this option, but it may make sense to inspect its contents. +.RE +.PP +.B option \fBdhcp6.reconf-msg\fR \fIdhcpv6-message\fR\fB;\fR +.RS 0.25i +.PP +The \fBreconf-msg\fR option is manufactured by servers, and sent to +clients in Reconfigure messages to inform them of what message +the client should Reconfigure using. There is no support for +DHCPv6 Reconfigure extensions, and this option is documented +informationally only. +.RE +.PP +.B option \fBdhcp6.reconf-accept ;\fR +.RS 0.25i +.PP +The \fBreconf-accept\fR option is included by DHCPv6 clients that +support the Reconfigure extentions, advertising that they will +respond if the server were to ask them to Reconfigure. There is +no support for DHCPv6 Reconfigure extensions, and this option is +documented informationally only. +.RE +.PP +.B option \fBdhcp6.sip-servers-names\fR \fIdomain-list\fR\fB;\fR +.RS 0.25i +.PP +The \fBsip-servers-names\fR option allows SIP clients to locate a +local SIP server that is to be used for all outbound SIP requests, a +so-called"outbound proxy server." If you wish to use manually entered +IPv6 addresses instead, please see the \fBsip-servers-addresses\fR option +below. +.RE +.PP +.B option +.B dhcp6.sip-servers-addresses +.I ip6-address \fR[\fB,\fR +.I ip6-address \fR... ] +.B ; +.RS 0.25i +.PP +The \fBsip-servers-addresses\fR option allows SIP clients to locate +a local SIP server that is to be used for all outbound SIP requests, +a so-called "outbound proxy servers." If you wish to use domain names +rather than IPv6 addresses, please see the \fBsip-servers-names\fR option +above. +.RE +.PP +.B option +.B dhcp6.name-servers +.I ip6-address \fR[\fB,\fR +.I ip6-address \fR... ] +.B ; +.RS 0.25i +.PP +The \fBname-servers\fR option instructs clients about locally available +recursive DNS servers. It is easiest to describe this as the "nameserver" +line in /etc/resolv.conf. +.RE +.PP +.B option \fBdhcp6.domain-search\fR \fIdomain-list\fR\fB;\fR +.RS 0.25i +.PP +The \fBdomain-search\fR option specifies the client's domain search path +to be applied to recursive DNS queries. It is easiest to describe this as +the "search" line in /etc/resolv.conf. +.RE +.PP +.B option \fBdhcp6.ia-pd\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The \fBia-pd\fR option is manufactured by clients and servers to create a +Prefix Delegation binding - to delegate an IPv6 prefix to the client. It is +not directly edited in dhcpd.conf(5) or dhclient.conf(5), but rather is +manufactured and consumed by the software. +.RE +.PP +.B option \fBdhcp6.ia-prefix\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The \fBia-prefix\fR option is placed inside \fBia-pd\fR options in order +to identify the prefix(es) allocated to the client. It is not directly +edited in dhcpd.conf(5) or dhclient.conf(5), but rather is +manufactured and consumed by the software. +.RE +.PP +.B option +.B dhcp6.nis-servers +.I ip6-address \fR[\fB, +.I ip6-address \fR... ] +.B ; +.RS 0.25i +.PP +The \fBnis-servers\fR option identifies, in order, NIS servers available +to the client. +.RE +.PP +.B option +.B dhcp6.nisp-servers +.I ip6-address \fR[\fB, +.I ip6-address \fR... ] +.B ; +.RS 0.25i +.PP +The \fBnisp-servers\fR option identifies, in order, NIS+ servers available +to the client. +.RE +.PP +.B option \fBnis-domain-name\fR \fIdomain-list\fR\fB;\fR +.RS 0.25i +.PP +The \fBnis-domain-name\fR option specifies the NIS domain name the client is +expected to use, and is related to the \fBnis-servers\fR option. +.RE +.PP +.B option \fBdhcp6.nis-domain-name\fR \fIdomain-name\fR\fB;\fR +.RS 0.25i +.PP +The \fBdhcp6.nis-domain-name\fR option specifies NIS domain name the +client is expected to use, and is related to \fBdhcp6.nis-servers\fR option. +.RE +.PP +.B option \fBnisp-domain-name\fR \fIdomain-list\fR\fB;\fR +.RS 0.25i +.PP +The \fBnisp-domain-name\fR option specifies the NIS+ domain name the client +is expected to use, and is related to the \fBnisp-servers\fR option. +.RE +.PP +.B option \fBdhcp6.nisp-domain-name\fR \fIdomain-name\fR\fB;\fR +.RS 0.25i +.PP +The \fBdhcp6.nis-domain-name\fR option specifies NIS+ domain name the +client is expected to use, and is related to \fBdhcp6.nisp-servers\fR option. +.RE +.PP +.B option +.B dhcp6.sntp-servers +.I ip6-address \fR[\fB, +.I ip6-address \fR... ] +.B ; +.RS 0.25i +.PP +The \fBsntp-servers\fR option specifies a list of local SNTP servers +available for the client to synchronize their clocks. +.RE +.PP +.B option \fBdhcp6.info-refresh-time\fR \fIuint32\fR\fB;\fR +.RS 0.25i +.PP +The \fBinfo-refresh-time\fR option gives DHCPv6 clients using +Information-request messages a hint as to how long they should between +refreshing the information they were given. Note that this option will +only be delivered to the client, and be likely to affect the client's +behaviour, if the client requested the option. +.RE +.PP +.B option \fBdhcp6.bcms-server-d\fR \fIdomain-list\fR\fB;\fR +.RS 0.25i +.PP +The \fBbcms-server-d\fR option contains the domain names of local BCMS +(Broadcast and Multicast Control Services) controllers which the client +may use. +.RE +.PP +.B option +.B dhcp6.bcms-server-a +.I ip6-address \fR[\fB, +.I ip6-address \fR... ] +.B ; +.RS 0.25i +.PP +The \fBbcms-server-a\fR option contains the IPv6 addresses of local BCMS +(Broadcast and Multicast Control Services) controllers which the client +may use. +.RE +.PP +.B option \fBdhcp6.remote-id\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The \fBremote-id\fR option is constructed by relay agents, to inform the +server of details pertaining to what the relay knows about the client (such +as what port it is attached to, and so forth). The contents of this option +have some vendor-specific structure (similar to VSIO), but we have chosen +to treat this option as an opaque field. +.RE +.PP +.B option \fBdhcp6.subscriber-id\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The \fBsubscriber-id\fR option is an opaque field provided by the relay agent, +which provides additional information about the subscriber in question. The +exact contents of this option depend upon the vendor and/or the operator's +configuration of the remote device, and as such is an opaque field. +.RE +.PP +.B option \fBdhcp6.fqdn\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The \fBfqdn\fR option is normally constructed by the client or server, +and negotiates the client's Fully Qualified Domain Name, as well as which +party is responsible for Dynamic DNS Updates. See the section on the +Client FQDN SubOptions for full details (the DHCPv4 and DHCPv6 FQDN options +use the same "fqdn." encapsulated space, so are in all ways identical). +.RE +.PP +.B option \fBdhcp6.lq-query\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The \fBlq-query\fR option is used internally by for lease query. +.RE +.PP +.B option \fBdhcp6.client-data\fR \fIstring\fR\fB;\fR +.RS 0.25i +.PP +The \fBclient-data\fR option is used internally by for lease query. +.RE +.PP +.B option \fBdhcp6.clt-time\fR \fIuint32\fR\fB;\fR +.RS 0.25i +.PP +The \fBclt-time\fR option is used internally by for lease query. +.RE +.PP +.B option \fBdhcp6.lq-relay-data\fR \fIip6-address string\fR\fB;\fR +.RS 0.25i +.PP +The \fBlq-relay-data\fR option is used internally by for lease query. +.RE +.PP +.B option +.B dhcp6.lq-client-link +.I ip6-address \fR[\fB,\fR +.I ip6-address \fR... ] +.B ; +.RS 0.25i +.PP +The \fBlq-client-link\fR option is used internally by for lease query. +.RE +.PP +.RE +.SH DEFINING NEW OPTIONS +The Internet Systems Consortium DHCP client and server provide the +capability to define new options. Each DHCP option has a name, a +code, and a structure. The name is used by you to refer to the +option. The code is a number, used by the DHCP server and client to +refer to an option. The structure describes what the contents of an +option looks like. +.PP +To define a new option, you need to choose a name for it that is not +in use for some other option - for example, you can't use "host-name" +because the DHCP protocol already defines a host-name option, which is +documented earlier in this manual page. If an option name doesn't +appear in this manual page, you can use it, but it's probably a good +idea to put some kind of unique string at the beginning so you can be +sure that future options don't take your name. For example, you +might define an option, "local-host-name", feeling some confidence +that no official DHCP option name will ever start with "local". +.PP +Once you have chosen a name, you must choose a code. All codes between +224 and 254 are reserved as \'site-local\' DHCP options, so you can pick +any one of these for your site (not for your product/application). In +RFC3942, site-local space was moved from starting at 128 to starting at +224. In practice, some vendors have interpreted the protocol rather +loosely and have used option code values greater than 128 themselves. +There's no real way to avoid this problem, and it was thought to be +unlikely to cause too much trouble in practice. If you come across +a vendor-documented option code in either the new or old site-local +spaces, please contact your vendor and inform them about rfc3942. +.PP +The structure of an option is simply the format in which the option +data appears. The ISC DHCP server currently supports a few simple +types, like integers, booleans, strings and IP addresses, and it also +supports the ability to define arrays of single types or arrays of +fixed sequences of types. +.PP +New options are declared as follows: +.PP +.B option +.I new-name +.B code +.I new-code +.B = +.I definition +.B ; +.PP +The values of +.I new-name +and +.I new-code +should be the name you have chosen for the new option and the code you +have chosen. The +.I definition +should be the definition of the structure of the option. +.PP +The following simple option type definitions are supported: +.PP +.B BOOLEAN +.PP +.B option +.I new-name +.B code +.I new-code +.B = +.B boolean +.B ; +.PP +An option of type boolean is a flag with a value of either on or off +(or true or false). So an example use of the boolean type would be: +.nf + +option use-zephyr code 180 = boolean; +option use-zephyr on; + +.fi +.B INTEGER +.PP +.B option +.I new-name +.B code +.I new-code +.B = +.I sign +.B integer +.I width +.B ; +.PP +The \fIsign\fR token should either be blank, \fIunsigned\fR +or \fIsigned\fR. The width can be either 8, 16 or 32, and refers to +the number of bits in the integer. So for example, the following two +lines show a definition of the sql-connection-max option and its use: +.nf + +option sql-connection-max code 192 = unsigned integer 16; +option sql-connection-max 1536; + +.fi +.B IP-ADDRESS +.PP +.B option +.I new-name +.B code +.I new-code +.B = +.B ip-address +.B ; +.PP +An option whose structure is an IP address can be expressed either as +a domain name or as a dotted quad. So the following is an example use +of the ip-address type: +.nf + +option sql-server-address code 193 = ip-address; +option sql-server-address sql.example.com; + +.fi +.B IP6-ADDRESS +.PP +.B option +.I new-name +.B code +.I new-code +.B = +.B ip6-address +.B ; +.PP +An option whose structure is an IPv6 address must be expressed as +a valid IPv6 address. The following is an example use of the +ip6-address type: +.nf + +option dhcp6.some-server code 1234 = array of ip6-address; +option dhcp6.some-server 3ffe:bbbb:aaaa:aaaa::1, 3ffe:bbbb:aaaa:aaaa::2; + +.fi +.PP +.B TEXT +.PP +.B option +.I new-name +.B code +.I new-code +.B = +.B text +.B ; +.PP +An option whose type is text will encode an ASCII text string. For +example: +.nf + +option sql-default-connection-name code 194 = text; +option sql-default-connection-name "PRODZA"; + +.fi +.PP +.B DATA STRING +.PP +.B option +.I new-name +.B code +.I new-code +.B = +.B string +.B ; +.PP +An option whose type is a data string is essentially just a collection +of bytes, and can be specified either as quoted text, like the text +type, or as a list of hexadecimal contents separated by colons whose +values must be between 0 and FF. For example: +.nf + +option sql-identification-token code 195 = string; +option sql-identification-token 17:23:19:a6:42:ea:99:7c:22; + +.fi +.PP +.B DOMAIN-LIST +.PP +.B option +.I new-name +.B code +.I new-code +.B = +.B domain-list +.B [compressed] +.B ; +.PP +An option whose type is \fBdomain-list\fR is an RFC1035 formatted (on the +wire, "DNS Format") list of domain names, separated by root labels. The +optional \fBcompressed\fR keyword indicates if the option should be +compressed relative to the start of the option contents (not the packet +contents). +.PP +When in doubt, omit the \fBcompressed\fR keyword. When the software receives +an option that is compressed and the \fBcompressed\fR keyword is omitted, it +will still decompress the option (relative to the option contents field). The +keyword only controls whether or not transmitted packets are compressed. +.PP +Note that when +.B domain-list +formatted options are output as environment variables to +.B dhclient-script(8), +the standard DNS \-escape mechanism is used: they are decimal. This is +appropriate for direct use in eg /etc/resolv.conf. +.nf + +.fi +.PP +.B ENCAPSULATION +.PP +.B option +.I new-name +.B code +.I new-code +.B = +.B encapsulate +.I identifier +.B ; +.PP +An option whose type is \fBencapsulate\fR will encapsulate the +contents of the option space specified in \fIidentifier\fR. Examples +of encapsulated options in the DHCP protocol as it currently exists +include the vendor-encapsulated-options option, the netware-suboptions +option and the relay-agent-information option. +.nf + +option space local; +option local.demo code 1 = text; +option local-encapsulation code 197 = encapsulate local; +option local.demo "demo"; + +.fi +.PP +.B ARRAYS +.PP +Options can contain arrays of any of the above types except for the +text and data string types, which aren't currently supported in +arrays. An example of an array definition is as follows: +.nf + +option kerberos-servers code 200 = array of ip-address; +option kerberos-servers 10.20.10.1, 10.20.11.1; + +.fi +.B RECORDS +.PP +Options can also contain data structures consisting of a sequence of +data types, which is sometimes called a record type. For example: +.nf + +option contrived-001 code 201 = { boolean, integer 32, text }; +option contrived-001 on 1772 "contrivance"; + +.fi +It's also possible to have options that are arrays of records, for +example: +.nf + +option new-static-routes code 201 = array of { + ip-address, ip-address, ip-address, integer 8 }; +option static-routes + 10.0.0.0 255.255.255.0 net-0-rtr.example.com 1, + 10.0.1.0 255.255.255.0 net-1-rtr.example.com 1, + 10.2.0.0 255.255.224.0 net-2-0-rtr.example.com 3; + +.fi +.SH VENDOR ENCAPSULATED OPTIONS +The DHCP protocol defines the \fBvendor-encapsulated-options\fR +option, which allows vendors to define their own options that will be +sent encapsulated in a standard DHCP option. It also defines +the \fBVendor Identified Vendor Sub Options\fR option ("VIVSO"), and the +DHCPv6 protocol defines the \fBVendor-specific Information Option\fR +("VSIO"). The format of all of these options is usually internally a +string of options, similarly to other normal DHCP options. The VIVSO +and VSIO options differ in that they contain options that correspond +to vendor Enterprise-ID numbers (assigned by IANA), which then contain +options according to each Vendor's specifications. You will need to refer +to your vendor's documentation in order to form options to their +specification. +.PP +The value of these options can be set in one of two ways. The first +way is to simply specify the data directly, using a text string or a +colon-separated list of hexadecimal values. For help in forming these +strings, please refer to \fBRFC2132\fR for the DHCPv4 \fBVendor Specific +Information Option\fR, \fBRFC3925\fR for the DHCPv4 \fBVendor Identified Vendor +Sub Options\fR, or \fBRFC3315\fR for the DHCPv6 \fBVendor-specific Information +Option\fR. For example: +.PP +.nf +option vendor-encapsulated-options + 2:4: + AC:11:41:1: + 3:12: + 73:75:6e:64:68:63:70:2d:73:65:72:76:65:72:31:37:2d:31: + 4:12: + 2f:65:78:70:6f:72:74:2f:72:6f:6f:74:2f:69:38:36:70:63; +option vivso + 00:00:09:bf:0E: + 01:0c: + 48:65:6c:6c:6f:20:77:6f:72:6c:64:21; +option dhcp6.vendor-opts + 00:00:09:bf: + 00:01:00:0c: + 48:65:6c:6c:6f:20:77:6f:72:6c:64:21; +.fi +.PP +The second way of setting the value of these options is to have the DHCP +server generate a vendor-specific option buffer. To do this, you +must do four things: define an option space, define some options in +that option space, provide values for them, and specify that that +option space should be used to generate the relevant option. +.PP +To define a new option space in which vendor options can be stored, +use the \fRoption space\fP statement: +.PP +.B option +.B space +.I name +.B [ [ code width +.I number +.B ] [ length width +.I number +.B ] [ hash size +.I number +.B ] ] ; +.PP +Where the numbers following \fBcode width\fR, \fBlength width\fR, +and \fBhash size\fR respectively identify the number of bytes used to +describe option codes, option lengths, and the size in buckets of the +hash tables to hold options in this space (most DHCPv4 option spaces +use 1 byte codes and lengths, which is the default, whereas most +DHCPv6 option spaces use 2 byte codes and lengths). +.PP +The code and length widths are used in DHCP protocol - you must configure +these numbers to match the applicable option space you are configuring. +They each default to 1. Valid values for code widths are 1, 2 or 4. +Valid values for length widths are 0, 1 or 2. Most DHCPv4 option spaces +use 1 byte codes and lengths, which is the default, whereas most DHCPv6 +option spaces use 2 byte codes and lengths. A zero-byte length produces +options similar to the DHCPv6 Vendor-specific Information Option - but +not their contents! +.PP +The hash size defaults depend upon the \fBcode width\fR selected, and +may be 254 or 1009. Valid values range between 1 and 65535. Note +that the higher you configure this value, the more memory will be used. It +is considered good practice to configure a value that is slightly larger +than the estimated number of options you plan to configure within the +space. Previous versions of ISC DHCP (up to and including DHCP 3.0.*), +this value was fixed at 9973. +.PP +The name can then be used in option definitions, as described earlier in +this document. For example: +.nf + +option space SUNW code width 1 length width 1 hash size 3; +option SUNW.server-address code 2 = ip-address; +option SUNW.server-name code 3 = text; +option SUNW.root-path code 4 = text; + +option space ISC code width 1 length width 1 hash size 3; +option ISC.sample code 1 = text; +option vendor.ISC code 2495 = encapsulate vivso-sample; +option vendor-class.ISC code 2495 = text; + +option ISC.sample "configuration text here"; +option vendor-class.ISC "vendor class here"; + +option space docsis code width 2 length width 2 hash size 17; +option docsis.tftp-servers code 32 = array of ip6-address; +option docsis.cablelabs-configuration-file code 33 = text; +option docsis.cablelabs-syslog-servers code 34 = array of ip6-address; +option docsis.device-id code 36 = string; +option docsis.time-servers code 37 = array of ip6-address; +option docsis.time-offset code 38 = signed integer 32; +option vsio.docsis code 4491 = encapsulate docsis; + +.fi +Once you have defined an option space and the format of some options, +you can set up scopes that define values for those options, and you +can say when to use them. For example, suppose you want to handle +two different classes of clients. Using the option space definition +shown in the previous example, you can send different option values to +different clients based on the vendor-class-identifier option that the +clients send, as follows: +.PP +.nf +class "vendor-classes" { + match option vendor-class-identifier; +} + +subclass "vendor-classes" "SUNW.Ultra-5_10" { + vendor-option-space SUNW; + option SUNW.root-path "/export/root/sparc"; +} + +subclass "vendor-classes" "SUNW.i86pc" { + vendor-option-space SUNW; + option SUNW.root-path "/export/root/i86pc"; +} + +option SUNW.server-address 172.17.65.1; +option SUNW.server-name "sundhcp-server17-1"; + +option vivso-sample.sample "Hello world!"; + +option docsis.tftp-servers ::1; + +.fi +.PP +As you can see in the preceding example, regular scoping rules apply, +so you can define values that are global in the global scope, and only +define values that are specific to a particular class in the local +scope. The \fBvendor-option-space\fR declaration tells the DHCP +server to use options in the SUNW option space to construct the DHCPv4 +.B vendor-encapsulated-options +option. This is a limitation of that option - the DHCPv4 VIVSO and the +DHCPv6 VSIO options can have multiple vendor definitions all at once (even +transmitted to the same client), so it is not necessary to configure this. +.SH SEE ALSO +dhcpd.conf(5), dhcpd.leases(5), dhclient.conf(5), dhcp-eval(5), dhcpd(8), +dhclient(8), RFC2132, RFC2131, RFC3046, RFC3315. +.SH AUTHOR +Information about Internet Systems Consortium can be found at +.B https://www.isc.org. diff --git a/common/discover.c b/common/discover.c new file mode 100644 index 0000000..3cd64a7 --- /dev/null +++ b/common/discover.c @@ -0,0 +1,1862 @@ +/* discover.c + + Find and identify the network interfaces. */ + +/* + * Copyright (c) 2013-2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2009,2011 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" + +/* length of line we can read from the IF file, 256 is too small in some cases */ +#define IF_LINE_LENGTH 1024 + +#define BSD_COMP /* needed on Solaris for SIOCGLIFNUM */ +#include <sys/ioctl.h> +#include <errno.h> + +#ifdef HAVE_NET_IF6_H +# include <net/if6.h> +#endif + +struct interface_info *interfaces, *dummy_interfaces, *fallback_interface; +int interfaces_invalidated; +int quiet_interface_discovery; +u_int16_t local_port; +u_int16_t remote_port; +int (*dhcp_interface_setup_hook) (struct interface_info *, struct iaddr *); +int (*dhcp_interface_discovery_hook) (struct interface_info *); +isc_result_t (*dhcp_interface_startup_hook) (struct interface_info *); +int (*dhcp_interface_shutdown_hook) (struct interface_info *); + +struct in_addr limited_broadcast; + +int local_family = AF_INET; +struct in_addr local_address; + +void (*bootp_packet_handler) (struct interface_info *, + struct dhcp_packet *, unsigned, + unsigned int, + struct iaddr, struct hardware *); + +#ifdef DHCPv6 +void (*dhcpv6_packet_handler)(struct interface_info *, + const char *, int, + int, const struct iaddr *, + isc_boolean_t); +#endif /* DHCPv6 */ + + +omapi_object_type_t *dhcp_type_interface; +#if defined (TRACING) +trace_type_t *interface_trace; +trace_type_t *inpacket_trace; +trace_type_t *outpacket_trace; +#endif +struct interface_info **interface_vector; +int interface_count; +int interface_max; + +OMAPI_OBJECT_ALLOC (interface, struct interface_info, dhcp_type_interface) + +isc_result_t interface_setup () +{ + isc_result_t status; + status = omapi_object_type_register (&dhcp_type_interface, + "interface", + dhcp_interface_set_value, + dhcp_interface_get_value, + dhcp_interface_destroy, + dhcp_interface_signal_handler, + dhcp_interface_stuff_values, + dhcp_interface_lookup, + dhcp_interface_create, + dhcp_interface_remove, + 0, 0, 0, + sizeof (struct interface_info), + interface_initialize, RC_MISC); + if (status != ISC_R_SUCCESS) + log_fatal ("Can't register interface object type: %s", + isc_result_totext (status)); + + return status; +} + +#if defined (TRACING) +void interface_trace_setup () +{ + interface_trace = trace_type_register ("interface", (void *)0, + trace_interface_input, + trace_interface_stop, MDL); + inpacket_trace = trace_type_register ("inpacket", (void *)0, + trace_inpacket_input, + trace_inpacket_stop, MDL); + outpacket_trace = trace_type_register ("outpacket", (void *)0, + trace_outpacket_input, + trace_outpacket_stop, MDL); +} +#endif + +isc_result_t interface_initialize (omapi_object_t *ipo, + const char *file, int line) +{ + struct interface_info *ip = (struct interface_info *)ipo; + ip -> rfdesc = ip -> wfdesc = -1; + return ISC_R_SUCCESS; +} + + +/* + * Scanning for Interfaces + * ----------------------- + * + * To find interfaces, we create an iterator that abstracts out most + * of the platform specifics. Use is fairly straightforward: + * + * - begin_iface_scan() starts the process. + * - Use next_iface() until it returns 0. + * - end_iface_scan() performs any necessary cleanup. + * + * We check for errors on each call to next_iface(), which returns a + * description of the error as a string if any occurs. + * + * We currently have code for Solaris and Linux. Other systems need + * to have code written. + * + * NOTE: the long-term goal is to use the interface code from BIND 9. + */ + +#if defined(SIOCGLIFCONF) && defined(SIOCGLIFNUM) && defined(SIOCGLIFFLAGS) + +/* HP/UX doesn't define struct lifconf, instead they define struct + * if_laddrconf. Similarly, 'struct lifreq' and 'struct lifaddrreq'. + */ +#ifdef ISC_PLATFORM_HAVEIF_LADDRCONF +# define lifc_len iflc_len +# define lifc_buf iflc_buf +# define lifc_req iflc_req +# define LIFCONF if_laddrconf +#else +# define ISC_HAVE_LIFC_FAMILY 1 +# define ISC_HAVE_LIFC_FLAGS 1 +# define LIFCONF lifconf +#endif + +#ifdef ISC_PLATFORM_HAVEIF_LADDRREQ +# define lifr_addr iflr_addr +# define lifr_name iflr_name +# define lifr_dstaddr iflr_dstaddr +# define lifr_flags iflr_flags +# define sockaddr_storage sockaddr_ext +# define ss_family sa_family +# define LIFREQ if_laddrreq +#else +# define LIFREQ lifreq +#endif + +#ifndef IF_NAMESIZE +# if defined(LIFNAMSIZ) +# define IF_NAMESIZE LIFNAMSIZ +# elif defined(IFNAMSIZ) +# define IF_NAMESIZE IFNAMSIZ +# else +# define IF_NAMESIZE 16 +# endif +#endif +#elif !defined(__linux) && !defined(HAVE_IFADDRS_H) +# define SIOCGLIFCONF SIOCGIFCONF +# define SIOCGLIFFLAGS SIOCGIFFLAGS +# define LIFREQ ifreq +# define LIFCONF ifconf +# define lifr_name ifr_name +# define lifr_addr ifr_addr +# define lifr_flags ifr_flags +# define lifc_len ifc_len +# define lifc_buf ifc_buf +# define lifc_req ifc_req +#ifdef _AIX +# define ss_family __ss_family +#endif +#endif + +#if defined(SIOCGLIFCONF) && defined(SIOCGLIFFLAGS) +/* + * Solaris support + * --------------- + * + * The SIOCGLIFCONF ioctl() are the extension that you need to use + * on Solaris to get information about IPv6 addresses. + * + * Solaris' extended interface is documented in the if_tcp man page. + */ + +/* + * Structure holding state about the scan. + */ +struct iface_conf_list { + int sock; /* file descriptor used to get information */ + int num; /* total number of interfaces */ + struct LIFCONF conf; /* structure used to get information */ + int next; /* next interface to retrieve when iterating */ +}; + +/* + * Structure used to return information about a specific interface. + */ +struct iface_info { + char name[IF_NAMESIZE+1]; /* name of the interface, e.g. "bge0" */ + struct sockaddr_storage addr; /* address information */ + isc_uint64_t flags; /* interface flags, e.g. IFF_LOOPBACK */ +}; + +/* + * Start a scan of interfaces. + * + * The iface_conf_list structure maintains state for this process. + */ +int +begin_iface_scan(struct iface_conf_list *ifaces) { +#ifdef ISC_PLATFORM_HAVELIFNUM + struct lifnum lifnum; +#else + int lifnum; +#endif + + ifaces->sock = socket(local_family, SOCK_DGRAM, IPPROTO_UDP); + if (ifaces->sock < 0) { + log_error("Error creating socket to list interfaces; %m"); + return 0; + } + + memset(&lifnum, 0, sizeof(lifnum)); +#ifdef ISC_PLATFORM_HAVELIFNUM + lifnum.lifn_family = AF_UNSPEC; +#endif +#ifdef SIOCGLIFNUM + if (ioctl(ifaces->sock, SIOCGLIFNUM, &lifnum) < 0) { + log_error("Error finding total number of interfaces; %m"); + close(ifaces->sock); + ifaces->sock = -1; + return 0; + } + +#ifdef ISC_PLATFORM_HAVELIFNUM + ifaces->num = lifnum.lifn_count; +#else + ifaces->num = lifnum; +#endif +#else + ifaces->num = 64; +#endif /* SIOCGLIFNUM */ + + memset(&ifaces->conf, 0, sizeof(ifaces->conf)); +#ifdef ISC_HAVE_LIFC_FAMILY + ifaces->conf.lifc_family = AF_UNSPEC; +#endif + ifaces->conf.lifc_len = ifaces->num * sizeof(struct LIFREQ); + ifaces->conf.lifc_buf = dmalloc(ifaces->conf.lifc_len, MDL); + if (ifaces->conf.lifc_buf == NULL) { + log_fatal("Out of memory getting interface list."); + } + + if (ioctl(ifaces->sock, SIOCGLIFCONF, &ifaces->conf) < 0) { + log_error("Error getting interfaces configuration list; %m"); + dfree(ifaces->conf.lifc_buf, MDL); + close(ifaces->sock); + ifaces->sock = -1; + return 0; + } + + ifaces->next = 0; + + return 1; +} + +/* + * Retrieve the next interface. + * + * Returns information in the info structure. + * Sets err to 1 if there is an error, otherwise 0. + */ +int +next_iface(struct iface_info *info, int *err, struct iface_conf_list *ifaces) { + struct LIFREQ *p; + struct LIFREQ tmp; + isc_boolean_t foundif; +#if defined(sun) || defined(__linux) + /* Pointer used to remove interface aliases. */ + char *s; +#endif + + do { + foundif = ISC_FALSE; + + if (ifaces->next >= ifaces->num) { + *err = 0; + return 0; + } + + p = ifaces->conf.lifc_req; + p += ifaces->next; + + if (strlen(p->lifr_name) >= sizeof(info->name)) { + *err = 1; + log_error("Interface name '%s' too long", p->lifr_name); + return 0; + } + + /* Reject if interface address family does not match */ + if (p->lifr_addr.ss_family != local_family) { + ifaces->next++; + continue; + } + + strcpy(info->name, p->lifr_name); + memset(&info->addr, 0, sizeof(info->addr)); + memcpy(&info->addr, &p->lifr_addr, sizeof(p->lifr_addr)); + +#if defined(sun) || defined(__linux) + /* interface aliases look like "eth0:1" or "wlan1:3" */ + s = strchr(info->name, ':'); + if (s != NULL) { + *s = '\0'; + } +#endif /* defined(sun) || defined(__linux) */ + + foundif = ISC_TRUE; + } while ((foundif == ISC_FALSE) || + (strncmp(info->name, "dummy", 5) == 0)); + + memset(&tmp, 0, sizeof(tmp)); + strcpy(tmp.lifr_name, info->name); + if (ioctl(ifaces->sock, SIOCGLIFFLAGS, &tmp) < 0) { + log_error("Error getting interface flags for '%s'; %m", + p->lifr_name); + *err = 1; + return 0; + } + info->flags = tmp.lifr_flags; + + ifaces->next++; + *err = 0; + return 1; +} + +/* + * End scan of interfaces. + */ +void +end_iface_scan(struct iface_conf_list *ifaces) { + dfree(ifaces->conf.lifc_buf, MDL); + close(ifaces->sock); + ifaces->sock = -1; +} + +#elif __linux /* !HAVE_SIOCGLIFCONF */ +/* + * Linux support + * ------------- + * + * In Linux, we use the /proc pseudo-filesystem to get information + * about interfaces, along with selected ioctl() calls. + * + * Linux low level access is documented in the netdevice man page. + */ + +/* + * Structure holding state about the scan. + */ +struct iface_conf_list { + int sock; /* file descriptor used to get information */ + FILE *fp; /* input from /proc/net/dev */ +#ifdef DHCPv6 + FILE *fp6; /* input from /proc/net/if_inet6 */ +#endif +}; + +/* + * Structure used to return information about a specific interface. + */ +struct iface_info { + char name[IFNAMSIZ]; /* name of the interface, e.g. "eth0" */ + struct sockaddr_storage addr; /* address information */ + isc_uint64_t flags; /* interface flags, e.g. IFF_LOOPBACK */ +}; + +/* + * Start a scan of interfaces. + * + * The iface_conf_list structure maintains state for this process. + */ +int +begin_iface_scan(struct iface_conf_list *ifaces) { + char buf[IF_LINE_LENGTH]; + int len; + int i; + + ifaces->fp = fopen("/proc/net/dev", "r"); + if (ifaces->fp == NULL) { + log_error("Error opening '/proc/net/dev' to list interfaces"); + return 0; + } + + /* + * The first 2 lines are header information, so read and ignore them. + */ + for (i=0; i<2; i++) { + if (fgets(buf, sizeof(buf), ifaces->fp) == NULL) { + log_error("Error reading headers from '/proc/net/dev'"); + fclose(ifaces->fp); + ifaces->fp = NULL; + return 0; + } + len = strlen(buf); + if ((len <= 0) || (buf[len-1] != '\n')) { + log_error("Bad header line in '/proc/net/dev'"); + fclose(ifaces->fp); + ifaces->fp = NULL; + return 0; + } + } + + ifaces->sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (ifaces->sock < 0) { + log_error("Error creating socket to list interfaces; %m"); + fclose(ifaces->fp); + ifaces->fp = NULL; + return 0; + } + +#ifdef DHCPv6 + if (local_family == AF_INET6) { + ifaces->fp6 = fopen("/proc/net/if_inet6", "r"); + if (ifaces->fp6 == NULL) { + log_error("Error opening '/proc/net/if_inet6' to " + "list IPv6 interfaces; %m"); + close(ifaces->sock); + ifaces->sock = -1; + fclose(ifaces->fp); + ifaces->fp = NULL; + return 0; + } + } +#endif + + return 1; +} + +/* + * Read our IPv4 interfaces from /proc/net/dev. + * + * The file looks something like this: + * + * Inter-| Receive ... + * face |bytes packets errs drop fifo frame ... + * lo: 1580562 4207 0 0 0 0 ... + * eth0: 0 0 0 0 0 0 ... + * eth1:1801552440 37895 0 14 0 ... + * + * We only care about the interface name, which is at the start of + * each line. + * + * We use an ioctl() to get the address and flags for each interface. + */ +static int +next_iface4(struct iface_info *info, int *err, struct iface_conf_list *ifaces) { + char buf[IF_LINE_LENGTH]; + int len; + char *p; + char *name; + struct ifreq tmp; + + /* + * Loop exits when we find an interface that has an address, or + * when we run out of interfaces. + */ + for (;;) { + do { + /* + * Read the next line in the file. + */ + if (fgets(buf, sizeof(buf), ifaces->fp) == NULL) { + if (ferror(ifaces->fp)) { + *err = 1; + log_error("Error reading interface " + "information"); + } else { + *err = 0; + } + return 0; + } + + /* + * Make sure the line is a nice, + * newline-terminated line. + */ + len = strlen(buf); + if ((len <= 0) || (buf[len-1] != '\n')) { + log_error("Bad line reading interface " + "information"); + *err = 1; + return 0; + } + + /* + * Figure out our name. + */ + p = strrchr(buf, ':'); + if (p == NULL) { + log_error("Bad line reading interface " + "information (no colon)"); + *err = 1; + return 0; + } + *p = '\0'; + name = buf; + while (isspace(*name)) { + name++; + } + + /* + * Copy our name into our interface structure. + */ + len = p - name; + if (len >= sizeof(info->name)) { + *err = 1; + log_error("Interface name '%s' too long", name); + return 0; + } + strncpy(info->name, name, sizeof(info->name) - 1); + +#ifdef ALIAS_NAMED_PERMUTED + /* interface aliases look like "eth0:1" or "wlan1:3" */ + s = strchr(info->name, ':'); + if (s != NULL) { + *s = '\0'; + } +#endif + +#ifdef SKIP_DUMMY_INTERFACES + } while (strncmp(info->name, "dummy", 5) == 0); +#else + } while (0); +#endif + + memset(&tmp, 0, sizeof(tmp)); + strncpy(tmp.ifr_name, name, sizeof(tmp.ifr_name) - 1); + if (ioctl(ifaces->sock, SIOCGIFADDR, &tmp) < 0) { + if (errno == EADDRNOTAVAIL) { + continue; + } + log_error("Error getting interface address " + "for '%s'; %m", name); + *err = 1; + return 0; + } + memcpy(&info->addr, &tmp.ifr_addr, sizeof(tmp.ifr_addr)); + + memset(&tmp, 0, sizeof(tmp)); + strncpy(tmp.ifr_name, name, sizeof(tmp.ifr_name) - 1); + if (ioctl(ifaces->sock, SIOCGIFFLAGS, &tmp) < 0) { + log_error("Error getting interface flags for '%s'; %m", + name); + *err = 1; + return 0; + } + info->flags = tmp.ifr_flags; + + *err = 0; + return 1; + } +} + +#ifdef DHCPv6 +/* + * Read our IPv6 interfaces from /proc/net/if_inet6. + * + * The file looks something like this: + * + * fe80000000000000025056fffec00008 05 40 20 80 vmnet8 + * 00000000000000000000000000000001 01 80 10 80 lo + * fe80000000000000025056fffec00001 06 40 20 80 vmnet1 + * 200108881936000202166ffffe497d9b 03 40 00 00 eth1 + * fe8000000000000002166ffffe497d9b 03 40 20 80 eth1 + * + * We get IPv6 address from the start, the interface name from the end, + * and ioctl() to get flags. + */ +static int +next_iface6(struct iface_info *info, int *err, struct iface_conf_list *ifaces) { + char buf[IF_LINE_LENGTH]; + int len; + char *p; + char *name; + int i; + struct sockaddr_in6 addr; + struct ifreq tmp; + + do { + /* + * Read the next line in the file. + */ + if (fgets(buf, sizeof(buf), ifaces->fp6) == NULL) { + if (ferror(ifaces->fp6)) { + *err = 1; + log_error("Error reading IPv6 " + "interface information"); + } else { + *err = 0; + } + return 0; + } + + /* + * Make sure the line is a nice, newline-terminated line. + */ + len = strlen(buf); + if ((len <= 0) || (buf[len-1] != '\n')) { + log_error("Bad line reading IPv6 " + "interface information"); + *err = 1; + return 0; + } + + /* + * Figure out our name. + */ + buf[--len] = '\0'; + p = strrchr(buf, ' '); + if (p == NULL) { + log_error("Bad line reading IPv6 interface " + "information (no space)"); + *err = 1; + return 0; + } + name = p+1; + + /* + * Copy our name into our interface structure. + */ + len = strlen(name); + if (len >= sizeof(info->name)) { + *err = 1; + log_error("IPv6 interface name '%s' too long", name); + return 0; + } + strcpy(info->name, name); + +#ifdef SKIP_DUMMY_INTERFACES + } while (strncmp(info->name, "dummy", 5) == 0); +#else + } while (0); +#endif + + /* + * Double-check we start with the IPv6 address. + */ + for (i=0; i<32; i++) { + if (!isxdigit(buf[i]) || isupper(buf[i])) { + *err = 1; + log_error("Bad line reading IPv6 interface address " + "for '%s'", name); + return 0; + } + } + + /* + * Load our socket structure. + */ + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + for (i=0; i<16; i++) { + unsigned char byte; + static const char hex[] = "0123456789abcdef"; + byte = ((index(hex, buf[i * 2]) - hex) << 4) | + (index(hex, buf[i * 2 + 1]) - hex); + addr.sin6_addr.s6_addr[i] = byte; + } + memcpy(&info->addr, &addr, sizeof(addr)); + + /* + * Get our flags. + */ + memset(&tmp, 0, sizeof(tmp)); + strcpy(tmp.ifr_name, name); + if (ioctl(ifaces->sock, SIOCGIFFLAGS, &tmp) < 0) { + log_error("Error getting interface flags for '%s'; %m", name); + *err = 1; + return 0; + } + info->flags = tmp.ifr_flags; + + *err = 0; + return 1; +} +#endif /* DHCPv6 */ + +/* + * Retrieve the next interface. + * + * Returns information in the info structure. + * Sets err to 1 if there is an error, otherwise 0. + */ +int +next_iface(struct iface_info *info, int *err, struct iface_conf_list *ifaces) { + if (next_iface4(info, err, ifaces)) { + return 1; + } +#ifdef DHCPv6 + if (!(*err)) { + if (local_family == AF_INET6) + return next_iface6(info, err, ifaces); + } +#endif + return 0; +} + +/* + * End scan of interfaces. + */ +void +end_iface_scan(struct iface_conf_list *ifaces) { + fclose(ifaces->fp); + ifaces->fp = NULL; + close(ifaces->sock); + ifaces->sock = -1; +#ifdef DHCPv6 + if (local_family == AF_INET6) { + fclose(ifaces->fp6); + ifaces->fp6 = NULL; + } +#endif +} +#else + +/* + * BSD support + * ----------- + * + * FreeBSD, NetBSD, OpenBSD, and OS X all have the getifaddrs() + * function. + * + * The getifaddrs() man page describes the use. + */ + +#include <ifaddrs.h> + +/* + * Structure holding state about the scan. + */ +struct iface_conf_list { + struct ifaddrs *head; /* beginning of the list */ + struct ifaddrs *next; /* current position in the list */ +}; + +/* + * Structure used to return information about a specific interface. + */ +struct iface_info { + char name[IFNAMSIZ]; /* name of the interface, e.g. "bge0" */ + struct sockaddr_storage addr; /* address information */ + isc_uint64_t flags; /* interface flags, e.g. IFF_LOOPBACK */ +}; + +/* + * Start a scan of interfaces. + * + * The iface_conf_list structure maintains state for this process. + */ +int +begin_iface_scan(struct iface_conf_list *ifaces) { + if (getifaddrs(&ifaces->head) != 0) { + log_error("Error getting interfaces; %m"); + return 0; + } + ifaces->next = ifaces->head; + return 1; +} + +/* + * Retrieve the next interface. + * + * Returns information in the info structure. + * Sets err to 1 if there is an error, otherwise 0. + */ +int +next_iface(struct iface_info *info, int *err, struct iface_conf_list *ifaces) { + if (ifaces->next == NULL) { + *err = 0; + return 0; + } + if (strlen(ifaces->next->ifa_name) >= sizeof(info->name)) { + log_error("Interface name '%s' too long", + ifaces->next->ifa_name); + *err = 1; + return 0; + } + strcpy(info->name, ifaces->next->ifa_name); + memcpy(&info->addr, ifaces->next->ifa_addr, + ifaces->next->ifa_addr->sa_len); + info->flags = ifaces->next->ifa_flags; + ifaces->next = ifaces->next->ifa_next; + *err = 0; + return 1; +} + +/* + * End scan of interfaces. + */ +void +end_iface_scan(struct iface_conf_list *ifaces) { + freeifaddrs(ifaces->head); + ifaces->head = NULL; + ifaces->next = NULL; +} +#endif + +/* XXX: perhaps create drealloc() rather than do it manually */ +void +add_ipv4_addr_to_interface(struct interface_info *iface, + const struct in_addr *addr) { + /* + * We don't expect a lot of addresses per IPv4 interface, so + * we use 4, as our "chunk size" for collecting addresses. + */ + if (iface->addresses == NULL) { + iface->addresses = dmalloc(4 * sizeof(struct in_addr), MDL); + if (iface->addresses == NULL) { + log_fatal("Out of memory saving IPv4 address " + "on interface."); + } + iface->address_count = 0; + iface->address_max = 4; + } else if (iface->address_count >= iface->address_max) { + struct in_addr *tmp; + int new_max; + + new_max = iface->address_max + 4; + tmp = dmalloc(new_max * sizeof(struct in_addr), MDL); + if (tmp == NULL) { + log_fatal("Out of memory saving IPv4 address " + "on interface."); + } + memcpy(tmp, + iface->addresses, + iface->address_max * sizeof(struct in_addr)); + dfree(iface->addresses, MDL); + iface->addresses = tmp; + iface->address_max = new_max; + } + iface->addresses[iface->address_count++] = *addr; +} + +#ifdef DHCPv6 +/* XXX: perhaps create drealloc() rather than do it manually */ +void +add_ipv6_addr_to_interface(struct interface_info *iface, + const struct in6_addr *addr) { + /* + * Each IPv6 interface will have at least two IPv6 addresses, + * and likely quite a few more. So we use 8, as our "chunk size" for + * collecting addresses. + */ + if (iface->v6addresses == NULL) { + iface->v6addresses = dmalloc(8 * sizeof(struct in6_addr), MDL); + if (iface->v6addresses == NULL) { + log_fatal("Out of memory saving IPv6 address " + "on interface."); + } + iface->v6address_count = 0; + iface->v6address_max = 8; + } else if (iface->v6address_count >= iface->v6address_max) { + struct in6_addr *tmp; + int new_max; + + new_max = iface->v6address_max + 8; + tmp = dmalloc(new_max * sizeof(struct in6_addr), MDL); + if (tmp == NULL) { + log_fatal("Out of memory saving IPv6 address " + "on interface."); + } + memcpy(tmp, + iface->v6addresses, + iface->v6address_max * sizeof(struct in6_addr)); + dfree(iface->v6addresses, MDL); + iface->v6addresses = tmp; + iface->v6address_max = new_max; + } + iface->v6addresses[iface->v6address_count++] = *addr; +} +#endif /* DHCPv6 */ + +/* Use the SIOCGIFCONF ioctl to get a list of all the attached interfaces. + For each interface that's of type INET and not the loopback interface, + register that interface with the network I/O software, figure out what + subnet it's on, and add it to the list of interfaces. */ + +void +discover_interfaces(int state) { + struct iface_conf_list ifaces; + struct iface_info info; + int err; + + struct interface_info *tmp; + struct interface_info *last, *next; + +#ifdef DHCPv6 + char abuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; +#endif /* DHCPv6 */ + + + struct subnet *subnet; + int ir; + isc_result_t status; + int wifcount = 0; + + static int setup_fallback = 0; + + if (!begin_iface_scan(&ifaces)) { + log_fatal("Can't get list of interfaces."); + } + + /* If we already have a list of interfaces, and we're running as + a DHCP server, the interfaces were requested. */ + if (interfaces && (state == DISCOVER_SERVER || + state == DISCOVER_RELAY || + state == DISCOVER_REQUESTED)) + ir = 0; + else if (state == DISCOVER_UNCONFIGURED) + ir = INTERFACE_REQUESTED | INTERFACE_AUTOMATIC; + else + ir = INTERFACE_REQUESTED; + + /* Cycle through the list of interfaces looking for IP addresses. */ + while (next_iface(&info, &err, &ifaces)) { + + /* See if we've seen an interface that matches this one. */ + for (tmp = interfaces; tmp; tmp = tmp->next) { + if (!strcmp(tmp->name, info.name)) + break; + } + + /* Skip non broadcast interfaces (plus loopback and + point-to-point in case an OS incorrectly marks them + as broadcast). Also skip down interfaces unless we're + trying to get a list of configurable interfaces. */ + if ((((local_family == AF_INET && + !(info.flags & IFF_BROADCAST)) || +#ifdef DHCPv6 + (local_family == AF_INET6 && + !(info.flags & IFF_MULTICAST)) || +#endif + info.flags & IFF_LOOPBACK || + info.flags & IFF_POINTOPOINT) && !tmp) || + (!(info.flags & IFF_UP) && + state != DISCOVER_UNCONFIGURED)) + continue; + + /* If there isn't already an interface by this name, + allocate one. */ + if (tmp == NULL) { + status = interface_allocate(&tmp, MDL); + if (status != ISC_R_SUCCESS) { + log_fatal("Error allocating interface %s: %s", + info.name, isc_result_totext(status)); + } + strcpy(tmp->name, info.name); + interface_snorf(tmp, ir); + interface_dereference(&tmp, MDL); + tmp = interfaces; /* XXX */ + } + + if (dhcp_interface_discovery_hook) { + (*dhcp_interface_discovery_hook)(tmp); + } + + if ((info.addr.ss_family == AF_INET) && + (local_family == AF_INET)) { + struct sockaddr_in *a = (struct sockaddr_in*)&info.addr; + struct iaddr addr; + + /* We don't want the loopback interface. */ + if (a->sin_addr.s_addr == htonl(INADDR_LOOPBACK) && + ((tmp->flags & INTERFACE_AUTOMATIC) && + state == DISCOVER_SERVER)) + continue; + + /* If the only address we have is 0.0.0.0, we + shouldn't consider the interface configured. */ + if (a->sin_addr.s_addr != htonl(INADDR_ANY)) + tmp->configured = 1; + + add_ipv4_addr_to_interface(tmp, &a->sin_addr); + + /* invoke the setup hook */ + addr.len = 4; + memcpy(addr.iabuf, &a->sin_addr.s_addr, addr.len); + if (dhcp_interface_setup_hook) { + (*dhcp_interface_setup_hook)(tmp, &addr); + } + } +#ifdef DHCPv6 + else if ((info.addr.ss_family == AF_INET6) && + (local_family == AF_INET6)) { + struct sockaddr_in6 *a = + (struct sockaddr_in6*)&info.addr; + struct iaddr addr; + + /* We don't want the loopback interface. */ + if (IN6_IS_ADDR_LOOPBACK(&a->sin6_addr) && + ((tmp->flags & INTERFACE_AUTOMATIC) && + state == DISCOVER_SERVER)) + continue; + + /* If the only address we have is 0.0.0.0, we + shouldn't consider the interface configured. */ + if (IN6_IS_ADDR_UNSPECIFIED(&a->sin6_addr)) + tmp->configured = 1; + + add_ipv6_addr_to_interface(tmp, &a->sin6_addr); + + /* invoke the setup hook */ + addr.len = 16; + memcpy(addr.iabuf, &a->sin6_addr, addr.len); + if (dhcp_interface_setup_hook) { + (*dhcp_interface_setup_hook)(tmp, &addr); + } + } +#endif /* DHCPv6 */ + } + + if (err) { + log_fatal("Error getting interface information."); + } + + end_iface_scan(&ifaces); + + + /* Mock-up an 'ifp' structure which is no longer used in the + * new interface-sensing code, but is used in higher layers + * (for example to sense fallback interfaces). + */ + for (tmp = interfaces ; tmp != NULL ; tmp = tmp->next) { + if (tmp->ifp == NULL) { + struct ifreq *tif; + + tif = (struct ifreq *)dmalloc(sizeof(struct ifreq), + MDL); + if (tif == NULL) + log_fatal("no space for ifp mockup."); + strcpy(tif->ifr_name, tmp->name); + tmp->ifp = tif; + } + } + + + /* If we're just trying to get a list of interfaces that we might + be able to configure, we can quit now. */ + if (state == DISCOVER_UNCONFIGURED) { + return; + } + + /* Weed out the interfaces that did not have IP addresses. */ + tmp = last = next = NULL; + if (interfaces) + interface_reference (&tmp, interfaces, MDL); + while (tmp) { + if (next) + interface_dereference (&next, MDL); + if (tmp -> next) + interface_reference (&next, tmp -> next, MDL); + /* skip interfaces that are running already */ + if (tmp -> flags & INTERFACE_RUNNING) { + interface_dereference(&tmp, MDL); + if(next) + interface_reference(&tmp, next, MDL); + continue; + } + if ((tmp -> flags & INTERFACE_AUTOMATIC) && + state == DISCOVER_REQUESTED) + tmp -> flags &= ~(INTERFACE_AUTOMATIC | + INTERFACE_REQUESTED); + +#ifdef DHCPv6 + if (!(tmp->flags & INTERFACE_REQUESTED)) { +#else + if (!tmp -> ifp || !(tmp -> flags & INTERFACE_REQUESTED)) { +#endif /* DHCPv6 */ + if ((tmp -> flags & INTERFACE_REQUESTED) != ir) + log_fatal ("%s: not found", tmp -> name); + if (!last) { + if (interfaces) + interface_dereference (&interfaces, + MDL); + if (next) + interface_reference (&interfaces, next, MDL); + } else { + interface_dereference (&last -> next, MDL); + if (next) + interface_reference (&last -> next, + next, MDL); + } + if (tmp -> next) + interface_dereference (&tmp -> next, MDL); + + /* Remember the interface in case we need to know + about it later. */ + if (dummy_interfaces) { + interface_reference (&tmp -> next, + dummy_interfaces, MDL); + interface_dereference (&dummy_interfaces, MDL); + } + interface_reference (&dummy_interfaces, tmp, MDL); + interface_dereference (&tmp, MDL); + if (next) + interface_reference (&tmp, next, MDL); + continue; + } + last = tmp; + + /* We must have a subnet declaration for each interface. */ + if (!tmp->shared_network && (state == DISCOVER_SERVER)) { + log_error("%s", ""); + if (local_family == AF_INET) { + log_error("No subnet declaration for %s (%s).", + tmp->name, + (tmp->addresses == NULL) ? + "no IPv4 addresses" : + inet_ntoa(tmp->addresses[0])); +#ifdef DHCPv6 + } else { + if (tmp->v6addresses != NULL) { + inet_ntop(AF_INET6, + &tmp->v6addresses[0], + abuf, + sizeof(abuf)); + } else { + strcpy(abuf, "no IPv6 addresses"); + } + log_error("No subnet6 declaration for %s (%s).", + tmp->name, + abuf); +#endif /* DHCPv6 */ + } + if (supports_multiple_interfaces(tmp)) { + log_error ("** Ignoring requests on %s. %s", + tmp -> name, "If this is not what"); + log_error (" you want, please write %s", +#ifdef DHCPv6 + (local_family != AF_INET) ? + "a subnet6 declaration" : +#endif + "a subnet declaration"); + log_error (" in your dhcpd.conf file %s", + "for the network segment"); + log_error (" to %s %s %s", + "which interface", + tmp -> name, "is attached. **"); + log_error ("%s", ""); + goto next; + } else { + log_error ("You must write a %s", +#ifdef DHCPv6 + (local_family != AF_INET) ? + "subnet6 declaration for this" : +#endif + "subnet declaration for this"); + log_error ("subnet. You cannot prevent %s", + "the DHCP server"); + log_error ("from listening on this subnet %s", + "because your"); + log_fatal ("operating system does not %s.", + "support this capability"); + } + } + + /* Find subnets that don't have valid interface + addresses... */ + for (subnet = (tmp -> shared_network + ? tmp -> shared_network -> subnets + : (struct subnet *)0); + subnet; subnet = subnet -> next_sibling) { + /* Set the interface address for this subnet + to the first address we found. */ + if (subnet->interface_address.len == 0) { + if (tmp->address_count > 0) { + subnet->interface_address.len = 4; + memcpy(subnet->interface_address.iabuf, + &tmp->addresses[0].s_addr, 4); + } else if (tmp->v6address_count > 0) { + subnet->interface_address.len = 16; + memcpy(subnet->interface_address.iabuf, + &tmp->v6addresses[0].s6_addr, + 16); + } else { + /* XXX: should be one */ + log_error("%s missing an interface " + "address", tmp->name); + continue; + } + } + } + + /* Flag the index as not having been set, so that the + interface registerer can set it or not as it chooses. */ + tmp -> index = -1; + + /* Register the interface... */ + if (local_family == AF_INET) { + if_register_receive(tmp); + if_register_send(tmp); +#ifdef DHCPv6 + } else { + if ((state == DISCOVER_SERVER) || + (state == DISCOVER_RELAY)) { + if_register6(tmp, 1); + } else { + if_register_linklocal6(tmp); + } +#endif /* DHCPv6 */ + } + + interface_stash (tmp); + wifcount++; +#if defined (F_SETFD) + if (fcntl (tmp -> rfdesc, F_SETFD, 1) < 0) + log_error ("Can't set close-on-exec on %s: %m", + tmp -> name); + if (tmp -> rfdesc != tmp -> wfdesc) { + if (fcntl (tmp -> wfdesc, F_SETFD, 1) < 0) + log_error ("Can't set close-on-exec on %s: %m", + tmp -> name); + } +#endif + next: + interface_dereference (&tmp, MDL); + if (next) + interface_reference (&tmp, next, MDL); + } + + /* + * Now register all the remaining interfaces as protocols. + * We register with omapi to allow for control of the interface, + * we've already registered the fd or socket with the socket + * manager as part of if_register_receive(). + */ + for (tmp = interfaces; tmp; tmp = tmp -> next) { + /* not if it's been registered before */ + if (tmp -> flags & INTERFACE_RUNNING) + continue; + if (tmp -> rfdesc == -1) + continue; + switch (local_family) { +#ifdef DHCPv6 + case AF_INET6: + status = omapi_register_io_object((omapi_object_t *)tmp, + if_readsocket, + 0, got_one_v6, 0, 0); + break; +#endif /* DHCPv6 */ + case AF_INET: + default: + status = omapi_register_io_object((omapi_object_t *)tmp, + if_readsocket, + 0, got_one, 0, 0); + break; + } + + if (status != ISC_R_SUCCESS) + log_fatal ("Can't register I/O handle for %s: %s", + tmp -> name, isc_result_totext (status)); + +#if defined(DHCPv6) + /* Only register the first interface for V6, since + * servers and relays all use the same socket. + * XXX: This has some messy side effects if we start + * dynamically adding and removing interfaces, but + * we're well beyond that point in terms of mess. + */ + if (((state == DISCOVER_SERVER) || (state == DISCOVER_RELAY)) && + (local_family == AF_INET6)) + break; +#endif + } /* for (tmp = interfaces; ... */ + + if (state == DISCOVER_SERVER && wifcount == 0) { + log_info ("%s", ""); + log_fatal ("Not configured to listen on any interfaces!"); + } + + if ((local_family == AF_INET) && !setup_fallback) { + setup_fallback = 1; + maybe_setup_fallback(); + } + +#if defined (F_SETFD) + if (fallback_interface) { + if (fcntl (fallback_interface -> rfdesc, F_SETFD, 1) < 0) + log_error ("Can't set close-on-exec on fallback: %m"); + if (fallback_interface -> rfdesc != fallback_interface -> wfdesc) { + if (fcntl (fallback_interface -> wfdesc, F_SETFD, 1) < 0) + log_error ("Can't set close-on-exec on fallback: %m"); + } + } +#endif /* F_SETFD */ +} + +int if_readsocket (h) + omapi_object_t *h; +{ + struct interface_info *ip; + + if (h -> type != dhcp_type_interface) + return -1; + ip = (struct interface_info *)h; + return ip -> rfdesc; +} + +int setup_fallback (struct interface_info **fp, const char *file, int line) +{ + isc_result_t status; + + status = interface_allocate (&fallback_interface, file, line); + if (status != ISC_R_SUCCESS) + log_fatal ("Error allocating fallback interface: %s", + isc_result_totext (status)); + strcpy (fallback_interface -> name, "fallback"); + if (dhcp_interface_setup_hook) + (*dhcp_interface_setup_hook) (fallback_interface, + (struct iaddr *)0); + status = interface_reference (fp, fallback_interface, file, line); + + fallback_interface -> index = -1; + interface_stash (fallback_interface); + return status == ISC_R_SUCCESS; +} + +void reinitialize_interfaces () +{ + struct interface_info *ip; + + for (ip = interfaces; ip; ip = ip -> next) { + if_reinitialize_receive (ip); + if_reinitialize_send (ip); + } + + if (fallback_interface) + if_reinitialize_send (fallback_interface); + + interfaces_invalidated = 1; +} + +isc_result_t got_one (h) + omapi_object_t *h; +{ + struct sockaddr_in from; + struct hardware hfrom; + struct iaddr ifrom; + int result; + union { + unsigned char packbuf [4095]; /* Packet input buffer. + Must be as large as largest + possible MTU. */ + struct dhcp_packet packet; + } u; + struct interface_info *ip; + + if (h -> type != dhcp_type_interface) + return DHCP_R_INVALIDARG; + ip = (struct interface_info *)h; + + again: + if ((result = + receive_packet (ip, u.packbuf, sizeof u, &from, &hfrom)) < 0) { + log_error ("receive_packet failed on %s: %m", ip -> name); + return ISC_R_UNEXPECTED; + } + if (result == 0) + return ISC_R_UNEXPECTED; + + /* + * If we didn't at least get the fixed portion of the BOOTP + * packet, drop the packet. + * Previously we allowed packets with no sname or filename + * as we were aware of at least one client that did. But + * a bug caused short packets to not work and nobody has + * complained, it seems rational to tighten up that + * restriction. + */ + if (result < DHCP_FIXED_NON_UDP) + return ISC_R_UNEXPECTED; + +#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO) + { + /* We retrieve the ifindex from the unused hfrom variable */ + unsigned int ifindex; + + memcpy(&ifindex, hfrom.hbuf, sizeof (ifindex)); + + /* + * Seek forward from the first interface to find the matching + * source interface by interface index. + */ + ip = interfaces; + while ((ip != NULL) && (if_nametoindex(ip->name) != ifindex)) + ip = ip->next; + if (ip == NULL) + return ISC_R_NOTFOUND; + } +#endif + + if (bootp_packet_handler) { + ifrom.len = 4; + memcpy (ifrom.iabuf, &from.sin_addr, ifrom.len); + + (*bootp_packet_handler) (ip, &u.packet, (unsigned)result, + from.sin_port, ifrom, &hfrom); + } + + /* If there is buffered data, read again. This is for, e.g., + bpf, which may return two packets at once. */ + if (ip -> rbuf_offset != ip -> rbuf_len) + goto again; + return ISC_R_SUCCESS; +} + +#ifdef DHCPv6 +isc_result_t +got_one_v6(omapi_object_t *h) { + struct sockaddr_in6 from; + struct in6_addr to; + struct iaddr ifrom; + int result; + char buf[65536]; /* maximum size for a UDP packet is 65536 */ + struct interface_info *ip; + int is_unicast; + unsigned int if_idx = 0; + + if (h->type != dhcp_type_interface) { + return DHCP_R_INVALIDARG; + } + ip = (struct interface_info *)h; + + result = receive_packet6(ip, (unsigned char *)buf, sizeof(buf), + &from, &to, &if_idx); + if (result < 0) { + log_error("receive_packet6() failed on %s: %m", ip->name); + return ISC_R_UNEXPECTED; + } + + /* 0 is 'any' interface. */ + if (if_idx == 0) + return ISC_R_NOTFOUND; + + if (dhcpv6_packet_handler != NULL) { + /* + * If a packet is not multicast, we assume it is unicast. + */ + if (IN6_IS_ADDR_MULTICAST(&to)) { + is_unicast = ISC_FALSE; + } else { + is_unicast = ISC_TRUE; + } + + ifrom.len = 16; + memcpy(ifrom.iabuf, &from.sin6_addr, ifrom.len); + + /* Seek forward to find the matching source interface. */ + ip = interfaces; + while ((ip != NULL) && (if_nametoindex(ip->name) != if_idx)) + ip = ip->next; + + if (ip == NULL) + return ISC_R_NOTFOUND; + + (*dhcpv6_packet_handler)(ip, buf, + result, from.sin6_port, + &ifrom, is_unicast); + } + + return ISC_R_SUCCESS; +} +#endif /* DHCPv6 */ + +isc_result_t dhcp_interface_set_value (omapi_object_t *h, + omapi_object_t *id, + omapi_data_string_t *name, + omapi_typed_data_t *value) +{ + struct interface_info *interface; + isc_result_t status; + + if (h -> type != dhcp_type_interface) + return DHCP_R_INVALIDARG; + interface = (struct interface_info *)h; + + if (!omapi_ds_strcmp (name, "name")) { + if ((value -> type == omapi_datatype_data || + value -> type == omapi_datatype_string) && + value -> u.buffer.len < sizeof interface -> name) { + memcpy (interface -> name, + value -> u.buffer.value, + value -> u.buffer.len); + interface -> name [value -> u.buffer.len] = 0; + } else + return DHCP_R_INVALIDARG; + return ISC_R_SUCCESS; + } + + /* Try to find some inner object that can take the value. */ + if (h -> inner && h -> inner -> type -> set_value) { + status = ((*(h -> inner -> type -> set_value)) + (h -> inner, id, name, value)); + if (status == ISC_R_SUCCESS || status == DHCP_R_UNCHANGED) + return status; + } + + return ISC_R_NOTFOUND; +} + + +isc_result_t dhcp_interface_get_value (omapi_object_t *h, + omapi_object_t *id, + omapi_data_string_t *name, + omapi_value_t **value) +{ + return ISC_R_NOTIMPLEMENTED; +} + +isc_result_t dhcp_interface_destroy (omapi_object_t *h, + const char *file, int line) +{ + struct interface_info *interface; + + if (h -> type != dhcp_type_interface) + return DHCP_R_INVALIDARG; + interface = (struct interface_info *)h; + + if (interface -> ifp) { + dfree (interface -> ifp, file, line); + interface -> ifp = 0; + } + if (interface -> next) + interface_dereference (&interface -> next, file, line); + if (interface -> rbuf) { + dfree (interface -> rbuf, file, line); + interface -> rbuf = (unsigned char *)0; + } + if (interface -> client) + interface -> client = (struct client_state *)0; + + if (interface -> shared_network) + omapi_object_dereference ((omapi_object_t **) + &interface -> shared_network, MDL); + + return ISC_R_SUCCESS; +} + +isc_result_t dhcp_interface_signal_handler (omapi_object_t *h, + const char *name, va_list ap) +{ + struct interface_info *ip, *interface; + isc_result_t status; + + if (h -> type != dhcp_type_interface) + return DHCP_R_INVALIDARG; + interface = (struct interface_info *)h; + + /* If it's an update signal, see if the interface is dead right + now, or isn't known at all, and if that's the case, revive it. */ + if (!strcmp (name, "update")) { + for (ip = dummy_interfaces; ip; ip = ip -> next) + if (ip == interface) + break; + if (ip && dhcp_interface_startup_hook) + return (*dhcp_interface_startup_hook) (ip); + + for (ip = interfaces; ip; ip = ip -> next) + if (ip == interface) + break; + if (!ip && dhcp_interface_startup_hook) + return (*dhcp_interface_startup_hook) (ip); + } + + /* Try to find some inner object that can take the value. */ + if (h -> inner && h -> inner -> type -> signal_handler) { + status = ((*(h -> inner -> type -> signal_handler)) + (h -> inner, name, ap)); + if (status == ISC_R_SUCCESS) + return status; + } + return ISC_R_NOTFOUND; +} + +isc_result_t dhcp_interface_stuff_values (omapi_object_t *c, + omapi_object_t *id, + omapi_object_t *h) +{ + struct interface_info *interface; + isc_result_t status; + + if (h -> type != dhcp_type_interface) + return DHCP_R_INVALIDARG; + interface = (struct interface_info *)h; + + /* Write out all the values. */ + + status = omapi_connection_put_name (c, "state"); + if (status != ISC_R_SUCCESS) + return status; + if ((interface->flags & INTERFACE_REQUESTED) != 0) + status = omapi_connection_put_string (c, "up"); + else + status = omapi_connection_put_string (c, "down"); + if (status != ISC_R_SUCCESS) + return status; + + /* Write out the inner object, if any. */ + if (h -> inner && h -> inner -> type -> stuff_values) { + status = ((*(h -> inner -> type -> stuff_values)) + (c, id, h -> inner)); + if (status == ISC_R_SUCCESS) + return status; + } + + return ISC_R_SUCCESS; +} + +isc_result_t dhcp_interface_lookup (omapi_object_t **ip, + omapi_object_t *id, + omapi_object_t *ref) +{ + omapi_value_t *tv = (omapi_value_t *)0; + isc_result_t status; + struct interface_info *interface; + + if (!ref) + return DHCP_R_NOKEYS; + + /* First see if we were sent a handle. */ + status = omapi_get_value_str (ref, id, "handle", &tv); + if (status == ISC_R_SUCCESS) { + status = omapi_handle_td_lookup (ip, tv -> value); + + omapi_value_dereference (&tv, MDL); + if (status != ISC_R_SUCCESS) + return status; + + /* Don't return the object if the type is wrong. */ + if ((*ip) -> type != dhcp_type_interface) { + omapi_object_dereference (ip, MDL); + return DHCP_R_INVALIDARG; + } + } + + /* Now look for an interface name. */ + status = omapi_get_value_str (ref, id, "name", &tv); + if (status == ISC_R_SUCCESS) { + char *s; + unsigned len; + for (interface = interfaces; interface; + interface = interface -> next) { + s = memchr (interface -> name, 0, IFNAMSIZ); + if (s) + len = s - &interface -> name [0]; + else + len = IFNAMSIZ; + if ((tv -> value -> u.buffer.len == len && + !memcmp (interface -> name, + (char *)tv -> value -> u.buffer.value, + len))) + break; + } + if (!interface) { + for (interface = dummy_interfaces; + interface; interface = interface -> next) { + s = memchr (interface -> name, 0, IFNAMSIZ); + if (s) + len = s - &interface -> name [0]; + else + len = IFNAMSIZ; + if ((tv -> value -> u.buffer.len == len && + !memcmp (interface -> name, + (char *) + tv -> value -> u.buffer.value, + len))) + break; + } + } + + omapi_value_dereference (&tv, MDL); + if (*ip && *ip != (omapi_object_t *)interface) { + omapi_object_dereference (ip, MDL); + return DHCP_R_KEYCONFLICT; + } else if (!interface) { + if (*ip) + omapi_object_dereference (ip, MDL); + return ISC_R_NOTFOUND; + } else if (!*ip) + omapi_object_reference (ip, + (omapi_object_t *)interface, + MDL); + } + + /* If we get to here without finding an interface, no valid key was + specified. */ + if (!*ip) + return DHCP_R_NOKEYS; + return ISC_R_SUCCESS; +} + +/* actually just go discover the interface */ +isc_result_t dhcp_interface_create (omapi_object_t **lp, + omapi_object_t *id) +{ + struct interface_info *hp; + isc_result_t status; + + hp = (struct interface_info *)0; + status = interface_allocate (&hp, MDL); + if (status != ISC_R_SUCCESS) + return status; + hp -> flags = INTERFACE_REQUESTED; + status = interface_reference ((struct interface_info **)lp, hp, MDL); + interface_dereference (&hp, MDL); + return status; +} + +isc_result_t dhcp_interface_remove (omapi_object_t *lp, + omapi_object_t *id) +{ + struct interface_info *interface, *ip, *last; + + interface = (struct interface_info *)lp; + + /* remove from interfaces */ + last = 0; + for (ip = interfaces; ip; ip = ip -> next) { + if (ip == interface) { + if (last) { + interface_dereference (&last -> next, MDL); + if (ip -> next) + interface_reference (&last -> next, + ip -> next, MDL); + } else { + interface_dereference (&interfaces, MDL); + if (ip -> next) + interface_reference (&interfaces, + ip -> next, MDL); + } + if (ip -> next) + interface_dereference (&ip -> next, MDL); + break; + } + last = ip; + } + if (!ip) + return ISC_R_NOTFOUND; + + /* add the interface to the dummy_interface list */ + if (dummy_interfaces) { + interface_reference (&interface -> next, + dummy_interfaces, MDL); + interface_dereference (&dummy_interfaces, MDL); + } + interface_reference (&dummy_interfaces, interface, MDL); + + /* do a DHCPRELEASE */ + if (dhcp_interface_shutdown_hook) + (*dhcp_interface_shutdown_hook) (interface); + + /* remove the io object */ + omapi_unregister_io_object ((omapi_object_t *)interface); + + switch(local_family) { +#ifdef DHCPv6 + case AF_INET6: + if_deregister6(interface); + break; +#endif /* DHCPv6 */ + case AF_INET: + default: + if_deregister_send(interface); + if_deregister_receive(interface); + break; + } + + return ISC_R_SUCCESS; +} + +void interface_stash (struct interface_info *tptr) +{ + struct interface_info **vec; + int delta; + + /* If the registerer didn't assign an index, assign one now. */ + if (tptr -> index == -1) { + tptr -> index = interface_count++; + while (tptr -> index < interface_max && + interface_vector [tptr -> index]) + tptr -> index = interface_count++; + } + + if (interface_max <= tptr -> index) { + delta = tptr -> index - interface_max + 10; + vec = dmalloc ((interface_max + delta) * + sizeof (struct interface_info *), MDL); + if (!vec) + return; + memset (&vec [interface_max], 0, + (sizeof (struct interface_info *)) * delta); + interface_max += delta; + if (interface_vector) { + memcpy (vec, interface_vector, + (interface_count * + sizeof (struct interface_info *))); + dfree (interface_vector, MDL); + } + interface_vector = vec; + } + interface_reference (&interface_vector [tptr -> index], tptr, MDL); + if (tptr -> index >= interface_count) + interface_count = tptr -> index + 1; +#if defined (TRACING) + trace_interface_register (interface_trace, tptr); +#endif +} + +void interface_snorf (struct interface_info *tmp, int ir) +{ + tmp -> circuit_id = (u_int8_t *)tmp -> name; + tmp -> circuit_id_len = strlen (tmp -> name); + tmp -> remote_id = 0; + tmp -> remote_id_len = 0; + tmp -> flags = ir; + if (interfaces) { + interface_reference (&tmp -> next, + interfaces, MDL); + interface_dereference (&interfaces, MDL); + } + interface_reference (&interfaces, tmp, MDL); +} diff --git a/common/dispatch.c b/common/dispatch.c new file mode 100644 index 0000000..c75a003 --- /dev/null +++ b/common/dispatch.c @@ -0,0 +1,435 @@ +/* dispatch.c + + Network input dispatcher... */ + +/* + * Copyright (c) 2004-2011,2013 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" + +#include <sys/time.h> + +struct timeout *timeouts; +static struct timeout *free_timeouts; + +void set_time(TIME t) +{ + /* Do any outstanding timeouts. */ + if (cur_tv . tv_sec != t) { + cur_tv . tv_sec = t; + cur_tv . tv_usec = 0; + process_outstanding_timeouts ((struct timeval *)0); + } +} + +struct timeval *process_outstanding_timeouts (struct timeval *tvp) +{ + /* Call any expired timeouts, and then if there's + still a timeout registered, time out the select + call then. */ + another: + if (timeouts) { + struct timeout *t; + if ((timeouts -> when . tv_sec < cur_tv . tv_sec) || + ((timeouts -> when . tv_sec == cur_tv . tv_sec) && + (timeouts -> when . tv_usec <= cur_tv . tv_usec))) { + t = timeouts; + timeouts = timeouts -> next; + (*(t -> func)) (t -> what); + if (t -> unref) + (*t -> unref) (&t -> what, MDL); + t -> next = free_timeouts; + free_timeouts = t; + goto another; + } + if (tvp) { + tvp -> tv_sec = timeouts -> when . tv_sec; + tvp -> tv_usec = timeouts -> when . tv_usec; + } + return tvp; + } else + return (struct timeval *)0; +} + +/* Wait for packets to come in using select(). When one does, call + receive_packet to receive the packet and possibly strip hardware + addressing information from it, and then call through the + bootp_packet_handler hook to try to do something with it. */ + +/* + * Use the DHCP timeout list as a place to store DHCP specific + * information, but use the ISC timer system to actually dispatch + * the events. + * + * There are several things that the DHCP timer code does that the + * ISC code doesn't: + * 1) It allows for negative times + * 2) The cancel arguments are different. The DHCP code uses the + * function and data to find the proper timer to cancel while the + * ISC code uses a pointer to the timer. + * 3) The DHCP code includes provision for incrementing and decrementing + * a reference counter associated with the data. + * The first one is fairly easy to fix but will take some time to go throuh + * the callers and update them. The second is also not all that difficult + * in concept - add a pointer to the appropriate structures to hold a pointer + * to the timer and use that. The complications arise in trying to ensure + * that all of the corner cases are covered. The last one is potentially + * more painful and requires more investigation. + * + * The plan is continue with the older DHCP calls and timer list. The + * calls will continue to manipulate the list but will also pass a + * timer to the ISC timer code for the actual dispatch. Later, if desired, + * we can go back and modify the underlying calls to use the ISC + * timer functions directly without requiring all of the code to change + * at the same time. + */ + +void +dispatch(void) +{ + isc_result_t status; + + do { + status = isc_app_ctxrun(dhcp_gbl_ctx.actx); + + /* + * isc_app_ctxrun can be stopped by receiving a + * signal. It will return ISC_R_RELOAD in that + * case. That is a normal behavior. + */ + + if (status == ISC_R_RELOAD) { + /* + * dhcp_set_control_state() will do the job. + * Note its first argument is ignored. + */ + status = dhcp_set_control_state(server_shutdown, + server_shutdown); + if (status == ISC_R_SUCCESS) + status = ISC_R_RELOAD; + } + } while (status == ISC_R_RELOAD); + + log_fatal ("Dispatch routine failed: %s -- exiting", + isc_result_totext (status)); +} + +void +isclib_timer_callback(isc_task_t *taskp, + isc_event_t *eventp) +{ + struct timeout *t = (struct timeout *)eventp->ev_arg; + struct timeout *q, *r; + + /* Get the current time... */ + gettimeofday (&cur_tv, (struct timezone *)0); + + /* + * Find the timeout on the dhcp list and remove it. + * As the list isn't ordered we search the entire list + */ + + r = NULL; + for (q = timeouts; q; q = q->next) { + if (q == t) { + if (r) + r->next = q->next; + else + timeouts = q->next; + break; + } + r = q; + } + + /* + * The timer should always be on the list. If it is we do + * the work and detach the timer block, if not we log an error. + * In both cases we attempt free the ISC event and continue + * processing. + */ + + if (q != NULL) { + /* call the callback function */ + (*(q->func)) (q->what); + if (q->unref) { + (*q->unref) (&q->what, MDL); + } + q->next = free_timeouts; + isc_timer_detach(&q->isc_timeout); + free_timeouts = q; + } else { + /* + * Hmm, we should clean up the timer structure but aren't + * sure about the pointer to the timer block we got so + * don't try to - may change this to a log_fatal + */ + log_error("Error finding timer structure"); + } + + isc_event_free(&eventp); + return; +} + +/* maximum value for usec */ +#define USEC_MAX 1000000 +#define DHCP_SEC_MAX 0xFFFFFFFF + +void add_timeout (when, where, what, ref, unref) + struct timeval *when; + void (*where) (void *); + void *what; + tvref_t ref; + tvunref_t unref; +{ + struct timeout *t, *q; + int usereset = 0; + isc_result_t status; + int64_t sec; + int usec; + isc_interval_t interval; + isc_time_t expires; + + /* See if this timeout supersedes an existing timeout. */ + t = (struct timeout *)0; + for (q = timeouts; q; q = q->next) { + if ((where == NULL || q->func == where) && + q->what == what) { + if (t) + t->next = q->next; + else + timeouts = q->next; + usereset = 1; + break; + } + t = q; + } + + /* If we didn't supersede a timeout, allocate a timeout + structure now. */ + if (!q) { + if (free_timeouts) { + q = free_timeouts; + free_timeouts = q->next; + } else { + q = ((struct timeout *) + dmalloc(sizeof(struct timeout), MDL)); + if (!q) { + log_fatal("add_timeout: no memory!"); + } + } + memset(q, 0, sizeof *q); + q->func = where; + q->ref = ref; + q->unref = unref; + if (q->ref) + (*q->ref)(&q->what, what, MDL); + else + q->what = what; + } + + /* + * The value passed in is a time from an epoch but we need a relative + * time so we need to do some math to try and recover the period. + * This is complicated by the fact that not all of the calls cared + * about the usec value, if it's zero we assume the caller didn't care. + * + * The ISC timer library doesn't seem to like negative values + * and can't accept any values above 4G-1 seconds so we limit + * the values to 0 <= value < 4G-1. We do it before + * checking the trace option so that both the trace code and + * the working code use the same values. + */ + + sec = when->tv_sec - cur_tv.tv_sec; + usec = when->tv_usec - cur_tv.tv_usec; + + if ((when->tv_usec != 0) && (usec < 0)) { + sec--; + usec += USEC_MAX; + } + + if (sec < 0) { + sec = 0; + usec = 0; + } else if (sec > DHCP_SEC_MAX) { + log_error("Timeout requested too large " + "reducing to 2^^32-1"); + sec = DHCP_SEC_MAX; + usec = 0; + } else if (usec < 0) { + usec = 0; + } else if (usec >= USEC_MAX) { + usec = USEC_MAX - 1; + } + + /* + * This is necessary for the tracing code but we put it + * here in case we want to compare timing information + * for some reason, like debugging. + */ + q->when.tv_sec = cur_tv.tv_sec + (sec & DHCP_SEC_MAX); + q->when.tv_usec = usec; + +#if defined (TRACING) + if (trace_playback()) { + /* + * If we are doing playback we need to handle the timers + * within this code rather than having the isclib handle + * them for us. We need to keep the timer list in order + * to allow us to find the ones to timeout. + * + * By using a different timer setup in the playback we may + * have variations between the orginal and the playback but + * it's the best we can do for now. + */ + + /* Beginning of list? */ + if (!timeouts || (timeouts->when.tv_sec > q-> when.tv_sec) || + ((timeouts->when.tv_sec == q->when.tv_sec) && + (timeouts->when.tv_usec > q->when.tv_usec))) { + q->next = timeouts; + timeouts = q; + return; + } + + /* Middle of list? */ + for (t = timeouts; t->next; t = t->next) { + if ((t->next->when.tv_sec > q->when.tv_sec) || + ((t->next->when.tv_sec == q->when.tv_sec) && + (t->next->when.tv_usec > q->when.tv_usec))) { + q->next = t->next; + t->next = q; + return; + } + } + + /* End of list. */ + t->next = q; + q->next = (struct timeout *)0; + return; + } +#endif + /* + * Don't bother sorting the DHCP list, just add it to the front. + * Eventually the list should be removed as we migrate the callers + * to the native ISC timer functions, if it becomes a performance + * problem before then we may need to order the list. + */ + q->next = timeouts; + timeouts = q; + + isc_interval_set(&interval, sec & DHCP_SEC_MAX, usec * 1000); + status = isc_time_nowplusinterval(&expires, &interval); + if (status != ISC_R_SUCCESS) { + /* + * The system time function isn't happy or returned + * a value larger than isc_time_t can hold. + */ + log_fatal("Unable to set up timer: %s", + isc_result_totext(status)); + } + + if (usereset == 0) { + status = isc_timer_create(dhcp_gbl_ctx.timermgr, + isc_timertype_once, &expires, + NULL, dhcp_gbl_ctx.task, + isclib_timer_callback, + (void *)q, &q->isc_timeout); + } else { + status = isc_timer_reset(q->isc_timeout, + isc_timertype_once, &expires, + NULL, 0); + } + + /* If it fails log an error and die */ + if (status != ISC_R_SUCCESS) { + log_fatal("Unable to add timeout to isclib\n"); + } + + return; +} + +void cancel_timeout (where, what) + void (*where) (void *); + void *what; +{ + struct timeout *t, *q; + + /* Look for this timeout on the list, and unlink it if we find it. */ + t = (struct timeout *)0; + for (q = timeouts; q; q = q -> next) { + if (q->func == where && q->what == what) { + if (t) + t->next = q->next; + else + timeouts = q->next; + break; + } + t = q; + } + + /* + * If we found the timeout, cancel it and put it on the free list. + * The TRACING stuff is ugly but we don't add a timer when doing + * playback so we don't want to remove them then either. + */ + if (q) { +#if defined (TRACING) + if (!trace_playback()) { +#endif + isc_timer_detach(&q->isc_timeout); +#if defined (TRACING) + } +#endif + + if (q->unref) + (*q->unref) (&q->what, MDL); + q->next = free_timeouts; + free_timeouts = q; + } +} + +#if defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT) +void cancel_all_timeouts () +{ + struct timeout *t, *n; + for (t = timeouts; t; t = n) { + n = t->next; + isc_timer_detach(&t->isc_timeout); + if (t->unref && t->what) + (*t->unref) (&t->what, MDL); + t->next = free_timeouts; + free_timeouts = t; + } +} + +void relinquish_timeouts () +{ + struct timeout *t, *n; + for (t = free_timeouts; t; t = n) { + n = t->next; + dfree(t, MDL); + } +} +#endif diff --git a/common/dlpi.c b/common/dlpi.c new file mode 100644 index 0000000..c34adc3 --- /dev/null +++ b/common/dlpi.c @@ -0,0 +1,1414 @@ +/* dlpi.c + + Data Link Provider Interface (DLPI) network interface code. */ + +/* + * Copyright (c) 2009-2011,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004,2007 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + * This software was written for Internet Systems Consortium + * by Eric James Negaard, <lmdejn@lmd.ericsson.se>. To learn more about + * Internet Systems Consortium, see ``https://www.isc.org''. + * + * Joost Mulders has also done considerable work in debugging the DLPI API + * support on Solaris and getting this code to work properly on a variety + * of different Solaris platforms. + */ + +/* + * Based largely in part to the existing NIT code in nit.c. + * + * This code has been developed and tested on sparc-based machines running + * SunOS 5.5.1, with le and hme network interfaces. It should be pretty + * generic, though. + */ + +/* + * Implementation notes: + * + * I first tried to write this code to the "vanilla" DLPI 2.0 API. + * It worked on a Sun Ultra-1 with a hme interface, but didn't work + * on Sun SparcStation 5's with "le" interfaces (the packets sent out + * via dlpiunitdatareq contained an Ethernet type of 0x0000 instead + * of the expected 0x0800). + * + * Therefore I added the "DLPI_RAW" code which is a Sun extension to + * the DLPI standard. This code works on both of the above machines. + * This is configurable in the OS-dependent include file by defining + * USE_DLPI_RAW. + * + * It quickly became apparant that I should also use the "pfmod" + * STREAMS module to cut down on the amount of user level packet + * processing. I don't know how widely available "pfmod" is, so it's + * use is conditionally included. This is configurable in the + * OS-dependent include file by defining USE_DLPI_PFMOD. + * + * A major quirk on the Sun's at least, is that no packets seem to get + * sent out the interface until six seconds after the interface is + * first "attached" to [per system reboot] (it's actually from when + * the interface is attached, not when it is plumbed, so putting a + * sleep into the dhclient-script at PREINIT time doesn't help). I + * HAVE tried, without success to poll the fd to see when it is ready + * for writing. This doesn't help at all. If the sleeps are not done, + * the initial DHCPREQUEST or DHCPDISCOVER never gets sent out, so + * I've put them here, when register_send and register_receive are + * called (split up into two three-second sleeps between the notices, + * so that it doesn't seem like so long when you're watching :-). The + * amount of time to sleep is configurable in the OS-dependent include + * file by defining DLPI_FIRST_SEND_WAIT to be the number of seconds + * to sleep. + */ + +/* + * The Open Group Technical Standard can be found here: + * http://www.opengroup.org/onlinepubs/009618899/index.htm + * + * The HP DLPI Programmer's Guide can be found here: + * http://docs.hp.com/en/B2355-90139/index.html + */ + +#include "dhcpd.h" + +#if defined (USE_DLPI_SEND) || defined (USE_DLPI_RECEIVE) || \ + defined(USE_DLPI_HWADDR) + +# include <sys/ioctl.h> +# include <sys/time.h> +# include <sys/dlpi.h> +# include <stropts.h> +# ifdef USE_DLPI_PFMOD +# include <sys/pfmod.h> +# endif +#include <poll.h> +#include <errno.h> + +# include <netinet/in_systm.h> +# include "includes/netinet/ip.h" +# include "includes/netinet/udp.h" +# include "includes/netinet/if_ether.h" + +# ifdef USE_DLPI_PFMOD +# ifdef USE_DLPI_RAW +# define DLPI_MODNAME "DLPI+RAW+PFMOD" +# else +# define DLPI_MODNAME "DLPI+PFMOD" +# endif +# else +# ifdef USE_DLPI_RAW +# define DLPI_MODNAME "DLPI+RAW" +# else +# define DLPI_MODNAME "DLPI" +# endif +# endif + +# ifndef ABS +# define ABS(x) ((x) >= 0 ? (x) : 0-(x)) +# endif + +#if defined(USE_DLPI_PFMOD) || defined(USE_DLPI_RAW) +static int strioctl (int fd, int cmd, int timeout, int len, char *dp); +#endif + +#define DLPI_MAXDLBUF 8192 /* Buffer size */ +#define DLPI_MAXDLADDR 1024 /* Max address size */ +#define DLPI_DEVDIR "/dev/" /* Device directory */ + +static int dlpiopen(const char *ifname); +static int dlpiunit (char *ifname); +static int dlpiinforeq (int fd); +static int dlpiphysaddrreq (int fd, unsigned long addrtype); +static int dlpiattachreq (int fd, unsigned long ppa); +static int dlpibindreq (int fd, unsigned long sap, unsigned long max_conind, + unsigned long service_mode, unsigned long conn_mgmt, + unsigned long xidtest); +#if defined(UNUSED_DLPI_INTERFACE) +/* These functions are unused at present, but may be used at a later date. + * defined out to avoid compiler warnings about unused static functions. + */ +static int dlpidetachreq (int fd); +static int dlpiunbindreq (int fd); +#endif +static int dlpiokack (int fd, char *bufp); +static int dlpiinfoack (int fd, char *bufp); +static int dlpiphysaddrack (int fd, char *bufp); +static int dlpibindack (int fd, char *bufp); +#if defined(USE_DLPI_SEND) || defined(USE_DLPI_RECEIVE) +/* These functions are not used if we're only sourcing the get_hw_addr() + * function (for USE_SOCKETS). + */ +static int dlpiunitdatareq (int fd, unsigned char *addr, int addrlen, + unsigned long minpri, unsigned long maxpri, + unsigned char *data, int datalen); +static int dlpiunitdataind (int fd, + unsigned char *dstaddr, + unsigned long *dstaddrlen, + unsigned char *srcaddr, + unsigned long *srcaddrlen, + unsigned long *grpaddr, + unsigned char *data, + int datalen); +#endif /* !USE_DLPI_HWADDR: USE_DLPI_SEND || USE_DLPI_RECEIVE */ +static int expected (unsigned long prim, union DL_primitives *dlp, + int msgflags); +static int strgetmsg (int fd, struct strbuf *ctlp, struct strbuf *datap, + int *flagsp, char *caller); + +/* Reinitializes the specified interface after an address change. This + is not required for packet-filter APIs. */ + +#ifdef USE_DLPI_SEND +void if_reinitialize_send (info) + struct interface_info *info; +{ +} +#endif + +#ifdef USE_DLPI_RECEIVE +void if_reinitialize_receive (info) + struct interface_info *info; +{ +} +#endif + +/* Called by get_interface_list for each interface that's discovered. + Opens a packet filter for each interface and adds it to the select + mask. */ + +int if_register_dlpi (info) + struct interface_info *info; +{ + int sock; + int unit; + long buf [DLPI_MAXDLBUF]; + union DL_primitives *dlp; + + dlp = (union DL_primitives *)buf; + + /* Open a DLPI device */ + if ((sock = dlpiopen (info -> name)) < 0) { + log_fatal ("Can't open DLPI device for %s: %m", info -> name); + } + + /* + * Submit a DL_INFO_REQ request, to find the dl_mac_type and + * dl_provider_style + */ + if (dlpiinforeq(sock) < 0 || dlpiinfoack(sock, (char *)buf) < 0) { + log_fatal ("Can't get DLPI MAC type for %s: %m", info -> name); + } else { + switch (dlp -> info_ack.dl_mac_type) { + case DL_CSMACD: /* IEEE 802.3 */ + case DL_ETHER: + info -> hw_address.hbuf [0] = HTYPE_ETHER; + break; + /* adding token ring 5/1999 - mayer@ping.at */ + case DL_TPR: + info -> hw_address.hbuf [0] = HTYPE_IEEE802; + break; + case DL_FDDI: + info -> hw_address.hbuf [0] = HTYPE_FDDI; + break; + default: + log_fatal("%s: unsupported DLPI MAC type %lu", info->name, + (unsigned long)dlp->info_ack.dl_mac_type); + break; + } + /* + * copy the sap length and broadcast address of this interface + * to interface_info. This fixes nothing but seemed nicer than to + * assume -2 and ffffff. + */ + info -> dlpi_sap_length = dlp -> info_ack.dl_sap_length; + info -> dlpi_broadcast_addr.hlen = + dlp -> info_ack.dl_brdcst_addr_length; + memcpy (info -> dlpi_broadcast_addr.hbuf, + (char *)dlp + dlp -> info_ack.dl_brdcst_addr_offset, + dlp -> info_ack.dl_brdcst_addr_length); + } + + if (dlp -> info_ack.dl_provider_style == DL_STYLE2) { + /* + * Attach to the device. If this fails, the device + * does not exist. + */ + unit = dlpiunit (info -> name); + + if (dlpiattachreq (sock, unit) < 0 + || dlpiokack (sock, (char *)buf) < 0) { + log_fatal ("Can't attach DLPI device for %s: %m", info -> name); + } + } + + /* + * Bind to the IP service access point (SAP), connectionless (CLDLS). + */ + if (dlpibindreq (sock, ETHERTYPE_IP, 0, DL_CLDLS, 0, 0) < 0 + || dlpibindack (sock, (char *)buf) < 0) { + log_fatal ("Can't bind DLPI device for %s: %m", info -> name); + } + + /* + * Submit a DL_PHYS_ADDR_REQ request, to find + * the hardware address + */ + if (dlpiphysaddrreq (sock, DL_CURR_PHYS_ADDR) < 0 + || dlpiphysaddrack (sock, (char *)buf) < 0) { + log_fatal ("Can't get DLPI hardware address for %s: %m", + info -> name); + } + + info -> hw_address.hlen = dlp -> physaddr_ack.dl_addr_length + 1; + memcpy (&info -> hw_address.hbuf [1], + (char *)buf + dlp -> physaddr_ack.dl_addr_offset, + dlp -> physaddr_ack.dl_addr_length); + +#ifdef USE_DLPI_RAW + if (strioctl (sock, DLIOCRAW, INFTIM, 0, 0) < 0) { + log_fatal ("Can't set DLPI RAW mode for %s: %m", + info -> name); + } +#endif + +#ifdef USE_DLPI_PFMOD + if (ioctl (sock, I_PUSH, "pfmod") < 0) { + log_fatal ("Can't push packet filter onto DLPI for %s: %m", + info -> name); + } +#endif + + return sock; +} + +#if defined(USE_DLPI_PFMOD) || defined(USE_DLPI_RAW) +static int +strioctl (fd, cmd, timeout, len, dp) +int fd; +int cmd; +int timeout; +int len; +char *dp; +{ + struct strioctl sio; + int rslt; + + sio.ic_cmd = cmd; + sio.ic_timout = timeout; + sio.ic_len = len; + sio.ic_dp = dp; + + if ((rslt = ioctl (fd, I_STR, &sio)) < 0) { + return rslt; + } else { + return sio.ic_len; + } +} +#endif /* USE_DPI_PFMOD || USE_DLPI_RAW */ + +#ifdef USE_DLPI_SEND +void if_register_send (info) + struct interface_info *info; +{ + /* If we're using the DLPI API for sending and receiving, + we don't need to register this interface twice. */ +#ifndef USE_DLPI_RECEIVE +# ifdef USE_DLPI_PFMOD + struct packetfilt pf; +# endif + + info -> wfdesc = if_register_dlpi (info); + +# ifdef USE_DLPI_PFMOD + /* Set up an PFMOD filter that rejects everything... */ + pf.Pf_Priority = 0; + pf.Pf_FilterLen = 1; + pf.Pf_Filter [0] = ENF_PUSHZERO; + + /* Install the filter */ + if (strioctl (info -> wfdesc, PFIOCSETF, INFTIM, + sizeof (pf), (char *)&pf) < 0) { + log_fatal ("Can't set PFMOD send filter on %s: %m", info -> name); + } + +# endif /* USE_DLPI_PFMOD */ +#else /* !defined (USE_DLPI_RECEIVE) */ + /* + * If using DLPI for both send and receive, simply re-use + * the read file descriptor that was set up earlier. + */ + info -> wfdesc = info -> rfdesc; +#endif + + if (!quiet_interface_discovery) + log_info ("Sending on DLPI/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); + +#ifdef DLPI_FIRST_SEND_WAIT +/* See the implementation notes at the beginning of this file */ +# ifdef USE_DLPI_RECEIVE + sleep (DLPI_FIRST_SEND_WAIT - (DLPI_FIRST_SEND_WAIT / 2)); +# else + sleep (DLPI_FIRST_SEND_WAIT); +# endif +#endif +} + +void if_deregister_send (info) + struct interface_info *info; +{ + /* If we're using the DLPI API for sending and receiving, + we don't need to register this interface twice. */ +#ifndef USE_DLPI_RECEIVE + close (info -> wfdesc); +#endif + info -> wfdesc = -1; + + if (!quiet_interface_discovery) + log_info ("Disabling output on DLPI/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} +#endif /* USE_DLPI_SEND */ + +#ifdef USE_DLPI_RECEIVE +/* Packet filter program... + XXX Changes to the filter program may require changes to the constant + offsets used in if_register_send to patch the NIT program! XXX */ + +void if_register_receive (info) + struct interface_info *info; +{ +#ifdef USE_DLPI_PFMOD + struct packetfilt pf; + struct ip iphdr; + u_int16_t offset; +#endif + + /* Open a DLPI device and hang it on this interface... */ + info -> rfdesc = if_register_dlpi (info); + +#ifdef USE_DLPI_PFMOD + /* Set up the PFMOD filter program. */ + /* XXX Unlike the BPF filter program, this one won't work if the + XXX IP packet is fragmented or if there are options on the IP + XXX header. */ + pf.Pf_Priority = 0; + pf.Pf_FilterLen = 0; + +#if defined (USE_DLPI_RAW) +# define ETHER_H_PREFIX (14) /* sizeof (ethernet_header) */ + /* + * ethertype == ETHERTYPE_IP + */ + offset = 12; + pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHWORD + (offset / 2); + pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT | ENF_CAND; + pf.Pf_Filter [pf.Pf_FilterLen++] = htons (ETHERTYPE_IP); +# else +# define ETHER_H_PREFIX (0) +# endif /* USE_DLPI_RAW */ + /* + * The packets that will be received on this file descriptor + * will be IP packets (due to the SAP that was specified in + * the dlbind call). There will be no ethernet header. + * Therefore, setup the packet filter to check the protocol + * field for UDP, and the destination port number equal + * to the local port. All offsets are relative to the start + * of an IP packet. + */ + + /* + * BOOTPS destination port + */ + offset = ETHER_H_PREFIX + sizeof (iphdr) + sizeof (u_int16_t); + pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHWORD + (offset / 2); + pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT | ENF_CAND; + pf.Pf_Filter [pf.Pf_FilterLen++] = local_port; + + /* + * protocol should be udp. this is a byte compare, test for + * endianess. + */ + offset = ETHER_H_PREFIX + ((u_int8_t *)&(iphdr.ip_p) - (u_int8_t *)&iphdr); + pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHWORD + (offset / 2); + pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT | ENF_AND; + pf.Pf_Filter [pf.Pf_FilterLen++] = htons (0x00FF); + pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT | ENF_CAND; + pf.Pf_Filter [pf.Pf_FilterLen++] = htons (IPPROTO_UDP); + + /* Install the filter... */ + if (strioctl (info -> rfdesc, PFIOCSETF, INFTIM, + sizeof (pf), (char *)&pf) < 0) { + log_fatal ("Can't set PFMOD receive filter on %s: %m", info -> name); + } +#endif /* USE_DLPI_PFMOD */ + + if (!quiet_interface_discovery) + log_info ("Listening on DLPI/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); + +#ifdef DLPI_FIRST_SEND_WAIT +/* See the implementation notes at the beginning of this file */ +# ifdef USE_DLPI_SEND + sleep (DLPI_FIRST_SEND_WAIT / 2); +# else + sleep (DLPI_FIRST_SEND_WAIT); +# endif +#endif +} + +void if_deregister_receive (info) + struct interface_info *info; +{ + /* If we're using the DLPI API for sending and receiving, + we don't need to register this interface twice. */ +#ifndef USE_DLPI_SEND + close (info -> rfdesc); +#endif + info -> rfdesc = -1; + + if (!quiet_interface_discovery) + log_info ("Disabling input on DLPI/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} +#endif /* USE_DLPI_RECEIVE */ + +#ifdef USE_DLPI_SEND +ssize_t send_packet (interface, packet, raw, len, from, to, hto) + struct interface_info *interface; + struct packet *packet; + struct dhcp_packet *raw; + size_t len; + struct in_addr from; + struct sockaddr_in *to; + struct hardware *hto; +{ +#ifdef USE_DLPI_RAW + double hh [32]; + int fudge; +#endif + double ih [1536 / sizeof (double)]; + unsigned char *dbuf = (unsigned char *)ih; + unsigned dbuflen; + unsigned char dstaddr [DLPI_MAXDLADDR]; + unsigned addrlen; + int result; + + if (!strcmp (interface -> name, "fallback")) + return send_fallback (interface, packet, raw, + len, from, to, hto); + + if (hto == NULL && interface->anycast_mac_addr.hlen) + hto = &interface->anycast_mac_addr; + + dbuflen = 0; + + /* Assemble the headers... */ +#ifdef USE_DLPI_RAW + assemble_hw_header (interface, (unsigned char *)hh, &dbuflen, hto); + if (dbuflen > sizeof hh) + log_fatal ("send_packet: hh buffer too small.\n"); + fudge = dbuflen % 4; /* IP header must be word-aligned. */ + memcpy (dbuf + fudge, (unsigned char *)hh, dbuflen); + dbuflen += fudge; +#endif + assemble_udp_ip_header (interface, dbuf, &dbuflen, from.s_addr, + to -> sin_addr.s_addr, to -> sin_port, + (unsigned char *)raw, len); + + /* Copy the data into the buffer (yuk). */ + memcpy (dbuf + dbuflen, raw, len); + dbuflen += len; + +#ifdef USE_DLPI_RAW + result = write (interface -> wfdesc, dbuf + fudge, dbuflen - fudge); +#else + + /* + * Setup the destination address (DLSAP) in dstaddr + * + * If sap_length < 0 we must deliver the DLSAP as phys+sap. + * If sap_length > 0 we must deliver the DLSAP as sap+phys. + * + * sap = Service Access Point == ETHERTYPE_IP + * sap + datalink address is called DLSAP in dlpi speak. + */ + { /* ENCODE DLSAP */ + unsigned char phys [DLPI_MAXDLADDR]; + unsigned char sap [4]; + int sap_len = interface -> dlpi_sap_length; + int phys_len = interface -> hw_address.hlen - 1; + + /* sap = htons (ETHERTYPE_IP) kludge */ + memset (sap, 0, sizeof (sap)); +# if (BYTE_ORDER == LITTLE_ENDIAN) + sap [0] = 0x00; + sap [1] = 0x08; +# else + sap [0] = 0x08; + sap [1] = 0x00; +# endif + + if (hto && hto -> hlen == interface -> hw_address.hlen) + memcpy ( phys, (char *) &hto -> hbuf [1], phys_len); + else + memcpy ( phys, interface -> dlpi_broadcast_addr.hbuf, + interface -> dlpi_broadcast_addr.hlen); + + if (sap_len < 0) { + memcpy ( dstaddr, phys, phys_len); + memcpy ( (char *) &dstaddr [phys_len], sap, ABS (sap_len)); + } + else { + memcpy ( dstaddr, (void *) sap, sap_len); + memcpy ( (char *) &dstaddr [sap_len], phys, phys_len); + } + addrlen = phys_len + ABS (sap_len); + } /* ENCODE DLSAP */ + + result = dlpiunitdatareq (interface -> wfdesc, dstaddr, addrlen, + 0, 0, dbuf, dbuflen); +#endif /* USE_DLPI_RAW */ + if (result < 0) + log_error ("send_packet: %m"); + return result; +} +#endif /* USE_DLPI_SEND */ + +#ifdef USE_DLPI_RECEIVE +ssize_t receive_packet (interface, buf, len, from, hfrom) + struct interface_info *interface; + unsigned char *buf; + size_t len; + struct sockaddr_in *from; + struct hardware *hfrom; +{ + unsigned char dbuf [1536]; + unsigned char srcaddr [DLPI_MAXDLADDR]; + unsigned long srcaddrlen; + int length = 0; + int offset = 0; + int bufix = 0; + unsigned paylen; + +#ifdef USE_DLPI_RAW + length = read (interface -> rfdesc, dbuf, sizeof (dbuf)); +#else + length = dlpiunitdataind (interface -> rfdesc, (unsigned char *)NULL, + (unsigned long *)NULL, srcaddr, &srcaddrlen, + (unsigned long *)NULL, dbuf, sizeof (dbuf)); +#endif + + if (length <= 0) { + log_error("receive_packet: %m"); + return length; + } + +# if !defined (USE_DLPI_RAW) + /* + * Copy the sender's hw address into hfrom + * If sap_len < 0 the DLSAP is as phys+sap. + * If sap_len > 0 the DLSAP is as sap+phys. + * + * sap is discarded here. + */ + { /* DECODE DLSAP */ + int sap_len = interface -> dlpi_sap_length; + int phys_len = interface -> hw_address.hlen - 1; + + if (hfrom && (srcaddrlen == ABS (sap_len) + phys_len )) { + hfrom -> hbuf [0] = interface -> hw_address.hbuf [0]; + hfrom -> hlen = interface -> hw_address.hlen; + + if (sap_len < 0) { + memcpy ((char *) &hfrom -> hbuf [1], srcaddr, phys_len); + } + else { + memcpy((char *)&hfrom->hbuf[1], srcaddr + sap_len, phys_len); + } + } + else if (hfrom) { + memset (hfrom, '\0', sizeof *hfrom); + } + } /* DECODE_DLSAP */ + +# endif /* !defined (USE_DLPI_RAW) */ + + /* Decode the IP and UDP headers... */ + bufix = 0; +#ifdef USE_DLPI_RAW + /* Decode the physical header... */ + offset = decode_hw_header (interface, dbuf, bufix, hfrom); + + /* If a physical layer checksum failed (dunno of any + physical layer that supports this, but WTH), skip this + packet. */ + if (offset < 0) { + return 0; + } + bufix += offset; + length -= offset; +#endif + offset = decode_udp_ip_header (interface, dbuf, bufix, + from, length, &paylen, 1); + + /* + * If the IP or UDP checksum was bad, skip the packet... + * + * Note: this happens all the time when writing packets via the + * fallback socket. The packet received by streams does not have + * the IP or UDP checksums filled in, as those are calculated by + * the hardware. + */ + if (offset < 0) { + return 0; + } + + bufix += offset; + length -= offset; + + if (length < paylen) + log_fatal("Internal inconsistency at %s:%d.", MDL); + + /* Copy out the data in the packet... */ + memcpy(buf, &dbuf [bufix], paylen); + return paylen; +} +#endif + +/* Common DLPI routines ... + * + * Written by Eric James Negaard, <lmdejn@lmd.ericsson.se> + * + * Based largely in part to the example code contained in the document + * "How to Use the STREAMS Data Link Provider Interface (DLPI)", written + * by Neal Nuckolls of SunSoft Internet Engineering. + * + * This code has been developed and tested on sparc-based machines running + * SunOS 5.5.1, with le and hme network interfaces. It should be pretty + * generic, though. + * + * The usual disclaimers apply. This code works for me. Don't blame me + * if it makes your machine or network go down in flames. That taken + * into consideration, use this code as you wish. If you make usefull + * modifications I'd appreciate hearing about it. + */ + +#define DLPI_MAXWAIT 15 /* Max timeout */ + + +/* + * Parse an interface name and extract the unit number + */ + +static int dlpiunit (ifname) + char *ifname; +{ + char *cp; + int unit; + + if (!ifname) { + return 0; + } + + /* Advance to the end of the name */ + cp = ifname; + while (*cp) cp++; + /* Back up to the start of the first digit */ + while ((*(cp-1) >= '0' && *(cp-1) <= '9') || *(cp - 1) == ':') cp--; + + /* Convert the unit number */ + unit = 0; + while (*cp >= '0' && *cp <= '9') { + unit *= 10; + unit += (*cp++ - '0'); + } + + return unit; +} + +/* + * dlpiopen - open the DLPI device for a given interface name + */ +static int +dlpiopen(const char *ifname) { + char devname [50]; + char *dp; + const char *cp, *ep; + + if (!ifname) { + return -1; + } + + /* Open a DLPI device */ + if (*ifname == '/') { + dp = devname; + } else { + /* Prepend the device directory */ + memcpy (devname, DLPI_DEVDIR, strlen (DLPI_DEVDIR)); + dp = &devname [strlen (DLPI_DEVDIR)]; + } + + /* Find the end of the interface name */ + ep = cp = ifname; + while (*ep) + ep++; + /* And back up to the first digit (unit number) */ + while ((*(ep - 1) >= '0' && *(ep - 1) <= '9') || *(ep - 1) == ':') + ep--; + + /* Copy everything up to the unit number */ + while (cp < ep) { + *dp++ = *cp++; + } + *dp = '\0'; + + return open (devname, O_RDWR, 0); +} + +/* + * dlpiinforeq - request information about the data link provider. + */ + +static int dlpiinforeq (fd) + int fd; +{ + dl_info_req_t info_req; + struct strbuf ctl; + int flags; + + info_req.dl_primitive = DL_INFO_REQ; + + ctl.maxlen = 0; + ctl.len = sizeof (info_req); + ctl.buf = (char *)&info_req; + + flags = RS_HIPRI; + + return putmsg (fd, &ctl, (struct strbuf *)NULL, flags); +} + +/* + * dlpiphysaddrreq - request the current physical address. + */ +static int dlpiphysaddrreq (fd, addrtype) + int fd; + unsigned long addrtype; +{ + dl_phys_addr_req_t physaddr_req; + struct strbuf ctl; + int flags; + + physaddr_req.dl_primitive = DL_PHYS_ADDR_REQ; + physaddr_req.dl_addr_type = addrtype; + + ctl.maxlen = 0; + ctl.len = sizeof (physaddr_req); + ctl.buf = (char *)&physaddr_req; + + flags = RS_HIPRI; + + return putmsg (fd, &ctl, (struct strbuf *)NULL, flags); +} + +/* + * dlpiattachreq - send a request to attach to a specific unit. + */ +static int dlpiattachreq (fd, ppa) + unsigned long ppa; + int fd; +{ + dl_attach_req_t attach_req; + struct strbuf ctl; + int flags; + + attach_req.dl_primitive = DL_ATTACH_REQ; + attach_req.dl_ppa = ppa; + + ctl.maxlen = 0; + ctl.len = sizeof (attach_req); + ctl.buf = (char *)&attach_req; + + flags = 0; + + return putmsg (fd, &ctl, (struct strbuf*)NULL, flags); +} + +/* + * dlpibindreq - send a request to bind to a specific SAP address. + */ +static int dlpibindreq (fd, sap, max_conind, service_mode, conn_mgmt, xidtest) + unsigned long sap; + unsigned long max_conind; + unsigned long service_mode; + unsigned long conn_mgmt; + unsigned long xidtest; + int fd; +{ + dl_bind_req_t bind_req; + struct strbuf ctl; + int flags; + + bind_req.dl_primitive = DL_BIND_REQ; + bind_req.dl_sap = sap; + bind_req.dl_max_conind = max_conind; + bind_req.dl_service_mode = service_mode; + bind_req.dl_conn_mgmt = conn_mgmt; + bind_req.dl_xidtest_flg = xidtest; + + ctl.maxlen = 0; + ctl.len = sizeof (bind_req); + ctl.buf = (char *)&bind_req; + + flags = 0; + + return putmsg (fd, &ctl, (struct strbuf*)NULL, flags); +} + +#if defined(UNUSED_DLPI_INTERFACE) +/* + * dlpiunbindreq - send a request to unbind. This function is not actually + * used by ISC DHCP, but is included for completeness in case it is + * ever required for new work. + */ +static int dlpiunbindreq (fd) + int fd; +{ + dl_unbind_req_t unbind_req; + struct strbuf ctl; + int flags; + + unbind_req.dl_primitive = DL_UNBIND_REQ; + + ctl.maxlen = 0; + ctl.len = sizeof (unbind_req); + ctl.buf = (char *)&unbind_req; + + flags = 0; + + return putmsg (fd, &ctl, (struct strbuf*)NULL, flags); +} + + +/* + * dlpidetachreq - send a request to detach. This function is not actually + * used by ISC DHCP, but is included for completeness in case it is + * ever required for new work. + */ +static int dlpidetachreq (fd) + int fd; +{ + dl_detach_req_t detach_req; + struct strbuf ctl; + int flags; + + detach_req.dl_primitive = DL_DETACH_REQ; + + ctl.maxlen = 0; + ctl.len = sizeof (detach_req); + ctl.buf = (char *)&detach_req; + + flags = 0; + + return putmsg (fd, &ctl, (struct strbuf*)NULL, flags); +} +#endif /* UNUSED_DLPI_INTERFACE */ + + +/* + * dlpibindack - receive an ack to a dlbindreq. + */ +static int dlpibindack (fd, bufp) + char *bufp; + int fd; +{ + union DL_primitives *dlp; + struct strbuf ctl; + int flags; + + ctl.maxlen = DLPI_MAXDLBUF; + ctl.len = 0; + ctl.buf = bufp; + + if (strgetmsg (fd, &ctl, + (struct strbuf*)NULL, &flags, "dlpibindack") < 0) { + return -1; + } + + dlp = (union DL_primitives *)ctl.buf; + + if (expected (DL_BIND_ACK, dlp, flags) == -1) { + return -1; + } + + if (ctl.len < sizeof (dl_bind_ack_t)) { + /* Returned structure is too short */ + return -1; + } + + return 0; +} + +/* + * dlpiokack - general acknowledgement reception. + */ +static int dlpiokack (fd, bufp) + char *bufp; + int fd; +{ + union DL_primitives *dlp; + struct strbuf ctl; + int flags; + + ctl.maxlen = DLPI_MAXDLBUF; + ctl.len = 0; + ctl.buf = bufp; + + if (strgetmsg (fd, &ctl, + (struct strbuf*)NULL, &flags, "dlpiokack") < 0) { + return -1; + } + + dlp = (union DL_primitives *)ctl.buf; + + if (expected (DL_OK_ACK, dlp, flags) == -1) { + return -1; + } + + if (ctl.len < sizeof (dl_ok_ack_t)) { + /* Returned structure is too short */ + return -1; + } + + return 0; +} + +/* + * dlpiinfoack - receive an ack to a dlinforeq. + */ +static int dlpiinfoack (fd, bufp) + char *bufp; + int fd; +{ + union DL_primitives *dlp; + struct strbuf ctl; + int flags; + + ctl.maxlen = DLPI_MAXDLBUF; + ctl.len = 0; + ctl.buf = bufp; + + if (strgetmsg (fd, &ctl, (struct strbuf *)NULL, &flags, + "dlpiinfoack") < 0) { + return -1; + } + + dlp = (union DL_primitives *) ctl.buf; + + if (expected (DL_INFO_ACK, dlp, flags) == -1) { + return -1; + } + + if (ctl.len < sizeof (dl_info_ack_t)) { + /* Returned structure is too short */ + return -1; + } + + return 0; +} + +/* + * dlpiphysaddrack - receive an ack to a dlpiphysaddrreq. + */ +int dlpiphysaddrack (fd, bufp) + char *bufp; + int fd; +{ + union DL_primitives *dlp; + struct strbuf ctl; + int flags; + + ctl.maxlen = DLPI_MAXDLBUF; + ctl.len = 0; + ctl.buf = bufp; + + if (strgetmsg (fd, &ctl, (struct strbuf *)NULL, &flags, + "dlpiphysaddrack") < 0) { + return -1; + } + + dlp = (union DL_primitives *)ctl.buf; + + if (expected (DL_PHYS_ADDR_ACK, dlp, flags) == -1) { + return -1; + } + + if (ctl.len < sizeof (dl_phys_addr_ack_t)) { + /* Returned structure is too short */ + return -1; + } + + return 0; +} + +#if defined(USE_DLPI_SEND) || defined(USE_DLPI_RECEIVE) +int dlpiunitdatareq (fd, addr, addrlen, minpri, maxpri, dbuf, dbuflen) + int fd; + unsigned char *addr; + int addrlen; + unsigned long minpri; + unsigned long maxpri; + unsigned char *dbuf; + int dbuflen; +{ + long buf [DLPI_MAXDLBUF]; + union DL_primitives *dlp; + struct strbuf ctl, data; + + /* Set up the control information... */ + dlp = (union DL_primitives *)buf; + dlp -> unitdata_req.dl_primitive = DL_UNITDATA_REQ; + dlp -> unitdata_req.dl_dest_addr_length = addrlen; + dlp -> unitdata_req.dl_dest_addr_offset = sizeof (dl_unitdata_req_t); + dlp -> unitdata_req.dl_priority.dl_min = minpri; + dlp -> unitdata_req.dl_priority.dl_max = maxpri; + + /* Append the destination address */ + memcpy ((char *)buf + dlp -> unitdata_req.dl_dest_addr_offset, + addr, addrlen); + + ctl.maxlen = 0; + ctl.len = dlp -> unitdata_req.dl_dest_addr_offset + addrlen; + ctl.buf = (char *)buf; + + data.maxlen = 0; + data.buf = (char *)dbuf; + data.len = dbuflen; + + /* Send the packet down the wire... */ + return putmsg (fd, &ctl, &data, 0); +} + +static int dlpiunitdataind (fd, daddr, daddrlen, + saddr, saddrlen, grpaddr, dbuf, dlen) + int fd; + unsigned char *daddr; + unsigned long *daddrlen; + unsigned char *saddr; + unsigned long *saddrlen; + unsigned long *grpaddr; + unsigned char *dbuf; + int dlen; +{ + long buf [DLPI_MAXDLBUF]; + union DL_primitives *dlp; + struct strbuf ctl, data; + int flags = 0; + int result; + + /* Set up the msg_buf structure... */ + dlp = (union DL_primitives *)buf; + dlp -> unitdata_ind.dl_primitive = DL_UNITDATA_IND; + + ctl.maxlen = DLPI_MAXDLBUF; + ctl.len = 0; + ctl.buf = (char *)buf; + + data.maxlen = dlen; + data.len = 0; + data.buf = (char *)dbuf; + + result = getmsg (fd, &ctl, &data, &flags); + + if (result < 0) { + log_debug("dlpiunitdataind: %m"); + return -1; + } + + if (ctl.len < sizeof (dl_unitdata_ind_t) || + dlp -> unitdata_ind.dl_primitive != DL_UNITDATA_IND) { + return -1; + } + + if (data.len <= 0) { + return data.len; + } + + /* Copy sender info */ + if (saddr) { + memcpy (saddr, + (char *)buf + dlp -> unitdata_ind.dl_src_addr_offset, + dlp -> unitdata_ind.dl_src_addr_length); + } + if (saddrlen) { + *saddrlen = dlp -> unitdata_ind.dl_src_addr_length; + } + + /* Copy destination info */ + if (daddr) { + memcpy (daddr, + (char *)buf + dlp -> unitdata_ind.dl_dest_addr_offset, + dlp -> unitdata_ind.dl_dest_addr_length); + } + if (daddrlen) { + *daddrlen = dlp -> unitdata_ind.dl_dest_addr_length; + } + + if (grpaddr) { + *grpaddr = dlp -> unitdata_ind.dl_group_address; + } + + return data.len; +} +#endif /* !USE_DLPI_HWADDR: USE_DLPI_RECEIVE || USE_DLPI_SEND */ + +/* + * expected - see if we got what we wanted. + */ +static int expected (prim, dlp, msgflags) + unsigned long prim; + union DL_primitives *dlp; + int msgflags; +{ + if (msgflags != RS_HIPRI) { + /* Message was not M_PCPROTO */ + return -1; + } + + if (dlp->dl_primitive != prim) { + /* Incorrect/unexpected return message */ + return -1; + } + + return 0; +} + +/* + * strgetmsg - get a message from a stream, with timeout. + */ +static int strgetmsg (fd, ctlp, datap, flagsp, caller) + struct strbuf *ctlp, *datap; + char *caller; + int *flagsp; + int fd; +{ + int result; + struct pollfd pfd; + int count; + time_t now; + time_t starttime; + int to_msec; + + pfd.fd = fd; + pfd.events = POLLPRI; /* We're only interested in knowing + * when we can receive the next high + * priority message. + */ + pfd.revents = 0; + + now = time (&starttime); + while (now <= starttime + DLPI_MAXWAIT) { + to_msec = ((starttime + DLPI_MAXWAIT) - now) * 1000; + count = poll (&pfd, 1, to_msec); + + if (count == 0) { + /* log_fatal ("strgetmsg: timeout"); */ + return -1; + } else if (count < 0) { + if (errno == EAGAIN || errno == EINTR) { + time (&now); + continue; + } else { + /* log_fatal ("poll: %m"); */ + return -1; + } + } else { + break; + } + } + + /* + * Set flags argument and issue getmsg (). + */ + *flagsp = 0; + if ((result = getmsg (fd, ctlp, datap, flagsp)) < 0) { + return result; + } + + /* + * Check for MOREDATA and/or MORECTL. + */ + if (result & (MORECTL|MOREDATA)) { + return -1; + } + + /* + * Check for at least sizeof (long) control data portion. + */ + if (ctlp -> len < sizeof (long)) { + return -1; + } + + return 0; +} + +#if defined(USE_DLPI_SEND) +int can_unicast_without_arp (ip) + struct interface_info *ip; +{ + return 1; +} + +int can_receive_unicast_unconfigured (ip) + struct interface_info *ip; +{ + return 1; +} + +int supports_multiple_interfaces (ip) + struct interface_info *ip; +{ + return 1; +} + +void maybe_setup_fallback () +{ + isc_result_t status; + struct interface_info *fbi = (struct interface_info *)0; + if (setup_fallback (&fbi, MDL)) { + if_register_fallback (fbi); + status = omapi_register_io_object ((omapi_object_t *)fbi, + if_readsocket, 0, + fallback_discard, 0, 0); + if (status != ISC_R_SUCCESS) + log_fatal ("Can't register I/O handle for %s: %s", + fbi -> name, isc_result_totext (status)); + interface_dereference (&fbi, MDL); + } +} +#endif /* USE_DLPI_SEND */ + +void +get_hw_addr(const char *name, struct hardware *hw) { + int sock, unit; + long buf[DLPI_MAXDLBUF]; + union DL_primitives *dlp; + + dlp = (union DL_primitives *)buf; + + /* + * Open a DLPI device. + */ + sock = dlpiopen(name); + if (sock < 0) { + log_fatal("Can't open DLPI device for %s: %m", name); + } + + /* + * Submit a DL_INFO_REQ request, to find the dl_mac_type and + * dl_provider_style + */ + if (dlpiinforeq(sock) < 0) { + log_fatal("Can't request DLPI MAC type for %s: %m", name); + } + if (dlpiinfoack(sock, (char *)buf) < 0) { + log_fatal("Can't get DLPI MAC type for %s: %m", name); + } + switch (dlp->info_ack.dl_mac_type) { + case DL_CSMACD: /* IEEE 802.3 */ + case DL_ETHER: + hw->hbuf[0] = HTYPE_ETHER; + break; + case DL_TPR: + hw->hbuf[0] = HTYPE_IEEE802; + break; + case DL_FDDI: + hw->hbuf[0] = HTYPE_FDDI; + break; + default: + log_fatal("%s: unsupported DLPI MAC type %lu", name, + (unsigned long)dlp->info_ack.dl_mac_type); + } + + if (dlp->info_ack.dl_provider_style == DL_STYLE2) { + /* + * Attach to the device. If this fails, the device + * does not exist. + */ + unit = dlpiunit((char *)name); + + if (dlpiattachreq(sock, unit) < 0 || + dlpiokack(sock, (char *)buf) < 0) { + log_fatal("Can't attach DLPI device for %s: %m", + name); + } + } + + /* + * Submit a DL_PHYS_ADDR_REQ request, to find + * the hardware address. + */ + if (dlpiphysaddrreq(sock, DL_CURR_PHYS_ADDR) < 0) { + log_fatal("Can't request DLPI hardware address for %s: %m", + name); + } + if (dlpiphysaddrack(sock, (char *)buf) < 0) { + log_fatal("Can't get DLPI hardware address for %s: %m", + name); + } + if (dlp->physaddr_ack.dl_addr_length < sizeof(hw->hbuf)) { + memcpy(hw->hbuf+1, + (char *)buf + dlp->physaddr_ack.dl_addr_offset, + dlp->physaddr_ack.dl_addr_length); + hw->hlen = dlp->physaddr_ack.dl_addr_length + 1; + } else { + memcpy(hw->hbuf+1, + (char *)buf + dlp->physaddr_ack.dl_addr_offset, + sizeof(hw->hbuf)-1); + hw->hlen = sizeof(hw->hbuf); + } + + close(sock); +} +#endif /* USE_DLPI_SEND || USE_DLPI_RECEIVE || USE_DLPI_HWADDR */ diff --git a/common/dns.c b/common/dns.c new file mode 100644 index 0000000..ebf6212 --- /dev/null +++ b/common/dns.c @@ -0,0 +1,1785 @@ +/* dns.c + + Domain Name Service subroutines. */ + +/* + * Copyright (c) 2009-2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2007 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2001-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" +#include "arpa/nameser.h" +#include <isc/md5.h> + +#include <dns/result.h> + +/* + * This file contains code to connect the DHCP code to the libdns modules. + * As part of that function it maintains a database of zone cuts that can + * be used to figure out which server should be contacted to update any + * given domain name. Included in the zone information may be a pointer + * to a key in which case that key is used for the update. If no zone + * is found then the DNS code determines the zone on its own. + * + * The way this works is that you define the domain name to which an + * SOA corresponds, and the addresses of some primaries for that domain name: + * + * zone FOO.COM { + * primary 10.0.17.1; + * secondary 10.0.22.1, 10.0.23.1; + * key "FOO.COM Key"; + * } + * + * If an update is requested for GAZANGA.TOPANGA.FOO.COM, then the name + * server looks in its database for a zone record for "GAZANGA.TOPANGA.FOO.COM", + * doesn't find it, looks for one for "TOPANGA.FOO.COM", doesn't find *that*, + * looks for "FOO.COM", finds it. So it + * attempts the update to the primary for FOO.COM. If that times out, it + * tries the secondaries. You can list multiple primaries if you have some + * kind of magic name server that supports that. You shouldn't list + * secondaries that don't know how to forward updates (e.g., BIND 8 doesn't + * support update forwarding, AFAIK). If no TSIG key is listed, the update + * is attempted without TSIG. + * + * You can also include IPv6 addresses via the primary6 and secondary6 + * options. The search order for the addresses is primary, primary6, + * secondary and lastly secondary6, with a limit on the number of + * addresses used. Currently this limit is 3. + * + * The DHCP server tries to find an existing zone for any given name by + * trying to look up a local zone structure for each domain containing + * that name, all the way up to '.'. If it finds one cached, it tries + * to use that one to do the update. That's why it tries to update + * "FOO.COM" above, even though theoretically it should try GAZANGA... + * and TOPANGA... first. + * + * If the update fails with a predefined zone the zone is marked as bad + * and another search of the predefined zones is done. If no predefined + * zone is found finding a zone is left to the DNS module via examination + * of SOA records. If the DNS module finds a zone it may cache the zone + * but the zone won't be cached here. + * + * TSIG updates are not performed on zones found by the DNS module - if + * you want TSIG updates you _must_ write a zone definition linking the + * key to the zone. In cases where you know for sure what the key is + * but do not want to hardcode the IP addresses of the primary or + * secondaries, a zone declaration can be made that doesn't include any + * primary or secondary declarations. When the DHCP server encounters + * this while hunting up a matching zone for a name, it looks up the SOA, + * fills in the IP addresses, and uses that record for the update. + * If the SOA lookup returns NXRRSET, a warning is printed and the zone is + * discarded, TSIG key and all. The search for the zone then continues + * as if the zone record hadn't been found. Zones without IP addresses + * don't match when initially hunting for a zone to update. + * + * When an update is attempted and no predefined zone is found + * that matches any enclosing domain of the domain being updated, the DHCP + * server goes through the same process that is done when the update to a + * predefined zone fails - starting with the most specific domain + * name (GAZANGA.TOPANGA.FOO.COM) and moving to the least specific (the root), + * it tries to look up an SOA record. + * + * TSIG keys are defined like this: + * + * key "FOO.COM Key" { + * algorithm HMAC-MD5.SIG-ALG.REG.INT; + * secret <Base64>; + * } + * + * <Base64> is a number expressed in base64 that represents the key. + * It's also permissible to use a quoted string here - this will be + * translated as the ASCII bytes making up the string, and will not + * include any NUL termination. The key name can be any text string, + * and the key type must be one of the key types defined in the draft + * or by the IANA. Currently only the HMAC-MD5... key type is + * supported. + * + * The DDNS processing has been split into two areas. One is the + * control code that determines what should be done. That code is found + * in the client or server directories. The other is the common code + * that performs functions such as properly formatting the arguments. + * That code is found in this file. The basic processing flow for a + * DDNS update is: + * In the client or server code determine what needs to be done and + * collect the necesary information then pass it to a function from + * this file. + * In this code lookup the zone and extract the zone and key information + * (if available) and prepare the arguments for the DNS module. + * When the DNS module completes its work (times out or gets a reply) + * it will trigger another function here which does generic processing + * and then passes control back to the code from the server or client. + * The server or client code then determines the next step which may + * result in another call to this module in which case the process repeats. + */ + +dns_zone_hash_t *dns_zone_hash; + +/* + * DHCP dns structures + * Normally the relationship between these structures isn't one to one + * but in the DHCP case it (mostly) is. To make the allocations, frees, + * and passing of the memory easier we make a single structure with all + * the pieces. + * + * The maximum size of the data buffer should be large enough for any + * items DHCP will generate + */ + +typedef struct dhcp_ddns_rdata { + dns_rdata_t rdata; + dns_rdatalist_t rdatalist; + dns_rdataset_t rdataset; +} dhcp_ddns_data_t; + +#if defined (NSUPDATE) + +void ddns_interlude(isc_task_t *, isc_event_t *); + + +#if defined (TRACING) +/* + * Code to support tracing DDNS packets. We trace packets going to and + * coming from the libdns code but don't try to track the packets + * exchanged between the libdns code and the dns server(s) it contacts. + * + * The code is split into two sets of routines + * input refers to messages received from the dns module + * output refers to messages sent to the dns module + * Currently there are three routines in each set + * write is used to write information about the message to the trace file + * this routine is called directly from the proper place in the code. + * read is used to read information about a message from the trace file + * this routine is called from the trace loop as it reads through + * the file and is registered via the trace_type_register routine. + * When playing back a trace file we shall absorb records of output + * messages as part of processing the write function, therefore + * any output messages we encounter are flagged as errors. + * stop isn't currently used in this code but is needed for the register + * routine. + * + * We pass a pointer to a control block to the dns module which it returns + * to use as part of the result. As the pointer may vary between traces + * we need to map between those from the trace file and the new ones during + * playback. + * + * The mapping is complicated a little as a pointer could be 4 or 8 bytes + * long. We treat the old pointer as an 8 byte quantity and pad and compare + * as necessary. + */ + +/* + * Structure used to map old pointers to new pointers. + * Old pointers are 8 bytes long as we don't know if the trace was + * done on a 64 bit or 32 bit machine. + */ +#define TRACE_PTR_LEN 8 + +typedef struct dhcp_ddns_map { + char old_pointer[TRACE_PTR_LEN]; + void *new_pointer; + struct dhcp_ddns_map *next; +} dhcp_ddns_map_t; + +/* The starting point for the map structure */ +static dhcp_ddns_map_t *ddns_map; + +trace_type_t *trace_ddns_input; +trace_type_t *trace_ddns_output; + +/* + * The data written to the trace file is: + * 32 bits result from dns + * 64 bits pointer of cb + */ + +void +trace_ddns_input_write(dhcp_ddns_cb_t *ddns_cb, isc_result_t result) +{ + trace_iov_t iov[2]; + u_int32_t old_result; + char old_pointer[TRACE_PTR_LEN]; + + old_result = htonl((u_int32_t)result); + memset(old_pointer, 0, TRACE_PTR_LEN); + memcpy(old_pointer, &ddns_cb, sizeof(ddns_cb)); + + iov[0].len = sizeof(old_result); + iov[0].buf = (char *)&old_result; + iov[1].len = TRACE_PTR_LEN; + iov[1].buf = old_pointer; + trace_write_packet_iov(trace_ddns_input, 2, iov, MDL); +} + +/* + * Process the result and pointer from the trace file. + * We use the pointer map to find the proper pointer for this instance. + * Then we need to construct an event to pass along to the interlude + * function. + */ +static void +trace_ddns_input_read(trace_type_t *ttype, unsigned length, + char *buf) +{ + u_int32_t old_result; + char old_pointer[TRACE_PTR_LEN]; + dns_clientupdateevent_t *eventp; + void *new_pointer; + dhcp_ddns_map_t *ddns_map_ptr; + + if (length < (sizeof(old_result) + TRACE_PTR_LEN)) { + log_error("trace_ddns_input_read: data too short"); + return; + } + + memcpy(&old_result, buf, sizeof(old_result)); + memcpy(old_pointer, buf + sizeof(old_result), TRACE_PTR_LEN); + + /* map the old pointer to a new pointer */ + for (ddns_map_ptr = ddns_map; + ddns_map_ptr != NULL; + ddns_map_ptr = ddns_map_ptr->next) { + if ((ddns_map_ptr->new_pointer != NULL) && + memcmp(ddns_map_ptr->old_pointer, + old_pointer, TRACE_PTR_LEN) == 0) { + new_pointer = ddns_map_ptr->new_pointer; + ddns_map_ptr->new_pointer = NULL; + memset(ddns_map_ptr->old_pointer, 0, TRACE_PTR_LEN); + break; + } + } + if (ddns_map_ptr == NULL) { + log_error("trace_dns_input_read: unable to map cb pointer"); + return; + } + + eventp = (dns_clientupdateevent_t *) + isc_event_allocate(dhcp_gbl_ctx.mctx, + dhcp_gbl_ctx.task, + 0, + ddns_interlude, + new_pointer, + sizeof(dns_clientupdateevent_t)); + if (eventp == NULL) { + log_error("trace_ddns_input_read: unable to allocate event"); + return; + } + eventp->result = ntohl(old_result); + + + ddns_interlude(dhcp_gbl_ctx.task, (isc_event_t *)eventp); + + return; +} + +static void +trace_ddns_input_stop(trace_type_t *ttype) +{ +} + +/* + * We use the same arguments as for the dns startupdate function to + * allows us to choose between the two via a macro. If tracing isn't + * in use we simply call the dns function directly. + * + * If we are doing playback we read the next packet from the file + * and compare the type. If it matches we extract the results and pointer + * from the trace file. The results are returned to the caller as if + * they had called the dns routine. The pointer is used to construct a + * map for when the "reply" is processed. + * + * The data written to trace file is: + * 32 bits result + * 64 bits pointer of cb (DDNS Control block) + * contents of cb + */ + +isc_result_t +trace_ddns_output_write(dns_client_t *client, dns_rdataclass_t rdclass, + dns_name_t *zonename, dns_namelist_t *prerequisites, + dns_namelist_t *updates, isc_sockaddrlist_t *servers, + dns_tsec_t *tsec, unsigned int options, + isc_task_t *task, isc_taskaction_t action, void *arg, + dns_clientupdatetrans_t **transp) +{ + isc_result_t result; + u_int32_t old_result; + char old_pointer[TRACE_PTR_LEN]; + dhcp_ddns_map_t *ddns_map_ptr; + + if (trace_playback() != 0) { + /* We are doing playback, extract the entry from the file */ + unsigned buflen = 0; + char *inbuf = NULL; + + result = trace_get_packet(&trace_ddns_output, + &buflen, &inbuf); + if (result != ISC_R_SUCCESS) { + log_error("trace_ddns_output_write: no input found"); + return (ISC_R_FAILURE); + } + if (buflen < (sizeof(old_result) + TRACE_PTR_LEN)) { + log_error("trace_ddns_output_write: data too short"); + dfree(inbuf, MDL); + return (ISC_R_FAILURE); + } + memcpy(&old_result, inbuf, sizeof(old_result)); + result = ntohl(old_result); + memcpy(old_pointer, inbuf + sizeof(old_result), TRACE_PTR_LEN); + dfree(inbuf, MDL); + + /* add the pointer to the pointer map */ + for (ddns_map_ptr = ddns_map; + ddns_map_ptr != NULL; + ddns_map_ptr = ddns_map_ptr->next) { + if (ddns_map_ptr->new_pointer == NULL) { + break; + } + } + + /* + * If we didn't find an empty entry, allocate an entry and + * link it into the list. The list isn't ordered. + */ + if (ddns_map_ptr == NULL) { + ddns_map_ptr = dmalloc(sizeof(*ddns_map_ptr), MDL); + if (ddns_map_ptr == NULL) { + log_error("trace_ddns_output_write: " + "unable to allocate map entry"); + return(ISC_R_FAILURE); + } + ddns_map_ptr->next = ddns_map; + ddns_map = ddns_map_ptr; + } + + memcpy(ddns_map_ptr->old_pointer, old_pointer, TRACE_PTR_LEN); + ddns_map_ptr->new_pointer = arg; + } + else { + /* We aren't doing playback, make the actual call */ + result = dns_client_startupdate(client, rdclass, zonename, + prerequisites, updates, + servers, tsec, options, + task, action, arg, transp); + } + + if (trace_record() != 0) { + /* We are recording, save the information to the file */ + trace_iov_t iov[3]; + old_result = htonl((u_int32_t)result); + memset(old_pointer, 0, TRACE_PTR_LEN); + memcpy(old_pointer, &arg, sizeof(arg)); + iov[0].len = sizeof(old_result); + iov[0].buf = (char *)&old_result; + iov[1].len = TRACE_PTR_LEN; + iov[1].buf = old_pointer; + + /* Write out the entire cb, in case we want to look at it */ + iov[2].len = sizeof(dhcp_ddns_cb_t); + iov[2].buf = (char *)arg; + + trace_write_packet_iov(trace_ddns_output, 3, iov, MDL); + } + + return(result); +} + +static void +trace_ddns_output_read(trace_type_t *ttype, unsigned length, + char *buf) +{ + log_error("unaccounted for ddns output."); +} + +static void +trace_ddns_output_stop(trace_type_t *ttype) +{ +} + +void +trace_ddns_init() +{ + trace_ddns_output = trace_type_register("ddns-output", NULL, + trace_ddns_output_read, + trace_ddns_output_stop, MDL); + trace_ddns_input = trace_type_register("ddns-input", NULL, + trace_ddns_input_read, + trace_ddns_input_stop, MDL); + ddns_map = NULL; +} + +#define ddns_update trace_ddns_output_write +#else +#define ddns_update dns_client_startupdate +#endif /* TRACING */ + +/* + * Code to allocate and free a dddns control block. This block is used + * to pass and track the information associated with a DDNS update request. + */ +dhcp_ddns_cb_t * +ddns_cb_alloc(const char *file, int line) +{ + dhcp_ddns_cb_t *ddns_cb; + int i; + + ddns_cb = dmalloc(sizeof(*ddns_cb), file, line); + if (ddns_cb != NULL) { + ISC_LIST_INIT(ddns_cb->zone_server_list); + for (i = 0; i < DHCP_MAXNS; i++) { + ISC_LINK_INIT(&ddns_cb->zone_addrs[i], link); + } + } + +#if defined (DEBUG_DNS_UPDATES) + log_info("%s(%d): Allocating ddns_cb=%p", file, line, ddns_cb); +#endif + + return(ddns_cb); +} + +void +ddns_cb_free(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) +{ +#if defined (DEBUG_DNS_UPDATES) + log_info("%s(%d): freeing ddns_cb=%p", file, line, ddns_cb); +#endif + + data_string_forget(&ddns_cb->fwd_name, file, line); + data_string_forget(&ddns_cb->rev_name, file, line); + data_string_forget(&ddns_cb->dhcid, file, line); + + if (ddns_cb->zone != NULL) { + forget_zone((struct dns_zone **)&ddns_cb->zone); + } + + /* Should be freed by now, check just in case. */ + if (ddns_cb->transaction != NULL) + log_error("Impossible memory leak at %s:%d (attempt to free " + "DDNS Control Block before transaction).", MDL); + + dfree(ddns_cb, file, line); +} + +void +ddns_cb_forget_zone(dhcp_ddns_cb_t *ddns_cb) +{ + int i; + + forget_zone(&ddns_cb->zone); + ddns_cb->zone_name[0] = 0; + ISC_LIST_INIT(ddns_cb->zone_server_list); + for (i = 0; i < DHCP_MAXNS; i++) { + ISC_LINK_INIT(&ddns_cb->zone_addrs[i], link); + } +} + +isc_result_t find_tsig_key (ns_tsig_key **key, const char *zname, + struct dns_zone *zone) +{ + ns_tsig_key *tkey; + + if (!zone) + return ISC_R_NOTFOUND; + + if (!zone -> key) { + return DHCP_R_KEY_UNKNOWN; + } + + if ((!zone -> key -> name || + strlen (zone -> key -> name) > NS_MAXDNAME) || + (!zone -> key -> algorithm || + strlen (zone -> key -> algorithm) > NS_MAXDNAME) || + (!zone -> key) || + (!zone -> key -> key) || + (zone -> key -> key -> len == 0)) { + return DHCP_R_INVALIDKEY; + } + tkey = dmalloc (sizeof *tkey, MDL); + if (!tkey) { + nomem: + return ISC_R_NOMEMORY; + } + memset (tkey, 0, sizeof *tkey); + tkey -> data = dmalloc (zone -> key -> key -> len, MDL); + if (!tkey -> data) { + dfree (tkey, MDL); + goto nomem; + } + strcpy (tkey -> name, zone -> key -> name); + strcpy (tkey -> alg, zone -> key -> algorithm); + memcpy (tkey -> data, + zone -> key -> key -> value, zone -> key -> key -> len); + tkey -> len = zone -> key -> key -> len; + *key = tkey; + return ISC_R_SUCCESS; +} + +void tkey_free (ns_tsig_key **key) +{ + if ((*key) -> data) + dfree ((*key) -> data, MDL); + dfree ((*key), MDL); + *key = (ns_tsig_key *)0; +} +#endif + +isc_result_t enter_dns_zone (struct dns_zone *zone) +{ + struct dns_zone *tz = (struct dns_zone *)0; + + if (dns_zone_hash) { + dns_zone_hash_lookup (&tz, + dns_zone_hash, zone -> name, 0, MDL); + if (tz == zone) { + dns_zone_dereference (&tz, MDL); + return ISC_R_SUCCESS; + } + if (tz) { + dns_zone_hash_delete (dns_zone_hash, + zone -> name, 0, MDL); + dns_zone_dereference (&tz, MDL); + } + } else { + if (!dns_zone_new_hash(&dns_zone_hash, DNS_HASH_SIZE, MDL)) + return ISC_R_NOMEMORY; + } + + dns_zone_hash_add (dns_zone_hash, zone -> name, 0, zone, MDL); + return ISC_R_SUCCESS; +} + +isc_result_t dns_zone_lookup (struct dns_zone **zone, const char *name) +{ + int len; + char *tname = (char *)0; + isc_result_t status; + + if (!dns_zone_hash) + return ISC_R_NOTFOUND; + + len = strlen (name); + if (name [len - 1] != '.') { + tname = dmalloc ((unsigned)len + 2, MDL); + if (!tname) + return ISC_R_NOMEMORY; + strcpy (tname, name); + tname [len] = '.'; + tname [len + 1] = 0; + name = tname; + } + if (!dns_zone_hash_lookup (zone, dns_zone_hash, name, 0, MDL)) + status = ISC_R_NOTFOUND; + else + status = ISC_R_SUCCESS; + + if (tname) + dfree (tname, MDL); + return status; +} + +int dns_zone_dereference (ptr, file, line) + struct dns_zone **ptr; + const char *file; + int line; +{ + struct dns_zone *dns_zone; + + if ((ptr == NULL) || (*ptr == NULL)) { + log_error("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort(); +#else + return (0); +#endif + } + + dns_zone = *ptr; + *ptr = NULL; + --dns_zone->refcnt; + rc_register(file, line, ptr, dns_zone, dns_zone->refcnt, 1, RC_MISC); + if (dns_zone->refcnt > 0) + return (1); + + if (dns_zone->refcnt < 0) { + log_error("%s(%d): negative refcnt!", file, line); +#if defined (DEBUG_RC_HISTORY) + dump_rc_history(dns_zone); +#endif +#if defined (POINTER_DEBUG) + abort(); +#else + return (0); +#endif + } + + if (dns_zone->name) + dfree(dns_zone->name, file, line); + if (dns_zone->key) + omapi_auth_key_dereference(&dns_zone->key, file, line); + if (dns_zone->primary) + option_cache_dereference(&dns_zone->primary, file, line); + if (dns_zone->secondary) + option_cache_dereference(&dns_zone->secondary, file, line); + if (dns_zone->primary6) + option_cache_dereference(&dns_zone->primary6, file, line); + if (dns_zone->secondary6) + option_cache_dereference(&dns_zone->secondary6, file, line); + dfree(dns_zone, file, line); + return (1); +} + +#if defined (NSUPDATE) +isc_result_t +find_cached_zone(dhcp_ddns_cb_t *ddns_cb, int direction) +{ + isc_result_t status = ISC_R_NOTFOUND; + const char *np; + struct dns_zone *zone = NULL; + struct data_string nsaddrs; + struct in_addr zone_addr; + struct in6_addr zone_addr6; + int ix; + + if (direction == FIND_FORWARD) { + np = (const char *)ddns_cb->fwd_name.data; + } else { + np = (const char *)ddns_cb->rev_name.data; + } + + /* We can't look up a null zone. */ + if ((np == NULL) || (*np == '\0')) { + return (DHCP_R_INVALIDARG); + } + + /* + * For each subzone, try to find a cached zone. + */ + for (;;) { + status = dns_zone_lookup(&zone, np); + if (status == ISC_R_SUCCESS) + break; + + np = strchr(np, '.'); + if (np == NULL) + break; + np++; + } + + if (status != ISC_R_SUCCESS) + return (status); + + /* Make sure the zone is valid. */ + if (zone->timeout && zone->timeout < cur_time) { + dns_zone_dereference(&zone, MDL); + return (ISC_R_CANCELED); + } + + /* Make sure the zone name will fit. */ + if (strlen(zone->name) > sizeof(ddns_cb->zone_name)) { + dns_zone_dereference(&zone, MDL); + return (ISC_R_NOSPACE); + } + strcpy((char *)&ddns_cb->zone_name[0], zone->name); + + memset (&nsaddrs, 0, sizeof nsaddrs); + ix = 0; + + if (zone->primary) { + if (evaluate_option_cache(&nsaddrs, NULL, NULL, NULL, + NULL, NULL, &global_scope, + zone->primary, MDL)) { + int ip = 0; + while (ix < DHCP_MAXNS) { + if (ip + 4 > nsaddrs.len) + break; + memcpy(&zone_addr, &nsaddrs.data[ip], 4); + isc_sockaddr_fromin(&ddns_cb->zone_addrs[ix], + &zone_addr, + NS_DEFAULTPORT); + ISC_LIST_APPEND(ddns_cb->zone_server_list, + &ddns_cb->zone_addrs[ix], + link); + ip += 4; + ix++; + } + data_string_forget(&nsaddrs, MDL); + } + } + + if (zone->primary6) { + if (evaluate_option_cache(&nsaddrs, NULL, NULL, NULL, + NULL, NULL, &global_scope, + zone->primary6, MDL)) { + int ip = 0; + while (ix < DHCP_MAXNS) { + if (ip + 16 > nsaddrs.len) + break; + memcpy(&zone_addr6, &nsaddrs.data[ip], 16); + isc_sockaddr_fromin6(&ddns_cb->zone_addrs[ix], + &zone_addr6, + NS_DEFAULTPORT); + ISC_LIST_APPEND(ddns_cb->zone_server_list, + &ddns_cb->zone_addrs[ix], + link); + ip += 16; + ix++; + } + data_string_forget(&nsaddrs, MDL); + } + } + + if (zone->secondary) { + if (evaluate_option_cache(&nsaddrs, NULL, NULL, NULL, + NULL, NULL, &global_scope, + zone->secondary, MDL)) { + int ip = 0; + while (ix < DHCP_MAXNS) { + if (ip + 4 > nsaddrs.len) + break; + memcpy(&zone_addr, &nsaddrs.data[ip], 4); + isc_sockaddr_fromin(&ddns_cb->zone_addrs[ix], + &zone_addr, + NS_DEFAULTPORT); + ISC_LIST_APPEND(ddns_cb->zone_server_list, + &ddns_cb->zone_addrs[ix], + link); + ip += 4; + ix++; + } + data_string_forget (&nsaddrs, MDL); + } + } + + if (zone->secondary6) { + if (evaluate_option_cache(&nsaddrs, NULL, NULL, NULL, + NULL, NULL, &global_scope, + zone->secondary6, MDL)) { + int ip = 0; + while (ix < DHCP_MAXNS) { + if (ip + 16 > nsaddrs.len) + break; + memcpy(&zone_addr6, &nsaddrs.data[ip], 16); + isc_sockaddr_fromin6(&ddns_cb->zone_addrs[ix], + &zone_addr6, + NS_DEFAULTPORT); + ISC_LIST_APPEND(ddns_cb->zone_server_list, + &ddns_cb->zone_addrs[ix], + link); + ip += 16; + ix++; + } + data_string_forget (&nsaddrs, MDL); + } + } + + dns_zone_reference(&ddns_cb->zone, zone, MDL); + dns_zone_dereference (&zone, MDL); + return ISC_R_SUCCESS; +} + +void forget_zone (struct dns_zone **zone) +{ + dns_zone_dereference (zone, MDL); +} + +void repudiate_zone (struct dns_zone **zone) +{ + /* XXX Currently we're not differentiating between a cached + XXX zone and a zone that's been repudiated, which means + XXX that if we reap cached zones, we blow away repudiated + XXX zones. This isn't a big problem since we're not yet + XXX caching zones... :'} */ + + /* verify that we have a pointer at least */ + if ((zone == NULL) || (*zone == NULL)) { + log_info("Null argument to repudiate zone"); + return; + } + + (*zone) -> timeout = cur_time - 1; + dns_zone_dereference (zone, MDL); +} + +/* Have to use TXT records for now. */ +#define T_DHCID T_TXT + +int get_dhcid (struct data_string *id, + int type, const u_int8_t *data, unsigned len) +{ + unsigned char buf[ISC_MD5_DIGESTLENGTH]; + isc_md5_t md5; + int i; + + /* Types can only be 0..(2^16)-1. */ + if (type < 0 || type > 65535) + return 0; + + /* + * Hexadecimal MD5 digest plus two byte type, NUL, + * and one byte for length for dns. + */ + if (!buffer_allocate (&id -> buffer, + (ISC_MD5_DIGESTLENGTH * 2) + 4, MDL)) + return 0; + id -> data = id -> buffer -> data; + + /* + * DHCP clients and servers should use the following forms of client + * identification, starting with the most preferable, and finishing + * with the least preferable. If the client does not send any of these + * forms of identification, the DHCP/DDNS interaction is not defined by + * this specification. The most preferable form of identification is + * the Globally Unique Identifier Option [TBD]. Next is the DHCP + * Client Identifier option. Last is the client's link-layer address, + * as conveyed in its DHCPREQUEST message. Implementors should note + * that the link-layer address cannot be used if there are no + * significant bytes in the chaddr field of the DHCP client's request, + * because this does not constitute a unique identifier. + * -- "Interaction between DHCP and DNS" + * <draft-ietf-dhc-dhcp-dns-12.txt> + * M. Stapp, Y. Rekhter + * + * We put the length into the first byte to turn + * this into a dns text string. This avoid needing to + * copy the string to add the byte later. + */ + id->buffer->data[0] = ISC_MD5_DIGESTLENGTH * 2 + 2; + + /* Put the type in the next two bytes. */ + id->buffer->data[1] = "0123456789abcdef"[(type >> 4) & 0xf]; + /* This should have been [type & 0xf] but now that + * it is in use we need to leave it this way in order + * to avoid disturbing customer's lease files + */ + id->buffer->data[2] = "0123456789abcdef"[type % 15]; + + /* Mash together an MD5 hash of the identifier. */ + isc_md5_init(&md5); + isc_md5_update(&md5, data, len); + isc_md5_final(&md5, buf); + + /* Convert into ASCII. */ + for (i = 0; i < ISC_MD5_DIGESTLENGTH; i++) { + id->buffer->data[i * 2 + 3] = + "0123456789abcdef"[(buf[i] >> 4) & 0xf]; + id->buffer->data[i * 2 + 4] = + "0123456789abcdef"[buf[i] & 0xf]; + } + + id->len = ISC_MD5_DIGESTLENGTH * 2 + 3; + id->buffer->data[id->len] = 0; + id->terminated = 1; + + return 1; +} + +/* + * The dhcid (text version) that we pass to DNS includes a length byte + * at the start but the text we store in the lease doesn't include the + * length byte. The following routines are to convert between the two + * styles. + * + * When converting from a dhcid to a leaseid we reuse the buffer and + * simply adjust the data pointer and length fields in the data string. + * This avoids any prolems with allocating space. + */ + +void +dhcid_tolease(struct data_string *dhcid, + struct data_string *leaseid) +{ + /* copy the data string then update the fields */ + data_string_copy(leaseid, dhcid, MDL); + leaseid->data++; + leaseid->len--; +} + +isc_result_t +dhcid_fromlease(struct data_string *dhcid, + struct data_string *leaseid) +{ + if (!buffer_allocate(&dhcid->buffer, leaseid->len + 2, MDL)) { + return(ISC_R_FAILURE); + } + + dhcid->data = dhcid->buffer->data; + + dhcid->buffer->data[0] = leaseid->len; + memcpy(dhcid->buffer->data + 1, leaseid->data, leaseid->len); + dhcid->len = leaseid->len + 1; + if (leaseid->terminated == 1) { + dhcid->buffer->data[dhcid->len] = 0; + dhcid->terminated = 1; + } + + return(ISC_R_SUCCESS); +} + +/* + * Construct the dataset for this item. + * This is a fairly simple arrangement as the operations we do are simple. + * If there is data we simply have the rdata point to it - the formatting + * must be correct already. We then link the rdatalist to the rdata and + * create a rdataset from the rdatalist. + */ + +static isc_result_t +make_dns_dataset(dns_rdataclass_t dataclass, + dns_rdatatype_t datatype, + dhcp_ddns_data_t *dataspace, + unsigned char *data, + int datalen, + int ttl) +{ + dns_rdata_t *rdata = &dataspace->rdata; + dns_rdatalist_t *rdatalist = &dataspace->rdatalist; + dns_rdataset_t *rdataset = &dataspace->rdataset; + + isc_region_t region; + + /* set up the rdata */ + dns_rdata_init(rdata); + + if (data == NULL) { + /* No data, set up the rdata fields we care about */ + rdata->flags = DNS_RDATA_UPDATE; + rdata->type = datatype; + rdata->rdclass = dataclass; + } else { + switch(datatype) { + case dns_rdatatype_a: + case dns_rdatatype_aaaa: + case dns_rdatatype_txt: + case dns_rdatatype_dhcid: + case dns_rdatatype_ptr: + /* The data must be in the right format we simply + * need to supply it via the correct structure */ + region.base = data; + region.length = datalen; + dns_rdata_fromregion(rdata, dataclass, datatype, + ®ion); + break; + default: + return(DHCP_R_INVALIDARG); + break; + } + } + + /* setup the datalist and attach the rdata to it */ + dns_rdatalist_init(rdatalist); + rdatalist->type = datatype; + rdatalist->rdclass = dataclass; + rdatalist->ttl = ttl; + ISC_LIST_APPEND(rdatalist->rdata, rdata, link); + + /* convert the datalist to a dataset */ + dns_rdataset_init(rdataset); + dns_rdatalist_tordataset(rdatalist, rdataset); + + return(ISC_R_SUCCESS); +} + +/* + * When a DHCP client or server intends to update an A RR, it first + * prepares a DNS UPDATE query which includes as a prerequisite the + * assertion that the name does not exist. The update section of the + * query attempts to add the new name and its IP address mapping (an A + * RR), and the DHCID RR with its unique client-identity. + * -- "Interaction between DHCP and DNS" + * + * There are two cases, one for the server and one for the client. + * + * For the server the first step will have a request of: + * The name is not in use + * Add an A RR + * Add a DHCID RR (currently txt) + * + * For the client the first step will have a request of: + * The A RR does not exist + * Add an A RR + * Add a DHCID RR (currently txt) + */ + +static isc_result_t +ddns_modify_fwd_add1(dhcp_ddns_cb_t *ddns_cb, + dhcp_ddns_data_t *dataspace, + dns_name_t *pname, + dns_name_t *uname) +{ + isc_result_t result; + + /* Construct the prerequisite list */ + if ((ddns_cb->flags & DDNS_INCLUDE_RRSET) != 0) { + /* The A RR shouldn't exist */ + result = make_dns_dataset(dns_rdataclass_none, + ddns_cb->address_type, + dataspace, NULL, 0, 0); + } else { + /* The name is not in use */ + result = make_dns_dataset(dns_rdataclass_none, + dns_rdatatype_any, + dataspace, NULL, 0, 0); + } + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link); + dataspace++; + + /* Construct the update list */ + /* Add the A RR */ + result = make_dns_dataset(dns_rdataclass_in, ddns_cb->address_type, + dataspace, + (unsigned char *)ddns_cb->address.iabuf, + ddns_cb->address.len, ddns_cb->ttl); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); + dataspace++; + + /* Add the DHCID RR */ + result = make_dns_dataset(dns_rdataclass_in, dns_rdatatype_txt, + dataspace, + (unsigned char *)ddns_cb->dhcid.data, + ddns_cb->dhcid.len, ddns_cb->ttl); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); + + return(ISC_R_SUCCESS); +} + +/* + * If the first update operation fails with YXDOMAIN, the updater can + * conclude that the intended name is in use. The updater then + * attempts to confirm that the DNS name is not being used by some + * other host. The updater prepares a second UPDATE query in which the + * prerequisite is that the desired name has attached to it a DHCID RR + * whose contents match the client identity. The update section of + * this query deletes the existing A records on the name, and adds the + * A record that matches the DHCP binding and the DHCID RR with the + * client identity. + * -- "Interaction between DHCP and DNS" + * + * The message for the second step depends on if we are doing conflict + * resolution. If we are we include a prerequisite. If not we delete + * the DHCID in addition to all A rrsets. + * + * Conflict resolution: + * DHCID RR exists, and matches client identity. + * Delete A RRset. + * Add A RR. + * + * Conflict override: + * Delete DHCID RRs. + * Add DHCID RR + * Delete A RRset. + * Add A RR. + */ + +static isc_result_t +ddns_modify_fwd_add2(dhcp_ddns_cb_t *ddns_cb, + dhcp_ddns_data_t *dataspace, + dns_name_t *pname, + dns_name_t *uname) +{ + isc_result_t result; + + /* + * If we are doing conflict resolution (unset) we use a prereq list. + * If not we delete the DHCID in addition to all A rrsets. + */ + if ((ddns_cb->flags & DDNS_CONFLICT_OVERRIDE) == 0) { + /* Construct the prereq list */ + /* The DHCID RR exists and matches the client identity */ + result = make_dns_dataset(dns_rdataclass_in, dns_rdatatype_txt, + dataspace, + (unsigned char *)ddns_cb->dhcid.data, + ddns_cb->dhcid.len, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link); + dataspace++; + } else { + /* Start constructing the update list. + * Conflict detection override: delete DHCID RRs */ + result = make_dns_dataset(dns_rdataclass_any, + dns_rdatatype_txt, + dataspace, NULL, 0, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); + dataspace++; + + /* Add current DHCID RR */ + result = make_dns_dataset(dns_rdataclass_in, dns_rdatatype_txt, + dataspace, + (unsigned char *)ddns_cb->dhcid.data, + ddns_cb->dhcid.len, ddns_cb->ttl); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); + dataspace++; + } + + /* Start or continue constructing the update list */ + /* Delete the A RRset */ + result = make_dns_dataset(dns_rdataclass_any, ddns_cb->address_type, + dataspace, NULL, 0, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); + dataspace++; + + /* Add the A RR */ + result = make_dns_dataset(dns_rdataclass_in, ddns_cb->address_type, + dataspace, + (unsigned char *)ddns_cb->address.iabuf, + ddns_cb->address.len, ddns_cb->ttl); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); + + return(ISC_R_SUCCESS); +} + +/* + * The entity chosen to handle the A record for this client (either the + * client or the server) SHOULD delete the A record that was added when + * the lease was made to the client. + * + * In order to perform this delete, the updater prepares an UPDATE + * query which contains two prerequisites. The first prerequisite + * asserts that the DHCID RR exists whose data is the client identity + * described in Section 4.3. The second prerequisite asserts that the + * data in the A RR contains the IP address of the lease that has + * expired or been released. + * -- "Interaction between DHCP and DNS" + * + * RFC 4703 has relaxed the prereqisites to only checking the DHCID RR + * and we have adopted that to minizmie problems due to interruptions + * when doing a deletion. + * + * First try has: + * DHCID RR exists, and matches client identity. + * Delete appropriate A RR. + */ + +static isc_result_t +ddns_modify_fwd_rem1(dhcp_ddns_cb_t *ddns_cb, + dhcp_ddns_data_t *dataspace, + dns_name_t *pname, + dns_name_t *uname) +{ + isc_result_t result; + + /* Consruct the prereq list */ + /* The DHCID RR exists and matches the client identity */ + result = make_dns_dataset(dns_rdataclass_in, dns_rdatatype_txt, + dataspace, + (unsigned char *)ddns_cb->dhcid.data, + ddns_cb->dhcid.len, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link); + dataspace++; + + /* Construct the update list */ + /* Delete A RRset */ + result = make_dns_dataset(dns_rdataclass_none, ddns_cb->address_type, + dataspace, + (unsigned char *)ddns_cb->address.iabuf, + ddns_cb->address.len, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); + + return(ISC_R_SUCCESS); +} + +/* + * If the deletion of the A succeeded, and there are no A or AAAA + * records left for this domain, then we can blow away the DHCID + * record as well. We can't blow away the DHCID record above + * because it's possible that more than one record has been added + * to this domain name. + * + * Second query has: + * A RR does not exist. + * AAAA RR does not exist. + * Delete appropriate DHCID RR. + */ + +static isc_result_t +ddns_modify_fwd_rem2(dhcp_ddns_cb_t *ddns_cb, + dhcp_ddns_data_t *dataspace, + dns_name_t *pname, + dns_name_t *uname) +{ + isc_result_t result; + + /* Construct the prereq list */ + /* The A RR does not exist */ + result = make_dns_dataset(dns_rdataclass_none, dns_rdatatype_a, + dataspace, NULL, 0, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link); + dataspace++; + + /* The AAAA RR does not exist */ + result = make_dns_dataset(dns_rdataclass_none, dns_rdatatype_aaaa, + dataspace, NULL, 0, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link); + dataspace++; + + /* Construct the update list */ + /* Delete DHCID RR */ + result = make_dns_dataset(dns_rdataclass_none, dns_rdatatype_txt, + dataspace, + (unsigned char *)ddns_cb->dhcid.data, + ddns_cb->dhcid.len, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); + + return(ISC_R_SUCCESS); +} + +/* + * This routine converts from the task action call into something + * easier to work with. It also handles the common case of a signature + * or zone not being correct. + */ +void ddns_interlude(isc_task_t *taskp, + isc_event_t *eventp) +{ + dhcp_ddns_cb_t *ddns_cb = (dhcp_ddns_cb_t *)eventp->ev_arg; + dns_clientupdateevent_t *ddns_event = (dns_clientupdateevent_t *)eventp; + isc_result_t eresult = ddns_event->result; + isc_result_t result; + + /* We've extracted the information we want from it, get rid of + * the event block.*/ + isc_event_free(&eventp); + +#if defined (TRACING) + if (trace_record()) { + trace_ddns_input_write(ddns_cb, eresult); + } +#endif + +#if defined (DEBUG_DNS_UPDATES) + print_dns_status(DDNS_PRINT_INBOUND, ddns_cb, eresult); +#endif + + /* This transaction is complete, clear the value */ + dns_client_destroyupdatetrans(&ddns_cb->transaction); + + /* If we cancelled or tried to cancel the operation we just + * need to clean up. */ + if ((eresult == ISC_R_CANCELED) || + ((ddns_cb->flags & DDNS_ABORT) != 0)) { +#if defined (DEBUG_DNS_UPDATES) + log_info("DDNS: completeing transaction cancellation cb=%p, " + "flags=%x, %s", + ddns_cb, ddns_cb->flags, isc_result_totext(eresult)); +#endif + if ((ddns_cb->flags & DDNS_ABORT) == 0) { + log_info("DDNS: cleaning up lease pointer for a cancel " + "cb=%p", ddns_cb); + /* + * We shouldn't actually be able to get here but + * we are. This means we haven't cleaned up + * the lease pointer so we need to do that before + * freeing the cb. + */ + ddns_cb->cur_func(ddns_cb, eresult); + return; + } + + if (ddns_cb->next_op != NULL) { + /* if necessary cleanup up next op block */ + ddns_cb_free(ddns_cb->next_op, MDL); + } + ddns_cb_free(ddns_cb, MDL); + return; + } + + /* If we had a problem with our key or zone try again */ + if ((eresult == DNS_R_NOTAUTH) || + (eresult == DNS_R_NOTZONE)) { + int i; + /* Our zone information was questionable, + * repudiate it and try again */ + log_error("DDNS: bad zone information, repudiating zone %s", + ddns_cb->zone_name); + repudiate_zone(&ddns_cb->zone); + ddns_cb->zone_name[0] = 0; + ISC_LIST_INIT(ddns_cb->zone_server_list); + for (i = 0; i < DHCP_MAXNS; i++) { + ISC_LINK_INIT(&ddns_cb->zone_addrs[i], link); + } + + if ((ddns_cb->state == DDNS_STATE_ADD_PTR) || + (ddns_cb->state == DDNS_STATE_REM_PTR)) { + result = ddns_modify_ptr(ddns_cb, MDL); + } else { + result = ddns_modify_fwd(ddns_cb, MDL); + } + + if (result != ISC_R_SUCCESS) { + /* if we couldn't redo the query log it and + * let the next function clean it up */ + log_info("DDNS: Failed to retry after zone failure"); + ddns_cb->cur_func(ddns_cb, result); + } + return; + } else { + /* pass it along to be processed */ + ddns_cb->cur_func(ddns_cb, eresult); + } + + return; +} + +/* + * This routine does the generic work for sending a ddns message to + * modify the forward record (A or AAAA) and calls one of a set of + * routines to build the specific message. + */ + +isc_result_t +ddns_modify_fwd(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) +{ + isc_result_t result; + dns_tsec_t *tsec_key = NULL; + + unsigned char *clientname; + dhcp_ddns_data_t *dataspace = NULL; + dns_namelist_t prereqlist, updatelist; + dns_fixedname_t zname0, pname0, uname0; + dns_name_t *zname = NULL, *pname, *uname; + + isc_sockaddrlist_t *zlist = NULL; + + /* Get a pointer to the clientname to make things easier. */ + clientname = (unsigned char *)ddns_cb->fwd_name.data; + + /* Extract and validate the type of the address. */ + if (ddns_cb->address.len == 4) { + ddns_cb->address_type = dns_rdatatype_a; + } else if (ddns_cb->address.len == 16) { + ddns_cb->address_type = dns_rdatatype_aaaa; + } else { + return DHCP_R_INVALIDARG; + } + + /* + * If we already have a zone use it, otherwise try to lookup the + * zone in our cache. If we find one we will have a pointer to + * the zone that needs to be dereferenced when we are done with it. + * If we don't find one that is okay we'll let the DNS code try and + * find the information for us. + */ + + if (ddns_cb->zone == NULL) { + result = find_cached_zone(ddns_cb, FIND_FORWARD); + } + + /* + * If we have a zone try to get any information we need + * from it - name, addresses and the key. The address + * and key may be empty the name can't be. + */ + if (ddns_cb->zone) { + /* Set up the zone name for use by DNS */ + result = dhcp_isc_name(ddns_cb->zone_name, &zname0, &zname); + if (result != ISC_R_SUCCESS) { + log_error("Unable to build name for zone for " + "fwd update: %s %s", + ddns_cb->zone_name, + isc_result_totext(result)); + goto cleanup; + } + + if (!(ISC_LIST_EMPTY(ddns_cb->zone_server_list))) { + /* If we have any addresses get them */ + zlist = &ddns_cb->zone_server_list; + } + + + if (ddns_cb->zone->key != NULL) { + /* + * Not having a key is fine, having a key + * but not a tsec is odd so we warn the user. + */ + /*sar*/ + /* should we do the warning? */ + tsec_key = ddns_cb->zone->key->tsec_key; + if (tsec_key == NULL) { + log_error("No tsec for use with key %s", + ddns_cb->zone->key->name); + } + } + } + + /* Set up the DNS names for the prereq and update lists */ + if (((result = dhcp_isc_name(clientname, &pname0, &pname)) + != ISC_R_SUCCESS) || + ((result = dhcp_isc_name(clientname, &uname0, &uname)) + != ISC_R_SUCCESS)) { + log_error("Unable to build name for fwd update: %s %s", + clientname, isc_result_totext(result)); + goto cleanup; + } + + /* Allocate the various isc dns library structures we may require. */ + dataspace = isc_mem_get(dhcp_gbl_ctx.mctx, sizeof(*dataspace) * 4); + if (dataspace == NULL) { + log_error("Unable to allocate memory for fwd update"); + result = ISC_R_NOMEMORY; + goto cleanup; + } + + ISC_LIST_INIT(prereqlist); + ISC_LIST_INIT(updatelist); + + switch(ddns_cb->state) { + case DDNS_STATE_ADD_FW_NXDOMAIN: + result = ddns_modify_fwd_add1(ddns_cb, dataspace, + pname, uname); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + ISC_LIST_APPEND(prereqlist, pname, link); + break; + case DDNS_STATE_ADD_FW_YXDHCID: + result = ddns_modify_fwd_add2(ddns_cb, dataspace, + pname, uname); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* If we aren't doing conflict override we have entries + * in the pname list and we need to attach it to the + * prereqlist */ + + if ((ddns_cb->flags & DDNS_CONFLICT_OVERRIDE) == 0) { + ISC_LIST_APPEND(prereqlist, pname, link); + } + + break; + case DDNS_STATE_REM_FW_YXDHCID: + result = ddns_modify_fwd_rem1(ddns_cb, dataspace, + pname, uname); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + ISC_LIST_APPEND(prereqlist, pname, link); + break; + case DDNS_STATE_REM_FW_NXRR: + result = ddns_modify_fwd_rem2(ddns_cb, dataspace, + pname, uname); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + ISC_LIST_APPEND(prereqlist, pname, link); + break; + + default: + log_error("Invalid operation in ddns code."); + result = DHCP_R_INVALIDARG; + goto cleanup; + break; + } + + /* + * We always have an update list but may not have a prereqlist + * if we are doing conflict override. + */ + ISC_LIST_APPEND(updatelist, uname, link); + + /* send the message, cleanup and return the result */ + result = ddns_update(dhcp_gbl_ctx.dnsclient, + dns_rdataclass_in, zname, + &prereqlist, &updatelist, + zlist, tsec_key, + DNS_CLIENTRESOPT_ALLOWRUN, + dhcp_gbl_ctx.task, + ddns_interlude, + (void *)ddns_cb, + &ddns_cb->transaction); + if (result == ISC_R_FAMILYNOSUPPORT) { + log_info("Unable to perform DDNS update, " + "address family not supported"); + } + +#if defined (DEBUG_DNS_UPDATES) + print_dns_status(DDNS_PRINT_OUTBOUND, ddns_cb, result); +#endif + + cleanup: +#if defined (DEBUG_DNS_UPDATES) + if (result != ISC_R_SUCCESS) { + log_info("DDNS: %s(%d): error in ddns_modify_fwd %s for %p", + file, line, isc_result_totext(result), ddns_cb); + } +#endif + + if (dataspace != NULL) { + isc_mem_put(dhcp_gbl_ctx.mctx, dataspace, + sizeof(*dataspace) * 4); + } + return(result); +} + + +isc_result_t +ddns_modify_ptr(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) +{ + isc_result_t result; + dns_tsec_t *tsec_key = NULL; + unsigned char *ptrname; + dhcp_ddns_data_t *dataspace = NULL; + dns_namelist_t updatelist; + dns_fixedname_t zname0, uname0; + dns_name_t *zname = NULL, *uname; + isc_sockaddrlist_t *zlist = NULL; + unsigned char buf[256]; + int buflen; + + /* + * Try to lookup the zone in the zone cache. As with the forward + * case it's okay if we don't have one, the DNS code will try to + * find something also if we succeed we will need to dereference + * the zone later. Unlike with the forward case we assume we won't + * have a pre-existing zone. + */ + result = find_cached_zone(ddns_cb, FIND_REVERSE); + if ((result == ISC_R_SUCCESS) && + !(ISC_LIST_EMPTY(ddns_cb->zone_server_list))) { + /* Set up the zone name for use by DNS */ + result = dhcp_isc_name(ddns_cb->zone_name, &zname0, &zname); + if (result != ISC_R_SUCCESS) { + log_error("Unable to build name for zone for " + "fwd update: %s %s", + ddns_cb->zone_name, + isc_result_totext(result)); + goto cleanup; + } + /* If we have any addresses get them */ + if (!(ISC_LIST_EMPTY(ddns_cb->zone_server_list))) { + zlist = &ddns_cb->zone_server_list; + } + + /* + * If we now have a zone try to get the key, NULL is okay, + * having a key but not a tsec is odd so we warn. + */ + /*sar*/ + /* should we do the warning if we have a key but no tsec? */ + if ((ddns_cb->zone != NULL) && (ddns_cb->zone->key != NULL)) { + tsec_key = ddns_cb->zone->key->tsec_key; + if (tsec_key == NULL) { + log_error("No tsec for use with key %s", + ddns_cb->zone->key->name); + } + } + } + + /* We must have a name for the update list */ + /* Get a pointer to the ptrname to make things easier. */ + ptrname = (unsigned char *)ddns_cb->rev_name.data; + + if ((result = dhcp_isc_name(ptrname, &uname0, &uname)) + != ISC_R_SUCCESS) { + log_error("Unable to build name for fwd update: %s %s", + ptrname, isc_result_totext(result)); + goto cleanup; + } + + /* + * Allocate the various isc dns library structures we may require. + * Allocating one blob avoids being halfway through the process + * and being unable to allocate as well as making the free easy. + */ + dataspace = isc_mem_get(dhcp_gbl_ctx.mctx, sizeof(*dataspace) * 2); + if (dataspace == NULL) { + log_error("Unable to allocate memory for fwd update"); + result = ISC_R_NOMEMORY; + goto cleanup; + } + + ISC_LIST_INIT(updatelist); + + /* + * Construct the update list + * We always delete what's currently there + * Delete PTR RR. + */ + result = make_dns_dataset(dns_rdataclass_any, dns_rdatatype_ptr, + &dataspace[0], NULL, 0, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + ISC_LIST_APPEND(uname->list, &dataspace[0].rdataset, link); + + /* + * If we are updating the pointer we then add the new one + * Add PTR RR. + */ + if (ddns_cb->state == DDNS_STATE_ADD_PTR) { +#if 0 + /* + * I've left this dead code in the file for now in case + * we decide to try and get rid of the ns_name functions. + * sar + */ + + /* + * Need to convert pointer into on the wire representation + * We replace the '.' characters with the lengths of the + * next name and add a length to the beginning for the first + * name. + */ + if (ddns_cb->fwd_name.len == 1) { + /* the root */ + buf[0] = 0; + buflen = 1; + } else { + unsigned char *cp; + buf[0] = '.'; + memcpy(&buf[1], ddns_cb->fwd_name.data, + ddns_cb->fwd_name.len); + for(cp = buf + ddns_cb->fwd_name.len, buflen = 0; + cp != buf; + cp--) { + if (*cp == '.') { + *cp = buflen; + buflen = 0; + } else { + buflen++; + } + } + *cp = buflen; + buflen = ddns_cb->fwd_name.len + 1; + } +#endif + /* + * Need to convert pointer into on the wire representation + */ + if (MRns_name_pton((char *)ddns_cb->fwd_name.data, + buf, 256) == -1) { + goto cleanup; + } + buflen = 0; + while (buf[buflen] != 0) { + buflen += buf[buflen] + 1; + } + buflen++; + + result = make_dns_dataset(dns_rdataclass_in, + dns_rdatatype_ptr, + &dataspace[1], + buf, buflen, ddns_cb->ttl); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + ISC_LIST_APPEND(uname->list, &dataspace[1].rdataset, link); + } + + ISC_LIST_APPEND(updatelist, uname, link); + + /*sar*/ + /* + * for now I'll cleanup the dataset immediately, it would be + * more efficient to keep it around in case the signaturure failed + * and we wanted to retry it. + */ + /* send the message, cleanup and return the result */ + result = ddns_update((dns_client_t *)dhcp_gbl_ctx.dnsclient, + dns_rdataclass_in, zname, + NULL, &updatelist, + zlist, tsec_key, + DNS_CLIENTRESOPT_ALLOWRUN, + dhcp_gbl_ctx.task, + ddns_interlude, (void *)ddns_cb, + &ddns_cb->transaction); + if (result == ISC_R_FAMILYNOSUPPORT) { + log_info("Unable to perform DDNS update, " + "address family not supported"); + } + +#if defined (DEBUG_DNS_UPDATES) + print_dns_status(DDNS_PRINT_OUTBOUND, ddns_cb, result); +#endif + + cleanup: +#if defined (DEBUG_DNS_UPDATES) + if (result != ISC_R_SUCCESS) { + log_info("DDNS: %s(%d): error in ddns_modify_ptr %s for %p", + file, line, isc_result_totext(result), ddns_cb); + } +#endif + + if (dataspace != NULL) { + isc_mem_put(dhcp_gbl_ctx.mctx, dataspace, + sizeof(*dataspace) * 2); + } + return(result); +} + +void +ddns_cancel(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) { + ddns_cb->flags |= DDNS_ABORT; + if (ddns_cb->transaction != NULL) { + dns_client_cancelupdate((dns_clientupdatetrans_t *) + ddns_cb->transaction); + } + ddns_cb->lease = NULL; + +#if defined (DEBUG_DNS_UPDATES) + log_info("DDNS: %s(%d): cancelling transaction for %p", + file, line, ddns_cb); +#endif +} + +#endif /* NSUPDATE */ + +HASH_FUNCTIONS (dns_zone, const char *, struct dns_zone, dns_zone_hash_t, + dns_zone_reference, dns_zone_dereference, do_case_hash) diff --git a/common/ethernet.c b/common/ethernet.c new file mode 100644 index 0000000..cd67ae2 --- /dev/null +++ b/common/ethernet.c @@ -0,0 +1,87 @@ +/* ethernet.c + + Packet assembly code, originally contributed by Archie Cobbs. */ + +/* + * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" + +#if defined (PACKET_ASSEMBLY) || defined (PACKET_DECODING) +#include "includes/netinet/if_ether.h" +#endif /* PACKET_ASSEMBLY || PACKET_DECODING */ + +#if defined (PACKET_ASSEMBLY) +/* Assemble an hardware header... */ + +void assemble_ethernet_header (interface, buf, bufix, to) + struct interface_info *interface; + unsigned char *buf; + unsigned *bufix; + struct hardware *to; +{ + struct isc_ether_header eh; + + if (to && to -> hlen == 7) /* XXX */ + memcpy (eh.ether_dhost, &to -> hbuf [1], + sizeof eh.ether_dhost); + else + memset (eh.ether_dhost, 0xff, sizeof (eh.ether_dhost)); + if (interface -> hw_address.hlen - 1 == sizeof (eh.ether_shost)) + memcpy (eh.ether_shost, &interface -> hw_address.hbuf [1], + sizeof (eh.ether_shost)); + else + memset (eh.ether_shost, 0x00, sizeof (eh.ether_shost)); + + eh.ether_type = htons (ETHERTYPE_IP); + + memcpy (&buf [*bufix], &eh, ETHER_HEADER_SIZE); + *bufix += ETHER_HEADER_SIZE; +} +#endif /* PACKET_ASSEMBLY */ + +#ifdef PACKET_DECODING +/* Decode a hardware header... */ + +ssize_t decode_ethernet_header (interface, buf, bufix, from) + struct interface_info *interface; + unsigned char *buf; + unsigned bufix; + struct hardware *from; +{ + struct isc_ether_header eh; + + memcpy (&eh, buf + bufix, ETHER_HEADER_SIZE); + +#ifdef USERLAND_FILTER + if (ntohs (eh.ether_type) != ETHERTYPE_IP) + return -1; +#endif + memcpy (&from -> hbuf [1], eh.ether_shost, sizeof (eh.ether_shost)); + from -> hbuf [0] = ARPHRD_ETHER; + from -> hlen = (sizeof eh.ether_shost) + 1; + + return ETHER_HEADER_SIZE; +} +#endif /* PACKET_DECODING */ diff --git a/common/execute.c b/common/execute.c new file mode 100644 index 0000000..d031392 --- /dev/null +++ b/common/execute.c @@ -0,0 +1,1157 @@ +/* execute.c + + Support for executable statements. */ + +/* + * Copyright (c) 2009,2013,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2007 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1998-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" +#include <omapip/omapip_p.h> +#include <sys/types.h> +#include <sys/wait.h> + +int execute_statements (result, packet, lease, client_state, + in_options, out_options, scope, statements) + struct binding_value **result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *out_options; + struct binding_scope **scope; + struct executable_statement *statements; +{ + struct executable_statement *r, *e, *next; + int rc; + int status; + struct binding *binding; + struct data_string ds; + struct binding_scope *ns; + + if (!statements) + return 1; + + r = (struct executable_statement *)0; + next = (struct executable_statement *)0; + e = (struct executable_statement *)0; + executable_statement_reference (&r, statements, MDL); + while (r && !(result && *result)) { + if (r -> next) + executable_statement_reference (&next, r -> next, MDL); + switch (r -> op) { + case statements_statement: +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: statements"); +#endif + status = execute_statements (result, packet, lease, + client_state, in_options, + out_options, scope, + r -> data.statements); +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: statements returns %d", status); +#endif + if (!status) + return 0; + break; + + case on_statement: + if (lease) { + if (r -> data.on.evtypes & ON_EXPIRY) { +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: on expiry"); +#endif + if (lease -> on_expiry) + executable_statement_dereference + (&lease -> on_expiry, MDL); + if (r -> data.on.statements) + executable_statement_reference + (&lease -> on_expiry, + r -> data.on.statements, MDL); + } + if (r -> data.on.evtypes & ON_RELEASE) { +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: on release"); +#endif + if (lease -> on_release) + executable_statement_dereference + (&lease -> on_release, MDL); + if (r -> data.on.statements) + executable_statement_reference + (&lease -> on_release, + r -> data.on.statements, MDL); + } + if (r -> data.on.evtypes & ON_COMMIT) { +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: on commit"); +#endif + if (lease -> on_commit) + executable_statement_dereference + (&lease -> on_commit, MDL); + if (r -> data.on.statements) + executable_statement_reference + (&lease -> on_commit, + r -> data.on.statements, MDL); + } + } + break; + + case switch_statement: +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: switch"); +#endif + status = (find_matching_case + (&e, packet, lease, client_state, + in_options, out_options, scope, + r -> data.s_switch.expr, + r -> data.s_switch.statements)); +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: switch: case %lx", (unsigned long)e); +#endif + if (status) { + if (!(execute_statements + (result, packet, lease, client_state, + in_options, out_options, scope, e))) { + executable_statement_dereference + (&e, MDL); + return 0; + } + executable_statement_dereference (&e, MDL); + } + break; + + /* These have no effect when executed. */ + case case_statement: + case default_statement: + break; + + case if_statement: + status = (evaluate_boolean_expression + (&rc, packet, + lease, client_state, in_options, + out_options, scope, r -> data.ie.expr)); + +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: if %s", (status + ? (rc ? "true" : "false") + : "NULL")); +#endif + /* XXX Treat NULL as false */ + if (!status) + rc = 0; + if (!execute_statements + (result, packet, lease, client_state, + in_options, out_options, scope, + rc ? r -> data.ie.tc : r -> data.ie.fc)) + return 0; + break; + + case eval_statement: + status = evaluate_expression + ((struct binding_value **)0, + packet, lease, client_state, in_options, + out_options, scope, r -> data.eval, MDL); +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: evaluate: %s", + (status ? "succeeded" : "failed")); +#else + POST(status); +#endif + break; + + case execute_statement: { +#ifdef ENABLE_EXECUTE + struct expression *expr; + char **argv; + int i, argc = r->data.execute.argc; + pid_t p; + + /* save room for the command and the NULL terminator */ + argv = dmalloc((argc + 2) * sizeof(*argv), MDL); + if (!argv) + break; + + argv[0] = dmalloc(strlen(r->data.execute.command) + 1, + MDL); + if (argv[0]) { + strcpy(argv[0], r->data.execute.command); + } else { + goto execute_out; + } + + log_debug("execute_statement argv[0] = %s", argv[0]); + + for (i = 1, expr = r->data.execute.arglist; expr; + expr = expr->data.arg.next, i++) { + memset (&ds, 0, sizeof(ds)); + status = (evaluate_data_expression + (&ds, packet, + lease, client_state, in_options, + out_options, scope, + expr->data.arg.val, MDL)); + if (status) { + argv[i] = dmalloc(ds.len + 1, MDL); + if (argv[i]) { + memcpy(argv[i], ds.data, + ds.len); + argv[i][ds.len] = 0; + log_debug("execute_statement argv[%d] = %s", i, argv[i]); + } + data_string_forget (&ds, MDL); + if (!argv[i]) { + log_debug("execute_statement failed argv[%d]", i); + goto execute_out; + } + } else { + log_debug("execute: bad arg %d", i); + goto execute_out; + } + } + argv[i] = NULL; + + if ((p = fork()) > 0) { + int status; + waitpid(p, &status, 0); + + if (status) { + log_error("execute: %s exit status %d", + argv[0], status); + } + } else if (p == 0) { + execvp(argv[0], argv); + log_error("Unable to execute %s: %m", argv[0]); + _exit(127); + } else { + log_error("execute: fork() failed"); + } + + execute_out: + for (i = 0; i <= argc; i++) { + if(argv[i]) + dfree(argv[i], MDL); + } + + dfree(argv, MDL); +#else /* !ENABLE_EXECUTE */ + log_fatal("Impossible case at %s:%d (ENABLE_EXECUTE " + "is not defined).", MDL); +#endif /* ENABLE_EXECUTE */ + break; + } + + case return_statement: + status = evaluate_expression + (result, packet, + lease, client_state, in_options, + out_options, scope, r -> data.retval, MDL); +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: return: %s", + (status ? "succeeded" : "failed")); +#else + POST(status); +#endif + break; + + case add_statement: +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: add %s", (r -> data.add -> name + ? r -> data.add -> name + : "<unnamed class>")); +#endif + classify (packet, r -> data.add); + break; + + case break_statement: +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: break"); +#endif + return 1; + + case supersede_option_statement: + case send_option_statement: +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: %s option %s.%s", + (r -> op == supersede_option_statement + ? "supersede" : "send"), + r -> data.option -> option -> universe -> name, + r -> data.option -> option -> name); + goto option_statement; +#endif + case default_option_statement: +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: default option %s.%s", + r -> data.option -> option -> universe -> name, + r -> data.option -> option -> name); + goto option_statement; +#endif + case append_option_statement: +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: append option %s.%s", + r -> data.option -> option -> universe -> name, + r -> data.option -> option -> name); + goto option_statement; +#endif + case prepend_option_statement: +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: prepend option %s.%s", + r -> data.option -> option -> universe -> name, + r -> data.option -> option -> name); + option_statement: +#endif + set_option (r -> data.option -> option -> universe, + out_options, r -> data.option, r -> op); + break; + + case set_statement: + case define_statement: + status = 1; + if (!scope) { + log_error("set %s: no scope", + r->data.set.name); + break; + } + if (!*scope) { + if (!binding_scope_allocate(scope, MDL)) { + log_error("set %s: can't allocate scope", + r->data.set.name); + break; + } + } + binding = find_binding(*scope, r->data.set.name); +#if defined (DEBUG_EXPRESSIONS) + log_debug("exec: set %s", r->data.set.name); +#else + POST(status); +#endif + if (binding == NULL) { + binding = dmalloc(sizeof(*binding), MDL); + if (binding != NULL) { + memset(binding, 0, sizeof(*binding)); + binding->name = + dmalloc(strlen + (r->data.set.name) + 1, + MDL); + if (binding->name != NULL) { + strcpy(binding->name, r->data.set.name); + binding->next = (*scope)->bindings; + (*scope)->bindings = binding; + } else { + dfree(binding, MDL); + binding = NULL; + } + } + } + if (binding != NULL) { + if (binding->value != NULL) + binding_value_dereference + (&binding->value, MDL); + if (r->op == set_statement) { + status = (evaluate_expression + (&binding->value, packet, + lease, client_state, + in_options, out_options, + scope, r->data.set.expr, + MDL)); + } else { + if (!(binding_value_allocate + (&binding->value, MDL))) { + dfree(binding, MDL); + binding = NULL; + } + if ((binding != NULL) && + (binding->value != NULL)) { + binding->value->type = + binding_function; + (fundef_reference + (&binding->value->value.fundef, + r->data.set.expr->data.func, + MDL)); + } + } + } +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: set %s%s", r -> data.set.name, + (binding && status ? "" : " (failed)")); +#else + POST(status); +#endif + break; + + case unset_statement: + if (!scope || !*scope) + break; + binding = find_binding (*scope, r -> data.unset); + if (binding) { + if (binding -> value) + binding_value_dereference + (&binding -> value, MDL); + status = 1; + } else + status = 0; +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: unset %s: %s", r -> data.unset, + (status ? "found" : "not found")); +#else + POST(status); +#endif + break; + + case let_statement: +#if defined (DEBUG_EXPRESSIONS) + log_debug("exec: let %s", r->data.let.name); +#endif + status = 0; + ns = NULL; + binding_scope_allocate (&ns, MDL); + e = r; + + next_let: + if (ns) { + binding = dmalloc(sizeof(*binding), MDL); + memset(binding, 0, sizeof(*binding)); + if (!binding) { + blb: + binding_scope_dereference(&ns, MDL); + } else { + binding->name = + dmalloc(strlen + (e->data.let.name + 1), + MDL); + if (binding->name) + strcpy(binding->name, + e->data.let.name); + else { + dfree(binding, MDL); + binding = NULL; + goto blb; + } + } + } else + binding = NULL; + + if (ns && binding) { + status = (evaluate_expression + (&binding->value, packet, lease, + client_state, + in_options, out_options, + scope, e->data.set.expr, MDL)); + binding->next = ns->bindings; + ns->bindings = binding; + } + +#if defined (DEBUG_EXPRESSIONS) + log_debug("exec: let %s%s", e->data.let.name, + (binding && status ? "" : "failed")); +#else + POST(status); +#endif + if (!e->data.let.statements) { + } else if (e->data.let.statements->op == + let_statement) { + e = e->data.let.statements; + goto next_let; + } else if (ns) { + if (scope && *scope) + binding_scope_reference(&ns->outer, + *scope, MDL); + execute_statements + (result, packet, lease, + client_state, + in_options, out_options, + &ns, e->data.let.statements); + } + if (ns) + binding_scope_dereference(&ns, MDL); + break; + + case log_statement: + memset (&ds, 0, sizeof ds); + status = (evaluate_data_expression + (&ds, packet, + lease, client_state, in_options, + out_options, scope, r -> data.log.expr, + MDL)); + +#if defined (DEBUG_EXPRESSIONS) + log_debug ("exec: log"); +#endif + + if (status) { + switch (r -> data.log.priority) { + case log_priority_fatal: + log_fatal ("%.*s", (int)ds.len, + ds.data); + break; + case log_priority_error: + log_error ("%.*s", (int)ds.len, + ds.data); + break; + case log_priority_debug: + log_debug ("%.*s", (int)ds.len, + ds.data); + break; + case log_priority_info: + log_info ("%.*s", (int)ds.len, + ds.data); + break; + } + data_string_forget (&ds, MDL); + } + + break; + + default: + log_error ("bogus statement type %d", r -> op); + break; + } + executable_statement_dereference (&r, MDL); + if (next) { + executable_statement_reference (&r, next, MDL); + executable_statement_dereference (&next, MDL); + } + } + + return 1; +} + +/* Execute all the statements in a particular scope, and all statements in + scopes outer from that scope, but if a particular limiting scope is + reached, do not execute statements in that scope or in scopes outer + from it. More specific scopes need to take precedence over less + specific scopes, so we recursively traverse the scope list, executing + the most outer scope first. */ + +void execute_statements_in_scope (result, packet, + lease, client_state, in_options, out_options, + scope, group, limiting_group) + struct binding_value **result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *out_options; + struct binding_scope **scope; + struct group *group; + struct group *limiting_group; +{ + struct group *limit; + + /* If we've recursed as far as we can, return. */ + if (!group) + return; + + /* As soon as we get to a scope that is outer than the limiting + scope, we are done. This is so that if somebody does something + like this, it does the expected thing: + + domain-name "fugue.com"; + shared-network FOO { + host bar { + domain-name "othello.fugue.com"; + fixed-address 10.20.30.40; + } + subnet 10.20.30.0 netmask 255.255.255.0 { + domain-name "manhattan.fugue.com"; + } + } + + The problem with the above arrangement is that the host's + group nesting will be host -> shared-network -> top-level, + and the limiting scope when we evaluate the host's scope + will be the subnet -> shared-network -> top-level, so we need + to know when we evaluate the host's scope to stop before we + evaluate the shared-networks scope, because it's outer than + the limiting scope, which means we've already evaluated it. */ + + for (limit = limiting_group; limit; limit = limit -> next) { + if (group == limit) + return; + } + + if (group -> next) + execute_statements_in_scope (result, packet, + lease, client_state, + in_options, out_options, scope, + group -> next, limiting_group); + execute_statements (result, packet, lease, client_state, in_options, + out_options, scope, group -> statements); +} + +/* Dereference or free any subexpressions of a statement being freed. */ + +int executable_statement_dereference (ptr, file, line) + struct executable_statement **ptr; + const char *file; + int line; +{ + if (!ptr || !*ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + (*ptr) -> refcnt--; + rc_register (file, line, ptr, *ptr, (*ptr) -> refcnt, 1, RC_MISC); + if ((*ptr) -> refcnt > 0) { + *ptr = (struct executable_statement *)0; + return 1; + } + + if ((*ptr) -> refcnt < 0) { + log_error ("%s(%d): negative refcnt!", file, line); +#if defined (DEBUG_RC_HISTORY) + dump_rc_history (*ptr); +#endif +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + if ((*ptr) -> next) + executable_statement_dereference (&(*ptr) -> next, file, line); + + switch ((*ptr) -> op) { + case statements_statement: + if ((*ptr) -> data.statements) + executable_statement_dereference + (&(*ptr) -> data.statements, file, line); + break; + + case on_statement: + if ((*ptr) -> data.on.statements) + executable_statement_dereference + (&(*ptr) -> data.on.statements, file, line); + break; + + case switch_statement: + if ((*ptr) -> data.s_switch.statements) + executable_statement_dereference + (&(*ptr) -> data.on.statements, file, line); + if ((*ptr) -> data.s_switch.expr) + expression_dereference (&(*ptr) -> data.s_switch.expr, + file, line); + break; + + case case_statement: + if ((*ptr) -> data.s_switch.expr) + expression_dereference (&(*ptr) -> data.c_case, + file, line); + break; + + case if_statement: + if ((*ptr) -> data.ie.expr) + expression_dereference (&(*ptr) -> data.ie.expr, + file, line); + if ((*ptr) -> data.ie.tc) + executable_statement_dereference + (&(*ptr) -> data.ie.tc, file, line); + if ((*ptr) -> data.ie.fc) + executable_statement_dereference + (&(*ptr) -> data.ie.fc, file, line); + break; + + case eval_statement: + if ((*ptr) -> data.eval) + expression_dereference (&(*ptr) -> data.eval, + file, line); + break; + + case return_statement: + if ((*ptr) -> data.eval) + expression_dereference (&(*ptr) -> data.eval, + file, line); + break; + + case set_statement: + if ((*ptr)->data.set.name) + dfree ((*ptr)->data.set.name, file, line); + if ((*ptr)->data.set.expr) + expression_dereference (&(*ptr) -> data.set.expr, + file, line); + break; + + case unset_statement: + if ((*ptr)->data.unset) + dfree ((*ptr)->data.unset, file, line); + break; + + case execute_statement: + if ((*ptr)->data.execute.command) + dfree ((*ptr)->data.execute.command, file, line); + if ((*ptr)->data.execute.arglist) + expression_dereference (&(*ptr) -> data.execute.arglist, + file, line); + break; + + case supersede_option_statement: + case send_option_statement: + case default_option_statement: + case append_option_statement: + case prepend_option_statement: + if ((*ptr) -> data.option) + option_cache_dereference (&(*ptr) -> data.option, + file, line); + break; + + default: + /* Nothing to do. */ + break; + } + + dfree ((*ptr), file, line); + *ptr = (struct executable_statement *)0; + return 1; +} + +void write_statements (file, statements, indent) + FILE *file; + struct executable_statement *statements; + int indent; +{ +#if defined ENABLE_EXECUTE + struct expression *expr; +#endif + struct executable_statement *r, *x; + const char *s, *t, *dot; + int col; + + if (!statements) + return; + + for (r = statements; r; r = r -> next) { + switch (r -> op) { + case statements_statement: + write_statements (file, r -> data.statements, indent); + break; + + case on_statement: + indent_spaces (file, indent); + fprintf (file, "on "); + s = ""; + if (r -> data.on.evtypes & ON_EXPIRY) { + fprintf (file, "%sexpiry", s); + s = " or "; + } + if (r -> data.on.evtypes & ON_COMMIT) { + fprintf (file, "%scommit", s); + s = " or "; + } + if (r -> data.on.evtypes & ON_RELEASE) { + fprintf (file, "%srelease", s); + /* s = " or "; */ + } + if (r -> data.on.statements) { + fprintf (file, " {"); + write_statements (file, + r -> data.on.statements, + indent + 2); + indent_spaces (file, indent); + fprintf (file, "}"); + } else { + fprintf (file, ";"); + } + break; + + case switch_statement: + indent_spaces (file, indent); + fprintf (file, "switch ("); + col = write_expression (file, + r -> data.s_switch.expr, + indent + 7, indent + 7, 1); + col = token_print_indent (file, col, indent + 7, + "", "", ")"); + token_print_indent (file, + col, indent, " ", "", "{"); + write_statements (file, r -> data.s_switch.statements, + indent + 2); + indent_spaces (file, indent); + fprintf (file, "}"); + break; + + case case_statement: + indent_spaces (file, indent - 1); + fprintf (file, "case "); + col = write_expression (file, + r -> data.s_switch.expr, + indent + 5, indent + 5, 1); + token_print_indent (file, col, indent + 5, + "", "", ":"); + break; + + case default_statement: + indent_spaces (file, indent - 1); + fprintf (file, "default: "); + break; + + case if_statement: + indent_spaces (file, indent); + fprintf (file, "if "); + x = r; + col = write_expression (file, + x -> data.ie.expr, + indent + 3, indent + 3, 1); + else_if: + token_print_indent (file, col, indent, " ", "", "{"); + write_statements (file, x -> data.ie.tc, indent + 2); + if (x -> data.ie.fc && + x -> data.ie.fc -> op == if_statement && + !x -> data.ie.fc -> next) { + indent_spaces (file, indent); + fprintf (file, "} elsif "); + x = x -> data.ie.fc; + col = write_expression (file, + x -> data.ie.expr, + indent + 6, + indent + 6, 1); + goto else_if; + } + if (x -> data.ie.fc) { + indent_spaces (file, indent); + fprintf (file, "} else {"); + write_statements (file, x -> data.ie.fc, + indent + 2); + } + indent_spaces (file, indent); + fprintf (file, "}"); + break; + + case eval_statement: + indent_spaces (file, indent); + fprintf (file, "eval "); + (void) write_expression (file, r -> data.eval, + indent + 5, indent + 5, 1); + fprintf (file, ";"); + break; + + case return_statement: + indent_spaces (file, indent); + fprintf (file, "return;"); + break; + + case add_statement: + indent_spaces (file, indent); + fprintf (file, "add \"%s\"", r -> data.add -> name); + break; + + case break_statement: + indent_spaces (file, indent); + fprintf (file, "break;"); + break; + + case supersede_option_statement: + case send_option_statement: + s = "supersede"; + goto option_statement; + + case default_option_statement: + s = "default"; + goto option_statement; + + case append_option_statement: + s = "append"; + goto option_statement; + + case prepend_option_statement: + s = "prepend"; + option_statement: + /* Note: the reason we don't try to pretty print + the option here is that the format of the option + may change in dhcpd.conf, and then when this + statement was read back, it would cause a syntax + error. */ + if (r -> data.option -> option -> universe == + &dhcp_universe) { + t = ""; + dot = ""; + } else { + t = (r -> data.option -> option -> + universe -> name); + dot = "."; + } + indent_spaces (file, indent); + fprintf (file, "%s %s%s%s = ", s, t, dot, + r -> data.option -> option -> name); + col = (indent + strlen (s) + strlen (t) + + strlen (dot) + strlen (r -> data.option -> + option -> name) + 4); + if (r -> data.option -> expression) + write_expression + (file, + r -> data.option -> expression, + col, indent + 8, 1); + else + token_indent_data_string + (file, col, indent + 8, "", "", + &r -> data.option -> data); + + fprintf (file, ";"); /* XXX */ + break; + + case set_statement: + indent_spaces (file, indent); + fprintf (file, "set "); + col = token_print_indent (file, indent + 4, indent + 4, + "", "", r -> data.set.name); + (void) token_print_indent (file, col, indent + 4, + " ", " ", "="); + col = write_expression (file, r -> data.set.expr, + indent + 3, indent + 3, 0); + (void) token_print_indent (file, col, indent + 4, + " ", "", ";"); + break; + + case unset_statement: + indent_spaces (file, indent); + fprintf (file, "unset "); + col = token_print_indent (file, indent + 6, indent + 6, + "", "", r -> data.set.name); + (void) token_print_indent (file, col, indent + 6, + " ", "", ";"); + break; + + case log_statement: + indent_spaces (file, indent); + fprintf (file, "log "); + col = token_print_indent (file, indent + 4, indent + 4, + "", "", "("); + switch (r -> data.log.priority) { + case log_priority_fatal: + (void) token_print_indent + (file, col, indent + 4, "", + " ", "fatal,"); + break; + case log_priority_error: + (void) token_print_indent + (file, col, indent + 4, "", + " ", "error,"); + break; + case log_priority_debug: + (void) token_print_indent + (file, col, indent + 4, "", + " ", "debug,"); + break; + case log_priority_info: + (void) token_print_indent + (file, col, indent + 4, "", + " ", "info,"); + break; + } + col = write_expression (file, r -> data.log.expr, + indent + 4, indent + 4, 0); + (void) token_print_indent (file, col, indent + 4, + "", "", ");"); + + break; + + case execute_statement: +#ifdef ENABLE_EXECUTE + indent_spaces (file, indent); + col = token_print_indent(file, indent + 4, indent + 4, + "", "", "execute"); + col = token_print_indent(file, col, indent + 4, " ", "", + "("); + col = token_print_indent(file, col, indent + 4, "\"", "\"", r->data.execute.command); + for (expr = r->data.execute.arglist; expr; expr = expr->data.arg.next) { + col = token_print_indent(file, col, indent + 4, "", " ", ","); + col = write_expression (file, expr->data.arg.val, col, indent + 4, 0); + } + (void) token_print_indent(file, col, indent + 4, "", "", ");"); +#else /* !ENABLE_EXECUTE */ + log_fatal("Impossible case at %s:%d (ENABLE_EXECUTE " + "is not defined).", MDL); +#endif /* ENABLE_EXECUTE */ + break; + + default: + log_fatal ("bogus statement type %d\n", r -> op); + } + } +} + +/* Find a case statement in the sequence of executable statements that + matches the expression, and if found, return the following statement. + If no case statement matches, try to find a default statement and + return that (the default statement can precede all the case statements). + Otherwise, return the null statement. */ + +int find_matching_case (struct executable_statement **ep, + struct packet *packet, struct lease *lease, + struct client_state *client_state, + struct option_state *in_options, + struct option_state *out_options, + struct binding_scope **scope, + struct expression *expr, + struct executable_statement *stmt) +{ + int status, sub; + struct executable_statement *s; + + if (is_data_expression (expr)) { + struct data_string cd, ds; + memset (&ds, 0, sizeof ds); + memset (&cd, 0, sizeof cd); + + status = (evaluate_data_expression (&ds, packet, lease, + client_state, in_options, + out_options, scope, expr, + MDL)); + if (status) { + for (s = stmt; s; s = s -> next) { + if (s -> op == case_statement) { + sub = (evaluate_data_expression + (&cd, packet, lease, client_state, + in_options, out_options, + scope, s -> data.c_case, MDL)); + if (sub && cd.len == ds.len && + !memcmp (cd.data, ds.data, cd.len)) + { + data_string_forget (&cd, MDL); + data_string_forget (&ds, MDL); + executable_statement_reference + (ep, s -> next, MDL); + return 1; + } + data_string_forget (&cd, MDL); + } + } + data_string_forget (&ds, MDL); + } + } else { + unsigned long n, c; + status = evaluate_numeric_expression (&n, packet, lease, + client_state, + in_options, out_options, + scope, expr); + + if (status) { + for (s = stmt; s; s = s -> next) { + if (s -> op == case_statement) { + sub = (evaluate_numeric_expression + (&c, packet, lease, client_state, + in_options, out_options, + scope, s -> data.c_case)); + if (sub && n == c) { + executable_statement_reference + (ep, s -> next, MDL); + return 1; + } + } + } + } + } + + /* If we didn't find a matching case statement, look for a default + statement and return the statement following it. */ + for (s = stmt; s; s = s -> next) + if (s -> op == default_statement) + break; + if (s) { + executable_statement_reference (ep, s -> next, MDL); + return 1; + } + return 0; +} + +int executable_statement_foreach (struct executable_statement *stmt, + int (*callback) (struct + executable_statement *, + void *, int), + void *vp, int condp) +{ + struct executable_statement *foo; + int ok = 0; + + for (foo = stmt; foo; foo = foo -> next) { + if ((*callback) (foo, vp, condp) != 0) + ok = 1; + switch (foo -> op) { + case null_statement: + break; + case if_statement: + if (executable_statement_foreach (foo -> data.ie.tc, + callback, vp, 1)) + ok = 1; + if (executable_statement_foreach (foo -> data.ie.fc, + callback, vp, 1)) + ok = 1; + break; + case add_statement: + break; + case eval_statement: + break; + case break_statement: + break; + case default_option_statement: + break; + case supersede_option_statement: + break; + case append_option_statement: + break; + case prepend_option_statement: + break; + case send_option_statement: + break; + case statements_statement: + if ((executable_statement_foreach + (foo -> data.statements, callback, vp, condp))) + ok = 1; + break; + case on_statement: + if ((executable_statement_foreach + (foo -> data.on.statements, callback, vp, 1))) + ok = 1; + break; + case switch_statement: + if ((executable_statement_foreach + (foo -> data.s_switch.statements, callback, vp, 1))) + ok = 1; + break; + case case_statement: + break; + case default_statement: + break; + case set_statement: + break; + case unset_statement: + break; + case let_statement: + if ((executable_statement_foreach + (foo -> data.let.statements, callback, vp, 0))) + ok = 1; + break; + case define_statement: + break; + case log_statement: + case return_statement: + case execute_statement: + break; + } + } + return ok; +} diff --git a/common/fddi.c b/common/fddi.c new file mode 100644 index 0000000..d998193 --- /dev/null +++ b/common/fddi.c @@ -0,0 +1,86 @@ +/* fddi.c + + Packet assembly code, originally contributed by Archie Cobbs. */ + +/* + * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" + +#if defined (DEC_FDDI) +#include <netinet/if_fddi.h> +#include <net/if_llc.h> + +#if defined (PACKET_ASSEMBLY) || defined (PACKET_DECODING) +#include "includes/netinet/if_ether.h" +#endif /* PACKET_ASSEMBLY || PACKET_DECODING */ + +#if defined (PACKET_ASSEMBLY) +/* Assemble an hardware header... */ + +void assemble_fddi_header (interface, buf, bufix, to) + struct interface_info *interface; + unsigned char *buf; + unsigned *bufix; + struct hardware *to; +{ + struct fddi_header fh; + struct llc lh; + + if (to && to -> hlen == 7) + memcpy (fh.fddi_dhost, &to -> hbuf [1], + sizeof (fh.fddi_dhost)); + memcpy (fh.fddi_shost, + &interface -> hw_address.hbuf [1], sizeof (fh.fddi_shost)); + fh.fddi_fc = FDDIFC_LLC_ASYNC; + memcpy (&buf [*bufix], &fh, sizeof fh); + *bufix += sizeof fh; + + lh.llc_dsap = LLC_SNAP_LSAP; + lh.llc_ssap = LLC_SNAP_LSAP; + lh.llc_un.type_snap.control = LLC_UI; + lh.llc_un.type_snap.ether_type = htons (ETHERTYPE_IP); + memcpy (&buf [*bufix], &lh, LLC_SNAP_LEN); + *bufix += LLC_SNAP_LEN; +} +#endif /* PACKET_ASSEMBLY */ + +#ifdef PACKET_DECODING +/* Decode a hardware header... */ + +ssize_t decode_fddi_header (interface, buf, bufix, from) + struct interface_info *interface; + unsigned char *buf; + unsigned bufix; + struct hardware *from; +{ + struct fddi_header fh; + struct llc lh; + + from -> hbuf [0] = HTYPE_FDDI; + memcpy (&from -> hbuf [1], fh.fddi_shost, sizeof fh.fddi_shost); + return FDDI_HEADER_SIZE + LLC_SNAP_LEN; +} +#endif /* PACKET_DECODING */ +#endif /* DEC_FDDI */ diff --git a/common/icmp.c b/common/icmp.c new file mode 100644 index 0000000..6f97f67 --- /dev/null +++ b/common/icmp.c @@ -0,0 +1,300 @@ +/* dhcp.c + + ICMP Protocol engine - for sending out pings and receiving + responses. */ + +/* + * Copyright (c) 2011,2013,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004,2007,2009 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" +#include "netinet/ip.h" +#include "netinet/ip_icmp.h" + +struct icmp_state *icmp_state; +static omapi_object_type_t *dhcp_type_icmp; +static int no_icmp; + +OMAPI_OBJECT_ALLOC (icmp_state, struct icmp_state, dhcp_type_icmp) + +#if defined (TRACING) +trace_type_t *trace_icmp_input; +trace_type_t *trace_icmp_output; +#endif + +/* Initialize the ICMP protocol. */ + +void icmp_startup (routep, handler) + int routep; + void (*handler) (struct iaddr, u_int8_t *, int); +{ + struct protoent *proto; + int protocol = 1; + int state; + isc_result_t result; + + /* Only initialize icmp once. */ + if (dhcp_type_icmp) + log_fatal ("attempted to reinitialize icmp protocol"); + + result = omapi_object_type_register (&dhcp_type_icmp, "icmp", + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + sizeof (struct icmp_state), + 0, RC_MISC); + + if (result != ISC_R_SUCCESS) + log_fatal ("Can't register icmp object type: %s", + isc_result_totext (result)); + + icmp_state_allocate (&icmp_state, MDL); + icmp_state -> icmp_handler = handler; + +#if defined (TRACING) + trace_icmp_input = trace_type_register ("icmp-input", (void *)0, + trace_icmp_input_input, + trace_icmp_input_stop, MDL); + trace_icmp_output = trace_type_register ("icmp-output", (void *)0, + trace_icmp_output_input, + trace_icmp_output_stop, MDL); + + /* If we're playing back a trace file, don't create the socket + or set up the callback. */ + if (!trace_playback ()) { +#endif + /* Get the protocol number (should be 1). */ + proto = getprotobyname ("icmp"); + if (proto) + protocol = proto -> p_proto; + + /* Get a raw socket for the ICMP protocol. */ + icmp_state -> socket = socket (AF_INET, SOCK_RAW, protocol); + if (icmp_state -> socket < 0) { + no_icmp = 1; + log_error ("unable to create icmp socket: %m"); + return; + } + +#if defined (HAVE_SETFD) + if (fcntl (icmp_state -> socket, F_SETFD, 1) < 0) + log_error ("Can't set close-on-exec on icmp: %m"); +#endif + + /* Make sure it does routing... */ + state = 0; + if (setsockopt (icmp_state -> socket, SOL_SOCKET, SO_DONTROUTE, + (char *)&state, sizeof state) < 0) + log_fatal ("Can't disable SO_DONTROUTE on ICMP: %m"); + + result = (omapi_register_io_object + ((omapi_object_t *)icmp_state, + icmp_readsocket, 0, icmp_echoreply, 0, 0)); + if (result != ISC_R_SUCCESS) + log_fatal ("Can't register icmp handle: %s", + isc_result_totext (result)); +#if defined (TRACING) + } +#endif +} + +int icmp_readsocket (h) + omapi_object_t *h; +{ + struct icmp_state *state; + + state = (struct icmp_state *)h; + return state -> socket; +} + +int icmp_echorequest (addr) + struct iaddr *addr; +{ + struct sockaddr_in to; + struct icmp icmp; + int status; +#if defined (TRACING) + trace_iov_t iov [2]; +#endif + + if (no_icmp) + return 1; + if (!icmp_state) + log_fatal ("ICMP protocol used before initialization."); + + memset (&to, 0, sizeof(to)); +#ifdef HAVE_SA_LEN + to.sin_len = sizeof to; +#endif + to.sin_family = AF_INET; + to.sin_port = 0; /* unused. */ + memcpy (&to.sin_addr, addr -> iabuf, sizeof to.sin_addr); /* XXX */ + + icmp.icmp_type = ICMP_ECHO; + icmp.icmp_code = 0; + icmp.icmp_cksum = 0; + icmp.icmp_seq = 0; +#if SIZEOF_STRUCT_IADDR_P == 8 + icmp.icmp_id = (((u_int32_t)(u_int64_t)addr) ^ + (u_int32_t)(((u_int64_t)addr) >> 32)); +#else + icmp.icmp_id = (u_int32_t)addr; +#endif + memset (&icmp.icmp_dun, 0, sizeof icmp.icmp_dun); + + icmp.icmp_cksum = wrapsum (checksum ((unsigned char *)&icmp, + sizeof icmp, 0)); + +#if defined (TRACING) + if (trace_playback ()) { + char *buf = (char *)0; + unsigned buflen = 0; + + /* Consume the ICMP event. */ + status = trace_get_packet (&trace_icmp_output, &buflen, &buf); + if (status != ISC_R_SUCCESS) + log_error ("icmp_echorequest: %s", + isc_result_totext (status)); + if (buf) + dfree (buf, MDL); + } else { + if (trace_record ()) { + iov [0].buf = (char *)addr; + iov [0].len = sizeof *addr; + iov [1].buf = (char *)&icmp; + iov [1].len = sizeof icmp; + trace_write_packet_iov (trace_icmp_output, + 2, iov, MDL); + } +#endif + /* Send the ICMP packet... */ + status = sendto (icmp_state -> socket, + (char *)&icmp, sizeof icmp, 0, + (struct sockaddr *)&to, sizeof to); + if (status < 0) + log_error ("icmp_echorequest %s: %m", + inet_ntoa(to.sin_addr)); + + if (status != sizeof icmp) + return 0; +#if defined (TRACING) + } +#endif + return 1; +} + +isc_result_t icmp_echoreply (h) + omapi_object_t *h; +{ + struct icmp *icfrom; + struct ip *ip; + struct sockaddr_in from; + u_int8_t icbuf [1500]; + int status; + SOCKLEN_T sl; + int hlen, len; + struct iaddr ia; + struct icmp_state *state; +#if defined (TRACING) + trace_iov_t iov [2]; +#endif + + state = (struct icmp_state *)h; + + sl = sizeof from; + status = recvfrom (state -> socket, (char *)icbuf, sizeof icbuf, 0, + (struct sockaddr *)&from, &sl); + if (status < 0) { + log_error ("icmp_echoreply: %m"); + return ISC_R_UNEXPECTED; + } + + /* Find the IP header length... */ + ip = (struct ip *)icbuf; + hlen = IP_HL (ip); + + /* Short packet? */ + if (status < hlen + (sizeof *icfrom)) { + return ISC_R_SUCCESS; + } + + len = status - hlen; + icfrom = (struct icmp *)(icbuf + hlen); + + /* Silently discard ICMP packets that aren't echoreplies. */ + if (icfrom -> icmp_type != ICMP_ECHOREPLY) { + return ISC_R_SUCCESS; + } + + /* If we were given a second-stage handler, call it. */ + if (state -> icmp_handler) { + memcpy (ia.iabuf, &from.sin_addr, sizeof from.sin_addr); + ia.len = sizeof from.sin_addr; + +#if defined (TRACING) + if (trace_record ()) { + ia.len = htonl(ia.len); + iov [0].buf = (char *)&ia; + iov [0].len = sizeof ia; + iov [1].buf = (char *)icbuf; + iov [1].len = len; + trace_write_packet_iov (trace_icmp_input, 2, iov, MDL); + ia.len = ntohl(ia.len); + } +#endif + (*state -> icmp_handler) (ia, icbuf, len); + } + return ISC_R_SUCCESS; +} + +#if defined (TRACING) +void trace_icmp_input_input (trace_type_t *ttype, unsigned length, char *buf) +{ + struct iaddr *ia; + u_int8_t *icbuf; + ia = (struct iaddr *)buf; + ia->len = ntohl(ia->len); + icbuf = (u_int8_t *)(ia + 1); + if (icmp_state -> icmp_handler) + (*icmp_state -> icmp_handler) (*ia, icbuf, + (int)(length - sizeof ia)); +} + +void trace_icmp_input_stop (trace_type_t *ttype) { } + +void trace_icmp_output_input (trace_type_t *ttype, unsigned length, char *buf) +{ + struct iaddr ia; + + if (length != (sizeof (struct icmp) + sizeof (ia))) { + log_error ("trace_icmp_output_input: data size mismatch %d:%d", + length, (int)(sizeof (struct icmp) + sizeof (ia))); + return; + } + ia.len = 4; + memcpy (ia.iabuf, buf, 4); + + log_error ("trace_icmp_output_input: unsent ping to %s", piaddr (ia)); +} + +void trace_icmp_output_stop (trace_type_t *ttype) { } +#endif /* TRACING */ diff --git a/common/inet.c b/common/inet.c new file mode 100644 index 0000000..0cff19d --- /dev/null +++ b/common/inet.c @@ -0,0 +1,624 @@ +/* inet.c + + Subroutines to manipulate internet addresses and ports in a safely portable + way... */ + +/* + * Copyright (c) 2011,2013,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2007-2009 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004,2005 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" + +/* Return just the network number of an internet address... */ + +struct iaddr subnet_number (addr, mask) + struct iaddr addr; + struct iaddr mask; +{ + int i; + struct iaddr rv; + + if (addr.len > sizeof(addr.iabuf)) + log_fatal("subnet_number():%s:%d: Invalid addr length.", MDL); + if (addr.len != mask.len) + log_fatal("subnet_number():%s:%d: Addr/mask length mismatch.", + MDL); + + rv.len = 0; + + /* Both addresses must have the same length... */ + if (addr.len != mask.len) + return rv; + + rv.len = addr.len; + for (i = 0; i < rv.len; i++) + rv.iabuf [i] = addr.iabuf [i] & mask.iabuf [i]; + return rv; +} + +/* Combine a network number and a integer to produce an internet address. + This won't work for subnets with more than 32 bits of host address, but + maybe this isn't a problem. */ + +struct iaddr ip_addr (subnet, mask, host_address) + struct iaddr subnet; + struct iaddr mask; + u_int32_t host_address; +{ + int i, j, k; + u_int32_t swaddr; + struct iaddr rv; + unsigned char habuf [sizeof swaddr]; + + if (subnet.len > sizeof(subnet.iabuf)) + log_fatal("ip_addr():%s:%d: Invalid addr length.", MDL); + if (subnet.len != mask.len) + log_fatal("ip_addr():%s:%d: Addr/mask length mismatch.", + MDL); + + swaddr = htonl (host_address); + memcpy (habuf, &swaddr, sizeof swaddr); + + /* Combine the subnet address and the host address. If + the host address is bigger than can fit in the subnet, + return a zero-length iaddr structure. */ + rv = subnet; + j = rv.len - sizeof habuf; + for (i = sizeof habuf - 1; i >= 0; i--) { + if (mask.iabuf [i + j]) { + if (habuf [i] > (mask.iabuf [i + j] ^ 0xFF)) { + rv.len = 0; + return rv; + } + for (k = i - 1; k >= 0; k--) { + if (habuf [k]) { + rv.len = 0; + return rv; + } + } + rv.iabuf [i + j] |= habuf [i]; + break; + } else + rv.iabuf [i + j] = habuf [i]; + } + + return rv; +} + +/* Given a subnet number and netmask, return the address on that subnet + for which the host portion of the address is all ones (the standard + broadcast address). */ + +struct iaddr broadcast_addr (subnet, mask) + struct iaddr subnet; + struct iaddr mask; +{ + int i; + struct iaddr rv; + + if (subnet.len > sizeof(subnet.iabuf)) + log_fatal("broadcast_addr():%s:%d: Invalid addr length.", MDL); + if (subnet.len != mask.len) + log_fatal("broadcast_addr():%s:%d: Addr/mask length mismatch.", + MDL); + + if (subnet.len != mask.len) { + rv.len = 0; + return rv; + } + + for (i = 0; i < subnet.len; i++) { + rv.iabuf [i] = subnet.iabuf [i] | (~mask.iabuf [i] & 255); + } + rv.len = subnet.len; + + return rv; +} + +u_int32_t host_addr (addr, mask) + struct iaddr addr; + struct iaddr mask; +{ + int i; + u_int32_t swaddr; + struct iaddr rv; + + if (addr.len > sizeof(addr.iabuf)) + log_fatal("host_addr():%s:%d: Invalid addr length.", MDL); + if (addr.len != mask.len) + log_fatal("host_addr():%s:%d: Addr/mask length mismatch.", + MDL); + + rv.len = 0; + + /* Mask out the network bits... */ + rv.len = addr.len; + for (i = 0; i < rv.len; i++) + rv.iabuf [i] = addr.iabuf [i] & ~mask.iabuf [i]; + + /* Copy out up to 32 bits... */ + memcpy (&swaddr, &rv.iabuf [rv.len - sizeof swaddr], sizeof swaddr); + + /* Swap it and return it. */ + return ntohl (swaddr); +} + +int addr_eq (addr1, addr2) + struct iaddr addr1, addr2; +{ + if (addr1.len > sizeof(addr1.iabuf)) + log_fatal("addr_eq():%s:%d: Invalid addr length.", MDL); + + if (addr1.len != addr2.len) + return 0; + return memcmp (addr1.iabuf, addr2.iabuf, addr1.len) == 0; +} + +/* addr_match + * + * compares an IP address against a network/mask combination + * by ANDing the IP with the mask and seeing whether the result + * matches the masked network value. + */ +int +addr_match(addr, match) + struct iaddr *addr; + struct iaddrmatch *match; +{ + int i; + + if (addr->len != match->addr.len) + return 0; + + for (i = 0 ; i < addr->len ; i++) { + if ((addr->iabuf[i] & match->mask.iabuf[i]) != + match->addr.iabuf[i]) + return 0; + } + return 1; +} + +/* + * Compares the addresses a1 and a2. + * + * If a1 < a2, returns -1. + * If a1 == a2, returns 0. + * If a1 > a2, returns 1. + * + * WARNING: if a1 and a2 differ in length, returns 0. + */ +int +addr_cmp(const struct iaddr *a1, const struct iaddr *a2) { + int i; + + if (a1->len != a2->len) { + return 0; + } + + for (i=0; i<a1->len; i++) { + if (a1->iabuf[i] < a2->iabuf[i]) { + return -1; + } + if (a1->iabuf[i] > a2->iabuf[i]) { + return 1; + } + } + + return 0; +} + +/* + * Performs a bitwise-OR of two addresses. + * + * Returns 1 if the result is non-zero, or 0 otherwise. + * + * WARNING: if a1 and a2 differ in length, returns 0. + */ +int +addr_or(struct iaddr *result, const struct iaddr *a1, const struct iaddr *a2) { + int i; + int all_zero; + + if (a1->len != a2->len) { + return 0; + } + + all_zero = 1; + + result->len = a1->len; + for (i=0; i<a1->len; i++) { + result->iabuf[i] = a1->iabuf[i] | a2->iabuf[i]; + if (result->iabuf[i] != 0) { + all_zero = 0; + } + } + + return !all_zero; +} + +/* + * Performs a bitwise-AND of two addresses. + * + * Returns 1 if the result is non-zero, or 0 otherwise. + * + * WARNING: if a1 and a2 differ in length, returns 0. + */ +int +addr_and(struct iaddr *result, const struct iaddr *a1, const struct iaddr *a2) { + int i; + int all_zero; + + if (a1->len != a2->len) { + return 0; + } + + all_zero = 1; + + result->len = a1->len; + for (i=0; i<a1->len; i++) { + result->iabuf[i] = a1->iabuf[i] & a2->iabuf[i]; + if (result->iabuf[i] != 0) { + all_zero = 0; + } + } + + return !all_zero; +} + +/* + * Check if a bitmask of the given length is valid for the address. + * This is not the case if any bits longer than the bitmask are 1. + * + * So, this is valid: + * + * 127.0.0.0/8 + * + * But this is not: + * + * 127.0.0.1/8 + * + * Because the final ".1" would get masked out by the /8. + */ +isc_boolean_t +is_cidr_mask_valid(const struct iaddr *addr, int bits) { + int zero_bits; + int zero_bytes; + int i; + char byte; + int shift_bits; + + /* + * Check our bit boundaries. + */ + if (bits < 0) { + return ISC_FALSE; + } + if (bits > (addr->len * 8)) { + return ISC_FALSE; + } + + /* + * Figure out how many low-order bits need to be zero. + */ + zero_bits = (addr->len * 8) - bits; + zero_bytes = zero_bits / 8; + + /* + * Check to make sure the low-order bytes are zero. + */ + for (i=1; i<=zero_bytes; i++) { + if (addr->iabuf[addr->len-i] != 0) { + return ISC_FALSE; + } + } + + /* + * Look to see if any bits not in right-hand bytes are + * non-zero, by making a byte that has these bits set to zero + * comparing to the original byte. If these two values are + * equal, then the right-hand bits are zero, and we are + * happy. + */ + shift_bits = zero_bits % 8; + if (shift_bits == 0) return ISC_TRUE; + byte = addr->iabuf[addr->len-zero_bytes-1]; + return (((byte >> shift_bits) << shift_bits) == byte); +} + +/* + * range2cidr + * + * Converts a range of IP addresses to a set of CIDR networks. + * + * Examples: + * 192.168.0.0 - 192.168.0.255 = 192.168.0.0/24 + * 10.0.0.0 - 10.0.1.127 = 10.0.0.0/24, 10.0.1.0/25 + * 255.255.255.32 - 255.255.255.255 = 255.255.255.32/27, 255.255.255.64/26, + * 255.255.255.128/25 + */ +isc_result_t +range2cidr(struct iaddrcidrnetlist **result, + const struct iaddr *lo, const struct iaddr *hi) { + struct iaddr addr; + struct iaddr mask; + int bit; + struct iaddr end_addr; + struct iaddr dummy; + int ofs, val; + struct iaddrcidrnetlist *net; + int tmp; + + if (result == NULL) { + return DHCP_R_INVALIDARG; + } + if (*result != NULL) { + return DHCP_R_INVALIDARG; + } + if ((lo == NULL) || (hi == NULL) || (lo->len != hi->len)) { + return DHCP_R_INVALIDARG; + } + + /* + * Put our start and end in the right order, if reversed. + */ + if (addr_cmp(lo, hi) > 0) { + const struct iaddr *tmp; + tmp = lo; + lo = hi; + hi = tmp; + } + + /* + * Theory of operation: + * + * ------------------- + * Start at the low end, and keep trying larger networks + * until we get one that is too big (explained below). + * + * We keep a "mask", which is the ones-complement of a + * normal netmask. So, a /23 has a netmask of 255.255.254.0, + * and a mask of 0.0.1.255. + * + * We know when a network is too big when we bitwise-AND the + * mask with the starting address and we get a non-zero + * result, like this: + * + * addr: 192.168.1.0, mask: 0.0.1.255 + * bitwise-AND: 0.0.1.0 + * + * A network is also too big if the bitwise-OR of the mask + * with the starting address is larger than the end address, + * like this: + * + * start: 192.168.1.0, mask: 0.0.1.255, end: 192.168.0.255 + * bitwise-OR: 192.168.1.255 + * + * ------------------- + * Once we have found a network that is too big, we add the + * appropriate CIDR network to our list of found networks. + * + * We then use the next IP address as our low address, and + * begin the process of searching for a network that is + * too big again, starting with an empty mask. + */ + addr = *lo; + bit = 0; + memset(&mask, 0, sizeof(mask)); + mask.len = addr.len; + while (addr_cmp(&addr, hi) <= 0) { + /* + * Bitwise-OR mask with (1 << bit) + */ + ofs = addr.len - (bit / 8) - 1; + val = 1 << (bit % 8); + if (ofs >= 0) { + mask.iabuf[ofs] |= val; + } + + /* + * See if we're too big, and save this network if so. + */ + addr_or(&end_addr, &addr, &mask); + if ((ofs < 0) || + (addr_cmp(&end_addr, hi) > 0) || + addr_and(&dummy, &addr, &mask)) { + /* + * Add a new prefix to our list. + */ + net = dmalloc(sizeof(*net), MDL); + if (net == NULL) { + while (*result != NULL) { + net = (*result)->next; + dfree(*result, MDL); + *result = net; + } + return ISC_R_NOMEMORY; + } + net->cidrnet.lo_addr = addr; + net->cidrnet.bits = (addr.len * 8) - bit; + net->next = *result; + *result = net; + + /* + * Figure out our new starting address, + * by adding (1 << bit) to our previous + * starting address. + */ + tmp = addr.iabuf[ofs] + val; + while ((ofs >= 0) && (tmp > 255)) { + addr.iabuf[ofs] = tmp - 256; + ofs--; + tmp = addr.iabuf[ofs] + 1; + } + if (ofs < 0) { + /* Gone past last address, we're done. */ + break; + } + addr.iabuf[ofs] = tmp; + + /* + * Reset our bit and mask. + */ + bit = 0; + memset(mask.iabuf, 0, sizeof(mask.iabuf)); + memset(end_addr.iabuf, 0, sizeof(end_addr.iabuf)); + } else { + /* + * If we're not too big, increase our network size. + */ + bit++; + } + } + + /* + * We're done. + */ + return ISC_R_SUCCESS; +} + +/* + * Free a list of CIDR networks, such as returned from range2cidr(). + */ +isc_result_t +free_iaddrcidrnetlist(struct iaddrcidrnetlist **result) { + struct iaddrcidrnetlist *p; + + if (result == NULL) { + return DHCP_R_INVALIDARG; + } + if (*result == NULL) { + return DHCP_R_INVALIDARG; + } + + while (*result != NULL) { + p = *result; + *result = p->next; + dfree(p, MDL); + } + + return ISC_R_SUCCESS; +} + +/* piaddr() turns an iaddr structure into a printable address. */ +/* XXX: should use a const pointer rather than passing the structure */ +const char * +piaddr(const struct iaddr addr) { + static char + pbuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + /* "255.255.255.255" */ + + /* INSIST((addr.len == 0) || (addr.len == 4) || (addr.len == 16)); */ + + if (addr.len == 0) { + return "<null address>"; + } + if (addr.len == 4) { + return inet_ntop(AF_INET, addr.iabuf, pbuf, sizeof(pbuf)); + } + if (addr.len == 16) { + return inet_ntop(AF_INET6, addr.iabuf, pbuf, sizeof(pbuf)); + } + + log_fatal("piaddr():%s:%d: Invalid address length %d.", MDL, + addr.len); + /* quell compiler warnings */ + return NULL; +} + +/* piaddrmask takes an iaddr structure mask, determines the bitlength of + * the mask, and then returns the printable CIDR notation of the two. + */ +char * +piaddrmask(struct iaddr *addr, struct iaddr *mask) { + int mw; + unsigned int oct, bit; + + if ((addr->len != 4) && (addr->len != 16)) + log_fatal("piaddrmask():%s:%d: Address length %d invalid", + MDL, addr->len); + if (addr->len != mask->len) + log_fatal("piaddrmask():%s:%d: Address and mask size mismatch", + MDL); + + /* Determine netmask width in bits. */ + for (mw = (mask->len * 8) ; mw > 0 ; ) { + oct = (mw - 1) / 8; + bit = 0x80 >> ((mw - 1) % 8); + if (!mask->iabuf[oct]) + mw -= 8; + else if (mask->iabuf[oct] & bit) + break; + else + mw--; + } + + if (mw < 0) + log_fatal("Impossible condition at %s:%d.", MDL); + + return piaddrcidr(addr, mw); +} + +/* Format an address and mask-length into printable CIDR notation. */ +char * +piaddrcidr(const struct iaddr *addr, unsigned int bits) { + static char + ret[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255/128")]; + /* "255.255.255.255/32" */ + + /* INSIST(addr != NULL); */ + /* INSIST((addr->len == 4) || (addr->len == 16)); */ + /* INSIST(bits <= (addr->len * 8)); */ + + if (bits > (addr->len * 8)) + return NULL; + + sprintf(ret, "%s/%d", piaddr(*addr), bits); + + return ret; +} + +/* Validate that the string represents a valid port number and + * return it in network byte order + */ + +u_int16_t +validate_port(char *port) { + long local_port = 0; + long lower = 1; + long upper = 65535; + char *endptr; + + errno = 0; + local_port = strtol(port, &endptr, 10); + + if ((*endptr != '\0') || (errno == ERANGE) || (errno == EINVAL)) + log_fatal ("Invalid port number specification: %s", port); + + if (local_port < lower || local_port > upper) + log_fatal("Port number specified is out of range (%ld-%ld).", + lower, upper); + + return htons((u_int16_t)local_port); +} diff --git a/common/lpf.c b/common/lpf.c new file mode 100644 index 0000000..6fe4954 --- /dev/null +++ b/common/lpf.c @@ -0,0 +1,537 @@ +/* lpf.c + + Linux packet filter code, contributed by Brian Murrel at Interlinx + Support Services in Vancouver, B.C. */ + +/* + * Copyright (c) 2014-2015 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2009,2012 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004,2007 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + */ + +#include "dhcpd.h" +#if defined (USE_LPF_SEND) || defined (USE_LPF_RECEIVE) +#include <sys/ioctl.h> +#include <sys/uio.h> +#include <errno.h> + +#include <asm/types.h> +#include <linux/filter.h> +#include <linux/if_ether.h> +#include <linux/if_packet.h> +#include <netinet/in_systm.h> +#include "includes/netinet/ip.h" +#include "includes/netinet/udp.h" +#include "includes/netinet/if_ether.h" +#include <net/if.h> + +/* Reinitializes the specified interface after an address change. This + is not required for packet-filter APIs. */ + +#ifdef USE_LPF_SEND +void if_reinitialize_send (info) + struct interface_info *info; +{ +} +#endif + +#ifdef USE_LPF_RECEIVE +void if_reinitialize_receive (info) + struct interface_info *info; +{ +} +#endif + +/* Called by get_interface_list for each interface that's discovered. + Opens a packet filter for each interface and adds it to the select + mask. */ + +int if_register_lpf (info) + struct interface_info *info; +{ + int sock; + union { + struct sockaddr_ll ll; + struct sockaddr common; + } sa; + struct ifreq ifr; + + /* Make an LPF socket. */ + if ((sock = socket(PF_PACKET, SOCK_RAW, + htons((short)ETH_P_ALL))) < 0) { + if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT || + errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT || + errno == EAFNOSUPPORT || errno == EINVAL) { + log_error ("socket: %m - make sure"); + log_error ("CONFIG_PACKET (Packet socket) %s", + "and CONFIG_FILTER"); + log_error ("(Socket Filtering) are enabled %s", + "in your kernel"); + log_fatal ("configuration!"); + } + log_fatal ("Open a socket for LPF: %m"); + } + + memset (&ifr, 0, sizeof ifr); + strncpy (ifr.ifr_name, (const char *)info -> ifp, sizeof ifr.ifr_name); + ifr.ifr_name[IFNAMSIZ-1] = '\0'; + if (ioctl (sock, SIOCGIFINDEX, &ifr)) + log_fatal ("Failed to get interface index: %m"); + + /* Bind to the interface name */ + memset (&sa, 0, sizeof sa); + sa.ll.sll_family = AF_PACKET; + sa.ll.sll_ifindex = ifr.ifr_ifindex; + if (bind (sock, &sa.common, sizeof sa)) { + if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT || + errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT || + errno == EAFNOSUPPORT || errno == EINVAL) { + log_error ("socket: %m - make sure"); + log_error ("CONFIG_PACKET (Packet socket) %s", + "and CONFIG_FILTER"); + log_error ("(Socket Filtering) are enabled %s", + "in your kernel"); + log_fatal ("configuration!"); + } + log_fatal ("Bind socket to interface: %m"); + + } + + get_hw_addr(info->name, &info->hw_address); + + return sock; +} +#endif /* USE_LPF_SEND || USE_LPF_RECEIVE */ + +#ifdef USE_LPF_SEND +void if_register_send (info) + struct interface_info *info; +{ + /* If we're using the lpf API for sending and receiving, + we don't need to register this interface twice. */ +#ifndef USE_LPF_RECEIVE + info -> wfdesc = if_register_lpf (info); +#else + info -> wfdesc = info -> rfdesc; +#endif + if (!quiet_interface_discovery) + log_info ("Sending on LPF/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} + +void if_deregister_send (info) + struct interface_info *info; +{ + /* don't need to close twice if we are using lpf for sending and + receiving */ +#ifndef USE_LPF_RECEIVE + /* for LPF this is simple, packet filters are removed when sockets + are closed */ + close (info -> wfdesc); +#endif + info -> wfdesc = -1; + if (!quiet_interface_discovery) + log_info ("Disabling output on LPF/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} +#endif /* USE_LPF_SEND */ + +#ifdef USE_LPF_RECEIVE +/* Defined in bpf.c. We can't extern these in dhcpd.h without pulling + in bpf includes... */ +extern struct sock_filter dhcp_bpf_filter []; +extern int dhcp_bpf_filter_len; + +#if defined (HAVE_TR_SUPPORT) +extern struct sock_filter dhcp_bpf_tr_filter []; +extern int dhcp_bpf_tr_filter_len; +static void lpf_tr_filter_setup (struct interface_info *); +#endif + +static void lpf_gen_filter_setup (struct interface_info *); + +void if_register_receive (info) + struct interface_info *info; +{ + /* Open a LPF device and hang it on this interface... */ + info -> rfdesc = if_register_lpf (info); + +#ifdef PACKET_AUXDATA + { + int val = 1; + + if (setsockopt(info->rfdesc, SOL_PACKET, PACKET_AUXDATA, + &val, sizeof(val)) < 0) { + if (errno != ENOPROTOOPT) { + log_fatal ("Failed to set auxiliary packet data: %m"); + } + } + } +#endif + + +#if defined (HAVE_TR_SUPPORT) + if (info -> hw_address.hbuf [0] == HTYPE_IEEE802) + lpf_tr_filter_setup (info); + else +#endif + lpf_gen_filter_setup (info); + + if (!quiet_interface_discovery) + log_info ("Listening on LPF/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} + +void if_deregister_receive (info) + struct interface_info *info; +{ + /* for LPF this is simple, packet filters are removed when sockets + are closed */ + close (info -> rfdesc); + info -> rfdesc = -1; + if (!quiet_interface_discovery) + log_info ("Disabling input on LPF/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} + +static void lpf_gen_filter_setup (info) + struct interface_info *info; +{ + struct sock_fprog p; + + memset(&p, 0, sizeof(p)); + + /* Set up the bpf filter program structure. This is defined in + bpf.c */ + p.len = dhcp_bpf_filter_len; + p.filter = dhcp_bpf_filter; + + /* Patch the server port into the LPF program... + XXX changes to filter program may require changes + to the insn number(s) used below! XXX */ + dhcp_bpf_filter [8].k = ntohs ((short)local_port); + + if (setsockopt (info -> rfdesc, SOL_SOCKET, SO_ATTACH_FILTER, &p, + sizeof p) < 0) { + if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT || + errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT || + errno == EAFNOSUPPORT) { + log_error ("socket: %m - make sure"); + log_error ("CONFIG_PACKET (Packet socket) %s", + "and CONFIG_FILTER"); + log_error ("(Socket Filtering) are enabled %s", + "in your kernel"); + log_fatal ("configuration!"); + } + log_fatal ("Can't install packet filter program: %m"); + } +} + +#if defined (HAVE_TR_SUPPORT) +static void lpf_tr_filter_setup (info) + struct interface_info *info; +{ + struct sock_fprog p; + + memset(&p, 0, sizeof(p)); + + /* Set up the bpf filter program structure. This is defined in + bpf.c */ + p.len = dhcp_bpf_tr_filter_len; + p.filter = dhcp_bpf_tr_filter; + + /* Patch the server port into the LPF program... + XXX changes to filter program may require changes + XXX to the insn number(s) used below! + XXX Token ring filter is null - when/if we have a filter + XXX that's not, we'll need this code. + XXX dhcp_bpf_filter [?].k = ntohs (local_port); */ + + if (setsockopt (info -> rfdesc, SOL_SOCKET, SO_ATTACH_FILTER, &p, + sizeof p) < 0) { + if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT || + errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT || + errno == EAFNOSUPPORT) { + log_error ("socket: %m - make sure"); + log_error ("CONFIG_PACKET (Packet socket) %s", + "and CONFIG_FILTER"); + log_error ("(Socket Filtering) are enabled %s", + "in your kernel"); + log_fatal ("configuration!"); + } + log_fatal ("Can't install packet filter program: %m"); + } +} +#endif /* HAVE_TR_SUPPORT */ +#endif /* USE_LPF_RECEIVE */ + +#ifdef USE_LPF_SEND +ssize_t send_packet (interface, packet, raw, len, from, to, hto) + struct interface_info *interface; + struct packet *packet; + struct dhcp_packet *raw; + size_t len; + struct in_addr from; + struct sockaddr_in *to; + struct hardware *hto; +{ + unsigned hbufp = 0, ibufp = 0; + double hh [16]; + double ih [1536 / sizeof (double)]; + unsigned char *buf = (unsigned char *)ih; + int result; + int fudge; + + if (!strcmp (interface -> name, "fallback")) + return send_fallback (interface, packet, raw, + len, from, to, hto); + + if (hto == NULL && interface->anycast_mac_addr.hlen) + hto = &interface->anycast_mac_addr; + + /* Assemble the headers... */ + assemble_hw_header (interface, (unsigned char *)hh, &hbufp, hto); + fudge = hbufp % 4; /* IP header must be word-aligned. */ + memcpy (buf + fudge, (unsigned char *)hh, hbufp); + ibufp = hbufp + fudge; + assemble_udp_ip_header (interface, buf, &ibufp, from.s_addr, + to -> sin_addr.s_addr, to -> sin_port, + (unsigned char *)raw, len); + memcpy (buf + ibufp, raw, len); + result = write(interface->wfdesc, buf + fudge, ibufp + len - fudge); + if (result < 0) + log_error ("send_packet: %m"); + return result; +} +#endif /* USE_LPF_SEND */ + +#ifdef USE_LPF_RECEIVE +ssize_t receive_packet (interface, buf, len, from, hfrom) + struct interface_info *interface; + unsigned char *buf; + size_t len; + struct sockaddr_in *from; + struct hardware *hfrom; +{ + int length = 0; + int offset = 0; + int csum_ready = 1; + unsigned char ibuf [1536]; + unsigned bufix = 0; + unsigned paylen; + unsigned char cmsgbuf[CMSG_LEN(sizeof(struct tpacket_auxdata))]; + struct iovec iov = { + .iov_base = ibuf, + .iov_len = sizeof ibuf, + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = sizeof(cmsgbuf), + }; + + length = recvmsg (interface->rfdesc, &msg, 0); + if (length <= 0) + return length; + +#ifdef PACKET_AUXDATA + { + /* Use auxiliary packet data to: + * + * a. Weed out extraneous VLAN-tagged packets - If the NIC driver is + * handling VLAN encapsulation (i.e. stripping/adding VLAN tags), + * then an inbound VLAN packet will be seen twice: Once by + * the parent interface (e.g. eth0) with a VLAN tag != 0; and once + * by the vlan interface (e.g. eth0.n) with a VLAN tag of 0 (i.e none). + * We want to discard the packet sent to the parent and thus respond + * only over the vlan interface. (Drivers for Intel PRO/1000 series + * NICs perform VLAN encapsulation, while drivers for PCnet series + * do not, for example. The linux kernel makes stripped vlan info + * visible to user space via CMSG/auxdata, this appears to not be + * true for BSD OSs.). NOTE: this is only supported on linux flavors + * which define the tpacket_auxdata.tp_vlan_tci. + * + * b. Determine if checksum is valid for use. It may not be if + * checksum offloading is enabled on the interface. */ + struct cmsghdr *cmsg; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_PACKET && + cmsg->cmsg_type == PACKET_AUXDATA) { + struct tpacket_auxdata *aux = (void *)CMSG_DATA(cmsg); + /* Discard packets with stripped vlan id */ + +#ifdef VLAN_TCI_PRESENT + if (aux->tp_vlan_tci != 0) + return 0; +#endif + + csum_ready = ((aux->tp_status & TP_STATUS_CSUMNOTREADY) + ? 0 : 1); + } + } + + } +#endif + + bufix = 0; + /* Decode the physical header... */ + offset = decode_hw_header (interface, ibuf, bufix, hfrom); + + /* If a physical layer checksum failed (dunno of any + physical layer that supports this, but WTH), skip this + packet. */ + if (offset < 0) { + return 0; + } + + bufix += offset; + length -= offset; + + /* Decode the IP and UDP headers... */ + offset = decode_udp_ip_header (interface, ibuf, bufix, from, + (unsigned)length, &paylen, csum_ready); + + /* If the IP or UDP checksum was bad, skip the packet... */ + if (offset < 0) + return 0; + + bufix += offset; + length -= offset; + + if (length < paylen) + log_fatal("Internal inconsistency at %s:%d.", MDL); + + /* Copy out the data in the packet... */ + memcpy(buf, &ibuf[bufix], paylen); + return paylen; +} + +int can_unicast_without_arp (ip) + struct interface_info *ip; +{ + return 1; +} + +int can_receive_unicast_unconfigured (ip) + struct interface_info *ip; +{ + return 1; +} + +int supports_multiple_interfaces (ip) + struct interface_info *ip; +{ + return 1; +} + +void maybe_setup_fallback () +{ + isc_result_t status; + struct interface_info *fbi = (struct interface_info *)0; + if (setup_fallback (&fbi, MDL)) { + if_register_fallback (fbi); + status = omapi_register_io_object ((omapi_object_t *)fbi, + if_readsocket, 0, + fallback_discard, 0, 0); + if (status != ISC_R_SUCCESS) + log_fatal ("Can't register I/O handle for \"%s\": %s", + fbi -> name, isc_result_totext (status)); + interface_dereference (&fbi, MDL); + } +} + +void +get_hw_addr(const char *name, struct hardware *hw) { + int sock; + struct ifreq tmp; + struct sockaddr *sa; + + if (strlen(name) >= sizeof(tmp.ifr_name)) { + log_fatal("Device name too long: \"%s\"", name); + } + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + log_fatal("Can't create socket for \"%s\": %m", name); + } + + memset(&tmp, 0, sizeof(tmp)); + strcpy(tmp.ifr_name, name); + if (ioctl(sock, SIOCGIFHWADDR, &tmp) < 0) { + log_fatal("Error getting hardware address for \"%s\": %m", + name); + } + + sa = &tmp.ifr_hwaddr; + switch (sa->sa_family) { + case ARPHRD_ETHER: + hw->hlen = 7; + hw->hbuf[0] = HTYPE_ETHER; + memcpy(&hw->hbuf[1], sa->sa_data, 6); + break; + case ARPHRD_IEEE802: +#ifdef ARPHRD_IEEE802_TR + case ARPHRD_IEEE802_TR: +#endif /* ARPHRD_IEEE802_TR */ + hw->hlen = 7; + hw->hbuf[0] = HTYPE_IEEE802; + memcpy(&hw->hbuf[1], sa->sa_data, 6); + break; + case ARPHRD_FDDI: + hw->hlen = 7; + hw->hbuf[0] = HTYPE_FDDI; + memcpy(&hw->hbuf[1], sa->sa_data, 6); + break; + default: + log_fatal("Unsupported device type %ld for \"%s\"", + (long int)sa->sa_family, name); + } + + close(sock); +} +#endif diff --git a/common/memory.c b/common/memory.c new file mode 100644 index 0000000..03a0d2e --- /dev/null +++ b/common/memory.c @@ -0,0 +1,148 @@ +/* memory.c + + Memory-resident database... */ + +/* + * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" + +struct group *root_group; +group_hash_t *group_name_hash; +int (*group_write_hook) (struct group_object *); + +isc_result_t delete_group (struct group_object *group, int writep) +{ + struct group_object *d; + + /* The group should exist and be hashed - if not, it's invalid. */ + if (group_name_hash) { + d = (struct group_object *)0; + group_hash_lookup (&d, group_name_hash, group -> name, + strlen (group -> name), MDL); + } else + return DHCP_R_INVALIDARG; + if (!d) + return DHCP_R_INVALIDARG; + + /* Also not okay to delete a group that's not the one in + the hash table. */ + if (d != group) + return DHCP_R_INVALIDARG; + + /* If it's dynamic, and we're deleting it, we can just blow away the + hash table entry. */ + if ((group -> flags & GROUP_OBJECT_DYNAMIC) && + !(group -> flags & GROUP_OBJECT_STATIC)) { + group_hash_delete (group_name_hash, + group -> name, strlen (group -> name), MDL); + } else { + group -> flags |= GROUP_OBJECT_DELETED; + if (group -> group) + group_dereference (&group -> group, MDL); + } + + /* Store the group declaration in the lease file. */ + if (writep && group_write_hook) { + if (!(*group_write_hook) (group)) + return ISC_R_IOERROR; + } + return ISC_R_SUCCESS; +} + +isc_result_t supersede_group (struct group_object *group, int writep) +{ + struct group_object *t; + + /* Register the group in the group name hash table, + so we can look it up later. */ + if (group_name_hash) { + t = (struct group_object *)0; + group_hash_lookup (&t, group_name_hash, + group -> name, + strlen (group -> name), MDL); + if (t && t != group) { + /* If this isn't a dynamic entry, then we need to flag + the replacement as not dynamic either - otherwise, + if the dynamic entry is deleted later, the static + entry will come back next time the server is stopped + and restarted. */ + if (!(t -> flags & GROUP_OBJECT_DYNAMIC)) + group -> flags |= GROUP_OBJECT_STATIC; + + /* Delete the old object if it hasn't already been + deleted. If it has already been deleted, get rid of + the hash table entry. This is a legitimate + situation - a deleted static object needs to be kept + around so we remember it's deleted. */ + if (!(t -> flags & GROUP_OBJECT_DELETED)) + delete_group (t, 0); + else { + group_hash_delete (group_name_hash, + group -> name, + strlen (group -> name), + MDL); + group_object_dereference (&t, MDL); + } + } + } else { + group_new_hash(&group_name_hash, GROUP_HASH_SIZE, MDL); + t = (struct group_object *)0; + } + + /* Add the group to the group name hash if it's not + already there, and also thread it into the list of + dynamic groups if appropriate. */ + if (!t) { + group_hash_add (group_name_hash, group -> name, + strlen (group -> name), group, MDL); + } + + /* Store the group declaration in the lease file. */ + if (writep && group_write_hook) { + if (!(*group_write_hook) (group)) + return ISC_R_IOERROR; + } + return ISC_R_SUCCESS; +} + +int clone_group (struct group **gp, struct group *group, + const char *file, int line) +{ + struct group *g = (struct group *)0; + + /* Normally gp should contain the null pointer, but for convenience + it's permissible to clone a group into itself. */ + if (*gp && *gp != group) + return 0; + if (!group_allocate (&g, file, line)) + return 0; + if (group == *gp) + *gp = (struct group *)0; + group_reference (gp, g, file, line); + g -> authoritative = group -> authoritative; + group_reference (&g -> next, group, file, line); + group_dereference (&g, file, line); + return 1; +} diff --git a/common/nit.c b/common/nit.c new file mode 100644 index 0000000..316e85f --- /dev/null +++ b/common/nit.c @@ -0,0 +1,416 @@ +/* nit.c + + Network Interface Tap (NIT) network interface code, by Ted Lemon + with one crucial tidbit of help from Stu Grossmen. */ + +/* + * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" +#if defined (USE_NIT_SEND) || defined (USE_NIT_RECEIVE) +#include <sys/ioctl.h> +#include <sys/uio.h> + +#include <sys/time.h> +#include <net/nit.h> +#include <net/nit_if.h> +#include <net/nit_pf.h> +#include <net/nit_buf.h> +#include <sys/stropts.h> +#include <net/packetfilt.h> + +#include <netinet/in_systm.h> +#include "includes/netinet/ip.h" +#include "includes/netinet/udp.h" +#include "includes/netinet/if_ether.h" + +/* Reinitializes the specified interface after an address change. This + is not required for packet-filter APIs. */ + +#ifdef USE_NIT_SEND +void if_reinitialize_send (info) + struct interface_info *info; +{ +} +#endif + +#ifdef USE_NIT_RECEIVE +void if_reinitialize_receive (info) + struct interface_info *info; +{ +} +#endif + +/* Called by get_interface_list for each interface that's discovered. + Opens a packet filter for each interface and adds it to the select + mask. */ + +int if_register_nit (info) + struct interface_info *info; +{ + int sock; + char filename[50]; + struct ifreq ifr; + struct strioctl sio; + + /* Open a NIT device */ + sock = open ("/dev/nit", O_RDWR); + if (sock < 0) + log_fatal ("Can't open NIT device for %s: %m", info -> name); + + /* Set the NIT device to point at this interface. */ + sio.ic_cmd = NIOCBIND; + sio.ic_len = sizeof *(info -> ifp); + sio.ic_dp = (char *)(info -> ifp); + sio.ic_timout = INFTIM; + if (ioctl (sock, I_STR, &sio) < 0) + log_fatal ("Can't attach interface %s to nit device: %m", + info -> name); + + /* Get the low-level address... */ + sio.ic_cmd = SIOCGIFADDR; + sio.ic_len = sizeof ifr; + sio.ic_dp = (char *)𝔦 + sio.ic_timout = INFTIM; + if (ioctl (sock, I_STR, &sio) < 0) + log_fatal ("Can't get physical layer address for %s: %m", + info -> name); + + /* XXX code below assumes ethernet interface! */ + info -> hw_address.hlen = 7; + info -> hw_address.hbuf [0] = ARPHRD_ETHER; + memcpy (&info -> hw_address.hbuf [1], + ifr.ifr_ifru.ifru_addr.sa_data, 6); + + if (ioctl (sock, I_PUSH, "pf") < 0) + log_fatal ("Can't push packet filter onto NIT for %s: %m", + info -> name); + + return sock; +} +#endif /* USE_NIT_SEND || USE_NIT_RECEIVE */ + +#ifdef USE_NIT_SEND +void if_register_send (info) + struct interface_info *info; +{ + /* If we're using the nit API for sending and receiving, + we don't need to register this interface twice. */ +#ifndef USE_NIT_RECEIVE + struct packetfilt pf; + struct strioctl sio; + + info -> wfdesc = if_register_nit (info); + + pf.Pf_Priority = 0; + pf.Pf_FilterLen = 1; + pf.Pf_Filter [0] = ENF_PUSHZERO; + + /* Set up an NIT filter that rejects everything... */ + sio.ic_cmd = NIOCSETF; + sio.ic_len = sizeof pf; + sio.ic_dp = (char *)&pf; + sio.ic_timout = INFTIM; + if (ioctl (info -> wfdesc, I_STR, &sio) < 0) + log_fatal ("Can't set NIT filter: %m"); +#else + info -> wfdesc = info -> rfdesc; +#endif + if (!quiet_interface_discovery) + log_info ("Sending on NIT/%s%s%s", + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} + +void if_deregister_send (info) + struct interface_info *info; +{ + /* If we're using the nit API for sending and receiving, + we don't need to register this interface twice. */ +#ifndef USE_NIT_RECEIVE + close (info -> wfdesc); +#endif + info -> wfdesc = -1; + if (!quiet_interface_discovery) + log_info ("Disabling output on NIT/%s%s%s", + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} +#endif /* USE_NIT_SEND */ + +#ifdef USE_NIT_RECEIVE +/* Packet filter program... + XXX Changes to the filter program may require changes to the constant + offsets used in if_register_send to patch the NIT program! XXX */ + +void if_register_receive (info) + struct interface_info *info; +{ + int flag = 1; + u_int32_t x; + struct packetfilt pf; + struct strioctl sio; + u_int16_t addr [2]; + struct timeval t; + + /* Open a NIT device and hang it on this interface... */ + info -> rfdesc = if_register_nit (info); + + /* Set the snap length to 0, which means always take the whole + packet. */ + x = 0; + if (ioctl (info -> rfdesc, NIOCSSNAP, &x) < 0) + log_fatal ("Can't set NIT snap length on %s: %m", info -> name); + + /* Set the stream to byte stream mode */ + if (ioctl (info -> rfdesc, I_SRDOPT, RMSGN) != 0) + log_info ("I_SRDOPT failed on %s: %m", info -> name); + +#if 0 + /* Push on the chunker... */ + if (ioctl (info -> rfdesc, I_PUSH, "nbuf") < 0) + log_fatal ("Can't push chunker onto NIT STREAM: %m"); + + /* Set the timeout to zero. */ + t.tv_sec = 0; + t.tv_usec = 0; + if (ioctl (info -> rfdesc, NIOCSTIME, &t) < 0) + log_fatal ("Can't set chunk timeout: %m"); +#endif + + /* Ask for no header... */ + x = 0; + if (ioctl (info -> rfdesc, NIOCSFLAGS, &x) < 0) + log_fatal ("Can't set NIT flags on %s: %m", info -> name); + + /* Set up the NIT filter program. */ + /* XXX Unlike the BPF filter program, this one won't work if the + XXX IP packet is fragmented or if there are options on the IP + XXX header. */ + pf.Pf_Priority = 0; + pf.Pf_FilterLen = 0; + + pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHWORD + 6; + pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT + ENF_CAND; + pf.Pf_Filter [pf.Pf_FilterLen++] = htons (ETHERTYPE_IP); + pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT; + pf.Pf_Filter [pf.Pf_FilterLen++] = htons (IPPROTO_UDP); + pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHWORD + 11; + pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT + ENF_AND; + pf.Pf_Filter [pf.Pf_FilterLen++] = htons (0xFF); + pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_CAND; + pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHWORD + 18; + pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT + ENF_CAND; + pf.Pf_Filter [pf.Pf_FilterLen++] = local_port; + + /* Install the filter... */ + sio.ic_cmd = NIOCSETF; + sio.ic_len = sizeof pf; + sio.ic_dp = (char *)&pf; + sio.ic_timout = INFTIM; + if (ioctl (info -> rfdesc, I_STR, &sio) < 0) + log_fatal ("Can't set NIT filter on %s: %m", info -> name); + + if (!quiet_interface_discovery) + log_info ("Listening on NIT/%s%s%s", + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} + +void if_deregister_receive (info) + struct interface_info *info; +{ + /* If we're using the nit API for sending and receiving, + we don't need to register this interface twice. */ + close (info -> rfdesc); + info -> rfdesc = -1; + + if (!quiet_interface_discovery) + log_info ("Disabling input on NIT/%s%s%s", + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} +#endif /* USE_NIT_RECEIVE */ + +#ifdef USE_NIT_SEND +ssize_t send_packet (interface, packet, raw, len, from, to, hto) + struct interface_info *interface; + struct packet *packet; + struct dhcp_packet *raw; + size_t len; + struct in_addr from; + struct sockaddr_in *to; + struct hardware *hto; +{ + unsigned hbufp, ibufp; + double hh [16]; + double ih [1536 / sizeof (double)]; + unsigned char *buf = (unsigned char *)ih; + struct sockaddr *junk; + struct strbuf ctl, data; + struct sockaddr_in foo; + int result; + + if (!strcmp (interface -> name, "fallback")) + return send_fallback (interface, packet, raw, + len, from, to, hto); + + if (hto == NULL && interface->anycast_mac_addr.hlen) + hto = &interface->anycast_mac_addr; + + /* Start with the sockaddr struct... */ + junk = (struct sockaddr *)&hh [0]; + hbufp = (((unsigned char *)&junk -> sa_data [0]) - + (unsigned char *)&hh[0]); + ibufp = 0; + + /* Assemble the headers... */ + assemble_hw_header (interface, (unsigned char *)junk, &hbufp, hto); + assemble_udp_ip_header (interface, buf, &ibufp, + from.s_addr, to -> sin_addr.s_addr, + to -> sin_port, (unsigned char *)raw, len); + + /* Copy the data into the buffer (yuk). */ + memcpy (buf + ibufp, raw, len); + + /* Set up the sockaddr structure... */ +#if USE_SIN_LEN + junk -> sa_len = hbufp - 2; /* XXX */ +#endif + junk -> sa_family = AF_UNSPEC; + + /* Set up the msg_buf structure... */ + ctl.buf = (char *)&hh [0]; + ctl.maxlen = ctl.len = hbufp; + data.buf = (char *)&ih [0]; + data.maxlen = data.len = ibufp + len; + + result = putmsg (interface -> wfdesc, &ctl, &data, 0); + if (result < 0) + log_error ("send_packet: %m"); + return result; +} +#endif /* USE_NIT_SEND */ + +#ifdef USE_NIT_RECEIVE +ssize_t receive_packet (interface, buf, len, from, hfrom) + struct interface_info *interface; + unsigned char *buf; + size_t len; + struct sockaddr_in *from; + struct hardware *hfrom; +{ + int nread; + int length = 0; + int offset = 0; + unsigned char ibuf [1536]; + int bufix = 0; + unsigned paylen; + + length = read (interface -> rfdesc, ibuf, sizeof ibuf); + if (length <= 0) + return length; + + /* Decode the physical header... */ + offset = decode_hw_header (interface, ibuf, bufix, hfrom); + + /* If a physical layer checksum failed (dunno of any + physical layer that supports this, but WTH), skip this + packet. */ + if (offset < 0) { + return 0; + } + + bufix += offset; + length -= offset; + + /* Decode the IP and UDP headers... */ + offset = decode_udp_ip_header (interface, ibuf, bufix, + from, length, &paylen, 1); + + /* If the IP or UDP checksum was bad, skip the packet... */ + if (offset < 0) + return 0; + + bufix += offset; + length -= offset; + + if (length < paylen) + log_fatal("Internal inconsistency at %s:%d.", MDL); + + /* Copy out the data in the packet... */ + memcpy(buf, &ibuf[bufix], paylen); + return paylen; +} + +int can_unicast_without_arp (ip) + struct interface_info *ip; +{ + return 1; +} + +int can_receive_unicast_unconfigured (ip) + struct interface_info *ip; +{ + return 1; +} + +int supports_multiple_interfaces (ip) + struct interface_info *ip; +{ + return 1; +} + +void maybe_setup_fallback () +{ + isc_result_t status; + struct interface_info *fbi = (struct interface_info *)0; + if (setup_fallback (&fbi, MDL)) { + if_register_fallback (fbi); + status = omapi_register_io_object ((omapi_object_t *)fbi, + if_readsocket, 0, + fallback_discard, 0, 0); + if (status != ISC_R_SUCCESS) + log_fatal ("Can't register I/O handle for %s: %s", + fbi -> name, isc_result_totext (status)); + interface_dereference (&fbi, MDL); + } +} +#endif diff --git a/common/ns_name.c b/common/ns_name.c new file mode 100644 index 0000000..829fe14 --- /dev/null +++ b/common/ns_name.c @@ -0,0 +1,789 @@ +/* + * Copyright (c) 2004,2009,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * http://www.isc.org/ + */ + +#include <sys/types.h> + +#include <netinet/in.h> +#include <sys/socket.h> + +#include <errno.h> +#include <string.h> +#include <ctype.h> + +#include "minires.h" +#include "arpa/nameser.h" + +/* Data. */ + +static const char digits[] = "0123456789"; + +/* Forward. */ + +static int special(int); +static int printable(int); +static int dn_find(const u_char *, const u_char *, + const u_char * const *, + const u_char * const *); + +/* Public. */ + +/* + * MRns_name_ntop(src, dst, dstsiz) + * Convert an encoded domain name to printable ascii as per RFC1035. + * return: + * Number of bytes written to buffer, or -1 (with errno set) + * notes: + * The root is returned as "." + * All other domains are returned in non absolute form + */ +int +MRns_name_ntop(const u_char *src, char *dst, size_t dstsiz) { + const u_char *cp; + char *dn, *eom; + u_char c; + u_int n; + + cp = src; + dn = dst; + eom = dst + dstsiz; + + while ((n = *cp++) != 0) { + if ((n & NS_CMPRSFLGS) != 0) { + /* Some kind of compression pointer. */ + errno = EMSGSIZE; + return (-1); + } + if (dn != dst) { + if (dn >= eom) { + errno = EMSGSIZE; + return (-1); + } + *dn++ = '.'; + } + if (dn + n >= eom) { + errno = EMSGSIZE; + return (-1); + } + for ((void)NULL; n > 0; n--) { + c = *cp++; + if (special(c)) { + if (dn + 1 >= eom) { + errno = EMSGSIZE; + return (-1); + } + *dn++ = '\\'; + *dn++ = (char)c; + } else if (!printable(c)) { + if (dn + 3 >= eom) { + errno = EMSGSIZE; + return (-1); + } + *dn++ = '\\'; + *dn++ = digits[c / 100]; + *dn++ = digits[(c % 100) / 10]; + *dn++ = digits[c % 10]; + } else { + if (dn >= eom) { + errno = EMSGSIZE; + return (-1); + } + *dn++ = (char)c; + } + } + } + if (dn == dst) { + if (dn >= eom) { + errno = EMSGSIZE; + return (-1); + } + *dn++ = '.'; + } + if (dn >= eom) { + errno = EMSGSIZE; + return (-1); + } + *dn++ = '\0'; + return (dn - dst); +} + +/* + * MRns_name_pton(src, dst, dstsiz) + * Convert a ascii string into an encoded domain name as per RFC1035. + * return: + * -1 if it fails + * 1 if string was fully qualified + * 0 is string was not fully qualified + * notes: + * Enforces label and domain length limits. + */ + +int +MRns_name_pton(const char *src, u_char *dst, size_t dstsiz) { + u_char *label, *bp, *eom; + int c, n, escaped; + char *cp; + + escaped = 0; + bp = dst; + eom = dst + dstsiz; + label = bp++; + + while ((c = *src++) != 0) { + if (escaped) { + if ((cp = strchr(digits, c)) != NULL) { + n = (cp - digits) * 100; + if ((c = *src++) == 0 || + (cp = strchr(digits, c)) == NULL) { + errno = EMSGSIZE; + return (-1); + } + n += (cp - digits) * 10; + if ((c = *src++) == 0 || + (cp = strchr(digits, c)) == NULL) { + errno = EMSGSIZE; + return (-1); + } + n += (cp - digits); + if (n > 255) { + errno = EMSGSIZE; + return (-1); + } + c = n; + } + escaped = 0; + } else if (c == '\\') { + escaped = 1; + continue; + } else if (c == '.') { + c = (bp - label - 1); + if ((c & NS_CMPRSFLGS) != 0) { /* Label too big. */ + errno = EMSGSIZE; + return (-1); + } + if (label >= eom) { + errno = EMSGSIZE; + return (-1); + } + *label = c; + /* Fully qualified ? */ + if (*src == '\0') { + if (c != 0) { + if (bp >= eom) { + errno = EMSGSIZE; + return (-1); + } + *bp++ = '\0'; + } + if ((bp - dst) > MAXCDNAME) { + errno = EMSGSIZE; + return (-1); + } + return (1); + } + if (c == 0 || *src == '.') { + errno = EMSGSIZE; + return (-1); + } + label = bp++; + continue; + } + if (bp >= eom) { + errno = EMSGSIZE; + return (-1); + } + *bp++ = (u_char)c; + } + c = (bp - label - 1); + if ((c & NS_CMPRSFLGS) != 0) { /* Label too big. */ + errno = EMSGSIZE; + return (-1); + } + if (label >= eom) { + errno = EMSGSIZE; + return (-1); + } + *label = c; + if (c != 0) { + if (bp >= eom) { + errno = EMSGSIZE; + return (-1); + } + *bp++ = 0; + } + if ((bp - dst) > MAXCDNAME) { /* src too big */ + errno = EMSGSIZE; + return (-1); + } + return (0); +} + +/* + * MRns_name_ntol(src, dst, dstsiz) + * Convert a network strings labels into all lowercase. + * return: + * Number of bytes written to buffer, or -1 (with errno set) + * notes: + * Enforces label and domain length limits. + */ + +int +MRns_name_ntol(const u_char *src, u_char *dst, size_t dstsiz) { + const u_char *cp; + u_char *dn, *eom; + u_char c; + u_int n; + + cp = src; + dn = dst; + eom = dst + dstsiz; + + if (dn >= eom) { + errno = EMSGSIZE; + return (-1); + } + while ((n = *cp++) != 0) { + if ((n & NS_CMPRSFLGS) != 0) { + /* Some kind of compression pointer. */ + errno = EMSGSIZE; + return (-1); + } + *dn++ = n; + if (dn + n >= eom) { + errno = EMSGSIZE; + return (-1); + } + for ((void)NULL; n > 0; n--) { + c = *cp++; + if (isupper(c)) + *dn++ = tolower(c); + else + *dn++ = c; + } + } + *dn++ = '\0'; + return (dn - dst); +} + +/* + * MRns_name_unpack(msg, eom, src, dst, dstsiz) + * Unpack a domain name from a message, source may be compressed. + * return: + * -1 if it fails, or consumed octets if it succeeds. + */ +int +MRns_name_unpack(const u_char *msg, const u_char *eom, const u_char *src, + u_char *dst, size_t dstsiz) +{ + const u_char *srcp, *dstlim; + u_char *dstp; + unsigned n; + int len; + int checked; + + len = -1; + checked = 0; + dstp = dst; + srcp = src; + dstlim = dst + dstsiz; + if (srcp < msg || srcp >= eom) { + errno = EMSGSIZE; + return (-1); + } + /* Fetch next label in domain name. */ + while ((n = *srcp++) != 0) { + /* Check for indirection. */ + switch (n & NS_CMPRSFLGS) { + case 0: + /* Limit checks. */ + if (dstp + n + 1 >= dstlim || srcp + n >= eom) { + errno = EMSGSIZE; + return (-1); + } + checked += n + 1; + *dstp++ = n; + memcpy(dstp, srcp, n); + dstp += n; + srcp += n; + break; + + case NS_CMPRSFLGS: + if (srcp >= eom) { + errno = EMSGSIZE; + return (-1); + } + if (len < 0) + len = srcp - src + 1; + srcp = msg + (((n & 0x3f) << 8) | (*srcp & 0xff)); + if (srcp < msg || srcp >= eom) { /* Out of range. */ + errno = EMSGSIZE; + return (-1); + } + checked += 2; + /* + * Check for loops in the compressed name; + * if we've looked at the whole message, + * there must be a loop. + */ + if (checked >= eom - msg) { + errno = EMSGSIZE; + return (-1); + } + break; + + default: + errno = EMSGSIZE; + return (-1); /* flag error */ + } + } + *dstp = '\0'; + if (len < 0) + len = srcp - src; + return (len); +} + +/* + * MRns_name_pack(src, dst, dstsiz, dnptrs, lastdnptr) + * Pack domain name 'domain' into 'comp_dn'. + * return: + * Size of the compressed name, or -1. + * notes: + * 'dnptrs' is an array of pointers to previous compressed names. + * dnptrs[0] is a pointer to the beginning of the message. The array + * ends with NULL. + * 'lastdnptr' is a pointer to the end of the array pointed to + * by 'dnptrs'. + * Side effects: + * The list of pointers in dnptrs is updated for labels inserted into + * the message as we compress the name. If 'dnptr' is NULL, we don't + * try to compress names. If 'lastdnptr' is NULL, we don't update the + * list. + */ +int +MRns_name_pack(const u_char *src, u_char *dst, unsigned dstsiz, + const u_char **dnptrs, const u_char **lastdnptr) +{ + u_char *dstp; + const u_char **cpp, **lpp, *eob, *msg; + const u_char *srcp; + unsigned n; + int l; + + srcp = src; + dstp = dst; + eob = dstp + dstsiz; + lpp = cpp = NULL; + if (dnptrs != NULL) { + if ((msg = *dnptrs++) != NULL) { + for (cpp = dnptrs; *cpp != NULL; cpp++) + (void)NULL; + lpp = cpp; /* end of list to search */ + } + } else + msg = NULL; + + /* make sure the domain we are about to add is legal */ + l = 0; + do { + n = *srcp; + if ((n & NS_CMPRSFLGS) != 0) { + errno = EMSGSIZE; + return (-1); + } + l += n + 1; + if (l > MAXCDNAME) { + errno = EMSGSIZE; + return (-1); + } + srcp += n + 1; + } while (n != 0); + + /* from here on we need to reset compression pointer array on error */ + srcp = src; + do { + /* Look to see if we can use pointers. */ + n = *srcp; + if (n != 0 && msg != NULL) { + l = dn_find(srcp, msg, (const u_char * const *)dnptrs, + (const u_char * const *)lpp); + if (l >= 0) { + if (dstp + 1 >= eob) { + goto cleanup; + } + *dstp++ = (l >> 8) | NS_CMPRSFLGS; + *dstp++ = l % 256; + return (dstp - dst); + } + /* Not found, save it. */ + if (lastdnptr != NULL && cpp < lastdnptr - 1 && + (dstp - msg) < 0x4000) { + *cpp++ = dstp; + *cpp = NULL; + } + } + /* copy label to buffer */ + if (n & NS_CMPRSFLGS) { /* Should not happen. */ + goto cleanup; + } + if (dstp + 1 + n >= eob) { + goto cleanup; + } + memcpy(dstp, srcp, n + 1); + srcp += n + 1; + dstp += n + 1; + } while (n != 0); + + if (dstp > eob) { +cleanup: + if (msg != NULL) + *lpp = NULL; + errno = EMSGSIZE; + return (-1); + } + return (dstp - dst); +} + +/* + * MRns_name_uncompress(msg, eom, src, dst, dstsiz) + * Expand compressed domain name to presentation format. + * return: + * Number of bytes read out of `src', or -1 (with errno set). + * note: + * Root domain returns as "." not "". + */ +int +MRns_name_uncompress(const u_char *msg, const u_char *eom, const u_char *src, + char *dst, size_t dstsiz) +{ + u_char tmp[NS_MAXCDNAME]; + int n; + + if ((n = MRns_name_unpack(msg, eom, src, tmp, sizeof tmp)) == -1) + return (-1); + if (MRns_name_ntop(tmp, dst, dstsiz) == -1) + return (-1); + return (n); +} + +/* + * MRns_name_compress(src, dst, dstsiz, dnptrs, lastdnptr) + * Compress a domain name into wire format, using compression pointers. + * return: + * Number of bytes consumed in `dst' or -1 (with errno set). + * notes: + * 'dnptrs' is an array of pointers to previous compressed names. + * dnptrs[0] is a pointer to the beginning of the message. + * The list ends with NULL. 'lastdnptr' is a pointer to the end of the + * array pointed to by 'dnptrs'. Side effect is to update the list of + * pointers for labels inserted into the message as we compress the name. + * If 'dnptr' is NULL, we don't try to compress names. If 'lastdnptr' + * is NULL, we don't update the list. + */ +int +MRns_name_compress(const char *src, u_char *dst, size_t dstsiz, + const u_char **dnptrs, const u_char **lastdnptr) +{ + u_char tmp[NS_MAXCDNAME]; + + if (MRns_name_pton(src, tmp, sizeof tmp) == -1) + return (-1); + return (MRns_name_pack(tmp, dst, dstsiz, dnptrs, lastdnptr)); +} + +/* + * MRns_name_skip(ptrptr, eom) + * Advance *ptrptr to skip over the compressed name it points at. + * return: + * 0 on success, -1 (with errno set) on failure. + */ +int +MRns_name_skip(const u_char **ptrptr, const u_char *eom) { + const u_char *cp; + u_int n; + + cp = *ptrptr; + while (cp < eom && (n = *cp++) != 0) { + /* Check for indirection. */ + switch (n & NS_CMPRSFLGS) { + case 0: /* normal case, n == len */ + cp += n; + continue; + case NS_CMPRSFLGS: /* indirection */ + cp++; + break; + default: /* illegal type */ + errno = EMSGSIZE; + return (-1); + } + break; + } + if (cp > eom) { + errno = EMSGSIZE; + return (-1); + } + *ptrptr = cp; + return (0); +} + +/* Private. */ + +/* + * special(ch) + * Thinking in noninternationalized USASCII (per the DNS spec), + * is this characted special ("in need of quoting") ? + * return: + * boolean. + */ +static int +special(int ch) { + switch (ch) { + case 0x22: /* '"' */ + case 0x2E: /* '.' */ + case 0x3B: /* ';' */ + case 0x5C: /* '\\' */ + /* Special modifiers in zone files. */ + case 0x40: /* '@' */ + case 0x24: /* '$' */ + return (1); + default: + return (0); + } +} + +/* + * printable(ch) + * Thinking in noninternationalized USASCII (per the DNS spec), + * is this character visible and not a space when printed ? + * return: + * boolean. + */ +static int +printable(int ch) { + return (ch > 0x20 && ch < 0x7f); +} + +/* + * Thinking in noninternationalized USASCII (per the DNS spec), + * convert this character to lower case if it's upper case. + */ +static int +mklower(int ch) { + if (ch >= 0x41 && ch <= 0x5A) + return (ch + 0x20); + return (ch); +} + +/* + * dn_find(domain, msg, dnptrs, lastdnptr) + * Search for the counted-label name in an array of compressed names. + * return: + * offset from msg if found, or -1. + * notes: + * dnptrs is the pointer to the first name on the list, + * not the pointer to the start of the message. + */ +static int +dn_find(const u_char *domain, const u_char *msg, + const u_char * const *dnptrs, + const u_char * const *lastdnptr) +{ + const u_char *dn, *cp, *sp; + const u_char * const *cpp; + u_int n; + + for (cpp = dnptrs; cpp < lastdnptr; cpp++) { + dn = domain; + sp = cp = *cpp; + while ((n = *cp++) != 0) { + /* + * check for indirection + */ + switch (n & NS_CMPRSFLGS) { + case 0: /* normal case, n == len */ + if (n != *dn++) + goto next; + for ((void)NULL; n > 0; n--) + if (mklower(*dn++) != mklower(*cp++)) + goto next; + /* Is next root for both ? */ + if (*dn == '\0' && *cp == '\0') + return (sp - msg); + if (*dn) + continue; + goto next; + + case NS_CMPRSFLGS: /* indirection */ + cp = msg + (((n & 0x3f) << 8) | *cp); + break; + + default: /* illegal type */ + errno = EMSGSIZE; + return (-1); + } + } + next: ; + } + errno = ENOENT; + return (-1); +} + +/*! + * \brief Creates a string of comma-separated domain-names from a + * compressed list + * + * Produces a null-terminated string of comma-separated domain-names from + * a buffer containing a compressed list of domain-names. The names will + * be dotted and without enclosing quotes. For example: + * If a compressed list contains the follwoing two domain names: + * + * a. one.two.com + * b. three.four.com + * + * The compressed data will look like this: + * + * 03 6f 6e 65 03 74 77 6f 03 63 6f 6d 00 05 74 68 + * 72 65 65 04 66 6f 75 72 c0 08 + * + * and will decompress into: + * + * one.two.com,three.four.com + * + * \param buf - buffer containing the compressed list of domain-names + * \param buflen - length of compressed list of domain-names + * \param dst_buf - buffer to receive the decompressed list + * \param dst_size - size of the destination buffer + * + * \return the length of the decompressed string when successful, -1 on + * error. + */ +int MRns_name_uncompress_list(const unsigned char* buf, int buflen, + char* dst_buf, size_t dst_size) +{ + const unsigned char* src = buf; + char* dst = dst_buf; + int consumed = 1; + int dst_remaining = dst_size; + int added_len = 0; + int first_pass = 1; + + if (!buf || buflen == 0 || *buf == 0x00) { + /* nothing to do */ + *dst = 0; + return (0); + } + + while ((consumed > 0) && (src < (buf + buflen))) + { + if (dst_remaining <= 0) { + errno = EMSGSIZE; + return (-1); + } + + if (!first_pass) { + *dst++ = ','; + *dst = '\0'; + dst_remaining--; + } + + consumed = MRns_name_uncompress(buf, buf + buflen, src, + dst, dst_remaining); + if (consumed < 0) { + return (-1); + } + + src += consumed; + added_len = strlen(dst); + dst_remaining -= added_len; + dst += added_len; + first_pass = 0; + } + *dst='\0'; + + /* return the length of the uncompressed list string */ + return (strlen(dst_buf)); +} + +/*! + * \brief Creates a compressed list from a string of comma-separated + * domain-names + * + * Produces a buffer containing a compressed data version of a list of + * domain-names extracted from a comma-separated string. Given a string + * containing: + * + * one.two.com,three.four.com + * + * It will compress this into: + * + * 03 6f 6e 65 03 74 77 6f 03 63 6f 6d 00 05 74 68 + * 72 65 65 04 66 6f 75 72 c0 08 + * + * \param buf - buffer containing the uncompressed string of domain-names + * \param buflen - length of uncompressed string of domain-names + * \param compbuf - buffer to receive the compressed list + * \param compbuf_size - size of the compression buffer + * + * \return the length of the compressed data when successful, -1 on error. + */ +int MRns_name_compress_list(const char* buf, int buflen, + unsigned char* compbuf, size_t compbuf_size) +{ + char cur_name[NS_MAXCDNAME]; + const unsigned char *dnptrs[256], **lastdnptr; + const char* src; + const char* src_end; + unsigned clen = 0; + int result = 0; + + memset(compbuf, 0, compbuf_size); + memset(dnptrs, 0, sizeof(dnptrs)); + dnptrs[0] = compbuf; + lastdnptr = &dnptrs[255]; + + src = buf; + src_end = buf + buflen; + while (src < src_end) { + char *comma = strchr(src, ','); + int copylen = ((comma != NULL) ? comma - src : strlen(src)); + if (copylen > (sizeof(cur_name) - 1)) { + errno = EMSGSIZE; + return (-1); + } + + memcpy(cur_name, src, copylen); + cur_name[copylen] = '\0'; + src += copylen + 1; + + result = MRns_name_compress(cur_name, compbuf + clen, + compbuf_size - clen, + dnptrs, lastdnptr); + + if (result < 0) { + return (-1); + } + + clen += result; + } + + /* return size of compressed list */ + return(clen); +} diff --git a/common/options.c b/common/options.c new file mode 100644 index 0000000..2e51bd4 --- /dev/null +++ b/common/options.c @@ -0,0 +1,4227 @@ +/* options.c + + DHCP options parsing and reassembly. */ + +/* + * Copyright (c) 2004-2012,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#define DHCP_OPTION_DATA +#include "dhcpd.h" +#include <omapip/omapip_p.h> +#include <limits.h> + +struct option *vendor_cfg_option; + +static int pretty_text(char **, char *, const unsigned char **, + const unsigned char *, int); +static int pretty_domain(char **, char *, const unsigned char **, + const unsigned char *); +static int prepare_option_buffer(struct universe *universe, struct buffer *bp, + unsigned char *buffer, unsigned length, + unsigned code, int terminatep, + struct option_cache **opp); + +/* Parse all available options out of the specified packet. */ +/* Note, the caller is responsible for allocating packet->options. */ +int parse_options (packet) + struct packet *packet; +{ + struct option_cache *op = NULL; + + /* If we don't see the magic cookie, there's nothing to parse. */ + if (memcmp (packet -> raw -> options, DHCP_OPTIONS_COOKIE, 4)) { + packet -> options_valid = 0; + return 1; + } + + /* Go through the options field, up to the end of the packet + or the End field. */ + if (!parse_option_buffer (packet -> options, + &packet -> raw -> options [4], + (packet -> packet_length - + DHCP_FIXED_NON_UDP - 4), + &dhcp_universe)) { + + /* STSN servers have a bug where they send a mangled + domain-name option, and whatever is beyond that in + the packet is junk. Microsoft clients accept this, + which is probably why whoever implemented the STSN + server isn't aware of the problem yet. To work around + this, we will accept corrupt packets from the server if + they contain a valid DHCP_MESSAGE_TYPE option, but + will not accept any corrupt client packets (the ISC DHCP + server is sufficiently widely used that it is probably + beneficial for it to be picky) and will not accept + packets whose type can't be determined. */ + + if ((op = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_MESSAGE_TYPE))) { + if (!op -> data.data || + (op -> data.data [0] != DHCPOFFER && + op -> data.data [0] != DHCPACK && + op -> data.data [0] != DHCPNAK)) + return 0; + } else + return 0; + } + + /* If we parsed a DHCP Option Overload option, parse more + options out of the buffer(s) containing them. */ + if ((op = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_OPTION_OVERLOAD))) { + if (op -> data.data [0] & 1) { + if (!parse_option_buffer + (packet -> options, + (unsigned char *)packet -> raw -> file, + sizeof packet -> raw -> file, + &dhcp_universe)) + return 0; + } + if (op -> data.data [0] & 2) { + if (!parse_option_buffer + (packet -> options, + (unsigned char *)packet -> raw -> sname, + sizeof packet -> raw -> sname, + &dhcp_universe)) + return 0; + } + } + packet -> options_valid = 1; + return 1; +} + +/* Parse options out of the specified buffer, storing addresses of option + * values in packet->options. + */ +int parse_option_buffer (options, buffer, length, universe) + struct option_state *options; + const unsigned char *buffer; + unsigned length; + struct universe *universe; +{ + unsigned len, offset; + unsigned code; + struct option_cache *op = NULL, *nop = NULL; + struct buffer *bp = (struct buffer *)0; + struct option *option = NULL; + char *reason = "general failure"; + + if (!buffer_allocate (&bp, length, MDL)) { + log_error ("no memory for option buffer."); + return 0; + } + memcpy (bp -> data, buffer, length); + + for (offset = 0; + (offset + universe->tag_size) <= length && + (code = universe->get_tag(buffer + offset)) != universe->end; ) { + offset += universe->tag_size; + + /* Pad options don't have a length - just skip them. */ + if (code == DHO_PAD) + continue; + + /* Don't look for length if the buffer isn't that big. */ + if ((offset + universe->length_size) > length) { + reason = "code tag at end of buffer - missing " + "length field"; + goto bogus; + } + + /* All other fields (except PAD and END handled above) + * have a length field, unless it's a DHCPv6 zero-length + * options space (eg any of the enterprise-id'd options). + * + * Zero-length-size option spaces basically consume the + * entire options buffer, so have at it. + */ + if (universe->get_length != NULL) + len = universe->get_length(buffer + offset); + else if (universe->length_size == 0) + len = length - universe->tag_size; + else { + log_fatal("Improperly configured option space(%s): " + "may not have a nonzero length size " + "AND a NULL get_length function.", + universe->name); + + /* Silence compiler warnings. */ + return 0; + } + + offset += universe->length_size; + + option_code_hash_lookup(&option, universe->code_hash, &code, + 0, MDL); + + /* If the length is outrageous, the options are bad. */ + if (offset + len > length) { + reason = "option length exceeds option buffer length"; + bogus: + log_error("parse_option_buffer: malformed option " + "%s.%s (code %u): %s.", universe->name, + option ? option->name : "<unknown>", + code, reason); + buffer_dereference (&bp, MDL); + return 0; + } + + /* If the option contains an encapsulation, parse it. If + the parse fails, or the option isn't an encapsulation (by + far the most common case), or the option isn't entirely + an encapsulation, keep the raw data as well. */ + if (!(option && + (option->format[0] == 'e' || + option->format[0] == 'E') && + (parse_encapsulated_suboptions(options, option, + bp->data + offset, len, + universe, NULL)))) { + op = lookup_option(universe, options, code); + + if (op != NULL && universe->concat_duplicates) { + struct data_string new; + memset(&new, 0, sizeof new); + if (!buffer_allocate(&new.buffer, + op->data.len + len, + MDL)) { + log_error("parse_option_buffer: " + "No memory."); + buffer_dereference(&bp, MDL); + return 0; + } + /* Copy old option to new data object. */ + memcpy(new.buffer->data, op->data.data, + op->data.len); + /* Concat new option behind old. */ + memcpy(new.buffer->data + op->data.len, + bp->data + offset, len); + new.len = op->data.len + len; + new.data = new.buffer->data; + /* Save new concat'd object. */ + data_string_forget(&op->data, MDL); + data_string_copy(&op->data, &new, MDL); + data_string_forget(&new, MDL); + } else if (op != NULL) { + /* We must append this statement onto the + * end of the list. + */ + while (op->next != NULL) + op = op->next; + + if (!option_cache_allocate(&nop, MDL)) { + log_error("parse_option_buffer: " + "No memory."); + buffer_dereference(&bp, MDL); + return 0; + } + + option_reference(&nop->option, op->option, MDL); + + nop->data.buffer = NULL; + buffer_reference(&nop->data.buffer, bp, MDL); + nop->data.data = bp->data + offset; + nop->data.len = len; + + option_cache_reference(&op->next, nop, MDL); + option_cache_dereference(&nop, MDL); + } else { + if (save_option_buffer(universe, options, bp, + bp->data + offset, len, + code, 1) == 0) { + log_error("parse_option_buffer: " + "save_option_buffer failed"); + buffer_dereference(&bp, MDL); + return 0; + } + } + } + option_dereference(&option, MDL); + offset += len; + } + buffer_dereference (&bp, MDL); + return 1; +} + +/* If an option in an option buffer turns out to be an encapsulation, + figure out what to do. If we don't know how to de-encapsulate it, + or it's not well-formed, return zero; otherwise, return 1, indicating + that we succeeded in de-encapsulating it. */ + +struct universe *find_option_universe (struct option *eopt, const char *uname) +{ + int i; + char *s, *t; + struct universe *universe = (struct universe *)0; + + /* Look for the E option in the option format. */ + s = strchr (eopt -> format, 'E'); + if (!s) { + log_error ("internal encapsulation format error 1."); + return 0; + } + /* Look for the universe name in the option format. */ + t = strchr (++s, '.'); + /* If there was no trailing '.', or there's something after the + trailing '.', the option is bogus and we can't use it. */ + if (!t || t [1]) { + log_error ("internal encapsulation format error 2."); + return 0; + } + if (t == s && uname) { + for (i = 0; i < universe_count; i++) { + if (!strcmp (universes [i] -> name, uname)) { + universe = universes [i]; + break; + } + } + } else if (t != s) { + for (i = 0; i < universe_count; i++) { + if (strlen (universes [i] -> name) == t - s && + !memcmp (universes [i] -> name, + s, (unsigned)(t - s))) { + universe = universes [i]; + break; + } + } + } + return universe; +} + +/* If an option in an option buffer turns out to be an encapsulation, + figure out what to do. If we don't know how to de-encapsulate it, + or it's not well-formed, return zero; otherwise, return 1, indicating + that we succeeded in de-encapsulating it. */ + +int parse_encapsulated_suboptions (struct option_state *options, + struct option *eopt, + const unsigned char *buffer, + unsigned len, struct universe *eu, + const char *uname) +{ + int i; + struct universe *universe = find_option_universe (eopt, uname); + + /* If we didn't find the universe, we can't do anything with it + right now (e.g., we can't decode vendor options until we've + decoded the packet and executed the scopes that it matches). */ + if (!universe) + return 0; + + /* If we don't have a decoding function for it, we can't decode + it. */ + if (!universe -> decode) + return 0; + + i = (*universe -> decode) (options, buffer, len, universe); + + /* If there is stuff before the suboptions, we have to keep it. */ + if (eopt -> format [0] != 'E') + return 0; + /* Otherwise, return the status of the decode function. */ + return i; +} + +int fqdn_universe_decode (struct option_state *options, + const unsigned char *buffer, + unsigned length, struct universe *u) +{ + struct buffer *bp = (struct buffer *)0; + + /* FQDN options have to be at least four bytes long. */ + if (length < 3) + return 0; + + /* Save the contents of the option in a buffer. */ + if (!buffer_allocate (&bp, length + 4, MDL)) { + log_error ("no memory for option buffer."); + return 0; + } + memcpy (&bp -> data [3], buffer + 1, length - 1); + + if (buffer [0] & 4) /* encoded */ + bp -> data [0] = 1; + else + bp -> data [0] = 0; + if (!save_option_buffer(&fqdn_universe, options, bp, + bp->data, 1, FQDN_ENCODED, 0)) { + bad: + buffer_dereference (&bp, MDL); + return 0; + } + + if (buffer [0] & 1) /* server-update */ + bp -> data [2] = 1; + else + bp -> data [2] = 0; + if (buffer [0] & 2) /* no-client-update */ + bp -> data [1] = 1; + else + bp -> data [1] = 0; + + /* XXX Ideally we should store the name in DNS format, so if the + XXX label isn't in DNS format, we convert it to DNS format, + XXX rather than converting labels specified in DNS format to + XXX the plain ASCII representation. But that's hard, so + XXX not now. */ + + /* Not encoded using DNS format? */ + if (!bp -> data [0]) { + unsigned i; + + /* Some broken clients NUL-terminate this option. */ + if (buffer [length - 1] == 0) { + --length; + bp -> data [1] = 1; + } + + /* Determine the length of the hostname component of the + name. If the name contains no '.' character, it + represents a non-qualified label. */ + for (i = 3; i < length && buffer [i] != '.'; i++); + i -= 3; + + /* Note: If the client sends a FQDN, the first '.' will + be used as a NUL terminator for the hostname. */ + if (i && (!save_option_buffer(&fqdn_universe, options, bp, + &bp->data[5], i, + FQDN_HOSTNAME, 0))) + goto bad; + /* Note: If the client sends a single label, the + FQDN_DOMAINNAME option won't be set. */ + if (length > 4 + i && + (!save_option_buffer(&fqdn_universe, options, bp, + &bp -> data[6 + i], length - 4 - i, + FQDN_DOMAINNAME, 1))) + goto bad; + /* Also save the whole name. */ + if (length > 3) { + if (!save_option_buffer(&fqdn_universe, options, bp, + &bp -> data [5], length - 3, + FQDN_FQDN, 1)) + goto bad; + } + } else { + unsigned len; + unsigned total_len = 0; + unsigned first_len = 0; + int terminated = 0; + unsigned char *s; + + s = &bp -> data[5]; + + while (s < &bp -> data[0] + length + 2) { + len = *s; + if (len > 63) { + log_info ("fancy bits in fqdn option"); + return 0; + } + if (len == 0) { + terminated = 1; + break; + } + if (s + len > &bp -> data [0] + length + 3) { + log_info ("fqdn tag longer than buffer"); + return 0; + } + + if (first_len == 0) { + first_len = len; + } + + *s = '.'; + s += len + 1; + total_len += len + 1; + } + + /* We wind up with a length that's one too many because + we shouldn't increment for the last label, but there's + no way to tell we're at the last label until we exit + the loop. :'*/ + if (total_len > 0) + total_len--; + + if (!terminated) { + first_len = total_len; + } + + if (first_len > 0 && + !save_option_buffer(&fqdn_universe, options, bp, + &bp -> data[6], first_len, + FQDN_HOSTNAME, 0)) + goto bad; + if (total_len > 0 && first_len != total_len) { + if (!save_option_buffer(&fqdn_universe, options, bp, + &bp->data[6 + first_len], + total_len - first_len, + FQDN_DOMAINNAME, 1)) + goto bad; + } + if (total_len > 0) + if (!save_option_buffer (&fqdn_universe, options, bp, + &bp -> data [6], total_len, + FQDN_FQDN, 1)) + goto bad; + } + + if (!save_option_buffer (&fqdn_universe, options, bp, + &bp -> data [1], 1, + FQDN_NO_CLIENT_UPDATE, 0)) + goto bad; + if (!save_option_buffer (&fqdn_universe, options, bp, + &bp -> data [2], 1, + FQDN_SERVER_UPDATE, 0)) + goto bad; + + if (!save_option_buffer (&fqdn_universe, options, bp, + &bp -> data [3], 1, + FQDN_RCODE1, 0)) + goto bad; + if (!save_option_buffer (&fqdn_universe, options, bp, + &bp -> data [4], 1, + FQDN_RCODE2, 0)) + goto bad; + + buffer_dereference (&bp, MDL); + return 1; +} + +/* + * Load all options into a buffer, and then split them out into the three + * separate fields in the dhcp packet (options, file, and sname) where + * options can be stored. + * + * returns 0 on error, length of packet on success + */ +int +cons_options(struct packet *inpacket, struct dhcp_packet *outpacket, + struct lease *lease, struct client_state *client_state, + int mms, struct option_state *in_options, + struct option_state *cfg_options, + struct binding_scope **scope, + int overload_avail, int terminate, int bootpp, + struct data_string *prl, const char *vuname) +{ +#define PRIORITY_COUNT 300 + unsigned priority_list[PRIORITY_COUNT]; + int priority_len; + unsigned char buffer[4096], agentopts[1024]; + unsigned index = 0; + unsigned mb_size = 0, mb_max = 0; + unsigned option_size = 0, agent_size = 0; + unsigned length; + int i; + struct option_cache *op; + struct data_string ds; + pair pp, *hash; + int overload_used = 0; + int of1 = 0, of2 = 0; + + memset(&ds, 0, sizeof ds); + + /* + * If there's a Maximum Message Size option in the incoming packet + * and no alternate maximum message size has been specified, or + * if the one specified in the packet is shorter than the + * alternative, take the one in the packet. + */ + + if (inpacket && + (op = lookup_option(&dhcp_universe, inpacket->options, + DHO_DHCP_MAX_MESSAGE_SIZE)) && + (evaluate_option_cache(&ds, inpacket, lease, + client_state, in_options, + cfg_options, scope, op, MDL) != 0)) { + if (ds.len >= sizeof (u_int16_t)) { + i = getUShort(ds.data); + if(!mms || (i < mms)) + mms = i; + } + data_string_forget(&ds, MDL); + } + + /* + * If the client has provided a maximum DHCP message size, + * use that, up to the MTU limit. Otherwise, if it's BOOTP, + * only 64 bytes; otherwise use up to the minimum IP MTU size + * (576 bytes). + * + * XXX if a BOOTP client specifies a max message size, we will + * honor it. + */ + if (mms) { + if (mms < DHCP_MTU_MIN) + /* Enforce minimum packet size, per RFC 2132 */ + mb_size = DHCP_MIN_OPTION_LEN; + else if (mms > DHCP_MTU_MAX) + /* + * TODO: Packets longer than 1500 bytes really + * should be allowed, but it requires upstream + * changes to the way the packet is allocated. For + * now, we forbid them. They won't be needed very + * often anyway. + */ + mb_size = DHCP_MAX_OPTION_LEN; + else + mb_size = mms - DHCP_FIXED_LEN; + } else if (bootpp) { + mb_size = 64; + if (inpacket != NULL && + (inpacket->packet_length >= 64 + DHCP_FIXED_NON_UDP)) + mb_size = inpacket->packet_length - DHCP_FIXED_NON_UDP; + } else + mb_size = DHCP_MIN_OPTION_LEN; + + /* + * If answering a client message, see whether any relay agent + * options were included with the message. If so, save them + * to copy back in later, and make space in the main buffer + * to accommodate them + */ + if (client_state == NULL) { + priority_list[0] = DHO_DHCP_AGENT_OPTIONS; + priority_len = 1; + agent_size = store_options(NULL, agentopts, 0, + sizeof(agentopts), + inpacket, lease, client_state, + in_options, cfg_options, scope, + priority_list, priority_len, + 0, 0, 0, NULL); + + mb_size += agent_size; + if (mb_size > DHCP_MAX_OPTION_LEN) + mb_size = DHCP_MAX_OPTION_LEN; + } + + /* + * Set offsets for buffer data to be copied into filename + * and servername fields + */ + if (mb_size > agent_size) + mb_max = mb_size - agent_size; + else + mb_max = mb_size; + + if (overload_avail & 1) { + of1 = mb_max; + mb_max += DHCP_FILE_LEN; + } + + if (overload_avail & 2) { + of2 = mb_max; + mb_max += DHCP_SNAME_LEN; + } + + /* + * Preload the option priority list with protocol-mandatory options. + * This effectively gives these options the highest priority. + * This provides the order for any available options, the option + * must be in the option cache in order to actually be included. + */ + priority_len = 0; + priority_list[priority_len++] = DHO_DHCP_MESSAGE_TYPE; + priority_list[priority_len++] = DHO_DHCP_SERVER_IDENTIFIER; + priority_list[priority_len++] = DHO_DHCP_LEASE_TIME; + priority_list[priority_len++] = DHO_DHCP_RENEWAL_TIME; + priority_list[priority_len++] = DHO_DHCP_REBINDING_TIME; + priority_list[priority_len++] = DHO_DHCP_MESSAGE; + priority_list[priority_len++] = DHO_DHCP_REQUESTED_ADDRESS; + priority_list[priority_len++] = DHO_ASSOCIATED_IP; + + if (prl != NULL && prl->len > 0) { + if ((op = lookup_option(&dhcp_universe, cfg_options, + DHO_SUBNET_SELECTION))) { + if (priority_len < PRIORITY_COUNT) + priority_list[priority_len++] = + DHO_SUBNET_SELECTION; + } + + data_string_truncate(prl, (PRIORITY_COUNT - priority_len)); + + /* + * Copy the client's PRL onto the priority_list after our high + * priority header. + */ + for (i = 0; i < prl->len; i++) { + /* + * Prevent client from changing order of delivery + * of relay agent information option. + */ + if (prl->data[i] != DHO_DHCP_AGENT_OPTIONS) + priority_list[priority_len++] = prl->data[i]; + } + + /* + * If the client doesn't request the FQDN option explicitly, + * to indicate priority, consider it lowest priority. Fit + * in the packet if there is space. Note that the option + * may only be included if the client supplied one. + */ + if ((inpacket != NULL) && (priority_len < PRIORITY_COUNT) && + (lookup_option(&fqdn_universe, inpacket->options, + FQDN_ENCODED) != NULL)) + priority_list[priority_len++] = DHO_FQDN; + + /* + * Some DHCP Servers will give the subnet-mask option if + * it is not on the parameter request list - so some client + * implementations have come to rely on this - so we will + * also make sure we supply this, at lowest priority. + * + * This is only done in response to DHCPDISCOVER or + * DHCPREQUEST messages, to avoid providing the option on + * DHCPINFORM or DHCPLEASEQUERY responses (if the client + * didn't request it). + */ + if ((inpacket != NULL) && (priority_len < PRIORITY_COUNT) && + ((inpacket->packet_type == DHCPDISCOVER) || + (inpacket->packet_type == DHCPREQUEST))) + priority_list[priority_len++] = DHO_SUBNET_MASK; + } else { + /* + * First, hardcode some more options that ought to be + * sent first...these are high priority to have in the + * packet. + */ + priority_list[priority_len++] = DHO_SUBNET_MASK; + priority_list[priority_len++] = DHO_ROUTERS; + priority_list[priority_len++] = DHO_DOMAIN_NAME_SERVERS; + priority_list[priority_len++] = DHO_HOST_NAME; + priority_list[priority_len++] = DHO_FQDN; + + /* + * Append a list of the standard DHCP options from the + * standard DHCP option space. Actually, if a site + * option space hasn't been specified, we wind up + * treating the dhcp option space as the site option + * space, and the first for loop is skipped, because + * it's slightly more general to do it this way, + * taking the 1Q99 DHCP futures work into account. + */ + if (cfg_options->site_code_min) { + for (i = 0; i < OPTION_HASH_SIZE; i++) { + hash = cfg_options->universes[dhcp_universe.index]; + if (hash) { + for (pp = hash[i]; pp; pp = pp->cdr) { + op = (struct option_cache *)(pp->car); + if (op->option->code < + cfg_options->site_code_min && + priority_len < PRIORITY_COUNT && + op->option->code != DHO_DHCP_AGENT_OPTIONS) + priority_list[priority_len++] = + op->option->code; + } + } + } + } + + /* + * Now cycle through the site option space, or if there + * is no site option space, we'll be cycling through the + * dhcp option space. + */ + for (i = 0; i < OPTION_HASH_SIZE; i++) { + hash = cfg_options->universes[cfg_options->site_universe]; + if (hash != NULL) + for (pp = hash[i]; pp; pp = pp->cdr) { + op = (struct option_cache *)(pp->car); + if (op->option->code >= + cfg_options->site_code_min && + priority_len < PRIORITY_COUNT && + op->option->code != DHO_DHCP_AGENT_OPTIONS) + priority_list[priority_len++] = + op->option->code; + } + } + + /* + * Put any spaces that are encapsulated on the list, + * sort out whether they contain values later. + */ + for (i = 0; i < cfg_options->universe_count; i++) { + if (universes[i]->enc_opt && + priority_len < PRIORITY_COUNT && + universes[i]->enc_opt->universe == &dhcp_universe) { + if (universes[i]->enc_opt->code != + DHO_DHCP_AGENT_OPTIONS) + priority_list[priority_len++] = + universes[i]->enc_opt->code; + } + } + + /* + * The vendor option space can't stand on its own, so always + * add it to the list. + */ + if (priority_len < PRIORITY_COUNT) + priority_list[priority_len++] = + DHO_VENDOR_ENCAPSULATED_OPTIONS; + } + + /* Put the cookie up front... */ + memcpy(buffer, DHCP_OPTIONS_COOKIE, 4); + index += 4; + + /* Copy the options into the big buffer... */ + option_size = store_options(&overload_used, buffer, index, mb_max, + inpacket, lease, client_state, + in_options, cfg_options, scope, + priority_list, priority_len, + of1, of2, terminate, vuname); + + /* If store_options() failed */ + if (option_size == 0) + return 0; + + /* How much was stored in the main buffer? */ + index += option_size; + + /* + * If we're going to have to overload, store the overload + * option first. + */ + if (overload_used) { + if (mb_size - agent_size - index < 3) + return 0; + + buffer[index++] = DHO_DHCP_OPTION_OVERLOAD; + buffer[index++] = 1; + buffer[index++] = overload_used; + + if (overload_used & 1) + memcpy(outpacket->file, &buffer[of1], DHCP_FILE_LEN); + + if (overload_used & 2) + memcpy(outpacket->sname, &buffer[of2], DHCP_SNAME_LEN); + } + + /* Now copy in preserved agent options, if any */ + if (agent_size) { + if (mb_size - index >= agent_size) { + memcpy(&buffer[index], agentopts, agent_size); + index += agent_size; + } else + log_error("Unable to store relay agent information " + "in reply packet."); + } + + /* Tack a DHO_END option onto the packet if we need to. */ + if (index < mb_size) + buffer[index++] = DHO_END; + + /* Copy main buffer into the options buffer of the packet */ + memcpy(outpacket->options, buffer, index); + + /* Figure out the length. */ + length = DHCP_FIXED_NON_UDP + index; + return length; +} + +/* + * XXX: We currently special case collecting VSIO options. + * We should be able to handle this in a more generic fashion, by + * including any encapsulated options that are present and desired. + * This will look something like the VSIO handling VSIO code. + * We may also consider handling the ORO-like options within + * encapsulated spaces. + */ + +struct vsio_state { + char *buf; + int buflen; + int bufpos; +}; + +static void +vsio_options(struct option_cache *oc, + struct packet *packet, + struct lease *dummy_lease, + struct client_state *dummy_client_state, + struct option_state *dummy_opt_state, + struct option_state *opt_state, + struct binding_scope **dummy_binding_scope, + struct universe *universe, + void *void_vsio_state) { + struct vsio_state *vs = (struct vsio_state *)void_vsio_state; + struct data_string ds; + int total_len; + + memset(&ds, 0, sizeof(ds)); + if (evaluate_option_cache(&ds, packet, NULL, + NULL, opt_state, NULL, + &global_scope, oc, MDL)) { + total_len = ds.len + universe->tag_size + universe->length_size; + if (total_len <= (vs->buflen - vs->bufpos)) { + if (universe->tag_size == 1) { + vs->buf[vs->bufpos++] = oc->option->code; + } else if (universe->tag_size == 2) { + putUShort((unsigned char *)vs->buf+vs->bufpos, + oc->option->code); + vs->bufpos += 2; + } else if (universe->tag_size == 4) { + putULong((unsigned char *)vs->buf+vs->bufpos, + oc->option->code); + vs->bufpos += 4; + } + if (universe->length_size == 1) { + vs->buf[vs->bufpos++] = ds.len; + } else if (universe->length_size == 2) { + putUShort((unsigned char *)vs->buf+vs->bufpos, + ds.len); + vs->bufpos += 2; + } else if (universe->length_size == 4) { + putULong((unsigned char *)vs->buf+vs->bufpos, + ds.len); + vs->bufpos += 4; + } + memcpy(vs->buf + vs->bufpos, ds.data, ds.len); + vs->bufpos += ds.len; + } else { + log_debug("No space for option %d in VSIO space %s.", + oc->option->code, universe->name); + } + data_string_forget(&ds, MDL); + } else { + log_error("Error evaluating option %d in VSIO space %s.", + oc->option->code, universe->name); + } +} + +/* + * Stores the options from the DHCPv6 universe into the buffer given. + * + * Required options are given as a 0-terminated list of option codes. + * Once those are added, the ORO is consulted. + */ + +int +store_options6(char *buf, int buflen, + struct option_state *opt_state, + struct packet *packet, + const int *required_opts, + struct data_string *oro) { + int i, j; + struct option_cache *oc; + struct option *o; + struct data_string ds; + int bufpos; + int oro_size; + u_int16_t code; + int in_required_opts; + int vsio_option_code; + int vsio_wanted; + struct vsio_state vs; + unsigned char *tmp; + + bufpos = 0; + vsio_wanted = 0; + + /* + * Find the option code for the VSIO universe. + */ + vsio_option_code = 0; + o = vsio_universe.enc_opt; + while (o != NULL) { + if (o->universe == &dhcpv6_universe) { + vsio_option_code = o->code; + break; + } + o = o->universe->enc_opt; + } + if (vsio_option_code == 0) { + log_fatal("No VSIO option code found."); + } + + if (required_opts != NULL) { + for (i=0; required_opts[i] != 0; i++) { + if (required_opts[i] == vsio_option_code) { + vsio_wanted = 1; + } + + oc = lookup_option(&dhcpv6_universe, + opt_state, required_opts[i]); + if (oc == NULL) { + continue; + } + memset(&ds, 0, sizeof(ds)); + for (; oc != NULL ; oc = oc->next) { + if (evaluate_option_cache(&ds, packet, NULL, + NULL, opt_state, + NULL, &global_scope, + oc, MDL)) { + if ((ds.len + 4) <= + (buflen - bufpos)) { + tmp = (unsigned char *)buf; + tmp += bufpos; + /* option tag */ + putUShort(tmp, + required_opts[i]); + /* option length */ + putUShort(tmp+2, ds.len); + /* option data */ + memcpy(tmp+4, ds.data, ds.len); + /* update position */ + bufpos += (4 + ds.len); + } else { + log_debug("No space for " + "option %d", + required_opts[i]); + } + data_string_forget(&ds, MDL); + } else { + log_error("Error evaluating option %d", + required_opts[i]); + } + } + } + } + + if (oro == NULL) { + oro_size = 0; + } else { + oro_size = oro->len / 2; + } + for (i=0; i<oro_size; i++) { + memcpy(&code, oro->data+(i*2), 2); + code = ntohs(code); + + /* + * See if we've already included this option because + * it is required. + */ + in_required_opts = 0; + if (required_opts != NULL) { + for (j=0; required_opts[j] != 0; j++) { + if (required_opts[j] == code) { + in_required_opts = 1; + break; + } + } + } + if (in_required_opts) { + continue; + } + + /* + * See if this is the VSIO option. + */ + if (code == vsio_option_code) { + vsio_wanted = 1; + } + + /* + * Not already added, find this option. + */ + oc = lookup_option(&dhcpv6_universe, opt_state, code); + memset(&ds, 0, sizeof(ds)); + for (; oc != NULL ; oc = oc->next) { + if (evaluate_option_cache(&ds, packet, NULL, NULL, + opt_state, NULL, + &global_scope, oc, MDL)) { + if ((ds.len + 4) <= (buflen - bufpos)) { + tmp = (unsigned char *)buf + bufpos; + /* option tag */ + putUShort(tmp, code); + /* option length */ + putUShort(tmp+2, ds.len); + /* option data */ + memcpy(tmp+4, ds.data, ds.len); + /* update position */ + bufpos += (4 + ds.len); + } else { + log_debug("No space for option %d", + code); + } + data_string_forget(&ds, MDL); + } else { + log_error("Error evaluating option %d", code); + } + } + } + + if (vsio_wanted) { + for (i=0; i < opt_state->universe_count; i++) { + if (opt_state->universes[i] != NULL) { + o = universes[i]->enc_opt; + if ((o != NULL) && + (o->universe == &vsio_universe)) { + /* + * Add the data from this VSIO option. + */ + vs.buf = buf; + vs.buflen = buflen; + vs.bufpos = bufpos+8; + option_space_foreach(packet, NULL, + NULL, + NULL, opt_state, + NULL, + universes[i], + (void *)&vs, + vsio_options); + + /* + * If there was actually data here, + * add the "header". + */ + if (vs.bufpos > bufpos+8) { + tmp = (unsigned char *)buf + + bufpos; + putUShort(tmp, + vsio_option_code); + putUShort(tmp+2, + vs.bufpos-bufpos-4); + putULong(tmp+4, o->code); + + bufpos = vs.bufpos; + } + } + } + } + } + + return bufpos; +} + +/* + * Store all the requested options into the requested buffer. + * XXX: ought to be static + */ +int +store_options(int *ocount, + unsigned char *buffer, unsigned index, unsigned buflen, + struct packet *packet, struct lease *lease, + struct client_state *client_state, + struct option_state *in_options, + struct option_state *cfg_options, + struct binding_scope **scope, + unsigned *priority_list, int priority_len, + unsigned first_cutoff, int second_cutoff, int terminate, + const char *vuname) +{ + int bufix = 0, six = 0, tix = 0; + int i; + int ix; + int tto; + int bufend, sbufend; + struct data_string od; + struct option_cache *oc; + struct option *option = NULL; + unsigned code; + + /* + * These arguments are relative to the start of the buffer, so + * reduce them by the current buffer index, and advance the + * buffer pointer to where we're going to start writing. + */ + buffer = &buffer[index]; + buflen -= index; + if (first_cutoff) + first_cutoff -= index; + if (second_cutoff) + second_cutoff -= index; + + /* Calculate the start and end of each section of the buffer */ + bufend = sbufend = buflen; + if (first_cutoff) { + if (first_cutoff >= buflen) + log_fatal("%s:%d:store_options: Invalid first cutoff.", MDL); + bufend = first_cutoff; + + if (second_cutoff) { + if (second_cutoff >= buflen) + log_fatal("%s:%d:store_options: Invalid second cutoff.", + MDL); + sbufend = second_cutoff; + } + } else if (second_cutoff) { + if (second_cutoff >= buflen) + log_fatal("%s:%d:store_options: Invalid second cutoff.", MDL); + bufend = second_cutoff; + } + + memset (&od, 0, sizeof od); + + /* Eliminate duplicate options from the parameter request list. + * Enforce RFC-mandated ordering of options that are present. + */ + for (i = 0; i < priority_len; i++) { + /* Eliminate duplicates. */ + tto = 0; + for (ix = i + 1; ix < priority_len + tto; ix++) { + if (tto) + priority_list [ix - tto] = + priority_list [ix]; + if (priority_list [i] == priority_list [ix]) { + tto++; + priority_len--; + } + } + + /* Enforce ordering of SUBNET_MASK options, according to + * RFC2132 Section 3.3: + * + * If both the subnet mask and the router option are + * specified in a DHCP reply, the subnet mask option MUST + * be first. + * + * This guidance does not specify what to do if the client + * PRL explicitly requests the options out of order, it is + * a general statement. + */ + if (priority_list[i] == DHO_SUBNET_MASK) { + for (ix = i - 1 ; ix >= 0 ; ix--) { + if (priority_list[ix] == DHO_ROUTERS) { + /* swap */ + priority_list[ix] = DHO_SUBNET_MASK; + priority_list[i] = DHO_ROUTERS; + break; + } + } + } + } + + /* Copy out the options in the order that they appear in the + priority list... */ + for (i = 0; i < priority_len; i++) { + /* Number of bytes left to store (some may already + have been stored by a previous pass). */ + unsigned length; + int optstart, soptstart, toptstart; + struct universe *u; + int have_encapsulation = 0; + struct data_string encapsulation; + int splitup; + + memset (&encapsulation, 0, sizeof encapsulation); + have_encapsulation = 0; + + if (option != NULL) + option_dereference(&option, MDL); + + /* Code for next option to try to store. */ + code = priority_list [i]; + + /* Look up the option in the site option space if the code + is above the cutoff, otherwise in the DHCP option space. */ + if (code >= cfg_options -> site_code_min) + u = universes [cfg_options -> site_universe]; + else + u = &dhcp_universe; + + oc = lookup_option (u, cfg_options, code); + + if (oc && oc->option) + option_reference(&option, oc->option, MDL); + else + option_code_hash_lookup(&option, u->code_hash, &code, 0, MDL); + + /* If it's a straight encapsulation, and the user supplied a + * value for the entire option, use that. Otherwise, search + * the encapsulated space. + * + * If it's a limited encapsulation with preceding data, and the + * user supplied values for the preceding bytes, search the + * encapsulated space. + */ + if ((option != NULL) && + (((oc == NULL) && (option->format[0] == 'E')) || + ((oc != NULL) && (option->format[0] == 'e')))) { + static char *s, *t; + struct option_cache *tmp; + struct data_string name; + + s = strchr (option->format, 'E'); + if (s) + t = strchr (++s, '.'); + if (s && t) { + memset (&name, 0, sizeof name); + + /* A zero-length universe name means the vendor + option space, if one is defined. */ + if (t == s) { + if (vendor_cfg_option) { + tmp = lookup_option (vendor_cfg_option -> universe, + cfg_options, + vendor_cfg_option -> code); + if (tmp) + /* No need to check the return as we check name.len below */ + (void) evaluate_option_cache (&name, packet, lease, + client_state, + in_options, + cfg_options, + scope, tmp, MDL); + } else if (vuname) { + name.data = (unsigned char *)s; + name.len = strlen (s); + } + } else { + name.data = (unsigned char *)s; + name.len = t - s; + } + + /* If we found a universe, and there are options configured + for that universe, try to encapsulate it. */ + if (name.len) { + have_encapsulation = + (option_space_encapsulate + (&encapsulation, packet, lease, client_state, + in_options, cfg_options, scope, &name)); + data_string_forget (&name, MDL); + } + } + } + + /* In order to avoid memory leaks, we have to get to here + with any option cache that we allocated in tmp not being + referenced by tmp, and whatever option cache is referenced + by oc being an actual reference. lookup_option doesn't + generate a reference (this needs to be fixed), so the + preceding goop ensures that if we *didn't* generate a new + option cache, oc still winds up holding an actual reference. */ + + /* If no data is available for this option, skip it. */ + if (!oc && !have_encapsulation) { + continue; + } + + /* Find the value of the option... */ + od.len = 0; + if (oc) { + /* No need to check the return as we check od.len below */ + (void) evaluate_option_cache (&od, packet, + lease, client_state, in_options, + cfg_options, scope, oc, MDL); + + /* If we have encapsulation for this option, and an oc + * lookup succeeded, but the evaluation failed, it is + * either because this is a complex atom (atoms before + * E on format list) and the top half of the option is + * not configured, or this is a simple encapsulated + * space and the evaluator is giving us a NULL. Prefer + * the evaluator's opinion over the subspace. + */ + if (!od.len) { + data_string_forget (&encapsulation, MDL); + data_string_forget (&od, MDL); + continue; + } + } + + /* We should now have a constant length for the option. */ + length = od.len; + if (have_encapsulation) { + length += encapsulation.len; + + /* od.len can be nonzero if we got here without an + * oc (cache lookup failed), but did have an encapsulated + * simple encapsulation space. + */ + if (!od.len) { + data_string_copy (&od, &encapsulation, MDL); + data_string_forget (&encapsulation, MDL); + } else { + struct buffer *bp = (struct buffer *)0; + if (!buffer_allocate (&bp, length, MDL)) { + option_cache_dereference (&oc, MDL); + data_string_forget (&od, MDL); + data_string_forget (&encapsulation, MDL); + continue; + } + memcpy (&bp -> data [0], od.data, od.len); + memcpy (&bp -> data [od.len], encapsulation.data, + encapsulation.len); + data_string_forget (&od, MDL); + data_string_forget (&encapsulation, MDL); + od.data = &bp -> data [0]; + buffer_reference (&od.buffer, bp, MDL); + buffer_dereference (&bp, MDL); + od.len = length; + od.terminated = 0; + } + } + + /* Do we add a NUL? */ + if (terminate && option && format_has_text(option->format)) { + length++; + tto = 1; + } else { + tto = 0; + } + + /* Try to store the option. */ + + /* If the option's length is more than 255, we must store it + in multiple hunks. Store 255-byte hunks first. However, + in any case, if the option data will cross a buffer + boundary, split it across that boundary. */ + + if (length > 255) + splitup = 1; + else + splitup = 0; + + ix = 0; + optstart = bufix; + soptstart = six; + toptstart = tix; + while (length) { + unsigned incr = length; + int *pix; + unsigned char *base; + + /* Try to fit it in the options buffer. */ + if (!splitup && + ((!six && !tix && (i == priority_len - 1) && + (bufix + 2 + length < bufend)) || + (bufix + 5 + length < bufend))) { + base = buffer; + pix = &bufix; + /* Try to fit it in the second buffer. */ + } else if (!splitup && first_cutoff && + (first_cutoff + six + 3 + length < sbufend)) { + base = &buffer[first_cutoff]; + pix = &six; + /* Try to fit it in the third buffer. */ + } else if (!splitup && second_cutoff && + (second_cutoff + tix + 3 + length < buflen)) { + base = &buffer[second_cutoff]; + pix = &tix; + /* Split the option up into the remaining space. */ + } else { + splitup = 1; + + /* Use any remaining options space. */ + if (bufix + 6 < bufend) { + incr = bufend - bufix - 5; + base = buffer; + pix = &bufix; + /* Use any remaining first_cutoff space. */ + } else if (first_cutoff && + (first_cutoff + six + 4 < sbufend)) { + incr = sbufend - (first_cutoff + six) - 3; + base = &buffer[first_cutoff]; + pix = &six; + /* Use any remaining second_cutoff space. */ + } else if (second_cutoff && + (second_cutoff + tix + 4 < buflen)) { + incr = buflen - (second_cutoff + tix) - 3; + base = &buffer[second_cutoff]; + pix = &tix; + /* Give up, roll back this option. */ + } else { + bufix = optstart; + six = soptstart; + tix = toptstart; + break; + } + } + + if (incr > length) + incr = length; + if (incr > 255) + incr = 255; + + /* Everything looks good - copy it in! */ + base [*pix] = code; + base [*pix + 1] = (unsigned char)incr; + if (tto && incr == length) { + if (incr > 1) + memcpy (base + *pix + 2, + od.data + ix, (unsigned)(incr - 1)); + base [*pix + 2 + incr - 1] = 0; + } else { + memcpy (base + *pix + 2, + od.data + ix, (unsigned)incr); + } + length -= incr; + ix += incr; + *pix += 2 + incr; + } + data_string_forget (&od, MDL); + } + + if (option != NULL) + option_dereference(&option, MDL); + + /* If we can overload, and we have, then PAD and END those spaces. */ + if (first_cutoff && six) { + if ((first_cutoff + six + 1) < sbufend) + memset (&buffer[first_cutoff + six + 1], DHO_PAD, + sbufend - (first_cutoff + six + 1)); + else if (first_cutoff + six >= sbufend) + log_fatal("Second buffer overflow in overloaded options."); + + buffer[first_cutoff + six] = DHO_END; + if (ocount != NULL) + *ocount |= 1; /* So that caller knows there's data there. */ + } + + if (second_cutoff && tix) { + if (second_cutoff + tix + 1 < buflen) { + memset (&buffer[second_cutoff + tix + 1], DHO_PAD, + buflen - (second_cutoff + tix + 1)); + } else if (second_cutoff + tix >= buflen) + log_fatal("Third buffer overflow in overloaded options."); + + buffer[second_cutoff + tix] = DHO_END; + if (ocount != NULL) + *ocount |= 2; /* So that caller knows there's data there. */ + } + + if ((six || tix) && (bufix + 3 > bufend)) + log_fatal("Not enough space for option overload option."); + + return bufix; +} + +/* Return true if the format string has a variable length text option + * ("t"), return false otherwise. + */ + +int +format_has_text(format) + const char *format; +{ + const char *p; + + p = format; + while (*p != '\0') { + switch (*p++) { + case 'd': + case 't': + return 1; + + /* These symbols are arbitrary, not fixed or + * determinable length...text options with them is + * invalid (whatever the case, they are never NULL + * terminated). + */ + case 'A': + case 'a': + case 'X': + case 'x': + case 'D': + return 0; + + case 'c': + /* 'c' only follows 'D' atoms, and indicates that + * compression may be used. If there was a 'D' + * atom already, we would have returned. So this + * is an error, but continue looking for 't' anyway. + */ + log_error("format_has_text(%s): 'c' atoms are illegal " + "except after 'D' atoms.", format); + break; + + /* 'E' is variable length, but not arbitrary...you + * can find its length if you can find an END option. + * N is (n)-byte in length but trails a name of a + * space defining the enumeration values. So treat + * both the same - valid, fixed-length fields. + */ + case 'E': + case 'N': + /* Consume the space name. */ + while ((*p != '\0') && (*p++ != '.')) + ; + break; + + default: + break; + } + } + + return 0; +} + +/* Determine the minimum length of a DHCP option prior to any variable + * or inconsistent length formats, according to its configured format + * variable (and possibly from supplied option cache contents for variable + * length format symbols). + */ + +int +format_min_length(format, oc) + const char *format; + struct option_cache *oc; +{ + const char *p, *name; + int min_len = 0; + int last_size = 0; + struct enumeration *espace; + + p = format; + while (*p != '\0') { + switch (*p++) { + case '6': /* IPv6 Address */ + min_len += 16; + last_size = 16; + break; + + case 'I': /* IPv4 Address */ + case 'l': /* int32_t */ + case 'L': /* uint32_t */ + case 'T': /* Lease Time, uint32_t equivalent */ + min_len += 4; + last_size = 4; + break; + + case 's': /* int16_t */ + case 'S': /* uint16_t */ + min_len += 2; + last_size = 2; + break; + + case 'N': /* Enumeration value. */ + /* Consume space name. */ + name = p; + p = strchr(p, '.'); + if (p == NULL) + log_fatal("Corrupt format: %s", format); + + espace = find_enumeration(name, p - name); + if (espace == NULL) { + log_error("Unknown enumeration: %s", format); + /* Max is safest value to return. */ + return INT_MAX; + } + + min_len += espace->width; + last_size = espace->width; + p++; + + break; + + case 'b': /* int8_t */ + case 'B': /* uint8_t */ + case 'F': /* Flag that is always true. */ + case 'f': /* Flag */ + min_len++; + last_size = 1; + break; + + case 'o': /* Last argument is optional. */ + min_len -= last_size; + + /* XXX: It MAY be possible to sense the end of an + * encapsulated space, but right now this is too + * hard to support. Return a safe value. + */ + case 'e': /* Encapsulation hint (there is an 'E' later). */ + case 'E': /* Encapsulated options. */ + return min_len; + + case 'd': /* "Domain name" */ + case 'D': /* "rfc1035 formatted names" */ + case 't': /* "ASCII Text" */ + case 'X': /* "ASCII or Hex Conditional */ + case 'x': /* "Hex" */ + case 'A': /* Array of all that precedes. */ + case 'a': /* Array of preceding symbol. */ + case 'Z': /* nothing. */ + return min_len; + + case 'c': /* Compress flag for D atom. */ + log_error("format_min_length(%s): 'c' atom is illegal " + "except after 'D' atom.", format); + return INT_MAX; + + default: + /* No safe value is known. */ + log_error("format_min_length(%s): No safe value " + "for unknown format symbols.", format); + return INT_MAX; + } + } + + return min_len; +} + + +/* Format the specified option so that a human can easily read it. */ + +const char *pretty_print_option (option, data, len, emit_commas, emit_quotes) + struct option *option; + const unsigned char *data; + unsigned len; + int emit_commas; + int emit_quotes; +{ + static char optbuf [32768]; /* XXX */ + static char *endbuf = &optbuf[sizeof(optbuf)]; + int hunksize = 0; + int opthunk = 0; + int hunkinc = 0; + int numhunk = -1; + int numelem = 0; + int count; + int i, j, k, l; + char fmtbuf[32] = ""; + struct iaddr iaddr; + struct enumeration *enumbuf[32]; /* MUST be same as fmtbuf */ + char *op = optbuf; + const unsigned char *dp = data; + char comma; + unsigned long tval; + isc_boolean_t a_array = ISC_FALSE; + int len_used; + + if (emit_commas) + comma = ','; + else + comma = ' '; + + memset (enumbuf, 0, sizeof enumbuf); + + /* Figure out the size of the data. */ + for (l = i = 0; option -> format [i]; i++, l++) { + if (l >= sizeof(fmtbuf) - 1) + log_fatal("Bounds failure on internal buffer at " + "%s:%d", MDL); + + if (!numhunk) { + log_error ("%s: Extra codes in format string: %s", + option -> name, + &(option -> format [i])); + break; + } + numelem++; + fmtbuf [l] = option -> format [i]; + switch (option -> format [i]) { + case 'a': + a_array = ISC_TRUE; + /* Fall through */ + case 'A': + --numelem; + fmtbuf [l] = 0; + numhunk = 0; + break; + case 'E': + /* Skip the universe name. */ + while (option -> format [i] && + option -> format [i] != '.') + i++; + /* Fall Through! */ + case 'X': + for (k = 0; k < len; k++) { + if (!isascii (data [k]) || + !isprint (data [k])) + break; + } + /* If we found no bogus characters, or the bogus + character we found is a trailing NUL, it's + okay to print this option as text. */ + if (k == len || (k + 1 == len && data [k] == 0)) { + fmtbuf [l] = 't'; + numhunk = -2; + } else { + fmtbuf [l] = 'x'; + hunksize++; + comma = ':'; + numhunk = 0; + a_array = ISC_TRUE; + hunkinc = 1; + } + fmtbuf [l + 1] = 0; + break; + case 'c': + /* The 'c' atom is a 'D' modifier only. */ + log_error("'c' atom not following D atom in format " + "string: %s", option->format); + break; + case 'D': + /* + * Skip the 'c' atom, if present. It does not affect + * how we convert wire->text format (if compression is + * present either way, we still process it). + */ + if (option->format[i+1] == 'c') + i++; + fmtbuf[l + 1] = 0; + numhunk = -2; + break; + case 'd': + fmtbuf[l] = 't'; + /* Fall Through ! */ + case 't': + fmtbuf[l + 1] = 0; + numhunk = -2; + break; + case 'N': + k = i; + while (option -> format [i] && + option -> format [i] != '.') + i++; + enumbuf [l] = + find_enumeration (&option -> format [k] + 1, + i - k - 1); + if (enumbuf[l] == NULL) { + hunksize += 1; + hunkinc = 1; + } else { + hunksize += enumbuf[l]->width; + hunkinc = enumbuf[l]->width; + } + break; + case '6': + hunksize += 16; + hunkinc = 16; + break; + case 'I': + case 'l': + case 'L': + case 'T': + hunksize += 4; + hunkinc = 4; + break; + case 's': + case 'S': + hunksize += 2; + hunkinc = 2; + break; + case 'b': + case 'B': + case 'f': + case 'F': + hunksize++; + hunkinc = 1; + break; + case 'e': + case 'Z': + break; + case 'o': + opthunk += hunkinc; + break; + default: + log_error ("%s: garbage in format string: %s", + option -> name, + &(option -> format [i])); + break; + } + } + + /* Check for too few bytes... */ + if (hunksize - opthunk > len) { + log_error ("%s: expecting at least %d bytes; got %d", + option -> name, + hunksize, len); + return "<error>"; + } + /* Check for too many bytes... */ + if (numhunk == -1 && hunksize < len) + log_error ("%s: %d extra bytes", + option -> name, + len - hunksize); + + /* If this is an array, compute its size. */ + if (numhunk == 0) { + if (a_array == ISC_TRUE) { + /* + * It is an 'a' type array - we repeat the + * last format type. A binary string for 'X' + * is also like this. hunkinc is the size + * of the last format type and we add 1 to + * cover the entire first record. + */ + + /* If format string had no valid entries prior to + * 'a' hunkinc will be 0. Ex: "a", "oa", "aA" */ + if (hunkinc == 0) { + log_error ("%s: invalid 'a' format: %s", + option->name, option->format); + return ("<error>"); + } + + numhunk = ((len - hunksize) / hunkinc) + 1; + len_used = hunksize + ((numhunk - 1) * hunkinc); + } else { + /* + * It is an 'A' type array - we repeat the + * entire record + */ + + /* If format string had no valid entries prior to + * 'A' hunksize will be 0. Ex: "A", "oA", "foA" */ + if (hunksize == 0) { + log_error ("%s: invalid 'A' format: %s", + option->name, option->format); + return ("<error>"); + } + + numhunk = len / hunksize; + len_used = numhunk * hunksize; + } + + /* See if we got an exact number of hunks. */ + if (len_used < len) { + log_error ("%s: %d extra bytes at end of array\n", + option -> name, + len - len_used); + } + } + + + /* A one-hunk array prints the same as a single hunk. */ + if (numhunk < 0) + numhunk = 1; + + /* Cycle through the array (or hunk) printing the data. */ + for (i = 0; i < numhunk; i++) { + if ((a_array == ISC_TRUE) && (i != 0) && (numelem > 0)) { + /* + * For 'a' type of arrays we repeat + * only the last format character + * We should never hit the case of numelem == 0 + * but let's include the check to be safe. + */ + j = numelem - 1; + } else { + /* + * for other types of arrays or the first + * time through for 'a' types, we go through + * the entire set of format characters. + */ + j = 0; + } + + for (; j < numelem; j++) { + switch (fmtbuf [j]) { + case 't': + /* endbuf-1 leaves room for NULL. */ + k = pretty_text(&op, endbuf - 1, &dp, + data + len, emit_quotes); + if (k == -1) { + log_error("Error printing text."); + break; + } + *op = 0; + break; + case 'D': /* RFC1035 format name list */ + for( ; dp < (data + len) ; dp += k) { + unsigned char nbuff[NS_MAXCDNAME]; + const unsigned char *nbp, *nend; + + nend = &nbuff[sizeof(nbuff)]; + + /* If this is for ISC DHCP consumption + * (emit_quotes), lay it out as a list + * of STRING tokens. Otherwise, it is + * a space-separated list of DNS- + * escaped names as /etc/resolv.conf + * might digest. + */ + if (dp != data) { + if (op + 2 > endbuf) + break; + + if (emit_quotes) + *op++ = ','; + *op++ = ' '; + } + + /* XXX: if fmtbuf[j+1] != 'c', we + * should warn if the data was + * compressed anyway. + */ + k = MRns_name_unpack(data, + data + len, + dp, nbuff, + sizeof(nbuff)); + + if (k == -1) { + log_error("Invalid domain " + "list."); + break; + } + + /* If emit_quotes, then use ISC DHCP + * escapes. Otherwise, rely only on + * ns_name_ntop(). + */ + if (emit_quotes) { + nbp = nbuff; + pretty_domain(&op, endbuf-1, + &nbp, nend); + } else { + /* ns_name_ntop() includes + * a trailing NUL in its + * count. + */ + count = MRns_name_ntop( + nbuff, op, + (endbuf-op)-1); + + if (count <= 0) { + log_error("Invalid " + "domain name."); + break; + } + + /* Consume all but the trailing + * NUL. + */ + op += count - 1; + + /* Replace the trailing NUL + * with the implicit root + * (in the unlikely event the + * domain name /is/ the root). + */ + *op++ = '.'; + } + } + *op = '\0'; + break; + /* pretty-printing an array of enums is + going to get ugly. */ + case 'N': + if (!enumbuf [j]) { + tval = *dp++; + goto enum_as_num; + } + + switch (enumbuf[j]->width) { + case 1: + tval = getUChar(dp); + break; + + case 2: + tval = getUShort(dp); + break; + + case 4: + tval = getULong(dp); + break; + + default: + log_fatal("Impossible case at %s:%d.", + MDL); + return "<double impossible condition>"; + } + + for (i = 0; ;i++) { + if (!enumbuf [j] -> values [i].name) + goto enum_as_num; + if (enumbuf [j] -> values [i].value == + tval) + break; + } + strcpy (op, enumbuf [j] -> values [i].name); + dp += enumbuf[j]->width; + break; + + enum_as_num: + sprintf(op, "%lu", tval); + break; + + case 'I': + iaddr.len = 4; + memcpy(iaddr.iabuf, dp, 4); + strcpy(op, piaddr(iaddr)); + dp += 4; + break; + case '6': + iaddr.len = 16; + memcpy(iaddr.iabuf, dp, 16); + strcpy(op, piaddr(iaddr)); + dp += 16; + break; + case 'l': + sprintf (op, "%ld", (long)getLong (dp)); + dp += 4; + break; + case 'T': + tval = getULong (dp); + if (tval == -1) + sprintf (op, "%s", "infinite"); + else + sprintf(op, "%lu", tval); + break; + case 'L': + sprintf(op, "%lu", + (unsigned long)getULong(dp)); + dp += 4; + break; + case 's': + sprintf (op, "%d", (int)getShort (dp)); + dp += 2; + break; + case 'S': + sprintf(op, "%u", (unsigned)getUShort(dp)); + dp += 2; + break; + case 'b': + sprintf (op, "%d", *(const char *)dp++); + break; + case 'B': + sprintf (op, "%d", *dp++); + break; + case 'X': + case 'x': + sprintf (op, "%x", *dp++); + break; + case 'f': + strcpy (op, *dp++ ? "true" : "false"); + break; + case 'F': + strcpy (op, "true"); + break; + case 'e': + case 'Z': + *op = '\0'; + break; + default: + log_error ("Unexpected format code %c", + fmtbuf [j]); + } + op += strlen (op); + if (dp == data + len) + break; + if (j + 1 < numelem && comma != ':') + *op++ = ' '; + } + if (i + 1 < numhunk) { + *op++ = comma; + } + if (dp == data + len) + break; + } + return optbuf; +} + +int get_option (result, universe, packet, lease, client_state, + in_options, cfg_options, options, scope, code, file, line) + struct data_string *result; + struct universe *universe; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct option_state *options; + struct binding_scope **scope; + unsigned code; + const char *file; + int line; +{ + struct option_cache *oc; + + if (!universe -> lookup_func) + return 0; + oc = ((*universe -> lookup_func) (universe, options, code)); + if (!oc) + return 0; + if (!evaluate_option_cache (result, packet, lease, client_state, + in_options, cfg_options, scope, oc, + file, line)) + return 0; + return 1; +} + +void set_option (universe, options, option, op) + struct universe *universe; + struct option_state *options; + struct option_cache *option; + enum statement_op op; +{ + struct option_cache *oc, *noc; + + switch (op) { + case if_statement: + case add_statement: + case eval_statement: + case break_statement: + default: + log_error ("bogus statement type in set_option."); + break; + + case default_option_statement: + oc = lookup_option (universe, options, + option -> option -> code); + if (oc) + break; + save_option (universe, options, option); + break; + + case supersede_option_statement: + case send_option_statement: + /* Install the option, replacing any existing version. */ + save_option (universe, options, option); + break; + + case append_option_statement: + case prepend_option_statement: + oc = lookup_option (universe, options, + option -> option -> code); + if (!oc) { + save_option (universe, options, option); + break; + } + /* If it's not an expression, make it into one. */ + if (!oc -> expression && oc -> data.len) { + if (!expression_allocate (&oc -> expression, MDL)) { + log_error ("Can't allocate const expression."); + break; + } + oc -> expression -> op = expr_const_data; + data_string_copy + (&oc -> expression -> data.const_data, + &oc -> data, MDL); + data_string_forget (&oc -> data, MDL); + } + noc = (struct option_cache *)0; + if (!option_cache_allocate (&noc, MDL)) + break; + if (op == append_option_statement) { + if (!make_concat (&noc -> expression, + oc -> expression, + option -> expression)) { + option_cache_dereference (&noc, MDL); + break; + } + } else { + if (!make_concat (&noc -> expression, + option -> expression, + oc -> expression)) { + option_cache_dereference (&noc, MDL); + break; + } + } + + /* If we are trying to combine compressed domain-lists then + * we need to change the expression opcode. The lists must + * be decompressed, combined, and then recompressed to work + * correctly. You cannot simply add two compressed lists + * together. */ + switch (((memcmp(option->option->format, "Dc", 2) == 0) + + (memcmp(oc->option->format, "Dc", 2) == 0))) { + case 1: + /* Only one is "Dc", this won't work + * Not sure if you can make this occur, but just + * in case. */ + log_error ("Both options must be Dc format"); + option_cache_dereference (&noc, MDL); + return; + case 2: + /* Both are "Dc", change the code */ + noc->expression->op = expr_concat_dclist; + break; + default: + /* Neither are "Dc", so as you were */ + break; + } + + option_reference(&(noc->option), oc->option, MDL); + save_option (universe, options, noc); + option_cache_dereference (&noc, MDL); + break; + } +} + +struct option_cache *lookup_option (universe, options, code) + struct universe *universe; + struct option_state *options; + unsigned code; +{ + if (!options) + return (struct option_cache *)0; + if (universe -> lookup_func) + return (*universe -> lookup_func) (universe, options, code); + else + log_error ("can't look up options in %s space.", + universe -> name); + return (struct option_cache *)0; +} + +struct option_cache *lookup_hashed_option (universe, options, code) + struct universe *universe; + struct option_state *options; + unsigned code; +{ + int hashix; + pair bptr; + pair *hash; + + /* Make sure there's a hash table. */ + if (universe -> index >= options -> universe_count || + !(options -> universes [universe -> index])) + return (struct option_cache *)0; + + hash = options -> universes [universe -> index]; + + hashix = compute_option_hash (code); + for (bptr = hash [hashix]; bptr; bptr = bptr -> cdr) { + if (((struct option_cache *)(bptr -> car)) -> option -> code == + code) + return (struct option_cache *)(bptr -> car); + } + return (struct option_cache *)0; +} + +/* Save a specified buffer into an option cache. */ +int +save_option_buffer(struct universe *universe, struct option_state *options, + struct buffer *bp, unsigned char *buffer, unsigned length, + unsigned code, int terminatep) +{ + struct option_cache *op = NULL; + int status = 1; + + status = prepare_option_buffer(universe, bp, buffer, length, code, + terminatep, &op); + + if (status == 0) + goto cleanup; + + save_option(universe, options, op); + + cleanup: + if (op != NULL) + option_cache_dereference(&op, MDL); + + return status; +} + +/* Append a specified buffer onto the tail of an option cache. */ +int +append_option_buffer(struct universe *universe, struct option_state *options, + struct buffer *bp, unsigned char *buffer, unsigned length, + unsigned code, int terminatep) +{ + struct option_cache *op = NULL; + int status = 1; + + status = prepare_option_buffer(universe, bp, buffer, length, code, + terminatep, &op); + + if (status == 0) + goto cleanup; + + also_save_option(universe, options, op); + + cleanup: + if (op != NULL) + option_cache_dereference(&op, MDL); + + return status; +} + +/* Create/copy a buffer into a new option cache. */ +static int +prepare_option_buffer(struct universe *universe, struct buffer *bp, + unsigned char *buffer, unsigned length, unsigned code, + int terminatep, struct option_cache **opp) +{ + struct buffer *lbp = NULL; + struct option *option = NULL; + struct option_cache *op; + int status = 1; + + /* Code sizes of 8, 16, and 32 bits are allowed. */ + switch(universe->tag_size) { + case 1: + if (code > 0xff) + return 0; + break; + case 2: + if (code > 0xffff) + return 0; + break; + case 4: + if (code > 0xffffffff) + return 0; + break; + + default: + log_fatal("Inconsistent universe tag size at %s:%d.", MDL); + } + + option_code_hash_lookup(&option, universe->code_hash, &code, 0, MDL); + + /* If we created an option structure for each option a client + * supplied, it's possible we may create > 2^32 option structures. + * That's not feasible. So by failing to enter these option + * structures into the code and name hash tables, references will + * never be more than 1 - when the option cache is destroyed, this + * will be cleaned up. + */ + if (!option) { + char nbuf[sizeof("unknown-4294967295")]; + + sprintf(nbuf, "unknown-%u", code); + + option = new_option(nbuf, MDL); + + if (!option) + return 0; + + option->format = default_option_format; + option->universe = universe; + option->code = code; + + /* new_option() doesn't set references, pretend. */ + option->refcnt = 1; + } + + if (!option_cache_allocate (opp, MDL)) { + log_error("No memory for option code %s.%s.", + universe->name, option->name); + status = 0; + goto cleanup; + } + + /* Pointer rather than double pointer makes for less parens. */ + op = *opp; + + option_reference(&op->option, option, MDL); + + /* If we weren't passed a buffer in which the data are saved and + refcounted, allocate one now. */ + if (!bp) { + if (!buffer_allocate (&lbp, length + terminatep, MDL)) { + log_error ("no memory for option buffer."); + + status = 0; + goto cleanup; + } + memcpy (lbp -> data, buffer, length + terminatep); + bp = lbp; + buffer = &bp -> data [0]; /* Refer to saved buffer. */ + } + + /* Reference buffer copy to option cache. */ + op -> data.buffer = (struct buffer *)0; + buffer_reference (&op -> data.buffer, bp, MDL); + + /* Point option cache into buffer. */ + op -> data.data = buffer; + op -> data.len = length; + + if (terminatep) { + /* NUL terminate (we can get away with this because we (or + the caller!) allocated one more than the buffer size, and + because the byte following the end of an option is always + the code of the next option, which the caller is getting + out of the *original* buffer. */ + buffer [length] = 0; + op -> data.terminated = 1; + } else + op -> data.terminated = 0; + + /* If this option is ultimately a text option, null determinate to + * comply with RFC2132 section 2. Mark a flag so this can be sensed + * later to echo NULLs back to clients that supplied them (they + * probably expect them). + */ + if (format_has_text(option->format)) { + int min_len = format_min_length(option->format, op); + + while ((op->data.len > min_len) && + (op->data.data[op->data.len-1] == '\0')) { + op->data.len--; + op->flags |= OPTION_HAD_NULLS; + } + } + + /* And let go of our references. */ + cleanup: + if (lbp != NULL) + buffer_dereference(&lbp, MDL); + option_dereference(&option, MDL); + + return status; +} + +static void +count_options(struct option_cache *dummy_oc, + struct packet *dummy_packet, + struct lease *dummy_lease, + struct client_state *dummy_client_state, + struct option_state *dummy_opt_state, + struct option_state *opt_state, + struct binding_scope **dummy_binding_scope, + struct universe *dummy_universe, + void *void_accumulator) { + int *accumulator = (int *)void_accumulator; + + *accumulator += 1; +} + +static void +collect_oro(struct option_cache *oc, + struct packet *dummy_packet, + struct lease *dummy_lease, + struct client_state *dummy_client_state, + struct option_state *dummy_opt_state, + struct option_state *opt_state, + struct binding_scope **dummy_binding_scope, + struct universe *dummy_universe, + void *void_oro) { + struct data_string *oro = (struct data_string *)void_oro; + + putUShort(oro->buffer->data + oro->len, oc->option->code); + oro->len += 2; +} + +/* build_server_oro() is presently unusued, but may be used at a future date + * with support for Reconfigure messages (as a hint to the client about new + * option value contents). + */ +void +build_server_oro(struct data_string *server_oro, + struct option_state *options, + const char *file, int line) { + int num_opts; + int i; + struct option *o; + + /* + * Count the number of options, so we can allocate enough memory. + * We want to mention sub-options too, so check all universes. + */ + num_opts = 0; + option_space_foreach(NULL, NULL, NULL, NULL, options, + NULL, &dhcpv6_universe, (void *)&num_opts, + count_options); + for (i=0; i < options->universe_count; i++) { + if (options->universes[i] != NULL) { + o = universes[i]->enc_opt; + while (o != NULL) { + if (o->universe == &dhcpv6_universe) { + num_opts++; + break; + } + o = o->universe->enc_opt; + } + } + } + + /* + * Allocate space. + */ + memset(server_oro, 0, sizeof(*server_oro)); + if (!buffer_allocate(&server_oro->buffer, num_opts * 2, MDL)) { + log_fatal("no memory to build server ORO"); + } + server_oro->data = server_oro->buffer->data; + + /* + * Copy the data in. + * We want to mention sub-options too, so check all universes. + */ + server_oro->len = 0; /* gets set in collect_oro */ + option_space_foreach(NULL, NULL, NULL, NULL, options, + NULL, &dhcpv6_universe, (void *)server_oro, + collect_oro); + for (i=0; i < options->universe_count; i++) { + if (options->universes[i] != NULL) { + o = universes[i]->enc_opt; + while (o != NULL) { + if (o->universe == &dhcpv6_universe) { + unsigned char *tmp; + tmp = server_oro->buffer->data; + putUShort(tmp + server_oro->len, + o->code); + server_oro->len += 2; + break; + } + o = o->universe->enc_opt; + } + } + } +} + +/* Wrapper function to put an option cache into an option state. */ +void +save_option(struct universe *universe, struct option_state *options, + struct option_cache *oc) +{ + if (universe->save_func) + (*universe->save_func)(universe, options, oc, ISC_FALSE); + else + log_error("can't store options in %s space.", universe->name); +} + +/* Wrapper function to append an option cache into an option state's list. */ +void +also_save_option(struct universe *universe, struct option_state *options, + struct option_cache *oc) +{ + if (universe->save_func) + (*universe->save_func)(universe, options, oc, ISC_TRUE); + else + log_error("can't store options in %s space.", universe->name); +} + +void +save_hashed_option(struct universe *universe, struct option_state *options, + struct option_cache *oc, isc_boolean_t appendp) +{ + int hashix; + pair bptr; + pair *hash = options -> universes [universe -> index]; + struct option_cache **ocloc; + + if (oc -> refcnt == 0) + abort (); + + /* Compute the hash. */ + hashix = compute_option_hash (oc -> option -> code); + + /* If there's no hash table, make one. */ + if (!hash) { + hash = (pair *)dmalloc (OPTION_HASH_SIZE * sizeof *hash, MDL); + if (!hash) { + log_error ("no memory to store %s.%s", + universe -> name, oc -> option -> name); + return; + } + memset (hash, 0, OPTION_HASH_SIZE * sizeof *hash); + options -> universes [universe -> index] = (void *)hash; + } else { + /* Try to find an existing option matching the new one. */ + for (bptr = hash [hashix]; bptr; bptr = bptr -> cdr) { + if (((struct option_cache *) + (bptr -> car)) -> option -> code == + oc -> option -> code) + break; + } + + /* Deal with collisions on the hash list. */ + if (bptr) { + ocloc = (struct option_cache **)&bptr->car; + + /* + * If appendp is set, append it onto the tail of the + * ->next list. If it is not set, rotate it into + * position at the head of the list. + */ + if (appendp) { + do { + ocloc = &(*ocloc)->next; + } while (*ocloc != NULL); + } else { + option_cache_dereference(ocloc, MDL); + } + + option_cache_reference(ocloc, oc, MDL); + return; + } + } + + /* Otherwise, just put the new one at the head of the list. */ + bptr = new_pair (MDL); + if (!bptr) { + log_error ("No memory for option_cache reference."); + return; + } + bptr -> cdr = hash [hashix]; + bptr -> car = 0; + option_cache_reference ((struct option_cache **)&bptr -> car, oc, MDL); + hash [hashix] = bptr; +} + +void delete_option (universe, options, code) + struct universe *universe; + struct option_state *options; + int code; +{ + if (universe -> delete_func) + (*universe -> delete_func) (universe, options, code); + else + log_error ("can't delete options from %s space.", + universe -> name); +} + +void delete_hashed_option (universe, options, code) + struct universe *universe; + struct option_state *options; + int code; +{ + int hashix; + pair bptr, prev = (pair)0; + pair *hash = options -> universes [universe -> index]; + + /* There may not be any options in this space. */ + if (!hash) + return; + + /* Try to find an existing option matching the new one. */ + hashix = compute_option_hash (code); + for (bptr = hash [hashix]; bptr; bptr = bptr -> cdr) { + if (((struct option_cache *)(bptr -> car)) -> option -> code + == code) + break; + prev = bptr; + } + /* If we found one, wipe it out... */ + if (bptr) { + if (prev) + prev -> cdr = bptr -> cdr; + else + hash [hashix] = bptr -> cdr; + option_cache_dereference + ((struct option_cache **)(&bptr -> car), MDL); + free_pair (bptr, MDL); + } +} + +extern struct option_cache *free_option_caches; /* XXX */ + +int option_cache_dereference (ptr, file, line) + struct option_cache **ptr; + const char *file; + int line; +{ + if (!ptr || !*ptr) { + log_error ("Null pointer in option_cache_dereference: %s(%d)", + file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + (*ptr) -> refcnt--; + rc_register (file, line, ptr, *ptr, (*ptr) -> refcnt, 1, RC_MISC); + if (!(*ptr) -> refcnt) { + if ((*ptr) -> data.buffer) + data_string_forget (&(*ptr) -> data, file, line); + if ((*ptr)->option) + option_dereference(&(*ptr)->option, MDL); + if ((*ptr) -> expression) + expression_dereference (&(*ptr) -> expression, + file, line); + if ((*ptr) -> next) + option_cache_dereference (&((*ptr) -> next), + file, line); + /* Put it back on the free list... */ + (*ptr) -> expression = (struct expression *)free_option_caches; + free_option_caches = *ptr; + dmalloc_reuse (free_option_caches, (char *)0, 0, 0); + } + if ((*ptr) -> refcnt < 0) { + log_error ("%s(%d): negative refcnt!", file, line); +#if defined (DEBUG_RC_HISTORY) + dump_rc_history (*ptr); +#endif +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct option_cache *)0; + return 0; +#endif + } + *ptr = (struct option_cache *)0; + return 1; + +} + +int hashed_option_state_dereference (universe, state, file, line) + struct universe *universe; + struct option_state *state; + const char *file; + int line; +{ + pair *heads; + pair cp, next; + int i; + + /* Get the pointer to the array of hash table bucket heads. */ + heads = (pair *)(state -> universes [universe -> index]); + if (!heads) + return 0; + + /* For each non-null head, loop through all the buckets dereferencing + the attached option cache structures and freeing the buckets. */ + for (i = 0; i < OPTION_HASH_SIZE; i++) { + for (cp = heads [i]; cp; cp = next) { + next = cp -> cdr; + option_cache_dereference + ((struct option_cache **)&cp -> car, + file, line); + free_pair (cp, file, line); + } + } + + dfree (heads, file, line); + state -> universes [universe -> index] = (void *)0; + return 1; +} + +/* The 'data_string' primitive doesn't have an appension mechanism. + * This function must then append a new option onto an existing buffer + * by first duplicating the original buffer and appending the desired + * values, followed by coping the new value into place. + */ +int +append_option(struct data_string *dst, struct universe *universe, + struct option *option, struct data_string *src) +{ + struct data_string tmp; + + if (src->len == 0 && option->format[0] != 'Z') + return 0; + + memset(&tmp, 0, sizeof(tmp)); + + /* Allocate a buffer to hold existing data, the current option's + * tag and length, and the option's content. + */ + if (!buffer_allocate(&tmp.buffer, + (dst->len + universe->length_size + + universe->tag_size + src->len), MDL)) { + /* XXX: This kills all options presently stored in the + * destination buffer. This is the way the original code + * worked, and assumes an 'all or nothing' approach to + * eg encapsulated option spaces. It may or may not be + * desirable. + */ + data_string_forget(dst, MDL); + return 0; + } + tmp.data = tmp.buffer->data; + + /* Copy the existing data off the destination. */ + if (dst->len != 0) + memcpy(tmp.buffer->data, dst->data, dst->len); + tmp.len = dst->len; + + /* Place the new option tag and length. */ + (*universe->store_tag)(tmp.buffer->data + tmp.len, option->code); + tmp.len += universe->tag_size; + (*universe->store_length)(tmp.buffer->data + tmp.len, src->len); + tmp.len += universe->length_size; + + /* Copy the option contents onto the end. */ + memcpy(tmp.buffer->data + tmp.len, src->data, src->len); + tmp.len += src->len; + + /* Play the shell game. */ + data_string_forget(dst, MDL); + data_string_copy(dst, &tmp, MDL); + data_string_forget(&tmp, MDL); + return 1; +} + +int +store_option(struct data_string *result, struct universe *universe, + struct packet *packet, struct lease *lease, + struct client_state *client_state, + struct option_state *in_options, struct option_state *cfg_options, + struct binding_scope **scope, struct option_cache *oc) +{ + struct data_string tmp; + struct universe *subu=NULL; + int status; + char *start, *end; + + memset(&tmp, 0, sizeof(tmp)); + + if (evaluate_option_cache(&tmp, packet, lease, client_state, + in_options, cfg_options, scope, oc, MDL)) { + /* If the option is an extended 'e'ncapsulation (not a + * direct 'E'ncapsulation), append the encapsulated space + * onto the currently prepared value. + */ + do { + if (oc->option->format && + oc->option->format[0] == 'e') { + /* Skip forward to the universe name. */ + start = strchr(oc->option->format, 'E'); + if (start == NULL) + break; + + /* Locate the name-terminating '.'. */ + end = strchr(++start, '.'); + + /* A zero-length name is not allowed in + * these kinds of encapsulations. + */ + if (end == NULL || start == end) + break; + + universe_hash_lookup(&subu, universe_hash, + start, end - start, MDL); + + if (subu == NULL) { + log_error("store_option: option %d " + "refers to unknown " + "option space '%.*s'.", + oc->option->code, + (int)(end - start), start); + break; + } + + /* Append encapsulations, if any. We + * already have the prepended values, so + * we send those even if there are no + * encapsulated options (and ->encapsulate() + * returns zero). + */ + subu->encapsulate(&tmp, packet, lease, + client_state, in_options, + cfg_options, scope, subu); + subu = NULL; + } + } while (ISC_FALSE); + + status = append_option(result, universe, oc->option, &tmp); + data_string_forget(&tmp, MDL); + + return status; + } + + return 0; +} + +int option_space_encapsulate (result, packet, lease, client_state, + in_options, cfg_options, scope, name) + struct data_string *result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct data_string *name; +{ + struct universe *u = NULL; + int status = 0; + + universe_hash_lookup(&u, universe_hash, + (const char *)name->data, name->len, MDL); + if (u == NULL) { + log_error("option_space_encapsulate: option space '%.*s' does " + "not exist, but is configured.", + (int)name->len, name->data); + return status; + } + + if (u->encapsulate != NULL) { + if (u->encapsulate(result, packet, lease, client_state, + in_options, cfg_options, scope, u)) + status = 1; + } else + log_error("encapsulation requested for '%s' with no support.", + name->data); + + return status; +} + +/* Attempt to store any 'E'ncapsulated options that have not yet been + * placed on the option buffer by the above (configuring a value in + * the space over-rides any values in the child universe). + * + * Note that there are far fewer universes than there will ever be + * options in any universe. So it is faster to traverse the + * configured universes, checking if each is encapsulated in the + * current universe, and if so attempting to do so. + * + * For each configured universe for this configuration option space, + * which is encapsulated within the current universe, can not be found + * by the lookup function (the universe-specific encapsulation + * functions would already have stored such a value), and encapsulates + * at least one option, append it. + */ +static int +search_subencapsulation(struct data_string *result, struct packet *packet, + struct lease *lease, struct client_state *client_state, + struct option_state *in_options, + struct option_state *cfg_options, + struct binding_scope **scope, + struct universe *universe) +{ + struct data_string sub; + struct universe *subu; + int i, status = 0; + + memset(&sub, 0, sizeof(sub)); + for (i = 0 ; i < cfg_options->universe_count ; i++) { + subu = universes[i]; + + if (subu == NULL) + log_fatal("Impossible condition at %s:%d.", MDL); + + if (subu->enc_opt != NULL && + subu->enc_opt->universe == universe && + subu->enc_opt->format != NULL && + subu->enc_opt->format[0] == 'E' && + lookup_option(universe, cfg_options, + subu->enc_opt->code) == NULL && + subu->encapsulate(&sub, packet, lease, client_state, + in_options, cfg_options, + scope, subu)) { + if (append_option(result, universe, + subu->enc_opt, &sub)) + status = 1; + + data_string_forget(&sub, MDL); + } + } + + return status; +} + +int hashed_option_space_encapsulate (result, packet, lease, client_state, + in_options, cfg_options, scope, universe) + struct data_string *result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct universe *universe; +{ + pair p, *hash; + int status; + int i; + + if (universe -> index >= cfg_options -> universe_count) + return 0; + + hash = cfg_options -> universes [universe -> index]; + if (!hash) + return 0; + + /* For each hash bucket, and each configured option cache within + * that bucket, append the option onto the buffer in encapsulated + * format appropriate to the universe. + */ + status = 0; + for (i = 0; i < OPTION_HASH_SIZE; i++) { + for (p = hash [i]; p; p = p -> cdr) { + if (store_option(result, universe, packet, lease, + client_state, in_options, cfg_options, + scope, (struct option_cache *)p->car)) + status = 1; + } + } + + if (search_subencapsulation(result, packet, lease, client_state, + in_options, cfg_options, scope, universe)) + status = 1; + + return status; +} + +int nwip_option_space_encapsulate (result, packet, lease, client_state, + in_options, cfg_options, scope, universe) + struct data_string *result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct universe *universe; +{ + pair ocp; + int status; + static struct option_cache *no_nwip; + struct data_string ds; + struct option_chain_head *head; + + if (universe -> index >= cfg_options -> universe_count) + return 0; + head = ((struct option_chain_head *) + cfg_options -> universes [nwip_universe.index]); + if (!head) + return 0; + + status = 0; + for (ocp = head -> first; ocp; ocp = ocp -> cdr) { + if (store_option (result, universe, packet, + lease, client_state, in_options, + cfg_options, scope, + (struct option_cache *)ocp -> car)) + status = 1; + } + + /* If there's no data, the nwip suboption is supposed to contain + a suboption saying there's no data. */ + if (!status) { + if (!no_nwip) { + unsigned one = 1; + static unsigned char nni [] = { 1, 0 }; + + memset (&ds, 0, sizeof ds); + ds.data = nni; + ds.len = 2; + if (option_cache_allocate (&no_nwip, MDL)) + data_string_copy (&no_nwip -> data, &ds, MDL); + if (!option_code_hash_lookup(&no_nwip->option, + nwip_universe.code_hash, + &one, 0, MDL)) + log_fatal("Nwip option hash does not contain " + "1 (%s:%d).", MDL); + } + if (no_nwip) { + if (store_option (result, universe, packet, lease, + client_state, in_options, + cfg_options, scope, no_nwip)) + status = 1; + } + } else { + memset (&ds, 0, sizeof ds); + + /* If we have nwip options, the first one has to be the + nwip-exists-in-option-area option. */ + if (!buffer_allocate (&ds.buffer, result -> len + 2, MDL)) { + data_string_forget (result, MDL); + return 0; + } + ds.data = &ds.buffer -> data [0]; + ds.buffer -> data [0] = 2; + ds.buffer -> data [1] = 0; + memcpy (&ds.buffer -> data [2], result -> data, result -> len); + data_string_forget (result, MDL); + data_string_copy (result, &ds, MDL); + data_string_forget (&ds, MDL); + } + + return status; +} + +/* We don't want to use ns_name_pton()...it doesn't tell us how many bytes + * it has consumed, and it plays havoc with our escapes. + * + * So this function does DNS encoding, and returns either the number of + * octects consumed (on success), or -1 on failure. + */ +static int +fqdn_encode(unsigned char *dst, int dstlen, const unsigned char *src, + int srclen) +{ + unsigned char *out; + int i, j, len, outlen=0; + + out = dst; + for (i = 0, j = 0 ; i < srclen ; i = j) { + while ((j < srclen) && (src[j] != '.') && (src[j] != '\0')) + j++; + + len = j - i; + if ((outlen + 1 + len) > dstlen) + return -1; + + *out++ = len; + outlen++; + + /* We only do one FQDN, ending in one root label. */ + if (len == 0) + return outlen; + + memcpy(out, src + i, len); + out += len; + outlen += len; + + /* Advance past the root label. */ + j++; + } + + if ((outlen + 1) > dstlen) + return -1; + + /* Place the root label. */ + *out++ = 0; + outlen++; + + return outlen; +} + +int fqdn_option_space_encapsulate (result, packet, lease, client_state, + in_options, cfg_options, scope, universe) + struct data_string *result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct universe *universe; +{ + pair ocp; + struct data_string results [FQDN_SUBOPTION_COUNT + 1]; + int status = 1; + int i; + unsigned len; + struct buffer *bp = (struct buffer *)0; + struct option_chain_head *head; + + /* If there's no FQDN universe, don't encapsulate. */ + if (fqdn_universe.index >= cfg_options -> universe_count) + return 0; + head = ((struct option_chain_head *) + cfg_options -> universes [fqdn_universe.index]); + if (!head) + return 0; + + /* Figure out the values of all the suboptions. */ + memset (results, 0, sizeof results); + for (ocp = head -> first; ocp; ocp = ocp -> cdr) { + struct option_cache *oc = (struct option_cache *)(ocp -> car); + if (oc -> option -> code > FQDN_SUBOPTION_COUNT) + continue; + /* No need to check the return code, we check the length later */ + (void) evaluate_option_cache (&results[oc->option->code], + packet, lease, client_state, + in_options, cfg_options, scope, + oc, MDL); + } + /* We add a byte for the flags field. + * We add two bytes for the two RCODE fields. + * We add a byte because we will prepend a label count. + * We add a byte because the input len doesn't count null termination, + * and we will add a root label. + */ + len = 5 + results [FQDN_FQDN].len; + /* Save the contents of the option in a buffer. */ + if (!buffer_allocate (&bp, len, MDL)) { + log_error ("no memory for option buffer."); + status = 0; + goto exit; + } + buffer_reference (&result -> buffer, bp, MDL); + result -> len = 3; + result -> data = &bp -> data [0]; + + memset (&bp -> data [0], 0, len); + /* XXX: The server should set bit 4 (yes, 4, not 3) to 1 if it is + * not going to perform any ddns updates. The client should set the + * bit if it doesn't want the server to perform any updates. + * The problem is at this layer of abstraction we have no idea if + * the caller is a client or server. + * + * See RFC4702, Section 3.1, 'The "N" bit'. + * + * if (?) + * bp->data[0] |= 8; + */ + if (results [FQDN_NO_CLIENT_UPDATE].len && + results [FQDN_NO_CLIENT_UPDATE].data [0]) + bp -> data [0] |= 2; + if (results [FQDN_SERVER_UPDATE].len && + results [FQDN_SERVER_UPDATE].data [0]) + bp -> data [0] |= 1; + if (results [FQDN_RCODE1].len) + bp -> data [1] = results [FQDN_RCODE1].data [0]; + if (results [FQDN_RCODE2].len) + bp -> data [2] = results [FQDN_RCODE2].data [0]; + + if (results [FQDN_ENCODED].len && + results [FQDN_ENCODED].data [0]) { + bp->data[0] |= 4; + if (results [FQDN_FQDN].len) { + i = fqdn_encode(&bp->data[3], len - 3, + results[FQDN_FQDN].data, + results[FQDN_FQDN].len); + + if (i < 0) { + status = 0; + goto exit; + } + + result->len += i; + result->terminated = 0; + } + } else { + if (results [FQDN_FQDN].len) { + memcpy (&bp -> data [3], results [FQDN_FQDN].data, + results [FQDN_FQDN].len); + result -> len += results [FQDN_FQDN].len; + result -> terminated = 0; + } + } + exit: + for (i = 1; i <= FQDN_SUBOPTION_COUNT; i++) { + if (results [i].len) + data_string_forget (&results [i], MDL); + } + buffer_dereference (&bp, MDL); + if (!status) + data_string_forget(result, MDL); + return status; +} + +/* + * Trap invalid attempts to inspect FQND6 contents. + */ +struct option_cache * +lookup_fqdn6_option(struct universe *universe, struct option_state *options, + unsigned code) +{ + log_fatal("Impossible condition at %s:%d.", MDL); + return NULL; +} + +/* + * Trap invalid attempts to save options directly to FQDN6 rather than FQDN. + */ +void +save_fqdn6_option(struct universe *universe, struct option_state *options, + struct option_cache *oc, isc_boolean_t appendp) +{ + log_fatal("Impossible condition at %s:%d.", MDL); +} + +/* + * Trap invalid attempts to delete an option out of the FQDN6 universe. + */ +void +delete_fqdn6_option(struct universe *universe, struct option_state *options, + int code) +{ + log_fatal("Impossible condition at %s:%d.", MDL); +} + +/* Shill to the DHCPv4 fqdn option cache any attempts to traverse the + * V6's option cache entry. + * + * This function is called speculatively by dhclient to setup + * environment variables. But it would have already called the + * foreach on the normal fqdn universe, so this is superfluous. + */ +void +fqdn6_option_space_foreach(struct packet *packet, struct lease *lease, + struct client_state *client_state, + struct option_state *in_options, + struct option_state *cfg_options, + struct binding_scope **scope, + struct universe *u, void *stuff, + void (*func)(struct option_cache *, + struct packet *, + struct lease *, + struct client_state *, + struct option_state *, + struct option_state *, + struct binding_scope **, + struct universe *, void *)) +{ + /* Pretend it is empty. */ + return; +} + +/* Turn the FQDN option space into a DHCPv6 FQDN option buffer. + */ +int +fqdn6_option_space_encapsulate(struct data_string *result, + struct packet *packet, struct lease *lease, + struct client_state *client_state, + struct option_state *in_options, + struct option_state *cfg_options, + struct binding_scope **scope, + struct universe *universe) +{ + pair ocp; + struct option_chain_head *head; + struct option_cache *oc; + unsigned char *data; + int i, len, rval = 0, count; + struct data_string results[FQDN_SUBOPTION_COUNT + 1]; + + if (fqdn_universe.index >= cfg_options->universe_count) + return 0; + head = ((struct option_chain_head *) + cfg_options->universes[fqdn_universe.index]); + if (head == NULL) + return 0; + + memset(results, 0, sizeof(results)); + for (ocp = head->first ; ocp != NULL ; ocp = ocp->cdr) { + oc = (struct option_cache *)(ocp->car); + if (oc->option->code > FQDN_SUBOPTION_COUNT) + log_fatal("Impossible condition at %s:%d.", MDL); + /* No need to check the return code, we check the length later */ + (void) evaluate_option_cache(&results[oc->option->code], packet, + lease, client_state, in_options, + cfg_options, scope, oc, MDL); + } + + /* We add a byte for the flags field at the start of the option. + * We add a byte because we will prepend a label count. + * We add a byte because the input length doesn't include a trailing + * NULL, and we will add a root label. + */ + len = results[FQDN_FQDN].len + 3; + if (!buffer_allocate(&result->buffer, len, MDL)) { + log_error("No memory for virtual option buffer."); + goto exit; + } + data = result->buffer->data; + result->data = data; + + /* The first byte is the flags field. */ + result->len = 1; + data[0] = 0; + /* XXX: The server should set bit 3 (yes, 3, not 4) to 1 if we + * are not going to perform any DNS updates. The problem is + * that at this layer of abstraction, we do not know if the caller + * is the client or the server. + * + * See RFC4704 Section 4.1, 'The "N" bit'. + * + * if (?) + * data[0] |= 4; + */ + if (results[FQDN_NO_CLIENT_UPDATE].len && + results[FQDN_NO_CLIENT_UPDATE].data[0]) + data[0] |= 2; + if (results[FQDN_SERVER_UPDATE].len && + results[FQDN_SERVER_UPDATE].data[0]) + data[0] |= 1; + + /* If there is no name, we're done. */ + if (results[FQDN_FQDN].len == 0) { + rval = 1; + goto exit; + } + + /* Convert textual representation to DNS format. */ + count = fqdn_encode(data + 1, len - 1, + results[FQDN_FQDN].data, results[FQDN_FQDN].len); + + if (count < 0) { + rval = 0; + data_string_forget(result, MDL); + goto exit; + } + + result->len += count; + result->terminated = 0; + + /* Success! */ + rval = 1; + + exit: + for (i = 1 ; i <= FQDN_SUBOPTION_COUNT ; i++) { + if (result[i].len) + data_string_forget(&results[i], MDL); + } + + return rval; +} + +/* Read the DHCPv6 FQDN option's contents into the FQDN virtual space. + */ +int +fqdn6_universe_decode(struct option_state *options, + const unsigned char *buffer, unsigned length, + struct universe *u) +{ + struct buffer *bp = NULL; + unsigned char *first_dot; + int len, hlen, dlen; + + /* The FQDN option has to be at least 1 byte long. */ + if (length < 1) + return 0; + + /* Save the contents of the option in a buffer. There are 3 + * one-byte values we record from the packet, so we go ahead + * and allocate a bigger buffer to accommodate them. But the + * 'length' we got (because it is a DNS encoded string) is + * one longer than we need...so we only add two extra octets. + */ + if (!buffer_allocate(&bp, length + 2, MDL)) { + log_error("No memory for dhcp6.fqdn option buffer."); + return 0; + } + + /* The v6 FQDN is always 'encoded' per DNS. */ + bp->data[0] = 1; + if (!save_option_buffer(&fqdn_universe, options, bp, + bp->data, 1, FQDN_ENCODED, 0)) + goto error; + + /* XXX: We need to process 'The "N" bit'. */ + + if (buffer[0] & 1) /* server-update. */ + bp->data[2] = 1; + else + bp->data[2] = 0; + + if (!save_option_buffer(&fqdn_universe, options, bp, bp->data + 2, 1, + FQDN_SERVER_UPDATE, 0)) + goto error; + + if (buffer[0] & 2) /* no-client-update. */ + bp->data[1] = 1; + else + bp->data[1] = 0; + + if (!save_option_buffer(&fqdn_universe, options, bp, bp->data + 1, 1, + FQDN_NO_CLIENT_UPDATE, 0)) + goto error; + + /* Convert the domain name to textual representation for config. */ + len = MRns_name_ntop(buffer + 1, (char *)bp->data + 3, length - 1); + if (len == -1) { + log_error("Unable to convert dhcp6.fqdn domain name to " + "printable form."); + goto error; + } + + /* Save the domain name. */ + if (len > 0) { + unsigned char *fqdn_start = bp->data + 3; + + if (!save_option_buffer(&fqdn_universe, options, bp, + fqdn_start, len, FQDN_FQDN, 1)) + goto error; + + first_dot = (unsigned char *)strchr((char *)fqdn_start, '.'); + + if (first_dot != NULL) { + hlen = first_dot - fqdn_start; + dlen = len - hlen; + } else { + hlen = len; + dlen = 0; + } + + if (!save_option_buffer(&fqdn_universe, options, bp, + fqdn_start, len, FQDN_FQDN, 1) || + ((hlen > 0) && + !save_option_buffer(&fqdn_universe, options, bp, + fqdn_start, hlen, + FQDN_HOSTNAME, 0)) || + ((dlen > 0) && + !save_option_buffer(&fqdn_universe, options, bp, + first_dot, dlen, FQDN_DOMAINNAME, 0))) + goto error; + } + + buffer_dereference(&bp, MDL); + return 1; + + error: + buffer_dereference(&bp, MDL); + return 0; +} + +void option_space_foreach (struct packet *packet, struct lease *lease, + struct client_state *client_state, + struct option_state *in_options, + struct option_state *cfg_options, + struct binding_scope **scope, + struct universe *u, void *stuff, + void (*func) (struct option_cache *, + struct packet *, + struct lease *, struct client_state *, + struct option_state *, + struct option_state *, + struct binding_scope **, + struct universe *, void *)) +{ + if (u -> foreach) + (*u -> foreach) (packet, lease, client_state, in_options, + cfg_options, scope, u, stuff, func); +} + +void suboption_foreach (struct packet *packet, struct lease *lease, + struct client_state *client_state, + struct option_state *in_options, + struct option_state *cfg_options, + struct binding_scope **scope, + struct universe *u, void *stuff, + void (*func) (struct option_cache *, + struct packet *, + struct lease *, struct client_state *, + struct option_state *, + struct option_state *, + struct binding_scope **, + struct universe *, void *), + struct option_cache *oc, + const char *vsname) +{ + struct universe *universe = find_option_universe (oc -> option, + vsname); + if (universe -> foreach) + (*universe -> foreach) (packet, lease, client_state, + in_options, cfg_options, + scope, universe, stuff, func); +} + +void hashed_option_space_foreach (struct packet *packet, struct lease *lease, + struct client_state *client_state, + struct option_state *in_options, + struct option_state *cfg_options, + struct binding_scope **scope, + struct universe *u, void *stuff, + void (*func) (struct option_cache *, + struct packet *, + struct lease *, + struct client_state *, + struct option_state *, + struct option_state *, + struct binding_scope **, + struct universe *, void *)) +{ + pair *hash; + int i; + struct option_cache *oc; + + if (cfg_options -> universe_count <= u -> index) + return; + + hash = cfg_options -> universes [u -> index]; + if (!hash) + return; + for (i = 0; i < OPTION_HASH_SIZE; i++) { + pair p; + /* XXX save _all_ options! XXX */ + for (p = hash [i]; p; p = p -> cdr) { + oc = (struct option_cache *)p -> car; + (*func) (oc, packet, lease, client_state, + in_options, cfg_options, scope, u, stuff); + } + } +} + +void +save_linked_option(struct universe *universe, struct option_state *options, + struct option_cache *oc, isc_boolean_t appendp) +{ + pair *tail; + struct option_chain_head *head; + struct option_cache **ocloc; + + if (universe -> index >= options -> universe_count) + return; + head = ((struct option_chain_head *) + options -> universes [universe -> index]); + if (!head) { + if (!option_chain_head_allocate (((struct option_chain_head **) + &options -> universes + [universe -> index]), MDL)) + return; + head = ((struct option_chain_head *) + options -> universes [universe -> index]); + } + + /* Find the tail of the list. */ + for (tail = &head -> first; *tail; tail = &((*tail) -> cdr)) { + ocloc = (struct option_cache **)&(*tail)->car; + + if (oc->option->code == (*ocloc)->option->code) { + if (appendp) { + do { + ocloc = &(*ocloc)->next; + } while (*ocloc != NULL); + } else { + option_cache_dereference(ocloc, MDL); + } + option_cache_reference(ocloc, oc, MDL); + return; + } + } + + *tail = cons (0, 0); + if (*tail) { + option_cache_reference ((struct option_cache **) + (&(*tail) -> car), oc, MDL); + } +} + +int linked_option_space_encapsulate (result, packet, lease, client_state, + in_options, cfg_options, scope, universe) + struct data_string *result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct universe *universe; +{ + int status = 0; + pair oc; + struct option_chain_head *head; + + if (universe -> index >= cfg_options -> universe_count) + return status; + head = ((struct option_chain_head *) + cfg_options -> universes [universe -> index]); + if (!head) + return status; + + for (oc = head -> first; oc; oc = oc -> cdr) { + if (store_option (result, universe, packet, + lease, client_state, in_options, cfg_options, + scope, (struct option_cache *)(oc -> car))) + status = 1; + } + + if (search_subencapsulation(result, packet, lease, client_state, + in_options, cfg_options, scope, universe)) + status = 1; + + return status; +} + +void delete_linked_option (universe, options, code) + struct universe *universe; + struct option_state *options; + int code; +{ + pair *tail, tmp = (pair)0; + struct option_chain_head *head; + + if (universe -> index >= options -> universe_count) + return; + head = ((struct option_chain_head *) + options -> universes [universe -> index]); + if (!head) + return; + + for (tail = &head -> first; *tail; tail = &((*tail) -> cdr)) { + if (code == + ((struct option_cache *)(*tail) -> car) -> option -> code) + { + tmp = (*tail) -> cdr; + option_cache_dereference ((struct option_cache **) + (&(*tail) -> car), MDL); + dfree (*tail, MDL); + (*tail) = tmp; + break; + } + } +} + +struct option_cache *lookup_linked_option (universe, options, code) + struct universe *universe; + struct option_state *options; + unsigned code; +{ + pair oc; + struct option_chain_head *head; + + if (universe -> index >= options -> universe_count) + return 0; + head = ((struct option_chain_head *) + options -> universes [universe -> index]); + if (!head) + return 0; + + for (oc = head -> first; oc; oc = oc -> cdr) { + if (code == + ((struct option_cache *)(oc -> car)) -> option -> code) { + return (struct option_cache *)(oc -> car); + } + } + + return (struct option_cache *)0; +} + +int linked_option_state_dereference (universe, state, file, line) + struct universe *universe; + struct option_state *state; + const char *file; + int line; +{ + return (option_chain_head_dereference + ((struct option_chain_head **) + (&state -> universes [universe -> index]), MDL)); +} + +void linked_option_space_foreach (struct packet *packet, struct lease *lease, + struct client_state *client_state, + struct option_state *in_options, + struct option_state *cfg_options, + struct binding_scope **scope, + struct universe *u, void *stuff, + void (*func) (struct option_cache *, + struct packet *, + struct lease *, + struct client_state *, + struct option_state *, + struct option_state *, + struct binding_scope **, + struct universe *, void *)) +{ + pair car; + struct option_chain_head *head; + + if (u -> index >= cfg_options -> universe_count) + return; + head = ((struct option_chain_head *) + cfg_options -> universes [u -> index]); + if (!head) + return; + for (car = head -> first; car; car = car -> cdr) { + (*func) ((struct option_cache *)(car -> car), + packet, lease, client_state, + in_options, cfg_options, scope, u, stuff); + } +} + +void do_packet (interface, packet, len, from_port, from, hfrom) + struct interface_info *interface; + struct dhcp_packet *packet; + unsigned len; + unsigned int from_port; + struct iaddr from; + struct hardware *hfrom; +{ + struct option_cache *op; + struct packet *decoded_packet; +#if defined (DEBUG_MEMORY_LEAKAGE) + unsigned long previous_outstanding = dmalloc_outstanding; +#endif + +#if defined (TRACING) + trace_inpacket_stash(interface, packet, len, from_port, from, hfrom); +#endif + + decoded_packet = NULL; + if (!packet_allocate(&decoded_packet, MDL)) { + log_error("do_packet: no memory for incoming packet!"); + return; + } + decoded_packet->raw = packet; + decoded_packet->packet_length = len; + decoded_packet->client_port = from_port; + decoded_packet->client_addr = from; + interface_reference(&decoded_packet->interface, interface, MDL); + decoded_packet->haddr = hfrom; + + if (packet->hlen > sizeof packet->chaddr) { + packet_dereference(&decoded_packet, MDL); + log_info("Discarding packet with bogus hlen."); + return; + } + + /* Allocate packet->options now so it is non-null for all packets */ + decoded_packet->options_valid = 0; + if (!option_state_allocate (&decoded_packet->options, MDL)) { + return; + } + + /* If there's an option buffer, try to parse it. */ + if (decoded_packet->packet_length >= DHCP_FIXED_NON_UDP + 4) { + if (!parse_options(decoded_packet)) { + packet_dereference (&decoded_packet, MDL); + return; + } + + if (decoded_packet->options_valid && + (op = lookup_option(&dhcp_universe, + decoded_packet->options, + DHO_DHCP_MESSAGE_TYPE))) { + struct data_string dp; + memset(&dp, 0, sizeof dp); + evaluate_option_cache(&dp, decoded_packet, NULL, NULL, + decoded_packet->options, NULL, + NULL, op, MDL); + if (dp.len > 0) + decoded_packet->packet_type = dp.data[0]; + else + decoded_packet->packet_type = 0; + data_string_forget(&dp, MDL); + } + } + + if (validate_packet(decoded_packet) != 0) { + if (decoded_packet->packet_type) + dhcp(decoded_packet); + else + bootp(decoded_packet); + } + + /* If the caller kept the packet, they'll have upped the refcnt. */ + packet_dereference(&decoded_packet, MDL); + +#if defined (DEBUG_MEMORY_LEAKAGE) + log_info("generation %ld: %ld new, %ld outstanding, %ld long-term", + dmalloc_generation, + dmalloc_outstanding - previous_outstanding, + dmalloc_outstanding, dmalloc_longterm); + dmalloc_dump_outstanding(); +#endif +#if defined (DEBUG_RC_HISTORY_EXHAUSTIVELY) + dump_rc_history(0); +#endif +} + +int +packet6_len_okay(const char *packet, int len) { + if (len < 1) { + return 0; + } + if ((packet[0] == DHCPV6_RELAY_FORW) || + (packet[0] == DHCPV6_RELAY_REPL)) { + if (len >= offsetof(struct dhcpv6_relay_packet, options)) { + return 1; + } else { + return 0; + } + } else { + if (len >= offsetof(struct dhcpv6_packet, options)) { + return 1; + } else { + return 0; + } + } +} + +#ifdef DHCPv6 +void +do_packet6(struct interface_info *interface, const char *packet, + int len, int from_port, const struct iaddr *from, + isc_boolean_t was_unicast) { + unsigned char msg_type; + const struct dhcpv6_packet *msg; + const struct dhcpv6_relay_packet *relay; + struct packet *decoded_packet; +#if defined (DEBUG_MEMORY_LEAKAGE) + unsigned long previous_outstanding = dmalloc_outstanding; +#endif + + if (!packet6_len_okay(packet, len)) { + log_info("do_packet6: " + "short packet from %s port %d, len %d, dropped", + piaddr(*from), from_port, len); + return; + } + + decoded_packet = NULL; + if (!packet_allocate(&decoded_packet, MDL)) { + log_error("do_packet6: no memory for incoming packet."); + return; + } + + if (!option_state_allocate(&decoded_packet->options, MDL)) { + log_error("do_packet6: no memory for options."); + packet_dereference(&decoded_packet, MDL); + return; + } + + /* IPv4 information, already set to 0 */ + /* decoded_packet->packet_type = 0; */ + /* memset(&decoded_packet->haddr, 0, sizeof(decoded_packet->haddr)); */ + /* decoded_packet->circuit_id = NULL; */ + /* decoded_packet->circuit_id_len = 0; */ + /* decoded_packet->remote_id = NULL; */ + /* decoded_packet->remote_id_len = 0; */ + decoded_packet->raw = (struct dhcp_packet *)packet; + decoded_packet->packet_length = (unsigned)len; + decoded_packet->client_port = from_port; + decoded_packet->client_addr = *from; + interface_reference(&decoded_packet->interface, interface, MDL); + + decoded_packet->unicast = was_unicast; + + msg_type = packet[0]; + if ((msg_type == DHCPV6_RELAY_FORW) || + (msg_type == DHCPV6_RELAY_REPL)) { + int relaylen = (int)(offsetof(struct dhcpv6_relay_packet, options)); + relay = (const struct dhcpv6_relay_packet *)packet; + decoded_packet->dhcpv6_msg_type = relay->msg_type; + + /* relay-specific data */ + decoded_packet->dhcpv6_hop_count = relay->hop_count; + memcpy(&decoded_packet->dhcpv6_link_address, + relay->link_address, sizeof(relay->link_address)); + memcpy(&decoded_packet->dhcpv6_peer_address, + relay->peer_address, sizeof(relay->peer_address)); + + if (!parse_option_buffer(decoded_packet->options, + relay->options, len - relaylen, + &dhcpv6_universe)) { + /* no logging here, as parse_option_buffer() logs all + cases where it fails */ + packet_dereference(&decoded_packet, MDL); + return; + } + } else { + int msglen = (int)(offsetof(struct dhcpv6_packet, options)); + msg = (const struct dhcpv6_packet *)packet; + decoded_packet->dhcpv6_msg_type = msg->msg_type; + + /* message-specific data */ + memcpy(decoded_packet->dhcpv6_transaction_id, + msg->transaction_id, + sizeof(decoded_packet->dhcpv6_transaction_id)); + + if (!parse_option_buffer(decoded_packet->options, + msg->options, len - msglen, + &dhcpv6_universe)) { + /* no logging here, as parse_option_buffer() logs all + cases where it fails */ + packet_dereference(&decoded_packet, MDL); + return; + } + } + + dhcpv6(decoded_packet); + + packet_dereference(&decoded_packet, MDL); + +#if defined (DEBUG_MEMORY_LEAKAGE) + log_info("generation %ld: %ld new, %ld outstanding, %ld long-term", + dmalloc_generation, + dmalloc_outstanding - previous_outstanding, + dmalloc_outstanding, dmalloc_longterm); + dmalloc_dump_outstanding(); +#endif +#if defined (DEBUG_RC_HISTORY_EXHAUSTIVELY) + dump_rc_history(0); +#endif +} +#endif /* DHCPv6 */ + +int +pretty_escape(char **dst, char *dend, const unsigned char **src, + const unsigned char *send) +{ + int count = 0; + + /* If there aren't as many bytes left as there are in the source + * buffer, don't even bother entering the loop. + */ + if (dst == NULL || dend == NULL || src == NULL || send == NULL || + *dst == NULL || *src == NULL || (*dst >= dend) || (*src > send) || + ((send - *src) > (dend - *dst))) + return -1; + + for ( ; *src < send ; (*src)++) { + if (!isascii (**src) || !isprint (**src)) { + /* Skip trailing NUL. */ + if ((*src + 1) != send || **src != '\0') { + if (*dst + 4 > dend) + return -1; + + sprintf(*dst, "\\%03o", + **src); + (*dst) += 4; + count += 4; + } + } else if (**src == '"' || **src == '\'' || **src == '$' || + **src == '`' || **src == '\\' || **src == '|' || + **src == '&') { + if (*dst + 2 > dend) + return -1; + + **dst = '\\'; + (*dst)++; + **dst = **src; + (*dst)++; + count += 2; + } else { + if (*dst + 1 > dend) + return -1; + + **dst = **src; + (*dst)++; + count++; + } + } + + return count; +} + +static int +pretty_text(char **dst, char *dend, const unsigned char **src, + const unsigned char *send, int emit_quotes) +{ + int count; + + if (dst == NULL || dend == NULL || src == NULL || send == NULL || + *dst == NULL || *src == NULL || + ((*dst + (emit_quotes ? 2 : 0)) > dend) || (*src > send)) + return -1; + + if (emit_quotes) { + **dst = '"'; + (*dst)++; + } + + /* dend-1 leaves 1 byte for the closing quote. */ + count = pretty_escape(dst, dend - (emit_quotes ? 1 : 0), src, send); + + if (count == -1) + return -1; + + if (emit_quotes && (*dst < dend)) { + **dst = '"'; + (*dst)++; + + /* Includes quote prior to pretty_escape(); */ + count += 2; + } + + return count; +} + +static int +pretty_domain(char **dst, char *dend, const unsigned char **src, + const unsigned char *send) +{ + const unsigned char *tend; + int count = 2; + int tsiz, status; + + if (dst == NULL || dend == NULL || src == NULL || send == NULL || + *dst == NULL || *src == NULL || + ((*dst + 2) > dend) || (*src >= send)) + return -1; + + **dst = '"'; + (*dst)++; + + do { + /* Continue loop until end of src buffer. */ + if (*src >= send) + break; + + /* Consume tag size. */ + tsiz = **src; + (*src)++; + + /* At root, finis. */ + if (tsiz == 0) + break; + + tend = (*src) + tsiz; + + /* If the tag exceeds the source buffer, it's illegal. + * This should also trap compression pointers (which should + * not be in these buffers). + */ + if (tend > send) + return -1; + + /* dend-2 leaves room for a trailing dot and quote. */ + status = pretty_escape(dst, dend-2, src, tend); + + if ((status == -1) || ((*dst + 2) > dend)) + return -1; + + **dst = '.'; + (*dst)++; + count += status + 1; + } + while(1); + + **dst = '"'; + (*dst)++; + + return count; +} + +/* + * Add the option identified with the option number and data to the + * options state. + */ +int +add_option(struct option_state *options, + unsigned int option_num, + void *data, + unsigned int data_len) +{ + struct option_cache *oc; + struct option *option; + + /* INSIST(options != NULL); */ + /* INSIST(data != NULL); */ + + option = NULL; + if (!option_code_hash_lookup(&option, dhcp_universe.code_hash, + &option_num, 0, MDL)) { + log_error("Attempting to add unknown option %d.", option_num); + return 0; + } + + oc = NULL; + if (!option_cache_allocate(&oc, MDL)) { + log_error("No memory for option cache adding %s (option %d).", + option->name, option_num); + return 0; + } + + if (!make_const_data(&oc->expression, + data, + data_len, + 0, + 0, + MDL)) { + log_error("No memory for constant data adding %s (option %d).", + option->name, option_num); + option_cache_dereference(&oc, MDL); + return 0; + } + + option_reference(&(oc->option), option, MDL); + save_option(&dhcp_universe, options, oc); + option_cache_dereference(&oc, MDL); + + return 1; +} + +/** + * Checks if received BOOTP/DHCPv4 packet is sane + * + * @param packet received, decoded packet + * + * @return 1 if packet is sane, 0 if it is not + */ +int validate_packet(struct packet *packet) +{ + struct option_cache *oc = NULL; + + oc = lookup_option (&dhcp_universe, packet->options, + DHO_DHCP_CLIENT_IDENTIFIER); + if (oc) { + /* Let's check if client-identifier is sane */ + if (oc->data.len == 0) { + log_debug("Dropped DHCPv4 packet with zero-length client-id"); + return (0); + + } else if (oc->data.len == 1) { + /* + * RFC2132, section 9.14 states that minimum length of client-id + * is 2. We will allow single-character client-ids for now (for + * backwards compatibility), but warn the user that support for + * this is against the standard. + */ + log_debug("Accepted DHCPv4 packet with one-character client-id - " + "a future version of ISC DHCP will reject this"); + } + } else { + /* + * If hlen is 0 we don't have any identifier, we warn the user + * but continue processing the packet as we can. + */ + if (packet->raw->hlen == 0) { + log_debug("Received DHCPv4 packet without client-id" + " option and empty hlen field."); + } + } + + /* @todo: Add checks for other received options */ + + return (1); +} diff --git a/common/packet.c b/common/packet.c new file mode 100644 index 0000000..e4f390e --- /dev/null +++ b/common/packet.c @@ -0,0 +1,359 @@ +/* packet.c + + Packet assembly code, originally contributed by Archie Cobbs. */ + +/* + * Copyright (c) 2009,2012,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004,2005,2007 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + * This code was originally contributed by Archie Cobbs, and is still + * very similar to that contribution, although the packet checksum code + * has been hacked significantly with the help of quite a few ISC DHCP + * users, without whose gracious and thorough help the checksum code would + * still be disabled. + */ + +#include "dhcpd.h" + +#if defined (PACKET_ASSEMBLY) || defined (PACKET_DECODING) +#include "includes/netinet/ip.h" +#include "includes/netinet/udp.h" +#include "includes/netinet/if_ether.h" +#endif /* PACKET_ASSEMBLY || PACKET_DECODING */ + +/* Compute the easy part of the checksum on a range of bytes. */ + +u_int32_t checksum (buf, nbytes, sum) + unsigned char *buf; + unsigned nbytes; + u_int32_t sum; +{ + unsigned i; + +#ifdef DEBUG_CHECKSUM + log_debug ("checksum (%x %d %x)", buf, nbytes, sum); +#endif + + /* Checksum all the pairs of bytes first... */ + for (i = 0; i < (nbytes & ~1U); i += 2) { +#ifdef DEBUG_CHECKSUM_VERBOSE + log_debug ("sum = %x", sum); +#endif + sum += (u_int16_t) ntohs(*((u_int16_t *)(buf + i))); + /* Add carry. */ + if (sum > 0xFFFF) + sum -= 0xFFFF; + } + + /* If there's a single byte left over, checksum it, too. Network + byte order is big-endian, so the remaining byte is the high byte. */ + if (i < nbytes) { +#ifdef DEBUG_CHECKSUM_VERBOSE + log_debug ("sum = %x", sum); +#endif + sum += buf [i] << 8; + /* Add carry. */ + if (sum > 0xFFFF) + sum -= 0xFFFF; + } + + return sum; +} + +/* Finish computing the checksum, and then put it into network byte order. */ + +u_int32_t wrapsum (sum) + u_int32_t sum; +{ +#ifdef DEBUG_CHECKSUM + log_debug ("wrapsum (%x)", sum); +#endif + + sum = ~sum & 0xFFFF; +#ifdef DEBUG_CHECKSUM_VERBOSE + log_debug ("sum = %x", sum); +#endif + +#ifdef DEBUG_CHECKSUM + log_debug ("wrapsum returns %x", htons (sum)); +#endif + return htons(sum); +} + +#ifdef PACKET_ASSEMBLY +void assemble_hw_header (interface, buf, bufix, to) + struct interface_info *interface; + unsigned char *buf; + unsigned *bufix; + struct hardware *to; +{ + switch (interface->hw_address.hbuf[0]) { +#if defined(HAVE_TR_SUPPORT) + case HTYPE_IEEE802: + assemble_tr_header(interface, buf, bufix, to); + break; +#endif +#if defined (DEC_FDDI) + case HTYPE_FDDI: + assemble_fddi_header(interface, buf, bufix, to); + break; +#endif + case HTYPE_INFINIBAND: + log_error("Attempt to assemble hw header for infiniband"); + break; + case HTYPE_ETHER: + default: + assemble_ethernet_header(interface, buf, bufix, to); + break; + } +} + +/* UDP header and IP header assembled together for convenience. */ + +void assemble_udp_ip_header (interface, buf, bufix, + from, to, port, data, len) + struct interface_info *interface; + unsigned char *buf; + unsigned *bufix; + u_int32_t from; + u_int32_t to; + u_int32_t port; + unsigned char *data; + unsigned len; +{ + struct ip ip; + struct udphdr udp; + + memset (&ip, 0, sizeof ip); + + /* Fill out the IP header */ + IP_V_SET (&ip, 4); + IP_HL_SET (&ip, 20); + ip.ip_tos = IPTOS_LOWDELAY; + ip.ip_len = htons(sizeof(ip) + sizeof(udp) + len); + ip.ip_id = 0; + ip.ip_off = 0; + ip.ip_ttl = 128; + ip.ip_p = IPPROTO_UDP; + ip.ip_sum = 0; + ip.ip_src.s_addr = from; + ip.ip_dst.s_addr = to; + + /* Checksum the IP header... */ + ip.ip_sum = wrapsum (checksum ((unsigned char *)&ip, sizeof ip, 0)); + + /* Copy the ip header into the buffer... */ + memcpy (&buf [*bufix], &ip, sizeof ip); + *bufix += sizeof ip; + + /* Fill out the UDP header */ + udp.uh_sport = local_port; /* XXX */ + udp.uh_dport = port; /* XXX */ + udp.uh_ulen = htons(sizeof(udp) + len); + memset (&udp.uh_sum, 0, sizeof udp.uh_sum); + + /* Compute UDP checksums, including the ``pseudo-header'', the UDP + header and the data. */ + + udp.uh_sum = + wrapsum (checksum ((unsigned char *)&udp, sizeof udp, + checksum (data, len, + checksum ((unsigned char *) + &ip.ip_src, + 2 * sizeof ip.ip_src, + IPPROTO_UDP + + (u_int32_t) + ntohs (udp.uh_ulen))))); + + /* Copy the udp header into the buffer... */ + memcpy (&buf [*bufix], &udp, sizeof udp); + *bufix += sizeof udp; +} +#endif /* PACKET_ASSEMBLY */ + +#ifdef PACKET_DECODING +/* Decode a hardware header... */ +/* Support for ethernet, TR and FDDI + * Doesn't support infiniband yet as the supported oses shouldn't get here + */ + +ssize_t decode_hw_header (interface, buf, bufix, from) + struct interface_info *interface; + unsigned char *buf; + unsigned bufix; + struct hardware *from; +{ + switch(interface->hw_address.hbuf[0]) { +#if defined (HAVE_TR_SUPPORT) + case HTYPE_IEEE802: + return (decode_tr_header(interface, buf, bufix, from)); +#endif +#if defined (DEC_FDDI) + case HTYPE_FDDI: + return (decode_fddi_header(interface, buf, bufix, from)); +#endif + case HTYPE_INFINIBAND: + log_error("Attempt to decode hw header for infiniband"); + return (0); + case HTYPE_ETHER: + default: + return (decode_ethernet_header(interface, buf, bufix, from)); + } +} + +/* UDP header and IP header decoded together for convenience. */ + +ssize_t +decode_udp_ip_header(struct interface_info *interface, + unsigned char *buf, unsigned bufix, + struct sockaddr_in *from, unsigned buflen, + unsigned *rbuflen, int csum_ready) +{ + unsigned char *data; + struct ip ip; + struct udphdr udp; + unsigned char *upp, *endbuf; + u_int32_t ip_len, ulen, pkt_len; + static unsigned int ip_packets_seen = 0; + static unsigned int ip_packets_bad_checksum = 0; + static unsigned int udp_packets_seen = 0; + static unsigned int udp_packets_bad_checksum = 0; + static unsigned int udp_packets_length_checked = 0; + static unsigned int udp_packets_length_overflow = 0; + unsigned len; + + /* Designate the end of the input buffer for bounds checks. */ + endbuf = buf + bufix + buflen; + + /* Assure there is at least an IP header there. */ + if ((buf + bufix + sizeof(ip)) > endbuf) + return -1; + + /* Copy the IP header into a stack aligned structure for inspection. + * There may be bits in the IP header that we're not decoding, so we + * copy out the bits we grok and skip ahead by ip.ip_hl * 4. + */ + upp = buf + bufix; + memcpy(&ip, upp, sizeof(ip)); + ip_len = (*upp & 0x0f) << 2; + upp += ip_len; + + /* Check the IP packet length. */ + pkt_len = ntohs(ip.ip_len); + if (pkt_len > buflen) + return -1; + + /* Assure after ip_len bytes that there is enough room for a UDP header. */ + if ((upp + sizeof(udp)) > endbuf) + return -1; + + /* Copy the UDP header into a stack aligned structure for inspection. */ + memcpy(&udp, upp, sizeof(udp)); + +#ifdef USERLAND_FILTER + /* Is it a UDP packet? */ + if (ip.ip_p != IPPROTO_UDP) + return -1; + + /* Is it to the port we're serving? */ + if (udp.uh_dport != local_port) + return -1; +#endif /* USERLAND_FILTER */ + + ulen = ntohs(udp.uh_ulen); + if (ulen < sizeof(udp)) + return -1; + + udp_packets_length_checked++; + if ((upp + ulen) > endbuf) { + udp_packets_length_overflow++; + if (((udp_packets_length_checked > 4) && + (udp_packets_length_overflow != 0)) && + ((udp_packets_length_checked / udp_packets_length_overflow) < 2)) { + log_info("%u udp packets in %u too long - dropped", + udp_packets_length_overflow, + udp_packets_length_checked); + udp_packets_length_overflow = 0; + udp_packets_length_checked = 0; + } + return -1; + } + + /* Check the IP header checksum - it should be zero. */ + ip_packets_seen++; + if (wrapsum (checksum (buf + bufix, ip_len, 0))) { + ++ip_packets_bad_checksum; + if (((ip_packets_seen > 4) && (ip_packets_bad_checksum != 0)) && + ((ip_packets_seen / ip_packets_bad_checksum) < 2)) { + log_info ("%u bad IP checksums seen in %u packets", + ip_packets_bad_checksum, ip_packets_seen); + ip_packets_seen = ip_packets_bad_checksum = 0; + } + return -1; + } + + /* Copy out the IP source address... */ + memcpy(&from->sin_addr, &ip.ip_src, 4); + + data = upp + sizeof(udp); + len = ulen - sizeof(udp); + + /* UDP check sum may be optional (udp.uh_sum == 0) or not ready if checksum + * offloading is in use */ + udp_packets_seen++; + if (udp.uh_sum && csum_ready) { + /* Check the UDP header checksum - since the received packet header + * contains the UDP checksum calculated by the transmitter, calculating + * it now should come out to zero. */ + if (wrapsum(checksum((unsigned char *)&udp, sizeof(udp), + checksum(data, len, + checksum((unsigned char *)&ip.ip_src, + 8, IPPROTO_UDP + ulen))))) { + udp_packets_bad_checksum++; + if (((udp_packets_seen > 4) && (udp_packets_bad_checksum != 0)) + && ((udp_packets_seen / udp_packets_bad_checksum) < 2)) { + log_info ("%u bad udp checksums in %u packets", + udp_packets_bad_checksum, udp_packets_seen); + udp_packets_seen = udp_packets_bad_checksum = 0; + } + + return -1; + } + } + + /* If at least 5 with less than 50% bad, start over */ + if (udp_packets_seen > 4) { + udp_packets_bad_checksum = 0; + udp_packets_seen = 0; + } + + /* Copy out the port... */ + memcpy (&from -> sin_port, &udp.uh_sport, sizeof udp.uh_sport); + + /* Save the length of the UDP payload. */ + if (rbuflen != NULL) + *rbuflen = len; + + /* Return the index to the UDP payload. */ + return ip_len + sizeof udp; +} +#endif /* PACKET_DECODING */ diff --git a/common/parse.c b/common/parse.c new file mode 100644 index 0000000..8f1518f --- /dev/null +++ b/common/parse.c @@ -0,0 +1,6007 @@ +/* parse.c + + Common parser code for dhcpd and dhclient. */ + +/* + * Copyright (c) 2004-2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" +#include <syslog.h> + +/* Enumerations can be specified in option formats, and are used for + parsing, so we define the routines that manage them here. */ + +struct enumeration *enumerations; + +void add_enumeration (struct enumeration *enumeration) +{ + enumeration -> next = enumerations; + enumerations = enumeration; +} + +struct enumeration *find_enumeration (const char *name, int length) +{ + struct enumeration *e; + + for (e = enumerations; e; e = e -> next) + if (strlen (e -> name) == length && + !memcmp (e -> name, name, (unsigned)length)) + return e; + return (struct enumeration *)0; +} + +struct enumeration_value *find_enumeration_value (const char *name, + int length, + unsigned *widthp, + const char *value) +{ + struct enumeration *e; + int i; + + e = find_enumeration (name, length); + if (e) { + if (widthp != NULL) + *widthp = e->width; + for (i = 0; e -> values [i].name; i++) { + if (!strcmp (value, e -> values [i].name)) + return &e -> values [i]; + } + } + return (struct enumeration_value *)0; +} + +/* Skip to the semicolon ending the current statement. If we encounter + braces, the matching closing brace terminates the statement. +*/ +void skip_to_semi (cfile) + struct parse *cfile; +{ + skip_to_rbrace(cfile, 0); +} + +/* Skips everything from the current point upto (and including) the given + number of right braces. If we encounter a semicolon but haven't seen a + left brace, consume it and return. + This lets us skip over: + + statement; + statement foo bar { } + statement foo bar { statement { } } + statement} + + ...et cetera. */ +void skip_to_rbrace (cfile, brace_count) + struct parse *cfile; + int brace_count; +{ + enum dhcp_token token; + const char *val; + +#if defined (DEBUG_TOKEN) + log_error("skip_to_rbrace: %d\n", brace_count); +#endif + do { + token = peek_token(&val, NULL, cfile); + if (token == RBRACE) { + if (brace_count > 0) { + --brace_count; + } + + if (brace_count == 0) { + /* Eat the brace and return. */ + skip_token(&val, NULL, cfile); + return; + } + } else if (token == LBRACE) { + brace_count++; + } else if (token == SEMI && (brace_count == 0)) { + /* Eat the semicolon and return. */ + skip_token(&val, NULL, cfile); + return; + } else if (token == EOL) { + /* EOL only happens when parsing /etc/resolv.conf, + and we treat it like a semicolon because the + resolv.conf file is line-oriented. */ + skip_token(&val, NULL, cfile); + return; + } + + /* Eat the current token */ + token = next_token(&val, NULL, cfile); + } while (token != END_OF_FILE); +} + +int parse_semi (cfile) + struct parse *cfile; +{ + enum dhcp_token token; + const char *val; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != SEMI) { + parse_warn (cfile, "semicolon expected."); + skip_to_semi (cfile); + return 0; + } + return 1; +} + +/* string-parameter :== STRING SEMI */ + +int parse_string (cfile, sptr, lptr) + struct parse *cfile; + char **sptr; + unsigned *lptr; +{ + const char *val; + enum dhcp_token token; + char *s; + unsigned len; + + token = next_token (&val, &len, cfile); + if (token != STRING) { + parse_warn (cfile, "expecting a string"); + skip_to_semi (cfile); + return 0; + } + s = (char *)dmalloc (len + 1, MDL); + if (!s) + log_fatal ("no memory for string %s.", val); + memcpy (s, val, len + 1); + + if (!parse_semi (cfile)) { + dfree (s, MDL); + return 0; + } + if (sptr) + *sptr = s; + else + dfree (s, MDL); + if (lptr) + *lptr = len; + return 1; +} + +/* + * hostname :== IDENTIFIER + * | IDENTIFIER DOT + * | hostname DOT IDENTIFIER + */ + +char *parse_host_name (cfile) + struct parse *cfile; +{ + const char *val; + enum dhcp_token token; + unsigned len = 0; + char *s; + char *t; + pair c = (pair)0; + int ltid = 0; + + /* Read a dotted hostname... */ + do { + /* Read a token, which should be an identifier. */ + token = peek_token (&val, (unsigned *)0, cfile); + if (!is_identifier (token) && token != NUMBER) + break; + skip_token(&val, (unsigned *)0, cfile); + + /* Store this identifier... */ + if (!(s = (char *)dmalloc (strlen (val) + 1, MDL))) + log_fatal ("can't allocate temp space for hostname."); + strcpy (s, val); + c = cons ((caddr_t)s, c); + len += strlen (s) + 1; + /* Look for a dot; if it's there, keep going, otherwise + we're done. */ + token = peek_token (&val, (unsigned *)0, cfile); + if (token == DOT) { + token = next_token (&val, (unsigned *)0, cfile); + ltid = 1; + } else + ltid = 0; + } while (token == DOT); + + /* Should be at least one token. */ + if (!len) + return (char *)0; + + /* Assemble the hostname together into a string. */ + if (!(s = (char *)dmalloc (len + ltid, MDL))) + log_fatal ("can't allocate space for hostname."); + t = s + len + ltid; + *--t = 0; + if (ltid) + *--t = '.'; + while (c) { + pair cdr = c -> cdr; + unsigned l = strlen ((char *)(c -> car)); + t -= l; + memcpy (t, (char *)(c -> car), l); + /* Free up temp space. */ + dfree (c -> car, MDL); + dfree (c, MDL); + c = cdr; + if (t != s) + *--t = '.'; + } + return s; +} + +/* ip-addr-or-hostname :== ip-address | hostname + ip-address :== NUMBER DOT NUMBER DOT NUMBER DOT NUMBER + + Parse an ip address or a hostname. If uniform is zero, put in + an expr_substring node to limit hostnames that evaluate to more + than one IP address. + + Note that RFC1123 permits hostnames to consist of all digits, + making it difficult to quickly disambiguate them from ip addresses. +*/ + +int parse_ip_addr_or_hostname (expr, cfile, uniform) + struct expression **expr; + struct parse *cfile; + int uniform; +{ + const char *val; + enum dhcp_token token; + unsigned char addr [4]; + unsigned len = sizeof addr; + char *name; + struct expression *x = (struct expression *)0; + int ipaddr = 0; + + token = peek_token (&val, (unsigned *)0, cfile); + + if (token == NUMBER) { + /* + * a hostname may be numeric, but domain names must + * start with a letter, so we can disambiguate by + * looking ahead a few tokens. we save the parse + * context first, and restore it after we know what + * we're dealing with. + */ + save_parse_state(cfile); + skip_token(NULL, NULL, cfile); + if (next_token(NULL, NULL, cfile) == DOT && + next_token(NULL, NULL, cfile) == NUMBER) + ipaddr = 1; + restore_parse_state(cfile); + + if (ipaddr && + parse_numeric_aggregate (cfile, addr, &len, DOT, 10, 8)) + return make_const_data (expr, addr, len, 0, 1, MDL); + + } + + if (is_identifier (token) || token == NUMBER) { + name = parse_host_name (cfile); + if (!name) + return 0; + if (!make_host_lookup (expr, name)) { + dfree(name, MDL); + return 0; + } + dfree(name, MDL); + if (!uniform) { + if (!make_limit (&x, *expr, 4)) + return 0; + expression_dereference (expr, MDL); + *expr = x; + } + } else { + if (token != RBRACE && token != LBRACE) + token = next_token (&val, (unsigned *)0, cfile); + parse_warn (cfile, "%s (%d): expecting IP address or hostname", + val, token); + if (token != SEMI) + skip_to_semi (cfile); + return 0; + } + + return 1; +} + +/* + * ip-address :== NUMBER DOT NUMBER DOT NUMBER DOT NUMBER + */ + +int parse_ip_addr (cfile, addr) + struct parse *cfile; + struct iaddr *addr; +{ + addr -> len = 4; + if (parse_numeric_aggregate (cfile, addr -> iabuf, + &addr -> len, DOT, 10, 8)) + return 1; + return 0; +} + +/* + * Return true if every character in the string is hexadecimal. + */ +static int +is_hex_string(const char *s) { + while (*s != '\0') { + if (!isxdigit((int)*s)) { + return 0; + } + s++; + } + return 1; +} + +/* + * ip-address6 :== (complicated set of rules) + * + * See section 2.2 of RFC 1884 for details. + * + * We are lazy for this. We pull numbers, names, colons, and dots + * together and then throw the resulting string at the inet_pton() + * function. + */ + +int +parse_ip6_addr(struct parse *cfile, struct iaddr *addr) { + enum dhcp_token token; + const char *val; + int val_len; + + char v6[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + int v6_len; + + /* + * First token is non-raw. This way we eat any whitespace before + * our IPv6 address begins, like one would expect. + */ + token = peek_token(&val, NULL, cfile); + + /* + * Gather symbols. + */ + v6_len = 0; + for (;;) { + if ((((token == NAME) || (token == NUMBER_OR_NAME)) && + is_hex_string(val)) || + (token == NUMBER) || + (token == DOT) || + (token == COLON)) { + + next_raw_token(&val, NULL, cfile); + val_len = strlen(val); + if ((v6_len + val_len) >= sizeof(v6)) { + parse_warn(cfile, "Invalid IPv6 address."); + skip_to_semi(cfile); + return 0; + } + memcpy(v6+v6_len, val, val_len); + v6_len += val_len; + + } else { + break; + } + token = peek_raw_token(&val, NULL, cfile); + } + v6[v6_len] = '\0'; + + /* + * Use inet_pton() for actual work. + */ + if (inet_pton(AF_INET6, v6, addr->iabuf) <= 0) { + parse_warn(cfile, "Invalid IPv6 address."); + skip_to_semi(cfile); + return 0; + } + addr->len = 16; + return 1; +} + +/* + * Same as parse_ip6_addr() above, but returns the value in the + * expression rather than in an address structure. + */ +int +parse_ip6_addr_expr(struct expression **expr, + struct parse *cfile) { + struct iaddr addr; + + if (!parse_ip6_addr(cfile, &addr)) { + return 0; + } + return make_const_data(expr, addr.iabuf, addr.len, 0, 1, MDL); +} + +/* + * ip6-prefix :== ip6-address "/" NUMBER + */ +int +parse_ip6_prefix(struct parse *cfile, struct iaddr *addr, u_int8_t *plen) { + enum dhcp_token token; + const char *val; + int n; + + if (!parse_ip6_addr(cfile, addr)) { + return 0; + } + token = next_token(&val, NULL, cfile); + if (token != SLASH) { + parse_warn(cfile, "Slash expected."); + if (token != SEMI) + skip_to_semi(cfile); + return 0; + } + token = next_token(&val, NULL, cfile); + if (token != NUMBER) { + parse_warn(cfile, "Number expected."); + if (token != SEMI) + skip_to_semi(cfile); + return 0; + } + n = atoi(val); + if ((n < 0) || (n > 128)) { + parse_warn(cfile, "Invalid IPv6 prefix length."); + skip_to_semi(cfile); + return 0; + } + if (!is_cidr_mask_valid(addr, n)) { + parse_warn(cfile, "network mask too short."); + skip_to_semi(cfile); + return 0; + } + *plen = n; + return 1; +} + +/* + * ip-address-with-subnet :== ip-address | + * ip-address "/" NUMBER + */ + +int +parse_ip_addr_with_subnet(cfile, match) + struct parse *cfile; + struct iaddrmatch *match; +{ + const char *val, *orig; + enum dhcp_token token; + int prefixlen; + int fflen; + unsigned char newval, warnmask=0; + + if (parse_ip_addr(cfile, &match->addr)) { + /* default to host mask */ + prefixlen = match->addr.len * 8; + + token = peek_token(&val, NULL, cfile); + + if (token == SLASH) { + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + + if (token != NUMBER) { + parse_warn(cfile, "Invalid CIDR prefix length:" + " expecting a number."); + return 0; + } + + prefixlen = atoi(val); + + if (prefixlen < 0 || + prefixlen > (match->addr.len * 8)) { + parse_warn(cfile, "subnet prefix is out of " + "range [0..%d].", + match->addr.len * 8); + return 0; + } + } + + /* construct a suitable mask field */ + + /* copy length */ + match->mask.len = match->addr.len; + + /* count of 0xff bytes in mask */ + fflen = prefixlen / 8; + + /* set leading mask */ + memset(match->mask.iabuf, 0xff, fflen); + + /* set zeroes */ + if (fflen < match->mask.len) { + match->mask.iabuf[fflen] = + "\x00\x80\xc0\xe0\xf0\xf8\xfc\xfe"[prefixlen % 8]; + + memset(match->mask.iabuf+fflen+1, 0x00, + match->mask.len - fflen - 1); + + /* AND-out insignificant bits from supplied netmask. */ + orig = piaddr(match->addr); + do { + newval = match->addr.iabuf[fflen] & + match->mask.iabuf[fflen]; + + if (newval != match->addr.iabuf[fflen]) { + warnmask = 1; + match->addr.iabuf[fflen] = newval; + } + } while (++fflen < match->mask.len); + + if (warnmask) { + log_error("Warning: Extraneous bits removed " + "in address component of %s/%d.", + orig, prefixlen); + log_error("New value: %s/%d.", + piaddr(match->addr), prefixlen); + } + } + + return 1; + } + + parse_warn(cfile, + "expecting ip-address or ip-address/prefixlen"); + + return 0; /* let caller pick up pieces */ +} + +/* + * hardware-parameter :== HARDWARE hardware-type colon-separated-hex-list SEMI + * hardware-type :== ETHERNET | TOKEN_RING | TOKEN_FDDI | INFINIBAND + * Note that INFINIBAND may not be useful for some items, such as classification + * as the hardware address won't always be available. + */ + +void parse_hardware_param (cfile, hardware) + struct parse *cfile; + struct hardware *hardware; +{ + const char *val; + enum dhcp_token token; + unsigned hlen; + unsigned char *t; + + token = next_token(&val, NULL, cfile); + switch (token) { + case ETHERNET: + hardware->hbuf[0] = HTYPE_ETHER; + break; + case TOKEN_RING: + hardware->hbuf[0] = HTYPE_IEEE802; + break; + case TOKEN_FDDI: + hardware->hbuf[0] = HTYPE_FDDI; + break; + case TOKEN_INFINIBAND: + hardware->hbuf[0] = HTYPE_INFINIBAND; + break; + default: + if (!strncmp(val, "unknown-", 8)) { + hardware->hbuf[0] = atoi(&val[8]); + } else { + parse_warn(cfile, + "expecting a network hardware type"); + skip_to_semi(cfile); + + return; + } + } + + /* Parse the hardware address information. Technically, + it would make a lot of sense to restrict the length of the + data we'll accept here to the length of a particular hardware + address type. Unfortunately, there are some broken clients + out there that put bogus data in the chaddr buffer, and we accept + that data in the lease file rather than simply failing on such + clients. Yuck. */ + hlen = 0; + token = peek_token(&val, NULL, cfile); + if (token == SEMI) { + hardware->hlen = 1; + goto out; + } + t = parse_numeric_aggregate(cfile, NULL, &hlen, COLON, 16, 8); + if (t == NULL) { + hardware->hlen = 1; + return; + } + if (hlen + 1 > sizeof(hardware->hbuf)) { + dfree(t, MDL); + parse_warn(cfile, "hardware address too long"); + } else { + hardware->hlen = hlen + 1; + memcpy((unsigned char *)&hardware->hbuf[1], t, hlen); + if (hlen + 1 < sizeof(hardware->hbuf)) + memset(&hardware->hbuf[hlen + 1], 0, + (sizeof(hardware->hbuf)) - hlen - 1); + dfree(t, MDL); + } + + out: + token = next_token(&val, NULL, cfile); + if (token != SEMI) { + parse_warn(cfile, "expecting semicolon."); + skip_to_semi(cfile); + } +} + +/* lease-time :== NUMBER SEMI */ + +void parse_lease_time (cfile, timep) + struct parse *cfile; + TIME *timep; +{ + const char *val; + enum dhcp_token token; + u_int32_t num; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != NUMBER) { + parse_warn (cfile, "Expecting numeric lease time"); + skip_to_semi (cfile); + return; + } + convert_num(cfile, (unsigned char *)&num, val, 10, 32); + /* Unswap the number - convert_num returns stuff in NBO. */ + *timep = ntohl(num); + + parse_semi (cfile); +} + +/* No BNF for numeric aggregates - that's defined by the caller. What + this function does is to parse a sequence of numbers separated by + the token specified in separator. If max is zero, any number of + numbers will be parsed; otherwise, exactly max numbers are + expected. Base and size tell us how to internalize the numbers + once they've been tokenized. + + buf - A pointer to space to return the parsed value, if it is null + then the function will allocate space for the return. + + max - The maximum number of items to store. If zero there is no + maximum. When buf is null and the function needs to allocate space + it will do an allocation of max size at the beginning if max is non + zero. If max is zero then the allocation will be done later, after + the function has determined the size necessary for the incoming + string. + + returns NULL on errors or a pointer to the value string on success. + The pointer will either be buf if it was non-NULL or newly allocated + space if buf was NULL + */ + + +unsigned char *parse_numeric_aggregate (cfile, buf, + max, separator, base, size) + struct parse *cfile; + unsigned char *buf; + unsigned *max; + int separator; + int base; + unsigned size; +{ + const char *val; + enum dhcp_token token; + unsigned char *bufp = buf, *s, *t; + unsigned count = 0; + pair c = (pair)0; + + if (!bufp && *max) { + bufp = (unsigned char *)dmalloc (*max * size / 8, MDL); + if (!bufp) + log_fatal ("no space for numeric aggregate"); + } + s = bufp; + + do { + if (count) { + token = peek_token (&val, (unsigned *)0, cfile); + if (token != separator) { + if (!*max) + break; + if (token != RBRACE && token != LBRACE) + token = next_token (&val, + (unsigned *)0, + cfile); + parse_warn (cfile, "too few numbers."); + if (token != SEMI) + skip_to_semi (cfile); + /* free bufp if it was allocated */ + if ((bufp != NULL) && (bufp != buf)) + dfree(bufp, MDL); + return (unsigned char *)0; + } + skip_token(&val, (unsigned *)0, cfile); + } + token = next_token (&val, (unsigned *)0, cfile); + + if (token == END_OF_FILE) { + parse_warn (cfile, "unexpected end of file"); + break; + } + + /* Allow NUMBER_OR_NAME if base is 16. */ + if (token != NUMBER && + (base != 16 || token != NUMBER_OR_NAME)) { + parse_warn (cfile, "expecting numeric value."); + skip_to_semi (cfile); + /* free bufp if it was allocated */ + if ((bufp != NULL) && (bufp != buf)) + dfree(bufp, MDL); + /* free any linked numbers we may have allocated */ + while (c) { + pair cdr = c->cdr; + dfree(c->car, MDL); + dfree(c, MDL); + c = cdr; + } + return (NULL); + } + /* If we can, convert the number now; otherwise, build + a linked list of all the numbers. */ + if (s) { + convert_num (cfile, s, val, base, size); + s += size / 8; + } else { + t = (unsigned char *)dmalloc (strlen (val) + 1, MDL); + if (!t) + log_fatal ("no temp space for number."); + strcpy ((char *)t, val); + c = cons ((caddr_t)t, c); + } + } while (++count != *max); + + /* If we had to cons up a list, convert it now. */ + if (c) { + /* + * No need to cleanup bufp, to get here we didn't allocate + * bufp above + */ + bufp = (unsigned char *)dmalloc (count * size / 8, MDL); + if (!bufp) + log_fatal ("no space for numeric aggregate."); + s = bufp + count - size / 8; + *max = count; + } + while (c) { + pair cdr = c -> cdr; + convert_num (cfile, s, (char *)(c -> car), base, size); + s -= size / 8; + /* Free up temp space. */ + dfree (c -> car, MDL); + dfree (c, MDL); + c = cdr; + } + return bufp; +} + +void convert_num (cfile, buf, str, base, size) + struct parse *cfile; + unsigned char *buf; + const char *str; + int base; + unsigned size; +{ + const unsigned char *ptr = (const unsigned char *)str; + int negative = 0; + u_int32_t val = 0; + int tval; + int max; + + if (*ptr == '-') { + negative = 1; + ++ptr; + } + + /* If base wasn't specified, figure it out from the data. */ + if (!base) { + if (ptr [0] == '0') { + if (ptr [1] == 'x') { + base = 16; + ptr += 2; + } else if (isascii (ptr [1]) && isdigit (ptr [1])) { + base = 8; + ptr += 1; + } else { + base = 10; + } + } else { + base = 10; + } + } + + do { + tval = *ptr++; + /* XXX assumes ASCII... */ + if (tval >= 'a') + tval = tval - 'a' + 10; + else if (tval >= 'A') + tval = tval - 'A' + 10; + else if (tval >= '0') + tval -= '0'; + else { + parse_warn (cfile, "Bogus number: %s.", str); + break; + } + if (tval >= base) { + parse_warn (cfile, + "Bogus number %s: digit %d not in base %d", + str, tval, base); + break; + } + val = val * base + tval; + } while (*ptr); + + if (negative) + max = (1 << (size - 1)); + else + max = (1 << (size - 1)) + ((1 << (size - 1)) - 1); + if (val > max) { + switch (base) { + case 8: + parse_warn (cfile, + "%s%lo exceeds max (%d) for precision.", + negative ? "-" : "", + (unsigned long)val, max); + break; + case 16: + parse_warn (cfile, + "%s%lx exceeds max (%d) for precision.", + negative ? "-" : "", + (unsigned long)val, max); + break; + default: + parse_warn (cfile, + "%s%lu exceeds max (%d) for precision.", + negative ? "-" : "", + (unsigned long)val, max); + break; + } + } + + if (negative) { + switch (size) { + case 8: + *buf = -(unsigned long)val; + break; + case 16: + putShort (buf, -(long)val); + break; + case 32: + putLong (buf, -(long)val); + break; + default: + parse_warn (cfile, + "Unexpected integer size: %d\n", size); + break; + } + } else { + switch (size) { + case 8: + *buf = (u_int8_t)val; + break; + case 16: + putUShort (buf, (u_int16_t)val); + break; + case 32: + putULong (buf, val); + break; + default: + parse_warn (cfile, + "Unexpected integer size: %d\n", size); + break; + } + } +} + +/* + * date :== NUMBER NUMBER SLASH NUMBER SLASH NUMBER + * NUMBER COLON NUMBER COLON NUMBER | + * NUMBER NUMBER SLASH NUMBER SLASH NUMBER + * NUMBER COLON NUMBER COLON NUMBER NUMBER | + * EPOCH NUMBER | + * NEVER + * + * Dates are stored in UTC or with a timezone offset; first number is day + * of week; next is year/month/day; next is hours:minutes:seconds on a + * 24-hour clock, followed by the timezone offset in seconds, which is + * optional. + */ + +/* + * just parse the date + * any trailing semi must be consumed by the caller of this routine + */ +TIME +parse_date_core(cfile) + struct parse *cfile; +{ + int guess; + int tzoff, year, mon, mday, hour, min, sec; + const char *val; + enum dhcp_token token; + static int months[11] = { 31, 59, 90, 120, 151, 181, + 212, 243, 273, 304, 334 }; + + /* "never", "epoch" or day of week */ + token = peek_token(&val, NULL, cfile); + if (token == NEVER) { + skip_token(&val, NULL, cfile); /* consume NEVER */ + return(MAX_TIME); + } + + /* This indicates 'local' time format. */ + if (token == EPOCH) { + skip_token(&val, NULL, cfile); /* consume EPOCH */ + token = peek_token(&val, NULL, cfile); + + if (token != NUMBER) { + if (token != SEMI) + skip_token(&val, NULL, cfile); + parse_warn(cfile, "Seconds since epoch expected."); + return((TIME)0); + } + + skip_token(&val, NULL, cfile); /* consume number */ + guess = atoi(val); + + return((TIME)guess); + } + + if (token != NUMBER) { + if (token != SEMI) + skip_token(&val, NULL, cfile); + parse_warn(cfile, "numeric day of week expected."); + return((TIME)0); + } + skip_token(&val, NULL, cfile); /* consume day of week */ + /* we are not using this for anything */ + + /* Year... */ + token = peek_token(&val, NULL, cfile); + if (token != NUMBER) { + if (token != SEMI) + skip_token(&val, NULL, cfile); + parse_warn(cfile, "numeric year expected."); + return((TIME)0); + } + skip_token(&val, NULL, cfile); /* consume year */ + + /* Note: the following is not a Y2K bug - it's a Y1.9K bug. Until + somebody invents a time machine, I think we can safely disregard + it. This actually works around a stupid Y2K bug that was present + in a very early beta release of dhcpd. */ + year = atoi(val); + if (year > 1900) + year -= 1900; + + /* Slash separating year from month... */ + token = peek_token(&val, NULL, cfile); + if (token != SLASH) { + if (token != SEMI) + skip_token(&val, NULL, cfile); + parse_warn(cfile, + "expected slash separating year from month."); + return((TIME)0); + } + skip_token(&val, NULL, cfile); /* consume SLASH */ + + /* Month... */ + token = peek_token(&val, NULL, cfile); + if (token != NUMBER) { + if (token != SEMI) + skip_token(&val, NULL, cfile); + parse_warn(cfile, "numeric month expected."); + return((TIME)0); + } + skip_token(&val, NULL, cfile); /* consume month */ + mon = atoi(val) - 1; + + /* Slash separating month from day... */ + token = peek_token(&val, NULL, cfile); + if (token != SLASH) { + if (token != SEMI) + skip_token(&val, NULL, cfile); + parse_warn(cfile, + "expected slash separating month from day."); + return((TIME)0); + } + skip_token(&val, NULL, cfile); /* consume SLASH */ + + /* Day of month... */ + token = peek_token(&val, NULL, cfile); + if (token != NUMBER) { + if (token != SEMI) + skip_token(&val, NULL, cfile); + parse_warn(cfile, "numeric day of month expected."); + return((TIME)0); + } + skip_token(&val, NULL, cfile); /* consume day of month */ + mday = atoi(val); + + /* Hour... */ + token = peek_token(&val, NULL, cfile); + if (token != NUMBER) { + if (token != SEMI) + skip_token(&val, NULL, cfile); + parse_warn(cfile, "numeric hour expected."); + return((TIME)0); + } + skip_token(&val, NULL, cfile); /* consume hour */ + hour = atoi(val); + + /* Colon separating hour from minute... */ + token = peek_token(&val, NULL, cfile); + if (token != COLON) { + if (token != SEMI) + skip_token(&val, NULL, cfile); + parse_warn(cfile, + "expected colon separating hour from minute."); + return((TIME)0); + } + skip_token(&val, NULL, cfile); /* consume colon */ + + /* Minute... */ + token = peek_token(&val, NULL, cfile); + if (token != NUMBER) { + if (token != SEMI) + skip_token(&val, NULL, cfile); + parse_warn(cfile, "numeric minute expected."); + return((TIME)0); + } + skip_token(&val, NULL, cfile); /* consume minute */ + min = atoi(val); + + /* Colon separating minute from second... */ + token = peek_token(&val, NULL, cfile); + if (token != COLON) { + if (token != SEMI) + skip_token(&val, NULL, cfile); + parse_warn(cfile, + "expected colon separating minute from second."); + return((TIME)0); + } + skip_token(&val, NULL, cfile); /* consume colon */ + + /* Second... */ + token = peek_token(&val, NULL, cfile); + if (token != NUMBER) { + if (token != SEMI) + skip_token(&val, NULL, cfile); + parse_warn(cfile, "numeric second expected."); + return((TIME)0); + } + skip_token(&val, NULL, cfile); /* consume second */ + sec = atoi(val); + + tzoff = 0; + token = peek_token(&val, NULL, cfile); + if (token == NUMBER) { + skip_token(&val, NULL, cfile); /* consume tzoff */ + tzoff = atoi(val); + } else if (token != SEMI) { + skip_token(&val, NULL, cfile); + parse_warn(cfile, + "Time zone offset or semicolon expected."); + return((TIME)0); + } + + /* Guess the time value... */ + guess = ((((((365 * (year - 70) + /* Days in years since '70 */ + (year - 69) / 4 + /* Leap days since '70 */ + (mon /* Days in months this year */ + ? months [mon - 1] + : 0) + + (mon > 1 && /* Leap day this year */ + !((year - 72) & 3)) + + mday - 1) * 24) + /* Day of month */ + hour) * 60) + + min) * 60) + sec + tzoff; + + /* This guess could be wrong because of leap seconds or other + weirdness we don't know about that the system does. For + now, we're just going to accept the guess, but at some point + it might be nice to do a successive approximation here to + get an exact value. Even if the error is small, if the + server is restarted frequently (and thus the lease database + is reread), the error could accumulate into something + significant. */ + + return((TIME)guess); +} + +/* + * Wrapper to consume the semicolon after the date + * :== date semi + */ + +TIME +parse_date(cfile) + struct parse *cfile; +{ + TIME guess; + guess = parse_date_core(cfile); + + /* Make sure the date ends in a semicolon... */ + if (!parse_semi(cfile)) + return((TIME)0); + return(guess); +} + + + +/* + * option-name :== IDENTIFIER | + IDENTIFIER . IDENTIFIER + */ + +isc_result_t +parse_option_name (cfile, allocate, known, opt) + struct parse *cfile; + int allocate; + int *known; + struct option **opt; +{ + const char *val; + enum dhcp_token token; + char *uname; + struct universe *universe; + struct option *option; + unsigned code; + + if (opt == NULL) + return DHCP_R_INVALIDARG; + + token = next_token (&val, (unsigned *)0, cfile); + if (!is_identifier (token)) { + parse_warn (cfile, + "expecting identifier after option keyword."); + if (token != SEMI) + skip_to_semi (cfile); + return DHCP_R_BADPARSE; + } + uname = dmalloc (strlen (val) + 1, MDL); + if (!uname) + log_fatal ("no memory for uname information."); + strcpy (uname, val); + token = peek_token (&val, (unsigned *)0, cfile); + if (token == DOT) { + /* Go ahead and take the DOT token... */ + skip_token(&val, (unsigned *)0, cfile); + + /* The next token should be an identifier... */ + token = next_token (&val, (unsigned *)0, cfile); + if (!is_identifier (token)) { + parse_warn (cfile, "expecting identifier after '.'"); + if (token != SEMI) + skip_to_semi (cfile); + return DHCP_R_BADPARSE; + } + + /* Look up the option name hash table for the specified + uname. */ + universe = (struct universe *)0; + if (!universe_hash_lookup (&universe, universe_hash, + uname, 0, MDL)) { + parse_warn (cfile, "no option space named %s.", uname); + skip_to_semi (cfile); + return ISC_R_NOTFOUND; + } + } else { + /* Use the default hash table, which contains all the + standard dhcp option names. */ + val = uname; + universe = &dhcp_universe; + } + + /* Look up the actual option info... */ + option_name_hash_lookup(opt, universe->name_hash, val, 0, MDL); + option = *opt; + + /* If we didn't get an option structure, it's an undefined option. */ + if (option) { + if (known) + *known = 1; + /* If the option name is of the form unknown-[decimal], use + * the trailing decimal value to find the option definition. + * If there is no definition, construct one. This is to + * support legacy use of unknown options in config files or + * lease databases. + */ + } else if (strncasecmp(val, "unknown-", 8) == 0) { + code = atoi(val+8); + + /* Option code 0 is always illegal for us, thanks + * to the option decoder. + */ + if (code == 0 || code == universe->end) { + parse_warn(cfile, "Option codes 0 and %u are illegal " + "in the %s space.", universe->end, + universe->name); + skip_to_semi(cfile); + dfree(uname, MDL); + return ISC_R_FAILURE; + } + + /* It's odd to think of unknown option codes as + * being known, but this means we know what the + * parsed name is talking about. + */ + if (known) + *known = 1; + + option_code_hash_lookup(opt, universe->code_hash, + &code, 0, MDL); + option = *opt; + + /* If we did not find an option of that code, + * manufacture an unknown-xxx option definition. + * Its single reference will ensure that it is + * deleted once the option is recycled out of + * existence (by the parent). + */ + if (option == NULL) { + option = new_option(val, MDL); + option->universe = universe; + option->code = code; + option->format = default_option_format; + option_reference(opt, option, MDL); + } else + log_info("option %s has been redefined as option %s. " + "Please update your configs if neccessary.", + val, option->name); + /* If we've been told to allocate, that means that this + * (might) be an option code definition, so we'll create + * an option structure and return it for the parent to + * decide. + */ + } else if (allocate) { + option = new_option(val, MDL); + option -> universe = universe; + option_reference(opt, option, MDL); + } else { + parse_warn(cfile, "no option named %s in space %s", + val, universe->name); + skip_to_semi (cfile); + dfree(uname, MDL); + return ISC_R_NOTFOUND; + } + + /* Free the initial identifier token. */ + dfree (uname, MDL); + return ISC_R_SUCCESS; +} + +/* IDENTIFIER [WIDTHS] SEMI + * WIDTHS ~= LENGTH WIDTH NUMBER + * CODE WIDTH NUMBER + */ + +void parse_option_space_decl (cfile) + struct parse *cfile; +{ + int token; + const char *val; + struct universe **ua, *nu; + char *nu_name; + int tsize=1, lsize=1, hsize = 0; + + skip_token(&val, (unsigned *)0, cfile); /* Discard the SPACE token, + which was checked by the + caller. */ + token = next_token (&val, (unsigned *)0, cfile); + if (!is_identifier (token)) { + parse_warn (cfile, "expecting identifier."); + skip_to_semi (cfile); + return; + } + nu = new_universe (MDL); + if (!nu) + log_fatal ("No memory for new option space."); + + /* Set up the server option universe... */ + nu_name = dmalloc (strlen (val) + 1, MDL); + if (!nu_name) + log_fatal ("No memory for new option space name."); + strcpy (nu_name, val); + nu -> name = nu_name; + + do { + token = next_token(&val, NULL, cfile); + switch(token) { + case SEMI: + break; + + case CODE: + token = next_token(&val, NULL, cfile); + if (token != WIDTH) { + parse_warn(cfile, "expecting width token."); + goto bad; + } + + token = next_token(&val, NULL, cfile); + if (token != NUMBER) { + parse_warn(cfile, "expecting number 1, 2, 4."); + goto bad; + } + + tsize = atoi(val); + + + switch (tsize) { + case 1: + if (!hsize) + hsize = BYTE_NAME_HASH_SIZE; + break; + case 2: + if (!hsize) + hsize = WORD_NAME_HASH_SIZE; + break; + case 4: + if (!hsize) + hsize = QUAD_NAME_HASH_SIZE; + break; + default: + parse_warn(cfile, "invalid code width (%d), " + "expecting a 1, 2 or 4.", + tsize); + goto bad; + } + break; + + case LENGTH: + token = next_token(&val, NULL, cfile); + if (token != WIDTH) { + parse_warn(cfile, "expecting width token."); + goto bad; + } + + token = next_token(&val, NULL, cfile); + if (token != NUMBER) { + parse_warn(cfile, "expecting number 1 or 2."); + goto bad; + } + + lsize = atoi(val); + if (lsize != 1 && lsize != 2) { + parse_warn(cfile, "invalid length width (%d) " + "expecting 1 or 2.", lsize); + goto bad; + } + + break; + + case HASH: + token = next_token(&val, NULL, cfile); + if (token != SIZE) { + parse_warn(cfile, "expecting size token."); + goto bad; + } + + token = next_token(&val, NULL, cfile); + if (token != NUMBER) { + parse_warn(cfile, "expecting a 10base number"); + goto bad; + } + + /* (2^31)-1 is the highest Mersenne prime we should + * probably allow... + */ + hsize = atoi(val); + if (hsize < 0 || hsize > 0x7FFFFFFF) { + parse_warn(cfile, "invalid hash length: %d", + hsize); + goto bad; + } + + break; + + default: + parse_warn(cfile, "Unexpected token."); + } + } while (token != SEMI); + + if (!hsize) + hsize = DEFAULT_SPACE_HASH_SIZE; + + nu -> lookup_func = lookup_hashed_option; + nu -> option_state_dereference = hashed_option_state_dereference; + nu -> foreach = hashed_option_space_foreach; + nu -> save_func = save_hashed_option; + nu -> delete_func = delete_hashed_option; + nu -> encapsulate = hashed_option_space_encapsulate; + nu -> decode = parse_option_buffer; + nu -> length_size = lsize; + nu -> tag_size = tsize; + switch(tsize) { + case 1: + nu->get_tag = getUChar; + nu->store_tag = putUChar; + break; + case 2: + nu->get_tag = getUShort; + nu->store_tag = putUShort; + break; + case 4: + nu->get_tag = getULong; + nu->store_tag = putULong; + break; + default: + log_fatal("Impossible condition at %s:%d.", MDL); + } + switch(lsize) { + case 0: + nu->get_length = NULL; + nu->store_length = NULL; + break; + case 1: + nu->get_length = getUChar; + nu->store_length = putUChar; + break; + case 2: + nu->get_length = getUShort; + nu->store_length = putUShort; + break; + default: + log_fatal("Impossible condition at %s:%d.", MDL); + } + nu -> index = universe_count++; + if (nu -> index >= universe_max) { + ua = dmalloc (universe_max * 2 * sizeof *ua, MDL); + if (!ua) + log_fatal ("No memory to expand option space array."); + memcpy (ua, universes, universe_max * sizeof *ua); + universe_max *= 2; + dfree (universes, MDL); + universes = ua; + } + universes [nu -> index] = nu; + if (!option_name_new_hash(&nu->name_hash, hsize, MDL) || + !option_code_new_hash(&nu->code_hash, hsize, MDL)) + log_fatal("Can't allocate %s option hash table.", nu->name); + universe_hash_add (universe_hash, nu -> name, 0, nu, MDL); + return; + + bad: + dfree(nu_name, MDL); + dfree(nu, MDL); +} + +/* This is faked up to look good right now. Ideally, this should do a + recursive parse and allow arbitrary data structure definitions, but for + now it just allows you to specify a single type, an array of single types, + a sequence of types, or an array of sequences of types. + + ocd :== NUMBER EQUALS ocsd SEMI + + ocsd :== ocsd_type | + ocsd_type_sequence | + ARRAY OF ocsd_simple_type_sequence + + ocsd_type_sequence :== LBRACE ocsd_types RBRACE + + ocsd_simple_type_sequence :== LBRACE ocsd_simple_types RBRACE + + ocsd_types :== ocsd_type | + ocsd_types ocsd_type + + ocsd_type :== ocsd_simple_type | + ARRAY OF ocsd_simple_type + + ocsd_simple_types :== ocsd_simple_type | + ocsd_simple_types ocsd_simple_type + + ocsd_simple_type :== BOOLEAN | + INTEGER NUMBER | + SIGNED INTEGER NUMBER | + UNSIGNED INTEGER NUMBER | + IP-ADDRESS | + TEXT | + STRING | + ENCAPSULATE identifier */ + +int parse_option_code_definition (cfile, option) + struct parse *cfile; + struct option *option; +{ + const char *val; + enum dhcp_token token; + struct option *oldopt; + unsigned arrayp = 0; + int recordp = 0; + int no_more_in_record = 0; + char tokbuf [128]; + unsigned tokix = 0; + char type; + int is_signed; + char *s; + int has_encapsulation = 0; + struct universe *encapsulated; + + /* Parse the option code. */ + token = next_token (&val, (unsigned *)0, cfile); + if (token != NUMBER) { + parse_warn (cfile, "expecting option code number."); + skip_to_semi (cfile); + return 0; + } + option -> code = atoi (val); + + token = next_token (&val, (unsigned *)0, cfile); + if (token != EQUAL) { + parse_warn (cfile, "expecting \"=\""); + skip_to_semi (cfile); + return 0; + } + + /* See if this is an array. */ + token = next_token (&val, (unsigned *)0, cfile); + if (token == ARRAY) { + token = next_token (&val, (unsigned *)0, cfile); + if (token != OF) { + parse_warn (cfile, "expecting \"of\"."); + skip_to_semi (cfile); + return 0; + } + arrayp = 1; + token = next_token (&val, (unsigned *)0, cfile); + } + + if (token == LBRACE) { + recordp = 1; + token = next_token (&val, (unsigned *)0, cfile); + } + + /* At this point we're expecting a data type. */ + next_type: + if (has_encapsulation) { + parse_warn (cfile, + "encapsulate must always be the last item."); + skip_to_semi (cfile); + return 0; + } + + switch (token) { + case ARRAY: + if (arrayp) { + parse_warn (cfile, "no nested arrays."); + skip_to_rbrace (cfile, recordp); + if (recordp) + skip_to_semi (cfile); + return 0; + } + token = next_token (&val, (unsigned *)0, cfile); + if (token != OF) { + parse_warn (cfile, "expecting \"of\"."); + skip_to_semi (cfile); + return 0; + } + arrayp = recordp + 1; + token = next_token (&val, (unsigned *)0, cfile); + if ((recordp) && (token == LBRACE)) { + parse_warn (cfile, + "only uniform array inside record."); + skip_to_rbrace (cfile, recordp + 1); + skip_to_semi (cfile); + return 0; + } + goto next_type; + case BOOLEAN: + type = 'f'; + break; + case INTEGER: + is_signed = 1; + parse_integer: + token = next_token (&val, (unsigned *)0, cfile); + if (token != NUMBER) { + parse_warn (cfile, "expecting number."); + skip_to_rbrace (cfile, recordp); + if (recordp) + skip_to_semi (cfile); + return 0; + } + switch (atoi (val)) { + case 8: + type = is_signed ? 'b' : 'B'; + break; + case 16: + type = is_signed ? 's' : 'S'; + break; + case 32: + type = is_signed ? 'l' : 'L'; + break; + default: + parse_warn (cfile, + "%s bit precision is not supported.", val); + skip_to_rbrace (cfile, recordp); + if (recordp) + skip_to_semi (cfile); + return 0; + } + break; + case SIGNED: + is_signed = 1; + parse_signed: + token = next_token (&val, (unsigned *)0, cfile); + if (token != INTEGER) { + parse_warn (cfile, "expecting \"integer\" keyword."); + skip_to_rbrace (cfile, recordp); + if (recordp) + skip_to_semi (cfile); + return 0; + } + goto parse_integer; + case UNSIGNED: + is_signed = 0; + goto parse_signed; + + case IP_ADDRESS: + type = 'I'; + break; + case IP6_ADDRESS: + type = '6'; + break; + case DOMAIN_NAME: + type = 'd'; + goto no_arrays; + case DOMAIN_LIST: + /* Consume optional compression indicator. */ + token = peek_token(&val, NULL, cfile); + if (token == COMPRESSED) { + skip_token(&val, NULL, cfile); + tokbuf[tokix++] = 'D'; + type = 'c'; + } else + type = 'D'; + goto no_arrays; + case TEXT: + type = 't'; + no_arrays: + if (arrayp) { + parse_warn (cfile, "arrays of text strings not %s", + "yet supported."); + skip_to_rbrace (cfile, recordp); + if (recordp) + skip_to_semi (cfile); + return 0; + } + no_more_in_record = 1; + break; + case STRING_TOKEN: + type = 'X'; + goto no_arrays; + + case ENCAPSULATE: + token = next_token (&val, (unsigned *)0, cfile); + if (!is_identifier (token)) { + parse_warn (cfile, + "expecting option space identifier"); + skip_to_semi (cfile); + return 0; + } + encapsulated = NULL; + if (!universe_hash_lookup(&encapsulated, universe_hash, + val, strlen(val), MDL)) { + parse_warn(cfile, "unknown option space %s", val); + skip_to_semi (cfile); + return 0; + } + if (strlen (val) + tokix + 2 > sizeof (tokbuf)) + goto toobig; + tokbuf [tokix++] = 'E'; + strcpy (&tokbuf [tokix], val); + tokix += strlen (val); + type = '.'; + has_encapsulation = 1; + break; + + case ZEROLEN: + type = 'Z'; + if (arrayp) { + parse_warn (cfile, "array incompatible with zerolen."); + skip_to_rbrace (cfile, recordp); + if (recordp) + skip_to_semi (cfile); + return 0; + } + no_more_in_record = 1; + break; + + default: + parse_warn (cfile, "unknown data type %s", val); + skip_to_rbrace (cfile, recordp); + if (recordp) + skip_to_semi (cfile); + return 0; + } + + if (tokix == sizeof tokbuf) { + toobig: + parse_warn (cfile, "too many types in record."); + skip_to_rbrace (cfile, recordp); + if (recordp) + skip_to_semi (cfile); + return 0; + } + tokbuf [tokix++] = type; + + if (recordp) { + token = next_token (&val, (unsigned *)0, cfile); + if (arrayp > recordp) { + if (tokix == sizeof tokbuf) { + parse_warn (cfile, + "too many types in record."); + skip_to_rbrace (cfile, 1); + skip_to_semi (cfile); + return 0; + } + arrayp = 0; + tokbuf[tokix++] = 'a'; + } + if (token == COMMA) { + if (no_more_in_record) { + parse_warn (cfile, + "%s must be at end of record.", + type == 't' ? "text" : "string"); + skip_to_rbrace (cfile, 1); + if (recordp) + skip_to_semi (cfile); + return 0; + } + token = next_token (&val, (unsigned *)0, cfile); + goto next_type; + } + if (token != RBRACE) { + parse_warn (cfile, "expecting right brace."); + skip_to_rbrace (cfile, 1); + if (recordp) + skip_to_semi (cfile); + return 0; + } + } + if (!parse_semi (cfile)) { + parse_warn (cfile, "semicolon expected."); + skip_to_semi (cfile); + if (recordp) + skip_to_semi (cfile); + return 0; + } + if (has_encapsulation && arrayp) { + parse_warn (cfile, + "Arrays of encapsulations don't make sense."); + return 0; + } + s = dmalloc(tokix + (arrayp ? 1 : 0) + 1, MDL); + if (s == NULL) { + log_fatal("no memory for option format."); + } + memcpy(s, tokbuf, tokix); + if (arrayp) { + s[tokix++] = (arrayp > recordp) ? 'a' : 'A'; + } + s[tokix] = '\0'; + + option -> format = s; + + oldopt = NULL; + option_code_hash_lookup(&oldopt, option->universe->code_hash, + &option->code, 0, MDL); + if (oldopt != NULL) { + /* + * XXX: This illegalizes a configuration syntax that was + * valid in 3.0.x, where multiple name->code mappings are + * given, but only one code->name mapping survives. It is + * unclear what can or should be done at this point, but it + * seems best to retain 3.0.x behaviour for upgrades to go + * smoothly. + * + option_name_hash_delete(option->universe->name_hash, + oldopt->name, 0, MDL); + */ + option_code_hash_delete(option->universe->code_hash, + &oldopt->code, 0, MDL); + + option_dereference(&oldopt, MDL); + } + option_code_hash_add(option->universe->code_hash, &option->code, 0, + option, MDL); + option_name_hash_add(option->universe->name_hash, option->name, 0, + option, MDL); + if (has_encapsulation) { + /* INSIST(tokbuf[0] == 'E'); */ + /* INSIST(encapsulated != NULL); */ + if (!option_code_hash_lookup(&encapsulated->enc_opt, + option->universe->code_hash, + &option->code, 0, MDL)) { + log_fatal("error finding encapsulated option (%s:%d)", + MDL); + } + } + return 1; +} + +/* + * base64 :== NUMBER_OR_STRING + */ + +int parse_base64 (data, cfile) + struct data_string *data; + struct parse *cfile; +{ + const char *val; + int i, j, k; + unsigned acc = 0; + static unsigned char + from64 [] = {64, 64, 64, 64, 64, 64, 64, 64, /* \"#$%&' */ + 64, 64, 64, 62, 64, 64, 64, 63, /* ()*+,-./ */ + 52, 53, 54, 55, 56, 57, 58, 59, /* 01234567 */ + 60, 61, 64, 64, 64, 64, 64, 64, /* 89:;<=>? */ + 64, 0, 1, 2, 3, 4, 5, 6, /* @ABCDEFG */ + 7, 8, 9, 10, 11, 12, 13, 14, /* HIJKLMNO */ + 15, 16, 17, 18, 19, 20, 21, 22, /* PQRSTUVW */ + 23, 24, 25, 64, 64, 64, 64, 64, /* XYZ[\]^_ */ + 64, 26, 27, 28, 29, 30, 31, 32, /* 'abcdefg */ + 33, 34, 35, 36, 37, 38, 39, 40, /* hijklmno */ + 41, 42, 43, 44, 45, 46, 47, 48, /* pqrstuvw */ + 49, 50, 51, 64, 64, 64, 64, 64}; /* xyz{|}~ */ + struct string_list *bufs = NULL, + *last = NULL, + *t; + int cc = 0; + int terminated = 0; + int valid_base64; + + /* It's possible for a + or a / to cause a base64 quantity to be + tokenized into more than one token, so we have to parse them all + in before decoding. */ + do { + unsigned l; + + (void)next_token(&val, &l, cfile); + t = dmalloc(l + sizeof(*t), MDL); + if (t == NULL) + log_fatal("no memory for base64 buffer."); + memset(t, 0, (sizeof(*t)) - 1); + memcpy(t->string, val, l + 1); + cc += l; + if (last) + last->next = t; + else + bufs = t; + last = t; + (void)peek_token(&val, NULL, cfile); + valid_base64 = 1; + for (i = 0; val[i]; i++) { + /* Check to see if the character is valid. It + may be out of range or within the right range + but not used in the mapping */ + if (((val[i] < ' ') || (val[i] > 'z')) || + ((from64[val[i] - ' '] > 63) && (val[i] != '='))) { + valid_base64 = 0; + break; /* no need to continue for loop */ + } + } + } while (valid_base64); + + data->len = cc; + data->len = (data->len * 3) / 4; + if (!buffer_allocate(&data->buffer, data->len, MDL)) { + parse_warn (cfile, "can't allocate buffer for base64 data."); + data->len = 0; + data->data = NULL; + goto out; + } + + j = k = 0; + for (t = bufs; t; t = t->next) { + for (i = 0; t->string[i]; i++) { + unsigned foo = t->string[i]; + if (terminated && foo != '=') { + parse_warn(cfile, + "stuff after base64 '=' terminator: %s.", + &t->string[i]); + goto bad; + } + if ((foo < ' ') || (foo > 'z')) { + bad64: + parse_warn(cfile, + "invalid base64 character %d.", + t->string[i]); + bad: + data_string_forget(data, MDL); + goto out; + } + if (foo == '=') + terminated = 1; + else { + foo = from64[foo - ' ']; + if (foo == 64) + goto bad64; + acc = (acc << 6) + foo; + switch (k % 4) { + case 0: + break; + case 1: + data->buffer->data[j++] = (acc >> 4); + acc = acc & 0x0f; + break; + + case 2: + data->buffer->data[j++] = (acc >> 2); + acc = acc & 0x03; + break; + case 3: + data->buffer->data[j++] = acc; + acc = 0; + break; + } + } + k++; + } + } + if (k % 4) { + if (acc) { + parse_warn(cfile, + "partial base64 value left over: %d.", + acc); + } + } + data->len = j; + data->data = data->buffer->data; + out: + for (t = bufs; t; t = last) { + last = t->next; + dfree(t, MDL); + } + if (data->len) + return 1; + else + return 0; +} + + +/* + * colon-separated-hex-list :== NUMBER | + * NUMBER COLON colon-separated-hex-list + */ + +int parse_cshl (data, cfile) + struct data_string *data; + struct parse *cfile; +{ + u_int8_t ibuf [128]; + unsigned ilen = 0; + unsigned tlen = 0; + struct option_tag *sl = (struct option_tag *)0; + struct option_tag *next, **last = &sl; + enum dhcp_token token; + const char *val; + unsigned char *rvp; + + do { + token = next_token (&val, (unsigned *)0, cfile); + if (token != NUMBER && token != NUMBER_OR_NAME) { + parse_warn (cfile, "expecting hexadecimal number."); + skip_to_semi (cfile); + for (; sl; sl = next) { + next = sl -> next; + dfree (sl, MDL); + } + return 0; + } + if (ilen == sizeof ibuf) { + next = (struct option_tag *) + dmalloc (ilen - 1 + + sizeof (struct option_tag), MDL); + if (!next) + log_fatal ("no memory for string list."); + memcpy (next -> data, ibuf, ilen); + *last = next; + last = &next -> next; + tlen += ilen; + ilen = 0; + } + convert_num (cfile, &ibuf [ilen++], val, 16, 8); + + token = peek_token (&val, (unsigned *)0, cfile); + if (token != COLON) + break; + skip_token(&val, (unsigned *)0, cfile); + } while (1); + + if (!buffer_allocate (&data -> buffer, tlen + ilen, MDL)) + log_fatal ("no memory to store octet data."); + data -> data = &data -> buffer -> data [0]; + data -> len = tlen + ilen; + data -> terminated = 0; + + rvp = &data -> buffer -> data [0]; + while (sl) { + next = sl -> next; + memcpy (rvp, sl -> data, sizeof ibuf); + rvp += sizeof ibuf; + dfree (sl, MDL); + sl = next; + } + + memcpy (rvp, ibuf, ilen); + return 1; +} + +/* + * executable-statements :== executable-statement executable-statements | + * executable-statement + * + * executable-statement :== + * IF if-statement | + * ADD class-name SEMI | + * BREAK SEMI | + * OPTION option-parameter SEMI | + * SUPERSEDE option-parameter SEMI | + * PREPEND option-parameter SEMI | + * APPEND option-parameter SEMI + */ + +int parse_executable_statements (statements, cfile, lose, case_context) + struct executable_statement **statements; + struct parse *cfile; + int *lose; + enum expression_context case_context; +{ + struct executable_statement **next; + + next = statements; + while (parse_executable_statement (next, cfile, lose, case_context)) + next = &((*next) -> next); + if (!*lose) + return 1; + return 0; +} + +int parse_executable_statement (result, cfile, lose, case_context) + struct executable_statement **result; + struct parse *cfile; + int *lose; + enum expression_context case_context; +{ +#if defined(ENABLE_EXECUTE) + unsigned len; + struct expression **ep; +#endif + enum dhcp_token token; + const char *val; + struct class *cta; + struct option *option=NULL; + struct option_cache *cache; + int known; + int flag; + int i; + struct dns_zone *zone; + isc_result_t status; + char *s; + + token = peek_token (&val, (unsigned *)0, cfile); + switch (token) { + case DB_TIME_FORMAT: + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token == DEFAULT) { + db_time_format = DEFAULT_TIME_FORMAT; + } else if (token == LOCAL) { + db_time_format = LOCAL_TIME_FORMAT; + } else { + parse_warn(cfile, "Expecting 'local' or 'default'."); + if (token != SEMI) + skip_to_semi(cfile); + *lose = 1; + return 0; + } + + token = next_token(&val, NULL, cfile); + if (token != SEMI) { + parse_warn(cfile, "Expecting a semicolon."); + *lose = 1; + return 0; + } + + /* We're done here. */ + return 1; + + case IF: + skip_token(&val, (unsigned *)0, cfile); + return parse_if_statement (result, cfile, lose); + + case TOKEN_ADD: + skip_token(&val, (unsigned *)0, cfile); + token = next_token (&val, (unsigned *)0, cfile); + if (token != STRING) { + parse_warn (cfile, "expecting class name."); + skip_to_semi (cfile); + *lose = 1; + return 0; + } + cta = (struct class *)0; + status = find_class (&cta, val, MDL); + if (status != ISC_R_SUCCESS) { + parse_warn (cfile, "class %s: %s", + val, isc_result_totext (status)); + skip_to_semi (cfile); + *lose = 1; + return 0; + } + if (!parse_semi (cfile)) { + *lose = 1; + return 0; + } + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for new statement."); + (*result) -> op = add_statement; + (*result) -> data.add = cta; + break; + + case BREAK: + skip_token(&val, (unsigned *)0, cfile); + if (!parse_semi (cfile)) { + *lose = 1; + return 0; + } + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for new statement."); + (*result) -> op = break_statement; + break; + + case SEND: + skip_token(&val, (unsigned *)0, cfile); + known = 0; + status = parse_option_name (cfile, 0, &known, &option); + if (status != ISC_R_SUCCESS || option == NULL) { + *lose = 1; + return 0; + } + status = parse_option_statement(result, cfile, 1, option, + send_option_statement); + option_dereference(&option, MDL); + return status; + + case SUPERSEDE: + case OPTION: + skip_token(&val, (unsigned *)0, cfile); + known = 0; + status = parse_option_name (cfile, 0, &known, &option); + if (status != ISC_R_SUCCESS || option == NULL) { + *lose = 1; + return 0; + } + status = parse_option_statement(result, cfile, 1, option, + supersede_option_statement); + option_dereference(&option, MDL); + return status; + + case ALLOW: + flag = 1; + goto pad; + case DENY: + flag = 0; + goto pad; + case IGNORE: + flag = 2; + pad: + skip_token(&val, (unsigned *)0, cfile); + cache = (struct option_cache *)0; + if (!parse_allow_deny (&cache, cfile, flag)) + return 0; + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for new statement."); + (*result) -> op = supersede_option_statement; + (*result) -> data.option = cache; + break; + + case DEFAULT: + skip_token(&val, (unsigned *)0, cfile); + token = peek_token (&val, (unsigned *)0, cfile); + if (token == COLON) + goto switch_default; + known = 0; + status = parse_option_name (cfile, 0, &known, &option); + if (status != ISC_R_SUCCESS || option == NULL) { + *lose = 1; + return 0; + } + status = parse_option_statement(result, cfile, 1, option, + default_option_statement); + option_dereference(&option, MDL); + return status; + + case PREPEND: + skip_token(&val, (unsigned *)0, cfile); + known = 0; + status = parse_option_name (cfile, 0, &known, &option); + if (status != ISC_R_SUCCESS || option == NULL) { + *lose = 1; + return 0; + } + status = parse_option_statement(result, cfile, 1, option, + prepend_option_statement); + option_dereference(&option, MDL); + return status; + + case APPEND: + skip_token(&val, (unsigned *)0, cfile); + known = 0; + status = parse_option_name (cfile, 0, &known, &option); + if (status != ISC_R_SUCCESS || option == NULL) { + *lose = 1; + return 0; + } + status = parse_option_statement(result, cfile, 1, option, + append_option_statement); + option_dereference(&option, MDL); + return status; + + case ON: + skip_token(&val, (unsigned *)0, cfile); + return parse_on_statement (result, cfile, lose); + + case SWITCH: + skip_token(&val, (unsigned *)0, cfile); + return parse_switch_statement (result, cfile, lose); + + case CASE: + skip_token(&val, (unsigned *)0, cfile); + if (case_context == context_any) { + parse_warn (cfile, + "case statement in inappropriate scope."); + *lose = 1; + skip_to_semi (cfile); + return 0; + } + return parse_case_statement (result, + cfile, lose, case_context); + + switch_default: + skip_token(&val, (unsigned *)0, cfile); + if (case_context == context_any) { + parse_warn (cfile, "switch default statement in %s", + "inappropriate scope."); + + *lose = 1; + return 0; + } else { + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for default statement."); + (*result) -> op = default_statement; + return 1; + } + + case DEFINE: + case TOKEN_SET: + skip_token(&val, (unsigned *)0, cfile); + if (token == DEFINE) + flag = 1; + else + flag = 0; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != NAME && token != NUMBER_OR_NAME) { + parse_warn (cfile, + "%s can't be a variable name", val); + badset: + skip_to_semi (cfile); + *lose = 1; + return 0; + } + + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for set statement."); + (*result) -> op = flag ? define_statement : set_statement; + (*result) -> data.set.name = dmalloc (strlen (val) + 1, MDL); + if (!(*result)->data.set.name) + log_fatal ("can't allocate variable name"); + strcpy ((*result) -> data.set.name, val); + token = next_token (&val, (unsigned *)0, cfile); + + if (token == LPAREN) { + struct string_list *head, *cur, *new; + struct expression *expr; + head = cur = (struct string_list *)0; + do { + token = next_token (&val, + (unsigned *)0, cfile); + if (token == RPAREN) + break; + if (token != NAME && token != NUMBER_OR_NAME) { + parse_warn (cfile, + "expecting argument name"); + skip_to_rbrace (cfile, 0); + *lose = 1; + executable_statement_dereference + (result, MDL); + return 0; + } + new = ((struct string_list *) + dmalloc (sizeof (struct string_list) + + strlen (val), MDL)); + if (!new) + log_fatal ("can't allocate string."); + memset (new, 0, sizeof *new); + strcpy (new -> string, val); + if (cur) { + cur -> next = new; + cur = new; + } else { + head = cur = new; + } + token = next_token (&val, + (unsigned *)0, cfile); + } while (token == COMMA); + + if (token != RPAREN) { + parse_warn (cfile, "expecting right paren."); + badx: + skip_to_semi (cfile); + *lose = 1; + executable_statement_dereference (result, MDL); + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LBRACE) { + parse_warn (cfile, "expecting left brace."); + goto badx; + } + + expr = (struct expression *)0; + if (!(expression_allocate (&expr, MDL))) + log_fatal ("can't allocate expression."); + expr -> op = expr_function; + if (!fundef_allocate (&expr -> data.func, MDL)) + log_fatal ("can't allocate fundef."); + expr -> data.func -> args = head; + (*result) -> data.set.expr = expr; + + if (!(parse_executable_statements + (&expr -> data.func -> statements, cfile, lose, + case_context))) { + if (*lose) + goto badx; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != RBRACE) { + parse_warn (cfile, "expecting rigt brace."); + goto badx; + } + } else { + if (token != EQUAL) { + parse_warn (cfile, + "expecting '=' in %s statement.", + flag ? "define" : "set"); + goto badset; + } + + if (!parse_expression (&(*result) -> data.set.expr, + cfile, lose, context_any, + (struct expression **)0, + expr_none)) { + if (!*lose) + parse_warn (cfile, + "expecting expression."); + else + *lose = 1; + skip_to_semi (cfile); + executable_statement_dereference (result, MDL); + return 0; + } + if (!parse_semi (cfile)) { + *lose = 1; + executable_statement_dereference (result, MDL); + return 0; + } + } + break; + + case UNSET: + skip_token(&val, (unsigned *)0, cfile); + token = next_token (&val, (unsigned *)0, cfile); + if (token != NAME && token != NUMBER_OR_NAME) { + parse_warn (cfile, + "%s can't be a variable name", val); + skip_to_semi (cfile); + *lose = 1; + return 0; + } + + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for set statement."); + (*result) -> op = unset_statement; + (*result) -> data.unset = dmalloc (strlen (val) + 1, MDL); + if (!(*result)->data.unset) + log_fatal ("can't allocate variable name"); + strcpy ((*result) -> data.unset, val); + if (!parse_semi (cfile)) { + *lose = 1; + executable_statement_dereference (result, MDL); + return 0; + } + break; + + case EVAL: + skip_token(&val, (unsigned *)0, cfile); + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for eval statement."); + (*result) -> op = eval_statement; + + if (!parse_expression (&(*result) -> data.eval, + cfile, lose, context_data, /* XXX */ + (struct expression **)0, expr_none)) { + if (!*lose) + parse_warn (cfile, + "expecting data expression."); + else + *lose = 1; + skip_to_semi (cfile); + executable_statement_dereference (result, MDL); + return 0; + } + if (!parse_semi (cfile)) { + *lose = 1; + executable_statement_dereference (result, MDL); + } + break; + + case EXECUTE: +#ifdef ENABLE_EXECUTE + skip_token(&val, NULL, cfile); + + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for execute statement."); + (*result)->op = execute_statement; + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) { + parse_warn(cfile, "left parenthesis expected."); + skip_to_semi(cfile); + *lose = 1; + return 0; + } + + token = next_token(&val, &len, cfile); + if (token != STRING) { + parse_warn(cfile, "Expecting a quoted string."); + skip_to_semi(cfile); + *lose = 1; + return 0; + } + + (*result)->data.execute.command = dmalloc(len + 1, MDL); + if ((*result)->data.execute.command == NULL) + log_fatal("can't allocate command name"); + strcpy((*result)->data.execute.command, val); + + ep = &(*result)->data.execute.arglist; + (*result)->data.execute.argc = 0; + + while((token = next_token(&val, NULL, cfile)) == COMMA) { + if (!expression_allocate(ep, MDL)) + log_fatal ("can't allocate expression"); + + if (!parse_data_expression (&(*ep) -> data.arg.val, + cfile, lose)) { + if (!*lose) { + parse_warn (cfile, + "expecting expression."); + *lose = 1; + } + skip_to_semi(cfile); + *lose = 1; + return 0; + } + ep = &(*ep)->data.arg.next; + (*result)->data.execute.argc++; + } + + if (token != RPAREN) { + parse_warn(cfile, "right parenthesis expected."); + skip_to_semi(cfile); + *lose = 1; + return 0; + } + + if (!parse_semi (cfile)) { + *lose = 1; + executable_statement_dereference (result, MDL); + } +#else /* ! ENABLE_EXECUTE */ + parse_warn(cfile, "define ENABLE_EXECUTE in site.h to " + "enable execute(); expressions."); + skip_to_semi(cfile); + *lose = 1; + return 0; +#endif /* ENABLE_EXECUTE */ + break; + + case RETURN: + skip_token(&val, (unsigned *)0, cfile); + + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for return statement."); + (*result) -> op = return_statement; + + if (!parse_expression (&(*result) -> data.retval, + cfile, lose, context_data, + (struct expression **)0, expr_none)) { + if (!*lose) + parse_warn (cfile, + "expecting data expression."); + else + *lose = 1; + skip_to_semi (cfile); + executable_statement_dereference (result, MDL); + return 0; + } + if (!parse_semi (cfile)) { + *lose = 1; + executable_statement_dereference (result, MDL); + return 0; + } + break; + + case LOG: + skip_token(&val, (unsigned *)0, cfile); + + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for log statement."); + (*result) -> op = log_statement; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) { + parse_warn (cfile, "left parenthesis expected."); + skip_to_semi (cfile); + *lose = 1; + return 0; + } + + token = peek_token (&val, (unsigned *)0, cfile); + i = 1; + if (token == FATAL) { + (*result) -> data.log.priority = log_priority_fatal; + } else if (token == ERROR) { + (*result) -> data.log.priority = log_priority_error; + } else if (token == TOKEN_DEBUG) { + (*result) -> data.log.priority = log_priority_debug; + } else if (token == INFO) { + (*result) -> data.log.priority = log_priority_info; + } else { + (*result) -> data.log.priority = log_priority_debug; + i = 0; + } + if (i) { + skip_token(&val, (unsigned *)0, cfile); + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) { + parse_warn (cfile, "comma expected."); + skip_to_semi (cfile); + *lose = 1; + return 0; + } + } + + if (!(parse_data_expression + (&(*result) -> data.log.expr, cfile, lose))) { + skip_to_semi (cfile); + *lose = 1; + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != RPAREN) { + parse_warn (cfile, "right parenthesis expected."); + skip_to_semi (cfile); + *lose = 1; + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != SEMI) { + parse_warn (cfile, "semicolon expected."); + skip_to_semi (cfile); + *lose = 1; + return 0; + } + break; + + /* Not really a statement, but we parse it here anyway + because it's appropriate for all DHCP agents with + parsers. */ + case ZONE: + skip_token(&val, (unsigned *)0, cfile); + zone = (struct dns_zone *)0; + if (!dns_zone_allocate (&zone, MDL)) + log_fatal ("no memory for new zone."); + zone -> name = parse_host_name (cfile); + if (!zone -> name) { + parse_warn (cfile, "expecting hostname."); + badzone: + *lose = 1; + skip_to_semi (cfile); + dns_zone_dereference (&zone, MDL); + return 0; + } + i = strlen (zone -> name); + if (zone -> name [i - 1] != '.') { + s = dmalloc ((unsigned)i + 2, MDL); + if (!s) { + parse_warn (cfile, "no trailing '.' on zone"); + goto badzone; + } + strcpy (s, zone -> name); + s [i] = '.'; + s [i + 1] = 0; + dfree (zone -> name, MDL); + zone -> name = s; + } + if (!parse_zone (zone, cfile)) + goto badzone; + status = enter_dns_zone (zone); + if (status != ISC_R_SUCCESS) { + parse_warn (cfile, "dns zone key %s: %s", + zone -> name, isc_result_totext (status)); + dns_zone_dereference (&zone, MDL); + return 0; + } + dns_zone_dereference (&zone, MDL); + return 1; + + /* Also not really a statement, but same idea as above. */ + case KEY: + skip_token(&val, (unsigned *)0, cfile); + if (!parse_key (cfile)) { + *lose = 1; + return 0; + } + return 1; + + default: + if (config_universe && is_identifier (token)) { + option = (struct option *)0; + option_name_hash_lookup(&option, + config_universe->name_hash, + val, 0, MDL); + if (option) { + skip_token(&val, (unsigned *)0, cfile); + status = parse_option_statement + (result, cfile, 1, option, + supersede_option_statement); + option_dereference(&option, MDL); + return status; + } + } + + if (token == NUMBER_OR_NAME || token == NAME) { + /* This is rather ugly. Since function calls are + data expressions, fake up an eval statement. */ + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for eval statement."); + (*result) -> op = eval_statement; + + if (!parse_expression (&(*result) -> data.eval, + cfile, lose, context_data, + (struct expression **)0, + expr_none)) { + if (!*lose) + parse_warn (cfile, "expecting " + "function call."); + else + *lose = 1; + skip_to_semi (cfile); + executable_statement_dereference (result, MDL); + return 0; + } + if (!parse_semi (cfile)) { + *lose = 1; + executable_statement_dereference (result, MDL); + return 0; + } + break; + } + + *lose = 0; + return 0; + } + + return 1; +} + +/* zone-statements :== zone-statement | + zone-statement zone-statements + zone-statement :== + PRIMARY ip-addresses SEMI | + SECONDARY ip-addresses SEMI | + PRIMARY6 ip-address6 SEMI | + SECONDARY6 ip-address6 SEMI | + key-reference SEMI + ip-addresses :== ip-addr-or-hostname | + ip-addr-or-hostname COMMA ip-addresses + key-reference :== KEY STRING | + KEY identifier */ + +int parse_zone (struct dns_zone *zone, struct parse *cfile) +{ + int token; + const char *val; + char *key_name; + struct option_cache *oc; + int done = 0; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LBRACE) { + parse_warn (cfile, "expecting left brace"); + return 0; + } + + do { + token = peek_token (&val, (unsigned *)0, cfile); + switch (token) { + case PRIMARY: + if (zone -> primary) { + parse_warn (cfile, + "more than one primary."); + skip_to_semi (cfile); + return 0; + } + if (!option_cache_allocate (&zone -> primary, MDL)) + log_fatal ("can't allocate primary option cache."); + oc = zone -> primary; + goto consemup; + + case SECONDARY: + if (zone -> secondary) { + parse_warn (cfile, "more than one secondary."); + skip_to_semi (cfile); + return 0; + } + if (!option_cache_allocate (&zone -> secondary, MDL)) + log_fatal ("can't allocate secondary."); + oc = zone -> secondary; + consemup: + skip_token(&val, (unsigned *)0, cfile); + do { + struct expression *expr = (struct expression *)0; + if (!parse_ip_addr_or_hostname (&expr, cfile, 0)) { + parse_warn (cfile, + "expecting IP addr or hostname."); + skip_to_semi (cfile); + return 0; + } + if (oc -> expression) { + struct expression *old = + (struct expression *)0; + expression_reference (&old, + oc -> expression, + MDL); + expression_dereference (&oc -> expression, + MDL); + if (!make_concat (&oc -> expression, + old, expr)) + log_fatal ("no memory for concat."); + expression_dereference (&expr, MDL); + expression_dereference (&old, MDL); + } else { + expression_reference (&oc -> expression, + expr, MDL); + expression_dereference (&expr, MDL); + } + token = next_token (&val, (unsigned *)0, cfile); + } while (token == COMMA); + if (token != SEMI) { + parse_warn (cfile, "expecting semicolon."); + skip_to_semi (cfile); + return 0; + } + break; + + case PRIMARY6: + if (zone->primary6) { + parse_warn(cfile, "more than one primary6."); + skip_to_semi(cfile); + return (0); + } + if (!option_cache_allocate (&zone->primary6, MDL)) + log_fatal("can't allocate primary6 option cache."); + oc = zone->primary6; + goto consemup6; + + case SECONDARY6: + if (zone->secondary6) { + parse_warn(cfile, "more than one secondary6."); + skip_to_semi(cfile); + return (0); + } + if (!option_cache_allocate (&zone->secondary6, MDL)) + log_fatal("can't allocate secondary6 " + "option cache."); + oc = zone->secondary6; + consemup6: + skip_token(&val, NULL, cfile); + do { + struct expression *expr = NULL; + if (parse_ip6_addr_expr(&expr, cfile) == 0) { + parse_warn(cfile, "expecting IPv6 addr."); + skip_to_semi(cfile); + return (0); + } + if (oc->expression) { + struct expression *old = NULL; + expression_reference(&old, oc->expression, + MDL); + expression_dereference(&oc->expression, + MDL); + if (!make_concat(&oc->expression, + old, expr)) + log_fatal("no memory for concat."); + expression_dereference(&expr, MDL); + expression_dereference(&old, MDL); + } else { + expression_reference(&oc->expression, + expr, MDL); + expression_dereference(&expr, MDL); + } + token = next_token(&val, NULL, cfile); + } while (token == COMMA); + if (token != SEMI) { + parse_warn(cfile, "expecting semicolon."); + skip_to_semi(cfile); + return (0); + } + break; + + case KEY: + skip_token(&val, NULL, cfile); + token = peek_token(&val, NULL, cfile); + if (token == STRING) { + skip_token(&val, NULL, cfile); + key_name = NULL; + } else { + key_name = parse_host_name(cfile); + if (!key_name) { + parse_warn(cfile, "expecting key name."); + skip_to_semi(cfile); + return (0); + } + val = key_name; + } + if (zone->key) { + log_fatal("Multiple key definitions for zone %s.", + zone->name); + } + if (omapi_auth_key_lookup_name(&zone->key, val) != + ISC_R_SUCCESS) + parse_warn(cfile, "unknown key %s", val); + if (key_name) + dfree(key_name, MDL); + if (!parse_semi(cfile)) + return (0); + break; + + default: + done = 1; + break; + } + } while (!done); + + token = next_token(&val, NULL, cfile); + if (token != RBRACE) { + parse_warn(cfile, "expecting right brace."); + return (0); + } + return (1); +} + +/* key-statements :== key-statement | + key-statement key-statements + key-statement :== + ALGORITHM host-name SEMI | + secret-definition SEMI + secret-definition :== SECRET base64val | + SECRET STRING */ + +int parse_key (struct parse *cfile) +{ + int token; + const char *val; + int done = 0; + struct auth_key *key; + struct data_string ds; + isc_result_t status; + char *s; + + key = (struct auth_key *)0; + if (omapi_auth_key_new (&key, MDL) != ISC_R_SUCCESS) + log_fatal ("no memory for key"); + + token = peek_token (&val, (unsigned *)0, cfile); + if (token == STRING) { + skip_token(&val, (unsigned *)0, cfile); + key -> name = dmalloc (strlen (val) + 1, MDL); + if (!key -> name) + log_fatal ("no memory for key name."); + strcpy (key -> name, val); + + } else { + key -> name = parse_host_name (cfile); + if (!key -> name) { + parse_warn (cfile, "expecting key name."); + skip_to_semi (cfile); + goto bad; + } + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LBRACE) { + parse_warn (cfile, "expecting left brace"); + goto bad; + } + + do { + token = next_token (&val, (unsigned *)0, cfile); + switch (token) { + case ALGORITHM: + if (key -> algorithm) { + parse_warn (cfile, + "key %s: too many algorithms", + key -> name); + goto rbad; + } + key -> algorithm = parse_host_name (cfile); + if (!key -> algorithm) { + parse_warn (cfile, + "expecting key algorithm name."); + goto rbad; + } + if (!parse_semi (cfile)) + goto rbad; + /* If the algorithm name isn't an FQDN, tack on + the .SIG-ALG.REG.NET. domain. */ + s = strrchr (key -> algorithm, '.'); + if (!s) { + static char add [] = ".SIG-ALG.REG.INT."; + s = dmalloc (strlen (key -> algorithm) + + sizeof (add), MDL); + if (!s) { + log_error ("no memory for key %s.", + "algorithm"); + goto rbad; + } + strcpy (s, key -> algorithm); + strcat (s, add); + dfree (key -> algorithm, MDL); + key -> algorithm = s; + } else if (s [1]) { + /* If there is no trailing '.', hack one in. */ + s = dmalloc (strlen (key -> algorithm) + 2, MDL); + if (!s) { + log_error ("no memory for key %s.", + key -> algorithm); + goto rbad; + } + strcpy (s, key -> algorithm); + strcat (s, "."); + dfree (key -> algorithm, MDL); + key -> algorithm = s; + } + break; + + case SECRET: + if (key -> key) { + parse_warn (cfile, "key %s: too many secrets", + key -> name); + goto rbad; + } + + memset (&ds, 0, sizeof(ds)); + if (!parse_base64 (&ds, cfile)) + goto rbad; + status = omapi_data_string_new (&key -> key, ds.len, + MDL); + if (status != ISC_R_SUCCESS) + goto rbad; + memcpy (key -> key -> value, + ds.buffer -> data, ds.len); + data_string_forget (&ds, MDL); + + if (!parse_semi (cfile)) + goto rbad; + break; + + default: + done = 1; + break; + } + } while (!done); + if (token != RBRACE) { + parse_warn (cfile, "expecting right brace."); + goto rbad; + } + /* Allow the BIND 8 syntax, which has a semicolon after each + closing brace. */ + token = peek_token (&val, (unsigned *)0, cfile); + if (token == SEMI) { + skip_token(&val, (unsigned *)0, cfile); + } + + /* Remember the key. */ + status = omapi_auth_key_enter (key); + if (status != ISC_R_SUCCESS) { + parse_warn (cfile, "tsig key %s: %s", + key -> name, isc_result_totext (status)); + goto bad; + } + omapi_auth_key_dereference (&key, MDL); + return 1; + + rbad: + skip_to_rbrace (cfile, 1); + bad: + omapi_auth_key_dereference (&key, MDL); + return 0; +} + +/* + * on-statement :== event-types LBRACE executable-statements RBRACE + * event-types :== event-type OR event-types | + * event-type + * event-type :== EXPIRY | COMMIT | RELEASE + */ + +int parse_on_statement (result, cfile, lose) + struct executable_statement **result; + struct parse *cfile; + int *lose; +{ + enum dhcp_token token; + const char *val; + + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for new statement."); + (*result) -> op = on_statement; + + do { + token = next_token (&val, (unsigned *)0, cfile); + switch (token) { + case EXPIRY: + (*result) -> data.on.evtypes |= ON_EXPIRY; + break; + + case COMMIT: + (*result) -> data.on.evtypes |= ON_COMMIT; + break; + + case RELEASE: + (*result) -> data.on.evtypes |= ON_RELEASE; + break; + + case TRANSMISSION: + (*result) -> data.on.evtypes |= ON_TRANSMISSION; + break; + + default: + parse_warn (cfile, "expecting a lease event type"); + skip_to_semi (cfile); + *lose = 1; + executable_statement_dereference (result, MDL); + return 0; + } + token = next_token (&val, (unsigned *)0, cfile); + } while (token == OR); + + /* Semicolon means no statements. */ + if (token == SEMI) + return 1; + + if (token != LBRACE) { + parse_warn (cfile, "left brace expected."); + skip_to_semi (cfile); + *lose = 1; + executable_statement_dereference (result, MDL); + return 0; + } + if (!parse_executable_statements (&(*result) -> data.on.statements, + cfile, lose, context_any)) { + if (*lose) { + /* Try to even things up. */ + do { + token = next_token (&val, + (unsigned *)0, cfile); + } while (token != END_OF_FILE && token != RBRACE); + executable_statement_dereference (result, MDL); + return 0; + } + } + token = next_token (&val, (unsigned *)0, cfile); + if (token != RBRACE) { + parse_warn (cfile, "right brace expected."); + skip_to_semi (cfile); + *lose = 1; + executable_statement_dereference (result, MDL); + return 0; + } + return 1; +} + +/* + * switch-statement :== LPAREN expr RPAREN LBRACE executable-statements RBRACE + * + */ + +int parse_switch_statement (result, cfile, lose) + struct executable_statement **result; + struct parse *cfile; + int *lose; +{ + enum dhcp_token token; + const char *val; + + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for new statement."); + (*result) -> op = switch_statement; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) { + parse_warn (cfile, "expecting left brace."); + pfui: + *lose = 1; + skip_to_semi (cfile); + gnorf: + executable_statement_dereference (result, MDL); + return 0; + } + + if (!parse_expression (&(*result) -> data.s_switch.expr, + cfile, lose, context_data_or_numeric, + (struct expression **)0, expr_none)) { + if (!*lose) { + parse_warn (cfile, + "expecting data or numeric expression."); + goto pfui; + } + goto gnorf; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != RPAREN) { + parse_warn (cfile, "right paren expected."); + goto pfui; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LBRACE) { + parse_warn (cfile, "left brace expected."); + goto pfui; + } + if (!(parse_executable_statements + (&(*result) -> data.s_switch.statements, cfile, lose, + (is_data_expression ((*result) -> data.s_switch.expr) + ? context_data : context_numeric)))) { + if (*lose) { + skip_to_rbrace (cfile, 1); + executable_statement_dereference (result, MDL); + return 0; + } + } + token = next_token (&val, (unsigned *)0, cfile); + if (token != RBRACE) { + parse_warn (cfile, "right brace expected."); + goto pfui; + } + return 1; +} + +/* + * case-statement :== CASE expr COLON + * + */ + +int parse_case_statement (result, cfile, lose, case_context) + struct executable_statement **result; + struct parse *cfile; + int *lose; + enum expression_context case_context; +{ + enum dhcp_token token; + const char *val; + + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for new statement."); + (*result) -> op = case_statement; + + if (!parse_expression (&(*result) -> data.c_case, + cfile, lose, case_context, + (struct expression **)0, expr_none)) + { + if (!*lose) { + parse_warn (cfile, "expecting %s expression.", + (case_context == context_data + ? "data" : "numeric")); + } + pfui: + *lose = 1; + skip_to_semi (cfile); + executable_statement_dereference (result, MDL); + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COLON) { + parse_warn (cfile, "colon expected."); + goto pfui; + } + return 1; +} + +/* + * if-statement :== boolean-expression LBRACE executable-statements RBRACE + * else-statement + * + * else-statement :== <null> | + * ELSE LBRACE executable-statements RBRACE | + * ELSE IF if-statement | + * ELSIF if-statement + */ + +int parse_if_statement (result, cfile, lose) + struct executable_statement **result; + struct parse *cfile; + int *lose; +{ + enum dhcp_token token; + const char *val; + int parenp; + + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for if statement."); + + (*result) -> op = if_statement; + + token = peek_token (&val, (unsigned *)0, cfile); + if (token == LPAREN) { + parenp = 1; + skip_token(&val, (unsigned *)0, cfile); + } else + parenp = 0; + + + if (!parse_boolean_expression (&(*result) -> data.ie.expr, + cfile, lose)) { + if (!*lose) + parse_warn (cfile, "boolean expression expected."); + executable_statement_dereference (result, MDL); + *lose = 1; + return 0; + } +#if defined (DEBUG_EXPRESSION_PARSE) + print_expression ("if condition", (*result) -> data.ie.expr); +#endif + if (parenp) { + token = next_token (&val, (unsigned *)0, cfile); + if (token != RPAREN) { + parse_warn (cfile, "expecting right paren."); + *lose = 1; + executable_statement_dereference (result, MDL); + return 0; + } + } + token = next_token (&val, (unsigned *)0, cfile); + if (token != LBRACE) { + parse_warn (cfile, "left brace expected."); + skip_to_semi (cfile); + *lose = 1; + executable_statement_dereference (result, MDL); + return 0; + } + if (!parse_executable_statements (&(*result) -> data.ie.tc, + cfile, lose, context_any)) { + if (*lose) { + /* Try to even things up. */ + do { + token = next_token (&val, + (unsigned *)0, cfile); + } while (token != END_OF_FILE && token != RBRACE); + executable_statement_dereference (result, MDL); + return 0; + } + } + token = next_token (&val, (unsigned *)0, cfile); + if (token != RBRACE) { + parse_warn (cfile, "right brace expected."); + skip_to_semi (cfile); + *lose = 1; + executable_statement_dereference (result, MDL); + return 0; + } + token = peek_token (&val, (unsigned *)0, cfile); + if (token == ELSE) { + skip_token(&val, (unsigned *)0, cfile); + token = peek_token (&val, (unsigned *)0, cfile); + if (token == IF) { + skip_token(&val, (unsigned *)0, cfile); + if (!parse_if_statement (&(*result) -> data.ie.fc, + cfile, lose)) { + if (!*lose) + parse_warn (cfile, + "expecting if statement"); + executable_statement_dereference (result, MDL); + *lose = 1; + return 0; + } + } else if (token != LBRACE) { + parse_warn (cfile, "left brace or if expected."); + skip_to_semi (cfile); + *lose = 1; + executable_statement_dereference (result, MDL); + return 0; + } else { + skip_token(&val, (unsigned *)0, cfile); + if (!(parse_executable_statements + (&(*result) -> data.ie.fc, + cfile, lose, context_any))) { + executable_statement_dereference (result, MDL); + return 0; + } + token = next_token (&val, (unsigned *)0, cfile); + if (token != RBRACE) { + parse_warn (cfile, "right brace expected."); + skip_to_semi (cfile); + *lose = 1; + executable_statement_dereference (result, MDL); + return 0; + } + } + } else if (token == ELSIF) { + skip_token(&val, (unsigned *)0, cfile); + if (!parse_if_statement (&(*result) -> data.ie.fc, + cfile, lose)) { + if (!*lose) + parse_warn (cfile, + "expecting conditional."); + executable_statement_dereference (result, MDL); + *lose = 1; + return 0; + } + } else + (*result) -> data.ie.fc = (struct executable_statement *)0; + + return 1; +} + +/* + * boolean_expression :== CHECK STRING | + * NOT boolean-expression | + * data-expression EQUAL data-expression | + * data-expression BANG EQUAL data-expression | + * data-expression REGEX_MATCH data-expression | + * boolean-expression AND boolean-expression | + * boolean-expression OR boolean-expression + * EXISTS OPTION-NAME + */ + +int parse_boolean_expression (expr, cfile, lose) + struct expression **expr; + struct parse *cfile; + int *lose; +{ + /* Parse an expression... */ + if (!parse_expression (expr, cfile, lose, context_boolean, + (struct expression **)0, expr_none)) + return 0; + + if (!is_boolean_expression (*expr) && + (*expr) -> op != expr_variable_reference && + (*expr) -> op != expr_funcall) { + parse_warn (cfile, "Expecting a boolean expression."); + *lose = 1; + expression_dereference (expr, MDL); + return 0; + } + return 1; +} + +/* boolean :== ON SEMI | OFF SEMI | TRUE SEMI | FALSE SEMI */ + +int parse_boolean (cfile) + struct parse *cfile; +{ + const char *val; + int rv; + + (void)next_token(&val, NULL, cfile); + if (!strcasecmp (val, "true") + || !strcasecmp (val, "on")) + rv = 1; + else if (!strcasecmp (val, "false") + || !strcasecmp (val, "off")) + rv = 0; + else { + parse_warn (cfile, + "boolean value (true/false/on/off) expected"); + skip_to_semi (cfile); + return 0; + } + parse_semi (cfile); + return rv; +} + + +/* + * data_expression :== SUBSTRING LPAREN data-expression COMMA + * numeric-expression COMMA + * numeric-expression RPAREN | + * CONCAT LPAREN data-expression COMMA + * data-expression RPAREN + * SUFFIX LPAREN data_expression COMMA + * numeric-expression RPAREN | + * LCASE LPAREN data_expression RPAREN | + * UCASE LPAREN data_expression RPAREN | + * OPTION option_name | + * HARDWARE | + * PACKET LPAREN numeric-expression COMMA + * numeric-expression RPAREN | + * STRING | + * colon_separated_hex_list + */ + +int parse_data_expression (expr, cfile, lose) + struct expression **expr; + struct parse *cfile; + int *lose; +{ + /* Parse an expression... */ + if (!parse_expression (expr, cfile, lose, context_data, + (struct expression **)0, expr_none)) + return 0; + + if (!is_data_expression (*expr) && + (*expr) -> op != expr_variable_reference && + (*expr) -> op != expr_funcall) { + expression_dereference (expr, MDL); + parse_warn (cfile, "Expecting a data expression."); + *lose = 1; + return 0; + } + return 1; +} + +/* + * numeric-expression :== EXTRACT_INT LPAREN data-expression + * COMMA number RPAREN | + * NUMBER + */ + +int parse_numeric_expression (expr, cfile, lose) + struct expression **expr; + struct parse *cfile; + int *lose; +{ + /* Parse an expression... */ + if (!parse_expression (expr, cfile, lose, context_numeric, + (struct expression **)0, expr_none)) + return 0; + + if (!is_numeric_expression (*expr) && + (*expr) -> op != expr_variable_reference && + (*expr) -> op != expr_funcall) { + expression_dereference (expr, MDL); + parse_warn (cfile, "Expecting a numeric expression."); + *lose = 1; + return 0; + } + return 1; +} +#if defined (NSUPDATE_OLD) +/* + * dns-expression :== + * UPDATE LPAREN ns-class COMMA ns-type COMMA data-expression COMMA + * data-expression COMMA numeric-expression RPAREN + * DELETE LPAREN ns-class COMMA ns-type COMMA data-expression COMMA + * data-expression RPAREN + * EXISTS LPAREN ns-class COMMA ns-type COMMA data-expression COMMA + * data-expression RPAREN + * NOT EXISTS LPAREN ns-class COMMA ns-type COMMA data-expression COMMA + * data-expression RPAREN + * ns-class :== IN | CHAOS | HS | NUMBER + * ns-type :== A | PTR | MX | TXT | NUMBER + */ + +int parse_dns_expression (expr, cfile, lose) + struct expression **expr; + struct parse *cfile; + int *lose; +{ + /* Parse an expression... */ + if (!parse_expression (expr, cfile, lose, context_dns, + (struct expression **)0, expr_none)) + return 0; + + if (!is_dns_expression (*expr) && + (*expr) -> op != expr_variable_reference && + (*expr) -> op != expr_funcall) { + expression_dereference (expr, MDL); + parse_warn (cfile, "Expecting a dns update subexpression."); + *lose = 1; + return 0; + } + return 1; +} +#endif /* NSUPDATE_OLD */ +/* Parse a subexpression that does not contain a binary operator. */ + +int parse_non_binary (expr, cfile, lose, context) + struct expression **expr; + struct parse *cfile; + int *lose; + enum expression_context context; +{ + enum dhcp_token token; + const char *val; + struct collection *col; + struct expression *nexp, **ep; + int known; + char *cptr; +#if defined (NSUPDATE_OLD) + enum expr_op opcode; + const char *s; + unsigned long u; +#endif + isc_result_t status; + unsigned len; + + token = peek_token (&val, (unsigned *)0, cfile); + + /* Check for unary operators... */ + switch (token) { + case CHECK: + skip_token(&val, (unsigned *)0, cfile); + token = next_token (&val, (unsigned *)0, cfile); + if (token != STRING) { + parse_warn (cfile, "string expected."); + skip_to_semi (cfile); + *lose = 1; + return 0; + } + for (col = collections; col; col = col -> next) + if (!strcmp (col -> name, val)) + break; + if (!col) { + parse_warn (cfile, "unknown collection."); + *lose = 1; + return 0; + } + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_check; + (*expr) -> data.check = col; + break; + + case TOKEN_NOT: + skip_token(&val, (unsigned *)0, cfile); +#if defined(NSUPDATE_OLD) + if (context == context_dns) { + token = peek_token (&val, (unsigned *)0, cfile); + goto not_exists; + } +#endif + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_not; + if (!parse_non_binary (&(*expr) -> data.not, + cfile, lose, context_boolean)) { + if (!*lose) { + parse_warn (cfile, "expression expected"); + skip_to_semi (cfile); + } + *lose = 1; + expression_dereference (expr, MDL); + return 0; + } + if (!is_boolean_expression ((*expr) -> data.not)) { + *lose = 1; + parse_warn (cfile, "boolean expression expected"); + skip_to_semi (cfile); + expression_dereference (expr, MDL); + return 0; + } + break; + + case LPAREN: + skip_token(&val, (unsigned *)0, cfile); + if (!parse_expression (expr, cfile, lose, context, + (struct expression **)0, expr_none)) { + if (!*lose) { + parse_warn (cfile, "expression expected"); + skip_to_semi (cfile); + } + *lose = 1; + return 0; + } + token = next_token (&val, (unsigned *)0, cfile); + if (token != RPAREN) { + *lose = 1; + parse_warn (cfile, "right paren expected"); + skip_to_semi (cfile); + return 0; + } + break; + + case EXISTS: +#if defined(NSUPDATE_OLD) + if (context == context_dns) + goto ns_exists; +#endif + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_exists; + known = 0; + /* Pass reference directly to expression structure. */ + status = parse_option_name(cfile, 0, &known, + &(*expr)->data.option); + if (status != ISC_R_SUCCESS || + (*expr)->data.option == NULL) { + *lose = 1; + expression_dereference (expr, MDL); + return 0; + } + break; + + case STATIC: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_static; + break; + + case KNOWN: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_known; + break; + + case SUBSTRING: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_substring; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) { + nolparen: + expression_dereference (expr, MDL); + parse_warn (cfile, "left parenthesis expected."); + *lose = 1; + return 0; + } + + if (!parse_data_expression (&(*expr) -> data.substring.expr, + cfile, lose)) { + nodata: + expression_dereference (expr, MDL); + if (!*lose) { + parse_warn (cfile, + "expecting data expression."); + skip_to_semi (cfile); + *lose = 1; + } + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) { + nocomma: + expression_dereference (expr, MDL); + parse_warn (cfile, "comma expected."); + *lose = 1; + + return 0; + } + + if (!parse_numeric_expression + (&(*expr) -> data.substring.offset,cfile, lose)) { + nonum: + if (!*lose) { + parse_warn (cfile, + "expecting numeric expression."); + skip_to_semi (cfile); + *lose = 1; + } + expression_dereference (expr, MDL); + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) + goto nocomma; + + if (!parse_numeric_expression + (&(*expr) -> data.substring.len, cfile, lose)) + goto nonum; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != RPAREN) { + norparen: + parse_warn (cfile, "right parenthesis expected."); + *lose = 1; + expression_dereference (expr, MDL); + return 0; + } + break; + + case SUFFIX: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_suffix; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) + goto nolparen; + + if (!parse_data_expression (&(*expr) -> data.suffix.expr, + cfile, lose)) + goto nodata; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) + goto nocomma; + + if (!parse_numeric_expression (&(*expr) -> data.suffix.len, + cfile, lose)) + goto nonum; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != RPAREN) + goto norparen; + break; + + case LCASE: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate(expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr)->op = expr_lcase; + + token = next_token(&val, (unsigned *)0, cfile); + if (token != LPAREN) + goto nolparen; + + if (!parse_data_expression(&(*expr)->data.lcase, cfile, lose)) + goto nodata; + + token = next_token(&val, (unsigned *)0, cfile); + if (token != RPAREN) + goto norparen; + break; + + case UCASE: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate(expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr)->op = expr_ucase; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) + goto nolparen; + + if (!parse_data_expression(&(*expr)->data.ucase, + cfile, lose)) + goto nodata; + + token = next_token(&val, (unsigned *)0, cfile); + if (token != RPAREN) + goto norparen; + break; + + case CONCAT: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_concat; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) + goto nolparen; + + if (!parse_data_expression (&(*expr) -> data.concat [0], + cfile, lose)) + goto nodata; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) + goto nocomma; + + concat_another: + if (!parse_data_expression (&(*expr) -> data.concat [1], + cfile, lose)) + goto nodata; + + token = next_token (&val, (unsigned *)0, cfile); + + if (token == COMMA) { + nexp = (struct expression *)0; + if (!expression_allocate (&nexp, MDL)) + log_fatal ("can't allocate at CONCAT2"); + nexp -> op = expr_concat; + expression_reference (&nexp -> data.concat [0], + *expr, MDL); + expression_dereference (expr, MDL); + expression_reference (expr, nexp, MDL); + expression_dereference (&nexp, MDL); + goto concat_another; + } + + if (token != RPAREN) + goto norparen; + break; + + case BINARY_TO_ASCII: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_binary_to_ascii; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) + goto nolparen; + + if (!parse_numeric_expression (&(*expr) -> data.b2a.base, + cfile, lose)) + goto nodata; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) + goto nocomma; + + if (!parse_numeric_expression (&(*expr) -> data.b2a.width, + cfile, lose)) + goto nodata; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) + goto nocomma; + + if (!parse_data_expression (&(*expr) -> data.b2a.separator, + cfile, lose)) + goto nodata; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) + goto nocomma; + + if (!parse_data_expression (&(*expr) -> data.b2a.buffer, + cfile, lose)) + goto nodata; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != RPAREN) + goto norparen; + break; + + case REVERSE: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_reverse; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) + goto nolparen; + + if (!(parse_numeric_expression + (&(*expr) -> data.reverse.width, cfile, lose))) + goto nodata; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) + goto nocomma; + + if (!(parse_data_expression + (&(*expr) -> data.reverse.buffer, cfile, lose))) + goto nodata; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != RPAREN) + goto norparen; + break; + + case PICK: + /* pick (a, b, c) actually produces an internal representation + that looks like pick (a, pick (b, pick (c, nil))). */ + skip_token(&val, (unsigned *)0, cfile); + if (!(expression_allocate (expr, MDL))) + log_fatal ("can't allocate expression"); + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) + goto nolparen; + + nexp = (struct expression *)0; + expression_reference (&nexp, *expr, MDL); + do { + nexp -> op = expr_pick_first_value; + if (!(parse_data_expression + (&nexp -> data.pick_first_value.car, + cfile, lose))) + goto nodata; + + token = next_token (&val, (unsigned *)0, cfile); + if (token == COMMA) { + struct expression *foo = (struct expression *)0; + if (!expression_allocate (&foo, MDL)) + log_fatal ("can't allocate expr"); + expression_reference + (&nexp -> data.pick_first_value.cdr, foo, MDL); + expression_dereference (&nexp, MDL); + expression_reference (&nexp, foo, MDL); + expression_dereference (&foo, MDL); + } + } while (token == COMMA); + expression_dereference (&nexp, MDL); + + if (token != RPAREN) + goto norparen; + break; + +#if defined(NSUPDATE_OLD) + /* dns-update and dns-delete are present for historical + purposes, but are deprecated in favor of ns-update + in combination with update, delete, exists and not + exists. */ + case DNS_UPDATE: + case DNS_DELETE: +#if !defined (NSUPDATE) + parse_warn (cfile, + "Please rebuild dhcpd with --with-nsupdate."); +#endif + skip_token(&val, (unsigned *)0, cfile); + if (token == DNS_UPDATE) + opcode = expr_ns_add; + else + opcode = expr_ns_delete; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) + goto nolparen; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != STRING) { + parse_warn (cfile, + "parse_expression: expecting string."); + badnsupdate: + skip_to_semi (cfile); + *lose = 1; + return 0; + } + + if (!strcasecmp (val, "a")) + u = T_A; + else if (!strcasecmp (val, "aaaa")) + u = T_AAAA; + else if (!strcasecmp (val, "ptr")) + u = T_PTR; + else if (!strcasecmp (val, "mx")) + u = T_MX; + else if (!strcasecmp (val, "cname")) + u = T_CNAME; + else if (!strcasecmp (val, "TXT")) + u = T_TXT; + else { + parse_warn (cfile, "unexpected rrtype: %s", val); + goto badnsupdate; + } + + s = (opcode == expr_ns_add + ? "old-dns-update" + : "old-dns-delete"); + cptr = dmalloc (strlen (s) + 1, MDL); + if (!cptr) + log_fatal ("can't allocate name for %s", s); + strcpy (cptr, s); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_funcall; + (*expr) -> data.funcall.name = cptr; + + /* Fake up a function call. */ + ep = &(*expr) -> data.funcall.arglist; + if (!expression_allocate (ep, MDL)) + log_fatal ("can't allocate expression"); + (*ep) -> op = expr_arg; + if (!make_const_int (&(*ep) -> data.arg.val, u)) + log_fatal ("can't allocate rrtype value."); + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) + goto nocomma; + ep = &((*ep) -> data.arg.next); + if (!expression_allocate (ep, MDL)) + log_fatal ("can't allocate expression"); + (*ep) -> op = expr_arg; + if (!(parse_data_expression (&(*ep) -> data.arg.val, + cfile, lose))) + goto nodata; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) + goto nocomma; + + ep = &((*ep) -> data.arg.next); + if (!expression_allocate (ep, MDL)) + log_fatal ("can't allocate expression"); + (*ep) -> op = expr_arg; + if (!(parse_data_expression (&(*ep) -> data.arg.val, + cfile, lose))) + goto nodata; + + if (opcode == expr_ns_add) { + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) + goto nocomma; + + ep = &((*ep) -> data.arg.next); + if (!expression_allocate (ep, MDL)) + log_fatal ("can't allocate expression"); + (*ep) -> op = expr_arg; + if (!(parse_numeric_expression (&(*ep) -> data.arg.val, + cfile, lose))) { + parse_warn (cfile, + "expecting numeric expression."); + goto badnsupdate; + } + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != RPAREN) + goto norparen; + break; + + case NS_UPDATE: +#if !defined (NSUPDATE) + parse_warn (cfile, + "Please rebuild dhcpd with --with-nsupdate."); +#endif + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) + goto nolparen; + + nexp = *expr; + do { + nexp -> op = expr_dns_transaction; + if (!(parse_dns_expression + (&nexp -> data.dns_transaction.car, + cfile, lose))) + { + if (!*lose) + parse_warn + (cfile, + "expecting dns expression."); + expression_dereference (expr, MDL); + *lose = 1; + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + + if (token == COMMA) { + if (!(expression_allocate + (&nexp -> data.dns_transaction.cdr, + MDL))) + log_fatal + ("can't allocate expression"); + nexp = nexp -> data.dns_transaction.cdr; + } + } while (token == COMMA); + + if (token != RPAREN) + goto norparen; + break; + + /* NOT EXISTS is special cased above... */ + not_exists: + token = peek_token (&val, (unsigned *)0, cfile); + if (token != EXISTS) { + parse_warn (cfile, "expecting DNS prerequisite."); + *lose = 1; + return 0; + } + opcode = expr_ns_not_exists; + goto nsupdatecode; + case TOKEN_ADD: + opcode = expr_ns_add; + goto nsupdatecode; + case TOKEN_DELETE: + opcode = expr_ns_delete; + goto nsupdatecode; + ns_exists: + opcode = expr_ns_exists; + nsupdatecode: + token = next_token (&val, (unsigned *)0, cfile); + +#if !defined (NSUPDATE) + parse_warn (cfile, + "Please rebuild dhcpd with --with-nsupdate."); +#endif + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = opcode; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) + goto nolparen; + + token = next_token (&val, (unsigned *)0, cfile); + if (!is_identifier (token) && token != NUMBER) { + parse_warn (cfile, "expecting identifier or number."); + badnsop: + expression_dereference (expr, MDL); + skip_to_semi (cfile); + *lose = 1; + return 0; + } + + if (token == NUMBER) + (*expr) -> data.ns_add.rrclass = atoi (val); + else if (!strcasecmp (val, "in")) + (*expr) -> data.ns_add.rrclass = C_IN; + else if (!strcasecmp (val, "chaos")) + (*expr) -> data.ns_add.rrclass = C_CHAOS; + else if (!strcasecmp (val, "hs")) + (*expr) -> data.ns_add.rrclass = C_HS; + else { + parse_warn (cfile, "unexpected rrclass: %s", val); + goto badnsop; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) + goto nocomma; + + token = next_token (&val, (unsigned *)0, cfile); + if (!is_identifier (token) && token != NUMBER) { + parse_warn (cfile, "expecting identifier or number."); + goto badnsop; + } + + if (token == NUMBER) + (*expr) -> data.ns_add.rrtype = atoi (val); + else if (!strcasecmp (val, "a")) + (*expr) -> data.ns_add.rrtype = T_A; + else if (!strcasecmp (val, "aaaa")) + (*expr) -> data.ns_add.rrtype = T_AAAA; + else if (!strcasecmp (val, "ptr")) + (*expr) -> data.ns_add.rrtype = T_PTR; + else if (!strcasecmp (val, "mx")) + (*expr) -> data.ns_add.rrtype = T_MX; + else if (!strcasecmp (val, "cname")) + (*expr) -> data.ns_add.rrtype = T_CNAME; + else if (!strcasecmp (val, "TXT")) + (*expr) -> data.ns_add.rrtype = T_TXT; + else { + parse_warn (cfile, "unexpected rrtype: %s", val); + goto badnsop; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) + goto nocomma; + + if (!(parse_data_expression + (&(*expr) -> data.ns_add.rrname, cfile, lose))) + goto nodata; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) + goto nocomma; + + if (!(parse_data_expression + (&(*expr) -> data.ns_add.rrdata, cfile, lose))) + goto nodata; + + if (opcode == expr_ns_add) { + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) + goto nocomma; + + if (!(parse_numeric_expression + (&(*expr) -> data.ns_add.ttl, cfile, + lose))) { + if (!*lose) + parse_warn (cfile, + "expecting numeric expression."); + goto badnsupdate; + } + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != RPAREN) + goto norparen; + break; +#endif /* NSUPDATE_OLD */ + case OPTION: + case CONFIG_OPTION: + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = (token == OPTION + ? expr_option + : expr_config_option); + skip_token(&val, (unsigned *)0, cfile); + known = 0; + /* Pass reference directly to expression structure. */ + status = parse_option_name(cfile, 0, &known, + &(*expr)->data.option); + if (status != ISC_R_SUCCESS || + (*expr)->data.option == NULL) { + *lose = 1; + expression_dereference (expr, MDL); + return 0; + } + break; + + case HARDWARE: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_hardware; + break; + + case LEASED_ADDRESS: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_leased_address; + break; + + case CLIENT_STATE: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_client_state; + break; + + case FILENAME: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_filename; + break; + + case SERVER_NAME: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_sname; + break; + + case LEASE_TIME: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_lease_time; + break; + + case TOKEN_NULL: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_null; + break; + + case HOST_DECL_NAME: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_host_decl_name; + break; + +#if defined(NSUPDATE_OLD) + case UPDATED_DNS_RR: + skip_token(&val, (unsigned *)0, cfile); + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) + goto nolparen; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != STRING) { + parse_warn (cfile, "expecting string."); + bad_rrtype: + *lose = 1; + return 0; + } + if (!strcasecmp (val, "a")) + s = "ddns-fwd-name"; + else if (!strcasecmp (val, "ptr")) + s = "ddns-rev-name"; + else { + parse_warn (cfile, "invalid DNS rrtype: %s", val); + goto bad_rrtype; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != RPAREN) + goto norparen; + + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_variable_reference; + (*expr) -> data.variable = + dmalloc (strlen (s) + 1, MDL); + if (!(*expr) -> data.variable) + log_fatal ("can't allocate variable name."); + strcpy ((*expr) -> data.variable, s); + break; +#endif /* NSUPDATE_OLD */ + case PACKET: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_packet; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) + goto nolparen; + + if (!parse_numeric_expression (&(*expr) -> data.packet.offset, + cfile, lose)) + goto nonum; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) + goto nocomma; + + if (!parse_numeric_expression (&(*expr) -> data.packet.len, + cfile, lose)) + goto nonum; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != RPAREN) + goto norparen; + break; + + case STRING: + skip_token(&val, &len, cfile); + if (!make_const_data (expr, (const unsigned char *)val, + len, 1, 1, MDL)) + log_fatal ("can't make constant string expression."); + break; + + case EXTRACT_INT: + skip_token(&val, (unsigned *)0, cfile); + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) { + parse_warn (cfile, "left parenthesis expected."); + *lose = 1; + return 0; + } + + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + + if (!parse_data_expression (&(*expr) -> data.extract_int, + cfile, lose)) { + if (!*lose) { + parse_warn (cfile, + "expecting data expression."); + skip_to_semi (cfile); + *lose = 1; + } + expression_dereference (expr, MDL); + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) { + parse_warn (cfile, "comma expected."); + *lose = 1; + expression_dereference (expr, MDL); + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != NUMBER) { + parse_warn (cfile, "number expected."); + *lose = 1; + expression_dereference (expr, MDL); + return 0; + } + switch (atoi (val)) { + case 8: + (*expr) -> op = expr_extract_int8; + break; + + case 16: + (*expr) -> op = expr_extract_int16; + break; + + case 32: + (*expr) -> op = expr_extract_int32; + break; + + default: + parse_warn (cfile, + "unsupported integer size %d", atoi (val)); + *lose = 1; + skip_to_semi (cfile); + expression_dereference (expr, MDL); + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != RPAREN) { + parse_warn (cfile, "right parenthesis expected."); + *lose = 1; + expression_dereference (expr, MDL); + return 0; + } + break; + + case ENCODE_INT: + skip_token(&val, (unsigned *)0, cfile); + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) { + parse_warn (cfile, "left parenthesis expected."); + *lose = 1; + return 0; + } + + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + + if (!parse_numeric_expression (&(*expr) -> data.encode_int, + cfile, lose)) { + parse_warn (cfile, "expecting numeric expression."); + skip_to_semi (cfile); + *lose = 1; + expression_dereference (expr, MDL); + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != COMMA) { + parse_warn (cfile, "comma expected."); + *lose = 1; + expression_dereference (expr, MDL); + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != NUMBER) { + parse_warn (cfile, "number expected."); + *lose = 1; + expression_dereference (expr, MDL); + return 0; + } + switch (atoi (val)) { + case 8: + (*expr) -> op = expr_encode_int8; + break; + + case 16: + (*expr) -> op = expr_encode_int16; + break; + + case 32: + (*expr) -> op = expr_encode_int32; + break; + + default: + parse_warn (cfile, + "unsupported integer size %d", atoi (val)); + *lose = 1; + skip_to_semi (cfile); + expression_dereference (expr, MDL); + return 0; + } + + token = next_token (&val, (unsigned *)0, cfile); + if (token != RPAREN) { + parse_warn (cfile, "right parenthesis expected."); + *lose = 1; + expression_dereference (expr, MDL); + return 0; + } + break; + + case NUMBER: + /* If we're in a numeric context, this should just be a + number, by itself. */ + if (context == context_numeric || + context == context_data_or_numeric) { + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_const_int; + (*expr) -> data.const_int = atoi (val); + break; + } + + case NUMBER_OR_NAME: + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + + (*expr) -> op = expr_const_data; + if (!parse_cshl (&(*expr) -> data.const_data, cfile)) { + expression_dereference (expr, MDL); + return 0; + } + break; + + case NS_FORMERR: + known = FORMERR; + goto ns_const; + ns_const: + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_const_int; + (*expr) -> data.const_int = known; + break; + + case NS_NOERROR: + known = ISC_R_SUCCESS; + goto ns_const; + + case NS_NOTAUTH: + known = DHCP_R_NOTAUTH; + goto ns_const; + + case NS_NOTIMP: + known = ISC_R_NOTIMPLEMENTED; + goto ns_const; + + case NS_NOTZONE: + known = DHCP_R_NOTZONE; + goto ns_const; + + case NS_NXDOMAIN: + known = DHCP_R_NXDOMAIN; + goto ns_const; + + case NS_NXRRSET: + known = DHCP_R_NXRRSET; + goto ns_const; + + case NS_REFUSED: + known = DHCP_R_REFUSED; + goto ns_const; + + case NS_SERVFAIL: + known = DHCP_R_SERVFAIL; + goto ns_const; + + case NS_YXDOMAIN: + known = DHCP_R_YXDOMAIN; + goto ns_const; + + case NS_YXRRSET: + known = DHCP_R_YXRRSET; + goto ns_const; + + case BOOTING: + known = S_INIT; + goto ns_const; + + case REBOOT: + known = S_REBOOTING; + goto ns_const; + + case SELECT: + known = S_SELECTING; + goto ns_const; + + case REQUEST: + known = S_REQUESTING; + goto ns_const; + + case BOUND: + known = S_BOUND; + goto ns_const; + + case RENEW: + known = S_RENEWING; + goto ns_const; + + case REBIND: + known = S_REBINDING; + goto ns_const; + + case DEFINED: + skip_token(&val, (unsigned *)0, cfile); + token = next_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) + goto nolparen; + + token = next_token (&val, (unsigned *)0, cfile); + if (token != NAME && token != NUMBER_OR_NAME) { + parse_warn (cfile, "%s can't be a variable name", val); + skip_to_semi (cfile); + *lose = 1; + return 0; + } + + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_variable_exists; + (*expr) -> data.variable = dmalloc (strlen (val) + 1, MDL); + if (!(*expr)->data.variable) + log_fatal ("can't allocate variable name"); + strcpy ((*expr) -> data.variable, val); + token = next_token (&val, (unsigned *)0, cfile); + if (token != RPAREN) + goto norparen; + break; + + /* This parses 'gethostname()'. */ + case GETHOSTNAME: + skip_token(&val, NULL, cfile); + if (!expression_allocate(expr, MDL)) + log_fatal("can't allocate expression"); + (*expr)->op = expr_gethostname; + + token = next_token(NULL, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + token = next_token(NULL, NULL, cfile); + if (token != RPAREN) + goto norparen; + break; + + case GETHOSTBYNAME: + skip_token(&val, NULL, cfile); + token = next_token(NULL, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + /* The argument is a quoted string. */ + token = next_token(&val, NULL, cfile); + if (token != STRING) { + parse_warn(cfile, "Expecting quoted literal: " + "\"foo.example.com\""); + skip_to_semi(cfile); + *lose = 1; + return 0; + } + if (!make_host_lookup(expr, val)) + log_fatal("Error creating gethostbyname() internal " + "record. (%s:%d)", MDL); + + token = next_token(NULL, NULL, cfile); + if (token != RPAREN) + goto norparen; + break; + + /* Not a valid start to an expression... */ + default: + if (token != NAME && token != NUMBER_OR_NAME) + return 0; + + skip_token(&val, (unsigned *)0, cfile); + + /* Save the name of the variable being referenced. */ + cptr = dmalloc (strlen (val) + 1, MDL); + if (!cptr) + log_fatal ("can't allocate variable name"); + strcpy (cptr, val); + + /* Simple variable reference, as far as we can tell. */ + token = peek_token (&val, (unsigned *)0, cfile); + if (token != LPAREN) { + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_variable_reference; + (*expr) -> data.variable = cptr; + break; + } + + skip_token(&val, (unsigned *)0, cfile); + if (!expression_allocate (expr, MDL)) + log_fatal ("can't allocate expression"); + (*expr) -> op = expr_funcall; + (*expr) -> data.funcall.name = cptr; + + /* Now parse the argument list. */ + ep = &(*expr) -> data.funcall.arglist; + do { + if (!expression_allocate (ep, MDL)) + log_fatal ("can't allocate expression"); + (*ep) -> op = expr_arg; + if (!parse_expression (&(*ep) -> data.arg.val, + cfile, lose, context_any, + (struct expression **)0, + expr_none)) { + if (!*lose) { + parse_warn (cfile, + "expecting expression."); + *lose = 1; + } + skip_to_semi (cfile); + expression_dereference (expr, MDL); + return 0; + } + ep = &((*ep) -> data.arg.next); + token = next_token (&val, (unsigned *)0, cfile); + } while (token == COMMA); + if (token != RPAREN) { + parse_warn (cfile, "Right parenthesis expected."); + skip_to_semi (cfile); + *lose = 1; + expression_dereference (expr, MDL); + return 0; + } + break; + } + return 1; +} + +/* Parse an expression. */ + +int parse_expression (expr, cfile, lose, context, plhs, binop) + struct expression **expr; + struct parse *cfile; + int *lose; + enum expression_context context; + struct expression **plhs; + enum expr_op binop; +{ + enum dhcp_token token; + const char *val; + struct expression *rhs = (struct expression *)0, *tmp; + struct expression *lhs = (struct expression *)0; + enum expr_op next_op; + enum expression_context + lhs_context = context_any, + rhs_context = context_any; + + /* Consume the left hand side we were passed. */ + if (plhs) { + expression_reference (&lhs, *plhs, MDL); + expression_dereference (plhs, MDL); + } + + new_rhs: + if (!parse_non_binary (&rhs, cfile, lose, context)) { + /* If we already have a left-hand side, then it's not + okay for there not to be a right-hand side here, so + we need to flag it as an error. */ + if (lhs) { + if (!*lose) { + parse_warn (cfile, + "expecting right-hand side."); + *lose = 1; + skip_to_semi (cfile); + } + expression_dereference (&lhs, MDL); + } + return 0; + } + + /* At this point, rhs contains either an entire subexpression, + or at least a left-hand-side. If we do not see a binary token + as the next token, we're done with the expression. */ + + token = peek_token (&val, (unsigned *)0, cfile); + switch (token) { + case BANG: + skip_token(&val, (unsigned *)0, cfile); + token = peek_token (&val, (unsigned *)0, cfile); + if (token != EQUAL) { + parse_warn (cfile, "! in boolean context without ="); + *lose = 1; + skip_to_semi (cfile); + if (lhs) + expression_dereference (&lhs, MDL); + return 0; + } + next_op = expr_not_equal; + context = expression_context (rhs); + break; + + case EQUAL: + next_op = expr_equal; + context = expression_context (rhs); + break; + + case TILDE: +#ifdef HAVE_REGEX_H + skip_token(&val, NULL, cfile); + token = peek_token(&val, NULL, cfile); + + if (token == TILDE) + next_op = expr_iregex_match; + else if (token == EQUAL) + next_op = expr_regex_match; + else { + parse_warn(cfile, "expecting ~= or ~~ operator"); + *lose = 1; + skip_to_semi(cfile); + if (lhs) + expression_dereference(&lhs, MDL); + return 0; + } + + context = expression_context(rhs); +#else + parse_warn(cfile, "No support for regex operator."); + *lose = 1; + skip_to_semi(cfile); + if (lhs != NULL) + expression_dereference(&lhs, MDL); + return 0; +#endif + break; + + case AND: + next_op = expr_and; + context = expression_context (rhs); + break; + + case OR: + next_op = expr_or; + context = expression_context (rhs); + break; + + case PLUS: + next_op = expr_add; + context = expression_context (rhs); + break; + + case MINUS: + next_op = expr_subtract; + context = expression_context (rhs); + break; + + case SLASH: + next_op = expr_divide; + context = expression_context (rhs); + break; + + case ASTERISK: + next_op = expr_multiply; + context = expression_context (rhs); + break; + + case PERCENT: + next_op = expr_remainder; + context = expression_context (rhs); + break; + + case AMPERSAND: + next_op = expr_binary_and; + context = expression_context (rhs); + break; + + case PIPE: + next_op = expr_binary_or; + context = expression_context (rhs); + break; + + case CARET: + next_op = expr_binary_xor; + context = expression_context (rhs); + break; + + default: + next_op = expr_none; + } + + /* If we have no lhs yet, we just parsed it. */ + if (!lhs) { + /* If there was no operator following what we just parsed, + then we're done - return it. */ + if (next_op == expr_none) { + *expr = rhs; + return 1; + } + lhs = rhs; + rhs = (struct expression *)0; + binop = next_op; + skip_token(&val, (unsigned *)0, cfile); + goto new_rhs; + } + + /* If the next binary operator is of greater precedence than the + * current operator, then rhs we have parsed so far is actually + * the lhs of the next operator. To get this value, we have to + * recurse. + */ + if (binop != expr_none && next_op != expr_none && + op_precedence (binop, next_op) < 0) { + + /* Eat the subexpression operator token, which we pass to + * parse_expression...we only peek()'d earlier. + */ + skip_token(&val, (unsigned *)0, cfile); + + /* Continue parsing of the right hand side with that token. */ + tmp = rhs; + rhs = (struct expression *)0; + if (!parse_expression (&rhs, cfile, lose, op_context (next_op), + &tmp, next_op)) { + if (!*lose) { + parse_warn (cfile, + "expecting a subexpression"); + *lose = 1; + } + return 0; + } + next_op = expr_none; + } + + if (binop != expr_none) { + rhs_context = expression_context(rhs); + lhs_context = expression_context(lhs); + + if ((rhs_context != context_any) && (lhs_context != context_any) && + (rhs_context != lhs_context)) { + parse_warn (cfile, "illegal expression relating different types"); + skip_to_semi (cfile); + expression_dereference (&rhs, MDL); + expression_dereference (&lhs, MDL); + *lose = 1; + return 0; + } + + switch(binop) { + case expr_not_equal: + case expr_equal: + if ((rhs_context != context_data_or_numeric) && + (rhs_context != context_data) && + (rhs_context != context_numeric) && + (rhs_context != context_any)) { + parse_warn (cfile, "expecting data/numeric expression"); + skip_to_semi (cfile); + expression_dereference (&rhs, MDL); + *lose = 1; + return 0; + } + break; + + case expr_regex_match: +#ifdef HAVE_REGEX_H + if (expression_context(rhs) != context_data) { + parse_warn(cfile, "expecting data expression"); + skip_to_semi(cfile); + expression_dereference(&rhs, MDL); + *lose = 1; + return 0; + } +#else + /* It should not be possible to attempt to parse the right + * hand side of an operator there is no support for. + */ + log_fatal("Impossible condition at %s:%d.", MDL); +#endif + break; + + case expr_and: + case expr_or: + if ((rhs_context != context_boolean) && + (rhs_context != context_any)) { + parse_warn (cfile, "expecting boolean expressions"); + skip_to_semi (cfile); + expression_dereference (&rhs, MDL); + *lose = 1; + return 0; + } + break; + + case expr_add: + case expr_subtract: + case expr_divide: + case expr_multiply: + case expr_remainder: + case expr_binary_and: + case expr_binary_or: + case expr_binary_xor: + if ((rhs_context != context_numeric) && + (rhs_context != context_any)) { + parse_warn (cfile, "expecting numeric expressions"); + skip_to_semi (cfile); + expression_dereference (&rhs, MDL); + *lose = 1; + return 0; + } + break; + + default: + break; + } + } + + /* Now, if we didn't find a binary operator, we're done parsing + this subexpression, so combine it with the preceding binary + operator and return the result. */ + if (next_op == expr_none) { + if (!expression_allocate (expr, MDL)) + log_fatal ("Can't allocate expression!"); + + (*expr) -> op = binop; + /* All the binary operators' data union members + are the same, so we'll cheat and use the member + for the equals operator. */ + (*expr) -> data.equal [0] = lhs; + (*expr) -> data.equal [1] = rhs; + return 1; + } + + /* Eat the operator token - we now know it was a binary operator... */ + skip_token(&val, (unsigned *)0, cfile); + + /* Now combine the LHS and the RHS using binop. */ + tmp = (struct expression *)0; + if (!expression_allocate (&tmp, MDL)) + log_fatal ("No memory for equal precedence combination."); + + /* Store the LHS and RHS. */ + tmp -> data.equal [0] = lhs; + tmp -> data.equal [1] = rhs; + tmp -> op = binop; + + lhs = tmp; + tmp = (struct expression *)0; + rhs = (struct expression *)0; + + binop = next_op; + goto new_rhs; +} + + +int parse_option_data (expr, cfile, lookups, option) +struct expression **expr; +struct parse *cfile; +int lookups; +struct option *option; +{ + const char *val; + const char *fmt = NULL; + struct expression *tmp; + enum dhcp_token token; + + do { + /* + * Set a flag if this is an array of a simple type (i.e., + * not an array of pairs of IP addresses, or something like + * that. + */ + int uniform = 0; + + and_again: + /* Set fmt to start of format for 'A' and one char back + * for 'a'. + */ + if ((fmt != NULL) && (fmt != option->format) && (*fmt == 'a')) + fmt -= 1; + else if ((fmt == NULL) || (*fmt == 'A')) + fmt = option->format; + + /* 'a' means always uniform */ + if ((fmt[0] != 'Z') && (tolower((unsigned char)fmt[1]) == 'a')) + uniform = 1; + + do { + if ((*fmt == 'A') || (*fmt == 'a')) + break; + if (*fmt == 'o') { + /* consume the optional flag */ + fmt++; + continue; + } + + if (fmt[1] == 'o') { + /* + * A value for the current format is + * optional - check to see if the next + * token is a semi-colon if so we don't + * need to parse it and doing so would + * consume the semi-colon which our + * caller is expecting to parse + */ + token = peek_token(&val, (unsigned *)0, + cfile); + if (token == SEMI) { + fmt++; + continue; + } + } + + tmp = *expr; + *expr = NULL; + + if (!parse_option_token(expr, cfile, &fmt, tmp, + uniform, lookups)) { + if (fmt [1] != 'o') { + if (tmp) + expression_dereference (&tmp, + MDL); + return 0; + } + *expr = tmp; + tmp = NULL; + } + if (tmp) + expression_dereference (&tmp, MDL); + + fmt++; + } while (*fmt != '\0'); + + if ((*fmt == 'A') || (*fmt == 'a')) { + token = peek_token (&val, (unsigned *)0, cfile); + /* Comma means: continue with next element in array */ + if (token == COMMA) { + skip_token(&val, (unsigned *)0, cfile); + continue; + } + /* no comma: end of array. + 'A' or end of string means: leave the loop */ + if ((*fmt == 'A') || (fmt[1] == '\0')) + break; + /* 'a' means: go on with next char */ + if (*fmt == 'a') { + fmt++; + goto and_again; + } + } + } while ((*fmt == 'A') || (*fmt == 'a')); + + return 1; +} + +/* option-statement :== identifier DOT identifier <syntax> SEMI + | identifier <syntax> SEMI + + Option syntax is handled specially through format strings, so it + would be painful to come up with BNF for it. However, it always + starts as above and ends in a SEMI. */ + +int parse_option_statement (result, cfile, lookups, option, op) + struct executable_statement **result; + struct parse *cfile; + int lookups; + struct option *option; + enum statement_op op; +{ + const char *val; + enum dhcp_token token; + struct expression *expr = (struct expression *)0; + int lose; + + token = peek_token (&val, (unsigned *)0, cfile); + if ((token == SEMI) && (option->format[0] != 'Z')) { + /* Eat the semicolon... */ + /* + * XXXSK: I'm not sure why we should ever get here, but we + * do during our startup. This confuses things if + * we are parsing a zero-length option, so don't + * eat the semicolon token in that case. + */ + skip_token(&val, (unsigned *)0, cfile); + } else if (token == EQUAL) { + /* Eat the equals sign. */ + skip_token(&val, (unsigned *)0, cfile); + + /* Parse a data expression and use its value for the data. */ + if (!parse_data_expression (&expr, cfile, &lose)) { + /* In this context, we must have an executable + statement, so if we found something else, it's + still an error. */ + if (!lose) { + parse_warn (cfile, + "expecting a data expression."); + skip_to_semi (cfile); + } + return 0; + } + } else { + if (! parse_option_data(&expr, cfile, lookups, option)) + return 0; + } + + if (!parse_semi (cfile)) + return 0; + if (!executable_statement_allocate (result, MDL)) + log_fatal ("no memory for option statement."); + + (*result)->op = op; + if (expr && !option_cache (&(*result)->data.option, + NULL, expr, option, MDL)) + log_fatal ("no memory for option cache"); + + if (expr) + expression_dereference (&expr, MDL); + + return 1; +} + +int parse_option_token (rv, cfile, fmt, expr, uniform, lookups) + struct expression **rv; + struct parse *cfile; + const char **fmt; + struct expression *expr; + int uniform; + int lookups; +{ + const char *val; + enum dhcp_token token; + struct expression *t = (struct expression *)0; + unsigned char buf [4]; + unsigned len; + struct iaddr addr; + int compress; + isc_boolean_t freeval = ISC_FALSE; + const char *f, *g; + struct enumeration_value *e; + + switch (**fmt) { + case 'U': + token = next_token (&val, &len, cfile); + if (!is_identifier (token)) { + if ((*fmt) [1] != 'o') { + parse_warn (cfile, "expecting identifier."); + if (token != SEMI) + skip_to_semi (cfile); + } + return 0; + } + if (!make_const_data (&t, (const unsigned char *)val, + len, 1, 1, MDL)) + log_fatal ("No memory for %s", val); + break; + + case 'E': + g = strchr (*fmt, '.'); + if (!g) { + parse_warn (cfile, + "malformed encapsulation format (bug!)"); + skip_to_semi (cfile); + return 0; + } + *fmt = g; + /* FALL THROUGH */ + /* to get string value for the option */ + case 'X': + token = peek_token (&val, (unsigned *)0, cfile); + if (token == NUMBER_OR_NAME || token == NUMBER) { + if (!expression_allocate (&t, MDL)) + return 0; + if (!parse_cshl (&t -> data.const_data, cfile)) { + expression_dereference (&t, MDL); + return 0; + } + t -> op = expr_const_data; + } else { + token = next_token (&val, &len, cfile); + + if(token == STRING) { + if (!make_const_data (&t, + (const unsigned char *)val, + len, 1, 1, MDL)) + log_fatal ("No memory for \"%s\"", val); + } else { + if ((*fmt) [1] != 'o') { + parse_warn (cfile, "expecting string " + "or hexadecimal data."); + skip_to_semi (cfile); + } + return 0; + } + } + break; + + case 'D': /* Domain list... */ + if ((*fmt)[1] == 'c') { + compress = 1; + /* Skip the compress-flag atom. */ + (*fmt)++; + } else + compress = 0; + + t = parse_domain_list(cfile, compress); + + if (!t) { + if ((*fmt)[1] != 'o') + skip_to_semi(cfile); + return 0; + } + + break; + + case 'd': /* Domain name... */ + val = parse_host_name (cfile); + if (!val) { + parse_warn (cfile, "not a valid domain name."); + skip_to_semi (cfile); + return 0; + } + len = strlen (val); + freeval = ISC_TRUE; + goto make_string; + + case 't': /* Text string... */ + token = next_token (&val, &len, cfile); + if (token != STRING && !is_identifier (token)) { + if ((*fmt) [1] != 'o') { + parse_warn (cfile, "expecting string."); + if (token != SEMI) + skip_to_semi (cfile); + } + return 0; + } + make_string: + if (!make_const_data (&t, (const unsigned char *)val, + len, 1, 1, MDL)) + log_fatal ("No memory for concatenation"); + if (freeval == ISC_TRUE) { + dfree((char *)val, MDL); + freeval = ISC_FALSE; + POST(freeval); + } + break; + + case 'N': + f = (*fmt) + 1; + g = strchr (*fmt, '.'); + if (!g) { + parse_warn (cfile, "malformed %s (bug!)", + "enumeration format"); + foo: + skip_to_semi (cfile); + return 0; + } + *fmt = g; + token = next_token (&val, (unsigned *)0, cfile); + if (!is_identifier (token)) { + parse_warn (cfile, + "identifier expected"); + goto foo; + } + e = find_enumeration_value (f, (*fmt) - f, &len, val); + if (!e) { + parse_warn (cfile, "unknown value"); + goto foo; + } + if (!make_const_data (&t, &e -> value, len, 0, 1, MDL)) + return 0; + break; + + case 'I': /* IP address or hostname. */ + if (lookups) { + if (!parse_ip_addr_or_hostname (&t, cfile, uniform)) + return 0; + } else { + if (!parse_ip_addr (cfile, &addr)) + return 0; + if (!make_const_data (&t, addr.iabuf, addr.len, + 0, 1, MDL)) + return 0; + } + break; + + case '6': /* IPv6 address. */ + if (!parse_ip6_addr(cfile, &addr)) { + return 0; + } + if (!make_const_data(&t, addr.iabuf, addr.len, 0, 1, MDL)) { + return 0; + } + break; + + case 'T': /* Lease interval. */ + token = next_token (&val, (unsigned *)0, cfile); + if (token != INFINITE) + goto check_number; + putLong (buf, -1); + if (!make_const_data (&t, buf, 4, 0, 1, MDL)) + return 0; + break; + + case 'L': /* Unsigned 32-bit integer... */ + case 'l': /* Signed 32-bit integer... */ + token = next_token (&val, (unsigned *)0, cfile); + check_number: + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) { + need_number: + if ((*fmt) [1] != 'o') { + parse_warn (cfile, "expecting number."); + if (token != SEMI) + skip_to_semi (cfile); + } + return 0; + } + convert_num (cfile, buf, val, 0, 32); + if (!make_const_data (&t, buf, 4, 0, 1, MDL)) + return 0; + break; + + case 's': /* Signed 16-bit integer. */ + case 'S': /* Unsigned 16-bit integer. */ + token = next_token (&val, (unsigned *)0, cfile); + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) + goto need_number; + convert_num (cfile, buf, val, 0, 16); + if (!make_const_data (&t, buf, 2, 0, 1, MDL)) + return 0; + break; + + case 'b': /* Signed 8-bit integer. */ + case 'B': /* Unsigned 8-bit integer. */ + token = next_token (&val, (unsigned *)0, cfile); + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) + goto need_number; + convert_num (cfile, buf, val, 0, 8); + if (!make_const_data (&t, buf, 1, 0, 1, MDL)) + return 0; + break; + + case 'f': /* Boolean flag. */ + token = next_token (&val, (unsigned *)0, cfile); + if (!is_identifier (token)) { + if ((*fmt) [1] != 'o') + parse_warn (cfile, "expecting identifier."); + bad_flag: + if ((*fmt) [1] != 'o') { + if (token != SEMI) + skip_to_semi (cfile); + } + return 0; + } + if (!strcasecmp (val, "true") + || !strcasecmp (val, "on")) + buf [0] = 1; + else if (!strcasecmp (val, "false") + || !strcasecmp (val, "off")) + buf [0] = 0; + else if (!strcasecmp (val, "ignore")) + buf [0] = 2; + else { + if ((*fmt) [1] != 'o') + parse_warn (cfile, "expecting boolean."); + goto bad_flag; + } + if (!make_const_data (&t, buf, 1, 0, 1, MDL)) + return 0; + break; + + case 'Z': /* Zero-length option. */ + token = peek_token (&val, (unsigned *)0, cfile); + if (token != SEMI) { + parse_warn(cfile, "semicolon expected."); + skip_to_semi(cfile); + } + buf[0] = '\0'; + if (!make_const_data(&t, /* expression */ + buf, /* buffer */ + 0, /* length */ + 0, /* terminated */ + 1, /* allocate */ + MDL)) + return 0; + break; + + default: + parse_warn (cfile, "Bad format '%c' in parse_option_token.", + **fmt); + skip_to_semi (cfile); + return 0; + } + if (expr) { + if (!make_concat (rv, expr, t)) + return 0; + } else + expression_reference (rv, t, MDL); + expression_dereference (&t, MDL); + return 1; +} + +int parse_option_decl (oc, cfile) + struct option_cache **oc; + struct parse *cfile; +{ + const char *val; + int token; + u_int8_t buf [4]; + u_int8_t hunkbuf [1024]; + unsigned hunkix = 0; + const char *fmt, *f; + struct option *option=NULL; + struct iaddr ip_addr; + u_int8_t *dp; + const u_int8_t *cdp; + unsigned len; + int nul_term = 0; + struct buffer *bp; + int known = 0; + int compress; + struct expression *express = NULL; + struct enumeration_value *e; + isc_result_t status; + + status = parse_option_name (cfile, 0, &known, &option); + if (status != ISC_R_SUCCESS || option == NULL) + return 0; + + fmt = option->format; + + /* Parse the option data... */ + do { + for (; *fmt; fmt++) { + if (*fmt == 'A') { + /* 'A' is an array of records, start at + * the beginning + */ + fmt = option->format; + break; + } + + if (*fmt == 'a') { + /* 'a' is an array of the last field, + * back up one format character + */ + fmt--; + break; + } + if (*fmt == 'o' && fmt != option -> format) + continue; + switch (*fmt) { + case 'E': + fmt = strchr (fmt, '.'); + if (!fmt) { + parse_warn (cfile, + "malformed %s (bug!)", + "encapsulation format"); + goto parse_exit; + } + /* FALL THROUGH */ + /* to get string value for the option */ + case 'X': + len = parse_X (cfile, &hunkbuf [hunkix], + sizeof hunkbuf - hunkix); + hunkix += len; + break; + + case 't': /* Text string... */ + token = peek_token (&val, + &len, cfile); + if (token == SEMI && fmt[1] == 'o') { + fmt++; + break; + } + token = next_token (&val, + &len, cfile); + if (token != STRING) { + parse_warn (cfile, + "expecting string."); + goto parse_exit; + } + if (hunkix + len + 1 > sizeof hunkbuf) { + parse_warn (cfile, + "option data buffer %s", + "overflow"); + goto parse_exit; + } + memcpy (&hunkbuf [hunkix], val, len + 1); + nul_term = 1; + hunkix += len; + break; + + case 'D': + if (fmt[1] == 'c') { + compress = 1; + fmt++; + } else + compress = 0; + + express = parse_domain_list(cfile, compress); + + if (express == NULL) + goto exit; + + if (express->op != expr_const_data) { + parse_warn(cfile, "unexpected " + "expression"); + goto parse_exit; + } + + len = express->data.const_data.len; + cdp = express->data.const_data.data; + + if ((hunkix + len) > sizeof(hunkbuf)) { + parse_warn(cfile, "option data buffer " + "overflow"); + goto parse_exit; + } + memcpy(&hunkbuf[hunkix], cdp, len); + hunkix += len; + + expression_dereference(&express, MDL); + break; + + case 'N': + f = fmt + 1; + fmt = strchr (fmt, '.'); + if (!fmt) { + parse_warn (cfile, + "malformed %s (bug!)", + "enumeration format"); + goto parse_exit; + } + token = next_token (&val, + (unsigned *)0, cfile); + if (!is_identifier (token)) { + parse_warn (cfile, + "identifier expected"); + goto parse_exit; + } + e = find_enumeration_value (f, fmt - f, + &len, val); + if (!e) { + parse_warn (cfile, + "unknown value"); + goto parse_exit; + } + dp = &e -> value; + goto alloc; + + case '6': + if (!parse_ip6_addr(cfile, &ip_addr)) + goto exit; + len = ip_addr.len; + dp = ip_addr.iabuf; + goto alloc; + + case 'I': /* IP address. */ + if (!parse_ip_addr (cfile, &ip_addr)) + goto exit; + len = ip_addr.len; + dp = ip_addr.iabuf; + + alloc: + if (hunkix + len > sizeof hunkbuf) { + parse_warn (cfile, + "option data buffer %s", + "overflow"); + goto parse_exit; + } + memcpy (&hunkbuf [hunkix], dp, len); + hunkix += len; + break; + + case 'L': /* Unsigned 32-bit integer... */ + case 'l': /* Signed 32-bit integer... */ + token = next_token (&val, + (unsigned *)0, cfile); + if ((token != NUMBER) && + (token != NUMBER_OR_NAME)) { + need_number: + parse_warn (cfile, + "expecting number."); + if (token != SEMI) + goto parse_exit; + else + goto exit; + } + convert_num (cfile, buf, val, 0, 32); + len = 4; + dp = buf; + goto alloc; + + case 's': /* Signed 16-bit integer. */ + case 'S': /* Unsigned 16-bit integer. */ + token = next_token (&val, + (unsigned *)0, cfile); + if ((token != NUMBER) && + (token != NUMBER_OR_NAME)) + goto need_number; + convert_num (cfile, buf, val, 0, 16); + len = 2; + dp = buf; + goto alloc; + + case 'b': /* Signed 8-bit integer. */ + case 'B': /* Unsigned 8-bit integer. */ + token = next_token (&val, + (unsigned *)0, cfile); + if ((token != NUMBER) && + (token != NUMBER_OR_NAME)) + goto need_number; + convert_num (cfile, buf, val, 0, 8); + len = 1; + dp = buf; + goto alloc; + + case 'f': /* Boolean flag. */ + token = next_token (&val, + (unsigned *)0, cfile); + if (!is_identifier (token)) { + parse_warn (cfile, + "expecting identifier."); + bad_flag: + if (token != SEMI) + goto parse_exit; + else + goto exit; + } + if (!strcasecmp (val, "true") + || !strcasecmp (val, "on")) + buf [0] = 1; + else if (!strcasecmp (val, "false") + || !strcasecmp (val, "off")) + buf [0] = 0; + else { + parse_warn (cfile, + "expecting boolean."); + goto bad_flag; + } + len = 1; + dp = buf; + goto alloc; + + case 'Z': /* Zero-length option */ + token = peek_token(&val, (unsigned *)0, cfile); + if (token != SEMI) { + parse_warn(cfile, + "semicolon expected."); + goto parse_exit; + } + len = 0; + buf[0] = '\0'; + break; + + default: + log_error ("parse_option_param: Bad format %c", + *fmt); + goto parse_exit; + } + } + token = next_token (&val, (unsigned *)0, cfile); + } while (*fmt && token == COMMA); + + if (token != SEMI) { + parse_warn (cfile, "semicolon expected."); + goto parse_exit; + } + + bp = (struct buffer *)0; + if (!buffer_allocate (&bp, hunkix + nul_term, MDL)) + log_fatal ("no memory to store option declaration."); + memcpy (bp -> data, hunkbuf, hunkix + nul_term); + + if (!option_cache_allocate (oc, MDL)) + log_fatal ("out of memory allocating option cache."); + + (*oc) -> data.buffer = bp; + (*oc) -> data.data = &bp -> data [0]; + (*oc) -> data.terminated = nul_term; + (*oc) -> data.len = hunkix; + option_reference(&(*oc)->option, option, MDL); + option_dereference(&option, MDL); + return 1; + +parse_exit: + if (express != NULL) + expression_dereference(&express, MDL); + skip_to_semi (cfile); +exit: + option_dereference(&option, MDL); + + return 0; +} + +/* Consider merging parse_cshl into this. */ + +int parse_X (cfile, buf, max) + struct parse *cfile; + u_int8_t *buf; + unsigned max; +{ + int token; + const char *val; + unsigned len; + + token = peek_token (&val, (unsigned *)0, cfile); + if (token == NUMBER_OR_NAME || token == NUMBER) { + len = 0; + do { + token = next_token (&val, (unsigned *)0, cfile); + if (token != NUMBER && token != NUMBER_OR_NAME) { + parse_warn (cfile, + "expecting hexadecimal constant."); + skip_to_semi (cfile); + return 0; + } + convert_num (cfile, &buf [len], val, 16, 8); + if (len++ > max) { + parse_warn (cfile, + "hexadecimal constant too long."); + skip_to_semi (cfile); + return 0; + } + token = peek_token (&val, (unsigned *)0, cfile); + if (token == COLON) + token = next_token (&val, + (unsigned *)0, cfile); + } while (token == COLON); + val = (char *)buf; + } else if (token == STRING) { + skip_token(&val, &len, cfile); + if (len + 1 > max) { + parse_warn (cfile, "string constant too long."); + skip_to_semi (cfile); + return 0; + } + memcpy (buf, val, len + 1); + } else { + parse_warn (cfile, "expecting string or hexadecimal data"); + skip_to_semi (cfile); + return 0; + } + return len; +} + +int parse_warn (struct parse *cfile, const char *fmt, ...) +{ + va_list list; + char lexbuf [256]; + char mbuf [1024]; + char fbuf [1024]; + unsigned i, lix; + + do_percentm (mbuf, fmt); + /* %Audit% This is log output. %2004.06.17,Safe% + * If we truncate we hope the user can get a hint from the log. + */ + snprintf (fbuf, sizeof fbuf, "%s line %d: %s", + cfile -> tlname, cfile -> lexline, mbuf); + + va_start (list, fmt); + vsnprintf (mbuf, sizeof mbuf, fbuf, list); + va_end (list); + + lix = 0; + for (i = 0; + cfile -> token_line [i] && i < (cfile -> lexchar - 1); i++) { + if (lix < (sizeof lexbuf) - 1) + lexbuf [lix++] = ' '; + if (cfile -> token_line [i] == '\t') { + for (; lix < (sizeof lexbuf) - 1 && (lix & 7); lix++) + lexbuf [lix] = ' '; + } + } + lexbuf [lix] = 0; + +#ifndef DEBUG + syslog (LOG_ERR, "%s", mbuf); + syslog (LOG_ERR, "%s", cfile -> token_line); + if (cfile -> lexchar < 81) + syslog (LOG_ERR, "%s^", lexbuf); +#endif + + if (log_perror) { + IGNORE_RET (write (STDERR_FILENO, mbuf, strlen (mbuf))); + IGNORE_RET (write (STDERR_FILENO, "\n", 1)); + IGNORE_RET (write (STDERR_FILENO, cfile -> token_line, + strlen (cfile -> token_line))); + IGNORE_RET (write (STDERR_FILENO, "\n", 1)); + if (cfile -> lexchar < 81) + IGNORE_RET (write (STDERR_FILENO, lexbuf, lix)); + IGNORE_RET (write (STDERR_FILENO, "^\n", 2)); + } + + cfile -> warnings_occurred = 1; + + return 0; +} + +struct expression * +parse_domain_list(struct parse *cfile, int compress) +{ + const char *val; + enum dhcp_token token = SEMI; + struct expression *t = NULL; + unsigned len, clen = 0; + int result; + unsigned char compbuf[256 * NS_MAXCDNAME]; + const unsigned char *dnptrs[256], **lastdnptr; + + memset(compbuf, 0, sizeof(compbuf)); + memset(dnptrs, 0, sizeof(dnptrs)); + dnptrs[0] = compbuf; + lastdnptr = &dnptrs[255]; + + do { + /* Consume the COMMA token if peeked. */ + if (token == COMMA) + skip_token(&val, NULL, cfile); + + /* Get next (or first) value. */ + token = next_token(&val, &len, cfile); + + if (token != STRING) { + parse_warn(cfile, "Expecting a domain string."); + return NULL; + } + + /* If compression pointers are enabled, compress. If not, + * just pack the names in series into the buffer. + */ + if (compress) { + result = MRns_name_compress(val, compbuf + clen, + sizeof(compbuf) - clen, + dnptrs, lastdnptr); + + if (result < 0) { + parse_warn(cfile, "Error compressing domain " + "list: %m"); + return NULL; + } + + clen += result; + } else { + result = MRns_name_pton(val, compbuf + clen, + sizeof(compbuf) - clen); + + /* result == 1 means the input was fully qualified. + * result == 0 means the input wasn't. + * result == -1 means bad things. + */ + if (result < 0) { + parse_warn(cfile, "Error assembling domain " + "list: %m"); + return NULL; + } + + /* + * We need to figure out how many bytes to increment + * our buffer pointer since pton doesn't tell us. + */ + while (compbuf[clen] != 0) + clen += compbuf[clen] + 1; + + /* Count the last label (0). */ + clen++; + } + + if (clen > sizeof(compbuf)) + log_fatal("Impossible error at %s:%d", MDL); + + token = peek_token(&val, NULL, cfile); + } while (token == COMMA); + + if (!make_const_data(&t, compbuf, clen, 1, 1, MDL)) + log_fatal("No memory for domain list object."); + + return t; +} + diff --git a/common/print.c b/common/print.c new file mode 100644 index 0000000..dfe0593 --- /dev/null +++ b/common/print.c @@ -0,0 +1,1519 @@ +/* print.c + + Turn data structures into printable text. */ + +/* + * Copyright (c) 2009-2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2007 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" + +int db_time_format = DEFAULT_TIME_FORMAT; + +char *quotify_string (const char *s, const char *file, int line) +{ + unsigned len = 0; + const char *sp; + char *buf, *nsp; + + for (sp = s; sp && *sp; sp++) { + if (*sp == ' ') + len++; + else if (!isascii ((int)*sp) || !isprint ((int)*sp)) + len += 4; + else if (*sp == '"' || *sp == '\\') + len += 2; + else + len++; + } + + buf = dmalloc (len + 1, file, line); + if (buf) { + nsp = buf; + for (sp = s; sp && *sp; sp++) { + if (*sp == ' ') + *nsp++ = ' '; + else if (!isascii ((int)*sp) || !isprint ((int)*sp)) { + sprintf (nsp, "\\%03o", + *(const unsigned char *)sp); + nsp += 4; + } else if (*sp == '"' || *sp == '\\') { + *nsp++ = '\\'; + *nsp++ = *sp; + } else + *nsp++ = *sp; + } + *nsp++ = 0; + } + return buf; +} + +char *quotify_buf (const unsigned char *s, unsigned len, + const char *file, int line) +{ + unsigned nulen = 0; + char *buf, *nsp; + int i; + + for (i = 0; i < len; i++) { + if (s [i] == ' ') + nulen++; + else if (!isascii (s [i]) || !isprint (s [i])) + nulen += 4; + else if (s [i] == '"' || s [i] == '\\') + nulen += 2; + else + nulen++; + } + + buf = dmalloc (nulen + 1, MDL); + if (buf) { + nsp = buf; + for (i = 0; i < len; i++) { + if (s [i] == ' ') + *nsp++ = ' '; + else if (!isascii (s [i]) || !isprint (s [i])) { + sprintf (nsp, "\\%03o", s [i]); + nsp += 4; + } else if (s [i] == '"' || s [i] == '\\') { + *nsp++ = '\\'; + *nsp++ = s [i]; + } else + *nsp++ = s [i]; + } + *nsp++ = 0; + } + return buf; +} + +char *print_base64 (const unsigned char *buf, unsigned len, + const char *file, int line) +{ + char *s, *b; + unsigned bl; + int i; + unsigned val, extra; + static char to64 [] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + bl = ((len * 4 + 2) / 3) + 1; + b = dmalloc (bl + 1, file, line); + if (!b) + return (char *)0; + + i = 0; + s = b; + while (i != len) { + val = buf [i++]; + extra = val & 3; + val = val >> 2; + *s++ = to64 [val]; + if (i == len) { + *s++ = to64 [extra << 4]; + *s++ = '='; + break; + } + val = (extra << 8) + buf [i++]; + extra = val & 15; + val = val >> 4; + *s++ = to64 [val]; + if (i == len) { + *s++ = to64 [extra << 2]; + *s++ = '='; + break; + } + val = (extra << 8) + buf [i++]; + extra = val & 0x3f; + val = val >> 6; + *s++ = to64 [val]; + *s++ = to64 [extra]; + } + if (!len) + *s++ = '='; + *s++ = 0; + if (s > b + bl + 1) + abort (); + return b; +} + +char *print_hw_addr (htype, hlen, data) + const int htype; + const int hlen; + const unsigned char *data; +{ + static char habuf [49]; + char *s; + int i; + + if (hlen <= 0) + habuf [0] = 0; + else { + s = habuf; + for (i = 0; i < hlen; i++) { + sprintf (s, "%02x", data [i]); + s += strlen (s); + *s++ = ':'; + } + *--s = 0; + } + return habuf; +} + +void print_lease (lease) + struct lease *lease; +{ + struct tm *t; + char tbuf [32]; + + log_debug (" Lease %s", + piaddr (lease -> ip_addr)); + + t = gmtime (&lease -> starts); + strftime (tbuf, sizeof tbuf, "%Y/%m/%d %H:%M:%S", t); + log_debug (" start %s", tbuf); + + t = gmtime (&lease -> ends); + strftime (tbuf, sizeof tbuf, "%Y/%m/%d %H:%M:%S", t); + log_debug (" end %s", tbuf); + + if (lease -> hardware_addr.hlen) + log_debug (" hardware addr = %s", + print_hw_addr (lease -> hardware_addr.hbuf [0], + lease -> hardware_addr.hlen - 1, + &lease -> hardware_addr.hbuf [1])); + log_debug (" host %s ", + lease -> host ? lease -> host -> name : "<none>"); +} + +#if defined (DEBUG_PACKET) +void dump_packet_option (struct option_cache *oc, + struct packet *packet, + struct lease *lease, + struct client_state *client, + struct option_state *in_options, + struct option_state *cfg_options, + struct binding_scope **scope, + struct universe *u, void *foo) +{ + const char *name, *dot; + struct data_string ds; + memset (&ds, 0, sizeof ds); + + if (u != &dhcp_universe) { + name = u -> name; + dot = "."; + } else { + name = ""; + dot = ""; + } + if (evaluate_option_cache (&ds, packet, lease, client, + in_options, cfg_options, scope, oc, MDL)) { + log_debug (" option %s%s%s %s;\n", + name, dot, oc -> option -> name, + pretty_print_option (oc -> option, + ds.data, ds.len, 1, 1)); + data_string_forget (&ds, MDL); + } +} + +void dump_packet (tp) + struct packet *tp; +{ + struct dhcp_packet *tdp = tp -> raw; + + log_debug ("packet length %d", tp -> packet_length); + log_debug ("op = %d htype = %d hlen = %d hops = %d", + tdp -> op, tdp -> htype, tdp -> hlen, tdp -> hops); + log_debug ("xid = %x secs = %ld flags = %x", + tdp -> xid, (unsigned long)tdp -> secs, tdp -> flags); + log_debug ("ciaddr = %s", inet_ntoa (tdp -> ciaddr)); + log_debug ("yiaddr = %s", inet_ntoa (tdp -> yiaddr)); + log_debug ("siaddr = %s", inet_ntoa (tdp -> siaddr)); + log_debug ("giaddr = %s", inet_ntoa (tdp -> giaddr)); + log_debug ("chaddr = %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x", + ((unsigned char *)(tdp -> chaddr)) [0], + ((unsigned char *)(tdp -> chaddr)) [1], + ((unsigned char *)(tdp -> chaddr)) [2], + ((unsigned char *)(tdp -> chaddr)) [3], + ((unsigned char *)(tdp -> chaddr)) [4], + ((unsigned char *)(tdp -> chaddr)) [5]); + log_debug ("filename = %s", tdp -> file); + log_debug ("server_name = %s", tdp -> sname); + if (tp -> options_valid) { + int i; + + for (i = 0; i < tp -> options -> universe_count; i++) { + if (tp -> options -> universes [i]) { + option_space_foreach (tp, (struct lease *)0, + (struct client_state *)0, + (struct option_state *)0, + tp -> options, + &global_scope, + universes [i], 0, + dump_packet_option); + } + } + } + log_debug ("%s", ""); +} +#endif + +void dump_raw (buf, len) + const unsigned char *buf; + unsigned len; +{ + int i; + char lbuf [80]; + int lbix = 0; + +/* + 1 2 3 4 5 6 7 +01234567890123456789012345678901234567890123456789012345678901234567890123 +280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................. +*/ + + memset(lbuf, ' ', 79); + lbuf [79] = 0; + + for (i = 0; i < len; i++) { + if ((i & 15) == 0) { + if (lbix) { + lbuf[53]=' '; + lbuf[54]=' '; + lbuf[55]=' '; + lbuf[73]='\0'; + log_info ("%s", lbuf); + } + memset(lbuf, ' ', 79); + lbuf [79] = 0; + sprintf (lbuf, "%03x:", i); + lbix = 4; + } else if ((i & 7) == 0) + lbuf [lbix++] = ' '; + + if(isprint(buf[i])) { + lbuf[56+(i%16)]=buf[i]; + } else { + lbuf[56+(i%16)]='.'; + } + + sprintf (&lbuf [lbix], " %02x", buf [i]); + lbix += 3; + lbuf[lbix]=' '; + + } + lbuf[53]=' '; + lbuf[54]=' '; + lbuf[55]=' '; + lbuf[73]='\0'; + log_info ("%s", lbuf); +} + +void hash_dump (table) + struct hash_table *table; +{ + int i; + struct hash_bucket *bp; + + if (!table) + return; + + for (i = 0; i < table -> hash_count; i++) { + if (!table -> buckets [i]) + continue; + log_info ("hash bucket %d:", i); + for (bp = table -> buckets [i]; bp; bp = bp -> next) { + if (bp -> len) + dump_raw (bp -> name, bp -> len); + else + log_info ("%s", (const char *)bp -> name); + } + } +} + +/* + * print a string as hex. This only outputs + * colon separated hex list no matter what + * the input looks like. See print_hex + * for a function that prints either cshl + * or a string if all bytes are printible + * It only uses limit characters from buf + * and doesn't do anything if buf == NULL + * + * len - length of data + * data - input data + * limit - length of buf to use + * buf - output buffer + */ +void print_hex_only (len, data, limit, buf) + unsigned len; + const u_int8_t *data; + unsigned limit; + char *buf; +{ + unsigned i; + + if ((buf == NULL) || (limit < 3)) + return; + + for (i = 0; (i < limit / 3) && (i < len); i++) { + sprintf(&buf[i*3], "%02x:", data[i]); + } + buf[(i * 3) - 1] = 0; + return; +} + +/* + * print a string as either text if all the characters + * are printable or colon separated hex if they aren't + * + * len - length of data + * data - input data + * limit - length of buf to use + * buf - output buffer + */ +void print_hex_or_string (len, data, limit, buf) + unsigned len; + const u_int8_t *data; + unsigned limit; + char *buf; +{ + unsigned i; + if ((buf == NULL) || (limit < 3)) + return; + + for (i = 0; (i < (limit - 3)) && (i < len); i++) { + if (!isascii(data[i]) || !isprint(data[i])) { + print_hex_only(len, data, limit, buf); + return; + } + } + + buf[0] = '"'; + i = len; + if (i > (limit - 3)) + i = limit - 3; + memcpy(&buf[1], data, i); + buf[i + 1] = '"'; + buf[i + 2] = 0; + return; +} + +/* + * print a string as either hex or text + * using static buffers to hold the output + * + * len - length of data + * data - input data + * limit - length of buf + * buf_num - the output buffer to use + */ +#define HBLEN 1024 +char *print_hex(len, data, limit, buf_num) + unsigned len; + const u_int8_t *data; + unsigned limit; + unsigned buf_num; +{ + static char hex_buf_1[HBLEN + 1]; + static char hex_buf_2[HBLEN + 1]; + static char hex_buf_3[HBLEN + 1]; + char *hex_buf; + + switch(buf_num) { + case 0: + hex_buf = hex_buf_1; + if (limit >= sizeof(hex_buf_1)) + limit = sizeof(hex_buf_1); + break; + case 1: + hex_buf = hex_buf_2; + if (limit >= sizeof(hex_buf_2)) + limit = sizeof(hex_buf_2); + break; + case 2: + hex_buf = hex_buf_3; + if (limit >= sizeof(hex_buf_3)) + limit = sizeof(hex_buf_3); + break; + default: + return(NULL); + } + + print_hex_or_string(len, data, limit, hex_buf); + return(hex_buf); +} + +#define DQLEN 80 + +char *print_dotted_quads (len, data) + unsigned len; + const u_int8_t *data; +{ + static char dq_buf [DQLEN + 1]; + int i; + char *s; + + s = &dq_buf [0]; + + i = 0; + + /* %Audit% Loop bounds checks to 21 bytes. %2004.06.17,Safe% + * The sprintf can't exceed 18 bytes, and since the loop enforces + * 21 bytes of space per iteration at no time can we exit the + * loop without at least 3 bytes spare. + */ + do { + sprintf (s, "%u.%u.%u.%u, ", + data [i], data [i + 1], data [i + 2], data [i + 3]); + s += strlen (s); + i += 4; + } while ((s - &dq_buf [0] > DQLEN - 21) && + i + 3 < len); + if (i == len) + s [-2] = 0; + else + strcpy (s, "..."); + return dq_buf; +} + +char *print_dec_1 (val) + unsigned long val; +{ + static char vbuf [32]; + sprintf (vbuf, "%lu", val); + return vbuf; +} + +char *print_dec_2 (val) + unsigned long val; +{ + static char vbuf [32]; + sprintf (vbuf, "%lu", val); + return vbuf; +} + +static unsigned print_subexpression (struct expression *, char *, unsigned); + +static unsigned print_subexpression (expr, buf, len) + struct expression *expr; + char *buf; + unsigned len; +{ + unsigned rv, left; + const char *s; + + switch (expr -> op) { + case expr_none: + if (len > 3) { + strcpy (buf, "nil"); + return 3; + } + break; + + case expr_match: + if (len > 7) { + strcpy (buf, "(match)"); + return 7; + } + break; + + case expr_check: + rv = 10 + strlen (expr -> data.check -> name); + if (len > rv) { + sprintf (buf, "(check %s)", + expr -> data.check -> name); + return rv; + } + break; + + case expr_equal: + if (len > 6) { + rv = 4; + strcpy (buf, "(eq "); + rv += print_subexpression (expr -> data.equal [0], + buf + rv, len - rv - 2); + buf [rv++] = ' '; + rv += print_subexpression (expr -> data.equal [1], + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_not_equal: + if (len > 7) { + rv = 5; + strcpy (buf, "(neq "); + rv += print_subexpression (expr -> data.equal [0], + buf + rv, len - rv - 2); + buf [rv++] = ' '; + rv += print_subexpression (expr -> data.equal [1], + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_regex_match: + if (len > 10) { + rv = 4; + strcpy(buf, "(regex "); + rv += print_subexpression(expr->data.equal[0], + buf + rv, len - rv - 2); + buf[rv++] = ' '; + rv += print_subexpression(expr->data.equal[1], + buf + rv, len - rv - 1); + buf[rv++] = ')'; + buf[rv] = 0; + return rv; + } + break; + + case expr_substring: + if (len > 11) { + rv = 8; + strcpy (buf, "(substr "); + rv += print_subexpression (expr -> data.substring.expr, + buf + rv, len - rv - 3); + buf [rv++] = ' '; + rv += print_subexpression + (expr -> data.substring.offset, + buf + rv, len - rv - 2); + buf [rv++] = ' '; + rv += print_subexpression (expr -> data.substring.len, + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_suffix: + if (len > 10) { + rv = 8; + strcpy (buf, "(suffix "); + rv += print_subexpression (expr -> data.suffix.expr, + buf + rv, len - rv - 2); + if (len > rv) + buf [rv++] = ' '; + rv += print_subexpression (expr -> data.suffix.len, + buf + rv, len - rv - 1); + if (len > rv) + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_lcase: + if (len > 9) { + rv = 7; + strcpy(buf, "(lcase "); + rv += print_subexpression(expr->data.lcase, + buf + rv, len - rv - 1); + buf[rv++] = ')'; + buf[rv] = 0; + return rv; + } + break; + + case expr_ucase: + if (len > 9) { + rv = 7; + strcpy(buf, "(ucase "); + rv += print_subexpression(expr->data.ucase, + buf + rv, len - rv - 1); + buf[rv++] = ')'; + buf[rv] = 0; + return rv; + } + break; + + case expr_concat: + if (len > 10) { + rv = 8; + strcpy (buf, "(concat "); + rv += print_subexpression (expr -> data.concat [0], + buf + rv, len - rv - 2); + buf [rv++] = ' '; + rv += print_subexpression (expr -> data.concat [1], + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_pick_first_value: + if (len > 8) { + rv = 6; + strcpy (buf, "(pick1st "); + rv += print_subexpression + (expr -> data.pick_first_value.car, + buf + rv, len - rv - 2); + buf [rv++] = ' '; + rv += print_subexpression + (expr -> data.pick_first_value.cdr, + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_host_lookup: + rv = 15 + strlen (expr -> data.host_lookup -> hostname); + if (len > rv) { + sprintf (buf, "(dns-lookup %s)", + expr -> data.host_lookup -> hostname); + return rv; + } + break; + + case expr_and: + s = "and"; + binop: + rv = strlen (s); + if (len > rv + 4) { + buf [0] = '('; + strcpy (&buf [1], s); + rv += 1; + buf [rv++] = ' '; + rv += print_subexpression (expr -> data.and [0], + buf + rv, len - rv - 2); + buf [rv++] = ' '; + rv += print_subexpression (expr -> data.and [1], + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_or: + s = "or"; + goto binop; + + case expr_add: + s = "+"; + goto binop; + + case expr_subtract: + s = "-"; + goto binop; + + case expr_multiply: + s = "*"; + goto binop; + + case expr_divide: + s = "/"; + goto binop; + + case expr_remainder: + s = "%"; + goto binop; + + case expr_binary_and: + s = "&"; + goto binop; + + case expr_binary_or: + s = "|"; + goto binop; + + case expr_binary_xor: + s = "^"; + goto binop; + + case expr_not: + if (len > 6) { + rv = 5; + strcpy (buf, "(not "); + rv += print_subexpression (expr -> data.not, + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_config_option: + s = "cfg-option"; + goto dooption; + + case expr_option: + s = "option"; + dooption: + rv = strlen (s) + 2 + (strlen (expr -> data.option -> name) + + strlen (expr -> data.option -> universe -> name)); + if (len > rv) { + sprintf (buf, "(option %s.%s)", + expr -> data.option -> universe -> name, + expr -> data.option -> name); + return rv; + } + break; + + case expr_hardware: + if (len > 10) { + strcpy (buf, "(hardware)"); + return 10; + } + break; + + case expr_packet: + if (len > 10) { + rv = 8; + strcpy (buf, "(substr "); + rv += print_subexpression (expr -> data.packet.offset, + buf + rv, len - rv - 2); + buf [rv++] = ' '; + rv += print_subexpression (expr -> data.packet.len, + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_const_data: + s = print_hex_1 (expr -> data.const_data.len, + expr -> data.const_data.data, len); + rv = strlen (s); + if (rv >= len) + rv = len - 1; + strncpy (buf, s, rv); + buf [rv] = 0; + return rv; + + case expr_encapsulate: + rv = 13; + strcpy (buf, "(encapsulate "); + rv += expr -> data.encapsulate.len; + if (rv + 2 > len) + rv = len - 2; + strncpy (buf, + (const char *)expr -> data.encapsulate.data, rv - 13); + buf [rv++] = ')'; + buf [rv++] = 0; + break; + + case expr_extract_int8: + if (len > 7) { + rv = 6; + strcpy (buf, "(int8 "); + rv += print_subexpression (expr -> data.extract_int, + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_extract_int16: + if (len > 8) { + rv = 7; + strcpy (buf, "(int16 "); + rv += print_subexpression (expr -> data.extract_int, + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_extract_int32: + if (len > 8) { + rv = 7; + strcpy (buf, "(int32 "); + rv += print_subexpression (expr -> data.extract_int, + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_encode_int8: + if (len > 7) { + rv = 6; + strcpy (buf, "(to-int8 "); + rv += print_subexpression (expr -> data.encode_int, + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_encode_int16: + if (len > 8) { + rv = 7; + strcpy (buf, "(to-int16 "); + rv += print_subexpression (expr -> data.encode_int, + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_encode_int32: + if (len > 8) { + rv = 7; + strcpy (buf, "(to-int32 "); + rv += print_subexpression (expr -> data.encode_int, + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_const_int: + s = print_dec_1 (expr -> data.const_int); + rv = strlen (s); + if (len > rv) { + strcpy (buf, s); + return rv; + } + break; + + case expr_exists: + rv = 10 + (strlen (expr -> data.option -> name) + + strlen (expr -> data.option -> universe -> name)); + if (len > rv) { + sprintf (buf, "(exists %s.%s)", + expr -> data.option -> universe -> name, + expr -> data.option -> name); + return rv; + } + break; + + case expr_variable_exists: + rv = 10 + strlen (expr -> data.variable); + if (len > rv) { + sprintf (buf, "(defined %s)", expr -> data.variable); + return rv; + } + break; + + case expr_variable_reference: + rv = strlen (expr -> data.variable); + if (len > rv) { + sprintf (buf, "%s", expr -> data.variable); + return rv; + } + break; + + case expr_known: + s = "known"; + astring: + rv = strlen (s); + if (len > rv) { + strcpy (buf, s); + return rv; + } + break; + + case expr_leased_address: + s = "leased-address"; + goto astring; + + case expr_client_state: + s = "client-state"; + goto astring; + + case expr_host_decl_name: + s = "host-decl-name"; + goto astring; + + case expr_lease_time: + s = "lease-time"; + goto astring; + + case expr_static: + s = "static"; + goto astring; + + case expr_filename: + s = "filename"; + goto astring; + + case expr_sname: + s = "server-name"; + goto astring; + + case expr_reverse: + if (len > 11) { + rv = 13; + strcpy (buf, "(reverse "); + rv += print_subexpression (expr -> data.reverse.width, + buf + rv, len - rv - 2); + buf [rv++] = ' '; + rv += print_subexpression (expr -> data.reverse.buffer, + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_binary_to_ascii: + if (len > 5) { + rv = 9; + strcpy (buf, "(b2a "); + rv += print_subexpression (expr -> data.b2a.base, + buf + rv, len - rv - 4); + buf [rv++] = ' '; + rv += print_subexpression (expr -> data.b2a.width, + buf + rv, len - rv - 3); + buf [rv++] = ' '; + rv += print_subexpression (expr -> data.b2a.separator, + buf + rv, len - rv - 2); + buf [rv++] = ' '; + rv += print_subexpression (expr -> data.b2a.buffer, + buf + rv, len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_dns_transaction: + rv = 10; + if (len < rv + 2) { + buf [0] = '('; + strcpy (&buf [1], "ns-update "); + while (len < rv + 2) { + rv += print_subexpression + (expr -> data.dns_transaction.car, + buf + rv, len - rv - 2); + buf [rv++] = ' '; + expr = expr -> data.dns_transaction.cdr; + } + buf [rv - 1] = ')'; + buf [rv] = 0; + return rv; + } + return 0; + + case expr_ns_delete: + s = "delete"; + left = 4; + goto dodnsupd; + case expr_ns_exists: + s = "exists"; + left = 4; + goto dodnsupd; + case expr_ns_not_exists: + s = "not_exists"; + left = 4; + goto dodnsupd; + case expr_ns_add: + s = "update"; + left = 5; + dodnsupd: + rv = strlen (s); + if (len > strlen (s) + 1) { + buf [0] = '('; + strcpy (buf + 1, s); + rv++; + buf [rv++] = ' '; + s = print_dec_1 (expr -> data.ns_add.rrclass); + if (len > rv + strlen (s) + left) { + strcpy (&buf [rv], s); + rv += strlen (&buf [rv]); + } + buf [rv++] = ' '; + left--; + s = print_dec_1 (expr -> data.ns_add.rrtype); + if (len > rv + strlen (s) + left) { + strcpy (&buf [rv], s); + rv += strlen (&buf [rv]); + } + buf [rv++] = ' '; + left--; + rv += print_subexpression + (expr -> data.ns_add.rrname, + buf + rv, len - rv - left); + buf [rv++] = ' '; + left--; + rv += print_subexpression + (expr -> data.ns_add.rrdata, + buf + rv, len - rv - left); + buf [rv++] = ' '; + left--; + rv += print_subexpression + (expr -> data.ns_add.ttl, + buf + rv, len - rv - left); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_null: + if (len > 6) { + strcpy (buf, "(null)"); + return 6; + } + break; + case expr_funcall: + rv = 12 + strlen (expr -> data.funcall.name); + if (len > rv + 1) { + strcpy (buf, "(funcall "); + strcpy (buf + 9, expr -> data.funcall.name); + buf [rv++] = ' '; + rv += print_subexpression + (expr -> data.funcall.arglist, buf + rv, + len - rv - 1); + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_arg: + rv = print_subexpression (expr -> data.arg.val, buf, len); + if (expr -> data.arg.next && rv + 2 < len) { + buf [rv++] = ' '; + rv += print_subexpression (expr -> data.arg.next, + buf, len); + if (rv + 1 < len) + buf [rv++] = 0; + return rv; + } + break; + + case expr_function: + rv = 9; + if (len > rv + 1) { + struct string_list *foo; + strcpy (buf, "(function"); + for (foo = expr -> data.func -> args; + foo; foo = foo -> next) { + if (len > rv + 2 + strlen (foo -> string)) { + buf [rv - 1] = ' '; + strcpy (&buf [rv], foo -> string); + rv += strlen (foo -> string); + } + } + buf [rv++] = ')'; + buf [rv] = 0; + return rv; + } + break; + + case expr_gethostname: + if (len > 13) { + strcpy(buf, "(gethostname)"); + return 13; + } + break; + + default: + log_fatal("Impossible case at %s:%d (undefined expression " + "%d).", MDL, expr->op); + break; + } + return 0; +} + +void print_expression (name, expr) + const char *name; + struct expression *expr; +{ + char buf [1024]; + + print_subexpression (expr, buf, sizeof buf); + log_info ("%s: %s", name, buf); +} + +int token_print_indent_concat (FILE *file, int col, int indent, + const char *prefix, + const char *suffix, ...) +{ + va_list list; + unsigned len; + char *s, *t, *u; + + va_start (list, suffix); + s = va_arg (list, char *); + len = 0; + while (s) { + len += strlen (s); + s = va_arg (list, char *); + } + va_end (list); + + t = dmalloc (len + 1, MDL); + if (!t) + log_fatal ("token_print_indent: no memory for copy buffer"); + + va_start (list, suffix); + s = va_arg (list, char *); + u = t; + while (s) { + len = strlen (s); + strcpy (u, s); + u += len; + s = va_arg (list, char *); + } + va_end (list); + + col = token_print_indent (file, col, indent, + prefix, suffix, t); + dfree (t, MDL); + return col; +} + +int token_indent_data_string (FILE *file, int col, int indent, + const char *prefix, const char *suffix, + struct data_string *data) +{ + int i; + char *buf; + char obuf [3]; + + /* See if this is just ASCII. */ + for (i = 0; i < data -> len; i++) + if (!isascii (data -> data [i]) || + !isprint (data -> data [i])) + break; + + /* If we have a purely ASCII string, output it as text. */ + if (i == data -> len) { + buf = dmalloc (data -> len + 3, MDL); + if (buf) { + buf [0] = '"'; + memcpy (buf + 1, data -> data, data -> len); + buf [data -> len + 1] = '"'; + buf [data -> len + 2] = 0; + i = token_print_indent (file, col, indent, + prefix, suffix, buf); + dfree (buf, MDL); + return i; + } + } + + for (i = 0; i < data -> len; i++) { + sprintf (obuf, "%2.2x", data -> data [i]); + col = token_print_indent (file, col, indent, + i == 0 ? prefix : "", + (i + 1 == data -> len + ? suffix + : ""), obuf); + if (i + 1 != data -> len) + col = token_print_indent (file, col, indent, + prefix, suffix, ":"); + } + return col; +} + +int token_print_indent (FILE *file, int col, int indent, + const char *prefix, + const char *suffix, const char *buf) +{ + int len = 0; + if (prefix != NULL) + len += strlen (prefix); + if (buf != NULL) + len += strlen (buf); + + if (col + len > 79) { + if (indent + len < 79) { + indent_spaces (file, indent); + col = indent; + } else { + indent_spaces (file, col); + col = len > 79 ? 0 : 79 - len - 1; + } + } else if (prefix && *prefix) { + fputs (prefix, file); + col += strlen (prefix); + } + if ((buf != NULL) && (*buf != 0)) { + fputs (buf, file); + col += strlen(buf); + } + if (suffix && *suffix) { + if (col + strlen (suffix) > 79) { + indent_spaces (file, indent); + col = indent; + } else { + fputs (suffix, file); + col += strlen (suffix); + } + } + return col; +} + +void indent_spaces (FILE *file, int indent) +{ + int i; + fputc ('\n', file); + for (i = 0; i < indent; i++) + fputc (' ', file); +} + +#if defined (NSUPDATE) +#if defined (DEBUG_DNS_UPDATES) +/* + * direction outbound (messages to the dns server) + * inbound (messages from the dns server) + * ddns_cb is the control block associated with the message + * result is the result from the dns code. For outbound calls + * it is from the call to pass the message to the dns library. + * For inbound calls it is from the event returned by the library. + * + * For outbound messages we print whatever we think is interesting + * from the control block. + * For inbound messages we only print the transaction id pointer + * and the result and expect that the user will match them up as + * necessary. Note well: the transaction information is opaque to + * us so we simply print the pointer to it. This should be sufficient + * to match requests and replys in a short sequence but is awkward + * when trying to use it for longer sequences. + */ +void +print_dns_status (int direction, + struct dhcp_ddns_cb *ddns_cb, + isc_result_t result) +{ + char obuf[1024]; + char *s = obuf, *end = &obuf[sizeof(obuf)-2]; + char *en; + const char *result_str; + char ddns_address[ + sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + + if (direction == DDNS_PRINT_INBOUND) { + log_info("DDNS reply: id ptr %p, result: %s", + ddns_cb->transaction, isc_result_totext(result)); + return; + } + + /* + * To avoid having to figure out if any of the strings + * aren't NULL terminated, just 0 the whole string + */ + memset(obuf, 0, 1024); + + en = "DDNS request: id ptr "; + if (s + strlen(en) + 16 < end) { + sprintf(s, "%s%p", en, ddns_cb->transaction); + s += strlen(s); + } else { + goto bailout; + } + + switch (ddns_cb->state) { + case DDNS_STATE_ADD_FW_NXDOMAIN: + en = " add forward "; + break; + case DDNS_STATE_ADD_FW_YXDHCID: + en = " modify forward "; + break; + + case DDNS_STATE_ADD_PTR: + en = " add reverse "; + break; + + case DDNS_STATE_REM_FW_YXDHCID: + en = " remove forward "; + break; + + case DDNS_STATE_REM_FW_NXRR: + en = " remove rrset "; + break; + + case DDNS_STATE_REM_PTR: + en = " remove reverse "; + break; + + case DDNS_STATE_CLEANUP: + en = " cleanup "; + break; + + default: + en = " unknown state "; + break; + } + + switch (ddns_cb->state) { + case DDNS_STATE_ADD_FW_NXDOMAIN: + case DDNS_STATE_ADD_FW_YXDHCID: + case DDNS_STATE_REM_FW_YXDHCID: + case DDNS_STATE_REM_FW_NXRR: + strcpy(ddns_address, piaddr(ddns_cb->address)); + if (s + strlen(en) + strlen(ddns_address) + + ddns_cb->fwd_name.len + 5 < end) { + sprintf(s, "%s%s for %.*s", en, ddns_address, + ddns_cb->fwd_name.len, + ddns_cb->fwd_name.data); + s += strlen(s); + } else { + goto bailout; + } + break; + + case DDNS_STATE_ADD_PTR: + case DDNS_STATE_REM_PTR: + if (s + strlen(en) + ddns_cb->fwd_name.len + + ddns_cb->rev_name.len + 5 < end) { + sprintf(s, "%s%.*s for %.*s", en, + ddns_cb->fwd_name.len, + ddns_cb->fwd_name.data, + ddns_cb->rev_name.len, + ddns_cb->rev_name.data); + s += strlen(s); + } else { + goto bailout; + } + break; + + case DDNS_STATE_CLEANUP: + default: + if (s + strlen(en) < end) { + sprintf(s, "%s", en); + s += strlen(s); + } else { + goto bailout; + } + break; + } + + en = " zone: "; + if (s + strlen(en) + strlen((char *)ddns_cb->zone_name) < end) { + sprintf(s, "%s%s", en, ddns_cb->zone_name); + s += strlen(s); + } else { + goto bailout; + } + + en = " dhcid: "; + if (ddns_cb->dhcid.len > 0) { + if (s + strlen(en) + ddns_cb->dhcid.len-1 < end) { + strcpy(s, en); + s += strlen(s); + strncpy(s, (char *)ddns_cb->dhcid.data+1, + ddns_cb->dhcid.len-1); + s += strlen(s); + } else { + goto bailout; + } + } else { + en = " dhcid: <empty>"; + if (s + strlen(en) < end) { + strcpy(s, en); + s += strlen(s); + } else { + goto bailout; + } + } + + en = " ttl: "; + if (s + strlen(en) + 10 < end) { + sprintf(s, "%s%ld", en, ddns_cb->ttl); + s += strlen(s); + } else { + goto bailout; + } + + en = " result: "; + result_str = isc_result_totext(result); + if (s + strlen(en) + strlen(result_str) < end) { + sprintf(s, "%s%s", en, result_str); + s += strlen(s); + } else { + goto bailout; + } + + bailout: + /* + * We either finished building the string or ran out + * of space, print whatever we have in case it is useful + */ + log_info("%s", obuf); + + return; +} +#endif +#endif /* NSUPDATE */ + +/* Format the given time as "A; # B", where A is the format + * used by the parser, and B is the local time, for humans. + */ +const char * +print_time(TIME t) +{ + static char buf[sizeof("epoch 9223372036854775807; " + "# Wed Jun 30 21:49:08 2147483647")]; + static char buf1[sizeof("# Wed Jun 30 21:49:08 2147483647")]; + time_t since_epoch; + /* The string: "6 2147483647/12/31 23:59:60;" + * is smaller than the other, used to declare the buffer size, so + * we can use one buffer for both. + */ + + if (t == MAX_TIME) + return "never;"; + + if (t < 0) + return NULL; + + /* For those lucky enough to have a 128-bit time_t, ensure that + * whatever (corrupt) value we're given doesn't exceed the static + * buffer. + */ +#if (MAX_TIME > 0x7fffffffffffffff) + if (t > 0x7fffffffffffffff) + return NULL; +#endif + + if (db_time_format == LOCAL_TIME_FORMAT) { + since_epoch = mktime(localtime(&t)); + if ((strftime(buf1, sizeof(buf1), + "# %a %b %d %H:%M:%S %Y", + localtime(&t)) == 0) || + (snprintf(buf, sizeof(buf), "epoch %lu; %s", + (unsigned long)since_epoch, buf1) >= sizeof(buf))) + return NULL; + + } else { + /* No bounds check for the year is necessary - in this case, + * strftime() will run out of space and assert an error. + */ + if (strftime(buf, sizeof(buf), "%w %Y/%m/%d %H:%M:%S;", + gmtime(&t)) == 0) + return NULL; + } + + return buf; +} diff --git a/common/raw.c b/common/raw.c new file mode 100644 index 0000000..a15f8ee --- /dev/null +++ b/common/raw.c @@ -0,0 +1,133 @@ +/* raw.c + + BSD raw socket interface code... */ + +/* XXX + + It's not clear how this should work, and that lack of clarity is + terribly detrimental to the NetBSD 1.1 kernel - it crashes and + burns. + + Using raw sockets ought to be a big win over using BPF or something + like it, because you don't need to deal with the complexities of + the physical layer, but it appears not to be possible with existing + raw socket implementations. This may be worth revisiting in the + future. For now, this code can probably be considered a curiosity. + Sigh. */ + +/* + * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" + +#if defined (USE_RAW_SEND) +#include <sys/uio.h> + +/* Generic interface registration routine... */ +void if_register_send (info) + struct interface_info *info; +{ + struct sockaddr_in name; + int sock; + struct socklist *tmp; + int flag; + + /* Set up the address we're going to connect to. */ + name.sin_family = AF_INET; + name.sin_port = local_port; + name.sin_addr.s_addr = htonl (INADDR_BROADCAST); + memset (name.sin_zero, 0, sizeof (name.sin_zero)); + + /* List addresses on which we're listening. */ + if (!quiet_interface_discovery) + log_info ("Sending on %s, port %d", + piaddr (info -> address), htons (local_port)); + if ((sock = socket (AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) + log_fatal ("Can't create dhcp socket: %m"); + + /* Set the BROADCAST option so that we can broadcast DHCP responses. */ + flag = 1; + if (setsockopt (sock, SOL_SOCKET, SO_BROADCAST, + &flag, sizeof flag) < 0) + log_fatal ("Can't set SO_BROADCAST option on dhcp socket: %m"); + + /* Set the IP_HDRINCL flag so that we can supply our own IP + headers... */ + if (setsockopt (sock, IPPROTO_IP, IP_HDRINCL, &flag, sizeof flag) < 0) + log_fatal ("Can't set IP_HDRINCL flag: %m"); + + info -> wfdesc = sock; + if (!quiet_interface_discovery) + log_info ("Sending on Raw/%s%s%s", + info -> name, + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} + +void if_deregister_send (info) + struct interface_info *info; +{ + close (info -> wfdesc); + info -> wfdesc = -1; + + if (!quiet_interface_discovery) + log_info ("Disabling output on Raw/%s%s%s", + info -> name, + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} + +size_t send_packet (interface, packet, raw, len, from, to, hto) + struct interface_info *interface; + struct packet *packet; + struct dhcp_packet *raw; + size_t len; + struct in_addr from; + struct sockaddr_in *to; + struct hardware *hto; +{ + unsigned char buf [256]; + int bufp = 0; + struct iovec iov [2]; + int result; + + /* Assemble the headers... */ + assemble_udp_ip_header (interface, buf, &bufp, from.s_addr, + to -> sin_addr.s_addr, to -> sin_port, + (unsigned char *)raw, len); + + /* Fire it off */ + iov [0].iov_base = (char *)buf; + iov [0].iov_len = bufp; + iov [1].iov_base = (char *)raw; + iov [1].iov_len = len; + + result = writev(interface -> wfdesc, iov, 2); + if (result < 0) + log_error ("send_packet: %m"); + return result; +} +#endif /* USE_SOCKET_SEND */ diff --git a/common/resolv.c b/common/resolv.c new file mode 100644 index 0000000..526cebf --- /dev/null +++ b/common/resolv.c @@ -0,0 +1,189 @@ +/* resolv.c + + Parser for /etc/resolv.conf file. */ + +/* + * Copyright (c) 2009,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2007 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" + +struct name_server *name_servers; +struct domain_search_list *domains; +char path_resolv_conf [] = _PATH_RESOLV_CONF; + +void read_resolv_conf (parse_time) + TIME parse_time; +{ + int file; + struct parse *cfile; + const char *val; + int token; + struct name_server *sp, *sl, *ns; + struct domain_search_list *dp, *dl, *nd; + isc_result_t status; + + if ((file = open (path_resolv_conf, O_RDONLY)) < 0) { + log_error ("Can't open %s: %m", path_resolv_conf); + return; + } + + cfile = NULL; + status = new_parse(&cfile, file, NULL, 0, path_resolv_conf, 1); + if (status != ISC_R_SUCCESS || cfile == NULL) + return; + + do { + token = next_token (&val, (unsigned *)0, cfile); + if (token == END_OF_FILE) + break; + else if (token == EOL) + continue; + else if (token == DOMAIN || token == SEARCH) { + do { + struct domain_search_list *nd, **dp; + char *dn; + + dn = parse_host_name (cfile); + if (!dn) + break; + + dp = &domains; + for (nd = domains; nd; nd = nd -> next) { + dp = &nd -> next; + if (!strcmp (nd -> domain, dn)) + break; + } + if (!nd) { + nd = new_domain_search_list (MDL); + if (!nd) + log_fatal ("No memory for %s", + dn); + nd -> next = + (struct domain_search_list *)0; + *dp = nd; + nd -> domain = dn; + } + nd -> rcdate = parse_time; + token = peek_token (&val, + (unsigned *)0, cfile); + } while (token != EOL); + if (token != EOL) { + parse_warn (cfile, + "junk after domain declaration"); + skip_to_semi (cfile); + } + skip_token(&val, (unsigned *)0, cfile); + } else if (token == NAMESERVER) { + struct name_server *ns, **sp; + struct iaddr iaddr; + + parse_ip_addr (cfile, &iaddr); + + sp = &name_servers; + for (ns = name_servers; ns; ns = ns -> next) { + sp = &ns -> next; + if (!memcmp (&ns -> addr.sin_addr, + iaddr.iabuf, iaddr.len)) + break; + } + if (!ns) { + ns = new_name_server (MDL); + if (!ns) + log_fatal ("No memory for nameserver %s", + piaddr (iaddr)); + ns -> next = (struct name_server *)0; + *sp = ns; + memcpy (&ns -> addr.sin_addr, + iaddr.iabuf, iaddr.len); +#ifdef HAVE_SA_LEN + ns -> addr.sin_len = sizeof ns -> addr; +#endif + ns -> addr.sin_family = AF_INET; + ns -> addr.sin_port = htons (53); + memset (ns -> addr.sin_zero, 0, + sizeof ns -> addr.sin_zero); + } + ns -> rcdate = parse_time; + skip_to_semi (cfile); + } else + skip_to_semi (cfile); /* Ignore what we don't grok. */ + } while (1); + skip_token(&val, (unsigned *)0, cfile); + + /* Lose servers that are no longer in /etc/resolv.conf. */ + sl = (struct name_server *)0; + for (sp = name_servers; sp; sp = ns) { + ns = sp -> next; + if (sp -> rcdate != parse_time) { + if (sl) + sl -> next = sp -> next; + else + name_servers = sp -> next; + /* We can't actually free the name server structure, + because somebody might be hanging on to it. If + your /etc/resolv.conf file changes a lot, this + could be a noticeable memory leak. */ + } else + sl = sp; + } + + /* Lose domains that are no longer in /etc/resolv.conf. */ + dl = (struct domain_search_list *)0; + for (dp = domains; dp; dp = nd) { + nd = dp -> next; + if (dp -> rcdate != parse_time) { + if (dl) + dl -> next = dp -> next; + else + domains = dp -> next; + free_domain_search_list (dp, MDL); + } else + dl = dp; + } + end_parse (&cfile); +} + +/* Pick a name server from the /etc/resolv.conf file. */ + +struct name_server *first_name_server () +{ + static TIME rcdate; + struct stat st; + + /* Check /etc/resolv.conf and reload it if it's changed. */ + if (cur_time > rcdate) { + if (stat (path_resolv_conf, &st) < 0) { + log_error ("Can't stat %s", path_resolv_conf); + return (struct name_server *)0; + } + if (st.st_mtime > rcdate) { + rcdate = cur_time + 1; + + read_resolv_conf (rcdate); + } + } + + return name_servers; +} diff --git a/common/socket.c b/common/socket.c new file mode 100644 index 0000000..5467a35 --- /dev/null +++ b/common/socket.c @@ -0,0 +1,1225 @@ +/* socket.c + + BSD socket interface code... */ + +/* + * Copyright (c) 2004-2015 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +/* SO_BINDTODEVICE support added by Elliot Poger (poger@leland.stanford.edu). + * This sockopt allows a socket to be bound to a particular interface, + * thus enabling the use of DHCPD on a multihomed host. + * If SO_BINDTODEVICE is defined in your system header files, the use of + * this sockopt will be automatically enabled. + * I have implemented it under Linux; other systems should be doable also. + */ + +#include "dhcpd.h" +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/uio.h> +#include <sys/uio.h> + +#if defined(sun) && defined(USE_V4_PKTINFO) +#include <sys/sysmacros.h> +#include <net/if.h> +#include <sys/sockio.h> +#include <net/if_dl.h> +#include <sys/dlpi.h> +#endif + +#ifdef USE_SOCKET_FALLBACK +# if !defined (USE_SOCKET_SEND) +# define if_register_send if_register_fallback +# define send_packet send_fallback +# define if_reinitialize_send if_reinitialize_fallback +# endif +#endif + +#if defined(DHCPv6) +/* + * XXX: this is gross. we need to go back and overhaul the API for socket + * handling. + */ +static int no_global_v6_socket = 0; +static unsigned int global_v6_socket_references = 0; +static int global_v6_socket = -1; + +static void if_register_multicast(struct interface_info *info); +#endif + +/* + * We can use a single socket for AF_INET (similar to AF_INET6) on all + * interfaces configured for DHCP if the system has support for IP_PKTINFO + * and IP_RECVPKTINFO (for example Solaris 11). + */ +#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO) +static unsigned int global_v4_socket_references = 0; +static int global_v4_socket = -1; +#endif + +/* + * If we can't bind() to a specific interface, then we can only have + * a single socket. This variable insures that we don't try to listen + * on two sockets. + */ +#if !defined(SO_BINDTODEVICE) && !defined(USE_FALLBACK) +static int once = 0; +#endif /* !defined(SO_BINDTODEVICE) && !defined(USE_FALLBACK) */ + +/* Reinitializes the specified interface after an address change. This + is not required for packet-filter APIs. */ + +#if defined (USE_SOCKET_SEND) || defined (USE_SOCKET_FALLBACK) +void if_reinitialize_send (info) + struct interface_info *info; +{ +#if 0 +#ifndef USE_SOCKET_RECEIVE + once = 0; + close (info -> wfdesc); +#endif + if_register_send (info); +#endif +} +#endif + +#ifdef USE_SOCKET_RECEIVE +void if_reinitialize_receive (info) + struct interface_info *info; +{ +#if 0 + once = 0; + close (info -> rfdesc); + if_register_receive (info); +#endif +} +#endif + +#if defined (USE_SOCKET_SEND) || \ + defined (USE_SOCKET_RECEIVE) || \ + defined (USE_SOCKET_FALLBACK) +/* Generic interface registration routine... */ +int +if_register_socket(struct interface_info *info, int family, + int *do_multicast, struct in6_addr *linklocal6) +{ + struct sockaddr_storage name; + int name_len; + int sock; + int flag; + int domain; +#ifdef DHCPv6 + struct sockaddr_in6 *addr6; +#endif + struct sockaddr_in *addr; + + /* INSIST((family == AF_INET) || (family == AF_INET6)); */ + +#if !defined(SO_BINDTODEVICE) && !defined(USE_FALLBACK) + /* Make sure only one interface is registered. */ + if (once) { + log_fatal ("The standard socket API can only support %s", + "hosts with a single network interface."); + } + once = 1; +#endif + + /* + * Set up the address we're going to bind to, depending on the + * address family. + */ + memset(&name, 0, sizeof(name)); + switch (family) { +#ifdef DHCPv6 + case AF_INET6: + addr6 = (struct sockaddr_in6 *)&name; + addr6->sin6_family = AF_INET6; + addr6->sin6_port = local_port; + if (linklocal6) { + memcpy(&addr6->sin6_addr, + linklocal6, + sizeof(addr6->sin6_addr)); + addr6->sin6_scope_id = if_nametoindex(info->name); + } +#ifdef HAVE_SA_LEN + addr6->sin6_len = sizeof(*addr6); +#endif + name_len = sizeof(*addr6); + domain = PF_INET6; + if ((info->flags & INTERFACE_STREAMS) == INTERFACE_UPSTREAM) { + *do_multicast = 0; + } + break; +#endif /* DHCPv6 */ + + case AF_INET: + default: + addr = (struct sockaddr_in *)&name; + addr->sin_family = AF_INET; + addr->sin_port = local_port; + memcpy(&addr->sin_addr, + &local_address, + sizeof(addr->sin_addr)); +#ifdef HAVE_SA_LEN + addr->sin_len = sizeof(*addr); +#endif + name_len = sizeof(*addr); + domain = PF_INET; + break; + } + + /* Make a socket... */ + sock = socket(domain, SOCK_DGRAM, IPPROTO_UDP); + if (sock < 0) { + log_fatal("Can't create dhcp socket: %m"); + } + + /* Set the REUSEADDR option so that we don't fail to start if + we're being restarted. */ + flag = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (char *)&flag, sizeof(flag)) < 0) { + log_fatal("Can't set SO_REUSEADDR option on dhcp socket: %m"); + } + + /* Set the BROADCAST option so that we can broadcast DHCP responses. + We shouldn't do this for fallback devices, and we can detect that + a device is a fallback because it has no ifp structure. */ + if (info->ifp && + (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, + (char *)&flag, sizeof(flag)) < 0)) { + log_fatal("Can't set SO_BROADCAST option on dhcp socket: %m"); + } + +#if defined(DHCPv6) && defined(SO_REUSEPORT) + /* + * We only set SO_REUSEPORT on AF_INET6 sockets, so that multiple + * daemons can bind to their own sockets and get data for their + * respective interfaces. This does not (and should not) affect + * DHCPv4 sockets; we can't yet support BSD sockets well, much + * less multiple sockets. Make sense only with multicast. + */ + if (local_family == AF_INET6) { + flag = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, + (char *)&flag, sizeof(flag)) < 0) { + log_fatal("Can't set SO_REUSEPORT option on dhcp " + "socket: %m"); + } + } +#endif + + /* Bind the socket to this interface's IP address. */ + if (bind(sock, (struct sockaddr *)&name, name_len) < 0) { + log_error("Can't bind to dhcp address: %m"); + log_error("Please make sure there is no other dhcp server"); + log_error("running and that there's no entry for dhcp or"); + log_error("bootp in /etc/inetd.conf. Also make sure you"); + log_error("are not running HP JetAdmin software, which"); + log_fatal("includes a bootp server."); + } + +#if defined(SO_BINDTODEVICE) + /* Bind this socket to this interface. */ + if ((local_family != AF_INET6) && (info->ifp != NULL) && + setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, + (char *)(info -> ifp), sizeof(*(info -> ifp))) < 0) { + log_fatal("setsockopt: SO_BINDTODEVICE: %m"); + } +#endif + + /* IP_BROADCAST_IF instructs the kernel which interface to send + * IP packets whose destination address is 255.255.255.255. These + * will be treated as subnet broadcasts on the interface identified + * by ip address (info -> primary_address). This is only known to + * be defined in SCO system headers, and may not be defined in all + * releases. + */ +#if defined(SCO) && defined(IP_BROADCAST_IF) + if (info->address_count && + setsockopt(sock, IPPROTO_IP, IP_BROADCAST_IF, &info->addresses[0], + sizeof(info->addresses[0])) < 0) + log_fatal("Can't set IP_BROADCAST_IF on dhcp socket: %m"); +#endif + +#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO) + /* + * If we turn on IP_RECVPKTINFO we will be able to receive + * the interface index information of the received packet. + */ + if (family == AF_INET) { + int on = 1; + if (setsockopt(sock, IPPROTO_IP, IP_RECVPKTINFO, + &on, sizeof(on)) != 0) { + log_fatal("setsockopt: IPV_RECVPKTINFO: %m"); + } + } +#endif + +#ifdef DHCPv6 + /* + * If we turn on IPV6_PKTINFO, we will be able to receive + * additional information, such as the destination IP address. + * We need this to spot unicast packets. + */ + if (family == AF_INET6) { + int on = 1; +#ifdef IPV6_RECVPKTINFO + /* RFC3542 */ + if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, + &on, sizeof(on)) != 0) { + log_fatal("setsockopt: IPV6_RECVPKTINFO: %m"); + } +#else + /* RFC2292 */ + if (setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO, + &on, sizeof(on)) != 0) { + log_fatal("setsockopt: IPV6_PKTINFO: %m"); + } +#endif + } + +#endif /* DHCPv6 */ + + return sock; +} + +#ifdef DHCPv6 +void set_multicast_hop_limit(struct interface_info* info, int hop_limit) { + if (setsockopt(info->wfdesc, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &hop_limit, sizeof(int)) < 0) { + log_fatal("setMulticaseHopLimit: IPV6_MULTICAST_HOPS: %m"); + } + + log_debug("Setting hop count limit to %d for interface %s", + hop_limit, info->name); + +} +#endif /* DHCPv6 */ + +#endif /* USE_SOCKET_SEND || USE_SOCKET_RECEIVE || USE_SOCKET_FALLBACK */ + +#if defined (USE_SOCKET_SEND) || defined (USE_SOCKET_FALLBACK) +void if_register_send (info) + struct interface_info *info; +{ +#ifndef USE_SOCKET_RECEIVE + info->wfdesc = if_register_socket(info, AF_INET, 0, NULL); + /* If this is a normal IPv4 address, get the hardware address. */ + if (strcmp(info->name, "fallback") != 0) + get_hw_addr(info->name, &info->hw_address); +#if defined (USE_SOCKET_FALLBACK) + /* Fallback only registers for send, but may need to receive as + well. */ + info->rfdesc = info->wfdesc; +#endif +#else + info->wfdesc = info->rfdesc; +#endif + if (!quiet_interface_discovery) + log_info ("Sending on Socket/%s%s%s", + info->name, + (info->shared_network ? "/" : ""), + (info->shared_network ? + info->shared_network->name : "")); +} + +#if defined (USE_SOCKET_SEND) +void if_deregister_send (info) + struct interface_info *info; +{ +#ifndef USE_SOCKET_RECEIVE + close (info -> wfdesc); +#endif + info -> wfdesc = -1; + + if (!quiet_interface_discovery) + log_info ("Disabling output on Socket/%s%s%s", + info -> name, + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} +#endif /* USE_SOCKET_SEND */ +#endif /* USE_SOCKET_SEND || USE_SOCKET_FALLBACK */ + +#ifdef USE_SOCKET_RECEIVE +void if_register_receive (info) + struct interface_info *info; +{ + +#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO) + if (global_v4_socket_references == 0) { + global_v4_socket = if_register_socket(info, AF_INET, 0, NULL); + if (global_v4_socket < 0) { + /* + * if_register_socket() fatally logs if it fails to + * create a socket, this is just a sanity check. + */ + log_fatal("Failed to create AF_INET socket %s:%d", + MDL); + } + } + + info->rfdesc = global_v4_socket; + global_v4_socket_references++; +#else + /* If we're using the socket API for sending and receiving, + we don't need to register this interface twice. */ + info->rfdesc = if_register_socket(info, AF_INET, 0, NULL); +#endif /* IP_PKTINFO... */ + /* If this is a normal IPv4 address, get the hardware address. */ + if (strcmp(info->name, "fallback") != 0) + get_hw_addr(info->name, &info->hw_address); + + if (!quiet_interface_discovery) + log_info ("Listening on Socket/%s%s%s", + info->name, + (info->shared_network ? "/" : ""), + (info->shared_network ? + info->shared_network->name : "")); +} + +void if_deregister_receive (info) + struct interface_info *info; +{ +#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO) + /* Dereference the global v4 socket. */ + if ((info->rfdesc == global_v4_socket) && + (info->wfdesc == global_v4_socket) && + (global_v4_socket_references > 0)) { + global_v4_socket_references--; + info->rfdesc = -1; + } else { + log_fatal("Impossible condition at %s:%d", MDL); + } + + if (global_v4_socket_references == 0) { + close(global_v4_socket); + global_v4_socket = -1; + } +#else + close(info->rfdesc); + info->rfdesc = -1; +#endif /* IP_PKTINFO... */ + if (!quiet_interface_discovery) + log_info ("Disabling input on Socket/%s%s%s", + info -> name, + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} +#endif /* USE_SOCKET_RECEIVE */ + + +#ifdef DHCPv6 +/* + * This function joins the interface to DHCPv6 multicast groups so we will + * receive multicast messages. + */ +static void +if_register_multicast(struct interface_info *info) { + int sock = info->rfdesc; + struct ipv6_mreq mreq; + + if (inet_pton(AF_INET6, All_DHCP_Relay_Agents_and_Servers, + &mreq.ipv6mr_multiaddr) <= 0) { + log_fatal("inet_pton: unable to convert '%s'", + All_DHCP_Relay_Agents_and_Servers); + } + mreq.ipv6mr_interface = if_nametoindex(info->name); + if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, + &mreq, sizeof(mreq)) < 0) { + log_fatal("setsockopt: IPV6_JOIN_GROUP: %m"); + } + + /* + * The relay agent code sets the streams so you know which way + * is up and down. But a relay agent shouldn't join to the + * Server address, or else you get fun loops. So up or down + * doesn't matter, we're just using that config to sense this is + * a relay agent. + */ + if ((info->flags & INTERFACE_STREAMS) == 0) { + if (inet_pton(AF_INET6, All_DHCP_Servers, + &mreq.ipv6mr_multiaddr) <= 0) { + log_fatal("inet_pton: unable to convert '%s'", + All_DHCP_Servers); + } + mreq.ipv6mr_interface = if_nametoindex(info->name); + if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, + &mreq, sizeof(mreq)) < 0) { + log_fatal("setsockopt: IPV6_JOIN_GROUP: %m"); + } + } +} + +void +if_register6(struct interface_info *info, int do_multicast) { + /* Bounce do_multicast to a stack variable because we may change it. */ + int req_multi = do_multicast; + + if (no_global_v6_socket) { + log_fatal("Impossible condition at %s:%d", MDL); + } + + if (global_v6_socket_references == 0) { + global_v6_socket = if_register_socket(info, AF_INET6, + &req_multi, NULL); + if (global_v6_socket < 0) { + /* + * if_register_socket() fatally logs if it fails to + * create a socket, this is just a sanity check. + */ + log_fatal("Impossible condition at %s:%d", MDL); + } else { + log_info("Bound to *:%d", ntohs(local_port)); + } + } + + info->rfdesc = global_v6_socket; + info->wfdesc = global_v6_socket; + global_v6_socket_references++; + + if (req_multi) + if_register_multicast(info); + + get_hw_addr(info->name, &info->hw_address); + + if (!quiet_interface_discovery) { + if (info->shared_network != NULL) { + log_info("Listening on Socket/%d/%s/%s", + global_v6_socket, info->name, + info->shared_network->name); + log_info("Sending on Socket/%d/%s/%s", + global_v6_socket, info->name, + info->shared_network->name); + } else { + log_info("Listening on Socket/%s", info->name); + log_info("Sending on Socket/%s", info->name); + } + } +} + +/* + * Register an IPv6 socket bound to the link-local address of + * the argument interface (used by clients on a multiple interface box, + * vs. a server or a relay using the global IPv6 socket and running + * *only* in a single instance). + */ +void +if_register_linklocal6(struct interface_info *info) { + int sock; + int count; + struct in6_addr *addr6 = NULL; + int req_multi = 0; + + if (global_v6_socket >= 0) { + log_fatal("Impossible condition at %s:%d", MDL); + } + + no_global_v6_socket = 1; + + /* get the (?) link-local address */ + for (count = 0; count < info->v6address_count; count++) { + addr6 = &info->v6addresses[count]; + if (IN6_IS_ADDR_LINKLOCAL(addr6)) + break; + } + + if (!addr6) { + log_fatal("no link-local IPv6 address for %s", info->name); + } + + sock = if_register_socket(info, AF_INET6, &req_multi, addr6); + + if (sock < 0) { + log_fatal("if_register_socket for %s fails", info->name); + } + + info->rfdesc = sock; + info->wfdesc = sock; + + get_hw_addr(info->name, &info->hw_address); + + if (!quiet_interface_discovery) { + if (info->shared_network != NULL) { + log_info("Listening on Socket/%d/%s/%s", + global_v6_socket, info->name, + info->shared_network->name); + log_info("Sending on Socket/%d/%s/%s", + global_v6_socket, info->name, + info->shared_network->name); + } else { + log_info("Listening on Socket/%s", info->name); + log_info("Sending on Socket/%s", info->name); + } + } +} + +void +if_deregister6(struct interface_info *info) { + /* client case */ + if (no_global_v6_socket) { + close(info->rfdesc); + info->rfdesc = -1; + info->wfdesc = -1; + } else if ((info->rfdesc == global_v6_socket) && + (info->wfdesc == global_v6_socket) && + (global_v6_socket_references > 0)) { + /* Dereference the global v6 socket. */ + global_v6_socket_references--; + info->rfdesc = -1; + info->wfdesc = -1; + } else { + log_fatal("Impossible condition at %s:%d", MDL); + } + + if (!quiet_interface_discovery) { + if (info->shared_network != NULL) { + log_info("Disabling input on Socket/%s/%s", info->name, + info->shared_network->name); + log_info("Disabling output on Socket/%s/%s", info->name, + info->shared_network->name); + } else { + log_info("Disabling input on Socket/%s", info->name); + log_info("Disabling output on Socket/%s", info->name); + } + } + + if (!no_global_v6_socket && + (global_v6_socket_references == 0)) { + close(global_v6_socket); + global_v6_socket = -1; + + log_info("Unbound from *:%d", ntohs(local_port)); + } +} +#endif /* DHCPv6 */ + +#if defined (USE_SOCKET_SEND) || defined (USE_SOCKET_FALLBACK) +ssize_t send_packet (interface, packet, raw, len, from, to, hto) + struct interface_info *interface; + struct packet *packet; + struct dhcp_packet *raw; + size_t len; + struct in_addr from; + struct sockaddr_in *to; + struct hardware *hto; +{ + int result; +#ifdef IGNORE_HOSTUNREACH + int retry = 0; + do { +#endif +#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO) + struct in_pktinfo pktinfo; + + if (interface->ifp != NULL) { + memset(&pktinfo, 0, sizeof (pktinfo)); + pktinfo.ipi_ifindex = interface->ifp->ifr_index; + if (setsockopt(interface->wfdesc, IPPROTO_IP, + IP_PKTINFO, (char *)&pktinfo, + sizeof(pktinfo)) < 0) + log_fatal("setsockopt: IP_PKTINFO: %m"); + } +#endif + result = sendto (interface -> wfdesc, (char *)raw, len, 0, + (struct sockaddr *)to, sizeof *to); +#ifdef IGNORE_HOSTUNREACH + } while (to -> sin_addr.s_addr == htonl (INADDR_BROADCAST) && + result < 0 && + (errno == EHOSTUNREACH || + errno == ECONNREFUSED) && + retry++ < 10); +#endif + if (result < 0) { + log_error ("send_packet: %m"); + if (errno == ENETUNREACH) + log_error ("send_packet: please consult README file%s", + " regarding broadcast address."); + } + return result; +} + +#endif /* USE_SOCKET_SEND || USE_SOCKET_FALLBACK */ + +#ifdef DHCPv6 +/* + * Solaris 9 is missing the CMSG_LEN and CMSG_SPACE macros, so we will + * synthesize them (based on the BIND 9 technique). + */ + +#ifndef CMSG_LEN +static size_t CMSG_LEN(size_t len) { + size_t hdrlen; + /* + * Cast NULL so that any pointer arithmetic performed by CMSG_DATA + * is correct. + */ + hdrlen = (size_t)CMSG_DATA(((struct cmsghdr *)NULL)); + return hdrlen + len; +} +#endif /* !CMSG_LEN */ + +#ifndef CMSG_SPACE +static size_t CMSG_SPACE(size_t len) { + struct msghdr msg; + struct cmsghdr *cmsgp; + + /* + * XXX: The buffer length is an ad-hoc value, but should be enough + * in a practical sense. + */ + union { + struct cmsghdr cmsg_sizer; + u_int8_t pktinfo_sizer[sizeof(struct cmsghdr) + 1024]; + } dummybuf; + + memset(&msg, 0, sizeof(msg)); + msg.msg_control = &dummybuf; + msg.msg_controllen = sizeof(dummybuf); + + cmsgp = (struct cmsghdr *)&dummybuf; + cmsgp->cmsg_len = CMSG_LEN(len); + + cmsgp = CMSG_NXTHDR(&msg, cmsgp); + if (cmsgp != NULL) { + return (char *)cmsgp - (char *)msg.msg_control; + } else { + return 0; + } +} +#endif /* !CMSG_SPACE */ + +#endif /* DHCPv6 */ + +#if defined(DHCPv6) || \ + (defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && \ + defined(USE_V4_PKTINFO)) +/* + * For both send_packet6() and receive_packet6() we need to allocate + * space for the cmsg header information. We do this once and reuse + * the buffer. We also need the control buf for send_packet() and + * receive_packet() when we use a single socket and IP_PKTINFO to + * send the packet out the correct interface. + */ +static void *control_buf = NULL; +static size_t control_buf_len = 0; + +static void +allocate_cmsg_cbuf(void) { + control_buf_len = CMSG_SPACE(sizeof(struct in6_pktinfo)); + control_buf = dmalloc(control_buf_len, MDL); + return; +} +#endif /* DHCPv6, IP_PKTINFO ... */ + +#ifdef DHCPv6 +/* + * For both send_packet6() and receive_packet6() we need to use the + * sendmsg()/recvmsg() functions rather than the simpler send()/recv() + * functions. + * + * In the case of send_packet6(), we need to do this in order to insure + * that the reply packet leaves on the same interface that it arrived + * on. + * + * In the case of receive_packet6(), we need to do this in order to + * get the IP address the packet was sent to. This is used to identify + * whether a packet is multicast or unicast. + * + * Helpful man pages: recvmsg, readv (talks about the iovec stuff), cmsg. + * + * Also see the sections in RFC 3542 about IPV6_PKTINFO. + */ + +/* Send an IPv6 packet */ +ssize_t send_packet6(struct interface_info *interface, + const unsigned char *raw, size_t len, + struct sockaddr_in6 *to) { + struct msghdr m; + struct iovec v; + struct sockaddr_in6 dst; + int result; + struct in6_pktinfo *pktinfo; + struct cmsghdr *cmsg; + unsigned int ifindex; + + /* + * If necessary allocate space for the control message header. + * The space is common between send and receive. + */ + + if (control_buf == NULL) { + allocate_cmsg_cbuf(); + if (control_buf == NULL) { + log_error("send_packet6: unable to allocate cmsg header"); + return(ENOMEM); + } + } + memset(control_buf, 0, control_buf_len); + + /* + * Initialize our message header structure. + */ + memset(&m, 0, sizeof(m)); + + /* + * Set the target address we're sending to. + * Enforce the scope ID for bogus BSDs. + */ + memcpy(&dst, to, sizeof(dst)); + m.msg_name = &dst; + m.msg_namelen = sizeof(dst); + ifindex = if_nametoindex(interface->name); + if (no_global_v6_socket) + dst.sin6_scope_id = ifindex; + + /* + * Set the data buffer we're sending. (Using this wacky + * "scatter-gather" stuff... we only have a single chunk + * of data to send, so we declare a single vector entry.) + */ + v.iov_base = (char *)raw; + v.iov_len = len; + m.msg_iov = &v; + m.msg_iovlen = 1; + + /* + * Setting the interface is a bit more involved. + * + * We have to create a "control message", and set that to + * define the IPv6 packet information. We could set the + * source address if we wanted, but we can safely let the + * kernel decide what that should be. + */ + m.msg_control = control_buf; + m.msg_controllen = control_buf_len; + cmsg = CMSG_FIRSTHDR(&m); + INSIST(cmsg != NULL); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo)); + pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); + memset(pktinfo, 0, sizeof(*pktinfo)); + pktinfo->ipi6_ifindex = ifindex; + + result = sendmsg(interface->wfdesc, &m, 0); + if (result < 0) { + log_error("send_packet6: %m"); + } + return result; +} +#endif /* DHCPv6 */ + +#ifdef USE_SOCKET_RECEIVE +ssize_t receive_packet (interface, buf, len, from, hfrom) + struct interface_info *interface; + unsigned char *buf; + size_t len; + struct sockaddr_in *from; + struct hardware *hfrom; +{ +#if !(defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO)) + SOCKLEN_T flen = sizeof *from; +#endif + int result; + + /* + * The normal Berkeley socket interface doesn't give us any way + * to know what hardware interface we received the message on, + * but we should at least make sure the structure is emptied. + */ + memset(hfrom, 0, sizeof(*hfrom)); + +#ifdef IGNORE_HOSTUNREACH + int retry = 0; + do { +#endif + +#if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO) + struct msghdr m; + struct iovec v; + struct cmsghdr *cmsg; + struct in_pktinfo *pktinfo; + unsigned int ifindex; + + /* + * If necessary allocate space for the control message header. + * The space is common between send and receive. + */ + if (control_buf == NULL) { + allocate_cmsg_cbuf(); + if (control_buf == NULL) { + log_error("receive_packet: unable to allocate cmsg " + "header"); + return(ENOMEM); + } + } + memset(control_buf, 0, control_buf_len); + + /* + * Initialize our message header structure. + */ + memset(&m, 0, sizeof(m)); + + /* + * Point so we can get the from address. + */ + m.msg_name = from; + m.msg_namelen = sizeof(*from); + + /* + * Set the data buffer we're receiving. (Using this wacky + * "scatter-gather" stuff... but we that doesn't really make + * sense for us, so we use a single vector entry.) + */ + v.iov_base = buf; + v.iov_len = len; + m.msg_iov = &v; + m.msg_iovlen = 1; + + /* + * Getting the interface is a bit more involved. + * + * We set up some space for a "control message". We have + * previously asked the kernel to give us packet + * information (when we initialized the interface), so we + * should get the interface index from that. + */ + m.msg_control = control_buf; + m.msg_controllen = control_buf_len; + + result = recvmsg(interface->rfdesc, &m, 0); + + if (result >= 0) { + /* + * If we did read successfully, then we need to loop + * through the control messages we received and + * find the one with our inteface index. + */ + cmsg = CMSG_FIRSTHDR(&m); + while (cmsg != NULL) { + if ((cmsg->cmsg_level == IPPROTO_IP) && + (cmsg->cmsg_type == IP_PKTINFO)) { + pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg); + ifindex = pktinfo->ipi_ifindex; + /* + * We pass the ifindex back to the caller + * using the unused hfrom parameter avoiding + * interface changes between sockets and + * the discover code. + */ + memcpy(hfrom->hbuf, &ifindex, sizeof(ifindex)); + return (result); + } + cmsg = CMSG_NXTHDR(&m, cmsg); + } + + /* + * We didn't find the necessary control message + * flag it as an error + */ + result = -1; + errno = EIO; + } +#else + result = recvfrom(interface -> rfdesc, (char *)buf, len, 0, + (struct sockaddr *)from, &flen); +#endif /* IP_PKTINFO ... */ +#ifdef IGNORE_HOSTUNREACH + } while (result < 0 && + (errno == EHOSTUNREACH || + errno == ECONNREFUSED) && + retry++ < 10); +#endif + return (result); +} + +#endif /* USE_SOCKET_RECEIVE */ + +#ifdef DHCPv6 +ssize_t +receive_packet6(struct interface_info *interface, + unsigned char *buf, size_t len, + struct sockaddr_in6 *from, struct in6_addr *to_addr, + unsigned int *if_idx) +{ + struct msghdr m; + struct iovec v; + int result; + struct cmsghdr *cmsg; + struct in6_pktinfo *pktinfo; + + /* + * If necessary allocate space for the control message header. + * The space is common between send and receive. + */ + if (control_buf == NULL) { + allocate_cmsg_cbuf(); + if (control_buf == NULL) { + log_error("receive_packet6: unable to allocate cmsg " + "header"); + return(ENOMEM); + } + } + memset(control_buf, 0, control_buf_len); + + /* + * Initialize our message header structure. + */ + memset(&m, 0, sizeof(m)); + + /* + * Point so we can get the from address. + */ + m.msg_name = from; + m.msg_namelen = sizeof(*from); + + /* + * Set the data buffer we're receiving. (Using this wacky + * "scatter-gather" stuff... but we that doesn't really make + * sense for us, so we use a single vector entry.) + */ + v.iov_base = buf; + v.iov_len = len; + m.msg_iov = &v; + m.msg_iovlen = 1; + + /* + * Getting the interface is a bit more involved. + * + * We set up some space for a "control message". We have + * previously asked the kernel to give us packet + * information (when we initialized the interface), so we + * should get the destination address from that. + */ + m.msg_control = control_buf; + m.msg_controllen = control_buf_len; + + result = recvmsg(interface->rfdesc, &m, 0); + + if (result >= 0) { + /* + * If we did read successfully, then we need to loop + * through the control messages we received and + * find the one with our destination address. + */ + cmsg = CMSG_FIRSTHDR(&m); + while (cmsg != NULL) { + if ((cmsg->cmsg_level == IPPROTO_IPV6) && + (cmsg->cmsg_type == IPV6_PKTINFO)) { + pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); + *to_addr = pktinfo->ipi6_addr; + *if_idx = pktinfo->ipi6_ifindex; + + return (result); + } + cmsg = CMSG_NXTHDR(&m, cmsg); + } + + /* + * We didn't find the necessary control message + * flag is as an error + */ + result = -1; + errno = EIO; + } + + return (result); +} +#endif /* DHCPv6 */ + +#if defined (USE_SOCKET_FALLBACK) +/* This just reads in a packet and silently discards it. */ + +isc_result_t fallback_discard (object) + omapi_object_t *object; +{ + char buf [1540]; + struct sockaddr_in from; + SOCKLEN_T flen = sizeof from; + int status; + struct interface_info *interface; + + if (object -> type != dhcp_type_interface) + return DHCP_R_INVALIDARG; + interface = (struct interface_info *)object; + + status = recvfrom (interface -> wfdesc, buf, sizeof buf, 0, + (struct sockaddr *)&from, &flen); +#if defined (DEBUG) + /* Only report fallback discard errors if we're debugging. */ + if (status < 0) { + log_error ("fallback_discard: %m"); + return ISC_R_UNEXPECTED; + } +#else + /* ignore the fact that status value is never used */ + IGNORE_UNUSED(status); +#endif + return ISC_R_SUCCESS; +} +#endif /* USE_SOCKET_FALLBACK */ + +#if defined (USE_SOCKET_SEND) +int can_unicast_without_arp (ip) + struct interface_info *ip; +{ + return 0; +} + +int can_receive_unicast_unconfigured (ip) + struct interface_info *ip; +{ +#if defined (SOCKET_CAN_RECEIVE_UNICAST_UNCONFIGURED) + return 1; +#else + return 0; +#endif +} + +int supports_multiple_interfaces (ip) + struct interface_info *ip; +{ +#if defined(SO_BINDTODEVICE) || \ + (defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && \ + defined(USE_V4_PKTINFO)) + return(1); +#else + return(0); +#endif +} + +/* If we have SO_BINDTODEVICE, set up a fallback interface; otherwise, + do not. */ + +void maybe_setup_fallback () +{ +#if defined (USE_SOCKET_FALLBACK) + isc_result_t status; + struct interface_info *fbi = (struct interface_info *)0; + if (setup_fallback (&fbi, MDL)) { + fbi -> wfdesc = if_register_socket (fbi, AF_INET, 0, NULL); + fbi -> rfdesc = fbi -> wfdesc; + log_info ("Sending on Socket/%s%s%s", + fbi -> name, + (fbi -> shared_network ? "/" : ""), + (fbi -> shared_network ? + fbi -> shared_network -> name : "")); + + status = omapi_register_io_object ((omapi_object_t *)fbi, + if_readsocket, 0, + fallback_discard, 0, 0); + if (status != ISC_R_SUCCESS) + log_fatal ("Can't register I/O handle for %s: %s", + fbi -> name, isc_result_totext (status)); + interface_dereference (&fbi, MDL); + } +#endif +} + + +#if defined(sun) && defined(USE_V4_PKTINFO) +/* This code assumes the existence of SIOCGLIFHWADDR */ +void +get_hw_addr(const char *name, struct hardware *hw) { + struct sockaddr_dl *dladdrp; + int sock, i; + struct lifreq lifr; + + memset(&lifr, 0, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, name, sizeof (lifr.lifr_name)); + /* + * Check if the interface is a virtual or IPMP interface - in those + * cases it has no hw address, so generate a random one. + */ + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0 || + ioctl(sock, SIOCGLIFFLAGS, &lifr) < 0) { + if (sock != -1) + (void) close(sock); + +#ifdef DHCPv6 + /* + * If approrpriate try this with an IPv6 socket + */ + if ((sock = socket(AF_INET6, SOCK_DGRAM, 0)) >= 0 && + ioctl(sock, SIOCGLIFFLAGS, &lifr) >= 0) { + goto flag_check; + } + if (sock != -1) + (void) close(sock); +#endif + log_fatal("Couldn't get interface flags for %s: %m", name); + + } + + flag_check: + if (lifr.lifr_flags & (IFF_VIRTUAL|IFF_IPMP)) { + hw->hlen = sizeof (hw->hbuf); + srandom((long)gethrtime()); + + hw->hbuf[0] = HTYPE_IPMP; + for (i = 1; i < hw->hlen; ++i) { + hw->hbuf[i] = random() % 256; + } + + if (sock != -1) + (void) close(sock); + return; + } + + if (ioctl(sock, SIOCGLIFHWADDR, &lifr) < 0) + log_fatal("Couldn't get interface hardware address for %s: %m", + name); + dladdrp = (struct sockaddr_dl *)&lifr.lifr_addr; + hw->hlen = dladdrp->sdl_alen+1; + switch (dladdrp->sdl_type) { + case DL_CSMACD: /* IEEE 802.3 */ + case DL_ETHER: + hw->hbuf[0] = HTYPE_ETHER; + break; + case DL_TPR: + hw->hbuf[0] = HTYPE_IEEE802; + break; + case DL_FDDI: + hw->hbuf[0] = HTYPE_FDDI; + break; + case DL_IB: + hw->hbuf[0] = HTYPE_INFINIBAND; + break; + default: + log_fatal("%s: unsupported DLPI MAC type %lu", name, + (unsigned long)dladdrp->sdl_type); + } + + memcpy(hw->hbuf+1, LLADDR(dladdrp), hw->hlen-1); + + if (sock != -1) + (void) close(sock); +} +#endif /* defined(sun) */ + +#endif /* USE_SOCKET_SEND */ diff --git a/common/tables.c b/common/tables.c new file mode 100644 index 0000000..ccd9701 --- /dev/null +++ b/common/tables.c @@ -0,0 +1,1410 @@ +/* tables.c + + Tables of information... */ + +/* + * Copyright (c) 2011-2012 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2009 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" + +/* XXXDPN: Moved here from hash.c, when it moved to libomapi. Not sure + where these really belong. */ +HASH_FUNCTIONS (group, const char *, struct group_object, group_hash_t, + group_reference, group_dereference, do_string_hash) +HASH_FUNCTIONS (universe, const char *, struct universe, universe_hash_t, 0, 0, + do_case_hash) +HASH_FUNCTIONS (option_name, const char *, struct option, option_name_hash_t, + option_reference, option_dereference, do_case_hash) +HASH_FUNCTIONS (option_code, const unsigned *, struct option, + option_code_hash_t, option_reference, option_dereference, + do_number_hash) + +/* DHCP Option names, formats and codes, from RFC1533. + + Format codes: + + I - IPv4 address + 6 - IPv6 address + l - 32-bit signed integer + L - 32-bit unsigned integer + s - 16-bit signed integer + S - 16-bit unsigned integer + b - 8-bit signed integer + B - 8-bit unsigned integer + t - ASCII text + T - Lease Time, 32-bit unsigned integer implying a number of seconds from + some event. The special all-ones value means 'infinite'. May either + be printed as a decimal, eg, "3600", or as this name, eg, "infinite". + f - flag (true or false) + A - array of all that precedes (e.g., fIA means array of records of + a flag and an IP address) + a - array of the preceding character (e.g., fIa means a single flag + followed by an array of IP addresses) + U - name of an option space (universe) + F - implicit flag - the presence of the option indicates that the + flag is true. + o - the preceding value is optional. + E - encapsulation, string or colon-separated hex list (the latter + two for parsing). E is followed by a text string containing + the name of the option space to encapsulate, followed by a '.'. + If the E is immediately followed by '.', the applicable vendor + option space is used if one is defined. + e - If an encapsulation directive is not the first thing in the string, + the option scanner requires an efficient way to find the encapsulation. + This is done by placing a 'e' at the beginning of the option. The + 'e' has no other purpose, and is not required if 'E' is the first + thing in the option. + X - either an ASCII string or binary data. On output, the string is + scanned to see if it's printable ASCII and, if so, output as a + quoted string. If not, it's output as colon-separated hex. On + input, the option can be specified either as a quoted string or as + a colon-separated hex list. + N - enumeration. N is followed by a text string containing + the name of the set of enumeration values to parse or emit, + followed by a '.'. The width of the data is specified in the + named enumeration. Named enumerations are tracked in parse.c. + d - Domain name (i.e., FOO or FOO.BAR). + D - Domain list (i.e., example.com eng.example.com) + c - When following a 'D' atom, enables compression pointers. + Z - Zero-length option +*/ + +struct universe dhcp_universe; +static struct option dhcp_options[] = { + { "subnet-mask", "I", &dhcp_universe, 1, 1 }, + { "time-offset", "l", &dhcp_universe, 2, 1 }, + { "routers", "IA", &dhcp_universe, 3, 1 }, + { "time-servers", "IA", &dhcp_universe, 4, 1 }, + { "ien116-name-servers", "IA", &dhcp_universe, 5, 1 }, + { "domain-name-servers", "IA", &dhcp_universe, 6, 1 }, + { "log-servers", "IA", &dhcp_universe, 7, 1 }, + { "cookie-servers", "IA", &dhcp_universe, 8, 1 }, + { "lpr-servers", "IA", &dhcp_universe, 9, 1 }, + { "impress-servers", "IA", &dhcp_universe, 10, 1 }, + { "resource-location-servers", "IA", &dhcp_universe, 11, 1 }, + { "host-name", "t", &dhcp_universe, 12, 1 }, + { "boot-size", "S", &dhcp_universe, 13, 1 }, + { "merit-dump", "t", &dhcp_universe, 14, 1 }, + { "domain-name", "t", &dhcp_universe, 15, 1 }, + { "swap-server", "I", &dhcp_universe, 16, 1 }, + { "root-path", "t", &dhcp_universe, 17, 1 }, + { "extensions-path", "t", &dhcp_universe, 18, 1 }, + { "ip-forwarding", "f", &dhcp_universe, 19, 1 }, + { "non-local-source-routing", "f", &dhcp_universe, 20, 1 }, + { "policy-filter", "IIA", &dhcp_universe, 21, 1 }, + { "max-dgram-reassembly", "S", &dhcp_universe, 22, 1 }, + { "default-ip-ttl", "B", &dhcp_universe, 23, 1 }, + { "path-mtu-aging-timeout", "L", &dhcp_universe, 24, 1 }, + { "path-mtu-plateau-table", "SA", &dhcp_universe, 25, 1 }, + { "interface-mtu", "S", &dhcp_universe, 26, 1 }, + { "all-subnets-local", "f", &dhcp_universe, 27, 1 }, + { "broadcast-address", "I", &dhcp_universe, 28, 1 }, + { "perform-mask-discovery", "f", &dhcp_universe, 29, 1 }, + { "mask-supplier", "f", &dhcp_universe, 30, 1 }, + { "router-discovery", "f", &dhcp_universe, 31, 1 }, + { "router-solicitation-address", "I", &dhcp_universe, 32, 1 }, + { "static-routes", "IIA", &dhcp_universe, 33, 1 }, + { "trailer-encapsulation", "f", &dhcp_universe, 34, 1 }, + { "arp-cache-timeout", "L", &dhcp_universe, 35, 1 }, + { "ieee802-3-encapsulation", "f", &dhcp_universe, 36, 1 }, + { "default-tcp-ttl", "B", &dhcp_universe, 37, 1 }, + { "tcp-keepalive-interval", "L", &dhcp_universe, 38, 1 }, + { "tcp-keepalive-garbage", "f", &dhcp_universe, 39, 1 }, + { "nis-domain", "t", &dhcp_universe, 40, 1 }, + { "nis-servers", "IA", &dhcp_universe, 41, 1 }, + { "ntp-servers", "IA", &dhcp_universe, 42, 1 }, + { "vendor-encapsulated-options", "E.", &dhcp_universe, 43, 1 }, + { "netbios-name-servers", "IA", &dhcp_universe, 44, 1 }, + { "netbios-dd-server", "IA", &dhcp_universe, 45, 1 }, + { "netbios-node-type", "B", &dhcp_universe, 46, 1 }, + { "netbios-scope", "t", &dhcp_universe, 47, 1 }, + { "font-servers", "IA", &dhcp_universe, 48, 1 }, + { "x-display-manager", "IA", &dhcp_universe, 49, 1 }, + { "dhcp-requested-address", "I", &dhcp_universe, 50, 1 }, + { "dhcp-lease-time", "L", &dhcp_universe, 51, 1 }, + { "dhcp-option-overload", "B", &dhcp_universe, 52, 1 }, + { "dhcp-message-type", "B", &dhcp_universe, 53, 1 }, + { "dhcp-server-identifier", "I", &dhcp_universe, 54, 1 }, + { "dhcp-parameter-request-list", "BA", &dhcp_universe, 55, 1 }, + { "dhcp-message", "t", &dhcp_universe, 56, 1 }, + { "dhcp-max-message-size", "S", &dhcp_universe, 57, 1 }, + { "dhcp-renewal-time", "L", &dhcp_universe, 58, 1 }, + { "dhcp-rebinding-time", "L", &dhcp_universe, 59, 1 }, + { "vendor-class-identifier", "X", &dhcp_universe, 60, 1 }, + { "dhcp-client-identifier", "X", &dhcp_universe, 61, 1 }, + { "nwip-domain", "t", &dhcp_universe, 62, 1 }, + { "nwip-suboptions", "Enwip.", &dhcp_universe, 63, 1 }, + { "nisplus-domain", "t", &dhcp_universe, 64, 1 }, + { "nisplus-servers", "IA", &dhcp_universe, 65, 1 }, + { "tftp-server-name", "t", &dhcp_universe, 66, 1 }, + { "bootfile-name", "t", &dhcp_universe, 67, 1 }, + { "mobile-ip-home-agent", "IA", &dhcp_universe, 68, 1 }, + { "smtp-server", "IA", &dhcp_universe, 69, 1 }, + { "pop-server", "IA", &dhcp_universe, 70, 1 }, + { "nntp-server", "IA", &dhcp_universe, 71, 1 }, + { "www-server", "IA", &dhcp_universe, 72, 1 }, + { "finger-server", "IA", &dhcp_universe, 73, 1 }, + { "irc-server", "IA", &dhcp_universe, 74, 1 }, + { "streettalk-server", "IA", &dhcp_universe, 75, 1 }, + { "streettalk-directory-assistance-server", "IA", + &dhcp_universe, 76, 1 }, + { "user-class", "t", &dhcp_universe, 77, 1 }, + { "slp-directory-agent", "fIa", &dhcp_universe, 78, 1 }, + { "slp-service-scope", "fto", &dhcp_universe, 79, 1 }, + /* 80 is the zero-length rapid-commit (RFC 4039) */ + { "fqdn", "Efqdn.", &dhcp_universe, 81, 1 }, + { "relay-agent-information", "Eagent.", &dhcp_universe, 82, 1 }, + /* 83 is iSNS (RFC 4174) */ + /* 84 is unassigned */ + { "nds-servers", "IA", &dhcp_universe, 85, 1 }, + { "nds-tree-name", "t", &dhcp_universe, 86, 1 }, + { "nds-context", "t", &dhcp_universe, 87, 1 }, + + /* Note: RFC4280 fails to identify if the DHCPv4 option is to use + * compression pointers or not. Assume not. + */ + { "bcms-controller-names", "D", &dhcp_universe, 88, 1 }, + { "bcms-controller-address", "Ia", &dhcp_universe, 89, 1 }, + + /* 90 is the authentication option (RFC 3118) */ + + { "client-last-transaction-time", "L", &dhcp_universe, 91, 1 }, + { "associated-ip", "Ia", &dhcp_universe, 92, 1 }, +#if 0 + /* Defined by RFC 4578 */ + { "pxe-system-type", "S", &dhcp_universe, 93, 1 }, + { "pxe-interface-id", "BBB", &dhcp_universe, 94, 1 }, + { "pxe-client-id", "BX", &dhcp_universe, 97, 1 }, +#endif + { "uap-servers", "t", &dhcp_universe, 98, 1 }, + { "netinfo-server-address", "Ia", &dhcp_universe, 112, 1 }, + { "netinfo-server-tag", "t", &dhcp_universe, 113, 1 }, + { "default-url", "t", &dhcp_universe, 114, 1 }, + { "subnet-selection", "I", &dhcp_universe, 118, 1 }, + { "domain-search", "Dc", &dhcp_universe, 119, 1 }, + { "vivco", "Evendor-class.", &dhcp_universe, 124, 1 }, + { "vivso", "Evendor.", &dhcp_universe, 125, 1 }, +#if 0 + /* Referenced by RFC 4578. + * DO NOT UNCOMMENT THESE DEFINITIONS: these names are placeholders + * and will not be used in future versions of the software. + */ + { "pxe-undefined-1", "X", &dhcp_universe, 128, 1 }, + { "pxe-undefined-2", "X", &dhcp_universe, 129, 1 }, + { "pxe-undefined-3", "X", &dhcp_universe, 130, 1 }, + { "pxe-undefined-4", "X", &dhcp_universe, 131, 1 }, + { "pxe-undefined-5", "X", &dhcp_universe, 132, 1 }, + { "pxe-undefined-6", "X", &dhcp_universe, 133, 1 }, + { "pxe-undefined-7", "X", &dhcp_universe, 134, 1 }, + { "pxe-undefined-8", "X", &dhcp_universe, 135, 1 }, +#endif +#if 0 + /* Not defined by RFC yet */ + { "tftp-server-address", "Ia", &dhcp_universe, 150, 1 }, +#endif +#if 0 + /* PXELINUX options: defined by RFC 5071 */ + { "pxelinux-magic", "BBBB", &dhcp_universe, 208, 1 }, + { "loader-configfile", "t", &dhcp_universe, 209, 1 }, + { "loader-pathprefix", "t", &dhcp_universe, 210, 1 }, + { "loader-reboottime", "L", &dhcp_universe, 211, 1 }, +#endif +#if 0 + /* Not defined by RFC yet */ + { "vss-info", "BX", &dhcp_universe, 221, 1 }, +#endif + { NULL, NULL, NULL, 0, 0 } +}; + +struct universe nwip_universe; +static struct option nwip_options[] = { + { "illegal-1", "", &nwip_universe, 1, 1 }, + { "illegal-2", "", &nwip_universe, 2, 1 }, + { "illegal-3", "", &nwip_universe, 3, 1 }, + { "illegal-4", "", &nwip_universe, 4, 1 }, + { "nsq-broadcast", "f", &nwip_universe, 5, 1 }, + { "preferred-dss", "IA", &nwip_universe, 6, 1 }, + { "nearest-nwip-server", "IA", &nwip_universe, 7, 1 }, + { "autoretries", "B", &nwip_universe, 8, 1 }, + { "autoretry-secs", "B", &nwip_universe, 9, 1 }, + { "nwip-1-1", "f", &nwip_universe, 10, 1 }, + { "primary-dss", "I", &nwip_universe, 11, 1 }, + { NULL, NULL, NULL, 0, 0 } +}; + +/* Note that the "FQDN suboption space" does not reflect the FQDN option + * format - rather, this is a handy "virtualization" of a flat option + * which makes manual configuration and presentation of some of its + * contents easier (each of these suboptions is a fixed-space field within + * the fqdn contents - domain and host names are derived from a common field, + * and differ in the left and right hand side of the leftmost dot, fqdn is + * the combination of the two). + * + * Note further that the DHCPv6 and DHCPv4 'fqdn' options use the same + * virtualized option space to store their work. + */ + +struct universe fqdn_universe; +struct universe fqdn6_universe; +static struct option fqdn_options[] = { + { "no-client-update", "f", &fqdn_universe, 1, 1 }, + { "server-update", "f", &fqdn_universe, 2, 1 }, + { "encoded", "f", &fqdn_universe, 3, 1 }, + { "rcode1", "B", &fqdn_universe, 4, 1 }, + { "rcode2", "B", &fqdn_universe, 5, 1 }, + { "hostname", "t", &fqdn_universe, 6, 1 }, + { "domainname", "t", &fqdn_universe, 7, 1 }, + { "fqdn", "t", &fqdn_universe, 8, 1 }, + { NULL, NULL, NULL, 0, 0 } +}; + +struct universe vendor_class_universe; +static struct option vendor_class_options[] = { + { "isc", "X", &vendor_class_universe, 2495, 1 }, + { NULL, NULL, NULL, 0, 0 } +}; + +struct universe vendor_universe; +static struct option vendor_options[] = { + { "isc", "Eisc.", &vendor_universe, 2495, 1 }, + { NULL, NULL, NULL, 0, 0 } +}; + +struct universe isc_universe; +static struct option isc_options [] = { + { "media", "t", &isc_universe, 1, 1 }, + { "update-assist", "X", &isc_universe, 2, 1 }, + { NULL, NULL, NULL, 0, 0 } +}; + +struct universe dhcpv6_universe; +static struct option dhcpv6_options[] = { + + /* RFC3315 OPTIONS */ + + /* Client and server DUIDs are opaque fields, but marking them + * up somewhat makes configuration easier. + */ + { "client-id", "X", &dhcpv6_universe, 1, 1 }, + { "server-id", "X", &dhcpv6_universe, 2, 1 }, + + /* ia-* options actually have at their ends a space for options + * that are specific to this instance of the option. We can not + * handle this yet at this stage of development, so the encoding + * of these options is unspecified ("X"). + */ + { "ia-na", "X", &dhcpv6_universe, 3, 1 }, + { "ia-ta", "X", &dhcpv6_universe, 4, 1 }, + { "ia-addr", "X", &dhcpv6_universe, 5, 1 }, + + /* "oro" is DHCPv6 speak for "parameter-request-list" */ + { "oro", "SA", &dhcpv6_universe, 6, 1 }, + + { "preference", "B", &dhcpv6_universe, 7, 1 }, + { "elapsed-time", "S", &dhcpv6_universe, 8, 1 }, + { "relay-msg", "X", &dhcpv6_universe, 9, 1 }, + + /* Option code 10 is curiously unassigned. */ + /* + * In draft-ietf-dhc-dhcpv6-25 there were two OPTION_CLIENT_MSG and + * OPTION_SERVER_MSG options. They were eventually unified as + * OPTION_RELAY_MSG, hence no option with value of 10. + */ +#if 0 + /* XXX: missing suitable atoms for the auth option. We may want + * to 'virtually encapsulate' this option a la the fqdn option + * seeing as it is processed explicitly by the server and unlikely + * to be configured by hand by users as such. + */ + { "auth", "Nauth-protocol.Nauth-algorithm.Nrdm-type.LLX", + &dhcpv6_universe, 11, 1 }, +#endif + { "unicast", "6", &dhcpv6_universe, 12, 1 }, + { "status-code", "Nstatus-codes.to", &dhcpv6_universe, 13, 1 }, + { "rapid-commit", "Z", &dhcpv6_universe, 14, 1 }, +#if 0 + /* XXX: user-class contents are of the form "StA" where the + * integer describes the length of the text field. We don't have + * an atom for pre-determined-length octet strings yet, so we + * can't quite do these two. + */ + { "user-class", "X", &dhcpv6_universe, 15, 1 }, + { "vendor-class", "X", &dhcpv6_universe, 16, 1 }, +#endif + { "vendor-opts", "Evsio.", &dhcpv6_universe, 17, 1 }, + { "interface-id", "X", &dhcpv6_universe, 18, 1 }, + { "reconf-msg", "Ndhcpv6-messages.", &dhcpv6_universe, 19, 1 }, + { "reconf-accept", "Z", &dhcpv6_universe, 20, 1 }, + + /* RFC3319 OPTIONS */ + + /* Of course: we would HAVE to have a different atom for + * domain names without compression. Typical. + */ + { "sip-servers-names", "D", &dhcpv6_universe, 21, 1 }, + { "sip-servers-addresses", "6A", &dhcpv6_universe, 22, 1 }, + + /* RFC3646 OPTIONS */ + + { "name-servers", "6A", &dhcpv6_universe, 23, 1 }, + { "domain-search", "D", &dhcpv6_universe, 24, 1 }, + + /* RFC3633 OPTIONS */ + + { "ia-pd", "X", &dhcpv6_universe, 25, 1 }, + { "ia-prefix", "X", &dhcpv6_universe, 26, 1 }, + + /* RFC3898 OPTIONS */ + + { "nis-servers", "6A", &dhcpv6_universe, 27, 1 }, + { "nisp-servers", "6A", &dhcpv6_universe, 28, 1 }, + { "nis-domain-name", "D", &dhcpv6_universe, 29, 1 }, + { "nisp-domain-name", "D", &dhcpv6_universe, 30, 1 }, + + /* RFC4075 OPTIONS */ + { "sntp-servers", "6A", &dhcpv6_universe, 31, 1 }, + + /* RFC4242 OPTIONS */ + + { "info-refresh-time", "T", &dhcpv6_universe, 32, 1 }, + + /* RFC4280 OPTIONS */ + + { "bcms-server-d", "D", &dhcpv6_universe, 33, 1 }, + { "bcms-server-a", "6A", &dhcpv6_universe, 34, 1 }, + + /* Note that 35 is not assigned. */ + + /* Not yet considering for inclusion. */ +#if 0 + /* RFC4776 OPTIONS */ + + { "geoconf-civic", "X", &dhcpv6_universe, 36, 1 }, +#endif + + /* RFC4649 OPTIONS */ + + /* The remote-id option looks like the VSIO option, but for all + * intents and purposes we only need to treat the entire field + * like a globally unique identifier (and if we create such an + * option, ensure the first 4 bytes are our enterprise-id followed + * by a globally unique ID so long as you're within that enterprise + * id). So we'll use "X" for now unless someone grumbles. + */ + { "remote-id", "X", &dhcpv6_universe, 37, 1 }, + + /* RFC4580 OPTIONS */ + + { "subscriber-id", "X", &dhcpv6_universe, 38, 1 }, + + /* RFC4704 OPTIONS */ + + /* The DHCPv6 FQDN option is...weird. + * + * We use the same "virtual" encapsulated space as DHCPv4's FQDN + * option, so it can all be configured in one place. Since the + * options system does not support multiple inheritance, we use + * a 'shill' layer to perform the different protocol conversions, + * and to redirect any queries in the DHCPv4 FQDN's space. + */ + { "fqdn", "Efqdn6-if-you-see-me-its-a-bug-bug-bug.", + &dhcpv6_universe, 39, 1 }, + + /* Not yet considering for inclusion. */ +#if 0 + /* draft-ietf-dhc-paa-option-05 */ + { "pana-agent", "6A", &dhcpv6_universe, 40, 1 }, + + /* RFC4833 OPTIONS */ + + { "new-posix-timezone", "t", &dhcpv6_universe, 41, 1 }, + { "new-tzdb-timezone", "t", &dhcpv6_universe, 42, 1 }, + + /* RFC4994 OPTIONS */ + + { "ero", "SA", &dhcpv6_universe, 43, 1 }, +#endif + + /* RFC5007 OPTIONS */ + + { "lq-query", "X", &dhcpv6_universe, 44, 1 }, + { "client-data", "X", &dhcpv6_universe, 45, 1 }, + { "clt-time", "L", &dhcpv6_universe, 46, 1 }, + { "lq-relay-data", "6X", &dhcpv6_universe, 47, 1 }, + { "lq-client-link", "6A", &dhcpv6_universe, 48, 1 }, + + { NULL, NULL, NULL, 0, 0 } +}; + +struct enumeration_value dhcpv6_duid_type_values[] = { + { "duid-llt", DUID_LLT }, /* Link-Local Plus Time */ + { "duid-en", DUID_EN }, /* DUID based upon enterprise-ID. */ + { "duid-ll", DUID_LL }, /* DUID from Link Local address only. */ + { NULL, 0 } +}; + +struct enumeration dhcpv6_duid_types = { + NULL, + "duid-types", 2, + dhcpv6_duid_type_values +}; + +struct enumeration_value dhcpv6_status_code_values[] = { + { "success", 0 }, /* Success */ + { "UnspecFail", 1 }, /* Failure, for unspecified reasons. */ + { "NoAddrsAvail", 2 }, /* Server has no addresses to assign. */ + { "NoBinding", 3 }, /* Client record (binding) unavailable. */ + { "NotOnLink", 4 }, /* Bad prefix for the link. */ + { "UseMulticast", 5 }, /* Not just good advice. It's the law. */ + { "NoPrefixAvail", 6 }, /* Server has no prefixes to assign. */ + { "UnknownQueryType", 7 }, /* Query-type unknown/unsupported. */ + { "MalformedQuery", 8 }, /* Leasequery not valid. */ + { "NotConfigured", 9 }, /* The target address is not in config. */ + { "NotAllowed", 10 }, /* Server doesn't allow the leasequery. */ + { NULL, 0 } +}; + +struct enumeration dhcpv6_status_codes = { + NULL, + "status-codes", 2, + dhcpv6_status_code_values +}; + +struct enumeration_value lq6_query_type_values[] = { + { "query-by-address", 1 }, + { "query-by-clientid", 2 }, + { NULL, 0 } +}; + +struct enumeration lq6_query_types = { + NULL, + "query-types", 2, + lq6_query_type_values +}; + +struct enumeration_value dhcpv6_message_values[] = { + { "SOLICIT", 1 }, + { "ADVERTISE", 2 }, + { "REQUEST", 3 }, + { "CONFIRM", 4 }, + { "RENEW", 5 }, + { "REBIND", 6 }, + { "REPLY", 7 }, + { "RELEASE", 8 }, + { "DECLINE", 9 }, + { "RECONFIGURE", 10 }, + { "INFORMATION-REQUEST", 11 }, + { "RELAY-FORW", 12 }, + { "RELAY-REPL", 13 }, + { "LEASEQUERY", 14 }, + { "LEASEQUERY-REPLY", 15 }, + { NULL, 0 } +}; + +/* Some code refers to a different table. */ +const char *dhcpv6_type_names[] = { + NULL, + "Solicit", + "Advertise", + "Request", + "Confirm", + "Renew", + "Rebind", + "Reply", + "Release", + "Decline", + "Reconfigure", + "Information-request", + "Relay-forward", + "Relay-reply", + "Leasequery", + "Leasequery-reply" +}; +const int dhcpv6_type_name_max = + (sizeof(dhcpv6_type_names) / sizeof(dhcpv6_type_names[0])); + +struct enumeration dhcpv6_messages = { + NULL, + "dhcpv6-messages", 1, + dhcpv6_message_values +}; + +struct universe vsio_universe; +static struct option vsio_options[] = { + { "isc", "Eisc6.", &vsio_universe, 2495, 1 }, + { NULL, NULL, NULL, 0, 0 } +}; + +struct universe isc6_universe; +static struct option isc6_options[] = { + { "media", "t", &isc6_universe, 1, 1 }, + { "update-assist", "X", &isc6_universe, 2, 1 }, + { NULL, NULL, NULL, 0, 0 } +}; + +const char *hardware_types [] = { + "unknown-0", + "ethernet", + "unknown-2", + "unknown-3", + "unknown-4", + "unknown-5", + "token-ring", + "unknown-7", + "fddi", + "unknown-9", + "unknown-10", + "unknown-11", + "unknown-12", + "unknown-13", + "unknown-14", + "unknown-15", + "unknown-16", + "unknown-17", + "unknown-18", + "unknown-19", + "unknown-20", + "unknown-21", + "unknown-22", + "unknown-23", + "unknown-24", + "unknown-25", + "unknown-26", + "unknown-27", + "unknown-28", + "unknown-29", + "unknown-30", + "unknown-31", + "infiniband", + "unknown-33", + "unknown-34", + "unknown-35", + "unknown-36", + "unknown-37", + "unknown-38", + "unknown-39", + "unknown-40", + "unknown-41", + "unknown-42", + "unknown-43", + "unknown-44", + "unknown-45", + "unknown-46", + "unknown-47", + "unknown-48", + "unknown-49", + "unknown-50", + "unknown-51", + "unknown-52", + "unknown-53", + "unknown-54", + "unknown-55", + "unknown-56", + "unknown-57", + "unknown-58", + "unknown-59", + "unknown-60", + "unknown-61", + "unknown-62", + "unknown-63", + "unknown-64", + "unknown-65", + "unknown-66", + "unknown-67", + "unknown-68", + "unknown-69", + "unknown-70", + "unknown-71", + "unknown-72", + "unknown-73", + "unknown-74", + "unknown-75", + "unknown-76", + "unknown-77", + "unknown-78", + "unknown-79", + "unknown-80", + "unknown-81", + "unknown-82", + "unknown-83", + "unknown-84", + "unknown-85", + "unknown-86", + "unknown-87", + "unknown-88", + "unknown-89", + "unknown-90", + "unknown-91", + "unknown-92", + "unknown-93", + "unknown-94", + "unknown-95", + "unknown-96", + "unknown-97", + "unknown-98", + "unknown-99", + "unknown-100", + "unknown-101", + "unknown-102", + "unknown-103", + "unknown-104", + "unknown-105", + "unknown-106", + "unknown-107", + "unknown-108", + "unknown-109", + "unknown-110", + "unknown-111", + "unknown-112", + "unknown-113", + "unknown-114", + "unknown-115", + "unknown-116", + "unknown-117", + "unknown-118", + "unknown-119", + "unknown-120", + "unknown-121", + "unknown-122", + "unknown-123", + "unknown-124", + "unknown-125", + "unknown-126", + "unknown-127", + "unknown-128", + "unknown-129", + "unknown-130", + "unknown-131", + "unknown-132", + "unknown-133", + "unknown-134", + "unknown-135", + "unknown-136", + "unknown-137", + "unknown-138", + "unknown-139", + "unknown-140", + "unknown-141", + "unknown-142", + "unknown-143", + "unknown-144", + "unknown-145", + "unknown-146", + "unknown-147", + "unknown-148", + "unknown-149", + "unknown-150", + "unknown-151", + "unknown-152", + "unknown-153", + "unknown-154", + "unknown-155", + "unknown-156", + "unknown-157", + "unknown-158", + "unknown-159", + "unknown-160", + "unknown-161", + "unknown-162", + "unknown-163", + "unknown-164", + "unknown-165", + "unknown-166", + "unknown-167", + "unknown-168", + "unknown-169", + "unknown-170", + "unknown-171", + "unknown-172", + "unknown-173", + "unknown-174", + "unknown-175", + "unknown-176", + "unknown-177", + "unknown-178", + "unknown-179", + "unknown-180", + "unknown-181", + "unknown-182", + "unknown-183", + "unknown-184", + "unknown-185", + "unknown-186", + "unknown-187", + "unknown-188", + "unknown-189", + "unknown-190", + "unknown-191", + "unknown-192", + "unknown-193", + "unknown-194", + "unknown-195", + "unknown-196", + "unknown-197", + "unknown-198", + "unknown-199", + "unknown-200", + "unknown-201", + "unknown-202", + "unknown-203", + "unknown-204", + "unknown-205", + "unknown-206", + "unknown-207", + "unknown-208", + "unknown-209", + "unknown-210", + "unknown-211", + "unknown-212", + "unknown-213", + "unknown-214", + "unknown-215", + "unknown-216", + "unknown-217", + "unknown-218", + "unknown-219", + "unknown-220", + "unknown-221", + "unknown-222", + "unknown-223", + "unknown-224", + "unknown-225", + "unknown-226", + "unknown-227", + "unknown-228", + "unknown-229", + "unknown-230", + "unknown-231", + "unknown-232", + "unknown-233", + "unknown-234", + "unknown-235", + "unknown-236", + "unknown-237", + "unknown-238", + "unknown-239", + "unknown-240", + "unknown-241", + "unknown-242", + "unknown-243", + "unknown-244", + "unknown-245", + "unknown-246", + "unknown-247", + "unknown-248", + "unknown-249", + "unknown-250", + "unknown-251", + "unknown-252", + "unknown-253", + "unknown-254", + "unknown-255" }; + +universe_hash_t *universe_hash; +struct universe **universes; +int universe_count, universe_max; + +/* Universe containing names of configuration options, which, rather than + writing "option universe-name.option-name ...;", can be set by writing + "option-name ...;". */ + +struct universe *config_universe; + +/* XXX: omapi must die...all the below keeps us from having to make the + * option structures omapi typed objects, which is a bigger headache. + */ + +char *default_option_format = (char *) "X"; + +/* Must match hash_reference/dereference types in omapip/hash.h. */ +int +option_reference(struct option **dest, struct option *src, + const char * file, int line) +{ + if (!dest || !src) + return DHCP_R_INVALIDARG; + + if (*dest) { +#if defined(POINTER_DEBUG) + log_fatal("%s(%d): reference store into non-null pointer!", + file, line); +#else + return DHCP_R_INVALIDARG; +#endif + } + + *dest = src; + src->refcnt++; + rc_register(file, line, dest, src, src->refcnt, 0, RC_MISC); + return(ISC_R_SUCCESS); +} + +int +option_dereference(struct option **dest, const char *file, int line) +{ + if (!dest) + return DHCP_R_INVALIDARG; + + if (!*dest) { +#if defined (POINTER_DEBUG) + log_fatal("%s(%d): dereference of null pointer!", file, line); +#else + return DHCP_R_INVALIDARG; +#endif + } + + if ((*dest)->refcnt <= 0) { +#if defined (POINTER_DEBUG) + log_fatal("%s(%d): dereference of <= 0 refcnt!", file, line); +#else + return DHCP_R_INVALIDARG; +#endif + } + + (*dest)->refcnt--; + + rc_register(file, line, dest, (*dest), (*dest)->refcnt, 1, RC_MISC); + + if ((*dest)->refcnt == 0) { + /* The option name may be packed in the same alloc as the + * option structure. + */ + if ((char *) (*dest)->name != (char *) ((*dest) + 1)) + dfree((char *) (*dest)->name, file, line); + + /* It's either a user-configured format (allocated), or the + * default static format. + */ + if (((*dest)->format != NULL) && + ((*dest)->format != default_option_format)) { + dfree((char *) (*dest)->format, file, line); + } + + dfree(*dest, file, line); + } + + *dest = NULL; + return ISC_R_SUCCESS; +} + +void initialize_common_option_spaces() +{ + unsigned code; + int i; + + /* The 'universes' table is dynamically grown to contain + * universe as they're configured - except during startup. + * Since we know how many we put down in .c files, we can + * allocate a more-than-right-sized buffer now, leaving some + * space for user-configured option spaces. + * + * 1: dhcp_universe (dhcpv4 options) + * 2: nwip_universe (dhcpv4 NWIP option) + * 3: fqdn_universe (dhcpv4 fqdn option - reusable for v6) + * 4: vendor_class_universe (VIVCO) + * 5: vendor_universe (VIVSO) + * 6: isc_universe (dhcpv4 isc config space) + * 7: dhcpv6_universe (dhcpv6 options) + * 8: vsio_universe (DHCPv6 Vendor-Identified space) + * 9: isc6_universe (ISC's Vendor universe in DHCPv6 VSIO) + * 10: fqdn6_universe (dhcpv6 fqdn option shill to v4) + * 11: agent_universe (dhcpv4 relay agent - see server/stables.c) + * 12: server_universe (server's config, see server/stables.c) + * 13: user-config + * 14: more user-config + * 15: more user-config + * 16: more user-config + */ + universe_max = 16; + i = universe_max * sizeof(struct universe *); + if (i <= 0) + log_fatal("Ludicrous initial size option space table."); + universes = dmalloc(i, MDL); + if (universes == NULL) + log_fatal("Can't allocate option space table."); + memset(universes, 0, i); + + /* Set up the DHCP option universe... */ + dhcp_universe.name = "dhcp"; + dhcp_universe.concat_duplicates = 1; + dhcp_universe.lookup_func = lookup_hashed_option; + dhcp_universe.option_state_dereference = + hashed_option_state_dereference; + dhcp_universe.save_func = save_hashed_option; + dhcp_universe.delete_func = delete_hashed_option; + dhcp_universe.encapsulate = hashed_option_space_encapsulate; + dhcp_universe.foreach = hashed_option_space_foreach; + dhcp_universe.decode = parse_option_buffer; + dhcp_universe.length_size = 1; + dhcp_universe.tag_size = 1; + dhcp_universe.get_tag = getUChar; + dhcp_universe.store_tag = putUChar; + dhcp_universe.get_length = getUChar; + dhcp_universe.store_length = putUChar; + dhcp_universe.site_code_min = 0; + dhcp_universe.end = DHO_END; + dhcp_universe.index = universe_count++; + universes [dhcp_universe.index] = &dhcp_universe; + if (!option_name_new_hash(&dhcp_universe.name_hash, + BYTE_NAME_HASH_SIZE, MDL) || + !option_code_new_hash(&dhcp_universe.code_hash, + BYTE_CODE_HASH_SIZE, MDL)) + log_fatal ("Can't allocate dhcp option hash table."); + for (i = 0 ; dhcp_options[i].name ; i++) { + option_code_hash_add(dhcp_universe.code_hash, + &dhcp_options[i].code, 0, + &dhcp_options[i], MDL); + option_name_hash_add(dhcp_universe.name_hash, + dhcp_options [i].name, 0, + &dhcp_options [i], MDL); + } +#if defined(REPORT_HASH_PERFORMANCE) + log_info("DHCP name hash: %s", + option_name_hash_report(dhcp_universe.name_hash)); + log_info("DHCP code hash: %s", + option_code_hash_report(dhcp_universe.code_hash)); +#endif + + /* Set up the Novell option universe (for option 63)... */ + nwip_universe.name = "nwip"; + nwip_universe.concat_duplicates = 0; /* XXX: reference? */ + nwip_universe.lookup_func = lookup_linked_option; + nwip_universe.option_state_dereference = + linked_option_state_dereference; + nwip_universe.save_func = save_linked_option; + nwip_universe.delete_func = delete_linked_option; + nwip_universe.encapsulate = nwip_option_space_encapsulate; + nwip_universe.foreach = linked_option_space_foreach; + nwip_universe.decode = parse_option_buffer; + nwip_universe.length_size = 1; + nwip_universe.tag_size = 1; + nwip_universe.get_tag = getUChar; + nwip_universe.store_tag = putUChar; + nwip_universe.get_length = getUChar; + nwip_universe.store_length = putUChar; + nwip_universe.site_code_min = 0; + nwip_universe.end = 0; + code = DHO_NWIP_SUBOPTIONS; + nwip_universe.enc_opt = NULL; + if (!option_code_hash_lookup(&nwip_universe.enc_opt, + dhcp_universe.code_hash, &code, 0, MDL)) + log_fatal("Unable to find NWIP parent option (%s:%d).", MDL); + nwip_universe.index = universe_count++; + universes [nwip_universe.index] = &nwip_universe; + if (!option_name_new_hash(&nwip_universe.name_hash, + NWIP_HASH_SIZE, MDL) || + !option_code_new_hash(&nwip_universe.code_hash, + NWIP_HASH_SIZE, MDL)) + log_fatal ("Can't allocate nwip option hash table."); + for (i = 0 ; nwip_options[i].name ; i++) { + option_code_hash_add(nwip_universe.code_hash, + &nwip_options[i].code, 0, + &nwip_options[i], MDL); + option_name_hash_add(nwip_universe.name_hash, + nwip_options[i].name, 0, + &nwip_options[i], MDL); + } +#if defined(REPORT_HASH_PERFORMANCE) + log_info("NWIP name hash: %s", + option_name_hash_report(nwip_universe.name_hash)); + log_info("NWIP code hash: %s", + option_code_hash_report(nwip_universe.code_hash)); +#endif + + /* Set up the FQDN option universe... */ + fqdn_universe.name = "fqdn"; + fqdn_universe.concat_duplicates = 0; + fqdn_universe.lookup_func = lookup_linked_option; + fqdn_universe.option_state_dereference = + linked_option_state_dereference; + fqdn_universe.save_func = save_linked_option; + fqdn_universe.delete_func = delete_linked_option; + fqdn_universe.encapsulate = fqdn_option_space_encapsulate; + fqdn_universe.foreach = linked_option_space_foreach; + fqdn_universe.decode = fqdn_universe_decode; + fqdn_universe.length_size = 1; + fqdn_universe.tag_size = 1; + fqdn_universe.get_tag = getUChar; + fqdn_universe.store_tag = putUChar; + fqdn_universe.get_length = getUChar; + fqdn_universe.store_length = putUChar; + fqdn_universe.site_code_min = 0; + fqdn_universe.end = 0; + fqdn_universe.index = universe_count++; + code = DHO_FQDN; + fqdn_universe.enc_opt = NULL; + if (!option_code_hash_lookup(&fqdn_universe.enc_opt, + dhcp_universe.code_hash, &code, 0, MDL)) + log_fatal("Unable to find FQDN parent option (%s:%d).", MDL); + universes [fqdn_universe.index] = &fqdn_universe; + if (!option_name_new_hash(&fqdn_universe.name_hash, + FQDN_HASH_SIZE, MDL) || + !option_code_new_hash(&fqdn_universe.code_hash, + FQDN_HASH_SIZE, MDL)) + log_fatal ("Can't allocate fqdn option hash table."); + for (i = 0 ; fqdn_options[i].name ; i++) { + option_code_hash_add(fqdn_universe.code_hash, + &fqdn_options[i].code, 0, + &fqdn_options[i], MDL); + option_name_hash_add(fqdn_universe.name_hash, + fqdn_options[i].name, 0, + &fqdn_options[i], MDL); + } +#if defined(REPORT_HASH_PERFORMANCE) + log_info("FQDN name hash: %s", + option_name_hash_report(fqdn_universe.name_hash)); + log_info("FQDN code hash: %s", + option_code_hash_report(fqdn_universe.code_hash)); +#endif + + /* Set up the Vendor Identified Vendor Class options (for option + * 125)... + */ + vendor_class_universe.name = "vendor-class"; + vendor_class_universe.concat_duplicates = 0; /* XXX: reference? */ + vendor_class_universe.lookup_func = lookup_hashed_option; + vendor_class_universe.option_state_dereference = + hashed_option_state_dereference; + vendor_class_universe.save_func = save_hashed_option; + vendor_class_universe.delete_func = delete_hashed_option; + vendor_class_universe.encapsulate = hashed_option_space_encapsulate; + vendor_class_universe.foreach = hashed_option_space_foreach; + vendor_class_universe.decode = parse_option_buffer; + vendor_class_universe.length_size = 1; + vendor_class_universe.tag_size = 4; + vendor_class_universe.get_tag = getULong; + vendor_class_universe.store_tag = putULong; + vendor_class_universe.get_length = getUChar; + vendor_class_universe.store_length = putUChar; + vendor_class_universe.site_code_min = 0; + vendor_class_universe.end = 0; + code = DHO_VIVCO_SUBOPTIONS; + vendor_class_universe.enc_opt = NULL; + if (!option_code_hash_lookup(&vendor_class_universe.enc_opt, + dhcp_universe.code_hash, &code, 0, MDL)) + log_fatal("Unable to find VIVCO parent option (%s:%d).", MDL); + vendor_class_universe.index = universe_count++; + universes[vendor_class_universe.index] = &vendor_class_universe; + if (!option_name_new_hash(&vendor_class_universe.name_hash, + VIVCO_HASH_SIZE, MDL) || + !option_code_new_hash(&vendor_class_universe.code_hash, + VIVCO_HASH_SIZE, MDL)) + log_fatal("Can't allocate Vendor Identified Vendor Class " + "option hash table."); + for (i = 0 ; vendor_class_options[i].name ; i++) { + option_code_hash_add(vendor_class_universe.code_hash, + &vendor_class_options[i].code, 0, + &vendor_class_options[i], MDL); + option_name_hash_add(vendor_class_universe.name_hash, + vendor_class_options[i].name, 0, + &vendor_class_options[i], MDL); + } +#if defined(REPORT_HASH_PERFORMANCE) + log_info("VIVCO name hash: %s", + option_name_hash_report(vendor_class_universe.name_hash)); + log_info("VIVCO code hash: %s", + option_code_hash_report(vendor_class_universe.code_hash)); +#endif + + /* Set up the Vendor Identified Vendor Sub-options (option 126)... */ + vendor_universe.name = "vendor"; + vendor_universe.concat_duplicates = 0; /* XXX: reference? */ + vendor_universe.lookup_func = lookup_hashed_option; + vendor_universe.option_state_dereference = + hashed_option_state_dereference; + vendor_universe.save_func = save_hashed_option; + vendor_universe.delete_func = delete_hashed_option; + vendor_universe.encapsulate = hashed_option_space_encapsulate; + vendor_universe.foreach = hashed_option_space_foreach; + vendor_universe.decode = parse_option_buffer; + vendor_universe.length_size = 1; + vendor_universe.tag_size = 4; + vendor_universe.get_tag = getULong; + vendor_universe.store_tag = putULong; + vendor_universe.get_length = getUChar; + vendor_universe.store_length = putUChar; + vendor_universe.site_code_min = 0; + vendor_universe.end = 0; + code = DHO_VIVSO_SUBOPTIONS; + vendor_universe.enc_opt = NULL; + if (!option_code_hash_lookup(&vendor_universe.enc_opt, + dhcp_universe.code_hash, &code, 0, MDL)) + log_fatal("Unable to find VIVSO parent option (%s:%d).", MDL); + vendor_universe.index = universe_count++; + universes[vendor_universe.index] = &vendor_universe; + if (!option_name_new_hash(&vendor_universe.name_hash, + VIVSO_HASH_SIZE, MDL) || + !option_code_new_hash(&vendor_universe.code_hash, + VIVSO_HASH_SIZE, MDL)) + log_fatal("Can't allocate Vendor Identified Vendor Sub-" + "options hash table."); + for (i = 0 ; vendor_options[i].name ; i++) { + option_code_hash_add(vendor_universe.code_hash, + &vendor_options[i].code, 0, + &vendor_options[i], MDL); + option_name_hash_add(vendor_universe.name_hash, + vendor_options[i].name, 0, + &vendor_options[i], MDL); + } +#if defined(REPORT_HASH_PERFORMANCE) + log_info("VIVSO name hash: %s", + option_name_hash_report(vendor_universe.name_hash)); + log_info("VIVSO code hash: %s", + option_code_hash_report(vendor_universe.code_hash)); +#endif + + /* Set up the ISC Vendor-option universe (for option 125.2495)... */ + isc_universe.name = "isc"; + isc_universe.concat_duplicates = 0; /* XXX: check VIVSO ref */ + isc_universe.lookup_func = lookup_linked_option; + isc_universe.option_state_dereference = + linked_option_state_dereference; + isc_universe.save_func = save_linked_option; + isc_universe.delete_func = delete_linked_option; + isc_universe.encapsulate = linked_option_space_encapsulate; + isc_universe.foreach = linked_option_space_foreach; + isc_universe.decode = parse_option_buffer; + isc_universe.length_size = 2; + isc_universe.tag_size = 2; + isc_universe.get_tag = getUShort; + isc_universe.store_tag = putUShort; + isc_universe.get_length = getUShort; + isc_universe.store_length = putUShort; + isc_universe.site_code_min = 0; + isc_universe.end = 0; + code = VENDOR_ISC_SUBOPTIONS; + isc_universe.enc_opt = NULL; + if (!option_code_hash_lookup(&isc_universe.enc_opt, + vendor_universe.code_hash, &code, 0, MDL)) + log_fatal("Unable to find ISC parent option (%s:%d).", MDL); + isc_universe.index = universe_count++; + universes[isc_universe.index] = &isc_universe; + if (!option_name_new_hash(&isc_universe.name_hash, + VIV_ISC_HASH_SIZE, MDL) || + !option_code_new_hash(&isc_universe.code_hash, + VIV_ISC_HASH_SIZE, MDL)) + log_fatal("Can't allocate ISC Vendor options hash table."); + for (i = 0 ; isc_options[i].name ; i++) { + option_code_hash_add(isc_universe.code_hash, + &isc_options[i].code, 0, + &isc_options[i], MDL); + option_name_hash_add(isc_universe.name_hash, + isc_options[i].name, 0, + &isc_options[i], MDL); + } +#if defined(REPORT_HASH_PERFORMANCE) + log_info("ISC name hash: %s", + option_name_hash_report(isc_universe.name_hash)); + log_info("ISC code hash: %s", + option_code_hash_report(isc_universe.code_hash)); +#endif + + /* Set up the DHCPv6 root universe. */ + dhcpv6_universe.name = "dhcp6"; + dhcpv6_universe.concat_duplicates = 0; + dhcpv6_universe.lookup_func = lookup_hashed_option; + dhcpv6_universe.option_state_dereference = + hashed_option_state_dereference; + dhcpv6_universe.save_func = save_hashed_option; + dhcpv6_universe.delete_func = delete_hashed_option; + dhcpv6_universe.encapsulate = hashed_option_space_encapsulate; + dhcpv6_universe.foreach = hashed_option_space_foreach; + dhcpv6_universe.decode = parse_option_buffer; + dhcpv6_universe.length_size = 2; + dhcpv6_universe.tag_size = 2; + dhcpv6_universe.get_tag = getUShort; + dhcpv6_universe.store_tag = putUShort; + dhcpv6_universe.get_length = getUShort; + dhcpv6_universe.store_length = putUShort; + dhcpv6_universe.site_code_min = 0; + /* DHCPv6 has no END option. */ + dhcpv6_universe.end = 0x00; + dhcpv6_universe.index = universe_count++; + universes[dhcpv6_universe.index] = &dhcpv6_universe; + if (!option_name_new_hash(&dhcpv6_universe.name_hash, + WORD_NAME_HASH_SIZE, MDL) || + !option_code_new_hash(&dhcpv6_universe.code_hash, + WORD_CODE_HASH_SIZE, MDL)) + log_fatal("Can't allocate dhcpv6 option hash tables."); + for (i = 0 ; dhcpv6_options[i].name ; i++) { + option_code_hash_add(dhcpv6_universe.code_hash, + &dhcpv6_options[i].code, 0, + &dhcpv6_options[i], MDL); + option_name_hash_add(dhcpv6_universe.name_hash, + dhcpv6_options[i].name, 0, + &dhcpv6_options[i], MDL); + } + + /* Add DHCPv6 protocol enumeration sets. */ + add_enumeration(&dhcpv6_duid_types); + add_enumeration(&dhcpv6_status_codes); + add_enumeration(&dhcpv6_messages); + + /* Set up DHCPv6 VSIO universe. */ + vsio_universe.name = "vsio"; + vsio_universe.concat_duplicates = 0; + vsio_universe.lookup_func = lookup_hashed_option; + vsio_universe.option_state_dereference = + hashed_option_state_dereference; + vsio_universe.save_func = save_hashed_option; + vsio_universe.delete_func = delete_hashed_option; + vsio_universe.encapsulate = hashed_option_space_encapsulate; + vsio_universe.foreach = hashed_option_space_foreach; + vsio_universe.decode = parse_option_buffer; + vsio_universe.length_size = 0; + vsio_universe.tag_size = 4; + vsio_universe.get_tag = getULong; + vsio_universe.store_tag = putULong; + vsio_universe.get_length = NULL; + vsio_universe.store_length = NULL; + vsio_universe.site_code_min = 0; + /* No END option. */ + vsio_universe.end = 0x00; + code = D6O_VENDOR_OPTS; + if (!option_code_hash_lookup(&vsio_universe.enc_opt, + dhcpv6_universe.code_hash, &code, 0, MDL)) + log_fatal("Unable to find VSIO parent option (%s:%d).", MDL); + vsio_universe.index = universe_count++; + universes[vsio_universe.index] = &vsio_universe; + if (!option_name_new_hash(&vsio_universe.name_hash, + VSIO_HASH_SIZE, MDL) || + !option_code_new_hash(&vsio_universe.code_hash, + VSIO_HASH_SIZE, MDL)) + log_fatal("Can't allocate Vendor Specific Information " + "Options space."); + for (i = 0 ; vsio_options[i].name != NULL ; i++) { + option_code_hash_add(vsio_universe.code_hash, + &vsio_options[i].code, 0, + &vsio_options[i], MDL); + option_name_hash_add(vsio_universe.name_hash, + vsio_options[i].name, 0, + &vsio_options[i], MDL); + } + + /* Add ISC VSIO sub-sub-option space. */ + isc6_universe.name = "isc6"; + isc6_universe.concat_duplicates = 0; + isc6_universe.lookup_func = lookup_hashed_option; + isc6_universe.option_state_dereference = + hashed_option_state_dereference; + isc6_universe.save_func = save_hashed_option; + isc6_universe.delete_func = delete_hashed_option; + isc6_universe.encapsulate = hashed_option_space_encapsulate; + isc6_universe.foreach = hashed_option_space_foreach; + isc6_universe.decode = parse_option_buffer; + isc6_universe.length_size = 0; + isc6_universe.tag_size = 4; + isc6_universe.get_tag = getULong; + isc6_universe.store_tag = putULong; + isc6_universe.get_length = NULL; + isc6_universe.store_length = NULL; + isc6_universe.site_code_min = 0; + /* No END option. */ + isc6_universe.end = 0x00; + code = 2495; + if (!option_code_hash_lookup(&isc6_universe.enc_opt, + vsio_universe.code_hash, &code, 0, MDL)) + log_fatal("Unable to find ISC parent option (%s:%d).", MDL); + isc6_universe.index = universe_count++; + universes[isc6_universe.index] = &isc6_universe; + if (!option_name_new_hash(&isc6_universe.name_hash, + VIV_ISC_HASH_SIZE, MDL) || + !option_code_new_hash(&isc6_universe.code_hash, + VIV_ISC_HASH_SIZE, MDL)) + log_fatal("Can't allocate Vendor Specific Information " + "Options space."); + for (i = 0 ; isc6_options[i].name != NULL ; i++) { + option_code_hash_add(isc6_universe.code_hash, + &isc6_options[i].code, 0, + &isc6_options[i], MDL); + option_name_hash_add(isc6_universe.name_hash, + isc6_options[i].name, 0, + &isc6_options[i], MDL); + } + + /* The fqdn6 option space is a protocol-wrapper shill for the + * old DHCPv4 space. + */ + fqdn6_universe.name = "fqdn6-if-you-see-me-its-a-bug-bug-bug"; + fqdn6_universe.lookup_func = lookup_fqdn6_option; + fqdn6_universe.option_state_dereference = NULL; /* Covered by v4. */ + fqdn6_universe.save_func = save_fqdn6_option; + fqdn6_universe.delete_func = delete_fqdn6_option; + fqdn6_universe.encapsulate = fqdn6_option_space_encapsulate; + fqdn6_universe.foreach = fqdn6_option_space_foreach; + fqdn6_universe.decode = fqdn6_universe_decode; + /* This is not a 'normal' encapsulated space, so these values are + * meaningless. + */ + fqdn6_universe.length_size = 0; + fqdn6_universe.tag_size = 0; + fqdn6_universe.get_tag = NULL; + fqdn6_universe.store_tag = NULL; + fqdn6_universe.get_length = NULL; + fqdn6_universe.store_length = NULL; + fqdn6_universe.site_code_min = 0; + fqdn6_universe.end = 0; + fqdn6_universe.index = universe_count++; + code = D6O_CLIENT_FQDN; + fqdn6_universe.enc_opt = NULL; + if (!option_code_hash_lookup(&fqdn6_universe.enc_opt, + dhcpv6_universe.code_hash, &code, 0, MDL)) + log_fatal("Unable to find FQDN v6 parent option. (%s:%d).", + MDL); + universes[fqdn6_universe.index] = &fqdn6_universe; + /* The fqdn6 space shares the same option space as the v4 space. + * So there are no name or code hashes on the v6 side. + */ + fqdn6_universe.name_hash = NULL; + fqdn6_universe.code_hash = NULL; + + + /* Set up the hash of DHCPv4 universes. */ + universe_new_hash(&universe_hash, UNIVERSE_HASH_SIZE, MDL); + universe_hash_add(universe_hash, dhcp_universe.name, 0, + &dhcp_universe, MDL); + universe_hash_add(universe_hash, nwip_universe.name, 0, + &nwip_universe, MDL); + universe_hash_add(universe_hash, fqdn_universe.name, 0, + &fqdn_universe, MDL); + universe_hash_add(universe_hash, vendor_class_universe.name, 0, + &vendor_class_universe, MDL); + universe_hash_add(universe_hash, vendor_universe.name, 0, + &vendor_universe, MDL); + universe_hash_add(universe_hash, isc_universe.name, 0, + &isc_universe, MDL); + + /* Set up hashes for DHCPv6 universes. */ + universe_hash_add(universe_hash, dhcpv6_universe.name, 0, + &dhcpv6_universe, MDL); + universe_hash_add(universe_hash, vsio_universe.name, 0, + &vsio_universe, MDL); + universe_hash_add(universe_hash, isc6_universe.name, 0, + &isc6_universe, MDL); +/* This should not be necessary. Listing here just for consistency. + * universe_hash_add(universe_hash, fqdn6_universe.name, 0, + * &fqdn6_universe, MDL); + */ +} diff --git a/common/tests/Atffile b/common/tests/Atffile new file mode 100644 index 0000000..10402ce --- /dev/null +++ b/common/tests/Atffile @@ -0,0 +1,5 @@ +Content-Type: application/X-atf-atffile; version="1" + +prop: test-suite = dhcp4 + +tp-glob: *_unittest diff --git a/common/tests/Makefile.am b/common/tests/Makefile.am new file mode 100644 index 0000000..cfe3d83 --- /dev/null +++ b/common/tests/Makefile.am @@ -0,0 +1,30 @@ +SUBDIRS = . + +AM_CPPFLAGS = $(ATF_CFLAGS) -I$(top_srcdir)/includes + +EXTRA_DIST = Atffile + +ATF_TESTS = + +if HAVE_ATF + +ATF_TESTS += alloc_unittest ns_name_unittest + +alloc_unittest_SOURCES = test_alloc.c $(top_srcdir)/tests/t_api_dhcp.c +alloc_unittest_LDADD = $(ATF_LDFLAGS) +alloc_unittest_LDADD += ../libdhcp.a \ + ../../omapip/libomapi.a ../../bind/lib/libdns.a \ + ../../bind/lib/libisc.a + +ns_name_unittest_SOURCES = ns_name_test.c $(top_srcdir)/tests/t_api_dhcp.c +ns_name_unittest_LDADD = $(ATF_LDFLAGS) +ns_name_unittest_LDADD += ../libdhcp.a \ + ../../omapip/libomapi.a ../../bind/lib/libdns.a \ + ../../bind/lib/libisc.a + +check: $(ATF_TESTS) + sh ${top_srcdir}/tests/unittest.sh + +endif + +check_PROGRAMS = $(ATF_TESTS) diff --git a/common/tests/Makefile.in b/common/tests/Makefile.in new file mode 100644 index 0000000..67d5865 --- /dev/null +++ b/common/tests/Makefile.in @@ -0,0 +1,694 @@ +# Makefile.in generated by automake 1.14 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +@HAVE_ATF_TRUE@am__append_1 = alloc_unittest ns_name_unittest +check_PROGRAMS = $(am__EXEEXT_2) +subdir = common/tests +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/depcomp +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/includes/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +@HAVE_ATF_TRUE@am__EXEEXT_1 = alloc_unittest$(EXEEXT) \ +@HAVE_ATF_TRUE@ ns_name_unittest$(EXEEXT) +am__EXEEXT_2 = $(am__EXEEXT_1) +am__alloc_unittest_SOURCES_DIST = test_alloc.c \ + $(top_srcdir)/tests/t_api_dhcp.c +@HAVE_ATF_TRUE@am_alloc_unittest_OBJECTS = test_alloc.$(OBJEXT) \ +@HAVE_ATF_TRUE@ t_api_dhcp.$(OBJEXT) +alloc_unittest_OBJECTS = $(am_alloc_unittest_OBJECTS) +am__DEPENDENCIES_1 = +@HAVE_ATF_TRUE@alloc_unittest_DEPENDENCIES = $(am__DEPENDENCIES_1) \ +@HAVE_ATF_TRUE@ ../libdhcp.a ../../omapip/libomapi.a \ +@HAVE_ATF_TRUE@ ../../bind/lib/libdns.a ../../bind/lib/libisc.a +am__ns_name_unittest_SOURCES_DIST = ns_name_test.c \ + $(top_srcdir)/tests/t_api_dhcp.c +@HAVE_ATF_TRUE@am_ns_name_unittest_OBJECTS = ns_name_test.$(OBJEXT) \ +@HAVE_ATF_TRUE@ t_api_dhcp.$(OBJEXT) +ns_name_unittest_OBJECTS = $(am_ns_name_unittest_OBJECTS) +@HAVE_ATF_TRUE@ns_name_unittest_DEPENDENCIES = $(am__DEPENDENCIES_1) \ +@HAVE_ATF_TRUE@ ../libdhcp.a ../../omapip/libomapi.a \ +@HAVE_ATF_TRUE@ ../../bind/lib/libdns.a ../../bind/lib/libisc.a +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/includes +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(alloc_unittest_SOURCES) $(ns_name_unittest_SOURCES) +DIST_SOURCES = $(am__alloc_unittest_SOURCES_DIST) \ + $(am__ns_name_unittest_SOURCES_DIST) +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +ATF_BIN = @ATF_BIN@ +ATF_CFLAGS = @ATF_CFLAGS@ +ATF_LDFLAGS = @ATF_LDFLAGS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDAP_CFLAGS = @LDAP_CFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_prefix_program = @ac_prefix_program@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +byte_order = @byte_order@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = . +AM_CPPFLAGS = $(ATF_CFLAGS) -I$(top_srcdir)/includes +EXTRA_DIST = Atffile +ATF_TESTS = $(am__append_1) +@HAVE_ATF_TRUE@alloc_unittest_SOURCES = test_alloc.c $(top_srcdir)/tests/t_api_dhcp.c +@HAVE_ATF_TRUE@alloc_unittest_LDADD = $(ATF_LDFLAGS) ../libdhcp.a \ +@HAVE_ATF_TRUE@ ../../omapip/libomapi.a ../../bind/lib/libdns.a \ +@HAVE_ATF_TRUE@ ../../bind/lib/libisc.a +@HAVE_ATF_TRUE@ns_name_unittest_SOURCES = ns_name_test.c $(top_srcdir)/tests/t_api_dhcp.c +@HAVE_ATF_TRUE@ns_name_unittest_LDADD = $(ATF_LDFLAGS) ../libdhcp.a \ +@HAVE_ATF_TRUE@ ../../omapip/libomapi.a ../../bind/lib/libdns.a \ +@HAVE_ATF_TRUE@ ../../bind/lib/libisc.a +all: all-recursive + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign common/tests/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign common/tests/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-checkPROGRAMS: + -test -z "$(check_PROGRAMS)" || rm -f $(check_PROGRAMS) + +alloc_unittest$(EXEEXT): $(alloc_unittest_OBJECTS) $(alloc_unittest_DEPENDENCIES) $(EXTRA_alloc_unittest_DEPENDENCIES) + @rm -f alloc_unittest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(alloc_unittest_OBJECTS) $(alloc_unittest_LDADD) $(LIBS) + +ns_name_unittest$(EXEEXT): $(ns_name_unittest_OBJECTS) $(ns_name_unittest_DEPENDENCIES) $(EXTRA_ns_name_unittest_DEPENDENCIES) + @rm -f ns_name_unittest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(ns_name_unittest_OBJECTS) $(ns_name_unittest_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ns_name_test.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_api_dhcp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_alloc.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +t_api_dhcp.o: $(top_srcdir)/tests/t_api_dhcp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT t_api_dhcp.o -MD -MP -MF $(DEPDIR)/t_api_dhcp.Tpo -c -o t_api_dhcp.o `test -f '$(top_srcdir)/tests/t_api_dhcp.c' || echo '$(srcdir)/'`$(top_srcdir)/tests/t_api_dhcp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_api_dhcp.Tpo $(DEPDIR)/t_api_dhcp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(top_srcdir)/tests/t_api_dhcp.c' object='t_api_dhcp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o t_api_dhcp.o `test -f '$(top_srcdir)/tests/t_api_dhcp.c' || echo '$(srcdir)/'`$(top_srcdir)/tests/t_api_dhcp.c + +t_api_dhcp.obj: $(top_srcdir)/tests/t_api_dhcp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT t_api_dhcp.obj -MD -MP -MF $(DEPDIR)/t_api_dhcp.Tpo -c -o t_api_dhcp.obj `if test -f '$(top_srcdir)/tests/t_api_dhcp.c'; then $(CYGPATH_W) '$(top_srcdir)/tests/t_api_dhcp.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/tests/t_api_dhcp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_api_dhcp.Tpo $(DEPDIR)/t_api_dhcp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(top_srcdir)/tests/t_api_dhcp.c' object='t_api_dhcp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o t_api_dhcp.obj `if test -f '$(top_srcdir)/tests/t_api_dhcp.c'; then $(CYGPATH_W) '$(top_srcdir)/tests/t_api_dhcp.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/tests/t_api_dhcp.c'; fi` + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS) +check: check-recursive +all-am: Makefile +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-checkPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-recursive + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) check-am install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \ + check-am clean clean-checkPROGRAMS clean-generic cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + installdirs-am maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic pdf pdf-am \ + ps ps-am tags tags-am uninstall uninstall-am + + +@HAVE_ATF_TRUE@check: $(ATF_TESTS) +@HAVE_ATF_TRUE@ sh ${top_srcdir}/tests/unittest.sh + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/common/tests/ns_name_test.c b/common/tests/ns_name_test.c new file mode 100644 index 0000000..93ffa76 --- /dev/null +++ b/common/tests/ns_name_test.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2014 by Internet Systems Consortium, Inc. ("ISC") + * + * Tests the newly added functions: MRns_name_compress_list and + * MRns_name_uncompress_list. These two functions rely on most of + * the other functions in ns_name.c. If these tests pass, then the + * majority of those functions work. + * + * This is not exhaustive test of these functions, much more could be + * done. + */ +#include "config.h" +#include <atf-c.h> +#include "dhcpd.h" + +ATF_TC(MRns_name_list_funcs); + +ATF_TC_HEAD(MRns_name_list_funcs, tc) { + atf_tc_set_md_var(tc, "descr", "MRns_name list funcs test, " + "compress from text, decompress to text"); +} + +void concat_lists (const char* label, struct data_string* list1, + struct data_string* list2, const char *refbuf, size_t reflen); + +ATF_TC_BODY(MRns_name_list_funcs, tc) { + + const char text_list[] = "one.two.com,three.two.com,four.two.com"; + unsigned char comp_list[] = { + 0x03,0x6f,0x6e,0x65,0x03,0x74,0x77,0x6f,0x03,0x63,0x6f, + 0x6d,0x00,0x05,0x74,0x68,0x72,0x65,0x65,0xc0,0x04,0x04, + 0x66,0x6f,0x75,0x72,0xc0,0x04}; + unsigned char compbuf[sizeof(comp_list)]; + char textbuf[sizeof(text_list)]; + int ret; + + memset(compbuf, 0x00, sizeof(compbuf)); + + /* Compress the reference text list */ + ret = MRns_name_compress_list(text_list, sizeof(text_list), + compbuf, sizeof(compbuf)); + + /* Verify compressed length is correct */ + ATF_REQUIRE_MSG((ret == sizeof(compbuf)), "compressed len %d wrong", ret); + + /* Verify compressed content is correct */ + ATF_REQUIRE_MSG((memcmp(comp_list, compbuf, sizeof(compbuf)) == 0), + "compressed buffer content wrong"); + + /* Decompress the new compressed list */ + ret = MRns_name_uncompress_list(compbuf, ret, textbuf, sizeof(textbuf)); + + /* Verify decompressed length is correct */ + ATF_REQUIRE_MSG((ret == strlen(text_list)), + "uncompressed len %d wrong", ret); + + /* Verify decompressed content is correct */ + ATF_REQUIRE_MSG((memcmp(textbuf, text_list, sizeof(textbuf)) == 0), + "uncompressed buffer content wrong"); +} + +ATF_TC(concat_dclists); + +ATF_TC_HEAD(concat_dclists, tc) { + atf_tc_set_md_var(tc, "descr", "concat_dclists function test, " + "permutate concating empty and non-empty lists"); +} + +ATF_TC_BODY(concat_dclists, tc) { + /* Compressed list version of "booya.com" */ + const char data[] = + {0x05, 0x62, 0x6f, 0x6f, 0x79, 0x61, 0x03, 0x63, 0x6f, 0x6d, 0x00 }; + + /* Concatenation of data with itself */ + const char data2[] = + {0x05, 0x62, 0x6f, 0x6f, 0x79, 0x61, 0x03, 0x63, 0x6f, 0x6d, 0x00, + 0xc0, 0x00 }; + + struct data_string nonempty; + struct data_string empty; + + /* Make a non-empty compressed list from data[] */ + nonempty.len = sizeof(data); + buffer_allocate(&(nonempty.buffer), nonempty.len, MDL); + memcpy(nonempty.buffer->data, data, nonempty.len); + nonempty.data = nonempty.buffer->data; + + /* Permutate NULL with non-empty list */ + concat_lists("null + null", NULL, NULL, "", 1); + concat_lists("null + nonempty", NULL, &nonempty, data, sizeof(data)); + concat_lists("nonempty + null", &nonempty, NULL, data, sizeof(data)); + + /* Permutate zeroed-out list with non-empty list */ + memset (&empty, 0x00, sizeof(struct data_string)); + concat_lists("zero-list + zero-list", &empty, &empty, "", 1); + concat_lists("zero-list + nonempty", &empty, &nonempty, data, sizeof(data)); + concat_lists("nonempty + zero-list", &nonempty, &empty, data, sizeof(data)); + + /* Create an empty list which is a buffer with 1 null in it */ + /* Make sure those work the same as zeroed out data_strings */ + buffer_allocate (&empty.buffer, 1, MDL); + empty.len = 1; + empty.data = empty.buffer->data; + + /* Permutate with empty list */ + concat_lists("empty + empty", &empty, &empty, "", 1); + concat_lists("empty + nonempty", &empty, &nonempty, data, sizeof(data)); + concat_lists("nonempty + empty", &nonempty, &empty, data, sizeof(data)); + + /* Permutate with list len of 0 */ + empty.len = 0; + concat_lists("zero-len + zero-len", &empty, &empty, "", 1); + concat_lists("zero-len + nonempty", &empty, &nonempty, data, sizeof(data)); + concat_lists("nonempty + zero-len", &nonempty, &empty, data, sizeof(data)); + + /* Lastly, make sure two non-empty lists concat correctly */ + concat_lists("nonempty + nonempty", &nonempty, &nonempty, + data2, sizeof(data2)); + + data_string_forget(&empty, MDL); + data_string_forget(&nonempty, MDL); +} + +/* Helper function which tests concatenating two compressed lists +* +* \param label text to print in error message +* \param list1 data_string containing first list +* \param list2 data_string containing second list +* \param refbuf buffer containing the expected concatentated data +* \param reflen length of the expected data +*/ +void concat_lists (const char* label, struct data_string* list1, + struct data_string* list2, const char *refbuf, size_t reflen) +{ + struct data_string result; + int rc; + + memset (&result, 0x00, sizeof(struct data_string)); + rc = concat_dclists (&result, list1, list2); + ATF_REQUIRE_MSG((rc >= 0), "%s concat_dclists call failed %d", + label, rc); + + ATF_REQUIRE_MSG(result.len == reflen, "%s result len: %d incorrect", + label, result.len); + + if (refbuf != NULL) { + ATF_REQUIRE_MSG((memcmp(result.data, refbuf, reflen) == 0), + "%s content is incorrect", label); + } + + data_string_forget(&result, MDL); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, MRns_name_list_funcs); + ATF_TP_ADD_TC(tp, concat_dclists); + + return (atf_no_error()); +} diff --git a/common/tests/test_alloc.c b/common/tests/test_alloc.c new file mode 100644 index 0000000..9e08213 --- /dev/null +++ b/common/tests/test_alloc.c @@ -0,0 +1,501 @@ +/* + * Copyright (c) 2007,2009-2014 by Internet Systems Consortium, Inc. ("ISC") + * + * We test the functions provided in alloc.c here. These are very + * basic functions, and it is very important that they work correctly. + * + * You can see two different styles of testing: + * + * - In the first, we have a single test for each function that tests + * all of the possible ways it can operate. (This is the case for + * the buffer tests.) + * + * - In the second, we have a separate test for each of the ways a + * function can operate. (This is the case for the data_string + * tests.) + * + * The advantage of a single test per function is that you have fewer + * tests, and less duplicated and extra code. The advantage of having + * a separate test is that each test is simpler. Plus if you need to + * allow certain tests to fail for some reason (known bugs that are + * hard to fix for example), then + */ + +/** @TODO: dmalloc() test */ + +#include "config.h" +#include <atf-c.h> +#include "dhcpd.h" + +static const char* checkString (struct data_string* ds, const char *src); + +ATF_TC(buffer_allocate); + +ATF_TC_HEAD(buffer_allocate, tc) { + atf_tc_set_md_var(tc, "descr", "buffer_allocate basic test"); +} + +ATF_TC_BODY(buffer_allocate, tc) { + struct buffer *buf = 0; + + /* + * Check a 0-length buffer. + */ + buf = NULL; + if (!buffer_allocate(&buf, 0, MDL)) { + atf_tc_fail("failed on 0-len buffer"); + } + if (!buffer_dereference(&buf, MDL)) { + atf_tc_fail("buffer_dereference() failed"); + } + if (buf != NULL) { + atf_tc_fail("buffer_dereference() did not NULL-out buffer"); + } + + /* + * Check an actual buffer. + */ + buf = NULL; + if (!buffer_allocate(&buf, 100, MDL)) { + atf_tc_fail("failed on allocate 100 bytes\n"); + } + if (!buffer_dereference(&buf, MDL)) { + atf_tc_fail("buffer_dereference() failed"); + } + if (buf != NULL) { + atf_tc_fail("buffer_dereference() did not NULL-out buffer"); + } + + /* + * Okay, we're happy. + */ + atf_tc_pass(); +} + +ATF_TC(buffer_reference); + +ATF_TC_HEAD(buffer_reference, tc) { + atf_tc_set_md_var(tc, "descr", "buffer_reference basic test"); +} + +ATF_TC_BODY(buffer_reference, tc) { + + struct buffer *a, *b; + + /* + * Create a buffer. + */ + a = NULL; + if (!buffer_allocate(&a, 100, MDL)) { + atf_tc_fail("failed on allocate 100 bytes"); + } + + /** + * Confirm buffer_reference() doesn't work if we pass in NULL. + * + * @TODO: we should confirm we get an error message here. + */ + if (buffer_reference(NULL, a, MDL)) { + atf_tc_fail("succeeded on an error input"); + } + + /** + * @TODO: we should confirm we get an error message if we pass + * a non-NULL target. + */ + + /* + * Confirm we work under normal circumstances. + */ + b = NULL; + if (!buffer_reference(&b, a, MDL)) { + atf_tc_fail("buffer_reference() failed"); + } + + if (b != a) { + atf_tc_fail("incorrect pointer returned"); + } + + if (b->refcnt != 2) { + atf_tc_fail("incorrect refcnt"); + } + + /* + * Clean up. + */ + if (!buffer_dereference(&b, MDL)) { + atf_tc_fail("buffer_dereference() failed"); + } + if (!buffer_dereference(&a, MDL)) { + atf_tc_fail("buffer_dereference() failed"); + } + +} + + +ATF_TC(buffer_dereference); + +ATF_TC_HEAD(buffer_dereference, tc) { + atf_tc_set_md_var(tc, "descr", "buffer_dereference basic test"); +} + +ATF_TC_BODY(buffer_dereference, tc) { + struct buffer *a, *b; + + /** + * Confirm buffer_dereference() doesn't work if we pass in NULL. + * + * TODO: we should confirm we get an error message here. + */ + if (buffer_dereference(NULL, MDL)) { + atf_tc_fail("succeeded on an error input"); + } + + /** + * Confirm buffer_dereference() doesn't work if we pass in + * a pointer to NULL. + * + * @TODO: we should confirm we get an error message here. + */ + a = NULL; + if (buffer_dereference(&a, MDL)) { + atf_tc_fail("succeeded on an error input"); + } + + /* + * Confirm we work under normal circumstances. + */ + a = NULL; + if (!buffer_allocate(&a, 100, MDL)) { + atf_tc_fail("failed on allocate"); + } + if (!buffer_dereference(&a, MDL)) { + atf_tc_fail("buffer_dereference() failed"); + } + if (a != NULL) { + atf_tc_fail("non-null buffer after buffer_dereference()"); + } + + /** + * Confirm we get an error from negative refcnt. + * + * @TODO: we should confirm we get an error message here. + */ + a = NULL; + if (!buffer_allocate(&a, 100, MDL)) { + atf_tc_fail("failed on allocate"); + } + b = NULL; + if (!buffer_reference(&b, a, MDL)) { + atf_tc_fail("buffer_reference() failed"); + } + a->refcnt = 0; /* purposely set to invalid value */ + if (buffer_dereference(&a, MDL)) { + atf_tc_fail("buffer_dereference() succeeded on error input"); + } + a->refcnt = 2; + if (!buffer_dereference(&b, MDL)) { + atf_tc_fail("buffer_dereference() failed"); + } + if (!buffer_dereference(&a, MDL)) { + atf_tc_fail("buffer_dereference() failed"); + } +} + +ATF_TC(data_string_forget); + +ATF_TC_HEAD(data_string_forget, tc) { + atf_tc_set_md_var(tc, "descr", "data_string_forget basic test"); +} + +ATF_TC_BODY(data_string_forget, tc) { + struct buffer *buf; + struct data_string a; + const char *str = "Lorem ipsum dolor sit amet turpis duis."; + + /* + * Create the string we want to forget. + */ + memset(&a, 0, sizeof(a)); + a.len = strlen(str); + buf = NULL; + if (!buffer_allocate(&buf, a.len, MDL)) { + atf_tc_fail("out of memory"); + } + if (!buffer_reference(&a.buffer, buf, MDL)) { + atf_tc_fail("buffer_reference() failed"); + } + a.data = a.buffer->data; + memcpy(a.buffer->data, str, a.len); + + /* + * Forget and confirm we've forgotten. + */ + data_string_forget(&a, MDL); + + if (a.len != 0) { + atf_tc_fail("incorrect length"); + } + + if (a.data != NULL) { + atf_tc_fail("incorrect data"); + } + if (a.terminated) { + atf_tc_fail("incorrect terminated"); + } + if (a.buffer != NULL) { + atf_tc_fail("incorrect buffer"); + } + if (buf->refcnt != 1) { + atf_tc_fail("too many references to buf"); + } + + /* + * Clean up buffer. + */ + if (!buffer_dereference(&buf, MDL)) { + atf_tc_fail("buffer_reference() failed"); + } +} + +ATF_TC(data_string_forget_nobuf); + +ATF_TC_HEAD(data_string_forget_nobuf, tc) { + atf_tc_set_md_var(tc, "descr", "data_string_forget test, " + "data_string without buffer"); +} + +ATF_TC_BODY(data_string_forget_nobuf, tc) { + struct data_string a; + const char *str = "Lorem ipsum dolor sit amet massa nunc."; + + /* + * Create the string we want to forget. + */ + memset(&a, 0, sizeof(a)); + a.len = strlen(str); + a.data = (const unsigned char *)str; + a.terminated = 1; + + /* + * Forget and confirm we've forgotten. + */ + data_string_forget(&a, MDL); + + if (a.len != 0) { + atf_tc_fail("incorrect length"); + } + if (a.data != NULL) { + atf_tc_fail("incorrect data"); + } + if (a.terminated) { + atf_tc_fail("incorrect terminated"); + } + if (a.buffer != NULL) { + atf_tc_fail("incorrect buffer"); + } +} + +ATF_TC(data_string_copy); + +ATF_TC_HEAD(data_string_copy, tc) { + atf_tc_set_md_var(tc, "descr", "data_string_copy basic test"); +} + +ATF_TC_BODY(data_string_copy, tc) { + struct data_string a, b; + const char *str = "Lorem ipsum dolor sit amet orci aliquam."; + + /* + * Create the string we want to copy. + */ + memset(&a, 0, sizeof(a)); + a.len = strlen(str); + if (!buffer_allocate(&a.buffer, a.len, MDL)) { + atf_tc_fail("out of memory"); + } + a.data = a.buffer->data; + memcpy(a.buffer->data, str, a.len); + + /* + * Copy the string, and confirm it works. + */ + memset(&b, 0, sizeof(b)); + data_string_copy(&b, &a, MDL); + + if (b.len != a.len) { + atf_tc_fail("incorrect length"); + } + if (b.data != a.data) { + atf_tc_fail("incorrect data"); + } + if (b.terminated != a.terminated) { + atf_tc_fail("incorrect terminated"); + } + if (b.buffer != a.buffer) { + atf_tc_fail("incorrect buffer"); + } + + /* + * Clean up. + */ + data_string_forget(&b, MDL); + data_string_forget(&a, MDL); +} + +ATF_TC(data_string_copy_nobuf); + +ATF_TC_HEAD(data_string_copy_nobuf, tc) { + atf_tc_set_md_var(tc, "descr", "data_string_copy test, " + "data_string without buffer"); +} + +ATF_TC_BODY(data_string_copy_nobuf, tc) { + struct data_string a, b; + const char *str = "Lorem ipsum dolor sit amet cras amet."; + + /* + * Create the string we want to copy. + */ + memset(&a, 0, sizeof(a)); + a.len = strlen(str); + a.data = (const unsigned char *)str; + a.terminated = 1; + + /* + * Copy the string, and confirm it works. + */ + memset(&b, 0, sizeof(b)); + data_string_copy(&b, &a, MDL); + + if (b.len != a.len) { + atf_tc_fail("incorrect length"); + } + if (b.data != a.data) { + atf_tc_fail("incorrect data"); + } + if (b.terminated != a.terminated) { + atf_tc_fail("incorrect terminated"); + } + if (b.buffer != a.buffer) { + atf_tc_fail("incorrect buffer"); + } + + /* + * Clean up. + */ + data_string_forget(&b, MDL); + data_string_forget(&a, MDL); + +} + + +ATF_TC(data_string_new); + +ATF_TC_HEAD(data_string_new, tc) { + atf_tc_set_md_var(tc, "descr", "data_string_new test, " + "exercises data_string_new function"); +} + +ATF_TC_BODY(data_string_new, tc) { + struct data_string new_string; + const char *src = "Really? Latin? ... geeks"; + int len_arg = 0; + const char *error; + + /* Case 1: Call with an invalid data_string pointer, should fail */ + if (data_string_new(NULL, src, len_arg, MDL)) { + atf_tc_fail("case 1: call should have failed"); + } + + /* Case 2: Passing in NULL src should fail */ + if (data_string_new(&new_string, NULL, 10, MDL)) { + atf_tc_fail("case 2: did not return success"); + } + + /* Case 3: Call with valid params, length includes NULL */ + len_arg = strlen(src) + 1; + if (data_string_new(&new_string, src, len_arg, MDL) == 0) { + atf_tc_fail("case 3: did not return success"); + } + + error = checkString(&new_string, src); + ATF_REQUIRE_MSG((error == NULL), "case 3: %s", error); + data_string_forget(&new_string, MDL); + + + /* Case 4: Call with valid params, length does not include NULL */ + len_arg = 7; + if (data_string_new(&new_string, src, len_arg, MDL) == 0) { + atf_tc_fail("case 4: did not return success"); + } + + error = checkString(&new_string, "Really?"); + ATF_REQUIRE_MSG((error == NULL), "case 4: %s", error); + data_string_forget(&new_string, MDL); + + + /* Case 5: Call with valid params, source string is "" */ + len_arg = 0; + if (data_string_new(&new_string, "", len_arg, MDL) == 0) { + atf_tc_fail("case 5: did not return success"); + } + + error = checkString(&new_string, ""); + ATF_REQUIRE_MSG((error == NULL), "case 4: %s", error); + data_string_forget(&new_string, MDL); + + +} + +/* Helper function which tests validity of a data_string +* +* Verifies that the given data_string contains a null-terminated string +* equal to a given string. +* +* \param string data_string to test +* \param src text content string should contain +* \return returns NULL if data_string is validate or an error message +* describing why it is invalid +*/ +const char* checkString (struct data_string* string, + const char* src) { + int src_len = strlen(src); + + if (string->buffer == NULL) { + return ("buffer is NULL"); + } + + if (string->data != string->buffer->data) { + return ("data not set to buffer->data"); + } + + if (string->len != src_len) { + return ("len is wrong "); + } + + if (string->terminated != 1) { + return ("terminated flag not set"); + } + + if (memcmp(string->data, src, src_len + 1)) { + return ("data content wrong"); + } + + return (NULL); +} + + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, buffer_allocate); + ATF_TP_ADD_TC(tp, buffer_reference); + ATF_TP_ADD_TC(tp, buffer_dereference); + ATF_TP_ADD_TC(tp, data_string_forget); + ATF_TP_ADD_TC(tp, data_string_forget_nobuf); + ATF_TP_ADD_TC(tp, data_string_copy); + ATF_TP_ADD_TC(tp, data_string_copy_nobuf); + ATF_TP_ADD_TC(tp, data_string_new); + + return (atf_no_error()); +} diff --git a/common/tr.c b/common/tr.c new file mode 100644 index 0000000..6e05faf --- /dev/null +++ b/common/tr.c @@ -0,0 +1,331 @@ +/* tr.c + + token ring interface support + Contributed in May of 1999 by Andrew Chittenden */ + +/* + * Copyright (c) 2011 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2007,2009 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + */ + +#include "dhcpd.h" + +#if defined (HAVE_TR_SUPPORT) && \ + (defined (PACKET_ASSEMBLY) || defined (PACKET_DECODING)) +#include "includes/netinet/ip.h" +#include "includes/netinet/udp.h" +#include "includes/netinet/if_ether.h" +#include "netinet/if_tr.h" +#include <sys/time.h> + +/* + * token ring device handling subroutines. These are required as token-ring + * does not have a simple on-the-wire header but requires the use of + * source routing + */ + +static int insert_source_routing (struct trh_hdr *trh, struct interface_info* interface); +static void save_source_routing (struct trh_hdr *trh, struct interface_info* interface); +static void expire_routes (void); + +/* + * As we keep a list of interesting routing information only, a singly + * linked list is all we need + */ +struct routing_entry { + struct routing_entry *next; + unsigned char addr[TR_ALEN]; + unsigned char iface[5]; + u_int16_t rcf; /* route control field */ + u_int16_t rseg[8]; /* routing registers */ + unsigned long access_time; /* time we last used this entry */ +}; + +static struct routing_entry *routing_info = NULL; + +static int routing_timeout = 10; +static struct timeval routing_timer; + +void assemble_tr_header (interface, buf, bufix, to) + struct interface_info *interface; + unsigned char *buf; + unsigned *bufix; + struct hardware *to; +{ + struct trh_hdr *trh; + int hdr_len; + struct trllc *llc; + + + /* set up the token header */ + trh = (struct trh_hdr *) &buf[*bufix]; + if (interface -> hw_address.hlen - 1 == sizeof (trh->saddr)) + memcpy (trh->saddr, &interface -> hw_address.hbuf [1], + sizeof (trh->saddr)); + else + memset (trh->saddr, 0x00, sizeof (trh->saddr)); + + if (to && to -> hlen == 7) /* XXX */ + memcpy (trh->daddr, &to -> hbuf [1], sizeof trh->daddr); + else + memset (trh->daddr, 0xff, sizeof (trh->daddr)); + + hdr_len = insert_source_routing (trh, interface); + + trh->ac = AC; + trh->fc = LLC_FRAME; + + /* set up the llc header for snap encoding after the tr header */ + llc = (struct trllc *)(buf + *bufix + hdr_len); + llc->dsap = EXTENDED_SAP; + llc->ssap = EXTENDED_SAP; + llc->llc = UI_CMD; + llc->protid[0] = 0; + llc->protid[1] = 0; + llc->protid[2] = 0; + llc->ethertype = htons(ETHERTYPE_IP); + + hdr_len += sizeof(struct trllc); + + *bufix += hdr_len; +} + + +static unsigned char tr_broadcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +/* + * decoding the token header is a bit complex as you can see here. It is + * further complicated by the linux kernel stripping off some valuable + * information (see comment below) even though we've asked for the raw + * packets. + */ +ssize_t decode_tr_header (interface, buf, bufix, from) + struct interface_info *interface; + unsigned char *buf; + unsigned bufix; + struct hardware *from; +{ + struct trh_hdr *trh = (struct trh_hdr *) buf + bufix; + struct trllc *llc; + struct ip *ip; + struct udphdr *udp; + unsigned int route_len = 0; + ssize_t hdr_len; + struct timeval now; + + /* see whether any source routing information has expired */ + gettimeofday(&now, NULL); + + if (routing_timer.tv_sec == 0) + routing_timer.tv_sec = now.tv_sec + routing_timeout; + else if ((now.tv_sec - routing_timer.tv_sec) > 0) + expire_routes(); + + /* the kernel might have stripped off the source + * routing bit. We try a heuristic to determine whether + * this is the case and put it back on if so + */ + route_len = (ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8; + llc = (struct trllc *)(buf + bufix + sizeof(struct trh_hdr)-TR_MAXRIFLEN+route_len); + if (llc->dsap == EXTENDED_SAP + && llc->ssap == EXTENDED_SAP + && llc->llc == UI_CMD + && llc->protid[0] == 0 + && llc->protid[1] == 0 + && llc->protid[2] == 0) { + /* say there is source routing information present */ + trh->saddr[0] |= TR_RII; + } + + if (trh->saddr[0] & TR_RII) + route_len = (ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8; + else + route_len = 0; + + hdr_len = sizeof (struct trh_hdr) - TR_MAXRIFLEN + route_len; + + /* now filter out unwanted packets: this is based on the packet + * filter code in bpf.c */ + llc = (struct trllc *)(buf + bufix + hdr_len); + ip = (struct ip *) (llc + 1); + udp = (struct udphdr *) ((unsigned char*) ip + IP_HL (ip)); + + /* make sure it is a snap encoded, IP, UDP, unfragmented packet sent + * to our port */ + if (llc->dsap != EXTENDED_SAP + || ntohs(llc->ethertype) != ETHERTYPE_IP + || ip->ip_p != IPPROTO_UDP + || (ntohs (ip->ip_off) & IP_OFFMASK) != 0 + || udp->uh_dport != local_port) + return -1; + + /* only save source routing information for packets from valued hosts */ + save_source_routing(trh, interface); + + return hdr_len + sizeof (struct trllc); +} + +/* insert_source_routing inserts source route information into the token ring + * header + */ +static int insert_source_routing (trh, interface) + struct trh_hdr *trh; + struct interface_info* interface; +{ + struct routing_entry *rover; + struct timeval now; + unsigned int route_len = 0; + + gettimeofday(&now, NULL); + + /* single route broadcasts as per rfc 1042 */ + if (memcmp(trh->daddr, tr_broadcast,TR_ALEN) == 0) { + trh->saddr[0] |= TR_RII; + trh->rcf = ((sizeof(trh->rcf)) << 8) & TR_RCF_LEN_MASK; + trh->rcf |= (TR_RCF_FRAME2K | TR_RCF_LIMITED_BROADCAST); + trh->rcf = htons(trh->rcf); + } else { + /* look for a routing entry */ + for (rover = routing_info; rover != NULL; rover = rover->next) { + if (memcmp(rover->addr, trh->daddr, TR_ALEN) == 0) + break; + } + + if (rover != NULL) { + /* success: route that frame */ + if ((rover->rcf & TR_RCF_LEN_MASK) >> 8) { + u_int16_t rcf = rover->rcf; + memcpy(trh->rseg,rover->rseg,sizeof(trh->rseg)); + rcf ^= TR_RCF_DIR_BIT; + rcf &= ~TR_RCF_BROADCAST_MASK; + trh->rcf = htons(rcf); + trh->saddr[0] |= TR_RII; + } + rover->access_time = now.tv_sec; + } else { + /* we don't have any routing information so send a + * limited broadcast */ + trh->saddr[0] |= TR_RII; + trh->rcf = ((sizeof(trh->rcf)) << 8) & TR_RCF_LEN_MASK; + trh->rcf |= (TR_RCF_FRAME2K | TR_RCF_LIMITED_BROADCAST); + trh->rcf = htons(trh->rcf); + } + } + + /* return how much of the header we've actually used */ + if (trh->saddr[0] & TR_RII) + route_len = (ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8; + else + route_len = 0; + + return sizeof (struct trh_hdr) - TR_MAXRIFLEN + route_len; +} + +/* + * save any source routing information + */ +static void save_source_routing(trh, interface) + struct trh_hdr *trh; + struct interface_info *interface; +{ + struct routing_entry *rover; + struct timeval now; + unsigned char saddr[TR_ALEN]; + u_int16_t rcf = 0; + + gettimeofday(&now, NULL); + + memcpy(saddr, trh->saddr, sizeof(saddr)); + saddr[0] &= 0x7f; /* strip off source routing present flag */ + + /* scan our table to see if we've got it */ + for (rover = routing_info; rover != NULL; rover = rover->next) { + if (memcmp(&rover->addr[0], &saddr[0], TR_ALEN) == 0) + break; + } + + /* found an entry so update it with fresh information */ + if (rover != NULL) { + if ((trh->saddr[0] & TR_RII) && + ((ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8) > 2) { + rcf = ntohs(trh->rcf); + rcf &= ~TR_RCF_BROADCAST_MASK; + memcpy(rover->rseg, trh->rseg, sizeof(rover->rseg)); + } + rover->rcf = rcf; + rover->access_time = now.tv_sec; + return; /* that's all folks */ + } + + /* no entry found, so create one */ + rover = dmalloc (sizeof (struct routing_entry), MDL); + if (rover == NULL) { + fprintf(stderr, + "%s: unable to save source routing information\n", + __FILE__); + return; + } + + memcpy(rover->addr, saddr, sizeof(rover->addr)); + memcpy(rover->iface, interface->name, 5); + rover->access_time = now.tv_sec; + if (trh->saddr[0] & TR_RII) { + if (((ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8) > 2) { + rcf = ntohs(trh->rcf); + rcf &= ~TR_RCF_BROADCAST_MASK; + memcpy(rover->rseg, trh->rseg, sizeof(rover->rseg)); + } + rover->rcf = rcf; + } + + /* insert into list */ + rover->next = routing_info; + routing_info = rover; + + return; +} + +/* + * get rid of old routes + */ +static void expire_routes() +{ + struct routing_entry *rover; + struct routing_entry **prover = &routing_info; + struct timeval now; + + gettimeofday(&now, NULL); + + while((rover = *prover) != NULL) { + if ((now.tv_sec - rover->access_time) > routing_timeout) { + *prover = rover->next; + dfree (rover, MDL); + } else + prover = &rover->next; + } + + /* Reset the timer */ + routing_timer.tv_sec = now.tv_sec + routing_timeout; + routing_timer.tv_usec = now.tv_usec; +} + +#endif diff --git a/common/tree.c b/common/tree.c new file mode 100644 index 0000000..23e86fa --- /dev/null +++ b/common/tree.c @@ -0,0 +1,4643 @@ +/* tree.c + + Routines for manipulating parse trees... */ + +/* + * Copyright (c) 2011-2013,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2007,2009 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" +#include <omapip/omapip_p.h> +#include <ctype.h> +#include <sys/wait.h> + +#ifdef HAVE_REGEX_H +# include <regex.h> +#endif + +struct binding_scope *global_scope; + +static int do_host_lookup (struct data_string *, struct dns_host_entry *); + +#define DS_SPRINTF_SIZE 128 + +/* + * If we are using a data_string structure to hold a NUL-terminated + * ASCII string, this function can be used to append a printf-formatted + * string to the end of it. The data_string structure will be resized to + * be big enough to hold the new string. + * + * If the append works, then 1 is returned. + * + * If it is not possible to allocate a buffer big enough to hold the + * new value, then the old data_string is unchanged, and 0 is returned. + */ +int +data_string_sprintfa(struct data_string *ds, const char *fmt, ...) { + va_list args; + int cur_strlen; + int max; + int vsnprintf_ret; + int new_len; + struct buffer *tmp_buffer; + + /* + * If the data_string is empty, then initialize it. + */ + if (ds->data == NULL) { + /* INSIST(ds.buffer == NULL); */ + if (!buffer_allocate(&ds->buffer, DS_SPRINTF_SIZE, MDL)) { + return 0; + } + ds->data = ds->buffer->data; + ds->len = DS_SPRINTF_SIZE; + *((char *)ds->data) = '\0'; + } + + /* + * Get the length of the string, and figure out how much space + * is left. + */ + cur_strlen = strlen((char *)ds->data); + max = ds->len - cur_strlen; + + /* + * Use vsnprintf(), which won't write past our space, but will + * tell us how much space it wants. + */ + va_start(args, fmt); + vsnprintf_ret = vsnprintf((char *)ds->data+cur_strlen, max, fmt, args); + va_end(args); + /* INSIST(vsnprintf_ret >= 0); */ + + /* + * If our buffer is not big enough, we need a new buffer. + */ + if (vsnprintf_ret >= max) { + /* + * Figure out a size big enough. + */ + new_len = ds->len * 2; + while (new_len <= cur_strlen + vsnprintf_ret) { + new_len *= 2; + } + + /* + * Create a new buffer and fill it. + */ + tmp_buffer = NULL; + if (!buffer_allocate(&tmp_buffer, new_len, MDL)) { + /* + * If we can't create a big enough buffer, + * we should remove any truncated output that we had. + */ + *((char *)ds->data+cur_strlen) = '\0'; + va_end(args); + return 0; + } + memcpy(tmp_buffer->data, ds->data, cur_strlen); + + /* Rerun the vsprintf. */ + va_start(args, fmt); + vsprintf((char *)tmp_buffer->data + cur_strlen, fmt, args); + va_end(args); + + /* + * Replace our old buffer with the new buffer. + */ + buffer_dereference(&ds->buffer, MDL); + buffer_reference(&ds->buffer, tmp_buffer, MDL); + buffer_dereference(&tmp_buffer, MDL); + ds->data = ds->buffer->data; + ds->len = new_len; + } + return 1; +} + +pair cons (car, cdr) + caddr_t car; + pair cdr; +{ + pair foo = (pair)dmalloc (sizeof *foo, MDL); + if (!foo) + log_fatal ("no memory for cons."); + foo -> car = car; + foo -> cdr = cdr; + return foo; +} + +int make_const_option_cache (oc, buffer, data, len, option, file, line) + struct option_cache **oc; + struct buffer **buffer; + u_int8_t *data; + unsigned len; + struct option *option; + const char *file; + int line; +{ + struct buffer *bp; + + if (buffer) { + bp = *buffer; + *buffer = 0; + } else { + bp = (struct buffer *)0; + if (!buffer_allocate (&bp, len, file, line)) { + log_error ("%s(%d): can't allocate buffer.", + file, line); + return 0; + } + } + + if (!option_cache_allocate (oc, file, line)) { + log_error ("%s(%d): can't allocate option cache.", file, line); + buffer_dereference (&bp, file, line); + return 0; + } + + (*oc) -> data.len = len; + (*oc) -> data.buffer = bp; + (*oc) -> data.data = &bp -> data [0]; + (*oc) -> data.terminated = 0; + if (data) + memcpy (&bp -> data [0], data, len); + option_reference(&((*oc)->option), option, MDL); + return 1; +} + +int make_host_lookup (expr, name) + struct expression **expr; + const char *name; +{ + if (!expression_allocate (expr, MDL)) { + log_error ("No memory for host lookup tree node."); + return 0; + } + (*expr) -> op = expr_host_lookup; + if (!enter_dns_host (&((*expr) -> data.host_lookup), name)) { + expression_dereference (expr, MDL); + return 0; + } + return 1; +} + +int enter_dns_host (dh, name) + struct dns_host_entry **dh; + const char *name; +{ + /* XXX This should really keep a hash table of hostnames + XXX and just add a new reference to a hostname that + XXX already exists, if possible, rather than creating + XXX a new structure. */ + if (!dns_host_entry_allocate (dh, name, MDL)) { + log_error ("Can't allocate space for new host."); + return 0; + } + return 1; +} + +int make_const_data (struct expression **expr, const unsigned char *data, + unsigned len, int terminated, int allocate, + const char *file, int line) +{ + struct expression *nt; + + if (!expression_allocate (expr, file, line)) { + log_error ("No memory for make_const_data tree node."); + return 0; + } + nt = *expr; + + if (len) { + if (allocate) { + if (!buffer_allocate (&nt -> data.const_data.buffer, + len + terminated, file, line)) { + log_error ("Can't allocate const_data buffer"); + expression_dereference (expr, file, line); + return 0; + } + nt -> data.const_data.data = + &nt -> data.const_data.buffer -> data [0]; + memcpy (nt -> data.const_data.buffer -> data, + data, len + terminated); + } else + nt -> data.const_data.data = data; + nt -> data.const_data.terminated = terminated; + } else + nt -> data.const_data.data = 0; + + nt -> op = expr_const_data; + nt -> data.const_data.len = len; + return 1; +} + +int make_const_int (expr, val) + struct expression **expr; + unsigned long val; +{ + if (!expression_allocate (expr, MDL)) { + log_error ("No memory for make_const_int tree node."); + return 0; + } + + (*expr) -> op = expr_const_int; + (*expr) -> data.const_int = val; + return 1; +} + +int make_concat (expr, left, right) + struct expression **expr; + struct expression *left, *right; +{ + /* If we're concatenating a null tree to a non-null tree, just + return the non-null tree; if both trees are null, return + a null tree. */ + if (!left) { + if (!right) + return 0; + expression_reference (expr, right, MDL); + return 1; + } + if (!right) { + expression_reference (expr, left, MDL); + return 1; + } + + /* Otherwise, allocate a new node to concatenate the two. */ + if (!expression_allocate (expr, MDL)) { + log_error ("No memory for concatenation expression node."); + return 0; + } + + (*expr) -> op = expr_concat; + expression_reference (&(*expr) -> data.concat [0], left, MDL); + expression_reference (&(*expr) -> data.concat [1], right, MDL); + return 1; +} + +int make_encapsulation (expr, name) + struct expression **expr; + struct data_string *name; +{ + /* Allocate a new node to store the encapsulation. */ + if (!expression_allocate (expr, MDL)) { + log_error ("No memory for encapsulation expression node."); + return 0; + } + + (*expr) -> op = expr_encapsulate; + data_string_copy (&(*expr) -> data.encapsulate, name, MDL); + return 1; +} + +int make_substring (new, expr, offset, length) + struct expression **new; + struct expression *expr; + struct expression *offset; + struct expression *length; +{ + /* Allocate an expression node to compute the substring. */ + if (!expression_allocate (new, MDL)) { + log_error ("no memory for substring expression."); + return 0; + } + (*new) -> op = expr_substring; + expression_reference (&(*new) -> data.substring.expr, expr, MDL); + expression_reference (&(*new) -> data.substring.offset, offset, MDL); + expression_reference (&(*new) -> data.substring.len, length, MDL); + return 1; +} + +int make_limit (new, expr, limit) + struct expression **new; + struct expression *expr; + int limit; +{ + /* Allocate a node to enforce a limit on evaluation. */ + if (!expression_allocate (new, MDL)) + log_error ("no memory for limit expression"); + (*new) -> op = expr_substring; + expression_reference (&(*new) -> data.substring.expr, expr, MDL); + + /* Offset is a constant 0. */ + if (!expression_allocate (&(*new) -> data.substring.offset, MDL)) { + log_error ("no memory for limit offset expression"); + expression_dereference (new, MDL); + return 0; + } + (*new) -> data.substring.offset -> op = expr_const_int; + (*new) -> data.substring.offset -> data.const_int = 0; + + /* Length is a constant: the specified limit. */ + if (!expression_allocate (&(*new) -> data.substring.len, MDL)) { + log_error ("no memory for limit length expression"); + expression_dereference (new, MDL); + return 0; + } + (*new) -> data.substring.len -> op = expr_const_int; + (*new) -> data.substring.len -> data.const_int = limit; + + return 1; +} + +int option_cache (struct option_cache **oc, struct data_string *dp, + struct expression *expr, struct option *option, + const char *file, int line) +{ + if (!option_cache_allocate (oc, file, line)) + return 0; + if (dp) + data_string_copy (&(*oc) -> data, dp, file, line); + if (expr) + expression_reference (&(*oc) -> expression, expr, file, line); + option_reference(&(*oc)->option, option, MDL); + return 1; +} + +int make_let (result, name) + struct executable_statement **result; + const char *name; +{ + if (!(executable_statement_allocate (result, MDL))) + return 0; + + (*result) -> op = let_statement; + (*result) -> data.let.name = dmalloc (strlen (name) + 1, MDL); + if (!(*result) -> data.let.name) { + executable_statement_dereference (result, MDL); + return 0; + } + strcpy ((*result) -> data.let.name, name); + return 1; +} + +static int do_host_lookup (result, dns) + struct data_string *result; + struct dns_host_entry *dns; +{ + struct hostent *h; + unsigned i, count; + unsigned new_len; + +#ifdef DEBUG_EVAL + log_debug ("time: now = %d dns = %d diff = %d", + cur_time, dns -> timeout, cur_time - dns -> timeout); +#endif + + /* If the record hasn't timed out, just copy the data and return. */ + if (cur_time <= dns -> timeout) { +#ifdef DEBUG_EVAL + log_debug ("easy copy: %d %s", + dns -> data.len, + (dns -> data.len > 4 + ? inet_ntoa (*(struct in_addr *)(dns -> data.data)) + : 0)); +#endif + data_string_copy (result, &dns -> data, MDL); + return 1; + } +#ifdef DEBUG_EVAL + log_debug ("Looking up %s", dns -> hostname); +#endif + + /* Otherwise, look it up... */ + h = gethostbyname (dns -> hostname); + if (!h) { +#ifndef NO_H_ERRNO + switch (h_errno) { + case HOST_NOT_FOUND: +#endif + log_error ("%s: host unknown.", dns -> hostname); +#ifndef NO_H_ERRNO + break; + case TRY_AGAIN: + log_error ("%s: temporary name server failure", + dns -> hostname); + break; + case NO_RECOVERY: + log_error ("%s: name server failed", dns -> hostname); + break; + case NO_DATA: + log_error ("%s: no A record associated with address", + dns -> hostname); + } +#endif /* !NO_H_ERRNO */ + + /* Okay to try again after a minute. */ + dns -> timeout = cur_time + 60; + data_string_forget (&dns -> data, MDL); + return 0; + } + +#ifdef DEBUG_EVAL + log_debug ("Lookup succeeded; first address is %s", + inet_ntoa (h -> h_addr_list [0])); +#endif + + /* Count the number of addresses we got... */ + for (count = 0; h -> h_addr_list [count]; count++) + ; + + /* Dereference the old data, if any. */ + data_string_forget (&dns -> data, MDL); + + /* Do we need to allocate more memory? */ + new_len = count * h -> h_length; + if (!buffer_allocate (&dns -> data.buffer, new_len, MDL)) + { + log_error ("No memory for %s.", dns -> hostname); + return 0; + } + + dns -> data.data = &dns -> data.buffer -> data [0]; + dns -> data.len = new_len; + dns -> data.terminated = 0; + + /* Addresses are conveniently stored one to the buffer, so we + have to copy them out one at a time... :'( */ + for (i = 0; i < count; i++) { + memcpy (&dns -> data.buffer -> data [h -> h_length * i], + h -> h_addr_list [i], (unsigned)(h -> h_length)); + } +#ifdef DEBUG_EVAL + log_debug ("dns -> data: %x h -> h_addr_list [0]: %x", + *(int *)(dns -> buffer), h -> h_addr_list [0]); +#endif + + /* XXX Set the timeout for an hour from now. + XXX This should really use the time on the DNS reply. */ + dns -> timeout = cur_time + 3600; + +#ifdef DEBUG_EVAL + log_debug ("hard copy: %d %s", dns -> data.len, + (dns -> data.len > 4 + ? inet_ntoa (*(struct in_addr *)(dns -> data.data)) : 0)); +#endif + data_string_copy (result, &dns -> data, MDL); + return 1; +} + +int evaluate_expression (result, packet, lease, client_state, + in_options, cfg_options, scope, expr, file, line) + struct binding_value **result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct expression *expr; + const char *file; + int line; +{ + struct binding_value *bv; + int status; + struct binding *binding; + + bv = (struct binding_value *)0; + + if (expr -> op == expr_variable_reference) { + if (!scope || !*scope) + return 0; + + binding = find_binding (*scope, expr -> data.variable); + + if (binding && binding -> value) { + if (result) + binding_value_reference (result, + binding -> value, + file, line); + return 1; + } else + return 0; + } else if (expr -> op == expr_funcall) { + struct string_list *s; + struct expression *arg; + struct binding_scope *ns; + struct binding *nb; + + if (!scope || !*scope) { + log_error ("%s: no such function.", + expr -> data.funcall.name); + return 0; + } + + binding = find_binding (*scope, expr -> data.funcall.name); + + if (!binding || !binding -> value) { + log_error ("%s: no such function.", + expr -> data.funcall.name); + return 0; + } + if (binding -> value -> type != binding_function) { + log_error ("%s: not a function.", + expr -> data.funcall.name); + return 0; + } + + /* Create a new binding scope in which to define + the arguments to the function. */ + ns = (struct binding_scope *)0; + if (!binding_scope_allocate (&ns, MDL)) { + log_error ("%s: can't allocate argument scope.", + expr -> data.funcall.name); + return 0; + } + + arg = expr -> data.funcall.arglist; + s = binding -> value -> value.fundef -> args; + while (arg && s) { + nb = dmalloc (sizeof *nb, MDL); + if (!nb) { + blb: + binding_scope_dereference (&ns, MDL); + return 0; + } else { + memset (nb, 0, sizeof *nb); + nb -> name = dmalloc (strlen (s -> string) + 1, + MDL); + if (nb -> name) + strcpy (nb -> name, s -> string); + else { + dfree (nb, MDL); + goto blb; + } + } + evaluate_expression (&nb -> value, packet, lease, + client_state, + in_options, cfg_options, scope, + arg -> data.arg.val, file, line); + nb -> next = ns -> bindings; + ns -> bindings = nb; + arg = arg -> data.arg.next; + s = s -> next; + } + if (arg) { + log_error ("%s: too many arguments.", + expr -> data.funcall.name); + binding_scope_dereference (&ns, MDL); + return 0; + } + if (s) { + log_error ("%s: too few arguments.", + expr -> data.funcall.name); + binding_scope_dereference (&ns, MDL); + return 0; + } + + if (scope && *scope) + binding_scope_reference (&ns -> outer, *scope, MDL); + + status = (execute_statements + (&bv, packet, + lease, client_state, in_options, cfg_options, &ns, + binding -> value -> value.fundef -> statements)); + binding_scope_dereference (&ns, MDL); + + if (!bv) + return 1; + } else if (is_boolean_expression (expr)) { + if (!binding_value_allocate (&bv, MDL)) + return 0; + bv -> type = binding_boolean; + status = (evaluate_boolean_expression + (&bv -> value.boolean, packet, lease, client_state, + in_options, cfg_options, scope, expr)); + } else if (is_numeric_expression (expr)) { + if (!binding_value_allocate (&bv, MDL)) + return 0; + bv -> type = binding_numeric; + status = (evaluate_numeric_expression + (&bv -> value.intval, packet, lease, client_state, + in_options, cfg_options, scope, expr)); + } else if (is_data_expression (expr)) { + if (!binding_value_allocate (&bv, MDL)) + return 0; + bv -> type = binding_data; + status = (evaluate_data_expression + (&bv -> value.data, packet, lease, client_state, + in_options, cfg_options, scope, expr, MDL)); +#if defined (NSUPDATE_OLD) + } else if (is_dns_expression (expr)) { + if (!binding_value_allocate (&bv, MDL)) + return 0; + bv -> type = binding_dns; + status = (evaluate_dns_expression + (&bv -> value.dns, packet, lease, client_state, + in_options, cfg_options, scope, expr)); +#endif + } else { + log_error ("%s: invalid expression type: %d", + "evaluate_expression", expr -> op); + return 0; + } + if (result && status) + binding_value_reference (result, bv, file, line); + binding_value_dereference (&bv, MDL); + + return status; +} + +int binding_value_dereference (struct binding_value **v, + const char *file, int line) +{ + struct binding_value *bv = *v; + + *v = (struct binding_value *)0; + + /* Decrement the reference count. If it's nonzero, we're + done. */ + --(bv -> refcnt); + rc_register (file, line, v, bv, bv -> refcnt, 1, RC_MISC); + if (bv -> refcnt > 0) + return 1; + if (bv -> refcnt < 0) { + log_error ("%s(%d): negative refcnt!", file, line); +#if defined (DEBUG_RC_HISTORY) + dump_rc_history (bv); +#endif +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + switch (bv -> type) { + case binding_boolean: + case binding_numeric: + break; + case binding_data: + if (bv -> value.data.buffer) + data_string_forget (&bv -> value.data, file, line); + break; + case binding_dns: +#if defined (NSUPDATE_OLD) + if (bv -> value.dns) { + if (bv -> value.dns -> r_data) { + dfree (bv -> value.dns -> r_data_ephem, MDL); + bv -> value.dns -> r_data = (unsigned char *)0; + bv -> value.dns -> r_data_ephem = + (unsigned char *)0; + } + minires_freeupdrec (bv -> value.dns); + } + break; +#endif + default: + log_error ("%s(%d): invalid binding type: %d", + file, line, bv -> type); + return 0; + } + free_binding_value(bv, file, line); + return 1; +} + +#if defined (NSUPDATE_OLD) +int evaluate_dns_expression (result, packet, lease, client_state, in_options, + cfg_options, scope, expr) + ns_updrec **result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct expression *expr; +{ + unsigned long ttl = 0; + char *tname; + struct data_string name, data; + int r0, r1, r2; + + if (!result || *result) { + log_error ("evaluate_dns_expression called with non-null %s", + "result pointer"); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + switch (expr -> op) { +#if defined (NSUPDATE) + case expr_ns_add: + r0 = evaluate_numeric_expression (&ttl, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.ns_add.ttl); + goto nsfinish; + + case expr_ns_exists: + ttl = 1; + + case expr_ns_delete: + case expr_ns_not_exists: + r0 = 1; + nsfinish: + memset (&name, 0, sizeof name); + r1 = evaluate_data_expression (&name, packet, lease, + client_state, + in_options, cfg_options, scope, + expr -> data.ns_add.rrname, + MDL); + if (r1) { + /* The result of the evaluation may or may not + be NUL-terminated, but we need it + terminated for sure, so we have to allocate + a buffer and terminate it. */ + tname = dmalloc (name.len + 1, MDL); + if (!tname) { + r2 = 0; + r1 = 0; + data_string_forget (&name, MDL); + } else { + memcpy (tname, name.data, name.len); + tname [name.len] = 0; + memset (&data, 0, sizeof data); + r2 = evaluate_data_expression + (&data, packet, lease, client_state, + in_options, cfg_options, scope, + expr -> data.ns_add.rrdata, MDL); + } + } else { + r2 = 0; + tname = NULL; + } + if (r0 && r1 && (r2 || expr -> op != expr_ns_add)) { + *result = minires_mkupdrec (((expr -> op == expr_ns_add || + expr -> op == expr_ns_delete) + ? S_UPDATE : S_PREREQ), + tname, + expr -> data.ns_add.rrclass, + expr -> data.ns_add.rrtype, + ttl); + if (!*result) { + ngood: + if (r2) { + data_string_forget (&data, MDL); + r2 = 0; + } + } else { + if (data.len) { + /* As a special case, if we get exactly + four bytes of data, it's an IP address + represented as a 32-bit quantity, which + is actually what we *should* be getting + here. Because res_mkupdrec is currently + broken and expects a dotted quad, convert + it. This should be fixed when the new + resolver is merged. */ + if (data.len == 4) { + (*result) -> r_data_ephem = + dmalloc (16, MDL); + if (!(*result) -> r_data_ephem) + goto dpngood; + (*result) -> r_data = + (*result) -> r_data_ephem; + /*%Audit% 16 bytes max. %2004.06.17,Safe%*/ + sprintf ((char *)(*result) -> r_data_ephem, + "%u.%u.%u.%u", + data.data [0] & 0xff, + data.data [1] & 0xff, + data.data [2] & 0xff, + data.data [3] & 0xff); + (*result) -> r_size = + strlen ((const char *) + (*result) -> r_data); + } else { + (*result) -> r_size = data.len; + (*result) -> r_data_ephem = + dmalloc (data.len, MDL); + if (!(*result) -> r_data_ephem) { + dpngood: /* double plus ungood. */ + minires_freeupdrec (*result); + *result = 0; + goto ngood; + } + (*result) -> r_data = + (*result) -> r_data_ephem; + memcpy ((*result) -> r_data_ephem, + data.data, data.len); + } + } else { + (*result) -> r_data = 0; + (*result) -> r_size = 0; + } + switch (expr -> op) { + case expr_ns_add: + (*result) -> r_opcode = ADD; + break; + case expr_ns_delete: + (*result) -> r_opcode = DELETE; + break; + case expr_ns_exists: + (*result) -> r_opcode = YXRRSET; + break; + case expr_ns_not_exists: + (*result) -> r_opcode = NXRRSET; + break; + + /* Can't happen, but satisfy gcc. */ + default: + break; + } + } + } + if (r1) { + data_string_forget (&name, MDL); + dfree (tname, MDL); + } + if (r2) + data_string_forget (&data, MDL); + /* One flaw in the thinking here: an IP address and an + ASCII string both look like data expressions, but + for A records, we want an ASCII string, not a + binary IP address. Do I need to turn binary IP + addresses into a separate type? */ + return (r0 && r1 && + (r2 || expr -> op != expr_ns_add) && *result); + +#else + case expr_ns_add: + case expr_ns_delete: + case expr_ns_exists: + case expr_ns_not_exists: + return 0; +#endif + case expr_funcall: + log_error ("%s: dns values for functions not supported.", + expr -> data.funcall.name); + break; + + case expr_variable_reference: + log_error ("%s: dns values for variables not supported.", + expr -> data.variable); + break; + + case expr_check: + case expr_equal: + case expr_not_equal: + case expr_regex_match: + case expr_iregex_match: + case expr_and: + case expr_or: + case expr_not: + case expr_match: + case expr_static: + case expr_known: + case expr_exists: + case expr_variable_exists: + log_error ("Boolean opcode in evaluate_dns_expression: %d", + expr -> op); + return 0; + + case expr_none: + case expr_substring: + case expr_suffix: + case expr_lcase: + case expr_ucase: + case expr_option: + case expr_hardware: + case expr_const_data: + case expr_packet: + case expr_concat: + case expr_encapsulate: + case expr_host_lookup: + case expr_encode_int8: + case expr_encode_int16: + case expr_encode_int32: + case expr_binary_to_ascii: + case expr_reverse: + case expr_filename: + case expr_sname: + case expr_pick_first_value: + case expr_host_decl_name: + case expr_config_option: + case expr_leased_address: + case expr_null: + case expr_gethostname: + log_error ("Data opcode in evaluate_dns_expression: %d", + expr -> op); + return 0; + + case expr_extract_int8: + case expr_extract_int16: + case expr_extract_int32: + case expr_const_int: + case expr_lease_time: + case expr_dns_transaction: + case expr_add: + case expr_subtract: + case expr_multiply: + case expr_divide: + case expr_remainder: + case expr_binary_and: + case expr_binary_or: + case expr_binary_xor: + case expr_client_state: + log_error ("Numeric opcode in evaluate_dns_expression: %d", + expr -> op); + return 0; + + case expr_function: + log_error ("Function opcode in evaluate_dns_expression: %d", + expr -> op); + return 0; + + case expr_arg: + break; + } + + log_error ("Bogus opcode in evaluate_dns_expression: %d", + expr -> op); + return 0; +} +#endif /* defined (NSUPDATE_OLD) */ + +int evaluate_boolean_expression (result, packet, lease, client_state, + in_options, cfg_options, scope, expr) + int *result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct expression *expr; +{ + struct data_string left, right; + int bleft, bright; + int sleft, sright; + struct binding *binding; + struct binding_value *bv, *obv; +#ifdef HAVE_REGEX_H + int regflags = REG_EXTENDED | REG_NOSUB; + regex_t re; +#endif + + switch (expr -> op) { + case expr_check: + *result = check_collection (packet, lease, + expr -> data.check); +#if defined (DEBUG_EXPRESSIONS) + log_debug ("bool: check (%s) returns %s", + expr -> data.check -> name, + *result ? "true" : "false"); +#endif + return 1; + + case expr_equal: + case expr_not_equal: + bv = obv = (struct binding_value *)0; + sleft = evaluate_expression (&bv, packet, lease, client_state, + in_options, cfg_options, scope, + expr -> data.equal [0], MDL); + sright = evaluate_expression (&obv, packet, lease, + client_state, in_options, + cfg_options, scope, + expr -> data.equal [1], MDL); + if (sleft && sright) { + if (bv -> type != obv -> type) + *result = expr -> op == expr_not_equal; + else { + switch (obv -> type) { + case binding_boolean: + if (bv -> value.boolean == obv -> value.boolean) + *result = expr -> op == expr_equal; + else + *result = expr -> op == expr_not_equal; + break; + + case binding_data: + if ((bv -> value.data.len == + obv -> value.data.len) && + !memcmp (bv -> value.data.data, + obv -> value.data.data, + obv -> value.data.len)) + *result = expr -> op == expr_equal; + else + *result = expr -> op == expr_not_equal; + break; + + case binding_numeric: + if (bv -> value.intval == obv -> value.intval) + *result = expr -> op == expr_equal; + else + *result = expr -> op == expr_not_equal; + break; +#if defined (NSUPDATE_OLD) + case binding_dns: +#if defined (NSUPDATE) + /* XXX This should be a comparison for equal + XXX values, not for identity. */ + if (bv -> value.dns == obv -> value.dns) + *result = expr -> op == expr_equal; + else + *result = expr -> op == expr_not_equal; +#else + *result = expr -> op == expr_not_equal; +#endif + break; +#endif /* NSUPDATE_OLD */ + case binding_function: + if (bv -> value.fundef == obv -> value.fundef) + *result = expr -> op == expr_equal; + else + *result = expr -> op == expr_not_equal; + break; + default: + *result = expr -> op == expr_not_equal; + break; + } + } + } else if (!sleft && !sright) + *result = expr -> op == expr_equal; + else + *result = expr -> op == expr_not_equal; + +#if defined (DEBUG_EXPRESSIONS) + log_debug ("bool: %sequal = %s", + expr -> op == expr_not_equal ? "not" : "", + (*result ? "true" : "false")); +#endif + if (sleft) + binding_value_dereference (&bv, MDL); + if (sright) + binding_value_dereference (&obv, MDL); + return 1; + + case expr_iregex_match: +#ifdef HAVE_REGEX_H + regflags |= REG_ICASE; +#endif + /* FALL THROUGH */ + case expr_regex_match: +#ifdef HAVE_REGEX_H + memset(&left, 0, sizeof left); + bleft = evaluate_data_expression(&left, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr->data.equal[0], MDL); + memset(&right, 0, sizeof right); + bright = evaluate_data_expression(&right, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr->data.equal[1], MDL); + + *result = 0; + memset(&re, 0, sizeof(re)); + if (bleft && bright && + (left.data != NULL) && (right.data != NULL) && + (regcomp(&re, (char *)right.data, regflags) == 0) && + (regexec(&re, (char *)left.data, (size_t)0, NULL, 0) == 0)) + *result = 1; + +#if defined (DEBUG_EXPRESSIONS) + log_debug("bool: %s ~= %s yields %s", + bleft ? print_hex_1(left.len, left.data, 20) + : "NULL", + bright ? print_hex_2 (right.len, right.data, 20) + : "NULL", + *result ? "true" : "false"); +#endif + + if (bleft) + data_string_forget(&left, MDL); + if (bright) + data_string_forget(&right, MDL); + + regfree(&re); + + /* + * If we have bleft and bright then we have a good + * syntax, otherwise not. + * + * XXX: we don't warn on invalid regular expression + * syntax, should we? + */ + return bleft && bright; +#else + /* It shouldn't be possible to configure a regex operator + * when there's no support. + */ + log_fatal("Impossible condition at %s:%d.", MDL); + break; +#endif + + case expr_and: + sleft = evaluate_boolean_expression (&bleft, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [0]); + if (sleft && bleft) + sright = evaluate_boolean_expression + (&bright, packet, lease, client_state, + in_options, cfg_options, + scope, expr -> data.and [1]); + else + sright = bright = 0; + +#if defined (DEBUG_EXPRESSIONS) + log_debug ("bool: and (%s, %s) = %s", + sleft ? (bleft ? "true" : "false") : "NULL", + sright ? (bright ? "true" : "false") : "NULL", + ((sleft && sright) + ? (bleft && bright ? "true" : "false") : "NULL")); +#endif + if (sleft && sright) { + *result = bleft && bright; + return 1; + } + return 0; + + case expr_or: + bleft = bright = 0; + sleft = evaluate_boolean_expression (&bleft, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.or [0]); + if (!sleft || !bleft) + sright = evaluate_boolean_expression + (&bright, packet, lease, client_state, + in_options, cfg_options, + scope, expr -> data.or [1]); + else + sright = 0; +#if defined (DEBUG_EXPRESSIONS) + log_debug ("bool: or (%s, %s) = %s", + sleft ? (bleft ? "true" : "false") : "NULL", + sright ? (bright ? "true" : "false") : "NULL", + ((sleft || sright) + ? (bleft || bright ? "true" : "false") : "NULL")); +#endif + if (sleft || sright) { + *result = bleft || bright; + return 1; + } + return 0; + + case expr_not: + sleft = evaluate_boolean_expression(&bleft, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr->data.not); +#if defined (DEBUG_EXPRESSIONS) + log_debug("bool: not (%s) = %s", + sleft ? (bleft ? "true" : "false") : "NULL", + sleft ? (!bleft ? "true" : "false") : "NULL"); +#endif + if (sleft) { + *result = !bleft; + return 1; + } + return 0; + + case expr_exists: + memset (&left, 0, sizeof left); + if (!in_options || + !get_option (&left, expr -> data.exists -> universe, + packet, lease, client_state, + in_options, cfg_options, in_options, + scope, expr -> data.exists -> code, MDL)) + *result = 0; + else { + *result = 1; + data_string_forget (&left, MDL); + } +#if defined (DEBUG_EXPRESSIONS) + log_debug ("bool: exists %s.%s = %s", + expr -> data.option -> universe -> name, + expr -> data.option -> name, + *result ? "true" : "false"); +#endif + return 1; + + case expr_known: + if (!packet) { +#if defined (DEBUG_EXPRESSIONS) + log_debug ("bool: known = NULL"); +#endif + return 0; + } +#if defined (DEBUG_EXPRESSIONS) + log_debug ("bool: known = %s", + packet -> known ? "true" : "false"); +#endif + *result = packet -> known; + return 1; + + case expr_static: + if (!lease || !(lease -> flags & STATIC_LEASE)) { +#if defined (DEBUG_EXPRESSIONS) + log_debug ("bool: static = false (%s %s %s %d)", + lease ? "y" : "n", + (lease && (lease -> flags & STATIC_LEASE) + ? "y" : "n"), + piaddr (lease -> ip_addr), + lease ? lease -> flags : 0); +#endif + *result = 0; + return 1; + } +#if defined (DEBUG_EXPRESSIONS) + log_debug ("bool: static = true"); +#endif + *result = 1; + return 1; + + case expr_variable_exists: + if (scope && *scope) { + binding = find_binding (*scope, expr -> data.variable); + + if (binding) { + if (binding -> value) + *result = 1; + else + *result = 0; + } else + *result = 0; + } else + *result = 0; +#if defined (DEBUG_EXPRESSIONS) + log_debug ("boolean: %s? = %s", expr -> data.variable, + *result ? "true" : "false"); +#endif + return 1; + + case expr_variable_reference: + if (scope && *scope) { + binding = find_binding (*scope, expr -> data.variable); + + if (binding && binding -> value) { + if (binding -> value -> type == + binding_boolean) { + *result = binding -> value -> value.boolean; + sleft = 1; + } else { + log_error ("binding type %d in %s.", + binding -> value -> type, + "evaluate_boolean_expression"); + sleft = 0; + } + } else + sleft = 0; + } else + sleft = 0; +#if defined (DEBUG_EXPRESSIONS) + log_debug ("boolean: %s = %s", expr -> data.variable, + sleft ? (*result ? "true" : "false") : "NULL"); +#endif + return sleft; + + case expr_funcall: + bv = (struct binding_value *)0; + sleft = evaluate_expression (&bv, packet, lease, client_state, + in_options, cfg_options, + scope, expr, MDL); + if (sleft) { + if (bv -> type != binding_boolean) + log_error ("%s() returned type %d in %s.", + expr -> data.funcall.name, + bv -> type, + "evaluate_boolean_expression"); + else + *result = bv -> value.boolean; + binding_value_dereference (&bv, MDL); + } +#if defined (DEBUG_EXPRESSIONS) + log_debug ("boolean: %s() = %s", expr -> data.funcall.name, + sleft ? (*result ? "true" : "false") : "NULL"); +#endif + break; + + case expr_none: + case expr_match: + case expr_substring: + case expr_suffix: + case expr_lcase: + case expr_ucase: + case expr_option: + case expr_hardware: + case expr_const_data: + case expr_packet: + case expr_concat: + case expr_encapsulate: + case expr_host_lookup: + case expr_encode_int8: + case expr_encode_int16: + case expr_encode_int32: + case expr_binary_to_ascii: + case expr_reverse: + case expr_pick_first_value: + case expr_host_decl_name: + case expr_config_option: + case expr_leased_address: + case expr_null: + case expr_filename: + case expr_sname: + case expr_gethostname: + case expr_concat_dclist: + log_error ("Data opcode in evaluate_boolean_expression: %d", + expr -> op); + return 0; + + case expr_extract_int8: + case expr_extract_int16: + case expr_extract_int32: + case expr_const_int: + case expr_lease_time: + case expr_dns_transaction: + case expr_add: + case expr_subtract: + case expr_multiply: + case expr_divide: + case expr_remainder: + case expr_binary_and: + case expr_binary_or: + case expr_binary_xor: + case expr_client_state: + log_error ("Numeric opcode in evaluate_boolean_expression: %d", + expr -> op); + return 0; + + case expr_ns_add: + case expr_ns_delete: + case expr_ns_exists: + case expr_ns_not_exists: + log_error ("dns opcode in evaluate_boolean_expression: %d", + expr -> op); + return 0; + + case expr_function: + log_error ("function definition in evaluate_boolean_expr"); + return 0; + + case expr_arg: + break; + } + + log_error ("Bogus opcode in evaluate_boolean_expression: %d", + expr -> op); + return 0; +} + +int evaluate_data_expression (result, packet, lease, client_state, + in_options, cfg_options, scope, expr, file, line) + struct data_string *result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct expression *expr; + const char *file; + int line; +{ + struct data_string data, other; + unsigned long offset, len, i; + int s0, s1, s2, s3; + int status; + struct binding *binding; + unsigned char *s; + struct binding_value *bv; + + switch (expr -> op) { + /* Extract N bytes starting at byte M of a data string. */ + case expr_substring: + memset (&data, 0, sizeof data); + s0 = evaluate_data_expression (&data, packet, lease, + client_state, + in_options, cfg_options, scope, + expr -> data.substring.expr, + MDL); + + /* Evaluate the offset and length. */ + s1 = evaluate_numeric_expression + (&offset, packet, lease, client_state, in_options, + cfg_options, scope, expr -> data.substring.offset); + s2 = evaluate_numeric_expression (&len, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.substring.len); + + if (s0 && s1 && s2) { + /* If the offset is after end of the string, + return an empty string. Otherwise, do the + adjustments and return what's left. */ + if (data.len > offset) { + data_string_copy (result, &data, file, line); + result -> len -= offset; + if (result -> len > len) { + result -> len = len; + result -> terminated = 0; + } + result -> data += offset; + } + s3 = 1; + } else + s3 = 0; + +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: substring (%s, %s, %s) = %s", + s0 ? print_hex_1 (data.len, data.data, 30) : "NULL", + s1 ? print_dec_1 (offset) : "NULL", + s2 ? print_dec_2 (len) : "NULL", + (s3 ? print_hex_2 (result -> len, result -> data, 30) + : "NULL")); +#endif + if (s0) + data_string_forget (&data, MDL); + if (s3) + return 1; + return 0; + + /* Extract the last N bytes of a data string. */ + case expr_suffix: + memset (&data, 0, sizeof data); + s0 = evaluate_data_expression (&data, packet, lease, + client_state, + in_options, cfg_options, scope, + expr -> data.suffix.expr, MDL); + /* Evaluate the length. */ + s1 = evaluate_numeric_expression (&len, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.suffix.len); + if (s0 && s1) { + data_string_copy (result, &data, file, line); + + /* If we are returning the last N bytes of a + string whose length is <= N, just return + the string - otherwise, compute a new + starting address and decrease the + length. */ + if (data.len > len) { + result -> data += data.len - len; + result -> len = len; + } + data_string_forget (&data, MDL); + } + +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: suffix (%s, %s) = %s", + s0 ? print_hex_1 (data.len, data.data, 30) : "NULL", + s1 ? print_dec_1 (len) : "NULL", + ((s0 && s1) + ? print_hex_2 (result -> len, result -> data, 30) + : "NULL")); +#endif + return s0 && s1; + + /* Convert string to lowercase. */ + case expr_lcase: + memset(&data, 0, sizeof data); + s0 = evaluate_data_expression(&data, packet, lease, + client_state, + in_options, cfg_options, scope, + expr->data.lcase, MDL); + s1 = 0; + if (s0) { + result->len = data.len; + if (buffer_allocate(&result->buffer, + result->len + data.terminated, + MDL)) { + result->data = &result->buffer->data[0]; + memcpy(result->buffer->data, data.data, + data.len + data.terminated); + result->terminated = data.terminated; + s = (unsigned char *)result->data; + for (i = 0; i < result->len; i++, s++) + *s = tolower(*s); + s1 = 1; + } else { + log_error("data: lcase: no buffer memory."); + } + } + +#if defined (DEBUG_EXPRESSIONS) + log_debug("data: lcase (%s) = %s", + s0 ? print_hex_1(data.len, data.data, 30) : "NULL", + s1 ? print_hex_2(result->len, result->data, 30) + : "NULL"); +#endif + if (s0) + data_string_forget(&data, MDL); + return s1; + + /* Convert string to uppercase. */ + case expr_ucase: + memset(&data, 0, sizeof data); + s0 = evaluate_data_expression(&data, packet, lease, + client_state, + in_options, cfg_options, scope, + expr->data.lcase, MDL); + s1 = 0; + if (s0) { + result->len = data.len; + if (buffer_allocate(&result->buffer, + result->len + data.terminated, + file, line)) { + result->data = &result->buffer->data[0]; + memcpy(result->buffer->data, data.data, + data.len + data.terminated); + result->terminated = data.terminated; + s = (unsigned char *)result->data; + for (i = 0; i < result->len; i++, s++) + *s = toupper(*s); + s1 = 1; + } else { + log_error("data: lcase: no buffer memory."); + } + } + +#if defined (DEBUG_EXPRESSIONS) + log_debug("data: ucase (%s) = %s", + s0 ? print_hex_1(data.len, data.data, 30) : "NULL", + s1 ? print_hex_2(result->len, result->data, 30) + : "NULL"); +#endif + if (s0) + data_string_forget(&data, MDL); + return s1; + + /* Extract an option. */ + case expr_option: + if (in_options) + s0 = get_option (result, + expr -> data.option -> universe, + packet, lease, client_state, + in_options, cfg_options, in_options, + scope, expr -> data.option -> code, + file, line); + else + s0 = 0; + +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: option %s.%s = %s", + expr -> data.option -> universe -> name, + expr -> data.option -> name, + s0 ? print_hex_1 (result -> len, result -> data, 60) + : "NULL"); +#endif + return s0; + + case expr_config_option: + if (cfg_options) + s0 = get_option (result, + expr -> data.option -> universe, + packet, lease, client_state, + in_options, cfg_options, cfg_options, + scope, expr -> data.option -> code, + file, line); + else + s0 = 0; + +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: config-option %s.%s = %s", + expr -> data.option -> universe -> name, + expr -> data.option -> name, + s0 ? print_hex_1 (result -> len, result -> data, 60) + : "NULL"); +#endif + return s0; + + /* Combine the hardware type and address. */ + case expr_hardware: + /* On the client, hardware is our hardware. */ + if (client_state) { + memset (result, 0, sizeof *result); + result -> data = + client_state -> interface -> hw_address.hbuf; + result -> len = + client_state -> interface -> hw_address.hlen; +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: hardware = %s", + print_hex_1 (result -> len, + result -> data, 60)); +#endif + return 1; + } + + /* The server cares about the client's hardware address, + so only in the case where we are examining a packet can + we return anything. */ + if (!packet || !packet -> raw) { + log_error ("data: hardware: raw packet not available"); + return 0; + } + if (packet -> raw -> hlen > sizeof packet -> raw -> chaddr) { + log_error ("data: hardware: invalid hlen (%d)\n", + packet -> raw -> hlen); + return 0; + } + result -> len = packet -> raw -> hlen + 1; + if (buffer_allocate (&result -> buffer, result -> len, + file, line)) { + result -> data = &result -> buffer -> data [0]; + result -> buffer -> data [0] = packet -> raw -> htype; + memcpy (&result -> buffer -> data [1], + packet -> raw -> chaddr, + packet -> raw -> hlen); + result -> terminated = 0; + } else { + log_error ("data: hardware: no memory for buffer."); + return 0; + } +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: hardware = %s", + print_hex_1 (result -> len, result -> data, 60)); +#endif + return 1; + + /* Extract part of the raw packet. */ + case expr_packet: + if (!packet || !packet -> raw) { + log_error ("data: packet: raw packet not available"); + return 0; + } + + s0 = evaluate_numeric_expression (&offset, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.packet.offset); + s1 = evaluate_numeric_expression (&len, + packet, lease, client_state, + in_options, cfg_options, + scope, + expr -> data.packet.len); + if (s0 && s1 && offset < packet -> packet_length) { + if (offset + len > packet -> packet_length) + result -> len = + packet -> packet_length - offset; + else + result -> len = len; + if (buffer_allocate (&result -> buffer, + result -> len, file, line)) { + result -> data = &result -> buffer -> data [0]; + memcpy (result -> buffer -> data, + (((unsigned char *)(packet -> raw)) + + offset), result -> len); + result -> terminated = 0; + } else { + log_error ("data: packet: no buffer memory."); + return 0; + } + s2 = 1; + } else + s2 = 0; +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: packet (%ld, %ld) = %s", + offset, len, + s2 ? print_hex_1 (result -> len, + result -> data, 60) : NULL); +#endif + return s2; + + /* The encapsulation of all defined options in an + option space... */ + case expr_encapsulate: + if (cfg_options) + s0 = option_space_encapsulate + (result, packet, lease, client_state, + in_options, cfg_options, scope, + &expr -> data.encapsulate); + else + s0 = 0; + +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: encapsulate (%s) = %s", + expr -> data.encapsulate.data, + s0 ? print_hex_1 (result -> len, + result -> data, 60) : "NULL"); +#endif + return s0; + + /* Some constant data... */ + case expr_const_data: +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: const = %s", + print_hex_1 (expr -> data.const_data.len, + expr -> data.const_data.data, 60)); +#endif + data_string_copy (result, + &expr -> data.const_data, file, line); + return 1; + + /* Hostname lookup... */ + case expr_host_lookup: + s0 = do_host_lookup (result, expr -> data.host_lookup); +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: DNS lookup (%s) = %s", + expr -> data.host_lookup -> hostname, + (s0 + ? print_dotted_quads (result -> len, result -> data) + : "NULL")); +#endif + return s0; + + /* Concatenation... */ + case expr_concat: + memset (&data, 0, sizeof data); + s0 = evaluate_data_expression (&data, packet, lease, + client_state, + in_options, cfg_options, scope, + expr -> data.concat [0], MDL); + memset (&other, 0, sizeof other); + s1 = evaluate_data_expression (&other, packet, lease, + client_state, + in_options, cfg_options, scope, + expr -> data.concat [1], MDL); + + if (s0 && s1) { + result -> len = data.len + other.len; + if (!buffer_allocate (&result -> buffer, + (result -> len + other.terminated), + file, line)) { + log_error ("data: concat: no memory"); + result -> len = 0; + data_string_forget (&data, MDL); + data_string_forget (&other, MDL); + return 0; + } + result -> data = &result -> buffer -> data [0]; + memcpy (result -> buffer -> data, data.data, data.len); + memcpy (&result -> buffer -> data [data.len], + other.data, other.len + other.terminated); + } + + if (s0) + data_string_forget (&data, MDL); + if (s1) + data_string_forget (&other, MDL); +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: concat (%s, %s) = %s", + s0 ? print_hex_1 (data.len, data.data, 20) : "NULL", + s1 ? print_hex_2 (other.len, other.data, 20) : "NULL", + ((s0 && s1) + ? print_hex_3 (result -> len, result -> data, 30) + : "NULL")); +#endif + return s0 && s1; + + case expr_encode_int8: + s0 = evaluate_numeric_expression (&len, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.encode_int); + if (s0) { + result -> len = 1; + if (!buffer_allocate (&result -> buffer, + 1, file, line)) { + log_error ("data: encode_int8: no memory"); + result -> len = 0; + s0 = 0; + } else { + result -> data = &result -> buffer -> data [0]; + result -> buffer -> data [0] = len; + } + } else + result -> len = 0; + +#if defined (DEBUG_EXPRESSIONS) + if (!s0) + log_debug ("data: encode_int8 (NULL) = NULL"); + else + log_debug ("data: encode_int8 (%ld) = %s", len, + print_hex_2 (result -> len, + result -> data, 20)); +#endif + return s0; + + + case expr_encode_int16: + s0 = evaluate_numeric_expression (&len, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.encode_int); + if (s0) { + result -> len = 2; + if (!buffer_allocate (&result -> buffer, 2, + file, line)) { + log_error ("data: encode_int16: no memory"); + result -> len = 0; + s0 = 0; + } else { + result -> data = &result -> buffer -> data [0]; + putUShort (result -> buffer -> data, len); + } + } else + result -> len = 0; + +#if defined (DEBUG_EXPRESSIONS) + if (!s0) + log_debug ("data: encode_int16 (NULL) = NULL"); + else + log_debug ("data: encode_int16 (%ld) = %s", len, + print_hex_2 (result -> len, + result -> data, 20)); +#endif + return s0; + + case expr_encode_int32: + s0 = evaluate_numeric_expression (&len, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.encode_int); + if (s0) { + result -> len = 4; + if (!buffer_allocate (&result -> buffer, 4, + file, line)) { + log_error ("data: encode_int32: no memory"); + result -> len = 0; + s0 = 0; + } else { + result -> data = &result -> buffer -> data [0]; + putULong (result -> buffer -> data, len); + } + } else + result -> len = 0; + +#if defined (DEBUG_EXPRESSIONS) + if (!s0) + log_debug ("data: encode_int32 (NULL) = NULL"); + else + log_debug ("data: encode_int32 (%ld) = %s", len, + print_hex_2 (result -> len, + result -> data, 20)); +#endif + return s0; + + case expr_binary_to_ascii: + /* Evaluate the base (offset) and width (len): */ + s0 = evaluate_numeric_expression + (&offset, packet, lease, client_state, in_options, + cfg_options, scope, expr -> data.b2a.base); + s1 = evaluate_numeric_expression (&len, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.b2a.width); + + /* Evaluate the separator string. */ + memset (&data, 0, sizeof data); + s2 = evaluate_data_expression (&data, packet, lease, + client_state, + in_options, cfg_options, scope, + expr -> data.b2a.separator, + MDL); + + /* Evaluate the data to be converted. */ + memset (&other, 0, sizeof other); + s3 = evaluate_data_expression (&other, packet, lease, + client_state, + in_options, cfg_options, scope, + expr -> data.b2a.buffer, MDL); + + if (s0 && s1 && s2 && s3) { + unsigned buflen, i; + + if (len != 8 && len != 16 && len != 32) { + log_info ("binary_to_ascii: %s %ld!", + "invalid width", len); + status = 0; + goto b2a_out; + } + len /= 8; + + /* The buffer must be a multiple of the number's + width. */ + if (other.len % len) { + log_info ("binary-to-ascii: %s %d %s %ld!", + "length of buffer", other.len, + "not a multiple of width", len); + status = 0; + goto b2a_out; + } + + /* Count the width of the output. */ + buflen = 0; + for (i = 0; i < other.len; i += len) { + if (len == 1) { + if (offset == 8) { + if (other.data [i] < 8) + buflen++; + else if (other.data [i] < 64) + buflen += 2; + else + buflen += 3; + } else if (offset == 10) { + if (other.data [i] < 10) + buflen++; + else if (other.data [i] < 100) + buflen += 2; + else + buflen += 3; + } else if (offset == 16) { + if (other.data [i] < 16) + buflen++; + else + buflen += 2; + } else + buflen += (converted_length + (&other.data [i], + offset, 1)); + } else + buflen += (converted_length + (&other.data [i], + offset, len)); + if (i + len != other.len) + buflen += data.len; + } + + if (!buffer_allocate (&result -> buffer, + buflen + 1, file, line)) { + log_error ("data: binary-to-ascii: no memory"); + status = 0; + goto b2a_out; + } + result -> data = &result -> buffer -> data [0]; + result -> len = buflen; + result -> terminated = 1; + + buflen = 0; + for (i = 0; i < other.len; i += len) { + buflen += (binary_to_ascii + (&result -> buffer -> data [buflen], + &other.data [i], offset, len)); + if (i + len != other.len) { + memcpy (&result -> + buffer -> data [buflen], + data.data, data.len); + buflen += data.len; + } + } + /* NUL terminate. */ + result -> buffer -> data [buflen] = 0; + status = 1; + } else + status = 0; + + b2a_out: +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: binary-to-ascii (%s, %s, %s, %s) = %s", + s0 ? print_dec_1 (offset) : "NULL", + s1 ? print_dec_2 (len) : "NULL", + s2 ? print_hex_1 (data.len, data.data, 30) : "NULL", + s3 ? print_hex_2 (other.len, other.data, 30) : "NULL", + (status ? print_hex_3 (result -> len, result -> data, 30) + : "NULL")); +#endif + if (s2) + data_string_forget (&data, MDL); + if (s3) + data_string_forget (&other, MDL); + if (status) + return 1; + return 0; + + case expr_reverse: + /* Evaluate the width (len): */ + s0 = evaluate_numeric_expression + (&len, packet, lease, client_state, in_options, + cfg_options, scope, expr -> data.reverse.width); + + /* Evaluate the data. */ + memset (&data, 0, sizeof data); + s1 = evaluate_data_expression (&data, packet, lease, + client_state, + in_options, cfg_options, scope, + expr -> data.reverse.buffer, + MDL); + + if (s0 && s1) { + int i; + + /* The buffer must be a multiple of the number's + width. */ + if (data.len % len) { + log_info ("reverse: %s %d %s %ld!", + "length of buffer", data.len, + "not a multiple of width", len); + status = 0; + goto reverse_out; + } + + /* XXX reverse in place? I don't think we can. */ + if (!buffer_allocate (&result -> buffer, + data.len, file, line)) { + log_error ("data: reverse: no memory"); + status = 0; + goto reverse_out; + } + result -> data = &result -> buffer -> data [0]; + result -> len = data.len; + result -> terminated = 0; + + for (i = 0; i < data.len; i += len) { + memcpy (&result -> buffer -> data [i], + &data.data [data.len - i - len], len); + } + status = 1; + } else + status = 0; + + reverse_out: +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: reverse (%s, %s) = %s", + s0 ? print_dec_1 (len) : "NULL", + s1 ? print_hex_1 (data.len, data.data, 30) : "NULL", + (status ? print_hex_3 (result -> len, result -> data, 30) + : "NULL")); +#endif + if (s0) + data_string_forget (&data, MDL); + if (status) + return 1; + return 0; + + case expr_leased_address: + if (!lease) { + log_debug("data: \"leased-address\" configuration " + "directive: there is no lease associated " + "with this client."); + return 0; + } + result -> len = lease -> ip_addr.len; + if (buffer_allocate (&result -> buffer, result -> len, + file, line)) { + result -> data = &result -> buffer -> data [0]; + memcpy (&result -> buffer -> data [0], + lease -> ip_addr.iabuf, lease -> ip_addr.len); + result -> terminated = 0; + } else { + log_error ("data: leased-address: no memory."); + return 0; + } +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: leased-address = %s", + print_hex_1 (result -> len, result -> data, 60)); +#endif + return 1; + + case expr_pick_first_value: + memset (&data, 0, sizeof data); + if ((evaluate_data_expression + (result, packet, + lease, client_state, in_options, cfg_options, + scope, expr -> data.pick_first_value.car, MDL))) { +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: pick_first_value (%s, xxx)", + print_hex_1 (result -> len, + result -> data, 40)); +#endif + return 1; + } + + if (expr -> data.pick_first_value.cdr && + (evaluate_data_expression + (result, packet, + lease, client_state, in_options, cfg_options, + scope, expr -> data.pick_first_value.cdr, MDL))) { +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: pick_first_value (NULL, %s)", + print_hex_1 (result -> len, + result -> data, 40)); +#endif + return 1; + } + +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: pick_first_value (NULL, NULL) = NULL"); +#endif + return 0; + + case expr_host_decl_name: + if (!lease || !lease -> host) { + log_error ("data: host_decl_name: not available"); + return 0; + } + result -> len = strlen (lease -> host -> name); + if (buffer_allocate (&result -> buffer, + result -> len + 1, file, line)) { + result -> data = &result -> buffer -> data [0]; + strcpy ((char *)&result -> buffer -> data [0], + lease -> host -> name); + result -> terminated = 1; + } else { + log_error ("data: host-decl-name: no memory."); + return 0; + } +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: host-decl-name = %s", lease -> host -> name); +#endif + return 1; + + case expr_null: +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: null = NULL"); +#endif + return 0; + + case expr_variable_reference: + if (scope && *scope) { + binding = find_binding (*scope, expr -> data.variable); + + if (binding && binding -> value) { + if (binding -> value -> type == binding_data) { + data_string_copy (result, + &binding -> value -> value.data, + file, line); + s0 = 1; + } else if (binding -> value -> type != binding_data) { + log_error ("binding type %d in %s.", + binding -> value -> type, + "evaluate_data_expression"); + s0 = 0; + } else + s0 = 0; + } else + s0 = 0; + } else + s0 = 0; +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: %s = %s", expr -> data.variable, + s0 ? print_hex_1 (result -> len, + result -> data, 50) : "NULL"); +#endif + return s0; + + case expr_funcall: + bv = (struct binding_value *)0; + s0 = evaluate_expression (&bv, packet, lease, client_state, + in_options, cfg_options, + scope, expr, MDL); + if (s0) { + if (bv -> type != binding_data) + log_error ("%s() returned type %d in %s.", + expr -> data.funcall.name, + bv -> type, + "evaluate_data_expression"); + else + data_string_copy (result, &bv -> value.data, + file, line); + binding_value_dereference (&bv, MDL); + } +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: %s = %s", expr -> data.funcall.name, + s0 ? print_hex_1 (result -> len, + result -> data, 50) : "NULL"); +#endif + break; + + /* Extract the filename. */ + case expr_filename: + if (packet && packet -> raw -> file [0]) { + char *fn = + memchr (packet -> raw -> file, 0, + sizeof packet -> raw -> file); + if (!fn) + fn = ((char *)packet -> raw -> file + + sizeof packet -> raw -> file); + result -> len = fn - &(packet -> raw -> file [0]); + if (buffer_allocate (&result -> buffer, + result -> len + 1, file, line)) { + result -> data = &result -> buffer -> data [0]; + memcpy (&result -> buffer -> data [0], + packet -> raw -> file, + result -> len); + result -> buffer -> data [result -> len] = 0; + result -> terminated = 1; + s0 = 1; + } else { + log_error ("data: filename: no memory."); + s0 = 0; + } + } else + s0 = 0; + +#if defined (DEBUG_EXPRESSIONS) + log_info ("data: filename = \"%s\"", + s0 ? (const char *)(result -> data) : "NULL"); +#endif + return s0; + + /* Extract the server name. */ + case expr_sname: + if (packet && packet -> raw -> sname [0]) { + char *fn = + memchr (packet -> raw -> sname, 0, + sizeof packet -> raw -> sname); + if (!fn) + fn = ((char *)packet -> raw -> sname + + sizeof packet -> raw -> sname); + result -> len = fn - &packet -> raw -> sname [0]; + if (buffer_allocate (&result -> buffer, + result -> len + 1, file, line)) { + result -> data = &result -> buffer -> data [0]; + memcpy (&result -> buffer -> data [0], + packet -> raw -> sname, + result -> len); + result -> buffer -> data [result -> len] = 0; + result -> terminated = 1; + s0 = 1; + } else { + log_error ("data: sname: no memory."); + s0 = 0; + } + } else + s0 = 0; + +#if defined (DEBUG_EXPRESSIONS) + log_info ("data: sname = \"%s\"", + s0 ? (const char *)(result -> data) : "NULL"); +#endif + return s0; + + /* Provide the system's local hostname as a return value. */ + case expr_gethostname: + /* + * Allocate a buffer to return. + * + * The largest valid hostname is maybe 64 octets at a single + * label, or 255 octets if you think a hostname is allowed + * to contain labels (plus termination). + */ + memset(result, 0, sizeof(*result)); + if (!buffer_allocate(&result->buffer, 255, file, line)) { + log_error("data: gethostname(): no memory for buffer"); + return 0; + } + result->data = result->buffer->data; + + /* + * On successful completion, gethostname() resturns 0. It may + * not null-terminate the string if there was insufficient + * space. + */ + if (!gethostname((char *)result->buffer->data, 255)) { + if (result->buffer->data[255] == '\0') + result->len = + strlen((char *)result->buffer->data); + else + result->len = 255; + return 1; + } + + data_string_forget(result, MDL); + return 0; + + case expr_concat_dclist: { + /* Operands are compressed domain-name lists ("Dc" format) + * Fetch both compressed lists then call concat_dclists which + * combines them into a single compressed list. */ + memset(&data, 0, sizeof data); + int outcome = 0; + s0 = evaluate_data_expression(&data, packet, lease, + client_state, + in_options, cfg_options, scope, + expr->data.concat[0], MDL); + + memset (&other, 0, sizeof other); + s1 = evaluate_data_expression (&other, packet, lease, + client_state, + in_options, cfg_options, scope, + expr->data.concat[1], MDL); + + if (s0 && s1) { + outcome = concat_dclists(result, &data, &other); + if (outcome == 0) { + log_error ("data: concat_dclist failed"); + } + } + +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: concat_dclists (%s, %s) = %s", + (s0 ? print_hex_1(data.len, data.data, data.len) + : "NULL"), + (s1 ? print_hex_2(other.len, other.data, other.len) + : "NULL"), + (((s0 && s1) && result->len > 0) + ? print_hex_3 (result->len, result->data, result->len) + : "NULL")); +#endif + if (s0) + data_string_forget (&data, MDL); + + if (s1) + data_string_forget (&other, MDL); + + return (outcome); + } /* expr_concat_dclist */ + + case expr_check: + case expr_equal: + case expr_not_equal: + case expr_regex_match: + case expr_iregex_match: + case expr_and: + case expr_or: + case expr_not: + case expr_match: + case expr_static: + case expr_known: + case expr_none: + case expr_exists: + case expr_variable_exists: + log_error ("Boolean opcode in evaluate_data_expression: %d", + expr -> op); + return 0; + + case expr_extract_int8: + case expr_extract_int16: + case expr_extract_int32: + case expr_const_int: + case expr_lease_time: + case expr_dns_transaction: + case expr_add: + case expr_subtract: + case expr_multiply: + case expr_divide: + case expr_remainder: + case expr_binary_and: + case expr_binary_or: + case expr_binary_xor: + case expr_client_state: + log_error ("Numeric opcode in evaluate_data_expression: %d", + expr -> op); + return 0; + + case expr_ns_add: + case expr_ns_delete: + case expr_ns_exists: + case expr_ns_not_exists: + log_error ("dns update opcode in evaluate_data_expression: %d", + expr -> op); + return 0; + + case expr_function: + log_error ("function definition in evaluate_data_expression"); + return 0; + + case expr_arg: + break; + } + + log_error ("Bogus opcode in evaluate_data_expression: %d", expr -> op); + return 0; +} + +int evaluate_numeric_expression (result, packet, lease, client_state, + in_options, cfg_options, scope, expr) + unsigned long *result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct expression *expr; +{ + struct data_string data; + int status, sleft, sright; +#if defined (NSUPDATE_OLD) + ns_updrec *nut; + ns_updque uq; + struct expression *cur, *next; +#endif + + struct binding *binding; + struct binding_value *bv; + unsigned long ileft, iright; + int rc = 0; + + switch (expr -> op) { + case expr_check: + case expr_equal: + case expr_not_equal: + case expr_regex_match: + case expr_iregex_match: + case expr_and: + case expr_or: + case expr_not: + case expr_match: + case expr_static: + case expr_known: + case expr_none: + case expr_exists: + case expr_variable_exists: + log_error ("Boolean opcode in evaluate_numeric_expression: %d", + expr -> op); + return 0; + + case expr_substring: + case expr_suffix: + case expr_lcase: + case expr_ucase: + case expr_option: + case expr_hardware: + case expr_const_data: + case expr_packet: + case expr_concat: + case expr_encapsulate: + case expr_host_lookup: + case expr_encode_int8: + case expr_encode_int16: + case expr_encode_int32: + case expr_binary_to_ascii: + case expr_reverse: + case expr_filename: + case expr_sname: + case expr_pick_first_value: + case expr_host_decl_name: + case expr_config_option: + case expr_leased_address: + case expr_null: + case expr_gethostname: + log_error ("Data opcode in evaluate_numeric_expression: %d", + expr -> op); + return 0; + + case expr_extract_int8: + memset (&data, 0, sizeof data); + status = evaluate_data_expression + (&data, packet, lease, client_state, in_options, + cfg_options, scope, expr -> data.extract_int, MDL); + if (status) + *result = data.data [0]; +#if defined (DEBUG_EXPRESSIONS) + log_debug ("num: extract_int8 (%s) = %s", + status ? print_hex_1 (data.len, data.data, 60) : "NULL", + status ? print_dec_1 (*result) : "NULL" ); +#endif + if (status) data_string_forget (&data, MDL); + return status; + + case expr_extract_int16: + memset(&data, 0, sizeof(data)); + status = (evaluate_data_expression + (&data, packet, lease, client_state, in_options, + cfg_options, scope, expr->data.extract_int, MDL)); + if (status && data.len >= 2) { + *result = getUShort(data.data); + rc = 1; + } +#if defined (DEBUG_EXPRESSIONS) + if (rc == 1) { + log_debug("num: extract_int16 (%s) = %ld", + print_hex_1(data.len, data.data, 60), + *result); + } else { + log_debug("num: extract_int16 (NULL) = NULL"); + } +#endif + if (status) + data_string_forget(&data, MDL); + + return (rc); + + case expr_extract_int32: + memset (&data, 0, sizeof data); + status = (evaluate_data_expression + (&data, packet, lease, client_state, in_options, + cfg_options, scope, expr -> data.extract_int, MDL)); + if (status && data.len >= 4) { + *result = getULong (data.data); + rc = 1; + } +#if defined (DEBUG_EXPRESSIONS) + if (rc == 1) { + log_debug ("num: extract_int32 (%s) = %ld", + print_hex_1 (data.len, data.data, 60), + *result); + } else { + log_debug ("num: extract_int32 (NULL) = NULL"); + } +#endif + if (status) data_string_forget (&data, MDL); + return (rc); + + case expr_const_int: + *result = expr -> data.const_int; +#if defined (DEBUG_EXPRESSIONS) + log_debug ("number: CONSTANT = %ld", *result); +#endif + return 1; + + case expr_lease_time: + if (!lease) { + log_error("data: leased_lease: not available"); + return (0); + } + if (lease->ends < cur_time) { + log_error("%s %lu when it is now %lu", + "data: lease_time: lease ends at", + (long)(lease->ends), (long)cur_time); + return (0); + } + *result = lease->ends - cur_time; +#if defined (DEBUG_EXPRESSIONS) + log_debug("number: lease-time = (%lu - %lu) = %ld", + (long unsigned)lease->ends, + (long unsigned)cur_time, *result); +#endif + return (1); + + case expr_dns_transaction: +#if !defined (NSUPDATE_OLD) + return 0; +#else + if (!resolver_inited) { + minires_ninit (&resolver_state); + resolver_inited = 1; + resolver_state.retrans = 1; + resolver_state.retry = 1; + } + ISC_LIST_INIT (uq); + cur = expr; + do { + next = cur -> data.dns_transaction.cdr; + nut = 0; + status = (evaluate_dns_expression + (&nut, packet, + lease, client_state, in_options, cfg_options, + scope, cur -> data.dns_transaction.car)); + if (!status) + goto dns_bad; + ISC_LIST_APPEND (uq, nut, r_link); + cur = next; + } while (next); + + /* Do the update and record the error code, if there was + an error; otherwise set it to NOERROR. */ + *result = minires_nupdate (&resolver_state, + ISC_LIST_HEAD (uq)); + status = 1; + + print_dns_status ((int)*result, &uq); + + dns_bad: + while (!ISC_LIST_EMPTY (uq)) { + ns_updrec *tmp = ISC_LIST_HEAD (uq); + ISC_LIST_UNLINK (uq, tmp, r_link); + if (tmp -> r_data_ephem) { + dfree (tmp -> r_data_ephem, MDL); + tmp -> r_data = (unsigned char *)0; + tmp -> r_data_ephem = (unsigned char *)0; + } + minires_freeupdrec (tmp); + } + return status; +#endif /* NSUPDATE_OLD */ + + case expr_variable_reference: + if (scope && *scope) { + binding = find_binding (*scope, expr -> data.variable); + + if (binding && binding -> value) { + if (binding -> value -> type == binding_numeric) { + *result = binding -> value -> value.intval; + status = 1; + } else { + log_error ("binding type %d in %s.", + binding -> value -> type, + "evaluate_numeric_expression"); + status = 0; + } + } else + status = 0; + } else + status = 0; +#if defined (DEBUG_EXPRESSIONS) + if (status) + log_debug ("numeric: %s = %ld", + expr -> data.variable, *result); + else + log_debug ("numeric: %s = NULL", + expr -> data.variable); +#endif + return status; + + case expr_funcall: + bv = (struct binding_value *)0; + status = evaluate_expression (&bv, packet, lease, + client_state, + in_options, cfg_options, + scope, expr, MDL); + if (status) { + if (bv -> type != binding_numeric) + log_error ("%s() returned type %d in %s.", + expr -> data.funcall.name, + bv -> type, + "evaluate_numeric_expression"); + else + *result = bv -> value.intval; + binding_value_dereference (&bv, MDL); + } +#if defined (DEBUG_EXPRESSIONS) + log_debug ("data: %s = %ld", expr -> data.funcall.name, + status ? *result : 0); +#endif + break; + + case expr_add: + sleft = evaluate_numeric_expression (&ileft, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [0]); + sright = evaluate_numeric_expression (&iright, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [1]); + +#if defined (DEBUG_EXPRESSIONS) + if (sleft && sright) + log_debug ("num: %ld + %ld = %ld", + ileft, iright, ileft + iright); + else if (sleft) + log_debug ("num: %ld + NULL = NULL", ileft); + else + log_debug ("num: NULL + %ld = NULL", iright); +#endif + if (sleft && sright) { + *result = ileft + iright; + return 1; + } + return 0; + + case expr_subtract: + sleft = evaluate_numeric_expression (&ileft, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [0]); + sright = evaluate_numeric_expression (&iright, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [1]); + +#if defined (DEBUG_EXPRESSIONS) + if (sleft && sright) + log_debug ("num: %ld - %ld = %ld", + ileft, iright, ileft - iright); + else if (sleft) + log_debug ("num: %ld - NULL = NULL", ileft); + else + log_debug ("num: NULL - %ld = NULL", iright); +#endif + if (sleft && sright) { + *result = ileft - iright; + return 1; + } + return 0; + + case expr_multiply: + sleft = evaluate_numeric_expression (&ileft, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [0]); + sright = evaluate_numeric_expression (&iright, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [1]); + +#if defined (DEBUG_EXPRESSIONS) + if (sleft && sright) + log_debug ("num: %ld * %ld = %ld", + ileft, iright, ileft * iright); + else if (sleft) + log_debug ("num: %ld * NULL = NULL", ileft); + else + log_debug ("num: NULL * %ld = NULL", iright); +#endif + if (sleft && sright) { + *result = ileft * iright; + return 1; + } + return 0; + + case expr_divide: + sleft = evaluate_numeric_expression (&ileft, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [0]); + sright = evaluate_numeric_expression (&iright, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [1]); + +#if defined (DEBUG_EXPRESSIONS) + if (sleft && sright) { + if (iright != 0) + log_debug ("num: %ld / %ld = %ld", + ileft, iright, ileft / iright); + else + log_debug ("num: %ld / %ld = NULL", + ileft, iright); + } else if (sleft) + log_debug ("num: %ld / NULL = NULL", ileft); + else + log_debug ("num: NULL / %ld = NULL", iright); +#endif + if (sleft && sright && iright) { + *result = ileft / iright; + return 1; + } + return 0; + + case expr_remainder: + sleft = evaluate_numeric_expression (&ileft, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [0]); + sright = evaluate_numeric_expression (&iright, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [1]); + +#if defined (DEBUG_EXPRESSIONS) + if (sleft && sright) { + if (iright != 0) + log_debug ("num: %ld %% %ld = %ld", + ileft, iright, ileft % iright); + else + log_debug ("num: %ld %% %ld = NULL", + ileft, iright); + } else if (sleft) + log_debug ("num: %ld %% NULL = NULL", ileft); + else + log_debug ("num: NULL %% %ld = NULL", iright); +#endif + if (sleft && sright && iright) { + *result = ileft % iright; + return 1; + } + return 0; + + case expr_binary_and: + sleft = evaluate_numeric_expression (&ileft, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [0]); + sright = evaluate_numeric_expression (&iright, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [1]); + +#if defined (DEBUG_EXPRESSIONS) + if (sleft && sright) + log_debug ("num: %ld | %ld = %ld", + ileft, iright, ileft & iright); + else if (sleft) + log_debug ("num: %ld & NULL = NULL", ileft); + else + log_debug ("num: NULL & %ld = NULL", iright); +#endif + if (sleft && sright) { + *result = ileft & iright; + return 1; + } + return 0; + + case expr_binary_or: + sleft = evaluate_numeric_expression (&ileft, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [0]); + sright = evaluate_numeric_expression (&iright, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [1]); + +#if defined (DEBUG_EXPRESSIONS) + if (sleft && sright) + log_debug ("num: %ld | %ld = %ld", + ileft, iright, ileft | iright); + else if (sleft) + log_debug ("num: %ld | NULL = NULL", ileft); + else + log_debug ("num: NULL | %ld = NULL", iright); +#endif + if (sleft && sright) { + *result = ileft | iright; + return 1; + } + return 0; + + case expr_binary_xor: + sleft = evaluate_numeric_expression (&ileft, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [0]); + sright = evaluate_numeric_expression (&iright, packet, lease, + client_state, + in_options, cfg_options, + scope, + expr -> data.and [1]); + +#if defined (DEBUG_EXPRESSIONS) + if (sleft && sright) + log_debug ("num: %ld ^ %ld = %ld", + ileft, iright, ileft ^ iright); + else if (sleft) + log_debug ("num: %ld ^ NULL = NULL", ileft); + else + log_debug ("num: NULL ^ %ld = NULL", iright); +#endif + if (sleft && sright) { + *result = ileft ^ iright; + return 1; + } + return 0; + + case expr_client_state: + if (client_state) { +#if defined (DEBUG_EXPRESSIONS) + log_debug ("num: client-state = %d", + client_state -> state); +#endif + *result = client_state -> state; + return 1; + } else { +#if defined (DEBUG_EXPRESSIONS) + log_debug ("num: client-state = NULL"); +#endif + return 0; + } + + case expr_ns_add: + case expr_ns_delete: + case expr_ns_exists: + case expr_ns_not_exists: + log_error ("dns opcode in evaluate_numeric_expression: %d", + expr -> op); + return 0; + + case expr_function: + log_error ("function definition in evaluate_numeric_expr"); + return 0; + + case expr_arg: + break; + + default: + log_fatal("Impossible case at %s:%d. Undefined operator " + "%d.", MDL, expr->op); + break; + } + + log_error ("evaluate_numeric_expression: bogus opcode %d", expr -> op); + return 0; +} + +/* + * Return data hanging off of an option cache structure, or if there + * isn't any, evaluate the expression hanging off of it and return the + * result of that evaluation. There should never be both an expression + * and a valid data_string. + * + * returns 0 if there wasn't an expression or it couldn't be evaluated + * returns non-zero if there was an expression or string that was evaluated + * When it returns zero the arguements, in particualr resutl, should not + * be modified + */ + +int evaluate_option_cache (result, packet, lease, client_state, + in_options, cfg_options, scope, oc, file, line) + struct data_string *result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct option_cache *oc; + const char *file; + int line; +{ + if (oc->data.data != NULL) { + data_string_copy (result, &oc -> data, file, line); + return 1; + } + if (!oc -> expression) + return 0; + return evaluate_data_expression (result, packet, lease, client_state, + in_options, cfg_options, scope, + oc -> expression, file, line); +} + +/* Evaluate an option cache and extract a boolean from the result. + * The boolean option cache is actually a trinary value where: + * + * 0 = return 0, ignore parameter 0 (also the case for no data) + * 1 = return 1, ignore parameter 0 + * 2 = return 0, ignore parameter 1 + * + * This supports both classic boolean flags on/off as well as the + * allow/deny/ignore keywords +*/ +int evaluate_boolean_option_cache (ignorep, packet, + lease, client_state, in_options, + cfg_options, scope, oc, file, line) + int *ignorep; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct option_cache *oc; + const char *file; + int line; +{ + int result = 0; + if (ignorep) + *ignorep = 0; + + /* Only attempt to evaluate if option_cache is not null. This permits + * us to be called with option_lookup() as an argument. */ + if (oc && in_options) { + struct data_string ds; + + memset(&ds, 0, sizeof ds); + if (evaluate_option_cache(&ds, packet, + lease, client_state, in_options, + cfg_options, scope, oc, file, + line)) { + /* We have a value for the option set result and + * ignore parameter accordingly. */ + if (ds.len) { + if (ds.data[0] == 1) + result = 1; + else if ((ds.data[0] == 2) && (ignorep != NULL)) + *ignorep = 1; + } + + data_string_forget(&ds, MDL); + } + } + + return (result); +} + +/* Evaluate a boolean expression and return the result of the evaluation, + or FALSE if it failed. */ + +int evaluate_boolean_expression_result (ignorep, packet, lease, client_state, + in_options, cfg_options, scope, expr) + int *ignorep; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct expression *expr; +{ + int result; + + /* So that we can be called with option_lookup as an argument. */ + if (!expr) + return 0; + + if (!evaluate_boolean_expression (&result, packet, lease, client_state, + in_options, cfg_options, + scope, expr)) + return 0; + + if (result == 2) { + *ignorep = 1; + result = 0; + } else + *ignorep = 0; + return result; +} + + +/* Dereference an expression node, and if the reference count goes to zero, + dereference any data it refers to, and then free it. */ +void expression_dereference (eptr, file, line) + struct expression **eptr; + const char *file; + int line; +{ + struct expression *expr = *eptr; + + /* Zero the pointer. */ + *eptr = (struct expression *)0; + + /* Decrement the reference count. If it's nonzero, we're + done. */ + --(expr -> refcnt); + rc_register (file, line, eptr, expr, expr -> refcnt, 1, RC_MISC); + if (expr -> refcnt > 0) + return; + if (expr -> refcnt < 0) { + log_error ("%s(%d): negative refcnt!", file, line); +#if defined (DEBUG_RC_HISTORY) + dump_rc_history (expr); +#endif +#if defined (POINTER_DEBUG) + abort (); +#else + return; +#endif + } + + /* Dereference subexpressions. */ + switch (expr -> op) { + /* All the binary operators can be handled the same way. */ + case expr_equal: + case expr_not_equal: + case expr_regex_match: + case expr_iregex_match: + case expr_concat: + case expr_and: + case expr_or: + case expr_add: + case expr_subtract: + case expr_multiply: + case expr_divide: + case expr_remainder: + case expr_binary_and: + case expr_binary_or: + case expr_binary_xor: + case expr_client_state: + if (expr -> data.equal [0]) + expression_dereference (&expr -> data.equal [0], + file, line); + if (expr -> data.equal [1]) + expression_dereference (&expr -> data.equal [1], + file, line); + break; + + case expr_substring: + if (expr -> data.substring.expr) + expression_dereference (&expr -> data.substring.expr, + file, line); + if (expr -> data.substring.offset) + expression_dereference (&expr -> data.substring.offset, + file, line); + if (expr -> data.substring.len) + expression_dereference (&expr -> data.substring.len, + file, line); + break; + + case expr_suffix: + if (expr -> data.suffix.expr) + expression_dereference (&expr -> data.suffix.expr, + file, line); + if (expr -> data.suffix.len) + expression_dereference (&expr -> data.suffix.len, + file, line); + break; + + case expr_lcase: + if (expr->data.lcase) + expression_dereference(&expr->data.lcase, MDL); + break; + + case expr_ucase: + if (expr->data.ucase) + expression_dereference(&expr->data.ucase, MDL); + break; + + case expr_not: + if (expr -> data.not) + expression_dereference (&expr -> data.not, file, line); + break; + + case expr_packet: + if (expr -> data.packet.offset) + expression_dereference (&expr -> data.packet.offset, + file, line); + if (expr -> data.packet.len) + expression_dereference (&expr -> data.packet.len, + file, line); + break; + + case expr_extract_int8: + case expr_extract_int16: + case expr_extract_int32: + if (expr -> data.extract_int) + expression_dereference (&expr -> data.extract_int, + file, line); + break; + + case expr_encode_int8: + case expr_encode_int16: + case expr_encode_int32: + if (expr -> data.encode_int) + expression_dereference (&expr -> data.encode_int, + file, line); + break; + + case expr_encapsulate: + case expr_const_data: + data_string_forget (&expr -> data.const_data, file, line); + break; + + case expr_host_lookup: + if (expr -> data.host_lookup) + dns_host_entry_dereference (&expr -> data.host_lookup, + file, line); + break; + + case expr_binary_to_ascii: + if (expr -> data.b2a.base) + expression_dereference (&expr -> data.b2a.base, + file, line); + if (expr -> data.b2a.width) + expression_dereference (&expr -> data.b2a.width, + file, line); + if (expr -> data.b2a.separator) + expression_dereference (&expr -> data.b2a.separator, + file, line); + if (expr -> data.b2a.buffer) + expression_dereference (&expr -> data.b2a.buffer, + file, line); + break; + + case expr_pick_first_value: + if (expr -> data.pick_first_value.car) + expression_dereference (&expr -> data.pick_first_value.car, + file, line); + if (expr -> data.pick_first_value.cdr) + expression_dereference (&expr -> data.pick_first_value.cdr, + file, line); + break; + + case expr_reverse: + if (expr -> data.reverse.width) + expression_dereference (&expr -> data.reverse.width, + file, line); + if (expr -> data.reverse.buffer) + expression_dereference + (&expr -> data.reverse.buffer, file, line); + break; + + case expr_dns_transaction: + if (expr -> data.dns_transaction.car) + expression_dereference (&expr -> data.dns_transaction.car, + file, line); + if (expr -> data.dns_transaction.cdr) + expression_dereference (&expr -> data.dns_transaction.cdr, + file, line); + break; + + case expr_ns_add: + if (expr -> data.ns_add.rrname) + expression_dereference (&expr -> data.ns_add.rrname, + file, line); + if (expr -> data.ns_add.rrdata) + expression_dereference (&expr -> data.ns_add.rrdata, + file, line); + if (expr -> data.ns_add.ttl) + expression_dereference (&expr -> data.ns_add.ttl, + file, line); + break; + + case expr_ns_delete: + case expr_ns_exists: + case expr_ns_not_exists: + if (expr -> data.ns_delete.rrname) + expression_dereference (&expr -> data.ns_delete.rrname, + file, line); + if (expr -> data.ns_delete.rrdata) + expression_dereference (&expr -> data.ns_delete.rrdata, + file, line); + break; + + case expr_variable_reference: + case expr_variable_exists: + if (expr -> data.variable) + dfree (expr -> data.variable, file, line); + break; + + case expr_funcall: + if (expr -> data.funcall.name) + dfree (expr -> data.funcall.name, file, line); + if (expr -> data.funcall.arglist) + expression_dereference (&expr -> data.funcall.arglist, + file, line); + break; + + case expr_arg: + if (expr -> data.arg.val) + expression_dereference (&expr -> data.arg.val, + file, line); + if (expr -> data.arg.next) + expression_dereference (&expr -> data.arg.next, + file, line); + break; + + case expr_function: + fundef_dereference (&expr -> data.func, file, line); + break; + + /* No subexpressions. */ + case expr_leased_address: + case expr_lease_time: + case expr_filename: + case expr_sname: + case expr_const_int: + case expr_check: + case expr_option: + case expr_hardware: + case expr_exists: + case expr_known: + case expr_null: + case expr_gethostname: + break; + + default: + break; + } + free_expression (expr, MDL); +} + +int is_dns_expression (expr) + struct expression *expr; +{ + return (expr -> op == expr_ns_add || + expr -> op == expr_ns_delete || + expr -> op == expr_ns_exists || + expr -> op == expr_ns_not_exists); +} + +int is_boolean_expression (expr) + struct expression *expr; +{ + return (expr -> op == expr_check || + expr -> op == expr_exists || + expr -> op == expr_variable_exists || + expr -> op == expr_equal || + expr -> op == expr_not_equal || + expr->op == expr_regex_match || + expr->op == expr_iregex_match || + expr -> op == expr_and || + expr -> op == expr_or || + expr -> op == expr_not || + expr -> op == expr_known || + expr -> op == expr_static); +} + +int is_data_expression (expr) + struct expression *expr; +{ + return (expr->op == expr_substring || + expr->op == expr_suffix || + expr->op == expr_lcase || + expr->op == expr_ucase || + expr->op == expr_option || + expr->op == expr_hardware || + expr->op == expr_const_data || + expr->op == expr_packet || + expr->op == expr_concat || + expr->op == expr_encapsulate || + expr->op == expr_encode_int8 || + expr->op == expr_encode_int16 || + expr->op == expr_encode_int32 || + expr->op == expr_host_lookup || + expr->op == expr_binary_to_ascii || + expr->op == expr_filename || + expr->op == expr_sname || + expr->op == expr_reverse || + expr->op == expr_pick_first_value || + expr->op == expr_host_decl_name || + expr->op == expr_leased_address || + expr->op == expr_config_option || + expr->op == expr_null || + expr->op == expr_gethostname); +} + +int is_numeric_expression (expr) + struct expression *expr; +{ + return (expr -> op == expr_extract_int8 || + expr -> op == expr_extract_int16 || + expr -> op == expr_extract_int32 || + expr -> op == expr_const_int || + expr -> op == expr_lease_time || + expr -> op == expr_dns_transaction || + expr -> op == expr_add || + expr -> op == expr_subtract || + expr -> op == expr_multiply || + expr -> op == expr_divide || + expr -> op == expr_remainder || + expr -> op == expr_binary_and || + expr -> op == expr_binary_or || + expr -> op == expr_binary_xor || + expr -> op == expr_client_state); +} + +int is_compound_expression (expr) + struct expression *expr; +{ + return (expr -> op == expr_ns_add || + expr -> op == expr_ns_delete || + expr -> op == expr_ns_exists || + expr -> op == expr_ns_not_exists || + expr -> op == expr_substring || + expr -> op == expr_suffix || + expr -> op == expr_option || + expr -> op == expr_concat || + expr -> op == expr_encode_int8 || + expr -> op == expr_encode_int16 || + expr -> op == expr_encode_int32 || + expr -> op == expr_binary_to_ascii || + expr -> op == expr_reverse || + expr -> op == expr_pick_first_value || + expr -> op == expr_config_option || + expr -> op == expr_extract_int8 || + expr -> op == expr_extract_int16 || + expr -> op == expr_extract_int32 || + expr -> op == expr_dns_transaction); +} + +static int op_val (enum expr_op); + +static int op_val (op) + enum expr_op op; +{ + switch (op) { + case expr_none: + case expr_match: + case expr_static: + case expr_check: + case expr_substring: + case expr_suffix: + case expr_lcase: + case expr_ucase: + case expr_concat: + case expr_encapsulate: + case expr_host_lookup: + case expr_not: + case expr_option: + case expr_hardware: + case expr_packet: + case expr_const_data: + case expr_extract_int8: + case expr_extract_int16: + case expr_extract_int32: + case expr_encode_int8: + case expr_encode_int16: + case expr_encode_int32: + case expr_const_int: + case expr_exists: + case expr_variable_exists: + case expr_known: + case expr_binary_to_ascii: + case expr_reverse: + case expr_filename: + case expr_sname: + case expr_pick_first_value: + case expr_host_decl_name: + case expr_config_option: + case expr_leased_address: + case expr_lease_time: + case expr_dns_transaction: + case expr_null: + case expr_variable_reference: + case expr_ns_add: + case expr_ns_delete: + case expr_ns_exists: + case expr_ns_not_exists: + case expr_arg: + case expr_funcall: + case expr_function: + /* XXXDPN: Need to assign sane precedences to these. */ + case expr_binary_and: + case expr_binary_or: + case expr_binary_xor: + case expr_client_state: + case expr_gethostname: + case expr_concat_dclist: + return 100; + + case expr_equal: + case expr_not_equal: + case expr_regex_match: + case expr_iregex_match: + return 4; + + case expr_or: + case expr_and: + return 3; + + case expr_add: + case expr_subtract: + return 2; + + case expr_multiply: + case expr_divide: + case expr_remainder: + return 1; + } + return 100; +} + +int op_precedence (op1, op2) + enum expr_op op1, op2; +{ + return op_val (op1) - op_val (op2); +} + +enum expression_context expression_context (struct expression *expr) +{ + if (is_data_expression (expr)) + return context_data; + if (is_numeric_expression (expr)) + return context_numeric; + if (is_boolean_expression (expr)) + return context_boolean; + if (is_dns_expression (expr)) + return context_dns; + return context_any; +} + +enum expression_context op_context (op) + enum expr_op op; +{ + switch (op) { +/* XXX Why aren't these specific? */ + case expr_none: + case expr_match: + case expr_static: + case expr_check: + case expr_substring: + case expr_suffix: + case expr_lcase: + case expr_ucase: + case expr_concat: + case expr_encapsulate: + case expr_host_lookup: + case expr_not: + case expr_option: + case expr_hardware: + case expr_packet: + case expr_const_data: + case expr_extract_int8: + case expr_extract_int16: + case expr_extract_int32: + case expr_encode_int8: + case expr_encode_int16: + case expr_encode_int32: + case expr_const_int: + case expr_exists: + case expr_variable_exists: + case expr_known: + case expr_binary_to_ascii: + case expr_reverse: + case expr_filename: + case expr_sname: + case expr_pick_first_value: + case expr_host_decl_name: + case expr_config_option: + case expr_leased_address: + case expr_lease_time: + case expr_null: + case expr_variable_reference: + case expr_ns_add: + case expr_ns_delete: + case expr_ns_exists: + case expr_ns_not_exists: + case expr_dns_transaction: + case expr_arg: + case expr_funcall: + case expr_function: + case expr_gethostname: + case expr_concat_dclist: + return context_any; + + case expr_equal: + case expr_not_equal: + case expr_regex_match: + case expr_iregex_match: + return context_data; + + case expr_and: + return context_boolean; + + case expr_or: + return context_boolean; + + case expr_add: + case expr_subtract: + case expr_multiply: + case expr_divide: + case expr_remainder: + case expr_binary_and: + case expr_binary_or: + case expr_binary_xor: + case expr_client_state: + return context_numeric; + } + return context_any; +} + +int write_expression (file, expr, col, indent, firstp) + FILE *file; + struct expression *expr; + int col; + int indent; + int firstp; +{ + struct expression *e; + const char *s; + char obuf [65]; + int scol; + int width; + + /* If this promises to be a fat expression, start a new line. */ + if (!firstp && is_compound_expression (expr)) { + indent_spaces (file, indent); + col = indent; + } + + switch (expr -> op) { + case expr_none: + col = token_print_indent (file, col, indent, "", "", "null"); + break; + + case expr_check: + col = token_print_indent (file, col, indent, "", "", "check"); + col = token_print_indent_concat (file, col, indent, + " ", "", "\"", + expr -> data.check -> name, + "\"", (char *)0); + break; + + case expr_regex_match: + s = "~="; + goto binary; + + case expr_iregex_match: + s = "~~"; + goto binary; + + case expr_not_equal: + s = "!="; + goto binary; + + case expr_equal: + s = "="; + binary: + col = write_expression (file, expr -> data.equal [0], + col, indent, 1); + col = token_print_indent (file, col, indent, " ", " ", s); + col = write_expression (file, expr -> data.equal [1], + col, indent + 2, 0); + break; + + case expr_substring: + col = token_print_indent (file, col, indent, "", "", + "substring"); + col = token_print_indent (file, col, indent, " ", "", "("); + scol = col; + col = write_expression (file, expr -> data.substring.expr, + col, scol, 1); + col = token_print_indent (file, col, indent, "", " ", ","); + col = write_expression (file, expr -> data.substring.offset, + col, indent, 0); + col = token_print_indent (file, col, scol, "", " ", ","); + col = write_expression (file, expr -> data.substring.len, + col, scol, 0); + col = token_print_indent (file, col, indent, "", "", ")"); + break; + + case expr_suffix: + col = token_print_indent (file, col, indent, "", "", "suffix"); + col = token_print_indent (file, col, indent, " ", "", "("); + scol = col; + col = write_expression (file, expr -> data.suffix.expr, + col, scol, 1); + col = token_print_indent (file, col, scol, "", " ", ","); + col = write_expression (file, expr -> data.suffix.len, + col, scol, 0); + col = token_print_indent (file, col, indent, "", "", ")"); + break; + + case expr_lcase: + col = token_print_indent(file, col, indent, "", "", "lcase"); + col = token_print_indent(file, col, indent, " ", "", "("); + scol = col; + col = write_expression(file, expr->data.lcase, col, scol, 1); + col = token_print_indent(file, col, indent, "", "", ")"); + break; + + case expr_ucase: + col = token_print_indent(file, col, indent, "", "", "ucase"); + col = token_print_indent(file, col, indent, " ", "", "("); + scol = col; + col = write_expression(file, expr->data.ucase, col, scol, 1); + col = token_print_indent(file, col, indent, "", "", ")"); + break; + + case expr_concat: + e = expr; + col = token_print_indent (file, col, indent, "", "", + "concat"); + col = token_print_indent (file, col, indent, " ", "", "("); + scol = col; + firstp = 1; + concat_again: + col = write_expression (file, e -> data.concat [0], + col, scol, firstp); + firstp = 0; + if (!e -> data.concat [1]) + goto no_concat_cdr; + col = token_print_indent (file, col, scol, "", " ", ","); + if (e -> data.concat [1] -> op == expr_concat) { + e = e -> data.concat [1]; + goto concat_again; + } + col = write_expression (file, e -> data.concat [1], + col, scol, 0); + no_concat_cdr: + col = token_print_indent (file, col, indent, "", "", ")"); + break; + + case expr_host_lookup: + col = token_print_indent (file, col, indent, "", "", + "gethostbyname"); + col = token_print_indent (file, col, indent, " ", "", "("); + col = token_print_indent_concat + (file, col, indent, "", "", + "\"", expr -> data.host_lookup -> hostname, "\"", + (char *)0); + col = token_print_indent (file, col, indent, "", "", ")"); + break; + + case expr_add: + s = "+"; + goto binary; + + case expr_subtract: + s = "-"; + goto binary; + + case expr_multiply: + s = "*"; + goto binary; + + case expr_divide: + s = "/"; + goto binary; + + case expr_remainder: + s = "%"; + goto binary; + + case expr_binary_and: + s = "&"; + goto binary; + + case expr_binary_or: + s = "|"; + goto binary; + + case expr_binary_xor: + s = "^"; + goto binary; + + case expr_and: + s = "and"; + goto binary; + + case expr_or: + s = "or"; + goto binary; + + case expr_not: + col = token_print_indent (file, col, indent, "", " ", "not"); + col = write_expression (file, + expr -> data.not, col, indent + 2, 1); + break; + + case expr_option: + s = "option"; + + print_option_name: + col = token_print_indent (file, col, indent, "", "", s); + + if (expr -> data.option -> universe != &dhcp_universe) { + col = token_print_indent (file, col, indent, + " ", "", + (expr -> data.option -> + universe -> name)); + col = token_print_indent (file, col, indent, "", "", + "."); + col = token_print_indent (file, col, indent, "", "", + expr -> data.option -> name); + } else { + col = token_print_indent (file, col, indent, " ", "", + expr -> data.option -> name); + } + break; + + case expr_hardware: + col = token_print_indent (file, col, indent, "", "", + "hardware"); + break; + + case expr_packet: + col = token_print_indent (file, col, indent, "", "", + "packet"); + col = token_print_indent (file, col, indent, " ", "", "("); + scol = col; + col = write_expression (file, expr -> data.packet.offset, + col, indent, 1); + col = token_print_indent (file, col, scol, "", " ", ","); + col = write_expression (file, expr -> data.packet.len, + col, scol, 0); + col = token_print_indent (file, col, indent, "", "", ")"); + break; + + case expr_const_data: + col = token_indent_data_string (file, col, indent, "", "", + &expr -> data.const_data); + break; + + case expr_extract_int8: + width = 8; + extract_int: + col = token_print_indent (file, col, indent, "", "", + "extract-int"); + col = token_print_indent (file, col, indent, " ", "", "("); + scol = col; + col = write_expression (file, expr -> data.extract_int, + col, indent, 1); + col = token_print_indent (file, col, scol, "", " ", ","); + sprintf (obuf, "%d", width); + col = token_print_indent (file, col, scol, " ", "", obuf); + col = token_print_indent (file, col, indent, "", "", ")"); + break; + + case expr_extract_int16: + width = 16; + goto extract_int; + + case expr_extract_int32: + width = 32; + goto extract_int; + + case expr_encode_int8: + width = 8; + encode_int: + col = token_print_indent (file, col, indent, "", "", + "encode-int"); + col = token_print_indent (file, col, indent, " ", "", "("); + scol = col; + col = write_expression (file, expr -> data.extract_int, + col, indent, 1); + col = token_print_indent (file, col, scol, "", " ", ","); + sprintf (obuf, "%d", width); + col = token_print_indent (file, col, scol, " ", "", obuf); + col = token_print_indent (file, col, indent, "", "", + ")"); + break; + + case expr_encode_int16: + width = 16; + goto encode_int; + + case expr_encode_int32: + width = 32; + goto encode_int; + + case expr_const_int: + sprintf (obuf, "%lu", expr -> data.const_int); + col = token_print_indent (file, col, indent, "", "", obuf); + break; + + case expr_exists: + s = "exists"; + goto print_option_name; + + case expr_encapsulate: + col = token_print_indent (file, col, indent, "", "", + "encapsulate"); + col = token_indent_data_string (file, col, indent, " ", "", + &expr -> data.encapsulate); + break; + + case expr_known: + col = token_print_indent (file, col, indent, "", "", "known"); + break; + + case expr_reverse: + col = token_print_indent (file, col, indent, "", "", + "reverse"); + col = token_print_indent (file, col, indent, " ", "", "("); + scol = col; + col = write_expression (file, expr -> data.reverse.width, + col, scol, 1); + col = token_print_indent (file, col, scol, "", " ", ","); + col = write_expression (file, expr -> data.reverse.buffer, + col, scol, 0); + col = token_print_indent (file, col, indent, "", "", + ")"); + break; + + case expr_leased_address: + col = token_print_indent (file, col, indent, "", "", + "leased-address"); + break; + + case expr_client_state: + col = token_print_indent (file, col, indent, "", "", + "client-state"); + break; + + case expr_binary_to_ascii: + col = token_print_indent (file, col, indent, "", "", + "binary-to-ascii"); + col = token_print_indent (file, col, indent, " ", "", + "("); + scol = col; + col = write_expression (file, expr -> data.b2a.base, + col, scol, 1); + col = token_print_indent (file, col, scol, "", " ", + ","); + col = write_expression (file, expr -> data.b2a.width, + col, scol, 0); + col = token_print_indent (file, col, scol, "", " ", + ","); + col = write_expression (file, expr -> data.b2a.separator, + col, scol, 0); + col = token_print_indent (file, col, scol, "", " ", + ","); + col = write_expression (file, expr -> data.b2a.buffer, + col, scol, 0); + col = token_print_indent (file, col, indent, "", "", + ")"); + break; + + case expr_config_option: + s = "config-option"; + goto print_option_name; + + case expr_host_decl_name: + col = token_print_indent (file, col, indent, "", "", + "host-decl-name"); + break; + + case expr_pick_first_value: + e = expr; + col = token_print_indent (file, col, indent, "", "", + "concat"); + col = token_print_indent (file, col, indent, " ", "", + "("); + scol = col; + firstp = 1; + pick_again: + col = write_expression (file, + e -> data.pick_first_value.car, + col, scol, firstp); + firstp = 0; + /* We're being very lisp-like right now - instead of + representing this expression as (first middle . last) we're + representing it as (first middle last), which means that the + tail cdr is always nil. Apologies to non-wisp-lizards - may + this obscure way of describing the problem motivate you to + learn more about the one true computing language. */ + if (!e -> data.pick_first_value.cdr) + goto no_pick_cdr; + col = token_print_indent (file, col, scol, "", " ", + ","); + if (e -> data.pick_first_value.cdr -> op == + expr_pick_first_value) { + e = e -> data.pick_first_value.cdr; + goto pick_again; + } + col = write_expression (file, + e -> data.pick_first_value.cdr, + col, scol, 0); + no_pick_cdr: + col = token_print_indent (file, col, indent, "", "", + ")"); + break; + + case expr_lease_time: + col = token_print_indent (file, col, indent, "", "", + "lease-time"); + break; + + case expr_dns_transaction: + col = token_print_indent (file, col, indent, "", "", + "ns-update"); + col = token_print_indent (file, col, indent, " ", "", + "("); + scol = 0; + for (e = expr; + e && e -> op == expr_dns_transaction; + e = e -> data.dns_transaction.cdr) { + if (!scol) { + scol = col; + firstp = 1; + } else + firstp = 0; + col = write_expression (file, + e -> data.dns_transaction.car, + col, scol, firstp); + if (e -> data.dns_transaction.cdr) + col = token_print_indent (file, col, scol, + "", " ", ","); + } + if (e) + col = write_expression (file, e, col, scol, 0); + col = token_print_indent (file, col, indent, "", "", ")"); + break; + + case expr_ns_add: + col = token_print_indent (file, col, indent, "", "", + "update"); + col = token_print_indent (file, col, indent, " ", "", + "("); + scol = col; + sprintf (obuf, "%d", expr -> data.ns_add.rrclass); + col = token_print_indent (file, col, scol, "", "", obuf); + col = token_print_indent (file, col, scol, "", " ", + ","); + sprintf (obuf, "%d", expr -> data.ns_add.rrtype); + col = token_print_indent (file, col, scol, "", "", obuf); + col = token_print_indent (file, col, scol, "", " ", + ","); + col = write_expression (file, expr -> data.ns_add.rrname, + col, scol, 0); + col = token_print_indent (file, col, scol, "", " ", + ","); + col = write_expression (file, expr -> data.ns_add.rrdata, + col, scol, 0); + col = token_print_indent (file, col, scol, "", " ", + ","); + col = write_expression (file, expr -> data.ns_add.ttl, + col, scol, 0); + col = token_print_indent (file, col, indent, "", "", + ")"); + break; + + case expr_ns_delete: + col = token_print_indent (file, col, indent, "", "", + "delete"); + col = token_print_indent (file, col, indent, " ", "", + "("); + finish_ns_small: + scol = col; + sprintf (obuf, "%d", expr -> data.ns_add.rrclass); + col = token_print_indent (file, col, scol, "", "", obuf); + col = token_print_indent (file, col, scol, "", " ", + ","); + sprintf (obuf, "%d", expr -> data.ns_add.rrtype); + col = token_print_indent (file, col, scol, "", "", obuf); + col = token_print_indent (file, col, scol, "", " ", + ","); + col = write_expression (file, expr -> data.ns_add.rrname, + col, scol, 0); + col = token_print_indent (file, col, scol, "", " ", + ","); + col = write_expression (file, expr -> data.ns_add.rrdata, + col, scol, 0); + col = token_print_indent (file, col, indent, "", "", + ")"); + break; + + case expr_ns_exists: + col = token_print_indent (file, col, indent, "", "", + "exists"); + col = token_print_indent (file, col, indent, " ", "", + "("); + goto finish_ns_small; + + case expr_ns_not_exists: + col = token_print_indent (file, col, indent, "", "", + "not exists"); + col = token_print_indent (file, col, indent, " ", "", + "("); + goto finish_ns_small; + + case expr_static: + col = token_print_indent (file, col, indent, "", "", + "static"); + break; + + case expr_null: + col = token_print_indent (file, col, indent, "", "", "null"); + break; + + case expr_variable_reference: + col = token_print_indent (file, indent, indent, "", "", + expr -> data.variable); + break; + + case expr_variable_exists: + col = token_print_indent (file, indent, indent, "", "", + "defined"); + col = token_print_indent (file, col, indent, " ", "", "("); + col = token_print_indent (file, col, indent, "", "", + expr -> data.variable); + col = token_print_indent (file, col, indent, "", "", ")"); + break; + + case expr_gethostname: + col = token_print_indent(file, col, indent, "", "", + "gethostname()"); + break; + + case expr_funcall: + col = token_print_indent(file, indent, indent, "", "", + expr->data.funcall.name); + col = token_print_indent(file, col, indent, " ", "", "("); + + firstp = 1; + e = expr->data.funcall.arglist; + while (e != NULL) { + if (!firstp) + col = token_print_indent(file, col, indent, + "", " ", ","); + + col = write_expression(file, e->data.arg.val, col, + indent, firstp); + firstp = 0; + e = e->data.arg.next; + } + + col = token_print_indent(file, col, indent, "", "", ")"); + break; + + default: + log_fatal ("invalid expression type in print_expression: %d", + expr -> op); + } + return col; +} + +struct binding *find_binding (struct binding_scope *scope, const char *name) +{ + struct binding *bp; + struct binding_scope *s; + + for (s = scope; s; s = s -> outer) { + for (bp = s -> bindings; bp; bp = bp -> next) { + if (!strcasecmp (name, bp -> name)) { + return bp; + } + } + } + return (struct binding *)0; +} + +int free_bindings (struct binding_scope *scope, const char *file, int line) +{ + struct binding *bp, *next; + + for (bp = scope -> bindings; bp; bp = next) { + next = bp -> next; + if (bp -> name) + dfree (bp -> name, file, line); + if (bp -> value) + binding_value_dereference (&bp -> value, file, line); + dfree (bp, file, line); + } + scope -> bindings = (struct binding *)0; + return 1; +} + +int binding_scope_dereference (ptr, file, line) + struct binding_scope **ptr; + const char *file; + int line; +{ + struct binding_scope *binding_scope; + + if (!ptr || !*ptr) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + binding_scope = *ptr; + *ptr = (struct binding_scope *)0; + --binding_scope -> refcnt; + rc_register (file, line, ptr, + binding_scope, binding_scope -> refcnt, 1, RC_MISC); + if (binding_scope -> refcnt > 0) + return 1; + + if (binding_scope -> refcnt < 0) { + log_error ("%s(%d): negative refcnt!", file, line); +#if defined (DEBUG_RC_HISTORY) + dump_rc_history (binding_scope); +#endif +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + free_bindings (binding_scope, file, line); + if (binding_scope -> outer) + binding_scope_dereference (&binding_scope -> outer, MDL); + dfree (binding_scope, file, line); + return 1; +} + +int fundef_dereference (ptr, file, line) + struct fundef **ptr; + const char *file; + int line; +{ + struct fundef *bp; + struct string_list *sp, *next; + + if ((ptr == NULL) || (*ptr == NULL)) { + log_error ("%s(%d): null pointer", file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + bp = *ptr; + bp -> refcnt--; + rc_register (file, line, ptr, bp, bp -> refcnt, 1, RC_MISC); + if (bp -> refcnt < 0) { + log_error ("%s(%d): negative refcnt!", file, line); +#if defined (DEBUG_RC_HISTORY) + dump_rc_history (bp); +#endif +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + if (!bp -> refcnt) { + for (sp = bp -> args; sp; sp = next) { + next = sp -> next; + dfree (sp, file, line); + } + if (bp -> statements) + executable_statement_dereference (&bp -> statements, + file, line); + dfree (bp, file, line); + } + *ptr = (struct fundef *)0; + return 1; +} + +#if defined (NOTYET) /* Post 3.0 final. */ +int data_subexpression_length (int *rv, + struct expression *expr) +{ + int crhs, clhs, llhs, lrhs; + switch (expr -> op) { + case expr_substring: + if (expr -> data.substring.len && + expr -> data.substring.len -> op == expr_const_int) { + (*rv = + (int)expr -> data.substring.len -> data.const_int); + return 1; + } + return 0; + + case expr_packet: + case expr_suffix: + if (expr -> data.suffix.len && + expr -> data.suffix.len -> op == expr_const_int) { + (*rv = + (int)expr -> data.suffix.len -> data.const_int); + return 1; + } + return 0; + + case expr_lcase: + return data_subexpression_length(rv, expr->data.lcase); + + case expr_ucase: + return data_subexpression_length(rv, expr->data.ucase); + + case expr_concat: + clhs = data_subexpression_length (&llhs, + expr -> data.concat [0]); + crhs = data_subexpression_length (&lrhs, + expr -> data.concat [1]); + if (crhs == 0 || clhs == 0) + return 0; + *rv = llhs + lrhs; + return 1; + break; + + case expr_hardware: + return 0; + + case expr_const_data: + *rv = expr -> data.const_data.len; + return 2; + + case expr_reverse: + return data_subexpression_length (rv, + expr -> data.reverse.buffer); + + case expr_leased_address: + case expr_lease_time: + *rv = 4; + return 2; + + case expr_pick_first_value: + clhs = data_subexpression_length (&llhs, + expr -> data.concat [0]); + crhs = data_subexpression_length (&lrhs, + expr -> data.concat [1]); + if (crhs == 0 || clhs == 0) + return 0; + if (llhs > lrhs) + *rv = llhs; + else + *rv = lrhs; + return 1; + + case expr_binary_to_ascii: + case expr_config_option: + case expr_host_decl_name: + case expr_encapsulate: + case expr_filename: + case expr_sname: + case expr_host_lookup: + case expr_option: + case expr_none: + case expr_match: + case expr_check: + case expr_equal: + case expr_regex_match: + case expr_iregex_match: + case expr_and: + case expr_or: + case expr_not: + case expr_extract_int8: + case expr_extract_int16: + case expr_extract_int32: + case expr_encode_int8: + case expr_encode_int16: + case expr_encode_int32: + case expr_const_int: + case expr_exists: + case expr_known: + case expr_dns_transaction: + case expr_static: + case expr_ns_add: + case expr_ns_delete: + case expr_ns_exists: + case expr_ns_not_exists: + case expr_not_equal: + case expr_null: + case expr_variable_exists: + case expr_variable_reference: + case expr_arg: + case expr_funcall: + case expr_function: + case expr_add: + case expr_subtract: + case expr_multiply: + case expr_divide: + case expr_remainder: + case expr_binary_and: + case expr_binary_or: + case expr_binary_xor: + case expr_client_state: + case expr_gethostname: + return 0; + } + return 0; +} + +int expr_valid_for_context (struct expression *expr, + enum expression_context context) +{ + /* We don't know at parse time what type of value a function may + return, so we can't flag an error on it. */ + if (expr -> op == expr_funcall || + expr -> op == expr_variable_reference) + return 1; + + switch (context) { + case context_any: + return 1; + + case context_boolean: + if (is_boolean_expression (expr)) + return 1; + return 0; + + case context_data: + if (is_data_expression (expr)) + return 1; + return 0; + + case context_numeric: + if (is_numeric_expression (expr)) + return 1; + return 0; + + case context_dns: + if (is_dns_expression (expr)) { + return 1; + } + return 0; + + case context_data_or_numeric: + if (is_numeric_expression (expr) || + is_data_expression (expr)) { + return 1; + } + return 0; + + case context_function: + if (expr -> op == expr_function) + return 1; + return 0; + } + return 0; +} +#endif /* NOTYET */ + +struct binding *create_binding (struct binding_scope **scope, const char *name) +{ + struct binding *binding; + + if (!*scope) { + if (!binding_scope_allocate (scope, MDL)) + return (struct binding *)0; + } + + binding = find_binding (*scope, name); + if (!binding) { + binding = dmalloc (sizeof *binding, MDL); + if (!binding) + return (struct binding *)0; + + memset (binding, 0, sizeof *binding); + binding -> name = dmalloc (strlen (name) + 1, MDL); + if (!binding -> name) { + dfree (binding, MDL); + return (struct binding *)0; + } + strcpy (binding -> name, name); + + binding -> next = (*scope) -> bindings; + (*scope) -> bindings = binding; + } + + return binding; +} + + +int bind_ds_value (struct binding_scope **scope, + const char *name, + struct data_string *value) +{ + struct binding *binding; + + binding = create_binding (scope, name); + if (!binding) + return 0; + + if (binding -> value) + binding_value_dereference (&binding -> value, MDL); + + if (!binding_value_allocate (&binding -> value, MDL)) + return 0; + + data_string_copy (&binding -> value -> value.data, value, MDL); + binding -> value -> type = binding_data; + + return 1; +} + + +int find_bound_string (struct data_string *value, + struct binding_scope *scope, + const char *name) +{ + struct binding *binding; + + binding = find_binding (scope, name); + if (!binding || + !binding -> value || + binding -> value -> type != binding_data) + return 0; + + if (binding -> value -> value.data.terminated) { + data_string_copy (value, &binding -> value -> value.data, MDL); + } else { + if (buffer_allocate (&value->buffer, + binding->value->value.data.len, + MDL) == 0) { + return 0; + } + + memcpy (value -> buffer -> data, + binding -> value -> value.data.data, + binding -> value -> value.data.len); + value -> data = value -> buffer -> data; + value -> len = binding -> value -> value.data.len; + } + + return 1; +} + +int unset (struct binding_scope *scope, const char *name) +{ + struct binding *binding; + + binding = find_binding (scope, name); + if (binding) { + if (binding -> value) + binding_value_dereference + (&binding -> value, MDL); + return 1; + } + return 0; +} + +/*! + * \brief Adds two Dc-formatted lists into a single Dc-formatted list + * + * Given two data_strings containing compressed lists, it constructs a + * third data_string containing a single compressed list: + * + * 1. Decompressing the first list into a buffer + * 2. Decompressing the second list onto the end of the buffer + * 3. Compressing the buffer into the result + * + * If either list is empty, the result will be the equal to the compressed + * content of the non-empty list. If both lists are empty, the result will + * be an "empty" list: a 1 byte buffer containing 0x00. + * + * It relies on two functions to decompress and compress: + * + * - MRns_name_uncompress_list() - produces a null-terminated string of + * comma-separated domain-names from a buffer containing "Dc" formatted + * data + * + * - MRns_name_compress_list() - produces a buffer containing "Dc" formatted + * data from a null-terminated string containing comma-separated domain-names + * + * \param result data_string which will contain the combined list + * in Dc format + * \param list1 data_string containing first Dc formatted list + * \param list2 data_string containing second Dc formatted list + * \return 0 if there is an error, the length of the new list when successful + */ +int concat_dclists (struct data_string* result, + struct data_string* list1, + struct data_string* list2) +{ + char uncompbuf[32*NS_MAXCDNAME]; + char *uncomp = uncompbuf; + int uncomp_len = 0; + int compbuf_max = 0; + int list_len = 0; + int i; + + /* If not empty, uncompress first list into the uncompressed buffer */ + if (list1 && (list1->data) && (list1->len)) { + list_len = MRns_name_uncompress_list(list1->data, + list1->len, uncomp, + sizeof(uncompbuf)); + if (list_len < 0) { + log_error ("concat_dclists:" + " error decompressing domain list 1"); + return (0); + } + + uncomp_len = list_len; + uncomp += list_len; + } + + /* If not empty, uncompress second list into the uncompressed buffer */ + if (list2 && (list2->data) && (list2->len)) { + /* If first list wasn't empty, add a comma */ + if (uncomp_len > 0) { + *uncomp++ = ','; + uncomp_len++; + } + + list_len = MRns_name_uncompress_list(list2->data, list2->len, + uncomp, (sizeof(uncompbuf) + - uncomp_len)); + if (list_len < 0) { + log_error ("concat_dclists:" + " error decompressing domain list 2"); + return (0); + } + + uncomp_len += list_len; + uncomp += list_len; + } + + /* If both lists were empty, return an "empty" result */ + if (uncomp_len == 0) { + if (!buffer_allocate (&result->buffer, 1, MDL)) { + log_error ("concat_dclists: empty list allocate fail"); + result->len = 0; + return (0); + } + + result->len = 1; + result->data = result->buffer->data; + return (1); + } + + /* Estimate the buffer size needed for decompression. The largest + * decompression would if one where there are no repeated portions, + * (i.e. no compressions). Therefore that size should be the + * decompressed string length + 2 for each comma + a final null. Each + * dot gets replaced with a length byte and is accounted for in string + * length. Mininum length is * uncomp_len + 3. */ + compbuf_max = uncomp_len + 3; + uncomp = uncompbuf; + for (i = 0; i < uncomp_len; i++) + if (*uncomp++ == ',') + compbuf_max += 2; + + /* Allocate compression buffer based on estimated max */ + if (!buffer_allocate (&result->buffer, compbuf_max, MDL)) { + log_error ("concat_dclists: No memory for result"); + result->len = 0; + return (0); + } + + /* Compress the combined list into result */ + list_len = MRns_name_compress_list(uncompbuf, uncomp_len, + result->buffer->data, compbuf_max); + + if (list_len <= 0) { + log_error ("concat_dlists: error compressing result"); + data_string_forget(result, MDL); + result->len = 0; + return (0); + } + + /* Update result length to actual size */ + result->len = list_len; + result->data = result->buffer->data; + return (list_len); +} + +/* vim: set tabstop=8: */ diff --git a/common/upf.c b/common/upf.c new file mode 100644 index 0000000..34011eb --- /dev/null +++ b/common/upf.c @@ -0,0 +1,367 @@ +/* upf.c + + Ultrix PacketFilter interface code. */ + +/* + * Copyright (c) 2004,2007,2009,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "dhcpd.h" +#if defined (USE_UPF_SEND) || defined (USE_UPF_RECEIVE) +#include <sys/ioctl.h> +#include <sys/uio.h> + +#include <net/pfilt.h> +#include <netinet/in_systm.h> +#include "includes/netinet/ip.h" +#include "includes/netinet/udp.h" +#include "includes/netinet/if_ether.h" + +/* Reinitializes the specified interface after an address change. This + is not required for packet-filter APIs. */ + +#ifdef USE_UPF_SEND +void if_reinitialize_send (info) + struct interface_info *info; +{ +} +#endif + +#ifdef USE_UPF_RECEIVE +void if_reinitialize_receive (info) + struct interface_info *info; +{ +} +#endif + +/* Called by get_interface_list for each interface that's discovered. + Opens a packet filter for each interface and adds it to the select + mask. */ + +int if_register_upf (info) + struct interface_info *info; +{ + int sock; + char filename[50]; + int b; + struct endevp param; + + /* Open a UPF device */ + for (b = 0; 1; b++) { + /* %Audit% Cannot exceed 36 bytes. %2004.06.17,Safe% */ + sprintf(filename, "/dev/pf/pfilt%d", b); + + sock = open (filename, O_RDWR, 0); + if (sock < 0) { + if (errno == EBUSY) { + continue; + } else { + log_fatal ("Can't find free upf: %m"); + } + } else { + break; + } + } + + /* Set the UPF device to point at this interface. */ + if (ioctl (sock, EIOCSETIF, info -> ifp) < 0) + log_fatal ("Can't attach interface %s to upf device %s: %m", + info -> name, filename); + + /* Get the hardware address. */ + if (ioctl (sock, EIOCDEVP, ¶m) < 0) + log_fatal ("Can't get interface %s hardware address: %m", + info -> name); + + /* We only know how to do ethernet. */ + if (param.end_dev_type != ENDT_10MB) + log_fatal ("Invalid device type on network interface %s: %d", + info -> name, param.end_dev_type); + + if (param.end_addr_len != 6) + log_fatal ("Invalid hardware address length on %s: %d", + info -> name, param.end_addr_len); + + info -> hw_address.hlen = 7; + info -> hw_address.hbuf [0] = ARPHRD_ETHER; + memcpy (&info -> hw_address.hbuf [1], param.end_addr, 6); + + return sock; +} +#endif /* USE_UPF_SEND || USE_UPF_RECEIVE */ + +#ifdef USE_UPF_SEND +void if_register_send (info) + struct interface_info *info; +{ + /* If we're using the upf API for sending and receiving, + we don't need to register this interface twice. */ +#ifndef USE_UPF_RECEIVE + info -> wfdesc = if_register_upf (info, interface); +#else + info -> wfdesc = info -> rfdesc; +#endif + if (!quiet_interface_discovery) + log_info ("Sending on UPF/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} + +void if_deregister_send (info) + struct interface_info *info; +{ +#ifndef USE_UPF_RECEIVE + close (info -> wfdesc); +#endif + info -> wfdesc = -1; + if (!quiet_interface_discovery) + log_info ("Disabling output on UPF/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} +#endif /* USE_UPF_SEND */ + +#ifdef USE_UPF_RECEIVE +/* Packet filter program... + XXX Changes to the filter program may require changes to the constant + offsets used in if_register_send to patch the UPF program! XXX */ + + +void if_register_receive (info) + struct interface_info *info; +{ + int flag = 1; + u_int32_t addr; + struct enfilter pf; + u_int32_t bits; + + /* Open a UPF device and hang it on this interface... */ + info -> rfdesc = if_register_upf (info); + + /* Allow the copyall flag to be set... */ + if (ioctl(info -> rfdesc, EIOCALLOWCOPYALL, &flag) < 0) + log_fatal ("Can't set ALLOWCOPYALL: %m"); + + /* Clear all the packet filter mode bits first... */ + flag = (ENHOLDSIG | ENBATCH | ENTSTAMP | ENPROMISC | + ENNONEXCL | ENCOPYALL); + if (ioctl (info -> rfdesc, EIOCMBIC, &flag) < 0) + log_fatal ("Can't clear pfilt bits: %m"); + + /* Set the ENBATCH and ENCOPYALL bits... */ + bits = ENBATCH | ENCOPYALL; + if (ioctl (info -> rfdesc, EIOCMBIS, &bits) < 0) + log_fatal ("Can't set ENBATCH|ENCOPYALL: %m"); + + /* Set up the UPF filter program. */ + /* XXX Unlike the BPF filter program, this one won't work if the + XXX IP packet is fragmented or if there are options on the IP + XXX header. */ + pf.enf_Priority = 0; + pf.enf_FilterLen = 0; + + pf.enf_Filter [pf.enf_FilterLen++] = ENF_PUSHWORD + 6; + pf.enf_Filter [pf.enf_FilterLen++] = ENF_PUSHLIT + ENF_CAND; + pf.enf_Filter [pf.enf_FilterLen++] = htons (ETHERTYPE_IP); + pf.enf_Filter [pf.enf_FilterLen++] = ENF_PUSHLIT; + pf.enf_Filter [pf.enf_FilterLen++] = htons (IPPROTO_UDP); + pf.enf_Filter [pf.enf_FilterLen++] = ENF_PUSHWORD + 11; + pf.enf_Filter [pf.enf_FilterLen++] = ENF_PUSHLIT + ENF_AND; + pf.enf_Filter [pf.enf_FilterLen++] = htons (0xFF); + pf.enf_Filter [pf.enf_FilterLen++] = ENF_CAND; + pf.enf_Filter [pf.enf_FilterLen++] = ENF_PUSHWORD + 18; + pf.enf_Filter [pf.enf_FilterLen++] = ENF_PUSHLIT + ENF_CAND; + pf.enf_Filter [pf.enf_FilterLen++] = local_port; + + if (ioctl (info -> rfdesc, EIOCSETF, &pf) < 0) + log_fatal ("Can't install packet filter program: %m"); + if (!quiet_interface_discovery) + log_info ("Listening on UPF/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} + +void if_deregister_receive (info) + struct interface_info *info; +{ + close (info -> rfdesc); + info -> rfdesc = -1; + if (!quiet_interface_discovery) + log_info ("Disabling input on UPF/%s/%s%s%s", + info -> name, + print_hw_addr (info -> hw_address.hbuf [0], + info -> hw_address.hlen - 1, + &info -> hw_address.hbuf [1]), + (info -> shared_network ? "/" : ""), + (info -> shared_network ? + info -> shared_network -> name : "")); +} +#endif /* USE_UPF_RECEIVE */ + +#ifdef USE_UPF_SEND +ssize_t send_packet (interface, packet, raw, len, from, to, hto) + struct interface_info *interface; + struct packet *packet; + struct dhcp_packet *raw; + size_t len; + struct in_addr from; + struct sockaddr_in *to; + struct hardware *hto; +{ + unsigned hbufp = 0, ibufp = 0; + double hw [4]; + double ip [32]; + struct iovec iov [3]; + int result; + int fudge; + + if (!strcmp (interface -> name, "fallback")) + return send_fallback (interface, packet, raw, + len, from, to, hto); + + if (hto == NULL && interface->anycast_mac_addr.hlen) + hto = &interface->anycast_mac_addr; + + /* Assemble the headers... */ + assemble_hw_header (interface, (unsigned char *)hw, &hbufp, hto); + assemble_udp_ip_header (interface, + (unsigned char *)ip, &ibufp, from.s_addr, + to -> sin_addr.s_addr, to -> sin_port, + (unsigned char *)raw, len); + + /* Fire it off */ + iov [0].iov_base = ((char *)hw); + iov [0].iov_len = hbufp; + iov [1].iov_base = ((char *)ip); + iov [1].iov_len = ibufp; + iov [2].iov_base = (char *)raw; + iov [2].iov_len = len; + + result = writev(interface -> wfdesc, iov, 3); + if (result < 0) + log_error ("send_packet: %m"); + return result; +} +#endif /* USE_UPF_SEND */ + +#ifdef USE_UPF_RECEIVE +ssize_t receive_packet (interface, buf, len, from, hfrom) + struct interface_info *interface; + unsigned char *buf; + size_t len; + struct sockaddr_in *from; + struct hardware *hfrom; +{ + int nread; + int length = 0; + int offset = 0; + unsigned char ibuf [1500 + sizeof (struct enstamp)]; + int bufix = 0; + unsigned paylen; + + length = read (interface -> rfdesc, ibuf, sizeof ibuf); + if (length <= 0) + return length; + + bufix = sizeof (struct enstamp); + /* Decode the physical header... */ + offset = decode_hw_header (interface, ibuf, bufix, hfrom); + + /* If a physical layer checksum failed (dunno of any + physical layer that supports this, but WTH), skip this + packet. */ + if (offset < 0) { + return 0; + } + + bufix += offset; + length -= offset; + + /* Decode the IP and UDP headers... */ + offset = decode_udp_ip_header (interface, ibuf, bufix, + from, length, &paylen, 1); + + /* If the IP or UDP checksum was bad, skip the packet... */ + if (offset < 0) + return 0; + + bufix += offset; + length -= offset; + + if (length < paylen) + log_fatal("Internal inconsistency at %s:%d.", MDL); + + /* Copy out the data in the packet... */ + memcpy (buf, &ibuf[bufix], paylen); + return paylen; +} + +int can_unicast_without_arp (ip) + struct interface_info *ip; +{ + return 1; +} + +int can_receive_unicast_unconfigured (ip) + struct interface_info *ip; +{ + return 1; +} + +int supports_multiple_interfaces (ip) + struct interface_info *ip; +{ + return 1; +} + +void maybe_setup_fallback () +{ + isc_result_t status; + struct interface_info *fbi = (struct interface_info *)0; + if (setup_fallback (&fbi, MDL)) { + if_register_fallback (fbi); + status = omapi_register_io_object ((omapi_object_t *)fbi, + if_readsocket, 0, + fallback_discard, 0, 0); + if (status != ISC_R_SUCCESS) + log_fatal ("Can't register I/O handle for %s: %s", + fbi -> name, isc_result_totext (status)); + interface_dereference (&fbi, MDL); + } +} +#endif |