diff options
Diffstat (limited to 'keama')
439 files changed, 31323 insertions, 0 deletions
diff --git a/keama/.gitignore b/keama/.gitignore new file mode 100644 index 00000000..deb1cb67 --- /dev/null +++ b/keama/.gitignore @@ -0,0 +1 @@ +keama diff --git a/keama/ChangeLog.md b/keama/ChangeLog.md new file mode 100644 index 00000000..fd386d9e --- /dev/null +++ b/keama/ChangeLog.md @@ -0,0 +1,18 @@ +* 3 [doc] fdupont + + New documentation including this file. + (Gitlab #34) + +* 2 [bug] fdupont + + Fixed dhcp4 option 67 wrong name. + (Gitlab #22) + +* 1 [func] fdupont + + Initial revision. + +LEGEND +* [bug] Bug fix. +* [doc] Update to documentation. +* [func] New feature. diff --git a/keama/Makefile.am b/keama/Makefile.am new file mode 100644 index 00000000..5630cc91 --- /dev/null +++ b/keama/Makefile.am @@ -0,0 +1,5 @@ +sbin_PROGRAMS = keama +keama_SOURCES = keama.c data.c conflex.c json.c confparse.c parse.c options.c +keama_SOURCES += reduce.c print.c eval.c +man_MANS = keama.8 +EXTRA_DIST = $(man_MANS) diff --git a/keama/Makefile.in b/keama/Makefile.in new file mode 100644 index 00000000..43e9bc26 --- /dev/null +++ b/keama/Makefile.in @@ -0,0 +1,721 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 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 = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +sbin_PROGRAMS = keama$(EXEEXT) +subdir = keama +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) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/includes/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man8dir)" +PROGRAMS = $(sbin_PROGRAMS) +am_keama_OBJECTS = keama.$(OBJEXT) data.$(OBJEXT) conflex.$(OBJEXT) \ + json.$(OBJEXT) confparse.$(OBJEXT) parse.$(OBJEXT) \ + options.$(OBJEXT) reduce.$(OBJEXT) print.$(OBJEXT) \ + eval.$(OBJEXT) +keama_OBJECTS = $(am_keama_OBJECTS) +keama_LDADD = $(LDADD) +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__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/conflex.Po ./$(DEPDIR)/confparse.Po \ + ./$(DEPDIR)/data.Po ./$(DEPDIR)/eval.Po ./$(DEPDIR)/json.Po \ + ./$(DEPDIR)/keama.Po ./$(DEPDIR)/options.Po \ + ./$(DEPDIR)/parse.Po ./$(DEPDIR)/print.Po \ + ./$(DEPDIR)/reduce.Po +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 = $(keama_SOURCES) +DIST_SOURCES = $(keama_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +man8dir = $(mandir)/man8 +NROFF = nroff +MANS = $(man_MANS) +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 +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +A = @A@ +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +ATF_BIN = @ATF_BIN@ +ATF_CFLAGS = @ATF_CFLAGS@ +ATF_LDFLAGS = @ATF_LDFLAGS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINDCONFIG = @BINDCONFIG@ +BINDDIR = @BINDDIR@ +BINDIOMUX = @BINDIOMUX@ +BINDLIBDNSDIR = @BINDLIBDNSDIR@ +BINDLIBIRSDIR = @BINDLIBIRSDIR@ +BINDLIBISCCFGDIR = @BINDLIBISCCFGDIR@ +BINDLIBISCDIR = @BINDLIBISCDIR@ +BINDLT = @BINDLT@ +BINDSRCDIR = @BINDSRCDIR@ +BINDSUBDIR = @BINDSUBDIR@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DHLIBS = @DHLIBS@ +DISTCHECK_ATF_CONFIGURE_FLAG = @DISTCHECK_ATF_CONFIGURE_FLAG@ +DISTCHECK_LIBBIND_CONFIGURE_FLAG = @DISTCHECK_LIBBIND_CONFIGURE_FLAG@ +DISTCHECK_LIBTOOL_CONFIGURE_FLAG = @DISTCHECK_LIBTOOL_CONFIGURE_FLAG@ +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@ +LDAP_LIBS = @LDAP_LIBS@ +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@ +Q = @Q@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +UNITTESTS = @UNITTESTS@ +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@ +pkgcfg_found = @pkgcfg_found@ +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@ +keama_SOURCES = keama.c data.c conflex.c json.c confparse.c parse.c \ + options.c reduce.c print.c eval.c +man_MANS = keama.8 +EXTRA_DIST = $(man_MANS) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign keama/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign keama/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__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-sbinPROGRAMS: $(sbin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-sbinPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(sbindir)" && rm -f $$files + +clean-sbinPROGRAMS: + -test -z "$(sbin_PROGRAMS)" || rm -f $(sbin_PROGRAMS) + +keama$(EXEEXT): $(keama_OBJECTS) $(keama_DEPENDENCIES) $(EXTRA_keama_DEPENDENCIES) + @rm -f keama$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(keama_OBJECTS) $(keama_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/conflex.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/confparse.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eval.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/json.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keama.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/options.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/print.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/reduce.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.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-man8: $(man_MANS) + @$(NORMAL_INSTALL) + @list1=''; \ + list2='$(man_MANS)'; \ + test -n "$(man8dir)" \ + && test -n "`echo $$list1$$list2`" \ + || exit 0; \ + echo " $(MKDIR_P) '$(DESTDIR)$(man8dir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(man8dir)" || exit 1; \ + { for i in $$list1; do echo "$$i"; done; \ + if test -n "$$list2"; then \ + for i in $$list2; do echo "$$i"; done \ + | sed -n '/\.8[a-z]*$$/p'; \ + fi; \ + } | while read p; do \ + if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; echo "$$p"; \ + done | \ + sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ + sed 'N;N;s,\n, ,g' | { \ + list=; while read file base inst; do \ + if test "$$base" = "$$inst"; then list="$$list $$file"; else \ + echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man8dir)/$$inst'"; \ + $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man8dir)/$$inst" || exit $$?; \ + fi; \ + done; \ + for i in $$list; do echo "$$i"; done | $(am__base_list) | \ + while read files; do \ + test -z "$$files" || { \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man8dir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(man8dir)" || exit $$?; }; \ + done; } + +uninstall-man8: + @$(NORMAL_UNINSTALL) + @list=''; test -n "$(man8dir)" || exit 0; \ + files=`{ for i in $$list; do echo "$$i"; done; \ + l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ + sed -n '/\.8[a-z]*$$/p'; \ + } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ + dir='$(DESTDIR)$(man8dir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) $(MANS) +installdirs: + for dir in "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man8dir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-sbinPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/conflex.Po + -rm -f ./$(DEPDIR)/confparse.Po + -rm -f ./$(DEPDIR)/data.Po + -rm -f ./$(DEPDIR)/eval.Po + -rm -f ./$(DEPDIR)/json.Po + -rm -f ./$(DEPDIR)/keama.Po + -rm -f ./$(DEPDIR)/options.Po + -rm -f ./$(DEPDIR)/parse.Po + -rm -f ./$(DEPDIR)/print.Po + -rm -f ./$(DEPDIR)/reduce.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-man + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-sbinPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: install-man8 + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/conflex.Po + -rm -f ./$(DEPDIR)/confparse.Po + -rm -f ./$(DEPDIR)/data.Po + -rm -f ./$(DEPDIR)/eval.Po + -rm -f ./$(DEPDIR)/json.Po + -rm -f ./$(DEPDIR)/keama.Po + -rm -f ./$(DEPDIR)/options.Po + -rm -f ./$(DEPDIR)/parse.Po + -rm -f ./$(DEPDIR)/print.Po + -rm -f ./$(DEPDIR)/reduce.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-man uninstall-sbinPROGRAMS + +uninstall-man: uninstall-man8 + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-sbinPROGRAMS cscopelist-am ctags ctags-am \ + distclean distclean-compile distclean-generic distclean-tags \ + distdir dvi dvi-am html html-am info info-am install \ + install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-man8 install-pdf install-pdf-am install-ps \ + install-ps-am install-sbinPROGRAMS install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-man uninstall-man8 \ + uninstall-sbinPROGRAMS + +.PRECIOUS: Makefile + + +# 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/keama/README.md b/keama/README.md new file mode 100644 index 00000000..4028fcba --- /dev/null +++ b/keama/README.md @@ -0,0 +1,53 @@ +# KEA Migration Assistant Short Guide. + +The KEA Migration Assistant (aka _keama_) is an experimental tool +which helps to translate ISC DHCP configurations to Kea. + +## How to get last sources + +From time to time the _keama_ is upgraded for bug fixes, support of +new or not yet ISC DHCP features or more likely support of new KEA +features. + +As now _keama_ is included in ISC DHCP the most recent code can be +found with the most recent ISC DHCP code in the master branch of the +gitlab repository. + +## How to build and install + +After the ISC DHCP build go to the keama directory and type: +```console +make +``` +To install it: +```console +make install +``` + +## Known limitations + +_keama_ uses a subset of the ISC DHCP configuration file parser with a lot +of sanaity checks removed so it does not know how to handle an incorrect +ISC DHCP configuration file and eventually can even crash on it. + +ISC DHCP and KEA have different models for many things, for instance +ISC DHCP supports the failover protocol when KEA supports High Availability. +In some cases _keama_ tries to cope with that, for instance for host +reservations which are global in ISC DHCP and by default per subnet in KEA. + +## How to use + +The manual explains how parameters guide _keama_ choices for lifetimes, +name literals, host reservation scope, etc. Directives were added to +the ISC DHCP syntax (they are valid but ignored) for options. + +Each time _keama_ finds a feature it can't translate it emits a comment +with a reference to the feature description in a kea (not isc dhcp) gitlab +issue in the "ISC DHCP Migration" milestone. The number of reports is +returned by _keama_ when it exits. + +## How to help + +If you have configuration patterns you would like to see supported +by Keama please feel free to reach out to us. We are always looking +to improve the tool. diff --git a/keama/REAME.md b/keama/REAME.md new file mode 100644 index 00000000..5d1243f1 --- /dev/null +++ b/keama/REAME.md @@ -0,0 +1,53 @@ +# KEA Migration Assistant Short Guide. + +The KEA Migration Assistant (aka _keama_) is an experimental tool +which helps to translate ISC DHCP configurations to Kea. + +## How to get last sources + +From time to time the _keama_ is upgraded for bug fixes, support of +new or not yet ISC DHCP features or more likely support of new KEA +features. + +As now _keama_ is included in ISC DHCP the most recent code can be +found with the most recent ISC DHCP code in the master branch of the +gitlab repository. + +## How to build and install + +After the ISC DHCP build go to the keama directory and type: +```console +make +``` +To install it: +```console +make install +``` + +## Known limitations + +_keama_ uses a subset of the ISC DHCP configuration file parser with a lot +of sanaity checks removed so it does not know how to handle an incorrect +ISC DHCP configuration file and eventually can even crash on it. + +ISC DHCP and KEA have different models for many things, for instance +ISC DHCP supports the failover protocol when KEA supports High Availability. +In some cases _keama_ tries to cope with that, for instance for host +reservations which are global in ISC DHCP and by default per subnet in KEA. + +## How to use + +The manual explains how parameters guide _keama_ choices for lifetimes, +name literals, host reservation scope, etc. Directives were added to +the ISC DHCP syntax (they are valid but ignored) for options. + +Each time _keama_ finds a feature it can't translate it emits a comment +with a reference to the feature description in a kea (not isc dhcp) gitlab +issue in the "ISC DHCP Migration" milestone. The number of reports is +returned by _keama_ when it exits. + +## How to help + +We already collected a lot of ISC DHCP configurations used in production +but you can supply new ones, in particular if they use a common +paradigm which can be easily translated but not (yet) supported. diff --git a/keama/conflex.c b/keama/conflex.c new file mode 100644 index 00000000..143487b0 --- /dev/null +++ b/keama/conflex.c @@ -0,0 +1,1550 @@ +/* + * Copyright (c) 2017 by Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +/* From common/conflex.c */ + +#include "keama.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <assert.h> +#include <ctype.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.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); + +struct parse * +new_parse(int file, char *inbuf, size_t buflen, const char *name, int eolp) +{ + struct parse *tmp; + + tmp = (struct parse *)malloc(sizeof(struct parse)); + assert(tmp != NULL); + memset(tmp, 0, sizeof(struct parse)); + + TAILQ_INSERT_TAIL(&parses, tmp); + + 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; + TAILQ_INIT(&tmp->comments); + + if (inbuf != NULL) { + tmp->inbuf = inbuf; + tmp->buflen = buflen; + tmp->bufsiz = 0; + } else { + struct stat sb; + + if (fstat(file, &sb) < 0) { + fprintf(stderr, "can't stat input\n"); + exit(1); + } + + if (sb.st_size == 0) + return tmp; + + 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) { + fprintf(stderr, "can't map input\n"); + exit(1); + } + } + + return tmp; +} + +void +end_parse(struct parse *cfile) +{ + /* "Memory" config files have no file. */ + if (cfile->file != -1) { + munmap(cfile->inbuf, cfile->bufsiz); + close(cfile->file); + } + + while (TAILQ_NEXT(cfile) != NULL) { + struct parse *saved_state; + + saved_state = TAILQ_NEXT(cfile); + TAILQ_REMOVE(&parses, saved_state); + free(saved_state); + } + + cfile->stack_size = 0; + if (cfile->stack != NULL) + free(cfile->stack); + cfile->stack = NULL; + TAILQ_REMOVE(&parses, cfile); + free(cfile); +} + +/* + * Save the current state of the parser. + * + * Only one state may be saved. Any previous saved state is + * lost. + */ +void +save_parse_state(struct parse *cfile) { + struct parse *tmp; + + /* + * Free any previous saved states. + */ + while (TAILQ_NEXT(cfile) != NULL) { + struct parse *saved_state; + + saved_state = TAILQ_NEXT(cfile); + TAILQ_REMOVE(&parses, saved_state); + free(saved_state); + } + + /* + * Save our current state. + */ + tmp = (struct parse *)malloc(sizeof(struct parse)); + if (tmp == NULL) + parse_error(cfile, "can't allocate state to be saved"); + memset(tmp, 0, sizeof(struct parse)); + /* save up to comments field */ + memcpy(tmp, cfile, (size_t)&(((struct parse *)0)->comments)); + TAILQ_INSERT_AFTER(&parses, cfile, tmp); +} + +/* + * Return the parser to the previous saved state. + * + * You must call save_parse_state() every time before calling + * restore_parse_state(). + */ +void +restore_parse_state(struct parse *cfile) { + struct parse *saved_state; + + if (TAILQ_NEXT(cfile) == NULL) + parse_error(cfile, "can't find saved state"); + + saved_state = TAILQ_NEXT(cfile); + TAILQ_REMOVE(&parses, saved_state); + /* restore up to comments field */ + memcpy(cfile, saved_state, (size_t)&(((struct parse *)0)->comments)); + free(saved_state); +} + +static int +get_char(struct parse *cfile) +{ + /* My kingdom for WITH... */ + int c; + + if (cfile->bufix == cfile->buflen) { + c = EOF; + } 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; + + for (;;) { + 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; + } + } + 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; + 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; + 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); +} + +/* + * The comment up to but not including EOL is saved. + */ + +static void +skip_to_eol(struct parse *cfile) +{ + char buf[128]; + unsigned cc = 0; + struct comment *comment; + + memset(buf, 0, sizeof(buf)); + buf[0] = '#'; + for (;;) { + int c; + + c = get_char(cfile); + if (c == EOF) + break; + if (c == EOL) { + break; + } + if (++cc < sizeof(buf) - 1) + buf[cc] = c; + } + comment = createComment(buf); + TAILQ_INSERT_TAIL(&cfile->comments, comment); +} + +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_error(cfile, + "whitespace too long, buffer overflow."); + } + cfile->tokbuf[ofs++] = c; + c = get_char(cfile); + if (c == EOF) + return END_OF_FILE; + } 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(struct parse *cfile) +{ + unsigned 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_error(cfile, "eof in string constant"); + 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_error(cfile, + "invalid hex digit: %x", + c); + 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_error(cfile, + "invalid octal digit %x", + c); + 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_error(cfile, + "string constant larger than internal buffer"); + cfile->tokbuf[i] = 0; + cfile->tlen = i; + cfile->tval = cfile->tokbuf; + return STRING; +} + +static enum dhcp_token +read_number(int c, struct parse *cfile) +{ + unsigned 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: + parse_error(cfile, + "read_number(): impossible case"); + } + + cfile->tokbuf[i] = c; + } + + if (i == sizeof(cfile->tokbuf)) + parse_error(cfile, + "numeric token larger than internal buffer"); + + 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(int c, struct parse *cfile) +{ + unsigned 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_error(cfile, "token larger than internal buffer"); + 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 (!strcasecmp(atom + 1, "uthoring-byte-order")) + return AUTHORING_BYTE_ORDER; + 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; + if (!strcasecmp(atom+1, "ig-endian")) { + return TOKEN_BIG_ENDIAN; + } + 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, "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; + if (!strcasecmp(atom + 1, "ex")) { + return TOKEN_HEX; + } + 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; + if (!strcasecmp (atom + 1, "ey-algorithm")) + return KEY_ALGORITHM; + 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; + } + if (!strcasecmp(atom+1, "ittle-endian")) { + return TOKEN_LITTLE_ENDIAN; + } + if (!strcasecmp(atom + 1, "ease-id-format")) { + return LEASE_ID_FORMAT; + } + 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, "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; + if (!strcasecmp(atom + 1, "ctal")) { + return TOKEN_OCTAL; + } + break; + case 'p': + if (!strcasecmp(atom + 1, "arse-vendor-option")) + return PARSE_VENDOR_OPT; + 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, "ool6")) + return POOL6; + 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, "pdate")) + return UPDATE; + break; + case 'v': + if (!strcasecmp(atom + 1, "6relay")) + return V6RELAY; + if (!strcasecmp(atom + 1, "6relopt")) + return V6RELOPT; + 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/keama/confparse.c b/keama/confparse.c new file mode 100644 index 00000000..5b916064 --- /dev/null +++ b/keama/confparse.c @@ -0,0 +1,4992 @@ +/* + * Copyright (c) 2017, 2018 by Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +/* From server/confpars.c */ + +#include "keama.h" + +#include <sys/errno.h> +#include <arpa/inet.h> +#include <assert.h> +#include <ctype.h> +#include <fcntl.h> +#include <time.h> +#include <stdlib.h> +#include <string.h> + +/* Print failover stuff once */ +isc_boolean_t failover_once = ISC_TRUE; + +/* To manage host-reservation-identifiers */ +isc_boolean_t use_client_id = ISC_FALSE; +isc_boolean_t use_flex_id = ISC_FALSE; +isc_boolean_t use_hw_address = ISC_FALSE; + +/* option and relays used for flexible host identifier */ +const struct option *host_id_option = NULL; +int host_id_relays = 0; + +/* Simple or complex config */ +unsigned subnet_counter = 0; + +/* For subclass name generation */ +unsigned subclass_counter = 0; + +/* To map reservations to declared subnets */ +struct subnet { + struct element *subnet; + struct element *share; + struct string *addr; + struct string *mask; + TAILQ_ENTRY(subnet) next; +}; + +TAILQ_HEAD(subnets, subnet) known_subnets; + +/* To map pools to subnets inside a shared-network */ +struct range { + struct element *pool; + struct element *share; + struct string *low; + TAILQ_ENTRY(range) next; +}; + +TAILQ_HEAD(ranges, range) known_ranges; + +static void post_process_lifetimes(struct parse *); +static size_t post_process_reservations(struct parse *); +static void post_process_classes(struct parse *); +static void post_process_generated_classes(struct parse *); +static void check_depend(struct element *, struct element *); +static void post_process_option_definitions(struct parse *); +static void add_host_reservation_identifiers(struct parse *, const char *); +static void add_host_id_option(struct parse *, const struct option *, int); +static void subclass_inherit(struct parse *, struct element *, + struct element *); +static void add_match_class(struct parse *, struct element *, + struct element *); +static void option_data_derive(struct parse *, struct handle *, + struct handle *); +static void derive_classes(struct parse *, struct handle *, struct handle *); +static isc_boolean_t is_hexa_only(const char *, unsigned len); +static void new_network_interface(struct parse *, struct element *); +static struct string *addrmask(const struct string *, const struct string *); +static struct element *find_match(struct parse *, struct element *, + isc_boolean_t *); +static struct element *find_location(struct element *, struct range *); +static int get_prefix_length(const char *, const char *); +static struct element *get_class(struct parse *, struct element *); +static void concat_classes(struct parse *, struct element *, struct element *); +static void generate_class(struct parse *, struct element *, struct element *, + struct element *); + +static struct string *CLASS_ALL; +static struct string *CLASS_KNOWN; + +/* Add head config file comments to the DHCP server map */ + +size_t +conf_file_parse(struct parse *cfile) +{ + struct element *top; + struct element *dhcp; + size_t issues; + + TAILQ_INIT(&known_subnets); + TAILQ_INIT(&known_ranges); + CLASS_ALL = makeString(-1, "ALL"); + CLASS_KNOWN = makeString(-1, "KNOWN"); + + top = createMap(); + top->kind = TOPLEVEL; + TAILQ_CONCAT(&top->comments, &cfile->comments); + + dhcp = createMap(); + dhcp->kind = ROOT_GROUP; + (void) peek_token(NULL, NULL, cfile); + TAILQ_CONCAT(&dhcp->comments, &cfile->comments); + stackPush(cfile, dhcp); + assert(cfile->stack_top == 1); + cfile->stack[0] = top; + + if (local_family == AF_INET) + mapSet(top, dhcp, "Dhcp4"); + else if (local_family == AF_INET6) + mapSet(top, dhcp, "Dhcp6"); + else + parse_error(cfile, "address family is not set"); + + issues = conf_file_subparse(cfile, ROOT_GROUP); + + /* Add a warning when interfaces-config is not present */ + if (subnet_counter > 0) { + struct element *ifconf; + + ifconf = mapGet(cfile->stack[1], "interfaces-config"); + if (ifconf == NULL) { + struct comment *comment; + + comment = createComment("/// This configuration " + "declares some subnets but " + "has no interfaces-config"); + TAILQ_INSERT_TAIL(&cfile->stack[1]->comments, comment); + comment = createComment("/// Reference Kea #245"); + TAILQ_INSERT_TAIL(&cfile->stack[1]->comments, comment); + } + } + + post_process_lifetimes(cfile); + if (!global_hr) + issues += post_process_reservations(cfile); + post_process_classes(cfile); + post_process_generated_classes(cfile); + post_process_option_definitions(cfile); + + return issues; +} + +/* Lifetime post-processing */ +static void +post_process_lifetimes(struct parse *cfile) +{ + struct element *entry; + + entry = mapGet(cfile->stack[1], "valid-lifetime"); + if ((entry == NULL) && use_isc_lifetimes) { + struct comment *comment; + + /* DEFAULT_DEFAULT_LEASE_TIME is 43200 */ + entry = createInt(43200); + comment = createComment("/// Use ISC DHCP default lifetime"); + TAILQ_INSERT_TAIL(&entry->comments, comment); + mapSet(cfile->stack[1], entry, "valid-lifetime"); + } + + entry = mapGet(cfile->stack[1], "min-valid-lifetime"); + if ((entry == NULL) && use_isc_lifetimes) { + struct comment *comment; + + /* DEFAULT_MIN_LEASE_TIME is 300 */ + entry = createInt(300); + comment = createComment("/// Use ISC DHCP min lifetime"); + TAILQ_INSERT_TAIL(&entry->comments, comment); + mapSet(cfile->stack[1], entry, "min-valid-lifetime"); + } + + entry = mapGet(cfile->stack[1], "max-valid-lifetime"); + if ((entry == NULL) && use_isc_lifetimes) { + struct comment *comment; + + /* DEFAULT_MAX_LEASE_TIME is 86400 */ + entry = createInt(86400); + comment = createComment("/// Use ISC DHCP max lifetime"); + TAILQ_INSERT_TAIL(&entry->comments, comment); + mapSet(cfile->stack[1], entry, "max-valid-lifetime"); + } + + /* Done for DHCPv4 */ + if (local_family == AF_INET) + return; + + /* There is no builtin default for preferred-lifetime, + nor min/max values in ISC DHCP. */ +} + +/* Reservation post-processing */ + +static size_t +post_process_reservations(struct parse *cfile) +{ + struct element *hosts; + struct element *orphans; + struct element *host; + struct element *where; + struct element *dest; + isc_boolean_t used_heuristic; + size_t issues; + + issues = 0; + hosts = mapGet(cfile->stack[1], "reservations"); + if ((hosts == NULL) || global_hr) + return issues; + mapRemove(cfile->stack[1], "reservations"); + orphans = createList(); + orphans->kind = HOST_DECL; + while (listSize(hosts) > 0) { + host = listGet(hosts, 0); + listRemove(hosts, 0); + used_heuristic = ISC_FALSE; + where = find_match(cfile, host, &used_heuristic); + if (where == cfile->stack[1]) + dest = orphans; + else + dest = mapGet(where, "reservations"); + if (dest == NULL) { + dest = createList(); + dest->kind = HOST_DECL; + mapSet(where, dest, "reservations"); + } + listPush(dest, host); + } + if (listSize(orphans) > 0) { + struct comment *comment; + + comment = createComment("/// Orphan reservations"); + TAILQ_INSERT_TAIL(&orphans->comments, comment); + comment = createComment("/// Kea reservations are per subnet"); + TAILQ_INSERT_TAIL(&orphans->comments, comment); + comment = createComment("/// Reference Kea #231"); + TAILQ_INSERT_TAIL(&orphans->comments, comment); + orphans->skip = ISC_TRUE; + issues++; + mapSet(cfile->stack[1], orphans, "reservations"); + } + return issues; +} + +/* Cleanup classes */ + +static void +post_process_classes(struct parse *cfile) +{ + struct element *classes; + struct element *class; + struct element *name; + struct element *entry; + struct element *reduced; + struct string *msg; + struct comment *comment; + isc_boolean_t lose; + size_t i; + + classes = mapGet(cfile->stack[1], "client-classes"); + if ((classes == NULL) || (listSize(classes) == 0)) + return; + for (i = 0; i < listSize(classes); i++) { + class = listGet(classes, i); + if ((class == NULL) || (class->type != ELEMENT_MAP)) + parse_error(cfile, "null global class at %i", + (unsigned)i); + name = mapGet(class, "name"); + if ((name == NULL) || (name->type != ELEMENT_STRING)) + parse_error(cfile, "global class at %u " + "without a name", (unsigned)i); + if (!mapContains(class, "super")) + goto cleanup_superclass; + + /* cleanup subclass */ + mapRemove(class,"super"); + entry = mapGet(class, "string"); + if (entry != NULL) { + if (entry->type != ELEMENT_STRING) + parse_error(cfile, "subclass %s has " + "a bad string selector", + stringValue(name)->content); + msg = makeString(-1, "/// subclass selector "); + appendString(msg, "'"); + concatString(msg, stringValue(entry)); + appendString(msg, "'"); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&class->comments, comment); + mapRemove(class, "string"); + continue; + } + entry = mapGet(class, "binary"); + if (entry == NULL) + parse_error(cfile, "subclass %s has no selector", + stringValue(name)->content); + msg = makeString(-1, "/// subclass selector 0x"); + concatString(msg, stringValue(entry)); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&class->comments, comment); + mapRemove(class, "binary"); + + cleanup_superclass: + /* cleanup superclass */ + entry = mapGet(class, "spawning"); + if (entry == NULL) + goto cleanup_class; + if (entry->type != ELEMENT_BOOLEAN) + parse_error(cfile, "superclass %s has bad " + "spawning flag", + stringValue(name)->content); + if (boolValue(entry)) { + msg = makeString(-1, "/// Spawning classes " + "are not supported by Kea"); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&class->comments, comment); + msg = makeString(-1, "/// Reference Kea #248"); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&class->comments, comment); + msg = makeString(-1, "/// spawn with: "); + } else + msg = makeString(-1, "/// match: "); + entry = mapGet(class, "submatch"); + + if (entry == NULL) + parse_error(cfile, "superclass %s has no submatch", + stringValue(name)->content); + lose = ISC_FALSE; + appendString(msg, print_data_expression(entry, &lose)); + if (!lose) { + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&class->comments, comment); + mapRemove(class, "spawning"); + mapRemove(class, "submatch"); + } + + cleanup_class: + /* cleanup class */ + entry = mapGet(class, "match-if"); + if (entry == NULL) + continue; + reduced = mapGet(class, "test"); + lose = ISC_FALSE; + if (reduced != NULL) + msg = makeString(-1, "/// from: match if "); + else + msg = makeString(-1, "/// match if "); + appendString(msg, print_boolean_expression(entry, &lose)); + if (!lose) { + comment = createComment(msg->content); + if (reduced != NULL) { + TAILQ_INSERT_TAIL(&reduced->comments, comment); + mapRemove(class, "match-if"); + continue; + } + TAILQ_INSERT_TAIL(&entry->comments, comment); + } + } +} + +/* Move generated client classes to the end of client class list */ + +static void +post_process_generated_classes(struct parse *cfile) +{ + struct element *generated; + struct element *classes; + struct element *class; + + generated = mapGet(cfile->stack[1], "generated-classes"); + if (generated == NULL) + return; + mapRemove(cfile->stack[1], "generated-classes"); + if (listSize(generated) == 0) + return; + classes = mapGet(cfile->stack[1], "client-classes"); + if (classes == NULL) { + classes = createList(); + mapSet(cfile->stack[1], classes, "client-classes"); + } + + while (listSize(generated) > 0) { + class = listGet(generated, 0); + listRemove(generated, 0); + check_depend(class, classes); + listPush(classes, class); + } +} + +static void +check_depend(struct element *class, struct element *classes) +{ + struct element *list; + + if (!mapContains(class, "depend")) + return; + list = mapGet(class, "depend"); + mapRemove(class, "depend"); + while (listSize(list) > 0) { + struct element *depend; + struct string *dname; + struct string *msg; + struct comment *comment; + isc_boolean_t found; + size_t i; + + depend = listGet(list, 0); + listRemove(list, 0); + assert(depend != NULL); + assert(depend->type == ELEMENT_STRING); + dname = stringValue(depend); + if (eqString(dname, CLASS_ALL) || + eqString(dname, CLASS_KNOWN)) + continue; + found = ISC_FALSE; + for (i = 0; i < listSize(classes); i++) { + struct element *item; + struct element *name; + + item = listGet(classes, i); + assert(item != NULL); + assert(item->type == ELEMENT_MAP); + name = mapGet(item, "name"); + if (name == NULL) + continue; + assert(name->type == ELEMENT_STRING); + if (eqString(stringValue(name), dname)) { + found = ISC_TRUE; + break; + } + } + if (found) + continue; + msg = makeString(-1, "/// Depend on missing '"); + concatString(msg, dname); + appendString(msg, "' class"); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&class->comments, comment); + class->skip = ISC_TRUE; + } +} + +static void +post_process_option_definitions(struct parse *cfile) +{ + struct element *opt_def; + struct element *def, *ndef; + + opt_def = mapGet(cfile->stack[1], "option-def"); + if (opt_def == NULL) + return; + TAILQ_FOREACH_SAFE(def, &opt_def->value.list_value, ndef) { + if (mapContains(def, "no-export")) + TAILQ_REMOVE(&opt_def->value.list_value, def); + } +} + +void +read_conf_file(struct parse *parent, const char *filename, int group_type) +{ + int file; + struct parse *cfile; + struct string *msg; + struct comment *comment; + size_t amount = parent->stack_size * sizeof(struct element *); + + if ((file = open (filename, O_RDONLY)) < 0) + + parse_error(parent, "Can't open %s: %s", + filename, strerror(errno)); + + cfile = new_parse(file, NULL, 0, filename, 0); + if (cfile == NULL) + parse_error(parent, "Can't create new parse structure"); + + cfile->stack = (struct element **)malloc(amount); + if (cfile->stack == NULL) + parse_error(parent, "Can't create new element stack"); + memcpy(cfile->stack, parent->stack, amount); + cfile->stack_size = parent->stack_size; + cfile->stack_top = parent->stack_top; + cfile->issue_counter = parent->issue_counter; + + msg = makeString(-1, "/// Begin file "); + concatString(msg, makeString(-1, filename)); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&cfile->comments, comment); + + conf_file_subparse(cfile, group_type); + + amount = cfile->stack_size * sizeof(struct element *); + if (cfile->stack_size > parent->stack_size) { + parent->stack = + (struct element **)realloc(parent->stack, amount); + if (parent->stack == NULL) + parse_error(cfile, "can't resize element stack"); + } + memcpy(parent->stack, cfile->stack, amount); + parent->stack_size = cfile->stack_size; + parent->stack_top = cfile->stack_top; + parent->issue_counter = cfile->issue_counter; + msg = makeString(-1, "/// End file "); + concatString(msg, makeString(-1, filename)); + comment= createComment(msg->content); + TAILQ_INSERT_TAIL(&parent->comments, comment); + end_parse(cfile); +} + +/* conf-file :== parameters declarations END_OF_FILE + parameters :== <nil> | parameter | parameters parameter + declarations :== <nil> | declaration | declarations declaration */ + +size_t +conf_file_subparse(struct parse *cfile, int type) +{ + const char *val; + enum dhcp_token token; + isc_boolean_t declaration = ISC_FALSE; + + for (;;) { + token = peek_token(&val, NULL, cfile); + if (token == END_OF_FILE) + break; + declaration = parse_statement(cfile, type, declaration); + } + skip_token(&val, NULL, cfile); + + return cfile->issue_counter; +} + +/* statement :== parameter | declaration | PERCENT directive + + parameter :== DEFAULT_LEASE_TIME lease_time + | MAX_LEASE_TIME lease_time + | DYNAMIC_BOOTP_LEASE_CUTOFF date + | DYNAMIC_BOOTP_LEASE_LENGTH lease_time + | BOOT_UNKNOWN_CLIENTS boolean + | ONE_LEASE_PER_CLIENT boolean + | GET_LEASE_HOSTNAMES boolean + | USE_HOST_DECL_NAME boolean + | NEXT_SERVER ip-addr-or-hostname SEMI + | option_parameter + | SERVER-IDENTIFIER ip-addr-or-hostname SEMI + | FILENAME string-parameter + | SERVER_NAME string-parameter + | hardware-parameter + | fixed-address-parameter + | ALLOW allow-deny-keyword + | DENY allow-deny-keyword + | USE_LEASE_ADDR_FOR_DEFAULT_ROUTE boolean + | AUTHORITATIVE + | NOT AUTHORITATIVE + + declaration :== host-declaration + | group-declaration + | shared-network-declaration + | subnet-declaration + | VENDOR_CLASS class-declaration + | USER_CLASS class-declaration + | RANGE address-range-declaration */ + +isc_boolean_t +parse_statement(struct parse *cfile, int type, isc_boolean_t declaration) +{ + enum dhcp_token token; + const char *val; + struct element *hardware; + struct element *cache; + struct element *et; + isc_boolean_t lose; + isc_boolean_t known; + isc_boolean_t authoritative; + struct option *option; + size_t host_decl = 0; + size_t subnet = 0; + size_t i; + + token = peek_token(&val, NULL, cfile); + + switch (token) { + case INCLUDE: + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token != STRING) + parse_error(cfile, "filename string expected."); + read_conf_file(cfile, val, type); + parse_semi(cfile); + return 1; + + case HOST: + skip_token(&val, NULL, cfile); + if (type != HOST_DECL && type != CLASS_DECL) + parse_host_declaration(cfile); + else + parse_error(cfile, + "host declarations not allowed here."); + return 1; + + case GROUP: + skip_token(&val, NULL, cfile); + if (type != HOST_DECL && type != CLASS_DECL) + parse_group_declaration(cfile); + else + parse_error(cfile, + "group declarations not allowed here."); + return 1; + + case SHARED_NETWORK: + skip_token(&val, NULL, cfile); + if (type == SHARED_NET_DECL || + type == HOST_DECL || + type == SUBNET_DECL || + type == CLASS_DECL) + parse_error(cfile, "shared-network parameters not %s.", + "allowed here"); + parse_shared_net_declaration(cfile); + return 1; + + case SUBNET: + case SUBNET6: + skip_token(&val, NULL, cfile); + if (type == HOST_DECL || type == SUBNET_DECL || + type == CLASS_DECL) + parse_error(cfile, + "subnet declarations not allowed here."); + + if (token == SUBNET) + parse_subnet_declaration(cfile); + else + parse_subnet6_declaration(cfile); + return 1; + + case VENDOR_CLASS: + case USER_CLASS: + case CLASS: + case SUBCLASS: + skip_token(&val, NULL, cfile); + if (token == VENDOR_CLASS) + parse_error(cfile, "obsolete 'vendor-class' " + "declaration"); + if (token == USER_CLASS) + parse_error(cfile, "obsolete 'user-class' " + "declaration"); + if (type == CLASS_DECL) + parse_error(cfile, + "class declarations not allowed here."); + parse_class_declaration(cfile, token == CLASS + ? CLASS_TYPE_CLASS + : CLASS_TYPE_SUBCLASS); + return 1; + + case HARDWARE: + if (!use_hw_address) { + add_host_reservation_identifiers(cfile, + "hw-address"); + use_hw_address = ISC_TRUE; + } + + skip_token(&val, NULL, cfile); + if (!host_decl) { + for (i = cfile->stack_top; i > 0; --i) { + if (cfile->stack[i]->kind == HOST_DECL) { + host_decl = i; + break; + } + } + } + if (!host_decl) + parse_error(cfile, "hardware address parameter %s", + "not allowed here."); + if (mapContains(cfile->stack[host_decl], "hw-address")) + parse_error(cfile, "Host hardware address already " + "configured."); + hardware = parse_hardware_param(cfile); + mapSet(cfile->stack[host_decl], hardware, "hw-address"); + if (hardware->skip) + cfile->stack[host_decl]->skip = ISC_TRUE; + break; + + case FIXED_ADDR: + case FIXED_ADDR6: + skip_token(&val, NULL, cfile); + if (!host_decl) { + for (i = cfile->stack_top; i > 0; --i) { + if (cfile->stack[i]->kind == HOST_DECL) { + host_decl = i; + break; + } + } + } + if (!host_decl) + parse_error(cfile, + "fixed-address parameter not " + "allowed here."); + cache = parse_fixed_addr_param(cfile, token); + if (token == FIXED_ADDR) { + struct element *addr; + + if (mapContains(cfile->stack[host_decl], "ip-address")) + parse_error(cfile, "Only one fixed address " + "declaration per host."); + addr = listGet(cache, 0); + listRemove(cache, 0); + mapSet(cfile->stack[host_decl], addr, "ip-address"); + if (listSize(cache) > 0) { + cache->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(cfile->stack[host_decl], + cache, "extra-ip-addresses"); + } + } else { + if (mapContains(cfile->stack[host_decl], + "ip-addresses")) + parse_error(cfile, "Only one fixed address " + "declaration per host."); + mapSet(cfile->stack[host_decl], cache, "ip-addresses"); + } + break; + + case POOL: + skip_token(&val, NULL, cfile); + if (type == POOL_DECL) + parse_error(cfile, "pool declared within pool."); + if (type != SUBNET_DECL && type != SHARED_NET_DECL) + parse_error(cfile, "pool declared outside of network"); + parse_pool_statement(cfile, type); + + return declaration; + + case RANGE: + skip_token(&val, NULL, cfile); + if (!subnet) { + for (i = cfile->stack_top; i > 0; --i) { + if (cfile->stack[i]->kind == SUBNET_DECL) { + subnet = i; + break; + } + } + } + if (type != SUBNET_DECL || !subnet) + parse_error(cfile, + "range declaration not allowed here."); + parse_address_range(cfile, type, subnet); + return declaration; + + case RANGE6: + if (local_family != AF_INET6) + goto unknown; + skip_token(NULL, NULL, cfile); + if (!subnet) { + for (i = cfile->stack_top; i > 0; --i) { + if (cfile->stack[i]->kind == SUBNET_DECL) { + subnet = i; + break; + } + } + } + if ((type != SUBNET_DECL) || !subnet) + parse_error(cfile, + "range6 declaration not allowed here."); + parse_address_range6(cfile, type, subnet); + return declaration; + + case PREFIX6: + if (local_family != AF_INET6) + goto unknown; + skip_token(NULL, NULL, cfile); + if (!subnet) { + for (i = cfile->stack_top; i > 0; --i) { + if (cfile->stack[i]->kind == SUBNET_DECL) { + subnet = i; + break; + } + } + } + if ((type != SUBNET_DECL) || !subnet) + parse_error(cfile, + "prefix6 declaration not allowed here."); + parse_prefix6(cfile, type, subnet); + return declaration; + + case FIXED_PREFIX6: + if (local_family != AF_INET6) + goto unknown; + skip_token(&val, NULL, cfile); + if (!host_decl) { + for (i = cfile->stack_top; i > 0; --i) { + if (cfile->stack[i]->kind == HOST_DECL) { + host_decl = i; + break; + } + } + } + if (!host_decl) + parse_error(cfile, + "fixed-prefix6 declaration not " + "allowed here."); + parse_fixed_prefix6(cfile, host_decl); + break; + + case POOL6: + if (local_family != AF_INET6) + goto unknown; + skip_token(&val, NULL, cfile); + if (type == POOL_DECL) + parse_error(cfile, "pool6 declared within pool."); + if (type != SUBNET_DECL) + parse_error(cfile, + "pool6 declared outside of network"); + parse_pool6_statement(cfile, type); + + return declaration; + + case TOKEN_NOT: + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + switch (token) { + case AUTHORITATIVE: + authoritative = ISC_FALSE; + goto authoritative; + default: + parse_error(cfile, "expecting assertion"); + } + break; + + case AUTHORITATIVE: + skip_token(&val, NULL, cfile); + authoritative = ISC_TRUE; + authoritative: + if (type == HOST_DECL) + parse_error(cfile, "authority makes no sense here."); + if (!subnet) { + for (i = cfile->stack_top; i > 0; --i) { + int kind; + + kind = cfile->stack[i]->kind; + if ((kind == SUBNET_DECL) || + (kind == SHARED_NET_DECL) || + (kind == ROOT_GROUP)) { + subnet = i; + break; + } + } + } + if (!subnet) + parse_error(cfile, "can't find root group"); + if (local_family == AF_INET) { + cache = createBool(authoritative); + TAILQ_CONCAT(&cache->comments, &cfile->comments); + mapSet(cfile->stack[subnet], cache, "authoritative"); + } + parse_semi(cfile); + break; + + /* "server-identifier" is a special hack, equivalent to + "option dhcp-server-identifier". */ + case SERVER_IDENTIFIER: + option = option_lookup_code("dhcp", + DHO_DHCP_SERVER_IDENTIFIER); + assert(option); + skip_token(&val, NULL, cfile); + goto finish_option; + + case OPTION: + skip_token(&val, NULL, cfile); + token = peek_token(&val, NULL, cfile); + if (token == SPACE) { + if (type != ROOT_GROUP) + parse_error(cfile, + "option space definitions %s", + "may not be scoped."); + parse_option_space_decl(cfile); + return declaration; + } + + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_TRUE, &known); + token = peek_token(&val, NULL, cfile); + if (token == CODE) { + if (type != ROOT_GROUP) + parse_error(cfile, + "option definitions%s", + " may not be scoped."); + skip_token(&val, NULL, cfile); + + /* next function must deal with redefinitions */ + parse_option_code_definition(cfile, option); + return declaration; + } + /* If this wasn't an option code definition, don't + allow an unknown option. */ + if (!known) + parse_error(cfile, "unknown option %s.%s", + option->space->old, option->old); + finish_option: + parse_option_statement(NULL, cfile, option, + supersede_option_statement); + return declaration; + + case FAILOVER: + if (failover_once) + fprintf(stderr, "ignoring failover\n"); + failover_once = ISC_FALSE; + skip_to_semi(cfile); + break; + + case SERVER_DUID: + if (local_family != AF_INET6) + goto unknown; + parse_server_duid_conf(cfile); + break; + + case LEASE_ID_FORMAT: + token = next_token(&val, NULL, cfile); + /* ignore: ISC DHCP specific */ + break; + + case PERCENT: + skip_token(&val, NULL, cfile); + if (type != ROOT_GROUP) + parse_error(cfile, "directives are only supported " + "at toplevel"); + parse_directive(cfile); + return declaration; + + unknown: + skip_token(&val, NULL, cfile); + + default: + et = createMap(); + TAILQ_CONCAT(&et->comments, &cfile->comments); + lose = ISC_FALSE; + if (!parse_executable_statement(et, cfile, &lose, + context_any, ISC_TRUE)) { + if (!lose) { + if (declaration) + parse_error(cfile, + "expecting a declaration"); + else + parse_error(cfile, + "expecting a parameter %s", + "or declaration"); + } + return declaration; + } + if (mapSize(et) == 0) + return declaration; + + et->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(cfile->stack[cfile->stack_top], et, "statement"); + } + + return 0; +} + +/*! + * + * \brief Parse allow and deny statements + * + * This function handles the common processing code for permit and deny + * statements in the parse_pool_statement and parse_pool6_statement functions. + * + * The allow or deny token should already be consumed, this function expects + * one of the following: + * known-clients; + * unknown-clients; + * known clients; + * unknown clients; + * authenticated clients; + * unauthenticated clients; + * all clients; + * dynamic bootp clients; + * members of <class name>; + * after <date>; + * + * \param[in] cfile = the configuration file being parsed + * \param[in] permit_head = the head of the permit list (permit or prohibit) + * to which to attach the newly created permit structure + */ + +void +get_permit(struct parse *cfile, struct element *permit_head) +{ + enum dhcp_token token; + const char *val; + struct string *permit; + struct string *alias = NULL; + struct comment *comment = NULL; + struct element *member; + isc_boolean_t need_clients = ISC_TRUE; + isc_boolean_t negative = ISC_FALSE; + + token = next_token(&val, NULL, cfile); + switch (token) { + case UNKNOWN: + permit = CLASS_KNOWN; + negative = ISC_TRUE; + alias = makeString(-1, "unknown clients"); + break; + + case KNOWN_CLIENTS: + need_clients = ISC_FALSE; + permit = CLASS_KNOWN; + alias = makeString(-1, "known-clients"); + break; + + case UNKNOWN_CLIENTS: + need_clients = ISC_FALSE; + permit = CLASS_KNOWN; + negative = ISC_TRUE; + alias = makeString(-1, "unknown-clients"); + break; + + case KNOWN: + permit = CLASS_KNOWN; + alias = makeString(-1, "known clients"); + break; + + case AUTHENTICATED: + permit = CLASS_ALL; + alias = makeString(-1, "authenticated clients"); + negative = ISC_TRUE; + authenticated_clients: + comment = createComment("/// [un]authenticated-clients is " + "not supported by ISC DHCP and Kea"); + break; + + case UNAUTHENTICATED: + permit = CLASS_ALL; + alias = makeString(-1, "unauthenticated clients"); + goto authenticated_clients; + break; + + case ALL: + permit = CLASS_ALL; + alias = makeString(-1, "all clients"); + break; + + case DYNAMIC: + /* bootp is not supported by Kea so the dynamic bootp + * client set is the empty set. */ + if (next_token(&val, NULL, cfile) != TOKEN_BOOTP) + parse_error(cfile, "expecting \"bootp\""); + permit = CLASS_ALL; + negative = ISC_TRUE; + alias = makeString(-1, "dynamic bootp clients"); + cfile->issue_counter++; + comment = createComment("/// dynamic-bootp-client is not " + "supported by Kea"); + break; + + case MEMBERS: + /* we don't check the class... */ + need_clients = ISC_FALSE; + if (next_token(&val, NULL, cfile) != OF) + parse_error(cfile, "expecting \"of\""); + if (next_token(&val, NULL, cfile) != STRING) + parse_error(cfile, "expecting class name."); + permit = makeString(-1, val); + break; + + case AFTER: + /* don't use parse_date_code() */ + need_clients = ISC_FALSE; + permit = makeString(-1, "AFTER_"); + alias = makeString(-1, "after "); + while (peek_raw_token(NULL, NULL, cfile) != SEMI) { + next_raw_token(&val, NULL, cfile); + appendString(permit, val); + appendString(alias, val); + } + permit_head->skip = ISC_TRUE; + cfile->issue_counter++; + comment = createComment("/// after <date> is not yet " + "supported by Kea"); + break; + + default: + parse_error(cfile, "expecting permit type."); + } + + /* + * The need_clients flag is set if we are expecting the + * CLIENTS token + */ + if (need_clients && (next_token(&val, NULL, cfile) != CLIENTS)) + parse_error(cfile, "expecting \"clients\""); + member = createMap(); + mapSet(member, createString(permit), "class"); + mapSet(member, createBool(!negative), "way"); + if (alias != NULL) + mapSet(member, createString(alias), "real"); + if (comment != NULL) + TAILQ_INSERT_TAIL(&permit_head->comments, comment); + listPush(permit_head, member); + parse_semi(cfile); + + return; +} + +/*! + * + * \brief Parse a pool statement + * + * Pool statements are used to group declarations and permit & deny information + * with a specific address range. They must be declared within a shared network + * or subnet and there may be multiple pools withing a shared network or subnet. + * Each pool may have a different set of permit or deny options. + * + * \param[in] cfile = the configuration file being parsed + * \param[in] type = the type of the enclosing statement. This must be + * SHARED_NET_DECL or SUBNET_DECL for this function. + * + * \return + * void - This function either parses the statement and updates the structures + * or it generates an error message and possible halts the program if + * it encounters a problem. + */ +void +parse_pool_statement(struct parse *cfile, int type) +{ + enum dhcp_token token; + const char *val; + isc_boolean_t done = ISC_FALSE; + struct element *pool; + struct element *pools; + struct element *permit; + struct element *prohibit; + int declaration = 0; + unsigned range_counter = 0; + + pool = createMap(); + pool->kind = POOL_DECL; + TAILQ_CONCAT(&pool->comments, &cfile->comments); + + if (type != SUBNET_DECL && type != SHARED_NET_DECL) + parse_error(cfile, "Dynamic pools are only valid inside " + "subnet or shared-network statements."); + parse_lbrace(cfile); + + stackPush(cfile, pool); + type = POOL_DECL; + + permit = createList(); + prohibit = createList(); + + do { + token = peek_token(&val, NULL, cfile); + switch (token) { + case TOKEN_NO: + case FAILOVER: + if (failover_once) + fprintf(stderr, "ignoring failover\n"); + failover_once = ISC_FALSE; + skip_to_semi(cfile); + break; + + case RANGE: + skip_token(&val, NULL, cfile); + parse_address_range(cfile, type, cfile->stack_top); + range_counter++; + break; + + case ALLOW: + skip_token(&val, NULL, cfile); + get_permit(cfile, permit); + break; + + case DENY: + skip_token(&val, NULL, cfile); + get_permit(cfile, prohibit); + break; + + case RBRACE: + skip_token(&val, NULL, cfile); + done = ISC_TRUE; + break; + + case END_OF_FILE: + /* + * We can get to END_OF_FILE if, for instance, + * the parse_statement() reads all available tokens + * and leaves us at the end. + */ + parse_error(cfile, "unexpected end of file"); + + default: + declaration = parse_statement(cfile, type, + declaration); + break; + } + } while (!done); + + cfile->stack_top--; + + generate_class(cfile, pool, permit, prohibit); + + pools = mapGet(cfile->stack[cfile->stack_top], "pools"); + if (pools == NULL) { + pools = createList(); + pools->kind = POOL_DECL; + mapSet(cfile->stack[cfile->stack_top], pools, "pools"); + } + if (range_counter == 0) { + struct comment *comment; + + /* no range */ + comment = createComment("empty pool"); + TAILQ_INSERT_TAIL(&pool->comments, comment); + pool->skip = ISC_TRUE; + cfile->issue_counter++; + listPush(pools, pool); + return; + } + /* spread extra ranges into pool copies */ + while (--range_counter != 0) { + struct handle *handle; + struct element *first; + struct element *saved; + isc_boolean_t seen = ISC_FALSE; + + first = createMap(); + saved = copy(pool); + TAILQ_CONCAT(&first->comments, &pool->comments); + while (mapSize(pool) > 0) { + handle = mapPop(pool); + if ((handle == NULL) || (handle->key == NULL) || + (handle->value == NULL)) + parse_error(cfile, "bad pool entry"); + if (strcmp(handle->key, "pool") != 0) + mapSet(first, handle->value, handle->key); + else if (!seen) { + mapSet(first, handle->value, handle->key); + mapRemove(saved, "pool"); + seen = ISC_TRUE; + } + } + listPush(pools, first); + pool = saved; + } + listPush(pools, pool); +} + +/* Expect a left brace */ + +void +parse_lbrace(struct parse *cfile) +{ + enum dhcp_token token; + const char *val; + + token = next_token(&val, NULL, cfile); + if (token != LBRACE) + parse_error(cfile, "expecting left brace."); +} + +/* host-declaration :== hostname RBRACE parameters declarations LBRACE */ + +void +parse_host_declaration(struct parse *cfile) +{ + const char *val; + enum dhcp_token token; + struct element *host; + struct string *name; + struct element *where; + struct element *hosts = NULL; + int declaration = 0; + isc_boolean_t used_heuristic = ISC_FALSE; + + host = createMap(); + host->kind = HOST_DECL; + TAILQ_CONCAT(&host->comments, &cfile->comments); + + name = parse_host_name(cfile); + if (!name) + parse_error(cfile, "expecting a name for host declaration."); + + mapSet(host, createString(name), "hostname"); + + parse_lbrace(cfile); + + stackPush(cfile, host); + + for (;;) { + token = peek_token(&val, NULL, cfile); + if (token == RBRACE) { + skip_token(&val, NULL, cfile); + break; + } + if (token == END_OF_FILE) + parse_error(cfile, "unexpected end of file"); + /* If the host declaration was created by the server, + remember to save it. */ + if (token == DYNAMIC) { + skip_token(&val, NULL, cfile); + parse_error(cfile, "dynamic hosts don't exist " + "in the config file"); + } + /* If the host declaration was created by the server, + remember to save it. */ + if (token == TOKEN_DELETED) { + skip_token(&val, NULL, cfile); + parse_error(cfile, "deleted hosts don't exist " + "in the config file"); + } + + if (token == GROUP) { + struct element *group; + struct comment *comment; + + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token != STRING && !is_identifier(token)) + parse_error(cfile, + "expecting string or identifier."); + group = createString(makeString(-1, val)); + group->skip = ISC_TRUE; + cfile->issue_counter++; + comment = createComment("/// Unsupported group in " + "host reservations"); + TAILQ_INSERT_TAIL(&group->comments, comment); + comment = createComment("/// Reference Kea #233"); + TAILQ_INSERT_TAIL(&group->comments, comment); + mapSet(host, group, "group"); + parse_semi(cfile); + continue; + } + + if (token == UID) { + struct string *client_id; + + if (!use_client_id) { + add_host_reservation_identifiers(cfile, + "client-id"); + use_client_id = ISC_TRUE; + } + + skip_token(&val, NULL, cfile); + + if (mapContains(host, "client-id")) + parse_error(cfile, "Host %s already has a " + "client identifier.", + name->content); + + /* See if it's a string or a cshl. */ + token = peek_token(&val, NULL, cfile); + if (token == STRING) { + skip_token(&val, NULL, cfile); + client_id = makeString(-1, val); + } else { + struct string *bin; + unsigned len = 0; + + bin = parse_numeric_aggregate + (cfile, NULL, &len, ':', 16, 8); + if (!bin) + parse_error(cfile, + "expecting hex list."); + client_id = makeStringExt(bin->length, + bin->content, 'H'); + } + mapSet(host, createString(client_id), "client-id"); + + parse_semi(cfile); + continue; + } + + if (token == HOST_IDENTIFIER) { + struct string *host_id; + isc_boolean_t known; + struct option *option; + struct element *expr; + struct string *data; + int relays = 0; + + if (!use_flex_id) { + add_host_reservation_identifiers(cfile, + "flex-id"); + use_flex_id = ISC_TRUE; + } + + if (mapContains(host, "host-identifier") || + mapContains(host, "flex-id")) + parse_error(cfile, + "only one host-identifier allowed " + "per host"); + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + host_id = makeString(-1, val); + appendString(host_id, " "); + if (token == V6RELOPT) { + token = next_token(&val, NULL, cfile); + + if (token != NUMBER) + parse_error(cfile, + "host-identifier v6relopt " + "must have a number"); + appendString(host_id, val); + appendString(host_id, " "); + relays = atoi(val); + if (relays < 0) + parse_error(cfile, + "host-identifier v6relopt " + "must have a number >= 0"); + if (relays > MAX_V6RELAY_HOPS) + relays = MAX_V6RELAY_HOPS + 1; + } else if (token != OPTION) + parse_error(cfile, + "host-identifier must be an option" + " or v6relopt"); + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_TRUE, &known); + if (!known) + parse_error(cfile, "unknown option %s.%s", + option->space->old, option->old); + appendString(host_id, option->space->name); + appendString(host_id, "."); + appendString(host_id, option->name); + appendString(host_id, " "); + + data = parse_option_textbin(cfile, option); + parse_semi(cfile); + + if (data == NULL) + parse_error(cfile, "can't get option data"); + concatString(host_id, data); + expr = createString(host_id); + expr->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(host, expr, "host-identifier"); + + if (host_id_option == NULL) + add_host_id_option(cfile, option, relays); + else if ((host_id_option != option) || + (host_id_relays != relays)) { + struct string *msg; + struct comment *comment; + + msg = allocString(); + appendString(msg, "/// Another option ("); + appendString(msg, host_id_option->name); + appendString(msg, ") is already used as "); + appendString(msg, "host-identifier"); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&expr->comments, comment); + continue; + } + + /* + * Everything good: set a flex-id and remove + * the host-identifier entry. + */ + mapSet(host, createString(data), "flex-id"); + mapRemove(host, "host-identifier"); + continue; + } + + declaration = parse_statement(cfile, HOST_DECL, declaration); + } + + cfile->stack_top--; + + where = find_match(cfile, host, &used_heuristic); + hosts = mapGet(where, "reservations"); + if (hosts == NULL) { + hosts = createList(); + hosts->kind = HOST_DECL; + mapSet(where, hosts, "reservations"); + if (used_heuristic) { + struct comment *comment; + + comment = createComment("/// Host reservations " + "without fixed addresses " + "were put in the last " + "declared subnet"); + TAILQ_INSERT_TAIL(&hosts->comments, comment); + comment = createComment("/// Reference Kea #231"); + TAILQ_INSERT_TAIL(&hosts->comments, comment); + } + } + listPush(hosts, host); +} + +/* Simple tool to declare used (and only used) reservation identifiers */ +static void +add_host_reservation_identifiers(struct parse *cfile, const char *id) +{ + struct element *ids; + + ids = mapGet(cfile->stack[1], "host-reservation-identifiers"); + if (ids == NULL) { + ids = createList(); + mapSet(cfile->stack[1], ids, "host-reservation-identifiers"); + } + listPush(ids, createString(makeString(-1, id))); +} + +/* Add the flexible host identifier glue */ +static void +add_host_id_option(struct parse *cfile, + const struct option *option, int relays) +{ + struct string *path; + struct string *expr; + struct element *params; + struct element *entry; + struct element *hooks; + struct comment *comment; + char buf[40]; + + host_id_option = option; + host_id_relays = relays; + + /* + * Using the example from the Kea Administrator Reference Manual + * as recommended by Tomek + */ + hooks = createList(); + mapSet(cfile->stack[1], hooks, "hooks-libraries"); + comment = createComment("/// The flexible host identifier " + "is a premium feature"); + TAILQ_INSERT_TAIL(&hooks->comments, comment); + entry = createMap(); + listPush(hooks, entry); + if (hook_library_path != NULL) + path = makeString(-1, hook_library_path); + else + path = makeString(-1, "/path/"); + appendString(path, "libdhcp_flex_id.so"); + params = createString(path); + if (hook_library_path == NULL) { + comment = createComment("/// Please update the path here"); + TAILQ_INSERT_TAIL(¶ms->comments, comment); + } + mapSet(entry, params, "library"); + params = createMap(); + mapSet(entry, params, "parameters"); + + snprintf(buf, sizeof(buf), "%soption[%u].hex", + relays > 0 ? "relay[0]." : "", option->code); + expr = makeString(-1, buf); + mapSet(params, createString(expr), "identifier-expression"); +} + +static void add_host_reservation_identifiers(struct parse *, const char *); +/* class-declaration :== STRING LBRACE parameters declarations RBRACE + * + * in fact: + * (CLASS) NAME(STRING) LBRACE ... RBRACE + * (SUBCLASS) SUPER(STRING) DATA/HASH(STRING | <hexa>) [BRACE ... RBRACE] + * + * class "name" { MATCH IF <boolean-expr> }: direct: belong when true + * class "name" { MATCH <data-expr> }: indirect: use subclasses + * class "name" { MATCH <data-expr> SPAWN WITH <data-expr> }: indirect: + * create dynamically a subclass + * subclass "super" <data-expr = string or binary aka hash>: belongs when + * super <data-expr> == <hash> + */ + +void +parse_class_declaration(struct parse *cfile, int type) +{ + const char *val = NULL; + enum dhcp_token token; + size_t group = 0; + size_t i = 0; + struct element *group_classes = NULL; + struct element *classes = NULL; + struct element *class = NULL; + struct element *pc = NULL; /* p(arent)c(lass) */ + struct element *tmp = NULL; + struct element *expr = NULL; + struct element *data = NULL; + isc_boolean_t binary = ISC_FALSE; + int declaration = 0; + struct string *name = NULL; + isc_boolean_t lose = ISC_FALSE; + isc_boolean_t matchedonce = ISC_FALSE; + isc_boolean_t submatchedonce = ISC_FALSE; + + token = next_token(&val, NULL, cfile); + if (token != STRING) + parse_error(cfile, "Expecting class name"); + + /* Find group and root classes */ + classes = mapGet(cfile->stack[1], "client-classes"); + if (classes == NULL) { + classes = createList(); + classes->kind = CLASS_DECL; + mapSet(cfile->stack[1], classes, "client-classes"); + } + for (group = cfile->stack_top; group > 0; --group) { + int kind; + + kind = cfile->stack[group]->kind; + if (kind == CLASS_DECL) + parse_error(cfile, "class in class"); + if ((kind == GROUP_DECL) || (kind == ROOT_GROUP)) + break; + } + if (!group) + parse_error(cfile, "can't find root group"); + if (cfile->stack[group]->kind == GROUP_DECL) { + group_classes = mapGet(cfile->stack[group], "client-classes"); + if (group_classes == NULL) { + group_classes = createList(); + group_classes->kind = CLASS_DECL; + mapSet(cfile->stack[group], group_classes, + "client-classes"); + } + } else + group_classes = classes; + + /* See if there's already a class with the specified name. */ + for (i = 0; i < listSize(classes); i++) { + struct element *name; + + tmp = listGet(classes, i); + name = mapGet(tmp, "name"); + if (name == NULL) + continue; + if (strcmp(stringValue(name)->content, val) == 0) { + pc = tmp; + break; + } + } + + /* If it is a class, we're updating it. If it's any of the other + * types (subclass, vendor or user class), the named class is a + * reference to the parent class so its mandatory. + */ + if ((pc != NULL) && (type == CLASS_TYPE_CLASS)) { + class = pc; + pc = NULL; + } else if (type != CLASS_TYPE_CLASS) { + if (pc == NULL) + parse_error(cfile, "no class named %s", val); + if (!mapContains(pc, "spawning") || + !mapContains(pc, "submatch")) + parse_error(cfile, "found class name %s but it is " + "not a suitable superclass", val); + } + + name = makeString(-1, val); + /* If this is a straight subclass, parse the hash string. */ + if (type == CLASS_TYPE_SUBCLASS) { + token = peek_token(&val, NULL, cfile); + if (token == STRING) { + unsigned len; + + skip_token(&val, &len, cfile); + data = createString(makeString(len, val)); + } else if (token == NUMBER_OR_NAME || token == NUMBER) { + data = createHexa(parse_hexa(cfile)); + binary = ISC_TRUE; + } else { + skip_token(&val, NULL, cfile); + parse_error(cfile, "Expecting string or hex list."); + } + } + + /* See if there's already a class in the hash table matching the + hash data. */ + if (type != CLASS_TYPE_CLASS) { + for (i = 0; i < listSize(classes); i++) { + struct element *super; + struct element *selector; + + tmp = listGet(classes, i); + super = mapGet(tmp, "super"); + if (super == NULL) + continue; + if (!eqString(stringValue(super), name)) + continue; + if (binary) + selector = mapGet(tmp, "binary"); + else + selector = mapGet(tmp, "string"); + if (selector == NULL) + continue; + if (eqString(stringValue(selector), + stringValue(data))) { + class = tmp; + break; + } + } + } + + /* Note the class declaration in the enclosing group */ + if (group_classes != classes) { + struct element *gc; + + gc = createMap(); + gc->kind = CLASS_DECL; + tmp = createString(name); + if (type == CLASS_TYPE_CLASS) + mapSet(gc, tmp, "name"); + else { + tmp->skip = ISC_TRUE; + mapSet(gc, tmp, "super"); + data->skip = ISC_TRUE; + if (binary) + mapSet(gc, data, "binary"); + else + mapSet(gc, data, "string"); + } + listPush(group_classes, gc); + } + + /* If we didn't find an existing class, allocate a new one. */ + if (!class) { + /* Allocate the class structure... */ + class = createMap(); + class->kind = CLASS_DECL; + TAILQ_CONCAT(&class->comments, &cfile->comments); + if (type == CLASS_TYPE_SUBCLASS) { + struct string *subname; + char buf[40]; + + cfile->issue_counter++; + tmp = createString(name); + tmp->skip = ISC_TRUE; + mapSet(class, tmp, "super"); + data->skip = ISC_TRUE; + if (binary) + mapSet(class, data, "binary"); + else + mapSet(class, data, "string"); + subname = makeString(-1, "sub#"); + concatString(subname, name); + snprintf(buf, sizeof(buf), + "#%u", subclass_counter++); + appendString(subname, buf); + mapSet(class, createString(subname), "name"); + } else + /* Save the name, if there is one. */ + mapSet(class, createString(name), "name"); + listPush(classes, class); + } + + /* Spawned classes don't have to have their own settings. */ + if (type == CLASS_TYPE_SUBCLASS) { + token = peek_token(&val, NULL, cfile); + if (token == SEMI) { + skip_token(&val, NULL, cfile); + subclass_inherit(cfile, class, copy(pc)); + return; + } + } + + parse_lbrace(cfile); + + stackPush(cfile, class); + + for (;;) { + token = peek_token(&val, NULL, cfile); + if (token == RBRACE) { + skip_token(&val, NULL, cfile); + break; + } else if (token == END_OF_FILE) { + skip_token(&val, NULL, cfile); + parse_error(cfile, "unexpected end of file"); + } else if (token == DYNAMIC) { + skip_token(&val, NULL, cfile); + parse_error(cfile, "dynamic classes don't exist " + "in the config file"); + } else if (token == TOKEN_DELETED) { + skip_token(&val, NULL, cfile); + parse_error(cfile, "deleted hosts don't exist " + "in the config file"); + } else if (token == MATCH) { + skip_token(&val, NULL, cfile); + if (pc) + parse_error(cfile, + "invalid match in subclass."); + token = peek_token(&val, NULL, cfile); + if (token != IF) { + expr = createBool(ISC_FALSE); + expr->skip = 1; + mapSet(class, expr, "spawning"); + goto submatch; + } + + skip_token(&val, NULL, cfile); + if (matchedonce) + parse_error(cfile, + "A class may only have " + "one 'match if' clause."); + matchedonce = ISC_TRUE; + expr = createMap(); + if (!parse_boolean_expression(expr, cfile, &lose)) { + if (!lose) + parse_error(cfile, + "expecting boolean expr."); + } else { + expr->skip = ISC_TRUE; + mapSet(class, expr, "match-if"); + add_match_class(cfile, class, copy(expr)); + parse_semi(cfile); + } + } else if (token == SPAWN) { + skip_token(&val, NULL, cfile); + if (pc) + parse_error(cfile, + "invalid spawn in subclass."); + expr = createBool(ISC_TRUE); + expr->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(class, expr, "spawning"); + token = next_token(&val, NULL, cfile); + if (token != WITH) + parse_error(cfile, + "expecting with after spawn"); + submatch: + if (submatchedonce) + parse_error(cfile, + "can't override existing " + "submatch/spawn"); + submatchedonce = ISC_TRUE; + expr = createMap(); + if (!parse_data_expression(expr, cfile, &lose)) { + if (!lose) + parse_error(cfile, + "expecting data expr."); + } else { + expr->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(class, expr, "submatch"); + parse_semi(cfile); + } + } else if (token == LEASE) { + struct comment *comment; + + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token != LIMIT) + parse_error(cfile, "expecting \"limit\""); + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "expecting a number"); + tmp = createInt(atoll(val)); + tmp->skip = ISC_TRUE; + cfile->issue_counter++; + comment = createComment("/// Per-class limit is not " + "supported by Kea"); + TAILQ_INSERT_TAIL(&tmp->comments, comment); + comment = createComment("/// Reference Kea #237"); + TAILQ_INSERT_TAIL(&tmp->comments, comment); + mapSet(class, tmp, "lease-limit"); + parse_semi(cfile); + } else + declaration = parse_statement(cfile, CLASS_DECL, + declaration); + } + + cfile->stack_top--; + + if (type == CLASS_TYPE_SUBCLASS) + subclass_inherit(cfile, class, copy(pc)); +} + +/* + * Inherit entries: + * - first copy entries from the current superclass to the subclass + * - second try to reduce the subclass matching condition + */ + +static void +subclass_inherit(struct parse *cfile, + struct element *class, + struct element *superclass) +{ + struct string *name; + struct element *guard; + struct element *submatch; + struct handle *handle; + struct string *gmsg; + struct string *mmsg; + struct string *dmsg; + struct element *expr; + struct element *data; + struct element *match; + struct element *reduced; + unsigned order = 0; + struct comment *comment; + isc_boolean_t marked = ISC_FALSE; + isc_boolean_t lose = ISC_FALSE; + isc_boolean_t modified = ISC_FALSE; + + expr = mapGet(superclass, "name"); + if (expr == NULL) + parse_error(cfile, "can't get superclass name"); + name = stringValue(expr); + guard = mapGet(superclass, "match-if"); + submatch = mapGet(superclass, "submatch"); + if (submatch == NULL) + parse_error(cfile, "can't get superclass submatch"); + + /* Iterates on (copy of) superclass entries */ + while (mapSize(superclass) > 0) { + handle = mapPop(superclass); + if ((handle == NULL) || (handle->key == NULL) || + (handle->value == NULL)) + parse_error(cfile, "can't get superclass %s item at " + "%u", name->content, order); + handle->order = order++; + /* Superclass specific entries */ + if ((strcmp(handle->key, "name") == 0) || + (strcmp(handle->key, "spawning") == 0) || + (strcmp(handle->key, "match-if") == 0) || + (strcmp(handle->key, "test") == 0) || + (strcmp(handle->key, "submatch") == 0)) + continue; + /* Subclass specific so impossible entries */ + if ((strcmp(handle->key, "super") == 0) || + (strcmp(handle->key, "binary") == 0) || + (strcmp(handle->key, "string") == 0)) + parse_error(cfile, "superclass %s has unexpected %s " + "at %u", + name->content, handle->key, order); + /* Special entries */ + if (strcmp(handle->key, "option-data") == 0) { + struct element *opt_list; + + opt_list = mapGet(class, handle->key); + if (opt_list != NULL) + merge_option_data(handle->value, opt_list); + else + mapSet(class, handle->value, handle->key); + continue; + } + /* Just copy */ + if ((strcmp(handle->key, "lease-limit") == 0) || + (strcmp(handle->key, "boot-file-name") == 0) || + (strcmp(handle->key, "serverhostname") == 0) || + (strcmp(handle->key, "next-server") == 0)) { + mapSet(class, handle->value, handle->key); + continue; + } + /* Unknown */ + if (!marked) { + marked = ISC_TRUE; + comment = createComment("/// copied from superclass"); + TAILQ_INSERT_TAIL(&handle->value->comments, comment); + } + comment = createComment("/// unhandled entry"); + TAILQ_INSERT_TAIL(&handle->value->comments, comment); + if (!handle->value->skip) { + handle->value->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(class, handle->value, handle->key); + } + + /* build [guard and] submatch = data */ + expr = mapGet(class, "binary"); + if (expr != NULL) { + data = createMap(); + mapSet(data, copy(expr), "const-data"); + } else + data = mapGet(class, "string"); + if (data == NULL) + parse_error(cfile, "can't get subclass %s data", + name->content); + match = createMap(); + mapSet(match, copy(submatch), "left"); + mapSet(match, copy(data), "right"); + expr = createMap(); + mapSet(expr, match, "equal"); + + if (guard != NULL) { + match = createMap(); + mapSet(match, copy(guard), "left"); + mapSet(match, expr, "right"); + expr = createMap(); + mapSet(expr, match, "and"); + + gmsg = makeString(-1, "/// from: match-if "); + appendString(gmsg, print_boolean_expression(guard, &lose)); + mmsg = makeString(-1, "/// match: "); + } else { + gmsg = NULL; + mmsg = makeString(-1, "/// from: match "); + } + + appendString(mmsg, print_data_expression(submatch, &lose)); + dmsg = makeString(-1, "/// data: "); + appendString(dmsg, print_data_expression(data, &lose)); + + /* evaluate the expression and try to reduce it */ + reduced = eval_boolean_expression(expr, &modified); + reduced = reduce_boolean_expression(reduced); + if ((reduced != NULL) && (reduced->type == ELEMENT_BOOLEAN)) + parse_error(cfile, "class matching rule evaluated to a " + "constant boolean expression: %s = %s", + print_data_expression(submatch, &lose), + print_data_expression(data, &lose)); + if ((reduced == NULL) || (reduced->type != ELEMENT_STRING)) + return; + if (!lose) { + if (gmsg != NULL) { + comment = createComment(gmsg->content); + TAILQ_INSERT_TAIL(&reduced->comments, comment); + } + comment = createComment(mmsg->content); + TAILQ_INSERT_TAIL(&reduced->comments, comment); + comment = createComment(dmsg->content); + TAILQ_INSERT_TAIL(&reduced->comments, comment); + } + mapSet(class, reduced, "test"); +} + +/* + * Try to reduce a match-if condition into a Kea evaluate bool "test" + */ + +static void +add_match_class(struct parse *cfile, + struct element *class, + struct element *expr) +{ + struct element *reduced; + isc_boolean_t modified = ISC_FALSE; + isc_boolean_t lose = ISC_FALSE; + + /* evaluate the expression and try to reduce it */ + reduced = eval_boolean_expression(expr, &modified); + reduced = reduce_boolean_expression(reduced); + if ((reduced != NULL) && (reduced->type == ELEMENT_BOOLEAN)) + parse_error(cfile, "'match if' with a constant boolean " + "expression %s", + print_boolean_expression(expr, &lose)); + if ((reduced != NULL) && (reduced->type == ELEMENT_STRING)) + mapSet(class, reduced, "test"); + else + cfile->issue_counter++; +} + +/* Move pools to subnets */ + +static void +relocate_pools(struct element *share) +{ + struct element *srcs; + struct element *dsts; + struct element *subnet; + struct range *range; + size_t i; + + srcs = mapGet(share, "pools"); + if (srcs == NULL) + return; + if (listSize(srcs) == 0) + return; + TAILQ_FOREACH(range, &known_ranges) { + if (range->share != share) + continue; + subnet = find_location(share, range); + if (subnet == NULL) + continue; + for (i = 0; i < listSize(srcs); i++) { + struct element *pool; + + pool = listGet(srcs, i); + if (range->pool != pool) + continue; + listRemove(srcs, i); + dsts = mapGet(subnet, "pools"); + if (dsts == NULL) { + dsts = createList(); + mapSet(subnet, dsts, "pools"); + } + listPush(dsts, pool); + } + } +} + +/* shared-network-declaration :== + hostname LBRACE declarations parameters RBRACE */ + +void +parse_shared_net_declaration(struct parse *cfile) +{ + const char *val; + enum dhcp_token token; + struct element *share; + struct element *subnets; + struct element *interface; + struct element *subnet; + struct string *name; + int declaration = 0; + + share = createMap(); + share->kind = SHARED_NET_DECL; + TAILQ_CONCAT(&share->comments, &cfile->comments); + + /* Get the name of the shared network... */ + token = peek_token(&val, NULL, cfile); + if (token == STRING) { + skip_token(&val, NULL, cfile); + + if (val[0] == 0) + parse_error(cfile, "zero-length shared network name"); + name = makeString(-1, val); + } else { + name = parse_host_name(cfile); + if (!name) + parse_error(cfile, + "expecting a name for shared-network"); + } + mapSet(share, createString(name), "name"); + + subnets = createList(); + mapSet(share, subnets, + local_family == AF_INET ? "subnet4" : "subnet6"); + + parse_lbrace(cfile); + + stackPush(cfile, share); + + for (;;) { + token = peek_token(&val, NULL, cfile); + if (token == RBRACE) { + skip_token(&val, NULL, cfile); + break; + } else if (token == END_OF_FILE) { + skip_token(&val, NULL, cfile); + parse_error(cfile, "unexpected end of file"); + } else if (token == INTERFACE) { + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (mapContains(share, "interface")) + parse_error(cfile, + "A shared network can't be " + "connected to two interfaces."); + interface = createString(makeString(-1, val)); + mapSet(share, interface, "interface"); + new_network_interface(cfile, interface); + parse_semi(cfile); + continue; + } + + declaration = parse_statement(cfile, SHARED_NET_DECL, + declaration); + } + + cfile->stack_top--; + + if (listSize(subnets) == 0) + parse_error(cfile, "empty shared-network decl"); + if (listSize(subnets) > 1) { + struct element *shares; + struct element *pools; + + shares = mapGet(cfile->stack[cfile->stack_top], + "shared-networks"); + if (shares == NULL) { + struct comment *comment; + + shares = createList(); + shares->kind = SHARED_NET_DECL; + mapSet(cfile->stack[cfile->stack_top], + shares, "shared-networks"); + comment = createComment("/// Kea shared-networks " + "are different, cf Kea #236"); + TAILQ_INSERT_TAIL(&shares->comments, comment); + } + listPush(shares, share); + + /* Pools are forbidden at shared-network level in Kea */ + relocate_pools(share); + pools = mapGet(share, "pools"); + if ((pools != NULL) && (listSize(pools) == 0)) { + mapRemove(share, "pools"); + pools = NULL; + } + if (pools != NULL) { + struct comment *comment; + + pools->skip = ISC_TRUE; + cfile->issue_counter++; + comment = createComment("/// Kea pools must be " + "in a subnet"); + TAILQ_INSERT_TAIL(&pools->comments, comment); + comment = createComment("/// Reference Kea #249"); + TAILQ_INSERT_TAIL(&pools->comments, comment); + } + pools = mapGet(share, "pd-pools"); + if ((pools != NULL) && (listSize(pools) == 0)) { + mapRemove(share, "pd-pools"); + pools = NULL; + } + if (pools != NULL) { + struct comment *comment; + + pools->skip = ISC_TRUE; + cfile->issue_counter++; + comment = createComment("/// Kea pools must be " + "in a subnet"); + TAILQ_INSERT_TAIL(&pools->comments, comment); + comment = createComment("/// Reference Kea #249"); + TAILQ_INSERT_TAIL(&pools->comments, comment); + } + return; + } + + /* There is one subnet so the shared network is useless */ + subnet = listGet(subnets, 0); + listRemove(subnets, 0); + mapRemove(share, "name"); + mapRemove(share, local_family == AF_INET ? "subnet4" : "subnet6"); + /* specific case before calling generic merge */ + if (mapContains(share, "pools") && + mapContains(subnet, "pools")) { + struct element *pools; + struct element *sub; + + pools = mapGet(share, "pools"); + mapRemove(share, "pools"); + sub = mapGet(subnet, "pools"); + concat(sub, pools); + } + if (mapContains(share, "pd-pools") && + mapContains(subnet, "pd-pools")) { + struct element *pools; + struct element *sub; + + pools = mapGet(share, "pd-pools"); + mapRemove(share, "pd-pools"); + sub = mapGet(subnet, "pd-pools"); + concat(sub, pools); + } + if (mapContains(share, "option-data") && + mapContains(subnet, "option-data")) { + struct element *opt_list; + struct element *sub; + + opt_list = mapGet(share, "option-data"); + mapRemove(share, "option-data"); + sub = mapGet(subnet, "option-data"); + merge_option_data(opt_list, sub); + } + merge(subnet, share); + + if (local_family == AF_INET) { + subnets = mapGet(cfile->stack[1], "subnet4"); + if (subnets == NULL) { + subnets = createList(); + subnets->kind = SUBNET_DECL; + mapSet(cfile->stack[1], subnets, "subnet4"); + } + } else { + subnets = mapGet(cfile->stack[1], "subnet6"); + if (subnets == NULL) { + subnets = createList(); + subnets->kind = SUBNET_DECL; + mapSet(cfile->stack[1], subnets, "subnet6"); + } + } + listPush(subnets, subnet); +} + +static void +common_subnet_parsing(struct parse *cfile, + struct element *subnets, + struct element *subnet) +{ + enum dhcp_token token; + const char *val; + struct element *interface; + int declaration = 0; + + parse_lbrace(cfile); + + stackPush(cfile, subnet); + + for (;;) { + token = peek_token(&val, NULL, cfile); + if (token == RBRACE) { + skip_token(&val, NULL, cfile); + break; + } else if (token == END_OF_FILE) { + skip_token(&val, NULL, cfile); + parse_error(cfile, "unexpected end of file"); + break; + } else if (token == INTERFACE) { + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (mapContains(subnet, "interface")) + parse_error(cfile, + "A subnet can't be connected " + "to two interfaces."); + interface = createString(makeString(-1, val)); + mapSet(subnet, interface, "interface"); + new_network_interface(cfile, interface); + parse_semi(cfile); + continue; + } + declaration = parse_statement(cfile, SUBNET_DECL, declaration); + } + + cfile->stack_top--; + + /* Add the subnet to the list of subnets in this shared net. */ + listPush(subnets, subnet); + + return; +} + +/* subnet-declaration :== + net NETMASK netmask RBRACE parameters declarations LBRACE */ + +void +parse_subnet_declaration(struct parse *cfile) +{ + const char *val; + enum dhcp_token token; + struct element *subnet; + struct subnet *chain; + struct element *subnets; + struct string *address; + struct string *netmask; + struct string *prefix; + unsigned char addr[4]; + unsigned len = sizeof(addr); + size_t parent = 0; + size_t i; + int kind = 0; + + subnet = createMap(); + subnet->kind = SUBNET_DECL; + TAILQ_CONCAT(&subnet->comments, &cfile->comments); + + subnet_counter++; + mapSet(subnet, createInt(subnet_counter), "id"); + + chain = (struct subnet *)malloc(sizeof(*chain)); + if (chain == NULL) + parse_error(cfile, "can't allocate subnet"); + memset(chain, 0, sizeof(*chain)); + chain->subnet = subnet; + TAILQ_INSERT_TAIL(&known_subnets, chain); + + /* Find parent */ + for (i = cfile->stack_top; i > 0; --i) { + kind = cfile->stack[i]->kind; + if ((kind == SHARED_NET_DECL) || + (kind == GROUP_DECL) || + (kind == ROOT_GROUP)) { + parent = i; + break; + } + } + if (kind == 0) + parse_error(cfile, "can't find a place to put subnet"); + if (kind == SHARED_NET_DECL) + chain->share = cfile->stack[parent]; + subnets = mapGet(cfile->stack[parent], "subnet4"); + if (subnets == NULL) { + if (kind == SHARED_NET_DECL) + parse_error(cfile, "shared network without subnets"); + subnets = createList(); + subnets->kind = SUBNET_DECL; + mapSet(cfile->stack[parent], subnets, "subnet4"); + } + + /* Get the network number... */ + address = parse_numeric_aggregate(cfile, addr, &len, DOT, 10, 8); + if (address == NULL) + parse_error(cfile, "can't decode network number"); + if (address->length != 4) + parse_error(cfile, "bad IPv4 address length"); + chain->addr = address; + + token = next_token(&val, NULL, cfile); + if (token != NETMASK) + parse_error(cfile, "Expecting netmask"); + + /* Get the netmask... */ + netmask = parse_numeric_aggregate(cfile, addr, &len, DOT, 10, 8); + if (netmask == NULL) + parse_error(cfile, "can't decode network mask"); + if (netmask->length != address->length) + parse_error(cfile, "bad IPv4 mask length"); + chain->mask = netmask; + + prefix = addrmask(address, netmask); + if (prefix == NULL) { + char bufa[INET_ADDRSTRLEN]; + char bufm[INET_ADDRSTRLEN]; + + inet_ntop(AF_INET, address->content, bufa, INET_ADDRSTRLEN); + inet_ntop(AF_INET, netmask->content, bufm, INET_ADDRSTRLEN); + parse_error(cfile, "can't get a prefix from %s mask %s", + bufa, bufm); + } + mapSet(subnet, createString(prefix), "subnet"); + + common_subnet_parsing(cfile, subnets, subnet); +} + +/* subnet6-declaration :== + net / bits RBRACE parameters declarations LBRACE */ + +void +parse_subnet6_declaration(struct parse *cfile) +{ + enum dhcp_token token; + const char *val; + struct element *subnet; + struct subnet *chain; + struct element *subnets; + struct string *address; + struct string *prefix; + struct string *netmask; + size_t parent = 0; + size_t i; + int kind = 0; + char *p; + + if (local_family != AF_INET6) + parse_error(cfile, "subnet6 statement is only supported " + "in DHCPv6 mode."); + + subnet = createMap(); + subnet->kind = SUBNET_DECL; + TAILQ_CONCAT(&subnet->comments, &cfile->comments); + + subnet_counter++; + mapSet(subnet, createInt(subnet_counter), "id"); + + chain = (struct subnet *)malloc(sizeof(*chain)); + if (chain == NULL) + parse_error(cfile, "can't allocate subnet"); + memset(chain, 0, sizeof(*chain)); + chain->subnet = subnet; + TAILQ_INSERT_TAIL(&known_subnets, chain); + + /* Find parent */ + for (i = cfile->stack_top; i > 0; --i) { + kind = cfile->stack[i]->kind; + if ((kind == SHARED_NET_DECL) || + (kind == GROUP_DECL) || + (kind == ROOT_GROUP)) { + parent = i; + break; + } + } + if (kind == 0) + parse_error(cfile, "can't find a place to put subnet"); + if (kind == SHARED_NET_DECL) + chain->share = cfile->stack[parent]; + subnets = mapGet(cfile->stack[parent], "subnet6"); + if (subnets == NULL) { + if (kind == SHARED_NET_DECL) + parse_error(cfile, "shared network without subnets"); + subnets = createList(); + subnets->kind = SUBNET_DECL; + mapSet(cfile->stack[parent], subnets, "subnet6"); + } + + address = parse_ip6_addr(cfile); + if (address == NULL) + parse_error(cfile, "can't decode network number"); + if (address->length != 16) + parse_error(cfile, "bad IPv6 address length"); + chain->addr = address; + + prefix = makeStringExt(address->length, address->content, '6'); + + token = next_token(&val, NULL, cfile); + if (token != SLASH) + parse_error(cfile, "Expecting a '/'."); + appendString(prefix, val); + + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "Expecting a number."); + appendString(prefix, val); + + netmask = makeString(16, "0123456789abcdef"); + memset(netmask->content, 0, 16); + p = netmask->content; + for (i = atoi(val); i >= 8; i -= 8) + *p++ = 0xff; + *p = 0xff << (8 - i); + chain->mask = netmask; + + mapSet(subnet, createString(prefix), "subnet"); + + common_subnet_parsing(cfile, subnets, subnet); +} + +/* group-declaration :== RBRACE parameters declarations LBRACE */ + +void +parse_group_declaration(struct parse *cfile) +{ + const char *val; + enum dhcp_token token; + struct element *group; + int declaration = 0; + struct string *name = NULL; + + if (mapContains(cfile->stack[cfile->stack_top], "group")) + parse_error(cfile, "another group is already open"); + group = createMap(); + group->skip = ISC_TRUE; + group->kind = GROUP_DECL; + TAILQ_CONCAT(&group->comments, &cfile->comments); + mapSet(cfile->stack[cfile->stack_top], group, "group"); + + token = peek_token(&val, NULL, cfile); + if (is_identifier(token) || token == STRING) { + skip_token(&val, NULL, cfile); + + name = makeString(-1, val); + if (!name) + parse_error(cfile, "no memory for group decl name %s", + val); + } + + parse_lbrace(cfile); + + stackPush(cfile, group); + + for (;;) { + token = peek_token(&val, NULL, cfile); + if (token == RBRACE) { + skip_token(&val, NULL, cfile); + break; + } else if (token == END_OF_FILE) { + skip_token(&val, NULL, cfile); + parse_error(cfile, "unexpected end of file"); + break; + } else if (token == TOKEN_DELETED) { + skip_token(&val, NULL, cfile); + parse_error(cfile, "deleted groups don't exist " + "in the config file"); + } else if (token == DYNAMIC) { + skip_token(&val, NULL, cfile); + parse_error(cfile, "dynamic groups don't exist " + "in the config file"); + } else if (token == STATIC) { + skip_token(&val, NULL, cfile); + parse_error(cfile, "static groups don't exist " + "in the config file"); + } + declaration = parse_statement(cfile, GROUP_DECL, declaration); + } + + cfile->stack_top--; + + if (name != NULL) + mapSet(group, createString(name), "name"); + close_group(cfile, group); +} + +/* + * Close a group. Called when a group is closed. + * - spread parameters to children + * - attach declarations at an upper level + */ + +void +close_group(struct parse *cfile, struct element *group) +{ + struct handle *handle; + struct handle *nh; + struct element *parent; + struct element *item; + struct element *param; + struct handle *hosts = NULL; + struct handle *shares = NULL; + struct handle *subnets = NULL; + struct handle *classes = NULL; + struct handle *pdpools = NULL; + struct handle *pools = NULL; + struct handles downs; + struct comment *comment; + const char *key = NULL; + const char *name = NULL; + unsigned order = 0; + isc_boolean_t marked = ISC_FALSE; + + TAILQ_INIT(&downs); + + /* check that group is in its parent */ + parent = cfile->stack[cfile->stack_top]; + if (parent->kind == PARAMETER) + parse_error(cfile, "unexpected kind for group parent %d", + parent->kind); + item = mapGet(parent, "group"); + if (item == NULL) + parse_error(cfile, "no group in parent"); + if (item != group) + parse_error(cfile, "got a different group from parent"); + mapRemove(parent, "group"); + + /* classify content */ + while (mapSize(group) > 0) { + handle = mapPop(group); + if ((handle == NULL) || (handle->key == NULL) || + (handle->value == NULL)) + parse_error(cfile, "can't get group item at %u", + order); + handle->order = order++; + switch (handle->value->kind) { + case TOPLEVEL: + case ROOT_GROUP: + case GROUP_DECL: + badkind: + parse_error(cfile, "impossible group item (kind %d) " + "for %s at order %u", + handle->value->kind, handle->key, order); + + case HOST_DECL: + if (strcmp(handle->key, "reservations") != 0) + parse_error(cfile, "expected reservations " + "got %s at %u", + handle->key, order); + if (hosts != NULL) + parse_error(cfile, "got reservations twice " + "at %u and %u", + hosts->order, order); + if ((parent->kind == HOST_DECL) || + (parent->kind == CLASS_DECL)) + parse_error(cfile, "host declarations not " + "allowed here."); + hosts = handle; + handle = NULL; + break; + + case SHARED_NET_DECL: + if (strcmp(handle->key, "shared-networks") != 0) + parse_error(cfile, "expected shared-networks " + "got %s at %u", + handle->key, order); + if ((parent->kind == SHARED_NET_DECL) || + (parent->kind == HOST_DECL) || + (parent->kind == SUBNET_DECL) || + (parent->kind == CLASS_DECL)) + parse_error(cfile, "shared-network parameters " + "not allowed here."); + shares = handle; + handle = NULL; + break; + + case SUBNET_DECL: + key = local_family == AF_INET ? "subnet4" : "subnet6"; + if (strcmp(handle->key, key) != 0) + parse_error(cfile, "expected %s got %s at %u", + key, handle->key, order); + if (subnets != NULL) + parse_error(cfile, "got %s twice at %u and %u", + key, subnets->order, order); + if ((parent->kind == HOST_DECL) || + (parent->kind == SUBNET_DECL) || + (parent->kind == CLASS_DECL)) + parse_error(cfile, "subnet declarations not " + "allowed here."); + subnets = handle; + handle = NULL; + break; + + case CLASS_DECL: + if (strcmp(handle->key, "client-classes") != 0) + parse_error(cfile, "expected client-classes " + "got %s at %u", + handle->key, order); + if (classes != NULL) + parse_error(cfile, "got %s twice at %u and %u", + key, classes->order, order); + if (parent->kind == CLASS_DECL) + parse_error(cfile, "class declarations not " + "allowed here."); + classes = handle; + handle = NULL; + break; + + case POOL_DECL: + if (strcmp(handle->key, "pd-pools") == 0) { + if (pdpools != NULL) + parse_error(cfile, "got pd-pools " + "twice at %u and %u", + pdpools->order, order); + pdpools = handle; + } else if (strcmp(handle->key, "pools") == 0) { + if (pools != NULL) + parse_error(cfile, "got pools twice " + "at %u and %u", + pools->order, order); + pools = handle; + } else + parse_error(cfile, "expecyed [pd-]pools got " + "%s at %u", + handle->key, order); + if (parent->kind == POOL_DECL) + parse_error(cfile, "pool declared within " + "pool."); + if ((parent->kind == HOST_DECL) || + (parent->kind == CLASS_DECL)) + parse_error(cfile, "pool declared outside " + "of network"); + handle = NULL; + break; + default: + if (handle->value->kind != PARAMETER) + goto badkind; + } + if (handle == NULL) + continue; + + /* we have a parameter */ + param = handle->value; + /* group name */ + if (strcmp(handle->key, "name") == 0) { + name = stringValue(param)->content; + continue; + } + /* unexpected values */ + if ((strcmp(handle->key, "reservations") == 0) || + (strcmp(handle->key, "group") == 0) || + (strcmp(handle->key, "shared-networks") == 0) || + (strcmp(handle->key, "subnet4") == 0) || + (strcmp(handle->key, "subnet6") == 0) || + (strcmp(handle->key, "subnet") == 0) || + (strcmp(handle->key, "client-classes") == 0) || + (strcmp(handle->key, "hw-address") == 0) || + (strcmp(handle->key, "ip-address") == 0) || + (strcmp(handle->key, "extra-ip-addresses") == 0) || + (strcmp(handle->key, "ip-addresses") == 0) || + (strcmp(handle->key, "prefixes") == 0) || + (strcmp(handle->key, "pool") == 0) || + (strcmp(handle->key, "prefix") == 0) || + (strcmp(handle->key, "delegated-len") == 0) || + (strcmp(handle->key, "prefix-len") == 0) || + (strcmp(handle->key, "prefix-highest") == 0) || + (strcmp(handle->key, "option-def") == 0) || + (strcmp(handle->key, "hostname") == 0) || + (strcmp(handle->key, "client-id") == 0) || + (strcmp(handle->key, "host-identifier") == 0) || + (strcmp(handle->key, "flex-id") == 0) || + (strcmp(handle->key, "test") == 0) || + (strcmp(handle->key, "authoritative") == 0) || + (strcmp(handle->key, "dhcp-ddns") == 0) || + (strcmp(handle->key, "host-reservation-identifiers") == 0)) + parse_error(cfile, "unexpected parameter %s " + "in group at %u", + handle->key, order); + + /* to parent at group position */ + if ((strcmp(handle->key, "option-space") == 0) || + (strcmp(handle->key, "server-duid") == 0) || + (strcmp(handle->key, "statement") == 0) || + (strcmp(handle->key, "config") == 0) || + (strcmp(handle->key, "ddns-update-style") == 0) || + (strcmp(handle->key, "echo-client-id") == 0)) { + if (!marked) { + struct string *msg; + + marked = ISC_TRUE; + msg = makeString(-1, "/// moved from group"); + if (name != NULL) + appendString(msg, " "); + appendString(msg, name); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(¶m->comments, comment); + } + mapSet(parent, param, handle->key); + free(handle); + continue; + } + /* To reconsider: qualifying-suffix, enable-updates */ + if ((strcmp(handle->key, "option-data") == 0) || + (strcmp(handle->key, "allow") == 0) || + (strcmp(handle->key, "deny") == 0) || + (strcmp(handle->key, "interface") == 0) || + (strcmp(handle->key, "valid-lifetime") == 0) || + (strcmp(handle->key, "preferred-lifetime") == 0) || + (strcmp(handle->key, "renew-timer") == 0) || + (strcmp(handle->key, "rebind-timer") == 0) || + (strcmp(handle->key, "boot-file-name") == 0) || + (strcmp(handle->key, "server-hostname") == 0) || + (strcmp(handle->key, "next-server") == 0) || + (strcmp(handle->key, "match-client-id") == 0)) { + TAILQ_INSERT_TAIL(&downs, handle); + continue; + } + /* unknown */ + if (!marked) { + struct string *msg; + + marked = ISC_TRUE; + msg = makeString(-1, "/// moved from group"); + if (name != NULL) + appendString(msg, " "); + appendString(msg, name); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(¶m->comments, comment); + } + comment = createComment("/// unhandled parameter"); + TAILQ_INSERT_TAIL(¶m->comments, comment); + param->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(parent, param, handle->key); + free(handle); + } + TAILQ_FOREACH_SAFE(handle, &downs, nh) { + if (strcmp(handle->key, "option-data") == 0) { + option_data_derive(cfile, handle, hosts); + option_data_derive(cfile, handle, shares); + option_data_derive(cfile, handle, subnets); + derive_classes(cfile, handle, classes); + option_data_derive(cfile, handle, pdpools); + option_data_derive(cfile, handle, pools); + } else if ((strcmp(handle->key, "allow") == 0) || + (strcmp(handle->key, "deny") == 0)) { + derive(handle, pdpools); + derive(handle, pools); + } else if ((strcmp(handle->key, "interface") == 0) || + (strcmp(handle->key, "valid-lifetime") == 0) || + (strcmp(handle->key, "preferred-lifetime") == 0) || + (strcmp(handle->key, "renew-timer") == 0) || + (strcmp(handle->key, "rebind-timer") == 0) || + (strcmp(handle->key, "match-client-id") == 0)) { + derive(handle, shares); + derive(handle, subnets); + } else if ((strcmp(handle->key, "boot-file-name") == 0) || + (strcmp(handle->key, "server-hostname") == 0)) { + derive(handle, hosts); + derive_classes(cfile, handle, classes); + } else if (strcmp(handle->key, "next-server") == 0) { + derive(handle, hosts); + derive(handle, subnets); + derive_classes(cfile, handle, classes); + } else + parse_error(cfile, "unexpected parameter %s to derive", + handle->key); + } + if (hosts != NULL) { + struct element *root; + + root = mapGet(cfile->stack[1], "reservations"); + if (root == NULL) + mapSet(cfile->stack[1], hosts->value, "reservations"); + else + concat(root, hosts->value); + } + if (shares != NULL) { + struct element *upper; + + upper = mapGet(parent, "shared-networks"); + if (upper == NULL) + mapSet(parent, shares->value, "shared-networks"); + else + concat(upper, shares->value); + } + key = local_family == AF_INET ? "subnet4" : "subnet6"; + if (subnets != NULL) { + struct element *upper; + + upper = mapGet(parent, key); + if (upper == NULL) + mapSet(parent, subnets->value, key); + else + concat(upper, subnets->value); + } + if (classes != NULL) { + struct element *upper; + size_t where; + int kind = 0; + + for (where = cfile->stack_top; where > 0; --where) { + kind = cfile->stack[where]->kind; + if ((kind == GROUP_DECL) || (kind == ROOT_GROUP)) + break; + } + if (kind == GROUP_DECL) { + upper = mapGet(cfile->stack[where], "client-classes"); + if (upper == NULL) + mapSet(cfile->stack[where], + classes->value, + "client-classes"); + else + concat_classes(cfile, upper, classes->value); + } + } + if (pdpools != NULL) { + struct element *upper; + + upper = mapGet(parent, "pd-pools"); + if (upper == NULL) + mapSet(parent, pdpools->value, "pools"); + else + concat(upper, pdpools->value); + } + if (pools != NULL) { + struct element *upper; + + upper = mapGet(parent, "pools"); + if (upper == NULL) + mapSet(parent, pools->value, "pools"); + else + concat(upper, pools->value); + } +} + +/* + * Specialized derivation routine for option-data + * (options are identified by space + name and/or code + */ + +static void +option_data_derive(struct parse *cfile, struct handle *src, struct handle *dst) +{ + struct element *list; + struct element *item; + struct element *opt_list; + size_t i; + + if (dst == NULL) + return; + list = dst->value; + assert(list != NULL); + assert(list->type == ELEMENT_LIST); + for (i = 0; i < listSize(list); i++) { + item = listGet(list, i); + assert(item != NULL); + assert(item->type == ELEMENT_MAP); + opt_list = mapGet(item, src->key); + if (opt_list != NULL) { + merge_option_data(src->value, opt_list); + continue; + } + opt_list = copy(src->value); + mapSet(item, opt_list, src->key); + } +} + +/* + * Specialized derivation routine for classes + * (which are by reference so a resolution step is needed) + */ +static void +derive_classes(struct parse *cfile, struct handle *src, struct handle *dst) +{ + struct element *list; + struct element *item; + size_t i; + + if (dst == NULL) + return; + list = dst->value; + assert(list != NULL); + assert(list->type == ELEMENT_LIST); + for (i = 0; i < listSize(list); i++) { + item = listGet(list, i); + assert(item != NULL); + assert(item->type == ELEMENT_MAP); + item = get_class(cfile, item); + if (item == NULL) + parse_error(cfile, "dangling class reference"); + if (strcmp(src->key, "option-data") == 0) { + struct element *opt_list; + + opt_list = mapGet(item, "option-data"); + if (opt_list != NULL) + merge_option_data(src->value, opt_list); + else + mapSet(item, copy(src->value), "option-data"); + continue; + } + if (mapContains(item, src->key)) + continue; + mapSet(item, copy(src->value), src->key); + } +} + +/* fixed-addr-parameter :== ip-addrs-or-hostnames SEMI + ip-addrs-or-hostnames :== ip-addr-or-hostname + | ip-addrs-or-hostnames ip-addr-or-hostname */ + +struct element * +parse_fixed_addr_param(struct parse *cfile, enum dhcp_token type) { + const char *val; + enum dhcp_token token; + struct element *addr; + struct element *addresses; + struct string *address; + + addresses = createList(); + TAILQ_CONCAT(&addresses->comments, &cfile->comments); + + do { + address = NULL; + if (type == FIXED_ADDR) + address = parse_ip_addr_or_hostname(cfile, ISC_TRUE); + else if (type == FIXED_ADDR6) + address = parse_ip6_addr_txt(cfile); + else + parse_error(cfile, "requires FIXED_ADDR[6]"); + if (address == NULL) + parse_error(cfile, "can't parse fixed address"); + addr = createString(address); + /* Take the comment for resolution into multiple addresses */ + TAILQ_CONCAT(&addr->comments, &cfile->comments); + listPush(addresses, addr); + token = peek_token(&val, NULL, cfile); + if (token == COMMA) + token = next_token(&val, NULL, cfile); + } while (token == COMMA); + + parse_semi(cfile); + + /* Sanity */ + if (listSize(addresses) == 0) + parse_error(cfile, "can't get fixed address"); + + return addresses; + +} + +#ifdef notyet +/* Parse the right side of a 'binding value'. + * + * set foo = "bar"; is a string + * set foo = false; is a boolean + * set foo = %31; is a numeric value. + */ +static struct element * +parse_binding_value(struct parse *cfile) +{ + struct element *value = NULL; + struct string *data; + const char *val; + unsigned buflen; + int token; + + token = peek_token(&val, NULL, cfile); + if (token == STRING) { + skip_token(&val, &buflen, cfile); + data = makeString(buflen, val); + value = createString(data); + } else if (token == NUMBER_OR_NAME) { + value = createMap(); + data = parse_hexa(cfile); + mapSet(value, createHexa(data), "const-data"); + } else if (token == PERCENT) { + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "expecting decimal number."); + value = createInt(atol(val)); + } else if (token == NAME) { + token = next_token(&val, NULL, cfile); + if (!strcasecmp(val, "true")) + value = createBool(ISC_TRUE); + else if (!strcasecmp(val, "false")) + value = createBool(ISC_FALSE); + else + parse_error(cfile, "expecting true or false"); + } else + parse_error(cfile, "expecting a constant value."); + + return value; +} +#endif + +/* address-range-declaration :== ip-address ip-address SEMI + | DYNAMIC_BOOTP ip-address ip-address SEMI */ + +void +parse_address_range(struct parse *cfile, int type, size_t where) +{ + struct string *low, *high, *range; + unsigned char addr[4]; + unsigned len = sizeof(addr); + enum dhcp_token token; + const char *val; + struct element *pool; + struct element *r; + struct range *chain; + size_t i; + int kind; + + if ((token = peek_token(&val, NULL, cfile)) == DYNAMIC_BOOTP) { + skip_token(&val, NULL, cfile); + } + + /* Get the bottom address in the range... */ + low = parse_numeric_aggregate(cfile, addr, &len, DOT, 10, 8); + if (low == NULL) + parse_error(cfile, "can't parse range (low)"); + + /* Only one address? */ + token = peek_token(&val, NULL, cfile); + if (token == SEMI) + high = low; + else { + /* Get the top address in the range... */ + high = parse_numeric_aggregate(cfile, addr, &len, DOT, 10, 8); + if (high == NULL) + parse_error(cfile, "can't parse range (high)"); + } + + token = next_token(&val, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "semicolon expected."); + + if (type != POOL_DECL) { + struct element *group; + struct element *pools; +#ifdef want_bootp + struct element *permit; +#endif + + group = cfile->stack[where]; + pool = createMap(); +#ifdef want_bootp + permit = createList(); + permit->skip = ISC_TRUE; + + /* Dynamic pools permit all clients. Otherwise + we prohibit BOOTP clients. */ + if (dynamic) { + struct string *all; + + all = makeString(-1, "all clients"); + listPush(permit, createString(all)); + mapSet(pool, permit, "allow"); + } else { + struct string *dyn_bootp; + + dyn_bootp = makeString(-1, "dynamic bootp clients"); + listPush(permit, createString(dyn_bootp)); + mapSet(pool, permit, "deny"); + } +#endif + + pools = mapGet(group, "pools"); + if (pools == NULL) { + pools = createList(); + pools->kind = POOL_DECL; + mapSet(group, pools, "pools"); + } + listPush(pools, pool); + } else + pool = cfile->stack[where]; + + /* Create the new address range... */ + if (memcmp(high->content, low->content, high->length) < 0) { + struct string *swap; + + swap = low; + low = high; + high = swap; + } + range = makeStringExt(low->length, low->content, 'I'); + appendString(range, " - "); + concatString(range, makeStringExt(high->length, high->content, 'I')); + + r = createString(range); + TAILQ_CONCAT(&r->comments, &cfile->comments); + + mapSet(pool, r, "pool"); + + chain = (struct range *)malloc(sizeof(*chain)); + if (chain == NULL) + parse_error(cfile, "can't allocate range"); + memset(chain, 0, sizeof(*chain)); + chain->pool = pool; + for (i = where; i > 0; --i) { + kind = cfile->stack[i]->kind; + if (kind == SHARED_NET_DECL) { + chain->share = cfile->stack[i]; + break; + } + } + chain->low = low; + TAILQ_INSERT_TAIL(&known_ranges, chain); +} + +/* address-range6-declaration :== ip-address6 ip-address6 SEMI + | ip-address6 SLASH number SEMI + | ip-address6 [SLASH number] TEMPORARY SEMI */ + +void +parse_address_range6(struct parse *cfile, int type, size_t where) +{ + struct string *low, *high, *range; + enum dhcp_token token; + const char *val; + isc_boolean_t is_temporary = ISC_FALSE; + struct element *pool; + struct element *r; + struct range *chain; + size_t i; + int kind; + + if (local_family != AF_INET6) + parse_error(cfile, "range6 statement is only supported " + "in DHCPv6 mode."); + + /* + * Read starting address as text. + */ + low = parse_ip6_addr_txt(cfile); + if (low == NULL) + parse_error(cfile, "can't parse range6 address (low)"); + range = allocString(); + concatString(range, low); + + /* + * See if we we're using range or CIDR notation or TEMPORARY + */ + token = peek_token(&val, NULL, cfile); + if (token == SLASH) { + appendString(range, val); + /* + * '/' means CIDR notation, so read the bits we want. + */ + skip_token(NULL, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "expecting number"); + /* + * no sanity checks + */ + appendString(range, val); + /* + * can be temporary (RFC 4941 like) + */ + token = peek_token(&val, NULL, cfile); + if (token == TEMPORARY) { + is_temporary = ISC_TRUE; + appendString(range, " "); + appendString(range, val); + skip_token(NULL, NULL, cfile); + } + } else if (token == TEMPORARY) { + /* + * temporary (RFC 4941) + */ + is_temporary = ISC_TRUE; + appendString(range, "/64 "); + appendString(range, val); + skip_token(NULL, NULL, cfile); + } else { + /* + * No '/', so we are looking for the end address of + * the IPv6 pool. + */ + high = parse_ip6_addr_txt(cfile); + if (high == NULL) + parse_error(cfile, + "can't parse range6 address (high)"); + /* No sanity checks */ + appendString(range, " - "); + appendString(range, high->content); + } + + token = next_token(NULL, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "semicolon expected."); + + if (type != POOL_DECL) { + struct element *group; + struct element *pools; + + group = cfile->stack[where]; + pool = createMap(); + pools = mapGet(group, "pools"); + if (pools == NULL) { + pools = createList(); + pools->kind = POOL_DECL; + mapSet(group, pools, "pools"); + } + listPush(pools, pool); + } else + pool = cfile->stack[where]; + + r = createString(range); + TAILQ_CONCAT(&r->comments, &cfile->comments); + if (is_temporary) { + pool->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(pool, r, "pool"); + + chain = (struct range *)malloc(sizeof(*chain)); + if (chain == NULL) + parse_error(cfile, "can't allocate range"); + memset(chain, 0, sizeof(*chain)); + chain->pool = pool; + for (i = where; i > 0; --i) { + kind = cfile->stack[i]->kind; + if (kind == SHARED_NET_DECL) { + chain->share = cfile->stack[i]; + break; + } + } + chain->low = low; + TAILQ_INSERT_TAIL(&known_ranges, chain); +} + +/* prefix6-declaration :== ip-address6 ip-address6 SLASH number SEMI */ + +void +parse_prefix6(struct parse *cfile, int type, size_t where) +{ + struct string *lo, *hi; + int plen; + int bits; + enum dhcp_token token; + const char *val; + struct element *pool; + struct element *prefix; + + if (local_family != AF_INET6) + parse_error(cfile, "prefix6 statement is only supported " + "in DHCPv6 mode."); + + /* + * Read starting and ending address as text. + */ + lo = parse_ip6_addr_txt(cfile); + if (lo == NULL) + parse_error(cfile, "can't parse prefix6 address (low)"); + + hi = parse_ip6_addr_txt(cfile); + if (hi == NULL) + parse_error(cfile, "can't parse prefix6 address (high)"); + + /* + * Next is '/' number ';'. + */ + token = next_token(NULL, NULL, cfile); + if (token != SLASH) + parse_error(cfile, "expecting '/'"); + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "expecting number"); + bits = atoi(val); + if ((bits <= 0) || (bits >= 128)) + parse_error(cfile, "networks have 0 to 128 bits (exclusive)"); + + token = next_token(NULL, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "semicolon expected."); + + if (type != POOL_DECL) { + struct element *group; + struct element *pools; + + group = cfile->stack[where]; + pool = createMap(); + pools = mapGet(group, "pd-pools"); + if (pools == NULL) { + pools = createList(); + pools->kind = POOL_DECL; + mapSet(group, pools, "pd-pools"); + } + listPush(pools, pool); + } else + pool = cfile->stack[where]; + + prefix = createString(lo); + TAILQ_CONCAT(&prefix->comments, &cfile->comments); + mapSet(pool, prefix, "prefix"); + mapSet(pool, createInt(bits), "delegated-len"); + plen = get_prefix_length(lo->content, hi->content); + if (plen >= 0) + mapSet(pool, createInt(plen), "prefix-len"); + else { + if (!pool->skip) + cfile->issue_counter++; + pool->skip = ISC_TRUE; + mapSet(pool, createString(hi), "prefix-highest"); + } +} + +/* fixed-prefix6 :== ip6-address SLASH number SEMI */ + +void +parse_fixed_prefix6(struct parse *cfile, size_t host_decl) +{ + struct string *ia; + enum dhcp_token token; + const char *val; + struct element *host; + struct element *prefixes; + struct element *prefix; + + if (local_family != AF_INET6) + parse_error(cfile, "fixed-prefix6 statement is only " + "supported in DHCPv6 mode."); + + /* + * Get the fixed-prefix list. + */ + host = cfile->stack[host_decl]; + prefixes = mapGet(host, "prefixes"); + if (prefixes == NULL) { + prefixes = createList(); + mapSet(host, prefixes, "prefixes"); + } + + ia = parse_ip6_addr_txt(cfile); + if (ia == NULL) + parse_error(cfile, "can't parse fixed-prefix6 address"); + token = next_token(&val, NULL, cfile); + if (token != SLASH) + parse_error(cfile, "expecting '/'"); + appendString(ia, val); + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "expecting number"); + appendString(ia, val); + token = next_token(NULL, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "semicolon expected."); + + prefix = createString(ia); + TAILQ_CONCAT(&prefix->comments, &cfile->comments); + listPush(prefixes, prefix); +} + +/*! + * + * \brief Parse a pool6 statement + * + * Pool statements are used to group declarations and permit & deny information + * with a specific address range. They must be declared within a shared network + * or subnet and there may be multiple pools withing a shared network or subnet. + * Each pool may have a different set of permit or deny options. + * + * \param[in] cfile = the configuration file being parsed + * \param[in] type = the type of the enclosing statement. This must be + * SUBNET_DECL for this function. + * + * \return + * void - This function either parses the statement and updates the structures + * or it generates an error message and possible halts the program if + * it encounters a problem. + */ + +void +parse_pool6_statement(struct parse *cfile, int type) +{ + enum dhcp_token token; + const char *val; + isc_boolean_t done = ISC_FALSE; + struct element *pool; + struct element *pools; + struct element *pdpool; + struct element *pdpools; + struct element *permit; + struct element *prohibit; + int declaration = 0; + unsigned range_counter = 0; + unsigned prefix_counter = 0; + + if (local_family != AF_INET6) + parse_error(cfile, "pool6 statement is only supported " + "in DHCPv6 mode."); + + pool = createMap(); + pool->kind = POOL_DECL; + TAILQ_CONCAT(&pool->comments, &cfile->comments); + + if (type != SUBNET_DECL) + parse_error(cfile, "pool6s are only valid inside " + "subnet statements."); + parse_lbrace(cfile); + + stackPush(cfile, pool); + type = POOL_DECL; + + permit = createList(); + prohibit = createList(); + + do { + token = peek_token(&val, NULL, cfile); + switch (token) { + case RANGE6: + skip_token(NULL, NULL, cfile); + parse_address_range6(cfile, type, cfile->stack_top); + range_counter++; + break; + + case PREFIX6: + skip_token(NULL, NULL, cfile); + parse_prefix6(cfile, type, cfile->stack_top); + mapSet(pool, createNull(), "***mark***"); + prefix_counter++; + break; + + case ALLOW: + skip_token(NULL, NULL, cfile); + get_permit(cfile, permit); + break; + + case DENY: + skip_token(NULL, NULL, cfile); + get_permit(cfile, prohibit); + break; + + case RBRACE: + skip_token(&val, NULL, cfile); + done = ISC_TRUE; + break; + + case END_OF_FILE: + /* + * We can get to END_OF_FILE if, for instance, + * the parse_statement() reads all available tokens + * and leaves us at the end. + */ + parse_error(cfile, "unexpected end of file"); + + default: + declaration = parse_statement(cfile, POOL_DECL, + declaration); + break; + } + } while (!done); + + cfile->stack_top--; + + generate_class(cfile, pool, permit, prohibit); + + /* + * Spread and eventually split between pools and pd-pools + */ + if (prefix_counter == 0) { + /* we need pools list */ + pools = mapGet(cfile->stack[cfile->stack_top], "pools"); + if (pools == NULL) { + pools = createList(); + pools->kind = POOL_DECL; + mapSet(cfile->stack[cfile->stack_top], pools, "pools"); + } + + /* no address or prefix range */ + if (range_counter == 0) { + struct comment *comment; + + comment = createComment("empty pool6"); + TAILQ_INSERT_TAIL(&pool->comments, comment); + pool->skip = ISC_TRUE; + cfile->issue_counter++; + listPush(pools, pool); + return; + } + } else { + /* we need pd-pools list */ + pdpools = mapGet(cfile->stack[cfile->stack_top], "pd-pools"); + if (pdpools == NULL) { + pdpools = createList(); + pdpools->kind = POOL_DECL; + mapSet(cfile->stack[cfile->stack_top], + pdpools, "pd-pools"); + } + + /* split and purge copies */ + pdpool = copy(pool); + while (mapContains(pdpool, "pool")) + mapRemove(pdpool, "pool"); + while (mapContains(pool, "prefix")) + mapRemove(pool, "prefix"); + while (mapContains(pool, "prefix-len")) + mapRemove(pool, "prefix-len"); + while (mapContains(pool, "delegated-len")) + mapRemove(pool, "delegated-len"); + while (mapContains(pool, "excluded-prefix")) + mapRemove(pool, "excluded-prefix"); + while (mapContains(pool, "excluded-prefix-len")) + mapRemove(pool, "excluded-prefix-len"); + while (mapContains(pool, "***mark***")) + mapRemove(pool, "***mark***"); + + /* spread extra prefixes into pdpool copies */ + while (--prefix_counter != 0) { + struct handle *handle; + struct element *first; + struct element *saved; + isc_boolean_t seen = ISC_FALSE; + + first = createMap(); + saved = copy(pdpool); + while (mapSize(pdpool) > 0) { + handle = mapPop(pdpool); + if ((handle == NULL) || + (handle->key == NULL) || + (handle->value == NULL)) + parse_error(cfile, "bad pdpool entry"); + if (strcmp(handle->key, "***mark***") == 0) { + if (!seen) { + mapRemove(saved, handle->key); + seen = ISC_TRUE; + } + continue; + } + if ((strcmp(handle->key, "prefix") != 0) && + (strcmp(handle->key, "prefix-len") != 0) && + (strcmp(handle->key, + "delegated-len") != 0) && + (strcmp(handle->key, + "excluded-prefix") != 0) && + (strcmp(handle->key, + "excluded-prefix-len") != 0)) + mapSet(first, handle->value, + handle->key); + else if (!seen) { + mapSet(first, handle->value, + handle->key); + mapRemove(saved, handle->key); + } + } + listPush(pdpools, first); + pdpool = saved; + } + if (!mapContains(pdpool, "***mark***")) + parse_error(cfile, "can't find prefix marker"); + mapRemove(pdpool, "***mark***"); + if (mapContains(pdpool, "***mark***")) + parse_error(cfile, "unexpected prefix marker"); + listPush(pdpools, pdpool); + } + + /* Do pools now */ + if (range_counter != 0) { + /* we need pools list */ + pools = mapGet(cfile->stack[cfile->stack_top], "pools"); + if (pools == NULL) { + pools = createList(); + pools->kind = POOL_DECL; + mapSet(cfile->stack[cfile->stack_top], pools, "pools"); + } + + /* spread extra prefixes into pool copies */ + while (--range_counter != 0) { + struct handle *handle; + struct element *first; + struct element *saved; + isc_boolean_t seen = ISC_FALSE; + + first = createMap(); + saved = copy(pool); + while (mapSize(pool) > 0) { + handle = mapPop(pool); + if ((handle == NULL) || + (handle->key == NULL) || + (handle->value == NULL)) + parse_error(cfile, "bad pool entry"); + if (strcmp(handle->key, "pool") != 0) + mapSet(first, handle->value, + handle->key); + else if (!seen) { + mapSet(first, handle->value, + handle->key); + mapRemove(saved, "pool"); + seen = ISC_TRUE; + } + } + listPush(pools, first); + pool = saved; + } + listPush(pools, pool); + } +} + +/* allow-deny-keyword :== BOOTP + | BOOTING + | DYNAMIC_BOOTP + | UNKNOWN_CLIENTS */ + +struct element * +parse_allow_deny(struct parse *cfile, int flag) +{ + enum dhcp_token token; + const char *val; + const char *value; + const char *name; + struct element *config; + struct option *option; + + switch (flag) { + case 0: + value = "deny"; + break; + case 1: + value = "allow"; + break; + case 2: + value = "ignore"; + break; + default: + value = "unknown?"; + break; + } + + token = next_token(&val, NULL, cfile); + switch (token) { + case TOKEN_BOOTP: + name = "allow-bootp"; + break; + + case BOOTING: + name = "allow-booting"; + break; + + case DYNAMIC_BOOTP: + name = "dynamic-bootp"; + break; + + case UNKNOWN_CLIENTS: + name = "boot-unknown-clients"; + break; + + case DUPLICATES: + name = "duplicates"; + break; + + case DECLINES: + name = "declines"; + break; + + case CLIENT_UPDATES: + name = "client-updates"; + break; + + case LEASEQUERY: + name = "leasequery"; + break; + + default: + parse_error(cfile, "expecting allow/deny key"); + } + parse_semi(cfile); + + config = createMap(); + mapSet(config, createString(makeString(-1, value)), "value"); + mapSet(config, createString(makeString(-1, name)), "name"); + option = option_lookup_name("server", name); + if (option == NULL) + parse_error(cfile, "unknown allow/deny keyword (%s)", name); + mapSet(config, createInt(option->code), "code"); + config->skip = ISC_TRUE; + cfile->issue_counter++; + return config; +} + +/* + * When we parse a server-duid statement in a config file, we will + * have the type of the server DUID to generate, and possibly the + * actual value defined. + * + * server-duid llt; + * server-duid llt ethernet|ieee802|fddi 213982198 00:16:6F:49:7D:9B; + * server-duid ll; + * server-duid ll ethernet|ieee802|fddi 00:16:6F:49:7D:9B; + * server-duid en 2495 "enterprise-specific-identifier-1234"; + */ +void +parse_server_duid_conf(struct parse *cfile) { + enum dhcp_token token; + const char *val; + unsigned int len; + struct string *ll_addr; + struct element *duid; + struct element *item; + int ll_type; + + duid = createMap(); + TAILQ_CONCAT(&duid->comments, &cfile->comments); + + /* + * Consume the SERVER_DUID token. + */ + next_token(&val, NULL, cfile); + + /* + * Obtain the DUID type. + */ + token = next_token(&val, NULL, cfile); + + /* + * Enterprise is the easiest - enterprise number and raw data + * are required. + */ + if (token == EN) { + item = createString(makeString(-1, "EN")); + mapSet(duid, item, "type"); + + /* + * Get enterprise number and identifier. + */ + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "enterprise number expected"); + item = createInt(atoi(val)); + mapSet(duid, item, "enterprise-id"); + + token = next_token(&val, &len, cfile); + if (token != STRING) + parse_error(cfile, "identifier expected"); + /* Kea requires a hexadecimal identifier */ + if (is_hexa_only(val, len)) + item = createString(makeString(len, val)); + else + item = createString(makeStringExt(len, val, 'X')); + mapSet(duid, item, "identifier"); + } + + /* + * Next easiest is the link-layer DUID. It consists only of + * the LL directive, or optionally the specific value to use. + * + * If we have LL only, then we set the type. If we have the + * value, then we set the actual DUID. + */ + else if (token == LL) { + item = createString(makeString(-1, "LL")); + mapSet(duid, item, "type"); + + if (peek_token(NULL, NULL, cfile) != SEMI) { + /* + * Get our hardware type and address. + */ + token = next_token(NULL, NULL, cfile); + switch (token) { + case ETHERNET: + ll_type = HTYPE_ETHER; + break; + case TOKEN_RING: + ll_type = HTYPE_IEEE802; + break; + case TOKEN_FDDI: + ll_type = HTYPE_FDDI; + break; + default: + parse_error(cfile, "hardware type expected"); + } + item = createInt(ll_type); + mapSet(duid, item, "htype"); + + ll_addr = parse_hexa(cfile); + if (ll_addr == NULL) + parse_error(cfile, + "can't get hardware address"); + item = createString(ll_addr); + mapSet(duid, item, "identifier"); + } + } + + /* + * Finally the link-layer DUID plus time. It consists only of + * the LLT directive, or optionally the specific value to use. + * + * If we have LLT only, then we set the type. If we have the + * value, then we set the actual DUID. + */ + else if (token == LLT) { + item = createString(makeString(-1, "LLT")); + mapSet(duid, item, "type"); + + if (peek_token(NULL, NULL, cfile) != SEMI) { + /* + * Get our hardware type, timestamp, and address. + */ + token = next_token(NULL, NULL, cfile); + switch (token) { + case ETHERNET: + ll_type = HTYPE_ETHER; + break; + case TOKEN_RING: + ll_type = HTYPE_IEEE802; + break; + case TOKEN_FDDI: + ll_type = HTYPE_FDDI; + break; + default: + parse_error(cfile, "hardware type expected"); + } + item = createInt(ll_type); + mapSet(duid, item, "htype"); + + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "timestamp expected"); + item = createInt(atoi(val)); + mapSet(duid, item, "time"); + + ll_addr = parse_hexa(cfile); + if (ll_addr == NULL) + parse_error(cfile, + "can't get hardware address"); + item = createString(ll_addr); + mapSet(duid, item, "identifier"); + } + } + + /* + * If users want they can use a number for DUID types. + * This is useful for supporting future, not-yet-defined + * DUID types. + * + * In this case, they have to put in the complete value. + * + * This also works for existing DUID types of course. + */ + else if (token == NUMBER) { + item = createString(makeString(-1, val)); + item->skip = ISC_TRUE; + /* Kea wants EN, LL or LLT so skip the whole thing */ + duid->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(duid, item, "type"); + + token = next_token(&val, &len, cfile); + if (token != STRING) + parse_error(cfile, "identifier expected"); + item = createString(makeString(len, val)); + mapSet(duid, item, "identifier"); + } + + /* + * Anything else is an error. + */ + else + parse_error(cfile, "DUID type of LLT, EN, or LL expected"); + + /* + * Finally consume our trailing semicolon. + */ + token = next_token(NULL, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "semicolon expected"); + + /* server-id is a global parameter */ + if (mapContains(cfile->stack[1], "server-id")) + parse_error(cfile, "there is already a server-id"); + /* DHCPv6 only but not fatal */ + if ((local_family != AF_INET6) && !duid->skip) { + duid->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(cfile->stack[1], duid, "server-id"); +} + +/* Check whether the argument is encoded in hexadecimal or not */ +static isc_boolean_t +is_hexa_only(const char *s, unsigned l) +{ + unsigned i; + + for (i = 0; i < l; i++) + if (!isxdigit((int)s[i])) + return ISC_FALSE; + return ISC_TRUE; +} + +/*! + * + * \brief Parse (and execute) a directive (extension) + * + * OPTION SPACE <name> [ALIAS <kea-name>] [KNOWN*2|UNKNOWN*2|DYNAMIC] + * OPTION <universe>.<name> [CHECK] + * [ALIAS <name>] + * [CODE <code> = "<format>"] + * [KNOWN*2|UNKNOWN*2|DYNAMIC] + * [LOCAL|DEFINE] + */ + +void +parse_directive(struct parse *cfile) +{ + enum dhcp_token token; + const char *val; + isc_boolean_t known; + struct option *option; + + token = peek_token(&val, NULL, cfile); + + switch (token) { + case OPTION: + skip_token(&val, NULL, cfile); + token = peek_token(&val, NULL, cfile); + if (token == SPACE) { + parse_option_space_dir(cfile); + return; + } + + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_TRUE, &known); + token = next_token(&val, NULL, cfile); + if (token == CHECK) { + struct string *datatype; + isc_boolean_t is_array = ISC_FALSE; + isc_boolean_t encapsulate = ISC_FALSE; + + datatype = convert_format(option->format, + &is_array, + &encapsulate); + printf("option ISC DHCP (Kea)\n" + " %s.%s (%s.%s)\n" + " format \"%s\" (type \"%s\" " + "array %s encap %s)\n" + " status %s\n", + option->space->old, option->old, + option->space->name, option->name, + option->format, datatype->content, + is_array ? "true" : "false", + encapsulate ? "true" : "false", + display_status(option->status)); + parse_semi(cfile); + return; + } + if (option->space->status == special) + parse_error(cfile, "attempt to modify config %s.%s", + option->space->old, option->name); + if (token == ALIAS) { + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, + "expecting identifier after " + "alias keyword."); + if (option->status != dynamic) + parse_error(cfile, + "attempt to rename %s.%s to %s", + option->space->name, + option->name, val); + option->name = strdup(val); + parse_semi(cfile); + return; + } + if (token == CODE) { + parse_option_code_dir(cfile, option); + return; + } + if ((token == KNOWN) || (token == UNKNOWN) || + (token == DYNAMIC)) { + parse_option_status_dir(cfile, option, token); + return; + } + if (token == LOCAL) { + parse_option_local_dir(cfile, option); + parse_semi(cfile); + return; + } + if (token == DEFINE) { + parse_option_define_dir(cfile, option); + parse_semi(cfile); + return; + } + parse_error(cfile, "unknown option directive %s", val); + + default: + parse_error(cfile, "unknown directive %s", val); + } +} + +/* Set alias and status for option spaces */ + +void +parse_option_space_dir(struct parse *cfile) +{ + enum dhcp_token token; + const char *val; + struct space *space; + + skip_token(NULL, NULL, cfile); /* Discard SPACE */ + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, "expecting identifier."); + space = space_lookup(val); + if (space == NULL) + parse_error(cfile, "can't find space '%s", val); + + token = next_token(&val, NULL, cfile); + if (token == CHECK) { + printf("space ISC DHCP (kea)\n" + " %s (%s)\n status %s\n%s", + space->old, space->name, + display_status(space->status), + space->vendor != NULL ? " vendor\n" : ""); + parse_semi(cfile); + return; + } + if (token == ALIAS) { + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, + "expecting identifier after " + "alias keyword."); + if (space->status != dynamic) + parse_error(cfile, + "attempt to rename %s to %s", + space->name, val); + space->name = strdup(val); + parse_semi(cfile); + return; + } + if (token == DYNAMIC) + space->status = dynamic; + else if (token == UNKNOWN) { + token = next_token(NULL, NULL, cfile); + if (token == KNOWN) + space->status = known; + else if (token == UNKNOWN) + space->status = kea_unknown; + else + parse_error(cfile, "expected KNOW or UNKNOWN"); + } else if (token != UNKNOWN) + parse_error(cfile, "expected KNOW or UNKNOWN or DYNAMIC"); + else { + if (token == KNOWN) + space->status = isc_dhcp_unknown; + else if (token == UNKNOWN) + parse_error(cfile, "illicit combination: space " + "%s is known by nobody", space->name); + else + parse_error(cfile, "expected KNOW or UNKNOWN"); + } + parse_semi(cfile); +} + +/* Alternative to parse_option_code_decl using the raw ISC DHCP format */ + +void +parse_option_code_dir(struct parse *cfile, struct option *option) +{ + const char *val; + enum dhcp_token token; + unsigned code; + struct element *def; + struct element *optdef; + struct string *datatype; + isc_boolean_t is_array = ISC_FALSE; + isc_boolean_t encapsulate = ISC_FALSE; + + def = createMap(); + mapSet(def, + createString(makeString(-1, option->space->name)), + "space"); + mapSet(def, createString(makeString(-1, option->name)), "name"); + + /* Parse the option code. */ + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "expecting option code number."); + code = atoi(val); + mapSet(def, createInt(code), "code"); + + /* We have the code so we can get the real option now */ + if (option->code == 0) { + struct option *from_code; + + option->code = code; + from_code = option_lookup_code(option->space->old, code); + if (from_code != NULL) + option = from_code; + } + + /* Redefinitions are not allowed */ + if ((option->status != dynamic) || + (strcmp(option->format, "u") != 0)) + parse_error(cfile, "attempt to redefine %s.%s code %u", + option->space->name, option->name, code); + + token = next_token(&val, NULL, cfile); + if (token != EQUAL) + parse_error(cfile, "expecting \"=\""); + token = next_token(&val, NULL, cfile); + if (token != STRING) + parse_error(cfile, "expecting format string"); + option->format = strdup(val); + parse_semi(cfile); + + datatype = convert_format(val, &is_array, &encapsulate); + + if ((datatype == NULL) && (strchr(datatype->content, '?') != NULL)) + parse_error(cfile, "failed to convert format \"%s\" for " + "option %s.%s code %u", + val, option->space->name, option->name, code); + /* todo */ + if (encapsulate) + parse_error(cfile, "option %s.%s code %u encapsulate?", + option->space->name, option->name, code); + + if (strchr(datatype->content, ',') == NULL) + mapSet(def, createString(datatype), "type"); + else { + mapSet(def, createString(datatype), "record-types"); + mapSet(def, createString(makeString(-1, "record")), "type"); + } + if (is_array) + mapSet(def, createBool(ISC_TRUE), "array"); + + optdef = mapGet(cfile->stack[1], "option-def"); + if (optdef == NULL) { + optdef = createList(); + mapSet(cfile->stack[1], optdef, "option-def"); + } + listPush(optdef, def); +} + +/* Update the option status for instance to add standard options */ + +void +parse_option_status_dir(struct parse *cfile, struct option *option, + enum dhcp_token token) +{ + if (token == DYNAMIC) + option->status = dynamic; + else if (token == KNOWN) { + token = next_token(NULL, NULL, cfile); + if (token == KNOWN) + option->status = known; + else if (token == UNKNOWN) + option->status = kea_unknown; + else + parse_error(cfile, "expected KNOW or UNKNOWN"); + } else if (token != UNKNOWN) + parse_error(cfile, "expected KNOW or UNKNOWN or DYNAMIC"); + else { + if (token == KNOWN) + option->status = isc_dhcp_unknown; + else if (token == UNKNOWN) + parse_error(cfile, "illicit combination: option " + "%s.%s code %u is known by nobody", + option->space->name, option->name, + option->code); + else + parse_error(cfile, "expected KNOW or UNKNOWN"); + } + parse_semi(cfile); +} + +/* Make the option definition not exported to Kea */ + +void +parse_option_local_dir(struct parse *cfile, struct option *option) +{ + struct element *optdef; + struct element *def; + struct element *elem; + size_t i; + + def = NULL; + if (option->code == 0) + parse_error(cfile, "unknown code for option %s.%s", + option->space->name, option->name); + + optdef = mapGet(cfile->stack[1], "option-def"); + if (optdef == NULL) { + optdef = createList(); + mapSet(cfile->stack[1], optdef, "option-def"); + goto not_found; + } + for (i = 0; i < listSize(optdef); i++) { + def = listGet(optdef, i); + elem = mapGet(def, "space"); + if ((elem == NULL) || (elem->type != ELEMENT_STRING)) + parse_error(cfile, "got an option definition " + "without space at %u", (unsigned)i); + if (strcmp(option->space->name, + stringValue(elem)->content) != 0) + continue; + elem = mapGet(def, "code"); + if ((elem == NULL) || (elem->type != ELEMENT_INTEGER)) + parse_error(cfile, "got an option definition " + "without code at %u", (unsigned)i); + if (intValue(elem) == option->code) + break; + } + if (def == NULL) + goto not_found; + def->skip = ISC_TRUE; + mapSet(def, createNull(), "no-export"); + return; + +not_found: + parse_error(cfile, "can't find option %s.%s code %u in definitions", + option->space->name, option->name, option->code); +} + +/* Make the opposite: force the definition */ + +void +parse_option_define_dir(struct parse *cfile, struct option *option) +{ + struct element *optdef; + struct element *def; + struct element *elem; + struct string *datatype; + isc_boolean_t is_array = ISC_FALSE; + isc_boolean_t encapsulate = ISC_FALSE; + size_t i; + + def = NULL; + if (option->code == 0) + parse_error(cfile, "unknown code for option %s.%s", + option->space->name, option->name); + + optdef = mapGet(cfile->stack[1], "option-def"); + if (optdef == NULL) { + optdef = createList(); + mapSet(cfile->stack[1], optdef, "option-def"); + goto no_search; + } + for (i = 0; i < listSize(optdef); i++) { + def = listGet(optdef, i); + elem = mapGet(def, "space"); + if ((elem == NULL) || (elem->type != ELEMENT_STRING)) + parse_error(cfile, "got an option definition " + "without space at %u", (unsigned)i); + if (strcmp(option->space->name, + stringValue(elem)->content) != 0) + continue; + elem = mapGet(def, "code"); + if ((elem == NULL) || (elem->type != ELEMENT_INTEGER)) + parse_error(cfile, "got an option definition " + "without code at %u", (unsigned)i); + if (intValue(elem) == option->code) + parse_error(cfile, "unexpected definition for " + "option %s.%s code %u", + option->space->name, option->name, + option->code); + } +no_search: + def = createMap(); + mapSet(def, + createString(makeString(-1, option->space->name)), + "space"); + mapSet(def, createString(makeString(-1, option->name)), "name"); + mapSet(def, createInt(option->code), "code"); + + datatype = convert_format(option->format, &is_array, &encapsulate); + + if ((datatype == NULL) && (strchr(datatype->content, '?') != NULL)) + parse_error(cfile, "failed to convert format \"%s\" for " + "option %s.%s code %u", + option->format, option->space->name, + option->name, option->code); + /* todo */ + if (encapsulate) + parse_error(cfile, "option %s.%s code %u encapsulate?", + option->space->name, option->name, option->code); + + if (strchr(datatype->content, ',') == NULL) + mapSet(def, createString(datatype), "type"); + else { + mapSet(def, createString(datatype), "record-types"); + mapSet(def, createString(makeString(-1, "record")), "type"); + } + if (is_array) + mapSet(def, createBool(ISC_TRUE), "array"); + + listPush(optdef, def); + + return; +} + +/* + * Push new interface on the interface list when it is not already. + */ + +static void +new_network_interface(struct parse *cfile, struct element *iface) +{ + struct element *ifconf; + struct element *iflist; + struct string *name = stringValue(iface); + int i; + + ifconf = mapGet(cfile->stack[1], "interfaces-config"); + if (ifconf == NULL) { + ifconf = createMap(); + mapSet(cfile->stack[1], ifconf, "interfaces-config"); + } + + iflist = mapGet(ifconf, "interfaces"); + if (iflist == NULL) { + iflist = createList(); + mapSet(ifconf, iflist, "interfaces"); + } + + for (i = 0; i < listSize(iflist); i++) { + struct element *item; + + item = listGet(iflist, i); + if ((item != NULL) && + (item->type == ELEMENT_STRING) && + eqString(stringValue(item), name)) + return; + } + + listPush(iflist, createString(name)); +} + +/* Convert address and mask in binary into address/len text */ + +static const uint32_t bitmasks[32 + 1] = { + 0xffffffff, 0x7fffffff, 0x3fffffff, 0x1fffffff, + 0x0fffffff, 0x07ffffff, 0x03ffffff, 0x01ffffff, + 0x00ffffff, 0x007fffff, 0x003fffff, 0x001fffff, + 0x000fffff, 0x0007ffff, 0x0003ffff, 0x0001ffff, + 0x0000ffff, 0x00007fff, 0x00003fff, 0x00001fff, + 0x00000fff, 0x000007ff, 0x000003ff, 0x000001ff, + 0x000000ff, 0x0000007f, 0x0000003f, 0x0000001f, + 0x0000000f, 0x00000007, 0x00000003, 0x00000001, + 0x00000000 }; + +static struct string * +addrmask(const struct string *address, const struct string *netmask) +{ + struct string *result; + uint8_t plen; + uint32_t mask; + + result = makeStringExt(address->length, address->content, 'I'); + + memcpy(&mask, netmask->content, 4); + mask = ntohl(mask); + for (plen = 0; plen <= 32; ++plen) + if (~mask == bitmasks[plen]) + break; + if (plen > 32) + return NULL; + + appendString(result, "/"); + concatString(result, makeStringExt(1, (char *)&plen, 'B')); + return result; +} + +/* + * find a place where to put a reservation + * (reservations aka hosts must be in a subnet in Kea < 1.5) + * (defaulting to the last defined subnet (e.g. for reservations + * without any address). + * (first step is to find an enclosing group). + */ + +static struct element * +find_match(struct parse *cfile, struct element *host, + isc_boolean_t *used_heuristicp) +{ + struct element *address; + struct subnet *subnet; + char addr[16]; + size_t group; + size_t i, len; + int kind; + + if (global_hr) { + struct element *hosts; + + hosts = mapGet(cfile->stack[1], "reservations"); + if (!hosts) { + mapSet(cfile->stack[1], + createString(makeString(-1, "global")), + "reservation-mode"); + hosts = createList(); + mapSet(cfile->stack[1], hosts, "reservations"); + } + *used_heuristicp = ISC_FALSE; + return cfile->stack[1]; + } + + for (group = cfile->stack_top; group > 0; --group) { + kind = cfile->stack[group]->kind; + if ((kind == GROUP_DECL) || (kind == ROOT_GROUP)) + break; + } + if (!group) + parse_error(cfile, "can't find root group"); + if (kind == GROUP_DECL) + return cfile->stack[group]; + + if (local_family == AF_INET) { + address = mapGet(host, "ip-address"); + if (address == NULL) { + if (TAILQ_EMPTY(&known_subnets)) + return cfile->stack[1]; + if (used_heuristicp) + *used_heuristicp = ISC_TRUE; + return TAILQ_LAST(&known_subnets, subnets)->subnet; + } + len = 4; + } else { + address = mapGet(host, "ip-addresses"); + if (address == NULL) { + if (TAILQ_EMPTY(&known_subnets)) + return cfile->stack[1]; + if (used_heuristicp) + *used_heuristicp = ISC_TRUE; + return TAILQ_LAST(&known_subnets, subnets)->subnet; + } + address = listGet(address, 0); + if (address == NULL) + return TAILQ_LAST(&known_subnets, subnets)->subnet; + len = 16; + } + + if (inet_pton(local_family, stringValue(address)->content, addr) != 1) + parse_error(cfile, "bad address %s", + stringValue(address)->content); + TAILQ_FOREACH(subnet, &known_subnets) { + isc_boolean_t matching = ISC_TRUE; + + if (subnet->mask->length != len) + continue; + for (i = 0; i < len; i++) + if ((addr[i] & subnet->mask->content[i]) != + subnet->addr->content[i]) { + matching = ISC_FALSE; + break; + } + if (matching) + return subnet->subnet; + } + return cfile->stack[1]; +} + +/* + * find a subnet where to put a pool + * (pools are not allowed at shared-network level in Kea) + */ + +static struct element * +find_location(struct element *share, struct range *range) +{ + struct subnet *subnet; + size_t i; + + TAILQ_FOREACH(subnet, &known_subnets) { + isc_boolean_t matching = ISC_TRUE; + + if (subnet->share != share) + continue; + if (subnet->mask->length != range->low->length) + continue; + for (i = 0; i < range->low->length; i++) + if ((range->low->content[i] & + subnet->mask->content[i]) != + subnet->addr->content[i]) { + matching = ISC_FALSE; + break; + } + if (matching) + return subnet->subnet; + } + return NULL; +} + +/* + * Compute a prefix length from lower - higher IPv6 addresses. + */ + +static const uint8_t bytemasks[8] = { + 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff +}; + +static int +get_prefix_length(const char *low, const char *high) +{ + uint8_t lo[16]; + uint8_t hi[16]; + uint8_t xor[16]; + int i, plen; + + if ((inet_pton(AF_INET6, low, lo) != 1) || + (inet_pton(AF_INET6, high, hi) != 1)) + return -100; + + for (i = 0; i < 16; i++) + xor[i] = lo[i] ^ hi[i]; + for (plen = 0; plen < 128; plen += 8) + if (xor[plen / 8] != 0) + break; + if (plen == 128) + return plen; + for (i = (plen / 8) + 1; i < 16; i++) + if (xor[i] != 0) + return -2; + for (i = 0; i < 8; i++) { + uint8_t msk = ~xor[plen / 8]; + + if (msk == bytemasks[i]) + return plen + i + 1; + } + return -1; +} + +/* + * Get a (global) class from its reference, i.e.: + * - name for a (super)class + * - super, and binary or string for a subclass + */ +static struct element * +get_class(struct parse *cfile, struct element *ref) +{ + struct element *classes; + struct element *class; + struct element *name; + struct element *selector; + struct element *param; + size_t i; + + classes = mapGet(cfile->stack[1], "client-classes"); + if ((classes == NULL) || (listSize(classes) == 0)) + return NULL; + + name = mapGet(ref, "super"); + if (name == NULL) { + name = mapGet(ref, "name"); + if (name == NULL) + return NULL; + for (i = 0; i < listSize(classes); i++) { + class = listGet(classes, i); + if (mapContains(ref, "super")) + continue; + param = mapGet(class, "name"); + if (param == NULL) + continue; + if (eqString(stringValue(name), stringValue(param))) + return class; + } + return NULL; + } + selector = mapGet(ref, "string"); + if (selector == NULL) { + selector = mapGet(ref, "binary"); + if (selector == NULL) + return NULL; + for (i = 0; i <listSize(classes); i++) { + class = listGet(classes, i); + param = mapGet(class, "super"); + if (param == NULL) + continue; + if (!eqString(stringValue(name), stringValue(param))) + continue; + param = mapGet(class, "binary"); + if (param == NULL) + continue; + if (eqString(stringValue(selector), + stringValue(param))) + return class; + } + return NULL; + } + for (i = 0; i <listSize(classes); i++) { + class = listGet(classes, i); + param = mapGet(class, "super"); + if (param == NULL) + continue; + if (!eqString(stringValue(name), stringValue(param))) + continue; + param = mapGet(class, "string"); + if (param == NULL) + continue; + if (eqString(stringValue(selector), stringValue(param))) + return class; + } + return NULL; +} + +/* + * Concatenate two class reference lists eliminating duplicates + * (complexity is bad: if this becomes a performance pig, use a hash table) + */ + +static void +concat_classes(struct parse *cfile, struct element *dst, struct element *src) +{ + struct element *class; + struct element *sitem; + struct element *ditem; + size_t i; + isc_boolean_t dup; + + while (listSize(src) > 0) { + sitem = listGet(src, 0); + listRemove(src, 0); + class = get_class(cfile, sitem); + if (class == NULL) + /* just ignore */ + continue; + dup = ISC_FALSE; + for (i = 0; i < listSize(dst); i++) { + ditem = listGet(dst, i); + if (class == get_class(cfile, ditem)) { + dup = ISC_TRUE; + break; + } + } + if (dup) + continue; + listPush(dst, sitem); + } +} + +/* Generate a class from allow/deny member lists */ + +static void +generate_class(struct parse *cfile, struct element *pool, + struct element *allow, struct element *deny) +{ + struct element *classes; + struct element *class; + struct element *elem; + struct element *prop; + struct element *depend; + struct element *result = NULL; + struct string *name; + struct string *expr; + struct string *msg; + struct comments comments; + struct comment *comment; + isc_boolean_t rescan; + size_t i; + + if ((listSize(allow) == 0) && (listSize(deny) == 0)) + return; + + classes = mapGet(cfile->stack[1], "generated-classes"); + if (classes == NULL) { + classes = createList(); + mapSet(cfile->stack[1], classes, "generated-classes"); + } + + /* Create comments */ + TAILQ_INIT(&comments); + comment = createComment("/// From:"); + TAILQ_INSERT_TAIL(&comments, comment); + for (i = 0; i < listSize(allow); i++) { + struct element *alias; + + elem = listGet(allow, i); + assert(elem != NULL); + prop = mapGet(elem, "class"); + assert(prop != NULL); + assert(prop->type == ELEMENT_STRING); + alias = mapGet(elem, "real"); + msg = makeString(-1, "/// allow "); + concatString(msg, stringValue(alias ? alias : prop)); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&comments, comment); + } + for (i = 0; i < listSize(deny); i++) { + struct element *alias; + + elem = listGet(deny, i); + assert(elem != NULL); + prop = mapGet(elem, "class"); + assert(prop != NULL); + assert(prop->type == ELEMENT_STRING); + alias = mapGet(elem, "real"); + msg = makeString(-1, "/// deny "); + concatString(msg, stringValue(alias ? alias : prop)); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&comments, comment); + } + TAILQ_CONCAT(&comments, &allow->comments); + TAILQ_CONCAT(&comments, &deny->comments); + + /* Deal with special cases */ + for (;;) { + rescan = ISC_FALSE; + for (i = 0; i < listSize(allow); i++) { + elem = listGet(allow, i); + assert(elem != NULL); + prop = mapGet(elem, "way"); + assert(prop != NULL); + assert(prop->type == ELEMENT_BOOLEAN); + class = mapGet(elem, "class"); + assert(class != NULL); + assert(class->type == ELEMENT_STRING); + /* allow !ALL and other */ + if (eqString(stringValue(class), CLASS_ALL) && + !boolValue(prop) && (listSize(allow) > 1)) { + listRemove(allow, i); + rescan = ISC_TRUE; + break; + } + /* allow ALL alone */ + if (eqString(stringValue(class), CLASS_ALL) && + boolValue(prop) && (listSize(allow) == 1)) { + resetList(allow); + rescan = ISC_TRUE; + break; + } + } + if (!rescan) + break; + } + + for (;;) { + rescan = ISC_FALSE; + for (i = 0; i < listSize(deny); i++) { + elem = listGet(deny, i); + assert(elem != NULL); + prop = mapGet(elem, "way"); + assert(prop != NULL); + assert(prop->type == ELEMENT_BOOLEAN); + class = mapGet(elem, "class"); + assert(class != NULL); + assert(class->type == ELEMENT_STRING); + /* DENY !ALL */ + if (eqString(stringValue(class), CLASS_ALL) && + !boolValue(prop)) { + listRemove(deny, i); + rescan = ISC_TRUE; + break; + } + /* DENY ALL */ + if (eqString(stringValue(class), CLASS_ALL) && + boolValue(prop)) { + resetList(allow); + if (listSize(deny) > 1) { + listRemove(deny, i); + resetList(deny); + listPush(deny, elem); + } + break; + } + } + if (!rescan) + break; + } + + /* Fully cleaned? */ + if ((listSize(allow) == 0) && (listSize(deny) == 0)) { + if (result != NULL) + TAILQ_CONCAT(&result->comments, &comments); + else + TAILQ_CONCAT(&pool->comments, &comments); + return; + } + + /* Unique allow member short cut */ + if ((listSize(allow) == 1) && (listSize(deny) == 0) && + !allow->skip && !deny->skip) { + elem = listGet(allow, 0); + assert(elem != NULL); + prop = mapGet(elem, "way"); + assert(prop != NULL); + assert(prop->type == ELEMENT_BOOLEAN); + class = mapGet(elem, "class"); + assert(class != NULL); + assert(class->type == ELEMENT_STRING); + if (boolValue(prop)) { + result = createString(stringValue(class)); + TAILQ_CONCAT(&result->comments, &comments); + mapSet(pool, result, "client-class"); + return; + } + } + + /* Build name */ + name = makeString(-1, "gen#"); + for (i = 0; i < listSize(allow); i++) { + elem = listGet(allow, i); + assert(elem != NULL); + prop = mapGet(elem, "way"); + assert(prop != NULL); + assert(prop->type == ELEMENT_BOOLEAN); + if (!boolValue(prop)) + appendString(name, "!"); + prop = mapGet(elem, "class"); + assert(prop != NULL); + assert(prop->type == ELEMENT_STRING); + concatString(name, stringValue(prop)); + appendString(name, "#"); + } + if (listSize(deny) > 0) { + appendString(name, "_AND_#"); + for (i = 0; i < listSize(deny); i++) { + elem = listGet(deny, i); + assert(elem != NULL); + prop = mapGet(elem, "way"); + assert(prop != NULL); + assert(prop->type == ELEMENT_BOOLEAN); + if (boolValue(prop)) + appendString(name, "!"); + prop = mapGet(elem, "class"); + assert(prop != NULL); + assert(prop->type == ELEMENT_STRING); + concatString(name, stringValue(prop)); + appendString(name, "#"); + } + } + + /* Check if it already exists */ + for (i = 0; i < listSize(classes); i++) { + struct element *descr; + + class = listGet(classes, i); + assert(class != NULL); + descr = mapGet(class, "name"); + assert(descr != NULL); + assert(descr->type == ELEMENT_STRING); + if (!eqString(name, stringValue(descr))) + continue; + result = createString(name); + TAILQ_CONCAT(&result->comments, &comments); + mapSet(pool, result, "client-class"); + return; + } + + /* Create expression */ + class = createMap(); + depend = createList(); + expr = allocString(); + + if ((listSize(allow) > 0) && (listSize(deny) > 0)) + appendString(expr, "("); + + for (i = 0; i < listSize(allow); i++) { + isc_boolean_t negative; + + if (i > 0) + appendString(expr, " or "); + elem = listGet(allow, i); + prop = mapGet(elem, "way"); + negative = !boolValue(prop); + prop = mapGet(elem, "class"); + if (negative) + appendString(expr, "not "); + appendString(expr, "member('"); + concatString(expr, stringValue(prop)); + appendString(expr, "')"); + listPush(depend, createString(stringValue(prop))); + } + + if ((listSize(allow) > 0) && (listSize(deny) > 0)) + appendString(expr, ") and "); + + for (i = 0; i < listSize(deny); i++) { + isc_boolean_t negative; + + if (i > 0) + appendString(expr, " and "); + elem = listGet(deny, i); + prop = mapGet(elem, "way"); + negative = boolValue(prop); + prop = mapGet(elem, "class"); + if (negative) + appendString(expr, "not "); + appendString(expr, "member('"); + concatString(expr, stringValue(prop)); + appendString(expr, "')"); + listPush(depend, createString(stringValue(prop))); + } + + mapSet(class, createString(name), "name"); + mapSet(class, createString(expr), "test"); + mapSet(class, depend, "depend"); + /* inherit untranslatable cases */ + class->skip |= allow->skip || deny->skip; + listPush(classes, class); + + result = createString(name); + TAILQ_CONCAT(&result->comments, &comments); + mapSet(pool, result, "client-class"); +} diff --git a/keama/data.c b/keama/data.c new file mode 100644 index 00000000..bd656289 --- /dev/null +++ b/keama/data.c @@ -0,0 +1,1258 @@ +/* + * Copyright (c) 2017 by Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * http://www.isc.org/ + */ + +#include "data.h" + +#include <sys/types.h> +#include <arpa/inet.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +struct string * +allocString(void) +{ + struct string *result; + + result = (struct string *)malloc(sizeof(struct string)); + assert(result != NULL); + memset(result, 0, sizeof(struct string)); + + return result; +} + +struct string * +makeString(int l, const char *s) +{ + struct string *result; + + result = allocString(); + if (l < 0) + result->length = strlen(s); + else + result->length = (size_t)l; + if (result->length > 0) { + result->content = (char *)malloc(result->length + 1); + assert(result->content != NULL); + memcpy(result->content, s, result->length); + result->content[result->length] = 0; + } + + return result; +} + +struct string * +makeStringExt(int l, const char *s, char fmt) +{ + switch (fmt) { + case 'Z': + /* zero-length */ + return allocString(); + + case 'l': { + /* 32-bit signed integer */ + int32_t x; + char buf[40]; + + assert(s != NULL); + assert(l > 3); + + memcpy(&x, s, 4); + x = (int32_t)ntohl((uint32_t)x); + snprintf(buf, sizeof(buf), "%lld", (long long)x); + return makeString(-1, buf); + } + + case 'L': { + /* 32-bit unsigned integer */ + uint32_t x; + char buf[40]; + + assert(s != NULL); + assert(l > 3); + + memcpy(&x, s, 4); + x = ntohl(x); + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)x); + return makeString(-1, buf); + } + + case 's': { + /* 16-bit signed integer */ + int16_t x; + char buf[20]; + + assert(s != NULL); + assert(l > 1); + + memcpy(&x, s, 2); + x = (int16_t)ntohs((uint16_t)x); + snprintf(buf, sizeof(buf), "%hd", x); + return makeString(-1, buf); + } + + case 'S': { + /* 16-bit unsigned integer */ + uint16_t x; + char buf[20]; + + assert(s != NULL); + assert(l > 1); + + memcpy(&x, s, 2); + x = ntohs(x); + snprintf(buf, sizeof(buf), "%hu", x); + return makeString(-1, buf); + } + + case 'b': { + /* 8-bit signed integer */ + int8_t x; + char buf[10]; + + assert(s != NULL); + assert(l > 0); + + memcpy(&x, s, 1); + snprintf(buf, sizeof(buf), "%hhd", x); + return makeString(-1, buf); + } + + case 'B': { + /* 8-bit unsigned integer */ + uint8_t x; + char buf[10]; + + assert(s != NULL); + assert(l > 0); + + memcpy(&x, s, 1); + snprintf(buf, sizeof(buf), "%hhu", x); + return makeString(-1, buf); + } + + case 'f': { + /* flag (true or false) */ + uint8_t f; + + assert(s != NULL); + assert(l > 0); + + f = *s; + return makeString(-1, f ? "true" : "false"); + } + + case 'X': { + /* binary data */ + struct string *result; + size_t i; + char buf[4]; + + assert((l == 0) || (s != NULL)); + + result = allocString(); + for (i = 0; i < l; i++) { + snprintf(buf, sizeof(buf), "%02hhx", (uint8_t)s[i]); + appendString(result, buf); + } + return result; + } + + case 'H': { + /* binary data with colons */ + struct string *result; + size_t i; + isc_boolean_t first = ISC_TRUE; + char buf[4]; + + assert((l == 0) || (s != NULL)); + + result = allocString(); + for (i = 0; i < l; i++) { + if (!first) + appendString(result, ":"); + first = ISC_FALSE; + snprintf(buf, sizeof(buf), "%02hhx", (uint8_t)s[i]); + appendString(result, buf); + } + return result; + } + + case 'I': { + /* IPv4 address to text */ + char buf[40 /* INET_ADDRSTRLEN == 26 */]; + + assert(l > 3); + assert(inet_ntop(AF_INET, s, buf, sizeof(buf)) != NULL); + return makeString(-1, buf); + } + + case 'i': { + /* IPv4 address to hexa */ + uint8_t a[4]; + char buf[10]; + + assert(inet_pton(AF_INET, s, a) == 1); + snprintf(buf, sizeof(buf), "%02hhx%02hhx%02hhx%02hhx", + a[0], a[1], a[2], a[3]); + return makeString(-1, buf); + } + + case '6': { + /* IPv6 address */ + char buf[80 /* INET6_ADDRSTRLEN == 46 */]; + + assert(l > 15); + assert(inet_ntop(AF_INET6, s, buf, sizeof(buf)) != NULL); + return makeString(-1, buf); + } + + case 'd': { + /* FQDN to DNS wire format */ + struct string *result; + const char *p; + const char *dot; + char ll; + + assert(s[l] == '0'); + + result = allocString(); + p = s; + while ((dot = strchr(p, '.')) != NULL) { + int len; + + len = dot - p - 1; + if ((len & 0xc0) != 0) + return NULL; + if (dot - s >= l) + return NULL; + ll = len & 0x3f; + concatString(result, makeString(1, &ll)); + concatString(result, makeString(len, p)); + p = dot + 1; + if (p - s == l) + break; + } + if (dot == NULL) { + ll = 0; + concatString(result, makeString(1, &ll)); + } + return result; + } + + default: + assert(0); + } +} + +struct string * +makeStringArray(int l, const char *s, char fmt) +{ + struct string *result; + size_t step; + isc_boolean_t first = ISC_TRUE; + + switch (fmt) { + case '6': + step = 16; + break; + case 'l': + case 'L': + case 'I': + step = 4; + break; + case 's': + case 'S': + step = 2; + break; + case 'b': + case 'B': + case 'f': + step = 1; + break; + default: + assert(0); + } + + assert((l % step) == 0); + + result = allocString(); + while (l > 0) { + if (!first) + appendString(result, ","); + first = ISC_FALSE; + concatString(result, makeStringExt(l, s, fmt)); + s += step; + l -= step; + } + return result; +} + +void +appendString(struct string *s, const char *a) +{ + size_t n; + + assert(s != NULL); + + if (a == NULL) + return; + n = strlen(a); + if (n == 0) + return; + s->content = (char *)realloc(s->content, s->length + n + 1); + assert(s->content != NULL); + memcpy(s->content + s->length, a, n); + s->length += n; + s->content[s->length] = 0; +} + +void +concatString(struct string *s, const struct string *a) +{ + assert(s != NULL); + assert(a != NULL); + + s->content = (char *)realloc(s->content, s->length + a->length + 1); + assert(s->content != NULL); + memcpy(s->content + s->length, a->content, a->length); + s->length += a->length; + s->content[s->length] = 0; +} + +isc_boolean_t +eqString(const struct string *s, const struct string *o) +{ + assert(s != NULL); + assert(o != NULL); + + if (s->length != o->length) + return ISC_FALSE; + if (s->length == 0) + return ISC_TRUE; + return ISC_TF(memcmp(s->content, o->content, s->length) == 0); +} + +struct string * +quote(struct string *s) +{ + struct string *result; + + result = makeString(-1, "'"); + concatString(result, s); + appendString(result, "'"); + return result; +} + +struct comment * +createComment(const char *line) +{ + struct comment *comment; + + assert(line != NULL); + + comment = (struct comment *)malloc(sizeof(struct comment)); + assert(comment != NULL); + memset(comment, 0, sizeof(struct comment)); + + comment->line = strdup(line); + + return comment; +} + +int64_t +intValue(const struct element *e) +{ + assert(e != NULL); + assert(e->type == ELEMENT_INTEGER); + return e->value.int_value; +} + +double +doubleValue(const struct element *e) +{ + assert(e != NULL); + assert(e->type == ELEMENT_REAL); + return e->value.double_value; +} + +isc_boolean_t +boolValue(const struct element *e) +{ + assert(e != NULL); + assert(e->type == ELEMENT_BOOLEAN); + /* could check if 0 or 1 */ + return e->value.bool_value; +} + +struct string * +stringValue(struct element *e) +{ + assert(e != NULL); + assert(e->type == ELEMENT_STRING); + return &e->value.string_value; +} + +struct list * +listValue(struct element *e) +{ + assert(e != NULL); + assert(e->type == ELEMENT_LIST); + return &e->value.list_value; +} + +struct map * +mapValue(struct element *e) +{ + assert(e != NULL); + assert(e->type == ELEMENT_MAP); + return &e->value.map_value; +} + +struct element * +create(void) +{ + struct element *elem; + + elem = (struct element *)malloc(sizeof(struct element)); + assert(elem != NULL); + memset(elem, 0, sizeof(struct element)); + TAILQ_INIT(&elem->comments); + + return elem; +} + +struct element * +createInt(int64_t i) +{ + struct element *elem; + + elem = create(); + elem->type = ELEMENT_INTEGER; + elem->value.int_value = i; + + return elem; +} + +struct element * +createDouble(double d) +{ + struct element *elem; + + elem = create(); + elem->type = ELEMENT_REAL; + elem->value.double_value = d; + + return elem; +} + +struct element * +createBool(isc_boolean_t b) +{ + struct element *elem; + + elem = create(); + elem->type = ELEMENT_BOOLEAN; + elem->value.bool_value = b; + + return elem; +} + +struct element * +createNull(void) +{ + struct element *elem; + + elem = create(); + elem->type = ELEMENT_NULL; + + return elem; +} + +struct element * +createString(const struct string *s) +{ + struct element *elem; + + elem = create(); + elem->type = ELEMENT_STRING; + elem->value.string_value = *s; + + return elem; +} + +struct element * +createList(void) +{ + struct element *elem; + + elem = create(); + elem->type = ELEMENT_LIST; + TAILQ_INIT(&elem->value.list_value); + + return elem; +} + +struct element * +createMap(void) +{ + struct element *elem; + + elem = create(); + elem->type = ELEMENT_MAP; + TAILQ_INIT(&elem->value.map_value); + + return elem; +} + +static void +reset(struct element *e) +{ + e->type = 0; + e->kind = 0; + assert(e->key == NULL); + memset(&e->value, 0, sizeof(e->value)); +} + +void +resetInt(struct element *e, int64_t i) +{ + assert(e != NULL); + + reset(e); + e->type = ELEMENT_INTEGER; + e->value.int_value = i; +} + +void +resetDouble(struct element *e, double d) +{ + assert(e != NULL); + + reset(e); + e->type = ELEMENT_REAL; + e->value.double_value = d; +} + +void +resetBool(struct element *e, isc_boolean_t b) +{ + assert(e != NULL); + + reset(e); + e->type = ELEMENT_BOOLEAN; + e->value.bool_value = b; +} + +void resetNull(struct element *e) +{ + assert(e != NULL); + + reset(e); + e->type = ELEMENT_NULL; +} + +void +resetString(struct element *e, const struct string *s) +{ + assert(e != NULL); + + reset(e); + e->type = ELEMENT_STRING; + e->value.string_value = *s; +} + +void +resetList(struct element *e) +{ + assert(e != NULL); + + reset(e); + e->type = ELEMENT_LIST; + TAILQ_INIT(&e->value.list_value); +} + +void +resetMap(struct element *e) +{ + assert(e != NULL); + + reset(e); + e->type = ELEMENT_MAP; + TAILQ_INIT(&e->value.map_value); +} + +void +resetBy(struct element *e, struct element *o) +{ + assert(e != NULL); + assert(o != NULL); + + reset(e); + e->type = o->type; + e->kind = o->kind; + e->skip = o->skip; + e->key = o->key; + o->key = NULL; + TAILQ_CONCAT(&e->comments, &o->comments); + + switch (e->type) { + case ELEMENT_INTEGER: + e->value.int_value = o->value.int_value; + break; + case ELEMENT_REAL: + e->value.double_value = o->value.double_value; + break; + case ELEMENT_BOOLEAN: + e->value.bool_value = o->value.bool_value; + break; + case ELEMENT_STRING: + e->value.string_value = o->value.string_value; + break; + case ELEMENT_LIST: + TAILQ_INIT(&e->value.list_value); + TAILQ_CONCAT(&e->value.list_value, &o->value.list_value); + break; + case ELEMENT_MAP: + TAILQ_INIT(&e->value.map_value); + TAILQ_CONCAT(&e->value.map_value, &o->value.map_value); + break; + default: + assert(0); + } + reset(o); +} + +struct element * +listGet(struct element *l, int i) +{ + struct element *elem; + + assert(l != NULL); + assert(l->type == ELEMENT_LIST); + assert(i >= 0); + + elem = TAILQ_FIRST(&l->value.list_value); + assert(elem != NULL); + assert(elem->key == NULL); + + unsigned j; + for (j = i; j > 0; --j) { + elem = TAILQ_NEXT(elem); + assert(elem != NULL); + assert(elem->key == NULL); + } + + return elem; +} + +void +listSet(struct element *l, struct element *e, int i) +{ + assert(l != NULL); + assert(l->type == ELEMENT_LIST); + assert(e != NULL); + assert(i >= 0); + + if (i == 0) { + TAILQ_INSERT_HEAD(&l->value.list_value, e); + } else { + struct element *prev; + + prev = TAILQ_FIRST(&l->value.list_value); + assert(prev != NULL); + assert(prev->key == NULL); + + unsigned j; + for (j = i; j > 1; --j) { + prev = TAILQ_NEXT(prev); + assert(prev != NULL); + assert(prev->key == NULL); + } + + TAILQ_INSERT_AFTER(&l->value.list_value, prev, e); + } +} + +void +listPush(struct element *l, struct element *e) +{ + assert(l != NULL); + assert(l->type == ELEMENT_LIST); + assert(e != NULL); + + TAILQ_INSERT_TAIL(&l->value.list_value, e); +} + +void +listRemove(struct element *l, int i) +{ + struct element *elem; + + assert(l != NULL); + assert(l->type == ELEMENT_LIST); + assert(i >= 0); + + elem = TAILQ_FIRST(&l->value.list_value); + assert(elem != NULL); + assert(elem->key == NULL); + + unsigned j; + for (j = i; j > 0; --j) { + elem = TAILQ_NEXT(elem); + assert(elem != NULL); + assert(elem->key == NULL); + } + + TAILQ_REMOVE(&l->value.list_value, elem); +} + +size_t +listSize(const struct element *l) +{ + struct element *elem; + size_t cnt; + + assert(l != NULL); + assert(l->type == ELEMENT_LIST); + + cnt = 0; + TAILQ_FOREACH(elem, &l->value.list_value) { + assert(elem->key == NULL); + cnt++; + } + + return cnt; +} + +void +concat(struct element *l, struct element *o) +{ + assert(l != NULL); + assert(l->type == ELEMENT_LIST); + assert(o != NULL); + assert(o->type == ELEMENT_LIST); + + TAILQ_CONCAT(&l->value.list_value, &o->value.list_value); +} + +struct element * +mapGet(struct element *m, const char *k) +{ + struct element *elem; + + assert(m != NULL); + assert(m->type == ELEMENT_MAP); + assert(k != NULL); + + TAILQ_FOREACH(elem, &m->value.map_value) { + assert(elem->key != NULL); + if (strcmp(elem->key, k) == 0) + break; + } + + return elem; +} + +void +mapSet(struct element *m, struct element *e, const char *k) +{ + assert(m != NULL); + assert(m->type == ELEMENT_MAP); + assert(e != NULL); + assert(k != NULL); +#if 0 + assert(mapGet(m, k) == NULL); +#endif + e->key = strdup(k); + assert(e->key != NULL); + TAILQ_INSERT_TAIL(&m->value.map_value, e); +} + +void +mapRemove(struct element *m, const char *k) +{ + struct element *elem; + + assert(m != NULL); + assert(m->type == ELEMENT_MAP); + assert(k != NULL); + + TAILQ_FOREACH(elem, &m->value.map_value) { + assert(elem->key != NULL); + if (strcmp(elem->key, k) == 0) + break; + } + + assert(elem != NULL); + TAILQ_REMOVE(&m->value.map_value, elem); +} + +isc_boolean_t +mapContains(const struct element *m, const char *k) +{ + struct element *elem; + + assert(m != NULL); + assert(m->type == ELEMENT_MAP); + assert(k != NULL); + + TAILQ_FOREACH(elem, &m->value.map_value) { + assert(elem->key != NULL); + if (strcmp(elem->key, k) == 0) + break; + } + + return ISC_TF(elem != NULL); +} + +size_t +mapSize(const struct element *m) +{ + struct element *elem; + size_t cnt; + + assert(m != NULL); + assert(m->type == ELEMENT_MAP); + + cnt = 0; + TAILQ_FOREACH(elem, &m->value.map_value) { + assert(elem->key != NULL); + cnt++; + } + + return cnt; +} + +void +merge(struct element *m, struct element *o) +{ + struct element *elem; + struct element *ne; + + assert(m != NULL); + assert(m->type == ELEMENT_MAP); + assert(o != NULL); + assert(o->type == ELEMENT_MAP); + + TAILQ_FOREACH_SAFE(elem, &o->value.map_value, ne) { + assert(elem->key != NULL); + TAILQ_REMOVE(&o->value.map_value, elem); + if (!mapContains(m, elem->key)) { + TAILQ_INSERT_TAIL(&m->value.map_value, elem); + } + } +} + +const char * +type2name(int t) +{ + switch (t) { + case ELEMENT_NONE: + return "not initialized?"; + case ELEMENT_INTEGER: + return "integer"; + case ELEMENT_REAL: + return "real"; + case ELEMENT_BOOLEAN: + return "boolean"; + case ELEMENT_NULL: + return "(unused) null"; + case ELEMENT_STRING: + return "string"; + case ELEMENT_LIST: + return "list"; + case ELEMENT_MAP: + return "map"; + default: +#if 0 + assert(0); +#endif + return "unknown?"; + } +} + +int +name2type(const char *n) +{ + assert(n != NULL); + if (strcmp(n, "integer") == 0) + return ELEMENT_INTEGER; + if (strcmp(n, "real") == 0) + return ELEMENT_REAL; + if (strcmp(n, "boolean") == 0) + return ELEMENT_BOOLEAN; + if (strcmp(n, "null") == 0) + return ELEMENT_NULL; + if (strcmp(n, "string") == 0) + return ELEMENT_STRING; + if (strcmp(n, "list") == 0) + return ELEMENT_LIST; + if (strcmp(n, "map") == 0) + return ELEMENT_MAP; +#if 0 + assert(0); +#endif + return ELEMENT_NONE; +} + +void +print(FILE *fp, const struct element *e, isc_boolean_t skip, unsigned indent) +{ + assert(fp != NULL); + assert(e != NULL); + + switch (e->type) { + case ELEMENT_LIST: + printList(fp, &e->value.list_value, skip, indent); + return; + case ELEMENT_MAP: + printMap(fp, &e->value.map_value, skip, indent); + return; + case ELEMENT_STRING: + printString(fp, &e->value.string_value); + return; + case ELEMENT_INTEGER: + fprintf(fp, "%lld", (long long)e->value.int_value); + return; + case ELEMENT_REAL: + fprintf(fp, "%f", e->value.double_value); + return; + case ELEMENT_BOOLEAN: + if (e->value.bool_value) + fprintf(fp, "true"); + else + fprintf(fp, "false"); + return; + case ELEMENT_NULL: + fprintf(fp, "null"); + return; + default: + assert(0); + } +} + +static void +addIndent(FILE *fp, int skip, unsigned indent) +{ + unsigned sp; + + if (skip) { + fprintf(fp, "//"); + if (indent > 2) + for (sp = 0; sp < indent - 2; ++sp) + fprintf(fp, " "); + } else + for (sp = 0; sp < indent; ++sp) + fprintf(fp, " "); +} + +void +printList(FILE *fp, const struct list *l, isc_boolean_t skip, unsigned indent) +{ + struct element *elem; + struct comment *comment; + isc_boolean_t first; + + assert(fp != NULL); + assert(l != NULL); + + if (TAILQ_EMPTY(l)) { + fprintf(fp, "[ ]"); + return; + } + + fprintf(fp, "[\n"); + first = ISC_TRUE; + TAILQ_FOREACH(elem, l) { + isc_boolean_t skip_elem = skip; + + assert(elem->key == NULL); + if (!skip) { + skip_elem = elem->skip; + if (skip_to_end(elem)) { + if (!first) + fprintf(fp, "\n"); + first = ISC_TRUE; + } + } + if (!first) + fprintf(fp, ",\n"); + first = ISC_FALSE; + TAILQ_FOREACH(comment, &elem->comments) { + addIndent(fp, skip_elem, indent + 2); + fprintf(fp, "%s\n", comment->line); + } + addIndent(fp, skip_elem, indent + 2); + print(fp, elem, skip_elem, indent + 2); + } + fprintf(fp, "\n"); + addIndent(fp, skip, indent); + fprintf(fp, "]"); +} + +void +printMap(FILE *fp, const struct map *m, isc_boolean_t skip, unsigned indent) +{ + struct element *elem; + struct comment *comment; + isc_boolean_t first; + + assert(fp != NULL); + assert(m != NULL); + + if (TAILQ_EMPTY(m)) { + fprintf(fp, "{ }"); + return; + } + + fprintf(fp, "{\n"); + first = ISC_TRUE; + TAILQ_FOREACH(elem, m) { + isc_boolean_t skip_elem = skip; + + assert(elem->key != NULL); + if (!skip) { + skip_elem = elem->skip; + if (skip_to_end(elem)) { + if (!first) + fprintf(fp, "\n"); + first = ISC_TRUE; + } + } + if (!first) + fprintf(fp, ",\n"); + first = ISC_FALSE; + TAILQ_FOREACH(comment, &elem->comments) { + addIndent(fp, skip_elem, indent + 2); + fprintf(fp, "%s\n", comment->line); + } + addIndent(fp, skip_elem, indent + 2); + fprintf(fp, "\"%s\": ", elem->key); + print(fp, elem, skip_elem, indent + 2); + } + fprintf(fp, "\n"); + addIndent(fp, skip, indent); + fprintf(fp, "}"); +} + +void +printString(FILE *fp, const struct string *s) +{ + size_t i; + + assert(fp != NULL); + assert(s != NULL); + + fprintf(fp, "\""); + for (i = 0; i < s->length; i++) { + char c = *(s->content + i); + + switch (c) { + case '"': + fprintf(fp, "\\\""); + break; + case '\\': + fprintf(fp, "\\\\"); + break; + case '\b': + fprintf(fp, "\\b"); + break; + case '\f': + fprintf(fp, "\\f"); + break; + case '\n': + fprintf(fp, "\\n"); + break; + case '\r': + fprintf(fp, "\\r"); + break; + case '\t': + fprintf(fp, "\\t"); + break; + default: + if ((c >= 0) && (c < 0x20)) { + fprintf(fp, "\\u%04x", (unsigned)c & 0xff); + } else { + fprintf(fp, "%c", c); + } + } + } + fprintf(fp, "\""); +} + +isc_boolean_t +skip_to_end(const struct element *e) +{ + do { + if (!e->skip) + return ISC_FALSE; + e = TAILQ_NEXT(e); + } while (e != NULL); + return ISC_TRUE; +} + +struct element * +copy(struct element *e) +{ + struct element *result; + struct comment *comment; + + assert(e != NULL); + + switch (e->type) { + case ELEMENT_INTEGER: + result = createInt(intValue(e)); + break; + case ELEMENT_REAL: + result = createDouble(doubleValue(e)); + break; + case ELEMENT_BOOLEAN: + result = createBool(boolValue(e)); + break; + case ELEMENT_NULL: + result = createNull(); + break; + case ELEMENT_STRING: + result = createString(stringValue(e)); + break; + case ELEMENT_LIST: + result = copyList(e); + break; + case ELEMENT_MAP: + result = copyMap(e); + break; + default: + assert(0); + } + result->kind = e->kind; + result->skip = e->skip; + /* don't copy key */ + /* copy comments */ + TAILQ_FOREACH(comment, &e->comments) { + /* do not reuse comment variable! */ + struct comment *tmp; + + tmp = createComment(comment->line); + TAILQ_INSERT_TAIL(&result->comments, tmp); + } + return result; +} + +struct element * +copyList(struct element *l) +{ + struct element *result; + size_t i; + + result = createList(); + for (i = 0; i < listSize(l); i++) + listPush(result, copy(listGet(l, i))); + return result; +} + +struct element * +copyMap(struct element *m) +{ + struct element *result; + struct element *item; + + result = createMap(); + TAILQ_FOREACH(item, &m->value.map_value) + mapSet(result, copy(item), item->key); + return result; +} + +struct handle * +mapPop(struct element *m) +{ + struct element *item; + struct handle *h; + + assert(m != NULL); + assert(m->type == ELEMENT_MAP); + + h = (struct handle *)malloc(sizeof(struct handle)); + assert(h != NULL); + memset(h, 0, sizeof(struct handle)); + TAILQ_INIT(&h->values); + + item = TAILQ_FIRST(&m->value.map_value); + assert(item != NULL); + assert(item->key != NULL); + h->key = strdup(item->key); + assert(h->key != NULL); + h->value = item; + + TAILQ_REMOVE(&m->value.map_value, item); + + return h; +} + +void +derive(struct handle *src, struct handle *dst) +{ + struct element *list; + struct element *item; + size_t i; + + if (dst == NULL) + return; + list = dst->value; + assert(list != NULL); + assert(list->type == ELEMENT_LIST); + for (i = 0; i < listSize(list); i++) { + item = listGet(list, i); + assert(item != NULL); + assert(item->type == ELEMENT_MAP); + if (mapContains(item, src->key)) + continue; + mapSet(item, copy(src->value), src->key); + } +} + +struct string * +hexaValue(struct element *s) +{ + struct string *h; + + assert(s != NULL); + assert(s->type == ELEMENT_STRING); + + h = stringValue(s); + assert(h->length >= 2); + + /* string leading 0x */ + return makeString(h->length - 2, h->content + 2); +} + +struct element * +createHexa(struct string *h) +{ + struct string *s; + + assert(h != NULL); + + s = makeString(-1, "0x"); + concatString(s, h); + return createString(s); +} diff --git a/keama/data.h b/keama/data.h new file mode 100644 index 00000000..b8078cc3 --- /dev/null +++ b/keama/data.h @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2017 by Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * http://www.isc.org/ + */ + +#ifndef DATA_H +#define DATA_H + +#include <stdint.h> +#include <stdio.h> + +/* From FreeBSD sys/queue.h */ + +/* + * Tail queue declarations. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ +} + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/* + * Tail queue functions. + */ +#define TAILQ_CONCAT(head1, head2) do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->next.tqe_prev = (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + } \ +} while (0) + +#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) + +#define TAILQ_FIRST(head) ((head)->tqh_first) + +#define TAILQ_FOREACH(var, head) \ + for ((var) = TAILQ_FIRST((head)); \ + (var); \ + (var) = TAILQ_NEXT((var))) + +#define TAILQ_FOREACH_SAFE(var, head, tvar) \ + for ((var) = TAILQ_FIRST((head)); \ + (var) && ((tvar) = TAILQ_NEXT((var)), 1); \ + (var) = (tvar)) + +#define TAILQ_INIT(head) do { \ + TAILQ_FIRST((head)) = NULL; \ + (head)->tqh_last = &TAILQ_FIRST((head)); \ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm) do { \ + if ((TAILQ_NEXT((elm)) = TAILQ_NEXT((listelm))) != NULL) \ + TAILQ_NEXT((elm))->next.tqe_prev = \ + &TAILQ_NEXT((elm)); \ + else { \ + (head)->tqh_last = &TAILQ_NEXT((elm)); \ + } \ + TAILQ_NEXT((listelm)) = (elm); \ + (elm)->next.tqe_prev = &TAILQ_NEXT((listelm)); \ +} while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm) do { \ + (elm)->next.tqe_prev = (listelm)->next.tqe_prev; \ + TAILQ_NEXT((elm)) = (listelm); \ + *(listelm)->next.tqe_prev = (elm); \ + (listelm)->next.tqe_prev = &TAILQ_NEXT((elm)); \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm) do { \ + if ((TAILQ_NEXT((elm)) = TAILQ_FIRST((head))) != NULL) \ + TAILQ_FIRST((head))->next.tqe_prev = \ + &TAILQ_NEXT((elm)); \ + else \ + (head)->tqh_last = &TAILQ_NEXT((elm)); \ + TAILQ_FIRST((head)) = (elm); \ + (elm)->next.tqe_prev = &TAILQ_FIRST((head)); \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm) do { \ + TAILQ_NEXT((elm)) = NULL; \ + (elm)->next.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &TAILQ_NEXT((elm)); \ +} while (0) + +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) + +#define TAILQ_NEXT(elm) ((elm)->next.tqe_next) + +#define TAILQ_PREV(elm, headname) \ + (*(((struct headname *)((elm)->next.tqe_prev))->tqh_last)) + +#define TAILQ_REMOVE(head, elm) do { \ + if ((TAILQ_NEXT((elm))) != NULL) \ + TAILQ_NEXT((elm))->next.tqe_prev = \ + (elm)->next.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->next.tqe_prev; \ + *(elm)->next.tqe_prev = TAILQ_NEXT((elm)); \ + (elm)->next.tqe_next = (void *)-1; \ + (elm)->next.tqe_prev = (void *)-1; \ +} while (0) + +#define TAILQ_SWAP(head1, head2, type) do { \ + struct type *swap_first = (head1)->tqh_first; \ + struct type **swap_last = (head1)->tqh_last; \ + (head1)->tqh_first = (head2)->tqh_first; \ + (head1)->tqh_last = (head2)->tqh_last; \ + (head2)->tqh_first = swap_first; \ + (head2)->tqh_last = swap_last; \ + if ((swap_first = (head1)->tqh_first) != NULL) \ + swap_first->next.tqe_prev = &(head1)->tqh_first; \ + else \ + (head1)->tqh_last = &(head1)->tqh_first; \ + if ((swap_first = (head2)->tqh_first) != NULL) \ + swap_first->next.tqe_prev = &(head2)->tqh_first; \ + else \ + (head2)->tqh_last = &(head2)->tqh_first; \ +} while (0) + +/* From bind9 lib/isc/include/isc/boolean.h */ + +typedef enum { isc_boolean_false = 0, isc_boolean_true = 1 } isc_boolean_t; + +#define ISC_FALSE isc_boolean_false +#define ISC_TRUE isc_boolean_true +#define ISC_TF(x) ((x) ? ISC_TRUE : ISC_FALSE) + +/* From Kea src/lib/cc/data.h */ + +struct element; + +/* Element types */ +#define ELEMENT_NONE 0 +#define ELEMENT_INTEGER 1 +#define ELEMENT_REAL 2 +#define ELEMENT_BOOLEAN 3 +#define ELEMENT_NULL 4 +#define ELEMENT_STRING 5 +#define ELEMENT_LIST 6 +#define ELEMENT_MAP 7 + +/* Element string */ +struct string { + size_t length; /* string length */ + char *content; /* string data */ +}; + +struct string *allocString(void); +/* In makeString() l == -1 means use strlen(s) */ +struct string *makeString(int l, const char *s); +/* format ZlLsSbBfXI6 + h */ +struct string *makeStringExt(int l, const char *s, char fmt); +/* format 6lLIsSbBj */ +struct string *makeStringArray(int l, const char *s, char fmt); +void appendString(struct string *s, const char *a); +void concatString(struct string *s, const struct string *a); +isc_boolean_t eqString(const struct string *s, const struct string *o); +/* quoting */ +struct string *quote(struct string *); + +/* Comments */ +struct comment { + char *line; /* comment line */ + TAILQ_ENTRY(comment) next; /* next line */ +}; +TAILQ_HEAD(comments, comment); + +struct comment *createComment(const char *line); + +/* Element list */ +TAILQ_HEAD(list, element); + +/* Element map */ +TAILQ_HEAD(map, element); + +/* Element value */ +union value { + int64_t int_value; /* integer */ + double double_value; /* real */ + isc_boolean_t bool_value; /* boolean */ + /**/ /* null */ + struct string string_value; /* string */ + struct list list_value; /* list */ + struct map map_value; /* map */ +}; + +/* Element */ +struct element { + int type; /* element type (ELEMENT_XXX) */ + int kind; /* element kind (e.g. ROOT_GROUP) */ + isc_boolean_t skip; /* skip as not converted */ + char *key; /* element key (for map) */ + union value value; /* value */ + struct comments comments; /* associated comments */ + TAILQ_ENTRY(element) next; /* next item in list or map chain */ +}; + +/* Value getters */ +int64_t intValue(const struct element *e); +double doubleValue(const struct element *e); +isc_boolean_t boolValue(const struct element *e); +struct string *stringValue(struct element *e); +struct list *listValue(struct element *e); +struct map *mapValue(struct element *e); + +/* Creators */ +struct element *create(void); +struct element *createInt(int64_t i); +struct element *createDouble(double d); +struct element *createBool(isc_boolean_t b); +struct element *createNull(void); +struct element *createString(const struct string *s); +struct element *createList(void); +struct element *createMap(void); + +/* Reset */ +void resetInt(struct element *e, int64_t i); +void resetDouble(struct element *e, double d); +void resetBool(struct element *e, isc_boolean_t b); +void resetNull(struct element *e); +void resetString(struct element *e, const struct string *s); +void resetList(struct element *e); +void resetMap(struct element *e); +void resetBy(struct element *e, struct element *o); + +/* List functions */ +struct element *listGet(struct element *l, int i); +void listSet(struct element *l, struct element *e, int i); +void listPush(struct element *l, struct element *e); +void listRemove(struct element *l, int i); +size_t listSize(const struct element *l); +void concat(struct element *l, struct element *o); + +/* Map functions */ +struct element *mapGet(struct element *m, const char *k); +void mapSet(struct element *m, struct element *e, const char *k); +void mapRemove(struct element *m, const char *k); +isc_boolean_t mapContains(const struct element *m, const char *k); +size_t mapSize(const struct element *m); +void merge(struct element *m, struct element *o); + +/* Tools */ +const char *type2name(int t); +int name2type(const char *n); +void print(FILE *fp, const struct element *e, + isc_boolean_t skip, unsigned indent); +void printList(FILE *fp, const struct list *l, + isc_boolean_t skip, unsigned indent); +void printMap(FILE *fp, const struct map *m, + isc_boolean_t skip, unsigned indent); +void printString(FILE *fp, const struct string *s); +isc_boolean_t skip_to_end(const struct element *e); + +struct element *copy(struct element *e); +struct element *copyList(struct element *l); +struct element *copyMap(struct element *m); + +/* Handles */ +TAILQ_HEAD(handles, handle); + +struct handle { + unsigned order; /* order */ + char *key; /* key */ + struct element *value; /* value */ + struct handles values; /* children */ + TAILQ_ENTRY(handle) next; /* siblings */ +}; + +struct handle* mapPop(struct element *); +void derive(struct handle *, struct handle *); + +/* Hexadecimal literals */ +struct string *hexaValue(struct element *); +struct element *createHexa(struct string *); + +#endif /* DATA_H */ diff --git a/keama/dhctoken.h b/keama/dhctoken.h new file mode 100644 index 00000000..ac24463a --- /dev/null +++ b/keama/dhctoken.h @@ -0,0 +1,389 @@ +/* dhctoken.h + + Tokens for config file lexer and parser. */ + +/* + * Copyright (c) 2004-2016 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/ + * + */ + +/* + * The following tokens have been deprecated and aren't in use anymore. + * They have been left in place to avoid disturbing the code. + * DNS_UPDATE, DNS_DELETE, NS_UPDATE, UPDATED_DNS_RR + */ +/* + * For the Kea Migration Assistant only '[' and ']' where added for + * the JSON test parser (no required cast to int in switches on tokens) + */ +enum dhcp_token { + SEMI = ';', + DOT = '.', + COLON = ':', + COMMA = ',', + SLASH = '/', + LBRACE = '{', + RBRACE = '}', + LBRACKET = '[', + RBRACKET = ']', + LPAREN = '(', + RPAREN = ')', + EQUAL = '=', + TILDE = '~', + BANG = '!', + PERCENT = '%', + PLUS = '+', + MINUS = '-', + ASTERISK = '*', + AMPERSAND = '&', + PIPE = '|', + CARET = '^', + ENDOFLINE = '\n', + QUESTIONMARK = '?', + + HOST = 256, + FIRST_TOKEN = HOST, + HARDWARE = 257, + FILENAME = 258, + FIXED_ADDR = 259, + OPTION = 260, + ETHERNET = 261, + STRING = 262, + NUMBER = 263, + NUMBER_OR_NAME = 264, + NAME = 265, + TIMESTAMP = 266, + STARTS = 267, + ENDS = 268, + UID = 269, + CLASS = 270, + LEASE = 271, + RANGE = 272, + PACKET = 273, + CIADDR = 274, + YIADDR = 275, + SIADDR = 276, + GIADDR = 277, + SUBNET = 278, + NETMASK = 279, + DEFAULT_LEASE_TIME = 280, + MAX_LEASE_TIME = 281, + VENDOR_CLASS = 282, + USER_CLASS = 283, + SHARED_NETWORK = 284, + SERVER_NAME = 285, + DYNAMIC_BOOTP = 286, + SERVER_IDENTIFIER = 287, + DYNAMIC_BOOTP_LEASE_CUTOFF = 288, + DYNAMIC_BOOTP_LEASE_LENGTH = 289, + BOOT_UNKNOWN_CLIENTS = 290, + NEXT_SERVER = 291, + TOKEN_RING = 292, + GROUP = 293, + ONE_LEASE_PER_CLIENT = 294, + GET_LEASE_HOSTNAMES = 295, + USE_HOST_DECL_NAMES = 296, + SEND = 297, + CLIENT_IDENTIFIER = 298, + REQUEST = 299, + REQUIRE = 300, + TIMEOUT = 301, + RETRY = 302, + SELECT_TIMEOUT = 303, + SCRIPT = 304, + INTERFACE = 305, + RENEW = 306, + REBIND = 307, + EXPIRE = 308, + UNKNOWN_CLIENTS = 309, + ALLOW = 310, + DENY = 312, + BOOTING = 313, + DEFAULT = 314, + MEDIA = 315, + MEDIUM = 316, + ALIAS = 317, + REBOOT = 318, + TOKEN_ABANDONED = 319, + BACKOFF_CUTOFF = 320, + INITIAL_INTERVAL = 321, + NAMESERVER = 322, + DOMAIN = 323, + SEARCH = 324, + SUPERSEDE = 325, + APPEND = 326, + PREPEND = 327, + HOSTNAME = 328, + CLIENT_HOSTNAME = 329, + REJECT = 330, + USE_LEASE_ADDR_FOR_DEFAULT_ROUTE = 331, + MIN_LEASE_TIME = 332, + MIN_SECS = 333, + AND = 334, + OR = 335, + SUBSTRING = 337, + SUFFIX = 338, + CHECK = 339, + EXTRACT_INT = 340, + IF = 341, + TOKEN_ADD = 342, + BREAK = 343, + ELSE = 344, + ELSIF = 345, + SUBCLASS = 346, + MATCH = 347, + SPAWN = 348, + WITH = 349, + EXISTS = 350, + POOL = 351, + UNKNOWN = 352, + CLIENTS = 353, + KNOWN = 354, + AUTHENTICATED = 355, + UNAUTHENTICATED = 356, + ALL = 357, + DYNAMIC = 358, + MEMBERS = 359, + OF = 360, + PSEUDO = 361, + LIMIT = 362, + BILLING = 363, + PEER = 364, + FAILOVER = 365, + MY = 366, + PARTNER = 367, + PRIMARY = 368, + SECONDARY = 369, + IDENTIFIER = 370, + PORT = 371, + MAX_TRANSMIT_IDLE = 372, + MAX_RESPONSE_DELAY = 373, + PARTNER_DOWN = 374, + NORMAL = 375, + COMMUNICATIONS_INTERRUPTED = 376, + POTENTIAL_CONFLICT = 377, + RECOVER = 378, + TOKEN_FDDI = 379, + AUTHORITATIVE = 380, + TOKEN_NOT = 381, + AUTHENTICATION = 383, + IGNORE = 384, + ACCEPT = 385, + PREFER = 386, + DONT = 387, + CODE = 388, + ARRAY = 389, + BOOLEAN = 390, + INTEGER = 391, + SIGNED = 392, + UNSIGNED = 393, + IP_ADDRESS = 394, + TEXT = 395, + STRING_TOKEN = 396, + SPACE = 397, + CONCAT = 398, + ENCODE_INT = 399, + REVERSE = 402, + LEASED_ADDRESS = 403, + BINARY_TO_ASCII = 404, + PICK = 405, + CONFIG_OPTION = 406, + HOST_DECL_NAME = 407, + ON = 408, + EXPIRY = 409, + RELEASE = 410, + COMMIT = 411, + DNS_UPDATE = 412, + LEASE_TIME = 413, + STATIC = 414, + NEVER = 415, + INFINITE = 416, + TOKEN_DELETED = 417, + UPDATED_DNS_RR = 418, + DNS_DELETE = 419, + DUPLICATES = 420, + DECLINES = 421, + TSTP = 422, + TSFP = 423, + OWNER = 424, + IS = 425, + HBA = 426, + MAX_UNACKED_UPDATES = 427, + MCLT = 428, + SPLIT = 429, + AT = 430, + TOKEN_NO = 431, + TOKEN_DELETE = 432, + NS_UPDATE = 433, + UPDATE = 434, + SWITCH = 435, + CASE = 436, + NS_FORMERR = 437, + NS_NOERROR = 438, + NS_NOTAUTH = 439, + NS_NOTIMP = 440, + NS_NOTZONE = 441, + NS_NXDOMAIN = 442, + NS_NXRRSET = 443, + NS_REFUSED = 444, + NS_SERVFAIL = 445, + NS_YXDOMAIN = 446, + NS_YXRRSET = 447, + TOKEN_NULL = 448, + TOKEN_SET = 449, + DEFINED = 450, + UNSET = 451, + EVAL = 452, + LET = 453, + FUNCTION = 454, + DEFINE = 455, + ZONE = 456, + KEY = 457, + SECRET = 458, + ALGORITHM = 459, + LOAD = 460, + BALANCE = 461, + TOKEN_MAX = 462, + SECONDS = 463, + ADDRESS = 464, + RESOLUTION_INTERRUPTED = 465, + STATE = 466, + UNKNOWN_STATE = 567, + CLTT = 568, + INCLUDE = 569, + BINDING = 570, + TOKEN_FREE = 571, + TOKEN_ACTIVE = 572, + TOKEN_EXPIRED = 573, + TOKEN_RELEASED = 574, + TOKEN_RESET = 575, + TOKEN_BACKUP = 576, + TOKEN_RESERVED = 577, + TOKEN_BOOTP = 578, + TOKEN_NEXT = 579, + OMAPI = 580, + LOG = 581, + FATAL = 582, + ERROR = 583, + TOKEN_DEBUG = 584, + INFO = 585, + RETURN = 586, + PAUSED = 587, + RECOVER_DONE = 588, + SHUTDOWN = 589, + STARTUP = 590, + ENCAPSULATE = 591, + VENDOR = 592, + CLIENT_STATE = 593, + INIT_REBOOT = 594, + TOKEN_INIT = 595, + SELECT = 596, + BOUND = 597, + RENEWING = 598, + REBINDING = 599, + RECONTACT_INTERVAL = 600, + CLIENT_UPDATES = 601, + TOKEN_NEW = 601, + TRANSMISSION = 602, + TOKEN_CLOSE = 603, + TOKEN_CREATE = 604, + TOKEN_OPEN = 605, + TOKEN_HELP = 606, + END_OF_FILE = 607, + RECOVER_WAIT = 608, + TOKEN_SERVER = 609, + CONNECT = 610, + REMOVE = 611, + REFRESH = 612, + DOMAIN_NAME = 613, + DO_FORWARD_UPDATE = 614, + KNOWN_CLIENTS = 615, + ATSFP = 616, + LCASE = 617, + UCASE = 618, + WIDTH = 619, + LENGTH = 620, + HASH = 621, + SIZE = 622, + EPOCH = 623, + DB_TIME_FORMAT = 624, + LOCAL = 625, + MAX_LEASE_MISBALANCE = 626, + MAX_LEASE_OWNERSHIP = 627, + MAX_BALANCE = 628, + MIN_BALANCE = 629, + DOMAIN_LIST = 630, + LEASEQUERY = 631, + EXECUTE = 632, + IP6_ADDRESS = 633, + FIXED_ADDR6 = 634, + COMPRESSED = 635, + SUBNET6 = 636, + HOST_IDENTIFIER = 637, + IA_NA = 638, + IA_TA = 639, + IA_PD = 640, + IAADDR = 641, + IAPREFIX = 642, + LEASE6 = 643, + PREFERRED_LIFE = 644, + MAX_LIFE = 645, + DEFAULT_DUID = 646, + SERVER_DUID = 647, + LLT = 648, + EN = 649, + LL = 650, + RANGE6 = 651, + WHITESPACE = 652, + TOKEN_ALSO = 653, + AFTER = 654, + ZEROLEN = 655, + TEMPORARY = 656, + PREFIX6 = 657, + FIXED_PREFIX6 = 658, + ANYCAST_MAC = 659, + CONFLICT_DONE = 660, + AUTO_PARTNER_DOWN = 661, + GETHOSTNAME = 662, + REWIND = 663, + INITIAL_DELAY = 664, + GETHOSTBYNAME = 665, + PRIMARY6 = 666, + SECONDARY6 = 667, + TOKEN_INFINIBAND = 668, + POOL6 = 669, + V6RELAY = 670, + V6RELOPT = 671, + PARSE_VENDOR_OPT = 672, + AUTHORING_BYTE_ORDER = 673, + TOKEN_LITTLE_ENDIAN = 674, + TOKEN_BIG_ENDIAN = 675, + LEASE_ID_FORMAT = 676, + TOKEN_HEX = 677, + TOKEN_OCTAL = 678, + KEY_ALGORITHM = 679 +}; + +#define is_identifier(x) ((x) >= FIRST_TOKEN && \ + (x) != STRING && \ + (x) != NUMBER && \ + (x) != END_OF_FILE) diff --git a/keama/doc.txt b/keama/doc.txt new file mode 100644 index 00000000..a1664a22 --- /dev/null +++ b/keama/doc.txt @@ -0,0 +1,516 @@ +Part 1: Kea Migration Assistant support +======================================= + +Files: +------ + - data.h (tailq list and element type declarations) + - data.c (element type code) + - keama.h (DHCP declarations) + - keama.c (main() code) + - json.c (JSON parser) + - option.c (option tables and code) + - keama.8 (man page) + +The code heavily uses tailq lists, i.e. doubled linked lists with +a pointer to the last (tail) element. + +The element structure mimics the Kea Element class with a few differences: + - no smart pointers + - extra fields to handle declaration kind, skip and comments + - maps are implemented as lists with an extra key field so the order + of insertion is kept and duplicates are possible + - strings are length + content (vs C strings) + +There is no attempt to avoid memory leaks. + +The skip flag is printed as '//' at the beginning of lines. It is set +when something cannot be converted and the issue counter (returned +by the keama command) incremented. + +Part 2: ISC DHCP lexer organization +=================================== + +Files: +----- + - dhctoken.h (from includes, enum dhcp_token definition) + - conflex.c (from common, lexical analyzer code) + +Tokens (dhcp_token enum): characters are set to their ASCII value, + others are >= 256 without real organization (e.g. END_OF_FILE is 607). + +The state is in a parse structure named "cfile". There is one per file +and a few routine save it in order to do a backtrack on a larger +set than the usual lookahead. +The largest function is intern() which recognizes keywords with +a switch on the first character and a tree of if strcasecmp's. + +Standard routines: +----------------- +enum dhcp_token +next_token(const char **rval, unsigned *rlen, struct parse *cfile); + +and + +enum dhcp_token +peek_token(const char **rval, unsigned *rlen, struct parse *cfile); + +rval: if not null the content of the token is put in it +rlen: if not null the length of the token is put in it +cfile: lexer context +return: the integer value of the token + +Changes: +------- + +Added LBRACKET '[' and RBRACKET ']' tokens for JSON parser +(switch on dhcp_token type). + +Added comments to collect ISC DHCP # comments, element stack to follow +declaration hierarchy, and issue counter to struct parse. + +Moved the parse_warn (renamed into parse_error and made fatal) routine +from conflex.c to keama.c + +Part 3: ISC DHCP parser organization +==================================== + +Files: +----- + - confparse.c (from server) + for the server in parse_statement()) + - parse.c (from common) + +4 classes: parameters, declarations, executable statements and expressions. + +the original code parses config and lease files, I kept only the first +at the exception of parse_binding_value(). + +entry point + | + V +conf_file_parse + | + V +conf_file_subparse <- read_conf_file (for include) + until END_OF_FILE call + | + V +parse_statement + parse parameters and declarations + switch on token and call parse_xxx_declaration routines + on default or DHCPv6 token in DHCPv4 mode call parse_executable_statement + and put the result under the "statement" key + | + V +parse_executable_statement + +According to comments the grammar is: + + conf-file :== parameters declarations END_OF_FILE + parameters :== <nil> | parameter | parameters parameter + declarations :== <nil> | declaration | declarations declaration + + statement :== parameter | declaration + + parameter :== DEFAULT_LEASE_TIME lease_time + | MAX_LEASE_TIME lease_time + | DYNAMIC_BOOTP_LEASE_CUTOFF date + | DYNAMIC_BOOTP_LEASE_LENGTH lease_time + | BOOT_UNKNOWN_CLIENTS boolean + | ONE_LEASE_PER_CLIENT boolean + | GET_LEASE_HOSTNAMES boolean + | USE_HOST_DECL_NAME boolean + | NEXT_SERVER ip-addr-or-hostname SEMI + | option_parameter + | SERVER-IDENTIFIER ip-addr-or-hostname SEMI + | FILENAME string-parameter + | SERVER_NAME string-parameter + | hardware-parameter + | fixed-address-parameter + | ALLOW allow-deny-keyword + | DENY allow-deny-keyword + | USE_LEASE_ADDR_FOR_DEFAULT_ROUTE boolean + | AUTHORITATIVE + | NOT AUTHORITATIVE + + declaration :== host-declaration + | group-declaration + | shared-network-declaration + | subnet-declaration + | VENDOR_CLASS class-declaration + | USER_CLASS class-declaration + | RANGE address-range-declaration + +Typically declarations use { } and are associated with a group +(changed to a type) in ROOT_GROUP (global), HOST_DECL, SHARED_NET_DECL, +SUBNET_DECL, CLASS_DECL, GROUP_DECL and POOL_DECL. + +ROOT: parent = TOPLEVEL, children = everythig but not POOL +HOST: parent = ROOT, GROUP, warn on SHARED or SUBNET, children = none +SHARED_NET: parent = ROOT, GROUP, children = HOST (warn), SUBNET, POOL4 +SUBNET: parent = ROOT, GROUP, SHARED, children = HOST (warn), POOL +CLASS: parent = ROOT, GROUP, children = none +GROUP: parent = ROOT, SHARED, children = anything but not POOL +POOL: parent = SHARED4, SUBNET, warn on others, children = none + +isc_boolean_t +parse_statement(struct parse *cfile, int type, isc_boolean_t declaration); + +cfile: parser context +type: declaration type +declaration and return: declaration or parameter + +On the common side: + + 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 + +isc_boolean_t +parse_executable_statement(struct element *result, + struct parse *cfile, isc_boolean_t *lose, + enum expression_context case_context, + isc_boolean_t direct); + +result: map element where to put the statement +cfile: parser context +lose: set to ISC_TRUE on failure +case_context: expression context +direct: called directly by parse_statement so can execute config statements +return: success + +parse_executable_statement + switch on keywords (far more than in the comments) + on default with an identifier try a config option, on number or name + call parse_expression for a function call + | + V +parse_expression + +expressions are divided into boolean, data (string) and numeric expressions + + 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 + + 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 | + V6RELAY LPAREN numeric-expression COMMA + data-expression RPAREN | + STRING | + colon_separated_hex_list + + numeric-expression :== EXTRACT_INT LPAREN data-expression + COMMA number RPAREN | + NUMBER + +parse_boolean_expression, parse_data_expression and parse_numeric_expression +calls parse_expression and check its result + +parse_expression itself is divided into parse_non_binary and internal +handling of binary operators + +isc_boolean_t +parse_non_binary(struct element *expr, struct parse *cfile, + isc_boolean_t *lose, enum expression_context context) + +isc_boolean_t +parse_expression(struct element *expr, struct parse *cfile, + isc_boolean_t *lose, enum expression_context context, + struct element *lhs, enum expr_op binop) + +expr: map element where to put the result +cfile: parser context +lose: set to ISC_TRUE on failure +context: expression context +lhs: NULL or left hand side +binop: expr_none or binary operation +return: success + +parse_non_binary + switch on unary and nullary operator keywords + on default try a variable reference or a function call + +parse_expression + call parse_non_binary to get the right hand side + switch on binary operator keywords to get the next operation + with one side if expr_none return else get the second hand + handle operator precedence, can call itself + return a map entry with the operator name as the key, and + left and right expression branches + +Part 4: Expression processing +============================= + +Files: +------ + - print.c (new) + - eval.c (new) + - reduce.c (new) + +Print: +------ + +const char * +print_expression(struct element *expr, isc_boolean_t *lose); +const char * +print_boolean_expression(struct element *expr, isc_boolean_t *lose); +const char * +print_data_expression(struct element *expr, isc_boolean_t *lose); +const char * +print_numeric_expression(struct element *expr, isc_boolean_t *lose); + +expr: expression to print +lose: failure (??? in output) flag +return: the text representing the expression + +Eval: +----- + +struct element * +eval_expression(struct element *expr, isc_boolean_t *modifiedp); +struct element * +eval_boolean_expression(struct element *expr, isc_boolean_t *modifiedp); +struct element * +eval_data_expression(struct element *expr, isc_boolean_t *modifiedp); +struct element * +eval_numeric_expression(struct element *expr, isc_boolean_t *modifiedp); + +expr: expression to evaluate +modifiedp: a different element was returned (still false for updates + inside a map) +return: the evaluated element (can have been updated for a map or a list, + or can be a fully different element) + +Evaluation is at parsing time so it is mainly a constant propagation. +(no beta reduction for instance) + +Reduce: +------- + +struct element * +reduce_boolean_expression(struct element *expr); +struct element * +reduce_data_expression(struct element *expr); +struct element * +reduce_numeric_expression(struct element *expr); + +expr: expression to reduce +return: NULL or the reduced expression as a Kea eval string + +reducing works for a limited (but interesting) set of expressions which +can be converted to kea evaluatebool and for literals. + +Part 5: Specific issues +======================= + +Reservations: +------------- + ISC DHCP host declarations are global, Kea reservations were per subnet + only until 1.5. + It is possible to use the fixed address but: + - it is possible to finish with orphan reservations, i.e. + reservations with an address which match no subnets + - a reservation can have no fixed address. In this case the MA puts + the reservation in the last declared subnet. + - a reservation can have more than one fixed address and these + addresses can belong to different subnets. Current code pushes + IPv4 extra addresses in a commented extra-ip-addresses but + it is legal feature for IPv6. + - it is not easy to use prefix6 + The use of groups in host declarations is unclear. + ISC DHCP UID is mapped to client-id, host-identifier to flex-id + Host reservation identifiers are generated on first use. + +Groups: +------- +TODO: search missing parameters from the Kea syntax. + (will be done in the third pass) + +Shared-Networks: +---------------- + Waiting for the feature to be supported by Kea. + Currently at the end of a shared network declaration: + - if there is no subnets it is a fatal error + - if there is one subnet the shared-network is squeezed + - if there are more than one subnet the shared-network is commented +TODO (useful only with Kea support for shared networks): combine permit / +deny classes (e.g. create negation) and pop filters to subnets when +there is one pool. + +Vendor-Classes and User-Classes: +-------------------------------- + ISC DHCP code is inconsistent: in particular before setting the + super-class "tname" to "implicit-vendor-class" / "implicit-user-class" + it allocates a buffer for data but does not copy the lexical value + "val" into it... So I removed support. + +Classes: +-------- + Only pure client-classes are supported by kea. + Dynamic/deleted stuff is not supported but does it make sense? + To spawn classes is not supported. + Match class selector is converted to Kea eval test when the corresponding + expression can be reduced. Fortunately it seems to be the common case! + Lease limit is not supported. + +Subclasses: +----------- + Understood how it works: + - (super) class defined with a MATCH <data-expression> (vs. + MATCH IF <boolean-expression>) + - subclasses defined by <superclass-name> <data-literal> which + are equivalent to + MATCH IF <superclass-data-expression> EQUAL <data-literal> + So subclasses are convertible when the data expression can be reduced. + Cf https://kb.isc.org/article/AA-01092/202/OMAPI-support-for-classes-and-subclasses.html + which BTW suggests the management API could manage classes... + +Hardware Addresses: +------------------- + Kea supports only Ethernet. + +Pools: +------ + All permissions are not supported by Kea at the exception of class members + but in a very different way so not convertible. + Mixed DHCPv6 address and prefix pools are not supported, perhaps in this + case the pool should be duplicated into pool and pd-pool instances? + The bootp stuff was ifdef's as bootp is obsolete. + Temporary (aka IA_TA) is commented ny the MA. + ISC DHCP supports interval ranges for prefix6. Kea has a different + and IMHO more powerful model. + Pool6 permissions are not supported. + +Failover: +--------- + Display a warning on the first use. + +Interfaces: +----------- + Referenced interface names are pushed to an interfaces-config but it is + very (too!) easy to finish with a Kea config without any interface. + +Hostnames: +---------- + ISC DHCP does dynamic resolution in parse_ip_addr_or_hostname. + Static (at conversion time) resolution to one address is done by + the MA for fixed-address. Resolution is considered as painful + there are better (and safer) ways to do this. The -r (resolve) + command line parameter controls the at-conversion-time resolution. + Note only the first address is returned. +TODO: check the multiple address comment is correctly taken + (need a known host resolving in a stable set of addresses) + +Options: +-------- + Some options are known only in ISC DHCP (almost fixed), a few only by Kea. + Formats are supposed to be the same, the only known exception + (DHCPv4 domain-search) was fixed by #5087. + For option spaces DHCPv4 vendor-encapsulated-options (code 43, in general + associated to vendor-class-identifier code 60) uses a dedicated feature + which had no equivalent in Kea (fixed). + Option definitions are convertible with a few exception: + - no support in Kea for an array of records (mainly by the lack + of a corresponding syntax). BTW there is no known use too. + - no support in Kea for an array at the end of a record (fixed) + All unsupported option declarations are set to full binary (X). + - X format means ASCII or hexa: + * standard options are in general mapped to binary + * new options are mapped to string with format x (vs x) + * when a string got hexadecimal data a warning in added in comments + suggesting to switch to plain binary. + - ISC DHCP use quotes for a domain-list but not for a domain-name, + this is no very coherent and makes domain-list different than + domain-name array. +Each time an option data has a format which is not convertible than +a CSV false binary data is produced. + We have no example in ISC DHCP, Kea or standard but it is possible + than an option defined as a fixed sized record followed by + (encapsulated) suboptions bugs (it already bugs toElement). + For operations on options ISC DHCP has supersede, send, append, + prepend, default (set if not yet present), Kea puts them in code order + with a few built-in exceptions. + To finish there is the way to enforce Kea to add an option in a response + is pretty different and can't be automatically translated (cf Kea #250). + +Duplicates: +----------- + Many things in ISC DHCP can be duplicated: + - options can be redefined + - same host identifier used twice + - same fixed address used in tow different hosts + etc. + Kea is far more strict and IMHO it is a good thing. Now the MA does + no particular check and multiple definitions work only for classes + (because it is the way the ISC DHCP parse works). + If we have Docsis space options, they are standard in Kea so they + will conflict. + +Dynamic DNS: +------------ + Details are very different so the MA maps only basic parameters + at the global scope. + +Expressions: +------------ + ISC DHCP expressions are typed: boolean, numeric, and data aka string. + The default for a literal is to be a string so literal numbers are + interpreted in hexadecimal (for a strange consequence look at + https://kb.isc.org/article/AA-00334/56/Do-the-list-of-parameters-in-the-dhcp-parameter-request-list-need-to-be-in-hex.html ). + String literals are converted to string elements, hexadecimal literals + are converted to const-data maps. +TODO reduce more hexa aka const-data + As booleans are not data there is no way to fix this: + /tmp/bool line 9: Expecting a data expression. + option ip-forwarding = foo = foo; + ^ + Cf Kea #247 + The tautology 'foo = foo' is not a data expression so is rejected by + both the MA and dhcpd (BTW the role of the MA is not to fix ISC DHCP + shortcomings so it does what it is expected to do here). + Note this does not work too: + option ip-forwarding = true; + because "true" is not a keyword and it is converted into a variable + reference... And I expect ISC DHCP makes this true a false at runtime + because the variable "true" is not defined by default. + Reduced expressions are pretty printed to allow an extra check. + Hardware for DHCPv4 is expansed into a concatenation of hw-type and + hw-address, this allows to simplify expression where only one is used. + +Variables: +---------- + ISC DHCP has a notion of variables in a scope where the scope can be + a lexical scope in the config or a scope in a function body + (ISC DHCP has even an unused "let" statement). + There is a variant of bindings for lease files using types and able + to recognize booleans and numbers. Unfortunately this is very specific... + +TODO: + - global host reservations + - class like if statement + - add more tests for classes in pools and class generation diff --git a/keama/eval.c b/keama/eval.c new file mode 100644 index 00000000..8e2dbdb5 --- /dev/null +++ b/keama/eval.c @@ -0,0 +1,2252 @@ +/* + * Copyright (c) 2017 by Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "keama.h" + +#include <sys/errno.h> +#include <sys/types.h> +#include <arpa/inet.h> +#include <ctype.h> +#include <netdb.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +static struct element *eval_equal_expression(struct element *, + struct element *); +static isc_boolean_t cmp_hexa(struct element *, isc_boolean_t, + struct element *,isc_boolean_t); +static void debug(const char* fmt, ...); + +struct element * +eval_expression(struct element *expr, isc_boolean_t *modifiedp) +{ + if ((expr->type == ELEMENT_BOOLEAN) || + (expr->type == ELEMENT_INTEGER) || + (expr->type == ELEMENT_STRING)) + return expr; + + if (is_boolean_expression(expr)) + return eval_boolean_expression(expr, modifiedp); + if (is_numeric_expression(expr)) + return eval_numeric_expression(expr, modifiedp); + if (is_data_expression(expr)) + return eval_data_expression(expr, modifiedp); + debug("can't type expression"); + return expr; +} + +/* + * 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 + */ + +struct element * +eval_boolean_expression(struct element *expr, isc_boolean_t *modifiedp) +{ + /* trivial case: already done */ + if (expr->type == ELEMENT_BOOLEAN) + return expr; + + /* + * From is_boolean_expression + */ + + if (expr->type != ELEMENT_MAP) + return expr; + + + /* check */ + if (mapContains(expr, "check")) + /* + * syntax := { "check": <collection_name> } + * semantic: check_collection + * on server try to match classes of the collection + */ + return expr; + + + /* exists */ + if (mapContains(expr, "exists")) + /* + * syntax := { "exists": + * { "universe": <option_space_old>, + * "name": <option_name> } + * } + * semantic: check universe/code from incoming packet + */ + return expr; + + /* variable-exists */ + if (mapContains(expr, "variable-exists")) + /* + * syntax := { "variable-exists": <variable_name> } + * semantics: find_binding(scope, name) + */ + return expr; + + /* equal */ + if (mapContains(expr, "equal")) { + /* + * syntax := { "equal": + * { "left": <expression>, + * "right": <expression> } + * } + * semantics: evaluate branches and return true + * if same type and same value + */ + struct element *arg; + struct element *left; + struct element *right; + struct element *equal; + struct comments comments; + isc_boolean_t lmodified = ISC_FALSE; + isc_boolean_t rmodified = ISC_FALSE; + + arg = mapGet(expr, "equal"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + left = mapGet(arg, "left"); + if (left == NULL) + return expr; + right = mapGet(arg, "right"); + if (right == NULL) + return expr; + left = eval_expression(left, &lmodified); + if (lmodified) { + mapRemove(arg, "left"); + mapSet(arg, left, "left"); + } + right = eval_expression(right, &rmodified); + if (rmodified) { + mapRemove(arg, "right"); + mapSet(arg, right, "right"); + } + + equal = eval_equal_expression(left, right); + if ((equal == NULL) || (equal->type != ELEMENT_BOOLEAN)) + return expr; + *modifiedp = ISC_TRUE; + TAILQ_INIT(&comments); + TAILQ_CONCAT(&comments, &expr->comments); + TAILQ_CONCAT(&comments, &arg->comments); + TAILQ_CONCAT(&comments, &equal->comments); + TAILQ_CONCAT(&equal->comments, &comments); + return equal; + } + + /* not-equal */ + if (mapContains(expr, "not-equal")) { + /* + * syntax := { "not-equal": + * { "left": <expression>, + * "right": <expression> } + * } + * semantics: evaluate branches and return true + * if different type or different value + */ + struct element *arg; + struct element *left; + struct element *right; + struct element *equal; + struct element *result; + isc_boolean_t lmodified = ISC_FALSE; + isc_boolean_t rmodified = ISC_FALSE; + + arg = mapGet(expr, "not-equal"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + left = mapGet(arg, "left"); + if (left == NULL) + return expr; + right = mapGet(arg, "right"); + if (right == NULL) + return expr; + left = eval_expression(left, &lmodified); + if (lmodified) { + mapRemove(arg, "left"); + mapSet(arg, left, "left"); + } + right = eval_expression(right, &rmodified); + if (rmodified) { + mapRemove(arg, "right"); + mapSet(arg, right, "right"); + } + + equal = eval_equal_expression(left, right); + if ((equal == NULL) || (equal->type != ELEMENT_BOOLEAN)) + return expr; + *modifiedp = ISC_TRUE; + result = createBool(ISC_TF(!boolValue(equal))); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &equal->comments); + return result; + } + + /* regex-match */ + if (mapContains(expr, "regex-match")) + /* + * syntax := { "regex-match": + * { "left": <data_expression>, + * "right": <data_expression> } + * } + * semantics: evaluate branches, compile right as a + * regex and apply it to left + */ + return expr; + + /* iregex-match */ + if (mapContains(expr, "iregex-match")) + /* + * syntax := { "regex-match": + * { "left": <data_expression>, + * "right": <data_expression> } + * } + * semantics: evaluate branches, compile right as a + * case insensistive regex and apply it to left + */ + return expr; + + /* and */ + if (mapContains(expr, "and")) { + /* + * syntax := { "and": + * { "left": <boolean_expression>, + * "right": <boolean_expression> } + * } + * semantics: evaluate branches, return true + * if both are true + */ + struct element *arg; + struct element *left; + struct element *right; + struct element *result; + isc_boolean_t lmodified = ISC_FALSE; + isc_boolean_t rmodified = ISC_FALSE; + + arg = mapGet(expr, "and"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + left = mapGet(arg, "left"); + if (left == NULL) + return expr; + right = mapGet(arg, "right"); + if (right == NULL) + debug("can't get and right branch"); + left = eval_boolean_expression(left, &lmodified); + if (lmodified) { + mapRemove(arg, "left"); + mapSet(arg, left, "left"); + } + right = eval_boolean_expression(right, &rmodified); + if (rmodified) { + mapRemove(arg, "right"); + mapSet(arg, right, "right"); + } + + if (left->type == ELEMENT_BOOLEAN) { + *modifiedp = ISC_TRUE; + if (!boolValue(left)) + result = createBool(ISC_FALSE); + else { + result = copy(right); + TAILQ_INIT(&result->comments); + } + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &left->comments); + TAILQ_CONCAT(&result->comments, &right->comments); + return result; + } + if (right->type == ELEMENT_BOOLEAN) { + *modifiedp = ISC_TRUE; + if (!boolValue(right)) + result = createBool(ISC_FALSE); + else { + result = copy(left); + TAILQ_INIT(&result->comments); + } + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &left->comments); + TAILQ_CONCAT(&result->comments, &right->comments); + return result; + } + return expr; + } + + /* or */ + if (mapContains(expr, "or")) { + /* + * syntax := { "or": + * { "left": <boolean_expression>, + * "right": <boolean_expression> } + * } + * semantics: evaluate branches, return true + * if any is true + */ + struct element *arg; + struct element *left; + struct element *right; + struct element *result; + isc_boolean_t lmodified = ISC_FALSE; + isc_boolean_t rmodified = ISC_FALSE; + + arg = mapGet(expr, "or"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + left = mapGet(arg, "left"); + if (left == NULL) + return expr; + right = mapGet(arg, "right"); + if (right == NULL) + return expr; + left = eval_boolean_expression(left, &lmodified); + if (lmodified) { + mapRemove(arg, "left"); + mapSet(arg, left, "left"); + } + right = eval_boolean_expression(right, &rmodified); + if (rmodified) { + mapRemove(arg, "right"); + mapSet(arg, right, "right"); + } + + if (left->type == ELEMENT_BOOLEAN) { + *modifiedp = ISC_TRUE; + if (boolValue(left)) + result = createBool(ISC_TRUE); + else { + result = copy(right); + TAILQ_INIT(&result->comments); + } + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &left->comments); + TAILQ_CONCAT(&result->comments, &right->comments); + return result; + } + if (right->type == ELEMENT_BOOLEAN) { + *modifiedp = ISC_TRUE; + if (boolValue(right)) + result = createBool(ISC_TRUE); + else { + result = copy(left); + TAILQ_INIT(&result->comments); + } + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &left->comments); + TAILQ_CONCAT(&result->comments, &right->comments); + return result; + } + return expr; + } + + /* not */ + if (mapContains(expr, "not")) { + /* + * syntax := { "not": <boolean_expression> } + * semantic: evaluate its branch and return its negation + */ + struct element *arg; + struct element *result; + isc_boolean_t modified = ISC_FALSE; + + arg = mapGet(expr, "not"); + if (arg == NULL) + return expr; + arg = eval_boolean_expression(arg, &modified); + if (modified) { + mapRemove(expr, "not"); + mapSet(expr, arg, "not"); + } + + /* remove double not */ + if ((arg->type == ELEMENT_MAP) && mapContains(arg, "not")) { + arg = mapGet(arg, "not"); + if (arg == NULL) + return expr; + *modifiedp = ISC_TRUE; + return arg; + } + + /* compose with equal */ + if ((arg->type == ELEMENT_MAP) && + mapContains(arg, "equal")) { + arg = mapGet(arg, "equal"); + if (arg == NULL) + return expr; + *modifiedp = ISC_TRUE; + result = createMap(); + mapSet(result, arg, "not-equal"); + return result; + } + + /* compose with not-equal */ + if ((arg->type == ELEMENT_MAP) && + mapContains(arg, "not-equal")) { + arg = mapGet(arg, "not-equal"); + if (arg == NULL) + return expr; + *modifiedp = ISC_TRUE; + result = createMap(); + mapSet(result, arg, "equal"); + return result; + } + + if (arg->type != ELEMENT_BOOLEAN) + return expr; + *modifiedp = ISC_TRUE; + result = createBool(ISC_TF(!boolValue(arg))); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + return result; + } + + /* known */ + if (mapContains(expr, "known")) + /* + * syntax := { "known": null } + * semantics: client is known, i.e., has a matching + * host declaration (aka reservation in Kea) + */ + return expr; + + /* static */ + if (mapContains(expr, "static")) + /* + * syntax := { "static": null } + * semantics: lease is static (doesn't exist in Kea) + */ + return expr; + + return expr; +} + +/* + * 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 | + * V6RELAY LPAREN numeric-expression COMMA + * data-expression RPAREN | + * STRING | + * colon_separated_hex_list + */ + +struct element * +eval_data_expression(struct element *expr, isc_boolean_t *modifiedp) +{ + /* trivial case: already done */ + if (expr->type == ELEMENT_STRING) + return expr; + + /* + * From is_data_expression + */ + + if (expr->type != ELEMENT_MAP) + return expr; + + /* substring */ + if (mapContains(expr, "substring")) { + /* + * syntax := { "substring": + * { "expression": <data_expression>, + * "offset": <numeric_expression>, + * "length": <numeric_expression> } + * } + * semantic: evaluate arguments, if the string is + * shorter than offset return "" else return substring + */ + struct element *arg; + struct element *string; + struct element *offset; + struct element *length; + struct element *result; + struct string *s; + struct string *r; + int64_t off; + int64_t len; + isc_boolean_t smodified = ISC_FALSE; + isc_boolean_t omodified = ISC_FALSE; + isc_boolean_t lmodified = ISC_FALSE; + + arg = mapGet(expr, "substring"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + string = mapGet(arg, "expression"); + if (string == NULL) + return expr; + offset = mapGet(arg, "offset"); + if (offset == NULL) + return expr; + length = mapGet(arg, "length"); + if (length == NULL) + return expr; + string = eval_data_expression(string, &smodified); + if (smodified) { + mapRemove(arg, "expression"); + mapSet(arg, string, "expression"); + } + offset = eval_numeric_expression(offset, &omodified); + if (omodified) { + mapRemove(arg, "offset"); + mapSet(arg, offset, "offset"); + } + length = eval_numeric_expression(length, &lmodified); + if (lmodified) { + mapRemove(arg, "length"); + mapSet(arg, length, "length"); + } + + if ((offset->type != ELEMENT_INTEGER) || + (length->type != ELEMENT_INTEGER)) + return expr; + off = intValue(offset); + len = intValue(length); + if ((off < 0) || (len < 0)) + return expr; + /* degenerated case */ + if (len == 0) { + *modifiedp = ISC_TRUE; + r = allocString(); + result = createString(r); + return result; + } + + /* return (part of) hw-address? */ + if ((local_family == AF_INET) && + (string->type == ELEMENT_MAP) && + mapContains(string, "concat") && + (off >= 1)) { + struct element *concat; + struct element *left; + struct element *right; + + concat = mapGet(string, "concat"); + if (concat->type != ELEMENT_MAP) + return expr; + left = mapGet(concat, "left"); + if (left == NULL) + return expr; + right = mapGet(concat, "right"); + if (right == NULL) + return expr; + /* from substring(hardware, ...) */ + if ((left->type == ELEMENT_MAP) && + mapContains(left, "hw-type")) { + *modifiedp = ISC_TRUE; + mapRemove(arg, "expression"); + mapSet(arg, right, "expression"); + mapRemove(arg, "offset"); + mapSet(arg, createInt(off - 1), "offset"); + return expr; + } + return expr; + } + + /* return hw-type? */ + if ((local_family == AF_INET) && + (string->type == ELEMENT_MAP) && + mapContains(string, "concat") && + (off == 0) && (len == 1)) { + struct element *concat; + struct element *left; + struct element *right; + + concat = mapGet(string, "concat"); + if (concat->type != ELEMENT_MAP) + return expr; + left = mapGet(concat, "left"); + if (left == NULL) + return expr; + right = mapGet(concat, "right"); + if (right == NULL) + return expr; + /* from substring(hardware, ...) */ + if ((left->type == ELEMENT_MAP) && + mapContains(left, "hw-type")) { + *modifiedp = ISC_TRUE; + return left; + } + return expr; + } + + if (string->type != ELEMENT_STRING) + return expr; + *modifiedp = ISC_TRUE; + s = stringValue(string); + if (s->length <= off) + r = allocString(); + else { + r = makeString(s->length - off, s->content + off); + if (r->length > len) + r->length = len; + } + result = createString(r); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &string->comments); + TAILQ_CONCAT(&result->comments, &offset->comments); + TAILQ_CONCAT(&result->comments, &length->comments); + return result; + } + + /* suffix */ + if (mapContains(expr, "suffix")) { + /* + * syntax := { "suffix": + * { "expression": <data_expression>, + * "length": <numeric_expression> } + * } + * semantic: evaluate arguments, if the string is + * shorter than length return it else return suffix + */ + struct element *arg; + struct element *string; + struct element *length; + struct element *result; + struct string *r; + int64_t len; + isc_boolean_t smodified = ISC_FALSE; + isc_boolean_t lmodified = ISC_FALSE; + + arg = mapGet(expr, "suffix"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + string = mapGet(arg, "expression"); + if (string == NULL) + return expr; + length = mapGet(arg, "length"); + if (length == NULL) + return expr; + string = eval_data_expression(string, &smodified); + if (smodified) { + mapRemove(arg, "expression"); + mapSet(arg, string, "expression"); + } + length = eval_numeric_expression(length, &lmodified); + if (lmodified) { + mapRemove(arg, "length"); + mapSet(arg, length, "length"); + } + + if ((string->type != ELEMENT_STRING) || + (length->type != ELEMENT_INTEGER)) + return expr; + len = intValue(length); + if (len < 0) + return expr; + *modifiedp = ISC_TRUE; + r = stringValue(string); + if (r->length > len) + r = makeString(r->length - len, r->content + len); + result = createString(r); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &string->comments); + TAILQ_CONCAT(&result->comments, &length->comments); + return result; + } + + /* lowercase */ + if (mapContains(expr, "lowercase")) { + /* + * syntax := { "lowercase": <data_expression> } + * semantic: evaluate its argument and apply tolower to + * its content + */ + struct element *arg; + struct element *result; + struct string *r; + size_t i; + isc_boolean_t modified = ISC_FALSE; + + arg = mapGet(expr, "lowercase"); + if (arg == NULL) + return expr; + arg = eval_data_expression(arg, &modified); + if (modified) { + mapRemove(expr, "lowercase"); + mapSet(expr, arg, "lowercase"); + } + + if (arg->type != ELEMENT_STRING) + return expr; + *modifiedp = ISC_TRUE; + r = allocString(); + concatString(r, stringValue(arg)); + for (i = 0; i < r->length; i++) + r->content[i] = tolower(r->content[i]); + result = createString(r); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + return result; + } + + /* uppercase */ + if (mapContains(expr, "uppercase")) { + /* + * syntax := { "uppercase": <data_expression> } + * semantic: evaluate its argument and apply toupper to + * its content + */ + struct element *arg; + struct element *result; + struct string *r; + size_t i; + isc_boolean_t modified = ISC_FALSE; + + arg = mapGet(expr, "uppercase"); + if (arg == NULL) + return expr; + arg = eval_data_expression(arg, &modified); + if (modified) { + mapRemove(expr, "lowercase"); + mapSet(expr, arg, "lowercase"); + } + + if (arg->type != ELEMENT_STRING) + return expr; + *modifiedp = ISC_TRUE; + r = allocString(); + concatString(r, stringValue(arg)); + for (i = 0; i < r->length; i++) + r->content[i] = toupper(r->content[i]); + result = createString(r); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + return result; + } + + /* option */ + if (mapContains(expr, "option")) + /* + * syntax := { "option": + * { "universe": <option_space_old>, + * "name": <option_name> } + * } + * semantic: get universe/code option from incoming packet + */ + return expr; + + /* hardware */ + if (mapContains(expr, "hardware")) { + /* + * syntax := { "hardware": null } + * semantic: get mac type and address from incoming packet + */ + struct element *left; + struct element *right; + struct element *concat; + struct element *result; + + if (local_family != AF_INET) + return expr; + *modifiedp = ISC_TRUE; + left = createMap(); + mapSet(left, createNull(), "hw-type"); + concat = createMap(); + mapSet(concat, left, "left"); + right = createMap(); + mapSet(right, createNull(), "hw-address"); + mapSet(concat, right, "right"); + result = createMap(); + mapSet(result, concat, "concat"); + return result; + } + + /* hw-type */ + if (mapContains(expr, "hw-type")) + /* + * syntax := { "hw-type": null } + * semantic: get mac type and address from incoming packet + */ + return expr; + + /* hw-address */ + if (mapContains(expr, "hw-address")) + /* + * syntax := { "hw-address": null } + * semantic: get mac type and address from incoming packet + */ + return expr; + + /* const-data */ + if (mapContains(expr, "const-data")) + /* + * syntax := { "const-data": <string> } + * semantic: embedded string value + */ + return expr; + + /* packet */ + if (mapContains(expr, "packet")) + /* + * syntax := { "packet": + * { "offset": <numeric_expression>, + * "length": <numeric_expression> } + * } + * semantic: return the selected substring of the incoming + * packet content + */ + return expr; + + /* concat */ + if (mapContains(expr, "concat")) { + /* + * syntax := { "concat": + * { "left": <data_expression>, + * "right": <data_expression> } + * } + * semantic: evaluate arguments and return the concatenation + */ + struct element *arg; + struct element *left; + struct element *right; + struct element *result; + struct string *r; + isc_boolean_t lmodified = ISC_FALSE; + isc_boolean_t rmodified = ISC_FALSE; + + arg = mapGet(expr, "concat"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + left = mapGet(arg, "left"); + if (left == NULL) + return expr; + right = mapGet(arg, "right"); + if (right == NULL) + return expr; + left = eval_data_expression(left, &lmodified); + if (lmodified) { + mapRemove(arg, "left"); + mapSet(arg, left, "left"); + } + right = eval_data_expression(right, &rmodified); + if (rmodified) { + mapRemove(arg, "right"); + mapSet(arg, right, "right"); + } + + /* degenerated cases */ + if ((left->type == ELEMENT_STRING) && + (stringValue(left)->length == 0)) { + *modifiedp = ISC_TRUE; + return right; + } + if ((right->type == ELEMENT_STRING) && + (stringValue(right)->length == 0)) { + *modifiedp = ISC_TRUE; + return left; + } + + if ((left->type != ELEMENT_STRING) || + (right->type != ELEMENT_STRING)) + return expr; + *modifiedp = ISC_TRUE; + r = allocString(); + concatString(r, stringValue(left)); + concatString(r, stringValue(right)); + result = createString(r); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &left->comments); + TAILQ_CONCAT(&result->comments, &right->comments); + return result; + } + + /* encapsulate */ + if (mapContains(expr, "encapsulate")) + /* + * syntax := { "encapsulate": <encapsulated_space> } + * semantic: encapsulate options of the given space + */ + return expr; + + /* encode-int8 */ + if (mapContains(expr, "encode-int8")) { + /* + * syntax := { "encode-int8": <numeric_expression> } + * semantic: return a string buffer with the evaluated + * number as content + */ + struct element *arg; + struct element *result; + struct string *r; + uint8_t val; + isc_boolean_t modified = ISC_FALSE; + + arg = mapGet(expr, "encode-int8"); + if (arg == NULL) + return expr; + arg = eval_numeric_expression(arg, &modified); + if (modified) { + mapRemove(expr, "encode-int8"); + mapSet(expr, arg, "encode-int8"); + } + + if (arg->type != ELEMENT_INTEGER) + return expr; + *modifiedp = ISC_TRUE; + val = (uint8_t)intValue(arg); + r = makeString(sizeof(val), (char *)&val); + result = createString(r); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + return result; + } + + /* encode-int16 */ + if (mapContains(expr, "encode-int16")) { + /* + * syntax := { "encode-int16": <numeric_expression> } + * semantic: return a string buffer with the evaluated + * number as content + */ + struct element *arg; + struct element *result; + struct string *r; + uint16_t val; + isc_boolean_t modified = ISC_FALSE; + + arg = mapGet(expr, "encode-int16"); + if (arg == NULL) + return expr; + arg = eval_numeric_expression(arg, &modified); + if (modified) { + mapRemove(expr, "encode-int16"); + mapSet(expr, arg, "encode-int16"); + } + + if (arg->type != ELEMENT_INTEGER) + return expr; + *modifiedp = ISC_TRUE; + val = (uint16_t)intValue(arg); + val = htons(val); + r = makeString(sizeof(val), (char *)&val); + result = createString(r); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + return result; + } + + /* encode-int32 */ + if (mapContains(expr, "encode-int32")) { + /* + * syntax := { "encode-int32": <numeric_expression> } + * semantic: return a string buffer with the evaluated + * number as content + */ + struct element *arg; + struct element *result; + struct string *r; + uint32_t val; + isc_boolean_t modified = ISC_FALSE; + + arg = mapGet(expr, "encode-int32"); + if (arg == NULL) + return expr; + arg = eval_numeric_expression(arg, &modified); + if (modified) { + mapRemove(expr, "encode-int32"); + mapSet(expr, arg, "encode-int32"); + } + + if (arg->type != ELEMENT_INTEGER) + return expr; + *modifiedp = ISC_TRUE; + val = (uint32_t)intValue(arg); + val = htonl(val); + r = makeString(sizeof(val), (char *)&val); + result = createString(r); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + return result; + } + + /* gethostbyname */ + if (mapContains(expr, "gethostbyname")) { + /* + * syntax := { "gethostbyname": <string> } + * semantic: call gethostbyname and return + * a binary buffer with addresses + */ + struct element *arg; + struct element *result; + struct string *r; + char *hostname; + struct hostent *h; + size_t i; + + if (local_family != AF_INET) + return expr; + arg = mapGet(expr, "gethostbyname"); + if ((arg == NULL) || (arg->type != ELEMENT_STRING)) + return expr; + hostname = stringValue(arg)->content; + h = gethostbyname(hostname); + r = allocString(); + if (h == NULL) { + switch (h_errno) { + case HOST_NOT_FOUND: + debug("gethostbyname: %s: host unknown", + hostname); + break; + case TRY_AGAIN: + debug("gethostbyname: %s: temporary name " + "server failure", hostname); + break; + case NO_RECOVERY: + debug("gethostbyname: %s: name server failed", + hostname); + break; + case NO_DATA: + debug("gethostbyname: %s: no A record " + "associated with address", hostname); + break; + } + } else + for (i = 0; h->h_addr_list[i] != NULL; i++) { + struct string *addr; + + addr = makeString(4, h->h_addr_list[i]); + concatString(r, addr); + } + *modifiedp = ISC_TRUE; + r = makeStringExt(r->length, r->content, 'X'); + result = createString(r); + TAILQ_CONCAT(&result->comments, &arg->comments); + return result; + } + + /* binary-to-ascii */ + if (mapContains(expr, "binary-to-ascii")) { + /* + * syntax := { "binary-to-ascii": + * { "base": <numeric_expression 2..16>, + * "width": <numeric_expression 8, 16 or 32>, + * "separator": <data_expression>, + * "buffer": <data_expression> } + * } + * semantic: split the input buffer into int8/16/32 numbers, + * output them separated by the given string + */ + struct element *arg; + struct element *base; + struct element *width; + struct element *separator; + struct element *buffer; + struct element *result; + struct string *sep; + struct string *buf; + struct string *r; + int64_t b; + int64_t w; + isc_boolean_t bmodified = ISC_FALSE; + isc_boolean_t wmodified = ISC_FALSE; + isc_boolean_t smodified = ISC_FALSE; + isc_boolean_t dmodified = ISC_FALSE; + + arg = mapGet(expr, "binary-to-ascii"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + base = mapGet(arg, "base"); + if (base == NULL) + return expr; + width = mapGet(arg, "width"); + if (width == NULL) + return expr; + separator = mapGet(arg, "separator"); + if (separator == NULL) + return expr; + buffer = mapGet(arg, "buffer"); + if (buffer == NULL) + return expr; + base = eval_numeric_expression(base, &bmodified); + if (bmodified) { + mapRemove(arg, "base"); + mapSet(arg, base, "base"); + } + width = eval_numeric_expression(width, &wmodified); + if (wmodified) { + mapRemove(arg, "width"); + mapSet(arg, width, "width"); + } + separator = eval_data_expression(separator, &smodified); + if (smodified) { + mapRemove(arg, "separator"); + mapSet(arg, separator, "separator"); + } + buffer = eval_data_expression(buffer, &dmodified); + if (dmodified) { + mapRemove(arg, "buffer"); + mapSet(arg, buffer, "buffer"); + } + + if ((base->type != ELEMENT_INTEGER) || + (width->type != ELEMENT_INTEGER) || + (separator->type != ELEMENT_STRING) || + (buffer->type != ELEMENT_STRING)) + return expr; + b = intValue(base); + if ((b < 2) || (b > 16)) + return expr; + if ((b != 8) && (b != 10) && (b != 16)) + return expr; + w = intValue(width); + if ((w != 8) && (w != 16) && (w != 32)) + return expr; + sep = stringValue(separator); + buf = stringValue(buffer); + r = allocString(); + if (w == 8) { + size_t i; + char *fmt; + + switch (b) { + case 8: + fmt = "%o"; + break; + case 10: + fmt = "%d"; + break; + case 16: + default: + fmt = "%x"; + break; + } + + for (i = 0; i < buf->length; i++) { + uint8_t val; + char num[4]; + + if (i != 0) + concatString(r, sep); + val = (uint8_t)buf->content[i]; + snprintf(num, sizeof(num), fmt, (int)val); + appendString(r, num); + } + } else if (w == 16) { + size_t i; + char *fmt; + + if ((buf->length % 2) != 0) + return expr; + + switch (b) { + case 8: + fmt = "%o"; + break; + case 10: + fmt = "%d"; + break; + case 16: + default: + fmt = "%x"; + break; + } + + for (i = 0; i < buf->length; i += 2) { + uint16_t val; + char num[8]; + + if (i != 0) + concatString(r, sep); + memcpy(&val, buf->content + i, 2); + val = ntohs(val); + snprintf(num, sizeof(num), fmt, (int)val); + appendString(r, num); + } + } else if (w == 32) { + size_t i; + char *fmt; + + if ((buf->length % 4) != 0) + return expr; + + switch (b) { + case 8: + fmt = "%llo"; + break; + case 10: + fmt = "%lld"; + break; + case 16: + default: + fmt = "%llx"; + break; + } + + for (i = 0; i < buf->length; i += 4) { + uint32_t val; + char num[40]; + + if (i != 0) + concatString(r, sep); + memcpy(&val, buf->content + i, 4); + val = ntohl(val); + snprintf(num, sizeof(num), fmt, + (long long)val); + appendString(r, num); + } + } + *modifiedp = ISC_TRUE; + result = createString(r); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &base->comments); + TAILQ_CONCAT(&result->comments, &width->comments); + TAILQ_CONCAT(&result->comments, &separator->comments); + TAILQ_CONCAT(&result->comments, &buffer->comments); + return result; + } + + /* filename */ + if (mapContains(expr, "filename")) + /* + * syntax := { "filename": null } + * semantic: get filename field from incoming DHCPv4 packet + */ + return expr; + + /* server-name */ + if (mapContains(expr, "server-name")) + /* + * syntax := { "server-name": null } + * semantic: get server-name field from incoming DHCPv4 packet + */ + return expr; + + /* reverse */ + if (mapContains(expr, "reverse")) { + /* + * syntax := { "reverse": + * { "width": <numeric_expression>, + * "buffer": <data_expression> } + * } + * semantic: reverse the input buffer by width chunks of bytes + */ + struct element *arg; + struct element *width; + struct element *buffer; + struct element *result; + struct string *buf; + struct string *r; + int64_t w; + size_t i; + isc_boolean_t wmodified = ISC_FALSE; + isc_boolean_t bmodified = ISC_FALSE; + + arg = mapGet(expr, "reverse"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + width = mapGet(arg, "width"); + if (width == NULL) + return expr; + buffer = mapGet(arg, "buffer"); + if (buffer == NULL) + return expr; + width = eval_numeric_expression(width, &wmodified); + if (wmodified) { + mapRemove(arg, "width"); + mapSet(arg, width, "width"); + } + buffer = eval_data_expression(buffer, &bmodified); + if (bmodified) { + mapRemove(arg, "buffer"); + mapSet(arg, buffer, "buffer"); + } + + if ((width->type != ELEMENT_INTEGER) || + (buffer->type != ELEMENT_STRING)) + return expr; + w = intValue(width); + if (w <= 0) + return expr; + buf = stringValue(buffer); + if ((buf->length % w) != 0) + return expr; + *modifiedp = ISC_TRUE; + r = allocString(); + concatString(r, buf); + for (i = 0; i < buf->length; i += w) { + memcpy(r->content + i, + buf->content + (buf->length - i - w), + w); + } + result = createString(r); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &width->comments); + TAILQ_CONCAT(&result->comments, &buffer->comments); + return result; + } + + /* pick-first-value */ + if (mapContains(expr, "pick-first-value")) { + /* + * syntax := { "pick-first-value": + * [ <data_expression>, ... ] + * } + * semantic: evaluates expressions and return the first + * not null, return null if all are null + */ + struct element *arg; + struct element *result; + size_t i; + isc_boolean_t modified; + isc_boolean_t can_decide = ISC_TRUE; + + arg = mapGet(expr, "pick-first-value"); + if ((arg == NULL) || (arg->type != ELEMENT_LIST)) + return expr; + + for (i = 0; i < listSize(arg); i++) { + struct element *item; + + item = listGet(arg, i); + if (item == NULL) + return expr; + modified = ISC_FALSE; + item = eval_data_expression(item, &modified); + if (modified) + listRemove(arg, i); + if (!can_decide) + goto restore; + if (item->type != ELEMENT_STRING) { + can_decide = ISC_FALSE; + goto restore; + } + if (stringValue(item)->length != 0) { + *modifiedp = ISC_TRUE; + TAILQ_CONCAT(&item->comments, &expr->comments); + TAILQ_CONCAT(&item->comments, &arg->comments); + return item; + } + restore: + listSet(arg, item, i); + } + if (!can_decide) + return expr; + *modifiedp = ISC_TRUE; + result = createString(allocString()); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + return result; + } + + /* host-decl-name */ + if (mapContains(expr, "host-decl-name")) + /* + * syntax := { "host-decl-name": null } + * semantic: return the name of the matching host + * declaration (aka revervation in kea) or null + */ + return expr; + + /* leased-address */ + if (mapContains(expr, "leased-address")) + /* + * syntax := { "leased-address": null } + * semantic: return the address of the assigned lease or + * log a message + */ + return expr; + + /* config-option */ + if (mapContains(expr, "config-option")) + /* + * syntax := { "config-option": + * { "universe": <option_space_old>, + * "name": <option_name> } + * } + * semantic: get universe/code option to send + */ + return expr; + + /* null */ + if (mapContains(expr, "null")) { + /* + * syntax := { "null": null } + * semantic: return null + */ + struct element *result; + + *modifiedp = ISC_TRUE; + result = createString(allocString()); + TAILQ_CONCAT(&result->comments, &expr->comments); + return result; + } + + /* gethostname */ + if (mapContains(expr, "gethostname")) { + /* + * syntax := { "gethostname": null } + * semantic: return gethostname + */ + struct element *result; + char buf[300 /* >= 255 + 1 */]; + + if (gethostname(buf, sizeof(buf)) != 0) { + debug("gethostname fails: %s", strerror(errno)); + return expr; + } + *modifiedp = ISC_TRUE; + result = createString(makeString(-1, buf)); + TAILQ_CONCAT(&result->comments, &expr->comments); + return result; + } + + /* v6relay */ + if (mapContains(expr, "v6relay")) { + /* + * syntax := { "v6relay": + * { "relay": <numeric_expression>, + * "relay-option" <data_expression> } + * } + * semantic: relay is a counter from client, 0 is no-op, + * 1 is the relay closest to the client, etc, option + * is a dhcp6 option ans is return when found + */ + struct element *arg; + struct element *relay; + isc_boolean_t modified = ISC_FALSE; + + if (local_family != AF_INET6) + return expr; + arg = mapGet(expr, "v6relay"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + relay = mapGet(arg, "relay"); + if (relay == NULL) + return expr; + relay = eval_numeric_expression(relay, &modified); + if (modified) { + mapRemove(arg, "relay"); + mapSet(arg, relay, "relay"); + } + return expr; + } + + return expr; +} + +/* + * numeric-expression :== EXTRACT_INT LPAREN data-expression + * COMMA number RPAREN | + * NUMBER + */ + +struct element * +eval_numeric_expression(struct element *expr, isc_boolean_t *modifiedp) +{ + /* trivial case: already done */ + if (expr->type == ELEMENT_INTEGER) + return expr; + + /* + * From is_numeric_expression + */ + + if (expr->type != ELEMENT_MAP) + return expr; + + /* extract-int8 */ + if (mapContains(expr, "extract-int8")) { + /* + * syntax := { "extract-int8": <data_expression> } + * semantic: extract from the evalkuated string buffer + * a number + */ + struct element *arg; + struct element *result; + uint8_t val = 0; + isc_boolean_t modified = ISC_FALSE; + + arg = mapGet(expr, "extract-int8"); + if (arg == NULL) + return expr; + arg = eval_data_expression(arg, &modified); + if (modified) { + mapRemove(expr, "extract-int8"); + mapSet(expr, arg, "extract-int8"); + } + + if (arg->type != ELEMENT_STRING) + return expr; + *modifiedp = ISC_TRUE; + if (stringValue(arg)->length > 0) + val = (uint8_t) stringValue(arg)->content[0]; + result = createInt(val); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + return result; + } + + /* extract-int16 */ + if (mapContains(expr, "extract-int16")) { + /* + * syntax := { "extract-int16": <data_expression> } + * semantic: extract from the evalkuated string buffer + * a number + */ + struct element *arg; + struct element *result; + uint16_t val; + isc_boolean_t modified = ISC_FALSE; + + arg = mapGet(expr, "extract-int16"); + if (arg == NULL) + return expr; + arg = eval_data_expression(arg, &modified); + if (modified) { + mapRemove(expr, "extract-int16"); + mapSet(expr, arg, "extract-int16"); + } + + if (arg->type != ELEMENT_STRING) + return expr; + if (stringValue(arg)->length < 2) + return expr; + *modifiedp = ISC_TRUE; + memcpy(&val, stringValue(arg)->content, 2); + val = ntohs(val); + result = createInt(val); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + return result; + } + + /* extract-int32 */ + if (mapContains(expr, "extract-int32")) { + /* + * syntax := { "extract-int32": <data_expression> } + * semantic: extract from the evalkuated string buffer + * a number + */ + struct element *arg; + struct element *result; + uint32_t val; + isc_boolean_t modified = ISC_FALSE; + + arg = mapGet(expr, "extract-int32"); + if (arg == NULL) + return expr; + arg = eval_data_expression(arg, &modified); + if (modified) { + mapRemove(expr, "extract-int32"); + mapSet(expr, arg, "extract-int32"); + } + + if (arg->type != ELEMENT_STRING) + return expr; + if (stringValue(arg)->length < 4) + return expr; + *modifiedp = ISC_TRUE; + memcpy(&val, stringValue(arg)->content, 4); + val = ntohl(val); + result = createInt(val); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + return result; + } + + /* const-int */ + if (mapContains(expr, "const-int")) { + /* + * syntax := { "const-int": <integer> } + * semantic: embedded integer value + */ + struct element *arg; + struct element *result; + + arg = mapGet(expr, "const-int"); + if ((arg == NULL) || (arg->type != ELEMENT_INTEGER)) + return expr; + *modifiedp = ISC_TRUE; + result = createInt(intValue(arg)); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + return result; + } + + /* lease-time */ + if (mapContains(expr, "lease-time")) + /* + * syntax := { "lease-time": null } + * semantic: return duration of the current lease, i.e + * the difference between expire time and now + */ + return expr; + + /* add */ + if (mapContains(expr, "add")) { + /* + * syntax := { "add": + * { "left": <boolean_expression>, + * "right": <boolean_expression> } + * } + * semantics: evaluate branches, return left plus right + * branches + */ + struct element *arg; + struct element *left; + struct element *right; + struct element *result; + isc_boolean_t lmodified = ISC_FALSE; + isc_boolean_t rmodified = ISC_FALSE; + + arg = mapGet(expr, "add"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + left = mapGet(arg, "left"); + if (left == NULL) + return expr; + right = mapGet(arg, "right"); + if (right == NULL) + return expr; + left = eval_numeric_expression(left, &lmodified); + if (lmodified) { + mapRemove(arg, "left"); + mapSet(arg, left, "left"); + } + right = eval_numeric_expression(right, &rmodified); + if (rmodified) { + mapRemove(arg, "right"); + mapSet(arg, right, "right"); + } + + if ((left->type != ELEMENT_INTEGER) || + (right->type != ELEMENT_INTEGER)) + return expr; + *modifiedp = ISC_TRUE; + result = createInt(intValue(left) + intValue(right)); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &left->comments); + TAILQ_CONCAT(&result->comments, &right->comments); + return result; + } + + /* subtract */ + if (mapContains(expr, "subtract")) { + /* + * syntax := { "subtract": + * { "left": <boolean_expression>, + * "right": <boolean_expression> } + * } + * semantics: evaluate branches, return left plus right + * branches + */ + struct element *arg; + struct element *left; + struct element *right; + struct element *result; + isc_boolean_t lmodified = ISC_FALSE; + isc_boolean_t rmodified = ISC_FALSE; + + arg = mapGet(expr, "subtract"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + left = mapGet(arg, "left"); + if (left == NULL) + return expr; + right = mapGet(arg, "right"); + if (right == NULL) + return expr; + left = eval_numeric_expression(left, &lmodified); + if (lmodified) { + mapRemove(arg, "left"); + mapSet(arg, left, "left"); + } + right = eval_numeric_expression(right, &rmodified); + if (rmodified) { + mapRemove(arg, "right"); + mapSet(arg, right, "right"); + } + + if ((left->type != ELEMENT_INTEGER) || + (right->type != ELEMENT_INTEGER)) + return expr; + *modifiedp = ISC_TRUE; + result = createInt(intValue(left) - intValue(right)); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &left->comments); + TAILQ_CONCAT(&result->comments, &right->comments); + return result; + } + + /* multiply */ + if (mapContains(expr, "multiply")) { + /* + * syntax := { "multiply": + * { "left": <boolean_expression>, + * "right": <boolean_expression> } + * } + * semantics: evaluate branches, return left plus right + * branches + */ + struct element *arg; + struct element *left; + struct element *right; + struct element *result; + isc_boolean_t lmodified = ISC_FALSE; + isc_boolean_t rmodified = ISC_FALSE; + + arg = mapGet(expr, "multiply"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + left = mapGet(arg, "left"); + if (left == NULL) + return expr; + right = mapGet(arg, "right"); + if (right == NULL) + return expr; + left = eval_numeric_expression(left, &lmodified); + if (lmodified) { + mapRemove(arg, "left"); + mapSet(arg, left, "left"); + } + right = eval_numeric_expression(right, &rmodified); + if (rmodified) { + mapRemove(arg, "right"); + mapSet(arg, right, "right"); + } + + if ((left->type != ELEMENT_INTEGER) || + (right->type != ELEMENT_INTEGER)) + return expr; + *modifiedp = ISC_TRUE; + result = createInt(intValue(left) * intValue(right)); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &left->comments); + TAILQ_CONCAT(&result->comments, &right->comments); + return result; + } + + /* divide */ + if (mapContains(expr, "divide")) { + /* + * syntax := { "divide": + * { "left": <boolean_expression>, + * "right": <boolean_expression> } + * } + * semantics: evaluate branches, return left plus right + * branches + */ + struct element *arg; + struct element *left; + struct element *right; + struct element *result; + isc_boolean_t lmodified = ISC_FALSE; + isc_boolean_t rmodified = ISC_FALSE; + + arg = mapGet(expr, "divide"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + left = mapGet(arg, "left"); + if (left == NULL) + return expr; + right = mapGet(arg, "right"); + if (right == NULL) + return expr; + left = eval_numeric_expression(left, &lmodified); + if (lmodified) { + mapRemove(arg, "left"); + mapSet(arg, left, "left"); + } + right = eval_numeric_expression(right, &rmodified); + if (rmodified) { + mapRemove(arg, "right"); + mapSet(arg, right, "right"); + } + + if ((left->type != ELEMENT_INTEGER) || + (right->type != ELEMENT_INTEGER)) + return expr; + if (intValue(right) == 0) + return expr; + *modifiedp = ISC_TRUE; + result = createInt(intValue(left) / intValue(right)); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &left->comments); + TAILQ_CONCAT(&result->comments, &right->comments); + return result; + } + + /* remainder */ + if (mapContains(expr, "remainder")) { + /* + * syntax := { "remainder": + * { "left": <boolean_expression>, + * "right": <boolean_expression> } + * } + * semantics: evaluate branches, return left plus right + * branches + */ + struct element *arg; + struct element *left; + struct element *right; + struct element *result; + isc_boolean_t lmodified = ISC_FALSE; + isc_boolean_t rmodified = ISC_FALSE; + + arg = mapGet(expr, "remainder"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + left = mapGet(arg, "left"); + if (left == NULL) + return expr; + right = mapGet(arg, "right"); + if (right == NULL) + return expr; + left = eval_numeric_expression(left, &lmodified); + if (lmodified) { + mapRemove(arg, "left"); + mapSet(arg, left, "left"); + } + right = eval_numeric_expression(right, &rmodified); + if (rmodified) { + mapRemove(arg, "right"); + mapSet(arg, right, "right"); + } + + if ((left->type != ELEMENT_INTEGER) || + (right->type != ELEMENT_INTEGER)) + return expr; + if (intValue(right) == 0) + return expr; + *modifiedp = ISC_TRUE; + result = createInt(intValue(left) % intValue(right)); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &left->comments); + TAILQ_CONCAT(&result->comments, &right->comments); + return result; + } + + /* binary-and */ + if (mapContains(expr, "binary-and")) { + /* + * syntax := { "binary-and": + * { "left": <boolean_expression>, + * "right": <boolean_expression> } + * } + * semantics: evaluate branches, return left plus right + * branches + */ + struct element *arg; + struct element *left; + struct element *right; + struct element *result; + isc_boolean_t lmodified = ISC_FALSE; + isc_boolean_t rmodified = ISC_FALSE; + + arg = mapGet(expr, "binary-and"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + left = mapGet(arg, "left"); + if (left == NULL) + return expr; + right = mapGet(arg, "right"); + if (right == NULL) + return expr; + left = eval_numeric_expression(left, &lmodified); + if (lmodified) { + mapRemove(arg, "left"); + mapSet(arg, left, "left"); + } + right = eval_numeric_expression(right, &rmodified); + if (rmodified) { + mapRemove(arg, "right"); + mapSet(arg, right, "right"); + } + + if ((left->type != ELEMENT_INTEGER) || + (right->type != ELEMENT_INTEGER)) + return expr; + *modifiedp = ISC_TRUE; + result = createInt(intValue(left) & intValue(right)); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &left->comments); + TAILQ_CONCAT(&result->comments, &right->comments); + return result; + } + + /* binary-or */ + if (mapContains(expr, "binary-or")) { + /* + * syntax := { "binary-or": + * { "left": <boolean_expression>, + * "right": <boolean_expression> } + * } + * semantics: evaluate branches, return left plus right + * branches + */ + struct element *arg; + struct element *left; + struct element *right; + struct element *result; + isc_boolean_t lmodified = ISC_FALSE; + isc_boolean_t rmodified = ISC_FALSE; + + arg = mapGet(expr, "binary-or"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + left = mapGet(arg, "left"); + if (left == NULL) + return expr; + right = mapGet(arg, "right"); + if (right == NULL) + return expr; + left = eval_numeric_expression(left, &lmodified); + if (lmodified) { + mapRemove(arg, "left"); + mapSet(arg, left, "left"); + } + right = eval_numeric_expression(right, &rmodified); + if (rmodified) { + mapRemove(arg, "right"); + mapSet(arg, right, "right"); + } + + if ((left->type != ELEMENT_INTEGER) || + (right->type != ELEMENT_INTEGER)) + return expr; + *modifiedp = ISC_TRUE; + result = createInt(intValue(left) | intValue(right)); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &left->comments); + TAILQ_CONCAT(&result->comments, &right->comments); + return result; + } + + /* binary-xor */ + if (mapContains(expr, "binary-xor")) { + /* + * syntax := { "binary-xor": + * { "left": <boolean_expression>, + * "right": <boolean_expression> } + * } + * semantics: evaluate branches, return left plus right + * branches + */ + struct element *arg; + struct element *left; + struct element *right; + struct element *result; + isc_boolean_t lmodified = ISC_FALSE; + isc_boolean_t rmodified = ISC_FALSE; + + arg = mapGet(expr, "binary-xor"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) + return expr; + left = mapGet(arg, "left"); + if (left == NULL) + return expr; + right = mapGet(arg, "right"); + if (right == NULL) + return expr; + left = eval_numeric_expression(left, &lmodified); + if (lmodified) { + mapRemove(arg, "left"); + mapSet(arg, left, "left"); + } + right = eval_numeric_expression(right, &rmodified); + if (rmodified) { + mapRemove(arg, "right"); + mapSet(arg, right, "right"); + } + + if ((left->type != ELEMENT_INTEGER) || + (right->type != ELEMENT_INTEGER)) + return expr; + *modifiedp = ISC_TRUE; + result = createInt(intValue(left) ^ intValue(right)); + TAILQ_CONCAT(&result->comments, &expr->comments); + TAILQ_CONCAT(&result->comments, &arg->comments); + TAILQ_CONCAT(&result->comments, &left->comments); + TAILQ_CONCAT(&result->comments, &right->comments); + return result; + } + + /* client-state */ + if (mapContains(expr, "client-state")) + /* + * syntax := { "client-state": null } + * semantic: return client state + */ + return expr; + + return expr; +} + +/* + * Check if the two evaluated expressions are equal, not equal, + * or we can't decide. + */ + +static struct element * +eval_equal_expression(struct element *left, struct element *right) +{ + struct element *result = NULL; + isc_boolean_t val; + + /* in theory boolean is not possible */ + if (left->type == ELEMENT_BOOLEAN) { + if (right->type == ELEMENT_BOOLEAN) + val = ISC_TF(boolValue(left) == boolValue(right)); + else if (right->type == ELEMENT_MAP) + return NULL; + else + val = ISC_FALSE; + } else + /* right is boolean */ + if (right->type == ELEMENT_BOOLEAN) { + if (left->type == ELEMENT_MAP) + return NULL; + else + val = ISC_FALSE; + } else + /* left is numeric literal */ + if (left->type == ELEMENT_INTEGER) { + if (right->type == ELEMENT_INTEGER) + val = ISC_TF(intValue(left) == intValue(right)); + else if ((right->type == ELEMENT_MAP) && + mapContains(right, "const-int")) { + struct element *ci; + + ci = mapGet(right, "const-int"); + if ((ci == NULL) || (ci->type != ELEMENT_INTEGER)) { + debug("bad const-int"); + return NULL; + } + val = ISC_TF(intValue(left) == intValue(ci)); + } else if (right->type == ELEMENT_MAP) + return NULL; + else + val = ISC_FALSE; + } else + /* left is const-int */ + if ((left->type == ELEMENT_MAP) && mapContains(left, "const-int")) { + if (right->type == ELEMENT_INTEGER) { + struct element *ci; + + ci = mapGet(left, "const-int"); + if ((ci == NULL) || (ci->type != ELEMENT_INTEGER)) { + debug("bad const-int"); + return NULL; + } + val = ISC_TF(intValue(ci) == intValue(right)); + } else if ((right->type == ELEMENT_MAP) && + mapContains(right, "const-int")) { + struct element *lci; + struct element *rci; + + lci = mapGet(left, "const-int"); + rci = mapGet(right, "const-int"); + if ((lci == NULL) || (lci->type != ELEMENT_INTEGER) || + (rci == NULL) || (rci->type != ELEMENT_INTEGER)) { + debug("bad const-int"); + return NULL; + } + val = ISC_TF(intValue(lci) == intValue(rci)); + } else if (right->type == ELEMENT_MAP) + return NULL; + else + val = ISC_FALSE; + } else + /* right is numeric literal */ + if (right->type == ELEMENT_INTEGER) { + if (left->type == ELEMENT_MAP) + return NULL; + else + val = ISC_FALSE; + } else + /* right is const-int */ + if ((right->type == ELEMENT_MAP) && mapContains(right, "const-int")) { + if (left->type == ELEMENT_MAP) + return NULL; + else + val = ISC_FALSE; + } else + /* left is data literal */ + if (left->type == ELEMENT_STRING) { + if (right->type == ELEMENT_STRING) + val = cmp_hexa(left, ISC_FALSE, right, ISC_FALSE); + else if ((right->type == ELEMENT_MAP) && + mapContains(right, "const-data")) { + struct element *cd; + + cd = mapGet(right, "const-data"); + if ((cd == NULL) || (cd->type != ELEMENT_STRING)) { + debug("bad const-data"); + return NULL; + } + val = cmp_hexa(left, ISC_FALSE, cd, ISC_TRUE); + } else if (right->type == ELEMENT_MAP) + return NULL; + else + val = ISC_FALSE; + } else + /* left is const-data */ + if ((left->type == ELEMENT_MAP) && mapContains(left, "const-data")) { + if (right->type == ELEMENT_STRING) { + struct element *cd; + + cd = mapGet(left, "const-data"); + if ((cd == NULL) || (cd->type != ELEMENT_STRING)) { + debug("bad const-data"); + return NULL; + } + val = cmp_hexa(cd, ISC_TRUE, right, ISC_FALSE); + } else if ((right->type == ELEMENT_MAP) && + mapContains(right, "const-data")) { + struct element *lcd; + struct element *rcd; + + lcd = mapGet(left, "const-data"); + rcd = mapGet(right, "const-data"); + if ((lcd == NULL) || (lcd->type != ELEMENT_STRING) || + (rcd == NULL) || (rcd->type != ELEMENT_STRING)) { + debug("bad const-data"); + return NULL; + } + val = cmp_hexa(lcd, ISC_TRUE, rcd, ISC_TRUE); + } else if (right->type == ELEMENT_MAP) + return NULL; + else + val = ISC_FALSE; + } else + /* right is data literal */ + if (right->type == ELEMENT_STRING) { + if (left->type == ELEMENT_MAP) + return NULL; + else + val = ISC_FALSE; + } else + /* right is const-data */ + if ((right->type == ELEMENT_MAP) && mapContains(right, "const-data")) { + if (left->type == ELEMENT_MAP) + return NULL; + else + val = ISC_FALSE; + } else + /* impossible cases */ + if ((left->type != ELEMENT_MAP) || (right->type != ELEMENT_MAP)) { + debug("equal between unexpected %s and %s", + type2name(left->type), type2name(right->type)); + val = ISC_FALSE; + } else + /* can't decide */ + return NULL; + + result = createBool(val); + TAILQ_CONCAT(&result->comments, &left->comments); + TAILQ_CONCAT(&result->comments, &right->comments); + return result; +} + +static isc_boolean_t +cmp_hexa(struct element *left, isc_boolean_t left_is_hexa, + struct element *right, isc_boolean_t right_is_hexa) +{ + struct string *sleft; + struct string *sright; + + /* both are not hexa */ + if (!left_is_hexa && !right_is_hexa) { + sleft = stringValue(left); + sright = stringValue(right); + /* eqString() compares lengths them use memcmp() */ + return eqString(sleft, sright); + } + + /* both are hexa */ + if (left_is_hexa && right_is_hexa) { + sleft = stringValue(left); + sright = stringValue(right); + if (sleft->length != sright->length) + return ISC_FALSE; + if (sleft->length == 0) { + debug("empty const-data"); + return ISC_TRUE; + } + return ISC_TF(strcasecmp(sleft->content, + sright->content) == 0); + } + + /* put the hexa at left */ + if (left_is_hexa) { + sleft = hexaValue(left); + sright = stringValue(right); + } else { + sleft = hexaValue(right); + sright = stringValue(left); + } + + /* hexa is double length */ + if (sleft->length != 2 * sright->length) + return ISC_FALSE; + + /* build the hexa representation */ + makeStringExt(sright->length, sright->content, 'X'); + + return ISC_TF(strcasecmp(sleft->content, sright->content) == 0); +} + +static void +debug(const char* fmt, ...) +{ + va_list list; + + va_start(list, fmt); + vfprintf(stderr, fmt, list); + fprintf(stderr, "\n"); + va_end(list); +} diff --git a/keama/json.c b/keama/json.c new file mode 100644 index 00000000..0b2667a2 --- /dev/null +++ b/keama/json.c @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2017 by Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +/* From Kea src/lib/cc/data.cc fromJSON() */ + +#include "keama.h" + +#include <stdlib.h> +#include <string.h> + +struct element * +json_parse(struct parse *cfile) +{ + struct element *elem; + const char *val; + enum dhcp_token token; + + elem = create(); + stackPush(cfile, elem); + cfile->stack[0] = elem; + cfile->stack_top = 0; + + token = next_token(&val, NULL, cfile); + switch (token) { + case NUMBER: + elem = createInt(atoll(val)); + TAILQ_CONCAT(&elem->comments, &cfile->comments); + break; + case STRING: + elem = createString(makeString(-1, val)); + TAILQ_CONCAT(&elem->comments, &cfile->comments); + break; + case NAME: + if (strcmp(val, "null") == 0) + elem = createNull(); + else if (strcmp(val, "true") == 0) + elem = createBool(ISC_TRUE); + else if (strcmp(val, "false") == 0) { + elem = createBool(ISC_FALSE); + elem->skip = ISC_TRUE; + } else + parse_error(cfile, "unknown name %s", val); + TAILQ_CONCAT(&elem->comments, &cfile->comments); + break; + case LBRACKET: + elem = json_list_parse(cfile); + break; + case LBRACE: + elem = json_map_parse(cfile); + break; + case END_OF_FILE: + parse_error(cfile, "unexpected end of file"); + default: + parse_error(cfile, "unexpected %s", val); + } + return elem; +} + +struct element * +json_list_parse(struct parse *cfile) +{ + struct element *list; + struct element *item; + const char *val; + enum dhcp_token token; + isc_boolean_t done = ISC_FALSE; + + list = createList(); + TAILQ_CONCAT(&list->comments, &cfile->comments); + stackPush(cfile, list); + do { + token = peek_token(&val, NULL, cfile); + switch (token) { + case RBRACKET: + done = ISC_TRUE; + break; + case END_OF_FILE: + parse_error(cfile, "unexpected end of file"); + case COMMA: + skip_token(&val, NULL, cfile); + if (listSize(list) == 0) + parse_error(cfile, "unexpected ','"); + item = json_parse(cfile); + listPush(list, item); + break; + default: + if (listSize(list) > 0) + parse_error(cfile, "expected ','"); + item = json_parse(cfile); + listPush(list, item); + break; + } + } while (!done); + skip_token(&val, NULL, cfile); + cfile->stack_top--; + return list; +} + +struct element * +json_map_parse(struct parse *cfile) +{ + struct element *map; + struct element *item; + const char *val; + const char *key; + enum dhcp_token token; + isc_boolean_t done = ISC_FALSE; + + map = createMap(); + TAILQ_CONCAT(&map->comments, &cfile->comments); + stackPush(cfile, map); + do { + token = peek_token(&val, NULL, cfile); + switch (token) { + case RBRACE: + done = ISC_TRUE; + break; + case END_OF_FILE: + parse_error(cfile, "unexpected end of file"); + case COMMA: + skip_token(&val, NULL, cfile); + if (mapSize(map) == 0) + parse_error(cfile, "unexpected ','"); + token = next_token(&val, NULL, cfile); + if (token != STRING) + parse_error(cfile, "unexpected %s, " + "expected \"key\":value", val); + key = strdup(val); + token = next_token(&val, NULL, cfile); + if (token != COLON) + parse_error(cfile, "unexpected %s, " + "expected ':'", val); + item = json_parse(cfile); + mapSet(map, item, key); + break; + case STRING: + skip_token(&val, NULL, cfile); + if (mapSize(map) > 0) + parse_error(cfile, "unexpected \"%s\", " + "expected ','", val); + key = strdup(val); + token = next_token(&val, NULL, cfile); + if (token != COLON) + parse_error(cfile, "unexpected %s, " + "expected ':'", val); + item = json_parse(cfile); + mapSet(map, item, key); + break; + default: + if (mapSize(map) == 0) + parse_error(cfile, "unexpected %s, " + "expected \"key\":value or '}'", + val); + else + parse_error(cfile, "unexpected %s, " + "expected ',' or '}'", val); + } + } while (!done); + skip_token(&val, NULL, cfile); + cfile->stack_top--; + return map; +} diff --git a/keama/keama.8 b/keama/keama.8 new file mode 100644 index 00000000..80a2823a --- /dev/null +++ b/keama/keama.8 @@ -0,0 +1,104 @@ +.\" keama.8 +.\" +.\" Copyright (c) 2017, 2018 by Internet Systems Consortium, Inc. ("ISC") +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +.\" OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.\" Internet Systems Consortium, Inc. +.\" 950 Charter Street +.\" Redwood City, CA 94063 +.\" <info@isc.org> +.\" https://www.isc.org/ +.\" +.\" This software has been written for Internet Systems Consortium +.\" by Ted Lemon in cooperation with Vixie Enterprises. +.\" +.\" Support and other services are available for ISC products - see +.\" https://www.isc.org for more information or to learn more about ISC. +.\" +.TH keama 8 +.SH NAME +keama - Kea Migration Assistant +.SH SYNOPSIS +.B keama +[ +.B -4 +| +.B -6] +[ +.B -D +] +[ +.B -N +] +[ +.B -r +.I {perform|fatal|pass} +] +[ +.B -l +.I hook-library-path +] +[ +.B -i +.I input-file +] +[ +.B -o +.I output-file +] +.SH DESCRIPTION +The Kea Migration Assistant converts an ISC DHCP configuration file into +the corresponding Kea configuration file. +.SH COMMAND LINE +.PP +\fIProtocol selection options:\fR +.TP +-4 +The input configuration is for DHCPv4. Incompatible with the \fB-6\fR +option. +.TP +-6 +The input configuration is for DHCPv6. Incompatible with the \fB-4\fR +option. +.TP +-D +Define ISC DHCP minimum, default and maximum builtin lifetimes. +.TP +-N +Instead of using global host reservations, put them in the matching subnet. +.TP +-r \fIaction\fR +Specify what to do with hostnames: resolve them into their first address, +raise a fatal error or pass them silently. +.TP +-p hook-library-path +Specify the path where hook libraries (e.g. flex-id) can be found +.TP +-i input-file +Specify the ISC DHCP configuration file to read. When it is not +given the standard input is used. +.TP +-o output-file +Specify the Kea configuration file to write. When it is not given +the standard output is used. +.PP +The number of conversion failures is returned. Note that any parsing warning +or error is fatal so please check and fix the ISC DHCP configuration file +before using this tool. +.SH SEE ALSO +dhcpd(8), kea-dhcp4(8), kea-dhcp6(8). +.SH AUTHOR +.B keama(8) +To learn more about Internet Systems Consortium, see +.B https://www.isc.org diff --git a/keama/keama.c b/keama/keama.c new file mode 100644 index 00000000..f1b50da0 --- /dev/null +++ b/keama/keama.c @@ -0,0 +1,225 @@ +/* + * Copyright(c) 2017, 2018 by Internet Systems Consortium, Inc.("ISC") + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include <sys/errno.h> +#include <arpa/inet.h> +#include <assert.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "keama.h" + +#define KEAMA_USAGE "Usage: keama [-4|-6] [-D] [-N]" \ + " [-r {perform|fatal|pass}\\n" \ + " [-l hook-library-path]" \ + " [-i input-file] [-o output-file]\n" + +static void +usage(const char *sfmt, const char *sarg) { + if (sfmt != NULL) { + fprintf(stderr, sfmt, sarg); + fprintf(stderr, "\n"); + } + fputs(KEAMA_USAGE, stderr); + exit(1); +} + +int local_family = 0; +char *hook_library_path = NULL; +char *input_file = NULL; +char *output_file = NULL; +FILE *input = NULL; +FILE *output = NULL; +isc_boolean_t use_isc_lifetimes = ISC_FALSE; +isc_boolean_t global_hr = ISC_TRUE; +isc_boolean_t json = ISC_FALSE; + +static const char use_noarg[] = "No argument for command: %s"; +static const char bad_resolve[] = "Bad -r argument: %s"; + +int +main(int argc, char **argv) { + int i, fd; + char *inbuf = NULL; + size_t oldsize = 0; + size_t newsize = 0; + ssize_t cc; + struct parse *cfile; + size_t cnt = 0; + + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-4") == 0) + local_family = AF_INET; + else if (strcmp(argv[i], "-6") == 0) + local_family = AF_INET6; + else if (strcmp(argv[i], "-D") == 0) + use_isc_lifetimes = ISC_TRUE; + else if (strcmp(argv[i], "-N") == 0) + global_hr = ISC_FALSE; + else if (strcmp(argv[i], "-T") == 0) + json = ISC_TRUE; + else if (strcmp(argv[i], "-r") == 0) { + if (++i == argc) + usage(use_noarg, argv[i - 1]); + if (strcmp(argv[i], "perform") == 0) + resolve = perform; + else if (strcmp(argv[i], "fatal") == 0) + resolve = fatal; + else if (strcmp(argv[i], "pass") == 0) + resolve = pass; + else + usage(bad_resolve, argv[i]); + } else if (strcmp(argv[i], "-l") == 0) { + if (++i == argc) + usage(use_noarg, argv[i - 1]); + hook_library_path = argv[i]; + } else if (strcmp(argv[i], "-i") == 0) { + if (++i == argc) + usage(use_noarg, argv[i - 1]); + input_file = argv[i]; + } else if (strcmp(argv[i], "-o") == 0) { + if (++i == argc) + usage(use_noarg, argv[i - 1]); + output_file = argv[i]; + } else + usage("Unknown command: %s", argv[i]); + } + + if (!json && (local_family == 0)) + usage("address family must be set using %s", "-4 or -6"); + + if (input_file == NULL) { + input_file = "--stdin--"; + fd = fileno(stdin); + for (;;) { + if (newsize == 0) + newsize = 1024; + else { + oldsize = newsize; + newsize *= 4; + } + inbuf = (char *)realloc(inbuf, newsize); + if (inbuf == 0) + usage("out of memory reading standard " + "input: %s", strerror(errno)); + cc = read(fd, inbuf + oldsize, newsize - oldsize); + if (cc < 0) + usage("error reading standard input: %s", + strerror(errno)); + if (cc + oldsize < newsize) { + newsize = cc + oldsize; + break; + } + } + } else { + fd = open(input_file, O_RDONLY); + if (fd < 0) + usage("Cannot open '%s' for reading", input_file); + } + + if (output_file) { + output = fopen(output_file, "w"); + if (output == NULL) + usage("Cannot open '%s' for writing", output_file); + } else + output = stdout; + + TAILQ_INIT(&parses); + cfile = new_parse(fd, inbuf, newsize, input_file, 0); + assert(cfile != NULL); + + if (json) { + struct element *elem; + + elem = json_parse(cfile); + if (elem != NULL) { + print(output, elem, 0, 0); + fprintf(output, "\n"); + } + } else { + spaces_init(); + options_init(); + cnt = conf_file_parse(cfile); + if (cfile->stack_top > 0) { + print(output, cfile->stack[0], 0, 0); + fprintf(output, "\n"); + } + } + + end_parse(cfile); + + exit(cnt); +} + +void +stackPush(struct parse *pc, struct element *elem) +{ + if (pc->stack_top + 2 >= pc->stack_size) { + size_t new_size = pc->stack_size + 10; + size_t amount = new_size * sizeof(struct element *); + + pc->stack = (struct element **)realloc(pc->stack, amount); + if (pc->stack == NULL) + parse_error(pc, "can't resize element stack"); + pc->stack_size = new_size; + } + pc->stack_top++; + pc->stack[pc->stack_top] = elem; +} + +void +parse_error(struct parse *cfile, const char *fmt, ...) +{ + va_list list; + char lexbuf[256]; + char mbuf[1024]; + char fbuf[1024]; + unsigned i, lix; + + snprintf(fbuf, sizeof(fbuf), "%s line %d: %s", + cfile->tlname, cfile->lexline, fmt); + + 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; + + fprintf(stderr, "%s\n%s\n", mbuf, cfile->token_line); + if (cfile->lexchar < 81) + fprintf(stderr, "%s^\n", lexbuf); + exit(-1); +} diff --git a/keama/keama.h b/keama/keama.h new file mode 100644 index 00000000..19593ad7 --- /dev/null +++ b/keama/keama.h @@ -0,0 +1,470 @@ +/* + * Copyright (c) 2017, 2018 by Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#ifndef EOL +#define EOL '\n' +#endif + +#include "data.h" +#include "dhctoken.h" + +#include <time.h> + +/* Resolution of FQDNs into IPv4 addresses */ +enum resolve { + perform = 0, /* resolve */ + fatal, /* raise a fatal error */ + pass /* pass the string wth a warning */ +} resolve; + +/* From includes/dhcp.h */ + +#define HTYPE_ETHER 1 +#define HTYPE_IEEE802 6 +#define HTYPE_FDDI 8 + +#define DHO_DHCP_SERVER_IDENTIFIER 54 +#define DHO_VENDOR_CLASS_IDENTIFIER 60 +#define DHO_USER_CLASS 77 +#define DHO_VIVSO_SUBOPTIONS 125 + +/* From includes/dhcp6.h */ +#define D6O_VENDOR_OPTS 17 +#define MAX_V6RELAY_HOPS 32 + +/* From includes/dhcpd.h */ + +extern int local_family; + +/* A parsing context. */ + +TAILQ_HEAD(parses, parse) parses; + +struct parse { + int lexline; + int lexchar; + char *token_line; + char *prev_line; + char *cur_line; + const char *tlname; + int eol_token; + + /* + * In order to give nice output when we have a parsing error + * in our file, we keep track of where we are in the line so + * that we can show the user. + * + * We need to keep track of two lines, because we can look + * ahead, via the "peek" function, to the next line sometimes. + * + * The "line1" and "line2" variables act as buffers for this + * information. The "lpos" variable tells us where we are in the + * line. + * + * When we "put back" a character from the parsing context, we + * do not want to have the character appear twice in the error + * output. So, we set a flag, the "ugflag", which the + * get_char() function uses to check for this condition. + */ + char line1[81]; + char line2[81]; + int lpos; + int line; + int tlpos; + int tline; + enum dhcp_token token; + int ugflag; + char *tval; + int tlen; + char tokbuf[1500]; + + int warnings_occurred; + int file; + char *inbuf; + size_t bufix, buflen; + size_t bufsiz; + + /* + * Additions for the Kea Migration Assistant. + */ + struct element **stack; + size_t stack_size; + size_t stack_top; + size_t issue_counter; + + /* don't save below this */ + struct comments comments; + + /* TAILQ_NEXT(self) is the saved state */ + TAILQ_ENTRY(parse) next; + +}; + +#define PARAMETER 0 +#define TOPLEVEL 1 +#define ROOT_GROUP 2 +#define HOST_DECL 3 +#define SHARED_NET_DECL 4 +#define SUBNET_DECL 5 +#define CLASS_DECL 6 +#define GROUP_DECL 7 +#define POOL_DECL 8 + +/* Used as an argument to parse_class_decl() */ +#define CLASS_TYPE_VENDOR 0 +#define CLASS_TYPE_USER 1 +#define CLASS_TYPE_CLASS 2 +#define CLASS_TYPE_SUBCLASS 3 + +#define CLASS_DECL_DELETED 1 +#define CLASS_DECL_DYNAMIC 2 +#define CLASS_DECL_STATIC 4 +#define CLASS_DECL_SUBCLASS 8 + +/* Hardware buffer size */ +#define HARDWARE_ADDR_LEN 20 + +/* Expression context */ + +enum expression_context { + context_any, /* indefinite */ + context_boolean, + context_data, + context_numeric, + context_dns, + context_data_or_numeric, /* indefinite */ + context_function +}; + +/* Statements */ + +enum statement_op { + null_statement, + if_statement, + add_statement, + eval_statement, + break_statement, + default_option_statement, + supersede_option_statement, + append_option_statement, + prepend_option_statement, + send_option_statement, + statements_statement, + on_statement, + switch_statement, + case_statement, + default_statement, + set_statement, + unset_statement, + let_statement, + define_statement, + log_statement, + return_statement, + execute_statement, + vendor_opt_statement +}; + +/* Expression tree structure. */ + +enum expr_op { + expr_none, + expr_match, + expr_check, + expr_equal, + expr_substring, + expr_suffix, + expr_concat, + expr_host_lookup, + expr_and, + expr_or, + expr_not, + expr_option, + expr_hardware, + expr_hw_type, /* derived from expr_hardware */ + expr_hw_address, /* derived from expr_hardware */ + expr_packet, + expr_const_data, + expr_extract_int8, + expr_extract_int16, + expr_extract_int32, + expr_encode_int8, + expr_encode_int16, + expr_encode_int32, + expr_const_int, + expr_exists, + expr_encapsulate, + expr_known, + expr_reverse, + expr_leased_address, + expr_binary_to_ascii, + expr_config_option, + expr_host_decl_name, + expr_pick_first_value, + expr_lease_time, + expr_dns_transaction, + expr_static, + expr_ns_add, + expr_ns_delete, + expr_ns_exists, + expr_ns_not_exists, + expr_not_equal, + expr_null, + expr_variable_exists, + expr_variable_reference, + expr_filename, + expr_sname, + expr_arg, + expr_funcall, + expr_function, + expr_add, + expr_subtract, + expr_multiply, + expr_divide, + expr_remainder, + expr_binary_and, + expr_binary_or, + expr_binary_xor, + expr_client_state, + expr_ucase, + expr_lcase, + expr_regex_match, + expr_iregex_match, + expr_gethostname, + expr_v6relay, + expr_concat_dclist +}; + +/* options */ + +enum option_status { + kea_unknown = 0, /* known only by ISC DHCP */ + isc_dhcp_unknown = 1, /* known only by Kea */ + known = 2, /* known by both ISC DHCP and Kea */ + special = 3, /* requires special processing */ + dynamic = 4 /* dynamic entry */ +}; + +struct option_def { /* ISC DHCP option definition */ + const char *name; /* option name */ + const char *format; /* format string */ + const char *space; /* space (aka universe) */ + unsigned code; /* code point */ + enum option_status status; /* status */ +}; + +struct space_def { /* ISC DHCP space definition */ + const char *old; /* ISC DHCP space name */ + const char *name; /* Kea space name */ + enum option_status status; /* status */ +}; + +struct space { + const char *old; /* ISC DHCP space name */ + const char *name; /* Kea space name */ + enum option_status status; /* status */ + struct element *vendor; /* vendor option */ + TAILQ_ENTRY(space) next; /* next space */ +}; + +struct option { + const char *old; /* ISC DHCP option name */ + const char *name; /* Kea option name */ + const char *format; /* ISC DHCP format string */ + const struct space *space; /* space (aka universe) */ + unsigned code; /* code point */ + enum option_status status; /* status */ + TAILQ_ENTRY(option) next; /* next option */ +}; + +/* Kea parse tools */ +void stackPush(struct parse *cfile, struct element *elem); + +/* From command line */ +extern char *hook_library_path; +extern isc_boolean_t use_isc_lifetimes; +extern isc_boolean_t global_hr; + +/* From common/parse.c */ +void parse_error(struct parse *, const char *, ...) + __attribute__((__format__(__printf__,2,3))) + __attribute__((noreturn)); + +/* conflex.c */ +struct parse *new_parse(int, char *, size_t, const char *, int); +void end_parse(struct parse *); +void save_parse_state(struct parse *); +void restore_parse_state(struct parse *); +enum dhcp_token next_token(const char **, unsigned *, struct parse *); +enum dhcp_token peek_token(const char **, unsigned *, struct parse *); +enum dhcp_token next_raw_token(const char **, unsigned *, struct parse *); +enum dhcp_token peek_raw_token(const char **, unsigned *, struct parse *); +/* + * Use skip_token when we are skipping a token we have previously + * used peek_token on as we know what the result will be in this case. + */ +#define skip_token(a,b,c) ((void) next_token((a),(b),(c))) + +/* confparse.c */ +size_t conf_file_parse(struct parse *); +void read_conf_file(struct parse *, const char *, int); +size_t conf_file_subparse(struct parse *, int); +isc_boolean_t parse_statement(struct parse *, int, isc_boolean_t); +void get_permit(struct parse *, struct element *); +void parse_pool_statement(struct parse *, int); +void parse_lbrace(struct parse *); +void parse_host_declaration(struct parse *); +void parse_class_declaration(struct parse *, int); +void parse_shared_net_declaration(struct parse *); +void parse_subnet_declaration(struct parse *); +void parse_subnet6_declaration(struct parse *); +void parse_group_declaration(struct parse *); +void close_group(struct parse *, struct element *); +struct element *parse_fixed_addr_param(struct parse *, enum dhcp_token); +void parse_address_range(struct parse *, int, size_t); +void parse_address_range6(struct parse *, int, size_t); +void parse_prefix6(struct parse *, int, size_t); +void parse_fixed_prefix6(struct parse *, size_t); +void parse_pool6_statement(struct parse *, int); +struct element *parse_allow_deny(struct parse *, int); +void parse_server_duid_conf(struct parse *); +void parse_directive(struct parse *); +void parse_option_space_dir(struct parse *); +void parse_option_code_dir(struct parse *, struct option *); +void parse_option_status_dir(struct parse *, struct option *, enum dhcp_token); +void parse_option_local_dir(struct parse *, struct option *); +void parse_option_define_dir(struct parse *, struct option *); + +/* parse.c */ +void skip_to_semi(struct parse *); +void skip_to_rbrace(struct parse *, int); +void parse_semi(struct parse *); +void parse_string(struct parse *, char **, unsigned *); +struct string *parse_host_name(struct parse *); +struct string *parse_ip_addr_or_hostname(struct parse *, isc_boolean_t); +struct string *parse_ip_addr(struct parse *); +struct string *parse_ip6_addr(struct parse *); +struct string *parse_ip6_addr_txt(struct parse *); +struct element *parse_hardware_param(struct parse *); +void parse_lease_time(struct parse *, time_t *); +struct string *parse_numeric_aggregate(struct parse *, + unsigned char *, unsigned *, + int, int, unsigned); +void convert_num(struct parse *, unsigned char *, const char *, + int, unsigned); +struct option *parse_option_name(struct parse *, isc_boolean_t, + isc_boolean_t *); +void parse_option_space_decl(struct parse *); +void parse_option_code_definition(struct parse *, struct option *); +void parse_vendor_code_definition(struct parse *, struct option *); +struct string *convert_format(const char *, isc_boolean_t *, isc_boolean_t *); +struct string *parse_base64(struct parse *); +struct string *parse_cshl(struct parse *); +struct string *parse_hexa(struct parse *); +isc_boolean_t parse_executable_statements(struct element *, + struct parse *, isc_boolean_t *, + enum expression_context); +isc_boolean_t parse_executable_statement(struct element *, + struct parse *, isc_boolean_t *, + enum expression_context, + isc_boolean_t); +isc_boolean_t parse_zone(struct element *, struct parse *); +isc_boolean_t parse_key(struct element *, struct parse *); +isc_boolean_t parse_on_statement(struct element *, struct parse *, + isc_boolean_t *); +isc_boolean_t parse_switch_statement(struct element *, struct parse *, + isc_boolean_t *); +isc_boolean_t parse_case_statement(struct element *, struct parse *, + isc_boolean_t *, enum expression_context); +isc_boolean_t parse_if_statement(struct element *, struct parse *, + isc_boolean_t *); +isc_boolean_t parse_boolean_expression(struct element *, struct parse *, + isc_boolean_t *); +/* currently unused */ +isc_boolean_t parse_boolean(struct parse *); +isc_boolean_t parse_data_expression(struct element *, struct parse *, + isc_boolean_t *); +isc_boolean_t numeric_expression(struct element *, struct parse *, + isc_boolean_t *); +isc_boolean_t parse_non_binary(struct element *, struct parse *, + isc_boolean_t *, enum expression_context); +isc_boolean_t parse_expression(struct element *, struct parse *, + isc_boolean_t *, enum expression_context, + struct element *, enum expr_op); +struct string *escape_option_string(unsigned, const char *, + isc_boolean_t *, isc_boolean_t *); +isc_boolean_t parse_option_data(struct element *, struct parse *, + struct option *); +isc_boolean_t parse_option_binary(struct element *, struct parse *, + struct option *, isc_boolean_t); +struct string * parse_option_textbin(struct parse *, struct option *); +isc_boolean_t parse_option_statement(struct element *, struct parse *, + struct option *, enum statement_op); +isc_boolean_t parse_config_data(struct element *, struct parse *, + struct option *); +isc_boolean_t parse_config_statement(struct element *, struct parse *, + struct option *, enum statement_op); +struct string *parse_option_token(struct parse *, const char *, + isc_boolean_t *, isc_boolean_t *, + isc_boolean_t *); +struct string *parse_option_token_binary(struct parse *, const char *); +struct string *parse_domain_list(struct parse *, isc_boolean_t); +isc_boolean_t is_boolean_expression(struct element *); +isc_boolean_t is_data_expression(struct element *); +isc_boolean_t is_numeric_expression(struct element *); +int expr_precedence(enum expr_op, struct element *); + +/* options.c */ +void spaces_init(void); +void options_init(void); +struct space *space_lookup(const char *); +struct option *option_lookup_name(const char *, const char *); +struct option *kea_lookup_name(const char *, const char *); +struct option *option_lookup_code(const char *, unsigned); +void push_space(struct space *); +void push_option(struct option *); +void add_option_data(struct element *, struct element *); +void merge_option_data(struct element *, struct element *); +struct comments *get_config_comments(unsigned); +const char *display_status(enum option_status); + +/* json.c */ +struct element *json_parse(struct parse *); +struct element *json_list_parse(struct parse *); +struct element *json_map_parse(struct parse *); + +/* print.c */ +const char *print_expression(struct element *, isc_boolean_t *); +const char *print_boolean_expression(struct element *, isc_boolean_t *); +const char *print_data_expression(struct element *, isc_boolean_t *); +const char *print_numeric_expression(struct element *, isc_boolean_t *); + +/* reduce.c */ +struct element *reduce_boolean_expression(struct element *); +struct element *reduce_data_expression(struct element *); +struct element *reduce_numeric_expression(struct element *); + +/* eval */ +struct element *eval_expression(struct element *, isc_boolean_t *); +struct element *eval_boolean_expression(struct element *, isc_boolean_t *); +struct element *eval_data_expression(struct element *, isc_boolean_t *); +struct element *eval_numeric_expression(struct element *, isc_boolean_t *); diff --git a/keama/options.c b/keama/options.c new file mode 100644 index 00000000..12fa5ddf --- /dev/null +++ b/keama/options.c @@ -0,0 +1,1154 @@ +/* + * Copyright (c) 2017, 2018 by Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "keama.h" + +TAILQ_HEAD(spaces, space) spaces; +TAILQ_HEAD(options, option) options; + +/* From common/tables.c */ + +/* Additional format codes: + + x - ISC DHCP and Kea string + Y - force full binary + u - undefined (parsed as X) +*/ + +/// SPACES +struct space_def space_defs[] = { + { "dhcp", "dhcp4", 2}, + { "nwip", "nwip", 0}, + { "agent", "dhcp-agent-options-space", 2}, + { "vendor-class", "_vivco_", 0}, + { "vendor", "_vivso_", 3}, + { "isc", "_isc_", 0}, + { "", "vendor-encapsulated-options-space", 1}, + { "_docsis3_", "vendor-4491", 1}, + { "dhcp6", "dhcp6", 2}, + { "vsio", "_vendor-opts-space_", 3}, + { "_vsio_", "vendor-opts-space", 1}, + { "isc6", "_isc6_", 0}, + { "_rsoo_", "rsoo-opts", 1}, + { "_isc6_", "vendor-2495", 1}, + { "server", "_server_", 0}, + { NULL, NULL, 0} +}; + +/// DHCPv4 +struct option_def options4[] = { + { "subnet-mask", "I", "dhcp", 1, 2}, + { "time-offset", "l", "dhcp", 2, 2}, + { "routers", "Ia", "dhcp", 3, 2}, + { "time-servers", "Ia", "dhcp", 4, 2}, + { "ien116-name-servers", "Ia", "dhcp", 5, 2}, + /// ien116-name-servers -> name-servers + { "domain-name-servers", "Ia", "dhcp", 6, 2}, + { "log-servers", "Ia", "dhcp", 7, 2}, + { "cookie-servers", "Ia", "dhcp", 8, 2}, + { "lpr-servers", "Ia", "dhcp", 9, 2}, + { "impress-servers", "Ia", "dhcp", 10, 2}, + { "resource-location-servers", "Ia", "dhcp", 11, 2}, + { "host-name", "t", "dhcp", 12, 2}, + { "boot-size", "S", "dhcp", 13, 2}, + { "merit-dump", "t", "dhcp", 14, 2}, + { "domain-name", "t", "dhcp", 15, 2}, + { "swap-server", "I", "dhcp", 16, 2}, + { "root-path", "t", "dhcp", 17, 2}, + { "extensions-path", "t", "dhcp", 18, 2}, + { "ip-forwarding", "f", "dhcp", 19, 2}, + { "non-local-source-routing", "f", "dhcp", 20, 2}, + { "policy-filter", "IIa", "dhcp", 21, 2}, + { "max-dgram-reassembly", "S", "dhcp", 22, 2}, + { "default-ip-ttl", "B", "dhcp", 23, 2}, + { "path-mtu-aging-timeout", "L", "dhcp", 24, 2}, + { "path-mtu-plateau-table", "Sa", "dhcp", 25, 2}, + { "interface-mtu", "S", "dhcp", 26, 2}, + { "all-subnets-local", "f", "dhcp", 27, 2}, + { "broadcast-address", "I", "dhcp", 28, 2}, + { "perform-mask-discovery", "f", "dhcp", 29, 2}, + { "mask-supplier", "f", "dhcp", 30, 2}, + { "router-discovery", "f", "dhcp", 31, 2}, + { "router-solicitation-address", "I", "dhcp", 32, 2}, + { "static-routes", "IIa", "dhcp", 33, 2}, + { "trailer-encapsulation", "f", "dhcp", 34, 2}, + { "arp-cache-timeout", "L", "dhcp", 35, 2}, + { "ieee802-3-encapsulation", "f", "dhcp", 36, 2}, + { "default-tcp-ttl", "B", "dhcp", 37, 2}, + { "tcp-keepalive-interval", "L", "dhcp", 38, 2}, + { "tcp-keepalive-garbage", "f", "dhcp", 39, 2}, + { "nis-domain", "t", "dhcp", 40, 2}, + { "nis-servers", "Ia", "dhcp", 41, 2}, + { "ntp-servers", "Ia", "dhcp", 42, 2}, + { "vendor-encapsulated-options", "E.", "dhcp", 43, 2}, + { "netbios-name-servers", "Ia", "dhcp", 44, 2}, + { "netbios-dd-server", "Ia", "dhcp", 45, 2}, + { "netbios-node-type", "B", "dhcp", 46, 2}, + { "netbios-scope", "t", "dhcp", 47, 2}, + { "font-servers", "Ia", "dhcp", 48, 2}, + { "x-display-manager", "Ia", "dhcp", 49, 2}, + { "dhcp-requested-address", "I", "dhcp", 50, 2}, + { "dhcp-lease-time", "L", "dhcp", 51, 2}, + { "dhcp-option-overload", "B", "dhcp", 52, 2}, + { "dhcp-message-type", "B", "dhcp", 53, 2}, + { "dhcp-server-identifier", "I", "dhcp", 54, 2}, + { "dhcp-parameter-request-list", "Ba", "dhcp", 55, 2}, + { "dhcp-message", "t", "dhcp", 56, 2}, + { "dhcp-max-message-size", "S", "dhcp", 57, 2}, + { "dhcp-renewal-time", "L", "dhcp", 58, 2}, + { "dhcp-rebinding-time", "L", "dhcp", 59, 2}, + { "vendor-class-identifier", "x", "dhcp", 60, 2}, + { "dhcp-client-identifier", "X", "dhcp", 61, 2}, + { "nwip-domain", "t", "dhcp", 62, 2}, + /// nwip-domain nwip-domain-name + { "nwip-suboptions", "Enwip.", "dhcp", 63, 2}, + { "nisplus-domain", "t", "dhcp", 64, 2}, + /// nisplus-domain nisplus-domain-name + { "nisplus-servers", "Ia", "dhcp", 65, 2}, + { "tftp-server-name", "t", "dhcp", 66, 2}, + { "bootfile-name", "t", "dhcp", 67, 2}, + /// bootfile-name boot-file-name + { "mobile-ip-home-agent", "Ia", "dhcp", 68, 2}, + { "smtp-server", "Ia", "dhcp", 69, 2}, + { "pop-server", "Ia", "dhcp", 70, 2}, + { "nntp-server", "Ia", "dhcp", 71, 2}, + { "www-server", "Ia", "dhcp", 72, 2}, + { "finger-server", "Ia", "dhcp", 73, 2}, + { "irc-server", "Ia", "dhcp", 74, 2}, + { "streettalk-server", "Ia", "dhcp", 75, 2}, + { "streettalk-directory-assistance-server", "Ia", + "dhcp", 76, 2}, + { "user-class", "tY", "dhcp", 77, 2}, + { "slp-directory-agent", "fIa", "dhcp", 78, 2}, + { "slp-service-scope", "fto", "dhcp", 79, 2}, + /* 80 is the zero-length rapid-commit (RFC 4039) */ + { "fqdn", "Efqdn.", "dhcp", 81, 2}, + { "relay-agent-information", "Eagent.", "dhcp", 82, 2}, + /// relay-agent-information dhcp-agent-options + /* 83 is iSNS (RFC 4174) */ + /* 84 is unassigned */ + { "nds-servers", "Ia", "dhcp", 85, 2}, + { "nds-tree-name", "t", "dhcp", 86, 2}, + { "nds-context", "t", "dhcp", 87, 2}, + { "bcms-controller-names", "D", "dhcp", 88, 2}, + { "bcms-controller-address", "Ia", "dhcp", 89, 2}, + { "authenticate", "X", "dhcp", 90, 1}, + /// not supported by ISC DHCP + { "client-last-transaction-time", "L", "dhcp", 91, 2}, + { "associated-ip", "Ia", "dhcp", 92, 2}, + { "pxe-system-type", "Sa", "dhcp", 93, 2}, + // pxe-system-type client-system + { "pxe-interface-id", "BBB", "dhcp", 94, 2}, + // pxe-interface-id client-ndi + { "pxe-client-id", "BX", "dhcp", 97, 2}, + // pxe-client-id uuid-guid + { "uap-servers", "t", "dhcp", 98, 2}, + { "geoconf-civic", "X", "dhcp", 99, 2}, + { "pcode", "t", "dhcp", 100, 2}, + { "tcode", "t", "dhcp", 101, 2}, + { "netinfo-server-address", "Ia", "dhcp", 112, 2}, + { "netinfo-server-tag", "t", "dhcp", 113, 2}, + { "default-url", "t", "dhcp", 114, 2}, + { "auto-config", "B", "dhcp", 116, 2}, + { "name-service-search", "Sa", "dhcp", 117, 2}, + { "subnet-selection", "I", "dhcp", 118, 2}, + { "domain-search", "Dc", "dhcp", 119, 2}, + { "vivco", "Evendor-class.", "dhcp", 124, 2}, + /// vivco vivco-suboptions + { "vivso", "Evendor.", "dhcp", 125, 2}, + /// vivso vivso-suboptions + {"pana-agent", "Ia", "dhcp", 136, 2}, + {"v4-lost", "d", "dhcp", 137, 2}, + {"capwap-ac-v4", "Ia", "dhcp", 138, 2}, + { "sip-ua-cs-domains", "Dc", "dhcp", 141, 2}, + { "ipv4-address-andsf", "Ia", "dhcp", 142, 0}, + /// not supported by Kea + { "rdnss-selection", "BIID", "dhcp", 146, 2}, + { "tftp-server-address", "Ia", "dhcp", 150, 0}, + /// not supported by Kea + { "v4-portparams", "BBS", "dhcp", 159, 2}, + { "v4-captive-portal", "t", "dhcp", 160, 2}, + { "option-6rd", "BB6Ia", "dhcp", 212, 2}, + {"v4-access-domain", "d", "dhcp", 213, 2}, + { NULL, NULL, NULL, 0, 0 } +}; + +/// DHCPv6 +struct option_def options6[] = { + { "client-id", "X", "dhcp6", 1, 2}, + /// client-id clientid + { "server-id", "X", "dhcp6", 2, 2}, + /// server-id serverid + { "ia-na", "X", "dhcp6", 3, 2}, + { "ia-ta", "X", "dhcp6", 4, 2}, + { "ia-addr", "X", "dhcp6", 5, 2}, + /// ia-addr iaaddr + { "oro", "Sa", "dhcp6", 6, 2}, + { "preference", "B", "dhcp6", 7, 2}, + { "elapsed-time", "S", "dhcp6", 8, 2}, + { "relay-msg", "X", "dhcp6", 9, 2}, + /// 10 is unassigned + { "auth", "X", "dhcp6", 11, 1}, + /// not supported by ISC DHCP + { "unicast", "6", "dhcp6", 12, 2}, + { "status-code", "Nstatus-codes.to", "dhcp6", 13, 2}, + { "rapid-commit", "Z", "dhcp6", 14, 2}, + { "user-class", "X", "dhcp6", 15, 1}, + /// not supported by ISC DHCP + { "vendor-class", "LX", "dhcp6", 16, 1}, + /// not supported by ISC DHCP + { "vendor-opts", "Evsio.", "dhcp6", 17, 2}, + { "interface-id", "X", "dhcp6", 18, 2}, + { "reconf-msg", "Ndhcpv6-messages.", "dhcp6", 19, 2}, + { "reconf-accept", "Z", "dhcp6", 20, 2}, + { "sip-servers-names", "D", "dhcp6", 21, 2}, + /// sip-servers-names sip-server-dns + { "sip-servers-addresses", "6a", "dhcp6", 22, 2}, + /// sip-servers-addresses sip-server-addr + { "name-servers", "6a", "dhcp6", 23, 2}, + /// name-servers dns-servers + { "domain-search", "D", "dhcp6", 24, 2}, + { "ia-pd", "X", "dhcp6", 25, 2}, + { "ia-prefix", "X", "dhcp6", 26, 2}, + /// ia-prefix iaprefix + { "nis-servers", "6a", "dhcp6", 27, 2}, + { "nisp-servers", "6a", "dhcp6", 28, 2}, + { "nis-domain-name", "D", "dhcp6", 29, 2}, + { "nisp-domain-name", "D", "dhcp6", 30, 2}, + { "sntp-servers", "6a", "dhcp6", 31, 2}, + { "info-refresh-time", "T", "dhcp6", 32, 2}, + /// info-refresh-time information-refresh-time + { "bcms-server-d", "D", "dhcp6", 33, 2}, + /// bcms-server-d bcms-server-dns + { "bcms-server-a", "6a", "dhcp6", 34, 2}, + /// bcms-server-a bcms-server-addr + /* Note that 35 is not assigned. */ + { "geoconf-civic", "X", "dhcp6", 36, 2}, + { "remote-id", "X", "dhcp6", 37, 2}, + { "subscriber-id", "X", "dhcp6", 38, 2}, + { "fqdn", "Efqdn6-if-you-see-me-its-a-bug-bug-bug.", + "dhcp6", 39, 2}, + /// fqdn client-fqdn + { "pana-agent", "6a", "dhcp6", 40, 2}, + { "new-posix-timezone", "t", "dhcp6", 41, 2}, + { "new-tzdb-timezone", "t", "dhcp6", 42, 2}, + { "ero", "Sa", "dhcp6", 43, 2}, + { "lq-query", "X", "dhcp6", 44, 2}, + { "client-data", "X", "dhcp6", 45, 2}, + { "clt-time", "L", "dhcp6", 46, 2}, + { "lq-relay-data", "6X", "dhcp6", 47, 2}, + { "lq-client-link", "6a", "dhcp6", 48, 2}, + { "v6-lost", "d", "dhcp6", 51, 2}, + { "capwap-ac-v6", "6a", "dhcp6", 52, 2}, + { "relay-id", "X", "dhcp6", 53, 2}, + { "v6-access-domain", "d", "dhcp6", 57, 2}, + { "sip-ua-cs-list", "D", "dhcp6", 58, 2}, + { "bootfile-url", "t", "dhcp6", 59, 2}, + { "bootfile-param", "X", "dhcp6", 60, 2}, + { "client-arch-type", "Sa", "dhcp6", 61, 2}, + { "nii", "BBB", "dhcp6", 62, 2}, + { "aftr-name", "d", "dhcp6", 64, 2}, + { "erp-local-domain-name", "d", "dhcp6", 65, 2}, + { "rsoo", "Ersoo.", "dhcp6", 66, 1}, + /// not supported by ISC DHCP + { "pd-exclude", "X", "dhcp6", 67, 1}, + /// not supported by ISC DHCP (prefix6 format) + { "rdnss-selection", "6BD", "dhcp6", 74, 2}, + { "client-linklayer-addr", "X", "dhcp6", 79, 2}, + { "link-address", "6", "dhcp6", 80, 2}, + { "solmax-rt", "L", "dhcp6", 82, 2}, + { "inf-max-rt", "L", "dhcp6", 83, 2}, + { "dhcpv4-msg", "X", "dhcp6", 87, 2}, + /// dhcpv4-msg dhcpv4-message + { "dhcp4-o-dhcp6-server", "6a", "dhcp6", 88, 2}, + /// dhcp4-o-dhcp6-server dhcp4o6-server-addr + { "v6-captive-portal", "t", "dhcp6", 103, 2}, + { "relay-source-port", "S", "dhcp6", 135, 2}, + { "ipv6-address-andsf", "6a", "dhcp6", 143, 2}, + { NULL, NULL, NULL, 0, 0 } +}; + +/// DHCPv4 AGENT +struct option_def agents[] = { + /// All not supported by Kea + { "circuit-id", "X", "agent", 1, 0}, + { "remote-id", "X", "agent", 2, 0}, + { "agent-id", "I", "agent", 3, 0}, + { "DOCSIS-device-class", "L", "agent", 4, 0}, + { "link-selection", "I", "agent", 5, 0}, + { "relay-port", "Z", "agent", 19, 0}, + { NULL, NULL, NULL, 0, 0 } +}; + +/// SERVER +struct option_def configs[] = { + { "default-lease-time", "T", "server", 1, 3}, + { "max-lease-time", "T", "server", 2, 3}, + { "min-lease-time", "T", "server", 3, 3}, + { "dynamic-bootp-lease-cutoff", "T", "server", 4, 0}, + { "dynamic-bootp-lease-length", "L", "server", 5, 0}, + { "boot-unknown-clients", "f", "server", 6, 0}, + { "dynamic-bootp", "f", "server", 7, 0}, + { "allow-bootp", "f", "server", 8, 0}, + { "allow-booting", "f", "server", 9, 0}, + { "one-lease-per-client", "f", "server", 10, 0}, + { "get-lease-hostnames", "f", "server", 11, 0}, + { "use-host-decl-names", "f", "server", 12, 0}, + { "use-lease-addr-for-default-route", "f", + "server", 13, 0}, + { "min-secs", "B", "server", 14, 0}, + { "filename", "t", "server", 15, 3}, + { "server-name", "t", "server", 16, 3}, + { "next-server", "I", "server", 17, 3}, + { "authoritative", "f", "server", 18, 3}, + { "vendor-option-space", "U", "server", 19, 3}, + { "always-reply-rfc1048", "f", "server", 20, 0}, + { "site-option-space", "X", "server", 21, 3}, + { "always-broadcast", "f", "server", 22, 0}, + { "ddns-domainname", "t", "server", 23, 3}, + { "ddns-hostname", "t", "server", 24, 0}, + { "ddns-rev-domainname", "t", "server", 25, 0}, + { "lease-file-name", "t", "server", 26, 0}, + { "pid-file-name", "t", "server", 27, 0}, + { "duplicates", "f", "server", 28, 0}, + { "declines", "f", "server", 29, 0}, + { "ddns-updates", "f", "server", 30, 3}, + { "omapi-port", "S", "server", 31, 0}, + { "local-port", "S", "server", 32, 0}, + { "limited-broadcast-address", "I", "server", 33, 0}, + { "remote-port", "S", "server", 34, 0}, + { "local-address", "I", "server", 35, 0}, + { "omapi-key", "d", "server", 36, 0}, + { "stash-agent-options", "f", "server", 37, 0}, + { "ddns-ttl", "T", "server", 38, 0}, + { "ddns-update-style", "Nddns-styles.", "server", 39, 3}, + { "client-updates", "f", "server", 40, 0}, + { "update-optimization", "f", "server", 41, 0}, + { "ping-check", "f", "server", 42, 0}, + { "update-static-leases", "f", "server", 43, 0}, + { "log-facility", "Nsyslog-facilities.", + "server", 44, 0}, + { "do-forward-updates", "f", "server", 45, 0}, + { "ping-timeout", "T", "server", 46, 0}, + { "infinite-is-reserved", "f", "server", 47, 0}, + { "update-conflict-detection", "f", "server", 48, 0}, + { "leasequery", "f", "server", 49, 0}, + { "adaptive-lease-time-threshold", "B", "server", 50, 0}, + { "do-reverse-updates", "f", "server", 51, 0}, + { "fqdn-reply", "f", "server", 52, 0}, + { "preferred-lifetime", "T", "server", 53, 3}, + { "dhcpv6-lease-file-name", "t", "server", 54, 0}, + { "dhcpv6-pid-file-name", "t", "server", 55, 0}, + { "limit-addrs-per-ia", "L", "server", 56, 0}, + { "limit-prefs-per-ia", "L", "server", 57, 0}, + { "delayed-ack", "S", "server", 58, 0}, + { "max-ack-delay", "L", "server", 59, 0}, + /* LDAP */ + { "dhcp-cache-threshold", "B", "server", 78, 0}, + { "dont-use-fsync", "f", "server", 79, 0}, + { "ddns-local-address4", "I", "server", 80, 0}, + { "ddns-local-address6", "6", "server", 81, 0}, + { "ignore-client-uids", "f", "server", 82, 3}, + { "log-threshold-low", "B", "server", 83, 0}, + { "log-threshold-high", "B", "server", 84, 0}, + { "echo-client-id", "f", "server", 85, 3}, + { "server-id-check", "f", "server", 86, 0}, + { "prefix-length-mode", "Nprefix_length_modes.", + "server", 87, 0}, + { "dhcpv6-set-tee-times", "f", "server", 88, 0}, + { "abandon-lease-time", "T", "server", 89, 0}, + { "use-eui-64", "f", "server", 90, 0}, + { "check-secs-byte-order", "f", "server", 91, 0}, + { "persist-eui-64-leases", "f", "server", 92, 0}, + { "ddns-dual-stack-mixed-mode", "f", "server", 93, 0}, + { "ddns-guard-id-must-match", "f", "server", 94, 0}, + { "ddns-other-guard-is-dynamic", "f", "server", 95, 0}, + { "release-on-roam", "f", "server", 96, 0}, + { "local-address6", "6", "server", 97, 0}, + { "bind-local-address6", "f", "server", 98, 0}, + { "ping-cltt-secs", "T", "server", 99, 0}, + { "ping-timeout-ms", "T", "server", 100, 0}, + { NULL, NULL, NULL, 0, 0 } +}; + +void +spaces_init(void) +{ + struct space_def *def; + struct space *space; + + TAILQ_INIT(&spaces); + + /* Fill spaces */ + for (def = space_defs; def->name != NULL; def++) { + space = (struct space *)malloc(sizeof(*space)); + assert(space != NULL); + memset(space, 0, sizeof(*space)); + space->old = def->old; + space->name = def->name; + space->status = def->status; + TAILQ_INSERT_TAIL(&spaces, space); + } +} + +void +options_init(void) +{ + struct option_def *def; + struct option *option; + + TAILQ_INIT(&options); + + /* Fill DHCPv4 options */ + for (def = options4; def->name != NULL; def++) { + option = (struct option *)malloc(sizeof(*option)); + assert(option != NULL); + memset(option, 0, sizeof(*option)); + option->old = def->name; + switch (def->code) { + case 5: + option->name = "name-servers"; + break; + case 62: + option->name = "nwip-domain-name"; + break; + case 64: + option->name = "nisplus-domain-name"; + break; + case 67: + option->name = "boot-file-name"; + break; + case 82: + option->name = "dhcp-agent-options"; + break; + case 93: + option->name = "client-system"; + break; + case 94: + option->name = "client-ndi"; + break; + case 97: + option->name = "uuid-guid"; + break; + case 124: + option->name = "vivco-suboptions"; + break; + case 125: + option->name = "vivso-suboptions"; + break; + default: + option->name = def->name; + } + option->format = def->format; + option->space = space_lookup(def->space); + assert(option->space != NULL); + option->code = def->code; + option->status = def->status; + TAILQ_INSERT_TAIL(&options, option); + } + + /* Fill DHCPv6 options */ + for (def = options6; def->name != NULL; def++) { + option = (struct option *)malloc(sizeof(*option)); + assert(option != NULL); + memset(option, 0, sizeof(*option)); + option->old = def->name; + switch (def->code) { + case 1: + option->name = "clientid"; + break; + case 2: + option->name = "serverid"; + break; + case 5: + option->name = "iaaddr"; + break; + case 21: + option->name = "sip-server-dns"; + break; + case 22: + option->name = "sip-server-addr"; + break; + case 23: + option->name = "dns-servers"; + break; + case 26: + option->name = "iaprefix"; + break; + case 32: + option->name = "information-refresh-time"; + break; + case 33: + option->name = "bcms-server-dns"; + break; + case 34: + option->name = "bcms-server-addr "; + break; + case 39: + option->name = "client-fqdn"; + break; + case 87: + option->name = "dhcpv4-message"; + break; + case 88: + option->name = "dhcp4o6-server-addr"; + break; + default: + option->name = def->name; + break; + } + option->format = def->format; + option->space = space_lookup(def->space); + assert(option->space != NULL); + option->code = def->code; + option->status = def->status; + TAILQ_INSERT_TAIL(&options, option); + } + + /* Fill agent options */ + for (def = agents; def->name != NULL; def++) { + option = (struct option *)malloc(sizeof(*option)); + assert(option != NULL); + memset(option, 0, sizeof(*option)); + option->old = def->name; + option->name = def->name; + option->format = def->format; + option->space = space_lookup(def->space); + assert(option->space != NULL); + option->code = def->code; + option->status = def->status; + TAILQ_INSERT_TAIL(&options, option); + } + + /* Fill server config options */ + for (def = configs; def->name != NULL; def++) { + option = (struct option *)malloc(sizeof(*option)); + assert(option != NULL); + memset(option, 0, sizeof(*option)); + option->old = def->name; + option->name = def->name; + option->format = def->format; + option->space = space_lookup(def->space); + assert(option->space != NULL); + option->code = def->code; + option->status = def->status; + TAILQ_INSERT_TAIL(&options, option); + } +} + +struct space * +space_lookup(const char *name) +{ + struct space *space; + + TAILQ_FOREACH(space, &spaces) { + if (space->status == isc_dhcp_unknown) + continue; + if (strcmp(name, space->old) == 0) + return space; + } + return NULL; +} + +struct option * +option_lookup_name(const char *space, const char *name) +{ + struct space *universe; + struct option *option; + + universe = space_lookup(space); + if (universe == NULL) + return NULL; + TAILQ_FOREACH(option, &options) { + if (option->status == isc_dhcp_unknown) + continue; + if (universe != option->space) + continue; + if (strcmp(name, option->old) == 0) + return option; + } + return NULL; +} + +struct option * +kea_lookup_name(const char *space, const char *name) +{ + struct space *universe; + struct option *option; + + TAILQ_FOREACH(universe, &spaces) { + if (universe->status == kea_unknown) + continue; + if (strcmp(name, universe->name) == 0) + break; + } + if (universe == NULL) + return NULL; + TAILQ_FOREACH(option, &options) { + if (option->status == kea_unknown) + continue; + if (universe != option->space) + continue; + if (strcmp(name, option->name) == 0) + return option; + } + return NULL; +} + +struct option * +option_lookup_code(const char *space, unsigned code) +{ + struct space *universe; + struct option *option; + + universe = space_lookup(space); + if (universe == NULL) + return NULL; + TAILQ_FOREACH(option, &options) { + if (universe != option->space) + continue; + if (code == option->code) + return option; + } + return NULL; +} + +void +push_space(struct space *space) +{ + space->status = dynamic; + TAILQ_INSERT_TAIL(&spaces, space); +} + +void +push_option(struct option *option) +{ + assert(option->space != NULL); + option->old = option->name; + option->status = dynamic; + TAILQ_INSERT_TAIL(&options, option); +} + +void +add_option_data(struct element *src, struct element *dst) +{ + struct string *sspace; + struct element *scode; + struct element *name; + struct option *option; + size_t i; + + sspace = stringValue(mapGet(src, "space")); + scode = mapGet(src, "code"); + name = mapGet(src, "name"); + assert((scode != NULL) || (name != NULL)); + + /* We'll use the code so fill it even it should always be available */ + if (scode == NULL) { + option = kea_lookup_name(sspace->content, + stringValue(name)->content); + assert(option != NULL); + scode = createInt(option->code); + mapSet(src, scode, "code"); + } + assert(intValue(scode) != 0); + + for (i = 0; i < listSize(dst); i++) { + struct element *od; + struct element *space; + struct element *code; + + od = listGet(dst, i); + space = mapGet(od, "space"); + if (!eqString(sspace, stringValue(space))) + continue; + code = mapGet(od, "code"); + if (code == NULL) { + name = mapGet(od, "name"); + assert(name != NULL); + option = kea_lookup_name(sspace->content, + stringValue(name)->content); + assert(option != NULL); + code = createInt(option->code); + mapSet(od, code, "code"); + } + /* check if the option is already present */ + if (intValue(scode) == intValue(code)) + return; + } + listPush(dst, copy(src)); +} + +void +merge_option_data(struct element *src, struct element *dst) +{ + struct element *od; + size_t i; + + for (i = 0; i < listSize(src); i++) { + od = listGet(src, i); + add_option_data(od, dst); + } +} + +struct comments * +get_config_comments(unsigned code) +{ + static struct comments comments; + struct comment *comment = NULL; + + TAILQ_INIT(&comments); + switch (code) { + case 4: /* dynamic-bootp-lease-cutoff */ + case 5: /* dynamic-bootp-lease-length */ + case 6: /* boot-unknown-clients */ + case 7: /* dynamic-bootp */ + case 8: /* allow-bootp */ + no_bootp: + comment = createComment("/// bootp protocol is not supported"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 9: /* allow-booting */ + comment = createComment("/// allow-booting is not supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// no concrete usage known?"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Reference Kea #239"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 10: /* one-lease-per-client */ + comment = createComment("/// one-lease-per-client is not " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Reference Kea #238"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 11: /* get-lease-hostnames */ + comment = createComment("/// get-lease-hostnames is not " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Reference Kea #240"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 12: /* use-host-decl-names */ + comment = createComment("/// use-host-decl-names defaults " + "to always on"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 13: /* use-lease-addr-for-default-route */ + comment = createComment("/// use-lease-addr-for-default-route " + "is obsolete"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 14: /* min-secs */ + comment = createComment("/// min-secs is not (yet?) " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Reference Kea #223"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 20: /* always-reply-rfc1048 */ + goto no_bootp; + + case 22: /* always-broadcast */ + comment = createComment("/// always-broadcast is not " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Reference Kea #241"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 24: /* ddns-hostname */ + comment = createComment("/// ddns-hostname is not supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Please use hostname in a " + "host reservation instead"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 25: /* ddns-rev-domainname */ + comment = createComment("/// ddns-rev-domainname is an " + "obsolete (so not supported) feature"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 26: /* lease-file-name */ + comment = createComment("/// lease-file-name is an internal " + "ISC DHCP feature"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 27: /* pid-file-name */ + comment = createComment("/// pid-file-nam is an internal " + "ISC DHCP feature"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 28: /* duplicates */ + comment = createComment("/// duplicates is not supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Kea model is different (and " + "stricter)"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 29: /* declines */ + comment = createComment("/// declines is not supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Kea honors decline messages " + " and holds address for " + "decline-probation-period"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 31: /* omapi-port */ + comment = createComment("/// omapi-port is an internal " + "ISC DHCP feature"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 32: /* local-port */ + comment = createComment("/// local-port is not supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// command line -p parameter " + "should be used instead"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 33: /* limited-broadcast-address */ + comment = createComment("/// limited-broadcast-address " + "is not (yet?) supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Reference Kea #224"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 34: /* remote-port */ + comment = createComment("/// remote-port is a not portable " + "(so not supported) feature"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 35: /* local-address */ + comment = createComment("/// local-address is not supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Kea equivalent feature is " + "to specify an interface address"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 36: /* omapi-key */ + comment = createComment("/// omapi-key is an internal " + "ISC DHCP feature"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 37: /* stash-agent-options */ + comment = createComment("/// stash-agent-options is not " + "(yet?) supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Reference Kea #218"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 38: /* ddns-ttl */ + comment = createComment("/// ddns-ttl is a D2 not (yet?) " + "supported feature"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Reference Kea #225"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 40: /* client-updates */ + comment = createComment("/// ddns-ttl client-updates is " + "not supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Kea model is very different"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 41: /* update-optimization */ + comment = createComment("/// update-optimization is not " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Kea follows RFC 4702"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 42: /* ping-check */ + comment = createComment("/// ping-check is not supported"); + TAILQ_INSERT_TAIL(&comments, comment); + no_ping: + comment = createComment("/// Kea has no ping probing"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 43: /* update-static-leases */ + comment = createComment("/// update-static-leases is an " + "obsolete feature"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 44: /* log-facility */ + comment = createComment("/// log-facility is not supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Please use the " + "KEA_LOGGER_DESTINATION environment " + "variable instead"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 45: /* do-forward-updates */ + comment = createComment("/// do-forward-updates is not " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + ddns_updates: + comment = createComment("/// Kea model is equivalent but " + "different"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 46: /* ping-timeout */ + comment = createComment("/// ping-timeout is not supported"); + TAILQ_INSERT_TAIL(&comments, comment); + goto no_ping; + + case 47: /* infinite-is-reserved */ + comment = createComment("/// infinite-is-reserved is not " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Kea does not support reserved " + "leases"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 48: /* update-conflict-detection */ + comment = createComment("/// update-conflict-detection is not " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// DDNS is handled by the D2 " + "server using a dedicated config"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 49: /* leasequery */ + comment = createComment("/// leasequery is not supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Kea does not (yet) support " + "the leasequery protocol"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 50: /* adaptive-lease-time-threshold */ + comment = createComment("/// adaptive-lease-time-threshold is " + "not supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Reference Kea #226"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 51: /* do-reverse-updates */ + comment = createComment("/// do-reverse-updates is not " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + goto ddns_updates; + + case 52: /* fqdn-reply */ + comment = createComment("/// fqdn-reply is an obsolete " + "feature"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 54: /* dhcpv6-lease-file-name */ + comment = createComment("/// dhcpv6-lease-file-name " + "is an internal ISC DHCP feature"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 55: /* dhcpv6-pid-file-name */ + comment = createComment("/// dhcpv6-pid-file-name " + "is an internal ISC DHCP feature"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 56: /* limit-addrs-per-ia */ + comment = createComment("/// limit-addrs-per-ia " + "is not (yet?) supported"); + TAILQ_INSERT_TAIL(&comments, comment); + limit_resources: + comment = createComment("/// Reference Kea #227"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 57: /* limit-prefs-per-ia */ + comment = createComment("/// limit-prefs-per-ia" + "is not (yet?) supported"); + TAILQ_INSERT_TAIL(&comments, comment); + goto limit_resources; + + case 58: /* delayed-ack */ + case 59: /* max-ack-delay */ + comment = createComment("/// delayed ack no supported"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 78: /* dhcp-cache-threshold */ + comment = createComment("/// dhcp-cache-threshold " + "is not (yet?) supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Reference Kea #228"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 79: /* dont-use-fsync */ + comment = createComment("/// dont-use-fsync is an internal " + "ISC DHCP feature"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 80: /* ddns-local-address4 */ + comment = createComment("/// ddns-local-address4 is not " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + d2_ip_address: + comment = createComment("/// Kea D2 equivalent config is " + "ip-address"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 81: /* ddns-local-address6 */ + comment = createComment("/// ddns-local-address6 is not " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + goto d2_ip_address; + + case 83: /* log-threshold-low */ + comment = createComment("/// log-threshold-low is not (yet?) " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + log_threshold: + comment = createComment("/// Reference Kea #222"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 84: /* log-threshold-high */ + comment = createComment("/// log-threshold-high is not (yet?) " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + goto log_threshold; + + case 86: /* server-id-check */ + comment = createComment("/// server-id-check is not (yet?) " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Reference Kea #242"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + + case 87: /* prefix-length-mode */ + comment = createComment("/// prefix-length-mode is not " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Kea model is different (and " + "simpler?)"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + case 88: /* dhcpv6-set-tee-times */ + comment = createComment("/// dhcpv6-set-tee-times is a " + "transitional (so not supported) " + "feature"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// T1 and T2 are .5 and .8 times " + "preferred-lifetime"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + case 89: /* abandon-lease-time */ + comment = createComment("/// abandon-lease-time is not " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Kea support equivalent (and " + "richer) expired-lease-processing " + "and decline-probation-period"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + case 90: /* use-eui-64 */ + comment = createComment("/// EUI-64 is not (yet) supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Reference Kea #265"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + case 96: /* release-on-roam */ + comment = createComment("/// release-on-roam is not (yet) " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Reference Kea #266"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + case 97: /* local-address6 */ + comment = createComment("/// local-address6 is not supported"); + TAILQ_INSERT_TAIL(&comments, comment); + comment = createComment("/// Kea equivalent feature is " + "to specify an interface address"); + TAILQ_INSERT_TAIL(&comments, comment); + break; + case 99: /* ping-cltt-secs */ + comment = createComment("/// ping-cltt-secs is not supported"); + TAILQ_INSERT_TAIL(&comments, comment); + goto no_ping; + case 100: /* ping-timeout-ms */ + comment = createComment("/// ping-timeout-ms is not " + "supported"); + TAILQ_INSERT_TAIL(&comments, comment); + goto no_ping; + } + return &comments; +} + +const char * +display_status(enum option_status status) +{ + switch (status) { + case kea_unknown: + case special: + return "known (unknown)"; + case isc_dhcp_unknown: + return "unknown (known)"; + case known: + return "known (known)"; + case dynamic: + return "dynamic (dynamic)"; + default: + return "??? (" "???" ")"; + } +} diff --git a/keama/parse.c b/keama/parse.c new file mode 100644 index 00000000..3596d5d5 --- /dev/null +++ b/keama/parse.c @@ -0,0 +1,6140 @@ +/* + * Copyright (c) 2017, 2018 by Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "keama.h" + +#include <sys/types.h> +#include <arpa/inet.h> +#include <ctype.h> +#include <netdb.h> +#include <stdlib.h> +#include <string.h> + +static void config_min_valid_lifetime(struct element *, struct parse *); +static void config_def_valid_lifetime(struct element *, struct parse *); +static void config_max_valid_lifetime(struct element *, struct parse *); +static void config_file(struct element *, struct parse *); +static void config_sname(struct element *, struct parse *); +static void config_next_server(struct element *, struct parse *); +static void config_vendor_option_space(struct element *, struct parse *); +static void config_site_option_space(struct element *, struct parse *); +static struct element *default_qualifying_suffix(void); +static void config_qualifying_suffix(struct element *, struct parse *); +static void config_enable_updates(struct element *, struct parse *); +static void config_ddns_update_style(struct element *, struct parse *); +static void config_preferred_lifetime(struct element *, struct parse *); +static void config_match_client_id(struct element *, struct parse *); +static void config_echo_client_id(struct element *, struct parse *); + +/* +static uint32_t getULong(const unsigned char *buf); +static int32_t getLong(const unsigned char *buf); +static uint32_t getUShort(const unsigned char *buf); +static int32_t getShort(const unsigned char *buf); +static uint32_t getUChar(const unsigned char *obuf); +*/ +static void putULong(unsigned char *obuf, uint32_t val); +static void putLong(unsigned char *obuf, int32_t val); +static void putUShort(unsigned char *obuf, uint32_t val); +static void putShort(unsigned char *obuf, int32_t val); +/* +static void putUChar(unsigned char *obuf, uint32_t val); +*/ + +/* +static isc_boolean_t is_compound_expression(struct element *); +*/ +static enum expression_context op_context(enum expr_op); +static int op_val(enum expr_op); +static int op_precedence(enum expr_op, enum expr_op); +static enum expression_context expression_context(struct element *); +static enum expr_op expression(struct element *); + +/* Skip to the semicolon ending the current statement. If we encounter + braces, the matching closing brace terminates the statement. +*/ +void +skip_to_semi(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(struct parse *cfile, int brace_count) +{ + enum dhcp_token token; + const char *val; + + 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); +} + +void +parse_semi(struct parse *cfile) +{ + enum dhcp_token token; + const char *val; + + token = next_token(&val, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "semicolon expected."); +} + +/* string-parameter :== STRING SEMI */ + +void +parse_string(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_error(cfile, "expecting a string"); + s = (char *)malloc(len + 1); + parse_error(cfile, "no memory for string %s.", val); + memcpy(s, val, len + 1); + + parse_semi(cfile); + if (sptr) + *sptr = s; + else + free(s); + if (lptr) + *lptr = len; +} + +/* + * hostname :== IDENTIFIER + * | IDENTIFIER DOT + * | hostname DOT IDENTIFIER + */ + +struct string * +parse_host_name(struct parse *cfile) +{ + const char *val; + enum dhcp_token token; + struct string *s = NULL; + + /* Read a dotted hostname... */ + do { + /* Read a token, which should be an identifier. */ + token = peek_token(&val, NULL, cfile); + if (!is_identifier(token) && token != NUMBER) + break; + skip_token(&val, NULL, cfile); + + /* Store this identifier... */ + if (s == NULL) + s = makeString(-1, val); + else + appendString(s, val); + /* Look for a dot; if it's there, keep going, otherwise + we're done. */ + token = peek_token(&val, NULL, cfile); + if (token == DOT) { + token = next_token(&val, NULL, cfile); + appendString(s, val); + } + } while (token == DOT); + + 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. + + Note that RFC1123 permits hostnames to consist of all digits, + making it difficult to quickly disambiguate them from ip addresses. +*/ + +struct string * +parse_ip_addr_or_hostname(struct parse *cfile, isc_boolean_t check_multi) +{ + const char *val; + enum dhcp_token token; + unsigned char addr[4]; + unsigned len = sizeof(addr); + isc_boolean_t ipaddr = ISC_FALSE; + struct string *bin = NULL; + + token = peek_token(&val, NULL, 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 = ISC_TRUE; + restore_parse_state(cfile); + + if (ipaddr) + bin = parse_numeric_aggregate(cfile, addr, &len, + DOT, 10, 8); + } + + if ((bin == NULL) && (is_identifier(token) || token == NUMBER)) { + struct string *name; + struct hostent *h; + + name = parse_host_name(cfile); + if (name == NULL) + return NULL; + + if (resolve == fatal) + parse_error(cfile, "expected IPv4 address. got " + "hostname %s", name->content); + else if (resolve == pass) + return name; + + /* from do_host_lookup */ + h = gethostbyname(name->content); + if ((h == NULL) || (h->h_addr_list[0] == NULL)) + parse_error(cfile, "%s: host unknown.", name->content); + if (check_multi && h->h_addr_list[1]) { + struct comment *comment; + char msg[128]; + + snprintf(msg, sizeof(msg), + "/// %s resolves into multiple addresses", + name->content); + comment = createComment(msg); + TAILQ_INSERT_TAIL(&cfile->comments, comment); + } + bin = makeString(4, h->h_addr_list[0]); + } + + if (bin == NULL) { + if (token != RBRACE && token != LBRACE) + token = next_token(&val, NULL, cfile); + parse_error(cfile, "%s (%d): expecting IP address or hostname", + val, token); + } + + return makeStringExt(bin->length, bin->content, 'I'); +} + +/* + * ip-address :== NUMBER DOT NUMBER DOT NUMBER DOT NUMBER + */ + +struct string * +parse_ip_addr(struct parse *cfile) +{ + unsigned char addr[4]; + unsigned len = sizeof(addr); + + return parse_numeric_aggregate(cfile, addr, &len, DOT, 10, 8); +} + +/* + * Return true if every character in the string is hexadecimal. + */ +static isc_boolean_t +is_hex_string(const char *s) +{ + while (*s != '\0') { + if (!isxdigit((int)*s)) { + return ISC_FALSE; + } + s++; + } + return ISC_TRUE; +} + +/* + * 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. + */ + +struct string * +parse_ip6_addr(struct parse *cfile) +{ + enum dhcp_token token; + const char *val; + char addr[16]; + 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 == TOKEN_ADD) || + (token == DOT) || + (token == COLON)) { + + next_raw_token(&val, NULL, cfile); + val_len = strlen(val); + if ((v6_len + val_len) >= sizeof(v6)) + parse_error(cfile, "Invalid IPv6 address."); + 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) <= 0) + parse_error(cfile, "Invalid IPv6 address."); + return makeString(16, addr); +} + +/* + * Same as parse_ip6_addr() above, but returns the value as a text + * rather than in an address binary structure. + */ +struct string * +parse_ip6_addr_txt(struct parse *cfile) +{ + const struct string *bin; + + bin = parse_ip6_addr(cfile); + return makeStringExt(bin->length, bin->content, '6'); +} + +/* + * 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. + */ + +struct element * +parse_hardware_param(struct parse *cfile) +{ + const char *val; + enum dhcp_token token; + isc_boolean_t ether = ISC_FALSE; + unsigned hlen; + struct string *t, *r; + struct element *hw; + + token = next_token(&val, NULL, cfile); + if (token == ETHERNET) + ether = ISC_TRUE; + else { + r = makeString(-1, val); + appendString(r, " "); + } + + /* 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) + parse_error(cfile, "empty hardware address"); + t = parse_numeric_aggregate(cfile, NULL, &hlen, COLON, 16, 8); + if (t == NULL) + parse_error(cfile, "can't get hardware address"); + if (hlen > HARDWARE_ADDR_LEN) + parse_error(cfile, "hardware address too long"); + token = next_token(&val, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "expecting semicolon."); + if (ether) + r = makeStringExt(hlen, t->content, 'H'); + else + concatString(r, makeStringExt(hlen,t->content, 'H')); + hw = createString(r); + TAILQ_CONCAT(&hw->comments, &cfile->comments); + if (!ether || (hlen != 6)) { + hw->skip = ISC_TRUE; + cfile->issue_counter++; + } + return hw; +} + +/* 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 string structure on success. + */ + +struct string * +parse_numeric_aggregate(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; + unsigned count = 0; + struct string *r = NULL, *t = NULL; + + if (!bufp && *max) { + bufp = (unsigned char *)malloc(*max * size / 8); + if (!bufp) + parse_error(cfile, "no space for numeric aggregate"); + } + s = bufp; + if (!s) { + r = allocString(); + t = makeString(size / 8, "bigger than needed"); + } + + do { + if (count) { + token = peek_token(&val, NULL, cfile); + if (token != separator) { + if (!*max) + break; + if (token != RBRACE && token != LBRACE) + token = next_token(&val, NULL, cfile); + parse_error(cfile, "too few numbers."); + } + skip_token(&val, NULL, cfile); + } + token = next_token(&val, NULL, cfile); + + if (token == END_OF_FILE) + parse_error(cfile, "unexpected end of file"); + + /* Allow NUMBER_OR_NAME if base is 16. */ + if (token != NUMBER && + (base != 16 || token != NUMBER_OR_NAME)) + parse_error(cfile, "expecting numeric value."); + /* 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 { + convert_num(cfile, (unsigned char *)t->content, + val, base, size); + concatString(r, t); + } + } while (++count != *max); + + *max = count; + if (bufp) + r = makeString(count * size / 8, (char *)bufp); + + return r; +} + +void +convert_num(struct parse *cfile, unsigned char *buf, const char *str, + int base, unsigned size) +{ + const unsigned char *ptr = (const unsigned char *)str; + int negative = 0; + uint32_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_error(cfile, "Bogus number: %s.", str); + if (tval >= base) + parse_error(cfile, + "Bogus number %s: digit %d not in base %d", + str, tval, base); + 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_error(cfile, + "%s%lo exceeds max (%d) for precision.", + negative ? "-" : "", + (unsigned long)val, max); + break; + case 16: + parse_error(cfile, + "%s%lx exceeds max (%d) for precision.", + negative ? "-" : "", + (unsigned long)val, max); + break; + default: + parse_error(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_error(cfile, + "Unexpected integer size: %d\n", size); + break; + } + } else { + switch (size) { + case 8: + *buf = (uint8_t)val; + break; + case 16: + putUShort (buf, (uint16_t)val); + break; + case 32: + putULong (buf, val); + break; + default: + parse_error(cfile, + "Unexpected integer size: %d\n", size); + } + } +} + +/* + * option-name :== IDENTIFIER | + IDENTIFIER . IDENTIFIER + */ + +struct option * +parse_option_name(struct parse *cfile, + isc_boolean_t allocate, + isc_boolean_t *known) +{ + const char *val; + enum dhcp_token token; + const char *uname; + struct space *space; + struct option *option = NULL; + unsigned code; + + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, + "expecting identifier after option keyword."); + + uname = strdup(val); + if (!uname) + parse_error(cfile, "no memory for uname information."); + token = peek_token(&val, NULL, cfile); + if (token == DOT) { + /* Go ahead and take the DOT token... */ + skip_token(&val, NULL, cfile); + + /* The next token should be an identifier... */ + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, "expecting identifier after '.'"); + + /* Look up the option name hash table for the specified + uname. */ + space = space_lookup(uname); + if (space == NULL) + parse_error(cfile, "no option space named %s.", uname); + } else { + /* Use the default hash table, which contains all the + standard dhcp option names. */ + val = uname; + space = space_lookup("dhcp"); + } + + option = option_lookup_name(space->old, val); + + if (option) { + if (known && (option->status != isc_dhcp_unknown)) + *known = ISC_TRUE; + } else if (space == space_lookup("server")) + parse_error(cfile, "unknown server option %s.", val); + + /* 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) + parse_error(cfile, "Option code 0 is illegal " + "in the %s space.", space->old); + if ((local_family == AF_INET) && (code == 255)) + parse_error(cfile, "Option code 255 is illegal " + "in the %s space.", space->old); + + /* 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 = ISC_TRUE; + option = option_lookup_code(space->old, code); + + /* If we did not find an option of that code, + * manufacture an unknown-xxx option definition. + */ + if (option == NULL) { + option = (struct option *)malloc(sizeof(*option)); + /* DHCP code does not check allocation failure? */ + memset(option, 0, sizeof(*option)); + option->name = strdup(val); + option->space = space; + option->code = code; + /* Mark format as undefined */ + option->format = "u"; + push_option(option); + } else { + struct comment *comment; + char msg[256]; + + snprintf(msg, sizeof(msg), + "/// option %s.%s redefinition", + space->name, val); + comment = createComment(msg); + TAILQ_INSERT_TAIL(&cfile->comments, comment); + } + /* 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 = (struct option *)malloc(sizeof(*option)); + /* DHCP code does not check allocation failure? */ + memset(option, 0, sizeof(*option)); + option->name = strdup(val); + option->space = space; + /* Mark format as undefined */ + option->format = "u"; + push_option(option); + } else + parse_error(cfile, "no option named %s in space %s", + val, space->old); + + return option; +} + +/* IDENTIFIER[WIDTHS] SEMI + * WIDTHS ~= LENGTH WIDTH NUMBER + * CODE WIDTH NUMBER + */ + +void +parse_option_space_decl(struct parse *cfile) +{ + int token; + const char *val; + struct element *nu; + struct element *p; + struct space *universe; + int tsize = 1, lsize = 1; + + skip_token(&val, NULL, cfile); /* Discard the SPACE token, + which was checked by the + caller. */ + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, "expecting identifier."); + nu = createMap(); + nu->skip = ISC_TRUE; + + /* Expect it will be usable in Kea */ + universe = (struct space *)malloc(sizeof(*universe)); + if (universe == NULL) + parse_error(cfile, "No memory for new option space."); + memset(universe, 0, sizeof(*universe)); + universe->old = strdup(val); + universe->name = universe->old; + push_space(universe); + + do { + token = next_token(&val, NULL, cfile); + switch(token) { + case SEMI: + break; + + case CODE: + if (mapSize(nu) == 0) { + cfile->issue_counter++; + mapSet(nu, + createString( + makeString(-1, universe->old)), + "name"); + } + token = next_token(&val, NULL, cfile); + if (token != WIDTH) + parse_error(cfile, "expecting width token."); + + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, + "expecting number 1, 2, 4."); + + tsize = atoi(val); + p = NULL; + if ((local_family == AF_INET) && (tsize != 1)) { + struct comment *comment; + + comment = createComment("/// Only code width " + "1 is supported"); + p = createInt(tsize); + TAILQ_INSERT_TAIL(&p->comments, comment); + } else if ((local_family == AF_INET6) && + (tsize != 2)) { + struct comment *comment; + + comment = createComment("/// Only code width " + "2 is supported"); + p = createInt(tsize); + TAILQ_INSERT_TAIL(&p->comments, comment); + } + if (p != NULL) + mapSet(nu, p, "code-width"); + break; + + case LENGTH: + if (mapSize(nu) == 0) { + cfile->issue_counter++; + mapSet(nu, + createString( + makeString(-1, universe->old)), + "name"); + } + token = next_token(&val, NULL, cfile); + if (token != WIDTH) + parse_error(cfile, "expecting width token."); + + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "expecting number 1 or 2."); + + lsize = atoi(val); + p = NULL; + if ((local_family == AF_INET) && (lsize != 1)) { + struct comment *comment; + + comment = createComment("/// Only length " + "width 1 is " + "supported"); + p = createInt(lsize); + TAILQ_INSERT_TAIL(&p->comments, comment); + } else if ((local_family == AF_INET6) && + (lsize != 2)) { + struct comment *comment; + + comment = createComment("/// Only length " + "width 2 is " + "supported"); + p = createInt(lsize); + TAILQ_INSERT_TAIL(&p->comments, comment); + } + if (p != NULL) + mapSet(nu, p, "length-width"); + break; + + case HASH: + token = next_token(&val, NULL, cfile); + if (token != SIZE) + parse_error(cfile, "expecting size token."); + + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, + "expecting a 10base number"); + break; + + default: + parse_error(cfile, "Unexpected token."); + } + } while (token != SEMI); + + if (mapSize(nu) > 1) + mapSet(cfile->stack[1], nu, "option-space"); +} + +/* 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 */ + +void +parse_option_code_definition(struct parse *cfile, struct option *option) +{ + const char *val; + enum dhcp_token token; + struct element *def; + unsigned code; + unsigned arrayp = 0; + isc_boolean_t is_array = ISC_FALSE; + int recordp = 0; + isc_boolean_t no_more_in_record = ISC_FALSE; + char *type; + isc_boolean_t is_signed; + isc_boolean_t has_encapsulation = ISC_FALSE; + isc_boolean_t not_supported = ISC_FALSE; + struct string *encapsulated; + struct string *datatype; + struct string *saved; + struct string *format; + struct element *optdef; + + if (option->space->status == special) { + parse_vendor_code_definition(cfile, option); + return; + } + + /* Put the option in the definition */ + def = createMap(); + mapSet(def, + createString(makeString(-1, option->space->name)), + "space"); + mapSet(def, createString(makeString(-1, option->name)), "name"); + TAILQ_CONCAT(&def->comments, &cfile->comments); + + /* Parse the option code. */ + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "expecting option code number."); + TAILQ_CONCAT(&def->comments, &cfile->comments); + code = atoi(val); + mapSet(def, createInt(code), "code"); + + /* We have the code so we can get the real option now */ + if (option->code == 0) { + struct option *from_code = NULL; + + option->code = code; + from_code = option_lookup_code(option->space->old, code); + if (from_code != NULL) { + option->status = from_code->status; + option->format = from_code->format; + } + } + + /* Redefinitions are not allowed */ + if ((option->status != dynamic) || + (strcmp(option->format, "u") != 0)) { + struct comment *comment; + + comment = createComment("/// Kea does not allow redefinition " + "of options"); + TAILQ_INSERT_TAIL(&def->comments, comment); + def->skip = ISC_TRUE; + cfile->issue_counter++; + /* Avoid option-data per name */ + option->status = kea_unknown; + } + + token = next_token(&val, NULL, cfile); + if (token != EQUAL) + parse_error(cfile, "expecting \"=\""); + saved = allocString(); + + /* See if this is an array. */ + token = next_token(&val, NULL, cfile); + if (token == ARRAY) { + token = next_token(&val, NULL, cfile); + if (token != OF) + parse_error(cfile, "expecting \"of\"."); + arrayp = 1; + token = next_token(&val, NULL, cfile); + appendString(saved, "array of"); + } + + if (token == LBRACE) { + recordp = 1; + token = next_token(&val, NULL, cfile); + if (arrayp) + appendString(saved, " "); + appendString(saved, "{"); + } + + /* At this point we're expecting a data type. */ + datatype = allocString(); + /* We record the format essentially for the binary one */ + format = allocString(); + next_type: + if (saved->length > 0) + appendString(saved, " "); + type = NULL; + if (has_encapsulation) + parse_error(cfile, + "encapsulate must always be the last item."); + + switch (token) { + case ARRAY: + if (arrayp) + parse_error(cfile, "no nested arrays."); + if (recordp) { + struct comment *comment; + + comment = createComment("/// unsupported array " + "inside a record"); + TAILQ_INSERT_TAIL(&def->comments, comment); + not_supported = ISC_TRUE; + cfile->issue_counter++; + } + token = next_token(&val, NULL, cfile); + if (token != OF) + parse_error(cfile, "expecting \"of\"."); + arrayp = recordp + 1; + token = next_token(&val, NULL, cfile); + if ((recordp) && (token == LBRACE)) + parse_error(cfile, + "only uniform array inside record."); + appendString(saved, "array of"); + if (token == LBRACE) { + struct comment *comment; + + comment = createComment("/// unsupported record " + "inside an array"); + TAILQ_INSERT_TAIL(&def->comments, comment); + not_supported = ISC_TRUE; + cfile->issue_counter++; + appendString(saved, " {"); + } + goto next_type; + case BOOLEAN: + type = "boolean"; + appendString(format, "f"); + break; + case INTEGER: + is_signed = ISC_TRUE; + parse_integer: + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "expecting number."); + switch (atoi(val)) { + case 8: + if (is_signed) { + type = "int8"; + appendString(format, "b"); + } else { + type = "uint8"; + appendString(format, "B"); + } + break; + case 16: + if (is_signed) { + type = "int16"; + appendString(format, "s"); + } else { + type = "uint16"; + appendString(format, "S"); + } + break; + case 32: + if (is_signed) { + type = "int32"; + appendString(format, "l"); + } else { + type = "uint32"; + appendString(format, "L"); + } + break; + default: + parse_error(cfile, + "%s bit precision is not supported.", val); + } + break; + case SIGNED: + is_signed = ISC_TRUE; + parse_signed: + token = next_token(&val, NULL, cfile); + if (token != INTEGER) + parse_error(cfile, "expecting \"integer\" keyword."); + goto parse_integer; + case UNSIGNED: + is_signed = ISC_FALSE; + goto parse_signed; + + case IP_ADDRESS: + type = "ipv4-address"; + appendString(format, "I"); + break; + case IP6_ADDRESS: + type = "ipv6-address"; + appendString(format, "6"); + break; + case DOMAIN_NAME: + type = "fqdn"; + appendString(format, "d"); + goto no_arrays; + case DOMAIN_LIST: + /* Consume optional compression indicator. */ + token = peek_token(&val, NULL, cfile); + appendString(format, "D"); + type = "fqdn"; + is_array = ISC_TRUE; + if (token == COMPRESSED) { + if (local_family == AF_INET6) + parse_error(cfile, "domain list in DHCPv6 " + "MUST NOT be compressed"); + skip_token(&val, NULL, cfile); + appendString(format, "c"); + appendString(saved, "compressed "); + } + appendString(saved, "list of "); + goto no_arrays; + case TEXT: + type = "string"; + appendString(format, "t"); + no_arrays: + if (arrayp) + parse_error(cfile, "arrays of text strings not %s", + "yet supported."); + no_more_in_record = ISC_TRUE; + break; + case STRING_TOKEN: + /* can be binary too */ + type = "string"; + appendString(format, "x"); + goto no_arrays; + + case ENCAPSULATE: + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, + "expecting option space identifier"); + encapsulated = makeString(-1, val); + has_encapsulation = ISC_TRUE; + appendString(format, "E"); + appendString(format, val); + appendString(format, "."); + appendString(saved, "encapsulate "); + appendString(saved, val); + if (datatype->length == 0) + type = "empty"; + break; + + case ZEROLEN: + type = "empty"; + appendString(format, "Z"); + if (arrayp) + parse_error(cfile, "array incompatible with zerolen."); + no_more_in_record = ISC_TRUE; + break; + + default: + parse_error(cfile, "unknown data type %s", val); + } + appendString(saved, type); + appendString(datatype, type); + + if (recordp) { + token = next_token(&val, NULL, cfile); + if (arrayp > recordp) { + is_array = ISC_TRUE; + arrayp = 0; + appendString(format, "a"); + } + if (token == COMMA) { + if (no_more_in_record) { + char last; + + last = format->content[format->length - 1]; + parse_error(cfile, + "%s must be at end of record.", + last == 't' ? "text" : "string"); + } + token = next_token(&val, NULL, cfile); + appendString(saved, ","); + appendString(datatype, ", "); + goto next_type; + } + if (token != RBRACE) + parse_error(cfile, "expecting right brace."); + appendString(saved, "}"); + } + parse_semi(cfile); + if (has_encapsulation && arrayp) + parse_error(cfile, + "Arrays of encapsulations don't make sense."); + if (arrayp) + appendString(format, (arrayp > recordp) ? "a" : "A"); + if (is_array || arrayp) { + struct element *array_def; + + array_def = createBool(ISC_TRUE); + if (not_supported) + array_def->skip = ISC_TRUE; + mapSet(def, array_def, "array"); + } + + if (not_supported) { + struct element *type_def; + struct element *saved_def; + struct comment *comment; + + saved_def = createString(saved); + saved_def->skip = ISC_TRUE; + mapSet(def, saved_def, "definition"); + type_def = createString(makeString(-1, "binary")); + comment = createComment("/// Option definition is not " + "compatible with Kea"); + TAILQ_INSERT_TAIL(&type_def->comments, comment); + comment = createComment("/// Fallback to full binary"); + TAILQ_INSERT_TAIL(&type_def->comments, comment); + mapSet(def, type_def, "type"); + } else if (recordp) { + mapSet(def, createString(datatype), "record-types"); + mapSet(def, createString(makeString(-1, "record")), "type"); + } else + mapSet(def, createString(datatype), "type"); + + /* Force full binary when the format is not supported by Kea */ + if (not_supported) + appendString(format, "Y"); + option->format = format->content; + + if (has_encapsulation) + mapSet(def, createString(encapsulated), "encapsulate"); + + optdef = mapGet(cfile->stack[1], "option-def"); + if (optdef == NULL) { + optdef = createList(); + mapSet(cfile->stack[1], optdef, "option-def"); + } + listPush(optdef, def); +} + +/* + * Specialized version of parse_option_code_definition for vendor options + * DHCPv4 vivso (code 125, space vendor) and DHCPv6 vendor-opts (17, + * space vsio). The syntax is a subnet: + * vcd :== NUMBER EQUALS ENCAPSULATE identifier SEMI + */ + +void +parse_vendor_code_definition(struct parse *cfile, struct option *option) +{ + const char *val; + enum dhcp_token token; + struct string *id; + struct string *space; + struct space *universe; + struct string *name; + unsigned code; + struct element *vendor; + + space = makeString(-1, "vendor-"); + + /* Parse the option code / vendor id. */ + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "expecting option code number."); + id = makeString(-1, val); + appendString(space, val); + + + token = next_token(&val, NULL, cfile); + if (token != EQUAL) + parse_error(cfile, "expecting \"=\""); + token = next_token(&val, NULL, cfile); + if (token != ENCAPSULATE) + parse_error(cfile, "expecting encapsulate"); + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, "expecting option space identifier"); + universe = space_lookup(val); + if (universe == NULL) + parse_error(cfile, "unknown option space %s", val); + /* Map the universe to vendor-<code> */ + universe->name = space->content; + /* Create the vendor option */ + vendor = createMap(); + if (local_family == AF_INET) { + space = makeString(-1, "dhcp4"); + name = makeString(-1, "vivso-suboptions"); + code = DHO_VIVSO_SUBOPTIONS; + } else { + space =makeString(-1, "dhcp6"); + name = makeString(-1, "vendor-opts"); + code = D6O_VENDOR_OPTS; + } + mapSet(vendor, createString(space), "space"); + mapSet(vendor, createString(name), "name"); + mapSet(vendor, createInt(code), "code"); + mapSet(vendor, createString(id), "data"); + universe->vendor = vendor; + parse_semi(cfile); +} + +struct string * +convert_format(const char *fmt, isc_boolean_t *is_array, + isc_boolean_t *encapsulate) +{ + struct string *datatype; + const char *g; + + if ((strchr(fmt, 'A') != NULL) || (strchr(fmt, 'a') != NULL) || + (strchr(fmt, 'D') != NULL)) + *is_array = ISC_TRUE; + + if (strchr(fmt, 'E') != NULL) + *encapsulate = ISC_TRUE; + + if ((strchr(fmt, 'Y') != NULL) || (strchr(fmt, 'A') != NULL) || + (strchr(fmt, 'E') != NULL) || (strchr(fmt, 'o') != NULL) || + (*fmt == 'X') || (*fmt == 'u')) + return makeString(-1, "binary"); + + datatype = allocString(); + + do { + if (datatype->length != 0) + appendString(datatype, ", "); + + switch (*fmt) { + case 'U': + case 't': + case 'x': + appendString(datatype, "string"); + break; + case 'I': + appendString(datatype, "ipv4-address"); + break; + case '6': + appendString(datatype, "ipv6-address"); + break; + case 'l': + appendString(datatype, "int32"); + break; + case 'L': + case 'T': + appendString(datatype, "uint32"); + break; + case 's': + appendString(datatype, "int16"); + break; + case 'S': + appendString(datatype, "uint16"); + break; + case 'b': + appendString(datatype, "int8"); + break; + case 'B': + appendString(datatype, "uint8"); + break; + case 'f': + appendString(datatype, "boolean"); + break; + case 'E': + case 'N': + g = strchr(fmt, '.'); + if (g == NULL) + return makeString(-1, "bad?!"); + if (*fmt == 'N') + return makeString(-1, "unsupported?!"); + fmt = g; + break; + case 'X': + appendString(datatype, "binary"); + break; + case 'd': + case 'D': + appendString(datatype, "fqdn"); + break; + case 'Z': + appendString(datatype, "empty"); + break; + case 'A': + case 'a': + case 'c': + /* ignored */ + break; + default: + return makeString(-1, "unknown?!"); + } + fmt++; + } while (*fmt != '\0'); + + return datatype; +} + +/* + * base64 :== NUMBER_OR_STRING + */ + +struct string * +parse_base64(struct parse *cfile) +{ + const char *val; + unsigned i; + 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 *t; + struct string *r; + isc_boolean_t valid_base64; + + r = allocString(); + + /* 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 = makeString(l, val); + concatString(r, t); + (void)peek_token(&val, NULL, cfile); + valid_base64 = ISC_TRUE; + 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 = ISC_FALSE; + break; /* no need to continue for loop */ + } + } + } while (valid_base64); + + return r; +} + +/* + * colon-separated-hex-list :== NUMBER | + * NUMBER COLON colon-separated-hex-list + */ + +struct string * +parse_cshl(struct parse *cfile) +{ + uint8_t ibuf; + char tbuf[4]; + isc_boolean_t first = ISC_TRUE; + struct string *data; + enum dhcp_token token; + const char *val; + + data = allocString(); + + for (;;) { + token = next_token(&val, NULL, cfile); + if (token != NUMBER && token != NUMBER_OR_NAME) + parse_error(cfile, "expecting hexadecimal number."); + convert_num(cfile, &ibuf, val, 16, 8); + if (first) + snprintf(tbuf, sizeof(tbuf), "%02hhx", ibuf); + else + snprintf(tbuf, sizeof(tbuf), ":%02hhx", ibuf); + first = ISC_FALSE; + appendString(data, tbuf); + + token = peek_token(&val, NULL, cfile); + if (token != COLON) + break; + skip_token(&val, NULL, cfile); + } + + return data; +} + +/* Same but without colons in output */ + +struct string * +parse_hexa(struct parse *cfile) +{ + uint8_t ibuf; + char tbuf[4]; + struct string *data; + enum dhcp_token token; + const char *val; + + data = allocString(); + + for (;;) { + token = next_token(&val, NULL, cfile); + if (token != NUMBER && token != NUMBER_OR_NAME) + parse_error(cfile, "expecting hexadecimal number."); + convert_num(cfile, &ibuf, val, 16, 8); + snprintf(tbuf, sizeof(tbuf), "%02hhx", ibuf); + appendString(data, tbuf); + + token = peek_token(&val, NULL, cfile); + if (token != COLON) + break; + skip_token(&val, NULL, cfile); + } + + return data; +} + +/* + * 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 + */ + +isc_boolean_t +parse_executable_statements(struct element *statements, + struct parse *cfile, isc_boolean_t *lose, + enum expression_context case_context) +{ + if (statements->type != ELEMENT_LIST) + parse_error(cfile, "statements is not a list?"); + for (;;) { + struct element *statement; + + statement = createMap(); + TAILQ_CONCAT(&statement->comments, &cfile->comments); + if (!parse_executable_statement(statement, cfile, lose, + case_context, ISC_FALSE)) + break; + TAILQ_CONCAT(&statement->comments, &cfile->comments); + listPush(statements, statement); + } + if (!*lose) + return ISC_TRUE; + + return ISC_FALSE; +} + +isc_boolean_t +parse_executable_statement(struct element *result, + struct parse *cfile, isc_boolean_t *lose, + enum expression_context case_context, + isc_boolean_t direct) +{ + unsigned len; + enum dhcp_token token; + const char *val; + struct element *st; + struct option *option; + struct element *var; + struct element *pri; + struct element *expr; + isc_boolean_t known; + int flag; + int i; + struct element *zone; + struct string *s; + static isc_boolean_t log_warning = ISC_TRUE; + + token = peek_token(&val, NULL, cfile); + switch (token) { + case DB_TIME_FORMAT: + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token == DEFAULT) + s = makeString(-1, val); + else if (token == LOCAL) + s = makeString(-1, val); + else + parse_error(cfile, "Expecting 'local' or 'default'."); + + token = next_token(&val, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "Expecting a semicolon."); + st = createString(s); + st->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, st, "db-time-format"); + + /* We're done here. */ + return ISC_TRUE; + + case IF: + skip_token(&val, NULL, cfile); + return parse_if_statement(result, cfile, lose); + + case TOKEN_ADD: + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token != STRING) + parse_error(cfile, "expecting class name."); + s = makeString(-1, val); + parse_semi(cfile); + st = createString(s); + st->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, st, "add-class"); + break; + + case BREAK: + skip_token(&val, NULL, cfile); + s = makeString(-1, val); + parse_semi(cfile); + st = createNull(); + st->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, st, "break"); + break; + + case SEND: + skip_token(&val, NULL, cfile); + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_FALSE, &known); + if (option == NULL) { + *lose = ISC_TRUE; + return ISC_FALSE; + } + return parse_option_statement(result, cfile, option, + send_option_statement); + + case SUPERSEDE: + case OPTION: + skip_token(&val, NULL, cfile); + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_FALSE, &known); + if (option == NULL) { + *lose = ISC_TRUE; + return ISC_FALSE; + } + return parse_option_statement(result, cfile, option, + supersede_option_statement); + + case ALLOW: + flag = 1; + goto pad; + case DENY: + flag = 0; + goto pad; + case IGNORE: + flag = 2; + pad: + skip_token(&val, NULL, cfile); + st = parse_allow_deny(cfile, flag); + mapSet(result, st, "config"); + break; + + case DEFAULT: + skip_token(&val, NULL, cfile); + token = peek_token(&val, NULL, cfile); + if (token == COLON) + goto switch_default; + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_FALSE, &known); + if (option == NULL) { + *lose = ISC_TRUE; + return ISC_FALSE; + } + return parse_option_statement(result, cfile, option, + default_option_statement); + case PREPEND: + skip_token(&val, NULL, cfile); + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_FALSE, &known); + if (option == NULL) { + *lose = ISC_TRUE; + return ISC_FALSE; + } + return parse_option_statement(result, cfile, option, + prepend_option_statement); + case APPEND: + skip_token(&val, NULL, cfile); + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_FALSE, &known); + if (option == NULL) { + *lose = ISC_TRUE; + return ISC_FALSE; + } + return parse_option_statement(result, cfile, option, + append_option_statement); + + case ON: + skip_token(&val, NULL, cfile); + return parse_on_statement(result, cfile, lose); + + case SWITCH: + skip_token(&val, NULL, cfile); + return parse_switch_statement(result, cfile, lose); + + case CASE: + skip_token(&val, NULL, cfile); + if (case_context == context_any) + parse_error(cfile, + "case statement in inappropriate scope."); + return parse_case_statement(result, + cfile, lose, case_context); + + switch_default: + skip_token(&val, NULL, cfile); + if (case_context == context_any) + parse_error(cfile, "switch default statement in %s", + "inappropriate scope."); + s = makeString(-1, "default"); + st = createNull(); + st->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, st, "default"); + return ISC_TRUE; + + case DEFINE: + case TOKEN_SET: + skip_token(&val, NULL, cfile); + if (token == DEFINE) + flag = 1; + else + flag = 0; + + token = next_token(&val, NULL, cfile); + if (token != NAME && token != NUMBER_OR_NAME) + parse_error(cfile, + "%s can't be a variable name", val); + st = createMap(); + st->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, st, flag ? "define" : "set"); + var = createString(makeString(-1, val)); + mapSet(st, var, "name"); + token = next_token(&val, NULL, cfile); + + if (token == LPAREN) { + struct element *func; + struct string *args; + + func = createMap(); + args = allocString(); + do { + token = next_token(&val, NULL, cfile); + if (token == RPAREN) + break; + if (token != NAME && token != NUMBER_OR_NAME) + parse_error(cfile, + "expecting argument name"); + if (args->length > 0) + appendString(args, ", "); + appendString(args, val); + token = next_token(&val, NULL, cfile); + } while (token == COMMA); + + if (token != RPAREN) { + parse_error(cfile, "expecting right paren."); + badx: + skip_to_semi(cfile); + *lose = ISC_TRUE; + return ISC_FALSE; + } + mapSet(func, createString(args), "arguments"); + + token = next_token(&val, NULL, cfile); + if (token != LBRACE) + parse_error(cfile, "expecting left brace."); + + expr = createList(); + if (!parse_executable_statements(expr, cfile, + lose, case_context)) { + if (*lose) + goto badx; + } + mapSet(func, expr, "body"); + mapSet(st, func, "function"); + + token = next_token(&val, NULL, cfile); + if (token != RBRACE) + parse_error(cfile, "expecting rigt brace."); + } else { + if (token != EQUAL) + parse_error(cfile, + "expecting '=' in %s statement.", + flag ? "define" : "set"); + + expr = createMap(); + if (!parse_expression(expr, cfile, lose, context_any, + NULL, expr_none)) { + if (!*lose) + parse_error(cfile, + "expecting expression."); + else + *lose = ISC_TRUE; + skip_to_semi(cfile); + return ISC_FALSE; + } + mapSet(st, expr, "value"); + parse_semi(cfile); + } + break; + + case UNSET: + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token != NAME && token != NUMBER_OR_NAME) + parse_error(cfile, "%s can't be a variable name", val); + + st = createMap(); + st->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, st, "unset"); + var = createString(makeString(-1, val)); + mapSet(st, var, "name"); + parse_semi(cfile); + break; + + case EVAL: + skip_token(&val, NULL, cfile); + expr = createMap(); + + if (!parse_expression(expr, cfile, lose, + context_data, /* XXX */ + NULL, expr_none)) { + if (!*lose) + parse_error(cfile, + "expecting data expression."); + else + *lose = ISC_TRUE; + skip_to_semi(cfile); + return ISC_FALSE; + } + mapSet(result, expr, "eval"); + parse_semi(cfile); + break; + + case EXECUTE: + skip_token(&val, NULL, cfile); + expr = createMap(); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + parse_error(cfile, "left parenthesis expected."); + + token = next_token(&val, &len, cfile); + if (token != STRING) + parse_error(cfile, "Expecting a quoted string."); + mapSet(expr, createString(makeString(len, val)), "command"); + + st = createList(); + + while ((token = next_token(&val, NULL, cfile)) == COMMA) { + var = createMap(); + if (!parse_data_expression(var, cfile, lose)) { + if (!*lose) + parse_error(cfile, + "expecting expression."); + skip_to_semi(cfile); + *lose = ISC_TRUE; + return ISC_FALSE; + } + listPush(st, var); + } + mapSet(expr, st, "arguments"); + + if (token != RPAREN) + parse_error(cfile, "right parenthesis expected."); + parse_semi(cfile); + mapSet(result, expr, "execute"); + break; + + case RETURN: + skip_token(&val, NULL, cfile); + + expr = createMap(); + + if (!parse_expression(expr, cfile, lose, context_data, + NULL, expr_none)) { + if (!*lose) + parse_error(cfile, + "expecting data expression."); + else + *lose = ISC_TRUE; + skip_to_semi(cfile); + return ISC_FALSE; + } + mapSet(result, expr, "return"); + parse_semi(cfile); + break; + + case LOG: + skip_token(&val, NULL, cfile); + + st = createMap(); + st->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, st, "log"); + if (log_warning) { + struct comment *comment; + + comment = createComment("/// Kea does not support " + "yet log statements"); + TAILQ_INSERT_TAIL(&st->comments, comment); + comment= createComment("/// Reference Kea #234"); + TAILQ_INSERT_TAIL(&st->comments, comment); + log_warning = ISC_FALSE; + } + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + parse_error(cfile, "left parenthesis expected."); + + token = peek_token(&val, NULL, cfile); + i = 1; + if (token == FATAL) + s = makeString(-1, val); + else if (token == ERROR) + s = makeString(-1, val); + else if (token == TOKEN_DEBUG) + s = makeString(-1, val); + else if (token == INFO) + s = makeString(-1, val); + else { + s = makeString(-1, "DEBUG"); + i = 0; + } + if (i) { + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token != COMMA) + parse_error(cfile, "comma expected."); + } + pri = createString(s); + mapSet(st, pri, "priority"); + + expr = createMap(); + if (!parse_data_expression(expr, cfile, lose)) { + skip_to_semi(cfile); + *lose = ISC_TRUE; + return ISC_FALSE; + } + mapSet(st, expr, "message"); + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + parse_error(cfile, "right parenthesis expected."); + + token = next_token(&val, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "semicolon expected."); + break; + + case PARSE_VENDOR_OPT: + /* The parse-vendor-option; The statement has no arguments. + * We simply set up the statement and when it gets executed it + * will find all information it needs in the packet and options. + */ + skip_token(&val, NULL, cfile); + parse_semi(cfile); + + /* Done by Kea after classification so this statement + * silently does not translate */ + 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, NULL, cfile); + zone = createMap(); + zone->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, zone, "zone"); + + s = parse_host_name(cfile); + if (s == NULL) { + parse_error(cfile, "expecting hostname."); + badzone: + *lose = ISC_TRUE; + skip_to_semi(cfile); + return ISC_FALSE; + } + if (s->content[s->length - 1] != '.') + appendString(s, "."); + mapSet(zone, createString(s), "name"); + if (!parse_zone(zone, cfile)) + goto badzone; + return ISC_TRUE; + + /* Also not really a statement, but same idea as above. */ + case KEY: + skip_token(&val, NULL, cfile); + if (!parse_key(result, cfile)) { + /* Kea TODO */ + *lose = ISC_TRUE; + return ISC_FALSE; + } + return ISC_TRUE; + + default: + if (is_identifier(token)) { + /* the config universe is the server one */ + option = option_lookup_name("server", val); + if (option) { + skip_token(&val, NULL, cfile); + result->skip = ISC_TRUE; + cfile->issue_counter++; + return parse_config_statement + (direct ? NULL : result, + cfile, option, + supersede_option_statement); + } + } + + if (token == NUMBER_OR_NAME || token == NAME) { + /* This is rather ugly. Since function calls are + data expressions, fake up an eval statement. */ + expr = createMap(); + + if (!parse_expression(expr, cfile, lose, context_data, + NULL, expr_none)) { + if (!*lose) + parse_error(cfile, "expecting " + "function call."); + else + *lose = ISC_TRUE; + skip_to_semi(cfile); + return ISC_FALSE; + } + mapSet(result, expr, "eval"); + parse_semi(cfile); + break; + } + + *lose = ISC_FALSE; + return ISC_FALSE; + } + + return ISC_TRUE; +} + +/* 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 */ + +isc_boolean_t +parse_zone(struct element *zone, struct parse *cfile) +{ + int token; + const char *val; + struct element *values; + struct string *key_name; + isc_boolean_t done = ISC_FALSE; + + token = next_token(&val, NULL, cfile); + if (token != LBRACE) + parse_error(cfile, "expecting left brace"); + + do { + token = peek_token(&val, NULL, cfile); + switch (token) { + case PRIMARY: + if (mapContains(zone, "primary")) + parse_error(cfile, "more than one primary."); + values = createList(); + mapSet(zone, values, "primary"); + goto consemup; + + case SECONDARY: + if (mapContains(zone, "secondary")) + parse_error(cfile, "more than one secondary."); + values = createList(); + mapSet(zone, values, "secondary"); + consemup: + skip_token(&val, NULL, cfile); + do { + struct string *value; + + value = parse_ip_addr_or_hostname(cfile, + ISC_FALSE); + if (value == NULL) + parse_error(cfile, + "expecting IP addr or " + "hostname."); + listPush(values, createString(value)); + token = next_token(&val, NULL, cfile); + } while (token == COMMA); + if (token != SEMI) + parse_error(cfile, "expecting semicolon."); + break; + + case PRIMARY6: + if (mapContains(zone, "primary6")) + parse_error(cfile, "more than one primary6."); + values = createList(); + mapSet(zone, values, "primary6"); + goto consemup6; + + case SECONDARY6: + if (mapContains(zone, "secondary6")) + parse_error(cfile, "more than one secondary6."); + values = createList(); + mapSet(zone, values, "secondary6"); + consemup6: + skip_token(&val, NULL, cfile); + do { + struct string *addr; + + addr = parse_ip6_addr_txt(cfile); + if (addr == NULL) + parse_error(cfile, "expecting IPv6 addr."); + listPush(values, createString(addr)); + token = next_token(&val, NULL, cfile); + } while (token == COMMA); + if (token != SEMI) + parse_error(cfile, "expecting semicolon."); + break; + + case KEY: + skip_token(&val, NULL, cfile); + token = peek_token(&val, NULL, cfile); + if (token == STRING) { + skip_token(&val, NULL, cfile); + key_name = makeString(-1, val); + } else { + key_name = parse_host_name(cfile); + if (!key_name) + parse_error(cfile, "expecting key name."); + } + if (mapContains(zone, "key")) + parse_error(cfile, "Multiple key definitions"); + mapSet(zone, createString(key_name), "key"); + parse_semi(cfile); + break; + + default: + done = 1; + break; + } + } while (!done); + + token = next_token(&val, NULL, cfile); + if (token != RBRACE) + parse_error(cfile, "expecting right brace."); + 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 + + Kea: where to put this? It is a D2 value */ + +isc_boolean_t +parse_key(struct element* result, struct parse *cfile) +{ + int token; + const char *val; + isc_boolean_t done = ISC_FALSE; + struct element *key; + struct string *alg; + struct string *sec; + struct element *keys; + char *s; + + key = createMap(); + key->skip = ISC_TRUE; + cfile->issue_counter++; + + token = peek_token(&val, NULL, cfile); + if (token == STRING) { + skip_token(&val, NULL, cfile); + mapSet(key, createString(makeString(-1, val)), "name"); + } else { + struct string *name; + + name = parse_host_name(cfile); + if (name == NULL) + parse_error(cfile, "expecting key name."); + mapSet(key, createString(name), "name"); + } + + token = next_token(&val, NULL, cfile); + if (token != LBRACE) + parse_error(cfile, "expecting left brace"); + + do { + token = next_token(&val, NULL, cfile); + switch (token) { + case ALGORITHM: + if (mapContains(key, "algorithm")) + parse_error(cfile, "key: too many algorithms"); + alg = parse_host_name(cfile); + if (alg == NULL) + parse_error(cfile, + "expecting key algorithm name."); + parse_semi(cfile); + /* If the algorithm name isn't an FQDN, tack on + the .SIG-ALG.REG.NET. domain. */ + s = strrchr(alg->content, '.'); + if (!s) + appendString(alg, ".SIG-ALG.REG.INT."); + /* If there is no trailing '.', hack one in. */ + else + appendString(alg, "."); + mapSet(key, createString(alg), "algorithm"); + break; + + case SECRET: + if (mapContains(key, "secret")) + parse_error(cfile, "key: too many secrets"); + + sec = parse_base64(cfile); + if (sec == NULL) { + skip_to_rbrace(cfile, 1); + return ISC_FALSE; + } + mapSet(key, createString(sec), "secret"); + + parse_semi(cfile); + break; + + default: + done = ISC_TRUE; + break; + } + } while (!done); + if (token != RBRACE) + parse_error(cfile, "expecting right brace."); + /* Allow the BIND 8 syntax, which has a semicolon after each + closing brace. */ + token = peek_token(&val, NULL, cfile); + if (token == SEMI) + skip_token(&val, NULL, cfile); + + /* Remember the key. */ + keys = mapGet(result, "tsig-keys"); + if (keys == NULL) { + keys = createList(); + mapSet(result, keys, "tsig-keys"); + } + listPush(keys, key); + return ISC_TRUE; +} + +/* + * on-statement :== event-types LBRACE executable-statements RBRACE + * event-types :== event-type OR event-types | + * event-type + * event-type :== EXPIRY | COMMIT | RELEASE + */ + +isc_boolean_t +parse_on_statement(struct element *result, + struct parse *cfile, + isc_boolean_t *lose) +{ + enum dhcp_token token; + const char *val; + struct element *statement; + struct string *cond; + struct element *body; + + statement = createMap(); + statement->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, statement, "on"); + + cond = allocString(); + do { + token = next_token(&val, NULL, cfile); + switch (token) { + case EXPIRY: + case COMMIT: + case RELEASE: + case TRANSMISSION: + appendString(cond, val); + break; + + default: + parse_error(cfile, "expecting a lease event type"); + } + token = next_token(&val, NULL, cfile); + if (token == OR) + appendString(cond, " or "); + } while (token == OR); + + mapSet(statement, createString(cond), "condition"); + + /* Semicolon means no statements. */ + if (token == SEMI) + return ISC_TRUE; + + if (token != LBRACE) + parse_error(cfile, "left brace expected."); + + body = createList(); + if (!parse_executable_statements(body, cfile, lose, context_any)) { + if (*lose) { + /* Try to even things up. */ + do { + token = next_token(&val, NULL, cfile); + } while (token != END_OF_FILE && token != RBRACE); + return ISC_FALSE; + } + } + mapSet(statement, body, "body"); + token = next_token(&val, NULL, cfile); + if (token != RBRACE) + parse_error(cfile, "right brace expected."); + return ISC_TRUE; +} + +/* + * switch-statement :== LPAREN expr RPAREN LBRACE executable-statements RBRACE + * + */ + +isc_boolean_t +parse_switch_statement(struct element *result, + struct parse *cfile, + isc_boolean_t *lose) +{ + enum dhcp_token token; + const char *val; + struct element *statement; + struct element *cond; + struct element *body; + + statement = createMap(); + statement->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, statement, "switch"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) { + parse_error(cfile, "expecting left brace."); + *lose = ISC_TRUE; + skip_to_semi(cfile); + return ISC_FALSE; + } + + cond = createMap(); + if (!parse_expression(cond, cfile, lose, context_data_or_numeric, + NULL, expr_none)) { + if (!*lose) + parse_error(cfile, + "expecting data or numeric expression."); + return ISC_FALSE; + } + mapSet(statement, cond, "condition"); + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + parse_error(cfile, "right paren expected."); + + token = next_token(&val, NULL, cfile); + if (token != LBRACE) + parse_error(cfile, "left brace expected."); + + body = createList(); + if (!parse_executable_statements(body, cfile, lose, + (is_data_expression(cond) ? context_data : context_numeric))) { + if (*lose) { + skip_to_rbrace(cfile, 1); + return ISC_FALSE; + } + } + mapSet(statement, body, "body"); + token = next_token(&val, NULL, cfile); + if (token != RBRACE) + parse_error(cfile, "right brace expected."); + return ISC_TRUE; +} + +/* + * case-statement :== CASE expr COLON + * + */ + +isc_boolean_t +parse_case_statement(struct element *result, + struct parse *cfile, + isc_boolean_t *lose, + enum expression_context case_context) +{ + enum dhcp_token token; + const char *val; + struct element *expr; + + expr = createMap(); + if (!parse_expression(expr, cfile, lose, case_context, + NULL, expr_none)) + { + if (!*lose) + parse_error(cfile, "expecting %s expression.", + (case_context == context_data + ? "data" : "numeric")); + *lose = ISC_TRUE; + skip_to_semi(cfile); + return ISC_FALSE; + } + + token = next_token(&val, NULL, cfile); + if (token != COLON) + parse_error(cfile, "colon expected."); + mapSet(result, expr, "case"); + return ISC_TRUE; +} + +/* + * 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 + */ + +isc_boolean_t +parse_if_statement(struct element *result, + struct parse *cfile, + isc_boolean_t *lose) +{ + enum dhcp_token token; + const char *val; + isc_boolean_t parenp; + struct element *statement; + struct element *cond; + struct element *branch; + + statement = createMap(); + statement->skip = ISC_TRUE; + cfile->issue_counter++; + + mapSet(result, statement, "if"); + + token = peek_token(&val, NULL, cfile); + if (token == LPAREN) { + parenp = ISC_TRUE; + skip_token(&val, NULL, cfile); + } else + parenp = ISC_FALSE; + + cond = createMap(); + if (!parse_boolean_expression(cond, cfile, lose)) { + if (!*lose) + parse_error(cfile, "boolean expression expected."); + *lose = ISC_TRUE; + return ISC_FALSE; + } + mapSet(statement, cond, "condition"); + if (parenp) { + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + parse_error(cfile, "expecting right paren."); + } + token = next_token(&val, NULL, cfile); + if (token != LBRACE) + parse_error(cfile, "left brace expected."); + branch = createList(); + if (!parse_executable_statements(branch, cfile, lose, context_any)) { + if (*lose) { + /* Try to even things up. */ + do { + token = next_token(&val, NULL, cfile); + } while (token != END_OF_FILE && token != RBRACE); + return ISC_FALSE; + } + } + mapSet(statement, branch, "then"); + token = next_token(&val, NULL, cfile); + if (token != RBRACE) + parse_error(cfile, "right brace expected."); + token = peek_token(&val, NULL, cfile); + if (token == ELSE) { + skip_token(&val, NULL, cfile); + token = peek_token(&val, NULL, cfile); + if (token == IF) { + skip_token(&val, NULL, cfile); + branch = createMap(); + if (!parse_if_statement(branch, cfile, lose)) { + if (!*lose) + parse_error(cfile, + "expecting if statement"); + *lose = ISC_TRUE; + return ISC_FALSE; + } + } else if (token != LBRACE) + parse_error(cfile, "left brace or if expected."); + else { + skip_token(&val, NULL, cfile); + branch = createList(); + if (!parse_executable_statements(branch, cfile, + lose, context_any)) + return ISC_FALSE; + token = next_token(&val, NULL, cfile); + if (token != RBRACE) + parse_error(cfile, "right brace expected."); + } + mapSet(statement, branch, "else"); + } else if (token == ELSIF) { + skip_token(&val, NULL, cfile); + branch = createMap(); + if (!parse_if_statement(branch, cfile, lose)) { + if (!*lose) + parse_error(cfile, + "expecting conditional."); + *lose = ISC_TRUE; + return ISC_FALSE; + } + mapSet(statement, branch, "else"); + } + + return ISC_TRUE; +} + +/* + * 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 + */ + +isc_boolean_t +parse_boolean_expression(struct element *expr, + struct parse *cfile, + isc_boolean_t *lose) +{ + /* Parse an expression... */ + if (!parse_expression(expr, cfile, lose, context_boolean, + NULL, expr_none)) + return ISC_FALSE; + + if (!is_boolean_expression(expr) && + !mapContains(expr, "variable-reference") && + !mapContains(expr, "funcall")) + parse_error(cfile, "Expecting a boolean expression."); + return ISC_TRUE; +} + +/* boolean :== ON SEMI | OFF SEMI | TRUE SEMI | FALSE SEMI */ + +isc_boolean_t +parse_boolean(struct parse *cfile) +{ + const char *val; + isc_boolean_t rv; + + (void)next_token(&val, NULL, cfile); + if (!strcasecmp (val, "true") + || !strcasecmp (val, "on")) + rv = ISC_TRUE; + else if (!strcasecmp (val, "false") + || !strcasecmp (val, "off")) + rv = ISC_FALSE; + else + parse_error(cfile, + "boolean value (true/false/on/off) expected"); + 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 | + * V6RELAY LPAREN numeric-expression COMMA + * data-expression RPAREN | + * STRING | + * colon_separated_hex_list + */ + +isc_boolean_t +parse_data_expression(struct element *expr, + struct parse *cfile, + isc_boolean_t *lose) +{ + /* Parse an expression... */ + if (!parse_expression(expr, cfile, lose, context_data, + NULL, expr_none)) + return ISC_FALSE; + + if (!is_data_expression(expr) && + !mapContains(expr, "variable-reference") && + !mapContains(expr, "funcall")) + parse_error(cfile, "Expecting a data expression."); + return ISC_TRUE; +} + +/* + * numeric-expression :== EXTRACT_INT LPAREN data-expression + * COMMA number RPAREN | + * NUMBER + */ + +isc_boolean_t +parse_numeric_expression(struct element *expr, + struct parse *cfile, + isc_boolean_t *lose) +{ + /* Parse an expression... */ + if (!parse_expression(expr, cfile, lose, context_numeric, + NULL, expr_none)) + return ISC_FALSE; + + if (!is_numeric_expression(expr) && + !mapContains(expr, "variable-reference") && + !mapContains(expr, "funcall")) + parse_error(cfile, "Expecting a numeric expression."); + return ISC_TRUE; +} + +/* Parse a subexpression that does not contain a binary operator. */ + +isc_boolean_t +parse_non_binary(struct element *expr, + struct parse *cfile, + isc_boolean_t *lose, + enum expression_context context) +{ + enum dhcp_token token; + const char *val; + struct element *nexp; + struct element *arg; + struct element *chain; + struct string *data; + struct comment *comment; + struct option *option; + isc_boolean_t known; + unsigned len; + + token = peek_token(&val, NULL, cfile); + + /* Check for unary operators... */ + switch (token) { + case CHECK: + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token != STRING) + parse_error(cfile, "string expected."); + nexp = createString(makeString(-1, val)); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "check"); + break; + + case TOKEN_NOT: + skip_token(&val, NULL, cfile); + nexp = createMap(); + + if (!parse_non_binary(nexp, cfile, lose, context_boolean)) { + if (!*lose) + parse_error(cfile, "expression expected"); + *lose = ISC_TRUE; + return ISC_FALSE; + } + if (!is_boolean_expression(nexp)) + parse_error(cfile, "boolean expression expected"); + if (!nexp->skip) { + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(expr, nexp, "not"); + break; + + case LPAREN: + skip_token(&val, NULL, cfile); + if (!parse_expression(expr, cfile, lose, context, + NULL, expr_none)) { + if (!*lose) + parse_error(cfile, "expression expected"); + *lose = ISC_TRUE; + return ISC_FALSE; + } + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + parse_error(cfile, "right paren expected"); + break; + + case EXISTS: + skip_token(&val, NULL, cfile); + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_FALSE, &known); + if (option == NULL) { + *lose = ISC_TRUE; + return ISC_FALSE;; + } + nexp = createMap(); + /* push infos to get it back trying to reduce it */ + mapSet(nexp, + createString(makeString(-1, option->space->old)), + "universe"); + mapSet(nexp, + createString(makeString(-1, option->name)), + "name"); + mapSet(nexp, createInt(option->code), "code"); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "exists"); + break; + + case STATIC: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "static"); + break; + + case KNOWN: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "known"); + break; + + case SUBSTRING: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "substring"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) { + nolparen: + parse_error(cfile, "left parenthesis expected."); + } + + arg = createMap(); + if (!parse_data_expression(arg, cfile, lose)) { + nodata: + if (!*lose) + parse_error(cfile, + "expecting data expression."); + return ISC_FALSE; + } + mapSet(nexp, arg, "expression"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) { + nocomma: + parse_error(cfile, "comma expected."); + } + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) { + nonum: + if (!*lose) + parse_error(cfile, + "expecting numeric expression."); + return ISC_FALSE; + } + mapSet(nexp, arg, "offset"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) + goto nonum; + mapSet(nexp, arg, "length"); + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) { + norparen: + parse_error(cfile, "right parenthesis expected."); + } + break; + + case SUFFIX: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "suffix"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + arg = createMap(); + if (!parse_data_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "expression"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) + goto nonum; + mapSet(nexp, arg, "length"); + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + goto norparen; + break; + + case LCASE: + skip_token(&val, NULL, cfile); + nexp = createMap(); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + if (!parse_data_expression(nexp, cfile, lose)) + goto nodata; + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + goto norparen; + if (!nexp->skip) { + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(expr, nexp, "lowercase"); + break; + + case UCASE: + skip_token(&val, NULL, cfile); + nexp = createMap(); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + if (!parse_data_expression(nexp, cfile, lose)) + goto nodata; + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + goto norparen; + if (!nexp->skip) { + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(expr, nexp, "uppercase"); + break; + + case CONCAT: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "concat"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + arg = createMap(); + if (!parse_data_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "left"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + concat_another: + arg = createMap(); + if (!parse_data_expression(arg, cfile, lose)) + goto nodata; + + token = next_token(&val, NULL, cfile); + + if (token == COMMA) { + chain = createMap(); + mapSet(nexp, chain, "right"); + nexp = createMap(); + mapSet(chain, nexp, "concat"); + mapSet(nexp, arg, "left"); + goto concat_another; + } + mapSet(nexp, arg, "right"); + + if (token != RPAREN) + goto norparen; + break; + + case BINARY_TO_ASCII: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "binary-to-ascii"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "base"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "width"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!parse_data_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "separator"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!parse_data_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "buffer"); + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + goto norparen; + break; + + case REVERSE: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "reverse"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + arg = createMap(); + if (!(parse_numeric_expression(arg, cfile, lose))) + goto nodata; + mapSet(nexp, arg, "width"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!(parse_data_expression(arg, cfile, lose))) + goto nodata; + mapSet(nexp, arg, "buffer"); + + token = next_token(&val, NULL, 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, NULL, cfile); + nexp = createList(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "pick-first-value"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + do { + arg = createMap(); + if (!(parse_data_expression(arg, cfile, lose))) + goto nodata; + listPush(nexp, arg); + + token = next_token(&val, NULL, cfile); + } while (token == COMMA); + + if (token != RPAREN) + goto norparen; + break; + + case OPTION: + case CONFIG_OPTION: + skip_token(&val, NULL, cfile); + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_FALSE, &known); + if (option == NULL) { + *lose = ISC_TRUE; + return ISC_FALSE; + } + nexp = createMap(); + mapSet(nexp, + createString(makeString(-1, option->space->old)), + "universe"); + mapSet(nexp, + createString(makeString(-1, option->name)), + "name"); + mapSet(nexp, createInt(option->code), "code"); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + if (token == OPTION) + mapSet(expr, nexp, "option"); + else { + createComment("/// config-option is " + "not supported by Kea"); + TAILQ_CONCAT(&nexp->comments, &cfile->comments); + mapSet(expr, nexp, "config-option"); + } + break; + + case HARDWARE: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "hardware"); + break; + + case LEASED_ADDRESS: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "leased-address"); + break; + + case CLIENT_STATE: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "client-state"); + break; + + case FILENAME: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "filename"); + break; + + case SERVER_NAME: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "server-name"); + break; + + case LEASE_TIME: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "lease-time"); + break; + + case TOKEN_NULL: + skip_token(&val, NULL, cfile); + /* can look at context to return directly ""? */ + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "null"); + break; + + case HOST_DECL_NAME: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "host-decl-name"); + break; + + case PACKET: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "packet"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) + goto nonum; + mapSet(nexp, arg, "offset"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) + goto nonum; + mapSet(nexp, arg, "length"); + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + goto norparen; + break; + + case STRING: + skip_token(&val, &len, cfile); + resetString(expr, makeString(len, val)); + break; + + case EXTRACT_INT: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + parse_error(cfile, "left parenthesis expected."); + + if (!parse_data_expression(nexp, cfile, lose)) { + if (!*lose) + parse_error(cfile, + "expecting data expression."); + return ISC_FALSE; + } + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + parse_error(cfile, "comma expected."); + + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "number expected."); + switch (atoi(val)) { + case 8: + mapSet(expr, nexp, "extract-int8"); + break; + + case 16: + mapSet(expr, nexp, "extract-int16"); + break; + + case 32: + mapSet(expr, nexp, "extract-int32"); + break; + + default: + parse_error(cfile, "unsupported integer size %s", val); + } + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + parse_error(cfile, "right parenthesis expected."); + break; + + case ENCODE_INT: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + parse_error(cfile, "left parenthesis expected."); + + if (!parse_numeric_expression(nexp, cfile, lose)) + parse_error(cfile, "expecting numeric expression."); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + parse_error(cfile, "comma expected."); + + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "number expected."); + switch (atoi(val)) { + case 8: + mapSet(expr, nexp, "encode-int8"); + break; + + case 16: + mapSet(expr, nexp, "encode-int16"); + break; + + case 32: + mapSet(expr, nexp, "encode-int32"); + break; + + default: + parse_error(cfile, "unsupported integer size %s", val); + } + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + parse_error(cfile, "right parenthesis expected."); + 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, NULL, cfile); + /* can also return a const-int */ + resetInt(expr, atoi(val)); + break; + } + + case NUMBER_OR_NAME: + /* Return a const-data to make a difference with + a string literal. createHexa() adds 0x */ + mapSet(expr, createHexa(parse_hexa(cfile)), "const-data"); + break; + + case NS_FORMERR: + skip_token(&val, NULL, cfile); +#ifndef FORMERR +#define FORMERR 1 +#endif + resetInt(expr, FORMERR); + comment = createComment("/// constant FORMERR(1)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_NOERROR: + skip_token(&val, NULL, cfile); +#ifndef ISC_R_SUCCESS +#define ISC_R_SUCCESS 0 +#endif + resetInt(expr, ISC_R_SUCCESS); + comment = createComment("/// constant ISC_R_SUCCESS(0)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_NOTAUTH: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_NOTAUTH +#define DHCP_R_NOTAUTH ((6 << 16) + 21) +#endif + resetInt(expr, DHCP_R_NOTAUTH); + comment = createComment("/// constant DHCP_R_NOTAUTH(393237)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_NOTIMP: + skip_token(&val, NULL, cfile); +#ifndef ISC_R_NOTIMPLEMENTED +#define ISC_R_NOTIMPLEMENTED 27 +#endif + resetInt(expr, ISC_R_NOTIMPLEMENTED); + comment = createComment("/// constant ISC_R_NOTIMPLEMENTED(27)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_NOTZONE: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_NOTZONE +#define DHCP_R_NOTZONE ((6 << 16) + 22) +#endif + resetInt(expr, DHCP_R_NOTZONE); + comment = createComment("/// constant DHCP_R_NOTZONE(393238)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_NXDOMAIN: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_NXDOMAIN +#define DHCP_R_NXDOMAIN ((6 << 16) + 15) +#endif + resetInt(expr, DHCP_R_NXDOMAIN); + comment = createComment("/// constant DHCP_R_NXDOMAIN(393231)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_NXRRSET: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_NXRRSET +#define DHCP_R_NXRRSET ((6 << 16) + 20) +#endif + resetInt(expr, DHCP_R_NXRRSET); + comment = createComment("/// constant DHCP_R_NXRRSET(393236)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_REFUSED: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_REFUSED +#define DHCP_R_REFUSED ((6 << 16) + 17) +#endif + resetInt(expr, DHCP_R_REFUSED); + comment = createComment("/// constant DHCP_R_REFUSED(393233)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_SERVFAIL: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_SERVFAIL +#define DHCP_R_SERVFAIL ((6 << 16) + 14) +#endif + resetInt(expr, DHCP_R_SERVFAIL); + comment = createComment("/// constant DHCP_R_SERVFAIL(393230)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_YXDOMAIN: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_YXDOMAIN +#define DHCP_R_YXDOMAIN ((6 << 16) + 18) +#endif + resetInt(expr, DHCP_R_YXDOMAIN); + comment = createComment("/// constant DHCP_R_YXDOMAIN(393234)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_YXRRSET: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_YXRRSET +#define DHCP_R_YXRRSET ((6 << 16) + 19) +#endif + resetInt(expr, DHCP_R_YXRRSET); + comment = createComment("/// constant DHCP_R_YXRRSET(393235)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case BOOTING: + skip_token(&val, NULL, cfile); +#ifndef S_INIT +#define S_INIT 2 +#endif + resetInt(expr, S_INIT); + comment = createComment("/// constant S_INIT(2)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case REBOOT: + skip_token(&val, NULL, cfile); +#ifndef S_REBOOTING +#define S_REBOOTING 1 +#endif + resetInt(expr, S_REBOOTING); + comment = createComment("/// constant S_REBOOTING(1)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case SELECT: + skip_token(&val, NULL, cfile); +#ifndef S_SELECTING +#define S_SELECTING 3 +#endif + resetInt(expr, S_SELECTING); + comment = createComment("/// constant S_SELECTING(3)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case REQUEST: + skip_token(&val, NULL, cfile); +#ifndef S_REQUESTING +#define S_REQUESTING 4 +#endif + resetInt(expr, S_REQUESTING); + comment = createComment("/// constant S_REQUESTING(4)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case BOUND: + skip_token(&val, NULL, cfile); +#ifndef S_BOUND +#define S_BOUND 5 +#endif + resetInt(expr, S_BOUND); + comment = createComment("/// constant S_BOUND(5)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case RENEW: + skip_token(&val, NULL, cfile); +#ifndef S_RENEWING +#define S_RENEWING 6 +#endif + resetInt(expr, S_RENEWING); + comment = createComment("/// constant S_RENEWING(6)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case REBIND: + skip_token(&val, NULL, cfile); +#ifndef S_REBINDING +#define S_REBINDING 7 +#endif + resetInt(expr, S_REBINDING); + comment = createComment("/// constant S_REBINDING(7)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case DEFINED: + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + token = next_token(&val, NULL, cfile); + if (token != NAME && token != NUMBER_OR_NAME) + parse_error(cfile, "%s can't be a variable name", val); + + nexp = createString(makeString(-1, val)); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "variable-exists"); + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + goto norparen; + break; + + /* This parses 'gethostname()'. */ + case GETHOSTNAME: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "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_error(cfile, "Expecting quoted literal: " + "\"foo.example.com\""); + nexp = createString(makeString(-1, val)); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "gethostbyname"); + + token = next_token(NULL, NULL, cfile); + if (token != RPAREN) + goto norparen; + break; + + case V6RELAY: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "v6relay"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "relay"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!parse_data_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "relay-option"); + + token = next_token(&val, NULL, cfile); + + if (token != RPAREN) + goto norparen; + break; + + /* Not a valid start to an expression... */ + default: + if (token != NAME && token != NUMBER_OR_NAME) + return ISC_FALSE; + + skip_token(&val, NULL, cfile); + + /* Save the name of the variable being referenced. */ + data = makeString(-1, val); + + /* Simple variable reference, as far as we can tell. */ + token = peek_token(&val, NULL, cfile); + if (token != LPAREN) { + nexp = createString(data); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "variable-reference"); + break; + } + + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "funcall"); + chain = createString(data); + mapSet(nexp, chain, "name"); + + /* Now parse the argument list. */ + chain = createList(); + do { + arg = createMap(); + if (!parse_expression(arg, cfile, lose, context_any, + NULL, expr_none)) { + if (!*lose) + parse_error(cfile, + "expecting expression."); + skip_to_semi(cfile); + return ISC_FALSE; + } + listPush(chain, arg); + token = next_token(&val, NULL, cfile); + } while (token == COMMA); + if (token != RPAREN) + parse_error(cfile, "Right parenthesis expected."); + mapSet(nexp, chain, "arguments"); + break; + } + return ISC_TRUE; +} + +/* Parse an expression. */ + +isc_boolean_t +parse_expression(struct element *expr, struct parse *cfile, + isc_boolean_t *lose, enum expression_context context, + struct element *lhs, enum expr_op binop) +{ + enum dhcp_token token; + const char *val; + struct element *rhs, *tmp; + enum expr_op next_op; + enum expression_context + lhs_context = context_any, + rhs_context = context_any; + const char *binop_name; + +new_rhs: + rhs = createMap(); + 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_error(cfile, + "expecting right-hand side."); + return ISC_FALSE; + } + + /* 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, NULL, cfile); + switch (token) { + case BANG: + skip_token(&val, NULL, cfile); + token = peek_token(&val, NULL, cfile); + if (token != EQUAL) + parse_error(cfile, "! in boolean context without ="); + next_op = expr_not_equal; + context = expression_context(rhs); + break; + + case EQUAL: + next_op = expr_equal; + context = expression_context(rhs); + break; + + case TILDE: + 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_error(cfile, "expecting ~= or ~~ operator"); + + context = expression_context(rhs); + 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) { + resetBy(expr, rhs); + return ISC_TRUE; + } + + lhs = rhs; + rhs = NULL; + binop = next_op; + skip_token(&val, NULL, 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, NULL, cfile); + + /* Continue parsing of the right hand side with that token. */ + tmp = rhs; + rhs = createMap(); + if (!parse_expression(rhs, cfile, lose, op_context(next_op), + tmp, next_op)) { + if (!*lose) + parse_error(cfile, + "expecting a subexpression"); + return ISC_FALSE; + } + next_op = expr_none; + } + + binop_name = "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_error(cfile, "illegal expression relating " + "different types"); + + switch (binop) { + case expr_not_equal: + binop_name = "not-equal"; + goto data_numeric; + case expr_equal: + binop_name = "equal"; + data_numeric: + if ((rhs_context != context_data_or_numeric) && + (rhs_context != context_data) && + (rhs_context != context_numeric) && + (rhs_context != context_any)) + parse_error(cfile, "expecting data/numeric " + "expression"); + break; + + case expr_iregex_match: + binop_name = "iregex-match"; + break; + + case expr_regex_match: + binop_name = "regex-match"; + if (expression_context(rhs) != context_data) + parse_error(cfile, + "expecting data expression"); + break; + + case expr_and: + binop_name = "and"; + goto boolean; + case expr_or: + binop_name = "or"; + boolean: + if ((rhs_context != context_boolean) && + (rhs_context != context_any)) { + parse_error(cfile, + "expecting boolean expressions"); + } + break; + + case expr_add: + binop_name = "add"; + goto numeric; + case expr_subtract: + binop_name = "subtract"; + goto numeric; + case expr_divide: + binop_name = "divide"; + goto numeric; + case expr_multiply: + binop_name = "multiply"; + goto numeric; + case expr_remainder: + binop_name = "remainder"; + goto numeric; + case expr_binary_and: + binop_name = "binary-and"; + goto numeric; + case expr_binary_or: + binop_name = "binary-or"; + goto numeric; + case expr_binary_xor: + binop_name = "binary-xor"; + numeric: + if ((rhs_context != context_numeric) && + (rhs_context != context_any)) + parse_error(cfile, + "expecting numeric expressions"); + 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) { + tmp = createMap(); + tmp->skip = ISC_TRUE; + mapSet(expr, tmp, binop_name); + /* All the binary operators' data union members + are the same, so we'll cheat and use the member + for the equals operator. */ + mapSet(tmp, lhs, "left"); + mapSet(tmp, rhs, "right"); + return ISC_TRUE;; + } + + /* Eat the operator token - we now know it was a binary operator... */ + skip_token(&val, NULL, cfile); + + /* Now combine the LHS and the RHS using binop. */ + tmp = createMap(); + tmp->skip = ISC_TRUE; + + /* Store the LHS and RHS. */ + mapSet(tmp, lhs, "left"); + mapSet(tmp, rhs, "right"); + + lhs = createMap(); + mapSet(lhs, tmp, binop_name); + + tmp = NULL; + rhs = NULL; + + binop = next_op; + goto new_rhs; +} + +/* Escape embedded commas, detected heading and leading space */ +struct string * +escape_option_string(unsigned len, const char *val, + isc_boolean_t *require_binary, + isc_boolean_t *modified) +{ + struct string *result; + struct string *add; + unsigned i; + char s[2]; + + result = allocString(); + add = allocString(); + if ((len > 0) && (isspace(val[0]) || isspace(val[len - 1]))) { + *require_binary = ISC_TRUE; + return result; + } + for (i = 0; i < len; i++) { + if (val[i] == ',') { + add->length = 2; + add->content = "\\,"; + *modified = ISC_TRUE; + } else { + add->length = 1; + s[0] = val[i]; + s[1] = 0; + add->content = s; + } + concatString(result, add); + } + free(add); + return result; +} + +isc_boolean_t +parse_option_data(struct element *expr, + struct parse *cfile, + struct option *option) +{ + const char *val; + const char *fmt; + enum dhcp_token token; + unsigned len; + struct string *data; + struct string *saved; + struct string *item; + struct element *elem; + struct comment *comment; + isc_boolean_t require_binary = ISC_FALSE; + isc_boolean_t canon_bool = ISC_FALSE; + isc_boolean_t modified = ISC_FALSE; + + /* Save the initial content */ + saved = allocString(); + save_parse_state(cfile); + for (;;) { + token = next_raw_token(&val, &len, cfile); + if ((token == SEMI) || (token == END_OF_FILE)) + break; + item = makeString(len, val); + if (token == STRING) { + appendString(saved, "\""); + concatString(saved, item); + appendString(saved, "\""); + } else + concatString(saved, item); + } + restore_parse_state(cfile); + + elem = createString(saved); + elem->skip = ISC_TRUE; + mapSet(expr, elem, "original-data"); + + /* Check for binary case */ + + fmt = option->format; + + if ((fmt == NULL) || (*fmt == 0)) + parse_error(cfile, "unknown format for option %s.%s\n", + option->space->name, option->name); + + if ((strchr(fmt, 'Y') != NULL) || (strchr(fmt, 'A') != NULL) || + (strchr(fmt, 'E') != NULL) || (strchr(fmt, 'o') != NULL) || + (*fmt == 'X') || (*fmt == 'u')) + return parse_option_binary(expr, cfile, option, ISC_FALSE); + + if (strchr(fmt, 'N') != NULL) + parse_error(cfile, "unsupported format %s for option %s.%s\n", + fmt, option->space->name, option->name); + + data = allocString(); + + save_parse_state(cfile); + /* Just collect data expecting ISC DHCP and Kea are compatible */ + do { + /* Set fmt one char back for 'a'. */ + if ((fmt != option->format) && (*fmt == 'a')) + fmt -= 1; + + do { + if (*fmt == 'a') + break; + if (data->length != 0) + appendString(data, ", "); + item = parse_option_token(cfile, fmt, &require_binary, + &canon_bool, &modified); + if ((*fmt == 'D') && (fmt[1] == 'c')) + fmt++; + if (require_binary) { + restore_parse_state(cfile); + return parse_option_binary(expr, cfile, option, + item == NULL); + } + if (item == NULL) + parse_error(cfile, "parse_option_data failed"); + concatString(data, item); + fmt++; + } while (*fmt != '\0'); + + if (*fmt == 'a') { + token = peek_token(&val, NULL, cfile); + /* Comma means: continue with next element in array */ + if (token == COMMA) { + skip_token(&val, NULL, cfile); + continue; + } + /* no comma: end of array. + end of string means: leave the loop */ + if (fmt[1] == '\0') + break; + /* 'a' means: go on with next char */ + if (*fmt == 'a') { + fmt++; + continue; + } + } + } while (*fmt == 'a'); + + if (!modified || eqString(saved, data)) + mapRemove(expr, "original-data"); + + elem = createString(data); + if (canon_bool) { + comment = createComment("/// canonized booleans to " + "lowercase true or false"); + TAILQ_INSERT_TAIL(&elem->comments, comment); + } + mapSet(expr, elem, "data"); + + return ISC_TRUE; +} + +isc_boolean_t +parse_option_binary(struct element *expr, struct parse *cfile, + struct option *option, isc_boolean_t ambiguous) +{ + const char *val; + const char *fmt; + enum dhcp_token token; + struct string *data; + struct string *item; + struct element *elem; + struct comment *comment; + const char *g; + + data = allocString(); + fmt = option->format; + + mapSet(expr, createBool(ISC_FALSE), "csv-format"); + + /* Just collect data expecting ISC DHCP and Kea are compatible */ + do { + /* Set fmt to start of format for 'A' and one char back + * for 'a'. + */ + if ((fmt != option->format) && (*fmt == 'a')) + fmt -= 1; + else if (*fmt == 'A') + fmt = option->format; + + 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, NULL, cfile); + if (token == SEMI) { + fmt++; + continue; + } + } + + item = parse_option_token_binary(cfile, fmt); + switch (*fmt) { + case 'E': + g = strchr(fmt, '.'); + if (g == NULL) + parse_error(cfile, + "malformed encapsulation " + "format (bug!)"); + fmt = g; + break; + case 'D': + if (fmt[1] == 'c') + fmt++; + break; + case 'N': + g = strchr(fmt, '.'); + if (g == NULL) + parse_error(cfile, + "malformed enumeration " + "format (bug!)"); + fmt = g; + break; + } + if (item != NULL) + concatString(data, item); + else if (fmt[1] != 'o') + parse_error(cfile, "parse_option_token_binary " + "failed"); + fmt++; + } while (*fmt != '\0'); + + if ((*fmt == 'A') || (*fmt == 'a')) { + token = peek_token(&val, NULL, cfile); + /* Comma means: continue with next element in array */ + if (token == COMMA) { + skip_token(&val, NULL, 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++; + continue; + } + } + } while ((*fmt == 'A') || (*fmt == 'a')); + + elem = mapGet(expr, "original-data"); + if ((elem != NULL) && eqString(stringValue(elem), data)) + mapRemove(expr, "original-data"); + + elem = createString(data); + if (ambiguous) { + comment = createComment("/// Please consider to change " + "last type in the record to binary"); + TAILQ_INSERT_TAIL(&elem->comments, comment); + comment = createComment("/// Reference Kea #246"); + TAILQ_INSERT_TAIL(&elem->comments, comment); + expr->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(expr, elem, "data"); + + return ISC_TRUE; +} + +struct string * +parse_option_textbin(struct parse *cfile, struct option *option) +{ + struct element *expr; + struct element *data; + const char *fmt; + + expr = createMap(); + fmt = option->format; + + if ((fmt == NULL) || (*fmt == 0)) + parse_error(cfile, "unknown format for option %s.%s\n", + option->space->name, option->name); + + if (strcmp(fmt, "t") != 0) { + if (!parse_option_binary(expr, cfile, option, ISC_FALSE)) + parse_error(cfile, "can't parse binary option data"); + data = mapGet(expr, "data"); + if (data == NULL) + parse_error(cfile, "can't get binary option data"); + if (data->type != ELEMENT_STRING) + parse_error(cfile, "option data must be binary"); + return stringValue(data); + } + + if (!parse_option_data(expr, cfile, option)) + parse_error(cfile, "can't parse text option data"); + data = mapGet(expr, "data"); + if (data == NULL) + parse_error(cfile, "can't get test option data"); + if (data->type != ELEMENT_STRING) + parse_error(cfile, "option data must be a string"); + return quote(stringValue(data)); +} + +/* 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. */ + +isc_boolean_t +parse_option_statement(struct element *result, + struct parse *cfile, + struct option *option, + enum statement_op op) +{ + const char *val; + enum dhcp_token token; + struct element *expr; + struct element *opt_data; + struct element *opt_data_list; + isc_boolean_t lose; + size_t where; + + if (option->space == space_lookup("server")) + return parse_config_statement(result, cfile, option, op); + + opt_data = createMap(); + TAILQ_CONCAT(&opt_data->comments, &cfile->comments); + mapSet(opt_data, + createString(makeString(-1, option->space->name)), "space"); + mapSet(opt_data, createString(makeString(-1, option->name)), "name"); + mapSet(opt_data, createInt(option->code), "code"); + if (option->status == kea_unknown) { + opt_data->skip = ISC_TRUE; + cfile->issue_counter++; + } + if (op != supersede_option_statement) { + struct string *msg; + struct comment *comment; + + msg = makeString(-1, "/// Kea does not support option data "); + appendString(msg, "set variants ("); + switch (op) { + case send_option_statement: + appendString(msg, "send"); + break; + case supersede_option_statement: + appendString(msg, "supersede"); + break; + case default_option_statement: + appendString(msg, "default"); + break; + case prepend_option_statement: + appendString(msg, "prepend"); + break; + case append_option_statement: + appendString(msg, "append"); + break; + default: + appendString(msg, "???"); + break; + } + appendString(msg, ")"); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&opt_data->comments, comment); + } + + /* Setting PRL is a standard hack */ + if ((option->space == space_lookup("dhcp")) && + (option->code == 55)) { + struct comment *comment; + + comment = createComment("/// Possible PRL hack"); + TAILQ_INSERT_TAIL(&opt_data->comments, comment); + comment = createComment("/// Consider setting \"always-send\" " + "to true when setting data " + "for relevant options, cf Kea #250"); + TAILQ_INSERT_TAIL(&opt_data->comments, comment); + } + + /* Setting ORO is a standard hack */ + if ((option->space == space_lookup("dhcp6")) && + (option->code == 6)) { + struct comment *comment; + + comment = createComment("/// Possible ORO hack"); + TAILQ_INSERT_TAIL(&opt_data->comments, comment); + comment = createComment("/// Consider setting \"always-send\" " + "to true when setting data " + "for relevant options, cf Kea #250"); + TAILQ_INSERT_TAIL(&opt_data->comments, comment); + } + + token = peek_token(&val, NULL, cfile); + /* We should keep a list of defined empty options */ + 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, NULL, cfile); + } else if (token == EQUAL) { + struct element *data; + isc_boolean_t modified = ISC_FALSE; + + /* Eat the equals sign. */ + skip_token(&val, NULL, cfile); + + /* Parse a data expression and use its value for the data. */ + expr = createMap(); + 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_error(cfile, + "expecting a data expression."); + return ISC_FALSE; + } + /* evaluate the expression */ + expr = eval_data_expression(expr, &modified); + + mapSet(opt_data, createBool(ISC_FALSE), "csv-format"); + + if (expr->type == ELEMENT_STRING) { + struct string *s; + struct string *r; + + s = stringValue(expr); + expr->skip = ISC_TRUE; + mapSet(opt_data, expr, "original-data"); + + r = makeStringExt(s->length, s->content, 'X'); + data = createString(r); + mapSet(opt_data, data, "data"); + } else if ((expr->type == ELEMENT_MAP) && + mapContains(expr, "const-data")) { + struct element *value; + struct string *r; + + value = mapGet(expr, "const-data"); + if ((value == NULL) || (value->type != ELEMENT_STRING)) + parse_error(cfile, "can't get const-data"); + r = hexaValue(value); + data = createString(r); + mapSet(opt_data, data, "data"); + } else { + opt_data->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(opt_data, expr, "expression"); + } + } else { + if (!parse_option_data(opt_data, cfile, option)) + return ISC_FALSE; + } + + parse_semi(cfile); + + if (result != NULL) { + opt_data->skip = ISC_TRUE; + mapSet(result, opt_data, "option"); + return ISC_TRUE; + } + + for (where = cfile->stack_top; where > 0; --where) { + if (cfile->stack[where]->kind != PARAMETER) + break; + } + + opt_data_list = mapGet(cfile->stack[where], "option-data"); + if (opt_data_list == NULL) { + opt_data_list = createList(); + mapSet(cfile->stack[where], opt_data_list, "option-data"); + } + if (!opt_data->skip && (option->space->vendor != NULL)) + add_option_data(option->space->vendor, opt_data_list); + listPush(opt_data_list, opt_data); + + return ISC_TRUE; +} + +/* Text version of parse_option_token */ + +struct string * +parse_option_token(struct parse *cfile, const char *fmt, + isc_boolean_t *require_binary, + isc_boolean_t *canon_bool, + isc_boolean_t *modified) +{ + const char *val; + enum dhcp_token token; + unsigned len; + struct string *item; + + switch (*fmt) { + case 'U': + token = next_token(&val, &len, cfile); + if (!is_identifier(token)) + parse_error(cfile, "expecting identifier."); + return makeString(len, val); + case 'x': + token = peek_token(&val, NULL, cfile); + if (token == NUMBER_OR_NAME || token == NUMBER) { + *require_binary = ISC_TRUE; + return NULL; + } + token = next_token(&val, &len, cfile); + if (token != STRING) + parse_error(cfile, "expecting string " + "or hexadecimal data."); + /* STRING can return embedded unexpected characters */ + return escape_option_string(len, val, require_binary, + modified); + case 'X': + token = peek_token(&val, NULL, cfile); + if (token == NUMBER_OR_NAME || token == NUMBER) { + return parse_hexa(cfile); + } + token = next_token(&val, &len, cfile); + if (token != STRING) + parse_error(cfile, "expecting string " + "or hexadecimal data."); + return makeStringExt(len, val, 'X'); + + case 'D': /* Domain list... */ + *modified = ISC_TRUE; + return parse_domain_list(cfile, ISC_FALSE); + + case 'd': /* Domain name... */ + *modified = ISC_TRUE; + item = parse_host_name(cfile); + if (item == NULL) + parse_error(cfile, "not a valid domain name."); + return item; + + case 't': /* Text string... */ + token = next_token(&val, &len, cfile); + if (token != STRING && !is_identifier(token)) + parse_error(cfile, "expecting string."); + /* STRING can return embedded unexpected characters */ + return escape_option_string(len, val, require_binary, + modified); + + case 'I': /* IP address or hostname. */ + *modified = ISC_TRUE; + return parse_ip_addr_or_hostname(cfile, ISC_FALSE); + + case '6': /* IPv6 address. */ + *modified = ISC_TRUE; + return parse_ip6_addr_txt(cfile); + + case 'T': /* Lease interval. */ + token = next_token(&val, NULL, cfile); + if (token == INFINITE) + return makeString(-1, "0xffffffff"); + goto check_number; + + case 'L': /* Unsigned 32-bit integer... */ + case 'l': + case 's': /* Signed 16-bit integer. */ + case 'S': /* Unsigned 16-bit integer. */ + case 'b': /* Signed 8-bit integer. */ + case 'B': /* Unsigned 8-bit integer. */ + token = next_token(&val, NULL, cfile); + check_number: + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) + parse_error(cfile, "expecting number."); + /* check octal */ + if (val[0] == '0' && isascii(val[1]) && isdigit(val[1])) + *require_binary = ISC_TRUE; + return makeString(-1, val); + + case 'f': /* Boolean flag. */ + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, "expecting identifier."); + if (strcasecmp(val, "true") == 0) + return makeString(-1, "true"); + if (strcasecmp(val, "on") == 0) { + *canon_bool = ISC_TRUE; + *modified = ISC_TRUE; + return makeString(-1, "true"); + } + if (strcasecmp(val, "false") == 0) + return makeString(-1, "false"); + if (strcasecmp(val, "off") == 0) { + *canon_bool = ISC_TRUE; + *modified = ISC_TRUE; + return makeString(-1, "false"); + } + parse_error(cfile, "expecting boolean."); + + case 'Z': /* Zero-length option. */ + token = peek_token(&val, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "semicolon expected."); + return allocString(); + + default: + parse_error(cfile, "Bad format '%c' in parse_option_token.", + *fmt); + } +} + +/* Binary (aka hexadecimal) version of parse_option_token */ + +struct string * +parse_option_token_binary(struct parse *cfile, const char *fmt) +{ + const char *val; + enum dhcp_token token; + unsigned len; + struct string *item; + uint8_t buf[4]; + + switch (*fmt) { + case 'U': + token = next_token(&val, &len, cfile); + if (!is_identifier(token)) { + if (fmt[1] == 'o') + return NULL; + parse_error(cfile, "expecting identifier."); + } + return makeStringExt(len, val, 'X'); + case 'E': + case 'X': + case 'x': + case 'u': + token = peek_token(&val, NULL, cfile); + if (token == NUMBER_OR_NAME || token == NUMBER) + return parse_hexa(cfile); + token = next_token(&val, &len, cfile); + if (token != STRING) { + if (fmt[1] == 'o') + return NULL; + parse_error(cfile, "expecting string " + "or hexadecimal data."); + } + return makeStringExt(len, val, 'X'); + + case 'D': /* Domain list... */ + item = parse_domain_list(cfile, ISC_TRUE); + if (item == NULL) { + if (fmt[1] == 'o') + return NULL; + parse_error(cfile, "parse_domain_list failed"); + } + return NULL; + + case 'd': /* Domain name... */ + item = parse_host_name(cfile); + if (item == NULL) + parse_error(cfile, "not a valid domain name."); + item = makeStringExt(item->length, item->content, 'd'); + if (item == NULL) + parse_error(cfile, "too long domain name."); + return makeStringExt(item->length, item->content, 'X'); + + case 't': /* Text string... */ + token = next_token(&val, &len, cfile); + if (token != STRING && !is_identifier(token)) { + if (fmt[1] == 'o') + return NULL; + parse_error(cfile, "expecting string."); + } + return makeStringExt(len, val, 'X'); + + case 'I': /* IP address or hostname. */ + item = parse_ip_addr_or_hostname(cfile, ISC_FALSE); + return makeStringExt(item->length, item->content, 'i'); + + case '6': /* IPv6 address. */ + item = parse_ip6_addr(cfile); + return makeStringExt(item->length, item->content, 'X'); + + case 'T': /* Lease interval. */ + token = next_token(&val, NULL, cfile); + if (token == INFINITE) + return makeString(-1, "ffffffff"); + goto check_number; + + case 'L': /* Unsigned 32-bit integer... */ + case 'l': /* Signed 32-bit integer... */ + token = next_token(&val, NULL, cfile); + check_number: + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) { + need_number: + if (fmt[1] == 'o') + return NULL; + parse_error(cfile, "expecting number."); + } + convert_num(cfile, buf, val, 0, 32); + return makeStringExt(4, (const char *)buf, 'X'); + + case 's': /* Signed 16-bit integer. */ + case 'S': /* Unsigned 16-bit integer. */ + token = next_token(&val, NULL, cfile); + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) + goto need_number; + convert_num(cfile, buf, val, 0, 16); + return makeStringExt(2, (const char *)buf, 'X'); + + case 'b': /* Signed 8-bit integer. */ + case 'B': /* Unsigned 8-bit integer. */ + token = next_token(&val, NULL, cfile); + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) + goto need_number; + convert_num(cfile, buf, val, 0, 8); + return makeStringExt(1, (const char *)buf, 'X'); + + case 'f': /* Boolean flag. */ + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) { + if (fmt[1] == 'o') + return NULL; + parse_error(cfile, "expecting identifier."); + } + if ((strcasecmp(val, "true") == 0) || + (strcasecmp(val, "on") == 0)) + return makeString(-1, "01"); + if ((strcasecmp(val, "false") == 0) || + (strcasecmp(val, "off") == 0)) + return makeString(-1, "00"); + if (strcasecmp(val, "ignore") == 0) + return makeString(-1, "02"); + if (fmt[1] == 'o') + return NULL; + parse_error(cfile, "expecting boolean."); + + case 'Z': /* Zero-length option. */ + token = peek_token(&val, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "semicolon expected."); + return allocString(); + + default: + parse_error(cfile, "Bad format '%c' in parse_option_token.", + *fmt); + } +} + +struct string * +parse_domain_list(struct parse *cfile, isc_boolean_t binary) +{ + const char *val; + enum dhcp_token token; + unsigned len; + struct string *result; + + token = SEMI; + result = allocString(); + + do { + /* Consume the COMMA token if peeked. */ + if (token == COMMA) { + skip_token(&val, NULL, cfile); + if (!binary) + appendString(result, ", "); + } + + /* Get next (or first) value. */ + token = next_token(&val, &len, cfile); + + if (token != STRING) + parse_error(cfile, "Expecting a domain string."); + + /* Just pack the names in series into the buffer. */ + if (binary) { + struct string *item; + + item = makeStringExt(len, val, 'd'); + if (item == NULL) + parse_error(cfile, "not a valid domain name."); + item = makeStringExt(item->length, item->content, 'X'); + concatString(result, item); + } else + concatString(result, makeString(len, val)); + + token = peek_token(&val, NULL, cfile); + } while (token == COMMA); + + return result; +} + +/* Specialized version of parse_option_data working on config + * options which are scalar (I6LSBtTfUXdNxxx.) only. */ + +isc_boolean_t +parse_config_data(struct element *expr, + struct parse *cfile, + struct option *option) +{ + const char *val; + enum dhcp_token token; + struct string *data; + struct element *elem; + unsigned len; + uint32_t u32; + uint16_t u16; + uint8_t u8; + + token = peek_token(&val, NULL, cfile); + + if (token == END_OF_FILE) + parse_error(cfile, "unexpected end of file"); + if (token == SEMI) + parse_error(cfile, "empty config option"); + if (token == COMMA) + parse_error(cfile, "multiple value config option"); + + /* from parse_option_token */ + + switch (option->format[0]) { + case 'U': /* universe */ + token = next_token(&val, &len, cfile); + if (!is_identifier(token)) + parse_error(cfile, "expecting identifier."); + elem = createString(makeString(len, val)); + break; + + case 'X': /* string or binary */ + token = next_token(&val, &len, cfile); + if (token == NUMBER_OR_NAME || token == NUMBER) + data = parse_cshl(cfile); + else if (token == STRING) + data = makeString(len, val); + else + parse_error(cfile, "expecting string " + "or hexadecimal data."); + elem = createString(data); + break; + + case 'd': /* FQDN */ + data = parse_host_name(cfile); + if (data == NULL) + parse_error(cfile, "not a valid domain name."); + elem = createString(data); + break; + + case 't': /* text */ + token = next_token(&val, &len, cfile); + elem = createString(makeString(len, val)); + break; + + case 'N': /* enumeration */ + token = next_token(&val, &len, cfile); + if (!is_identifier(token)) + parse_error(cfile, "identifier expected"); + elem = createString(makeString(len, val)); + break; + + case 'I': /* IP address or hostname. */ + data = parse_ip_addr_or_hostname(cfile, ISC_FALSE); + if (data == NULL) + parse_error(cfile, "expecting IP address of hostname"); + elem = createString(data); + break; + + case '6': /* IPv6 address. */ + data = parse_ip6_addr_txt(cfile); + if (data == NULL) + parse_error(cfile, "expecting IPv6 address"); + elem = createString(data); + break; + + case 'T': /* Lease interval. */ + token = next_token(&val, NULL, cfile); + if (token != INFINITE) + goto check_number; + elem = createInt(-1); + break; + + case 'L': /* Unsigned 32-bit integer... */ + token = next_token(&val, NULL, cfile); + check_number: + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) + parse_error(cfile, "expecting number."); + convert_num(cfile, (unsigned char *)&u32, val, 0, 32); + elem = createInt(ntohl(u32)); + break; + + case 'S': /* Unsigned 16-bit integer. */ + token = next_token(&val, NULL, cfile); + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) + parse_error(cfile, "expecting number."); + convert_num(cfile, (unsigned char *)&u16, val, 0, 16); + elem = createInt(ntohs(u16)); + break; + + case 'B': /* Unsigned 8-bit integer. */ + token = next_token(&val, NULL, cfile); + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) + parse_error(cfile, "expecting number."); + convert_num(cfile, (unsigned char *)&u8, val, 0, 8); + elem = createInt(ntohs(u8)); + break; + + case 'f': + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, "expecting boolean."); + if ((strcasecmp(val, "true") == 0) || + (strcasecmp(val, "on") == 0)) + elem = createBool(ISC_TRUE); + else if ((strcasecmp(val, "false") == 0) || + (strcasecmp(val, "off") == 0)) + elem = createBool(ISC_FALSE); + else if (strcasecmp(val, "ignore") == 0) { + elem = createNull(); + elem->skip = ISC_TRUE; + } else + parse_error(cfile, "expecting boolean."); + break; + + default: + parse_error(cfile, "Bad format '%c' in parse_config_data.", + option->format[0]); + } + + mapSet(expr, elem, "value"); + + return ISC_TRUE; +} + +/* Specialized version of parse_option_statement for config options */ + +isc_boolean_t +parse_config_statement(struct element *result, + struct parse *cfile, + struct option *option, + enum statement_op op) +{ + const char *val; + enum dhcp_token token; + struct comments *comments; + struct element *expr; + struct element *config; + struct element *config_list; + isc_boolean_t lose; + size_t where; + + config = createMap(); + TAILQ_CONCAT(&config->comments, &cfile->comments); + comments = get_config_comments(option->code); + TAILQ_CONCAT(&config->comments, comments); + mapSet(config, createString(makeString(-1, option->name)), "name"); + mapSet(config, createInt(option->code), "code"); + if (option->status == kea_unknown) { + config->skip = ISC_TRUE; + cfile->issue_counter++; + } + if (op != supersede_option_statement) { + struct string *msg; + struct comment *comment; + + msg = makeString(-1, "/// Kea does not support option data "); + appendString(msg, "set variants ("); + switch (op) { + case send_option_statement: + appendString(msg, "send"); + break; + case supersede_option_statement: + appendString(msg, "supersede"); + break; + case default_option_statement: + appendString(msg, "default"); + break; + case prepend_option_statement: + appendString(msg, "prepend"); + break; + case append_option_statement: + appendString(msg, "append"); + break; + default: + appendString(msg, "???"); + break; + } + appendString(msg, ")"); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&config->comments, comment); + } + + token = peek_token(&val, NULL, cfile); + /* We should keep a list of defined empty options */ + 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, NULL, cfile); + } else if (token == EQUAL) { + /* Eat the equals sign. */ + skip_token(&val, NULL, cfile); + + /* Parse a data expression and use its value for the data. */ + expr = createMap(); + 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_error(cfile, + "expecting a data expression."); + return ISC_FALSE; + } + mapSet(config, expr, "value"); + } else { + if (!parse_config_data(config, cfile, option)) + return ISC_FALSE; + } + + parse_semi(cfile); + + if (result != NULL) { + config->skip = ISC_TRUE; + mapSet(result, config, "config"); + return ISC_TRUE; + } + + for (where = cfile->stack_top; where > 0; --where) { + if ((cfile->stack[where]->kind == PARAMETER) || + (cfile->stack[where]->kind == POOL_DECL)) + continue; + break; + } + + if (option->status != special) { + config_list = mapGet(cfile->stack[where], "config"); + if (config_list == NULL) { + config_list = createList(); + config_list->skip = ISC_TRUE; + mapSet(cfile->stack[where], config_list, "config"); + } + listPush(config_list, config); + return ISC_TRUE; + } + + /* deal with all special cases */ + + switch (option->code) { + case 1: /* default-lease-time */ + config_def_valid_lifetime(config, cfile); + break; + case 2: /* max-lease-time */ + config_max_valid_lifetime(config, cfile); + break; + case 3: /* min-lease-time */ + config_min_valid_lifetime(config, cfile); + break; + case 15: /* filename */ + config_file(config, cfile); + break; + case 16: /* server-name */ + config_sname(config, cfile); + break; + case 17: /* next-server */ + config_next_server(config, cfile); + break; + case 18: /* authoritative */ + parse_error(cfile, "authoritative is a statement, " + "here it is used as a config option"); + case 19: /* vendor-option-space */ + config_vendor_option_space(config, cfile); + break; + case 21: /* site-option-space */ + config_site_option_space(config, cfile); + break; + case 23: /* ddns-domainname */ + config_qualifying_suffix(config, cfile); + break; + case 30: /* ddns-updates */ + config_enable_updates(config, cfile); + break; + case 39: /* ddns-update-style */ + config_ddns_update_style(config, cfile); + break; + case 53: /* preferred-lifetime */ + config_preferred_lifetime(config, cfile); + break; + case 82: /* ignore-client-uids */ + config_match_client_id(config, cfile); + break; + case 85: /* echo-client-id */ + config_echo_client_id(config, cfile); + break; + default: + parse_error(cfile, "unsupported config option %s (%u)", + option->name, option->code); + } + + return ISC_TRUE; +} + +static void +config_def_valid_lifetime(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + isc_boolean_t pop_from_pool = ISC_FALSE; + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == ROOT_GROUP) || + (kind == SHARED_NET_DECL) || + (kind == SUBNET_DECL) || + (kind == GROUP_DECL)) + break; + if (kind == POOL_DECL) { + pop_from_pool = ISC_TRUE; + continue; + } + comment = createComment("/// default-valid-lifetime in " + "unsupported scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + break; + } + if (pop_from_pool) { + comment= createComment("/// default-valid-lifetime moved from " + "an internal pool scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + } + mapSet(cfile->stack[scope], value, "valid-lifetime"); +} + +static void +config_min_valid_lifetime(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + isc_boolean_t pop_from_pool = ISC_FALSE; + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == ROOT_GROUP) || + (kind == SHARED_NET_DECL) || + (kind == SUBNET_DECL) || + (kind == GROUP_DECL)) + break; + if (kind == POOL_DECL) { + pop_from_pool = ISC_TRUE; + continue; + } + comment = createComment("/// min-valid-lifetime in " + "unsupported scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + break; + } + if (pop_from_pool) { + comment= createComment("/// min-valid-lifetime moved from " + "an internal pool scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + } + mapSet(cfile->stack[scope], value, "min-valid-lifetime"); +} + +static void +config_max_valid_lifetime(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + isc_boolean_t pop_from_pool = ISC_FALSE; + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == ROOT_GROUP) || + (kind == SHARED_NET_DECL) || + (kind == SUBNET_DECL) || + (kind == GROUP_DECL)) + break; + if (kind == POOL_DECL) { + pop_from_pool = ISC_TRUE; + continue; + } + comment = createComment("/// max-valid-lifetime in " + "unsupported scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + break; + } + if (pop_from_pool) { + comment= createComment("/// max-valid-lifetime moved from " + "an internal pool scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + } + mapSet(cfile->stack[scope], value, "max-valid-lifetime"); +} + +static void +config_file(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + isc_boolean_t popped = ISC_FALSE; + + if (local_family != AF_INET) + parse_error(cfile, "boot-file-name is DHCPv4 only"); + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == HOST_DECL) || + (kind == CLASS_DECL) || + (kind == GROUP_DECL)) + break; + if (kind == ROOT_GROUP) { + popped = ISC_TRUE; + break; + } + } + if (popped) { + comment = createComment("/// boot-file-name was defined in " + "an unsupported scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(cfile->stack[scope], value, "boot-file-name"); +} + +static void +config_sname(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + isc_boolean_t popped = ISC_FALSE; + + if (local_family != AF_INET) + parse_error(cfile, "server-hostname is DHCPv4 only"); + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == HOST_DECL) || + (kind == CLASS_DECL) || + (kind == GROUP_DECL)) + break; + if (kind == ROOT_GROUP) { + popped = ISC_TRUE; + break; + } + } + if (popped) { + comment = createComment("/// server-hostname was defined in " + "an unsupported scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(cfile->stack[scope], value, "server-hostname"); +} + +static void +config_next_server(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + isc_boolean_t popped = ISC_FALSE; + + if (local_family != AF_INET) + parse_error(cfile, "next-server is DHCPv4 only"); + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == ROOT_GROUP) || + (kind == HOST_DECL) || + (kind == CLASS_DECL) || + (kind == SUBNET_DECL) || + (kind == GROUP_DECL)) + break; + popped = ISC_TRUE; + } + if (popped) { + comment = createComment("/// next-server moved from " + "an internal unsupported scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + } + mapSet(cfile->stack[scope], value, "next-server"); +} + +static void +config_vendor_option_space(struct element *config, struct parse *cfile) +{ + struct element *defs; + struct element *def; + struct element *opts; + struct element *opt; + struct element *space; + + if (local_family != AF_INET) + parse_error(cfile, "vendor-option-space is DHCPv4 only"); + + /* create local option definition */ + def = createMap(); + mapSet(def, + createString(makeString(-1, "vendor-encapsulated-options")), + "name"); + mapSet(def, createInt(43), "code"); + mapSet(def, createString(makeString(-1, "empty")), "type"); + space = mapGet(config, "value"); + if (space == NULL) + parse_error(cfile, "vendor-option-space has no value"); + if (space->type != ELEMENT_STRING) + parse_error(cfile, + "vendor-option-space value is not a string"); + mapSet(def, space, "encapsulate"); + + /* add it */ + defs = mapGet(cfile->stack[cfile->stack_top], "option-def"); + if (defs == NULL) { + defs = createList(); + mapSet(cfile->stack[cfile->stack_top], defs, "option-def"); + } else { + size_t i; + + /* Look for duplicate */ + for (i = 0; i < listSize(defs); i++) { + struct element *item; + struct element *code; + struct element *old; + + item = listGet(defs, i); + if ((item == NULL) || (item->type != ELEMENT_MAP)) + continue; + code = mapGet(item, "code"); + if ((code == NULL) || + (code->type != ELEMENT_INTEGER) || + (intValue(code) != 43)) + continue; + old = mapGet(item, "encapsulate"); + if ((old == NULL) || (old->type != ELEMENT_STRING)) + continue; + if (eqString(stringValue(space), stringValue(old))) + return; + } + } + listPush(defs, def); + + /* add a data too assuming at least one suboption exists */ + opt = createMap(); + mapSet(opt, + createString(makeString(-1, "vendor-encapsulated-options")), + "name"); + mapSet(opt, createInt(43), "code"); + opts = mapGet(cfile->stack[cfile->stack_top], "option-data"); + if (opts == NULL) { + opts = createList(); + mapSet(cfile->stack[cfile->stack_top], opts, "option-data"); + } + listPush(opts, opt); +} + +static void +config_site_option_space(struct element *config, struct parse *cfile) +{ + struct element *defs; + struct element *space; + struct string *msg; + struct comment *comment; + + if (local_family != AF_INET) + parse_error(cfile, "site-option-space is DHCPv4 only"); + + space = mapGet(config, "value"); + if (space == NULL) + parse_error(cfile, "site-option-space has no value"); + if (space->type != ELEMENT_STRING) + parse_error(cfile, "site-option-space value is not a string"); + + defs = mapGet(cfile->stack[cfile->stack_top], "option-def"); + if (defs == NULL) { + defs = createList(); + mapSet(cfile->stack[cfile->stack_top], defs, "option-def"); + } + + msg = makeString(-1, "/// site-option-space '"); + concatString(msg, stringValue(space)); + appendString(msg, "'"); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&defs->comments, comment); + msg = makeString(-1, "/// Please to move private (code 224..254)"); + appendString(msg, " option definitions from '"); + concatString(msg, stringValue(space)); + appendString(msg, "' to 'dhcp4' space"); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&defs->comments, comment); +} + +static struct element * +default_qualifying_suffix(void) +{ + struct element *qs; + struct comment *comment; + + qs = createString(allocString()); + comment = createComment("/// Unspecified ddns-domainname (default " + "domain-name option value)"); + TAILQ_INSERT_TAIL(&qs->comments, comment); + comment = createComment("/// Kea requires a qualifying-suffix"); + TAILQ_INSERT_TAIL(&qs->comments, comment); + comment = createComment("/// Initialized to \"\": please put a value"); + TAILQ_INSERT_TAIL(&qs->comments, comment); + return qs; +} + +static void +config_qualifying_suffix(struct element *config, struct parse *cfile) +{ + struct element *value; + size_t scope; + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) + if ((cfile->stack[scope]->kind != PARAMETER) || + (cfile->stack[scope]->kind != POOL_DECL)) + break; + if (cfile->stack[scope]->kind != ROOT_GROUP) { + struct comment *comment; + + comment = createComment("/// Only global qualifying-suffix " + "is supported"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(cfile->stack[scope], value, "qualifying-suffix"); + } else { + struct element *d2; + + d2 = mapGet(cfile->stack[1], "dhcp-ddns"); + if (d2 == NULL) { + d2 = createMap(); + mapSet(d2, createBool(ISC_FALSE), "enable-updates"); + mapSet(cfile->stack[1], d2, "dhcp-ddns"); + } else if (mapContains(d2, "qualifying-suffix")) + mapRemove(d2, "qualifying-suffix"); + mapSet(d2, value, "qualifying-suffix"); + } +} + +static void +config_enable_updates(struct element *config, struct parse *cfile) +{ + struct element *value; + size_t scope; + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) + if ((cfile->stack[scope]->kind != PARAMETER) || + (cfile->stack[scope]->kind != POOL_DECL)) + break; + if (cfile->stack[scope]->kind != ROOT_GROUP) { + struct comment *comment; + + comment = createComment("/// Only global enable-updates " + "is supported"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(cfile->stack[scope], value, "enable-updates"); + } else { + struct element *d2; + + d2 = mapGet(cfile->stack[1], "dhcp-ddns"); + if (d2 == NULL) { + d2 = createMap(); + mapSet(cfile->stack[1], d2, "dhcp-ddns"); + if (boolValue(value)) { + struct element *qs; + + qs = default_qualifying_suffix(); + mapSet(d2, qs, "qualifying-suffix"); + } + } else if (mapContains(d2, "enable-updates")) + mapRemove(d2, "enable-updates"); + mapSet(d2, value, "enable-updates"); + } +} + +static void +config_ddns_update_style(struct element *config, struct parse *cfile) +{ + struct element *value; + isc_boolean_t enable = ISC_TRUE; + size_t scope; + + value = mapGet(config, "value"); + if (strcmp(stringValue(value)->content, "standard") == 0) + enable = ISC_TRUE; + else if (strcmp(stringValue(value)->content, "none") == 0) + enable = ISC_FALSE; + else { + struct string *msg; + struct comment *comment; + + for (scope = cfile->stack_top; scope > 0; --scope) + if ((cfile->stack[scope]->kind != PARAMETER) || + (cfile->stack[scope]->kind != POOL_DECL)) + break; + msg = makeString(-1, "/// Unsupported ddns-update-style "); + concatString(msg, stringValue(value)); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(cfile->stack[scope], value, "ddns-update-style"); + } + + for (scope = cfile->stack_top; scope > 0; --scope) + if ((cfile->stack[scope]->kind != PARAMETER) || + (cfile->stack[scope]->kind != POOL_DECL)) + break; + if (cfile->stack[scope]->kind != ROOT_GROUP) { + struct comment *comment; + + comment = createComment("/// Only global ddns-update-style " + "is supported"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(cfile->stack[scope], value, "ddns-update-style"); + } else { + struct element *d2; + + /* map ddns-update-style into enable-updates */ + value = createBool(enable); + d2 = mapGet(cfile->stack[1], "dhcp-ddns"); + if (d2 == NULL) { + d2 = createMap(); + mapSet(cfile->stack[1], d2, "dhcp-ddns"); + if (boolValue(value)) { + struct element *qs; + + qs = default_qualifying_suffix(); + mapSet(d2, qs, "qualifying-suffix"); + } + } else if (mapContains(d2, "enable-updates")) + mapRemove(d2, "enable-updates"); + mapSet(d2, value, "enable-updates"); + } +} + +static void +config_preferred_lifetime(struct element *config, struct parse *cfile) +{ + struct element *value; + struct element *child; + struct comment *comment; + size_t scope; + isc_boolean_t pop_from_pool = ISC_FALSE; + + if (local_family != AF_INET6) + parse_error(cfile, "preferred-lifetime is DHCPv6 only"); + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == ROOT_GROUP) || + (kind == SHARED_NET_DECL) || + (kind == SUBNET_DECL) || + (kind == GROUP_DECL)) + break; + if (kind == POOL_DECL) { + pop_from_pool = ISC_TRUE; + continue; + } + comment = createComment("/// preferred-lifetime in " + "unsupported scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + break; + } + if (pop_from_pool) { + comment = createComment("/// preferred-lifetime moved from " + "an internal pool scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + /* if there is another specified value and we are + * enough lucky to have already got it... */ + if (mapContains(cfile->stack[scope], "preferred-lifetime")) { + comment = createComment("/// Avoid to overwrite " + "current value..."); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + } + } + mapSet(cfile->stack[scope], value, "preferred-lifetime"); + /* derive T1 and T2 */ + child = createInt(intValue(value) / 2); + child->skip = value->skip; + mapSet(cfile->stack[scope], child, "renew-timer"); + child = createInt(intValue(value) * 4 / 5); + child->skip = value->skip; + mapSet(cfile->stack[scope], child, "rebind-timer"); +} + +static void +config_match_client_id(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + isc_boolean_t pop_from_pool = ISC_FALSE; + + if (local_family != AF_INET) + parse_error(cfile, "ignore-client-uids is DHCPv4 only"); + + value = mapGet(config, "value"); + /* match-client-id is !ignore-client-uids */ + value = createBool(!boolValue(value)); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == ROOT_GROUP) || + (kind == SHARED_NET_DECL) || + (kind == SUBNET_DECL) || + (kind == GROUP_DECL)) + break; + if (kind == POOL_DECL) { + pop_from_pool = ISC_TRUE; + continue; + } + comment = createComment("/// match-client-id in unsupported " + "scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + break; + } + if (pop_from_pool) { + comment= createComment("/// match-client-id moved from " + "an internal pool scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + } + mapSet(cfile->stack[scope], value, "match-client-id"); +} + +static void +config_echo_client_id(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + + if (local_family != AF_INET) + parse_error(cfile, "echo-client-id is DHCPv4 only"); + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if (kind == ROOT_GROUP) + break; + comment = createComment("/// Only global echo-client-id " + "is supported"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(cfile->stack[scope], value, "echo-client-id"); +} + +/* parse_error moved to keama.c */ + +/* From omapi/convert.c */ +/* +static uint32_t +getULong(const unsigned char *buf) +{ + uint32_t ibuf; + + memcpy(&ibuf, buf, sizeof(uint32_t)); + return ntohl(ibuf); +} + +static int32_t +getLong(const unsigned char *buf) +{ + int32_t ibuf; + + memcpy(&ibuf, buf, sizeof(int32_t)); + return ntohl(ibuf); +} + +static uint32_t +getUShort(const unsigned char *buf) +{ + unsigned short ibuf; + + memcpy(&ibuf, buf, sizeof(uint16_t)); + return ntohs(ibuf); +} + +static int32_t +getShort(const unsigned char *buf) +{ + short ibuf; + + memcpy(&ibuf, buf, sizeof(int16_t)); + return ntohs(ibuf); +} + +static uint32_t +getUChar(const unsigned char *obuf) +{ + return obuf[0]; +} +*/ +static void +putULong(unsigned char *obuf, uint32_t val) +{ + uint32_t tmp = htonl(val); + memcpy(obuf, &tmp, sizeof(tmp)); +} + +static void +putLong(unsigned char *obuf, int32_t val) +{ + int32_t tmp = htonl(val); + memcpy(obuf, &tmp, sizeof(tmp)); +} + +static void +putUShort(unsigned char *obuf, uint32_t val) +{ + uint16_t tmp = htons(val); + memcpy(obuf, &tmp, sizeof(tmp)); +} + +static void +putShort(unsigned char *obuf, int32_t val) +{ + int16_t tmp = htons(val); + memcpy(obuf, &tmp, sizeof(tmp)); +} +/* +static void +putUChar(unsigned char *obuf, uint32_t val) +{ + *obuf = val; +} +*/ +/* From common/tree.c */ + +isc_boolean_t +is_boolean_expression(struct element *expr) +{ + if (expr->type == ELEMENT_BOOLEAN) + return ISC_TRUE; + if (expr->type != ELEMENT_MAP) + return ISC_FALSE; + return (mapContains(expr, "check") || + mapContains(expr, "exists") || + mapContains(expr, "variable-exists") || + mapContains(expr, "equal") || + mapContains(expr, "not-equal") || + mapContains(expr, "regex-match") || + mapContains(expr, "iregex-match") || + mapContains(expr, "and") || + mapContains(expr, "or") || + mapContains(expr, "not") || + mapContains(expr, "known") || + mapContains(expr, "static")); +} + +isc_boolean_t +is_data_expression(struct element *expr) +{ + if (expr->type == ELEMENT_STRING) + return ISC_TRUE; + if (expr->type != ELEMENT_MAP) + return ISC_FALSE; + return (mapContains(expr, "substring") || + mapContains(expr, "suffix") || + mapContains(expr, "lowercase") || + mapContains(expr, "uppercase") || + mapContains(expr, "option") || + mapContains(expr, "hardware") || + mapContains(expr, "hw-type") || + mapContains(expr, "hw-address") || + mapContains(expr, "const-data") || + mapContains(expr, "packet") || + mapContains(expr, "concat") || + mapContains(expr, "encapsulate") || + mapContains(expr, "encode-int8") || + mapContains(expr, "encode-int16") || + mapContains(expr, "encode-int32") || + mapContains(expr, "gethostbyname") || + mapContains(expr, "binary-to-ascii") || + mapContains(expr, "filename") || + mapContains(expr, "server-name") || + mapContains(expr, "reverse") || + mapContains(expr, "pick-first-value") || + mapContains(expr, "host-decl-name") || + mapContains(expr, "leased-address") || + mapContains(expr, "config-option") || + mapContains(expr, "null") || + mapContains(expr, "gethostname") || + mapContains(expr, "v6relay")); +} + +isc_boolean_t +is_numeric_expression(struct element *expr) +{ + if (expr->type == ELEMENT_INTEGER) + return ISC_TRUE; + if (expr->type != ELEMENT_MAP) + return ISC_FALSE; + return (mapContains(expr, "extract-int8") || + mapContains(expr, "extract-int16") || + mapContains(expr, "extract-int32") || + mapContains(expr, "const-int") || + mapContains(expr, "lease-time") || + mapContains(expr, "add") || + mapContains(expr, "subtract") || + mapContains(expr, "multiply") || + mapContains(expr, "divide") || + mapContains(expr, "remainder") || + mapContains(expr, "binary-and") || + mapContains(expr, "binary-or") || + mapContains(expr, "binary-xor") || + mapContains(expr, "client-state")); +} +/* +static isc_boolean_t +is_compound_expression(struct element *expr) +{ + return (mapContains(expr, "substring") || + mapContains(expr, "suffix") || + mapContains(expr, "option") || + mapContains(expr, "concat") || + mapContains(expr, "encode-int8") || + mapContains(expr, "encode-int16") || + mapContains(expr, "encode-int32") || + mapContains(expr, "binary-to-ascii") || + mapContains(expr, "reverse") || + mapContains(expr, "pick-first-value") || + mapContains(expr, "config-option") || + mapContains(expr, "extract-int8") || + mapContains(expr, "extract-int16") || + mapContains(expr, "extract-int32") || + mapContains(expr, "v6relay")); +} +*/ +static enum expression_context +op_context(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_hw_type: + case expr_hw_address: + 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_v6relay: + 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; +} + +static int +op_val(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_hw_type: + case expr_hw_address: + case expr_packet: +#ifdef keep_expr_const_data_precedence + case expr_const_data: +#endif + 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_v6relay: + 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; +#ifndef keep_expr_const_data_precedence + case expr_const_data: + return 0; +#endif + } + return 100; +} + +static int +op_precedence(enum expr_op op1, enum expr_op op2) +{ + return op_val(op1) - op_val(op2); +} + +static enum expression_context +expression_context(struct element *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; + return context_any; +} + +static enum expr_op +expression(struct element *expr) +{ + if (expr->type != ELEMENT_MAP) + return expr_none; + if (mapContains(expr, "match")) + return expr_match; + if (mapContains(expr, "check")) + return expr_check; + if (mapContains(expr, "equal")) + return expr_equal; + if (mapContains(expr, "substring")) + return expr_substring; + if (mapContains(expr, "suffix")) + return expr_suffix; + if (mapContains(expr, "concat")) + return expr_concat; + if (mapContains(expr, "and")) + return expr_and; + if (mapContains(expr, "or")) + return expr_or; + if (mapContains(expr, "not")) + return expr_not; + if (mapContains(expr, "option")) + return expr_option; + if (mapContains(expr, "hardware")) + return expr_hardware; + if (mapContains(expr, "hw-type")) + return expr_hw_type; + if (mapContains(expr, "hw-address")) + return expr_hw_address; + if (mapContains(expr, "packet")) + return expr_packet; + if (mapContains(expr, "const-data")) + return expr_const_data; + if (mapContains(expr, "extract-int8")) + return expr_extract_int8; + if (mapContains(expr, "extract-int16")) + return expr_extract_int16; + if (mapContains(expr, "extract-int32")) + return expr_extract_int32; + if (mapContains(expr, "encode-int8")) + return expr_encode_int8; + if (mapContains(expr, "encode-int16")) + return expr_encode_int16; + if (mapContains(expr, "encode-int32")) + return expr_encode_int32; + if (mapContains(expr, "const-int")) + return expr_const_int; + if (mapContains(expr, "exists")) + return expr_exists; + if (mapContains(expr, "encapsulate")) + return expr_encapsulate; + if (mapContains(expr, "known")) + return expr_known; + if (mapContains(expr, "reverse")) + return expr_reverse; + if (mapContains(expr, "leased-address")) + return expr_leased_address; + if (mapContains(expr, "binary-to-ascii")) + return expr_binary_to_ascii; + if (mapContains(expr, "config-option")) + return expr_config_option; + if (mapContains(expr, "host-decl-name")) + return expr_host_decl_name; + if (mapContains(expr, "pick-first-value")) + return expr_pick_first_value; + if (mapContains(expr, "lease-time")) + return expr_lease_time; + if (mapContains(expr, "static")) + return expr_static; + if (mapContains(expr, "not-equal")) + return expr_not_equal; + if (mapContains(expr, "null")) + return expr_null; + if (mapContains(expr, "variable-exists")) + return expr_variable_exists; + if (mapContains(expr, "variable-reference")) + return expr_variable_reference; + if (mapContains(expr, "filename")) + return expr_filename; + if (mapContains(expr, "server-name")) + return expr_sname; + if (mapContains(expr, "arguments")) + return expr_arg; + if (mapContains(expr, "funcall")) + return expr_funcall; + if (mapContains(expr, "function")) + return expr_function; + if (mapContains(expr, "add")) + return expr_add; + if (mapContains(expr, "subtract")) + return expr_subtract; + if (mapContains(expr, "multiply")) + return expr_multiply; + if (mapContains(expr, "divide")) + return expr_divide; + if (mapContains(expr, "remainder")) + return expr_remainder; + if (mapContains(expr, "binary-and")) + return expr_binary_and; + if (mapContains(expr, "binary-or")) + return expr_binary_or; + if (mapContains(expr, "binary-xor")) + return expr_binary_xor; + if (mapContains(expr, "client-state")) + return expr_client_state; + if (mapContains(expr, "uppercase")) + return expr_ucase; + if (mapContains(expr, "lowercase")) + return expr_lcase; + if (mapContains(expr, "regex-match")) + return expr_regex_match; + if (mapContains(expr, "iregex-match")) + return expr_iregex_match; + if (mapContains(expr, "gethostname")) + return expr_gethostname; + if (mapContains(expr, "v6relay")) + return expr_v6relay; + if (TAILQ_EMPTY(&expr->value.map_value)) { + fprintf(stderr, "empty expression"); + if (expr->key != NULL) + fprintf(stderr, " for %s", expr->key); + } else { + struct element *item; + isc_boolean_t first = ISC_TRUE; + + TAILQ_FOREACH(item, &expr->value.map_value) { + const char *key; + + key = item->key; + if (key == NULL) + continue; + if (first) + fprintf(stderr, ": %s", key); + else + fprintf(stderr, ", %s", key); + first = ISC_FALSE; + } + } + fputs("\n", stderr); + return expr_none; +} + +int +expr_precedence(enum expr_op op, struct element *expr) +{ + if (expr->type != ELEMENT_MAP) + return op_val(op); + return op_val(op) - op_val(expression(expr)); +} diff --git a/keama/print.c b/keama/print.c new file mode 100644 index 00000000..be85bd4d --- /dev/null +++ b/keama/print.c @@ -0,0 +1,1490 @@ +/* + * Copyright (c) 2017 by Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "keama.h" + +#include <sys/errno.h> +#include <sys/types.h> +#include <arpa/inet.h> +#include <ctype.h> +#include <netdb.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +static void debug(const char* fmt, ...); + +const char * +print_expression(struct element *expr, isc_boolean_t *lose) +{ + if (expr->type == ELEMENT_BOOLEAN) + return print_boolean_expression(expr, lose); + if (expr->type == ELEMENT_INTEGER) + return print_numeric_expression(expr, lose); + if (expr->type == ELEMENT_STRING) + return print_data_expression(expr, lose); + + if (is_boolean_expression(expr)) + return print_boolean_expression(expr, lose); + if (is_numeric_expression(expr)) + return print_numeric_expression(expr, lose); + if (is_data_expression(expr)) + return print_data_expression(expr, lose); + *lose = ISC_TRUE; + return "???"; +} + +const char * +print_boolean_expression(struct element *expr, isc_boolean_t *lose) +{ + struct string *result; + + if (expr->type == ELEMENT_BOOLEAN) { + if (boolValue(expr)) + return "true"; + else + return "false"; + } + + /* + * From is_boolean_expression + */ + if (expr->type != ELEMENT_MAP) { + *lose = ISC_TRUE; + return "???"; + } + result = allocString(); + + /* check */ + if (mapContains(expr, "check")) { + struct element *name; + + appendString(result, "check "); + name = mapGet(expr, "check"); + if ((name == NULL) || (name->type != ELEMENT_STRING)) { + *lose = ISC_TRUE; + appendString(result, "???"); + } else + concatString(result, stringValue(name)); + return result->content; + } + + /* exists */ + if (mapContains(expr, "exists")) { + struct element *arg; + struct element *universe; + struct element *name; + + appendString(result, "exists "); + arg = mapGet(expr, "exists"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + universe = mapGet(arg, "universe"); + if ((universe == NULL) || (universe->type != ELEMENT_STRING)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + concatString(result, stringValue(universe)); + appendString(result, "."); + name = mapGet(arg, "name"); + if ((name == NULL) || (name->type != ELEMENT_STRING)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + concatString(result, stringValue(name)); + return result->content; + } + + /* variable-exists */ + if (mapContains(expr, "variable-exists")) { + struct element *name; + + appendString(result, "variable-exists "); + name = mapGet(expr, "variable-exists"); + if ((name == NULL) || (name->type != ELEMENT_STRING)) { + *lose = ISC_TRUE; + appendString(result, "???"); + } else + concatString(result, stringValue(name)); + return result->content; + } + + /* equal */ + if (mapContains(expr, "equal")) { + struct element *arg; + struct element *left; + struct element *right; + isc_boolean_t add_parenthesis; + + appendString(result, "equal "); + arg = mapGet(expr, "equal"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + left = mapGet(arg, "left"); + if (left == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + result = allocString(); + add_parenthesis = ISC_TF(expr_precedence(expr_equal, + left) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(left, lose)); + if (add_parenthesis) + appendString(result, ")"); + appendString(result, " = "); + right = mapGet(arg, "right"); + if (right == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + add_parenthesis = ISC_TF(expr_precedence(expr_equal, + right) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(right, lose)); + if (add_parenthesis) + appendString(result, ")"); + return result->content; + } + + /* not-equal */ + if (mapContains(expr, "not-equal")) { + struct element *arg; + struct element *left; + struct element *right; + isc_boolean_t add_parenthesis; + + appendString(result, "not-equal "); + arg = mapGet(expr, "not-equal"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + left = mapGet(arg, "left"); + if (left == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + result = allocString(); + add_parenthesis = ISC_TF(expr_precedence(expr_not_equal, + left) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(left, lose)); + if (add_parenthesis) + appendString(result, ")"); + appendString(result, " != "); + right = mapGet(arg, "right"); + if (right == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + add_parenthesis = ISC_TF(expr_precedence(expr_not_equal, + right) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(right, lose)); + if (add_parenthesis) + appendString(result, ")"); + return result->content; + } + + /* regex-match */ + if (mapContains(expr, "regex-match")) { + struct element *arg; + struct element *left; + struct element *right; + isc_boolean_t add_parenthesis; + + appendString(result, "regex-match "); + arg = mapGet(expr, "regex-match"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + left = mapGet(arg, "left"); + if (left == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + result = allocString(); + add_parenthesis = ISC_TF(expr_precedence(expr_regex_match, + left) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(left, lose)); + if (add_parenthesis) + appendString(result, ")"); + appendString(result, " ~= "); + right = mapGet(arg, "right"); + if (right == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + appendString(result, print_expression(right, lose)); + return result->content; + } + + /* iregex-match */ + if (mapContains(expr, "iregex-match")) { + struct element *arg; + struct element *left; + struct element *right; + isc_boolean_t add_parenthesis; + + appendString(result, "iregex-match "); + arg = mapGet(expr, "iregex-match"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + left = mapGet(arg, "left"); + if (left == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + result = allocString(); + add_parenthesis = ISC_TF(expr_precedence(expr_iregex_match, + left) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(left, lose)); + if (add_parenthesis) + appendString(result, ")"); + appendString(result, " ~~ "); + right = mapGet(arg, "right"); + if (right == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + appendString(result, print_expression(right, lose)); + return result->content; + } + + /* and */ + if (mapContains(expr, "and")) { + struct element *arg; + struct element *left; + struct element *right; + isc_boolean_t add_parenthesis; + + appendString(result, "and "); + arg = mapGet(expr, "and"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + left = mapGet(arg, "left"); + if (left == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + result = allocString(); + add_parenthesis = ISC_TF(expr_precedence(expr_and, + left) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(left, lose)); + if (add_parenthesis) + appendString(result, ")"); + appendString(result, " and "); + right = mapGet(arg, "right"); + if (right == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + add_parenthesis = ISC_TF(expr_precedence(expr_and, + right) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(right, lose)); + if (add_parenthesis) + appendString(result, ")"); + return result->content; + } + + /* or */ + if (mapContains(expr, "or")) { + struct element *arg; + struct element *left; + struct element *right; + isc_boolean_t add_parenthesis; + + appendString(result, "or "); + arg = mapGet(expr, "or"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + left = mapGet(arg, "left"); + if (left == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + result = allocString(); + add_parenthesis = ISC_TF(expr_precedence(expr_or, + left) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(left, lose)); + if (add_parenthesis) + appendString(result, ")"); + appendString(result, " or "); + right = mapGet(arg, "right"); + if (right == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + add_parenthesis = ISC_TF(expr_precedence(expr_or, + right) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(right, lose)); + if (add_parenthesis) + appendString(result, ")"); + return result->content; + } + + /* not */ + if (mapContains(expr, "not")) { + struct element *arg; + isc_boolean_t add_parenthesis; + + appendString(result, "not "); + arg = mapGet(expr, "not"); + if (arg == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + add_parenthesis = ISC_TF(expr_precedence(expr_not, + arg) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(arg, lose)); + if (add_parenthesis) + appendString(result, ")"); + return result->content; + } + + /* known */ + if (mapContains(expr, "known")) { + return "known"; + } + + /* static */ + if (mapContains(expr, "static")) { + return "static"; + } + + /* variable-reference */ + if (mapContains(expr, "variable-reference")) { + struct element *name; + + appendString(result, "variable-reference "); + name = mapGet(expr, "variable-reference"); + if ((name == NULL) || (name->type != ELEMENT_STRING)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + return stringValue(name)->content; + } + + /* funcall */ + if (mapContains(expr, "funcall")) { + struct element *arg; + struct element *name; + struct element *args; + size_t i; + + appendString(result, "funcall "); + arg = mapGet(expr, "funcall"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + name = mapGet(arg, "name"); + if ((name == NULL) || (name->type != ELEMENT_STRING)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + result = allocString(); + concatString(result, stringValue(name)); + appendString(result, "("); + args = mapGet(arg, "arguments"); + if ((args == NULL) || (args->type != ELEMENT_LIST)) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + for (i = 0; i < listSize(args); i++) { + struct element *item; + + if (i != 0) + appendString(result, ", "); + item = listGet(args, i); + if (item == NULL) { + debug("funcall null argument %u", + (unsigned)i); + *lose = ISC_TRUE; + appendString(result, "???"); + continue; + } + appendString(result, print_expression(item, lose)); + } + appendString(result, ")"); + return result->content; + } + + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; +} + +const char * +print_data_expression(struct element *expr, isc_boolean_t *lose) +{ + struct string *result; + + if (expr->type == ELEMENT_STRING) + return quote(stringValue(expr))->content; + + /* + * From is_data_expression + */ + if (expr->type != ELEMENT_MAP) { + *lose = ISC_TRUE; + return "???"; + } + result = allocString(); + + /* substring */ + if (mapContains(expr, "substring")) { + struct element *arg; + struct element *string; + struct element *offset; + struct element *length; + + appendString(result, "substring("); + arg = mapGet(expr, "substring"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + string = mapGet(arg, "expression"); + if (string == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_data_expression(string, lose)); + appendString(result, ", "); + offset = mapGet(arg, "offset"); + if (offset == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_numeric_expression(offset, lose)); + appendString(result, ", "); + length = mapGet(arg, "length"); + if (length == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_numeric_expression(length, lose)); + appendString(result, ")"); + return result->content; + } + + /* suffix */ + if (mapContains(expr, "suffix")) { + struct element *arg; + struct element *string; + struct element *length; + + appendString(result, "suffix("); + arg = mapGet(expr, "suffix"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + string = mapGet(arg, "expression"); + if (string == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_data_expression(string, lose)); + appendString(result, ", "); + length = mapGet(arg, "length"); + if (length == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_numeric_expression(length, lose)); + appendString(result, ")"); + return result->content; + } + + /* lowercase */ + if (mapContains(expr, "lowercase")) { + struct element *arg; + + appendString(result, "lowercase("); + arg = mapGet(expr, "lowercase"); + if (arg == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_data_expression(arg, lose)); + appendString(result, ")"); + return result->content; + } + + /* uppercase */ + if (mapContains(expr, "uppercase")) { + struct element *arg; + + appendString(result, "uppercase("); + arg = mapGet(expr, "uppercase"); + if (arg == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_data_expression(arg, lose)); + appendString(result, ")"); + return result->content; + } + + /* option */ + if (mapContains(expr, "option")) { + struct element *arg; + struct element *universe; + struct element *name; + + appendString(result, "option "); + arg = mapGet(expr, "option"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + universe = mapGet(arg, "universe"); + if ((universe == NULL) || (universe->type != ELEMENT_STRING)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + concatString(result, stringValue(universe)); + appendString(result, "."); + name = mapGet(arg, "name"); + if ((name == NULL) || (name->type != ELEMENT_STRING)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + concatString(result, stringValue(name)); + return result->content; + } + + /* hardware */ + if (mapContains(expr, "hardware")) + return "hardware"; + + /* hw-type */ + if (mapContains(expr, "hw-type")) + return "hw-type"; + + /* hw-address */ + if (mapContains(expr, "hw-address")) + return "hw-address"; + + /* const-data */ + if (mapContains(expr, "const-data")) { + struct element *arg; + + arg = mapGet(expr, "const-data"); + if ((arg == NULL) || (arg->type != ELEMENT_STRING)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + concatString(result, stringValue(arg)); + return result->content; + } + + /* packet */ + if (mapContains(expr, "packet")) { + struct element *arg; + struct element *offset; + struct element *length; + + appendString(result, "packet("); + arg = mapGet(expr, "packet"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + offset = mapGet(arg, "offset"); + if (offset == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_numeric_expression(offset, lose)); + appendString(result, ", "); + length = mapGet(arg, "length"); + if (length == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_numeric_expression(length, lose)); + appendString(result, ")"); + return result->content; + } + + /* concat */ + if (mapContains(expr, "concat")) { + struct element *arg; + struct element *left; + struct element *right; + + appendString(result, "concat("); + arg = mapGet(expr, "concat"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + left = mapGet(arg, "left"); + if (left == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_data_expression(left, lose)); + appendString(result, ", "); + right = mapGet(arg, "right"); + if (right == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_data_expression(right, lose)); + appendString(result, ")"); + return result->content; + } + + /* encapsulate */ + if (mapContains(expr, "encapsulate")) { + struct element *arg; + + appendString(result, "encapsulate "); + arg = mapGet(expr, "encapsulate"); + if (arg == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + appendString(result, print_data_expression(arg, lose)); + return result->content; + } + + /* encode-int8 */ + if (mapContains(expr, "encode-int8")) { + struct element *arg; + + appendString(result, "encode-int("); + arg = mapGet(expr, "encode-int8"); + if (arg == NULL) { + *lose = ISC_TRUE; + appendString(result, "???, 8)"); + return result->content; + } + appendString(result, print_numeric_expression(arg, lose)); + appendString(result, ", 8)"); + return result->content; + } + + /* encode-int16 */ + if (mapContains(expr, "encode-int16")) { + struct element *arg; + + appendString(result, "encode-int("); + arg = mapGet(expr, "encode-int16"); + if (arg == NULL) { + *lose = ISC_TRUE; + appendString(result, "???, 16)"); + return result->content; + } + appendString(result, print_numeric_expression(arg, lose)); + appendString(result, ", 16)"); + return result->content; + } + + /* encode-int32 */ + if (mapContains(expr, "encode-int32")) { + struct element *arg; + + appendString(result, "encode-int("); + arg = mapGet(expr, "encode-int32"); + if (arg == NULL) { + *lose = ISC_TRUE; + appendString(result, "???, 32)"); + return result->content; + } + appendString(result, print_numeric_expression(arg, lose)); + appendString(result, ", 32)"); + return result->content; + } + + /* gethostbyname */ + if (mapContains(expr, "gethostbyname")) { + struct element *arg; + + appendString(result, "gethostbyname("); + arg = mapGet(expr, "gethostbyname"); + if (arg == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + appendString(result, print_data_expression(arg, lose)); + appendString(result, ")"); + return result->content; + } + + /* binary-to-ascii */ + if (mapContains(expr, "binary-to-ascii")) { + struct element *arg; + struct element *base; + struct element *width; + struct element *separator; + struct element *buffer; + + appendString(result, "binary-to-ascii("); + arg = mapGet(expr, "binary-to-ascii"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + base = mapGet(arg, "base"); + if (base == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_numeric_expression(base, lose)); + appendString(result, ", "); + width = mapGet(arg, "width"); + if (width == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_numeric_expression(width, lose)); + appendString(result, ", "); + separator = mapGet(arg, "separator"); + if (separator == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_data_expression(separator, lose)); + appendString(result, ", "); + buffer = mapGet(arg, "buffer"); + if (buffer == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_data_expression(buffer, lose)); + appendString(result, ")"); + return result->content; + } + + /* filename */ + if (mapContains(expr, "filename")) + return "filename"; + + /* server-name */ + if (mapContains(expr, "server-name")) + return "server-name"; + + /* reverse */ + if (mapContains(expr, "reverse")) { + struct element *arg; + struct element *width; + struct element *buffer; + + appendString(result, "reverse("); + arg = mapGet(expr, "reverse"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + width = mapGet(arg, "width"); + if (width == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_numeric_expression(width, lose)); + appendString(result, ", "); + buffer = mapGet(arg, "buffer"); + if (buffer == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_data_expression(buffer, lose)); + appendString(result, ")"); + return result->content; + } + + /* pick-first-value */ + if (mapContains(expr, "pick-first-value")) { + struct element *arg; + size_t i; + + appendString(result, "pick-first-value("); + arg = mapGet(expr, "pick-first-value"); + if ((arg == NULL) || (arg->type != ELEMENT_LIST)) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + for (i = 0; i < listSize(arg); i++) { + struct element *item; + + if (i != 0) + appendString(result, ", "); + item = listGet(arg, i); + if (item == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + continue; + } + appendString(result, + print_data_expression(item, lose)); + } + appendString(result, ")"); + return result->content; + } + + /* host-decl-name */ + if (mapContains(expr, "host-decl-name")) + return "host-decl-name"; + + /* leased-address */ + if (mapContains(expr, "leased-address")) + return "leased-address"; + + /* config-option */ + if (mapContains(expr, "config-option")) { + struct element *arg; + struct element *universe; + struct element *name; + + appendString(result, "config-option "); + arg = mapGet(expr, "config-option"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + universe = mapGet(arg, "universe"); + if ((universe == NULL) || (universe->type != ELEMENT_STRING)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + concatString(result, stringValue(universe)); + appendString(result, "."); + name = mapGet(arg, "name"); + if ((name == NULL) || (name->type != ELEMENT_STRING)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + concatString(result, stringValue(name)); + return result->content; + } + + /* null */ + if (mapContains(expr, "null")) + return "null"; + + /* gethostname */ + if (mapContains(expr, "gethostname")) + return "gethostname"; + + /* v6relay */ + if (mapContains(expr, "v6relay")) { + struct element *arg; + struct element *relay; + struct element *option; + + + appendString(result, "v6relay("); + arg = mapGet(expr, "v6relay"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + relay = mapGet(arg, "relay"); + if (relay == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_numeric_expression(relay, lose)); + appendString(result, ", "); + option = mapGet(arg, "relay-option"); + if (option == NULL) { + *lose = ISC_TRUE; + appendString(result, "???" ")"); + return result->content; + } + appendString(result, print_data_expression(option, lose)); + appendString(result, ")"); + return result->content; + } + + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; +} + +const char * +print_numeric_expression(struct element *expr, isc_boolean_t *lose) +{ + struct string *result; + + if (expr->type == ELEMENT_INTEGER) { + char buf[20]; + + snprintf(buf, sizeof(buf), "%lld", (long long)intValue(expr)); + result = makeString(-1, buf); + return result->content; + } + + /* + * From is_numeric_expression + */ + if (expr->type != ELEMENT_MAP) { + *lose = ISC_TRUE; + return "???"; + } + result = allocString(); + + /* extract-int8 */ + if (mapContains(expr, "extract-int8")) { + struct element *arg; + + appendString(result, "extract-int("); + arg = mapGet(expr, "extract-int8"); + if (arg == NULL) { + *lose = ISC_TRUE; + appendString(result, "???, 8)"); + return result->content; + } + appendString(result, print_data_expression(arg, lose)); + appendString(result, ", 8)"); + return result->content; + } + + /* extract-int16 */ + if (mapContains(expr, "extract-int16")) { + struct element *arg; + + appendString(result, "extract-int("); + arg = mapGet(expr, "extract-int16"); + if (arg == NULL) { + *lose = ISC_TRUE; + appendString(result, "???, 16)"); + return result->content; + } + appendString(result, print_data_expression(arg, lose)); + appendString(result, ", 16)"); + return result->content; + } + + /* extract-int32 */ + if (mapContains(expr, "extract-int32")) { + struct element *arg; + + appendString(result, "extract-int("); + arg = mapGet(expr, "extract-int32"); + if (arg == NULL) { + *lose = ISC_TRUE; + appendString(result, "???, 32)"); + return result->content; + } + appendString(result, print_data_expression(arg, lose)); + appendString(result, ", 32)"); + return result->content; + } + + /* const-int */ + if (mapContains(expr, "const-int")) { + struct element *arg; + char buf[20]; + + arg = mapGet(expr, "const-int"); + if ((arg == NULL) || (arg->type != ELEMENT_INTEGER)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + snprintf(buf, sizeof(buf), "%lld", (long long)intValue(arg)); + result = makeString(-1, buf); + return result->content; + } + + /* lease-time */ + if (mapContains(expr, "lease-time")) + return "lease-time"; + + /* add */ + if (mapContains(expr, "add")) { + struct element *arg; + struct element *left; + struct element *right; + isc_boolean_t add_parenthesis; + + appendString(result, "add "); + arg = mapGet(expr, "add"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + left = mapGet(arg, "left"); + if (left == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + result = allocString(); + add_parenthesis = ISC_TF(expr_precedence(expr_add, + left) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(left, lose)); + if (add_parenthesis) + appendString(result, ")"); + appendString(result, " + "); + right = mapGet(arg, "right"); + if (right == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + add_parenthesis = ISC_TF(expr_precedence(expr_add, + right) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(right, lose)); + if (add_parenthesis) + appendString(result, ")"); + return result->content; + } + + /* subtract */ + if (mapContains(expr, "subtract")) { + struct element *arg; + struct element *left; + struct element *right; + isc_boolean_t add_parenthesis; + + appendString(result, "subtract "); + arg = mapGet(expr, "subtract"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + left = mapGet(arg, "left"); + if (left == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + result = allocString(); + add_parenthesis = ISC_TF(expr_precedence(expr_subtract, + left) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(left, lose)); + if (add_parenthesis) + appendString(result, ")"); + appendString(result, " - "); + right = mapGet(arg, "right"); + if (right == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + add_parenthesis = ISC_TF(expr_precedence(expr_subtract, + right) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(right, lose)); + if (add_parenthesis) + appendString(result, ")"); + return result->content; + } + + /* multiply */ + if (mapContains(expr, "multiply")) { + struct element *arg; + struct element *left; + struct element *right; + isc_boolean_t add_parenthesis; + + appendString(result, "multiply "); + arg = mapGet(expr, "multiply"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + left = mapGet(arg, "left"); + if (left == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + result = allocString(); + add_parenthesis = ISC_TF(expr_precedence(expr_multiply, + left) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(left, lose)); + if (add_parenthesis) + appendString(result, ")"); + appendString(result, " * "); + right = mapGet(arg, "right"); + if (right == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + add_parenthesis = ISC_TF(expr_precedence(expr_multiply, + right) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(right, lose)); + if (add_parenthesis) + appendString(result, ")"); + return result->content; + } + + /* divide */ + if (mapContains(expr, "divide")) { + struct element *arg; + struct element *left; + struct element *right; + isc_boolean_t add_parenthesis; + + appendString(result, "divide "); + arg = mapGet(expr, "divide"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + left = mapGet(arg, "left"); + if (left == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + result = allocString(); + add_parenthesis = ISC_TF(expr_precedence(expr_divide, + left) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(left, lose)); + if (add_parenthesis) + appendString(result, ")"); + appendString(result, " / "); + right = mapGet(arg, "right"); + if (right == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + add_parenthesis = ISC_TF(expr_precedence(expr_divide, + right) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(right, lose)); + if (add_parenthesis) + appendString(result, ")"); + return result->content; + } + + /* remainder */ + if (mapContains(expr, "remainder")) { + struct element *arg; + struct element *left; + struct element *right; + isc_boolean_t add_parenthesis; + + appendString(result, "remainder "); + arg = mapGet(expr, "remainder"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + left = mapGet(arg, "left"); + if (left == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + result = allocString(); + add_parenthesis = ISC_TF(expr_precedence(expr_remainder, + left) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(left, lose)); + if (add_parenthesis) + appendString(result, ")"); + appendString(result, " % "); + right = mapGet(arg, "right"); + if (right == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + add_parenthesis = ISC_TF(expr_precedence(expr_remainder, + right) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(right, lose)); + if (add_parenthesis) + appendString(result, ")"); + return result->content; + } + + /* binary-and */ + if (mapContains(expr, "binary-and")) { + struct element *arg; + struct element *left; + struct element *right; + isc_boolean_t add_parenthesis; + + appendString(result, "binary-and "); + arg = mapGet(expr, "binary-and"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + left = mapGet(arg, "left"); + if (left == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + result = allocString(); + add_parenthesis = ISC_TF(expr_precedence(expr_binary_and, + left) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(left, lose)); + if (add_parenthesis) + appendString(result, ")"); + appendString(result, " & "); + right = mapGet(arg, "right"); + if (right == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + add_parenthesis = ISC_TF(expr_precedence(expr_binary_and, + right) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(right, lose)); + if (add_parenthesis) + appendString(result, ")"); + return result->content; + } + + /* binary-or */ + if (mapContains(expr, "binary-or")) { + struct element *arg; + struct element *left; + struct element *right; + isc_boolean_t add_parenthesis; + + appendString(result, "binary-or "); + arg = mapGet(expr, "binary-or"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + left = mapGet(arg, "left"); + if (left == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + result = allocString(); + add_parenthesis = ISC_TF(expr_precedence(expr_binary_or, + left) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(left, lose)); + if (add_parenthesis) + appendString(result, ")"); + appendString(result, " | "); + right = mapGet(arg, "right"); + if (right == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + add_parenthesis = ISC_TF(expr_precedence(expr_binary_or, + right) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(right, lose)); + if (add_parenthesis) + appendString(result, ")"); + return result->content; + } + + /* binary-xor */ + if (mapContains(expr, "binary-xor")) { + struct element *arg; + struct element *left; + struct element *right; + isc_boolean_t add_parenthesis; + + appendString(result, "binary-xor "); + arg = mapGet(expr, "binary-xor"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + left = mapGet(arg, "left"); + if (left == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + result = allocString(); + add_parenthesis = ISC_TF(expr_precedence(expr_binary_xor, + left) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(left, lose)); + if (add_parenthesis) + appendString(result, ")"); + appendString(result, " ^ "); + right = mapGet(arg, "right"); + if (right == NULL) { + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; + } + add_parenthesis = ISC_TF(expr_precedence(expr_binary_xor, + right) < 0); + if (add_parenthesis) + appendString(result, "("); + appendString(result, print_expression(right, lose)); + if (add_parenthesis) + appendString(result, ")"); + return result->content; + } + + /* client-state */ + if (mapContains(expr, "client-state")) + return "client-state"; + + *lose = ISC_TRUE; + appendString(result, "???"); + return result->content; +} + +static void +debug(const char* fmt, ...) +{ + va_list list; + + va_start(list, fmt); + vfprintf(stderr, fmt, list); + fprintf(stderr, "\n"); + va_end(list); +} diff --git a/keama/reduce.c b/keama/reduce.c new file mode 100644 index 00000000..24939473 --- /dev/null +++ b/keama/reduce.c @@ -0,0 +1,1016 @@ +/* + * Copyright (c) 2017 by Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +#include "keama.h" + +#include <sys/errno.h> +#include <sys/types.h> +#include <arpa/inet.h> +#include <ctype.h> +#include <netdb.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +static struct element *reduce_equal_expression(struct element *left, + struct element *right); +static void debug(const char* fmt, ...); + +/* + * 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 + */ + +struct element * +reduce_boolean_expression(struct element *expr) +{ + /* trivial case: already done */ + if (expr->type == ELEMENT_BOOLEAN) + return expr; + + /* + * From is_boolean_expression + */ + + if (expr->type != ELEMENT_MAP) + return NULL; + + /* check */ + if (mapContains(expr, "check")) + /* + * syntax := { "check": <collection_name> } + * semantic: check_collection + * on server try to match classes of the collection + */ + return NULL; + + + /* exists */ + if (mapContains(expr, "exists")) { + /* + * syntax := { "exists": + * { "universe": <option_space_old>, + * "name": <option_name> } + * } + * semantic: check universe/code from incoming packet + */ + struct element *arg; + struct element *universe; + struct element *name; + struct option *option; + char result[80]; + + arg = mapGet(expr, "exists"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + debug("can't get exists argument"); + return NULL; + } + universe = mapGet(arg, "universe"); + if ((universe == NULL) || (universe->type != ELEMENT_STRING)) { + debug("can't get exists option universe"); + return NULL; + } + name = mapGet(arg, "name"); + if ((name == NULL) || (name->type != ELEMENT_STRING)) { + debug("can't get exists option name"); + return NULL; + } + option = option_lookup_name(stringValue(universe)->content, + stringValue(name)->content); + if ((option == NULL) || (option->code == 0)) + return NULL; + if (((local_family == AF_INET) && + (strcmp(option->space->name, "dhcp4") != 0)) || + ((local_family == AF_INET6) && + (strcmp(option->space->name, "dhcp6") != 0))) + return NULL; + snprintf(result, sizeof(result), + "option[%u].exists", option->code); + return createString(makeString(-1, result)); + } + + /* variable-exists */ + if (mapContains(expr, "variable-exists")) + /* + * syntax := { "variable-exists": <variable_name> } + * semantics: find_binding(scope, name) + */ + return NULL; + + /* equal */ + if (mapContains(expr, "equal")) { + /* + * syntax := { "equal": + * { "left": <expression>, + * "right": <expression> } + * } + * semantics: evaluate branches and return true + * if same type and same value + */ + struct element *arg; + struct element *left; + struct element *right; + + arg = mapGet(expr, "equal"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + debug("can't get equal argument"); + return NULL; + } + left = mapGet(arg, "left"); + if (left == NULL) { + debug("can't get equal left branch"); + return NULL; + } + right = mapGet(arg, "right"); + if (right == NULL) { + debug("can't get equal right branch"); + return NULL; + } + return reduce_equal_expression(left, right); + } + + /* not-equal */ + if (mapContains(expr, "not-equal")) { + /* + * syntax := { "not-equal": + * { "left": <expression>, + * "right": <expression> } + * } + * semantics: evaluate branches and return true + * if different type or different value + */ + struct element *arg; + struct element *left; + struct element *right; + struct element *equal; + struct string *result; + + arg = mapGet(expr, "not-equal"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + debug("can't get not-equal argument"); + return NULL; + } + left = mapGet(arg, "left"); + if (left == NULL) { + debug("can't get not-equal left branch"); + return NULL; + } + right = mapGet(arg, "right"); + if (right == NULL) { + debug("can't get not-equal right branch"); + return NULL; + } + equal = reduce_equal_expression(left, right); + if ((equal == NULL) || (equal->type != ELEMENT_STRING)) + return NULL; + result = makeString(-1, "not ("); + concatString(result, stringValue(equal)); + appendString(result, ")"); + return createString(result); + } + + /* regex-match */ + if (mapContains(expr, "regex-match")) + /* + * syntax := { "regex-match": + * { "left": <data_expression>, + * "right": <data_expression> } + * } + * semantics: evaluate branches, compile right as a + * regex and apply it to left + */ + return NULL; + + /* iregex-match */ + if (mapContains(expr, "iregex-match")) + /* + * syntax := { "regex-match": + * { "left": <data_expression>, + * "right": <data_expression> } + * } + * semantics: evaluate branches, compile right as a + * case insensistive regex and apply it to left + */ + return NULL; + + /* and */ + if (mapContains(expr, "and")) { + /* + * syntax := { "and": + * { "left": <boolean_expression>, + * "right": <boolean_expression> } + * } + * semantics: evaluate branches, return true + * if both are true + */ + struct element *arg; + struct element *left; + struct element *right; + struct string *result; + + arg = mapGet(expr, "and"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + debug("can't get and argument"); + return NULL; + } + left = mapGet(arg, "left"); + if (left == NULL) { + debug("can't get and left branch"); + return NULL; + } + right = mapGet(arg, "right"); + if (right == NULL) { + debug("can't get and right branch"); + return NULL; + } + left = reduce_boolean_expression(left); + if ((left == NULL) || (left->type != ELEMENT_STRING)) + return NULL; + right = reduce_boolean_expression(right); + if ((right == NULL) || (right->type != ELEMENT_STRING)) + return NULL; + result = makeString(-1, "("); + concatString(result, stringValue(left)); + appendString(result, ") and ("); + concatString(result, stringValue(right)); + appendString(result, ")"); + return createString(result); + } + + /* or */ + if (mapContains(expr, "or")) { + /* + * syntax := { "or": + * { "left": <boolean_expression>, + * "right": <boolean_expression> } + * } + * semantics: evaluate branches, return true + * if any is true + */ + struct element *arg; + struct element *left; + struct element *right; + struct string *result; + + arg = mapGet(expr, "or"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + debug("can't get or argument"); + return NULL; + } + left = mapGet(arg, "left"); + if (left == NULL) { + debug("can't get or left branch"); + return NULL; + } + right = mapGet(arg, "right"); + if (right == NULL) { + debug("can't get or right branch"); + return NULL; + } + left = reduce_boolean_expression(left); + if ((left == NULL) || (left->type != ELEMENT_STRING)) + return NULL; + right = reduce_boolean_expression(right); + if ((right == NULL) || (right->type != ELEMENT_STRING)) + return NULL; + result = makeString(-1, "("); + concatString(result, stringValue(left)); + appendString(result, ") or ("); + concatString(result, stringValue(right)); + appendString(result, ")"); + return createString(result); + } + + /* not */ + if (mapContains(expr, "not")) { + /* + * syntax := { "not": <boolean_expression> } + * semantic: evaluate its branch and return its negation + */ + struct element *arg; + struct string *result; + + arg = mapGet(expr, "not"); + if (arg == NULL) { + debug("can't get not argument"); + return NULL; + } + arg = reduce_boolean_expression(arg); + if ((arg == NULL) || (arg->type != ELEMENT_STRING)) + return NULL; + result = makeString(-1, "not ("); + concatString(result, stringValue(arg)); + appendString(result, ")"); + return createString(result); + } + + /* known */ + if (mapContains(expr, "known")) + /* + * syntax := { "known": null } + * semantics: client is known, i.e., has a matching + * host declaration (aka reservation in Kea) + */ + return NULL; + + /* static */ + if (mapContains(expr, "static")) + /* + * syntax := { "static": null } + * semantics: lease is static (doesn't exist in Kea) + */ + return NULL; + + return NULL; +} + +/* + * 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 | + * V6RELAY LPAREN numeric-expression COMMA + * data-expression RPAREN | + * STRING | + * colon_separated_hex_list + */ + +struct element * +reduce_data_expression(struct element *expr) +{ + /* trivial case: already done */ + if (expr->type == ELEMENT_STRING) + return expr; + + /* + * From is_data_expression + */ + + if (expr->type != ELEMENT_MAP) + return NULL; + + /* substring */ + if (mapContains(expr, "substring")) { + /* + * syntax := { "substring": + * { "expression": <data_expression>, + * "offset": <numeric_expression>, + * "length": <numeric_expression> } + * } + * semantic: evaluate arguments, if the string is + * shorter than offset return "" else return substring + */ + struct element *arg; + struct element *string; + struct element *offset; + struct element *length; + struct string *result; + int64_t off; + int64_t len; + char buf[80]; + + arg = mapGet(expr, "substring"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + debug("can't get substring argument"); + return NULL; + } + string = mapGet(arg, "expression"); + if (string == NULL) { + debug("can't get substring expression"); + return NULL; + } + offset = mapGet(arg, "offset"); + if (offset == NULL) { + debug("can't get substring offset"); + return NULL; + } + length = mapGet(arg, "length"); + if (length == NULL) { + debug("can't get substring length"); + return NULL; + } + /* can't be a literal as it was evaluated before */ + string = reduce_data_expression(string); + if ((string == NULL) || (string->type != ELEMENT_STRING)) + return NULL; + offset = reduce_numeric_expression(offset); + if ((offset == NULL) || (offset->type != ELEMENT_INTEGER)) + return NULL; + off = intValue(offset); + if (off < 0) { + debug("substring with a negative offset (%lld)", + (long long)off); + return NULL; + } + length = reduce_numeric_expression(length); + if ((length == NULL) || (length->type != ELEMENT_INTEGER)) + return NULL; + len = intValue(length); + if (len < 0) { + debug("substring with a negative length (%lld)", + (long long)len); + return NULL; + } + result = makeString(-1, "substring("); + concatString(result, stringValue(string)); + snprintf(buf, sizeof(buf), + ",%u,%u)", (unsigned)off, (unsigned)len); + appendString(result, buf); + return createString(result); + } + + /* suffix */ + if (mapContains(expr, "suffix")) { + /* + * syntax := { "suffix": + * { "expression": <data_expression>, + * "length": <numeric_expression> } + * } + * semantic: evaluate arguments, if the string is + * shorter than length return it else return suffix + */ + struct element *arg; + struct element *string; + struct element *length; + struct string *result; + int64_t len; + char buf[80]; + + arg = mapGet(expr, "suffix"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + debug("can't get suffix argument"); + return NULL; + } + string = mapGet(arg, "expression"); + if (string == NULL) { + debug("can't get suffix expression"); + return NULL; + } + length = mapGet(arg, "length"); + if (length == NULL) { + debug("can't get suffix length"); + return NULL; + } + /* can't be a literal as it was evaluated before */ + string = reduce_data_expression(string); + if ((string == NULL) || (string->type != ELEMENT_STRING)) + return NULL; + length = reduce_numeric_expression(length); + if ((length == NULL) || (length->type != ELEMENT_INTEGER)) + return NULL; + len = intValue(length); + if (len < 0) { + debug("suffix with a negative length (%lld)", + (long long)len); + return NULL; + } + result = makeString(-1, "substring("); + concatString(result, stringValue(string)); + snprintf(buf, sizeof(buf), ",-%u,all)", (unsigned)len); + appendString(result, buf); + return createString(result); + } + + /* lowercase */ + if (mapContains(expr, "lowercase")) + /* + * syntax := { "lowercase": <data_expression> } + * semantic: evaluate its argument and apply tolower to + * its content + */ + return NULL; + + /* uppercase */ + if (mapContains(expr, "uppercase")) + /* + * syntax := { "uppercase": <data_expression> } + * semantic: evaluate its argument and apply toupper to + * its content + */ + return NULL; + + /* option */ + if (mapContains(expr, "option")) { + /* + * syntax := { "option": + * { "universe": <option_space_old>, + * "name": <option_name> } + * } + * semantic: get universe/code option from incoming packet + */ + struct element *arg; + struct element *universe; + struct element *name; + struct option *option; + char result[80]; + + arg = mapGet(expr, "option"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + debug("can't get option argument"); + return NULL; + } + universe = mapGet(arg, "universe"); + if ((universe == NULL) || (universe->type != ELEMENT_STRING)) { + debug("can't get option universe"); + return NULL; + } + name = mapGet(arg, "name"); + if ((name == NULL) || (name->type != ELEMENT_STRING)) { + debug("can't get option name"); + return NULL; + } + option = option_lookup_name(stringValue(universe)->content, + stringValue(name)->content); + if ((option == NULL) || (option->code == 0)) + return NULL; + if (((local_family == AF_INET) && + (strcmp(option->space->name, "dhcp4") != 0)) || + ((local_family == AF_INET6) && + (strcmp(option->space->name, "dhcp6") != 0))) + return NULL; + snprintf(result, sizeof(result), + "option[%u].hex", option->code); + return createString(makeString(-1, result)); + } + + /* hardware */ + if (mapContains(expr, "hardware")) { + /* + * syntax := { "hardware": null } + * semantic: get mac type and address from incoming packet + */ + struct string *result; + + if (local_family != AF_INET) { + debug("get hardware for DHCPv6"); + return NULL; + } + result = makeString(-1, + "concat(substring(pkt4.htype,-1,all),pkt4.mac)"); + return createString(result); + } + + /* hw-type */ + if (mapContains(expr, "hw-type")) { + /* + * ADDED + * syntax := { "hw-type": null } + * semantic: get mac type from incoming packet + */ + struct string *result; + + if (local_family != AF_INET) { + debug("get hw-type for DHCPv6"); + return NULL; + } + result = makeString(-1, "substring(pkt4.htype,-1,all)"); + return createString(result); + } + + /* hw-address */ + if (mapContains(expr, "hw-address")) { + /* + * ADDED + * syntax := { "hw-address": null } + * semantic: get mac address from incoming packet + */ + struct string *result; + + if (local_family != AF_INET) { + debug("get hw-address for DHCPv6"); + return NULL; + } + result = makeString(-1, "pkt4.mac"); + return createString(result); + } + + /* const-data */ + if (mapContains(expr, "const-data")) { + /* + * syntax := { "const-data": <string> } + * semantic: embedded string value + */ + struct element *arg; + + arg = mapGet(expr, "const-data"); + if ((arg == NULL) || (arg->type != ELEMENT_STRING)) { + debug("can't get const-data argument"); + return NULL; + } + return createString(stringValue(arg)); + } + + /* packet */ + if (mapContains(expr, "packet")) + /* + * syntax := { "packet": + * { "offset": <numeric_expression>, + * "length": <numeric_expression> } + * } + * semantic: return the selected substring of the incoming + * packet content + */ + return NULL; + + /* concat */ + if (mapContains(expr, "concat")) { + /* + * syntax := { "concat": + * { "left": <data_expression>, + * "right": <data_expression> } + * } + * semantic: evaluate arguments and return the concatenation + */ + struct element *arg; + struct element *left; + struct element *right; + struct string *result; + + arg = mapGet(expr, "concat"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + debug("can't get concat argument"); + return NULL; + } + left = mapGet(arg, "left"); + if (left == NULL) { + debug("can't get concat left branch"); + return NULL; + } + right = mapGet(arg, "right"); + if (right == NULL) { + debug("can't get concat right branch"); + return NULL; + } + /* left is a literal case */ + if (left->type == ELEMENT_STRING) { + /* can't be a literal as it was evaluated before */ + right = reduce_data_expression(right); + if ((right == NULL) || (right->type != ELEMENT_STRING)) + return NULL; + result = makeString(-1, "concat("); + concatString(result, quote(stringValue(left))); + appendString(result, ", "); + concatString(result, stringValue(right)); + appendString(result, ")"); + return createString(result); + } + left = reduce_data_expression(left); + if ((left == NULL) || (left->type != ELEMENT_STRING)) + return NULL; + /* right is a literal case */ + if (right->type == ELEMENT_STRING) { + /* literal left was handled before */ + result = makeString(-1, "concat("); + concatString(result, stringValue(left)); + appendString(result, ", "); + concatString(result, quote(stringValue(right))); + appendString(result, ")"); + return createString(result); + } + right = reduce_data_expression(right); + if ((right == NULL) || (right->type != ELEMENT_STRING)) + return NULL; + result = makeString(-1, "concat("); + concatString(result, stringValue(left)); + appendString(result, ", "); + concatString(result, stringValue(right)); + appendString(result, ")"); + return createString(result); + } + + /* encapsulate */ + if (mapContains(expr, "encapsulate")) + /* + * syntax := { "encapsulate": <encapsulated_space> } + * semantic: encapsulate options of the given space + */ + return NULL; + + /* encode-int8 */ + if (mapContains(expr, "encode-int8")) + /* + * syntax := { "encode-int8": <numeric_expression> } + * semantic: return a string buffer with the evaluated + * number as content + */ + return NULL; + + /* encode-int16 */ + if (mapContains(expr, "encode-int16")) + /* + * syntax := { "encode-int16": <numeric_expression> } + * semantic: return a string buffer with the evaluated + * number as content + */ + return NULL; + + /* encode-int32 */ + if (mapContains(expr, "encode-int32")) + /* + * syntax := { "encode-int32": <numeric_expression> } + * semantic: return a string buffer with the evaluated + * number as content + */ + return NULL; + + /* gethostbyname */ + if (mapContains(expr, "gethostbyname")) + /* + * syntax := { "gethostbyname": <string> } + * semantic: call gethostbyname and return + * a binary buffer with addresses + */ + return NULL; + + /* binary-to-ascii */ + if (mapContains(expr, "binary-to-ascii")) + /* + * syntax := { "binary-to-ascii": + * { "base": <numeric_expression 2..16>, + * "width": <numeric_expression 8, 16 or 32>, + * "separator": <data_expression>, + * "buffer": <data_expression> } + * } + * semantic: split the input buffer into int8/16/32 numbers, + * output them separated by the given string + */ + return NULL; + + /* filename */ + if (mapContains(expr, "filename")) + /* + * syntax := { "filename": null } + * semantic: get filename field from incoming DHCPv4 packet + */ + return NULL; + + /* server-name */ + if (mapContains(expr, "server-name")) + /* + * syntax := { "server-name": null } + * semantic: get server-name field from incoming DHCPv4 packet + */ + return NULL; + + /* reverse */ + if (mapContains(expr, "reverse")) + /* + * syntax := { "reverse": + * { "width": <numeric_expression>, + * "buffer": <data_expression> } + * } + * semantic: reverse the input buffer by width chunks of bytes + */ + return NULL; + + /* pick-first-value */ + if (mapContains(expr, "pick-first-value")) + /* + * syntax := { "pick-first-value": + * [ <data_expression>, ... ] + * } + * semantic: evaluates expressions and return the first + * not null, return null if all are null + */ + return NULL; + + /* host-decl-name */ + if (mapContains(expr, "host-decl-name")) + /* + * syntax := { "host-decl-name": null } + * semantic: return the name of the matching host + * declaration (aka revervation in kea) or null + */ + return NULL; + + /* leased-address */ + if (mapContains(expr, "leased-address")) + /* + * syntax := { "leased-address": null } + * semantic: return the address of the assigned lease or + * log a message + */ + return NULL; + + /* config-option */ + if (mapContains(expr, "config-option")) + /* + * syntax := { "config-option": + * { "universe": <option_space_old>, + * "name": <option_name> } + * } + * semantic: get universe/code option to send + */ + return NULL; + + /* null */ + if (mapContains(expr, "null")) { + /* + * syntax := { "null": null } + * semantic: return null + */ + debug("unexpected null: this expression was not evaluated"); + return NULL; + } + + /* gethostname */ + if (mapContains(expr, "gethostname")) { + /* + * syntax := { "gethostname": null } + * semantic: return gethostname + */ + debug("unexpected gethostname: this expression was not " + "evaluated"); + return NULL; + } + + /* v6relay */ + if (mapContains(expr, "v6relay")) { + /* + * syntax := { "v6relay": + * { "relay": <numeric_expression>, + * "relay-option" <data_expression> } + * } + * semantic: relay is a counter from client, 0 is no-op, + * 1 is the relay closest to the client, etc, option + * is a dhcp6 option ans is return when found + */ + struct element *arg; + struct element *relay; + struct element *universe; + struct element *name; + struct option *option; + int64_t r; + char result[100]; + + if (local_family != AF_INET6) { + debug("get v6relay for DHCPv4"); + return NULL; + } + arg = mapGet(expr, "v6relay"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + debug("can't get v6relay argument"); + return NULL; + } + relay = mapGet(arg, "relay"); + if (relay == NULL) { + debug("can't get v6relay relay"); + return NULL; + } + relay = reduce_numeric_expression(relay); + if ((relay == NULL) || (relay->type != ELEMENT_INTEGER)) + return NULL; + r = intValue(relay); + if (r < 0) { + debug("v6relay called with illegal relay (%lld)", + (long long)r); + return NULL; + } + arg = mapGet(arg, "relay-option"); + if ((arg == NULL) || (arg->type != ELEMENT_MAP)) { + debug("can't get v6relay relay-option"); + return NULL; + } + universe = mapGet(arg, "universe"); + if ((universe == NULL) || (universe->type != ELEMENT_STRING)) { + debug("can't get v6relay option universe"); + NULL; + } + name = mapGet(arg, "name"); + if ((name == NULL) || (name->type != ELEMENT_STRING)) { + debug("can't get v6relay option name"); + return NULL; + } + option = option_lookup_name(stringValue(universe)->content, + stringValue(name)->content); + if ((option == NULL) || (option->code == 0) || + (strcmp(option->space->name, "dhcp6") != 0)) + return NULL; + if (r == 0) + snprintf(result, sizeof(result), + "option[%u].hex", option->code); + else { + /* r > MAX_V6RELAY_HOPS means the relay closest + to server */ + if (r > MAX_V6RELAY_HOPS) + r = 0; + /* Kea counts from the server, use negative nesting + levels to count from the client */ + snprintf(result, sizeof(result), + "relay6[%d].option[%u].hex", + (int)-r, option->code); + } + return createString(makeString(-1, result)); + } + + return NULL; +} + +struct element * +reduce_numeric_expression(struct element *expr) +{ + /* trivial case: already done */ + if (expr->type == ELEMENT_INTEGER) + return expr; + + if (expr->type != ELEMENT_MAP) + return NULL; + + /* Kea has no numeric operators... */ + return NULL; +} + +static struct element * +reduce_equal_expression(struct element *left, struct element *right) +{ + struct string *result; + + /* + * numeric case was handled by evaluation + */ + + if (!is_data_expression(left) || !is_data_expression(right)) + return NULL; + + /* left is a literal case */ + if (left->type == ELEMENT_STRING) { + /* can't be a literal as it was evaluated before */ + right = reduce_data_expression(right); + if ((right == NULL) || (right->type != ELEMENT_STRING)) + return NULL; + result = allocString(); + concatString(result, quote(stringValue(left))); + appendString(result, " == "); + concatString(result, stringValue(right)); + return createString(result); + } + left = reduce_data_expression(left); + if ((left == NULL) || (left->type != ELEMENT_STRING)) + return NULL; + + /* right is a literal case */ + if (right->type == ELEMENT_STRING) { + /* literal left was handled before */ + result = allocString(); + concatString(result, stringValue(left)); + appendString(result, " == "); + concatString(result, quote(stringValue(right))); + return createString(result); + } + right = reduce_data_expression(right); + if ((right == NULL) || (right->type != ELEMENT_STRING)) + return NULL; + + result = allocString(); + concatString(result, stringValue(left)); + appendString(result, " == "); + concatString(result, stringValue(right)); + return createString(result); +} + +static void +debug(const char* fmt, ...) +{ + va_list list; + + va_start(list, fmt); + vfprintf(stderr, fmt, list); + fprintf(stderr, "\n"); + va_end(list); +} diff --git a/keama/tests/README b/keama/tests/README new file mode 100644 index 00000000..69686155 --- /dev/null +++ b/keama/tests/README @@ -0,0 +1,42 @@ +Tests are dividing on error vs working, and DHCPv4 vs DHCPv6. + +Names of files about test xyz have xyz as body and an extension. + +Extensions: + - .err4 = source for error test in DHCPv4 + - .errF = source for error test in DHCPv4 with -r fatal + - .errP = source for error test in DHCPv4 with -r pass + - .err6 = source for error test in DHCPv6 + - .err = source for error test in DHCPv4 and DHCPv6 + - .msg = resultat (first line of standard error) for error test + - .in4 = source for working test in DHCPv4 + - .in6 = source for working test in DHCPv6 + - .ind = source for working test in DHCPv4 with -D + - .inD = source for working test in DHCPv6 with -D + - .inn = source for working test in DHCPv4 with -N + - .inN = source for working test in DHCPv6 with -N + - .inl = source for working test in DHCPv4 with -l $HOOK + - .inL = source for working test in DHCPv6 with -l $HOOK + - .outl = resultat for working test with default hook library path + - .outL = resultat for working test with default hook library path + - .out = resultat for working test +There is no working test in both DHCPv4 and DHCPv6. +The body of the name of a working test must include 4 or 6 so +the .out can be submitted to kea-dhcp4 or kea-dhcp6 + +runone.sh xyz.ext + -> run the xyz test +runall.sh + -> run all tests + +Check output syntax with kea-dhcp4 and kea-dhcp6: + - Set KEA4 and KEA6 environment variables to kea-dhcp4 and kea-dhcp6 + - Set HOOK to a place to find hooks (currently libdhcp_flex_id.so), + please use the directory name with a trailing / + - The en0 interface is supposed to exist (or replace "en0" in all files) + - Note that runall.sh must be run before checkall.sh + +checkone.sh xyz.out + -> check the syntax of xyz.out +checkall.sh + -> check the syntax of all .out files diff --git a/keama/tests/badcasexsc.err b/keama/tests/badcasexsc.err new file mode 100644 index 00000000..9cfdd87c --- /dev/null +++ b/keama/tests/badcasexsc.err @@ -0,0 +1,7 @@ +# bad case executable statement construct + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# a raw default statement +case "foo": break; diff --git a/keama/tests/badcasexsc.msg b/keama/tests/badcasexsc.msg new file mode 100644 index 00000000..636bb31a --- /dev/null +++ b/keama/tests/badcasexsc.msg @@ -0,0 +1 @@ +badcasexsc.err line 7: case statement in inappropriate scope. diff --git a/keama/tests/badclass.err b/keama/tests/badclass.err new file mode 100644 index 00000000..8dc59c05 --- /dev/null +++ b/keama/tests/badclass.err @@ -0,0 +1,12 @@ +# bad (double match-if) class declaration config + +# options +option mysystem code 250 = text; +option myversion code 251 = unsigned integer 16; + +# class declaration +class "foobar" { + match if option mysystem = "version1"; + match if option mysystem = "version2"; + option myversion 1; +} diff --git a/keama/tests/badclass.msg b/keama/tests/badclass.msg new file mode 100644 index 00000000..69a603ab --- /dev/null +++ b/keama/tests/badclass.msg @@ -0,0 +1 @@ +badclass.err line 10: A class may only have one 'match if' clause. diff --git a/keama/tests/badclass2.err b/keama/tests/badclass2.err new file mode 100644 index 00000000..551a1729 --- /dev/null +++ b/keama/tests/badclass2.err @@ -0,0 +1,12 @@ +# bad (2 match) class declaration config + +# options +option mysystem code 250 = text; +option myversion code 251 = unsigned integer 16; + +# superclass declaration +class "foobar" { + match option mysystem; + match option myversion; + option myversion 1; +} diff --git a/keama/tests/badclass2.msg b/keama/tests/badclass2.msg new file mode 100644 index 00000000..3afd2b3b --- /dev/null +++ b/keama/tests/badclass2.msg @@ -0,0 +1 @@ +badclass2.err line 10: can't override existing submatch/spawn diff --git a/keama/tests/baddecl2array.err b/keama/tests/baddecl2array.err new file mode 100644 index 00000000..80701141 --- /dev/null +++ b/keama/tests/baddecl2array.err @@ -0,0 +1,4 @@ +# bad 2a (array in array) option declaration + +option space foobar; +option foobar.fmt-Bat code 1 = array of array of unsigned integer 8; diff --git a/keama/tests/baddecl2array.msg b/keama/tests/baddecl2array.msg new file mode 100644 index 00000000..e7ad089e --- /dev/null +++ b/keama/tests/baddecl2array.msg @@ -0,0 +1 @@ +baddecl2array.err line 4: no nested arrays. diff --git a/keama/tests/baddecl2record.err b/keama/tests/baddecl2record.err new file mode 100644 index 00000000..d237b636 --- /dev/null +++ b/keama/tests/baddecl2record.err @@ -0,0 +1,4 @@ +# bad 2r (record in record) option declaration + +option space foobar; +option foobar.fmt-Bat code 1 = { unsigned integer 8, { text } }; diff --git a/keama/tests/baddecl2record.msg b/keama/tests/baddecl2record.msg new file mode 100644 index 00000000..4dfb2082 --- /dev/null +++ b/keama/tests/baddecl2record.msg @@ -0,0 +1 @@ +baddecl2record.err line 4: unknown data type { diff --git a/keama/tests/baddeclBt.err b/keama/tests/baddeclBt.err new file mode 100644 index 00000000..921521f2 --- /dev/null +++ b/keama/tests/baddeclBt.err @@ -0,0 +1,4 @@ +# bad Bt (not in record) option declaration + +option space foobar; +option foobar.fmt-Bat code 1 = unsigned integer 8, text; diff --git a/keama/tests/baddeclBt.msg b/keama/tests/baddeclBt.msg new file mode 100644 index 00000000..49c63a58 --- /dev/null +++ b/keama/tests/baddeclBt.msg @@ -0,0 +1 @@ +baddeclBt.err line 4: semicolon expected. diff --git a/keama/tests/baddefaultxsc.err b/keama/tests/baddefaultxsc.err new file mode 100644 index 00000000..69157dd4 --- /dev/null +++ b/keama/tests/baddefaultxsc.err @@ -0,0 +1,7 @@ +# bad default executable statement construct + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# a raw default statement +default: break; diff --git a/keama/tests/baddefaultxsc.msg b/keama/tests/baddefaultxsc.msg new file mode 100644 index 00000000..7780a1bb --- /dev/null +++ b/keama/tests/baddefaultxsc.msg @@ -0,0 +1 @@ +baddefaultxsc.err line 7: switch default statement in inappropriate scope. diff --git a/keama/tests/baddomain.notyet b/keama/tests/baddomain.notyet new file mode 100644 index 00000000..a38f21c9 --- /dev/null +++ b/keama/tests/baddomain.notyet @@ -0,0 +1,6 @@ +# bad domain (label too long) option data + +option space foobar; +option foobar.fmt-d code 1 = domain-name; +option foobar.fmt-d + this-label-is-very-very-very-very-very-very-long-so-it-is-too-long.us; diff --git a/keama/tests/badduid.err b/keama/tests/badduid.err new file mode 100644 index 00000000..c3acab31 --- /dev/null +++ b/keama/tests/badduid.err @@ -0,0 +1,4 @@ +# bad (no type) server duid config + +# server duid declaration +server-duid "enterprise-specific-identifier-1234"; diff --git a/keama/tests/badduid.msg b/keama/tests/badduid.msg new file mode 100644 index 00000000..ebcaade4 --- /dev/null +++ b/keama/tests/badduid.msg @@ -0,0 +1 @@ +badduid.err line 4: DUID type of LLT, EN, or LL expected diff --git a/keama/tests/badinclude.err b/keama/tests/badinclude.err new file mode 100644 index 00000000..7ab310ff --- /dev/null +++ b/keama/tests/badinclude.err @@ -0,0 +1,3 @@ +# bad include config + +include 192.168.0.1; diff --git a/keama/tests/badinclude.msg b/keama/tests/badinclude.msg new file mode 100644 index 00000000..e7ebf997 --- /dev/null +++ b/keama/tests/badinclude.msg @@ -0,0 +1 @@ +badinclude.err line 3: filename string expected. diff --git a/keama/tests/badoption66.err6 b/keama/tests/badoption66.err6 new file mode 100644 index 00000000..a972d2f2 --- /dev/null +++ b/keama/tests/badoption66.err6 @@ -0,0 +1,3 @@ +# bad '6' (Ipv6 address) option data + +option dhcp6.unicast "2001::1"; diff --git a/keama/tests/badoption66.msg b/keama/tests/badoption66.msg new file mode 100644 index 00000000..227ce94d --- /dev/null +++ b/keama/tests/badoption66.msg @@ -0,0 +1 @@ +badoption66.err6 line 3: Invalid IPv6 address. diff --git a/keama/tests/badoptionD6.notyet b/keama/tests/badoptionD6.notyet new file mode 100644 index 00000000..daf9541c --- /dev/null +++ b/keama/tests/badoptionD6.notyet @@ -0,0 +1,4 @@ +# bad 'D' (domain-list) option data + +option dhcp6.domain-search example.com; + diff --git a/keama/tests/badoptionDc4.notyet b/keama/tests/badoptionDc4.notyet new file mode 100644 index 00000000..f56fb46a --- /dev/null +++ b/keama/tests/badoptionDc4.notyet @@ -0,0 +1,3 @@ +# bad 'Dc' (domain-list compressed) option data + +option domain-search example.com; diff --git a/keama/tests/badoptionI4.err4 b/keama/tests/badoptionI4.err4 new file mode 100644 index 00000000..b0f6331d --- /dev/null +++ b/keama/tests/badoptionI4.err4 @@ -0,0 +1,3 @@ +# bad 'I' (IPv4 address) option data + +option swap-server "10.5.5.1"; diff --git a/keama/tests/badoptionI4.msg b/keama/tests/badoptionI4.msg new file mode 100644 index 00000000..6609975d --- /dev/null +++ b/keama/tests/badoptionI4.msg @@ -0,0 +1 @@ +badoptionI4.err4 line 3: 10.5.5.1 (262): expecting IP address or hostname diff --git a/keama/tests/badoptiond4.err4 b/keama/tests/badoptiond4.err4 new file mode 100644 index 00000000..005c26cd --- /dev/null +++ b/keama/tests/badoptiond4.err4 @@ -0,0 +1,5 @@ +# bad d (domain-name) option data + +option space foobar; +option foobar.fmt-d code 13 = domain-name; +option foobar.fmt-d "www.example.com"; diff --git a/keama/tests/badoptiond4.msg b/keama/tests/badoptiond4.msg new file mode 100644 index 00000000..e1f833e7 --- /dev/null +++ b/keama/tests/badoptiond4.msg @@ -0,0 +1 @@ +badoptiond4.err4 line 5: not a valid domain name. diff --git a/keama/tests/badstatusdir.err b/keama/tests/badstatusdir.err new file mode 100644 index 00000000..aab8fd56 --- /dev/null +++ b/keama/tests/badstatusdir.err @@ -0,0 +1,7 @@ +# Bad status directive + +option space foobar; +option foobar.opt1 code 1 = text; + +# unknown/unknown does not make sense +% option foobar.opt1 unknown unknown; diff --git a/keama/tests/badstatusdir.msg b/keama/tests/badstatusdir.msg new file mode 100644 index 00000000..d020cee4 --- /dev/null +++ b/keama/tests/badstatusdir.msg @@ -0,0 +1 @@ +badstatusdir.err line 7: illicit combination: option foobar.opt1 code 1 is known by nobody diff --git a/keama/tests/badsubclass.err b/keama/tests/badsubclass.err new file mode 100644 index 00000000..38c817b7 --- /dev/null +++ b/keama/tests/badsubclass.err @@ -0,0 +1,11 @@ +# bad (selector type) subclass declaration config + +# superclass declaration +class "foobar" { + match substring(option vendor-class-identifier, 0, 3); +} + +# subclass declaration +subclass "foobar" true { + default-lease-time 1800; +} diff --git a/keama/tests/badsubclass.msg b/keama/tests/badsubclass.msg new file mode 100644 index 00000000..a0a85028 --- /dev/null +++ b/keama/tests/badsubclass.msg @@ -0,0 +1 @@ +badsubclass.err line 9: Expecting string or hex list. diff --git a/keama/tests/bintadx6.in6 b/keama/tests/bintadx6.in6 new file mode 100644 index 00000000..dc0cede7 --- /dev/null +++ b/keama/tests/bintadx6.in6 @@ -0,0 +1,9 @@ +# binary-to-ascii data expression + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# reduce literals +class "literal" { + match if option dhcp6.client-data = binary-to-ascii(16, 8, "-", "\007foo"); +} diff --git a/keama/tests/bintadx6.out b/keama/tests/bintadx6.out new file mode 100644 index 00000000..999e3602 --- /dev/null +++ b/keama/tests/bintadx6.out @@ -0,0 +1,15 @@ +{ + # binary-to-ascii data expression + # empty configs are not accepted by Kea + "Dhcp6": { + "valid-lifetime": 1800, + "client-classes": [ + # reduce literals + { + "name": "literal", + /// from: match if (option dhcp6.client-data) = (binary-to-ascii(16, 8, '-', 'foo')) + "test": "option[45].hex == '7-66-6f-6f'" + } + ] + } +} diff --git a/keama/tests/bootfilename4.in4 b/keama/tests/bootfilename4.in4 new file mode 100644 index 00000000..5b6daeba --- /dev/null +++ b/keama/tests/bootfilename4.in4 @@ -0,0 +1,4 @@ +# Gitlab #22 dhcp4 option 67 wrong name + +option bootfile-name "boot.pxe"; + diff --git a/keama/tests/bootfilename4.out b/keama/tests/bootfilename4.out new file mode 100644 index 00000000..a08cb625 --- /dev/null +++ b/keama/tests/bootfilename4.out @@ -0,0 +1,13 @@ +{ + # Gitlab #22 dhcp4 option 67 wrong name + "Dhcp4": { + "option-data": [ + { + "space": "dhcp4", + "name": "boot-file-name", + "code": 67, + "data": "boot.pxe" + } + ] + } +} diff --git a/keama/tests/charcasedx4.in4 b/keama/tests/charcasedx4.in4 new file mode 100644 index 00000000..dd9ba03e --- /dev/null +++ b/keama/tests/charcasedx4.in4 @@ -0,0 +1,9 @@ +# lcase/ucase data expression + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# reduce literals +class "literal" { + match if option host-name = concat(ucase("www."), "example.", lcase("COM")); +} diff --git a/keama/tests/charcasedx4.out b/keama/tests/charcasedx4.out new file mode 100644 index 00000000..fcdc20d1 --- /dev/null +++ b/keama/tests/charcasedx4.out @@ -0,0 +1,15 @@ +{ + # lcase/ucase data expression + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800, + "client-classes": [ + # reduce literals + { + "name": "literal", + /// from: match if (option dhcp.host-name) = (concat(uppercase('www.'), concat('example.', lowercase('COM')))) + "test": "option[12].hex == 'WWW.example.com'" + } + ] + } +} diff --git a/keama/tests/checkall.sh b/keama/tests/checkall.sh new file mode 100644 index 00000000..edeffdcb --- /dev/null +++ b/keama/tests/checkall.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +#set -x + +cd "$(dirname "$0")" + +log=/tmp/log +rm $log 2> /dev/null + +for t in *.out +do + /bin/sh checkone.sh $t + if [ $? -ne 0 ]; then + echo `basename $t` >> $log + fi +done diff --git a/keama/tests/checkone.sh b/keama/tests/checkone.sh new file mode 100644 index 00000000..cbd5d9fd --- /dev/null +++ b/keama/tests/checkone.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +#set -x + +if [ $# -ne 1 ]; then + echo "usage: $0 test-name" >&2 + exit 1 +fi + +if [ x$KEA4 = x ]; then + echo "KEA4 is not set" >&2 +fi +if [ x$KEA6 = x ]; then + echo "KEA6 is not set" >&2 +fi + +file=$1 + +cd "$(dirname "$0")" + +isout=$(expr $file : ".*\.out") +if [ $isout -eq 0 ]; then + full=$file.out +else + full=$file +fi +if [ ! -f $full ]; then + echo "can't find $file" >&2 + exit 1 +fi + +is4=$(expr $file : ".*4") +is6=$(expr $file : ".*6") +if [ \( $is4 -eq 0 \) -a \( $is6 -eq 0 \) ]; then + echo "can't get version from $file" >&2 + exit 1 +fi + +base=$(basename $full .out) +log=/tmp/$base.log$$ +if [ $is4 -ne 0 ]; then + $KEA4 -t $full >& $log + if [ $? -ne 0 ]; then + echo "$full raised an error" >&2 + exit 1 + fi +fi +if [ $is6 -ne 0 ]; then + $KEA6 -t $full >& $log + if [ $? -ne 0 ]; then + echo "$full raised an error" >&2 + exit 1 + fi +fi diff --git a/keama/tests/class4.in4 b/keama/tests/class4.in4 new file mode 100644 index 00000000..f573a02c --- /dev/null +++ b/keama/tests/class4.in4 @@ -0,0 +1,11 @@ +# class declaration config + +# options +option mysystem code 250 = text; +option myversion code 251 = unsigned integer 16; + +# class declaration +class "foobar" { + match if option mysystem = "version1"; + option myversion 1; +} diff --git a/keama/tests/class4.out b/keama/tests/class4.out new file mode 100644 index 00000000..41ddf27f --- /dev/null +++ b/keama/tests/class4.out @@ -0,0 +1,36 @@ +{ + # class declaration config + # options + "Dhcp4": { + "option-def": [ + { + "space": "dhcp4", + "name": "mysystem", + "code": 250, + "type": "string" + }, + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "type": "uint16" + } + ], + "client-classes": [ + # class declaration + { + "name": "foobar", + /// from: match if (option dhcp.mysystem) = 'version1' + "test": "option[250].hex == 'version1'", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "1" + } + ] + } + ] + } +} diff --git a/keama/tests/class4empty.in4 b/keama/tests/class4empty.in4 new file mode 100644 index 00000000..d0b9faa5 --- /dev/null +++ b/keama/tests/class4empty.in4 @@ -0,0 +1,5 @@ +# void class declaration config + +# class declaration +class "foobar" { +} diff --git a/keama/tests/class4empty.out b/keama/tests/class4empty.out new file mode 100644 index 00000000..d2359856 --- /dev/null +++ b/keama/tests/class4empty.out @@ -0,0 +1,11 @@ +{ + # void class declaration config + # class declaration + "Dhcp4": { + "client-classes": [ + { + "name": "foobar" + } + ] + } +} diff --git a/keama/tests/class6.in6 b/keama/tests/class6.in6 new file mode 100644 index 00000000..039a80c9 --- /dev/null +++ b/keama/tests/class6.in6 @@ -0,0 +1,11 @@ +# class declaration config + +# options +option dhcp6.mysystem code 1250 = text; +option dhcp6.myversion code 1251 = unsigned integer 16; + +# class declaration +class "foobar" { + match if option dhcp6.mysystem = "version1"; + option dhcp6.myversion 1; +} diff --git a/keama/tests/class6.out b/keama/tests/class6.out new file mode 100644 index 00000000..8c9af83d --- /dev/null +++ b/keama/tests/class6.out @@ -0,0 +1,36 @@ +{ + # class declaration config + # options + "Dhcp6": { + "option-def": [ + { + "space": "dhcp6", + "name": "mysystem", + "code": 1250, + "type": "string" + }, + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "type": "uint16" + } + ], + "client-classes": [ + # class declaration + { + "name": "foobar", + /// from: match if (option dhcp6.mysystem) = 'version1' + "test": "option[1250].hex == 'version1'", + "option-data": [ + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "data": "1" + } + ] + } + ] + } +} diff --git a/keama/tests/class6empty.in6 b/keama/tests/class6empty.in6 new file mode 100644 index 00000000..d0b9faa5 --- /dev/null +++ b/keama/tests/class6empty.in6 @@ -0,0 +1,5 @@ +# void class declaration config + +# class declaration +class "foobar" { +} diff --git a/keama/tests/class6empty.out b/keama/tests/class6empty.out new file mode 100644 index 00000000..a9869b5f --- /dev/null +++ b/keama/tests/class6empty.out @@ -0,0 +1,11 @@ +{ + # void class declaration config + # class declaration + "Dhcp6": { + "client-classes": [ + { + "name": "foobar" + } + ] + } +} diff --git a/keama/tests/classbadmatch.err b/keama/tests/classbadmatch.err new file mode 100644 index 00000000..b9eac948 --- /dev/null +++ b/keama/tests/classbadmatch.err @@ -0,0 +1,7 @@ +# bad (match with a boolean expression) class declaration config + +# class declaration +class "foobar" { + match option server.duplicates = 0; + default-lease-time 1800; +} diff --git a/keama/tests/classbadmatch.msg b/keama/tests/classbadmatch.msg new file mode 100644 index 00000000..5fcda249 --- /dev/null +++ b/keama/tests/classbadmatch.msg @@ -0,0 +1 @@ +classbadmatch.err line 5: Expecting a data expression. diff --git a/keama/tests/classbadmatchif.err b/keama/tests/classbadmatchif.err new file mode 100644 index 00000000..7eb88d65 --- /dev/null +++ b/keama/tests/classbadmatchif.err @@ -0,0 +1,7 @@ +# bad (match if with a data expression) class declaration config + +# class declaration +class "foobar" { + match if option server.duplicates; + default-lease-time 1800; +} diff --git a/keama/tests/classbadmatchif.msg b/keama/tests/classbadmatchif.msg new file mode 100644 index 00000000..073d48c2 --- /dev/null +++ b/keama/tests/classbadmatchif.msg @@ -0,0 +1 @@ +classbadmatchif.err line 5: Expecting a boolean expression. diff --git a/keama/tests/classinclass.err b/keama/tests/classinclass.err new file mode 100644 index 00000000..36aa0b4b --- /dev/null +++ b/keama/tests/classinclass.err @@ -0,0 +1,10 @@ +# class declaration inside class declaration config + +# class declaration +class "foobar" { + # can't put a class declaration here + subclass "foobar" "illegal" { + default-lease-time 1800; + } +} + diff --git a/keama/tests/classinclass.msg b/keama/tests/classinclass.msg new file mode 100644 index 00000000..5e1c26c9 --- /dev/null +++ b/keama/tests/classinclass.msg @@ -0,0 +1 @@ +classinclass.err line 6: class declarations not allowed here. diff --git a/keama/tests/concatdx4.in4 b/keama/tests/concatdx4.in4 new file mode 100644 index 00000000..b94a3a2f --- /dev/null +++ b/keama/tests/concatdx4.in4 @@ -0,0 +1,19 @@ +# concat data expression + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# use substring in a reductible match +class "reductible" { + match concat("domain=", suffix(option host-name, 3)); +} + +subclass "reductible" "domain=com" { } + +# reduce literals too +class "literal" { + match if option host-name = concat("www.", "example.", "com"); +} + +# raw +option host-name = concat("www.", option domain-name); diff --git a/keama/tests/concatdx4.out b/keama/tests/concatdx4.out new file mode 100644 index 00000000..4602a497 --- /dev/null +++ b/keama/tests/concatdx4.out @@ -0,0 +1,48 @@ +{ + # concat data expression + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800, + "client-classes": [ + # use substring in a reductible match + /// match: concat('domain=', suffix(option dhcp.host-name, 3)) + { + "name": "reductible" + }, + /// subclass selector 'domain=com' + { + "name": "sub#reductible#0", + /// from: match concat('domain=', suffix(option dhcp.host-name, 3)) + /// data: 'domain=com' + "test": "concat('domain=', substring(option[12].hex,-3,all)) == 'domain=com'" + }, + # reduce literals too + { + "name": "literal", + /// from: match if (option dhcp.host-name) = (concat('www.', concat('example.', 'com'))) + "test": "option[12].hex == 'www.example.com'" + } + ], + "option-data": [ +// # raw +// { +// "space": "dhcp4", +// "name": "host-name", +// "code": 12, +// "csv-format": false, +// "expression": { +// "concat": { +// "left": "www.", +// "right": { +// "option": { +// "universe": "dhcp", +// "name": "domain-name", +// "code": 15 +// } +// } +// } +// } +// } + ] + } +} diff --git a/keama/tests/concatnulldx4.in4 b/keama/tests/concatnulldx4.in4 new file mode 100644 index 00000000..9b5e2a42 --- /dev/null +++ b/keama/tests/concatnulldx4.in4 @@ -0,0 +1,18 @@ +# concat with null argument data expression + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# null left argument +class "null-left" { + match concat("", suffix(option host-name, 3)); +} + +subclass "null-left" "com" { } + +# null right argument +class "null-right" { + match concat(suffix(option host-name, 3), substring("foobar", 0, 0)); +} + +subclass "null-right" "org" { } diff --git a/keama/tests/concatnulldx4.out b/keama/tests/concatnulldx4.out new file mode 100644 index 00000000..66cdbdf8 --- /dev/null +++ b/keama/tests/concatnulldx4.out @@ -0,0 +1,33 @@ +{ + # concat with null argument data expression + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800, + "client-classes": [ + # null left argument + /// match: concat('', suffix(option dhcp.host-name, 3)) + { + "name": "null-left" + }, + /// subclass selector 'com' + { + "name": "sub#null-left#0", + /// from: match concat('', suffix(option dhcp.host-name, 3)) + /// data: 'com' + "test": "substring(option[12].hex,-3,all) == 'com'" + }, + # null right argument + /// match: concat(suffix(option dhcp.host-name, 3), substring('foobar', 0, 0)) + { + "name": "null-right" + }, + /// subclass selector 'org' + { + "name": "sub#null-right#1", + /// from: match concat(suffix(option dhcp.host-name, 3), substring('foobar', 0, 0)) + /// data: 'org' + "test": "substring(option[12].hex,-3,all) == 'org'" + } + ] + } +} diff --git a/keama/tests/configdata4.in4 b/keama/tests/configdata4.in4 new file mode 100644 index 00000000..f8c5c7bc --- /dev/null +++ b/keama/tests/configdata4.in4 @@ -0,0 +1,26 @@ +# config (aka server option space data config + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# configs +dynamic-bootp-lease-length 1200; + +boot-unknown-clients false; + +min-secs 20; + +lease-file-name "/tmp/leases"; + +local-port 10067; + +local-address 10.5.5.1; + +# not quoted? +omapi-key my.key.biz; + +limit-addrs-per-ia 12345; + +ddns-local-address6 2001::1; + +pid-file-name = "/tmp/pid"; diff --git a/keama/tests/configdata4.out b/keama/tests/configdata4.out new file mode 100644 index 00000000..a1af336e --- /dev/null +++ b/keama/tests/configdata4.out @@ -0,0 +1,74 @@ +{ + # config (aka server option space data config + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800 +// "config": [ +// /// bootp protocol is not supported +// { +// "name": "dynamic-bootp-lease-length", +// "code": 5, +// "value": 1200 +// }, +// /// bootp protocol is not supported +// { +// "name": "boot-unknown-clients", +// "code": 6, +// "value": false +// }, +// /// min-secs is not (yet?) supported +// /// Reference Kea #223 +// { +// "name": "min-secs", +// "code": 14, +// "value": 5120 +// }, +// /// lease-file-name is an internal ISC DHCP feature +// { +// "name": "lease-file-name", +// "code": 26, +// "value": "/tmp/leases" +// }, +// /// local-port is not supported +// /// command line -p parameter should be used instead +// { +// "name": "local-port", +// "code": 32, +// "value": 10067 +// }, +// /// local-address is not supported +// /// Kea equivalent feature is to specify an interface address +// { +// "name": "local-address", +// "code": 35, +// "value": "10.5.5.1" +// }, +// /// omapi-key is an internal ISC DHCP feature +// { +// "name": "omapi-key", +// "code": 36, +// "value": "my.key.biz" +// }, +// /// limit-addrs-per-ia is not (yet?) supported +// /// Reference Kea #227 +// { +// "name": "limit-addrs-per-ia", +// "code": 56, +// "value": 12345 +// }, +// /// ddns-local-address6 is not supported +// /// Kea D2 equivalent config is ip-address +// { +// "name": "ddns-local-address6", +// "code": 81, +// "value": "2001::1" +// }, +// /// pid-file-nam is an internal ISC DHCP feature +// { +// "name": "pid-file-name", +// "code": 27, +// "value": "/tmp/pid" +// } +// ] + } +} diff --git a/keama/tests/dbtimeformat4.in4 b/keama/tests/dbtimeformat4.in4 new file mode 100644 index 00000000..b3225a82 --- /dev/null +++ b/keama/tests/dbtimeformat4.in4 @@ -0,0 +1,6 @@ +# db-time-format config + +# empty configs are not accepted by Kea +default-lease-time 1800; + +db-time-format default; diff --git a/keama/tests/dbtimeformat4.out b/keama/tests/dbtimeformat4.out new file mode 100644 index 00000000..0988bfbd --- /dev/null +++ b/keama/tests/dbtimeformat4.out @@ -0,0 +1,10 @@ +{ + # db-time-format config + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800 +// "statement": { +// "db-time-format": "default" +// } + } +} diff --git a/keama/tests/dbtimeformat6.in6 b/keama/tests/dbtimeformat6.in6 new file mode 100644 index 00000000..619d1c91 --- /dev/null +++ b/keama/tests/dbtimeformat6.in6 @@ -0,0 +1,6 @@ +# db-time-format config + +# empty configs are not accepted by Kea +default-lease-time 1800; + +db-time-format local; diff --git a/keama/tests/dbtimeformat6.out b/keama/tests/dbtimeformat6.out new file mode 100644 index 00000000..42460de6 --- /dev/null +++ b/keama/tests/dbtimeformat6.out @@ -0,0 +1,10 @@ +{ + # db-time-format config + # empty configs are not accepted by Kea + "Dhcp6": { + "valid-lifetime": 1800 +// "statement": { +// "db-time-format": "local" +// } + } +} diff --git a/keama/tests/ddnsupdstyle6.in6 b/keama/tests/ddnsupdstyle6.in6 new file mode 100644 index 00000000..ef23fe38 --- /dev/null +++ b/keama/tests/ddnsupdstyle6.in6 @@ -0,0 +1,12 @@ +# ddns-update-style + +ddns-update-style standard; + +# embedded in pool +subnet6 2001::/64 { + pool6 { + ddns-update-style interim; + range6 2001::1000 2001::1fff; + } +} + diff --git a/keama/tests/ddnsupdstyle6.out b/keama/tests/ddnsupdstyle6.out new file mode 100644 index 00000000..88323bb0 --- /dev/null +++ b/keama/tests/ddnsupdstyle6.out @@ -0,0 +1,29 @@ +{ + # ddns-update-style + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp6": { + "dhcp-ddns": { + /// Unspecified ddns-domainname (default domain-name option value) + /// Kea requires a qualifying-suffix + /// Initialized to "": please put a value + "qualifying-suffix": "", + "enable-updates": true + }, + "subnet6": [ + # embedded in pool + { + "id": 1, + "subnet": "2001::/64", + "pools": [ + { +// /// Unsupported ddns-update-style interim +// /// Only global ddns-update-style is supported +// "ddns-update-style": "interim", + "pool": "2001::1000 - 2001::1fff" + } + ] + } + ] + } +} diff --git a/keama/tests/defaultexpr6.in6 b/keama/tests/defaultexpr6.in6 new file mode 100644 index 00000000..e81bd8d3 --- /dev/null +++ b/keama/tests/defaultexpr6.in6 @@ -0,0 +1,10 @@ +# default expressions + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# default expression is a variable reference +foo; + +# or a function call when there are parentheses +bar ("abcd", "xyxt"); diff --git a/keama/tests/defaultexpr6.out b/keama/tests/defaultexpr6.out new file mode 100644 index 00000000..8646e8af --- /dev/null +++ b/keama/tests/defaultexpr6.out @@ -0,0 +1,25 @@ +{ + # default expressions + # empty configs are not accepted by Kea + "Dhcp6": { + "valid-lifetime": 1800 +// # default expression is a variable reference +// "statement": { +// "eval": { +// "variable-reference": "foo" +// } +// } +// # or a function call when there are parentheses +// "statement": { +// "eval": { +// "funcall": { +// "name": "bar", +// "arguments": [ +// "abcd", +// "xyxt" +// ] +// } +// } +// } + } +} diff --git a/keama/tests/denyunknown6.in6 b/keama/tests/denyunknown6.in6 new file mode 100644 index 00000000..ccd5541a --- /dev/null +++ b/keama/tests/denyunknown6.in6 @@ -0,0 +1,15 @@ +# DHCPv6 deny unknown client config + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# subnet declaration +subnet6 2001::/64 { + # pool declaration + pool6 { + # avoid empty pool + range6 2001::100 2001::200; + # call get_permit + deny unknown clients; + } +} diff --git a/keama/tests/denyunknown6.out b/keama/tests/denyunknown6.out new file mode 100644 index 00000000..68db27b7 --- /dev/null +++ b/keama/tests/denyunknown6.out @@ -0,0 +1,32 @@ +{ + # DHCPv6 deny unknown client config + # empty configs are not accepted by Kea + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp6": { + "valid-lifetime": 1800, + "subnet6": [ + # subnet declaration + { + "id": 1, + "subnet": "2001::/64", + "pools": [ + # pool declaration + { + # avoid empty pool + "pool": "2001::100 - 2001::200", + /// From: + /// deny unknown clients + "client-class": "gen#_AND_#KNOWN#" + } + ] + } + ], + "client-classes": [ + { + "name": "gen#_AND_#KNOWN#", + "test": "member('KNOWN')" + } + ] + } +} diff --git a/keama/tests/docsis4.dir b/keama/tests/docsis4.dir new file mode 100644 index 00000000..82b206ef --- /dev/null +++ b/keama/tests/docsis4.dir @@ -0,0 +1,11 @@ +# DOCSIS DHCPv4 vendor space + +option space docsis; + +option vendor.docsis code 4491 = encapsulate docsis; + +option docsis.oro code 1 = array of unsigned integer 8; +option docsis.tftp-servers code 2 = array of ip-address; + +% option docsis.oro local; +% option docsis.tftp-servers local; diff --git a/keama/tests/docsis6.dir b/keama/tests/docsis6.dir new file mode 100644 index 00000000..ff7cd62a --- /dev/null +++ b/keama/tests/docsis6.dir @@ -0,0 +1,27 @@ +# DOCSIS DHCPv6 vendor space + +option space docsis; + +option vsio.docsis code 4491 = encapsulate docsis; + +option docsis.oro code 1 = array of unsigned integer 16; +option docsis.device-type code 2 = text; +option docsis.vendor-type code 10 = text; +option docsis.tftp-servers code 32 = array of ip6-address; +option docsis.config-file code 33 = text; +option docsis.syslog-servers code 34 = array of ip6-address; +% option docsis.device-id code 36 = "X"; +option docsis.time-servers code 37 = array of ip6-address; +option docsis.time-offset code 38 = unsigned integer 32; +% option docsis.cmts-cm-mac code 1026 = "X"; + +% option docsis.oro local; +% option docsis.device-type local; +% option docsis.vendor-type local; +% option docsis.tftp-servers local; +% option docsis.config-file local; +% option docsis.syslog-servers local; +% option docsis.device-id local; +% option docsis.time-servers local; +% option docsis.time-offset local; +% option docsis.cmts-cm-mac local; diff --git a/keama/tests/duid2.err b/keama/tests/duid2.err new file mode 100644 index 00000000..fcbef592 --- /dev/null +++ b/keama/tests/duid2.err @@ -0,0 +1,7 @@ +# two server duid config + +# EN server duid declaration +server-duid en 2495 "enterprise-specific-identifier-1234"; + +# LL server duid declaration +server-duid ll ethernet 00:16:6F:49:7D:9B; diff --git a/keama/tests/duid2.msg b/keama/tests/duid2.msg new file mode 100644 index 00000000..06a8a00f --- /dev/null +++ b/keama/tests/duid2.msg @@ -0,0 +1 @@ +duid2.err line 7: there is already a server-id diff --git a/keama/tests/duiden6.in6 b/keama/tests/duiden6.in6 new file mode 100644 index 00000000..ae8385ea --- /dev/null +++ b/keama/tests/duiden6.in6 @@ -0,0 +1,4 @@ +# EN server duid config + +# EN server duid declaration +server-duid en 2495 "enterprise-specific-identifier-1234"; diff --git a/keama/tests/duiden6.out b/keama/tests/duiden6.out new file mode 100644 index 00000000..1e7e3021 --- /dev/null +++ b/keama/tests/duiden6.out @@ -0,0 +1,11 @@ +{ + # EN server duid config + # EN server duid declaration + "Dhcp6": { + "server-id": { + "type": "EN", + "enterprise-id": 2495, + "identifier": "656e74657270726973652d73706563696669632d6964656e7469666965722d31323334" + } + } +} diff --git a/keama/tests/duidennoid.err b/keama/tests/duidennoid.err new file mode 100644 index 00000000..ae6d12ae --- /dev/null +++ b/keama/tests/duidennoid.err @@ -0,0 +1,5 @@ +# bad (no identifier) EN server duid config + +# EN server duid declaration +server-duid en 2495; + diff --git a/keama/tests/duidennoid.msg b/keama/tests/duidennoid.msg new file mode 100644 index 00000000..054fa65e --- /dev/null +++ b/keama/tests/duidennoid.msg @@ -0,0 +1 @@ +duidennoid.err line 4: identifier expected diff --git a/keama/tests/duidennonum.err b/keama/tests/duidennonum.err new file mode 100644 index 00000000..abdfdd7c --- /dev/null +++ b/keama/tests/duidennonum.err @@ -0,0 +1,4 @@ +# bad (no number) EN server duid config + +# EN server duid declaration +server-duid en "enterprise-specific-identifier-1234"; diff --git a/keama/tests/duidennonum.msg b/keama/tests/duidennonum.msg new file mode 100644 index 00000000..941a0d03 --- /dev/null +++ b/keama/tests/duidennonum.msg @@ -0,0 +1 @@ +duidennonum.err line 4: enterprise number expected diff --git a/keama/tests/duidll6.in6 b/keama/tests/duidll6.in6 new file mode 100644 index 00000000..fac38c7a --- /dev/null +++ b/keama/tests/duidll6.in6 @@ -0,0 +1,5 @@ +# LL server duid config + +# LL server duid declaration +server-duid ll; + diff --git a/keama/tests/duidll6.out b/keama/tests/duidll6.out new file mode 100644 index 00000000..089539d7 --- /dev/null +++ b/keama/tests/duidll6.out @@ -0,0 +1,9 @@ +{ + # LL server duid config + # LL server duid declaration + "Dhcp6": { + "server-id": { + "type": "LL" + } + } +} diff --git a/keama/tests/duidllbadtype.err b/keama/tests/duidllbadtype.err new file mode 100644 index 00000000..2526f6e1 --- /dev/null +++ b/keama/tests/duidllbadtype.err @@ -0,0 +1,4 @@ +# bad (unknown hardware type) LL server duid config + +# LL server duid declaration +server-duid ll foobar 00:16:6F:49:7D:9B; diff --git a/keama/tests/duidllbadtype.msg b/keama/tests/duidllbadtype.msg new file mode 100644 index 00000000..341ebd0b --- /dev/null +++ b/keama/tests/duidllbadtype.msg @@ -0,0 +1 @@ +duidllbadtype.err line 4: hardware type expected diff --git a/keama/tests/duidllhw6.in6 b/keama/tests/duidllhw6.in6 new file mode 100644 index 00000000..48312801 --- /dev/null +++ b/keama/tests/duidllhw6.in6 @@ -0,0 +1,6 @@ +# LL server duid config + +# LL server duid declaration +server-duid ll ethernet 00:16:6F:49:7D:9B; + + diff --git a/keama/tests/duidllhw6.out b/keama/tests/duidllhw6.out new file mode 100644 index 00000000..ad7fbdac --- /dev/null +++ b/keama/tests/duidllhw6.out @@ -0,0 +1,11 @@ +{ + # LL server duid config + # LL server duid declaration + "Dhcp6": { + "server-id": { + "type": "LL", + "htype": 1, + "identifier": "00166f497d9b" + } + } +} diff --git a/keama/tests/duidllnohw.err b/keama/tests/duidllnohw.err new file mode 100644 index 00000000..a93b90d3 --- /dev/null +++ b/keama/tests/duidllnohw.err @@ -0,0 +1,4 @@ +# bad (no hardware address) LL server duid config + +# LL server duid declaration +server-duid ll fddi; diff --git a/keama/tests/duidllnohw.msg b/keama/tests/duidllnohw.msg new file mode 100644 index 00000000..b2d955ec --- /dev/null +++ b/keama/tests/duidllnohw.msg @@ -0,0 +1 @@ +duidllnohw.err line 4: expecting hexadecimal number. diff --git a/keama/tests/duidllt6.in6 b/keama/tests/duidllt6.in6 new file mode 100644 index 00000000..25420b3c --- /dev/null +++ b/keama/tests/duidllt6.in6 @@ -0,0 +1,5 @@ +# LLT server duid config + +# LLT server duid declaration +server-duid llt; + diff --git a/keama/tests/duidllt6.out b/keama/tests/duidllt6.out new file mode 100644 index 00000000..2a1ea79e --- /dev/null +++ b/keama/tests/duidllt6.out @@ -0,0 +1,9 @@ +{ + # LLT server duid config + # LLT server duid declaration + "Dhcp6": { + "server-id": { + "type": "LLT" + } + } +} diff --git a/keama/tests/duidlltbadtype.err b/keama/tests/duidlltbadtype.err new file mode 100644 index 00000000..66ab6642 --- /dev/null +++ b/keama/tests/duidlltbadtype.err @@ -0,0 +1,4 @@ +# bad (unknown hardware type) LLT server duid config + +# LLT server duid declaration +server-duid llt foobar 213982198 00:16:6F:49:7D:9B; diff --git a/keama/tests/duidlltbadtype.msg b/keama/tests/duidlltbadtype.msg new file mode 100644 index 00000000..32305a75 --- /dev/null +++ b/keama/tests/duidlltbadtype.msg @@ -0,0 +1 @@ +duidlltbadtype.err line 4: hardware type expected diff --git a/keama/tests/duidlltnohw.err b/keama/tests/duidlltnohw.err new file mode 100644 index 00000000..3208ed60 --- /dev/null +++ b/keama/tests/duidlltnohw.err @@ -0,0 +1,4 @@ +# bad (no hardware address) LLT server duid config + +# LLT server duid declaration +server-duid llt token-ring 213982198; diff --git a/keama/tests/duidlltnohw.msg b/keama/tests/duidlltnohw.msg new file mode 100644 index 00000000..7bfaa245 --- /dev/null +++ b/keama/tests/duidlltnohw.msg @@ -0,0 +1 @@ +duidlltnohw.err line 4: expecting hexadecimal number. diff --git a/keama/tests/duidlltnotime.err b/keama/tests/duidlltnotime.err new file mode 100644 index 00000000..f53321bb --- /dev/null +++ b/keama/tests/duidlltnotime.err @@ -0,0 +1,4 @@ +# bad (no timestamp) LLT server duid config + +# LLT server duid declaration +server-duid llt token-ring A8:16:6F:49:7D:9B; diff --git a/keama/tests/duidlltnotime.msg b/keama/tests/duidlltnotime.msg new file mode 100644 index 00000000..2b7fb8a3 --- /dev/null +++ b/keama/tests/duidlltnotime.msg @@ -0,0 +1 @@ +duidlltnotime.err line 4: timestamp expected diff --git a/keama/tests/duidlltthw4.err4 b/keama/tests/duidlltthw4.err4 new file mode 100644 index 00000000..ae039d18 --- /dev/null +++ b/keama/tests/duidlltthw4.err4 @@ -0,0 +1,4 @@ +# LLT server duid config + +# LLT server duid declaration +server-duid llt token-ring 213982198 00:16:6F:49:7D:9B; diff --git a/keama/tests/duidlltthw4.msg b/keama/tests/duidlltthw4.msg new file mode 100644 index 00000000..df163bb1 --- /dev/null +++ b/keama/tests/duidlltthw4.msg @@ -0,0 +1 @@ +duidlltthw4.err4 line 4: expecting a parameter or declaration diff --git a/keama/tests/duidlltthw6.in6 b/keama/tests/duidlltthw6.in6 new file mode 100644 index 00000000..ae039d18 --- /dev/null +++ b/keama/tests/duidlltthw6.in6 @@ -0,0 +1,4 @@ +# LLT server duid config + +# LLT server duid declaration +server-duid llt token-ring 213982198 00:16:6F:49:7D:9B; diff --git a/keama/tests/duidlltthw6.out b/keama/tests/duidlltthw6.out new file mode 100644 index 00000000..de2d0a48 --- /dev/null +++ b/keama/tests/duidlltthw6.out @@ -0,0 +1,12 @@ +{ + # LLT server duid config + # LLT server duid declaration + "Dhcp6": { + "server-id": { + "type": "LLT", + "htype": 6, + "time": 213982198, + "identifier": "00166f497d9b" + } + } +} diff --git a/keama/tests/duidnoid.err b/keama/tests/duidnoid.err new file mode 100644 index 00000000..32a2c9cb --- /dev/null +++ b/keama/tests/duidnoid.err @@ -0,0 +1,4 @@ +# bad (no identifier) numeric server duid config + +# server duid declaration +server-duid 9; diff --git a/keama/tests/duidnoid.msg b/keama/tests/duidnoid.msg new file mode 100644 index 00000000..3e44395a --- /dev/null +++ b/keama/tests/duidnoid.msg @@ -0,0 +1 @@ +duidnoid.err line 4: identifier expected diff --git a/keama/tests/enableupdates6.in6 b/keama/tests/enableupdates6.in6 new file mode 100644 index 00000000..b6641957 --- /dev/null +++ b/keama/tests/enableupdates6.in6 @@ -0,0 +1,8 @@ +# ddns-updates (aka enable-updates) + +ddns-updates on; + +# embedded +class "foo" { + ddns-updates off; +} diff --git a/keama/tests/enableupdates6.out b/keama/tests/enableupdates6.out new file mode 100644 index 00000000..8d20a773 --- /dev/null +++ b/keama/tests/enableupdates6.out @@ -0,0 +1,20 @@ +{ + # ddns-updates (aka enable-updates) + "Dhcp6": { + "dhcp-ddns": { + /// Unspecified ddns-domainname (default domain-name option value) + /// Kea requires a qualifying-suffix + /// Initialized to "": please put a value + "qualifying-suffix": "", + "enable-updates": true + }, + "client-classes": [ + # embedded + { + "name": "foo" +// /// Only global enable-updates is supported +// "enable-updates": false + } + ] + } +} diff --git a/keama/tests/encodedx6.in6 b/keama/tests/encodedx6.in6 new file mode 100644 index 00000000..f6b16377 --- /dev/null +++ b/keama/tests/encodedx6.in6 @@ -0,0 +1,9 @@ +# encode data expression and extract numeric expression + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# reduce literals +class "literal" { + match if option dhcp6.client-data != encode-int(extract-int("\bbar",32),16); +} diff --git a/keama/tests/encodedx6.out b/keama/tests/encodedx6.out new file mode 100644 index 00000000..d1b8b38a --- /dev/null +++ b/keama/tests/encodedx6.out @@ -0,0 +1,15 @@ +{ + # encode data expression and extract numeric expression + # empty configs are not accepted by Kea + "Dhcp6": { + "valid-lifetime": 1800, + "client-classes": [ + # reduce literals + { + "name": "literal", + /// from: match if (option dhcp6.client-data) != (encode-int(extract-int('bar', 32), 16)) + "test": "not (option[45].hex == 'ar')" + } + ] + } +} diff --git a/keama/tests/env b/keama/tests/env new file mode 100644 index 00000000..9fe0fcd4 --- /dev/null +++ b/keama/tests/env @@ -0,0 +1,3 @@ +setenv KEA4 /tmp/kea/src/bin/dhcp4/kea-dhcp4 +setenv KEA6 /tmp/kea/src/bin/dhcp6/kea-dhcp6 +setenv HOOK /tmp/kea/premium/src/hooks/dhcp/flex_id/.libs/ diff --git a/keama/tests/escapestring4.in4 b/keama/tests/escapestring4.in4 new file mode 100644 index 00000000..3f318e6c --- /dev/null +++ b/keama/tests/escapestring4.in4 @@ -0,0 +1,6 @@ +# string option-data with embedded commas + +# vendor option space +option a-string code 250 = text; + +option a-string "foo, bar"; diff --git a/keama/tests/escapestring4.out b/keama/tests/escapestring4.out new file mode 100644 index 00000000..caef76c3 --- /dev/null +++ b/keama/tests/escapestring4.out @@ -0,0 +1,23 @@ +{ + # string option-data with embedded commas + # vendor option space + "Dhcp4": { + "option-def": [ + { + "space": "dhcp4", + "name": "a-string", + "code": 250, + "type": "string" + } + ], + "option-data": [ + { + "space": "dhcp4", + "name": "a-string", + "code": 250, +// "original-data": "\"foo, bar\"", + "data": "foo\\, bar" + } + ] + } +} diff --git a/keama/tests/execstatement4.in4 b/keama/tests/execstatement4.in4 new file mode 100644 index 00000000..6adc6b1e --- /dev/null +++ b/keama/tests/execstatement4.in4 @@ -0,0 +1,7 @@ +# DHCPv4 executable statement config + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# impossible to convert statement statement +break; diff --git a/keama/tests/execstatement4.out b/keama/tests/execstatement4.out new file mode 100644 index 00000000..4988de92 --- /dev/null +++ b/keama/tests/execstatement4.out @@ -0,0 +1,11 @@ +{ + # DHCPv4 executable statement config + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800 +// # impossible to convert statement statement +// "statement": { +// "break": null +// } + } +} diff --git a/keama/tests/execstatement6.in6 b/keama/tests/execstatement6.in6 new file mode 100644 index 00000000..fe49424a --- /dev/null +++ b/keama/tests/execstatement6.in6 @@ -0,0 +1,7 @@ +# DHCPv6 executable statement config + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# impossible to convert statement statement +break; diff --git a/keama/tests/execstatement6.out b/keama/tests/execstatement6.out new file mode 100644 index 00000000..25ac01af --- /dev/null +++ b/keama/tests/execstatement6.out @@ -0,0 +1,11 @@ +{ + # DHCPv6 executable statement config + # empty configs are not accepted by Kea + "Dhcp6": { + "valid-lifetime": 1800 +// # impossible to convert statement statement +// "statement": { +// "break": null +// } + } +} diff --git a/keama/tests/existsbx4.in4 b/keama/tests/existsbx4.in4 new file mode 100644 index 00000000..650a208e --- /dev/null +++ b/keama/tests/existsbx4.in4 @@ -0,0 +1,15 @@ +# exists boolean expression + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# use exists in a reductible match if +class "reductible" { + match if exists host-name; +} + +# if test is a boolean too +if exists host-name { + log(info, concat("hostname:", option host-name)); +} + diff --git a/keama/tests/existsbx4.out b/keama/tests/existsbx4.out new file mode 100644 index 00000000..7d977ac6 --- /dev/null +++ b/keama/tests/existsbx4.out @@ -0,0 +1,48 @@ +{ + # exists boolean expression + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800, + "client-classes": [ + # use exists in a reductible match if + { + "name": "reductible", + /// from: match if exists dhcp.host-name + "test": "option[12].exists" + } + ] +// # if test is a boolean too +// "statement": { +// "if": { +// "condition": { +// "exists": { +// "universe": "dhcp", +// "name": "host-name", +// "code": 12 +// } +// }, +// "then": [ +// { +// /// Kea does not support yet log statements +// /// Reference Kea #234 +// "log": { +// "priority": "info", +// "message": { +// "concat": { +// "left": "hostname:", +// "right": { +// "option": { +// "universe": "dhcp", +// "name": "host-name", +// "code": 12 +// } +// } +// } +// } +// } +// } +// ] +// } +// } + } +} diff --git a/keama/tests/filename4.in4 b/keama/tests/filename4.in4 new file mode 100644 index 00000000..9bf3ad00 --- /dev/null +++ b/keama/tests/filename4.in4 @@ -0,0 +1,10 @@ +# filename (aka boot-file-name) and server-name (aka server-hostname) + +filename "/var/boot/boot1"; +server-name "foobar.biz"; + +# embedded +class "foo" { + filename "/var/boot/boot2"; + server-name "none.biz"; +} diff --git a/keama/tests/filename4.out b/keama/tests/filename4.out new file mode 100644 index 00000000..a391eca5 --- /dev/null +++ b/keama/tests/filename4.out @@ -0,0 +1,17 @@ +{ + # filename (aka boot-file-name) and server-name (aka server-hostname) + "Dhcp4": { +// /// boot-file-name was defined in an unsupported scope +// "boot-file-name": "/var/boot/boot1", +// /// server-hostname was defined in an unsupported scope +// "server-hostname": "foobar.biz", + "client-classes": [ + # embedded + { + "name": "foo", + "boot-file-name": "/var/boot/boot2", + "server-hostname": "none.biz" + } + ] + } +} diff --git a/keama/tests/filenamedx4.notyet b/keama/tests/filenamedx4.notyet new file mode 100644 index 00000000..127bcc7a --- /dev/null +++ b/keama/tests/filenamedx4.notyet @@ -0,0 +1,20 @@ +# filename data expression +# Kea has no filename extractor in libeval + +# authoritative is mandatory +authoritative; + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# by filename superclass +class "byfn" { + match filename; +} + +subclass "byfn" "boot00070e364819" { + option host-name "test1"; +} + +# raw +option host-name = concat("host-", substring(filename, 4, 12)); diff --git a/keama/tests/fixedaddressinroot4.err4 b/keama/tests/fixedaddressinroot4.err4 new file mode 100644 index 00000000..f5d2480a --- /dev/null +++ b/keama/tests/fixedaddressinroot4.err4 @@ -0,0 +1,4 @@ +# DHCPv4 fixed address declaration in root config + +# DHCPv4 fixed address declaration must be in a host declaration +fixed-address 204.152.185.133; diff --git a/keama/tests/fixedaddressinroot4.msg b/keama/tests/fixedaddressinroot4.msg new file mode 100644 index 00000000..fd0424a5 --- /dev/null +++ b/keama/tests/fixedaddressinroot4.msg @@ -0,0 +1 @@ +fixedaddressinroot4.err4 line 4: fixed-address parameter not allowed here. diff --git a/keama/tests/fixedaddressinroot6.err6 b/keama/tests/fixedaddressinroot6.err6 new file mode 100644 index 00000000..f42854a7 --- /dev/null +++ b/keama/tests/fixedaddressinroot6.err6 @@ -0,0 +1,4 @@ +# DHCPv6 fixed address declaration in root config + +# DHCPv6 fixed address declaration must be in a host declaration +fixed-address6 2001::1; diff --git a/keama/tests/fixedaddressinroot6.msg b/keama/tests/fixedaddressinroot6.msg new file mode 100644 index 00000000..21da6d84 --- /dev/null +++ b/keama/tests/fixedaddressinroot6.msg @@ -0,0 +1 @@ +fixedaddressinroot6.err6 line 4: fixed-address parameter not allowed here. diff --git a/keama/tests/fixedprefixinroot.err6 b/keama/tests/fixedprefixinroot.err6 new file mode 100644 index 00000000..7415aaf3 --- /dev/null +++ b/keama/tests/fixedprefixinroot.err6 @@ -0,0 +1,4 @@ +# DHCPv6 fixed prefix declaration in root config + +# DHCPv6 fixed prefix declaration must be in a host declaration +fixed-prefix6 2001:0:0:1/64; diff --git a/keama/tests/fixedprefixinroot.msg b/keama/tests/fixedprefixinroot.msg new file mode 100644 index 00000000..0b835acd --- /dev/null +++ b/keama/tests/fixedprefixinroot.msg @@ -0,0 +1 @@ +fixedprefixinroot.err6 line 4: fixed-prefix6 declaration not allowed here. diff --git a/keama/tests/fqdncompressed.err6 b/keama/tests/fqdncompressed.err6 new file mode 100644 index 00000000..aed1fe63 --- /dev/null +++ b/keama/tests/fqdncompressed.err6 @@ -0,0 +1,7 @@ +# option definition config + +# options +option space foobar; + +# compressed list of FQDNs are forbidden in DHCPv6 +option foobar.compressed-list code 1 = domain-list compressed; diff --git a/keama/tests/fqdncompressed.msg b/keama/tests/fqdncompressed.msg new file mode 100644 index 00000000..cfd157ef --- /dev/null +++ b/keama/tests/fqdncompressed.msg @@ -0,0 +1 @@ +fqdncompressed.err6 line 7: domain list in DHCPv6 MUST NOT be compressed diff --git a/keama/tests/gethostdx4.notyet b/keama/tests/gethostdx4.notyet new file mode 100644 index 00000000..567d66a4 --- /dev/null +++ b/keama/tests/gethostdx4.notyet @@ -0,0 +1,13 @@ +# gethostname and gethostbyname data expressions + +# authoritative is mandatory +authoritative; + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# gethostname +option domain-name = suffix(gethostname(), (1 + 2) * 2); + +# gethostbyaddr +option ntp-servers = gethostbyname("www.apple.fr"); diff --git a/keama/tests/global4.in4 b/keama/tests/global4.in4 new file mode 100644 index 00000000..ddfaec67 --- /dev/null +++ b/keama/tests/global4.in4 @@ -0,0 +1,10 @@ +# DHCPv4 global reservation config + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# global reservation +host foobar { + hardware ethernet 00:0B:FD:32:E6:FA; + option ip-forwarding off; +} diff --git a/keama/tests/global4.out b/keama/tests/global4.out new file mode 100644 index 00000000..66b380ff --- /dev/null +++ b/keama/tests/global4.out @@ -0,0 +1,28 @@ +{ + # DHCPv4 global reservation config + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800, + "host-reservation-identifiers": [ + "hw-address" + ], + "reservation-mode": "global", + "reservations": [ + # global reservation + { + "hostname": "foobar", + "hw-address": "00:0b:fd:32:e6:fa", + "option-data": [ + { + "space": "dhcp4", + "name": "ip-forwarding", + "code": 19, +// "original-data": "off", + /// canonized booleans to lowercase true or false + "data": "false" + } + ] + } + ] + } +} diff --git a/keama/tests/global6.in6 b/keama/tests/global6.in6 new file mode 100644 index 00000000..8bba7082 --- /dev/null +++ b/keama/tests/global6.in6 @@ -0,0 +1,10 @@ +# DHCPv6 global reservation config + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# global reservation +host foobar { + hardware ethernet 00:0B:FD:32:E6:FA; + option dhcp6.name-servers 2a01:e00::2, 2a01:e00::1; +} diff --git a/keama/tests/global6.out b/keama/tests/global6.out new file mode 100644 index 00000000..5616e7ec --- /dev/null +++ b/keama/tests/global6.out @@ -0,0 +1,26 @@ +{ + # DHCPv6 global reservation config + # empty configs are not accepted by Kea + "Dhcp6": { + "valid-lifetime": 1800, + "host-reservation-identifiers": [ + "hw-address" + ], + "reservation-mode": "global", + "reservations": [ + # global reservation + { + "hostname": "foobar", + "hw-address": "00:0b:fd:32:e6:fa", + "option-data": [ + { + "space": "dhcp6", + "name": "dns-servers", + "code": 23, + "data": "2a01:e00::2, 2a01:e00::1" + } + ] + } + ] + } +} diff --git a/keama/tests/groupclass4.in4 b/keama/tests/groupclass4.in4 new file mode 100644 index 00000000..b306f66f --- /dev/null +++ b/keama/tests/groupclass4.in4 @@ -0,0 +1,32 @@ +# group and class declaration config + +# options +option mysystem code 250 = text; +option myversion code 251 = unsigned integer 16; + +# superclass declaration +class "foobar" { + match option mysystem; + option myversion 1; +} + +# simple subclass declaration +subclass "foobar" "version1"; + +group machin { + next-server 10.10.10.1; + # this option is not propagated because the superclass takes precedence + option myversion 99; + + # option setting subclass declaration + subclass "foobar" "version2" { option myversion 2; } + + # complex subclass declaration + subclass "foobar" "version3" { + option myversion 3; + next-server 192.168.0.1; + } + + # another simple subclass declaration + subclass "foobar" "version10"; +} diff --git a/keama/tests/groupclass4.out b/keama/tests/groupclass4.out new file mode 100644 index 00000000..71e40072 --- /dev/null +++ b/keama/tests/groupclass4.out @@ -0,0 +1,102 @@ +{ + # group and class declaration config + # options + "Dhcp4": { + "option-def": [ + { + "space": "dhcp4", + "name": "mysystem", + "code": 250, + "type": "string" + }, + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "type": "uint16" + } + ], + "client-classes": [ + # superclass declaration + /// match: option dhcp.mysystem + { + "name": "foobar", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "1" + } + ] + }, + # simple subclass declaration + /// subclass selector 'version1' + { + "name": "sub#foobar#0", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "1" + } + ], + /// from: match option dhcp.mysystem + /// data: 'version1' + "test": "option[250].hex == 'version1'" + }, + # option setting subclass declaration + /// subclass selector 'version2' + { + "name": "sub#foobar#1", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "2" + } + ], + /// from: match option dhcp.mysystem + /// data: 'version2' + "test": "option[250].hex == 'version2'", + "next-server": "10.10.10.1" + }, + # complex subclass declaration + /// subclass selector 'version3' + { + "name": "sub#foobar#2", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "3" + } + ], + "next-server": "192.168.0.1", + /// from: match option dhcp.mysystem + /// data: 'version3' + "test": "option[250].hex == 'version3'" + }, + # another simple subclass declaration + /// subclass selector 'version10' + { + "name": "sub#foobar#3", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "1" + } + ], + /// from: match option dhcp.mysystem + /// data: 'version10' + "test": "option[250].hex == 'version10'", + "next-server": "10.10.10.1" + } + ] + } +} diff --git a/keama/tests/groupclass6.in6 b/keama/tests/groupclass6.in6 new file mode 100644 index 00000000..4cc21b78 --- /dev/null +++ b/keama/tests/groupclass6.in6 @@ -0,0 +1,35 @@ +# group and class declaration config + +# options +option dhcp6.mysystem code 1250 = text; +option dhcp6.myversion code 1251 = unsigned integer 16; +option dhcp6.myvalue code 1252 = text; + +# superclass declaration +class "foobar" { + match option dhcp6.mysystem; + option dhcp6.myversion 1; +} + +# simple subclass declaration +subclass "foobar" "version1"; + +# anonymous group +group { + # this option is not propagated because the superclass takes precedence + option dhcp6.myversion 99; + + option dhcp6.myvalue "foo"; + + # option setting subclass declaration + subclass "foobar" "version2" { option dhcp6.myversion 2; } + + # complex subclass declaration + subclass "foobar" "version3" { + option dhcp6.myversion 3; + option dhcp6.myvalue "bar"; + } + + # another simple subclass declaration + subclass "foobar" "version10"; +} diff --git a/keama/tests/groupclass6.out b/keama/tests/groupclass6.out new file mode 100644 index 00000000..68144b89 --- /dev/null +++ b/keama/tests/groupclass6.out @@ -0,0 +1,123 @@ +{ + # group and class declaration config + # options + "Dhcp6": { + "option-def": [ + { + "space": "dhcp6", + "name": "mysystem", + "code": 1250, + "type": "string" + }, + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "type": "uint16" + }, + { + "space": "dhcp6", + "name": "myvalue", + "code": 1252, + "type": "string" + } + ], + "client-classes": [ + # superclass declaration + /// match: option dhcp6.mysystem + { + "name": "foobar", + "option-data": [ + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "data": "1" + } + ] + }, + # simple subclass declaration + /// subclass selector 'version1' + { + "name": "sub#foobar#0", + "option-data": [ + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "data": "1" + } + ], + /// from: match option dhcp6.mysystem + /// data: 'version1' + "test": "option[1250].hex == 'version1'" + }, + # option setting subclass declaration + /// subclass selector 'version2' + { + "name": "sub#foobar#1", + "option-data": [ + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "data": "2" + }, + { + "space": "dhcp6", + "name": "myvalue", + "code": 1252, + "data": "foo" + } + ], + /// from: match option dhcp6.mysystem + /// data: 'version2' + "test": "option[1250].hex == 'version2'" + }, + # complex subclass declaration + /// subclass selector 'version3' + { + "name": "sub#foobar#2", + "option-data": [ + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "data": "3" + }, + { + "space": "dhcp6", + "name": "myvalue", + "code": 1252, + "data": "bar" + } + ], + /// from: match option dhcp6.mysystem + /// data: 'version3' + "test": "option[1250].hex == 'version3'" + }, + # another simple subclass declaration + /// subclass selector 'version10' + { + "name": "sub#foobar#3", + "option-data": [ + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "data": "1" + }, + { + "space": "dhcp6", + "name": "myvalue", + "code": 1252, + "data": "foo" + } + ], + /// from: match option dhcp6.mysystem + /// data: 'version10' + "test": "option[1250].hex == 'version10'" + } + ] + } +} diff --git a/keama/tests/groupgroup4.in4 b/keama/tests/groupgroup4.in4 new file mode 100644 index 00000000..3be93d7d --- /dev/null +++ b/keama/tests/groupgroup4.in4 @@ -0,0 +1,45 @@ +# multiple groups declaration config + +# options +option mysystem code 250 = text; +option myversion code 251 = unsigned integer 16; +option myvalue code 252 = text; + +# superclass declaration +class "foobar" { + match option mysystem; + option myversion 1; +} + +# simple subclass declaration +subclass "foobar" "version1"; + +group first { + next-server 10.10.10.1; + # this option is not propagated because the superclass takes precedence + option myversion 99; + + # option setting subclass declaration + subclass "foobar" "version2" { option myversion 2; } + + # complex subclass declaration + subclass "foobar" "version3" { + option myversion 3; + next-server 192.168.0.1; + } + + group second { + # another simple subclass declaration + subclass "foobar" "version10"; + + # and a final subclass declaration + subclass "foobar" "version20" { + option myversion 20; + next-server 192.168.0.20; + option myvalue "twenty"; + } + + # positions of delaration do not matter + option myvalue "ten"; + } +} diff --git a/keama/tests/groupgroup4.out b/keama/tests/groupgroup4.out new file mode 100644 index 00000000..09dd0574 --- /dev/null +++ b/keama/tests/groupgroup4.out @@ -0,0 +1,138 @@ +{ + # multiple groups declaration config + # options + "Dhcp4": { + "option-def": [ + { + "space": "dhcp4", + "name": "mysystem", + "code": 250, + "type": "string" + }, + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "type": "uint16" + }, + { + "space": "dhcp4", + "name": "myvalue", + "code": 252, + "type": "string" + } + ], + "client-classes": [ + # superclass declaration + /// match: option dhcp.mysystem + { + "name": "foobar", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "1" + } + ] + }, + # simple subclass declaration + /// subclass selector 'version1' + { + "name": "sub#foobar#0", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "1" + } + ], + /// from: match option dhcp.mysystem + /// data: 'version1' + "test": "option[250].hex == 'version1'" + }, + # option setting subclass declaration + /// subclass selector 'version2' + { + "name": "sub#foobar#1", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "2" + } + ], + /// from: match option dhcp.mysystem + /// data: 'version2' + "test": "option[250].hex == 'version2'", + "next-server": "10.10.10.1" + }, + # complex subclass declaration + /// subclass selector 'version3' + { + "name": "sub#foobar#2", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "3" + } + ], + "next-server": "192.168.0.1", + /// from: match option dhcp.mysystem + /// data: 'version3' + "test": "option[250].hex == 'version3'" + }, + # another simple subclass declaration + /// subclass selector 'version10' + { + "name": "sub#foobar#3", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "1" + }, + # positions of delaration do not matter + { + "space": "dhcp4", + "name": "myvalue", + "code": 252, + "data": "ten" + } + ], + /// from: match option dhcp.mysystem + /// data: 'version10' + "test": "option[250].hex == 'version10'", + "next-server": "10.10.10.1" + }, + # and a final subclass declaration + /// subclass selector 'version20' + { + "name": "sub#foobar#4", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "20" + }, + { + "space": "dhcp4", + "name": "myvalue", + "code": 252, + "data": "twenty" + } + ], + "next-server": "192.168.0.20", + /// from: match option dhcp.mysystem + /// data: 'version20' + "test": "option[250].hex == 'version20'" + } + ] + } +} diff --git a/keama/tests/grouphost4.inn b/keama/tests/grouphost4.inn new file mode 100644 index 00000000..da9d5291 --- /dev/null +++ b/keama/tests/grouphost4.inn @@ -0,0 +1,35 @@ +# group and host declarations config + +# subnet4 declaration +subnet 10.5.5.0 netmask 255.255.255.224 { + range 10.5.5.5 10.5.5.10; +} + +# host declaration +host test1 { + hardware ethernet 00:0B:FD:32:E6:FA; + fixed-address 10.5.5.1, 10.10.10.10; +} + +# group declaration +group "foobar" { + default-lease-time 1800; + option domain-search "example.com", "example.org"; + next-server 192.168.0.1; + + # host declarations + host test2 { + hardware ethernet 00:07:0E:36:48:19; + fixed-address 10.5.5.2; + option domain-name "example.com"; + option domain-search "example.com", "com"; + } + + host test3 { + hardware fddi 00:07:0E:36:48:19; + fixed-address 10.10.10.1; + default-lease-time 3600; + } +} + +subnet 10.10.10.0 netmask 255.255.255.224 { } diff --git a/keama/tests/grouphost4.out b/keama/tests/grouphost4.out new file mode 100644 index 00000000..2027c536 --- /dev/null +++ b/keama/tests/grouphost4.out @@ -0,0 +1,78 @@ +{ + # group and host declarations config + # subnet4 declaration + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "subnet4": [ + { + "id": 1, + "subnet": "10.5.5.0/27", + "pools": [ + { + "pool": "10.5.5.5 - 10.5.5.10" + } + ], + "reservations": [ + # host declaration + { + "hostname": "test1", + "hw-address": "00:0b:fd:32:e6:fa", + "ip-address": "10.5.5.1" +// "extra-ip-addresses": [ +// "10.10.10.10" +// ] + }, + # host declarations + { + "hostname": "test2", + "hw-address": "00:07:0e:36:48:19", + "ip-address": "10.5.5.2", + "option-data": [ + { + "space": "dhcp4", + "name": "domain-name", + "code": 15, + "data": "example.com" + }, + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\", \"com\"", + "data": "example.com, com" + } + ], + "next-server": "192.168.0.1" + } + ] + }, + { + "id": 2, + "subnet": "10.10.10.0/27", + "reservations": [ +// { +// "hostname": "test3", +// "hw-address": "fddi 00:07:0e:36:48:19", +// "ip-address": "10.10.10.1", +// /// default-valid-lifetime in unsupported scope +// "valid-lifetime": 3600, +// "option-data": [ +// { +// "space": "dhcp4", +// "name": "domain-search", +// "code": 119, +// "original-data": "\"example.com\", \"example.org\"", +// "data": "example.com, example.org" +// } +// ], +// "next-server": "192.168.0.1" +// } + ] + } + ], + "host-reservation-identifiers": [ + "hw-address" + ] + } +} diff --git a/keama/tests/groupinclass.err b/keama/tests/groupinclass.err new file mode 100644 index 00000000..dbc320a4 --- /dev/null +++ b/keama/tests/groupinclass.err @@ -0,0 +1,10 @@ +# group declaration inside class declaration config + +# host declaration +class "foobar" { + # can't put a group declaration here + group "illegal" { + default-lease-time 1800; + } +} + diff --git a/keama/tests/groupinclass.msg b/keama/tests/groupinclass.msg new file mode 100644 index 00000000..ee72c674 --- /dev/null +++ b/keama/tests/groupinclass.msg @@ -0,0 +1 @@ +groupinclass.err line 6: group declarations not allowed here. diff --git a/keama/tests/groupsubnet4.in4 b/keama/tests/groupsubnet4.in4 new file mode 100644 index 00000000..a7a199dc --- /dev/null +++ b/keama/tests/groupsubnet4.in4 @@ -0,0 +1,24 @@ +# Group with DHCPv4 subnet declaration config + +# parameter which will be changed in subnet +default-lease-time 1200; + +# group declaration +group foobar { + # option + option domain-search "example.com", "example.org"; + + # parameters + default-lease-time 3600; + ignore-client-uids false; + + # DHCPv4 subnet declaration + subnet 10.5.5.0 netmask 255.255.255.224 { + # at least one pool is required + pool { + range 10.5.5.5 10.5.5.10; + } + interface "en0"; + default-lease-time 1800; + } +} diff --git a/keama/tests/groupsubnet4.out b/keama/tests/groupsubnet4.out new file mode 100644 index 00000000..f2db5f85 --- /dev/null +++ b/keama/tests/groupsubnet4.out @@ -0,0 +1,38 @@ +{ + # Group with DHCPv4 subnet declaration config + # parameter which will be changed in subnet + "Dhcp4": { + "valid-lifetime": 1200, + "interfaces-config": { + "interfaces": [ + "en0" + ] + }, + "subnet4": [ + # DHCPv4 subnet declaration + { + "id": 1, + "subnet": "10.5.5.0/27", + "pools": [ + # at least one pool is required + { + "pool": "10.5.5.5 - 10.5.5.10" + } + ], + "interface": "en0", + "valid-lifetime": 1800, + "option-data": [ + # option + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "match-client-id": true + } + ] + } +} diff --git a/keama/tests/groupsubnet6.in6 b/keama/tests/groupsubnet6.in6 new file mode 100644 index 00000000..90277618 --- /dev/null +++ b/keama/tests/groupsubnet6.in6 @@ -0,0 +1,24 @@ +# Group with DHCPv6 subnet declaration config + +# parameter which will be changed in subnet +default-lease-time 1200; + +# group declaration +group foobar { + # option + option dhcp6.domain-search "example.com", "example.org"; + + # parameters + default-lease-time 3600; + + # DHCPv4 subnet declaration + subnet6 2001::/64 { + # at least one pool is required + pool6 { + range6 2001::100 2001::200; + } + interface "en0"; + default-lease-time 1800; + option dhcp6.lq-relay-data 2001::1 "foobar"; + } +} diff --git a/keama/tests/groupsubnet6.out b/keama/tests/groupsubnet6.out new file mode 100644 index 00000000..63d5794e --- /dev/null +++ b/keama/tests/groupsubnet6.out @@ -0,0 +1,44 @@ +{ + # Group with DHCPv6 subnet declaration config + # parameter which will be changed in subnet + "Dhcp6": { + "valid-lifetime": 1200, + "interfaces-config": { + "interfaces": [ + "en0" + ] + }, + "subnet6": [ + # DHCPv4 subnet declaration + { + "id": 1, + "subnet": "2001::/64", + "pools": [ + # at least one pool is required + { + "pool": "2001::100 - 2001::200" + } + ], + "interface": "en0", + "valid-lifetime": 1800, + "option-data": [ + { + "space": "dhcp6", + "name": "lq-relay-data", + "code": 47, +// "original-data": "2001::1 \"foobar\"", + "data": "2001::1, 666f6f626172" + }, + # option + { + "space": "dhcp6", + "name": "domain-search", + "code": 24, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ] + } + ] + } +} diff --git a/keama/tests/groupsubnetif.err4 b/keama/tests/groupsubnetif.err4 new file mode 100644 index 00000000..9a407b11 --- /dev/null +++ b/keama/tests/groupsubnetif.err4 @@ -0,0 +1,19 @@ +# bad (interface unlnown in this cope) group declaration config + +# parameter which will be changed in subnet +default-lease-time 1200; + +# group declaration +group foobar { + # interface + interface "foo"; + + # DHCPv4 subnet declaration + subnet 10.5.5.0 netmask 255.255.255.224 { + # at least one pool is required + pool { + range 10.5.5.5 10.5.5.10; + } + interface "bar"; + } +} diff --git a/keama/tests/groupsubnetif.msg b/keama/tests/groupsubnetif.msg new file mode 100644 index 00000000..1828b932 --- /dev/null +++ b/keama/tests/groupsubnetif.msg @@ -0,0 +1 @@ +groupsubnetif.err4 line 7: expecting a parameter or declaration diff --git a/keama/tests/hardware2dx4.in4 b/keama/tests/hardware2dx4.in4 new file mode 100644 index 00000000..73046612 --- /dev/null +++ b/keama/tests/hardware2dx4.in4 @@ -0,0 +1,13 @@ +# simplified hardware data expression + +# hardware type class +class "ethernet" { + match if substring(hardware, 0, 1) = encode-int(1, 8); +} + +# ethernet address superclass +class "ethernet-address" { + match substring(hardware, 1, 6); +} + +subclass "ethernet-address" 00:0B:FD:32:E6:FA { } diff --git a/keama/tests/hardware2dx4.out b/keama/tests/hardware2dx4.out new file mode 100644 index 00000000..77c3f533 --- /dev/null +++ b/keama/tests/hardware2dx4.out @@ -0,0 +1,25 @@ +{ + # simplified hardware data expression + # hardware type class + "Dhcp4": { + "client-classes": [ + { + "name": "ethernet", + /// from: match if (substring(hardware, 0, 1)) = (encode-int(1, 8)) + "test": "substring(pkt4.htype,-1,all) == '\u0001'" + }, + # ethernet address superclass + /// match: substring(hardware, 1, 6) + { + "name": "ethernet-address" + }, + /// subclass selector 0x0x000bfd32e6fa + { + "name": "sub#ethernet-address#0", + /// from: match substring(hardware, 1, 6) + /// data: 0x000bfd32e6fa + "test": "substring(pkt4.mac,0,6) == 0x000bfd32e6fa" + } + ] + } +} diff --git a/keama/tests/hardwaredx4.in4 b/keama/tests/hardwaredx4.in4 new file mode 100644 index 00000000..46160776 --- /dev/null +++ b/keama/tests/hardwaredx4.in4 @@ -0,0 +1,16 @@ +# hardware data expression + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# pretty standard hardware superclass +class "byhw" { + match hardware; +} + +subclass "byhw" 01:00:07:0E:36:48:19 { + option host-name "test1"; +} + +# raw +option host-name = binary-to-ascii(16, 8, "-", hardware); diff --git a/keama/tests/hardwaredx4.out b/keama/tests/hardwaredx4.out new file mode 100644 index 00000000..a3f13938 --- /dev/null +++ b/keama/tests/hardwaredx4.out @@ -0,0 +1,55 @@ +{ + # hardware data expression + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800, + "client-classes": [ + # pretty standard hardware superclass + /// match: hardware + { + "name": "byhw" + }, + /// subclass selector 0x0x0100070e364819 + { + "name": "sub#byhw#0", + "option-data": [ + { + "space": "dhcp4", + "name": "host-name", + "code": 12, + "data": "test1" + } + ], + /// from: match hardware + /// data: 0x0100070e364819 + "test": "concat(substring(pkt4.htype,-1,all), pkt4.mac) == 0x0100070e364819" + } + ], + "option-data": [ +// # raw +// { +// "space": "dhcp4", +// "name": "host-name", +// "code": 12, +// "csv-format": false, +// "expression": { +// "binary-to-ascii": { +// "base": 16, +// "width": 8, +// "separator": "-", +// "buffer": { +// "concat": { +// "left": { +// "hw-type": null +// }, +// "right": { +// "hw-address": null +// } +// } +// } +// } +// } +// } + ] + } +} diff --git a/keama/tests/hardwareinroot.err b/keama/tests/hardwareinroot.err new file mode 100644 index 00000000..22902f20 --- /dev/null +++ b/keama/tests/hardwareinroot.err @@ -0,0 +1,5 @@ +# hardware declaration in root config + +# hardware declaration must be in a host declaration +hardware ethernet 00:0B:FD:32:E6:FA; + diff --git a/keama/tests/hardwareinroot.msg b/keama/tests/hardwareinroot.msg new file mode 100644 index 00000000..6cf018d0 --- /dev/null +++ b/keama/tests/hardwareinroot.msg @@ -0,0 +1 @@ +hardwareinroot.err line 4: hardware address parameter not allowed here. diff --git a/keama/tests/host6.notyet b/keama/tests/host6.notyet new file mode 100644 index 00000000..daff911e --- /dev/null +++ b/keama/tests/host6.notyet @@ -0,0 +1,20 @@ +# DHCPv6 host declaration config + +# authoritative is mandatory +authoritative; + +# subnet declaration +subnet6 2001::/64 { + range6 2001::100 2001::200; +} + +# host declarations +host test1 { + hardware ethernet 00:07:0E:36:48:19; + fixed-address6 2001::1, 2001::10; +} + +host test2 { + hardware fddi 00:07:0E:36:48:19; + fixed-prefix6 2001:0:0:1::/64; +} diff --git a/keama/tests/hostidentifier4.inl b/keama/tests/hostidentifier4.inl new file mode 100644 index 00000000..9e50ddd3 --- /dev/null +++ b/keama/tests/hostidentifier4.inl @@ -0,0 +1,30 @@ +# host declaration with flexible identifiers config + +# subnet4 declaration +subnet 10.5.5.0 netmask 255.255.255.224 { + range 10.5.5.5 10.5.5.10; +} + +# option definition + +option my-id code 250 = text; + +# host declarations +host test1 { + host-identifier option my-id test1; + option domain-search "example.com", "example.org"; + default-lease-time 1800; + fixed-address 10.5.5.1, 10.10.10.10; +} + +host test2 { + hardware ethernet 00:07:0E:36:48:19; + fixed-address 10.5.5.2; +} + +host test3 { + hardware fddi 00:07:0E:36:48:19; + fixed-address 10.10.10.1; +} + +subnet 10.10.10.0 netmask 255.255.255.224 { } diff --git a/keama/tests/hostidentifier4.outl b/keama/tests/hostidentifier4.outl new file mode 100644 index 00000000..24af2ac5 --- /dev/null +++ b/keama/tests/hostidentifier4.outl @@ -0,0 +1,78 @@ +{ + # host declaration with flexible identifiers config + # subnet4 declaration + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "subnet4": [ + { + "id": 1, + "subnet": "10.5.5.0/27", + "pools": [ + { + "pool": "10.5.5.5 - 10.5.5.10" + } + ] + }, + { + "id": 2, + "subnet": "10.10.10.0/27" + } + ], + "option-def": [ + # option definition + { + "space": "dhcp4", + "name": "my-id", + "code": 250, + "type": "string" + } + ], + "host-reservation-identifiers": [ + "flex-id", + "hw-address" + ], + /// The flexible host identifier is a premium feature + "hooks-libraries": [ + { + "library": "/path/libdhcp_flex_id.so", + "parameters": { + "identifier-expression": "option[250].hex" + } + } + ], + "reservation-mode": "global", + "reservations": [ + # host declarations + { + "hostname": "test1", + "flex-id": "'test1'", + "option-data": [ + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], +// /// default-valid-lifetime in unsupported scope +// "valid-lifetime": 1800, + "ip-address": "10.5.5.1" +// "extra-ip-addresses": [ +// "10.10.10.10" +// ] + }, + { + "hostname": "test2", + "hw-address": "00:07:0e:36:48:19", + "ip-address": "10.5.5.2" + } +// { +// "hostname": "test3", +// "hw-address": "fddi 00:07:0e:36:48:19", +// "ip-address": "10.10.10.1" +// } + ] + } +} diff --git a/keama/tests/hostinclass.err b/keama/tests/hostinclass.err new file mode 100644 index 00000000..ac5075ee --- /dev/null +++ b/keama/tests/hostinclass.err @@ -0,0 +1,10 @@ +# host declaration inside class declaration config + +# class declaration +class "foobar" { + # can't put a host declaration here + host illegal { + hardware ethernet 00:07:0E:36:48:19; + } +} + diff --git a/keama/tests/hostinclass.msg b/keama/tests/hostinclass.msg new file mode 100644 index 00000000..004446ba --- /dev/null +++ b/keama/tests/hostinclass.msg @@ -0,0 +1 @@ +hostinclass.err line 6: host declarations not allowed here. diff --git a/keama/tests/hostinhost.err b/keama/tests/hostinhost.err new file mode 100644 index 00000000..8da7d425 --- /dev/null +++ b/keama/tests/hostinhost.err @@ -0,0 +1,11 @@ +# host declaration inside host declaration config + +# host declaration +host foobar { + hardware ethernet 00:0B:FD:32:E6:FA; + # can't put another host declaration here + host illegal { + hardware ethernet 00:07:0E:36:48:19; + } +} + diff --git a/keama/tests/hostinhost.msg b/keama/tests/hostinhost.msg new file mode 100644 index 00000000..3ddfbd7a --- /dev/null +++ b/keama/tests/hostinhost.msg @@ -0,0 +1 @@ +hostinhost.err line 7: host declarations not allowed here. diff --git a/keama/tests/hostname4.in4 b/keama/tests/hostname4.in4 new file mode 100644 index 00000000..8e2db319 --- /dev/null +++ b/keama/tests/hostname4.in4 @@ -0,0 +1,18 @@ +# host name config + +# subnet4 declaration +subnet 10.5.5.0 netmask 255.255.255.224 { + range 10.5.5.5 10.5.5.10; +} + +# host declaration +host test1 { + hardware ethernet 00:0B:FD:32:E6:FA; + fixed-address 10.5.5.1; +} + +# host declaration using a longer name +host test2.example.com { + hardware ethernet 00:07:0E:36:48:19; + fixed-address 10.5.5.2; +} diff --git a/keama/tests/hostname4.out b/keama/tests/hostname4.out new file mode 100644 index 00000000..d6973a48 --- /dev/null +++ b/keama/tests/hostname4.out @@ -0,0 +1,37 @@ +{ + # host name config + # subnet4 declaration + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "subnet4": [ + { + "id": 1, + "subnet": "10.5.5.0/27", + "pools": [ + { + "pool": "10.5.5.5 - 10.5.5.10" + } + ] + } + ], + "host-reservation-identifiers": [ + "hw-address" + ], + "reservation-mode": "global", + "reservations": [ + # host declaration + { + "hostname": "test1", + "hw-address": "00:0b:fd:32:e6:fa", + "ip-address": "10.5.5.1" + }, + # host declaration using a longer name + { + "hostname": "test2.example.com", + "hw-address": "00:07:0e:36:48:19", + "ip-address": "10.5.5.2" + } + ] + } +} diff --git a/keama/tests/hostnum.errF b/keama/tests/hostnum.errF new file mode 100644 index 00000000..4c89dc27 --- /dev/null +++ b/keama/tests/hostnum.errF @@ -0,0 +1,7 @@ +# numeric with address hostname config + +host 1901.fr { + hardware ethernet 00:07:0E:36:48:19; + fixed-address 1901.fr; +} + diff --git a/keama/tests/hostnum.msg b/keama/tests/hostnum.msg new file mode 100644 index 00000000..cc734bc0 --- /dev/null +++ b/keama/tests/hostnum.msg @@ -0,0 +1 @@ +hostnum.errF line 5: expected IPv4 address. got hostname 1901.fr diff --git a/keama/tests/hostuid4.inn b/keama/tests/hostuid4.inn new file mode 100644 index 00000000..867fa8b4 --- /dev/null +++ b/keama/tests/hostuid4.inn @@ -0,0 +1,29 @@ +# host declaration with client-identfiers config + +# subnet4 declaration +subnet 10.5.5.0 netmask 255.255.255.224 { + range 10.5.5.5 10.5.5.10; +} + +# recommended when using dhcp-client-identifier options +ignore-client-uids false; + +# host declarations +host test1 { + uid 01:02:03:04:05:0a:0b:0c:0d:0e:0f; + option domain-search "example.com", "example.org"; + default-lease-time 1800; + fixed-address 10.5.5.1, 10.10.10.10; +} + +host test2 { + hardware ethernet 00:07:0E:36:48:19; + fixed-address 10.5.5.2; +} + +host test3 { + hardware fddi 00:07:0E:36:48:19; + fixed-address 10.10.10.1; +} + +subnet 10.10.10.0 netmask 255.255.255.224 { } diff --git a/keama/tests/hostuid4.out b/keama/tests/hostuid4.out new file mode 100644 index 00000000..ad3f3e36 --- /dev/null +++ b/keama/tests/hostuid4.out @@ -0,0 +1,62 @@ +{ + # host declaration with client-identfiers config + # subnet4 declaration + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "subnet4": [ + { + "id": 1, + "subnet": "10.5.5.0/27", + "pools": [ + { + "pool": "10.5.5.5 - 10.5.5.10" + } + ], + "reservations": [ + # host declarations + { + "hostname": "test1", + "client-id": "01:02:03:04:05:0a:0b:0c:0d:0e:0f", + "option-data": [ + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], +// /// default-valid-lifetime in unsupported scope +// "valid-lifetime": 1800, + "ip-address": "10.5.5.1" +// "extra-ip-addresses": [ +// "10.10.10.10" +// ] + }, + { + "hostname": "test2", + "hw-address": "00:07:0e:36:48:19", + "ip-address": "10.5.5.2" + } + ] + }, + { + "id": 2, + "subnet": "10.10.10.0/27", + "reservations": [ +// { +// "hostname": "test3", +// "hw-address": "fddi 00:07:0e:36:48:19", +// "ip-address": "10.10.10.1" +// } + ] + } + ], + "match-client-id": true, + "host-reservation-identifiers": [ + "client-id", + "hw-address" + ] + } +} diff --git a/keama/tests/ifxsc4.in4 b/keama/tests/ifxsc4.in4 new file mode 100644 index 00000000..b3a59bcc --- /dev/null +++ b/keama/tests/ifxsc4.in4 @@ -0,0 +1,17 @@ +# if executable statement construct + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# if statement +# first true is not recognized even as a boolean expression +if true { + option ip-forwarding true; +} + +# another +if ( option user-class = "accounting" ) { + option boot-size 100000; +} elsif option user-class = "engineering" { + option domain-name "example.com"; +} diff --git a/keama/tests/ifxsc4.out b/keama/tests/ifxsc4.out new file mode 100644 index 00000000..ca086910 --- /dev/null +++ b/keama/tests/ifxsc4.out @@ -0,0 +1,79 @@ +{ + # if executable statement construct + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800 +// # if statement +// # first true is not recognized even as a boolean expression +// "statement": { +// "if": { +// "condition": { +// "variable-reference": "true" +// }, +// "then": [ +// { +// "option": { +// "space": "dhcp4", +// "name": "ip-forwarding", +// "code": 19, +// "data": "true" +// } +// } +// ] +// } +// } +// # another +// "statement": { +// "if": { +// "condition": { +// "equal": { +// "left": { +// "option": { +// "universe": "dhcp", +// "name": "user-class", +// "code": 77 +// } +// }, +// "right": "accounting" +// } +// }, +// "then": [ +// { +// "option": { +// "space": "dhcp4", +// "name": "boot-size", +// "code": 13, +// "data": "100000" +// } +// } +// ], +// "else": { +// "if": { +// "condition": { +// "equal": { +// "left": { +// "option": { +// "universe": "dhcp", +// "name": "user-class", +// "code": 77 +// } +// }, +// "right": "engineering" +// } +// }, +// "then": [ +// { +// "option": { +// "space": "dhcp4", +// "name": "domain-name", +// "code": 15, +// "data": "example.com" +// } +// } +// ] +// } +// } +// } +// } + } +} diff --git a/keama/tests/ipaddr6.in6 b/keama/tests/ipaddr6.in6 new file mode 100644 index 00000000..60c27175 --- /dev/null +++ b/keama/tests/ipaddr6.in6 @@ -0,0 +1,3 @@ +# IPv6 addresses config + +option dhcp6.name-servers 2001::, 200a::0bF, 2001::192.168.0.1; diff --git a/keama/tests/ipaddr6.out b/keama/tests/ipaddr6.out new file mode 100644 index 00000000..b2118c61 --- /dev/null +++ b/keama/tests/ipaddr6.out @@ -0,0 +1,14 @@ +{ + # IPv6 addresses config + "Dhcp6": { + "option-data": [ + { + "space": "dhcp6", + "name": "dns-servers", + "code": 23, +// "original-data": "2001::, 200a::0bF, 2001::192.168.0.1", + "data": "2001::, 200a::bf, 2001::c0a8:1" + } + ] + } +} diff --git a/keama/tests/ipaddrhost4.in4 b/keama/tests/ipaddrhost4.in4 new file mode 100644 index 00000000..1c43f81b --- /dev/null +++ b/keama/tests/ipaddrhost4.in4 @@ -0,0 +1,8 @@ +# hostname config + +host test1 { + hardware ethernet 00:07:0E:36:48:19; + fixed-address www.isc.org; +} + +subnet 149.20.64.0 netmask 255.255.255.128 { } diff --git a/keama/tests/ipaddrhost4.out b/keama/tests/ipaddrhost4.out new file mode 100644 index 00000000..22a36643 --- /dev/null +++ b/keama/tests/ipaddrhost4.out @@ -0,0 +1,24 @@ +{ + # hostname config + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "host-reservation-identifiers": [ + "hw-address" + ], + "reservation-mode": "global", + "reservations": [ + { + "hostname": "test1", + "hw-address": "00:07:0e:36:48:19", + "ip-address": "151.101.122.217" + } + ], + "subnet4": [ + { + "id": 1, + "subnet": "149.20.64.0/25" + } + ] + } +} diff --git a/keama/tests/ipaddrs4.notyet4 b/keama/tests/ipaddrs4.notyet4 new file mode 100644 index 00000000..6430e0f9 --- /dev/null +++ b/keama/tests/ipaddrs4.notyet4 @@ -0,0 +1,11 @@ +# hostname config + +# authoritative is mandatory +authoritative; + +host test1 { + hardware ethernet 00:07:0E:36:48:19; + # www.apple.fr has multiple addresses but they are not returned + # in a stable order + fixed-address www.apple.fr; +} diff --git a/keama/tests/lifetime4.ind b/keama/tests/lifetime4.ind new file mode 100644 index 00000000..4e8594ab --- /dev/null +++ b/keama/tests/lifetime4.ind @@ -0,0 +1,5 @@ +# DHCPv4 use ISC DHCP default lifetimes but not when configured. + +min-lease-time 3600; +default-lease-time 7200; +max-lease-time 14400; diff --git a/keama/tests/lifetime4.out b/keama/tests/lifetime4.out new file mode 100644 index 00000000..e5360af0 --- /dev/null +++ b/keama/tests/lifetime4.out @@ -0,0 +1,8 @@ +{ + # DHCPv4 use ISC DHCP default lifetimes but not when configured. + "Dhcp4": { + "min-valid-lifetime": 3600, + "valid-lifetime": 7200, + "max-valid-lifetime": 14400 + } +} diff --git a/keama/tests/lifetime6.inD b/keama/tests/lifetime6.inD new file mode 100644 index 00000000..4e8594ab --- /dev/null +++ b/keama/tests/lifetime6.inD @@ -0,0 +1,5 @@ +# DHCPv4 use ISC DHCP default lifetimes but not when configured. + +min-lease-time 3600; +default-lease-time 7200; +max-lease-time 14400; diff --git a/keama/tests/lifetime6.out b/keama/tests/lifetime6.out new file mode 100644 index 00000000..5cc28de0 --- /dev/null +++ b/keama/tests/lifetime6.out @@ -0,0 +1,8 @@ +{ + # DHCPv4 use ISC DHCP default lifetimes but not when configured. + "Dhcp6": { + "min-valid-lifetime": 3600, + "valid-lifetime": 7200, + "max-valid-lifetime": 14400 + } +} diff --git a/keama/tests/lifetimedef4.ind b/keama/tests/lifetimedef4.ind new file mode 100644 index 00000000..881edf57 --- /dev/null +++ b/keama/tests/lifetimedef4.ind @@ -0,0 +1 @@ +# DHCPv4 use ISC DHCP default lifetimes diff --git a/keama/tests/lifetimedef4.out b/keama/tests/lifetimedef4.out new file mode 100644 index 00000000..1e194bf4 --- /dev/null +++ b/keama/tests/lifetimedef4.out @@ -0,0 +1,11 @@ +{ + # DHCPv4 use ISC DHCP default lifetimes + "Dhcp4": { + /// Use ISC DHCP default lifetime + "valid-lifetime": 43200, + /// Use ISC DHCP min lifetime + "min-valid-lifetime": 300, + /// Use ISC DHCP max lifetime + "max-valid-lifetime": 86400 + } +} diff --git a/keama/tests/lifetimedef6.inD b/keama/tests/lifetimedef6.inD new file mode 100644 index 00000000..59718e40 --- /dev/null +++ b/keama/tests/lifetimedef6.inD @@ -0,0 +1 @@ +# DHCPv6 use ISC DHCP default lifetimes diff --git a/keama/tests/lifetimedef6.out b/keama/tests/lifetimedef6.out new file mode 100644 index 00000000..274997d1 --- /dev/null +++ b/keama/tests/lifetimedef6.out @@ -0,0 +1,11 @@ +{ + # DHCPv6 use ISC DHCP default lifetimes + "Dhcp6": { + /// Use ISC DHCP default lifetime + "valid-lifetime": 43200, + /// Use ISC DHCP min lifetime + "min-valid-lifetime": 300, + /// Use ISC DHCP max lifetime + "max-valid-lifetime": 86400 + } +} diff --git a/keama/tests/listarray.err b/keama/tests/listarray.err new file mode 100644 index 00000000..14082605 --- /dev/null +++ b/keama/tests/listarray.err @@ -0,0 +1,7 @@ +# option definition config + +# options +option space foobar; + +# arrays of domain lists are forbidden +option foobar.array-list code 1 = array of domain-list; diff --git a/keama/tests/listarray.msg b/keama/tests/listarray.msg new file mode 100644 index 00000000..2a363531 --- /dev/null +++ b/keama/tests/listarray.msg @@ -0,0 +1 @@ +listarray.err line 7: arrays of text strings not yet supported. diff --git a/keama/tests/minimal4.in4 b/keama/tests/minimal4.in4 new file mode 100644 index 00000000..cfcc1282 --- /dev/null +++ b/keama/tests/minimal4.in4 @@ -0,0 +1,4 @@ +# DHCPv4 minimal config + +# empty configs are not accepted by Kea +default-lease-time 1800; diff --git a/keama/tests/minimal4.out b/keama/tests/minimal4.out new file mode 100644 index 00000000..e256972c --- /dev/null +++ b/keama/tests/minimal4.out @@ -0,0 +1,7 @@ +{ + # DHCPv4 minimal config + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800 + } +} diff --git a/keama/tests/minimal6.in6 b/keama/tests/minimal6.in6 new file mode 100644 index 00000000..57c83491 --- /dev/null +++ b/keama/tests/minimal6.in6 @@ -0,0 +1,4 @@ +# DHCPv6 minimal config + +# empty configs are not accepted by Kea +default-lease-time 1800; diff --git a/keama/tests/minimal6.out b/keama/tests/minimal6.out new file mode 100644 index 00000000..c07d37fd --- /dev/null +++ b/keama/tests/minimal6.out @@ -0,0 +1,7 @@ +{ + # DHCPv6 minimal config + # empty configs are not accepted by Kea + "Dhcp6": { + "valid-lifetime": 1800 + } +} diff --git a/keama/tests/mixedarray.err b/keama/tests/mixedarray.err new file mode 100644 index 00000000..507aca78 --- /dev/null +++ b/keama/tests/mixedarray.err @@ -0,0 +1,7 @@ +# option definition config + +# options +option space foobar; + +# not uniform arrays are forbidden +option foobar.nestarray code 1 = { array of { unsigned integer 32 } }; diff --git a/keama/tests/mixedarray.msg b/keama/tests/mixedarray.msg new file mode 100644 index 00000000..c409ad01 --- /dev/null +++ b/keama/tests/mixedarray.msg @@ -0,0 +1 @@ +mixedarray.err line 7: only uniform array inside record. diff --git a/keama/tests/nestarray.err b/keama/tests/nestarray.err new file mode 100644 index 00000000..58f16bbe --- /dev/null +++ b/keama/tests/nestarray.err @@ -0,0 +1,7 @@ +# option definition config + +# options +option space foobar; + +# nested arrays are forbidden +option foobar.nestarray code 1 = array of array of unsigned integer 32; diff --git a/keama/tests/nestarray.msg b/keama/tests/nestarray.msg new file mode 100644 index 00000000..92335fad --- /dev/null +++ b/keama/tests/nestarray.msg @@ -0,0 +1 @@ +nestarray.err line 7: no nested arrays. diff --git a/keama/tests/noauth4.in4 b/keama/tests/noauth4.in4 new file mode 100644 index 00000000..e2fce24c --- /dev/null +++ b/keama/tests/noauth4.in4 @@ -0,0 +1,7 @@ +# no(t) authoritative config + +# authoritative is no longer mandatory +#authoritative; + +subnet 10.5.5.0 netmask 255.255.255.224 { } + diff --git a/keama/tests/noauth4.out b/keama/tests/noauth4.out new file mode 100644 index 00000000..8dac17f7 --- /dev/null +++ b/keama/tests/noauth4.out @@ -0,0 +1,15 @@ +{ + # no(t) authoritative config + # authoritative is no longer mandatory + #authoritative; + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "subnet4": [ + { + "id": 1, + "subnet": "10.5.5.0/27" + } + ] + } +} diff --git a/keama/tests/noauth6.in6 b/keama/tests/noauth6.in6 new file mode 100644 index 00000000..42894e91 --- /dev/null +++ b/keama/tests/noauth6.in6 @@ -0,0 +1,6 @@ +# no(t) authoritative config + +# authoritative is no longer mandatory +#authoritative; + +subnet6 2001::/64 { } diff --git a/keama/tests/noauth6.out b/keama/tests/noauth6.out new file mode 100644 index 00000000..93f8429e --- /dev/null +++ b/keama/tests/noauth6.out @@ -0,0 +1,15 @@ +{ + # no(t) authoritative config + # authoritative is no longer mandatory + #authoritative; + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp6": { + "subnet6": [ + { + "id": 1, + "subnet": "2001::/64" + } + ] + } +} diff --git a/keama/tests/noclass.err b/keama/tests/noclass.err new file mode 100644 index 00000000..8aed2e34 --- /dev/null +++ b/keama/tests/noclass.err @@ -0,0 +1,7 @@ +# orphan subclass declaration config + +# class declaration +subclass "foobar" "abcd { + default-lease-time 1800; +} + diff --git a/keama/tests/noclass.msg b/keama/tests/noclass.msg new file mode 100644 index 00000000..ab8719e3 --- /dev/null +++ b/keama/tests/noclass.msg @@ -0,0 +1 @@ +noclass.err line 4: no class named foobar diff --git a/keama/tests/noinclude.err b/keama/tests/noinclude.err new file mode 100644 index 00000000..f39f388b --- /dev/null +++ b/keama/tests/noinclude.err @@ -0,0 +1,3 @@ +# no file include config + +include "do-not-exist"; diff --git a/keama/tests/noinclude.msg b/keama/tests/noinclude.msg new file mode 100644 index 00000000..91b2284b --- /dev/null +++ b/keama/tests/noinclude.msg @@ -0,0 +1 @@ +noinclude.err line 3: Can't open do-not-exist: No such file or directory diff --git a/keama/tests/nosubclass.err b/keama/tests/nosubclass.err new file mode 100644 index 00000000..cf421259 --- /dev/null +++ b/keama/tests/nosubclass.err @@ -0,0 +1,11 @@ +# bad (missing selector) subclass declaration config + +# superclass declaration +class "foobar" { + match substring(option vendor-class-identifier, 0, 3); +} + +# subclass declaration +subclass "foobar" { + default-lease-time 1800; +} diff --git a/keama/tests/nosubclass.msg b/keama/tests/nosubclass.msg new file mode 100644 index 00000000..762e440f --- /dev/null +++ b/keama/tests/nosubclass.msg @@ -0,0 +1 @@ +nosubclass.err line 9: Expecting string or hex list. diff --git a/keama/tests/nosuperclass.err b/keama/tests/nosuperclass.err new file mode 100644 index 00000000..5d2e329f --- /dev/null +++ b/keama/tests/nosuperclass.err @@ -0,0 +1,11 @@ +# bas superclass subclass declaration config + +# class (but not superclass) declaration +class "foobar" { + match if substring(option vendor-class-identifier, 0, 3) = "APC"; +} + +# subclass declaration +subclass "foobar" "abcd { + default-lease-time 1800; +} diff --git a/keama/tests/nosuperclass.msg b/keama/tests/nosuperclass.msg new file mode 100644 index 00000000..ec448cad --- /dev/null +++ b/keama/tests/nosuperclass.msg @@ -0,0 +1 @@ +nosuperclass.err line 9: found class name foobar but it is not a suitable superclass diff --git a/keama/tests/notbx4.in4 b/keama/tests/notbx4.in4 new file mode 100644 index 00000000..6248b8c6 --- /dev/null +++ b/keama/tests/notbx4.in4 @@ -0,0 +1,12 @@ +# not boolean expression + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# use not in a reductible match if +class "reductible" { + match if not (option host-name = "www.example.com"); +} + +# if test is a boolean too +if not check "foo" { add "bar"; } diff --git a/keama/tests/notbx4.out b/keama/tests/notbx4.out new file mode 100644 index 00000000..186cdfff --- /dev/null +++ b/keama/tests/notbx4.out @@ -0,0 +1,30 @@ +{ + # not boolean expression + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800, + "client-classes": [ + # use not in a reductible match if + { + "name": "reductible", + /// from: match if not (option dhcp.host-name) = 'www.example.com' + "test": "not (option[12].hex == 'www.example.com')" + } + ] +// # if test is a boolean too +// "statement": { +// "if": { +// "condition": { +// "not": { +// "check": "foo" +// } +// }, +// "then": [ +// { +// "add-class": "bar" +// } +// ] +// } +// } + } +} diff --git a/keama/tests/notnotbx4.in4 b/keama/tests/notnotbx4.in4 new file mode 100644 index 00000000..e9d98800 --- /dev/null +++ b/keama/tests/notnotbx4.in4 @@ -0,0 +1,15 @@ +# double not boolean expression + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# use double not in a reductible match if +class "reductible" { + match if not (not ((option host-name = "www.example.com") or + (option host-name = "www.example.org"))); +} + +# use not with != +class "other" { + match if not (option host-name != "www.example.com"); +} diff --git a/keama/tests/notnotbx4.out b/keama/tests/notnotbx4.out new file mode 100644 index 00000000..ae1878cc --- /dev/null +++ b/keama/tests/notnotbx4.out @@ -0,0 +1,21 @@ +{ + # double not boolean expression + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800, + "client-classes": [ + # use double not in a reductible match if + { + "name": "reductible", + /// from: match if not not ((option dhcp.host-name) = 'www.example.com') or ((option dhcp.host-name) = 'www.example.org') + "test": "(option[12].hex == 'www.example.com') or (option[12].hex == 'www.example.org')" + }, + # use not with != + { + "name": "other", + /// from: match if not (option dhcp.host-name) != 'www.example.com' + "test": "option[12].hex == 'www.example.com'" + } + ] + } +} diff --git a/keama/tests/nxdomainnx6.in6 b/keama/tests/nxdomainnx6.in6 new file mode 100644 index 00000000..f2a2e0f5 --- /dev/null +++ b/keama/tests/nxdomainnx6.in6 @@ -0,0 +1,12 @@ +# nxdomain numeric expression + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# reduce literals +class "literal" { + match if substring(option dhcp6.interface-id, 0, nxdomain % 128) = ab:cd; +} + +# raw +option dhcp6.interface-id = encode-int(nxdomain + bound, 32); diff --git a/keama/tests/nxdomainnx6.out b/keama/tests/nxdomainnx6.out new file mode 100644 index 00000000..c90552e4 --- /dev/null +++ b/keama/tests/nxdomainnx6.out @@ -0,0 +1,28 @@ +{ + # nxdomain numeric expression + # empty configs are not accepted by Kea + "Dhcp6": { + "valid-lifetime": 1800, + "client-classes": [ + # reduce literals + { + "name": "literal", + /// from: match if (substring(option dhcp6.interface-id, 0, 393231 % 128)) = 0xabcd + "test": "substring(option[18].hex,0,15) == 0xabcd" + } + ], + "option-data": [ + # raw + { + "space": "dhcp6", + "name": "interface-id", + "code": 18, + "csv-format": false, +// /// constant DHCP_R_NXDOMAIN(393231) +// /// constant S_BOUND(5) +// "original-data": "\u0000\u0006\u0000\u0014", + "data": "00060014" + } + ] + } +} diff --git a/keama/tests/onxsc4.in4 b/keama/tests/onxsc4.in4 new file mode 100644 index 00000000..f62b720c --- /dev/null +++ b/keama/tests/onxsc4.in4 @@ -0,0 +1,12 @@ +# on executable statement construct + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# empty on statement +on expiry; + +# another one +on commit or release { + execute ("myscript", packet(5, 2)); +} diff --git a/keama/tests/onxsc4.out b/keama/tests/onxsc4.out new file mode 100644 index 00000000..95134a52 --- /dev/null +++ b/keama/tests/onxsc4.out @@ -0,0 +1,34 @@ +{ + # on executable statement construct + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800 +// # empty on statement +// "statement": { +// "on": { +// "condition": "expiry" +// } +// } +// # another one +// "statement": { +// "on": { +// "condition": "commit or release", +// "body": [ +// { +// "execute": { +// "command": "myscript", +// "arguments": [ +// { +// "packet": { +// "offset": 5, +// "length": 2 +// } +// } +// ] +// } +// } +// ] +// } +// } + } +} diff --git a/keama/tests/optdatagrouppool4.in4 b/keama/tests/optdatagrouppool4.in4 new file mode 100644 index 00000000..999abea2 --- /dev/null +++ b/keama/tests/optdatagrouppool4.in4 @@ -0,0 +1,18 @@ +# embedded option-data in DHCPv4 pool config + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# subnet declaration +subnet 10.5.5.0 netmask 255.255.255.224 { + # pool declaration + pool { + # avoid empty pool + range 10.5.5.5 10.5.5.10; + # for a silly reason option-data is not allowed in DHCPv4 pools + # try to fool this rule using a group + group fool { + option domain-search "example.com", "example.org"; + } + } +} diff --git a/keama/tests/optdatagrouppool4.out b/keama/tests/optdatagrouppool4.out new file mode 100644 index 00000000..4fb102d3 --- /dev/null +++ b/keama/tests/optdatagrouppool4.out @@ -0,0 +1,23 @@ +{ + # embedded option-data in DHCPv4 pool config + # empty configs are not accepted by Kea + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "valid-lifetime": 1800, + "subnet4": [ + # subnet declaration + { + "id": 1, + "subnet": "10.5.5.0/27", + "pools": [ + # pool declaration + { + # avoid empty pool + "pool": "10.5.5.5 - 10.5.5.10" + } + ] + } + ] + } +} diff --git a/keama/tests/optiondata4.in4 b/keama/tests/optiondata4.in4 new file mode 100644 index 00000000..6c5ab639 --- /dev/null +++ b/keama/tests/optiondata4.in4 @@ -0,0 +1,48 @@ +# option data config + +# options +option space foobar; + +option ip-forwarding on; + +option foobar.fmt-b-si8 code 3 = signed integer 8; +option foobar.fmt-b-si8 -100; + +option default-ip-ttl 20; + +option foobar.fmt-s-si16 code 6 = signed integer 16; +option foobar.fmt-s-si16 -1000; + +option boot-size 16000; + +option time-offset -1200; + +option path-mtu-aging-timeout 86400; + +option swap-server 10.5.5.1; + +option foobar.fmt-6 code 12 = ip6-address; +option foobar.fmt-6 2001::1; + +option foobar.fmt-d code 13 = domain-name; +# Silly, d aka domain-name are without quotes, D aka domain-list are with +option foobar.fmt-d www.example.com; + +option bcms-controller-names "foo.bar", "www.no-where.biz"; + +option domain-search "example.com", "example.org"; + +option tftp-server-name "my-server"; + +option dhcp-client-identifier 01:02:aa:bb; + +option foobar.fmt-Z code 18 = zerolen; +option foobar.fmt-Z; + +option foobar.fmt-Ba code 50 = array of unsigned integer 8; +option dhcp-parameter-request-list 1, 2, 3; + +option foobar.fmt-fB code 100 = { boolean, unsigned integer 8 }; +option foobar.fmt-fB off 66; + +option routers 10.5.5.1, 10.5.5.2, 10.5.5.3; diff --git a/keama/tests/optiondata4.out b/keama/tests/optiondata4.out new file mode 100644 index 00000000..eca745d9 --- /dev/null +++ b/keama/tests/optiondata4.out @@ -0,0 +1,173 @@ +{ + # option data config + # options + "Dhcp4": { + "option-data": [ + { + "space": "dhcp4", + "name": "ip-forwarding", + "code": 19, +// "original-data": "on", + /// canonized booleans to lowercase true or false + "data": "true" + }, + { + "space": "foobar", + "name": "fmt-b-si8", + "code": 3, + "data": "-100" + }, + { + "space": "dhcp4", + "name": "default-ip-ttl", + "code": 23, + "data": "20" + }, + { + "space": "foobar", + "name": "fmt-s-si16", + "code": 6, + "data": "-1000" + }, + { + "space": "dhcp4", + "name": "boot-size", + "code": 13, + "data": "16000" + }, + { + "space": "dhcp4", + "name": "time-offset", + "code": 2, + "data": "-1200" + }, + { + "space": "dhcp4", + "name": "path-mtu-aging-timeout", + "code": 24, + "data": "86400" + }, + { + "space": "dhcp4", + "name": "swap-server", + "code": 16, + "data": "10.5.5.1" + }, + { + "space": "foobar", + "name": "fmt-6", + "code": 12, + "data": "2001::1" + }, + # Silly, d aka domain-name are without quotes, D aka domain-list are with + { + "space": "foobar", + "name": "fmt-d", + "code": 13, + "data": "www.example.com" + }, + { + "space": "dhcp4", + "name": "bcms-controller-names", + "code": 88, +// "original-data": "\"foo.bar\", \"www.no-where.biz\"", + "data": "foo.bar, www.no-where.biz" + }, + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + }, + { + "space": "dhcp4", + "name": "tftp-server-name", + "code": 66, + "data": "my-server" + }, + { + "space": "dhcp4", + "name": "dhcp-client-identifier", + "code": 61, +// "original-data": "01:02:aa:bb", + "csv-format": false, + "data": "0102aabb" + }, + { + "space": "foobar", + "name": "fmt-Z", + "code": 18, + "data": "" + }, + /// Possible PRL hack + /// Consider setting "always-send" to true when setting data for relevant options, cf Kea #250 + { + "space": "dhcp4", + "name": "dhcp-parameter-request-list", + "code": 55, + "data": "1, 2, 3" + }, + { + "space": "foobar", + "name": "fmt-fB", + "code": 100, +// "original-data": "off 66", + /// canonized booleans to lowercase true or false + "data": "false, 66" + }, + { + "space": "dhcp4", + "name": "routers", + "code": 3, + "data": "10.5.5.1, 10.5.5.2, 10.5.5.3" + } + ], + "option-def": [ + { + "space": "foobar", + "name": "fmt-b-si8", + "code": 3, + "type": "int8" + }, + { + "space": "foobar", + "name": "fmt-s-si16", + "code": 6, + "type": "int16" + }, + { + "space": "foobar", + "name": "fmt-6", + "code": 12, + "type": "ipv6-address" + }, + { + "space": "foobar", + "name": "fmt-d", + "code": 13, + "type": "fqdn" + }, + { + "space": "foobar", + "name": "fmt-Z", + "code": 18, + "type": "empty" + }, + { + "space": "foobar", + "name": "fmt-Ba", + "code": 50, + "array": true, + "type": "uint8" + }, + { + "space": "foobar", + "name": "fmt-fB", + "code": 100, + "record-types": "boolean, uint8", + "type": "record" + } + ] + } +} diff --git a/keama/tests/optiondata6.in6 b/keama/tests/optiondata6.in6 new file mode 100644 index 00000000..5af5b87b --- /dev/null +++ b/keama/tests/optiondata6.in6 @@ -0,0 +1,49 @@ +# option data config + +# options +option space foobar; + +option foobar.fmt-f code 1 = boolean; +option foobar.fmt-f true; + +option foobar.fmt-b-i8 code 2 = integer 8; +option foobar.fmt-b-i8 -10; + +option dhcp6.preference 20; + +option foobar.fmt-s-i16 code 5 = integer 16; +option foobar.fmt-s-i16 -10000; + +option foobar.fmt-S-ui16 code 7 = unsigned integer 16; +option foobar.fmt-S-ui16 36000; + +option foobar.fmt-l-i32 code 8 = integer 32; +option foobar.fmt-l-i32 -86400; + +option dhcp6.clt-time 604800; + +option foobar.fmt-I code 11 = ip-address; +option foobar.fmt-I 10.5.5.1; + +option foobar.fmt-6 code 12 = ip6-address; +option dhcp6.unicast 2001::1; + +option foobar.fmt-d code 13 = domain-name; +# Silly, d aka domain-name are without quotes, D aka domain-list are with +option foobar.fmt-d www.example.com; + +option dhcp6.domain-search "example.com", "example.org"; + +option dhcp6.bootfile-url "http://nowhere/"; + +option dhcp6.geoconf-civic de:ad:be:ef; + +option dhcp6.rapid-commit; + +option foobar.fmt-Ba code 50 = array of unsigned integer 8; +option foobar.fmt-Ba 1, 2, 3; + +option foobar.fmt-fB code 100 = { boolean, unsigned integer 8 }; +option foobar.fmt-fB false 66; + +option dhcp6.name-servers 2a01:e00::2, 2a01:e00::2; diff --git a/keama/tests/optiondata6.out b/keama/tests/optiondata6.out new file mode 100644 index 00000000..b01ba36b --- /dev/null +++ b/keama/tests/optiondata6.out @@ -0,0 +1,178 @@ +{ + # option data config + # options + "Dhcp6": { + "option-def": [ + { + "space": "foobar", + "name": "fmt-f", + "code": 1, + "type": "boolean" + }, + { + "space": "foobar", + "name": "fmt-b-i8", + "code": 2, + "type": "int8" + }, + { + "space": "foobar", + "name": "fmt-s-i16", + "code": 5, + "type": "int16" + }, + { + "space": "foobar", + "name": "fmt-S-ui16", + "code": 7, + "type": "uint16" + }, + { + "space": "foobar", + "name": "fmt-l-i32", + "code": 8, + "type": "int32" + }, + { + "space": "foobar", + "name": "fmt-I", + "code": 11, + "type": "ipv4-address" + }, + { + "space": "foobar", + "name": "fmt-6", + "code": 12, + "type": "ipv6-address" + }, + { + "space": "foobar", + "name": "fmt-d", + "code": 13, + "type": "fqdn" + }, + { + "space": "foobar", + "name": "fmt-Ba", + "code": 50, + "array": true, + "type": "uint8" + }, + { + "space": "foobar", + "name": "fmt-fB", + "code": 100, + "record-types": "boolean, uint8", + "type": "record" + } + ], + "option-data": [ + { + "space": "foobar", + "name": "fmt-f", + "code": 1, + "data": "true" + }, + { + "space": "foobar", + "name": "fmt-b-i8", + "code": 2, + "data": "-10" + }, + { + "space": "dhcp6", + "name": "preference", + "code": 7, + "data": "20" + }, + { + "space": "foobar", + "name": "fmt-s-i16", + "code": 5, + "data": "-10000" + }, + { + "space": "foobar", + "name": "fmt-S-ui16", + "code": 7, + "data": "36000" + }, + { + "space": "foobar", + "name": "fmt-l-i32", + "code": 8, + "data": "-86400" + }, + { + "space": "dhcp6", + "name": "clt-time", + "code": 46, + "data": "604800" + }, + { + "space": "foobar", + "name": "fmt-I", + "code": 11, + "data": "10.5.5.1" + }, + { + "space": "dhcp6", + "name": "unicast", + "code": 12, + "data": "2001::1" + }, + # Silly, d aka domain-name are without quotes, D aka domain-list are with + { + "space": "foobar", + "name": "fmt-d", + "code": 13, + "data": "www.example.com" + }, + { + "space": "dhcp6", + "name": "domain-search", + "code": 24, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + }, + { + "space": "dhcp6", + "name": "bootfile-url", + "code": 59, + "data": "http://nowhere/" + }, + { + "space": "dhcp6", + "name": "geoconf-civic", + "code": 36, +// "original-data": "de:ad:be:ef", + "csv-format": false, + "data": "deadbeef" + }, + { + "space": "dhcp6", + "name": "rapid-commit", + "code": 14, + "data": "" + }, + { + "space": "foobar", + "name": "fmt-Ba", + "code": 50, + "data": "1, 2, 3" + }, + { + "space": "foobar", + "name": "fmt-fB", + "code": 100, + "data": "false, 66" + }, + { + "space": "dhcp6", + "name": "dns-servers", + "code": 23, + "data": "2a01:e00::2, 2a01:e00::2" + } + ] + } +} diff --git a/keama/tests/optiondatapool4.in4 b/keama/tests/optiondatapool4.in4 new file mode 100644 index 00000000..58751b01 --- /dev/null +++ b/keama/tests/optiondatapool4.in4 @@ -0,0 +1,12 @@ +# option-data in DHCPv4 pool config + +# subnet declaration +subnet 10.5.5.0 netmask 255.255.255.224 { + # pool declaration + pool { + # avoid empty pool + range 10.5.5.5 10.5.5.10; + # fixed now + option domain-search "example.com", "example.org"; + } +} diff --git a/keama/tests/optiondatapool4.out b/keama/tests/optiondatapool4.out new file mode 100644 index 00000000..1d3ee427 --- /dev/null +++ b/keama/tests/optiondatapool4.out @@ -0,0 +1,31 @@ +{ + # option-data in DHCPv4 pool config + # subnet declaration + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "subnet4": [ + { + "id": 1, + "subnet": "10.5.5.0/27", + "pools": [ + # pool declaration + { + # avoid empty pool + "pool": "10.5.5.5 - 10.5.5.10", + "option-data": [ + # fixed now + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ] + } + ] + } + ] + } +} diff --git a/keama/tests/optiondatapool6.in6 b/keama/tests/optiondatapool6.in6 new file mode 100644 index 00000000..d09ebaf9 --- /dev/null +++ b/keama/tests/optiondatapool6.in6 @@ -0,0 +1,13 @@ +# option-data in DHCPv6 pool config + +# subnet declaration +subnet6 2001::/64 { + # pool declaration + pool6 { + # avoid empty pool + range6 2001::100 2001::200; + # for a silly reason option-data is not allowed in DHCPv4 pools + # but allowed in DHCPv6 pools + option dhcp6.domain-search "example.com", "example.org"; + } +} diff --git a/keama/tests/optiondatapool6.out b/keama/tests/optiondatapool6.out new file mode 100644 index 00000000..48f722d4 --- /dev/null +++ b/keama/tests/optiondatapool6.out @@ -0,0 +1,32 @@ +{ + # option-data in DHCPv6 pool config + # subnet declaration + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp6": { + "subnet6": [ + { + "id": 1, + "subnet": "2001::/64", + "pools": [ + # pool declaration + { + # avoid empty pool + "pool": "2001::100 - 2001::200", + "option-data": [ + # for a silly reason option-data is not allowed in DHCPv4 pools + # but allowed in DHCPv6 pools + { + "space": "dhcp6", + "name": "domain-search", + "code": 24, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ] + } + ] + } + ] + } +} diff --git a/keama/tests/optiondecl4.in4 b/keama/tests/optiondecl4.in4 new file mode 100644 index 00000000..4be48838 --- /dev/null +++ b/keama/tests/optiondecl4.in4 @@ -0,0 +1,27 @@ +# option definition config + +# options +option space foobar; + +option foobar.fmt-f code 1 = boolean; +option foobar.fmt-b-i8 code 2 = integer 8; +option foobar.fmt-b-si8 code 3 = signed integer 8; +option foobar.fmt-B-ui8 code 4 = unsigned integer 8; +option foobar.fmt-s-i16 code 5 = integer 16; +option foobar.fmt-s-si16 code 6 = signed integer 16; +option foobar.fmt-S-ui16 code 7 = unsigned integer 16; +option foobar.fmt-l-i32 code 8 = integer 32; +option foobar.fmt-l-si32 code 9 = signed integer 32; +option foobar.fmt-L-ui32 code 10 = unsigned integer 32; +option foobar.fmt-I code 11 = ip-address; +option foobar.fmt-6 code 12 = ip6-address; +option foobar.fmt-d code 13 = domain-name; +option foobar.fmt-D-list code 14 = domain-list; +option foobar.fmt-Dc code 15 = domain-list compressed; +option foobar.fmt-t code 16 = text; +option foobar.fmt-X code 17 = string; +option foobar.fmt-Z code 18 = zerolen; + +option foobar.fmt-Ba code 50 = array of unsigned integer 8; +option foobar.fmt-fB code 100 = { boolean, unsigned integer 8 }; +option foobar.fmt-Ia code 150 = { unsigned integer 32, array of boolean }; diff --git a/keama/tests/optiondecl4.out b/keama/tests/optiondecl4.out new file mode 100644 index 00000000..f74aa32f --- /dev/null +++ b/keama/tests/optiondecl4.out @@ -0,0 +1,143 @@ +{ + # option definition config + # options + "Dhcp4": { + "option-def": [ + { + "space": "foobar", + "name": "fmt-f", + "code": 1, + "type": "boolean" + }, + { + "space": "foobar", + "name": "fmt-b-i8", + "code": 2, + "type": "int8" + }, + { + "space": "foobar", + "name": "fmt-b-si8", + "code": 3, + "type": "int8" + }, + { + "space": "foobar", + "name": "fmt-B-ui8", + "code": 4, + "type": "uint8" + }, + { + "space": "foobar", + "name": "fmt-s-i16", + "code": 5, + "type": "int16" + }, + { + "space": "foobar", + "name": "fmt-s-si16", + "code": 6, + "type": "int16" + }, + { + "space": "foobar", + "name": "fmt-S-ui16", + "code": 7, + "type": "uint16" + }, + { + "space": "foobar", + "name": "fmt-l-i32", + "code": 8, + "type": "int32" + }, + { + "space": "foobar", + "name": "fmt-l-si32", + "code": 9, + "type": "int32" + }, + { + "space": "foobar", + "name": "fmt-L-ui32", + "code": 10, + "type": "uint32" + }, + { + "space": "foobar", + "name": "fmt-I", + "code": 11, + "type": "ipv4-address" + }, + { + "space": "foobar", + "name": "fmt-6", + "code": 12, + "type": "ipv6-address" + }, + { + "space": "foobar", + "name": "fmt-d", + "code": 13, + "type": "fqdn" + }, + { + "space": "foobar", + "name": "fmt-D-list", + "code": 14, + "array": true, + "type": "fqdn" + }, + { + "space": "foobar", + "name": "fmt-Dc", + "code": 15, + "array": true, + "type": "fqdn" + }, + { + "space": "foobar", + "name": "fmt-t", + "code": 16, + "type": "string" + }, + { + "space": "foobar", + "name": "fmt-X", + "code": 17, + "type": "string" + }, + { + "space": "foobar", + "name": "fmt-Z", + "code": 18, + "type": "empty" + }, + { + "space": "foobar", + "name": "fmt-Ba", + "code": 50, + "array": true, + "type": "uint8" + }, + { + "space": "foobar", + "name": "fmt-fB", + "code": 100, + "record-types": "boolean, uint8", + "type": "record" + }, + /// unsupported array inside a record + { + "space": "foobar", + "name": "fmt-Ia", + "code": 150, +// "array": true, +// "definition": "{ uint32, array of boolean}", + /// Option definition is not compatible with Kea + /// Fallback to full binary + "type": "binary" + } + ] + } +} diff --git a/keama/tests/optiondecl6.in6 b/keama/tests/optiondecl6.in6 new file mode 100644 index 00000000..37662c0d --- /dev/null +++ b/keama/tests/optiondecl6.in6 @@ -0,0 +1,27 @@ +# option definition config + +# options +option space foobar; + +option foobar.fmt-f code 1 = boolean; +option foobar.fmt-b-i8 code 2 = integer 8; +option foobar.fmt-b-si8 code 3 = signed integer 8; +option foobar.fmt-B-ui8 code 4 = unsigned integer 8; +option foobar.fmt-s-i16 code 5 = integer 16; +option foobar.fmt-s-si16 code 6 = signed integer 16; +option foobar.fmt-S-ui16 code 7 = unsigned integer 16; +option foobar.fmt-l-i32 code 8 = integer 32; +option foobar.fmt-l-si32 code 9 = signed integer 32; +option foobar.fmt-L-ui32 code 10 = unsigned integer 32; +option foobar.fmt-I code 11 = ip-address; +option foobar.fmt-6 code 12 = ip6-address; +option foobar.fmt-d code 13 = domain-name; +option foobar.fmt-D-list code 14 = domain-list; +#option foobar.fmt-Dc code 15 = domain-list compressed; +option foobar.fmt-t code 16 = text; +option foobar.fmt-X code 17 = string; +option foobar.fmt-Z code 18 = zerolen; + +option foobar.fmt-Ba code 50 = array of unsigned integer 8; +option foobar.fmt-fB code 100 = { boolean, unsigned integer 8 }; +option foobar.fmt-Lfa code 150 = { unsigned integer 32, array of boolean }; diff --git a/keama/tests/optiondecl6.out b/keama/tests/optiondecl6.out new file mode 100644 index 00000000..d4cf828b --- /dev/null +++ b/keama/tests/optiondecl6.out @@ -0,0 +1,137 @@ +{ + # option definition config + # options + "Dhcp6": { + "option-def": [ + { + "space": "foobar", + "name": "fmt-f", + "code": 1, + "type": "boolean" + }, + { + "space": "foobar", + "name": "fmt-b-i8", + "code": 2, + "type": "int8" + }, + { + "space": "foobar", + "name": "fmt-b-si8", + "code": 3, + "type": "int8" + }, + { + "space": "foobar", + "name": "fmt-B-ui8", + "code": 4, + "type": "uint8" + }, + { + "space": "foobar", + "name": "fmt-s-i16", + "code": 5, + "type": "int16" + }, + { + "space": "foobar", + "name": "fmt-s-si16", + "code": 6, + "type": "int16" + }, + { + "space": "foobar", + "name": "fmt-S-ui16", + "code": 7, + "type": "uint16" + }, + { + "space": "foobar", + "name": "fmt-l-i32", + "code": 8, + "type": "int32" + }, + { + "space": "foobar", + "name": "fmt-l-si32", + "code": 9, + "type": "int32" + }, + { + "space": "foobar", + "name": "fmt-L-ui32", + "code": 10, + "type": "uint32" + }, + { + "space": "foobar", + "name": "fmt-I", + "code": 11, + "type": "ipv4-address" + }, + { + "space": "foobar", + "name": "fmt-6", + "code": 12, + "type": "ipv6-address" + }, + { + "space": "foobar", + "name": "fmt-d", + "code": 13, + "type": "fqdn" + }, + { + "space": "foobar", + "name": "fmt-D-list", + "code": 14, + "array": true, + "type": "fqdn" + }, + #option foobar.fmt-Dc code 15 = domain-list compressed; + { + "space": "foobar", + "name": "fmt-t", + "code": 16, + "type": "string" + }, + { + "space": "foobar", + "name": "fmt-X", + "code": 17, + "type": "string" + }, + { + "space": "foobar", + "name": "fmt-Z", + "code": 18, + "type": "empty" + }, + { + "space": "foobar", + "name": "fmt-Ba", + "code": 50, + "array": true, + "type": "uint8" + }, + { + "space": "foobar", + "name": "fmt-fB", + "code": 100, + "record-types": "boolean, uint8", + "type": "record" + }, + /// unsupported array inside a record + { + "space": "foobar", + "name": "fmt-Lfa", + "code": 150, +// "array": true, +// "definition": "{ uint32, array of boolean}", + /// Option definition is not compatible with Kea + /// Fallback to full binary + "type": "binary" + } + ] + } +} diff --git a/keama/tests/optiondeclBat4.in4 b/keama/tests/optiondeclBat4.in4 new file mode 100644 index 00000000..0901a14f --- /dev/null +++ b/keama/tests/optiondeclBat4.in4 @@ -0,0 +1,6 @@ +# bad Bat (non terminal array) option declaration + +option space foobar; +option foobar.fmt-Bat code 1 = { array of unsigned integer 8, text }; + + diff --git a/keama/tests/optiondeclBat4.out b/keama/tests/optiondeclBat4.out new file mode 100644 index 00000000..e671dcfa --- /dev/null +++ b/keama/tests/optiondeclBat4.out @@ -0,0 +1,18 @@ +{ + # bad Bat (non terminal array) option declaration + "Dhcp4": { + "option-def": [ + /// unsupported array inside a record + { + "space": "foobar", + "name": "fmt-Bat", + "code": 1, +// "array": true, +// "definition": "{ array of uint8, string}", + /// Option definition is not compatible with Kea + /// Fallback to full binary + "type": "binary" + } + ] + } +} diff --git a/keama/tests/optionencap4.in4 b/keama/tests/optionencap4.in4 new file mode 100644 index 00000000..f5dddfdb --- /dev/null +++ b/keama/tests/optionencap4.in4 @@ -0,0 +1,7 @@ +# option definition config + +# options +option space foobar; +option space xyz; + +option foobar.encap code 1 = encapsulate xyz; diff --git a/keama/tests/optionencap4.out b/keama/tests/optionencap4.out new file mode 100644 index 00000000..ee59fc42 --- /dev/null +++ b/keama/tests/optionencap4.out @@ -0,0 +1,15 @@ +{ + # option definition config + # options + "Dhcp4": { + "option-def": [ + { + "space": "foobar", + "name": "encap", + "code": 1, + "type": "empty", + "encapsulate": "xyz" + } + ] + } +} diff --git a/keama/tests/optionencap6.in6 b/keama/tests/optionencap6.in6 new file mode 100644 index 00000000..f5dddfdb --- /dev/null +++ b/keama/tests/optionencap6.in6 @@ -0,0 +1,7 @@ +# option definition config + +# options +option space foobar; +option space xyz; + +option foobar.encap code 1 = encapsulate xyz; diff --git a/keama/tests/optionencap6.out b/keama/tests/optionencap6.out new file mode 100644 index 00000000..733fc714 --- /dev/null +++ b/keama/tests/optionencap6.out @@ -0,0 +1,15 @@ +{ + # option definition config + # options + "Dhcp6": { + "option-def": [ + { + "space": "foobar", + "name": "encap", + "code": 1, + "type": "empty", + "encapsulate": "xyz" + } + ] + } +} diff --git a/keama/tests/optionexpr4.in4 b/keama/tests/optionexpr4.in4 new file mode 100644 index 00000000..bc0889dd --- /dev/null +++ b/keama/tests/optionexpr4.in4 @@ -0,0 +1,16 @@ +# option data expressions + +# options +option mytext code 250 = text; +option mytext = substring("foobar", 1, 3); + +option mydata code 251 = string; +option mydata = concat("\b", "\125\126"); + +option mydata2 code 252 = string; +option mydata2 = aa:bb:cc; + +# not yet +option mydata3 code 253 = string; +option mydata3 = concat(aa, bb, cc); + diff --git a/keama/tests/optionexpr4.out b/keama/tests/optionexpr4.out new file mode 100644 index 00000000..3582b3d7 --- /dev/null +++ b/keama/tests/optionexpr4.out @@ -0,0 +1,81 @@ +{ + # option data expressions + # options + "Dhcp4": { + "option-def": [ + { + "space": "dhcp4", + "name": "mytext", + "code": 250, + "type": "string" + }, + { + "space": "dhcp4", + "name": "mydata", + "code": 251, + "type": "string" + }, + { + "space": "dhcp4", + "name": "mydata2", + "code": 252, + "type": "string" + }, + # not yet + { + "space": "dhcp4", + "name": "mydata3", + "code": 253, + "type": "string" + } + ], + "option-data": [ + { + "space": "dhcp4", + "name": "mytext", + "code": 250, + "csv-format": false, +// "original-data": "oob", + "data": "6f6f62" + }, + { + "space": "dhcp4", + "name": "mydata", + "code": 251, + "csv-format": false, +// "original-data": "\bUV", + "data": "085556" + }, + { + "space": "dhcp4", + "name": "mydata2", + "code": 252, + "csv-format": false, + "data": "aabbcc" + } +// { +// "space": "dhcp4", +// "name": "mydata3", +// "code": 253, +// "csv-format": false, +// "expression": { +// "concat": { +// "left": { +// "const-data": "0xaa" +// }, +// "right": { +// "concat": { +// "left": { +// "const-data": "0xbb" +// }, +// "right": { +// "const-data": "0xcc" +// } +// } +// } +// } +// } +// } + ] + } +} diff --git a/keama/tests/optionspace4.in4 b/keama/tests/optionspace4.in4 new file mode 100644 index 00000000..fa87b6a5 --- /dev/null +++ b/keama/tests/optionspace4.in4 @@ -0,0 +1,15 @@ +# group and class declaration config + +# option spaces +option space foobar; + +option space foo code width 1 length width 1; + +option space bar code width 4 length width 4; + +option space full code width 2 length width 2 hash size 111; + +option foobar.test code 1 = text; + +option foo.test code 1 = text; + diff --git a/keama/tests/optionspace4.out b/keama/tests/optionspace4.out new file mode 100644 index 00000000..53482692 --- /dev/null +++ b/keama/tests/optionspace4.out @@ -0,0 +1,34 @@ +{ + # group and class declaration config + # option spaces + "Dhcp4": { +// "option-space": { +// "name": "bar", +// /// Only code width 1 is supported +// "code-width": 4, +// /// Only length width 1 is supported +// "length-width": 4 +// }, +// "option-space": { +// "name": "full", +// /// Only code width 1 is supported +// "code-width": 2, +// /// Only length width 1 is supported +// "length-width": 2 +// }, + "option-def": [ + { + "space": "foobar", + "name": "test", + "code": 1, + "type": "string" + }, + { + "space": "foo", + "name": "test", + "code": 1, + "type": "string" + } + ] + } +} diff --git a/keama/tests/optionspace6.in6 b/keama/tests/optionspace6.in6 new file mode 100644 index 00000000..a94a665f --- /dev/null +++ b/keama/tests/optionspace6.in6 @@ -0,0 +1,15 @@ +# group and class declaration config + +# option spaces +option space foobar; + +option space foo code width 2 length width 2; + +option space bar code width 4 length width 4; + +option space full code width 1 length width 1 hash size 111; + +option foobar.test code 1 = text; + +option foo.test code 1 = text; + diff --git a/keama/tests/optionspace6.out b/keama/tests/optionspace6.out new file mode 100644 index 00000000..fdc9ea72 --- /dev/null +++ b/keama/tests/optionspace6.out @@ -0,0 +1,34 @@ +{ + # group and class declaration config + # option spaces + "Dhcp6": { +// "option-space": { +// "name": "bar", +// /// Only code width 2 is supported +// "code-width": 4, +// /// Only length width 2 is supported +// "length-width": 4 +// }, +// "option-space": { +// "name": "full", +// /// Only code width 2 is supported +// "code-width": 1, +// /// Only length width 2 is supported +// "length-width": 1 +// }, + "option-def": [ + { + "space": "foobar", + "name": "test", + "code": 1, + "type": "string" + }, + { + "space": "foo", + "name": "test", + "code": 1, + "type": "string" + } + ] + } +} diff --git a/keama/tests/optionvendor4.in4 b/keama/tests/optionvendor4.in4 new file mode 100644 index 00000000..49be45d7 --- /dev/null +++ b/keama/tests/optionvendor4.in4 @@ -0,0 +1,8 @@ +# group and class declaration config + +# vendor option space +option space foobar; + +option vendor.foobar code 12345 = encapsulate foobar; +option foobar.test code 1 = text; +option foobar.test "a test"; diff --git a/keama/tests/optionvendor4.out b/keama/tests/optionvendor4.out new file mode 100644 index 00000000..80a9a5e8 --- /dev/null +++ b/keama/tests/optionvendor4.out @@ -0,0 +1,28 @@ +{ + # group and class declaration config + # vendor option space + "Dhcp4": { + "option-def": [ + { + "space": "vendor-12345", + "name": "test", + "code": 1, + "type": "string" + } + ], + "option-data": [ + { + "space": "dhcp4", + "name": "vivso-suboptions", + "code": 125, + "data": "12345" + }, + { + "space": "vendor-12345", + "name": "test", + "code": 1, + "data": "a test" + } + ] + } +} diff --git a/keama/tests/optionvendor6.in6 b/keama/tests/optionvendor6.in6 new file mode 100644 index 00000000..9a1a3757 --- /dev/null +++ b/keama/tests/optionvendor6.in6 @@ -0,0 +1,8 @@ +# group and class declaration config + +# vendor option space +option space foobar; + +option vsio.foobar code 12345 = encapsulate foobar; +option foobar.test code 1 = text; +option foobar.test "a test"; diff --git a/keama/tests/optionvendor6.out b/keama/tests/optionvendor6.out new file mode 100644 index 00000000..8e163978 --- /dev/null +++ b/keama/tests/optionvendor6.out @@ -0,0 +1,28 @@ +{ + # group and class declaration config + # vendor option space + "Dhcp6": { + "option-def": [ + { + "space": "vendor-12345", + "name": "test", + "code": 1, + "type": "string" + } + ], + "option-data": [ + { + "space": "dhcp6", + "name": "vendor-opts", + "code": 17, + "data": "12345" + }, + { + "space": "vendor-12345", + "name": "test", + "code": 1, + "data": "a test" + } + ] + } +} diff --git a/keama/tests/orphan4.inn b/keama/tests/orphan4.inn new file mode 100644 index 00000000..85f95639 --- /dev/null +++ b/keama/tests/orphan4.inn @@ -0,0 +1,10 @@ +# DHCPv4 orphan reservation config + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# orphan reservation +host foobar { + hardware ethernet 00:0B:FD:32:E6:FA; + option ip-forwarding off; +} diff --git a/keama/tests/orphan4.out b/keama/tests/orphan4.out new file mode 100644 index 00000000..c0146767 --- /dev/null +++ b/keama/tests/orphan4.out @@ -0,0 +1,30 @@ +{ + # DHCPv4 orphan reservation config + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800, + "host-reservation-identifiers": [ + "hw-address" + ] +// /// Orphan reservations +// /// Kea reservations are per subnet +// /// Reference Kea #231 +// "reservations": [ +// # orphan reservation +// { +// "hostname": "foobar", +// "hw-address": "00:0b:fd:32:e6:fa", +// "option-data": [ +// { +// "space": "dhcp4", +// "name": "ip-forwarding", +// "code": 19, +// "original-data": "off", +// /// canonized booleans to lowercase true or false +// "data": "false" +// } +// ] +// } +// ] + } +} diff --git a/keama/tests/orphan6.inN b/keama/tests/orphan6.inN new file mode 100644 index 00000000..e85368f2 --- /dev/null +++ b/keama/tests/orphan6.inN @@ -0,0 +1,10 @@ +# DHCPv6 orphan reservation config + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# orphan reservation +host foobar { + hardware ethernet 00:0B:FD:32:E6:FA; + option dhcp6.name-servers 2a01:e00::2, 2a01:e00::1; +} diff --git a/keama/tests/orphan6.out b/keama/tests/orphan6.out new file mode 100644 index 00000000..0c0b8114 --- /dev/null +++ b/keama/tests/orphan6.out @@ -0,0 +1,28 @@ +{ + # DHCPv6 orphan reservation config + # empty configs are not accepted by Kea + "Dhcp6": { + "valid-lifetime": 1800, + "host-reservation-identifiers": [ + "hw-address" + ] +// /// Orphan reservations +// /// Kea reservations are per subnet +// /// Reference Kea #231 +// "reservations": [ +// # orphan reservation +// { +// "hostname": "foobar", +// "hw-address": "00:0b:fd:32:e6:fa", +// "option-data": [ +// { +// "space": "dhcp6", +// "name": "dns-servers", +// "code": 23, +// "data": "2a01:e00::2, 2a01:e00::1" +// } +// ] +// } +// ] + } +} diff --git a/keama/tests/packetdx4.notyet b/keama/tests/packetdx4.notyet new file mode 100644 index 00000000..03f3237d --- /dev/null +++ b/keama/tests/packetdx4.notyet @@ -0,0 +1,20 @@ +# packet data expression +# Kea has no raw packet extractor in libeval + +# authoritative is mandatory +authoritative; + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# pretty standard hardware superclass extracting the Ethernet address directly +class "byhw" { + match packet(28, 6); +} + +subclass "byhw" 00:07:0E:36:48:19 { + option host-name "test1"; +} + +# raw +option host-name = binary-to-ascii(16, 8, "-", packet(28, 6)); diff --git a/keama/tests/permitauth4.in4 b/keama/tests/permitauth4.in4 new file mode 100644 index 00000000..b9663fd2 --- /dev/null +++ b/keama/tests/permitauth4.in4 @@ -0,0 +1,15 @@ +# DHCPv4 permit authenticated client config + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# subnet declaration +subnet 10.5.5.0 netmask 255.255.255.224 { + # pool declaration + pool { + # avoid empty pool + range 10.5.5.5 10.5.5.10; + # call get_permit + allow authenticated clients; + } +} diff --git a/keama/tests/permitauth4.out b/keama/tests/permitauth4.out new file mode 100644 index 00000000..0f938566 --- /dev/null +++ b/keama/tests/permitauth4.out @@ -0,0 +1,33 @@ +{ + # DHCPv4 permit authenticated client config + # empty configs are not accepted by Kea + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "valid-lifetime": 1800, + "subnet4": [ + # subnet declaration + { + "id": 1, + "subnet": "10.5.5.0/27", + "pools": [ + # pool declaration + { + # avoid empty pool + "pool": "10.5.5.5 - 10.5.5.10", + /// From: + /// allow authenticated clients + /// [un]authenticated-clients is not supported by ISC DHCP and Kea + "client-class": "gen#!ALL#" + } + ] + } + ], + "client-classes": [ + { + "name": "gen#!ALL#", + "test": "not member('ALL')" + } + ] + } +} diff --git a/keama/tests/permitauth6.in6 b/keama/tests/permitauth6.in6 new file mode 100644 index 00000000..ce0a25aa --- /dev/null +++ b/keama/tests/permitauth6.in6 @@ -0,0 +1,15 @@ +# DHCPv6 permit authenticated client config + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# subnet declaration +subnet6 2001::/64 { + # pool declaration + pool6 { + # avoid empty pool + range6 2001::100 2001::200; + # call get_permit + deny unauthenticated clients; + } +} diff --git a/keama/tests/permitauth6.out b/keama/tests/permitauth6.out new file mode 100644 index 00000000..f2013e77 --- /dev/null +++ b/keama/tests/permitauth6.out @@ -0,0 +1,33 @@ +{ + # DHCPv6 permit authenticated client config + # empty configs are not accepted by Kea + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp6": { + "valid-lifetime": 1800, + "subnet6": [ + # subnet declaration + { + "id": 1, + "subnet": "2001::/64", + "pools": [ + # pool declaration + { + # avoid empty pool + "pool": "2001::100 - 2001::200", + /// From: + /// deny unauthenticated clients + /// [un]authenticated-clients is not supported by ISC DHCP and Kea + "client-class": "gen#_AND_#!ALL#" + } + ] + } + ], + "client-classes": [ + { + "name": "gen#_AND_#!ALL#", + "test": "not member('ALL')" + } + ] + } +} diff --git a/keama/tests/permitknown4.in4 b/keama/tests/permitknown4.in4 new file mode 100644 index 00000000..df15e11e --- /dev/null +++ b/keama/tests/permitknown4.in4 @@ -0,0 +1,15 @@ +# DHCPv4 permit known client config + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# subnet declaration +subnet 10.5.5.0 netmask 255.255.255.224 { + # pool declaration + pool { + # avoid empty pool + range 10.5.5.5 10.5.5.10; + # call get_permit + allow known clients; + } +} diff --git a/keama/tests/permitknown4.out b/keama/tests/permitknown4.out new file mode 100644 index 00000000..1a27d5a9 --- /dev/null +++ b/keama/tests/permitknown4.out @@ -0,0 +1,26 @@ +{ + # DHCPv4 permit known client config + # empty configs are not accepted by Kea + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "valid-lifetime": 1800, + "subnet4": [ + # subnet declaration + { + "id": 1, + "subnet": "10.5.5.0/27", + "pools": [ + # pool declaration + { + # avoid empty pool + "pool": "10.5.5.5 - 10.5.5.10", + /// From: + /// allow known clients + "client-class": "KNOWN" + } + ] + } + ] + } +} diff --git a/keama/tests/pickdx6.in6 b/keama/tests/pickdx6.in6 new file mode 100644 index 00000000..ccfa48ce --- /dev/null +++ b/keama/tests/pickdx6.in6 @@ -0,0 +1,15 @@ +# pick-first-value data expression + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# reduce literals +class "literal" { + match if option dhcp6.client-data = + pick-first-value(substring("abcd",0,0),null,"foobar"); +} + +# null +class "null" { + match if option dhcp6.client-data = pick(null); +} diff --git a/keama/tests/pickdx6.out b/keama/tests/pickdx6.out new file mode 100644 index 00000000..d8de1130 --- /dev/null +++ b/keama/tests/pickdx6.out @@ -0,0 +1,21 @@ +{ + # pick-first-value data expression + # empty configs are not accepted by Kea + "Dhcp6": { + "valid-lifetime": 1800, + "client-classes": [ + # reduce literals + { + "name": "literal", + /// from: match if (option dhcp6.client-data) = (pick-first-value(substring('abcd', 0, 0), null, 'foobar')) + "test": "option[45].hex == 'foobar'" + }, + # null + { + "name": "null", + /// from: match if (option dhcp6.client-data) = (pick-first-value(null)) + "test": "option[45].hex == ''" + } + ] + } +} diff --git a/keama/tests/pool4.in4 b/keama/tests/pool4.in4 new file mode 100644 index 00000000..0e156141 --- /dev/null +++ b/keama/tests/pool4.in4 @@ -0,0 +1,11 @@ +# DHCPv4 pool config + +# subnet declaration +subnet 10.5.5.0 netmask 255.255.255.224 { + # pool declaration + pool { + option domain-search "example.com", "example.org"; + default-lease-time 1800; + range 10.5.5.5 10.5.5.10; + } +} diff --git a/keama/tests/pool4.out b/keama/tests/pool4.out new file mode 100644 index 00000000..e0b6bee8 --- /dev/null +++ b/keama/tests/pool4.out @@ -0,0 +1,31 @@ +{ + # DHCPv4 pool config + # subnet declaration + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "subnet4": [ + { + "id": 1, + "subnet": "10.5.5.0/27", + /// default-valid-lifetime moved from an internal pool scope + "valid-lifetime": 1800, + "pools": [ + # pool declaration + { + "option-data": [ + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "pool": "10.5.5.5 - 10.5.5.10" + } + ] + } + ] + } +} diff --git a/keama/tests/pool42.in4 b/keama/tests/pool42.in4 new file mode 100644 index 00000000..7e7fea3f --- /dev/null +++ b/keama/tests/pool42.in4 @@ -0,0 +1,15 @@ +# DHCPv4 pool with 2 ranges config + +# subnet declaration +subnet 10.5.5.0 netmask 255.255.255.224 { + # pool declaration + pool { + option domain-search "example.com", "example.org"; + default-lease-time 1800; + range 10.5.5.5 10.5.5.10; + # add a second range + range 10.5.5.11 10.5.5.12; + } + # interface + interface "en0"; +} diff --git a/keama/tests/pool42.out b/keama/tests/pool42.out new file mode 100644 index 00000000..38a039b8 --- /dev/null +++ b/keama/tests/pool42.out @@ -0,0 +1,49 @@ +{ + # DHCPv4 pool with 2 ranges config + # subnet declaration + "Dhcp4": { + "subnet4": [ + { + "id": 1, + "subnet": "10.5.5.0/27", + /// default-valid-lifetime moved from an internal pool scope + "valid-lifetime": 1800, + "pools": [ + # pool declaration + { + "option-data": [ + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "pool": "10.5.5.5 - 10.5.5.10" + }, + # pool declaration + { + "option-data": [ + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + # add a second range + "pool": "10.5.5.11 - 10.5.5.12" + } + ], + "interface": "en0" + } + ], + "interfaces-config": { + "interfaces": [ + "en0" + ] + } + } +} diff --git a/keama/tests/pool6.in6 b/keama/tests/pool6.in6 new file mode 100644 index 00000000..2cd2fa39 --- /dev/null +++ b/keama/tests/pool6.in6 @@ -0,0 +1,11 @@ +# DHCPv6 pool config + +# subnet declaration +subnet6 2001::/64 { + # pool declaration + pool6 { + option dhcp6.domain-search "example.com", "example.org"; + default-lease-time 1800; + range6 2001::100 2001::200; + } +} diff --git a/keama/tests/pool6.out b/keama/tests/pool6.out new file mode 100644 index 00000000..626610dc --- /dev/null +++ b/keama/tests/pool6.out @@ -0,0 +1,31 @@ +{ + # DHCPv6 pool config + # subnet declaration + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp6": { + "subnet6": [ + { + "id": 1, + "subnet": "2001::/64", + /// default-valid-lifetime moved from an internal pool scope + "valid-lifetime": 1800, + "pools": [ + # pool declaration + { + "option-data": [ + { + "space": "dhcp6", + "name": "domain-search", + "code": 24, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "pool": "2001::100 - 2001::200" + } + ] + } + ] + } +} diff --git a/keama/tests/pool6in4.err4 b/keama/tests/pool6in4.err4 new file mode 100644 index 00000000..44d19cf6 --- /dev/null +++ b/keama/tests/pool6in4.err4 @@ -0,0 +1,13 @@ +# DHCPv6 pool declaration in DHCPv4 config + +# host declaration +host foobar { + hardware ethernet 00:0B:FD:32:E6:FA; + default-lease-time 1800; +} + +# There is a declaration so the error message is different + +# DHCPv6 pool declaration must be in DHCPv6 config +range6 2001::/64; + diff --git a/keama/tests/pool6in4.msg b/keama/tests/pool6in4.msg new file mode 100644 index 00000000..f6df706e --- /dev/null +++ b/keama/tests/pool6in4.msg @@ -0,0 +1 @@ +pool6in4.err4 line 12: expecting a declaration diff --git a/keama/tests/poolinroot4.err4 b/keama/tests/poolinroot4.err4 new file mode 100644 index 00000000..6caa6d47 --- /dev/null +++ b/keama/tests/poolinroot4.err4 @@ -0,0 +1,7 @@ +# DHCPv4 pool declaration in root config + +# DHCPv4 pool declaration must be in a shared-network or subnet declaration +pool { + range 204.152.185.135 204.152.185.185; +} + diff --git a/keama/tests/poolinroot4.msg b/keama/tests/poolinroot4.msg new file mode 100644 index 00000000..845744fd --- /dev/null +++ b/keama/tests/poolinroot4.msg @@ -0,0 +1 @@ +poolinroot4.err4 line 4: pool declared outside of network diff --git a/keama/tests/poolinroot6.err6 b/keama/tests/poolinroot6.err6 new file mode 100644 index 00000000..6605f086 --- /dev/null +++ b/keama/tests/poolinroot6.err6 @@ -0,0 +1,7 @@ +# DHCPv6 pool declaration in root config + +# DHCPv6 pool declaration must be in a shared-network or subnet declaration +pool6 { + range6 2001::/64; +} + diff --git a/keama/tests/poolinroot6.msg b/keama/tests/poolinroot6.msg new file mode 100644 index 00000000..5f48f13d --- /dev/null +++ b/keama/tests/poolinroot6.msg @@ -0,0 +1 @@ +poolinroot6.err6 line 4: pool6 declared outside of network diff --git a/keama/tests/preferred6.in6 b/keama/tests/preferred6.in6 new file mode 100644 index 00000000..9fd8519f --- /dev/null +++ b/keama/tests/preferred6.in6 @@ -0,0 +1,14 @@ +# preferred lifetime + +preferred-lifetime 1200; + +# embedded in pool +subnet6 2001::/64 { + # silently overwritten + preferred-lifetime 1800; + pool6 { + preferred-lifetime 2400; + range6 2001::1000 2001::1fff; + } +} + diff --git a/keama/tests/preferred6.out b/keama/tests/preferred6.out new file mode 100644 index 00000000..88acf33d --- /dev/null +++ b/keama/tests/preferred6.out @@ -0,0 +1,30 @@ +{ + # preferred lifetime + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp6": { + "preferred-lifetime": 1200, + "renew-timer": 600, + "rebind-timer": 960, + "subnet6": [ + # embedded in pool + { + "id": 1, + "subnet": "2001::/64", + "preferred-lifetime": 1800, + "renew-timer": 900, + "rebind-timer": 1440, +// /// preferred-lifetime moved from an internal pool scope +// /// Avoid to overwrite current value... +// "preferred-lifetime": 2400, +// "renew-timer": 1200, +// "rebind-timer": 1920, + "pools": [ + { + "pool": "2001::1000 - 2001::1fff" + } + ] + } + ] + } +} diff --git a/keama/tests/prefix0.err6 b/keama/tests/prefix0.err6 new file mode 100644 index 00000000..c10b6d15 --- /dev/null +++ b/keama/tests/prefix0.err6 @@ -0,0 +1,9 @@ +# DHCPv6 (bad 128 bit length) prefix config + +# subnet declaration +subnet6 2001::/64 { + # range declaration + option dhcp6.domain-search "example.com", "example.org"; + default-lease-time 1800; + prefix6 2001:0:0:10:: 2001:0:0:1f:: / 128; +} diff --git a/keama/tests/prefix0.msg b/keama/tests/prefix0.msg new file mode 100644 index 00000000..73952817 --- /dev/null +++ b/keama/tests/prefix0.msg @@ -0,0 +1 @@ +prefix0.err6 line 8: networks have 0 to 128 bits (exclusive) diff --git a/keama/tests/prefix128.err6 b/keama/tests/prefix128.err6 new file mode 100644 index 00000000..c10b6d15 --- /dev/null +++ b/keama/tests/prefix128.err6 @@ -0,0 +1,9 @@ +# DHCPv6 (bad 128 bit length) prefix config + +# subnet declaration +subnet6 2001::/64 { + # range declaration + option dhcp6.domain-search "example.com", "example.org"; + default-lease-time 1800; + prefix6 2001:0:0:10:: 2001:0:0:1f:: / 128; +} diff --git a/keama/tests/prefix128.msg b/keama/tests/prefix128.msg new file mode 100644 index 00000000..ee874c74 --- /dev/null +++ b/keama/tests/prefix128.msg @@ -0,0 +1 @@ +prefix128.err6 line 8: networks have 0 to 128 bits (exclusive) diff --git a/keama/tests/prefix6.in6 b/keama/tests/prefix6.in6 new file mode 100644 index 00000000..e5c01c90 --- /dev/null +++ b/keama/tests/prefix6.in6 @@ -0,0 +1,9 @@ +# DHCPv6 prefix config + +# subnet declaration +subnet6 2001::/64 { + # range declaration + option dhcp6.domain-search "example.com", "example.org"; + default-lease-time 1800; + prefix6 2001:0:0:10:: 2001:0:0:1f:: / 64; +} diff --git a/keama/tests/prefix6.out b/keama/tests/prefix6.out new file mode 100644 index 00000000..e0fe3ea5 --- /dev/null +++ b/keama/tests/prefix6.out @@ -0,0 +1,32 @@ +{ + # DHCPv6 prefix config + # subnet declaration + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp6": { + "subnet6": [ + { + "id": 1, + "subnet": "2001::/64", + "option-data": [ + # range declaration + { + "space": "dhcp6", + "name": "domain-search", + "code": 24, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "valid-lifetime": 1800, + "pd-pools": [ + { + "prefix": "2001:0:0:10::", + "delegated-len": 64, + "prefix-len": 60 + } + ] + } + ] + } +} diff --git a/keama/tests/prefix62.in6 b/keama/tests/prefix62.in6 new file mode 100644 index 00000000..408481a5 --- /dev/null +++ b/keama/tests/prefix62.in6 @@ -0,0 +1,10 @@ +# DHCPv6 prefix config + +# subnet declaration +subnet6 2001::/64 { + # range declaration + option dhcp6.domain-search "example.com", "example.org"; + default-lease-time 1800; + prefix6 2001:0:0:1:: 2001:0:0:3:: / 64; + interface "en0"; +} diff --git a/keama/tests/prefix62.out b/keama/tests/prefix62.out new file mode 100644 index 00000000..0ce536a8 --- /dev/null +++ b/keama/tests/prefix62.out @@ -0,0 +1,36 @@ +{ + # DHCPv6 prefix config + # subnet declaration + "Dhcp6": { + "subnet6": [ + { + "id": 1, + "subnet": "2001::/64", + "option-data": [ + # range declaration + { + "space": "dhcp6", + "name": "domain-search", + "code": 24, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "valid-lifetime": 1800, + "pd-pools": [ +// { +// "prefix": "2001:0:0:1::", +// "delegated-len": 64, +// "prefix-highest": "2001:0:0:3::" +// } + ], + "interface": "en0" + } + ], + "interfaces-config": { + "interfaces": [ + "en0" + ] + } + } +} diff --git a/keama/tests/prefixinroot6.err6 b/keama/tests/prefixinroot6.err6 new file mode 100644 index 00000000..f1fd4a1e --- /dev/null +++ b/keama/tests/prefixinroot6.err6 @@ -0,0 +1,4 @@ +# DHCPv6 prefix declaration in root config + +# DHCPv6 prefix declaration must be in a subnet declaration +prefix6 2001:: 2001:0:1:: / 64; diff --git a/keama/tests/prefixinroot6.msg b/keama/tests/prefixinroot6.msg new file mode 100644 index 00000000..8aa498f4 --- /dev/null +++ b/keama/tests/prefixinroot6.msg @@ -0,0 +1 @@ +prefixinroot6.err6 line 4: prefix6 declaration not allowed here. diff --git a/keama/tests/qualifyingsuffix4.in4 b/keama/tests/qualifyingsuffix4.in4 new file mode 100644 index 00000000..cef53f4d --- /dev/null +++ b/keama/tests/qualifyingsuffix4.in4 @@ -0,0 +1,9 @@ +# ddns-domainname (aka qualifying-suffix) + +ddns-domainname ".biz"; +ddns-updates true; + +# embedded +class "foo" { + ddns-domainname ".bar"; +} diff --git a/keama/tests/qualifyingsuffix4.out b/keama/tests/qualifyingsuffix4.out new file mode 100644 index 00000000..f014362d --- /dev/null +++ b/keama/tests/qualifyingsuffix4.out @@ -0,0 +1,17 @@ +{ + # ddns-domainname (aka qualifying-suffix) + "Dhcp4": { + "dhcp-ddns": { + "qualifying-suffix": ".biz", + "enable-updates": true + }, + "client-classes": [ + # embedded + { + "name": "foo" +// /// Only global qualifying-suffix is supported +// "qualifying-suffix": ".bar" + } + ] + } +} diff --git a/keama/tests/qualifyingsuffix6.in6 b/keama/tests/qualifyingsuffix6.in6 new file mode 100644 index 00000000..3957eae2 --- /dev/null +++ b/keama/tests/qualifyingsuffix6.in6 @@ -0,0 +1,8 @@ +# ddns-domainname (aka qualifying-suffix) + +ddns-domainname ".biz"; + +# embedded +class "foo" { + ddns-domainname ".bar"; +} diff --git a/keama/tests/qualifyingsuffix6.out b/keama/tests/qualifyingsuffix6.out new file mode 100644 index 00000000..44ae84a1 --- /dev/null +++ b/keama/tests/qualifyingsuffix6.out @@ -0,0 +1,17 @@ +{ + # ddns-domainname (aka qualifying-suffix) + "Dhcp6": { + "dhcp-ddns": { + "enable-updates": false, + "qualifying-suffix": ".biz" + }, + "client-classes": [ + # embedded + { + "name": "foo" +// /// Only global qualifying-suffix is supported +// "qualifying-suffix": ".bar" + } + ] + } +} diff --git a/keama/tests/range4.in4 b/keama/tests/range4.in4 new file mode 100644 index 00000000..f755a2eb --- /dev/null +++ b/keama/tests/range4.in4 @@ -0,0 +1,9 @@ +# DHCPv4 range config + +# subnet declaration +subnet 10.5.5.0 netmask 255.255.255.224 { + # range declaration + option domain-search "example.com", "example.org"; + default-lease-time 1800; + range 10.5.5.5 10.5.5.10; +} diff --git a/keama/tests/range4.out b/keama/tests/range4.out new file mode 100644 index 00000000..94611f7e --- /dev/null +++ b/keama/tests/range4.out @@ -0,0 +1,30 @@ +{ + # DHCPv4 range config + # subnet declaration + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "subnet4": [ + { + "id": 1, + "subnet": "10.5.5.0/27", + "option-data": [ + # range declaration + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "valid-lifetime": 1800, + "pools": [ + { + "pool": "10.5.5.5 - 10.5.5.10" + } + ] + } + ] + } +} diff --git a/keama/tests/range6.in6 b/keama/tests/range6.in6 new file mode 100644 index 00000000..45a0635a --- /dev/null +++ b/keama/tests/range6.in6 @@ -0,0 +1,11 @@ +# DHCPv6 range config + +# subnet declaration +subnet6 2001::/64 { + # range declaration + option dhcp6.domain-search "example.com", "example.org"; + default-lease-time 1800; + range6 2001::100 2001::200; + range6 2001::1000/116; + interface "en0"; +} diff --git a/keama/tests/range6.out b/keama/tests/range6.out new file mode 100644 index 00000000..1e506ab5 --- /dev/null +++ b/keama/tests/range6.out @@ -0,0 +1,37 @@ +{ + # DHCPv6 range config + # subnet declaration + "Dhcp6": { + "subnet6": [ + { + "id": 1, + "subnet": "2001::/64", + "option-data": [ + # range declaration + { + "space": "dhcp6", + "name": "domain-search", + "code": 24, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "valid-lifetime": 1800, + "pools": [ + { + "pool": "2001::100 - 2001::200" + }, + { + "pool": "2001::1000/116" + } + ], + "interface": "en0" + } + ], + "interfaces-config": { + "interfaces": [ + "en0" + ] + } + } +} diff --git a/keama/tests/range6in4.err4 b/keama/tests/range6in4.err4 new file mode 100644 index 00000000..af3630c0 --- /dev/null +++ b/keama/tests/range6in4.err4 @@ -0,0 +1,5 @@ +# DHCPv6 pool declaration in DHCPv4 config + +# DHCPv6 pool declaration must be in DHCPv6 config +range6 2001::/64; + diff --git a/keama/tests/range6in4.msg b/keama/tests/range6in4.msg new file mode 100644 index 00000000..d27a8452 --- /dev/null +++ b/keama/tests/range6in4.msg @@ -0,0 +1 @@ +range6in4.err4 line 4: expecting a parameter or declaration diff --git a/keama/tests/rangeinroot4.err4 b/keama/tests/rangeinroot4.err4 new file mode 100644 index 00000000..7a3d36f9 --- /dev/null +++ b/keama/tests/rangeinroot4.err4 @@ -0,0 +1,4 @@ +# DHCPv4 range declaration in root config + +# DHCPv4 range declaration must be in a subnet declaration +range 204.152.185.135 204.152.185.185; diff --git a/keama/tests/rangeinroot4.msg b/keama/tests/rangeinroot4.msg new file mode 100644 index 00000000..4ed397e4 --- /dev/null +++ b/keama/tests/rangeinroot4.msg @@ -0,0 +1 @@ +rangeinroot4.err4 line 4: range declaration not allowed here. diff --git a/keama/tests/rangeinroot6.err6 b/keama/tests/rangeinroot6.err6 new file mode 100644 index 00000000..e8c5c65e --- /dev/null +++ b/keama/tests/rangeinroot6.err6 @@ -0,0 +1,5 @@ +# DHCPv6 range declaration in root config + +# DHCPv6 range declaration must be in a subnet declaration +range6 2001::/64; + diff --git a/keama/tests/rangeinroot6.msg b/keama/tests/rangeinroot6.msg new file mode 100644 index 00000000..969fe658 --- /dev/null +++ b/keama/tests/rangeinroot6.msg @@ -0,0 +1 @@ +rangeinroot6.err6 line 4: range6 declaration not allowed here. diff --git a/keama/tests/reversedx6.in6 b/keama/tests/reversedx6.in6 new file mode 100644 index 00000000..ba762787 --- /dev/null +++ b/keama/tests/reversedx6.in6 @@ -0,0 +1,9 @@ +# reverse data expression + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# reduce literals +class "literal" { + match if option dhcp6.client-data = reverse(2 & 2, "foobar"); +} diff --git a/keama/tests/reversedx6.out b/keama/tests/reversedx6.out new file mode 100644 index 00000000..f13efe95 --- /dev/null +++ b/keama/tests/reversedx6.out @@ -0,0 +1,15 @@ +{ + # reverse data expression + # empty configs are not accepted by Kea + "Dhcp6": { + "valid-lifetime": 1800, + "client-classes": [ + # reduce literals + { + "name": "literal", + /// from: match if (option dhcp6.client-data) = (reverse(2 & 2, 'foobar')) + "test": "option[45].hex == 'arobfo'" + } + ] + } +} diff --git a/keama/tests/runall.sh b/keama/tests/runall.sh new file mode 100644 index 00000000..f8cf357e --- /dev/null +++ b/keama/tests/runall.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +#set -x + +cd "$(dirname "$0")" + +echo subdirs: +/bin/sh samples/runall.sh + +echo tests: +for t in *.err* *.in* +do + echo `basename $t` + /bin/sh runone.sh $t +done diff --git a/keama/tests/runone.sh b/keama/tests/runone.sh new file mode 100644 index 00000000..4838c973 --- /dev/null +++ b/keama/tests/runone.sh @@ -0,0 +1,119 @@ +#!/bin/sh + +#set -x + +if [ $# -ne 1 ]; then + echo "usage: $0 test-name" >&2 + exit 1 +fi + +file=$1 + +cd "$(dirname "$0")" + +isin=$(expr $file : ".*\.in*") +iserr=$(expr $file : ".*\.err*") +if [ \( $isin -eq 0 \) -a \( $iserr -eq 0 \) ]; then + full=$file.in* + if [ ! -f $full ]; then + full=$file.err* + fi +else + full=$file +fi + +if [ ! -f $full ]; then + echo "can't find $file" >&2 + exit 1 +fi + +errcase=$(expr $full : ".*\.err*") + +trail= +if [ $errcase -eq 0 ]; then + trail=$(expr $full : ".*\.in\(.\)") +else + trail=$(expr $full : ".*\.err\(.\)") +fi + +options="" +dual=0 +hook=0 + +case $trail in + '') dual=1;; + 4) options="-4";; + 6) options="-6";; + F) options="-4 -r fatal";; + P) options="-4 -r pass";; + d) options="-4 -D";; + D) options="-6 -D";; + n) options="-4 -N";; + N) options="-6 -N";; + l) options="-4 -l ${HOOK:-/path/}"; hook=1;; + L) options="-6 -l ${HOOK:-/path/}"; hook=1;; + *) echo "unrecognized trail '$trail' in '$full'" >&2; exit 1;; +esac + +if [ $errcase -ne 0 ]; then + base=$(basename $full .err$trail) +else + if [ $dual -ne 0 ]; then + echo "required trail ([45FP]) in '$full'" >&2 + exit 1 + fi + base=$(basename $full .in$trail) +fi + +out=/tmp/$base.out$$ +expected="" +if [ $errcase -ne 0 ]; then + expected=$base.msg +else + expected=$base.out +fi + +if [ $errcase -ne 0 ]; then + if [ $dual -eq 1 ]; then + ../keama -4 -i $full >$out 2>&1 + if [ $? -ne 255 ]; then + echo "$full -4 doesn't fail as expected" >&2 + exit 1 + fi + ../keama -6 -i $full >$out 2>&1 + if [ $? -ne 255 ]; then + echo "$full -6 doesn't fail as expected" >&2 + exit 1 + fi + else + ../keama $options -i $full >$out 2>&1 + if [ $? -ne 255 ]; then + echo "$full doesn't fail as expected" >&2 + exit 1 + fi + fi +else + ../keama $options -i $full -o $out >&2 + if [ $? -eq 255 ]; then + echo "$full raised an error" >&2 + exit 1 + fi +fi + +if [ $hook -eq 1 ]; then + sed s,/path/,${HOOK:-/path/}, < ${expected}L > $expected +fi + +if [ $errcase -ne 0 ]; then + cat $out | head -1 | diff --brief - $expected + if [ $? -ne 0 ]; then + echo "$full doesn't provide expected output" >&2 + exit 1 + fi +else + diff --brief $out $expected + if [ $? -ne 0 ]; then + echo "$full doesn't provide expected output" >&2 + exit 1 + fi +fi diff --git a/keama/tests/samples/example.conf b/keama/tests/samples/example.conf new file mode 100644 index 00000000..62b088dc --- /dev/null +++ b/keama/tests/samples/example.conf @@ -0,0 +1,111 @@ +# dhcpd.conf +# +# Sample configuration file for ISC dhcpd +# + +# option definitions common to all supported networks... +option domain-name "example.org"; +#option domain-name-servers ns1.example.org, ns2.example.org; +option domain-name-servers 10.35.0.1, 10.35.0.2; + +default-lease-time 600; +max-lease-time 7200; + +# Use this to enble / disable dynamic dns updates globally. +#ddns-update-style none; + +# If this DHCP server is the official DHCP server for the local +# network, the authoritative directive should be uncommented. +#authoritative; + +# Use this to send dhcp log messages to a different log file (you also +# have to hack syslog.conf to complete the redirection). +log-facility local7; + +# No service will be given on this subnet, but declaring it helps the +# DHCP server to understand the network topology. + +subnet 10.152.187.0 netmask 255.255.255.0 { +} + +# This is a very basic subnet declaration. + +subnet 10.254.239.0 netmask 255.255.255.224 { + range 10.254.239.10 10.254.239.20; +# option routers rtr-239-0-1.example.org, rtr-239-0-2.example.org; + option routers 10.254.239.1, 10.254.239.2; +} + +# This declaration allows BOOTP clients to get dynamic addresses, +# which we don't really recommend. + +subnet 10.254.239.32 netmask 255.255.255.224 { + range dynamic-bootp 10.254.239.40 10.254.239.60; + option broadcast-address 10.254.239.31; +# option routers rtr-239-32-1.example.org; + option routers 10.254.239.33; +} + +# A slightly different configuration for an internal subnet. +subnet 10.5.5.0 netmask 255.255.255.224 { + range 10.5.5.26 10.5.5.30; +# option domain-name-servers ns1.internal.example.org; + option domain-name-servers 10.35.1.1; + option domain-name "internal.example.org"; + option routers 10.5.5.1; + option broadcast-address 10.5.5.31; + default-lease-time 600; + max-lease-time 7200; +} + +# Hosts which require special configuration options can be listed in +# host statements. If no address is specified, the address will be +# allocated dynamically (if possible), but the host-specific information +# will still come from the host declaration. + +host passacaglia { + hardware ethernet 0:0:c0:5d:bd:95; + filename "vmunix.passacaglia"; + server-name "toccata.example.com"; +} + +# Fixed IP addresses can also be specified for hosts. These addresses +# should not also be listed as being available for dynamic assignment. +# Hosts for which fixed IP addresses have been specified can boot using +# BOOTP or DHCP. Hosts for which no fixed address is specified can only +# be booted with DHCP, unless there is an address range on the subnet +# to which a BOOTP client is connected which has the dynamic-bootp flag +# set. +host fantasia { + hardware ethernet 08:00:07:26:c0:a5; +# fixed-address fantasia.example.com; + fixed-address 10.5.5.20; +} + +# You can declare a class of clients and then do address allocation +# based on that. The example below shows a case where all clients +# in a certain class get addresses on the 10.17.224/24 subnet, and all +# other clients get addresses on the 10.0.29/24 subnet. + +class "foo" { + match if substring (option vendor-class-identifier, 0, 4) = "SUNW"; +} + +shared-network 224-29 { + subnet 10.17.224.0 netmask 255.255.255.0 { +# option routers rtr-224.example.org; + option routers 10.17.224.1; + } + subnet 10.0.29.0 netmask 255.255.255.0 { +# option routers rtr-29.example.org; + option routers 10.0.29.1; + } + pool { + allow members of "foo"; + range 10.17.224.10 10.17.224.250; + } + pool { + deny members of "foo"; + range 10.0.29.10 10.0.29.230; + } +} diff --git a/keama/tests/samples/example.conf.orig b/keama/tests/samples/example.conf.orig new file mode 100644 index 00000000..8b99f496 --- /dev/null +++ b/keama/tests/samples/example.conf.orig @@ -0,0 +1,104 @@ +# dhcpd.conf +# +# Sample configuration file for ISC dhcpd +# + +# option definitions common to all supported networks... +option domain-name "example.org"; +option domain-name-servers ns1.example.org, ns2.example.org; + +default-lease-time 600; +max-lease-time 7200; + +# Use this to enble / disable dynamic dns updates globally. +#ddns-update-style none; + +# If this DHCP server is the official DHCP server for the local +# network, the authoritative directive should be uncommented. +#authoritative; + +# Use this to send dhcp log messages to a different log file (you also +# have to hack syslog.conf to complete the redirection). +log-facility local7; + +# No service will be given on this subnet, but declaring it helps the +# DHCP server to understand the network topology. + +subnet 10.152.187.0 netmask 255.255.255.0 { +} + +# This is a very basic subnet declaration. + +subnet 10.254.239.0 netmask 255.255.255.224 { + range 10.254.239.10 10.254.239.20; + option routers rtr-239-0-1.example.org, rtr-239-0-2.example.org; +} + +# This declaration allows BOOTP clients to get dynamic addresses, +# which we don't really recommend. + +subnet 10.254.239.32 netmask 255.255.255.224 { + range dynamic-bootp 10.254.239.40 10.254.239.60; + option broadcast-address 10.254.239.31; + option routers rtr-239-32-1.example.org; +} + +# A slightly different configuration for an internal subnet. +subnet 10.5.5.0 netmask 255.255.255.224 { + range 10.5.5.26 10.5.5.30; + option domain-name-servers ns1.internal.example.org; + option domain-name "internal.example.org"; + option routers 10.5.5.1; + option broadcast-address 10.5.5.31; + default-lease-time 600; + max-lease-time 7200; +} + +# Hosts which require special configuration options can be listed in +# host statements. If no address is specified, the address will be +# allocated dynamically (if possible), but the host-specific information +# will still come from the host declaration. + +host passacaglia { + hardware ethernet 0:0:c0:5d:bd:95; + filename "vmunix.passacaglia"; + server-name "toccata.example.com"; +} + +# Fixed IP addresses can also be specified for hosts. These addresses +# should not also be listed as being available for dynamic assignment. +# Hosts for which fixed IP addresses have been specified can boot using +# BOOTP or DHCP. Hosts for which no fixed address is specified can only +# be booted with DHCP, unless there is an address range on the subnet +# to which a BOOTP client is connected which has the dynamic-bootp flag +# set. +host fantasia { + hardware ethernet 08:00:07:26:c0:a5; + fixed-address fantasia.example.com; +} + +# You can declare a class of clients and then do address allocation +# based on that. The example below shows a case where all clients +# in a certain class get addresses on the 10.17.224/24 subnet, and all +# other clients get addresses on the 10.0.29/24 subnet. + +class "foo" { + match if substring (option vendor-class-identifier, 0, 4) = "SUNW"; +} + +shared-network 224-29 { + subnet 10.17.224.0 netmask 255.255.255.0 { + option routers rtr-224.example.org; + } + subnet 10.0.29.0 netmask 255.255.255.0 { + option routers rtr-29.example.org; + } + pool { + allow members of "foo"; + range 10.17.224.10 10.17.224.250; + } + pool { + deny members of "foo"; + range 10.0.29.10 10.0.29.230; + } +} diff --git a/keama/tests/samples/example.json b/keama/tests/samples/example.json new file mode 100644 index 00000000..7123a158 --- /dev/null +++ b/keama/tests/samples/example.json @@ -0,0 +1,223 @@ +{ + # dhcpd.conf + # + # Sample configuration file for ISC dhcpd + # + # option definitions common to all supported networks... + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "option-data": [ + { + "space": "dhcp4", + "name": "domain-name", + "code": 15, + "data": "example.org" + }, + #option domain-name-servers ns1.example.org, ns2.example.org; + { + "space": "dhcp4", + "name": "domain-name-servers", + "code": 6, + "data": "10.35.0.1, 10.35.0.2" + } + ], + "valid-lifetime": 600, + "max-valid-lifetime": 7200, +// "config": [ +// /// log-facility is not supported +// /// Please use the KEA_LOGGER_DESTINATION environment variable instead +// { +// "name": "log-facility", +// "code": 44, +// "value": "local7" +// } +// ], + "subnet4": [ + # No service will be given on this subnet, but declaring it helps the + # DHCP server to understand the network topology. + { + "id": 1, + "subnet": "10.152.187.0/24" + }, + # This is a very basic subnet declaration. + { + "id": 2, + "subnet": "10.254.239.0/27", + "pools": [ + { + "pool": "10.254.239.10 - 10.254.239.20" + } + ], + "option-data": [ + # option routers rtr-239-0-1.example.org, rtr-239-0-2.example.org; + { + "space": "dhcp4", + "name": "routers", + "code": 3, + "data": "10.254.239.1, 10.254.239.2" + } + ] + }, + # This declaration allows BOOTP clients to get dynamic addresses, + # which we don't really recommend. + { + "id": 3, + "subnet": "10.254.239.32/27", + "pools": [ + { + "pool": "10.254.239.40 - 10.254.239.60" + } + ], + "option-data": [ + { + "space": "dhcp4", + "name": "broadcast-address", + "code": 28, + "data": "10.254.239.31" + }, + # option routers rtr-239-32-1.example.org; + { + "space": "dhcp4", + "name": "routers", + "code": 3, + "data": "10.254.239.33" + } + ] + }, + # A slightly different configuration for an internal subnet. + { + "id": 4, + "subnet": "10.5.5.0/27", + "pools": [ + { + "pool": "10.5.5.26 - 10.5.5.30" + } + ], + "option-data": [ + # option domain-name-servers ns1.internal.example.org; + { + "space": "dhcp4", + "name": "domain-name-servers", + "code": 6, + "data": "10.35.1.1" + }, + { + "space": "dhcp4", + "name": "domain-name", + "code": 15, + "data": "internal.example.org" + }, + { + "space": "dhcp4", + "name": "routers", + "code": 3, + "data": "10.5.5.1" + }, + { + "space": "dhcp4", + "name": "broadcast-address", + "code": 28, + "data": "10.5.5.31" + } + ], + "valid-lifetime": 600, + "max-valid-lifetime": 7200, + /// Host reservations without fixed addresses were put in the last declared subnet + /// Reference Kea #231 + "reservations": [ + # Hosts which require special configuration options can be listed in + # host statements. If no address is specified, the address will be + # allocated dynamically (if possible), but the host-specific information + # will still come from the host declaration. + { + "hostname": "passacaglia", + "hw-address": "00:00:c0:5d:bd:95", + "boot-file-name": "vmunix.passacaglia", + "server-hostname": "toccata.example.com" + }, + # Fixed IP addresses can also be specified for hosts. These addresses + # should not also be listed as being available for dynamic assignment. + # Hosts for which fixed IP addresses have been specified can boot using + # BOOTP or DHCP. Hosts for which no fixed address is specified can only + # be booted with DHCP, unless there is an address range on the subnet + # to which a BOOTP client is connected which has the dynamic-bootp flag + # set. + { + "hostname": "fantasia", + "hw-address": "08:00:07:26:c0:a5", + "ip-address": "10.5.5.20" + } + ] + } + ], + "host-reservation-identifiers": [ + "hw-address" + ], + "client-classes": [ + # You can declare a class of clients and then do address allocation + # based on that. The example below shows a case where all clients + # in a certain class get addresses on the 10.17.224/24 subnet, and all + # other clients get addresses on the 10.0.29/24 subnet. + { + "name": "foo", + /// from: match if (substring(option dhcp.vendor-class-identifier, 0, 4)) = 'SUNW' + "test": "substring(option[60].hex,0,4) == 'SUNW'" + }, + { + "name": "gen#_AND_#!foo#", + "test": "not member('foo')" + } + ], + /// Kea shared-networks are different, cf Kea #236 + "shared-networks": [ + { + "name": "224-29", + "subnet4": [ + { + "id": 5, + "subnet": "10.17.224.0/24", + "option-data": [ + # option routers rtr-224.example.org; + { + "space": "dhcp4", + "name": "routers", + "code": 3, + "data": "10.17.224.1" + } + ], + "pools": [ + { + "pool": "10.17.224.10 - 10.17.224.250", + /// From: + /// allow foo + "client-class": "foo" + } + ] + }, + { + "id": 6, + "subnet": "10.0.29.0/24", + "option-data": [ + # option routers rtr-29.example.org; + { + "space": "dhcp4", + "name": "routers", + "code": 3, + "data": "10.0.29.1" + } + ], + "pools": [ + { + "pool": "10.0.29.10 - 10.0.29.230", + /// From: + /// deny foo + "client-class": "gen#_AND_#!foo#" + } + ] + } + ] + } + ] + } +} diff --git a/keama/tests/samples/runall.sh b/keama/tests/samples/runall.sh new file mode 100644 index 00000000..bc41c7f8 --- /dev/null +++ b/keama/tests/samples/runall.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +#set -x + +cd "$(dirname "$0")" + +for t in example simple test-a6 vmnet8 +do + echo $t + /bin/sh runone.sh $t +done diff --git a/keama/tests/samples/runone.sh b/keama/tests/samples/runone.sh new file mode 100644 index 00000000..6526d798 --- /dev/null +++ b/keama/tests/samples/runone.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +#set -x + +if [ $# -ne 1 ]; then + echo "usage: $0 config-name" >&2 + exit 1 +fi + +file=$1 + +cd "$(dirname "$0")" + +trail=$(expr $file : ".*6$") +options="" +if [ $trail -eq 0 ]; then + options="-4" +else + options="-6" +fi + +out=/tmp/$file.out$$ + +../../keama $options -N -i $file.conf -o $out >&2 +status=$? +if [ $status -eq 255 ]; then + echo "$file config raised an error" >&2 + exit 1 +fi + +expected=$file.json + +diff --brief $out $expected +if [ $? -ne 0 ]; then + echo "$file config doesn't provide expected output" >&2 + exit 1 +fi + +exit $status diff --git a/keama/tests/samples/simple.conf b/keama/tests/samples/simple.conf new file mode 100644 index 00000000..76c1cfa5 --- /dev/null +++ b/keama/tests/samples/simple.conf @@ -0,0 +1,33 @@ +# ------------------------- +# dhcpd.conf +# +# Sample configuration file for ISC dhcpd +# + +# option definitions common to all supported networks... +option domain-name "example.org"; +# option domain-name-servers ns1.example.org, ns2.example.org; + +default-lease-time 1800; +max-lease-time 7200; + +# We're going to be authoritative for the network we've +# just created. + +authoritative; + +# No service will be given on this subnet, but we're telling +# the DHCP server about it so it understands it's there and +# not to hand out leases for it. + +subnet 10.14.8.195 netmask 255.255.255.0 { +} + +# But we do want to hand out leases for the 192.168.1.0/24 +# network for purposes of this test.. + +subnet 192.168.1.0 netmask 255.255.255.0 { + range 192.168.1.100 192.168.1.150; + option routers 192.168.1.1; +} +# ------------------------- diff --git a/keama/tests/samples/simple.json b/keama/tests/samples/simple.json new file mode 100644 index 00000000..7cd02423 --- /dev/null +++ b/keama/tests/samples/simple.json @@ -0,0 +1,53 @@ +{ + # ------------------------- + # dhcpd.conf + # + # Sample configuration file for ISC dhcpd + # + # option definitions common to all supported networks... + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "option-data": [ + { + "space": "dhcp4", + "name": "domain-name", + "code": 15, + "data": "example.org" + } + ], + "valid-lifetime": 1800, + "max-valid-lifetime": 7200, + # We're going to be authoritative for the network we've + # just created. + "authoritative": true, + "subnet4": [ + # No service will be given on this subnet, but we're telling + # the DHCP server about it so it understands it's there and + # not to hand out leases for it. + { + "id": 1, + "subnet": "10.14.8.195/24" + }, + # But we do want to hand out leases for the 192.168.1.0/24 + # network for purposes of this test.. + { + "id": 2, + "subnet": "192.168.1.0/24", + "pools": [ + { + "pool": "192.168.1.100 - 192.168.1.150" + } + ], + "option-data": [ + { + "space": "dhcp4", + "name": "routers", + "code": 3, + "data": "192.168.1.1" + } + ] + } + ] + } +} diff --git a/keama/tests/samples/test-a6.conf b/keama/tests/samples/test-a6.conf new file mode 100644 index 00000000..9514c4aa --- /dev/null +++ b/keama/tests/samples/test-a6.conf @@ -0,0 +1,75 @@ +# +# Define the DHCPv6 option space. +# +# Option numbers are assigned by IANA: +# http://www.iana.org/assignments/dhcpv6-parameters +# +option dhcp6.time-servers code 1040 = array of ip6-address; +option dhcp6.time-offset code 1041 = signed integer 32; + +# +# Define the DOCSIS option space. +# TODO: DOCSIS oro definition +# +# this space is already known by Kea +option space docsis code width 2 length width 2; +option vsio.docsis code 4491 = encapsulate docsis; +option docsis.tftp-servers code 32 = array of ip6-address; +% option docsis.tftp-servers local; +option docsis.cablelabs-configuration-file code 33 = text; +% option docsis.cablelabs-configuration-file alias config-file; +% option docsis.cablelabs-configuration-file local; +option docsis.cablelabs-syslog-servers code 34 = array of ip6-address; +% option docsis.cablelabs-syslog-servers alias syslog-servers; +% option docsis.cablelabs-syslog-servers local; +#option docsis.device-id code 36 = string; +% option docsis.device-id code 36 = "X"; +% option docsis.device-id local; + +# +# Declare some options. +# +option dhcp6.time-servers 3ffe:bbbb:aaaa:aaaa::1, 3ffe:bbbb:aaaa:aaaa::2; +option docsis.tftp-servers 3ffe:cccc:aaaa:aaaa::1, 3ffe:cccc:aaaa:aaaa::2; + +# +# DNS server IP address to update dynamically +# +ddns-update-style interim; +ddns-domainname "foo.com"; + +# +# Per-host settings. +# +host cablemodem-1 { + host-identifier option + dhcp6.client-id 00:01:00:01:0c:00:a1:41:00:06:5b:50:99:f6; + fixed-address6 3ffe:aaaa:aaaa:aaaa::ffff; + ddns-domainname "bar.com"; + option dhcp6.time-servers 3ffe:aaaa:aaaa:aaaa::1, + 3ffe:aaaa:aaaa:aaaa::2; + option docsis.tftp-servers 3ffe:aaaa:aaaa:aaaa::1, + 3ffe:aaaa:aaaa:aaaa::2; + option dhcp6.time-offset -14400; # -4 hours + option docsis.cablelabs-configuration-file "bootfile.cfg"; + option docsis.cablelabs-syslog-servers 3ffe:aaaa:aaaa:aaaa::1, + 3ffe:aaaa:aaaa:aaaa::2; +} + +#host cablemodem-2 { +# host-identifier option docsis.device-id 00:06:5B:50:99:F6; +# option dhcp6.time-servers 3ffe:dddd:aaaa:aaaa::1, +# 3ffe:dddd:aaaa:aaaa::2; +# option docsis.tftp-servers 3ffe:dddd:aaaa:aaaa::1, +# 3ffe:dddd:aaaa:aaaa::2; +# option dhcp6.time-offset -14400; # -4 hours +# option docsis.cablelabs-configuration-file "bootfile.cfg"; +# option docsis.cablelabs-syslog-servers 3ffe:aaaa:aaaa:aaaa::1, +# 3ffe:aaaa:aaaa:aaaa::2; +#} + +# XXX: for testing +subnet6 3ffe:aaaa:aaaa:aaaa::/64 { +} + + diff --git a/keama/tests/samples/test-a6.conf.orig b/keama/tests/samples/test-a6.conf.orig new file mode 100644 index 00000000..9fe42364 --- /dev/null +++ b/keama/tests/samples/test-a6.conf.orig @@ -0,0 +1,67 @@ +# +# Define the DHCPv6 option space. +# +# Option numbers are assigned by IANA: +# http://www.iana.org/assignments/dhcpv6-parameters +# +option dhcp6.time-servers code 40 = array of ip6-address; +option dhcp6.time-offset code 41 = signed integer 32; + +# +# Define the DOCSIS option space. +# TODO: DOCSIS oro definition +# +option space docsis code width 2 length width 2; +option vsio.docsis code 4491 = encapsulate docsis; +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; + +# +# Declare some options. +# +option dhcp6.time-servers 3ffe:bbbb:aaaa:aaaa::1, 3ffe:bbbb:aaaa:aaaa::2; +option docsis.tftp-servers 3ffe:cccc:aaaa:aaaa::1, 3ffe:cccc:aaaa:aaaa::2; + +# +# DNS server IP address to update dynamically +# +ddns-update-style interim; +ddns-domainname "foo.com"; + +# +# Per-host settings. +# +host cablemodem-1 { + host-identifier option + dhcp6.client-id 00:01:00:01:0c:00:a1:41:00:06:5b:50:99:f6; + fixed-address6 3ffe:aaaa:aaaa:aaaa::ffff; + ddns-domainname "bar.com"; + option dhcp6.time-servers 3ffe:aaaa:aaaa:aaaa::1, + 3ffe:aaaa:aaaa:aaaa::2; + option docsis.tftp-servers 3ffe:aaaa:aaaa:aaaa::1, + 3ffe:aaaa:aaaa:aaaa::2; + option dhcp6.time-offset -14400; # -4 hours + option docsis.cablelabs-configuration-file "bootfile.cfg"; + option docsis.cablelabs-syslog-servers 3ffe:aaaa:aaaa:aaaa::1, + 3ffe:aaaa:aaaa:aaaa::2; +} + +#host cablemodem-2 { +# host-identifier option docsis.device-id 00:06:5B:50:99:F6; +# option dhcp6.time-servers 3ffe:dddd:aaaa:aaaa::1, +# 3ffe:dddd:aaaa:aaaa::2; +# option docsis.tftp-servers 3ffe:dddd:aaaa:aaaa::1, +# 3ffe:dddd:aaaa:aaaa::2; +# option dhcp6.time-offset -14400; # -4 hours +# option docsis.cablelabs-configuration-file "bootfile.cfg"; +# option docsis.cablelabs-syslog-servers 3ffe:aaaa:aaaa:aaaa::1, +# 3ffe:aaaa:aaaa:aaaa::2; +#} + +# XXX: for testing +subnet6 fe80::20c:29ff:fe42:820/128 { +} + + diff --git a/keama/tests/samples/test-a6.json b/keama/tests/samples/test-a6.json new file mode 100644 index 00000000..8b768686 --- /dev/null +++ b/keama/tests/samples/test-a6.json @@ -0,0 +1,144 @@ +{ + # + # Define the DHCPv6 option space. + # + # Option numbers are assigned by IANA: + # http://www.iana.org/assignments/dhcpv6-parameters + # + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp6": { + "option-def": [ + { + "space": "dhcp6", + "name": "time-servers", + "code": 1040, + "array": true, + "type": "ipv6-address" + }, + { + "space": "dhcp6", + "name": "time-offset", + "code": 1041, + "type": "int32" + } + ], + "option-data": [ + #option docsis.device-id code 36 = string; + # + # Declare some options. + # + { + "space": "dhcp6", + "name": "time-servers", + "code": 1040, + "data": "3ffe:bbbb:aaaa:aaaa::1, 3ffe:bbbb:aaaa:aaaa::2" + }, + { + "space": "dhcp6", + "name": "vendor-opts", + "code": 17, + "data": "4491" + }, + { + "space": "vendor-4491", + "name": "tftp-servers", + "code": 32, + "data": "3ffe:cccc:aaaa:aaaa::1, 3ffe:cccc:aaaa:aaaa::2" + } + ], +// /// Unsupported ddns-update-style interim +// "ddns-update-style": "interim", + "dhcp-ddns": { + "enable-updates": true, + "qualifying-suffix": "foo.com" + }, + "host-reservation-identifiers": [ + "flex-id" + ], + /// The flexible host identifier is a premium feature + "hooks-libraries": [ + { + /// Please update the path here + "library": "/path/libdhcp_flex_id.so", + "parameters": { + "identifier-expression": "option[1].hex" + } + } + ], + "subnet6": [ + #host cablemodem-2 { + # host-identifier option docsis.device-id 00:06:5B:50:99:F6; + # option dhcp6.time-servers 3ffe:dddd:aaaa:aaaa::1, + # 3ffe:dddd:aaaa:aaaa::2; + # option docsis.tftp-servers 3ffe:dddd:aaaa:aaaa::1, + # 3ffe:dddd:aaaa:aaaa::2; + # option dhcp6.time-offset -14400; # -4 hours + # option docsis.cablelabs-configuration-file "bootfile.cfg"; + # option docsis.cablelabs-syslog-servers 3ffe:aaaa:aaaa:aaaa::1, + # 3ffe:aaaa:aaaa:aaaa::2; + #} + # XXX: for testing + { + "id": 1, + "subnet": "3ffe:aaaa:aaaa:aaaa::/64", + "reservations": [ + # + # Per-host settings. + # + { + "hostname": "cablemodem-1", + "flex-id": "000100010c00a14100065b5099f6", + "ip-addresses": [ + "3ffe:aaaa:aaaa:aaaa::ffff" + ], +// /// Only global qualifying-suffix is supported +// "qualifying-suffix": "bar.com", + "option-data": [ + { + "space": "dhcp6", + "name": "time-servers", + "code": 1040, +// "original-data": "3ffe:aaaa:aaaa:aaaa::1, \n\t\t\t\t 3ffe:aaaa:aaaa:aaaa::2", + "data": "3ffe:aaaa:aaaa:aaaa::1, 3ffe:aaaa:aaaa:aaaa::2" + }, + { + "space": "dhcp6", + "name": "vendor-opts", + "code": 17, + "data": "4491" + }, + { + "space": "vendor-4491", + "name": "tftp-servers", + "code": 32, +// "original-data": "3ffe:aaaa:aaaa:aaaa::1,\n\t\t\t\t 3ffe:aaaa:aaaa:aaaa::2", + "data": "3ffe:aaaa:aaaa:aaaa::1, 3ffe:aaaa:aaaa:aaaa::2" + }, + { + "space": "dhcp6", + "name": "time-offset", + "code": 1041, + "data": "-14400" + }, + # -4 hours + { + "space": "vendor-4491", + "name": "config-file", + "code": 33, + "data": "bootfile.cfg" + }, + { + "space": "vendor-4491", + "name": "syslog-servers", + "code": 34, +// "original-data": "3ffe:aaaa:aaaa:aaaa::1,\n\t\t\t\t\t 3ffe:aaaa:aaaa:aaaa::2", + "data": "3ffe:aaaa:aaaa:aaaa::1, 3ffe:aaaa:aaaa:aaaa::2" + } + ] + } + ] + } + ] + } +} diff --git a/keama/tests/samples/vmnet8.conf b/keama/tests/samples/vmnet8.conf new file mode 100644 index 00000000..9484938e --- /dev/null +++ b/keama/tests/samples/vmnet8.conf @@ -0,0 +1,43 @@ +# Configuration file for ISC 2.0 vmnet-dhcpd operating on vmnet8. +# +# This file was automatically generated by the VMware configuration program. +# See Instructions below if you want to modify it. +# +# We set domain-name-servers to make some DHCP clients happy +# (dhclient as configured in SuSE, TurboLinux, etc.). +# We also supply a domain name to make pump (Red Hat 6.x) happy. +# + + +###### VMNET DHCP Configuration. Start of "DO NOT MODIFY SECTION" ##### +# Modification Instructions: This section of the configuration file contains +# information generated by the configuration program. Do not modify this +# section. +# You are free to modify everything else. Also, this section must start +# on a new line +# This file will get backed up with a different name in the same directory +# if this section is edited and you try to configure DHCP again. + +# Written at: 04/12/2017 14:00:17 +allow unknown-clients; +default-lease-time 1800; # default is 30 minutes +max-lease-time 7200; # default is 2 hours + +subnet 172.16.254.0 netmask 255.255.255.0 { + range 172.16.254.128 172.16.254.254; + option broadcast-address 172.16.254.255; + option domain-name-servers 172.16.254.2; + option domain-name localdomain; + default-lease-time 1800; # default is 30 minutes + max-lease-time 7200; # default is 2 hours + option netbios-name-servers 172.16.254.2; + option routers 172.16.254.2; +} +host vmnet8 { + hardware ethernet 00:50:56:C0:00:08; + fixed-address 172.16.254.1; + option domain-name-servers 0.0.0.0; +# option domain-name ""; + option routers 0.0.0.0; +} +####### VMNET DHCP Configuration. End of "DO NOT MODIFY SECTION" ####### diff --git a/keama/tests/samples/vmnet8.conf.orig b/keama/tests/samples/vmnet8.conf.orig new file mode 100644 index 00000000..313deca7 --- /dev/null +++ b/keama/tests/samples/vmnet8.conf.orig @@ -0,0 +1,43 @@ +# Configuration file for ISC 2.0 vmnet-dhcpd operating on vmnet8. +# +# This file was automatically generated by the VMware configuration program. +# See Instructions below if you want to modify it. +# +# We set domain-name-servers to make some DHCP clients happy +# (dhclient as configured in SuSE, TurboLinux, etc.). +# We also supply a domain name to make pump (Red Hat 6.x) happy. +# + + +###### VMNET DHCP Configuration. Start of "DO NOT MODIFY SECTION" ##### +# Modification Instructions: This section of the configuration file contains +# information generated by the configuration program. Do not modify this +# section. +# You are free to modify everything else. Also, this section must start +# on a new line +# This file will get backed up with a different name in the same directory +# if this section is edited and you try to configure DHCP again. + +# Written at: 04/12/2017 14:00:17 +allow unknown-clients; +default-lease-time 1800; # default is 30 minutes +max-lease-time 7200; # default is 2 hours + +subnet 172.16.254.0 netmask 255.255.255.0 { + range 172.16.254.128 172.16.254.254; + option broadcast-address 172.16.254.255; + option domain-name-servers 172.16.254.2; + option domain-name localdomain; + default-lease-time 1800; # default is 30 minutes + max-lease-time 7200; # default is 2 hours + option netbios-name-servers 172.16.254.2; + option routers 172.16.254.2; +} +host vmnet8 { + hardware ethernet 00:50:56:C0:00:08; + fixed-address 172.16.254.1; + option domain-name-servers 0.0.0.0; + option domain-name ""; + option routers 0.0.0.0; +} +####### VMNET DHCP Configuration. End of "DO NOT MODIFY SECTION" ####### diff --git a/keama/tests/samples/vmnet8.json b/keama/tests/samples/vmnet8.json new file mode 100644 index 00000000..9b207ff8 --- /dev/null +++ b/keama/tests/samples/vmnet8.json @@ -0,0 +1,105 @@ +{ + # Configuration file for ISC 2.0 vmnet-dhcpd operating on vmnet8. + # + # This file was automatically generated by the VMware configuration program. + # See Instructions below if you want to modify it. + # + # We set domain-name-servers to make some DHCP clients happy + # (dhclient as configured in SuSE, TurboLinux, etc.). + # We also supply a domain name to make pump (Red Hat 6.x) happy. + # + ###### VMNET DHCP Configuration. Start of "DO NOT MODIFY SECTION" ##### + # Modification Instructions: This section of the configuration file contains + # information generated by the configuration program. Do not modify this + # section. + # You are free to modify everything else. Also, this section must start + # on a new line + # This file will get backed up with a different name in the same directory + # if this section is edited and you try to configure DHCP again. + # Written at: 04/12/2017 14:00:17 + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { +// "statement": { +// "config": { +// "value": "allow", +// "name": "boot-unknown-clients", +// "code": 6 +// } +// }, + "valid-lifetime": 1800, + "max-valid-lifetime": 7200, + "subnet4": [ + # default is 2 hours + { + "id": 1, + "subnet": "172.16.254.0/24", + "pools": [ + { + "pool": "172.16.254.128 - 172.16.254.254" + } + ], + "option-data": [ + { + "space": "dhcp4", + "name": "broadcast-address", + "code": 28, + "data": "172.16.254.255" + }, + { + "space": "dhcp4", + "name": "domain-name-servers", + "code": 6, + "data": "172.16.254.2" + }, + { + "space": "dhcp4", + "name": "domain-name", + "code": 15, + "data": "localdomain" + }, + # default is 2 hours + { + "space": "dhcp4", + "name": "netbios-name-servers", + "code": 44, + "data": "172.16.254.2" + }, + { + "space": "dhcp4", + "name": "routers", + "code": 3, + "data": "172.16.254.2" + } + ], + "valid-lifetime": 1800, + "max-valid-lifetime": 7200, + "reservations": [ + { + "hostname": "vmnet8", + "hw-address": "00:50:56:c0:00:08", + "ip-address": "172.16.254.1", + "option-data": [ + { + "space": "dhcp4", + "name": "domain-name-servers", + "code": 6, + "data": "0.0.0.0" + }, + # option domain-name ""; + { + "space": "dhcp4", + "name": "routers", + "code": 3, + "data": "0.0.0.0" + } + ] + } + ] + } + ], + "host-reservation-identifiers": [ + "hw-address" + ] + } +} diff --git a/keama/tests/share0.err b/keama/tests/share0.err new file mode 100644 index 00000000..6b2184e4 --- /dev/null +++ b/keama/tests/share0.err @@ -0,0 +1,6 @@ +# bad (empty name)shared-network declaration config + +# shared-network declaration +shared-network "" { + +} diff --git a/keama/tests/share0.msg b/keama/tests/share0.msg new file mode 100644 index 00000000..4296f4e8 --- /dev/null +++ b/keama/tests/share0.msg @@ -0,0 +1 @@ +share0.err line 4: zero-length shared network name diff --git a/keama/tests/share2if.err b/keama/tests/share2if.err new file mode 100644 index 00000000..2c40326d --- /dev/null +++ b/keama/tests/share2if.err @@ -0,0 +1,7 @@ +# bad (2 interfaces)shared-network declaration config + +# shared-network declaration +shared-network "foobar" { + interface "foo"; + interface "bar"; +} diff --git a/keama/tests/share2if.msg b/keama/tests/share2if.msg new file mode 100644 index 00000000..bc4b9c10 --- /dev/null +++ b/keama/tests/share2if.msg @@ -0,0 +1 @@ +share2if.err line 6: A shared network can't be connected to two interfaces. diff --git a/keama/tests/shareempty.err b/keama/tests/shareempty.err new file mode 100644 index 00000000..2e9e3a12 --- /dev/null +++ b/keama/tests/shareempty.err @@ -0,0 +1,6 @@ +# bad (no subnet) shared-network declaration config + +# shared-network declaration +shared-network "foobar" { + interface "foo"; +} diff --git a/keama/tests/shareempty.msg b/keama/tests/shareempty.msg new file mode 100644 index 00000000..fb74cc62 --- /dev/null +++ b/keama/tests/shareempty.msg @@ -0,0 +1 @@ +shareempty.err line 6: empty shared-network decl diff --git a/keama/tests/shareinclass.err b/keama/tests/shareinclass.err new file mode 100644 index 00000000..d68e724e --- /dev/null +++ b/keama/tests/shareinclass.err @@ -0,0 +1,11 @@ +# shared-network declaration inside class declaration config + +# class declaration +class "foobar" { + hardware ethernet 00:0B:FD:32:E6:FA; + # can't put a shared-network declaration here + shared-network "illegal" { + default-lease-time 1800; + } +} + diff --git a/keama/tests/shareinclass.msg b/keama/tests/shareinclass.msg new file mode 100644 index 00000000..656898c2 --- /dev/null +++ b/keama/tests/shareinclass.msg @@ -0,0 +1 @@ +shareinclass.err line 5: hardware address parameter not allowed here. diff --git a/keama/tests/shareinhost.err b/keama/tests/shareinhost.err new file mode 100644 index 00000000..93b049c7 --- /dev/null +++ b/keama/tests/shareinhost.err @@ -0,0 +1,11 @@ +# shared-network declaration inside host declaration config + +# host declaration +host foobar { + hardware ethernet 00:0B:FD:32:E6:FA; + # can't put a shared-network declaration here + shared-network "illegal" { + default-lease-time 1800; + } +} + diff --git a/keama/tests/shareinhost.msg b/keama/tests/shareinhost.msg new file mode 100644 index 00000000..22da166d --- /dev/null +++ b/keama/tests/shareinhost.msg @@ -0,0 +1 @@ +shareinhost.err line 7: shared-network parameters not allowed here. diff --git a/keama/tests/shareinshare.err b/keama/tests/shareinshare.err new file mode 100644 index 00000000..68750613 --- /dev/null +++ b/keama/tests/shareinshare.err @@ -0,0 +1,10 @@ +# shared-network declaration inside shared-network declaration config + +# shared-network declaration +shared-network "foobar" { + # can't put another shared-network declaration here + shared-network "illegal" { + default-lease-time 1800; + } +} + diff --git a/keama/tests/shareinshare.msg b/keama/tests/shareinshare.msg new file mode 100644 index 00000000..a2579e8e --- /dev/null +++ b/keama/tests/shareinshare.msg @@ -0,0 +1 @@ +shareinshare.err line 6: shared-network parameters not allowed here. diff --git a/keama/tests/shareinsubnet4.err4 b/keama/tests/shareinsubnet4.err4 new file mode 100644 index 00000000..ecc0d080 --- /dev/null +++ b/keama/tests/shareinsubnet4.err4 @@ -0,0 +1,10 @@ +# shared-network declaration inside DHCPv4 subnet declaration config + +# subnet declaration +subnet 10.5.5.0 netmask 255.255.255.224 { + # can't put a shared-network declaration here + shared-network "illegal" { + default-lease-time 1800; + } +} + diff --git a/keama/tests/shareinsubnet4.msg b/keama/tests/shareinsubnet4.msg new file mode 100644 index 00000000..c332da87 --- /dev/null +++ b/keama/tests/shareinsubnet4.msg @@ -0,0 +1 @@ +shareinsubnet4.err4 line 6: shared-network parameters not allowed here. diff --git a/keama/tests/shareinsubnet6.err6 b/keama/tests/shareinsubnet6.err6 new file mode 100644 index 00000000..fec6f6d2 --- /dev/null +++ b/keama/tests/shareinsubnet6.err6 @@ -0,0 +1,10 @@ +# shared-network declaration inside DHCPv6 subnet declaration config + +# subnet declaration +subnet6 2001::/64 { + # can't put a shared-network declaration here + shared-network "illegal" { + default-lease-time 1800; + } +} + diff --git a/keama/tests/shareinsubnet6.msg b/keama/tests/shareinsubnet6.msg new file mode 100644 index 00000000..640e5323 --- /dev/null +++ b/keama/tests/shareinsubnet6.msg @@ -0,0 +1 @@ +shareinsubnet6.err6 line 6: shared-network parameters not allowed here. diff --git a/keama/tests/sharenoname.err b/keama/tests/sharenoname.err new file mode 100644 index 00000000..854a1728 --- /dev/null +++ b/keama/tests/sharenoname.err @@ -0,0 +1,6 @@ +# bad (no name)shared-network declaration config + +# shared-network declaration +shared-network { + +} diff --git a/keama/tests/sharenoname.msg b/keama/tests/sharenoname.msg new file mode 100644 index 00000000..ad12f0d7 --- /dev/null +++ b/keama/tests/sharenoname.msg @@ -0,0 +1 @@ +sharenoname.err line 4: expecting a name for shared-network diff --git a/keama/tests/shareone4.in4 b/keama/tests/shareone4.in4 new file mode 100644 index 00000000..ef0e15ac --- /dev/null +++ b/keama/tests/shareone4.in4 @@ -0,0 +1,22 @@ +# DHCPv4 one-subnet shared-network declaration config + +# shared-network declaration +shared-network "foobar" { + # interface + interface "en0"; + # option + option domain-search "example.com", "example.org"; + # parameter + default-lease-time 1800; + # subnet declaration + subnet 10.5.5.0 netmask 255.255.255.224 { + # redefined parameter + default-lease-time 3600; + # another option + option ip-forwarding true; + } + # pool (must be after the subnet) + pool { + range 10.5.5.5 10.5.5.10; + } +} diff --git a/keama/tests/shareone4.out b/keama/tests/shareone4.out new file mode 100644 index 00000000..56f5cbb7 --- /dev/null +++ b/keama/tests/shareone4.out @@ -0,0 +1,44 @@ +{ + # DHCPv4 one-subnet shared-network declaration config + # shared-network declaration + "Dhcp4": { + "interfaces-config": { + "interfaces": [ + "en0" + ] + }, + "subnet4": [ + # subnet declaration + { + "id": 1, + "subnet": "10.5.5.0/27", + "valid-lifetime": 3600, + "option-data": [ + # another option + { + "space": "dhcp4", + "name": "ip-forwarding", + "code": 19, + "data": "true" + }, + # interface + # option + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "interface": "en0", + "pools": [ + # pool (must be after the subnet) + { + "pool": "10.5.5.5 - 10.5.5.10" + } + ] + } + ] + } +} diff --git a/keama/tests/shareone6.in6 b/keama/tests/shareone6.in6 new file mode 100644 index 00000000..11b03cb0 --- /dev/null +++ b/keama/tests/shareone6.in6 @@ -0,0 +1,19 @@ +# DHCPv6 one-subnet shared-network declaration config + +# shared-network declaration +shared-network "foobar" { + # interface + interface "en0"; + # option + option dhcp6.domain-search "example.com", "example.org"; + # parameter + default-lease-time 1800; + # subnet declaration + subnet6 2001::/64 { + # redefined parameter + default-lease-time 3600; + # pool + range6 2001::1000 2001::2000; + } + # tried another pool here but DHCPv6 pools are allowed only in subnets +} diff --git a/keama/tests/shareone6.out b/keama/tests/shareone6.out new file mode 100644 index 00000000..6f8389c3 --- /dev/null +++ b/keama/tests/shareone6.out @@ -0,0 +1,37 @@ +{ + # DHCPv6 one-subnet shared-network declaration config + # shared-network declaration + "Dhcp6": { + "interfaces-config": { + "interfaces": [ + "en0" + ] + }, + "subnet6": [ + # subnet declaration + { + "id": 1, + "subnet": "2001::/64", + "valid-lifetime": 3600, + "pools": [ + { + # pool + "pool": "2001::1000 - 2001::2000" + } + ], + "interface": "en0", + "option-data": [ + # interface + # option + { + "space": "dhcp6", + "name": "domain-search", + "code": 24, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ] + } + ] + } +} diff --git a/keama/tests/sharepools4.in4 b/keama/tests/sharepools4.in4 new file mode 100644 index 00000000..b42a8481 --- /dev/null +++ b/keama/tests/sharepools4.in4 @@ -0,0 +1,27 @@ +# DHCPv4 two pools and subnets shared-network declaration config + +# shared-network declaration +shared-network "foobar" { + # interface + interface "en0"; + # option + option domain-search "example.com", "example.org"; + # parameter + default-lease-time 1800; + # subnet declaration + subnet 10.5.5.0 netmask 255.255.255.224 { + # redefined parameter + default-lease-time 3600; + # another option + option ip-forwarding true; + } + # second subnet declaration + subnet 10.10.10.0 netmask 255.255.255.224 { } + # pools at shared-network level + pool { + range 10.5.5.5 10.5.5.10; + } + pool { + range 10.10.10.5 10.10.10.10; + } +} diff --git a/keama/tests/sharepools4.out b/keama/tests/sharepools4.out new file mode 100644 index 00000000..087ec5c8 --- /dev/null +++ b/keama/tests/sharepools4.out @@ -0,0 +1,63 @@ +{ + # DHCPv4 two pools and subnets shared-network declaration config + # shared-network declaration + "Dhcp4": { + "interfaces-config": { + "interfaces": [ + "en0" + ] + }, + /// Kea shared-networks are different, cf Kea #236 + "shared-networks": [ + { + "name": "foobar", + "subnet4": [ + # subnet declaration + { + "id": 1, + "subnet": "10.5.5.0/27", + "valid-lifetime": 3600, + "option-data": [ + # another option + { + "space": "dhcp4", + "name": "ip-forwarding", + "code": 19, + "data": "true" + } + ], + "pools": [ + # pools at shared-network level + { + "pool": "10.5.5.5 - 10.5.5.10" + } + ] + }, + # second subnet declaration + { + "id": 2, + "subnet": "10.10.10.0/27", + "pools": [ + { + "pool": "10.10.10.5 - 10.10.10.10" + } + ] + } + ], + "interface": "en0", + "option-data": [ + # interface + # option + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "valid-lifetime": 1800 + } + ] + } +} diff --git a/keama/tests/sharetwo4.in4 b/keama/tests/sharetwo4.in4 new file mode 100644 index 00000000..fa9126fe --- /dev/null +++ b/keama/tests/sharetwo4.in4 @@ -0,0 +1,29 @@ +# DHCPv4 two subnets in shared-network declaration config + +# shared-network declaration +shared-network "foobar" { + # interface + interface "en0"; + # option + option domain-search "example.com", "example.org"; + # parameter + default-lease-time 1800; + # subnet declaration + subnet 10.5.5.0 netmask 255.255.255.224 { + # redefined parameter + default-lease-time 3600; + # another option + option ip-forwarding true; + # pool inside the subnet + pool { + range 10.5.5.5 10.5.5.10; + } + } + # second subnet declaration + subnet 10.10.10.0 netmask 255.255.255.224 { + # pool inside the subnet + pool { + range 10.10.10.5 10.10.10.10; + } + } +} diff --git a/keama/tests/sharetwo4.out b/keama/tests/sharetwo4.out new file mode 100644 index 00000000..71f7832e --- /dev/null +++ b/keama/tests/sharetwo4.out @@ -0,0 +1,64 @@ +{ + # DHCPv4 two subnets in shared-network declaration config + # shared-network declaration + "Dhcp4": { + "interfaces-config": { + "interfaces": [ + "en0" + ] + }, + /// Kea shared-networks are different, cf Kea #236 + "shared-networks": [ + { + "name": "foobar", + "subnet4": [ + # subnet declaration + { + "id": 1, + "subnet": "10.5.5.0/27", + "valid-lifetime": 3600, + "option-data": [ + # another option + { + "space": "dhcp4", + "name": "ip-forwarding", + "code": 19, + "data": "true" + } + ], + "pools": [ + # pool inside the subnet + { + "pool": "10.5.5.5 - 10.5.5.10" + } + ] + }, + # second subnet declaration + { + "id": 2, + "subnet": "10.10.10.0/27", + "pools": [ + # pool inside the subnet + { + "pool": "10.10.10.5 - 10.10.10.10" + } + ] + } + ], + "interface": "en0", + "option-data": [ + # interface + # option + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "valid-lifetime": 1800 + } + ] + } +} diff --git a/keama/tests/sharetwo6.in6 b/keama/tests/sharetwo6.in6 new file mode 100644 index 00000000..3c75aad5 --- /dev/null +++ b/keama/tests/sharetwo6.in6 @@ -0,0 +1,24 @@ +# DHCPv6 two subnets in shared-network declaration config + +# shared-network declaration +shared-network "foobar" { + # interface + interface "en0"; + # option + option dhcp6.domain-search "example.com", "example.org"; + # parameter + default-lease-time 1800; + # subnet declaration + subnet6 2001::/64 { + # redefined parameter + default-lease-time 3600; + # pool + range6 2001::1000 2001::2000; + } + # second subnet declaration + subnet6 2002::/64 { + pool6 { + prefix6 2001:0:0:10:: 2001:0:0:1f:: /64; + } + } +} diff --git a/keama/tests/sharetwo6.out b/keama/tests/sharetwo6.out new file mode 100644 index 00000000..23df4e7c --- /dev/null +++ b/keama/tests/sharetwo6.out @@ -0,0 +1,56 @@ +{ + # DHCPv6 two subnets in shared-network declaration config + # shared-network declaration + "Dhcp6": { + "interfaces-config": { + "interfaces": [ + "en0" + ] + }, + /// Kea shared-networks are different, cf Kea #236 + "shared-networks": [ + { + "name": "foobar", + "subnet6": [ + # subnet declaration + { + "id": 1, + "subnet": "2001::/64", + "valid-lifetime": 3600, + "pools": [ + { + # pool + "pool": "2001::1000 - 2001::2000" + } + ] + }, + # second subnet declaration + { + "id": 2, + "subnet": "2002::/64", + "pd-pools": [ + { + "prefix": "2001:0:0:10::", + "delegated-len": 64, + "prefix-len": 60 + } + ] + } + ], + "interface": "en0", + "option-data": [ + # interface + # option + { + "space": "dhcp6", + "name": "domain-search", + "code": 24, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "valid-lifetime": 1800 + } + ] + } +} diff --git a/keama/tests/sname4.notyet b/keama/tests/sname4.notyet new file mode 100644 index 00000000..172b2acf --- /dev/null +++ b/keama/tests/sname4.notyet @@ -0,0 +1,20 @@ +# server-name data expression +# Kea has no server-name extractor in libeval + +# authoritative is mandatory +authoritative; + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# by filename superclass +class "byfn" { + match server-name; +} + +subclass "byfn" "foobar" { + option domain-name-servers 10.5.5.1; +} + +# raw +option bootfile-name = concat(server-name, "/", option host-name); diff --git a/keama/tests/spawning6.in6 b/keama/tests/spawning6.in6 new file mode 100644 index 00000000..0c33afe1 --- /dev/null +++ b/keama/tests/spawning6.in6 @@ -0,0 +1,11 @@ +# spawning declaration config + +# options +option dhcp6.mysystem code 1250 = text; +option dhcp6.myversion code 1251 = unsigned integer 16; + +# superclass declaration +class "foobar" { + spawn with option dhcp6.mysystem; + option dhcp6.myversion 1; +} diff --git a/keama/tests/spawning6.out b/keama/tests/spawning6.out new file mode 100644 index 00000000..ce556d80 --- /dev/null +++ b/keama/tests/spawning6.out @@ -0,0 +1,37 @@ +{ + # spawning declaration config + # options + "Dhcp6": { + "option-def": [ + { + "space": "dhcp6", + "name": "mysystem", + "code": 1250, + "type": "string" + }, + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "type": "uint16" + } + ], + "client-classes": [ + # superclass declaration + /// Spawning classes are not supported by Kea + /// Reference Kea #248 + /// spawn with: option dhcp6.mysystem + { + "name": "foobar", + "option-data": [ + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "data": "1" + } + ] + } + ] + } +} diff --git a/keama/tests/subclass4.in4 b/keama/tests/subclass4.in4 new file mode 100644 index 00000000..bdae6632 --- /dev/null +++ b/keama/tests/subclass4.in4 @@ -0,0 +1,23 @@ +# subclass declaration config + +# options +option mysystem code 250 = text; +option myversion code 251 = unsigned integer 16; + +# superclass declaration +class "foobar" { + match option mysystem; + option myversion 1; +} + +# simple subclass declaration +subclass "foobar" "version1"; + +# option setting subclass declaration +subclass "foobar" "version2" { option myversion 2; } + +# complex subclass declaration +subclass "foobar" "version3" { + option myversion 3; + next-server 192.168.0.1; +} diff --git a/keama/tests/subclass4.out b/keama/tests/subclass4.out new file mode 100644 index 00000000..e155d0de --- /dev/null +++ b/keama/tests/subclass4.out @@ -0,0 +1,84 @@ +{ + # subclass declaration config + # options + "Dhcp4": { + "option-def": [ + { + "space": "dhcp4", + "name": "mysystem", + "code": 250, + "type": "string" + }, + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "type": "uint16" + } + ], + "client-classes": [ + # superclass declaration + /// match: option dhcp.mysystem + { + "name": "foobar", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "1" + } + ] + }, + # simple subclass declaration + /// subclass selector 'version1' + { + "name": "sub#foobar#0", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "1" + } + ], + /// from: match option dhcp.mysystem + /// data: 'version1' + "test": "option[250].hex == 'version1'" + }, + # option setting subclass declaration + /// subclass selector 'version2' + { + "name": "sub#foobar#1", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "2" + } + ], + /// from: match option dhcp.mysystem + /// data: 'version2' + "test": "option[250].hex == 'version2'" + }, + # complex subclass declaration + /// subclass selector 'version3' + { + "name": "sub#foobar#2", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "3" + } + ], + "next-server": "192.168.0.1", + /// from: match option dhcp.mysystem + /// data: 'version3' + "test": "option[250].hex == 'version3'" + } + ] + } +} diff --git a/keama/tests/subclass6.in6 b/keama/tests/subclass6.in6 new file mode 100644 index 00000000..41408180 --- /dev/null +++ b/keama/tests/subclass6.in6 @@ -0,0 +1,23 @@ +# subclass declaration config + +# options +option dhcp6.mysystem code 1250 = text; +option dhcp6.myversion code 1251 = unsigned integer 16; + +# superclass declaration +class "foobar" { + match option dhcp6.mysystem; + option dhcp6.myversion 1; +} + +# simple subclass declaration +subclass "foobar" "version1"; + +# option setting subclass declaration +subclass "foobar" "version2" { option dhcp6.myversion 2; } + +# complex subclass declaration +subclass "foobar" "version3" { + option dhcp6.myversion 3; + option dhcp6.rapid-commit; +} diff --git a/keama/tests/subclass6.out b/keama/tests/subclass6.out new file mode 100644 index 00000000..3828d58d --- /dev/null +++ b/keama/tests/subclass6.out @@ -0,0 +1,89 @@ +{ + # subclass declaration config + # options + "Dhcp6": { + "option-def": [ + { + "space": "dhcp6", + "name": "mysystem", + "code": 1250, + "type": "string" + }, + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "type": "uint16" + } + ], + "client-classes": [ + # superclass declaration + /// match: option dhcp6.mysystem + { + "name": "foobar", + "option-data": [ + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "data": "1" + } + ] + }, + # simple subclass declaration + /// subclass selector 'version1' + { + "name": "sub#foobar#0", + "option-data": [ + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "data": "1" + } + ], + /// from: match option dhcp6.mysystem + /// data: 'version1' + "test": "option[1250].hex == 'version1'" + }, + # option setting subclass declaration + /// subclass selector 'version2' + { + "name": "sub#foobar#1", + "option-data": [ + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "data": "2" + } + ], + /// from: match option dhcp6.mysystem + /// data: 'version2' + "test": "option[1250].hex == 'version2'" + }, + # complex subclass declaration + /// subclass selector 'version3' + { + "name": "sub#foobar#2", + "option-data": [ + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "data": "3" + }, + { + "space": "dhcp6", + "name": "rapid-commit", + "code": 14, + "data": "" + } + ], + /// from: match option dhcp6.mysystem + /// data: 'version3' + "test": "option[1250].hex == 'version3'" + } + ] + } +} diff --git a/keama/tests/subclassbinsel4.in4 b/keama/tests/subclassbinsel4.in4 new file mode 100644 index 00000000..95d5aa54 --- /dev/null +++ b/keama/tests/subclassbinsel4.in4 @@ -0,0 +1,16 @@ +# subclass declaration config + +# options +option myversion code 251 = unsigned integer 16; + +# superclass declaration +class "foobar" { + match hardware; + option myversion 1; +} + +# simple subclass declaration +subclass "foobar" 1:00:07:0E:36:48:19; + +# option setting subclass declaration +subclass "foobar" 1:00:0B:FD:32:E6:FA { option myversion 2; } diff --git a/keama/tests/subclassbinsel4.out b/keama/tests/subclassbinsel4.out new file mode 100644 index 00000000..589afcb8 --- /dev/null +++ b/keama/tests/subclassbinsel4.out @@ -0,0 +1,61 @@ +{ + # subclass declaration config + # options + "Dhcp4": { + "option-def": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "type": "uint16" + } + ], + "client-classes": [ + # superclass declaration + /// match: hardware + { + "name": "foobar", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "1" + } + ] + }, + # simple subclass declaration + /// subclass selector 0x0x0100070e364819 + { + "name": "sub#foobar#0", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "1" + } + ], + /// from: match hardware + /// data: 0x0100070e364819 + "test": "concat(substring(pkt4.htype,-1,all), pkt4.mac) == 0x0100070e364819" + }, + # option setting subclass declaration + /// subclass selector 0x0x01000bfd32e6fa + { + "name": "sub#foobar#1", + "option-data": [ + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "data": "2" + } + ], + /// from: match hardware + /// data: 0x01000bfd32e6fa + "test": "concat(substring(pkt4.htype,-1,all), pkt4.mac) == 0x01000bfd32e6fa" + } + ] + } +} diff --git a/keama/tests/subclassbinsel6.in6 b/keama/tests/subclassbinsel6.in6 new file mode 100644 index 00000000..0a7e36d7 --- /dev/null +++ b/keama/tests/subclassbinsel6.in6 @@ -0,0 +1,25 @@ +# subclass declaration config + +# options +option dhcp6.hardware code 1250 = string; +option dhcp6.myversion code 1251 = unsigned integer 16; + +# superclass declaration +class "foobar" { + # no harware in DHCPv6 + match option dhcp6.hardware; + option dhcp6.myversion 1; +} + +# simple subclass declaration +subclass "foobar" 1:00:07:0E:36:48:19; + +# option setting subclass declaration +subclass "foobar" 1:00:0B:FD:32:E6:FA { option dhcp6.myversion 2; } + +# complex subclass declaration +subclass "foobar" 1:00:02:B3:88:C5:27 { + option dhcp6.myversion 3; + lease limit 20; +} + diff --git a/keama/tests/subclassbinsel6.out b/keama/tests/subclassbinsel6.out new file mode 100644 index 00000000..42c96bb2 --- /dev/null +++ b/keama/tests/subclassbinsel6.out @@ -0,0 +1,88 @@ +{ + # subclass declaration config + # options + "Dhcp6": { + "option-def": [ + { + "space": "dhcp6", + "name": "hardware", + "code": 1250, + "type": "string" + }, + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "type": "uint16" + } + ], + "client-classes": [ + # superclass declaration + /// match: option dhcp6.hardware + { + "name": "foobar", + "option-data": [ + # no harware in DHCPv6 + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "data": "1" + } + ] + }, + # simple subclass declaration + /// subclass selector 0x0x0100070e364819 + { + "name": "sub#foobar#0", + "option-data": [ + # no harware in DHCPv6 + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "data": "1" + } + ], + /// from: match option dhcp6.hardware + /// data: 0x0100070e364819 + "test": "option[1250].hex == 0x0100070e364819" + }, + # option setting subclass declaration + /// subclass selector 0x0x01000bfd32e6fa + { + "name": "sub#foobar#1", + "option-data": [ + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "data": "2" + } + ], + /// from: match option dhcp6.hardware + /// data: 0x01000bfd32e6fa + "test": "option[1250].hex == 0x01000bfd32e6fa" + }, + # complex subclass declaration + /// subclass selector 0x0x010002b388c527 + { + "name": "sub#foobar#2", + "option-data": [ + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "data": "3" + } + ], +// /// Per-class limit is not supported by Kea +// /// Reference Kea #237 +// "lease-limit": 20, + /// from: match option dhcp6.hardware + /// data: 0x010002b388c527 + "test": "option[1250].hex == 0x010002b388c527" + } + ] + } +} diff --git a/keama/tests/subclassguard4.in4 b/keama/tests/subclassguard4.in4 new file mode 100644 index 00000000..9a22d356 --- /dev/null +++ b/keama/tests/subclassguard4.in4 @@ -0,0 +1,24 @@ +# subclass with guard declaration config + +# options +option mysystem code 250 = text; +option myversion code 251 = unsigned integer 16; +option mydescr code 252 = text; + +# superclass declaration +class "foobar" { + match if option myversion = 0:1; + match option mysystem; +} + +# simple subclass declaration +subclass "foobar" "system1"; + +# option setting subclass declaration +subclass "foobar" "system2" { option mydescr "1.2"; } + +# complex subclass declaration +subclass "foobar" "system3" { + option mydescr "1.3"; + next-server 192.168.0.1; +} diff --git a/keama/tests/subclassguard4.out b/keama/tests/subclassguard4.out new file mode 100644 index 00000000..910b5b93 --- /dev/null +++ b/keama/tests/subclassguard4.out @@ -0,0 +1,79 @@ +{ + # subclass with guard declaration config + # options + "Dhcp4": { + "option-def": [ + { + "space": "dhcp4", + "name": "mysystem", + "code": 250, + "type": "string" + }, + { + "space": "dhcp4", + "name": "myversion", + "code": 251, + "type": "uint16" + }, + { + "space": "dhcp4", + "name": "mydescr", + "code": 252, + "type": "string" + } + ], + "client-classes": [ + # superclass declaration + /// match: option dhcp.mysystem + { + "name": "foobar", + /// from: match if (option dhcp.myversion) = 0x0001 + "test": "option[251].hex == 0x0001" + }, + # simple subclass declaration + /// subclass selector 'system1' + { + "name": "sub#foobar#0", + /// from: match-if (option dhcp.myversion) = 0x0001 + /// match: option dhcp.mysystem + /// data: 'system1' + "test": "(option[251].hex == 0x0001) and (option[250].hex == 'system1')" + }, + # option setting subclass declaration + /// subclass selector 'system2' + { + "name": "sub#foobar#1", + "option-data": [ + { + "space": "dhcp4", + "name": "mydescr", + "code": 252, + "data": "1.2" + } + ], + /// from: match-if (option dhcp.myversion) = 0x0001 + /// match: option dhcp.mysystem + /// data: 'system2' + "test": "(option[251].hex == 0x0001) and (option[250].hex == 'system2')" + }, + # complex subclass declaration + /// subclass selector 'system3' + { + "name": "sub#foobar#2", + "option-data": [ + { + "space": "dhcp4", + "name": "mydescr", + "code": 252, + "data": "1.3" + } + ], + "next-server": "192.168.0.1", + /// from: match-if (option dhcp.myversion) = 0x0001 + /// match: option dhcp.mysystem + /// data: 'system3' + "test": "(option[251].hex == 0x0001) and (option[250].hex == 'system3')" + } + ] + } +} diff --git a/keama/tests/subclassguard6.in6 b/keama/tests/subclassguard6.in6 new file mode 100644 index 00000000..5c2119a6 --- /dev/null +++ b/keama/tests/subclassguard6.in6 @@ -0,0 +1,24 @@ +# subclass with guard declaration config + +# options +option dhcp6.mysystem code 1250 = text; +option dhcp6.myversion code 1251 = unsigned integer 16; +option dhcp6.mydescr code 1252 = text; + +# superclass declaration +class "foobar" { + match if option dhcp6.myversion = 0:1; + match option dhcp6.mysystem; +} + +# simple subclass declaration +subclass "foobar" "system1"; + +# option setting subclass declaration +subclass "foobar" "system2" { option dhcp6.mydescr "1.2"; } + +# complex subclass declaration +subclass "foobar" "system3" { + option dhcp6.mydescr "1.3"; + option dhcp6.rapid-commit; +} diff --git a/keama/tests/subclassguard6.out b/keama/tests/subclassguard6.out new file mode 100644 index 00000000..26a9d4dc --- /dev/null +++ b/keama/tests/subclassguard6.out @@ -0,0 +1,84 @@ +{ + # subclass with guard declaration config + # options + "Dhcp6": { + "option-def": [ + { + "space": "dhcp6", + "name": "mysystem", + "code": 1250, + "type": "string" + }, + { + "space": "dhcp6", + "name": "myversion", + "code": 1251, + "type": "uint16" + }, + { + "space": "dhcp6", + "name": "mydescr", + "code": 1252, + "type": "string" + } + ], + "client-classes": [ + # superclass declaration + /// match: option dhcp6.mysystem + { + "name": "foobar", + /// from: match if (option dhcp6.myversion) = 0x0001 + "test": "option[1251].hex == 0x0001" + }, + # simple subclass declaration + /// subclass selector 'system1' + { + "name": "sub#foobar#0", + /// from: match-if (option dhcp6.myversion) = 0x0001 + /// match: option dhcp6.mysystem + /// data: 'system1' + "test": "(option[1251].hex == 0x0001) and (option[1250].hex == 'system1')" + }, + # option setting subclass declaration + /// subclass selector 'system2' + { + "name": "sub#foobar#1", + "option-data": [ + { + "space": "dhcp6", + "name": "mydescr", + "code": 1252, + "data": "1.2" + } + ], + /// from: match-if (option dhcp6.myversion) = 0x0001 + /// match: option dhcp6.mysystem + /// data: 'system2' + "test": "(option[1251].hex == 0x0001) and (option[1250].hex == 'system2')" + }, + # complex subclass declaration + /// subclass selector 'system3' + { + "name": "sub#foobar#2", + "option-data": [ + { + "space": "dhcp6", + "name": "mydescr", + "code": 1252, + "data": "1.3" + }, + { + "space": "dhcp6", + "name": "rapid-commit", + "code": 14, + "data": "" + } + ], + /// from: match-if (option dhcp6.myversion) = 0x0001 + /// match: option dhcp6.mysystem + /// data: 'system3' + "test": "(option[1251].hex == 0x0001) and (option[1250].hex == 'system3')" + } + ] + } +} diff --git a/keama/tests/subnet4.in4 b/keama/tests/subnet4.in4 new file mode 100644 index 00000000..9c9247a4 --- /dev/null +++ b/keama/tests/subnet4.in4 @@ -0,0 +1,16 @@ +# DHCPv4 subnet declaration config + +# parameter which will be changed in subnet +default-lease-time 1800; + +# DHCPv4 subnet declaration +subnet 10.5.5.0 netmask 255.255.255.224 { + # at least one pool is required + pool { + range 10.5.5.5 10.5.5.10; + } + option domain-search "example.com", "example.org"; + default-lease-time 3600; + ignore-client-uids false; +} + diff --git a/keama/tests/subnet4.out b/keama/tests/subnet4.out new file mode 100644 index 00000000..2b50c272 --- /dev/null +++ b/keama/tests/subnet4.out @@ -0,0 +1,33 @@ +{ + # DHCPv4 subnet declaration config + # parameter which will be changed in subnet + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "valid-lifetime": 1800, + "subnet4": [ + # DHCPv4 subnet declaration + { + "id": 1, + "subnet": "10.5.5.0/27", + "pools": [ + # at least one pool is required + { + "pool": "10.5.5.5 - 10.5.5.10" + } + ], + "option-data": [ + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "valid-lifetime": 3600, + "match-client-id": true + } + ] + } +} diff --git a/keama/tests/subnet42if.err4 b/keama/tests/subnet42if.err4 new file mode 100644 index 00000000..b65c5d72 --- /dev/null +++ b/keama/tests/subnet42if.err4 @@ -0,0 +1,8 @@ +# bad (2 interfaces) DHCPv4 subnet declaration config + +# DHCPv4 subnet declaration +subnet 10.5.5.0 netmask 255.255.255.224 { + interface "foo"; + interface "bar"; +} + diff --git a/keama/tests/subnet42if.msg b/keama/tests/subnet42if.msg new file mode 100644 index 00000000..22cd15dc --- /dev/null +++ b/keama/tests/subnet42if.msg @@ -0,0 +1 @@ +subnet42if.err4 line 6: A subnet can't be connected to two interfaces. diff --git a/keama/tests/subnet4auth.in4 b/keama/tests/subnet4auth.in4 new file mode 100644 index 00000000..dd7750d7 --- /dev/null +++ b/keama/tests/subnet4auth.in4 @@ -0,0 +1,18 @@ +# DHCPv4 subnet declaration config + +# parameter which will be changed in subnet +default-lease-time 1800; + +# DHCPv4 subnet declaration +subnet 10.5.5.0 netmask 255.255.255.224 { + # at least one pool is required + pool { + range 10.5.5.5 10.5.5.10; + } + # authorize here + authoritative; + option domain-search "example.com", "example.org"; + default-lease-time 3600; + ignore-client-uids false; +} + diff --git a/keama/tests/subnet4auth.out b/keama/tests/subnet4auth.out new file mode 100644 index 00000000..ad605779 --- /dev/null +++ b/keama/tests/subnet4auth.out @@ -0,0 +1,35 @@ +{ + # DHCPv4 subnet declaration config + # parameter which will be changed in subnet + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp4": { + "valid-lifetime": 1800, + "subnet4": [ + # DHCPv4 subnet declaration + { + "id": 1, + "subnet": "10.5.5.0/27", + "pools": [ + # at least one pool is required + { + "pool": "10.5.5.5 - 10.5.5.10" + } + ], + # authorize here + "authoritative": true, + "option-data": [ + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "valid-lifetime": 3600, + "match-client-id": true + } + ] + } +} diff --git a/keama/tests/subnet4badmask.err4 b/keama/tests/subnet4badmask.err4 new file mode 100644 index 00000000..979f5cbb --- /dev/null +++ b/keama/tests/subnet4badmask.err4 @@ -0,0 +1,7 @@ +# bad (bad netmask) DHCPv4 subnet declaration config + +# DHCPv4 subnet declaration +subnet 10.5.5.0 netmask 255.255.255.96 { + default-lease-time 1800; +} + diff --git a/keama/tests/subnet4badmask.msg b/keama/tests/subnet4badmask.msg new file mode 100644 index 00000000..860aec2d --- /dev/null +++ b/keama/tests/subnet4badmask.msg @@ -0,0 +1 @@ +subnet4badmask.err4 line 4: can't get a prefix from 10.5.5.0 mask 255.255.255.96 diff --git a/keama/tests/subnet4inclass.err4 b/keama/tests/subnet4inclass.err4 new file mode 100644 index 00000000..51ede4b0 --- /dev/null +++ b/keama/tests/subnet4inclass.err4 @@ -0,0 +1,10 @@ +# DHCPv4 subnet declaration inside class declaration config + +# class declaration +class "foobar" { + # can't put a DHCPv4 subnet declaration here + subnet 10.5.5.0 netmask 255.255.255.224 { + default-lease-time 1800; + } +} + diff --git a/keama/tests/subnet4inclass.msg b/keama/tests/subnet4inclass.msg new file mode 100644 index 00000000..eabc9e47 --- /dev/null +++ b/keama/tests/subnet4inclass.msg @@ -0,0 +1 @@ +subnet4inclass.err4 line 6: subnet declarations not allowed here. diff --git a/keama/tests/subnet4inhost.err4 b/keama/tests/subnet4inhost.err4 new file mode 100644 index 00000000..266e6dc3 --- /dev/null +++ b/keama/tests/subnet4inhost.err4 @@ -0,0 +1,11 @@ +# DHCPv4 subnet declaration inside host declaration config + +# host declaration +host foobar { + hardware ethernet 00:0B:FD:32:E6:FA; + # can't put a DHCPv4 subnet declaration here + subnet 10.5.5.0 netmask 255.255.255.224 { + default-lease-time 1800; + } +} + diff --git a/keama/tests/subnet4inhost.msg b/keama/tests/subnet4inhost.msg new file mode 100644 index 00000000..49e7d18d --- /dev/null +++ b/keama/tests/subnet4inhost.msg @@ -0,0 +1 @@ +subnet4inhost.err4 line 7: subnet declarations not allowed here. diff --git a/keama/tests/subnet4nomask.err4 b/keama/tests/subnet4nomask.err4 new file mode 100644 index 00000000..42266d03 --- /dev/null +++ b/keama/tests/subnet4nomask.err4 @@ -0,0 +1,7 @@ +# bad (no netmask) DHCPv4 subnet declaration config + +# DHCPv4 subnet declaration +subnet 10.5.5.0/27 { + default-lease-time 1800; +} + diff --git a/keama/tests/subnet4nomask.msg b/keama/tests/subnet4nomask.msg new file mode 100644 index 00000000..2d786c10 --- /dev/null +++ b/keama/tests/subnet4nomask.msg @@ -0,0 +1 @@ +subnet4nomask.err4 line 4: Expecting netmask diff --git a/keama/tests/subnet6.in6 b/keama/tests/subnet6.in6 new file mode 100644 index 00000000..ec834fd3 --- /dev/null +++ b/keama/tests/subnet6.in6 @@ -0,0 +1,18 @@ +# DHCPv6 subnet declaration config + +# parameter which will be changed in subnet +default-lease-time 1800; + +# DHCPv4 subnet declaration +subnet6 2001::/64 { + # at least one pool is required + pool6 { + range6 2001::100 2001::200; + } + option dhcp6.domain-search "example.com", "example.org"; + default-lease-time 3600; + pool6 { + prefix6 2001:0:0:10:: 2001:0:0:1f:: /64; + } +} + diff --git a/keama/tests/subnet6.out b/keama/tests/subnet6.out new file mode 100644 index 00000000..f3535931 --- /dev/null +++ b/keama/tests/subnet6.out @@ -0,0 +1,39 @@ +{ + # DHCPv6 subnet declaration config + # parameter which will be changed in subnet + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp6": { + "valid-lifetime": 1800, + "subnet6": [ + # DHCPv4 subnet declaration + { + "id": 1, + "subnet": "2001::/64", + "pools": [ + # at least one pool is required + { + "pool": "2001::100 - 2001::200" + } + ], + "option-data": [ + { + "space": "dhcp6", + "name": "domain-search", + "code": 24, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "valid-lifetime": 3600, + "pd-pools": [ + { + "prefix": "2001:0:0:10::", + "delegated-len": 64, + "prefix-len": 60 + } + ] + } + ] + } +} diff --git a/keama/tests/subnet62if.err6 b/keama/tests/subnet62if.err6 new file mode 100644 index 00000000..37fdc3f9 --- /dev/null +++ b/keama/tests/subnet62if.err6 @@ -0,0 +1,8 @@ +# bad (2 interfaces) DHCPv6 subnet declaration config + +# DHCPv6 subnet declaration +subnet6 2001::/64 { + interface "foo"; + interface "bar"; +} + diff --git a/keama/tests/subnet62if.msg b/keama/tests/subnet62if.msg new file mode 100644 index 00000000..66f6b900 --- /dev/null +++ b/keama/tests/subnet62if.msg @@ -0,0 +1 @@ +subnet62if.err6 line 6: A subnet can't be connected to two interfaces. diff --git a/keama/tests/subnet6auth.in6 b/keama/tests/subnet6auth.in6 new file mode 100644 index 00000000..a41ff6f4 --- /dev/null +++ b/keama/tests/subnet6auth.in6 @@ -0,0 +1,21 @@ +# DHCPv6 subnet declaration config + +# parameter which will be changed in subnet +default-lease-time 1800; + +# DHCPv4 subnet declaration +subnet6 2001::/64 { + # at least one pool is required + pool6 { + range6 2001::100 2001::200; + } + # authorize here + authoritative; + option dhcp6.domain-search "example.com", "example.org"; + default-lease-time 3600; + interface "en0"; + pool6 { + prefix6 2001:0:0:10:: 2001:0:0:1f:: /64; + } +} + diff --git a/keama/tests/subnet6auth.out b/keama/tests/subnet6auth.out new file mode 100644 index 00000000..aa79d1df --- /dev/null +++ b/keama/tests/subnet6auth.out @@ -0,0 +1,44 @@ +{ + # DHCPv6 subnet declaration config + # parameter which will be changed in subnet + "Dhcp6": { + "valid-lifetime": 1800, + "subnet6": [ + # DHCPv4 subnet declaration + { + "id": 1, + "subnet": "2001::/64", + "pools": [ + # at least one pool is required + { + "pool": "2001::100 - 2001::200" + } + ], + "option-data": [ + # authorize here + { + "space": "dhcp6", + "name": "domain-search", + "code": 24, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "valid-lifetime": 3600, + "interface": "en0", + "pd-pools": [ + { + "prefix": "2001:0:0:10::", + "delegated-len": 64, + "prefix-len": 60 + } + ] + } + ], + "interfaces-config": { + "interfaces": [ + "en0" + ] + } + } +} diff --git a/keama/tests/subnet6inclass.err6 b/keama/tests/subnet6inclass.err6 new file mode 100644 index 00000000..52368cd8 --- /dev/null +++ b/keama/tests/subnet6inclass.err6 @@ -0,0 +1,10 @@ +# DHCPv6 subnet declaration inside class declaration config + +# class declaration +class "foobar" { + # can't put a DHCPv6 subnet declaration here + subnet6 2001::/64 { + default-lease-time 1800; + } +} + diff --git a/keama/tests/subnet6inclass.msg b/keama/tests/subnet6inclass.msg new file mode 100644 index 00000000..e7360029 --- /dev/null +++ b/keama/tests/subnet6inclass.msg @@ -0,0 +1 @@ +subnet6inclass.err6 line 6: subnet declarations not allowed here. diff --git a/keama/tests/subnet6inhost.err6 b/keama/tests/subnet6inhost.err6 new file mode 100644 index 00000000..914decd6 --- /dev/null +++ b/keama/tests/subnet6inhost.err6 @@ -0,0 +1,11 @@ +# DHCPv6 subnet declaration inside host declaration config + +# host declaration +host foobar { + hardware ethernet 00:0B:FD:32:E6:FA; + # can't put a DHCPv6 subnet declaration here + subnet6 2001::/64 { + default-lease-time 1800; + } +} + diff --git a/keama/tests/subnet6inhost.msg b/keama/tests/subnet6inhost.msg new file mode 100644 index 00000000..bd49b89f --- /dev/null +++ b/keama/tests/subnet6inhost.msg @@ -0,0 +1 @@ +subnet6inhost.err6 line 7: subnet declarations not allowed here. diff --git a/keama/tests/subnet6multi.in6 b/keama/tests/subnet6multi.in6 new file mode 100644 index 00000000..a9974ba0 --- /dev/null +++ b/keama/tests/subnet6multi.in6 @@ -0,0 +1,19 @@ +# DHCPv6 subnet declaration config + +# parameter which will be changed in subnet +default-lease-time 1800; + +# DHCPv4 subnet declaration +subnet6 2001::/64 { + # at least one pool is required + pool6 { + # the pool is shared between addresses and prefixes + range6 2001::100 2001::200; + range6 2001::1000 2001::2000; + prefix6 2001:0:0:10:: 2001:0:0:1f:: /64; + prefix6 2001:0:0:80:: 2001:0:0:ff:: /64; + } + option dhcp6.domain-search "example.com", "example.org"; + default-lease-time 3600; +} + diff --git a/keama/tests/subnet6multi.out b/keama/tests/subnet6multi.out new file mode 100644 index 00000000..9a4defb6 --- /dev/null +++ b/keama/tests/subnet6multi.out @@ -0,0 +1,49 @@ +{ + # DHCPv6 subnet declaration config + # parameter which will be changed in subnet + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp6": { + "valid-lifetime": 1800, + "subnet6": [ + # DHCPv4 subnet declaration + { + "id": 1, + "subnet": "2001::/64", + "pd-pools": [ + { + "prefix": "2001:0:0:10::", + "delegated-len": 64, + "prefix-len": 60 + }, + # at least one pool is required + { + "prefix": "2001:0:0:80::", + "delegated-len": 64, + "prefix-len": 57 + } + ], + "pools": [ + { + # the pool is shared between addresses and prefixes + "pool": "2001::100 - 2001::200" + }, + # at least one pool is required + { + "pool": "2001::1000 - 2001::2000" + } + ], + "option-data": [ + { + "space": "dhcp6", + "name": "domain-search", + "code": 24, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "valid-lifetime": 3600 + } + ] + } +} diff --git a/keama/tests/subnet6nolen.err6 b/keama/tests/subnet6nolen.err6 new file mode 100644 index 00000000..1a71ac79 --- /dev/null +++ b/keama/tests/subnet6nolen.err6 @@ -0,0 +1,7 @@ +# bad (no length) DHCPv6 subnet declaration config + +# DHCPv6 subnet declaration +subnet6 2001::/ { + default-lease-time 1800; +} + diff --git a/keama/tests/subnet6nolen.msg b/keama/tests/subnet6nolen.msg new file mode 100644 index 00000000..cc36f0e1 --- /dev/null +++ b/keama/tests/subnet6nolen.msg @@ -0,0 +1 @@ +subnet6nolen.err6 line 4: Expecting a number. diff --git a/keama/tests/subnet6noslash.err6 b/keama/tests/subnet6noslash.err6 new file mode 100644 index 00000000..2083eab6 --- /dev/null +++ b/keama/tests/subnet6noslash.err6 @@ -0,0 +1,7 @@ +# bad (no /) DHCPv6 subnet declaration config + +# DHCPv6 subnet declaration +subnet6 2001:: { + default-lease-time 1800; +} + diff --git a/keama/tests/subnet6noslash.msg b/keama/tests/subnet6noslash.msg new file mode 100644 index 00000000..63fa81c6 --- /dev/null +++ b/keama/tests/subnet6noslash.msg @@ -0,0 +1 @@ +subnet6noslash.err6 line 4: Expecting a '/'. diff --git a/keama/tests/subnet6one.in6 b/keama/tests/subnet6one.in6 new file mode 100644 index 00000000..7c0c3a0b --- /dev/null +++ b/keama/tests/subnet6one.in6 @@ -0,0 +1,18 @@ +# DHCPv6 subnet declaration config + +# parameter which will be changed in subnet +default-lease-time 1800; + +# DHCPv4 subnet declaration +subnet6 2001::/64 { + # at least one pool is required + pool6 { + # the pool is shared between addresses and prefixes + range6 2001::100 2001::200; + prefix6 2001:0:0:10:: 2001:0:0:1f:: /64; + } + option dhcp6.domain-search "example.com", "example.org"; + default-lease-time 3600; + interface "en0"; +} + diff --git a/keama/tests/subnet6one.out b/keama/tests/subnet6one.out new file mode 100644 index 00000000..e1388c81 --- /dev/null +++ b/keama/tests/subnet6one.out @@ -0,0 +1,45 @@ +{ + # DHCPv6 subnet declaration config + # parameter which will be changed in subnet + "Dhcp6": { + "valid-lifetime": 1800, + "subnet6": [ + # DHCPv4 subnet declaration + { + "id": 1, + "subnet": "2001::/64", + "pd-pools": [ + # at least one pool is required + { + "prefix": "2001:0:0:10::", + "delegated-len": 64, + "prefix-len": 60 + } + ], + "pools": [ + # at least one pool is required + { + # the pool is shared between addresses and prefixes + "pool": "2001::100 - 2001::200" + } + ], + "option-data": [ + { + "space": "dhcp6", + "name": "domain-search", + "code": 24, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "valid-lifetime": 3600, + "interface": "en0" + } + ], + "interfaces-config": { + "interfaces": [ + "en0" + ] + } + } +} diff --git a/keama/tests/subnetinsubnet4.err4 b/keama/tests/subnetinsubnet4.err4 new file mode 100644 index 00000000..c326c16d --- /dev/null +++ b/keama/tests/subnetinsubnet4.err4 @@ -0,0 +1,10 @@ +# DHCPv4 subnet declaration inside another subnet declaration config + +# DHCPv4 subnet declaration +subnet 10.254.239.32 netmask 255.255.255.224 { + # can't put another subnet declaration here + subnet 10.5.5.0 netmask 255.255.255.224 { + default-lease-time 1800; + } +} + diff --git a/keama/tests/subnetinsubnet4.msg b/keama/tests/subnetinsubnet4.msg new file mode 100644 index 00000000..710cffa7 --- /dev/null +++ b/keama/tests/subnetinsubnet4.msg @@ -0,0 +1 @@ +subnetinsubnet4.err4 line 6: subnet declarations not allowed here. diff --git a/keama/tests/subnetinsubnet6.err6 b/keama/tests/subnetinsubnet6.err6 new file mode 100644 index 00000000..ac9a6097 --- /dev/null +++ b/keama/tests/subnetinsubnet6.err6 @@ -0,0 +1,10 @@ +# DHCPv6 subnet declaration inside another subnet declaration config + +# DHCPv6 subnet declaration +subnet6 2001:2::/64 { + # can't put another subnet declaration here + subnet6 2001::/64 { + default-lease-time 1800; + } +} + diff --git a/keama/tests/subnetinsubnet6.msg b/keama/tests/subnetinsubnet6.msg new file mode 100644 index 00000000..27f8f3fa --- /dev/null +++ b/keama/tests/subnetinsubnet6.msg @@ -0,0 +1 @@ +subnetinsubnet6.err6 line 6: subnet declarations not allowed here. diff --git a/keama/tests/substringdx4.in4 b/keama/tests/substringdx4.in4 new file mode 100644 index 00000000..ed0c9d35 --- /dev/null +++ b/keama/tests/substringdx4.in4 @@ -0,0 +1,21 @@ +# substring data expression + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# use substring in a reductible match +class "reductible" { + match substring(option host-name, 0, 3); +} + +subclass "reductible" "www" { } + +# reduce literals too +class "literal" { + match if option host-name = substring("www.example.com", 0, 3); +} + +# raw +option domain-name = substring(option domain-name, 4, 1000); + + diff --git a/keama/tests/substringdx4.out b/keama/tests/substringdx4.out new file mode 100644 index 00000000..75865424 --- /dev/null +++ b/keama/tests/substringdx4.out @@ -0,0 +1,49 @@ +{ + # substring data expression + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800, + "client-classes": [ + # use substring in a reductible match + /// match: substring(option dhcp.host-name, 0, 3) + { + "name": "reductible" + }, + /// subclass selector 'www' + { + "name": "sub#reductible#0", + /// from: match substring(option dhcp.host-name, 0, 3) + /// data: 'www' + "test": "substring(option[12].hex,0,3) == 'www'" + }, + # reduce literals too + { + "name": "literal", + /// from: match if (option dhcp.host-name) = (substring('www.example.com', 0, 3)) + "test": "option[12].hex == 'www'" + } + ], + "option-data": [ +// # raw +// { +// "space": "dhcp4", +// "name": "domain-name", +// "code": 15, +// "csv-format": false, +// "expression": { +// "substring": { +// "expression": { +// "option": { +// "universe": "dhcp", +// "name": "domain-name", +// "code": 15 +// } +// }, +// "offset": 4, +// "length": 1000 +// } +// } +// } + ] + } +} diff --git a/keama/tests/suffixdx4.in4 b/keama/tests/suffixdx4.in4 new file mode 100644 index 00000000..fc692d10 --- /dev/null +++ b/keama/tests/suffixdx4.in4 @@ -0,0 +1,26 @@ +# suffix data expression +# in fact ISC DHCP suffix can be reduced into Kea substring + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# use suffix in a reductible match +class "reductible" { + match suffix(option host-name, 3); +} + +subclass "reductible" "com" { + option domain-search "example.com"; +} + +subclass "reductible" "org" { + option domain-search "example.org"; +} + +# reduce literals too +class "literal" { + match if option domain-name = suffix("www.example.com", 3); +} + +# raw +option domain-name = suffix(option domain-name, 3); diff --git a/keama/tests/suffixdx4.out b/keama/tests/suffixdx4.out new file mode 100644 index 00000000..12f045d6 --- /dev/null +++ b/keama/tests/suffixdx4.out @@ -0,0 +1,74 @@ +{ + # suffix data expression + # in fact ISC DHCP suffix can be reduced into Kea substring + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800, + "client-classes": [ + # use suffix in a reductible match + /// match: suffix(option dhcp.host-name, 3) + { + "name": "reductible" + }, + /// subclass selector 'com' + { + "name": "sub#reductible#0", + "option-data": [ + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.com\"", + "data": "example.com" + } + ], + /// from: match suffix(option dhcp.host-name, 3) + /// data: 'com' + "test": "substring(option[12].hex,-3,all) == 'com'" + }, + /// subclass selector 'org' + { + "name": "sub#reductible#1", + "option-data": [ + { + "space": "dhcp4", + "name": "domain-search", + "code": 119, +// "original-data": "\"example.org\"", + "data": "example.org" + } + ], + /// from: match suffix(option dhcp.host-name, 3) + /// data: 'org' + "test": "substring(option[12].hex,-3,all) == 'org'" + }, + # reduce literals too + { + "name": "literal", + /// from: match if (option dhcp.domain-name) = (suffix('www.example.com', 3)) + "test": "option[15].hex == '.example.com'" + } + ], + "option-data": [ +// # raw +// { +// "space": "dhcp4", +// "name": "domain-name", +// "code": 15, +// "csv-format": false, +// "expression": { +// "suffix": { +// "expression": { +// "option": { +// "universe": "dhcp", +// "name": "domain-name", +// "code": 15 +// } +// }, +// "length": 3 +// } +// } +// } + ] + } +} diff --git a/keama/tests/switchxsc4.in4 b/keama/tests/switchxsc4.in4 new file mode 100644 index 00000000..7ef6f2f3 --- /dev/null +++ b/keama/tests/switchxsc4.in4 @@ -0,0 +1,18 @@ +# switch executable statement construct + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# a switch +switch (option user-class) { +case "accounting": + add "acct"; + send ip-forwarding false; + default-lease-time 3600; + break; +case "engineering": + allow booting; + log (debug, option host-name); + set foo = "bar"; + break; +} diff --git a/keama/tests/switchxsc4.out b/keama/tests/switchxsc4.out new file mode 100644 index 00000000..e45421d3 --- /dev/null +++ b/keama/tests/switchxsc4.out @@ -0,0 +1,79 @@ +{ + # switch executable statement construct + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800 +// # a switch +// "statement": { +// "switch": { +// "condition": { +// "option": { +// "universe": "dhcp", +// "name": "user-class", +// "code": 77 +// } +// }, +// "body": [ +// { +// "case": "accounting" +// }, +// { +// "add-class": "acct" +// }, +// { +// /// Kea does not support option data set variants (send) +// "option": { +// "space": "dhcp4", +// "name": "ip-forwarding", +// "code": 19, +// "data": "false" +// } +// }, +// { +// "config": { +// "name": "default-lease-time", +// "code": 1, +// "value": 3600 +// } +// }, +// { +// "break": null +// }, +// { +// "case": "engineering" +// }, +// { +// "config": { +// "value": "allow", +// "name": "allow-booting", +// "code": 9 +// } +// }, +// { +// /// Kea does not support yet log statements +// /// Reference Kea #234 +// "log": { +// "priority": "debug", +// "message": { +// "option": { +// "universe": "dhcp", +// "name": "host-name", +// "code": 12 +// } +// } +// } +// }, +// { +// "set": { +// "name": "foo", +// "value": "bar" +// } +// }, +// { +// "break": null +// } +// ] +// } +// } + } +} diff --git a/keama/tests/switchxsc6.in6 b/keama/tests/switchxsc6.in6 new file mode 100644 index 00000000..a9c74f86 --- /dev/null +++ b/keama/tests/switchxsc6.in6 @@ -0,0 +1,18 @@ +# switch executable statement construct + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# a switch +switch (option dhcp6.remote-id) { +case "accounting": + default dhcp6.bootfile-url foobar; + default-lease-time 3600; + unset foo; + break; +case "engineering": + deny declines; + log (debug, "hello"); + define foo (x) { return "world"; } + break; +} diff --git a/keama/tests/switchxsc6.out b/keama/tests/switchxsc6.out new file mode 100644 index 00000000..b7fbd06c --- /dev/null +++ b/keama/tests/switchxsc6.out @@ -0,0 +1,82 @@ +{ + # switch executable statement construct + # empty configs are not accepted by Kea + "Dhcp6": { + "valid-lifetime": 1800 +// # a switch +// "statement": { +// "switch": { +// "condition": { +// "option": { +// "universe": "dhcp6", +// "name": "remote-id", +// "code": 37 +// } +// }, +// "body": [ +// { +// "case": "accounting" +// }, +// { +// /// Kea does not support option data set variants (default) +// "option": { +// "space": "dhcp6", +// "name": "bootfile-url", +// "code": 59, +// "data": "foobar" +// } +// }, +// { +// "config": { +// "name": "default-lease-time", +// "code": 1, +// "value": 3600 +// } +// }, +// { +// "unset": { +// "name": "foo" +// } +// }, +// { +// "break": null +// }, +// { +// "case": "engineering" +// }, +// { +// "config": { +// "value": "deny", +// "name": "declines", +// "code": 29 +// } +// }, +// { +// /// Kea does not support yet log statements +// /// Reference Kea #234 +// "log": { +// "priority": "debug", +// "message": "hello" +// } +// }, +// { +// "define": { +// "name": "foo", +// "function": { +// "arguments": "x", +// "body": [ +// { +// "return": "world" +// } +// ] +// } +// } +// }, +// { +// "break": null +// } +// ] +// } +// } + } +} diff --git a/keama/tests/tautology.err b/keama/tests/tautology.err new file mode 100644 index 00000000..e1f506dd --- /dev/null +++ b/keama/tests/tautology.err @@ -0,0 +1,9 @@ +# bad (tautology) class declaration config + +# class declaration +class "tautology" { + # tautology + # note that true does not work as it is a variable reference + # and for the same reason quotes are needed (or one can use hexa) + match if "foo" = "foo"; +} diff --git a/keama/tests/tautology.msg b/keama/tests/tautology.msg new file mode 100644 index 00000000..80bf2feb --- /dev/null +++ b/keama/tests/tautology.msg @@ -0,0 +1 @@ +tautology.err line 8: 'match if' with a constant boolean expression 'foo' = 'foo' diff --git a/keama/tests/tautologyhexa.err b/keama/tests/tautologyhexa.err new file mode 100644 index 00000000..69249d86 --- /dev/null +++ b/keama/tests/tautologyhexa.err @@ -0,0 +1,9 @@ +# bad (tautology) class declaration config + +# class declaration +class "tautology" { + # tautology + # note that true does not work as it is a variable reference + # and for the same reason quotes are needed (or one can use hexa) + match if 12:34 = 56:78:9a; +} diff --git a/keama/tests/tautologyhexa.msg b/keama/tests/tautologyhexa.msg new file mode 100644 index 00000000..7482482b --- /dev/null +++ b/keama/tests/tautologyhexa.msg @@ -0,0 +1 @@ +tautologyhexa.err line 8: 'match if' with a constant boolean expression 0x1234 = 0x56789a diff --git a/keama/tests/tautologysub.err b/keama/tests/tautologysub.err new file mode 100644 index 00000000..08de2575 --- /dev/null +++ b/keama/tests/tautologysub.err @@ -0,0 +1,9 @@ +# bad (tautology) class declaration config + +# superclass declaration +class "constant" { + match "foo"; +} + +# subclass declaration +subclass "constant" "bar"; diff --git a/keama/tests/tautologysub.msg b/keama/tests/tautologysub.msg new file mode 100644 index 00000000..d30f39a7 --- /dev/null +++ b/keama/tests/tautologysub.msg @@ -0,0 +1 @@ +tautologysub.err line 9: class matching rule evaluated to a constant boolean expression: 'foo' = 'bar' diff --git a/keama/tests/temporary6.in6 b/keama/tests/temporary6.in6 new file mode 100644 index 00000000..dec7d99b --- /dev/null +++ b/keama/tests/temporary6.in6 @@ -0,0 +1,10 @@ +# DHCPv6 temporary (aka IA_TA) range config + +# subnet declaration +subnet6 2001::/64 { + # range declaration + option dhcp6.domain-search "example.com", "example.org"; + default-lease-time 1800; + range6 2001::100 temporary; + range6 2001::1000/116 temporary; +} diff --git a/keama/tests/temporary6.out b/keama/tests/temporary6.out new file mode 100644 index 00000000..e7af7bba --- /dev/null +++ b/keama/tests/temporary6.out @@ -0,0 +1,33 @@ +{ + # DHCPv6 temporary (aka IA_TA) range config + # subnet declaration + /// This configuration declares some subnets but has no interfaces-config + /// Reference Kea #245 + "Dhcp6": { + "subnet6": [ + { + "id": 1, + "subnet": "2001::/64", + "option-data": [ + # range declaration + { + "space": "dhcp6", + "name": "domain-search", + "code": 24, +// "original-data": "\"example.com\", \"example.org\"", + "data": "example.com, example.org" + } + ], + "valid-lifetime": 1800, + "pools": [ +// { +// "pool": "2001::100/64 temporary" +// } +// { +// "pool": "2001::1000/116 temporary" +// } + ] + } + ] + } +} diff --git a/keama/tests/textarray.err b/keama/tests/textarray.err new file mode 100644 index 00000000..7cecd7a0 --- /dev/null +++ b/keama/tests/textarray.err @@ -0,0 +1,7 @@ +# option definition config + +# options +option space foobar; + +# array of text are forbidden +option foobarstring-array code 1 = array of text; diff --git a/keama/tests/textarray.msg b/keama/tests/textarray.msg new file mode 100644 index 00000000..f1115cbe --- /dev/null +++ b/keama/tests/textarray.msg @@ -0,0 +1 @@ +textarray.err line 7: arrays of text strings not yet supported. diff --git a/keama/tests/unknownoption.err b/keama/tests/unknownoption.err new file mode 100644 index 00000000..ce4aacbb --- /dev/null +++ b/keama/tests/unknownoption.err @@ -0,0 +1,4 @@ +# unknown option config + +# unknown option +option this-option-does-not-exist off; diff --git a/keama/tests/unknownoption.msg b/keama/tests/unknownoption.msg new file mode 100644 index 00000000..0fb2f1e9 --- /dev/null +++ b/keama/tests/unknownoption.msg @@ -0,0 +1 @@ +unknownoption.err line 4: unknown option dhcp.this-option-does-not-exist diff --git a/keama/tests/unknownspace.err b/keama/tests/unknownspace.err new file mode 100644 index 00000000..8165a972 --- /dev/null +++ b/keama/tests/unknownspace.err @@ -0,0 +1,4 @@ +# unknown option config + +# unknown option +option this-space-does-not-exist.domain-search "example.com"; diff --git a/keama/tests/unknownspace.msg b/keama/tests/unknownspace.msg new file mode 100644 index 00000000..f6e378af --- /dev/null +++ b/keama/tests/unknownspace.msg @@ -0,0 +1 @@ +unknownspace.err line 4: no option space named this-space-does-not-exist. diff --git a/keama/tests/userclass.err b/keama/tests/userclass.err new file mode 100644 index 00000000..e8a3d27c --- /dev/null +++ b/keama/tests/userclass.err @@ -0,0 +1,6 @@ +# user-class declaration config + +# user-class declaration +user-class "foobar" { +} + diff --git a/keama/tests/userclass.msg b/keama/tests/userclass.msg new file mode 100644 index 00000000..2208500e --- /dev/null +++ b/keama/tests/userclass.msg @@ -0,0 +1 @@ +userclass.err line 4: obsolete 'user-class' declaration diff --git a/keama/tests/vendorclass.err b/keama/tests/vendorclass.err new file mode 100644 index 00000000..81d1b4e3 --- /dev/null +++ b/keama/tests/vendorclass.err @@ -0,0 +1,6 @@ +# vendor-class declaration config + +# vendor-class declaration +vendor-class "foobar" { +} + diff --git a/keama/tests/vendorclass.msg b/keama/tests/vendorclass.msg new file mode 100644 index 00000000..fc45fe66 --- /dev/null +++ b/keama/tests/vendorclass.msg @@ -0,0 +1 @@ +vendorclass.err line 4: obsolete 'vendor-class' declaration diff --git a/keama/tests/vendorspace4.in4 b/keama/tests/vendorspace4.in4 new file mode 100644 index 00000000..98f9eada --- /dev/null +++ b/keama/tests/vendorspace4.in4 @@ -0,0 +1,11 @@ +# vendor option space config + +option space foo; +option foo.bar code 1 = text; + +# class declaration +class "foobar" { + match if option vendor-class-identifier = "foo"; + vendor-option-space foo; + option foo.bar "foobar"; +} diff --git a/keama/tests/vendorspace4.out b/keama/tests/vendorspace4.out new file mode 100644 index 00000000..09feae6f --- /dev/null +++ b/keama/tests/vendorspace4.out @@ -0,0 +1,41 @@ +{ + # vendor option space config + "Dhcp4": { + "option-def": [ + { + "space": "foo", + "name": "bar", + "code": 1, + "type": "string" + } + ], + "client-classes": [ + # class declaration + { + "name": "foobar", + /// from: match if (option dhcp.vendor-class-identifier) = 'foo' + "test": "option[60].hex == 'foo'", + "option-def": [ + { + "name": "vendor-encapsulated-options", + "code": 43, + "type": "empty", + "encapsulate": "foo" + } + ], + "option-data": [ + { + "name": "vendor-encapsulated-options", + "code": 43 + }, + { + "space": "foo", + "name": "bar", + "code": 1, + "data": "foobar" + } + ] + } + ] + } +} diff --git a/keama/tests/zone4.in4 b/keama/tests/zone4.in4 new file mode 100644 index 00000000..654e6c8e --- /dev/null +++ b/keama/tests/zone4.in4 @@ -0,0 +1,24 @@ +# zone executable statement construct + +# empty configs are not accepted by Kea +default-lease-time 1800; + +# a zone +zone example.com { + primary 10.5.5.1, 10.5.5.2; + secondary 10.10.10.1; + primary6 2001::1, 2001::2; + secondary6 2002::1; + key "mykey"; +} + +# a key; +key "mykey" { + algorithm hmac-md5; + secret "somekeydata"; +} + +# another key (with bind 8 semi-colon) +key example.com { + algorithm aes-gmac.dreams; +}; diff --git a/keama/tests/zone4.out b/keama/tests/zone4.out new file mode 100644 index 00000000..7565db4c --- /dev/null +++ b/keama/tests/zone4.out @@ -0,0 +1,47 @@ +{ + # zone executable statement construct + # empty configs are not accepted by Kea + "Dhcp4": { + "valid-lifetime": 1800 +// # a zone +// "statement": { +// "zone": { +// "name": "example.com.", +// "primary": [ +// "10.5.5.1", +// "10.5.5.2" +// ], +// "secondary": [ +// "10.10.10.1" +// ], +// "primary6": [ +// "2001::1", +// "2001::2" +// ], +// "secondary6": [ +// "2002::1" +// ], +// "key": "mykey" +// } +// } +// # a key; +// "statement": { +// "tsig-keys": [ +// { +// "name": "mykey", +// "algorithm": "hmac-md5.SIG-ALG.REG.INT.", +// "secret": "somekeydata" +// } +// ] +// } +// # another key (with bind 8 semi-colon) +// "statement": { +// "tsig-keys": [ +// { +// "name": "example.com", +// "algorithm": "aes-gmac.dreams." +// } +// ] +// } + } +} |