Blame SOURCES/0007-Dhcpd-revert-Dhcpd-module-to-1.1.0-compatible-add-Dh.patch

3b98a7
From c93dc5ca4c87f38772c98e3134ddc6662a98bc02 Mon Sep 17 00:00:00 2001
3b98a7
From: Dominic Cleal <dcleal@redhat.com>
3b98a7
Date: Fri, 12 Jun 2015 11:09:09 +0100
3b98a7
Subject: [PATCH] Dhcpd: revert Dhcpd module to 1.1.0-compatible, add Dhcpd_140
3b98a7
3b98a7
In order to keep the default sshd config lens compatible with 1.1.0,
3b98a7
the lens from 1.4.0 has been kept in the Dhcpd_140 module and is not
3b98a7
loaded by default.  Use aug_transform, augtool --transform etc. to use
3b98a7
it instead of Dhcpd.
3b98a7
---
3b98a7
 lenses/dhcpd.aug                | 182 ++--------
3b98a7
 lenses/dhcpd_140.aug            | 512 +++++++++++++++++++++++++++
3b98a7
 lenses/tests/test_dhcpd.aug     | 214 +----------
3b98a7
 lenses/tests/test_dhcpd_140.aug | 606 ++++++++++++++++++++++++++++++++
3b98a7
 tests/Makefile.am               |   1 +
3b98a7
 5 files changed, 1152 insertions(+), 363 deletions(-)
3b98a7
 create mode 100644 lenses/dhcpd_140.aug
3b98a7
 create mode 100644 lenses/tests/test_dhcpd_140.aug
3b98a7
3b98a7
diff --git a/lenses/dhcpd.aug b/lenses/dhcpd.aug
3b98a7
index f84a409c..21a5ce60 100644
3b98a7
--- a/lenses/dhcpd.aug
3b98a7
+++ b/lenses/dhcpd.aug
3b98a7
@@ -65,12 +65,12 @@ let eos               = comment?
3b98a7
 let sep_spc           = del /[ \t]+/ " "
3b98a7
 let sep_osp           = del /[ \t]*/ ""
3b98a7
 let sep_scl           = del /[ \t]*;([ \t]*\n)*/ ";\n"
3b98a7
-let sep_obr           = del /[ \t\n]*\{([ \t]*\n)*/ " {\n"
3b98a7
+let sep_obr           = del /[ \t]*\{([ \t]*\n)*/ " {\n"
3b98a7
 let sep_cbr           = del /[ \t]*\}([ \t]*\n)*/ "}\n"
3b98a7
 let sep_com           = del /[ \t\n]*,[ \t\n]*/ ", "
3b98a7
 let sep_slh           = del "\/" "/"
3b98a7
 let sep_col           = del ":" ":"
3b98a7
-let sep_eq            = del /[ \t\n]*=[ \t\n]*/ "="
3b98a7
+let sep_eq            = del /[ \t]*=[ \t]*/ "="
3b98a7
 let scl               = del ";" ";"
3b98a7
 
3b98a7
 (* Define basic types *)
3b98a7
@@ -94,11 +94,6 @@ let ip                = Rx.ipv4
3b98a7
   let bare = del qchar? "" . store (bchar+) . del qchar? ""
3b98a7
   let quote = Quote.do_quote (store (bchar* . /[ \t'\/]/ . bchar*)+)
3b98a7
   let dquote = Quote.do_dquote (store (bchar+))
3b98a7
-  (* these two are for special cases.  bare_to_scl is for any bareword that is
3b98a7
-   * space or semicolon terminated.  dquote_any allows almost any character in
3b98a7
-   * between the quotes. *)
3b98a7
-  let bare_to_scl = Quote.do_dquote_opt (store /[^" \t\n;]+/)
3b98a7
-  let dquote_any = Quote.do_dquote (store /[^"\n]*[ \t]+[^"\n]*/)
3b98a7
 
3b98a7
 let sto_to_spc        = store /[^\\#,;\{\}" \t\n]+|"[^\\#"\n]+"/
3b98a7
 let sto_to_scl        = store /[^ \t;][^;\n=]+[^ \t;]|[^ \t;=]+/
3b98a7
@@ -197,7 +192,6 @@ let stmt_string_re    = "ddns-update-style"
3b98a7
                       | "use-lease-addr-for-default-route"
3b98a7
                       | "vendor-option-space"
3b98a7
                       | "primary"
3b98a7
-                      | "omapi-key"
3b98a7
 
3b98a7
 let stmt_string_tpl (kw:regexp) (l:lens) = [ indent
3b98a7
                         . key kw
3b98a7
@@ -230,51 +224,26 @@ let stmt_range        = [ indent
3b98a7
 let stmt_hardware     = [ indent
3b98a7
                         . key "hardware"
3b98a7
                         . sep_spc
3b98a7
-                        . [ label "type" . store /ethernet|tokenring|fddi/ ]
3b98a7
+                        . [ label "type" . store /ethernet|tokenring/ ]
3b98a7
                         . sep_spc
3b98a7
                         . [ label "address" . store /[a-fA-F0-9:-]+/ ]
3b98a7
                         . sep_scl
3b98a7
                         . eos ]
3b98a7
 
3b98a7
-(************************************************************************
3b98a7
- *                         SET STATEMENTS
3b98a7
- *************************************************************************)
3b98a7
-let stmt_set          = [ indent
3b98a7
-                        . key "set"
3b98a7
-                        . sep_spc
3b98a7
-                        . store word
3b98a7
-                        . sep_spc
3b98a7
-                        . Sep.equal
3b98a7
-                        . sep_spc
3b98a7
-                        . [ label "value" . sto_to_scl ]
3b98a7
-                        . sep_scl
3b98a7
-                        . eos ]
3b98a7
-
3b98a7
 (************************************************************************
3b98a7
  *                         OPTION STATEMENTS
3b98a7
  *************************************************************************)
3b98a7
 (* The general case is considering options as a list *)
3b98a7
 
3b98a7
+let stmt_option_code  = [ label "label" . store word . sep_spc ]
3b98a7
+                        . [ key "code" . sep_spc . store word ]
3b98a7
+                        . sep_eq
3b98a7
+                        . [ label "type" . store word ]
3b98a7
 
3b98a7
-let stmt_option_value = /((array of[ \t]+)?(((un)?signed[ \t]+)?integer (8|16|32)|string|ip6?-address|boolean|domain-list|text)|encapsulate [A-Za-z0-9_.-]+)/
3b98a7
 
3b98a7
 let stmt_option_list  = ([ label "arg" . bare ] | [ label "arg" . quote ])
3b98a7
                         . ( sep_com . ([ label "arg" . bare ] | [ label "arg" . quote ]))*
3b98a7
 
3b98a7
-let del_trail_spc = del /[ \t\n]*/ ""
3b98a7
-
3b98a7
-let stmt_record = counter "record" . Util.del_str "{"
3b98a7
-                . sep_spc
3b98a7
-                . ([seq "record" . store stmt_option_value . sep_com]*
3b98a7
-                .  [seq "record" . store stmt_option_value . del_trail_spc])?
3b98a7
-                . Util.del_str "}"
3b98a7
-
3b98a7
-let stmt_option_code  = [ label "label" . store word . sep_spc ]
3b98a7
-                        . [ key "code" . sep_spc . store word ]
3b98a7
-                        . sep_eq
3b98a7
-                        . ([ label "type" . store stmt_option_value ]
3b98a7
-                          |[ label "record" . stmt_record ]) 
3b98a7
-
3b98a7
 let stmt_option_basic = [ key word . sep_spc . stmt_option_list ]
3b98a7
 let stmt_option_extra = [ key word . sep_spc . store /true|false/ . sep_spc . stmt_option_list ]
3b98a7
 
3b98a7
@@ -302,13 +271,10 @@ let stmt_option = stmt_option1 | stmt_option2
3b98a7
 (* this statement is not well documented in the manual dhcpd.conf
3b98a7
    we support basic use case *)
3b98a7
 
3b98a7
-let stmt_subclass = [ indent . key "subclass" . sep_spc 
3b98a7
-                      . ( [ label "name" .  bare_to_scl ]|[ label "name" .  dquote_any ] )
3b98a7
-                      . sep_spc 
3b98a7
-                      . ( [ label "value" . bare_to_scl ]|[ label "value" . dquote_any ] ) 
3b98a7
-                      . sep_scl 
3b98a7
-                      . eos ]
3b98a7
-
3b98a7
+let stmt_subclass = [ indent . key "subclass" . sep_spc .
3b98a7
+                      ([ label "name" . quote ]|
3b98a7
+                       [ label "name" . bare ]) . sep_spc .
3b98a7
+                       [ label "value" . bare ] . sep_scl . eos ]
3b98a7
 
3b98a7
 (************************************************************************
3b98a7
  *                         ALLOW/DENY STATEMENTS
3b98a7
@@ -316,18 +282,10 @@ let stmt_subclass = [ indent . key "subclass" . sep_spc
3b98a7
 (* We have to use special key for allow/deny members of
3b98a7
   to avoid ambiguity in the put direction *)
3b98a7
 
3b98a7
-let allow_deny_re     = /unknown(-|[ ]+)clients/
3b98a7
-                      | /known(-|[ ]+)clients/
3b98a7
-                      | /all[ ]+clients/
3b98a7
+let allow_deny_re     = "unknown-clients"
3b98a7
                       | /dynamic[ ]+bootp[ ]+clients/
3b98a7
                       | /authenticated[ ]+clients/
3b98a7
                       | /unauthenticated[ ]+clients/
3b98a7
-                      | "bootp"
3b98a7
-                      | "booting"
3b98a7
-                      | "duplicates"
3b98a7
-                      | "declines"
3b98a7
-                      | "client-updates"
3b98a7
-                      | "leasequery"
3b98a7
 
3b98a7
 let stmt_secu_re      = "allow"
3b98a7
                       | "deny"
3b98a7
@@ -335,17 +293,9 @@ let stmt_secu_re      = "allow"
3b98a7
 let del_allow = del /allow[ ]+members[ ]+of/ "allow members of"
3b98a7
 let del_deny  = del /deny[ \t]+members[ \t]+of/ "deny members of"
3b98a7
 
3b98a7
-(* bare is anything but whitespace, quote marks or semicolon.
3b98a7
- * technically this should be locked down to mostly alphanumerics, but the
3b98a7
- * idea right now is just to make things work.  Also ideally I would use
3b98a7
- * dquote_space but I had a whale of a time with it.  It doesn't like
3b98a7
- * semicolon termination and my attempts to fix that led me to 3 hours of
3b98a7
- * frustration and back to this :)
3b98a7
- *)
3b98a7
 let stmt_secu_tpl (l:lens) (s:string) =
3b98a7
-                  [ indent . l . sep_spc . label s . bare_to_scl . sep_scl . eos ] |
3b98a7
-                  [ indent . l . sep_spc . label s . dquote_any . sep_scl . eos ]
3b98a7
-
3b98a7
+                  [ indent . l . sep_spc . label s . bare . sep_scl . eos ] |
3b98a7
+                  [ indent . l . sep_spc . label s . quote . sep_scl . eos ]
3b98a7
 
3b98a7
 let stmt_secu         = [ indent . key stmt_secu_re . sep_spc .
3b98a7
                           store allow_deny_re . sep_scl . eos ] |
3b98a7
@@ -356,62 +306,17 @@ let stmt_secu         = [ indent . key stmt_secu_re . sep_spc .
3b98a7
  *                         MATCH STATEMENTS
3b98a7
  *************************************************************************)
3b98a7
 
3b98a7
+let sto_fct = store (word . /[ \t]*\([^)]*\)/)
3b98a7
+let sto_option = store (/option[ ]+/ . word)
3b98a7
 let sto_com = /[^ \t\n,\(\)][^,\(\)]*[^ \t\n,\(\)]|[^ \t\n,\(\)]+/ | word . /[ \t]*\([^)]*\)/
3b98a7
-(* this is already the most complicated part of this module and it's about to
3b98a7
- * get worse.  match statements can be way more complicated than this
3b98a7
- *
3b98a7
- * examples:
3b98a7
- *      using or:
3b98a7
- *      match if ((option vendor-class-identifier="Banana Bready") or (option vendor-class-identifier="Cherry Sunfire"));
3b98a7
- *      unneeded parenthesis:
3b98a7
- *      match if (option vendor-class-identifier="Hello");
3b98a7
- *
3b98a7
- *      and of course the fact that the above two rules used one of infinately
3b98a7
- *      many potential options instead of a builtin function.
3b98a7
- *)
3b98a7
-(* sto_com doesn't support quoted strings as arguments.  It also doesn't
3b98a7
-   support single arguments (needs to match a comma) It will need to be
3b98a7
-   updated for lcase, ucase and log to be workable.
3b98a7
-
3b98a7
-   it also doesn't support no arguments, so gethostbyname() doesn't work.
3b98a7
-
3b98a7
-   option and config-option are considered operators.  They should be matched
3b98a7
-   in stmt_entry but also available under "match if" and "if" conditionals
3b98a7
-   leased-address, host-decl-name, both take no args and return a value.  We
3b98a7
-   might need to treat them as variable names in the parser.
3b98a7
-
3b98a7
-   things like this may be near-impossible to parse even with recursion
3b98a7
-   because we have no way of knowing when or if a subfunction takes arguments
3b98a7
-   set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
3b98a7
-
3b98a7
-   even if we could parse it, they could get arbitrarily complicated like:
3b98a7
-   binary-to-ascii(16, 8, ":", substring(hardware, 1, 6) and substring(hardware, 2, 3));
3b98a7
-
3b98a7
-   so at some point we may need to programmatically knock it off and tell
3b98a7
-   people to put weird stuff in an include file that augeas doesn't parse.
3b98a7
-
3b98a7
-   the other option is to change the API to not parse the if statement at all,
3b98a7
-   just pull in the conditional as a string.
3b98a7
- *)
3b98a7
-
3b98a7
-let fct_re = "substring" | "binary-to-ascii" | "suffix" | "lcase" | "ucase"
3b98a7
-             | "gethostbyname" | "packet"
3b98a7
-             | "concat" | "reverse" | "encode-int"
3b98a7
-             | "extract-int" | "lease-time" | "client-state" | "exists" | "known" | "static"
3b98a7
-             | "pick-first-value" | "log" | "execute"
3b98a7
-
3b98a7
-(* not needs to be different because it's a negation of whatever happens next *)
3b98a7
-let op_re = "~="|"="|"~~"|"and"|"or"
3b98a7
+let fct_re = "substring" | "binary-to-ascii"
3b98a7
 
3b98a7
 let fct_args = [ label "args" . dels "(" . sep_osp .
3b98a7
                  ([ label "arg" . store sto_com ] . [ label "arg" . sep_com . store sto_com ]+) .
3b98a7
                         sep_osp . dels ")" ]
3b98a7
 
3b98a7
-let stmt_match_ifopt = [ dels "if" . sep_spc . key "option" . sep_spc . store word .
3b98a7
-                      sep_eq . ([ label "value" . bare_to_scl ]|[ label "value" . dquote_any ]) ]
3b98a7
-
3b98a7
-let stmt_match_func = [ store fct_re . sep_osp . label "function" . fct_args ] .
3b98a7
-                      sep_eq . ([ label "value" . bare_to_scl ]|[ label "value" . dquote_any ])
3b98a7
+let stmt_match_if = [ dels "if" . sep_spc . store fct_re . sep_osp . label "function" . fct_args ] .
3b98a7
+                      sep_eq . ([ label "value" . bare ]|[ label "value" . quote ])
3b98a7
 
3b98a7
 let stmt_match_pfv = [ label "function" . store "pick-first-value" . sep_spc .
3b98a7
                        dels "(" . sep_osp .
3b98a7
@@ -422,7 +327,7 @@ let stmt_match_pfv = [ label "function" . store "pick-first-value" . sep_spc .
3b98a7
 
3b98a7
 let stmt_match_tpl (l:lens) = [ indent . key "match" . sep_spc . l . sep_scl . eos ]
3b98a7
 
3b98a7
-let stmt_match = stmt_match_tpl (dels "if" . sep_spc . stmt_match_func | stmt_match_pfv | stmt_match_ifopt)
3b98a7
+let stmt_match = stmt_match_tpl (stmt_match_if | stmt_match_pfv )
3b98a7
 
3b98a7
 (************************************************************************
3b98a7
  *                         BLOCK STATEMENTS
3b98a7
@@ -438,11 +343,12 @@ let stmt_entry        =   stmt_secu
3b98a7
                         | stmt_noarg
3b98a7
                         | stmt_match
3b98a7
                         | stmt_subclass
3b98a7
-                        | stmt_set
3b98a7
                         | empty
3b98a7
                         | comment
3b98a7
 
3b98a7
-let stmt_block_noarg_re = "pool" | "group"
3b98a7
+let stmt_block_noarg_re = "pool"
3b98a7
+                        | "group"
3b98a7
+                        | "allow-update"
3b98a7
 
3b98a7
 let stmt_block_noarg (body:lens)
3b98a7
                         = [ indent
3b98a7
@@ -456,14 +362,16 @@ let stmt_block_arg_re = "host"
3b98a7
                       | "shared-network"
3b98a7
                       | /failover[ ]+peer/
3b98a7
                       | "zone"
3b98a7
-                      | "group"
3b98a7
-                      | "on"
3b98a7
+                      | "key"
3b98a7
 
3b98a7
 let stmt_block_arg (body:lens)
3b98a7
-                      = ([ indent . key stmt_block_arg_re . sep_spc . dquote_any . sep_obr . body* . sep_cbr ]
3b98a7
-                         |[ indent . key stmt_block_arg_re . sep_spc . bare_to_scl . sep_obr . body* . sep_cbr ]
3b98a7
-                         |[ indent . del /key/ "key" . label "key_block" . sep_spc . dquote_any . sep_obr . body* . sep_cbr . del /(;([ \t]*\n)*)?/ ""  ]
3b98a7
-                         |[ indent . del /key/ "key" . label "key_block" . sep_spc . bare_to_scl . sep_obr . body* . sep_cbr . del /(;([ \t]*\n)*)?/ "" ])
3b98a7
+                      = [ indent
3b98a7
+                        . key stmt_block_arg_re
3b98a7
+                        . sep_spc
3b98a7
+                        . sto_to_spc
3b98a7
+                        . sep_obr
3b98a7
+                        . body*
3b98a7
+                        . sep_cbr ]
3b98a7
 
3b98a7
 let stmt_block_subnet (body:lens)
3b98a7
                       = [ indent
3b98a7
@@ -476,37 +384,11 @@ let stmt_block_subnet (body:lens)
3b98a7
                         . body*
3b98a7
                         . sep_cbr ]
3b98a7
 
3b98a7
-let conditional (body:lens) =
3b98a7
-     let condition         = /[^{ \r\t\n][^{\n]*[^{ \r\t\n]|[^{ \t\n\r]/
3b98a7
-  in let elsif = [ indent
3b98a7
-                 . Build.xchgs "elsif" "@elsif"
3b98a7
-                 . sep_spc
3b98a7
-                 . store condition
3b98a7
-                 . sep_obr
3b98a7
-                 . body*
3b98a7
-                 . sep_cbr ]
3b98a7
-  in let else = [  indent
3b98a7
-                 . Build.xchgs "else" "@else"
3b98a7
-                 . sep_obr
3b98a7
-                 . body*
3b98a7
-                 . sep_cbr ]
3b98a7
-  in [ indent
3b98a7
-     . Build.xchgs "if" "@if"
3b98a7
-     . sep_spc
3b98a7
-     . store condition
3b98a7
-     . sep_obr
3b98a7
-     . body*
3b98a7
-     . sep_cbr
3b98a7
-     . elsif*
3b98a7
-     . else? ]
3b98a7
-
3b98a7
-
3b98a7
 let all_block (body:lens) =
3b98a7
     let lns1 = stmt_block_subnet body in
3b98a7
     let lns2 = stmt_block_arg body in
3b98a7
     let lns3 = stmt_block_noarg body in
3b98a7
-    let lns4 = conditional body in
3b98a7
-    (lns1 | lns2 | lns3 | lns4 | stmt_entry)
3b98a7
+    (lns1 | lns2 | lns3 | stmt_entry)
3b98a7
 
3b98a7
 let rec lns_staging = stmt_entry|all_block lns_staging
3b98a7
 let lns = (lns_staging)*
3b98a7
diff --git a/lenses/dhcpd_140.aug b/lenses/dhcpd_140.aug
3b98a7
new file mode 100644
3b98a7
index 00000000..c9072990
3b98a7
--- /dev/null
3b98a7
+++ b/lenses/dhcpd_140.aug
3b98a7
@@ -0,0 +1,512 @@
3b98a7
+(*
3b98a7
+Module: Dhcpd_140
3b98a7
+  BIND dhcp 3 server configuration module for Augeas
3b98a7
+
3b98a7
+  This module is compatible with Augeas 1.4.0, but is not loaded by default.
3b98a7
+
3b98a7
+Author: Francis Giraldeau <francis.giraldeau@usherbrooke.ca>
3b98a7
+
3b98a7
+About: Reference
3b98a7
+  Reference: manual of dhcpd.conf and dhcp-eval
3b98a7
+  Follow dhclient module for tree structure
3b98a7
+
3b98a7
+About: License
3b98a7
+    This file is licensed under the GPL.
3b98a7
+
3b98a7
+About: Lens Usage
3b98a7
+  Sample usage of this lens in augtool
3b98a7
+
3b98a7
+  Directive without argument.
3b98a7
+  Set this dhcpd server authoritative on the domain.
3b98a7
+  > clear /files/etc/dhcp3/dhcpd.conf/authoritative
3b98a7
+
3b98a7
+  Directives with integer or string argument.
3b98a7
+  Set max-lease-time to one hour:
3b98a7
+  > set /files/etc/dhcp3/dhcpd.conf/max-lease-time 3600
3b98a7
+
3b98a7
+  Options are declared as a list, even for single values.
3b98a7
+  Set the domain of the network:
3b98a7
+  > set /files/etc/dhcp3/dhcpd.conf/option/domain-name/arg example.org
3b98a7
+  Set two name server:
3b98a7
+  > set /files/etc/dhcp3/dhcpd.conf/option/domain-name-servers/arg[1] foo.example.org
3b98a7
+  > set /files/etc/dhcp3/dhcpd.conf/option/domain-name-servers/arg[2] bar.example.org
3b98a7
+
3b98a7
+  Create the subnet 172.16.0.1 with 10 addresses:
3b98a7
+  > clear /files/etc/dhcp3/dhcpd.conf/subnet[last() + 1]
3b98a7
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/network 172.16.0.0
3b98a7
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/netmask 255.255.255.0
3b98a7
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/range/from 172.16.0.10
3b98a7
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/range/to 172.16.0.20
3b98a7
+
3b98a7
+  Create a new group "foo" with one static host. Nodes type and address are ordered.
3b98a7
+  > ins group after /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/*[last()]
3b98a7
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[last()]/host foo
3b98a7
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[host='foo']/host/hardware/type "ethernet"
3b98a7
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[host='foo']/host/hardware/address "00:00:00:aa:bb:cc"
3b98a7
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[host='foo']/host/fixed-address 172.16.0.100
3b98a7
+
3b98a7
+About: Configuration files
3b98a7
+  This lens applies to /etc/dhcpd3/dhcpd.conf. See <filter>.
3b98a7
+*)
3b98a7
+
3b98a7
+module Dhcpd_140 =
3b98a7
+
3b98a7
+(************************************************************************
3b98a7
+ *                           USEFUL PRIMITIVES
3b98a7
+ *************************************************************************)
3b98a7
+let dels (s:string)   = del s s
3b98a7
+let eol               = Util.eol
3b98a7
+let comment           = Util.comment
3b98a7
+let empty             = Util.empty
3b98a7
+let indent            = Util.indent
3b98a7
+let eos               = comment?
3b98a7
+
3b98a7
+(* Define separators *)
3b98a7
+let sep_spc           = del /[ \t]+/ " "
3b98a7
+let sep_osp           = del /[ \t]*/ ""
3b98a7
+let sep_scl           = del /[ \t]*;([ \t]*\n)*/ ";\n"
3b98a7
+let sep_obr           = del /[ \t\n]*\{([ \t]*\n)*/ " {\n"
3b98a7
+let sep_cbr           = del /[ \t]*\}([ \t]*\n)*/ "}\n"
3b98a7
+let sep_com           = del /[ \t\n]*,[ \t\n]*/ ", "
3b98a7
+let sep_slh           = del "\/" "/"
3b98a7
+let sep_col           = del ":" ":"
3b98a7
+let sep_eq            = del /[ \t\n]*=[ \t\n]*/ "="
3b98a7
+let scl               = del ";" ";"
3b98a7
+
3b98a7
+(* Define basic types *)
3b98a7
+let word              = /[A-Za-z0-9_.-]+(\[[0-9]+\])?/
3b98a7
+let ip                = Rx.ipv4
3b98a7
+
3b98a7
+(* Define fields *)
3b98a7
+
3b98a7
+(* adapted from sysconfig.aug *)
3b98a7
+  (* Chars allowed in a bare string *)
3b98a7
+  let bchar = /[^ \t\n"'\\{}#,()\/]|\\\\./
3b98a7
+  let qchar = /["']/  (* " *)
3b98a7
+
3b98a7
+  (* We split the handling of right hand sides into a few cases:
3b98a7
+   *   bare  - strings that contain no spaces, optionally enclosed in
3b98a7
+   *           single or double quotes
3b98a7
+   *   dquot - strings that contain at least one space, apostrophe or slash
3b98a7
+   *           which must be enclosed in double quotes
3b98a7
+   *   squot - strings that contain an unescaped double quote
3b98a7
+   *)
3b98a7
+  let bare = del qchar? "" . store (bchar+) . del qchar? ""
3b98a7
+  let quote = Quote.do_quote (store (bchar* . /[ \t'\/]/ . bchar*)+)
3b98a7
+  let dquote = Quote.do_dquote (store (bchar+))
3b98a7
+  (* these two are for special cases.  bare_to_scl is for any bareword that is
3b98a7
+   * space or semicolon terminated.  dquote_any allows almost any character in
3b98a7
+   * between the quotes. *)
3b98a7
+  let bare_to_scl = Quote.do_dquote_opt (store /[^" \t\n;]+/)
3b98a7
+  let dquote_any = Quote.do_dquote (store /[^"\n]*[ \t]+[^"\n]*/)
3b98a7
+
3b98a7
+let sto_to_spc        = store /[^\\#,;\{\}" \t\n]+|"[^\\#"\n]+"/
3b98a7
+let sto_to_scl        = store /[^ \t;][^;\n=]+[^ \t;]|[^ \t;=]+/
3b98a7
+
3b98a7
+let sto_number        = store /[0-9][0-9]*/
3b98a7
+
3b98a7
+(************************************************************************
3b98a7
+ *                         NO ARG STATEMENTS
3b98a7
+ *************************************************************************)
3b98a7
+
3b98a7
+let stmt_noarg_re     =   "authoritative"
3b98a7
+                        | "primary"
3b98a7
+                        | "secondary"
3b98a7
+
3b98a7
+let stmt_noarg        = [ indent
3b98a7
+                        . key stmt_noarg_re
3b98a7
+                        . sep_scl
3b98a7
+                        . eos ]
3b98a7
+
3b98a7
+(************************************************************************
3b98a7
+ *                         INT ARG STATEMENTS
3b98a7
+ *************************************************************************)
3b98a7
+
3b98a7
+let stmt_integer_re   = "default-lease-time"
3b98a7
+                      | "max-lease-time"
3b98a7
+                      | "min-lease-time"
3b98a7
+                      | /lease[ ]+limit/
3b98a7
+                      | "port"
3b98a7
+                      | /peer[ ]+port/
3b98a7
+                      | "max-response-delay"
3b98a7
+                      | "max-unacked-updates"
3b98a7
+                      | "mclt"
3b98a7
+                      | "split"
3b98a7
+                      | /load[ ]+balance[ ]+max[ ]+seconds/
3b98a7
+                      | "max-lease-misbalance"
3b98a7
+                      | "max-lease-ownership"
3b98a7
+                      | "min-balance"
3b98a7
+                      | "max-balance"
3b98a7
+                      | "adaptive-lease-time-threshold"
3b98a7
+                      | "dynamic-bootp-lease-length"
3b98a7
+                      | "local-port"
3b98a7
+                      | "min-sec"
3b98a7
+                      | "omapi-port"
3b98a7
+                      | "ping-timeout"
3b98a7
+                      | "remote-port"
3b98a7
+
3b98a7
+let stmt_integer      = [ indent
3b98a7
+                        . key stmt_integer_re
3b98a7
+                        . sep_spc
3b98a7
+                        . sto_number
3b98a7
+                        . sep_scl
3b98a7
+                        . eos ]
3b98a7
+
3b98a7
+(************************************************************************
3b98a7
+ *                         STRING ARG STATEMENTS
3b98a7
+ *************************************************************************)
3b98a7
+
3b98a7
+let stmt_string_re    = "ddns-update-style"
3b98a7
+                      | "ddns-updates"
3b98a7
+                      | "ddns-hostname"
3b98a7
+                      | "ddns-domainname"
3b98a7
+                      | "ddns-rev-domainname"
3b98a7
+                      | "log-facility"
3b98a7
+                      | "server-name"
3b98a7
+                      | "fixed-address"
3b98a7
+                      | /failover[ ]+peer/
3b98a7
+                      | "use-host-decl-names"
3b98a7
+                      | "next-server"
3b98a7
+                      | "address"
3b98a7
+                      | /peer[ ]+address/
3b98a7
+                      | "type"
3b98a7
+                      | "file"
3b98a7
+                      | "algorithm"
3b98a7
+                      | "secret"
3b98a7
+                      | "key"
3b98a7
+                      | "include"
3b98a7
+                      | "hba"
3b98a7
+                      | "boot-unknown-clients"
3b98a7
+                      | "db-time-format"
3b98a7
+                      | "do-forward-updates"
3b98a7
+                      | "dynamic-bootp-lease-cutoff"
3b98a7
+                      | "get-lease-hostnames"
3b98a7
+                      | "infinite-is-reserved"
3b98a7
+                      | "lease-file-name"
3b98a7
+                      | "local-address"
3b98a7
+                      | "one-lease-per-client"
3b98a7
+                      | "pid-file-name"
3b98a7
+                      | "ping-check"
3b98a7
+                      | "server-identifier"
3b98a7
+                      | "site-option-space"
3b98a7
+                      | "stash-agent-options"
3b98a7
+                      | "update-conflict-detection"
3b98a7
+                      | "update-optimization"
3b98a7
+                      | "update-static-leases"
3b98a7
+                      | "use-host-decl-names"
3b98a7
+                      | "use-lease-addr-for-default-route"
3b98a7
+                      | "vendor-option-space"
3b98a7
+                      | "primary"
3b98a7
+                      | "omapi-key"
3b98a7
+
3b98a7
+let stmt_string_tpl (kw:regexp) (l:lens) = [ indent
3b98a7
+                        . key kw
3b98a7
+                        . sep_spc
3b98a7
+                        . l
3b98a7
+                        . sep_scl
3b98a7
+                        . eos ]
3b98a7
+
3b98a7
+let stmt_string  = stmt_string_tpl stmt_string_re bare
3b98a7
+                 | stmt_string_tpl stmt_string_re quote
3b98a7
+                 | stmt_string_tpl "filename" dquote
3b98a7
+
3b98a7
+(************************************************************************
3b98a7
+ *                         RANGE STATEMENTS
3b98a7
+ *************************************************************************)
3b98a7
+
3b98a7
+let stmt_range        = [ indent
3b98a7
+                        . key "range"
3b98a7
+                        . sep_spc
3b98a7
+                        . [ label "flag" . store /dynamic-bootp/ . sep_spc ]?
3b98a7
+                        . [ label "from" . store ip . sep_spc ]?
3b98a7
+                        . [ label "to" . store ip ]
3b98a7
+                        . sep_scl
3b98a7
+                        . eos ]
3b98a7
+
3b98a7
+(************************************************************************
3b98a7
+ *                         HARDWARE STATEMENTS
3b98a7
+ *************************************************************************)
3b98a7
+
3b98a7
+let stmt_hardware     = [ indent
3b98a7
+                        . key "hardware"
3b98a7
+                        . sep_spc
3b98a7
+                        . [ label "type" . store /ethernet|tokenring|fddi/ ]
3b98a7
+                        . sep_spc
3b98a7
+                        . [ label "address" . store /[a-fA-F0-9:-]+/ ]
3b98a7
+                        . sep_scl
3b98a7
+                        . eos ]
3b98a7
+
3b98a7
+(************************************************************************
3b98a7
+ *                         SET STATEMENTS
3b98a7
+ *************************************************************************)
3b98a7
+let stmt_set          = [ indent
3b98a7
+                        . key "set"
3b98a7
+                        . sep_spc
3b98a7
+                        . store word
3b98a7
+                        . sep_spc
3b98a7
+                        . Sep.equal
3b98a7
+                        . sep_spc
3b98a7
+                        . [ label "value" . sto_to_scl ]
3b98a7
+                        . sep_scl
3b98a7
+                        . eos ]
3b98a7
+
3b98a7
+(************************************************************************
3b98a7
+ *                         OPTION STATEMENTS
3b98a7
+ *************************************************************************)
3b98a7
+(* The general case is considering options as a list *)
3b98a7
+
3b98a7
+
3b98a7
+let stmt_option_value = /((array of[ \t]+)?(((un)?signed[ \t]+)?integer (8|16|32)|string|ip6?-address|boolean|domain-list|text)|encapsulate [A-Za-z0-9_.-]+)/
3b98a7
+
3b98a7
+let stmt_option_list  = ([ label "arg" . bare ] | [ label "arg" . quote ])
3b98a7
+                        . ( sep_com . ([ label "arg" . bare ] | [ label "arg" . quote ]))*
3b98a7
+
3b98a7
+let del_trail_spc = del /[ \t\n]*/ ""
3b98a7
+
3b98a7
+let stmt_record = counter "record" . Util.del_str "{"
3b98a7
+                . sep_spc
3b98a7
+                . ([seq "record" . store stmt_option_value . sep_com]*
3b98a7
+                .  [seq "record" . store stmt_option_value . del_trail_spc])?
3b98a7
+                . Util.del_str "}"
3b98a7
+
3b98a7
+let stmt_option_code  = [ label "label" . store word . sep_spc ]
3b98a7
+                        . [ key "code" . sep_spc . store word ]
3b98a7
+                        . sep_eq
3b98a7
+                        . ([ label "type" . store stmt_option_value ]
3b98a7
+                          |[ label "record" . stmt_record ]) 
3b98a7
+
3b98a7
+let stmt_option_basic = [ key word . sep_spc . stmt_option_list ]
3b98a7
+let stmt_option_extra = [ key word . sep_spc . store /true|false/ . sep_spc . stmt_option_list ]
3b98a7
+
3b98a7
+let stmt_option_body = stmt_option_basic | stmt_option_extra
3b98a7
+
3b98a7
+let stmt_option1  = [ indent
3b98a7
+                        . key "option"
3b98a7
+                        . sep_spc
3b98a7
+                        . stmt_option_body
3b98a7
+                        . sep_scl
3b98a7
+                        . eos ]
3b98a7
+
3b98a7
+let stmt_option2  = [ indent
3b98a7
+                        . dels "option" . label "rfc-code"
3b98a7
+                        . sep_spc
3b98a7
+                        . stmt_option_code
3b98a7
+                        . sep_scl
3b98a7
+                        . eos ]
3b98a7
+
3b98a7
+let stmt_option = stmt_option1 | stmt_option2
3b98a7
+
3b98a7
+(************************************************************************
3b98a7
+ *                         SUBCLASS STATEMENTS
3b98a7
+ *************************************************************************)
3b98a7
+(* this statement is not well documented in the manual dhcpd.conf
3b98a7
+   we support basic use case *)
3b98a7
+
3b98a7
+let stmt_subclass = [ indent . key "subclass" . sep_spc 
3b98a7
+                      . ( [ label "name" .  bare_to_scl ]|[ label "name" .  dquote_any ] )
3b98a7
+                      . sep_spc 
3b98a7
+                      . ( [ label "value" . bare_to_scl ]|[ label "value" . dquote_any ] ) 
3b98a7
+                      . sep_scl 
3b98a7
+                      . eos ]
3b98a7
+
3b98a7
+
3b98a7
+(************************************************************************
3b98a7
+ *                         ALLOW/DENY STATEMENTS
3b98a7
+ *************************************************************************)
3b98a7
+(* We have to use special key for allow/deny members of
3b98a7
+  to avoid ambiguity in the put direction *)
3b98a7
+
3b98a7
+let allow_deny_re     = /unknown(-|[ ]+)clients/
3b98a7
+                      | /known(-|[ ]+)clients/
3b98a7
+                      | /all[ ]+clients/
3b98a7
+                      | /dynamic[ ]+bootp[ ]+clients/
3b98a7
+                      | /authenticated[ ]+clients/
3b98a7
+                      | /unauthenticated[ ]+clients/
3b98a7
+                      | "bootp"
3b98a7
+                      | "booting"
3b98a7
+                      | "duplicates"
3b98a7
+                      | "declines"
3b98a7
+                      | "client-updates"
3b98a7
+                      | "leasequery"
3b98a7
+
3b98a7
+let stmt_secu_re      = "allow"
3b98a7
+                      | "deny"
3b98a7
+
3b98a7
+let del_allow = del /allow[ ]+members[ ]+of/ "allow members of"
3b98a7
+let del_deny  = del /deny[ \t]+members[ \t]+of/ "deny members of"
3b98a7
+
3b98a7
+(* bare is anything but whitespace, quote marks or semicolon.
3b98a7
+ * technically this should be locked down to mostly alphanumerics, but the
3b98a7
+ * idea right now is just to make things work.  Also ideally I would use
3b98a7
+ * dquote_space but I had a whale of a time with it.  It doesn't like
3b98a7
+ * semicolon termination and my attempts to fix that led me to 3 hours of
3b98a7
+ * frustration and back to this :)
3b98a7
+ *)
3b98a7
+let stmt_secu_tpl (l:lens) (s:string) =
3b98a7
+                  [ indent . l . sep_spc . label s . bare_to_scl . sep_scl . eos ] |
3b98a7
+                  [ indent . l . sep_spc . label s . dquote_any . sep_scl . eos ]
3b98a7
+
3b98a7
+
3b98a7
+let stmt_secu         = [ indent . key stmt_secu_re . sep_spc .
3b98a7
+                          store allow_deny_re . sep_scl . eos ] |
3b98a7
+                        stmt_secu_tpl del_allow "allow-members-of" |
3b98a7
+                        stmt_secu_tpl del_deny "deny-members-of"
3b98a7
+
3b98a7
+(************************************************************************
3b98a7
+ *                         MATCH STATEMENTS
3b98a7
+ *************************************************************************)
3b98a7
+
3b98a7
+let sto_com = /[^ \t\n,\(\)][^,\(\)]*[^ \t\n,\(\)]|[^ \t\n,\(\)]+/ | word . /[ \t]*\([^)]*\)/
3b98a7
+(* this is already the most complicated part of this module and it's about to
3b98a7
+ * get worse.  match statements can be way more complicated than this
3b98a7
+ *
3b98a7
+ * examples:
3b98a7
+ *      using or:
3b98a7
+ *      match if ((option vendor-class-identifier="Banana Bready") or (option vendor-class-identifier="Cherry Sunfire"));
3b98a7
+ *      unneeded parenthesis:
3b98a7
+ *      match if (option vendor-class-identifier="Hello");
3b98a7
+ *
3b98a7
+ *      and of course the fact that the above two rules used one of infinately
3b98a7
+ *      many potential options instead of a builtin function.
3b98a7
+ *)
3b98a7
+(* sto_com doesn't support quoted strings as arguments.  It also doesn't
3b98a7
+   support single arguments (needs to match a comma) It will need to be
3b98a7
+   updated for lcase, ucase and log to be workable.
3b98a7
+
3b98a7
+   it also doesn't support no arguments, so gethostbyname() doesn't work.
3b98a7
+
3b98a7
+   option and config-option are considered operators.  They should be matched
3b98a7
+   in stmt_entry but also available under "match if" and "if" conditionals
3b98a7
+   leased-address, host-decl-name, both take no args and return a value.  We
3b98a7
+   might need to treat them as variable names in the parser.
3b98a7
+
3b98a7
+   things like this may be near-impossible to parse even with recursion
3b98a7
+   because we have no way of knowing when or if a subfunction takes arguments
3b98a7
+   set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
3b98a7
+
3b98a7
+   even if we could parse it, they could get arbitrarily complicated like:
3b98a7
+   binary-to-ascii(16, 8, ":", substring(hardware, 1, 6) and substring(hardware, 2, 3));
3b98a7
+
3b98a7
+   so at some point we may need to programmatically knock it off and tell
3b98a7
+   people to put weird stuff in an include file that augeas doesn't parse.
3b98a7
+
3b98a7
+   the other option is to change the API to not parse the if statement at all,
3b98a7
+   just pull in the conditional as a string.
3b98a7
+ *)
3b98a7
+
3b98a7
+let fct_re = "substring" | "binary-to-ascii" | "suffix" | "lcase" | "ucase"
3b98a7
+             | "gethostbyname" | "packet"
3b98a7
+             | "concat" | "reverse" | "encode-int"
3b98a7
+             | "extract-int" | "lease-time" | "client-state" | "exists" | "known" | "static"
3b98a7
+             | "pick-first-value" | "log" | "execute"
3b98a7
+
3b98a7
+(* not needs to be different because it's a negation of whatever happens next *)
3b98a7
+let op_re = "~="|"="|"~~"|"and"|"or"
3b98a7
+
3b98a7
+let fct_args = [ label "args" . dels "(" . sep_osp .
3b98a7
+                 ([ label "arg" . store sto_com ] . [ label "arg" . sep_com . store sto_com ]+) .
3b98a7
+                        sep_osp . dels ")" ]
3b98a7
+
3b98a7
+let stmt_match_ifopt = [ dels "if" . sep_spc . key "option" . sep_spc . store word .
3b98a7
+                      sep_eq . ([ label "value" . bare_to_scl ]|[ label "value" . dquote_any ]) ]
3b98a7
+
3b98a7
+let stmt_match_func = [ store fct_re . sep_osp . label "function" . fct_args ] .
3b98a7
+                      sep_eq . ([ label "value" . bare_to_scl ]|[ label "value" . dquote_any ])
3b98a7
+
3b98a7
+let stmt_match_pfv = [ label "function" . store "pick-first-value" . sep_spc .
3b98a7
+                       dels "(" . sep_osp .
3b98a7
+                       [ label "args" .
3b98a7
+                         [ label "arg" . store sto_com ] .
3b98a7
+                         [ sep_com . label "arg" . store sto_com ]+ ] .
3b98a7
+                       dels ")" ]
3b98a7
+
3b98a7
+let stmt_match_tpl (l:lens) = [ indent . key "match" . sep_spc . l . sep_scl . eos ]
3b98a7
+
3b98a7
+let stmt_match = stmt_match_tpl (dels "if" . sep_spc . stmt_match_func | stmt_match_pfv | stmt_match_ifopt)
3b98a7
+
3b98a7
+(************************************************************************
3b98a7
+ *                         BLOCK STATEMENTS
3b98a7
+ *************************************************************************)
3b98a7
+(* Blocks doesn't support comments at the end of the closing bracket *)
3b98a7
+
3b98a7
+let stmt_entry        =   stmt_secu
3b98a7
+                        | stmt_option
3b98a7
+                        | stmt_hardware
3b98a7
+                        | stmt_range
3b98a7
+                        | stmt_string
3b98a7
+                        | stmt_integer
3b98a7
+                        | stmt_noarg
3b98a7
+                        | stmt_match
3b98a7
+                        | stmt_subclass
3b98a7
+                        | stmt_set
3b98a7
+                        | empty
3b98a7
+                        | comment
3b98a7
+
3b98a7
+let stmt_block_noarg_re = "pool" | "group"
3b98a7
+
3b98a7
+let stmt_block_noarg (body:lens)
3b98a7
+                        = [ indent
3b98a7
+                        . key stmt_block_noarg_re
3b98a7
+                        . sep_obr
3b98a7
+                        . body*
3b98a7
+                        . sep_cbr ]
3b98a7
+
3b98a7
+let stmt_block_arg_re = "host"
3b98a7
+                      | "class"
3b98a7
+                      | "shared-network"
3b98a7
+                      | /failover[ ]+peer/
3b98a7
+                      | "zone"
3b98a7
+                      | "group"
3b98a7
+                      | "on"
3b98a7
+
3b98a7
+let stmt_block_arg (body:lens)
3b98a7
+                      = ([ indent . key stmt_block_arg_re . sep_spc . dquote_any . sep_obr . body* . sep_cbr ]
3b98a7
+                         |[ indent . key stmt_block_arg_re . sep_spc . bare_to_scl . sep_obr . body* . sep_cbr ]
3b98a7
+                         |[ indent . del /key/ "key" . label "key_block" . sep_spc . dquote_any . sep_obr . body* . sep_cbr . del /(;([ \t]*\n)*)?/ ""  ]
3b98a7
+                         |[ indent . del /key/ "key" . label "key_block" . sep_spc . bare_to_scl . sep_obr . body* . sep_cbr . del /(;([ \t]*\n)*)?/ "" ])
3b98a7
+
3b98a7
+let stmt_block_subnet (body:lens)
3b98a7
+                      = [ indent
3b98a7
+                        . key "subnet"
3b98a7
+                        . sep_spc
3b98a7
+                        . [ label "network" . store ip ]
3b98a7
+                        . sep_spc
3b98a7
+                        . [ key "netmask" . sep_spc . store ip ]
3b98a7
+                        . sep_obr
3b98a7
+                        . body*
3b98a7
+                        . sep_cbr ]
3b98a7
+
3b98a7
+let conditional (body:lens) =
3b98a7
+     let condition         = /[^{ \r\t\n][^{\n]*[^{ \r\t\n]|[^{ \t\n\r]/
3b98a7
+  in let elsif = [ indent
3b98a7
+                 . Build.xchgs "elsif" "@elsif"
3b98a7
+                 . sep_spc
3b98a7
+                 . store condition
3b98a7
+                 . sep_obr
3b98a7
+                 . body*
3b98a7
+                 . sep_cbr ]
3b98a7
+  in let else = [  indent
3b98a7
+                 . Build.xchgs "else" "@else"
3b98a7
+                 . sep_obr
3b98a7
+                 . body*
3b98a7
+                 . sep_cbr ]
3b98a7
+  in [ indent
3b98a7
+     . Build.xchgs "if" "@if"
3b98a7
+     . sep_spc
3b98a7
+     . store condition
3b98a7
+     . sep_obr
3b98a7
+     . body*
3b98a7
+     . sep_cbr
3b98a7
+     . elsif*
3b98a7
+     . else? ]
3b98a7
+
3b98a7
+
3b98a7
+let all_block (body:lens) =
3b98a7
+    let lns1 = stmt_block_subnet body in
3b98a7
+    let lns2 = stmt_block_arg body in
3b98a7
+    let lns3 = stmt_block_noarg body in
3b98a7
+    let lns4 = conditional body in
3b98a7
+    (lns1 | lns2 | lns3 | lns4 | stmt_entry)
3b98a7
+
3b98a7
+let rec lns_staging = stmt_entry|all_block lns_staging
3b98a7
+let lns = (lns_staging)*
3b98a7
diff --git a/lenses/tests/test_dhcpd.aug b/lenses/tests/test_dhcpd.aug
3b98a7
index 0af337c2..96630296 100644
3b98a7
--- a/lenses/tests/test_dhcpd.aug
3b98a7
+++ b/lenses/tests/test_dhcpd.aug
3b98a7
@@ -28,9 +28,6 @@ max-lease-time 7200;
3b98a7
 # network, the authoritative directive should be uncommented.
3b98a7
 authoritative;
3b98a7
 
3b98a7
-allow booting;
3b98a7
-allow bootp;
3b98a7
-
3b98a7
 # Use this to send dhcp log messages to a different log file (you also
3b98a7
 # have to hack syslog.conf to complete the redirection).
3b98a7
 log-facility local7;
3b98a7
@@ -182,12 +179,7 @@ fixed-address 10.1.1.1;}}" =
3b98a7
     }
3b98a7
   }
3b98a7
 
3b98a7
-test lns get "group fan-tas_tic { }" =
3b98a7
-  { "group" = "fan-tas_tic" }
3b98a7
-
3b98a7
 test Dhcpd.stmt_secu get "allow members of \"foo\";" =  { "allow-members-of" = "foo" }
3b98a7
-test Dhcpd.stmt_secu get "allow booting;" =  { "allow" = "booting" }
3b98a7
-test Dhcpd.stmt_secu get "allow bootp;" =  { "allow" = "bootp" }
3b98a7
 test Dhcpd.stmt_option get "option voip-boot-server code 66 = string;" =
3b98a7
   { "rfc-code"
3b98a7
     { "label" = "voip-boot-server" }
3b98a7
@@ -195,30 +187,6 @@ test Dhcpd.stmt_option get "option voip-boot-server code 66 = string;" =
3b98a7
     { "type" = "string" }
3b98a7
   }
3b98a7
 
3b98a7
-test Dhcpd.stmt_option get "option special-option code 25 = array of string;" =
3b98a7
-  { "rfc-code"
3b98a7
-    { "label" = "special-option" }
3b98a7
-    { "code" = "25" }
3b98a7
-    { "type" = "array of string" }
3b98a7
-  }
3b98a7
-
3b98a7
-test Dhcpd.stmt_option get "option special-option code 25 = integer 32;" =
3b98a7
-  { "rfc-code"
3b98a7
-    { "label" = "special-option" }
3b98a7
-    { "code" = "25" }
3b98a7
-    { "type" = "integer 32" }
3b98a7
-  }
3b98a7
-
3b98a7
-
3b98a7
-test Dhcpd.stmt_option get "option special-option code 25 = array of integer 32;" =
3b98a7
-  { "rfc-code"
3b98a7
-    { "label" = "special-option" }
3b98a7
-    { "code" = "25" }
3b98a7
-    { "type" = "array of integer 32" }
3b98a7
-  }
3b98a7
-
3b98a7
-
3b98a7
-
3b98a7
 test Dhcpd.lns get "authoritative;
3b98a7
 log-facility local7;
3b98a7
 ddns-update-style none;
3b98a7
@@ -274,7 +242,7 @@ failover peer \"redondance01\" {
3b98a7
     }
3b98a7
   }
3b98a7
   { "next-server" = "10.1.1.1" }
3b98a7
-  { "failover peer" = "redondance01"
3b98a7
+  { "failover peer" = "\"redondance01\""
3b98a7
     { "primary" }
3b98a7
     { "address" = "10.1.1.1" }
3b98a7
     { "port" = "647" }
3b98a7
@@ -291,26 +259,6 @@ failover peer \"redondance01\" {
3b98a7
     { "load balance max seconds" = "3" }
3b98a7
   }
3b98a7
 
3b98a7
-
3b98a7
-(* test get and put for record types *)
3b98a7
-let record_test = "option test_records code 123 = { string, ip-address, integer 32, ip6-address, domain-list };"
3b98a7
-
3b98a7
-test Dhcpd.lns get record_test =
3b98a7
- { "rfc-code"
3b98a7
-   { "label" = "test_records" }
3b98a7
-   { "code" = "123" }
3b98a7
-     { "record"
3b98a7
-        { "1" = "string" }
3b98a7
-        { "2" = "ip-address" }
3b98a7
-        { "3" = "integer 32" }
3b98a7
-        { "4" = "ip6-address" }
3b98a7
-        { "5" = "domain-list" }
3b98a7
-     }
3b98a7
- }
3b98a7
-
3b98a7
-test Dhcpd.lns put record_test after set "/rfc-code[1]/code" "124" = 
3b98a7
-  "option test_records code 124 = { string, ip-address, integer 32, ip6-address, domain-list };"
3b98a7
-
3b98a7
 test Dhcpd.lns get "
3b98a7
 option CallManager code 150 = ip-address;
3b98a7
 option slp-directory-agent true 10.1.1.1, 10.2.2.2;
3b98a7
@@ -386,25 +334,6 @@ test Dhcpd.stmt_match get "match if substring (option dhcp-client-identifier, 1,
3b98a7
     { "value" = "RAS" }
3b98a7
   }
3b98a7
 
3b98a7
-test Dhcpd.stmt_match get "match if suffix (option dhcp-client-identifier, 4) = \"RAS\";" =
3b98a7
-  { "match"
3b98a7
-    { "function" = "suffix"
3b98a7
-      { "args"
3b98a7
-        { "arg" = "option dhcp-client-identifier" }
3b98a7
-        { "arg" = "4" }
3b98a7
-      }
3b98a7
-    }
3b98a7
-    { "value" = "RAS" }
3b98a7
-  }
3b98a7
-
3b98a7
-test Dhcpd.stmt_match get "match if option vendor-class-identifier=\"RAS\";" =
3b98a7
-  { "match"
3b98a7
-    { "option" = "vendor-class-identifier"
3b98a7
-      { "value" = "RAS" }
3b98a7
-    }
3b98a7
-  }
3b98a7
-
3b98a7
-
3b98a7
 test Dhcpd.lns get "match pick-first-value (option dhcp-client-identifier, hardware);" =
3b98a7
   { "match"
3b98a7
     { "function" = "pick-first-value"
3b98a7
@@ -436,26 +365,12 @@ test Dhcpd.stmt_match get "match if binary-to-ascii(16, 32, \"\", substring(hard
3b98a7
     { "value" = "1525400" }
3b98a7
   }
3b98a7
 
3b98a7
-test Dhcpd.lns get "subclass allocation-class-1 1:8:0:2b:4c:39:ad;" =
3b98a7
-  { "subclass"
3b98a7
-    { "name" = "allocation-class-1" }
3b98a7
-    { "value" = "1:8:0:2b:4c:39:ad" }
3b98a7
-  }
3b98a7
-
3b98a7
-
3b98a7
 test Dhcpd.lns get "subclass \"allocation-class-1\" 1:8:0:2b:4c:39:ad;" =
3b98a7
   { "subclass"
3b98a7
     { "name" = "allocation-class-1" }
3b98a7
     { "value" = "1:8:0:2b:4c:39:ad" }
3b98a7
   }
3b98a7
 
3b98a7
-test Dhcpd.lns get "subclass \"quoted class\" \"quoted value\";" =
3b98a7
-  { "subclass"
3b98a7
-    { "name" = "quoted class" }
3b98a7
-    { "value" = "quoted value" }
3b98a7
-  }
3b98a7
-
3b98a7
-
3b98a7
 (* overall test *)
3b98a7
 test Dhcpd.lns put conf after rm "/x" = conf
3b98a7
 
3b98a7
@@ -477,130 +392,3 @@ filename \"pxelinux.0\";
3b98a7
 test Dhcpd.lns put "subnet 172.16.0.0 netmask 255.255.255.0 {
3b98a7
 }" after
3b98a7
   set "subnet/filename" "pxelinux.0" = input311
3b98a7
-
3b98a7
-(* GH issue #34: support conditional structures *)
3b98a7
-let gh34_empty = "if exists dhcp-parameter-request-list {
3b98a7
-}\n"
3b98a7
-
3b98a7
-test Dhcpd.lns get gh34_empty =
3b98a7
-  { "@if" = "exists dhcp-parameter-request-list" }
3b98a7
-
3b98a7
-let gh34_empty_multi = "subnet 192.168.100.0 netmask 255.255.255.0 {
3b98a7
- if true {
3b98a7
- } elsif false {
3b98a7
- } else {
3b98a7
- }
3b98a7
-}\n"
3b98a7
-
3b98a7
-test Dhcpd.lns get gh34_empty_multi =
3b98a7
-  { "subnet"
3b98a7
-    { "network" = "192.168.100.0" }
3b98a7
-    { "netmask" = "255.255.255.0" }
3b98a7
-    { "@if" = "true"
3b98a7
-      { "@elsif" = "false" }
3b98a7
-      { "@else" } }
3b98a7
-  }
3b98a7
-
3b98a7
-let gh34_simple = "if exists dhcp-parameter-request-list {
3b98a7
-  default-lease-time 600;
3b98a7
-  } else {
3b98a7
-default-lease-time 200;
3b98a7
-}\n"
3b98a7
-
3b98a7
-test Dhcpd.lns get gh34_simple =
3b98a7
-  { "@if" = "exists dhcp-parameter-request-list"
3b98a7
-    { "default-lease-time" = "600" }
3b98a7
-    { "@else"
3b98a7
-      { "default-lease-time" = "200" } } }
3b98a7
-
3b98a7
-test Dhcpd.lns get "omapi-key fookey;" =
3b98a7
-  { "omapi-key" = "fookey" }
3b98a7
-
3b98a7
-(* almost all DHCP groups should support braces starting on the next line *)
3b98a7
-test Dhcpd.lns get "class introduction
3b98a7
-{
3b98a7
-}" =
3b98a7
-  { "class" = "introduction" }
3b98a7
-
3b98a7
-(* equals should work the same *)
3b98a7
-test Dhcpd.lns get "option test_records code 123 =
3b98a7
-                             string;" =
3b98a7
- { "rfc-code"
3b98a7
-   { "label" = "test_records" }
3b98a7
-   { "code" = "123" }
3b98a7
-   { "type" = "string" }
3b98a7
- }
3b98a7
-
3b98a7
-test Dhcpd.lns get "deny members of \"Are things like () allowed?\";" =
3b98a7
-  { "deny-members-of" = "Are things like () allowed?" }
3b98a7
-
3b98a7
-test Dhcpd.lns get "deny unknown clients;" =
3b98a7
-  { "deny" = "unknown clients" }
3b98a7
-test Dhcpd.lns get "deny known-clients;" =
3b98a7
-  { "deny" = "known-clients" }
3b98a7
-
3b98a7
-test Dhcpd.lns get "set ClientMac = binary-to-ascii(16, 8, \":\" , substring(hardware, 1, 6));" =
3b98a7
-  { "set" = "ClientMac"
3b98a7
-    { "value" = "binary-to-ascii(16, 8, \":\" , substring(hardware, 1, 6))" }
3b98a7
-  }
3b98a7
-
3b98a7
-test Dhcpd.lns get "set myvariable = foo;" =
3b98a7
-  { "set" = "myvariable"
3b98a7
-    { "value" = "foo" }
3b98a7
-  }
3b98a7
-
3b98a7
-test Dhcpd.stmt_hardware get "hardware fddi 00:01:02:03:04:05;" =
3b98a7
-  { "hardware"
3b98a7
-    { "type" = "fddi" }
3b98a7
-    { "address" = "00:01:02:03:04:05" }
3b98a7
-  }
3b98a7
-
3b98a7
-test Dhcpd.lns get "on commit
3b98a7
-{
3b98a7
-  set test = thing;
3b98a7
-}" =
3b98a7
-  { "on" = "commit"
3b98a7
-    { "set" = "test"
3b98a7
-      { "value" = "thing" }
3b98a7
-    }
3b98a7
-  }
3b98a7
-
3b98a7
-(* key block get/put/set test *)
3b98a7
-let key_tests = "key sample {
3b98a7
-    algorithm hmac-md5;
3b98a7
-    secret \"secret==\";
3b98a7
-}
3b98a7
-
3b98a7
-key \"interesting\" { };
3b98a7
-
3b98a7
-key \"third key\" {
3b98a7
-    secret \"two==\";
3b98a7
-}"
3b98a7
-
3b98a7
-test Dhcpd.lns get key_tests =
3b98a7
-  { "key_block" = "sample"
3b98a7
-    { "algorithm"  = "hmac-md5" }
3b98a7
-    { "secret" = "secret==" }
3b98a7
-  }
3b98a7
-  { "key_block" = "interesting" }
3b98a7
-  { "key_block" = "third key"
3b98a7
-    { "secret" = "two==" }
3b98a7
-  }
3b98a7
-
3b98a7
-test Dhcpd.lns put key_tests after set "/key_block[1]" "sample2" =
3b98a7
-  "key sample2 {
3b98a7
-    algorithm hmac-md5;
3b98a7
-    secret \"secret==\";
3b98a7
-}
3b98a7
-
3b98a7
-key \"interesting\" { };
3b98a7
-
3b98a7
-key \"third key\" {
3b98a7
-    secret \"two==\";
3b98a7
-}"
3b98a7
-
3b98a7
-test Dhcpd.lns get "group \"hello\" { }" =
3b98a7
-  { "group" = "hello" }
3b98a7
-
3b98a7
-test Dhcpd.lns get "class \"testing class with spaces and quotes and ()\" {}" =
3b98a7
-  { "class" = "testing class with spaces and quotes and ()" }
3b98a7
diff --git a/lenses/tests/test_dhcpd_140.aug b/lenses/tests/test_dhcpd_140.aug
3b98a7
new file mode 100644
3b98a7
index 00000000..9d6fdc88
3b98a7
--- /dev/null
3b98a7
+++ b/lenses/tests/test_dhcpd_140.aug
3b98a7
@@ -0,0 +1,606 @@
3b98a7
+module Test_dhcpd_140 =
3b98a7
+
3b98a7
+let lns = Dhcpd_140.lns
3b98a7
+
3b98a7
+let conf = "#
3b98a7
+# Sample configuration file for ISC dhcpd for Debian
3b98a7
+#
3b98a7
+# Attention: If /etc/ltsp/dhcpd.conf exists, that will be used as
3b98a7
+# configuration file instead of this file.
3b98a7
+#
3b98a7
+# $Id: dhcpd.conf,v 1.1.1.1 2002/05/21 00:07:44 peloy Exp $
3b98a7
+#
3b98a7
+
3b98a7
+# The ddns-updates-style parameter controls whether or not the server will
3b98a7
+# attempt to do a DNS update when a lease is confirmed. We default to the
3b98a7
+# behavior of the version 2 packages ('none', since DHCP v2 didn't
3b98a7
+# have support for DDNS.)
3b98a7
+ddns-update-style none;
3b98a7
+
3b98a7
+# option definitions common to all supported networks...
3b98a7
+option domain-name \"example.org\";
3b98a7
+option domain-name-servers ns1.example.org, ns2.example.org;
3b98a7
+
3b98a7
+default-lease-time 600;
3b98a7
+max-lease-time 7200;
3b98a7
+
3b98a7
+# If this DHCP server is the official DHCP server for the local
3b98a7
+# network, the authoritative directive should be uncommented.
3b98a7
+authoritative;
3b98a7
+
3b98a7
+allow booting;
3b98a7
+allow bootp;
3b98a7
+
3b98a7
+# Use this to send dhcp log messages to a different log file (you also
3b98a7
+# have to hack syslog.conf to complete the redirection).
3b98a7
+log-facility local7;
3b98a7
+
3b98a7
+# No service will be given on this subnet, but declaring it helps the
3b98a7
+# DHCP server to understand the network topology.
3b98a7
+
3b98a7
+subnet 10.152.187.0 netmask 255.255.255.0 {
3b98a7
+}
3b98a7
+
3b98a7
+# This is a very basic subnet declaration.
3b98a7
+
3b98a7
+subnet 10.254.239.0 netmask 255.255.255.224 {
3b98a7
+  range 10.254.239.10 10.254.239.20;
3b98a7
+  option routers rtr-239-0-1.example.org, rtr-239-0-2.example.org;
3b98a7
+}
3b98a7
+
3b98a7
+# This declaration allows BOOTP clients to get dynamic addresses,
3b98a7
+# which we don't really recommend.
3b98a7
+
3b98a7
+subnet 10.254.239.32 netmask 255.255.255.224 {
3b98a7
+  range dynamic-bootp 10.254.239.40 10.254.239.60;
3b98a7
+  option broadcast-address 10.254.239.31;
3b98a7
+  option routers rtr-239-32-1.example.org;
3b98a7
+}
3b98a7
+
3b98a7
+# A slightly different configuration for an internal subnet.
3b98a7
+subnet 10.5.5.0 netmask 255.255.255.224 {
3b98a7
+  range 10.5.5.26 10.5.5.30;
3b98a7
+  option domain-name-servers ns1.internal.example.org;
3b98a7
+  option domain-name \"internal.example.org\";
3b98a7
+  option routers 10.5.5.1;
3b98a7
+  option broadcast-address 10.5.5.31;
3b98a7
+  default-lease-time 600;
3b98a7
+  max-lease-time 7200;
3b98a7
+}
3b98a7
+
3b98a7
+# Hosts which require special configuration options can be listed in
3b98a7
+# host statements.   If no address is specified, the address will be
3b98a7
+# allocated dynamically (if possible), but the host-specific information
3b98a7
+# will still come from the host declaration.
3b98a7
+
3b98a7
+host passacaglia {
3b98a7
+  hardware ethernet 0:0:c0:5d:bd:95;
3b98a7
+  filename \"vmunix.passacaglia\";
3b98a7
+  server-name \"toccata.fugue.com\";
3b98a7
+}
3b98a7
+
3b98a7
+# Fixed IP addresses can also be specified for hosts.   These addresses
3b98a7
+# should not also be listed as being available for dynamic assignment.
3b98a7
+# Hosts for which fixed IP addresses have been specified can boot using
3b98a7
+# BOOTP or DHCP.   Hosts for which no fixed address is specified can only
3b98a7
+# be booted with DHCP, unless there is an address range on the subnet
3b98a7
+# to which a BOOTP client is connected which has the dynamic-bootp flag
3b98a7
+# set.
3b98a7
+host fantasia {
3b98a7
+  hardware ethernet 08:00:07:26:c0:a5;
3b98a7
+  fixed-address fantasia.fugue.com;
3b98a7
+}
3b98a7
+
3b98a7
+# You can declare a class of clients and then do address allocation
3b98a7
+# based on that.   The example below shows a case where all clients
3b98a7
+# in a certain class get addresses on the 10.17.224/24 subnet, and all
3b98a7
+# other clients get addresses on the 10.0.29/24 subnet.
3b98a7
+
3b98a7
+#class \"foo\" {
3b98a7
+#  match if substring (option vendor-class-identifier, 0, 4) = \"SUNW\";
3b98a7
+#}
3b98a7
+
3b98a7
+shared-network 224-29 {
3b98a7
+  subnet 10.17.224.0 netmask 255.255.255.0 {
3b98a7
+    option routers rtr-224.example.org;
3b98a7
+  }
3b98a7
+  subnet 10.0.29.0 netmask 255.255.255.0 {
3b98a7
+    option routers rtr-29.example.org;
3b98a7
+  }
3b98a7
+  pool {
3b98a7
+    allow members of \"foo\";
3b98a7
+    range 10.17.224.10 10.17.224.250;
3b98a7
+  }
3b98a7
+  pool {
3b98a7
+    deny members of \"foo\";
3b98a7
+    range 10.0.29.10 10.0.29.230;
3b98a7
+  }
3b98a7
+}
3b98a7
+"
3b98a7
+
3b98a7
+test lns get "authoritative;" = { "authoritative" }
3b98a7
+test lns get "ddns-update-style none;" = { "ddns-update-style" = "none" }
3b98a7
+test lns get "option domain-name \"example.org\";" =
3b98a7
+  { "option"
3b98a7
+    { "domain-name"
3b98a7
+      { "arg" = "example.org" }
3b98a7
+    }
3b98a7
+  }
3b98a7
+
3b98a7
+test lns get "option domain-name-servers ns1.example.org, ns2.example.org;" =
3b98a7
+  { "option"
3b98a7
+    { "domain-name-servers"
3b98a7
+      { "arg" = "ns1.example.org" }
3b98a7
+      { "arg" = "ns2.example.org" }
3b98a7
+    }
3b98a7
+  }
3b98a7
+
3b98a7
+test lns get "default-lease-time 600;" = { "default-lease-time" = "600" }
3b98a7
+test lns get "range 10.254.239.60;" =
3b98a7
+{ "range"
3b98a7
+    { "to" = "10.254.239.60" }
3b98a7
+  }
3b98a7
+
3b98a7
+test lns get "range dynamic-bootp 10.254.239.60;" =
3b98a7
+  { "range"
3b98a7
+    { "flag" = "dynamic-bootp" }
3b98a7
+    { "to" = "10.254.239.60" }
3b98a7
+  }
3b98a7
+
3b98a7
+test lns get "range dynamic-bootp 10.254.239.40 10.254.239.60;" =
3b98a7
+  { "range"
3b98a7
+    { "flag" = "dynamic-bootp" }
3b98a7
+    { "from" = "10.254.239.40" }
3b98a7
+    { "to" = "10.254.239.60" }
3b98a7
+  }
3b98a7
+
3b98a7
+test lns get "subnet 10.152.187.0 netmask 255.255.255.0 {}\n" =
3b98a7
+  { "subnet"
3b98a7
+    { "network" = "10.152.187.0" }
3b98a7
+    { "netmask" = "255.255.255.0" }
3b98a7
+  }
3b98a7
+
3b98a7
+test lns get " pool {
3b98a7
+    pool {
3b98a7
+
3b98a7
+    }
3b98a7
+}
3b98a7
+" =
3b98a7
+  { "pool"
3b98a7
+    { "pool" }
3b98a7
+  }
3b98a7
+
3b98a7
+test lns get "group { host some-host {hardware ethernet 00:00:aa:bb:cc:dd;
3b98a7
+fixed-address 10.1.1.1;}}" =
3b98a7
+  { "group"
3b98a7
+    { "host" = "some-host"
3b98a7
+      { "hardware"
3b98a7
+        { "type" = "ethernet" }
3b98a7
+        { "address" = "00:00:aa:bb:cc:dd" }
3b98a7
+      }
3b98a7
+      { "fixed-address" = "10.1.1.1" }
3b98a7
+    }
3b98a7
+  }
3b98a7
+
3b98a7
+test lns get "group fan-tas_tic { }" =
3b98a7
+  { "group" = "fan-tas_tic" }
3b98a7
+
3b98a7
+test Dhcpd_140.stmt_secu get "allow members of \"foo\";" =  { "allow-members-of" = "foo" }
3b98a7
+test Dhcpd_140.stmt_secu get "allow booting;" =  { "allow" = "booting" }
3b98a7
+test Dhcpd_140.stmt_secu get "allow bootp;" =  { "allow" = "bootp" }
3b98a7
+test Dhcpd_140.stmt_option get "option voip-boot-server code 66 = string;" =
3b98a7
+  { "rfc-code"
3b98a7
+    { "label" = "voip-boot-server" }
3b98a7
+    { "code" = "66" }
3b98a7
+    { "type" = "string" }
3b98a7
+  }
3b98a7
+
3b98a7
+test Dhcpd_140.stmt_option get "option special-option code 25 = array of string;" =
3b98a7
+  { "rfc-code"
3b98a7
+    { "label" = "special-option" }
3b98a7
+    { "code" = "25" }
3b98a7
+    { "type" = "array of string" }
3b98a7
+  }
3b98a7
+
3b98a7
+test Dhcpd_140.stmt_option get "option special-option code 25 = integer 32;" =
3b98a7
+  { "rfc-code"
3b98a7
+    { "label" = "special-option" }
3b98a7
+    { "code" = "25" }
3b98a7
+    { "type" = "integer 32" }
3b98a7
+  }
3b98a7
+
3b98a7
+
3b98a7
+test Dhcpd_140.stmt_option get "option special-option code 25 = array of integer 32;" =
3b98a7
+  { "rfc-code"
3b98a7
+    { "label" = "special-option" }
3b98a7
+    { "code" = "25" }
3b98a7
+    { "type" = "array of integer 32" }
3b98a7
+  }
3b98a7
+
3b98a7
+
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "authoritative;
3b98a7
+log-facility local7;
3b98a7
+ddns-update-style none;
3b98a7
+default-lease-time 21600;
3b98a7
+max-lease-time 43200;
3b98a7
+
3b98a7
+# Additional options for VOIP
3b98a7
+option voip-boot-server code 66 = string;
3b98a7
+option voip-vlan-id code 128 = string;
3b98a7
+" =
3b98a7
+  { "authoritative" }
3b98a7
+  { "log-facility" = "local7" }
3b98a7
+  { "ddns-update-style" = "none" }
3b98a7
+  { "default-lease-time" = "21600" }
3b98a7
+  { "max-lease-time" = "43200"
3b98a7
+    { "#comment" = "Additional options for VOIP" }
3b98a7
+  }
3b98a7
+  { "rfc-code"
3b98a7
+    { "label" = "voip-boot-server" }
3b98a7
+    { "code" = "66" }
3b98a7
+    { "type" = "string" }
3b98a7
+  }
3b98a7
+  { "rfc-code"
3b98a7
+    { "label" = "voip-vlan-id" }
3b98a7
+    { "code" = "128" }
3b98a7
+    { "type" = "string" }
3b98a7
+  }
3b98a7
+
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "
3b98a7
+option domain-name-servers 10.1.1.1, 10.11.2.1, 10.1.3.1;
3b98a7
+next-server 10.1.1.1;
3b98a7
+
3b98a7
+failover peer \"redondance01\" {
3b98a7
+         primary;
3b98a7
+         address 10.1.1.1;
3b98a7
+         port 647;
3b98a7
+         peer address 10.1.1.1;
3b98a7
+         peer port 647;
3b98a7
+         max-response-delay 20;
3b98a7
+         max-unacked-updates 10;
3b98a7
+         mclt 3600;         #comment.
3b98a7
+         split 128;         #comment.
3b98a7
+         load balance max seconds 3;
3b98a7
+       }
3b98a7
+" =
3b98a7
+  {  }
3b98a7
+  { "option"
3b98a7
+    { "domain-name-servers"
3b98a7
+      { "arg" = "10.1.1.1" }
3b98a7
+      { "arg" = "10.11.2.1" }
3b98a7
+      { "arg" = "10.1.3.1" }
3b98a7
+    }
3b98a7
+  }
3b98a7
+  { "next-server" = "10.1.1.1" }
3b98a7
+  { "failover peer" = "redondance01"
3b98a7
+    { "primary" }
3b98a7
+    { "address" = "10.1.1.1" }
3b98a7
+    { "port" = "647" }
3b98a7
+    { "peer address" = "10.1.1.1" }
3b98a7
+    { "peer port" = "647" }
3b98a7
+    { "max-response-delay" = "20" }
3b98a7
+    { "max-unacked-updates" = "10" }
3b98a7
+    { "mclt" = "3600"
3b98a7
+      { "#comment" = "comment." }
3b98a7
+    }
3b98a7
+    { "split" = "128"
3b98a7
+      { "#comment" = "comment." }
3b98a7
+    }
3b98a7
+    { "load balance max seconds" = "3" }
3b98a7
+  }
3b98a7
+
3b98a7
+
3b98a7
+(* test get and put for record types *)
3b98a7
+let record_test = "option test_records code 123 = { string, ip-address, integer 32, ip6-address, domain-list };"
3b98a7
+
3b98a7
+test Dhcpd_140.lns get record_test =
3b98a7
+ { "rfc-code"
3b98a7
+   { "label" = "test_records" }
3b98a7
+   { "code" = "123" }
3b98a7
+     { "record"
3b98a7
+        { "1" = "string" }
3b98a7
+        { "2" = "ip-address" }
3b98a7
+        { "3" = "integer 32" }
3b98a7
+        { "4" = "ip6-address" }
3b98a7
+        { "5" = "domain-list" }
3b98a7
+     }
3b98a7
+ }
3b98a7
+
3b98a7
+test Dhcpd_140.lns put record_test after set "/rfc-code[1]/code" "124" = 
3b98a7
+  "option test_records code 124 = { string, ip-address, integer 32, ip6-address, domain-list };"
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "
3b98a7
+option CallManager code 150 = ip-address;
3b98a7
+option slp-directory-agent true 10.1.1.1, 10.2.2.2;
3b98a7
+option slp-service-scope true \"SLP-GLOBAL\";
3b98a7
+option nds-context \"EXAMPLE\";
3b98a7
+option nds-tree-name \"EXAMPLE\";
3b98a7
+" =
3b98a7
+  {  }
3b98a7
+  { "rfc-code"
3b98a7
+    { "label" = "CallManager" }
3b98a7
+    { "code" = "150" }
3b98a7
+    { "type" = "ip-address" }
3b98a7
+  }
3b98a7
+  { "option"
3b98a7
+    { "slp-directory-agent" = "true"
3b98a7
+      { "arg" = "10.1.1.1" }
3b98a7
+      { "arg" = "10.2.2.2" }
3b98a7
+    }
3b98a7
+  }
3b98a7
+  { "option"
3b98a7
+    { "slp-service-scope" = "true"
3b98a7
+      { "arg" = "SLP-GLOBAL" }
3b98a7
+    }
3b98a7
+  }
3b98a7
+  { "option"
3b98a7
+    { "nds-context"
3b98a7
+      { "arg" = "EXAMPLE" }
3b98a7
+    }
3b98a7
+  }
3b98a7
+  { "option"
3b98a7
+    { "nds-tree-name"
3b98a7
+      { "arg" = "EXAMPLE" }
3b98a7
+    }
3b98a7
+  }
3b98a7
+
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "option voip-vlan-id \"VLAN=1234;\";" =
3b98a7
+  { "option"
3b98a7
+    { "voip-vlan-id"
3b98a7
+      { "arg" = "VLAN=1234;" }
3b98a7
+    }
3b98a7
+  }
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "option domain-name \"x.example.com y.example.com z.example.com\";" =
3b98a7
+  { "option"
3b98a7
+    { "domain-name"
3b98a7
+      { "arg" = "x.example.com y.example.com z.example.com" }
3b98a7
+    }
3b98a7
+  }
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "include \"/etc/dhcpd.master\";" =
3b98a7
+  { "include" = "/etc/dhcpd.master" }
3b98a7
+
3b98a7
+test Dhcpd_140.lns put "\n" after set "/include" "/etc/dhcpd.master" =
3b98a7
+  "\ninclude \"/etc/dhcpd.master\";\n"
3b98a7
+
3b98a7
+test Dhcpd_140.fct_args get "(option dhcp-client-identifier, 1, 3)" =
3b98a7
+  { "args"
3b98a7
+    { "arg" = "option dhcp-client-identifier" }
3b98a7
+    { "arg" = "1" }
3b98a7
+    { "arg" = "3" }
3b98a7
+  }
3b98a7
+
3b98a7
+test Dhcpd_140.stmt_match get "match if substring (option dhcp-client-identifier, 1, 3) = \"RAS\";" =
3b98a7
+  { "match"
3b98a7
+    { "function" = "substring"
3b98a7
+      { "args"
3b98a7
+        { "arg" = "option dhcp-client-identifier" }
3b98a7
+        { "arg" = "1" }
3b98a7
+        { "arg" = "3" }
3b98a7
+      }
3b98a7
+    }
3b98a7
+    { "value" = "RAS" }
3b98a7
+  }
3b98a7
+
3b98a7
+test Dhcpd_140.stmt_match get "match if suffix (option dhcp-client-identifier, 4) = \"RAS\";" =
3b98a7
+  { "match"
3b98a7
+    { "function" = "suffix"
3b98a7
+      { "args"
3b98a7
+        { "arg" = "option dhcp-client-identifier" }
3b98a7
+        { "arg" = "4" }
3b98a7
+      }
3b98a7
+    }
3b98a7
+    { "value" = "RAS" }
3b98a7
+  }
3b98a7
+
3b98a7
+test Dhcpd_140.stmt_match get "match if option vendor-class-identifier=\"RAS\";" =
3b98a7
+  { "match"
3b98a7
+    { "option" = "vendor-class-identifier"
3b98a7
+      { "value" = "RAS" }
3b98a7
+    }
3b98a7
+  }
3b98a7
+
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "match pick-first-value (option dhcp-client-identifier, hardware);" =
3b98a7
+  { "match"
3b98a7
+    { "function" = "pick-first-value"
3b98a7
+      { "args"
3b98a7
+        { "arg" = "option dhcp-client-identifier" }
3b98a7
+        { "arg" = "hardware"  }
3b98a7
+      }
3b98a7
+    }
3b98a7
+  }
3b98a7
+
3b98a7
+test Dhcpd_140.fct_args get "(16, 32, \"\", substring(hardware, 0, 4))" =
3b98a7
+  { "args"
3b98a7
+    { "arg" = "16" }
3b98a7
+    { "arg" = "32" }
3b98a7
+    { "arg" = "\"\"" }
3b98a7
+    { "arg" = "substring(hardware, 0, 4)" }
3b98a7
+  }
3b98a7
+
3b98a7
+test Dhcpd_140.stmt_match get "match if binary-to-ascii(16, 32, \"\", substring(hardware, 0, 4)) = \"1525400\";" =
3b98a7
+  { "match"
3b98a7
+    { "function" = "binary-to-ascii"
3b98a7
+      { "args"
3b98a7
+        { "arg" = "16" }
3b98a7
+        { "arg" = "32" }
3b98a7
+        { "arg" = "\"\"" }
3b98a7
+        { "arg" = "substring(hardware, 0, 4)" }
3b98a7
+      }
3b98a7
+    }
3b98a7
+    { "value" = "1525400" }
3b98a7
+  }
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "subclass allocation-class-1 1:8:0:2b:4c:39:ad;" =
3b98a7
+  { "subclass"
3b98a7
+    { "name" = "allocation-class-1" }
3b98a7
+    { "value" = "1:8:0:2b:4c:39:ad" }
3b98a7
+  }
3b98a7
+
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "subclass \"allocation-class-1\" 1:8:0:2b:4c:39:ad;" =
3b98a7
+  { "subclass"
3b98a7
+    { "name" = "allocation-class-1" }
3b98a7
+    { "value" = "1:8:0:2b:4c:39:ad" }
3b98a7
+  }
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "subclass \"quoted class\" \"quoted value\";" =
3b98a7
+  { "subclass"
3b98a7
+    { "name" = "quoted class" }
3b98a7
+    { "value" = "quoted value" }
3b98a7
+  }
3b98a7
+
3b98a7
+
3b98a7
+(* overall test *)
3b98a7
+test Dhcpd_140.lns put conf after rm "/x" = conf
3b98a7
+
3b98a7
+(* bug #293: primary should support argument *)
3b98a7
+let input293 = "zone EXAMPLE.ORG. {
3b98a7
+  primary 127.0.0.1;
3b98a7
+}"
3b98a7
+
3b98a7
+test Dhcpd_140.lns get input293 = 
3b98a7
+  { "zone" = "EXAMPLE.ORG."
3b98a7
+    { "primary" = "127.0.0.1" }
3b98a7
+  }
3b98a7
+
3b98a7
+(* bug #311: filename should be quoted *)
3b98a7
+let input311 = "subnet 172.16.0.0 netmask 255.255.255.0 {
3b98a7
+filename \"pxelinux.0\";
3b98a7
+}"
3b98a7
+
3b98a7
+test Dhcpd_140.lns put "subnet 172.16.0.0 netmask 255.255.255.0 {
3b98a7
+}" after
3b98a7
+  set "subnet/filename" "pxelinux.0" = input311
3b98a7
+
3b98a7
+(* GH issue #34: support conditional structures *)
3b98a7
+let gh34_empty = "if exists dhcp-parameter-request-list {
3b98a7
+}\n"
3b98a7
+
3b98a7
+test Dhcpd_140.lns get gh34_empty =
3b98a7
+  { "@if" = "exists dhcp-parameter-request-list" }
3b98a7
+
3b98a7
+let gh34_empty_multi = "subnet 192.168.100.0 netmask 255.255.255.0 {
3b98a7
+ if true {
3b98a7
+ } elsif false {
3b98a7
+ } else {
3b98a7
+ }
3b98a7
+}\n"
3b98a7
+
3b98a7
+test Dhcpd_140.lns get gh34_empty_multi =
3b98a7
+  { "subnet"
3b98a7
+    { "network" = "192.168.100.0" }
3b98a7
+    { "netmask" = "255.255.255.0" }
3b98a7
+    { "@if" = "true"
3b98a7
+      { "@elsif" = "false" }
3b98a7
+      { "@else" } }
3b98a7
+  }
3b98a7
+
3b98a7
+let gh34_simple = "if exists dhcp-parameter-request-list {
3b98a7
+  default-lease-time 600;
3b98a7
+  } else {
3b98a7
+default-lease-time 200;
3b98a7
+}\n"
3b98a7
+
3b98a7
+test Dhcpd_140.lns get gh34_simple =
3b98a7
+  { "@if" = "exists dhcp-parameter-request-list"
3b98a7
+    { "default-lease-time" = "600" }
3b98a7
+    { "@else"
3b98a7
+      { "default-lease-time" = "200" } } }
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "omapi-key fookey;" =
3b98a7
+  { "omapi-key" = "fookey" }
3b98a7
+
3b98a7
+(* almost all DHCP groups should support braces starting on the next line *)
3b98a7
+test Dhcpd_140.lns get "class introduction
3b98a7
+{
3b98a7
+}" =
3b98a7
+  { "class" = "introduction" }
3b98a7
+
3b98a7
+(* equals should work the same *)
3b98a7
+test Dhcpd_140.lns get "option test_records code 123 =
3b98a7
+                             string;" =
3b98a7
+ { "rfc-code"
3b98a7
+   { "label" = "test_records" }
3b98a7
+   { "code" = "123" }
3b98a7
+   { "type" = "string" }
3b98a7
+ }
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "deny members of \"Are things like () allowed?\";" =
3b98a7
+  { "deny-members-of" = "Are things like () allowed?" }
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "deny unknown clients;" =
3b98a7
+  { "deny" = "unknown clients" }
3b98a7
+test Dhcpd_140.lns get "deny known-clients;" =
3b98a7
+  { "deny" = "known-clients" }
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "set ClientMac = binary-to-ascii(16, 8, \":\" , substring(hardware, 1, 6));" =
3b98a7
+  { "set" = "ClientMac"
3b98a7
+    { "value" = "binary-to-ascii(16, 8, \":\" , substring(hardware, 1, 6))" }
3b98a7
+  }
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "set myvariable = foo;" =
3b98a7
+  { "set" = "myvariable"
3b98a7
+    { "value" = "foo" }
3b98a7
+  }
3b98a7
+
3b98a7
+test Dhcpd_140.stmt_hardware get "hardware fddi 00:01:02:03:04:05;" =
3b98a7
+  { "hardware"
3b98a7
+    { "type" = "fddi" }
3b98a7
+    { "address" = "00:01:02:03:04:05" }
3b98a7
+  }
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "on commit
3b98a7
+{
3b98a7
+  set test = thing;
3b98a7
+}" =
3b98a7
+  { "on" = "commit"
3b98a7
+    { "set" = "test"
3b98a7
+      { "value" = "thing" }
3b98a7
+    }
3b98a7
+  }
3b98a7
+
3b98a7
+(* key block get/put/set test *)
3b98a7
+let key_tests = "key sample {
3b98a7
+    algorithm hmac-md5;
3b98a7
+    secret \"secret==\";
3b98a7
+}
3b98a7
+
3b98a7
+key \"interesting\" { };
3b98a7
+
3b98a7
+key \"third key\" {
3b98a7
+    secret \"two==\";
3b98a7
+}"
3b98a7
+
3b98a7
+test Dhcpd_140.lns get key_tests =
3b98a7
+  { "key_block" = "sample"
3b98a7
+    { "algorithm"  = "hmac-md5" }
3b98a7
+    { "secret" = "secret==" }
3b98a7
+  }
3b98a7
+  { "key_block" = "interesting" }
3b98a7
+  { "key_block" = "third key"
3b98a7
+    { "secret" = "two==" }
3b98a7
+  }
3b98a7
+
3b98a7
+test Dhcpd_140.lns put key_tests after set "/key_block[1]" "sample2" =
3b98a7
+  "key sample2 {
3b98a7
+    algorithm hmac-md5;
3b98a7
+    secret \"secret==\";
3b98a7
+}
3b98a7
+
3b98a7
+key \"interesting\" { };
3b98a7
+
3b98a7
+key \"third key\" {
3b98a7
+    secret \"two==\";
3b98a7
+}"
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "group \"hello\" { }" =
3b98a7
+  { "group" = "hello" }
3b98a7
+
3b98a7
+test Dhcpd_140.lns get "class \"testing class with spaces and quotes and ()\" {}" =
3b98a7
+  { "class" = "testing class with spaces and quotes and ()" }
3b98a7
diff --git a/tests/Makefile.am b/tests/Makefile.am
3b98a7
index 387ac7d2..315cac9c 100644
3b98a7
--- a/tests/Makefile.am
3b98a7
+++ b/tests/Makefile.am
3b98a7
@@ -58,6 +58,7 @@ lens_tests =			\
3b98a7
   lens-device_map.sh    \
3b98a7
   lens-dhclient.sh		\
3b98a7
   lens-dhcpd.sh		\
3b98a7
+  lens-dhcpd_140.sh		\
3b98a7
   lens-dns_zone.sh		\
3b98a7
   lens-dnsmasq.sh		\
3b98a7
   lens-dovecot.sh		\
3b98a7
-- 
c941cc
2.24.1
3b98a7