Bump to 1.14.1
[platform/upstream/augeas.git] / lenses / dhcpd.aug
1 (*
2 Module: Dhcpd
3   BIND dhcp 3 server configuration module for Augeas
4
5 Author: Francis Giraldeau <francis.giraldeau@usherbrooke.ca>
6
7 About: Reference
8   Reference: manual of dhcpd.conf and dhcp-eval
9   Follow dhclient module for tree structure
10
11 About: License
12     This file is licensed under the GPL.
13
14 About: Lens Usage
15   Sample usage of this lens in augtool
16
17   Directive without argument.
18   Set this dhcpd server authoritative on the domain.
19   > clear /files/etc/dhcp3/dhcpd.conf/authoritative
20
21   Directives with integer or string argument.
22   Set max-lease-time to one hour:
23   > set /files/etc/dhcp3/dhcpd.conf/max-lease-time 3600
24
25   Options are declared as a list, even for single values.
26   Set the domain of the network:
27   > set /files/etc/dhcp3/dhcpd.conf/option/domain-name/arg example.org
28   Set two name server:
29   > set /files/etc/dhcp3/dhcpd.conf/option/domain-name-servers/arg[1] foo.example.org
30   > set /files/etc/dhcp3/dhcpd.conf/option/domain-name-servers/arg[2] bar.example.org
31
32   Create the subnet 172.16.0.1 with 10 addresses:
33   > clear /files/etc/dhcp3/dhcpd.conf/subnet[last() + 1]
34   > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/network 172.16.0.0
35   > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/netmask 255.255.255.0
36   > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/range/from 172.16.0.10
37   > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/range/to 172.16.0.20
38
39   Create a new group "foo" with one static host. Nodes type and address are ordered.
40   > ins group after /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/*[last()]
41   > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[last()]/host foo
42   > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[host='foo']/host/hardware/type "ethernet"
43   > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[host='foo']/host/hardware/address "00:00:00:aa:bb:cc"
44   > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[host='foo']/host/fixed-address 172.16.0.100
45
46 About: Configuration files
47   This lens applies to /etc/dhcpd3/dhcpd.conf. See <filter>.
48 *)
49
50 module Dhcpd =
51
52 autoload xfm
53
54 (************************************************************************
55  *                           USEFUL PRIMITIVES
56  *************************************************************************)
57 let dels (s:string)   = del s s
58 let eol               = Util.eol
59 let comment           = Util.comment
60 let empty             = Util.empty
61 let indent            = Util.indent
62 let eos               = comment?
63
64 (* Define separators *)
65 let sep_spc           = del /[ \t]+/ " "
66 let sep_osp           = del /[ \t]*/ ""
67 let sep_scl           = del /[ \t]*;([ \t]*\n)*/ ";\n"
68 let sep_obr           = del /[ \t\n]*\{([ \t]*\n)*/ " {\n"
69 let sep_cbr           = del /[ \t]*\}([ \t]*\n)*/ "}\n"
70 let sep_com           = del /[ \t\n]*,[ \t\n]*/ ", "
71 let sep_slh           = del "\/" "/"
72 let sep_col           = del ":" ":"
73 let sep_eq            = del /[ \t\n]*=[ \t\n]*/ "="
74 let scl               = del ";" ";"
75
76 (* Define basic types *)
77 let word              = /[A-Za-z0-9_.-]+(\[[0-9]+\])?/
78 let ip                = Rx.ipv4
79
80 (* Define fields *)
81
82 (* adapted from sysconfig.aug *)
83   (* Chars allowed in a bare string *)
84   let bchar = /[^ \t\n"'\\{}#,()\/]|\\\\./
85   let qchar = /["']/  (* " *)
86
87   (* We split the handling of right hand sides into a few cases:
88    *   bare  - strings that contain no spaces, optionally enclosed in
89    *           single or double quotes
90    *   dquot - strings that contain at least one space, apostrophe or slash
91    *           which must be enclosed in double quotes
92    *   squot - strings that contain an unescaped double quote
93    *)
94   let bare = del qchar? "" . store (bchar+) . del qchar? ""
95   let quote = Quote.do_quote (store (bchar* . /[ \t'\/]/ . bchar*)+)
96   let dquote = Quote.do_dquote (store (bchar+))
97   (* these two are for special cases.  bare_to_scl is for any bareword that is
98    * space or semicolon terminated.  dquote_any allows almost any character in
99    * between the quotes. *)
100   let bare_to_scl = Quote.do_dquote_opt (store /[^" \t\n;]+/)
101   let dquote_any = Quote.do_dquote (store /[^"\n]*[ \t]+[^"\n]*/)
102
103 let sto_to_spc        = store /[^\\#,;\{\}" \t\n]+|"[^\\#"\n]+"/
104 let sto_to_scl        = store /[^ \t;][^;\n=]+[^ \t;]|[^ \t;=]+/
105
106 let sto_number        = store /[0-9][0-9]*/
107
108 (************************************************************************
109  *                         NO ARG STATEMENTS
110  *************************************************************************)
111
112 let stmt_noarg_re     =   "authoritative"
113                         | "primary"
114                         | "secondary"
115
116 let stmt_noarg        = [ indent
117                         . key stmt_noarg_re
118                         . sep_scl
119                         . eos ]
120
121 (************************************************************************
122  *                         INT ARG STATEMENTS
123  *************************************************************************)
124
125 let stmt_integer_re   = "default-lease-time"
126                       | "max-lease-time"
127                       | "min-lease-time"
128                       | /lease[ ]+limit/
129                       | "port"
130                       | /peer[ ]+port/
131                       | "max-response-delay"
132                       | "max-unacked-updates"
133                       | "mclt"
134                       | "split"
135                       | /load[ ]+balance[ ]+max[ ]+seconds/
136                       | "max-lease-misbalance"
137                       | "max-lease-ownership"
138                       | "min-balance"
139                       | "max-balance"
140                       | "adaptive-lease-time-threshold"
141                       | "dynamic-bootp-lease-length"
142                       | "local-port"
143                       | "min-sec"
144                       | "omapi-port"
145                       | "ping-timeout"
146                       | "remote-port"
147
148 let stmt_integer      = [ indent
149                         . key stmt_integer_re
150                         . sep_spc
151                         . sto_number
152                         . sep_scl
153                         . eos ]
154
155 (************************************************************************
156  *                         STRING ARG STATEMENTS
157  *************************************************************************)
158
159 let stmt_string_re    = "ddns-update-style"
160                       | "ddns-updates"
161                       | "ddns-hostname"
162                       | "ddns-domainname"
163                       | "ddns-rev-domainname"
164                       | "log-facility"
165                       | "server-name"
166                       | "fixed-address"
167                       | /failover[ ]+peer/
168                       | "use-host-decl-names"
169                       | "next-server"
170                       | "address"
171                       | /peer[ ]+address/
172                       | "type"
173                       | "file"
174                       | "algorithm"
175                       | "secret"
176                       | "key"
177                       | "include"
178                       | "hba"
179                       | "boot-unknown-clients"
180                       | "db-time-format"
181                       | "do-forward-updates"
182                       | "dynamic-bootp-lease-cutoff"
183                       | "get-lease-hostnames"
184                       | "infinite-is-reserved"
185                       | "lease-file-name"
186                       | "local-address"
187                       | "one-lease-per-client"
188                       | "pid-file-name"
189                       | "ping-check"
190                       | "server-identifier"
191                       | "site-option-space"
192                       | "stash-agent-options"
193                       | "update-conflict-detection"
194                       | "update-optimization"
195                       | "update-static-leases"
196                       | "use-host-decl-names"
197                       | "use-lease-addr-for-default-route"
198                       | "vendor-option-space"
199                       | "primary"
200                       | "omapi-key"
201
202 let stmt_string_tpl (kw:regexp) (l:lens) = [ indent
203                         . key kw
204                         . sep_spc
205                         . l
206                         . sep_scl
207                         . eos ]
208
209 let stmt_string  = stmt_string_tpl stmt_string_re bare
210                  | stmt_string_tpl stmt_string_re quote
211                  | stmt_string_tpl "filename" dquote
212
213 (************************************************************************
214  *                         RANGE STATEMENTS
215  *************************************************************************)
216
217 let stmt_range        = [ indent
218                         . key "range"
219                         . sep_spc
220                         . [ label "flag" . store /dynamic-bootp/ . sep_spc ]?
221                         . [ label "from" . store ip . sep_spc ]?
222                         . [ label "to" . store ip ]
223                         . sep_scl
224                         . eos ]
225
226 (************************************************************************
227  *                         HARDWARE STATEMENTS
228  *************************************************************************)
229
230 let stmt_hardware     = [ indent
231                         . key "hardware"
232                         . sep_spc
233                         . [ label "type" . store /ethernet|tokenring|fddi/ ]
234                         . sep_spc
235                         . [ label "address" . store /[a-fA-F0-9:-]+/ ]
236                         . sep_scl
237                         . eos ]
238
239 (************************************************************************
240  *                         SET STATEMENTS
241  *************************************************************************)
242 let stmt_set          = [ indent
243                         . key "set"
244                         . sep_spc
245                         . store word
246                         . sep_spc
247                         . Sep.equal
248                         . sep_spc
249                         . [ label "value" . sto_to_scl ]
250                         . sep_scl
251                         . eos ]
252
253 (************************************************************************
254  *                         OPTION STATEMENTS
255  *************************************************************************)
256 (* The general case is considering options as a list *)
257
258
259 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_.-]+)/
260
261 let stmt_option_list  = ([ label "arg" . bare ] | [ label "arg" . quote ])
262                         . ( sep_com . ([ label "arg" . bare ] | [ label "arg" . quote ]))*
263
264 let del_trail_spc = del /[ \t\n]*/ ""
265
266 let stmt_record = counter "record" . Util.del_str "{"
267                 . sep_spc
268                 . ([seq "record" . store stmt_option_value . sep_com]*
269                 .  [seq "record" . store stmt_option_value . del_trail_spc])?
270                 . Util.del_str "}"
271
272 let stmt_option_code  = [ label "label" . store word . sep_spc ]
273                         . [ key "code" . sep_spc . store word ]
274                         . sep_eq
275                         . ([ label "type" . store stmt_option_value ]
276                           |[ label "record" . stmt_record ]) 
277
278 let stmt_option_basic = [ key word . sep_spc . stmt_option_list ]
279 let stmt_option_extra = [ key word . sep_spc . store /true|false/ . sep_spc . stmt_option_list ]
280
281 let stmt_option_body = stmt_option_basic | stmt_option_extra
282
283 let stmt_option1  = [ indent
284                         . key "option"
285                         . sep_spc
286                         . stmt_option_body
287                         . sep_scl
288                         . eos ]
289
290 let stmt_option2  = [ indent
291                         . dels "option" . label "rfc-code"
292                         . sep_spc
293                         . stmt_option_code
294                         . sep_scl
295                         . eos ]
296
297 let stmt_option = stmt_option1 | stmt_option2
298
299 (************************************************************************
300  *                         SUBCLASS STATEMENTS
301  *************************************************************************)
302 (* this statement is not well documented in the manual dhcpd.conf
303    we support basic use case *)
304
305 let stmt_subclass = [ indent . key "subclass" . sep_spc 
306                       . ( [ label "name" .  bare_to_scl ]|[ label "name" .  dquote_any ] )
307                       . sep_spc 
308                       . ( [ label "value" . bare_to_scl ]|[ label "value" . dquote_any ] ) 
309                       . sep_scl 
310                       . eos ]
311
312
313 (************************************************************************
314  *                         ALLOW/DENY STATEMENTS
315  *************************************************************************)
316 (* We have to use special key for allow/deny members of
317   to avoid ambiguity in the put direction *)
318
319 let allow_deny_re     = /unknown(-|[ ]+)clients/
320                       | /known(-|[ ]+)clients/
321                       | /all[ ]+clients/
322                       | /dynamic[ ]+bootp[ ]+clients/
323                       | /authenticated[ ]+clients/
324                       | /unauthenticated[ ]+clients/
325                       | "bootp"
326                       | "booting"
327                       | "duplicates"
328                       | "declines"
329                       | "client-updates"
330                       | "leasequery"
331
332 let stmt_secu_re      = "allow"
333                       | "deny"
334
335 let del_allow = del /allow[ ]+members[ ]+of/ "allow members of"
336 let del_deny  = del /deny[ \t]+members[ \t]+of/ "deny members of"
337
338 (* bare is anything but whitespace, quote marks or semicolon.
339  * technically this should be locked down to mostly alphanumerics, but the
340  * idea right now is just to make things work.  Also ideally I would use
341  * dquote_space but I had a whale of a time with it.  It doesn't like
342  * semicolon termination and my attempts to fix that led me to 3 hours of
343  * frustration and back to this :)
344  *)
345 let stmt_secu_tpl (l:lens) (s:string) =
346                   [ indent . l . sep_spc . label s . bare_to_scl . sep_scl . eos ] |
347                   [ indent . l . sep_spc . label s . dquote_any . sep_scl . eos ]
348
349
350 let stmt_secu         = [ indent . key stmt_secu_re . sep_spc .
351                           store allow_deny_re . sep_scl . eos ] |
352                         stmt_secu_tpl del_allow "allow-members-of" |
353                         stmt_secu_tpl del_deny "deny-members-of"
354
355 (************************************************************************
356  *                         MATCH STATEMENTS
357  *************************************************************************)
358
359 let sto_com = /[^ \t\n,\(\)][^,\(\)]*[^ \t\n,\(\)]|[^ \t\n,\(\)]+/ | word . /[ \t]*\([^)]*\)/
360 (* this is already the most complicated part of this module and it's about to
361  * get worse.  match statements can be way more complicated than this
362  *
363  * examples:
364  *      using or:
365  *      match if ((option vendor-class-identifier="Banana Bready") or (option vendor-class-identifier="Cherry Sunfire"));
366  *      unneeded parenthesis:
367  *      match if (option vendor-class-identifier="Hello");
368  *
369  *      and of course the fact that the above two rules used one of infinately
370  *      many potential options instead of a builtin function.
371  *)
372 (* sto_com doesn't support quoted strings as arguments.  It also doesn't
373    support single arguments (needs to match a comma) It will need to be
374    updated for lcase, ucase and log to be workable.
375
376    it also doesn't support no arguments, so gethostbyname() doesn't work.
377
378    option and config-option are considered operators.  They should be matched
379    in stmt_entry but also available under "match if" and "if" conditionals
380    leased-address, host-decl-name, both take no args and return a value.  We
381    might need to treat them as variable names in the parser.
382
383    things like this may be near-impossible to parse even with recursion
384    because we have no way of knowing when or if a subfunction takes arguments
385    set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
386
387    even if we could parse it, they could get arbitrarily complicated like:
388    binary-to-ascii(16, 8, ":", substring(hardware, 1, 6) and substring(hardware, 2, 3));
389
390    so at some point we may need to programmatically knock it off and tell
391    people to put weird stuff in an include file that augeas doesn't parse.
392
393    the other option is to change the API to not parse the if statement at all,
394    just pull in the conditional as a string.
395  *)
396
397 let fct_re = "substring" | "binary-to-ascii" | "suffix" | "lcase" | "ucase"
398              | "gethostbyname" | "packet"
399              | "concat" | "reverse" | "encode-int"
400              | "extract-int" | "lease-time" | "client-state" | "exists" | "known" | "static"
401              | "pick-first-value" | "log" | "execute"
402
403 (* not needs to be different because it's a negation of whatever happens next *)
404 let op_re = "~="|"="|"~~"|"and"|"or"
405
406 let fct_args = [ label "args" . dels "(" . sep_osp .
407                  ([ label "arg" . store sto_com ] . [ label "arg" . sep_com . store sto_com ]+) .
408                         sep_osp . dels ")" ]
409
410 let stmt_match_ifopt = [ dels "if" . sep_spc . key "option" . sep_spc . store word .
411                       sep_eq . ([ label "value" . bare_to_scl ]|[ label "value" . dquote_any ]) ]
412
413 let stmt_match_func = [ store fct_re . sep_osp . label "function" . fct_args ] .
414                       sep_eq . ([ label "value" . bare_to_scl ]|[ label "value" . dquote_any ])
415
416 let stmt_match_pfv = [ label "function" . store "pick-first-value" . sep_spc .
417                        dels "(" . sep_osp .
418                        [ label "args" .
419                          [ label "arg" . store sto_com ] .
420                          [ sep_com . label "arg" . store sto_com ]+ ] .
421                        dels ")" ]
422
423 let stmt_match_tpl (l:lens) = [ indent . key "match" . sep_spc . l . sep_scl . eos ]
424
425 let stmt_match = stmt_match_tpl (dels "if" . sep_spc . stmt_match_func | stmt_match_pfv | stmt_match_ifopt)
426
427 (************************************************************************
428  *                         BLOCK STATEMENTS
429  *************************************************************************)
430 (* Blocks doesn't support comments at the end of the closing bracket *)
431
432 let stmt_entry        =   stmt_secu
433                         | stmt_option
434                         | stmt_hardware
435                         | stmt_range
436                         | stmt_string
437                         | stmt_integer
438                         | stmt_noarg
439                         | stmt_match
440                         | stmt_subclass
441                         | stmt_set
442                         | empty
443                         | comment
444
445 let stmt_block_noarg_re = "pool" | "group"
446
447 let stmt_block_noarg (body:lens)
448                         = [ indent
449                         . key stmt_block_noarg_re
450                         . sep_obr
451                         . body*
452                         . sep_cbr ]
453
454 let stmt_block_arg_re = "host"
455                       | "class"
456                       | "shared-network"
457                       | /failover[ ]+peer/
458                       | "zone"
459                       | "group"
460                       | "on"
461
462 let stmt_block_arg (body:lens)
463                       = ([ indent . key stmt_block_arg_re . sep_spc . dquote_any . sep_obr . body* . sep_cbr ]
464                          |[ indent . key stmt_block_arg_re . sep_spc . bare_to_scl . sep_obr . body* . sep_cbr ]
465                          |[ indent . del /key/ "key" . label "key_block" . sep_spc . dquote_any . sep_obr . body* . sep_cbr . del /(;([ \t]*\n)*)?/ ""  ]
466                          |[ indent . del /key/ "key" . label "key_block" . sep_spc . bare_to_scl . sep_obr . body* . sep_cbr . del /(;([ \t]*\n)*)?/ "" ])
467
468 let stmt_block_subnet (body:lens)
469                       = [ indent
470                         . key "subnet"
471                         . sep_spc
472                         . [ label "network" . store ip ]
473                         . sep_spc
474                         . [ key "netmask" . sep_spc . store ip ]
475                         . sep_obr
476                         . body*
477                         . sep_cbr ]
478
479 let conditional (body:lens) =
480      let condition         = /[^{ \r\t\n][^{\n]*[^{ \r\t\n]|[^{ \t\n\r]/
481   in let elsif = [ indent
482                  . Build.xchgs "elsif" "@elsif"
483                  . sep_spc
484                  . store condition
485                  . sep_obr
486                  . body*
487                  . sep_cbr ]
488   in let else = [  indent
489                  . Build.xchgs "else" "@else"
490                  . sep_obr
491                  . body*
492                  . sep_cbr ]
493   in [ indent
494      . Build.xchgs "if" "@if"
495      . sep_spc
496      . store condition
497      . sep_obr
498      . body*
499      . sep_cbr
500      . elsif*
501      . else? ]
502
503
504 let all_block (body:lens) =
505     let lns1 = stmt_block_subnet body in
506     let lns2 = stmt_block_arg body in
507     let lns3 = stmt_block_noarg body in
508     let lns4 = conditional body in
509     (lns1 | lns2 | lns3 | lns4 | stmt_entry)
510
511 let rec lns_staging = stmt_entry|all_block lns_staging
512 let lns = (lns_staging)*
513
514 let filter = incl "/etc/dhcp3/dhcpd.conf"
515            . incl "/etc/dhcp/dhcpd.conf"
516            . incl "/etc/dhcpd.conf"
517
518 let xfm = transform lns filter