summaryrefslogtreecommitdiff
path: root/keama
diff options
context:
space:
mode:
Diffstat (limited to 'keama')
-rw-r--r--keama/.gitignore1
-rw-r--r--keama/ChangeLog.md18
-rw-r--r--keama/Makefile.am5
-rw-r--r--keama/Makefile.in721
-rw-r--r--keama/README.md53
-rw-r--r--keama/REAME.md53
-rw-r--r--keama/conflex.c1550
-rw-r--r--keama/confparse.c4992
-rw-r--r--keama/data.c1258
-rw-r--r--keama/data.h304
-rw-r--r--keama/dhctoken.h389
-rw-r--r--keama/doc.txt516
-rw-r--r--keama/eval.c2252
-rw-r--r--keama/json.c182
-rw-r--r--keama/keama.8104
-rw-r--r--keama/keama.c225
-rw-r--r--keama/keama.h470
-rw-r--r--keama/options.c1154
-rw-r--r--keama/parse.c6140
-rw-r--r--keama/print.c1490
-rw-r--r--keama/reduce.c1016
-rw-r--r--keama/tests/README42
-rw-r--r--keama/tests/badcasexsc.err7
-rw-r--r--keama/tests/badcasexsc.msg1
-rw-r--r--keama/tests/badclass.err12
-rw-r--r--keama/tests/badclass.msg1
-rw-r--r--keama/tests/badclass2.err12
-rw-r--r--keama/tests/badclass2.msg1
-rw-r--r--keama/tests/baddecl2array.err4
-rw-r--r--keama/tests/baddecl2array.msg1
-rw-r--r--keama/tests/baddecl2record.err4
-rw-r--r--keama/tests/baddecl2record.msg1
-rw-r--r--keama/tests/baddeclBt.err4
-rw-r--r--keama/tests/baddeclBt.msg1
-rw-r--r--keama/tests/baddefaultxsc.err7
-rw-r--r--keama/tests/baddefaultxsc.msg1
-rw-r--r--keama/tests/baddomain.notyet6
-rw-r--r--keama/tests/badduid.err4
-rw-r--r--keama/tests/badduid.msg1
-rw-r--r--keama/tests/badinclude.err3
-rw-r--r--keama/tests/badinclude.msg1
-rw-r--r--keama/tests/badoption66.err63
-rw-r--r--keama/tests/badoption66.msg1
-rw-r--r--keama/tests/badoptionD6.notyet4
-rw-r--r--keama/tests/badoptionDc4.notyet3
-rw-r--r--keama/tests/badoptionI4.err43
-rw-r--r--keama/tests/badoptionI4.msg1
-rw-r--r--keama/tests/badoptiond4.err45
-rw-r--r--keama/tests/badoptiond4.msg1
-rw-r--r--keama/tests/badstatusdir.err7
-rw-r--r--keama/tests/badstatusdir.msg1
-rw-r--r--keama/tests/badsubclass.err11
-rw-r--r--keama/tests/badsubclass.msg1
-rw-r--r--keama/tests/bintadx6.in69
-rw-r--r--keama/tests/bintadx6.out15
-rw-r--r--keama/tests/bootfilename4.in44
-rw-r--r--keama/tests/bootfilename4.out13
-rw-r--r--keama/tests/charcasedx4.in49
-rw-r--r--keama/tests/charcasedx4.out15
-rw-r--r--keama/tests/checkall.sh16
-rw-r--r--keama/tests/checkone.sh54
-rw-r--r--keama/tests/class4.in411
-rw-r--r--keama/tests/class4.out36
-rw-r--r--keama/tests/class4empty.in45
-rw-r--r--keama/tests/class4empty.out11
-rw-r--r--keama/tests/class6.in611
-rw-r--r--keama/tests/class6.out36
-rw-r--r--keama/tests/class6empty.in65
-rw-r--r--keama/tests/class6empty.out11
-rw-r--r--keama/tests/classbadmatch.err7
-rw-r--r--keama/tests/classbadmatch.msg1
-rw-r--r--keama/tests/classbadmatchif.err7
-rw-r--r--keama/tests/classbadmatchif.msg1
-rw-r--r--keama/tests/classinclass.err10
-rw-r--r--keama/tests/classinclass.msg1
-rw-r--r--keama/tests/concatdx4.in419
-rw-r--r--keama/tests/concatdx4.out48
-rw-r--r--keama/tests/concatnulldx4.in418
-rw-r--r--keama/tests/concatnulldx4.out33
-rw-r--r--keama/tests/configdata4.in426
-rw-r--r--keama/tests/configdata4.out74
-rw-r--r--keama/tests/dbtimeformat4.in46
-rw-r--r--keama/tests/dbtimeformat4.out10
-rw-r--r--keama/tests/dbtimeformat6.in66
-rw-r--r--keama/tests/dbtimeformat6.out10
-rw-r--r--keama/tests/ddnsupdstyle6.in612
-rw-r--r--keama/tests/ddnsupdstyle6.out29
-rw-r--r--keama/tests/defaultexpr6.in610
-rw-r--r--keama/tests/defaultexpr6.out25
-rw-r--r--keama/tests/denyunknown6.in615
-rw-r--r--keama/tests/denyunknown6.out32
-rw-r--r--keama/tests/docsis4.dir11
-rw-r--r--keama/tests/docsis6.dir27
-rw-r--r--keama/tests/duid2.err7
-rw-r--r--keama/tests/duid2.msg1
-rw-r--r--keama/tests/duiden6.in64
-rw-r--r--keama/tests/duiden6.out11
-rw-r--r--keama/tests/duidennoid.err5
-rw-r--r--keama/tests/duidennoid.msg1
-rw-r--r--keama/tests/duidennonum.err4
-rw-r--r--keama/tests/duidennonum.msg1
-rw-r--r--keama/tests/duidll6.in65
-rw-r--r--keama/tests/duidll6.out9
-rw-r--r--keama/tests/duidllbadtype.err4
-rw-r--r--keama/tests/duidllbadtype.msg1
-rw-r--r--keama/tests/duidllhw6.in66
-rw-r--r--keama/tests/duidllhw6.out11
-rw-r--r--keama/tests/duidllnohw.err4
-rw-r--r--keama/tests/duidllnohw.msg1
-rw-r--r--keama/tests/duidllt6.in65
-rw-r--r--keama/tests/duidllt6.out9
-rw-r--r--keama/tests/duidlltbadtype.err4
-rw-r--r--keama/tests/duidlltbadtype.msg1
-rw-r--r--keama/tests/duidlltnohw.err4
-rw-r--r--keama/tests/duidlltnohw.msg1
-rw-r--r--keama/tests/duidlltnotime.err4
-rw-r--r--keama/tests/duidlltnotime.msg1
-rw-r--r--keama/tests/duidlltthw4.err44
-rw-r--r--keama/tests/duidlltthw4.msg1
-rw-r--r--keama/tests/duidlltthw6.in64
-rw-r--r--keama/tests/duidlltthw6.out12
-rw-r--r--keama/tests/duidnoid.err4
-rw-r--r--keama/tests/duidnoid.msg1
-rw-r--r--keama/tests/enableupdates6.in68
-rw-r--r--keama/tests/enableupdates6.out20
-rw-r--r--keama/tests/encodedx6.in69
-rw-r--r--keama/tests/encodedx6.out15
-rw-r--r--keama/tests/env3
-rw-r--r--keama/tests/escapestring4.in46
-rw-r--r--keama/tests/escapestring4.out23
-rw-r--r--keama/tests/execstatement4.in47
-rw-r--r--keama/tests/execstatement4.out11
-rw-r--r--keama/tests/execstatement6.in67
-rw-r--r--keama/tests/execstatement6.out11
-rw-r--r--keama/tests/existsbx4.in415
-rw-r--r--keama/tests/existsbx4.out48
-rw-r--r--keama/tests/filename4.in410
-rw-r--r--keama/tests/filename4.out17
-rw-r--r--keama/tests/filenamedx4.notyet20
-rw-r--r--keama/tests/fixedaddressinroot4.err44
-rw-r--r--keama/tests/fixedaddressinroot4.msg1
-rw-r--r--keama/tests/fixedaddressinroot6.err64
-rw-r--r--keama/tests/fixedaddressinroot6.msg1
-rw-r--r--keama/tests/fixedprefixinroot.err64
-rw-r--r--keama/tests/fixedprefixinroot.msg1
-rw-r--r--keama/tests/fqdncompressed.err67
-rw-r--r--keama/tests/fqdncompressed.msg1
-rw-r--r--keama/tests/gethostdx4.notyet13
-rw-r--r--keama/tests/global4.in410
-rw-r--r--keama/tests/global4.out28
-rw-r--r--keama/tests/global6.in610
-rw-r--r--keama/tests/global6.out26
-rw-r--r--keama/tests/groupclass4.in432
-rw-r--r--keama/tests/groupclass4.out102
-rw-r--r--keama/tests/groupclass6.in635
-rw-r--r--keama/tests/groupclass6.out123
-rw-r--r--keama/tests/groupgroup4.in445
-rw-r--r--keama/tests/groupgroup4.out138
-rw-r--r--keama/tests/grouphost4.inn35
-rw-r--r--keama/tests/grouphost4.out78
-rw-r--r--keama/tests/groupinclass.err10
-rw-r--r--keama/tests/groupinclass.msg1
-rw-r--r--keama/tests/groupsubnet4.in424
-rw-r--r--keama/tests/groupsubnet4.out38
-rw-r--r--keama/tests/groupsubnet6.in624
-rw-r--r--keama/tests/groupsubnet6.out44
-rw-r--r--keama/tests/groupsubnetif.err419
-rw-r--r--keama/tests/groupsubnetif.msg1
-rw-r--r--keama/tests/hardware2dx4.in413
-rw-r--r--keama/tests/hardware2dx4.out25
-rw-r--r--keama/tests/hardwaredx4.in416
-rw-r--r--keama/tests/hardwaredx4.out55
-rw-r--r--keama/tests/hardwareinroot.err5
-rw-r--r--keama/tests/hardwareinroot.msg1
-rw-r--r--keama/tests/host6.notyet20
-rw-r--r--keama/tests/hostidentifier4.inl30
-rw-r--r--keama/tests/hostidentifier4.outl78
-rw-r--r--keama/tests/hostinclass.err10
-rw-r--r--keama/tests/hostinclass.msg1
-rw-r--r--keama/tests/hostinhost.err11
-rw-r--r--keama/tests/hostinhost.msg1
-rw-r--r--keama/tests/hostname4.in418
-rw-r--r--keama/tests/hostname4.out37
-rw-r--r--keama/tests/hostnum.errF7
-rw-r--r--keama/tests/hostnum.msg1
-rw-r--r--keama/tests/hostuid4.inn29
-rw-r--r--keama/tests/hostuid4.out62
-rw-r--r--keama/tests/ifxsc4.in417
-rw-r--r--keama/tests/ifxsc4.out79
-rw-r--r--keama/tests/ipaddr6.in63
-rw-r--r--keama/tests/ipaddr6.out14
-rw-r--r--keama/tests/ipaddrhost4.in48
-rw-r--r--keama/tests/ipaddrhost4.out24
-rw-r--r--keama/tests/ipaddrs4.notyet411
-rw-r--r--keama/tests/lifetime4.ind5
-rw-r--r--keama/tests/lifetime4.out8
-rw-r--r--keama/tests/lifetime6.inD5
-rw-r--r--keama/tests/lifetime6.out8
-rw-r--r--keama/tests/lifetimedef4.ind1
-rw-r--r--keama/tests/lifetimedef4.out11
-rw-r--r--keama/tests/lifetimedef6.inD1
-rw-r--r--keama/tests/lifetimedef6.out11
-rw-r--r--keama/tests/listarray.err7
-rw-r--r--keama/tests/listarray.msg1
-rw-r--r--keama/tests/minimal4.in44
-rw-r--r--keama/tests/minimal4.out7
-rw-r--r--keama/tests/minimal6.in64
-rw-r--r--keama/tests/minimal6.out7
-rw-r--r--keama/tests/mixedarray.err7
-rw-r--r--keama/tests/mixedarray.msg1
-rw-r--r--keama/tests/nestarray.err7
-rw-r--r--keama/tests/nestarray.msg1
-rw-r--r--keama/tests/noauth4.in47
-rw-r--r--keama/tests/noauth4.out15
-rw-r--r--keama/tests/noauth6.in66
-rw-r--r--keama/tests/noauth6.out15
-rw-r--r--keama/tests/noclass.err7
-rw-r--r--keama/tests/noclass.msg1
-rw-r--r--keama/tests/noinclude.err3
-rw-r--r--keama/tests/noinclude.msg1
-rw-r--r--keama/tests/nosubclass.err11
-rw-r--r--keama/tests/nosubclass.msg1
-rw-r--r--keama/tests/nosuperclass.err11
-rw-r--r--keama/tests/nosuperclass.msg1
-rw-r--r--keama/tests/notbx4.in412
-rw-r--r--keama/tests/notbx4.out30
-rw-r--r--keama/tests/notnotbx4.in415
-rw-r--r--keama/tests/notnotbx4.out21
-rw-r--r--keama/tests/nxdomainnx6.in612
-rw-r--r--keama/tests/nxdomainnx6.out28
-rw-r--r--keama/tests/onxsc4.in412
-rw-r--r--keama/tests/onxsc4.out34
-rw-r--r--keama/tests/optdatagrouppool4.in418
-rw-r--r--keama/tests/optdatagrouppool4.out23
-rw-r--r--keama/tests/optiondata4.in448
-rw-r--r--keama/tests/optiondata4.out173
-rw-r--r--keama/tests/optiondata6.in649
-rw-r--r--keama/tests/optiondata6.out178
-rw-r--r--keama/tests/optiondatapool4.in412
-rw-r--r--keama/tests/optiondatapool4.out31
-rw-r--r--keama/tests/optiondatapool6.in613
-rw-r--r--keama/tests/optiondatapool6.out32
-rw-r--r--keama/tests/optiondecl4.in427
-rw-r--r--keama/tests/optiondecl4.out143
-rw-r--r--keama/tests/optiondecl6.in627
-rw-r--r--keama/tests/optiondecl6.out137
-rw-r--r--keama/tests/optiondeclBat4.in46
-rw-r--r--keama/tests/optiondeclBat4.out18
-rw-r--r--keama/tests/optionencap4.in47
-rw-r--r--keama/tests/optionencap4.out15
-rw-r--r--keama/tests/optionencap6.in67
-rw-r--r--keama/tests/optionencap6.out15
-rw-r--r--keama/tests/optionexpr4.in416
-rw-r--r--keama/tests/optionexpr4.out81
-rw-r--r--keama/tests/optionspace4.in415
-rw-r--r--keama/tests/optionspace4.out34
-rw-r--r--keama/tests/optionspace6.in615
-rw-r--r--keama/tests/optionspace6.out34
-rw-r--r--keama/tests/optionvendor4.in48
-rw-r--r--keama/tests/optionvendor4.out28
-rw-r--r--keama/tests/optionvendor6.in68
-rw-r--r--keama/tests/optionvendor6.out28
-rw-r--r--keama/tests/orphan4.inn10
-rw-r--r--keama/tests/orphan4.out30
-rw-r--r--keama/tests/orphan6.inN10
-rw-r--r--keama/tests/orphan6.out28
-rw-r--r--keama/tests/packetdx4.notyet20
-rw-r--r--keama/tests/permitauth4.in415
-rw-r--r--keama/tests/permitauth4.out33
-rw-r--r--keama/tests/permitauth6.in615
-rw-r--r--keama/tests/permitauth6.out33
-rw-r--r--keama/tests/permitknown4.in415
-rw-r--r--keama/tests/permitknown4.out26
-rw-r--r--keama/tests/pickdx6.in615
-rw-r--r--keama/tests/pickdx6.out21
-rw-r--r--keama/tests/pool4.in411
-rw-r--r--keama/tests/pool4.out31
-rw-r--r--keama/tests/pool42.in415
-rw-r--r--keama/tests/pool42.out49
-rw-r--r--keama/tests/pool6.in611
-rw-r--r--keama/tests/pool6.out31
-rw-r--r--keama/tests/pool6in4.err413
-rw-r--r--keama/tests/pool6in4.msg1
-rw-r--r--keama/tests/poolinroot4.err47
-rw-r--r--keama/tests/poolinroot4.msg1
-rw-r--r--keama/tests/poolinroot6.err67
-rw-r--r--keama/tests/poolinroot6.msg1
-rw-r--r--keama/tests/preferred6.in614
-rw-r--r--keama/tests/preferred6.out30
-rw-r--r--keama/tests/prefix0.err69
-rw-r--r--keama/tests/prefix0.msg1
-rw-r--r--keama/tests/prefix128.err69
-rw-r--r--keama/tests/prefix128.msg1
-rw-r--r--keama/tests/prefix6.in69
-rw-r--r--keama/tests/prefix6.out32
-rw-r--r--keama/tests/prefix62.in610
-rw-r--r--keama/tests/prefix62.out36
-rw-r--r--keama/tests/prefixinroot6.err64
-rw-r--r--keama/tests/prefixinroot6.msg1
-rw-r--r--keama/tests/qualifyingsuffix4.in49
-rw-r--r--keama/tests/qualifyingsuffix4.out17
-rw-r--r--keama/tests/qualifyingsuffix6.in68
-rw-r--r--keama/tests/qualifyingsuffix6.out17
-rw-r--r--keama/tests/range4.in49
-rw-r--r--keama/tests/range4.out30
-rw-r--r--keama/tests/range6.in611
-rw-r--r--keama/tests/range6.out37
-rw-r--r--keama/tests/range6in4.err45
-rw-r--r--keama/tests/range6in4.msg1
-rw-r--r--keama/tests/rangeinroot4.err44
-rw-r--r--keama/tests/rangeinroot4.msg1
-rw-r--r--keama/tests/rangeinroot6.err65
-rw-r--r--keama/tests/rangeinroot6.msg1
-rw-r--r--keama/tests/reversedx6.in69
-rw-r--r--keama/tests/reversedx6.out15
-rw-r--r--keama/tests/runall.sh15
-rw-r--r--keama/tests/runone.sh119
-rw-r--r--keama/tests/samples/example.conf111
-rw-r--r--keama/tests/samples/example.conf.orig104
-rw-r--r--keama/tests/samples/example.json223
-rw-r--r--keama/tests/samples/runall.sh11
-rw-r--r--keama/tests/samples/runone.sh39
-rw-r--r--keama/tests/samples/simple.conf33
-rw-r--r--keama/tests/samples/simple.json53
-rw-r--r--keama/tests/samples/test-a6.conf75
-rw-r--r--keama/tests/samples/test-a6.conf.orig67
-rw-r--r--keama/tests/samples/test-a6.json144
-rw-r--r--keama/tests/samples/vmnet8.conf43
-rw-r--r--keama/tests/samples/vmnet8.conf.orig43
-rw-r--r--keama/tests/samples/vmnet8.json105
-rw-r--r--keama/tests/share0.err6
-rw-r--r--keama/tests/share0.msg1
-rw-r--r--keama/tests/share2if.err7
-rw-r--r--keama/tests/share2if.msg1
-rw-r--r--keama/tests/shareempty.err6
-rw-r--r--keama/tests/shareempty.msg1
-rw-r--r--keama/tests/shareinclass.err11
-rw-r--r--keama/tests/shareinclass.msg1
-rw-r--r--keama/tests/shareinhost.err11
-rw-r--r--keama/tests/shareinhost.msg1
-rw-r--r--keama/tests/shareinshare.err10
-rw-r--r--keama/tests/shareinshare.msg1
-rw-r--r--keama/tests/shareinsubnet4.err410
-rw-r--r--keama/tests/shareinsubnet4.msg1
-rw-r--r--keama/tests/shareinsubnet6.err610
-rw-r--r--keama/tests/shareinsubnet6.msg1
-rw-r--r--keama/tests/sharenoname.err6
-rw-r--r--keama/tests/sharenoname.msg1
-rw-r--r--keama/tests/shareone4.in422
-rw-r--r--keama/tests/shareone4.out44
-rw-r--r--keama/tests/shareone6.in619
-rw-r--r--keama/tests/shareone6.out37
-rw-r--r--keama/tests/sharepools4.in427
-rw-r--r--keama/tests/sharepools4.out63
-rw-r--r--keama/tests/sharetwo4.in429
-rw-r--r--keama/tests/sharetwo4.out64
-rw-r--r--keama/tests/sharetwo6.in624
-rw-r--r--keama/tests/sharetwo6.out56
-rw-r--r--keama/tests/sname4.notyet20
-rw-r--r--keama/tests/spawning6.in611
-rw-r--r--keama/tests/spawning6.out37
-rw-r--r--keama/tests/subclass4.in423
-rw-r--r--keama/tests/subclass4.out84
-rw-r--r--keama/tests/subclass6.in623
-rw-r--r--keama/tests/subclass6.out89
-rw-r--r--keama/tests/subclassbinsel4.in416
-rw-r--r--keama/tests/subclassbinsel4.out61
-rw-r--r--keama/tests/subclassbinsel6.in625
-rw-r--r--keama/tests/subclassbinsel6.out88
-rw-r--r--keama/tests/subclassguard4.in424
-rw-r--r--keama/tests/subclassguard4.out79
-rw-r--r--keama/tests/subclassguard6.in624
-rw-r--r--keama/tests/subclassguard6.out84
-rw-r--r--keama/tests/subnet4.in416
-rw-r--r--keama/tests/subnet4.out33
-rw-r--r--keama/tests/subnet42if.err48
-rw-r--r--keama/tests/subnet42if.msg1
-rw-r--r--keama/tests/subnet4auth.in418
-rw-r--r--keama/tests/subnet4auth.out35
-rw-r--r--keama/tests/subnet4badmask.err47
-rw-r--r--keama/tests/subnet4badmask.msg1
-rw-r--r--keama/tests/subnet4inclass.err410
-rw-r--r--keama/tests/subnet4inclass.msg1
-rw-r--r--keama/tests/subnet4inhost.err411
-rw-r--r--keama/tests/subnet4inhost.msg1
-rw-r--r--keama/tests/subnet4nomask.err47
-rw-r--r--keama/tests/subnet4nomask.msg1
-rw-r--r--keama/tests/subnet6.in618
-rw-r--r--keama/tests/subnet6.out39
-rw-r--r--keama/tests/subnet62if.err68
-rw-r--r--keama/tests/subnet62if.msg1
-rw-r--r--keama/tests/subnet6auth.in621
-rw-r--r--keama/tests/subnet6auth.out44
-rw-r--r--keama/tests/subnet6inclass.err610
-rw-r--r--keama/tests/subnet6inclass.msg1
-rw-r--r--keama/tests/subnet6inhost.err611
-rw-r--r--keama/tests/subnet6inhost.msg1
-rw-r--r--keama/tests/subnet6multi.in619
-rw-r--r--keama/tests/subnet6multi.out49
-rw-r--r--keama/tests/subnet6nolen.err67
-rw-r--r--keama/tests/subnet6nolen.msg1
-rw-r--r--keama/tests/subnet6noslash.err67
-rw-r--r--keama/tests/subnet6noslash.msg1
-rw-r--r--keama/tests/subnet6one.in618
-rw-r--r--keama/tests/subnet6one.out45
-rw-r--r--keama/tests/subnetinsubnet4.err410
-rw-r--r--keama/tests/subnetinsubnet4.msg1
-rw-r--r--keama/tests/subnetinsubnet6.err610
-rw-r--r--keama/tests/subnetinsubnet6.msg1
-rw-r--r--keama/tests/substringdx4.in421
-rw-r--r--keama/tests/substringdx4.out49
-rw-r--r--keama/tests/suffixdx4.in426
-rw-r--r--keama/tests/suffixdx4.out74
-rw-r--r--keama/tests/switchxsc4.in418
-rw-r--r--keama/tests/switchxsc4.out79
-rw-r--r--keama/tests/switchxsc6.in618
-rw-r--r--keama/tests/switchxsc6.out82
-rw-r--r--keama/tests/tautology.err9
-rw-r--r--keama/tests/tautology.msg1
-rw-r--r--keama/tests/tautologyhexa.err9
-rw-r--r--keama/tests/tautologyhexa.msg1
-rw-r--r--keama/tests/tautologysub.err9
-rw-r--r--keama/tests/tautologysub.msg1
-rw-r--r--keama/tests/temporary6.in610
-rw-r--r--keama/tests/temporary6.out33
-rw-r--r--keama/tests/textarray.err7
-rw-r--r--keama/tests/textarray.msg1
-rw-r--r--keama/tests/unknownoption.err4
-rw-r--r--keama/tests/unknownoption.msg1
-rw-r--r--keama/tests/unknownspace.err4
-rw-r--r--keama/tests/unknownspace.msg1
-rw-r--r--keama/tests/userclass.err6
-rw-r--r--keama/tests/userclass.msg1
-rw-r--r--keama/tests/vendorclass.err6
-rw-r--r--keama/tests/vendorclass.msg1
-rw-r--r--keama/tests/vendorspace4.in411
-rw-r--r--keama/tests/vendorspace4.out41
-rw-r--r--keama/tests/zone4.in424
-rw-r--r--keama/tests/zone4.out47
439 files changed, 31323 insertions, 0 deletions
diff --git a/keama/.gitignore b/keama/.gitignore
new file mode 100644
index 00000000..deb1cb67
--- /dev/null
+++ b/keama/.gitignore
@@ -0,0 +1 @@
+keama
diff --git a/keama/ChangeLog.md b/keama/ChangeLog.md
new file mode 100644
index 00000000..fd386d9e
--- /dev/null
+++ b/keama/ChangeLog.md
@@ -0,0 +1,18 @@
+* 3 [doc] fdupont
+
+ New documentation including this file.
+ (Gitlab #34)
+
+* 2 [bug] fdupont
+
+ Fixed dhcp4 option 67 wrong name.
+ (Gitlab #22)
+
+* 1 [func] fdupont
+
+ Initial revision.
+
+LEGEND
+* [bug] Bug fix.
+* [doc] Update to documentation.
+* [func] New feature.
diff --git a/keama/Makefile.am b/keama/Makefile.am
new file mode 100644
index 00000000..5630cc91
--- /dev/null
+++ b/keama/Makefile.am
@@ -0,0 +1,5 @@
+sbin_PROGRAMS = keama
+keama_SOURCES = keama.c data.c conflex.c json.c confparse.c parse.c options.c
+keama_SOURCES += reduce.c print.c eval.c
+man_MANS = keama.8
+EXTRA_DIST = $(man_MANS)
diff --git a/keama/Makefile.in b/keama/Makefile.in
new file mode 100644
index 00000000..43e9bc26
--- /dev/null
+++ b/keama/Makefile.in
@@ -0,0 +1,721 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+sbin_PROGRAMS = keama$(EXEEXT)
+subdir = keama
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/includes/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man8dir)"
+PROGRAMS = $(sbin_PROGRAMS)
+am_keama_OBJECTS = keama.$(OBJEXT) data.$(OBJEXT) conflex.$(OBJEXT) \
+ json.$(OBJEXT) confparse.$(OBJEXT) parse.$(OBJEXT) \
+ options.$(OBJEXT) reduce.$(OBJEXT) print.$(OBJEXT) \
+ eval.$(OBJEXT)
+keama_OBJECTS = $(am_keama_OBJECTS)
+keama_LDADD = $(LDADD)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/includes
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/conflex.Po ./$(DEPDIR)/confparse.Po \
+ ./$(DEPDIR)/data.Po ./$(DEPDIR)/eval.Po ./$(DEPDIR)/json.Po \
+ ./$(DEPDIR)/keama.Po ./$(DEPDIR)/options.Po \
+ ./$(DEPDIR)/parse.Po ./$(DEPDIR)/print.Po \
+ ./$(DEPDIR)/reduce.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(keama_SOURCES)
+DIST_SOURCES = $(keama_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+man8dir = $(mandir)/man8
+NROFF = nroff
+MANS = $(man_MANS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+A = @A@
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ATF_BIN = @ATF_BIN@
+ATF_CFLAGS = @ATF_CFLAGS@
+ATF_LDFLAGS = @ATF_LDFLAGS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINDCONFIG = @BINDCONFIG@
+BINDDIR = @BINDDIR@
+BINDIOMUX = @BINDIOMUX@
+BINDLIBDNSDIR = @BINDLIBDNSDIR@
+BINDLIBIRSDIR = @BINDLIBIRSDIR@
+BINDLIBISCCFGDIR = @BINDLIBISCCFGDIR@
+BINDLIBISCDIR = @BINDLIBISCDIR@
+BINDLT = @BINDLT@
+BINDSRCDIR = @BINDSRCDIR@
+BINDSUBDIR = @BINDSUBDIR@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DHLIBS = @DHLIBS@
+DISTCHECK_ATF_CONFIGURE_FLAG = @DISTCHECK_ATF_CONFIGURE_FLAG@
+DISTCHECK_LIBBIND_CONFIGURE_FLAG = @DISTCHECK_LIBBIND_CONFIGURE_FLAG@
+DISTCHECK_LIBTOOL_CONFIGURE_FLAG = @DISTCHECK_LIBTOOL_CONFIGURE_FLAG@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDAP_CFLAGS = @LDAP_CFLAGS@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+Q = @Q@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+UNITTESTS = @UNITTESTS@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_prefix_program = @ac_prefix_program@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+byte_order = @byte_order@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgcfg_found = @pkgcfg_found@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+keama_SOURCES = keama.c data.c conflex.c json.c confparse.c parse.c \
+ options.c reduce.c print.c eval.c
+man_MANS = keama.8
+EXTRA_DIST = $(man_MANS)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign keama/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign keama/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-sbinPROGRAMS: $(sbin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-sbinPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(sbindir)" && rm -f $$files
+
+clean-sbinPROGRAMS:
+ -test -z "$(sbin_PROGRAMS)" || rm -f $(sbin_PROGRAMS)
+
+keama$(EXEEXT): $(keama_OBJECTS) $(keama_DEPENDENCIES) $(EXTRA_keama_DEPENDENCIES)
+ @rm -f keama$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(keama_OBJECTS) $(keama_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/conflex.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/confparse.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eval.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/json.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keama.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/options.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/print.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/reduce.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+install-man8: $(man_MANS)
+ @$(NORMAL_INSTALL)
+ @list1=''; \
+ list2='$(man_MANS)'; \
+ test -n "$(man8dir)" \
+ && test -n "`echo $$list1$$list2`" \
+ || exit 0; \
+ echo " $(MKDIR_P) '$(DESTDIR)$(man8dir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(man8dir)" || exit 1; \
+ { for i in $$list1; do echo "$$i"; done; \
+ if test -n "$$list2"; then \
+ for i in $$list2; do echo "$$i"; done \
+ | sed -n '/\.8[a-z]*$$/p'; \
+ fi; \
+ } | while read p; do \
+ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; echo "$$p"; \
+ done | \
+ sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \
+ sed 'N;N;s,\n, ,g' | { \
+ list=; while read file base inst; do \
+ if test "$$base" = "$$inst"; then list="$$list $$file"; else \
+ echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man8dir)/$$inst'"; \
+ $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man8dir)/$$inst" || exit $$?; \
+ fi; \
+ done; \
+ for i in $$list; do echo "$$i"; done | $(am__base_list) | \
+ while read files; do \
+ test -z "$$files" || { \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man8dir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(man8dir)" || exit $$?; }; \
+ done; }
+
+uninstall-man8:
+ @$(NORMAL_UNINSTALL)
+ @list=''; test -n "$(man8dir)" || exit 0; \
+ files=`{ for i in $$list; do echo "$$i"; done; \
+ l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \
+ sed -n '/\.8[a-z]*$$/p'; \
+ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \
+ dir='$(DESTDIR)$(man8dir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(MANS)
+installdirs:
+ for dir in "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man8dir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-sbinPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/conflex.Po
+ -rm -f ./$(DEPDIR)/confparse.Po
+ -rm -f ./$(DEPDIR)/data.Po
+ -rm -f ./$(DEPDIR)/eval.Po
+ -rm -f ./$(DEPDIR)/json.Po
+ -rm -f ./$(DEPDIR)/keama.Po
+ -rm -f ./$(DEPDIR)/options.Po
+ -rm -f ./$(DEPDIR)/parse.Po
+ -rm -f ./$(DEPDIR)/print.Po
+ -rm -f ./$(DEPDIR)/reduce.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-man
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-sbinPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man: install-man8
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/conflex.Po
+ -rm -f ./$(DEPDIR)/confparse.Po
+ -rm -f ./$(DEPDIR)/data.Po
+ -rm -f ./$(DEPDIR)/eval.Po
+ -rm -f ./$(DEPDIR)/json.Po
+ -rm -f ./$(DEPDIR)/keama.Po
+ -rm -f ./$(DEPDIR)/options.Po
+ -rm -f ./$(DEPDIR)/parse.Po
+ -rm -f ./$(DEPDIR)/print.Po
+ -rm -f ./$(DEPDIR)/reduce.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-man uninstall-sbinPROGRAMS
+
+uninstall-man: uninstall-man8
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-sbinPROGRAMS cscopelist-am ctags ctags-am \
+ distclean distclean-compile distclean-generic distclean-tags \
+ distdir dvi dvi-am html html-am info info-am install \
+ install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-man8 install-pdf install-pdf-am install-ps \
+ install-ps-am install-sbinPROGRAMS install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-man uninstall-man8 \
+ uninstall-sbinPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/keama/README.md b/keama/README.md
new file mode 100644
index 00000000..4028fcba
--- /dev/null
+++ b/keama/README.md
@@ -0,0 +1,53 @@
+# KEA Migration Assistant Short Guide.
+
+The KEA Migration Assistant (aka _keama_) is an experimental tool
+which helps to translate ISC DHCP configurations to Kea.
+
+## How to get last sources
+
+From time to time the _keama_ is upgraded for bug fixes, support of
+new or not yet ISC DHCP features or more likely support of new KEA
+features.
+
+As now _keama_ is included in ISC DHCP the most recent code can be
+found with the most recent ISC DHCP code in the master branch of the
+gitlab repository.
+
+## How to build and install
+
+After the ISC DHCP build go to the keama directory and type:
+```console
+make
+```
+To install it:
+```console
+make install
+```
+
+## Known limitations
+
+_keama_ uses a subset of the ISC DHCP configuration file parser with a lot
+of sanaity checks removed so it does not know how to handle an incorrect
+ISC DHCP configuration file and eventually can even crash on it.
+
+ISC DHCP and KEA have different models for many things, for instance
+ISC DHCP supports the failover protocol when KEA supports High Availability.
+In some cases _keama_ tries to cope with that, for instance for host
+reservations which are global in ISC DHCP and by default per subnet in KEA.
+
+## How to use
+
+The manual explains how parameters guide _keama_ choices for lifetimes,
+name literals, host reservation scope, etc. Directives were added to
+the ISC DHCP syntax (they are valid but ignored) for options.
+
+Each time _keama_ finds a feature it can't translate it emits a comment
+with a reference to the feature description in a kea (not isc dhcp) gitlab
+issue in the "ISC DHCP Migration" milestone. The number of reports is
+returned by _keama_ when it exits.
+
+## How to help
+
+If you have configuration patterns you would like to see supported
+by Keama please feel free to reach out to us. We are always looking
+to improve the tool.
diff --git a/keama/REAME.md b/keama/REAME.md
new file mode 100644
index 00000000..5d1243f1
--- /dev/null
+++ b/keama/REAME.md
@@ -0,0 +1,53 @@
+# KEA Migration Assistant Short Guide.
+
+The KEA Migration Assistant (aka _keama_) is an experimental tool
+which helps to translate ISC DHCP configurations to Kea.
+
+## How to get last sources
+
+From time to time the _keama_ is upgraded for bug fixes, support of
+new or not yet ISC DHCP features or more likely support of new KEA
+features.
+
+As now _keama_ is included in ISC DHCP the most recent code can be
+found with the most recent ISC DHCP code in the master branch of the
+gitlab repository.
+
+## How to build and install
+
+After the ISC DHCP build go to the keama directory and type:
+```console
+make
+```
+To install it:
+```console
+make install
+```
+
+## Known limitations
+
+_keama_ uses a subset of the ISC DHCP configuration file parser with a lot
+of sanaity checks removed so it does not know how to handle an incorrect
+ISC DHCP configuration file and eventually can even crash on it.
+
+ISC DHCP and KEA have different models for many things, for instance
+ISC DHCP supports the failover protocol when KEA supports High Availability.
+In some cases _keama_ tries to cope with that, for instance for host
+reservations which are global in ISC DHCP and by default per subnet in KEA.
+
+## How to use
+
+The manual explains how parameters guide _keama_ choices for lifetimes,
+name literals, host reservation scope, etc. Directives were added to
+the ISC DHCP syntax (they are valid but ignored) for options.
+
+Each time _keama_ finds a feature it can't translate it emits a comment
+with a reference to the feature description in a kea (not isc dhcp) gitlab
+issue in the "ISC DHCP Migration" milestone. The number of reports is
+returned by _keama_ when it exits.
+
+## How to help
+
+We already collected a lot of ISC DHCP configurations used in production
+but you can supply new ones, in particular if they use a common
+paradigm which can be easily translated but not (yet) supported.
diff --git a/keama/conflex.c b/keama/conflex.c
new file mode 100644
index 00000000..143487b0
--- /dev/null
+++ b/keama/conflex.c
@@ -0,0 +1,1550 @@
+/*
+ * Copyright (c) 2017 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+/* From common/conflex.c */
+
+#include "keama.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <assert.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int get_char(struct parse *);
+static void unget_char(struct parse *, int);
+static void skip_to_eol(struct parse *);
+static enum dhcp_token read_whitespace(int c, struct parse *cfile);
+static enum dhcp_token read_string(struct parse *);
+static enum dhcp_token read_number(int, struct parse *);
+static enum dhcp_token read_num_or_name(int, struct parse *);
+static enum dhcp_token intern(char *, enum dhcp_token);
+
+struct parse *
+new_parse(int file, char *inbuf, size_t buflen, const char *name, int eolp)
+{
+ struct parse *tmp;
+
+ tmp = (struct parse *)malloc(sizeof(struct parse));
+ assert(tmp != NULL);
+ memset(tmp, 0, sizeof(struct parse));
+
+ TAILQ_INSERT_TAIL(&parses, tmp);
+
+ tmp->tlname = name;
+ tmp->lpos = tmp->line = 1;
+ tmp->cur_line = tmp->line1;
+ tmp->prev_line = tmp->line2;
+ tmp->token_line = tmp->cur_line;
+ tmp->cur_line[0] = tmp->prev_line[0] = 0;
+ tmp->file = file;
+ tmp->eol_token = eolp;
+ TAILQ_INIT(&tmp->comments);
+
+ if (inbuf != NULL) {
+ tmp->inbuf = inbuf;
+ tmp->buflen = buflen;
+ tmp->bufsiz = 0;
+ } else {
+ struct stat sb;
+
+ if (fstat(file, &sb) < 0) {
+ fprintf(stderr, "can't stat input\n");
+ exit(1);
+ }
+
+ if (sb.st_size == 0)
+ return tmp;
+
+ tmp->bufsiz = tmp->buflen = (size_t) sb.st_size;
+ tmp->inbuf = mmap(NULL, tmp->bufsiz, PROT_READ, MAP_SHARED,
+ file, 0);
+
+ if (tmp->inbuf == MAP_FAILED) {
+ fprintf(stderr, "can't map input\n");
+ exit(1);
+ }
+ }
+
+ return tmp;
+}
+
+void
+end_parse(struct parse *cfile)
+{
+ /* "Memory" config files have no file. */
+ if (cfile->file != -1) {
+ munmap(cfile->inbuf, cfile->bufsiz);
+ close(cfile->file);
+ }
+
+ while (TAILQ_NEXT(cfile) != NULL) {
+ struct parse *saved_state;
+
+ saved_state = TAILQ_NEXT(cfile);
+ TAILQ_REMOVE(&parses, saved_state);
+ free(saved_state);
+ }
+
+ cfile->stack_size = 0;
+ if (cfile->stack != NULL)
+ free(cfile->stack);
+ cfile->stack = NULL;
+ TAILQ_REMOVE(&parses, cfile);
+ free(cfile);
+}
+
+/*
+ * Save the current state of the parser.
+ *
+ * Only one state may be saved. Any previous saved state is
+ * lost.
+ */
+void
+save_parse_state(struct parse *cfile) {
+ struct parse *tmp;
+
+ /*
+ * Free any previous saved states.
+ */
+ while (TAILQ_NEXT(cfile) != NULL) {
+ struct parse *saved_state;
+
+ saved_state = TAILQ_NEXT(cfile);
+ TAILQ_REMOVE(&parses, saved_state);
+ free(saved_state);
+ }
+
+ /*
+ * Save our current state.
+ */
+ tmp = (struct parse *)malloc(sizeof(struct parse));
+ if (tmp == NULL)
+ parse_error(cfile, "can't allocate state to be saved");
+ memset(tmp, 0, sizeof(struct parse));
+ /* save up to comments field */
+ memcpy(tmp, cfile, (size_t)&(((struct parse *)0)->comments));
+ TAILQ_INSERT_AFTER(&parses, cfile, tmp);
+}
+
+/*
+ * Return the parser to the previous saved state.
+ *
+ * You must call save_parse_state() every time before calling
+ * restore_parse_state().
+ */
+void
+restore_parse_state(struct parse *cfile) {
+ struct parse *saved_state;
+
+ if (TAILQ_NEXT(cfile) == NULL)
+ parse_error(cfile, "can't find saved state");
+
+ saved_state = TAILQ_NEXT(cfile);
+ TAILQ_REMOVE(&parses, saved_state);
+ /* restore up to comments field */
+ memcpy(cfile, saved_state, (size_t)&(((struct parse *)0)->comments));
+ free(saved_state);
+}
+
+static int
+get_char(struct parse *cfile)
+{
+ /* My kingdom for WITH... */
+ int c;
+
+ if (cfile->bufix == cfile->buflen) {
+ c = EOF;
+ } else {
+ c = cfile->inbuf[cfile->bufix];
+ cfile->bufix++;
+ }
+
+ if (!cfile->ugflag) {
+ if (c == EOL) {
+ if (cfile->cur_line == cfile->line1) {
+ cfile->cur_line = cfile->line2;
+ cfile->prev_line = cfile->line1;
+ } else {
+ cfile->cur_line = cfile->line1;
+ cfile->prev_line = cfile->line2;
+ }
+ cfile->line++;
+ cfile->lpos = 1;
+ cfile->cur_line[0] = 0;
+ } else if (c != EOF) {
+ if (cfile->lpos <= 80) {
+ cfile->cur_line[cfile->lpos - 1] = c;
+ cfile->cur_line[cfile->lpos] = 0;
+ }
+ cfile->lpos++;
+ }
+ } else
+ cfile->ugflag = 0;
+ return c;
+}
+
+/*
+ * Return a character to our input buffer.
+ */
+static void
+unget_char(struct parse *cfile, int c) {
+ if (c != EOF) {
+ cfile->bufix--;
+ cfile->ugflag = 1; /* do not put characters into
+ our error buffer on the next
+ call to get_char() */
+ }
+}
+
+/*
+ * GENERAL NOTE ABOUT TOKENS
+ *
+ * We normally only want non-whitespace tokens. There are some
+ * circumstances where we *do* want to see whitespace (for example
+ * when parsing IPv6 addresses).
+ *
+ * Generally we use the next_token() function to read tokens. This
+ * in turn calls get_next_token, which does *not* return tokens for
+ * whitespace. Rather, it skips these.
+ *
+ * When we need to see whitespace, we us next_raw_token(), which also
+ * returns the WHITESPACE token.
+ *
+ * The peek_token() and peek_raw_token() functions work as expected.
+ *
+ * Warning: if you invoke peek_token(), then if there is a whitespace
+ * token, it will be lost, and subsequent use of next_raw_token() or
+ * peek_raw_token() will NOT see it.
+ */
+
+static enum dhcp_token
+get_raw_token(struct parse *cfile) {
+ int c;
+ enum dhcp_token ttok;
+ static char tb[2];
+ int l, p;
+
+ for (;;) {
+ l = cfile->line;
+ p = cfile->lpos;
+
+ c = get_char(cfile);
+ if (!((c == '\n') && cfile->eol_token) &&
+ isascii(c) && isspace(c)) {
+ ttok = read_whitespace(c, cfile);
+ break;
+ }
+ if (c == '#') {
+ skip_to_eol(cfile);
+ continue;
+ }
+ if (c == '"') {
+ cfile->lexline = l;
+ cfile->lexchar = p;
+ ttok = read_string(cfile);
+ break;
+ }
+ if ((isascii(c) && isdigit(c)) || c == '-') {
+ cfile->lexline = l;
+ cfile->lexchar = p;
+ ttok = read_number(c, cfile);
+ break;
+ } else if (isascii(c) && isalpha(c)) {
+ cfile->lexline = l;
+ cfile->lexchar = p;
+ ttok = read_num_or_name(c, cfile);
+ break;
+ } else if (c == EOF) {
+ ttok = END_OF_FILE;
+ cfile->tlen = 0;
+ break;
+ } else {
+ cfile->lexline = l;
+ cfile->lexchar = p;
+ tb[0] = c;
+ tb[1] = 0;
+ cfile->tval = tb;
+ cfile->tlen = 1;
+ ttok = c;
+ break;
+ }
+ }
+ return ttok;
+}
+
+/*
+ * The get_next_token() function consumes the next token and
+ * returns it to the caller.
+ *
+ * Since the code is almost the same for "normal" and "raw"
+ * input, we pass a flag to alter the way it works.
+ */
+
+static enum dhcp_token
+get_next_token(const char **rval, unsigned *rlen,
+ struct parse *cfile, isc_boolean_t raw) {
+ int rv;
+
+ if (cfile->token) {
+ if (cfile->lexline != cfile->tline)
+ cfile->token_line = cfile->cur_line;
+ cfile->lexchar = cfile->tlpos;
+ cfile->lexline = cfile->tline;
+ rv = cfile->token;
+ cfile->token = 0;
+ } else {
+ rv = get_raw_token(cfile);
+ cfile->token_line = cfile->cur_line;
+ }
+
+ if (!raw) {
+ while (rv == WHITESPACE) {
+ rv = get_raw_token(cfile);
+ cfile->token_line = cfile->cur_line;
+ }
+ }
+
+ if (rval)
+ *rval = cfile->tval;
+ if (rlen)
+ *rlen = cfile->tlen;
+ return rv;
+}
+
+/*
+ * Get the next token from cfile and return it.
+ *
+ * If rval is non-NULL, set the pointer it contains to
+ * the contents of the token.
+ *
+ * If rlen is non-NULL, set the integer it contains to
+ * the length of the token.
+ */
+
+enum dhcp_token
+next_token(const char **rval, unsigned *rlen, struct parse *cfile) {
+ return get_next_token(rval, rlen, cfile, ISC_FALSE);
+}
+
+
+/*
+ * The same as the next_token() function above, but will return space
+ * as the WHITESPACE token.
+ */
+
+enum dhcp_token
+next_raw_token(const char **rval, unsigned *rlen, struct parse *cfile) {
+ return get_next_token(rval, rlen, cfile, ISC_TRUE);
+}
+
+
+/*
+ * The do_peek_token() function checks the next token without
+ * consuming it, and returns it to the caller.
+ *
+ * Since the code is almost the same for "normal" and "raw"
+ * input, we pass a flag to alter the way it works. (See the
+ * warning in the GENERAL NOTES ABOUT TOKENS above though.)
+ */
+
+enum dhcp_token
+do_peek_token(const char **rval, unsigned int *rlen,
+ struct parse *cfile, isc_boolean_t raw) {
+ int x;
+
+ if (!cfile->token || (!raw && (cfile->token == WHITESPACE))) {
+ cfile->tlpos = cfile->lexchar;
+ cfile->tline = cfile->lexline;
+
+ do {
+ cfile->token = get_raw_token(cfile);
+ } while (!raw && (cfile->token == WHITESPACE));
+
+ if (cfile->lexline != cfile->tline)
+ cfile->token_line = cfile->prev_line;
+
+ x = cfile->lexchar;
+ cfile->lexchar = cfile->tlpos;
+ cfile->tlpos = x;
+
+ x = cfile->lexline;
+ cfile->lexline = cfile->tline;
+ cfile->tline = x;
+ }
+ if (rval)
+ *rval = cfile->tval;
+ if (rlen)
+ *rlen = cfile->tlen;
+ return cfile->token;
+}
+
+
+/*
+ * Get the next token from cfile and return it, leaving it for a
+ * subsequent call to next_token().
+ *
+ * Note that it WILL consume whitespace tokens.
+ *
+ * If rval is non-NULL, set the pointer it contains to
+ * the contents of the token.
+ *
+ * If rlen is non-NULL, set the integer it contains to
+ * the length of the token.
+ */
+
+enum dhcp_token
+peek_token(const char **rval, unsigned *rlen, struct parse *cfile) {
+ return do_peek_token(rval, rlen, cfile, ISC_FALSE);
+}
+
+
+/*
+ * The same as the peek_token() function above, but will return space
+ * as the WHITESPACE token.
+ */
+
+enum dhcp_token
+peek_raw_token(const char **rval, unsigned *rlen, struct parse *cfile) {
+ return do_peek_token(rval, rlen, cfile, ISC_TRUE);
+}
+
+/*
+ * The comment up to but not including EOL is saved.
+ */
+
+static void
+skip_to_eol(struct parse *cfile)
+{
+ char buf[128];
+ unsigned cc = 0;
+ struct comment *comment;
+
+ memset(buf, 0, sizeof(buf));
+ buf[0] = '#';
+ for (;;) {
+ int c;
+
+ c = get_char(cfile);
+ if (c == EOF)
+ break;
+ if (c == EOL) {
+ break;
+ }
+ if (++cc < sizeof(buf) - 1)
+ buf[cc] = c;
+ }
+ comment = createComment(buf);
+ TAILQ_INSERT_TAIL(&cfile->comments, comment);
+}
+
+static enum dhcp_token
+read_whitespace(int c, struct parse *cfile) {
+ int ofs;
+
+ /*
+ * Read as much whitespace as we have available.
+ */
+ ofs = 0;
+ do {
+ if (ofs >= (sizeof(cfile->tokbuf) - 1)) {
+ /*
+ * As the file includes a huge amount of whitespace,
+ * it's probably broken.
+ * Print out a warning and bail out.
+ */
+ parse_error(cfile,
+ "whitespace too long, buffer overflow.");
+ }
+ cfile->tokbuf[ofs++] = c;
+ c = get_char(cfile);
+ if (c == EOF)
+ return END_OF_FILE;
+ } while (!((c == '\n') && cfile->eol_token) &&
+ isascii(c) && isspace(c));
+
+ /*
+ * Put the last (non-whitespace) character back.
+ */
+ unget_char(cfile, c);
+
+ /*
+ * Return our token.
+ */
+ cfile->tokbuf[ofs] = '\0';
+ cfile->tlen = ofs;
+ cfile->tval = cfile->tokbuf;
+ return WHITESPACE;
+}
+
+static enum dhcp_token
+read_string(struct parse *cfile)
+{
+ unsigned i;
+ int bs = 0;
+ int c;
+ int value = 0;
+ int hex = 0;
+
+ for (i = 0; i < sizeof(cfile->tokbuf); i++) {
+ again:
+ c = get_char(cfile);
+ if (c == EOF)
+ parse_error(cfile, "eof in string constant");
+ if (bs == 1) {
+ switch (c) {
+ case 't':
+ cfile->tokbuf[i] = '\t';
+ break;
+ case 'r':
+ cfile->tokbuf[i] = '\r';
+ break;
+ case 'n':
+ cfile->tokbuf[i] = '\n';
+ break;
+ case 'b':
+ cfile->tokbuf[i] = '\b';
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ hex = 0;
+ value = c - '0';
+ ++bs;
+ goto again;
+ case 'x':
+ hex = 1;
+ value = 0;
+ ++bs;
+ goto again;
+ default:
+ cfile->tokbuf[i] = c;
+ break;
+ }
+ bs = 0;
+ } else if (bs > 1) {
+ if (hex) {
+ if (c >= '0' && c <= '9') {
+ value = value * 16 + (c - '0');
+ } else if (c >= 'a' && c <= 'f') {
+ value = value * 16 + (c - 'a' + 10);
+ } else if (c >= 'A' && c <= 'F') {
+ value = value * 16 + (c - 'A' + 10);
+ } else
+ parse_error(cfile,
+ "invalid hex digit: %x",
+ c);
+ if (++bs == 4) {
+ cfile->tokbuf[i] = value;
+ bs = 0;
+ } else
+ goto again;
+ } else {
+ if (c >= '0' && c <= '7') {
+ value = value * 8 + (c - '0');
+ } else {
+ if (value != 0)
+ parse_error(cfile,
+ "invalid octal digit %x",
+ c);
+ else
+ cfile->tokbuf[i] = 0;
+ bs = 0;
+ }
+ if (++bs == 4) {
+ cfile->tokbuf[i] = value;
+ bs = 0;
+ } else
+ goto again;
+ }
+ } else if (c == '\\') {
+ bs = 1;
+ goto again;
+ } else if (c == '"')
+ break;
+ else
+ cfile->tokbuf[i] = c;
+ }
+ /* Normally, I'd feel guilty about this, but we're talking about
+ strings that'll fit in a DHCP packet here... */
+ if (i == sizeof(cfile->tokbuf))
+ parse_error(cfile,
+ "string constant larger than internal buffer");
+ cfile->tokbuf[i] = 0;
+ cfile->tlen = i;
+ cfile->tval = cfile->tokbuf;
+ return STRING;
+}
+
+static enum dhcp_token
+read_number(int c, struct parse *cfile)
+{
+ unsigned i = 0;
+ int token = NUMBER;
+
+ cfile->tokbuf[i++] = c;
+ for (; i < sizeof(cfile->tokbuf); i++) {
+ c = get_char(cfile);
+
+ /* Promote NUMBER->NUMBER_OR_NAME->NAME, never demote.
+ * Except in the case of '0x' syntax hex, which gets called
+ * a NAME at '0x', and returned to NUMBER_OR_NAME once it's
+ * verified to be at least 0xf or less.
+ */
+ switch (isascii(c) ? token : BREAK) {
+ case NUMBER:
+ if (isdigit(c))
+ break;
+ /* FALLTHROUGH */
+ case NUMBER_OR_NAME:
+ if (isxdigit(c)) {
+ token = NUMBER_OR_NAME;
+ break;
+ }
+ /* FALLTHROUGH */
+ case NAME:
+ if ((i == 2) && isxdigit(c) &&
+ (cfile->tokbuf[0] == '0') &&
+ ((cfile->tokbuf[1] == 'x') ||
+ (cfile->tokbuf[1] == 'X'))) {
+ token = NUMBER_OR_NAME;
+ break;
+ } else if (((c == '-') || (c == '_') || isalnum(c))) {
+ token = NAME;
+ break;
+ }
+ /* FALLTHROUGH */
+ case BREAK:
+ /* At this point c is either EOF or part of the next
+ * token. If not EOF, rewind the file one byte so
+ * the next token is read from there.
+ */
+ unget_char(cfile, c);
+ goto end_read;
+
+ default:
+ parse_error(cfile,
+ "read_number(): impossible case");
+ }
+
+ cfile->tokbuf[i] = c;
+ }
+
+ if (i == sizeof(cfile->tokbuf))
+ parse_error(cfile,
+ "numeric token larger than internal buffer");
+
+ end_read:
+ cfile->tokbuf[i] = 0;
+ cfile->tlen = i;
+ cfile->tval = cfile->tokbuf;
+
+ /*
+ * If this entire token from start to finish was "-", such as
+ * the middle parameter in "42 - 7", return just the MINUS token.
+ */
+ if ((i == 1) && (cfile->tokbuf[i] == '-'))
+ return MINUS;
+ else
+ return token;
+}
+
+static enum dhcp_token
+read_num_or_name(int c, struct parse *cfile)
+{
+ unsigned i = 0;
+ enum dhcp_token rv = NUMBER_OR_NAME;
+
+ cfile->tokbuf[i++] = c;
+ for (; i < sizeof(cfile->tokbuf); i++) {
+ c = get_char(cfile);
+ if (!isascii(c) ||
+ (c != '-' && c != '_' && !isalnum(c))) {
+ unget_char(cfile, c);
+ break;
+ }
+ if (!isxdigit(c))
+ rv = NAME;
+ cfile->tokbuf[i] = c;
+ }
+ if (i == sizeof(cfile->tokbuf))
+ parse_error(cfile, "token larger than internal buffer");
+ cfile->tokbuf[i] = 0;
+ cfile->tlen = i;
+ cfile->tval = cfile->tokbuf;
+ return intern(cfile->tval, rv);
+}
+
+static enum dhcp_token
+intern(char *atom, enum dhcp_token dfv) {
+ if (!isascii(atom[0]))
+ return dfv;
+
+ switch (tolower((unsigned char)atom[0])) {
+ case '-':
+ if (atom [1] == 0)
+ return MINUS;
+ break;
+
+ case 'a':
+ if (!strcasecmp(atom + 1, "bandoned"))
+ return TOKEN_ABANDONED;
+ if (!strcasecmp(atom + 1, "ctive"))
+ return TOKEN_ACTIVE;
+ if (!strncasecmp(atom + 1, "dd", 2)) {
+ if (atom[3] == '\0')
+ return TOKEN_ADD;
+ else if (!strcasecmp(atom + 3, "ress"))
+ return ADDRESS;
+ break;
+ }
+ if (!strcasecmp(atom + 1, "fter"))
+ return AFTER;
+ if (isascii(atom[1]) &&
+ (tolower((unsigned char)atom[1]) == 'l')) {
+ if (!strcasecmp(atom + 2, "gorithm"))
+ return ALGORITHM;
+ if (!strcasecmp(atom + 2, "ias"))
+ return ALIAS;
+ if (isascii(atom[2]) &&
+ (tolower((unsigned char)atom[2]) == 'l')) {
+ if (atom[3] == '\0')
+ return ALL;
+ else if (!strcasecmp(atom + 3, "ow"))
+ return ALLOW;
+ break;
+ }
+ if (!strcasecmp(atom + 2, "so"))
+ return TOKEN_ALSO;
+ break;
+ }
+ if (isascii(atom[1]) &&
+ (tolower((unsigned char)atom[1]) == 'n')) {
+ if (!strcasecmp(atom + 2, "d"))
+ return AND;
+ if (!strcasecmp(atom + 2, "ycast-mac"))
+ return ANYCAST_MAC;
+ break;
+ }
+ if (!strcasecmp(atom + 1, "ppend"))
+ return APPEND;
+ if (!strcasecmp(atom + 1, "rray"))
+ return ARRAY;
+ if (isascii(atom[1]) &&
+ (tolower((unsigned char)atom[1]) == 't')) {
+ if (atom[2] == '\0')
+ return AT;
+ if (!strcasecmp(atom + 2, "sfp"))
+ return ATSFP;
+ break;
+ }
+ if (!strcasecmp(atom + 1, "uthoring-byte-order"))
+ return AUTHORING_BYTE_ORDER;
+ if (!strncasecmp(atom + 1, "ut", 2)) {
+ if (isascii(atom[3]) &&
+ (tolower((unsigned char)atom[3]) == 'h')) {
+ if (!strncasecmp(atom + 4, "enticat", 7)) {
+ if (!strcasecmp(atom + 11, "ed"))
+ return AUTHENTICATED;
+ if (!strcasecmp(atom + 11, "ion"))
+ return AUTHENTICATION;
+ break;
+ }
+ if (!strcasecmp(atom + 4, "oritative"))
+ return AUTHORITATIVE;
+ break;
+ }
+ if (!strcasecmp(atom + 3, "o-partner-down"))
+ return AUTO_PARTNER_DOWN;
+ break;
+ }
+ break;
+ case 'b':
+ if (!strcasecmp(atom + 1, "ackup"))
+ return TOKEN_BACKUP;
+ if (!strcasecmp(atom + 1, "ootp"))
+ return TOKEN_BOOTP;
+ if (!strcasecmp(atom + 1, "inding"))
+ return BINDING;
+ if (!strcasecmp(atom + 1, "inary-to-ascii"))
+ return BINARY_TO_ASCII;
+ if (!strcasecmp(atom + 1, "ackoff-cutoff"))
+ return BACKOFF_CUTOFF;
+ if (!strcasecmp(atom + 1, "ooting"))
+ return BOOTING;
+ if (!strcasecmp(atom + 1, "oot-unknown-clients"))
+ return BOOT_UNKNOWN_CLIENTS;
+ if (!strcasecmp(atom + 1, "reak"))
+ return BREAK;
+ if (!strcasecmp(atom + 1, "illing"))
+ return BILLING;
+ if (!strcasecmp(atom + 1, "oolean"))
+ return BOOLEAN;
+ if (!strcasecmp(atom + 1, "alance"))
+ return BALANCE;
+ if (!strcasecmp(atom + 1, "ound"))
+ return BOUND;
+ if (!strcasecmp(atom+1, "ig-endian")) {
+ return TOKEN_BIG_ENDIAN;
+ }
+ break;
+ case 'c':
+ if (!strcasecmp(atom + 1, "ase"))
+ return CASE;
+ if (!strcasecmp(atom + 1, "heck"))
+ return CHECK;
+ if (!strcasecmp(atom + 1, "iaddr"))
+ return CIADDR;
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 'l') {
+ if (!strcasecmp(atom + 2, "ass"))
+ return CLASS;
+ if (!strncasecmp(atom + 2, "ient", 4)) {
+ if (!strcasecmp(atom + 6, "s"))
+ return CLIENTS;
+ if (atom[6] == '-') {
+ if (!strcasecmp(atom + 7, "hostname"))
+ return CLIENT_HOSTNAME;
+ if (!strcasecmp(atom + 7, "identifier"))
+ return CLIENT_IDENTIFIER;
+ if (!strcasecmp(atom + 7, "state"))
+ return CLIENT_STATE;
+ if (!strcasecmp(atom + 7, "updates"))
+ return CLIENT_UPDATES;
+ break;
+ }
+ break;
+ }
+ if (!strcasecmp(atom + 2, "ose"))
+ return TOKEN_CLOSE;
+ if (!strcasecmp(atom + 2, "tt"))
+ return CLTT;
+ break;
+ }
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 'o') {
+ if (!strcasecmp(atom + 2, "de"))
+ return CODE;
+ if (isascii(atom[2]) &&
+ tolower((unsigned char)atom[2]) == 'm') {
+ if (!strcasecmp(atom + 3, "mit"))
+ return COMMIT;
+ if (!strcasecmp(atom + 3,
+ "munications-interrupted"))
+ return COMMUNICATIONS_INTERRUPTED;
+ if (!strcasecmp(atom + 3, "pressed"))
+ return COMPRESSED;
+ break;
+ }
+ if (isascii(atom[2]) &&
+ tolower((unsigned char)atom[2]) == 'n') {
+ if (!strcasecmp(atom + 3, "cat"))
+ return CONCAT;
+ if (!strcasecmp(atom + 3, "fig-option"))
+ return CONFIG_OPTION;
+ if (!strcasecmp(atom + 3, "flict-done"))
+ return CONFLICT_DONE;
+ if (!strcasecmp(atom + 3, "nect"))
+ return CONNECT;
+ break;
+ }
+ break;
+ }
+ if (!strcasecmp(atom + 1, "reate"))
+ return TOKEN_CREATE;
+ break;
+ case 'd':
+ if (!strcasecmp(atom + 1, "b-time-format"))
+ return DB_TIME_FORMAT;
+ if (!strcasecmp(atom + 1, "omain"))
+ return DOMAIN;
+ if (!strncasecmp(atom + 1, "omain-", 6)) {
+ if (!strcasecmp(atom + 7, "name"))
+ return DOMAIN_NAME;
+ if (!strcasecmp(atom + 7, "list"))
+ return DOMAIN_LIST;
+ }
+ if (!strcasecmp(atom + 1, "o-forward-updates"))
+ return DO_FORWARD_UPDATE;
+ /* do-forward-update is included for historical reasons */
+ if (!strcasecmp(atom + 1, "o-forward-update"))
+ return DO_FORWARD_UPDATE;
+ if (!strcasecmp(atom + 1, "ebug"))
+ return TOKEN_DEBUG;
+ if (!strcasecmp(atom + 1, "eny"))
+ return DENY;
+ if (!strcasecmp(atom + 1, "eleted"))
+ return TOKEN_DELETED;
+ if (!strcasecmp(atom + 1, "elete"))
+ return TOKEN_DELETE;
+ if (!strncasecmp(atom + 1, "efault", 6)) {
+ if (!atom [7])
+ return DEFAULT;
+ if (!strcasecmp(atom + 7, "-duid"))
+ return DEFAULT_DUID;
+ if (!strcasecmp(atom + 7, "-lease-time"))
+ return DEFAULT_LEASE_TIME;
+ break;
+ }
+ if (!strncasecmp(atom + 1, "ynamic", 6)) {
+ if (!atom [7])
+ return DYNAMIC;
+ if (!strncasecmp(atom + 7, "-bootp", 6)) {
+ if (!atom [13])
+ return DYNAMIC_BOOTP;
+ if (!strcasecmp(atom + 13, "-lease-cutoff"))
+ return DYNAMIC_BOOTP_LEASE_CUTOFF;
+ if (!strcasecmp(atom + 13, "-lease-length"))
+ return DYNAMIC_BOOTP_LEASE_LENGTH;
+ break;
+ }
+ }
+ if (!strcasecmp(atom + 1, "uplicates"))
+ return DUPLICATES;
+ if (!strcasecmp(atom + 1, "eclines"))
+ return DECLINES;
+ if (!strncasecmp(atom + 1, "efine", 5)) {
+ if (!strcasecmp(atom + 6, "d"))
+ return DEFINED;
+ if (!atom [6])
+ return DEFINE;
+ }
+ break;
+ case 'e':
+ if (isascii(atom [1]) &&
+ tolower((unsigned char)atom[1]) == 'x') {
+ if (!strcasecmp(atom + 2, "tract-int"))
+ return EXTRACT_INT;
+ if (!strcasecmp(atom + 2, "ists"))
+ return EXISTS;
+ if (!strcasecmp(atom + 2, "piry"))
+ return EXPIRY;
+ if (!strcasecmp(atom + 2, "pire"))
+ return EXPIRE;
+ if (!strcasecmp(atom + 2, "pired"))
+ return TOKEN_EXPIRED;
+ }
+ if (!strcasecmp(atom + 1, "ncode-int"))
+ return ENCODE_INT;
+ if (!strcasecmp(atom + 1, "poch"))
+ return EPOCH;
+ if (!strcasecmp(atom + 1, "thernet"))
+ return ETHERNET;
+ if (!strcasecmp(atom + 1, "nds"))
+ return ENDS;
+ if (!strncasecmp(atom + 1, "ls", 2)) {
+ if (!strcasecmp(atom + 3, "e"))
+ return ELSE;
+ if (!strcasecmp(atom + 3, "if"))
+ return ELSIF;
+ break;
+ }
+ if (!strcasecmp(atom + 1, "rror"))
+ return ERROR;
+ if (!strcasecmp(atom + 1, "val"))
+ return EVAL;
+ if (!strcasecmp(atom + 1, "ncapsulate"))
+ return ENCAPSULATE;
+ if (!strcasecmp(atom + 1, "xecute"))
+ return EXECUTE;
+ if (!strcasecmp(atom+1, "n")) {
+ return EN;
+ }
+ break;
+ case 'f':
+ if (!strcasecmp(atom + 1, "atal"))
+ return FATAL;
+ if (!strcasecmp(atom + 1, "ilename"))
+ return FILENAME;
+ if (!strcasecmp(atom + 1, "ixed-address"))
+ return FIXED_ADDR;
+ if (!strcasecmp(atom + 1, "ixed-address6"))
+ return FIXED_ADDR6;
+ if (!strcasecmp(atom + 1, "ixed-prefix6"))
+ return FIXED_PREFIX6;
+ if (!strcasecmp(atom + 1, "ddi"))
+ return TOKEN_FDDI;
+ if (!strcasecmp(atom + 1, "ormerr"))
+ return NS_FORMERR;
+ if (!strcasecmp(atom + 1, "unction"))
+ return FUNCTION;
+ if (!strcasecmp(atom + 1, "ailover"))
+ return FAILOVER;
+ if (!strcasecmp(atom + 1, "ree"))
+ return TOKEN_FREE;
+ break;
+ case 'g':
+ if (!strncasecmp(atom + 1, "et", 2)) {
+ if (!strcasecmp(atom + 3, "-lease-hostnames"))
+ return GET_LEASE_HOSTNAMES;
+ if (!strcasecmp(atom + 3, "hostbyname"))
+ return GETHOSTBYNAME;
+ if (!strcasecmp(atom + 3, "hostname"))
+ return GETHOSTNAME;
+ break;
+ }
+ if (!strcasecmp(atom + 1, "iaddr"))
+ return GIADDR;
+ if (!strcasecmp(atom + 1, "roup"))
+ return GROUP;
+ break;
+ case 'h':
+ if (!strcasecmp(atom + 1, "ash"))
+ return HASH;
+ if (!strcasecmp(atom + 1, "ba"))
+ return HBA;
+ if (!strcasecmp(atom + 1, "ost"))
+ return HOST;
+ if (!strcasecmp(atom + 1, "ost-decl-name"))
+ return HOST_DECL_NAME;
+ if (!strcasecmp(atom + 1, "ost-identifier"))
+ return HOST_IDENTIFIER;
+ if (!strcasecmp(atom + 1, "ardware"))
+ return HARDWARE;
+ if (!strcasecmp(atom + 1, "ostname"))
+ return HOSTNAME;
+ if (!strcasecmp(atom + 1, "elp"))
+ return TOKEN_HELP;
+ if (!strcasecmp(atom + 1, "ex")) {
+ return TOKEN_HEX;
+ }
+ break;
+ case 'i':
+ if (!strcasecmp(atom+1, "a-na"))
+ return IA_NA;
+ if (!strcasecmp(atom+1, "a-ta"))
+ return IA_TA;
+ if (!strcasecmp(atom+1, "a-pd"))
+ return IA_PD;
+ if (!strcasecmp(atom+1, "aaddr"))
+ return IAADDR;
+ if (!strcasecmp(atom+1, "aprefix"))
+ return IAPREFIX;
+ if (!strcasecmp(atom + 1, "nclude"))
+ return INCLUDE;
+ if (!strcasecmp(atom + 1, "nteger"))
+ return INTEGER;
+ if (!strcasecmp(atom + 1, "nfiniband"))
+ return TOKEN_INFINIBAND;
+ if (!strcasecmp(atom + 1, "nfinite"))
+ return INFINITE;
+ if (!strcasecmp(atom + 1, "nfo"))
+ return INFO;
+ if (!strcasecmp(atom + 1, "p-address"))
+ return IP_ADDRESS;
+ if (!strcasecmp(atom + 1, "p6-address"))
+ return IP6_ADDRESS;
+ if (!strcasecmp(atom + 1, "nitial-interval"))
+ return INITIAL_INTERVAL;
+ if (!strcasecmp(atom + 1, "nitial-delay"))
+ return INITIAL_DELAY;
+ if (!strcasecmp(atom + 1, "nterface"))
+ return INTERFACE;
+ if (!strcasecmp(atom + 1, "dentifier"))
+ return IDENTIFIER;
+ if (!strcasecmp(atom + 1, "f"))
+ return IF;
+ if (!strcasecmp(atom + 1, "s"))
+ return IS;
+ if (!strcasecmp(atom + 1, "gnore"))
+ return IGNORE;
+ break;
+ case 'k':
+ if (!strncasecmp(atom + 1, "nown", 4)) {
+ if (!strcasecmp(atom + 5, "-clients"))
+ return KNOWN_CLIENTS;
+ if (!atom[5])
+ return KNOWN;
+ break;
+ }
+ if (!strcasecmp(atom + 1, "ey"))
+ return KEY;
+ if (!strcasecmp (atom + 1, "ey-algorithm"))
+ return KEY_ALGORITHM;
+ break;
+ case 'l':
+ if (!strcasecmp(atom + 1, "case"))
+ return LCASE;
+ if (!strcasecmp(atom + 1, "ease"))
+ return LEASE;
+ if (!strcasecmp(atom + 1, "ease6"))
+ return LEASE6;
+ if (!strcasecmp(atom + 1, "eased-address"))
+ return LEASED_ADDRESS;
+ if (!strcasecmp(atom + 1, "ease-time"))
+ return LEASE_TIME;
+ if (!strcasecmp(atom + 1, "easequery"))
+ return LEASEQUERY;
+ if (!strcasecmp(atom + 1, "ength"))
+ return LENGTH;
+ if (!strcasecmp(atom + 1, "imit"))
+ return LIMIT;
+ if (!strcasecmp(atom + 1, "et"))
+ return LET;
+ if (!strcasecmp(atom + 1, "oad"))
+ return LOAD;
+ if (!strcasecmp(atom + 1, "ocal"))
+ return LOCAL;
+ if (!strcasecmp(atom + 1, "og"))
+ return LOG;
+ if (!strcasecmp(atom+1, "lt")) {
+ return LLT;
+ }
+ if (!strcasecmp(atom+1, "l")) {
+ return LL;
+ }
+ if (!strcasecmp(atom+1, "ittle-endian")) {
+ return TOKEN_LITTLE_ENDIAN;
+ }
+ if (!strcasecmp(atom + 1, "ease-id-format")) {
+ return LEASE_ID_FORMAT;
+ }
+ break;
+ case 'm':
+ if (!strncasecmp(atom + 1, "ax", 2)) {
+ if (!atom [3])
+ return TOKEN_MAX;
+ if (!strcasecmp(atom + 3, "-balance"))
+ return MAX_BALANCE;
+ if (!strncasecmp(atom + 3, "-lease-", 7)) {
+ if (!strcasecmp(atom + 10, "misbalance"))
+ return MAX_LEASE_MISBALANCE;
+ if (!strcasecmp(atom + 10, "ownership"))
+ return MAX_LEASE_OWNERSHIP;
+ if (!strcasecmp(atom + 10, "time"))
+ return MAX_LEASE_TIME;
+ }
+ if (!strcasecmp(atom + 3, "-life"))
+ return MAX_LIFE;
+ if (!strcasecmp(atom + 3, "-transmit-idle"))
+ return MAX_TRANSMIT_IDLE;
+ if (!strcasecmp(atom + 3, "-response-delay"))
+ return MAX_RESPONSE_DELAY;
+ if (!strcasecmp(atom + 3, "-unacked-updates"))
+ return MAX_UNACKED_UPDATES;
+ }
+ if (!strncasecmp(atom + 1, "in-", 3)) {
+ if (!strcasecmp(atom + 4, "balance"))
+ return MIN_BALANCE;
+ if (!strcasecmp(atom + 4, "lease-time"))
+ return MIN_LEASE_TIME;
+ if (!strcasecmp(atom + 4, "secs"))
+ return MIN_SECS;
+ break;
+ }
+ if (!strncasecmp(atom + 1, "edi", 3)) {
+ if (!strcasecmp(atom + 4, "a"))
+ return MEDIA;
+ if (!strcasecmp(atom + 4, "um"))
+ return MEDIUM;
+ break;
+ }
+ if (!strcasecmp(atom + 1, "atch"))
+ return MATCH;
+ if (!strcasecmp(atom + 1, "embers"))
+ return MEMBERS;
+ if (!strcasecmp(atom + 1, "y"))
+ return MY;
+ if (!strcasecmp(atom + 1, "clt"))
+ return MCLT;
+ break;
+ case 'n':
+ if (!strcasecmp(atom + 1, "ormal"))
+ return NORMAL;
+ if (!strcasecmp(atom + 1, "ameserver"))
+ return NAMESERVER;
+ if (!strcasecmp(atom + 1, "etmask"))
+ return NETMASK;
+ if (!strcasecmp(atom + 1, "ever"))
+ return NEVER;
+ if (!strcasecmp(atom + 1, "ext-server"))
+ return NEXT_SERVER;
+ if (!strcasecmp(atom + 1, "ot"))
+ return TOKEN_NOT;
+ if (!strcasecmp(atom + 1, "o"))
+ return TOKEN_NO;
+ if (!strcasecmp(atom + 1, "oerror"))
+ return NS_NOERROR;
+ if (!strcasecmp(atom + 1, "otauth"))
+ return NS_NOTAUTH;
+ if (!strcasecmp(atom + 1, "otimp"))
+ return NS_NOTIMP;
+ if (!strcasecmp(atom + 1, "otzone"))
+ return NS_NOTZONE;
+ if (!strcasecmp(atom + 1, "xdomain"))
+ return NS_NXDOMAIN;
+ if (!strcasecmp(atom + 1, "xrrset"))
+ return NS_NXRRSET;
+ if (!strcasecmp(atom + 1, "ull"))
+ return TOKEN_NULL;
+ if (!strcasecmp(atom + 1, "ext"))
+ return TOKEN_NEXT;
+ if (!strcasecmp(atom + 1, "ew"))
+ return TOKEN_NEW;
+ break;
+ case 'o':
+ if (!strcasecmp(atom + 1, "mapi"))
+ return OMAPI;
+ if (!strcasecmp(atom + 1, "r"))
+ return OR;
+ if (!strcasecmp(atom + 1, "n"))
+ return ON;
+ if (!strcasecmp(atom + 1, "pen"))
+ return TOKEN_OPEN;
+ if (!strcasecmp(atom + 1, "ption"))
+ return OPTION;
+ if (!strcasecmp(atom + 1, "ne-lease-per-client"))
+ return ONE_LEASE_PER_CLIENT;
+ if (!strcasecmp(atom + 1, "f"))
+ return OF;
+ if (!strcasecmp(atom + 1, "wner"))
+ return OWNER;
+ if (!strcasecmp(atom + 1, "ctal")) {
+ return TOKEN_OCTAL;
+ }
+ break;
+ case 'p':
+ if (!strcasecmp(atom + 1, "arse-vendor-option"))
+ return PARSE_VENDOR_OPT;
+ if (!strcasecmp(atom + 1, "repend"))
+ return PREPEND;
+ if (!strcasecmp(atom + 1, "referred-life"))
+ return PREFERRED_LIFE;
+ if (!strcasecmp(atom + 1, "acket"))
+ return PACKET;
+ if (!strcasecmp(atom + 1, "ool"))
+ return POOL;
+ if (!strcasecmp(atom + 1, "ool6"))
+ return POOL6;
+ if (!strcasecmp(atom + 1, "refix6"))
+ return PREFIX6;
+ if (!strcasecmp(atom + 1, "seudo"))
+ return PSEUDO;
+ if (!strcasecmp(atom + 1, "eer"))
+ return PEER;
+ if (!strcasecmp(atom + 1, "rimary"))
+ return PRIMARY;
+ if (!strcasecmp(atom + 1, "rimary6"))
+ return PRIMARY6;
+ if (!strncasecmp(atom + 1, "artner", 6)) {
+ if (!atom [7])
+ return PARTNER;
+ if (!strcasecmp(atom + 7, "-down"))
+ return PARTNER_DOWN;
+ }
+ if (!strcasecmp(atom + 1, "ort"))
+ return PORT;
+ if (!strcasecmp(atom + 1, "otential-conflict"))
+ return POTENTIAL_CONFLICT;
+ if (!strcasecmp(atom + 1, "ick-first-value") ||
+ !strcasecmp(atom + 1, "ick"))
+ return PICK;
+ if (!strcasecmp(atom + 1, "aused"))
+ return PAUSED;
+ break;
+ case 'r':
+ if (!strcasecmp(atom + 1, "ange"))
+ return RANGE;
+ if (!strcasecmp(atom + 1, "ange6"))
+ return RANGE6;
+ if (isascii(atom[1]) &&
+ (tolower((unsigned char)atom[1]) == 'e')) {
+ if (!strcasecmp(atom + 2, "bind"))
+ return REBIND;
+ if (!strcasecmp(atom + 2, "boot"))
+ return REBOOT;
+ if (!strcasecmp(atom + 2, "contact-interval"))
+ return RECONTACT_INTERVAL;
+ if (!strncasecmp(atom + 2, "cover", 5)) {
+ if (atom[7] == '\0')
+ return RECOVER;
+ if (!strcasecmp(atom + 7, "-done"))
+ return RECOVER_DONE;
+ if (!strcasecmp(atom + 7, "-wait"))
+ return RECOVER_WAIT;
+ break;
+ }
+ if (!strcasecmp(atom + 2, "fresh"))
+ return REFRESH;
+ if (!strcasecmp(atom + 2, "fused"))
+ return NS_REFUSED;
+ if (!strcasecmp(atom + 2, "ject"))
+ return REJECT;
+ if (!strcasecmp(atom + 2, "lease"))
+ return RELEASE;
+ if (!strcasecmp(atom + 2, "leased"))
+ return TOKEN_RELEASED;
+ if (!strcasecmp(atom + 2, "move"))
+ return REMOVE;
+ if (!strcasecmp(atom + 2, "new"))
+ return RENEW;
+ if (!strcasecmp(atom + 2, "quest"))
+ return REQUEST;
+ if (!strcasecmp(atom + 2, "quire"))
+ return REQUIRE;
+ if (isascii(atom[2]) &&
+ (tolower((unsigned char)atom[2]) == 's')) {
+ if (!strcasecmp(atom + 3, "erved"))
+ return TOKEN_RESERVED;
+ if (!strcasecmp(atom + 3, "et"))
+ return TOKEN_RESET;
+ if (!strcasecmp(atom + 3,
+ "olution-interrupted"))
+ return RESOLUTION_INTERRUPTED;
+ break;
+ }
+ if (!strcasecmp(atom + 2, "try"))
+ return RETRY;
+ if (!strcasecmp(atom + 2, "turn"))
+ return RETURN;
+ if (!strcasecmp(atom + 2, "verse"))
+ return REVERSE;
+ if (!strcasecmp(atom + 2, "wind"))
+ return REWIND;
+ break;
+ }
+ break;
+ case 's':
+ if (!strcasecmp(atom + 1, "cript"))
+ return SCRIPT;
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 'e') {
+ if (!strcasecmp(atom + 2, "arch"))
+ return SEARCH;
+ if (isascii(atom[2]) &&
+ tolower((unsigned char)atom[2]) == 'c') {
+ if (!strncasecmp(atom + 3, "ond", 3)) {
+ if (!strcasecmp(atom + 6, "ary"))
+ return SECONDARY;
+ if (!strcasecmp(atom + 6, "ary6"))
+ return SECONDARY6;
+ if (!strcasecmp(atom + 6, "s"))
+ return SECONDS;
+ break;
+ }
+ if (!strcasecmp(atom + 3, "ret"))
+ return SECRET;
+ break;
+ }
+ if (!strncasecmp(atom + 2, "lect", 4)) {
+ if (atom[6] == '\0')
+ return SELECT;
+ if (!strcasecmp(atom + 6, "-timeout"))
+ return SELECT_TIMEOUT;
+ break;
+ }
+ if (!strcasecmp(atom + 2, "nd"))
+ return SEND;
+ if (!strncasecmp(atom + 2, "rv", 2)) {
+ if (!strncasecmp(atom + 4, "er", 2)) {
+ if (atom[6] == '\0')
+ return TOKEN_SERVER;
+ if (atom[6] == '-') {
+ if (!strcasecmp(atom + 7,
+ "duid"))
+ return SERVER_DUID;
+ if (!strcasecmp(atom + 7,
+ "name"))
+ return SERVER_NAME;
+ if (!strcasecmp(atom + 7,
+ "identifier"))
+ return SERVER_IDENTIFIER;
+ break;
+ }
+ break;
+ }
+ if (!strcasecmp(atom + 4, "fail"))
+ return NS_SERVFAIL;
+ break;
+ }
+ if (!strcasecmp(atom + 2, "t"))
+ return TOKEN_SET;
+ break;
+ }
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 'h') {
+ if (!strcasecmp(atom + 2, "ared-network"))
+ return SHARED_NETWORK;
+ if (!strcasecmp(atom + 2, "utdown"))
+ return SHUTDOWN;
+ break;
+ }
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 'i') {
+ if (!strcasecmp(atom + 2, "addr"))
+ return SIADDR;
+ if (!strcasecmp(atom + 2, "gned"))
+ return SIGNED;
+ if (!strcasecmp(atom + 2, "ze"))
+ return SIZE;
+ break;
+ }
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 'p') {
+ if (isascii(atom[2]) &&
+ tolower((unsigned char)atom[2]) == 'a') {
+ if (!strcasecmp(atom + 3, "ce"))
+ return SPACE;
+ if (!strcasecmp(atom + 3, "wn"))
+ return SPAWN;
+ break;
+ }
+ if (!strcasecmp(atom + 2, "lit"))
+ return SPLIT;
+ break;
+ }
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 't') {
+ if (isascii(atom[2]) &&
+ tolower((unsigned char)atom[2]) == 'a') {
+ if (!strncasecmp(atom + 3, "rt", 2)) {
+ if (!strcasecmp(atom + 5, "s"))
+ return STARTS;
+ if (!strcasecmp(atom + 5, "up"))
+ return STARTUP;
+ break;
+ }
+ if (isascii(atom[3]) &&
+ tolower((unsigned char)atom[3]) == 't') {
+ if (!strcasecmp(atom + 4, "e"))
+ return STATE;
+ if (!strcasecmp(atom + 4, "ic"))
+ return STATIC;
+ break;
+ }
+ }
+ if (!strcasecmp(atom + 2, "ring"))
+ return STRING_TOKEN;
+ break;
+ }
+ if (!strncasecmp(atom + 1, "ub", 2)) {
+ if (!strcasecmp(atom + 3, "class"))
+ return SUBCLASS;
+ if (!strcasecmp(atom + 3, "net"))
+ return SUBNET;
+ if (!strcasecmp(atom + 3, "net6"))
+ return SUBNET6;
+ if (!strcasecmp(atom + 3, "string"))
+ return SUBSTRING;
+ break;
+ }
+ if (isascii(atom[1]) &&
+ tolower((unsigned char)atom[1]) == 'u') {
+ if (!strcasecmp(atom + 2, "ffix"))
+ return SUFFIX;
+ if (!strcasecmp(atom + 2, "persede"))
+ return SUPERSEDE;
+ }
+ if (!strcasecmp(atom + 1, "witch"))
+ return SWITCH;
+ break;
+ case 't':
+ if (!strcasecmp(atom + 1, "imestamp"))
+ return TIMESTAMP;
+ if (!strcasecmp(atom + 1, "imeout"))
+ return TIMEOUT;
+ if (!strcasecmp(atom + 1, "oken-ring"))
+ return TOKEN_RING;
+ if (!strcasecmp(atom + 1, "ext"))
+ return TEXT;
+ if (!strcasecmp(atom + 1, "stp"))
+ return TSTP;
+ if (!strcasecmp(atom + 1, "sfp"))
+ return TSFP;
+ if (!strcasecmp(atom + 1, "ransmission"))
+ return TRANSMISSION;
+ if (!strcasecmp(atom + 1, "emporary"))
+ return TEMPORARY;
+ break;
+ case 'u':
+ if (!strcasecmp(atom + 1, "case"))
+ return UCASE;
+ if (!strcasecmp(atom + 1, "nset"))
+ return UNSET;
+ if (!strcasecmp(atom + 1, "nsigned"))
+ return UNSIGNED;
+ if (!strcasecmp(atom + 1, "id"))
+ return UID;
+ if (!strncasecmp(atom + 1, "se", 2)) {
+ if (!strcasecmp(atom + 3, "r-class"))
+ return USER_CLASS;
+ if (!strcasecmp(atom + 3, "-host-decl-names"))
+ return USE_HOST_DECL_NAMES;
+ if (!strcasecmp(atom + 3,
+ "-lease-addr-for-default-route"))
+ return USE_LEASE_ADDR_FOR_DEFAULT_ROUTE;
+ break;
+ }
+ if (!strncasecmp(atom + 1, "nknown", 6)) {
+ if (!strcasecmp(atom + 7, "-clients"))
+ return UNKNOWN_CLIENTS;
+ if (!strcasecmp(atom + 7, "-state"))
+ return UNKNOWN_STATE;
+ if (!atom [7])
+ return UNKNOWN;
+ break;
+ }
+ if (!strcasecmp(atom + 1, "nauthenticated"))
+ return UNAUTHENTICATED;
+ if (!strcasecmp(atom + 1, "pdate"))
+ return UPDATE;
+ break;
+ case 'v':
+ if (!strcasecmp(atom + 1, "6relay"))
+ return V6RELAY;
+ if (!strcasecmp(atom + 1, "6relopt"))
+ return V6RELOPT;
+ if (!strcasecmp(atom + 1, "endor-class"))
+ return VENDOR_CLASS;
+ if (!strcasecmp(atom + 1, "endor"))
+ return VENDOR;
+ break;
+ case 'w':
+ if (!strcasecmp(atom + 1, "ith"))
+ return WITH;
+ if (!strcasecmp(atom + 1, "idth"))
+ return WIDTH;
+ break;
+ case 'y':
+ if (!strcasecmp(atom + 1, "iaddr"))
+ return YIADDR;
+ if (!strcasecmp(atom + 1, "xdomain"))
+ return NS_YXDOMAIN;
+ if (!strcasecmp(atom + 1, "xrrset"))
+ return NS_YXRRSET;
+ break;
+ case 'z':
+ if (!strcasecmp(atom + 1, "erolen"))
+ return ZEROLEN;
+ if (!strcasecmp(atom + 1, "one"))
+ return ZONE;
+ break;
+ }
+ return dfv;
+}
+
diff --git a/keama/confparse.c b/keama/confparse.c
new file mode 100644
index 00000000..5b916064
--- /dev/null
+++ b/keama/confparse.c
@@ -0,0 +1,4992 @@
+/*
+ * Copyright (c) 2017, 2018 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+/* From server/confpars.c */
+
+#include "keama.h"
+
+#include <sys/errno.h>
+#include <arpa/inet.h>
+#include <assert.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Print failover stuff once */
+isc_boolean_t failover_once = ISC_TRUE;
+
+/* To manage host-reservation-identifiers */
+isc_boolean_t use_client_id = ISC_FALSE;
+isc_boolean_t use_flex_id = ISC_FALSE;
+isc_boolean_t use_hw_address = ISC_FALSE;
+
+/* option and relays used for flexible host identifier */
+const struct option *host_id_option = NULL;
+int host_id_relays = 0;
+
+/* Simple or complex config */
+unsigned subnet_counter = 0;
+
+/* For subclass name generation */
+unsigned subclass_counter = 0;
+
+/* To map reservations to declared subnets */
+struct subnet {
+ struct element *subnet;
+ struct element *share;
+ struct string *addr;
+ struct string *mask;
+ TAILQ_ENTRY(subnet) next;
+};
+
+TAILQ_HEAD(subnets, subnet) known_subnets;
+
+/* To map pools to subnets inside a shared-network */
+struct range {
+ struct element *pool;
+ struct element *share;
+ struct string *low;
+ TAILQ_ENTRY(range) next;
+};
+
+TAILQ_HEAD(ranges, range) known_ranges;
+
+static void post_process_lifetimes(struct parse *);
+static size_t post_process_reservations(struct parse *);
+static void post_process_classes(struct parse *);
+static void post_process_generated_classes(struct parse *);
+static void check_depend(struct element *, struct element *);
+static void post_process_option_definitions(struct parse *);
+static void add_host_reservation_identifiers(struct parse *, const char *);
+static void add_host_id_option(struct parse *, const struct option *, int);
+static void subclass_inherit(struct parse *, struct element *,
+ struct element *);
+static void add_match_class(struct parse *, struct element *,
+ struct element *);
+static void option_data_derive(struct parse *, struct handle *,
+ struct handle *);
+static void derive_classes(struct parse *, struct handle *, struct handle *);
+static isc_boolean_t is_hexa_only(const char *, unsigned len);
+static void new_network_interface(struct parse *, struct element *);
+static struct string *addrmask(const struct string *, const struct string *);
+static struct element *find_match(struct parse *, struct element *,
+ isc_boolean_t *);
+static struct element *find_location(struct element *, struct range *);
+static int get_prefix_length(const char *, const char *);
+static struct element *get_class(struct parse *, struct element *);
+static void concat_classes(struct parse *, struct element *, struct element *);
+static void generate_class(struct parse *, struct element *, struct element *,
+ struct element *);
+
+static struct string *CLASS_ALL;
+static struct string *CLASS_KNOWN;
+
+/* Add head config file comments to the DHCP server map */
+
+size_t
+conf_file_parse(struct parse *cfile)
+{
+ struct element *top;
+ struct element *dhcp;
+ size_t issues;
+
+ TAILQ_INIT(&known_subnets);
+ TAILQ_INIT(&known_ranges);
+ CLASS_ALL = makeString(-1, "ALL");
+ CLASS_KNOWN = makeString(-1, "KNOWN");
+
+ top = createMap();
+ top->kind = TOPLEVEL;
+ TAILQ_CONCAT(&top->comments, &cfile->comments);
+
+ dhcp = createMap();
+ dhcp->kind = ROOT_GROUP;
+ (void) peek_token(NULL, NULL, cfile);
+ TAILQ_CONCAT(&dhcp->comments, &cfile->comments);
+ stackPush(cfile, dhcp);
+ assert(cfile->stack_top == 1);
+ cfile->stack[0] = top;
+
+ if (local_family == AF_INET)
+ mapSet(top, dhcp, "Dhcp4");
+ else if (local_family == AF_INET6)
+ mapSet(top, dhcp, "Dhcp6");
+ else
+ parse_error(cfile, "address family is not set");
+
+ issues = conf_file_subparse(cfile, ROOT_GROUP);
+
+ /* Add a warning when interfaces-config is not present */
+ if (subnet_counter > 0) {
+ struct element *ifconf;
+
+ ifconf = mapGet(cfile->stack[1], "interfaces-config");
+ if (ifconf == NULL) {
+ struct comment *comment;
+
+ comment = createComment("/// This configuration "
+ "declares some subnets but "
+ "has no interfaces-config");
+ TAILQ_INSERT_TAIL(&cfile->stack[1]->comments, comment);
+ comment = createComment("/// Reference Kea #245");
+ TAILQ_INSERT_TAIL(&cfile->stack[1]->comments, comment);
+ }
+ }
+
+ post_process_lifetimes(cfile);
+ if (!global_hr)
+ issues += post_process_reservations(cfile);
+ post_process_classes(cfile);
+ post_process_generated_classes(cfile);
+ post_process_option_definitions(cfile);
+
+ return issues;
+}
+
+/* Lifetime post-processing */
+static void
+post_process_lifetimes(struct parse *cfile)
+{
+ struct element *entry;
+
+ entry = mapGet(cfile->stack[1], "valid-lifetime");
+ if ((entry == NULL) && use_isc_lifetimes) {
+ struct comment *comment;
+
+ /* DEFAULT_DEFAULT_LEASE_TIME is 43200 */
+ entry = createInt(43200);
+ comment = createComment("/// Use ISC DHCP default lifetime");
+ TAILQ_INSERT_TAIL(&entry->comments, comment);
+ mapSet(cfile->stack[1], entry, "valid-lifetime");
+ }
+
+ entry = mapGet(cfile->stack[1], "min-valid-lifetime");
+ if ((entry == NULL) && use_isc_lifetimes) {
+ struct comment *comment;
+
+ /* DEFAULT_MIN_LEASE_TIME is 300 */
+ entry = createInt(300);
+ comment = createComment("/// Use ISC DHCP min lifetime");
+ TAILQ_INSERT_TAIL(&entry->comments, comment);
+ mapSet(cfile->stack[1], entry, "min-valid-lifetime");
+ }
+
+ entry = mapGet(cfile->stack[1], "max-valid-lifetime");
+ if ((entry == NULL) && use_isc_lifetimes) {
+ struct comment *comment;
+
+ /* DEFAULT_MAX_LEASE_TIME is 86400 */
+ entry = createInt(86400);
+ comment = createComment("/// Use ISC DHCP max lifetime");
+ TAILQ_INSERT_TAIL(&entry->comments, comment);
+ mapSet(cfile->stack[1], entry, "max-valid-lifetime");
+ }
+
+ /* Done for DHCPv4 */
+ if (local_family == AF_INET)
+ return;
+
+ /* There is no builtin default for preferred-lifetime,
+ nor min/max values in ISC DHCP. */
+}
+
+/* Reservation post-processing */
+
+static size_t
+post_process_reservations(struct parse *cfile)
+{
+ struct element *hosts;
+ struct element *orphans;
+ struct element *host;
+ struct element *where;
+ struct element *dest;
+ isc_boolean_t used_heuristic;
+ size_t issues;
+
+ issues = 0;
+ hosts = mapGet(cfile->stack[1], "reservations");
+ if ((hosts == NULL) || global_hr)
+ return issues;
+ mapRemove(cfile->stack[1], "reservations");
+ orphans = createList();
+ orphans->kind = HOST_DECL;
+ while (listSize(hosts) > 0) {
+ host = listGet(hosts, 0);
+ listRemove(hosts, 0);
+ used_heuristic = ISC_FALSE;
+ where = find_match(cfile, host, &used_heuristic);
+ if (where == cfile->stack[1])
+ dest = orphans;
+ else
+ dest = mapGet(where, "reservations");
+ if (dest == NULL) {
+ dest = createList();
+ dest->kind = HOST_DECL;
+ mapSet(where, dest, "reservations");
+ }
+ listPush(dest, host);
+ }
+ if (listSize(orphans) > 0) {
+ struct comment *comment;
+
+ comment = createComment("/// Orphan reservations");
+ TAILQ_INSERT_TAIL(&orphans->comments, comment);
+ comment = createComment("/// Kea reservations are per subnet");
+ TAILQ_INSERT_TAIL(&orphans->comments, comment);
+ comment = createComment("/// Reference Kea #231");
+ TAILQ_INSERT_TAIL(&orphans->comments, comment);
+ orphans->skip = ISC_TRUE;
+ issues++;
+ mapSet(cfile->stack[1], orphans, "reservations");
+ }
+ return issues;
+}
+
+/* Cleanup classes */
+
+static void
+post_process_classes(struct parse *cfile)
+{
+ struct element *classes;
+ struct element *class;
+ struct element *name;
+ struct element *entry;
+ struct element *reduced;
+ struct string *msg;
+ struct comment *comment;
+ isc_boolean_t lose;
+ size_t i;
+
+ classes = mapGet(cfile->stack[1], "client-classes");
+ if ((classes == NULL) || (listSize(classes) == 0))
+ return;
+ for (i = 0; i < listSize(classes); i++) {
+ class = listGet(classes, i);
+ if ((class == NULL) || (class->type != ELEMENT_MAP))
+ parse_error(cfile, "null global class at %i",
+ (unsigned)i);
+ name = mapGet(class, "name");
+ if ((name == NULL) || (name->type != ELEMENT_STRING))
+ parse_error(cfile, "global class at %u "
+ "without a name", (unsigned)i);
+ if (!mapContains(class, "super"))
+ goto cleanup_superclass;
+
+ /* cleanup subclass */
+ mapRemove(class,"super");
+ entry = mapGet(class, "string");
+ if (entry != NULL) {
+ if (entry->type != ELEMENT_STRING)
+ parse_error(cfile, "subclass %s has "
+ "a bad string selector",
+ stringValue(name)->content);
+ msg = makeString(-1, "/// subclass selector ");
+ appendString(msg, "'");
+ concatString(msg, stringValue(entry));
+ appendString(msg, "'");
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&class->comments, comment);
+ mapRemove(class, "string");
+ continue;
+ }
+ entry = mapGet(class, "binary");
+ if (entry == NULL)
+ parse_error(cfile, "subclass %s has no selector",
+ stringValue(name)->content);
+ msg = makeString(-1, "/// subclass selector 0x");
+ concatString(msg, stringValue(entry));
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&class->comments, comment);
+ mapRemove(class, "binary");
+
+ cleanup_superclass:
+ /* cleanup superclass */
+ entry = mapGet(class, "spawning");
+ if (entry == NULL)
+ goto cleanup_class;
+ if (entry->type != ELEMENT_BOOLEAN)
+ parse_error(cfile, "superclass %s has bad "
+ "spawning flag",
+ stringValue(name)->content);
+ if (boolValue(entry)) {
+ msg = makeString(-1, "/// Spawning classes "
+ "are not supported by Kea");
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&class->comments, comment);
+ msg = makeString(-1, "/// Reference Kea #248");
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&class->comments, comment);
+ msg = makeString(-1, "/// spawn with: ");
+ } else
+ msg = makeString(-1, "/// match: ");
+ entry = mapGet(class, "submatch");
+
+ if (entry == NULL)
+ parse_error(cfile, "superclass %s has no submatch",
+ stringValue(name)->content);
+ lose = ISC_FALSE;
+ appendString(msg, print_data_expression(entry, &lose));
+ if (!lose) {
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&class->comments, comment);
+ mapRemove(class, "spawning");
+ mapRemove(class, "submatch");
+ }
+
+ cleanup_class:
+ /* cleanup class */
+ entry = mapGet(class, "match-if");
+ if (entry == NULL)
+ continue;
+ reduced = mapGet(class, "test");
+ lose = ISC_FALSE;
+ if (reduced != NULL)
+ msg = makeString(-1, "/// from: match if ");
+ else
+ msg = makeString(-1, "/// match if ");
+ appendString(msg, print_boolean_expression(entry, &lose));
+ if (!lose) {
+ comment = createComment(msg->content);
+ if (reduced != NULL) {
+ TAILQ_INSERT_TAIL(&reduced->comments, comment);
+ mapRemove(class, "match-if");
+ continue;
+ }
+ TAILQ_INSERT_TAIL(&entry->comments, comment);
+ }
+ }
+}
+
+/* Move generated client classes to the end of client class list */
+
+static void
+post_process_generated_classes(struct parse *cfile)
+{
+ struct element *generated;
+ struct element *classes;
+ struct element *class;
+
+ generated = mapGet(cfile->stack[1], "generated-classes");
+ if (generated == NULL)
+ return;
+ mapRemove(cfile->stack[1], "generated-classes");
+ if (listSize(generated) == 0)
+ return;
+ classes = mapGet(cfile->stack[1], "client-classes");
+ if (classes == NULL) {
+ classes = createList();
+ mapSet(cfile->stack[1], classes, "client-classes");
+ }
+
+ while (listSize(generated) > 0) {
+ class = listGet(generated, 0);
+ listRemove(generated, 0);
+ check_depend(class, classes);
+ listPush(classes, class);
+ }
+}
+
+static void
+check_depend(struct element *class, struct element *classes)
+{
+ struct element *list;
+
+ if (!mapContains(class, "depend"))
+ return;
+ list = mapGet(class, "depend");
+ mapRemove(class, "depend");
+ while (listSize(list) > 0) {
+ struct element *depend;
+ struct string *dname;
+ struct string *msg;
+ struct comment *comment;
+ isc_boolean_t found;
+ size_t i;
+
+ depend = listGet(list, 0);
+ listRemove(list, 0);
+ assert(depend != NULL);
+ assert(depend->type == ELEMENT_STRING);
+ dname = stringValue(depend);
+ if (eqString(dname, CLASS_ALL) ||
+ eqString(dname, CLASS_KNOWN))
+ continue;
+ found = ISC_FALSE;
+ for (i = 0; i < listSize(classes); i++) {
+ struct element *item;
+ struct element *name;
+
+ item = listGet(classes, i);
+ assert(item != NULL);
+ assert(item->type == ELEMENT_MAP);
+ name = mapGet(item, "name");
+ if (name == NULL)
+ continue;
+ assert(name->type == ELEMENT_STRING);
+ if (eqString(stringValue(name), dname)) {
+ found = ISC_TRUE;
+ break;
+ }
+ }
+ if (found)
+ continue;
+ msg = makeString(-1, "/// Depend on missing '");
+ concatString(msg, dname);
+ appendString(msg, "' class");
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&class->comments, comment);
+ class->skip = ISC_TRUE;
+ }
+}
+
+static void
+post_process_option_definitions(struct parse *cfile)
+{
+ struct element *opt_def;
+ struct element *def, *ndef;
+
+ opt_def = mapGet(cfile->stack[1], "option-def");
+ if (opt_def == NULL)
+ return;
+ TAILQ_FOREACH_SAFE(def, &opt_def->value.list_value, ndef) {
+ if (mapContains(def, "no-export"))
+ TAILQ_REMOVE(&opt_def->value.list_value, def);
+ }
+}
+
+void
+read_conf_file(struct parse *parent, const char *filename, int group_type)
+{
+ int file;
+ struct parse *cfile;
+ struct string *msg;
+ struct comment *comment;
+ size_t amount = parent->stack_size * sizeof(struct element *);
+
+ if ((file = open (filename, O_RDONLY)) < 0)
+
+ parse_error(parent, "Can't open %s: %s",
+ filename, strerror(errno));
+
+ cfile = new_parse(file, NULL, 0, filename, 0);
+ if (cfile == NULL)
+ parse_error(parent, "Can't create new parse structure");
+
+ cfile->stack = (struct element **)malloc(amount);
+ if (cfile->stack == NULL)
+ parse_error(parent, "Can't create new element stack");
+ memcpy(cfile->stack, parent->stack, amount);
+ cfile->stack_size = parent->stack_size;
+ cfile->stack_top = parent->stack_top;
+ cfile->issue_counter = parent->issue_counter;
+
+ msg = makeString(-1, "/// Begin file ");
+ concatString(msg, makeString(-1, filename));
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&cfile->comments, comment);
+
+ conf_file_subparse(cfile, group_type);
+
+ amount = cfile->stack_size * sizeof(struct element *);
+ if (cfile->stack_size > parent->stack_size) {
+ parent->stack =
+ (struct element **)realloc(parent->stack, amount);
+ if (parent->stack == NULL)
+ parse_error(cfile, "can't resize element stack");
+ }
+ memcpy(parent->stack, cfile->stack, amount);
+ parent->stack_size = cfile->stack_size;
+ parent->stack_top = cfile->stack_top;
+ parent->issue_counter = cfile->issue_counter;
+ msg = makeString(-1, "/// End file ");
+ concatString(msg, makeString(-1, filename));
+ comment= createComment(msg->content);
+ TAILQ_INSERT_TAIL(&parent->comments, comment);
+ end_parse(cfile);
+}
+
+/* conf-file :== parameters declarations END_OF_FILE
+ parameters :== <nil> | parameter | parameters parameter
+ declarations :== <nil> | declaration | declarations declaration */
+
+size_t
+conf_file_subparse(struct parse *cfile, int type)
+{
+ const char *val;
+ enum dhcp_token token;
+ isc_boolean_t declaration = ISC_FALSE;
+
+ for (;;) {
+ token = peek_token(&val, NULL, cfile);
+ if (token == END_OF_FILE)
+ break;
+ declaration = parse_statement(cfile, type, declaration);
+ }
+ skip_token(&val, NULL, cfile);
+
+ return cfile->issue_counter;
+}
+
+/* statement :== parameter | declaration | PERCENT directive
+
+ parameter :== DEFAULT_LEASE_TIME lease_time
+ | MAX_LEASE_TIME lease_time
+ | DYNAMIC_BOOTP_LEASE_CUTOFF date
+ | DYNAMIC_BOOTP_LEASE_LENGTH lease_time
+ | BOOT_UNKNOWN_CLIENTS boolean
+ | ONE_LEASE_PER_CLIENT boolean
+ | GET_LEASE_HOSTNAMES boolean
+ | USE_HOST_DECL_NAME boolean
+ | NEXT_SERVER ip-addr-or-hostname SEMI
+ | option_parameter
+ | SERVER-IDENTIFIER ip-addr-or-hostname SEMI
+ | FILENAME string-parameter
+ | SERVER_NAME string-parameter
+ | hardware-parameter
+ | fixed-address-parameter
+ | ALLOW allow-deny-keyword
+ | DENY allow-deny-keyword
+ | USE_LEASE_ADDR_FOR_DEFAULT_ROUTE boolean
+ | AUTHORITATIVE
+ | NOT AUTHORITATIVE
+
+ declaration :== host-declaration
+ | group-declaration
+ | shared-network-declaration
+ | subnet-declaration
+ | VENDOR_CLASS class-declaration
+ | USER_CLASS class-declaration
+ | RANGE address-range-declaration */
+
+isc_boolean_t
+parse_statement(struct parse *cfile, int type, isc_boolean_t declaration)
+{
+ enum dhcp_token token;
+ const char *val;
+ struct element *hardware;
+ struct element *cache;
+ struct element *et;
+ isc_boolean_t lose;
+ isc_boolean_t known;
+ isc_boolean_t authoritative;
+ struct option *option;
+ size_t host_decl = 0;
+ size_t subnet = 0;
+ size_t i;
+
+ token = peek_token(&val, NULL, cfile);
+
+ switch (token) {
+ case INCLUDE:
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != STRING)
+ parse_error(cfile, "filename string expected.");
+ read_conf_file(cfile, val, type);
+ parse_semi(cfile);
+ return 1;
+
+ case HOST:
+ skip_token(&val, NULL, cfile);
+ if (type != HOST_DECL && type != CLASS_DECL)
+ parse_host_declaration(cfile);
+ else
+ parse_error(cfile,
+ "host declarations not allowed here.");
+ return 1;
+
+ case GROUP:
+ skip_token(&val, NULL, cfile);
+ if (type != HOST_DECL && type != CLASS_DECL)
+ parse_group_declaration(cfile);
+ else
+ parse_error(cfile,
+ "group declarations not allowed here.");
+ return 1;
+
+ case SHARED_NETWORK:
+ skip_token(&val, NULL, cfile);
+ if (type == SHARED_NET_DECL ||
+ type == HOST_DECL ||
+ type == SUBNET_DECL ||
+ type == CLASS_DECL)
+ parse_error(cfile, "shared-network parameters not %s.",
+ "allowed here");
+ parse_shared_net_declaration(cfile);
+ return 1;
+
+ case SUBNET:
+ case SUBNET6:
+ skip_token(&val, NULL, cfile);
+ if (type == HOST_DECL || type == SUBNET_DECL ||
+ type == CLASS_DECL)
+ parse_error(cfile,
+ "subnet declarations not allowed here.");
+
+ if (token == SUBNET)
+ parse_subnet_declaration(cfile);
+ else
+ parse_subnet6_declaration(cfile);
+ return 1;
+
+ case VENDOR_CLASS:
+ case USER_CLASS:
+ case CLASS:
+ case SUBCLASS:
+ skip_token(&val, NULL, cfile);
+ if (token == VENDOR_CLASS)
+ parse_error(cfile, "obsolete 'vendor-class' "
+ "declaration");
+ if (token == USER_CLASS)
+ parse_error(cfile, "obsolete 'user-class' "
+ "declaration");
+ if (type == CLASS_DECL)
+ parse_error(cfile,
+ "class declarations not allowed here.");
+ parse_class_declaration(cfile, token == CLASS
+ ? CLASS_TYPE_CLASS
+ : CLASS_TYPE_SUBCLASS);
+ return 1;
+
+ case HARDWARE:
+ if (!use_hw_address) {
+ add_host_reservation_identifiers(cfile,
+ "hw-address");
+ use_hw_address = ISC_TRUE;
+ }
+
+ skip_token(&val, NULL, cfile);
+ if (!host_decl) {
+ for (i = cfile->stack_top; i > 0; --i) {
+ if (cfile->stack[i]->kind == HOST_DECL) {
+ host_decl = i;
+ break;
+ }
+ }
+ }
+ if (!host_decl)
+ parse_error(cfile, "hardware address parameter %s",
+ "not allowed here.");
+ if (mapContains(cfile->stack[host_decl], "hw-address"))
+ parse_error(cfile, "Host hardware address already "
+ "configured.");
+ hardware = parse_hardware_param(cfile);
+ mapSet(cfile->stack[host_decl], hardware, "hw-address");
+ if (hardware->skip)
+ cfile->stack[host_decl]->skip = ISC_TRUE;
+ break;
+
+ case FIXED_ADDR:
+ case FIXED_ADDR6:
+ skip_token(&val, NULL, cfile);
+ if (!host_decl) {
+ for (i = cfile->stack_top; i > 0; --i) {
+ if (cfile->stack[i]->kind == HOST_DECL) {
+ host_decl = i;
+ break;
+ }
+ }
+ }
+ if (!host_decl)
+ parse_error(cfile,
+ "fixed-address parameter not "
+ "allowed here.");
+ cache = parse_fixed_addr_param(cfile, token);
+ if (token == FIXED_ADDR) {
+ struct element *addr;
+
+ if (mapContains(cfile->stack[host_decl], "ip-address"))
+ parse_error(cfile, "Only one fixed address "
+ "declaration per host.");
+ addr = listGet(cache, 0);
+ listRemove(cache, 0);
+ mapSet(cfile->stack[host_decl], addr, "ip-address");
+ if (listSize(cache) > 0) {
+ cache->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(cfile->stack[host_decl],
+ cache, "extra-ip-addresses");
+ }
+ } else {
+ if (mapContains(cfile->stack[host_decl],
+ "ip-addresses"))
+ parse_error(cfile, "Only one fixed address "
+ "declaration per host.");
+ mapSet(cfile->stack[host_decl], cache, "ip-addresses");
+ }
+ break;
+
+ case POOL:
+ skip_token(&val, NULL, cfile);
+ if (type == POOL_DECL)
+ parse_error(cfile, "pool declared within pool.");
+ if (type != SUBNET_DECL && type != SHARED_NET_DECL)
+ parse_error(cfile, "pool declared outside of network");
+ parse_pool_statement(cfile, type);
+
+ return declaration;
+
+ case RANGE:
+ skip_token(&val, NULL, cfile);
+ if (!subnet) {
+ for (i = cfile->stack_top; i > 0; --i) {
+ if (cfile->stack[i]->kind == SUBNET_DECL) {
+ subnet = i;
+ break;
+ }
+ }
+ }
+ if (type != SUBNET_DECL || !subnet)
+ parse_error(cfile,
+ "range declaration not allowed here.");
+ parse_address_range(cfile, type, subnet);
+ return declaration;
+
+ case RANGE6:
+ if (local_family != AF_INET6)
+ goto unknown;
+ skip_token(NULL, NULL, cfile);
+ if (!subnet) {
+ for (i = cfile->stack_top; i > 0; --i) {
+ if (cfile->stack[i]->kind == SUBNET_DECL) {
+ subnet = i;
+ break;
+ }
+ }
+ }
+ if ((type != SUBNET_DECL) || !subnet)
+ parse_error(cfile,
+ "range6 declaration not allowed here.");
+ parse_address_range6(cfile, type, subnet);
+ return declaration;
+
+ case PREFIX6:
+ if (local_family != AF_INET6)
+ goto unknown;
+ skip_token(NULL, NULL, cfile);
+ if (!subnet) {
+ for (i = cfile->stack_top; i > 0; --i) {
+ if (cfile->stack[i]->kind == SUBNET_DECL) {
+ subnet = i;
+ break;
+ }
+ }
+ }
+ if ((type != SUBNET_DECL) || !subnet)
+ parse_error(cfile,
+ "prefix6 declaration not allowed here.");
+ parse_prefix6(cfile, type, subnet);
+ return declaration;
+
+ case FIXED_PREFIX6:
+ if (local_family != AF_INET6)
+ goto unknown;
+ skip_token(&val, NULL, cfile);
+ if (!host_decl) {
+ for (i = cfile->stack_top; i > 0; --i) {
+ if (cfile->stack[i]->kind == HOST_DECL) {
+ host_decl = i;
+ break;
+ }
+ }
+ }
+ if (!host_decl)
+ parse_error(cfile,
+ "fixed-prefix6 declaration not "
+ "allowed here.");
+ parse_fixed_prefix6(cfile, host_decl);
+ break;
+
+ case POOL6:
+ if (local_family != AF_INET6)
+ goto unknown;
+ skip_token(&val, NULL, cfile);
+ if (type == POOL_DECL)
+ parse_error(cfile, "pool6 declared within pool.");
+ if (type != SUBNET_DECL)
+ parse_error(cfile,
+ "pool6 declared outside of network");
+ parse_pool6_statement(cfile, type);
+
+ return declaration;
+
+ case TOKEN_NOT:
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ switch (token) {
+ case AUTHORITATIVE:
+ authoritative = ISC_FALSE;
+ goto authoritative;
+ default:
+ parse_error(cfile, "expecting assertion");
+ }
+ break;
+
+ case AUTHORITATIVE:
+ skip_token(&val, NULL, cfile);
+ authoritative = ISC_TRUE;
+ authoritative:
+ if (type == HOST_DECL)
+ parse_error(cfile, "authority makes no sense here.");
+ if (!subnet) {
+ for (i = cfile->stack_top; i > 0; --i) {
+ int kind;
+
+ kind = cfile->stack[i]->kind;
+ if ((kind == SUBNET_DECL) ||
+ (kind == SHARED_NET_DECL) ||
+ (kind == ROOT_GROUP)) {
+ subnet = i;
+ break;
+ }
+ }
+ }
+ if (!subnet)
+ parse_error(cfile, "can't find root group");
+ if (local_family == AF_INET) {
+ cache = createBool(authoritative);
+ TAILQ_CONCAT(&cache->comments, &cfile->comments);
+ mapSet(cfile->stack[subnet], cache, "authoritative");
+ }
+ parse_semi(cfile);
+ break;
+
+ /* "server-identifier" is a special hack, equivalent to
+ "option dhcp-server-identifier". */
+ case SERVER_IDENTIFIER:
+ option = option_lookup_code("dhcp",
+ DHO_DHCP_SERVER_IDENTIFIER);
+ assert(option);
+ skip_token(&val, NULL, cfile);
+ goto finish_option;
+
+ case OPTION:
+ skip_token(&val, NULL, cfile);
+ token = peek_token(&val, NULL, cfile);
+ if (token == SPACE) {
+ if (type != ROOT_GROUP)
+ parse_error(cfile,
+ "option space definitions %s",
+ "may not be scoped.");
+ parse_option_space_decl(cfile);
+ return declaration;
+ }
+
+ known = ISC_FALSE;
+ option = parse_option_name(cfile, ISC_TRUE, &known);
+ token = peek_token(&val, NULL, cfile);
+ if (token == CODE) {
+ if (type != ROOT_GROUP)
+ parse_error(cfile,
+ "option definitions%s",
+ " may not be scoped.");
+ skip_token(&val, NULL, cfile);
+
+ /* next function must deal with redefinitions */
+ parse_option_code_definition(cfile, option);
+ return declaration;
+ }
+ /* If this wasn't an option code definition, don't
+ allow an unknown option. */
+ if (!known)
+ parse_error(cfile, "unknown option %s.%s",
+ option->space->old, option->old);
+ finish_option:
+ parse_option_statement(NULL, cfile, option,
+ supersede_option_statement);
+ return declaration;
+
+ case FAILOVER:
+ if (failover_once)
+ fprintf(stderr, "ignoring failover\n");
+ failover_once = ISC_FALSE;
+ skip_to_semi(cfile);
+ break;
+
+ case SERVER_DUID:
+ if (local_family != AF_INET6)
+ goto unknown;
+ parse_server_duid_conf(cfile);
+ break;
+
+ case LEASE_ID_FORMAT:
+ token = next_token(&val, NULL, cfile);
+ /* ignore: ISC DHCP specific */
+ break;
+
+ case PERCENT:
+ skip_token(&val, NULL, cfile);
+ if (type != ROOT_GROUP)
+ parse_error(cfile, "directives are only supported "
+ "at toplevel");
+ parse_directive(cfile);
+ return declaration;
+
+ unknown:
+ skip_token(&val, NULL, cfile);
+
+ default:
+ et = createMap();
+ TAILQ_CONCAT(&et->comments, &cfile->comments);
+ lose = ISC_FALSE;
+ if (!parse_executable_statement(et, cfile, &lose,
+ context_any, ISC_TRUE)) {
+ if (!lose) {
+ if (declaration)
+ parse_error(cfile,
+ "expecting a declaration");
+ else
+ parse_error(cfile,
+ "expecting a parameter %s",
+ "or declaration");
+ }
+ return declaration;
+ }
+ if (mapSize(et) == 0)
+ return declaration;
+
+ et->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(cfile->stack[cfile->stack_top], et, "statement");
+ }
+
+ return 0;
+}
+
+/*!
+ *
+ * \brief Parse allow and deny statements
+ *
+ * This function handles the common processing code for permit and deny
+ * statements in the parse_pool_statement and parse_pool6_statement functions.
+ *
+ * The allow or deny token should already be consumed, this function expects
+ * one of the following:
+ * known-clients;
+ * unknown-clients;
+ * known clients;
+ * unknown clients;
+ * authenticated clients;
+ * unauthenticated clients;
+ * all clients;
+ * dynamic bootp clients;
+ * members of <class name>;
+ * after <date>;
+ *
+ * \param[in] cfile = the configuration file being parsed
+ * \param[in] permit_head = the head of the permit list (permit or prohibit)
+ * to which to attach the newly created permit structure
+ */
+
+void
+get_permit(struct parse *cfile, struct element *permit_head)
+{
+ enum dhcp_token token;
+ const char *val;
+ struct string *permit;
+ struct string *alias = NULL;
+ struct comment *comment = NULL;
+ struct element *member;
+ isc_boolean_t need_clients = ISC_TRUE;
+ isc_boolean_t negative = ISC_FALSE;
+
+ token = next_token(&val, NULL, cfile);
+ switch (token) {
+ case UNKNOWN:
+ permit = CLASS_KNOWN;
+ negative = ISC_TRUE;
+ alias = makeString(-1, "unknown clients");
+ break;
+
+ case KNOWN_CLIENTS:
+ need_clients = ISC_FALSE;
+ permit = CLASS_KNOWN;
+ alias = makeString(-1, "known-clients");
+ break;
+
+ case UNKNOWN_CLIENTS:
+ need_clients = ISC_FALSE;
+ permit = CLASS_KNOWN;
+ negative = ISC_TRUE;
+ alias = makeString(-1, "unknown-clients");
+ break;
+
+ case KNOWN:
+ permit = CLASS_KNOWN;
+ alias = makeString(-1, "known clients");
+ break;
+
+ case AUTHENTICATED:
+ permit = CLASS_ALL;
+ alias = makeString(-1, "authenticated clients");
+ negative = ISC_TRUE;
+ authenticated_clients:
+ comment = createComment("/// [un]authenticated-clients is "
+ "not supported by ISC DHCP and Kea");
+ break;
+
+ case UNAUTHENTICATED:
+ permit = CLASS_ALL;
+ alias = makeString(-1, "unauthenticated clients");
+ goto authenticated_clients;
+ break;
+
+ case ALL:
+ permit = CLASS_ALL;
+ alias = makeString(-1, "all clients");
+ break;
+
+ case DYNAMIC:
+ /* bootp is not supported by Kea so the dynamic bootp
+ * client set is the empty set. */
+ if (next_token(&val, NULL, cfile) != TOKEN_BOOTP)
+ parse_error(cfile, "expecting \"bootp\"");
+ permit = CLASS_ALL;
+ negative = ISC_TRUE;
+ alias = makeString(-1, "dynamic bootp clients");
+ cfile->issue_counter++;
+ comment = createComment("/// dynamic-bootp-client is not "
+ "supported by Kea");
+ break;
+
+ case MEMBERS:
+ /* we don't check the class... */
+ need_clients = ISC_FALSE;
+ if (next_token(&val, NULL, cfile) != OF)
+ parse_error(cfile, "expecting \"of\"");
+ if (next_token(&val, NULL, cfile) != STRING)
+ parse_error(cfile, "expecting class name.");
+ permit = makeString(-1, val);
+ break;
+
+ case AFTER:
+ /* don't use parse_date_code() */
+ need_clients = ISC_FALSE;
+ permit = makeString(-1, "AFTER_");
+ alias = makeString(-1, "after ");
+ while (peek_raw_token(NULL, NULL, cfile) != SEMI) {
+ next_raw_token(&val, NULL, cfile);
+ appendString(permit, val);
+ appendString(alias, val);
+ }
+ permit_head->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ comment = createComment("/// after <date> is not yet "
+ "supported by Kea");
+ break;
+
+ default:
+ parse_error(cfile, "expecting permit type.");
+ }
+
+ /*
+ * The need_clients flag is set if we are expecting the
+ * CLIENTS token
+ */
+ if (need_clients && (next_token(&val, NULL, cfile) != CLIENTS))
+ parse_error(cfile, "expecting \"clients\"");
+ member = createMap();
+ mapSet(member, createString(permit), "class");
+ mapSet(member, createBool(!negative), "way");
+ if (alias != NULL)
+ mapSet(member, createString(alias), "real");
+ if (comment != NULL)
+ TAILQ_INSERT_TAIL(&permit_head->comments, comment);
+ listPush(permit_head, member);
+ parse_semi(cfile);
+
+ return;
+}
+
+/*!
+ *
+ * \brief Parse a pool statement
+ *
+ * Pool statements are used to group declarations and permit & deny information
+ * with a specific address range. They must be declared within a shared network
+ * or subnet and there may be multiple pools withing a shared network or subnet.
+ * Each pool may have a different set of permit or deny options.
+ *
+ * \param[in] cfile = the configuration file being parsed
+ * \param[in] type = the type of the enclosing statement. This must be
+ * SHARED_NET_DECL or SUBNET_DECL for this function.
+ *
+ * \return
+ * void - This function either parses the statement and updates the structures
+ * or it generates an error message and possible halts the program if
+ * it encounters a problem.
+ */
+void
+parse_pool_statement(struct parse *cfile, int type)
+{
+ enum dhcp_token token;
+ const char *val;
+ isc_boolean_t done = ISC_FALSE;
+ struct element *pool;
+ struct element *pools;
+ struct element *permit;
+ struct element *prohibit;
+ int declaration = 0;
+ unsigned range_counter = 0;
+
+ pool = createMap();
+ pool->kind = POOL_DECL;
+ TAILQ_CONCAT(&pool->comments, &cfile->comments);
+
+ if (type != SUBNET_DECL && type != SHARED_NET_DECL)
+ parse_error(cfile, "Dynamic pools are only valid inside "
+ "subnet or shared-network statements.");
+ parse_lbrace(cfile);
+
+ stackPush(cfile, pool);
+ type = POOL_DECL;
+
+ permit = createList();
+ prohibit = createList();
+
+ do {
+ token = peek_token(&val, NULL, cfile);
+ switch (token) {
+ case TOKEN_NO:
+ case FAILOVER:
+ if (failover_once)
+ fprintf(stderr, "ignoring failover\n");
+ failover_once = ISC_FALSE;
+ skip_to_semi(cfile);
+ break;
+
+ case RANGE:
+ skip_token(&val, NULL, cfile);
+ parse_address_range(cfile, type, cfile->stack_top);
+ range_counter++;
+ break;
+
+ case ALLOW:
+ skip_token(&val, NULL, cfile);
+ get_permit(cfile, permit);
+ break;
+
+ case DENY:
+ skip_token(&val, NULL, cfile);
+ get_permit(cfile, prohibit);
+ break;
+
+ case RBRACE:
+ skip_token(&val, NULL, cfile);
+ done = ISC_TRUE;
+ break;
+
+ case END_OF_FILE:
+ /*
+ * We can get to END_OF_FILE if, for instance,
+ * the parse_statement() reads all available tokens
+ * and leaves us at the end.
+ */
+ parse_error(cfile, "unexpected end of file");
+
+ default:
+ declaration = parse_statement(cfile, type,
+ declaration);
+ break;
+ }
+ } while (!done);
+
+ cfile->stack_top--;
+
+ generate_class(cfile, pool, permit, prohibit);
+
+ pools = mapGet(cfile->stack[cfile->stack_top], "pools");
+ if (pools == NULL) {
+ pools = createList();
+ pools->kind = POOL_DECL;
+ mapSet(cfile->stack[cfile->stack_top], pools, "pools");
+ }
+ if (range_counter == 0) {
+ struct comment *comment;
+
+ /* no range */
+ comment = createComment("empty pool");
+ TAILQ_INSERT_TAIL(&pool->comments, comment);
+ pool->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ listPush(pools, pool);
+ return;
+ }
+ /* spread extra ranges into pool copies */
+ while (--range_counter != 0) {
+ struct handle *handle;
+ struct element *first;
+ struct element *saved;
+ isc_boolean_t seen = ISC_FALSE;
+
+ first = createMap();
+ saved = copy(pool);
+ TAILQ_CONCAT(&first->comments, &pool->comments);
+ while (mapSize(pool) > 0) {
+ handle = mapPop(pool);
+ if ((handle == NULL) || (handle->key == NULL) ||
+ (handle->value == NULL))
+ parse_error(cfile, "bad pool entry");
+ if (strcmp(handle->key, "pool") != 0)
+ mapSet(first, handle->value, handle->key);
+ else if (!seen) {
+ mapSet(first, handle->value, handle->key);
+ mapRemove(saved, "pool");
+ seen = ISC_TRUE;
+ }
+ }
+ listPush(pools, first);
+ pool = saved;
+ }
+ listPush(pools, pool);
+}
+
+/* Expect a left brace */
+
+void
+parse_lbrace(struct parse *cfile)
+{
+ enum dhcp_token token;
+ const char *val;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE)
+ parse_error(cfile, "expecting left brace.");
+}
+
+/* host-declaration :== hostname RBRACE parameters declarations LBRACE */
+
+void
+parse_host_declaration(struct parse *cfile)
+{
+ const char *val;
+ enum dhcp_token token;
+ struct element *host;
+ struct string *name;
+ struct element *where;
+ struct element *hosts = NULL;
+ int declaration = 0;
+ isc_boolean_t used_heuristic = ISC_FALSE;
+
+ host = createMap();
+ host->kind = HOST_DECL;
+ TAILQ_CONCAT(&host->comments, &cfile->comments);
+
+ name = parse_host_name(cfile);
+ if (!name)
+ parse_error(cfile, "expecting a name for host declaration.");
+
+ mapSet(host, createString(name), "hostname");
+
+ parse_lbrace(cfile);
+
+ stackPush(cfile, host);
+
+ for (;;) {
+ token = peek_token(&val, NULL, cfile);
+ if (token == RBRACE) {
+ skip_token(&val, NULL, cfile);
+ break;
+ }
+ if (token == END_OF_FILE)
+ parse_error(cfile, "unexpected end of file");
+ /* If the host declaration was created by the server,
+ remember to save it. */
+ if (token == DYNAMIC) {
+ skip_token(&val, NULL, cfile);
+ parse_error(cfile, "dynamic hosts don't exist "
+ "in the config file");
+ }
+ /* If the host declaration was created by the server,
+ remember to save it. */
+ if (token == TOKEN_DELETED) {
+ skip_token(&val, NULL, cfile);
+ parse_error(cfile, "deleted hosts don't exist "
+ "in the config file");
+ }
+
+ if (token == GROUP) {
+ struct element *group;
+ struct comment *comment;
+
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != STRING && !is_identifier(token))
+ parse_error(cfile,
+ "expecting string or identifier.");
+ group = createString(makeString(-1, val));
+ group->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ comment = createComment("/// Unsupported group in "
+ "host reservations");
+ TAILQ_INSERT_TAIL(&group->comments, comment);
+ comment = createComment("/// Reference Kea #233");
+ TAILQ_INSERT_TAIL(&group->comments, comment);
+ mapSet(host, group, "group");
+ parse_semi(cfile);
+ continue;
+ }
+
+ if (token == UID) {
+ struct string *client_id;
+
+ if (!use_client_id) {
+ add_host_reservation_identifiers(cfile,
+ "client-id");
+ use_client_id = ISC_TRUE;
+ }
+
+ skip_token(&val, NULL, cfile);
+
+ if (mapContains(host, "client-id"))
+ parse_error(cfile, "Host %s already has a "
+ "client identifier.",
+ name->content);
+
+ /* See if it's a string or a cshl. */
+ token = peek_token(&val, NULL, cfile);
+ if (token == STRING) {
+ skip_token(&val, NULL, cfile);
+ client_id = makeString(-1, val);
+ } else {
+ struct string *bin;
+ unsigned len = 0;
+
+ bin = parse_numeric_aggregate
+ (cfile, NULL, &len, ':', 16, 8);
+ if (!bin)
+ parse_error(cfile,
+ "expecting hex list.");
+ client_id = makeStringExt(bin->length,
+ bin->content, 'H');
+ }
+ mapSet(host, createString(client_id), "client-id");
+
+ parse_semi(cfile);
+ continue;
+ }
+
+ if (token == HOST_IDENTIFIER) {
+ struct string *host_id;
+ isc_boolean_t known;
+ struct option *option;
+ struct element *expr;
+ struct string *data;
+ int relays = 0;
+
+ if (!use_flex_id) {
+ add_host_reservation_identifiers(cfile,
+ "flex-id");
+ use_flex_id = ISC_TRUE;
+ }
+
+ if (mapContains(host, "host-identifier") ||
+ mapContains(host, "flex-id"))
+ parse_error(cfile,
+ "only one host-identifier allowed "
+ "per host");
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ host_id = makeString(-1, val);
+ appendString(host_id, " ");
+ if (token == V6RELOPT) {
+ token = next_token(&val, NULL, cfile);
+
+ if (token != NUMBER)
+ parse_error(cfile,
+ "host-identifier v6relopt "
+ "must have a number");
+ appendString(host_id, val);
+ appendString(host_id, " ");
+ relays = atoi(val);
+ if (relays < 0)
+ parse_error(cfile,
+ "host-identifier v6relopt "
+ "must have a number >= 0");
+ if (relays > MAX_V6RELAY_HOPS)
+ relays = MAX_V6RELAY_HOPS + 1;
+ } else if (token != OPTION)
+ parse_error(cfile,
+ "host-identifier must be an option"
+ " or v6relopt");
+ known = ISC_FALSE;
+ option = parse_option_name(cfile, ISC_TRUE, &known);
+ if (!known)
+ parse_error(cfile, "unknown option %s.%s",
+ option->space->old, option->old);
+ appendString(host_id, option->space->name);
+ appendString(host_id, ".");
+ appendString(host_id, option->name);
+ appendString(host_id, " ");
+
+ data = parse_option_textbin(cfile, option);
+ parse_semi(cfile);
+
+ if (data == NULL)
+ parse_error(cfile, "can't get option data");
+ concatString(host_id, data);
+ expr = createString(host_id);
+ expr->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(host, expr, "host-identifier");
+
+ if (host_id_option == NULL)
+ add_host_id_option(cfile, option, relays);
+ else if ((host_id_option != option) ||
+ (host_id_relays != relays)) {
+ struct string *msg;
+ struct comment *comment;
+
+ msg = allocString();
+ appendString(msg, "/// Another option (");
+ appendString(msg, host_id_option->name);
+ appendString(msg, ") is already used as ");
+ appendString(msg, "host-identifier");
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ continue;
+ }
+
+ /*
+ * Everything good: set a flex-id and remove
+ * the host-identifier entry.
+ */
+ mapSet(host, createString(data), "flex-id");
+ mapRemove(host, "host-identifier");
+ continue;
+ }
+
+ declaration = parse_statement(cfile, HOST_DECL, declaration);
+ }
+
+ cfile->stack_top--;
+
+ where = find_match(cfile, host, &used_heuristic);
+ hosts = mapGet(where, "reservations");
+ if (hosts == NULL) {
+ hosts = createList();
+ hosts->kind = HOST_DECL;
+ mapSet(where, hosts, "reservations");
+ if (used_heuristic) {
+ struct comment *comment;
+
+ comment = createComment("/// Host reservations "
+ "without fixed addresses "
+ "were put in the last "
+ "declared subnet");
+ TAILQ_INSERT_TAIL(&hosts->comments, comment);
+ comment = createComment("/// Reference Kea #231");
+ TAILQ_INSERT_TAIL(&hosts->comments, comment);
+ }
+ }
+ listPush(hosts, host);
+}
+
+/* Simple tool to declare used (and only used) reservation identifiers */
+static void
+add_host_reservation_identifiers(struct parse *cfile, const char *id)
+{
+ struct element *ids;
+
+ ids = mapGet(cfile->stack[1], "host-reservation-identifiers");
+ if (ids == NULL) {
+ ids = createList();
+ mapSet(cfile->stack[1], ids, "host-reservation-identifiers");
+ }
+ listPush(ids, createString(makeString(-1, id)));
+}
+
+/* Add the flexible host identifier glue */
+static void
+add_host_id_option(struct parse *cfile,
+ const struct option *option, int relays)
+{
+ struct string *path;
+ struct string *expr;
+ struct element *params;
+ struct element *entry;
+ struct element *hooks;
+ struct comment *comment;
+ char buf[40];
+
+ host_id_option = option;
+ host_id_relays = relays;
+
+ /*
+ * Using the example from the Kea Administrator Reference Manual
+ * as recommended by Tomek
+ */
+ hooks = createList();
+ mapSet(cfile->stack[1], hooks, "hooks-libraries");
+ comment = createComment("/// The flexible host identifier "
+ "is a premium feature");
+ TAILQ_INSERT_TAIL(&hooks->comments, comment);
+ entry = createMap();
+ listPush(hooks, entry);
+ if (hook_library_path != NULL)
+ path = makeString(-1, hook_library_path);
+ else
+ path = makeString(-1, "/path/");
+ appendString(path, "libdhcp_flex_id.so");
+ params = createString(path);
+ if (hook_library_path == NULL) {
+ comment = createComment("/// Please update the path here");
+ TAILQ_INSERT_TAIL(&params->comments, comment);
+ }
+ mapSet(entry, params, "library");
+ params = createMap();
+ mapSet(entry, params, "parameters");
+
+ snprintf(buf, sizeof(buf), "%soption[%u].hex",
+ relays > 0 ? "relay[0]." : "", option->code);
+ expr = makeString(-1, buf);
+ mapSet(params, createString(expr), "identifier-expression");
+}
+
+static void add_host_reservation_identifiers(struct parse *, const char *);
+/* class-declaration :== STRING LBRACE parameters declarations RBRACE
+ *
+ * in fact:
+ * (CLASS) NAME(STRING) LBRACE ... RBRACE
+ * (SUBCLASS) SUPER(STRING) DATA/HASH(STRING | <hexa>) [BRACE ... RBRACE]
+ *
+ * class "name" { MATCH IF <boolean-expr> }: direct: belong when true
+ * class "name" { MATCH <data-expr> }: indirect: use subclasses
+ * class "name" { MATCH <data-expr> SPAWN WITH <data-expr> }: indirect:
+ * create dynamically a subclass
+ * subclass "super" <data-expr = string or binary aka hash>: belongs when
+ * super <data-expr> == <hash>
+ */
+
+void
+parse_class_declaration(struct parse *cfile, int type)
+{
+ const char *val = NULL;
+ enum dhcp_token token;
+ size_t group = 0;
+ size_t i = 0;
+ struct element *group_classes = NULL;
+ struct element *classes = NULL;
+ struct element *class = NULL;
+ struct element *pc = NULL; /* p(arent)c(lass) */
+ struct element *tmp = NULL;
+ struct element *expr = NULL;
+ struct element *data = NULL;
+ isc_boolean_t binary = ISC_FALSE;
+ int declaration = 0;
+ struct string *name = NULL;
+ isc_boolean_t lose = ISC_FALSE;
+ isc_boolean_t matchedonce = ISC_FALSE;
+ isc_boolean_t submatchedonce = ISC_FALSE;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != STRING)
+ parse_error(cfile, "Expecting class name");
+
+ /* Find group and root classes */
+ classes = mapGet(cfile->stack[1], "client-classes");
+ if (classes == NULL) {
+ classes = createList();
+ classes->kind = CLASS_DECL;
+ mapSet(cfile->stack[1], classes, "client-classes");
+ }
+ for (group = cfile->stack_top; group > 0; --group) {
+ int kind;
+
+ kind = cfile->stack[group]->kind;
+ if (kind == CLASS_DECL)
+ parse_error(cfile, "class in class");
+ if ((kind == GROUP_DECL) || (kind == ROOT_GROUP))
+ break;
+ }
+ if (!group)
+ parse_error(cfile, "can't find root group");
+ if (cfile->stack[group]->kind == GROUP_DECL) {
+ group_classes = mapGet(cfile->stack[group], "client-classes");
+ if (group_classes == NULL) {
+ group_classes = createList();
+ group_classes->kind = CLASS_DECL;
+ mapSet(cfile->stack[group], group_classes,
+ "client-classes");
+ }
+ } else
+ group_classes = classes;
+
+ /* See if there's already a class with the specified name. */
+ for (i = 0; i < listSize(classes); i++) {
+ struct element *name;
+
+ tmp = listGet(classes, i);
+ name = mapGet(tmp, "name");
+ if (name == NULL)
+ continue;
+ if (strcmp(stringValue(name)->content, val) == 0) {
+ pc = tmp;
+ break;
+ }
+ }
+
+ /* If it is a class, we're updating it. If it's any of the other
+ * types (subclass, vendor or user class), the named class is a
+ * reference to the parent class so its mandatory.
+ */
+ if ((pc != NULL) && (type == CLASS_TYPE_CLASS)) {
+ class = pc;
+ pc = NULL;
+ } else if (type != CLASS_TYPE_CLASS) {
+ if (pc == NULL)
+ parse_error(cfile, "no class named %s", val);
+ if (!mapContains(pc, "spawning") ||
+ !mapContains(pc, "submatch"))
+ parse_error(cfile, "found class name %s but it is "
+ "not a suitable superclass", val);
+ }
+
+ name = makeString(-1, val);
+ /* If this is a straight subclass, parse the hash string. */
+ if (type == CLASS_TYPE_SUBCLASS) {
+ token = peek_token(&val, NULL, cfile);
+ if (token == STRING) {
+ unsigned len;
+
+ skip_token(&val, &len, cfile);
+ data = createString(makeString(len, val));
+ } else if (token == NUMBER_OR_NAME || token == NUMBER) {
+ data = createHexa(parse_hexa(cfile));
+ binary = ISC_TRUE;
+ } else {
+ skip_token(&val, NULL, cfile);
+ parse_error(cfile, "Expecting string or hex list.");
+ }
+ }
+
+ /* See if there's already a class in the hash table matching the
+ hash data. */
+ if (type != CLASS_TYPE_CLASS) {
+ for (i = 0; i < listSize(classes); i++) {
+ struct element *super;
+ struct element *selector;
+
+ tmp = listGet(classes, i);
+ super = mapGet(tmp, "super");
+ if (super == NULL)
+ continue;
+ if (!eqString(stringValue(super), name))
+ continue;
+ if (binary)
+ selector = mapGet(tmp, "binary");
+ else
+ selector = mapGet(tmp, "string");
+ if (selector == NULL)
+ continue;
+ if (eqString(stringValue(selector),
+ stringValue(data))) {
+ class = tmp;
+ break;
+ }
+ }
+ }
+
+ /* Note the class declaration in the enclosing group */
+ if (group_classes != classes) {
+ struct element *gc;
+
+ gc = createMap();
+ gc->kind = CLASS_DECL;
+ tmp = createString(name);
+ if (type == CLASS_TYPE_CLASS)
+ mapSet(gc, tmp, "name");
+ else {
+ tmp->skip = ISC_TRUE;
+ mapSet(gc, tmp, "super");
+ data->skip = ISC_TRUE;
+ if (binary)
+ mapSet(gc, data, "binary");
+ else
+ mapSet(gc, data, "string");
+ }
+ listPush(group_classes, gc);
+ }
+
+ /* If we didn't find an existing class, allocate a new one. */
+ if (!class) {
+ /* Allocate the class structure... */
+ class = createMap();
+ class->kind = CLASS_DECL;
+ TAILQ_CONCAT(&class->comments, &cfile->comments);
+ if (type == CLASS_TYPE_SUBCLASS) {
+ struct string *subname;
+ char buf[40];
+
+ cfile->issue_counter++;
+ tmp = createString(name);
+ tmp->skip = ISC_TRUE;
+ mapSet(class, tmp, "super");
+ data->skip = ISC_TRUE;
+ if (binary)
+ mapSet(class, data, "binary");
+ else
+ mapSet(class, data, "string");
+ subname = makeString(-1, "sub#");
+ concatString(subname, name);
+ snprintf(buf, sizeof(buf),
+ "#%u", subclass_counter++);
+ appendString(subname, buf);
+ mapSet(class, createString(subname), "name");
+ } else
+ /* Save the name, if there is one. */
+ mapSet(class, createString(name), "name");
+ listPush(classes, class);
+ }
+
+ /* Spawned classes don't have to have their own settings. */
+ if (type == CLASS_TYPE_SUBCLASS) {
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI) {
+ skip_token(&val, NULL, cfile);
+ subclass_inherit(cfile, class, copy(pc));
+ return;
+ }
+ }
+
+ parse_lbrace(cfile);
+
+ stackPush(cfile, class);
+
+ for (;;) {
+ token = peek_token(&val, NULL, cfile);
+ if (token == RBRACE) {
+ skip_token(&val, NULL, cfile);
+ break;
+ } else if (token == END_OF_FILE) {
+ skip_token(&val, NULL, cfile);
+ parse_error(cfile, "unexpected end of file");
+ } else if (token == DYNAMIC) {
+ skip_token(&val, NULL, cfile);
+ parse_error(cfile, "dynamic classes don't exist "
+ "in the config file");
+ } else if (token == TOKEN_DELETED) {
+ skip_token(&val, NULL, cfile);
+ parse_error(cfile, "deleted hosts don't exist "
+ "in the config file");
+ } else if (token == MATCH) {
+ skip_token(&val, NULL, cfile);
+ if (pc)
+ parse_error(cfile,
+ "invalid match in subclass.");
+ token = peek_token(&val, NULL, cfile);
+ if (token != IF) {
+ expr = createBool(ISC_FALSE);
+ expr->skip = 1;
+ mapSet(class, expr, "spawning");
+ goto submatch;
+ }
+
+ skip_token(&val, NULL, cfile);
+ if (matchedonce)
+ parse_error(cfile,
+ "A class may only have "
+ "one 'match if' clause.");
+ matchedonce = ISC_TRUE;
+ expr = createMap();
+ if (!parse_boolean_expression(expr, cfile, &lose)) {
+ if (!lose)
+ parse_error(cfile,
+ "expecting boolean expr.");
+ } else {
+ expr->skip = ISC_TRUE;
+ mapSet(class, expr, "match-if");
+ add_match_class(cfile, class, copy(expr));
+ parse_semi(cfile);
+ }
+ } else if (token == SPAWN) {
+ skip_token(&val, NULL, cfile);
+ if (pc)
+ parse_error(cfile,
+ "invalid spawn in subclass.");
+ expr = createBool(ISC_TRUE);
+ expr->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(class, expr, "spawning");
+ token = next_token(&val, NULL, cfile);
+ if (token != WITH)
+ parse_error(cfile,
+ "expecting with after spawn");
+ submatch:
+ if (submatchedonce)
+ parse_error(cfile,
+ "can't override existing "
+ "submatch/spawn");
+ submatchedonce = ISC_TRUE;
+ expr = createMap();
+ if (!parse_data_expression(expr, cfile, &lose)) {
+ if (!lose)
+ parse_error(cfile,
+ "expecting data expr.");
+ } else {
+ expr->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(class, expr, "submatch");
+ parse_semi(cfile);
+ }
+ } else if (token == LEASE) {
+ struct comment *comment;
+
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != LIMIT)
+ parse_error(cfile, "expecting \"limit\"");
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile, "expecting a number");
+ tmp = createInt(atoll(val));
+ tmp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ comment = createComment("/// Per-class limit is not "
+ "supported by Kea");
+ TAILQ_INSERT_TAIL(&tmp->comments, comment);
+ comment = createComment("/// Reference Kea #237");
+ TAILQ_INSERT_TAIL(&tmp->comments, comment);
+ mapSet(class, tmp, "lease-limit");
+ parse_semi(cfile);
+ } else
+ declaration = parse_statement(cfile, CLASS_DECL,
+ declaration);
+ }
+
+ cfile->stack_top--;
+
+ if (type == CLASS_TYPE_SUBCLASS)
+ subclass_inherit(cfile, class, copy(pc));
+}
+
+/*
+ * Inherit entries:
+ * - first copy entries from the current superclass to the subclass
+ * - second try to reduce the subclass matching condition
+ */
+
+static void
+subclass_inherit(struct parse *cfile,
+ struct element *class,
+ struct element *superclass)
+{
+ struct string *name;
+ struct element *guard;
+ struct element *submatch;
+ struct handle *handle;
+ struct string *gmsg;
+ struct string *mmsg;
+ struct string *dmsg;
+ struct element *expr;
+ struct element *data;
+ struct element *match;
+ struct element *reduced;
+ unsigned order = 0;
+ struct comment *comment;
+ isc_boolean_t marked = ISC_FALSE;
+ isc_boolean_t lose = ISC_FALSE;
+ isc_boolean_t modified = ISC_FALSE;
+
+ expr = mapGet(superclass, "name");
+ if (expr == NULL)
+ parse_error(cfile, "can't get superclass name");
+ name = stringValue(expr);
+ guard = mapGet(superclass, "match-if");
+ submatch = mapGet(superclass, "submatch");
+ if (submatch == NULL)
+ parse_error(cfile, "can't get superclass submatch");
+
+ /* Iterates on (copy of) superclass entries */
+ while (mapSize(superclass) > 0) {
+ handle = mapPop(superclass);
+ if ((handle == NULL) || (handle->key == NULL) ||
+ (handle->value == NULL))
+ parse_error(cfile, "can't get superclass %s item at "
+ "%u", name->content, order);
+ handle->order = order++;
+ /* Superclass specific entries */
+ if ((strcmp(handle->key, "name") == 0) ||
+ (strcmp(handle->key, "spawning") == 0) ||
+ (strcmp(handle->key, "match-if") == 0) ||
+ (strcmp(handle->key, "test") == 0) ||
+ (strcmp(handle->key, "submatch") == 0))
+ continue;
+ /* Subclass specific so impossible entries */
+ if ((strcmp(handle->key, "super") == 0) ||
+ (strcmp(handle->key, "binary") == 0) ||
+ (strcmp(handle->key, "string") == 0))
+ parse_error(cfile, "superclass %s has unexpected %s "
+ "at %u",
+ name->content, handle->key, order);
+ /* Special entries */
+ if (strcmp(handle->key, "option-data") == 0) {
+ struct element *opt_list;
+
+ opt_list = mapGet(class, handle->key);
+ if (opt_list != NULL)
+ merge_option_data(handle->value, opt_list);
+ else
+ mapSet(class, handle->value, handle->key);
+ continue;
+ }
+ /* Just copy */
+ if ((strcmp(handle->key, "lease-limit") == 0) ||
+ (strcmp(handle->key, "boot-file-name") == 0) ||
+ (strcmp(handle->key, "serverhostname") == 0) ||
+ (strcmp(handle->key, "next-server") == 0)) {
+ mapSet(class, handle->value, handle->key);
+ continue;
+ }
+ /* Unknown */
+ if (!marked) {
+ marked = ISC_TRUE;
+ comment = createComment("/// copied from superclass");
+ TAILQ_INSERT_TAIL(&handle->value->comments, comment);
+ }
+ comment = createComment("/// unhandled entry");
+ TAILQ_INSERT_TAIL(&handle->value->comments, comment);
+ if (!handle->value->skip) {
+ handle->value->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ mapSet(class, handle->value, handle->key);
+ }
+
+ /* build [guard and] submatch = data */
+ expr = mapGet(class, "binary");
+ if (expr != NULL) {
+ data = createMap();
+ mapSet(data, copy(expr), "const-data");
+ } else
+ data = mapGet(class, "string");
+ if (data == NULL)
+ parse_error(cfile, "can't get subclass %s data",
+ name->content);
+ match = createMap();
+ mapSet(match, copy(submatch), "left");
+ mapSet(match, copy(data), "right");
+ expr = createMap();
+ mapSet(expr, match, "equal");
+
+ if (guard != NULL) {
+ match = createMap();
+ mapSet(match, copy(guard), "left");
+ mapSet(match, expr, "right");
+ expr = createMap();
+ mapSet(expr, match, "and");
+
+ gmsg = makeString(-1, "/// from: match-if ");
+ appendString(gmsg, print_boolean_expression(guard, &lose));
+ mmsg = makeString(-1, "/// match: ");
+ } else {
+ gmsg = NULL;
+ mmsg = makeString(-1, "/// from: match ");
+ }
+
+ appendString(mmsg, print_data_expression(submatch, &lose));
+ dmsg = makeString(-1, "/// data: ");
+ appendString(dmsg, print_data_expression(data, &lose));
+
+ /* evaluate the expression and try to reduce it */
+ reduced = eval_boolean_expression(expr, &modified);
+ reduced = reduce_boolean_expression(reduced);
+ if ((reduced != NULL) && (reduced->type == ELEMENT_BOOLEAN))
+ parse_error(cfile, "class matching rule evaluated to a "
+ "constant boolean expression: %s = %s",
+ print_data_expression(submatch, &lose),
+ print_data_expression(data, &lose));
+ if ((reduced == NULL) || (reduced->type != ELEMENT_STRING))
+ return;
+ if (!lose) {
+ if (gmsg != NULL) {
+ comment = createComment(gmsg->content);
+ TAILQ_INSERT_TAIL(&reduced->comments, comment);
+ }
+ comment = createComment(mmsg->content);
+ TAILQ_INSERT_TAIL(&reduced->comments, comment);
+ comment = createComment(dmsg->content);
+ TAILQ_INSERT_TAIL(&reduced->comments, comment);
+ }
+ mapSet(class, reduced, "test");
+}
+
+/*
+ * Try to reduce a match-if condition into a Kea evaluate bool "test"
+ */
+
+static void
+add_match_class(struct parse *cfile,
+ struct element *class,
+ struct element *expr)
+{
+ struct element *reduced;
+ isc_boolean_t modified = ISC_FALSE;
+ isc_boolean_t lose = ISC_FALSE;
+
+ /* evaluate the expression and try to reduce it */
+ reduced = eval_boolean_expression(expr, &modified);
+ reduced = reduce_boolean_expression(reduced);
+ if ((reduced != NULL) && (reduced->type == ELEMENT_BOOLEAN))
+ parse_error(cfile, "'match if' with a constant boolean "
+ "expression %s",
+ print_boolean_expression(expr, &lose));
+ if ((reduced != NULL) && (reduced->type == ELEMENT_STRING))
+ mapSet(class, reduced, "test");
+ else
+ cfile->issue_counter++;
+}
+
+/* Move pools to subnets */
+
+static void
+relocate_pools(struct element *share)
+{
+ struct element *srcs;
+ struct element *dsts;
+ struct element *subnet;
+ struct range *range;
+ size_t i;
+
+ srcs = mapGet(share, "pools");
+ if (srcs == NULL)
+ return;
+ if (listSize(srcs) == 0)
+ return;
+ TAILQ_FOREACH(range, &known_ranges) {
+ if (range->share != share)
+ continue;
+ subnet = find_location(share, range);
+ if (subnet == NULL)
+ continue;
+ for (i = 0; i < listSize(srcs); i++) {
+ struct element *pool;
+
+ pool = listGet(srcs, i);
+ if (range->pool != pool)
+ continue;
+ listRemove(srcs, i);
+ dsts = mapGet(subnet, "pools");
+ if (dsts == NULL) {
+ dsts = createList();
+ mapSet(subnet, dsts, "pools");
+ }
+ listPush(dsts, pool);
+ }
+ }
+}
+
+/* shared-network-declaration :==
+ hostname LBRACE declarations parameters RBRACE */
+
+void
+parse_shared_net_declaration(struct parse *cfile)
+{
+ const char *val;
+ enum dhcp_token token;
+ struct element *share;
+ struct element *subnets;
+ struct element *interface;
+ struct element *subnet;
+ struct string *name;
+ int declaration = 0;
+
+ share = createMap();
+ share->kind = SHARED_NET_DECL;
+ TAILQ_CONCAT(&share->comments, &cfile->comments);
+
+ /* Get the name of the shared network... */
+ token = peek_token(&val, NULL, cfile);
+ if (token == STRING) {
+ skip_token(&val, NULL, cfile);
+
+ if (val[0] == 0)
+ parse_error(cfile, "zero-length shared network name");
+ name = makeString(-1, val);
+ } else {
+ name = parse_host_name(cfile);
+ if (!name)
+ parse_error(cfile,
+ "expecting a name for shared-network");
+ }
+ mapSet(share, createString(name), "name");
+
+ subnets = createList();
+ mapSet(share, subnets,
+ local_family == AF_INET ? "subnet4" : "subnet6");
+
+ parse_lbrace(cfile);
+
+ stackPush(cfile, share);
+
+ for (;;) {
+ token = peek_token(&val, NULL, cfile);
+ if (token == RBRACE) {
+ skip_token(&val, NULL, cfile);
+ break;
+ } else if (token == END_OF_FILE) {
+ skip_token(&val, NULL, cfile);
+ parse_error(cfile, "unexpected end of file");
+ } else if (token == INTERFACE) {
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (mapContains(share, "interface"))
+ parse_error(cfile,
+ "A shared network can't be "
+ "connected to two interfaces.");
+ interface = createString(makeString(-1, val));
+ mapSet(share, interface, "interface");
+ new_network_interface(cfile, interface);
+ parse_semi(cfile);
+ continue;
+ }
+
+ declaration = parse_statement(cfile, SHARED_NET_DECL,
+ declaration);
+ }
+
+ cfile->stack_top--;
+
+ if (listSize(subnets) == 0)
+ parse_error(cfile, "empty shared-network decl");
+ if (listSize(subnets) > 1) {
+ struct element *shares;
+ struct element *pools;
+
+ shares = mapGet(cfile->stack[cfile->stack_top],
+ "shared-networks");
+ if (shares == NULL) {
+ struct comment *comment;
+
+ shares = createList();
+ shares->kind = SHARED_NET_DECL;
+ mapSet(cfile->stack[cfile->stack_top],
+ shares, "shared-networks");
+ comment = createComment("/// Kea shared-networks "
+ "are different, cf Kea #236");
+ TAILQ_INSERT_TAIL(&shares->comments, comment);
+ }
+ listPush(shares, share);
+
+ /* Pools are forbidden at shared-network level in Kea */
+ relocate_pools(share);
+ pools = mapGet(share, "pools");
+ if ((pools != NULL) && (listSize(pools) == 0)) {
+ mapRemove(share, "pools");
+ pools = NULL;
+ }
+ if (pools != NULL) {
+ struct comment *comment;
+
+ pools->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ comment = createComment("/// Kea pools must be "
+ "in a subnet");
+ TAILQ_INSERT_TAIL(&pools->comments, comment);
+ comment = createComment("/// Reference Kea #249");
+ TAILQ_INSERT_TAIL(&pools->comments, comment);
+ }
+ pools = mapGet(share, "pd-pools");
+ if ((pools != NULL) && (listSize(pools) == 0)) {
+ mapRemove(share, "pd-pools");
+ pools = NULL;
+ }
+ if (pools != NULL) {
+ struct comment *comment;
+
+ pools->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ comment = createComment("/// Kea pools must be "
+ "in a subnet");
+ TAILQ_INSERT_TAIL(&pools->comments, comment);
+ comment = createComment("/// Reference Kea #249");
+ TAILQ_INSERT_TAIL(&pools->comments, comment);
+ }
+ return;
+ }
+
+ /* There is one subnet so the shared network is useless */
+ subnet = listGet(subnets, 0);
+ listRemove(subnets, 0);
+ mapRemove(share, "name");
+ mapRemove(share, local_family == AF_INET ? "subnet4" : "subnet6");
+ /* specific case before calling generic merge */
+ if (mapContains(share, "pools") &&
+ mapContains(subnet, "pools")) {
+ struct element *pools;
+ struct element *sub;
+
+ pools = mapGet(share, "pools");
+ mapRemove(share, "pools");
+ sub = mapGet(subnet, "pools");
+ concat(sub, pools);
+ }
+ if (mapContains(share, "pd-pools") &&
+ mapContains(subnet, "pd-pools")) {
+ struct element *pools;
+ struct element *sub;
+
+ pools = mapGet(share, "pd-pools");
+ mapRemove(share, "pd-pools");
+ sub = mapGet(subnet, "pd-pools");
+ concat(sub, pools);
+ }
+ if (mapContains(share, "option-data") &&
+ mapContains(subnet, "option-data")) {
+ struct element *opt_list;
+ struct element *sub;
+
+ opt_list = mapGet(share, "option-data");
+ mapRemove(share, "option-data");
+ sub = mapGet(subnet, "option-data");
+ merge_option_data(opt_list, sub);
+ }
+ merge(subnet, share);
+
+ if (local_family == AF_INET) {
+ subnets = mapGet(cfile->stack[1], "subnet4");
+ if (subnets == NULL) {
+ subnets = createList();
+ subnets->kind = SUBNET_DECL;
+ mapSet(cfile->stack[1], subnets, "subnet4");
+ }
+ } else {
+ subnets = mapGet(cfile->stack[1], "subnet6");
+ if (subnets == NULL) {
+ subnets = createList();
+ subnets->kind = SUBNET_DECL;
+ mapSet(cfile->stack[1], subnets, "subnet6");
+ }
+ }
+ listPush(subnets, subnet);
+}
+
+static void
+common_subnet_parsing(struct parse *cfile,
+ struct element *subnets,
+ struct element *subnet)
+{
+ enum dhcp_token token;
+ const char *val;
+ struct element *interface;
+ int declaration = 0;
+
+ parse_lbrace(cfile);
+
+ stackPush(cfile, subnet);
+
+ for (;;) {
+ token = peek_token(&val, NULL, cfile);
+ if (token == RBRACE) {
+ skip_token(&val, NULL, cfile);
+ break;
+ } else if (token == END_OF_FILE) {
+ skip_token(&val, NULL, cfile);
+ parse_error(cfile, "unexpected end of file");
+ break;
+ } else if (token == INTERFACE) {
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (mapContains(subnet, "interface"))
+ parse_error(cfile,
+ "A subnet can't be connected "
+ "to two interfaces.");
+ interface = createString(makeString(-1, val));
+ mapSet(subnet, interface, "interface");
+ new_network_interface(cfile, interface);
+ parse_semi(cfile);
+ continue;
+ }
+ declaration = parse_statement(cfile, SUBNET_DECL, declaration);
+ }
+
+ cfile->stack_top--;
+
+ /* Add the subnet to the list of subnets in this shared net. */
+ listPush(subnets, subnet);
+
+ return;
+}
+
+/* subnet-declaration :==
+ net NETMASK netmask RBRACE parameters declarations LBRACE */
+
+void
+parse_subnet_declaration(struct parse *cfile)
+{
+ const char *val;
+ enum dhcp_token token;
+ struct element *subnet;
+ struct subnet *chain;
+ struct element *subnets;
+ struct string *address;
+ struct string *netmask;
+ struct string *prefix;
+ unsigned char addr[4];
+ unsigned len = sizeof(addr);
+ size_t parent = 0;
+ size_t i;
+ int kind = 0;
+
+ subnet = createMap();
+ subnet->kind = SUBNET_DECL;
+ TAILQ_CONCAT(&subnet->comments, &cfile->comments);
+
+ subnet_counter++;
+ mapSet(subnet, createInt(subnet_counter), "id");
+
+ chain = (struct subnet *)malloc(sizeof(*chain));
+ if (chain == NULL)
+ parse_error(cfile, "can't allocate subnet");
+ memset(chain, 0, sizeof(*chain));
+ chain->subnet = subnet;
+ TAILQ_INSERT_TAIL(&known_subnets, chain);
+
+ /* Find parent */
+ for (i = cfile->stack_top; i > 0; --i) {
+ kind = cfile->stack[i]->kind;
+ if ((kind == SHARED_NET_DECL) ||
+ (kind == GROUP_DECL) ||
+ (kind == ROOT_GROUP)) {
+ parent = i;
+ break;
+ }
+ }
+ if (kind == 0)
+ parse_error(cfile, "can't find a place to put subnet");
+ if (kind == SHARED_NET_DECL)
+ chain->share = cfile->stack[parent];
+ subnets = mapGet(cfile->stack[parent], "subnet4");
+ if (subnets == NULL) {
+ if (kind == SHARED_NET_DECL)
+ parse_error(cfile, "shared network without subnets");
+ subnets = createList();
+ subnets->kind = SUBNET_DECL;
+ mapSet(cfile->stack[parent], subnets, "subnet4");
+ }
+
+ /* Get the network number... */
+ address = parse_numeric_aggregate(cfile, addr, &len, DOT, 10, 8);
+ if (address == NULL)
+ parse_error(cfile, "can't decode network number");
+ if (address->length != 4)
+ parse_error(cfile, "bad IPv4 address length");
+ chain->addr = address;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NETMASK)
+ parse_error(cfile, "Expecting netmask");
+
+ /* Get the netmask... */
+ netmask = parse_numeric_aggregate(cfile, addr, &len, DOT, 10, 8);
+ if (netmask == NULL)
+ parse_error(cfile, "can't decode network mask");
+ if (netmask->length != address->length)
+ parse_error(cfile, "bad IPv4 mask length");
+ chain->mask = netmask;
+
+ prefix = addrmask(address, netmask);
+ if (prefix == NULL) {
+ char bufa[INET_ADDRSTRLEN];
+ char bufm[INET_ADDRSTRLEN];
+
+ inet_ntop(AF_INET, address->content, bufa, INET_ADDRSTRLEN);
+ inet_ntop(AF_INET, netmask->content, bufm, INET_ADDRSTRLEN);
+ parse_error(cfile, "can't get a prefix from %s mask %s",
+ bufa, bufm);
+ }
+ mapSet(subnet, createString(prefix), "subnet");
+
+ common_subnet_parsing(cfile, subnets, subnet);
+}
+
+/* subnet6-declaration :==
+ net / bits RBRACE parameters declarations LBRACE */
+
+void
+parse_subnet6_declaration(struct parse *cfile)
+{
+ enum dhcp_token token;
+ const char *val;
+ struct element *subnet;
+ struct subnet *chain;
+ struct element *subnets;
+ struct string *address;
+ struct string *prefix;
+ struct string *netmask;
+ size_t parent = 0;
+ size_t i;
+ int kind = 0;
+ char *p;
+
+ if (local_family != AF_INET6)
+ parse_error(cfile, "subnet6 statement is only supported "
+ "in DHCPv6 mode.");
+
+ subnet = createMap();
+ subnet->kind = SUBNET_DECL;
+ TAILQ_CONCAT(&subnet->comments, &cfile->comments);
+
+ subnet_counter++;
+ mapSet(subnet, createInt(subnet_counter), "id");
+
+ chain = (struct subnet *)malloc(sizeof(*chain));
+ if (chain == NULL)
+ parse_error(cfile, "can't allocate subnet");
+ memset(chain, 0, sizeof(*chain));
+ chain->subnet = subnet;
+ TAILQ_INSERT_TAIL(&known_subnets, chain);
+
+ /* Find parent */
+ for (i = cfile->stack_top; i > 0; --i) {
+ kind = cfile->stack[i]->kind;
+ if ((kind == SHARED_NET_DECL) ||
+ (kind == GROUP_DECL) ||
+ (kind == ROOT_GROUP)) {
+ parent = i;
+ break;
+ }
+ }
+ if (kind == 0)
+ parse_error(cfile, "can't find a place to put subnet");
+ if (kind == SHARED_NET_DECL)
+ chain->share = cfile->stack[parent];
+ subnets = mapGet(cfile->stack[parent], "subnet6");
+ if (subnets == NULL) {
+ if (kind == SHARED_NET_DECL)
+ parse_error(cfile, "shared network without subnets");
+ subnets = createList();
+ subnets->kind = SUBNET_DECL;
+ mapSet(cfile->stack[parent], subnets, "subnet6");
+ }
+
+ address = parse_ip6_addr(cfile);
+ if (address == NULL)
+ parse_error(cfile, "can't decode network number");
+ if (address->length != 16)
+ parse_error(cfile, "bad IPv6 address length");
+ chain->addr = address;
+
+ prefix = makeStringExt(address->length, address->content, '6');
+
+ token = next_token(&val, NULL, cfile);
+ if (token != SLASH)
+ parse_error(cfile, "Expecting a '/'.");
+ appendString(prefix, val);
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile, "Expecting a number.");
+ appendString(prefix, val);
+
+ netmask = makeString(16, "0123456789abcdef");
+ memset(netmask->content, 0, 16);
+ p = netmask->content;
+ for (i = atoi(val); i >= 8; i -= 8)
+ *p++ = 0xff;
+ *p = 0xff << (8 - i);
+ chain->mask = netmask;
+
+ mapSet(subnet, createString(prefix), "subnet");
+
+ common_subnet_parsing(cfile, subnets, subnet);
+}
+
+/* group-declaration :== RBRACE parameters declarations LBRACE */
+
+void
+parse_group_declaration(struct parse *cfile)
+{
+ const char *val;
+ enum dhcp_token token;
+ struct element *group;
+ int declaration = 0;
+ struct string *name = NULL;
+
+ if (mapContains(cfile->stack[cfile->stack_top], "group"))
+ parse_error(cfile, "another group is already open");
+ group = createMap();
+ group->skip = ISC_TRUE;
+ group->kind = GROUP_DECL;
+ TAILQ_CONCAT(&group->comments, &cfile->comments);
+ mapSet(cfile->stack[cfile->stack_top], group, "group");
+
+ token = peek_token(&val, NULL, cfile);
+ if (is_identifier(token) || token == STRING) {
+ skip_token(&val, NULL, cfile);
+
+ name = makeString(-1, val);
+ if (!name)
+ parse_error(cfile, "no memory for group decl name %s",
+ val);
+ }
+
+ parse_lbrace(cfile);
+
+ stackPush(cfile, group);
+
+ for (;;) {
+ token = peek_token(&val, NULL, cfile);
+ if (token == RBRACE) {
+ skip_token(&val, NULL, cfile);
+ break;
+ } else if (token == END_OF_FILE) {
+ skip_token(&val, NULL, cfile);
+ parse_error(cfile, "unexpected end of file");
+ break;
+ } else if (token == TOKEN_DELETED) {
+ skip_token(&val, NULL, cfile);
+ parse_error(cfile, "deleted groups don't exist "
+ "in the config file");
+ } else if (token == DYNAMIC) {
+ skip_token(&val, NULL, cfile);
+ parse_error(cfile, "dynamic groups don't exist "
+ "in the config file");
+ } else if (token == STATIC) {
+ skip_token(&val, NULL, cfile);
+ parse_error(cfile, "static groups don't exist "
+ "in the config file");
+ }
+ declaration = parse_statement(cfile, GROUP_DECL, declaration);
+ }
+
+ cfile->stack_top--;
+
+ if (name != NULL)
+ mapSet(group, createString(name), "name");
+ close_group(cfile, group);
+}
+
+/*
+ * Close a group. Called when a group is closed.
+ * - spread parameters to children
+ * - attach declarations at an upper level
+ */
+
+void
+close_group(struct parse *cfile, struct element *group)
+{
+ struct handle *handle;
+ struct handle *nh;
+ struct element *parent;
+ struct element *item;
+ struct element *param;
+ struct handle *hosts = NULL;
+ struct handle *shares = NULL;
+ struct handle *subnets = NULL;
+ struct handle *classes = NULL;
+ struct handle *pdpools = NULL;
+ struct handle *pools = NULL;
+ struct handles downs;
+ struct comment *comment;
+ const char *key = NULL;
+ const char *name = NULL;
+ unsigned order = 0;
+ isc_boolean_t marked = ISC_FALSE;
+
+ TAILQ_INIT(&downs);
+
+ /* check that group is in its parent */
+ parent = cfile->stack[cfile->stack_top];
+ if (parent->kind == PARAMETER)
+ parse_error(cfile, "unexpected kind for group parent %d",
+ parent->kind);
+ item = mapGet(parent, "group");
+ if (item == NULL)
+ parse_error(cfile, "no group in parent");
+ if (item != group)
+ parse_error(cfile, "got a different group from parent");
+ mapRemove(parent, "group");
+
+ /* classify content */
+ while (mapSize(group) > 0) {
+ handle = mapPop(group);
+ if ((handle == NULL) || (handle->key == NULL) ||
+ (handle->value == NULL))
+ parse_error(cfile, "can't get group item at %u",
+ order);
+ handle->order = order++;
+ switch (handle->value->kind) {
+ case TOPLEVEL:
+ case ROOT_GROUP:
+ case GROUP_DECL:
+ badkind:
+ parse_error(cfile, "impossible group item (kind %d) "
+ "for %s at order %u",
+ handle->value->kind, handle->key, order);
+
+ case HOST_DECL:
+ if (strcmp(handle->key, "reservations") != 0)
+ parse_error(cfile, "expected reservations "
+ "got %s at %u",
+ handle->key, order);
+ if (hosts != NULL)
+ parse_error(cfile, "got reservations twice "
+ "at %u and %u",
+ hosts->order, order);
+ if ((parent->kind == HOST_DECL) ||
+ (parent->kind == CLASS_DECL))
+ parse_error(cfile, "host declarations not "
+ "allowed here.");
+ hosts = handle;
+ handle = NULL;
+ break;
+
+ case SHARED_NET_DECL:
+ if (strcmp(handle->key, "shared-networks") != 0)
+ parse_error(cfile, "expected shared-networks "
+ "got %s at %u",
+ handle->key, order);
+ if ((parent->kind == SHARED_NET_DECL) ||
+ (parent->kind == HOST_DECL) ||
+ (parent->kind == SUBNET_DECL) ||
+ (parent->kind == CLASS_DECL))
+ parse_error(cfile, "shared-network parameters "
+ "not allowed here.");
+ shares = handle;
+ handle = NULL;
+ break;
+
+ case SUBNET_DECL:
+ key = local_family == AF_INET ? "subnet4" : "subnet6";
+ if (strcmp(handle->key, key) != 0)
+ parse_error(cfile, "expected %s got %s at %u",
+ key, handle->key, order);
+ if (subnets != NULL)
+ parse_error(cfile, "got %s twice at %u and %u",
+ key, subnets->order, order);
+ if ((parent->kind == HOST_DECL) ||
+ (parent->kind == SUBNET_DECL) ||
+ (parent->kind == CLASS_DECL))
+ parse_error(cfile, "subnet declarations not "
+ "allowed here.");
+ subnets = handle;
+ handle = NULL;
+ break;
+
+ case CLASS_DECL:
+ if (strcmp(handle->key, "client-classes") != 0)
+ parse_error(cfile, "expected client-classes "
+ "got %s at %u",
+ handle->key, order);
+ if (classes != NULL)
+ parse_error(cfile, "got %s twice at %u and %u",
+ key, classes->order, order);
+ if (parent->kind == CLASS_DECL)
+ parse_error(cfile, "class declarations not "
+ "allowed here.");
+ classes = handle;
+ handle = NULL;
+ break;
+
+ case POOL_DECL:
+ if (strcmp(handle->key, "pd-pools") == 0) {
+ if (pdpools != NULL)
+ parse_error(cfile, "got pd-pools "
+ "twice at %u and %u",
+ pdpools->order, order);
+ pdpools = handle;
+ } else if (strcmp(handle->key, "pools") == 0) {
+ if (pools != NULL)
+ parse_error(cfile, "got pools twice "
+ "at %u and %u",
+ pools->order, order);
+ pools = handle;
+ } else
+ parse_error(cfile, "expecyed [pd-]pools got "
+ "%s at %u",
+ handle->key, order);
+ if (parent->kind == POOL_DECL)
+ parse_error(cfile, "pool declared within "
+ "pool.");
+ if ((parent->kind == HOST_DECL) ||
+ (parent->kind == CLASS_DECL))
+ parse_error(cfile, "pool declared outside "
+ "of network");
+ handle = NULL;
+ break;
+ default:
+ if (handle->value->kind != PARAMETER)
+ goto badkind;
+ }
+ if (handle == NULL)
+ continue;
+
+ /* we have a parameter */
+ param = handle->value;
+ /* group name */
+ if (strcmp(handle->key, "name") == 0) {
+ name = stringValue(param)->content;
+ continue;
+ }
+ /* unexpected values */
+ if ((strcmp(handle->key, "reservations") == 0) ||
+ (strcmp(handle->key, "group") == 0) ||
+ (strcmp(handle->key, "shared-networks") == 0) ||
+ (strcmp(handle->key, "subnet4") == 0) ||
+ (strcmp(handle->key, "subnet6") == 0) ||
+ (strcmp(handle->key, "subnet") == 0) ||
+ (strcmp(handle->key, "client-classes") == 0) ||
+ (strcmp(handle->key, "hw-address") == 0) ||
+ (strcmp(handle->key, "ip-address") == 0) ||
+ (strcmp(handle->key, "extra-ip-addresses") == 0) ||
+ (strcmp(handle->key, "ip-addresses") == 0) ||
+ (strcmp(handle->key, "prefixes") == 0) ||
+ (strcmp(handle->key, "pool") == 0) ||
+ (strcmp(handle->key, "prefix") == 0) ||
+ (strcmp(handle->key, "delegated-len") == 0) ||
+ (strcmp(handle->key, "prefix-len") == 0) ||
+ (strcmp(handle->key, "prefix-highest") == 0) ||
+ (strcmp(handle->key, "option-def") == 0) ||
+ (strcmp(handle->key, "hostname") == 0) ||
+ (strcmp(handle->key, "client-id") == 0) ||
+ (strcmp(handle->key, "host-identifier") == 0) ||
+ (strcmp(handle->key, "flex-id") == 0) ||
+ (strcmp(handle->key, "test") == 0) ||
+ (strcmp(handle->key, "authoritative") == 0) ||
+ (strcmp(handle->key, "dhcp-ddns") == 0) ||
+ (strcmp(handle->key, "host-reservation-identifiers") == 0))
+ parse_error(cfile, "unexpected parameter %s "
+ "in group at %u",
+ handle->key, order);
+
+ /* to parent at group position */
+ if ((strcmp(handle->key, "option-space") == 0) ||
+ (strcmp(handle->key, "server-duid") == 0) ||
+ (strcmp(handle->key, "statement") == 0) ||
+ (strcmp(handle->key, "config") == 0) ||
+ (strcmp(handle->key, "ddns-update-style") == 0) ||
+ (strcmp(handle->key, "echo-client-id") == 0)) {
+ if (!marked) {
+ struct string *msg;
+
+ marked = ISC_TRUE;
+ msg = makeString(-1, "/// moved from group");
+ if (name != NULL)
+ appendString(msg, " ");
+ appendString(msg, name);
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&param->comments, comment);
+ }
+ mapSet(parent, param, handle->key);
+ free(handle);
+ continue;
+ }
+ /* To reconsider: qualifying-suffix, enable-updates */
+ if ((strcmp(handle->key, "option-data") == 0) ||
+ (strcmp(handle->key, "allow") == 0) ||
+ (strcmp(handle->key, "deny") == 0) ||
+ (strcmp(handle->key, "interface") == 0) ||
+ (strcmp(handle->key, "valid-lifetime") == 0) ||
+ (strcmp(handle->key, "preferred-lifetime") == 0) ||
+ (strcmp(handle->key, "renew-timer") == 0) ||
+ (strcmp(handle->key, "rebind-timer") == 0) ||
+ (strcmp(handle->key, "boot-file-name") == 0) ||
+ (strcmp(handle->key, "server-hostname") == 0) ||
+ (strcmp(handle->key, "next-server") == 0) ||
+ (strcmp(handle->key, "match-client-id") == 0)) {
+ TAILQ_INSERT_TAIL(&downs, handle);
+ continue;
+ }
+ /* unknown */
+ if (!marked) {
+ struct string *msg;
+
+ marked = ISC_TRUE;
+ msg = makeString(-1, "/// moved from group");
+ if (name != NULL)
+ appendString(msg, " ");
+ appendString(msg, name);
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&param->comments, comment);
+ }
+ comment = createComment("/// unhandled parameter");
+ TAILQ_INSERT_TAIL(&param->comments, comment);
+ param->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(parent, param, handle->key);
+ free(handle);
+ }
+ TAILQ_FOREACH_SAFE(handle, &downs, nh) {
+ if (strcmp(handle->key, "option-data") == 0) {
+ option_data_derive(cfile, handle, hosts);
+ option_data_derive(cfile, handle, shares);
+ option_data_derive(cfile, handle, subnets);
+ derive_classes(cfile, handle, classes);
+ option_data_derive(cfile, handle, pdpools);
+ option_data_derive(cfile, handle, pools);
+ } else if ((strcmp(handle->key, "allow") == 0) ||
+ (strcmp(handle->key, "deny") == 0)) {
+ derive(handle, pdpools);
+ derive(handle, pools);
+ } else if ((strcmp(handle->key, "interface") == 0) ||
+ (strcmp(handle->key, "valid-lifetime") == 0) ||
+ (strcmp(handle->key, "preferred-lifetime") == 0) ||
+ (strcmp(handle->key, "renew-timer") == 0) ||
+ (strcmp(handle->key, "rebind-timer") == 0) ||
+ (strcmp(handle->key, "match-client-id") == 0)) {
+ derive(handle, shares);
+ derive(handle, subnets);
+ } else if ((strcmp(handle->key, "boot-file-name") == 0) ||
+ (strcmp(handle->key, "server-hostname") == 0)) {
+ derive(handle, hosts);
+ derive_classes(cfile, handle, classes);
+ } else if (strcmp(handle->key, "next-server") == 0) {
+ derive(handle, hosts);
+ derive(handle, subnets);
+ derive_classes(cfile, handle, classes);
+ } else
+ parse_error(cfile, "unexpected parameter %s to derive",
+ handle->key);
+ }
+ if (hosts != NULL) {
+ struct element *root;
+
+ root = mapGet(cfile->stack[1], "reservations");
+ if (root == NULL)
+ mapSet(cfile->stack[1], hosts->value, "reservations");
+ else
+ concat(root, hosts->value);
+ }
+ if (shares != NULL) {
+ struct element *upper;
+
+ upper = mapGet(parent, "shared-networks");
+ if (upper == NULL)
+ mapSet(parent, shares->value, "shared-networks");
+ else
+ concat(upper, shares->value);
+ }
+ key = local_family == AF_INET ? "subnet4" : "subnet6";
+ if (subnets != NULL) {
+ struct element *upper;
+
+ upper = mapGet(parent, key);
+ if (upper == NULL)
+ mapSet(parent, subnets->value, key);
+ else
+ concat(upper, subnets->value);
+ }
+ if (classes != NULL) {
+ struct element *upper;
+ size_t where;
+ int kind = 0;
+
+ for (where = cfile->stack_top; where > 0; --where) {
+ kind = cfile->stack[where]->kind;
+ if ((kind == GROUP_DECL) || (kind == ROOT_GROUP))
+ break;
+ }
+ if (kind == GROUP_DECL) {
+ upper = mapGet(cfile->stack[where], "client-classes");
+ if (upper == NULL)
+ mapSet(cfile->stack[where],
+ classes->value,
+ "client-classes");
+ else
+ concat_classes(cfile, upper, classes->value);
+ }
+ }
+ if (pdpools != NULL) {
+ struct element *upper;
+
+ upper = mapGet(parent, "pd-pools");
+ if (upper == NULL)
+ mapSet(parent, pdpools->value, "pools");
+ else
+ concat(upper, pdpools->value);
+ }
+ if (pools != NULL) {
+ struct element *upper;
+
+ upper = mapGet(parent, "pools");
+ if (upper == NULL)
+ mapSet(parent, pools->value, "pools");
+ else
+ concat(upper, pools->value);
+ }
+}
+
+/*
+ * Specialized derivation routine for option-data
+ * (options are identified by space + name and/or code
+ */
+
+static void
+option_data_derive(struct parse *cfile, struct handle *src, struct handle *dst)
+{
+ struct element *list;
+ struct element *item;
+ struct element *opt_list;
+ size_t i;
+
+ if (dst == NULL)
+ return;
+ list = dst->value;
+ assert(list != NULL);
+ assert(list->type == ELEMENT_LIST);
+ for (i = 0; i < listSize(list); i++) {
+ item = listGet(list, i);
+ assert(item != NULL);
+ assert(item->type == ELEMENT_MAP);
+ opt_list = mapGet(item, src->key);
+ if (opt_list != NULL) {
+ merge_option_data(src->value, opt_list);
+ continue;
+ }
+ opt_list = copy(src->value);
+ mapSet(item, opt_list, src->key);
+ }
+}
+
+/*
+ * Specialized derivation routine for classes
+ * (which are by reference so a resolution step is needed)
+ */
+static void
+derive_classes(struct parse *cfile, struct handle *src, struct handle *dst)
+{
+ struct element *list;
+ struct element *item;
+ size_t i;
+
+ if (dst == NULL)
+ return;
+ list = dst->value;
+ assert(list != NULL);
+ assert(list->type == ELEMENT_LIST);
+ for (i = 0; i < listSize(list); i++) {
+ item = listGet(list, i);
+ assert(item != NULL);
+ assert(item->type == ELEMENT_MAP);
+ item = get_class(cfile, item);
+ if (item == NULL)
+ parse_error(cfile, "dangling class reference");
+ if (strcmp(src->key, "option-data") == 0) {
+ struct element *opt_list;
+
+ opt_list = mapGet(item, "option-data");
+ if (opt_list != NULL)
+ merge_option_data(src->value, opt_list);
+ else
+ mapSet(item, copy(src->value), "option-data");
+ continue;
+ }
+ if (mapContains(item, src->key))
+ continue;
+ mapSet(item, copy(src->value), src->key);
+ }
+}
+
+/* fixed-addr-parameter :== ip-addrs-or-hostnames SEMI
+ ip-addrs-or-hostnames :== ip-addr-or-hostname
+ | ip-addrs-or-hostnames ip-addr-or-hostname */
+
+struct element *
+parse_fixed_addr_param(struct parse *cfile, enum dhcp_token type) {
+ const char *val;
+ enum dhcp_token token;
+ struct element *addr;
+ struct element *addresses;
+ struct string *address;
+
+ addresses = createList();
+ TAILQ_CONCAT(&addresses->comments, &cfile->comments);
+
+ do {
+ address = NULL;
+ if (type == FIXED_ADDR)
+ address = parse_ip_addr_or_hostname(cfile, ISC_TRUE);
+ else if (type == FIXED_ADDR6)
+ address = parse_ip6_addr_txt(cfile);
+ else
+ parse_error(cfile, "requires FIXED_ADDR[6]");
+ if (address == NULL)
+ parse_error(cfile, "can't parse fixed address");
+ addr = createString(address);
+ /* Take the comment for resolution into multiple addresses */
+ TAILQ_CONCAT(&addr->comments, &cfile->comments);
+ listPush(addresses, addr);
+ token = peek_token(&val, NULL, cfile);
+ if (token == COMMA)
+ token = next_token(&val, NULL, cfile);
+ } while (token == COMMA);
+
+ parse_semi(cfile);
+
+ /* Sanity */
+ if (listSize(addresses) == 0)
+ parse_error(cfile, "can't get fixed address");
+
+ return addresses;
+
+}
+
+#ifdef notyet
+/* Parse the right side of a 'binding value'.
+ *
+ * set foo = "bar"; is a string
+ * set foo = false; is a boolean
+ * set foo = %31; is a numeric value.
+ */
+static struct element *
+parse_binding_value(struct parse *cfile)
+{
+ struct element *value = NULL;
+ struct string *data;
+ const char *val;
+ unsigned buflen;
+ int token;
+
+ token = peek_token(&val, NULL, cfile);
+ if (token == STRING) {
+ skip_token(&val, &buflen, cfile);
+ data = makeString(buflen, val);
+ value = createString(data);
+ } else if (token == NUMBER_OR_NAME) {
+ value = createMap();
+ data = parse_hexa(cfile);
+ mapSet(value, createHexa(data), "const-data");
+ } else if (token == PERCENT) {
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile, "expecting decimal number.");
+ value = createInt(atol(val));
+ } else if (token == NAME) {
+ token = next_token(&val, NULL, cfile);
+ if (!strcasecmp(val, "true"))
+ value = createBool(ISC_TRUE);
+ else if (!strcasecmp(val, "false"))
+ value = createBool(ISC_FALSE);
+ else
+ parse_error(cfile, "expecting true or false");
+ } else
+ parse_error(cfile, "expecting a constant value.");
+
+ return value;
+}
+#endif
+
+/* address-range-declaration :== ip-address ip-address SEMI
+ | DYNAMIC_BOOTP ip-address ip-address SEMI */
+
+void
+parse_address_range(struct parse *cfile, int type, size_t where)
+{
+ struct string *low, *high, *range;
+ unsigned char addr[4];
+ unsigned len = sizeof(addr);
+ enum dhcp_token token;
+ const char *val;
+ struct element *pool;
+ struct element *r;
+ struct range *chain;
+ size_t i;
+ int kind;
+
+ if ((token = peek_token(&val, NULL, cfile)) == DYNAMIC_BOOTP) {
+ skip_token(&val, NULL, cfile);
+ }
+
+ /* Get the bottom address in the range... */
+ low = parse_numeric_aggregate(cfile, addr, &len, DOT, 10, 8);
+ if (low == NULL)
+ parse_error(cfile, "can't parse range (low)");
+
+ /* Only one address? */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI)
+ high = low;
+ else {
+ /* Get the top address in the range... */
+ high = parse_numeric_aggregate(cfile, addr, &len, DOT, 10, 8);
+ if (high == NULL)
+ parse_error(cfile, "can't parse range (high)");
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != SEMI)
+ parse_error(cfile, "semicolon expected.");
+
+ if (type != POOL_DECL) {
+ struct element *group;
+ struct element *pools;
+#ifdef want_bootp
+ struct element *permit;
+#endif
+
+ group = cfile->stack[where];
+ pool = createMap();
+#ifdef want_bootp
+ permit = createList();
+ permit->skip = ISC_TRUE;
+
+ /* Dynamic pools permit all clients. Otherwise
+ we prohibit BOOTP clients. */
+ if (dynamic) {
+ struct string *all;
+
+ all = makeString(-1, "all clients");
+ listPush(permit, createString(all));
+ mapSet(pool, permit, "allow");
+ } else {
+ struct string *dyn_bootp;
+
+ dyn_bootp = makeString(-1, "dynamic bootp clients");
+ listPush(permit, createString(dyn_bootp));
+ mapSet(pool, permit, "deny");
+ }
+#endif
+
+ pools = mapGet(group, "pools");
+ if (pools == NULL) {
+ pools = createList();
+ pools->kind = POOL_DECL;
+ mapSet(group, pools, "pools");
+ }
+ listPush(pools, pool);
+ } else
+ pool = cfile->stack[where];
+
+ /* Create the new address range... */
+ if (memcmp(high->content, low->content, high->length) < 0) {
+ struct string *swap;
+
+ swap = low;
+ low = high;
+ high = swap;
+ }
+ range = makeStringExt(low->length, low->content, 'I');
+ appendString(range, " - ");
+ concatString(range, makeStringExt(high->length, high->content, 'I'));
+
+ r = createString(range);
+ TAILQ_CONCAT(&r->comments, &cfile->comments);
+
+ mapSet(pool, r, "pool");
+
+ chain = (struct range *)malloc(sizeof(*chain));
+ if (chain == NULL)
+ parse_error(cfile, "can't allocate range");
+ memset(chain, 0, sizeof(*chain));
+ chain->pool = pool;
+ for (i = where; i > 0; --i) {
+ kind = cfile->stack[i]->kind;
+ if (kind == SHARED_NET_DECL) {
+ chain->share = cfile->stack[i];
+ break;
+ }
+ }
+ chain->low = low;
+ TAILQ_INSERT_TAIL(&known_ranges, chain);
+}
+
+/* address-range6-declaration :== ip-address6 ip-address6 SEMI
+ | ip-address6 SLASH number SEMI
+ | ip-address6 [SLASH number] TEMPORARY SEMI */
+
+void
+parse_address_range6(struct parse *cfile, int type, size_t where)
+{
+ struct string *low, *high, *range;
+ enum dhcp_token token;
+ const char *val;
+ isc_boolean_t is_temporary = ISC_FALSE;
+ struct element *pool;
+ struct element *r;
+ struct range *chain;
+ size_t i;
+ int kind;
+
+ if (local_family != AF_INET6)
+ parse_error(cfile, "range6 statement is only supported "
+ "in DHCPv6 mode.");
+
+ /*
+ * Read starting address as text.
+ */
+ low = parse_ip6_addr_txt(cfile);
+ if (low == NULL)
+ parse_error(cfile, "can't parse range6 address (low)");
+ range = allocString();
+ concatString(range, low);
+
+ /*
+ * See if we we're using range or CIDR notation or TEMPORARY
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SLASH) {
+ appendString(range, val);
+ /*
+ * '/' means CIDR notation, so read the bits we want.
+ */
+ skip_token(NULL, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile, "expecting number");
+ /*
+ * no sanity checks
+ */
+ appendString(range, val);
+ /*
+ * can be temporary (RFC 4941 like)
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == TEMPORARY) {
+ is_temporary = ISC_TRUE;
+ appendString(range, " ");
+ appendString(range, val);
+ skip_token(NULL, NULL, cfile);
+ }
+ } else if (token == TEMPORARY) {
+ /*
+ * temporary (RFC 4941)
+ */
+ is_temporary = ISC_TRUE;
+ appendString(range, "/64 ");
+ appendString(range, val);
+ skip_token(NULL, NULL, cfile);
+ } else {
+ /*
+ * No '/', so we are looking for the end address of
+ * the IPv6 pool.
+ */
+ high = parse_ip6_addr_txt(cfile);
+ if (high == NULL)
+ parse_error(cfile,
+ "can't parse range6 address (high)");
+ /* No sanity checks */
+ appendString(range, " - ");
+ appendString(range, high->content);
+ }
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != SEMI)
+ parse_error(cfile, "semicolon expected.");
+
+ if (type != POOL_DECL) {
+ struct element *group;
+ struct element *pools;
+
+ group = cfile->stack[where];
+ pool = createMap();
+ pools = mapGet(group, "pools");
+ if (pools == NULL) {
+ pools = createList();
+ pools->kind = POOL_DECL;
+ mapSet(group, pools, "pools");
+ }
+ listPush(pools, pool);
+ } else
+ pool = cfile->stack[where];
+
+ r = createString(range);
+ TAILQ_CONCAT(&r->comments, &cfile->comments);
+ if (is_temporary) {
+ pool->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ mapSet(pool, r, "pool");
+
+ chain = (struct range *)malloc(sizeof(*chain));
+ if (chain == NULL)
+ parse_error(cfile, "can't allocate range");
+ memset(chain, 0, sizeof(*chain));
+ chain->pool = pool;
+ for (i = where; i > 0; --i) {
+ kind = cfile->stack[i]->kind;
+ if (kind == SHARED_NET_DECL) {
+ chain->share = cfile->stack[i];
+ break;
+ }
+ }
+ chain->low = low;
+ TAILQ_INSERT_TAIL(&known_ranges, chain);
+}
+
+/* prefix6-declaration :== ip-address6 ip-address6 SLASH number SEMI */
+
+void
+parse_prefix6(struct parse *cfile, int type, size_t where)
+{
+ struct string *lo, *hi;
+ int plen;
+ int bits;
+ enum dhcp_token token;
+ const char *val;
+ struct element *pool;
+ struct element *prefix;
+
+ if (local_family != AF_INET6)
+ parse_error(cfile, "prefix6 statement is only supported "
+ "in DHCPv6 mode.");
+
+ /*
+ * Read starting and ending address as text.
+ */
+ lo = parse_ip6_addr_txt(cfile);
+ if (lo == NULL)
+ parse_error(cfile, "can't parse prefix6 address (low)");
+
+ hi = parse_ip6_addr_txt(cfile);
+ if (hi == NULL)
+ parse_error(cfile, "can't parse prefix6 address (high)");
+
+ /*
+ * Next is '/' number ';'.
+ */
+ token = next_token(NULL, NULL, cfile);
+ if (token != SLASH)
+ parse_error(cfile, "expecting '/'");
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile, "expecting number");
+ bits = atoi(val);
+ if ((bits <= 0) || (bits >= 128))
+ parse_error(cfile, "networks have 0 to 128 bits (exclusive)");
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != SEMI)
+ parse_error(cfile, "semicolon expected.");
+
+ if (type != POOL_DECL) {
+ struct element *group;
+ struct element *pools;
+
+ group = cfile->stack[where];
+ pool = createMap();
+ pools = mapGet(group, "pd-pools");
+ if (pools == NULL) {
+ pools = createList();
+ pools->kind = POOL_DECL;
+ mapSet(group, pools, "pd-pools");
+ }
+ listPush(pools, pool);
+ } else
+ pool = cfile->stack[where];
+
+ prefix = createString(lo);
+ TAILQ_CONCAT(&prefix->comments, &cfile->comments);
+ mapSet(pool, prefix, "prefix");
+ mapSet(pool, createInt(bits), "delegated-len");
+ plen = get_prefix_length(lo->content, hi->content);
+ if (plen >= 0)
+ mapSet(pool, createInt(plen), "prefix-len");
+ else {
+ if (!pool->skip)
+ cfile->issue_counter++;
+ pool->skip = ISC_TRUE;
+ mapSet(pool, createString(hi), "prefix-highest");
+ }
+}
+
+/* fixed-prefix6 :== ip6-address SLASH number SEMI */
+
+void
+parse_fixed_prefix6(struct parse *cfile, size_t host_decl)
+{
+ struct string *ia;
+ enum dhcp_token token;
+ const char *val;
+ struct element *host;
+ struct element *prefixes;
+ struct element *prefix;
+
+ if (local_family != AF_INET6)
+ parse_error(cfile, "fixed-prefix6 statement is only "
+ "supported in DHCPv6 mode.");
+
+ /*
+ * Get the fixed-prefix list.
+ */
+ host = cfile->stack[host_decl];
+ prefixes = mapGet(host, "prefixes");
+ if (prefixes == NULL) {
+ prefixes = createList();
+ mapSet(host, prefixes, "prefixes");
+ }
+
+ ia = parse_ip6_addr_txt(cfile);
+ if (ia == NULL)
+ parse_error(cfile, "can't parse fixed-prefix6 address");
+ token = next_token(&val, NULL, cfile);
+ if (token != SLASH)
+ parse_error(cfile, "expecting '/'");
+ appendString(ia, val);
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile, "expecting number");
+ appendString(ia, val);
+ token = next_token(NULL, NULL, cfile);
+ if (token != SEMI)
+ parse_error(cfile, "semicolon expected.");
+
+ prefix = createString(ia);
+ TAILQ_CONCAT(&prefix->comments, &cfile->comments);
+ listPush(prefixes, prefix);
+}
+
+/*!
+ *
+ * \brief Parse a pool6 statement
+ *
+ * Pool statements are used to group declarations and permit & deny information
+ * with a specific address range. They must be declared within a shared network
+ * or subnet and there may be multiple pools withing a shared network or subnet.
+ * Each pool may have a different set of permit or deny options.
+ *
+ * \param[in] cfile = the configuration file being parsed
+ * \param[in] type = the type of the enclosing statement. This must be
+ * SUBNET_DECL for this function.
+ *
+ * \return
+ * void - This function either parses the statement and updates the structures
+ * or it generates an error message and possible halts the program if
+ * it encounters a problem.
+ */
+
+void
+parse_pool6_statement(struct parse *cfile, int type)
+{
+ enum dhcp_token token;
+ const char *val;
+ isc_boolean_t done = ISC_FALSE;
+ struct element *pool;
+ struct element *pools;
+ struct element *pdpool;
+ struct element *pdpools;
+ struct element *permit;
+ struct element *prohibit;
+ int declaration = 0;
+ unsigned range_counter = 0;
+ unsigned prefix_counter = 0;
+
+ if (local_family != AF_INET6)
+ parse_error(cfile, "pool6 statement is only supported "
+ "in DHCPv6 mode.");
+
+ pool = createMap();
+ pool->kind = POOL_DECL;
+ TAILQ_CONCAT(&pool->comments, &cfile->comments);
+
+ if (type != SUBNET_DECL)
+ parse_error(cfile, "pool6s are only valid inside "
+ "subnet statements.");
+ parse_lbrace(cfile);
+
+ stackPush(cfile, pool);
+ type = POOL_DECL;
+
+ permit = createList();
+ prohibit = createList();
+
+ do {
+ token = peek_token(&val, NULL, cfile);
+ switch (token) {
+ case RANGE6:
+ skip_token(NULL, NULL, cfile);
+ parse_address_range6(cfile, type, cfile->stack_top);
+ range_counter++;
+ break;
+
+ case PREFIX6:
+ skip_token(NULL, NULL, cfile);
+ parse_prefix6(cfile, type, cfile->stack_top);
+ mapSet(pool, createNull(), "***mark***");
+ prefix_counter++;
+ break;
+
+ case ALLOW:
+ skip_token(NULL, NULL, cfile);
+ get_permit(cfile, permit);
+ break;
+
+ case DENY:
+ skip_token(NULL, NULL, cfile);
+ get_permit(cfile, prohibit);
+ break;
+
+ case RBRACE:
+ skip_token(&val, NULL, cfile);
+ done = ISC_TRUE;
+ break;
+
+ case END_OF_FILE:
+ /*
+ * We can get to END_OF_FILE if, for instance,
+ * the parse_statement() reads all available tokens
+ * and leaves us at the end.
+ */
+ parse_error(cfile, "unexpected end of file");
+
+ default:
+ declaration = parse_statement(cfile, POOL_DECL,
+ declaration);
+ break;
+ }
+ } while (!done);
+
+ cfile->stack_top--;
+
+ generate_class(cfile, pool, permit, prohibit);
+
+ /*
+ * Spread and eventually split between pools and pd-pools
+ */
+ if (prefix_counter == 0) {
+ /* we need pools list */
+ pools = mapGet(cfile->stack[cfile->stack_top], "pools");
+ if (pools == NULL) {
+ pools = createList();
+ pools->kind = POOL_DECL;
+ mapSet(cfile->stack[cfile->stack_top], pools, "pools");
+ }
+
+ /* no address or prefix range */
+ if (range_counter == 0) {
+ struct comment *comment;
+
+ comment = createComment("empty pool6");
+ TAILQ_INSERT_TAIL(&pool->comments, comment);
+ pool->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ listPush(pools, pool);
+ return;
+ }
+ } else {
+ /* we need pd-pools list */
+ pdpools = mapGet(cfile->stack[cfile->stack_top], "pd-pools");
+ if (pdpools == NULL) {
+ pdpools = createList();
+ pdpools->kind = POOL_DECL;
+ mapSet(cfile->stack[cfile->stack_top],
+ pdpools, "pd-pools");
+ }
+
+ /* split and purge copies */
+ pdpool = copy(pool);
+ while (mapContains(pdpool, "pool"))
+ mapRemove(pdpool, "pool");
+ while (mapContains(pool, "prefix"))
+ mapRemove(pool, "prefix");
+ while (mapContains(pool, "prefix-len"))
+ mapRemove(pool, "prefix-len");
+ while (mapContains(pool, "delegated-len"))
+ mapRemove(pool, "delegated-len");
+ while (mapContains(pool, "excluded-prefix"))
+ mapRemove(pool, "excluded-prefix");
+ while (mapContains(pool, "excluded-prefix-len"))
+ mapRemove(pool, "excluded-prefix-len");
+ while (mapContains(pool, "***mark***"))
+ mapRemove(pool, "***mark***");
+
+ /* spread extra prefixes into pdpool copies */
+ while (--prefix_counter != 0) {
+ struct handle *handle;
+ struct element *first;
+ struct element *saved;
+ isc_boolean_t seen = ISC_FALSE;
+
+ first = createMap();
+ saved = copy(pdpool);
+ while (mapSize(pdpool) > 0) {
+ handle = mapPop(pdpool);
+ if ((handle == NULL) ||
+ (handle->key == NULL) ||
+ (handle->value == NULL))
+ parse_error(cfile, "bad pdpool entry");
+ if (strcmp(handle->key, "***mark***") == 0) {
+ if (!seen) {
+ mapRemove(saved, handle->key);
+ seen = ISC_TRUE;
+ }
+ continue;
+ }
+ if ((strcmp(handle->key, "prefix") != 0) &&
+ (strcmp(handle->key, "prefix-len") != 0) &&
+ (strcmp(handle->key,
+ "delegated-len") != 0) &&
+ (strcmp(handle->key,
+ "excluded-prefix") != 0) &&
+ (strcmp(handle->key,
+ "excluded-prefix-len") != 0))
+ mapSet(first, handle->value,
+ handle->key);
+ else if (!seen) {
+ mapSet(first, handle->value,
+ handle->key);
+ mapRemove(saved, handle->key);
+ }
+ }
+ listPush(pdpools, first);
+ pdpool = saved;
+ }
+ if (!mapContains(pdpool, "***mark***"))
+ parse_error(cfile, "can't find prefix marker");
+ mapRemove(pdpool, "***mark***");
+ if (mapContains(pdpool, "***mark***"))
+ parse_error(cfile, "unexpected prefix marker");
+ listPush(pdpools, pdpool);
+ }
+
+ /* Do pools now */
+ if (range_counter != 0) {
+ /* we need pools list */
+ pools = mapGet(cfile->stack[cfile->stack_top], "pools");
+ if (pools == NULL) {
+ pools = createList();
+ pools->kind = POOL_DECL;
+ mapSet(cfile->stack[cfile->stack_top], pools, "pools");
+ }
+
+ /* spread extra prefixes into pool copies */
+ while (--range_counter != 0) {
+ struct handle *handle;
+ struct element *first;
+ struct element *saved;
+ isc_boolean_t seen = ISC_FALSE;
+
+ first = createMap();
+ saved = copy(pool);
+ while (mapSize(pool) > 0) {
+ handle = mapPop(pool);
+ if ((handle == NULL) ||
+ (handle->key == NULL) ||
+ (handle->value == NULL))
+ parse_error(cfile, "bad pool entry");
+ if (strcmp(handle->key, "pool") != 0)
+ mapSet(first, handle->value,
+ handle->key);
+ else if (!seen) {
+ mapSet(first, handle->value,
+ handle->key);
+ mapRemove(saved, "pool");
+ seen = ISC_TRUE;
+ }
+ }
+ listPush(pools, first);
+ pool = saved;
+ }
+ listPush(pools, pool);
+ }
+}
+
+/* allow-deny-keyword :== BOOTP
+ | BOOTING
+ | DYNAMIC_BOOTP
+ | UNKNOWN_CLIENTS */
+
+struct element *
+parse_allow_deny(struct parse *cfile, int flag)
+{
+ enum dhcp_token token;
+ const char *val;
+ const char *value;
+ const char *name;
+ struct element *config;
+ struct option *option;
+
+ switch (flag) {
+ case 0:
+ value = "deny";
+ break;
+ case 1:
+ value = "allow";
+ break;
+ case 2:
+ value = "ignore";
+ break;
+ default:
+ value = "unknown?";
+ break;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ switch (token) {
+ case TOKEN_BOOTP:
+ name = "allow-bootp";
+ break;
+
+ case BOOTING:
+ name = "allow-booting";
+ break;
+
+ case DYNAMIC_BOOTP:
+ name = "dynamic-bootp";
+ break;
+
+ case UNKNOWN_CLIENTS:
+ name = "boot-unknown-clients";
+ break;
+
+ case DUPLICATES:
+ name = "duplicates";
+ break;
+
+ case DECLINES:
+ name = "declines";
+ break;
+
+ case CLIENT_UPDATES:
+ name = "client-updates";
+ break;
+
+ case LEASEQUERY:
+ name = "leasequery";
+ break;
+
+ default:
+ parse_error(cfile, "expecting allow/deny key");
+ }
+ parse_semi(cfile);
+
+ config = createMap();
+ mapSet(config, createString(makeString(-1, value)), "value");
+ mapSet(config, createString(makeString(-1, name)), "name");
+ option = option_lookup_name("server", name);
+ if (option == NULL)
+ parse_error(cfile, "unknown allow/deny keyword (%s)", name);
+ mapSet(config, createInt(option->code), "code");
+ config->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ return config;
+}
+
+/*
+ * When we parse a server-duid statement in a config file, we will
+ * have the type of the server DUID to generate, and possibly the
+ * actual value defined.
+ *
+ * server-duid llt;
+ * server-duid llt ethernet|ieee802|fddi 213982198 00:16:6F:49:7D:9B;
+ * server-duid ll;
+ * server-duid ll ethernet|ieee802|fddi 00:16:6F:49:7D:9B;
+ * server-duid en 2495 "enterprise-specific-identifier-1234";
+ */
+void
+parse_server_duid_conf(struct parse *cfile) {
+ enum dhcp_token token;
+ const char *val;
+ unsigned int len;
+ struct string *ll_addr;
+ struct element *duid;
+ struct element *item;
+ int ll_type;
+
+ duid = createMap();
+ TAILQ_CONCAT(&duid->comments, &cfile->comments);
+
+ /*
+ * Consume the SERVER_DUID token.
+ */
+ next_token(&val, NULL, cfile);
+
+ /*
+ * Obtain the DUID type.
+ */
+ token = next_token(&val, NULL, cfile);
+
+ /*
+ * Enterprise is the easiest - enterprise number and raw data
+ * are required.
+ */
+ if (token == EN) {
+ item = createString(makeString(-1, "EN"));
+ mapSet(duid, item, "type");
+
+ /*
+ * Get enterprise number and identifier.
+ */
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile, "enterprise number expected");
+ item = createInt(atoi(val));
+ mapSet(duid, item, "enterprise-id");
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING)
+ parse_error(cfile, "identifier expected");
+ /* Kea requires a hexadecimal identifier */
+ if (is_hexa_only(val, len))
+ item = createString(makeString(len, val));
+ else
+ item = createString(makeStringExt(len, val, 'X'));
+ mapSet(duid, item, "identifier");
+ }
+
+ /*
+ * Next easiest is the link-layer DUID. It consists only of
+ * the LL directive, or optionally the specific value to use.
+ *
+ * If we have LL only, then we set the type. If we have the
+ * value, then we set the actual DUID.
+ */
+ else if (token == LL) {
+ item = createString(makeString(-1, "LL"));
+ mapSet(duid, item, "type");
+
+ if (peek_token(NULL, NULL, cfile) != SEMI) {
+ /*
+ * Get our hardware type and address.
+ */
+ token = next_token(NULL, NULL, cfile);
+ switch (token) {
+ case ETHERNET:
+ ll_type = HTYPE_ETHER;
+ break;
+ case TOKEN_RING:
+ ll_type = HTYPE_IEEE802;
+ break;
+ case TOKEN_FDDI:
+ ll_type = HTYPE_FDDI;
+ break;
+ default:
+ parse_error(cfile, "hardware type expected");
+ }
+ item = createInt(ll_type);
+ mapSet(duid, item, "htype");
+
+ ll_addr = parse_hexa(cfile);
+ if (ll_addr == NULL)
+ parse_error(cfile,
+ "can't get hardware address");
+ item = createString(ll_addr);
+ mapSet(duid, item, "identifier");
+ }
+ }
+
+ /*
+ * Finally the link-layer DUID plus time. It consists only of
+ * the LLT directive, or optionally the specific value to use.
+ *
+ * If we have LLT only, then we set the type. If we have the
+ * value, then we set the actual DUID.
+ */
+ else if (token == LLT) {
+ item = createString(makeString(-1, "LLT"));
+ mapSet(duid, item, "type");
+
+ if (peek_token(NULL, NULL, cfile) != SEMI) {
+ /*
+ * Get our hardware type, timestamp, and address.
+ */
+ token = next_token(NULL, NULL, cfile);
+ switch (token) {
+ case ETHERNET:
+ ll_type = HTYPE_ETHER;
+ break;
+ case TOKEN_RING:
+ ll_type = HTYPE_IEEE802;
+ break;
+ case TOKEN_FDDI:
+ ll_type = HTYPE_FDDI;
+ break;
+ default:
+ parse_error(cfile, "hardware type expected");
+ }
+ item = createInt(ll_type);
+ mapSet(duid, item, "htype");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile, "timestamp expected");
+ item = createInt(atoi(val));
+ mapSet(duid, item, "time");
+
+ ll_addr = parse_hexa(cfile);
+ if (ll_addr == NULL)
+ parse_error(cfile,
+ "can't get hardware address");
+ item = createString(ll_addr);
+ mapSet(duid, item, "identifier");
+ }
+ }
+
+ /*
+ * If users want they can use a number for DUID types.
+ * This is useful for supporting future, not-yet-defined
+ * DUID types.
+ *
+ * In this case, they have to put in the complete value.
+ *
+ * This also works for existing DUID types of course.
+ */
+ else if (token == NUMBER) {
+ item = createString(makeString(-1, val));
+ item->skip = ISC_TRUE;
+ /* Kea wants EN, LL or LLT so skip the whole thing */
+ duid->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(duid, item, "type");
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING)
+ parse_error(cfile, "identifier expected");
+ item = createString(makeString(len, val));
+ mapSet(duid, item, "identifier");
+ }
+
+ /*
+ * Anything else is an error.
+ */
+ else
+ parse_error(cfile, "DUID type of LLT, EN, or LL expected");
+
+ /*
+ * Finally consume our trailing semicolon.
+ */
+ token = next_token(NULL, NULL, cfile);
+ if (token != SEMI)
+ parse_error(cfile, "semicolon expected");
+
+ /* server-id is a global parameter */
+ if (mapContains(cfile->stack[1], "server-id"))
+ parse_error(cfile, "there is already a server-id");
+ /* DHCPv6 only but not fatal */
+ if ((local_family != AF_INET6) && !duid->skip) {
+ duid->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ mapSet(cfile->stack[1], duid, "server-id");
+}
+
+/* Check whether the argument is encoded in hexadecimal or not */
+static isc_boolean_t
+is_hexa_only(const char *s, unsigned l)
+{
+ unsigned i;
+
+ for (i = 0; i < l; i++)
+ if (!isxdigit((int)s[i]))
+ return ISC_FALSE;
+ return ISC_TRUE;
+}
+
+/*!
+ *
+ * \brief Parse (and execute) a directive (extension)
+ *
+ * OPTION SPACE <name> [ALIAS <kea-name>] [KNOWN*2|UNKNOWN*2|DYNAMIC]
+ * OPTION <universe>.<name> [CHECK]
+ * [ALIAS <name>]
+ * [CODE <code> = "<format>"]
+ * [KNOWN*2|UNKNOWN*2|DYNAMIC]
+ * [LOCAL|DEFINE]
+ */
+
+void
+parse_directive(struct parse *cfile)
+{
+ enum dhcp_token token;
+ const char *val;
+ isc_boolean_t known;
+ struct option *option;
+
+ token = peek_token(&val, NULL, cfile);
+
+ switch (token) {
+ case OPTION:
+ skip_token(&val, NULL, cfile);
+ token = peek_token(&val, NULL, cfile);
+ if (token == SPACE) {
+ parse_option_space_dir(cfile);
+ return;
+ }
+
+ known = ISC_FALSE;
+ option = parse_option_name(cfile, ISC_TRUE, &known);
+ token = next_token(&val, NULL, cfile);
+ if (token == CHECK) {
+ struct string *datatype;
+ isc_boolean_t is_array = ISC_FALSE;
+ isc_boolean_t encapsulate = ISC_FALSE;
+
+ datatype = convert_format(option->format,
+ &is_array,
+ &encapsulate);
+ printf("option ISC DHCP (Kea)\n"
+ " %s.%s (%s.%s)\n"
+ " format \"%s\" (type \"%s\" "
+ "array %s encap %s)\n"
+ " status %s\n",
+ option->space->old, option->old,
+ option->space->name, option->name,
+ option->format, datatype->content,
+ is_array ? "true" : "false",
+ encapsulate ? "true" : "false",
+ display_status(option->status));
+ parse_semi(cfile);
+ return;
+ }
+ if (option->space->status == special)
+ parse_error(cfile, "attempt to modify config %s.%s",
+ option->space->old, option->name);
+ if (token == ALIAS) {
+ token = next_token(&val, NULL, cfile);
+ if (!is_identifier(token))
+ parse_error(cfile,
+ "expecting identifier after "
+ "alias keyword.");
+ if (option->status != dynamic)
+ parse_error(cfile,
+ "attempt to rename %s.%s to %s",
+ option->space->name,
+ option->name, val);
+ option->name = strdup(val);
+ parse_semi(cfile);
+ return;
+ }
+ if (token == CODE) {
+ parse_option_code_dir(cfile, option);
+ return;
+ }
+ if ((token == KNOWN) || (token == UNKNOWN) ||
+ (token == DYNAMIC)) {
+ parse_option_status_dir(cfile, option, token);
+ return;
+ }
+ if (token == LOCAL) {
+ parse_option_local_dir(cfile, option);
+ parse_semi(cfile);
+ return;
+ }
+ if (token == DEFINE) {
+ parse_option_define_dir(cfile, option);
+ parse_semi(cfile);
+ return;
+ }
+ parse_error(cfile, "unknown option directive %s", val);
+
+ default:
+ parse_error(cfile, "unknown directive %s", val);
+ }
+}
+
+/* Set alias and status for option spaces */
+
+void
+parse_option_space_dir(struct parse *cfile)
+{
+ enum dhcp_token token;
+ const char *val;
+ struct space *space;
+
+ skip_token(NULL, NULL, cfile); /* Discard SPACE */
+ token = next_token(&val, NULL, cfile);
+ if (!is_identifier(token))
+ parse_error(cfile, "expecting identifier.");
+ space = space_lookup(val);
+ if (space == NULL)
+ parse_error(cfile, "can't find space '%s", val);
+
+ token = next_token(&val, NULL, cfile);
+ if (token == CHECK) {
+ printf("space ISC DHCP (kea)\n"
+ " %s (%s)\n status %s\n%s",
+ space->old, space->name,
+ display_status(space->status),
+ space->vendor != NULL ? " vendor\n" : "");
+ parse_semi(cfile);
+ return;
+ }
+ if (token == ALIAS) {
+ token = next_token(&val, NULL, cfile);
+ if (!is_identifier(token))
+ parse_error(cfile,
+ "expecting identifier after "
+ "alias keyword.");
+ if (space->status != dynamic)
+ parse_error(cfile,
+ "attempt to rename %s to %s",
+ space->name, val);
+ space->name = strdup(val);
+ parse_semi(cfile);
+ return;
+ }
+ if (token == DYNAMIC)
+ space->status = dynamic;
+ else if (token == UNKNOWN) {
+ token = next_token(NULL, NULL, cfile);
+ if (token == KNOWN)
+ space->status = known;
+ else if (token == UNKNOWN)
+ space->status = kea_unknown;
+ else
+ parse_error(cfile, "expected KNOW or UNKNOWN");
+ } else if (token != UNKNOWN)
+ parse_error(cfile, "expected KNOW or UNKNOWN or DYNAMIC");
+ else {
+ if (token == KNOWN)
+ space->status = isc_dhcp_unknown;
+ else if (token == UNKNOWN)
+ parse_error(cfile, "illicit combination: space "
+ "%s is known by nobody", space->name);
+ else
+ parse_error(cfile, "expected KNOW or UNKNOWN");
+ }
+ parse_semi(cfile);
+}
+
+/* Alternative to parse_option_code_decl using the raw ISC DHCP format */
+
+void
+parse_option_code_dir(struct parse *cfile, struct option *option)
+{
+ const char *val;
+ enum dhcp_token token;
+ unsigned code;
+ struct element *def;
+ struct element *optdef;
+ struct string *datatype;
+ isc_boolean_t is_array = ISC_FALSE;
+ isc_boolean_t encapsulate = ISC_FALSE;
+
+ def = createMap();
+ mapSet(def,
+ createString(makeString(-1, option->space->name)),
+ "space");
+ mapSet(def, createString(makeString(-1, option->name)), "name");
+
+ /* Parse the option code. */
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile, "expecting option code number.");
+ code = atoi(val);
+ mapSet(def, createInt(code), "code");
+
+ /* We have the code so we can get the real option now */
+ if (option->code == 0) {
+ struct option *from_code;
+
+ option->code = code;
+ from_code = option_lookup_code(option->space->old, code);
+ if (from_code != NULL)
+ option = from_code;
+ }
+
+ /* Redefinitions are not allowed */
+ if ((option->status != dynamic) ||
+ (strcmp(option->format, "u") != 0))
+ parse_error(cfile, "attempt to redefine %s.%s code %u",
+ option->space->name, option->name, code);
+
+ token = next_token(&val, NULL, cfile);
+ if (token != EQUAL)
+ parse_error(cfile, "expecting \"=\"");
+ token = next_token(&val, NULL, cfile);
+ if (token != STRING)
+ parse_error(cfile, "expecting format string");
+ option->format = strdup(val);
+ parse_semi(cfile);
+
+ datatype = convert_format(val, &is_array, &encapsulate);
+
+ if ((datatype == NULL) && (strchr(datatype->content, '?') != NULL))
+ parse_error(cfile, "failed to convert format \"%s\" for "
+ "option %s.%s code %u",
+ val, option->space->name, option->name, code);
+ /* todo */
+ if (encapsulate)
+ parse_error(cfile, "option %s.%s code %u encapsulate?",
+ option->space->name, option->name, code);
+
+ if (strchr(datatype->content, ',') == NULL)
+ mapSet(def, createString(datatype), "type");
+ else {
+ mapSet(def, createString(datatype), "record-types");
+ mapSet(def, createString(makeString(-1, "record")), "type");
+ }
+ if (is_array)
+ mapSet(def, createBool(ISC_TRUE), "array");
+
+ optdef = mapGet(cfile->stack[1], "option-def");
+ if (optdef == NULL) {
+ optdef = createList();
+ mapSet(cfile->stack[1], optdef, "option-def");
+ }
+ listPush(optdef, def);
+}
+
+/* Update the option status for instance to add standard options */
+
+void
+parse_option_status_dir(struct parse *cfile, struct option *option,
+ enum dhcp_token token)
+{
+ if (token == DYNAMIC)
+ option->status = dynamic;
+ else if (token == KNOWN) {
+ token = next_token(NULL, NULL, cfile);
+ if (token == KNOWN)
+ option->status = known;
+ else if (token == UNKNOWN)
+ option->status = kea_unknown;
+ else
+ parse_error(cfile, "expected KNOW or UNKNOWN");
+ } else if (token != UNKNOWN)
+ parse_error(cfile, "expected KNOW or UNKNOWN or DYNAMIC");
+ else {
+ if (token == KNOWN)
+ option->status = isc_dhcp_unknown;
+ else if (token == UNKNOWN)
+ parse_error(cfile, "illicit combination: option "
+ "%s.%s code %u is known by nobody",
+ option->space->name, option->name,
+ option->code);
+ else
+ parse_error(cfile, "expected KNOW or UNKNOWN");
+ }
+ parse_semi(cfile);
+}
+
+/* Make the option definition not exported to Kea */
+
+void
+parse_option_local_dir(struct parse *cfile, struct option *option)
+{
+ struct element *optdef;
+ struct element *def;
+ struct element *elem;
+ size_t i;
+
+ def = NULL;
+ if (option->code == 0)
+ parse_error(cfile, "unknown code for option %s.%s",
+ option->space->name, option->name);
+
+ optdef = mapGet(cfile->stack[1], "option-def");
+ if (optdef == NULL) {
+ optdef = createList();
+ mapSet(cfile->stack[1], optdef, "option-def");
+ goto not_found;
+ }
+ for (i = 0; i < listSize(optdef); i++) {
+ def = listGet(optdef, i);
+ elem = mapGet(def, "space");
+ if ((elem == NULL) || (elem->type != ELEMENT_STRING))
+ parse_error(cfile, "got an option definition "
+ "without space at %u", (unsigned)i);
+ if (strcmp(option->space->name,
+ stringValue(elem)->content) != 0)
+ continue;
+ elem = mapGet(def, "code");
+ if ((elem == NULL) || (elem->type != ELEMENT_INTEGER))
+ parse_error(cfile, "got an option definition "
+ "without code at %u", (unsigned)i);
+ if (intValue(elem) == option->code)
+ break;
+ }
+ if (def == NULL)
+ goto not_found;
+ def->skip = ISC_TRUE;
+ mapSet(def, createNull(), "no-export");
+ return;
+
+not_found:
+ parse_error(cfile, "can't find option %s.%s code %u in definitions",
+ option->space->name, option->name, option->code);
+}
+
+/* Make the opposite: force the definition */
+
+void
+parse_option_define_dir(struct parse *cfile, struct option *option)
+{
+ struct element *optdef;
+ struct element *def;
+ struct element *elem;
+ struct string *datatype;
+ isc_boolean_t is_array = ISC_FALSE;
+ isc_boolean_t encapsulate = ISC_FALSE;
+ size_t i;
+
+ def = NULL;
+ if (option->code == 0)
+ parse_error(cfile, "unknown code for option %s.%s",
+ option->space->name, option->name);
+
+ optdef = mapGet(cfile->stack[1], "option-def");
+ if (optdef == NULL) {
+ optdef = createList();
+ mapSet(cfile->stack[1], optdef, "option-def");
+ goto no_search;
+ }
+ for (i = 0; i < listSize(optdef); i++) {
+ def = listGet(optdef, i);
+ elem = mapGet(def, "space");
+ if ((elem == NULL) || (elem->type != ELEMENT_STRING))
+ parse_error(cfile, "got an option definition "
+ "without space at %u", (unsigned)i);
+ if (strcmp(option->space->name,
+ stringValue(elem)->content) != 0)
+ continue;
+ elem = mapGet(def, "code");
+ if ((elem == NULL) || (elem->type != ELEMENT_INTEGER))
+ parse_error(cfile, "got an option definition "
+ "without code at %u", (unsigned)i);
+ if (intValue(elem) == option->code)
+ parse_error(cfile, "unexpected definition for "
+ "option %s.%s code %u",
+ option->space->name, option->name,
+ option->code);
+ }
+no_search:
+ def = createMap();
+ mapSet(def,
+ createString(makeString(-1, option->space->name)),
+ "space");
+ mapSet(def, createString(makeString(-1, option->name)), "name");
+ mapSet(def, createInt(option->code), "code");
+
+ datatype = convert_format(option->format, &is_array, &encapsulate);
+
+ if ((datatype == NULL) && (strchr(datatype->content, '?') != NULL))
+ parse_error(cfile, "failed to convert format \"%s\" for "
+ "option %s.%s code %u",
+ option->format, option->space->name,
+ option->name, option->code);
+ /* todo */
+ if (encapsulate)
+ parse_error(cfile, "option %s.%s code %u encapsulate?",
+ option->space->name, option->name, option->code);
+
+ if (strchr(datatype->content, ',') == NULL)
+ mapSet(def, createString(datatype), "type");
+ else {
+ mapSet(def, createString(datatype), "record-types");
+ mapSet(def, createString(makeString(-1, "record")), "type");
+ }
+ if (is_array)
+ mapSet(def, createBool(ISC_TRUE), "array");
+
+ listPush(optdef, def);
+
+ return;
+}
+
+/*
+ * Push new interface on the interface list when it is not already.
+ */
+
+static void
+new_network_interface(struct parse *cfile, struct element *iface)
+{
+ struct element *ifconf;
+ struct element *iflist;
+ struct string *name = stringValue(iface);
+ int i;
+
+ ifconf = mapGet(cfile->stack[1], "interfaces-config");
+ if (ifconf == NULL) {
+ ifconf = createMap();
+ mapSet(cfile->stack[1], ifconf, "interfaces-config");
+ }
+
+ iflist = mapGet(ifconf, "interfaces");
+ if (iflist == NULL) {
+ iflist = createList();
+ mapSet(ifconf, iflist, "interfaces");
+ }
+
+ for (i = 0; i < listSize(iflist); i++) {
+ struct element *item;
+
+ item = listGet(iflist, i);
+ if ((item != NULL) &&
+ (item->type == ELEMENT_STRING) &&
+ eqString(stringValue(item), name))
+ return;
+ }
+
+ listPush(iflist, createString(name));
+}
+
+/* Convert address and mask in binary into address/len text */
+
+static const uint32_t bitmasks[32 + 1] = {
+ 0xffffffff, 0x7fffffff, 0x3fffffff, 0x1fffffff,
+ 0x0fffffff, 0x07ffffff, 0x03ffffff, 0x01ffffff,
+ 0x00ffffff, 0x007fffff, 0x003fffff, 0x001fffff,
+ 0x000fffff, 0x0007ffff, 0x0003ffff, 0x0001ffff,
+ 0x0000ffff, 0x00007fff, 0x00003fff, 0x00001fff,
+ 0x00000fff, 0x000007ff, 0x000003ff, 0x000001ff,
+ 0x000000ff, 0x0000007f, 0x0000003f, 0x0000001f,
+ 0x0000000f, 0x00000007, 0x00000003, 0x00000001,
+ 0x00000000 };
+
+static struct string *
+addrmask(const struct string *address, const struct string *netmask)
+{
+ struct string *result;
+ uint8_t plen;
+ uint32_t mask;
+
+ result = makeStringExt(address->length, address->content, 'I');
+
+ memcpy(&mask, netmask->content, 4);
+ mask = ntohl(mask);
+ for (plen = 0; plen <= 32; ++plen)
+ if (~mask == bitmasks[plen])
+ break;
+ if (plen > 32)
+ return NULL;
+
+ appendString(result, "/");
+ concatString(result, makeStringExt(1, (char *)&plen, 'B'));
+ return result;
+}
+
+/*
+ * find a place where to put a reservation
+ * (reservations aka hosts must be in a subnet in Kea < 1.5)
+ * (defaulting to the last defined subnet (e.g. for reservations
+ * without any address).
+ * (first step is to find an enclosing group).
+ */
+
+static struct element *
+find_match(struct parse *cfile, struct element *host,
+ isc_boolean_t *used_heuristicp)
+{
+ struct element *address;
+ struct subnet *subnet;
+ char addr[16];
+ size_t group;
+ size_t i, len;
+ int kind;
+
+ if (global_hr) {
+ struct element *hosts;
+
+ hosts = mapGet(cfile->stack[1], "reservations");
+ if (!hosts) {
+ mapSet(cfile->stack[1],
+ createString(makeString(-1, "global")),
+ "reservation-mode");
+ hosts = createList();
+ mapSet(cfile->stack[1], hosts, "reservations");
+ }
+ *used_heuristicp = ISC_FALSE;
+ return cfile->stack[1];
+ }
+
+ for (group = cfile->stack_top; group > 0; --group) {
+ kind = cfile->stack[group]->kind;
+ if ((kind == GROUP_DECL) || (kind == ROOT_GROUP))
+ break;
+ }
+ if (!group)
+ parse_error(cfile, "can't find root group");
+ if (kind == GROUP_DECL)
+ return cfile->stack[group];
+
+ if (local_family == AF_INET) {
+ address = mapGet(host, "ip-address");
+ if (address == NULL) {
+ if (TAILQ_EMPTY(&known_subnets))
+ return cfile->stack[1];
+ if (used_heuristicp)
+ *used_heuristicp = ISC_TRUE;
+ return TAILQ_LAST(&known_subnets, subnets)->subnet;
+ }
+ len = 4;
+ } else {
+ address = mapGet(host, "ip-addresses");
+ if (address == NULL) {
+ if (TAILQ_EMPTY(&known_subnets))
+ return cfile->stack[1];
+ if (used_heuristicp)
+ *used_heuristicp = ISC_TRUE;
+ return TAILQ_LAST(&known_subnets, subnets)->subnet;
+ }
+ address = listGet(address, 0);
+ if (address == NULL)
+ return TAILQ_LAST(&known_subnets, subnets)->subnet;
+ len = 16;
+ }
+
+ if (inet_pton(local_family, stringValue(address)->content, addr) != 1)
+ parse_error(cfile, "bad address %s",
+ stringValue(address)->content);
+ TAILQ_FOREACH(subnet, &known_subnets) {
+ isc_boolean_t matching = ISC_TRUE;
+
+ if (subnet->mask->length != len)
+ continue;
+ for (i = 0; i < len; i++)
+ if ((addr[i] & subnet->mask->content[i]) !=
+ subnet->addr->content[i]) {
+ matching = ISC_FALSE;
+ break;
+ }
+ if (matching)
+ return subnet->subnet;
+ }
+ return cfile->stack[1];
+}
+
+/*
+ * find a subnet where to put a pool
+ * (pools are not allowed at shared-network level in Kea)
+ */
+
+static struct element *
+find_location(struct element *share, struct range *range)
+{
+ struct subnet *subnet;
+ size_t i;
+
+ TAILQ_FOREACH(subnet, &known_subnets) {
+ isc_boolean_t matching = ISC_TRUE;
+
+ if (subnet->share != share)
+ continue;
+ if (subnet->mask->length != range->low->length)
+ continue;
+ for (i = 0; i < range->low->length; i++)
+ if ((range->low->content[i] &
+ subnet->mask->content[i]) !=
+ subnet->addr->content[i]) {
+ matching = ISC_FALSE;
+ break;
+ }
+ if (matching)
+ return subnet->subnet;
+ }
+ return NULL;
+}
+
+/*
+ * Compute a prefix length from lower - higher IPv6 addresses.
+ */
+
+static const uint8_t bytemasks[8] = {
+ 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff
+};
+
+static int
+get_prefix_length(const char *low, const char *high)
+{
+ uint8_t lo[16];
+ uint8_t hi[16];
+ uint8_t xor[16];
+ int i, plen;
+
+ if ((inet_pton(AF_INET6, low, lo) != 1) ||
+ (inet_pton(AF_INET6, high, hi) != 1))
+ return -100;
+
+ for (i = 0; i < 16; i++)
+ xor[i] = lo[i] ^ hi[i];
+ for (plen = 0; plen < 128; plen += 8)
+ if (xor[plen / 8] != 0)
+ break;
+ if (plen == 128)
+ return plen;
+ for (i = (plen / 8) + 1; i < 16; i++)
+ if (xor[i] != 0)
+ return -2;
+ for (i = 0; i < 8; i++) {
+ uint8_t msk = ~xor[plen / 8];
+
+ if (msk == bytemasks[i])
+ return plen + i + 1;
+ }
+ return -1;
+}
+
+/*
+ * Get a (global) class from its reference, i.e.:
+ * - name for a (super)class
+ * - super, and binary or string for a subclass
+ */
+static struct element *
+get_class(struct parse *cfile, struct element *ref)
+{
+ struct element *classes;
+ struct element *class;
+ struct element *name;
+ struct element *selector;
+ struct element *param;
+ size_t i;
+
+ classes = mapGet(cfile->stack[1], "client-classes");
+ if ((classes == NULL) || (listSize(classes) == 0))
+ return NULL;
+
+ name = mapGet(ref, "super");
+ if (name == NULL) {
+ name = mapGet(ref, "name");
+ if (name == NULL)
+ return NULL;
+ for (i = 0; i < listSize(classes); i++) {
+ class = listGet(classes, i);
+ if (mapContains(ref, "super"))
+ continue;
+ param = mapGet(class, "name");
+ if (param == NULL)
+ continue;
+ if (eqString(stringValue(name), stringValue(param)))
+ return class;
+ }
+ return NULL;
+ }
+ selector = mapGet(ref, "string");
+ if (selector == NULL) {
+ selector = mapGet(ref, "binary");
+ if (selector == NULL)
+ return NULL;
+ for (i = 0; i <listSize(classes); i++) {
+ class = listGet(classes, i);
+ param = mapGet(class, "super");
+ if (param == NULL)
+ continue;
+ if (!eqString(stringValue(name), stringValue(param)))
+ continue;
+ param = mapGet(class, "binary");
+ if (param == NULL)
+ continue;
+ if (eqString(stringValue(selector),
+ stringValue(param)))
+ return class;
+ }
+ return NULL;
+ }
+ for (i = 0; i <listSize(classes); i++) {
+ class = listGet(classes, i);
+ param = mapGet(class, "super");
+ if (param == NULL)
+ continue;
+ if (!eqString(stringValue(name), stringValue(param)))
+ continue;
+ param = mapGet(class, "string");
+ if (param == NULL)
+ continue;
+ if (eqString(stringValue(selector), stringValue(param)))
+ return class;
+ }
+ return NULL;
+}
+
+/*
+ * Concatenate two class reference lists eliminating duplicates
+ * (complexity is bad: if this becomes a performance pig, use a hash table)
+ */
+
+static void
+concat_classes(struct parse *cfile, struct element *dst, struct element *src)
+{
+ struct element *class;
+ struct element *sitem;
+ struct element *ditem;
+ size_t i;
+ isc_boolean_t dup;
+
+ while (listSize(src) > 0) {
+ sitem = listGet(src, 0);
+ listRemove(src, 0);
+ class = get_class(cfile, sitem);
+ if (class == NULL)
+ /* just ignore */
+ continue;
+ dup = ISC_FALSE;
+ for (i = 0; i < listSize(dst); i++) {
+ ditem = listGet(dst, i);
+ if (class == get_class(cfile, ditem)) {
+ dup = ISC_TRUE;
+ break;
+ }
+ }
+ if (dup)
+ continue;
+ listPush(dst, sitem);
+ }
+}
+
+/* Generate a class from allow/deny member lists */
+
+static void
+generate_class(struct parse *cfile, struct element *pool,
+ struct element *allow, struct element *deny)
+{
+ struct element *classes;
+ struct element *class;
+ struct element *elem;
+ struct element *prop;
+ struct element *depend;
+ struct element *result = NULL;
+ struct string *name;
+ struct string *expr;
+ struct string *msg;
+ struct comments comments;
+ struct comment *comment;
+ isc_boolean_t rescan;
+ size_t i;
+
+ if ((listSize(allow) == 0) && (listSize(deny) == 0))
+ return;
+
+ classes = mapGet(cfile->stack[1], "generated-classes");
+ if (classes == NULL) {
+ classes = createList();
+ mapSet(cfile->stack[1], classes, "generated-classes");
+ }
+
+ /* Create comments */
+ TAILQ_INIT(&comments);
+ comment = createComment("/// From:");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ for (i = 0; i < listSize(allow); i++) {
+ struct element *alias;
+
+ elem = listGet(allow, i);
+ assert(elem != NULL);
+ prop = mapGet(elem, "class");
+ assert(prop != NULL);
+ assert(prop->type == ELEMENT_STRING);
+ alias = mapGet(elem, "real");
+ msg = makeString(-1, "/// allow ");
+ concatString(msg, stringValue(alias ? alias : prop));
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&comments, comment);
+ }
+ for (i = 0; i < listSize(deny); i++) {
+ struct element *alias;
+
+ elem = listGet(deny, i);
+ assert(elem != NULL);
+ prop = mapGet(elem, "class");
+ assert(prop != NULL);
+ assert(prop->type == ELEMENT_STRING);
+ alias = mapGet(elem, "real");
+ msg = makeString(-1, "/// deny ");
+ concatString(msg, stringValue(alias ? alias : prop));
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&comments, comment);
+ }
+ TAILQ_CONCAT(&comments, &allow->comments);
+ TAILQ_CONCAT(&comments, &deny->comments);
+
+ /* Deal with special cases */
+ for (;;) {
+ rescan = ISC_FALSE;
+ for (i = 0; i < listSize(allow); i++) {
+ elem = listGet(allow, i);
+ assert(elem != NULL);
+ prop = mapGet(elem, "way");
+ assert(prop != NULL);
+ assert(prop->type == ELEMENT_BOOLEAN);
+ class = mapGet(elem, "class");
+ assert(class != NULL);
+ assert(class->type == ELEMENT_STRING);
+ /* allow !ALL and other */
+ if (eqString(stringValue(class), CLASS_ALL) &&
+ !boolValue(prop) && (listSize(allow) > 1)) {
+ listRemove(allow, i);
+ rescan = ISC_TRUE;
+ break;
+ }
+ /* allow ALL alone */
+ if (eqString(stringValue(class), CLASS_ALL) &&
+ boolValue(prop) && (listSize(allow) == 1)) {
+ resetList(allow);
+ rescan = ISC_TRUE;
+ break;
+ }
+ }
+ if (!rescan)
+ break;
+ }
+
+ for (;;) {
+ rescan = ISC_FALSE;
+ for (i = 0; i < listSize(deny); i++) {
+ elem = listGet(deny, i);
+ assert(elem != NULL);
+ prop = mapGet(elem, "way");
+ assert(prop != NULL);
+ assert(prop->type == ELEMENT_BOOLEAN);
+ class = mapGet(elem, "class");
+ assert(class != NULL);
+ assert(class->type == ELEMENT_STRING);
+ /* DENY !ALL */
+ if (eqString(stringValue(class), CLASS_ALL) &&
+ !boolValue(prop)) {
+ listRemove(deny, i);
+ rescan = ISC_TRUE;
+ break;
+ }
+ /* DENY ALL */
+ if (eqString(stringValue(class), CLASS_ALL) &&
+ boolValue(prop)) {
+ resetList(allow);
+ if (listSize(deny) > 1) {
+ listRemove(deny, i);
+ resetList(deny);
+ listPush(deny, elem);
+ }
+ break;
+ }
+ }
+ if (!rescan)
+ break;
+ }
+
+ /* Fully cleaned? */
+ if ((listSize(allow) == 0) && (listSize(deny) == 0)) {
+ if (result != NULL)
+ TAILQ_CONCAT(&result->comments, &comments);
+ else
+ TAILQ_CONCAT(&pool->comments, &comments);
+ return;
+ }
+
+ /* Unique allow member short cut */
+ if ((listSize(allow) == 1) && (listSize(deny) == 0) &&
+ !allow->skip && !deny->skip) {
+ elem = listGet(allow, 0);
+ assert(elem != NULL);
+ prop = mapGet(elem, "way");
+ assert(prop != NULL);
+ assert(prop->type == ELEMENT_BOOLEAN);
+ class = mapGet(elem, "class");
+ assert(class != NULL);
+ assert(class->type == ELEMENT_STRING);
+ if (boolValue(prop)) {
+ result = createString(stringValue(class));
+ TAILQ_CONCAT(&result->comments, &comments);
+ mapSet(pool, result, "client-class");
+ return;
+ }
+ }
+
+ /* Build name */
+ name = makeString(-1, "gen#");
+ for (i = 0; i < listSize(allow); i++) {
+ elem = listGet(allow, i);
+ assert(elem != NULL);
+ prop = mapGet(elem, "way");
+ assert(prop != NULL);
+ assert(prop->type == ELEMENT_BOOLEAN);
+ if (!boolValue(prop))
+ appendString(name, "!");
+ prop = mapGet(elem, "class");
+ assert(prop != NULL);
+ assert(prop->type == ELEMENT_STRING);
+ concatString(name, stringValue(prop));
+ appendString(name, "#");
+ }
+ if (listSize(deny) > 0) {
+ appendString(name, "_AND_#");
+ for (i = 0; i < listSize(deny); i++) {
+ elem = listGet(deny, i);
+ assert(elem != NULL);
+ prop = mapGet(elem, "way");
+ assert(prop != NULL);
+ assert(prop->type == ELEMENT_BOOLEAN);
+ if (boolValue(prop))
+ appendString(name, "!");
+ prop = mapGet(elem, "class");
+ assert(prop != NULL);
+ assert(prop->type == ELEMENT_STRING);
+ concatString(name, stringValue(prop));
+ appendString(name, "#");
+ }
+ }
+
+ /* Check if it already exists */
+ for (i = 0; i < listSize(classes); i++) {
+ struct element *descr;
+
+ class = listGet(classes, i);
+ assert(class != NULL);
+ descr = mapGet(class, "name");
+ assert(descr != NULL);
+ assert(descr->type == ELEMENT_STRING);
+ if (!eqString(name, stringValue(descr)))
+ continue;
+ result = createString(name);
+ TAILQ_CONCAT(&result->comments, &comments);
+ mapSet(pool, result, "client-class");
+ return;
+ }
+
+ /* Create expression */
+ class = createMap();
+ depend = createList();
+ expr = allocString();
+
+ if ((listSize(allow) > 0) && (listSize(deny) > 0))
+ appendString(expr, "(");
+
+ for (i = 0; i < listSize(allow); i++) {
+ isc_boolean_t negative;
+
+ if (i > 0)
+ appendString(expr, " or ");
+ elem = listGet(allow, i);
+ prop = mapGet(elem, "way");
+ negative = !boolValue(prop);
+ prop = mapGet(elem, "class");
+ if (negative)
+ appendString(expr, "not ");
+ appendString(expr, "member('");
+ concatString(expr, stringValue(prop));
+ appendString(expr, "')");
+ listPush(depend, createString(stringValue(prop)));
+ }
+
+ if ((listSize(allow) > 0) && (listSize(deny) > 0))
+ appendString(expr, ") and ");
+
+ for (i = 0; i < listSize(deny); i++) {
+ isc_boolean_t negative;
+
+ if (i > 0)
+ appendString(expr, " and ");
+ elem = listGet(deny, i);
+ prop = mapGet(elem, "way");
+ negative = boolValue(prop);
+ prop = mapGet(elem, "class");
+ if (negative)
+ appendString(expr, "not ");
+ appendString(expr, "member('");
+ concatString(expr, stringValue(prop));
+ appendString(expr, "')");
+ listPush(depend, createString(stringValue(prop)));
+ }
+
+ mapSet(class, createString(name), "name");
+ mapSet(class, createString(expr), "test");
+ mapSet(class, depend, "depend");
+ /* inherit untranslatable cases */
+ class->skip |= allow->skip || deny->skip;
+ listPush(classes, class);
+
+ result = createString(name);
+ TAILQ_CONCAT(&result->comments, &comments);
+ mapSet(pool, result, "client-class");
+}
diff --git a/keama/data.c b/keama/data.c
new file mode 100644
index 00000000..bd656289
--- /dev/null
+++ b/keama/data.c
@@ -0,0 +1,1258 @@
+/*
+ * Copyright (c) 2017 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * http://www.isc.org/
+ */
+
+#include "data.h"
+
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct string *
+allocString(void)
+{
+ struct string *result;
+
+ result = (struct string *)malloc(sizeof(struct string));
+ assert(result != NULL);
+ memset(result, 0, sizeof(struct string));
+
+ return result;
+}
+
+struct string *
+makeString(int l, const char *s)
+{
+ struct string *result;
+
+ result = allocString();
+ if (l < 0)
+ result->length = strlen(s);
+ else
+ result->length = (size_t)l;
+ if (result->length > 0) {
+ result->content = (char *)malloc(result->length + 1);
+ assert(result->content != NULL);
+ memcpy(result->content, s, result->length);
+ result->content[result->length] = 0;
+ }
+
+ return result;
+}
+
+struct string *
+makeStringExt(int l, const char *s, char fmt)
+{
+ switch (fmt) {
+ case 'Z':
+ /* zero-length */
+ return allocString();
+
+ case 'l': {
+ /* 32-bit signed integer */
+ int32_t x;
+ char buf[40];
+
+ assert(s != NULL);
+ assert(l > 3);
+
+ memcpy(&x, s, 4);
+ x = (int32_t)ntohl((uint32_t)x);
+ snprintf(buf, sizeof(buf), "%lld", (long long)x);
+ return makeString(-1, buf);
+ }
+
+ case 'L': {
+ /* 32-bit unsigned integer */
+ uint32_t x;
+ char buf[40];
+
+ assert(s != NULL);
+ assert(l > 3);
+
+ memcpy(&x, s, 4);
+ x = ntohl(x);
+ snprintf(buf, sizeof(buf), "%llu", (unsigned long long)x);
+ return makeString(-1, buf);
+ }
+
+ case 's': {
+ /* 16-bit signed integer */
+ int16_t x;
+ char buf[20];
+
+ assert(s != NULL);
+ assert(l > 1);
+
+ memcpy(&x, s, 2);
+ x = (int16_t)ntohs((uint16_t)x);
+ snprintf(buf, sizeof(buf), "%hd", x);
+ return makeString(-1, buf);
+ }
+
+ case 'S': {
+ /* 16-bit unsigned integer */
+ uint16_t x;
+ char buf[20];
+
+ assert(s != NULL);
+ assert(l > 1);
+
+ memcpy(&x, s, 2);
+ x = ntohs(x);
+ snprintf(buf, sizeof(buf), "%hu", x);
+ return makeString(-1, buf);
+ }
+
+ case 'b': {
+ /* 8-bit signed integer */
+ int8_t x;
+ char buf[10];
+
+ assert(s != NULL);
+ assert(l > 0);
+
+ memcpy(&x, s, 1);
+ snprintf(buf, sizeof(buf), "%hhd", x);
+ return makeString(-1, buf);
+ }
+
+ case 'B': {
+ /* 8-bit unsigned integer */
+ uint8_t x;
+ char buf[10];
+
+ assert(s != NULL);
+ assert(l > 0);
+
+ memcpy(&x, s, 1);
+ snprintf(buf, sizeof(buf), "%hhu", x);
+ return makeString(-1, buf);
+ }
+
+ case 'f': {
+ /* flag (true or false) */
+ uint8_t f;
+
+ assert(s != NULL);
+ assert(l > 0);
+
+ f = *s;
+ return makeString(-1, f ? "true" : "false");
+ }
+
+ case 'X': {
+ /* binary data */
+ struct string *result;
+ size_t i;
+ char buf[4];
+
+ assert((l == 0) || (s != NULL));
+
+ result = allocString();
+ for (i = 0; i < l; i++) {
+ snprintf(buf, sizeof(buf), "%02hhx", (uint8_t)s[i]);
+ appendString(result, buf);
+ }
+ return result;
+ }
+
+ case 'H': {
+ /* binary data with colons */
+ struct string *result;
+ size_t i;
+ isc_boolean_t first = ISC_TRUE;
+ char buf[4];
+
+ assert((l == 0) || (s != NULL));
+
+ result = allocString();
+ for (i = 0; i < l; i++) {
+ if (!first)
+ appendString(result, ":");
+ first = ISC_FALSE;
+ snprintf(buf, sizeof(buf), "%02hhx", (uint8_t)s[i]);
+ appendString(result, buf);
+ }
+ return result;
+ }
+
+ case 'I': {
+ /* IPv4 address to text */
+ char buf[40 /* INET_ADDRSTRLEN == 26 */];
+
+ assert(l > 3);
+ assert(inet_ntop(AF_INET, s, buf, sizeof(buf)) != NULL);
+ return makeString(-1, buf);
+ }
+
+ case 'i': {
+ /* IPv4 address to hexa */
+ uint8_t a[4];
+ char buf[10];
+
+ assert(inet_pton(AF_INET, s, a) == 1);
+ snprintf(buf, sizeof(buf), "%02hhx%02hhx%02hhx%02hhx",
+ a[0], a[1], a[2], a[3]);
+ return makeString(-1, buf);
+ }
+
+ case '6': {
+ /* IPv6 address */
+ char buf[80 /* INET6_ADDRSTRLEN == 46 */];
+
+ assert(l > 15);
+ assert(inet_ntop(AF_INET6, s, buf, sizeof(buf)) != NULL);
+ return makeString(-1, buf);
+ }
+
+ case 'd': {
+ /* FQDN to DNS wire format */
+ struct string *result;
+ const char *p;
+ const char *dot;
+ char ll;
+
+ assert(s[l] == '0');
+
+ result = allocString();
+ p = s;
+ while ((dot = strchr(p, '.')) != NULL) {
+ int len;
+
+ len = dot - p - 1;
+ if ((len & 0xc0) != 0)
+ return NULL;
+ if (dot - s >= l)
+ return NULL;
+ ll = len & 0x3f;
+ concatString(result, makeString(1, &ll));
+ concatString(result, makeString(len, p));
+ p = dot + 1;
+ if (p - s == l)
+ break;
+ }
+ if (dot == NULL) {
+ ll = 0;
+ concatString(result, makeString(1, &ll));
+ }
+ return result;
+ }
+
+ default:
+ assert(0);
+ }
+}
+
+struct string *
+makeStringArray(int l, const char *s, char fmt)
+{
+ struct string *result;
+ size_t step;
+ isc_boolean_t first = ISC_TRUE;
+
+ switch (fmt) {
+ case '6':
+ step = 16;
+ break;
+ case 'l':
+ case 'L':
+ case 'I':
+ step = 4;
+ break;
+ case 's':
+ case 'S':
+ step = 2;
+ break;
+ case 'b':
+ case 'B':
+ case 'f':
+ step = 1;
+ break;
+ default:
+ assert(0);
+ }
+
+ assert((l % step) == 0);
+
+ result = allocString();
+ while (l > 0) {
+ if (!first)
+ appendString(result, ",");
+ first = ISC_FALSE;
+ concatString(result, makeStringExt(l, s, fmt));
+ s += step;
+ l -= step;
+ }
+ return result;
+}
+
+void
+appendString(struct string *s, const char *a)
+{
+ size_t n;
+
+ assert(s != NULL);
+
+ if (a == NULL)
+ return;
+ n = strlen(a);
+ if (n == 0)
+ return;
+ s->content = (char *)realloc(s->content, s->length + n + 1);
+ assert(s->content != NULL);
+ memcpy(s->content + s->length, a, n);
+ s->length += n;
+ s->content[s->length] = 0;
+}
+
+void
+concatString(struct string *s, const struct string *a)
+{
+ assert(s != NULL);
+ assert(a != NULL);
+
+ s->content = (char *)realloc(s->content, s->length + a->length + 1);
+ assert(s->content != NULL);
+ memcpy(s->content + s->length, a->content, a->length);
+ s->length += a->length;
+ s->content[s->length] = 0;
+}
+
+isc_boolean_t
+eqString(const struct string *s, const struct string *o)
+{
+ assert(s != NULL);
+ assert(o != NULL);
+
+ if (s->length != o->length)
+ return ISC_FALSE;
+ if (s->length == 0)
+ return ISC_TRUE;
+ return ISC_TF(memcmp(s->content, o->content, s->length) == 0);
+}
+
+struct string *
+quote(struct string *s)
+{
+ struct string *result;
+
+ result = makeString(-1, "'");
+ concatString(result, s);
+ appendString(result, "'");
+ return result;
+}
+
+struct comment *
+createComment(const char *line)
+{
+ struct comment *comment;
+
+ assert(line != NULL);
+
+ comment = (struct comment *)malloc(sizeof(struct comment));
+ assert(comment != NULL);
+ memset(comment, 0, sizeof(struct comment));
+
+ comment->line = strdup(line);
+
+ return comment;
+}
+
+int64_t
+intValue(const struct element *e)
+{
+ assert(e != NULL);
+ assert(e->type == ELEMENT_INTEGER);
+ return e->value.int_value;
+}
+
+double
+doubleValue(const struct element *e)
+{
+ assert(e != NULL);
+ assert(e->type == ELEMENT_REAL);
+ return e->value.double_value;
+}
+
+isc_boolean_t
+boolValue(const struct element *e)
+{
+ assert(e != NULL);
+ assert(e->type == ELEMENT_BOOLEAN);
+ /* could check if 0 or 1 */
+ return e->value.bool_value;
+}
+
+struct string *
+stringValue(struct element *e)
+{
+ assert(e != NULL);
+ assert(e->type == ELEMENT_STRING);
+ return &e->value.string_value;
+}
+
+struct list *
+listValue(struct element *e)
+{
+ assert(e != NULL);
+ assert(e->type == ELEMENT_LIST);
+ return &e->value.list_value;
+}
+
+struct map *
+mapValue(struct element *e)
+{
+ assert(e != NULL);
+ assert(e->type == ELEMENT_MAP);
+ return &e->value.map_value;
+}
+
+struct element *
+create(void)
+{
+ struct element *elem;
+
+ elem = (struct element *)malloc(sizeof(struct element));
+ assert(elem != NULL);
+ memset(elem, 0, sizeof(struct element));
+ TAILQ_INIT(&elem->comments);
+
+ return elem;
+}
+
+struct element *
+createInt(int64_t i)
+{
+ struct element *elem;
+
+ elem = create();
+ elem->type = ELEMENT_INTEGER;
+ elem->value.int_value = i;
+
+ return elem;
+}
+
+struct element *
+createDouble(double d)
+{
+ struct element *elem;
+
+ elem = create();
+ elem->type = ELEMENT_REAL;
+ elem->value.double_value = d;
+
+ return elem;
+}
+
+struct element *
+createBool(isc_boolean_t b)
+{
+ struct element *elem;
+
+ elem = create();
+ elem->type = ELEMENT_BOOLEAN;
+ elem->value.bool_value = b;
+
+ return elem;
+}
+
+struct element *
+createNull(void)
+{
+ struct element *elem;
+
+ elem = create();
+ elem->type = ELEMENT_NULL;
+
+ return elem;
+}
+
+struct element *
+createString(const struct string *s)
+{
+ struct element *elem;
+
+ elem = create();
+ elem->type = ELEMENT_STRING;
+ elem->value.string_value = *s;
+
+ return elem;
+}
+
+struct element *
+createList(void)
+{
+ struct element *elem;
+
+ elem = create();
+ elem->type = ELEMENT_LIST;
+ TAILQ_INIT(&elem->value.list_value);
+
+ return elem;
+}
+
+struct element *
+createMap(void)
+{
+ struct element *elem;
+
+ elem = create();
+ elem->type = ELEMENT_MAP;
+ TAILQ_INIT(&elem->value.map_value);
+
+ return elem;
+}
+
+static void
+reset(struct element *e)
+{
+ e->type = 0;
+ e->kind = 0;
+ assert(e->key == NULL);
+ memset(&e->value, 0, sizeof(e->value));
+}
+
+void
+resetInt(struct element *e, int64_t i)
+{
+ assert(e != NULL);
+
+ reset(e);
+ e->type = ELEMENT_INTEGER;
+ e->value.int_value = i;
+}
+
+void
+resetDouble(struct element *e, double d)
+{
+ assert(e != NULL);
+
+ reset(e);
+ e->type = ELEMENT_REAL;
+ e->value.double_value = d;
+}
+
+void
+resetBool(struct element *e, isc_boolean_t b)
+{
+ assert(e != NULL);
+
+ reset(e);
+ e->type = ELEMENT_BOOLEAN;
+ e->value.bool_value = b;
+}
+
+void resetNull(struct element *e)
+{
+ assert(e != NULL);
+
+ reset(e);
+ e->type = ELEMENT_NULL;
+}
+
+void
+resetString(struct element *e, const struct string *s)
+{
+ assert(e != NULL);
+
+ reset(e);
+ e->type = ELEMENT_STRING;
+ e->value.string_value = *s;
+}
+
+void
+resetList(struct element *e)
+{
+ assert(e != NULL);
+
+ reset(e);
+ e->type = ELEMENT_LIST;
+ TAILQ_INIT(&e->value.list_value);
+}
+
+void
+resetMap(struct element *e)
+{
+ assert(e != NULL);
+
+ reset(e);
+ e->type = ELEMENT_MAP;
+ TAILQ_INIT(&e->value.map_value);
+}
+
+void
+resetBy(struct element *e, struct element *o)
+{
+ assert(e != NULL);
+ assert(o != NULL);
+
+ reset(e);
+ e->type = o->type;
+ e->kind = o->kind;
+ e->skip = o->skip;
+ e->key = o->key;
+ o->key = NULL;
+ TAILQ_CONCAT(&e->comments, &o->comments);
+
+ switch (e->type) {
+ case ELEMENT_INTEGER:
+ e->value.int_value = o->value.int_value;
+ break;
+ case ELEMENT_REAL:
+ e->value.double_value = o->value.double_value;
+ break;
+ case ELEMENT_BOOLEAN:
+ e->value.bool_value = o->value.bool_value;
+ break;
+ case ELEMENT_STRING:
+ e->value.string_value = o->value.string_value;
+ break;
+ case ELEMENT_LIST:
+ TAILQ_INIT(&e->value.list_value);
+ TAILQ_CONCAT(&e->value.list_value, &o->value.list_value);
+ break;
+ case ELEMENT_MAP:
+ TAILQ_INIT(&e->value.map_value);
+ TAILQ_CONCAT(&e->value.map_value, &o->value.map_value);
+ break;
+ default:
+ assert(0);
+ }
+ reset(o);
+}
+
+struct element *
+listGet(struct element *l, int i)
+{
+ struct element *elem;
+
+ assert(l != NULL);
+ assert(l->type == ELEMENT_LIST);
+ assert(i >= 0);
+
+ elem = TAILQ_FIRST(&l->value.list_value);
+ assert(elem != NULL);
+ assert(elem->key == NULL);
+
+ unsigned j;
+ for (j = i; j > 0; --j) {
+ elem = TAILQ_NEXT(elem);
+ assert(elem != NULL);
+ assert(elem->key == NULL);
+ }
+
+ return elem;
+}
+
+void
+listSet(struct element *l, struct element *e, int i)
+{
+ assert(l != NULL);
+ assert(l->type == ELEMENT_LIST);
+ assert(e != NULL);
+ assert(i >= 0);
+
+ if (i == 0) {
+ TAILQ_INSERT_HEAD(&l->value.list_value, e);
+ } else {
+ struct element *prev;
+
+ prev = TAILQ_FIRST(&l->value.list_value);
+ assert(prev != NULL);
+ assert(prev->key == NULL);
+
+ unsigned j;
+ for (j = i; j > 1; --j) {
+ prev = TAILQ_NEXT(prev);
+ assert(prev != NULL);
+ assert(prev->key == NULL);
+ }
+
+ TAILQ_INSERT_AFTER(&l->value.list_value, prev, e);
+ }
+}
+
+void
+listPush(struct element *l, struct element *e)
+{
+ assert(l != NULL);
+ assert(l->type == ELEMENT_LIST);
+ assert(e != NULL);
+
+ TAILQ_INSERT_TAIL(&l->value.list_value, e);
+}
+
+void
+listRemove(struct element *l, int i)
+{
+ struct element *elem;
+
+ assert(l != NULL);
+ assert(l->type == ELEMENT_LIST);
+ assert(i >= 0);
+
+ elem = TAILQ_FIRST(&l->value.list_value);
+ assert(elem != NULL);
+ assert(elem->key == NULL);
+
+ unsigned j;
+ for (j = i; j > 0; --j) {
+ elem = TAILQ_NEXT(elem);
+ assert(elem != NULL);
+ assert(elem->key == NULL);
+ }
+
+ TAILQ_REMOVE(&l->value.list_value, elem);
+}
+
+size_t
+listSize(const struct element *l)
+{
+ struct element *elem;
+ size_t cnt;
+
+ assert(l != NULL);
+ assert(l->type == ELEMENT_LIST);
+
+ cnt = 0;
+ TAILQ_FOREACH(elem, &l->value.list_value) {
+ assert(elem->key == NULL);
+ cnt++;
+ }
+
+ return cnt;
+}
+
+void
+concat(struct element *l, struct element *o)
+{
+ assert(l != NULL);
+ assert(l->type == ELEMENT_LIST);
+ assert(o != NULL);
+ assert(o->type == ELEMENT_LIST);
+
+ TAILQ_CONCAT(&l->value.list_value, &o->value.list_value);
+}
+
+struct element *
+mapGet(struct element *m, const char *k)
+{
+ struct element *elem;
+
+ assert(m != NULL);
+ assert(m->type == ELEMENT_MAP);
+ assert(k != NULL);
+
+ TAILQ_FOREACH(elem, &m->value.map_value) {
+ assert(elem->key != NULL);
+ if (strcmp(elem->key, k) == 0)
+ break;
+ }
+
+ return elem;
+}
+
+void
+mapSet(struct element *m, struct element *e, const char *k)
+{
+ assert(m != NULL);
+ assert(m->type == ELEMENT_MAP);
+ assert(e != NULL);
+ assert(k != NULL);
+#if 0
+ assert(mapGet(m, k) == NULL);
+#endif
+ e->key = strdup(k);
+ assert(e->key != NULL);
+ TAILQ_INSERT_TAIL(&m->value.map_value, e);
+}
+
+void
+mapRemove(struct element *m, const char *k)
+{
+ struct element *elem;
+
+ assert(m != NULL);
+ assert(m->type == ELEMENT_MAP);
+ assert(k != NULL);
+
+ TAILQ_FOREACH(elem, &m->value.map_value) {
+ assert(elem->key != NULL);
+ if (strcmp(elem->key, k) == 0)
+ break;
+ }
+
+ assert(elem != NULL);
+ TAILQ_REMOVE(&m->value.map_value, elem);
+}
+
+isc_boolean_t
+mapContains(const struct element *m, const char *k)
+{
+ struct element *elem;
+
+ assert(m != NULL);
+ assert(m->type == ELEMENT_MAP);
+ assert(k != NULL);
+
+ TAILQ_FOREACH(elem, &m->value.map_value) {
+ assert(elem->key != NULL);
+ if (strcmp(elem->key, k) == 0)
+ break;
+ }
+
+ return ISC_TF(elem != NULL);
+}
+
+size_t
+mapSize(const struct element *m)
+{
+ struct element *elem;
+ size_t cnt;
+
+ assert(m != NULL);
+ assert(m->type == ELEMENT_MAP);
+
+ cnt = 0;
+ TAILQ_FOREACH(elem, &m->value.map_value) {
+ assert(elem->key != NULL);
+ cnt++;
+ }
+
+ return cnt;
+}
+
+void
+merge(struct element *m, struct element *o)
+{
+ struct element *elem;
+ struct element *ne;
+
+ assert(m != NULL);
+ assert(m->type == ELEMENT_MAP);
+ assert(o != NULL);
+ assert(o->type == ELEMENT_MAP);
+
+ TAILQ_FOREACH_SAFE(elem, &o->value.map_value, ne) {
+ assert(elem->key != NULL);
+ TAILQ_REMOVE(&o->value.map_value, elem);
+ if (!mapContains(m, elem->key)) {
+ TAILQ_INSERT_TAIL(&m->value.map_value, elem);
+ }
+ }
+}
+
+const char *
+type2name(int t)
+{
+ switch (t) {
+ case ELEMENT_NONE:
+ return "not initialized?";
+ case ELEMENT_INTEGER:
+ return "integer";
+ case ELEMENT_REAL:
+ return "real";
+ case ELEMENT_BOOLEAN:
+ return "boolean";
+ case ELEMENT_NULL:
+ return "(unused) null";
+ case ELEMENT_STRING:
+ return "string";
+ case ELEMENT_LIST:
+ return "list";
+ case ELEMENT_MAP:
+ return "map";
+ default:
+#if 0
+ assert(0);
+#endif
+ return "unknown?";
+ }
+}
+
+int
+name2type(const char *n)
+{
+ assert(n != NULL);
+ if (strcmp(n, "integer") == 0)
+ return ELEMENT_INTEGER;
+ if (strcmp(n, "real") == 0)
+ return ELEMENT_REAL;
+ if (strcmp(n, "boolean") == 0)
+ return ELEMENT_BOOLEAN;
+ if (strcmp(n, "null") == 0)
+ return ELEMENT_NULL;
+ if (strcmp(n, "string") == 0)
+ return ELEMENT_STRING;
+ if (strcmp(n, "list") == 0)
+ return ELEMENT_LIST;
+ if (strcmp(n, "map") == 0)
+ return ELEMENT_MAP;
+#if 0
+ assert(0);
+#endif
+ return ELEMENT_NONE;
+}
+
+void
+print(FILE *fp, const struct element *e, isc_boolean_t skip, unsigned indent)
+{
+ assert(fp != NULL);
+ assert(e != NULL);
+
+ switch (e->type) {
+ case ELEMENT_LIST:
+ printList(fp, &e->value.list_value, skip, indent);
+ return;
+ case ELEMENT_MAP:
+ printMap(fp, &e->value.map_value, skip, indent);
+ return;
+ case ELEMENT_STRING:
+ printString(fp, &e->value.string_value);
+ return;
+ case ELEMENT_INTEGER:
+ fprintf(fp, "%lld", (long long)e->value.int_value);
+ return;
+ case ELEMENT_REAL:
+ fprintf(fp, "%f", e->value.double_value);
+ return;
+ case ELEMENT_BOOLEAN:
+ if (e->value.bool_value)
+ fprintf(fp, "true");
+ else
+ fprintf(fp, "false");
+ return;
+ case ELEMENT_NULL:
+ fprintf(fp, "null");
+ return;
+ default:
+ assert(0);
+ }
+}
+
+static void
+addIndent(FILE *fp, int skip, unsigned indent)
+{
+ unsigned sp;
+
+ if (skip) {
+ fprintf(fp, "//");
+ if (indent > 2)
+ for (sp = 0; sp < indent - 2; ++sp)
+ fprintf(fp, " ");
+ } else
+ for (sp = 0; sp < indent; ++sp)
+ fprintf(fp, " ");
+}
+
+void
+printList(FILE *fp, const struct list *l, isc_boolean_t skip, unsigned indent)
+{
+ struct element *elem;
+ struct comment *comment;
+ isc_boolean_t first;
+
+ assert(fp != NULL);
+ assert(l != NULL);
+
+ if (TAILQ_EMPTY(l)) {
+ fprintf(fp, "[ ]");
+ return;
+ }
+
+ fprintf(fp, "[\n");
+ first = ISC_TRUE;
+ TAILQ_FOREACH(elem, l) {
+ isc_boolean_t skip_elem = skip;
+
+ assert(elem->key == NULL);
+ if (!skip) {
+ skip_elem = elem->skip;
+ if (skip_to_end(elem)) {
+ if (!first)
+ fprintf(fp, "\n");
+ first = ISC_TRUE;
+ }
+ }
+ if (!first)
+ fprintf(fp, ",\n");
+ first = ISC_FALSE;
+ TAILQ_FOREACH(comment, &elem->comments) {
+ addIndent(fp, skip_elem, indent + 2);
+ fprintf(fp, "%s\n", comment->line);
+ }
+ addIndent(fp, skip_elem, indent + 2);
+ print(fp, elem, skip_elem, indent + 2);
+ }
+ fprintf(fp, "\n");
+ addIndent(fp, skip, indent);
+ fprintf(fp, "]");
+}
+
+void
+printMap(FILE *fp, const struct map *m, isc_boolean_t skip, unsigned indent)
+{
+ struct element *elem;
+ struct comment *comment;
+ isc_boolean_t first;
+
+ assert(fp != NULL);
+ assert(m != NULL);
+
+ if (TAILQ_EMPTY(m)) {
+ fprintf(fp, "{ }");
+ return;
+ }
+
+ fprintf(fp, "{\n");
+ first = ISC_TRUE;
+ TAILQ_FOREACH(elem, m) {
+ isc_boolean_t skip_elem = skip;
+
+ assert(elem->key != NULL);
+ if (!skip) {
+ skip_elem = elem->skip;
+ if (skip_to_end(elem)) {
+ if (!first)
+ fprintf(fp, "\n");
+ first = ISC_TRUE;
+ }
+ }
+ if (!first)
+ fprintf(fp, ",\n");
+ first = ISC_FALSE;
+ TAILQ_FOREACH(comment, &elem->comments) {
+ addIndent(fp, skip_elem, indent + 2);
+ fprintf(fp, "%s\n", comment->line);
+ }
+ addIndent(fp, skip_elem, indent + 2);
+ fprintf(fp, "\"%s\": ", elem->key);
+ print(fp, elem, skip_elem, indent + 2);
+ }
+ fprintf(fp, "\n");
+ addIndent(fp, skip, indent);
+ fprintf(fp, "}");
+}
+
+void
+printString(FILE *fp, const struct string *s)
+{
+ size_t i;
+
+ assert(fp != NULL);
+ assert(s != NULL);
+
+ fprintf(fp, "\"");
+ for (i = 0; i < s->length; i++) {
+ char c = *(s->content + i);
+
+ switch (c) {
+ case '"':
+ fprintf(fp, "\\\"");
+ break;
+ case '\\':
+ fprintf(fp, "\\\\");
+ break;
+ case '\b':
+ fprintf(fp, "\\b");
+ break;
+ case '\f':
+ fprintf(fp, "\\f");
+ break;
+ case '\n':
+ fprintf(fp, "\\n");
+ break;
+ case '\r':
+ fprintf(fp, "\\r");
+ break;
+ case '\t':
+ fprintf(fp, "\\t");
+ break;
+ default:
+ if ((c >= 0) && (c < 0x20)) {
+ fprintf(fp, "\\u%04x", (unsigned)c & 0xff);
+ } else {
+ fprintf(fp, "%c", c);
+ }
+ }
+ }
+ fprintf(fp, "\"");
+}
+
+isc_boolean_t
+skip_to_end(const struct element *e)
+{
+ do {
+ if (!e->skip)
+ return ISC_FALSE;
+ e = TAILQ_NEXT(e);
+ } while (e != NULL);
+ return ISC_TRUE;
+}
+
+struct element *
+copy(struct element *e)
+{
+ struct element *result;
+ struct comment *comment;
+
+ assert(e != NULL);
+
+ switch (e->type) {
+ case ELEMENT_INTEGER:
+ result = createInt(intValue(e));
+ break;
+ case ELEMENT_REAL:
+ result = createDouble(doubleValue(e));
+ break;
+ case ELEMENT_BOOLEAN:
+ result = createBool(boolValue(e));
+ break;
+ case ELEMENT_NULL:
+ result = createNull();
+ break;
+ case ELEMENT_STRING:
+ result = createString(stringValue(e));
+ break;
+ case ELEMENT_LIST:
+ result = copyList(e);
+ break;
+ case ELEMENT_MAP:
+ result = copyMap(e);
+ break;
+ default:
+ assert(0);
+ }
+ result->kind = e->kind;
+ result->skip = e->skip;
+ /* don't copy key */
+ /* copy comments */
+ TAILQ_FOREACH(comment, &e->comments) {
+ /* do not reuse comment variable! */
+ struct comment *tmp;
+
+ tmp = createComment(comment->line);
+ TAILQ_INSERT_TAIL(&result->comments, tmp);
+ }
+ return result;
+}
+
+struct element *
+copyList(struct element *l)
+{
+ struct element *result;
+ size_t i;
+
+ result = createList();
+ for (i = 0; i < listSize(l); i++)
+ listPush(result, copy(listGet(l, i)));
+ return result;
+}
+
+struct element *
+copyMap(struct element *m)
+{
+ struct element *result;
+ struct element *item;
+
+ result = createMap();
+ TAILQ_FOREACH(item, &m->value.map_value)
+ mapSet(result, copy(item), item->key);
+ return result;
+}
+
+struct handle *
+mapPop(struct element *m)
+{
+ struct element *item;
+ struct handle *h;
+
+ assert(m != NULL);
+ assert(m->type == ELEMENT_MAP);
+
+ h = (struct handle *)malloc(sizeof(struct handle));
+ assert(h != NULL);
+ memset(h, 0, sizeof(struct handle));
+ TAILQ_INIT(&h->values);
+
+ item = TAILQ_FIRST(&m->value.map_value);
+ assert(item != NULL);
+ assert(item->key != NULL);
+ h->key = strdup(item->key);
+ assert(h->key != NULL);
+ h->value = item;
+
+ TAILQ_REMOVE(&m->value.map_value, item);
+
+ return h;
+}
+
+void
+derive(struct handle *src, struct handle *dst)
+{
+ struct element *list;
+ struct element *item;
+ size_t i;
+
+ if (dst == NULL)
+ return;
+ list = dst->value;
+ assert(list != NULL);
+ assert(list->type == ELEMENT_LIST);
+ for (i = 0; i < listSize(list); i++) {
+ item = listGet(list, i);
+ assert(item != NULL);
+ assert(item->type == ELEMENT_MAP);
+ if (mapContains(item, src->key))
+ continue;
+ mapSet(item, copy(src->value), src->key);
+ }
+}
+
+struct string *
+hexaValue(struct element *s)
+{
+ struct string *h;
+
+ assert(s != NULL);
+ assert(s->type == ELEMENT_STRING);
+
+ h = stringValue(s);
+ assert(h->length >= 2);
+
+ /* string leading 0x */
+ return makeString(h->length - 2, h->content + 2);
+}
+
+struct element *
+createHexa(struct string *h)
+{
+ struct string *s;
+
+ assert(h != NULL);
+
+ s = makeString(-1, "0x");
+ concatString(s, h);
+ return createString(s);
+}
diff --git a/keama/data.h b/keama/data.h
new file mode 100644
index 00000000..b8078cc3
--- /dev/null
+++ b/keama/data.h
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 2017 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * http://www.isc.org/
+ */
+
+#ifndef DATA_H
+#define DATA_H
+
+#include <stdint.h>
+#include <stdio.h>
+
+/* From FreeBSD sys/queue.h */
+
+/*
+ * Tail queue declarations.
+ */
+#define TAILQ_HEAD(name, type) \
+struct name { \
+ struct type *tqh_first; /* first element */ \
+ struct type **tqh_last; /* addr of last next element */ \
+}
+
+#define TAILQ_ENTRY(type) \
+struct { \
+ struct type *tqe_next; /* next element */ \
+ struct type **tqe_prev; /* address of previous next element */ \
+}
+
+/*
+ * Tail queue functions.
+ */
+#define TAILQ_CONCAT(head1, head2) do { \
+ if (!TAILQ_EMPTY(head2)) { \
+ *(head1)->tqh_last = (head2)->tqh_first; \
+ (head2)->tqh_first->next.tqe_prev = (head1)->tqh_last; \
+ (head1)->tqh_last = (head2)->tqh_last; \
+ TAILQ_INIT((head2)); \
+ } \
+} while (0)
+
+#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
+
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+
+#define TAILQ_FOREACH(var, head) \
+ for ((var) = TAILQ_FIRST((head)); \
+ (var); \
+ (var) = TAILQ_NEXT((var)))
+
+#define TAILQ_FOREACH_SAFE(var, head, tvar) \
+ for ((var) = TAILQ_FIRST((head)); \
+ (var) && ((tvar) = TAILQ_NEXT((var)), 1); \
+ (var) = (tvar))
+
+#define TAILQ_INIT(head) do { \
+ TAILQ_FIRST((head)) = NULL; \
+ (head)->tqh_last = &TAILQ_FIRST((head)); \
+} while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm) do { \
+ if ((TAILQ_NEXT((elm)) = TAILQ_NEXT((listelm))) != NULL) \
+ TAILQ_NEXT((elm))->next.tqe_prev = \
+ &TAILQ_NEXT((elm)); \
+ else { \
+ (head)->tqh_last = &TAILQ_NEXT((elm)); \
+ } \
+ TAILQ_NEXT((listelm)) = (elm); \
+ (elm)->next.tqe_prev = &TAILQ_NEXT((listelm)); \
+} while (0)
+
+#define TAILQ_INSERT_BEFORE(listelm, elm) do { \
+ (elm)->next.tqe_prev = (listelm)->next.tqe_prev; \
+ TAILQ_NEXT((elm)) = (listelm); \
+ *(listelm)->next.tqe_prev = (elm); \
+ (listelm)->next.tqe_prev = &TAILQ_NEXT((elm)); \
+} while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm) do { \
+ if ((TAILQ_NEXT((elm)) = TAILQ_FIRST((head))) != NULL) \
+ TAILQ_FIRST((head))->next.tqe_prev = \
+ &TAILQ_NEXT((elm)); \
+ else \
+ (head)->tqh_last = &TAILQ_NEXT((elm)); \
+ TAILQ_FIRST((head)) = (elm); \
+ (elm)->next.tqe_prev = &TAILQ_FIRST((head)); \
+} while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm) do { \
+ TAILQ_NEXT((elm)) = NULL; \
+ (elm)->next.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &TAILQ_NEXT((elm)); \
+} while (0)
+
+#define TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+
+#define TAILQ_NEXT(elm) ((elm)->next.tqe_next)
+
+#define TAILQ_PREV(elm, headname) \
+ (*(((struct headname *)((elm)->next.tqe_prev))->tqh_last))
+
+#define TAILQ_REMOVE(head, elm) do { \
+ if ((TAILQ_NEXT((elm))) != NULL) \
+ TAILQ_NEXT((elm))->next.tqe_prev = \
+ (elm)->next.tqe_prev; \
+ else \
+ (head)->tqh_last = (elm)->next.tqe_prev; \
+ *(elm)->next.tqe_prev = TAILQ_NEXT((elm)); \
+ (elm)->next.tqe_next = (void *)-1; \
+ (elm)->next.tqe_prev = (void *)-1; \
+} while (0)
+
+#define TAILQ_SWAP(head1, head2, type) do { \
+ struct type *swap_first = (head1)->tqh_first; \
+ struct type **swap_last = (head1)->tqh_last; \
+ (head1)->tqh_first = (head2)->tqh_first; \
+ (head1)->tqh_last = (head2)->tqh_last; \
+ (head2)->tqh_first = swap_first; \
+ (head2)->tqh_last = swap_last; \
+ if ((swap_first = (head1)->tqh_first) != NULL) \
+ swap_first->next.tqe_prev = &(head1)->tqh_first; \
+ else \
+ (head1)->tqh_last = &(head1)->tqh_first; \
+ if ((swap_first = (head2)->tqh_first) != NULL) \
+ swap_first->next.tqe_prev = &(head2)->tqh_first; \
+ else \
+ (head2)->tqh_last = &(head2)->tqh_first; \
+} while (0)
+
+/* From bind9 lib/isc/include/isc/boolean.h */
+
+typedef enum { isc_boolean_false = 0, isc_boolean_true = 1 } isc_boolean_t;
+
+#define ISC_FALSE isc_boolean_false
+#define ISC_TRUE isc_boolean_true
+#define ISC_TF(x) ((x) ? ISC_TRUE : ISC_FALSE)
+
+/* From Kea src/lib/cc/data.h */
+
+struct element;
+
+/* Element types */
+#define ELEMENT_NONE 0
+#define ELEMENT_INTEGER 1
+#define ELEMENT_REAL 2
+#define ELEMENT_BOOLEAN 3
+#define ELEMENT_NULL 4
+#define ELEMENT_STRING 5
+#define ELEMENT_LIST 6
+#define ELEMENT_MAP 7
+
+/* Element string */
+struct string {
+ size_t length; /* string length */
+ char *content; /* string data */
+};
+
+struct string *allocString(void);
+/* In makeString() l == -1 means use strlen(s) */
+struct string *makeString(int l, const char *s);
+/* format ZlLsSbBfXI6 + h */
+struct string *makeStringExt(int l, const char *s, char fmt);
+/* format 6lLIsSbBj */
+struct string *makeStringArray(int l, const char *s, char fmt);
+void appendString(struct string *s, const char *a);
+void concatString(struct string *s, const struct string *a);
+isc_boolean_t eqString(const struct string *s, const struct string *o);
+/* quoting */
+struct string *quote(struct string *);
+
+/* Comments */
+struct comment {
+ char *line; /* comment line */
+ TAILQ_ENTRY(comment) next; /* next line */
+};
+TAILQ_HEAD(comments, comment);
+
+struct comment *createComment(const char *line);
+
+/* Element list */
+TAILQ_HEAD(list, element);
+
+/* Element map */
+TAILQ_HEAD(map, element);
+
+/* Element value */
+union value {
+ int64_t int_value; /* integer */
+ double double_value; /* real */
+ isc_boolean_t bool_value; /* boolean */
+ /**/ /* null */
+ struct string string_value; /* string */
+ struct list list_value; /* list */
+ struct map map_value; /* map */
+};
+
+/* Element */
+struct element {
+ int type; /* element type (ELEMENT_XXX) */
+ int kind; /* element kind (e.g. ROOT_GROUP) */
+ isc_boolean_t skip; /* skip as not converted */
+ char *key; /* element key (for map) */
+ union value value; /* value */
+ struct comments comments; /* associated comments */
+ TAILQ_ENTRY(element) next; /* next item in list or map chain */
+};
+
+/* Value getters */
+int64_t intValue(const struct element *e);
+double doubleValue(const struct element *e);
+isc_boolean_t boolValue(const struct element *e);
+struct string *stringValue(struct element *e);
+struct list *listValue(struct element *e);
+struct map *mapValue(struct element *e);
+
+/* Creators */
+struct element *create(void);
+struct element *createInt(int64_t i);
+struct element *createDouble(double d);
+struct element *createBool(isc_boolean_t b);
+struct element *createNull(void);
+struct element *createString(const struct string *s);
+struct element *createList(void);
+struct element *createMap(void);
+
+/* Reset */
+void resetInt(struct element *e, int64_t i);
+void resetDouble(struct element *e, double d);
+void resetBool(struct element *e, isc_boolean_t b);
+void resetNull(struct element *e);
+void resetString(struct element *e, const struct string *s);
+void resetList(struct element *e);
+void resetMap(struct element *e);
+void resetBy(struct element *e, struct element *o);
+
+/* List functions */
+struct element *listGet(struct element *l, int i);
+void listSet(struct element *l, struct element *e, int i);
+void listPush(struct element *l, struct element *e);
+void listRemove(struct element *l, int i);
+size_t listSize(const struct element *l);
+void concat(struct element *l, struct element *o);
+
+/* Map functions */
+struct element *mapGet(struct element *m, const char *k);
+void mapSet(struct element *m, struct element *e, const char *k);
+void mapRemove(struct element *m, const char *k);
+isc_boolean_t mapContains(const struct element *m, const char *k);
+size_t mapSize(const struct element *m);
+void merge(struct element *m, struct element *o);
+
+/* Tools */
+const char *type2name(int t);
+int name2type(const char *n);
+void print(FILE *fp, const struct element *e,
+ isc_boolean_t skip, unsigned indent);
+void printList(FILE *fp, const struct list *l,
+ isc_boolean_t skip, unsigned indent);
+void printMap(FILE *fp, const struct map *m,
+ isc_boolean_t skip, unsigned indent);
+void printString(FILE *fp, const struct string *s);
+isc_boolean_t skip_to_end(const struct element *e);
+
+struct element *copy(struct element *e);
+struct element *copyList(struct element *l);
+struct element *copyMap(struct element *m);
+
+/* Handles */
+TAILQ_HEAD(handles, handle);
+
+struct handle {
+ unsigned order; /* order */
+ char *key; /* key */
+ struct element *value; /* value */
+ struct handles values; /* children */
+ TAILQ_ENTRY(handle) next; /* siblings */
+};
+
+struct handle* mapPop(struct element *);
+void derive(struct handle *, struct handle *);
+
+/* Hexadecimal literals */
+struct string *hexaValue(struct element *);
+struct element *createHexa(struct string *);
+
+#endif /* DATA_H */
diff --git a/keama/dhctoken.h b/keama/dhctoken.h
new file mode 100644
index 00000000..ac24463a
--- /dev/null
+++ b/keama/dhctoken.h
@@ -0,0 +1,389 @@
+/* dhctoken.h
+
+ Tokens for config file lexer and parser. */
+
+/*
+ * Copyright (c) 2004-2016 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-2003 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+/*
+ * The following tokens have been deprecated and aren't in use anymore.
+ * They have been left in place to avoid disturbing the code.
+ * DNS_UPDATE, DNS_DELETE, NS_UPDATE, UPDATED_DNS_RR
+ */
+/*
+ * For the Kea Migration Assistant only '[' and ']' where added for
+ * the JSON test parser (no required cast to int in switches on tokens)
+ */
+enum dhcp_token {
+ SEMI = ';',
+ DOT = '.',
+ COLON = ':',
+ COMMA = ',',
+ SLASH = '/',
+ LBRACE = '{',
+ RBRACE = '}',
+ LBRACKET = '[',
+ RBRACKET = ']',
+ LPAREN = '(',
+ RPAREN = ')',
+ EQUAL = '=',
+ TILDE = '~',
+ BANG = '!',
+ PERCENT = '%',
+ PLUS = '+',
+ MINUS = '-',
+ ASTERISK = '*',
+ AMPERSAND = '&',
+ PIPE = '|',
+ CARET = '^',
+ ENDOFLINE = '\n',
+ QUESTIONMARK = '?',
+
+ HOST = 256,
+ FIRST_TOKEN = HOST,
+ HARDWARE = 257,
+ FILENAME = 258,
+ FIXED_ADDR = 259,
+ OPTION = 260,
+ ETHERNET = 261,
+ STRING = 262,
+ NUMBER = 263,
+ NUMBER_OR_NAME = 264,
+ NAME = 265,
+ TIMESTAMP = 266,
+ STARTS = 267,
+ ENDS = 268,
+ UID = 269,
+ CLASS = 270,
+ LEASE = 271,
+ RANGE = 272,
+ PACKET = 273,
+ CIADDR = 274,
+ YIADDR = 275,
+ SIADDR = 276,
+ GIADDR = 277,
+ SUBNET = 278,
+ NETMASK = 279,
+ DEFAULT_LEASE_TIME = 280,
+ MAX_LEASE_TIME = 281,
+ VENDOR_CLASS = 282,
+ USER_CLASS = 283,
+ SHARED_NETWORK = 284,
+ SERVER_NAME = 285,
+ DYNAMIC_BOOTP = 286,
+ SERVER_IDENTIFIER = 287,
+ DYNAMIC_BOOTP_LEASE_CUTOFF = 288,
+ DYNAMIC_BOOTP_LEASE_LENGTH = 289,
+ BOOT_UNKNOWN_CLIENTS = 290,
+ NEXT_SERVER = 291,
+ TOKEN_RING = 292,
+ GROUP = 293,
+ ONE_LEASE_PER_CLIENT = 294,
+ GET_LEASE_HOSTNAMES = 295,
+ USE_HOST_DECL_NAMES = 296,
+ SEND = 297,
+ CLIENT_IDENTIFIER = 298,
+ REQUEST = 299,
+ REQUIRE = 300,
+ TIMEOUT = 301,
+ RETRY = 302,
+ SELECT_TIMEOUT = 303,
+ SCRIPT = 304,
+ INTERFACE = 305,
+ RENEW = 306,
+ REBIND = 307,
+ EXPIRE = 308,
+ UNKNOWN_CLIENTS = 309,
+ ALLOW = 310,
+ DENY = 312,
+ BOOTING = 313,
+ DEFAULT = 314,
+ MEDIA = 315,
+ MEDIUM = 316,
+ ALIAS = 317,
+ REBOOT = 318,
+ TOKEN_ABANDONED = 319,
+ BACKOFF_CUTOFF = 320,
+ INITIAL_INTERVAL = 321,
+ NAMESERVER = 322,
+ DOMAIN = 323,
+ SEARCH = 324,
+ SUPERSEDE = 325,
+ APPEND = 326,
+ PREPEND = 327,
+ HOSTNAME = 328,
+ CLIENT_HOSTNAME = 329,
+ REJECT = 330,
+ USE_LEASE_ADDR_FOR_DEFAULT_ROUTE = 331,
+ MIN_LEASE_TIME = 332,
+ MIN_SECS = 333,
+ AND = 334,
+ OR = 335,
+ SUBSTRING = 337,
+ SUFFIX = 338,
+ CHECK = 339,
+ EXTRACT_INT = 340,
+ IF = 341,
+ TOKEN_ADD = 342,
+ BREAK = 343,
+ ELSE = 344,
+ ELSIF = 345,
+ SUBCLASS = 346,
+ MATCH = 347,
+ SPAWN = 348,
+ WITH = 349,
+ EXISTS = 350,
+ POOL = 351,
+ UNKNOWN = 352,
+ CLIENTS = 353,
+ KNOWN = 354,
+ AUTHENTICATED = 355,
+ UNAUTHENTICATED = 356,
+ ALL = 357,
+ DYNAMIC = 358,
+ MEMBERS = 359,
+ OF = 360,
+ PSEUDO = 361,
+ LIMIT = 362,
+ BILLING = 363,
+ PEER = 364,
+ FAILOVER = 365,
+ MY = 366,
+ PARTNER = 367,
+ PRIMARY = 368,
+ SECONDARY = 369,
+ IDENTIFIER = 370,
+ PORT = 371,
+ MAX_TRANSMIT_IDLE = 372,
+ MAX_RESPONSE_DELAY = 373,
+ PARTNER_DOWN = 374,
+ NORMAL = 375,
+ COMMUNICATIONS_INTERRUPTED = 376,
+ POTENTIAL_CONFLICT = 377,
+ RECOVER = 378,
+ TOKEN_FDDI = 379,
+ AUTHORITATIVE = 380,
+ TOKEN_NOT = 381,
+ AUTHENTICATION = 383,
+ IGNORE = 384,
+ ACCEPT = 385,
+ PREFER = 386,
+ DONT = 387,
+ CODE = 388,
+ ARRAY = 389,
+ BOOLEAN = 390,
+ INTEGER = 391,
+ SIGNED = 392,
+ UNSIGNED = 393,
+ IP_ADDRESS = 394,
+ TEXT = 395,
+ STRING_TOKEN = 396,
+ SPACE = 397,
+ CONCAT = 398,
+ ENCODE_INT = 399,
+ REVERSE = 402,
+ LEASED_ADDRESS = 403,
+ BINARY_TO_ASCII = 404,
+ PICK = 405,
+ CONFIG_OPTION = 406,
+ HOST_DECL_NAME = 407,
+ ON = 408,
+ EXPIRY = 409,
+ RELEASE = 410,
+ COMMIT = 411,
+ DNS_UPDATE = 412,
+ LEASE_TIME = 413,
+ STATIC = 414,
+ NEVER = 415,
+ INFINITE = 416,
+ TOKEN_DELETED = 417,
+ UPDATED_DNS_RR = 418,
+ DNS_DELETE = 419,
+ DUPLICATES = 420,
+ DECLINES = 421,
+ TSTP = 422,
+ TSFP = 423,
+ OWNER = 424,
+ IS = 425,
+ HBA = 426,
+ MAX_UNACKED_UPDATES = 427,
+ MCLT = 428,
+ SPLIT = 429,
+ AT = 430,
+ TOKEN_NO = 431,
+ TOKEN_DELETE = 432,
+ NS_UPDATE = 433,
+ UPDATE = 434,
+ SWITCH = 435,
+ CASE = 436,
+ NS_FORMERR = 437,
+ NS_NOERROR = 438,
+ NS_NOTAUTH = 439,
+ NS_NOTIMP = 440,
+ NS_NOTZONE = 441,
+ NS_NXDOMAIN = 442,
+ NS_NXRRSET = 443,
+ NS_REFUSED = 444,
+ NS_SERVFAIL = 445,
+ NS_YXDOMAIN = 446,
+ NS_YXRRSET = 447,
+ TOKEN_NULL = 448,
+ TOKEN_SET = 449,
+ DEFINED = 450,
+ UNSET = 451,
+ EVAL = 452,
+ LET = 453,
+ FUNCTION = 454,
+ DEFINE = 455,
+ ZONE = 456,
+ KEY = 457,
+ SECRET = 458,
+ ALGORITHM = 459,
+ LOAD = 460,
+ BALANCE = 461,
+ TOKEN_MAX = 462,
+ SECONDS = 463,
+ ADDRESS = 464,
+ RESOLUTION_INTERRUPTED = 465,
+ STATE = 466,
+ UNKNOWN_STATE = 567,
+ CLTT = 568,
+ INCLUDE = 569,
+ BINDING = 570,
+ TOKEN_FREE = 571,
+ TOKEN_ACTIVE = 572,
+ TOKEN_EXPIRED = 573,
+ TOKEN_RELEASED = 574,
+ TOKEN_RESET = 575,
+ TOKEN_BACKUP = 576,
+ TOKEN_RESERVED = 577,
+ TOKEN_BOOTP = 578,
+ TOKEN_NEXT = 579,
+ OMAPI = 580,
+ LOG = 581,
+ FATAL = 582,
+ ERROR = 583,
+ TOKEN_DEBUG = 584,
+ INFO = 585,
+ RETURN = 586,
+ PAUSED = 587,
+ RECOVER_DONE = 588,
+ SHUTDOWN = 589,
+ STARTUP = 590,
+ ENCAPSULATE = 591,
+ VENDOR = 592,
+ CLIENT_STATE = 593,
+ INIT_REBOOT = 594,
+ TOKEN_INIT = 595,
+ SELECT = 596,
+ BOUND = 597,
+ RENEWING = 598,
+ REBINDING = 599,
+ RECONTACT_INTERVAL = 600,
+ CLIENT_UPDATES = 601,
+ TOKEN_NEW = 601,
+ TRANSMISSION = 602,
+ TOKEN_CLOSE = 603,
+ TOKEN_CREATE = 604,
+ TOKEN_OPEN = 605,
+ TOKEN_HELP = 606,
+ END_OF_FILE = 607,
+ RECOVER_WAIT = 608,
+ TOKEN_SERVER = 609,
+ CONNECT = 610,
+ REMOVE = 611,
+ REFRESH = 612,
+ DOMAIN_NAME = 613,
+ DO_FORWARD_UPDATE = 614,
+ KNOWN_CLIENTS = 615,
+ ATSFP = 616,
+ LCASE = 617,
+ UCASE = 618,
+ WIDTH = 619,
+ LENGTH = 620,
+ HASH = 621,
+ SIZE = 622,
+ EPOCH = 623,
+ DB_TIME_FORMAT = 624,
+ LOCAL = 625,
+ MAX_LEASE_MISBALANCE = 626,
+ MAX_LEASE_OWNERSHIP = 627,
+ MAX_BALANCE = 628,
+ MIN_BALANCE = 629,
+ DOMAIN_LIST = 630,
+ LEASEQUERY = 631,
+ EXECUTE = 632,
+ IP6_ADDRESS = 633,
+ FIXED_ADDR6 = 634,
+ COMPRESSED = 635,
+ SUBNET6 = 636,
+ HOST_IDENTIFIER = 637,
+ IA_NA = 638,
+ IA_TA = 639,
+ IA_PD = 640,
+ IAADDR = 641,
+ IAPREFIX = 642,
+ LEASE6 = 643,
+ PREFERRED_LIFE = 644,
+ MAX_LIFE = 645,
+ DEFAULT_DUID = 646,
+ SERVER_DUID = 647,
+ LLT = 648,
+ EN = 649,
+ LL = 650,
+ RANGE6 = 651,
+ WHITESPACE = 652,
+ TOKEN_ALSO = 653,
+ AFTER = 654,
+ ZEROLEN = 655,
+ TEMPORARY = 656,
+ PREFIX6 = 657,
+ FIXED_PREFIX6 = 658,
+ ANYCAST_MAC = 659,
+ CONFLICT_DONE = 660,
+ AUTO_PARTNER_DOWN = 661,
+ GETHOSTNAME = 662,
+ REWIND = 663,
+ INITIAL_DELAY = 664,
+ GETHOSTBYNAME = 665,
+ PRIMARY6 = 666,
+ SECONDARY6 = 667,
+ TOKEN_INFINIBAND = 668,
+ POOL6 = 669,
+ V6RELAY = 670,
+ V6RELOPT = 671,
+ PARSE_VENDOR_OPT = 672,
+ AUTHORING_BYTE_ORDER = 673,
+ TOKEN_LITTLE_ENDIAN = 674,
+ TOKEN_BIG_ENDIAN = 675,
+ LEASE_ID_FORMAT = 676,
+ TOKEN_HEX = 677,
+ TOKEN_OCTAL = 678,
+ KEY_ALGORITHM = 679
+};
+
+#define is_identifier(x) ((x) >= FIRST_TOKEN && \
+ (x) != STRING && \
+ (x) != NUMBER && \
+ (x) != END_OF_FILE)
diff --git a/keama/doc.txt b/keama/doc.txt
new file mode 100644
index 00000000..a1664a22
--- /dev/null
+++ b/keama/doc.txt
@@ -0,0 +1,516 @@
+Part 1: Kea Migration Assistant support
+=======================================
+
+Files:
+------
+ - data.h (tailq list and element type declarations)
+ - data.c (element type code)
+ - keama.h (DHCP declarations)
+ - keama.c (main() code)
+ - json.c (JSON parser)
+ - option.c (option tables and code)
+ - keama.8 (man page)
+
+The code heavily uses tailq lists, i.e. doubled linked lists with
+a pointer to the last (tail) element.
+
+The element structure mimics the Kea Element class with a few differences:
+ - no smart pointers
+ - extra fields to handle declaration kind, skip and comments
+ - maps are implemented as lists with an extra key field so the order
+ of insertion is kept and duplicates are possible
+ - strings are length + content (vs C strings)
+
+There is no attempt to avoid memory leaks.
+
+The skip flag is printed as '//' at the beginning of lines. It is set
+when something cannot be converted and the issue counter (returned
+by the keama command) incremented.
+
+Part 2: ISC DHCP lexer organization
+===================================
+
+Files:
+-----
+ - dhctoken.h (from includes, enum dhcp_token definition)
+ - conflex.c (from common, lexical analyzer code)
+
+Tokens (dhcp_token enum): characters are set to their ASCII value,
+ others are >= 256 without real organization (e.g. END_OF_FILE is 607).
+
+The state is in a parse structure named "cfile". There is one per file
+and a few routine save it in order to do a backtrack on a larger
+set than the usual lookahead.
+The largest function is intern() which recognizes keywords with
+a switch on the first character and a tree of if strcasecmp's.
+
+Standard routines:
+-----------------
+enum dhcp_token
+next_token(const char **rval, unsigned *rlen, struct parse *cfile);
+
+and
+
+enum dhcp_token
+peek_token(const char **rval, unsigned *rlen, struct parse *cfile);
+
+rval: if not null the content of the token is put in it
+rlen: if not null the length of the token is put in it
+cfile: lexer context
+return: the integer value of the token
+
+Changes:
+-------
+
+Added LBRACKET '[' and RBRACKET ']' tokens for JSON parser
+(switch on dhcp_token type).
+
+Added comments to collect ISC DHCP # comments, element stack to follow
+declaration hierarchy, and issue counter to struct parse.
+
+Moved the parse_warn (renamed into parse_error and made fatal) routine
+from conflex.c to keama.c
+
+Part 3: ISC DHCP parser organization
+====================================
+
+Files:
+-----
+ - confparse.c (from server)
+ for the server in parse_statement())
+ - parse.c (from common)
+
+4 classes: parameters, declarations, executable statements and expressions.
+
+the original code parses config and lease files, I kept only the first
+at the exception of parse_binding_value().
+
+entry point
+ |
+ V
+conf_file_parse
+ |
+ V
+conf_file_subparse <- read_conf_file (for include)
+ until END_OF_FILE call
+ |
+ V
+parse_statement
+ parse parameters and declarations
+ switch on token and call parse_xxx_declaration routines
+ on default or DHCPv6 token in DHCPv4 mode call parse_executable_statement
+ and put the result under the "statement" key
+ |
+ V
+parse_executable_statement
+
+According to comments the grammar is:
+
+ conf-file :== parameters declarations END_OF_FILE
+ parameters :== <nil> | parameter | parameters parameter
+ declarations :== <nil> | declaration | declarations declaration
+
+ statement :== parameter | declaration
+
+ parameter :== DEFAULT_LEASE_TIME lease_time
+ | MAX_LEASE_TIME lease_time
+ | DYNAMIC_BOOTP_LEASE_CUTOFF date
+ | DYNAMIC_BOOTP_LEASE_LENGTH lease_time
+ | BOOT_UNKNOWN_CLIENTS boolean
+ | ONE_LEASE_PER_CLIENT boolean
+ | GET_LEASE_HOSTNAMES boolean
+ | USE_HOST_DECL_NAME boolean
+ | NEXT_SERVER ip-addr-or-hostname SEMI
+ | option_parameter
+ | SERVER-IDENTIFIER ip-addr-or-hostname SEMI
+ | FILENAME string-parameter
+ | SERVER_NAME string-parameter
+ | hardware-parameter
+ | fixed-address-parameter
+ | ALLOW allow-deny-keyword
+ | DENY allow-deny-keyword
+ | USE_LEASE_ADDR_FOR_DEFAULT_ROUTE boolean
+ | AUTHORITATIVE
+ | NOT AUTHORITATIVE
+
+ declaration :== host-declaration
+ | group-declaration
+ | shared-network-declaration
+ | subnet-declaration
+ | VENDOR_CLASS class-declaration
+ | USER_CLASS class-declaration
+ | RANGE address-range-declaration
+
+Typically declarations use { } and are associated with a group
+(changed to a type) in ROOT_GROUP (global), HOST_DECL, SHARED_NET_DECL,
+SUBNET_DECL, CLASS_DECL, GROUP_DECL and POOL_DECL.
+
+ROOT: parent = TOPLEVEL, children = everythig but not POOL
+HOST: parent = ROOT, GROUP, warn on SHARED or SUBNET, children = none
+SHARED_NET: parent = ROOT, GROUP, children = HOST (warn), SUBNET, POOL4
+SUBNET: parent = ROOT, GROUP, SHARED, children = HOST (warn), POOL
+CLASS: parent = ROOT, GROUP, children = none
+GROUP: parent = ROOT, SHARED, children = anything but not POOL
+POOL: parent = SHARED4, SUBNET, warn on others, children = none
+
+isc_boolean_t
+parse_statement(struct parse *cfile, int type, isc_boolean_t declaration);
+
+cfile: parser context
+type: declaration type
+declaration and return: declaration or parameter
+
+On the common side:
+
+ executable-statements :== executable-statement executable-statements |
+ executable-statement
+
+ executable-statement :==
+ IF if-statement |
+ ADD class-name SEMI |
+ BREAK SEMI |
+ OPTION option-parameter SEMI |
+ SUPERSEDE option-parameter SEMI |
+ PREPEND option-parameter SEMI |
+ APPEND option-parameter SEMI
+
+isc_boolean_t
+parse_executable_statement(struct element *result,
+ struct parse *cfile, isc_boolean_t *lose,
+ enum expression_context case_context,
+ isc_boolean_t direct);
+
+result: map element where to put the statement
+cfile: parser context
+lose: set to ISC_TRUE on failure
+case_context: expression context
+direct: called directly by parse_statement so can execute config statements
+return: success
+
+parse_executable_statement
+ switch on keywords (far more than in the comments)
+ on default with an identifier try a config option, on number or name
+ call parse_expression for a function call
+ |
+ V
+parse_expression
+
+expressions are divided into boolean, data (string) and numeric expressions
+
+ boolean_expression :== CHECK STRING |
+ NOT boolean-expression |
+ data-expression EQUAL data-expression |
+ data-expression BANG EQUAL data-expression |
+ data-expression REGEX_MATCH data-expression |
+ boolean-expression AND boolean-expression |
+ boolean-expression OR boolean-expression
+ EXISTS OPTION-NAME
+
+ data_expression :== SUBSTRING LPAREN data-expression COMMA
+ numeric-expression COMMA
+ numeric-expression RPAREN |
+ CONCAT LPAREN data-expression COMMA
+ data-expression RPAREN
+ SUFFIX LPAREN data_expression COMMA
+ numeric-expression RPAREN |
+ LCASE LPAREN data_expression RPAREN |
+ UCASE LPAREN data_expression RPAREN |
+ OPTION option_name |
+ HARDWARE |
+ PACKET LPAREN numeric-expression COMMA
+ numeric-expression RPAREN |
+ V6RELAY LPAREN numeric-expression COMMA
+ data-expression RPAREN |
+ STRING |
+ colon_separated_hex_list
+
+ numeric-expression :== EXTRACT_INT LPAREN data-expression
+ COMMA number RPAREN |
+ NUMBER
+
+parse_boolean_expression, parse_data_expression and parse_numeric_expression
+calls parse_expression and check its result
+
+parse_expression itself is divided into parse_non_binary and internal
+handling of binary operators
+
+isc_boolean_t
+parse_non_binary(struct element *expr, struct parse *cfile,
+ isc_boolean_t *lose, enum expression_context context)
+
+isc_boolean_t
+parse_expression(struct element *expr, struct parse *cfile,
+ isc_boolean_t *lose, enum expression_context context,
+ struct element *lhs, enum expr_op binop)
+
+expr: map element where to put the result
+cfile: parser context
+lose: set to ISC_TRUE on failure
+context: expression context
+lhs: NULL or left hand side
+binop: expr_none or binary operation
+return: success
+
+parse_non_binary
+ switch on unary and nullary operator keywords
+ on default try a variable reference or a function call
+
+parse_expression
+ call parse_non_binary to get the right hand side
+ switch on binary operator keywords to get the next operation
+ with one side if expr_none return else get the second hand
+ handle operator precedence, can call itself
+ return a map entry with the operator name as the key, and
+ left and right expression branches
+
+Part 4: Expression processing
+=============================
+
+Files:
+------
+ - print.c (new)
+ - eval.c (new)
+ - reduce.c (new)
+
+Print:
+------
+
+const char *
+print_expression(struct element *expr, isc_boolean_t *lose);
+const char *
+print_boolean_expression(struct element *expr, isc_boolean_t *lose);
+const char *
+print_data_expression(struct element *expr, isc_boolean_t *lose);
+const char *
+print_numeric_expression(struct element *expr, isc_boolean_t *lose);
+
+expr: expression to print
+lose: failure (??? in output) flag
+return: the text representing the expression
+
+Eval:
+-----
+
+struct element *
+eval_expression(struct element *expr, isc_boolean_t *modifiedp);
+struct element *
+eval_boolean_expression(struct element *expr, isc_boolean_t *modifiedp);
+struct element *
+eval_data_expression(struct element *expr, isc_boolean_t *modifiedp);
+struct element *
+eval_numeric_expression(struct element *expr, isc_boolean_t *modifiedp);
+
+expr: expression to evaluate
+modifiedp: a different element was returned (still false for updates
+ inside a map)
+return: the evaluated element (can have been updated for a map or a list,
+ or can be a fully different element)
+
+Evaluation is at parsing time so it is mainly a constant propagation.
+(no beta reduction for instance)
+
+Reduce:
+-------
+
+struct element *
+reduce_boolean_expression(struct element *expr);
+struct element *
+reduce_data_expression(struct element *expr);
+struct element *
+reduce_numeric_expression(struct element *expr);
+
+expr: expression to reduce
+return: NULL or the reduced expression as a Kea eval string
+
+reducing works for a limited (but interesting) set of expressions which
+can be converted to kea evaluatebool and for literals.
+
+Part 5: Specific issues
+=======================
+
+Reservations:
+-------------
+ ISC DHCP host declarations are global, Kea reservations were per subnet
+ only until 1.5.
+ It is possible to use the fixed address but:
+ - it is possible to finish with orphan reservations, i.e.
+ reservations with an address which match no subnets
+ - a reservation can have no fixed address. In this case the MA puts
+ the reservation in the last declared subnet.
+ - a reservation can have more than one fixed address and these
+ addresses can belong to different subnets. Current code pushes
+ IPv4 extra addresses in a commented extra-ip-addresses but
+ it is legal feature for IPv6.
+ - it is not easy to use prefix6
+ The use of groups in host declarations is unclear.
+ ISC DHCP UID is mapped to client-id, host-identifier to flex-id
+ Host reservation identifiers are generated on first use.
+
+Groups:
+-------
+TODO: search missing parameters from the Kea syntax.
+ (will be done in the third pass)
+
+Shared-Networks:
+----------------
+ Waiting for the feature to be supported by Kea.
+ Currently at the end of a shared network declaration:
+ - if there is no subnets it is a fatal error
+ - if there is one subnet the shared-network is squeezed
+ - if there are more than one subnet the shared-network is commented
+TODO (useful only with Kea support for shared networks): combine permit /
+deny classes (e.g. create negation) and pop filters to subnets when
+there is one pool.
+
+Vendor-Classes and User-Classes:
+--------------------------------
+ ISC DHCP code is inconsistent: in particular before setting the
+ super-class "tname" to "implicit-vendor-class" / "implicit-user-class"
+ it allocates a buffer for data but does not copy the lexical value
+ "val" into it... So I removed support.
+
+Classes:
+--------
+ Only pure client-classes are supported by kea.
+ Dynamic/deleted stuff is not supported but does it make sense?
+ To spawn classes is not supported.
+ Match class selector is converted to Kea eval test when the corresponding
+ expression can be reduced. Fortunately it seems to be the common case!
+ Lease limit is not supported.
+
+Subclasses:
+-----------
+ Understood how it works:
+ - (super) class defined with a MATCH <data-expression> (vs.
+ MATCH IF <boolean-expression>)
+ - subclasses defined by <superclass-name> <data-literal> which
+ are equivalent to
+ MATCH IF <superclass-data-expression> EQUAL <data-literal>
+ So subclasses are convertible when the data expression can be reduced.
+ Cf https://kb.isc.org/article/AA-01092/202/OMAPI-support-for-classes-and-subclasses.html
+ which BTW suggests the management API could manage classes...
+
+Hardware Addresses:
+-------------------
+ Kea supports only Ethernet.
+
+Pools:
+------
+ All permissions are not supported by Kea at the exception of class members
+ but in a very different way so not convertible.
+ Mixed DHCPv6 address and prefix pools are not supported, perhaps in this
+ case the pool should be duplicated into pool and pd-pool instances?
+ The bootp stuff was ifdef's as bootp is obsolete.
+ Temporary (aka IA_TA) is commented ny the MA.
+ ISC DHCP supports interval ranges for prefix6. Kea has a different
+ and IMHO more powerful model.
+ Pool6 permissions are not supported.
+
+Failover:
+---------
+ Display a warning on the first use.
+
+Interfaces:
+-----------
+ Referenced interface names are pushed to an interfaces-config but it is
+ very (too!) easy to finish with a Kea config without any interface.
+
+Hostnames:
+----------
+ ISC DHCP does dynamic resolution in parse_ip_addr_or_hostname.
+ Static (at conversion time) resolution to one address is done by
+ the MA for fixed-address. Resolution is considered as painful
+ there are better (and safer) ways to do this. The -r (resolve)
+ command line parameter controls the at-conversion-time resolution.
+ Note only the first address is returned.
+TODO: check the multiple address comment is correctly taken
+ (need a known host resolving in a stable set of addresses)
+
+Options:
+--------
+ Some options are known only in ISC DHCP (almost fixed), a few only by Kea.
+ Formats are supposed to be the same, the only known exception
+ (DHCPv4 domain-search) was fixed by #5087.
+ For option spaces DHCPv4 vendor-encapsulated-options (code 43, in general
+ associated to vendor-class-identifier code 60) uses a dedicated feature
+ which had no equivalent in Kea (fixed).
+ Option definitions are convertible with a few exception:
+ - no support in Kea for an array of records (mainly by the lack
+ of a corresponding syntax). BTW there is no known use too.
+ - no support in Kea for an array at the end of a record (fixed)
+ All unsupported option declarations are set to full binary (X).
+ - X format means ASCII or hexa:
+ * standard options are in general mapped to binary
+ * new options are mapped to string with format x (vs x)
+ * when a string got hexadecimal data a warning in added in comments
+ suggesting to switch to plain binary.
+ - ISC DHCP use quotes for a domain-list but not for a domain-name,
+ this is no very coherent and makes domain-list different than
+ domain-name array.
+Each time an option data has a format which is not convertible than
+a CSV false binary data is produced.
+ We have no example in ISC DHCP, Kea or standard but it is possible
+ than an option defined as a fixed sized record followed by
+ (encapsulated) suboptions bugs (it already bugs toElement).
+ For operations on options ISC DHCP has supersede, send, append,
+ prepend, default (set if not yet present), Kea puts them in code order
+ with a few built-in exceptions.
+ To finish there is the way to enforce Kea to add an option in a response
+ is pretty different and can't be automatically translated (cf Kea #250).
+
+Duplicates:
+-----------
+ Many things in ISC DHCP can be duplicated:
+ - options can be redefined
+ - same host identifier used twice
+ - same fixed address used in tow different hosts
+ etc.
+ Kea is far more strict and IMHO it is a good thing. Now the MA does
+ no particular check and multiple definitions work only for classes
+ (because it is the way the ISC DHCP parse works).
+ If we have Docsis space options, they are standard in Kea so they
+ will conflict.
+
+Dynamic DNS:
+------------
+ Details are very different so the MA maps only basic parameters
+ at the global scope.
+
+Expressions:
+------------
+ ISC DHCP expressions are typed: boolean, numeric, and data aka string.
+ The default for a literal is to be a string so literal numbers are
+ interpreted in hexadecimal (for a strange consequence look at
+ https://kb.isc.org/article/AA-00334/56/Do-the-list-of-parameters-in-the-dhcp-parameter-request-list-need-to-be-in-hex.html ).
+ String literals are converted to string elements, hexadecimal literals
+ are converted to const-data maps.
+TODO reduce more hexa aka const-data
+ As booleans are not data there is no way to fix this:
+ /tmp/bool line 9: Expecting a data expression.
+ option ip-forwarding = foo = foo;
+ ^
+ Cf Kea #247
+ The tautology 'foo = foo' is not a data expression so is rejected by
+ both the MA and dhcpd (BTW the role of the MA is not to fix ISC DHCP
+ shortcomings so it does what it is expected to do here).
+ Note this does not work too:
+ option ip-forwarding = true;
+ because "true" is not a keyword and it is converted into a variable
+ reference... And I expect ISC DHCP makes this true a false at runtime
+ because the variable "true" is not defined by default.
+ Reduced expressions are pretty printed to allow an extra check.
+ Hardware for DHCPv4 is expansed into a concatenation of hw-type and
+ hw-address, this allows to simplify expression where only one is used.
+
+Variables:
+----------
+ ISC DHCP has a notion of variables in a scope where the scope can be
+ a lexical scope in the config or a scope in a function body
+ (ISC DHCP has even an unused "let" statement).
+ There is a variant of bindings for lease files using types and able
+ to recognize booleans and numbers. Unfortunately this is very specific...
+
+TODO:
+ - global host reservations
+ - class like if statement
+ - add more tests for classes in pools and class generation
diff --git a/keama/eval.c b/keama/eval.c
new file mode 100644
index 00000000..8e2dbdb5
--- /dev/null
+++ b/keama/eval.c
@@ -0,0 +1,2252 @@
+/*
+ * Copyright (c) 2017 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "keama.h"
+
+#include <sys/errno.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+static struct element *eval_equal_expression(struct element *,
+ struct element *);
+static isc_boolean_t cmp_hexa(struct element *, isc_boolean_t,
+ struct element *,isc_boolean_t);
+static void debug(const char* fmt, ...);
+
+struct element *
+eval_expression(struct element *expr, isc_boolean_t *modifiedp)
+{
+ if ((expr->type == ELEMENT_BOOLEAN) ||
+ (expr->type == ELEMENT_INTEGER) ||
+ (expr->type == ELEMENT_STRING))
+ return expr;
+
+ if (is_boolean_expression(expr))
+ return eval_boolean_expression(expr, modifiedp);
+ if (is_numeric_expression(expr))
+ return eval_numeric_expression(expr, modifiedp);
+ if (is_data_expression(expr))
+ return eval_data_expression(expr, modifiedp);
+ debug("can't type expression");
+ return expr;
+}
+
+/*
+ * boolean_expression :== CHECK STRING |
+ * NOT boolean-expression |
+ * data-expression EQUAL data-expression |
+ * data-expression BANG EQUAL data-expression |
+ * data-expression REGEX_MATCH data-expression |
+ * boolean-expression AND boolean-expression |
+ * boolean-expression OR boolean-expression
+ * EXISTS OPTION-NAME
+ */
+
+struct element *
+eval_boolean_expression(struct element *expr, isc_boolean_t *modifiedp)
+{
+ /* trivial case: already done */
+ if (expr->type == ELEMENT_BOOLEAN)
+ return expr;
+
+ /*
+ * From is_boolean_expression
+ */
+
+ if (expr->type != ELEMENT_MAP)
+ return expr;
+
+
+ /* check */
+ if (mapContains(expr, "check"))
+ /*
+ * syntax := { "check": <collection_name> }
+ * semantic: check_collection
+ * on server try to match classes of the collection
+ */
+ return expr;
+
+
+ /* exists */
+ if (mapContains(expr, "exists"))
+ /*
+ * syntax := { "exists":
+ * { "universe": <option_space_old>,
+ * "name": <option_name> }
+ * }
+ * semantic: check universe/code from incoming packet
+ */
+ return expr;
+
+ /* variable-exists */
+ if (mapContains(expr, "variable-exists"))
+ /*
+ * syntax := { "variable-exists": <variable_name> }
+ * semantics: find_binding(scope, name)
+ */
+ return expr;
+
+ /* equal */
+ if (mapContains(expr, "equal")) {
+ /*
+ * syntax := { "equal":
+ * { "left": <expression>,
+ * "right": <expression> }
+ * }
+ * semantics: evaluate branches and return true
+ * if same type and same value
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct element *equal;
+ struct comments comments;
+ isc_boolean_t lmodified = ISC_FALSE;
+ isc_boolean_t rmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "equal");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ left = mapGet(arg, "left");
+ if (left == NULL)
+ return expr;
+ right = mapGet(arg, "right");
+ if (right == NULL)
+ return expr;
+ left = eval_expression(left, &lmodified);
+ if (lmodified) {
+ mapRemove(arg, "left");
+ mapSet(arg, left, "left");
+ }
+ right = eval_expression(right, &rmodified);
+ if (rmodified) {
+ mapRemove(arg, "right");
+ mapSet(arg, right, "right");
+ }
+
+ equal = eval_equal_expression(left, right);
+ if ((equal == NULL) || (equal->type != ELEMENT_BOOLEAN))
+ return expr;
+ *modifiedp = ISC_TRUE;
+ TAILQ_INIT(&comments);
+ TAILQ_CONCAT(&comments, &expr->comments);
+ TAILQ_CONCAT(&comments, &arg->comments);
+ TAILQ_CONCAT(&comments, &equal->comments);
+ TAILQ_CONCAT(&equal->comments, &comments);
+ return equal;
+ }
+
+ /* not-equal */
+ if (mapContains(expr, "not-equal")) {
+ /*
+ * syntax := { "not-equal":
+ * { "left": <expression>,
+ * "right": <expression> }
+ * }
+ * semantics: evaluate branches and return true
+ * if different type or different value
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct element *equal;
+ struct element *result;
+ isc_boolean_t lmodified = ISC_FALSE;
+ isc_boolean_t rmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "not-equal");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ left = mapGet(arg, "left");
+ if (left == NULL)
+ return expr;
+ right = mapGet(arg, "right");
+ if (right == NULL)
+ return expr;
+ left = eval_expression(left, &lmodified);
+ if (lmodified) {
+ mapRemove(arg, "left");
+ mapSet(arg, left, "left");
+ }
+ right = eval_expression(right, &rmodified);
+ if (rmodified) {
+ mapRemove(arg, "right");
+ mapSet(arg, right, "right");
+ }
+
+ equal = eval_equal_expression(left, right);
+ if ((equal == NULL) || (equal->type != ELEMENT_BOOLEAN))
+ return expr;
+ *modifiedp = ISC_TRUE;
+ result = createBool(ISC_TF(!boolValue(equal)));
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &equal->comments);
+ return result;
+ }
+
+ /* regex-match */
+ if (mapContains(expr, "regex-match"))
+ /*
+ * syntax := { "regex-match":
+ * { "left": <data_expression>,
+ * "right": <data_expression> }
+ * }
+ * semantics: evaluate branches, compile right as a
+ * regex and apply it to left
+ */
+ return expr;
+
+ /* iregex-match */
+ if (mapContains(expr, "iregex-match"))
+ /*
+ * syntax := { "regex-match":
+ * { "left": <data_expression>,
+ * "right": <data_expression> }
+ * }
+ * semantics: evaluate branches, compile right as a
+ * case insensistive regex and apply it to left
+ */
+ return expr;
+
+ /* and */
+ if (mapContains(expr, "and")) {
+ /*
+ * syntax := { "and":
+ * { "left": <boolean_expression>,
+ * "right": <boolean_expression> }
+ * }
+ * semantics: evaluate branches, return true
+ * if both are true
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct element *result;
+ isc_boolean_t lmodified = ISC_FALSE;
+ isc_boolean_t rmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "and");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ left = mapGet(arg, "left");
+ if (left == NULL)
+ return expr;
+ right = mapGet(arg, "right");
+ if (right == NULL)
+ debug("can't get and right branch");
+ left = eval_boolean_expression(left, &lmodified);
+ if (lmodified) {
+ mapRemove(arg, "left");
+ mapSet(arg, left, "left");
+ }
+ right = eval_boolean_expression(right, &rmodified);
+ if (rmodified) {
+ mapRemove(arg, "right");
+ mapSet(arg, right, "right");
+ }
+
+ if (left->type == ELEMENT_BOOLEAN) {
+ *modifiedp = ISC_TRUE;
+ if (!boolValue(left))
+ result = createBool(ISC_FALSE);
+ else {
+ result = copy(right);
+ TAILQ_INIT(&result->comments);
+ }
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &left->comments);
+ TAILQ_CONCAT(&result->comments, &right->comments);
+ return result;
+ }
+ if (right->type == ELEMENT_BOOLEAN) {
+ *modifiedp = ISC_TRUE;
+ if (!boolValue(right))
+ result = createBool(ISC_FALSE);
+ else {
+ result = copy(left);
+ TAILQ_INIT(&result->comments);
+ }
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &left->comments);
+ TAILQ_CONCAT(&result->comments, &right->comments);
+ return result;
+ }
+ return expr;
+ }
+
+ /* or */
+ if (mapContains(expr, "or")) {
+ /*
+ * syntax := { "or":
+ * { "left": <boolean_expression>,
+ * "right": <boolean_expression> }
+ * }
+ * semantics: evaluate branches, return true
+ * if any is true
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct element *result;
+ isc_boolean_t lmodified = ISC_FALSE;
+ isc_boolean_t rmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "or");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ left = mapGet(arg, "left");
+ if (left == NULL)
+ return expr;
+ right = mapGet(arg, "right");
+ if (right == NULL)
+ return expr;
+ left = eval_boolean_expression(left, &lmodified);
+ if (lmodified) {
+ mapRemove(arg, "left");
+ mapSet(arg, left, "left");
+ }
+ right = eval_boolean_expression(right, &rmodified);
+ if (rmodified) {
+ mapRemove(arg, "right");
+ mapSet(arg, right, "right");
+ }
+
+ if (left->type == ELEMENT_BOOLEAN) {
+ *modifiedp = ISC_TRUE;
+ if (boolValue(left))
+ result = createBool(ISC_TRUE);
+ else {
+ result = copy(right);
+ TAILQ_INIT(&result->comments);
+ }
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &left->comments);
+ TAILQ_CONCAT(&result->comments, &right->comments);
+ return result;
+ }
+ if (right->type == ELEMENT_BOOLEAN) {
+ *modifiedp = ISC_TRUE;
+ if (boolValue(right))
+ result = createBool(ISC_TRUE);
+ else {
+ result = copy(left);
+ TAILQ_INIT(&result->comments);
+ }
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &left->comments);
+ TAILQ_CONCAT(&result->comments, &right->comments);
+ return result;
+ }
+ return expr;
+ }
+
+ /* not */
+ if (mapContains(expr, "not")) {
+ /*
+ * syntax := { "not": <boolean_expression> }
+ * semantic: evaluate its branch and return its negation
+ */
+ struct element *arg;
+ struct element *result;
+ isc_boolean_t modified = ISC_FALSE;
+
+ arg = mapGet(expr, "not");
+ if (arg == NULL)
+ return expr;
+ arg = eval_boolean_expression(arg, &modified);
+ if (modified) {
+ mapRemove(expr, "not");
+ mapSet(expr, arg, "not");
+ }
+
+ /* remove double not */
+ if ((arg->type == ELEMENT_MAP) && mapContains(arg, "not")) {
+ arg = mapGet(arg, "not");
+ if (arg == NULL)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ return arg;
+ }
+
+ /* compose with equal */
+ if ((arg->type == ELEMENT_MAP) &&
+ mapContains(arg, "equal")) {
+ arg = mapGet(arg, "equal");
+ if (arg == NULL)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ result = createMap();
+ mapSet(result, arg, "not-equal");
+ return result;
+ }
+
+ /* compose with not-equal */
+ if ((arg->type == ELEMENT_MAP) &&
+ mapContains(arg, "not-equal")) {
+ arg = mapGet(arg, "not-equal");
+ if (arg == NULL)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ result = createMap();
+ mapSet(result, arg, "equal");
+ return result;
+ }
+
+ if (arg->type != ELEMENT_BOOLEAN)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ result = createBool(ISC_TF(!boolValue(arg)));
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ return result;
+ }
+
+ /* known */
+ if (mapContains(expr, "known"))
+ /*
+ * syntax := { "known": null }
+ * semantics: client is known, i.e., has a matching
+ * host declaration (aka reservation in Kea)
+ */
+ return expr;
+
+ /* static */
+ if (mapContains(expr, "static"))
+ /*
+ * syntax := { "static": null }
+ * semantics: lease is static (doesn't exist in Kea)
+ */
+ return expr;
+
+ return expr;
+}
+
+/*
+ * data_expression :== SUBSTRING LPAREN data-expression COMMA
+ * numeric-expression COMMA
+ * numeric-expression RPAREN |
+ * CONCAT LPAREN data-expression COMMA
+ * data-expression RPAREN
+ * SUFFIX LPAREN data_expression COMMA
+ * numeric-expression RPAREN |
+ * LCASE LPAREN data_expression RPAREN |
+ * UCASE LPAREN data_expression RPAREN |
+ * OPTION option_name |
+ * HARDWARE |
+ * PACKET LPAREN numeric-expression COMMA
+ * numeric-expression RPAREN |
+ * V6RELAY LPAREN numeric-expression COMMA
+ * data-expression RPAREN |
+ * STRING |
+ * colon_separated_hex_list
+ */
+
+struct element *
+eval_data_expression(struct element *expr, isc_boolean_t *modifiedp)
+{
+ /* trivial case: already done */
+ if (expr->type == ELEMENT_STRING)
+ return expr;
+
+ /*
+ * From is_data_expression
+ */
+
+ if (expr->type != ELEMENT_MAP)
+ return expr;
+
+ /* substring */
+ if (mapContains(expr, "substring")) {
+ /*
+ * syntax := { "substring":
+ * { "expression": <data_expression>,
+ * "offset": <numeric_expression>,
+ * "length": <numeric_expression> }
+ * }
+ * semantic: evaluate arguments, if the string is
+ * shorter than offset return "" else return substring
+ */
+ struct element *arg;
+ struct element *string;
+ struct element *offset;
+ struct element *length;
+ struct element *result;
+ struct string *s;
+ struct string *r;
+ int64_t off;
+ int64_t len;
+ isc_boolean_t smodified = ISC_FALSE;
+ isc_boolean_t omodified = ISC_FALSE;
+ isc_boolean_t lmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "substring");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ string = mapGet(arg, "expression");
+ if (string == NULL)
+ return expr;
+ offset = mapGet(arg, "offset");
+ if (offset == NULL)
+ return expr;
+ length = mapGet(arg, "length");
+ if (length == NULL)
+ return expr;
+ string = eval_data_expression(string, &smodified);
+ if (smodified) {
+ mapRemove(arg, "expression");
+ mapSet(arg, string, "expression");
+ }
+ offset = eval_numeric_expression(offset, &omodified);
+ if (omodified) {
+ mapRemove(arg, "offset");
+ mapSet(arg, offset, "offset");
+ }
+ length = eval_numeric_expression(length, &lmodified);
+ if (lmodified) {
+ mapRemove(arg, "length");
+ mapSet(arg, length, "length");
+ }
+
+ if ((offset->type != ELEMENT_INTEGER) ||
+ (length->type != ELEMENT_INTEGER))
+ return expr;
+ off = intValue(offset);
+ len = intValue(length);
+ if ((off < 0) || (len < 0))
+ return expr;
+ /* degenerated case */
+ if (len == 0) {
+ *modifiedp = ISC_TRUE;
+ r = allocString();
+ result = createString(r);
+ return result;
+ }
+
+ /* return (part of) hw-address? */
+ if ((local_family == AF_INET) &&
+ (string->type == ELEMENT_MAP) &&
+ mapContains(string, "concat") &&
+ (off >= 1)) {
+ struct element *concat;
+ struct element *left;
+ struct element *right;
+
+ concat = mapGet(string, "concat");
+ if (concat->type != ELEMENT_MAP)
+ return expr;
+ left = mapGet(concat, "left");
+ if (left == NULL)
+ return expr;
+ right = mapGet(concat, "right");
+ if (right == NULL)
+ return expr;
+ /* from substring(hardware, ...) */
+ if ((left->type == ELEMENT_MAP) &&
+ mapContains(left, "hw-type")) {
+ *modifiedp = ISC_TRUE;
+ mapRemove(arg, "expression");
+ mapSet(arg, right, "expression");
+ mapRemove(arg, "offset");
+ mapSet(arg, createInt(off - 1), "offset");
+ return expr;
+ }
+ return expr;
+ }
+
+ /* return hw-type? */
+ if ((local_family == AF_INET) &&
+ (string->type == ELEMENT_MAP) &&
+ mapContains(string, "concat") &&
+ (off == 0) && (len == 1)) {
+ struct element *concat;
+ struct element *left;
+ struct element *right;
+
+ concat = mapGet(string, "concat");
+ if (concat->type != ELEMENT_MAP)
+ return expr;
+ left = mapGet(concat, "left");
+ if (left == NULL)
+ return expr;
+ right = mapGet(concat, "right");
+ if (right == NULL)
+ return expr;
+ /* from substring(hardware, ...) */
+ if ((left->type == ELEMENT_MAP) &&
+ mapContains(left, "hw-type")) {
+ *modifiedp = ISC_TRUE;
+ return left;
+ }
+ return expr;
+ }
+
+ if (string->type != ELEMENT_STRING)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ s = stringValue(string);
+ if (s->length <= off)
+ r = allocString();
+ else {
+ r = makeString(s->length - off, s->content + off);
+ if (r->length > len)
+ r->length = len;
+ }
+ result = createString(r);
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &string->comments);
+ TAILQ_CONCAT(&result->comments, &offset->comments);
+ TAILQ_CONCAT(&result->comments, &length->comments);
+ return result;
+ }
+
+ /* suffix */
+ if (mapContains(expr, "suffix")) {
+ /*
+ * syntax := { "suffix":
+ * { "expression": <data_expression>,
+ * "length": <numeric_expression> }
+ * }
+ * semantic: evaluate arguments, if the string is
+ * shorter than length return it else return suffix
+ */
+ struct element *arg;
+ struct element *string;
+ struct element *length;
+ struct element *result;
+ struct string *r;
+ int64_t len;
+ isc_boolean_t smodified = ISC_FALSE;
+ isc_boolean_t lmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "suffix");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ string = mapGet(arg, "expression");
+ if (string == NULL)
+ return expr;
+ length = mapGet(arg, "length");
+ if (length == NULL)
+ return expr;
+ string = eval_data_expression(string, &smodified);
+ if (smodified) {
+ mapRemove(arg, "expression");
+ mapSet(arg, string, "expression");
+ }
+ length = eval_numeric_expression(length, &lmodified);
+ if (lmodified) {
+ mapRemove(arg, "length");
+ mapSet(arg, length, "length");
+ }
+
+ if ((string->type != ELEMENT_STRING) ||
+ (length->type != ELEMENT_INTEGER))
+ return expr;
+ len = intValue(length);
+ if (len < 0)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ r = stringValue(string);
+ if (r->length > len)
+ r = makeString(r->length - len, r->content + len);
+ result = createString(r);
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &string->comments);
+ TAILQ_CONCAT(&result->comments, &length->comments);
+ return result;
+ }
+
+ /* lowercase */
+ if (mapContains(expr, "lowercase")) {
+ /*
+ * syntax := { "lowercase": <data_expression> }
+ * semantic: evaluate its argument and apply tolower to
+ * its content
+ */
+ struct element *arg;
+ struct element *result;
+ struct string *r;
+ size_t i;
+ isc_boolean_t modified = ISC_FALSE;
+
+ arg = mapGet(expr, "lowercase");
+ if (arg == NULL)
+ return expr;
+ arg = eval_data_expression(arg, &modified);
+ if (modified) {
+ mapRemove(expr, "lowercase");
+ mapSet(expr, arg, "lowercase");
+ }
+
+ if (arg->type != ELEMENT_STRING)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ r = allocString();
+ concatString(r, stringValue(arg));
+ for (i = 0; i < r->length; i++)
+ r->content[i] = tolower(r->content[i]);
+ result = createString(r);
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ return result;
+ }
+
+ /* uppercase */
+ if (mapContains(expr, "uppercase")) {
+ /*
+ * syntax := { "uppercase": <data_expression> }
+ * semantic: evaluate its argument and apply toupper to
+ * its content
+ */
+ struct element *arg;
+ struct element *result;
+ struct string *r;
+ size_t i;
+ isc_boolean_t modified = ISC_FALSE;
+
+ arg = mapGet(expr, "uppercase");
+ if (arg == NULL)
+ return expr;
+ arg = eval_data_expression(arg, &modified);
+ if (modified) {
+ mapRemove(expr, "lowercase");
+ mapSet(expr, arg, "lowercase");
+ }
+
+ if (arg->type != ELEMENT_STRING)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ r = allocString();
+ concatString(r, stringValue(arg));
+ for (i = 0; i < r->length; i++)
+ r->content[i] = toupper(r->content[i]);
+ result = createString(r);
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ return result;
+ }
+
+ /* option */
+ if (mapContains(expr, "option"))
+ /*
+ * syntax := { "option":
+ * { "universe": <option_space_old>,
+ * "name": <option_name> }
+ * }
+ * semantic: get universe/code option from incoming packet
+ */
+ return expr;
+
+ /* hardware */
+ if (mapContains(expr, "hardware")) {
+ /*
+ * syntax := { "hardware": null }
+ * semantic: get mac type and address from incoming packet
+ */
+ struct element *left;
+ struct element *right;
+ struct element *concat;
+ struct element *result;
+
+ if (local_family != AF_INET)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ left = createMap();
+ mapSet(left, createNull(), "hw-type");
+ concat = createMap();
+ mapSet(concat, left, "left");
+ right = createMap();
+ mapSet(right, createNull(), "hw-address");
+ mapSet(concat, right, "right");
+ result = createMap();
+ mapSet(result, concat, "concat");
+ return result;
+ }
+
+ /* hw-type */
+ if (mapContains(expr, "hw-type"))
+ /*
+ * syntax := { "hw-type": null }
+ * semantic: get mac type and address from incoming packet
+ */
+ return expr;
+
+ /* hw-address */
+ if (mapContains(expr, "hw-address"))
+ /*
+ * syntax := { "hw-address": null }
+ * semantic: get mac type and address from incoming packet
+ */
+ return expr;
+
+ /* const-data */
+ if (mapContains(expr, "const-data"))
+ /*
+ * syntax := { "const-data": <string> }
+ * semantic: embedded string value
+ */
+ return expr;
+
+ /* packet */
+ if (mapContains(expr, "packet"))
+ /*
+ * syntax := { "packet":
+ * { "offset": <numeric_expression>,
+ * "length": <numeric_expression> }
+ * }
+ * semantic: return the selected substring of the incoming
+ * packet content
+ */
+ return expr;
+
+ /* concat */
+ if (mapContains(expr, "concat")) {
+ /*
+ * syntax := { "concat":
+ * { "left": <data_expression>,
+ * "right": <data_expression> }
+ * }
+ * semantic: evaluate arguments and return the concatenation
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct element *result;
+ struct string *r;
+ isc_boolean_t lmodified = ISC_FALSE;
+ isc_boolean_t rmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "concat");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ left = mapGet(arg, "left");
+ if (left == NULL)
+ return expr;
+ right = mapGet(arg, "right");
+ if (right == NULL)
+ return expr;
+ left = eval_data_expression(left, &lmodified);
+ if (lmodified) {
+ mapRemove(arg, "left");
+ mapSet(arg, left, "left");
+ }
+ right = eval_data_expression(right, &rmodified);
+ if (rmodified) {
+ mapRemove(arg, "right");
+ mapSet(arg, right, "right");
+ }
+
+ /* degenerated cases */
+ if ((left->type == ELEMENT_STRING) &&
+ (stringValue(left)->length == 0)) {
+ *modifiedp = ISC_TRUE;
+ return right;
+ }
+ if ((right->type == ELEMENT_STRING) &&
+ (stringValue(right)->length == 0)) {
+ *modifiedp = ISC_TRUE;
+ return left;
+ }
+
+ if ((left->type != ELEMENT_STRING) ||
+ (right->type != ELEMENT_STRING))
+ return expr;
+ *modifiedp = ISC_TRUE;
+ r = allocString();
+ concatString(r, stringValue(left));
+ concatString(r, stringValue(right));
+ result = createString(r);
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &left->comments);
+ TAILQ_CONCAT(&result->comments, &right->comments);
+ return result;
+ }
+
+ /* encapsulate */
+ if (mapContains(expr, "encapsulate"))
+ /*
+ * syntax := { "encapsulate": <encapsulated_space> }
+ * semantic: encapsulate options of the given space
+ */
+ return expr;
+
+ /* encode-int8 */
+ if (mapContains(expr, "encode-int8")) {
+ /*
+ * syntax := { "encode-int8": <numeric_expression> }
+ * semantic: return a string buffer with the evaluated
+ * number as content
+ */
+ struct element *arg;
+ struct element *result;
+ struct string *r;
+ uint8_t val;
+ isc_boolean_t modified = ISC_FALSE;
+
+ arg = mapGet(expr, "encode-int8");
+ if (arg == NULL)
+ return expr;
+ arg = eval_numeric_expression(arg, &modified);
+ if (modified) {
+ mapRemove(expr, "encode-int8");
+ mapSet(expr, arg, "encode-int8");
+ }
+
+ if (arg->type != ELEMENT_INTEGER)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ val = (uint8_t)intValue(arg);
+ r = makeString(sizeof(val), (char *)&val);
+ result = createString(r);
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ return result;
+ }
+
+ /* encode-int16 */
+ if (mapContains(expr, "encode-int16")) {
+ /*
+ * syntax := { "encode-int16": <numeric_expression> }
+ * semantic: return a string buffer with the evaluated
+ * number as content
+ */
+ struct element *arg;
+ struct element *result;
+ struct string *r;
+ uint16_t val;
+ isc_boolean_t modified = ISC_FALSE;
+
+ arg = mapGet(expr, "encode-int16");
+ if (arg == NULL)
+ return expr;
+ arg = eval_numeric_expression(arg, &modified);
+ if (modified) {
+ mapRemove(expr, "encode-int16");
+ mapSet(expr, arg, "encode-int16");
+ }
+
+ if (arg->type != ELEMENT_INTEGER)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ val = (uint16_t)intValue(arg);
+ val = htons(val);
+ r = makeString(sizeof(val), (char *)&val);
+ result = createString(r);
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ return result;
+ }
+
+ /* encode-int32 */
+ if (mapContains(expr, "encode-int32")) {
+ /*
+ * syntax := { "encode-int32": <numeric_expression> }
+ * semantic: return a string buffer with the evaluated
+ * number as content
+ */
+ struct element *arg;
+ struct element *result;
+ struct string *r;
+ uint32_t val;
+ isc_boolean_t modified = ISC_FALSE;
+
+ arg = mapGet(expr, "encode-int32");
+ if (arg == NULL)
+ return expr;
+ arg = eval_numeric_expression(arg, &modified);
+ if (modified) {
+ mapRemove(expr, "encode-int32");
+ mapSet(expr, arg, "encode-int32");
+ }
+
+ if (arg->type != ELEMENT_INTEGER)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ val = (uint32_t)intValue(arg);
+ val = htonl(val);
+ r = makeString(sizeof(val), (char *)&val);
+ result = createString(r);
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ return result;
+ }
+
+ /* gethostbyname */
+ if (mapContains(expr, "gethostbyname")) {
+ /*
+ * syntax := { "gethostbyname": <string> }
+ * semantic: call gethostbyname and return
+ * a binary buffer with addresses
+ */
+ struct element *arg;
+ struct element *result;
+ struct string *r;
+ char *hostname;
+ struct hostent *h;
+ size_t i;
+
+ if (local_family != AF_INET)
+ return expr;
+ arg = mapGet(expr, "gethostbyname");
+ if ((arg == NULL) || (arg->type != ELEMENT_STRING))
+ return expr;
+ hostname = stringValue(arg)->content;
+ h = gethostbyname(hostname);
+ r = allocString();
+ if (h == NULL) {
+ switch (h_errno) {
+ case HOST_NOT_FOUND:
+ debug("gethostbyname: %s: host unknown",
+ hostname);
+ break;
+ case TRY_AGAIN:
+ debug("gethostbyname: %s: temporary name "
+ "server failure", hostname);
+ break;
+ case NO_RECOVERY:
+ debug("gethostbyname: %s: name server failed",
+ hostname);
+ break;
+ case NO_DATA:
+ debug("gethostbyname: %s: no A record "
+ "associated with address", hostname);
+ break;
+ }
+ } else
+ for (i = 0; h->h_addr_list[i] != NULL; i++) {
+ struct string *addr;
+
+ addr = makeString(4, h->h_addr_list[i]);
+ concatString(r, addr);
+ }
+ *modifiedp = ISC_TRUE;
+ r = makeStringExt(r->length, r->content, 'X');
+ result = createString(r);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ return result;
+ }
+
+ /* binary-to-ascii */
+ if (mapContains(expr, "binary-to-ascii")) {
+ /*
+ * syntax := { "binary-to-ascii":
+ * { "base": <numeric_expression 2..16>,
+ * "width": <numeric_expression 8, 16 or 32>,
+ * "separator": <data_expression>,
+ * "buffer": <data_expression> }
+ * }
+ * semantic: split the input buffer into int8/16/32 numbers,
+ * output them separated by the given string
+ */
+ struct element *arg;
+ struct element *base;
+ struct element *width;
+ struct element *separator;
+ struct element *buffer;
+ struct element *result;
+ struct string *sep;
+ struct string *buf;
+ struct string *r;
+ int64_t b;
+ int64_t w;
+ isc_boolean_t bmodified = ISC_FALSE;
+ isc_boolean_t wmodified = ISC_FALSE;
+ isc_boolean_t smodified = ISC_FALSE;
+ isc_boolean_t dmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "binary-to-ascii");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ base = mapGet(arg, "base");
+ if (base == NULL)
+ return expr;
+ width = mapGet(arg, "width");
+ if (width == NULL)
+ return expr;
+ separator = mapGet(arg, "separator");
+ if (separator == NULL)
+ return expr;
+ buffer = mapGet(arg, "buffer");
+ if (buffer == NULL)
+ return expr;
+ base = eval_numeric_expression(base, &bmodified);
+ if (bmodified) {
+ mapRemove(arg, "base");
+ mapSet(arg, base, "base");
+ }
+ width = eval_numeric_expression(width, &wmodified);
+ if (wmodified) {
+ mapRemove(arg, "width");
+ mapSet(arg, width, "width");
+ }
+ separator = eval_data_expression(separator, &smodified);
+ if (smodified) {
+ mapRemove(arg, "separator");
+ mapSet(arg, separator, "separator");
+ }
+ buffer = eval_data_expression(buffer, &dmodified);
+ if (dmodified) {
+ mapRemove(arg, "buffer");
+ mapSet(arg, buffer, "buffer");
+ }
+
+ if ((base->type != ELEMENT_INTEGER) ||
+ (width->type != ELEMENT_INTEGER) ||
+ (separator->type != ELEMENT_STRING) ||
+ (buffer->type != ELEMENT_STRING))
+ return expr;
+ b = intValue(base);
+ if ((b < 2) || (b > 16))
+ return expr;
+ if ((b != 8) && (b != 10) && (b != 16))
+ return expr;
+ w = intValue(width);
+ if ((w != 8) && (w != 16) && (w != 32))
+ return expr;
+ sep = stringValue(separator);
+ buf = stringValue(buffer);
+ r = allocString();
+ if (w == 8) {
+ size_t i;
+ char *fmt;
+
+ switch (b) {
+ case 8:
+ fmt = "%o";
+ break;
+ case 10:
+ fmt = "%d";
+ break;
+ case 16:
+ default:
+ fmt = "%x";
+ break;
+ }
+
+ for (i = 0; i < buf->length; i++) {
+ uint8_t val;
+ char num[4];
+
+ if (i != 0)
+ concatString(r, sep);
+ val = (uint8_t)buf->content[i];
+ snprintf(num, sizeof(num), fmt, (int)val);
+ appendString(r, num);
+ }
+ } else if (w == 16) {
+ size_t i;
+ char *fmt;
+
+ if ((buf->length % 2) != 0)
+ return expr;
+
+ switch (b) {
+ case 8:
+ fmt = "%o";
+ break;
+ case 10:
+ fmt = "%d";
+ break;
+ case 16:
+ default:
+ fmt = "%x";
+ break;
+ }
+
+ for (i = 0; i < buf->length; i += 2) {
+ uint16_t val;
+ char num[8];
+
+ if (i != 0)
+ concatString(r, sep);
+ memcpy(&val, buf->content + i, 2);
+ val = ntohs(val);
+ snprintf(num, sizeof(num), fmt, (int)val);
+ appendString(r, num);
+ }
+ } else if (w == 32) {
+ size_t i;
+ char *fmt;
+
+ if ((buf->length % 4) != 0)
+ return expr;
+
+ switch (b) {
+ case 8:
+ fmt = "%llo";
+ break;
+ case 10:
+ fmt = "%lld";
+ break;
+ case 16:
+ default:
+ fmt = "%llx";
+ break;
+ }
+
+ for (i = 0; i < buf->length; i += 4) {
+ uint32_t val;
+ char num[40];
+
+ if (i != 0)
+ concatString(r, sep);
+ memcpy(&val, buf->content + i, 4);
+ val = ntohl(val);
+ snprintf(num, sizeof(num), fmt,
+ (long long)val);
+ appendString(r, num);
+ }
+ }
+ *modifiedp = ISC_TRUE;
+ result = createString(r);
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &base->comments);
+ TAILQ_CONCAT(&result->comments, &width->comments);
+ TAILQ_CONCAT(&result->comments, &separator->comments);
+ TAILQ_CONCAT(&result->comments, &buffer->comments);
+ return result;
+ }
+
+ /* filename */
+ if (mapContains(expr, "filename"))
+ /*
+ * syntax := { "filename": null }
+ * semantic: get filename field from incoming DHCPv4 packet
+ */
+ return expr;
+
+ /* server-name */
+ if (mapContains(expr, "server-name"))
+ /*
+ * syntax := { "server-name": null }
+ * semantic: get server-name field from incoming DHCPv4 packet
+ */
+ return expr;
+
+ /* reverse */
+ if (mapContains(expr, "reverse")) {
+ /*
+ * syntax := { "reverse":
+ * { "width": <numeric_expression>,
+ * "buffer": <data_expression> }
+ * }
+ * semantic: reverse the input buffer by width chunks of bytes
+ */
+ struct element *arg;
+ struct element *width;
+ struct element *buffer;
+ struct element *result;
+ struct string *buf;
+ struct string *r;
+ int64_t w;
+ size_t i;
+ isc_boolean_t wmodified = ISC_FALSE;
+ isc_boolean_t bmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "reverse");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ width = mapGet(arg, "width");
+ if (width == NULL)
+ return expr;
+ buffer = mapGet(arg, "buffer");
+ if (buffer == NULL)
+ return expr;
+ width = eval_numeric_expression(width, &wmodified);
+ if (wmodified) {
+ mapRemove(arg, "width");
+ mapSet(arg, width, "width");
+ }
+ buffer = eval_data_expression(buffer, &bmodified);
+ if (bmodified) {
+ mapRemove(arg, "buffer");
+ mapSet(arg, buffer, "buffer");
+ }
+
+ if ((width->type != ELEMENT_INTEGER) ||
+ (buffer->type != ELEMENT_STRING))
+ return expr;
+ w = intValue(width);
+ if (w <= 0)
+ return expr;
+ buf = stringValue(buffer);
+ if ((buf->length % w) != 0)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ r = allocString();
+ concatString(r, buf);
+ for (i = 0; i < buf->length; i += w) {
+ memcpy(r->content + i,
+ buf->content + (buf->length - i - w),
+ w);
+ }
+ result = createString(r);
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &width->comments);
+ TAILQ_CONCAT(&result->comments, &buffer->comments);
+ return result;
+ }
+
+ /* pick-first-value */
+ if (mapContains(expr, "pick-first-value")) {
+ /*
+ * syntax := { "pick-first-value":
+ * [ <data_expression>, ... ]
+ * }
+ * semantic: evaluates expressions and return the first
+ * not null, return null if all are null
+ */
+ struct element *arg;
+ struct element *result;
+ size_t i;
+ isc_boolean_t modified;
+ isc_boolean_t can_decide = ISC_TRUE;
+
+ arg = mapGet(expr, "pick-first-value");
+ if ((arg == NULL) || (arg->type != ELEMENT_LIST))
+ return expr;
+
+ for (i = 0; i < listSize(arg); i++) {
+ struct element *item;
+
+ item = listGet(arg, i);
+ if (item == NULL)
+ return expr;
+ modified = ISC_FALSE;
+ item = eval_data_expression(item, &modified);
+ if (modified)
+ listRemove(arg, i);
+ if (!can_decide)
+ goto restore;
+ if (item->type != ELEMENT_STRING) {
+ can_decide = ISC_FALSE;
+ goto restore;
+ }
+ if (stringValue(item)->length != 0) {
+ *modifiedp = ISC_TRUE;
+ TAILQ_CONCAT(&item->comments, &expr->comments);
+ TAILQ_CONCAT(&item->comments, &arg->comments);
+ return item;
+ }
+ restore:
+ listSet(arg, item, i);
+ }
+ if (!can_decide)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ result = createString(allocString());
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ return result;
+ }
+
+ /* host-decl-name */
+ if (mapContains(expr, "host-decl-name"))
+ /*
+ * syntax := { "host-decl-name": null }
+ * semantic: return the name of the matching host
+ * declaration (aka revervation in kea) or null
+ */
+ return expr;
+
+ /* leased-address */
+ if (mapContains(expr, "leased-address"))
+ /*
+ * syntax := { "leased-address": null }
+ * semantic: return the address of the assigned lease or
+ * log a message
+ */
+ return expr;
+
+ /* config-option */
+ if (mapContains(expr, "config-option"))
+ /*
+ * syntax := { "config-option":
+ * { "universe": <option_space_old>,
+ * "name": <option_name> }
+ * }
+ * semantic: get universe/code option to send
+ */
+ return expr;
+
+ /* null */
+ if (mapContains(expr, "null")) {
+ /*
+ * syntax := { "null": null }
+ * semantic: return null
+ */
+ struct element *result;
+
+ *modifiedp = ISC_TRUE;
+ result = createString(allocString());
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ return result;
+ }
+
+ /* gethostname */
+ if (mapContains(expr, "gethostname")) {
+ /*
+ * syntax := { "gethostname": null }
+ * semantic: return gethostname
+ */
+ struct element *result;
+ char buf[300 /* >= 255 + 1 */];
+
+ if (gethostname(buf, sizeof(buf)) != 0) {
+ debug("gethostname fails: %s", strerror(errno));
+ return expr;
+ }
+ *modifiedp = ISC_TRUE;
+ result = createString(makeString(-1, buf));
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ return result;
+ }
+
+ /* v6relay */
+ if (mapContains(expr, "v6relay")) {
+ /*
+ * syntax := { "v6relay":
+ * { "relay": <numeric_expression>,
+ * "relay-option" <data_expression> }
+ * }
+ * semantic: relay is a counter from client, 0 is no-op,
+ * 1 is the relay closest to the client, etc, option
+ * is a dhcp6 option ans is return when found
+ */
+ struct element *arg;
+ struct element *relay;
+ isc_boolean_t modified = ISC_FALSE;
+
+ if (local_family != AF_INET6)
+ return expr;
+ arg = mapGet(expr, "v6relay");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ relay = mapGet(arg, "relay");
+ if (relay == NULL)
+ return expr;
+ relay = eval_numeric_expression(relay, &modified);
+ if (modified) {
+ mapRemove(arg, "relay");
+ mapSet(arg, relay, "relay");
+ }
+ return expr;
+ }
+
+ return expr;
+}
+
+/*
+ * numeric-expression :== EXTRACT_INT LPAREN data-expression
+ * COMMA number RPAREN |
+ * NUMBER
+ */
+
+struct element *
+eval_numeric_expression(struct element *expr, isc_boolean_t *modifiedp)
+{
+ /* trivial case: already done */
+ if (expr->type == ELEMENT_INTEGER)
+ return expr;
+
+ /*
+ * From is_numeric_expression
+ */
+
+ if (expr->type != ELEMENT_MAP)
+ return expr;
+
+ /* extract-int8 */
+ if (mapContains(expr, "extract-int8")) {
+ /*
+ * syntax := { "extract-int8": <data_expression> }
+ * semantic: extract from the evalkuated string buffer
+ * a number
+ */
+ struct element *arg;
+ struct element *result;
+ uint8_t val = 0;
+ isc_boolean_t modified = ISC_FALSE;
+
+ arg = mapGet(expr, "extract-int8");
+ if (arg == NULL)
+ return expr;
+ arg = eval_data_expression(arg, &modified);
+ if (modified) {
+ mapRemove(expr, "extract-int8");
+ mapSet(expr, arg, "extract-int8");
+ }
+
+ if (arg->type != ELEMENT_STRING)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ if (stringValue(arg)->length > 0)
+ val = (uint8_t) stringValue(arg)->content[0];
+ result = createInt(val);
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ return result;
+ }
+
+ /* extract-int16 */
+ if (mapContains(expr, "extract-int16")) {
+ /*
+ * syntax := { "extract-int16": <data_expression> }
+ * semantic: extract from the evalkuated string buffer
+ * a number
+ */
+ struct element *arg;
+ struct element *result;
+ uint16_t val;
+ isc_boolean_t modified = ISC_FALSE;
+
+ arg = mapGet(expr, "extract-int16");
+ if (arg == NULL)
+ return expr;
+ arg = eval_data_expression(arg, &modified);
+ if (modified) {
+ mapRemove(expr, "extract-int16");
+ mapSet(expr, arg, "extract-int16");
+ }
+
+ if (arg->type != ELEMENT_STRING)
+ return expr;
+ if (stringValue(arg)->length < 2)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ memcpy(&val, stringValue(arg)->content, 2);
+ val = ntohs(val);
+ result = createInt(val);
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ return result;
+ }
+
+ /* extract-int32 */
+ if (mapContains(expr, "extract-int32")) {
+ /*
+ * syntax := { "extract-int32": <data_expression> }
+ * semantic: extract from the evalkuated string buffer
+ * a number
+ */
+ struct element *arg;
+ struct element *result;
+ uint32_t val;
+ isc_boolean_t modified = ISC_FALSE;
+
+ arg = mapGet(expr, "extract-int32");
+ if (arg == NULL)
+ return expr;
+ arg = eval_data_expression(arg, &modified);
+ if (modified) {
+ mapRemove(expr, "extract-int32");
+ mapSet(expr, arg, "extract-int32");
+ }
+
+ if (arg->type != ELEMENT_STRING)
+ return expr;
+ if (stringValue(arg)->length < 4)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ memcpy(&val, stringValue(arg)->content, 4);
+ val = ntohl(val);
+ result = createInt(val);
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ return result;
+ }
+
+ /* const-int */
+ if (mapContains(expr, "const-int")) {
+ /*
+ * syntax := { "const-int": <integer> }
+ * semantic: embedded integer value
+ */
+ struct element *arg;
+ struct element *result;
+
+ arg = mapGet(expr, "const-int");
+ if ((arg == NULL) || (arg->type != ELEMENT_INTEGER))
+ return expr;
+ *modifiedp = ISC_TRUE;
+ result = createInt(intValue(arg));
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ return result;
+ }
+
+ /* lease-time */
+ if (mapContains(expr, "lease-time"))
+ /*
+ * syntax := { "lease-time": null }
+ * semantic: return duration of the current lease, i.e
+ * the difference between expire time and now
+ */
+ return expr;
+
+ /* add */
+ if (mapContains(expr, "add")) {
+ /*
+ * syntax := { "add":
+ * { "left": <boolean_expression>,
+ * "right": <boolean_expression> }
+ * }
+ * semantics: evaluate branches, return left plus right
+ * branches
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct element *result;
+ isc_boolean_t lmodified = ISC_FALSE;
+ isc_boolean_t rmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "add");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ left = mapGet(arg, "left");
+ if (left == NULL)
+ return expr;
+ right = mapGet(arg, "right");
+ if (right == NULL)
+ return expr;
+ left = eval_numeric_expression(left, &lmodified);
+ if (lmodified) {
+ mapRemove(arg, "left");
+ mapSet(arg, left, "left");
+ }
+ right = eval_numeric_expression(right, &rmodified);
+ if (rmodified) {
+ mapRemove(arg, "right");
+ mapSet(arg, right, "right");
+ }
+
+ if ((left->type != ELEMENT_INTEGER) ||
+ (right->type != ELEMENT_INTEGER))
+ return expr;
+ *modifiedp = ISC_TRUE;
+ result = createInt(intValue(left) + intValue(right));
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &left->comments);
+ TAILQ_CONCAT(&result->comments, &right->comments);
+ return result;
+ }
+
+ /* subtract */
+ if (mapContains(expr, "subtract")) {
+ /*
+ * syntax := { "subtract":
+ * { "left": <boolean_expression>,
+ * "right": <boolean_expression> }
+ * }
+ * semantics: evaluate branches, return left plus right
+ * branches
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct element *result;
+ isc_boolean_t lmodified = ISC_FALSE;
+ isc_boolean_t rmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "subtract");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ left = mapGet(arg, "left");
+ if (left == NULL)
+ return expr;
+ right = mapGet(arg, "right");
+ if (right == NULL)
+ return expr;
+ left = eval_numeric_expression(left, &lmodified);
+ if (lmodified) {
+ mapRemove(arg, "left");
+ mapSet(arg, left, "left");
+ }
+ right = eval_numeric_expression(right, &rmodified);
+ if (rmodified) {
+ mapRemove(arg, "right");
+ mapSet(arg, right, "right");
+ }
+
+ if ((left->type != ELEMENT_INTEGER) ||
+ (right->type != ELEMENT_INTEGER))
+ return expr;
+ *modifiedp = ISC_TRUE;
+ result = createInt(intValue(left) - intValue(right));
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &left->comments);
+ TAILQ_CONCAT(&result->comments, &right->comments);
+ return result;
+ }
+
+ /* multiply */
+ if (mapContains(expr, "multiply")) {
+ /*
+ * syntax := { "multiply":
+ * { "left": <boolean_expression>,
+ * "right": <boolean_expression> }
+ * }
+ * semantics: evaluate branches, return left plus right
+ * branches
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct element *result;
+ isc_boolean_t lmodified = ISC_FALSE;
+ isc_boolean_t rmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "multiply");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ left = mapGet(arg, "left");
+ if (left == NULL)
+ return expr;
+ right = mapGet(arg, "right");
+ if (right == NULL)
+ return expr;
+ left = eval_numeric_expression(left, &lmodified);
+ if (lmodified) {
+ mapRemove(arg, "left");
+ mapSet(arg, left, "left");
+ }
+ right = eval_numeric_expression(right, &rmodified);
+ if (rmodified) {
+ mapRemove(arg, "right");
+ mapSet(arg, right, "right");
+ }
+
+ if ((left->type != ELEMENT_INTEGER) ||
+ (right->type != ELEMENT_INTEGER))
+ return expr;
+ *modifiedp = ISC_TRUE;
+ result = createInt(intValue(left) * intValue(right));
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &left->comments);
+ TAILQ_CONCAT(&result->comments, &right->comments);
+ return result;
+ }
+
+ /* divide */
+ if (mapContains(expr, "divide")) {
+ /*
+ * syntax := { "divide":
+ * { "left": <boolean_expression>,
+ * "right": <boolean_expression> }
+ * }
+ * semantics: evaluate branches, return left plus right
+ * branches
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct element *result;
+ isc_boolean_t lmodified = ISC_FALSE;
+ isc_boolean_t rmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "divide");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ left = mapGet(arg, "left");
+ if (left == NULL)
+ return expr;
+ right = mapGet(arg, "right");
+ if (right == NULL)
+ return expr;
+ left = eval_numeric_expression(left, &lmodified);
+ if (lmodified) {
+ mapRemove(arg, "left");
+ mapSet(arg, left, "left");
+ }
+ right = eval_numeric_expression(right, &rmodified);
+ if (rmodified) {
+ mapRemove(arg, "right");
+ mapSet(arg, right, "right");
+ }
+
+ if ((left->type != ELEMENT_INTEGER) ||
+ (right->type != ELEMENT_INTEGER))
+ return expr;
+ if (intValue(right) == 0)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ result = createInt(intValue(left) / intValue(right));
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &left->comments);
+ TAILQ_CONCAT(&result->comments, &right->comments);
+ return result;
+ }
+
+ /* remainder */
+ if (mapContains(expr, "remainder")) {
+ /*
+ * syntax := { "remainder":
+ * { "left": <boolean_expression>,
+ * "right": <boolean_expression> }
+ * }
+ * semantics: evaluate branches, return left plus right
+ * branches
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct element *result;
+ isc_boolean_t lmodified = ISC_FALSE;
+ isc_boolean_t rmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "remainder");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ left = mapGet(arg, "left");
+ if (left == NULL)
+ return expr;
+ right = mapGet(arg, "right");
+ if (right == NULL)
+ return expr;
+ left = eval_numeric_expression(left, &lmodified);
+ if (lmodified) {
+ mapRemove(arg, "left");
+ mapSet(arg, left, "left");
+ }
+ right = eval_numeric_expression(right, &rmodified);
+ if (rmodified) {
+ mapRemove(arg, "right");
+ mapSet(arg, right, "right");
+ }
+
+ if ((left->type != ELEMENT_INTEGER) ||
+ (right->type != ELEMENT_INTEGER))
+ return expr;
+ if (intValue(right) == 0)
+ return expr;
+ *modifiedp = ISC_TRUE;
+ result = createInt(intValue(left) % intValue(right));
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &left->comments);
+ TAILQ_CONCAT(&result->comments, &right->comments);
+ return result;
+ }
+
+ /* binary-and */
+ if (mapContains(expr, "binary-and")) {
+ /*
+ * syntax := { "binary-and":
+ * { "left": <boolean_expression>,
+ * "right": <boolean_expression> }
+ * }
+ * semantics: evaluate branches, return left plus right
+ * branches
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct element *result;
+ isc_boolean_t lmodified = ISC_FALSE;
+ isc_boolean_t rmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "binary-and");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ left = mapGet(arg, "left");
+ if (left == NULL)
+ return expr;
+ right = mapGet(arg, "right");
+ if (right == NULL)
+ return expr;
+ left = eval_numeric_expression(left, &lmodified);
+ if (lmodified) {
+ mapRemove(arg, "left");
+ mapSet(arg, left, "left");
+ }
+ right = eval_numeric_expression(right, &rmodified);
+ if (rmodified) {
+ mapRemove(arg, "right");
+ mapSet(arg, right, "right");
+ }
+
+ if ((left->type != ELEMENT_INTEGER) ||
+ (right->type != ELEMENT_INTEGER))
+ return expr;
+ *modifiedp = ISC_TRUE;
+ result = createInt(intValue(left) & intValue(right));
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &left->comments);
+ TAILQ_CONCAT(&result->comments, &right->comments);
+ return result;
+ }
+
+ /* binary-or */
+ if (mapContains(expr, "binary-or")) {
+ /*
+ * syntax := { "binary-or":
+ * { "left": <boolean_expression>,
+ * "right": <boolean_expression> }
+ * }
+ * semantics: evaluate branches, return left plus right
+ * branches
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct element *result;
+ isc_boolean_t lmodified = ISC_FALSE;
+ isc_boolean_t rmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "binary-or");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ left = mapGet(arg, "left");
+ if (left == NULL)
+ return expr;
+ right = mapGet(arg, "right");
+ if (right == NULL)
+ return expr;
+ left = eval_numeric_expression(left, &lmodified);
+ if (lmodified) {
+ mapRemove(arg, "left");
+ mapSet(arg, left, "left");
+ }
+ right = eval_numeric_expression(right, &rmodified);
+ if (rmodified) {
+ mapRemove(arg, "right");
+ mapSet(arg, right, "right");
+ }
+
+ if ((left->type != ELEMENT_INTEGER) ||
+ (right->type != ELEMENT_INTEGER))
+ return expr;
+ *modifiedp = ISC_TRUE;
+ result = createInt(intValue(left) | intValue(right));
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &left->comments);
+ TAILQ_CONCAT(&result->comments, &right->comments);
+ return result;
+ }
+
+ /* binary-xor */
+ if (mapContains(expr, "binary-xor")) {
+ /*
+ * syntax := { "binary-xor":
+ * { "left": <boolean_expression>,
+ * "right": <boolean_expression> }
+ * }
+ * semantics: evaluate branches, return left plus right
+ * branches
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct element *result;
+ isc_boolean_t lmodified = ISC_FALSE;
+ isc_boolean_t rmodified = ISC_FALSE;
+
+ arg = mapGet(expr, "binary-xor");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP))
+ return expr;
+ left = mapGet(arg, "left");
+ if (left == NULL)
+ return expr;
+ right = mapGet(arg, "right");
+ if (right == NULL)
+ return expr;
+ left = eval_numeric_expression(left, &lmodified);
+ if (lmodified) {
+ mapRemove(arg, "left");
+ mapSet(arg, left, "left");
+ }
+ right = eval_numeric_expression(right, &rmodified);
+ if (rmodified) {
+ mapRemove(arg, "right");
+ mapSet(arg, right, "right");
+ }
+
+ if ((left->type != ELEMENT_INTEGER) ||
+ (right->type != ELEMENT_INTEGER))
+ return expr;
+ *modifiedp = ISC_TRUE;
+ result = createInt(intValue(left) ^ intValue(right));
+ TAILQ_CONCAT(&result->comments, &expr->comments);
+ TAILQ_CONCAT(&result->comments, &arg->comments);
+ TAILQ_CONCAT(&result->comments, &left->comments);
+ TAILQ_CONCAT(&result->comments, &right->comments);
+ return result;
+ }
+
+ /* client-state */
+ if (mapContains(expr, "client-state"))
+ /*
+ * syntax := { "client-state": null }
+ * semantic: return client state
+ */
+ return expr;
+
+ return expr;
+}
+
+/*
+ * Check if the two evaluated expressions are equal, not equal,
+ * or we can't decide.
+ */
+
+static struct element *
+eval_equal_expression(struct element *left, struct element *right)
+{
+ struct element *result = NULL;
+ isc_boolean_t val;
+
+ /* in theory boolean is not possible */
+ if (left->type == ELEMENT_BOOLEAN) {
+ if (right->type == ELEMENT_BOOLEAN)
+ val = ISC_TF(boolValue(left) == boolValue(right));
+ else if (right->type == ELEMENT_MAP)
+ return NULL;
+ else
+ val = ISC_FALSE;
+ } else
+ /* right is boolean */
+ if (right->type == ELEMENT_BOOLEAN) {
+ if (left->type == ELEMENT_MAP)
+ return NULL;
+ else
+ val = ISC_FALSE;
+ } else
+ /* left is numeric literal */
+ if (left->type == ELEMENT_INTEGER) {
+ if (right->type == ELEMENT_INTEGER)
+ val = ISC_TF(intValue(left) == intValue(right));
+ else if ((right->type == ELEMENT_MAP) &&
+ mapContains(right, "const-int")) {
+ struct element *ci;
+
+ ci = mapGet(right, "const-int");
+ if ((ci == NULL) || (ci->type != ELEMENT_INTEGER)) {
+ debug("bad const-int");
+ return NULL;
+ }
+ val = ISC_TF(intValue(left) == intValue(ci));
+ } else if (right->type == ELEMENT_MAP)
+ return NULL;
+ else
+ val = ISC_FALSE;
+ } else
+ /* left is const-int */
+ if ((left->type == ELEMENT_MAP) && mapContains(left, "const-int")) {
+ if (right->type == ELEMENT_INTEGER) {
+ struct element *ci;
+
+ ci = mapGet(left, "const-int");
+ if ((ci == NULL) || (ci->type != ELEMENT_INTEGER)) {
+ debug("bad const-int");
+ return NULL;
+ }
+ val = ISC_TF(intValue(ci) == intValue(right));
+ } else if ((right->type == ELEMENT_MAP) &&
+ mapContains(right, "const-int")) {
+ struct element *lci;
+ struct element *rci;
+
+ lci = mapGet(left, "const-int");
+ rci = mapGet(right, "const-int");
+ if ((lci == NULL) || (lci->type != ELEMENT_INTEGER) ||
+ (rci == NULL) || (rci->type != ELEMENT_INTEGER)) {
+ debug("bad const-int");
+ return NULL;
+ }
+ val = ISC_TF(intValue(lci) == intValue(rci));
+ } else if (right->type == ELEMENT_MAP)
+ return NULL;
+ else
+ val = ISC_FALSE;
+ } else
+ /* right is numeric literal */
+ if (right->type == ELEMENT_INTEGER) {
+ if (left->type == ELEMENT_MAP)
+ return NULL;
+ else
+ val = ISC_FALSE;
+ } else
+ /* right is const-int */
+ if ((right->type == ELEMENT_MAP) && mapContains(right, "const-int")) {
+ if (left->type == ELEMENT_MAP)
+ return NULL;
+ else
+ val = ISC_FALSE;
+ } else
+ /* left is data literal */
+ if (left->type == ELEMENT_STRING) {
+ if (right->type == ELEMENT_STRING)
+ val = cmp_hexa(left, ISC_FALSE, right, ISC_FALSE);
+ else if ((right->type == ELEMENT_MAP) &&
+ mapContains(right, "const-data")) {
+ struct element *cd;
+
+ cd = mapGet(right, "const-data");
+ if ((cd == NULL) || (cd->type != ELEMENT_STRING)) {
+ debug("bad const-data");
+ return NULL;
+ }
+ val = cmp_hexa(left, ISC_FALSE, cd, ISC_TRUE);
+ } else if (right->type == ELEMENT_MAP)
+ return NULL;
+ else
+ val = ISC_FALSE;
+ } else
+ /* left is const-data */
+ if ((left->type == ELEMENT_MAP) && mapContains(left, "const-data")) {
+ if (right->type == ELEMENT_STRING) {
+ struct element *cd;
+
+ cd = mapGet(left, "const-data");
+ if ((cd == NULL) || (cd->type != ELEMENT_STRING)) {
+ debug("bad const-data");
+ return NULL;
+ }
+ val = cmp_hexa(cd, ISC_TRUE, right, ISC_FALSE);
+ } else if ((right->type == ELEMENT_MAP) &&
+ mapContains(right, "const-data")) {
+ struct element *lcd;
+ struct element *rcd;
+
+ lcd = mapGet(left, "const-data");
+ rcd = mapGet(right, "const-data");
+ if ((lcd == NULL) || (lcd->type != ELEMENT_STRING) ||
+ (rcd == NULL) || (rcd->type != ELEMENT_STRING)) {
+ debug("bad const-data");
+ return NULL;
+ }
+ val = cmp_hexa(lcd, ISC_TRUE, rcd, ISC_TRUE);
+ } else if (right->type == ELEMENT_MAP)
+ return NULL;
+ else
+ val = ISC_FALSE;
+ } else
+ /* right is data literal */
+ if (right->type == ELEMENT_STRING) {
+ if (left->type == ELEMENT_MAP)
+ return NULL;
+ else
+ val = ISC_FALSE;
+ } else
+ /* right is const-data */
+ if ((right->type == ELEMENT_MAP) && mapContains(right, "const-data")) {
+ if (left->type == ELEMENT_MAP)
+ return NULL;
+ else
+ val = ISC_FALSE;
+ } else
+ /* impossible cases */
+ if ((left->type != ELEMENT_MAP) || (right->type != ELEMENT_MAP)) {
+ debug("equal between unexpected %s and %s",
+ type2name(left->type), type2name(right->type));
+ val = ISC_FALSE;
+ } else
+ /* can't decide */
+ return NULL;
+
+ result = createBool(val);
+ TAILQ_CONCAT(&result->comments, &left->comments);
+ TAILQ_CONCAT(&result->comments, &right->comments);
+ return result;
+}
+
+static isc_boolean_t
+cmp_hexa(struct element *left, isc_boolean_t left_is_hexa,
+ struct element *right, isc_boolean_t right_is_hexa)
+{
+ struct string *sleft;
+ struct string *sright;
+
+ /* both are not hexa */
+ if (!left_is_hexa && !right_is_hexa) {
+ sleft = stringValue(left);
+ sright = stringValue(right);
+ /* eqString() compares lengths them use memcmp() */
+ return eqString(sleft, sright);
+ }
+
+ /* both are hexa */
+ if (left_is_hexa && right_is_hexa) {
+ sleft = stringValue(left);
+ sright = stringValue(right);
+ if (sleft->length != sright->length)
+ return ISC_FALSE;
+ if (sleft->length == 0) {
+ debug("empty const-data");
+ return ISC_TRUE;
+ }
+ return ISC_TF(strcasecmp(sleft->content,
+ sright->content) == 0);
+ }
+
+ /* put the hexa at left */
+ if (left_is_hexa) {
+ sleft = hexaValue(left);
+ sright = stringValue(right);
+ } else {
+ sleft = hexaValue(right);
+ sright = stringValue(left);
+ }
+
+ /* hexa is double length */
+ if (sleft->length != 2 * sright->length)
+ return ISC_FALSE;
+
+ /* build the hexa representation */
+ makeStringExt(sright->length, sright->content, 'X');
+
+ return ISC_TF(strcasecmp(sleft->content, sright->content) == 0);
+}
+
+static void
+debug(const char* fmt, ...)
+{
+ va_list list;
+
+ va_start(list, fmt);
+ vfprintf(stderr, fmt, list);
+ fprintf(stderr, "\n");
+ va_end(list);
+}
diff --git a/keama/json.c b/keama/json.c
new file mode 100644
index 00000000..0b2667a2
--- /dev/null
+++ b/keama/json.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2017 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+/* From Kea src/lib/cc/data.cc fromJSON() */
+
+#include "keama.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+struct element *
+json_parse(struct parse *cfile)
+{
+ struct element *elem;
+ const char *val;
+ enum dhcp_token token;
+
+ elem = create();
+ stackPush(cfile, elem);
+ cfile->stack[0] = elem;
+ cfile->stack_top = 0;
+
+ token = next_token(&val, NULL, cfile);
+ switch (token) {
+ case NUMBER:
+ elem = createInt(atoll(val));
+ TAILQ_CONCAT(&elem->comments, &cfile->comments);
+ break;
+ case STRING:
+ elem = createString(makeString(-1, val));
+ TAILQ_CONCAT(&elem->comments, &cfile->comments);
+ break;
+ case NAME:
+ if (strcmp(val, "null") == 0)
+ elem = createNull();
+ else if (strcmp(val, "true") == 0)
+ elem = createBool(ISC_TRUE);
+ else if (strcmp(val, "false") == 0) {
+ elem = createBool(ISC_FALSE);
+ elem->skip = ISC_TRUE;
+ } else
+ parse_error(cfile, "unknown name %s", val);
+ TAILQ_CONCAT(&elem->comments, &cfile->comments);
+ break;
+ case LBRACKET:
+ elem = json_list_parse(cfile);
+ break;
+ case LBRACE:
+ elem = json_map_parse(cfile);
+ break;
+ case END_OF_FILE:
+ parse_error(cfile, "unexpected end of file");
+ default:
+ parse_error(cfile, "unexpected %s", val);
+ }
+ return elem;
+}
+
+struct element *
+json_list_parse(struct parse *cfile)
+{
+ struct element *list;
+ struct element *item;
+ const char *val;
+ enum dhcp_token token;
+ isc_boolean_t done = ISC_FALSE;
+
+ list = createList();
+ TAILQ_CONCAT(&list->comments, &cfile->comments);
+ stackPush(cfile, list);
+ do {
+ token = peek_token(&val, NULL, cfile);
+ switch (token) {
+ case RBRACKET:
+ done = ISC_TRUE;
+ break;
+ case END_OF_FILE:
+ parse_error(cfile, "unexpected end of file");
+ case COMMA:
+ skip_token(&val, NULL, cfile);
+ if (listSize(list) == 0)
+ parse_error(cfile, "unexpected ','");
+ item = json_parse(cfile);
+ listPush(list, item);
+ break;
+ default:
+ if (listSize(list) > 0)
+ parse_error(cfile, "expected ','");
+ item = json_parse(cfile);
+ listPush(list, item);
+ break;
+ }
+ } while (!done);
+ skip_token(&val, NULL, cfile);
+ cfile->stack_top--;
+ return list;
+}
+
+struct element *
+json_map_parse(struct parse *cfile)
+{
+ struct element *map;
+ struct element *item;
+ const char *val;
+ const char *key;
+ enum dhcp_token token;
+ isc_boolean_t done = ISC_FALSE;
+
+ map = createMap();
+ TAILQ_CONCAT(&map->comments, &cfile->comments);
+ stackPush(cfile, map);
+ do {
+ token = peek_token(&val, NULL, cfile);
+ switch (token) {
+ case RBRACE:
+ done = ISC_TRUE;
+ break;
+ case END_OF_FILE:
+ parse_error(cfile, "unexpected end of file");
+ case COMMA:
+ skip_token(&val, NULL, cfile);
+ if (mapSize(map) == 0)
+ parse_error(cfile, "unexpected ','");
+ token = next_token(&val, NULL, cfile);
+ if (token != STRING)
+ parse_error(cfile, "unexpected %s, "
+ "expected \"key\":value", val);
+ key = strdup(val);
+ token = next_token(&val, NULL, cfile);
+ if (token != COLON)
+ parse_error(cfile, "unexpected %s, "
+ "expected ':'", val);
+ item = json_parse(cfile);
+ mapSet(map, item, key);
+ break;
+ case STRING:
+ skip_token(&val, NULL, cfile);
+ if (mapSize(map) > 0)
+ parse_error(cfile, "unexpected \"%s\", "
+ "expected ','", val);
+ key = strdup(val);
+ token = next_token(&val, NULL, cfile);
+ if (token != COLON)
+ parse_error(cfile, "unexpected %s, "
+ "expected ':'", val);
+ item = json_parse(cfile);
+ mapSet(map, item, key);
+ break;
+ default:
+ if (mapSize(map) == 0)
+ parse_error(cfile, "unexpected %s, "
+ "expected \"key\":value or '}'",
+ val);
+ else
+ parse_error(cfile, "unexpected %s, "
+ "expected ',' or '}'", val);
+ }
+ } while (!done);
+ skip_token(&val, NULL, cfile);
+ cfile->stack_top--;
+ return map;
+}
diff --git a/keama/keama.8 b/keama/keama.8
new file mode 100644
index 00000000..80a2823a
--- /dev/null
+++ b/keama/keama.8
@@ -0,0 +1,104 @@
+.\" keama.8
+.\"
+.\" Copyright (c) 2017, 2018 by Internet Systems Consortium, Inc. ("ISC")
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+.\" OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.\" Internet Systems Consortium, Inc.
+.\" 950 Charter Street
+.\" Redwood City, CA 94063
+.\" <info@isc.org>
+.\" https://www.isc.org/
+.\"
+.\" This software has been written for Internet Systems Consortium
+.\" by Ted Lemon in cooperation with Vixie Enterprises.
+.\"
+.\" Support and other services are available for ISC products - see
+.\" https://www.isc.org for more information or to learn more about ISC.
+.\"
+.TH keama 8
+.SH NAME
+keama - Kea Migration Assistant
+.SH SYNOPSIS
+.B keama
+[
+.B -4
+|
+.B -6]
+[
+.B -D
+]
+[
+.B -N
+]
+[
+.B -r
+.I {perform|fatal|pass}
+]
+[
+.B -l
+.I hook-library-path
+]
+[
+.B -i
+.I input-file
+]
+[
+.B -o
+.I output-file
+]
+.SH DESCRIPTION
+The Kea Migration Assistant converts an ISC DHCP configuration file into
+the corresponding Kea configuration file.
+.SH COMMAND LINE
+.PP
+\fIProtocol selection options:\fR
+.TP
+-4
+The input configuration is for DHCPv4. Incompatible with the \fB-6\fR
+option.
+.TP
+-6
+The input configuration is for DHCPv6. Incompatible with the \fB-4\fR
+option.
+.TP
+-D
+Define ISC DHCP minimum, default and maximum builtin lifetimes.
+.TP
+-N
+Instead of using global host reservations, put them in the matching subnet.
+.TP
+-r \fIaction\fR
+Specify what to do with hostnames: resolve them into their first address,
+raise a fatal error or pass them silently.
+.TP
+-p hook-library-path
+Specify the path where hook libraries (e.g. flex-id) can be found
+.TP
+-i input-file
+Specify the ISC DHCP configuration file to read. When it is not
+given the standard input is used.
+.TP
+-o output-file
+Specify the Kea configuration file to write. When it is not given
+the standard output is used.
+.PP
+The number of conversion failures is returned. Note that any parsing warning
+or error is fatal so please check and fix the ISC DHCP configuration file
+before using this tool.
+.SH SEE ALSO
+dhcpd(8), kea-dhcp4(8), kea-dhcp6(8).
+.SH AUTHOR
+.B keama(8)
+To learn more about Internet Systems Consortium, see
+.B https://www.isc.org
diff --git a/keama/keama.c b/keama/keama.c
new file mode 100644
index 00000000..f1b50da0
--- /dev/null
+++ b/keama/keama.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright(c) 2017, 2018 by Internet Systems Consortium, Inc.("ISC")
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include <sys/errno.h>
+#include <arpa/inet.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "keama.h"
+
+#define KEAMA_USAGE "Usage: keama [-4|-6] [-D] [-N]" \
+ " [-r {perform|fatal|pass}\\n" \
+ " [-l hook-library-path]" \
+ " [-i input-file] [-o output-file]\n"
+
+static void
+usage(const char *sfmt, const char *sarg) {
+ if (sfmt != NULL) {
+ fprintf(stderr, sfmt, sarg);
+ fprintf(stderr, "\n");
+ }
+ fputs(KEAMA_USAGE, stderr);
+ exit(1);
+}
+
+int local_family = 0;
+char *hook_library_path = NULL;
+char *input_file = NULL;
+char *output_file = NULL;
+FILE *input = NULL;
+FILE *output = NULL;
+isc_boolean_t use_isc_lifetimes = ISC_FALSE;
+isc_boolean_t global_hr = ISC_TRUE;
+isc_boolean_t json = ISC_FALSE;
+
+static const char use_noarg[] = "No argument for command: %s";
+static const char bad_resolve[] = "Bad -r argument: %s";
+
+int
+main(int argc, char **argv) {
+ int i, fd;
+ char *inbuf = NULL;
+ size_t oldsize = 0;
+ size_t newsize = 0;
+ ssize_t cc;
+ struct parse *cfile;
+ size_t cnt = 0;
+
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-4") == 0)
+ local_family = AF_INET;
+ else if (strcmp(argv[i], "-6") == 0)
+ local_family = AF_INET6;
+ else if (strcmp(argv[i], "-D") == 0)
+ use_isc_lifetimes = ISC_TRUE;
+ else if (strcmp(argv[i], "-N") == 0)
+ global_hr = ISC_FALSE;
+ else if (strcmp(argv[i], "-T") == 0)
+ json = ISC_TRUE;
+ else if (strcmp(argv[i], "-r") == 0) {
+ if (++i == argc)
+ usage(use_noarg, argv[i - 1]);
+ if (strcmp(argv[i], "perform") == 0)
+ resolve = perform;
+ else if (strcmp(argv[i], "fatal") == 0)
+ resolve = fatal;
+ else if (strcmp(argv[i], "pass") == 0)
+ resolve = pass;
+ else
+ usage(bad_resolve, argv[i]);
+ } else if (strcmp(argv[i], "-l") == 0) {
+ if (++i == argc)
+ usage(use_noarg, argv[i - 1]);
+ hook_library_path = argv[i];
+ } else if (strcmp(argv[i], "-i") == 0) {
+ if (++i == argc)
+ usage(use_noarg, argv[i - 1]);
+ input_file = argv[i];
+ } else if (strcmp(argv[i], "-o") == 0) {
+ if (++i == argc)
+ usage(use_noarg, argv[i - 1]);
+ output_file = argv[i];
+ } else
+ usage("Unknown command: %s", argv[i]);
+ }
+
+ if (!json && (local_family == 0))
+ usage("address family must be set using %s", "-4 or -6");
+
+ if (input_file == NULL) {
+ input_file = "--stdin--";
+ fd = fileno(stdin);
+ for (;;) {
+ if (newsize == 0)
+ newsize = 1024;
+ else {
+ oldsize = newsize;
+ newsize *= 4;
+ }
+ inbuf = (char *)realloc(inbuf, newsize);
+ if (inbuf == 0)
+ usage("out of memory reading standard "
+ "input: %s", strerror(errno));
+ cc = read(fd, inbuf + oldsize, newsize - oldsize);
+ if (cc < 0)
+ usage("error reading standard input: %s",
+ strerror(errno));
+ if (cc + oldsize < newsize) {
+ newsize = cc + oldsize;
+ break;
+ }
+ }
+ } else {
+ fd = open(input_file, O_RDONLY);
+ if (fd < 0)
+ usage("Cannot open '%s' for reading", input_file);
+ }
+
+ if (output_file) {
+ output = fopen(output_file, "w");
+ if (output == NULL)
+ usage("Cannot open '%s' for writing", output_file);
+ } else
+ output = stdout;
+
+ TAILQ_INIT(&parses);
+ cfile = new_parse(fd, inbuf, newsize, input_file, 0);
+ assert(cfile != NULL);
+
+ if (json) {
+ struct element *elem;
+
+ elem = json_parse(cfile);
+ if (elem != NULL) {
+ print(output, elem, 0, 0);
+ fprintf(output, "\n");
+ }
+ } else {
+ spaces_init();
+ options_init();
+ cnt = conf_file_parse(cfile);
+ if (cfile->stack_top > 0) {
+ print(output, cfile->stack[0], 0, 0);
+ fprintf(output, "\n");
+ }
+ }
+
+ end_parse(cfile);
+
+ exit(cnt);
+}
+
+void
+stackPush(struct parse *pc, struct element *elem)
+{
+ if (pc->stack_top + 2 >= pc->stack_size) {
+ size_t new_size = pc->stack_size + 10;
+ size_t amount = new_size * sizeof(struct element *);
+
+ pc->stack = (struct element **)realloc(pc->stack, amount);
+ if (pc->stack == NULL)
+ parse_error(pc, "can't resize element stack");
+ pc->stack_size = new_size;
+ }
+ pc->stack_top++;
+ pc->stack[pc->stack_top] = elem;
+}
+
+void
+parse_error(struct parse *cfile, const char *fmt, ...)
+{
+ va_list list;
+ char lexbuf[256];
+ char mbuf[1024];
+ char fbuf[1024];
+ unsigned i, lix;
+
+ snprintf(fbuf, sizeof(fbuf), "%s line %d: %s",
+ cfile->tlname, cfile->lexline, fmt);
+
+ va_start(list, fmt);
+ vsnprintf(mbuf, sizeof(mbuf), fbuf, list);
+ va_end(list);
+
+ lix = 0;
+ for (i = 0;
+ cfile->token_line[i] && i < (cfile->lexchar - 1); i++) {
+ if (lix < sizeof(lexbuf) - 1)
+ lexbuf[lix++] = ' ';
+ if (cfile->token_line[i] == '\t') {
+ for (; lix < (sizeof lexbuf) - 1 && (lix & 7); lix++)
+ lexbuf[lix] = ' ';
+ }
+ }
+ lexbuf[lix] = 0;
+
+ fprintf(stderr, "%s\n%s\n", mbuf, cfile->token_line);
+ if (cfile->lexchar < 81)
+ fprintf(stderr, "%s^\n", lexbuf);
+ exit(-1);
+}
diff --git a/keama/keama.h b/keama/keama.h
new file mode 100644
index 00000000..19593ad7
--- /dev/null
+++ b/keama/keama.h
@@ -0,0 +1,470 @@
+/*
+ * Copyright (c) 2017, 2018 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#ifndef EOL
+#define EOL '\n'
+#endif
+
+#include "data.h"
+#include "dhctoken.h"
+
+#include <time.h>
+
+/* Resolution of FQDNs into IPv4 addresses */
+enum resolve {
+ perform = 0, /* resolve */
+ fatal, /* raise a fatal error */
+ pass /* pass the string wth a warning */
+} resolve;
+
+/* From includes/dhcp.h */
+
+#define HTYPE_ETHER 1
+#define HTYPE_IEEE802 6
+#define HTYPE_FDDI 8
+
+#define DHO_DHCP_SERVER_IDENTIFIER 54
+#define DHO_VENDOR_CLASS_IDENTIFIER 60
+#define DHO_USER_CLASS 77
+#define DHO_VIVSO_SUBOPTIONS 125
+
+/* From includes/dhcp6.h */
+#define D6O_VENDOR_OPTS 17
+#define MAX_V6RELAY_HOPS 32
+
+/* From includes/dhcpd.h */
+
+extern int local_family;
+
+/* A parsing context. */
+
+TAILQ_HEAD(parses, parse) parses;
+
+struct parse {
+ int lexline;
+ int lexchar;
+ char *token_line;
+ char *prev_line;
+ char *cur_line;
+ const char *tlname;
+ int eol_token;
+
+ /*
+ * In order to give nice output when we have a parsing error
+ * in our file, we keep track of where we are in the line so
+ * that we can show the user.
+ *
+ * We need to keep track of two lines, because we can look
+ * ahead, via the "peek" function, to the next line sometimes.
+ *
+ * The "line1" and "line2" variables act as buffers for this
+ * information. The "lpos" variable tells us where we are in the
+ * line.
+ *
+ * When we "put back" a character from the parsing context, we
+ * do not want to have the character appear twice in the error
+ * output. So, we set a flag, the "ugflag", which the
+ * get_char() function uses to check for this condition.
+ */
+ char line1[81];
+ char line2[81];
+ int lpos;
+ int line;
+ int tlpos;
+ int tline;
+ enum dhcp_token token;
+ int ugflag;
+ char *tval;
+ int tlen;
+ char tokbuf[1500];
+
+ int warnings_occurred;
+ int file;
+ char *inbuf;
+ size_t bufix, buflen;
+ size_t bufsiz;
+
+ /*
+ * Additions for the Kea Migration Assistant.
+ */
+ struct element **stack;
+ size_t stack_size;
+ size_t stack_top;
+ size_t issue_counter;
+
+ /* don't save below this */
+ struct comments comments;
+
+ /* TAILQ_NEXT(self) is the saved state */
+ TAILQ_ENTRY(parse) next;
+
+};
+
+#define PARAMETER 0
+#define TOPLEVEL 1
+#define ROOT_GROUP 2
+#define HOST_DECL 3
+#define SHARED_NET_DECL 4
+#define SUBNET_DECL 5
+#define CLASS_DECL 6
+#define GROUP_DECL 7
+#define POOL_DECL 8
+
+/* Used as an argument to parse_class_decl() */
+#define CLASS_TYPE_VENDOR 0
+#define CLASS_TYPE_USER 1
+#define CLASS_TYPE_CLASS 2
+#define CLASS_TYPE_SUBCLASS 3
+
+#define CLASS_DECL_DELETED 1
+#define CLASS_DECL_DYNAMIC 2
+#define CLASS_DECL_STATIC 4
+#define CLASS_DECL_SUBCLASS 8
+
+/* Hardware buffer size */
+#define HARDWARE_ADDR_LEN 20
+
+/* Expression context */
+
+enum expression_context {
+ context_any, /* indefinite */
+ context_boolean,
+ context_data,
+ context_numeric,
+ context_dns,
+ context_data_or_numeric, /* indefinite */
+ context_function
+};
+
+/* Statements */
+
+enum statement_op {
+ null_statement,
+ if_statement,
+ add_statement,
+ eval_statement,
+ break_statement,
+ default_option_statement,
+ supersede_option_statement,
+ append_option_statement,
+ prepend_option_statement,
+ send_option_statement,
+ statements_statement,
+ on_statement,
+ switch_statement,
+ case_statement,
+ default_statement,
+ set_statement,
+ unset_statement,
+ let_statement,
+ define_statement,
+ log_statement,
+ return_statement,
+ execute_statement,
+ vendor_opt_statement
+};
+
+/* Expression tree structure. */
+
+enum expr_op {
+ expr_none,
+ expr_match,
+ expr_check,
+ expr_equal,
+ expr_substring,
+ expr_suffix,
+ expr_concat,
+ expr_host_lookup,
+ expr_and,
+ expr_or,
+ expr_not,
+ expr_option,
+ expr_hardware,
+ expr_hw_type, /* derived from expr_hardware */
+ expr_hw_address, /* derived from expr_hardware */
+ expr_packet,
+ expr_const_data,
+ expr_extract_int8,
+ expr_extract_int16,
+ expr_extract_int32,
+ expr_encode_int8,
+ expr_encode_int16,
+ expr_encode_int32,
+ expr_const_int,
+ expr_exists,
+ expr_encapsulate,
+ expr_known,
+ expr_reverse,
+ expr_leased_address,
+ expr_binary_to_ascii,
+ expr_config_option,
+ expr_host_decl_name,
+ expr_pick_first_value,
+ expr_lease_time,
+ expr_dns_transaction,
+ expr_static,
+ expr_ns_add,
+ expr_ns_delete,
+ expr_ns_exists,
+ expr_ns_not_exists,
+ expr_not_equal,
+ expr_null,
+ expr_variable_exists,
+ expr_variable_reference,
+ expr_filename,
+ expr_sname,
+ expr_arg,
+ expr_funcall,
+ expr_function,
+ expr_add,
+ expr_subtract,
+ expr_multiply,
+ expr_divide,
+ expr_remainder,
+ expr_binary_and,
+ expr_binary_or,
+ expr_binary_xor,
+ expr_client_state,
+ expr_ucase,
+ expr_lcase,
+ expr_regex_match,
+ expr_iregex_match,
+ expr_gethostname,
+ expr_v6relay,
+ expr_concat_dclist
+};
+
+/* options */
+
+enum option_status {
+ kea_unknown = 0, /* known only by ISC DHCP */
+ isc_dhcp_unknown = 1, /* known only by Kea */
+ known = 2, /* known by both ISC DHCP and Kea */
+ special = 3, /* requires special processing */
+ dynamic = 4 /* dynamic entry */
+};
+
+struct option_def { /* ISC DHCP option definition */
+ const char *name; /* option name */
+ const char *format; /* format string */
+ const char *space; /* space (aka universe) */
+ unsigned code; /* code point */
+ enum option_status status; /* status */
+};
+
+struct space_def { /* ISC DHCP space definition */
+ const char *old; /* ISC DHCP space name */
+ const char *name; /* Kea space name */
+ enum option_status status; /* status */
+};
+
+struct space {
+ const char *old; /* ISC DHCP space name */
+ const char *name; /* Kea space name */
+ enum option_status status; /* status */
+ struct element *vendor; /* vendor option */
+ TAILQ_ENTRY(space) next; /* next space */
+};
+
+struct option {
+ const char *old; /* ISC DHCP option name */
+ const char *name; /* Kea option name */
+ const char *format; /* ISC DHCP format string */
+ const struct space *space; /* space (aka universe) */
+ unsigned code; /* code point */
+ enum option_status status; /* status */
+ TAILQ_ENTRY(option) next; /* next option */
+};
+
+/* Kea parse tools */
+void stackPush(struct parse *cfile, struct element *elem);
+
+/* From command line */
+extern char *hook_library_path;
+extern isc_boolean_t use_isc_lifetimes;
+extern isc_boolean_t global_hr;
+
+/* From common/parse.c */
+void parse_error(struct parse *, const char *, ...)
+ __attribute__((__format__(__printf__,2,3)))
+ __attribute__((noreturn));
+
+/* conflex.c */
+struct parse *new_parse(int, char *, size_t, const char *, int);
+void end_parse(struct parse *);
+void save_parse_state(struct parse *);
+void restore_parse_state(struct parse *);
+enum dhcp_token next_token(const char **, unsigned *, struct parse *);
+enum dhcp_token peek_token(const char **, unsigned *, struct parse *);
+enum dhcp_token next_raw_token(const char **, unsigned *, struct parse *);
+enum dhcp_token peek_raw_token(const char **, unsigned *, struct parse *);
+/*
+ * Use skip_token when we are skipping a token we have previously
+ * used peek_token on as we know what the result will be in this case.
+ */
+#define skip_token(a,b,c) ((void) next_token((a),(b),(c)))
+
+/* confparse.c */
+size_t conf_file_parse(struct parse *);
+void read_conf_file(struct parse *, const char *, int);
+size_t conf_file_subparse(struct parse *, int);
+isc_boolean_t parse_statement(struct parse *, int, isc_boolean_t);
+void get_permit(struct parse *, struct element *);
+void parse_pool_statement(struct parse *, int);
+void parse_lbrace(struct parse *);
+void parse_host_declaration(struct parse *);
+void parse_class_declaration(struct parse *, int);
+void parse_shared_net_declaration(struct parse *);
+void parse_subnet_declaration(struct parse *);
+void parse_subnet6_declaration(struct parse *);
+void parse_group_declaration(struct parse *);
+void close_group(struct parse *, struct element *);
+struct element *parse_fixed_addr_param(struct parse *, enum dhcp_token);
+void parse_address_range(struct parse *, int, size_t);
+void parse_address_range6(struct parse *, int, size_t);
+void parse_prefix6(struct parse *, int, size_t);
+void parse_fixed_prefix6(struct parse *, size_t);
+void parse_pool6_statement(struct parse *, int);
+struct element *parse_allow_deny(struct parse *, int);
+void parse_server_duid_conf(struct parse *);
+void parse_directive(struct parse *);
+void parse_option_space_dir(struct parse *);
+void parse_option_code_dir(struct parse *, struct option *);
+void parse_option_status_dir(struct parse *, struct option *, enum dhcp_token);
+void parse_option_local_dir(struct parse *, struct option *);
+void parse_option_define_dir(struct parse *, struct option *);
+
+/* parse.c */
+void skip_to_semi(struct parse *);
+void skip_to_rbrace(struct parse *, int);
+void parse_semi(struct parse *);
+void parse_string(struct parse *, char **, unsigned *);
+struct string *parse_host_name(struct parse *);
+struct string *parse_ip_addr_or_hostname(struct parse *, isc_boolean_t);
+struct string *parse_ip_addr(struct parse *);
+struct string *parse_ip6_addr(struct parse *);
+struct string *parse_ip6_addr_txt(struct parse *);
+struct element *parse_hardware_param(struct parse *);
+void parse_lease_time(struct parse *, time_t *);
+struct string *parse_numeric_aggregate(struct parse *,
+ unsigned char *, unsigned *,
+ int, int, unsigned);
+void convert_num(struct parse *, unsigned char *, const char *,
+ int, unsigned);
+struct option *parse_option_name(struct parse *, isc_boolean_t,
+ isc_boolean_t *);
+void parse_option_space_decl(struct parse *);
+void parse_option_code_definition(struct parse *, struct option *);
+void parse_vendor_code_definition(struct parse *, struct option *);
+struct string *convert_format(const char *, isc_boolean_t *, isc_boolean_t *);
+struct string *parse_base64(struct parse *);
+struct string *parse_cshl(struct parse *);
+struct string *parse_hexa(struct parse *);
+isc_boolean_t parse_executable_statements(struct element *,
+ struct parse *, isc_boolean_t *,
+ enum expression_context);
+isc_boolean_t parse_executable_statement(struct element *,
+ struct parse *, isc_boolean_t *,
+ enum expression_context,
+ isc_boolean_t);
+isc_boolean_t parse_zone(struct element *, struct parse *);
+isc_boolean_t parse_key(struct element *, struct parse *);
+isc_boolean_t parse_on_statement(struct element *, struct parse *,
+ isc_boolean_t *);
+isc_boolean_t parse_switch_statement(struct element *, struct parse *,
+ isc_boolean_t *);
+isc_boolean_t parse_case_statement(struct element *, struct parse *,
+ isc_boolean_t *, enum expression_context);
+isc_boolean_t parse_if_statement(struct element *, struct parse *,
+ isc_boolean_t *);
+isc_boolean_t parse_boolean_expression(struct element *, struct parse *,
+ isc_boolean_t *);
+/* currently unused */
+isc_boolean_t parse_boolean(struct parse *);
+isc_boolean_t parse_data_expression(struct element *, struct parse *,
+ isc_boolean_t *);
+isc_boolean_t numeric_expression(struct element *, struct parse *,
+ isc_boolean_t *);
+isc_boolean_t parse_non_binary(struct element *, struct parse *,
+ isc_boolean_t *, enum expression_context);
+isc_boolean_t parse_expression(struct element *, struct parse *,
+ isc_boolean_t *, enum expression_context,
+ struct element *, enum expr_op);
+struct string *escape_option_string(unsigned, const char *,
+ isc_boolean_t *, isc_boolean_t *);
+isc_boolean_t parse_option_data(struct element *, struct parse *,
+ struct option *);
+isc_boolean_t parse_option_binary(struct element *, struct parse *,
+ struct option *, isc_boolean_t);
+struct string * parse_option_textbin(struct parse *, struct option *);
+isc_boolean_t parse_option_statement(struct element *, struct parse *,
+ struct option *, enum statement_op);
+isc_boolean_t parse_config_data(struct element *, struct parse *,
+ struct option *);
+isc_boolean_t parse_config_statement(struct element *, struct parse *,
+ struct option *, enum statement_op);
+struct string *parse_option_token(struct parse *, const char *,
+ isc_boolean_t *, isc_boolean_t *,
+ isc_boolean_t *);
+struct string *parse_option_token_binary(struct parse *, const char *);
+struct string *parse_domain_list(struct parse *, isc_boolean_t);
+isc_boolean_t is_boolean_expression(struct element *);
+isc_boolean_t is_data_expression(struct element *);
+isc_boolean_t is_numeric_expression(struct element *);
+int expr_precedence(enum expr_op, struct element *);
+
+/* options.c */
+void spaces_init(void);
+void options_init(void);
+struct space *space_lookup(const char *);
+struct option *option_lookup_name(const char *, const char *);
+struct option *kea_lookup_name(const char *, const char *);
+struct option *option_lookup_code(const char *, unsigned);
+void push_space(struct space *);
+void push_option(struct option *);
+void add_option_data(struct element *, struct element *);
+void merge_option_data(struct element *, struct element *);
+struct comments *get_config_comments(unsigned);
+const char *display_status(enum option_status);
+
+/* json.c */
+struct element *json_parse(struct parse *);
+struct element *json_list_parse(struct parse *);
+struct element *json_map_parse(struct parse *);
+
+/* print.c */
+const char *print_expression(struct element *, isc_boolean_t *);
+const char *print_boolean_expression(struct element *, isc_boolean_t *);
+const char *print_data_expression(struct element *, isc_boolean_t *);
+const char *print_numeric_expression(struct element *, isc_boolean_t *);
+
+/* reduce.c */
+struct element *reduce_boolean_expression(struct element *);
+struct element *reduce_data_expression(struct element *);
+struct element *reduce_numeric_expression(struct element *);
+
+/* eval */
+struct element *eval_expression(struct element *, isc_boolean_t *);
+struct element *eval_boolean_expression(struct element *, isc_boolean_t *);
+struct element *eval_data_expression(struct element *, isc_boolean_t *);
+struct element *eval_numeric_expression(struct element *, isc_boolean_t *);
diff --git a/keama/options.c b/keama/options.c
new file mode 100644
index 00000000..12fa5ddf
--- /dev/null
+++ b/keama/options.c
@@ -0,0 +1,1154 @@
+/*
+ * Copyright (c) 2017, 2018 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "keama.h"
+
+TAILQ_HEAD(spaces, space) spaces;
+TAILQ_HEAD(options, option) options;
+
+/* From common/tables.c */
+
+/* Additional format codes:
+
+ x - ISC DHCP and Kea string
+ Y - force full binary
+ u - undefined (parsed as X)
+*/
+
+/// SPACES
+struct space_def space_defs[] = {
+ { "dhcp", "dhcp4", 2},
+ { "nwip", "nwip", 0},
+ { "agent", "dhcp-agent-options-space", 2},
+ { "vendor-class", "_vivco_", 0},
+ { "vendor", "_vivso_", 3},
+ { "isc", "_isc_", 0},
+ { "", "vendor-encapsulated-options-space", 1},
+ { "_docsis3_", "vendor-4491", 1},
+ { "dhcp6", "dhcp6", 2},
+ { "vsio", "_vendor-opts-space_", 3},
+ { "_vsio_", "vendor-opts-space", 1},
+ { "isc6", "_isc6_", 0},
+ { "_rsoo_", "rsoo-opts", 1},
+ { "_isc6_", "vendor-2495", 1},
+ { "server", "_server_", 0},
+ { NULL, NULL, 0}
+};
+
+/// DHCPv4
+struct option_def options4[] = {
+ { "subnet-mask", "I", "dhcp", 1, 2},
+ { "time-offset", "l", "dhcp", 2, 2},
+ { "routers", "Ia", "dhcp", 3, 2},
+ { "time-servers", "Ia", "dhcp", 4, 2},
+ { "ien116-name-servers", "Ia", "dhcp", 5, 2},
+ /// ien116-name-servers -> name-servers
+ { "domain-name-servers", "Ia", "dhcp", 6, 2},
+ { "log-servers", "Ia", "dhcp", 7, 2},
+ { "cookie-servers", "Ia", "dhcp", 8, 2},
+ { "lpr-servers", "Ia", "dhcp", 9, 2},
+ { "impress-servers", "Ia", "dhcp", 10, 2},
+ { "resource-location-servers", "Ia", "dhcp", 11, 2},
+ { "host-name", "t", "dhcp", 12, 2},
+ { "boot-size", "S", "dhcp", 13, 2},
+ { "merit-dump", "t", "dhcp", 14, 2},
+ { "domain-name", "t", "dhcp", 15, 2},
+ { "swap-server", "I", "dhcp", 16, 2},
+ { "root-path", "t", "dhcp", 17, 2},
+ { "extensions-path", "t", "dhcp", 18, 2},
+ { "ip-forwarding", "f", "dhcp", 19, 2},
+ { "non-local-source-routing", "f", "dhcp", 20, 2},
+ { "policy-filter", "IIa", "dhcp", 21, 2},
+ { "max-dgram-reassembly", "S", "dhcp", 22, 2},
+ { "default-ip-ttl", "B", "dhcp", 23, 2},
+ { "path-mtu-aging-timeout", "L", "dhcp", 24, 2},
+ { "path-mtu-plateau-table", "Sa", "dhcp", 25, 2},
+ { "interface-mtu", "S", "dhcp", 26, 2},
+ { "all-subnets-local", "f", "dhcp", 27, 2},
+ { "broadcast-address", "I", "dhcp", 28, 2},
+ { "perform-mask-discovery", "f", "dhcp", 29, 2},
+ { "mask-supplier", "f", "dhcp", 30, 2},
+ { "router-discovery", "f", "dhcp", 31, 2},
+ { "router-solicitation-address", "I", "dhcp", 32, 2},
+ { "static-routes", "IIa", "dhcp", 33, 2},
+ { "trailer-encapsulation", "f", "dhcp", 34, 2},
+ { "arp-cache-timeout", "L", "dhcp", 35, 2},
+ { "ieee802-3-encapsulation", "f", "dhcp", 36, 2},
+ { "default-tcp-ttl", "B", "dhcp", 37, 2},
+ { "tcp-keepalive-interval", "L", "dhcp", 38, 2},
+ { "tcp-keepalive-garbage", "f", "dhcp", 39, 2},
+ { "nis-domain", "t", "dhcp", 40, 2},
+ { "nis-servers", "Ia", "dhcp", 41, 2},
+ { "ntp-servers", "Ia", "dhcp", 42, 2},
+ { "vendor-encapsulated-options", "E.", "dhcp", 43, 2},
+ { "netbios-name-servers", "Ia", "dhcp", 44, 2},
+ { "netbios-dd-server", "Ia", "dhcp", 45, 2},
+ { "netbios-node-type", "B", "dhcp", 46, 2},
+ { "netbios-scope", "t", "dhcp", 47, 2},
+ { "font-servers", "Ia", "dhcp", 48, 2},
+ { "x-display-manager", "Ia", "dhcp", 49, 2},
+ { "dhcp-requested-address", "I", "dhcp", 50, 2},
+ { "dhcp-lease-time", "L", "dhcp", 51, 2},
+ { "dhcp-option-overload", "B", "dhcp", 52, 2},
+ { "dhcp-message-type", "B", "dhcp", 53, 2},
+ { "dhcp-server-identifier", "I", "dhcp", 54, 2},
+ { "dhcp-parameter-request-list", "Ba", "dhcp", 55, 2},
+ { "dhcp-message", "t", "dhcp", 56, 2},
+ { "dhcp-max-message-size", "S", "dhcp", 57, 2},
+ { "dhcp-renewal-time", "L", "dhcp", 58, 2},
+ { "dhcp-rebinding-time", "L", "dhcp", 59, 2},
+ { "vendor-class-identifier", "x", "dhcp", 60, 2},
+ { "dhcp-client-identifier", "X", "dhcp", 61, 2},
+ { "nwip-domain", "t", "dhcp", 62, 2},
+ /// nwip-domain nwip-domain-name
+ { "nwip-suboptions", "Enwip.", "dhcp", 63, 2},
+ { "nisplus-domain", "t", "dhcp", 64, 2},
+ /// nisplus-domain nisplus-domain-name
+ { "nisplus-servers", "Ia", "dhcp", 65, 2},
+ { "tftp-server-name", "t", "dhcp", 66, 2},
+ { "bootfile-name", "t", "dhcp", 67, 2},
+ /// bootfile-name boot-file-name
+ { "mobile-ip-home-agent", "Ia", "dhcp", 68, 2},
+ { "smtp-server", "Ia", "dhcp", 69, 2},
+ { "pop-server", "Ia", "dhcp", 70, 2},
+ { "nntp-server", "Ia", "dhcp", 71, 2},
+ { "www-server", "Ia", "dhcp", 72, 2},
+ { "finger-server", "Ia", "dhcp", 73, 2},
+ { "irc-server", "Ia", "dhcp", 74, 2},
+ { "streettalk-server", "Ia", "dhcp", 75, 2},
+ { "streettalk-directory-assistance-server", "Ia",
+ "dhcp", 76, 2},
+ { "user-class", "tY", "dhcp", 77, 2},
+ { "slp-directory-agent", "fIa", "dhcp", 78, 2},
+ { "slp-service-scope", "fto", "dhcp", 79, 2},
+ /* 80 is the zero-length rapid-commit (RFC 4039) */
+ { "fqdn", "Efqdn.", "dhcp", 81, 2},
+ { "relay-agent-information", "Eagent.", "dhcp", 82, 2},
+ /// relay-agent-information dhcp-agent-options
+ /* 83 is iSNS (RFC 4174) */
+ /* 84 is unassigned */
+ { "nds-servers", "Ia", "dhcp", 85, 2},
+ { "nds-tree-name", "t", "dhcp", 86, 2},
+ { "nds-context", "t", "dhcp", 87, 2},
+ { "bcms-controller-names", "D", "dhcp", 88, 2},
+ { "bcms-controller-address", "Ia", "dhcp", 89, 2},
+ { "authenticate", "X", "dhcp", 90, 1},
+ /// not supported by ISC DHCP
+ { "client-last-transaction-time", "L", "dhcp", 91, 2},
+ { "associated-ip", "Ia", "dhcp", 92, 2},
+ { "pxe-system-type", "Sa", "dhcp", 93, 2},
+ // pxe-system-type client-system
+ { "pxe-interface-id", "BBB", "dhcp", 94, 2},
+ // pxe-interface-id client-ndi
+ { "pxe-client-id", "BX", "dhcp", 97, 2},
+ // pxe-client-id uuid-guid
+ { "uap-servers", "t", "dhcp", 98, 2},
+ { "geoconf-civic", "X", "dhcp", 99, 2},
+ { "pcode", "t", "dhcp", 100, 2},
+ { "tcode", "t", "dhcp", 101, 2},
+ { "netinfo-server-address", "Ia", "dhcp", 112, 2},
+ { "netinfo-server-tag", "t", "dhcp", 113, 2},
+ { "default-url", "t", "dhcp", 114, 2},
+ { "auto-config", "B", "dhcp", 116, 2},
+ { "name-service-search", "Sa", "dhcp", 117, 2},
+ { "subnet-selection", "I", "dhcp", 118, 2},
+ { "domain-search", "Dc", "dhcp", 119, 2},
+ { "vivco", "Evendor-class.", "dhcp", 124, 2},
+ /// vivco vivco-suboptions
+ { "vivso", "Evendor.", "dhcp", 125, 2},
+ /// vivso vivso-suboptions
+ {"pana-agent", "Ia", "dhcp", 136, 2},
+ {"v4-lost", "d", "dhcp", 137, 2},
+ {"capwap-ac-v4", "Ia", "dhcp", 138, 2},
+ { "sip-ua-cs-domains", "Dc", "dhcp", 141, 2},
+ { "ipv4-address-andsf", "Ia", "dhcp", 142, 0},
+ /// not supported by Kea
+ { "rdnss-selection", "BIID", "dhcp", 146, 2},
+ { "tftp-server-address", "Ia", "dhcp", 150, 0},
+ /// not supported by Kea
+ { "v4-portparams", "BBS", "dhcp", 159, 2},
+ { "v4-captive-portal", "t", "dhcp", 160, 2},
+ { "option-6rd", "BB6Ia", "dhcp", 212, 2},
+ {"v4-access-domain", "d", "dhcp", 213, 2},
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+/// DHCPv6
+struct option_def options6[] = {
+ { "client-id", "X", "dhcp6", 1, 2},
+ /// client-id clientid
+ { "server-id", "X", "dhcp6", 2, 2},
+ /// server-id serverid
+ { "ia-na", "X", "dhcp6", 3, 2},
+ { "ia-ta", "X", "dhcp6", 4, 2},
+ { "ia-addr", "X", "dhcp6", 5, 2},
+ /// ia-addr iaaddr
+ { "oro", "Sa", "dhcp6", 6, 2},
+ { "preference", "B", "dhcp6", 7, 2},
+ { "elapsed-time", "S", "dhcp6", 8, 2},
+ { "relay-msg", "X", "dhcp6", 9, 2},
+ /// 10 is unassigned
+ { "auth", "X", "dhcp6", 11, 1},
+ /// not supported by ISC DHCP
+ { "unicast", "6", "dhcp6", 12, 2},
+ { "status-code", "Nstatus-codes.to", "dhcp6", 13, 2},
+ { "rapid-commit", "Z", "dhcp6", 14, 2},
+ { "user-class", "X", "dhcp6", 15, 1},
+ /// not supported by ISC DHCP
+ { "vendor-class", "LX", "dhcp6", 16, 1},
+ /// not supported by ISC DHCP
+ { "vendor-opts", "Evsio.", "dhcp6", 17, 2},
+ { "interface-id", "X", "dhcp6", 18, 2},
+ { "reconf-msg", "Ndhcpv6-messages.", "dhcp6", 19, 2},
+ { "reconf-accept", "Z", "dhcp6", 20, 2},
+ { "sip-servers-names", "D", "dhcp6", 21, 2},
+ /// sip-servers-names sip-server-dns
+ { "sip-servers-addresses", "6a", "dhcp6", 22, 2},
+ /// sip-servers-addresses sip-server-addr
+ { "name-servers", "6a", "dhcp6", 23, 2},
+ /// name-servers dns-servers
+ { "domain-search", "D", "dhcp6", 24, 2},
+ { "ia-pd", "X", "dhcp6", 25, 2},
+ { "ia-prefix", "X", "dhcp6", 26, 2},
+ /// ia-prefix iaprefix
+ { "nis-servers", "6a", "dhcp6", 27, 2},
+ { "nisp-servers", "6a", "dhcp6", 28, 2},
+ { "nis-domain-name", "D", "dhcp6", 29, 2},
+ { "nisp-domain-name", "D", "dhcp6", 30, 2},
+ { "sntp-servers", "6a", "dhcp6", 31, 2},
+ { "info-refresh-time", "T", "dhcp6", 32, 2},
+ /// info-refresh-time information-refresh-time
+ { "bcms-server-d", "D", "dhcp6", 33, 2},
+ /// bcms-server-d bcms-server-dns
+ { "bcms-server-a", "6a", "dhcp6", 34, 2},
+ /// bcms-server-a bcms-server-addr
+ /* Note that 35 is not assigned. */
+ { "geoconf-civic", "X", "dhcp6", 36, 2},
+ { "remote-id", "X", "dhcp6", 37, 2},
+ { "subscriber-id", "X", "dhcp6", 38, 2},
+ { "fqdn", "Efqdn6-if-you-see-me-its-a-bug-bug-bug.",
+ "dhcp6", 39, 2},
+ /// fqdn client-fqdn
+ { "pana-agent", "6a", "dhcp6", 40, 2},
+ { "new-posix-timezone", "t", "dhcp6", 41, 2},
+ { "new-tzdb-timezone", "t", "dhcp6", 42, 2},
+ { "ero", "Sa", "dhcp6", 43, 2},
+ { "lq-query", "X", "dhcp6", 44, 2},
+ { "client-data", "X", "dhcp6", 45, 2},
+ { "clt-time", "L", "dhcp6", 46, 2},
+ { "lq-relay-data", "6X", "dhcp6", 47, 2},
+ { "lq-client-link", "6a", "dhcp6", 48, 2},
+ { "v6-lost", "d", "dhcp6", 51, 2},
+ { "capwap-ac-v6", "6a", "dhcp6", 52, 2},
+ { "relay-id", "X", "dhcp6", 53, 2},
+ { "v6-access-domain", "d", "dhcp6", 57, 2},
+ { "sip-ua-cs-list", "D", "dhcp6", 58, 2},
+ { "bootfile-url", "t", "dhcp6", 59, 2},
+ { "bootfile-param", "X", "dhcp6", 60, 2},
+ { "client-arch-type", "Sa", "dhcp6", 61, 2},
+ { "nii", "BBB", "dhcp6", 62, 2},
+ { "aftr-name", "d", "dhcp6", 64, 2},
+ { "erp-local-domain-name", "d", "dhcp6", 65, 2},
+ { "rsoo", "Ersoo.", "dhcp6", 66, 1},
+ /// not supported by ISC DHCP
+ { "pd-exclude", "X", "dhcp6", 67, 1},
+ /// not supported by ISC DHCP (prefix6 format)
+ { "rdnss-selection", "6BD", "dhcp6", 74, 2},
+ { "client-linklayer-addr", "X", "dhcp6", 79, 2},
+ { "link-address", "6", "dhcp6", 80, 2},
+ { "solmax-rt", "L", "dhcp6", 82, 2},
+ { "inf-max-rt", "L", "dhcp6", 83, 2},
+ { "dhcpv4-msg", "X", "dhcp6", 87, 2},
+ /// dhcpv4-msg dhcpv4-message
+ { "dhcp4-o-dhcp6-server", "6a", "dhcp6", 88, 2},
+ /// dhcp4-o-dhcp6-server dhcp4o6-server-addr
+ { "v6-captive-portal", "t", "dhcp6", 103, 2},
+ { "relay-source-port", "S", "dhcp6", 135, 2},
+ { "ipv6-address-andsf", "6a", "dhcp6", 143, 2},
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+/// DHCPv4 AGENT
+struct option_def agents[] = {
+ /// All not supported by Kea
+ { "circuit-id", "X", "agent", 1, 0},
+ { "remote-id", "X", "agent", 2, 0},
+ { "agent-id", "I", "agent", 3, 0},
+ { "DOCSIS-device-class", "L", "agent", 4, 0},
+ { "link-selection", "I", "agent", 5, 0},
+ { "relay-port", "Z", "agent", 19, 0},
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+/// SERVER
+struct option_def configs[] = {
+ { "default-lease-time", "T", "server", 1, 3},
+ { "max-lease-time", "T", "server", 2, 3},
+ { "min-lease-time", "T", "server", 3, 3},
+ { "dynamic-bootp-lease-cutoff", "T", "server", 4, 0},
+ { "dynamic-bootp-lease-length", "L", "server", 5, 0},
+ { "boot-unknown-clients", "f", "server", 6, 0},
+ { "dynamic-bootp", "f", "server", 7, 0},
+ { "allow-bootp", "f", "server", 8, 0},
+ { "allow-booting", "f", "server", 9, 0},
+ { "one-lease-per-client", "f", "server", 10, 0},
+ { "get-lease-hostnames", "f", "server", 11, 0},
+ { "use-host-decl-names", "f", "server", 12, 0},
+ { "use-lease-addr-for-default-route", "f",
+ "server", 13, 0},
+ { "min-secs", "B", "server", 14, 0},
+ { "filename", "t", "server", 15, 3},
+ { "server-name", "t", "server", 16, 3},
+ { "next-server", "I", "server", 17, 3},
+ { "authoritative", "f", "server", 18, 3},
+ { "vendor-option-space", "U", "server", 19, 3},
+ { "always-reply-rfc1048", "f", "server", 20, 0},
+ { "site-option-space", "X", "server", 21, 3},
+ { "always-broadcast", "f", "server", 22, 0},
+ { "ddns-domainname", "t", "server", 23, 3},
+ { "ddns-hostname", "t", "server", 24, 0},
+ { "ddns-rev-domainname", "t", "server", 25, 0},
+ { "lease-file-name", "t", "server", 26, 0},
+ { "pid-file-name", "t", "server", 27, 0},
+ { "duplicates", "f", "server", 28, 0},
+ { "declines", "f", "server", 29, 0},
+ { "ddns-updates", "f", "server", 30, 3},
+ { "omapi-port", "S", "server", 31, 0},
+ { "local-port", "S", "server", 32, 0},
+ { "limited-broadcast-address", "I", "server", 33, 0},
+ { "remote-port", "S", "server", 34, 0},
+ { "local-address", "I", "server", 35, 0},
+ { "omapi-key", "d", "server", 36, 0},
+ { "stash-agent-options", "f", "server", 37, 0},
+ { "ddns-ttl", "T", "server", 38, 0},
+ { "ddns-update-style", "Nddns-styles.", "server", 39, 3},
+ { "client-updates", "f", "server", 40, 0},
+ { "update-optimization", "f", "server", 41, 0},
+ { "ping-check", "f", "server", 42, 0},
+ { "update-static-leases", "f", "server", 43, 0},
+ { "log-facility", "Nsyslog-facilities.",
+ "server", 44, 0},
+ { "do-forward-updates", "f", "server", 45, 0},
+ { "ping-timeout", "T", "server", 46, 0},
+ { "infinite-is-reserved", "f", "server", 47, 0},
+ { "update-conflict-detection", "f", "server", 48, 0},
+ { "leasequery", "f", "server", 49, 0},
+ { "adaptive-lease-time-threshold", "B", "server", 50, 0},
+ { "do-reverse-updates", "f", "server", 51, 0},
+ { "fqdn-reply", "f", "server", 52, 0},
+ { "preferred-lifetime", "T", "server", 53, 3},
+ { "dhcpv6-lease-file-name", "t", "server", 54, 0},
+ { "dhcpv6-pid-file-name", "t", "server", 55, 0},
+ { "limit-addrs-per-ia", "L", "server", 56, 0},
+ { "limit-prefs-per-ia", "L", "server", 57, 0},
+ { "delayed-ack", "S", "server", 58, 0},
+ { "max-ack-delay", "L", "server", 59, 0},
+ /* LDAP */
+ { "dhcp-cache-threshold", "B", "server", 78, 0},
+ { "dont-use-fsync", "f", "server", 79, 0},
+ { "ddns-local-address4", "I", "server", 80, 0},
+ { "ddns-local-address6", "6", "server", 81, 0},
+ { "ignore-client-uids", "f", "server", 82, 3},
+ { "log-threshold-low", "B", "server", 83, 0},
+ { "log-threshold-high", "B", "server", 84, 0},
+ { "echo-client-id", "f", "server", 85, 3},
+ { "server-id-check", "f", "server", 86, 0},
+ { "prefix-length-mode", "Nprefix_length_modes.",
+ "server", 87, 0},
+ { "dhcpv6-set-tee-times", "f", "server", 88, 0},
+ { "abandon-lease-time", "T", "server", 89, 0},
+ { "use-eui-64", "f", "server", 90, 0},
+ { "check-secs-byte-order", "f", "server", 91, 0},
+ { "persist-eui-64-leases", "f", "server", 92, 0},
+ { "ddns-dual-stack-mixed-mode", "f", "server", 93, 0},
+ { "ddns-guard-id-must-match", "f", "server", 94, 0},
+ { "ddns-other-guard-is-dynamic", "f", "server", 95, 0},
+ { "release-on-roam", "f", "server", 96, 0},
+ { "local-address6", "6", "server", 97, 0},
+ { "bind-local-address6", "f", "server", 98, 0},
+ { "ping-cltt-secs", "T", "server", 99, 0},
+ { "ping-timeout-ms", "T", "server", 100, 0},
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+void
+spaces_init(void)
+{
+ struct space_def *def;
+ struct space *space;
+
+ TAILQ_INIT(&spaces);
+
+ /* Fill spaces */
+ for (def = space_defs; def->name != NULL; def++) {
+ space = (struct space *)malloc(sizeof(*space));
+ assert(space != NULL);
+ memset(space, 0, sizeof(*space));
+ space->old = def->old;
+ space->name = def->name;
+ space->status = def->status;
+ TAILQ_INSERT_TAIL(&spaces, space);
+ }
+}
+
+void
+options_init(void)
+{
+ struct option_def *def;
+ struct option *option;
+
+ TAILQ_INIT(&options);
+
+ /* Fill DHCPv4 options */
+ for (def = options4; def->name != NULL; def++) {
+ option = (struct option *)malloc(sizeof(*option));
+ assert(option != NULL);
+ memset(option, 0, sizeof(*option));
+ option->old = def->name;
+ switch (def->code) {
+ case 5:
+ option->name = "name-servers";
+ break;
+ case 62:
+ option->name = "nwip-domain-name";
+ break;
+ case 64:
+ option->name = "nisplus-domain-name";
+ break;
+ case 67:
+ option->name = "boot-file-name";
+ break;
+ case 82:
+ option->name = "dhcp-agent-options";
+ break;
+ case 93:
+ option->name = "client-system";
+ break;
+ case 94:
+ option->name = "client-ndi";
+ break;
+ case 97:
+ option->name = "uuid-guid";
+ break;
+ case 124:
+ option->name = "vivco-suboptions";
+ break;
+ case 125:
+ option->name = "vivso-suboptions";
+ break;
+ default:
+ option->name = def->name;
+ }
+ option->format = def->format;
+ option->space = space_lookup(def->space);
+ assert(option->space != NULL);
+ option->code = def->code;
+ option->status = def->status;
+ TAILQ_INSERT_TAIL(&options, option);
+ }
+
+ /* Fill DHCPv6 options */
+ for (def = options6; def->name != NULL; def++) {
+ option = (struct option *)malloc(sizeof(*option));
+ assert(option != NULL);
+ memset(option, 0, sizeof(*option));
+ option->old = def->name;
+ switch (def->code) {
+ case 1:
+ option->name = "clientid";
+ break;
+ case 2:
+ option->name = "serverid";
+ break;
+ case 5:
+ option->name = "iaaddr";
+ break;
+ case 21:
+ option->name = "sip-server-dns";
+ break;
+ case 22:
+ option->name = "sip-server-addr";
+ break;
+ case 23:
+ option->name = "dns-servers";
+ break;
+ case 26:
+ option->name = "iaprefix";
+ break;
+ case 32:
+ option->name = "information-refresh-time";
+ break;
+ case 33:
+ option->name = "bcms-server-dns";
+ break;
+ case 34:
+ option->name = "bcms-server-addr ";
+ break;
+ case 39:
+ option->name = "client-fqdn";
+ break;
+ case 87:
+ option->name = "dhcpv4-message";
+ break;
+ case 88:
+ option->name = "dhcp4o6-server-addr";
+ break;
+ default:
+ option->name = def->name;
+ break;
+ }
+ option->format = def->format;
+ option->space = space_lookup(def->space);
+ assert(option->space != NULL);
+ option->code = def->code;
+ option->status = def->status;
+ TAILQ_INSERT_TAIL(&options, option);
+ }
+
+ /* Fill agent options */
+ for (def = agents; def->name != NULL; def++) {
+ option = (struct option *)malloc(sizeof(*option));
+ assert(option != NULL);
+ memset(option, 0, sizeof(*option));
+ option->old = def->name;
+ option->name = def->name;
+ option->format = def->format;
+ option->space = space_lookup(def->space);
+ assert(option->space != NULL);
+ option->code = def->code;
+ option->status = def->status;
+ TAILQ_INSERT_TAIL(&options, option);
+ }
+
+ /* Fill server config options */
+ for (def = configs; def->name != NULL; def++) {
+ option = (struct option *)malloc(sizeof(*option));
+ assert(option != NULL);
+ memset(option, 0, sizeof(*option));
+ option->old = def->name;
+ option->name = def->name;
+ option->format = def->format;
+ option->space = space_lookup(def->space);
+ assert(option->space != NULL);
+ option->code = def->code;
+ option->status = def->status;
+ TAILQ_INSERT_TAIL(&options, option);
+ }
+}
+
+struct space *
+space_lookup(const char *name)
+{
+ struct space *space;
+
+ TAILQ_FOREACH(space, &spaces) {
+ if (space->status == isc_dhcp_unknown)
+ continue;
+ if (strcmp(name, space->old) == 0)
+ return space;
+ }
+ return NULL;
+}
+
+struct option *
+option_lookup_name(const char *space, const char *name)
+{
+ struct space *universe;
+ struct option *option;
+
+ universe = space_lookup(space);
+ if (universe == NULL)
+ return NULL;
+ TAILQ_FOREACH(option, &options) {
+ if (option->status == isc_dhcp_unknown)
+ continue;
+ if (universe != option->space)
+ continue;
+ if (strcmp(name, option->old) == 0)
+ return option;
+ }
+ return NULL;
+}
+
+struct option *
+kea_lookup_name(const char *space, const char *name)
+{
+ struct space *universe;
+ struct option *option;
+
+ TAILQ_FOREACH(universe, &spaces) {
+ if (universe->status == kea_unknown)
+ continue;
+ if (strcmp(name, universe->name) == 0)
+ break;
+ }
+ if (universe == NULL)
+ return NULL;
+ TAILQ_FOREACH(option, &options) {
+ if (option->status == kea_unknown)
+ continue;
+ if (universe != option->space)
+ continue;
+ if (strcmp(name, option->name) == 0)
+ return option;
+ }
+ return NULL;
+}
+
+struct option *
+option_lookup_code(const char *space, unsigned code)
+{
+ struct space *universe;
+ struct option *option;
+
+ universe = space_lookup(space);
+ if (universe == NULL)
+ return NULL;
+ TAILQ_FOREACH(option, &options) {
+ if (universe != option->space)
+ continue;
+ if (code == option->code)
+ return option;
+ }
+ return NULL;
+}
+
+void
+push_space(struct space *space)
+{
+ space->status = dynamic;
+ TAILQ_INSERT_TAIL(&spaces, space);
+}
+
+void
+push_option(struct option *option)
+{
+ assert(option->space != NULL);
+ option->old = option->name;
+ option->status = dynamic;
+ TAILQ_INSERT_TAIL(&options, option);
+}
+
+void
+add_option_data(struct element *src, struct element *dst)
+{
+ struct string *sspace;
+ struct element *scode;
+ struct element *name;
+ struct option *option;
+ size_t i;
+
+ sspace = stringValue(mapGet(src, "space"));
+ scode = mapGet(src, "code");
+ name = mapGet(src, "name");
+ assert((scode != NULL) || (name != NULL));
+
+ /* We'll use the code so fill it even it should always be available */
+ if (scode == NULL) {
+ option = kea_lookup_name(sspace->content,
+ stringValue(name)->content);
+ assert(option != NULL);
+ scode = createInt(option->code);
+ mapSet(src, scode, "code");
+ }
+ assert(intValue(scode) != 0);
+
+ for (i = 0; i < listSize(dst); i++) {
+ struct element *od;
+ struct element *space;
+ struct element *code;
+
+ od = listGet(dst, i);
+ space = mapGet(od, "space");
+ if (!eqString(sspace, stringValue(space)))
+ continue;
+ code = mapGet(od, "code");
+ if (code == NULL) {
+ name = mapGet(od, "name");
+ assert(name != NULL);
+ option = kea_lookup_name(sspace->content,
+ stringValue(name)->content);
+ assert(option != NULL);
+ code = createInt(option->code);
+ mapSet(od, code, "code");
+ }
+ /* check if the option is already present */
+ if (intValue(scode) == intValue(code))
+ return;
+ }
+ listPush(dst, copy(src));
+}
+
+void
+merge_option_data(struct element *src, struct element *dst)
+{
+ struct element *od;
+ size_t i;
+
+ for (i = 0; i < listSize(src); i++) {
+ od = listGet(src, i);
+ add_option_data(od, dst);
+ }
+}
+
+struct comments *
+get_config_comments(unsigned code)
+{
+ static struct comments comments;
+ struct comment *comment = NULL;
+
+ TAILQ_INIT(&comments);
+ switch (code) {
+ case 4: /* dynamic-bootp-lease-cutoff */
+ case 5: /* dynamic-bootp-lease-length */
+ case 6: /* boot-unknown-clients */
+ case 7: /* dynamic-bootp */
+ case 8: /* allow-bootp */
+ no_bootp:
+ comment = createComment("/// bootp protocol is not supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 9: /* allow-booting */
+ comment = createComment("/// allow-booting is not supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// no concrete usage known?");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Reference Kea #239");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 10: /* one-lease-per-client */
+ comment = createComment("/// one-lease-per-client is not "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Reference Kea #238");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 11: /* get-lease-hostnames */
+ comment = createComment("/// get-lease-hostnames is not "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Reference Kea #240");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 12: /* use-host-decl-names */
+ comment = createComment("/// use-host-decl-names defaults "
+ "to always on");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 13: /* use-lease-addr-for-default-route */
+ comment = createComment("/// use-lease-addr-for-default-route "
+ "is obsolete");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 14: /* min-secs */
+ comment = createComment("/// min-secs is not (yet?) "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Reference Kea #223");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 20: /* always-reply-rfc1048 */
+ goto no_bootp;
+
+ case 22: /* always-broadcast */
+ comment = createComment("/// always-broadcast is not "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Reference Kea #241");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 24: /* ddns-hostname */
+ comment = createComment("/// ddns-hostname is not supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Please use hostname in a "
+ "host reservation instead");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 25: /* ddns-rev-domainname */
+ comment = createComment("/// ddns-rev-domainname is an "
+ "obsolete (so not supported) feature");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 26: /* lease-file-name */
+ comment = createComment("/// lease-file-name is an internal "
+ "ISC DHCP feature");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 27: /* pid-file-name */
+ comment = createComment("/// pid-file-nam is an internal "
+ "ISC DHCP feature");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 28: /* duplicates */
+ comment = createComment("/// duplicates is not supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Kea model is different (and "
+ "stricter)");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 29: /* declines */
+ comment = createComment("/// declines is not supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Kea honors decline messages "
+ " and holds address for "
+ "decline-probation-period");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 31: /* omapi-port */
+ comment = createComment("/// omapi-port is an internal "
+ "ISC DHCP feature");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 32: /* local-port */
+ comment = createComment("/// local-port is not supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// command line -p parameter "
+ "should be used instead");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 33: /* limited-broadcast-address */
+ comment = createComment("/// limited-broadcast-address "
+ "is not (yet?) supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Reference Kea #224");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 34: /* remote-port */
+ comment = createComment("/// remote-port is a not portable "
+ "(so not supported) feature");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 35: /* local-address */
+ comment = createComment("/// local-address is not supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Kea equivalent feature is "
+ "to specify an interface address");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 36: /* omapi-key */
+ comment = createComment("/// omapi-key is an internal "
+ "ISC DHCP feature");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 37: /* stash-agent-options */
+ comment = createComment("/// stash-agent-options is not "
+ "(yet?) supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Reference Kea #218");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 38: /* ddns-ttl */
+ comment = createComment("/// ddns-ttl is a D2 not (yet?) "
+ "supported feature");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Reference Kea #225");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 40: /* client-updates */
+ comment = createComment("/// ddns-ttl client-updates is "
+ "not supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Kea model is very different");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 41: /* update-optimization */
+ comment = createComment("/// update-optimization is not "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Kea follows RFC 4702");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 42: /* ping-check */
+ comment = createComment("/// ping-check is not supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ no_ping:
+ comment = createComment("/// Kea has no ping probing");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 43: /* update-static-leases */
+ comment = createComment("/// update-static-leases is an "
+ "obsolete feature");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 44: /* log-facility */
+ comment = createComment("/// log-facility is not supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Please use the "
+ "KEA_LOGGER_DESTINATION environment "
+ "variable instead");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 45: /* do-forward-updates */
+ comment = createComment("/// do-forward-updates is not "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ ddns_updates:
+ comment = createComment("/// Kea model is equivalent but "
+ "different");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 46: /* ping-timeout */
+ comment = createComment("/// ping-timeout is not supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ goto no_ping;
+
+ case 47: /* infinite-is-reserved */
+ comment = createComment("/// infinite-is-reserved is not "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Kea does not support reserved "
+ "leases");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 48: /* update-conflict-detection */
+ comment = createComment("/// update-conflict-detection is not "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// DDNS is handled by the D2 "
+ "server using a dedicated config");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 49: /* leasequery */
+ comment = createComment("/// leasequery is not supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Kea does not (yet) support "
+ "the leasequery protocol");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 50: /* adaptive-lease-time-threshold */
+ comment = createComment("/// adaptive-lease-time-threshold is "
+ "not supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Reference Kea #226");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 51: /* do-reverse-updates */
+ comment = createComment("/// do-reverse-updates is not "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ goto ddns_updates;
+
+ case 52: /* fqdn-reply */
+ comment = createComment("/// fqdn-reply is an obsolete "
+ "feature");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 54: /* dhcpv6-lease-file-name */
+ comment = createComment("/// dhcpv6-lease-file-name "
+ "is an internal ISC DHCP feature");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 55: /* dhcpv6-pid-file-name */
+ comment = createComment("/// dhcpv6-pid-file-name "
+ "is an internal ISC DHCP feature");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 56: /* limit-addrs-per-ia */
+ comment = createComment("/// limit-addrs-per-ia "
+ "is not (yet?) supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ limit_resources:
+ comment = createComment("/// Reference Kea #227");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 57: /* limit-prefs-per-ia */
+ comment = createComment("/// limit-prefs-per-ia"
+ "is not (yet?) supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ goto limit_resources;
+
+ case 58: /* delayed-ack */
+ case 59: /* max-ack-delay */
+ comment = createComment("/// delayed ack no supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 78: /* dhcp-cache-threshold */
+ comment = createComment("/// dhcp-cache-threshold "
+ "is not (yet?) supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Reference Kea #228");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 79: /* dont-use-fsync */
+ comment = createComment("/// dont-use-fsync is an internal "
+ "ISC DHCP feature");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 80: /* ddns-local-address4 */
+ comment = createComment("/// ddns-local-address4 is not "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ d2_ip_address:
+ comment = createComment("/// Kea D2 equivalent config is "
+ "ip-address");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 81: /* ddns-local-address6 */
+ comment = createComment("/// ddns-local-address6 is not "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ goto d2_ip_address;
+
+ case 83: /* log-threshold-low */
+ comment = createComment("/// log-threshold-low is not (yet?) "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ log_threshold:
+ comment = createComment("/// Reference Kea #222");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 84: /* log-threshold-high */
+ comment = createComment("/// log-threshold-high is not (yet?) "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ goto log_threshold;
+
+ case 86: /* server-id-check */
+ comment = createComment("/// server-id-check is not (yet?) "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Reference Kea #242");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+
+ case 87: /* prefix-length-mode */
+ comment = createComment("/// prefix-length-mode is not "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Kea model is different (and "
+ "simpler?)");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+ case 88: /* dhcpv6-set-tee-times */
+ comment = createComment("/// dhcpv6-set-tee-times is a "
+ "transitional (so not supported) "
+ "feature");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// T1 and T2 are .5 and .8 times "
+ "preferred-lifetime");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+ case 89: /* abandon-lease-time */
+ comment = createComment("/// abandon-lease-time is not "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Kea support equivalent (and "
+ "richer) expired-lease-processing "
+ "and decline-probation-period");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+ case 90: /* use-eui-64 */
+ comment = createComment("/// EUI-64 is not (yet) supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Reference Kea #265");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+ case 96: /* release-on-roam */
+ comment = createComment("/// release-on-roam is not (yet) "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Reference Kea #266");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+ case 97: /* local-address6 */
+ comment = createComment("/// local-address6 is not supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ comment = createComment("/// Kea equivalent feature is "
+ "to specify an interface address");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ break;
+ case 99: /* ping-cltt-secs */
+ comment = createComment("/// ping-cltt-secs is not supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ goto no_ping;
+ case 100: /* ping-timeout-ms */
+ comment = createComment("/// ping-timeout-ms is not "
+ "supported");
+ TAILQ_INSERT_TAIL(&comments, comment);
+ goto no_ping;
+ }
+ return &comments;
+}
+
+const char *
+display_status(enum option_status status)
+{
+ switch (status) {
+ case kea_unknown:
+ case special:
+ return "known (unknown)";
+ case isc_dhcp_unknown:
+ return "unknown (known)";
+ case known:
+ return "known (known)";
+ case dynamic:
+ return "dynamic (dynamic)";
+ default:
+ return "??? (" "???" ")";
+ }
+}
diff --git a/keama/parse.c b/keama/parse.c
new file mode 100644
index 00000000..3596d5d5
--- /dev/null
+++ b/keama/parse.c
@@ -0,0 +1,6140 @@
+/*
+ * Copyright (c) 2017, 2018 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "keama.h"
+
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void config_min_valid_lifetime(struct element *, struct parse *);
+static void config_def_valid_lifetime(struct element *, struct parse *);
+static void config_max_valid_lifetime(struct element *, struct parse *);
+static void config_file(struct element *, struct parse *);
+static void config_sname(struct element *, struct parse *);
+static void config_next_server(struct element *, struct parse *);
+static void config_vendor_option_space(struct element *, struct parse *);
+static void config_site_option_space(struct element *, struct parse *);
+static struct element *default_qualifying_suffix(void);
+static void config_qualifying_suffix(struct element *, struct parse *);
+static void config_enable_updates(struct element *, struct parse *);
+static void config_ddns_update_style(struct element *, struct parse *);
+static void config_preferred_lifetime(struct element *, struct parse *);
+static void config_match_client_id(struct element *, struct parse *);
+static void config_echo_client_id(struct element *, struct parse *);
+
+/*
+static uint32_t getULong(const unsigned char *buf);
+static int32_t getLong(const unsigned char *buf);
+static uint32_t getUShort(const unsigned char *buf);
+static int32_t getShort(const unsigned char *buf);
+static uint32_t getUChar(const unsigned char *obuf);
+*/
+static void putULong(unsigned char *obuf, uint32_t val);
+static void putLong(unsigned char *obuf, int32_t val);
+static void putUShort(unsigned char *obuf, uint32_t val);
+static void putShort(unsigned char *obuf, int32_t val);
+/*
+static void putUChar(unsigned char *obuf, uint32_t val);
+*/
+
+/*
+static isc_boolean_t is_compound_expression(struct element *);
+*/
+static enum expression_context op_context(enum expr_op);
+static int op_val(enum expr_op);
+static int op_precedence(enum expr_op, enum expr_op);
+static enum expression_context expression_context(struct element *);
+static enum expr_op expression(struct element *);
+
+/* Skip to the semicolon ending the current statement. If we encounter
+ braces, the matching closing brace terminates the statement.
+*/
+void
+skip_to_semi(struct parse *cfile)
+{
+ skip_to_rbrace(cfile, 0);
+}
+
+/* Skips everything from the current point upto (and including) the given
+ number of right braces. If we encounter a semicolon but haven't seen a
+ left brace, consume it and return.
+ This lets us skip over:
+
+ statement;
+ statement foo bar { }
+ statement foo bar { statement { } }
+ statement}
+
+ ...et cetera. */
+void
+skip_to_rbrace(struct parse *cfile, int brace_count)
+{
+ enum dhcp_token token;
+ const char *val;
+
+ do {
+ token = peek_token(&val, NULL, cfile);
+ if (token == RBRACE) {
+ if (brace_count > 0) {
+ --brace_count;
+ }
+
+ if (brace_count == 0) {
+ /* Eat the brace and return. */
+ skip_token(&val, NULL, cfile);
+ return;
+ }
+ } else if (token == LBRACE) {
+ brace_count++;
+ } else if (token == SEMI && (brace_count == 0)) {
+ /* Eat the semicolon and return. */
+ skip_token(&val, NULL, cfile);
+ return;
+ } else if (token == EOL) {
+ /* EOL only happens when parsing /etc/resolv.conf,
+ and we treat it like a semicolon because the
+ resolv.conf file is line-oriented. */
+ skip_token(&val, NULL, cfile);
+ return;
+ }
+
+ /* Eat the current token */
+ token = next_token(&val, NULL, cfile);
+ } while (token != END_OF_FILE);
+}
+
+void
+parse_semi(struct parse *cfile)
+{
+ enum dhcp_token token;
+ const char *val;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != SEMI)
+ parse_error(cfile, "semicolon expected.");
+}
+
+/* string-parameter :== STRING SEMI */
+
+void
+parse_string(struct parse *cfile, char **sptr, unsigned *lptr)
+{
+ const char *val;
+ enum dhcp_token token;
+ char *s;
+ unsigned len;
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING)
+ parse_error(cfile, "expecting a string");
+ s = (char *)malloc(len + 1);
+ parse_error(cfile, "no memory for string %s.", val);
+ memcpy(s, val, len + 1);
+
+ parse_semi(cfile);
+ if (sptr)
+ *sptr = s;
+ else
+ free(s);
+ if (lptr)
+ *lptr = len;
+}
+
+/*
+ * hostname :== IDENTIFIER
+ * | IDENTIFIER DOT
+ * | hostname DOT IDENTIFIER
+ */
+
+struct string *
+parse_host_name(struct parse *cfile)
+{
+ const char *val;
+ enum dhcp_token token;
+ struct string *s = NULL;
+
+ /* Read a dotted hostname... */
+ do {
+ /* Read a token, which should be an identifier. */
+ token = peek_token(&val, NULL, cfile);
+ if (!is_identifier(token) && token != NUMBER)
+ break;
+ skip_token(&val, NULL, cfile);
+
+ /* Store this identifier... */
+ if (s == NULL)
+ s = makeString(-1, val);
+ else
+ appendString(s, val);
+ /* Look for a dot; if it's there, keep going, otherwise
+ we're done. */
+ token = peek_token(&val, NULL, cfile);
+ if (token == DOT) {
+ token = next_token(&val, NULL, cfile);
+ appendString(s, val);
+ }
+ } while (token == DOT);
+
+ return s;
+}
+
+/* ip-addr-or-hostname :== ip-address | hostname
+ ip-address :== NUMBER DOT NUMBER DOT NUMBER DOT NUMBER
+
+ Parse an ip address or a hostname.
+
+ Note that RFC1123 permits hostnames to consist of all digits,
+ making it difficult to quickly disambiguate them from ip addresses.
+*/
+
+struct string *
+parse_ip_addr_or_hostname(struct parse *cfile, isc_boolean_t check_multi)
+{
+ const char *val;
+ enum dhcp_token token;
+ unsigned char addr[4];
+ unsigned len = sizeof(addr);
+ isc_boolean_t ipaddr = ISC_FALSE;
+ struct string *bin = NULL;
+
+ token = peek_token(&val, NULL, cfile);
+ if (token == NUMBER) {
+ /*
+ * a hostname may be numeric, but domain names must
+ * start with a letter, so we can disambiguate by
+ * looking ahead a few tokens. we save the parse
+ * context first, and restore it after we know what
+ * we're dealing with.
+ */
+ save_parse_state(cfile);
+ skip_token(NULL, NULL, cfile);
+ if (next_token(NULL, NULL, cfile) == DOT &&
+ next_token(NULL, NULL, cfile) == NUMBER)
+ ipaddr = ISC_TRUE;
+ restore_parse_state(cfile);
+
+ if (ipaddr)
+ bin = parse_numeric_aggregate(cfile, addr, &len,
+ DOT, 10, 8);
+ }
+
+ if ((bin == NULL) && (is_identifier(token) || token == NUMBER)) {
+ struct string *name;
+ struct hostent *h;
+
+ name = parse_host_name(cfile);
+ if (name == NULL)
+ return NULL;
+
+ if (resolve == fatal)
+ parse_error(cfile, "expected IPv4 address. got "
+ "hostname %s", name->content);
+ else if (resolve == pass)
+ return name;
+
+ /* from do_host_lookup */
+ h = gethostbyname(name->content);
+ if ((h == NULL) || (h->h_addr_list[0] == NULL))
+ parse_error(cfile, "%s: host unknown.", name->content);
+ if (check_multi && h->h_addr_list[1]) {
+ struct comment *comment;
+ char msg[128];
+
+ snprintf(msg, sizeof(msg),
+ "/// %s resolves into multiple addresses",
+ name->content);
+ comment = createComment(msg);
+ TAILQ_INSERT_TAIL(&cfile->comments, comment);
+ }
+ bin = makeString(4, h->h_addr_list[0]);
+ }
+
+ if (bin == NULL) {
+ if (token != RBRACE && token != LBRACE)
+ token = next_token(&val, NULL, cfile);
+ parse_error(cfile, "%s (%d): expecting IP address or hostname",
+ val, token);
+ }
+
+ return makeStringExt(bin->length, bin->content, 'I');
+}
+
+/*
+ * ip-address :== NUMBER DOT NUMBER DOT NUMBER DOT NUMBER
+ */
+
+struct string *
+parse_ip_addr(struct parse *cfile)
+{
+ unsigned char addr[4];
+ unsigned len = sizeof(addr);
+
+ return parse_numeric_aggregate(cfile, addr, &len, DOT, 10, 8);
+}
+
+/*
+ * Return true if every character in the string is hexadecimal.
+ */
+static isc_boolean_t
+is_hex_string(const char *s)
+{
+ while (*s != '\0') {
+ if (!isxdigit((int)*s)) {
+ return ISC_FALSE;
+ }
+ s++;
+ }
+ return ISC_TRUE;
+}
+
+/*
+ * ip-address6 :== (complicated set of rules)
+ *
+ * See section 2.2 of RFC 1884 for details.
+ *
+ * We are lazy for this. We pull numbers, names, colons, and dots
+ * together and then throw the resulting string at the inet_pton()
+ * function.
+ */
+
+struct string *
+parse_ip6_addr(struct parse *cfile)
+{
+ enum dhcp_token token;
+ const char *val;
+ char addr[16];
+ int val_len;
+ char v6[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+ int v6_len;
+
+ /*
+ * First token is non-raw. This way we eat any whitespace before
+ * our IPv6 address begins, like one would expect.
+ */
+ token = peek_token(&val, NULL, cfile);
+
+ /*
+ * Gather symbols.
+ */
+ v6_len = 0;
+ for (;;) {
+ if ((((token == NAME) || (token == NUMBER_OR_NAME)) &&
+ is_hex_string(val)) ||
+ (token == NUMBER) ||
+ (token == TOKEN_ADD) ||
+ (token == DOT) ||
+ (token == COLON)) {
+
+ next_raw_token(&val, NULL, cfile);
+ val_len = strlen(val);
+ if ((v6_len + val_len) >= sizeof(v6))
+ parse_error(cfile, "Invalid IPv6 address.");
+ memcpy(v6+v6_len, val, val_len);
+ v6_len += val_len;
+
+ } else {
+ break;
+ }
+ token = peek_raw_token(&val, NULL, cfile);
+ }
+ v6[v6_len] = '\0';
+
+ /*
+ * Use inet_pton() for actual work.
+ */
+ if (inet_pton(AF_INET6, v6, addr) <= 0)
+ parse_error(cfile, "Invalid IPv6 address.");
+ return makeString(16, addr);
+}
+
+/*
+ * Same as parse_ip6_addr() above, but returns the value as a text
+ * rather than in an address binary structure.
+ */
+struct string *
+parse_ip6_addr_txt(struct parse *cfile)
+{
+ const struct string *bin;
+
+ bin = parse_ip6_addr(cfile);
+ return makeStringExt(bin->length, bin->content, '6');
+}
+
+/*
+ * hardware-parameter :== HARDWARE hardware-type colon-separated-hex-list SEMI
+ * hardware-type :== ETHERNET | TOKEN_RING | TOKEN_FDDI | INFINIBAND
+ * Note that INFINIBAND may not be useful for some items, such as classification
+ * as the hardware address won't always be available.
+ */
+
+struct element *
+parse_hardware_param(struct parse *cfile)
+{
+ const char *val;
+ enum dhcp_token token;
+ isc_boolean_t ether = ISC_FALSE;
+ unsigned hlen;
+ struct string *t, *r;
+ struct element *hw;
+
+ token = next_token(&val, NULL, cfile);
+ if (token == ETHERNET)
+ ether = ISC_TRUE;
+ else {
+ r = makeString(-1, val);
+ appendString(r, " ");
+ }
+
+ /* Parse the hardware address information. Technically,
+ it would make a lot of sense to restrict the length of the
+ data we'll accept here to the length of a particular hardware
+ address type. Unfortunately, there are some broken clients
+ out there that put bogus data in the chaddr buffer, and we accept
+ that data in the lease file rather than simply failing on such
+ clients. Yuck. */
+ hlen = 0;
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI)
+ parse_error(cfile, "empty hardware address");
+ t = parse_numeric_aggregate(cfile, NULL, &hlen, COLON, 16, 8);
+ if (t == NULL)
+ parse_error(cfile, "can't get hardware address");
+ if (hlen > HARDWARE_ADDR_LEN)
+ parse_error(cfile, "hardware address too long");
+ token = next_token(&val, NULL, cfile);
+ if (token != SEMI)
+ parse_error(cfile, "expecting semicolon.");
+ if (ether)
+ r = makeStringExt(hlen, t->content, 'H');
+ else
+ concatString(r, makeStringExt(hlen,t->content, 'H'));
+ hw = createString(r);
+ TAILQ_CONCAT(&hw->comments, &cfile->comments);
+ if (!ether || (hlen != 6)) {
+ hw->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ return hw;
+}
+
+/* No BNF for numeric aggregates - that's defined by the caller. What
+ this function does is to parse a sequence of numbers separated by
+ the token specified in separator. If max is zero, any number of
+ numbers will be parsed; otherwise, exactly max numbers are
+ expected. Base and size tell us how to internalize the numbers
+ once they've been tokenized.
+
+ buf - A pointer to space to return the parsed value, if it is null
+ then the function will allocate space for the return.
+
+ max - The maximum number of items to store. If zero there is no
+ maximum. When buf is null and the function needs to allocate space
+ it will do an allocation of max size at the beginning if max is non
+ zero. If max is zero then the allocation will be done later, after
+ the function has determined the size necessary for the incoming
+ string.
+
+ returns NULL on errors or a pointer to the string structure on success.
+ */
+
+struct string *
+parse_numeric_aggregate(struct parse *cfile, unsigned char *buf,
+ unsigned *max, int separator,
+ int base, unsigned size)
+{
+ const char *val;
+ enum dhcp_token token;
+ unsigned char *bufp = buf, *s;
+ unsigned count = 0;
+ struct string *r = NULL, *t = NULL;
+
+ if (!bufp && *max) {
+ bufp = (unsigned char *)malloc(*max * size / 8);
+ if (!bufp)
+ parse_error(cfile, "no space for numeric aggregate");
+ }
+ s = bufp;
+ if (!s) {
+ r = allocString();
+ t = makeString(size / 8, "bigger than needed");
+ }
+
+ do {
+ if (count) {
+ token = peek_token(&val, NULL, cfile);
+ if (token != separator) {
+ if (!*max)
+ break;
+ if (token != RBRACE && token != LBRACE)
+ token = next_token(&val, NULL, cfile);
+ parse_error(cfile, "too few numbers.");
+ }
+ skip_token(&val, NULL, cfile);
+ }
+ token = next_token(&val, NULL, cfile);
+
+ if (token == END_OF_FILE)
+ parse_error(cfile, "unexpected end of file");
+
+ /* Allow NUMBER_OR_NAME if base is 16. */
+ if (token != NUMBER &&
+ (base != 16 || token != NUMBER_OR_NAME))
+ parse_error(cfile, "expecting numeric value.");
+ /* If we can, convert the number now; otherwise, build
+ a linked list of all the numbers. */
+ if (s) {
+ convert_num(cfile, s, val, base, size);
+ s += size / 8;
+ } else {
+ convert_num(cfile, (unsigned char *)t->content,
+ val, base, size);
+ concatString(r, t);
+ }
+ } while (++count != *max);
+
+ *max = count;
+ if (bufp)
+ r = makeString(count * size / 8, (char *)bufp);
+
+ return r;
+}
+
+void
+convert_num(struct parse *cfile, unsigned char *buf, const char *str,
+ int base, unsigned size)
+{
+ const unsigned char *ptr = (const unsigned char *)str;
+ int negative = 0;
+ uint32_t val = 0;
+ int tval;
+ int max;
+
+ if (*ptr == '-') {
+ negative = 1;
+ ++ptr;
+ }
+
+ /* If base wasn't specified, figure it out from the data. */
+ if (!base) {
+ if (ptr[0] == '0') {
+ if (ptr[1] == 'x') {
+ base = 16;
+ ptr += 2;
+ } else if (isascii(ptr[1]) && isdigit(ptr[1])) {
+ base = 8;
+ ptr += 1;
+ } else {
+ base = 10;
+ }
+ } else {
+ base = 10;
+ }
+ }
+
+ do {
+ tval = *ptr++;
+ /* XXX assumes ASCII... */
+ if (tval >= 'a')
+ tval = tval - 'a' + 10;
+ else if (tval >= 'A')
+ tval = tval - 'A' + 10;
+ else if (tval >= '0')
+ tval -= '0';
+ else
+ parse_error(cfile, "Bogus number: %s.", str);
+ if (tval >= base)
+ parse_error(cfile,
+ "Bogus number %s: digit %d not in base %d",
+ str, tval, base);
+ val = val * base + tval;
+ } while (*ptr);
+
+ if (negative)
+ max = (1 << (size - 1));
+ else
+ max = (1 << (size - 1)) + ((1 << (size - 1)) - 1);
+ if (val > max) {
+ switch (base) {
+ case 8:
+ parse_error(cfile,
+ "%s%lo exceeds max (%d) for precision.",
+ negative ? "-" : "",
+ (unsigned long)val, max);
+ break;
+ case 16:
+ parse_error(cfile,
+ "%s%lx exceeds max (%d) for precision.",
+ negative ? "-" : "",
+ (unsigned long)val, max);
+ break;
+ default:
+ parse_error(cfile,
+ "%s%lu exceeds max (%d) for precision.",
+ negative ? "-" : "",
+ (unsigned long)val, max);
+ break;
+ }
+ }
+
+ if (negative) {
+ switch (size) {
+ case 8:
+ *buf = -(unsigned long)val;
+ break;
+ case 16:
+ putShort(buf, -(long)val);
+ break;
+ case 32:
+ putLong(buf, -(long)val);
+ break;
+ default:
+ parse_error(cfile,
+ "Unexpected integer size: %d\n", size);
+ break;
+ }
+ } else {
+ switch (size) {
+ case 8:
+ *buf = (uint8_t)val;
+ break;
+ case 16:
+ putUShort (buf, (uint16_t)val);
+ break;
+ case 32:
+ putULong (buf, val);
+ break;
+ default:
+ parse_error(cfile,
+ "Unexpected integer size: %d\n", size);
+ }
+ }
+}
+
+/*
+ * option-name :== IDENTIFIER |
+ IDENTIFIER . IDENTIFIER
+ */
+
+struct option *
+parse_option_name(struct parse *cfile,
+ isc_boolean_t allocate,
+ isc_boolean_t *known)
+{
+ const char *val;
+ enum dhcp_token token;
+ const char *uname;
+ struct space *space;
+ struct option *option = NULL;
+ unsigned code;
+
+ token = next_token(&val, NULL, cfile);
+ if (!is_identifier(token))
+ parse_error(cfile,
+ "expecting identifier after option keyword.");
+
+ uname = strdup(val);
+ if (!uname)
+ parse_error(cfile, "no memory for uname information.");
+ token = peek_token(&val, NULL, cfile);
+ if (token == DOT) {
+ /* Go ahead and take the DOT token... */
+ skip_token(&val, NULL, cfile);
+
+ /* The next token should be an identifier... */
+ token = next_token(&val, NULL, cfile);
+ if (!is_identifier(token))
+ parse_error(cfile, "expecting identifier after '.'");
+
+ /* Look up the option name hash table for the specified
+ uname. */
+ space = space_lookup(uname);
+ if (space == NULL)
+ parse_error(cfile, "no option space named %s.", uname);
+ } else {
+ /* Use the default hash table, which contains all the
+ standard dhcp option names. */
+ val = uname;
+ space = space_lookup("dhcp");
+ }
+
+ option = option_lookup_name(space->old, val);
+
+ if (option) {
+ if (known && (option->status != isc_dhcp_unknown))
+ *known = ISC_TRUE;
+ } else if (space == space_lookup("server"))
+ parse_error(cfile, "unknown server option %s.", val);
+
+ /* If the option name is of the form unknown-[decimal], use
+ * the trailing decimal value to find the option definition.
+ * If there is no definition, construct one. This is to
+ * support legacy use of unknown options in config files or
+ * lease databases.
+ */
+ else if (strncasecmp(val, "unknown-", 8) == 0) {
+ code = atoi(val + 8);
+
+ /* Option code 0 is always illegal for us, thanks
+ * to the option decoder.
+ */
+ if (code == 0)
+ parse_error(cfile, "Option code 0 is illegal "
+ "in the %s space.", space->old);
+ if ((local_family == AF_INET) && (code == 255))
+ parse_error(cfile, "Option code 255 is illegal "
+ "in the %s space.", space->old);
+
+ /* It's odd to think of unknown option codes as
+ * being known, but this means we know what the
+ * parsed name is talking about.
+ */
+ if (known)
+ *known = ISC_TRUE;
+ option = option_lookup_code(space->old, code);
+
+ /* If we did not find an option of that code,
+ * manufacture an unknown-xxx option definition.
+ */
+ if (option == NULL) {
+ option = (struct option *)malloc(sizeof(*option));
+ /* DHCP code does not check allocation failure? */
+ memset(option, 0, sizeof(*option));
+ option->name = strdup(val);
+ option->space = space;
+ option->code = code;
+ /* Mark format as undefined */
+ option->format = "u";
+ push_option(option);
+ } else {
+ struct comment *comment;
+ char msg[256];
+
+ snprintf(msg, sizeof(msg),
+ "/// option %s.%s redefinition",
+ space->name, val);
+ comment = createComment(msg);
+ TAILQ_INSERT_TAIL(&cfile->comments, comment);
+ }
+ /* If we've been told to allocate, that means that this
+ * (might) be an option code definition, so we'll create
+ * an option structure and return it for the parent to
+ * decide.
+ */
+ } else if (allocate) {
+ option = (struct option *)malloc(sizeof(*option));
+ /* DHCP code does not check allocation failure? */
+ memset(option, 0, sizeof(*option));
+ option->name = strdup(val);
+ option->space = space;
+ /* Mark format as undefined */
+ option->format = "u";
+ push_option(option);
+ } else
+ parse_error(cfile, "no option named %s in space %s",
+ val, space->old);
+
+ return option;
+}
+
+/* IDENTIFIER[WIDTHS] SEMI
+ * WIDTHS ~= LENGTH WIDTH NUMBER
+ * CODE WIDTH NUMBER
+ */
+
+void
+parse_option_space_decl(struct parse *cfile)
+{
+ int token;
+ const char *val;
+ struct element *nu;
+ struct element *p;
+ struct space *universe;
+ int tsize = 1, lsize = 1;
+
+ skip_token(&val, NULL, cfile); /* Discard the SPACE token,
+ which was checked by the
+ caller. */
+ token = next_token(&val, NULL, cfile);
+ if (!is_identifier(token))
+ parse_error(cfile, "expecting identifier.");
+ nu = createMap();
+ nu->skip = ISC_TRUE;
+
+ /* Expect it will be usable in Kea */
+ universe = (struct space *)malloc(sizeof(*universe));
+ if (universe == NULL)
+ parse_error(cfile, "No memory for new option space.");
+ memset(universe, 0, sizeof(*universe));
+ universe->old = strdup(val);
+ universe->name = universe->old;
+ push_space(universe);
+
+ do {
+ token = next_token(&val, NULL, cfile);
+ switch(token) {
+ case SEMI:
+ break;
+
+ case CODE:
+ if (mapSize(nu) == 0) {
+ cfile->issue_counter++;
+ mapSet(nu,
+ createString(
+ makeString(-1, universe->old)),
+ "name");
+ }
+ token = next_token(&val, NULL, cfile);
+ if (token != WIDTH)
+ parse_error(cfile, "expecting width token.");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile,
+ "expecting number 1, 2, 4.");
+
+ tsize = atoi(val);
+ p = NULL;
+ if ((local_family == AF_INET) && (tsize != 1)) {
+ struct comment *comment;
+
+ comment = createComment("/// Only code width "
+ "1 is supported");
+ p = createInt(tsize);
+ TAILQ_INSERT_TAIL(&p->comments, comment);
+ } else if ((local_family == AF_INET6) &&
+ (tsize != 2)) {
+ struct comment *comment;
+
+ comment = createComment("/// Only code width "
+ "2 is supported");
+ p = createInt(tsize);
+ TAILQ_INSERT_TAIL(&p->comments, comment);
+ }
+ if (p != NULL)
+ mapSet(nu, p, "code-width");
+ break;
+
+ case LENGTH:
+ if (mapSize(nu) == 0) {
+ cfile->issue_counter++;
+ mapSet(nu,
+ createString(
+ makeString(-1, universe->old)),
+ "name");
+ }
+ token = next_token(&val, NULL, cfile);
+ if (token != WIDTH)
+ parse_error(cfile, "expecting width token.");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile, "expecting number 1 or 2.");
+
+ lsize = atoi(val);
+ p = NULL;
+ if ((local_family == AF_INET) && (lsize != 1)) {
+ struct comment *comment;
+
+ comment = createComment("/// Only length "
+ "width 1 is "
+ "supported");
+ p = createInt(lsize);
+ TAILQ_INSERT_TAIL(&p->comments, comment);
+ } else if ((local_family == AF_INET6) &&
+ (lsize != 2)) {
+ struct comment *comment;
+
+ comment = createComment("/// Only length "
+ "width 2 is "
+ "supported");
+ p = createInt(lsize);
+ TAILQ_INSERT_TAIL(&p->comments, comment);
+ }
+ if (p != NULL)
+ mapSet(nu, p, "length-width");
+ break;
+
+ case HASH:
+ token = next_token(&val, NULL, cfile);
+ if (token != SIZE)
+ parse_error(cfile, "expecting size token.");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile,
+ "expecting a 10base number");
+ break;
+
+ default:
+ parse_error(cfile, "Unexpected token.");
+ }
+ } while (token != SEMI);
+
+ if (mapSize(nu) > 1)
+ mapSet(cfile->stack[1], nu, "option-space");
+}
+
+/* This is faked up to look good right now. Ideally, this should do a
+ recursive parse and allow arbitrary data structure definitions, but for
+ now it just allows you to specify a single type, an array of single types,
+ a sequence of types, or an array of sequences of types.
+
+ ocd :== NUMBER EQUALS ocsd SEMI
+
+ ocsd :== ocsd_type |
+ ocsd_type_sequence |
+ ARRAY OF ocsd_simple_type_sequence
+
+ ocsd_type_sequence :== LBRACE ocsd_types RBRACE
+
+ ocsd_simple_type_sequence :== LBRACE ocsd_simple_types RBRACE
+
+ ocsd_types :== ocsd_type |
+ ocsd_types ocsd_type
+
+ ocsd_type :== ocsd_simple_type |
+ ARRAY OF ocsd_simple_type
+
+ ocsd_simple_types :== ocsd_simple_type |
+ ocsd_simple_types ocsd_simple_type
+
+ ocsd_simple_type :== BOOLEAN |
+ INTEGER NUMBER |
+ SIGNED INTEGER NUMBER |
+ UNSIGNED INTEGER NUMBER |
+ IP-ADDRESS |
+ TEXT |
+ STRING |
+ ENCAPSULATE identifier */
+
+void
+parse_option_code_definition(struct parse *cfile, struct option *option)
+{
+ const char *val;
+ enum dhcp_token token;
+ struct element *def;
+ unsigned code;
+ unsigned arrayp = 0;
+ isc_boolean_t is_array = ISC_FALSE;
+ int recordp = 0;
+ isc_boolean_t no_more_in_record = ISC_FALSE;
+ char *type;
+ isc_boolean_t is_signed;
+ isc_boolean_t has_encapsulation = ISC_FALSE;
+ isc_boolean_t not_supported = ISC_FALSE;
+ struct string *encapsulated;
+ struct string *datatype;
+ struct string *saved;
+ struct string *format;
+ struct element *optdef;
+
+ if (option->space->status == special) {
+ parse_vendor_code_definition(cfile, option);
+ return;
+ }
+
+ /* Put the option in the definition */
+ def = createMap();
+ mapSet(def,
+ createString(makeString(-1, option->space->name)),
+ "space");
+ mapSet(def, createString(makeString(-1, option->name)), "name");
+ TAILQ_CONCAT(&def->comments, &cfile->comments);
+
+ /* Parse the option code. */
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile, "expecting option code number.");
+ TAILQ_CONCAT(&def->comments, &cfile->comments);
+ code = atoi(val);
+ mapSet(def, createInt(code), "code");
+
+ /* We have the code so we can get the real option now */
+ if (option->code == 0) {
+ struct option *from_code = NULL;
+
+ option->code = code;
+ from_code = option_lookup_code(option->space->old, code);
+ if (from_code != NULL) {
+ option->status = from_code->status;
+ option->format = from_code->format;
+ }
+ }
+
+ /* Redefinitions are not allowed */
+ if ((option->status != dynamic) ||
+ (strcmp(option->format, "u") != 0)) {
+ struct comment *comment;
+
+ comment = createComment("/// Kea does not allow redefinition "
+ "of options");
+ TAILQ_INSERT_TAIL(&def->comments, comment);
+ def->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ /* Avoid option-data per name */
+ option->status = kea_unknown;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != EQUAL)
+ parse_error(cfile, "expecting \"=\"");
+ saved = allocString();
+
+ /* See if this is an array. */
+ token = next_token(&val, NULL, cfile);
+ if (token == ARRAY) {
+ token = next_token(&val, NULL, cfile);
+ if (token != OF)
+ parse_error(cfile, "expecting \"of\".");
+ arrayp = 1;
+ token = next_token(&val, NULL, cfile);
+ appendString(saved, "array of");
+ }
+
+ if (token == LBRACE) {
+ recordp = 1;
+ token = next_token(&val, NULL, cfile);
+ if (arrayp)
+ appendString(saved, " ");
+ appendString(saved, "{");
+ }
+
+ /* At this point we're expecting a data type. */
+ datatype = allocString();
+ /* We record the format essentially for the binary one */
+ format = allocString();
+ next_type:
+ if (saved->length > 0)
+ appendString(saved, " ");
+ type = NULL;
+ if (has_encapsulation)
+ parse_error(cfile,
+ "encapsulate must always be the last item.");
+
+ switch (token) {
+ case ARRAY:
+ if (arrayp)
+ parse_error(cfile, "no nested arrays.");
+ if (recordp) {
+ struct comment *comment;
+
+ comment = createComment("/// unsupported array "
+ "inside a record");
+ TAILQ_INSERT_TAIL(&def->comments, comment);
+ not_supported = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ token = next_token(&val, NULL, cfile);
+ if (token != OF)
+ parse_error(cfile, "expecting \"of\".");
+ arrayp = recordp + 1;
+ token = next_token(&val, NULL, cfile);
+ if ((recordp) && (token == LBRACE))
+ parse_error(cfile,
+ "only uniform array inside record.");
+ appendString(saved, "array of");
+ if (token == LBRACE) {
+ struct comment *comment;
+
+ comment = createComment("/// unsupported record "
+ "inside an array");
+ TAILQ_INSERT_TAIL(&def->comments, comment);
+ not_supported = ISC_TRUE;
+ cfile->issue_counter++;
+ appendString(saved, " {");
+ }
+ goto next_type;
+ case BOOLEAN:
+ type = "boolean";
+ appendString(format, "f");
+ break;
+ case INTEGER:
+ is_signed = ISC_TRUE;
+ parse_integer:
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile, "expecting number.");
+ switch (atoi(val)) {
+ case 8:
+ if (is_signed) {
+ type = "int8";
+ appendString(format, "b");
+ } else {
+ type = "uint8";
+ appendString(format, "B");
+ }
+ break;
+ case 16:
+ if (is_signed) {
+ type = "int16";
+ appendString(format, "s");
+ } else {
+ type = "uint16";
+ appendString(format, "S");
+ }
+ break;
+ case 32:
+ if (is_signed) {
+ type = "int32";
+ appendString(format, "l");
+ } else {
+ type = "uint32";
+ appendString(format, "L");
+ }
+ break;
+ default:
+ parse_error(cfile,
+ "%s bit precision is not supported.", val);
+ }
+ break;
+ case SIGNED:
+ is_signed = ISC_TRUE;
+ parse_signed:
+ token = next_token(&val, NULL, cfile);
+ if (token != INTEGER)
+ parse_error(cfile, "expecting \"integer\" keyword.");
+ goto parse_integer;
+ case UNSIGNED:
+ is_signed = ISC_FALSE;
+ goto parse_signed;
+
+ case IP_ADDRESS:
+ type = "ipv4-address";
+ appendString(format, "I");
+ break;
+ case IP6_ADDRESS:
+ type = "ipv6-address";
+ appendString(format, "6");
+ break;
+ case DOMAIN_NAME:
+ type = "fqdn";
+ appendString(format, "d");
+ goto no_arrays;
+ case DOMAIN_LIST:
+ /* Consume optional compression indicator. */
+ token = peek_token(&val, NULL, cfile);
+ appendString(format, "D");
+ type = "fqdn";
+ is_array = ISC_TRUE;
+ if (token == COMPRESSED) {
+ if (local_family == AF_INET6)
+ parse_error(cfile, "domain list in DHCPv6 "
+ "MUST NOT be compressed");
+ skip_token(&val, NULL, cfile);
+ appendString(format, "c");
+ appendString(saved, "compressed ");
+ }
+ appendString(saved, "list of ");
+ goto no_arrays;
+ case TEXT:
+ type = "string";
+ appendString(format, "t");
+ no_arrays:
+ if (arrayp)
+ parse_error(cfile, "arrays of text strings not %s",
+ "yet supported.");
+ no_more_in_record = ISC_TRUE;
+ break;
+ case STRING_TOKEN:
+ /* can be binary too */
+ type = "string";
+ appendString(format, "x");
+ goto no_arrays;
+
+ case ENCAPSULATE:
+ token = next_token(&val, NULL, cfile);
+ if (!is_identifier(token))
+ parse_error(cfile,
+ "expecting option space identifier");
+ encapsulated = makeString(-1, val);
+ has_encapsulation = ISC_TRUE;
+ appendString(format, "E");
+ appendString(format, val);
+ appendString(format, ".");
+ appendString(saved, "encapsulate ");
+ appendString(saved, val);
+ if (datatype->length == 0)
+ type = "empty";
+ break;
+
+ case ZEROLEN:
+ type = "empty";
+ appendString(format, "Z");
+ if (arrayp)
+ parse_error(cfile, "array incompatible with zerolen.");
+ no_more_in_record = ISC_TRUE;
+ break;
+
+ default:
+ parse_error(cfile, "unknown data type %s", val);
+ }
+ appendString(saved, type);
+ appendString(datatype, type);
+
+ if (recordp) {
+ token = next_token(&val, NULL, cfile);
+ if (arrayp > recordp) {
+ is_array = ISC_TRUE;
+ arrayp = 0;
+ appendString(format, "a");
+ }
+ if (token == COMMA) {
+ if (no_more_in_record) {
+ char last;
+
+ last = format->content[format->length - 1];
+ parse_error(cfile,
+ "%s must be at end of record.",
+ last == 't' ? "text" : "string");
+ }
+ token = next_token(&val, NULL, cfile);
+ appendString(saved, ",");
+ appendString(datatype, ", ");
+ goto next_type;
+ }
+ if (token != RBRACE)
+ parse_error(cfile, "expecting right brace.");
+ appendString(saved, "}");
+ }
+ parse_semi(cfile);
+ if (has_encapsulation && arrayp)
+ parse_error(cfile,
+ "Arrays of encapsulations don't make sense.");
+ if (arrayp)
+ appendString(format, (arrayp > recordp) ? "a" : "A");
+ if (is_array || arrayp) {
+ struct element *array_def;
+
+ array_def = createBool(ISC_TRUE);
+ if (not_supported)
+ array_def->skip = ISC_TRUE;
+ mapSet(def, array_def, "array");
+ }
+
+ if (not_supported) {
+ struct element *type_def;
+ struct element *saved_def;
+ struct comment *comment;
+
+ saved_def = createString(saved);
+ saved_def->skip = ISC_TRUE;
+ mapSet(def, saved_def, "definition");
+ type_def = createString(makeString(-1, "binary"));
+ comment = createComment("/// Option definition is not "
+ "compatible with Kea");
+ TAILQ_INSERT_TAIL(&type_def->comments, comment);
+ comment = createComment("/// Fallback to full binary");
+ TAILQ_INSERT_TAIL(&type_def->comments, comment);
+ mapSet(def, type_def, "type");
+ } else if (recordp) {
+ mapSet(def, createString(datatype), "record-types");
+ mapSet(def, createString(makeString(-1, "record")), "type");
+ } else
+ mapSet(def, createString(datatype), "type");
+
+ /* Force full binary when the format is not supported by Kea */
+ if (not_supported)
+ appendString(format, "Y");
+ option->format = format->content;
+
+ if (has_encapsulation)
+ mapSet(def, createString(encapsulated), "encapsulate");
+
+ optdef = mapGet(cfile->stack[1], "option-def");
+ if (optdef == NULL) {
+ optdef = createList();
+ mapSet(cfile->stack[1], optdef, "option-def");
+ }
+ listPush(optdef, def);
+}
+
+/*
+ * Specialized version of parse_option_code_definition for vendor options
+ * DHCPv4 vivso (code 125, space vendor) and DHCPv6 vendor-opts (17,
+ * space vsio). The syntax is a subnet:
+ * vcd :== NUMBER EQUALS ENCAPSULATE identifier SEMI
+ */
+
+void
+parse_vendor_code_definition(struct parse *cfile, struct option *option)
+{
+ const char *val;
+ enum dhcp_token token;
+ struct string *id;
+ struct string *space;
+ struct space *universe;
+ struct string *name;
+ unsigned code;
+ struct element *vendor;
+
+ space = makeString(-1, "vendor-");
+
+ /* Parse the option code / vendor id. */
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile, "expecting option code number.");
+ id = makeString(-1, val);
+ appendString(space, val);
+
+
+ token = next_token(&val, NULL, cfile);
+ if (token != EQUAL)
+ parse_error(cfile, "expecting \"=\"");
+ token = next_token(&val, NULL, cfile);
+ if (token != ENCAPSULATE)
+ parse_error(cfile, "expecting encapsulate");
+ token = next_token(&val, NULL, cfile);
+ if (!is_identifier(token))
+ parse_error(cfile, "expecting option space identifier");
+ universe = space_lookup(val);
+ if (universe == NULL)
+ parse_error(cfile, "unknown option space %s", val);
+ /* Map the universe to vendor-<code> */
+ universe->name = space->content;
+ /* Create the vendor option */
+ vendor = createMap();
+ if (local_family == AF_INET) {
+ space = makeString(-1, "dhcp4");
+ name = makeString(-1, "vivso-suboptions");
+ code = DHO_VIVSO_SUBOPTIONS;
+ } else {
+ space =makeString(-1, "dhcp6");
+ name = makeString(-1, "vendor-opts");
+ code = D6O_VENDOR_OPTS;
+ }
+ mapSet(vendor, createString(space), "space");
+ mapSet(vendor, createString(name), "name");
+ mapSet(vendor, createInt(code), "code");
+ mapSet(vendor, createString(id), "data");
+ universe->vendor = vendor;
+ parse_semi(cfile);
+}
+
+struct string *
+convert_format(const char *fmt, isc_boolean_t *is_array,
+ isc_boolean_t *encapsulate)
+{
+ struct string *datatype;
+ const char *g;
+
+ if ((strchr(fmt, 'A') != NULL) || (strchr(fmt, 'a') != NULL) ||
+ (strchr(fmt, 'D') != NULL))
+ *is_array = ISC_TRUE;
+
+ if (strchr(fmt, 'E') != NULL)
+ *encapsulate = ISC_TRUE;
+
+ if ((strchr(fmt, 'Y') != NULL) || (strchr(fmt, 'A') != NULL) ||
+ (strchr(fmt, 'E') != NULL) || (strchr(fmt, 'o') != NULL) ||
+ (*fmt == 'X') || (*fmt == 'u'))
+ return makeString(-1, "binary");
+
+ datatype = allocString();
+
+ do {
+ if (datatype->length != 0)
+ appendString(datatype, ", ");
+
+ switch (*fmt) {
+ case 'U':
+ case 't':
+ case 'x':
+ appendString(datatype, "string");
+ break;
+ case 'I':
+ appendString(datatype, "ipv4-address");
+ break;
+ case '6':
+ appendString(datatype, "ipv6-address");
+ break;
+ case 'l':
+ appendString(datatype, "int32");
+ break;
+ case 'L':
+ case 'T':
+ appendString(datatype, "uint32");
+ break;
+ case 's':
+ appendString(datatype, "int16");
+ break;
+ case 'S':
+ appendString(datatype, "uint16");
+ break;
+ case 'b':
+ appendString(datatype, "int8");
+ break;
+ case 'B':
+ appendString(datatype, "uint8");
+ break;
+ case 'f':
+ appendString(datatype, "boolean");
+ break;
+ case 'E':
+ case 'N':
+ g = strchr(fmt, '.');
+ if (g == NULL)
+ return makeString(-1, "bad?!");
+ if (*fmt == 'N')
+ return makeString(-1, "unsupported?!");
+ fmt = g;
+ break;
+ case 'X':
+ appendString(datatype, "binary");
+ break;
+ case 'd':
+ case 'D':
+ appendString(datatype, "fqdn");
+ break;
+ case 'Z':
+ appendString(datatype, "empty");
+ break;
+ case 'A':
+ case 'a':
+ case 'c':
+ /* ignored */
+ break;
+ default:
+ return makeString(-1, "unknown?!");
+ }
+ fmt++;
+ } while (*fmt != '\0');
+
+ return datatype;
+}
+
+/*
+ * base64 :== NUMBER_OR_STRING
+ */
+
+struct string *
+parse_base64(struct parse *cfile)
+{
+ const char *val;
+ unsigned i;
+ static unsigned char
+ from64[] = {64, 64, 64, 64, 64, 64, 64, 64, /* \"#$%&' */
+ 64, 64, 64, 62, 64, 64, 64, 63, /* ()*+,-./ */
+ 52, 53, 54, 55, 56, 57, 58, 59, /* 01234567 */
+ 60, 61, 64, 64, 64, 64, 64, 64, /* 89:;<=>? */
+ 64, 0, 1, 2, 3, 4, 5, 6, /* @ABCDEFG */
+ 7, 8, 9, 10, 11, 12, 13, 14, /* HIJKLMNO */
+ 15, 16, 17, 18, 19, 20, 21, 22, /* PQRSTUVW */
+ 23, 24, 25, 64, 64, 64, 64, 64, /* XYZ[\]^_ */
+ 64, 26, 27, 28, 29, 30, 31, 32, /* 'abcdefg */
+ 33, 34, 35, 36, 37, 38, 39, 40, /* hijklmno */
+ 41, 42, 43, 44, 45, 46, 47, 48, /* pqrstuvw */
+ 49, 50, 51, 64, 64, 64, 64, 64}; /* xyz{|}~ */
+ struct string *t;
+ struct string *r;
+ isc_boolean_t valid_base64;
+
+ r = allocString();
+
+ /* It's possible for a + or a / to cause a base64 quantity to be
+ tokenized into more than one token, so we have to parse them all
+ in before decoding. */
+ do {
+ unsigned l;
+
+ (void)next_token(&val, &l, cfile);
+ t = makeString(l, val);
+ concatString(r, t);
+ (void)peek_token(&val, NULL, cfile);
+ valid_base64 = ISC_TRUE;
+ for (i = 0; val[i]; i++) {
+ /* Check to see if the character is valid. It
+ may be out of range or within the right range
+ but not used in the mapping */
+ if (((val[i] < ' ') || (val[i] > 'z')) ||
+ ((from64[val[i] - ' '] > 63) && (val[i] != '='))) {
+ valid_base64 = ISC_FALSE;
+ break; /* no need to continue for loop */
+ }
+ }
+ } while (valid_base64);
+
+ return r;
+}
+
+/*
+ * colon-separated-hex-list :== NUMBER |
+ * NUMBER COLON colon-separated-hex-list
+ */
+
+struct string *
+parse_cshl(struct parse *cfile)
+{
+ uint8_t ibuf;
+ char tbuf[4];
+ isc_boolean_t first = ISC_TRUE;
+ struct string *data;
+ enum dhcp_token token;
+ const char *val;
+
+ data = allocString();
+
+ for (;;) {
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER && token != NUMBER_OR_NAME)
+ parse_error(cfile, "expecting hexadecimal number.");
+ convert_num(cfile, &ibuf, val, 16, 8);
+ if (first)
+ snprintf(tbuf, sizeof(tbuf), "%02hhx", ibuf);
+ else
+ snprintf(tbuf, sizeof(tbuf), ":%02hhx", ibuf);
+ first = ISC_FALSE;
+ appendString(data, tbuf);
+
+ token = peek_token(&val, NULL, cfile);
+ if (token != COLON)
+ break;
+ skip_token(&val, NULL, cfile);
+ }
+
+ return data;
+}
+
+/* Same but without colons in output */
+
+struct string *
+parse_hexa(struct parse *cfile)
+{
+ uint8_t ibuf;
+ char tbuf[4];
+ struct string *data;
+ enum dhcp_token token;
+ const char *val;
+
+ data = allocString();
+
+ for (;;) {
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER && token != NUMBER_OR_NAME)
+ parse_error(cfile, "expecting hexadecimal number.");
+ convert_num(cfile, &ibuf, val, 16, 8);
+ snprintf(tbuf, sizeof(tbuf), "%02hhx", ibuf);
+ appendString(data, tbuf);
+
+ token = peek_token(&val, NULL, cfile);
+ if (token != COLON)
+ break;
+ skip_token(&val, NULL, cfile);
+ }
+
+ return data;
+}
+
+/*
+ * executable-statements :== executable-statement executable-statements |
+ * executable-statement
+ *
+ * executable-statement :==
+ * IF if-statement |
+ * ADD class-name SEMI |
+ * BREAK SEMI |
+ * OPTION option-parameter SEMI |
+ * SUPERSEDE option-parameter SEMI |
+ * PREPEND option-parameter SEMI |
+ * APPEND option-parameter SEMI
+ */
+
+isc_boolean_t
+parse_executable_statements(struct element *statements,
+ struct parse *cfile, isc_boolean_t *lose,
+ enum expression_context case_context)
+{
+ if (statements->type != ELEMENT_LIST)
+ parse_error(cfile, "statements is not a list?");
+ for (;;) {
+ struct element *statement;
+
+ statement = createMap();
+ TAILQ_CONCAT(&statement->comments, &cfile->comments);
+ if (!parse_executable_statement(statement, cfile, lose,
+ case_context, ISC_FALSE))
+ break;
+ TAILQ_CONCAT(&statement->comments, &cfile->comments);
+ listPush(statements, statement);
+ }
+ if (!*lose)
+ return ISC_TRUE;
+
+ return ISC_FALSE;
+}
+
+isc_boolean_t
+parse_executable_statement(struct element *result,
+ struct parse *cfile, isc_boolean_t *lose,
+ enum expression_context case_context,
+ isc_boolean_t direct)
+{
+ unsigned len;
+ enum dhcp_token token;
+ const char *val;
+ struct element *st;
+ struct option *option;
+ struct element *var;
+ struct element *pri;
+ struct element *expr;
+ isc_boolean_t known;
+ int flag;
+ int i;
+ struct element *zone;
+ struct string *s;
+ static isc_boolean_t log_warning = ISC_TRUE;
+
+ token = peek_token(&val, NULL, cfile);
+ switch (token) {
+ case DB_TIME_FORMAT:
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token == DEFAULT)
+ s = makeString(-1, val);
+ else if (token == LOCAL)
+ s = makeString(-1, val);
+ else
+ parse_error(cfile, "Expecting 'local' or 'default'.");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != SEMI)
+ parse_error(cfile, "Expecting a semicolon.");
+ st = createString(s);
+ st->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(result, st, "db-time-format");
+
+ /* We're done here. */
+ return ISC_TRUE;
+
+ case IF:
+ skip_token(&val, NULL, cfile);
+ return parse_if_statement(result, cfile, lose);
+
+ case TOKEN_ADD:
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != STRING)
+ parse_error(cfile, "expecting class name.");
+ s = makeString(-1, val);
+ parse_semi(cfile);
+ st = createString(s);
+ st->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(result, st, "add-class");
+ break;
+
+ case BREAK:
+ skip_token(&val, NULL, cfile);
+ s = makeString(-1, val);
+ parse_semi(cfile);
+ st = createNull();
+ st->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(result, st, "break");
+ break;
+
+ case SEND:
+ skip_token(&val, NULL, cfile);
+ known = ISC_FALSE;
+ option = parse_option_name(cfile, ISC_FALSE, &known);
+ if (option == NULL) {
+ *lose = ISC_TRUE;
+ return ISC_FALSE;
+ }
+ return parse_option_statement(result, cfile, option,
+ send_option_statement);
+
+ case SUPERSEDE:
+ case OPTION:
+ skip_token(&val, NULL, cfile);
+ known = ISC_FALSE;
+ option = parse_option_name(cfile, ISC_FALSE, &known);
+ if (option == NULL) {
+ *lose = ISC_TRUE;
+ return ISC_FALSE;
+ }
+ return parse_option_statement(result, cfile, option,
+ supersede_option_statement);
+
+ case ALLOW:
+ flag = 1;
+ goto pad;
+ case DENY:
+ flag = 0;
+ goto pad;
+ case IGNORE:
+ flag = 2;
+ pad:
+ skip_token(&val, NULL, cfile);
+ st = parse_allow_deny(cfile, flag);
+ mapSet(result, st, "config");
+ break;
+
+ case DEFAULT:
+ skip_token(&val, NULL, cfile);
+ token = peek_token(&val, NULL, cfile);
+ if (token == COLON)
+ goto switch_default;
+ known = ISC_FALSE;
+ option = parse_option_name(cfile, ISC_FALSE, &known);
+ if (option == NULL) {
+ *lose = ISC_TRUE;
+ return ISC_FALSE;
+ }
+ return parse_option_statement(result, cfile, option,
+ default_option_statement);
+ case PREPEND:
+ skip_token(&val, NULL, cfile);
+ known = ISC_FALSE;
+ option = parse_option_name(cfile, ISC_FALSE, &known);
+ if (option == NULL) {
+ *lose = ISC_TRUE;
+ return ISC_FALSE;
+ }
+ return parse_option_statement(result, cfile, option,
+ prepend_option_statement);
+ case APPEND:
+ skip_token(&val, NULL, cfile);
+ known = ISC_FALSE;
+ option = parse_option_name(cfile, ISC_FALSE, &known);
+ if (option == NULL) {
+ *lose = ISC_TRUE;
+ return ISC_FALSE;
+ }
+ return parse_option_statement(result, cfile, option,
+ append_option_statement);
+
+ case ON:
+ skip_token(&val, NULL, cfile);
+ return parse_on_statement(result, cfile, lose);
+
+ case SWITCH:
+ skip_token(&val, NULL, cfile);
+ return parse_switch_statement(result, cfile, lose);
+
+ case CASE:
+ skip_token(&val, NULL, cfile);
+ if (case_context == context_any)
+ parse_error(cfile,
+ "case statement in inappropriate scope.");
+ return parse_case_statement(result,
+ cfile, lose, case_context);
+
+ switch_default:
+ skip_token(&val, NULL, cfile);
+ if (case_context == context_any)
+ parse_error(cfile, "switch default statement in %s",
+ "inappropriate scope.");
+ s = makeString(-1, "default");
+ st = createNull();
+ st->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(result, st, "default");
+ return ISC_TRUE;
+
+ case DEFINE:
+ case TOKEN_SET:
+ skip_token(&val, NULL, cfile);
+ if (token == DEFINE)
+ flag = 1;
+ else
+ flag = 0;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NAME && token != NUMBER_OR_NAME)
+ parse_error(cfile,
+ "%s can't be a variable name", val);
+ st = createMap();
+ st->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(result, st, flag ? "define" : "set");
+ var = createString(makeString(-1, val));
+ mapSet(st, var, "name");
+ token = next_token(&val, NULL, cfile);
+
+ if (token == LPAREN) {
+ struct element *func;
+ struct string *args;
+
+ func = createMap();
+ args = allocString();
+ do {
+ token = next_token(&val, NULL, cfile);
+ if (token == RPAREN)
+ break;
+ if (token != NAME && token != NUMBER_OR_NAME)
+ parse_error(cfile,
+ "expecting argument name");
+ if (args->length > 0)
+ appendString(args, ", ");
+ appendString(args, val);
+ token = next_token(&val, NULL, cfile);
+ } while (token == COMMA);
+
+ if (token != RPAREN) {
+ parse_error(cfile, "expecting right paren.");
+ badx:
+ skip_to_semi(cfile);
+ *lose = ISC_TRUE;
+ return ISC_FALSE;
+ }
+ mapSet(func, createString(args), "arguments");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE)
+ parse_error(cfile, "expecting left brace.");
+
+ expr = createList();
+ if (!parse_executable_statements(expr, cfile,
+ lose, case_context)) {
+ if (*lose)
+ goto badx;
+ }
+ mapSet(func, expr, "body");
+ mapSet(st, func, "function");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != RBRACE)
+ parse_error(cfile, "expecting rigt brace.");
+ } else {
+ if (token != EQUAL)
+ parse_error(cfile,
+ "expecting '=' in %s statement.",
+ flag ? "define" : "set");
+
+ expr = createMap();
+ if (!parse_expression(expr, cfile, lose, context_any,
+ NULL, expr_none)) {
+ if (!*lose)
+ parse_error(cfile,
+ "expecting expression.");
+ else
+ *lose = ISC_TRUE;
+ skip_to_semi(cfile);
+ return ISC_FALSE;
+ }
+ mapSet(st, expr, "value");
+ parse_semi(cfile);
+ }
+ break;
+
+ case UNSET:
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != NAME && token != NUMBER_OR_NAME)
+ parse_error(cfile, "%s can't be a variable name", val);
+
+ st = createMap();
+ st->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(result, st, "unset");
+ var = createString(makeString(-1, val));
+ mapSet(st, var, "name");
+ parse_semi(cfile);
+ break;
+
+ case EVAL:
+ skip_token(&val, NULL, cfile);
+ expr = createMap();
+
+ if (!parse_expression(expr, cfile, lose,
+ context_data, /* XXX */
+ NULL, expr_none)) {
+ if (!*lose)
+ parse_error(cfile,
+ "expecting data expression.");
+ else
+ *lose = ISC_TRUE;
+ skip_to_semi(cfile);
+ return ISC_FALSE;
+ }
+ mapSet(result, expr, "eval");
+ parse_semi(cfile);
+ break;
+
+ case EXECUTE:
+ skip_token(&val, NULL, cfile);
+ expr = createMap();
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN)
+ parse_error(cfile, "left parenthesis expected.");
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING)
+ parse_error(cfile, "Expecting a quoted string.");
+ mapSet(expr, createString(makeString(len, val)), "command");
+
+ st = createList();
+
+ while ((token = next_token(&val, NULL, cfile)) == COMMA) {
+ var = createMap();
+ if (!parse_data_expression(var, cfile, lose)) {
+ if (!*lose)
+ parse_error(cfile,
+ "expecting expression.");
+ skip_to_semi(cfile);
+ *lose = ISC_TRUE;
+ return ISC_FALSE;
+ }
+ listPush(st, var);
+ }
+ mapSet(expr, st, "arguments");
+
+ if (token != RPAREN)
+ parse_error(cfile, "right parenthesis expected.");
+ parse_semi(cfile);
+ mapSet(result, expr, "execute");
+ break;
+
+ case RETURN:
+ skip_token(&val, NULL, cfile);
+
+ expr = createMap();
+
+ if (!parse_expression(expr, cfile, lose, context_data,
+ NULL, expr_none)) {
+ if (!*lose)
+ parse_error(cfile,
+ "expecting data expression.");
+ else
+ *lose = ISC_TRUE;
+ skip_to_semi(cfile);
+ return ISC_FALSE;
+ }
+ mapSet(result, expr, "return");
+ parse_semi(cfile);
+ break;
+
+ case LOG:
+ skip_token(&val, NULL, cfile);
+
+ st = createMap();
+ st->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(result, st, "log");
+ if (log_warning) {
+ struct comment *comment;
+
+ comment = createComment("/// Kea does not support "
+ "yet log statements");
+ TAILQ_INSERT_TAIL(&st->comments, comment);
+ comment= createComment("/// Reference Kea #234");
+ TAILQ_INSERT_TAIL(&st->comments, comment);
+ log_warning = ISC_FALSE;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN)
+ parse_error(cfile, "left parenthesis expected.");
+
+ token = peek_token(&val, NULL, cfile);
+ i = 1;
+ if (token == FATAL)
+ s = makeString(-1, val);
+ else if (token == ERROR)
+ s = makeString(-1, val);
+ else if (token == TOKEN_DEBUG)
+ s = makeString(-1, val);
+ else if (token == INFO)
+ s = makeString(-1, val);
+ else {
+ s = makeString(-1, "DEBUG");
+ i = 0;
+ }
+ if (i) {
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != COMMA)
+ parse_error(cfile, "comma expected.");
+ }
+ pri = createString(s);
+ mapSet(st, pri, "priority");
+
+ expr = createMap();
+ if (!parse_data_expression(expr, cfile, lose)) {
+ skip_to_semi(cfile);
+ *lose = ISC_TRUE;
+ return ISC_FALSE;
+ }
+ mapSet(st, expr, "message");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != RPAREN)
+ parse_error(cfile, "right parenthesis expected.");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != SEMI)
+ parse_error(cfile, "semicolon expected.");
+ break;
+
+ case PARSE_VENDOR_OPT:
+ /* The parse-vendor-option; The statement has no arguments.
+ * We simply set up the statement and when it gets executed it
+ * will find all information it needs in the packet and options.
+ */
+ skip_token(&val, NULL, cfile);
+ parse_semi(cfile);
+
+ /* Done by Kea after classification so this statement
+ * silently does not translate */
+ break;
+
+ /* Not really a statement, but we parse it here anyway
+ because it's appropriate for all DHCP agents with
+ parsers. */
+ case ZONE:
+ skip_token(&val, NULL, cfile);
+ zone = createMap();
+ zone->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(result, zone, "zone");
+
+ s = parse_host_name(cfile);
+ if (s == NULL) {
+ parse_error(cfile, "expecting hostname.");
+ badzone:
+ *lose = ISC_TRUE;
+ skip_to_semi(cfile);
+ return ISC_FALSE;
+ }
+ if (s->content[s->length - 1] != '.')
+ appendString(s, ".");
+ mapSet(zone, createString(s), "name");
+ if (!parse_zone(zone, cfile))
+ goto badzone;
+ return ISC_TRUE;
+
+ /* Also not really a statement, but same idea as above. */
+ case KEY:
+ skip_token(&val, NULL, cfile);
+ if (!parse_key(result, cfile)) {
+ /* Kea TODO */
+ *lose = ISC_TRUE;
+ return ISC_FALSE;
+ }
+ return ISC_TRUE;
+
+ default:
+ if (is_identifier(token)) {
+ /* the config universe is the server one */
+ option = option_lookup_name("server", val);
+ if (option) {
+ skip_token(&val, NULL, cfile);
+ result->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ return parse_config_statement
+ (direct ? NULL : result,
+ cfile, option,
+ supersede_option_statement);
+ }
+ }
+
+ if (token == NUMBER_OR_NAME || token == NAME) {
+ /* This is rather ugly. Since function calls are
+ data expressions, fake up an eval statement. */
+ expr = createMap();
+
+ if (!parse_expression(expr, cfile, lose, context_data,
+ NULL, expr_none)) {
+ if (!*lose)
+ parse_error(cfile, "expecting "
+ "function call.");
+ else
+ *lose = ISC_TRUE;
+ skip_to_semi(cfile);
+ return ISC_FALSE;
+ }
+ mapSet(result, expr, "eval");
+ parse_semi(cfile);
+ break;
+ }
+
+ *lose = ISC_FALSE;
+ return ISC_FALSE;
+ }
+
+ return ISC_TRUE;
+}
+
+/* zone-statements :== zone-statement |
+ zone-statement zone-statements
+ zone-statement :==
+ PRIMARY ip-addresses SEMI |
+ SECONDARY ip-addresses SEMI |
+ PRIMARY6 ip-address6 SEMI |
+ SECONDARY6 ip-address6 SEMI |
+ key-reference SEMI
+ ip-addresses :== ip-addr-or-hostname |
+ ip-addr-or-hostname COMMA ip-addresses
+ key-reference :== KEY STRING |
+ KEY identifier */
+
+isc_boolean_t
+parse_zone(struct element *zone, struct parse *cfile)
+{
+ int token;
+ const char *val;
+ struct element *values;
+ struct string *key_name;
+ isc_boolean_t done = ISC_FALSE;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE)
+ parse_error(cfile, "expecting left brace");
+
+ do {
+ token = peek_token(&val, NULL, cfile);
+ switch (token) {
+ case PRIMARY:
+ if (mapContains(zone, "primary"))
+ parse_error(cfile, "more than one primary.");
+ values = createList();
+ mapSet(zone, values, "primary");
+ goto consemup;
+
+ case SECONDARY:
+ if (mapContains(zone, "secondary"))
+ parse_error(cfile, "more than one secondary.");
+ values = createList();
+ mapSet(zone, values, "secondary");
+ consemup:
+ skip_token(&val, NULL, cfile);
+ do {
+ struct string *value;
+
+ value = parse_ip_addr_or_hostname(cfile,
+ ISC_FALSE);
+ if (value == NULL)
+ parse_error(cfile,
+ "expecting IP addr or "
+ "hostname.");
+ listPush(values, createString(value));
+ token = next_token(&val, NULL, cfile);
+ } while (token == COMMA);
+ if (token != SEMI)
+ parse_error(cfile, "expecting semicolon.");
+ break;
+
+ case PRIMARY6:
+ if (mapContains(zone, "primary6"))
+ parse_error(cfile, "more than one primary6.");
+ values = createList();
+ mapSet(zone, values, "primary6");
+ goto consemup6;
+
+ case SECONDARY6:
+ if (mapContains(zone, "secondary6"))
+ parse_error(cfile, "more than one secondary6.");
+ values = createList();
+ mapSet(zone, values, "secondary6");
+ consemup6:
+ skip_token(&val, NULL, cfile);
+ do {
+ struct string *addr;
+
+ addr = parse_ip6_addr_txt(cfile);
+ if (addr == NULL)
+ parse_error(cfile, "expecting IPv6 addr.");
+ listPush(values, createString(addr));
+ token = next_token(&val, NULL, cfile);
+ } while (token == COMMA);
+ if (token != SEMI)
+ parse_error(cfile, "expecting semicolon.");
+ break;
+
+ case KEY:
+ skip_token(&val, NULL, cfile);
+ token = peek_token(&val, NULL, cfile);
+ if (token == STRING) {
+ skip_token(&val, NULL, cfile);
+ key_name = makeString(-1, val);
+ } else {
+ key_name = parse_host_name(cfile);
+ if (!key_name)
+ parse_error(cfile, "expecting key name.");
+ }
+ if (mapContains(zone, "key"))
+ parse_error(cfile, "Multiple key definitions");
+ mapSet(zone, createString(key_name), "key");
+ parse_semi(cfile);
+ break;
+
+ default:
+ done = 1;
+ break;
+ }
+ } while (!done);
+
+ token = next_token(&val, NULL, cfile);
+ if (token != RBRACE)
+ parse_error(cfile, "expecting right brace.");
+ return (1);
+}
+
+/* key-statements :== key-statement |
+ key-statement key-statements
+ key-statement :==
+ ALGORITHM host-name SEMI |
+ secret-definition SEMI
+ secret-definition :== SECRET base64val |
+ SECRET STRING
+
+ Kea: where to put this? It is a D2 value */
+
+isc_boolean_t
+parse_key(struct element* result, struct parse *cfile)
+{
+ int token;
+ const char *val;
+ isc_boolean_t done = ISC_FALSE;
+ struct element *key;
+ struct string *alg;
+ struct string *sec;
+ struct element *keys;
+ char *s;
+
+ key = createMap();
+ key->skip = ISC_TRUE;
+ cfile->issue_counter++;
+
+ token = peek_token(&val, NULL, cfile);
+ if (token == STRING) {
+ skip_token(&val, NULL, cfile);
+ mapSet(key, createString(makeString(-1, val)), "name");
+ } else {
+ struct string *name;
+
+ name = parse_host_name(cfile);
+ if (name == NULL)
+ parse_error(cfile, "expecting key name.");
+ mapSet(key, createString(name), "name");
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE)
+ parse_error(cfile, "expecting left brace");
+
+ do {
+ token = next_token(&val, NULL, cfile);
+ switch (token) {
+ case ALGORITHM:
+ if (mapContains(key, "algorithm"))
+ parse_error(cfile, "key: too many algorithms");
+ alg = parse_host_name(cfile);
+ if (alg == NULL)
+ parse_error(cfile,
+ "expecting key algorithm name.");
+ parse_semi(cfile);
+ /* If the algorithm name isn't an FQDN, tack on
+ the .SIG-ALG.REG.NET. domain. */
+ s = strrchr(alg->content, '.');
+ if (!s)
+ appendString(alg, ".SIG-ALG.REG.INT.");
+ /* If there is no trailing '.', hack one in. */
+ else
+ appendString(alg, ".");
+ mapSet(key, createString(alg), "algorithm");
+ break;
+
+ case SECRET:
+ if (mapContains(key, "secret"))
+ parse_error(cfile, "key: too many secrets");
+
+ sec = parse_base64(cfile);
+ if (sec == NULL) {
+ skip_to_rbrace(cfile, 1);
+ return ISC_FALSE;
+ }
+ mapSet(key, createString(sec), "secret");
+
+ parse_semi(cfile);
+ break;
+
+ default:
+ done = ISC_TRUE;
+ break;
+ }
+ } while (!done);
+ if (token != RBRACE)
+ parse_error(cfile, "expecting right brace.");
+ /* Allow the BIND 8 syntax, which has a semicolon after each
+ closing brace. */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI)
+ skip_token(&val, NULL, cfile);
+
+ /* Remember the key. */
+ keys = mapGet(result, "tsig-keys");
+ if (keys == NULL) {
+ keys = createList();
+ mapSet(result, keys, "tsig-keys");
+ }
+ listPush(keys, key);
+ return ISC_TRUE;
+}
+
+/*
+ * on-statement :== event-types LBRACE executable-statements RBRACE
+ * event-types :== event-type OR event-types |
+ * event-type
+ * event-type :== EXPIRY | COMMIT | RELEASE
+ */
+
+isc_boolean_t
+parse_on_statement(struct element *result,
+ struct parse *cfile,
+ isc_boolean_t *lose)
+{
+ enum dhcp_token token;
+ const char *val;
+ struct element *statement;
+ struct string *cond;
+ struct element *body;
+
+ statement = createMap();
+ statement->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(result, statement, "on");
+
+ cond = allocString();
+ do {
+ token = next_token(&val, NULL, cfile);
+ switch (token) {
+ case EXPIRY:
+ case COMMIT:
+ case RELEASE:
+ case TRANSMISSION:
+ appendString(cond, val);
+ break;
+
+ default:
+ parse_error(cfile, "expecting a lease event type");
+ }
+ token = next_token(&val, NULL, cfile);
+ if (token == OR)
+ appendString(cond, " or ");
+ } while (token == OR);
+
+ mapSet(statement, createString(cond), "condition");
+
+ /* Semicolon means no statements. */
+ if (token == SEMI)
+ return ISC_TRUE;
+
+ if (token != LBRACE)
+ parse_error(cfile, "left brace expected.");
+
+ body = createList();
+ if (!parse_executable_statements(body, cfile, lose, context_any)) {
+ if (*lose) {
+ /* Try to even things up. */
+ do {
+ token = next_token(&val, NULL, cfile);
+ } while (token != END_OF_FILE && token != RBRACE);
+ return ISC_FALSE;
+ }
+ }
+ mapSet(statement, body, "body");
+ token = next_token(&val, NULL, cfile);
+ if (token != RBRACE)
+ parse_error(cfile, "right brace expected.");
+ return ISC_TRUE;
+}
+
+/*
+ * switch-statement :== LPAREN expr RPAREN LBRACE executable-statements RBRACE
+ *
+ */
+
+isc_boolean_t
+parse_switch_statement(struct element *result,
+ struct parse *cfile,
+ isc_boolean_t *lose)
+{
+ enum dhcp_token token;
+ const char *val;
+ struct element *statement;
+ struct element *cond;
+ struct element *body;
+
+ statement = createMap();
+ statement->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(result, statement, "switch");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN) {
+ parse_error(cfile, "expecting left brace.");
+ *lose = ISC_TRUE;
+ skip_to_semi(cfile);
+ return ISC_FALSE;
+ }
+
+ cond = createMap();
+ if (!parse_expression(cond, cfile, lose, context_data_or_numeric,
+ NULL, expr_none)) {
+ if (!*lose)
+ parse_error(cfile,
+ "expecting data or numeric expression.");
+ return ISC_FALSE;
+ }
+ mapSet(statement, cond, "condition");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != RPAREN)
+ parse_error(cfile, "right paren expected.");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE)
+ parse_error(cfile, "left brace expected.");
+
+ body = createList();
+ if (!parse_executable_statements(body, cfile, lose,
+ (is_data_expression(cond) ? context_data : context_numeric))) {
+ if (*lose) {
+ skip_to_rbrace(cfile, 1);
+ return ISC_FALSE;
+ }
+ }
+ mapSet(statement, body, "body");
+ token = next_token(&val, NULL, cfile);
+ if (token != RBRACE)
+ parse_error(cfile, "right brace expected.");
+ return ISC_TRUE;
+}
+
+/*
+ * case-statement :== CASE expr COLON
+ *
+ */
+
+isc_boolean_t
+parse_case_statement(struct element *result,
+ struct parse *cfile,
+ isc_boolean_t *lose,
+ enum expression_context case_context)
+{
+ enum dhcp_token token;
+ const char *val;
+ struct element *expr;
+
+ expr = createMap();
+ if (!parse_expression(expr, cfile, lose, case_context,
+ NULL, expr_none))
+ {
+ if (!*lose)
+ parse_error(cfile, "expecting %s expression.",
+ (case_context == context_data
+ ? "data" : "numeric"));
+ *lose = ISC_TRUE;
+ skip_to_semi(cfile);
+ return ISC_FALSE;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != COLON)
+ parse_error(cfile, "colon expected.");
+ mapSet(result, expr, "case");
+ return ISC_TRUE;
+}
+
+/*
+ * if-statement :== boolean-expression LBRACE executable-statements RBRACE
+ * else-statement
+ *
+ * else-statement :== <null> |
+ * ELSE LBRACE executable-statements RBRACE |
+ * ELSE IF if-statement |
+ * ELSIF if-statement
+ */
+
+isc_boolean_t
+parse_if_statement(struct element *result,
+ struct parse *cfile,
+ isc_boolean_t *lose)
+{
+ enum dhcp_token token;
+ const char *val;
+ isc_boolean_t parenp;
+ struct element *statement;
+ struct element *cond;
+ struct element *branch;
+
+ statement = createMap();
+ statement->skip = ISC_TRUE;
+ cfile->issue_counter++;
+
+ mapSet(result, statement, "if");
+
+ token = peek_token(&val, NULL, cfile);
+ if (token == LPAREN) {
+ parenp = ISC_TRUE;
+ skip_token(&val, NULL, cfile);
+ } else
+ parenp = ISC_FALSE;
+
+ cond = createMap();
+ if (!parse_boolean_expression(cond, cfile, lose)) {
+ if (!*lose)
+ parse_error(cfile, "boolean expression expected.");
+ *lose = ISC_TRUE;
+ return ISC_FALSE;
+ }
+ mapSet(statement, cond, "condition");
+ if (parenp) {
+ token = next_token(&val, NULL, cfile);
+ if (token != RPAREN)
+ parse_error(cfile, "expecting right paren.");
+ }
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE)
+ parse_error(cfile, "left brace expected.");
+ branch = createList();
+ if (!parse_executable_statements(branch, cfile, lose, context_any)) {
+ if (*lose) {
+ /* Try to even things up. */
+ do {
+ token = next_token(&val, NULL, cfile);
+ } while (token != END_OF_FILE && token != RBRACE);
+ return ISC_FALSE;
+ }
+ }
+ mapSet(statement, branch, "then");
+ token = next_token(&val, NULL, cfile);
+ if (token != RBRACE)
+ parse_error(cfile, "right brace expected.");
+ token = peek_token(&val, NULL, cfile);
+ if (token == ELSE) {
+ skip_token(&val, NULL, cfile);
+ token = peek_token(&val, NULL, cfile);
+ if (token == IF) {
+ skip_token(&val, NULL, cfile);
+ branch = createMap();
+ if (!parse_if_statement(branch, cfile, lose)) {
+ if (!*lose)
+ parse_error(cfile,
+ "expecting if statement");
+ *lose = ISC_TRUE;
+ return ISC_FALSE;
+ }
+ } else if (token != LBRACE)
+ parse_error(cfile, "left brace or if expected.");
+ else {
+ skip_token(&val, NULL, cfile);
+ branch = createList();
+ if (!parse_executable_statements(branch, cfile,
+ lose, context_any))
+ return ISC_FALSE;
+ token = next_token(&val, NULL, cfile);
+ if (token != RBRACE)
+ parse_error(cfile, "right brace expected.");
+ }
+ mapSet(statement, branch, "else");
+ } else if (token == ELSIF) {
+ skip_token(&val, NULL, cfile);
+ branch = createMap();
+ if (!parse_if_statement(branch, cfile, lose)) {
+ if (!*lose)
+ parse_error(cfile,
+ "expecting conditional.");
+ *lose = ISC_TRUE;
+ return ISC_FALSE;
+ }
+ mapSet(statement, branch, "else");
+ }
+
+ return ISC_TRUE;
+}
+
+/*
+ * boolean_expression :== CHECK STRING |
+ * NOT boolean-expression |
+ * data-expression EQUAL data-expression |
+ * data-expression BANG EQUAL data-expression |
+ * data-expression REGEX_MATCH data-expression |
+ * boolean-expression AND boolean-expression |
+ * boolean-expression OR boolean-expression
+ * EXISTS OPTION-NAME
+ */
+
+isc_boolean_t
+parse_boolean_expression(struct element *expr,
+ struct parse *cfile,
+ isc_boolean_t *lose)
+{
+ /* Parse an expression... */
+ if (!parse_expression(expr, cfile, lose, context_boolean,
+ NULL, expr_none))
+ return ISC_FALSE;
+
+ if (!is_boolean_expression(expr) &&
+ !mapContains(expr, "variable-reference") &&
+ !mapContains(expr, "funcall"))
+ parse_error(cfile, "Expecting a boolean expression.");
+ return ISC_TRUE;
+}
+
+/* boolean :== ON SEMI | OFF SEMI | TRUE SEMI | FALSE SEMI */
+
+isc_boolean_t
+parse_boolean(struct parse *cfile)
+{
+ const char *val;
+ isc_boolean_t rv;
+
+ (void)next_token(&val, NULL, cfile);
+ if (!strcasecmp (val, "true")
+ || !strcasecmp (val, "on"))
+ rv = ISC_TRUE;
+ else if (!strcasecmp (val, "false")
+ || !strcasecmp (val, "off"))
+ rv = ISC_FALSE;
+ else
+ parse_error(cfile,
+ "boolean value (true/false/on/off) expected");
+ parse_semi(cfile);
+ return rv;
+}
+
+/*
+ * data_expression :== SUBSTRING LPAREN data-expression COMMA
+ * numeric-expression COMMA
+ * numeric-expression RPAREN |
+ * CONCAT LPAREN data-expression COMMA
+ * data-expression RPAREN
+ * SUFFIX LPAREN data_expression COMMA
+ * numeric-expression RPAREN |
+ * LCASE LPAREN data_expression RPAREN |
+ * UCASE LPAREN data_expression RPAREN |
+ * OPTION option_name |
+ * HARDWARE |
+ * PACKET LPAREN numeric-expression COMMA
+ * numeric-expression RPAREN |
+ * V6RELAY LPAREN numeric-expression COMMA
+ * data-expression RPAREN |
+ * STRING |
+ * colon_separated_hex_list
+ */
+
+isc_boolean_t
+parse_data_expression(struct element *expr,
+ struct parse *cfile,
+ isc_boolean_t *lose)
+{
+ /* Parse an expression... */
+ if (!parse_expression(expr, cfile, lose, context_data,
+ NULL, expr_none))
+ return ISC_FALSE;
+
+ if (!is_data_expression(expr) &&
+ !mapContains(expr, "variable-reference") &&
+ !mapContains(expr, "funcall"))
+ parse_error(cfile, "Expecting a data expression.");
+ return ISC_TRUE;
+}
+
+/*
+ * numeric-expression :== EXTRACT_INT LPAREN data-expression
+ * COMMA number RPAREN |
+ * NUMBER
+ */
+
+isc_boolean_t
+parse_numeric_expression(struct element *expr,
+ struct parse *cfile,
+ isc_boolean_t *lose)
+{
+ /* Parse an expression... */
+ if (!parse_expression(expr, cfile, lose, context_numeric,
+ NULL, expr_none))
+ return ISC_FALSE;
+
+ if (!is_numeric_expression(expr) &&
+ !mapContains(expr, "variable-reference") &&
+ !mapContains(expr, "funcall"))
+ parse_error(cfile, "Expecting a numeric expression.");
+ return ISC_TRUE;
+}
+
+/* Parse a subexpression that does not contain a binary operator. */
+
+isc_boolean_t
+parse_non_binary(struct element *expr,
+ struct parse *cfile,
+ isc_boolean_t *lose,
+ enum expression_context context)
+{
+ enum dhcp_token token;
+ const char *val;
+ struct element *nexp;
+ struct element *arg;
+ struct element *chain;
+ struct string *data;
+ struct comment *comment;
+ struct option *option;
+ isc_boolean_t known;
+ unsigned len;
+
+ token = peek_token(&val, NULL, cfile);
+
+ /* Check for unary operators... */
+ switch (token) {
+ case CHECK:
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != STRING)
+ parse_error(cfile, "string expected.");
+ nexp = createString(makeString(-1, val));
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "check");
+ break;
+
+ case TOKEN_NOT:
+ skip_token(&val, NULL, cfile);
+ nexp = createMap();
+
+ if (!parse_non_binary(nexp, cfile, lose, context_boolean)) {
+ if (!*lose)
+ parse_error(cfile, "expression expected");
+ *lose = ISC_TRUE;
+ return ISC_FALSE;
+ }
+ if (!is_boolean_expression(nexp))
+ parse_error(cfile, "boolean expression expected");
+ if (!nexp->skip) {
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ mapSet(expr, nexp, "not");
+ break;
+
+ case LPAREN:
+ skip_token(&val, NULL, cfile);
+ if (!parse_expression(expr, cfile, lose, context,
+ NULL, expr_none)) {
+ if (!*lose)
+ parse_error(cfile, "expression expected");
+ *lose = ISC_TRUE;
+ return ISC_FALSE;
+ }
+ token = next_token(&val, NULL, cfile);
+ if (token != RPAREN)
+ parse_error(cfile, "right paren expected");
+ break;
+
+ case EXISTS:
+ skip_token(&val, NULL, cfile);
+ known = ISC_FALSE;
+ option = parse_option_name(cfile, ISC_FALSE, &known);
+ if (option == NULL) {
+ *lose = ISC_TRUE;
+ return ISC_FALSE;;
+ }
+ nexp = createMap();
+ /* push infos to get it back trying to reduce it */
+ mapSet(nexp,
+ createString(makeString(-1, option->space->old)),
+ "universe");
+ mapSet(nexp,
+ createString(makeString(-1, option->name)),
+ "name");
+ mapSet(nexp, createInt(option->code), "code");
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "exists");
+ break;
+
+ case STATIC:
+ skip_token(&val, NULL, cfile);
+ nexp = createNull();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "static");
+ break;
+
+ case KNOWN:
+ skip_token(&val, NULL, cfile);
+ nexp = createNull();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "known");
+ break;
+
+ case SUBSTRING:
+ skip_token(&val, NULL, cfile);
+ nexp = createMap();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "substring");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN) {
+ nolparen:
+ parse_error(cfile, "left parenthesis expected.");
+ }
+
+ arg = createMap();
+ if (!parse_data_expression(arg, cfile, lose)) {
+ nodata:
+ if (!*lose)
+ parse_error(cfile,
+ "expecting data expression.");
+ return ISC_FALSE;
+ }
+ mapSet(nexp, arg, "expression");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != COMMA) {
+ nocomma:
+ parse_error(cfile, "comma expected.");
+ }
+
+ arg = createMap();
+ if (!parse_numeric_expression(arg, cfile, lose)) {
+ nonum:
+ if (!*lose)
+ parse_error(cfile,
+ "expecting numeric expression.");
+ return ISC_FALSE;
+ }
+ mapSet(nexp, arg, "offset");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ arg = createMap();
+ if (!parse_numeric_expression(arg, cfile, lose))
+ goto nonum;
+ mapSet(nexp, arg, "length");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != RPAREN) {
+ norparen:
+ parse_error(cfile, "right parenthesis expected.");
+ }
+ break;
+
+ case SUFFIX:
+ skip_token(&val, NULL, cfile);
+ nexp = createMap();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "suffix");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ arg = createMap();
+ if (!parse_data_expression(arg, cfile, lose))
+ goto nodata;
+ mapSet(nexp, arg, "expression");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ arg = createMap();
+ if (!parse_numeric_expression(arg, cfile, lose))
+ goto nonum;
+ mapSet(nexp, arg, "length");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case LCASE:
+ skip_token(&val, NULL, cfile);
+ nexp = createMap();
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ if (!parse_data_expression(nexp, cfile, lose))
+ goto nodata;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ if (!nexp->skip) {
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ mapSet(expr, nexp, "lowercase");
+ break;
+
+ case UCASE:
+ skip_token(&val, NULL, cfile);
+ nexp = createMap();
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ if (!parse_data_expression(nexp, cfile, lose))
+ goto nodata;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ if (!nexp->skip) {
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ mapSet(expr, nexp, "uppercase");
+ break;
+
+ case CONCAT:
+ skip_token(&val, NULL, cfile);
+ nexp = createMap();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "concat");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ arg = createMap();
+ if (!parse_data_expression(arg, cfile, lose))
+ goto nodata;
+ mapSet(nexp, arg, "left");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ concat_another:
+ arg = createMap();
+ if (!parse_data_expression(arg, cfile, lose))
+ goto nodata;
+
+ token = next_token(&val, NULL, cfile);
+
+ if (token == COMMA) {
+ chain = createMap();
+ mapSet(nexp, chain, "right");
+ nexp = createMap();
+ mapSet(chain, nexp, "concat");
+ mapSet(nexp, arg, "left");
+ goto concat_another;
+ }
+ mapSet(nexp, arg, "right");
+
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case BINARY_TO_ASCII:
+ skip_token(&val, NULL, cfile);
+ nexp = createMap();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "binary-to-ascii");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ arg = createMap();
+ if (!parse_numeric_expression(arg, cfile, lose))
+ goto nodata;
+ mapSet(nexp, arg, "base");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ arg = createMap();
+ if (!parse_numeric_expression(arg, cfile, lose))
+ goto nodata;
+ mapSet(nexp, arg, "width");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ arg = createMap();
+ if (!parse_data_expression(arg, cfile, lose))
+ goto nodata;
+ mapSet(nexp, arg, "separator");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ arg = createMap();
+ if (!parse_data_expression(arg, cfile, lose))
+ goto nodata;
+ mapSet(nexp, arg, "buffer");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case REVERSE:
+ skip_token(&val, NULL, cfile);
+ nexp = createMap();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "reverse");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ arg = createMap();
+ if (!(parse_numeric_expression(arg, cfile, lose)))
+ goto nodata;
+ mapSet(nexp, arg, "width");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ arg = createMap();
+ if (!(parse_data_expression(arg, cfile, lose)))
+ goto nodata;
+ mapSet(nexp, arg, "buffer");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case PICK:
+ /* pick (a, b, c) actually produces an internal representation
+ that looks like pick (a, pick (b, pick (c, nil))). */
+ skip_token(&val, NULL, cfile);
+ nexp = createList();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "pick-first-value");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ do {
+ arg = createMap();
+ if (!(parse_data_expression(arg, cfile, lose)))
+ goto nodata;
+ listPush(nexp, arg);
+
+ token = next_token(&val, NULL, cfile);
+ } while (token == COMMA);
+
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case OPTION:
+ case CONFIG_OPTION:
+ skip_token(&val, NULL, cfile);
+ known = ISC_FALSE;
+ option = parse_option_name(cfile, ISC_FALSE, &known);
+ if (option == NULL) {
+ *lose = ISC_TRUE;
+ return ISC_FALSE;
+ }
+ nexp = createMap();
+ mapSet(nexp,
+ createString(makeString(-1, option->space->old)),
+ "universe");
+ mapSet(nexp,
+ createString(makeString(-1, option->name)),
+ "name");
+ mapSet(nexp, createInt(option->code), "code");
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ if (token == OPTION)
+ mapSet(expr, nexp, "option");
+ else {
+ createComment("/// config-option is "
+ "not supported by Kea");
+ TAILQ_CONCAT(&nexp->comments, &cfile->comments);
+ mapSet(expr, nexp, "config-option");
+ }
+ break;
+
+ case HARDWARE:
+ skip_token(&val, NULL, cfile);
+ nexp = createNull();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "hardware");
+ break;
+
+ case LEASED_ADDRESS:
+ skip_token(&val, NULL, cfile);
+ nexp = createNull();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "leased-address");
+ break;
+
+ case CLIENT_STATE:
+ skip_token(&val, NULL, cfile);
+ nexp = createNull();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "client-state");
+ break;
+
+ case FILENAME:
+ skip_token(&val, NULL, cfile);
+ nexp = createNull();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "filename");
+ break;
+
+ case SERVER_NAME:
+ skip_token(&val, NULL, cfile);
+ nexp = createNull();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "server-name");
+ break;
+
+ case LEASE_TIME:
+ skip_token(&val, NULL, cfile);
+ nexp = createNull();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "lease-time");
+ break;
+
+ case TOKEN_NULL:
+ skip_token(&val, NULL, cfile);
+ /* can look at context to return directly ""? */
+ nexp = createNull();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "null");
+ break;
+
+ case HOST_DECL_NAME:
+ skip_token(&val, NULL, cfile);
+ nexp = createNull();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "host-decl-name");
+ break;
+
+ case PACKET:
+ skip_token(&val, NULL, cfile);
+ nexp = createMap();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "packet");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ arg = createMap();
+ if (!parse_numeric_expression(arg, cfile, lose))
+ goto nonum;
+ mapSet(nexp, arg, "offset");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ arg = createMap();
+ if (!parse_numeric_expression(arg, cfile, lose))
+ goto nonum;
+ mapSet(nexp, arg, "length");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case STRING:
+ skip_token(&val, &len, cfile);
+ resetString(expr, makeString(len, val));
+ break;
+
+ case EXTRACT_INT:
+ skip_token(&val, NULL, cfile);
+ nexp = createMap();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN)
+ parse_error(cfile, "left parenthesis expected.");
+
+ if (!parse_data_expression(nexp, cfile, lose)) {
+ if (!*lose)
+ parse_error(cfile,
+ "expecting data expression.");
+ return ISC_FALSE;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != COMMA)
+ parse_error(cfile, "comma expected.");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile, "number expected.");
+ switch (atoi(val)) {
+ case 8:
+ mapSet(expr, nexp, "extract-int8");
+ break;
+
+ case 16:
+ mapSet(expr, nexp, "extract-int16");
+ break;
+
+ case 32:
+ mapSet(expr, nexp, "extract-int32");
+ break;
+
+ default:
+ parse_error(cfile, "unsupported integer size %s", val);
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != RPAREN)
+ parse_error(cfile, "right parenthesis expected.");
+ break;
+
+ case ENCODE_INT:
+ skip_token(&val, NULL, cfile);
+ nexp = createMap();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN)
+ parse_error(cfile, "left parenthesis expected.");
+
+ if (!parse_numeric_expression(nexp, cfile, lose))
+ parse_error(cfile, "expecting numeric expression.");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != COMMA)
+ parse_error(cfile, "comma expected.");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER)
+ parse_error(cfile, "number expected.");
+ switch (atoi(val)) {
+ case 8:
+ mapSet(expr, nexp, "encode-int8");
+ break;
+
+ case 16:
+ mapSet(expr, nexp, "encode-int16");
+ break;
+
+ case 32:
+ mapSet(expr, nexp, "encode-int32");
+ break;
+
+ default:
+ parse_error(cfile, "unsupported integer size %s", val);
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != RPAREN)
+ parse_error(cfile, "right parenthesis expected.");
+ break;
+
+ case NUMBER:
+ /* If we're in a numeric context, this should just be a
+ number, by itself. */
+ if (context == context_numeric ||
+ context == context_data_or_numeric) {
+ skip_token(&val, NULL, cfile);
+ /* can also return a const-int */
+ resetInt(expr, atoi(val));
+ break;
+ }
+
+ case NUMBER_OR_NAME:
+ /* Return a const-data to make a difference with
+ a string literal. createHexa() adds 0x */
+ mapSet(expr, createHexa(parse_hexa(cfile)), "const-data");
+ break;
+
+ case NS_FORMERR:
+ skip_token(&val, NULL, cfile);
+#ifndef FORMERR
+#define FORMERR 1
+#endif
+ resetInt(expr, FORMERR);
+ comment = createComment("/// constant FORMERR(1)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case NS_NOERROR:
+ skip_token(&val, NULL, cfile);
+#ifndef ISC_R_SUCCESS
+#define ISC_R_SUCCESS 0
+#endif
+ resetInt(expr, ISC_R_SUCCESS);
+ comment = createComment("/// constant ISC_R_SUCCESS(0)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case NS_NOTAUTH:
+ skip_token(&val, NULL, cfile);
+#ifndef DHCP_R_NOTAUTH
+#define DHCP_R_NOTAUTH ((6 << 16) + 21)
+#endif
+ resetInt(expr, DHCP_R_NOTAUTH);
+ comment = createComment("/// constant DHCP_R_NOTAUTH(393237)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case NS_NOTIMP:
+ skip_token(&val, NULL, cfile);
+#ifndef ISC_R_NOTIMPLEMENTED
+#define ISC_R_NOTIMPLEMENTED 27
+#endif
+ resetInt(expr, ISC_R_NOTIMPLEMENTED);
+ comment = createComment("/// constant ISC_R_NOTIMPLEMENTED(27)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case NS_NOTZONE:
+ skip_token(&val, NULL, cfile);
+#ifndef DHCP_R_NOTZONE
+#define DHCP_R_NOTZONE ((6 << 16) + 22)
+#endif
+ resetInt(expr, DHCP_R_NOTZONE);
+ comment = createComment("/// constant DHCP_R_NOTZONE(393238)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case NS_NXDOMAIN:
+ skip_token(&val, NULL, cfile);
+#ifndef DHCP_R_NXDOMAIN
+#define DHCP_R_NXDOMAIN ((6 << 16) + 15)
+#endif
+ resetInt(expr, DHCP_R_NXDOMAIN);
+ comment = createComment("/// constant DHCP_R_NXDOMAIN(393231)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case NS_NXRRSET:
+ skip_token(&val, NULL, cfile);
+#ifndef DHCP_R_NXRRSET
+#define DHCP_R_NXRRSET ((6 << 16) + 20)
+#endif
+ resetInt(expr, DHCP_R_NXRRSET);
+ comment = createComment("/// constant DHCP_R_NXRRSET(393236)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case NS_REFUSED:
+ skip_token(&val, NULL, cfile);
+#ifndef DHCP_R_REFUSED
+#define DHCP_R_REFUSED ((6 << 16) + 17)
+#endif
+ resetInt(expr, DHCP_R_REFUSED);
+ comment = createComment("/// constant DHCP_R_REFUSED(393233)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case NS_SERVFAIL:
+ skip_token(&val, NULL, cfile);
+#ifndef DHCP_R_SERVFAIL
+#define DHCP_R_SERVFAIL ((6 << 16) + 14)
+#endif
+ resetInt(expr, DHCP_R_SERVFAIL);
+ comment = createComment("/// constant DHCP_R_SERVFAIL(393230)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case NS_YXDOMAIN:
+ skip_token(&val, NULL, cfile);
+#ifndef DHCP_R_YXDOMAIN
+#define DHCP_R_YXDOMAIN ((6 << 16) + 18)
+#endif
+ resetInt(expr, DHCP_R_YXDOMAIN);
+ comment = createComment("/// constant DHCP_R_YXDOMAIN(393234)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case NS_YXRRSET:
+ skip_token(&val, NULL, cfile);
+#ifndef DHCP_R_YXRRSET
+#define DHCP_R_YXRRSET ((6 << 16) + 19)
+#endif
+ resetInt(expr, DHCP_R_YXRRSET);
+ comment = createComment("/// constant DHCP_R_YXRRSET(393235)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case BOOTING:
+ skip_token(&val, NULL, cfile);
+#ifndef S_INIT
+#define S_INIT 2
+#endif
+ resetInt(expr, S_INIT);
+ comment = createComment("/// constant S_INIT(2)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case REBOOT:
+ skip_token(&val, NULL, cfile);
+#ifndef S_REBOOTING
+#define S_REBOOTING 1
+#endif
+ resetInt(expr, S_REBOOTING);
+ comment = createComment("/// constant S_REBOOTING(1)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case SELECT:
+ skip_token(&val, NULL, cfile);
+#ifndef S_SELECTING
+#define S_SELECTING 3
+#endif
+ resetInt(expr, S_SELECTING);
+ comment = createComment("/// constant S_SELECTING(3)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case REQUEST:
+ skip_token(&val, NULL, cfile);
+#ifndef S_REQUESTING
+#define S_REQUESTING 4
+#endif
+ resetInt(expr, S_REQUESTING);
+ comment = createComment("/// constant S_REQUESTING(4)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case BOUND:
+ skip_token(&val, NULL, cfile);
+#ifndef S_BOUND
+#define S_BOUND 5
+#endif
+ resetInt(expr, S_BOUND);
+ comment = createComment("/// constant S_BOUND(5)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case RENEW:
+ skip_token(&val, NULL, cfile);
+#ifndef S_RENEWING
+#define S_RENEWING 6
+#endif
+ resetInt(expr, S_RENEWING);
+ comment = createComment("/// constant S_RENEWING(6)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case REBIND:
+ skip_token(&val, NULL, cfile);
+#ifndef S_REBINDING
+#define S_REBINDING 7
+#endif
+ resetInt(expr, S_REBINDING);
+ comment = createComment("/// constant S_REBINDING(7)");
+ TAILQ_INSERT_TAIL(&expr->comments, comment);
+ break;
+
+ case DEFINED:
+ skip_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NAME && token != NUMBER_OR_NAME)
+ parse_error(cfile, "%s can't be a variable name", val);
+
+ nexp = createString(makeString(-1, val));
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "variable-exists");
+ token = next_token(&val, NULL, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ /* This parses 'gethostname()'. */
+ case GETHOSTNAME:
+ skip_token(&val, NULL, cfile);
+ nexp = createNull();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "gethostname");
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case GETHOSTBYNAME:
+ skip_token(&val, NULL, cfile);
+ token = next_token(NULL, NULL, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ /* The argument is a quoted string. */
+ token = next_token(&val, NULL, cfile);
+ if (token != STRING)
+ parse_error(cfile, "Expecting quoted literal: "
+ "\"foo.example.com\"");
+ nexp = createString(makeString(-1, val));
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "gethostbyname");
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ case V6RELAY:
+ skip_token(&val, NULL, cfile);
+ nexp = createMap();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "v6relay");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LPAREN)
+ goto nolparen;
+
+ arg = createMap();
+ if (!parse_numeric_expression(arg, cfile, lose))
+ goto nodata;
+ mapSet(nexp, arg, "relay");
+
+ token = next_token(&val, NULL, cfile);
+ if (token != COMMA)
+ goto nocomma;
+
+ arg = createMap();
+ if (!parse_data_expression(arg, cfile, lose))
+ goto nodata;
+ mapSet(nexp, arg, "relay-option");
+
+ token = next_token(&val, NULL, cfile);
+
+ if (token != RPAREN)
+ goto norparen;
+ break;
+
+ /* Not a valid start to an expression... */
+ default:
+ if (token != NAME && token != NUMBER_OR_NAME)
+ return ISC_FALSE;
+
+ skip_token(&val, NULL, cfile);
+
+ /* Save the name of the variable being referenced. */
+ data = makeString(-1, val);
+
+ /* Simple variable reference, as far as we can tell. */
+ token = peek_token(&val, NULL, cfile);
+ if (token != LPAREN) {
+ nexp = createString(data);
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "variable-reference");
+ break;
+ }
+
+ skip_token(&val, NULL, cfile);
+ nexp = createMap();
+ nexp->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(expr, nexp, "funcall");
+ chain = createString(data);
+ mapSet(nexp, chain, "name");
+
+ /* Now parse the argument list. */
+ chain = createList();
+ do {
+ arg = createMap();
+ if (!parse_expression(arg, cfile, lose, context_any,
+ NULL, expr_none)) {
+ if (!*lose)
+ parse_error(cfile,
+ "expecting expression.");
+ skip_to_semi(cfile);
+ return ISC_FALSE;
+ }
+ listPush(chain, arg);
+ token = next_token(&val, NULL, cfile);
+ } while (token == COMMA);
+ if (token != RPAREN)
+ parse_error(cfile, "Right parenthesis expected.");
+ mapSet(nexp, chain, "arguments");
+ break;
+ }
+ return ISC_TRUE;
+}
+
+/* Parse an expression. */
+
+isc_boolean_t
+parse_expression(struct element *expr, struct parse *cfile,
+ isc_boolean_t *lose, enum expression_context context,
+ struct element *lhs, enum expr_op binop)
+{
+ enum dhcp_token token;
+ const char *val;
+ struct element *rhs, *tmp;
+ enum expr_op next_op;
+ enum expression_context
+ lhs_context = context_any,
+ rhs_context = context_any;
+ const char *binop_name;
+
+new_rhs:
+ rhs = createMap();
+ if (!parse_non_binary(rhs, cfile, lose, context)) {
+ /* If we already have a left-hand side, then it's not
+ okay for there not to be a right-hand side here, so
+ we need to flag it as an error. */
+ if (lhs)
+ if (!*lose)
+ parse_error(cfile,
+ "expecting right-hand side.");
+ return ISC_FALSE;
+ }
+
+ /* At this point, rhs contains either an entire subexpression,
+ or at least a left-hand-side. If we do not see a binary token
+ as the next token, we're done with the expression. */
+
+ token = peek_token(&val, NULL, cfile);
+ switch (token) {
+ case BANG:
+ skip_token(&val, NULL, cfile);
+ token = peek_token(&val, NULL, cfile);
+ if (token != EQUAL)
+ parse_error(cfile, "! in boolean context without =");
+ next_op = expr_not_equal;
+ context = expression_context(rhs);
+ break;
+
+ case EQUAL:
+ next_op = expr_equal;
+ context = expression_context(rhs);
+ break;
+
+ case TILDE:
+ skip_token(&val, NULL, cfile);
+ token = peek_token(&val, NULL, cfile);
+
+ if (token == TILDE)
+ next_op = expr_iregex_match;
+ else if (token == EQUAL)
+ next_op = expr_regex_match;
+ else
+ parse_error(cfile, "expecting ~= or ~~ operator");
+
+ context = expression_context(rhs);
+ break;
+
+ case AND:
+ next_op = expr_and;
+ context = expression_context(rhs);
+ break;
+
+ case OR:
+ next_op = expr_or;
+ context = expression_context(rhs);
+ break;
+
+ case PLUS:
+ next_op = expr_add;
+ context = expression_context(rhs);
+ break;
+
+ case MINUS:
+ next_op = expr_subtract;
+ context = expression_context(rhs);
+ break;
+
+ case SLASH:
+ next_op = expr_divide;
+ context = expression_context(rhs);
+ break;
+
+ case ASTERISK:
+ next_op = expr_multiply;
+ context = expression_context(rhs);
+ break;
+
+ case PERCENT:
+ next_op = expr_remainder;
+ context = expression_context(rhs);
+ break;
+
+ case AMPERSAND:
+ next_op = expr_binary_and;
+ context = expression_context(rhs);
+ break;
+
+ case PIPE:
+ next_op = expr_binary_or;
+ context = expression_context(rhs);
+ break;
+
+ case CARET:
+ next_op = expr_binary_xor;
+ context = expression_context(rhs);
+ break;
+
+ default:
+ next_op = expr_none;
+ }
+
+ /* If we have no lhs yet, we just parsed it. */
+ if (!lhs) {
+ /* If there was no operator following what we just parsed,
+ then we're done - return it. */
+ if (next_op == expr_none) {
+ resetBy(expr, rhs);
+ return ISC_TRUE;
+ }
+
+ lhs = rhs;
+ rhs = NULL;
+ binop = next_op;
+ skip_token(&val, NULL, cfile);
+ goto new_rhs;
+ }
+
+ /* If the next binary operator is of greater precedence than the
+ * current operator, then rhs we have parsed so far is actually
+ * the lhs of the next operator. To get this value, we have to
+ * recurse.
+ */
+ if (binop != expr_none && next_op != expr_none &&
+ op_precedence(binop, next_op) < 0) {
+
+ /* Eat the subexpression operator token, which we pass to
+ * parse_expression...we only peek()'d earlier.
+ */
+ skip_token(&val, NULL, cfile);
+
+ /* Continue parsing of the right hand side with that token. */
+ tmp = rhs;
+ rhs = createMap();
+ if (!parse_expression(rhs, cfile, lose, op_context(next_op),
+ tmp, next_op)) {
+ if (!*lose)
+ parse_error(cfile,
+ "expecting a subexpression");
+ return ISC_FALSE;
+ }
+ next_op = expr_none;
+ }
+
+ binop_name = "none";
+ if (binop != expr_none) {
+ rhs_context = expression_context(rhs);
+ lhs_context = expression_context(lhs);
+
+ if ((rhs_context != context_any) &&
+ (lhs_context != context_any) &&
+ (rhs_context != lhs_context))
+ parse_error(cfile, "illegal expression relating "
+ "different types");
+
+ switch (binop) {
+ case expr_not_equal:
+ binop_name = "not-equal";
+ goto data_numeric;
+ case expr_equal:
+ binop_name = "equal";
+ data_numeric:
+ if ((rhs_context != context_data_or_numeric) &&
+ (rhs_context != context_data) &&
+ (rhs_context != context_numeric) &&
+ (rhs_context != context_any))
+ parse_error(cfile, "expecting data/numeric "
+ "expression");
+ break;
+
+ case expr_iregex_match:
+ binop_name = "iregex-match";
+ break;
+
+ case expr_regex_match:
+ binop_name = "regex-match";
+ if (expression_context(rhs) != context_data)
+ parse_error(cfile,
+ "expecting data expression");
+ break;
+
+ case expr_and:
+ binop_name = "and";
+ goto boolean;
+ case expr_or:
+ binop_name = "or";
+ boolean:
+ if ((rhs_context != context_boolean) &&
+ (rhs_context != context_any)) {
+ parse_error(cfile,
+ "expecting boolean expressions");
+ }
+ break;
+
+ case expr_add:
+ binop_name = "add";
+ goto numeric;
+ case expr_subtract:
+ binop_name = "subtract";
+ goto numeric;
+ case expr_divide:
+ binop_name = "divide";
+ goto numeric;
+ case expr_multiply:
+ binop_name = "multiply";
+ goto numeric;
+ case expr_remainder:
+ binop_name = "remainder";
+ goto numeric;
+ case expr_binary_and:
+ binop_name = "binary-and";
+ goto numeric;
+ case expr_binary_or:
+ binop_name = "binary-or";
+ goto numeric;
+ case expr_binary_xor:
+ binop_name = "binary-xor";
+ numeric:
+ if ((rhs_context != context_numeric) &&
+ (rhs_context != context_any))
+ parse_error(cfile,
+ "expecting numeric expressions");
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Now, if we didn't find a binary operator, we're done parsing
+ this subexpression, so combine it with the preceding binary
+ operator and return the result. */
+ if (next_op == expr_none) {
+ tmp = createMap();
+ tmp->skip = ISC_TRUE;
+ mapSet(expr, tmp, binop_name);
+ /* All the binary operators' data union members
+ are the same, so we'll cheat and use the member
+ for the equals operator. */
+ mapSet(tmp, lhs, "left");
+ mapSet(tmp, rhs, "right");
+ return ISC_TRUE;;
+ }
+
+ /* Eat the operator token - we now know it was a binary operator... */
+ skip_token(&val, NULL, cfile);
+
+ /* Now combine the LHS and the RHS using binop. */
+ tmp = createMap();
+ tmp->skip = ISC_TRUE;
+
+ /* Store the LHS and RHS. */
+ mapSet(tmp, lhs, "left");
+ mapSet(tmp, rhs, "right");
+
+ lhs = createMap();
+ mapSet(lhs, tmp, binop_name);
+
+ tmp = NULL;
+ rhs = NULL;
+
+ binop = next_op;
+ goto new_rhs;
+}
+
+/* Escape embedded commas, detected heading and leading space */
+struct string *
+escape_option_string(unsigned len, const char *val,
+ isc_boolean_t *require_binary,
+ isc_boolean_t *modified)
+{
+ struct string *result;
+ struct string *add;
+ unsigned i;
+ char s[2];
+
+ result = allocString();
+ add = allocString();
+ if ((len > 0) && (isspace(val[0]) || isspace(val[len - 1]))) {
+ *require_binary = ISC_TRUE;
+ return result;
+ }
+ for (i = 0; i < len; i++) {
+ if (val[i] == ',') {
+ add->length = 2;
+ add->content = "\\,";
+ *modified = ISC_TRUE;
+ } else {
+ add->length = 1;
+ s[0] = val[i];
+ s[1] = 0;
+ add->content = s;
+ }
+ concatString(result, add);
+ }
+ free(add);
+ return result;
+}
+
+isc_boolean_t
+parse_option_data(struct element *expr,
+ struct parse *cfile,
+ struct option *option)
+{
+ const char *val;
+ const char *fmt;
+ enum dhcp_token token;
+ unsigned len;
+ struct string *data;
+ struct string *saved;
+ struct string *item;
+ struct element *elem;
+ struct comment *comment;
+ isc_boolean_t require_binary = ISC_FALSE;
+ isc_boolean_t canon_bool = ISC_FALSE;
+ isc_boolean_t modified = ISC_FALSE;
+
+ /* Save the initial content */
+ saved = allocString();
+ save_parse_state(cfile);
+ for (;;) {
+ token = next_raw_token(&val, &len, cfile);
+ if ((token == SEMI) || (token == END_OF_FILE))
+ break;
+ item = makeString(len, val);
+ if (token == STRING) {
+ appendString(saved, "\"");
+ concatString(saved, item);
+ appendString(saved, "\"");
+ } else
+ concatString(saved, item);
+ }
+ restore_parse_state(cfile);
+
+ elem = createString(saved);
+ elem->skip = ISC_TRUE;
+ mapSet(expr, elem, "original-data");
+
+ /* Check for binary case */
+
+ fmt = option->format;
+
+ if ((fmt == NULL) || (*fmt == 0))
+ parse_error(cfile, "unknown format for option %s.%s\n",
+ option->space->name, option->name);
+
+ if ((strchr(fmt, 'Y') != NULL) || (strchr(fmt, 'A') != NULL) ||
+ (strchr(fmt, 'E') != NULL) || (strchr(fmt, 'o') != NULL) ||
+ (*fmt == 'X') || (*fmt == 'u'))
+ return parse_option_binary(expr, cfile, option, ISC_FALSE);
+
+ if (strchr(fmt, 'N') != NULL)
+ parse_error(cfile, "unsupported format %s for option %s.%s\n",
+ fmt, option->space->name, option->name);
+
+ data = allocString();
+
+ save_parse_state(cfile);
+ /* Just collect data expecting ISC DHCP and Kea are compatible */
+ do {
+ /* Set fmt one char back for 'a'. */
+ if ((fmt != option->format) && (*fmt == 'a'))
+ fmt -= 1;
+
+ do {
+ if (*fmt == 'a')
+ break;
+ if (data->length != 0)
+ appendString(data, ", ");
+ item = parse_option_token(cfile, fmt, &require_binary,
+ &canon_bool, &modified);
+ if ((*fmt == 'D') && (fmt[1] == 'c'))
+ fmt++;
+ if (require_binary) {
+ restore_parse_state(cfile);
+ return parse_option_binary(expr, cfile, option,
+ item == NULL);
+ }
+ if (item == NULL)
+ parse_error(cfile, "parse_option_data failed");
+ concatString(data, item);
+ fmt++;
+ } while (*fmt != '\0');
+
+ if (*fmt == 'a') {
+ token = peek_token(&val, NULL, cfile);
+ /* Comma means: continue with next element in array */
+ if (token == COMMA) {
+ skip_token(&val, NULL, cfile);
+ continue;
+ }
+ /* no comma: end of array.
+ end of string means: leave the loop */
+ if (fmt[1] == '\0')
+ break;
+ /* 'a' means: go on with next char */
+ if (*fmt == 'a') {
+ fmt++;
+ continue;
+ }
+ }
+ } while (*fmt == 'a');
+
+ if (!modified || eqString(saved, data))
+ mapRemove(expr, "original-data");
+
+ elem = createString(data);
+ if (canon_bool) {
+ comment = createComment("/// canonized booleans to "
+ "lowercase true or false");
+ TAILQ_INSERT_TAIL(&elem->comments, comment);
+ }
+ mapSet(expr, elem, "data");
+
+ return ISC_TRUE;
+}
+
+isc_boolean_t
+parse_option_binary(struct element *expr, struct parse *cfile,
+ struct option *option, isc_boolean_t ambiguous)
+{
+ const char *val;
+ const char *fmt;
+ enum dhcp_token token;
+ struct string *data;
+ struct string *item;
+ struct element *elem;
+ struct comment *comment;
+ const char *g;
+
+ data = allocString();
+ fmt = option->format;
+
+ mapSet(expr, createBool(ISC_FALSE), "csv-format");
+
+ /* Just collect data expecting ISC DHCP and Kea are compatible */
+ do {
+ /* Set fmt to start of format for 'A' and one char back
+ * for 'a'.
+ */
+ if ((fmt != option->format) && (*fmt == 'a'))
+ fmt -= 1;
+ else if (*fmt == 'A')
+ fmt = option->format;
+
+ do {
+ if ((*fmt == 'A') || (*fmt == 'a'))
+ break;
+ if (*fmt == 'o') {
+ /* consume the optional flag */
+ fmt++;
+ continue;
+ }
+
+ if (fmt[1] == 'o') {
+ /*
+ * A value for the current format is
+ * optional - check to see if the next
+ * token is a semi-colon if so we don't
+ * need to parse it and doing so would
+ * consume the semi-colon which our
+ * caller is expecting to parse
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI) {
+ fmt++;
+ continue;
+ }
+ }
+
+ item = parse_option_token_binary(cfile, fmt);
+ switch (*fmt) {
+ case 'E':
+ g = strchr(fmt, '.');
+ if (g == NULL)
+ parse_error(cfile,
+ "malformed encapsulation "
+ "format (bug!)");
+ fmt = g;
+ break;
+ case 'D':
+ if (fmt[1] == 'c')
+ fmt++;
+ break;
+ case 'N':
+ g = strchr(fmt, '.');
+ if (g == NULL)
+ parse_error(cfile,
+ "malformed enumeration "
+ "format (bug!)");
+ fmt = g;
+ break;
+ }
+ if (item != NULL)
+ concatString(data, item);
+ else if (fmt[1] != 'o')
+ parse_error(cfile, "parse_option_token_binary "
+ "failed");
+ fmt++;
+ } while (*fmt != '\0');
+
+ if ((*fmt == 'A') || (*fmt == 'a')) {
+ token = peek_token(&val, NULL, cfile);
+ /* Comma means: continue with next element in array */
+ if (token == COMMA) {
+ skip_token(&val, NULL, cfile);
+ continue;
+ }
+ /* no comma: end of array.
+ 'A' or end of string means: leave the loop */
+ if ((*fmt == 'A') || (fmt[1] == '\0'))
+ break;
+ /* 'a' means: go on with next char */
+ if (*fmt == 'a') {
+ fmt++;
+ continue;
+ }
+ }
+ } while ((*fmt == 'A') || (*fmt == 'a'));
+
+ elem = mapGet(expr, "original-data");
+ if ((elem != NULL) && eqString(stringValue(elem), data))
+ mapRemove(expr, "original-data");
+
+ elem = createString(data);
+ if (ambiguous) {
+ comment = createComment("/// Please consider to change "
+ "last type in the record to binary");
+ TAILQ_INSERT_TAIL(&elem->comments, comment);
+ comment = createComment("/// Reference Kea #246");
+ TAILQ_INSERT_TAIL(&elem->comments, comment);
+ expr->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ mapSet(expr, elem, "data");
+
+ return ISC_TRUE;
+}
+
+struct string *
+parse_option_textbin(struct parse *cfile, struct option *option)
+{
+ struct element *expr;
+ struct element *data;
+ const char *fmt;
+
+ expr = createMap();
+ fmt = option->format;
+
+ if ((fmt == NULL) || (*fmt == 0))
+ parse_error(cfile, "unknown format for option %s.%s\n",
+ option->space->name, option->name);
+
+ if (strcmp(fmt, "t") != 0) {
+ if (!parse_option_binary(expr, cfile, option, ISC_FALSE))
+ parse_error(cfile, "can't parse binary option data");
+ data = mapGet(expr, "data");
+ if (data == NULL)
+ parse_error(cfile, "can't get binary option data");
+ if (data->type != ELEMENT_STRING)
+ parse_error(cfile, "option data must be binary");
+ return stringValue(data);
+ }
+
+ if (!parse_option_data(expr, cfile, option))
+ parse_error(cfile, "can't parse text option data");
+ data = mapGet(expr, "data");
+ if (data == NULL)
+ parse_error(cfile, "can't get test option data");
+ if (data->type != ELEMENT_STRING)
+ parse_error(cfile, "option data must be a string");
+ return quote(stringValue(data));
+}
+
+/* option-statement :== identifier DOT identifier <syntax> SEMI
+ | identifier <syntax> SEMI
+
+ Option syntax is handled specially through format strings, so it
+ would be painful to come up with BNF for it. However, it always
+ starts as above and ends in a SEMI. */
+
+isc_boolean_t
+parse_option_statement(struct element *result,
+ struct parse *cfile,
+ struct option *option,
+ enum statement_op op)
+{
+ const char *val;
+ enum dhcp_token token;
+ struct element *expr;
+ struct element *opt_data;
+ struct element *opt_data_list;
+ isc_boolean_t lose;
+ size_t where;
+
+ if (option->space == space_lookup("server"))
+ return parse_config_statement(result, cfile, option, op);
+
+ opt_data = createMap();
+ TAILQ_CONCAT(&opt_data->comments, &cfile->comments);
+ mapSet(opt_data,
+ createString(makeString(-1, option->space->name)), "space");
+ mapSet(opt_data, createString(makeString(-1, option->name)), "name");
+ mapSet(opt_data, createInt(option->code), "code");
+ if (option->status == kea_unknown) {
+ opt_data->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ if (op != supersede_option_statement) {
+ struct string *msg;
+ struct comment *comment;
+
+ msg = makeString(-1, "/// Kea does not support option data ");
+ appendString(msg, "set variants (");
+ switch (op) {
+ case send_option_statement:
+ appendString(msg, "send");
+ break;
+ case supersede_option_statement:
+ appendString(msg, "supersede");
+ break;
+ case default_option_statement:
+ appendString(msg, "default");
+ break;
+ case prepend_option_statement:
+ appendString(msg, "prepend");
+ break;
+ case append_option_statement:
+ appendString(msg, "append");
+ break;
+ default:
+ appendString(msg, "???");
+ break;
+ }
+ appendString(msg, ")");
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&opt_data->comments, comment);
+ }
+
+ /* Setting PRL is a standard hack */
+ if ((option->space == space_lookup("dhcp")) &&
+ (option->code == 55)) {
+ struct comment *comment;
+
+ comment = createComment("/// Possible PRL hack");
+ TAILQ_INSERT_TAIL(&opt_data->comments, comment);
+ comment = createComment("/// Consider setting \"always-send\" "
+ "to true when setting data "
+ "for relevant options, cf Kea #250");
+ TAILQ_INSERT_TAIL(&opt_data->comments, comment);
+ }
+
+ /* Setting ORO is a standard hack */
+ if ((option->space == space_lookup("dhcp6")) &&
+ (option->code == 6)) {
+ struct comment *comment;
+
+ comment = createComment("/// Possible ORO hack");
+ TAILQ_INSERT_TAIL(&opt_data->comments, comment);
+ comment = createComment("/// Consider setting \"always-send\" "
+ "to true when setting data "
+ "for relevant options, cf Kea #250");
+ TAILQ_INSERT_TAIL(&opt_data->comments, comment);
+ }
+
+ token = peek_token(&val, NULL, cfile);
+ /* We should keep a list of defined empty options */
+ if ((token == SEMI) && (option->format[0] != 'Z')) {
+ /* Eat the semicolon... */
+ /*
+ * XXXSK: I'm not sure why we should ever get here, but we
+ * do during our startup. This confuses things if
+ * we are parsing a zero-length option, so don't
+ * eat the semicolon token in that case.
+ */
+ skip_token(&val, NULL, cfile);
+ } else if (token == EQUAL) {
+ struct element *data;
+ isc_boolean_t modified = ISC_FALSE;
+
+ /* Eat the equals sign. */
+ skip_token(&val, NULL, cfile);
+
+ /* Parse a data expression and use its value for the data. */
+ expr = createMap();
+ if (!parse_data_expression(expr, cfile, &lose)) {
+ /* In this context, we must have an executable
+ statement, so if we found something else, it's
+ still an error. */
+ if (!lose)
+ parse_error(cfile,
+ "expecting a data expression.");
+ return ISC_FALSE;
+ }
+ /* evaluate the expression */
+ expr = eval_data_expression(expr, &modified);
+
+ mapSet(opt_data, createBool(ISC_FALSE), "csv-format");
+
+ if (expr->type == ELEMENT_STRING) {
+ struct string *s;
+ struct string *r;
+
+ s = stringValue(expr);
+ expr->skip = ISC_TRUE;
+ mapSet(opt_data, expr, "original-data");
+
+ r = makeStringExt(s->length, s->content, 'X');
+ data = createString(r);
+ mapSet(opt_data, data, "data");
+ } else if ((expr->type == ELEMENT_MAP) &&
+ mapContains(expr, "const-data")) {
+ struct element *value;
+ struct string *r;
+
+ value = mapGet(expr, "const-data");
+ if ((value == NULL) || (value->type != ELEMENT_STRING))
+ parse_error(cfile, "can't get const-data");
+ r = hexaValue(value);
+ data = createString(r);
+ mapSet(opt_data, data, "data");
+ } else {
+ opt_data->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(opt_data, expr, "expression");
+ }
+ } else {
+ if (!parse_option_data(opt_data, cfile, option))
+ return ISC_FALSE;
+ }
+
+ parse_semi(cfile);
+
+ if (result != NULL) {
+ opt_data->skip = ISC_TRUE;
+ mapSet(result, opt_data, "option");
+ return ISC_TRUE;
+ }
+
+ for (where = cfile->stack_top; where > 0; --where) {
+ if (cfile->stack[where]->kind != PARAMETER)
+ break;
+ }
+
+ opt_data_list = mapGet(cfile->stack[where], "option-data");
+ if (opt_data_list == NULL) {
+ opt_data_list = createList();
+ mapSet(cfile->stack[where], opt_data_list, "option-data");
+ }
+ if (!opt_data->skip && (option->space->vendor != NULL))
+ add_option_data(option->space->vendor, opt_data_list);
+ listPush(opt_data_list, opt_data);
+
+ return ISC_TRUE;
+}
+
+/* Text version of parse_option_token */
+
+struct string *
+parse_option_token(struct parse *cfile, const char *fmt,
+ isc_boolean_t *require_binary,
+ isc_boolean_t *canon_bool,
+ isc_boolean_t *modified)
+{
+ const char *val;
+ enum dhcp_token token;
+ unsigned len;
+ struct string *item;
+
+ switch (*fmt) {
+ case 'U':
+ token = next_token(&val, &len, cfile);
+ if (!is_identifier(token))
+ parse_error(cfile, "expecting identifier.");
+ return makeString(len, val);
+ case 'x':
+ token = peek_token(&val, NULL, cfile);
+ if (token == NUMBER_OR_NAME || token == NUMBER) {
+ *require_binary = ISC_TRUE;
+ return NULL;
+ }
+ token = next_token(&val, &len, cfile);
+ if (token != STRING)
+ parse_error(cfile, "expecting string "
+ "or hexadecimal data.");
+ /* STRING can return embedded unexpected characters */
+ return escape_option_string(len, val, require_binary,
+ modified);
+ case 'X':
+ token = peek_token(&val, NULL, cfile);
+ if (token == NUMBER_OR_NAME || token == NUMBER) {
+ return parse_hexa(cfile);
+ }
+ token = next_token(&val, &len, cfile);
+ if (token != STRING)
+ parse_error(cfile, "expecting string "
+ "or hexadecimal data.");
+ return makeStringExt(len, val, 'X');
+
+ case 'D': /* Domain list... */
+ *modified = ISC_TRUE;
+ return parse_domain_list(cfile, ISC_FALSE);
+
+ case 'd': /* Domain name... */
+ *modified = ISC_TRUE;
+ item = parse_host_name(cfile);
+ if (item == NULL)
+ parse_error(cfile, "not a valid domain name.");
+ return item;
+
+ case 't': /* Text string... */
+ token = next_token(&val, &len, cfile);
+ if (token != STRING && !is_identifier(token))
+ parse_error(cfile, "expecting string.");
+ /* STRING can return embedded unexpected characters */
+ return escape_option_string(len, val, require_binary,
+ modified);
+
+ case 'I': /* IP address or hostname. */
+ *modified = ISC_TRUE;
+ return parse_ip_addr_or_hostname(cfile, ISC_FALSE);
+
+ case '6': /* IPv6 address. */
+ *modified = ISC_TRUE;
+ return parse_ip6_addr_txt(cfile);
+
+ case 'T': /* Lease interval. */
+ token = next_token(&val, NULL, cfile);
+ if (token == INFINITE)
+ return makeString(-1, "0xffffffff");
+ goto check_number;
+
+ case 'L': /* Unsigned 32-bit integer... */
+ case 'l':
+ case 's': /* Signed 16-bit integer. */
+ case 'S': /* Unsigned 16-bit integer. */
+ case 'b': /* Signed 8-bit integer. */
+ case 'B': /* Unsigned 8-bit integer. */
+ token = next_token(&val, NULL, cfile);
+ check_number:
+ if ((token != NUMBER) && (token != NUMBER_OR_NAME))
+ parse_error(cfile, "expecting number.");
+ /* check octal */
+ if (val[0] == '0' && isascii(val[1]) && isdigit(val[1]))
+ *require_binary = ISC_TRUE;
+ return makeString(-1, val);
+
+ case 'f': /* Boolean flag. */
+ token = next_token(&val, NULL, cfile);
+ if (!is_identifier(token))
+ parse_error(cfile, "expecting identifier.");
+ if (strcasecmp(val, "true") == 0)
+ return makeString(-1, "true");
+ if (strcasecmp(val, "on") == 0) {
+ *canon_bool = ISC_TRUE;
+ *modified = ISC_TRUE;
+ return makeString(-1, "true");
+ }
+ if (strcasecmp(val, "false") == 0)
+ return makeString(-1, "false");
+ if (strcasecmp(val, "off") == 0) {
+ *canon_bool = ISC_TRUE;
+ *modified = ISC_TRUE;
+ return makeString(-1, "false");
+ }
+ parse_error(cfile, "expecting boolean.");
+
+ case 'Z': /* Zero-length option. */
+ token = peek_token(&val, NULL, cfile);
+ if (token != SEMI)
+ parse_error(cfile, "semicolon expected.");
+ return allocString();
+
+ default:
+ parse_error(cfile, "Bad format '%c' in parse_option_token.",
+ *fmt);
+ }
+}
+
+/* Binary (aka hexadecimal) version of parse_option_token */
+
+struct string *
+parse_option_token_binary(struct parse *cfile, const char *fmt)
+{
+ const char *val;
+ enum dhcp_token token;
+ unsigned len;
+ struct string *item;
+ uint8_t buf[4];
+
+ switch (*fmt) {
+ case 'U':
+ token = next_token(&val, &len, cfile);
+ if (!is_identifier(token)) {
+ if (fmt[1] == 'o')
+ return NULL;
+ parse_error(cfile, "expecting identifier.");
+ }
+ return makeStringExt(len, val, 'X');
+ case 'E':
+ case 'X':
+ case 'x':
+ case 'u':
+ token = peek_token(&val, NULL, cfile);
+ if (token == NUMBER_OR_NAME || token == NUMBER)
+ return parse_hexa(cfile);
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ if (fmt[1] == 'o')
+ return NULL;
+ parse_error(cfile, "expecting string "
+ "or hexadecimal data.");
+ }
+ return makeStringExt(len, val, 'X');
+
+ case 'D': /* Domain list... */
+ item = parse_domain_list(cfile, ISC_TRUE);
+ if (item == NULL) {
+ if (fmt[1] == 'o')
+ return NULL;
+ parse_error(cfile, "parse_domain_list failed");
+ }
+ return NULL;
+
+ case 'd': /* Domain name... */
+ item = parse_host_name(cfile);
+ if (item == NULL)
+ parse_error(cfile, "not a valid domain name.");
+ item = makeStringExt(item->length, item->content, 'd');
+ if (item == NULL)
+ parse_error(cfile, "too long domain name.");
+ return makeStringExt(item->length, item->content, 'X');
+
+ case 't': /* Text string... */
+ token = next_token(&val, &len, cfile);
+ if (token != STRING && !is_identifier(token)) {
+ if (fmt[1] == 'o')
+ return NULL;
+ parse_error(cfile, "expecting string.");
+ }
+ return makeStringExt(len, val, 'X');
+
+ case 'I': /* IP address or hostname. */
+ item = parse_ip_addr_or_hostname(cfile, ISC_FALSE);
+ return makeStringExt(item->length, item->content, 'i');
+
+ case '6': /* IPv6 address. */
+ item = parse_ip6_addr(cfile);
+ return makeStringExt(item->length, item->content, 'X');
+
+ case 'T': /* Lease interval. */
+ token = next_token(&val, NULL, cfile);
+ if (token == INFINITE)
+ return makeString(-1, "ffffffff");
+ goto check_number;
+
+ case 'L': /* Unsigned 32-bit integer... */
+ case 'l': /* Signed 32-bit integer... */
+ token = next_token(&val, NULL, cfile);
+ check_number:
+ if ((token != NUMBER) && (token != NUMBER_OR_NAME)) {
+ need_number:
+ if (fmt[1] == 'o')
+ return NULL;
+ parse_error(cfile, "expecting number.");
+ }
+ convert_num(cfile, buf, val, 0, 32);
+ return makeStringExt(4, (const char *)buf, 'X');
+
+ case 's': /* Signed 16-bit integer. */
+ case 'S': /* Unsigned 16-bit integer. */
+ token = next_token(&val, NULL, cfile);
+ if ((token != NUMBER) && (token != NUMBER_OR_NAME))
+ goto need_number;
+ convert_num(cfile, buf, val, 0, 16);
+ return makeStringExt(2, (const char *)buf, 'X');
+
+ case 'b': /* Signed 8-bit integer. */
+ case 'B': /* Unsigned 8-bit integer. */
+ token = next_token(&val, NULL, cfile);
+ if ((token != NUMBER) && (token != NUMBER_OR_NAME))
+ goto need_number;
+ convert_num(cfile, buf, val, 0, 8);
+ return makeStringExt(1, (const char *)buf, 'X');
+
+ case 'f': /* Boolean flag. */
+ token = next_token(&val, NULL, cfile);
+ if (!is_identifier(token)) {
+ if (fmt[1] == 'o')
+ return NULL;
+ parse_error(cfile, "expecting identifier.");
+ }
+ if ((strcasecmp(val, "true") == 0) ||
+ (strcasecmp(val, "on") == 0))
+ return makeString(-1, "01");
+ if ((strcasecmp(val, "false") == 0) ||
+ (strcasecmp(val, "off") == 0))
+ return makeString(-1, "00");
+ if (strcasecmp(val, "ignore") == 0)
+ return makeString(-1, "02");
+ if (fmt[1] == 'o')
+ return NULL;
+ parse_error(cfile, "expecting boolean.");
+
+ case 'Z': /* Zero-length option. */
+ token = peek_token(&val, NULL, cfile);
+ if (token != SEMI)
+ parse_error(cfile, "semicolon expected.");
+ return allocString();
+
+ default:
+ parse_error(cfile, "Bad format '%c' in parse_option_token.",
+ *fmt);
+ }
+}
+
+struct string *
+parse_domain_list(struct parse *cfile, isc_boolean_t binary)
+{
+ const char *val;
+ enum dhcp_token token;
+ unsigned len;
+ struct string *result;
+
+ token = SEMI;
+ result = allocString();
+
+ do {
+ /* Consume the COMMA token if peeked. */
+ if (token == COMMA) {
+ skip_token(&val, NULL, cfile);
+ if (!binary)
+ appendString(result, ", ");
+ }
+
+ /* Get next (or first) value. */
+ token = next_token(&val, &len, cfile);
+
+ if (token != STRING)
+ parse_error(cfile, "Expecting a domain string.");
+
+ /* Just pack the names in series into the buffer. */
+ if (binary) {
+ struct string *item;
+
+ item = makeStringExt(len, val, 'd');
+ if (item == NULL)
+ parse_error(cfile, "not a valid domain name.");
+ item = makeStringExt(item->length, item->content, 'X');
+ concatString(result, item);
+ } else
+ concatString(result, makeString(len, val));
+
+ token = peek_token(&val, NULL, cfile);
+ } while (token == COMMA);
+
+ return result;
+}
+
+/* Specialized version of parse_option_data working on config
+ * options which are scalar (I6LSBtTfUXdNxxx.) only. */
+
+isc_boolean_t
+parse_config_data(struct element *expr,
+ struct parse *cfile,
+ struct option *option)
+{
+ const char *val;
+ enum dhcp_token token;
+ struct string *data;
+ struct element *elem;
+ unsigned len;
+ uint32_t u32;
+ uint16_t u16;
+ uint8_t u8;
+
+ token = peek_token(&val, NULL, cfile);
+
+ if (token == END_OF_FILE)
+ parse_error(cfile, "unexpected end of file");
+ if (token == SEMI)
+ parse_error(cfile, "empty config option");
+ if (token == COMMA)
+ parse_error(cfile, "multiple value config option");
+
+ /* from parse_option_token */
+
+ switch (option->format[0]) {
+ case 'U': /* universe */
+ token = next_token(&val, &len, cfile);
+ if (!is_identifier(token))
+ parse_error(cfile, "expecting identifier.");
+ elem = createString(makeString(len, val));
+ break;
+
+ case 'X': /* string or binary */
+ token = next_token(&val, &len, cfile);
+ if (token == NUMBER_OR_NAME || token == NUMBER)
+ data = parse_cshl(cfile);
+ else if (token == STRING)
+ data = makeString(len, val);
+ else
+ parse_error(cfile, "expecting string "
+ "or hexadecimal data.");
+ elem = createString(data);
+ break;
+
+ case 'd': /* FQDN */
+ data = parse_host_name(cfile);
+ if (data == NULL)
+ parse_error(cfile, "not a valid domain name.");
+ elem = createString(data);
+ break;
+
+ case 't': /* text */
+ token = next_token(&val, &len, cfile);
+ elem = createString(makeString(len, val));
+ break;
+
+ case 'N': /* enumeration */
+ token = next_token(&val, &len, cfile);
+ if (!is_identifier(token))
+ parse_error(cfile, "identifier expected");
+ elem = createString(makeString(len, val));
+ break;
+
+ case 'I': /* IP address or hostname. */
+ data = parse_ip_addr_or_hostname(cfile, ISC_FALSE);
+ if (data == NULL)
+ parse_error(cfile, "expecting IP address of hostname");
+ elem = createString(data);
+ break;
+
+ case '6': /* IPv6 address. */
+ data = parse_ip6_addr_txt(cfile);
+ if (data == NULL)
+ parse_error(cfile, "expecting IPv6 address");
+ elem = createString(data);
+ break;
+
+ case 'T': /* Lease interval. */
+ token = next_token(&val, NULL, cfile);
+ if (token != INFINITE)
+ goto check_number;
+ elem = createInt(-1);
+ break;
+
+ case 'L': /* Unsigned 32-bit integer... */
+ token = next_token(&val, NULL, cfile);
+ check_number:
+ if ((token != NUMBER) && (token != NUMBER_OR_NAME))
+ parse_error(cfile, "expecting number.");
+ convert_num(cfile, (unsigned char *)&u32, val, 0, 32);
+ elem = createInt(ntohl(u32));
+ break;
+
+ case 'S': /* Unsigned 16-bit integer. */
+ token = next_token(&val, NULL, cfile);
+ if ((token != NUMBER) && (token != NUMBER_OR_NAME))
+ parse_error(cfile, "expecting number.");
+ convert_num(cfile, (unsigned char *)&u16, val, 0, 16);
+ elem = createInt(ntohs(u16));
+ break;
+
+ case 'B': /* Unsigned 8-bit integer. */
+ token = next_token(&val, NULL, cfile);
+ if ((token != NUMBER) && (token != NUMBER_OR_NAME))
+ parse_error(cfile, "expecting number.");
+ convert_num(cfile, (unsigned char *)&u8, val, 0, 8);
+ elem = createInt(ntohs(u8));
+ break;
+
+ case 'f':
+ token = next_token(&val, NULL, cfile);
+ if (!is_identifier(token))
+ parse_error(cfile, "expecting boolean.");
+ if ((strcasecmp(val, "true") == 0) ||
+ (strcasecmp(val, "on") == 0))
+ elem = createBool(ISC_TRUE);
+ else if ((strcasecmp(val, "false") == 0) ||
+ (strcasecmp(val, "off") == 0))
+ elem = createBool(ISC_FALSE);
+ else if (strcasecmp(val, "ignore") == 0) {
+ elem = createNull();
+ elem->skip = ISC_TRUE;
+ } else
+ parse_error(cfile, "expecting boolean.");
+ break;
+
+ default:
+ parse_error(cfile, "Bad format '%c' in parse_config_data.",
+ option->format[0]);
+ }
+
+ mapSet(expr, elem, "value");
+
+ return ISC_TRUE;
+}
+
+/* Specialized version of parse_option_statement for config options */
+
+isc_boolean_t
+parse_config_statement(struct element *result,
+ struct parse *cfile,
+ struct option *option,
+ enum statement_op op)
+{
+ const char *val;
+ enum dhcp_token token;
+ struct comments *comments;
+ struct element *expr;
+ struct element *config;
+ struct element *config_list;
+ isc_boolean_t lose;
+ size_t where;
+
+ config = createMap();
+ TAILQ_CONCAT(&config->comments, &cfile->comments);
+ comments = get_config_comments(option->code);
+ TAILQ_CONCAT(&config->comments, comments);
+ mapSet(config, createString(makeString(-1, option->name)), "name");
+ mapSet(config, createInt(option->code), "code");
+ if (option->status == kea_unknown) {
+ config->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ if (op != supersede_option_statement) {
+ struct string *msg;
+ struct comment *comment;
+
+ msg = makeString(-1, "/// Kea does not support option data ");
+ appendString(msg, "set variants (");
+ switch (op) {
+ case send_option_statement:
+ appendString(msg, "send");
+ break;
+ case supersede_option_statement:
+ appendString(msg, "supersede");
+ break;
+ case default_option_statement:
+ appendString(msg, "default");
+ break;
+ case prepend_option_statement:
+ appendString(msg, "prepend");
+ break;
+ case append_option_statement:
+ appendString(msg, "append");
+ break;
+ default:
+ appendString(msg, "???");
+ break;
+ }
+ appendString(msg, ")");
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&config->comments, comment);
+ }
+
+ token = peek_token(&val, NULL, cfile);
+ /* We should keep a list of defined empty options */
+ if ((token == SEMI) && (option->format[0] != 'Z')) {
+ /* Eat the semicolon... */
+ /*
+ * XXXSK: I'm not sure why we should ever get here, but we
+ * do during our startup. This confuses things if
+ * we are parsing a zero-length option, so don't
+ * eat the semicolon token in that case.
+ */
+ skip_token(&val, NULL, cfile);
+ } else if (token == EQUAL) {
+ /* Eat the equals sign. */
+ skip_token(&val, NULL, cfile);
+
+ /* Parse a data expression and use its value for the data. */
+ expr = createMap();
+ if (!parse_data_expression(expr, cfile, &lose)) {
+ /* In this context, we must have an executable
+ statement, so if we found something else, it's
+ still an error. */
+ if (!lose)
+ parse_error(cfile,
+ "expecting a data expression.");
+ return ISC_FALSE;
+ }
+ mapSet(config, expr, "value");
+ } else {
+ if (!parse_config_data(config, cfile, option))
+ return ISC_FALSE;
+ }
+
+ parse_semi(cfile);
+
+ if (result != NULL) {
+ config->skip = ISC_TRUE;
+ mapSet(result, config, "config");
+ return ISC_TRUE;
+ }
+
+ for (where = cfile->stack_top; where > 0; --where) {
+ if ((cfile->stack[where]->kind == PARAMETER) ||
+ (cfile->stack[where]->kind == POOL_DECL))
+ continue;
+ break;
+ }
+
+ if (option->status != special) {
+ config_list = mapGet(cfile->stack[where], "config");
+ if (config_list == NULL) {
+ config_list = createList();
+ config_list->skip = ISC_TRUE;
+ mapSet(cfile->stack[where], config_list, "config");
+ }
+ listPush(config_list, config);
+ return ISC_TRUE;
+ }
+
+ /* deal with all special cases */
+
+ switch (option->code) {
+ case 1: /* default-lease-time */
+ config_def_valid_lifetime(config, cfile);
+ break;
+ case 2: /* max-lease-time */
+ config_max_valid_lifetime(config, cfile);
+ break;
+ case 3: /* min-lease-time */
+ config_min_valid_lifetime(config, cfile);
+ break;
+ case 15: /* filename */
+ config_file(config, cfile);
+ break;
+ case 16: /* server-name */
+ config_sname(config, cfile);
+ break;
+ case 17: /* next-server */
+ config_next_server(config, cfile);
+ break;
+ case 18: /* authoritative */
+ parse_error(cfile, "authoritative is a statement, "
+ "here it is used as a config option");
+ case 19: /* vendor-option-space */
+ config_vendor_option_space(config, cfile);
+ break;
+ case 21: /* site-option-space */
+ config_site_option_space(config, cfile);
+ break;
+ case 23: /* ddns-domainname */
+ config_qualifying_suffix(config, cfile);
+ break;
+ case 30: /* ddns-updates */
+ config_enable_updates(config, cfile);
+ break;
+ case 39: /* ddns-update-style */
+ config_ddns_update_style(config, cfile);
+ break;
+ case 53: /* preferred-lifetime */
+ config_preferred_lifetime(config, cfile);
+ break;
+ case 82: /* ignore-client-uids */
+ config_match_client_id(config, cfile);
+ break;
+ case 85: /* echo-client-id */
+ config_echo_client_id(config, cfile);
+ break;
+ default:
+ parse_error(cfile, "unsupported config option %s (%u)",
+ option->name, option->code);
+ }
+
+ return ISC_TRUE;
+}
+
+static void
+config_def_valid_lifetime(struct element *config, struct parse *cfile)
+{
+ struct element *value;
+ struct comment *comment;
+ size_t scope;
+ isc_boolean_t pop_from_pool = ISC_FALSE;
+
+ value = mapGet(config, "value");
+
+ for (scope = cfile->stack_top; scope > 0; --scope) {
+ int kind = cfile->stack[scope]->kind;
+
+ if (kind == PARAMETER)
+ continue;
+ if ((kind == ROOT_GROUP) ||
+ (kind == SHARED_NET_DECL) ||
+ (kind == SUBNET_DECL) ||
+ (kind == GROUP_DECL))
+ break;
+ if (kind == POOL_DECL) {
+ pop_from_pool = ISC_TRUE;
+ continue;
+ }
+ comment = createComment("/// default-valid-lifetime in "
+ "unsupported scope");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ value->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ break;
+ }
+ if (pop_from_pool) {
+ comment= createComment("/// default-valid-lifetime moved from "
+ "an internal pool scope");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ }
+ mapSet(cfile->stack[scope], value, "valid-lifetime");
+}
+
+static void
+config_min_valid_lifetime(struct element *config, struct parse *cfile)
+{
+ struct element *value;
+ struct comment *comment;
+ size_t scope;
+ isc_boolean_t pop_from_pool = ISC_FALSE;
+
+ value = mapGet(config, "value");
+
+ for (scope = cfile->stack_top; scope > 0; --scope) {
+ int kind = cfile->stack[scope]->kind;
+
+ if (kind == PARAMETER)
+ continue;
+ if ((kind == ROOT_GROUP) ||
+ (kind == SHARED_NET_DECL) ||
+ (kind == SUBNET_DECL) ||
+ (kind == GROUP_DECL))
+ break;
+ if (kind == POOL_DECL) {
+ pop_from_pool = ISC_TRUE;
+ continue;
+ }
+ comment = createComment("/// min-valid-lifetime in "
+ "unsupported scope");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ value->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ break;
+ }
+ if (pop_from_pool) {
+ comment= createComment("/// min-valid-lifetime moved from "
+ "an internal pool scope");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ }
+ mapSet(cfile->stack[scope], value, "min-valid-lifetime");
+}
+
+static void
+config_max_valid_lifetime(struct element *config, struct parse *cfile)
+{
+ struct element *value;
+ struct comment *comment;
+ size_t scope;
+ isc_boolean_t pop_from_pool = ISC_FALSE;
+
+ value = mapGet(config, "value");
+
+ for (scope = cfile->stack_top; scope > 0; --scope) {
+ int kind = cfile->stack[scope]->kind;
+
+ if (kind == PARAMETER)
+ continue;
+ if ((kind == ROOT_GROUP) ||
+ (kind == SHARED_NET_DECL) ||
+ (kind == SUBNET_DECL) ||
+ (kind == GROUP_DECL))
+ break;
+ if (kind == POOL_DECL) {
+ pop_from_pool = ISC_TRUE;
+ continue;
+ }
+ comment = createComment("/// max-valid-lifetime in "
+ "unsupported scope");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ value->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ break;
+ }
+ if (pop_from_pool) {
+ comment= createComment("/// max-valid-lifetime moved from "
+ "an internal pool scope");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ }
+ mapSet(cfile->stack[scope], value, "max-valid-lifetime");
+}
+
+static void
+config_file(struct element *config, struct parse *cfile)
+{
+ struct element *value;
+ struct comment *comment;
+ size_t scope;
+ isc_boolean_t popped = ISC_FALSE;
+
+ if (local_family != AF_INET)
+ parse_error(cfile, "boot-file-name is DHCPv4 only");
+
+ value = mapGet(config, "value");
+
+ for (scope = cfile->stack_top; scope > 0; --scope) {
+ int kind = cfile->stack[scope]->kind;
+
+ if (kind == PARAMETER)
+ continue;
+ if ((kind == HOST_DECL) ||
+ (kind == CLASS_DECL) ||
+ (kind == GROUP_DECL))
+ break;
+ if (kind == ROOT_GROUP) {
+ popped = ISC_TRUE;
+ break;
+ }
+ }
+ if (popped) {
+ comment = createComment("/// boot-file-name was defined in "
+ "an unsupported scope");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ value->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ mapSet(cfile->stack[scope], value, "boot-file-name");
+}
+
+static void
+config_sname(struct element *config, struct parse *cfile)
+{
+ struct element *value;
+ struct comment *comment;
+ size_t scope;
+ isc_boolean_t popped = ISC_FALSE;
+
+ if (local_family != AF_INET)
+ parse_error(cfile, "server-hostname is DHCPv4 only");
+
+ value = mapGet(config, "value");
+
+ for (scope = cfile->stack_top; scope > 0; --scope) {
+ int kind = cfile->stack[scope]->kind;
+
+ if (kind == PARAMETER)
+ continue;
+ if ((kind == HOST_DECL) ||
+ (kind == CLASS_DECL) ||
+ (kind == GROUP_DECL))
+ break;
+ if (kind == ROOT_GROUP) {
+ popped = ISC_TRUE;
+ break;
+ }
+ }
+ if (popped) {
+ comment = createComment("/// server-hostname was defined in "
+ "an unsupported scope");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ value->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ mapSet(cfile->stack[scope], value, "server-hostname");
+}
+
+static void
+config_next_server(struct element *config, struct parse *cfile)
+{
+ struct element *value;
+ struct comment *comment;
+ size_t scope;
+ isc_boolean_t popped = ISC_FALSE;
+
+ if (local_family != AF_INET)
+ parse_error(cfile, "next-server is DHCPv4 only");
+
+ value = mapGet(config, "value");
+
+ for (scope = cfile->stack_top; scope > 0; --scope) {
+ int kind = cfile->stack[scope]->kind;
+
+ if (kind == PARAMETER)
+ continue;
+ if ((kind == ROOT_GROUP) ||
+ (kind == HOST_DECL) ||
+ (kind == CLASS_DECL) ||
+ (kind == SUBNET_DECL) ||
+ (kind == GROUP_DECL))
+ break;
+ popped = ISC_TRUE;
+ }
+ if (popped) {
+ comment = createComment("/// next-server moved from "
+ "an internal unsupported scope");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ }
+ mapSet(cfile->stack[scope], value, "next-server");
+}
+
+static void
+config_vendor_option_space(struct element *config, struct parse *cfile)
+{
+ struct element *defs;
+ struct element *def;
+ struct element *opts;
+ struct element *opt;
+ struct element *space;
+
+ if (local_family != AF_INET)
+ parse_error(cfile, "vendor-option-space is DHCPv4 only");
+
+ /* create local option definition */
+ def = createMap();
+ mapSet(def,
+ createString(makeString(-1, "vendor-encapsulated-options")),
+ "name");
+ mapSet(def, createInt(43), "code");
+ mapSet(def, createString(makeString(-1, "empty")), "type");
+ space = mapGet(config, "value");
+ if (space == NULL)
+ parse_error(cfile, "vendor-option-space has no value");
+ if (space->type != ELEMENT_STRING)
+ parse_error(cfile,
+ "vendor-option-space value is not a string");
+ mapSet(def, space, "encapsulate");
+
+ /* add it */
+ defs = mapGet(cfile->stack[cfile->stack_top], "option-def");
+ if (defs == NULL) {
+ defs = createList();
+ mapSet(cfile->stack[cfile->stack_top], defs, "option-def");
+ } else {
+ size_t i;
+
+ /* Look for duplicate */
+ for (i = 0; i < listSize(defs); i++) {
+ struct element *item;
+ struct element *code;
+ struct element *old;
+
+ item = listGet(defs, i);
+ if ((item == NULL) || (item->type != ELEMENT_MAP))
+ continue;
+ code = mapGet(item, "code");
+ if ((code == NULL) ||
+ (code->type != ELEMENT_INTEGER) ||
+ (intValue(code) != 43))
+ continue;
+ old = mapGet(item, "encapsulate");
+ if ((old == NULL) || (old->type != ELEMENT_STRING))
+ continue;
+ if (eqString(stringValue(space), stringValue(old)))
+ return;
+ }
+ }
+ listPush(defs, def);
+
+ /* add a data too assuming at least one suboption exists */
+ opt = createMap();
+ mapSet(opt,
+ createString(makeString(-1, "vendor-encapsulated-options")),
+ "name");
+ mapSet(opt, createInt(43), "code");
+ opts = mapGet(cfile->stack[cfile->stack_top], "option-data");
+ if (opts == NULL) {
+ opts = createList();
+ mapSet(cfile->stack[cfile->stack_top], opts, "option-data");
+ }
+ listPush(opts, opt);
+}
+
+static void
+config_site_option_space(struct element *config, struct parse *cfile)
+{
+ struct element *defs;
+ struct element *space;
+ struct string *msg;
+ struct comment *comment;
+
+ if (local_family != AF_INET)
+ parse_error(cfile, "site-option-space is DHCPv4 only");
+
+ space = mapGet(config, "value");
+ if (space == NULL)
+ parse_error(cfile, "site-option-space has no value");
+ if (space->type != ELEMENT_STRING)
+ parse_error(cfile, "site-option-space value is not a string");
+
+ defs = mapGet(cfile->stack[cfile->stack_top], "option-def");
+ if (defs == NULL) {
+ defs = createList();
+ mapSet(cfile->stack[cfile->stack_top], defs, "option-def");
+ }
+
+ msg = makeString(-1, "/// site-option-space '");
+ concatString(msg, stringValue(space));
+ appendString(msg, "'");
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&defs->comments, comment);
+ msg = makeString(-1, "/// Please to move private (code 224..254)");
+ appendString(msg, " option definitions from '");
+ concatString(msg, stringValue(space));
+ appendString(msg, "' to 'dhcp4' space");
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&defs->comments, comment);
+}
+
+static struct element *
+default_qualifying_suffix(void)
+{
+ struct element *qs;
+ struct comment *comment;
+
+ qs = createString(allocString());
+ comment = createComment("/// Unspecified ddns-domainname (default "
+ "domain-name option value)");
+ TAILQ_INSERT_TAIL(&qs->comments, comment);
+ comment = createComment("/// Kea requires a qualifying-suffix");
+ TAILQ_INSERT_TAIL(&qs->comments, comment);
+ comment = createComment("/// Initialized to \"\": please put a value");
+ TAILQ_INSERT_TAIL(&qs->comments, comment);
+ return qs;
+}
+
+static void
+config_qualifying_suffix(struct element *config, struct parse *cfile)
+{
+ struct element *value;
+ size_t scope;
+
+ value = mapGet(config, "value");
+
+ for (scope = cfile->stack_top; scope > 0; --scope)
+ if ((cfile->stack[scope]->kind != PARAMETER) ||
+ (cfile->stack[scope]->kind != POOL_DECL))
+ break;
+ if (cfile->stack[scope]->kind != ROOT_GROUP) {
+ struct comment *comment;
+
+ comment = createComment("/// Only global qualifying-suffix "
+ "is supported");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ value->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(cfile->stack[scope], value, "qualifying-suffix");
+ } else {
+ struct element *d2;
+
+ d2 = mapGet(cfile->stack[1], "dhcp-ddns");
+ if (d2 == NULL) {
+ d2 = createMap();
+ mapSet(d2, createBool(ISC_FALSE), "enable-updates");
+ mapSet(cfile->stack[1], d2, "dhcp-ddns");
+ } else if (mapContains(d2, "qualifying-suffix"))
+ mapRemove(d2, "qualifying-suffix");
+ mapSet(d2, value, "qualifying-suffix");
+ }
+}
+
+static void
+config_enable_updates(struct element *config, struct parse *cfile)
+{
+ struct element *value;
+ size_t scope;
+
+ value = mapGet(config, "value");
+
+ for (scope = cfile->stack_top; scope > 0; --scope)
+ if ((cfile->stack[scope]->kind != PARAMETER) ||
+ (cfile->stack[scope]->kind != POOL_DECL))
+ break;
+ if (cfile->stack[scope]->kind != ROOT_GROUP) {
+ struct comment *comment;
+
+ comment = createComment("/// Only global enable-updates "
+ "is supported");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ value->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(cfile->stack[scope], value, "enable-updates");
+ } else {
+ struct element *d2;
+
+ d2 = mapGet(cfile->stack[1], "dhcp-ddns");
+ if (d2 == NULL) {
+ d2 = createMap();
+ mapSet(cfile->stack[1], d2, "dhcp-ddns");
+ if (boolValue(value)) {
+ struct element *qs;
+
+ qs = default_qualifying_suffix();
+ mapSet(d2, qs, "qualifying-suffix");
+ }
+ } else if (mapContains(d2, "enable-updates"))
+ mapRemove(d2, "enable-updates");
+ mapSet(d2, value, "enable-updates");
+ }
+}
+
+static void
+config_ddns_update_style(struct element *config, struct parse *cfile)
+{
+ struct element *value;
+ isc_boolean_t enable = ISC_TRUE;
+ size_t scope;
+
+ value = mapGet(config, "value");
+ if (strcmp(stringValue(value)->content, "standard") == 0)
+ enable = ISC_TRUE;
+ else if (strcmp(stringValue(value)->content, "none") == 0)
+ enable = ISC_FALSE;
+ else {
+ struct string *msg;
+ struct comment *comment;
+
+ for (scope = cfile->stack_top; scope > 0; --scope)
+ if ((cfile->stack[scope]->kind != PARAMETER) ||
+ (cfile->stack[scope]->kind != POOL_DECL))
+ break;
+ msg = makeString(-1, "/// Unsupported ddns-update-style ");
+ concatString(msg, stringValue(value));
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ value->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(cfile->stack[scope], value, "ddns-update-style");
+ }
+
+ for (scope = cfile->stack_top; scope > 0; --scope)
+ if ((cfile->stack[scope]->kind != PARAMETER) ||
+ (cfile->stack[scope]->kind != POOL_DECL))
+ break;
+ if (cfile->stack[scope]->kind != ROOT_GROUP) {
+ struct comment *comment;
+
+ comment = createComment("/// Only global ddns-update-style "
+ "is supported");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ value->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ mapSet(cfile->stack[scope], value, "ddns-update-style");
+ } else {
+ struct element *d2;
+
+ /* map ddns-update-style into enable-updates */
+ value = createBool(enable);
+ d2 = mapGet(cfile->stack[1], "dhcp-ddns");
+ if (d2 == NULL) {
+ d2 = createMap();
+ mapSet(cfile->stack[1], d2, "dhcp-ddns");
+ if (boolValue(value)) {
+ struct element *qs;
+
+ qs = default_qualifying_suffix();
+ mapSet(d2, qs, "qualifying-suffix");
+ }
+ } else if (mapContains(d2, "enable-updates"))
+ mapRemove(d2, "enable-updates");
+ mapSet(d2, value, "enable-updates");
+ }
+}
+
+static void
+config_preferred_lifetime(struct element *config, struct parse *cfile)
+{
+ struct element *value;
+ struct element *child;
+ struct comment *comment;
+ size_t scope;
+ isc_boolean_t pop_from_pool = ISC_FALSE;
+
+ if (local_family != AF_INET6)
+ parse_error(cfile, "preferred-lifetime is DHCPv6 only");
+
+ value = mapGet(config, "value");
+
+ for (scope = cfile->stack_top; scope > 0; --scope) {
+ int kind = cfile->stack[scope]->kind;
+
+ if (kind == PARAMETER)
+ continue;
+ if ((kind == ROOT_GROUP) ||
+ (kind == SHARED_NET_DECL) ||
+ (kind == SUBNET_DECL) ||
+ (kind == GROUP_DECL))
+ break;
+ if (kind == POOL_DECL) {
+ pop_from_pool = ISC_TRUE;
+ continue;
+ }
+ comment = createComment("/// preferred-lifetime in "
+ "unsupported scope");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ value->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ break;
+ }
+ if (pop_from_pool) {
+ comment = createComment("/// preferred-lifetime moved from "
+ "an internal pool scope");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ /* if there is another specified value and we are
+ * enough lucky to have already got it... */
+ if (mapContains(cfile->stack[scope], "preferred-lifetime")) {
+ comment = createComment("/// Avoid to overwrite "
+ "current value...");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ value->skip = ISC_TRUE;
+ }
+ }
+ mapSet(cfile->stack[scope], value, "preferred-lifetime");
+ /* derive T1 and T2 */
+ child = createInt(intValue(value) / 2);
+ child->skip = value->skip;
+ mapSet(cfile->stack[scope], child, "renew-timer");
+ child = createInt(intValue(value) * 4 / 5);
+ child->skip = value->skip;
+ mapSet(cfile->stack[scope], child, "rebind-timer");
+}
+
+static void
+config_match_client_id(struct element *config, struct parse *cfile)
+{
+ struct element *value;
+ struct comment *comment;
+ size_t scope;
+ isc_boolean_t pop_from_pool = ISC_FALSE;
+
+ if (local_family != AF_INET)
+ parse_error(cfile, "ignore-client-uids is DHCPv4 only");
+
+ value = mapGet(config, "value");
+ /* match-client-id is !ignore-client-uids */
+ value = createBool(!boolValue(value));
+
+ for (scope = cfile->stack_top; scope > 0; --scope) {
+ int kind = cfile->stack[scope]->kind;
+
+ if (kind == PARAMETER)
+ continue;
+ if ((kind == ROOT_GROUP) ||
+ (kind == SHARED_NET_DECL) ||
+ (kind == SUBNET_DECL) ||
+ (kind == GROUP_DECL))
+ break;
+ if (kind == POOL_DECL) {
+ pop_from_pool = ISC_TRUE;
+ continue;
+ }
+ comment = createComment("/// match-client-id in unsupported "
+ "scope");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ value->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ break;
+ }
+ if (pop_from_pool) {
+ comment= createComment("/// match-client-id moved from "
+ "an internal pool scope");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ }
+ mapSet(cfile->stack[scope], value, "match-client-id");
+}
+
+static void
+config_echo_client_id(struct element *config, struct parse *cfile)
+{
+ struct element *value;
+ struct comment *comment;
+ size_t scope;
+
+ if (local_family != AF_INET)
+ parse_error(cfile, "echo-client-id is DHCPv4 only");
+
+ value = mapGet(config, "value");
+
+ for (scope = cfile->stack_top; scope > 0; --scope) {
+ int kind = cfile->stack[scope]->kind;
+
+ if (kind == PARAMETER)
+ continue;
+ if (kind == ROOT_GROUP)
+ break;
+ comment = createComment("/// Only global echo-client-id "
+ "is supported");
+ TAILQ_INSERT_TAIL(&value->comments, comment);
+ value->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ mapSet(cfile->stack[scope], value, "echo-client-id");
+}
+
+/* parse_error moved to keama.c */
+
+/* From omapi/convert.c */
+/*
+static uint32_t
+getULong(const unsigned char *buf)
+{
+ uint32_t ibuf;
+
+ memcpy(&ibuf, buf, sizeof(uint32_t));
+ return ntohl(ibuf);
+}
+
+static int32_t
+getLong(const unsigned char *buf)
+{
+ int32_t ibuf;
+
+ memcpy(&ibuf, buf, sizeof(int32_t));
+ return ntohl(ibuf);
+}
+
+static uint32_t
+getUShort(const unsigned char *buf)
+{
+ unsigned short ibuf;
+
+ memcpy(&ibuf, buf, sizeof(uint16_t));
+ return ntohs(ibuf);
+}
+
+static int32_t
+getShort(const unsigned char *buf)
+{
+ short ibuf;
+
+ memcpy(&ibuf, buf, sizeof(int16_t));
+ return ntohs(ibuf);
+}
+
+static uint32_t
+getUChar(const unsigned char *obuf)
+{
+ return obuf[0];
+}
+*/
+static void
+putULong(unsigned char *obuf, uint32_t val)
+{
+ uint32_t tmp = htonl(val);
+ memcpy(obuf, &tmp, sizeof(tmp));
+}
+
+static void
+putLong(unsigned char *obuf, int32_t val)
+{
+ int32_t tmp = htonl(val);
+ memcpy(obuf, &tmp, sizeof(tmp));
+}
+
+static void
+putUShort(unsigned char *obuf, uint32_t val)
+{
+ uint16_t tmp = htons(val);
+ memcpy(obuf, &tmp, sizeof(tmp));
+}
+
+static void
+putShort(unsigned char *obuf, int32_t val)
+{
+ int16_t tmp = htons(val);
+ memcpy(obuf, &tmp, sizeof(tmp));
+}
+/*
+static void
+putUChar(unsigned char *obuf, uint32_t val)
+{
+ *obuf = val;
+}
+*/
+/* From common/tree.c */
+
+isc_boolean_t
+is_boolean_expression(struct element *expr)
+{
+ if (expr->type == ELEMENT_BOOLEAN)
+ return ISC_TRUE;
+ if (expr->type != ELEMENT_MAP)
+ return ISC_FALSE;
+ return (mapContains(expr, "check") ||
+ mapContains(expr, "exists") ||
+ mapContains(expr, "variable-exists") ||
+ mapContains(expr, "equal") ||
+ mapContains(expr, "not-equal") ||
+ mapContains(expr, "regex-match") ||
+ mapContains(expr, "iregex-match") ||
+ mapContains(expr, "and") ||
+ mapContains(expr, "or") ||
+ mapContains(expr, "not") ||
+ mapContains(expr, "known") ||
+ mapContains(expr, "static"));
+}
+
+isc_boolean_t
+is_data_expression(struct element *expr)
+{
+ if (expr->type == ELEMENT_STRING)
+ return ISC_TRUE;
+ if (expr->type != ELEMENT_MAP)
+ return ISC_FALSE;
+ return (mapContains(expr, "substring") ||
+ mapContains(expr, "suffix") ||
+ mapContains(expr, "lowercase") ||
+ mapContains(expr, "uppercase") ||
+ mapContains(expr, "option") ||
+ mapContains(expr, "hardware") ||
+ mapContains(expr, "hw-type") ||
+ mapContains(expr, "hw-address") ||
+ mapContains(expr, "const-data") ||
+ mapContains(expr, "packet") ||
+ mapContains(expr, "concat") ||
+ mapContains(expr, "encapsulate") ||
+ mapContains(expr, "encode-int8") ||
+ mapContains(expr, "encode-int16") ||
+ mapContains(expr, "encode-int32") ||
+ mapContains(expr, "gethostbyname") ||
+ mapContains(expr, "binary-to-ascii") ||
+ mapContains(expr, "filename") ||
+ mapContains(expr, "server-name") ||
+ mapContains(expr, "reverse") ||
+ mapContains(expr, "pick-first-value") ||
+ mapContains(expr, "host-decl-name") ||
+ mapContains(expr, "leased-address") ||
+ mapContains(expr, "config-option") ||
+ mapContains(expr, "null") ||
+ mapContains(expr, "gethostname") ||
+ mapContains(expr, "v6relay"));
+}
+
+isc_boolean_t
+is_numeric_expression(struct element *expr)
+{
+ if (expr->type == ELEMENT_INTEGER)
+ return ISC_TRUE;
+ if (expr->type != ELEMENT_MAP)
+ return ISC_FALSE;
+ return (mapContains(expr, "extract-int8") ||
+ mapContains(expr, "extract-int16") ||
+ mapContains(expr, "extract-int32") ||
+ mapContains(expr, "const-int") ||
+ mapContains(expr, "lease-time") ||
+ mapContains(expr, "add") ||
+ mapContains(expr, "subtract") ||
+ mapContains(expr, "multiply") ||
+ mapContains(expr, "divide") ||
+ mapContains(expr, "remainder") ||
+ mapContains(expr, "binary-and") ||
+ mapContains(expr, "binary-or") ||
+ mapContains(expr, "binary-xor") ||
+ mapContains(expr, "client-state"));
+}
+/*
+static isc_boolean_t
+is_compound_expression(struct element *expr)
+{
+ return (mapContains(expr, "substring") ||
+ mapContains(expr, "suffix") ||
+ mapContains(expr, "option") ||
+ mapContains(expr, "concat") ||
+ mapContains(expr, "encode-int8") ||
+ mapContains(expr, "encode-int16") ||
+ mapContains(expr, "encode-int32") ||
+ mapContains(expr, "binary-to-ascii") ||
+ mapContains(expr, "reverse") ||
+ mapContains(expr, "pick-first-value") ||
+ mapContains(expr, "config-option") ||
+ mapContains(expr, "extract-int8") ||
+ mapContains(expr, "extract-int16") ||
+ mapContains(expr, "extract-int32") ||
+ mapContains(expr, "v6relay"));
+}
+*/
+static enum expression_context
+op_context(enum expr_op op)
+{
+ switch (op) {
+/* XXX Why aren't these specific? */
+ case expr_none:
+ case expr_match:
+ case expr_static:
+ case expr_check:
+ case expr_substring:
+ case expr_suffix:
+ case expr_lcase:
+ case expr_ucase:
+ case expr_concat:
+ case expr_encapsulate:
+ case expr_host_lookup:
+ case expr_not:
+ case expr_option:
+ case expr_hardware:
+ case expr_hw_type:
+ case expr_hw_address:
+ case expr_packet:
+ case expr_const_data:
+ case expr_extract_int8:
+ case expr_extract_int16:
+ case expr_extract_int32:
+ case expr_encode_int8:
+ case expr_encode_int16:
+ case expr_encode_int32:
+ case expr_const_int:
+ case expr_exists:
+ case expr_variable_exists:
+ case expr_known:
+ case expr_binary_to_ascii:
+ case expr_reverse:
+ case expr_filename:
+ case expr_sname:
+ case expr_pick_first_value:
+ case expr_host_decl_name:
+ case expr_config_option:
+ case expr_leased_address:
+ case expr_lease_time:
+ case expr_null:
+ case expr_variable_reference:
+ case expr_ns_add:
+ case expr_ns_delete:
+ case expr_ns_exists:
+ case expr_ns_not_exists:
+ case expr_dns_transaction:
+ case expr_arg:
+ case expr_funcall:
+ case expr_function:
+ case expr_gethostname:
+ case expr_v6relay:
+ case expr_concat_dclist:
+ return context_any;
+
+ case expr_equal:
+ case expr_not_equal:
+ case expr_regex_match:
+ case expr_iregex_match:
+ return context_data;
+
+ case expr_and:
+ return context_boolean;
+
+ case expr_or:
+ return context_boolean;
+
+ case expr_add:
+ case expr_subtract:
+ case expr_multiply:
+ case expr_divide:
+ case expr_remainder:
+ case expr_binary_and:
+ case expr_binary_or:
+ case expr_binary_xor:
+ case expr_client_state:
+ return context_numeric;
+ }
+ return context_any;
+}
+
+static int
+op_val(enum expr_op op)
+{
+ switch (op) {
+ case expr_none:
+ case expr_match:
+ case expr_static:
+ case expr_check:
+ case expr_substring:
+ case expr_suffix:
+ case expr_lcase:
+ case expr_ucase:
+ case expr_concat:
+ case expr_encapsulate:
+ case expr_host_lookup:
+ case expr_not:
+ case expr_option:
+ case expr_hardware:
+ case expr_hw_type:
+ case expr_hw_address:
+ case expr_packet:
+#ifdef keep_expr_const_data_precedence
+ case expr_const_data:
+#endif
+ case expr_extract_int8:
+ case expr_extract_int16:
+ case expr_extract_int32:
+ case expr_encode_int8:
+ case expr_encode_int16:
+ case expr_encode_int32:
+ case expr_const_int:
+ case expr_exists:
+ case expr_variable_exists:
+ case expr_known:
+ case expr_binary_to_ascii:
+ case expr_reverse:
+ case expr_filename:
+ case expr_sname:
+ case expr_pick_first_value:
+ case expr_host_decl_name:
+ case expr_config_option:
+ case expr_leased_address:
+ case expr_lease_time:
+ case expr_dns_transaction:
+ case expr_null:
+ case expr_variable_reference:
+ case expr_ns_add:
+ case expr_ns_delete:
+ case expr_ns_exists:
+ case expr_ns_not_exists:
+ case expr_arg:
+ case expr_funcall:
+ case expr_function:
+ /* XXXDPN: Need to assign sane precedences to these. */
+ case expr_binary_and:
+ case expr_binary_or:
+ case expr_binary_xor:
+ case expr_client_state:
+ case expr_gethostname:
+ case expr_v6relay:
+ case expr_concat_dclist:
+ return 100;
+
+ case expr_equal:
+ case expr_not_equal:
+ case expr_regex_match:
+ case expr_iregex_match:
+ return 4;
+
+ case expr_or:
+ case expr_and:
+ return 3;
+
+ case expr_add:
+ case expr_subtract:
+ return 2;
+
+ case expr_multiply:
+ case expr_divide:
+ case expr_remainder:
+ return 1;
+#ifndef keep_expr_const_data_precedence
+ case expr_const_data:
+ return 0;
+#endif
+ }
+ return 100;
+}
+
+static int
+op_precedence(enum expr_op op1, enum expr_op op2)
+{
+ return op_val(op1) - op_val(op2);
+}
+
+static enum expression_context
+expression_context(struct element *expr)
+{
+ if (is_data_expression(expr))
+ return context_data;
+ if (is_numeric_expression(expr))
+ return context_numeric;
+ if (is_boolean_expression(expr))
+ return context_boolean;
+ return context_any;
+}
+
+static enum expr_op
+expression(struct element *expr)
+{
+ if (expr->type != ELEMENT_MAP)
+ return expr_none;
+ if (mapContains(expr, "match"))
+ return expr_match;
+ if (mapContains(expr, "check"))
+ return expr_check;
+ if (mapContains(expr, "equal"))
+ return expr_equal;
+ if (mapContains(expr, "substring"))
+ return expr_substring;
+ if (mapContains(expr, "suffix"))
+ return expr_suffix;
+ if (mapContains(expr, "concat"))
+ return expr_concat;
+ if (mapContains(expr, "and"))
+ return expr_and;
+ if (mapContains(expr, "or"))
+ return expr_or;
+ if (mapContains(expr, "not"))
+ return expr_not;
+ if (mapContains(expr, "option"))
+ return expr_option;
+ if (mapContains(expr, "hardware"))
+ return expr_hardware;
+ if (mapContains(expr, "hw-type"))
+ return expr_hw_type;
+ if (mapContains(expr, "hw-address"))
+ return expr_hw_address;
+ if (mapContains(expr, "packet"))
+ return expr_packet;
+ if (mapContains(expr, "const-data"))
+ return expr_const_data;
+ if (mapContains(expr, "extract-int8"))
+ return expr_extract_int8;
+ if (mapContains(expr, "extract-int16"))
+ return expr_extract_int16;
+ if (mapContains(expr, "extract-int32"))
+ return expr_extract_int32;
+ if (mapContains(expr, "encode-int8"))
+ return expr_encode_int8;
+ if (mapContains(expr, "encode-int16"))
+ return expr_encode_int16;
+ if (mapContains(expr, "encode-int32"))
+ return expr_encode_int32;
+ if (mapContains(expr, "const-int"))
+ return expr_const_int;
+ if (mapContains(expr, "exists"))
+ return expr_exists;
+ if (mapContains(expr, "encapsulate"))
+ return expr_encapsulate;
+ if (mapContains(expr, "known"))
+ return expr_known;
+ if (mapContains(expr, "reverse"))
+ return expr_reverse;
+ if (mapContains(expr, "leased-address"))
+ return expr_leased_address;
+ if (mapContains(expr, "binary-to-ascii"))
+ return expr_binary_to_ascii;
+ if (mapContains(expr, "config-option"))
+ return expr_config_option;
+ if (mapContains(expr, "host-decl-name"))
+ return expr_host_decl_name;
+ if (mapContains(expr, "pick-first-value"))
+ return expr_pick_first_value;
+ if (mapContains(expr, "lease-time"))
+ return expr_lease_time;
+ if (mapContains(expr, "static"))
+ return expr_static;
+ if (mapContains(expr, "not-equal"))
+ return expr_not_equal;
+ if (mapContains(expr, "null"))
+ return expr_null;
+ if (mapContains(expr, "variable-exists"))
+ return expr_variable_exists;
+ if (mapContains(expr, "variable-reference"))
+ return expr_variable_reference;
+ if (mapContains(expr, "filename"))
+ return expr_filename;
+ if (mapContains(expr, "server-name"))
+ return expr_sname;
+ if (mapContains(expr, "arguments"))
+ return expr_arg;
+ if (mapContains(expr, "funcall"))
+ return expr_funcall;
+ if (mapContains(expr, "function"))
+ return expr_function;
+ if (mapContains(expr, "add"))
+ return expr_add;
+ if (mapContains(expr, "subtract"))
+ return expr_subtract;
+ if (mapContains(expr, "multiply"))
+ return expr_multiply;
+ if (mapContains(expr, "divide"))
+ return expr_divide;
+ if (mapContains(expr, "remainder"))
+ return expr_remainder;
+ if (mapContains(expr, "binary-and"))
+ return expr_binary_and;
+ if (mapContains(expr, "binary-or"))
+ return expr_binary_or;
+ if (mapContains(expr, "binary-xor"))
+ return expr_binary_xor;
+ if (mapContains(expr, "client-state"))
+ return expr_client_state;
+ if (mapContains(expr, "uppercase"))
+ return expr_ucase;
+ if (mapContains(expr, "lowercase"))
+ return expr_lcase;
+ if (mapContains(expr, "regex-match"))
+ return expr_regex_match;
+ if (mapContains(expr, "iregex-match"))
+ return expr_iregex_match;
+ if (mapContains(expr, "gethostname"))
+ return expr_gethostname;
+ if (mapContains(expr, "v6relay"))
+ return expr_v6relay;
+ if (TAILQ_EMPTY(&expr->value.map_value)) {
+ fprintf(stderr, "empty expression");
+ if (expr->key != NULL)
+ fprintf(stderr, " for %s", expr->key);
+ } else {
+ struct element *item;
+ isc_boolean_t first = ISC_TRUE;
+
+ TAILQ_FOREACH(item, &expr->value.map_value) {
+ const char *key;
+
+ key = item->key;
+ if (key == NULL)
+ continue;
+ if (first)
+ fprintf(stderr, ": %s", key);
+ else
+ fprintf(stderr, ", %s", key);
+ first = ISC_FALSE;
+ }
+ }
+ fputs("\n", stderr);
+ return expr_none;
+}
+
+int
+expr_precedence(enum expr_op op, struct element *expr)
+{
+ if (expr->type != ELEMENT_MAP)
+ return op_val(op);
+ return op_val(op) - op_val(expression(expr));
+}
diff --git a/keama/print.c b/keama/print.c
new file mode 100644
index 00000000..be85bd4d
--- /dev/null
+++ b/keama/print.c
@@ -0,0 +1,1490 @@
+/*
+ * Copyright (c) 2017 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "keama.h"
+
+#include <sys/errno.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+static void debug(const char* fmt, ...);
+
+const char *
+print_expression(struct element *expr, isc_boolean_t *lose)
+{
+ if (expr->type == ELEMENT_BOOLEAN)
+ return print_boolean_expression(expr, lose);
+ if (expr->type == ELEMENT_INTEGER)
+ return print_numeric_expression(expr, lose);
+ if (expr->type == ELEMENT_STRING)
+ return print_data_expression(expr, lose);
+
+ if (is_boolean_expression(expr))
+ return print_boolean_expression(expr, lose);
+ if (is_numeric_expression(expr))
+ return print_numeric_expression(expr, lose);
+ if (is_data_expression(expr))
+ return print_data_expression(expr, lose);
+ *lose = ISC_TRUE;
+ return "???";
+}
+
+const char *
+print_boolean_expression(struct element *expr, isc_boolean_t *lose)
+{
+ struct string *result;
+
+ if (expr->type == ELEMENT_BOOLEAN) {
+ if (boolValue(expr))
+ return "true";
+ else
+ return "false";
+ }
+
+ /*
+ * From is_boolean_expression
+ */
+ if (expr->type != ELEMENT_MAP) {
+ *lose = ISC_TRUE;
+ return "???";
+ }
+ result = allocString();
+
+ /* check */
+ if (mapContains(expr, "check")) {
+ struct element *name;
+
+ appendString(result, "check ");
+ name = mapGet(expr, "check");
+ if ((name == NULL) || (name->type != ELEMENT_STRING)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ } else
+ concatString(result, stringValue(name));
+ return result->content;
+ }
+
+ /* exists */
+ if (mapContains(expr, "exists")) {
+ struct element *arg;
+ struct element *universe;
+ struct element *name;
+
+ appendString(result, "exists ");
+ arg = mapGet(expr, "exists");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ universe = mapGet(arg, "universe");
+ if ((universe == NULL) || (universe->type != ELEMENT_STRING)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ concatString(result, stringValue(universe));
+ appendString(result, ".");
+ name = mapGet(arg, "name");
+ if ((name == NULL) || (name->type != ELEMENT_STRING)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ concatString(result, stringValue(name));
+ return result->content;
+ }
+
+ /* variable-exists */
+ if (mapContains(expr, "variable-exists")) {
+ struct element *name;
+
+ appendString(result, "variable-exists ");
+ name = mapGet(expr, "variable-exists");
+ if ((name == NULL) || (name->type != ELEMENT_STRING)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ } else
+ concatString(result, stringValue(name));
+ return result->content;
+ }
+
+ /* equal */
+ if (mapContains(expr, "equal")) {
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ isc_boolean_t add_parenthesis;
+
+ appendString(result, "equal ");
+ arg = mapGet(expr, "equal");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ result = allocString();
+ add_parenthesis = ISC_TF(expr_precedence(expr_equal,
+ left) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(left, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ appendString(result, " = ");
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ add_parenthesis = ISC_TF(expr_precedence(expr_equal,
+ right) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(right, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* not-equal */
+ if (mapContains(expr, "not-equal")) {
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ isc_boolean_t add_parenthesis;
+
+ appendString(result, "not-equal ");
+ arg = mapGet(expr, "not-equal");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ result = allocString();
+ add_parenthesis = ISC_TF(expr_precedence(expr_not_equal,
+ left) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(left, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ appendString(result, " != ");
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ add_parenthesis = ISC_TF(expr_precedence(expr_not_equal,
+ right) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(right, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* regex-match */
+ if (mapContains(expr, "regex-match")) {
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ isc_boolean_t add_parenthesis;
+
+ appendString(result, "regex-match ");
+ arg = mapGet(expr, "regex-match");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ result = allocString();
+ add_parenthesis = ISC_TF(expr_precedence(expr_regex_match,
+ left) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(left, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ appendString(result, " ~= ");
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ appendString(result, print_expression(right, lose));
+ return result->content;
+ }
+
+ /* iregex-match */
+ if (mapContains(expr, "iregex-match")) {
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ isc_boolean_t add_parenthesis;
+
+ appendString(result, "iregex-match ");
+ arg = mapGet(expr, "iregex-match");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ result = allocString();
+ add_parenthesis = ISC_TF(expr_precedence(expr_iregex_match,
+ left) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(left, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ appendString(result, " ~~ ");
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ appendString(result, print_expression(right, lose));
+ return result->content;
+ }
+
+ /* and */
+ if (mapContains(expr, "and")) {
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ isc_boolean_t add_parenthesis;
+
+ appendString(result, "and ");
+ arg = mapGet(expr, "and");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ result = allocString();
+ add_parenthesis = ISC_TF(expr_precedence(expr_and,
+ left) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(left, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ appendString(result, " and ");
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ add_parenthesis = ISC_TF(expr_precedence(expr_and,
+ right) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(right, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* or */
+ if (mapContains(expr, "or")) {
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ isc_boolean_t add_parenthesis;
+
+ appendString(result, "or ");
+ arg = mapGet(expr, "or");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ result = allocString();
+ add_parenthesis = ISC_TF(expr_precedence(expr_or,
+ left) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(left, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ appendString(result, " or ");
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ add_parenthesis = ISC_TF(expr_precedence(expr_or,
+ right) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(right, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* not */
+ if (mapContains(expr, "not")) {
+ struct element *arg;
+ isc_boolean_t add_parenthesis;
+
+ appendString(result, "not ");
+ arg = mapGet(expr, "not");
+ if (arg == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ add_parenthesis = ISC_TF(expr_precedence(expr_not,
+ arg) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(arg, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* known */
+ if (mapContains(expr, "known")) {
+ return "known";
+ }
+
+ /* static */
+ if (mapContains(expr, "static")) {
+ return "static";
+ }
+
+ /* variable-reference */
+ if (mapContains(expr, "variable-reference")) {
+ struct element *name;
+
+ appendString(result, "variable-reference ");
+ name = mapGet(expr, "variable-reference");
+ if ((name == NULL) || (name->type != ELEMENT_STRING)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ return stringValue(name)->content;
+ }
+
+ /* funcall */
+ if (mapContains(expr, "funcall")) {
+ struct element *arg;
+ struct element *name;
+ struct element *args;
+ size_t i;
+
+ appendString(result, "funcall ");
+ arg = mapGet(expr, "funcall");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ name = mapGet(arg, "name");
+ if ((name == NULL) || (name->type != ELEMENT_STRING)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ result = allocString();
+ concatString(result, stringValue(name));
+ appendString(result, "(");
+ args = mapGet(arg, "arguments");
+ if ((args == NULL) || (args->type != ELEMENT_LIST)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ for (i = 0; i < listSize(args); i++) {
+ struct element *item;
+
+ if (i != 0)
+ appendString(result, ", ");
+ item = listGet(args, i);
+ if (item == NULL) {
+ debug("funcall null argument %u",
+ (unsigned)i);
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ continue;
+ }
+ appendString(result, print_expression(item, lose));
+ }
+ appendString(result, ")");
+ return result->content;
+ }
+
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+}
+
+const char *
+print_data_expression(struct element *expr, isc_boolean_t *lose)
+{
+ struct string *result;
+
+ if (expr->type == ELEMENT_STRING)
+ return quote(stringValue(expr))->content;
+
+ /*
+ * From is_data_expression
+ */
+ if (expr->type != ELEMENT_MAP) {
+ *lose = ISC_TRUE;
+ return "???";
+ }
+ result = allocString();
+
+ /* substring */
+ if (mapContains(expr, "substring")) {
+ struct element *arg;
+ struct element *string;
+ struct element *offset;
+ struct element *length;
+
+ appendString(result, "substring(");
+ arg = mapGet(expr, "substring");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ string = mapGet(arg, "expression");
+ if (string == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_data_expression(string, lose));
+ appendString(result, ", ");
+ offset = mapGet(arg, "offset");
+ if (offset == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_numeric_expression(offset, lose));
+ appendString(result, ", ");
+ length = mapGet(arg, "length");
+ if (length == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_numeric_expression(length, lose));
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* suffix */
+ if (mapContains(expr, "suffix")) {
+ struct element *arg;
+ struct element *string;
+ struct element *length;
+
+ appendString(result, "suffix(");
+ arg = mapGet(expr, "suffix");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ string = mapGet(arg, "expression");
+ if (string == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_data_expression(string, lose));
+ appendString(result, ", ");
+ length = mapGet(arg, "length");
+ if (length == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_numeric_expression(length, lose));
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* lowercase */
+ if (mapContains(expr, "lowercase")) {
+ struct element *arg;
+
+ appendString(result, "lowercase(");
+ arg = mapGet(expr, "lowercase");
+ if (arg == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_data_expression(arg, lose));
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* uppercase */
+ if (mapContains(expr, "uppercase")) {
+ struct element *arg;
+
+ appendString(result, "uppercase(");
+ arg = mapGet(expr, "uppercase");
+ if (arg == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_data_expression(arg, lose));
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* option */
+ if (mapContains(expr, "option")) {
+ struct element *arg;
+ struct element *universe;
+ struct element *name;
+
+ appendString(result, "option ");
+ arg = mapGet(expr, "option");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ universe = mapGet(arg, "universe");
+ if ((universe == NULL) || (universe->type != ELEMENT_STRING)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ concatString(result, stringValue(universe));
+ appendString(result, ".");
+ name = mapGet(arg, "name");
+ if ((name == NULL) || (name->type != ELEMENT_STRING)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ concatString(result, stringValue(name));
+ return result->content;
+ }
+
+ /* hardware */
+ if (mapContains(expr, "hardware"))
+ return "hardware";
+
+ /* hw-type */
+ if (mapContains(expr, "hw-type"))
+ return "hw-type";
+
+ /* hw-address */
+ if (mapContains(expr, "hw-address"))
+ return "hw-address";
+
+ /* const-data */
+ if (mapContains(expr, "const-data")) {
+ struct element *arg;
+
+ arg = mapGet(expr, "const-data");
+ if ((arg == NULL) || (arg->type != ELEMENT_STRING)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ concatString(result, stringValue(arg));
+ return result->content;
+ }
+
+ /* packet */
+ if (mapContains(expr, "packet")) {
+ struct element *arg;
+ struct element *offset;
+ struct element *length;
+
+ appendString(result, "packet(");
+ arg = mapGet(expr, "packet");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ offset = mapGet(arg, "offset");
+ if (offset == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_numeric_expression(offset, lose));
+ appendString(result, ", ");
+ length = mapGet(arg, "length");
+ if (length == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_numeric_expression(length, lose));
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* concat */
+ if (mapContains(expr, "concat")) {
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+
+ appendString(result, "concat(");
+ arg = mapGet(expr, "concat");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_data_expression(left, lose));
+ appendString(result, ", ");
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_data_expression(right, lose));
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* encapsulate */
+ if (mapContains(expr, "encapsulate")) {
+ struct element *arg;
+
+ appendString(result, "encapsulate ");
+ arg = mapGet(expr, "encapsulate");
+ if (arg == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ appendString(result, print_data_expression(arg, lose));
+ return result->content;
+ }
+
+ /* encode-int8 */
+ if (mapContains(expr, "encode-int8")) {
+ struct element *arg;
+
+ appendString(result, "encode-int(");
+ arg = mapGet(expr, "encode-int8");
+ if (arg == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???, 8)");
+ return result->content;
+ }
+ appendString(result, print_numeric_expression(arg, lose));
+ appendString(result, ", 8)");
+ return result->content;
+ }
+
+ /* encode-int16 */
+ if (mapContains(expr, "encode-int16")) {
+ struct element *arg;
+
+ appendString(result, "encode-int(");
+ arg = mapGet(expr, "encode-int16");
+ if (arg == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???, 16)");
+ return result->content;
+ }
+ appendString(result, print_numeric_expression(arg, lose));
+ appendString(result, ", 16)");
+ return result->content;
+ }
+
+ /* encode-int32 */
+ if (mapContains(expr, "encode-int32")) {
+ struct element *arg;
+
+ appendString(result, "encode-int(");
+ arg = mapGet(expr, "encode-int32");
+ if (arg == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???, 32)");
+ return result->content;
+ }
+ appendString(result, print_numeric_expression(arg, lose));
+ appendString(result, ", 32)");
+ return result->content;
+ }
+
+ /* gethostbyname */
+ if (mapContains(expr, "gethostbyname")) {
+ struct element *arg;
+
+ appendString(result, "gethostbyname(");
+ arg = mapGet(expr, "gethostbyname");
+ if (arg == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ appendString(result, print_data_expression(arg, lose));
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* binary-to-ascii */
+ if (mapContains(expr, "binary-to-ascii")) {
+ struct element *arg;
+ struct element *base;
+ struct element *width;
+ struct element *separator;
+ struct element *buffer;
+
+ appendString(result, "binary-to-ascii(");
+ arg = mapGet(expr, "binary-to-ascii");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ base = mapGet(arg, "base");
+ if (base == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_numeric_expression(base, lose));
+ appendString(result, ", ");
+ width = mapGet(arg, "width");
+ if (width == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_numeric_expression(width, lose));
+ appendString(result, ", ");
+ separator = mapGet(arg, "separator");
+ if (separator == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_data_expression(separator, lose));
+ appendString(result, ", ");
+ buffer = mapGet(arg, "buffer");
+ if (buffer == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_data_expression(buffer, lose));
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* filename */
+ if (mapContains(expr, "filename"))
+ return "filename";
+
+ /* server-name */
+ if (mapContains(expr, "server-name"))
+ return "server-name";
+
+ /* reverse */
+ if (mapContains(expr, "reverse")) {
+ struct element *arg;
+ struct element *width;
+ struct element *buffer;
+
+ appendString(result, "reverse(");
+ arg = mapGet(expr, "reverse");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ width = mapGet(arg, "width");
+ if (width == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_numeric_expression(width, lose));
+ appendString(result, ", ");
+ buffer = mapGet(arg, "buffer");
+ if (buffer == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_data_expression(buffer, lose));
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* pick-first-value */
+ if (mapContains(expr, "pick-first-value")) {
+ struct element *arg;
+ size_t i;
+
+ appendString(result, "pick-first-value(");
+ arg = mapGet(expr, "pick-first-value");
+ if ((arg == NULL) || (arg->type != ELEMENT_LIST)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ for (i = 0; i < listSize(arg); i++) {
+ struct element *item;
+
+ if (i != 0)
+ appendString(result, ", ");
+ item = listGet(arg, i);
+ if (item == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ continue;
+ }
+ appendString(result,
+ print_data_expression(item, lose));
+ }
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* host-decl-name */
+ if (mapContains(expr, "host-decl-name"))
+ return "host-decl-name";
+
+ /* leased-address */
+ if (mapContains(expr, "leased-address"))
+ return "leased-address";
+
+ /* config-option */
+ if (mapContains(expr, "config-option")) {
+ struct element *arg;
+ struct element *universe;
+ struct element *name;
+
+ appendString(result, "config-option ");
+ arg = mapGet(expr, "config-option");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ universe = mapGet(arg, "universe");
+ if ((universe == NULL) || (universe->type != ELEMENT_STRING)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ concatString(result, stringValue(universe));
+ appendString(result, ".");
+ name = mapGet(arg, "name");
+ if ((name == NULL) || (name->type != ELEMENT_STRING)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ concatString(result, stringValue(name));
+ return result->content;
+ }
+
+ /* null */
+ if (mapContains(expr, "null"))
+ return "null";
+
+ /* gethostname */
+ if (mapContains(expr, "gethostname"))
+ return "gethostname";
+
+ /* v6relay */
+ if (mapContains(expr, "v6relay")) {
+ struct element *arg;
+ struct element *relay;
+ struct element *option;
+
+
+ appendString(result, "v6relay(");
+ arg = mapGet(expr, "v6relay");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ relay = mapGet(arg, "relay");
+ if (relay == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_numeric_expression(relay, lose));
+ appendString(result, ", ");
+ option = mapGet(arg, "relay-option");
+ if (option == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???" ")");
+ return result->content;
+ }
+ appendString(result, print_data_expression(option, lose));
+ appendString(result, ")");
+ return result->content;
+ }
+
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+}
+
+const char *
+print_numeric_expression(struct element *expr, isc_boolean_t *lose)
+{
+ struct string *result;
+
+ if (expr->type == ELEMENT_INTEGER) {
+ char buf[20];
+
+ snprintf(buf, sizeof(buf), "%lld", (long long)intValue(expr));
+ result = makeString(-1, buf);
+ return result->content;
+ }
+
+ /*
+ * From is_numeric_expression
+ */
+ if (expr->type != ELEMENT_MAP) {
+ *lose = ISC_TRUE;
+ return "???";
+ }
+ result = allocString();
+
+ /* extract-int8 */
+ if (mapContains(expr, "extract-int8")) {
+ struct element *arg;
+
+ appendString(result, "extract-int(");
+ arg = mapGet(expr, "extract-int8");
+ if (arg == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???, 8)");
+ return result->content;
+ }
+ appendString(result, print_data_expression(arg, lose));
+ appendString(result, ", 8)");
+ return result->content;
+ }
+
+ /* extract-int16 */
+ if (mapContains(expr, "extract-int16")) {
+ struct element *arg;
+
+ appendString(result, "extract-int(");
+ arg = mapGet(expr, "extract-int16");
+ if (arg == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???, 16)");
+ return result->content;
+ }
+ appendString(result, print_data_expression(arg, lose));
+ appendString(result, ", 16)");
+ return result->content;
+ }
+
+ /* extract-int32 */
+ if (mapContains(expr, "extract-int32")) {
+ struct element *arg;
+
+ appendString(result, "extract-int(");
+ arg = mapGet(expr, "extract-int32");
+ if (arg == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???, 32)");
+ return result->content;
+ }
+ appendString(result, print_data_expression(arg, lose));
+ appendString(result, ", 32)");
+ return result->content;
+ }
+
+ /* const-int */
+ if (mapContains(expr, "const-int")) {
+ struct element *arg;
+ char buf[20];
+
+ arg = mapGet(expr, "const-int");
+ if ((arg == NULL) || (arg->type != ELEMENT_INTEGER)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ snprintf(buf, sizeof(buf), "%lld", (long long)intValue(arg));
+ result = makeString(-1, buf);
+ return result->content;
+ }
+
+ /* lease-time */
+ if (mapContains(expr, "lease-time"))
+ return "lease-time";
+
+ /* add */
+ if (mapContains(expr, "add")) {
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ isc_boolean_t add_parenthesis;
+
+ appendString(result, "add ");
+ arg = mapGet(expr, "add");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ result = allocString();
+ add_parenthesis = ISC_TF(expr_precedence(expr_add,
+ left) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(left, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ appendString(result, " + ");
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ add_parenthesis = ISC_TF(expr_precedence(expr_add,
+ right) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(right, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* subtract */
+ if (mapContains(expr, "subtract")) {
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ isc_boolean_t add_parenthesis;
+
+ appendString(result, "subtract ");
+ arg = mapGet(expr, "subtract");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ result = allocString();
+ add_parenthesis = ISC_TF(expr_precedence(expr_subtract,
+ left) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(left, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ appendString(result, " - ");
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ add_parenthesis = ISC_TF(expr_precedence(expr_subtract,
+ right) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(right, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* multiply */
+ if (mapContains(expr, "multiply")) {
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ isc_boolean_t add_parenthesis;
+
+ appendString(result, "multiply ");
+ arg = mapGet(expr, "multiply");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ result = allocString();
+ add_parenthesis = ISC_TF(expr_precedence(expr_multiply,
+ left) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(left, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ appendString(result, " * ");
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ add_parenthesis = ISC_TF(expr_precedence(expr_multiply,
+ right) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(right, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* divide */
+ if (mapContains(expr, "divide")) {
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ isc_boolean_t add_parenthesis;
+
+ appendString(result, "divide ");
+ arg = mapGet(expr, "divide");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ result = allocString();
+ add_parenthesis = ISC_TF(expr_precedence(expr_divide,
+ left) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(left, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ appendString(result, " / ");
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ add_parenthesis = ISC_TF(expr_precedence(expr_divide,
+ right) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(right, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* remainder */
+ if (mapContains(expr, "remainder")) {
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ isc_boolean_t add_parenthesis;
+
+ appendString(result, "remainder ");
+ arg = mapGet(expr, "remainder");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ result = allocString();
+ add_parenthesis = ISC_TF(expr_precedence(expr_remainder,
+ left) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(left, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ appendString(result, " % ");
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ add_parenthesis = ISC_TF(expr_precedence(expr_remainder,
+ right) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(right, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* binary-and */
+ if (mapContains(expr, "binary-and")) {
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ isc_boolean_t add_parenthesis;
+
+ appendString(result, "binary-and ");
+ arg = mapGet(expr, "binary-and");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ result = allocString();
+ add_parenthesis = ISC_TF(expr_precedence(expr_binary_and,
+ left) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(left, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ appendString(result, " & ");
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ add_parenthesis = ISC_TF(expr_precedence(expr_binary_and,
+ right) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(right, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* binary-or */
+ if (mapContains(expr, "binary-or")) {
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ isc_boolean_t add_parenthesis;
+
+ appendString(result, "binary-or ");
+ arg = mapGet(expr, "binary-or");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ result = allocString();
+ add_parenthesis = ISC_TF(expr_precedence(expr_binary_or,
+ left) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(left, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ appendString(result, " | ");
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ add_parenthesis = ISC_TF(expr_precedence(expr_binary_or,
+ right) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(right, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* binary-xor */
+ if (mapContains(expr, "binary-xor")) {
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ isc_boolean_t add_parenthesis;
+
+ appendString(result, "binary-xor ");
+ arg = mapGet(expr, "binary-xor");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ result = allocString();
+ add_parenthesis = ISC_TF(expr_precedence(expr_binary_xor,
+ left) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(left, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ appendString(result, " ^ ");
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ add_parenthesis = ISC_TF(expr_precedence(expr_binary_xor,
+ right) < 0);
+ if (add_parenthesis)
+ appendString(result, "(");
+ appendString(result, print_expression(right, lose));
+ if (add_parenthesis)
+ appendString(result, ")");
+ return result->content;
+ }
+
+ /* client-state */
+ if (mapContains(expr, "client-state"))
+ return "client-state";
+
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+}
+
+static void
+debug(const char* fmt, ...)
+{
+ va_list list;
+
+ va_start(list, fmt);
+ vfprintf(stderr, fmt, list);
+ fprintf(stderr, "\n");
+ va_end(list);
+}
diff --git a/keama/reduce.c b/keama/reduce.c
new file mode 100644
index 00000000..24939473
--- /dev/null
+++ b/keama/reduce.c
@@ -0,0 +1,1016 @@
+/*
+ * Copyright (c) 2017 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ */
+
+#include "keama.h"
+
+#include <sys/errno.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+static struct element *reduce_equal_expression(struct element *left,
+ struct element *right);
+static void debug(const char* fmt, ...);
+
+/*
+ * boolean_expression :== CHECK STRING |
+ * NOT boolean-expression |
+ * data-expression EQUAL data-expression |
+ * data-expression BANG EQUAL data-expression |
+ * data-expression REGEX_MATCH data-expression |
+ * boolean-expression AND boolean-expression |
+ * boolean-expression OR boolean-expression
+ * EXISTS OPTION-NAME
+ */
+
+struct element *
+reduce_boolean_expression(struct element *expr)
+{
+ /* trivial case: already done */
+ if (expr->type == ELEMENT_BOOLEAN)
+ return expr;
+
+ /*
+ * From is_boolean_expression
+ */
+
+ if (expr->type != ELEMENT_MAP)
+ return NULL;
+
+ /* check */
+ if (mapContains(expr, "check"))
+ /*
+ * syntax := { "check": <collection_name> }
+ * semantic: check_collection
+ * on server try to match classes of the collection
+ */
+ return NULL;
+
+
+ /* exists */
+ if (mapContains(expr, "exists")) {
+ /*
+ * syntax := { "exists":
+ * { "universe": <option_space_old>,
+ * "name": <option_name> }
+ * }
+ * semantic: check universe/code from incoming packet
+ */
+ struct element *arg;
+ struct element *universe;
+ struct element *name;
+ struct option *option;
+ char result[80];
+
+ arg = mapGet(expr, "exists");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ debug("can't get exists argument");
+ return NULL;
+ }
+ universe = mapGet(arg, "universe");
+ if ((universe == NULL) || (universe->type != ELEMENT_STRING)) {
+ debug("can't get exists option universe");
+ return NULL;
+ }
+ name = mapGet(arg, "name");
+ if ((name == NULL) || (name->type != ELEMENT_STRING)) {
+ debug("can't get exists option name");
+ return NULL;
+ }
+ option = option_lookup_name(stringValue(universe)->content,
+ stringValue(name)->content);
+ if ((option == NULL) || (option->code == 0))
+ return NULL;
+ if (((local_family == AF_INET) &&
+ (strcmp(option->space->name, "dhcp4") != 0)) ||
+ ((local_family == AF_INET6) &&
+ (strcmp(option->space->name, "dhcp6") != 0)))
+ return NULL;
+ snprintf(result, sizeof(result),
+ "option[%u].exists", option->code);
+ return createString(makeString(-1, result));
+ }
+
+ /* variable-exists */
+ if (mapContains(expr, "variable-exists"))
+ /*
+ * syntax := { "variable-exists": <variable_name> }
+ * semantics: find_binding(scope, name)
+ */
+ return NULL;
+
+ /* equal */
+ if (mapContains(expr, "equal")) {
+ /*
+ * syntax := { "equal":
+ * { "left": <expression>,
+ * "right": <expression> }
+ * }
+ * semantics: evaluate branches and return true
+ * if same type and same value
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+
+ arg = mapGet(expr, "equal");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ debug("can't get equal argument");
+ return NULL;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ debug("can't get equal left branch");
+ return NULL;
+ }
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ debug("can't get equal right branch");
+ return NULL;
+ }
+ return reduce_equal_expression(left, right);
+ }
+
+ /* not-equal */
+ if (mapContains(expr, "not-equal")) {
+ /*
+ * syntax := { "not-equal":
+ * { "left": <expression>,
+ * "right": <expression> }
+ * }
+ * semantics: evaluate branches and return true
+ * if different type or different value
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct element *equal;
+ struct string *result;
+
+ arg = mapGet(expr, "not-equal");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ debug("can't get not-equal argument");
+ return NULL;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ debug("can't get not-equal left branch");
+ return NULL;
+ }
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ debug("can't get not-equal right branch");
+ return NULL;
+ }
+ equal = reduce_equal_expression(left, right);
+ if ((equal == NULL) || (equal->type != ELEMENT_STRING))
+ return NULL;
+ result = makeString(-1, "not (");
+ concatString(result, stringValue(equal));
+ appendString(result, ")");
+ return createString(result);
+ }
+
+ /* regex-match */
+ if (mapContains(expr, "regex-match"))
+ /*
+ * syntax := { "regex-match":
+ * { "left": <data_expression>,
+ * "right": <data_expression> }
+ * }
+ * semantics: evaluate branches, compile right as a
+ * regex and apply it to left
+ */
+ return NULL;
+
+ /* iregex-match */
+ if (mapContains(expr, "iregex-match"))
+ /*
+ * syntax := { "regex-match":
+ * { "left": <data_expression>,
+ * "right": <data_expression> }
+ * }
+ * semantics: evaluate branches, compile right as a
+ * case insensistive regex and apply it to left
+ */
+ return NULL;
+
+ /* and */
+ if (mapContains(expr, "and")) {
+ /*
+ * syntax := { "and":
+ * { "left": <boolean_expression>,
+ * "right": <boolean_expression> }
+ * }
+ * semantics: evaluate branches, return true
+ * if both are true
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct string *result;
+
+ arg = mapGet(expr, "and");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ debug("can't get and argument");
+ return NULL;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ debug("can't get and left branch");
+ return NULL;
+ }
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ debug("can't get and right branch");
+ return NULL;
+ }
+ left = reduce_boolean_expression(left);
+ if ((left == NULL) || (left->type != ELEMENT_STRING))
+ return NULL;
+ right = reduce_boolean_expression(right);
+ if ((right == NULL) || (right->type != ELEMENT_STRING))
+ return NULL;
+ result = makeString(-1, "(");
+ concatString(result, stringValue(left));
+ appendString(result, ") and (");
+ concatString(result, stringValue(right));
+ appendString(result, ")");
+ return createString(result);
+ }
+
+ /* or */
+ if (mapContains(expr, "or")) {
+ /*
+ * syntax := { "or":
+ * { "left": <boolean_expression>,
+ * "right": <boolean_expression> }
+ * }
+ * semantics: evaluate branches, return true
+ * if any is true
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct string *result;
+
+ arg = mapGet(expr, "or");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ debug("can't get or argument");
+ return NULL;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ debug("can't get or left branch");
+ return NULL;
+ }
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ debug("can't get or right branch");
+ return NULL;
+ }
+ left = reduce_boolean_expression(left);
+ if ((left == NULL) || (left->type != ELEMENT_STRING))
+ return NULL;
+ right = reduce_boolean_expression(right);
+ if ((right == NULL) || (right->type != ELEMENT_STRING))
+ return NULL;
+ result = makeString(-1, "(");
+ concatString(result, stringValue(left));
+ appendString(result, ") or (");
+ concatString(result, stringValue(right));
+ appendString(result, ")");
+ return createString(result);
+ }
+
+ /* not */
+ if (mapContains(expr, "not")) {
+ /*
+ * syntax := { "not": <boolean_expression> }
+ * semantic: evaluate its branch and return its negation
+ */
+ struct element *arg;
+ struct string *result;
+
+ arg = mapGet(expr, "not");
+ if (arg == NULL) {
+ debug("can't get not argument");
+ return NULL;
+ }
+ arg = reduce_boolean_expression(arg);
+ if ((arg == NULL) || (arg->type != ELEMENT_STRING))
+ return NULL;
+ result = makeString(-1, "not (");
+ concatString(result, stringValue(arg));
+ appendString(result, ")");
+ return createString(result);
+ }
+
+ /* known */
+ if (mapContains(expr, "known"))
+ /*
+ * syntax := { "known": null }
+ * semantics: client is known, i.e., has a matching
+ * host declaration (aka reservation in Kea)
+ */
+ return NULL;
+
+ /* static */
+ if (mapContains(expr, "static"))
+ /*
+ * syntax := { "static": null }
+ * semantics: lease is static (doesn't exist in Kea)
+ */
+ return NULL;
+
+ return NULL;
+}
+
+/*
+ * data_expression :== SUBSTRING LPAREN data-expression COMMA
+ * numeric-expression COMMA
+ * numeric-expression RPAREN |
+ * CONCAT LPAREN data-expression COMMA
+ * data-expression RPAREN
+ * SUFFIX LPAREN data_expression COMMA
+ * numeric-expression RPAREN |
+ * LCASE LPAREN data_expression RPAREN |
+ * UCASE LPAREN data_expression RPAREN |
+ * OPTION option_name |
+ * HARDWARE |
+ * PACKET LPAREN numeric-expression COMMA
+ * numeric-expression RPAREN |
+ * V6RELAY LPAREN numeric-expression COMMA
+ * data-expression RPAREN |
+ * STRING |
+ * colon_separated_hex_list
+ */
+
+struct element *
+reduce_data_expression(struct element *expr)
+{
+ /* trivial case: already done */
+ if (expr->type == ELEMENT_STRING)
+ return expr;
+
+ /*
+ * From is_data_expression
+ */
+
+ if (expr->type != ELEMENT_MAP)
+ return NULL;
+
+ /* substring */
+ if (mapContains(expr, "substring")) {
+ /*
+ * syntax := { "substring":
+ * { "expression": <data_expression>,
+ * "offset": <numeric_expression>,
+ * "length": <numeric_expression> }
+ * }
+ * semantic: evaluate arguments, if the string is
+ * shorter than offset return "" else return substring
+ */
+ struct element *arg;
+ struct element *string;
+ struct element *offset;
+ struct element *length;
+ struct string *result;
+ int64_t off;
+ int64_t len;
+ char buf[80];
+
+ arg = mapGet(expr, "substring");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ debug("can't get substring argument");
+ return NULL;
+ }
+ string = mapGet(arg, "expression");
+ if (string == NULL) {
+ debug("can't get substring expression");
+ return NULL;
+ }
+ offset = mapGet(arg, "offset");
+ if (offset == NULL) {
+ debug("can't get substring offset");
+ return NULL;
+ }
+ length = mapGet(arg, "length");
+ if (length == NULL) {
+ debug("can't get substring length");
+ return NULL;
+ }
+ /* can't be a literal as it was evaluated before */
+ string = reduce_data_expression(string);
+ if ((string == NULL) || (string->type != ELEMENT_STRING))
+ return NULL;
+ offset = reduce_numeric_expression(offset);
+ if ((offset == NULL) || (offset->type != ELEMENT_INTEGER))
+ return NULL;
+ off = intValue(offset);
+ if (off < 0) {
+ debug("substring with a negative offset (%lld)",
+ (long long)off);
+ return NULL;
+ }
+ length = reduce_numeric_expression(length);
+ if ((length == NULL) || (length->type != ELEMENT_INTEGER))
+ return NULL;
+ len = intValue(length);
+ if (len < 0) {
+ debug("substring with a negative length (%lld)",
+ (long long)len);
+ return NULL;
+ }
+ result = makeString(-1, "substring(");
+ concatString(result, stringValue(string));
+ snprintf(buf, sizeof(buf),
+ ",%u,%u)", (unsigned)off, (unsigned)len);
+ appendString(result, buf);
+ return createString(result);
+ }
+
+ /* suffix */
+ if (mapContains(expr, "suffix")) {
+ /*
+ * syntax := { "suffix":
+ * { "expression": <data_expression>,
+ * "length": <numeric_expression> }
+ * }
+ * semantic: evaluate arguments, if the string is
+ * shorter than length return it else return suffix
+ */
+ struct element *arg;
+ struct element *string;
+ struct element *length;
+ struct string *result;
+ int64_t len;
+ char buf[80];
+
+ arg = mapGet(expr, "suffix");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ debug("can't get suffix argument");
+ return NULL;
+ }
+ string = mapGet(arg, "expression");
+ if (string == NULL) {
+ debug("can't get suffix expression");
+ return NULL;
+ }
+ length = mapGet(arg, "length");
+ if (length == NULL) {
+ debug("can't get suffix length");
+ return NULL;
+ }
+ /* can't be a literal as it was evaluated before */
+ string = reduce_data_expression(string);
+ if ((string == NULL) || (string->type != ELEMENT_STRING))
+ return NULL;
+ length = reduce_numeric_expression(length);
+ if ((length == NULL) || (length->type != ELEMENT_INTEGER))
+ return NULL;
+ len = intValue(length);
+ if (len < 0) {
+ debug("suffix with a negative length (%lld)",
+ (long long)len);
+ return NULL;
+ }
+ result = makeString(-1, "substring(");
+ concatString(result, stringValue(string));
+ snprintf(buf, sizeof(buf), ",-%u,all)", (unsigned)len);
+ appendString(result, buf);
+ return createString(result);
+ }
+
+ /* lowercase */
+ if (mapContains(expr, "lowercase"))
+ /*
+ * syntax := { "lowercase": <data_expression> }
+ * semantic: evaluate its argument and apply tolower to
+ * its content
+ */
+ return NULL;
+
+ /* uppercase */
+ if (mapContains(expr, "uppercase"))
+ /*
+ * syntax := { "uppercase": <data_expression> }
+ * semantic: evaluate its argument and apply toupper to
+ * its content
+ */
+ return NULL;
+
+ /* option */
+ if (mapContains(expr, "option")) {
+ /*
+ * syntax := { "option":
+ * { "universe": <option_space_old>,
+ * "name": <option_name> }
+ * }
+ * semantic: get universe/code option from incoming packet
+ */
+ struct element *arg;
+ struct element *universe;
+ struct element *name;
+ struct option *option;
+ char result[80];
+
+ arg = mapGet(expr, "option");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ debug("can't get option argument");
+ return NULL;
+ }
+ universe = mapGet(arg, "universe");
+ if ((universe == NULL) || (universe->type != ELEMENT_STRING)) {
+ debug("can't get option universe");
+ return NULL;
+ }
+ name = mapGet(arg, "name");
+ if ((name == NULL) || (name->type != ELEMENT_STRING)) {
+ debug("can't get option name");
+ return NULL;
+ }
+ option = option_lookup_name(stringValue(universe)->content,
+ stringValue(name)->content);
+ if ((option == NULL) || (option->code == 0))
+ return NULL;
+ if (((local_family == AF_INET) &&
+ (strcmp(option->space->name, "dhcp4") != 0)) ||
+ ((local_family == AF_INET6) &&
+ (strcmp(option->space->name, "dhcp6") != 0)))
+ return NULL;
+ snprintf(result, sizeof(result),
+ "option[%u].hex", option->code);
+ return createString(makeString(-1, result));
+ }
+
+ /* hardware */
+ if (mapContains(expr, "hardware")) {
+ /*
+ * syntax := { "hardware": null }
+ * semantic: get mac type and address from incoming packet
+ */
+ struct string *result;
+
+ if (local_family != AF_INET) {
+ debug("get hardware for DHCPv6");
+ return NULL;
+ }
+ result = makeString(-1,
+ "concat(substring(pkt4.htype,-1,all),pkt4.mac)");
+ return createString(result);
+ }
+
+ /* hw-type */
+ if (mapContains(expr, "hw-type")) {
+ /*
+ * ADDED
+ * syntax := { "hw-type": null }
+ * semantic: get mac type from incoming packet
+ */
+ struct string *result;
+
+ if (local_family != AF_INET) {
+ debug("get hw-type for DHCPv6");
+ return NULL;
+ }
+ result = makeString(-1, "substring(pkt4.htype,-1,all)");
+ return createString(result);
+ }
+
+ /* hw-address */
+ if (mapContains(expr, "hw-address")) {
+ /*
+ * ADDED
+ * syntax := { "hw-address": null }
+ * semantic: get mac address from incoming packet
+ */
+ struct string *result;
+
+ if (local_family != AF_INET) {
+ debug("get hw-address for DHCPv6");
+ return NULL;
+ }
+ result = makeString(-1, "pkt4.mac");
+ return createString(result);
+ }
+
+ /* const-data */
+ if (mapContains(expr, "const-data")) {
+ /*
+ * syntax := { "const-data": <string> }
+ * semantic: embedded string value
+ */
+ struct element *arg;
+
+ arg = mapGet(expr, "const-data");
+ if ((arg == NULL) || (arg->type != ELEMENT_STRING)) {
+ debug("can't get const-data argument");
+ return NULL;
+ }
+ return createString(stringValue(arg));
+ }
+
+ /* packet */
+ if (mapContains(expr, "packet"))
+ /*
+ * syntax := { "packet":
+ * { "offset": <numeric_expression>,
+ * "length": <numeric_expression> }
+ * }
+ * semantic: return the selected substring of the incoming
+ * packet content
+ */
+ return NULL;
+
+ /* concat */
+ if (mapContains(expr, "concat")) {
+ /*
+ * syntax := { "concat":
+ * { "left": <data_expression>,
+ * "right": <data_expression> }
+ * }
+ * semantic: evaluate arguments and return the concatenation
+ */
+ struct element *arg;
+ struct element *left;
+ struct element *right;
+ struct string *result;
+
+ arg = mapGet(expr, "concat");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ debug("can't get concat argument");
+ return NULL;
+ }
+ left = mapGet(arg, "left");
+ if (left == NULL) {
+ debug("can't get concat left branch");
+ return NULL;
+ }
+ right = mapGet(arg, "right");
+ if (right == NULL) {
+ debug("can't get concat right branch");
+ return NULL;
+ }
+ /* left is a literal case */
+ if (left->type == ELEMENT_STRING) {
+ /* can't be a literal as it was evaluated before */
+ right = reduce_data_expression(right);
+ if ((right == NULL) || (right->type != ELEMENT_STRING))
+ return NULL;
+ result = makeString(-1, "concat(");
+ concatString(result, quote(stringValue(left)));
+ appendString(result, ", ");
+ concatString(result, stringValue(right));
+ appendString(result, ")");
+ return createString(result);
+ }
+ left = reduce_data_expression(left);
+ if ((left == NULL) || (left->type != ELEMENT_STRING))
+ return NULL;
+ /* right is a literal case */
+ if (right->type == ELEMENT_STRING) {
+ /* literal left was handled before */
+ result = makeString(-1, "concat(");
+ concatString(result, stringValue(left));
+ appendString(result, ", ");
+ concatString(result, quote(stringValue(right)));
+ appendString(result, ")");
+ return createString(result);
+ }
+ right = reduce_data_expression(right);
+ if ((right == NULL) || (right->type != ELEMENT_STRING))
+ return NULL;
+ result = makeString(-1, "concat(");
+ concatString(result, stringValue(left));
+ appendString(result, ", ");
+ concatString(result, stringValue(right));
+ appendString(result, ")");
+ return createString(result);
+ }
+
+ /* encapsulate */
+ if (mapContains(expr, "encapsulate"))
+ /*
+ * syntax := { "encapsulate": <encapsulated_space> }
+ * semantic: encapsulate options of the given space
+ */
+ return NULL;
+
+ /* encode-int8 */
+ if (mapContains(expr, "encode-int8"))
+ /*
+ * syntax := { "encode-int8": <numeric_expression> }
+ * semantic: return a string buffer with the evaluated
+ * number as content
+ */
+ return NULL;
+
+ /* encode-int16 */
+ if (mapContains(expr, "encode-int16"))
+ /*
+ * syntax := { "encode-int16": <numeric_expression> }
+ * semantic: return a string buffer with the evaluated
+ * number as content
+ */
+ return NULL;
+
+ /* encode-int32 */
+ if (mapContains(expr, "encode-int32"))
+ /*
+ * syntax := { "encode-int32": <numeric_expression> }
+ * semantic: return a string buffer with the evaluated
+ * number as content
+ */
+ return NULL;
+
+ /* gethostbyname */
+ if (mapContains(expr, "gethostbyname"))
+ /*
+ * syntax := { "gethostbyname": <string> }
+ * semantic: call gethostbyname and return
+ * a binary buffer with addresses
+ */
+ return NULL;
+
+ /* binary-to-ascii */
+ if (mapContains(expr, "binary-to-ascii"))
+ /*
+ * syntax := { "binary-to-ascii":
+ * { "base": <numeric_expression 2..16>,
+ * "width": <numeric_expression 8, 16 or 32>,
+ * "separator": <data_expression>,
+ * "buffer": <data_expression> }
+ * }
+ * semantic: split the input buffer into int8/16/32 numbers,
+ * output them separated by the given string
+ */
+ return NULL;
+
+ /* filename */
+ if (mapContains(expr, "filename"))
+ /*
+ * syntax := { "filename": null }
+ * semantic: get filename field from incoming DHCPv4 packet
+ */
+ return NULL;
+
+ /* server-name */
+ if (mapContains(expr, "server-name"))
+ /*
+ * syntax := { "server-name": null }
+ * semantic: get server-name field from incoming DHCPv4 packet
+ */
+ return NULL;
+
+ /* reverse */
+ if (mapContains(expr, "reverse"))
+ /*
+ * syntax := { "reverse":
+ * { "width": <numeric_expression>,
+ * "buffer": <data_expression> }
+ * }
+ * semantic: reverse the input buffer by width chunks of bytes
+ */
+ return NULL;
+
+ /* pick-first-value */
+ if (mapContains(expr, "pick-first-value"))
+ /*
+ * syntax := { "pick-first-value":
+ * [ <data_expression>, ... ]
+ * }
+ * semantic: evaluates expressions and return the first
+ * not null, return null if all are null
+ */
+ return NULL;
+
+ /* host-decl-name */
+ if (mapContains(expr, "host-decl-name"))
+ /*
+ * syntax := { "host-decl-name": null }
+ * semantic: return the name of the matching host
+ * declaration (aka revervation in kea) or null
+ */
+ return NULL;
+
+ /* leased-address */
+ if (mapContains(expr, "leased-address"))
+ /*
+ * syntax := { "leased-address": null }
+ * semantic: return the address of the assigned lease or
+ * log a message
+ */
+ return NULL;
+
+ /* config-option */
+ if (mapContains(expr, "config-option"))
+ /*
+ * syntax := { "config-option":
+ * { "universe": <option_space_old>,
+ * "name": <option_name> }
+ * }
+ * semantic: get universe/code option to send
+ */
+ return NULL;
+
+ /* null */
+ if (mapContains(expr, "null")) {
+ /*
+ * syntax := { "null": null }
+ * semantic: return null
+ */
+ debug("unexpected null: this expression was not evaluated");
+ return NULL;
+ }
+
+ /* gethostname */
+ if (mapContains(expr, "gethostname")) {
+ /*
+ * syntax := { "gethostname": null }
+ * semantic: return gethostname
+ */
+ debug("unexpected gethostname: this expression was not "
+ "evaluated");
+ return NULL;
+ }
+
+ /* v6relay */
+ if (mapContains(expr, "v6relay")) {
+ /*
+ * syntax := { "v6relay":
+ * { "relay": <numeric_expression>,
+ * "relay-option" <data_expression> }
+ * }
+ * semantic: relay is a counter from client, 0 is no-op,
+ * 1 is the relay closest to the client, etc, option
+ * is a dhcp6 option ans is return when found
+ */
+ struct element *arg;
+ struct element *relay;
+ struct element *universe;
+ struct element *name;
+ struct option *option;
+ int64_t r;
+ char result[100];
+
+ if (local_family != AF_INET6) {
+ debug("get v6relay for DHCPv4");
+ return NULL;
+ }
+ arg = mapGet(expr, "v6relay");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ debug("can't get v6relay argument");
+ return NULL;
+ }
+ relay = mapGet(arg, "relay");
+ if (relay == NULL) {
+ debug("can't get v6relay relay");
+ return NULL;
+ }
+ relay = reduce_numeric_expression(relay);
+ if ((relay == NULL) || (relay->type != ELEMENT_INTEGER))
+ return NULL;
+ r = intValue(relay);
+ if (r < 0) {
+ debug("v6relay called with illegal relay (%lld)",
+ (long long)r);
+ return NULL;
+ }
+ arg = mapGet(arg, "relay-option");
+ if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
+ debug("can't get v6relay relay-option");
+ return NULL;
+ }
+ universe = mapGet(arg, "universe");
+ if ((universe == NULL) || (universe->type != ELEMENT_STRING)) {
+ debug("can't get v6relay option universe");
+ NULL;
+ }
+ name = mapGet(arg, "name");
+ if ((name == NULL) || (name->type != ELEMENT_STRING)) {
+ debug("can't get v6relay option name");
+ return NULL;
+ }
+ option = option_lookup_name(stringValue(universe)->content,
+ stringValue(name)->content);
+ if ((option == NULL) || (option->code == 0) ||
+ (strcmp(option->space->name, "dhcp6") != 0))
+ return NULL;
+ if (r == 0)
+ snprintf(result, sizeof(result),
+ "option[%u].hex", option->code);
+ else {
+ /* r > MAX_V6RELAY_HOPS means the relay closest
+ to server */
+ if (r > MAX_V6RELAY_HOPS)
+ r = 0;
+ /* Kea counts from the server, use negative nesting
+ levels to count from the client */
+ snprintf(result, sizeof(result),
+ "relay6[%d].option[%u].hex",
+ (int)-r, option->code);
+ }
+ return createString(makeString(-1, result));
+ }
+
+ return NULL;
+}
+
+struct element *
+reduce_numeric_expression(struct element *expr)
+{
+ /* trivial case: already done */
+ if (expr->type == ELEMENT_INTEGER)
+ return expr;
+
+ if (expr->type != ELEMENT_MAP)
+ return NULL;
+
+ /* Kea has no numeric operators... */
+ return NULL;
+}
+
+static struct element *
+reduce_equal_expression(struct element *left, struct element *right)
+{
+ struct string *result;
+
+ /*
+ * numeric case was handled by evaluation
+ */
+
+ if (!is_data_expression(left) || !is_data_expression(right))
+ return NULL;
+
+ /* left is a literal case */
+ if (left->type == ELEMENT_STRING) {
+ /* can't be a literal as it was evaluated before */
+ right = reduce_data_expression(right);
+ if ((right == NULL) || (right->type != ELEMENT_STRING))
+ return NULL;
+ result = allocString();
+ concatString(result, quote(stringValue(left)));
+ appendString(result, " == ");
+ concatString(result, stringValue(right));
+ return createString(result);
+ }
+ left = reduce_data_expression(left);
+ if ((left == NULL) || (left->type != ELEMENT_STRING))
+ return NULL;
+
+ /* right is a literal case */
+ if (right->type == ELEMENT_STRING) {
+ /* literal left was handled before */
+ result = allocString();
+ concatString(result, stringValue(left));
+ appendString(result, " == ");
+ concatString(result, quote(stringValue(right)));
+ return createString(result);
+ }
+ right = reduce_data_expression(right);
+ if ((right == NULL) || (right->type != ELEMENT_STRING))
+ return NULL;
+
+ result = allocString();
+ concatString(result, stringValue(left));
+ appendString(result, " == ");
+ concatString(result, stringValue(right));
+ return createString(result);
+}
+
+static void
+debug(const char* fmt, ...)
+{
+ va_list list;
+
+ va_start(list, fmt);
+ vfprintf(stderr, fmt, list);
+ fprintf(stderr, "\n");
+ va_end(list);
+}
diff --git a/keama/tests/README b/keama/tests/README
new file mode 100644
index 00000000..69686155
--- /dev/null
+++ b/keama/tests/README
@@ -0,0 +1,42 @@
+Tests are dividing on error vs working, and DHCPv4 vs DHCPv6.
+
+Names of files about test xyz have xyz as body and an extension.
+
+Extensions:
+ - .err4 = source for error test in DHCPv4
+ - .errF = source for error test in DHCPv4 with -r fatal
+ - .errP = source for error test in DHCPv4 with -r pass
+ - .err6 = source for error test in DHCPv6
+ - .err = source for error test in DHCPv4 and DHCPv6
+ - .msg = resultat (first line of standard error) for error test
+ - .in4 = source for working test in DHCPv4
+ - .in6 = source for working test in DHCPv6
+ - .ind = source for working test in DHCPv4 with -D
+ - .inD = source for working test in DHCPv6 with -D
+ - .inn = source for working test in DHCPv4 with -N
+ - .inN = source for working test in DHCPv6 with -N
+ - .inl = source for working test in DHCPv4 with -l $HOOK
+ - .inL = source for working test in DHCPv6 with -l $HOOK
+ - .outl = resultat for working test with default hook library path
+ - .outL = resultat for working test with default hook library path
+ - .out = resultat for working test
+There is no working test in both DHCPv4 and DHCPv6.
+The body of the name of a working test must include 4 or 6 so
+the .out can be submitted to kea-dhcp4 or kea-dhcp6
+
+runone.sh xyz.ext
+ -> run the xyz test
+runall.sh
+ -> run all tests
+
+Check output syntax with kea-dhcp4 and kea-dhcp6:
+ - Set KEA4 and KEA6 environment variables to kea-dhcp4 and kea-dhcp6
+ - Set HOOK to a place to find hooks (currently libdhcp_flex_id.so),
+ please use the directory name with a trailing /
+ - The en0 interface is supposed to exist (or replace "en0" in all files)
+ - Note that runall.sh must be run before checkall.sh
+
+checkone.sh xyz.out
+ -> check the syntax of xyz.out
+checkall.sh
+ -> check the syntax of all .out files
diff --git a/keama/tests/badcasexsc.err b/keama/tests/badcasexsc.err
new file mode 100644
index 00000000..9cfdd87c
--- /dev/null
+++ b/keama/tests/badcasexsc.err
@@ -0,0 +1,7 @@
+# bad case executable statement construct
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# a raw default statement
+case "foo": break;
diff --git a/keama/tests/badcasexsc.msg b/keama/tests/badcasexsc.msg
new file mode 100644
index 00000000..636bb31a
--- /dev/null
+++ b/keama/tests/badcasexsc.msg
@@ -0,0 +1 @@
+badcasexsc.err line 7: case statement in inappropriate scope.
diff --git a/keama/tests/badclass.err b/keama/tests/badclass.err
new file mode 100644
index 00000000..8dc59c05
--- /dev/null
+++ b/keama/tests/badclass.err
@@ -0,0 +1,12 @@
+# bad (double match-if) class declaration config
+
+# options
+option mysystem code 250 = text;
+option myversion code 251 = unsigned integer 16;
+
+# class declaration
+class "foobar" {
+ match if option mysystem = "version1";
+ match if option mysystem = "version2";
+ option myversion 1;
+}
diff --git a/keama/tests/badclass.msg b/keama/tests/badclass.msg
new file mode 100644
index 00000000..69a603ab
--- /dev/null
+++ b/keama/tests/badclass.msg
@@ -0,0 +1 @@
+badclass.err line 10: A class may only have one 'match if' clause.
diff --git a/keama/tests/badclass2.err b/keama/tests/badclass2.err
new file mode 100644
index 00000000..551a1729
--- /dev/null
+++ b/keama/tests/badclass2.err
@@ -0,0 +1,12 @@
+# bad (2 match) class declaration config
+
+# options
+option mysystem code 250 = text;
+option myversion code 251 = unsigned integer 16;
+
+# superclass declaration
+class "foobar" {
+ match option mysystem;
+ match option myversion;
+ option myversion 1;
+}
diff --git a/keama/tests/badclass2.msg b/keama/tests/badclass2.msg
new file mode 100644
index 00000000..3afd2b3b
--- /dev/null
+++ b/keama/tests/badclass2.msg
@@ -0,0 +1 @@
+badclass2.err line 10: can't override existing submatch/spawn
diff --git a/keama/tests/baddecl2array.err b/keama/tests/baddecl2array.err
new file mode 100644
index 00000000..80701141
--- /dev/null
+++ b/keama/tests/baddecl2array.err
@@ -0,0 +1,4 @@
+# bad 2a (array in array) option declaration
+
+option space foobar;
+option foobar.fmt-Bat code 1 = array of array of unsigned integer 8;
diff --git a/keama/tests/baddecl2array.msg b/keama/tests/baddecl2array.msg
new file mode 100644
index 00000000..e7ad089e
--- /dev/null
+++ b/keama/tests/baddecl2array.msg
@@ -0,0 +1 @@
+baddecl2array.err line 4: no nested arrays.
diff --git a/keama/tests/baddecl2record.err b/keama/tests/baddecl2record.err
new file mode 100644
index 00000000..d237b636
--- /dev/null
+++ b/keama/tests/baddecl2record.err
@@ -0,0 +1,4 @@
+# bad 2r (record in record) option declaration
+
+option space foobar;
+option foobar.fmt-Bat code 1 = { unsigned integer 8, { text } };
diff --git a/keama/tests/baddecl2record.msg b/keama/tests/baddecl2record.msg
new file mode 100644
index 00000000..4dfb2082
--- /dev/null
+++ b/keama/tests/baddecl2record.msg
@@ -0,0 +1 @@
+baddecl2record.err line 4: unknown data type {
diff --git a/keama/tests/baddeclBt.err b/keama/tests/baddeclBt.err
new file mode 100644
index 00000000..921521f2
--- /dev/null
+++ b/keama/tests/baddeclBt.err
@@ -0,0 +1,4 @@
+# bad Bt (not in record) option declaration
+
+option space foobar;
+option foobar.fmt-Bat code 1 = unsigned integer 8, text;
diff --git a/keama/tests/baddeclBt.msg b/keama/tests/baddeclBt.msg
new file mode 100644
index 00000000..49c63a58
--- /dev/null
+++ b/keama/tests/baddeclBt.msg
@@ -0,0 +1 @@
+baddeclBt.err line 4: semicolon expected.
diff --git a/keama/tests/baddefaultxsc.err b/keama/tests/baddefaultxsc.err
new file mode 100644
index 00000000..69157dd4
--- /dev/null
+++ b/keama/tests/baddefaultxsc.err
@@ -0,0 +1,7 @@
+# bad default executable statement construct
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# a raw default statement
+default: break;
diff --git a/keama/tests/baddefaultxsc.msg b/keama/tests/baddefaultxsc.msg
new file mode 100644
index 00000000..7780a1bb
--- /dev/null
+++ b/keama/tests/baddefaultxsc.msg
@@ -0,0 +1 @@
+baddefaultxsc.err line 7: switch default statement in inappropriate scope.
diff --git a/keama/tests/baddomain.notyet b/keama/tests/baddomain.notyet
new file mode 100644
index 00000000..a38f21c9
--- /dev/null
+++ b/keama/tests/baddomain.notyet
@@ -0,0 +1,6 @@
+# bad domain (label too long) option data
+
+option space foobar;
+option foobar.fmt-d code 1 = domain-name;
+option foobar.fmt-d
+ this-label-is-very-very-very-very-very-very-long-so-it-is-too-long.us;
diff --git a/keama/tests/badduid.err b/keama/tests/badduid.err
new file mode 100644
index 00000000..c3acab31
--- /dev/null
+++ b/keama/tests/badduid.err
@@ -0,0 +1,4 @@
+# bad (no type) server duid config
+
+# server duid declaration
+server-duid "enterprise-specific-identifier-1234";
diff --git a/keama/tests/badduid.msg b/keama/tests/badduid.msg
new file mode 100644
index 00000000..ebcaade4
--- /dev/null
+++ b/keama/tests/badduid.msg
@@ -0,0 +1 @@
+badduid.err line 4: DUID type of LLT, EN, or LL expected
diff --git a/keama/tests/badinclude.err b/keama/tests/badinclude.err
new file mode 100644
index 00000000..7ab310ff
--- /dev/null
+++ b/keama/tests/badinclude.err
@@ -0,0 +1,3 @@
+# bad include config
+
+include 192.168.0.1;
diff --git a/keama/tests/badinclude.msg b/keama/tests/badinclude.msg
new file mode 100644
index 00000000..e7ebf997
--- /dev/null
+++ b/keama/tests/badinclude.msg
@@ -0,0 +1 @@
+badinclude.err line 3: filename string expected.
diff --git a/keama/tests/badoption66.err6 b/keama/tests/badoption66.err6
new file mode 100644
index 00000000..a972d2f2
--- /dev/null
+++ b/keama/tests/badoption66.err6
@@ -0,0 +1,3 @@
+# bad '6' (Ipv6 address) option data
+
+option dhcp6.unicast "2001::1";
diff --git a/keama/tests/badoption66.msg b/keama/tests/badoption66.msg
new file mode 100644
index 00000000..227ce94d
--- /dev/null
+++ b/keama/tests/badoption66.msg
@@ -0,0 +1 @@
+badoption66.err6 line 3: Invalid IPv6 address.
diff --git a/keama/tests/badoptionD6.notyet b/keama/tests/badoptionD6.notyet
new file mode 100644
index 00000000..daf9541c
--- /dev/null
+++ b/keama/tests/badoptionD6.notyet
@@ -0,0 +1,4 @@
+# bad 'D' (domain-list) option data
+
+option dhcp6.domain-search example.com;
+
diff --git a/keama/tests/badoptionDc4.notyet b/keama/tests/badoptionDc4.notyet
new file mode 100644
index 00000000..f56fb46a
--- /dev/null
+++ b/keama/tests/badoptionDc4.notyet
@@ -0,0 +1,3 @@
+# bad 'Dc' (domain-list compressed) option data
+
+option domain-search example.com;
diff --git a/keama/tests/badoptionI4.err4 b/keama/tests/badoptionI4.err4
new file mode 100644
index 00000000..b0f6331d
--- /dev/null
+++ b/keama/tests/badoptionI4.err4
@@ -0,0 +1,3 @@
+# bad 'I' (IPv4 address) option data
+
+option swap-server "10.5.5.1";
diff --git a/keama/tests/badoptionI4.msg b/keama/tests/badoptionI4.msg
new file mode 100644
index 00000000..6609975d
--- /dev/null
+++ b/keama/tests/badoptionI4.msg
@@ -0,0 +1 @@
+badoptionI4.err4 line 3: 10.5.5.1 (262): expecting IP address or hostname
diff --git a/keama/tests/badoptiond4.err4 b/keama/tests/badoptiond4.err4
new file mode 100644
index 00000000..005c26cd
--- /dev/null
+++ b/keama/tests/badoptiond4.err4
@@ -0,0 +1,5 @@
+# bad d (domain-name) option data
+
+option space foobar;
+option foobar.fmt-d code 13 = domain-name;
+option foobar.fmt-d "www.example.com";
diff --git a/keama/tests/badoptiond4.msg b/keama/tests/badoptiond4.msg
new file mode 100644
index 00000000..e1f833e7
--- /dev/null
+++ b/keama/tests/badoptiond4.msg
@@ -0,0 +1 @@
+badoptiond4.err4 line 5: not a valid domain name.
diff --git a/keama/tests/badstatusdir.err b/keama/tests/badstatusdir.err
new file mode 100644
index 00000000..aab8fd56
--- /dev/null
+++ b/keama/tests/badstatusdir.err
@@ -0,0 +1,7 @@
+# Bad status directive
+
+option space foobar;
+option foobar.opt1 code 1 = text;
+
+# unknown/unknown does not make sense
+% option foobar.opt1 unknown unknown;
diff --git a/keama/tests/badstatusdir.msg b/keama/tests/badstatusdir.msg
new file mode 100644
index 00000000..d020cee4
--- /dev/null
+++ b/keama/tests/badstatusdir.msg
@@ -0,0 +1 @@
+badstatusdir.err line 7: illicit combination: option foobar.opt1 code 1 is known by nobody
diff --git a/keama/tests/badsubclass.err b/keama/tests/badsubclass.err
new file mode 100644
index 00000000..38c817b7
--- /dev/null
+++ b/keama/tests/badsubclass.err
@@ -0,0 +1,11 @@
+# bad (selector type) subclass declaration config
+
+# superclass declaration
+class "foobar" {
+ match substring(option vendor-class-identifier, 0, 3);
+}
+
+# subclass declaration
+subclass "foobar" true {
+ default-lease-time 1800;
+}
diff --git a/keama/tests/badsubclass.msg b/keama/tests/badsubclass.msg
new file mode 100644
index 00000000..a0a85028
--- /dev/null
+++ b/keama/tests/badsubclass.msg
@@ -0,0 +1 @@
+badsubclass.err line 9: Expecting string or hex list.
diff --git a/keama/tests/bintadx6.in6 b/keama/tests/bintadx6.in6
new file mode 100644
index 00000000..dc0cede7
--- /dev/null
+++ b/keama/tests/bintadx6.in6
@@ -0,0 +1,9 @@
+# binary-to-ascii data expression
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# reduce literals
+class "literal" {
+ match if option dhcp6.client-data = binary-to-ascii(16, 8, "-", "\007foo");
+}
diff --git a/keama/tests/bintadx6.out b/keama/tests/bintadx6.out
new file mode 100644
index 00000000..999e3602
--- /dev/null
+++ b/keama/tests/bintadx6.out
@@ -0,0 +1,15 @@
+{
+ # binary-to-ascii data expression
+ # empty configs are not accepted by Kea
+ "Dhcp6": {
+ "valid-lifetime": 1800,
+ "client-classes": [
+ # reduce literals
+ {
+ "name": "literal",
+ /// from: match if (option dhcp6.client-data) = (binary-to-ascii(16, 8, '-', 'foo'))
+ "test": "option[45].hex == '7-66-6f-6f'"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/bootfilename4.in4 b/keama/tests/bootfilename4.in4
new file mode 100644
index 00000000..5b6daeba
--- /dev/null
+++ b/keama/tests/bootfilename4.in4
@@ -0,0 +1,4 @@
+# Gitlab #22 dhcp4 option 67 wrong name
+
+option bootfile-name "boot.pxe";
+
diff --git a/keama/tests/bootfilename4.out b/keama/tests/bootfilename4.out
new file mode 100644
index 00000000..a08cb625
--- /dev/null
+++ b/keama/tests/bootfilename4.out
@@ -0,0 +1,13 @@
+{
+ # Gitlab #22 dhcp4 option 67 wrong name
+ "Dhcp4": {
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "boot-file-name",
+ "code": 67,
+ "data": "boot.pxe"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/charcasedx4.in4 b/keama/tests/charcasedx4.in4
new file mode 100644
index 00000000..dd9ba03e
--- /dev/null
+++ b/keama/tests/charcasedx4.in4
@@ -0,0 +1,9 @@
+# lcase/ucase data expression
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# reduce literals
+class "literal" {
+ match if option host-name = concat(ucase("www."), "example.", lcase("COM"));
+}
diff --git a/keama/tests/charcasedx4.out b/keama/tests/charcasedx4.out
new file mode 100644
index 00000000..fcdc20d1
--- /dev/null
+++ b/keama/tests/charcasedx4.out
@@ -0,0 +1,15 @@
+{
+ # lcase/ucase data expression
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "client-classes": [
+ # reduce literals
+ {
+ "name": "literal",
+ /// from: match if (option dhcp.host-name) = (concat(uppercase('www.'), concat('example.', lowercase('COM'))))
+ "test": "option[12].hex == 'WWW.example.com'"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/checkall.sh b/keama/tests/checkall.sh
new file mode 100644
index 00000000..edeffdcb
--- /dev/null
+++ b/keama/tests/checkall.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+#set -x
+
+cd "$(dirname "$0")"
+
+log=/tmp/log
+rm $log 2> /dev/null
+
+for t in *.out
+do
+ /bin/sh checkone.sh $t
+ if [ $? -ne 0 ]; then
+ echo `basename $t` >> $log
+ fi
+done
diff --git a/keama/tests/checkone.sh b/keama/tests/checkone.sh
new file mode 100644
index 00000000..cbd5d9fd
--- /dev/null
+++ b/keama/tests/checkone.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+#set -x
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 test-name" >&2
+ exit 1
+fi
+
+if [ x$KEA4 = x ]; then
+ echo "KEA4 is not set" >&2
+fi
+if [ x$KEA6 = x ]; then
+ echo "KEA6 is not set" >&2
+fi
+
+file=$1
+
+cd "$(dirname "$0")"
+
+isout=$(expr $file : ".*\.out")
+if [ $isout -eq 0 ]; then
+ full=$file.out
+else
+ full=$file
+fi
+if [ ! -f $full ]; then
+ echo "can't find $file" >&2
+ exit 1
+fi
+
+is4=$(expr $file : ".*4")
+is6=$(expr $file : ".*6")
+if [ \( $is4 -eq 0 \) -a \( $is6 -eq 0 \) ]; then
+ echo "can't get version from $file" >&2
+ exit 1
+fi
+
+base=$(basename $full .out)
+log=/tmp/$base.log$$
+if [ $is4 -ne 0 ]; then
+ $KEA4 -t $full >& $log
+ if [ $? -ne 0 ]; then
+ echo "$full raised an error" >&2
+ exit 1
+ fi
+fi
+if [ $is6 -ne 0 ]; then
+ $KEA6 -t $full >& $log
+ if [ $? -ne 0 ]; then
+ echo "$full raised an error" >&2
+ exit 1
+ fi
+fi
diff --git a/keama/tests/class4.in4 b/keama/tests/class4.in4
new file mode 100644
index 00000000..f573a02c
--- /dev/null
+++ b/keama/tests/class4.in4
@@ -0,0 +1,11 @@
+# class declaration config
+
+# options
+option mysystem code 250 = text;
+option myversion code 251 = unsigned integer 16;
+
+# class declaration
+class "foobar" {
+ match if option mysystem = "version1";
+ option myversion 1;
+}
diff --git a/keama/tests/class4.out b/keama/tests/class4.out
new file mode 100644
index 00000000..41ddf27f
--- /dev/null
+++ b/keama/tests/class4.out
@@ -0,0 +1,36 @@
+{
+ # class declaration config
+ # options
+ "Dhcp4": {
+ "option-def": [
+ {
+ "space": "dhcp4",
+ "name": "mysystem",
+ "code": 250,
+ "type": "string"
+ },
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "type": "uint16"
+ }
+ ],
+ "client-classes": [
+ # class declaration
+ {
+ "name": "foobar",
+ /// from: match if (option dhcp.mysystem) = 'version1'
+ "test": "option[250].hex == 'version1'",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "1"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/class4empty.in4 b/keama/tests/class4empty.in4
new file mode 100644
index 00000000..d0b9faa5
--- /dev/null
+++ b/keama/tests/class4empty.in4
@@ -0,0 +1,5 @@
+# void class declaration config
+
+# class declaration
+class "foobar" {
+}
diff --git a/keama/tests/class4empty.out b/keama/tests/class4empty.out
new file mode 100644
index 00000000..d2359856
--- /dev/null
+++ b/keama/tests/class4empty.out
@@ -0,0 +1,11 @@
+{
+ # void class declaration config
+ # class declaration
+ "Dhcp4": {
+ "client-classes": [
+ {
+ "name": "foobar"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/class6.in6 b/keama/tests/class6.in6
new file mode 100644
index 00000000..039a80c9
--- /dev/null
+++ b/keama/tests/class6.in6
@@ -0,0 +1,11 @@
+# class declaration config
+
+# options
+option dhcp6.mysystem code 1250 = text;
+option dhcp6.myversion code 1251 = unsigned integer 16;
+
+# class declaration
+class "foobar" {
+ match if option dhcp6.mysystem = "version1";
+ option dhcp6.myversion 1;
+}
diff --git a/keama/tests/class6.out b/keama/tests/class6.out
new file mode 100644
index 00000000..8c9af83d
--- /dev/null
+++ b/keama/tests/class6.out
@@ -0,0 +1,36 @@
+{
+ # class declaration config
+ # options
+ "Dhcp6": {
+ "option-def": [
+ {
+ "space": "dhcp6",
+ "name": "mysystem",
+ "code": 1250,
+ "type": "string"
+ },
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "type": "uint16"
+ }
+ ],
+ "client-classes": [
+ # class declaration
+ {
+ "name": "foobar",
+ /// from: match if (option dhcp6.mysystem) = 'version1'
+ "test": "option[1250].hex == 'version1'",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "data": "1"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/class6empty.in6 b/keama/tests/class6empty.in6
new file mode 100644
index 00000000..d0b9faa5
--- /dev/null
+++ b/keama/tests/class6empty.in6
@@ -0,0 +1,5 @@
+# void class declaration config
+
+# class declaration
+class "foobar" {
+}
diff --git a/keama/tests/class6empty.out b/keama/tests/class6empty.out
new file mode 100644
index 00000000..a9869b5f
--- /dev/null
+++ b/keama/tests/class6empty.out
@@ -0,0 +1,11 @@
+{
+ # void class declaration config
+ # class declaration
+ "Dhcp6": {
+ "client-classes": [
+ {
+ "name": "foobar"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/classbadmatch.err b/keama/tests/classbadmatch.err
new file mode 100644
index 00000000..b9eac948
--- /dev/null
+++ b/keama/tests/classbadmatch.err
@@ -0,0 +1,7 @@
+# bad (match with a boolean expression) class declaration config
+
+# class declaration
+class "foobar" {
+ match option server.duplicates = 0;
+ default-lease-time 1800;
+}
diff --git a/keama/tests/classbadmatch.msg b/keama/tests/classbadmatch.msg
new file mode 100644
index 00000000..5fcda249
--- /dev/null
+++ b/keama/tests/classbadmatch.msg
@@ -0,0 +1 @@
+classbadmatch.err line 5: Expecting a data expression.
diff --git a/keama/tests/classbadmatchif.err b/keama/tests/classbadmatchif.err
new file mode 100644
index 00000000..7eb88d65
--- /dev/null
+++ b/keama/tests/classbadmatchif.err
@@ -0,0 +1,7 @@
+# bad (match if with a data expression) class declaration config
+
+# class declaration
+class "foobar" {
+ match if option server.duplicates;
+ default-lease-time 1800;
+}
diff --git a/keama/tests/classbadmatchif.msg b/keama/tests/classbadmatchif.msg
new file mode 100644
index 00000000..073d48c2
--- /dev/null
+++ b/keama/tests/classbadmatchif.msg
@@ -0,0 +1 @@
+classbadmatchif.err line 5: Expecting a boolean expression.
diff --git a/keama/tests/classinclass.err b/keama/tests/classinclass.err
new file mode 100644
index 00000000..36aa0b4b
--- /dev/null
+++ b/keama/tests/classinclass.err
@@ -0,0 +1,10 @@
+# class declaration inside class declaration config
+
+# class declaration
+class "foobar" {
+ # can't put a class declaration here
+ subclass "foobar" "illegal" {
+ default-lease-time 1800;
+ }
+}
+
diff --git a/keama/tests/classinclass.msg b/keama/tests/classinclass.msg
new file mode 100644
index 00000000..5e1c26c9
--- /dev/null
+++ b/keama/tests/classinclass.msg
@@ -0,0 +1 @@
+classinclass.err line 6: class declarations not allowed here.
diff --git a/keama/tests/concatdx4.in4 b/keama/tests/concatdx4.in4
new file mode 100644
index 00000000..b94a3a2f
--- /dev/null
+++ b/keama/tests/concatdx4.in4
@@ -0,0 +1,19 @@
+# concat data expression
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# use substring in a reductible match
+class "reductible" {
+ match concat("domain=", suffix(option host-name, 3));
+}
+
+subclass "reductible" "domain=com" { }
+
+# reduce literals too
+class "literal" {
+ match if option host-name = concat("www.", "example.", "com");
+}
+
+# raw
+option host-name = concat("www.", option domain-name);
diff --git a/keama/tests/concatdx4.out b/keama/tests/concatdx4.out
new file mode 100644
index 00000000..4602a497
--- /dev/null
+++ b/keama/tests/concatdx4.out
@@ -0,0 +1,48 @@
+{
+ # concat data expression
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "client-classes": [
+ # use substring in a reductible match
+ /// match: concat('domain=', suffix(option dhcp.host-name, 3))
+ {
+ "name": "reductible"
+ },
+ /// subclass selector 'domain=com'
+ {
+ "name": "sub#reductible#0",
+ /// from: match concat('domain=', suffix(option dhcp.host-name, 3))
+ /// data: 'domain=com'
+ "test": "concat('domain=', substring(option[12].hex,-3,all)) == 'domain=com'"
+ },
+ # reduce literals too
+ {
+ "name": "literal",
+ /// from: match if (option dhcp.host-name) = (concat('www.', concat('example.', 'com')))
+ "test": "option[12].hex == 'www.example.com'"
+ }
+ ],
+ "option-data": [
+// # raw
+// {
+// "space": "dhcp4",
+// "name": "host-name",
+// "code": 12,
+// "csv-format": false,
+// "expression": {
+// "concat": {
+// "left": "www.",
+// "right": {
+// "option": {
+// "universe": "dhcp",
+// "name": "domain-name",
+// "code": 15
+// }
+// }
+// }
+// }
+// }
+ ]
+ }
+}
diff --git a/keama/tests/concatnulldx4.in4 b/keama/tests/concatnulldx4.in4
new file mode 100644
index 00000000..9b5e2a42
--- /dev/null
+++ b/keama/tests/concatnulldx4.in4
@@ -0,0 +1,18 @@
+# concat with null argument data expression
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# null left argument
+class "null-left" {
+ match concat("", suffix(option host-name, 3));
+}
+
+subclass "null-left" "com" { }
+
+# null right argument
+class "null-right" {
+ match concat(suffix(option host-name, 3), substring("foobar", 0, 0));
+}
+
+subclass "null-right" "org" { }
diff --git a/keama/tests/concatnulldx4.out b/keama/tests/concatnulldx4.out
new file mode 100644
index 00000000..66cdbdf8
--- /dev/null
+++ b/keama/tests/concatnulldx4.out
@@ -0,0 +1,33 @@
+{
+ # concat with null argument data expression
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "client-classes": [
+ # null left argument
+ /// match: concat('', suffix(option dhcp.host-name, 3))
+ {
+ "name": "null-left"
+ },
+ /// subclass selector 'com'
+ {
+ "name": "sub#null-left#0",
+ /// from: match concat('', suffix(option dhcp.host-name, 3))
+ /// data: 'com'
+ "test": "substring(option[12].hex,-3,all) == 'com'"
+ },
+ # null right argument
+ /// match: concat(suffix(option dhcp.host-name, 3), substring('foobar', 0, 0))
+ {
+ "name": "null-right"
+ },
+ /// subclass selector 'org'
+ {
+ "name": "sub#null-right#1",
+ /// from: match concat(suffix(option dhcp.host-name, 3), substring('foobar', 0, 0))
+ /// data: 'org'
+ "test": "substring(option[12].hex,-3,all) == 'org'"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/configdata4.in4 b/keama/tests/configdata4.in4
new file mode 100644
index 00000000..f8c5c7bc
--- /dev/null
+++ b/keama/tests/configdata4.in4
@@ -0,0 +1,26 @@
+# config (aka server option space data config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# configs
+dynamic-bootp-lease-length 1200;
+
+boot-unknown-clients false;
+
+min-secs 20;
+
+lease-file-name "/tmp/leases";
+
+local-port 10067;
+
+local-address 10.5.5.1;
+
+# not quoted?
+omapi-key my.key.biz;
+
+limit-addrs-per-ia 12345;
+
+ddns-local-address6 2001::1;
+
+pid-file-name = "/tmp/pid";
diff --git a/keama/tests/configdata4.out b/keama/tests/configdata4.out
new file mode 100644
index 00000000..a1af336e
--- /dev/null
+++ b/keama/tests/configdata4.out
@@ -0,0 +1,74 @@
+{
+ # config (aka server option space data config
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800
+// "config": [
+// /// bootp protocol is not supported
+// {
+// "name": "dynamic-bootp-lease-length",
+// "code": 5,
+// "value": 1200
+// },
+// /// bootp protocol is not supported
+// {
+// "name": "boot-unknown-clients",
+// "code": 6,
+// "value": false
+// },
+// /// min-secs is not (yet?) supported
+// /// Reference Kea #223
+// {
+// "name": "min-secs",
+// "code": 14,
+// "value": 5120
+// },
+// /// lease-file-name is an internal ISC DHCP feature
+// {
+// "name": "lease-file-name",
+// "code": 26,
+// "value": "/tmp/leases"
+// },
+// /// local-port is not supported
+// /// command line -p parameter should be used instead
+// {
+// "name": "local-port",
+// "code": 32,
+// "value": 10067
+// },
+// /// local-address is not supported
+// /// Kea equivalent feature is to specify an interface address
+// {
+// "name": "local-address",
+// "code": 35,
+// "value": "10.5.5.1"
+// },
+// /// omapi-key is an internal ISC DHCP feature
+// {
+// "name": "omapi-key",
+// "code": 36,
+// "value": "my.key.biz"
+// },
+// /// limit-addrs-per-ia is not (yet?) supported
+// /// Reference Kea #227
+// {
+// "name": "limit-addrs-per-ia",
+// "code": 56,
+// "value": 12345
+// },
+// /// ddns-local-address6 is not supported
+// /// Kea D2 equivalent config is ip-address
+// {
+// "name": "ddns-local-address6",
+// "code": 81,
+// "value": "2001::1"
+// },
+// /// pid-file-nam is an internal ISC DHCP feature
+// {
+// "name": "pid-file-name",
+// "code": 27,
+// "value": "/tmp/pid"
+// }
+// ]
+ }
+}
diff --git a/keama/tests/dbtimeformat4.in4 b/keama/tests/dbtimeformat4.in4
new file mode 100644
index 00000000..b3225a82
--- /dev/null
+++ b/keama/tests/dbtimeformat4.in4
@@ -0,0 +1,6 @@
+# db-time-format config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+db-time-format default;
diff --git a/keama/tests/dbtimeformat4.out b/keama/tests/dbtimeformat4.out
new file mode 100644
index 00000000..0988bfbd
--- /dev/null
+++ b/keama/tests/dbtimeformat4.out
@@ -0,0 +1,10 @@
+{
+ # db-time-format config
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800
+// "statement": {
+// "db-time-format": "default"
+// }
+ }
+}
diff --git a/keama/tests/dbtimeformat6.in6 b/keama/tests/dbtimeformat6.in6
new file mode 100644
index 00000000..619d1c91
--- /dev/null
+++ b/keama/tests/dbtimeformat6.in6
@@ -0,0 +1,6 @@
+# db-time-format config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+db-time-format local;
diff --git a/keama/tests/dbtimeformat6.out b/keama/tests/dbtimeformat6.out
new file mode 100644
index 00000000..42460de6
--- /dev/null
+++ b/keama/tests/dbtimeformat6.out
@@ -0,0 +1,10 @@
+{
+ # db-time-format config
+ # empty configs are not accepted by Kea
+ "Dhcp6": {
+ "valid-lifetime": 1800
+// "statement": {
+// "db-time-format": "local"
+// }
+ }
+}
diff --git a/keama/tests/ddnsupdstyle6.in6 b/keama/tests/ddnsupdstyle6.in6
new file mode 100644
index 00000000..ef23fe38
--- /dev/null
+++ b/keama/tests/ddnsupdstyle6.in6
@@ -0,0 +1,12 @@
+# ddns-update-style
+
+ddns-update-style standard;
+
+# embedded in pool
+subnet6 2001::/64 {
+ pool6 {
+ ddns-update-style interim;
+ range6 2001::1000 2001::1fff;
+ }
+}
+
diff --git a/keama/tests/ddnsupdstyle6.out b/keama/tests/ddnsupdstyle6.out
new file mode 100644
index 00000000..88323bb0
--- /dev/null
+++ b/keama/tests/ddnsupdstyle6.out
@@ -0,0 +1,29 @@
+{
+ # ddns-update-style
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp6": {
+ "dhcp-ddns": {
+ /// Unspecified ddns-domainname (default domain-name option value)
+ /// Kea requires a qualifying-suffix
+ /// Initialized to "": please put a value
+ "qualifying-suffix": "",
+ "enable-updates": true
+ },
+ "subnet6": [
+ # embedded in pool
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "pools": [
+ {
+// /// Unsupported ddns-update-style interim
+// /// Only global ddns-update-style is supported
+// "ddns-update-style": "interim",
+ "pool": "2001::1000 - 2001::1fff"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/defaultexpr6.in6 b/keama/tests/defaultexpr6.in6
new file mode 100644
index 00000000..e81bd8d3
--- /dev/null
+++ b/keama/tests/defaultexpr6.in6
@@ -0,0 +1,10 @@
+# default expressions
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# default expression is a variable reference
+foo;
+
+# or a function call when there are parentheses
+bar ("abcd", "xyxt");
diff --git a/keama/tests/defaultexpr6.out b/keama/tests/defaultexpr6.out
new file mode 100644
index 00000000..8646e8af
--- /dev/null
+++ b/keama/tests/defaultexpr6.out
@@ -0,0 +1,25 @@
+{
+ # default expressions
+ # empty configs are not accepted by Kea
+ "Dhcp6": {
+ "valid-lifetime": 1800
+// # default expression is a variable reference
+// "statement": {
+// "eval": {
+// "variable-reference": "foo"
+// }
+// }
+// # or a function call when there are parentheses
+// "statement": {
+// "eval": {
+// "funcall": {
+// "name": "bar",
+// "arguments": [
+// "abcd",
+// "xyxt"
+// ]
+// }
+// }
+// }
+ }
+}
diff --git a/keama/tests/denyunknown6.in6 b/keama/tests/denyunknown6.in6
new file mode 100644
index 00000000..ccd5541a
--- /dev/null
+++ b/keama/tests/denyunknown6.in6
@@ -0,0 +1,15 @@
+# DHCPv6 deny unknown client config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# subnet declaration
+subnet6 2001::/64 {
+ # pool declaration
+ pool6 {
+ # avoid empty pool
+ range6 2001::100 2001::200;
+ # call get_permit
+ deny unknown clients;
+ }
+}
diff --git a/keama/tests/denyunknown6.out b/keama/tests/denyunknown6.out
new file mode 100644
index 00000000..68db27b7
--- /dev/null
+++ b/keama/tests/denyunknown6.out
@@ -0,0 +1,32 @@
+{
+ # DHCPv6 deny unknown client config
+ # empty configs are not accepted by Kea
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp6": {
+ "valid-lifetime": 1800,
+ "subnet6": [
+ # subnet declaration
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "pools": [
+ # pool declaration
+ {
+ # avoid empty pool
+ "pool": "2001::100 - 2001::200",
+ /// From:
+ /// deny unknown clients
+ "client-class": "gen#_AND_#KNOWN#"
+ }
+ ]
+ }
+ ],
+ "client-classes": [
+ {
+ "name": "gen#_AND_#KNOWN#",
+ "test": "member('KNOWN')"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/docsis4.dir b/keama/tests/docsis4.dir
new file mode 100644
index 00000000..82b206ef
--- /dev/null
+++ b/keama/tests/docsis4.dir
@@ -0,0 +1,11 @@
+# DOCSIS DHCPv4 vendor space
+
+option space docsis;
+
+option vendor.docsis code 4491 = encapsulate docsis;
+
+option docsis.oro code 1 = array of unsigned integer 8;
+option docsis.tftp-servers code 2 = array of ip-address;
+
+% option docsis.oro local;
+% option docsis.tftp-servers local;
diff --git a/keama/tests/docsis6.dir b/keama/tests/docsis6.dir
new file mode 100644
index 00000000..ff7cd62a
--- /dev/null
+++ b/keama/tests/docsis6.dir
@@ -0,0 +1,27 @@
+# DOCSIS DHCPv6 vendor space
+
+option space docsis;
+
+option vsio.docsis code 4491 = encapsulate docsis;
+
+option docsis.oro code 1 = array of unsigned integer 16;
+option docsis.device-type code 2 = text;
+option docsis.vendor-type code 10 = text;
+option docsis.tftp-servers code 32 = array of ip6-address;
+option docsis.config-file code 33 = text;
+option docsis.syslog-servers code 34 = array of ip6-address;
+% option docsis.device-id code 36 = "X";
+option docsis.time-servers code 37 = array of ip6-address;
+option docsis.time-offset code 38 = unsigned integer 32;
+% option docsis.cmts-cm-mac code 1026 = "X";
+
+% option docsis.oro local;
+% option docsis.device-type local;
+% option docsis.vendor-type local;
+% option docsis.tftp-servers local;
+% option docsis.config-file local;
+% option docsis.syslog-servers local;
+% option docsis.device-id local;
+% option docsis.time-servers local;
+% option docsis.time-offset local;
+% option docsis.cmts-cm-mac local;
diff --git a/keama/tests/duid2.err b/keama/tests/duid2.err
new file mode 100644
index 00000000..fcbef592
--- /dev/null
+++ b/keama/tests/duid2.err
@@ -0,0 +1,7 @@
+# two server duid config
+
+# EN server duid declaration
+server-duid en 2495 "enterprise-specific-identifier-1234";
+
+# LL server duid declaration
+server-duid ll ethernet 00:16:6F:49:7D:9B;
diff --git a/keama/tests/duid2.msg b/keama/tests/duid2.msg
new file mode 100644
index 00000000..06a8a00f
--- /dev/null
+++ b/keama/tests/duid2.msg
@@ -0,0 +1 @@
+duid2.err line 7: there is already a server-id
diff --git a/keama/tests/duiden6.in6 b/keama/tests/duiden6.in6
new file mode 100644
index 00000000..ae8385ea
--- /dev/null
+++ b/keama/tests/duiden6.in6
@@ -0,0 +1,4 @@
+# EN server duid config
+
+# EN server duid declaration
+server-duid en 2495 "enterprise-specific-identifier-1234";
diff --git a/keama/tests/duiden6.out b/keama/tests/duiden6.out
new file mode 100644
index 00000000..1e7e3021
--- /dev/null
+++ b/keama/tests/duiden6.out
@@ -0,0 +1,11 @@
+{
+ # EN server duid config
+ # EN server duid declaration
+ "Dhcp6": {
+ "server-id": {
+ "type": "EN",
+ "enterprise-id": 2495,
+ "identifier": "656e74657270726973652d73706563696669632d6964656e7469666965722d31323334"
+ }
+ }
+}
diff --git a/keama/tests/duidennoid.err b/keama/tests/duidennoid.err
new file mode 100644
index 00000000..ae6d12ae
--- /dev/null
+++ b/keama/tests/duidennoid.err
@@ -0,0 +1,5 @@
+# bad (no identifier) EN server duid config
+
+# EN server duid declaration
+server-duid en 2495;
+
diff --git a/keama/tests/duidennoid.msg b/keama/tests/duidennoid.msg
new file mode 100644
index 00000000..054fa65e
--- /dev/null
+++ b/keama/tests/duidennoid.msg
@@ -0,0 +1 @@
+duidennoid.err line 4: identifier expected
diff --git a/keama/tests/duidennonum.err b/keama/tests/duidennonum.err
new file mode 100644
index 00000000..abdfdd7c
--- /dev/null
+++ b/keama/tests/duidennonum.err
@@ -0,0 +1,4 @@
+# bad (no number) EN server duid config
+
+# EN server duid declaration
+server-duid en "enterprise-specific-identifier-1234";
diff --git a/keama/tests/duidennonum.msg b/keama/tests/duidennonum.msg
new file mode 100644
index 00000000..941a0d03
--- /dev/null
+++ b/keama/tests/duidennonum.msg
@@ -0,0 +1 @@
+duidennonum.err line 4: enterprise number expected
diff --git a/keama/tests/duidll6.in6 b/keama/tests/duidll6.in6
new file mode 100644
index 00000000..fac38c7a
--- /dev/null
+++ b/keama/tests/duidll6.in6
@@ -0,0 +1,5 @@
+# LL server duid config
+
+# LL server duid declaration
+server-duid ll;
+
diff --git a/keama/tests/duidll6.out b/keama/tests/duidll6.out
new file mode 100644
index 00000000..089539d7
--- /dev/null
+++ b/keama/tests/duidll6.out
@@ -0,0 +1,9 @@
+{
+ # LL server duid config
+ # LL server duid declaration
+ "Dhcp6": {
+ "server-id": {
+ "type": "LL"
+ }
+ }
+}
diff --git a/keama/tests/duidllbadtype.err b/keama/tests/duidllbadtype.err
new file mode 100644
index 00000000..2526f6e1
--- /dev/null
+++ b/keama/tests/duidllbadtype.err
@@ -0,0 +1,4 @@
+# bad (unknown hardware type) LL server duid config
+
+# LL server duid declaration
+server-duid ll foobar 00:16:6F:49:7D:9B;
diff --git a/keama/tests/duidllbadtype.msg b/keama/tests/duidllbadtype.msg
new file mode 100644
index 00000000..341ebd0b
--- /dev/null
+++ b/keama/tests/duidllbadtype.msg
@@ -0,0 +1 @@
+duidllbadtype.err line 4: hardware type expected
diff --git a/keama/tests/duidllhw6.in6 b/keama/tests/duidllhw6.in6
new file mode 100644
index 00000000..48312801
--- /dev/null
+++ b/keama/tests/duidllhw6.in6
@@ -0,0 +1,6 @@
+# LL server duid config
+
+# LL server duid declaration
+server-duid ll ethernet 00:16:6F:49:7D:9B;
+
+
diff --git a/keama/tests/duidllhw6.out b/keama/tests/duidllhw6.out
new file mode 100644
index 00000000..ad7fbdac
--- /dev/null
+++ b/keama/tests/duidllhw6.out
@@ -0,0 +1,11 @@
+{
+ # LL server duid config
+ # LL server duid declaration
+ "Dhcp6": {
+ "server-id": {
+ "type": "LL",
+ "htype": 1,
+ "identifier": "00166f497d9b"
+ }
+ }
+}
diff --git a/keama/tests/duidllnohw.err b/keama/tests/duidllnohw.err
new file mode 100644
index 00000000..a93b90d3
--- /dev/null
+++ b/keama/tests/duidllnohw.err
@@ -0,0 +1,4 @@
+# bad (no hardware address) LL server duid config
+
+# LL server duid declaration
+server-duid ll fddi;
diff --git a/keama/tests/duidllnohw.msg b/keama/tests/duidllnohw.msg
new file mode 100644
index 00000000..b2d955ec
--- /dev/null
+++ b/keama/tests/duidllnohw.msg
@@ -0,0 +1 @@
+duidllnohw.err line 4: expecting hexadecimal number.
diff --git a/keama/tests/duidllt6.in6 b/keama/tests/duidllt6.in6
new file mode 100644
index 00000000..25420b3c
--- /dev/null
+++ b/keama/tests/duidllt6.in6
@@ -0,0 +1,5 @@
+# LLT server duid config
+
+# LLT server duid declaration
+server-duid llt;
+
diff --git a/keama/tests/duidllt6.out b/keama/tests/duidllt6.out
new file mode 100644
index 00000000..2a1ea79e
--- /dev/null
+++ b/keama/tests/duidllt6.out
@@ -0,0 +1,9 @@
+{
+ # LLT server duid config
+ # LLT server duid declaration
+ "Dhcp6": {
+ "server-id": {
+ "type": "LLT"
+ }
+ }
+}
diff --git a/keama/tests/duidlltbadtype.err b/keama/tests/duidlltbadtype.err
new file mode 100644
index 00000000..66ab6642
--- /dev/null
+++ b/keama/tests/duidlltbadtype.err
@@ -0,0 +1,4 @@
+# bad (unknown hardware type) LLT server duid config
+
+# LLT server duid declaration
+server-duid llt foobar 213982198 00:16:6F:49:7D:9B;
diff --git a/keama/tests/duidlltbadtype.msg b/keama/tests/duidlltbadtype.msg
new file mode 100644
index 00000000..32305a75
--- /dev/null
+++ b/keama/tests/duidlltbadtype.msg
@@ -0,0 +1 @@
+duidlltbadtype.err line 4: hardware type expected
diff --git a/keama/tests/duidlltnohw.err b/keama/tests/duidlltnohw.err
new file mode 100644
index 00000000..3208ed60
--- /dev/null
+++ b/keama/tests/duidlltnohw.err
@@ -0,0 +1,4 @@
+# bad (no hardware address) LLT server duid config
+
+# LLT server duid declaration
+server-duid llt token-ring 213982198;
diff --git a/keama/tests/duidlltnohw.msg b/keama/tests/duidlltnohw.msg
new file mode 100644
index 00000000..7bfaa245
--- /dev/null
+++ b/keama/tests/duidlltnohw.msg
@@ -0,0 +1 @@
+duidlltnohw.err line 4: expecting hexadecimal number.
diff --git a/keama/tests/duidlltnotime.err b/keama/tests/duidlltnotime.err
new file mode 100644
index 00000000..f53321bb
--- /dev/null
+++ b/keama/tests/duidlltnotime.err
@@ -0,0 +1,4 @@
+# bad (no timestamp) LLT server duid config
+
+# LLT server duid declaration
+server-duid llt token-ring A8:16:6F:49:7D:9B;
diff --git a/keama/tests/duidlltnotime.msg b/keama/tests/duidlltnotime.msg
new file mode 100644
index 00000000..2b7fb8a3
--- /dev/null
+++ b/keama/tests/duidlltnotime.msg
@@ -0,0 +1 @@
+duidlltnotime.err line 4: timestamp expected
diff --git a/keama/tests/duidlltthw4.err4 b/keama/tests/duidlltthw4.err4
new file mode 100644
index 00000000..ae039d18
--- /dev/null
+++ b/keama/tests/duidlltthw4.err4
@@ -0,0 +1,4 @@
+# LLT server duid config
+
+# LLT server duid declaration
+server-duid llt token-ring 213982198 00:16:6F:49:7D:9B;
diff --git a/keama/tests/duidlltthw4.msg b/keama/tests/duidlltthw4.msg
new file mode 100644
index 00000000..df163bb1
--- /dev/null
+++ b/keama/tests/duidlltthw4.msg
@@ -0,0 +1 @@
+duidlltthw4.err4 line 4: expecting a parameter or declaration
diff --git a/keama/tests/duidlltthw6.in6 b/keama/tests/duidlltthw6.in6
new file mode 100644
index 00000000..ae039d18
--- /dev/null
+++ b/keama/tests/duidlltthw6.in6
@@ -0,0 +1,4 @@
+# LLT server duid config
+
+# LLT server duid declaration
+server-duid llt token-ring 213982198 00:16:6F:49:7D:9B;
diff --git a/keama/tests/duidlltthw6.out b/keama/tests/duidlltthw6.out
new file mode 100644
index 00000000..de2d0a48
--- /dev/null
+++ b/keama/tests/duidlltthw6.out
@@ -0,0 +1,12 @@
+{
+ # LLT server duid config
+ # LLT server duid declaration
+ "Dhcp6": {
+ "server-id": {
+ "type": "LLT",
+ "htype": 6,
+ "time": 213982198,
+ "identifier": "00166f497d9b"
+ }
+ }
+}
diff --git a/keama/tests/duidnoid.err b/keama/tests/duidnoid.err
new file mode 100644
index 00000000..32a2c9cb
--- /dev/null
+++ b/keama/tests/duidnoid.err
@@ -0,0 +1,4 @@
+# bad (no identifier) numeric server duid config
+
+# server duid declaration
+server-duid 9;
diff --git a/keama/tests/duidnoid.msg b/keama/tests/duidnoid.msg
new file mode 100644
index 00000000..3e44395a
--- /dev/null
+++ b/keama/tests/duidnoid.msg
@@ -0,0 +1 @@
+duidnoid.err line 4: identifier expected
diff --git a/keama/tests/enableupdates6.in6 b/keama/tests/enableupdates6.in6
new file mode 100644
index 00000000..b6641957
--- /dev/null
+++ b/keama/tests/enableupdates6.in6
@@ -0,0 +1,8 @@
+# ddns-updates (aka enable-updates)
+
+ddns-updates on;
+
+# embedded
+class "foo" {
+ ddns-updates off;
+}
diff --git a/keama/tests/enableupdates6.out b/keama/tests/enableupdates6.out
new file mode 100644
index 00000000..8d20a773
--- /dev/null
+++ b/keama/tests/enableupdates6.out
@@ -0,0 +1,20 @@
+{
+ # ddns-updates (aka enable-updates)
+ "Dhcp6": {
+ "dhcp-ddns": {
+ /// Unspecified ddns-domainname (default domain-name option value)
+ /// Kea requires a qualifying-suffix
+ /// Initialized to "": please put a value
+ "qualifying-suffix": "",
+ "enable-updates": true
+ },
+ "client-classes": [
+ # embedded
+ {
+ "name": "foo"
+// /// Only global enable-updates is supported
+// "enable-updates": false
+ }
+ ]
+ }
+}
diff --git a/keama/tests/encodedx6.in6 b/keama/tests/encodedx6.in6
new file mode 100644
index 00000000..f6b16377
--- /dev/null
+++ b/keama/tests/encodedx6.in6
@@ -0,0 +1,9 @@
+# encode data expression and extract numeric expression
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# reduce literals
+class "literal" {
+ match if option dhcp6.client-data != encode-int(extract-int("\bbar",32),16);
+}
diff --git a/keama/tests/encodedx6.out b/keama/tests/encodedx6.out
new file mode 100644
index 00000000..d1b8b38a
--- /dev/null
+++ b/keama/tests/encodedx6.out
@@ -0,0 +1,15 @@
+{
+ # encode data expression and extract numeric expression
+ # empty configs are not accepted by Kea
+ "Dhcp6": {
+ "valid-lifetime": 1800,
+ "client-classes": [
+ # reduce literals
+ {
+ "name": "literal",
+ /// from: match if (option dhcp6.client-data) != (encode-int(extract-int('bar', 32), 16))
+ "test": "not (option[45].hex == 'ar')"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/env b/keama/tests/env
new file mode 100644
index 00000000..9fe0fcd4
--- /dev/null
+++ b/keama/tests/env
@@ -0,0 +1,3 @@
+setenv KEA4 /tmp/kea/src/bin/dhcp4/kea-dhcp4
+setenv KEA6 /tmp/kea/src/bin/dhcp6/kea-dhcp6
+setenv HOOK /tmp/kea/premium/src/hooks/dhcp/flex_id/.libs/
diff --git a/keama/tests/escapestring4.in4 b/keama/tests/escapestring4.in4
new file mode 100644
index 00000000..3f318e6c
--- /dev/null
+++ b/keama/tests/escapestring4.in4
@@ -0,0 +1,6 @@
+# string option-data with embedded commas
+
+# vendor option space
+option a-string code 250 = text;
+
+option a-string "foo, bar";
diff --git a/keama/tests/escapestring4.out b/keama/tests/escapestring4.out
new file mode 100644
index 00000000..caef76c3
--- /dev/null
+++ b/keama/tests/escapestring4.out
@@ -0,0 +1,23 @@
+{
+ # string option-data with embedded commas
+ # vendor option space
+ "Dhcp4": {
+ "option-def": [
+ {
+ "space": "dhcp4",
+ "name": "a-string",
+ "code": 250,
+ "type": "string"
+ }
+ ],
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "a-string",
+ "code": 250,
+// "original-data": "\"foo, bar\"",
+ "data": "foo\\, bar"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/execstatement4.in4 b/keama/tests/execstatement4.in4
new file mode 100644
index 00000000..6adc6b1e
--- /dev/null
+++ b/keama/tests/execstatement4.in4
@@ -0,0 +1,7 @@
+# DHCPv4 executable statement config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# impossible to convert statement statement
+break;
diff --git a/keama/tests/execstatement4.out b/keama/tests/execstatement4.out
new file mode 100644
index 00000000..4988de92
--- /dev/null
+++ b/keama/tests/execstatement4.out
@@ -0,0 +1,11 @@
+{
+ # DHCPv4 executable statement config
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800
+// # impossible to convert statement statement
+// "statement": {
+// "break": null
+// }
+ }
+}
diff --git a/keama/tests/execstatement6.in6 b/keama/tests/execstatement6.in6
new file mode 100644
index 00000000..fe49424a
--- /dev/null
+++ b/keama/tests/execstatement6.in6
@@ -0,0 +1,7 @@
+# DHCPv6 executable statement config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# impossible to convert statement statement
+break;
diff --git a/keama/tests/execstatement6.out b/keama/tests/execstatement6.out
new file mode 100644
index 00000000..25ac01af
--- /dev/null
+++ b/keama/tests/execstatement6.out
@@ -0,0 +1,11 @@
+{
+ # DHCPv6 executable statement config
+ # empty configs are not accepted by Kea
+ "Dhcp6": {
+ "valid-lifetime": 1800
+// # impossible to convert statement statement
+// "statement": {
+// "break": null
+// }
+ }
+}
diff --git a/keama/tests/existsbx4.in4 b/keama/tests/existsbx4.in4
new file mode 100644
index 00000000..650a208e
--- /dev/null
+++ b/keama/tests/existsbx4.in4
@@ -0,0 +1,15 @@
+# exists boolean expression
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# use exists in a reductible match if
+class "reductible" {
+ match if exists host-name;
+}
+
+# if test is a boolean too
+if exists host-name {
+ log(info, concat("hostname:", option host-name));
+}
+
diff --git a/keama/tests/existsbx4.out b/keama/tests/existsbx4.out
new file mode 100644
index 00000000..7d977ac6
--- /dev/null
+++ b/keama/tests/existsbx4.out
@@ -0,0 +1,48 @@
+{
+ # exists boolean expression
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "client-classes": [
+ # use exists in a reductible match if
+ {
+ "name": "reductible",
+ /// from: match if exists dhcp.host-name
+ "test": "option[12].exists"
+ }
+ ]
+// # if test is a boolean too
+// "statement": {
+// "if": {
+// "condition": {
+// "exists": {
+// "universe": "dhcp",
+// "name": "host-name",
+// "code": 12
+// }
+// },
+// "then": [
+// {
+// /// Kea does not support yet log statements
+// /// Reference Kea #234
+// "log": {
+// "priority": "info",
+// "message": {
+// "concat": {
+// "left": "hostname:",
+// "right": {
+// "option": {
+// "universe": "dhcp",
+// "name": "host-name",
+// "code": 12
+// }
+// }
+// }
+// }
+// }
+// }
+// ]
+// }
+// }
+ }
+}
diff --git a/keama/tests/filename4.in4 b/keama/tests/filename4.in4
new file mode 100644
index 00000000..9bf3ad00
--- /dev/null
+++ b/keama/tests/filename4.in4
@@ -0,0 +1,10 @@
+# filename (aka boot-file-name) and server-name (aka server-hostname)
+
+filename "/var/boot/boot1";
+server-name "foobar.biz";
+
+# embedded
+class "foo" {
+ filename "/var/boot/boot2";
+ server-name "none.biz";
+}
diff --git a/keama/tests/filename4.out b/keama/tests/filename4.out
new file mode 100644
index 00000000..a391eca5
--- /dev/null
+++ b/keama/tests/filename4.out
@@ -0,0 +1,17 @@
+{
+ # filename (aka boot-file-name) and server-name (aka server-hostname)
+ "Dhcp4": {
+// /// boot-file-name was defined in an unsupported scope
+// "boot-file-name": "/var/boot/boot1",
+// /// server-hostname was defined in an unsupported scope
+// "server-hostname": "foobar.biz",
+ "client-classes": [
+ # embedded
+ {
+ "name": "foo",
+ "boot-file-name": "/var/boot/boot2",
+ "server-hostname": "none.biz"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/filenamedx4.notyet b/keama/tests/filenamedx4.notyet
new file mode 100644
index 00000000..127bcc7a
--- /dev/null
+++ b/keama/tests/filenamedx4.notyet
@@ -0,0 +1,20 @@
+# filename data expression
+# Kea has no filename extractor in libeval
+
+# authoritative is mandatory
+authoritative;
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# by filename superclass
+class "byfn" {
+ match filename;
+}
+
+subclass "byfn" "boot00070e364819" {
+ option host-name "test1";
+}
+
+# raw
+option host-name = concat("host-", substring(filename, 4, 12));
diff --git a/keama/tests/fixedaddressinroot4.err4 b/keama/tests/fixedaddressinroot4.err4
new file mode 100644
index 00000000..f5d2480a
--- /dev/null
+++ b/keama/tests/fixedaddressinroot4.err4
@@ -0,0 +1,4 @@
+# DHCPv4 fixed address declaration in root config
+
+# DHCPv4 fixed address declaration must be in a host declaration
+fixed-address 204.152.185.133;
diff --git a/keama/tests/fixedaddressinroot4.msg b/keama/tests/fixedaddressinroot4.msg
new file mode 100644
index 00000000..fd0424a5
--- /dev/null
+++ b/keama/tests/fixedaddressinroot4.msg
@@ -0,0 +1 @@
+fixedaddressinroot4.err4 line 4: fixed-address parameter not allowed here.
diff --git a/keama/tests/fixedaddressinroot6.err6 b/keama/tests/fixedaddressinroot6.err6
new file mode 100644
index 00000000..f42854a7
--- /dev/null
+++ b/keama/tests/fixedaddressinroot6.err6
@@ -0,0 +1,4 @@
+# DHCPv6 fixed address declaration in root config
+
+# DHCPv6 fixed address declaration must be in a host declaration
+fixed-address6 2001::1;
diff --git a/keama/tests/fixedaddressinroot6.msg b/keama/tests/fixedaddressinroot6.msg
new file mode 100644
index 00000000..21da6d84
--- /dev/null
+++ b/keama/tests/fixedaddressinroot6.msg
@@ -0,0 +1 @@
+fixedaddressinroot6.err6 line 4: fixed-address parameter not allowed here.
diff --git a/keama/tests/fixedprefixinroot.err6 b/keama/tests/fixedprefixinroot.err6
new file mode 100644
index 00000000..7415aaf3
--- /dev/null
+++ b/keama/tests/fixedprefixinroot.err6
@@ -0,0 +1,4 @@
+# DHCPv6 fixed prefix declaration in root config
+
+# DHCPv6 fixed prefix declaration must be in a host declaration
+fixed-prefix6 2001:0:0:1/64;
diff --git a/keama/tests/fixedprefixinroot.msg b/keama/tests/fixedprefixinroot.msg
new file mode 100644
index 00000000..0b835acd
--- /dev/null
+++ b/keama/tests/fixedprefixinroot.msg
@@ -0,0 +1 @@
+fixedprefixinroot.err6 line 4: fixed-prefix6 declaration not allowed here.
diff --git a/keama/tests/fqdncompressed.err6 b/keama/tests/fqdncompressed.err6
new file mode 100644
index 00000000..aed1fe63
--- /dev/null
+++ b/keama/tests/fqdncompressed.err6
@@ -0,0 +1,7 @@
+# option definition config
+
+# options
+option space foobar;
+
+# compressed list of FQDNs are forbidden in DHCPv6
+option foobar.compressed-list code 1 = domain-list compressed;
diff --git a/keama/tests/fqdncompressed.msg b/keama/tests/fqdncompressed.msg
new file mode 100644
index 00000000..cfd157ef
--- /dev/null
+++ b/keama/tests/fqdncompressed.msg
@@ -0,0 +1 @@
+fqdncompressed.err6 line 7: domain list in DHCPv6 MUST NOT be compressed
diff --git a/keama/tests/gethostdx4.notyet b/keama/tests/gethostdx4.notyet
new file mode 100644
index 00000000..567d66a4
--- /dev/null
+++ b/keama/tests/gethostdx4.notyet
@@ -0,0 +1,13 @@
+# gethostname and gethostbyname data expressions
+
+# authoritative is mandatory
+authoritative;
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# gethostname
+option domain-name = suffix(gethostname(), (1 + 2) * 2);
+
+# gethostbyaddr
+option ntp-servers = gethostbyname("www.apple.fr");
diff --git a/keama/tests/global4.in4 b/keama/tests/global4.in4
new file mode 100644
index 00000000..ddfaec67
--- /dev/null
+++ b/keama/tests/global4.in4
@@ -0,0 +1,10 @@
+# DHCPv4 global reservation config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# global reservation
+host foobar {
+ hardware ethernet 00:0B:FD:32:E6:FA;
+ option ip-forwarding off;
+}
diff --git a/keama/tests/global4.out b/keama/tests/global4.out
new file mode 100644
index 00000000..66b380ff
--- /dev/null
+++ b/keama/tests/global4.out
@@ -0,0 +1,28 @@
+{
+ # DHCPv4 global reservation config
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "host-reservation-identifiers": [
+ "hw-address"
+ ],
+ "reservation-mode": "global",
+ "reservations": [
+ # global reservation
+ {
+ "hostname": "foobar",
+ "hw-address": "00:0b:fd:32:e6:fa",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "ip-forwarding",
+ "code": 19,
+// "original-data": "off",
+ /// canonized booleans to lowercase true or false
+ "data": "false"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/global6.in6 b/keama/tests/global6.in6
new file mode 100644
index 00000000..8bba7082
--- /dev/null
+++ b/keama/tests/global6.in6
@@ -0,0 +1,10 @@
+# DHCPv6 global reservation config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# global reservation
+host foobar {
+ hardware ethernet 00:0B:FD:32:E6:FA;
+ option dhcp6.name-servers 2a01:e00::2, 2a01:e00::1;
+}
diff --git a/keama/tests/global6.out b/keama/tests/global6.out
new file mode 100644
index 00000000..5616e7ec
--- /dev/null
+++ b/keama/tests/global6.out
@@ -0,0 +1,26 @@
+{
+ # DHCPv6 global reservation config
+ # empty configs are not accepted by Kea
+ "Dhcp6": {
+ "valid-lifetime": 1800,
+ "host-reservation-identifiers": [
+ "hw-address"
+ ],
+ "reservation-mode": "global",
+ "reservations": [
+ # global reservation
+ {
+ "hostname": "foobar",
+ "hw-address": "00:0b:fd:32:e6:fa",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "dns-servers",
+ "code": 23,
+ "data": "2a01:e00::2, 2a01:e00::1"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/groupclass4.in4 b/keama/tests/groupclass4.in4
new file mode 100644
index 00000000..b306f66f
--- /dev/null
+++ b/keama/tests/groupclass4.in4
@@ -0,0 +1,32 @@
+# group and class declaration config
+
+# options
+option mysystem code 250 = text;
+option myversion code 251 = unsigned integer 16;
+
+# superclass declaration
+class "foobar" {
+ match option mysystem;
+ option myversion 1;
+}
+
+# simple subclass declaration
+subclass "foobar" "version1";
+
+group machin {
+ next-server 10.10.10.1;
+ # this option is not propagated because the superclass takes precedence
+ option myversion 99;
+
+ # option setting subclass declaration
+ subclass "foobar" "version2" { option myversion 2; }
+
+ # complex subclass declaration
+ subclass "foobar" "version3" {
+ option myversion 3;
+ next-server 192.168.0.1;
+ }
+
+ # another simple subclass declaration
+ subclass "foobar" "version10";
+}
diff --git a/keama/tests/groupclass4.out b/keama/tests/groupclass4.out
new file mode 100644
index 00000000..71e40072
--- /dev/null
+++ b/keama/tests/groupclass4.out
@@ -0,0 +1,102 @@
+{
+ # group and class declaration config
+ # options
+ "Dhcp4": {
+ "option-def": [
+ {
+ "space": "dhcp4",
+ "name": "mysystem",
+ "code": 250,
+ "type": "string"
+ },
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "type": "uint16"
+ }
+ ],
+ "client-classes": [
+ # superclass declaration
+ /// match: option dhcp.mysystem
+ {
+ "name": "foobar",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "1"
+ }
+ ]
+ },
+ # simple subclass declaration
+ /// subclass selector 'version1'
+ {
+ "name": "sub#foobar#0",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "1"
+ }
+ ],
+ /// from: match option dhcp.mysystem
+ /// data: 'version1'
+ "test": "option[250].hex == 'version1'"
+ },
+ # option setting subclass declaration
+ /// subclass selector 'version2'
+ {
+ "name": "sub#foobar#1",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "2"
+ }
+ ],
+ /// from: match option dhcp.mysystem
+ /// data: 'version2'
+ "test": "option[250].hex == 'version2'",
+ "next-server": "10.10.10.1"
+ },
+ # complex subclass declaration
+ /// subclass selector 'version3'
+ {
+ "name": "sub#foobar#2",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "3"
+ }
+ ],
+ "next-server": "192.168.0.1",
+ /// from: match option dhcp.mysystem
+ /// data: 'version3'
+ "test": "option[250].hex == 'version3'"
+ },
+ # another simple subclass declaration
+ /// subclass selector 'version10'
+ {
+ "name": "sub#foobar#3",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "1"
+ }
+ ],
+ /// from: match option dhcp.mysystem
+ /// data: 'version10'
+ "test": "option[250].hex == 'version10'",
+ "next-server": "10.10.10.1"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/groupclass6.in6 b/keama/tests/groupclass6.in6
new file mode 100644
index 00000000..4cc21b78
--- /dev/null
+++ b/keama/tests/groupclass6.in6
@@ -0,0 +1,35 @@
+# group and class declaration config
+
+# options
+option dhcp6.mysystem code 1250 = text;
+option dhcp6.myversion code 1251 = unsigned integer 16;
+option dhcp6.myvalue code 1252 = text;
+
+# superclass declaration
+class "foobar" {
+ match option dhcp6.mysystem;
+ option dhcp6.myversion 1;
+}
+
+# simple subclass declaration
+subclass "foobar" "version1";
+
+# anonymous group
+group {
+ # this option is not propagated because the superclass takes precedence
+ option dhcp6.myversion 99;
+
+ option dhcp6.myvalue "foo";
+
+ # option setting subclass declaration
+ subclass "foobar" "version2" { option dhcp6.myversion 2; }
+
+ # complex subclass declaration
+ subclass "foobar" "version3" {
+ option dhcp6.myversion 3;
+ option dhcp6.myvalue "bar";
+ }
+
+ # another simple subclass declaration
+ subclass "foobar" "version10";
+}
diff --git a/keama/tests/groupclass6.out b/keama/tests/groupclass6.out
new file mode 100644
index 00000000..68144b89
--- /dev/null
+++ b/keama/tests/groupclass6.out
@@ -0,0 +1,123 @@
+{
+ # group and class declaration config
+ # options
+ "Dhcp6": {
+ "option-def": [
+ {
+ "space": "dhcp6",
+ "name": "mysystem",
+ "code": 1250,
+ "type": "string"
+ },
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "type": "uint16"
+ },
+ {
+ "space": "dhcp6",
+ "name": "myvalue",
+ "code": 1252,
+ "type": "string"
+ }
+ ],
+ "client-classes": [
+ # superclass declaration
+ /// match: option dhcp6.mysystem
+ {
+ "name": "foobar",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "data": "1"
+ }
+ ]
+ },
+ # simple subclass declaration
+ /// subclass selector 'version1'
+ {
+ "name": "sub#foobar#0",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "data": "1"
+ }
+ ],
+ /// from: match option dhcp6.mysystem
+ /// data: 'version1'
+ "test": "option[1250].hex == 'version1'"
+ },
+ # option setting subclass declaration
+ /// subclass selector 'version2'
+ {
+ "name": "sub#foobar#1",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "data": "2"
+ },
+ {
+ "space": "dhcp6",
+ "name": "myvalue",
+ "code": 1252,
+ "data": "foo"
+ }
+ ],
+ /// from: match option dhcp6.mysystem
+ /// data: 'version2'
+ "test": "option[1250].hex == 'version2'"
+ },
+ # complex subclass declaration
+ /// subclass selector 'version3'
+ {
+ "name": "sub#foobar#2",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "data": "3"
+ },
+ {
+ "space": "dhcp6",
+ "name": "myvalue",
+ "code": 1252,
+ "data": "bar"
+ }
+ ],
+ /// from: match option dhcp6.mysystem
+ /// data: 'version3'
+ "test": "option[1250].hex == 'version3'"
+ },
+ # another simple subclass declaration
+ /// subclass selector 'version10'
+ {
+ "name": "sub#foobar#3",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "data": "1"
+ },
+ {
+ "space": "dhcp6",
+ "name": "myvalue",
+ "code": 1252,
+ "data": "foo"
+ }
+ ],
+ /// from: match option dhcp6.mysystem
+ /// data: 'version10'
+ "test": "option[1250].hex == 'version10'"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/groupgroup4.in4 b/keama/tests/groupgroup4.in4
new file mode 100644
index 00000000..3be93d7d
--- /dev/null
+++ b/keama/tests/groupgroup4.in4
@@ -0,0 +1,45 @@
+# multiple groups declaration config
+
+# options
+option mysystem code 250 = text;
+option myversion code 251 = unsigned integer 16;
+option myvalue code 252 = text;
+
+# superclass declaration
+class "foobar" {
+ match option mysystem;
+ option myversion 1;
+}
+
+# simple subclass declaration
+subclass "foobar" "version1";
+
+group first {
+ next-server 10.10.10.1;
+ # this option is not propagated because the superclass takes precedence
+ option myversion 99;
+
+ # option setting subclass declaration
+ subclass "foobar" "version2" { option myversion 2; }
+
+ # complex subclass declaration
+ subclass "foobar" "version3" {
+ option myversion 3;
+ next-server 192.168.0.1;
+ }
+
+ group second {
+ # another simple subclass declaration
+ subclass "foobar" "version10";
+
+ # and a final subclass declaration
+ subclass "foobar" "version20" {
+ option myversion 20;
+ next-server 192.168.0.20;
+ option myvalue "twenty";
+ }
+
+ # positions of delaration do not matter
+ option myvalue "ten";
+ }
+}
diff --git a/keama/tests/groupgroup4.out b/keama/tests/groupgroup4.out
new file mode 100644
index 00000000..09dd0574
--- /dev/null
+++ b/keama/tests/groupgroup4.out
@@ -0,0 +1,138 @@
+{
+ # multiple groups declaration config
+ # options
+ "Dhcp4": {
+ "option-def": [
+ {
+ "space": "dhcp4",
+ "name": "mysystem",
+ "code": 250,
+ "type": "string"
+ },
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "type": "uint16"
+ },
+ {
+ "space": "dhcp4",
+ "name": "myvalue",
+ "code": 252,
+ "type": "string"
+ }
+ ],
+ "client-classes": [
+ # superclass declaration
+ /// match: option dhcp.mysystem
+ {
+ "name": "foobar",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "1"
+ }
+ ]
+ },
+ # simple subclass declaration
+ /// subclass selector 'version1'
+ {
+ "name": "sub#foobar#0",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "1"
+ }
+ ],
+ /// from: match option dhcp.mysystem
+ /// data: 'version1'
+ "test": "option[250].hex == 'version1'"
+ },
+ # option setting subclass declaration
+ /// subclass selector 'version2'
+ {
+ "name": "sub#foobar#1",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "2"
+ }
+ ],
+ /// from: match option dhcp.mysystem
+ /// data: 'version2'
+ "test": "option[250].hex == 'version2'",
+ "next-server": "10.10.10.1"
+ },
+ # complex subclass declaration
+ /// subclass selector 'version3'
+ {
+ "name": "sub#foobar#2",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "3"
+ }
+ ],
+ "next-server": "192.168.0.1",
+ /// from: match option dhcp.mysystem
+ /// data: 'version3'
+ "test": "option[250].hex == 'version3'"
+ },
+ # another simple subclass declaration
+ /// subclass selector 'version10'
+ {
+ "name": "sub#foobar#3",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "1"
+ },
+ # positions of delaration do not matter
+ {
+ "space": "dhcp4",
+ "name": "myvalue",
+ "code": 252,
+ "data": "ten"
+ }
+ ],
+ /// from: match option dhcp.mysystem
+ /// data: 'version10'
+ "test": "option[250].hex == 'version10'",
+ "next-server": "10.10.10.1"
+ },
+ # and a final subclass declaration
+ /// subclass selector 'version20'
+ {
+ "name": "sub#foobar#4",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "20"
+ },
+ {
+ "space": "dhcp4",
+ "name": "myvalue",
+ "code": 252,
+ "data": "twenty"
+ }
+ ],
+ "next-server": "192.168.0.20",
+ /// from: match option dhcp.mysystem
+ /// data: 'version20'
+ "test": "option[250].hex == 'version20'"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/grouphost4.inn b/keama/tests/grouphost4.inn
new file mode 100644
index 00000000..da9d5291
--- /dev/null
+++ b/keama/tests/grouphost4.inn
@@ -0,0 +1,35 @@
+# group and host declarations config
+
+# subnet4 declaration
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ range 10.5.5.5 10.5.5.10;
+}
+
+# host declaration
+host test1 {
+ hardware ethernet 00:0B:FD:32:E6:FA;
+ fixed-address 10.5.5.1, 10.10.10.10;
+}
+
+# group declaration
+group "foobar" {
+ default-lease-time 1800;
+ option domain-search "example.com", "example.org";
+ next-server 192.168.0.1;
+
+ # host declarations
+ host test2 {
+ hardware ethernet 00:07:0E:36:48:19;
+ fixed-address 10.5.5.2;
+ option domain-name "example.com";
+ option domain-search "example.com", "com";
+ }
+
+ host test3 {
+ hardware fddi 00:07:0E:36:48:19;
+ fixed-address 10.10.10.1;
+ default-lease-time 3600;
+ }
+}
+
+subnet 10.10.10.0 netmask 255.255.255.224 { }
diff --git a/keama/tests/grouphost4.out b/keama/tests/grouphost4.out
new file mode 100644
index 00000000..2027c536
--- /dev/null
+++ b/keama/tests/grouphost4.out
@@ -0,0 +1,78 @@
+{
+ # group and host declarations config
+ # subnet4 declaration
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "subnet4": [
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ "pools": [
+ {
+ "pool": "10.5.5.5 - 10.5.5.10"
+ }
+ ],
+ "reservations": [
+ # host declaration
+ {
+ "hostname": "test1",
+ "hw-address": "00:0b:fd:32:e6:fa",
+ "ip-address": "10.5.5.1"
+// "extra-ip-addresses": [
+// "10.10.10.10"
+// ]
+ },
+ # host declarations
+ {
+ "hostname": "test2",
+ "hw-address": "00:07:0e:36:48:19",
+ "ip-address": "10.5.5.2",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "domain-name",
+ "code": 15,
+ "data": "example.com"
+ },
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\", \"com\"",
+ "data": "example.com, com"
+ }
+ ],
+ "next-server": "192.168.0.1"
+ }
+ ]
+ },
+ {
+ "id": 2,
+ "subnet": "10.10.10.0/27",
+ "reservations": [
+// {
+// "hostname": "test3",
+// "hw-address": "fddi 00:07:0e:36:48:19",
+// "ip-address": "10.10.10.1",
+// /// default-valid-lifetime in unsupported scope
+// "valid-lifetime": 3600,
+// "option-data": [
+// {
+// "space": "dhcp4",
+// "name": "domain-search",
+// "code": 119,
+// "original-data": "\"example.com\", \"example.org\"",
+// "data": "example.com, example.org"
+// }
+// ],
+// "next-server": "192.168.0.1"
+// }
+ ]
+ }
+ ],
+ "host-reservation-identifiers": [
+ "hw-address"
+ ]
+ }
+}
diff --git a/keama/tests/groupinclass.err b/keama/tests/groupinclass.err
new file mode 100644
index 00000000..dbc320a4
--- /dev/null
+++ b/keama/tests/groupinclass.err
@@ -0,0 +1,10 @@
+# group declaration inside class declaration config
+
+# host declaration
+class "foobar" {
+ # can't put a group declaration here
+ group "illegal" {
+ default-lease-time 1800;
+ }
+}
+
diff --git a/keama/tests/groupinclass.msg b/keama/tests/groupinclass.msg
new file mode 100644
index 00000000..ee72c674
--- /dev/null
+++ b/keama/tests/groupinclass.msg
@@ -0,0 +1 @@
+groupinclass.err line 6: group declarations not allowed here.
diff --git a/keama/tests/groupsubnet4.in4 b/keama/tests/groupsubnet4.in4
new file mode 100644
index 00000000..a7a199dc
--- /dev/null
+++ b/keama/tests/groupsubnet4.in4
@@ -0,0 +1,24 @@
+# Group with DHCPv4 subnet declaration config
+
+# parameter which will be changed in subnet
+default-lease-time 1200;
+
+# group declaration
+group foobar {
+ # option
+ option domain-search "example.com", "example.org";
+
+ # parameters
+ default-lease-time 3600;
+ ignore-client-uids false;
+
+ # DHCPv4 subnet declaration
+ subnet 10.5.5.0 netmask 255.255.255.224 {
+ # at least one pool is required
+ pool {
+ range 10.5.5.5 10.5.5.10;
+ }
+ interface "en0";
+ default-lease-time 1800;
+ }
+}
diff --git a/keama/tests/groupsubnet4.out b/keama/tests/groupsubnet4.out
new file mode 100644
index 00000000..f2db5f85
--- /dev/null
+++ b/keama/tests/groupsubnet4.out
@@ -0,0 +1,38 @@
+{
+ # Group with DHCPv4 subnet declaration config
+ # parameter which will be changed in subnet
+ "Dhcp4": {
+ "valid-lifetime": 1200,
+ "interfaces-config": {
+ "interfaces": [
+ "en0"
+ ]
+ },
+ "subnet4": [
+ # DHCPv4 subnet declaration
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ "pools": [
+ # at least one pool is required
+ {
+ "pool": "10.5.5.5 - 10.5.5.10"
+ }
+ ],
+ "interface": "en0",
+ "valid-lifetime": 1800,
+ "option-data": [
+ # option
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "match-client-id": true
+ }
+ ]
+ }
+}
diff --git a/keama/tests/groupsubnet6.in6 b/keama/tests/groupsubnet6.in6
new file mode 100644
index 00000000..90277618
--- /dev/null
+++ b/keama/tests/groupsubnet6.in6
@@ -0,0 +1,24 @@
+# Group with DHCPv6 subnet declaration config
+
+# parameter which will be changed in subnet
+default-lease-time 1200;
+
+# group declaration
+group foobar {
+ # option
+ option dhcp6.domain-search "example.com", "example.org";
+
+ # parameters
+ default-lease-time 3600;
+
+ # DHCPv4 subnet declaration
+ subnet6 2001::/64 {
+ # at least one pool is required
+ pool6 {
+ range6 2001::100 2001::200;
+ }
+ interface "en0";
+ default-lease-time 1800;
+ option dhcp6.lq-relay-data 2001::1 "foobar";
+ }
+}
diff --git a/keama/tests/groupsubnet6.out b/keama/tests/groupsubnet6.out
new file mode 100644
index 00000000..63d5794e
--- /dev/null
+++ b/keama/tests/groupsubnet6.out
@@ -0,0 +1,44 @@
+{
+ # Group with DHCPv6 subnet declaration config
+ # parameter which will be changed in subnet
+ "Dhcp6": {
+ "valid-lifetime": 1200,
+ "interfaces-config": {
+ "interfaces": [
+ "en0"
+ ]
+ },
+ "subnet6": [
+ # DHCPv4 subnet declaration
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "pools": [
+ # at least one pool is required
+ {
+ "pool": "2001::100 - 2001::200"
+ }
+ ],
+ "interface": "en0",
+ "valid-lifetime": 1800,
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "lq-relay-data",
+ "code": 47,
+// "original-data": "2001::1 \"foobar\"",
+ "data": "2001::1, 666f6f626172"
+ },
+ # option
+ {
+ "space": "dhcp6",
+ "name": "domain-search",
+ "code": 24,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/groupsubnetif.err4 b/keama/tests/groupsubnetif.err4
new file mode 100644
index 00000000..9a407b11
--- /dev/null
+++ b/keama/tests/groupsubnetif.err4
@@ -0,0 +1,19 @@
+# bad (interface unlnown in this cope) group declaration config
+
+# parameter which will be changed in subnet
+default-lease-time 1200;
+
+# group declaration
+group foobar {
+ # interface
+ interface "foo";
+
+ # DHCPv4 subnet declaration
+ subnet 10.5.5.0 netmask 255.255.255.224 {
+ # at least one pool is required
+ pool {
+ range 10.5.5.5 10.5.5.10;
+ }
+ interface "bar";
+ }
+}
diff --git a/keama/tests/groupsubnetif.msg b/keama/tests/groupsubnetif.msg
new file mode 100644
index 00000000..1828b932
--- /dev/null
+++ b/keama/tests/groupsubnetif.msg
@@ -0,0 +1 @@
+groupsubnetif.err4 line 7: expecting a parameter or declaration
diff --git a/keama/tests/hardware2dx4.in4 b/keama/tests/hardware2dx4.in4
new file mode 100644
index 00000000..73046612
--- /dev/null
+++ b/keama/tests/hardware2dx4.in4
@@ -0,0 +1,13 @@
+# simplified hardware data expression
+
+# hardware type class
+class "ethernet" {
+ match if substring(hardware, 0, 1) = encode-int(1, 8);
+}
+
+# ethernet address superclass
+class "ethernet-address" {
+ match substring(hardware, 1, 6);
+}
+
+subclass "ethernet-address" 00:0B:FD:32:E6:FA { }
diff --git a/keama/tests/hardware2dx4.out b/keama/tests/hardware2dx4.out
new file mode 100644
index 00000000..77c3f533
--- /dev/null
+++ b/keama/tests/hardware2dx4.out
@@ -0,0 +1,25 @@
+{
+ # simplified hardware data expression
+ # hardware type class
+ "Dhcp4": {
+ "client-classes": [
+ {
+ "name": "ethernet",
+ /// from: match if (substring(hardware, 0, 1)) = (encode-int(1, 8))
+ "test": "substring(pkt4.htype,-1,all) == '\u0001'"
+ },
+ # ethernet address superclass
+ /// match: substring(hardware, 1, 6)
+ {
+ "name": "ethernet-address"
+ },
+ /// subclass selector 0x0x000bfd32e6fa
+ {
+ "name": "sub#ethernet-address#0",
+ /// from: match substring(hardware, 1, 6)
+ /// data: 0x000bfd32e6fa
+ "test": "substring(pkt4.mac,0,6) == 0x000bfd32e6fa"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/hardwaredx4.in4 b/keama/tests/hardwaredx4.in4
new file mode 100644
index 00000000..46160776
--- /dev/null
+++ b/keama/tests/hardwaredx4.in4
@@ -0,0 +1,16 @@
+# hardware data expression
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# pretty standard hardware superclass
+class "byhw" {
+ match hardware;
+}
+
+subclass "byhw" 01:00:07:0E:36:48:19 {
+ option host-name "test1";
+}
+
+# raw
+option host-name = binary-to-ascii(16, 8, "-", hardware);
diff --git a/keama/tests/hardwaredx4.out b/keama/tests/hardwaredx4.out
new file mode 100644
index 00000000..a3f13938
--- /dev/null
+++ b/keama/tests/hardwaredx4.out
@@ -0,0 +1,55 @@
+{
+ # hardware data expression
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "client-classes": [
+ # pretty standard hardware superclass
+ /// match: hardware
+ {
+ "name": "byhw"
+ },
+ /// subclass selector 0x0x0100070e364819
+ {
+ "name": "sub#byhw#0",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "host-name",
+ "code": 12,
+ "data": "test1"
+ }
+ ],
+ /// from: match hardware
+ /// data: 0x0100070e364819
+ "test": "concat(substring(pkt4.htype,-1,all), pkt4.mac) == 0x0100070e364819"
+ }
+ ],
+ "option-data": [
+// # raw
+// {
+// "space": "dhcp4",
+// "name": "host-name",
+// "code": 12,
+// "csv-format": false,
+// "expression": {
+// "binary-to-ascii": {
+// "base": 16,
+// "width": 8,
+// "separator": "-",
+// "buffer": {
+// "concat": {
+// "left": {
+// "hw-type": null
+// },
+// "right": {
+// "hw-address": null
+// }
+// }
+// }
+// }
+// }
+// }
+ ]
+ }
+}
diff --git a/keama/tests/hardwareinroot.err b/keama/tests/hardwareinroot.err
new file mode 100644
index 00000000..22902f20
--- /dev/null
+++ b/keama/tests/hardwareinroot.err
@@ -0,0 +1,5 @@
+# hardware declaration in root config
+
+# hardware declaration must be in a host declaration
+hardware ethernet 00:0B:FD:32:E6:FA;
+
diff --git a/keama/tests/hardwareinroot.msg b/keama/tests/hardwareinroot.msg
new file mode 100644
index 00000000..6cf018d0
--- /dev/null
+++ b/keama/tests/hardwareinroot.msg
@@ -0,0 +1 @@
+hardwareinroot.err line 4: hardware address parameter not allowed here.
diff --git a/keama/tests/host6.notyet b/keama/tests/host6.notyet
new file mode 100644
index 00000000..daff911e
--- /dev/null
+++ b/keama/tests/host6.notyet
@@ -0,0 +1,20 @@
+# DHCPv6 host declaration config
+
+# authoritative is mandatory
+authoritative;
+
+# subnet declaration
+subnet6 2001::/64 {
+ range6 2001::100 2001::200;
+}
+
+# host declarations
+host test1 {
+ hardware ethernet 00:07:0E:36:48:19;
+ fixed-address6 2001::1, 2001::10;
+}
+
+host test2 {
+ hardware fddi 00:07:0E:36:48:19;
+ fixed-prefix6 2001:0:0:1::/64;
+}
diff --git a/keama/tests/hostidentifier4.inl b/keama/tests/hostidentifier4.inl
new file mode 100644
index 00000000..9e50ddd3
--- /dev/null
+++ b/keama/tests/hostidentifier4.inl
@@ -0,0 +1,30 @@
+# host declaration with flexible identifiers config
+
+# subnet4 declaration
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ range 10.5.5.5 10.5.5.10;
+}
+
+# option definition
+
+option my-id code 250 = text;
+
+# host declarations
+host test1 {
+ host-identifier option my-id test1;
+ option domain-search "example.com", "example.org";
+ default-lease-time 1800;
+ fixed-address 10.5.5.1, 10.10.10.10;
+}
+
+host test2 {
+ hardware ethernet 00:07:0E:36:48:19;
+ fixed-address 10.5.5.2;
+}
+
+host test3 {
+ hardware fddi 00:07:0E:36:48:19;
+ fixed-address 10.10.10.1;
+}
+
+subnet 10.10.10.0 netmask 255.255.255.224 { }
diff --git a/keama/tests/hostidentifier4.outl b/keama/tests/hostidentifier4.outl
new file mode 100644
index 00000000..24af2ac5
--- /dev/null
+++ b/keama/tests/hostidentifier4.outl
@@ -0,0 +1,78 @@
+{
+ # host declaration with flexible identifiers config
+ # subnet4 declaration
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "subnet4": [
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ "pools": [
+ {
+ "pool": "10.5.5.5 - 10.5.5.10"
+ }
+ ]
+ },
+ {
+ "id": 2,
+ "subnet": "10.10.10.0/27"
+ }
+ ],
+ "option-def": [
+ # option definition
+ {
+ "space": "dhcp4",
+ "name": "my-id",
+ "code": 250,
+ "type": "string"
+ }
+ ],
+ "host-reservation-identifiers": [
+ "flex-id",
+ "hw-address"
+ ],
+ /// The flexible host identifier is a premium feature
+ "hooks-libraries": [
+ {
+ "library": "/path/libdhcp_flex_id.so",
+ "parameters": {
+ "identifier-expression": "option[250].hex"
+ }
+ }
+ ],
+ "reservation-mode": "global",
+ "reservations": [
+ # host declarations
+ {
+ "hostname": "test1",
+ "flex-id": "'test1'",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+// /// default-valid-lifetime in unsupported scope
+// "valid-lifetime": 1800,
+ "ip-address": "10.5.5.1"
+// "extra-ip-addresses": [
+// "10.10.10.10"
+// ]
+ },
+ {
+ "hostname": "test2",
+ "hw-address": "00:07:0e:36:48:19",
+ "ip-address": "10.5.5.2"
+ }
+// {
+// "hostname": "test3",
+// "hw-address": "fddi 00:07:0e:36:48:19",
+// "ip-address": "10.10.10.1"
+// }
+ ]
+ }
+}
diff --git a/keama/tests/hostinclass.err b/keama/tests/hostinclass.err
new file mode 100644
index 00000000..ac5075ee
--- /dev/null
+++ b/keama/tests/hostinclass.err
@@ -0,0 +1,10 @@
+# host declaration inside class declaration config
+
+# class declaration
+class "foobar" {
+ # can't put a host declaration here
+ host illegal {
+ hardware ethernet 00:07:0E:36:48:19;
+ }
+}
+
diff --git a/keama/tests/hostinclass.msg b/keama/tests/hostinclass.msg
new file mode 100644
index 00000000..004446ba
--- /dev/null
+++ b/keama/tests/hostinclass.msg
@@ -0,0 +1 @@
+hostinclass.err line 6: host declarations not allowed here.
diff --git a/keama/tests/hostinhost.err b/keama/tests/hostinhost.err
new file mode 100644
index 00000000..8da7d425
--- /dev/null
+++ b/keama/tests/hostinhost.err
@@ -0,0 +1,11 @@
+# host declaration inside host declaration config
+
+# host declaration
+host foobar {
+ hardware ethernet 00:0B:FD:32:E6:FA;
+ # can't put another host declaration here
+ host illegal {
+ hardware ethernet 00:07:0E:36:48:19;
+ }
+}
+
diff --git a/keama/tests/hostinhost.msg b/keama/tests/hostinhost.msg
new file mode 100644
index 00000000..3ddfbd7a
--- /dev/null
+++ b/keama/tests/hostinhost.msg
@@ -0,0 +1 @@
+hostinhost.err line 7: host declarations not allowed here.
diff --git a/keama/tests/hostname4.in4 b/keama/tests/hostname4.in4
new file mode 100644
index 00000000..8e2db319
--- /dev/null
+++ b/keama/tests/hostname4.in4
@@ -0,0 +1,18 @@
+# host name config
+
+# subnet4 declaration
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ range 10.5.5.5 10.5.5.10;
+}
+
+# host declaration
+host test1 {
+ hardware ethernet 00:0B:FD:32:E6:FA;
+ fixed-address 10.5.5.1;
+}
+
+# host declaration using a longer name
+host test2.example.com {
+ hardware ethernet 00:07:0E:36:48:19;
+ fixed-address 10.5.5.2;
+}
diff --git a/keama/tests/hostname4.out b/keama/tests/hostname4.out
new file mode 100644
index 00000000..d6973a48
--- /dev/null
+++ b/keama/tests/hostname4.out
@@ -0,0 +1,37 @@
+{
+ # host name config
+ # subnet4 declaration
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "subnet4": [
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ "pools": [
+ {
+ "pool": "10.5.5.5 - 10.5.5.10"
+ }
+ ]
+ }
+ ],
+ "host-reservation-identifiers": [
+ "hw-address"
+ ],
+ "reservation-mode": "global",
+ "reservations": [
+ # host declaration
+ {
+ "hostname": "test1",
+ "hw-address": "00:0b:fd:32:e6:fa",
+ "ip-address": "10.5.5.1"
+ },
+ # host declaration using a longer name
+ {
+ "hostname": "test2.example.com",
+ "hw-address": "00:07:0e:36:48:19",
+ "ip-address": "10.5.5.2"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/hostnum.errF b/keama/tests/hostnum.errF
new file mode 100644
index 00000000..4c89dc27
--- /dev/null
+++ b/keama/tests/hostnum.errF
@@ -0,0 +1,7 @@
+# numeric with address hostname config
+
+host 1901.fr {
+ hardware ethernet 00:07:0E:36:48:19;
+ fixed-address 1901.fr;
+}
+
diff --git a/keama/tests/hostnum.msg b/keama/tests/hostnum.msg
new file mode 100644
index 00000000..cc734bc0
--- /dev/null
+++ b/keama/tests/hostnum.msg
@@ -0,0 +1 @@
+hostnum.errF line 5: expected IPv4 address. got hostname 1901.fr
diff --git a/keama/tests/hostuid4.inn b/keama/tests/hostuid4.inn
new file mode 100644
index 00000000..867fa8b4
--- /dev/null
+++ b/keama/tests/hostuid4.inn
@@ -0,0 +1,29 @@
+# host declaration with client-identfiers config
+
+# subnet4 declaration
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ range 10.5.5.5 10.5.5.10;
+}
+
+# recommended when using dhcp-client-identifier options
+ignore-client-uids false;
+
+# host declarations
+host test1 {
+ uid 01:02:03:04:05:0a:0b:0c:0d:0e:0f;
+ option domain-search "example.com", "example.org";
+ default-lease-time 1800;
+ fixed-address 10.5.5.1, 10.10.10.10;
+}
+
+host test2 {
+ hardware ethernet 00:07:0E:36:48:19;
+ fixed-address 10.5.5.2;
+}
+
+host test3 {
+ hardware fddi 00:07:0E:36:48:19;
+ fixed-address 10.10.10.1;
+}
+
+subnet 10.10.10.0 netmask 255.255.255.224 { }
diff --git a/keama/tests/hostuid4.out b/keama/tests/hostuid4.out
new file mode 100644
index 00000000..ad3f3e36
--- /dev/null
+++ b/keama/tests/hostuid4.out
@@ -0,0 +1,62 @@
+{
+ # host declaration with client-identfiers config
+ # subnet4 declaration
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "subnet4": [
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ "pools": [
+ {
+ "pool": "10.5.5.5 - 10.5.5.10"
+ }
+ ],
+ "reservations": [
+ # host declarations
+ {
+ "hostname": "test1",
+ "client-id": "01:02:03:04:05:0a:0b:0c:0d:0e:0f",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+// /// default-valid-lifetime in unsupported scope
+// "valid-lifetime": 1800,
+ "ip-address": "10.5.5.1"
+// "extra-ip-addresses": [
+// "10.10.10.10"
+// ]
+ },
+ {
+ "hostname": "test2",
+ "hw-address": "00:07:0e:36:48:19",
+ "ip-address": "10.5.5.2"
+ }
+ ]
+ },
+ {
+ "id": 2,
+ "subnet": "10.10.10.0/27",
+ "reservations": [
+// {
+// "hostname": "test3",
+// "hw-address": "fddi 00:07:0e:36:48:19",
+// "ip-address": "10.10.10.1"
+// }
+ ]
+ }
+ ],
+ "match-client-id": true,
+ "host-reservation-identifiers": [
+ "client-id",
+ "hw-address"
+ ]
+ }
+}
diff --git a/keama/tests/ifxsc4.in4 b/keama/tests/ifxsc4.in4
new file mode 100644
index 00000000..b3a59bcc
--- /dev/null
+++ b/keama/tests/ifxsc4.in4
@@ -0,0 +1,17 @@
+# if executable statement construct
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# if statement
+# first true is not recognized even as a boolean expression
+if true {
+ option ip-forwarding true;
+}
+
+# another
+if ( option user-class = "accounting" ) {
+ option boot-size 100000;
+} elsif option user-class = "engineering" {
+ option domain-name "example.com";
+}
diff --git a/keama/tests/ifxsc4.out b/keama/tests/ifxsc4.out
new file mode 100644
index 00000000..ca086910
--- /dev/null
+++ b/keama/tests/ifxsc4.out
@@ -0,0 +1,79 @@
+{
+ # if executable statement construct
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800
+// # if statement
+// # first true is not recognized even as a boolean expression
+// "statement": {
+// "if": {
+// "condition": {
+// "variable-reference": "true"
+// },
+// "then": [
+// {
+// "option": {
+// "space": "dhcp4",
+// "name": "ip-forwarding",
+// "code": 19,
+// "data": "true"
+// }
+// }
+// ]
+// }
+// }
+// # another
+// "statement": {
+// "if": {
+// "condition": {
+// "equal": {
+// "left": {
+// "option": {
+// "universe": "dhcp",
+// "name": "user-class",
+// "code": 77
+// }
+// },
+// "right": "accounting"
+// }
+// },
+// "then": [
+// {
+// "option": {
+// "space": "dhcp4",
+// "name": "boot-size",
+// "code": 13,
+// "data": "100000"
+// }
+// }
+// ],
+// "else": {
+// "if": {
+// "condition": {
+// "equal": {
+// "left": {
+// "option": {
+// "universe": "dhcp",
+// "name": "user-class",
+// "code": 77
+// }
+// },
+// "right": "engineering"
+// }
+// },
+// "then": [
+// {
+// "option": {
+// "space": "dhcp4",
+// "name": "domain-name",
+// "code": 15,
+// "data": "example.com"
+// }
+// }
+// ]
+// }
+// }
+// }
+// }
+ }
+}
diff --git a/keama/tests/ipaddr6.in6 b/keama/tests/ipaddr6.in6
new file mode 100644
index 00000000..60c27175
--- /dev/null
+++ b/keama/tests/ipaddr6.in6
@@ -0,0 +1,3 @@
+# IPv6 addresses config
+
+option dhcp6.name-servers 2001::, 200a::0bF, 2001::192.168.0.1;
diff --git a/keama/tests/ipaddr6.out b/keama/tests/ipaddr6.out
new file mode 100644
index 00000000..b2118c61
--- /dev/null
+++ b/keama/tests/ipaddr6.out
@@ -0,0 +1,14 @@
+{
+ # IPv6 addresses config
+ "Dhcp6": {
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "dns-servers",
+ "code": 23,
+// "original-data": "2001::, 200a::0bF, 2001::192.168.0.1",
+ "data": "2001::, 200a::bf, 2001::c0a8:1"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/ipaddrhost4.in4 b/keama/tests/ipaddrhost4.in4
new file mode 100644
index 00000000..1c43f81b
--- /dev/null
+++ b/keama/tests/ipaddrhost4.in4
@@ -0,0 +1,8 @@
+# hostname config
+
+host test1 {
+ hardware ethernet 00:07:0E:36:48:19;
+ fixed-address www.isc.org;
+}
+
+subnet 149.20.64.0 netmask 255.255.255.128 { }
diff --git a/keama/tests/ipaddrhost4.out b/keama/tests/ipaddrhost4.out
new file mode 100644
index 00000000..22a36643
--- /dev/null
+++ b/keama/tests/ipaddrhost4.out
@@ -0,0 +1,24 @@
+{
+ # hostname config
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "host-reservation-identifiers": [
+ "hw-address"
+ ],
+ "reservation-mode": "global",
+ "reservations": [
+ {
+ "hostname": "test1",
+ "hw-address": "00:07:0e:36:48:19",
+ "ip-address": "151.101.122.217"
+ }
+ ],
+ "subnet4": [
+ {
+ "id": 1,
+ "subnet": "149.20.64.0/25"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/ipaddrs4.notyet4 b/keama/tests/ipaddrs4.notyet4
new file mode 100644
index 00000000..6430e0f9
--- /dev/null
+++ b/keama/tests/ipaddrs4.notyet4
@@ -0,0 +1,11 @@
+# hostname config
+
+# authoritative is mandatory
+authoritative;
+
+host test1 {
+ hardware ethernet 00:07:0E:36:48:19;
+ # www.apple.fr has multiple addresses but they are not returned
+ # in a stable order
+ fixed-address www.apple.fr;
+}
diff --git a/keama/tests/lifetime4.ind b/keama/tests/lifetime4.ind
new file mode 100644
index 00000000..4e8594ab
--- /dev/null
+++ b/keama/tests/lifetime4.ind
@@ -0,0 +1,5 @@
+# DHCPv4 use ISC DHCP default lifetimes but not when configured.
+
+min-lease-time 3600;
+default-lease-time 7200;
+max-lease-time 14400;
diff --git a/keama/tests/lifetime4.out b/keama/tests/lifetime4.out
new file mode 100644
index 00000000..e5360af0
--- /dev/null
+++ b/keama/tests/lifetime4.out
@@ -0,0 +1,8 @@
+{
+ # DHCPv4 use ISC DHCP default lifetimes but not when configured.
+ "Dhcp4": {
+ "min-valid-lifetime": 3600,
+ "valid-lifetime": 7200,
+ "max-valid-lifetime": 14400
+ }
+}
diff --git a/keama/tests/lifetime6.inD b/keama/tests/lifetime6.inD
new file mode 100644
index 00000000..4e8594ab
--- /dev/null
+++ b/keama/tests/lifetime6.inD
@@ -0,0 +1,5 @@
+# DHCPv4 use ISC DHCP default lifetimes but not when configured.
+
+min-lease-time 3600;
+default-lease-time 7200;
+max-lease-time 14400;
diff --git a/keama/tests/lifetime6.out b/keama/tests/lifetime6.out
new file mode 100644
index 00000000..5cc28de0
--- /dev/null
+++ b/keama/tests/lifetime6.out
@@ -0,0 +1,8 @@
+{
+ # DHCPv4 use ISC DHCP default lifetimes but not when configured.
+ "Dhcp6": {
+ "min-valid-lifetime": 3600,
+ "valid-lifetime": 7200,
+ "max-valid-lifetime": 14400
+ }
+}
diff --git a/keama/tests/lifetimedef4.ind b/keama/tests/lifetimedef4.ind
new file mode 100644
index 00000000..881edf57
--- /dev/null
+++ b/keama/tests/lifetimedef4.ind
@@ -0,0 +1 @@
+# DHCPv4 use ISC DHCP default lifetimes
diff --git a/keama/tests/lifetimedef4.out b/keama/tests/lifetimedef4.out
new file mode 100644
index 00000000..1e194bf4
--- /dev/null
+++ b/keama/tests/lifetimedef4.out
@@ -0,0 +1,11 @@
+{
+ # DHCPv4 use ISC DHCP default lifetimes
+ "Dhcp4": {
+ /// Use ISC DHCP default lifetime
+ "valid-lifetime": 43200,
+ /// Use ISC DHCP min lifetime
+ "min-valid-lifetime": 300,
+ /// Use ISC DHCP max lifetime
+ "max-valid-lifetime": 86400
+ }
+}
diff --git a/keama/tests/lifetimedef6.inD b/keama/tests/lifetimedef6.inD
new file mode 100644
index 00000000..59718e40
--- /dev/null
+++ b/keama/tests/lifetimedef6.inD
@@ -0,0 +1 @@
+# DHCPv6 use ISC DHCP default lifetimes
diff --git a/keama/tests/lifetimedef6.out b/keama/tests/lifetimedef6.out
new file mode 100644
index 00000000..274997d1
--- /dev/null
+++ b/keama/tests/lifetimedef6.out
@@ -0,0 +1,11 @@
+{
+ # DHCPv6 use ISC DHCP default lifetimes
+ "Dhcp6": {
+ /// Use ISC DHCP default lifetime
+ "valid-lifetime": 43200,
+ /// Use ISC DHCP min lifetime
+ "min-valid-lifetime": 300,
+ /// Use ISC DHCP max lifetime
+ "max-valid-lifetime": 86400
+ }
+}
diff --git a/keama/tests/listarray.err b/keama/tests/listarray.err
new file mode 100644
index 00000000..14082605
--- /dev/null
+++ b/keama/tests/listarray.err
@@ -0,0 +1,7 @@
+# option definition config
+
+# options
+option space foobar;
+
+# arrays of domain lists are forbidden
+option foobar.array-list code 1 = array of domain-list;
diff --git a/keama/tests/listarray.msg b/keama/tests/listarray.msg
new file mode 100644
index 00000000..2a363531
--- /dev/null
+++ b/keama/tests/listarray.msg
@@ -0,0 +1 @@
+listarray.err line 7: arrays of text strings not yet supported.
diff --git a/keama/tests/minimal4.in4 b/keama/tests/minimal4.in4
new file mode 100644
index 00000000..cfcc1282
--- /dev/null
+++ b/keama/tests/minimal4.in4
@@ -0,0 +1,4 @@
+# DHCPv4 minimal config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
diff --git a/keama/tests/minimal4.out b/keama/tests/minimal4.out
new file mode 100644
index 00000000..e256972c
--- /dev/null
+++ b/keama/tests/minimal4.out
@@ -0,0 +1,7 @@
+{
+ # DHCPv4 minimal config
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800
+ }
+}
diff --git a/keama/tests/minimal6.in6 b/keama/tests/minimal6.in6
new file mode 100644
index 00000000..57c83491
--- /dev/null
+++ b/keama/tests/minimal6.in6
@@ -0,0 +1,4 @@
+# DHCPv6 minimal config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
diff --git a/keama/tests/minimal6.out b/keama/tests/minimal6.out
new file mode 100644
index 00000000..c07d37fd
--- /dev/null
+++ b/keama/tests/minimal6.out
@@ -0,0 +1,7 @@
+{
+ # DHCPv6 minimal config
+ # empty configs are not accepted by Kea
+ "Dhcp6": {
+ "valid-lifetime": 1800
+ }
+}
diff --git a/keama/tests/mixedarray.err b/keama/tests/mixedarray.err
new file mode 100644
index 00000000..507aca78
--- /dev/null
+++ b/keama/tests/mixedarray.err
@@ -0,0 +1,7 @@
+# option definition config
+
+# options
+option space foobar;
+
+# not uniform arrays are forbidden
+option foobar.nestarray code 1 = { array of { unsigned integer 32 } };
diff --git a/keama/tests/mixedarray.msg b/keama/tests/mixedarray.msg
new file mode 100644
index 00000000..c409ad01
--- /dev/null
+++ b/keama/tests/mixedarray.msg
@@ -0,0 +1 @@
+mixedarray.err line 7: only uniform array inside record.
diff --git a/keama/tests/nestarray.err b/keama/tests/nestarray.err
new file mode 100644
index 00000000..58f16bbe
--- /dev/null
+++ b/keama/tests/nestarray.err
@@ -0,0 +1,7 @@
+# option definition config
+
+# options
+option space foobar;
+
+# nested arrays are forbidden
+option foobar.nestarray code 1 = array of array of unsigned integer 32;
diff --git a/keama/tests/nestarray.msg b/keama/tests/nestarray.msg
new file mode 100644
index 00000000..92335fad
--- /dev/null
+++ b/keama/tests/nestarray.msg
@@ -0,0 +1 @@
+nestarray.err line 7: no nested arrays.
diff --git a/keama/tests/noauth4.in4 b/keama/tests/noauth4.in4
new file mode 100644
index 00000000..e2fce24c
--- /dev/null
+++ b/keama/tests/noauth4.in4
@@ -0,0 +1,7 @@
+# no(t) authoritative config
+
+# authoritative is no longer mandatory
+#authoritative;
+
+subnet 10.5.5.0 netmask 255.255.255.224 { }
+
diff --git a/keama/tests/noauth4.out b/keama/tests/noauth4.out
new file mode 100644
index 00000000..8dac17f7
--- /dev/null
+++ b/keama/tests/noauth4.out
@@ -0,0 +1,15 @@
+{
+ # no(t) authoritative config
+ # authoritative is no longer mandatory
+ #authoritative;
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "subnet4": [
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/noauth6.in6 b/keama/tests/noauth6.in6
new file mode 100644
index 00000000..42894e91
--- /dev/null
+++ b/keama/tests/noauth6.in6
@@ -0,0 +1,6 @@
+# no(t) authoritative config
+
+# authoritative is no longer mandatory
+#authoritative;
+
+subnet6 2001::/64 { }
diff --git a/keama/tests/noauth6.out b/keama/tests/noauth6.out
new file mode 100644
index 00000000..93f8429e
--- /dev/null
+++ b/keama/tests/noauth6.out
@@ -0,0 +1,15 @@
+{
+ # no(t) authoritative config
+ # authoritative is no longer mandatory
+ #authoritative;
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp6": {
+ "subnet6": [
+ {
+ "id": 1,
+ "subnet": "2001::/64"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/noclass.err b/keama/tests/noclass.err
new file mode 100644
index 00000000..8aed2e34
--- /dev/null
+++ b/keama/tests/noclass.err
@@ -0,0 +1,7 @@
+# orphan subclass declaration config
+
+# class declaration
+subclass "foobar" "abcd {
+ default-lease-time 1800;
+}
+
diff --git a/keama/tests/noclass.msg b/keama/tests/noclass.msg
new file mode 100644
index 00000000..ab8719e3
--- /dev/null
+++ b/keama/tests/noclass.msg
@@ -0,0 +1 @@
+noclass.err line 4: no class named foobar
diff --git a/keama/tests/noinclude.err b/keama/tests/noinclude.err
new file mode 100644
index 00000000..f39f388b
--- /dev/null
+++ b/keama/tests/noinclude.err
@@ -0,0 +1,3 @@
+# no file include config
+
+include "do-not-exist";
diff --git a/keama/tests/noinclude.msg b/keama/tests/noinclude.msg
new file mode 100644
index 00000000..91b2284b
--- /dev/null
+++ b/keama/tests/noinclude.msg
@@ -0,0 +1 @@
+noinclude.err line 3: Can't open do-not-exist: No such file or directory
diff --git a/keama/tests/nosubclass.err b/keama/tests/nosubclass.err
new file mode 100644
index 00000000..cf421259
--- /dev/null
+++ b/keama/tests/nosubclass.err
@@ -0,0 +1,11 @@
+# bad (missing selector) subclass declaration config
+
+# superclass declaration
+class "foobar" {
+ match substring(option vendor-class-identifier, 0, 3);
+}
+
+# subclass declaration
+subclass "foobar" {
+ default-lease-time 1800;
+}
diff --git a/keama/tests/nosubclass.msg b/keama/tests/nosubclass.msg
new file mode 100644
index 00000000..762e440f
--- /dev/null
+++ b/keama/tests/nosubclass.msg
@@ -0,0 +1 @@
+nosubclass.err line 9: Expecting string or hex list.
diff --git a/keama/tests/nosuperclass.err b/keama/tests/nosuperclass.err
new file mode 100644
index 00000000..5d2e329f
--- /dev/null
+++ b/keama/tests/nosuperclass.err
@@ -0,0 +1,11 @@
+# bas superclass subclass declaration config
+
+# class (but not superclass) declaration
+class "foobar" {
+ match if substring(option vendor-class-identifier, 0, 3) = "APC";
+}
+
+# subclass declaration
+subclass "foobar" "abcd {
+ default-lease-time 1800;
+}
diff --git a/keama/tests/nosuperclass.msg b/keama/tests/nosuperclass.msg
new file mode 100644
index 00000000..ec448cad
--- /dev/null
+++ b/keama/tests/nosuperclass.msg
@@ -0,0 +1 @@
+nosuperclass.err line 9: found class name foobar but it is not a suitable superclass
diff --git a/keama/tests/notbx4.in4 b/keama/tests/notbx4.in4
new file mode 100644
index 00000000..6248b8c6
--- /dev/null
+++ b/keama/tests/notbx4.in4
@@ -0,0 +1,12 @@
+# not boolean expression
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# use not in a reductible match if
+class "reductible" {
+ match if not (option host-name = "www.example.com");
+}
+
+# if test is a boolean too
+if not check "foo" { add "bar"; }
diff --git a/keama/tests/notbx4.out b/keama/tests/notbx4.out
new file mode 100644
index 00000000..186cdfff
--- /dev/null
+++ b/keama/tests/notbx4.out
@@ -0,0 +1,30 @@
+{
+ # not boolean expression
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "client-classes": [
+ # use not in a reductible match if
+ {
+ "name": "reductible",
+ /// from: match if not (option dhcp.host-name) = 'www.example.com'
+ "test": "not (option[12].hex == 'www.example.com')"
+ }
+ ]
+// # if test is a boolean too
+// "statement": {
+// "if": {
+// "condition": {
+// "not": {
+// "check": "foo"
+// }
+// },
+// "then": [
+// {
+// "add-class": "bar"
+// }
+// ]
+// }
+// }
+ }
+}
diff --git a/keama/tests/notnotbx4.in4 b/keama/tests/notnotbx4.in4
new file mode 100644
index 00000000..e9d98800
--- /dev/null
+++ b/keama/tests/notnotbx4.in4
@@ -0,0 +1,15 @@
+# double not boolean expression
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# use double not in a reductible match if
+class "reductible" {
+ match if not (not ((option host-name = "www.example.com") or
+ (option host-name = "www.example.org")));
+}
+
+# use not with !=
+class "other" {
+ match if not (option host-name != "www.example.com");
+}
diff --git a/keama/tests/notnotbx4.out b/keama/tests/notnotbx4.out
new file mode 100644
index 00000000..ae1878cc
--- /dev/null
+++ b/keama/tests/notnotbx4.out
@@ -0,0 +1,21 @@
+{
+ # double not boolean expression
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "client-classes": [
+ # use double not in a reductible match if
+ {
+ "name": "reductible",
+ /// from: match if not not ((option dhcp.host-name) = 'www.example.com') or ((option dhcp.host-name) = 'www.example.org')
+ "test": "(option[12].hex == 'www.example.com') or (option[12].hex == 'www.example.org')"
+ },
+ # use not with !=
+ {
+ "name": "other",
+ /// from: match if not (option dhcp.host-name) != 'www.example.com'
+ "test": "option[12].hex == 'www.example.com'"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/nxdomainnx6.in6 b/keama/tests/nxdomainnx6.in6
new file mode 100644
index 00000000..f2a2e0f5
--- /dev/null
+++ b/keama/tests/nxdomainnx6.in6
@@ -0,0 +1,12 @@
+# nxdomain numeric expression
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# reduce literals
+class "literal" {
+ match if substring(option dhcp6.interface-id, 0, nxdomain % 128) = ab:cd;
+}
+
+# raw
+option dhcp6.interface-id = encode-int(nxdomain + bound, 32);
diff --git a/keama/tests/nxdomainnx6.out b/keama/tests/nxdomainnx6.out
new file mode 100644
index 00000000..c90552e4
--- /dev/null
+++ b/keama/tests/nxdomainnx6.out
@@ -0,0 +1,28 @@
+{
+ # nxdomain numeric expression
+ # empty configs are not accepted by Kea
+ "Dhcp6": {
+ "valid-lifetime": 1800,
+ "client-classes": [
+ # reduce literals
+ {
+ "name": "literal",
+ /// from: match if (substring(option dhcp6.interface-id, 0, 393231 % 128)) = 0xabcd
+ "test": "substring(option[18].hex,0,15) == 0xabcd"
+ }
+ ],
+ "option-data": [
+ # raw
+ {
+ "space": "dhcp6",
+ "name": "interface-id",
+ "code": 18,
+ "csv-format": false,
+// /// constant DHCP_R_NXDOMAIN(393231)
+// /// constant S_BOUND(5)
+// "original-data": "\u0000\u0006\u0000\u0014",
+ "data": "00060014"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/onxsc4.in4 b/keama/tests/onxsc4.in4
new file mode 100644
index 00000000..f62b720c
--- /dev/null
+++ b/keama/tests/onxsc4.in4
@@ -0,0 +1,12 @@
+# on executable statement construct
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# empty on statement
+on expiry;
+
+# another one
+on commit or release {
+ execute ("myscript", packet(5, 2));
+}
diff --git a/keama/tests/onxsc4.out b/keama/tests/onxsc4.out
new file mode 100644
index 00000000..95134a52
--- /dev/null
+++ b/keama/tests/onxsc4.out
@@ -0,0 +1,34 @@
+{
+ # on executable statement construct
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800
+// # empty on statement
+// "statement": {
+// "on": {
+// "condition": "expiry"
+// }
+// }
+// # another one
+// "statement": {
+// "on": {
+// "condition": "commit or release",
+// "body": [
+// {
+// "execute": {
+// "command": "myscript",
+// "arguments": [
+// {
+// "packet": {
+// "offset": 5,
+// "length": 2
+// }
+// }
+// ]
+// }
+// }
+// ]
+// }
+// }
+ }
+}
diff --git a/keama/tests/optdatagrouppool4.in4 b/keama/tests/optdatagrouppool4.in4
new file mode 100644
index 00000000..999abea2
--- /dev/null
+++ b/keama/tests/optdatagrouppool4.in4
@@ -0,0 +1,18 @@
+# embedded option-data in DHCPv4 pool config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# subnet declaration
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ # pool declaration
+ pool {
+ # avoid empty pool
+ range 10.5.5.5 10.5.5.10;
+ # for a silly reason option-data is not allowed in DHCPv4 pools
+ # try to fool this rule using a group
+ group fool {
+ option domain-search "example.com", "example.org";
+ }
+ }
+}
diff --git a/keama/tests/optdatagrouppool4.out b/keama/tests/optdatagrouppool4.out
new file mode 100644
index 00000000..4fb102d3
--- /dev/null
+++ b/keama/tests/optdatagrouppool4.out
@@ -0,0 +1,23 @@
+{
+ # embedded option-data in DHCPv4 pool config
+ # empty configs are not accepted by Kea
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "subnet4": [
+ # subnet declaration
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ "pools": [
+ # pool declaration
+ {
+ # avoid empty pool
+ "pool": "10.5.5.5 - 10.5.5.10"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/optiondata4.in4 b/keama/tests/optiondata4.in4
new file mode 100644
index 00000000..6c5ab639
--- /dev/null
+++ b/keama/tests/optiondata4.in4
@@ -0,0 +1,48 @@
+# option data config
+
+# options
+option space foobar;
+
+option ip-forwarding on;
+
+option foobar.fmt-b-si8 code 3 = signed integer 8;
+option foobar.fmt-b-si8 -100;
+
+option default-ip-ttl 20;
+
+option foobar.fmt-s-si16 code 6 = signed integer 16;
+option foobar.fmt-s-si16 -1000;
+
+option boot-size 16000;
+
+option time-offset -1200;
+
+option path-mtu-aging-timeout 86400;
+
+option swap-server 10.5.5.1;
+
+option foobar.fmt-6 code 12 = ip6-address;
+option foobar.fmt-6 2001::1;
+
+option foobar.fmt-d code 13 = domain-name;
+# Silly, d aka domain-name are without quotes, D aka domain-list are with
+option foobar.fmt-d www.example.com;
+
+option bcms-controller-names "foo.bar", "www.no-where.biz";
+
+option domain-search "example.com", "example.org";
+
+option tftp-server-name "my-server";
+
+option dhcp-client-identifier 01:02:aa:bb;
+
+option foobar.fmt-Z code 18 = zerolen;
+option foobar.fmt-Z;
+
+option foobar.fmt-Ba code 50 = array of unsigned integer 8;
+option dhcp-parameter-request-list 1, 2, 3;
+
+option foobar.fmt-fB code 100 = { boolean, unsigned integer 8 };
+option foobar.fmt-fB off 66;
+
+option routers 10.5.5.1, 10.5.5.2, 10.5.5.3;
diff --git a/keama/tests/optiondata4.out b/keama/tests/optiondata4.out
new file mode 100644
index 00000000..eca745d9
--- /dev/null
+++ b/keama/tests/optiondata4.out
@@ -0,0 +1,173 @@
+{
+ # option data config
+ # options
+ "Dhcp4": {
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "ip-forwarding",
+ "code": 19,
+// "original-data": "on",
+ /// canonized booleans to lowercase true or false
+ "data": "true"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-b-si8",
+ "code": 3,
+ "data": "-100"
+ },
+ {
+ "space": "dhcp4",
+ "name": "default-ip-ttl",
+ "code": 23,
+ "data": "20"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-s-si16",
+ "code": 6,
+ "data": "-1000"
+ },
+ {
+ "space": "dhcp4",
+ "name": "boot-size",
+ "code": 13,
+ "data": "16000"
+ },
+ {
+ "space": "dhcp4",
+ "name": "time-offset",
+ "code": 2,
+ "data": "-1200"
+ },
+ {
+ "space": "dhcp4",
+ "name": "path-mtu-aging-timeout",
+ "code": 24,
+ "data": "86400"
+ },
+ {
+ "space": "dhcp4",
+ "name": "swap-server",
+ "code": 16,
+ "data": "10.5.5.1"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-6",
+ "code": 12,
+ "data": "2001::1"
+ },
+ # Silly, d aka domain-name are without quotes, D aka domain-list are with
+ {
+ "space": "foobar",
+ "name": "fmt-d",
+ "code": 13,
+ "data": "www.example.com"
+ },
+ {
+ "space": "dhcp4",
+ "name": "bcms-controller-names",
+ "code": 88,
+// "original-data": "\"foo.bar\", \"www.no-where.biz\"",
+ "data": "foo.bar, www.no-where.biz"
+ },
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ },
+ {
+ "space": "dhcp4",
+ "name": "tftp-server-name",
+ "code": 66,
+ "data": "my-server"
+ },
+ {
+ "space": "dhcp4",
+ "name": "dhcp-client-identifier",
+ "code": 61,
+// "original-data": "01:02:aa:bb",
+ "csv-format": false,
+ "data": "0102aabb"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-Z",
+ "code": 18,
+ "data": ""
+ },
+ /// Possible PRL hack
+ /// Consider setting "always-send" to true when setting data for relevant options, cf Kea #250
+ {
+ "space": "dhcp4",
+ "name": "dhcp-parameter-request-list",
+ "code": 55,
+ "data": "1, 2, 3"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-fB",
+ "code": 100,
+// "original-data": "off 66",
+ /// canonized booleans to lowercase true or false
+ "data": "false, 66"
+ },
+ {
+ "space": "dhcp4",
+ "name": "routers",
+ "code": 3,
+ "data": "10.5.5.1, 10.5.5.2, 10.5.5.3"
+ }
+ ],
+ "option-def": [
+ {
+ "space": "foobar",
+ "name": "fmt-b-si8",
+ "code": 3,
+ "type": "int8"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-s-si16",
+ "code": 6,
+ "type": "int16"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-6",
+ "code": 12,
+ "type": "ipv6-address"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-d",
+ "code": 13,
+ "type": "fqdn"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-Z",
+ "code": 18,
+ "type": "empty"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-Ba",
+ "code": 50,
+ "array": true,
+ "type": "uint8"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-fB",
+ "code": 100,
+ "record-types": "boolean, uint8",
+ "type": "record"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/optiondata6.in6 b/keama/tests/optiondata6.in6
new file mode 100644
index 00000000..5af5b87b
--- /dev/null
+++ b/keama/tests/optiondata6.in6
@@ -0,0 +1,49 @@
+# option data config
+
+# options
+option space foobar;
+
+option foobar.fmt-f code 1 = boolean;
+option foobar.fmt-f true;
+
+option foobar.fmt-b-i8 code 2 = integer 8;
+option foobar.fmt-b-i8 -10;
+
+option dhcp6.preference 20;
+
+option foobar.fmt-s-i16 code 5 = integer 16;
+option foobar.fmt-s-i16 -10000;
+
+option foobar.fmt-S-ui16 code 7 = unsigned integer 16;
+option foobar.fmt-S-ui16 36000;
+
+option foobar.fmt-l-i32 code 8 = integer 32;
+option foobar.fmt-l-i32 -86400;
+
+option dhcp6.clt-time 604800;
+
+option foobar.fmt-I code 11 = ip-address;
+option foobar.fmt-I 10.5.5.1;
+
+option foobar.fmt-6 code 12 = ip6-address;
+option dhcp6.unicast 2001::1;
+
+option foobar.fmt-d code 13 = domain-name;
+# Silly, d aka domain-name are without quotes, D aka domain-list are with
+option foobar.fmt-d www.example.com;
+
+option dhcp6.domain-search "example.com", "example.org";
+
+option dhcp6.bootfile-url "http://nowhere/";
+
+option dhcp6.geoconf-civic de:ad:be:ef;
+
+option dhcp6.rapid-commit;
+
+option foobar.fmt-Ba code 50 = array of unsigned integer 8;
+option foobar.fmt-Ba 1, 2, 3;
+
+option foobar.fmt-fB code 100 = { boolean, unsigned integer 8 };
+option foobar.fmt-fB false 66;
+
+option dhcp6.name-servers 2a01:e00::2, 2a01:e00::2;
diff --git a/keama/tests/optiondata6.out b/keama/tests/optiondata6.out
new file mode 100644
index 00000000..b01ba36b
--- /dev/null
+++ b/keama/tests/optiondata6.out
@@ -0,0 +1,178 @@
+{
+ # option data config
+ # options
+ "Dhcp6": {
+ "option-def": [
+ {
+ "space": "foobar",
+ "name": "fmt-f",
+ "code": 1,
+ "type": "boolean"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-b-i8",
+ "code": 2,
+ "type": "int8"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-s-i16",
+ "code": 5,
+ "type": "int16"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-S-ui16",
+ "code": 7,
+ "type": "uint16"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-l-i32",
+ "code": 8,
+ "type": "int32"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-I",
+ "code": 11,
+ "type": "ipv4-address"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-6",
+ "code": 12,
+ "type": "ipv6-address"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-d",
+ "code": 13,
+ "type": "fqdn"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-Ba",
+ "code": 50,
+ "array": true,
+ "type": "uint8"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-fB",
+ "code": 100,
+ "record-types": "boolean, uint8",
+ "type": "record"
+ }
+ ],
+ "option-data": [
+ {
+ "space": "foobar",
+ "name": "fmt-f",
+ "code": 1,
+ "data": "true"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-b-i8",
+ "code": 2,
+ "data": "-10"
+ },
+ {
+ "space": "dhcp6",
+ "name": "preference",
+ "code": 7,
+ "data": "20"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-s-i16",
+ "code": 5,
+ "data": "-10000"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-S-ui16",
+ "code": 7,
+ "data": "36000"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-l-i32",
+ "code": 8,
+ "data": "-86400"
+ },
+ {
+ "space": "dhcp6",
+ "name": "clt-time",
+ "code": 46,
+ "data": "604800"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-I",
+ "code": 11,
+ "data": "10.5.5.1"
+ },
+ {
+ "space": "dhcp6",
+ "name": "unicast",
+ "code": 12,
+ "data": "2001::1"
+ },
+ # Silly, d aka domain-name are without quotes, D aka domain-list are with
+ {
+ "space": "foobar",
+ "name": "fmt-d",
+ "code": 13,
+ "data": "www.example.com"
+ },
+ {
+ "space": "dhcp6",
+ "name": "domain-search",
+ "code": 24,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ },
+ {
+ "space": "dhcp6",
+ "name": "bootfile-url",
+ "code": 59,
+ "data": "http://nowhere/"
+ },
+ {
+ "space": "dhcp6",
+ "name": "geoconf-civic",
+ "code": 36,
+// "original-data": "de:ad:be:ef",
+ "csv-format": false,
+ "data": "deadbeef"
+ },
+ {
+ "space": "dhcp6",
+ "name": "rapid-commit",
+ "code": 14,
+ "data": ""
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-Ba",
+ "code": 50,
+ "data": "1, 2, 3"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-fB",
+ "code": 100,
+ "data": "false, 66"
+ },
+ {
+ "space": "dhcp6",
+ "name": "dns-servers",
+ "code": 23,
+ "data": "2a01:e00::2, 2a01:e00::2"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/optiondatapool4.in4 b/keama/tests/optiondatapool4.in4
new file mode 100644
index 00000000..58751b01
--- /dev/null
+++ b/keama/tests/optiondatapool4.in4
@@ -0,0 +1,12 @@
+# option-data in DHCPv4 pool config
+
+# subnet declaration
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ # pool declaration
+ pool {
+ # avoid empty pool
+ range 10.5.5.5 10.5.5.10;
+ # fixed now
+ option domain-search "example.com", "example.org";
+ }
+}
diff --git a/keama/tests/optiondatapool4.out b/keama/tests/optiondatapool4.out
new file mode 100644
index 00000000..1d3ee427
--- /dev/null
+++ b/keama/tests/optiondatapool4.out
@@ -0,0 +1,31 @@
+{
+ # option-data in DHCPv4 pool config
+ # subnet declaration
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "subnet4": [
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ "pools": [
+ # pool declaration
+ {
+ # avoid empty pool
+ "pool": "10.5.5.5 - 10.5.5.10",
+ "option-data": [
+ # fixed now
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/optiondatapool6.in6 b/keama/tests/optiondatapool6.in6
new file mode 100644
index 00000000..d09ebaf9
--- /dev/null
+++ b/keama/tests/optiondatapool6.in6
@@ -0,0 +1,13 @@
+# option-data in DHCPv6 pool config
+
+# subnet declaration
+subnet6 2001::/64 {
+ # pool declaration
+ pool6 {
+ # avoid empty pool
+ range6 2001::100 2001::200;
+ # for a silly reason option-data is not allowed in DHCPv4 pools
+ # but allowed in DHCPv6 pools
+ option dhcp6.domain-search "example.com", "example.org";
+ }
+}
diff --git a/keama/tests/optiondatapool6.out b/keama/tests/optiondatapool6.out
new file mode 100644
index 00000000..48f722d4
--- /dev/null
+++ b/keama/tests/optiondatapool6.out
@@ -0,0 +1,32 @@
+{
+ # option-data in DHCPv6 pool config
+ # subnet declaration
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp6": {
+ "subnet6": [
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "pools": [
+ # pool declaration
+ {
+ # avoid empty pool
+ "pool": "2001::100 - 2001::200",
+ "option-data": [
+ # for a silly reason option-data is not allowed in DHCPv4 pools
+ # but allowed in DHCPv6 pools
+ {
+ "space": "dhcp6",
+ "name": "domain-search",
+ "code": 24,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/optiondecl4.in4 b/keama/tests/optiondecl4.in4
new file mode 100644
index 00000000..4be48838
--- /dev/null
+++ b/keama/tests/optiondecl4.in4
@@ -0,0 +1,27 @@
+# option definition config
+
+# options
+option space foobar;
+
+option foobar.fmt-f code 1 = boolean;
+option foobar.fmt-b-i8 code 2 = integer 8;
+option foobar.fmt-b-si8 code 3 = signed integer 8;
+option foobar.fmt-B-ui8 code 4 = unsigned integer 8;
+option foobar.fmt-s-i16 code 5 = integer 16;
+option foobar.fmt-s-si16 code 6 = signed integer 16;
+option foobar.fmt-S-ui16 code 7 = unsigned integer 16;
+option foobar.fmt-l-i32 code 8 = integer 32;
+option foobar.fmt-l-si32 code 9 = signed integer 32;
+option foobar.fmt-L-ui32 code 10 = unsigned integer 32;
+option foobar.fmt-I code 11 = ip-address;
+option foobar.fmt-6 code 12 = ip6-address;
+option foobar.fmt-d code 13 = domain-name;
+option foobar.fmt-D-list code 14 = domain-list;
+option foobar.fmt-Dc code 15 = domain-list compressed;
+option foobar.fmt-t code 16 = text;
+option foobar.fmt-X code 17 = string;
+option foobar.fmt-Z code 18 = zerolen;
+
+option foobar.fmt-Ba code 50 = array of unsigned integer 8;
+option foobar.fmt-fB code 100 = { boolean, unsigned integer 8 };
+option foobar.fmt-Ia code 150 = { unsigned integer 32, array of boolean };
diff --git a/keama/tests/optiondecl4.out b/keama/tests/optiondecl4.out
new file mode 100644
index 00000000..f74aa32f
--- /dev/null
+++ b/keama/tests/optiondecl4.out
@@ -0,0 +1,143 @@
+{
+ # option definition config
+ # options
+ "Dhcp4": {
+ "option-def": [
+ {
+ "space": "foobar",
+ "name": "fmt-f",
+ "code": 1,
+ "type": "boolean"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-b-i8",
+ "code": 2,
+ "type": "int8"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-b-si8",
+ "code": 3,
+ "type": "int8"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-B-ui8",
+ "code": 4,
+ "type": "uint8"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-s-i16",
+ "code": 5,
+ "type": "int16"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-s-si16",
+ "code": 6,
+ "type": "int16"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-S-ui16",
+ "code": 7,
+ "type": "uint16"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-l-i32",
+ "code": 8,
+ "type": "int32"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-l-si32",
+ "code": 9,
+ "type": "int32"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-L-ui32",
+ "code": 10,
+ "type": "uint32"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-I",
+ "code": 11,
+ "type": "ipv4-address"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-6",
+ "code": 12,
+ "type": "ipv6-address"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-d",
+ "code": 13,
+ "type": "fqdn"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-D-list",
+ "code": 14,
+ "array": true,
+ "type": "fqdn"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-Dc",
+ "code": 15,
+ "array": true,
+ "type": "fqdn"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-t",
+ "code": 16,
+ "type": "string"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-X",
+ "code": 17,
+ "type": "string"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-Z",
+ "code": 18,
+ "type": "empty"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-Ba",
+ "code": 50,
+ "array": true,
+ "type": "uint8"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-fB",
+ "code": 100,
+ "record-types": "boolean, uint8",
+ "type": "record"
+ },
+ /// unsupported array inside a record
+ {
+ "space": "foobar",
+ "name": "fmt-Ia",
+ "code": 150,
+// "array": true,
+// "definition": "{ uint32, array of boolean}",
+ /// Option definition is not compatible with Kea
+ /// Fallback to full binary
+ "type": "binary"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/optiondecl6.in6 b/keama/tests/optiondecl6.in6
new file mode 100644
index 00000000..37662c0d
--- /dev/null
+++ b/keama/tests/optiondecl6.in6
@@ -0,0 +1,27 @@
+# option definition config
+
+# options
+option space foobar;
+
+option foobar.fmt-f code 1 = boolean;
+option foobar.fmt-b-i8 code 2 = integer 8;
+option foobar.fmt-b-si8 code 3 = signed integer 8;
+option foobar.fmt-B-ui8 code 4 = unsigned integer 8;
+option foobar.fmt-s-i16 code 5 = integer 16;
+option foobar.fmt-s-si16 code 6 = signed integer 16;
+option foobar.fmt-S-ui16 code 7 = unsigned integer 16;
+option foobar.fmt-l-i32 code 8 = integer 32;
+option foobar.fmt-l-si32 code 9 = signed integer 32;
+option foobar.fmt-L-ui32 code 10 = unsigned integer 32;
+option foobar.fmt-I code 11 = ip-address;
+option foobar.fmt-6 code 12 = ip6-address;
+option foobar.fmt-d code 13 = domain-name;
+option foobar.fmt-D-list code 14 = domain-list;
+#option foobar.fmt-Dc code 15 = domain-list compressed;
+option foobar.fmt-t code 16 = text;
+option foobar.fmt-X code 17 = string;
+option foobar.fmt-Z code 18 = zerolen;
+
+option foobar.fmt-Ba code 50 = array of unsigned integer 8;
+option foobar.fmt-fB code 100 = { boolean, unsigned integer 8 };
+option foobar.fmt-Lfa code 150 = { unsigned integer 32, array of boolean };
diff --git a/keama/tests/optiondecl6.out b/keama/tests/optiondecl6.out
new file mode 100644
index 00000000..d4cf828b
--- /dev/null
+++ b/keama/tests/optiondecl6.out
@@ -0,0 +1,137 @@
+{
+ # option definition config
+ # options
+ "Dhcp6": {
+ "option-def": [
+ {
+ "space": "foobar",
+ "name": "fmt-f",
+ "code": 1,
+ "type": "boolean"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-b-i8",
+ "code": 2,
+ "type": "int8"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-b-si8",
+ "code": 3,
+ "type": "int8"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-B-ui8",
+ "code": 4,
+ "type": "uint8"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-s-i16",
+ "code": 5,
+ "type": "int16"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-s-si16",
+ "code": 6,
+ "type": "int16"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-S-ui16",
+ "code": 7,
+ "type": "uint16"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-l-i32",
+ "code": 8,
+ "type": "int32"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-l-si32",
+ "code": 9,
+ "type": "int32"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-L-ui32",
+ "code": 10,
+ "type": "uint32"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-I",
+ "code": 11,
+ "type": "ipv4-address"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-6",
+ "code": 12,
+ "type": "ipv6-address"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-d",
+ "code": 13,
+ "type": "fqdn"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-D-list",
+ "code": 14,
+ "array": true,
+ "type": "fqdn"
+ },
+ #option foobar.fmt-Dc code 15 = domain-list compressed;
+ {
+ "space": "foobar",
+ "name": "fmt-t",
+ "code": 16,
+ "type": "string"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-X",
+ "code": 17,
+ "type": "string"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-Z",
+ "code": 18,
+ "type": "empty"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-Ba",
+ "code": 50,
+ "array": true,
+ "type": "uint8"
+ },
+ {
+ "space": "foobar",
+ "name": "fmt-fB",
+ "code": 100,
+ "record-types": "boolean, uint8",
+ "type": "record"
+ },
+ /// unsupported array inside a record
+ {
+ "space": "foobar",
+ "name": "fmt-Lfa",
+ "code": 150,
+// "array": true,
+// "definition": "{ uint32, array of boolean}",
+ /// Option definition is not compatible with Kea
+ /// Fallback to full binary
+ "type": "binary"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/optiondeclBat4.in4 b/keama/tests/optiondeclBat4.in4
new file mode 100644
index 00000000..0901a14f
--- /dev/null
+++ b/keama/tests/optiondeclBat4.in4
@@ -0,0 +1,6 @@
+# bad Bat (non terminal array) option declaration
+
+option space foobar;
+option foobar.fmt-Bat code 1 = { array of unsigned integer 8, text };
+
+
diff --git a/keama/tests/optiondeclBat4.out b/keama/tests/optiondeclBat4.out
new file mode 100644
index 00000000..e671dcfa
--- /dev/null
+++ b/keama/tests/optiondeclBat4.out
@@ -0,0 +1,18 @@
+{
+ # bad Bat (non terminal array) option declaration
+ "Dhcp4": {
+ "option-def": [
+ /// unsupported array inside a record
+ {
+ "space": "foobar",
+ "name": "fmt-Bat",
+ "code": 1,
+// "array": true,
+// "definition": "{ array of uint8, string}",
+ /// Option definition is not compatible with Kea
+ /// Fallback to full binary
+ "type": "binary"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/optionencap4.in4 b/keama/tests/optionencap4.in4
new file mode 100644
index 00000000..f5dddfdb
--- /dev/null
+++ b/keama/tests/optionencap4.in4
@@ -0,0 +1,7 @@
+# option definition config
+
+# options
+option space foobar;
+option space xyz;
+
+option foobar.encap code 1 = encapsulate xyz;
diff --git a/keama/tests/optionencap4.out b/keama/tests/optionencap4.out
new file mode 100644
index 00000000..ee59fc42
--- /dev/null
+++ b/keama/tests/optionencap4.out
@@ -0,0 +1,15 @@
+{
+ # option definition config
+ # options
+ "Dhcp4": {
+ "option-def": [
+ {
+ "space": "foobar",
+ "name": "encap",
+ "code": 1,
+ "type": "empty",
+ "encapsulate": "xyz"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/optionencap6.in6 b/keama/tests/optionencap6.in6
new file mode 100644
index 00000000..f5dddfdb
--- /dev/null
+++ b/keama/tests/optionencap6.in6
@@ -0,0 +1,7 @@
+# option definition config
+
+# options
+option space foobar;
+option space xyz;
+
+option foobar.encap code 1 = encapsulate xyz;
diff --git a/keama/tests/optionencap6.out b/keama/tests/optionencap6.out
new file mode 100644
index 00000000..733fc714
--- /dev/null
+++ b/keama/tests/optionencap6.out
@@ -0,0 +1,15 @@
+{
+ # option definition config
+ # options
+ "Dhcp6": {
+ "option-def": [
+ {
+ "space": "foobar",
+ "name": "encap",
+ "code": 1,
+ "type": "empty",
+ "encapsulate": "xyz"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/optionexpr4.in4 b/keama/tests/optionexpr4.in4
new file mode 100644
index 00000000..bc0889dd
--- /dev/null
+++ b/keama/tests/optionexpr4.in4
@@ -0,0 +1,16 @@
+# option data expressions
+
+# options
+option mytext code 250 = text;
+option mytext = substring("foobar", 1, 3);
+
+option mydata code 251 = string;
+option mydata = concat("\b", "\125\126");
+
+option mydata2 code 252 = string;
+option mydata2 = aa:bb:cc;
+
+# not yet
+option mydata3 code 253 = string;
+option mydata3 = concat(aa, bb, cc);
+
diff --git a/keama/tests/optionexpr4.out b/keama/tests/optionexpr4.out
new file mode 100644
index 00000000..3582b3d7
--- /dev/null
+++ b/keama/tests/optionexpr4.out
@@ -0,0 +1,81 @@
+{
+ # option data expressions
+ # options
+ "Dhcp4": {
+ "option-def": [
+ {
+ "space": "dhcp4",
+ "name": "mytext",
+ "code": 250,
+ "type": "string"
+ },
+ {
+ "space": "dhcp4",
+ "name": "mydata",
+ "code": 251,
+ "type": "string"
+ },
+ {
+ "space": "dhcp4",
+ "name": "mydata2",
+ "code": 252,
+ "type": "string"
+ },
+ # not yet
+ {
+ "space": "dhcp4",
+ "name": "mydata3",
+ "code": 253,
+ "type": "string"
+ }
+ ],
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "mytext",
+ "code": 250,
+ "csv-format": false,
+// "original-data": "oob",
+ "data": "6f6f62"
+ },
+ {
+ "space": "dhcp4",
+ "name": "mydata",
+ "code": 251,
+ "csv-format": false,
+// "original-data": "\bUV",
+ "data": "085556"
+ },
+ {
+ "space": "dhcp4",
+ "name": "mydata2",
+ "code": 252,
+ "csv-format": false,
+ "data": "aabbcc"
+ }
+// {
+// "space": "dhcp4",
+// "name": "mydata3",
+// "code": 253,
+// "csv-format": false,
+// "expression": {
+// "concat": {
+// "left": {
+// "const-data": "0xaa"
+// },
+// "right": {
+// "concat": {
+// "left": {
+// "const-data": "0xbb"
+// },
+// "right": {
+// "const-data": "0xcc"
+// }
+// }
+// }
+// }
+// }
+// }
+ ]
+ }
+}
diff --git a/keama/tests/optionspace4.in4 b/keama/tests/optionspace4.in4
new file mode 100644
index 00000000..fa87b6a5
--- /dev/null
+++ b/keama/tests/optionspace4.in4
@@ -0,0 +1,15 @@
+# group and class declaration config
+
+# option spaces
+option space foobar;
+
+option space foo code width 1 length width 1;
+
+option space bar code width 4 length width 4;
+
+option space full code width 2 length width 2 hash size 111;
+
+option foobar.test code 1 = text;
+
+option foo.test code 1 = text;
+
diff --git a/keama/tests/optionspace4.out b/keama/tests/optionspace4.out
new file mode 100644
index 00000000..53482692
--- /dev/null
+++ b/keama/tests/optionspace4.out
@@ -0,0 +1,34 @@
+{
+ # group and class declaration config
+ # option spaces
+ "Dhcp4": {
+// "option-space": {
+// "name": "bar",
+// /// Only code width 1 is supported
+// "code-width": 4,
+// /// Only length width 1 is supported
+// "length-width": 4
+// },
+// "option-space": {
+// "name": "full",
+// /// Only code width 1 is supported
+// "code-width": 2,
+// /// Only length width 1 is supported
+// "length-width": 2
+// },
+ "option-def": [
+ {
+ "space": "foobar",
+ "name": "test",
+ "code": 1,
+ "type": "string"
+ },
+ {
+ "space": "foo",
+ "name": "test",
+ "code": 1,
+ "type": "string"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/optionspace6.in6 b/keama/tests/optionspace6.in6
new file mode 100644
index 00000000..a94a665f
--- /dev/null
+++ b/keama/tests/optionspace6.in6
@@ -0,0 +1,15 @@
+# group and class declaration config
+
+# option spaces
+option space foobar;
+
+option space foo code width 2 length width 2;
+
+option space bar code width 4 length width 4;
+
+option space full code width 1 length width 1 hash size 111;
+
+option foobar.test code 1 = text;
+
+option foo.test code 1 = text;
+
diff --git a/keama/tests/optionspace6.out b/keama/tests/optionspace6.out
new file mode 100644
index 00000000..fdc9ea72
--- /dev/null
+++ b/keama/tests/optionspace6.out
@@ -0,0 +1,34 @@
+{
+ # group and class declaration config
+ # option spaces
+ "Dhcp6": {
+// "option-space": {
+// "name": "bar",
+// /// Only code width 2 is supported
+// "code-width": 4,
+// /// Only length width 2 is supported
+// "length-width": 4
+// },
+// "option-space": {
+// "name": "full",
+// /// Only code width 2 is supported
+// "code-width": 1,
+// /// Only length width 2 is supported
+// "length-width": 1
+// },
+ "option-def": [
+ {
+ "space": "foobar",
+ "name": "test",
+ "code": 1,
+ "type": "string"
+ },
+ {
+ "space": "foo",
+ "name": "test",
+ "code": 1,
+ "type": "string"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/optionvendor4.in4 b/keama/tests/optionvendor4.in4
new file mode 100644
index 00000000..49be45d7
--- /dev/null
+++ b/keama/tests/optionvendor4.in4
@@ -0,0 +1,8 @@
+# group and class declaration config
+
+# vendor option space
+option space foobar;
+
+option vendor.foobar code 12345 = encapsulate foobar;
+option foobar.test code 1 = text;
+option foobar.test "a test";
diff --git a/keama/tests/optionvendor4.out b/keama/tests/optionvendor4.out
new file mode 100644
index 00000000..80a9a5e8
--- /dev/null
+++ b/keama/tests/optionvendor4.out
@@ -0,0 +1,28 @@
+{
+ # group and class declaration config
+ # vendor option space
+ "Dhcp4": {
+ "option-def": [
+ {
+ "space": "vendor-12345",
+ "name": "test",
+ "code": 1,
+ "type": "string"
+ }
+ ],
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "vivso-suboptions",
+ "code": 125,
+ "data": "12345"
+ },
+ {
+ "space": "vendor-12345",
+ "name": "test",
+ "code": 1,
+ "data": "a test"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/optionvendor6.in6 b/keama/tests/optionvendor6.in6
new file mode 100644
index 00000000..9a1a3757
--- /dev/null
+++ b/keama/tests/optionvendor6.in6
@@ -0,0 +1,8 @@
+# group and class declaration config
+
+# vendor option space
+option space foobar;
+
+option vsio.foobar code 12345 = encapsulate foobar;
+option foobar.test code 1 = text;
+option foobar.test "a test";
diff --git a/keama/tests/optionvendor6.out b/keama/tests/optionvendor6.out
new file mode 100644
index 00000000..8e163978
--- /dev/null
+++ b/keama/tests/optionvendor6.out
@@ -0,0 +1,28 @@
+{
+ # group and class declaration config
+ # vendor option space
+ "Dhcp6": {
+ "option-def": [
+ {
+ "space": "vendor-12345",
+ "name": "test",
+ "code": 1,
+ "type": "string"
+ }
+ ],
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "vendor-opts",
+ "code": 17,
+ "data": "12345"
+ },
+ {
+ "space": "vendor-12345",
+ "name": "test",
+ "code": 1,
+ "data": "a test"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/orphan4.inn b/keama/tests/orphan4.inn
new file mode 100644
index 00000000..85f95639
--- /dev/null
+++ b/keama/tests/orphan4.inn
@@ -0,0 +1,10 @@
+# DHCPv4 orphan reservation config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# orphan reservation
+host foobar {
+ hardware ethernet 00:0B:FD:32:E6:FA;
+ option ip-forwarding off;
+}
diff --git a/keama/tests/orphan4.out b/keama/tests/orphan4.out
new file mode 100644
index 00000000..c0146767
--- /dev/null
+++ b/keama/tests/orphan4.out
@@ -0,0 +1,30 @@
+{
+ # DHCPv4 orphan reservation config
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "host-reservation-identifiers": [
+ "hw-address"
+ ]
+// /// Orphan reservations
+// /// Kea reservations are per subnet
+// /// Reference Kea #231
+// "reservations": [
+// # orphan reservation
+// {
+// "hostname": "foobar",
+// "hw-address": "00:0b:fd:32:e6:fa",
+// "option-data": [
+// {
+// "space": "dhcp4",
+// "name": "ip-forwarding",
+// "code": 19,
+// "original-data": "off",
+// /// canonized booleans to lowercase true or false
+// "data": "false"
+// }
+// ]
+// }
+// ]
+ }
+}
diff --git a/keama/tests/orphan6.inN b/keama/tests/orphan6.inN
new file mode 100644
index 00000000..e85368f2
--- /dev/null
+++ b/keama/tests/orphan6.inN
@@ -0,0 +1,10 @@
+# DHCPv6 orphan reservation config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# orphan reservation
+host foobar {
+ hardware ethernet 00:0B:FD:32:E6:FA;
+ option dhcp6.name-servers 2a01:e00::2, 2a01:e00::1;
+}
diff --git a/keama/tests/orphan6.out b/keama/tests/orphan6.out
new file mode 100644
index 00000000..0c0b8114
--- /dev/null
+++ b/keama/tests/orphan6.out
@@ -0,0 +1,28 @@
+{
+ # DHCPv6 orphan reservation config
+ # empty configs are not accepted by Kea
+ "Dhcp6": {
+ "valid-lifetime": 1800,
+ "host-reservation-identifiers": [
+ "hw-address"
+ ]
+// /// Orphan reservations
+// /// Kea reservations are per subnet
+// /// Reference Kea #231
+// "reservations": [
+// # orphan reservation
+// {
+// "hostname": "foobar",
+// "hw-address": "00:0b:fd:32:e6:fa",
+// "option-data": [
+// {
+// "space": "dhcp6",
+// "name": "dns-servers",
+// "code": 23,
+// "data": "2a01:e00::2, 2a01:e00::1"
+// }
+// ]
+// }
+// ]
+ }
+}
diff --git a/keama/tests/packetdx4.notyet b/keama/tests/packetdx4.notyet
new file mode 100644
index 00000000..03f3237d
--- /dev/null
+++ b/keama/tests/packetdx4.notyet
@@ -0,0 +1,20 @@
+# packet data expression
+# Kea has no raw packet extractor in libeval
+
+# authoritative is mandatory
+authoritative;
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# pretty standard hardware superclass extracting the Ethernet address directly
+class "byhw" {
+ match packet(28, 6);
+}
+
+subclass "byhw" 00:07:0E:36:48:19 {
+ option host-name "test1";
+}
+
+# raw
+option host-name = binary-to-ascii(16, 8, "-", packet(28, 6));
diff --git a/keama/tests/permitauth4.in4 b/keama/tests/permitauth4.in4
new file mode 100644
index 00000000..b9663fd2
--- /dev/null
+++ b/keama/tests/permitauth4.in4
@@ -0,0 +1,15 @@
+# DHCPv4 permit authenticated client config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# subnet declaration
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ # pool declaration
+ pool {
+ # avoid empty pool
+ range 10.5.5.5 10.5.5.10;
+ # call get_permit
+ allow authenticated clients;
+ }
+}
diff --git a/keama/tests/permitauth4.out b/keama/tests/permitauth4.out
new file mode 100644
index 00000000..0f938566
--- /dev/null
+++ b/keama/tests/permitauth4.out
@@ -0,0 +1,33 @@
+{
+ # DHCPv4 permit authenticated client config
+ # empty configs are not accepted by Kea
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "subnet4": [
+ # subnet declaration
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ "pools": [
+ # pool declaration
+ {
+ # avoid empty pool
+ "pool": "10.5.5.5 - 10.5.5.10",
+ /// From:
+ /// allow authenticated clients
+ /// [un]authenticated-clients is not supported by ISC DHCP and Kea
+ "client-class": "gen#!ALL#"
+ }
+ ]
+ }
+ ],
+ "client-classes": [
+ {
+ "name": "gen#!ALL#",
+ "test": "not member('ALL')"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/permitauth6.in6 b/keama/tests/permitauth6.in6
new file mode 100644
index 00000000..ce0a25aa
--- /dev/null
+++ b/keama/tests/permitauth6.in6
@@ -0,0 +1,15 @@
+# DHCPv6 permit authenticated client config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# subnet declaration
+subnet6 2001::/64 {
+ # pool declaration
+ pool6 {
+ # avoid empty pool
+ range6 2001::100 2001::200;
+ # call get_permit
+ deny unauthenticated clients;
+ }
+}
diff --git a/keama/tests/permitauth6.out b/keama/tests/permitauth6.out
new file mode 100644
index 00000000..f2013e77
--- /dev/null
+++ b/keama/tests/permitauth6.out
@@ -0,0 +1,33 @@
+{
+ # DHCPv6 permit authenticated client config
+ # empty configs are not accepted by Kea
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp6": {
+ "valid-lifetime": 1800,
+ "subnet6": [
+ # subnet declaration
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "pools": [
+ # pool declaration
+ {
+ # avoid empty pool
+ "pool": "2001::100 - 2001::200",
+ /// From:
+ /// deny unauthenticated clients
+ /// [un]authenticated-clients is not supported by ISC DHCP and Kea
+ "client-class": "gen#_AND_#!ALL#"
+ }
+ ]
+ }
+ ],
+ "client-classes": [
+ {
+ "name": "gen#_AND_#!ALL#",
+ "test": "not member('ALL')"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/permitknown4.in4 b/keama/tests/permitknown4.in4
new file mode 100644
index 00000000..df15e11e
--- /dev/null
+++ b/keama/tests/permitknown4.in4
@@ -0,0 +1,15 @@
+# DHCPv4 permit known client config
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# subnet declaration
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ # pool declaration
+ pool {
+ # avoid empty pool
+ range 10.5.5.5 10.5.5.10;
+ # call get_permit
+ allow known clients;
+ }
+}
diff --git a/keama/tests/permitknown4.out b/keama/tests/permitknown4.out
new file mode 100644
index 00000000..1a27d5a9
--- /dev/null
+++ b/keama/tests/permitknown4.out
@@ -0,0 +1,26 @@
+{
+ # DHCPv4 permit known client config
+ # empty configs are not accepted by Kea
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "subnet4": [
+ # subnet declaration
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ "pools": [
+ # pool declaration
+ {
+ # avoid empty pool
+ "pool": "10.5.5.5 - 10.5.5.10",
+ /// From:
+ /// allow known clients
+ "client-class": "KNOWN"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/pickdx6.in6 b/keama/tests/pickdx6.in6
new file mode 100644
index 00000000..ccfa48ce
--- /dev/null
+++ b/keama/tests/pickdx6.in6
@@ -0,0 +1,15 @@
+# pick-first-value data expression
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# reduce literals
+class "literal" {
+ match if option dhcp6.client-data =
+ pick-first-value(substring("abcd",0,0),null,"foobar");
+}
+
+# null
+class "null" {
+ match if option dhcp6.client-data = pick(null);
+}
diff --git a/keama/tests/pickdx6.out b/keama/tests/pickdx6.out
new file mode 100644
index 00000000..d8de1130
--- /dev/null
+++ b/keama/tests/pickdx6.out
@@ -0,0 +1,21 @@
+{
+ # pick-first-value data expression
+ # empty configs are not accepted by Kea
+ "Dhcp6": {
+ "valid-lifetime": 1800,
+ "client-classes": [
+ # reduce literals
+ {
+ "name": "literal",
+ /// from: match if (option dhcp6.client-data) = (pick-first-value(substring('abcd', 0, 0), null, 'foobar'))
+ "test": "option[45].hex == 'foobar'"
+ },
+ # null
+ {
+ "name": "null",
+ /// from: match if (option dhcp6.client-data) = (pick-first-value(null))
+ "test": "option[45].hex == ''"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/pool4.in4 b/keama/tests/pool4.in4
new file mode 100644
index 00000000..0e156141
--- /dev/null
+++ b/keama/tests/pool4.in4
@@ -0,0 +1,11 @@
+# DHCPv4 pool config
+
+# subnet declaration
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ # pool declaration
+ pool {
+ option domain-search "example.com", "example.org";
+ default-lease-time 1800;
+ range 10.5.5.5 10.5.5.10;
+ }
+}
diff --git a/keama/tests/pool4.out b/keama/tests/pool4.out
new file mode 100644
index 00000000..e0b6bee8
--- /dev/null
+++ b/keama/tests/pool4.out
@@ -0,0 +1,31 @@
+{
+ # DHCPv4 pool config
+ # subnet declaration
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "subnet4": [
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ /// default-valid-lifetime moved from an internal pool scope
+ "valid-lifetime": 1800,
+ "pools": [
+ # pool declaration
+ {
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "pool": "10.5.5.5 - 10.5.5.10"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/pool42.in4 b/keama/tests/pool42.in4
new file mode 100644
index 00000000..7e7fea3f
--- /dev/null
+++ b/keama/tests/pool42.in4
@@ -0,0 +1,15 @@
+# DHCPv4 pool with 2 ranges config
+
+# subnet declaration
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ # pool declaration
+ pool {
+ option domain-search "example.com", "example.org";
+ default-lease-time 1800;
+ range 10.5.5.5 10.5.5.10;
+ # add a second range
+ range 10.5.5.11 10.5.5.12;
+ }
+ # interface
+ interface "en0";
+}
diff --git a/keama/tests/pool42.out b/keama/tests/pool42.out
new file mode 100644
index 00000000..38a039b8
--- /dev/null
+++ b/keama/tests/pool42.out
@@ -0,0 +1,49 @@
+{
+ # DHCPv4 pool with 2 ranges config
+ # subnet declaration
+ "Dhcp4": {
+ "subnet4": [
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ /// default-valid-lifetime moved from an internal pool scope
+ "valid-lifetime": 1800,
+ "pools": [
+ # pool declaration
+ {
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "pool": "10.5.5.5 - 10.5.5.10"
+ },
+ # pool declaration
+ {
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ # add a second range
+ "pool": "10.5.5.11 - 10.5.5.12"
+ }
+ ],
+ "interface": "en0"
+ }
+ ],
+ "interfaces-config": {
+ "interfaces": [
+ "en0"
+ ]
+ }
+ }
+}
diff --git a/keama/tests/pool6.in6 b/keama/tests/pool6.in6
new file mode 100644
index 00000000..2cd2fa39
--- /dev/null
+++ b/keama/tests/pool6.in6
@@ -0,0 +1,11 @@
+# DHCPv6 pool config
+
+# subnet declaration
+subnet6 2001::/64 {
+ # pool declaration
+ pool6 {
+ option dhcp6.domain-search "example.com", "example.org";
+ default-lease-time 1800;
+ range6 2001::100 2001::200;
+ }
+}
diff --git a/keama/tests/pool6.out b/keama/tests/pool6.out
new file mode 100644
index 00000000..626610dc
--- /dev/null
+++ b/keama/tests/pool6.out
@@ -0,0 +1,31 @@
+{
+ # DHCPv6 pool config
+ # subnet declaration
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp6": {
+ "subnet6": [
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ /// default-valid-lifetime moved from an internal pool scope
+ "valid-lifetime": 1800,
+ "pools": [
+ # pool declaration
+ {
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "domain-search",
+ "code": 24,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "pool": "2001::100 - 2001::200"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/pool6in4.err4 b/keama/tests/pool6in4.err4
new file mode 100644
index 00000000..44d19cf6
--- /dev/null
+++ b/keama/tests/pool6in4.err4
@@ -0,0 +1,13 @@
+# DHCPv6 pool declaration in DHCPv4 config
+
+# host declaration
+host foobar {
+ hardware ethernet 00:0B:FD:32:E6:FA;
+ default-lease-time 1800;
+}
+
+# There is a declaration so the error message is different
+
+# DHCPv6 pool declaration must be in DHCPv6 config
+range6 2001::/64;
+
diff --git a/keama/tests/pool6in4.msg b/keama/tests/pool6in4.msg
new file mode 100644
index 00000000..f6df706e
--- /dev/null
+++ b/keama/tests/pool6in4.msg
@@ -0,0 +1 @@
+pool6in4.err4 line 12: expecting a declaration
diff --git a/keama/tests/poolinroot4.err4 b/keama/tests/poolinroot4.err4
new file mode 100644
index 00000000..6caa6d47
--- /dev/null
+++ b/keama/tests/poolinroot4.err4
@@ -0,0 +1,7 @@
+# DHCPv4 pool declaration in root config
+
+# DHCPv4 pool declaration must be in a shared-network or subnet declaration
+pool {
+ range 204.152.185.135 204.152.185.185;
+}
+
diff --git a/keama/tests/poolinroot4.msg b/keama/tests/poolinroot4.msg
new file mode 100644
index 00000000..845744fd
--- /dev/null
+++ b/keama/tests/poolinroot4.msg
@@ -0,0 +1 @@
+poolinroot4.err4 line 4: pool declared outside of network
diff --git a/keama/tests/poolinroot6.err6 b/keama/tests/poolinroot6.err6
new file mode 100644
index 00000000..6605f086
--- /dev/null
+++ b/keama/tests/poolinroot6.err6
@@ -0,0 +1,7 @@
+# DHCPv6 pool declaration in root config
+
+# DHCPv6 pool declaration must be in a shared-network or subnet declaration
+pool6 {
+ range6 2001::/64;
+}
+
diff --git a/keama/tests/poolinroot6.msg b/keama/tests/poolinroot6.msg
new file mode 100644
index 00000000..5f48f13d
--- /dev/null
+++ b/keama/tests/poolinroot6.msg
@@ -0,0 +1 @@
+poolinroot6.err6 line 4: pool6 declared outside of network
diff --git a/keama/tests/preferred6.in6 b/keama/tests/preferred6.in6
new file mode 100644
index 00000000..9fd8519f
--- /dev/null
+++ b/keama/tests/preferred6.in6
@@ -0,0 +1,14 @@
+# preferred lifetime
+
+preferred-lifetime 1200;
+
+# embedded in pool
+subnet6 2001::/64 {
+ # silently overwritten
+ preferred-lifetime 1800;
+ pool6 {
+ preferred-lifetime 2400;
+ range6 2001::1000 2001::1fff;
+ }
+}
+
diff --git a/keama/tests/preferred6.out b/keama/tests/preferred6.out
new file mode 100644
index 00000000..88acf33d
--- /dev/null
+++ b/keama/tests/preferred6.out
@@ -0,0 +1,30 @@
+{
+ # preferred lifetime
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp6": {
+ "preferred-lifetime": 1200,
+ "renew-timer": 600,
+ "rebind-timer": 960,
+ "subnet6": [
+ # embedded in pool
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "preferred-lifetime": 1800,
+ "renew-timer": 900,
+ "rebind-timer": 1440,
+// /// preferred-lifetime moved from an internal pool scope
+// /// Avoid to overwrite current value...
+// "preferred-lifetime": 2400,
+// "renew-timer": 1200,
+// "rebind-timer": 1920,
+ "pools": [
+ {
+ "pool": "2001::1000 - 2001::1fff"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/prefix0.err6 b/keama/tests/prefix0.err6
new file mode 100644
index 00000000..c10b6d15
--- /dev/null
+++ b/keama/tests/prefix0.err6
@@ -0,0 +1,9 @@
+# DHCPv6 (bad 128 bit length) prefix config
+
+# subnet declaration
+subnet6 2001::/64 {
+ # range declaration
+ option dhcp6.domain-search "example.com", "example.org";
+ default-lease-time 1800;
+ prefix6 2001:0:0:10:: 2001:0:0:1f:: / 128;
+}
diff --git a/keama/tests/prefix0.msg b/keama/tests/prefix0.msg
new file mode 100644
index 00000000..73952817
--- /dev/null
+++ b/keama/tests/prefix0.msg
@@ -0,0 +1 @@
+prefix0.err6 line 8: networks have 0 to 128 bits (exclusive)
diff --git a/keama/tests/prefix128.err6 b/keama/tests/prefix128.err6
new file mode 100644
index 00000000..c10b6d15
--- /dev/null
+++ b/keama/tests/prefix128.err6
@@ -0,0 +1,9 @@
+# DHCPv6 (bad 128 bit length) prefix config
+
+# subnet declaration
+subnet6 2001::/64 {
+ # range declaration
+ option dhcp6.domain-search "example.com", "example.org";
+ default-lease-time 1800;
+ prefix6 2001:0:0:10:: 2001:0:0:1f:: / 128;
+}
diff --git a/keama/tests/prefix128.msg b/keama/tests/prefix128.msg
new file mode 100644
index 00000000..ee874c74
--- /dev/null
+++ b/keama/tests/prefix128.msg
@@ -0,0 +1 @@
+prefix128.err6 line 8: networks have 0 to 128 bits (exclusive)
diff --git a/keama/tests/prefix6.in6 b/keama/tests/prefix6.in6
new file mode 100644
index 00000000..e5c01c90
--- /dev/null
+++ b/keama/tests/prefix6.in6
@@ -0,0 +1,9 @@
+# DHCPv6 prefix config
+
+# subnet declaration
+subnet6 2001::/64 {
+ # range declaration
+ option dhcp6.domain-search "example.com", "example.org";
+ default-lease-time 1800;
+ prefix6 2001:0:0:10:: 2001:0:0:1f:: / 64;
+}
diff --git a/keama/tests/prefix6.out b/keama/tests/prefix6.out
new file mode 100644
index 00000000..e0fe3ea5
--- /dev/null
+++ b/keama/tests/prefix6.out
@@ -0,0 +1,32 @@
+{
+ # DHCPv6 prefix config
+ # subnet declaration
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp6": {
+ "subnet6": [
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "option-data": [
+ # range declaration
+ {
+ "space": "dhcp6",
+ "name": "domain-search",
+ "code": 24,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "valid-lifetime": 1800,
+ "pd-pools": [
+ {
+ "prefix": "2001:0:0:10::",
+ "delegated-len": 64,
+ "prefix-len": 60
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/prefix62.in6 b/keama/tests/prefix62.in6
new file mode 100644
index 00000000..408481a5
--- /dev/null
+++ b/keama/tests/prefix62.in6
@@ -0,0 +1,10 @@
+# DHCPv6 prefix config
+
+# subnet declaration
+subnet6 2001::/64 {
+ # range declaration
+ option dhcp6.domain-search "example.com", "example.org";
+ default-lease-time 1800;
+ prefix6 2001:0:0:1:: 2001:0:0:3:: / 64;
+ interface "en0";
+}
diff --git a/keama/tests/prefix62.out b/keama/tests/prefix62.out
new file mode 100644
index 00000000..0ce536a8
--- /dev/null
+++ b/keama/tests/prefix62.out
@@ -0,0 +1,36 @@
+{
+ # DHCPv6 prefix config
+ # subnet declaration
+ "Dhcp6": {
+ "subnet6": [
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "option-data": [
+ # range declaration
+ {
+ "space": "dhcp6",
+ "name": "domain-search",
+ "code": 24,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "valid-lifetime": 1800,
+ "pd-pools": [
+// {
+// "prefix": "2001:0:0:1::",
+// "delegated-len": 64,
+// "prefix-highest": "2001:0:0:3::"
+// }
+ ],
+ "interface": "en0"
+ }
+ ],
+ "interfaces-config": {
+ "interfaces": [
+ "en0"
+ ]
+ }
+ }
+}
diff --git a/keama/tests/prefixinroot6.err6 b/keama/tests/prefixinroot6.err6
new file mode 100644
index 00000000..f1fd4a1e
--- /dev/null
+++ b/keama/tests/prefixinroot6.err6
@@ -0,0 +1,4 @@
+# DHCPv6 prefix declaration in root config
+
+# DHCPv6 prefix declaration must be in a subnet declaration
+prefix6 2001:: 2001:0:1:: / 64;
diff --git a/keama/tests/prefixinroot6.msg b/keama/tests/prefixinroot6.msg
new file mode 100644
index 00000000..8aa498f4
--- /dev/null
+++ b/keama/tests/prefixinroot6.msg
@@ -0,0 +1 @@
+prefixinroot6.err6 line 4: prefix6 declaration not allowed here.
diff --git a/keama/tests/qualifyingsuffix4.in4 b/keama/tests/qualifyingsuffix4.in4
new file mode 100644
index 00000000..cef53f4d
--- /dev/null
+++ b/keama/tests/qualifyingsuffix4.in4
@@ -0,0 +1,9 @@
+# ddns-domainname (aka qualifying-suffix)
+
+ddns-domainname ".biz";
+ddns-updates true;
+
+# embedded
+class "foo" {
+ ddns-domainname ".bar";
+}
diff --git a/keama/tests/qualifyingsuffix4.out b/keama/tests/qualifyingsuffix4.out
new file mode 100644
index 00000000..f014362d
--- /dev/null
+++ b/keama/tests/qualifyingsuffix4.out
@@ -0,0 +1,17 @@
+{
+ # ddns-domainname (aka qualifying-suffix)
+ "Dhcp4": {
+ "dhcp-ddns": {
+ "qualifying-suffix": ".biz",
+ "enable-updates": true
+ },
+ "client-classes": [
+ # embedded
+ {
+ "name": "foo"
+// /// Only global qualifying-suffix is supported
+// "qualifying-suffix": ".bar"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/qualifyingsuffix6.in6 b/keama/tests/qualifyingsuffix6.in6
new file mode 100644
index 00000000..3957eae2
--- /dev/null
+++ b/keama/tests/qualifyingsuffix6.in6
@@ -0,0 +1,8 @@
+# ddns-domainname (aka qualifying-suffix)
+
+ddns-domainname ".biz";
+
+# embedded
+class "foo" {
+ ddns-domainname ".bar";
+}
diff --git a/keama/tests/qualifyingsuffix6.out b/keama/tests/qualifyingsuffix6.out
new file mode 100644
index 00000000..44ae84a1
--- /dev/null
+++ b/keama/tests/qualifyingsuffix6.out
@@ -0,0 +1,17 @@
+{
+ # ddns-domainname (aka qualifying-suffix)
+ "Dhcp6": {
+ "dhcp-ddns": {
+ "enable-updates": false,
+ "qualifying-suffix": ".biz"
+ },
+ "client-classes": [
+ # embedded
+ {
+ "name": "foo"
+// /// Only global qualifying-suffix is supported
+// "qualifying-suffix": ".bar"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/range4.in4 b/keama/tests/range4.in4
new file mode 100644
index 00000000..f755a2eb
--- /dev/null
+++ b/keama/tests/range4.in4
@@ -0,0 +1,9 @@
+# DHCPv4 range config
+
+# subnet declaration
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ # range declaration
+ option domain-search "example.com", "example.org";
+ default-lease-time 1800;
+ range 10.5.5.5 10.5.5.10;
+}
diff --git a/keama/tests/range4.out b/keama/tests/range4.out
new file mode 100644
index 00000000..94611f7e
--- /dev/null
+++ b/keama/tests/range4.out
@@ -0,0 +1,30 @@
+{
+ # DHCPv4 range config
+ # subnet declaration
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "subnet4": [
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ "option-data": [
+ # range declaration
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "valid-lifetime": 1800,
+ "pools": [
+ {
+ "pool": "10.5.5.5 - 10.5.5.10"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/range6.in6 b/keama/tests/range6.in6
new file mode 100644
index 00000000..45a0635a
--- /dev/null
+++ b/keama/tests/range6.in6
@@ -0,0 +1,11 @@
+# DHCPv6 range config
+
+# subnet declaration
+subnet6 2001::/64 {
+ # range declaration
+ option dhcp6.domain-search "example.com", "example.org";
+ default-lease-time 1800;
+ range6 2001::100 2001::200;
+ range6 2001::1000/116;
+ interface "en0";
+}
diff --git a/keama/tests/range6.out b/keama/tests/range6.out
new file mode 100644
index 00000000..1e506ab5
--- /dev/null
+++ b/keama/tests/range6.out
@@ -0,0 +1,37 @@
+{
+ # DHCPv6 range config
+ # subnet declaration
+ "Dhcp6": {
+ "subnet6": [
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "option-data": [
+ # range declaration
+ {
+ "space": "dhcp6",
+ "name": "domain-search",
+ "code": 24,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "valid-lifetime": 1800,
+ "pools": [
+ {
+ "pool": "2001::100 - 2001::200"
+ },
+ {
+ "pool": "2001::1000/116"
+ }
+ ],
+ "interface": "en0"
+ }
+ ],
+ "interfaces-config": {
+ "interfaces": [
+ "en0"
+ ]
+ }
+ }
+}
diff --git a/keama/tests/range6in4.err4 b/keama/tests/range6in4.err4
new file mode 100644
index 00000000..af3630c0
--- /dev/null
+++ b/keama/tests/range6in4.err4
@@ -0,0 +1,5 @@
+# DHCPv6 pool declaration in DHCPv4 config
+
+# DHCPv6 pool declaration must be in DHCPv6 config
+range6 2001::/64;
+
diff --git a/keama/tests/range6in4.msg b/keama/tests/range6in4.msg
new file mode 100644
index 00000000..d27a8452
--- /dev/null
+++ b/keama/tests/range6in4.msg
@@ -0,0 +1 @@
+range6in4.err4 line 4: expecting a parameter or declaration
diff --git a/keama/tests/rangeinroot4.err4 b/keama/tests/rangeinroot4.err4
new file mode 100644
index 00000000..7a3d36f9
--- /dev/null
+++ b/keama/tests/rangeinroot4.err4
@@ -0,0 +1,4 @@
+# DHCPv4 range declaration in root config
+
+# DHCPv4 range declaration must be in a subnet declaration
+range 204.152.185.135 204.152.185.185;
diff --git a/keama/tests/rangeinroot4.msg b/keama/tests/rangeinroot4.msg
new file mode 100644
index 00000000..4ed397e4
--- /dev/null
+++ b/keama/tests/rangeinroot4.msg
@@ -0,0 +1 @@
+rangeinroot4.err4 line 4: range declaration not allowed here.
diff --git a/keama/tests/rangeinroot6.err6 b/keama/tests/rangeinroot6.err6
new file mode 100644
index 00000000..e8c5c65e
--- /dev/null
+++ b/keama/tests/rangeinroot6.err6
@@ -0,0 +1,5 @@
+# DHCPv6 range declaration in root config
+
+# DHCPv6 range declaration must be in a subnet declaration
+range6 2001::/64;
+
diff --git a/keama/tests/rangeinroot6.msg b/keama/tests/rangeinroot6.msg
new file mode 100644
index 00000000..969fe658
--- /dev/null
+++ b/keama/tests/rangeinroot6.msg
@@ -0,0 +1 @@
+rangeinroot6.err6 line 4: range6 declaration not allowed here.
diff --git a/keama/tests/reversedx6.in6 b/keama/tests/reversedx6.in6
new file mode 100644
index 00000000..ba762787
--- /dev/null
+++ b/keama/tests/reversedx6.in6
@@ -0,0 +1,9 @@
+# reverse data expression
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# reduce literals
+class "literal" {
+ match if option dhcp6.client-data = reverse(2 & 2, "foobar");
+}
diff --git a/keama/tests/reversedx6.out b/keama/tests/reversedx6.out
new file mode 100644
index 00000000..f13efe95
--- /dev/null
+++ b/keama/tests/reversedx6.out
@@ -0,0 +1,15 @@
+{
+ # reverse data expression
+ # empty configs are not accepted by Kea
+ "Dhcp6": {
+ "valid-lifetime": 1800,
+ "client-classes": [
+ # reduce literals
+ {
+ "name": "literal",
+ /// from: match if (option dhcp6.client-data) = (reverse(2 & 2, 'foobar'))
+ "test": "option[45].hex == 'arobfo'"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/runall.sh b/keama/tests/runall.sh
new file mode 100644
index 00000000..f8cf357e
--- /dev/null
+++ b/keama/tests/runall.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+#set -x
+
+cd "$(dirname "$0")"
+
+echo subdirs:
+/bin/sh samples/runall.sh
+
+echo tests:
+for t in *.err* *.in*
+do
+ echo `basename $t`
+ /bin/sh runone.sh $t
+done
diff --git a/keama/tests/runone.sh b/keama/tests/runone.sh
new file mode 100644
index 00000000..4838c973
--- /dev/null
+++ b/keama/tests/runone.sh
@@ -0,0 +1,119 @@
+#!/bin/sh
+
+#set -x
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 test-name" >&2
+ exit 1
+fi
+
+file=$1
+
+cd "$(dirname "$0")"
+
+isin=$(expr $file : ".*\.in*")
+iserr=$(expr $file : ".*\.err*")
+if [ \( $isin -eq 0 \) -a \( $iserr -eq 0 \) ]; then
+ full=$file.in*
+ if [ ! -f $full ]; then
+ full=$file.err*
+ fi
+else
+ full=$file
+fi
+
+if [ ! -f $full ]; then
+ echo "can't find $file" >&2
+ exit 1
+fi
+
+errcase=$(expr $full : ".*\.err*")
+
+trail=
+if [ $errcase -eq 0 ]; then
+ trail=$(expr $full : ".*\.in\(.\)")
+else
+ trail=$(expr $full : ".*\.err\(.\)")
+fi
+
+options=""
+dual=0
+hook=0
+
+case $trail in
+ '') dual=1;;
+ 4) options="-4";;
+ 6) options="-6";;
+ F) options="-4 -r fatal";;
+ P) options="-4 -r pass";;
+ d) options="-4 -D";;
+ D) options="-6 -D";;
+ n) options="-4 -N";;
+ N) options="-6 -N";;
+ l) options="-4 -l ${HOOK:-/path/}"; hook=1;;
+ L) options="-6 -l ${HOOK:-/path/}"; hook=1;;
+ *) echo "unrecognized trail '$trail' in '$full'" >&2; exit 1;;
+esac
+
+if [ $errcase -ne 0 ]; then
+ base=$(basename $full .err$trail)
+else
+ if [ $dual -ne 0 ]; then
+ echo "required trail ([45FP]) in '$full'" >&2
+ exit 1
+ fi
+ base=$(basename $full .in$trail)
+fi
+
+out=/tmp/$base.out$$
+expected=""
+if [ $errcase -ne 0 ]; then
+ expected=$base.msg
+else
+ expected=$base.out
+fi
+
+if [ $errcase -ne 0 ]; then
+ if [ $dual -eq 1 ]; then
+ ../keama -4 -i $full >$out 2>&1
+ if [ $? -ne 255 ]; then
+ echo "$full -4 doesn't fail as expected" >&2
+ exit 1
+ fi
+ ../keama -6 -i $full >$out 2>&1
+ if [ $? -ne 255 ]; then
+ echo "$full -6 doesn't fail as expected" >&2
+ exit 1
+ fi
+ else
+ ../keama $options -i $full >$out 2>&1
+ if [ $? -ne 255 ]; then
+ echo "$full doesn't fail as expected" >&2
+ exit 1
+ fi
+ fi
+else
+ ../keama $options -i $full -o $out >&2
+ if [ $? -eq 255 ]; then
+ echo "$full raised an error" >&2
+ exit 1
+ fi
+fi
+
+if [ $hook -eq 1 ]; then
+ sed s,/path/,${HOOK:-/path/}, < ${expected}L > $expected
+fi
+
+if [ $errcase -ne 0 ]; then
+ cat $out | head -1 | diff --brief - $expected
+ if [ $? -ne 0 ]; then
+ echo "$full doesn't provide expected output" >&2
+ exit 1
+ fi
+else
+ diff --brief $out $expected
+ if [ $? -ne 0 ]; then
+ echo "$full doesn't provide expected output" >&2
+ exit 1
+ fi
+fi
diff --git a/keama/tests/samples/example.conf b/keama/tests/samples/example.conf
new file mode 100644
index 00000000..62b088dc
--- /dev/null
+++ b/keama/tests/samples/example.conf
@@ -0,0 +1,111 @@
+# dhcpd.conf
+#
+# Sample configuration file for ISC dhcpd
+#
+
+# option definitions common to all supported networks...
+option domain-name "example.org";
+#option domain-name-servers ns1.example.org, ns2.example.org;
+option domain-name-servers 10.35.0.1, 10.35.0.2;
+
+default-lease-time 600;
+max-lease-time 7200;
+
+# Use this to enble / disable dynamic dns updates globally.
+#ddns-update-style none;
+
+# If this DHCP server is the official DHCP server for the local
+# network, the authoritative directive should be uncommented.
+#authoritative;
+
+# Use this to send dhcp log messages to a different log file (you also
+# have to hack syslog.conf to complete the redirection).
+log-facility local7;
+
+# No service will be given on this subnet, but declaring it helps the
+# DHCP server to understand the network topology.
+
+subnet 10.152.187.0 netmask 255.255.255.0 {
+}
+
+# This is a very basic subnet declaration.
+
+subnet 10.254.239.0 netmask 255.255.255.224 {
+ range 10.254.239.10 10.254.239.20;
+# option routers rtr-239-0-1.example.org, rtr-239-0-2.example.org;
+ option routers 10.254.239.1, 10.254.239.2;
+}
+
+# This declaration allows BOOTP clients to get dynamic addresses,
+# which we don't really recommend.
+
+subnet 10.254.239.32 netmask 255.255.255.224 {
+ range dynamic-bootp 10.254.239.40 10.254.239.60;
+ option broadcast-address 10.254.239.31;
+# option routers rtr-239-32-1.example.org;
+ option routers 10.254.239.33;
+}
+
+# A slightly different configuration for an internal subnet.
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ range 10.5.5.26 10.5.5.30;
+# option domain-name-servers ns1.internal.example.org;
+ option domain-name-servers 10.35.1.1;
+ option domain-name "internal.example.org";
+ option routers 10.5.5.1;
+ option broadcast-address 10.5.5.31;
+ default-lease-time 600;
+ max-lease-time 7200;
+}
+
+# Hosts which require special configuration options can be listed in
+# host statements. If no address is specified, the address will be
+# allocated dynamically (if possible), but the host-specific information
+# will still come from the host declaration.
+
+host passacaglia {
+ hardware ethernet 0:0:c0:5d:bd:95;
+ filename "vmunix.passacaglia";
+ server-name "toccata.example.com";
+}
+
+# Fixed IP addresses can also be specified for hosts. These addresses
+# should not also be listed as being available for dynamic assignment.
+# Hosts for which fixed IP addresses have been specified can boot using
+# BOOTP or DHCP. Hosts for which no fixed address is specified can only
+# be booted with DHCP, unless there is an address range on the subnet
+# to which a BOOTP client is connected which has the dynamic-bootp flag
+# set.
+host fantasia {
+ hardware ethernet 08:00:07:26:c0:a5;
+# fixed-address fantasia.example.com;
+ fixed-address 10.5.5.20;
+}
+
+# You can declare a class of clients and then do address allocation
+# based on that. The example below shows a case where all clients
+# in a certain class get addresses on the 10.17.224/24 subnet, and all
+# other clients get addresses on the 10.0.29/24 subnet.
+
+class "foo" {
+ match if substring (option vendor-class-identifier, 0, 4) = "SUNW";
+}
+
+shared-network 224-29 {
+ subnet 10.17.224.0 netmask 255.255.255.0 {
+# option routers rtr-224.example.org;
+ option routers 10.17.224.1;
+ }
+ subnet 10.0.29.0 netmask 255.255.255.0 {
+# option routers rtr-29.example.org;
+ option routers 10.0.29.1;
+ }
+ pool {
+ allow members of "foo";
+ range 10.17.224.10 10.17.224.250;
+ }
+ pool {
+ deny members of "foo";
+ range 10.0.29.10 10.0.29.230;
+ }
+}
diff --git a/keama/tests/samples/example.conf.orig b/keama/tests/samples/example.conf.orig
new file mode 100644
index 00000000..8b99f496
--- /dev/null
+++ b/keama/tests/samples/example.conf.orig
@@ -0,0 +1,104 @@
+# dhcpd.conf
+#
+# Sample configuration file for ISC dhcpd
+#
+
+# option definitions common to all supported networks...
+option domain-name "example.org";
+option domain-name-servers ns1.example.org, ns2.example.org;
+
+default-lease-time 600;
+max-lease-time 7200;
+
+# Use this to enble / disable dynamic dns updates globally.
+#ddns-update-style none;
+
+# If this DHCP server is the official DHCP server for the local
+# network, the authoritative directive should be uncommented.
+#authoritative;
+
+# Use this to send dhcp log messages to a different log file (you also
+# have to hack syslog.conf to complete the redirection).
+log-facility local7;
+
+# No service will be given on this subnet, but declaring it helps the
+# DHCP server to understand the network topology.
+
+subnet 10.152.187.0 netmask 255.255.255.0 {
+}
+
+# This is a very basic subnet declaration.
+
+subnet 10.254.239.0 netmask 255.255.255.224 {
+ range 10.254.239.10 10.254.239.20;
+ option routers rtr-239-0-1.example.org, rtr-239-0-2.example.org;
+}
+
+# This declaration allows BOOTP clients to get dynamic addresses,
+# which we don't really recommend.
+
+subnet 10.254.239.32 netmask 255.255.255.224 {
+ range dynamic-bootp 10.254.239.40 10.254.239.60;
+ option broadcast-address 10.254.239.31;
+ option routers rtr-239-32-1.example.org;
+}
+
+# A slightly different configuration for an internal subnet.
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ range 10.5.5.26 10.5.5.30;
+ option domain-name-servers ns1.internal.example.org;
+ option domain-name "internal.example.org";
+ option routers 10.5.5.1;
+ option broadcast-address 10.5.5.31;
+ default-lease-time 600;
+ max-lease-time 7200;
+}
+
+# Hosts which require special configuration options can be listed in
+# host statements. If no address is specified, the address will be
+# allocated dynamically (if possible), but the host-specific information
+# will still come from the host declaration.
+
+host passacaglia {
+ hardware ethernet 0:0:c0:5d:bd:95;
+ filename "vmunix.passacaglia";
+ server-name "toccata.example.com";
+}
+
+# Fixed IP addresses can also be specified for hosts. These addresses
+# should not also be listed as being available for dynamic assignment.
+# Hosts for which fixed IP addresses have been specified can boot using
+# BOOTP or DHCP. Hosts for which no fixed address is specified can only
+# be booted with DHCP, unless there is an address range on the subnet
+# to which a BOOTP client is connected which has the dynamic-bootp flag
+# set.
+host fantasia {
+ hardware ethernet 08:00:07:26:c0:a5;
+ fixed-address fantasia.example.com;
+}
+
+# You can declare a class of clients and then do address allocation
+# based on that. The example below shows a case where all clients
+# in a certain class get addresses on the 10.17.224/24 subnet, and all
+# other clients get addresses on the 10.0.29/24 subnet.
+
+class "foo" {
+ match if substring (option vendor-class-identifier, 0, 4) = "SUNW";
+}
+
+shared-network 224-29 {
+ subnet 10.17.224.0 netmask 255.255.255.0 {
+ option routers rtr-224.example.org;
+ }
+ subnet 10.0.29.0 netmask 255.255.255.0 {
+ option routers rtr-29.example.org;
+ }
+ pool {
+ allow members of "foo";
+ range 10.17.224.10 10.17.224.250;
+ }
+ pool {
+ deny members of "foo";
+ range 10.0.29.10 10.0.29.230;
+ }
+}
diff --git a/keama/tests/samples/example.json b/keama/tests/samples/example.json
new file mode 100644
index 00000000..7123a158
--- /dev/null
+++ b/keama/tests/samples/example.json
@@ -0,0 +1,223 @@
+{
+ # dhcpd.conf
+ #
+ # Sample configuration file for ISC dhcpd
+ #
+ # option definitions common to all supported networks...
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "domain-name",
+ "code": 15,
+ "data": "example.org"
+ },
+ #option domain-name-servers ns1.example.org, ns2.example.org;
+ {
+ "space": "dhcp4",
+ "name": "domain-name-servers",
+ "code": 6,
+ "data": "10.35.0.1, 10.35.0.2"
+ }
+ ],
+ "valid-lifetime": 600,
+ "max-valid-lifetime": 7200,
+// "config": [
+// /// log-facility is not supported
+// /// Please use the KEA_LOGGER_DESTINATION environment variable instead
+// {
+// "name": "log-facility",
+// "code": 44,
+// "value": "local7"
+// }
+// ],
+ "subnet4": [
+ # No service will be given on this subnet, but declaring it helps the
+ # DHCP server to understand the network topology.
+ {
+ "id": 1,
+ "subnet": "10.152.187.0/24"
+ },
+ # This is a very basic subnet declaration.
+ {
+ "id": 2,
+ "subnet": "10.254.239.0/27",
+ "pools": [
+ {
+ "pool": "10.254.239.10 - 10.254.239.20"
+ }
+ ],
+ "option-data": [
+ # option routers rtr-239-0-1.example.org, rtr-239-0-2.example.org;
+ {
+ "space": "dhcp4",
+ "name": "routers",
+ "code": 3,
+ "data": "10.254.239.1, 10.254.239.2"
+ }
+ ]
+ },
+ # This declaration allows BOOTP clients to get dynamic addresses,
+ # which we don't really recommend.
+ {
+ "id": 3,
+ "subnet": "10.254.239.32/27",
+ "pools": [
+ {
+ "pool": "10.254.239.40 - 10.254.239.60"
+ }
+ ],
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "broadcast-address",
+ "code": 28,
+ "data": "10.254.239.31"
+ },
+ # option routers rtr-239-32-1.example.org;
+ {
+ "space": "dhcp4",
+ "name": "routers",
+ "code": 3,
+ "data": "10.254.239.33"
+ }
+ ]
+ },
+ # A slightly different configuration for an internal subnet.
+ {
+ "id": 4,
+ "subnet": "10.5.5.0/27",
+ "pools": [
+ {
+ "pool": "10.5.5.26 - 10.5.5.30"
+ }
+ ],
+ "option-data": [
+ # option domain-name-servers ns1.internal.example.org;
+ {
+ "space": "dhcp4",
+ "name": "domain-name-servers",
+ "code": 6,
+ "data": "10.35.1.1"
+ },
+ {
+ "space": "dhcp4",
+ "name": "domain-name",
+ "code": 15,
+ "data": "internal.example.org"
+ },
+ {
+ "space": "dhcp4",
+ "name": "routers",
+ "code": 3,
+ "data": "10.5.5.1"
+ },
+ {
+ "space": "dhcp4",
+ "name": "broadcast-address",
+ "code": 28,
+ "data": "10.5.5.31"
+ }
+ ],
+ "valid-lifetime": 600,
+ "max-valid-lifetime": 7200,
+ /// Host reservations without fixed addresses were put in the last declared subnet
+ /// Reference Kea #231
+ "reservations": [
+ # Hosts which require special configuration options can be listed in
+ # host statements. If no address is specified, the address will be
+ # allocated dynamically (if possible), but the host-specific information
+ # will still come from the host declaration.
+ {
+ "hostname": "passacaglia",
+ "hw-address": "00:00:c0:5d:bd:95",
+ "boot-file-name": "vmunix.passacaglia",
+ "server-hostname": "toccata.example.com"
+ },
+ # Fixed IP addresses can also be specified for hosts. These addresses
+ # should not also be listed as being available for dynamic assignment.
+ # Hosts for which fixed IP addresses have been specified can boot using
+ # BOOTP or DHCP. Hosts for which no fixed address is specified can only
+ # be booted with DHCP, unless there is an address range on the subnet
+ # to which a BOOTP client is connected which has the dynamic-bootp flag
+ # set.
+ {
+ "hostname": "fantasia",
+ "hw-address": "08:00:07:26:c0:a5",
+ "ip-address": "10.5.5.20"
+ }
+ ]
+ }
+ ],
+ "host-reservation-identifiers": [
+ "hw-address"
+ ],
+ "client-classes": [
+ # You can declare a class of clients and then do address allocation
+ # based on that. The example below shows a case where all clients
+ # in a certain class get addresses on the 10.17.224/24 subnet, and all
+ # other clients get addresses on the 10.0.29/24 subnet.
+ {
+ "name": "foo",
+ /// from: match if (substring(option dhcp.vendor-class-identifier, 0, 4)) = 'SUNW'
+ "test": "substring(option[60].hex,0,4) == 'SUNW'"
+ },
+ {
+ "name": "gen#_AND_#!foo#",
+ "test": "not member('foo')"
+ }
+ ],
+ /// Kea shared-networks are different, cf Kea #236
+ "shared-networks": [
+ {
+ "name": "224-29",
+ "subnet4": [
+ {
+ "id": 5,
+ "subnet": "10.17.224.0/24",
+ "option-data": [
+ # option routers rtr-224.example.org;
+ {
+ "space": "dhcp4",
+ "name": "routers",
+ "code": 3,
+ "data": "10.17.224.1"
+ }
+ ],
+ "pools": [
+ {
+ "pool": "10.17.224.10 - 10.17.224.250",
+ /// From:
+ /// allow foo
+ "client-class": "foo"
+ }
+ ]
+ },
+ {
+ "id": 6,
+ "subnet": "10.0.29.0/24",
+ "option-data": [
+ # option routers rtr-29.example.org;
+ {
+ "space": "dhcp4",
+ "name": "routers",
+ "code": 3,
+ "data": "10.0.29.1"
+ }
+ ],
+ "pools": [
+ {
+ "pool": "10.0.29.10 - 10.0.29.230",
+ /// From:
+ /// deny foo
+ "client-class": "gen#_AND_#!foo#"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/samples/runall.sh b/keama/tests/samples/runall.sh
new file mode 100644
index 00000000..bc41c7f8
--- /dev/null
+++ b/keama/tests/samples/runall.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+#set -x
+
+cd "$(dirname "$0")"
+
+for t in example simple test-a6 vmnet8
+do
+ echo $t
+ /bin/sh runone.sh $t
+done
diff --git a/keama/tests/samples/runone.sh b/keama/tests/samples/runone.sh
new file mode 100644
index 00000000..6526d798
--- /dev/null
+++ b/keama/tests/samples/runone.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+#set -x
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 config-name" >&2
+ exit 1
+fi
+
+file=$1
+
+cd "$(dirname "$0")"
+
+trail=$(expr $file : ".*6$")
+options=""
+if [ $trail -eq 0 ]; then
+ options="-4"
+else
+ options="-6"
+fi
+
+out=/tmp/$file.out$$
+
+../../keama $options -N -i $file.conf -o $out >&2
+status=$?
+if [ $status -eq 255 ]; then
+ echo "$file config raised an error" >&2
+ exit 1
+fi
+
+expected=$file.json
+
+diff --brief $out $expected
+if [ $? -ne 0 ]; then
+ echo "$file config doesn't provide expected output" >&2
+ exit 1
+fi
+
+exit $status
diff --git a/keama/tests/samples/simple.conf b/keama/tests/samples/simple.conf
new file mode 100644
index 00000000..76c1cfa5
--- /dev/null
+++ b/keama/tests/samples/simple.conf
@@ -0,0 +1,33 @@
+# -------------------------
+# dhcpd.conf
+#
+# Sample configuration file for ISC dhcpd
+#
+
+# option definitions common to all supported networks...
+option domain-name "example.org";
+# option domain-name-servers ns1.example.org, ns2.example.org;
+
+default-lease-time 1800;
+max-lease-time 7200;
+
+# We're going to be authoritative for the network we've
+# just created.
+
+authoritative;
+
+# No service will be given on this subnet, but we're telling
+# the DHCP server about it so it understands it's there and
+# not to hand out leases for it.
+
+subnet 10.14.8.195 netmask 255.255.255.0 {
+}
+
+# But we do want to hand out leases for the 192.168.1.0/24
+# network for purposes of this test..
+
+subnet 192.168.1.0 netmask 255.255.255.0 {
+ range 192.168.1.100 192.168.1.150;
+ option routers 192.168.1.1;
+}
+# -------------------------
diff --git a/keama/tests/samples/simple.json b/keama/tests/samples/simple.json
new file mode 100644
index 00000000..7cd02423
--- /dev/null
+++ b/keama/tests/samples/simple.json
@@ -0,0 +1,53 @@
+{
+ # -------------------------
+ # dhcpd.conf
+ #
+ # Sample configuration file for ISC dhcpd
+ #
+ # option definitions common to all supported networks...
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "domain-name",
+ "code": 15,
+ "data": "example.org"
+ }
+ ],
+ "valid-lifetime": 1800,
+ "max-valid-lifetime": 7200,
+ # We're going to be authoritative for the network we've
+ # just created.
+ "authoritative": true,
+ "subnet4": [
+ # No service will be given on this subnet, but we're telling
+ # the DHCP server about it so it understands it's there and
+ # not to hand out leases for it.
+ {
+ "id": 1,
+ "subnet": "10.14.8.195/24"
+ },
+ # But we do want to hand out leases for the 192.168.1.0/24
+ # network for purposes of this test..
+ {
+ "id": 2,
+ "subnet": "192.168.1.0/24",
+ "pools": [
+ {
+ "pool": "192.168.1.100 - 192.168.1.150"
+ }
+ ],
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "routers",
+ "code": 3,
+ "data": "192.168.1.1"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/samples/test-a6.conf b/keama/tests/samples/test-a6.conf
new file mode 100644
index 00000000..9514c4aa
--- /dev/null
+++ b/keama/tests/samples/test-a6.conf
@@ -0,0 +1,75 @@
+#
+# Define the DHCPv6 option space.
+#
+# Option numbers are assigned by IANA:
+# http://www.iana.org/assignments/dhcpv6-parameters
+#
+option dhcp6.time-servers code 1040 = array of ip6-address;
+option dhcp6.time-offset code 1041 = signed integer 32;
+
+#
+# Define the DOCSIS option space.
+# TODO: DOCSIS oro definition
+#
+# this space is already known by Kea
+option space docsis code width 2 length width 2;
+option vsio.docsis code 4491 = encapsulate docsis;
+option docsis.tftp-servers code 32 = array of ip6-address;
+% option docsis.tftp-servers local;
+option docsis.cablelabs-configuration-file code 33 = text;
+% option docsis.cablelabs-configuration-file alias config-file;
+% option docsis.cablelabs-configuration-file local;
+option docsis.cablelabs-syslog-servers code 34 = array of ip6-address;
+% option docsis.cablelabs-syslog-servers alias syslog-servers;
+% option docsis.cablelabs-syslog-servers local;
+#option docsis.device-id code 36 = string;
+% option docsis.device-id code 36 = "X";
+% option docsis.device-id local;
+
+#
+# Declare some options.
+#
+option dhcp6.time-servers 3ffe:bbbb:aaaa:aaaa::1, 3ffe:bbbb:aaaa:aaaa::2;
+option docsis.tftp-servers 3ffe:cccc:aaaa:aaaa::1, 3ffe:cccc:aaaa:aaaa::2;
+
+#
+# DNS server IP address to update dynamically
+#
+ddns-update-style interim;
+ddns-domainname "foo.com";
+
+#
+# Per-host settings.
+#
+host cablemodem-1 {
+ host-identifier option
+ dhcp6.client-id 00:01:00:01:0c:00:a1:41:00:06:5b:50:99:f6;
+ fixed-address6 3ffe:aaaa:aaaa:aaaa::ffff;
+ ddns-domainname "bar.com";
+ option dhcp6.time-servers 3ffe:aaaa:aaaa:aaaa::1,
+ 3ffe:aaaa:aaaa:aaaa::2;
+ option docsis.tftp-servers 3ffe:aaaa:aaaa:aaaa::1,
+ 3ffe:aaaa:aaaa:aaaa::2;
+ option dhcp6.time-offset -14400; # -4 hours
+ option docsis.cablelabs-configuration-file "bootfile.cfg";
+ option docsis.cablelabs-syslog-servers 3ffe:aaaa:aaaa:aaaa::1,
+ 3ffe:aaaa:aaaa:aaaa::2;
+}
+
+#host cablemodem-2 {
+# host-identifier option docsis.device-id 00:06:5B:50:99:F6;
+# option dhcp6.time-servers 3ffe:dddd:aaaa:aaaa::1,
+# 3ffe:dddd:aaaa:aaaa::2;
+# option docsis.tftp-servers 3ffe:dddd:aaaa:aaaa::1,
+# 3ffe:dddd:aaaa:aaaa::2;
+# option dhcp6.time-offset -14400; # -4 hours
+# option docsis.cablelabs-configuration-file "bootfile.cfg";
+# option docsis.cablelabs-syslog-servers 3ffe:aaaa:aaaa:aaaa::1,
+# 3ffe:aaaa:aaaa:aaaa::2;
+#}
+
+# XXX: for testing
+subnet6 3ffe:aaaa:aaaa:aaaa::/64 {
+}
+
+
diff --git a/keama/tests/samples/test-a6.conf.orig b/keama/tests/samples/test-a6.conf.orig
new file mode 100644
index 00000000..9fe42364
--- /dev/null
+++ b/keama/tests/samples/test-a6.conf.orig
@@ -0,0 +1,67 @@
+#
+# Define the DHCPv6 option space.
+#
+# Option numbers are assigned by IANA:
+# http://www.iana.org/assignments/dhcpv6-parameters
+#
+option dhcp6.time-servers code 40 = array of ip6-address;
+option dhcp6.time-offset code 41 = signed integer 32;
+
+#
+# Define the DOCSIS option space.
+# TODO: DOCSIS oro definition
+#
+option space docsis code width 2 length width 2;
+option vsio.docsis code 4491 = encapsulate docsis;
+option docsis.tftp-servers code 32 = array of ip6-address;
+option docsis.cablelabs-configuration-file code 33 = text;
+option docsis.cablelabs-syslog-servers code 34 = array of ip6-address;
+option docsis.device-id code 36 = string;
+
+#
+# Declare some options.
+#
+option dhcp6.time-servers 3ffe:bbbb:aaaa:aaaa::1, 3ffe:bbbb:aaaa:aaaa::2;
+option docsis.tftp-servers 3ffe:cccc:aaaa:aaaa::1, 3ffe:cccc:aaaa:aaaa::2;
+
+#
+# DNS server IP address to update dynamically
+#
+ddns-update-style interim;
+ddns-domainname "foo.com";
+
+#
+# Per-host settings.
+#
+host cablemodem-1 {
+ host-identifier option
+ dhcp6.client-id 00:01:00:01:0c:00:a1:41:00:06:5b:50:99:f6;
+ fixed-address6 3ffe:aaaa:aaaa:aaaa::ffff;
+ ddns-domainname "bar.com";
+ option dhcp6.time-servers 3ffe:aaaa:aaaa:aaaa::1,
+ 3ffe:aaaa:aaaa:aaaa::2;
+ option docsis.tftp-servers 3ffe:aaaa:aaaa:aaaa::1,
+ 3ffe:aaaa:aaaa:aaaa::2;
+ option dhcp6.time-offset -14400; # -4 hours
+ option docsis.cablelabs-configuration-file "bootfile.cfg";
+ option docsis.cablelabs-syslog-servers 3ffe:aaaa:aaaa:aaaa::1,
+ 3ffe:aaaa:aaaa:aaaa::2;
+}
+
+#host cablemodem-2 {
+# host-identifier option docsis.device-id 00:06:5B:50:99:F6;
+# option dhcp6.time-servers 3ffe:dddd:aaaa:aaaa::1,
+# 3ffe:dddd:aaaa:aaaa::2;
+# option docsis.tftp-servers 3ffe:dddd:aaaa:aaaa::1,
+# 3ffe:dddd:aaaa:aaaa::2;
+# option dhcp6.time-offset -14400; # -4 hours
+# option docsis.cablelabs-configuration-file "bootfile.cfg";
+# option docsis.cablelabs-syslog-servers 3ffe:aaaa:aaaa:aaaa::1,
+# 3ffe:aaaa:aaaa:aaaa::2;
+#}
+
+# XXX: for testing
+subnet6 fe80::20c:29ff:fe42:820/128 {
+}
+
+
diff --git a/keama/tests/samples/test-a6.json b/keama/tests/samples/test-a6.json
new file mode 100644
index 00000000..8b768686
--- /dev/null
+++ b/keama/tests/samples/test-a6.json
@@ -0,0 +1,144 @@
+{
+ #
+ # Define the DHCPv6 option space.
+ #
+ # Option numbers are assigned by IANA:
+ # http://www.iana.org/assignments/dhcpv6-parameters
+ #
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp6": {
+ "option-def": [
+ {
+ "space": "dhcp6",
+ "name": "time-servers",
+ "code": 1040,
+ "array": true,
+ "type": "ipv6-address"
+ },
+ {
+ "space": "dhcp6",
+ "name": "time-offset",
+ "code": 1041,
+ "type": "int32"
+ }
+ ],
+ "option-data": [
+ #option docsis.device-id code 36 = string;
+ #
+ # Declare some options.
+ #
+ {
+ "space": "dhcp6",
+ "name": "time-servers",
+ "code": 1040,
+ "data": "3ffe:bbbb:aaaa:aaaa::1, 3ffe:bbbb:aaaa:aaaa::2"
+ },
+ {
+ "space": "dhcp6",
+ "name": "vendor-opts",
+ "code": 17,
+ "data": "4491"
+ },
+ {
+ "space": "vendor-4491",
+ "name": "tftp-servers",
+ "code": 32,
+ "data": "3ffe:cccc:aaaa:aaaa::1, 3ffe:cccc:aaaa:aaaa::2"
+ }
+ ],
+// /// Unsupported ddns-update-style interim
+// "ddns-update-style": "interim",
+ "dhcp-ddns": {
+ "enable-updates": true,
+ "qualifying-suffix": "foo.com"
+ },
+ "host-reservation-identifiers": [
+ "flex-id"
+ ],
+ /// The flexible host identifier is a premium feature
+ "hooks-libraries": [
+ {
+ /// Please update the path here
+ "library": "/path/libdhcp_flex_id.so",
+ "parameters": {
+ "identifier-expression": "option[1].hex"
+ }
+ }
+ ],
+ "subnet6": [
+ #host cablemodem-2 {
+ # host-identifier option docsis.device-id 00:06:5B:50:99:F6;
+ # option dhcp6.time-servers 3ffe:dddd:aaaa:aaaa::1,
+ # 3ffe:dddd:aaaa:aaaa::2;
+ # option docsis.tftp-servers 3ffe:dddd:aaaa:aaaa::1,
+ # 3ffe:dddd:aaaa:aaaa::2;
+ # option dhcp6.time-offset -14400; # -4 hours
+ # option docsis.cablelabs-configuration-file "bootfile.cfg";
+ # option docsis.cablelabs-syslog-servers 3ffe:aaaa:aaaa:aaaa::1,
+ # 3ffe:aaaa:aaaa:aaaa::2;
+ #}
+ # XXX: for testing
+ {
+ "id": 1,
+ "subnet": "3ffe:aaaa:aaaa:aaaa::/64",
+ "reservations": [
+ #
+ # Per-host settings.
+ #
+ {
+ "hostname": "cablemodem-1",
+ "flex-id": "000100010c00a14100065b5099f6",
+ "ip-addresses": [
+ "3ffe:aaaa:aaaa:aaaa::ffff"
+ ],
+// /// Only global qualifying-suffix is supported
+// "qualifying-suffix": "bar.com",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "time-servers",
+ "code": 1040,
+// "original-data": "3ffe:aaaa:aaaa:aaaa::1, \n\t\t\t\t 3ffe:aaaa:aaaa:aaaa::2",
+ "data": "3ffe:aaaa:aaaa:aaaa::1, 3ffe:aaaa:aaaa:aaaa::2"
+ },
+ {
+ "space": "dhcp6",
+ "name": "vendor-opts",
+ "code": 17,
+ "data": "4491"
+ },
+ {
+ "space": "vendor-4491",
+ "name": "tftp-servers",
+ "code": 32,
+// "original-data": "3ffe:aaaa:aaaa:aaaa::1,\n\t\t\t\t 3ffe:aaaa:aaaa:aaaa::2",
+ "data": "3ffe:aaaa:aaaa:aaaa::1, 3ffe:aaaa:aaaa:aaaa::2"
+ },
+ {
+ "space": "dhcp6",
+ "name": "time-offset",
+ "code": 1041,
+ "data": "-14400"
+ },
+ # -4 hours
+ {
+ "space": "vendor-4491",
+ "name": "config-file",
+ "code": 33,
+ "data": "bootfile.cfg"
+ },
+ {
+ "space": "vendor-4491",
+ "name": "syslog-servers",
+ "code": 34,
+// "original-data": "3ffe:aaaa:aaaa:aaaa::1,\n\t\t\t\t\t 3ffe:aaaa:aaaa:aaaa::2",
+ "data": "3ffe:aaaa:aaaa:aaaa::1, 3ffe:aaaa:aaaa:aaaa::2"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/samples/vmnet8.conf b/keama/tests/samples/vmnet8.conf
new file mode 100644
index 00000000..9484938e
--- /dev/null
+++ b/keama/tests/samples/vmnet8.conf
@@ -0,0 +1,43 @@
+# Configuration file for ISC 2.0 vmnet-dhcpd operating on vmnet8.
+#
+# This file was automatically generated by the VMware configuration program.
+# See Instructions below if you want to modify it.
+#
+# We set domain-name-servers to make some DHCP clients happy
+# (dhclient as configured in SuSE, TurboLinux, etc.).
+# We also supply a domain name to make pump (Red Hat 6.x) happy.
+#
+
+
+###### VMNET DHCP Configuration. Start of "DO NOT MODIFY SECTION" #####
+# Modification Instructions: This section of the configuration file contains
+# information generated by the configuration program. Do not modify this
+# section.
+# You are free to modify everything else. Also, this section must start
+# on a new line
+# This file will get backed up with a different name in the same directory
+# if this section is edited and you try to configure DHCP again.
+
+# Written at: 04/12/2017 14:00:17
+allow unknown-clients;
+default-lease-time 1800; # default is 30 minutes
+max-lease-time 7200; # default is 2 hours
+
+subnet 172.16.254.0 netmask 255.255.255.0 {
+ range 172.16.254.128 172.16.254.254;
+ option broadcast-address 172.16.254.255;
+ option domain-name-servers 172.16.254.2;
+ option domain-name localdomain;
+ default-lease-time 1800; # default is 30 minutes
+ max-lease-time 7200; # default is 2 hours
+ option netbios-name-servers 172.16.254.2;
+ option routers 172.16.254.2;
+}
+host vmnet8 {
+ hardware ethernet 00:50:56:C0:00:08;
+ fixed-address 172.16.254.1;
+ option domain-name-servers 0.0.0.0;
+# option domain-name "";
+ option routers 0.0.0.0;
+}
+####### VMNET DHCP Configuration. End of "DO NOT MODIFY SECTION" #######
diff --git a/keama/tests/samples/vmnet8.conf.orig b/keama/tests/samples/vmnet8.conf.orig
new file mode 100644
index 00000000..313deca7
--- /dev/null
+++ b/keama/tests/samples/vmnet8.conf.orig
@@ -0,0 +1,43 @@
+# Configuration file for ISC 2.0 vmnet-dhcpd operating on vmnet8.
+#
+# This file was automatically generated by the VMware configuration program.
+# See Instructions below if you want to modify it.
+#
+# We set domain-name-servers to make some DHCP clients happy
+# (dhclient as configured in SuSE, TurboLinux, etc.).
+# We also supply a domain name to make pump (Red Hat 6.x) happy.
+#
+
+
+###### VMNET DHCP Configuration. Start of "DO NOT MODIFY SECTION" #####
+# Modification Instructions: This section of the configuration file contains
+# information generated by the configuration program. Do not modify this
+# section.
+# You are free to modify everything else. Also, this section must start
+# on a new line
+# This file will get backed up with a different name in the same directory
+# if this section is edited and you try to configure DHCP again.
+
+# Written at: 04/12/2017 14:00:17
+allow unknown-clients;
+default-lease-time 1800; # default is 30 minutes
+max-lease-time 7200; # default is 2 hours
+
+subnet 172.16.254.0 netmask 255.255.255.0 {
+ range 172.16.254.128 172.16.254.254;
+ option broadcast-address 172.16.254.255;
+ option domain-name-servers 172.16.254.2;
+ option domain-name localdomain;
+ default-lease-time 1800; # default is 30 minutes
+ max-lease-time 7200; # default is 2 hours
+ option netbios-name-servers 172.16.254.2;
+ option routers 172.16.254.2;
+}
+host vmnet8 {
+ hardware ethernet 00:50:56:C0:00:08;
+ fixed-address 172.16.254.1;
+ option domain-name-servers 0.0.0.0;
+ option domain-name "";
+ option routers 0.0.0.0;
+}
+####### VMNET DHCP Configuration. End of "DO NOT MODIFY SECTION" #######
diff --git a/keama/tests/samples/vmnet8.json b/keama/tests/samples/vmnet8.json
new file mode 100644
index 00000000..9b207ff8
--- /dev/null
+++ b/keama/tests/samples/vmnet8.json
@@ -0,0 +1,105 @@
+{
+ # Configuration file for ISC 2.0 vmnet-dhcpd operating on vmnet8.
+ #
+ # This file was automatically generated by the VMware configuration program.
+ # See Instructions below if you want to modify it.
+ #
+ # We set domain-name-servers to make some DHCP clients happy
+ # (dhclient as configured in SuSE, TurboLinux, etc.).
+ # We also supply a domain name to make pump (Red Hat 6.x) happy.
+ #
+ ###### VMNET DHCP Configuration. Start of "DO NOT MODIFY SECTION" #####
+ # Modification Instructions: This section of the configuration file contains
+ # information generated by the configuration program. Do not modify this
+ # section.
+ # You are free to modify everything else. Also, this section must start
+ # on a new line
+ # This file will get backed up with a different name in the same directory
+ # if this section is edited and you try to configure DHCP again.
+ # Written at: 04/12/2017 14:00:17
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+// "statement": {
+// "config": {
+// "value": "allow",
+// "name": "boot-unknown-clients",
+// "code": 6
+// }
+// },
+ "valid-lifetime": 1800,
+ "max-valid-lifetime": 7200,
+ "subnet4": [
+ # default is 2 hours
+ {
+ "id": 1,
+ "subnet": "172.16.254.0/24",
+ "pools": [
+ {
+ "pool": "172.16.254.128 - 172.16.254.254"
+ }
+ ],
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "broadcast-address",
+ "code": 28,
+ "data": "172.16.254.255"
+ },
+ {
+ "space": "dhcp4",
+ "name": "domain-name-servers",
+ "code": 6,
+ "data": "172.16.254.2"
+ },
+ {
+ "space": "dhcp4",
+ "name": "domain-name",
+ "code": 15,
+ "data": "localdomain"
+ },
+ # default is 2 hours
+ {
+ "space": "dhcp4",
+ "name": "netbios-name-servers",
+ "code": 44,
+ "data": "172.16.254.2"
+ },
+ {
+ "space": "dhcp4",
+ "name": "routers",
+ "code": 3,
+ "data": "172.16.254.2"
+ }
+ ],
+ "valid-lifetime": 1800,
+ "max-valid-lifetime": 7200,
+ "reservations": [
+ {
+ "hostname": "vmnet8",
+ "hw-address": "00:50:56:c0:00:08",
+ "ip-address": "172.16.254.1",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "domain-name-servers",
+ "code": 6,
+ "data": "0.0.0.0"
+ },
+ # option domain-name "";
+ {
+ "space": "dhcp4",
+ "name": "routers",
+ "code": 3,
+ "data": "0.0.0.0"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "host-reservation-identifiers": [
+ "hw-address"
+ ]
+ }
+}
diff --git a/keama/tests/share0.err b/keama/tests/share0.err
new file mode 100644
index 00000000..6b2184e4
--- /dev/null
+++ b/keama/tests/share0.err
@@ -0,0 +1,6 @@
+# bad (empty name)shared-network declaration config
+
+# shared-network declaration
+shared-network "" {
+
+}
diff --git a/keama/tests/share0.msg b/keama/tests/share0.msg
new file mode 100644
index 00000000..4296f4e8
--- /dev/null
+++ b/keama/tests/share0.msg
@@ -0,0 +1 @@
+share0.err line 4: zero-length shared network name
diff --git a/keama/tests/share2if.err b/keama/tests/share2if.err
new file mode 100644
index 00000000..2c40326d
--- /dev/null
+++ b/keama/tests/share2if.err
@@ -0,0 +1,7 @@
+# bad (2 interfaces)shared-network declaration config
+
+# shared-network declaration
+shared-network "foobar" {
+ interface "foo";
+ interface "bar";
+}
diff --git a/keama/tests/share2if.msg b/keama/tests/share2if.msg
new file mode 100644
index 00000000..bc4b9c10
--- /dev/null
+++ b/keama/tests/share2if.msg
@@ -0,0 +1 @@
+share2if.err line 6: A shared network can't be connected to two interfaces.
diff --git a/keama/tests/shareempty.err b/keama/tests/shareempty.err
new file mode 100644
index 00000000..2e9e3a12
--- /dev/null
+++ b/keama/tests/shareempty.err
@@ -0,0 +1,6 @@
+# bad (no subnet) shared-network declaration config
+
+# shared-network declaration
+shared-network "foobar" {
+ interface "foo";
+}
diff --git a/keama/tests/shareempty.msg b/keama/tests/shareempty.msg
new file mode 100644
index 00000000..fb74cc62
--- /dev/null
+++ b/keama/tests/shareempty.msg
@@ -0,0 +1 @@
+shareempty.err line 6: empty shared-network decl
diff --git a/keama/tests/shareinclass.err b/keama/tests/shareinclass.err
new file mode 100644
index 00000000..d68e724e
--- /dev/null
+++ b/keama/tests/shareinclass.err
@@ -0,0 +1,11 @@
+# shared-network declaration inside class declaration config
+
+# class declaration
+class "foobar" {
+ hardware ethernet 00:0B:FD:32:E6:FA;
+ # can't put a shared-network declaration here
+ shared-network "illegal" {
+ default-lease-time 1800;
+ }
+}
+
diff --git a/keama/tests/shareinclass.msg b/keama/tests/shareinclass.msg
new file mode 100644
index 00000000..656898c2
--- /dev/null
+++ b/keama/tests/shareinclass.msg
@@ -0,0 +1 @@
+shareinclass.err line 5: hardware address parameter not allowed here.
diff --git a/keama/tests/shareinhost.err b/keama/tests/shareinhost.err
new file mode 100644
index 00000000..93b049c7
--- /dev/null
+++ b/keama/tests/shareinhost.err
@@ -0,0 +1,11 @@
+# shared-network declaration inside host declaration config
+
+# host declaration
+host foobar {
+ hardware ethernet 00:0B:FD:32:E6:FA;
+ # can't put a shared-network declaration here
+ shared-network "illegal" {
+ default-lease-time 1800;
+ }
+}
+
diff --git a/keama/tests/shareinhost.msg b/keama/tests/shareinhost.msg
new file mode 100644
index 00000000..22da166d
--- /dev/null
+++ b/keama/tests/shareinhost.msg
@@ -0,0 +1 @@
+shareinhost.err line 7: shared-network parameters not allowed here.
diff --git a/keama/tests/shareinshare.err b/keama/tests/shareinshare.err
new file mode 100644
index 00000000..68750613
--- /dev/null
+++ b/keama/tests/shareinshare.err
@@ -0,0 +1,10 @@
+# shared-network declaration inside shared-network declaration config
+
+# shared-network declaration
+shared-network "foobar" {
+ # can't put another shared-network declaration here
+ shared-network "illegal" {
+ default-lease-time 1800;
+ }
+}
+
diff --git a/keama/tests/shareinshare.msg b/keama/tests/shareinshare.msg
new file mode 100644
index 00000000..a2579e8e
--- /dev/null
+++ b/keama/tests/shareinshare.msg
@@ -0,0 +1 @@
+shareinshare.err line 6: shared-network parameters not allowed here.
diff --git a/keama/tests/shareinsubnet4.err4 b/keama/tests/shareinsubnet4.err4
new file mode 100644
index 00000000..ecc0d080
--- /dev/null
+++ b/keama/tests/shareinsubnet4.err4
@@ -0,0 +1,10 @@
+# shared-network declaration inside DHCPv4 subnet declaration config
+
+# subnet declaration
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ # can't put a shared-network declaration here
+ shared-network "illegal" {
+ default-lease-time 1800;
+ }
+}
+
diff --git a/keama/tests/shareinsubnet4.msg b/keama/tests/shareinsubnet4.msg
new file mode 100644
index 00000000..c332da87
--- /dev/null
+++ b/keama/tests/shareinsubnet4.msg
@@ -0,0 +1 @@
+shareinsubnet4.err4 line 6: shared-network parameters not allowed here.
diff --git a/keama/tests/shareinsubnet6.err6 b/keama/tests/shareinsubnet6.err6
new file mode 100644
index 00000000..fec6f6d2
--- /dev/null
+++ b/keama/tests/shareinsubnet6.err6
@@ -0,0 +1,10 @@
+# shared-network declaration inside DHCPv6 subnet declaration config
+
+# subnet declaration
+subnet6 2001::/64 {
+ # can't put a shared-network declaration here
+ shared-network "illegal" {
+ default-lease-time 1800;
+ }
+}
+
diff --git a/keama/tests/shareinsubnet6.msg b/keama/tests/shareinsubnet6.msg
new file mode 100644
index 00000000..640e5323
--- /dev/null
+++ b/keama/tests/shareinsubnet6.msg
@@ -0,0 +1 @@
+shareinsubnet6.err6 line 6: shared-network parameters not allowed here.
diff --git a/keama/tests/sharenoname.err b/keama/tests/sharenoname.err
new file mode 100644
index 00000000..854a1728
--- /dev/null
+++ b/keama/tests/sharenoname.err
@@ -0,0 +1,6 @@
+# bad (no name)shared-network declaration config
+
+# shared-network declaration
+shared-network {
+
+}
diff --git a/keama/tests/sharenoname.msg b/keama/tests/sharenoname.msg
new file mode 100644
index 00000000..ad12f0d7
--- /dev/null
+++ b/keama/tests/sharenoname.msg
@@ -0,0 +1 @@
+sharenoname.err line 4: expecting a name for shared-network
diff --git a/keama/tests/shareone4.in4 b/keama/tests/shareone4.in4
new file mode 100644
index 00000000..ef0e15ac
--- /dev/null
+++ b/keama/tests/shareone4.in4
@@ -0,0 +1,22 @@
+# DHCPv4 one-subnet shared-network declaration config
+
+# shared-network declaration
+shared-network "foobar" {
+ # interface
+ interface "en0";
+ # option
+ option domain-search "example.com", "example.org";
+ # parameter
+ default-lease-time 1800;
+ # subnet declaration
+ subnet 10.5.5.0 netmask 255.255.255.224 {
+ # redefined parameter
+ default-lease-time 3600;
+ # another option
+ option ip-forwarding true;
+ }
+ # pool (must be after the subnet)
+ pool {
+ range 10.5.5.5 10.5.5.10;
+ }
+}
diff --git a/keama/tests/shareone4.out b/keama/tests/shareone4.out
new file mode 100644
index 00000000..56f5cbb7
--- /dev/null
+++ b/keama/tests/shareone4.out
@@ -0,0 +1,44 @@
+{
+ # DHCPv4 one-subnet shared-network declaration config
+ # shared-network declaration
+ "Dhcp4": {
+ "interfaces-config": {
+ "interfaces": [
+ "en0"
+ ]
+ },
+ "subnet4": [
+ # subnet declaration
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ "valid-lifetime": 3600,
+ "option-data": [
+ # another option
+ {
+ "space": "dhcp4",
+ "name": "ip-forwarding",
+ "code": 19,
+ "data": "true"
+ },
+ # interface
+ # option
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "interface": "en0",
+ "pools": [
+ # pool (must be after the subnet)
+ {
+ "pool": "10.5.5.5 - 10.5.5.10"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/shareone6.in6 b/keama/tests/shareone6.in6
new file mode 100644
index 00000000..11b03cb0
--- /dev/null
+++ b/keama/tests/shareone6.in6
@@ -0,0 +1,19 @@
+# DHCPv6 one-subnet shared-network declaration config
+
+# shared-network declaration
+shared-network "foobar" {
+ # interface
+ interface "en0";
+ # option
+ option dhcp6.domain-search "example.com", "example.org";
+ # parameter
+ default-lease-time 1800;
+ # subnet declaration
+ subnet6 2001::/64 {
+ # redefined parameter
+ default-lease-time 3600;
+ # pool
+ range6 2001::1000 2001::2000;
+ }
+ # tried another pool here but DHCPv6 pools are allowed only in subnets
+}
diff --git a/keama/tests/shareone6.out b/keama/tests/shareone6.out
new file mode 100644
index 00000000..6f8389c3
--- /dev/null
+++ b/keama/tests/shareone6.out
@@ -0,0 +1,37 @@
+{
+ # DHCPv6 one-subnet shared-network declaration config
+ # shared-network declaration
+ "Dhcp6": {
+ "interfaces-config": {
+ "interfaces": [
+ "en0"
+ ]
+ },
+ "subnet6": [
+ # subnet declaration
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "valid-lifetime": 3600,
+ "pools": [
+ {
+ # pool
+ "pool": "2001::1000 - 2001::2000"
+ }
+ ],
+ "interface": "en0",
+ "option-data": [
+ # interface
+ # option
+ {
+ "space": "dhcp6",
+ "name": "domain-search",
+ "code": 24,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/sharepools4.in4 b/keama/tests/sharepools4.in4
new file mode 100644
index 00000000..b42a8481
--- /dev/null
+++ b/keama/tests/sharepools4.in4
@@ -0,0 +1,27 @@
+# DHCPv4 two pools and subnets shared-network declaration config
+
+# shared-network declaration
+shared-network "foobar" {
+ # interface
+ interface "en0";
+ # option
+ option domain-search "example.com", "example.org";
+ # parameter
+ default-lease-time 1800;
+ # subnet declaration
+ subnet 10.5.5.0 netmask 255.255.255.224 {
+ # redefined parameter
+ default-lease-time 3600;
+ # another option
+ option ip-forwarding true;
+ }
+ # second subnet declaration
+ subnet 10.10.10.0 netmask 255.255.255.224 { }
+ # pools at shared-network level
+ pool {
+ range 10.5.5.5 10.5.5.10;
+ }
+ pool {
+ range 10.10.10.5 10.10.10.10;
+ }
+}
diff --git a/keama/tests/sharepools4.out b/keama/tests/sharepools4.out
new file mode 100644
index 00000000..087ec5c8
--- /dev/null
+++ b/keama/tests/sharepools4.out
@@ -0,0 +1,63 @@
+{
+ # DHCPv4 two pools and subnets shared-network declaration config
+ # shared-network declaration
+ "Dhcp4": {
+ "interfaces-config": {
+ "interfaces": [
+ "en0"
+ ]
+ },
+ /// Kea shared-networks are different, cf Kea #236
+ "shared-networks": [
+ {
+ "name": "foobar",
+ "subnet4": [
+ # subnet declaration
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ "valid-lifetime": 3600,
+ "option-data": [
+ # another option
+ {
+ "space": "dhcp4",
+ "name": "ip-forwarding",
+ "code": 19,
+ "data": "true"
+ }
+ ],
+ "pools": [
+ # pools at shared-network level
+ {
+ "pool": "10.5.5.5 - 10.5.5.10"
+ }
+ ]
+ },
+ # second subnet declaration
+ {
+ "id": 2,
+ "subnet": "10.10.10.0/27",
+ "pools": [
+ {
+ "pool": "10.10.10.5 - 10.10.10.10"
+ }
+ ]
+ }
+ ],
+ "interface": "en0",
+ "option-data": [
+ # interface
+ # option
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "valid-lifetime": 1800
+ }
+ ]
+ }
+}
diff --git a/keama/tests/sharetwo4.in4 b/keama/tests/sharetwo4.in4
new file mode 100644
index 00000000..fa9126fe
--- /dev/null
+++ b/keama/tests/sharetwo4.in4
@@ -0,0 +1,29 @@
+# DHCPv4 two subnets in shared-network declaration config
+
+# shared-network declaration
+shared-network "foobar" {
+ # interface
+ interface "en0";
+ # option
+ option domain-search "example.com", "example.org";
+ # parameter
+ default-lease-time 1800;
+ # subnet declaration
+ subnet 10.5.5.0 netmask 255.255.255.224 {
+ # redefined parameter
+ default-lease-time 3600;
+ # another option
+ option ip-forwarding true;
+ # pool inside the subnet
+ pool {
+ range 10.5.5.5 10.5.5.10;
+ }
+ }
+ # second subnet declaration
+ subnet 10.10.10.0 netmask 255.255.255.224 {
+ # pool inside the subnet
+ pool {
+ range 10.10.10.5 10.10.10.10;
+ }
+ }
+}
diff --git a/keama/tests/sharetwo4.out b/keama/tests/sharetwo4.out
new file mode 100644
index 00000000..71f7832e
--- /dev/null
+++ b/keama/tests/sharetwo4.out
@@ -0,0 +1,64 @@
+{
+ # DHCPv4 two subnets in shared-network declaration config
+ # shared-network declaration
+ "Dhcp4": {
+ "interfaces-config": {
+ "interfaces": [
+ "en0"
+ ]
+ },
+ /// Kea shared-networks are different, cf Kea #236
+ "shared-networks": [
+ {
+ "name": "foobar",
+ "subnet4": [
+ # subnet declaration
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ "valid-lifetime": 3600,
+ "option-data": [
+ # another option
+ {
+ "space": "dhcp4",
+ "name": "ip-forwarding",
+ "code": 19,
+ "data": "true"
+ }
+ ],
+ "pools": [
+ # pool inside the subnet
+ {
+ "pool": "10.5.5.5 - 10.5.5.10"
+ }
+ ]
+ },
+ # second subnet declaration
+ {
+ "id": 2,
+ "subnet": "10.10.10.0/27",
+ "pools": [
+ # pool inside the subnet
+ {
+ "pool": "10.10.10.5 - 10.10.10.10"
+ }
+ ]
+ }
+ ],
+ "interface": "en0",
+ "option-data": [
+ # interface
+ # option
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "valid-lifetime": 1800
+ }
+ ]
+ }
+}
diff --git a/keama/tests/sharetwo6.in6 b/keama/tests/sharetwo6.in6
new file mode 100644
index 00000000..3c75aad5
--- /dev/null
+++ b/keama/tests/sharetwo6.in6
@@ -0,0 +1,24 @@
+# DHCPv6 two subnets in shared-network declaration config
+
+# shared-network declaration
+shared-network "foobar" {
+ # interface
+ interface "en0";
+ # option
+ option dhcp6.domain-search "example.com", "example.org";
+ # parameter
+ default-lease-time 1800;
+ # subnet declaration
+ subnet6 2001::/64 {
+ # redefined parameter
+ default-lease-time 3600;
+ # pool
+ range6 2001::1000 2001::2000;
+ }
+ # second subnet declaration
+ subnet6 2002::/64 {
+ pool6 {
+ prefix6 2001:0:0:10:: 2001:0:0:1f:: /64;
+ }
+ }
+}
diff --git a/keama/tests/sharetwo6.out b/keama/tests/sharetwo6.out
new file mode 100644
index 00000000..23df4e7c
--- /dev/null
+++ b/keama/tests/sharetwo6.out
@@ -0,0 +1,56 @@
+{
+ # DHCPv6 two subnets in shared-network declaration config
+ # shared-network declaration
+ "Dhcp6": {
+ "interfaces-config": {
+ "interfaces": [
+ "en0"
+ ]
+ },
+ /// Kea shared-networks are different, cf Kea #236
+ "shared-networks": [
+ {
+ "name": "foobar",
+ "subnet6": [
+ # subnet declaration
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "valid-lifetime": 3600,
+ "pools": [
+ {
+ # pool
+ "pool": "2001::1000 - 2001::2000"
+ }
+ ]
+ },
+ # second subnet declaration
+ {
+ "id": 2,
+ "subnet": "2002::/64",
+ "pd-pools": [
+ {
+ "prefix": "2001:0:0:10::",
+ "delegated-len": 64,
+ "prefix-len": 60
+ }
+ ]
+ }
+ ],
+ "interface": "en0",
+ "option-data": [
+ # interface
+ # option
+ {
+ "space": "dhcp6",
+ "name": "domain-search",
+ "code": 24,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "valid-lifetime": 1800
+ }
+ ]
+ }
+}
diff --git a/keama/tests/sname4.notyet b/keama/tests/sname4.notyet
new file mode 100644
index 00000000..172b2acf
--- /dev/null
+++ b/keama/tests/sname4.notyet
@@ -0,0 +1,20 @@
+# server-name data expression
+# Kea has no server-name extractor in libeval
+
+# authoritative is mandatory
+authoritative;
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# by filename superclass
+class "byfn" {
+ match server-name;
+}
+
+subclass "byfn" "foobar" {
+ option domain-name-servers 10.5.5.1;
+}
+
+# raw
+option bootfile-name = concat(server-name, "/", option host-name);
diff --git a/keama/tests/spawning6.in6 b/keama/tests/spawning6.in6
new file mode 100644
index 00000000..0c33afe1
--- /dev/null
+++ b/keama/tests/spawning6.in6
@@ -0,0 +1,11 @@
+# spawning declaration config
+
+# options
+option dhcp6.mysystem code 1250 = text;
+option dhcp6.myversion code 1251 = unsigned integer 16;
+
+# superclass declaration
+class "foobar" {
+ spawn with option dhcp6.mysystem;
+ option dhcp6.myversion 1;
+}
diff --git a/keama/tests/spawning6.out b/keama/tests/spawning6.out
new file mode 100644
index 00000000..ce556d80
--- /dev/null
+++ b/keama/tests/spawning6.out
@@ -0,0 +1,37 @@
+{
+ # spawning declaration config
+ # options
+ "Dhcp6": {
+ "option-def": [
+ {
+ "space": "dhcp6",
+ "name": "mysystem",
+ "code": 1250,
+ "type": "string"
+ },
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "type": "uint16"
+ }
+ ],
+ "client-classes": [
+ # superclass declaration
+ /// Spawning classes are not supported by Kea
+ /// Reference Kea #248
+ /// spawn with: option dhcp6.mysystem
+ {
+ "name": "foobar",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "data": "1"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/subclass4.in4 b/keama/tests/subclass4.in4
new file mode 100644
index 00000000..bdae6632
--- /dev/null
+++ b/keama/tests/subclass4.in4
@@ -0,0 +1,23 @@
+# subclass declaration config
+
+# options
+option mysystem code 250 = text;
+option myversion code 251 = unsigned integer 16;
+
+# superclass declaration
+class "foobar" {
+ match option mysystem;
+ option myversion 1;
+}
+
+# simple subclass declaration
+subclass "foobar" "version1";
+
+# option setting subclass declaration
+subclass "foobar" "version2" { option myversion 2; }
+
+# complex subclass declaration
+subclass "foobar" "version3" {
+ option myversion 3;
+ next-server 192.168.0.1;
+}
diff --git a/keama/tests/subclass4.out b/keama/tests/subclass4.out
new file mode 100644
index 00000000..e155d0de
--- /dev/null
+++ b/keama/tests/subclass4.out
@@ -0,0 +1,84 @@
+{
+ # subclass declaration config
+ # options
+ "Dhcp4": {
+ "option-def": [
+ {
+ "space": "dhcp4",
+ "name": "mysystem",
+ "code": 250,
+ "type": "string"
+ },
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "type": "uint16"
+ }
+ ],
+ "client-classes": [
+ # superclass declaration
+ /// match: option dhcp.mysystem
+ {
+ "name": "foobar",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "1"
+ }
+ ]
+ },
+ # simple subclass declaration
+ /// subclass selector 'version1'
+ {
+ "name": "sub#foobar#0",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "1"
+ }
+ ],
+ /// from: match option dhcp.mysystem
+ /// data: 'version1'
+ "test": "option[250].hex == 'version1'"
+ },
+ # option setting subclass declaration
+ /// subclass selector 'version2'
+ {
+ "name": "sub#foobar#1",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "2"
+ }
+ ],
+ /// from: match option dhcp.mysystem
+ /// data: 'version2'
+ "test": "option[250].hex == 'version2'"
+ },
+ # complex subclass declaration
+ /// subclass selector 'version3'
+ {
+ "name": "sub#foobar#2",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "3"
+ }
+ ],
+ "next-server": "192.168.0.1",
+ /// from: match option dhcp.mysystem
+ /// data: 'version3'
+ "test": "option[250].hex == 'version3'"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/subclass6.in6 b/keama/tests/subclass6.in6
new file mode 100644
index 00000000..41408180
--- /dev/null
+++ b/keama/tests/subclass6.in6
@@ -0,0 +1,23 @@
+# subclass declaration config
+
+# options
+option dhcp6.mysystem code 1250 = text;
+option dhcp6.myversion code 1251 = unsigned integer 16;
+
+# superclass declaration
+class "foobar" {
+ match option dhcp6.mysystem;
+ option dhcp6.myversion 1;
+}
+
+# simple subclass declaration
+subclass "foobar" "version1";
+
+# option setting subclass declaration
+subclass "foobar" "version2" { option dhcp6.myversion 2; }
+
+# complex subclass declaration
+subclass "foobar" "version3" {
+ option dhcp6.myversion 3;
+ option dhcp6.rapid-commit;
+}
diff --git a/keama/tests/subclass6.out b/keama/tests/subclass6.out
new file mode 100644
index 00000000..3828d58d
--- /dev/null
+++ b/keama/tests/subclass6.out
@@ -0,0 +1,89 @@
+{
+ # subclass declaration config
+ # options
+ "Dhcp6": {
+ "option-def": [
+ {
+ "space": "dhcp6",
+ "name": "mysystem",
+ "code": 1250,
+ "type": "string"
+ },
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "type": "uint16"
+ }
+ ],
+ "client-classes": [
+ # superclass declaration
+ /// match: option dhcp6.mysystem
+ {
+ "name": "foobar",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "data": "1"
+ }
+ ]
+ },
+ # simple subclass declaration
+ /// subclass selector 'version1'
+ {
+ "name": "sub#foobar#0",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "data": "1"
+ }
+ ],
+ /// from: match option dhcp6.mysystem
+ /// data: 'version1'
+ "test": "option[1250].hex == 'version1'"
+ },
+ # option setting subclass declaration
+ /// subclass selector 'version2'
+ {
+ "name": "sub#foobar#1",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "data": "2"
+ }
+ ],
+ /// from: match option dhcp6.mysystem
+ /// data: 'version2'
+ "test": "option[1250].hex == 'version2'"
+ },
+ # complex subclass declaration
+ /// subclass selector 'version3'
+ {
+ "name": "sub#foobar#2",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "data": "3"
+ },
+ {
+ "space": "dhcp6",
+ "name": "rapid-commit",
+ "code": 14,
+ "data": ""
+ }
+ ],
+ /// from: match option dhcp6.mysystem
+ /// data: 'version3'
+ "test": "option[1250].hex == 'version3'"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/subclassbinsel4.in4 b/keama/tests/subclassbinsel4.in4
new file mode 100644
index 00000000..95d5aa54
--- /dev/null
+++ b/keama/tests/subclassbinsel4.in4
@@ -0,0 +1,16 @@
+# subclass declaration config
+
+# options
+option myversion code 251 = unsigned integer 16;
+
+# superclass declaration
+class "foobar" {
+ match hardware;
+ option myversion 1;
+}
+
+# simple subclass declaration
+subclass "foobar" 1:00:07:0E:36:48:19;
+
+# option setting subclass declaration
+subclass "foobar" 1:00:0B:FD:32:E6:FA { option myversion 2; }
diff --git a/keama/tests/subclassbinsel4.out b/keama/tests/subclassbinsel4.out
new file mode 100644
index 00000000..589afcb8
--- /dev/null
+++ b/keama/tests/subclassbinsel4.out
@@ -0,0 +1,61 @@
+{
+ # subclass declaration config
+ # options
+ "Dhcp4": {
+ "option-def": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "type": "uint16"
+ }
+ ],
+ "client-classes": [
+ # superclass declaration
+ /// match: hardware
+ {
+ "name": "foobar",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "1"
+ }
+ ]
+ },
+ # simple subclass declaration
+ /// subclass selector 0x0x0100070e364819
+ {
+ "name": "sub#foobar#0",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "1"
+ }
+ ],
+ /// from: match hardware
+ /// data: 0x0100070e364819
+ "test": "concat(substring(pkt4.htype,-1,all), pkt4.mac) == 0x0100070e364819"
+ },
+ # option setting subclass declaration
+ /// subclass selector 0x0x01000bfd32e6fa
+ {
+ "name": "sub#foobar#1",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "data": "2"
+ }
+ ],
+ /// from: match hardware
+ /// data: 0x01000bfd32e6fa
+ "test": "concat(substring(pkt4.htype,-1,all), pkt4.mac) == 0x01000bfd32e6fa"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/subclassbinsel6.in6 b/keama/tests/subclassbinsel6.in6
new file mode 100644
index 00000000..0a7e36d7
--- /dev/null
+++ b/keama/tests/subclassbinsel6.in6
@@ -0,0 +1,25 @@
+# subclass declaration config
+
+# options
+option dhcp6.hardware code 1250 = string;
+option dhcp6.myversion code 1251 = unsigned integer 16;
+
+# superclass declaration
+class "foobar" {
+ # no harware in DHCPv6
+ match option dhcp6.hardware;
+ option dhcp6.myversion 1;
+}
+
+# simple subclass declaration
+subclass "foobar" 1:00:07:0E:36:48:19;
+
+# option setting subclass declaration
+subclass "foobar" 1:00:0B:FD:32:E6:FA { option dhcp6.myversion 2; }
+
+# complex subclass declaration
+subclass "foobar" 1:00:02:B3:88:C5:27 {
+ option dhcp6.myversion 3;
+ lease limit 20;
+}
+
diff --git a/keama/tests/subclassbinsel6.out b/keama/tests/subclassbinsel6.out
new file mode 100644
index 00000000..42c96bb2
--- /dev/null
+++ b/keama/tests/subclassbinsel6.out
@@ -0,0 +1,88 @@
+{
+ # subclass declaration config
+ # options
+ "Dhcp6": {
+ "option-def": [
+ {
+ "space": "dhcp6",
+ "name": "hardware",
+ "code": 1250,
+ "type": "string"
+ },
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "type": "uint16"
+ }
+ ],
+ "client-classes": [
+ # superclass declaration
+ /// match: option dhcp6.hardware
+ {
+ "name": "foobar",
+ "option-data": [
+ # no harware in DHCPv6
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "data": "1"
+ }
+ ]
+ },
+ # simple subclass declaration
+ /// subclass selector 0x0x0100070e364819
+ {
+ "name": "sub#foobar#0",
+ "option-data": [
+ # no harware in DHCPv6
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "data": "1"
+ }
+ ],
+ /// from: match option dhcp6.hardware
+ /// data: 0x0100070e364819
+ "test": "option[1250].hex == 0x0100070e364819"
+ },
+ # option setting subclass declaration
+ /// subclass selector 0x0x01000bfd32e6fa
+ {
+ "name": "sub#foobar#1",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "data": "2"
+ }
+ ],
+ /// from: match option dhcp6.hardware
+ /// data: 0x01000bfd32e6fa
+ "test": "option[1250].hex == 0x01000bfd32e6fa"
+ },
+ # complex subclass declaration
+ /// subclass selector 0x0x010002b388c527
+ {
+ "name": "sub#foobar#2",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "data": "3"
+ }
+ ],
+// /// Per-class limit is not supported by Kea
+// /// Reference Kea #237
+// "lease-limit": 20,
+ /// from: match option dhcp6.hardware
+ /// data: 0x010002b388c527
+ "test": "option[1250].hex == 0x010002b388c527"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/subclassguard4.in4 b/keama/tests/subclassguard4.in4
new file mode 100644
index 00000000..9a22d356
--- /dev/null
+++ b/keama/tests/subclassguard4.in4
@@ -0,0 +1,24 @@
+# subclass with guard declaration config
+
+# options
+option mysystem code 250 = text;
+option myversion code 251 = unsigned integer 16;
+option mydescr code 252 = text;
+
+# superclass declaration
+class "foobar" {
+ match if option myversion = 0:1;
+ match option mysystem;
+}
+
+# simple subclass declaration
+subclass "foobar" "system1";
+
+# option setting subclass declaration
+subclass "foobar" "system2" { option mydescr "1.2"; }
+
+# complex subclass declaration
+subclass "foobar" "system3" {
+ option mydescr "1.3";
+ next-server 192.168.0.1;
+}
diff --git a/keama/tests/subclassguard4.out b/keama/tests/subclassguard4.out
new file mode 100644
index 00000000..910b5b93
--- /dev/null
+++ b/keama/tests/subclassguard4.out
@@ -0,0 +1,79 @@
+{
+ # subclass with guard declaration config
+ # options
+ "Dhcp4": {
+ "option-def": [
+ {
+ "space": "dhcp4",
+ "name": "mysystem",
+ "code": 250,
+ "type": "string"
+ },
+ {
+ "space": "dhcp4",
+ "name": "myversion",
+ "code": 251,
+ "type": "uint16"
+ },
+ {
+ "space": "dhcp4",
+ "name": "mydescr",
+ "code": 252,
+ "type": "string"
+ }
+ ],
+ "client-classes": [
+ # superclass declaration
+ /// match: option dhcp.mysystem
+ {
+ "name": "foobar",
+ /// from: match if (option dhcp.myversion) = 0x0001
+ "test": "option[251].hex == 0x0001"
+ },
+ # simple subclass declaration
+ /// subclass selector 'system1'
+ {
+ "name": "sub#foobar#0",
+ /// from: match-if (option dhcp.myversion) = 0x0001
+ /// match: option dhcp.mysystem
+ /// data: 'system1'
+ "test": "(option[251].hex == 0x0001) and (option[250].hex == 'system1')"
+ },
+ # option setting subclass declaration
+ /// subclass selector 'system2'
+ {
+ "name": "sub#foobar#1",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "mydescr",
+ "code": 252,
+ "data": "1.2"
+ }
+ ],
+ /// from: match-if (option dhcp.myversion) = 0x0001
+ /// match: option dhcp.mysystem
+ /// data: 'system2'
+ "test": "(option[251].hex == 0x0001) and (option[250].hex == 'system2')"
+ },
+ # complex subclass declaration
+ /// subclass selector 'system3'
+ {
+ "name": "sub#foobar#2",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "mydescr",
+ "code": 252,
+ "data": "1.3"
+ }
+ ],
+ "next-server": "192.168.0.1",
+ /// from: match-if (option dhcp.myversion) = 0x0001
+ /// match: option dhcp.mysystem
+ /// data: 'system3'
+ "test": "(option[251].hex == 0x0001) and (option[250].hex == 'system3')"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/subclassguard6.in6 b/keama/tests/subclassguard6.in6
new file mode 100644
index 00000000..5c2119a6
--- /dev/null
+++ b/keama/tests/subclassguard6.in6
@@ -0,0 +1,24 @@
+# subclass with guard declaration config
+
+# options
+option dhcp6.mysystem code 1250 = text;
+option dhcp6.myversion code 1251 = unsigned integer 16;
+option dhcp6.mydescr code 1252 = text;
+
+# superclass declaration
+class "foobar" {
+ match if option dhcp6.myversion = 0:1;
+ match option dhcp6.mysystem;
+}
+
+# simple subclass declaration
+subclass "foobar" "system1";
+
+# option setting subclass declaration
+subclass "foobar" "system2" { option dhcp6.mydescr "1.2"; }
+
+# complex subclass declaration
+subclass "foobar" "system3" {
+ option dhcp6.mydescr "1.3";
+ option dhcp6.rapid-commit;
+}
diff --git a/keama/tests/subclassguard6.out b/keama/tests/subclassguard6.out
new file mode 100644
index 00000000..26a9d4dc
--- /dev/null
+++ b/keama/tests/subclassguard6.out
@@ -0,0 +1,84 @@
+{
+ # subclass with guard declaration config
+ # options
+ "Dhcp6": {
+ "option-def": [
+ {
+ "space": "dhcp6",
+ "name": "mysystem",
+ "code": 1250,
+ "type": "string"
+ },
+ {
+ "space": "dhcp6",
+ "name": "myversion",
+ "code": 1251,
+ "type": "uint16"
+ },
+ {
+ "space": "dhcp6",
+ "name": "mydescr",
+ "code": 1252,
+ "type": "string"
+ }
+ ],
+ "client-classes": [
+ # superclass declaration
+ /// match: option dhcp6.mysystem
+ {
+ "name": "foobar",
+ /// from: match if (option dhcp6.myversion) = 0x0001
+ "test": "option[1251].hex == 0x0001"
+ },
+ # simple subclass declaration
+ /// subclass selector 'system1'
+ {
+ "name": "sub#foobar#0",
+ /// from: match-if (option dhcp6.myversion) = 0x0001
+ /// match: option dhcp6.mysystem
+ /// data: 'system1'
+ "test": "(option[1251].hex == 0x0001) and (option[1250].hex == 'system1')"
+ },
+ # option setting subclass declaration
+ /// subclass selector 'system2'
+ {
+ "name": "sub#foobar#1",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "mydescr",
+ "code": 1252,
+ "data": "1.2"
+ }
+ ],
+ /// from: match-if (option dhcp6.myversion) = 0x0001
+ /// match: option dhcp6.mysystem
+ /// data: 'system2'
+ "test": "(option[1251].hex == 0x0001) and (option[1250].hex == 'system2')"
+ },
+ # complex subclass declaration
+ /// subclass selector 'system3'
+ {
+ "name": "sub#foobar#2",
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "mydescr",
+ "code": 1252,
+ "data": "1.3"
+ },
+ {
+ "space": "dhcp6",
+ "name": "rapid-commit",
+ "code": 14,
+ "data": ""
+ }
+ ],
+ /// from: match-if (option dhcp6.myversion) = 0x0001
+ /// match: option dhcp6.mysystem
+ /// data: 'system3'
+ "test": "(option[1251].hex == 0x0001) and (option[1250].hex == 'system3')"
+ }
+ ]
+ }
+}
diff --git a/keama/tests/subnet4.in4 b/keama/tests/subnet4.in4
new file mode 100644
index 00000000..9c9247a4
--- /dev/null
+++ b/keama/tests/subnet4.in4
@@ -0,0 +1,16 @@
+# DHCPv4 subnet declaration config
+
+# parameter which will be changed in subnet
+default-lease-time 1800;
+
+# DHCPv4 subnet declaration
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ # at least one pool is required
+ pool {
+ range 10.5.5.5 10.5.5.10;
+ }
+ option domain-search "example.com", "example.org";
+ default-lease-time 3600;
+ ignore-client-uids false;
+}
+
diff --git a/keama/tests/subnet4.out b/keama/tests/subnet4.out
new file mode 100644
index 00000000..2b50c272
--- /dev/null
+++ b/keama/tests/subnet4.out
@@ -0,0 +1,33 @@
+{
+ # DHCPv4 subnet declaration config
+ # parameter which will be changed in subnet
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "subnet4": [
+ # DHCPv4 subnet declaration
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ "pools": [
+ # at least one pool is required
+ {
+ "pool": "10.5.5.5 - 10.5.5.10"
+ }
+ ],
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "valid-lifetime": 3600,
+ "match-client-id": true
+ }
+ ]
+ }
+}
diff --git a/keama/tests/subnet42if.err4 b/keama/tests/subnet42if.err4
new file mode 100644
index 00000000..b65c5d72
--- /dev/null
+++ b/keama/tests/subnet42if.err4
@@ -0,0 +1,8 @@
+# bad (2 interfaces) DHCPv4 subnet declaration config
+
+# DHCPv4 subnet declaration
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ interface "foo";
+ interface "bar";
+}
+
diff --git a/keama/tests/subnet42if.msg b/keama/tests/subnet42if.msg
new file mode 100644
index 00000000..22cd15dc
--- /dev/null
+++ b/keama/tests/subnet42if.msg
@@ -0,0 +1 @@
+subnet42if.err4 line 6: A subnet can't be connected to two interfaces.
diff --git a/keama/tests/subnet4auth.in4 b/keama/tests/subnet4auth.in4
new file mode 100644
index 00000000..dd7750d7
--- /dev/null
+++ b/keama/tests/subnet4auth.in4
@@ -0,0 +1,18 @@
+# DHCPv4 subnet declaration config
+
+# parameter which will be changed in subnet
+default-lease-time 1800;
+
+# DHCPv4 subnet declaration
+subnet 10.5.5.0 netmask 255.255.255.224 {
+ # at least one pool is required
+ pool {
+ range 10.5.5.5 10.5.5.10;
+ }
+ # authorize here
+ authoritative;
+ option domain-search "example.com", "example.org";
+ default-lease-time 3600;
+ ignore-client-uids false;
+}
+
diff --git a/keama/tests/subnet4auth.out b/keama/tests/subnet4auth.out
new file mode 100644
index 00000000..ad605779
--- /dev/null
+++ b/keama/tests/subnet4auth.out
@@ -0,0 +1,35 @@
+{
+ # DHCPv4 subnet declaration config
+ # parameter which will be changed in subnet
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "subnet4": [
+ # DHCPv4 subnet declaration
+ {
+ "id": 1,
+ "subnet": "10.5.5.0/27",
+ "pools": [
+ # at least one pool is required
+ {
+ "pool": "10.5.5.5 - 10.5.5.10"
+ }
+ ],
+ # authorize here
+ "authoritative": true,
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "valid-lifetime": 3600,
+ "match-client-id": true
+ }
+ ]
+ }
+}
diff --git a/keama/tests/subnet4badmask.err4 b/keama/tests/subnet4badmask.err4
new file mode 100644
index 00000000..979f5cbb
--- /dev/null
+++ b/keama/tests/subnet4badmask.err4
@@ -0,0 +1,7 @@
+# bad (bad netmask) DHCPv4 subnet declaration config
+
+# DHCPv4 subnet declaration
+subnet 10.5.5.0 netmask 255.255.255.96 {
+ default-lease-time 1800;
+}
+
diff --git a/keama/tests/subnet4badmask.msg b/keama/tests/subnet4badmask.msg
new file mode 100644
index 00000000..860aec2d
--- /dev/null
+++ b/keama/tests/subnet4badmask.msg
@@ -0,0 +1 @@
+subnet4badmask.err4 line 4: can't get a prefix from 10.5.5.0 mask 255.255.255.96
diff --git a/keama/tests/subnet4inclass.err4 b/keama/tests/subnet4inclass.err4
new file mode 100644
index 00000000..51ede4b0
--- /dev/null
+++ b/keama/tests/subnet4inclass.err4
@@ -0,0 +1,10 @@
+# DHCPv4 subnet declaration inside class declaration config
+
+# class declaration
+class "foobar" {
+ # can't put a DHCPv4 subnet declaration here
+ subnet 10.5.5.0 netmask 255.255.255.224 {
+ default-lease-time 1800;
+ }
+}
+
diff --git a/keama/tests/subnet4inclass.msg b/keama/tests/subnet4inclass.msg
new file mode 100644
index 00000000..eabc9e47
--- /dev/null
+++ b/keama/tests/subnet4inclass.msg
@@ -0,0 +1 @@
+subnet4inclass.err4 line 6: subnet declarations not allowed here.
diff --git a/keama/tests/subnet4inhost.err4 b/keama/tests/subnet4inhost.err4
new file mode 100644
index 00000000..266e6dc3
--- /dev/null
+++ b/keama/tests/subnet4inhost.err4
@@ -0,0 +1,11 @@
+# DHCPv4 subnet declaration inside host declaration config
+
+# host declaration
+host foobar {
+ hardware ethernet 00:0B:FD:32:E6:FA;
+ # can't put a DHCPv4 subnet declaration here
+ subnet 10.5.5.0 netmask 255.255.255.224 {
+ default-lease-time 1800;
+ }
+}
+
diff --git a/keama/tests/subnet4inhost.msg b/keama/tests/subnet4inhost.msg
new file mode 100644
index 00000000..49e7d18d
--- /dev/null
+++ b/keama/tests/subnet4inhost.msg
@@ -0,0 +1 @@
+subnet4inhost.err4 line 7: subnet declarations not allowed here.
diff --git a/keama/tests/subnet4nomask.err4 b/keama/tests/subnet4nomask.err4
new file mode 100644
index 00000000..42266d03
--- /dev/null
+++ b/keama/tests/subnet4nomask.err4
@@ -0,0 +1,7 @@
+# bad (no netmask) DHCPv4 subnet declaration config
+
+# DHCPv4 subnet declaration
+subnet 10.5.5.0/27 {
+ default-lease-time 1800;
+}
+
diff --git a/keama/tests/subnet4nomask.msg b/keama/tests/subnet4nomask.msg
new file mode 100644
index 00000000..2d786c10
--- /dev/null
+++ b/keama/tests/subnet4nomask.msg
@@ -0,0 +1 @@
+subnet4nomask.err4 line 4: Expecting netmask
diff --git a/keama/tests/subnet6.in6 b/keama/tests/subnet6.in6
new file mode 100644
index 00000000..ec834fd3
--- /dev/null
+++ b/keama/tests/subnet6.in6
@@ -0,0 +1,18 @@
+# DHCPv6 subnet declaration config
+
+# parameter which will be changed in subnet
+default-lease-time 1800;
+
+# DHCPv4 subnet declaration
+subnet6 2001::/64 {
+ # at least one pool is required
+ pool6 {
+ range6 2001::100 2001::200;
+ }
+ option dhcp6.domain-search "example.com", "example.org";
+ default-lease-time 3600;
+ pool6 {
+ prefix6 2001:0:0:10:: 2001:0:0:1f:: /64;
+ }
+}
+
diff --git a/keama/tests/subnet6.out b/keama/tests/subnet6.out
new file mode 100644
index 00000000..f3535931
--- /dev/null
+++ b/keama/tests/subnet6.out
@@ -0,0 +1,39 @@
+{
+ # DHCPv6 subnet declaration config
+ # parameter which will be changed in subnet
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp6": {
+ "valid-lifetime": 1800,
+ "subnet6": [
+ # DHCPv4 subnet declaration
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "pools": [
+ # at least one pool is required
+ {
+ "pool": "2001::100 - 2001::200"
+ }
+ ],
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "domain-search",
+ "code": 24,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "valid-lifetime": 3600,
+ "pd-pools": [
+ {
+ "prefix": "2001:0:0:10::",
+ "delegated-len": 64,
+ "prefix-len": 60
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/subnet62if.err6 b/keama/tests/subnet62if.err6
new file mode 100644
index 00000000..37fdc3f9
--- /dev/null
+++ b/keama/tests/subnet62if.err6
@@ -0,0 +1,8 @@
+# bad (2 interfaces) DHCPv6 subnet declaration config
+
+# DHCPv6 subnet declaration
+subnet6 2001::/64 {
+ interface "foo";
+ interface "bar";
+}
+
diff --git a/keama/tests/subnet62if.msg b/keama/tests/subnet62if.msg
new file mode 100644
index 00000000..66f6b900
--- /dev/null
+++ b/keama/tests/subnet62if.msg
@@ -0,0 +1 @@
+subnet62if.err6 line 6: A subnet can't be connected to two interfaces.
diff --git a/keama/tests/subnet6auth.in6 b/keama/tests/subnet6auth.in6
new file mode 100644
index 00000000..a41ff6f4
--- /dev/null
+++ b/keama/tests/subnet6auth.in6
@@ -0,0 +1,21 @@
+# DHCPv6 subnet declaration config
+
+# parameter which will be changed in subnet
+default-lease-time 1800;
+
+# DHCPv4 subnet declaration
+subnet6 2001::/64 {
+ # at least one pool is required
+ pool6 {
+ range6 2001::100 2001::200;
+ }
+ # authorize here
+ authoritative;
+ option dhcp6.domain-search "example.com", "example.org";
+ default-lease-time 3600;
+ interface "en0";
+ pool6 {
+ prefix6 2001:0:0:10:: 2001:0:0:1f:: /64;
+ }
+}
+
diff --git a/keama/tests/subnet6auth.out b/keama/tests/subnet6auth.out
new file mode 100644
index 00000000..aa79d1df
--- /dev/null
+++ b/keama/tests/subnet6auth.out
@@ -0,0 +1,44 @@
+{
+ # DHCPv6 subnet declaration config
+ # parameter which will be changed in subnet
+ "Dhcp6": {
+ "valid-lifetime": 1800,
+ "subnet6": [
+ # DHCPv4 subnet declaration
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "pools": [
+ # at least one pool is required
+ {
+ "pool": "2001::100 - 2001::200"
+ }
+ ],
+ "option-data": [
+ # authorize here
+ {
+ "space": "dhcp6",
+ "name": "domain-search",
+ "code": 24,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "valid-lifetime": 3600,
+ "interface": "en0",
+ "pd-pools": [
+ {
+ "prefix": "2001:0:0:10::",
+ "delegated-len": 64,
+ "prefix-len": 60
+ }
+ ]
+ }
+ ],
+ "interfaces-config": {
+ "interfaces": [
+ "en0"
+ ]
+ }
+ }
+}
diff --git a/keama/tests/subnet6inclass.err6 b/keama/tests/subnet6inclass.err6
new file mode 100644
index 00000000..52368cd8
--- /dev/null
+++ b/keama/tests/subnet6inclass.err6
@@ -0,0 +1,10 @@
+# DHCPv6 subnet declaration inside class declaration config
+
+# class declaration
+class "foobar" {
+ # can't put a DHCPv6 subnet declaration here
+ subnet6 2001::/64 {
+ default-lease-time 1800;
+ }
+}
+
diff --git a/keama/tests/subnet6inclass.msg b/keama/tests/subnet6inclass.msg
new file mode 100644
index 00000000..e7360029
--- /dev/null
+++ b/keama/tests/subnet6inclass.msg
@@ -0,0 +1 @@
+subnet6inclass.err6 line 6: subnet declarations not allowed here.
diff --git a/keama/tests/subnet6inhost.err6 b/keama/tests/subnet6inhost.err6
new file mode 100644
index 00000000..914decd6
--- /dev/null
+++ b/keama/tests/subnet6inhost.err6
@@ -0,0 +1,11 @@
+# DHCPv6 subnet declaration inside host declaration config
+
+# host declaration
+host foobar {
+ hardware ethernet 00:0B:FD:32:E6:FA;
+ # can't put a DHCPv6 subnet declaration here
+ subnet6 2001::/64 {
+ default-lease-time 1800;
+ }
+}
+
diff --git a/keama/tests/subnet6inhost.msg b/keama/tests/subnet6inhost.msg
new file mode 100644
index 00000000..bd49b89f
--- /dev/null
+++ b/keama/tests/subnet6inhost.msg
@@ -0,0 +1 @@
+subnet6inhost.err6 line 7: subnet declarations not allowed here.
diff --git a/keama/tests/subnet6multi.in6 b/keama/tests/subnet6multi.in6
new file mode 100644
index 00000000..a9974ba0
--- /dev/null
+++ b/keama/tests/subnet6multi.in6
@@ -0,0 +1,19 @@
+# DHCPv6 subnet declaration config
+
+# parameter which will be changed in subnet
+default-lease-time 1800;
+
+# DHCPv4 subnet declaration
+subnet6 2001::/64 {
+ # at least one pool is required
+ pool6 {
+ # the pool is shared between addresses and prefixes
+ range6 2001::100 2001::200;
+ range6 2001::1000 2001::2000;
+ prefix6 2001:0:0:10:: 2001:0:0:1f:: /64;
+ prefix6 2001:0:0:80:: 2001:0:0:ff:: /64;
+ }
+ option dhcp6.domain-search "example.com", "example.org";
+ default-lease-time 3600;
+}
+
diff --git a/keama/tests/subnet6multi.out b/keama/tests/subnet6multi.out
new file mode 100644
index 00000000..9a4defb6
--- /dev/null
+++ b/keama/tests/subnet6multi.out
@@ -0,0 +1,49 @@
+{
+ # DHCPv6 subnet declaration config
+ # parameter which will be changed in subnet
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp6": {
+ "valid-lifetime": 1800,
+ "subnet6": [
+ # DHCPv4 subnet declaration
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "pd-pools": [
+ {
+ "prefix": "2001:0:0:10::",
+ "delegated-len": 64,
+ "prefix-len": 60
+ },
+ # at least one pool is required
+ {
+ "prefix": "2001:0:0:80::",
+ "delegated-len": 64,
+ "prefix-len": 57
+ }
+ ],
+ "pools": [
+ {
+ # the pool is shared between addresses and prefixes
+ "pool": "2001::100 - 2001::200"
+ },
+ # at least one pool is required
+ {
+ "pool": "2001::1000 - 2001::2000"
+ }
+ ],
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "domain-search",
+ "code": 24,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "valid-lifetime": 3600
+ }
+ ]
+ }
+}
diff --git a/keama/tests/subnet6nolen.err6 b/keama/tests/subnet6nolen.err6
new file mode 100644
index 00000000..1a71ac79
--- /dev/null
+++ b/keama/tests/subnet6nolen.err6
@@ -0,0 +1,7 @@
+# bad (no length) DHCPv6 subnet declaration config
+
+# DHCPv6 subnet declaration
+subnet6 2001::/ {
+ default-lease-time 1800;
+}
+
diff --git a/keama/tests/subnet6nolen.msg b/keama/tests/subnet6nolen.msg
new file mode 100644
index 00000000..cc36f0e1
--- /dev/null
+++ b/keama/tests/subnet6nolen.msg
@@ -0,0 +1 @@
+subnet6nolen.err6 line 4: Expecting a number.
diff --git a/keama/tests/subnet6noslash.err6 b/keama/tests/subnet6noslash.err6
new file mode 100644
index 00000000..2083eab6
--- /dev/null
+++ b/keama/tests/subnet6noslash.err6
@@ -0,0 +1,7 @@
+# bad (no /) DHCPv6 subnet declaration config
+
+# DHCPv6 subnet declaration
+subnet6 2001:: {
+ default-lease-time 1800;
+}
+
diff --git a/keama/tests/subnet6noslash.msg b/keama/tests/subnet6noslash.msg
new file mode 100644
index 00000000..63fa81c6
--- /dev/null
+++ b/keama/tests/subnet6noslash.msg
@@ -0,0 +1 @@
+subnet6noslash.err6 line 4: Expecting a '/'.
diff --git a/keama/tests/subnet6one.in6 b/keama/tests/subnet6one.in6
new file mode 100644
index 00000000..7c0c3a0b
--- /dev/null
+++ b/keama/tests/subnet6one.in6
@@ -0,0 +1,18 @@
+# DHCPv6 subnet declaration config
+
+# parameter which will be changed in subnet
+default-lease-time 1800;
+
+# DHCPv4 subnet declaration
+subnet6 2001::/64 {
+ # at least one pool is required
+ pool6 {
+ # the pool is shared between addresses and prefixes
+ range6 2001::100 2001::200;
+ prefix6 2001:0:0:10:: 2001:0:0:1f:: /64;
+ }
+ option dhcp6.domain-search "example.com", "example.org";
+ default-lease-time 3600;
+ interface "en0";
+}
+
diff --git a/keama/tests/subnet6one.out b/keama/tests/subnet6one.out
new file mode 100644
index 00000000..e1388c81
--- /dev/null
+++ b/keama/tests/subnet6one.out
@@ -0,0 +1,45 @@
+{
+ # DHCPv6 subnet declaration config
+ # parameter which will be changed in subnet
+ "Dhcp6": {
+ "valid-lifetime": 1800,
+ "subnet6": [
+ # DHCPv4 subnet declaration
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "pd-pools": [
+ # at least one pool is required
+ {
+ "prefix": "2001:0:0:10::",
+ "delegated-len": 64,
+ "prefix-len": 60
+ }
+ ],
+ "pools": [
+ # at least one pool is required
+ {
+ # the pool is shared between addresses and prefixes
+ "pool": "2001::100 - 2001::200"
+ }
+ ],
+ "option-data": [
+ {
+ "space": "dhcp6",
+ "name": "domain-search",
+ "code": 24,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "valid-lifetime": 3600,
+ "interface": "en0"
+ }
+ ],
+ "interfaces-config": {
+ "interfaces": [
+ "en0"
+ ]
+ }
+ }
+}
diff --git a/keama/tests/subnetinsubnet4.err4 b/keama/tests/subnetinsubnet4.err4
new file mode 100644
index 00000000..c326c16d
--- /dev/null
+++ b/keama/tests/subnetinsubnet4.err4
@@ -0,0 +1,10 @@
+# DHCPv4 subnet declaration inside another subnet declaration config
+
+# DHCPv4 subnet declaration
+subnet 10.254.239.32 netmask 255.255.255.224 {
+ # can't put another subnet declaration here
+ subnet 10.5.5.0 netmask 255.255.255.224 {
+ default-lease-time 1800;
+ }
+}
+
diff --git a/keama/tests/subnetinsubnet4.msg b/keama/tests/subnetinsubnet4.msg
new file mode 100644
index 00000000..710cffa7
--- /dev/null
+++ b/keama/tests/subnetinsubnet4.msg
@@ -0,0 +1 @@
+subnetinsubnet4.err4 line 6: subnet declarations not allowed here.
diff --git a/keama/tests/subnetinsubnet6.err6 b/keama/tests/subnetinsubnet6.err6
new file mode 100644
index 00000000..ac9a6097
--- /dev/null
+++ b/keama/tests/subnetinsubnet6.err6
@@ -0,0 +1,10 @@
+# DHCPv6 subnet declaration inside another subnet declaration config
+
+# DHCPv6 subnet declaration
+subnet6 2001:2::/64 {
+ # can't put another subnet declaration here
+ subnet6 2001::/64 {
+ default-lease-time 1800;
+ }
+}
+
diff --git a/keama/tests/subnetinsubnet6.msg b/keama/tests/subnetinsubnet6.msg
new file mode 100644
index 00000000..27f8f3fa
--- /dev/null
+++ b/keama/tests/subnetinsubnet6.msg
@@ -0,0 +1 @@
+subnetinsubnet6.err6 line 6: subnet declarations not allowed here.
diff --git a/keama/tests/substringdx4.in4 b/keama/tests/substringdx4.in4
new file mode 100644
index 00000000..ed0c9d35
--- /dev/null
+++ b/keama/tests/substringdx4.in4
@@ -0,0 +1,21 @@
+# substring data expression
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# use substring in a reductible match
+class "reductible" {
+ match substring(option host-name, 0, 3);
+}
+
+subclass "reductible" "www" { }
+
+# reduce literals too
+class "literal" {
+ match if option host-name = substring("www.example.com", 0, 3);
+}
+
+# raw
+option domain-name = substring(option domain-name, 4, 1000);
+
+
diff --git a/keama/tests/substringdx4.out b/keama/tests/substringdx4.out
new file mode 100644
index 00000000..75865424
--- /dev/null
+++ b/keama/tests/substringdx4.out
@@ -0,0 +1,49 @@
+{
+ # substring data expression
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "client-classes": [
+ # use substring in a reductible match
+ /// match: substring(option dhcp.host-name, 0, 3)
+ {
+ "name": "reductible"
+ },
+ /// subclass selector 'www'
+ {
+ "name": "sub#reductible#0",
+ /// from: match substring(option dhcp.host-name, 0, 3)
+ /// data: 'www'
+ "test": "substring(option[12].hex,0,3) == 'www'"
+ },
+ # reduce literals too
+ {
+ "name": "literal",
+ /// from: match if (option dhcp.host-name) = (substring('www.example.com', 0, 3))
+ "test": "option[12].hex == 'www'"
+ }
+ ],
+ "option-data": [
+// # raw
+// {
+// "space": "dhcp4",
+// "name": "domain-name",
+// "code": 15,
+// "csv-format": false,
+// "expression": {
+// "substring": {
+// "expression": {
+// "option": {
+// "universe": "dhcp",
+// "name": "domain-name",
+// "code": 15
+// }
+// },
+// "offset": 4,
+// "length": 1000
+// }
+// }
+// }
+ ]
+ }
+}
diff --git a/keama/tests/suffixdx4.in4 b/keama/tests/suffixdx4.in4
new file mode 100644
index 00000000..fc692d10
--- /dev/null
+++ b/keama/tests/suffixdx4.in4
@@ -0,0 +1,26 @@
+# suffix data expression
+# in fact ISC DHCP suffix can be reduced into Kea substring
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# use suffix in a reductible match
+class "reductible" {
+ match suffix(option host-name, 3);
+}
+
+subclass "reductible" "com" {
+ option domain-search "example.com";
+}
+
+subclass "reductible" "org" {
+ option domain-search "example.org";
+}
+
+# reduce literals too
+class "literal" {
+ match if option domain-name = suffix("www.example.com", 3);
+}
+
+# raw
+option domain-name = suffix(option domain-name, 3);
diff --git a/keama/tests/suffixdx4.out b/keama/tests/suffixdx4.out
new file mode 100644
index 00000000..12f045d6
--- /dev/null
+++ b/keama/tests/suffixdx4.out
@@ -0,0 +1,74 @@
+{
+ # suffix data expression
+ # in fact ISC DHCP suffix can be reduced into Kea substring
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800,
+ "client-classes": [
+ # use suffix in a reductible match
+ /// match: suffix(option dhcp.host-name, 3)
+ {
+ "name": "reductible"
+ },
+ /// subclass selector 'com'
+ {
+ "name": "sub#reductible#0",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.com\"",
+ "data": "example.com"
+ }
+ ],
+ /// from: match suffix(option dhcp.host-name, 3)
+ /// data: 'com'
+ "test": "substring(option[12].hex,-3,all) == 'com'"
+ },
+ /// subclass selector 'org'
+ {
+ "name": "sub#reductible#1",
+ "option-data": [
+ {
+ "space": "dhcp4",
+ "name": "domain-search",
+ "code": 119,
+// "original-data": "\"example.org\"",
+ "data": "example.org"
+ }
+ ],
+ /// from: match suffix(option dhcp.host-name, 3)
+ /// data: 'org'
+ "test": "substring(option[12].hex,-3,all) == 'org'"
+ },
+ # reduce literals too
+ {
+ "name": "literal",
+ /// from: match if (option dhcp.domain-name) = (suffix('www.example.com', 3))
+ "test": "option[15].hex == '.example.com'"
+ }
+ ],
+ "option-data": [
+// # raw
+// {
+// "space": "dhcp4",
+// "name": "domain-name",
+// "code": 15,
+// "csv-format": false,
+// "expression": {
+// "suffix": {
+// "expression": {
+// "option": {
+// "universe": "dhcp",
+// "name": "domain-name",
+// "code": 15
+// }
+// },
+// "length": 3
+// }
+// }
+// }
+ ]
+ }
+}
diff --git a/keama/tests/switchxsc4.in4 b/keama/tests/switchxsc4.in4
new file mode 100644
index 00000000..7ef6f2f3
--- /dev/null
+++ b/keama/tests/switchxsc4.in4
@@ -0,0 +1,18 @@
+# switch executable statement construct
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# a switch
+switch (option user-class) {
+case "accounting":
+ add "acct";
+ send ip-forwarding false;
+ default-lease-time 3600;
+ break;
+case "engineering":
+ allow booting;
+ log (debug, option host-name);
+ set foo = "bar";
+ break;
+}
diff --git a/keama/tests/switchxsc4.out b/keama/tests/switchxsc4.out
new file mode 100644
index 00000000..e45421d3
--- /dev/null
+++ b/keama/tests/switchxsc4.out
@@ -0,0 +1,79 @@
+{
+ # switch executable statement construct
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800
+// # a switch
+// "statement": {
+// "switch": {
+// "condition": {
+// "option": {
+// "universe": "dhcp",
+// "name": "user-class",
+// "code": 77
+// }
+// },
+// "body": [
+// {
+// "case": "accounting"
+// },
+// {
+// "add-class": "acct"
+// },
+// {
+// /// Kea does not support option data set variants (send)
+// "option": {
+// "space": "dhcp4",
+// "name": "ip-forwarding",
+// "code": 19,
+// "data": "false"
+// }
+// },
+// {
+// "config": {
+// "name": "default-lease-time",
+// "code": 1,
+// "value": 3600
+// }
+// },
+// {
+// "break": null
+// },
+// {
+// "case": "engineering"
+// },
+// {
+// "config": {
+// "value": "allow",
+// "name": "allow-booting",
+// "code": 9
+// }
+// },
+// {
+// /// Kea does not support yet log statements
+// /// Reference Kea #234
+// "log": {
+// "priority": "debug",
+// "message": {
+// "option": {
+// "universe": "dhcp",
+// "name": "host-name",
+// "code": 12
+// }
+// }
+// }
+// },
+// {
+// "set": {
+// "name": "foo",
+// "value": "bar"
+// }
+// },
+// {
+// "break": null
+// }
+// ]
+// }
+// }
+ }
+}
diff --git a/keama/tests/switchxsc6.in6 b/keama/tests/switchxsc6.in6
new file mode 100644
index 00000000..a9c74f86
--- /dev/null
+++ b/keama/tests/switchxsc6.in6
@@ -0,0 +1,18 @@
+# switch executable statement construct
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# a switch
+switch (option dhcp6.remote-id) {
+case "accounting":
+ default dhcp6.bootfile-url foobar;
+ default-lease-time 3600;
+ unset foo;
+ break;
+case "engineering":
+ deny declines;
+ log (debug, "hello");
+ define foo (x) { return "world"; }
+ break;
+}
diff --git a/keama/tests/switchxsc6.out b/keama/tests/switchxsc6.out
new file mode 100644
index 00000000..b7fbd06c
--- /dev/null
+++ b/keama/tests/switchxsc6.out
@@ -0,0 +1,82 @@
+{
+ # switch executable statement construct
+ # empty configs are not accepted by Kea
+ "Dhcp6": {
+ "valid-lifetime": 1800
+// # a switch
+// "statement": {
+// "switch": {
+// "condition": {
+// "option": {
+// "universe": "dhcp6",
+// "name": "remote-id",
+// "code": 37
+// }
+// },
+// "body": [
+// {
+// "case": "accounting"
+// },
+// {
+// /// Kea does not support option data set variants (default)
+// "option": {
+// "space": "dhcp6",
+// "name": "bootfile-url",
+// "code": 59,
+// "data": "foobar"
+// }
+// },
+// {
+// "config": {
+// "name": "default-lease-time",
+// "code": 1,
+// "value": 3600
+// }
+// },
+// {
+// "unset": {
+// "name": "foo"
+// }
+// },
+// {
+// "break": null
+// },
+// {
+// "case": "engineering"
+// },
+// {
+// "config": {
+// "value": "deny",
+// "name": "declines",
+// "code": 29
+// }
+// },
+// {
+// /// Kea does not support yet log statements
+// /// Reference Kea #234
+// "log": {
+// "priority": "debug",
+// "message": "hello"
+// }
+// },
+// {
+// "define": {
+// "name": "foo",
+// "function": {
+// "arguments": "x",
+// "body": [
+// {
+// "return": "world"
+// }
+// ]
+// }
+// }
+// },
+// {
+// "break": null
+// }
+// ]
+// }
+// }
+ }
+}
diff --git a/keama/tests/tautology.err b/keama/tests/tautology.err
new file mode 100644
index 00000000..e1f506dd
--- /dev/null
+++ b/keama/tests/tautology.err
@@ -0,0 +1,9 @@
+# bad (tautology) class declaration config
+
+# class declaration
+class "tautology" {
+ # tautology
+ # note that true does not work as it is a variable reference
+ # and for the same reason quotes are needed (or one can use hexa)
+ match if "foo" = "foo";
+}
diff --git a/keama/tests/tautology.msg b/keama/tests/tautology.msg
new file mode 100644
index 00000000..80bf2feb
--- /dev/null
+++ b/keama/tests/tautology.msg
@@ -0,0 +1 @@
+tautology.err line 8: 'match if' with a constant boolean expression 'foo' = 'foo'
diff --git a/keama/tests/tautologyhexa.err b/keama/tests/tautologyhexa.err
new file mode 100644
index 00000000..69249d86
--- /dev/null
+++ b/keama/tests/tautologyhexa.err
@@ -0,0 +1,9 @@
+# bad (tautology) class declaration config
+
+# class declaration
+class "tautology" {
+ # tautology
+ # note that true does not work as it is a variable reference
+ # and for the same reason quotes are needed (or one can use hexa)
+ match if 12:34 = 56:78:9a;
+}
diff --git a/keama/tests/tautologyhexa.msg b/keama/tests/tautologyhexa.msg
new file mode 100644
index 00000000..7482482b
--- /dev/null
+++ b/keama/tests/tautologyhexa.msg
@@ -0,0 +1 @@
+tautologyhexa.err line 8: 'match if' with a constant boolean expression 0x1234 = 0x56789a
diff --git a/keama/tests/tautologysub.err b/keama/tests/tautologysub.err
new file mode 100644
index 00000000..08de2575
--- /dev/null
+++ b/keama/tests/tautologysub.err
@@ -0,0 +1,9 @@
+# bad (tautology) class declaration config
+
+# superclass declaration
+class "constant" {
+ match "foo";
+}
+
+# subclass declaration
+subclass "constant" "bar";
diff --git a/keama/tests/tautologysub.msg b/keama/tests/tautologysub.msg
new file mode 100644
index 00000000..d30f39a7
--- /dev/null
+++ b/keama/tests/tautologysub.msg
@@ -0,0 +1 @@
+tautologysub.err line 9: class matching rule evaluated to a constant boolean expression: 'foo' = 'bar'
diff --git a/keama/tests/temporary6.in6 b/keama/tests/temporary6.in6
new file mode 100644
index 00000000..dec7d99b
--- /dev/null
+++ b/keama/tests/temporary6.in6
@@ -0,0 +1,10 @@
+# DHCPv6 temporary (aka IA_TA) range config
+
+# subnet declaration
+subnet6 2001::/64 {
+ # range declaration
+ option dhcp6.domain-search "example.com", "example.org";
+ default-lease-time 1800;
+ range6 2001::100 temporary;
+ range6 2001::1000/116 temporary;
+}
diff --git a/keama/tests/temporary6.out b/keama/tests/temporary6.out
new file mode 100644
index 00000000..e7af7bba
--- /dev/null
+++ b/keama/tests/temporary6.out
@@ -0,0 +1,33 @@
+{
+ # DHCPv6 temporary (aka IA_TA) range config
+ # subnet declaration
+ /// This configuration declares some subnets but has no interfaces-config
+ /// Reference Kea #245
+ "Dhcp6": {
+ "subnet6": [
+ {
+ "id": 1,
+ "subnet": "2001::/64",
+ "option-data": [
+ # range declaration
+ {
+ "space": "dhcp6",
+ "name": "domain-search",
+ "code": 24,
+// "original-data": "\"example.com\", \"example.org\"",
+ "data": "example.com, example.org"
+ }
+ ],
+ "valid-lifetime": 1800,
+ "pools": [
+// {
+// "pool": "2001::100/64 temporary"
+// }
+// {
+// "pool": "2001::1000/116 temporary"
+// }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/textarray.err b/keama/tests/textarray.err
new file mode 100644
index 00000000..7cecd7a0
--- /dev/null
+++ b/keama/tests/textarray.err
@@ -0,0 +1,7 @@
+# option definition config
+
+# options
+option space foobar;
+
+# array of text are forbidden
+option foobarstring-array code 1 = array of text;
diff --git a/keama/tests/textarray.msg b/keama/tests/textarray.msg
new file mode 100644
index 00000000..f1115cbe
--- /dev/null
+++ b/keama/tests/textarray.msg
@@ -0,0 +1 @@
+textarray.err line 7: arrays of text strings not yet supported.
diff --git a/keama/tests/unknownoption.err b/keama/tests/unknownoption.err
new file mode 100644
index 00000000..ce4aacbb
--- /dev/null
+++ b/keama/tests/unknownoption.err
@@ -0,0 +1,4 @@
+# unknown option config
+
+# unknown option
+option this-option-does-not-exist off;
diff --git a/keama/tests/unknownoption.msg b/keama/tests/unknownoption.msg
new file mode 100644
index 00000000..0fb2f1e9
--- /dev/null
+++ b/keama/tests/unknownoption.msg
@@ -0,0 +1 @@
+unknownoption.err line 4: unknown option dhcp.this-option-does-not-exist
diff --git a/keama/tests/unknownspace.err b/keama/tests/unknownspace.err
new file mode 100644
index 00000000..8165a972
--- /dev/null
+++ b/keama/tests/unknownspace.err
@@ -0,0 +1,4 @@
+# unknown option config
+
+# unknown option
+option this-space-does-not-exist.domain-search "example.com";
diff --git a/keama/tests/unknownspace.msg b/keama/tests/unknownspace.msg
new file mode 100644
index 00000000..f6e378af
--- /dev/null
+++ b/keama/tests/unknownspace.msg
@@ -0,0 +1 @@
+unknownspace.err line 4: no option space named this-space-does-not-exist.
diff --git a/keama/tests/userclass.err b/keama/tests/userclass.err
new file mode 100644
index 00000000..e8a3d27c
--- /dev/null
+++ b/keama/tests/userclass.err
@@ -0,0 +1,6 @@
+# user-class declaration config
+
+# user-class declaration
+user-class "foobar" {
+}
+
diff --git a/keama/tests/userclass.msg b/keama/tests/userclass.msg
new file mode 100644
index 00000000..2208500e
--- /dev/null
+++ b/keama/tests/userclass.msg
@@ -0,0 +1 @@
+userclass.err line 4: obsolete 'user-class' declaration
diff --git a/keama/tests/vendorclass.err b/keama/tests/vendorclass.err
new file mode 100644
index 00000000..81d1b4e3
--- /dev/null
+++ b/keama/tests/vendorclass.err
@@ -0,0 +1,6 @@
+# vendor-class declaration config
+
+# vendor-class declaration
+vendor-class "foobar" {
+}
+
diff --git a/keama/tests/vendorclass.msg b/keama/tests/vendorclass.msg
new file mode 100644
index 00000000..fc45fe66
--- /dev/null
+++ b/keama/tests/vendorclass.msg
@@ -0,0 +1 @@
+vendorclass.err line 4: obsolete 'vendor-class' declaration
diff --git a/keama/tests/vendorspace4.in4 b/keama/tests/vendorspace4.in4
new file mode 100644
index 00000000..98f9eada
--- /dev/null
+++ b/keama/tests/vendorspace4.in4
@@ -0,0 +1,11 @@
+# vendor option space config
+
+option space foo;
+option foo.bar code 1 = text;
+
+# class declaration
+class "foobar" {
+ match if option vendor-class-identifier = "foo";
+ vendor-option-space foo;
+ option foo.bar "foobar";
+}
diff --git a/keama/tests/vendorspace4.out b/keama/tests/vendorspace4.out
new file mode 100644
index 00000000..09feae6f
--- /dev/null
+++ b/keama/tests/vendorspace4.out
@@ -0,0 +1,41 @@
+{
+ # vendor option space config
+ "Dhcp4": {
+ "option-def": [
+ {
+ "space": "foo",
+ "name": "bar",
+ "code": 1,
+ "type": "string"
+ }
+ ],
+ "client-classes": [
+ # class declaration
+ {
+ "name": "foobar",
+ /// from: match if (option dhcp.vendor-class-identifier) = 'foo'
+ "test": "option[60].hex == 'foo'",
+ "option-def": [
+ {
+ "name": "vendor-encapsulated-options",
+ "code": 43,
+ "type": "empty",
+ "encapsulate": "foo"
+ }
+ ],
+ "option-data": [
+ {
+ "name": "vendor-encapsulated-options",
+ "code": 43
+ },
+ {
+ "space": "foo",
+ "name": "bar",
+ "code": 1,
+ "data": "foobar"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/keama/tests/zone4.in4 b/keama/tests/zone4.in4
new file mode 100644
index 00000000..654e6c8e
--- /dev/null
+++ b/keama/tests/zone4.in4
@@ -0,0 +1,24 @@
+# zone executable statement construct
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
+
+# a zone
+zone example.com {
+ primary 10.5.5.1, 10.5.5.2;
+ secondary 10.10.10.1;
+ primary6 2001::1, 2001::2;
+ secondary6 2002::1;
+ key "mykey";
+}
+
+# a key;
+key "mykey" {
+ algorithm hmac-md5;
+ secret "somekeydata";
+}
+
+# another key (with bind 8 semi-colon)
+key example.com {
+ algorithm aes-gmac.dreams;
+};
diff --git a/keama/tests/zone4.out b/keama/tests/zone4.out
new file mode 100644
index 00000000..7565db4c
--- /dev/null
+++ b/keama/tests/zone4.out
@@ -0,0 +1,47 @@
+{
+ # zone executable statement construct
+ # empty configs are not accepted by Kea
+ "Dhcp4": {
+ "valid-lifetime": 1800
+// # a zone
+// "statement": {
+// "zone": {
+// "name": "example.com.",
+// "primary": [
+// "10.5.5.1",
+// "10.5.5.2"
+// ],
+// "secondary": [
+// "10.10.10.1"
+// ],
+// "primary6": [
+// "2001::1",
+// "2001::2"
+// ],
+// "secondary6": [
+// "2002::1"
+// ],
+// "key": "mykey"
+// }
+// }
+// # a key;
+// "statement": {
+// "tsig-keys": [
+// {
+// "name": "mykey",
+// "algorithm": "hmac-md5.SIG-ALG.REG.INT.",
+// "secret": "somekeydata"
+// }
+// ]
+// }
+// # another key (with bind 8 semi-colon)
+// "statement": {
+// "tsig-keys": [
+// {
+// "name": "example.com",
+// "algorithm": "aes-gmac.dreams."
+// }
+// ]
+// }
+ }
+}