summaryrefslogtreecommitdiffstats
path: root/plugins/sudoers/regress
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/sudoers/regress')
-rw-r--r--plugins/sudoers/regress/check_symbols/check_symbols.c114
-rw-r--r--plugins/sudoers/regress/corpus/seed/ldif/invalid_b64.ldif33
-rw-r--r--plugins/sudoers/regress/corpus/seed/ldif/pr196.ldif6
-rw-r--r--plugins/sudoers/regress/corpus/seed/ldif/sample.ldif295
-rw-r--r--plugins/sudoers/regress/corpus/seed/ldif/valid_b64.ldif44
-rw-r--r--plugins/sudoers/regress/corpus/seed/policy/policy.11
-rw-r--r--plugins/sudoers/regress/corpus/seed/policy/policy.25
-rw-r--r--plugins/sudoers/regress/corpus/seed/policy/policy.311
-rw-r--r--plugins/sudoers/regress/corpus/seed/policy/policy.436
-rw-r--r--plugins/sudoers/regress/corpus/seed/policy/policy.536
-rw-r--r--plugins/sudoers/regress/cvtsudoers/sudoers126
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/sudoers.defs19
-rw-r--r--plugins/sudoers/regress/cvtsudoers/sudoers197
-rw-r--r--plugins/sudoers/regress/cvtsudoers/sudoers297
-rw-r--r--plugins/sudoers/regress/cvtsudoers/sudoers397
-rw-r--r--plugins/sudoers/regress/cvtsudoers/sudoers497
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test1.out.ok14
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test1.sh10
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test10.out.ok1
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test10.sh10
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test11.out.ok7
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test11.sh8
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test12.out.ok8
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test12.sh8
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test13.out.ok7
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test13.sh8
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test14.out.ok7
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test14.sh8
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test15.out.ok1
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test15.sh10
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test16.out.ok1
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test16.sh10
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test17.out.ok1
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test17.sh10
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test18.out.ok1
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test18.sh10
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test19.out.ok11
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test19.sh8
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test2.out.ok10
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test2.sh10
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test20.conf6
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test20.out.ok1
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test20.sh13
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test21.conf8
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test21.out.ok24
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test21.sh14
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test22.out.ok31
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test22.sh73
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test23.out.ok20
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test23.sh9
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test24.out.ok89
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test24.sh9
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test25.out.ok31
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test25.sh53
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test26.err.ok3
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test26.out.ok0
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test26.sh49
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test27.out.ok16
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test27.sh12
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test28.out.ok10
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test28.sh74
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test29.out.ok4
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test29.sh61
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test3.out.ok7
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test3.sh10
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test30.out.ok26
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test30.sh15
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test31.conf9
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test31.out.ok24
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test31.sh14
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test32.err.ok1
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test32.out.ok119
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test32.sh22
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test33.out.ok7
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test33.sh62
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test34.out.ok18
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test34.sh9
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test35.out.ok18
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test35.sh9
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test36.out.ok17
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test36.sh9
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test37.out.ok17
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test37.sh10
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test38.out.ok14
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test38.sh10
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test39.out.ok0
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test39.sh12
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test4.out.ok5
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test4.sh10
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test40.out.ok10
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test40.sh29
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test5.out.ok6
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test5.sh10
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test6.out.ok1
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test6.sh10
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test7.out.ok2
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test7.sh10
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test8.out.ok1
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test8.sh10
-rw-r--r--plugins/sudoers/regress/cvtsudoers/test9.out.ok1
-rwxr-xr-xplugins/sudoers/regress/cvtsudoers/test9.sh10
-rw-r--r--plugins/sudoers/regress/editor/check_editor.c174
-rw-r--r--plugins/sudoers/regress/env_match/check_env_pattern.c96
-rw-r--r--plugins/sudoers/regress/env_match/data22
-rw-r--r--plugins/sudoers/regress/exptilde/check_exptilde.c113
-rw-r--r--plugins/sudoers/regress/fuzz/fuzz_policy.c932
-rw-r--r--plugins/sudoers/regress/fuzz/fuzz_policy.dict56
-rw-r--r--plugins/sudoers/regress/fuzz/fuzz_stubs.c100
-rw-r--r--plugins/sudoers/regress/fuzz/fuzz_sudoers.c419
-rw-r--r--plugins/sudoers/regress/fuzz/fuzz_sudoers.dict221
-rw-r--r--plugins/sudoers/regress/fuzz/fuzz_sudoers.out.ok577
-rw-r--r--plugins/sudoers/regress/fuzz/fuzz_sudoers_ldif.c151
-rw-r--r--plugins/sudoers/regress/fuzz/fuzz_sudoers_ldif.dict14
-rwxr-xr-xplugins/sudoers/regress/harness.in217
-rw-r--r--plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c455
-rw-r--r--plugins/sudoers/regress/parser/check_addr.c152
-rw-r--r--plugins/sudoers/regress/parser/check_addr.in13
-rw-r--r--plugins/sudoers/regress/parser/check_base64.c130
-rw-r--r--plugins/sudoers/regress/parser/check_digest.c140
-rw-r--r--plugins/sudoers/regress/parser/check_digest.out.ok36
-rw-r--r--plugins/sudoers/regress/parser/check_fill.c225
-rw-r--r--plugins/sudoers/regress/parser/check_gentime.c98
-rw-r--r--plugins/sudoers/regress/serialize_list/check_serialize_list.c96
-rw-r--r--plugins/sudoers/regress/starttime/check_starttime.c154
-rw-r--r--plugins/sudoers/regress/sudoers/test1.in12
-rw-r--r--plugins/sudoers/regress/sudoers/test1.json.ok154
-rw-r--r--plugins/sudoers/regress/sudoers/test1.ldif.ok88
-rw-r--r--plugins/sudoers/regress/sudoers/test1.ldif2sudo.ok13
-rw-r--r--plugins/sudoers/regress/sudoers/test1.out.ok6
-rw-r--r--plugins/sudoers/regress/sudoers/test1.toke.ok8
-rw-r--r--plugins/sudoers/regress/sudoers/test10.in1
-rw-r--r--plugins/sudoers/regress/sudoers/test10.json.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test10.ldif.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test10.out.ok2
-rw-r--r--plugins/sudoers/regress/sudoers/test10.toke.ok1
-rw-r--r--plugins/sudoers/regress/sudoers/test11.in1
-rw-r--r--plugins/sudoers/regress/sudoers/test11.json.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test11.ldif.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test11.out.ok1
-rw-r--r--plugins/sudoers/regress/sudoers/test11.toke.ok2
-rw-r--r--plugins/sudoers/regress/sudoers/test12.in1
-rw-r--r--plugins/sudoers/regress/sudoers/test12.json.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test12.ldif.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test12.out.ok1
-rw-r--r--plugins/sudoers/regress/sudoers/test12.toke.ok2
-rw-r--r--plugins/sudoers/regress/sudoers/test13.in1
-rw-r--r--plugins/sudoers/regress/sudoers/test13.json.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test13.ldif.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test13.out.ok1
-rw-r--r--plugins/sudoers/regress/sudoers/test13.toke.ok2
-rw-r--r--plugins/sudoers/regress/sudoers/test14.in6
-rw-r--r--plugins/sudoers/regress/sudoers/test14.json.ok62
-rw-r--r--plugins/sudoers/regress/sudoers/test14.ldif.ok20
-rw-r--r--plugins/sudoers/regress/sudoers/test14.ldif2sudo.ok12
-rw-r--r--plugins/sudoers/regress/sudoers/test14.out.ok7
-rw-r--r--plugins/sudoers/regress/sudoers/test14.toke.ok6
-rw-r--r--plugins/sudoers/regress/sudoers/test15.in2
-rw-r--r--plugins/sudoers/regress/sudoers/test15.json.ok19
-rw-r--r--plugins/sudoers/regress/sudoers/test15.ldif.ok9
-rw-r--r--plugins/sudoers/regress/sudoers/test15.ldif2sudo.ok2
-rw-r--r--plugins/sudoers/regress/sudoers/test15.out.ok3
-rw-r--r--plugins/sudoers/regress/sudoers/test15.toke.ok2
-rw-r--r--plugins/sudoers/regress/sudoers/test16.in3
-rw-r--r--plugins/sudoers/regress/sudoers/test16.json.ok24
-rw-r--r--plugins/sudoers/regress/sudoers/test16.ldif.ok9
-rw-r--r--plugins/sudoers/regress/sudoers/test16.ldif2sudo.ok2
-rw-r--r--plugins/sudoers/regress/sudoers/test16.out.ok5
-rw-r--r--plugins/sudoers/regress/sudoers/test16.toke.ok3
-rw-r--r--plugins/sudoers/regress/sudoers/test17.in13
-rw-r--r--plugins/sudoers/regress/sudoers/test17.json.ok180
-rw-r--r--plugins/sudoers/regress/sudoers/test17.ldif.ok104
-rw-r--r--plugins/sudoers/regress/sudoers/test17.ldif2sudo.ok29
-rw-r--r--plugins/sudoers/regress/sudoers/test17.out.ok13
-rw-r--r--plugins/sudoers/regress/sudoers/test17.toke.ok11
-rw-r--r--plugins/sudoers/regress/sudoers/test18.in8
-rw-r--r--plugins/sudoers/regress/sudoers/test18.json.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test18.ldif.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test18.out.ok3
-rw-r--r--plugins/sudoers/regress/sudoers/test18.toke.ok10
-rw-r--r--plugins/sudoers/regress/sudoers/test19.in12
-rw-r--r--plugins/sudoers/regress/sudoers/test19.json.ok187
-rw-r--r--plugins/sudoers/regress/sudoers/test19.ldif.ok103
-rw-r--r--plugins/sudoers/regress/sudoers/test19.ldif2sudo.ok30
-rw-r--r--plugins/sudoers/regress/sudoers/test19.out.ok12
-rw-r--r--plugins/sudoers/regress/sudoers/test19.toke.ok12
-rw-r--r--plugins/sudoers/regress/sudoers/test2.in63
-rw-r--r--plugins/sudoers/regress/sudoers/test2.json.ok420
-rw-r--r--plugins/sudoers/regress/sudoers/test2.ldif.ok168
-rw-r--r--plugins/sudoers/regress/sudoers/test2.ldif2sudo.ok41
-rw-r--r--plugins/sudoers/regress/sudoers/test2.out.ok43
-rw-r--r--plugins/sudoers/regress/sudoers/test2.toke.ok63
-rw-r--r--plugins/sudoers/regress/sudoers/test20.in26
-rw-r--r--plugins/sudoers/regress/sudoers/test20.json.ok114
-rw-r--r--plugins/sudoers/regress/sudoers/test20.ldif.ok28
-rw-r--r--plugins/sudoers/regress/sudoers/test20.ldif2sudo.ok22
-rw-r--r--plugins/sudoers/regress/sudoers/test20.out.ok24
-rw-r--r--plugins/sudoers/regress/sudoers/test20.toke.ok26
-rw-r--r--plugins/sudoers/regress/sudoers/test21.in36
-rw-r--r--plugins/sudoers/regress/sudoers/test21.json.ok169
-rw-r--r--plugins/sudoers/regress/sudoers/test21.ldif.ok39
-rw-r--r--plugins/sudoers/regress/sudoers/test21.ldif2sudo.ok33
-rw-r--r--plugins/sudoers/regress/sudoers/test21.out.ok35
-rw-r--r--plugins/sudoers/regress/sudoers/test21.toke.ok36
-rw-r--r--plugins/sudoers/regress/sudoers/test22.in6
-rw-r--r--plugins/sudoers/regress/sudoers/test22.json.ok88
-rw-r--r--plugins/sudoers/regress/sudoers/test22.ldif.ok40
-rw-r--r--plugins/sudoers/regress/sudoers/test22.ldif2sudo.ok11
-rw-r--r--plugins/sudoers/regress/sudoers/test22.out.ok6
-rw-r--r--plugins/sudoers/regress/sudoers/test22.toke.ok6
-rw-r--r--plugins/sudoers/regress/sudoers/test23.in11
-rw-r--r--plugins/sudoers/regress/sudoers/test23.json.ok102
-rw-r--r--plugins/sudoers/regress/sudoers/test23.ldif.ok14
-rw-r--r--plugins/sudoers/regress/sudoers/test23.ldif2sudo.ok8
-rw-r--r--plugins/sudoers/regress/sudoers/test23.out.ok10
-rw-r--r--plugins/sudoers/regress/sudoers/test23.toke.ok11
-rw-r--r--plugins/sudoers/regress/sudoers/test24.in6
-rw-r--r--plugins/sudoers/regress/sudoers/test24.json.ok61
-rw-r--r--plugins/sudoers/regress/sudoers/test24.ldif.ok39
-rw-r--r--plugins/sudoers/regress/sudoers/test24.ldif2sudo.ok8
-rw-r--r--plugins/sudoers/regress/sudoers/test24.out.ok7
-rw-r--r--plugins/sudoers/regress/sudoers/test24.toke.ok6
-rw-r--r--plugins/sudoers/regress/sudoers/test25.in3
-rw-r--r--plugins/sudoers/regress/sudoers/test25.json.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test25.ldif.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test25.out.ok1
-rw-r--r--plugins/sudoers/regress/sudoers/test25.toke.ok3
-rw-r--r--plugins/sudoers/regress/sudoers/test26.in128
-rw-r--r--plugins/sudoers/regress/sudoers/test26.json.ok626
-rw-r--r--plugins/sudoers/regress/sudoers/test26.ldif.ok134
-rw-r--r--plugins/sudoers/regress/sudoers/test26.ldif2sudo.ok128
-rw-r--r--plugins/sudoers/regress/sudoers/test26.out.ok122
-rw-r--r--plugins/sudoers/regress/sudoers/test26.toke.ok128
-rw-r--r--plugins/sudoers/regress/sudoers/test27.in13
-rw-r--r--plugins/sudoers/regress/sudoers/test27.json.ok125
-rw-r--r--plugins/sudoers/regress/sudoers/test27.ldif.ok60
-rw-r--r--plugins/sudoers/regress/sudoers/test27.ldif2sudo.ok16
-rw-r--r--plugins/sudoers/regress/sudoers/test27.out.ok9
-rw-r--r--plugins/sudoers/regress/sudoers/test27.toke.ok13
-rw-r--r--plugins/sudoers/regress/sudoers/test28.in36
-rw-r--r--plugins/sudoers/regress/sudoers/test28.json.ok199
-rw-r--r--plugins/sudoers/regress/sudoers/test28.ldif.ok117
-rw-r--r--plugins/sudoers/regress/sudoers/test28.ldif2sudo.ok12
-rw-r--r--plugins/sudoers/regress/sudoers/test28.out.ok14
-rw-r--r--plugins/sudoers/regress/sudoers/test28.toke.ok32
-rw-r--r--plugins/sudoers/regress/sudoers/test29.in11
-rw-r--r--plugins/sudoers/regress/sudoers/test29.json.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test29.ldif.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test29.out.ok1
-rw-r--r--plugins/sudoers/regress/sudoers/test29.toke.ok11
-rw-r--r--plugins/sudoers/regress/sudoers/test3.in6
-rw-r--r--plugins/sudoers/regress/sudoers/test3.json.ok45
-rw-r--r--plugins/sudoers/regress/sudoers/test3.ldif.ok12
-rw-r--r--plugins/sudoers/regress/sudoers/test3.ldif2sudo.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test3.out.ok8
-rw-r--r--plugins/sudoers/regress/sudoers/test3.toke.ok6
-rw-r--r--plugins/sudoers/regress/sudoers/test30.in10
-rw-r--r--plugins/sudoers/regress/sudoers/test30.json.ok79
-rw-r--r--plugins/sudoers/regress/sudoers/test30.ldif.ok38
-rw-r--r--plugins/sudoers/regress/sudoers/test30.ldif2sudo.ok11
-rw-r--r--plugins/sudoers/regress/sudoers/test30.out.ok6
-rw-r--r--plugins/sudoers/regress/sudoers/test30.sudo.ok7
-rw-r--r--plugins/sudoers/regress/sudoers/test30.toke.ok10
-rw-r--r--plugins/sudoers/regress/sudoers/test4.in7
-rw-r--r--plugins/sudoers/regress/sudoers/test4.json.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test4.ldif.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test4.out.ok3
-rw-r--r--plugins/sudoers/regress/sudoers/test4.toke.ok5
-rw-r--r--plugins/sudoers/regress/sudoers/test5.in3
-rw-r--r--plugins/sudoers/regress/sudoers/test5.json.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test5.ldif.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test5.out.ok1
-rw-r--r--plugins/sudoers/regress/sudoers/test5.toke.ok3
-rw-r--r--plugins/sudoers/regress/sudoers/test6.in15
-rw-r--r--plugins/sudoers/regress/sudoers/test6.json.ok158
-rw-r--r--plugins/sudoers/regress/sudoers/test6.ldif.ok70
-rw-r--r--plugins/sudoers/regress/sudoers/test6.ldif2sudo.ok5
-rw-r--r--plugins/sudoers/regress/sudoers/test6.out.ok13
-rw-r--r--plugins/sudoers/regress/sudoers/test6.toke.ok15
-rw-r--r--plugins/sudoers/regress/sudoers/test7.in7
-rw-r--r--plugins/sudoers/regress/sudoers/test7.json.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test7.ldif.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test7.out.ok1
-rw-r--r--plugins/sudoers/regress/sudoers/test7.toke.ok7
-rw-r--r--plugins/sudoers/regress/sudoers/test8.in8
-rw-r--r--plugins/sudoers/regress/sudoers/test8.json.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test8.ldif.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test8.out.ok4
-rw-r--r--plugins/sudoers/regress/sudoers/test8.toke.ok7
-rw-r--r--plugins/sudoers/regress/sudoers/test9.in0
-rw-r--r--plugins/sudoers/regress/sudoers/test9.json.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test9.ldif.ok0
-rw-r--r--plugins/sudoers/regress/sudoers/test9.out.ok2
-rw-r--r--plugins/sudoers/regress/sudoers/test9.toke.ok0
-rw-r--r--plugins/sudoers/regress/testsudoers/group17
-rw-r--r--plugins/sudoers/regress/testsudoers/passwd7
-rw-r--r--plugins/sudoers/regress/testsudoers/test1.out.ok11
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test1.sh15
-rw-r--r--plugins/sudoers/regress/testsudoers/test10.out.ok59
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test10.sh46
-rw-r--r--plugins/sudoers/regress/testsudoers/test11.out.ok25
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test11.sh25
-rw-r--r--plugins/sudoers/regress/testsudoers/test12.out.ok18
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test12.sh19
-rw-r--r--plugins/sudoers/regress/testsudoers/test13.out.ok22
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test13.sh22
-rw-r--r--plugins/sudoers/regress/testsudoers/test14.out.ok18
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test14.sh25
-rw-r--r--plugins/sudoers/regress/testsudoers/test15.out.ok19
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test15.sh24
-rw-r--r--plugins/sudoers/regress/testsudoers/test16.out.ok12
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test16.sh41
-rw-r--r--plugins/sudoers/regress/testsudoers/test17.out.ok12
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test17.sh51
-rw-r--r--plugins/sudoers/regress/testsudoers/test18.out.ok72
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test18.sh40
-rw-r--r--plugins/sudoers/regress/testsudoers/test19.out.ok24
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test19.sh20
-rw-r--r--plugins/sudoers/regress/testsudoers/test2.inc1
-rw-r--r--plugins/sudoers/regress/testsudoers/test2.out.ok29
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test2.sh25
-rw-r--r--plugins/sudoers/regress/testsudoers/test20.out.ok12
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test20.sh18
-rw-r--r--plugins/sudoers/regress/testsudoers/test21.out.ok12
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test21.sh20
-rw-r--r--plugins/sudoers/regress/testsudoers/test22.out.ok11
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test22.sh18
-rw-r--r--plugins/sudoers/regress/testsudoers/test23.out.ok11
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test23.sh17
-rw-r--r--plugins/sudoers/regress/testsudoers/test24.out.ok48
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test24.sh42
-rw-r--r--plugins/sudoers/regress/testsudoers/test25.out.ok59
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test25.sh48
-rw-r--r--plugins/sudoers/regress/testsudoers/test26.out.ok57
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test26.sh50
-rw-r--r--plugins/sudoers/regress/testsudoers/test27.out.ok14
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test27.sh22
-rw-r--r--plugins/sudoers/regress/testsudoers/test28.out.ok125
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test28.sh99
-rw-r--r--plugins/sudoers/regress/testsudoers/test29.out.ok133
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test29.sh71
-rw-r--r--plugins/sudoers/regress/testsudoers/test3.out.ok59
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test3.sh48
-rw-r--r--plugins/sudoers/regress/testsudoers/test30.out.ok133
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test30.sh71
-rw-r--r--plugins/sudoers/regress/testsudoers/test31.out.ok131
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test31.sh71
-rw-r--r--plugins/sudoers/regress/testsudoers/test4.out.ok7
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test4.sh13
-rw-r--r--plugins/sudoers/regress/testsudoers/test5.out.ok14
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test5.sh31
-rw-r--r--plugins/sudoers/regress/testsudoers/test6.out.ok12
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test6.sh13
-rw-r--r--plugins/sudoers/regress/testsudoers/test7.out.ok12
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test7.sh13
-rw-r--r--plugins/sudoers/regress/testsudoers/test8.out.ok29
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test8.sh24
-rw-r--r--plugins/sudoers/regress/testsudoers/test9.out.ok12
-rwxr-xr-xplugins/sudoers/regress/testsudoers/test9.sh15
-rw-r--r--plugins/sudoers/regress/unescape/check_unesc.c203
-rw-r--r--plugins/sudoers/regress/visudo/test1.out.ok1
-rwxr-xr-xplugins/sudoers/regress/visudo/test1.sh14
-rw-r--r--plugins/sudoers/regress/visudo/test10.out.ok1
-rwxr-xr-xplugins/sudoers/regress/visudo/test10.sh13
-rw-r--r--plugins/sudoers/regress/visudo/test2.err.ok1
-rw-r--r--plugins/sudoers/regress/visudo/test2.out.ok0
-rwxr-xr-xplugins/sudoers/regress/visudo/test2.sh17
-rw-r--r--plugins/sudoers/regress/visudo/test3.err.ok2
-rw-r--r--plugins/sudoers/regress/visudo/test3.out.ok1
-rwxr-xr-xplugins/sudoers/regress/visudo/test3.sh37
-rw-r--r--plugins/sudoers/regress/visudo/test4.out.ok1
-rwxr-xr-xplugins/sudoers/regress/visudo/test4.sh16
-rw-r--r--plugins/sudoers/regress/visudo/test5.out.ok1
-rwxr-xr-xplugins/sudoers/regress/visudo/test5.sh10
-rw-r--r--plugins/sudoers/regress/visudo/test6.out.ok1
-rwxr-xr-xplugins/sudoers/regress/visudo/test6.sh27
-rw-r--r--plugins/sudoers/regress/visudo/test7.out.ok1
-rwxr-xr-xplugins/sudoers/regress/visudo/test7.sh31
-rw-r--r--plugins/sudoers/regress/visudo/test8.err.ok1
-rw-r--r--plugins/sudoers/regress/visudo/test8.out.ok1
-rwxr-xr-xplugins/sudoers/regress/visudo/test8.sh32
-rw-r--r--plugins/sudoers/regress/visudo/test9.out.ok1
-rwxr-xr-xplugins/sudoers/regress/visudo/test9.sh14
382 files changed, 15349 insertions, 0 deletions
diff --git a/plugins/sudoers/regress/check_symbols/check_symbols.c b/plugins/sudoers/regress/check_symbols/check_symbols.c
new file mode 100644
index 0000000..41e9479
--- /dev/null
+++ b/plugins/sudoers/regress/check_symbols/check_symbols.c
@@ -0,0 +1,114 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2012-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <sudo_compat.h>
+#include <sudo_dso.h>
+#include <sudo_util.h>
+#include <sudo_fatal.h>
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+sudo_noreturn static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-v] plugin.so symbols_file\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ void *handle, *sym;
+ const char *plugin_path;
+ const char *symbols_file;
+ char *cp, line[LINE_MAX];
+ FILE *fp;
+ int ch, ntests = 0, errors = 0;
+
+ initprogname(argc > 0 ? argv[0] : "check_symbols");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 2)
+ usage();
+ plugin_path = argv[0];
+ symbols_file = argv[1];
+
+ handle = sudo_dso_load(plugin_path, SUDO_DSO_LAZY|SUDO_DSO_GLOBAL);
+ if (handle == NULL) {
+ const char *errstr = sudo_dso_strerror();
+ sudo_fatalx_nodebug("unable to load %s: %s", plugin_path,
+ errstr ? errstr : "unknown error");
+ }
+
+ fp = fopen(symbols_file, "r");
+ if (fp == NULL)
+ sudo_fatal_nodebug("unable to open %s", symbols_file);
+
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ ntests++;
+ if ((cp = strchr(line, '\n')) != NULL)
+ *cp = '\0';
+ sym = sudo_dso_findsym(handle, line);
+ if (sym == NULL) {
+ const char *errstr = sudo_dso_strerror();
+ printf("%s: test %d: unable to resolve symbol %s: %s\n",
+ getprogname(), ntests, line, errstr ? errstr : "unknown error");
+ errors++;
+ }
+ }
+
+ /*
+ * Make sure unexported symbols are not available.
+ */
+ ntests++;
+ sym = sudo_dso_findsym(handle, "user_in_group");
+ if (sym != NULL) {
+ printf("%s: test %d: able to resolve local symbol user_in_group\n",
+ getprogname(), ntests);
+ errors++;
+ }
+
+ sudo_dso_unload(handle);
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ exit(errors);
+}
diff --git a/plugins/sudoers/regress/corpus/seed/ldif/invalid_b64.ldif b/plugins/sudoers/regress/corpus/seed/ldif/invalid_b64.ldif
new file mode 100644
index 0000000..b8c7b99
--- /dev/null
+++ b/plugins/sudoers/regress/corpus/seed/ldif/invalid_b64.ldif
@@ -0,0 +1,33 @@
+# defaults, SUDOers, sudo.ws
+dn:: Y249ZGVmYXVsdHMsb3U9U1VET2VycyxkYz1zdWRvLGRjPXdz
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption:: bG9nX29@1dHB1dA==
+
+# root, SUDOers, sudo.ws
+dn:: Y249cm9vdCxvdT1TVURPZXJzLGRjPXN1ZG8sZGM9_d3M=
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: ALL
+sudoCommand: ALL
+sudoOption: !authenticate
+sudoOrder: 10
+
+# %wheel, SUDOers, sudo.ws
+dn:: Y249JXdoZWVsLG91PVNVRE9lcnMsZGM9c3VkbyxkYz13cw!==
+objectClass: top
+objectClass: sudoRole
+cn: %wheel
+sudoUser: %wheel
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: +sudo-hosts
+sudoCommand: ALL
+sudoOption: !authenticate
+sudoOrder: 10
diff --git a/plugins/sudoers/regress/corpus/seed/ldif/pr196.ldif b/plugins/sudoers/regress/corpus/seed/ldif/pr196.ldif
new file mode 100644
index 0000000..78c150a
--- /dev/null
+++ b/plugins/sudoers/regress/corpus/seed/ldif/pr196.ldif
@@ -0,0 +1,6 @@
+# Exercise the fix for https://github.com/sudo-project/sudo/pull/169
+#
+# If the last byte of the input file was a backslash, the parser would
+# read past the end of the buffer.
+#
+dn: cn= Manager\ \ No newline at end of file
diff --git a/plugins/sudoers/regress/corpus/seed/ldif/sample.ldif b/plugins/sudoers/regress/corpus/seed/ldif/sample.ldif
new file mode 100644
index 0000000..81474a1
--- /dev/null
+++ b/plugins/sudoers/regress/corpus/seed/ldif/sample.ldif
@@ -0,0 +1,295 @@
+# LDIF version of the example sudoers file
+
+# Unable to translate ./examples/sudoers:12:17:
+# Defaults>root !set_logname
+
+# Unable to translate ./examples/sudoers:16:24:
+# Defaults!/usr/bin/more, /usr/bin/pg, /usr/bin/less noexec
+
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: syslog=auth
+sudoOption: runcwd=~
+
+dn: cn=root,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 1
+
+dn: cn=%wheel,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %wheel
+sudoUser: %wheel
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 2
+
+dn: cn=FULLTIMERS,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: FULLTIMERS
+sudoUser: millert
+sudoUser: mikef
+sudoUser: dowdy
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoOption: !authenticate
+sudoOption: !lecture
+sudoOption: !runchroot=*
+sudoCommand: ALL
+sudoOrder: 3
+
+dn: cn=PARTTIMERS,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: PARTTIMERS
+sudoUser: bostley
+sudoUser: jwfox
+sudoUser: crawl
+sudoHost: ALL
+sudoCommand: ALL
+sudoOrder: 4
+
+dn: cn=jack,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: jack
+sudoUser: jack
+sudoHost: 128.138.243.0
+sudoHost: 128.138.204.0/24
+sudoHost: 128.138.242.0
+sudoCommand: ALL
+sudoOrder: 5
+
+dn: cn=lisa,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: lisa
+sudoUser: lisa
+sudoHost: 128.138.0.0/255.255.0.0
+sudoCommand: ALL
+sudoOrder: 6
+
+dn: cn=operator,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: operator
+sudoUser: operator
+sudoHost: ALL
+sudoCommand: /usr/sbin/dump
+sudoCommand: /usr/sbin/rdump
+sudoCommand: /usr/sbin/restore
+sudoCommand: /usr/sbin/rrestore
+sudoCommand: /usr/bin/mt
+sudoCommand: sha224:0GomF8mNN3wlDt1HD9XldjJ3SNgpFdbjO1+NsQ== /home/operator/bin/start_backups
+sudoCommand: /usr/bin/kill
+sudoCommand: /usr/bin/top
+sudoCommand: /usr/sbin/shutdown
+sudoCommand: /usr/sbin/halt
+sudoCommand: /usr/sbin/reboot
+sudoCommand: /usr/sbin/lpc
+sudoCommand: /usr/bin/lprm
+sudoCommand: sudoedit /etc/printcap
+sudoCommand: /usr/oper/bin/
+sudoOrder: 7
+
+dn: cn=joe,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: joe
+sudoUser: joe
+sudoHost: ALL
+sudoCommand: /usr/bin/su operator
+sudoOrder: 8
+
+dn: cn=pete,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: pete
+sudoUser: pete
+sudoHost: boa
+sudoHost: nag
+sudoHost: python
+sudoCommand: /usr/bin/passwd ^[a-zA-Z0-9_]+$
+sudoCommand: !/usr/bin/passwd root
+sudoOrder: 9
+
+dn: cn=bob,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: bob
+sudoUser: bob
+sudoHost: bigtime
+sudoHost: eclipse
+sudoHost: moet
+sudoHost: anchor
+sudoRunAsUser: root
+sudoRunAsUser: operator
+sudoCommand: ALL
+sudoOrder: 10
+
+dn: cn=bob_1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: bob_1
+sudoUser: bob
+sudoHost: grolsch
+sudoHost: dandelion
+sudoHost: black
+sudoRunAsUser: root
+sudoRunAsUser: operator
+sudoCommand: ALL
+sudoOrder: 11
+
+dn: cn=jim,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: jim
+sudoUser: jim
+sudoHost: +biglab
+sudoCommand: ALL
+sudoOrder: 12
+
+dn: cn=\+secretaries,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: \+secretaries
+sudoUser: +secretaries
+sudoHost: ALL
+sudoCommand: /usr/sbin/lpc
+sudoCommand: /usr/bin/lprm
+sudoCommand: /usr/bin/adduser
+sudoCommand: /usr/bin/rmuser
+sudoOrder: 13
+
+dn: cn=fred,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: fred
+sudoUser: fred
+sudoHost: ALL
+sudoRunAsUser: oracle
+sudoRunAsUser: sybase
+sudoOption: !authenticate
+sudoCommand: ALL
+sudoOrder: 14
+
+dn: cn=john,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: john
+sudoUser: john
+sudoHost: widget
+sudoHost: thalamus
+sudoHost: foobar
+sudoCommand: /usr/bin/su ^[a-zA-Z0-9_]+$
+sudoCommand: !/usr/bin/su root
+sudoOrder: 15
+
+dn: cn=jen,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: jen
+sudoUser: jen
+sudoHost: ALL
+sudoHost: !primary
+sudoHost: !mail
+sudoHost: !www
+sudoHost: !ns
+sudoCommand: ALL
+sudoOrder: 16
+
+dn: cn=jill,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: jill
+sudoUser: jill
+sudoHost: primary
+sudoHost: mail
+sudoHost: www
+sudoHost: ns
+sudoOption: log_year
+sudoOption: logfile=/var/log/sudo.log
+sudoCommand: /usr/bin/
+sudoCommand: !/usr/bin/su
+sudoCommand: !/sbin/sh
+sudoCommand: !/usr/bin/sh
+sudoCommand: !/usr/bin/csh
+sudoCommand: !/usr/bin/ksh
+sudoCommand: !/usr/local/bin/tcsh
+sudoCommand: !/usr/bin/rsh
+sudoCommand: !/usr/local/bin/zsh
+sudoOrder: 17
+
+dn: cn=steve,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: steve
+sudoUser: steve
+sudoHost: 128.138.243.0
+sudoHost: 128.138.204.0/24
+sudoHost: 128.138.242.0
+sudoRunAsUser: operator
+sudoCommand: /usr/local/op_commands/
+sudoOrder: 18
+
+dn: cn=matt,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: matt
+sudoUser: matt
+sudoHost: valkyrie
+sudoCommand: /usr/bin/kill
+sudoCommand: /usr/bin/top
+sudoOrder: 19
+
+dn: cn=WEBADMIN,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: WEBADMIN
+sudoUser: will
+sudoUser: wendy
+sudoUser: wim
+sudoHost: www
+sudoRunAsUser: www
+sudoCommand: ALL
+sudoOrder: 20
+
+dn: cn=WEBADMIN_1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: WEBADMIN_1
+sudoUser: will
+sudoUser: wendy
+sudoUser: wim
+sudoHost: www
+sudoRunAsUser: root
+sudoCommand: /usr/bin/su www
+sudoOrder: 21
+
+dn: cn=ALL,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: ALL
+sudoUser: ALL
+sudoHost: orion
+sudoHost: perseus
+sudoHost: hercules
+sudoOption: !authenticate
+sudoCommand: /sbin/umount /CDROM
+sudoCommand: /sbin/mount -o nosuid,nodev /dev/cd0a /CDROM
+sudoOrder: 22
+
diff --git a/plugins/sudoers/regress/corpus/seed/ldif/valid_b64.ldif b/plugins/sudoers/regress/corpus/seed/ldif/valid_b64.ldif
new file mode 100644
index 0000000..d17e670
--- /dev/null
+++ b/plugins/sudoers/regress/corpus/seed/ldif/valid_b64.ldif
@@ -0,0 +1,44 @@
+# defaults, SUDOers, sudo.ws
+dn:: Y249ZGVmYXVsdHMsb3U9U1VET2VycyxkYz1zdWRvLGRjPXdz
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption:: bG9nX291dHB1dA==
+
+# root, SUDOers, sudo.ws
+dn:: Y249cm9vdCxvdT1TVURPZXJzLGRjPXN1ZG8sZGM9d3M=
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: ALL
+sudoCommand: ALL
+sudoOption: !authenticate
+sudoOrder: 10
+
+# %wheel, SUDOers, sudo.ws
+dn:: Y249JXdoZWVsLG91PVNVRE9lcnMsZGM9c3VkbyxkYz13cw==
+objectClass: top
+objectClass: sudoRole
+cn: %wheel
+sudoUser: %wheel
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: +sudo-hosts
+sudoCommand: ALL
+sudoOption: !authenticate
+sudoOrder: 10
+
+# millert, SUDOers, other-domain.com
+dn:: Y249bWlsbGVydCxvdT1TVURPZXJzLGRjPW90aGVyLWRvbWFpbixkYz1jb20=
+objectClass: top
+objectClass: sudoRole
+cn: millert
+sudoUser: millert
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: ALL
+sudoOrder: 5
diff --git a/plugins/sudoers/regress/corpus/seed/policy/policy.1 b/plugins/sudoers/regress/corpus/seed/policy/policy.1
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/plugins/sudoers/regress/corpus/seed/policy/policy.1
@@ -0,0 +1 @@
+
diff --git a/plugins/sudoers/regress/corpus/seed/policy/policy.2 b/plugins/sudoers/regress/corpus/seed/policy/policy.2
new file mode 100644
index 0000000..ea1793d
--- /dev/null
+++ b/plugins/sudoers/regress/corpus/seed/policy/policy.2
@@ -0,0 +1,5 @@
+# Minimal test case
+user=root
+uid=0
+gid=0
+host=localhost
diff --git a/plugins/sudoers/regress/corpus/seed/policy/policy.3 b/plugins/sudoers/regress/corpus/seed/policy/policy.3
new file mode 100644
index 0000000..b865e4c
--- /dev/null
+++ b/plugins/sudoers/regress/corpus/seed/policy/policy.3
@@ -0,0 +1,11 @@
+# Reproduce CVE-2021-3156
+run_shell=true
+sudoedit=true
+user=millert
+uid=1000
+gid=1000
+cwd=/home/millert
+host=localhost
+argv=foo
+argv=\
+argv=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
diff --git a/plugins/sudoers/regress/corpus/seed/policy/policy.4 b/plugins/sudoers/regress/corpus/seed/policy/policy.4
new file mode 100644
index 0000000..e5e3191
--- /dev/null
+++ b/plugins/sudoers/regress/corpus/seed/policy/policy.4
@@ -0,0 +1,36 @@
+# sudo -u nobody /usr/bin/id
+
+plugin_path=/usr/libexec/sudo/sudoers.so
+runas_user=nobody
+progname=sudo
+network_addrs=127.0.0.1/255.255.255.0
+plugin_dir=/usr/libexec/sudo/
+
+user=millert
+pid=1234
+ppid=1230
+pgid=1234
+tcpgid=1234
+sid=1230
+uid=1000
+euid=0
+gid=1000
+egid=1000
+groups=20,0,1000
+umask=022
+cwd=/home/millert
+tty=/dev/pts/1
+host=sudo.ws
+lines=24
+cols=80
+rlimit_core=infinity,infinity
+rlimit_cpu=infinity,infinity
+rlimit_data=1610612736,34359738368
+rlimit_fsize=infinity,infinity
+rlimit_memlock=2727370752,8182112256
+rlimit_nofile=256,1024
+rlimit_nproc=256,512
+rlimit_rss=8175603712,8182112256
+rlimit_stack=4194304,33554432
+
+argv=/usr/bin/id
diff --git a/plugins/sudoers/regress/corpus/seed/policy/policy.5 b/plugins/sudoers/regress/corpus/seed/policy/policy.5
new file mode 100644
index 0000000..bffae4d
--- /dev/null
+++ b/plugins/sudoers/regress/corpus/seed/policy/policy.5
@@ -0,0 +1,36 @@
+# sudoedit /etc/hosts
+
+plugin_path=/usr/libexec/sudo/sudoers.so
+progname=sudoedit
+network_addrs=127.0.0.1/255.255.255.0
+plugin_dir=/usr/libexec/sudo/
+
+user=millert
+pid=1234
+ppid=1230
+pgid=1234
+tcpgid=1234
+sid=1230
+uid=1000
+euid=0
+gid=1000
+egid=1000
+groups=20,0,1000
+umask=022
+cwd=/home/millert
+tty=/dev/pts/1
+host=sudo.ws
+lines=24
+cols=80
+rlimit_core=infinity,infinity
+rlimit_cpu=infinity,infinity
+rlimit_data=1610612736,34359738368
+rlimit_fsize=infinity,infinity
+rlimit_memlock=2727370752,8182112256
+rlimit_nofile=256,1024
+rlimit_nproc=256,512
+rlimit_rss=8175603712,8182112256
+rlimit_stack=4194304,33554432
+
+argv=sudoedit
+argv=/etc/hosts
diff --git a/plugins/sudoers/regress/cvtsudoers/sudoers b/plugins/sudoers/regress/cvtsudoers/sudoers
new file mode 100644
index 0000000..8a926f8
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/sudoers
@@ -0,0 +1,126 @@
+#
+# Sample /etc/sudoers file.
+#
+# This file MUST be edited with the 'visudo' command as root.
+#
+# See the sudoers man page for the details on how to write a sudoers file.
+
+##
+# Override built-in defaults
+##
+Defaults syslog=auth
+Defaults>root !set_logname
+Defaults:FULLTIMERS !lecture
+Defaults:millert !authenticate
+Defaults@SERVERS log_year, logfile=/var/log/sudo.log
+Defaults!PAGERS noexec
+
+##
+# User alias specification
+##
+User_Alias FULLTIMERS = millert, mikef, dowdy
+User_Alias PARTTIMERS = bostley, jwfox, crawl
+User_Alias WEBADMIN = will, wendy, wim
+
+##
+# Runas alias specification
+##
+Runas_Alias OP = root, operator
+Runas_Alias DB = oracle, sybase
+
+##
+# Host alias specification
+##
+Host_Alias SPARC = bigtime, eclipse, moet, anchor:\
+ SGI = grolsch, dandelion, black:\
+ ALPHA = widget, thalamus, foobar:\
+ HPPA = boa, nag, python
+Host_Alias CUNETS = 128.138.0.0/255.255.0.0
+Host_Alias CSNETS = 128.138.243.0, 128.138.204.0/24, 128.138.242.0
+Host_Alias SERVERS = primary, mail, www, ns
+Host_Alias CDROM = orion, perseus, hercules
+
+##
+# Cmnd alias specification
+##
+Cmnd_Alias DUMPS = /usr/sbin/dump, /usr/sbin/rdump, /usr/sbin/restore, \
+ /usr/sbin/rrestore, /usr/bin/mt, \
+ sha224:0GomF8mNN3wlDt1HD9XldjJ3SNgpFdbjO1+NsQ== \
+ /home/operator/bin/start_backups
+Cmnd_Alias KILL = /usr/bin/kill, /usr/bin/top
+Cmnd_Alias PRINTING = /usr/sbin/lpc, /usr/bin/lprm
+Cmnd_Alias SHUTDOWN = /usr/sbin/shutdown
+Cmnd_Alias HALT = /usr/sbin/halt
+Cmnd_Alias REBOOT = /usr/sbin/reboot
+Cmnd_Alias SHELLS = /sbin/sh, /usr/bin/sh, /usr/bin/csh, /usr/bin/ksh, \
+ /usr/local/bin/tcsh, /usr/bin/rsh, \
+ /usr/local/bin/zsh
+Cmnd_Alias SU = /usr/bin/su
+Cmnd_Alias VIPW = /usr/sbin/vipw, /usr/bin/passwd, /usr/bin/chsh, \
+ /usr/bin/chfn
+Cmnd_Alias PAGERS = /usr/bin/more, /usr/bin/pg, /usr/bin/less
+
+##
+# User specification
+##
+
+# root and users in group wheel can run anything on any machine as any user
+root ALL = (ALL) ALL
+%wheel ALL = (ALL) ALL
+
+# full time sysadmins can run anything on any machine without a password
+FULLTIMERS ALL = NOPASSWD: ALL
+
+# part time sysadmins may run anything but need a password
+PARTTIMERS ALL = ALL
+
+# jack may run anything on machines in CSNETS
+jack CSNETS = ALL
+
+# lisa may run any command on any host in CUNETS (a class B network)
+lisa CUNETS = ALL
+
+# operator may run maintenance commands and anything in /usr/oper/bin/
+operator ALL = DUMPS, KILL, SHUTDOWN, HALT, REBOOT, PRINTING,\
+ sudoedit /etc/printcap, /usr/oper/bin/
+
+# joe may su only to operator
+joe ALL = /usr/bin/su operator
+
+# pete may change passwords for anyone but root on the hp snakes
+pete HPPA = /usr/bin/passwd [A-Za-z]*, !/usr/bin/passwd *root*
+
+# bob may run anything on the sparc and sgi machines as any user
+# listed in the Runas_Alias "OP" (ie: root and operator)
+bob SPARC = (OP) ALL : SGI = (OP) ALL
+
+# fred can run commands as oracle or sybase without a password
+fred ALL = (DB) NOPASSWD: ALL
+
+# on the alphas, john may su to anyone but root and flags are not allowed
+john ALPHA = /usr/bin/su [!-]*, !/usr/bin/su *root*
+
+# jen can run anything on all machines except the ones
+# in the "SERVERS" Host_Alias
+jen ALL, !SERVERS = ALL
+
+# jill can run any commands in the directory /usr/bin/, except for
+# those in the SU and SHELLS aliases.
+jill SERVERS = /usr/bin/, !SU, !SHELLS
+
+# steve can run any command in the directory /usr/local/op_commands/
+# as user operator.
+steve CSNETS = (operator) /usr/local/op_commands/
+
+# matt needs to be able to kill things on his workstation when
+# they get hung.
+matt valkyrie = KILL
+
+# users in the WEBADMIN User_Alias (will, wendy, and wim)
+# may run any command as user www (which owns the web pages)
+# or simply su to www.
+WEBADMIN www = (www) ALL, (root) /usr/bin/su www
+
+# anyone can mount/unmount a cd-rom on the machines in the CDROM alias
+ALL CDROM = NOPASSWD: /sbin/umount /CDROM,\
+ /sbin/mount -o nosuid\,nodev /dev/cd0a /CDROM
diff --git a/plugins/sudoers/regress/cvtsudoers/sudoers.defs b/plugins/sudoers/regress/cvtsudoers/sudoers.defs
new file mode 100755
index 0000000..b374930
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/sudoers.defs
@@ -0,0 +1,19 @@
+Defaults syslog=auth
+Defaults>ROOT !set_logname
+Defaults:FULLTIMERS !lecture
+Defaults:millert !authenticate
+Defaults@SERVERS log_year, logfile=/var/log/sudo.log
+Defaults!PAGERS noexec
+
+User_Alias FULLTIMERS = millert, mikef, dowdy
+User_Alias PARTTIMERS = bostley, jwfox, crawl
+
+Host_Alias SERVERS = primary, mail, www, ns
+Host_Alias CDROM = orion, perseus, hercules
+
+Cmnd_Alias VIPW = /usr/sbin/vipw, /usr/bin/passwd, /usr/bin/chsh, \
+ /usr/bin/chfn
+Cmnd_Alias PAGERS = /usr/bin/more, /usr/bin/pg, /usr/bin/less
+
+Runas_Alias ROOT = root, toor
+Runas_Alias OPERATOR = operator, backup
diff --git a/plugins/sudoers/regress/cvtsudoers/sudoers1 b/plugins/sudoers/regress/cvtsudoers/sudoers1
new file mode 100644
index 0000000..d7a05ca
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/sudoers1
@@ -0,0 +1,97 @@
+## sudoers file.
+##
+## This file MUST be edited with the 'visudo' command as root.
+## Failure to use 'visudo' may result in syntax or file permission errors
+## that prevent sudo from running.
+##
+## See the sudoers man page for the details on how to write a sudoers file.
+##
+
+##
+## Host alias specification
+##
+## Groups of machines. These may include host names (optionally with wildcards),
+## IP addresses, network numbers or netgroups.
+Host_Alias WEBSERVERS = www1, www2, www3
+
+##
+## User alias specification
+##
+## Groups of users. These may consist of user names, uids, Unix groups,
+## or netgroups.
+User_Alias ADMINS = millert, dowdy, mikef
+
+##
+## Cmnd alias specification
+##
+## Groups of commands. Often used to group related commands together.
+Cmnd_Alias PROCESSES = /usr/bin/nice, /bin/kill, /usr/bin/renice, \
+ /usr/bin/pkill, /usr/bin/top
+Cmnd_Alias REBOOT = /sbin/halt, /sbin/reboot, /sbin/poweroff
+
+##
+## Defaults specification
+##
+## You may wish to keep some of the following environment variables
+## when running commands via sudo.
+##
+## Locale settings
+# Defaults env_keep += "LANG LANGUAGE LINGUAS LC_* _XKB_CHARSET"
+##
+## Run X applications through sudo; HOME is used to find the
+## .Xauthority file. Note that other programs use HOME to find
+## configuration files and this may lead to privilege escalation!
+# Defaults env_keep += "HOME"
+##
+## X11 resource path settings
+# Defaults env_keep += "XAPPLRESDIR XFILESEARCHPATH XUSERFILESEARCHPATH"
+##
+## Desktop path settings
+# Defaults env_keep += "QTDIR KDEDIR"
+##
+## Allow sudo-run commands to inherit the callers' ConsoleKit session
+# Defaults env_keep += "XDG_SESSION_COOKIE"
+##
+## Uncomment to enable special input methods. Care should be taken as
+## this may allow users to subvert the command being run via sudo.
+# Defaults env_keep += "XMODIFIERS GTK_IM_MODULE QT_IM_MODULE QT_IM_SWITCHER"
+##
+## Uncomment to use a hard-coded PATH instead of the user's to find commands
+Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+##
+## Uncomment to send mail if the user does not enter the correct password.
+# Defaults mail_badpass
+##
+## Uncomment to enable logging of a command's output, except for
+## sudoreplay and reboot. Use sudoreplay to play back logged sessions.
+Defaults log_output
+Defaults!/usr/bin/sudoreplay !log_output
+Defaults!/usr/local/bin/sudoreplay !log_output
+Defaults!REBOOT !log_output
+
+##
+## Runas alias specification
+##
+
+##
+## User privilege specification
+##
+root ALL=(ALL) ALL
+ALL ALL=(ALL) /usr/bin/id
+
+## Uncomment to allow members of group wheel to execute any command
+# %wheel ALL=(ALL) ALL
+
+## Same thing without a password
+# %wheel ALL=(ALL) NOPASSWD: ALL
+
+## Uncomment to allow members of group sudo to execute any command
+# %sudo ALL=(ALL) ALL
+
+## Uncomment to allow any user to run sudo if they know the password
+## of the user they are running the command as (root by default).
+# Defaults targetpw # Ask for the password of the target user
+# ALL ALL=(ALL) ALL # WARNING: only use this together with 'Defaults targetpw'
+
+## Read drop-in files from /etc/sudoers.d
+#@includedir /etc/sudoers.d
diff --git a/plugins/sudoers/regress/cvtsudoers/sudoers2 b/plugins/sudoers/regress/cvtsudoers/sudoers2
new file mode 100644
index 0000000..442d5e6
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/sudoers2
@@ -0,0 +1,97 @@
+## sudoers file.
+##
+## This file MUST be edited with the 'visudo' command as root.
+## Failure to use 'visudo' may result in syntax or file permission errors
+## that prevent sudo from running.
+##
+## See the sudoers man page for the details on how to write a sudoers file.
+##
+
+##
+## Host alias specification
+##
+## Groups of machines. These may include host names (optionally with wildcards),
+## IP addresses, network numbers or netgroups.
+Host_Alias WEBSERVERS = www1, www2, www3, www4
+
+##
+## User alias specification
+##
+## Groups of users. These may consist of user names, uids, Unix groups,
+## or netgroups.
+User_Alias ADMINS = millert, dowdy, mikef
+
+##
+## Cmnd alias specification
+##
+## Groups of commands. Often used to group related commands together.
+Cmnd_Alias PROCESSES = /usr/bin/nice, /bin/kill, /usr/bin/renice, \
+ /usr/bin/pkill, /usr/bin/top
+Cmnd_Alias REBOOT = /sbin/halt, /sbin/reboot, /sbin/poweroff
+
+##
+## Defaults specification
+##
+## You may wish to keep some of the following environment variables
+## when running commands via sudo.
+##
+## Locale settings
+# Defaults env_keep += "LANG LANGUAGE LINGUAS LC_* _XKB_CHARSET"
+##
+## Run X applications through sudo; HOME is used to find the
+## .Xauthority file. Note that other programs use HOME to find
+## configuration files and this may lead to privilege escalation!
+# Defaults env_keep += "HOME"
+##
+## X11 resource path settings
+# Defaults env_keep += "XAPPLRESDIR XFILESEARCHPATH XUSERFILESEARCHPATH"
+##
+## Desktop path settings
+# Defaults env_keep += "QTDIR KDEDIR"
+##
+## Allow sudo-run commands to inherit the callers' ConsoleKit session
+# Defaults env_keep += "XDG_SESSION_COOKIE"
+##
+## Uncomment to enable special input methods. Care should be taken as
+## this may allow users to subvert the command being run via sudo.
+# Defaults env_keep += "XMODIFIERS GTK_IM_MODULE QT_IM_MODULE QT_IM_SWITCHER"
+##
+## Uncomment to use a hard-coded PATH instead of the user's to find commands
+Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+##
+## Uncomment to send mail if the user does not enter the correct password.
+# Defaults mail_badpass
+##
+## Uncomment to enable logging of a command's output, except for
+## sudoreplay and reboot. Use sudoreplay to play back logged sessions.
+Defaults log_output
+Defaults!/usr/bin/sudoreplay !log_output
+Defaults!/usr/local/bin/sudoreplay !log_output
+Defaults!REBOOT !log_output
+
+##
+## Runas alias specification
+##
+
+##
+## User privilege specification
+##
+root ALL=(ALL) ALL
+ALL ALL=(ALL) /usr/bin/id
+
+## Uncomment to allow members of group wheel to execute any command
+# %wheel ALL=(ALL) ALL
+
+## Same thing without a password
+# %wheel ALL=(ALL) NOPASSWD: ALL
+
+## Uncomment to allow members of group sudo to execute any command
+# %sudo ALL=(ALL) ALL
+
+## Uncomment to allow any user to run sudo if they know the password
+## of the user they are running the command as (root by default).
+# Defaults targetpw # Ask for the password of the target user
+# ALL ALL=(ALL) ALL # WARNING: only use this together with 'Defaults targetpw'
+
+## Read drop-in files from /etc/sudoers.d
+#@includedir /etc/sudoers.d
diff --git a/plugins/sudoers/regress/cvtsudoers/sudoers3 b/plugins/sudoers/regress/cvtsudoers/sudoers3
new file mode 100644
index 0000000..ee2769e
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/sudoers3
@@ -0,0 +1,97 @@
+## sudoers file.
+##
+## This file MUST be edited with the 'visudo' command as root.
+## Failure to use 'visudo' may result in syntax or file permission errors
+## that prevent sudo from running.
+##
+## See the sudoers man page for the details on how to write a sudoers file.
+##
+
+##
+## Host alias specification
+##
+## Groups of machines. These may include host names (optionally with wildcards),
+## IP addresses, network numbers or netgroups.
+Host_Alias WEBSERVERS_1 = www1, www2, www3, www5
+
+##
+## User alias specification
+##
+## Groups of users. These may consist of user names, uids, Unix groups,
+## or netgroups.
+User_Alias ADMINS = millert, dowdy, mikef
+
+##
+## Cmnd alias specification
+##
+## Groups of commands. Often used to group related commands together.
+Cmnd_Alias PROCESSES = /usr/bin/nice, /bin/kill, /usr/bin/renice, \
+ /usr/bin/pkill, /usr/bin/top
+Cmnd_Alias REBOOT = /sbin/halt, /sbin/reboot, /sbin/poweroff
+
+##
+## Defaults specification
+##
+## You may wish to keep some of the following environment variables
+## when running commands via sudo.
+##
+## Locale settings
+# Defaults env_keep += "LANG LANGUAGE LINGUAS LC_* _XKB_CHARSET"
+##
+## Run X applications through sudo; HOME is used to find the
+## .Xauthority file. Note that other programs use HOME to find
+## configuration files and this may lead to privilege escalation!
+# Defaults env_keep += "HOME"
+##
+## X11 resource path settings
+# Defaults env_keep += "XAPPLRESDIR XFILESEARCHPATH XUSERFILESEARCHPATH"
+##
+## Desktop path settings
+# Defaults env_keep += "QTDIR KDEDIR"
+##
+## Allow sudo-run commands to inherit the callers' ConsoleKit session
+# Defaults env_keep += "XDG_SESSION_COOKIE"
+##
+## Uncomment to enable special input methods. Care should be taken as
+## this may allow users to subvert the command being run via sudo.
+# Defaults env_keep += "XMODIFIERS GTK_IM_MODULE QT_IM_MODULE QT_IM_SWITCHER"
+##
+## Uncomment to use a hard-coded PATH instead of the user's to find commands
+Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+##
+## Uncomment to send mail if the user does not enter the correct password.
+# Defaults mail_badpass
+##
+## Uncomment to enable logging of a command's output, except for
+## sudoreplay and reboot. Use sudoreplay to play back logged sessions.
+# Defaults log_output
+# Defaults!/usr/bin/sudoreplay !log_output
+# Defaults!/usr/local/bin/sudoreplay !log_output
+# Defaults!REBOOT !log_output
+
+##
+## Runas alias specification
+##
+
+##
+## User privilege specification
+##
+root ALL=(ALL) ALL
+ALL ALL=(ALL) /usr/bin/id
+
+## Uncomment to allow members of group wheel to execute any command
+# %wheel ALL=(ALL) ALL
+
+## Same thing without a password
+# %wheel ALL=(ALL) NOPASSWD: ALL
+
+## Uncomment to allow members of group sudo to execute any command
+# %sudo ALL=(ALL) ALL
+
+## Uncomment to allow any user to run sudo if they know the password
+## of the user they are running the command as (root by default).
+# Defaults targetpw # Ask for the password of the target user
+# ALL ALL=(ALL) ALL # WARNING: only use this together with 'Defaults targetpw'
+
+## Read drop-in files from /etc/sudoers.d
+#@includedir /etc/sudoers.d
diff --git a/plugins/sudoers/regress/cvtsudoers/sudoers4 b/plugins/sudoers/regress/cvtsudoers/sudoers4
new file mode 100644
index 0000000..c85b0dc
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/sudoers4
@@ -0,0 +1,97 @@
+## sudoers file.
+##
+## This file MUST be edited with the 'visudo' command as root.
+## Failure to use 'visudo' may result in syntax or file permission errors
+## that prevent sudo from running.
+##
+## See the sudoers man page for the details on how to write a sudoers file.
+##
+
+##
+## Host alias specification
+##
+## Groups of machines. These may include host names (optionally with wildcards),
+## IP addresses, network numbers or netgroups.
+Host_Alias WEBSERVERS_1 = www1, www2, www3, www5
+
+##
+## User alias specification
+##
+## Groups of users. These may consist of user names, uids, Unix groups,
+## or netgroups.
+User_Alias ADMINS = millert, dowdy, mikef
+
+##
+## Cmnd alias specification
+##
+## Groups of commands. Often used to group related commands together.
+Cmnd_Alias PROCESSES = /usr/bin/nice, /bin/kill, /usr/bin/renice, \
+ /usr/bin/pkill, /usr/bin/top
+Cmnd_Alias REBOOT = /sbin/halt, /sbin/reboot, /sbin/poweroff
+
+##
+## Defaults specification
+##
+## You may wish to keep some of the following environment variables
+## when running commands via sudo.
+##
+## Locale settings
+# Defaults env_keep += "LANG LANGUAGE LINGUAS LC_* _XKB_CHARSET"
+##
+## Run X applications through sudo; HOME is used to find the
+## .Xauthority file. Note that other programs use HOME to find
+## configuration files and this may lead to privilege escalation!
+# Defaults env_keep += "HOME"
+##
+## X11 resource path settings
+# Defaults env_keep += "XAPPLRESDIR XFILESEARCHPATH XUSERFILESEARCHPATH"
+##
+## Desktop path settings
+# Defaults env_keep += "QTDIR KDEDIR"
+##
+## Allow sudo-run commands to inherit the callers' ConsoleKit session
+# Defaults env_keep += "XDG_SESSION_COOKIE"
+##
+## Uncomment to enable special input methods. Care should be taken as
+## this may allow users to subvert the command being run via sudo.
+# Defaults env_keep += "XMODIFIERS GTK_IM_MODULE QT_IM_MODULE QT_IM_SWITCHER"
+##
+## Uncomment to use a hard-coded PATH instead of the user's to find commands
+Defaults secure_path="/opt/sudo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+##
+## Uncomment to send mail if the user does not enter the correct password.
+# Defaults mail_badpass
+##
+## Uncomment to enable logging of a command's output, except for
+## sudoreplay and reboot. Use sudoreplay to play back logged sessions.
+# Defaults log_output
+# Defaults!/usr/bin/sudoreplay !log_output
+# Defaults!/usr/local/bin/sudoreplay !log_output
+# Defaults!REBOOT !log_output
+
+##
+## Runas alias specification
+##
+
+##
+## User privilege specification
+##
+root ALL=(ALL) ALL
+ALL ALL=(ALL) /usr/bin/id
+
+## Uncomment to allow members of group wheel to execute any command
+# %wheel ALL=(ALL) ALL
+
+## Same thing without a password
+# %wheel ALL=(ALL) NOPASSWD: ALL
+
+## Uncomment to allow members of group sudo to execute any command
+# %sudo ALL=(ALL) ALL
+
+## Uncomment to allow any user to run sudo if they know the password
+## of the user they are running the command as (root by default).
+# Defaults targetpw # Ask for the password of the target user
+# ALL ALL=(ALL) ALL # WARNING: only use this together with 'Defaults targetpw'
+
+## Read drop-in files from /etc/sudoers.d
+#@includedir /etc/sudoers.d
diff --git a/plugins/sudoers/regress/cvtsudoers/test1.out.ok b/plugins/sudoers/regress/cvtsudoers/test1.out.ok
new file mode 100644
index 0000000..da3f555
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test1.out.ok
@@ -0,0 +1,14 @@
+Defaults syslog=auth
+Defaults>root !set_logname
+Defaults:FULLTIMERS !lecture
+Defaults:millert !authenticate
+Defaults!PAGERS noexec
+
+Host_Alias CDROM = orion, perseus, hercules
+User_Alias FULLTIMERS = millert, mikef, dowdy
+Cmnd_Alias PAGERS = /usr/bin/more, /usr/bin/pg, /usr/bin/less
+
+FULLTIMERS ALL = NOPASSWD: ALL
+
+ALL CDROM = NOPASSWD: /sbin/umount /CDROM, /sbin/mount -o nosuid\,nodev\
+ /dev/cd0a /CDROM
diff --git a/plugins/sudoers/regress/cvtsudoers/test1.sh b/plugins/sudoers/regress/cvtsudoers/test1.sh
new file mode 100755
index 0000000..ba5f87f
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test1.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test user and host filters
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -m user=millert,host=hercules $TESTDIR/sudoers
+
+exit 0
diff --git a/plugins/sudoers/regress/cvtsudoers/test10.out.ok b/plugins/sudoers/regress/cvtsudoers/test10.out.ok
new file mode 100644
index 0000000..26a05d2
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test10.out.ok
@@ -0,0 +1 @@
+Defaults!PAGERS noexec
diff --git a/plugins/sudoers/regress/cvtsudoers/test10.sh b/plugins/sudoers/regress/cvtsudoers/test10.sh
new file mode 100755
index 0000000..38550a9
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test10.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test command defaults filtering
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -s aliases,privileges -d command $TESTDIR/sudoers
+
+exit 0
diff --git a/plugins/sudoers/regress/cvtsudoers/test11.out.ok b/plugins/sudoers/regress/cvtsudoers/test11.out.ok
new file mode 100644
index 0000000..5c4c4e8
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test11.out.ok
@@ -0,0 +1,7 @@
+Defaults!PAGERS noexec
+
+Host_Alias CDROM = orion, perseus, hercules
+Runas_Alias OPERATOR = operator, backup
+Cmnd_Alias PAGERS = /usr/bin/more, /usr/bin/pg, /usr/bin/less
+User_Alias PARTTIMERS = bostley, jwfox, crawl
+Cmnd_Alias VIPW = /usr/sbin/vipw, /usr/bin/passwd, /usr/bin/chsh, /usr/bin/chfn
diff --git a/plugins/sudoers/regress/cvtsudoers/test11.sh b/plugins/sudoers/regress/cvtsudoers/test11.sh
new file mode 100755
index 0000000..f8d37ef
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test11.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# Test that Aliases are removed when filtering by defaults type
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -d command $TESTDIR/sudoers.defs
diff --git a/plugins/sudoers/regress/cvtsudoers/test12.out.ok b/plugins/sudoers/regress/cvtsudoers/test12.out.ok
new file mode 100644
index 0000000..7f2b15e
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test12.out.ok
@@ -0,0 +1,8 @@
+Defaults:FULLTIMERS !lecture
+Defaults:millert !authenticate
+
+Host_Alias CDROM = orion, perseus, hercules
+User_Alias FULLTIMERS = millert, mikef, dowdy
+Runas_Alias OPERATOR = operator, backup
+User_Alias PARTTIMERS = bostley, jwfox, crawl
+Cmnd_Alias VIPW = /usr/sbin/vipw, /usr/bin/passwd, /usr/bin/chsh, /usr/bin/chfn
diff --git a/plugins/sudoers/regress/cvtsudoers/test12.sh b/plugins/sudoers/regress/cvtsudoers/test12.sh
new file mode 100755
index 0000000..dd2cab0
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test12.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# Test that Aliases are removed when filtering by defaults type
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -d user $TESTDIR/sudoers.defs
diff --git a/plugins/sudoers/regress/cvtsudoers/test13.out.ok b/plugins/sudoers/regress/cvtsudoers/test13.out.ok
new file mode 100644
index 0000000..5276327
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test13.out.ok
@@ -0,0 +1,7 @@
+Defaults@SERVERS log_year, logfile=/var/log/sudo.log
+
+Host_Alias CDROM = orion, perseus, hercules
+Runas_Alias OPERATOR = operator, backup
+User_Alias PARTTIMERS = bostley, jwfox, crawl
+Host_Alias SERVERS = primary, mail, www, ns
+Cmnd_Alias VIPW = /usr/sbin/vipw, /usr/bin/passwd, /usr/bin/chsh, /usr/bin/chfn
diff --git a/plugins/sudoers/regress/cvtsudoers/test13.sh b/plugins/sudoers/regress/cvtsudoers/test13.sh
new file mode 100755
index 0000000..8ee6000
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test13.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# Test that Aliases are removed when filtering by defaults type
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -d host $TESTDIR/sudoers.defs
diff --git a/plugins/sudoers/regress/cvtsudoers/test14.out.ok b/plugins/sudoers/regress/cvtsudoers/test14.out.ok
new file mode 100644
index 0000000..3f7710a
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test14.out.ok
@@ -0,0 +1,7 @@
+Defaults>ROOT !set_logname
+
+Host_Alias CDROM = orion, perseus, hercules
+Runas_Alias OPERATOR = operator, backup
+User_Alias PARTTIMERS = bostley, jwfox, crawl
+Runas_Alias ROOT = root, toor
+Cmnd_Alias VIPW = /usr/sbin/vipw, /usr/bin/passwd, /usr/bin/chsh, /usr/bin/chfn
diff --git a/plugins/sudoers/regress/cvtsudoers/test14.sh b/plugins/sudoers/regress/cvtsudoers/test14.sh
new file mode 100755
index 0000000..4486faa
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test14.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# Test that Aliases are removed when filtering by defaults type
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -d runas $TESTDIR/sudoers.defs
diff --git a/plugins/sudoers/regress/cvtsudoers/test15.out.ok b/plugins/sudoers/regress/cvtsudoers/test15.out.ok
new file mode 100644
index 0000000..5177139
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test15.out.ok
@@ -0,0 +1 @@
+user1 host1, host2, host3 = ALL
diff --git a/plugins/sudoers/regress/cvtsudoers/test15.sh b/plugins/sudoers/regress/cvtsudoers/test15.sh
new file mode 100755
index 0000000..6d65c78
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test15.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test filters and pruning
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -p -m user=user1 <<EOF
+user1, user2, user3, %group1 host1, host2, host3 = ALL
+EOF
diff --git a/plugins/sudoers/regress/cvtsudoers/test16.out.ok b/plugins/sudoers/regress/cvtsudoers/test16.out.ok
new file mode 100644
index 0000000..38359b1
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test16.out.ok
@@ -0,0 +1 @@
+user2 host2 = ALL
diff --git a/plugins/sudoers/regress/cvtsudoers/test16.sh b/plugins/sudoers/regress/cvtsudoers/test16.sh
new file mode 100755
index 0000000..4a1632c
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test16.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test filters and pruning
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -p -m user=user2,host=host2 <<EOF
+user1, user2, user3, %group1 host1, host2, host3 = ALL
+EOF
diff --git a/plugins/sudoers/regress/cvtsudoers/test17.out.ok b/plugins/sudoers/regress/cvtsudoers/test17.out.ok
new file mode 100644
index 0000000..d35dd06
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test17.out.ok
@@ -0,0 +1 @@
+%group1 host1 = ALL
diff --git a/plugins/sudoers/regress/cvtsudoers/test17.sh b/plugins/sudoers/regress/cvtsudoers/test17.sh
new file mode 100755
index 0000000..e8e8082
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test17.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test filters and pruning
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -p -m group=group1,host=host1 <<EOF
+user1, user2, user3, %group1 host1, host2, host3 = ALL
+EOF
diff --git a/plugins/sudoers/regress/cvtsudoers/test18.out.ok b/plugins/sudoers/regress/cvtsudoers/test18.out.ok
new file mode 100644
index 0000000..3055452
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test18.out.ok
@@ -0,0 +1 @@
+%group1 ALL = ALL
diff --git a/plugins/sudoers/regress/cvtsudoers/test18.sh b/plugins/sudoers/regress/cvtsudoers/test18.sh
new file mode 100755
index 0000000..cceed89
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test18.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test filters and pruning
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -p -m group=group1,host=somehost <<EOF
+user1, user2, user3, %group1 ALL = ALL
+EOF
diff --git a/plugins/sudoers/regress/cvtsudoers/test19.out.ok b/plugins/sudoers/regress/cvtsudoers/test19.out.ok
new file mode 100644
index 0000000..66b95ac
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test19.out.ok
@@ -0,0 +1,11 @@
+Defaults syslog=auth
+Defaults>root !set_logname
+Defaults:FULLTIMERS !lecture
+Defaults@SERVERS log_year, logfile=/var/log/sudo.log
+Defaults!PAGERS noexec
+
+User_Alias FULLTIMERS = millert, mikef, dowdy
+Cmnd_Alias PAGERS = /usr/bin/more, /usr/bin/pg, /usr/bin/less
+Host_Alias SERVERS = primary, mail, www, ns
+
+FULLTIMERS ALL = NOPASSWD: ALL
diff --git a/plugins/sudoers/regress/cvtsudoers/test19.sh b/plugins/sudoers/regress/cvtsudoers/test19.sh
new file mode 100755
index 0000000..b3c1bf7
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test19.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# Test filters and pruning; alias contents don't get pruned
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -p -m user=FULLTIMERS,host=SERVERS $TESTDIR/sudoers
diff --git a/plugins/sudoers/regress/cvtsudoers/test2.out.ok b/plugins/sudoers/regress/cvtsudoers/test2.out.ok
new file mode 100644
index 0000000..d99e0e5
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test2.out.ok
@@ -0,0 +1,10 @@
+Defaults syslog=auth
+Defaults>root !set_logname
+Defaults:millert, mikef, dowdy !lecture
+Defaults:millert !authenticate
+Defaults!/usr/bin/more, /usr/bin/pg, /usr/bin/less noexec
+
+millert, mikef, dowdy ALL = NOPASSWD: ALL
+
+ALL orion, perseus, hercules = NOPASSWD: /sbin/umount /CDROM, /sbin/mount -o\
+ nosuid\,nodev /dev/cd0a /CDROM
diff --git a/plugins/sudoers/regress/cvtsudoers/test2.sh b/plugins/sudoers/regress/cvtsudoers/test2.sh
new file mode 100755
index 0000000..70e9553
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test2.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test user and host filters, expanding aliases
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -e -m user=millert,host=hercules $TESTDIR/sudoers
+
+exit 0
diff --git a/plugins/sudoers/regress/cvtsudoers/test20.conf b/plugins/sudoers/regress/cvtsudoers/test20.conf
new file mode 100644
index 0000000..b60725c
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test20.conf
@@ -0,0 +1,6 @@
+defaults = global
+expand_aliases = yes
+input_format = sudoers
+match = user=user2
+output_format = sudoers
+prune_matches = yes
diff --git a/plugins/sudoers/regress/cvtsudoers/test20.out.ok b/plugins/sudoers/regress/cvtsudoers/test20.out.ok
new file mode 100644
index 0000000..79b420b
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test20.out.ok
@@ -0,0 +1 @@
+user2 ALL = /usr/bin/id
diff --git a/plugins/sudoers/regress/cvtsudoers/test20.sh b/plugins/sudoers/regress/cvtsudoers/test20.sh
new file mode 100755
index 0000000..e651e16
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test20.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+#
+# Test cvtsudoers.conf
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c $TESTDIR/test20.conf <<EOF
+Defaults:SOMEUSERS authenticate, timestamp_timeout=0
+User_Alias SOMEUSERS = user1, user2, user3
+
+SOMEUSERS ALL = /usr/bin/id
+EOF
diff --git a/plugins/sudoers/regress/cvtsudoers/test21.conf b/plugins/sudoers/regress/cvtsudoers/test21.conf
new file mode 100644
index 0000000..01fd3a3
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test21.conf
@@ -0,0 +1,8 @@
+defaults = all
+expand_aliases = no
+input_format = sudoers
+order_increment = 10
+order_start = 1000
+output_format = ldif
+sudoers_base = ou=SUDOers,dc=my-domain,dc=com
+suppress = defaults
diff --git a/plugins/sudoers/regress/cvtsudoers/test21.out.ok b/plugins/sudoers/regress/cvtsudoers/test21.out.ok
new file mode 100644
index 0000000..78285f1
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test21.out.ok
@@ -0,0 +1,24 @@
+dn: cn=ALL,ou=SUDOers,dc=my-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: ALL
+sudoUser: ALL
+sudoHost: ALL
+sudoRunAsUser:
+sudoOption: !authenticate
+sudoCommand: /usr/bin/id
+sudoOrder: 1000
+
+dn: cn=FULLTIMERS,ou=SUDOers,dc=my-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: FULLTIMERS
+sudoUser: user1
+sudoUser: user2
+sudoUser: user3
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 1010
+
diff --git a/plugins/sudoers/regress/cvtsudoers/test21.sh b/plugins/sudoers/regress/cvtsudoers/test21.sh
new file mode 100755
index 0000000..836a353
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test21.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# Test cvtsudoers.conf
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c $TESTDIR/test21.conf <<EOF
+Defaults authenticate, timestamp_timeout=0
+User_Alias FULLTIMERS = user1, user2, user3
+
+ALL ALL = (:) NOPASSWD:/usr/bin/id
+FULLTIMERS ALL = (ALL:ALL) ALL
+EOF
diff --git a/plugins/sudoers/regress/cvtsudoers/test22.out.ok b/plugins/sudoers/regress/cvtsudoers/test22.out.ok
new file mode 100644
index 0000000..d404815
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test22.out.ok
@@ -0,0 +1,31 @@
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: log_output
+
+dn: cn=root,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoOption: !authenticate
+sudoCommand: ALL
+sudoOrder: 10
+
+dn: cn=%wheel,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %wheel
+sudoUser: %wheel
+sudoHost: +sudo-hosts
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoOption: !authenticate
+sudoCommand: ALL
+sudoOrder: 20
+
diff --git a/plugins/sudoers/regress/cvtsudoers/test22.sh b/plugins/sudoers/regress/cvtsudoers/test22.sh
new file mode 100755
index 0000000..9b4899b
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test22.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+#
+# Test LDAP base filtering.
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -i ldif -b "ou=SUDOers,dc=sudo,dc=ws" -I 10 -O 10 <<EOF
+dn: dc=sudo,dc=ws
+objectClass: dcObject
+objectClass: organization
+dc: courtesan
+o: Sudo World Headquarters
+description: Sudo World Headquarters
+
+# Organizational Role for Directory Manager
+dn: cn=Manager,dc=sudo,dc=ws
+objectClass: organizationalRole
+cn: Manager
+description: Directory Manager
+
+# SUDOers, sudo.ws
+dn: ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: organizationalUnit
+description: SUDO Configuration Subtree
+ou: SUDOers
+
+# defaults, SUDOers, sudo.ws
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: log_output
+
+# root, SUDOers, sudo.ws
+dn: cn=root,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: ALL
+sudoCommand: ALL
+sudoOption: !authenticate
+sudoOrder: 10
+
+# %wheel, SUDOers, sudo.ws
+dn: cn=%wheel,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %wheel
+sudoUser: %wheel
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: +sudo-hosts
+sudoCommand: ALL
+sudoOption: !authenticate
+sudoOrder: 10
+
+# millert, SUDOers, other-domain.com
+dn: cn=millert,ou=SUDOers,dc=other-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: millert
+sudoUser: millert
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: ALL
+sudoOrder: 5
+EOF
diff --git a/plugins/sudoers/regress/cvtsudoers/test23.out.ok b/plugins/sudoers/regress/cvtsudoers/test23.out.ok
new file mode 100644
index 0000000..7fc33c2
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test23.out.ok
@@ -0,0 +1,20 @@
+Defaults logfile=/var/log/sudo
+
+root ALL = (ALL) ALL
+
+%wheel ALL = (ALL) ALL
+
++admins ALL = NOPASSWD: ALL
+
+jack 128.138.204.0/24, 128.138.242.0, 128.138.243.0 = ALL
+
+lisa 128.138.0.0/255.255.0.0 = ALL
+
+operator ALL = /usr/sbin/dump, /usr/sbin/rdump, /usr/sbin/restore,\
+ /usr/sbin/rrestore, /usr/bin/mt,\
+ sha224:0GomF8mNN3wlDt1HD9XldjJ3SNgpFdbjO1+NsQ==\
+ /home/operator/bin/start_backups, /usr/bin/kill, /usr/bin/top,\
+ /usr/sbin/shutdown, /usr/sbin/halt, /usr/sbin/reboot, /usr/sbin/lpc,\
+ /usr/bin/lprm, sudoedit /etc/printcap, /usr/oper/bin/
+
+joe ALL = /usr/bin/su operator
diff --git a/plugins/sudoers/regress/cvtsudoers/test23.sh b/plugins/sudoers/regress/cvtsudoers/test23.sh
new file mode 100755
index 0000000..02b8238
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test23.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+#
+# Test round-tripping of sudoers -> LDIF -> sudoers
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -b "ou=SUDOers,dc=sudo,dc=ws" $TESTDIR/test23.out.ok | \
+ $CVTSUDOERS -c "" -i LDIF -f sudoers | grep -v '^#'
diff --git a/plugins/sudoers/regress/cvtsudoers/test24.out.ok b/plugins/sudoers/regress/cvtsudoers/test24.out.ok
new file mode 100644
index 0000000..0951767
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test24.out.ok
@@ -0,0 +1,89 @@
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: logfile=/var/log/sudo
+
+dn: cn=root,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoCommand: ALL
+sudoOrder: 1
+
+dn: cn=%wheel,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %wheel
+sudoUser: %wheel
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoCommand: ALL
+sudoOrder: 2
+
+dn: cn=\+admins,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: \+admins
+sudoUser: +admins
+sudoHost: ALL
+sudoOption: !authenticate
+sudoCommand: ALL
+sudoOrder: 3
+
+dn: cn=jack,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: jack
+sudoUser: jack
+sudoHost: 128.138.204.0/24
+sudoHost: 128.138.242.0
+sudoHost: 128.138.243.0
+sudoCommand: ALL
+sudoOrder: 4
+
+dn: cn=lisa,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: lisa
+sudoUser: lisa
+sudoHost: 128.138.0.0/255.255.0.0
+sudoCommand: ALL
+sudoOrder: 5
+
+dn: cn=operator,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: operator
+sudoUser: operator
+sudoHost: ALL
+sudoCommand: /usr/sbin/dump
+sudoCommand: /usr/sbin/rdump
+sudoCommand: /usr/sbin/restore
+sudoCommand: /usr/sbin/rrestore
+sudoCommand: /usr/bin/mt
+sudoCommand: sha224:0GomF8mNN3wlDt1HD9XldjJ3SNgpFdbjO1+NsQ== /home/operator/bin/start_backups
+sudoCommand: /usr/bin/kill
+sudoCommand: /usr/bin/top
+sudoCommand: /usr/sbin/shutdown
+sudoCommand: /usr/sbin/halt
+sudoCommand: /usr/sbin/reboot
+sudoCommand: /usr/sbin/lpc
+sudoCommand: /usr/bin/lprm
+sudoCommand: sudoedit /etc/printcap
+sudoCommand: /usr/oper/bin/
+sudoOrder: 6
+
+dn: cn=joe,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: joe
+sudoUser: joe
+sudoHost: ALL
+sudoCommand: /usr/bin/su operator
+sudoOrder: 7
+
diff --git a/plugins/sudoers/regress/cvtsudoers/test24.sh b/plugins/sudoers/regress/cvtsudoers/test24.sh
new file mode 100755
index 0000000..72d9983
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test24.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+#
+# Test round-tripping of LDIF -> sudoers -> LDIF
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -i LDIF -f sudoers $TESTDIR/test24.out.ok | \
+ $CVTSUDOERS -c "" -b "ou=SUDOers,dc=sudo,dc=ws"
diff --git a/plugins/sudoers/regress/cvtsudoers/test25.out.ok b/plugins/sudoers/regress/cvtsudoers/test25.out.ok
new file mode 100644
index 0000000..d404815
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test25.out.ok
@@ -0,0 +1,31 @@
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: log_output
+
+dn: cn=root,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoOption: !authenticate
+sudoCommand: ALL
+sudoOrder: 10
+
+dn: cn=%wheel,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %wheel
+sudoUser: %wheel
+sudoHost: +sudo-hosts
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoOption: !authenticate
+sudoCommand: ALL
+sudoOrder: 20
+
diff --git a/plugins/sudoers/regress/cvtsudoers/test25.sh b/plugins/sudoers/regress/cvtsudoers/test25.sh
new file mode 100755
index 0000000..bbb9b51
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test25.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+#
+# Test LDIF base64 attribute parsing
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -i ldif -b "ou=SUDOers,dc=sudo,dc=ws" -I 10 -O 10 <<EOF
+# defaults, SUDOers, sudo.ws
+dn:: Y249ZGVmYXVsdHMsb3U9U1VET2VycyxkYz1zdWRvLGRjPXdz
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption:: bG9nX291dHB1dA==
+
+# root, SUDOers, sudo.ws
+dn:: Y249cm9vdCxvdT1TVURPZXJzLGRjPXN1ZG8sZGM9d3M=
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: ALL
+sudoCommand: ALL
+sudoOption: !authenticate
+sudoOrder: 10
+
+# %wheel, SUDOers, sudo.ws
+dn:: Y249JXdoZWVsLG91PVNVRE9lcnMsZGM9c3VkbyxkYz13cw==
+objectClass: top
+objectClass: sudoRole
+cn: %wheel
+sudoUser: %wheel
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: +sudo-hosts
+sudoCommand: ALL
+sudoOption: !authenticate
+sudoOrder: 10
+
+# millert, SUDOers, other-domain.com
+dn:: Y249bWlsbGVydCxvdT1TVURPZXJzLGRjPW90aGVyLWRvbWFpbixkYz1jb20=
+objectClass: top
+objectClass: sudoRole
+cn: millert
+sudoUser: millert
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: ALL
+sudoOrder: 5
+EOF
diff --git a/plugins/sudoers/regress/cvtsudoers/test26.err.ok b/plugins/sudoers/regress/cvtsudoers/test26.err.ok
new file mode 100644
index 0000000..a9c5e6a
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test26.err.ok
@@ -0,0 +1,3 @@
+cvtsudoers: invalid LDIF attribute: sudoOption:: bG9nX29@1dHB1dA==
+cvtsudoers: invalid LDIF attribute: dn:: Y249cm9vdCxvdT1TVURPZXJzLGRjPXN1ZG8sZGM9_d3M=
+cvtsudoers: invalid LDIF attribute: dn:: Y249JXdoZWVsLG91PVNVRE9lcnMsZGM9c3VkbyxkYz13cw!==
diff --git a/plugins/sudoers/regress/cvtsudoers/test26.out.ok b/plugins/sudoers/regress/cvtsudoers/test26.out.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test26.out.ok
diff --git a/plugins/sudoers/regress/cvtsudoers/test26.sh b/plugins/sudoers/regress/cvtsudoers/test26.sh
new file mode 100755
index 0000000..08c0246
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test26.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+#
+# Test LDIF invalid base64 attribute parsing
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -i ldif -b "ou=SUDOers,dc=sudo,dc=ws" -I 10 -O 10 <<EOF
+# defaults, SUDOers, sudo.ws
+dn:: Y249ZGVmYXVsdHMsb3U9U1VET2VycyxkYz1zdWRvLGRjPXdz
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption:: bG9nX29@1dHB1dA==
+
+# root, SUDOers, sudo.ws
+dn:: Y249cm9vdCxvdT1TVURPZXJzLGRjPXN1ZG8sZGM9_d3M=
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: ALL
+sudoCommand: ALL
+sudoOption: !authenticate
+sudoOrder: 10
+
+# %wheel, SUDOers, sudo.ws
+dn:: Y249JXdoZWVsLG91PVNVRE9lcnMsZGM9c3VkbyxkYz13cw!==
+objectClass: top
+objectClass: sudoRole
+cn: %wheel
+sudoUser: %wheel
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: +sudo-hosts
+sudoCommand: ALL
+sudoOption: !authenticate
+sudoOrder: 10
+EOF
+
+# cvtsudoers should exit with an error
+if [ $? -eq 0 ]; then
+ exit 1
+else
+ exit 0
+fi
diff --git a/plugins/sudoers/regress/cvtsudoers/test27.out.ok b/plugins/sudoers/regress/cvtsudoers/test27.out.ok
new file mode 100644
index 0000000..ab9c948
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test27.out.ok
@@ -0,0 +1,16 @@
+dn:: Y249ZGVmYXVsdHMsb3U9U1VET2Vyc8KpLGRjPXN1ZG8sZGM9d3M=
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption:: YmFkcGFzc19tZXNzYWdlPUJhZCBwYXNzd29yZMKh
+
+dn:: Y249cm9vdCxvdT1TVURPZXJzwqksZGM9c3VkbyxkYz13cw==
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoHost: ALL
+sudoCommand: ALL
+sudoOrder: 1
+
diff --git a/plugins/sudoers/regress/cvtsudoers/test27.sh b/plugins/sudoers/regress/cvtsudoers/test27.sh
new file mode 100755
index 0000000..c1a2df3
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test27.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+#
+# Test base64 encoding of non-safe strings
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -b "ou=SUDOers©,dc=sudo,dc=ws" <<EOF
+Defaults badpass_message="Bad password¡"
+
+root ALL = ALL
+EOF
diff --git a/plugins/sudoers/regress/cvtsudoers/test28.out.ok b/plugins/sudoers/regress/cvtsudoers/test28.out.ok
new file mode 100644
index 0000000..ba19cb9
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test28.out.ok
@@ -0,0 +1,10 @@
+Defaults log_output
+
+# sudoRole millert
+millert ALL = (ALL : ALL) ALL
+
+# sudoRole root
+root ALL = (ALL : ALL) NOPASSWD: ALL
+
+# sudoRole %wheel
+%wheel +sudo-hosts = (ALL : ALL) NOPASSWD: ALL
diff --git a/plugins/sudoers/regress/cvtsudoers/test28.sh b/plugins/sudoers/regress/cvtsudoers/test28.sh
new file mode 100755
index 0000000..e902355
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test28.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+#
+# Test LDAP sudoOrder when converting to sudoers.
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -i ldif -b "ou=SUDOers,dc=sudo,dc=ws" -f sudoers <<EOF
+dn: dc=sudo,dc=ws
+objectClass: dcObject
+objectClass: organization
+dc: courtesan
+o: Sudo World Headquarters
+description: Sudo World Headquarters
+
+# Organizational Role for Directory Manager
+dn: cn=Manager,dc=sudo,dc=ws
+objectClass: organizationalRole
+cn: Manager
+description: Directory Manager
+
+# SUDOers, sudo.ws
+dn: ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: organizationalUnit
+description: SUDO Configuration Subtree
+ou: SUDOers
+
+# defaults, SUDOers, sudo.ws
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: log_output
+
+# root, SUDOers, sudo.ws
+dn: cn=root,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: ALL
+sudoCommand: ALL
+sudoOption: !authenticate
+sudoOrder: 10
+
+# %wheel, SUDOers, sudo.ws
+dn: cn=%wheel,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %wheel
+sudoUser: %wheel
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: +sudo-hosts
+sudoCommand: ALL
+sudoOption: !authenticate
+sudoOrder: 20
+
+# millert, SUDOers, sudo.ws
+dn: cn=millert,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: millert
+sudoUser: millert
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: ALL
+sudoCommand: ALL
+sudoOrder: 5
+EOF
diff --git a/plugins/sudoers/regress/cvtsudoers/test29.out.ok b/plugins/sudoers/regress/cvtsudoers/test29.out.ok
new file mode 100644
index 0000000..c168898
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test29.out.ok
@@ -0,0 +1,4 @@
+Defaults log_output
+
+# sudoRole millert, millert2
+millert ALL = (ALL : ALL) ALL, NOPASSWD: ALL
diff --git a/plugins/sudoers/regress/cvtsudoers/test29.sh b/plugins/sudoers/regress/cvtsudoers/test29.sh
new file mode 100755
index 0000000..2cb50f3
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test29.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+#
+# Test LDAP sudoOrder when converting to sudoers.
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -i ldif -b "ou=SUDOers,dc=sudo,dc=ws" -f sudoers <<EOF
+dn: dc=sudo,dc=ws
+objectClass: dcObject
+objectClass: organization
+dc: courtesan
+o: Sudo World Headquarters
+description: Sudo World Headquarters
+
+# Organizational Role for Directory Manager
+dn: cn=Manager,dc=sudo,dc=ws
+objectClass: organizationalRole
+cn: Manager
+description: Directory Manager
+
+# SUDOers, sudo.ws
+dn: ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: organizationalUnit
+description: SUDO Configuration Subtree
+ou: SUDOers
+
+# defaults, SUDOers, sudo.ws
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: log_output
+
+# millert, SUDOers, sudo.ws
+dn: cn=millert,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: millert
+sudoUser: millert
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: ALL
+sudoCommand: ALL
+sudoOrder: 5
+
+# millert2, SUDOers, sudo.ws
+dn: cn=millert2,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: millert2
+sudoUser: millert
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: ALL
+sudoCommand: ALL
+sudoOption: !authenticate
+sudoOrder: 10
+EOF
diff --git a/plugins/sudoers/regress/cvtsudoers/test3.out.ok b/plugins/sudoers/regress/cvtsudoers/test3.out.ok
new file mode 100644
index 0000000..8a37975
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test3.out.ok
@@ -0,0 +1,7 @@
+Defaults syslog=auth
+Defaults>root !set_logname
+Defaults!PAGERS noexec
+
+Cmnd_Alias PAGERS = /usr/bin/more, /usr/bin/pg, /usr/bin/less
+
+%wheel ALL = (ALL) ALL
diff --git a/plugins/sudoers/regress/cvtsudoers/test3.sh b/plugins/sudoers/regress/cvtsudoers/test3.sh
new file mode 100755
index 0000000..8e42cbc
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test3.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test group and host filters
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -m group=wheel,host=blackhole $TESTDIR/sudoers
+
+exit 0
diff --git a/plugins/sudoers/regress/cvtsudoers/test30.out.ok b/plugins/sudoers/regress/cvtsudoers/test30.out.ok
new file mode 100644
index 0000000..009a54e
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test30.out.ok
@@ -0,0 +1,26 @@
+{
+ "User_Specs": [
+ {
+ "User_List": [
+ { "username": "user1" },
+ { "username": "user2" },
+ { "username": "user3" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "/path/to/cmda" },
+ {
+ "command": "/path/to/cmdb",
+ "negated": true
+ },
+ { "command": "/path/to/cmdc" }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/cvtsudoers/test30.sh b/plugins/sudoers/regress/cvtsudoers/test30.sh
new file mode 100755
index 0000000..c5f7615
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test30.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# Test alias expansion when converting to JSON.
+# See https://bugzilla.sudo.ws/show_bug.cgi?id=853
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -e -f json <<EOF
+Cmnd_Alias CMDA=/path/to/cmda
+Cmnd_Alias CMDB=/path/to/cmdb
+Cmnd_Alias CMDC=/path/to/cmdc
+User_Alias USERS=user1,user2,user3
+USERS ALL=CMDA,!CMDB,CMDC
+EOF
diff --git a/plugins/sudoers/regress/cvtsudoers/test31.conf b/plugins/sudoers/regress/cvtsudoers/test31.conf
new file mode 100644
index 0000000..345dbfc
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test31.conf
@@ -0,0 +1,9 @@
+defaults = all
+expand_aliases = no
+input_format = sudoers
+order_increment = 5
+order_padding = 2
+order_start = 1000
+output_format = ldif
+sudoers_base = ou=SUDOers,dc=my-domain,dc=com
+suppress = defaults
diff --git a/plugins/sudoers/regress/cvtsudoers/test31.out.ok b/plugins/sudoers/regress/cvtsudoers/test31.out.ok
new file mode 100644
index 0000000..41ffd1b
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test31.out.ok
@@ -0,0 +1,24 @@
+dn: cn=ALL,ou=SUDOers,dc=my-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: ALL
+sudoUser: ALL
+sudoHost: ALL
+sudoRunAsUser:
+sudoOption: !authenticate
+sudoCommand: /usr/bin/id
+sudoOrder: 100000
+
+dn: cn=FULLTIMERS,ou=SUDOers,dc=my-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: FULLTIMERS
+sudoUser: user1
+sudoUser: user2
+sudoUser: user3
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 100005
+
diff --git a/plugins/sudoers/regress/cvtsudoers/test31.sh b/plugins/sudoers/regress/cvtsudoers/test31.sh
new file mode 100755
index 0000000..38dd93c
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test31.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# Test cvtsudoers.conf with padding
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c $TESTDIR/test31.conf <<EOF
+Defaults authenticate, timestamp_timeout=0
+User_Alias FULLTIMERS = user1, user2, user3
+
+ALL ALL = (:) NOPASSWD:/usr/bin/id
+FULLTIMERS ALL = (ALL:ALL) ALL
+EOF
diff --git a/plugins/sudoers/regress/cvtsudoers/test32.err.ok b/plugins/sudoers/regress/cvtsudoers/test32.err.ok
new file mode 100644
index 0000000..c001436
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test32.err.ok
@@ -0,0 +1 @@
+cvtsudoers: too many sudoers entries, maximum 10
diff --git a/plugins/sudoers/regress/cvtsudoers/test32.out.ok b/plugins/sudoers/regress/cvtsudoers/test32.out.ok
new file mode 100644
index 0000000..9900199
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test32.out.ok
@@ -0,0 +1,119 @@
+dn: cn=user0,ou=SUDOers,dc=my-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: user0
+sudoUser: user0
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 10000
+
+dn: cn=user1,ou=SUDOers,dc=my-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: user1
+sudoUser: user1
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 10001
+
+dn: cn=user2,ou=SUDOers,dc=my-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: user2
+sudoUser: user2
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 10002
+
+dn: cn=user3,ou=SUDOers,dc=my-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: user3
+sudoUser: user3
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 10003
+
+dn: cn=user4,ou=SUDOers,dc=my-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: user4
+sudoUser: user4
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 10004
+
+dn: cn=user5,ou=SUDOers,dc=my-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: user5
+sudoUser: user5
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 10005
+
+dn: cn=user6,ou=SUDOers,dc=my-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: user6
+sudoUser: user6
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 10006
+
+dn: cn=user7,ou=SUDOers,dc=my-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: user7
+sudoUser: user7
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 10007
+
+dn: cn=user8,ou=SUDOers,dc=my-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: user8
+sudoUser: user8
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 10008
+
+dn: cn=user9,ou=SUDOers,dc=my-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: user9
+sudoUser: user9
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 10009
+
+dn: cn=user10,ou=SUDOers,dc=my-domain,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: user10
+sudoUser: user10
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
diff --git a/plugins/sudoers/regress/cvtsudoers/test32.sh b/plugins/sudoers/regress/cvtsudoers/test32.sh
new file mode 100755
index 0000000..2119da1
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test32.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# Test cvtsudoers.conf with invalid padding
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -b "ou=SUDOers,dc=my-domain,dc=com" -O 1000 -P 1 <<EOF
+user0 ALL = (ALL:ALL) ALL
+user1 ALL = (ALL:ALL) ALL
+user2 ALL = (ALL:ALL) ALL
+user3 ALL = (ALL:ALL) ALL
+user4 ALL = (ALL:ALL) ALL
+user5 ALL = (ALL:ALL) ALL
+user6 ALL = (ALL:ALL) ALL
+user7 ALL = (ALL:ALL) ALL
+user8 ALL = (ALL:ALL) ALL
+user9 ALL = (ALL:ALL) ALL
+user10 ALL = (ALL:ALL) ALL
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/cvtsudoers/test33.out.ok b/plugins/sudoers/regress/cvtsudoers/test33.out.ok
new file mode 100644
index 0000000..6584701
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test33.out.ok
@@ -0,0 +1,7 @@
+Defaults log_output
+
+# sudoRole root
+root ALL = (ALL : ALL) NOPASSWD: ALL
+
+# sudoRole millert
+millert ALL = (ALL, !bin, !root : ALL, !wheel) ALL
diff --git a/plugins/sudoers/regress/cvtsudoers/test33.sh b/plugins/sudoers/regress/cvtsudoers/test33.sh
new file mode 100755
index 0000000..1fdd20d
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test33.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+#
+# Test LDAP negated sudoRunAsUser and sudoRunAsGroup converted to sudoers.
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -i ldif -b "ou=SUDOers,dc=sudo,dc=ws" -f sudoers <<EOF
+dn: dc=sudo,dc=ws
+objectClass: dcObject
+objectClass: organization
+dc: courtesan
+o: Sudo World Headquarters
+description: Sudo World Headquarters
+
+# Organizational Role for Directory Manager
+dn: cn=Manager,dc=sudo,dc=ws
+objectClass: organizationalRole
+cn: Manager
+description: Directory Manager
+
+# SUDOers, sudo.ws
+dn: ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: organizationalUnit
+description: SUDO Configuration Subtree
+ou: SUDOers
+
+# defaults, SUDOers, sudo.ws
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: log_output
+
+# root, SUDOers, sudo.ws
+dn: cn=root,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoHost: ALL
+sudoCommand: ALL
+sudoOption: !authenticate
+
+# millert, SUDOers, sudo.ws
+dn: cn=millert,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: millert
+sudoUser: millert
+sudoRunAsUser: !bin
+sudoRunAsUser: !root
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoRunAsGroup: !wheel
+sudoHost: ALL
+sudoCommand: ALL
+EOF
diff --git a/plugins/sudoers/regress/cvtsudoers/test34.out.ok b/plugins/sudoers/regress/cvtsudoers/test34.out.ok
new file mode 100644
index 0000000..6a3655e
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test34.out.ok
@@ -0,0 +1,18 @@
+Defaults log_output
+Defaults!/usr/bin/sudoreplay !log_output
+Defaults!/usr/local/bin/sudoreplay !log_output
+Defaults!REBOOT !log_output
+Defaults\
+ secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
+
+User_Alias ADMINS = millert, dowdy, mikef
+Cmnd_Alias PROCESSES = /usr/bin/nice, /bin/kill, /usr/bin/renice,\
+ /usr/bin/pkill, /usr/bin/top
+Cmnd_Alias REBOOT = /sbin/halt, /sbin/reboot, /sbin/poweroff
+Host_Alias WEBSERVERS = www1, www2, www3
+Host_Alias WEBSERVERS_1 = www1, www2, www3, www5
+Host_Alias WEBSERVERS_2 = www1, www2, www3, www4
+
+root ALL = (ALL) ALL
+
+ALL ALL = (ALL) /usr/bin/id
diff --git a/plugins/sudoers/regress/cvtsudoers/test34.sh b/plugins/sudoers/regress/cvtsudoers/test34.sh
new file mode 100755
index 0000000..d9f22e2
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test34.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+#
+# Test cvtsudoers merge
+# * three files, two bound to a host, one global
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -f sudoers -l /dev/null xerxes:${TESTDIR}/sudoers1 ${TESTDIR}/sudoers2 xyzzy:${TESTDIR}/sudoers3
diff --git a/plugins/sudoers/regress/cvtsudoers/test35.out.ok b/plugins/sudoers/regress/cvtsudoers/test35.out.ok
new file mode 100644
index 0000000..47ef832
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test35.out.ok
@@ -0,0 +1,18 @@
+Defaults@xerxes, xyzzy log_output
+Defaults!/usr/bin/sudoreplay !log_output
+Defaults!/usr/local/bin/sudoreplay !log_output
+Defaults!REBOOT !log_output
+Defaults\
+ secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
+
+User_Alias ADMINS = millert, dowdy, mikef
+Cmnd_Alias PROCESSES = /usr/bin/nice, /bin/kill, /usr/bin/renice,\
+ /usr/bin/pkill, /usr/bin/top
+Cmnd_Alias REBOOT = /sbin/halt, /sbin/reboot, /sbin/poweroff
+Host_Alias WEBSERVERS = www1, www2, www3
+Host_Alias WEBSERVERS_1 = www1, www2, www3, www5
+Host_Alias WEBSERVERS_2 = www1, www2, www3, www4
+
+root ALL = (ALL) ALL
+
+ALL ALL = (ALL) /usr/bin/id
diff --git a/plugins/sudoers/regress/cvtsudoers/test35.sh b/plugins/sudoers/regress/cvtsudoers/test35.sh
new file mode 100755
index 0000000..5c2cc1d
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test35.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+#
+# Test cvtsudoers merge
+# * three files, two bound to a host, one global
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -f sudoers -l /dev/null xerxes:${TESTDIR}/sudoers1 xyzzy:${TESTDIR}/sudoers2 ${TESTDIR}/sudoers3
diff --git a/plugins/sudoers/regress/cvtsudoers/test36.out.ok b/plugins/sudoers/regress/cvtsudoers/test36.out.ok
new file mode 100644
index 0000000..5c87fbc
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test36.out.ok
@@ -0,0 +1,17 @@
+Defaults\
+ secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
+Defaults log_output
+Defaults!/usr/bin/sudoreplay !log_output
+Defaults!/usr/local/bin/sudoreplay !log_output
+Defaults!REBOOT !log_output
+
+User_Alias ADMINS = millert, dowdy, mikef
+Cmnd_Alias PROCESSES = /usr/bin/nice, /bin/kill, /usr/bin/renice,\
+ /usr/bin/pkill, /usr/bin/top
+Cmnd_Alias REBOOT = /sbin/halt, /sbin/reboot, /sbin/poweroff
+Host_Alias WEBSERVERS = www1, www2, www3
+Host_Alias WEBSERVERS_1 = www1, www2, www3, www4
+
+root ALL = (ALL) ALL
+
+ALL ALL = (ALL) /usr/bin/id
diff --git a/plugins/sudoers/regress/cvtsudoers/test36.sh b/plugins/sudoers/regress/cvtsudoers/test36.sh
new file mode 100755
index 0000000..be02415
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test36.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+#
+# Test cvtsudoers merge
+# * three files, each bound to a host
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -f sudoers -l /dev/null xerxes:${TESTDIR}/sudoers1 xyzzy:${TESTDIR}/sudoers2 plugh:${TESTDIR}/sudoers2
diff --git a/plugins/sudoers/regress/cvtsudoers/test37.out.ok b/plugins/sudoers/regress/cvtsudoers/test37.out.ok
new file mode 100644
index 0000000..5c87fbc
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test37.out.ok
@@ -0,0 +1,17 @@
+Defaults\
+ secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
+Defaults log_output
+Defaults!/usr/bin/sudoreplay !log_output
+Defaults!/usr/local/bin/sudoreplay !log_output
+Defaults!REBOOT !log_output
+
+User_Alias ADMINS = millert, dowdy, mikef
+Cmnd_Alias PROCESSES = /usr/bin/nice, /bin/kill, /usr/bin/renice,\
+ /usr/bin/pkill, /usr/bin/top
+Cmnd_Alias REBOOT = /sbin/halt, /sbin/reboot, /sbin/poweroff
+Host_Alias WEBSERVERS = www1, www2, www3
+Host_Alias WEBSERVERS_1 = www1, www2, www3, www4
+
+root ALL = (ALL) ALL
+
+ALL ALL = (ALL) /usr/bin/id
diff --git a/plugins/sudoers/regress/cvtsudoers/test37.sh b/plugins/sudoers/regress/cvtsudoers/test37.sh
new file mode 100755
index 0000000..0f38b90
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test37.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test cvtsudoers merge:
+# * two files, each bound to a host
+# * only difference is a conflicting WEBSERVERS definition
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -f sudoers -l /dev/null xerxes:${TESTDIR}/sudoers1 xyzzy:${TESTDIR}/sudoers2
diff --git a/plugins/sudoers/regress/cvtsudoers/test38.out.ok b/plugins/sudoers/regress/cvtsudoers/test38.out.ok
new file mode 100644
index 0000000..26ac014
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test38.out.ok
@@ -0,0 +1,14 @@
+Defaults@xerxes\
+ secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
+Defaults@xyzzy\
+ secure_path=/opt/sudo/bin\:/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
+
+User_Alias ADMINS = millert, dowdy, mikef
+Cmnd_Alias PROCESSES = /usr/bin/nice, /bin/kill, /usr/bin/renice,\
+ /usr/bin/pkill, /usr/bin/top
+Cmnd_Alias REBOOT = /sbin/halt, /sbin/reboot, /sbin/poweroff
+Host_Alias WEBSERVERS_1 = www1, www2, www3, www5
+
+root ALL = (ALL) ALL
+
+ALL ALL = (ALL) /usr/bin/id
diff --git a/plugins/sudoers/regress/cvtsudoers/test38.sh b/plugins/sudoers/regress/cvtsudoers/test38.sh
new file mode 100755
index 0000000..4273136
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test38.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test cvtsudoers merge:
+# * two files, each bound to a host
+# * only difference is a conflicting secure_path definition
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -f sudoers -l /dev/null xerxes:${TESTDIR}/sudoers3 xyzzy:${TESTDIR}/sudoers4
diff --git a/plugins/sudoers/regress/cvtsudoers/test39.out.ok b/plugins/sudoers/regress/cvtsudoers/test39.out.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test39.out.ok
diff --git a/plugins/sudoers/regress/cvtsudoers/test39.sh b/plugins/sudoers/regress/cvtsudoers/test39.sh
new file mode 100755
index 0000000..8ece026
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test39.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+#
+# Test handling of a backslash at EOF with no trailing newline.
+#
+# If compiled with address sanitizer, cvtsudoers will crash without the
+# fix in ceaf706ab74b from https://github.com/sudo-project/sudo/pull/196.
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+printf 'dn: cn= Manager\\' | \
+ $CVTSUDOERS -c "" -b "ou=SUDOers,dc=sudo,dc=ws" -i ldif -f sudoers
diff --git a/plugins/sudoers/regress/cvtsudoers/test4.out.ok b/plugins/sudoers/regress/cvtsudoers/test4.out.ok
new file mode 100644
index 0000000..f8e7d2e
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test4.out.ok
@@ -0,0 +1,5 @@
+Defaults syslog=auth
+Defaults>root !set_logname
+Defaults!/usr/bin/more, /usr/bin/pg, /usr/bin/less noexec
+
+%wheel ALL = (ALL) ALL
diff --git a/plugins/sudoers/regress/cvtsudoers/test4.sh b/plugins/sudoers/regress/cvtsudoers/test4.sh
new file mode 100755
index 0000000..860eff4
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test4.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test group and host filters, expanding aliases
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -e -m group=wheel,host=blackhole $TESTDIR/sudoers
+
+exit 0
diff --git a/plugins/sudoers/regress/cvtsudoers/test40.out.ok b/plugins/sudoers/regress/cvtsudoers/test40.out.ok
new file mode 100644
index 0000000..0fc19e4
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test40.out.ok
@@ -0,0 +1,10 @@
+dn: cn=user0,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user0
+sudoUser: user0
+sudoHost: A00
+sudoRunAsUser: 0
+sudoCommand: /bin/ls
+sudoOrder: 1
+
diff --git a/plugins/sudoers/regress/cvtsudoers/test40.sh b/plugins/sudoers/regress/cvtsudoers/test40.sh
new file mode 100755
index 0000000..ff03c2c
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test40.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Test use-after-free in cvtsudoers when filtering by command.
+#
+# If compiled with address sanitizer, cvtsudoers will crash without the
+# fix in 9da99e0e671e.
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -i ldif -b "ou=SUDOers,dc=sudo,dc=ws" -m cmd='/bin/ls' -p <<EOF
+objectClass:sudoRole
+sudoUser:user0
+sudoHost:A00
+sudoCommand:/bin/ls
+sudoRunAs:0
+
+objectClass:sudoRole
+sudoUser:user0
+sudoHost:A00
+sudoRunAsUser:
+sudoCommand:
+
+objectClass:sudoRole
+sudoUser:user0
+sudoHost:A00
+sudoRunAs:
+sudoCommand:
+EOF
diff --git a/plugins/sudoers/regress/cvtsudoers/test5.out.ok b/plugins/sudoers/regress/cvtsudoers/test5.out.ok
new file mode 100644
index 0000000..d209fdf
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test5.out.ok
@@ -0,0 +1,6 @@
+Defaults syslog=auth
+Defaults>root !set_logname
+Defaults:FULLTIMERS !lecture
+Defaults:millert !authenticate
+Defaults@SERVERS log_year, logfile=/var/log/sudo.log
+Defaults!PAGERS noexec
diff --git a/plugins/sudoers/regress/cvtsudoers/test5.sh b/plugins/sudoers/regress/cvtsudoers/test5.sh
new file mode 100755
index 0000000..dd7782d
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test5.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test defaults type filtering
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -s aliases,privileges -d all $TESTDIR/sudoers
+
+exit 0
diff --git a/plugins/sudoers/regress/cvtsudoers/test6.out.ok b/plugins/sudoers/regress/cvtsudoers/test6.out.ok
new file mode 100644
index 0000000..5e65e61
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test6.out.ok
@@ -0,0 +1 @@
+Defaults syslog=auth
diff --git a/plugins/sudoers/regress/cvtsudoers/test6.sh b/plugins/sudoers/regress/cvtsudoers/test6.sh
new file mode 100755
index 0000000..a6c7ec4
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test6.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test global defaults filtering
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -s aliases,privileges -d global $TESTDIR/sudoers
+
+exit 0
diff --git a/plugins/sudoers/regress/cvtsudoers/test7.out.ok b/plugins/sudoers/regress/cvtsudoers/test7.out.ok
new file mode 100644
index 0000000..381de43
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test7.out.ok
@@ -0,0 +1,2 @@
+Defaults:FULLTIMERS !lecture
+Defaults:millert !authenticate
diff --git a/plugins/sudoers/regress/cvtsudoers/test7.sh b/plugins/sudoers/regress/cvtsudoers/test7.sh
new file mode 100755
index 0000000..2f1a301
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test7.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test user defaults filtering
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -s aliases,privileges -d user $TESTDIR/sudoers
+
+exit 0
diff --git a/plugins/sudoers/regress/cvtsudoers/test8.out.ok b/plugins/sudoers/regress/cvtsudoers/test8.out.ok
new file mode 100644
index 0000000..7079ee0
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test8.out.ok
@@ -0,0 +1 @@
+Defaults>root !set_logname
diff --git a/plugins/sudoers/regress/cvtsudoers/test8.sh b/plugins/sudoers/regress/cvtsudoers/test8.sh
new file mode 100755
index 0000000..d00f90e
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test8.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test runas defaults filtering
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -s aliases,privileges -d runas $TESTDIR/sudoers
+
+exit 0
diff --git a/plugins/sudoers/regress/cvtsudoers/test9.out.ok b/plugins/sudoers/regress/cvtsudoers/test9.out.ok
new file mode 100644
index 0000000..d2a39c4
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test9.out.ok
@@ -0,0 +1 @@
+Defaults@SERVERS log_year, logfile=/var/log/sudo.log
diff --git a/plugins/sudoers/regress/cvtsudoers/test9.sh b/plugins/sudoers/regress/cvtsudoers/test9.sh
new file mode 100755
index 0000000..b668e1f
--- /dev/null
+++ b/plugins/sudoers/regress/cvtsudoers/test9.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test host defaults filtering
+#
+
+: ${CVTSUDOERS=cvtsudoers}
+
+$CVTSUDOERS -c "" -f sudoers -s aliases,privileges -d host $TESTDIR/sudoers
+
+exit 0
diff --git a/plugins/sudoers/regress/editor/check_editor.c b/plugins/sudoers/regress/editor/check_editor.c
new file mode 100644
index 0000000..65e00c0
--- /dev/null
+++ b/plugins/sudoers/regress/editor/check_editor.c
@@ -0,0 +1,174 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2021-2022 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include <sudoers.h>
+#include <def_data.c>
+
+/* Note hard-coded array lengths. */
+struct test_data {
+ const char *editor_var;
+ int nfiles;
+ const char *files[4];
+ const char *editor_path;
+ int edit_argc;
+ const char *edit_argv[10];
+} test_data[] = {
+ {
+ /* Bug #942 */
+ "SUDO_EDITOR=sh -c \"vi \\$1\"",
+ 1,
+ { "/etc/motd", NULL },
+ "/usr/bin/sh",
+ 5,
+ { "sh", "-c", "vi $1", "--", "/etc/motd", NULL }
+ },
+ {
+ /* Try connecting to the emacs server, falling back on plain emacs. */
+ "VISUAL=sh -c \"emacsclient -a emacs -n \\\"\\$@\\\" || emacs \\\"\\$@\\\"\"",
+ 1,
+ { "/etc/motd", NULL },
+ "/usr/bin/sh",
+ 5,
+ { "sh", "-c", "emacsclient -a emacs -n \"$@\" || emacs \"$@\"", "--", "/etc/motd", NULL }
+ },
+ {
+ /* GitHub issue #99 */
+ "EDITOR=/usr/bin/vi\\",
+ 1,
+ { "/etc/hosts", "/bogus/file", NULL },
+ "/usr/bin/vi\\",
+ 3,
+ { "/usr/bin/vi\\", "--", "/etc/hosts", "/bogus/file", NULL }
+ },
+ {
+ /* GitHub issue #179 */
+ "EDITOR=sed -rie s/^\\\\(foo\\\\)/waldo\\\\1/",
+ 1,
+ { "/etc/sudoers", NULL },
+ "/usr/bin/sed",
+ 5,
+ { "sed", "-rie", "s/^\\(foo\\)/waldo\\1/", "--", "/etc/sudoers", NULL }
+ },
+ { NULL }
+};
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/* STUB */
+int
+find_path(const char *infile, char **outfile, struct stat *sbp,
+ const char *path, bool ignore_dot, char * const *allowlist)
+{
+ if (infile[0] == '/') {
+ *outfile = strdup(infile);
+ } else {
+ if (asprintf(outfile, "/usr/bin/%s", infile) == -1)
+ *outfile = NULL;
+ }
+ if (*outfile == NULL)
+ return NOT_FOUND_ERROR;
+ return FOUND;
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct test_data *data;
+ int ch, ntests = 0, errors = 0;
+
+ initprogname(argc > 0 ? argv[0] : "check_editor");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ for (data = test_data; data->editor_var != NULL; data++) {
+ const char *env_editor = NULL;
+ char *cp, *editor_path, **edit_argv = NULL;
+ int i, edit_argc = 0;
+
+ /* clear existing editor environment vars */
+ putenv((char *)"VISUAL=");
+ putenv((char *)"EDITOR=");
+ putenv((char *)"SUDO_EDITOR=");
+
+ putenv((char *)data->editor_var);
+ editor_path = find_editor(data->nfiles, (char **)data->files,
+ &edit_argc, &edit_argv, NULL, &env_editor);
+ ntests++;
+ if (strcmp(editor_path, data->editor_path) != 0) {
+ sudo_warnx("test %d: editor_path: expected \"%s\", got \"%s\"",
+ ntests, data->editor_path, editor_path);
+ errors++;
+ }
+ ntests++;
+ cp = strchr(data->editor_var, '=') + 1;
+ if (strcmp(env_editor, cp) != 0) {
+ sudo_warnx("test %d: env_editor: expected \"%s\", got \"%s\"",
+ ntests, cp, env_editor ? env_editor : "(NULL)");
+ errors++;
+ }
+ ntests++;
+ if (edit_argc != data->edit_argc) {
+ sudo_warnx("test %d: edit_argc: expected %d, got %d",
+ ntests, data->edit_argc, edit_argc);
+ errors++;
+ } else {
+ ntests++;
+ for (i = 0; i < edit_argc; i++) {
+ if (strcmp(edit_argv[i], data->edit_argv[i]) != 0) {
+ sudo_warnx("test %d: edit_argv[%d]: expected \"%s\", got \"%s\"",
+ ntests, i, data->edit_argv[i], edit_argv[i]);
+ errors++;
+ break;
+ }
+ }
+ }
+
+ free(editor_path);
+ edit_argc -= data->nfiles + 1;
+ for (i = 0; i < edit_argc; i++) {
+ free(edit_argv[i]);
+ }
+ free(edit_argv);
+ }
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ exit(errors);
+}
diff --git a/plugins/sudoers/regress/env_match/check_env_pattern.c b/plugins/sudoers/regress/env_match/check_env_pattern.c
new file mode 100644
index 0000000..81649c1
--- /dev/null
+++ b/plugins/sudoers/regress/env_match/check_env_pattern.c
@@ -0,0 +1,96 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2017 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sudoers.h>
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+sudo_noreturn static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-v] [inputfile]\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp = stdin;
+ char pattern[1024], string[1024];
+ int ch, errors = 0, tests = 0, got, want;
+
+ initprogname(argc > 0 ? argv[0] : "check_env_pattern");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0) {
+ if ((fp = fopen(argv[0], "r")) == NULL) {
+ perror(argv[0]);
+ return EXIT_FAILURE;
+ }
+ }
+
+ /*
+ * Read in test file, which is formatted thusly:
+ *
+ * pattern string 1/0
+ *
+ */
+ for (;;) {
+ bool full_match = false;
+
+ got = fscanf(fp, "%s %s %d\n", pattern, string, &want);
+ if (got == EOF)
+ break;
+ if (got == 3) {
+ got = matches_env_pattern(pattern, string, &full_match);
+ if (full_match)
+ got++;
+ if (got != want) {
+ fprintf(stderr,
+ "%s: %s %s: want %d, got %d\n",
+ getprogname(), pattern, string, want, got);
+ errors++;
+ }
+ tests++;
+ }
+ }
+ if (tests != 0) {
+ printf("%s: %d test%s run, %d errors, %d%% success rate\n",
+ getprogname(), tests, tests == 1 ? "" : "s", errors,
+ (tests - errors) * 100 / tests);
+ }
+ return errors;
+}
diff --git a/plugins/sudoers/regress/env_match/data b/plugins/sudoers/regress/env_match/data
new file mode 100644
index 0000000..ea28b1b
--- /dev/null
+++ b/plugins/sudoers/regress/env_match/data
@@ -0,0 +1,22 @@
+foo=(){false;} foo=(){false;} 2
+foo foo=(){false;} 1
+foo= foo=(){false;} 0
+foo=* foo=(){false;} 1
+foo=(* foo=(){false;} 2
+foo=()* foo=(){false;} 2
+foo=*()* foo=(){false;} 2
+foo() foo()=a 1
+foo*() foo()=b 1
+foo*()* foo()= 1
+foo()* foo()= 1
+foo* foo()= 1
+fo*o*() foo()= 1
+fo*o*() fooo()== 1
+fo*o*() foooo()= 1
+fo*o*() foooo 0
+MYPATH=*:/mydir:* MYPATH=/dir1/subdir1:/mydir:/dir2:/dir3/subdir2 2
+MYPATH=*:/mydir:** MYPATH=/dir1/subdir1:/mydir:/dir2:/dir3/subdir2 2
+MYPATH=*:/mdir:* MYPATH=/dir1/subdir1:/mydir:/dir2:/dir3/subdir2 0
+a*a*a*a*a*a* aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=b 1
+a*a*a*a*a*a*=b* aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=b 2
+a*a*a*a*a*a*=* aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=c 1
diff --git a/plugins/sudoers/regress/exptilde/check_exptilde.c b/plugins/sudoers/regress/exptilde/check_exptilde.c
new file mode 100644
index 0000000..6962a94
--- /dev/null
+++ b/plugins/sudoers/regress/exptilde/check_exptilde.c
@@ -0,0 +1,113 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include <sudoers.h>
+
+#include <def_data.c>
+
+struct test_data {
+ const char *input;
+ const char *output;
+ const char *user;
+ bool result;
+} test_data[] = {
+ { "foo/bar", NULL, NULL, false },
+ { "~root", "/", NULL, true },
+ { "~", "/home/millert", "millert", true },
+ { "~/foo", "/home/millert/foo", "millert", true },
+ { "~millert", "/home/millert", "millert", true },
+ { "~millert/bar", "/home/millert/bar", "millert", true },
+ { NULL }
+};
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+ int ch, ntests = 0, errors = 0;
+ struct test_data *td;
+ struct passwd *pw;
+ char *path = NULL;
+ bool result;
+
+ initprogname(argc > 0 ? argv[0] : "check_exptilde");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Prime the passwd cache */
+ pw = sudo_mkpwent("root", 0, 0, "/", "/bin/sh");
+ if (pw == NULL)
+ sudo_fatalx("unable to create passwd entry for root");
+ sudo_pw_delref(pw);
+
+ pw = sudo_mkpwent("millert", 8036, 20, "/home/millert", "/bin/tcsh");
+ if (pw == NULL)
+ sudo_fatalx("unable to create passwd entry for millert");
+ sudo_pw_delref(pw);
+
+ for (td = test_data; td->input != NULL; td++) {
+ ntests++;
+ if ((path = strdup(td->input)) == NULL)
+ sudo_fatal(NULL);
+ result = expand_tilde(&path, td->user);
+ if (result != td->result) {
+ errors++;
+ if (result) {
+ sudo_warnx("unexpected success: input %s, output %s",
+ td->input, path);
+ } else {
+ sudo_warnx("unexpected failure: input %s", td->input);
+ }
+ } else if (td->result && strcmp(path, td->output) != 0) {
+ errors++;
+ sudo_warnx("incorrect output for input %s: expected %s, got %s",
+ td->input, td->output, path);
+ }
+ free(path);
+ }
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
diff --git a/plugins/sudoers/regress/fuzz/fuzz_policy.c b/plugins/sudoers/regress/fuzz/fuzz_policy.c
new file mode 100644
index 0000000..0b01e4e
--- /dev/null
+++ b/plugins/sudoers/regress/fuzz/fuzz_policy.c
@@ -0,0 +1,932 @@
+/*
+ * Copyright (c) 2021-2023 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <string.h>
+#ifndef HAVE_GETADDRINFO
+# include <compat/getaddrinfo.h>
+#endif
+
+#include <sudoers.h>
+#include <sudo_iolog.h>
+#include <interfaces.h>
+#include <timestamp.h>
+#include "auth/sudo_auth.h"
+
+extern char **environ;
+extern sudo_dso_public struct policy_plugin sudoers_policy;
+
+char *audit_msg;
+
+static int pass;
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+static FILE *
+open_data(const uint8_t *data, size_t size)
+{
+#ifdef HAVE_FMEMOPEN
+ /* Operate in-memory. */
+ return fmemopen((void *)data, size, "r");
+#else
+ char tempfile[] = "/tmp/sudoers.XXXXXX";
+ size_t nwritten;
+ int fd;
+
+ /* Use (unlinked) temporary file. */
+ fd = mkstemp(tempfile);
+ if (fd == -1)
+ return NULL;
+ unlink(tempfile);
+ nwritten = write(fd, data, size);
+ if (nwritten != size) {
+ close(fd);
+ return NULL;
+ }
+ lseek(fd, 0, SEEK_SET);
+ return fdopen(fd, "r");
+#endif
+}
+
+/*
+ * Array that gets resized as needed.
+ */
+struct dynamic_array {
+ char **entries;
+ size_t len;
+ size_t size;
+};
+
+static void
+free_strvec(char **vec)
+{
+ size_t i;
+
+ for (i = 0; vec[i] != NULL; i++)
+ free(vec[i]);
+}
+
+static void
+free_dynamic_array(struct dynamic_array *arr)
+{
+ if (arr->entries != NULL) {
+ free_strvec(arr->entries);
+ free(arr->entries);
+ }
+ memset(arr, 0, sizeof(*arr));
+}
+
+static bool
+push(struct dynamic_array *arr, const char *entry)
+{
+ char *copy = NULL;
+
+ if (entry != NULL) {
+ if ((copy = strdup(entry)) == NULL)
+ return false;
+ }
+
+ if (arr->len + (entry != NULL) >= arr->size) {
+ char **tmp = reallocarray(arr->entries, arr->size + 1024, sizeof(char *));
+ if (tmp == NULL) {
+ free(copy);
+ return false;
+ }
+ arr->entries = tmp;
+ arr->size += 1024;
+ }
+ if (copy != NULL)
+ arr->entries[arr->len++] = copy;
+ arr->entries[arr->len] = NULL;
+
+ return true;
+}
+
+static int
+fuzz_conversation(int num_msgs, const struct sudo_conv_message msgs[],
+ struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
+{
+ int n;
+
+ for (n = 0; n < num_msgs; n++) {
+ const struct sudo_conv_message *msg = &msgs[n];
+
+ switch (msg->msg_type & 0xff) {
+ case SUDO_CONV_PROMPT_ECHO_ON:
+ case SUDO_CONV_PROMPT_MASK:
+ case SUDO_CONV_PROMPT_ECHO_OFF:
+ /* input not supported */
+ return -1;
+ case SUDO_CONV_ERROR_MSG:
+ case SUDO_CONV_INFO_MSG:
+ /* no output for fuzzers */
+ break;
+ default:
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int
+fuzz_printf(int msg_type, const char * restrict fmt, ...)
+{
+ return 0;
+}
+
+static int
+fuzz_hook_stub(struct sudo_hook *hook)
+{
+ return 0;
+}
+
+/*
+ * The fuzzing environment may not have DNS available, this may result
+ * in long delays that cause a timeout when fuzzing.
+ * This getaddrinfo() resolves every name as "localhost" (127.0.0.1).
+ */
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+/* Avoid compilation errors if getaddrinfo() or freeaddrinfo() are macros. */
+# undef getaddrinfo
+# undef freeaddrinfo
+
+int
+# ifdef HAVE_GETADDRINFO
+getaddrinfo(
+# else
+sudo_getaddrinfo(
+# endif
+ const char *nodename, const char *servname,
+ const struct addrinfo *hints, struct addrinfo **res)
+{
+ struct addrinfo *ai;
+ struct in_addr addr;
+ unsigned short port = 0;
+
+ /* Stub getaddrinfo(3) to avoid a DNS timeout in CIfuzz. */
+ if (servname == NULL) {
+ /* Must have either nodename or servname. */
+ if (nodename == NULL)
+ return EAI_NONAME;
+ } else {
+ struct servent *servent;
+ const char *errstr;
+
+ /* Parse servname as a port number or IPv4 TCP service name. */
+ port = sudo_strtonum(servname, 0, USHRT_MAX, &errstr);
+ if (errstr != NULL && errno == ERANGE)
+ return EAI_SERVICE;
+ if (hints != NULL && ISSET(hints->ai_flags, AI_NUMERICSERV))
+ return EAI_NONAME;
+ servent = getservbyname(servname, "tcp");
+ if (servent == NULL)
+ return EAI_NONAME;
+ port = htons(servent->s_port);
+ }
+
+ /* Hard-code IPv4 localhost for fuzzing. */
+ ai = calloc(1, sizeof(*ai) + sizeof(struct sockaddr_in));
+ if (ai == NULL)
+ return EAI_MEMORY;
+ ai->ai_canonname = strdup("localhost");
+ if (ai == NULL) {
+ free(ai);
+ return EAI_MEMORY;
+ }
+ ai->ai_family = AF_INET;
+ ai->ai_protocol = IPPROTO_TCP;
+ ai->ai_addrlen = sizeof(struct sockaddr_in);
+ ai->ai_addr = (struct sockaddr *)(ai + 1);
+ inet_pton(AF_INET, "127.0.0.1", &addr);
+ ((struct sockaddr_in *)ai->ai_addr)->sin_family = AF_INET;
+ ((struct sockaddr_in *)ai->ai_addr)->sin_addr = addr;
+ ((struct sockaddr_in *)ai->ai_addr)->sin_port = htons(port);
+ *res = ai;
+ return 0;
+}
+
+void
+# ifdef HAVE_GETADDRINFO
+freeaddrinfo(struct addrinfo *ai)
+# else
+sudo_freeaddrinfo(struct addrinfo *ai)
+# endif
+{
+ struct addrinfo *next;
+
+ while (ai != NULL) {
+ next = ai->ai_next;
+ free(ai->ai_canonname);
+ free(ai);
+ ai = next;
+ }
+}
+#endif /* FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */
+
+enum fuzz_policy_pass {
+ PASS_NONE,
+ PASS_VERSION,
+ PASS_CHECK_LOG_LOCAL,
+ PASS_CHECK_LOG_REMOTE,
+ PASS_CHECK_NOT_FOUND,
+ PASS_CHECK_NOT_FOUND_DOT,
+ PASS_LIST,
+ PASS_LIST_OTHER,
+ PASS_LIST_CHECK,
+ PASS_VALIDATE,
+ PASS_INVALIDATE
+};
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ struct dynamic_array plugin_args = { NULL };
+ struct dynamic_array settings = { NULL };
+ struct dynamic_array user_info = { NULL };
+ struct dynamic_array argv = { NULL };
+ struct dynamic_array env_add = { NULL };
+ char **command_info = NULL, **argv_out = NULL, **user_env_out = NULL;
+ const char *errstr = NULL;
+ const int num_passes = 10;
+ char *line = NULL;
+ size_t linesize = 0;
+ ssize_t linelen;
+ int res = 1;
+ FILE *fp;
+
+ fp = open_data(data, size);
+ if (fp == NULL)
+ return 0;
+
+ initprogname("fuzz_policy");
+ sudoers_debug_register(getprogname(), NULL);
+ if (getenv("SUDO_FUZZ_VERBOSE") == NULL)
+ sudo_warn_set_conversation(fuzz_conversation);
+
+ /* user_info and settings must be non-NULL (even if empty). */
+ push(&user_info, NULL);
+ push(&settings, NULL);
+
+ /* Iterate over each line of data. */
+ while ((linelen = getdelim(&line, &linesize, '\n', fp)) != -1) {
+ if (line[linelen - 1] == '\n')
+ line[linelen - 1] = '\0';
+
+ /* Skip comments and blank lines. */
+ if (line[0] == '#' || line[0] == '\0')
+ continue;
+
+ /* plugin args */
+ if (strncmp(line, "error_recovery=", sizeof("error_recovery=") - 1) == 0) {
+ push(&plugin_args, line);
+ continue;
+ }
+ if (strncmp(line, "sudoers_file=", sizeof("sudoers_file=") - 1) == 0) {
+ push(&plugin_args, line);
+ continue;
+ }
+ if (strncmp(line, "sudoers_mode=", sizeof("sudoers_mode=") - 1) == 0) {
+ push(&plugin_args, line);
+ continue;
+ }
+ if (strncmp(line, "sudoers_gid=", sizeof("sudoers_gid=") - 1) == 0) {
+ push(&plugin_args, line);
+ continue;
+ }
+ if (strncmp(line, "sudoers_uid=", sizeof("sudoers_uid=") - 1) == 0) {
+ push(&plugin_args, line);
+ continue;
+ }
+ if (strncmp(line, "ldap_conf=", sizeof("ldap_conf=") - 1) == 0) {
+ push(&plugin_args, line);
+ continue;
+ }
+ if (strncmp(line, "ldap_secret=", sizeof("ldap_secret=") - 1) == 0) {
+ push(&plugin_args, line);
+ continue;
+ }
+
+ /* user info */
+ if (strncmp(line, "user=", sizeof("user=") - 1) == 0) {
+ push(&user_info, line);
+ continue;
+ }
+ if (strncmp(line, "uid=", sizeof("uid=") - 1) == 0) {
+ push(&user_info, line);
+ continue;
+ }
+ if (strncmp(line, "gid=", sizeof("gid=") - 1) == 0) {
+ push(&user_info, line);
+ continue;
+ }
+ if (strncmp(line, "groups=", sizeof("groups=") - 1) == 0) {
+ push(&user_info, line);
+ continue;
+ }
+ if (strncmp(line, "cwd=", sizeof("cwd=") - 1) == 0) {
+ push(&user_info, line);
+ continue;
+ }
+ if (strncmp(line, "tty=", sizeof("tty=") - 1) == 0) {
+ push(&user_info, line);
+ continue;
+ }
+ if (strncmp(line, "host=", sizeof("host=") - 1) == 0) {
+ push(&user_info, line);
+ continue;
+ }
+ if (strncmp(line, "lines=", sizeof("lines=") - 1) == 0) {
+ push(&user_info, line);
+ continue;
+ }
+ if (strncmp(line, "cols=", sizeof("cols=") - 1) == 0) {
+ push(&user_info, line);
+ continue;
+ }
+ if (strncmp(line, "sid=", sizeof("sid=") - 1) == 0) {
+ push(&user_info, line);
+ continue;
+ }
+ if (strncmp(line, "umask=", sizeof("umask=") - 1) == 0) {
+ push(&user_info, line);
+ continue;
+ }
+ if (strncmp(line, "rlimit_", sizeof("rlimit_") - 1) == 0) {
+ push(&user_info, line);
+ continue;
+ }
+
+ /* First argv entry is the command, the rest are args. */
+ if (strncmp(line, "argv=", sizeof("argv=") - 1) == 0) {
+ push(&argv, line);
+ continue;
+ }
+
+ /* Additional environment variables to add. */
+ if (strncmp(line, "env=", sizeof("env=") - 1) == 0) {
+ const char *cp = line + sizeof("env=") - 1;
+ if (strchr(cp, '=') != NULL)
+ push(&env_add, cp);
+ continue;
+ }
+
+ /* Treat anything else as a setting. */
+ push(&settings, line);
+ }
+ fclose(fp);
+ free(line);
+ line = NULL;
+
+ /* Exercise code paths that use KRB5CCNAME and SUDO_PROMPT. */
+ putenv((char *)"KRB5CCNAME=/tmp/krb5cc_123456");
+ putenv((char *)"SUDO_PROMPT=[sudo] password for %p: ");
+
+ sudoers_policy.register_hooks(SUDO_API_VERSION, fuzz_hook_stub);
+
+ for (pass = 1; res == 1 && pass <= num_passes; pass++) {
+ /* Call policy open function */
+ res = sudoers_policy.open(SUDO_API_VERSION, fuzz_conversation,
+ fuzz_printf, settings.entries, user_info.entries, environ,
+ plugin_args.entries, &errstr);
+ if (res == 1) {
+ if (argv.len == 0) {
+ /* Must have a command to check. */
+ push(&argv, "/usr/bin/id");
+ }
+
+ switch (pass) {
+ case PASS_NONE:
+ break;
+ case PASS_VERSION:
+ /* sudo -V */
+ sudoers_policy.show_version(true);
+ break;
+ case PASS_CHECK_LOG_LOCAL: {
+ /* sudo command w/ local I/O logging (MODE_RUN) */
+ sudoers_policy.check_policy((int)argv.len, argv.entries,
+ env_add.entries, &command_info, &argv_out, &user_env_out,
+ &errstr);
+ /* call check_policy() again to check for leaks. */
+ sudoers_policy.check_policy((int)argv.len, argv.entries,
+ env_add.entries, &command_info, &argv_out, &user_env_out,
+ &errstr);
+ /* sudo_auth_begin_session() is stubbed out below. */
+ sudoers_policy.init_session(NULL, NULL, NULL);
+ break;
+ }
+ case PASS_CHECK_LOG_REMOTE:
+ /* sudo command w/ remote I/O logging (MODE_RUN) */
+ sudoers_policy.check_policy((int)argv.len, argv.entries,
+ env_add.entries, &command_info, &argv_out, &user_env_out,
+ &errstr);
+ /* call check_policy() again to check for leaks. */
+ sudoers_policy.check_policy((int)argv.len, argv.entries,
+ env_add.entries, &command_info, &argv_out, &user_env_out,
+ &errstr);
+ /* sudo_auth_begin_session() is stubbed out below. */
+ sudoers_policy.init_session(NULL, NULL, NULL);
+ break;
+ case PASS_CHECK_NOT_FOUND:
+ /* sudo command (not found) */
+ sudoers_policy.check_policy((int)argv.len, argv.entries,
+ env_add.entries, &command_info, &argv_out, &user_env_out,
+ &errstr);
+ /* sudo_auth_begin_session() is stubbed out below. */
+ sudoers_policy.init_session(NULL, NULL, NULL);
+ break;
+ case PASS_CHECK_NOT_FOUND_DOT:
+ /* sudo command (found but in cwd) */
+ sudoers_policy.check_policy((int)argv.len, argv.entries,
+ env_add.entries, &command_info, &argv_out, &user_env_out,
+ &errstr);
+ /* call check_policy() again to check for leaks. */
+ sudoers_policy.check_policy((int)argv.len, argv.entries,
+ env_add.entries, &command_info, &argv_out, &user_env_out,
+ &errstr);
+ /* sudo_auth_begin_session() is stubbed out below. */
+ sudoers_policy.init_session(NULL, NULL, NULL);
+ break;
+ case PASS_LIST:
+ /* sudo -l (MODE_LIST) */
+ sudoers_policy.list(0, NULL, false, NULL, &errstr);
+ /* call list() again to check for leaks. */
+ sudoers_policy.list(0, NULL, false, NULL, &errstr);
+ break;
+ case PASS_LIST_OTHER:
+ /* sudo -l -U root (MODE_LIST) */
+ sudoers_policy.list(0, NULL, false, "root", &errstr);
+ /* call list() again to check for leaks. */
+ sudoers_policy.list(0, NULL, false, "root", &errstr);
+ break;
+ case PASS_LIST_CHECK:
+ /* sudo -l command (MODE_CHECK) */
+ sudoers_policy.list((int)argv.len, argv.entries, false, NULL,
+ &errstr);
+ /* call list() again to check for leaks. */
+ sudoers_policy.list((int)argv.len, argv.entries, false, NULL,
+ &errstr);
+ break;
+ case PASS_VALIDATE:
+ /* sudo -v (MODE_VALIDATE) */
+ sudoers_policy.validate(&errstr);
+ /* call validate() again to check for leaks. */
+ sudoers_policy.validate(&errstr);
+ break;
+ case PASS_INVALIDATE:
+ /* sudo -k */
+ sudoers_policy.invalidate(false);
+ /* call invalidate() again to check for leaks. */
+ sudoers_policy.invalidate(false);
+ break;
+ }
+ }
+
+ /* Free resources. */
+ if (sudoers_policy.close != NULL)
+ sudoers_policy.close(0, 0);
+ else
+ sudoers_cleanup();
+ }
+
+ sudoers_policy.deregister_hooks(SUDO_API_VERSION, fuzz_hook_stub);
+
+ free_dynamic_array(&plugin_args);
+ free_dynamic_array(&settings);
+ free_dynamic_array(&user_info);
+ free_dynamic_array(&argv);
+ free_dynamic_array(&env_add);
+
+ sudoers_debug_deregister();
+
+ fflush(stdout);
+
+ return 0;
+}
+
+/* STUB */
+bool
+user_is_exempt(const struct sudoers_context *ctx)
+{
+ return false;
+}
+
+/* STUB */
+bool
+set_interfaces(const char *ai)
+{
+ return true;
+}
+
+/* STUB */
+void
+dump_interfaces(const char *ai)
+{
+ return;
+}
+
+/* STUB */
+void
+dump_auth_methods(void)
+{
+ return;
+}
+
+/* STUB */
+int
+sudo_auth_begin_session(const struct sudoers_context *ctx, struct passwd *pw,
+ char **user_env[])
+{
+ return 1;
+}
+
+/* STUB */
+int
+sudo_auth_end_session(void)
+{
+ return 1;
+}
+
+/* STUB */
+bool
+sudo_auth_needs_end_session(void)
+{
+ return false;
+}
+
+/* STUB */
+int
+timestamp_remove(const struct sudoers_context *ctx, bool unlink_it)
+{
+ return true;
+}
+
+/* STUB */
+int
+create_admin_success_flag(const struct sudoers_context *ctx)
+{
+ return true;
+}
+
+/* STUB */
+static int
+sudo_file_open(struct sudoers_context *ctx, struct sudo_nss *nss)
+{
+ return 0;
+}
+
+/* STUB */
+static int
+sudo_file_close(struct sudoers_context *ctx, struct sudo_nss *nss)
+{
+ return 0;
+}
+
+/* STUB */
+static struct sudoers_parse_tree *
+sudo_file_parse(struct sudoers_context *ctx, const struct sudo_nss *nss)
+{
+ static struct sudoers_parse_tree parse_tree;
+
+ return &parse_tree;
+}
+
+/* STUB */
+static int
+sudo_file_query(struct sudoers_context *ctx, const struct sudo_nss *nss,
+ struct passwd *pw)
+{
+ return 0;
+}
+
+/* STUB */
+static int
+sudo_file_getdefs(struct sudoers_context *ctx, const struct sudo_nss *nss)
+{
+ /* Set some Defaults */
+ set_default(ctx, "log_input", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "log_output", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "env_file", "/dev/null", true, "sudoers", 1, 1, false);
+ set_default(ctx, "restricted_env_file", "/dev/null", true, "sudoers", 1, 1, false);
+ set_default(ctx, "exempt_group", "sudo", true, "sudoers", 1, 1, false);
+ set_default(ctx, "runchroot", "/", true, "sudoers", 1, 1, false);
+ set_default(ctx, "runcwd", "~", true, "sudoers", 1, 1, false);
+ set_default(ctx, "fqdn", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "runas_default", "root", true, "sudoers", 1, 1, false);
+ set_default(ctx, "tty_tickets", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "umask", "022", true, "sudoers", 1, 1, false);
+ set_default(ctx, "logfile", "/var/log/sudo", true, "sudoers", 1, 1, false);
+ set_default(ctx, "syslog", "auth", true, "sudoers", 1, 1, false);
+ set_default(ctx, "syslog_goodpri", "notice", true, "sudoers", 1, 1, false);
+ set_default(ctx, "syslog_badpri", "alert", true, "sudoers", 1, 1, false);
+ set_default(ctx, "syslog_maxlen", "2048", true, "sudoers", 1, 1, false);
+ set_default(ctx, "loglinelen", "0", true, "sudoers", 1, 1, false);
+ set_default(ctx, "log_year", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "log_host", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "mailerpath", NULL, false, "sudoers", 1, 1, false);
+ set_default(ctx, "mailerflags", "-t", true, "sudoers", 1, 1, false);
+ set_default(ctx, "mailto", "root@localhost", true, "sudoers", 1, 1, false);
+ set_default(ctx, "mailfrom", "sudo@sudo.ws", true, "sudoers", 1, 1, false);
+ set_default(ctx, "mailsub", "Someone has been naughty on %h", true, "sudoers", 1, 1, false);
+ set_default(ctx, "timestampowner", "#0", true, "sudoers", 1, 1, false);
+ set_default(ctx, "compress_io", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "iolog_flush", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "iolog_flush", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "maxseq", "2176782336", true, "sudoers", 1, 1, false);
+ set_default(ctx, "sudoedit_checkdir", NULL, false, "sudoers", 1, 1, false);
+ set_default(ctx, "sudoedit_follow", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "ignore_iolog_errors", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "ignore_iolog_errors", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "noexec", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "exec_background", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "use_pty", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "utmp_runas", NULL, true, "sudoers", 1, 1, false);
+ set_default(ctx, "iolog_mode", "0640", true, "sudoers", 1, 1, false);
+ set_default(ctx, "iolog_user", NULL, false, "sudoers", 1, 1, false);
+ set_default(ctx, "iolog_group", NULL, false, "sudoers", 1, 1, false);
+ if (pass != PASS_CHECK_LOG_LOCAL) {
+ set_default(ctx, "log_servers", "localhost", true, "sudoers", 1, 1, false);
+ set_default(ctx, "log_server_timeout", "30", true, "sudoers", 1, 1, false);
+ set_default(ctx, "log_server_cabundle", "/etc/ssl/cacert.pem", true, "sudoers", 1, 1, false);
+ set_default(ctx, "log_server_peer_cert", "/etc/ssl/localhost.crt", true, "sudoers", 1, 1, false);
+ set_default(ctx, "log_server_peer_key", "/etc/ssl/private/localhost.key", true, "sudoers", 1, 1, false);
+ }
+
+ return 0;
+}
+
+static struct sudo_nss sudo_nss_file = {
+ { NULL, NULL },
+ "sudoers",
+ sudo_file_open,
+ sudo_file_close,
+ sudo_file_parse,
+ sudo_file_query,
+ sudo_file_getdefs
+};
+
+struct sudo_nss_list *
+sudo_read_nss(void)
+{
+ static struct sudo_nss_list snl = TAILQ_HEAD_INITIALIZER(snl);
+
+ if (TAILQ_EMPTY(&snl))
+ TAILQ_INSERT_TAIL(&snl, &sudo_nss_file, entries);
+
+ return &snl;
+}
+
+/* STUB */
+int
+check_user(struct sudoers_context *ctx, unsigned int validated,
+ unsigned int mode)
+{
+ return AUTH_SUCCESS;
+}
+
+/* STUB */
+int
+check_user_runchroot(const char *runchroot)
+{
+ return true;
+}
+
+/* STUB */
+int
+check_user_runcwd(const char *runcwd)
+{
+ return true;
+}
+
+/* STUB */
+void
+group_plugin_unload(void)
+{
+ return;
+}
+
+/* STUB */
+bool
+log_warning(const struct sudoers_context *ctx, unsigned int flags,
+ const char * restrict fmt, ...)
+{
+ return true;
+}
+
+/* STUB */
+bool
+log_warningx(const struct sudoers_context *ctx, unsigned int flags,
+ const char * restrict fmt, ...)
+{
+ return true;
+}
+
+/* STUB */
+bool
+gai_log_warning(const struct sudoers_context *ctx, unsigned int flags,
+ int errnum, const char * restrict fmt, ...)
+{
+ return true;
+}
+
+/* STUB */
+bool
+log_denial(const struct sudoers_context *ctx, unsigned int status,
+ bool inform_user)
+{
+ return true;
+}
+
+/* STUB */
+bool
+log_failure(const struct sudoers_context *ctx,unsigned int status, int flags)
+{
+ return true;
+}
+
+/* STUB */
+bool
+log_exit_status(const struct sudoers_context *ctx, int exit_status)
+{
+ return true;
+}
+
+/* STUB */
+bool
+mail_parse_errors(const struct sudoers_context *ctx)
+{
+ return true;
+}
+
+/* STUB */
+bool
+log_parse_error(const struct sudoers_context *ctx, const char *file,
+ int line, int column, const char * restrict fmt, va_list args)
+{
+ return true;
+}
+
+/* STUB */
+int
+audit_failure(const struct sudoers_context *ctx, char *const argv[],
+ char const * restrict const fmt, ...)
+{
+ return 0;
+}
+
+/* STUB */
+unsigned int
+sudoers_lookup(struct sudo_nss_list *snl, struct sudoers_context *ctx,
+ time_t now, sudoers_lookup_callback_fn_t callback, void *cb_data,
+ int *cmnd_status, int pwflag)
+{
+ return VALIDATE_SUCCESS;
+}
+
+/* STUB */
+int
+display_cmnd(struct sudoers_context *ctx, const struct sudo_nss_list *snl,
+ struct passwd *pw, int verbose)
+{
+ return true;
+}
+
+/* STUB */
+int
+display_privs(struct sudoers_context *ctx, const struct sudo_nss_list *snl,
+ struct passwd *pw, int verbose)
+{
+ return true;
+}
+
+/* STUB */
+int
+find_path(const char *infile, char **outfile, struct stat *sbp,
+ const char *path, bool ignore_dot, char * const *allowlist)
+{
+ switch (pass) {
+ case PASS_CHECK_NOT_FOUND:
+ return NOT_FOUND;
+ case PASS_CHECK_NOT_FOUND_DOT:
+ return NOT_FOUND_DOT;
+ default:
+ if (infile[0] == '/') {
+ *outfile = strdup(infile);
+ } else {
+ if (asprintf(outfile, "/usr/bin/%s", infile) == -1)
+ *outfile = NULL;
+ }
+ if (*outfile == NULL)
+ return NOT_FOUND_ERROR;
+ return FOUND;
+ }
+}
+
+/* STUB */
+int
+resolve_cmnd(struct sudoers_context *ctx, const char *infile, char **outfile,
+ const char *path)
+{
+ return find_path(infile, outfile, NULL, path, false, NULL);
+}
+
+/* STUB */
+bool
+expand_iolog_path(const char *inpath, char *path, size_t pathlen,
+ const struct iolog_path_escape *escapes, void *closure)
+{
+ return strlcpy(path, inpath, pathlen) < pathlen;
+}
+
+/* STUB */
+bool
+iolog_nextid(const char *iolog_dir, char sessid[7])
+{
+ strlcpy(sessid, "000001", 7);
+ return true;
+}
+
+/* STUB */
+bool
+cb_maxseq(struct sudoers_context *ctx, const char *file,
+ int line, int column, const union sudo_defs_val *sd_un, int op)
+{
+ return true;
+}
+
+/* STUB */
+bool
+cb_iolog_user(struct sudoers_context *ctx, const char *file,
+ int line, int column, const union sudo_defs_val *sd_un, int op)
+{
+ return true;
+}
+
+/* STUB */
+bool
+cb_iolog_group(struct sudoers_context *ctx, const char *file,
+ int line, int column, const union sudo_defs_val *sd_un, int op)
+{
+ return true;
+}
+
+/* STUB */
+bool
+cb_iolog_mode(struct sudoers_context *ctx, const char *file,
+ int line, int column, const union sudo_defs_val *sd_un, int op)
+{
+ return true;
+}
+
+/* STUB */
+bool
+cb_group_plugin(struct sudoers_context *ctx, const char *file,
+ int line, int column, const union sudo_defs_val *sd_un, int op)
+{
+ return true;
+}
+
+/* STUB */
+bool
+cb_timestampowner(struct sudoers_context *ctx, const char *file,
+ int line, int column, const union sudo_defs_val *sd_un, int op)
+{
+ return true;
+}
+
+/* STUB */
+void
+bsdauth_set_style(const char *style)
+{
+ return;
+}
diff --git a/plugins/sudoers/regress/fuzz/fuzz_policy.dict b/plugins/sudoers/regress/fuzz/fuzz_policy.dict
new file mode 100644
index 0000000..d00aeae
--- /dev/null
+++ b/plugins/sudoers/regress/fuzz/fuzz_policy.dict
@@ -0,0 +1,56 @@
+# Policy plugin keywords (all are keyword = value)
+
+# sudoers plugin options from sudo.conf
+"error_recovery"
+"sudoers_file"
+"sudoers_uid"
+"sudoers_gid"
+"sudoers_mode"
+"ldap_conf"
+"ldap_secret"
+
+# command line settings from front-end
+"closefrom"
+"cmnd_chroot"
+"cmnd_cwd"
+"runas_user"
+"runas_group"
+"prompt"
+"set_home"
+"preserve_environment"
+"run_shell"
+"login_shell"
+"implied_shell"
+"preserve_groups"
+"ignore_ticket"
+"update_ticket"
+"noninteractive"
+"sudoedit"
+"login_class"
+"intercept_setid"
+"intercept_ptrace"
+"selinux_role"
+"selinux_type"
+"apparmor_profile"
+"bsdauth_type"
+"network_addrs"
+"max_groups"
+"remote_host"
+"timeout"
+"askpass"
+"plugin_dir"
+"progname"
+
+# user information from front-end
+"user"
+"uid"
+"gid"
+"groups"
+"cwd"
+"tty"
+"host"
+"lines"
+"cols"
+"sid"
+"tcpgid"
+"umask"
diff --git a/plugins/sudoers/regress/fuzz/fuzz_stubs.c b/plugins/sudoers/regress/fuzz/fuzz_stubs.c
new file mode 100644
index 0000000..ce47bf5
--- /dev/null
+++ b/plugins/sudoers/regress/fuzz/fuzz_stubs.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <sys/socket.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <unistd.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#ifdef NEED_RESOLV_H
+# include <arpa/nameser.h>
+# include <resolv.h>
+#endif /* NEED_RESOLV_H */
+#include <netdb.h>
+
+#include <sudoers.h>
+#include <timestamp.h>
+#include <interfaces.h>
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+struct interface_list *
+get_interfaces(void)
+{
+ static struct interface_list empty = SLIST_HEAD_INITIALIZER(interfaces);
+ return &empty;
+}
+
+void
+init_eventlog_config(void)
+{
+ return;
+}
+
+bool
+pivot_root(const char *new_root, struct sudoers_pivot *state)
+{
+ return true;
+}
+
+bool
+unpivot_root(struct sudoers_pivot *state)
+{
+ return true;
+}
+
+int
+group_plugin_query(const char *user, const char *group, const struct passwd *pw)
+{
+ return false;
+}
+
+bool
+set_perms(const struct sudoers_context *ctx, int perm)
+{
+ return true;
+}
+
+bool
+restore_perms(void)
+{
+ return true;
+}
+
+bool
+rewind_perms(void)
+{
+ return true;
+}
+
+bool
+sudo_nss_can_continue(const struct sudo_nss *nss, int match)
+{
+ return true;
+}
diff --git a/plugins/sudoers/regress/fuzz/fuzz_sudoers.c b/plugins/sudoers/regress/fuzz/fuzz_sudoers.c
new file mode 100644
index 0000000..e77634a
--- /dev/null
+++ b/plugins/sudoers/regress/fuzz/fuzz_sudoers.c
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2021-2023 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <sys/socket.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <unistd.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#ifdef NEED_RESOLV_H
+# include <arpa/nameser.h>
+# include <resolv.h>
+#endif /* NEED_RESOLV_H */
+#include <netdb.h>
+
+#include <sudoers.h>
+#include <interfaces.h>
+
+static int fuzz_conversation(int num_msgs, const struct sudo_conv_message msgs[], struct sudo_conv_reply replies[], struct sudo_conv_callback *callback);
+static int fuzz_printf(int msg_type, const char * restrict fmt, ...);
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+/* For set_cmnd_path() */
+static const char *orig_cmnd;
+
+/* Required to link with parser. */
+sudo_conv_t sudo_conv = fuzz_conversation;
+sudo_printf_t sudo_printf = fuzz_printf;
+
+FILE *
+open_sudoers(const char *file, char **outfile, bool doedit, bool *keepopen)
+{
+ /*
+ * If we allow the fuzzer to choose include paths it will
+ * include random files in the file system.
+ * This leads to bug reports that cannot be reproduced.
+ */
+ return NULL;
+}
+
+static int
+fuzz_printf(int msg_type, const char * restrict fmt, ...)
+{
+ return 0;
+}
+
+static int
+fuzz_conversation(int num_msgs, const struct sudo_conv_message msgs[],
+ struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
+{
+ int n;
+
+ for (n = 0; n < num_msgs; n++) {
+ const struct sudo_conv_message *msg = &msgs[n];
+
+ switch (msg->msg_type & 0xff) {
+ case SUDO_CONV_PROMPT_ECHO_ON:
+ case SUDO_CONV_PROMPT_MASK:
+ case SUDO_CONV_PROMPT_ECHO_OFF:
+ /* input not supported */
+ return -1;
+ case SUDO_CONV_ERROR_MSG:
+ case SUDO_CONV_INFO_MSG:
+ /* no output for fuzzers */
+ break;
+ default:
+ return -1;
+ }
+ }
+ return 0;
+}
+
+bool
+init_envtables(void)
+{
+ return true;
+}
+
+int
+set_cmnd_path(struct sudoers_context *ctx, const char *runchroot)
+{
+ /* Reallocate ctx->user.cmnd to catch bugs in command_matches(). */
+ char *new_cmnd = strdup(orig_cmnd);
+ if (new_cmnd == NULL)
+ return NOT_FOUND_ERROR;
+ free(ctx->user.cmnd);
+ ctx->user.cmnd = new_cmnd;
+ return FOUND;
+}
+
+/* STUB */
+bool
+mail_parse_errors(const struct sudoers_context *ctx)
+{
+ return true;
+}
+
+/* STUB */
+bool
+log_warningx(const struct sudoers_context *ctx, unsigned int flags,
+ const char * restrict fmt, ...)
+{
+ return true;
+}
+
+static int
+sudo_fuzz_query(struct sudoers_context *ctx, const struct sudo_nss *nss,
+ struct passwd *pw)
+{
+ return 0;
+}
+
+static int
+cb_unused(struct sudoers_parse_tree *parse_tree, struct alias *a, void *v)
+{
+ return 0;
+}
+
+bool
+cb_log_input(struct sudoers_context *ctx, const char *file,
+ int line, int column, const union sudo_defs_val *sd_un, int op)
+{
+ return 0;
+}
+
+bool
+cb_log_output(struct sudoers_context *ctx, const char *file,
+ int line, int column, const union sudo_defs_val *sd_un, int op)
+{
+ return 0;
+}
+
+static FILE *
+open_data(const uint8_t *data, size_t size)
+{
+#ifdef HAVE_FMEMOPEN
+ /* Operate in-memory. */
+ return fmemopen((void *)data, size, "r");
+#else
+ char tempfile[] = "/tmp/sudoers.XXXXXX";
+ size_t nwritten;
+ int fd;
+
+ /* Use (unlinked) temporary file. */
+ fd = mkstemp(tempfile);
+ if (fd == -1)
+ return NULL;
+ unlink(tempfile);
+ nwritten = write(fd, data, size);
+ if (nwritten != size) {
+ close(fd);
+ return NULL;
+ }
+ lseek(fd, 0, SEEK_SET);
+ return fdopen(fd, "r");
+#endif
+}
+
+static struct user_data {
+ const char *user;
+ const char *runuser;
+ const char *rungroup;
+} user_data[] = {
+ { "root", NULL, NULL },
+ { "millert", "operator", NULL },
+ { "millert", NULL, "wheel" },
+ { "operator", NULL, NULL },
+ { NULL }
+};
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ struct sudoers_context ctx = { { NULL } };
+ struct user_data *ud;
+ struct sudo_nss sudo_nss_fuzz;
+ struct sudo_nss_list snl = TAILQ_HEAD_INITIALIZER(snl);
+ struct sudoers_parse_tree parse_tree;
+ struct interface_list *interfaces;
+ struct passwd *pw;
+ struct group *gr;
+ const char *gids[10];
+ time_t now;
+ FILE *fp;
+
+ /* Don't waste time fuzzing tiny inputs. */
+ if (size < 5)
+ return 0;
+
+ fp = open_data(data, size);
+ if (fp == NULL)
+ return 0;
+
+ initprogname("fuzz_sudoers");
+ sudoers_debug_register(getprogname(), NULL);
+ if (getenv("SUDO_FUZZ_VERBOSE") == NULL)
+ sudo_warn_set_conversation(fuzz_conversation);
+
+ /* Sudoers locale setup. */
+ sudoers_initlocale(setlocale(LC_ALL, ""), "C");
+ sudo_warn_set_locale_func(sudoers_warn_setlocale);
+ bindtextdomain("sudoers", LOCALEDIR);
+ textdomain("sudoers");
+
+ /* Use the sudoers locale for everything. */
+ sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, NULL);
+
+ /* Prime the group cache */
+ gr = sudo_mkgrent("wheel", 0, "millert", "root", (char *)NULL);
+ if (gr == NULL)
+ goto done;
+ sudo_gr_delref(gr);
+
+ gr = sudo_mkgrent("operator", 5, "operator", "root", "millert", (char *)NULL);
+ if (gr == NULL)
+ goto done;
+ sudo_gr_delref(gr);
+
+ gr = sudo_mkgrent("staff", 20, "root", "millert", (char *)NULL);
+ if (gr == NULL)
+ goto done;
+ sudo_gr_delref(gr);
+
+ gr = sudo_mkgrent("sudo", 100, "root", "millert", (char *)NULL);
+ if (gr == NULL)
+ goto done;
+ sudo_gr_delref(gr);
+
+ /* Prime the passwd cache */
+ pw = sudo_mkpwent("root", 0, 0, "/", "/bin/sh");
+ if (pw == NULL)
+ goto done;
+ gids[0] = "0";
+ gids[1] = "20";
+ gids[2] = "5";
+ gids[3] = NULL;
+ if (sudo_set_gidlist(pw, -1, NULL, (char **)gids, ENTRY_TYPE_FRONTEND) == -1)
+ goto done;
+ sudo_pw_delref(pw);
+
+ pw = sudo_mkpwent("operator", 2, 5, "/operator", "/sbin/nologin");
+ if (pw == NULL)
+ goto done;
+ gids[0] = "5";
+ gids[1] = NULL;
+ if (sudo_set_gidlist(pw, -1, NULL, (char **)gids, ENTRY_TYPE_FRONTEND) == -1)
+ goto done;
+ sudo_pw_delref(pw);
+
+ pw = sudo_mkpwent("millert", 8036, 20, "/home/millert", "/bin/tcsh");
+ if (pw == NULL)
+ goto done;
+ gids[0] = "0";
+ gids[1] = "20";
+ gids[2] = "5";
+ gids[3] = "100";
+ gids[4] = NULL;
+ if (sudo_set_gidlist(pw, -1, NULL, (char **)gids, ENTRY_TYPE_FRONTEND) == -1)
+ goto done;
+ sudo_pw_delref(pw);
+
+ /* The minimum needed to perform matching. */
+ ctx.user.host = ctx.user.shost = strdup("localhost");
+ ctx.runas.host = ctx.runas.shost = strdup("localhost");
+ orig_cmnd = (char *)"/usr/bin/id";
+ ctx.user.cmnd = strdup(orig_cmnd);
+ ctx.user.cmnd_args = strdup("-u");
+ if (ctx.user.host == NULL || ctx.runas.host == NULL ||
+ ctx.user.cmnd == NULL || ctx.user.cmnd_args == NULL)
+ goto done;
+ ctx.user.cmnd_base = sudo_basename(ctx.user.cmnd);
+ time(&now);
+
+ /* Add a fake network interfaces. */
+ interfaces = get_interfaces();
+ if (SLIST_EMPTY(interfaces)) {
+ static struct interface interface;
+
+ interface.family = AF_INET;
+ inet_pton(AF_INET, "128.138.243.151", &interface.addr.ip4);
+ inet_pton(AF_INET, "255.255.255.0", &interface.netmask.ip4);
+ SLIST_INSERT_HEAD(interfaces, &interface, entries);
+ }
+
+ /* Only one sudoers source, the sudoers file itself. */
+ init_parse_tree(&parse_tree, NULL, NULL, &ctx, NULL);
+ memset(&sudo_nss_fuzz, 0, sizeof(sudo_nss_fuzz));
+ sudo_nss_fuzz.parse_tree = &parse_tree;
+ sudo_nss_fuzz.query = sudo_fuzz_query;
+ TAILQ_INSERT_TAIL(&snl, &sudo_nss_fuzz, entries);
+
+ /* Initialize defaults and parse sudoers. */
+ init_defaults();
+ init_parser(&ctx, "sudoers");
+ sudoersrestart(fp);
+ sudoersparse();
+ reparent_parse_tree(&parse_tree);
+
+ if (!parse_error) {
+ /* Match user/host/command against parsed policy. */
+ for (ud = user_data; ud->user != NULL; ud++) {
+ int cmnd_status;
+
+ /* Invoking user. */
+ free(ctx.user.name);
+ ctx.user.name = strdup(ud->user);
+ if (ctx.user.name == NULL)
+ goto done;
+ if (ctx.user.pw != NULL)
+ sudo_pw_delref(ctx.user.pw);
+ ctx.user.pw = sudo_getpwnam(ctx.user.name);
+ if (ctx.user.pw == NULL) {
+ sudo_warnx_nodebug("unknown user %s", ctx.user.name);
+ continue;
+ }
+
+ /* Run user. */
+ if (ctx.runas.pw != NULL)
+ sudo_pw_delref(ctx.runas.pw);
+ if (ud->runuser != NULL) {
+ ctx.runas.user = (char *)ud->runuser;
+ SET(ctx.settings.flags, RUNAS_USER_SPECIFIED);
+ ctx.runas.pw = sudo_getpwnam(ctx.runas.user);
+ } else {
+ ctx.runas.user = NULL;
+ CLR(ctx.settings.flags, RUNAS_USER_SPECIFIED);
+ ctx.runas.pw = sudo_getpwnam("root");
+ }
+ if (ctx.runas.pw == NULL) {
+ sudo_warnx_nodebug("unknown run user %s", ctx.runas.user);
+ continue;
+ }
+
+ /* Run group. */
+ if (ctx.runas.gr != NULL)
+ sudo_gr_delref(ctx.runas.gr);
+ if (ud->rungroup != NULL) {
+ ctx.runas.group = (char *)ud->rungroup;
+ SET(ctx.settings.flags, RUNAS_GROUP_SPECIFIED);
+ ctx.runas.gr = sudo_getgrnam(ctx.runas.group);
+ if (ctx.runas.gr == NULL) {
+ sudo_warnx_nodebug("unknown run group %s",
+ ctx.runas.group);
+ continue;
+ }
+ } else {
+ ctx.runas.group = NULL;
+ CLR(ctx.settings.flags, RUNAS_GROUP_SPECIFIED);
+ ctx.runas.gr = NULL;
+ }
+
+ update_defaults(&ctx, &parse_tree, NULL, SETDEF_ALL, false);
+
+ sudoers_lookup(&snl, &ctx, now, NULL, NULL, &cmnd_status,
+ false);
+
+ /* Match again as a pseudo-command (list, validate, etc). */
+ sudoers_lookup(&snl, &ctx, now, NULL, NULL, &cmnd_status,
+ true);
+
+ /* Display privileges. */
+ display_privs(&ctx, &snl, ctx.user.pw, false);
+ display_privs(&ctx, &snl, ctx.user.pw, true);
+ }
+
+ /* Expand tildes in runcwd and runchroot. */
+ if (ctx.runas.pw != NULL) {
+ if (def_runcwd != NULL && strcmp(def_runcwd, "*") != 0) {
+ expand_tilde(&def_runcwd, ctx.runas.pw->pw_name);
+ }
+ if (def_runchroot != NULL && strcmp(def_runchroot, "*") != 0) {
+ expand_tilde(&def_runchroot, ctx.runas.pw->pw_name);
+ }
+ }
+
+ /* Check Defaults and aliases. */
+ check_defaults(&parse_tree, false);
+ check_aliases(&parse_tree, true, false, cb_unused);
+ }
+
+done:
+ /* Cleanup. */
+ fclose(fp);
+ free_parse_tree(&parse_tree);
+ reset_parser();
+ sudoers_ctx_free(&ctx);
+ sudo_freepwcache();
+ sudo_freegrcache();
+ sudoers_setlocale(SUDOERS_LOCALE_USER, NULL);
+ sudoers_debug_deregister();
+ fflush(stdout);
+
+ return 0;
+}
diff --git a/plugins/sudoers/regress/fuzz/fuzz_sudoers.dict b/plugins/sudoers/regress/fuzz/fuzz_sudoers.dict
new file mode 100644
index 0000000..101e002
--- /dev/null
+++ b/plugins/sudoers/regress/fuzz/fuzz_sudoers.dict
@@ -0,0 +1,221 @@
+# Sudoers policy keywords (all are keyword = value)
+
+# Users and groups
+"root"
+"wheel"
+"staff"
+"sudo"
+
+# Aliases
+"Cmnd_Alias"
+"Cmd_Alias"
+"Host_Alias"
+"Runas_Alias"
+"User_Alias"
+
+# Special keywords
+"ALL"
+"(ALL:ALL)"
+"sudoedit"
+
+# Date_Spec
+"20170214083000Z"
+"2017021408Z"
+"20160315220000-0500"
+"20151201235900"
+
+# Timeout_Spec
+"7d8h30m10s"
+"14d"
+"8h30m"
+"600s"
+"3600"
+
+# Command digests
+"sha224:"
+"sha256:"
+"sha384:"
+"sha512:"
+
+# Command tags
+"NOPASSWD"
+"PASSWD"
+"NOEXEC"
+"EXEC"
+"SETENV"
+"NOSETENV"
+"LOG_INPUT"
+"NOLOG_INPUT"
+"LOG_OUTPUT"
+"NOLOG_OUTPUT"
+"FOLLOWLNK"
+"NOFOLLOWLNK"
+"MAIL"
+"NOMAIL"
+
+# Command options
+"CHROOT"
+"CWD"
+"CMND_TIMEOUT"
+"NOTBEFORE"
+"NOTAFTER"
+"ROLE"
+"TYPE"
+"APPARMOR_PROFILE"
+"PRIVS"
+"LIMITPRIVS"
+
+# Defaults settings
+"Defaults"
+"syslog"
+"syslog_goodpri"
+"syslog_badpri"
+"long_otp_prompt"
+"ignore_dot"
+"mail_always"
+"mail_badpass"
+"mail_no_user"
+"mail_no_host"
+"mail_no_perms"
+"mail_all_cmnds"
+"tty_tickets"
+"lecture"
+"lecture_file"
+"authenticate"
+"root_sudo"
+"log_host"
+"log_year"
+"shell_noargs"
+"set_home"
+"always_set_home"
+"path_info"
+"fqdn"
+"insults"
+"requiretty"
+"env_editor"
+"rootpw"
+"runaspw"
+"targetpw"
+"use_loginclass"
+"set_logname"
+"stay_setuid"
+"preserve_groups"
+"loglinelen"
+"timestamp_timeout"
+"passwd_timeout"
+"passwd_tries"
+"umask"
+"logfile"
+"mailerpath"
+"mailerflags"
+"mailto"
+"mailfrom"
+"mailsub"
+"badpass_message"
+"lecture_status_dir"
+"timestampdir"
+"timestampowner"
+"exempt_group"
+"passprompt"
+"passprompt_override"
+"runas_default"
+"secure_path"
+"editor"
+"listpw"
+"verifypw"
+"noexec"
+"ignore_local_sudoers"
+"closefrom"
+"closefrom_override"
+"setenv"
+"env_reset"
+"env_check"
+"env_delete"
+"env_keep"
+"role"
+"type"
+"apparmor_profile"
+"env_file"
+"restricted_env_file"
+"sudoers_locale"
+"visiblepw"
+"pwfeedback"
+"fast_glob"
+"umask_override"
+"log_input"
+"log_output"
+"compress_io"
+"use_pty"
+"group_plugin"
+"iolog_dir"
+"iolog_file"
+"set_utmp"
+"utmp_runas"
+"privs"
+"limitprivs"
+"exec_background"
+"pam_service"
+"pam_login_service"
+"pam_setcred"
+"pam_session"
+"pam_acct_mgmt"
+"maxseq"
+"use_netgroups"
+"sudoedit_checkdir"
+"sudoedit_follow"
+"always_query_group_plugin"
+"netgroup_tuple"
+"ignore_audit_errors"
+"ignore_iolog_errors"
+"ignore_logfile_errors"
+"match_group_by_gid"
+"syslog_maxlen"
+"iolog_user"
+"iolog_group"
+"iolog_mode"
+"fdexec"
+"ignore_unknown_defaults"
+"command_timeout"
+"user_command_timeouts"
+"iolog_flush"
+"syslog_pid"
+"timestamp_type"
+"authfail_message"
+"case_insensitive_user"
+"case_insensitive_group"
+"log_allowed"
+"log_denied"
+"log_servers"
+"log_server_timeout"
+"log_server_keepalive"
+"log_server_cabundle"
+"log_server_peer_cert"
+"log_server_peer_key"
+"log_server_verify"
+"runas_allow_unknown_id"
+"runas_check_shell"
+"pam_ruser"
+"pam_rhost"
+"runcwd"
+"runchroot"
+"log_format"
+"selinux"
+"admin_flag"
+"intercept"
+"log_subcmds"
+"log_exit_status"
+"intercept_authenticate"
+"intercept_allow_setid"
+"rlimit_as"
+"rlimit_core"
+"rlimit_cpu"
+"rlimit_data"
+"rlimit_fsize"
+"rlimit_locks"
+"rlimit_memlock"
+"rlimit_nofile"
+"rlimit_nproc"
+"rlimit_rss"
+"rlimit_stack"
+"log_passwords"
+"passprompt_regex"
diff --git a/plugins/sudoers/regress/fuzz/fuzz_sudoers.out.ok b/plugins/sudoers/regress/fuzz/fuzz_sudoers.out.ok
new file mode 100644
index 0000000..11f2ca8
--- /dev/null
+++ b/plugins/sudoers/regress/fuzz/fuzz_sudoers.out.ok
@@ -0,0 +1,577 @@
+Running: examples/sudoers
+Matching Defaults entries for root on localhost:
+ syslog=auth, runcwd=~
+
+Runas and Command-specific defaults for root:
+ Defaults>root !set_logname Defaults!/usr/bin/more, /usr/bin/pg, /usr/bin/less noexec
+
+
+User root may run the following commands on localhost:
+ (ALL) ALL
+ (ALL) ALL
+
+Matching Defaults entries for root on localhost:
+ syslog=auth, runcwd=~
+
+Runas and Command-specific defaults for root:
+ Defaults>root !set_logname Defaults!/usr/bin/more, /usr/bin/pg, /usr/bin/less noexec
+
+
+User root may run the following commands on localhost:
+
+Sudoers entry:
+ RunAsUsers: ALL
+ Commands:
+ ALL
+
+Sudoers entry:
+ RunAsUsers: ALL
+ Commands:
+ ALL
+
+Matching Defaults entries for millert on localhost:
+ syslog=auth, runcwd=~, !lecture, runchroot=*, !authenticate
+
+Runas and Command-specific defaults for millert:
+ Defaults>root !set_logname Defaults!/usr/bin/more, /usr/bin/pg, /usr/bin/less noexec
+
+
+User millert may run the following commands on localhost:
+ (ALL) ALL
+ (root) NOPASSWD: ALL
+
+Matching Defaults entries for millert on localhost:
+ syslog=auth, runcwd=~, !lecture, runchroot=*, !authenticate
+
+Runas and Command-specific defaults for millert:
+ Defaults>root !set_logname Defaults!/usr/bin/more, /usr/bin/pg, /usr/bin/less noexec
+
+
+User millert may run the following commands on localhost:
+
+Sudoers entry:
+ RunAsUsers: ALL
+ Commands:
+ ALL
+
+Sudoers entry:
+ RunAsUsers: root
+ Options: !authenticate
+ Commands:
+ ALL
+
+Matching Defaults entries for millert on localhost:
+ syslog=auth, runcwd=~, !lecture, runchroot=*, !authenticate
+
+Runas and Command-specific defaults for millert:
+ Defaults>root !set_logname Defaults!/usr/bin/more, /usr/bin/pg, /usr/bin/less noexec
+
+
+User millert may run the following commands on localhost:
+ (ALL) ALL
+ (root) NOPASSWD: ALL
+
+Matching Defaults entries for millert on localhost:
+ syslog=auth, runcwd=~, !lecture, runchroot=*, !authenticate
+
+Runas and Command-specific defaults for millert:
+ Defaults>root !set_logname Defaults!/usr/bin/more, /usr/bin/pg, /usr/bin/less noexec
+
+
+User millert may run the following commands on localhost:
+
+Sudoers entry:
+ RunAsUsers: ALL
+ Commands:
+ ALL
+
+Sudoers entry:
+ RunAsUsers: root
+ Options: !authenticate
+ Commands:
+ ALL
+
+Matching Defaults entries for operator on localhost:
+ syslog=auth, runcwd=~
+
+Runas and Command-specific defaults for operator:
+ Defaults>root !set_logname Defaults!/usr/bin/more, /usr/bin/pg, /usr/bin/less noexec
+
+
+User operator may run the following commands on localhost:
+ (root) /usr/sbin/dump, /usr/sbin/rdump, /usr/sbin/restore, /usr/sbin/rrestore, /usr/bin/mt, sha224:0GomF8mNN3wlDt1HD9XldjJ3SNgpFdbjO1+NsQ== /home/operator/bin/start_backups, /usr/bin/kill, /usr/bin/top, /usr/sbin/shutdown, /usr/sbin/halt, /usr/sbin/reboot, /usr/sbin/lpc, /usr/bin/lprm, sudoedit /etc/printcap, /usr/oper/bin/
+
+Matching Defaults entries for operator on localhost:
+ syslog=auth, runcwd=~
+
+Runas and Command-specific defaults for operator:
+ Defaults>root !set_logname Defaults!/usr/bin/more, /usr/bin/pg, /usr/bin/less noexec
+
+
+User operator may run the following commands on localhost:
+
+Sudoers entry:
+ RunAsUsers: root
+ Commands:
+ /usr/sbin/dump
+ /usr/sbin/rdump
+ /usr/sbin/restore
+ /usr/sbin/rrestore
+ /usr/bin/mt
+ sha224:0GomF8mNN3wlDt1HD9XldjJ3SNgpFdbjO1+NsQ== /home/operator/bin/start_backups
+ /usr/bin/kill
+ /usr/bin/top
+ /usr/sbin/shutdown
+ /usr/sbin/halt
+ /usr/sbin/reboot
+ /usr/sbin/lpc
+ /usr/bin/lprm
+ sudoedit /etc/printcap
+ /usr/oper/bin/
+
+Executed examples/sudoers
+Running: regress/sudoers/test1.in
+User root is not allowed to run sudo on localhost.
+
+User root is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+Executed regress/sudoers/test1.in
+Running: regress/sudoers/test2.in
+User root is not allowed to run sudo on localhost.
+
+User root is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+Executed regress/sudoers/test2.in
+Running: regress/sudoers/test3.in
+User root is not allowed to run sudo on localhost.
+
+User root is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+Executed regress/sudoers/test3.in
+Running: regress/sudoers/test4.in
+sudoers:7:1: invalid line continuation
+User_Alias BAR = bar
+^~~~~~~~~~
+Executed regress/sudoers/test4.in
+Running: regress/sudoers/test5.in
+sudoers:2:19: empty string
+User_Alias FOO = ""
+ ^
+sudoers:3:2: empty string
+"" ALL = ALL
+ ^
+Executed regress/sudoers/test5.in
+Running: regress/sudoers/test6.in
+Runas and Command-specific defaults for root:
+ Defaults>#123 set_home
+ Defaults>#123 set_home
+
+
+User root may run the following commands on localhost:
+ (root) ALL
+ (#0 : #0) ALL
+ (root) ALL
+ (#0 : #0) ALL
+ (root) ALL
+ (root) ALL
+
+Runas and Command-specific defaults for root:
+ Defaults>#123 set_home
+ Defaults>#123 set_home
+
+
+User root may run the following commands on localhost:
+
+Sudoers entry:
+ RunAsUsers: root
+ Commands:
+ ALL
+
+Sudoers entry:
+ RunAsUsers: #0
+ RunAsGroups: #0
+ Commands:
+ ALL
+
+Sudoers entry:
+ RunAsUsers: root
+ Commands:
+ ALL
+
+Sudoers entry:
+ RunAsUsers: #0
+ RunAsGroups: #0
+ Commands:
+ ALL
+
+Sudoers entry:
+ RunAsUsers: root
+ Commands:
+ ALL
+
+Sudoers entry:
+ RunAsUsers: root
+ Commands:
+ ALL
+
+Runas and Command-specific defaults for millert:
+ Defaults>#123 set_home
+ Defaults>#123 set_home
+
+
+User millert may run the following commands on localhost:
+ (root) ALL
+ (root) ALL
+
+Runas and Command-specific defaults for millert:
+ Defaults>#123 set_home
+ Defaults>#123 set_home
+
+
+User millert may run the following commands on localhost:
+
+Sudoers entry:
+ RunAsUsers: root
+ Commands:
+ ALL
+
+Sudoers entry:
+ RunAsUsers: root
+ Commands:
+ ALL
+
+Runas and Command-specific defaults for millert:
+ Defaults>#123 set_home
+ Defaults>#123 set_home
+
+
+User millert may run the following commands on localhost:
+ (root) ALL
+ (root) ALL
+
+Runas and Command-specific defaults for millert:
+ Defaults>#123 set_home
+ Defaults>#123 set_home
+
+
+User millert may run the following commands on localhost:
+
+Sudoers entry:
+ RunAsUsers: root
+ Commands:
+ ALL
+
+Sudoers entry:
+ RunAsUsers: root
+ Commands:
+ ALL
+
+User operator is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+Executed regress/sudoers/test6.in
+Running: regress/sudoers/test7.in
+sudoers:2:21: empty group
+User_Alias FOO1 = "%"
+ ^
+sudoers:3:22: empty group
+User_Alias FOO2 = "%:"
+ ^
+sudoers:4:21: empty netgroup
+User_Alias FOO3 = "+"
+ ^
+sudoers:5:19: empty group
+User_Alias FOO4 = %
+ ^
+sudoers:6:19: empty group
+User_Alias FOO5 = %:
+ ^~
+sudoers:7:19: empty netgroup
+User_Alias FOO6 = +
+ ^
+Executed regress/sudoers/test7.in
+Running: regress/sudoers/test8.in
+sudoers:8:20: unexpected line break in string
+User_Alias UA4 = "x
+ ^
+Executed regress/sudoers/test8.in
+Running: regress/sudoers/test9.in
+Executed regress/sudoers/test9.in
+Running: regress/sudoers/test10.in
+Executed regress/sudoers/test10.in
+Running: regress/sudoers/test11.in
+sudoers:1:6: syntax error
+bogus
+ ^
+Executed regress/sudoers/test11.in
+Running: regress/sudoers/test12.in
+sudoers:1:17: syntax error
+user ALL = (ALL)
+ ^
+Executed regress/sudoers/test12.in
+Running: regress/sudoers/test13.in
+sudoers:1:17: syntax error
+user ALL = (ALL)
+ ^
+Executed regress/sudoers/test13.in
+Running: regress/sudoers/test14.in
+User root is not allowed to run sudo on localhost.
+
+User root is not allowed to run sudo on localhost.
+
+User millert may run the following commands on localhost:
+ (root) sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1, sha224:d7910e1967342b4605cb73a550944044c631cd3514001900966962ac /bin/ls, sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM=, sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= /bin/sh, sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw /bin/kill
+
+User millert may run the following commands on localhost:
+
+Sudoers entry:
+ RunAsUsers: root
+ Commands:
+ sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1, sha224:d7910e1967342b4605cb73a550944044c631cd3514001900966962ac /bin/ls
+ sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM=, sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= /bin/sh
+ sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw /bin/kill
+
+User millert may run the following commands on localhost:
+ (root) sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1, sha224:d7910e1967342b4605cb73a550944044c631cd3514001900966962ac /bin/ls, sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM=, sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= /bin/sh, sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw /bin/kill
+
+User millert may run the following commands on localhost:
+
+Sudoers entry:
+ RunAsUsers: root
+ Commands:
+ sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1, sha224:d7910e1967342b4605cb73a550944044c631cd3514001900966962ac /bin/ls
+ sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM=, sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= /bin/sh
+ sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw /bin/kill
+
+User operator may run the following commands on localhost:
+ (root) sha384:knMlCLkJ71K6uRrKo5C1CAvZ5kq+mRpjKDD/RofGosFjiGcYhiYYZORVyiRHgBnu, sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= ALL
+
+User operator may run the following commands on localhost:
+
+Sudoers entry:
+ RunAsUsers: root
+ Commands:
+ sha384:knMlCLkJ71K6uRrKo5C1CAvZ5kq+mRpjKDD/RofGosFjiGcYhiYYZORVyiRHgBnu, sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= ALL
+
+Executed regress/sudoers/test14.in
+Running: regress/sudoers/test15.in
+User root is not allowed to run sudo on localhost.
+
+User root is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+Executed regress/sudoers/test15.in
+Running: regress/sudoers/test16.in
+User root is not allowed to run sudo on localhost.
+
+User root is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+Executed regress/sudoers/test16.in
+Running: regress/sudoers/test17.in
+User root is not allowed to run sudo on localhost.
+
+User root is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+Executed regress/sudoers/test17.in
+Running: regress/sudoers/test18.in
+sudoers:4:21: invalid timeout value
+user0 ALL = TIMEOUT=7dd4h10m30s /usr/bin/id, /usr/bin/who, TIMEOUT=0 /bin/ls
+ ^~~~~~~~~~~
+sudoers:5:21: invalid timeout value
+user1 ALL = TIMEOUT=7d4h10mm30s /usr/bin/id
+ ^~~~~~~~~~~
+sudoers:6:21: invalid timeout value
+user2 ALL = TIMEOUT=4hg10m30s /usr/bin/id
+ ^~~~~~~~~
+sudoers:7:21: invalid timeout value
+user3 ALL = TIMEOUT=10m30ss /usr/bin/id
+ ^~~~~~~
+sudoers:8:21: invalid timeout value
+user4 ALL = TIMEOUT=14g /usr/bin/id
+ ^~~
+Executed regress/sudoers/test18.in
+Running: regress/sudoers/test19.in
+User root is not allowed to run sudo on localhost.
+
+User root is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+Executed regress/sudoers/test19.in
+Running: regress/sudoers/test20.in
+User root is not allowed to run sudo on localhost.
+
+User root is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+Executed regress/sudoers/test20.in
+Running: regress/sudoers/test21.in
+User root is not allowed to run sudo on localhost.
+
+User root is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+Executed regress/sudoers/test21.in
+Running: regress/sudoers/test22.in
+User root is not allowed to run sudo on localhost.
+
+User root is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+Executed regress/sudoers/test22.in
+Running: regress/sudoers/test23.in
+User root is not allowed to run sudo on localhost.
+
+User root is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+Executed regress/sudoers/test23.in
+Running: regress/sudoers/test24.in
+User root is not allowed to run sudo on localhost.
+
+User root is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User millert is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+User operator is not allowed to run sudo on localhost.
+
+Executed regress/sudoers/test24.in
+Running: regress/sudoers/test25.in
+sudoers:4:28: syntax error
+foo ALL = CWD=~ron /bin/ls \
+ ^~
+Executed regress/sudoers/test25.in
diff --git a/plugins/sudoers/regress/fuzz/fuzz_sudoers_ldif.c b/plugins/sudoers/regress/fuzz/fuzz_sudoers_ldif.c
new file mode 100644
index 0000000..9f9b394
--- /dev/null
+++ b/plugins/sudoers/regress/fuzz/fuzz_sudoers_ldif.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+
+#include <sudoers.h>
+
+static int fuzz_printf(int msg_type, const char * restrict fmt, ...);
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+/* Required to link with parser. */
+sudo_printf_t sudo_printf = fuzz_printf;
+
+FILE *
+open_sudoers(const char *file, char **outfile, bool doedit, bool *keepopen)
+{
+ /*
+ * If we allow the fuzzer to choose include paths it will
+ * include random files in the file system.
+ * This leads to bug reports that cannot be reproduced.
+ */
+ return NULL;
+}
+
+static int
+fuzz_printf(int msg_type, const char * restrict fmt, ...)
+{
+ return 0;
+}
+
+bool
+init_envtables(void)
+{
+ return true;
+}
+
+int
+set_cmnd_path(struct sudoers_context *ctx, const char *runchroot)
+{
+ /* Cannot return FOUND without also setting ctx->user.cmnd to a new value. */
+ return NOT_FOUND;
+}
+
+static FILE *
+open_data(const uint8_t *data, size_t size)
+{
+#ifdef HAVE_FMEMOPEN
+ /* Operate in-memory. */
+ return fmemopen((void *)data, size, "r");
+#else
+ char tempfile[] = "/tmp/ldif.XXXXXX";
+ size_t nwritten;
+ int fd;
+
+ /* Use (unlinked) temporary file. */
+ fd = mkstemp(tempfile);
+ if (fd == -1)
+ return NULL;
+ unlink(tempfile);
+ nwritten = write(fd, data, size);
+ if (nwritten != size) {
+ close(fd);
+ return NULL;
+ }
+ lseek(fd, 0, SEEK_SET);
+ return fdopen(fd, "r");
+#endif
+}
+
+static int
+fuzz_conversation(int num_msgs, const struct sudo_conv_message msgs[],
+ struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
+{
+ int n;
+
+ for (n = 0; n < num_msgs; n++) {
+ const struct sudo_conv_message *msg = &msgs[n];
+
+ switch (msg->msg_type & 0xff) {
+ case SUDO_CONV_PROMPT_ECHO_ON:
+ case SUDO_CONV_PROMPT_MASK:
+ case SUDO_CONV_PROMPT_ECHO_OFF:
+ /* input not supported */
+ return -1;
+ case SUDO_CONV_ERROR_MSG:
+ case SUDO_CONV_INFO_MSG:
+ /* no output for fuzzers */
+ break;
+ default:
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ struct sudoers_context ctx = { { NULL } };
+ struct sudoers_parse_tree parse_tree;
+ FILE *fp;
+
+ /* Don't waste time fuzzing tiny inputs. */
+ if (size < 5)
+ return 0;
+
+ fp = open_data(data, size);
+ if (fp == NULL)
+ return 0;
+
+ initprogname("fuzz_sudoers_ldif");
+ sudoers_debug_register(getprogname(), NULL);
+ if (getenv("SUDO_FUZZ_VERBOSE") == NULL)
+ sudo_warn_set_conversation(fuzz_conversation);
+
+ /* Initialize defaults and parse LDIF-format sudoers. */
+ init_defaults();
+ init_parse_tree(&parse_tree, NULL, NULL, &ctx, NULL);
+ sudoers_parse_ldif(&parse_tree, fp, "ou=SUDOers,dc=sudo,dc=ws", true);
+
+ /* Cleanup. */
+ free_parse_tree(&parse_tree);
+ fclose(fp);
+ sudoers_debug_deregister();
+ fflush(stdout);
+
+ return 0;
+}
diff --git a/plugins/sudoers/regress/fuzz/fuzz_sudoers_ldif.dict b/plugins/sudoers/regress/fuzz/fuzz_sudoers_ldif.dict
new file mode 100644
index 0000000..7c4d2d0
--- /dev/null
+++ b/plugins/sudoers/regress/fuzz/fuzz_sudoers_ldif.dict
@@ -0,0 +1,14 @@
+# Sudoers LDIF attributes
+
+"description"
+"objectClass"
+"organizationalRole"
+"sudoCommand"
+"sudoHost"
+"sudoOption"
+"sudoOption"
+"sudoOrder"
+"sudoRunAs"
+"sudoRunAsGroup"
+"sudoRunAsUser"
+"sudoUser"
diff --git a/plugins/sudoers/regress/harness.in b/plugins/sudoers/regress/harness.in
new file mode 100755
index 0000000..9aa6e76
--- /dev/null
+++ b/plugins/sudoers/regress/harness.in
@@ -0,0 +1,217 @@
+#!/bin/sh
+#
+# Copyright (c) 2022 Todd C. Miller <Todd.Miller@sudo.ws>
+#
+# 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+#
+# Simple test harness for sudoers tests.
+# usage: harness [-v] test_group [test_name ...]
+#
+srcdir="@abs_srcdir@"
+builddir="@abs_builddir@"
+SHELL=@SHELL@
+verbose=0
+rval=0
+ntests=0
+errors=0
+
+umask 022
+
+if [ "$1" = "-v" ]; then
+ verbose=1
+ shift
+fi
+
+if [ $# -eq 0 ]; then
+ echo "usage: harness test_group [test_name ...]" >&2
+ exit 1
+fi
+group="$1"
+shift
+srcdir=${srcdir%"/regress"}
+builddir=${builddir%"/regress"}
+
+if [ ! -d "$srcdir/regress/$group" ]; then
+ echo "missing test group: regress/$group" >&2
+ exit 1
+fi
+
+case "$group" in
+sudoers)
+ mkdir -p "$builddir/regress/$group"
+ if [ $# -eq 0 ]; then
+ tests=
+ for t in $srcdir/regress/$group/*.in; do
+ tests="$tests `basename $t .in`"
+ done
+ set -- $tests
+ fi
+ while [ $# -ne 0 ]; do
+ test="$1"
+ shift
+ in="$srcdir/regress/sudoers/${test}.in"
+ out="$builddir/regress/sudoers/${test}.out"
+ out_ok="$srcdir/regress/sudoers/${test}.out.ok"
+ toke="$builddir/regress/sudoers/${test}.toke"
+ toke_ok="$srcdir/regress/sudoers/${test}.toke.ok"
+ json="$builddir/regress/sudoers/${test}.json"
+ json_ok="$srcdir/regress/sudoers/${test}.json.ok"
+ ldif="$builddir/regress/sudoers/${test}.ldif"
+ ldif_ok="$srcdir/regress/sudoers/${test}.ldif.ok"
+ ldif2sudo="$builddir/regress/sudoers/${test}.ldif2sudo"
+ ldif2sudo_ok="$srcdir/regress/sudoers/${test}.ldif2sudo.ok"
+ sudo="$builddir/regress/sudoers/${test}.sudo"
+
+ $builddir/testsudoers -dt <$in >$out 2>$toke || true
+ ntests=`expr $ntests + 1`
+ if cmp $out $out_ok >/dev/null; then
+ if [ $verbose -eq 1 ]; then
+ echo "$group/$test (parse): OK"
+ fi
+ else
+ errors=`expr $errors + 1`
+ echo "$group/$test (parse): FAIL"
+ diff $out $out_ok || true
+ fi
+ ntests=`expr $ntests + 1`
+ if cmp $toke $toke_ok >/dev/null; then
+ if [ $verbose -eq 1 ]; then
+ echo "$group/$test (toke): OK"
+ fi
+ else
+ errors=`expr $errors + 1`
+ echo "$group/$test (toke): FAIL"
+ diff $toke $toke_ok || true
+ fi
+
+ $builddir/cvtsudoers -c "" -f json $in >$json 2>/dev/null || true
+ ntests=`expr $ntests + 1`
+ if cmp $json $json_ok >/dev/null; then
+ if [ $verbose -eq 1 ]; then
+ echo "$group/$test (json): OK"
+ fi
+ else
+ errors=`expr $errors + 1`
+ echo "$group/$test (json): FAIL"
+ diff $json $json_ok || true
+ fi
+
+ SUDOERS_BASE="ou=SUDOers,dc=sudo,dc=ws" \
+ $builddir/cvtsudoers -c "" -f ldif < $in >$ldif 2>/dev/null || true
+ ntests=`expr $ntests + 1`
+ if cmp $ldif $ldif_ok >/dev/null; then
+ if [ $verbose -eq 1 ]; then
+ echo "$group/$test (ldif): OK"
+ fi
+ else
+ errors=`expr $errors + 1`
+ echo "$group/$test: (ldif) FAIL"
+ diff $ldif $ldif_ok || true
+ fi
+
+ $builddir/cvtsudoers -c "" -f sudoers $in >$sudo 2>/dev/null || true
+ ntests=`expr $ntests + 1`
+ if $builddir/visudo -qcf $sudo; then
+ if [ $verbose -eq 1 ]; then
+ echo "$group/$test (reparse): OK"
+ fi
+ else
+ errors=`expr $errors + 1`
+ echo "$group/$test: (reparse) FAIL"
+ $builddir/visudo -cf $sudo || true
+ fi
+
+ if test -s $ldif_ok; then
+ $builddir/cvtsudoers -c "" -i ldif -f sudoers $ldif_ok >$ldif2sudo || true
+ ntests=`expr $ntests + 1`
+ if cmp $ldif2sudo $ldif2sudo_ok >/dev/null; then
+ if [ $verbose -eq 1 ]; then
+ echo "$group/$test (ldif2sudo): OK"
+ fi
+ else
+ errors=`expr $errors + 1`
+ echo "$group/$test: (ldif2sudo) FAIL"
+ diff $ldif $ldif_ok || true
+ fi
+ fi
+ done
+ ${AWK-awk} -v group=$group -v ntests=$ntests -v errors=$errors \
+ 'END {printf("%s: %d tests run, %d errors, %d%% success rate\n", group, ntests, errors, (ntests - errors) * 100 / ntests)}' < /dev/null
+ if test $errors -ne 0; then
+ rval=`expr $rval + $errors`
+ fi
+ ;;
+*)
+ TESTSUDOERS=$builddir/testsudoers; export TESTSUDOERS
+ VISUDO=$builddir/visudo; export VISUDO
+ CVTSUDOERS=$builddir/cvtsudoers; export CVTSUDOERS
+ mkdir -p "regress/$group"
+ if [ $# -eq 0 ]; then
+ tests=
+ for t in $srcdir/regress/$group/*.sh; do
+ tests="$tests `basename $t .sh`"
+ done
+ set -- $tests
+ fi
+ while [ $# -ne 0 ]; do
+ test="$1"
+ shift
+
+ cmd="$srcdir/regress/$group/${test}.sh"
+ out="$builddir/regress/$group/${test}.out"
+ out_ok="$srcdir/regress/$group/${test}.out.ok"
+ err="$builddir/regress/$group/${test}.err"
+ err_ok="$srcdir/regress/$group/${test}.err.ok"
+ status=0
+ TESTDIR=$srcdir/regress/$group $SHELL $cmd >$out 2>$err || status=$?
+ ntests=`expr $ntests + 1`
+ if cmp $out $out_ok >/dev/null; then
+ if test $status -eq 0; then
+ if [ $verbose -eq 1 ]; then
+ echo "$group/$test: OK"
+ fi
+ else
+ errors=`expr $errors + 1`
+ echo "$group/$test (exit $status): FAIL"
+ fi
+ else
+ errors=`expr $errors + 1`
+ echo "$group/$test: FAIL"
+ diff $out $out_ok || true
+ fi
+ ntests=`expr $ntests + 1`
+ if test -s $err_ok; then
+ if cmp $err $err_ok >/dev/null; then
+ if [ $verbose -eq 1 ]; then
+ echo "$group/$test (stderr): OK"
+ fi
+ else
+ errors=`expr $errors + 1`
+ echo "$group/$test (stderr): FAIL"
+ diff $err $err_ok || true
+ fi
+ elif test -s $err; then
+ errors=`expr $errors + 1`
+ echo "$group/$test (stderr): FAIL"
+ cat $err 1>&2
+ fi
+ done
+ ${AWK-awk} -v group=$group -v ntests=$ntests -v errors=$errors \
+ 'END {printf("%s: %d tests run, %d errors, %d%% success rate\n", group, ntests, errors, (ntests - errors) * 100 / ntests)}' < /dev/null
+ if test $errors -ne 0; then
+ rval=`expr $rval + $errors`
+ fi
+ ;;
+esac
+
+exit $rval
diff --git a/plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c b/plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c
new file mode 100644
index 0000000..991c30c
--- /dev/null
+++ b/plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c
@@ -0,0 +1,455 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2018-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <pwd.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include <sudoers.h>
+#include <sudo_eventlog.h>
+#include <sudo_iolog.h>
+#include <sudo_plugin.h>
+
+#include <def_data.c> /* for iolog_path.c */
+
+extern struct io_plugin sudoers_io;
+
+sudo_printf_t sudo_printf;
+sudo_conv_t sudo_conv;
+struct sudo_plugin_event * (*plugin_event_alloc)(void);
+
+static struct sudoers_context io_ctx = SUDOERS_CONTEXT_INITIALIZER;
+
+sudo_dso_public int main(int argc, char *argv[], char *envp[]);
+
+sudo_noreturn static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-v] pathname\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+static int
+sudo_printf_int(int msg_type, const char * restrict fmt, ...)
+{
+ va_list ap;
+ int len;
+
+ switch (msg_type) {
+ case SUDO_CONV_INFO_MSG:
+ va_start(ap, fmt);
+ len = vfprintf(stdout, fmt, ap);
+ va_end(ap);
+ break;
+ case SUDO_CONV_ERROR_MSG:
+ va_start(ap, fmt);
+ len = vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ break;
+ default:
+ len = -1;
+ errno = EINVAL;
+ break;
+ }
+
+ return len;
+}
+
+static bool
+validate_iolog_info(const char *log_dir, bool legacy)
+{
+ struct eventlog *evlog;
+ time_t now;
+
+ time(&now);
+
+ /* Parse log file. */
+ if ((evlog = iolog_parse_loginfo(-1, log_dir)) == NULL)
+ return false;
+
+ if (evlog->cwd == NULL || strcmp(evlog->cwd, "/") != 0) {
+ sudo_warnx("bad cwd: want \"/\", got \"%s\"",
+ evlog->cwd ? evlog->cwd : "NULL");
+ return false;
+ }
+
+ /* No host in the legacy log file. */
+ if (!legacy) {
+ if (evlog->submithost == NULL || strcmp(evlog->submithost, "localhost") != 0) {
+ sudo_warnx("bad host: want \"localhost\", got \"%s\"",
+ evlog->submithost ? evlog->submithost : "NULL");
+ return false;
+ }
+ }
+
+ if (evlog->submituser == NULL || strcmp(evlog->submituser, "nobody") != 0) {
+ sudo_warnx("bad user: want \"nobody\" got \"%s\"",
+ evlog->submituser ? evlog->submituser : "NULL");
+ return false;
+ }
+
+ if (evlog->runuser == NULL || strcmp(evlog->runuser, "root") != 0) {
+ sudo_warnx("bad runuser: want \"root\" got \"%s\"",
+ evlog->runuser ? evlog->runuser : "NULL");
+ return false;
+ }
+
+ /* No runas group specified, should be NULL. */
+ if (evlog->rungroup != NULL) {
+ sudo_warnx("bad rungroup: want \"\" got \"%s\"", evlog->rungroup);
+ return false;
+ }
+
+ if (evlog->ttyname == NULL || strcmp(evlog->ttyname, "/dev/console") != 0) {
+ sudo_warnx("bad tty: want \"/dev/console\" got \"%s\"",
+ evlog->ttyname ? evlog->ttyname : "NULL");
+ return false;
+ }
+
+ if (evlog->command == NULL || strcmp(evlog->command, "/usr/bin/id") != 0) {
+ sudo_warnx("bad command: want \"/usr/bin/id\" got \"%s\"",
+ evlog->command ? evlog->command : "NULL");
+ return false;
+ }
+
+ if (evlog->lines != 24) {
+ sudo_warnx("bad lines: want 24 got %d", evlog->lines);
+ return false;
+ }
+
+ if (evlog->columns != 80) {
+ sudo_warnx("bad columns: want 80 got %d", evlog->columns);
+ return false;
+ }
+
+ if (evlog->submit_time.tv_sec < now - 10 || evlog->submit_time.tv_sec > now + 10) {
+ sudo_warnx("bad submit_time: want %lld got %lld", (long long)now,
+ (long long)evlog->submit_time.tv_sec);
+ return false;
+ }
+
+ eventlog_free(evlog);
+
+ return true;
+}
+
+static bool
+validate_timing(FILE *fp, int recno, int type, unsigned int p1, unsigned int p2)
+{
+ struct timing_closure timing;
+ char buf[LINE_MAX];
+
+ if (!fgets(buf, sizeof(buf), fp)) {
+ sudo_warn("unable to read timing file");
+ return false;
+ }
+ buf[strcspn(buf, "\n")] = '\0';
+ if (!iolog_parse_timing(buf, &timing)) {
+ sudo_warnx("invalid timing file line: %s", buf);
+ return false;
+ }
+ if (timing.event != type) {
+ sudo_warnx("record %d: want type %d, got type %d", recno, type,
+ timing.event);
+ return false;
+ }
+ if (type == IO_EVENT_WINSIZE) {
+ if (timing.u.winsize.lines != (int)p1) {
+ sudo_warnx("record %d: want %u lines, got %u", recno, p1,
+ timing.u.winsize.lines);
+ return false;
+ }
+ if (timing.u.winsize.cols != (int)p2) {
+ sudo_warnx("record %d: want %u cols, got %u", recno, p2,
+ timing.u.winsize.cols);
+ return false;
+ }
+ } else {
+ if (timing.u.nbytes != p1) {
+ sudo_warnx("record %d: want len %u, got type %zu", recno, p1,
+ timing.u.nbytes);
+ return false;
+ }
+ }
+ if (timing.delay.tv_sec != 0) {
+ sudo_warnx("record %d: got excessive delay %lld.%09ld", recno,
+ (long long)timing.delay.tv_sec, timing.delay.tv_nsec);
+ return false;
+ }
+
+ return true;
+}
+
+
+/*
+ * Test sudoers I/O log plugin endpoints.
+ */
+static void
+test_endpoints(const struct sudoers_context *ctx, int *ntests, int *nerrors,
+ const char *iolog_dir, char *envp[])
+{
+ int rc, cmnd_argc = 1;
+ const char *errstr = NULL;
+ char buf[1024], iolog_path[PATH_MAX];
+ char runas_gid[64], runas_uid[64];
+ FILE *fp;
+ const char *cmnd_argv[] = {
+ "/usr/bin/id",
+ NULL
+ };
+ const char *user_info[] = {
+ "cols=80",
+ "lines=24",
+ "cwd=/",
+ "host=localhost",
+ "tty=/dev/console",
+ "user=nobody",
+ NULL
+ };
+ const char *command_info[] = {
+ "command=/usr/bin/id",
+ iolog_path,
+ "iolog_stdin=true",
+ "iolog_stdout=true",
+ "iolog_stderr=true",
+ "iolog_ttyin=true",
+ "iolog_ttyout=true",
+ "iolog_compress=false",
+ "iolog_mode=0644",
+ runas_gid,
+ runas_uid,
+ NULL
+ };
+ char *settings[] = {
+ NULL
+ };
+ const char output[] = "uid=0(root) gid=0(wheel)\r\n";
+ const unsigned int outlen = sizeof(output) - 1;
+
+ /* Set runas uid/gid to root. */
+ snprintf(runas_uid, sizeof(runas_uid), "runas_uid=%u",
+ (unsigned int)ctx->runas.pw->pw_uid);
+ snprintf(runas_gid, sizeof(runas_gid), "runas_gid=%u",
+ (unsigned int)ctx->runas.pw->pw_gid);
+
+ /* Set path to the iolog directory the user passed in. */
+ snprintf(iolog_path, sizeof(iolog_path), "iolog_path=%s", iolog_dir);
+
+ /* Test open endpoint. */
+ rc = sudoers_io.open(SUDO_API_VERSION, NULL, sudo_printf_int, settings,
+ (char **)user_info, (char **)command_info, cmnd_argc,
+ (char **)cmnd_argv, envp, NULL, &errstr);
+ (*ntests)++;
+ if (rc != 1) {
+ sudo_warnx("I/O log open endpoint failed");
+ (*nerrors)++;
+ return;
+ }
+
+ /* Test log_ttyout endpoint. */
+ rc = sudoers_io.log_ttyout(output, outlen, &errstr);
+ (*ntests)++;
+ if (rc != 1) {
+ sudo_warnx("I/O log_ttyout endpoint failed");
+ (*nerrors)++;
+ return;
+ }
+
+ /* Test change_winsize endpoint (twice). */
+ rc = sudoers_io.change_winsize(32, 128, &errstr);
+ (*ntests)++;
+ if (rc != 1) {
+ sudo_warnx("I/O change_winsize endpoint failed");
+ (*nerrors)++;
+ return;
+ }
+ rc = sudoers_io.change_winsize(24, 80, &errstr);
+ (*ntests)++;
+ if (rc != 1) {
+ sudo_warnx("I/O change_winsize endpoint failed");
+ (*nerrors)++;
+ return;
+ }
+
+ /* Close the plugin. */
+ sudoers_io.close(0, 0);
+
+ /* Validate I/O log info file (json). */
+ (*ntests)++;
+ if (!validate_iolog_info(iolog_dir, false))
+ (*nerrors)++;
+
+ /* Validate I/O log info file (legacy). */
+ snprintf(iolog_path, sizeof(iolog_path), "%s/log.json", iolog_dir);
+ unlink(iolog_path);
+ (*ntests)++;
+ if (!validate_iolog_info(iolog_dir, true))
+ (*nerrors)++;
+
+ /* Validate the timing file. */
+ snprintf(iolog_path, sizeof(iolog_path), "%s/timing", iolog_dir);
+ (*ntests)++;
+ if ((fp = fopen(iolog_path, "r")) == NULL) {
+ sudo_warn("unable to open %s", iolog_path);
+ (*nerrors)++;
+ return;
+ }
+
+ /* Line 1: output of id command. */
+ if (!validate_timing(fp, 1, IO_EVENT_TTYOUT, outlen, 0)) {
+ (*nerrors)++;
+ return;
+ }
+
+ /* Line 2: window size change. */
+ if (!validate_timing(fp, 2, IO_EVENT_WINSIZE, 32, 128)) {
+ (*nerrors)++;
+ return;
+ }
+
+ /* Line 3: window size change. */
+ if (!validate_timing(fp, 3, IO_EVENT_WINSIZE, 24, 80)) {
+ (*nerrors)++;
+ return;
+ }
+
+ /* Validate ttyout log file. */
+ snprintf(iolog_path, sizeof(iolog_path), "%s/ttyout", iolog_dir);
+ (*ntests)++;
+ fclose(fp);
+ if ((fp = fopen(iolog_path, "r")) == NULL) {
+ sudo_warn("unable to open %s", iolog_path);
+ (*nerrors)++;
+ return;
+ }
+ if (!fgets(buf, sizeof(buf), fp)) {
+ sudo_warn("unable to read %s", iolog_path);
+ (*nerrors)++;
+ return;
+ }
+ if (strcmp(buf, output) != 0) {
+ sudo_warnx("ttylog mismatch: want \"%s\", got \"%s\"", output, buf);
+ (*nerrors)++;
+ return;
+ }
+}
+
+int
+main(int argc, char *argv[], char *envp[])
+{
+ struct passwd *tpw;
+ int ch, tests = 0, errors = 0;
+ const char *iolog_dir;
+
+ initprogname(argc > 0 ? argv[0] : "check_iolog_plugin");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+ iolog_dir = argv[0];
+
+ /* Set runas user. */
+ if ((tpw = getpwuid(0)) == NULL) {
+ if ((tpw = getpwnam("root")) == NULL)
+ sudo_fatalx("unable to look up uid 0 or root");
+ }
+ io_ctx.runas.pw = pw_dup(tpw);
+
+ /* Set invoking user. */
+ if ((tpw = getpwuid(geteuid())) == NULL)
+ sudo_fatalx("unable to look up invoking user's uid");
+ io_ctx.user.pw = pw_dup(tpw);
+
+ /* Set iolog uid/gid to invoking user. */
+ iolog_set_owner(io_ctx.user.pw->pw_uid, io_ctx.user.pw->pw_gid);
+
+ test_endpoints(&io_ctx, &tests, &errors, iolog_dir, envp);
+
+ if (tests != 0) {
+ printf("check_iolog_plugin: %d test%s run, %d errors, %d%% success rate\n",
+ tests, tests == 1 ? "" : "s", errors,
+ (tests - errors) * 100 / tests);
+ }
+
+ exit(errors);
+}
+
+/* Stub functions */
+
+bool
+set_perms(const struct sudoers_context *ctx, int perm)
+{
+ return true;
+}
+
+bool
+restore_perms(void)
+{
+ return true;
+}
+
+bool
+log_warning(const struct sudoers_context *ctx, unsigned int flags,
+ const char * restrict fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ sudo_vwarn_nodebug(fmt, ap);
+ va_end(ap);
+
+ return true;
+}
+
+bool
+log_warningx(const struct sudoers_context *ctx, unsigned int flags,
+ const char * restrict fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ sudo_vwarnx_nodebug(fmt, ap);
+ va_end(ap);
+
+ return true;
+}
+
+const struct sudoers_context *
+sudoers_get_context(void)
+{
+ return &io_ctx;
+}
diff --git a/plugins/sudoers/regress/parser/check_addr.c b/plugins/sudoers/regress/parser/check_addr.c
new file mode 100644
index 0000000..3db53c6
--- /dev/null
+++ b/plugins/sudoers/regress/parser/check_addr.c
@@ -0,0 +1,152 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2011-2013 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include <sudoers.h>
+#include <interfaces.h>
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+static int
+check_addr(char *input)
+{
+ int expected, matched;
+ const char *errstr;
+ size_t len;
+ char *cp;
+
+ while (isspace((unsigned char)*input))
+ input++;
+
+ /* input: "addr[/mask] 1/0" */
+ len = strcspn(input, " \t");
+ cp = input + len;
+ while (isspace((unsigned char)*cp))
+ cp++;
+ expected = (int)sudo_strtonum(cp, 0, 1, &errstr);
+ if (errstr != NULL)
+ sudo_fatalx("expecting 0 or 1, got %s", cp);
+ input[len] = '\0';
+
+ matched = addr_matches(input) == ALLOW;
+ if (matched != expected) {
+ sudo_warnx("%s %smatched: FAIL", input, matched ? "" : "not ");
+ return 1;
+ }
+ return 0;
+}
+
+sudo_noreturn static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-v] datafile\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch, ntests = 0, errors = 0;
+ char *cp, line[2048];
+ size_t len;
+ FILE *fp;
+
+ initprogname(argc > 0 ? argv[0] : "check_addr");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ fp = fopen(argv[0], "r");
+ if (fp == NULL)
+ sudo_fatalx("unable to open %s", argv[0]);
+
+ /*
+ * Input is in the following format. There are two types of
+ * lines: interfaces, which sets the address and mask of the
+ * locally connected ethernet interfaces for the lines that
+ * follow and, address lines that include and address (with
+ * optional netmask) to match, followed by expected match status
+ * (1 or 0). E.g.
+ *
+ * interfaces: addr1/mask addr2/mask ...
+ * address: addr[/mask] 1/0
+ * address: addr[/mask] 1/0
+ * interfaces: addr3/mask addr4/mask ...
+ * address: addr[/mask] 1/0
+ */
+
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ len = strcspn(line, "\n");
+ line[len] = '\0';
+
+ /* Ignore comments */
+ if ((cp = strchr(line, '#')) != NULL)
+ *cp = '\0';
+
+ /* Skip blank lines. */
+ if (line[0] == '\0')
+ continue;
+
+ if (strncmp(line, "interfaces:", sizeof("interfaces:") - 1) == 0) {
+ if (!set_interfaces(line + sizeof("interfaces:") - 1)) {
+ sudo_warn("unable to parse interfaces list");
+ errors++;
+ }
+ } else if (strncmp(line, "address:", sizeof("address:") - 1) == 0) {
+ errors += check_addr(line + sizeof("address:") - 1);
+ ntests++;
+ } else {
+ sudo_warnx("unexpected data line: %s", line);
+ continue;
+ }
+ }
+
+ if (ntests != 0) {
+ printf("check_addr: %d tests run, %d errors, %d%% success rate\n",
+ ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ exit(errors);
+}
diff --git a/plugins/sudoers/regress/parser/check_addr.in b/plugins/sudoers/regress/parser/check_addr.in
new file mode 100644
index 0000000..a3c8612
--- /dev/null
+++ b/plugins/sudoers/regress/parser/check_addr.in
@@ -0,0 +1,13 @@
+#
+interfaces: 10.5.54.73/255.255.240.0
+address: 10.5.48.0 1
+address: 10.5.54.0/20 1
+#
+interfaces: 128.138.243.151/255.255.255.0 128.138.241.53/255.255.255.0
+address: 128.138.243.0 1
+address: 128.138.243.0/24 1
+address: 128.138.241.0 1
+address: 128.138.241.0/24 1
+address: 128.138.242.0/24 0
+address: 128.138.0.0 0
+address: 128.138.0.0/16 1
diff --git a/plugins/sudoers/regress/parser/check_base64.c b/plugins/sudoers/regress/parser/check_base64.c
new file mode 100644
index 0000000..4fe54ad
--- /dev/null
+++ b/plugins/sudoers/regress/parser/check_base64.c
@@ -0,0 +1,130 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include <sudo_compat.h>
+#include <sudo_util.h>
+
+/* From parse.h */
+extern size_t base64_decode(const char *str, unsigned char *dst, size_t dsize);
+extern size_t base64_encode(const unsigned char *in, size_t in_len, char *out, size_t out_len);
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+static unsigned char bstring1[] = { 0xea, 0xb8, 0xa2, 0x71, 0xef, 0x67, 0xc1, 0xcd, 0x0d, 0xd9, 0xa6, 0xaa, 0xa8, 0x24, 0x77, 0x2a, 0xfc, 0x6f, 0x76, 0x37, 0x1b, 0xed, 0x9e, 0x1a, 0x90, 0x5f, 0xcf, 0xbc, 0x00 };
+
+struct base64_test {
+ const char *ascii;
+ const char *encoded;
+} test_strings[] = {
+ {
+ (char *)bstring1,
+ "6riice9nwc0N2aaqqCR3Kvxvdjcb7Z4akF/PvA=="
+ },
+ {
+ "any carnal pleasure.",
+ "YW55IGNhcm5hbCBwbGVhc3VyZS4="
+ },
+ {
+ "any carnal pleasure",
+ "YW55IGNhcm5hbCBwbGVhc3VyZQ=="
+ },
+ {
+ "any carnal pleasur",
+ "YW55IGNhcm5hbCBwbGVhc3Vy"
+ },
+ {
+ "any carnal pleasu",
+ "YW55IGNhcm5hbCBwbGVhc3U="
+ },
+ {
+ "any carnal pleas",
+ "YW55IGNhcm5hbCBwbGVhcw=="
+ }
+};
+
+int
+main(int argc, char *argv[])
+{
+ int ch, ntests = nitems(test_strings);
+ int i, errors = 0;
+ unsigned char buf[64];
+ size_t len;
+
+ initprogname(argc > 0 ? argv[0] : "check_base64");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ for (i = 0; i < ntests; i++) {
+ /* Test decode. */
+ len = base64_decode(test_strings[i].encoded, buf, sizeof(buf));
+ if (len == (size_t)-1) {
+ fprintf(stderr, "check_base64: failed to decode %s\n",
+ test_strings[i].encoded);
+ errors++;
+ } else {
+ buf[len] = '\0';
+ if (strcmp(test_strings[i].ascii, (char *)buf) != 0) {
+ fprintf(stderr, "check_base64: expected %s, got %s\n",
+ test_strings[i].ascii, buf);
+ errors++;
+ }
+ }
+
+ /* Test encode. */
+ len = base64_encode((unsigned char *)test_strings[i].ascii,
+ strlen(test_strings[i].ascii), (char *)buf, sizeof(buf));
+ if (len == (size_t)-1) {
+ fprintf(stderr, "check_base64: failed to encode %s\n",
+ test_strings[i].ascii);
+ errors++;
+ } else {
+ if (strcmp(test_strings[i].encoded, (char *)buf) != 0) {
+ fprintf(stderr, "check_base64: expected %s, got %s\n",
+ test_strings[i].encoded, buf);
+ errors++;
+ }
+ }
+ }
+ ntests *= 2; /* we test in both directions */
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ exit(errors);
+}
diff --git a/plugins/sudoers/regress/parser/check_digest.c b/plugins/sudoers/regress/parser/check_digest.c
new file mode 100644
index 0000000..66527a6
--- /dev/null
+++ b/plugins/sudoers/regress/parser/check_digest.c
@@ -0,0 +1,140 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sudo_compat.h>
+#include <sudo_fatal.h>
+#include <sudo_queue.h>
+#include <sudo_digest.h>
+#include <sudo_util.h>
+#include <parse.h>
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+#define NUM_TESTS 8
+static const char *test_strings[NUM_TESTS] = {
+ "",
+ "a",
+ "abc",
+ "message digest",
+ "abcdefghijklmnopqrstuvwxyz",
+ "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+ "12345678901234567890123456789012345678901234567890123456789"
+ "012345678901234567890",
+};
+
+static unsigned char *
+check_digest(unsigned int digest_type, const char *buf, size_t buflen,
+ size_t *digest_len)
+{
+ char tfile[] = "digest.XXXXXX";
+ unsigned char *digest = NULL;
+ int tfd;
+
+ /* Write test data to temporary file. */
+ tfd = mkstemp(tfile);
+ if (tfd == -1) {
+ sudo_warn_nodebug("mkstemp");
+ goto done;
+ }
+ if ((size_t)write(tfd, buf, buflen) != buflen) {
+ sudo_warn_nodebug("write");
+ goto done;
+ }
+ lseek(tfd, 0, SEEK_SET);
+
+ /* Get file digest. */
+ digest = sudo_filedigest(tfd, tfile, digest_type, digest_len);
+ if (digest == NULL) {
+ /* Warning (if any) printed by sudo_filedigest() */
+ goto done;
+ }
+done:
+ if (tfd != -1) {
+ close(tfd);
+ unlink(tfile);
+ }
+ return digest;
+}
+
+int
+main(int argc, char *argv[])
+{
+ static const char hex[] = "0123456789abcdef";
+ char buf[1000 * 1000];
+ unsigned char *digest;
+ unsigned int i, j;
+ size_t digest_len;
+ int ch;
+ unsigned int digest_type;
+
+ initprogname(argc > 0 ? argv[0] : "check_digest");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ for (digest_type = 0; digest_type < SUDO_DIGEST_INVALID; digest_type++) {
+ for (i = 0; i < NUM_TESTS; i++) {
+ digest = check_digest(digest_type, test_strings[i],
+ strlen(test_strings[i]), &digest_len);
+ if (digest != NULL) {
+ printf("%s (\"%s\") = ", digest_type_to_name(digest_type),
+ test_strings[i]);
+ for (j = 0; j < digest_len; j++) {
+ putchar(hex[digest[j] >> 4]);
+ putchar(hex[digest[j] & 0x0f]);
+ }
+ putchar('\n');
+ free(digest);
+ }
+ }
+
+ /* Simulate a string of a million 'a' characters. */
+ memset(buf, 'a', sizeof(buf));
+ digest = check_digest(digest_type, buf, sizeof(buf), &digest_len);
+ if (digest != NULL) {
+ printf("%s (one million 'a' characters) = ",
+ digest_type_to_name(digest_type));
+ for (j = 0; j < digest_len; j++) {
+ putchar(hex[digest[j] >> 4]);
+ putchar(hex[digest[j] & 0x0f]);
+ }
+ putchar('\n');
+ free(digest);
+ }
+ }
+
+ return 0;
+}
diff --git a/plugins/sudoers/regress/parser/check_digest.out.ok b/plugins/sudoers/regress/parser/check_digest.out.ok
new file mode 100644
index 0000000..a353664
--- /dev/null
+++ b/plugins/sudoers/regress/parser/check_digest.out.ok
@@ -0,0 +1,36 @@
+sha224 ("") = d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f
+sha224 ("a") = abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5
+sha224 ("abc") = 23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7
+sha224 ("message digest") = 2cb21c83ae2f004de7e81c3c7019cbcb65b71ab656b22d6d0c39b8eb
+sha224 ("abcdefghijklmnopqrstuvwxyz") = 45a5f72c39c5cff2522eb3429799e49e5f44b356ef926bcf390dccc2
+sha224 ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525
+sha224 ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") = bff72b4fcb7d75e5632900ac5f90d219e05e97a7bde72e740db393d9
+sha224 ("12345678901234567890123456789012345678901234567890123456789012345678901234567890") = b50aecbe4e9bb0b57bc5f3ae760a8e01db24f203fb3cdcd13148046e
+sha224 (one million 'a' characters) = 20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67
+sha256 ("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+sha256 ("a") = ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb
+sha256 ("abc") = ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
+sha256 ("message digest") = f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650
+sha256 ("abcdefghijklmnopqrstuvwxyz") = 71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73
+sha256 ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1
+sha256 ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") = db4bfcbd4da0cd85a60c3c37d3fbd8805c77f15fc6b1fdfe614ee0a7c8fdb4c0
+sha256 ("12345678901234567890123456789012345678901234567890123456789012345678901234567890") = f371bc4a311f2b009eef952dd83ca80e2b60026c8e935592d0f9c308453c813e
+sha256 (one million 'a' characters) = cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0
+sha384 ("") = 38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b
+sha384 ("a") = 54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d57bc35efae0b5afd3145f31
+sha384 ("abc") = cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7
+sha384 ("message digest") = 473ed35167ec1f5d8e550368a3db39be54639f828868e9454c239fc8b52e3c61dbd0d8b4de1390c256dcbb5d5fd99cd5
+sha384 ("abcdefghijklmnopqrstuvwxyz") = feb67349df3db6f5924815d6c3dc133f091809213731fe5c7b5f4999e463479ff2877f5f2936fa63bb43784b12f3ebb4
+sha384 ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b
+sha384 ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") = 1761336e3f7cbfe51deb137f026f89e01a448e3b1fafa64039c1464ee8732f11a5341a6f41e0c202294736ed64db1a84
+sha384 ("12345678901234567890123456789012345678901234567890123456789012345678901234567890") = b12932b0627d1c060942f5447764155655bd4da0c9afa6dd9b9ef53129af1b8fb0195996d2de9ca0df9d821ffee67026
+sha384 (one million 'a' characters) = 9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985
+sha512 ("") = cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e
+sha512 ("a") = 1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75
+sha512 ("abc") = ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f
+sha512 ("message digest") = 107dbf389d9e9f71a3a95f6c055b9251bc5268c2be16d6c13492ea45b0199f3309e16455ab1e96118e8a905d5597b72038ddb372a89826046de66687bb420e7c
+sha512 ("abcdefghijklmnopqrstuvwxyz") = 4dbff86cc2ca1bae1e16468a05cb9881c97f1753bce3619034898faa1aabe429955a1bf8ec483d7421fe3c1646613a59ed5441fb0f321389f77f48a879c7b1f1
+sha512 ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445
+sha512 ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") = 1e07be23c26a86ea37ea810c8ec7809352515a970e9253c26f536cfc7a9996c45c8370583e0a78fa4a90041d71a4ceab7423f19c71b9d5a3e01249f0bebd5894
+sha512 ("12345678901234567890123456789012345678901234567890123456789012345678901234567890") = 72ec1ef1124a45b047e8b7c75a932195135bb61de24ec0d1914042246e0aec3a2354e093d76f3048b456764346900cb130d2a4fd5dd16abb5e30bcb850dee843
+sha512 (one million 'a' characters) = e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b
diff --git a/plugins/sudoers/regress/parser/check_fill.c b/plugins/sudoers/regress/parser/check_fill.c
new file mode 100644
index 0000000..cd8475f
--- /dev/null
+++ b/plugins/sudoers/regress/parser/check_fill.c
@@ -0,0 +1,225 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2011-2016 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include <compat/stdbool.h>
+#endif /* HAVE_STDBOOL_H */
+#include <string.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include <sudo_compat.h>
+#include <sudo_queue.h>
+#include <parse.h>
+#include <toke.h>
+#include <sudo_plugin.h>
+#include <sudo_util.h>
+#include <gram.h>
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * TODO: test realloc
+ */
+
+YYSTYPE sudoerslval;
+
+struct fill_test {
+ const char *input;
+ const char *output;
+ int len;
+ bool addspace;
+};
+
+/*
+ * In "normal" fill, anything can be escaped and hex chars are expanded.
+ */
+static struct fill_test txt_data[] = {
+ { "Embedded\\x20Space", "Embedded Space", 0 },
+ { "\\x20Leading", " Leading", 0 },
+ { "Trailing\\x20", "Trailing ", 0 },
+ { "Multiple\\x20\\x20Spaces", "Multiple Spaces", 0 },
+ { "Hexparse\\x200Check", "Hexparse 0Check", 0 },
+ { "Escaped\\\\Escape", "Escaped\\Escape", 0 },
+ { "LongGroupName", "LongGrou", 8 }
+};
+
+/*
+ * The only escaped chars in a command should be [,:= \t#]
+ * The rest are done by glob() or fnmatch().
+ */
+static struct fill_test cmd_data[] = {
+ { "foo\\,bar", "foo,bar", 0 },
+ { "this\\:that", "this:that", 0 },
+ { "foo\\=bar", "foo=bar", 0 },
+ { "tab\\\tstop", "tab\tstop", 0 },
+ { "not a \\#comment", "not a #comment", 0 }
+};
+
+/*
+ * No escaped characters in command line args.
+ * Arguments get appended.
+ */
+static struct fill_test args_data[] = {
+ { "/", "/", 0, false },
+ { "-type", "/ -type", 0, true },
+ { "f", "/ -type f", 0, true },
+ { "-exec", "/ -type f -exec", 0, true },
+ { "ls", "/ -type f -exec ls", 0, true },
+ { "{}", "/ -type f -exec ls {}", 0, true }
+};
+
+static int
+check_fill(const char *input, int len, bool addspace, const char *expect, char **resultp)
+{
+ if (sudoerslval.string != NULL) {
+ free(sudoerslval.string);
+ sudoerslval.string = NULL;
+ }
+ if (!fill(input, len))
+ return -1;
+ *resultp = sudoerslval.string;
+ return !strcmp(sudoerslval.string, expect);
+}
+
+static int
+check_fill_cmnd(const char *input, int len, bool addspace, const char *expect, char **resultp)
+{
+ if (sudoerslval.command.cmnd != NULL) {
+ free(sudoerslval.command.cmnd);
+ sudoerslval.command.cmnd = NULL;
+ }
+ if (!fill_cmnd(input, len))
+ return -1;
+ *resultp = sudoerslval.command.cmnd;
+ return !strcmp(sudoerslval.command.cmnd, expect);
+}
+
+static int
+check_fill_args(const char *input, int len, bool addspace, const char *expect, char **resultp)
+{
+ /* Must not free old sudoerslval.command.args as gets appended to. */
+ if (!fill_args(input, len, addspace))
+ return -1;
+ *resultp = sudoerslval.command.args;
+ return !strcmp(sudoerslval.command.args, expect);
+}
+
+static int
+do_tests(int (*checker)(const char *, int, bool, const char *, char **),
+ struct fill_test *data, size_t ntests)
+{
+ int errors = 0;
+ unsigned int i;
+ int len;
+ char *result;
+
+ for (i = 0; i < ntests; i++) {
+ if (data[i].len == 0)
+ len = (int)strlen(data[i].input);
+ else
+ len = data[i].len;
+
+ switch ((*checker)(data[i].input, len, data[i].addspace, data[i].output, &result)) {
+ case 0:
+ /* no match */
+ fprintf(stderr, "Failed parsing %.*s: expected [%s], got [%s]\n",
+ (int)data[i].len, data[i].input, data[i].output, result);
+ errors++;
+ break;
+ case 1:
+ /* match */
+ break;
+ default:
+ /* error */
+ fprintf(stderr, "Failed parsing %.*s: fill function failure\n",
+ (int)data[i].len, data[i].input);
+ errors++;
+ break;
+ }
+ }
+
+ return errors;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch, ntests, errors = 0;
+
+ initprogname(argc > 0 ? argv[0] : "check_fill");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ errors += do_tests(check_fill, txt_data, nitems(txt_data));
+ errors += do_tests(check_fill_cmnd, cmd_data, nitems(cmd_data));
+ errors += do_tests(check_fill_args, args_data, nitems(args_data));
+
+ ntests = nitems(txt_data) + nitems(cmd_data) + nitems(args_data);
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
+
+/* STUB */
+void
+sudoerserror(const char *s)
+{
+ return;
+}
+
+/* STUB */
+bool
+sudoers_strict(void)
+{
+ return false;
+}
+
+/* STUB */
+bool
+parser_leak_add(enum parser_leak_types type, void *v)
+{
+ return true;
+}
+
+/* STUB */
+bool
+parser_leak_remove(enum parser_leak_types type, void *v)
+{
+ return true;
+}
diff --git a/plugins/sudoers/regress/parser/check_gentime.c b/plugins/sudoers/regress/parser/check_gentime.c
new file mode 100644
index 0000000..8b1e0f6
--- /dev/null
+++ b/plugins/sudoers/regress/parser/check_gentime.c
@@ -0,0 +1,98 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2017 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include <sudo_compat.h>
+#include <sudo_util.h>
+#include <sudoers_debug.h>
+#include <parse.h>
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+const struct gentime_test {
+ const char *gentime;
+ time_t unixtime;
+} tests[] = {
+ { "199412161032ZZ", -1 },
+ { "199412161032Z", 787573920 },
+ { "199412160532-0500", 787573920 },
+ { "199412160532-05000", -1 },
+ { "199412160532", 787573920 }, /* local time is EST */
+ { "20170214083000-0500", 1487079000 },
+ { "201702140830-0500", 1487079000 },
+ { "201702140830", 1487079000 }, /* local time is EST */
+ { "201702140830.3-0500", 1487079018 },
+ { "201702140830,3-0500", 1487079018 },
+ { "20170214083000.5Z", 1487061000 },
+ { "20170214083000,5Z", 1487061000 },
+ { "201702142359.4Z", 1487116764 },
+ { "201702142359,4Z", 1487116764 },
+ { "2017021408.5Z", 1487061000 },
+ { "2017021408,5Z", 1487061000 },
+ { "20170214Z", -1 },
+};
+
+int
+main(int argc, char *argv[])
+{
+ const int ntests = nitems(tests);
+ int ch, i, errors = 0;
+ time_t result;
+
+ initprogname(argc > 0 ? argv[0] : "check_gentime");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Do local time tests in Eastern Standard Time. */
+ putenv((char *)"TZ=EST5EST5");
+ tzset();
+
+ for (i = 0; i < ntests; i++) {
+ result = parse_gentime(tests[i].gentime);
+ if (result != tests[i].unixtime) {
+ fprintf(stderr, "check_gentime[%d]: %s: expected %lld, got %lld\n",
+ i, tests[i].gentime,
+ (long long)tests[i].unixtime, (long long)result);
+ errors++;
+ }
+ }
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+ exit(errors);
+}
diff --git a/plugins/sudoers/regress/serialize_list/check_serialize_list.c b/plugins/sudoers/regress/serialize_list/check_serialize_list.c
new file mode 100644
index 0000000..cf559c5
--- /dev/null
+++ b/plugins/sudoers/regress/serialize_list/check_serialize_list.c
@@ -0,0 +1,96 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2022 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include <sudoers.h>
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+static void
+test_serialize_list(int *ntests_out, int *errors_out)
+{
+ int ntests = *ntests_out;
+ int errors = *errors_out;
+ const char *expected = "myvar=a value with spaces,this\\,and\\,that,\\,";
+ struct list_members members = SLIST_HEAD_INITIALIZER(members);
+ struct list_member lm1, lm2, lm3;
+ char *result;
+
+ lm1.value = (char *)"a value with spaces";
+ lm2.value = (char *)"this,and,that";
+ lm3.value = (char *)",";
+ SLIST_INSERT_HEAD(&members, &lm3, entries);
+ SLIST_INSERT_HEAD(&members, &lm2, entries);
+ SLIST_INSERT_HEAD(&members, &lm1, entries);
+
+ ntests++;
+ result = serialize_list("myvar", &members);
+ if (result == NULL) {
+ sudo_warnx("serialize_list returns NULL");
+ ++errors;
+ goto done;
+ }
+ ntests++;
+ if (strcmp(result, expected) != 0) {
+ sudo_warnx("got \"%s\", expected \"%s\"", result, expected);
+ ++errors;
+ goto done;
+ }
+
+done:
+ free(result);
+ *ntests_out = ntests;
+ *errors_out = errors;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch, ntests = 0, errors = 0;
+
+ initprogname(argc > 0 ? argv[0] : "check_serialize_list");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ test_serialize_list(&ntests, &errors);
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ exit(errors);
+}
diff --git a/plugins/sudoers/regress/starttime/check_starttime.c b/plugins/sudoers/regress/starttime/check_starttime.c
new file mode 100644
index 0000000..406d7fb
--- /dev/null
+++ b/plugins/sudoers/regress/starttime/check_starttime.c
@@ -0,0 +1,154 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2017 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sudo_compat.h>
+#include <sudo_util.h>
+#include <sudo_fatal.h>
+#include <timestamp.h>
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+#if defined(sudo_kinfo_proc) || defined(__linux__) || defined(HAVE_STRUCT_PSINFO_PR_TTYDEV) || defined(HAVE_PSTAT_GETPROC) || defined(__gnu_hurd__)
+
+#ifdef __linux__
+static int
+get_now(struct timespec *now)
+{
+ const char *errstr;
+ char buf[1024];
+ time_t seconds;
+ int ret = -1;
+ FILE *fp;
+
+ /* Linux process start time is relative to boot time. */
+ fp = fopen("/proc/stat", "r");
+ if (fp != NULL) {
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (strncmp(buf, "btime ", 6) != 0)
+ continue;
+ buf[strcspn(buf, "\n")] = '\0';
+
+ /* Boot time is in seconds since the epoch. */
+ seconds = sudo_strtonum(buf + 6, 0, TIME_T_MAX, &errstr);
+ if (errstr != NULL)
+ return -1;
+
+ /* Instead of the real time, "now" is relative to boot time. */
+ if (sudo_gettime_real(now) == -1)
+ return -1;
+ now->tv_sec -= seconds;
+ ret = 0;
+ break;
+ }
+ fclose(fp);
+ }
+ return ret;
+}
+#else
+static int
+get_now(struct timespec *now)
+{
+ /* Process start time is relative to wall clock time. */
+ return sudo_gettime_real(now);
+}
+#endif
+
+int
+main(int argc, char *argv[])
+{
+ int ch, ntests = 0, errors = 0;
+ struct timespec now, then, delta;
+ time_t timeoff = 0;
+ pid_t pids[2];
+ char *faketime;
+ unsigned int i;
+
+ initprogname(argc > 0 ? argv[0] : "check_starttime");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (get_now(&now) == -1)
+ sudo_fatal_nodebug("unable to get current time");
+
+ pids[0] = getpid();
+ pids[1] = getppid();
+
+ /* Debian CI pipeline runs tests using faketime. */
+ faketime = getenv("FAKETIME");
+ if (faketime != NULL)
+ timeoff = sudo_strtonum(faketime, TIME_T_MIN, TIME_T_MAX, NULL);
+
+ for (i = 0; i < 2; i++) {
+ ntests++;
+ if (get_starttime(pids[i], &then) == -1) {
+ printf("%s: test %d: unable to get start time for pid %d\n",
+ getprogname(), ntests, (int)pids[i]);
+ errors++;
+ }
+ if (i != 0)
+ continue;
+
+ /* Verify our own process start time, allowing for some drift. */
+ ntests++;
+ sudo_timespecsub(&then, &now, &delta);
+ delta.tv_sec += timeoff;
+ if (delta.tv_sec > 30 || delta.tv_sec < -30) {
+ printf("%s: test %d: unexpected start time for pid %d: %s",
+ getprogname(), ntests, (int)pids[i], ctime(&then.tv_sec));
+ errors++;
+ }
+ }
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
+
+#else
+
+int
+main(int argc, char *argv[])
+{
+ /* get_starttime not supported */
+ return 0;
+}
+
+#endif
diff --git a/plugins/sudoers/regress/sudoers/test1.in b/plugins/sudoers/regress/sudoers/test1.in
new file mode 100644
index 0000000..d87c872
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test1.in
@@ -0,0 +1,12 @@
+#
+# Verify that all command tags are parsed OK.
+# See https://bugzilla.sudo.ws/show_bug.cgi?id=437
+#
+user1 ALL = LOG_INPUT: LOG_OUTPUT: /usr/bin/su -:\
+ ALL = NOLOG_INPUT: NOLOG_OUTPUT: /usr/bin/id
+user2 ALL = NOPASSWD: NOEXEC: SETENV: /usr/bin/vi:\
+ ALL = PASSWD: EXEC: NOSETENV: /usr/bin/echo
+user3 ALL = MAIL: /bin/sh:\
+ ALL = NOMAIL: /usr/bin/id
+user4 ALL = FOLLOW: sudoedit /etc/motd:\
+ ALL = NOFOLLOW: sudoedit /home/*/*
diff --git a/plugins/sudoers/regress/sudoers/test1.json.ok b/plugins/sudoers/regress/sudoers/test1.json.ok
new file mode 100644
index 0000000..9523e4a
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test1.json.ok
@@ -0,0 +1,154 @@
+{
+ "User_Specs": [
+ {
+ "User_List": [
+ { "username": "user1" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "log_input": true },
+ { "log_output": true }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/su -" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user1" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "log_input": false },
+ { "log_output": false }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user2" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "authenticate": false },
+ { "noexec": true },
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/vi" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user2" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "authenticate": true },
+ { "noexec": false },
+ { "setenv": false }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/echo" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user3" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "send_mail": true }
+ ],
+ "Commands": [
+ { "command": "/bin/sh" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user3" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "send_mail": false }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user4" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "sudoedit_follow": true }
+ ],
+ "Commands": [
+ { "command": "sudoedit /etc/motd" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user4" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "sudoedit_follow": false }
+ ],
+ "Commands": [
+ { "command": "sudoedit /home/*/*" }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test1.ldif.ok b/plugins/sudoers/regress/sudoers/test1.ldif.ok
new file mode 100644
index 0000000..7f3fcfc
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test1.ldif.ok
@@ -0,0 +1,88 @@
+dn: cn=user1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user1
+sudoUser: user1
+sudoHost: ALL
+sudoOption: log_input
+sudoOption: log_output
+sudoCommand: /usr/bin/su -
+sudoOrder: 1
+
+dn: cn=user1_1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user1_1
+sudoUser: user1
+sudoHost: ALL
+sudoOption: !log_input
+sudoOption: !log_output
+sudoCommand: /usr/bin/id
+sudoOrder: 2
+
+dn: cn=user2,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user2
+sudoUser: user2
+sudoHost: ALL
+sudoOption: !authenticate
+sudoOption: noexec
+sudoOption: setenv
+sudoCommand: /usr/bin/vi
+sudoOrder: 3
+
+dn: cn=user2_1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user2_1
+sudoUser: user2
+sudoHost: ALL
+sudoOption: authenticate
+sudoOption: !noexec
+sudoOption: !setenv
+sudoCommand: /usr/bin/echo
+sudoOrder: 4
+
+dn: cn=user3,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user3
+sudoUser: user3
+sudoHost: ALL
+sudoOption: mail_all_cmnds
+sudoCommand: /bin/sh
+sudoOrder: 5
+
+dn: cn=user3_1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user3_1
+sudoUser: user3
+sudoHost: ALL
+sudoOption: !mail_all_cmnds
+sudoOption: !mail_always
+sudoOption: !mail_no_perms
+sudoCommand: /usr/bin/id
+sudoOrder: 6
+
+dn: cn=user4,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user4
+sudoUser: user4
+sudoHost: ALL
+sudoOption: sudoedit_follow
+sudoCommand: sudoedit /etc/motd
+sudoOrder: 7
+
+dn: cn=user4_1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user4_1
+sudoUser: user4
+sudoHost: ALL
+sudoOption: !sudoedit_follow
+sudoCommand: sudoedit /home/*/*
+sudoOrder: 8
+
diff --git a/plugins/sudoers/regress/sudoers/test1.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test1.ldif2sudo.ok
new file mode 100644
index 0000000..126fe91
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test1.ldif2sudo.ok
@@ -0,0 +1,13 @@
+# sudoRole user1, user1_1
+user1 ALL = LOG_INPUT: LOG_OUTPUT: /usr/bin/su -, NOLOG_INPUT: NOLOG_OUTPUT:\
+ /usr/bin/id
+
+# sudoRole user2, user2_1
+user2 ALL = SETENV: NOEXEC: NOPASSWD: /usr/bin/vi, NOSETENV: EXEC: PASSWD:\
+ /usr/bin/echo
+
+# sudoRole user3, user3_1
+user3 ALL = MAIL: /bin/sh, NOMAIL: /usr/bin/id
+
+# sudoRole user4, user4_1
+user4 ALL = FOLLOW: sudoedit /etc/motd, NOFOLLOW: sudoedit /home/*/*
diff --git a/plugins/sudoers/regress/sudoers/test1.out.ok b/plugins/sudoers/regress/sudoers/test1.out.ok
new file mode 100644
index 0000000..3d6bab4
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test1.out.ok
@@ -0,0 +1,6 @@
+Parses OK
+
+user1 ALL = LOG_INPUT: LOG_OUTPUT: /usr/bin/su - : ALL = NOLOG_INPUT: NOLOG_OUTPUT: /usr/bin/id
+user2 ALL = SETENV: NOEXEC: NOPASSWD: /usr/bin/vi : ALL = NOSETENV: EXEC: PASSWD: /usr/bin/echo
+user3 ALL = MAIL: /bin/sh : ALL = NOMAIL: /usr/bin/id
+user4 ALL = FOLLOW: sudoedit /etc/motd : ALL = NOFOLLOW: sudoedit /home/*/*
diff --git a/plugins/sudoers/regress/sudoers/test1.toke.ok b/plugins/sudoers/regress/sudoers/test1.toke.ok
new file mode 100644
index 0000000..28c8592
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test1.toke.ok
@@ -0,0 +1,8 @@
+#
+#
+#
+#
+WORD(6) ALL = LOG_INPUT LOG_OUTPUT COMMAND ARG : ALL = NOLOG_INPUT NOLOG_OUTPUT COMMAND
+WORD(6) ALL = NOPASSWD NOEXEC SETENV COMMAND : ALL = PASSWD EXEC NOSETENV COMMAND
+WORD(6) ALL = MAIL COMMAND : ALL = NOMAIL COMMAND
+WORD(6) ALL = FOLLOW COMMAND ARG : ALL = NOFOLLOW COMMAND ARG
diff --git a/plugins/sudoers/regress/sudoers/test10.in b/plugins/sudoers/regress/sudoers/test10.in
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test10.in
@@ -0,0 +1 @@
+
diff --git a/plugins/sudoers/regress/sudoers/test10.json.ok b/plugins/sudoers/regress/sudoers/test10.json.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test10.json.ok
diff --git a/plugins/sudoers/regress/sudoers/test10.ldif.ok b/plugins/sudoers/regress/sudoers/test10.ldif.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test10.ldif.ok
diff --git a/plugins/sudoers/regress/sudoers/test10.out.ok b/plugins/sudoers/regress/sudoers/test10.out.ok
new file mode 100644
index 0000000..5af5c53
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test10.out.ok
@@ -0,0 +1,2 @@
+Parses OK
+
diff --git a/plugins/sudoers/regress/sudoers/test10.toke.ok b/plugins/sudoers/regress/sudoers/test10.toke.ok
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test10.toke.ok
@@ -0,0 +1 @@
+
diff --git a/plugins/sudoers/regress/sudoers/test11.in b/plugins/sudoers/regress/sudoers/test11.in
new file mode 100644
index 0000000..5ffba7b
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test11.in
@@ -0,0 +1 @@
+bogus
diff --git a/plugins/sudoers/regress/sudoers/test11.json.ok b/plugins/sudoers/regress/sudoers/test11.json.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test11.json.ok
diff --git a/plugins/sudoers/regress/sudoers/test11.ldif.ok b/plugins/sudoers/regress/sudoers/test11.ldif.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test11.ldif.ok
diff --git a/plugins/sudoers/regress/sudoers/test11.out.ok b/plugins/sudoers/regress/sudoers/test11.out.ok
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test11.out.ok
@@ -0,0 +1 @@
+
diff --git a/plugins/sudoers/regress/sudoers/test11.toke.ok b/plugins/sudoers/regress/sudoers/test11.toke.ok
new file mode 100644
index 0000000..bfef7a7
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test11.toke.ok
@@ -0,0 +1,2 @@
+WORD(6)
+<*> \ No newline at end of file
diff --git a/plugins/sudoers/regress/sudoers/test12.in b/plugins/sudoers/regress/sudoers/test12.in
new file mode 100644
index 0000000..23bda4a
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test12.in
@@ -0,0 +1 @@
+user ALL = (ALL)
diff --git a/plugins/sudoers/regress/sudoers/test12.json.ok b/plugins/sudoers/regress/sudoers/test12.json.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test12.json.ok
diff --git a/plugins/sudoers/regress/sudoers/test12.ldif.ok b/plugins/sudoers/regress/sudoers/test12.ldif.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test12.ldif.ok
diff --git a/plugins/sudoers/regress/sudoers/test12.out.ok b/plugins/sudoers/regress/sudoers/test12.out.ok
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test12.out.ok
@@ -0,0 +1 @@
+
diff --git a/plugins/sudoers/regress/sudoers/test12.toke.ok b/plugins/sudoers/regress/sudoers/test12.toke.ok
new file mode 100644
index 0000000..0d79959
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test12.toke.ok
@@ -0,0 +1,2 @@
+WORD(6) ALL = ( ALL )
+<*> \ No newline at end of file
diff --git a/plugins/sudoers/regress/sudoers/test13.in b/plugins/sudoers/regress/sudoers/test13.in
new file mode 100644
index 0000000..b8002bc
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test13.in
@@ -0,0 +1 @@
+user ALL = (ALL) \ No newline at end of file
diff --git a/plugins/sudoers/regress/sudoers/test13.json.ok b/plugins/sudoers/regress/sudoers/test13.json.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test13.json.ok
diff --git a/plugins/sudoers/regress/sudoers/test13.ldif.ok b/plugins/sudoers/regress/sudoers/test13.ldif.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test13.ldif.ok
diff --git a/plugins/sudoers/regress/sudoers/test13.out.ok b/plugins/sudoers/regress/sudoers/test13.out.ok
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test13.out.ok
@@ -0,0 +1 @@
+
diff --git a/plugins/sudoers/regress/sudoers/test13.toke.ok b/plugins/sudoers/regress/sudoers/test13.toke.ok
new file mode 100644
index 0000000..0d79959
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test13.toke.ok
@@ -0,0 +1,2 @@
+WORD(6) ALL = ( ALL )
+<*> \ No newline at end of file
diff --git a/plugins/sudoers/regress/sudoers/test14.in b/plugins/sudoers/regress/sudoers/test14.in
new file mode 100644
index 0000000..bdbac68
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test14.in
@@ -0,0 +1,6 @@
+Cmnd_Alias LS = sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1, sha224:d7910e1967342b4605cb73a550944044c631cd3514001900966962ac /bin/ls
+Cmnd_Alias SH = sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM=, sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= /bin/sh
+
+millert ALL = LS, SH, sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw /bin/kill
+
+operator ALL = sha384:knMlCLkJ71K6uRrKo5C1CAvZ5kq+mRpjKDD/RofGosFjiGcYhiYYZORVyiRHgBnu, sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= ALL
diff --git a/plugins/sudoers/regress/sudoers/test14.json.ok b/plugins/sudoers/regress/sudoers/test14.json.ok
new file mode 100644
index 0000000..7e0e3c3
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test14.json.ok
@@ -0,0 +1,62 @@
+{
+ "Command_Aliases": {
+ "LS": [
+ {
+ "command": "/bin/ls",
+ "sha224": "d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1",
+ "sha224": "d7910e1967342b4605cb73a550944044c631cd3514001900966962ac"
+ }
+ ],
+ "SH": [
+ {
+ "command": "/bin/sh",
+ "sha256": "hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM=",
+ "sha256": "1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4="
+ }
+ ]
+ },
+ "User_Specs": [
+ {
+ "User_List": [
+ { "username": "millert" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "cmndalias": "LS" },
+ { "cmndalias": "SH" },
+ {
+ "command": "/bin/kill",
+ "sha512": "srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "operator" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ {
+ "command": "ALL",
+ "sha384": "knMlCLkJ71K6uRrKo5C1CAvZ5kq+mRpjKDD/RofGosFjiGcYhiYYZORVyiRHgBnu",
+ "sha256": "1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4="
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test14.ldif.ok b/plugins/sudoers/regress/sudoers/test14.ldif.ok
new file mode 100644
index 0000000..4d26c5d
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test14.ldif.ok
@@ -0,0 +1,20 @@
+dn: cn=millert,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: millert
+sudoUser: millert
+sudoHost: ALL
+sudoCommand: sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1, sha224:d7910e1967342b4605cb73a550944044c631cd3514001900966962ac /bin/ls
+sudoCommand: sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM=, sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= /bin/sh
+sudoCommand: sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw /bin/kill
+sudoOrder: 1
+
+dn: cn=operator,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: operator
+sudoUser: operator
+sudoHost: ALL
+sudoCommand: sha384:knMlCLkJ71K6uRrKo5C1CAvZ5kq+mRpjKDD/RofGosFjiGcYhiYYZORVyiRHgBnu, sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= ALL
+sudoOrder: 2
+
diff --git a/plugins/sudoers/regress/sudoers/test14.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test14.ldif2sudo.ok
new file mode 100644
index 0000000..c742a95
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test14.ldif2sudo.ok
@@ -0,0 +1,12 @@
+# sudoRole millert
+millert ALL = sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1,\
+ sha224:d7910e1967342b4605cb73a550944044c631cd3514001900966962ac /bin/ls,\
+ sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM=,\
+ sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= /bin/sh,\
+ sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw\
+ /bin/kill
+
+# sudoRole operator
+operator ALL =\
+ sha384:knMlCLkJ71K6uRrKo5C1CAvZ5kq+mRpjKDD/RofGosFjiGcYhiYYZORVyiRHgBnu,\
+ sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= ALL
diff --git a/plugins/sudoers/regress/sudoers/test14.out.ok b/plugins/sudoers/regress/sudoers/test14.out.ok
new file mode 100644
index 0000000..658f74d
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test14.out.ok
@@ -0,0 +1,7 @@
+Parses OK
+
+Cmnd_Alias LS = sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1, sha224:d7910e1967342b4605cb73a550944044c631cd3514001900966962ac /bin/ls
+Cmnd_Alias SH = sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM=, sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= /bin/sh
+
+millert ALL = LS, SH, sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw /bin/kill
+operator ALL = sha384:knMlCLkJ71K6uRrKo5C1CAvZ5kq+mRpjKDD/RofGosFjiGcYhiYYZORVyiRHgBnu, sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= ALL
diff --git a/plugins/sudoers/regress/sudoers/test14.toke.ok b/plugins/sudoers/regress/sudoers/test14.toke.ok
new file mode 100644
index 0000000..edf8099
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test14.toke.ok
@@ -0,0 +1,6 @@
+CMNDALIAS ALIAS = SHA224_TOK : DIGEST , SHA224_TOK : DIGEST COMMAND
+CMNDALIAS ALIAS = SHA256_TOK : DIGEST , SHA256_TOK : DIGEST COMMAND
+
+WORD(6) ALL = ALIAS , ALIAS , SHA512_TOK : DIGEST COMMAND
+
+WORD(6) ALL = SHA384_TOK : DIGEST , SHA256_TOK : DIGEST ALL
diff --git a/plugins/sudoers/regress/sudoers/test15.in b/plugins/sudoers/regress/sudoers/test15.in
new file mode 100644
index 0000000..11bcb13
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test15.in
@@ -0,0 +1,2 @@
+# Test parsing of sudoedit rule
+user ALL = sudoedit /etc/motd
diff --git a/plugins/sudoers/regress/sudoers/test15.json.ok b/plugins/sudoers/regress/sudoers/test15.json.ok
new file mode 100644
index 0000000..ff1795a
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test15.json.ok
@@ -0,0 +1,19 @@
+{
+ "User_Specs": [
+ {
+ "User_List": [
+ { "username": "user" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "sudoedit /etc/motd" }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test15.ldif.ok b/plugins/sudoers/regress/sudoers/test15.ldif.ok
new file mode 100644
index 0000000..ac35ba0
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test15.ldif.ok
@@ -0,0 +1,9 @@
+dn: cn=user,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user
+sudoUser: user
+sudoHost: ALL
+sudoCommand: sudoedit /etc/motd
+sudoOrder: 1
+
diff --git a/plugins/sudoers/regress/sudoers/test15.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test15.ldif2sudo.ok
new file mode 100644
index 0000000..775d59e
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test15.ldif2sudo.ok
@@ -0,0 +1,2 @@
+# sudoRole user
+user ALL = sudoedit /etc/motd
diff --git a/plugins/sudoers/regress/sudoers/test15.out.ok b/plugins/sudoers/regress/sudoers/test15.out.ok
new file mode 100644
index 0000000..b230cf2
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test15.out.ok
@@ -0,0 +1,3 @@
+Parses OK
+
+user ALL = sudoedit /etc/motd
diff --git a/plugins/sudoers/regress/sudoers/test15.toke.ok b/plugins/sudoers/regress/sudoers/test15.toke.ok
new file mode 100644
index 0000000..08bb2b8
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test15.toke.ok
@@ -0,0 +1,2 @@
+#
+WORD(6) ALL = COMMAND ARG
diff --git a/plugins/sudoers/regress/sudoers/test16.in b/plugins/sudoers/regress/sudoers/test16.in
new file mode 100644
index 0000000..d2a79ea
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test16.in
@@ -0,0 +1,3 @@
+# Test parsing of sudoedit rule in a Cmnd_Alias
+Cmnd_Alias EDIT = sudoedit /etc/motd
+user ALL = EDIT
diff --git a/plugins/sudoers/regress/sudoers/test16.json.ok b/plugins/sudoers/regress/sudoers/test16.json.ok
new file mode 100644
index 0000000..7c42654
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test16.json.ok
@@ -0,0 +1,24 @@
+{
+ "Command_Aliases": {
+ "EDIT": [
+ { "command": "sudoedit /etc/motd" }
+ ]
+ },
+ "User_Specs": [
+ {
+ "User_List": [
+ { "username": "user" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "cmndalias": "EDIT" }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test16.ldif.ok b/plugins/sudoers/regress/sudoers/test16.ldif.ok
new file mode 100644
index 0000000..ac35ba0
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test16.ldif.ok
@@ -0,0 +1,9 @@
+dn: cn=user,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user
+sudoUser: user
+sudoHost: ALL
+sudoCommand: sudoedit /etc/motd
+sudoOrder: 1
+
diff --git a/plugins/sudoers/regress/sudoers/test16.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test16.ldif2sudo.ok
new file mode 100644
index 0000000..775d59e
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test16.ldif2sudo.ok
@@ -0,0 +1,2 @@
+# sudoRole user
+user ALL = sudoedit /etc/motd
diff --git a/plugins/sudoers/regress/sudoers/test16.out.ok b/plugins/sudoers/regress/sudoers/test16.out.ok
new file mode 100644
index 0000000..7b8c918
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test16.out.ok
@@ -0,0 +1,5 @@
+Parses OK
+
+Cmnd_Alias EDIT = sudoedit /etc/motd
+
+user ALL = EDIT
diff --git a/plugins/sudoers/regress/sudoers/test16.toke.ok b/plugins/sudoers/regress/sudoers/test16.toke.ok
new file mode 100644
index 0000000..debc4c7
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test16.toke.ok
@@ -0,0 +1,3 @@
+#
+CMNDALIAS ALIAS = COMMAND ARG
+WORD(6) ALL = ALIAS
diff --git a/plugins/sudoers/regress/sudoers/test17.in b/plugins/sudoers/regress/sudoers/test17.in
new file mode 100644
index 0000000..37d066c
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test17.in
@@ -0,0 +1,13 @@
+# Test parsing of command_timeout and TIMEOUT syntax
+Defaults command_timeout=2d8h10m59s
+user0 ALL = TIMEOUT=7D4H10M30S /usr/bin/id, /usr/bin/who, TIMEOUT=0 /bin/ls
+user1 ALL = TIMEOUT=7d4h10m30s /usr/bin/id
+user2 ALL = TIMEOUT=4h10m30s /usr/bin/id
+user3 ALL = TIMEOUT=10m30s /usr/bin/id
+user4 ALL = TIMEOUT=14d /usr/bin/id
+user5 ALL = TIMEOUT=5m /usr/bin/id
+user6 ALL = TIMEOUT=30s /usr/bin/id
+user7 ALL = TIMEOUT=45 /usr/bin/id
+user8 ALL = TIMEOUT=7d4h10m30s /usr/bin/id, TIMEOUT=4h10m30s /usr/bin/id, \
+ TIMEOUT=10m30s /usr/bin/id, TIMEOUT=14d /usr/bin/id, \
+ TIMEOUT=5m /usr/bin/id, TIMEOUT=30s /usr/bin/id
diff --git a/plugins/sudoers/regress/sudoers/test17.json.ok b/plugins/sudoers/regress/sudoers/test17.json.ok
new file mode 100644
index 0000000..2f39a37
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test17.json.ok
@@ -0,0 +1,180 @@
+{
+ "Defaults": [
+ {
+ "Options": [
+ { "command_timeout": "2d8h10m59s" }
+ ]
+ }
+ ],
+ "User_Specs": [
+ {
+ "User_List": [
+ { "username": "user0" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "command_timeout": 619830 }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" },
+ { "command": "/usr/bin/who" },
+ { "command": "/bin/ls" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user1" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "command_timeout": 619830 }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user2" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "command_timeout": 15030 }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user3" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "command_timeout": 630 }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user4" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "command_timeout": 1209600 }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user5" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "command_timeout": 300 }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user6" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "command_timeout": 30 }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user7" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "command_timeout": 45 }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user8" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "command_timeout": 619830 }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" },
+ { "command": "/usr/bin/id" },
+ { "command": "/usr/bin/id" },
+ { "command": "/usr/bin/id" },
+ { "command": "/usr/bin/id" },
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test17.ldif.ok b/plugins/sudoers/regress/sudoers/test17.ldif.ok
new file mode 100644
index 0000000..bdc784c
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test17.ldif.ok
@@ -0,0 +1,104 @@
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: command_timeout=2d8h10m59s
+
+dn: cn=user0,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user0
+sudoUser: user0
+sudoHost: ALL
+sudoOption: command_timeout=619830
+sudoCommand: /usr/bin/id
+sudoCommand: /usr/bin/who
+sudoCommand: /bin/ls
+sudoOrder: 1
+
+dn: cn=user1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user1
+sudoUser: user1
+sudoHost: ALL
+sudoOption: command_timeout=619830
+sudoCommand: /usr/bin/id
+sudoOrder: 2
+
+dn: cn=user2,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user2
+sudoUser: user2
+sudoHost: ALL
+sudoOption: command_timeout=15030
+sudoCommand: /usr/bin/id
+sudoOrder: 3
+
+dn: cn=user3,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user3
+sudoUser: user3
+sudoHost: ALL
+sudoOption: command_timeout=630
+sudoCommand: /usr/bin/id
+sudoOrder: 4
+
+dn: cn=user4,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user4
+sudoUser: user4
+sudoHost: ALL
+sudoOption: command_timeout=1209600
+sudoCommand: /usr/bin/id
+sudoOrder: 5
+
+dn: cn=user5,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user5
+sudoUser: user5
+sudoHost: ALL
+sudoOption: command_timeout=300
+sudoCommand: /usr/bin/id
+sudoOrder: 6
+
+dn: cn=user6,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user6
+sudoUser: user6
+sudoHost: ALL
+sudoOption: command_timeout=30
+sudoCommand: /usr/bin/id
+sudoOrder: 7
+
+dn: cn=user7,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user7
+sudoUser: user7
+sudoHost: ALL
+sudoOption: command_timeout=45
+sudoCommand: /usr/bin/id
+sudoOrder: 8
+
+dn: cn=user8,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user8
+sudoUser: user8
+sudoHost: ALL
+sudoOption: command_timeout=619830
+sudoCommand: /usr/bin/id
+sudoCommand: /usr/bin/id
+sudoCommand: /usr/bin/id
+sudoCommand: /usr/bin/id
+sudoCommand: /usr/bin/id
+sudoCommand: /usr/bin/id
+sudoOrder: 9
+
diff --git a/plugins/sudoers/regress/sudoers/test17.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test17.ldif2sudo.ok
new file mode 100644
index 0000000..608f52f
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test17.ldif2sudo.ok
@@ -0,0 +1,29 @@
+Defaults command_timeout=2d8h10m59s
+
+# sudoRole user0
+user0 ALL = TIMEOUT=619830 /usr/bin/id, /usr/bin/who, /bin/ls
+
+# sudoRole user1
+user1 ALL = TIMEOUT=619830 /usr/bin/id
+
+# sudoRole user2
+user2 ALL = TIMEOUT=15030 /usr/bin/id
+
+# sudoRole user3
+user3 ALL = TIMEOUT=630 /usr/bin/id
+
+# sudoRole user4
+user4 ALL = TIMEOUT=1209600 /usr/bin/id
+
+# sudoRole user5
+user5 ALL = TIMEOUT=300 /usr/bin/id
+
+# sudoRole user6
+user6 ALL = TIMEOUT=30 /usr/bin/id
+
+# sudoRole user7
+user7 ALL = TIMEOUT=45 /usr/bin/id
+
+# sudoRole user8
+user8 ALL = TIMEOUT=619830 /usr/bin/id, /usr/bin/id, /usr/bin/id, /usr/bin/id,\
+ /usr/bin/id, /usr/bin/id
diff --git a/plugins/sudoers/regress/sudoers/test17.out.ok b/plugins/sudoers/regress/sudoers/test17.out.ok
new file mode 100644
index 0000000..f0c8086
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test17.out.ok
@@ -0,0 +1,13 @@
+Parses OK
+
+Defaults command_timeout=2d8h10m59s
+
+user0 ALL = TIMEOUT=619830 /usr/bin/id, /usr/bin/who, /bin/ls
+user1 ALL = TIMEOUT=619830 /usr/bin/id
+user2 ALL = TIMEOUT=15030 /usr/bin/id
+user3 ALL = TIMEOUT=630 /usr/bin/id
+user4 ALL = TIMEOUT=1209600 /usr/bin/id
+user5 ALL = TIMEOUT=300 /usr/bin/id
+user6 ALL = TIMEOUT=30 /usr/bin/id
+user7 ALL = TIMEOUT=45 /usr/bin/id
+user8 ALL = TIMEOUT=619830 /usr/bin/id, TIMEOUT=15030 /usr/bin/id, TIMEOUT=630 /usr/bin/id, TIMEOUT=1209600 /usr/bin/id, TIMEOUT=300 /usr/bin/id, TIMEOUT=30 /usr/bin/id
diff --git a/plugins/sudoers/regress/sudoers/test17.toke.ok b/plugins/sudoers/regress/sudoers/test17.toke.ok
new file mode 100644
index 0000000..d0a82ca
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test17.toke.ok
@@ -0,0 +1,11 @@
+#
+DEFAULTS DEFVAR = WORD(2)
+WORD(6) ALL = CMND_TIMEOUT = WORD(6) COMMAND , COMMAND , CMND_TIMEOUT = WORD(6) COMMAND
+WORD(6) ALL = CMND_TIMEOUT = WORD(6) COMMAND
+WORD(6) ALL = CMND_TIMEOUT = WORD(6) COMMAND
+WORD(6) ALL = CMND_TIMEOUT = WORD(6) COMMAND
+WORD(6) ALL = CMND_TIMEOUT = WORD(6) COMMAND
+WORD(6) ALL = CMND_TIMEOUT = WORD(6) COMMAND
+WORD(6) ALL = CMND_TIMEOUT = WORD(6) COMMAND
+WORD(6) ALL = CMND_TIMEOUT = WORD(6) COMMAND
+WORD(6) ALL = CMND_TIMEOUT = WORD(6) COMMAND , CMND_TIMEOUT = WORD(6) COMMAND , CMND_TIMEOUT = WORD(6) COMMAND , CMND_TIMEOUT = WORD(6) COMMAND , CMND_TIMEOUT = WORD(6) COMMAND , CMND_TIMEOUT = WORD(6) COMMAND
diff --git a/plugins/sudoers/regress/sudoers/test18.in b/plugins/sudoers/regress/sudoers/test18.in
new file mode 100644
index 0000000..8d94ec7
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test18.in
@@ -0,0 +1,8 @@
+# Test command_timeout and TIMEOUT syntax errors
+Defaults command_timeout=2d8h10m59ss
+Defaults:root command_timeout=15f
+user0 ALL = TIMEOUT=7dd4h10m30s /usr/bin/id, /usr/bin/who, TIMEOUT=0 /bin/ls
+user1 ALL = TIMEOUT=7d4h10mm30s /usr/bin/id
+user2 ALL = TIMEOUT=4hg10m30s /usr/bin/id
+user3 ALL = TIMEOUT=10m30ss /usr/bin/id
+user4 ALL = TIMEOUT=14g /usr/bin/id
diff --git a/plugins/sudoers/regress/sudoers/test18.json.ok b/plugins/sudoers/regress/sudoers/test18.json.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test18.json.ok
diff --git a/plugins/sudoers/regress/sudoers/test18.ldif.ok b/plugins/sudoers/regress/sudoers/test18.ldif.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test18.ldif.ok
diff --git a/plugins/sudoers/regress/sudoers/test18.out.ok b/plugins/sudoers/regress/sudoers/test18.out.ok
new file mode 100644
index 0000000..03f9ef6
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test18.out.ok
@@ -0,0 +1,3 @@
+
+Defaults command_timeout=2d8h10m59ss
+Defaults:root command_timeout=15f
diff --git a/plugins/sudoers/regress/sudoers/test18.toke.ok b/plugins/sudoers/regress/sudoers/test18.toke.ok
new file mode 100644
index 0000000..7c800a8
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test18.toke.ok
@@ -0,0 +1,10 @@
+#
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS_USER WORD(6) DEFVAR = WORD(2)
+WORD(6) ALL = CMND_TIMEOUT = WORD(6) <*> COMMAND , COMMAND , CMND_TIMEOUT = WORD(6) COMMAND
+WORD(6) ALL = CMND_TIMEOUT = WORD(6) <*> COMMAND
+WORD(6) ALL = CMND_TIMEOUT = WORD(6) <*> COMMAND
+WORD(6) ALL = CMND_TIMEOUT = WORD(6) <*> COMMAND
+WORD(6) ALL = CMND_TIMEOUT = WORD(6) <*> COMMAND
+sudoers:2:26: value "2d8h10m59ss" is invalid for option "command_timeout"
+sudoers:3:31: value "15f" is invalid for option "command_timeout"
diff --git a/plugins/sudoers/regress/sudoers/test19.in b/plugins/sudoers/regress/sudoers/test19.in
new file mode 100644
index 0000000..5f637a7
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test19.in
@@ -0,0 +1,12 @@
+# Test parsing of NOTBEFORE and NOTAFTER syntax
+# Local time zone parsing is checked in visudo/test10.sh
+user0 ALL = NOTBEFORE=20170214083000Z NOTAFTER=20170301083000Z /usr/bin/id, /bin/ls
+user1 ALL = NOTBEFORE=201702140830Z /usr/bin/id, NOTAFTER=20170301083000Z /bin/ls
+user2 ALL = NOTBEFORE=201702140830.3Z /usr/bin/id
+user3 ALL = NOTBEFORE=2017021408Z /usr/bin/id
+user4 ALL = NOTBEFORE=2017021408.4Z /usr/bin/id
+user5 ALL = NOTBEFORE=20170214083000.5Z /usr/bin/id
+user6 ALL = NOTBEFORE=20170214083000\,5Z /usr/bin/id
+user7 ALL = NOTBEFORE=20170214033000-0500 /usr/bin/id
+user8 ALL = NOTBEFORE=20170214033000.0-0500 /usr/bin/id
+user9 ALL = NOTBEFORE=20170214033000\,0-0500 /usr/bin/id
diff --git a/plugins/sudoers/regress/sudoers/test19.json.ok b/plugins/sudoers/regress/sudoers/test19.json.ok
new file mode 100644
index 0000000..c9a1bfd
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test19.json.ok
@@ -0,0 +1,187 @@
+{
+ "User_Specs": [
+ {
+ "User_List": [
+ { "username": "user0" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "notbefore": "20170214083000Z" },
+ { "notafter": "20170301083000Z" }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" },
+ { "command": "/bin/ls" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user1" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "notbefore": "20170214083000Z" }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" },
+ { "command": "/bin/ls" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user2" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "notbefore": "20170214083018Z" }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user3" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "notbefore": "20170214080000Z" }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user4" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "notbefore": "20170214082400Z" }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user5" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "notbefore": "20170214083000Z" }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user6" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "notbefore": "20170214083000Z" }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user7" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "notbefore": "20170214083000Z" }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user8" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "notbefore": "20170214083000Z" }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user9" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "notbefore": "20170214083000Z" }
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test19.ldif.ok b/plugins/sudoers/regress/sudoers/test19.ldif.ok
new file mode 100644
index 0000000..362aa9e
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test19.ldif.ok
@@ -0,0 +1,103 @@
+dn: cn=user0,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user0
+sudoUser: user0
+sudoHost: ALL
+sudoNotBefore: 20170214083000Z
+sudoNotAfter: 20170301083000Z
+sudoCommand: /usr/bin/id
+sudoCommand: /bin/ls
+sudoOrder: 1
+
+dn: cn=user1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user1
+sudoUser: user1
+sudoHost: ALL
+sudoNotBefore: 20170214083000Z
+sudoCommand: /usr/bin/id
+sudoCommand: /bin/ls
+sudoOrder: 2
+
+dn: cn=user2,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user2
+sudoUser: user2
+sudoHost: ALL
+sudoNotBefore: 20170214083018Z
+sudoCommand: /usr/bin/id
+sudoOrder: 3
+
+dn: cn=user3,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user3
+sudoUser: user3
+sudoHost: ALL
+sudoNotBefore: 20170214080000Z
+sudoCommand: /usr/bin/id
+sudoOrder: 4
+
+dn: cn=user4,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user4
+sudoUser: user4
+sudoHost: ALL
+sudoNotBefore: 20170214082400Z
+sudoCommand: /usr/bin/id
+sudoOrder: 5
+
+dn: cn=user5,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user5
+sudoUser: user5
+sudoHost: ALL
+sudoNotBefore: 20170214083000Z
+sudoCommand: /usr/bin/id
+sudoOrder: 6
+
+dn: cn=user6,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user6
+sudoUser: user6
+sudoHost: ALL
+sudoNotBefore: 20170214083000Z
+sudoCommand: /usr/bin/id
+sudoOrder: 7
+
+dn: cn=user7,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user7
+sudoUser: user7
+sudoHost: ALL
+sudoNotBefore: 20170214083000Z
+sudoCommand: /usr/bin/id
+sudoOrder: 8
+
+dn: cn=user8,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user8
+sudoUser: user8
+sudoHost: ALL
+sudoNotBefore: 20170214083000Z
+sudoCommand: /usr/bin/id
+sudoOrder: 9
+
+dn: cn=user9,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user9
+sudoUser: user9
+sudoHost: ALL
+sudoNotBefore: 20170214083000Z
+sudoCommand: /usr/bin/id
+sudoOrder: 10
+
diff --git a/plugins/sudoers/regress/sudoers/test19.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test19.ldif2sudo.ok
new file mode 100644
index 0000000..1aef1bc
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test19.ldif2sudo.ok
@@ -0,0 +1,30 @@
+# sudoRole user0
+user0 ALL = NOTBEFORE=20170214083000Z NOTAFTER=20170301083000Z /usr/bin/id,\
+ /bin/ls
+
+# sudoRole user1
+user1 ALL = NOTBEFORE=20170214083000Z /usr/bin/id, /bin/ls
+
+# sudoRole user2
+user2 ALL = NOTBEFORE=20170214083018Z /usr/bin/id
+
+# sudoRole user3
+user3 ALL = NOTBEFORE=20170214080000Z /usr/bin/id
+
+# sudoRole user4
+user4 ALL = NOTBEFORE=20170214082400Z /usr/bin/id
+
+# sudoRole user5
+user5 ALL = NOTBEFORE=20170214083000Z /usr/bin/id
+
+# sudoRole user6
+user6 ALL = NOTBEFORE=20170214083000Z /usr/bin/id
+
+# sudoRole user7
+user7 ALL = NOTBEFORE=20170214083000Z /usr/bin/id
+
+# sudoRole user8
+user8 ALL = NOTBEFORE=20170214083000Z /usr/bin/id
+
+# sudoRole user9
+user9 ALL = NOTBEFORE=20170214083000Z /usr/bin/id
diff --git a/plugins/sudoers/regress/sudoers/test19.out.ok b/plugins/sudoers/regress/sudoers/test19.out.ok
new file mode 100644
index 0000000..dacfefd
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test19.out.ok
@@ -0,0 +1,12 @@
+Parses OK
+
+user0 ALL = NOTBEFORE=20170214083000Z NOTAFTER=20170301083000Z /usr/bin/id, /bin/ls
+user1 ALL = NOTBEFORE=20170214083000Z /usr/bin/id, NOTAFTER=20170301083000Z /bin/ls
+user2 ALL = NOTBEFORE=20170214083018Z /usr/bin/id
+user3 ALL = NOTBEFORE=20170214080000Z /usr/bin/id
+user4 ALL = NOTBEFORE=20170214082400Z /usr/bin/id
+user5 ALL = NOTBEFORE=20170214083000Z /usr/bin/id
+user6 ALL = NOTBEFORE=20170214083000Z /usr/bin/id
+user7 ALL = NOTBEFORE=20170214083000Z /usr/bin/id
+user8 ALL = NOTBEFORE=20170214083000Z /usr/bin/id
+user9 ALL = NOTBEFORE=20170214083000Z /usr/bin/id
diff --git a/plugins/sudoers/regress/sudoers/test19.toke.ok b/plugins/sudoers/regress/sudoers/test19.toke.ok
new file mode 100644
index 0000000..04461d9
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test19.toke.ok
@@ -0,0 +1,12 @@
+#
+#
+WORD(6) ALL = NOTBEFORE = WORD(6) NOTAFTER = WORD(6) COMMAND , COMMAND
+WORD(6) ALL = NOTBEFORE = WORD(6) COMMAND , NOTAFTER = WORD(6) COMMAND
+WORD(6) ALL = NOTBEFORE = WORD(6) COMMAND
+WORD(6) ALL = NOTBEFORE = WORD(6) COMMAND
+WORD(6) ALL = NOTBEFORE = WORD(6) COMMAND
+WORD(6) ALL = NOTBEFORE = WORD(6) COMMAND
+WORD(6) ALL = NOTBEFORE = WORD(6) COMMAND
+WORD(6) ALL = NOTBEFORE = WORD(6) COMMAND
+WORD(6) ALL = NOTBEFORE = WORD(6) COMMAND
+WORD(6) ALL = NOTBEFORE = WORD(6) COMMAND
diff --git a/plugins/sudoers/regress/sudoers/test2.in b/plugins/sudoers/regress/sudoers/test2.in
new file mode 100644
index 0000000..b81ae54
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test2.in
@@ -0,0 +1,63 @@
+# Check quoted user name in User_Alias
+User_Alias UA1 = "foo"
+User_Alias UA2 = "foo.bar"
+User_Alias UA3 = "foo\""
+User_Alias UA4 = "foo:bar"
+User_Alias UA5 = "foo:bar\""
+
+# Check quoted group name in User_Alias
+User_Alias UA6 = "%baz"
+User_Alias UA7 = "%baz.biz"
+
+# Check quoted non-Unix group name in User_Alias
+User_Alias UA8 = "%:C/non UNIX 0 c"
+User_Alias UA9 = "%:C/non\'UNIX\'1 c"
+User_Alias UA10 = "%:C/non\"UNIX\"0 c"
+User_Alias UA11 = "%:C/non_UNIX_0 c"
+User_Alias UA12 = "%:C/non\'UNIX_3 c"
+
+# Check quoted user name in Runas_Alias
+Runas_Alias RA1 = "foo"
+Runas_Alias RA2 = "foo\""
+Runas_Alias RA3 = "foo:bar"
+Runas_Alias RA4 = "foo:bar\""
+
+# Check quoted host name in Defaults
+Defaults@"somehost" set_home
+Defaults@"quoted\"" set_home
+
+# Check quoted user name in Defaults
+Defaults:"you" set_home
+Defaults:"us\"" set_home
+Defaults:"%them" set_home
+Defaults:"%: non UNIX 0 c" set_home
+Defaults:"+net" set_home
+
+# Check quoted runas name in Defaults
+Defaults>"someone" set_home
+Defaults>"some one" set_home
+
+# Check quoted command in Defaults
+# XXX - not currently supported
+#Defaults!"/bin/ls -l" set_home
+#Defaults!"/bin/ls -l \"foo\"" set_home
+
+# Check quoted user, runas and host name in Cmnd_Spec
+"foo" "hosta" = ("root") ALL
+"foo.bar" "hostb" = ("root") ALL
+"foo\"" "hostc" = ("root") ALL
+"foo:bar" "hostd" = ("root") ALL
+"foo:bar\"" "hoste" = ("root") ALL
+
+# Check quoted group/netgroup name in Cmnd_Spec
+"%baz" "hosta" = ("root") ALL
+"%baz.biz" "hostb" = ("root") ALL
+"%:C/non UNIX 0 c" "hostc" = ("root") ALL
+"%:C/non\'UNIX\'1 c" "hostd" = ("root") ALL
+"%:C/non\"UNIX\"0 c" "hoste" = ("root") ALL
+"%:C/non_UNIX_0 c" "hostf" = ("root") ALL
+"%:C/non\'UNIX_3 c" "hostg" = ("root") ALL
+"+netgr" "hosth" = ("root") ALL
+
+# Check that quotes don't need escaping in command and args
+user ALL = /bin/ls "", /bin/echo " ", /bin/foo"bar ""
diff --git a/plugins/sudoers/regress/sudoers/test2.json.ok b/plugins/sudoers/regress/sudoers/test2.json.ok
new file mode 100644
index 0000000..46e4c48
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test2.json.ok
@@ -0,0 +1,420 @@
+{
+ "Defaults": [
+ {
+ "Binding": [
+ { "hostname": "somehost" }
+ ],
+ "Options": [
+ { "set_home": true }
+ ]
+ },
+ {
+ "Binding": [
+ { "hostname": "quoted\"" }
+ ],
+ "Options": [
+ { "set_home": true }
+ ]
+ },
+ {
+ "Binding": [
+ { "username": "you" }
+ ],
+ "Options": [
+ { "set_home": true }
+ ]
+ },
+ {
+ "Binding": [
+ { "username": "us\"" }
+ ],
+ "Options": [
+ { "set_home": true }
+ ]
+ },
+ {
+ "Binding": [
+ { "usergroup": "them" }
+ ],
+ "Options": [
+ { "set_home": true }
+ ]
+ },
+ {
+ "Binding": [
+ { "nonunixgroup": " non UNIX 0 c" }
+ ],
+ "Options": [
+ { "set_home": true }
+ ]
+ },
+ {
+ "Binding": [
+ { "netgroup": "net" }
+ ],
+ "Options": [
+ { "set_home": true }
+ ]
+ },
+ {
+ "Binding": [
+ { "username": "someone" }
+ ],
+ "Options": [
+ { "set_home": true }
+ ]
+ },
+ {
+ "Binding": [
+ { "username": "some one" }
+ ],
+ "Options": [
+ { "set_home": true }
+ ]
+ }
+ ],
+ "User_Aliases": {
+ "UA1": [
+ { "username": "foo" }
+ ],
+ "UA10": [
+ { "nonunixgroup": "C/non\"UNIX\"0 c" }
+ ],
+ "UA11": [
+ { "nonunixgroup": "C/non_UNIX_0 c" }
+ ],
+ "UA12": [
+ { "nonunixgroup": "C/non\\'UNIX_3 c" }
+ ],
+ "UA2": [
+ { "username": "foo.bar" }
+ ],
+ "UA3": [
+ { "username": "foo\"" }
+ ],
+ "UA4": [
+ { "username": "foo:bar" }
+ ],
+ "UA5": [
+ { "username": "foo:bar\"" }
+ ],
+ "UA6": [
+ { "usergroup": "baz" }
+ ],
+ "UA7": [
+ { "usergroup": "baz.biz" }
+ ],
+ "UA8": [
+ { "nonunixgroup": "C/non UNIX 0 c" }
+ ],
+ "UA9": [
+ { "nonunixgroup": "C/non\\'UNIX\\'1 c" }
+ ]
+ },
+ "Runas_Aliases": {
+ "RA1": [
+ { "username": "foo" }
+ ],
+ "RA2": [
+ { "username": "foo\"" }
+ ],
+ "RA3": [
+ { "username": "foo:bar" }
+ ],
+ "RA4": [
+ { "username": "foo:bar\"" }
+ ]
+ },
+ "User_Specs": [
+ {
+ "User_List": [
+ { "username": "foo" }
+ ],
+ "Host_List": [
+ { "hostname": "hosta" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "root" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "foo.bar" }
+ ],
+ "Host_List": [
+ { "hostname": "hostb" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "root" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "foo\"" }
+ ],
+ "Host_List": [
+ { "hostname": "hostc" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "root" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "foo:bar" }
+ ],
+ "Host_List": [
+ { "hostname": "hostd" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "root" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "foo:bar\"" }
+ ],
+ "Host_List": [
+ { "hostname": "hoste" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "root" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "usergroup": "baz" }
+ ],
+ "Host_List": [
+ { "hostname": "hosta" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "root" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "usergroup": "baz.biz" }
+ ],
+ "Host_List": [
+ { "hostname": "hostb" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "root" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "nonunixgroup": "C/non UNIX 0 c" }
+ ],
+ "Host_List": [
+ { "hostname": "hostc" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "root" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "nonunixgroup": "C/non\\'UNIX\\'1 c" }
+ ],
+ "Host_List": [
+ { "hostname": "hostd" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "root" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "nonunixgroup": "C/non\"UNIX\"0 c" }
+ ],
+ "Host_List": [
+ { "hostname": "hoste" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "root" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "nonunixgroup": "C/non_UNIX_0 c" }
+ ],
+ "Host_List": [
+ { "hostname": "hostf" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "root" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "nonunixgroup": "C/non\\'UNIX_3 c" }
+ ],
+ "Host_List": [
+ { "hostname": "hostg" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "root" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "netgroup": "netgr" }
+ ],
+ "Host_List": [
+ { "hostname": "hosth" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "root" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "/bin/ls \"\"" },
+ { "command": "/bin/echo \" \"" },
+ { "command": "/bin/foo\"bar \"\"" }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test2.ldif.ok b/plugins/sudoers/regress/sudoers/test2.ldif.ok
new file mode 100644
index 0000000..31d708f
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test2.ldif.ok
@@ -0,0 +1,168 @@
+# Unable to translate stdin:26:29:
+# Defaults@somehost set_home
+
+# Unable to translate stdin:27:29:
+# Defaults@quoted\" set_home
+
+# Unable to translate stdin:30:24:
+# Defaults:you set_home
+
+# Unable to translate stdin:31:25:
+# Defaults:us\" set_home
+
+# Unable to translate stdin:32:26:
+# Defaults:%them set_home
+
+# Unable to translate stdin:33:36:
+# Defaults:"%: non UNIX 0 c" set_home
+
+# Unable to translate stdin:34:25:
+# Defaults:+net set_home
+
+# Unable to translate stdin:37:28:
+# Defaults>someone set_home
+
+# Unable to translate stdin:38:29:
+# Defaults>"some one" set_home
+
+dn: cn=foo,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: foo
+sudoUser: foo
+sudoHost: hosta
+sudoRunAsUser: root
+sudoCommand: ALL
+sudoOrder: 1
+
+dn: cn=foo.bar,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: foo.bar
+sudoUser: foo.bar
+sudoHost: hostb
+sudoRunAsUser: root
+sudoCommand: ALL
+sudoOrder: 2
+
+dn: cn=foo\",ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: foo\"
+sudoUser: foo"
+sudoHost: hostc
+sudoRunAsUser: root
+sudoCommand: ALL
+sudoOrder: 3
+
+dn: cn=foo:bar,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: foo:bar
+sudoUser: foo:bar
+sudoHost: hostd
+sudoRunAsUser: root
+sudoCommand: ALL
+sudoOrder: 4
+
+dn: cn=foo:bar\",ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: foo:bar\"
+sudoUser: foo:bar"
+sudoHost: hoste
+sudoRunAsUser: root
+sudoCommand: ALL
+sudoOrder: 5
+
+dn: cn=%baz,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %baz
+sudoUser: %baz
+sudoHost: hosta
+sudoRunAsUser: root
+sudoCommand: ALL
+sudoOrder: 6
+
+dn: cn=%baz.biz,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %baz.biz
+sudoUser: %baz.biz
+sudoHost: hostb
+sudoRunAsUser: root
+sudoCommand: ALL
+sudoOrder: 7
+
+dn: cn=%:C/non UNIX 0 c,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %:C/non UNIX 0 c
+sudoUser: %:C/non UNIX 0 c
+sudoHost: hostc
+sudoRunAsUser: root
+sudoCommand: ALL
+sudoOrder: 8
+
+dn: cn=%:C/non\\'UNIX\\'1 c,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %:C/non\\'UNIX\\'1 c
+sudoUser: %:C/non\'UNIX\'1 c
+sudoHost: hostd
+sudoRunAsUser: root
+sudoCommand: ALL
+sudoOrder: 9
+
+dn: cn=%:C/non\"UNIX\"0 c,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %:C/non\"UNIX\"0 c
+sudoUser: %:C/non"UNIX"0 c
+sudoHost: hoste
+sudoRunAsUser: root
+sudoCommand: ALL
+sudoOrder: 10
+
+dn: cn=%:C/non_UNIX_0 c,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %:C/non_UNIX_0 c
+sudoUser: %:C/non_UNIX_0 c
+sudoHost: hostf
+sudoRunAsUser: root
+sudoCommand: ALL
+sudoOrder: 11
+
+dn: cn=%:C/non\\'UNIX_3 c,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %:C/non\\'UNIX_3 c
+sudoUser: %:C/non\'UNIX_3 c
+sudoHost: hostg
+sudoRunAsUser: root
+sudoCommand: ALL
+sudoOrder: 12
+
+dn: cn=\+netgr,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: \+netgr
+sudoUser: +netgr
+sudoHost: hosth
+sudoRunAsUser: root
+sudoCommand: ALL
+sudoOrder: 13
+
+dn: cn=user,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user
+sudoUser: user
+sudoHost: ALL
+sudoCommand: /bin/ls ""
+sudoCommand: /bin/echo " "
+sudoCommand: /bin/foo"bar ""
+sudoOrder: 14
+
diff --git a/plugins/sudoers/regress/sudoers/test2.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test2.ldif2sudo.ok
new file mode 100644
index 0000000..0277437
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test2.ldif2sudo.ok
@@ -0,0 +1,41 @@
+# sudoRole foo
+foo hosta = (root) ALL
+
+# sudoRole foo.bar
+foo.bar hostb = (root) ALL
+
+# sudoRole foo"
+foo\" hostc = (root) ALL
+
+# sudoRole foo:bar
+foo\:bar hostd = (root) ALL
+
+# sudoRole foo:bar"
+foo\:bar\" hoste = (root) ALL
+
+# sudoRole %baz
+%baz hosta = (root) ALL
+
+# sudoRole %baz.biz
+%baz.biz hostb = (root) ALL
+
+# sudoRole %:C/non UNIX 0 c
+"%:C/non UNIX 0 c" hostc = (root) ALL
+
+# sudoRole %:C/non\'UNIX\'1 c
+"%:C/non\'UNIX\'1 c" hostd = (root) ALL
+
+# sudoRole %:C/non"UNIX"0 c
+"%:C/non\"UNIX\"0 c" hoste = (root) ALL
+
+# sudoRole %:C/non_UNIX_0 c
+"%:C/non_UNIX_0 c" hostf = (root) ALL
+
+# sudoRole %:C/non\'UNIX_3 c
+"%:C/non\'UNIX_3 c" hostg = (root) ALL
+
+# sudoRole +netgr
++netgr hosth = (root) ALL
+
+# sudoRole user
+user ALL = /bin/ls "", /bin/echo " ", /bin/foo"bar ""
diff --git a/plugins/sudoers/regress/sudoers/test2.out.ok b/plugins/sudoers/regress/sudoers/test2.out.ok
new file mode 100644
index 0000000..99171b9
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test2.out.ok
@@ -0,0 +1,43 @@
+Parses OK
+
+Defaults@somehost set_home
+Defaults@quoted\" set_home
+Defaults:you set_home
+Defaults:us\" set_home
+Defaults:%them set_home
+Defaults:"%: non UNIX 0 c" set_home
+Defaults:+net set_home
+Defaults>someone set_home
+Defaults>"some one" set_home
+
+Runas_Alias RA1 = foo
+Runas_Alias RA2 = foo\"
+Runas_Alias RA3 = foo\:bar
+Runas_Alias RA4 = foo\:bar\"
+User_Alias UA1 = foo
+User_Alias UA10 = "%:C/non\"UNIX\"0 c"
+User_Alias UA11 = "%:C/non_UNIX_0 c"
+User_Alias UA12 = "%:C/non\'UNIX_3 c"
+User_Alias UA2 = foo.bar
+User_Alias UA3 = foo\"
+User_Alias UA4 = foo\:bar
+User_Alias UA5 = foo\:bar\"
+User_Alias UA6 = %baz
+User_Alias UA7 = %baz.biz
+User_Alias UA8 = "%:C/non UNIX 0 c"
+User_Alias UA9 = "%:C/non\'UNIX\'1 c"
+
+foo hosta = (root) ALL
+foo.bar hostb = (root) ALL
+foo\" hostc = (root) ALL
+foo\:bar hostd = (root) ALL
+foo\:bar\" hoste = (root) ALL
+%baz hosta = (root) ALL
+%baz.biz hostb = (root) ALL
+"%:C/non UNIX 0 c" hostc = (root) ALL
+"%:C/non\'UNIX\'1 c" hostd = (root) ALL
+"%:C/non\"UNIX\"0 c" hoste = (root) ALL
+"%:C/non_UNIX_0 c" hostf = (root) ALL
+"%:C/non\'UNIX_3 c" hostg = (root) ALL
++netgr hosth = (root) ALL
+user ALL = /bin/ls "", /bin/echo " ", /bin/foo"bar ""
diff --git a/plugins/sudoers/regress/sudoers/test2.toke.ok b/plugins/sudoers/regress/sudoers/test2.toke.ok
new file mode 100644
index 0000000..4c4b88d
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test2.toke.ok
@@ -0,0 +1,63 @@
+#
+USERALIAS ALIAS = BEGINSTR STRBODY ENDSTR WORD(4)
+USERALIAS ALIAS = BEGINSTR STRBODY ENDSTR WORD(4)
+USERALIAS ALIAS = BEGINSTR STRBODY ENDSTR WORD(4)
+USERALIAS ALIAS = BEGINSTR STRBODY ENDSTR WORD(4)
+USERALIAS ALIAS = BEGINSTR STRBODY ENDSTR WORD(4)
+
+#
+USERALIAS ALIAS = BEGINSTR STRBODY ENDSTR USERGROUP
+USERALIAS ALIAS = BEGINSTR STRBODY ENDSTR USERGROUP
+
+#
+USERALIAS ALIAS = BEGINSTR STRBODY ENDSTR USERGROUP
+USERALIAS ALIAS = BEGINSTR STRBODY BACKSLASH STRBODY BACKSLASH STRBODY ENDSTR USERGROUP
+USERALIAS ALIAS = BEGINSTR STRBODY ENDSTR USERGROUP
+USERALIAS ALIAS = BEGINSTR STRBODY ENDSTR USERGROUP
+USERALIAS ALIAS = BEGINSTR STRBODY BACKSLASH STRBODY ENDSTR USERGROUP
+
+#
+RUNASALIAS ALIAS = BEGINSTR STRBODY ENDSTR WORD(4)
+RUNASALIAS ALIAS = BEGINSTR STRBODY ENDSTR WORD(4)
+RUNASALIAS ALIAS = BEGINSTR STRBODY ENDSTR WORD(4)
+RUNASALIAS ALIAS = BEGINSTR STRBODY ENDSTR WORD(4)
+
+#
+DEFAULTS_HOST BEGINSTR STRBODY ENDSTR WORD(4) DEFVAR
+DEFAULTS_HOST BEGINSTR STRBODY ENDSTR WORD(4) DEFVAR
+
+#
+DEFAULTS_USER BEGINSTR STRBODY ENDSTR WORD(4) DEFVAR
+DEFAULTS_USER BEGINSTR STRBODY ENDSTR WORD(4) DEFVAR
+DEFAULTS_USER BEGINSTR STRBODY ENDSTR USERGROUP DEFVAR
+DEFAULTS_USER BEGINSTR STRBODY ENDSTR USERGROUP DEFVAR
+DEFAULTS_USER BEGINSTR STRBODY ENDSTR NETGROUP DEFVAR
+
+#
+DEFAULTS_RUNAS BEGINSTR STRBODY ENDSTR WORD(4) DEFVAR
+DEFAULTS_RUNAS BEGINSTR STRBODY ENDSTR WORD(4) DEFVAR
+
+#
+#
+#
+#
+
+#
+BEGINSTR STRBODY ENDSTR WORD(4) BEGINSTR STRBODY ENDSTR WORD(4) = ( BEGINSTR STRBODY ENDSTR WORD(4) ) ALL
+BEGINSTR STRBODY ENDSTR WORD(4) BEGINSTR STRBODY ENDSTR WORD(4) = ( BEGINSTR STRBODY ENDSTR WORD(4) ) ALL
+BEGINSTR STRBODY ENDSTR WORD(4) BEGINSTR STRBODY ENDSTR WORD(4) = ( BEGINSTR STRBODY ENDSTR WORD(4) ) ALL
+BEGINSTR STRBODY ENDSTR WORD(4) BEGINSTR STRBODY ENDSTR WORD(4) = ( BEGINSTR STRBODY ENDSTR WORD(4) ) ALL
+BEGINSTR STRBODY ENDSTR WORD(4) BEGINSTR STRBODY ENDSTR WORD(4) = ( BEGINSTR STRBODY ENDSTR WORD(4) ) ALL
+
+#
+BEGINSTR STRBODY ENDSTR USERGROUP BEGINSTR STRBODY ENDSTR WORD(4) = ( BEGINSTR STRBODY ENDSTR WORD(4) ) ALL
+BEGINSTR STRBODY ENDSTR USERGROUP BEGINSTR STRBODY ENDSTR WORD(4) = ( BEGINSTR STRBODY ENDSTR WORD(4) ) ALL
+BEGINSTR STRBODY ENDSTR USERGROUP BEGINSTR STRBODY ENDSTR WORD(4) = ( BEGINSTR STRBODY ENDSTR WORD(4) ) ALL
+BEGINSTR STRBODY BACKSLASH STRBODY BACKSLASH STRBODY ENDSTR USERGROUP BEGINSTR STRBODY ENDSTR WORD(4) = ( BEGINSTR STRBODY ENDSTR WORD(4) ) ALL
+BEGINSTR STRBODY ENDSTR USERGROUP BEGINSTR STRBODY ENDSTR WORD(4) = ( BEGINSTR STRBODY ENDSTR WORD(4) ) ALL
+BEGINSTR STRBODY ENDSTR USERGROUP BEGINSTR STRBODY ENDSTR WORD(4) = ( BEGINSTR STRBODY ENDSTR WORD(4) ) ALL
+BEGINSTR STRBODY BACKSLASH STRBODY ENDSTR USERGROUP BEGINSTR STRBODY ENDSTR WORD(4) = ( BEGINSTR STRBODY ENDSTR WORD(4) ) ALL
+BEGINSTR STRBODY ENDSTR NETGROUP BEGINSTR STRBODY ENDSTR WORD(4) = ( BEGINSTR STRBODY ENDSTR WORD(4) ) ALL
+
+#
+WORD(6) ALL = COMMAND ARG , COMMAND ARG ARG , COMMAND ARG
diff --git a/plugins/sudoers/regress/sudoers/test20.in b/plugins/sudoers/regress/sudoers/test20.in
new file mode 100644
index 0000000..c24f88a
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test20.in
@@ -0,0 +1,26 @@
+# Test parsing of tuples
+Defaults lecture
+Defaults !lecture
+Defaults lecture=never
+Defaults lecture=once
+Defaults lecture=always
+
+Defaults listpw
+Defaults !listpw
+Defaults listpw=never
+Defaults listpw=any
+Defaults listpw=all
+Defaults listpw=always
+
+Defaults verifypw
+Defaults !verifypw
+Defaults verifypw=never
+Defaults verifypw=any
+Defaults verifypw=all
+Defaults verifypw=always
+
+Defaults fdexec
+Defaults !fdexec
+Defaults fdexec=never
+Defaults fdexec=digest_only
+Defaults fdexec=always
diff --git a/plugins/sudoers/regress/sudoers/test20.json.ok b/plugins/sudoers/regress/sudoers/test20.json.ok
new file mode 100644
index 0000000..f2f1d55
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test20.json.ok
@@ -0,0 +1,114 @@
+{
+ "Defaults": [
+ {
+ "Options": [
+ { "lecture": true }
+ ]
+ },
+ {
+ "Options": [
+ { "lecture": false }
+ ]
+ },
+ {
+ "Options": [
+ { "lecture": "never" }
+ ]
+ },
+ {
+ "Options": [
+ { "lecture": "once" }
+ ]
+ },
+ {
+ "Options": [
+ { "lecture": "always" }
+ ]
+ },
+ {
+ "Options": [
+ { "listpw": true }
+ ]
+ },
+ {
+ "Options": [
+ { "listpw": false }
+ ]
+ },
+ {
+ "Options": [
+ { "listpw": "never" }
+ ]
+ },
+ {
+ "Options": [
+ { "listpw": "any" }
+ ]
+ },
+ {
+ "Options": [
+ { "listpw": "all" }
+ ]
+ },
+ {
+ "Options": [
+ { "listpw": "always" }
+ ]
+ },
+ {
+ "Options": [
+ { "verifypw": true }
+ ]
+ },
+ {
+ "Options": [
+ { "verifypw": false }
+ ]
+ },
+ {
+ "Options": [
+ { "verifypw": "never" }
+ ]
+ },
+ {
+ "Options": [
+ { "verifypw": "any" }
+ ]
+ },
+ {
+ "Options": [
+ { "verifypw": "all" }
+ ]
+ },
+ {
+ "Options": [
+ { "verifypw": "always" }
+ ]
+ },
+ {
+ "Options": [
+ { "fdexec": true }
+ ]
+ },
+ {
+ "Options": [
+ { "fdexec": false }
+ ]
+ },
+ {
+ "Options": [
+ { "fdexec": "never" }
+ ]
+ },
+ {
+ "Options": [
+ { "fdexec": "digest_only" }
+ ]
+ },
+ {
+ "Options": [
+ { "fdexec": "always" }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test20.ldif.ok b/plugins/sudoers/regress/sudoers/test20.ldif.ok
new file mode 100644
index 0000000..de01cde
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test20.ldif.ok
@@ -0,0 +1,28 @@
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: lecture
+sudoOption: !lecture
+sudoOption: lecture=never
+sudoOption: lecture=once
+sudoOption: lecture=always
+sudoOption: listpw
+sudoOption: !listpw
+sudoOption: listpw=never
+sudoOption: listpw=any
+sudoOption: listpw=all
+sudoOption: listpw=always
+sudoOption: verifypw
+sudoOption: !verifypw
+sudoOption: verifypw=never
+sudoOption: verifypw=any
+sudoOption: verifypw=all
+sudoOption: verifypw=always
+sudoOption: fdexec
+sudoOption: !fdexec
+sudoOption: fdexec=never
+sudoOption: fdexec=digest_only
+sudoOption: fdexec=always
+
diff --git a/plugins/sudoers/regress/sudoers/test20.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test20.ldif2sudo.ok
new file mode 100644
index 0000000..e1c743c
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test20.ldif2sudo.ok
@@ -0,0 +1,22 @@
+Defaults lecture
+Defaults !lecture
+Defaults lecture=never
+Defaults lecture=once
+Defaults lecture=always
+Defaults listpw
+Defaults !listpw
+Defaults listpw=never
+Defaults listpw=any
+Defaults listpw=all
+Defaults listpw=always
+Defaults verifypw
+Defaults !verifypw
+Defaults verifypw=never
+Defaults verifypw=any
+Defaults verifypw=all
+Defaults verifypw=always
+Defaults fdexec
+Defaults !fdexec
+Defaults fdexec=never
+Defaults fdexec=digest_only
+Defaults fdexec=always
diff --git a/plugins/sudoers/regress/sudoers/test20.out.ok b/plugins/sudoers/regress/sudoers/test20.out.ok
new file mode 100644
index 0000000..260be81
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test20.out.ok
@@ -0,0 +1,24 @@
+Parses OK
+
+Defaults lecture
+Defaults !lecture
+Defaults lecture=never
+Defaults lecture=once
+Defaults lecture=always
+Defaults listpw
+Defaults !listpw
+Defaults listpw=never
+Defaults listpw=any
+Defaults listpw=all
+Defaults listpw=always
+Defaults verifypw
+Defaults !verifypw
+Defaults verifypw=never
+Defaults verifypw=any
+Defaults verifypw=all
+Defaults verifypw=always
+Defaults fdexec
+Defaults !fdexec
+Defaults fdexec=never
+Defaults fdexec=digest_only
+Defaults fdexec=always
diff --git a/plugins/sudoers/regress/sudoers/test20.toke.ok b/plugins/sudoers/regress/sudoers/test20.toke.ok
new file mode 100644
index 0000000..1847149
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test20.toke.ok
@@ -0,0 +1,26 @@
+#
+DEFAULTS DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+
+DEFAULTS DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+
+DEFAULTS DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+
+DEFAULTS DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
diff --git a/plugins/sudoers/regress/sudoers/test21.in b/plugins/sudoers/regress/sudoers/test21.in
new file mode 100644
index 0000000..65416cf
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test21.in
@@ -0,0 +1,36 @@
+# Test parsing of syslog settings
+Defaults syslog
+Defaults !syslog
+Defaults syslog=auth
+Defaults syslog=daemon
+Defaults syslog=user
+Defaults syslog=local0
+Defaults syslog=local1
+Defaults syslog=local2
+Defaults syslog=local3
+Defaults syslog=local4
+Defaults syslog=local5
+Defaults syslog=local6
+Defaults syslog=local7
+
+Defaults !syslog_goodpri
+Defaults syslog_goodpri=alert
+Defaults syslog_goodpri=crit
+Defaults syslog_goodpri=debug
+Defaults syslog_goodpri=emerg
+Defaults syslog_goodpri=err
+Defaults syslog_goodpri=info
+Defaults syslog_goodpri=notice
+Defaults syslog_goodpri=warning
+Defaults syslog_goodpri=none
+
+Defaults !syslog_badpri
+Defaults syslog_badpri=alert
+Defaults syslog_badpri=crit
+Defaults syslog_badpri=debug
+Defaults syslog_badpri=emerg
+Defaults syslog_badpri=err
+Defaults syslog_badpri=info
+Defaults syslog_badpri=notice
+Defaults syslog_badpri=warning
+Defaults syslog_badpri=none
diff --git a/plugins/sudoers/regress/sudoers/test21.json.ok b/plugins/sudoers/regress/sudoers/test21.json.ok
new file mode 100644
index 0000000..7896965
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test21.json.ok
@@ -0,0 +1,169 @@
+{
+ "Defaults": [
+ {
+ "Options": [
+ { "syslog": true }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog": false }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog": "auth" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog": "daemon" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog": "user" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog": "local0" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog": "local1" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog": "local2" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog": "local3" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog": "local4" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog": "local5" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog": "local6" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog": "local7" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_goodpri": false }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_goodpri": "alert" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_goodpri": "crit" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_goodpri": "debug" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_goodpri": "emerg" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_goodpri": "err" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_goodpri": "info" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_goodpri": "notice" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_goodpri": "warning" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_goodpri": "none" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_badpri": false }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_badpri": "alert" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_badpri": "crit" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_badpri": "debug" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_badpri": "emerg" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_badpri": "err" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_badpri": "info" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_badpri": "notice" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_badpri": "warning" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_badpri": "none" }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test21.ldif.ok b/plugins/sudoers/regress/sudoers/test21.ldif.ok
new file mode 100644
index 0000000..b3bede8
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test21.ldif.ok
@@ -0,0 +1,39 @@
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: syslog
+sudoOption: !syslog
+sudoOption: syslog=auth
+sudoOption: syslog=daemon
+sudoOption: syslog=user
+sudoOption: syslog=local0
+sudoOption: syslog=local1
+sudoOption: syslog=local2
+sudoOption: syslog=local3
+sudoOption: syslog=local4
+sudoOption: syslog=local5
+sudoOption: syslog=local6
+sudoOption: syslog=local7
+sudoOption: !syslog_goodpri
+sudoOption: syslog_goodpri=alert
+sudoOption: syslog_goodpri=crit
+sudoOption: syslog_goodpri=debug
+sudoOption: syslog_goodpri=emerg
+sudoOption: syslog_goodpri=err
+sudoOption: syslog_goodpri=info
+sudoOption: syslog_goodpri=notice
+sudoOption: syslog_goodpri=warning
+sudoOption: syslog_goodpri=none
+sudoOption: !syslog_badpri
+sudoOption: syslog_badpri=alert
+sudoOption: syslog_badpri=crit
+sudoOption: syslog_badpri=debug
+sudoOption: syslog_badpri=emerg
+sudoOption: syslog_badpri=err
+sudoOption: syslog_badpri=info
+sudoOption: syslog_badpri=notice
+sudoOption: syslog_badpri=warning
+sudoOption: syslog_badpri=none
+
diff --git a/plugins/sudoers/regress/sudoers/test21.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test21.ldif2sudo.ok
new file mode 100644
index 0000000..56e09ff
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test21.ldif2sudo.ok
@@ -0,0 +1,33 @@
+Defaults syslog
+Defaults !syslog
+Defaults syslog=auth
+Defaults syslog=daemon
+Defaults syslog=user
+Defaults syslog=local0
+Defaults syslog=local1
+Defaults syslog=local2
+Defaults syslog=local3
+Defaults syslog=local4
+Defaults syslog=local5
+Defaults syslog=local6
+Defaults syslog=local7
+Defaults !syslog_goodpri
+Defaults syslog_goodpri=alert
+Defaults syslog_goodpri=crit
+Defaults syslog_goodpri=debug
+Defaults syslog_goodpri=emerg
+Defaults syslog_goodpri=err
+Defaults syslog_goodpri=info
+Defaults syslog_goodpri=notice
+Defaults syslog_goodpri=warning
+Defaults syslog_goodpri=none
+Defaults !syslog_badpri
+Defaults syslog_badpri=alert
+Defaults syslog_badpri=crit
+Defaults syslog_badpri=debug
+Defaults syslog_badpri=emerg
+Defaults syslog_badpri=err
+Defaults syslog_badpri=info
+Defaults syslog_badpri=notice
+Defaults syslog_badpri=warning
+Defaults syslog_badpri=none
diff --git a/plugins/sudoers/regress/sudoers/test21.out.ok b/plugins/sudoers/regress/sudoers/test21.out.ok
new file mode 100644
index 0000000..136ec64
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test21.out.ok
@@ -0,0 +1,35 @@
+Parses OK
+
+Defaults syslog
+Defaults !syslog
+Defaults syslog=auth
+Defaults syslog=daemon
+Defaults syslog=user
+Defaults syslog=local0
+Defaults syslog=local1
+Defaults syslog=local2
+Defaults syslog=local3
+Defaults syslog=local4
+Defaults syslog=local5
+Defaults syslog=local6
+Defaults syslog=local7
+Defaults !syslog_goodpri
+Defaults syslog_goodpri=alert
+Defaults syslog_goodpri=crit
+Defaults syslog_goodpri=debug
+Defaults syslog_goodpri=emerg
+Defaults syslog_goodpri=err
+Defaults syslog_goodpri=info
+Defaults syslog_goodpri=notice
+Defaults syslog_goodpri=warning
+Defaults syslog_goodpri=none
+Defaults !syslog_badpri
+Defaults syslog_badpri=alert
+Defaults syslog_badpri=crit
+Defaults syslog_badpri=debug
+Defaults syslog_badpri=emerg
+Defaults syslog_badpri=err
+Defaults syslog_badpri=info
+Defaults syslog_badpri=notice
+Defaults syslog_badpri=warning
+Defaults syslog_badpri=none
diff --git a/plugins/sudoers/regress/sudoers/test21.toke.ok b/plugins/sudoers/regress/sudoers/test21.toke.ok
new file mode 100644
index 0000000..871584b
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test21.toke.ok
@@ -0,0 +1,36 @@
+#
+DEFAULTS DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
diff --git a/plugins/sudoers/regress/sudoers/test22.in b/plugins/sudoers/regress/sudoers/test22.in
new file mode 100644
index 0000000..ecf2fd9
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test22.in
@@ -0,0 +1,6 @@
+# Test parsing of empty Runas_List
+
+user1 ALL = ( : ) ALL
+user2 ALL = (:) ALL
+user3 ALL = ( ) ALL
+user4 ALL = () ALL
diff --git a/plugins/sudoers/regress/sudoers/test22.json.ok b/plugins/sudoers/regress/sudoers/test22.json.ok
new file mode 100644
index 0000000..22141a1
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test22.json.ok
@@ -0,0 +1,88 @@
+{
+ "User_Specs": [
+ {
+ "User_List": [
+ { "username": "user1" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user2" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user3" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user4" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test22.ldif.ok b/plugins/sudoers/regress/sudoers/test22.ldif.ok
new file mode 100644
index 0000000..14c3df4
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test22.ldif.ok
@@ -0,0 +1,40 @@
+dn: cn=user1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user1
+sudoUser: user1
+sudoHost: ALL
+sudoRunAsUser:
+sudoCommand: ALL
+sudoOrder: 1
+
+dn: cn=user2,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user2
+sudoUser: user2
+sudoHost: ALL
+sudoRunAsUser:
+sudoCommand: ALL
+sudoOrder: 2
+
+dn: cn=user3,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user3
+sudoUser: user3
+sudoHost: ALL
+sudoRunAsUser:
+sudoCommand: ALL
+sudoOrder: 3
+
+dn: cn=user4,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user4
+sudoUser: user4
+sudoHost: ALL
+sudoRunAsUser:
+sudoCommand: ALL
+sudoOrder: 4
+
diff --git a/plugins/sudoers/regress/sudoers/test22.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test22.ldif2sudo.ok
new file mode 100644
index 0000000..e0c98e0
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test22.ldif2sudo.ok
@@ -0,0 +1,11 @@
+# sudoRole user1
+user1 ALL = () ALL
+
+# sudoRole user2
+user2 ALL = () ALL
+
+# sudoRole user3
+user3 ALL = () ALL
+
+# sudoRole user4
+user4 ALL = () ALL
diff --git a/plugins/sudoers/regress/sudoers/test22.out.ok b/plugins/sudoers/regress/sudoers/test22.out.ok
new file mode 100644
index 0000000..7117e18
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test22.out.ok
@@ -0,0 +1,6 @@
+Parses OK
+
+user1 ALL = (root) ALL
+user2 ALL = (root) ALL
+user3 ALL = (root) ALL
+user4 ALL = (root) ALL
diff --git a/plugins/sudoers/regress/sudoers/test22.toke.ok b/plugins/sudoers/regress/sudoers/test22.toke.ok
new file mode 100644
index 0000000..9eeb964
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test22.toke.ok
@@ -0,0 +1,6 @@
+#
+
+WORD(6) ALL = ( : ) ALL
+WORD(6) ALL = ( : ) ALL
+WORD(6) ALL = ( ) ALL
+WORD(6) ALL = ( ) ALL
diff --git a/plugins/sudoers/regress/sudoers/test23.in b/plugins/sudoers/regress/sudoers/test23.in
new file mode 100644
index 0000000..5e03d9e
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test23.in
@@ -0,0 +1,11 @@
+# Test parsing of env_check, env_delete and env_keep
+Defaults env_check = "COLORTERM LANG LANGUAGE LC_* LINGUAS"
+Defaults env_check += "TERM TZ"
+
+Defaults env_delete = "IFS CDPATH LOCALDOMAIN RES_OPTIONS HOSTALIASES"
+Defaults env_delete += "NLSPATH PATH_LOCALE LD_* _RLD*"
+
+Defaults env_keep += "LANG LANGUAGE LINGUAS LC_* _XKB_CHARSET"
+Defaults env_keep -= _XKB_CHARSET
+Defaults env_keep += "XAPPLRESDIR XFILESEARCHPATH XUSERFILESEARCHPATH"
+Defaults env_keep += XDG_SESSION_COOKIE
diff --git a/plugins/sudoers/regress/sudoers/test23.json.ok b/plugins/sudoers/regress/sudoers/test23.json.ok
new file mode 100644
index 0000000..5e8b0da
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test23.json.ok
@@ -0,0 +1,102 @@
+{
+ "Defaults": [
+ {
+ "Options": [
+ {
+ "operation": "list_assign",
+ "env_check": [
+ "COLORTERM",
+ "LANG",
+ "LANGUAGE",
+ "LC_*",
+ "LINGUAS"
+ ]
+ }
+ ]
+ },
+ {
+ "Options": [
+ {
+ "operation": "list_add",
+ "env_check": [
+ "TERM",
+ "TZ"
+ ]
+ }
+ ]
+ },
+ {
+ "Options": [
+ {
+ "operation": "list_assign",
+ "env_delete": [
+ "IFS",
+ "CDPATH",
+ "LOCALDOMAIN",
+ "RES_OPTIONS",
+ "HOSTALIASES"
+ ]
+ }
+ ]
+ },
+ {
+ "Options": [
+ {
+ "operation": "list_add",
+ "env_delete": [
+ "NLSPATH",
+ "PATH_LOCALE",
+ "LD_*",
+ "_RLD*"
+ ]
+ }
+ ]
+ },
+ {
+ "Options": [
+ {
+ "operation": "list_add",
+ "env_keep": [
+ "LANG",
+ "LANGUAGE",
+ "LINGUAS",
+ "LC_*",
+ "_XKB_CHARSET"
+ ]
+ }
+ ]
+ },
+ {
+ "Options": [
+ {
+ "operation": "list_remove",
+ "env_keep": [
+ "_XKB_CHARSET"
+ ]
+ }
+ ]
+ },
+ {
+ "Options": [
+ {
+ "operation": "list_add",
+ "env_keep": [
+ "XAPPLRESDIR",
+ "XFILESEARCHPATH",
+ "XUSERFILESEARCHPATH"
+ ]
+ }
+ ]
+ },
+ {
+ "Options": [
+ {
+ "operation": "list_add",
+ "env_keep": [
+ "XDG_SESSION_COOKIE"
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test23.ldif.ok b/plugins/sudoers/regress/sudoers/test23.ldif.ok
new file mode 100644
index 0000000..97c1fbd
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test23.ldif.ok
@@ -0,0 +1,14 @@
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: env_check=COLORTERM LANG LANGUAGE LC_* LINGUAS
+sudoOption: env_check+=TERM TZ
+sudoOption: env_delete=IFS CDPATH LOCALDOMAIN RES_OPTIONS HOSTALIASES
+sudoOption: env_delete+=NLSPATH PATH_LOCALE LD_* _RLD*
+sudoOption: env_keep+=LANG LANGUAGE LINGUAS LC_* _XKB_CHARSET
+sudoOption: env_keep-=_XKB_CHARSET
+sudoOption: env_keep+=XAPPLRESDIR XFILESEARCHPATH XUSERFILESEARCHPATH
+sudoOption: env_keep+=XDG_SESSION_COOKIE
+
diff --git a/plugins/sudoers/regress/sudoers/test23.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test23.ldif2sudo.ok
new file mode 100644
index 0000000..bb9f2e9
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test23.ldif2sudo.ok
@@ -0,0 +1,8 @@
+Defaults env_check="COLORTERM LANG LANGUAGE LC_* LINGUAS"
+Defaults env_check+="TERM TZ"
+Defaults env_delete="IFS CDPATH LOCALDOMAIN RES_OPTIONS HOSTALIASES"
+Defaults env_delete+="NLSPATH PATH_LOCALE LD_* _RLD*"
+Defaults env_keep+="LANG LANGUAGE LINGUAS LC_* _XKB_CHARSET"
+Defaults env_keep-=_XKB_CHARSET
+Defaults env_keep+="XAPPLRESDIR XFILESEARCHPATH XUSERFILESEARCHPATH"
+Defaults env_keep+=XDG_SESSION_COOKIE
diff --git a/plugins/sudoers/regress/sudoers/test23.out.ok b/plugins/sudoers/regress/sudoers/test23.out.ok
new file mode 100644
index 0000000..fe6e415
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test23.out.ok
@@ -0,0 +1,10 @@
+Parses OK
+
+Defaults env_check="COLORTERM LANG LANGUAGE LC_* LINGUAS"
+Defaults env_check+="TERM TZ"
+Defaults env_delete="IFS CDPATH LOCALDOMAIN RES_OPTIONS HOSTALIASES"
+Defaults env_delete+="NLSPATH PATH_LOCALE LD_* _RLD*"
+Defaults env_keep+="LANG LANGUAGE LINGUAS LC_* _XKB_CHARSET"
+Defaults env_keep-=_XKB_CHARSET
+Defaults env_keep+="XAPPLRESDIR XFILESEARCHPATH XUSERFILESEARCHPATH"
+Defaults env_keep+=XDG_SESSION_COOKIE
diff --git a/plugins/sudoers/regress/sudoers/test23.toke.ok b/plugins/sudoers/regress/sudoers/test23.toke.ok
new file mode 100644
index 0000000..3d7cd84
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test23.toke.ok
@@ -0,0 +1,11 @@
+#
+DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR += BEGINSTR STRBODY ENDSTR WORD(4)
+
+DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR += BEGINSTR STRBODY ENDSTR WORD(4)
+
+DEFAULTS DEFVAR += BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR -= WORD(2)
+DEFAULTS DEFVAR += BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR += WORD(2)
diff --git a/plugins/sudoers/regress/sudoers/test24.in b/plugins/sudoers/regress/sudoers/test24.in
new file mode 100644
index 0000000..3fc3bbc
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test24.in
@@ -0,0 +1,6 @@
+# Test parsing of CHROOT and CWD syntax
+Defaults runcwd=~
+Defaults runchroot=/
+#
+user0 ALL = CHROOT=/var/www CWD=/htdocs /bin/ksh
+user1 ALL = CWD=~root /usr/bin/id, CWD=/tmp /bin/ls
diff --git a/plugins/sudoers/regress/sudoers/test24.json.ok b/plugins/sudoers/regress/sudoers/test24.json.ok
new file mode 100644
index 0000000..964359c
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test24.json.ok
@@ -0,0 +1,61 @@
+{
+ "Defaults": [
+ {
+ "Options": [
+ { "runcwd": "~" }
+ ]
+ },
+ {
+ "Options": [
+ { "runchroot": "/" }
+ ]
+ }
+ ],
+ "User_Specs": [
+ {
+ "User_List": [
+ { "username": "user0" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ "runchroot": "/var/www",
+ "runcwd": "/htdocs"
+ ],
+ "Commands": [
+ { "command": "/bin/ksh" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user1" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ "runcwd": "~root"
+ ],
+ "Commands": [
+ { "command": "/usr/bin/id" }
+ ]
+ },
+ {
+ "Options": [
+ "runcwd": "/tmp"
+ ],
+ "Commands": [
+ { "command": "/bin/ls" }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test24.ldif.ok b/plugins/sudoers/regress/sudoers/test24.ldif.ok
new file mode 100644
index 0000000..aeb5f7a
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test24.ldif.ok
@@ -0,0 +1,39 @@
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: runcwd=~
+sudoOption: runchroot=/
+
+dn: cn=user0,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user0
+sudoUser: user0
+sudoHost: ALL
+sudoOption: runchroot=/var/www
+sudoOption: runcwd=/htdocs
+sudoCommand: /bin/ksh
+sudoOrder: 1
+
+dn: cn=user1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user1
+sudoUser: user1
+sudoHost: ALL
+sudoOption: runcwd=~root
+sudoCommand: /usr/bin/id
+sudoOrder: 2
+
+dn: cn=user1_1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user1_1
+sudoUser: user1
+sudoHost: ALL
+sudoOption: runcwd=/tmp
+sudoCommand: /bin/ls
+sudoOrder: 3
+
diff --git a/plugins/sudoers/regress/sudoers/test24.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test24.ldif2sudo.ok
new file mode 100644
index 0000000..4c09657
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test24.ldif2sudo.ok
@@ -0,0 +1,8 @@
+Defaults runcwd=~
+Defaults runchroot=/
+
+# sudoRole user0
+user0 ALL = CHROOT=/var/www CWD=/htdocs /bin/ksh
+
+# sudoRole user1, user1_1
+user1 ALL = CWD=~root /usr/bin/id, CWD=/tmp /bin/ls
diff --git a/plugins/sudoers/regress/sudoers/test24.out.ok b/plugins/sudoers/regress/sudoers/test24.out.ok
new file mode 100644
index 0000000..530b700
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test24.out.ok
@@ -0,0 +1,7 @@
+Parses OK
+
+Defaults runcwd=~
+Defaults runchroot=/
+
+user0 ALL = CHROOT=/var/www CWD=/htdocs /bin/ksh
+user1 ALL = CWD=~root /usr/bin/id, CWD=/tmp /bin/ls
diff --git a/plugins/sudoers/regress/sudoers/test24.toke.ok b/plugins/sudoers/regress/sudoers/test24.toke.ok
new file mode 100644
index 0000000..47842f4
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test24.toke.ok
@@ -0,0 +1,6 @@
+#
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+#
+WORD(6) ALL = CHROOT = WORD(5) CWD = WORD(5) COMMAND
+WORD(6) ALL = CWD = WORD(5) COMMAND , CWD = WORD(5) COMMAND
diff --git a/plugins/sudoers/regress/sudoers/test25.in b/plugins/sudoers/regress/sudoers/test25.in
new file mode 100644
index 0000000..fe35587
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test25.in
@@ -0,0 +1,3 @@
+# Test continuation character when there is nothing to continue
+# Used to leak "~ron" when run under address sanitizer
+foo ALL = CWD=~ron /bin/ls \
diff --git a/plugins/sudoers/regress/sudoers/test25.json.ok b/plugins/sudoers/regress/sudoers/test25.json.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test25.json.ok
diff --git a/plugins/sudoers/regress/sudoers/test25.ldif.ok b/plugins/sudoers/regress/sudoers/test25.ldif.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test25.ldif.ok
diff --git a/plugins/sudoers/regress/sudoers/test25.out.ok b/plugins/sudoers/regress/sudoers/test25.out.ok
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test25.out.ok
@@ -0,0 +1 @@
+
diff --git a/plugins/sudoers/regress/sudoers/test25.toke.ok b/plugins/sudoers/regress/sudoers/test25.toke.ok
new file mode 100644
index 0000000..e58461e
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test25.toke.ok
@@ -0,0 +1,3 @@
+#
+#
+WORD(6) ALL = CWD = WORD(5) COMMAND <*> \ No newline at end of file
diff --git a/plugins/sudoers/regress/sudoers/test26.in b/plugins/sudoers/regress/sudoers/test26.in
new file mode 100644
index 0000000..842f2b4
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test26.in
@@ -0,0 +1,128 @@
+# Defaults settings that trigger callbacks
+Defaults fqdn
+Defaults runas_default=root
+Defaults tty_tickets
+Defaults umask=022
+Defaults runchroot=/
+Defaults logfile=/var/log/sudo
+Defaults log_format=json
+Defaults syslog=auth, syslog_badpri=alert, syslog_goodpri=notice
+Defaults syslog_maxlen=2048
+Defaults !loglinelen, log_year, log_host
+Defaults !mailerpath, mailerflags="-t", mailfrom="sudo@sudo.ws", mailto="root@localhost", mailsub="*** Sudo information for %h ***"
+
+# All other Defaults settings
+Defaults long_otp_prompt
+Defaults ignore_dot
+Defaults !mail_always
+Defaults !mail_badpass
+Defaults !mail_no_user
+Defaults !mail_no_host
+Defaults !mail_no_perms
+Defaults !mail_all_cmnds
+Defaults lecture=always
+Defaults lecture_file=/etc/sudo.lecture
+Defaults authenticate
+Defaults root_sudo
+Defaults shell_noargs
+Defaults set_home
+Defaults always_set_home
+Defaults path_info
+Defaults insults
+Defaults !requiretty
+Defaults env_editor
+Defaults !rootpw
+Defaults !runaspw
+Defaults !targetpw
+Defaults use_loginclass
+Defaults set_logname
+Defaults !stay_setuid
+Defaults !preserve_groups
+Defaults timestamp_timeout=.5
+Defaults passwd_timeout=5
+Defaults passwd_tries=3
+Defaults badpass_message="Take off, eh!"
+Defaults lecture_status_dir="/var/lib/sudo/lectured"
+Defaults timestampdir="/run/sudo/ts"
+Defaults timestampowner=root
+Defaults exempt_group=sudo
+Defaults passprompt="%p's sudo password: "
+Defaults passprompt_override
+Defaults secure_path="/usr/bin:/usr/sbin:/bin:/sbin"
+Defaults editor=/usr/bin/vi
+Defaults listpw=any
+Defaults verifypw=all
+Defaults noexec
+Defaults ignore_local_sudoers
+Defaults closefrom=3
+Defaults closefrom_override
+Defaults !setenv
+Defaults env_reset
+Defaults env_check += "TERMCAP"
+Defaults !env_delete
+Defaults env_keep += "LANG LANGUAGE LINGUAS LC_* _XKB_CHARSET"
+#Defaults role
+#Defaults type
+Defaults env_file="/etc/environment"
+Defaults restricted_env_file="/etc/environment.sudo"
+Defaults sudoers_locale=C
+Defaults !visiblepw
+Defaults pwfeedback
+Defaults fast_glob
+Defaults umask_override
+Defaults log_input
+Defaults log_output
+Defaults compress_io
+Defaults use_pty
+#Defaults group_plugin
+Defaults iolog_dir="/var/log/sudo-io"
+Defaults iolog_file="%{seq}"
+Defaults set_utmp
+Defaults utmp_runas
+#Defaults privs
+#Defaults limitprivs
+Defaults !exec_background
+Defaults pam_service="sudo"
+Defaults pam_login_service="sudo-login"
+Defaults pam_setcred
+Defaults pam_session
+Defaults pam_acct_mgmt
+Defaults maxseq=2176782336
+Defaults use_netgroups
+Defaults sudoedit_checkdir
+Defaults !sudoedit_follow
+Defaults always_query_group_plugin
+Defaults netgroup_tuple
+Defaults ignore_audit_errors
+Defaults ignore_iolog_errors
+Defaults ignore_logfile_errors
+Defaults !match_group_by_gid
+Defaults iolog_user=root
+Defaults iolog_group=root
+Defaults iolog_mode=0600
+Defaults fdexec=digest_only
+Defaults !ignore_unknown_defaults
+Defaults command_timeout=7d8h30m10s
+Defaults user_command_timeouts
+Defaults iolog_flush
+Defaults syslog_pid
+Defaults timestamp_type=tty
+Defaults authfail_message="Learn to type!"
+Defaults case_insensitive_user
+Defaults case_insensitive_group
+Defaults log_allowed
+Defaults log_denied
+Defaults !log_servers
+Defaults log_server_timeout=10
+Defaults log_server_keepalive
+Defaults !log_server_cabundle
+Defaults !log_server_peer_cert
+Defaults !log_server_peer_key
+Defaults !log_server_verify
+Defaults runas_allow_unknown_id
+Defaults runas_check_shell
+Defaults pam_ruser
+Defaults pam_rhost
+Defaults runcwd=~
+Defaults !selinux
+Defaults !admin_flag
diff --git a/plugins/sudoers/regress/sudoers/test26.json.ok b/plugins/sudoers/regress/sudoers/test26.json.ok
new file mode 100644
index 0000000..4fabe5f
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test26.json.ok
@@ -0,0 +1,626 @@
+{
+ "Defaults": [
+ {
+ "Options": [
+ { "fqdn": true }
+ ]
+ },
+ {
+ "Options": [
+ { "runas_default": "root" }
+ ]
+ },
+ {
+ "Options": [
+ { "tty_tickets": true }
+ ]
+ },
+ {
+ "Options": [
+ { "umask": "022" }
+ ]
+ },
+ {
+ "Options": [
+ { "runchroot": "/" }
+ ]
+ },
+ {
+ "Options": [
+ { "logfile": "/var/log/sudo" }
+ ]
+ },
+ {
+ "Options": [
+ { "log_format": "json" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog": "auth" },
+ { "syslog_badpri": "alert" },
+ { "syslog_goodpri": "notice" }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_maxlen": "2048" }
+ ]
+ },
+ {
+ "Options": [
+ { "loglinelen": false },
+ { "log_year": true },
+ { "log_host": true }
+ ]
+ },
+ {
+ "Options": [
+ { "mailerpath": false },
+ { "mailerflags": "-t" },
+ { "mailfrom": "sudo@sudo.ws" },
+ { "mailto": "root@localhost" },
+ { "mailsub": "*** Sudo information for %h ***" }
+ ]
+ },
+ {
+ "Options": [
+ { "long_otp_prompt": true }
+ ]
+ },
+ {
+ "Options": [
+ { "ignore_dot": true }
+ ]
+ },
+ {
+ "Options": [
+ { "mail_always": false }
+ ]
+ },
+ {
+ "Options": [
+ { "mail_badpass": false }
+ ]
+ },
+ {
+ "Options": [
+ { "mail_no_user": false }
+ ]
+ },
+ {
+ "Options": [
+ { "mail_no_host": false }
+ ]
+ },
+ {
+ "Options": [
+ { "mail_no_perms": false }
+ ]
+ },
+ {
+ "Options": [
+ { "mail_all_cmnds": false }
+ ]
+ },
+ {
+ "Options": [
+ { "lecture": "always" }
+ ]
+ },
+ {
+ "Options": [
+ { "lecture_file": "/etc/sudo.lecture" }
+ ]
+ },
+ {
+ "Options": [
+ { "authenticate": true }
+ ]
+ },
+ {
+ "Options": [
+ { "root_sudo": true }
+ ]
+ },
+ {
+ "Options": [
+ { "shell_noargs": true }
+ ]
+ },
+ {
+ "Options": [
+ { "set_home": true }
+ ]
+ },
+ {
+ "Options": [
+ { "always_set_home": true }
+ ]
+ },
+ {
+ "Options": [
+ { "path_info": true }
+ ]
+ },
+ {
+ "Options": [
+ { "insults": true }
+ ]
+ },
+ {
+ "Options": [
+ { "requiretty": false }
+ ]
+ },
+ {
+ "Options": [
+ { "env_editor": true }
+ ]
+ },
+ {
+ "Options": [
+ { "rootpw": false }
+ ]
+ },
+ {
+ "Options": [
+ { "runaspw": false }
+ ]
+ },
+ {
+ "Options": [
+ { "targetpw": false }
+ ]
+ },
+ {
+ "Options": [
+ { "use_loginclass": true }
+ ]
+ },
+ {
+ "Options": [
+ { "set_logname": true }
+ ]
+ },
+ {
+ "Options": [
+ { "stay_setuid": false }
+ ]
+ },
+ {
+ "Options": [
+ { "preserve_groups": false }
+ ]
+ },
+ {
+ "Options": [
+ { "timestamp_timeout": ".5" }
+ ]
+ },
+ {
+ "Options": [
+ { "passwd_timeout": "5" }
+ ]
+ },
+ {
+ "Options": [
+ { "passwd_tries": "3" }
+ ]
+ },
+ {
+ "Options": [
+ { "badpass_message": "Take off, eh!" }
+ ]
+ },
+ {
+ "Options": [
+ { "lecture_status_dir": "/var/lib/sudo/lectured" }
+ ]
+ },
+ {
+ "Options": [
+ { "timestampdir": "/run/sudo/ts" }
+ ]
+ },
+ {
+ "Options": [
+ { "timestampowner": "root" }
+ ]
+ },
+ {
+ "Options": [
+ { "exempt_group": "sudo" }
+ ]
+ },
+ {
+ "Options": [
+ { "passprompt": "%p's sudo password: " }
+ ]
+ },
+ {
+ "Options": [
+ { "passprompt_override": true }
+ ]
+ },
+ {
+ "Options": [
+ { "secure_path": "/usr/bin:/usr/sbin:/bin:/sbin" }
+ ]
+ },
+ {
+ "Options": [
+ { "editor": "/usr/bin/vi" }
+ ]
+ },
+ {
+ "Options": [
+ { "listpw": "any" }
+ ]
+ },
+ {
+ "Options": [
+ { "verifypw": "all" }
+ ]
+ },
+ {
+ "Options": [
+ { "noexec": true }
+ ]
+ },
+ {
+ "Options": [
+ { "ignore_local_sudoers": true }
+ ]
+ },
+ {
+ "Options": [
+ { "closefrom": "3" }
+ ]
+ },
+ {
+ "Options": [
+ { "closefrom_override": true }
+ ]
+ },
+ {
+ "Options": [
+ { "setenv": false }
+ ]
+ },
+ {
+ "Options": [
+ { "env_reset": true }
+ ]
+ },
+ {
+ "Options": [
+ {
+ "operation": "list_add",
+ "env_check": [
+ "TERMCAP"
+ ]
+ }
+ ]
+ },
+ {
+ "Options": [
+ { "env_delete": false }
+ ]
+ },
+ {
+ "Options": [
+ {
+ "operation": "list_add",
+ "env_keep": [
+ "LANG",
+ "LANGUAGE",
+ "LINGUAS",
+ "LC_*",
+ "_XKB_CHARSET"
+ ]
+ }
+ ]
+ },
+ {
+ "Options": [
+ { "env_file": "/etc/environment" }
+ ]
+ },
+ {
+ "Options": [
+ { "restricted_env_file": "/etc/environment.sudo" }
+ ]
+ },
+ {
+ "Options": [
+ { "sudoers_locale": "C" }
+ ]
+ },
+ {
+ "Options": [
+ { "visiblepw": false }
+ ]
+ },
+ {
+ "Options": [
+ { "pwfeedback": true }
+ ]
+ },
+ {
+ "Options": [
+ { "fast_glob": true }
+ ]
+ },
+ {
+ "Options": [
+ { "umask_override": true }
+ ]
+ },
+ {
+ "Options": [
+ { "log_input": true }
+ ]
+ },
+ {
+ "Options": [
+ { "log_output": true }
+ ]
+ },
+ {
+ "Options": [
+ { "compress_io": true }
+ ]
+ },
+ {
+ "Options": [
+ { "use_pty": true }
+ ]
+ },
+ {
+ "Options": [
+ { "iolog_dir": "/var/log/sudo-io" }
+ ]
+ },
+ {
+ "Options": [
+ { "iolog_file": "%{seq}" }
+ ]
+ },
+ {
+ "Options": [
+ { "set_utmp": true }
+ ]
+ },
+ {
+ "Options": [
+ { "utmp_runas": true }
+ ]
+ },
+ {
+ "Options": [
+ { "exec_background": false }
+ ]
+ },
+ {
+ "Options": [
+ { "pam_service": "sudo" }
+ ]
+ },
+ {
+ "Options": [
+ { "pam_login_service": "sudo-login" }
+ ]
+ },
+ {
+ "Options": [
+ { "pam_setcred": true }
+ ]
+ },
+ {
+ "Options": [
+ { "pam_session": true }
+ ]
+ },
+ {
+ "Options": [
+ { "pam_acct_mgmt": true }
+ ]
+ },
+ {
+ "Options": [
+ { "maxseq": "2176782336" }
+ ]
+ },
+ {
+ "Options": [
+ { "use_netgroups": true }
+ ]
+ },
+ {
+ "Options": [
+ { "sudoedit_checkdir": true }
+ ]
+ },
+ {
+ "Options": [
+ { "sudoedit_follow": false }
+ ]
+ },
+ {
+ "Options": [
+ { "always_query_group_plugin": true }
+ ]
+ },
+ {
+ "Options": [
+ { "netgroup_tuple": true }
+ ]
+ },
+ {
+ "Options": [
+ { "ignore_audit_errors": true }
+ ]
+ },
+ {
+ "Options": [
+ { "ignore_iolog_errors": true }
+ ]
+ },
+ {
+ "Options": [
+ { "ignore_logfile_errors": true }
+ ]
+ },
+ {
+ "Options": [
+ { "match_group_by_gid": false }
+ ]
+ },
+ {
+ "Options": [
+ { "iolog_user": "root" }
+ ]
+ },
+ {
+ "Options": [
+ { "iolog_group": "root" }
+ ]
+ },
+ {
+ "Options": [
+ { "iolog_mode": "0600" }
+ ]
+ },
+ {
+ "Options": [
+ { "fdexec": "digest_only" }
+ ]
+ },
+ {
+ "Options": [
+ { "ignore_unknown_defaults": false }
+ ]
+ },
+ {
+ "Options": [
+ { "command_timeout": "7d8h30m10s" }
+ ]
+ },
+ {
+ "Options": [
+ { "user_command_timeouts": true }
+ ]
+ },
+ {
+ "Options": [
+ { "iolog_flush": true }
+ ]
+ },
+ {
+ "Options": [
+ { "syslog_pid": true }
+ ]
+ },
+ {
+ "Options": [
+ { "timestamp_type": "tty" }
+ ]
+ },
+ {
+ "Options": [
+ { "authfail_message": "Learn to type!" }
+ ]
+ },
+ {
+ "Options": [
+ { "case_insensitive_user": true }
+ ]
+ },
+ {
+ "Options": [
+ { "case_insensitive_group": true }
+ ]
+ },
+ {
+ "Options": [
+ { "log_allowed": true }
+ ]
+ },
+ {
+ "Options": [
+ { "log_denied": true }
+ ]
+ },
+ {
+ "Options": [
+ { "log_servers": false }
+ ]
+ },
+ {
+ "Options": [
+ { "log_server_timeout": "10" }
+ ]
+ },
+ {
+ "Options": [
+ { "log_server_keepalive": true }
+ ]
+ },
+ {
+ "Options": [
+ { "log_server_cabundle": false }
+ ]
+ },
+ {
+ "Options": [
+ { "log_server_peer_cert": false }
+ ]
+ },
+ {
+ "Options": [
+ { "log_server_peer_key": false }
+ ]
+ },
+ {
+ "Options": [
+ { "log_server_verify": false }
+ ]
+ },
+ {
+ "Options": [
+ { "runas_allow_unknown_id": true }
+ ]
+ },
+ {
+ "Options": [
+ { "runas_check_shell": true }
+ ]
+ },
+ {
+ "Options": [
+ { "pam_ruser": true }
+ ]
+ },
+ {
+ "Options": [
+ { "pam_rhost": true }
+ ]
+ },
+ {
+ "Options": [
+ { "runcwd": "~" }
+ ]
+ },
+ {
+ "Options": [
+ { "selinux": false }
+ ]
+ },
+ {
+ "Options": [
+ { "admin_flag": false }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test26.ldif.ok b/plugins/sudoers/regress/sudoers/test26.ldif.ok
new file mode 100644
index 0000000..912e265
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test26.ldif.ok
@@ -0,0 +1,134 @@
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: fqdn
+sudoOption: runas_default=root
+sudoOption: tty_tickets
+sudoOption: umask=022
+sudoOption: runchroot=/
+sudoOption: logfile=/var/log/sudo
+sudoOption: log_format=json
+sudoOption: syslog=auth
+sudoOption: syslog_badpri=alert
+sudoOption: syslog_goodpri=notice
+sudoOption: syslog_maxlen=2048
+sudoOption: !loglinelen
+sudoOption: log_year
+sudoOption: log_host
+sudoOption: !mailerpath
+sudoOption: mailerflags=-t
+sudoOption: mailfrom=sudo@sudo.ws
+sudoOption: mailto=root@localhost
+sudoOption: mailsub=*** Sudo information for %h ***
+sudoOption: long_otp_prompt
+sudoOption: ignore_dot
+sudoOption: !mail_always
+sudoOption: !mail_badpass
+sudoOption: !mail_no_user
+sudoOption: !mail_no_host
+sudoOption: !mail_no_perms
+sudoOption: !mail_all_cmnds
+sudoOption: lecture=always
+sudoOption: lecture_file=/etc/sudo.lecture
+sudoOption: authenticate
+sudoOption: root_sudo
+sudoOption: shell_noargs
+sudoOption: set_home
+sudoOption: always_set_home
+sudoOption: path_info
+sudoOption: insults
+sudoOption: !requiretty
+sudoOption: env_editor
+sudoOption: !rootpw
+sudoOption: !runaspw
+sudoOption: !targetpw
+sudoOption: use_loginclass
+sudoOption: set_logname
+sudoOption: !stay_setuid
+sudoOption: !preserve_groups
+sudoOption: timestamp_timeout=.5
+sudoOption: passwd_timeout=5
+sudoOption: passwd_tries=3
+sudoOption: badpass_message=Take off, eh!
+sudoOption: lecture_status_dir=/var/lib/sudo/lectured
+sudoOption: timestampdir=/run/sudo/ts
+sudoOption: timestampowner=root
+sudoOption: exempt_group=sudo
+sudoOption: passprompt=%p's sudo password:
+sudoOption: passprompt_override
+sudoOption: secure_path=/usr/bin:/usr/sbin:/bin:/sbin
+sudoOption: editor=/usr/bin/vi
+sudoOption: listpw=any
+sudoOption: verifypw=all
+sudoOption: noexec
+sudoOption: ignore_local_sudoers
+sudoOption: closefrom=3
+sudoOption: closefrom_override
+sudoOption: !setenv
+sudoOption: env_reset
+sudoOption: env_check+=TERMCAP
+sudoOption: !env_delete
+sudoOption: env_keep+=LANG LANGUAGE LINGUAS LC_* _XKB_CHARSET
+sudoOption: env_file=/etc/environment
+sudoOption: restricted_env_file=/etc/environment.sudo
+sudoOption: sudoers_locale=C
+sudoOption: !visiblepw
+sudoOption: pwfeedback
+sudoOption: fast_glob
+sudoOption: umask_override
+sudoOption: log_input
+sudoOption: log_output
+sudoOption: compress_io
+sudoOption: use_pty
+sudoOption: iolog_dir=/var/log/sudo-io
+sudoOption: iolog_file=%{seq}
+sudoOption: set_utmp
+sudoOption: utmp_runas
+sudoOption: !exec_background
+sudoOption: pam_service=sudo
+sudoOption: pam_login_service=sudo-login
+sudoOption: pam_setcred
+sudoOption: pam_session
+sudoOption: pam_acct_mgmt
+sudoOption: maxseq=2176782336
+sudoOption: use_netgroups
+sudoOption: sudoedit_checkdir
+sudoOption: !sudoedit_follow
+sudoOption: always_query_group_plugin
+sudoOption: netgroup_tuple
+sudoOption: ignore_audit_errors
+sudoOption: ignore_iolog_errors
+sudoOption: ignore_logfile_errors
+sudoOption: !match_group_by_gid
+sudoOption: iolog_user=root
+sudoOption: iolog_group=root
+sudoOption: iolog_mode=0600
+sudoOption: fdexec=digest_only
+sudoOption: !ignore_unknown_defaults
+sudoOption: command_timeout=7d8h30m10s
+sudoOption: user_command_timeouts
+sudoOption: iolog_flush
+sudoOption: syslog_pid
+sudoOption: timestamp_type=tty
+sudoOption: authfail_message=Learn to type!
+sudoOption: case_insensitive_user
+sudoOption: case_insensitive_group
+sudoOption: log_allowed
+sudoOption: log_denied
+sudoOption: !log_servers
+sudoOption: log_server_timeout=10
+sudoOption: log_server_keepalive
+sudoOption: !log_server_cabundle
+sudoOption: !log_server_peer_cert
+sudoOption: !log_server_peer_key
+sudoOption: !log_server_verify
+sudoOption: runas_allow_unknown_id
+sudoOption: runas_check_shell
+sudoOption: pam_ruser
+sudoOption: pam_rhost
+sudoOption: runcwd=~
+sudoOption: !selinux
+sudoOption: !admin_flag
+
diff --git a/plugins/sudoers/regress/sudoers/test26.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test26.ldif2sudo.ok
new file mode 100644
index 0000000..706c6fd
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test26.ldif2sudo.ok
@@ -0,0 +1,128 @@
+Defaults fqdn
+Defaults runas_default=root
+Defaults tty_tickets
+Defaults umask=022
+Defaults runchroot=/
+Defaults logfile=/var/log/sudo
+Defaults log_format=json
+Defaults syslog=auth
+Defaults syslog_badpri=alert
+Defaults syslog_goodpri=notice
+Defaults syslog_maxlen=2048
+Defaults !loglinelen
+Defaults log_year
+Defaults log_host
+Defaults !mailerpath
+Defaults mailerflags=-t
+Defaults mailfrom=sudo@sudo.ws
+Defaults mailto=root@localhost
+Defaults mailsub="*** Sudo information for %h ***"
+Defaults long_otp_prompt
+Defaults ignore_dot
+Defaults !mail_always
+Defaults !mail_badpass
+Defaults !mail_no_user
+Defaults !mail_no_host
+Defaults !mail_no_perms
+Defaults !mail_all_cmnds
+Defaults lecture=always
+Defaults lecture_file=/etc/sudo.lecture
+Defaults authenticate
+Defaults root_sudo
+Defaults shell_noargs
+Defaults set_home
+Defaults always_set_home
+Defaults path_info
+Defaults insults
+Defaults !requiretty
+Defaults env_editor
+Defaults !rootpw
+Defaults !runaspw
+Defaults !targetpw
+Defaults use_loginclass
+Defaults set_logname
+Defaults !stay_setuid
+Defaults !preserve_groups
+Defaults timestamp_timeout=.5
+Defaults passwd_timeout=5
+Defaults passwd_tries=3
+Defaults badpass_message="Take off, eh!"
+Defaults lecture_status_dir=/var/lib/sudo/lectured
+Defaults timestampdir=/run/sudo/ts
+Defaults timestampowner=root
+Defaults exempt_group=sudo
+Defaults passprompt="%p's sudo password:"
+Defaults passprompt_override
+Defaults secure_path=/usr/bin\:/usr/sbin\:/bin\:/sbin
+Defaults editor=/usr/bin/vi
+Defaults listpw=any
+Defaults verifypw=all
+Defaults noexec
+Defaults ignore_local_sudoers
+Defaults closefrom=3
+Defaults closefrom_override
+Defaults !setenv
+Defaults env_reset
+Defaults env_check+=TERMCAP
+Defaults !env_delete
+Defaults env_keep+="LANG LANGUAGE LINGUAS LC_* _XKB_CHARSET"
+Defaults env_file=/etc/environment
+Defaults restricted_env_file=/etc/environment.sudo
+Defaults sudoers_locale=C
+Defaults !visiblepw
+Defaults pwfeedback
+Defaults fast_glob
+Defaults umask_override
+Defaults log_input
+Defaults log_output
+Defaults compress_io
+Defaults use_pty
+Defaults iolog_dir=/var/log/sudo-io
+Defaults iolog_file=%{seq}
+Defaults set_utmp
+Defaults utmp_runas
+Defaults !exec_background
+Defaults pam_service=sudo
+Defaults pam_login_service=sudo-login
+Defaults pam_setcred
+Defaults pam_session
+Defaults pam_acct_mgmt
+Defaults maxseq=2176782336
+Defaults use_netgroups
+Defaults sudoedit_checkdir
+Defaults !sudoedit_follow
+Defaults always_query_group_plugin
+Defaults netgroup_tuple
+Defaults ignore_audit_errors
+Defaults ignore_iolog_errors
+Defaults ignore_logfile_errors
+Defaults !match_group_by_gid
+Defaults iolog_user=root
+Defaults iolog_group=root
+Defaults iolog_mode=0600
+Defaults fdexec=digest_only
+Defaults !ignore_unknown_defaults
+Defaults command_timeout=7d8h30m10s
+Defaults user_command_timeouts
+Defaults iolog_flush
+Defaults syslog_pid
+Defaults timestamp_type=tty
+Defaults authfail_message="Learn to type!"
+Defaults case_insensitive_user
+Defaults case_insensitive_group
+Defaults log_allowed
+Defaults log_denied
+Defaults !log_servers
+Defaults log_server_timeout=10
+Defaults log_server_keepalive
+Defaults !log_server_cabundle
+Defaults !log_server_peer_cert
+Defaults !log_server_peer_key
+Defaults !log_server_verify
+Defaults runas_allow_unknown_id
+Defaults runas_check_shell
+Defaults pam_ruser
+Defaults pam_rhost
+Defaults runcwd=~
+Defaults !selinux
+Defaults !admin_flag
diff --git a/plugins/sudoers/regress/sudoers/test26.out.ok b/plugins/sudoers/regress/sudoers/test26.out.ok
new file mode 100644
index 0000000..e9f07fb
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test26.out.ok
@@ -0,0 +1,122 @@
+Parses OK
+
+Defaults fqdn
+Defaults runas_default=root
+Defaults tty_tickets
+Defaults umask=022
+Defaults runchroot=/
+Defaults logfile=/var/log/sudo
+Defaults log_format=json
+Defaults syslog=auth, syslog_badpri=alert, syslog_goodpri=notice
+Defaults syslog_maxlen=2048
+Defaults !loglinelen, log_year, log_host
+Defaults !mailerpath, mailerflags=-t, mailfrom=sudo@sudo.ws, mailto=root@localhost, mailsub="*** Sudo information for %h ***"
+Defaults long_otp_prompt
+Defaults ignore_dot
+Defaults !mail_always
+Defaults !mail_badpass
+Defaults !mail_no_user
+Defaults !mail_no_host
+Defaults !mail_no_perms
+Defaults !mail_all_cmnds
+Defaults lecture=always
+Defaults lecture_file=/etc/sudo.lecture
+Defaults authenticate
+Defaults root_sudo
+Defaults shell_noargs
+Defaults set_home
+Defaults always_set_home
+Defaults path_info
+Defaults insults
+Defaults !requiretty
+Defaults env_editor
+Defaults !rootpw
+Defaults !runaspw
+Defaults !targetpw
+Defaults use_loginclass
+Defaults set_logname
+Defaults !stay_setuid
+Defaults !preserve_groups
+Defaults timestamp_timeout=.5
+Defaults passwd_timeout=5
+Defaults passwd_tries=3
+Defaults badpass_message="Take off, eh!"
+Defaults lecture_status_dir=/var/lib/sudo/lectured
+Defaults timestampdir=/run/sudo/ts
+Defaults timestampowner=root
+Defaults exempt_group=sudo
+Defaults passprompt="%p's sudo password: "
+Defaults passprompt_override
+Defaults secure_path=/usr/bin\:/usr/sbin\:/bin\:/sbin
+Defaults editor=/usr/bin/vi
+Defaults listpw=any
+Defaults verifypw=all
+Defaults noexec
+Defaults ignore_local_sudoers
+Defaults closefrom=3
+Defaults closefrom_override
+Defaults !setenv
+Defaults env_reset
+Defaults env_check+=TERMCAP
+Defaults !env_delete
+Defaults env_keep+="LANG LANGUAGE LINGUAS LC_* _XKB_CHARSET"
+Defaults env_file=/etc/environment
+Defaults restricted_env_file=/etc/environment.sudo
+Defaults sudoers_locale=C
+Defaults !visiblepw
+Defaults pwfeedback
+Defaults fast_glob
+Defaults umask_override
+Defaults log_input
+Defaults log_output
+Defaults compress_io
+Defaults use_pty
+Defaults iolog_dir=/var/log/sudo-io
+Defaults iolog_file=%{seq}
+Defaults set_utmp
+Defaults utmp_runas
+Defaults !exec_background
+Defaults pam_service=sudo
+Defaults pam_login_service=sudo-login
+Defaults pam_setcred
+Defaults pam_session
+Defaults pam_acct_mgmt
+Defaults maxseq=2176782336
+Defaults use_netgroups
+Defaults sudoedit_checkdir
+Defaults !sudoedit_follow
+Defaults always_query_group_plugin
+Defaults netgroup_tuple
+Defaults ignore_audit_errors
+Defaults ignore_iolog_errors
+Defaults ignore_logfile_errors
+Defaults !match_group_by_gid
+Defaults iolog_user=root
+Defaults iolog_group=root
+Defaults iolog_mode=0600
+Defaults fdexec=digest_only
+Defaults !ignore_unknown_defaults
+Defaults command_timeout=7d8h30m10s
+Defaults user_command_timeouts
+Defaults iolog_flush
+Defaults syslog_pid
+Defaults timestamp_type=tty
+Defaults authfail_message="Learn to type!"
+Defaults case_insensitive_user
+Defaults case_insensitive_group
+Defaults log_allowed
+Defaults log_denied
+Defaults !log_servers
+Defaults log_server_timeout=10
+Defaults log_server_keepalive
+Defaults !log_server_cabundle
+Defaults !log_server_peer_cert
+Defaults !log_server_peer_key
+Defaults !log_server_verify
+Defaults runas_allow_unknown_id
+Defaults runas_check_shell
+Defaults pam_ruser
+Defaults pam_rhost
+Defaults runcwd=~
+Defaults !selinux
+Defaults !admin_flag
diff --git a/plugins/sudoers/regress/sudoers/test26.toke.ok b/plugins/sudoers/regress/sudoers/test26.toke.ok
new file mode 100644
index 0000000..9e125fe
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test26.toke.ok
@@ -0,0 +1,128 @@
+#
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2) , DEFVAR = WORD(2) , DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS !DEFVAR , DEFVAR , DEFVAR
+DEFAULTS !DEFVAR , DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4) , DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4) , DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4) , DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+
+#
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR += BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR += BEGINSTR STRBODY ENDSTR WORD(4)
+#
+#
+DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+#
+DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+#
+#
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS !DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR
+DEFAULTS DEFVAR = WORD(2)
+DEFAULTS !DEFVAR
+DEFAULTS !DEFVAR
diff --git a/plugins/sudoers/regress/sudoers/test27.in b/plugins/sudoers/regress/sudoers/test27.in
new file mode 100644
index 0000000..9e6c28f
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test27.in
@@ -0,0 +1,13 @@
+# Query the group plugin too (if there is one)
+Defaults always_query_group_plugin
+
+# Test RunasGroup with and without RunasUser
+root ALL = ( root : wheel ) ALL
+millert ALL = ( : wheel ) ALL
+%sudo ALL = ( : ALL ) ALL
+
+# Test RunasUser with %group syntax
+operator ALL = ( %wheel ) ALL
+
+# Test netgroup for user and host
++netusers +nethosts = ( +netrunas ) ALL
diff --git a/plugins/sudoers/regress/sudoers/test27.json.ok b/plugins/sudoers/regress/sudoers/test27.json.ok
new file mode 100644
index 0000000..f097b0e
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test27.json.ok
@@ -0,0 +1,125 @@
+{
+ "Defaults": [
+ {
+ "Options": [
+ { "always_query_group_plugin": true }
+ ]
+ }
+ ],
+ "User_Specs": [
+ {
+ "User_List": [
+ { "username": "root" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "root" }
+ ],
+ "runasgroups": [
+ { "usergroup": "wheel" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "millert" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "" }
+ ],
+ "runasgroups": [
+ { "usergroup": "wheel" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "usergroup": "sudo" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "" }
+ ],
+ "runasgroups": [
+ { "usergroup": "ALL" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "operator" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "usergroup": "wheel" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "netgroup": "netusers" }
+ ],
+ "Host_List": [
+ { "netgroup": "nethosts" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "netgroup": "netrunas" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test27.ldif.ok b/plugins/sudoers/regress/sudoers/test27.ldif.ok
new file mode 100644
index 0000000..e6be300
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test27.ldif.ok
@@ -0,0 +1,60 @@
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: always_query_group_plugin
+
+dn: cn=root,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoHost: ALL
+sudoRunAsUser: root
+sudoRunAsGroup: wheel
+sudoCommand: ALL
+sudoOrder: 1
+
+dn: cn=millert,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: millert
+sudoUser: millert
+sudoHost: ALL
+sudoRunAsUser:
+sudoRunAsGroup: wheel
+sudoCommand: ALL
+sudoOrder: 2
+
+dn: cn=%sudo,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %sudo
+sudoUser: %sudo
+sudoHost: ALL
+sudoRunAsUser:
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 3
+
+dn: cn=operator,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: operator
+sudoUser: operator
+sudoHost: ALL
+sudoRunAsUser: %wheel
+sudoCommand: ALL
+sudoOrder: 4
+
+dn: cn=\+netusers,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: \+netusers
+sudoUser: +netusers
+sudoHost: +nethosts
+sudoRunAsUser: +netrunas
+sudoCommand: ALL
+sudoOrder: 5
+
diff --git a/plugins/sudoers/regress/sudoers/test27.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test27.ldif2sudo.ok
new file mode 100644
index 0000000..a73f459
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test27.ldif2sudo.ok
@@ -0,0 +1,16 @@
+Defaults always_query_group_plugin
+
+# sudoRole root
+root ALL = (root : wheel) ALL
+
+# sudoRole millert
+millert ALL = ( : wheel) ALL
+
+# sudoRole %sudo
+%sudo ALL = ( : ALL) ALL
+
+# sudoRole operator
+operator ALL = (%wheel) ALL
+
+# sudoRole +netusers
++netusers +nethosts = (+netrunas) ALL
diff --git a/plugins/sudoers/regress/sudoers/test27.out.ok b/plugins/sudoers/regress/sudoers/test27.out.ok
new file mode 100644
index 0000000..70cdc04
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test27.out.ok
@@ -0,0 +1,9 @@
+Parses OK
+
+Defaults always_query_group_plugin
+
+root ALL = (root : wheel) ALL
+millert ALL = (root : wheel) ALL
+%sudo ALL = (root : ALL) ALL
+operator ALL = (%wheel) ALL
++netusers +nethosts = (+netrunas) ALL
diff --git a/plugins/sudoers/regress/sudoers/test27.toke.ok b/plugins/sudoers/regress/sudoers/test27.toke.ok
new file mode 100644
index 0000000..e7b3eb6
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test27.toke.ok
@@ -0,0 +1,13 @@
+#
+DEFAULTS DEFVAR
+
+#
+WORD(6) ALL = ( WORD(6) : WORD(6) ) ALL
+WORD(6) ALL = ( : WORD(6) ) ALL
+USERGROUP ALL = ( : ALL ) ALL
+
+#
+WORD(6) ALL = ( USERGROUP ) ALL
+
+#
+NETGROUP NETGROUP = ( NETGROUP ) ALL
diff --git a/plugins/sudoers/regress/sudoers/test28.in b/plugins/sudoers/regress/sudoers/test28.in
new file mode 100644
index 0000000..e546aed
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test28.in
@@ -0,0 +1,36 @@
+# Test passprompt_regex
+Defaults passprompt_regex="(?i)password: *"
+
+# Test simple command with regex args
+user ALL = /bin/ls ^/etc/(hosts|motd|issue)$
+
+# Test wildcard command with regex args
+user ALL = /usr/bin/c* ^/etc/(hosts|motd|issue)$
+
+# Test regex command with no args
+user ALL = ^/usr/bin/(who|w|id|whoami)$
+
+# Test regex command with empty args
+user ALL = ^/usr/bin/(who|w|id|whoami)$ ""
+
+# Test regex command with simple args
+user ALL = ^/usr/bin/(who|w|id|whoami)$ root
+
+# Test regex command with wildcard args
+user ALL = ^/usr/bin/(who|w|id|whoami)$ -*
+
+# Test regex command with regex args
+user ALL = ^/usr/bin/(who|w|id|whoami)$ ^(-[ahi] ?)+$
+
+# Test sudoedit with regex args
+user ALL = sudoedit ^/etc/(hosts|motd|issue)$
+
+# Test regex command with escapted '$', no args
+user ALL = ^/usr/bin/\$tree$
+
+# Combined entry
+user host1 = /bin/ls ^/etc/(hosts|motd|issue)$, \
+ /usr/bin/c* ^/etc/(hosts|motd|issue)$ : \
+ host2 = ^/usr/bin/(who|w|id|whoami)$ "", \
+ ^/usr/bin/(who|w|id|whoami)$ root : \
+ host3 = /bin/echo ^\$foo$
diff --git a/plugins/sudoers/regress/sudoers/test28.json.ok b/plugins/sudoers/regress/sudoers/test28.json.ok
new file mode 100644
index 0000000..4fa4145
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test28.json.ok
@@ -0,0 +1,199 @@
+{
+ "Defaults": [
+ {
+ "Options": [
+ {
+ "operation": "list_assign",
+ "passprompt_regex": [
+ "(?i)password:",
+ "*"
+ ]
+ }
+ ]
+ }
+ ],
+ "User_Specs": [
+ {
+ "User_List": [
+ { "username": "user" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "/bin/ls ^/etc/(hosts|motd|issue)$" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "/usr/bin/c* ^/etc/(hosts|motd|issue)$" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "^/usr/bin/(who|w|id|whoami)$" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "^/usr/bin/(who|w|id|whoami)$ \"\"" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "^/usr/bin/(who|w|id|whoami)$ root" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "^/usr/bin/(who|w|id|whoami)$ -*" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "^/usr/bin/(who|w|id|whoami)$ ^(-[ahi] ?)+$" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "sudoedit ^/etc/(hosts|motd|issue)$" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "^/usr/bin/\\$tree$" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user" }
+ ],
+ "Host_List": [
+ { "hostname": "host1" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "/bin/ls ^/etc/(hosts|motd|issue)$" },
+ { "command": "/usr/bin/c* ^/etc/(hosts|motd|issue)$" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user" }
+ ],
+ "Host_List": [
+ { "hostname": "host2" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "^/usr/bin/(who|w|id|whoami)$ \"\"" },
+ { "command": "^/usr/bin/(who|w|id|whoami)$ root" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user" }
+ ],
+ "Host_List": [
+ { "hostname": "host3" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "/bin/echo ^\\$foo$" }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test28.ldif.ok b/plugins/sudoers/regress/sudoers/test28.ldif.ok
new file mode 100644
index 0000000..1ecc586
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test28.ldif.ok
@@ -0,0 +1,117 @@
+dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: passprompt_regex=(?i)password: *
+
+dn: cn=user,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user
+sudoUser: user
+sudoHost: ALL
+sudoCommand: /bin/ls ^/etc/(hosts|motd|issue)$
+sudoOrder: 1
+
+dn: cn=user_1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user_1
+sudoUser: user
+sudoHost: ALL
+sudoCommand: /usr/bin/c* ^/etc/(hosts|motd|issue)$
+sudoOrder: 2
+
+dn: cn=user_2,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user_2
+sudoUser: user
+sudoHost: ALL
+sudoCommand: ^/usr/bin/(who|w|id|whoami)$
+sudoOrder: 3
+
+dn: cn=user_3,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user_3
+sudoUser: user
+sudoHost: ALL
+sudoCommand: ^/usr/bin/(who|w|id|whoami)$ ""
+sudoOrder: 4
+
+dn: cn=user_4,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user_4
+sudoUser: user
+sudoHost: ALL
+sudoCommand: ^/usr/bin/(who|w|id|whoami)$ root
+sudoOrder: 5
+
+dn: cn=user_5,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user_5
+sudoUser: user
+sudoHost: ALL
+sudoCommand: ^/usr/bin/(who|w|id|whoami)$ -*
+sudoOrder: 6
+
+dn: cn=user_6,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user_6
+sudoUser: user
+sudoHost: ALL
+sudoCommand: ^/usr/bin/(who|w|id|whoami)$ ^(-[ahi] ?)+$
+sudoOrder: 7
+
+dn: cn=user_7,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user_7
+sudoUser: user
+sudoHost: ALL
+sudoCommand: sudoedit ^/etc/(hosts|motd|issue)$
+sudoOrder: 8
+
+dn: cn=user_8,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user_8
+sudoUser: user
+sudoHost: ALL
+sudoCommand: ^/usr/bin/\$tree$
+sudoOrder: 9
+
+dn: cn=user_9,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user_9
+sudoUser: user
+sudoHost: host1
+sudoCommand: /bin/ls ^/etc/(hosts|motd|issue)$
+sudoCommand: /usr/bin/c* ^/etc/(hosts|motd|issue)$
+sudoOrder: 10
+
+dn: cn=user_10,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user_10
+sudoUser: user
+sudoHost: host2
+sudoCommand: ^/usr/bin/(who|w|id|whoami)$ ""
+sudoCommand: ^/usr/bin/(who|w|id|whoami)$ root
+sudoOrder: 11
+
+dn: cn=user_11,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user_11
+sudoUser: user
+sudoHost: host3
+sudoCommand: /bin/echo ^\$foo$
+sudoOrder: 12
+
diff --git a/plugins/sudoers/regress/sudoers/test28.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test28.ldif2sudo.ok
new file mode 100644
index 0000000..0b642fe
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test28.ldif2sudo.ok
@@ -0,0 +1,12 @@
+Defaults passprompt_regex="(?i)password: *"
+
+# sudoRole user, user_1, user_2, user_3, user_4, user_5, user_6, user_7,
+# user_8, user_9, user_10, user_11
+user ALL = /bin/ls ^/etc/(hosts|motd|issue)$, /usr/bin/c*\
+ ^/etc/(hosts|motd|issue)$, ^/usr/bin/(who|w|id|whoami)$,\
+ ^/usr/bin/(who|w|id|whoami)$ "", ^/usr/bin/(who|w|id|whoami)$ root,\
+ ^/usr/bin/(who|w|id|whoami)$ -*, ^/usr/bin/(who|w|id|whoami)$ ^(-[ahi]\
+ ?)+$, sudoedit ^/etc/(hosts|motd|issue)$, ^/usr/bin/\$tree$ : host1 =\
+ /bin/ls ^/etc/(hosts|motd|issue)$, /usr/bin/c* ^/etc/(hosts|motd|issue)$ :\
+ host2 = ^/usr/bin/(who|w|id|whoami)$ "", ^/usr/bin/(who|w|id|whoami)$ root\
+ : host3 = /bin/echo ^\$foo$
diff --git a/plugins/sudoers/regress/sudoers/test28.out.ok b/plugins/sudoers/regress/sudoers/test28.out.ok
new file mode 100644
index 0000000..443573c
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test28.out.ok
@@ -0,0 +1,14 @@
+Parses OK
+
+Defaults passprompt_regex="(?i)password: *"
+
+user ALL = /bin/ls ^/etc/(hosts|motd|issue)$
+user ALL = /usr/bin/c* ^/etc/(hosts|motd|issue)$
+user ALL = ^/usr/bin/(who|w|id|whoami)$
+user ALL = ^/usr/bin/(who|w|id|whoami)$ ""
+user ALL = ^/usr/bin/(who|w|id|whoami)$ root
+user ALL = ^/usr/bin/(who|w|id|whoami)$ -*
+user ALL = ^/usr/bin/(who|w|id|whoami)$ ^(-[ahi] ?)+$
+user ALL = sudoedit ^/etc/(hosts|motd|issue)$
+user ALL = ^/usr/bin/\$tree$
+user host1 = /bin/ls ^/etc/(hosts|motd|issue)$, /usr/bin/c* ^/etc/(hosts|motd|issue)$ : host2 = ^/usr/bin/(who|w|id|whoami)$ "", ^/usr/bin/(who|w|id|whoami)$ root : host3 = /bin/echo ^\$foo$
diff --git a/plugins/sudoers/regress/sudoers/test28.toke.ok b/plugins/sudoers/regress/sudoers/test28.toke.ok
new file mode 100644
index 0000000..03918b9
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test28.toke.ok
@@ -0,0 +1,32 @@
+#
+DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
+
+#
+WORD(6) ALL = COMMAND ARG REGEX
+
+#
+WORD(6) ALL = COMMAND ARG REGEX
+
+#
+WORD(6) ALL = COMMAND
+
+#
+WORD(6) ALL = COMMAND ARG
+
+#
+WORD(6) ALL = COMMAND ARG
+
+#
+WORD(6) ALL = COMMAND ARG
+
+#
+WORD(6) ALL = COMMAND ARG REGEX
+
+#
+WORD(6) ALL = COMMAND ARG REGEX
+
+#
+WORD(6) ALL = COMMAND
+
+#
+WORD(6) WORD(6) = COMMAND ARG REGEX , COMMAND ARG REGEX : WORD(6) = COMMAND ARG , COMMAND ARG : WORD(6) = COMMAND ARG REGEX QUOTEDCHAR
diff --git a/plugins/sudoers/regress/sudoers/test29.in b/plugins/sudoers/regress/sudoers/test29.in
new file mode 100644
index 0000000..34092cd
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test29.in
@@ -0,0 +1,11 @@
+# Test lexer regex syntax errors
+# We don't test regcomp() errors since regerror() strings are not
+# standardized.
+
+user ALL = /bin/ls ^/etc/(hosts|motd|issue
+
+user ALL = ^/bin/ls
+
+user ALL = ^/bin/ls$ ^error
+
+user ALL = ^/bin/ls$ ^error # comment
diff --git a/plugins/sudoers/regress/sudoers/test29.json.ok b/plugins/sudoers/regress/sudoers/test29.json.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test29.json.ok
diff --git a/plugins/sudoers/regress/sudoers/test29.ldif.ok b/plugins/sudoers/regress/sudoers/test29.ldif.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test29.ldif.ok
diff --git a/plugins/sudoers/regress/sudoers/test29.out.ok b/plugins/sudoers/regress/sudoers/test29.out.ok
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test29.out.ok
@@ -0,0 +1 @@
+
diff --git a/plugins/sudoers/regress/sudoers/test29.toke.ok b/plugins/sudoers/regress/sudoers/test29.toke.ok
new file mode 100644
index 0000000..ce3a4a9
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test29.toke.ok
@@ -0,0 +1,11 @@
+#
+#
+#
+
+WORD(6) ALL = COMMAND ARG REGEX ERROR <*>
+
+WORD(6) ALL = WORD(6) <*>
+
+WORD(6) ALL = COMMAND ARG REGEX ERROR <*>
+
+WORD(6) ALL = COMMAND ARG REGEX ERROR <*> #
diff --git a/plugins/sudoers/regress/sudoers/test3.in b/plugins/sudoers/regress/sudoers/test3.in
new file mode 100644
index 0000000..82fcd83
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test3.in
@@ -0,0 +1,6 @@
+# Test whitespace in User_List as part of a per-user Defaults entry
+User_Alias FOO = foo, bar
+Defaults:FOO env_reset
+Defaults:foo,bar env_reset
+Defaults:foo,\ bar env_reset
+Defaults:foo, bar env_reset
diff --git a/plugins/sudoers/regress/sudoers/test3.json.ok b/plugins/sudoers/regress/sudoers/test3.json.ok
new file mode 100644
index 0000000..fc69eb1
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test3.json.ok
@@ -0,0 +1,45 @@
+{
+ "Defaults": [
+ {
+ "Binding": [
+ { "useralias": "FOO" }
+ ],
+ "Options": [
+ { "env_reset": true }
+ ]
+ },
+ {
+ "Binding": [
+ { "username": "foo" },
+ { "username": "bar" }
+ ],
+ "Options": [
+ { "env_reset": true }
+ ]
+ },
+ {
+ "Binding": [
+ { "username": "foo" },
+ { "username": " bar" }
+ ],
+ "Options": [
+ { "env_reset": true }
+ ]
+ },
+ {
+ "Binding": [
+ { "username": "foo" },
+ { "username": "bar" }
+ ],
+ "Options": [
+ { "env_reset": true }
+ ]
+ }
+ ],
+ "User_Aliases": {
+ "FOO": [
+ { "username": "foo" },
+ { "username": "bar" }
+ ]
+ }
+}
diff --git a/plugins/sudoers/regress/sudoers/test3.ldif.ok b/plugins/sudoers/regress/sudoers/test3.ldif.ok
new file mode 100644
index 0000000..783cde5
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test3.ldif.ok
@@ -0,0 +1,12 @@
+# Unable to translate stdin:3:23:
+# Defaults:foo, bar env_reset
+
+# Unable to translate stdin:4:27:
+# Defaults:foo, bar env_reset
+
+# Unable to translate stdin:5:29:
+# Defaults:foo, " bar" env_reset
+
+# Unable to translate stdin:6:28:
+# Defaults:foo, bar env_reset
+
diff --git a/plugins/sudoers/regress/sudoers/test3.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test3.ldif2sudo.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test3.ldif2sudo.ok
diff --git a/plugins/sudoers/regress/sudoers/test3.out.ok b/plugins/sudoers/regress/sudoers/test3.out.ok
new file mode 100644
index 0000000..7f620c4
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test3.out.ok
@@ -0,0 +1,8 @@
+Parses OK
+
+Defaults:FOO env_reset
+Defaults:foo, bar env_reset
+Defaults:foo, " bar" env_reset
+Defaults:foo, bar env_reset
+
+User_Alias FOO = foo, bar
diff --git a/plugins/sudoers/regress/sudoers/test3.toke.ok b/plugins/sudoers/regress/sudoers/test3.toke.ok
new file mode 100644
index 0000000..028f333
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test3.toke.ok
@@ -0,0 +1,6 @@
+#
+USERALIAS ALIAS = WORD(6) , WORD(6)
+DEFAULTS_USER ALIAS DEFVAR
+DEFAULTS_USER WORD(6) , WORD(6) DEFVAR
+DEFAULTS_USER WORD(6) , WORD(6) DEFVAR
+DEFAULTS_USER WORD(6) , WORD(6) DEFVAR
diff --git a/plugins/sudoers/regress/sudoers/test30.in b/plugins/sudoers/regress/sudoers/test30.in
new file mode 100644
index 0000000..c89d739
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test30.in
@@ -0,0 +1,10 @@
+# Test parsing of "list" pseudo-command.
+# It should be allowed as a command but also as a user or host.
+
+user1 ALL = list
+
+list ALL = ALL
+
+user2 ALL = (list : list) ALL
+
+user3 list = ALL
diff --git a/plugins/sudoers/regress/sudoers/test30.json.ok b/plugins/sudoers/regress/sudoers/test30.json.ok
new file mode 100644
index 0000000..e95f2c6
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test30.json.ok
@@ -0,0 +1,79 @@
+{
+ "User_Specs": [
+ {
+ "User_List": [
+ { "username": "user1" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Commands": [
+ { "command": "list" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "list" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user2" }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "username": "list" }
+ ],
+ "runasgroups": [
+ { "usergroup": "list" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "username": "user3" }
+ ],
+ "Host_List": [
+ { "hostname": "list" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test30.ldif.ok b/plugins/sudoers/regress/sudoers/test30.ldif.ok
new file mode 100644
index 0000000..2c1913f
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test30.ldif.ok
@@ -0,0 +1,38 @@
+dn: cn=user1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user1
+sudoUser: user1
+sudoHost: ALL
+sudoCommand: list
+sudoOrder: 1
+
+dn: cn=list,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: list
+sudoUser: list
+sudoHost: ALL
+sudoCommand: ALL
+sudoOrder: 2
+
+dn: cn=user2,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user2
+sudoUser: user2
+sudoHost: ALL
+sudoRunAsUser: list
+sudoRunAsGroup: list
+sudoCommand: ALL
+sudoOrder: 3
+
+dn: cn=user3,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: user3
+sudoUser: user3
+sudoHost: list
+sudoCommand: ALL
+sudoOrder: 4
+
diff --git a/plugins/sudoers/regress/sudoers/test30.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test30.ldif2sudo.ok
new file mode 100644
index 0000000..8e7b68e
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test30.ldif2sudo.ok
@@ -0,0 +1,11 @@
+# sudoRole user1
+user1 ALL = list
+
+# sudoRole list
+list ALL = ALL
+
+# sudoRole user2
+user2 ALL = (list : list) ALL
+
+# sudoRole user3
+user3 list = ALL
diff --git a/plugins/sudoers/regress/sudoers/test30.out.ok b/plugins/sudoers/regress/sudoers/test30.out.ok
new file mode 100644
index 0000000..95fc8ff
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test30.out.ok
@@ -0,0 +1,6 @@
+Parses OK
+
+user1 ALL = list
+list ALL = ALL
+user2 ALL = (list : list) ALL
+user3 list = ALL
diff --git a/plugins/sudoers/regress/sudoers/test30.sudo.ok b/plugins/sudoers/regress/sudoers/test30.sudo.ok
new file mode 100644
index 0000000..1f34b03
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test30.sudo.ok
@@ -0,0 +1,7 @@
+user1 ALL = list
+
+list ALL = ALL
+
+user2 ALL = (list : list) ALL
+
+user3 list = ALL
diff --git a/plugins/sudoers/regress/sudoers/test30.toke.ok b/plugins/sudoers/regress/sudoers/test30.toke.ok
new file mode 100644
index 0000000..a6a05ef
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test30.toke.ok
@@ -0,0 +1,10 @@
+#
+#
+
+WORD(6) ALL = WORD(6)
+
+WORD(6) ALL = ALL
+
+WORD(6) ALL = ( WORD(6) : WORD(6) ) ALL
+
+WORD(6) WORD(6) = ALL
diff --git a/plugins/sudoers/regress/sudoers/test4.in b/plugins/sudoers/regress/sudoers/test4.in
new file mode 100644
index 0000000..b8df454
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test4.in
@@ -0,0 +1,7 @@
+# Test line continuation with anchored matches
+User_Alias FOO = foo \
+: BAR = bar
+
+# This used to pass for sudo < 1.8.1 (though it should not have)
+User_Alias FOO = foo \
+User_Alias BAR = bar
diff --git a/plugins/sudoers/regress/sudoers/test4.json.ok b/plugins/sudoers/regress/sudoers/test4.json.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test4.json.ok
diff --git a/plugins/sudoers/regress/sudoers/test4.ldif.ok b/plugins/sudoers/regress/sudoers/test4.ldif.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test4.ldif.ok
diff --git a/plugins/sudoers/regress/sudoers/test4.out.ok b/plugins/sudoers/regress/sudoers/test4.out.ok
new file mode 100644
index 0000000..de27db3
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test4.out.ok
@@ -0,0 +1,3 @@
+
+User_Alias BAR = bar
+User_Alias FOO = foo
diff --git a/plugins/sudoers/regress/sudoers/test4.toke.ok b/plugins/sudoers/regress/sudoers/test4.toke.ok
new file mode 100644
index 0000000..a808e5a
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test4.toke.ok
@@ -0,0 +1,5 @@
+#
+USERALIAS ALIAS = WORD(6) : ALIAS = WORD(6)
+
+#
+USERALIAS ALIAS = WORD(6) ERROR <*> ALIAS = WORD(6)
diff --git a/plugins/sudoers/regress/sudoers/test5.in b/plugins/sudoers/regress/sudoers/test5.in
new file mode 100644
index 0000000..354f589
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test5.in
@@ -0,0 +1,3 @@
+# Test empty string in User_Alias and Command_Spec
+User_Alias FOO = ""
+"" ALL = ALL
diff --git a/plugins/sudoers/regress/sudoers/test5.json.ok b/plugins/sudoers/regress/sudoers/test5.json.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test5.json.ok
diff --git a/plugins/sudoers/regress/sudoers/test5.ldif.ok b/plugins/sudoers/regress/sudoers/test5.ldif.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test5.ldif.ok
diff --git a/plugins/sudoers/regress/sudoers/test5.out.ok b/plugins/sudoers/regress/sudoers/test5.out.ok
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test5.out.ok
@@ -0,0 +1 @@
+
diff --git a/plugins/sudoers/regress/sudoers/test5.toke.ok b/plugins/sudoers/regress/sudoers/test5.toke.ok
new file mode 100644
index 0000000..9376455
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test5.toke.ok
@@ -0,0 +1,3 @@
+#
+USERALIAS ALIAS = BEGINSTR ENDSTR ERROR <*>
+BEGINSTR ENDSTR ERROR <*> ALL = ALL
diff --git a/plugins/sudoers/regress/sudoers/test6.in b/plugins/sudoers/regress/sudoers/test6.in
new file mode 100644
index 0000000..e804571
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test6.in
@@ -0,0 +1,15 @@
+# Check that uids work in per-user and per-runas Defaults
+Defaults:#123 set_home
+Defaults>#123 set_home
+Defaults:"#123" set_home
+Defaults>"#123" set_home
+
+# Check that uids work in a Command_Spec
+#0 ALL = ALL
+#0 ALL = (#0 : #0) ALL
+"#0" ALL = ALL
+"#0" ALL = ("#0" : "#0") ALL
+
+# Check that gids work in a Command_Spec
+%#0 ALL = ALL
+"%#0" ALL = ALL
diff --git a/plugins/sudoers/regress/sudoers/test6.json.ok b/plugins/sudoers/regress/sudoers/test6.json.ok
new file mode 100644
index 0000000..be1f80f
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test6.json.ok
@@ -0,0 +1,158 @@
+{
+ "Defaults": [
+ {
+ "Binding": [
+ { "userid": 123 }
+ ],
+ "Options": [
+ { "set_home": true }
+ ]
+ },
+ {
+ "Binding": [
+ { "userid": 123 }
+ ],
+ "Options": [
+ { "set_home": true }
+ ]
+ },
+ {
+ "Binding": [
+ { "userid": 123 }
+ ],
+ "Options": [
+ { "set_home": true }
+ ]
+ },
+ {
+ "Binding": [
+ { "userid": 123 }
+ ],
+ "Options": [
+ { "set_home": true }
+ ]
+ }
+ ],
+ "User_Specs": [
+ {
+ "User_List": [
+ { "userid": 0 }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "userid": 0 }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "userid": 0 }
+ ],
+ "runasgroups": [
+ { "usergroup": "#0" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "userid": 0 }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "userid": 0 }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "runasusers": [
+ { "userid": 0 }
+ ],
+ "runasgroups": [
+ { "usergroup": "#0" }
+ ],
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "usergid": 0 }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ },
+ {
+ "User_List": [
+ { "usergid": 0 }
+ ],
+ "Host_List": [
+ { "hostname": "ALL" }
+ ],
+ "Cmnd_Specs": [
+ {
+ "Options": [
+ { "setenv": true }
+ ],
+ "Commands": [
+ { "command": "ALL" }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/plugins/sudoers/regress/sudoers/test6.ldif.ok b/plugins/sudoers/regress/sudoers/test6.ldif.ok
new file mode 100644
index 0000000..046b334
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test6.ldif.ok
@@ -0,0 +1,70 @@
+# Unable to translate stdin:2:23:
+# Defaults:#123 set_home
+
+# Unable to translate stdin:3:23:
+# Defaults>#123 set_home
+
+# Unable to translate stdin:4:25:
+# Defaults:#123 set_home
+
+# Unable to translate stdin:5:25:
+# Defaults>#123 set_home
+
+dn: cn=\#0,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: \#0
+sudoUser: #0
+sudoHost: ALL
+sudoCommand: ALL
+sudoOrder: 1
+
+dn: cn=\#0_1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: \#0_1
+sudoUser: #0
+sudoHost: ALL
+sudoRunAsUser: #0
+sudoRunAsGroup: #0
+sudoCommand: ALL
+sudoOrder: 2
+
+dn: cn=\#0_2,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: \#0_2
+sudoUser: #0
+sudoHost: ALL
+sudoCommand: ALL
+sudoOrder: 3
+
+dn: cn=\#0_3,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: \#0_3
+sudoUser: #0
+sudoHost: ALL
+sudoRunAsUser: #0
+sudoRunAsGroup: #0
+sudoCommand: ALL
+sudoOrder: 4
+
+dn: cn=%\#0,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %\#0
+sudoUser: %#0
+sudoHost: ALL
+sudoCommand: ALL
+sudoOrder: 5
+
+dn: cn=%\#0_1,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: %\#0_1
+sudoUser: %#0
+sudoHost: ALL
+sudoCommand: ALL
+sudoOrder: 6
+
diff --git a/plugins/sudoers/regress/sudoers/test6.ldif2sudo.ok b/plugins/sudoers/regress/sudoers/test6.ldif2sudo.ok
new file mode 100644
index 0000000..bfe40bb
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test6.ldif2sudo.ok
@@ -0,0 +1,5 @@
+# sudoRole #0, #0_1, #0_2, #0_3
+#0 ALL = ALL, (#0 : #0) ALL, ALL, (#0 : #0) ALL
+
+# sudoRole %#0, %#0_1
+%#0 ALL = ALL, ALL
diff --git a/plugins/sudoers/regress/sudoers/test6.out.ok b/plugins/sudoers/regress/sudoers/test6.out.ok
new file mode 100644
index 0000000..73b8fe0
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test6.out.ok
@@ -0,0 +1,13 @@
+Parses OK
+
+Defaults:#123 set_home
+Defaults>#123 set_home
+Defaults:#123 set_home
+Defaults>#123 set_home
+
+#0 ALL = ALL
+#0 ALL = (#0 : #0) ALL
+#0 ALL = ALL
+#0 ALL = (#0 : #0) ALL
+%#0 ALL = ALL
+%#0 ALL = ALL
diff --git a/plugins/sudoers/regress/sudoers/test6.toke.ok b/plugins/sudoers/regress/sudoers/test6.toke.ok
new file mode 100644
index 0000000..db8e1c5
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test6.toke.ok
@@ -0,0 +1,15 @@
+#
+DEFAULTS_USER WORD(6) DEFVAR
+DEFAULTS_RUNAS WORD(6) DEFVAR
+DEFAULTS_USER BEGINSTR STRBODY ENDSTR WORD(4) DEFVAR
+DEFAULTS_RUNAS BEGINSTR STRBODY ENDSTR WORD(4) DEFVAR
+
+#
+WORD(6) ALL = ALL
+WORD(6) ALL = ( WORD(6) : WORD(6) ) ALL
+BEGINSTR STRBODY ENDSTR WORD(4) ALL = ALL
+BEGINSTR STRBODY ENDSTR WORD(4) ALL = ( BEGINSTR STRBODY ENDSTR WORD(4) : BEGINSTR STRBODY ENDSTR WORD(4) ) ALL
+
+#
+USERGROUP ALL = ALL
+BEGINSTR STRBODY ENDSTR USERGROUP ALL = ALL
diff --git a/plugins/sudoers/regress/sudoers/test7.in b/plugins/sudoers/regress/sudoers/test7.in
new file mode 100644
index 0000000..7b241d0
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test7.in
@@ -0,0 +1,7 @@
+# These should all be syntax errors
+User_Alias FOO1 = "%"
+User_Alias FOO2 = "%:"
+User_Alias FOO3 = "+"
+User_Alias FOO4 = %
+User_Alias FOO5 = %:
+User_Alias FOO6 = +
diff --git a/plugins/sudoers/regress/sudoers/test7.json.ok b/plugins/sudoers/regress/sudoers/test7.json.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test7.json.ok
diff --git a/plugins/sudoers/regress/sudoers/test7.ldif.ok b/plugins/sudoers/regress/sudoers/test7.ldif.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test7.ldif.ok
diff --git a/plugins/sudoers/regress/sudoers/test7.out.ok b/plugins/sudoers/regress/sudoers/test7.out.ok
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test7.out.ok
@@ -0,0 +1 @@
+
diff --git a/plugins/sudoers/regress/sudoers/test7.toke.ok b/plugins/sudoers/regress/sudoers/test7.toke.ok
new file mode 100644
index 0000000..a5bf018
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test7.toke.ok
@@ -0,0 +1,7 @@
+#
+USERALIAS ALIAS = BEGINSTR STRBODY ENDSTR ERROR <*>
+USERALIAS ALIAS = BEGINSTR STRBODY ENDSTR ERROR <*>
+USERALIAS ALIAS = BEGINSTR STRBODY ENDSTR ERROR <*>
+USERALIAS ALIAS = ERROR <*>
+USERALIAS ALIAS = ERROR <*>
+USERALIAS ALIAS = ERROR <*>
diff --git a/plugins/sudoers/regress/sudoers/test8.in b/plugins/sudoers/regress/sudoers/test8.in
new file mode 100644
index 0000000..d25e834
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test8.in
@@ -0,0 +1,8 @@
+# Test quoted strings
+User_Alias UA1 = "xy"
+User_Alias UA2 = "x\
+y"
+User_Alias UA3 = x\"y
+
+# A newline in the middle of a string is an error
+User_Alias UA4 = "x
diff --git a/plugins/sudoers/regress/sudoers/test8.json.ok b/plugins/sudoers/regress/sudoers/test8.json.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test8.json.ok
diff --git a/plugins/sudoers/regress/sudoers/test8.ldif.ok b/plugins/sudoers/regress/sudoers/test8.ldif.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test8.ldif.ok
diff --git a/plugins/sudoers/regress/sudoers/test8.out.ok b/plugins/sudoers/regress/sudoers/test8.out.ok
new file mode 100644
index 0000000..e62f97f
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test8.out.ok
@@ -0,0 +1,4 @@
+
+User_Alias UA1 = xy
+User_Alias UA2 = xy
+User_Alias UA3 = x\"y
diff --git a/plugins/sudoers/regress/sudoers/test8.toke.ok b/plugins/sudoers/regress/sudoers/test8.toke.ok
new file mode 100644
index 0000000..1bc46cb
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test8.toke.ok
@@ -0,0 +1,7 @@
+#
+USERALIAS ALIAS = BEGINSTR STRBODY ENDSTR WORD(4)
+USERALIAS ALIAS = BEGINSTR STRBODY STRBODY ENDSTR WORD(4)
+USERALIAS ALIAS = WORD(6)
+
+#
+USERALIAS ALIAS = BEGINSTR STRBODY ERROR <*>
diff --git a/plugins/sudoers/regress/sudoers/test9.in b/plugins/sudoers/regress/sudoers/test9.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test9.in
diff --git a/plugins/sudoers/regress/sudoers/test9.json.ok b/plugins/sudoers/regress/sudoers/test9.json.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test9.json.ok
diff --git a/plugins/sudoers/regress/sudoers/test9.ldif.ok b/plugins/sudoers/regress/sudoers/test9.ldif.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test9.ldif.ok
diff --git a/plugins/sudoers/regress/sudoers/test9.out.ok b/plugins/sudoers/regress/sudoers/test9.out.ok
new file mode 100644
index 0000000..5af5c53
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test9.out.ok
@@ -0,0 +1,2 @@
+Parses OK
+
diff --git a/plugins/sudoers/regress/sudoers/test9.toke.ok b/plugins/sudoers/regress/sudoers/test9.toke.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/sudoers/test9.toke.ok
diff --git a/plugins/sudoers/regress/testsudoers/group b/plugins/sudoers/regress/testsudoers/group
new file mode 100644
index 0000000..f272010
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/group
@@ -0,0 +1,17 @@
+wheel:*:0:root
+daemon:*:1:daemon
+kmem:*:2:root
+sys:*:3:root
+tty:*:4:root
+operator:*:5:root
+bin:*:7:
+wsrc:*:9:
+users:*:10:
+auth:*:11:
+games:*:13:
+staff:*:20:root
+guest:*:31:root
+admin:*:1000:
+fakeshell:*:1001:
+nogroup:*:32766:
+nobody:*:32767:
diff --git a/plugins/sudoers/regress/testsudoers/passwd b/plugins/sudoers/regress/testsudoers/passwd
new file mode 100644
index 0000000..c3d0a9c
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/passwd
@@ -0,0 +1,7 @@
+root:*:0:0:Charlie &:/root:/bin/sh
+daemon:*:1:1:The devil himself:/root:/sbin/nologin
+operator:*:2:5:System &:/operator:/sbin/nologin
+bin:*:3:7:Binaries Commands and Source:/:/sbin/nologin
+admin:*:1000:1000:Admin user:/home/admin:/bin/sh
+fakeshell:*:1001:1001:Shell test user:/home/fakeshell:/shell/does/not/exist
+nobody:*:32767:32767:Unprivileged user:/nonexistent:/sbin/nologin
diff --git a/plugins/sudoers/regress/testsudoers/test1.out.ok b/plugins/sudoers/regress/testsudoers/test1.out.ok
new file mode 100644
index 0000000..06c27c4
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test1.out.ok
@@ -0,0 +1,11 @@
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas unmatched
+
+Password required
+
+Command unmatched
diff --git a/plugins/sudoers/regress/testsudoers/test1.sh b/plugins/sudoers/regress/testsudoers/test1.sh
new file mode 100755
index 0000000..495f237
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test1.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# Test for NULL dereference with "sudo -g group" when the sudoers rule
+# has no runas user or group listed.
+# This is RedHat bug Bug 667103.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+$TESTSUDOERS -g bin -P ${TESTDIR}/group root id <<EOF
+root ALL = ALL
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test10.out.ok b/plugins/sudoers/regress/testsudoers/test10.out.ok
new file mode 100644
index 0000000..94e912e
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test10.out.ok
@@ -0,0 +1,59 @@
+Testing @include of a path with escaped white space
+
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+Testing @include of a double-quoted path with white space
+
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+Testing #include of a path with escaped white space
+
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+Testing #include of a double-quoted path with white space
+
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test10.sh b/plugins/sudoers/regress/testsudoers/test10.sh
new file mode 100755
index 0000000..c4f0f2d
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test10.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# Test @include of a file with embedded white space
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+# Create test file
+TESTDIR="`pwd`/regress/testsudoers"
+cat >"$TESTDIR/test 10.inc" <<EOF
+root ALL = ALL
+EOF
+
+MYUID=`\ls -lnd "$TESTDIR/test 10.inc" | awk '{print $3}'`
+MYGID=`\ls -lnd "$TESTDIR/test 10.inc" | awk '{print $4}'`
+exec 2>&1
+
+echo "Testing @include of a path with escaped white space"
+echo ""
+$TESTSUDOERS -U $MYUID -G $MYGID root id <<-EOF
+ @include $TESTDIR/test\ 10.inc
+EOF
+
+echo ""
+echo "Testing @include of a double-quoted path with white space"
+echo ""
+$TESTSUDOERS -U $MYUID -G $MYGID root id <<-EOF
+ @include "$TESTDIR/test 10.inc"
+EOF
+
+echo ""
+echo "Testing #include of a path with escaped white space"
+echo ""
+$TESTSUDOERS -U $MYUID -G $MYGID root id <<-EOF
+ #include $TESTDIR/test\ 10.inc
+EOF
+
+echo ""
+echo "Testing #include of a double-quoted path with white space"
+echo ""
+$TESTSUDOERS -U $MYUID -G $MYGID root id <<-EOF
+ #include "$TESTDIR/test 10.inc"
+EOF
+
+rm -f "$TESTDIR/test 10.inc"
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test11.out.ok b/plugins/sudoers/regress/testsudoers/test11.out.ok
new file mode 100644
index 0000000..ee98540
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test11.out.ok
@@ -0,0 +1,25 @@
+Testing @include with garbage after the path name
+
+sudoers:1:24: syntax error
+@include sudoers.local womp womp
+ ^~~~
+testsudoers: unable to open sudoers.local: No such file or directory
+
+Entries for user root:
+
+Password required
+
+Parse error
+
+Testing #include with garbage after the path name
+
+sudoers:1:24: syntax error
+#include sudoers.local womp womp
+ ^~~~
+testsudoers: unable to open sudoers.local: No such file or directory
+
+Entries for user root:
+
+Password required
+
+Parse error
diff --git a/plugins/sudoers/regress/testsudoers/test11.sh b/plugins/sudoers/regress/testsudoers/test11.sh
new file mode 100755
index 0000000..d52754d
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test11.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Test @include with garbage after the path name
+# The standard error output is dup'd to the standard output.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+MYUID=`\ls -ln $TESTDIR/test2.inc | awk '{print $3}'`
+MYGID=`\ls -ln $TESTDIR/test2.inc | awk '{print $4}'`
+
+echo "Testing @include with garbage after the path name"
+echo ""
+$TESTSUDOERS -U $MYUID -G $MYGID root id <<EOF 2>&1 | sed 's/\(syntax error\), .*/\1/'
+@include sudoers.local womp womp
+EOF
+
+echo ""
+echo "Testing #include with garbage after the path name"
+echo ""
+$TESTSUDOERS -U $MYUID -G $MYGID root id <<EOF 2>&1 | sed 's/\(syntax error\), .*/\1/'
+#include sudoers.local womp womp
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test12.out.ok b/plugins/sudoers/regress/testsudoers/test12.out.ok
new file mode 100644
index 0000000..a28a831
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test12.out.ok
@@ -0,0 +1,18 @@
+Testing sudoers with multiple syntax errors
+
+sudoers:1:20: syntax error
+User_Alias A1 = u1 u2 : A2 = u3, u4
+ ^~
+sudoers:3:26: syntax error
+millert ALL = /fail : foo
+ ^
+sudoers:5:16: syntax error
+root ALL = ALL bar
+ ^~~
+sudoers:7:12: expected a fully-qualified path name
+root ALL = baz
+ ^~~
+
+User_Alias A1 = u1
+
+millert ALL = /fail
diff --git a/plugins/sudoers/regress/testsudoers/test12.sh b/plugins/sudoers/regress/testsudoers/test12.sh
new file mode 100755
index 0000000..8890ca5
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test12.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+#
+# Test sudoers file with multiple syntax errors
+# The standard error output is dup'd to the standard output.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+echo "Testing sudoers with multiple syntax errors"
+echo ""
+$TESTSUDOERS -d <<EOF 2>&1 | sed 's/\(syntax error\), .*/\1/'
+User_Alias A1 = u1 u2 : A2 = u3, u4
+
+millert ALL = /fail : foo
+
+root ALL = ALL bar
+
+root ALL = baz
+EOF
diff --git a/plugins/sudoers/regress/testsudoers/test13.out.ok b/plugins/sudoers/regress/testsudoers/test13.out.ok
new file mode 100644
index 0000000..bfb9f53
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test13.out.ok
@@ -0,0 +1,22 @@
+Testing alias definitions using reserved words
+
+sudoers:1:12: syntax error, reserved word ALL used as an alias name
+Cmnd_Alias ALL=ALL
+ ^~~
+sudoers:2:12: syntax error, reserved word CHROOT used as an alias name
+Cmnd_Alias CHROOT=foo
+ ^~~~~~
+sudoers:3:12: syntax error, reserved word CMND_TIMEOUT used as an alias name
+User_Alias TIMEOUT=foo
+ ^~~~~~~
+sudoers:4:13: syntax error, reserved word CWD used as an alias name
+Runas_Alias CWD=bar
+ ^~~
+sudoers:5:12: syntax error, reserved word NOTBEFORE used as an alias name
+Host_Alias NOTBEFORE=baz
+ ^~~~~~~~~
+sudoers:6:12: syntax error, reserved word NOTAFTER used as an alias name
+Host_Alias NOTAFTER=biff
+ ^~~~~~~~
+
+root ALL = ALL
diff --git a/plugins/sudoers/regress/testsudoers/test13.sh b/plugins/sudoers/regress/testsudoers/test13.sh
new file mode 100755
index 0000000..d9c3d0c
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test13.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# Test sudoers file with reserved words as alias names.
+# The standard error output is dup'd to the standard output.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+echo "Testing alias definitions using reserved words"
+echo ""
+$TESTSUDOERS -d <<EOF 2>&1
+Cmnd_Alias ALL=ALL
+Cmnd_Alias CHROOT=foo
+User_Alias TIMEOUT=foo
+Runas_Alias CWD=bar
+Host_Alias NOTBEFORE=baz
+Host_Alias NOTAFTER=biff
+
+root ALL = ALL
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test14.out.ok b/plugins/sudoers/regress/testsudoers/test14.out.ok
new file mode 100644
index 0000000..add1bb2
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test14.out.ok
@@ -0,0 +1,18 @@
+
+Testing user privilege without a newline
+
+Parses OK
+
+millert ALL = ALL
+
+Testing alias without a newline
+
+Parses OK
+
+Cmnd_Alias FOO = /bin/bar
+
+Testing Defaults without a newline
+
+Parses OK
+
+Defaults log_output
diff --git a/plugins/sudoers/regress/testsudoers/test14.sh b/plugins/sudoers/regress/testsudoers/test14.sh
new file mode 100755
index 0000000..7739c67
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test14.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Test entries with no trailing newline.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+
+echo ""
+echo "Testing user privilege without a newline"
+echo ""
+printf "millert ALL = ALL" | $TESTSUDOERS -d
+
+echo ""
+echo "Testing alias without a newline"
+echo ""
+printf "Cmnd_Alias FOO=/bin/bar" | $TESTSUDOERS -d
+
+echo ""
+echo "Testing Defaults without a newline"
+echo ""
+printf "Defaults log_output" | $TESTSUDOERS -d
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test15.out.ok b/plugins/sudoers/regress/testsudoers/test15.out.ok
new file mode 100644
index 0000000..cc4361d
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test15.out.ok
@@ -0,0 +1,19 @@
+Testing @include of a file with a missing newline
+
+Parses OK
+
+Entries for user root:
+
+ALL = /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd unmatched
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test15.sh b/plugins/sudoers/regress/testsudoers/test15.sh
new file mode 100755
index 0000000..a4596cd
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test15.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# Test @include of a file with a missing newline
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+# Create test file
+TESTDIR="`pwd`/regress/testsudoers"
+printf "root ALL = ALL" >"$TESTDIR/test15.inc"
+
+MYUID=`\ls -lnd "$TESTDIR/test15.inc" | awk '{print $3}'`
+MYGID=`\ls -lnd "$TESTDIR/test15.inc" | awk '{print $4}'`
+exec 2>&1
+
+echo "Testing @include of a file with a missing newline"
+echo ""
+$TESTSUDOERS -U $MYUID -G $MYGID root id <<-EOF
+ @include $TESTDIR/test15.inc
+ ALL ALL = /usr/bin/id
+EOF
+
+rm -f "$TESTDIR/test15.inc"
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test16.out.ok b/plugins/sudoers/regress/testsudoers/test16.out.ok
new file mode 100644
index 0000000..3c4e7fa
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test16.out.ok
@@ -0,0 +1,12 @@
+Parses OK
+
+Entries for user root:
+
+ALL = (ALL) ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test16.sh b/plugins/sudoers/regress/testsudoers/test16.sh
new file mode 100755
index 0000000..507bdd4
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test16.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Test to exercise Bug #994, a crash matching sudoCommand ALL.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+$TESTSUDOERS -i ldif root id <<-EOF
+dn: dc=sudo,dc=ws
+objectClass: dcObject
+objectClass: organization
+dc: bigwheel
+o: Big Wheel
+description: Big Wheel
+
+# Organizational Role for Directory Manager
+dn: cn=Manager,dc=sudo,dc=ws
+objectClass: organizationalRole
+cn: Manager
+description: Directory Manager
+
+# SUDOers, sudo.ws
+dn: ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: organizationalUnit
+description: SUDO Configuration Subtree
+ou: SUDOers
+
+# root, SUDOers, sudo.ws
+dn: cn=root,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoRunAs: ALL
+sudoHost: ALL
+sudoCommand: ALL
+sudoOrder: 10
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test17.out.ok b/plugins/sudoers/regress/testsudoers/test17.out.ok
new file mode 100644
index 0000000..56c4715
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test17.out.ok
@@ -0,0 +1,12 @@
+Parses OK
+
+Entries for user root:
+
+ALL = (ALL) sha224:fIoq2MAfM/PZKTbkn9RE4VZ8YHjwnwTgE28Hxw== ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test17.sh b/plugins/sudoers/regress/testsudoers/test17.sh
new file mode 100755
index 0000000..b98b907
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test17.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+#
+# Test that digest matching works with LDAP sudoCommand: ALL
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+# Create test command with known digest
+TESTDIR="`pwd`/regress/testsudoers"
+cat >"$TESTDIR/hello" <<EOF
+#!/bin/sh
+echo Hello World
+EOF
+chmod 755 "$TESTDIR/hello"
+SHA224_DIGEST="fIoq2MAfM/PZKTbkn9RE4VZ8YHjwnwTgE28Hxw=="
+
+$TESTSUDOERS -i ldif root "${TESTDIR}/hello" <<-EOF
+dn: dc=sudo,dc=ws
+objectClass: dcObject
+objectClass: organization
+dc: bigwheel
+o: Big Wheel
+description: Big Wheel
+
+# Organizational Role for Directory Manager
+dn: cn=Manager,dc=sudo,dc=ws
+objectClass: organizationalRole
+cn: Manager
+description: Directory Manager
+
+# SUDOers, sudo.ws
+dn: ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: organizationalUnit
+description: SUDO Configuration Subtree
+ou: SUDOers
+
+# root, SUDOers, sudo.ws
+dn: cn=root,ou=SUDOers,dc=sudo,dc=ws
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoRunAs: ALL
+sudoHost: ALL
+sudoCommand: sha224:$SHA224_DIGEST ALL
+sudoOrder: 10
+EOF
+
+rm -f "$TESTDIR/hello"
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test18.out.ok b/plugins/sudoers/regress/testsudoers/test18.out.ok
new file mode 100644
index 0000000..c497a7a
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test18.out.ok
@@ -0,0 +1,72 @@
+Parses OK
+
+Entries for user root:
+
+ALL = ^/bin/ls$ ^-[lAt]$
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+Parses OK
+
+Entries for user root:
+
+ALL = ^/bin/cat$ /var/log/*
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+Parses OK
+
+Entries for user root:
+
+ALL = /bin/cat ^/var/log/[^/]+$
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+Parses OK
+
+Entries for user root:
+
+ALL = /bin/*at ^/var/log/[^/]+$
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+Parses OK
+
+Entries for user root:
+
+ALL = /usr/bin/grep \^foo$
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+Parses OK
+
+Entries for user root:
+
+ALL = sudoedit ^/etc/(motd|issue|hosts)$
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test18.sh b/plugins/sudoers/regress/testsudoers/test18.sh
new file mode 100755
index 0000000..645b9a5
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test18.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+#
+# Test regular expressions
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+
+# Command and args: regex
+$TESTSUDOERS root /bin/ls -l <<'EOF'
+root ALL = ^/bin/ls$ ^-[lAt]$
+EOF
+
+# Command: regex, args: wildcard
+$TESTSUDOERS root /bin/cat /var/log/syslog <<'EOF'
+root ALL = ^/bin/cat$ /var/log/*
+EOF
+
+# Command: path, args: regex
+$TESTSUDOERS root /bin/cat /var/log/authlog <<'EOF'
+root ALL = /bin/cat ^/var/log/[^/]+$
+EOF
+
+# Command: wildcard, args: regex
+$TESTSUDOERS root /bin/cat /var/log/mail <<'EOF'
+root ALL = /bin/*at ^/var/log/[^/]+$
+EOF
+
+# Command: path, args: args start with escaped ^
+$TESTSUDOERS root /usr/bin/grep '^foo$' <<'EOF'
+root ALL = /usr/bin/grep \^foo$
+EOF
+
+# Command: sudoedit, args: regex
+$TESTSUDOERS root sudoedit /etc/motd <<'EOF'
+root ALL = sudoedit ^/etc/(motd|issue|hosts)$
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test19.out.ok b/plugins/sudoers/regress/testsudoers/test19.out.ok
new file mode 100644
index 0000000..db2142d
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test19.out.ok
@@ -0,0 +1,24 @@
+Parses OK
+
+Entries for user root:
+
+ALL = /bin/ls ""
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+Parses OK
+
+Entries for user root:
+
+ALL = /bin/ls ""
+ host allowed
+ runas allowed
+ cmnd unmatched
+
+Password required
+
+Command unmatched
diff --git a/plugins/sudoers/regress/testsudoers/test19.sh b/plugins/sudoers/regress/testsudoers/test19.sh
new file mode 100755
index 0000000..113eb2a
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test19.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# Verify that "" in sudoers does not match a literal "" on the command line.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+
+# This should succeed
+$TESTSUDOERS root /bin/ls <<'EOF'
+root ALL = /bin/ls ""
+EOF
+
+# This should fail
+$TESTSUDOERS root /bin/ls '""' <<'EOF'
+root ALL = /bin/ls ""
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test2.inc b/plugins/sudoers/regress/testsudoers/test2.inc
new file mode 100644
index 0000000..52ca040
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test2.inc
@@ -0,0 +1 @@
+root ALL = ALL
diff --git a/plugins/sudoers/regress/testsudoers/test2.out.ok b/plugins/sudoers/regress/testsudoers/test2.out.ok
new file mode 100644
index 0000000..a017d8a
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test2.out.ok
@@ -0,0 +1,29 @@
+Testing @include
+
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+Testing #include
+
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test2.sh b/plugins/sudoers/regress/testsudoers/test2.sh
new file mode 100755
index 0000000..0b0b3f8
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test2.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Test @include facility
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+MYUID=`\ls -ln $TESTDIR/test2.inc | awk '{print $3}'`
+MYGID=`\ls -ln $TESTDIR/test2.inc | awk '{print $4}'`
+exec 2>&1
+
+echo "Testing @include"
+echo ""
+$TESTSUDOERS -U $MYUID -G $MYGID root id <<EOF
+@include $TESTDIR/test2.inc
+EOF
+
+echo ""
+echo "Testing #include"
+echo ""
+$TESTSUDOERS -U $MYUID -G $MYGID root id <<EOF
+#include $TESTDIR/test2.inc
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test20.out.ok b/plugins/sudoers/regress/testsudoers/test20.out.ok
new file mode 100644
index 0000000..6a8e451
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test20.out.ok
@@ -0,0 +1,12 @@
+Parses OK
+
+Entries for user root:
+
+ALL = CHROOT=/ /bin/ls
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test20.sh b/plugins/sudoers/regress/testsudoers/test20.sh
new file mode 100755
index 0000000..4325175
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test20.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+# Verify CHROOT and CWD support
+# This will catch an unpatched double-free in set_cmnd_path() under ASAN.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+
+# Exercise double free of user_cmnd in set_cmnd_path() under ASAN.
+# We need more than one rule where the last rule matches and has CHROOT.
+$TESTSUDOERS root /bin/ls <<'EOF'
+root ALL = CWD=/ /bin/pwd
+root ALL = CHROOT=/ /bin/ls
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test21.out.ok b/plugins/sudoers/regress/testsudoers/test21.out.ok
new file mode 100644
index 0000000..391b668
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test21.out.ok
@@ -0,0 +1,12 @@
+Parses OK
+
+Entries for user admin:
+
+ALL = (USERALIAS : GROUPALIAS) /bin/ls
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test21.sh b/plugins/sudoers/regress/testsudoers/test21.sh
new file mode 100755
index 0000000..714caf1
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test21.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# Verify that a Runas_Alias works in both user and group lists.
+# This tests a bug fixed in sudo 1.9.14.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+
+# The user in USERALIAS must *not* belong to the group in GROUPALIAS
+# in the group or passwd file in order to reproduce the bug.
+$TESTSUDOERS -u root -g bin -p ${TESTDIR}/passwd -P ${TESTDIR}/group \
+ admin /bin/ls <<'EOF'
+Runas_Alias USERALIAS = root
+Runas_Alias GROUPALIAS = bin
+admin ALL = (USERALIAS : GROUPALIAS) /bin/ls
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test22.out.ok b/plugins/sudoers/regress/testsudoers/test22.out.ok
new file mode 100644
index 0000000..54f273f
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test22.out.ok
@@ -0,0 +1,11 @@
+Parses OK
+
+Entries for user admin:
+
+ALL = /bin/ls
+ host allowed
+ runas unmatched
+
+Password required
+
+Command unmatched
diff --git a/plugins/sudoers/regress/testsudoers/test22.sh b/plugins/sudoers/regress/testsudoers/test22.sh
new file mode 100755
index 0000000..9d4dbcb
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test22.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+# Verify that a user is only allowed to run commands with a group
+# that is specified by sudoers (or that the runas user is a member of).
+# This tests a bug fixed in sudo 1.9.14.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+
+# The root user must *not* belong to the group specified below.
+$TESTSUDOERS -u root -g bin -p ${TESTDIR}/passwd -P ${TESTDIR}/group \
+ admin /bin/ls <<'EOF'
+admin ALL = /bin/ls
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test23.out.ok b/plugins/sudoers/regress/testsudoers/test23.out.ok
new file mode 100644
index 0000000..2e99ac8
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test23.out.ok
@@ -0,0 +1,11 @@
+Parses OK
+
+Entries for user admin:
+
+ALL = (root) /bin/ls
+ host allowed
+ runas unmatched
+
+Password required
+
+Command unmatched
diff --git a/plugins/sudoers/regress/testsudoers/test23.sh b/plugins/sudoers/regress/testsudoers/test23.sh
new file mode 100755
index 0000000..a790c6a
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test23.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# Verify that a user is not allowed to run commands with their own
+# user and group if sudoers doesn't explicitly permit it.
+# This tests a bug fixed in sudo 1.9.14.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+
+$TESTSUDOERS -u admin -g admin -p ${TESTDIR}/passwd -P ${TESTDIR}/group \
+ admin /bin/ls <<'EOF'
+admin ALL = (root) /bin/ls
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test24.out.ok b/plugins/sudoers/regress/testsudoers/test24.out.ok
new file mode 100644
index 0000000..0a38de3
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test24.out.ok
@@ -0,0 +1,48 @@
+Parses OK
+
+Entries for user root:
+
+ALL = NOTBEFORE=20170214083000Z /bin/ls
+ host allowed
+ date allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+Parses OK
+
+Entries for user root:
+
+ALL = NOTBEFORE=20170214083001Z /bin/ls
+ host allowed
+ date denied
+
+Password required
+
+Command unmatched
+Parses OK
+
+Entries for user root:
+
+ALL = NOTAFTER=20170214083000Z /bin/ls
+ host allowed
+ date allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+Parses OK
+
+Entries for user root:
+
+ALL = NOTAFTER=20170214083000Z /bin/ls
+ host allowed
+ date denied
+
+Password required
+
+Command unmatched
diff --git a/plugins/sudoers/regress/testsudoers/test24.sh b/plugins/sudoers/regress/testsudoers/test24.sh
new file mode 100755
index 0000000..8be4ebc
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test24.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# Verify that NOTBEFORE and NOTAFTER work as expected.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+
+retval=0
+
+$TESTSUDOERS -T 20170214083000Z root /bin/ls <<'EOF'
+root ALL = NOTBEFORE=20170214083000Z /bin/ls
+EOF
+if [ $? -ne 0 ]; then
+ retval=$?
+fi
+
+# expect failure
+$TESTSUDOERS -T 20170214083000Z root /bin/ls <<'EOF'
+root ALL = NOTBEFORE=20170214083001Z /bin/ls
+EOF
+if [ $? -eq 0 ]; then
+ retval=1
+fi
+
+$TESTSUDOERS -T 20170214083000Z root /bin/ls <<'EOF'
+root ALL = NOTAFTER=20170214083000Z /bin/ls
+EOF
+if [ $? -ne 0 ]; then
+ retval=$?
+fi
+
+# expect failure
+$TESTSUDOERS -T 20170214083001Z root /bin/ls <<'EOF'
+root ALL = NOTAFTER=20170214083000Z /bin/ls
+EOF
+if [ $? -eq 0 ]; then
+ retval=1
+fi
+
+exit $retval
diff --git a/plugins/sudoers/regress/testsudoers/test25.out.ok b/plugins/sudoers/regress/testsudoers/test25.out.ok
new file mode 100644
index 0000000..d23bdb2
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test25.out.ok
@@ -0,0 +1,59 @@
+A simple sudoers rule should not allow the user to set the cwd:
+Parses OK
+
+Entries for user root:
+
+ALL = /bin/ls
+ host allowed
+ runas allowed
+ cmnd allowed
+
+User root is not allowed to change directory to /
+
+Password required
+
+Command denied
+
+User cannot override the sudoers cwd:
+Parses OK
+
+Entries for user root:
+
+ALL = CWD=/some/where/else /bin/ls
+ host allowed
+ runas allowed
+ cmnd allowed
+
+User root is not allowed to change directory to /
+
+Password required
+
+Command denied
+
+User can set cwd if sudoers rule sets cwd to '*':
+Parses OK
+
+Entries for user root:
+
+ALL = CWD=* /bin/ls
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+User can set cwd runcwd Defaults is '*':
+Parses OK
+
+Entries for user root:
+
+ALL = /bin/ls
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test25.sh b/plugins/sudoers/regress/testsudoers/test25.sh
new file mode 100755
index 0000000..a3c395c
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test25.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+#
+# Test user-specified cwd handling
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+cd /
+
+retval=0
+
+# Sudo used to allow the user to set the cwd to the current value.
+# Now, a cwd must be explicitly set in sudoers to use the -D option.
+printf "A simple sudoers rule should not allow the user to set the cwd:\n"
+$TESTSUDOERS -D / root /bin/ls <<'EOF'
+root ALL = /bin/ls
+EOF
+if [ $? -eq 0 ]; then
+ retval=1
+fi
+
+printf "\nUser cannot override the sudoers cwd:\n"
+$TESTSUDOERS -D / root /bin/ls <<'EOF'
+root ALL = CWD=/some/where/else /bin/ls
+EOF
+if [ $? -eq 0 ]; then
+ retval=1
+fi
+
+printf "\nUser can set cwd if sudoers rule sets cwd to '*':\n"
+$TESTSUDOERS -D /usr root /bin/ls <<'EOF'
+root ALL = CWD=* /bin/ls
+EOF
+if [ $? -ne 0 ]; then
+ retval=$?
+fi
+
+printf "\nUser can set cwd runcwd Defaults is '*':\n"
+$TESTSUDOERS -D /usr root /bin/ls <<'EOF'
+Defaults runcwd = "*"
+root ALL = /bin/ls
+EOF
+if [ $? -ne 0 ]; then
+ retval=$?
+fi
+
+exit $retval
diff --git a/plugins/sudoers/regress/testsudoers/test26.out.ok b/plugins/sudoers/regress/testsudoers/test26.out.ok
new file mode 100644
index 0000000..281817c
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test26.out.ok
@@ -0,0 +1,57 @@
+A simple sudoers rule should not allow the user to chroot:
+Parses OK
+
+Entries for user root:
+
+ALL = /bin/ls
+ host allowed
+ runas allowed
+ cmnd allowed
+
+User root is not allowed to change root directory to /
+
+Password required
+
+Command denied
+
+User cannot override the sudoers chroot:
+Parses OK
+
+Entries for user root:
+
+ALL = CHROOT=/some/where/else /bin/ls
+ host allowed
+ runas allowed
+ cmnd unmatched
+
+Password required
+
+Command unmatched
+
+User can chroot if sudoers rule sets chroot to '*':
+Parses OK
+
+Entries for user root:
+
+ALL = CHROOT=* /bin/ls
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+User can chroot if runchroot Defaults is '*':
+Parses OK
+
+Entries for user root:
+
+ALL = /bin/ls
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test26.sh b/plugins/sudoers/regress/testsudoers/test26.sh
new file mode 100755
index 0000000..bef55da
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test26.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+#
+# Test user-specified chroot handling
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+cd /
+
+retval=0
+
+printf "A simple sudoers rule should not allow the user to chroot:\n"
+$TESTSUDOERS -R / root /bin/ls <<'EOF'
+root ALL = /bin/ls
+EOF
+if [ $? -eq 0 ]; then
+ retval=1
+fi
+
+# Because command_matches() uses the per-rule CHROOT, this results in
+# an unmatched rule instead of a matched rule that is rejected later.
+# This is different from the CWD checking which is performed after
+# matching is done.
+printf "\nUser cannot override the sudoers chroot:\n"
+$TESTSUDOERS -R / root /bin/ls <<'EOF'
+root ALL = CHROOT=/some/where/else /bin/ls
+EOF
+if [ $? -eq 0 ]; then
+ retval=1
+fi
+
+printf "\nUser can chroot if sudoers rule sets chroot to '*':\n"
+$TESTSUDOERS -R /usr root /bin/ls <<'EOF'
+root ALL = CHROOT=* /bin/ls
+EOF
+if [ $? -ne 0 ]; then
+ retval=$?
+fi
+
+printf "\nUser can chroot if runchroot Defaults is '*':\n"
+$TESTSUDOERS -R /usr root /bin/ls <<'EOF'
+Defaults runchroot = "*"
+root ALL = /bin/ls
+EOF
+if [ $? -ne 0 ]; then
+ retval=$?
+fi
+
+exit $retval
diff --git a/plugins/sudoers/regress/testsudoers/test27.out.ok b/plugins/sudoers/regress/testsudoers/test27.out.ok
new file mode 100644
index 0000000..73c06b7
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test27.out.ok
@@ -0,0 +1,14 @@
+Parses OK
+
+Entries for user admin:
+
+ALL = (ALL) /bin/ls
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Invalid shell for user fakeshell: /shell/does/not/exist
+
+Password required
+
+Command denied
diff --git a/plugins/sudoers/regress/testsudoers/test27.sh b/plugins/sudoers/regress/testsudoers/test27.sh
new file mode 100755
index 0000000..8733bb8
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test27.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# Verify that runas_check_shell works as expected.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+
+# This should fail due to fakeshell's shell
+$TESTSUDOERS -u fakeshell -p ${TESTDIR}/passwd -P ${TESTDIR}/group \
+ admin /bin/ls <<'EOF'
+Defaults runas_check_shell
+admin ALL = (ALL) /bin/ls
+EOF
+
+# Expected failure
+if [ $? -eq 0 ]; then
+ exit 1
+else
+ exit 0
+fi
diff --git a/plugins/sudoers/regress/testsudoers/test28.out.ok b/plugins/sudoers/regress/testsudoers/test28.out.ok
new file mode 100644
index 0000000..188d8de
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test28.out.ok
@@ -0,0 +1,125 @@
+This should match the 'ALL=ALL' rule.
+Parses OK
+
+Entries for user admin:
+
+ALL = (admin : staff) NOPASSWD: ALL
+ host allowed
+ runas unmatched
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+This should match the 'ALL=ALL' rule.
+Parses OK
+
+Entries for user admin:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+This should match the 'ALL=(:staff) NOPASSWD: ALL' rule.
+Parses OK
+
+Entries for user admin:
+
+ALL = (admin : staff) NOPASSWD: ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Command allowed
+
+This should match the 'ALL=(:staff) NOPASSWD: ALL' rule.
+Parses OK
+
+Entries for user admin:
+
+ALL = ALL
+ host allowed
+ runas unmatched
+
+ALL = (admin : staff) NOPASSWD: ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Command allowed
+
+This should match the 'ALL=(:staff) NOPASSWD: ALL' rule.
+Parses OK
+
+Entries for user admin:
+
+ALL = ALL
+ host allowed
+ runas unmatched
+
+ALL = (admin : staff) NOPASSWD: ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Command allowed
+
+This should match the 'ALL=(:staff) NOPASSWD: ALL' rule.
+Parses OK
+
+Entries for user admin:
+
+ALL = ALL
+ host allowed
+ runas unmatched
+
+ALL = (admin : staff) NOPASSWD: ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Command allowed
+
+This should not match any rules.
+Parses OK
+
+Entries for user admin:
+
+ALL = ALL
+ host allowed
+ runas unmatched
+
+ALL = (admin : staff) NOPASSWD: ALL
+ host allowed
+ runas unmatched
+
+Password required
+
+Command unmatched
+
+This should not match any rules.
+Parses OK
+
+Entries for user admin:
+
+ALL = ALL
+ host allowed
+ runas unmatched
+
+ALL = (admin : users) NOPASSWD: ALL
+ host allowed
+ runas unmatched
+
+Password required
+
+Command unmatched
diff --git a/plugins/sudoers/regress/testsudoers/test28.sh b/plugins/sudoers/regress/testsudoers/test28.sh
new file mode 100755
index 0000000..0465531
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test28.sh
@@ -0,0 +1,99 @@
+#!/bin/sh
+#
+# Verify that a rule with an empty Runas user matches correctly.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+
+status=0
+
+echo "This should match the 'ALL=ALL' rule."
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group \
+ admin /bin/ls <<'EOF'
+admin ALL = ALL
+ALL ALL=(:staff) NOPASSWD: ALL
+EOF
+if [ $? -ne 0 ]; then
+ status=1
+fi
+
+echo ""
+echo "This should match the 'ALL=ALL' rule."
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group \
+ admin /bin/ls <<'EOF'
+ALL ALL=(:staff) NOPASSWD: ALL
+admin ALL = ALL
+EOF
+if [ $? -ne 0 ]; then
+ status=1
+fi
+
+echo ""
+echo "This should match the 'ALL=(:staff) NOPASSWD: ALL' rule."
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -g staff \
+ admin /bin/ls <<'EOF'
+admin ALL = ALL
+ALL ALL=(:staff) NOPASSWD: ALL
+EOF
+if [ $? -ne 0 ]; then
+ status=1
+fi
+
+echo ""
+echo "This should match the 'ALL=(:staff) NOPASSWD: ALL' rule."
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -g staff \
+ admin /bin/ls <<'EOF'
+ALL ALL=(:staff) NOPASSWD: ALL
+admin ALL = ALL
+EOF
+if [ $? -ne 0 ]; then
+ status=1
+fi
+
+echo ""
+echo "This should match the 'ALL=(:staff) NOPASSWD: ALL' rule."
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -u admin \
+ admin /bin/ls <<'EOF'
+ALL ALL=(:staff) NOPASSWD: ALL
+admin ALL = ALL
+EOF
+if [ $? -ne 0 ]; then
+ status=1
+fi
+
+echo ""
+echo "This should match the 'ALL=(:staff) NOPASSWD: ALL' rule."
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -u admin -g staff \
+ admin /bin/ls <<'EOF'
+ALL ALL=(:staff) NOPASSWD: ALL
+admin ALL = ALL
+EOF
+if [ $? -ne 0 ]; then
+ status=1
+fi
+
+echo ""
+echo "This should not match any rules."
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -g guest \
+ admin /bin/ls <<'EOF'
+ALL ALL=(:staff) NOPASSWD: ALL
+admin ALL = ALL
+EOF
+if [ $? -eq 0 ]; then
+ status=1
+fi
+
+echo ""
+echo "This should not match any rules."
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -u root -g users \
+ admin /bin/ls <<'EOF'
+ALL ALL=(:users) NOPASSWD: ALL
+admin ALL = ALL
+EOF
+if [ $? -eq 0 ]; then
+ status=1
+fi
+
+exit $status
diff --git a/plugins/sudoers/regress/testsudoers/test29.out.ok b/plugins/sudoers/regress/testsudoers/test29.out.ok
new file mode 100644
index 0000000..bf145c7
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test29.out.ok
@@ -0,0 +1,133 @@
+listpw = all, 'sudo -l' should require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = NOPASSWD: ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+ALL = /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+listpw = all, 'sudo -l' should require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd allowed
+
+ALL = NOPASSWD: ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+listpw = all, 'sudo -l' should not require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = NOPASSWD: ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+ALL = NOPASSWD: /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Command allowed
+
+listpw = always, 'sudo -l' should require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = NOPASSWD: ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+listpw = any, 'sudo -l' should require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+ALL = /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+listpw = any, 'sudo -l' should not require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+ALL = NOPASSWD: /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Command allowed
+
+listpw = any, 'sudo -l' should not require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = NOPASSWD: /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd allowed
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Command allowed
+
+listpw = never, 'sudo -l' should not require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = PASSWD: /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test29.sh b/plugins/sudoers/regress/testsudoers/test29.sh
new file mode 100755
index 0000000..802b812
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test29.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+#
+# Exercise listpw Defaults settings.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+
+status=0
+
+echo "listpw = all, 'sudo -l' should require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -l admin <<'EOF'
+Defaults listpw = all
+admin ALL = NOPASSWD: ALL
+admin ALL = /usr/bin/id
+EOF
+
+echo ""
+echo "listpw = all, 'sudo -l' should require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -l admin <<'EOF'
+Defaults listpw = all
+admin ALL = /usr/bin/id
+admin ALL = NOPASSWD: ALL
+EOF
+
+echo ""
+echo "listpw = all, 'sudo -l' should not require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -l admin <<'EOF'
+Defaults listpw = all
+admin ALL = NOPASSWD: ALL
+admin ALL = NOPASSWD: /usr/bin/id
+EOF
+
+echo ""
+echo "listpw = always, 'sudo -l' should require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -l admin <<'EOF'
+Defaults listpw = always
+admin ALL = NOPASSWD: ALL
+EOF
+
+echo ""
+echo "listpw = any, 'sudo -l' should require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -l admin <<'EOF'
+Defaults listpw = any
+admin ALL = ALL
+admin ALL = /usr/bin/id
+EOF
+
+echo ""
+echo "listpw = any, 'sudo -l' should not require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -l admin <<'EOF'
+Defaults listpw = any
+admin ALL = ALL
+admin ALL = NOPASSWD: /usr/bin/id
+EOF
+
+echo ""
+echo "listpw = any, 'sudo -l' should not require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -l admin <<'EOF'
+Defaults listpw = any
+admin ALL = NOPASSWD: /usr/bin/id
+admin ALL = ALL
+EOF
+
+echo ""
+echo "listpw = never, 'sudo -l' should not require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -l admin <<'EOF'
+Defaults listpw = never
+admin ALL = PASSWD: /usr/bin/id
+EOF
diff --git a/plugins/sudoers/regress/testsudoers/test3.out.ok b/plugins/sudoers/regress/testsudoers/test3.out.ok
new file mode 100644
index 0000000..fc61e3d
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test3.out.ok
@@ -0,0 +1,59 @@
+Testing @includedir of an unquoted path
+
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+Testing @includedir of a double-quoted path
+
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+Testing #includedir of an unquoted path
+
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+Testing #includedir of a double-quoted path
+
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test3.sh b/plugins/sudoers/regress/testsudoers/test3.sh
new file mode 100755
index 0000000..d166de9
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test3.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+#
+# Test @includedir facility
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+TESTDIR="`pwd`/regress/testsudoers"
+# make sure include file is owned by current user
+rm -rf "$TESTDIR/test3.d"
+mkdir "$TESTDIR/test3.d"
+cat >"$TESTDIR/test3.d/root" <<-EOF
+ root ALL = ALL
+EOF
+
+MYUID=`\ls -lnd $TESTDIR/test3.d | awk '{print $3}'`
+MYGID=`\ls -lnd $TESTDIR/test3.d | awk '{print $4}'`
+exec 2>&1
+
+echo "Testing @includedir of an unquoted path"
+echo ""
+$TESTSUDOERS -U $MYUID -G $MYGID root id <<-EOF
+ @includedir $TESTDIR/test3.d
+EOF
+
+echo ""
+echo "Testing @includedir of a double-quoted path"
+echo ""
+$TESTSUDOERS -U $MYUID -G $MYGID root id <<-EOF
+ @includedir "$TESTDIR/test3.d"
+EOF
+
+echo ""
+echo "Testing #includedir of an unquoted path"
+echo ""
+$TESTSUDOERS -U $MYUID -G $MYGID root id <<-EOF
+ #includedir $TESTDIR/test3.d
+EOF
+
+echo ""
+echo "Testing #includedir of a double-quoted path"
+echo ""
+$TESTSUDOERS -U $MYUID -G $MYGID root id <<-EOF
+ #includedir "$TESTDIR/test3.d"
+EOF
+
+rm -rf "$TESTDIR/test3.d"
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test30.out.ok b/plugins/sudoers/regress/testsudoers/test30.out.ok
new file mode 100644
index 0000000..5763072
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test30.out.ok
@@ -0,0 +1,133 @@
+verifypw = all, 'sudo -v' should require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = NOPASSWD: ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+ALL = /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+verifypw = all, 'sudo -v' should require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd allowed
+
+ALL = NOPASSWD: ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+verifypw = all, 'sudo -v' should not require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = NOPASSWD: ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+ALL = NOPASSWD: /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Command allowed
+
+verifypw = always, 'sudo -v' should require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = NOPASSWD: ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+verifypw = any, 'sudo -v' should require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+ALL = /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+verifypw = any, 'sudo -v' should not require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+ALL = NOPASSWD: /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Command allowed
+
+verifypw = any, 'sudo -v' should not require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = NOPASSWD: /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd allowed
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Command allowed
+
+verifypw = never, 'sudo -v' should not require a password
+Parses OK
+
+Entries for user admin:
+
+ALL = PASSWD: /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test30.sh b/plugins/sudoers/regress/testsudoers/test30.sh
new file mode 100755
index 0000000..57b30ae
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test30.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+#
+# Exercise verifypw Defaults settings.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+
+status=0
+
+echo "verifypw = all, 'sudo -v' should require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -v admin <<'EOF'
+Defaults verifypw = all
+admin ALL = NOPASSWD: ALL
+admin ALL = /usr/bin/id
+EOF
+
+echo ""
+echo "verifypw = all, 'sudo -v' should require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -v admin <<'EOF'
+Defaults verifypw = all
+admin ALL = /usr/bin/id
+admin ALL = NOPASSWD: ALL
+EOF
+
+echo ""
+echo "verifypw = all, 'sudo -v' should not require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -v admin <<'EOF'
+Defaults verifypw = all
+admin ALL = NOPASSWD: ALL
+admin ALL = NOPASSWD: /usr/bin/id
+EOF
+
+echo ""
+echo "verifypw = always, 'sudo -v' should require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -v admin <<'EOF'
+Defaults verifypw = always
+admin ALL = NOPASSWD: ALL
+EOF
+
+echo ""
+echo "verifypw = any, 'sudo -v' should require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -v admin <<'EOF'
+Defaults verifypw = any
+admin ALL = ALL
+admin ALL = /usr/bin/id
+EOF
+
+echo ""
+echo "verifypw = any, 'sudo -v' should not require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -v admin <<'EOF'
+Defaults verifypw = any
+admin ALL = ALL
+admin ALL = NOPASSWD: /usr/bin/id
+EOF
+
+echo ""
+echo "verifypw = any, 'sudo -v' should not require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -v admin <<'EOF'
+Defaults verifypw = any
+admin ALL = NOPASSWD: /usr/bin/id
+admin ALL = ALL
+EOF
+
+echo ""
+echo "verifypw = never, 'sudo -v' should not require a password"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -v admin <<'EOF'
+Defaults verifypw = never
+admin ALL = PASSWD: /usr/bin/id
+EOF
diff --git a/plugins/sudoers/regress/testsudoers/test31.out.ok b/plugins/sudoers/regress/testsudoers/test31.out.ok
new file mode 100644
index 0000000..04b2347
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test31.out.ok
@@ -0,0 +1,131 @@
+'sudo -U root -l' with no matching rules
+Parses OK
+
+Entries for user admin:
+
+Password required
+
+Command denied
+
+'sudo -U root -l' with a matching ALL=ALL rule
+Parses OK
+
+Entries for user admin:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+'sudo -U root -l' with a matching list rule
+Parses OK
+
+Entries for user admin:
+
+ALL = NOPASSWD: list
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Command allowed
+
+'sudo -U root -l' without a matching list rule
+Parses OK
+
+Entries for user admin:
+
+ALL = (operator) list
+ host allowed
+ runas unmatched
+
+Password required
+
+Command denied
+
+'sudo -U root -l' with a negated list rule
+Parses OK
+
+Entries for user admin:
+
+ALL = !list
+ host allowed
+ runas allowed
+ cmnd denied
+
+Password required
+
+Command denied
+
+'sudo -U root -l' with a list rule that is later negated
+Parses OK
+
+Entries for user admin:
+
+ALL = NOPASSWD: list, !list
+ host allowed
+ runas allowed
+ cmnd allowed
+ runas allowed
+ cmnd denied
+
+Command denied
+
+'sudo -l command' with a matching command
+Parses OK
+
+Entries for user admin:
+
+ALL = /bin/ls
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+'sudo -l command' without a matching command
+Parses OK
+
+Entries for user admin:
+
+ALL = /bin/ls
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+'sudo -U root -l command' without list privileges
+Parses OK
+
+Entries for user admin:
+
+ALL = /usr/bin/id
+ host allowed
+ runas allowed
+ cmnd unmatched
+
+Password required
+
+Command denied
+
+'sudo -U root -l command' with list privileges
+Parses OK
+
+Entries for user admin:
+
+ALL = list
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test31.sh b/plugins/sudoers/regress/testsudoers/test31.sh
new file mode 100755
index 0000000..a40906d
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test31.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+#
+# Exercise "sudo -U user -l [command]"
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+
+status=0
+
+echo "'sudo -U root -l' with no matching rules"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -L root admin <<'EOF'
+root ALL = ALL
+EOF
+
+echo ""
+echo "'sudo -U root -l' with a matching ALL=ALL rule"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -L root admin <<'EOF'
+admin ALL = ALL
+EOF
+
+echo ""
+echo "'sudo -U root -l' with a matching list rule"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -L root admin <<'EOF'
+admin ALL = NOPASSWD: list
+EOF
+
+echo ""
+echo "'sudo -U root -l' without a matching list rule"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -L root admin <<'EOF'
+admin ALL = (operator) list
+EOF
+
+echo ""
+echo "'sudo -U root -l' with a negated list rule"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -L root admin <<'EOF'
+admin ALL = !list
+EOF
+
+echo ""
+echo "'sudo -U root -l' with a list rule that is later negated"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -L root admin <<'EOF'
+admin ALL = NOPASSWD: list, !list
+EOF
+
+echo ""
+echo "'sudo -l command' with a matching command"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -l admin /bin/ls <<'EOF'
+admin ALL = /bin/ls
+EOF
+
+echo ""
+echo "'sudo -l command' without a matching command"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -l admin /usr/bin/id <<'EOF'
+admin ALL = /bin/ls
+EOF
+
+echo ""
+echo "'sudo -U root -l command' without list privileges"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -L root admin /bin/ls <<'EOF'
+root ALL = ALL
+admin ALL = /usr/bin/id
+EOF
+
+echo ""
+echo "'sudo -U root -l command' with list privileges"
+$TESTSUDOERS -p ${TESTDIR}/passwd -P ${TESTDIR}/group -L root admin /bin/ls <<'EOF'
+root ALL = ALL
+admin ALL = list
+EOF
diff --git a/plugins/sudoers/regress/testsudoers/test4.out.ok b/plugins/sudoers/regress/testsudoers/test4.out.ok
new file mode 100644
index 0000000..4987d8b
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test4.out.ok
@@ -0,0 +1,7 @@
+testsudoers: test2.inc should be owned by uid 1
+
+Entries for user root:
+
+Password required
+
+Parse error
diff --git a/plugins/sudoers/regress/testsudoers/test4.sh b/plugins/sudoers/regress/testsudoers/test4.sh
new file mode 100755
index 0000000..4d496c7
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test4.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+#
+# Test sudoers owner check
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+$TESTSUDOERS -U 1 root id <<EOF
+@include $TESTDIR/test2.inc
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test5.out.ok b/plugins/sudoers/regress/testsudoers/test5.out.ok
new file mode 100644
index 0000000..3bd1747
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test5.out.ok
@@ -0,0 +1,14 @@
+testsudoers: test5.inc is world writable
+
+Entries for user root:
+
+Password required
+
+Parse error
+testsudoers: test5.inc should be owned by gid 4294967294
+
+Entries for user root:
+
+Password required
+
+Parse error
diff --git a/plugins/sudoers/regress/testsudoers/test5.sh b/plugins/sudoers/regress/testsudoers/test5.sh
new file mode 100755
index 0000000..317ad98
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test5.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+#
+# Test sudoers file mode check
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+# Create test file
+TESTFILE="`pwd`/regress/testsudoers/test5.inc"
+cat >"$TESTFILE" <<EOF
+root ALL = ALL
+EOF
+
+MYUID=`\ls -ln $TESTFILE | awk '{print $3}'`
+MYGID=`\ls -ln $TESTFILE | awk '{print $4}'`
+exec 2>&1
+
+# Test world writable
+chmod 666 $TESTFILE
+$TESTSUDOERS -U $MYUID -G $MYGID root id <<EOF
+@include $TESTFILE
+EOF
+
+# Test group writable
+chmod 664 $TESTFILE
+$TESTSUDOERS -U $MYUID -G -2 root id <<EOF
+@include $TESTFILE
+EOF
+
+rm -f $TESTFILE
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test6.out.ok b/plugins/sudoers/regress/testsudoers/test6.out.ok
new file mode 100644
index 0000000..71eb49c
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test6.out.ok
@@ -0,0 +1,12 @@
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test6.sh b/plugins/sudoers/regress/testsudoers/test6.sh
new file mode 100755
index 0000000..f3b54f8
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test6.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+#
+# Verify sudoers matching by uid.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+$TESTSUDOERS root id <<EOF
+#0 ALL = ALL
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test7.out.ok b/plugins/sudoers/regress/testsudoers/test7.out.ok
new file mode 100644
index 0000000..71eb49c
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test7.out.ok
@@ -0,0 +1,12 @@
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test7.sh b/plugins/sudoers/regress/testsudoers/test7.sh
new file mode 100755
index 0000000..9e28c1a
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test7.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+#
+# Verify sudoers matching by gid.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+exec 2>&1
+$TESTSUDOERS root id <<EOF
+%#0 ALL = ALL
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test8.out.ok b/plugins/sudoers/regress/testsudoers/test8.out.ok
new file mode 100644
index 0000000..51fa7cf
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test8.out.ok
@@ -0,0 +1,29 @@
+Testing @include without a newline
+
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
+
+Testing #include without a newline
+
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test8.sh b/plugins/sudoers/regress/testsudoers/test8.sh
new file mode 100755
index 0000000..c22b590
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test8.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# Test @include facility w/o a final newline.
+# Same as test2.sh but missing the final newline.
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+MYUID=`\ls -ln $TESTDIR/test2.inc | awk '{print $3}'`
+MYGID=`\ls -ln $TESTDIR/test2.inc | awk '{print $4}'`
+exec 2>&1
+
+echo "Testing @include without a newline"
+echo ""
+printf "@include $TESTDIR/test2.inc" | \
+ $TESTSUDOERS -U $MYUID -G $MYGID root id
+
+echo ""
+echo "Testing #include without a newline"
+echo ""
+printf "#include $TESTDIR/test2.inc" | \
+ $TESTSUDOERS -U $MYUID -G $MYGID root id
+
+exit 0
diff --git a/plugins/sudoers/regress/testsudoers/test9.out.ok b/plugins/sudoers/regress/testsudoers/test9.out.ok
new file mode 100644
index 0000000..71eb49c
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test9.out.ok
@@ -0,0 +1,12 @@
+Parses OK
+
+Entries for user root:
+
+ALL = ALL
+ host allowed
+ runas allowed
+ cmnd allowed
+
+Password required
+
+Command allowed
diff --git a/plugins/sudoers/regress/testsudoers/test9.sh b/plugins/sudoers/regress/testsudoers/test9.sh
new file mode 100755
index 0000000..850bbac
--- /dev/null
+++ b/plugins/sudoers/regress/testsudoers/test9.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# Test #include facility
+#
+
+: ${TESTSUDOERS=testsudoers}
+
+MYUID=`\ls -ln $TESTDIR/test2.inc | awk '{print $3}'`
+MYGID=`\ls -ln $TESTDIR/test2.inc | awk '{print $4}'`
+exec 2>&1
+$TESTSUDOERS -U $MYUID -G $MYGID root id <<EOF
+#include $TESTDIR/test2.inc
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/unescape/check_unesc.c b/plugins/sudoers/regress/unescape/check_unesc.c
new file mode 100644
index 0000000..87497c8
--- /dev/null
+++ b/plugins/sudoers/regress/unescape/check_unesc.c
@@ -0,0 +1,203 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2021-2022 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include <sudoers.h>
+
+struct test_data {
+ const char *input;
+ const char *result;
+ size_t result_len;
+ size_t bufsize;
+};
+
+/* strlcpy_unescape() does not unescape whitespace */
+static struct test_data strlcpy_unescape_test_data[] = {
+ { "\\\0ABC", "\\", 1, 2 }, /* 1 */
+ { "\\ \\;", "\\ ;", 3, 4 }, /* 2 */
+ { "\\\t\\;", "\\\t;", 3, 4 }, /* 3 */
+ { "\\foo", "foo", 3, 4 }, /* 4 */
+ { "foo\\ bar", "foo\\ bar", 8, 9 }, /* 5 */
+ { "foo bar", "f", 7, 2 }, /* 6 */
+ { "foo bar", "", 7, 1 }, /* 7 */
+ { "foo bar", NULL, 7, 0 }, /* 8 */
+ { NULL }
+};
+
+/* unescape_string() _does_ unescape whitespace */
+static struct test_data unescape_string_test_data[] = {
+ { "foo\\ bar", "foo bar", 7, 8 }, /* 1 */
+ { "foo\\,bar", "foo,bar", 7, 8 }, /* 2 */
+ { "baz \\", "baz \\", 5, 5 }, /* 3 */
+ { "\\foo", "foo", 3, 4 }, /* 4 */
+ { "var=aaa,b\\,b", "var=aaa,b,b", 11, 12 }, /* 5 */
+ { "\\a\\ b\\ c\\\\", "a b c\\", 6, 10 }, /* 6 */
+ { "\\", "\\", 1, 1 }, /* 7 */
+ { "foo", "foo", 3, 3 }, /* 8 */
+ { "", "", 0, 0 }, /* 9 */
+ { NULL }
+};
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+static void
+test_strlcpy_unescape(int *ntests_out, int *errors_out)
+{
+ int ntests = *ntests_out;
+ int errors = *errors_out;
+ struct test_data *td;
+ char buf[1024];
+ size_t len;
+
+ for (td = strlcpy_unescape_test_data; td->input != NULL; td++) {
+ ntests++;
+ memset(buf, 'A', sizeof(buf));
+ len = strlcpy_unescape(buf, td->input, td->bufsize);
+ if (len != td->result_len) {
+ sudo_warnx("%d: \"%s\": bad return %zu, expected %zu",
+ ntests, td->input, len, td->result_len);
+ errors++;
+ }
+ len = td->result ? strlen(td->result) : 0;
+ if ((len != 0 || td->bufsize != 0) && len >= td->bufsize) {
+ sudo_warnx("%d: \"%s\": bad length %zu >= %zu",
+ ntests, td->input, len, td->bufsize);
+ errors++;
+ }
+ if (td->result != NULL && strcmp(td->result, buf) != 0) {
+ sudo_warnx("%d: \"%s\": got \"%s\", expected \"%s\"",
+ ntests, td->input, buf, td->result);
+ errors++;
+ }
+ if (buf[td->bufsize] != 'A') {
+ sudo_warnx("%d: \"%s\": wrote past end of buffer at %zu (0x%x)",
+ ntests, td->input, td->bufsize, buf[td->bufsize]);
+ errors++;
+ }
+ }
+
+ *ntests_out = ntests;
+ *errors_out = errors;
+}
+
+static void
+test_unescape_string(int *ntests_out, int *errors_out)
+{
+ int ntests = *ntests_out;
+ int errors = *errors_out;
+ struct test_data *td;
+ char buf[1024];
+
+ for (td = unescape_string_test_data; td->input != NULL; td++) {
+ ntests++;
+ memset(buf, 'A', sizeof(buf));
+ memcpy(buf, td->input, td->bufsize);
+ buf[td->bufsize] = '\0';
+ unescape_string(buf);
+ if (strcmp(td->result, buf) != 0) {
+ sudo_warnx("%d: \"%s\": got \"%s\", expected \"%s\"",
+ ntests, td->input, buf, td->result);
+ errors++;
+ }
+ }
+
+ *ntests_out = ntests;
+ *errors_out = errors;
+}
+
+static void
+test_strvec_join(char sep, int *ntests_out, int *errors_out)
+{
+ int ntests = *ntests_out;
+ int errors = *errors_out;
+ char buf[64*1024 + 1], expected[64*1024 + 3];
+ char *argv[3], *result;
+
+ /* Test joining an argument vector while unescaping. */
+ /* Simulate: sudoedit -s '\' `perl -e 'print "A" x 65536'` */
+ memset(buf, 'A', sizeof(buf));
+ buf[sizeof(buf) - 1] = '\0';
+ argv[0] = (char *)"\\";
+ argv[1] = buf;
+ argv[2] = NULL;
+
+ memset(expected, 'A', sizeof(expected));
+ expected[0] = '\\';
+ expected[1] = sep;
+ expected[sizeof(expected) - 1] = '\0';
+
+ ntests++;
+ result = strvec_join(argv, sep, strlcpy_unescape);
+ if (result == NULL) {
+ sudo_warnx("%d: failed to join argument vector", ntests);
+ errors++;
+ } else if (strcmp(result, expected) != 0) {
+ sudo_warnx("%d: got \"%s\", expected \"%s\"", ntests,
+ result, expected);
+ errors++;
+ }
+ free(result);
+
+ *ntests_out = ntests;
+ *errors_out = errors;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch, ntests = 0, errors = 0;
+
+ initprogname(argc > 0 ? argv[0] : "check_unesc");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* strlcpy_unescape tests */
+ test_strlcpy_unescape(&ntests, &errors);
+
+ /* unescape_string test */
+ test_unescape_string(&ntests, &errors);
+
+ /* strvec_join test */
+ test_strvec_join(' ', &ntests, &errors);
+ test_strvec_join('\n', &ntests, &errors);
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ exit(errors);
+}
diff --git a/plugins/sudoers/regress/visudo/test1.out.ok b/plugins/sudoers/regress/visudo/test1.out.ok
new file mode 100644
index 0000000..e5c355c
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test1.out.ok
@@ -0,0 +1 @@
+stdin: parsed OK
diff --git a/plugins/sudoers/regress/visudo/test1.sh b/plugins/sudoers/regress/visudo/test1.sh
new file mode 100755
index 0000000..5676eea
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test1.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# Sudo Bug 519:
+# Visudo in strict mode reports "parse error" even if there is no error
+#
+
+: ${VISUDO=visudo}
+
+$VISUDO -csf - <<EOF
+User_Alias FOO = nobody
+FOO ALL=(ALL) NOPASSWD: ALL
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/visudo/test10.out.ok b/plugins/sudoers/regress/visudo/test10.out.ok
new file mode 100644
index 0000000..e5c355c
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test10.out.ok
@@ -0,0 +1 @@
+stdin: parsed OK
diff --git a/plugins/sudoers/regress/visudo/test10.sh b/plugins/sudoers/regress/visudo/test10.sh
new file mode 100755
index 0000000..38b7f3e
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test10.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+#
+# Test parsing of NOTBEFORE/NOTAFTER using local time zone
+#
+
+: ${VISUDO=visudo}
+
+$VISUDO -cf - <<-EOF
+ user1 ALL = NOTBEFORE=20151201235900 /usr/bin/id
+ user2 ALL = NOTBEFORE=20151201235900.2 /usr/bin/id
+ user3 ALL = NOTBEFORE=20151201235900\,2 /usr/bin/id
+ user4 ALL = NOTBEFORE=2015120123 /usr/bin/id
+ EOF
diff --git a/plugins/sudoers/regress/visudo/test2.err.ok b/plugins/sudoers/regress/visudo/test2.err.ok
new file mode 100644
index 0000000..d4b356f
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test2.err.ok
@@ -0,0 +1 @@
+stdin:1:12: cycle in User_Alias "FOO"
diff --git a/plugins/sudoers/regress/visudo/test2.out.ok b/plugins/sudoers/regress/visudo/test2.out.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test2.out.ok
diff --git a/plugins/sudoers/regress/visudo/test2.sh b/plugins/sudoers/regress/visudo/test2.sh
new file mode 100755
index 0000000..8ab2382
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test2.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# Test cycle detection
+# Prior to sudo 1.8.6p5 this resulted in a core dump (stack smash)
+# The names of the aliases (or rather their lexical order) is important.
+#
+
+: ${VISUDO=visudo}
+
+$VISUDO -csf - <<EOF
+User_Alias YYY = FOO
+User_Alias XXX = nobody
+User_Alias FOO = XXX, YYY
+FOO ALL = ALL
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/visudo/test3.err.ok b/plugins/sudoers/regress/visudo/test3.err.ok
new file mode 100644
index 0000000..38fe9b8
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test3.err.ok
@@ -0,0 +1,2 @@
+Warning: stdin:1:12: unused User_Alias "A"
+Warning: stdin:2:12: unused User_Alias "B"
diff --git a/plugins/sudoers/regress/visudo/test3.out.ok b/plugins/sudoers/regress/visudo/test3.out.ok
new file mode 100644
index 0000000..e5c355c
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test3.out.ok
@@ -0,0 +1 @@
+stdin: parsed OK
diff --git a/plugins/sudoers/regress/visudo/test3.sh b/plugins/sudoers/regress/visudo/test3.sh
new file mode 100755
index 0000000..d219f56
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test3.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+#
+# Sudo Bug 361:
+# Exercises a bug in the redblack tree code.
+#
+
+: ${VISUDO=visudo}
+
+$VISUDO -cf - <<EOF
+User_Alias A=a
+User_Alias B=a
+User_Alias C=a
+User_Alias D=a
+User_Alias E=a
+User_Alias F=a
+User_Alias G=a
+User_Alias H=a
+User_Alias I=a
+User_Alias J=a
+User_Alias K=a
+User_Alias L=a
+User_Alias M=a
+
+C ALL=(ALL) ALL
+E ALL=(ALL) ALL
+J ALL=(ALL) ALL
+D ALL=(ALL) ALL
+L ALL=(ALL) ALL
+H ALL=(ALL) ALL
+F ALL=(ALL) ALL
+G ALL=(ALL) ALL
+M ALL=(ALL) ALL
+K ALL=(ALL) ALL
+I ALL=(ALL) ALL
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/visudo/test4.out.ok b/plugins/sudoers/regress/visudo/test4.out.ok
new file mode 100644
index 0000000..e5c355c
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test4.out.ok
@@ -0,0 +1 @@
+stdin: parsed OK
diff --git a/plugins/sudoers/regress/visudo/test4.sh b/plugins/sudoers/regress/visudo/test4.sh
new file mode 100755
index 0000000..465b91c
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test4.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+#
+# Test cycle detection and duplicate entries.
+# Prior to sudo 1.8.7 this resulted in a false positive.
+#
+
+: ${VISUDO=visudo}
+
+$VISUDO -csf - <<EOF
+Host_Alias H1 = host1
+Host_Alias H2 = H1, host2
+Host_Alias H3 = H1, H2
+root H3 = ALL
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/visudo/test5.out.ok b/plugins/sudoers/regress/visudo/test5.out.ok
new file mode 100644
index 0000000..e5c355c
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test5.out.ok
@@ -0,0 +1 @@
+stdin: parsed OK
diff --git a/plugins/sudoers/regress/visudo/test5.sh b/plugins/sudoers/regress/visudo/test5.sh
new file mode 100755
index 0000000..c870df9
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test5.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Test comment on the last line with no newline
+#
+
+: ${VISUDO=visudo}
+
+printf "# one comment\n#two comments" | $VISUDO -csf -
+
+exit 0
diff --git a/plugins/sudoers/regress/visudo/test6.out.ok b/plugins/sudoers/regress/visudo/test6.out.ok
new file mode 100644
index 0000000..e5c355c
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test6.out.ok
@@ -0,0 +1 @@
+stdin: parsed OK
diff --git a/plugins/sudoers/regress/visudo/test6.sh b/plugins/sudoers/regress/visudo/test6.sh
new file mode 100755
index 0000000..5f7adee
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test6.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# Verify parsing of Defaults syntax
+#
+
+: ${VISUDO=visudo}
+
+$VISUDO -csf - <<EOF
+Defaults syslog=auth
+Defaults>root !set_logname
+Defaults:FULLTIMERS !lecture
+Defaults:millert !authenticate
+Defaults@SERVERS log_year, logfile=/var/log/sudo.log
+Defaults!PAGERS noexec
+
+Defaults env_keep -= "HOME"
+Defaults env_keep = "COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS"
+Defaults env_keep += "MAIL PS1 PS2 QTDIR LANG LC_ADDRESS LC_CTYPE"
+
+User_Alias FULLTIMERS = millert, mikef, dowdy
+
+Cmnd_Alias PAGERS = /usr/bin/more, /usr/bin/pg, /usr/bin/less
+
+Host_Alias SERVERS = primary, mail, www, ns
+EOF
+
+exit 0
diff --git a/plugins/sudoers/regress/visudo/test7.out.ok b/plugins/sudoers/regress/visudo/test7.out.ok
new file mode 100644
index 0000000..e5c355c
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test7.out.ok
@@ -0,0 +1 @@
+stdin: parsed OK
diff --git a/plugins/sudoers/regress/visudo/test7.sh b/plugins/sudoers/regress/visudo/test7.sh
new file mode 100755
index 0000000..b993fe7
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test7.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+#
+# Test sudoers_locale early Defaults
+#
+
+: ${VISUDO=visudo}
+
+LANG=C; export LANG
+LC_NUMERIC=fr_FR.UTF-8; export LC_NUMERIC
+
+# First check that visudo supports non-C locales
+# Note that older versions of sudo did not set the locale
+# until sudoers was read so this check will fail on them.
+$VISUDO -csf - >/dev/null 2>&1 <<-EOF
+ Defaults sudoers_locale = fr_FR.UTF-8
+ Defaults passwd_timeout = "2,5"
+ EOF
+
+# Now make sure we can set passwd_timeout to a floating point value
+# using a non-C locale.
+if [ $? -eq 0 ]; then
+ $VISUDO -csf - <<-EOF
+ Defaults passwd_timeout = "2,5"
+ Defaults sudoers_locale = fr_FR.UTF-8
+ EOF
+else
+ # No support for LC_NUMERIC?
+ echo "stdin: parsed OK"
+fi
+
+exit 0
diff --git a/plugins/sudoers/regress/visudo/test8.err.ok b/plugins/sudoers/regress/visudo/test8.err.ok
new file mode 100644
index 0000000..3e71f62
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test8.err.ok
@@ -0,0 +1 @@
+visudo: stdin:1: value "2.5" is invalid for option "passwd_timeout"
diff --git a/plugins/sudoers/regress/visudo/test8.out.ok b/plugins/sudoers/regress/visudo/test8.out.ok
new file mode 100644
index 0000000..16ebc45
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test8.out.ok
@@ -0,0 +1 @@
+parse error in stdin near line 1
diff --git a/plugins/sudoers/regress/visudo/test8.sh b/plugins/sudoers/regress/visudo/test8.sh
new file mode 100755
index 0000000..85bdd9f
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test8.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Test sudoers_locale early Defaults
+#
+
+: ${VISUDO=visudo}
+
+LANG=C; export LANG
+LC_NUMERIC=fr_FR.UTF-8; export LC_NUMERIC
+
+# First check that visudo supports non-C locales
+# Note that older versions of sudo did not set the locale
+# until sudoers was read so this check will fail on them.
+$VISUDO -csf - >/dev/null 2>&1 <<-EOF
+ Defaults sudoers_locale = fr_FR.UTF-8
+ Defaults passwd_timeout = "2,5"
+ EOF
+
+# Now make sure we can set passwd_timeout to a floating point value
+# using a non-C locale.
+if [ $? -eq 0 ]; then
+ $VISUDO -csf - <<-EOF
+ Defaults passwd_timeout = "2.5"
+ Defaults sudoers_locale = fr_FR.UTF-8
+ EOF
+else
+ # No support for LC_NUMERIC?
+ echo "parse error in stdin near line 1"
+ echo 'visudo: stdin:1: value "2.5" is invalid for option "passwd_timeout"' 1>&2
+fi
+
+exit 0
diff --git a/plugins/sudoers/regress/visudo/test9.out.ok b/plugins/sudoers/regress/visudo/test9.out.ok
new file mode 100644
index 0000000..e5c355c
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test9.out.ok
@@ -0,0 +1 @@
+stdin: parsed OK
diff --git a/plugins/sudoers/regress/visudo/test9.sh b/plugins/sudoers/regress/visudo/test9.sh
new file mode 100755
index 0000000..175ad6e
--- /dev/null
+++ b/plugins/sudoers/regress/visudo/test9.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# Test IP and network address in host-based Defaults statements
+# Bugzilla #766
+#
+
+: ${VISUDO=visudo}
+
+$VISUDO -cf - <<-EOF
+ Defaults@127.0.0.1 !authenticate
+ Defaults@10.0.0.0/8 !always_set_home
+ EOF
+
+exit 0