summaryrefslogtreecommitdiffstats
path: root/sudoers
diff options
context:
space:
mode:
authorSumit Bose <sbose@nb.localdomain>2008-09-18 10:46:33 +0200
committerSumit Bose <sbose@nb.localdomain>2008-09-18 10:46:33 +0200
commit7bfb88bbf648000b4c2bf853a11ad2bd3f4b2d85 (patch)
treeb5e5bf2cd5768dd129d23fb76cb30304cd5315e5 /sudoers
parent575f23f9ca93b302cb487c037edcbb8bf7554437 (diff)
downloadipa_policy-7bfb88bbf648000b4c2bf853a11ad2bd3f4b2d85.tar.gz
ipa_policy-7bfb88bbf648000b4c2bf853a11ad2bd3f4b2d85.tar.xz
ipa_policy-7bfb88bbf648000b4c2bf853a11ad2bd3f4b2d85.zip
added fcusack's work
Diffstat (limited to 'sudoers')
-rw-r--r--sudoers/ipa.rng42
-rw-r--r--sudoers/netgroup.rng49
-rw-r--r--sudoers/options.rng448
-rw-r--r--sudoers/posixGroup.rng49
-rw-r--r--sudoers/sudoOptions.rng73
-rw-r--r--sudoers/sudoers.rng64
-rw-r--r--sudoers/sudoers.xslt566
-rw-r--r--sudoers/sudoers2xml214
-rw-r--r--sudoers/ttygroup.rng49
-rw-r--r--sudoers/user.rng49
-rw-r--r--sudoers/username.rng11
11 files changed, 1614 insertions, 0 deletions
diff --git a/sudoers/ipa.rng b/sudoers/ipa.rng
new file mode 100644
index 0000000..759caee
--- /dev/null
+++ b/sudoers/ipa.rng
@@ -0,0 +1,42 @@
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+ xmlns:a="http://freeipa.org/xml/rng/ns/annotations/1.0">
+ <a:doc> top (root) level IPA pattern </a:doc>
+
+ <!-- this simply allows any valid XML to be enclosed in <ipa> -->
+ <start ns="http://freeipa.org/xml/rng/ipa/1.0">
+ <element name="ipa">
+ <!-- note that text is not allowed -->
+ <zeroOrMore>
+ <ref name="anyElement" />
+ </zeroOrMore>
+ </element>
+ </start>
+
+ <!--
+ This allows anything in ANOTHER namespace.
+ The way nsName works requires that this be in each schema (grammar);
+ it cannot be included.
+ -->
+ <define name="anyElement">
+ <element>
+ <anyName>
+ <except>
+ <nsName />
+ <nsName ns="" />
+ <nsName ns="http://freeipa.org/xml/rng/ipa/1.0" />
+ </except>
+ </anyName>
+ <zeroOrMore>
+ <choice>
+ <attribute>
+ <anyName />
+ </attribute>
+ <text/>
+ <ref name="anyElement" />
+ </choice>
+ </zeroOrMore>
+ </element>
+ </define>
+
+</grammar>
+
diff --git a/sudoers/netgroup.rng b/sudoers/netgroup.rng
new file mode 100644
index 0000000..24e0171
--- /dev/null
+++ b/sudoers/netgroup.rng
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+ datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
+ xmlns:a="http://freeipa.org/xml/rng/ns/annotations/1.0">
+
+ <a:doc> netgroup configuration</a:doc>
+
+ <start ns="http://freeipa.org/xml/rng/netgroup/1.0">
+ <element name="netgroup">
+ <ref name="options"/>
+ <ref name="anyElement"/>
+ </element>
+
+ </start>
+
+ <define name="options">
+ <zeroOrMore>
+ <choice>
+ <attribute name="groupnames">
+ <text/>
+ </attribute>
+ </choice>
+ </zeroOrMore>
+ </define>
+ <!--
+ This allows anything in another namespace.
+ The way nsName works requires that this be in each schema (grammar);
+ it cannot be included.
+ -->
+ <define name="anyElement">
+ <element>
+ <anyName>
+ <except>
+ <nsName/>
+ <nsName ns=""/>
+ </except>
+ </anyName>
+ <zeroOrMore>
+ <choice>
+ <attribute>
+ <anyName/>
+ </attribute>
+ <text/>
+ <ref name="anyElement"/>
+ </choice>
+ </zeroOrMore>
+ </element>
+ </define>
+</grammar>
diff --git a/sudoers/options.rng b/sudoers/options.rng
new file mode 100644
index 0000000..e2782e1
--- /dev/null
+++ b/sudoers/options.rng
@@ -0,0 +1,448 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+ datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
+ xmlns:a="http://freeipa.org/xml/rng/ns/annotations/1.0">
+
+ <include href="username.rng"/>
+
+ <define name="options">
+ <oneOrMore>
+ <choice>
+
+ <!-- flag options -->
+
+ <attribute name="always_set_home" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="authenticate" a:defaultValue="on">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="env_editor" a:defaultValue="on">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="env_reset" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="fqdn" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+<!--
+ this option is ignored by sudo
+ <attribute name="ignore_dot" a:defaultValue="on">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+-->
+
+<!-- global option only -->
+ <attribute name="ignore_local_sudoers" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="insults" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="log_host" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="log_year" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="long_otp_prompt" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="mail_always" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="mail_badpass" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="mail_no_host" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="mail_no_perms" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="mail_no_user" a:defaultValue="on">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="noexec" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="path_info" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="passprompt_override" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="preserve_groups" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="requiretty" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="root_sudo" a:defaultValue="on">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="rootpw" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="runaspw" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="set_home" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="set_logname" a:defaultValue="on">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="setenv" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="shell_noargs" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="stay_setuid" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="targetpw" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="tty_tickets" a:defaultValue="off">
+ <choice>
+ <value>on</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <!-- integer options -->
+
+ <attribute name="passwd_tries" a:defaultValue="3">
+ <data type="integer">
+ <param name="minInclusive"> 1 </param>
+ <param name="maxInclusive"> 65535 </param> <!-- ??? -->
+ </data>
+ </attribute>
+
+ <!-- integer/boolean options -->
+
+ <attribute name="loglinelen" a:defaultValue="80">
+ <data type="integer">
+ <param name="minInclusive"> 0 </param>
+ <param name="maxInclusive"> 65535 </param> <!-- ??? -->
+ </data>
+ </attribute>
+
+ <attribute name="passwd_timeout" a:defaultValue="0">
+ <data type="integer">
+ <param name="minInclusive"> 0 </param>
+ <param name="maxInclusive"> 65535 </param> <!-- ??? -->
+ </data>
+ </attribute>
+
+ <attribute name="timestamp_timeout" a:defaultValue="5">
+ <data type="integer">
+ <param name="minInclusive"> -1 </param>
+ <param name="maxInclusive"> 65535 </param> <!-- ??? -->
+ </data>
+ </attribute>
+
+ <attribute name="umask" a:defaultValue="0022">
+ <data type="string">
+ <param name="pattern">(0[0-7]{3})</param>
+ </data>
+ </attribute>
+
+ <!-- string options -->
+ <attribute name="badpass_message" a:defaultValue="Sorry, try again.">
+ <text/>
+ </attribute>
+
+ <attribute name="editor" a:defaultValue="/PATH/TO/VI">
+ <!-- NOTE: absolute path not required -->
+ <text/>
+ </attribute>
+
+ <attribute name="mailsub"
+ a:defaultValue="*** SECURITY information for %h ***">
+ <text/>
+ </attribute>
+
+ <attribute name="noexec_file" a:defaultValue="/PATH/TO/SUDO_NOEXEC.SO">
+ <data type="string">
+ <param name="pattern">/.*</param>
+ </data>
+ </attribute>
+
+ <attribute name="passprompt" a:defaultValue="Password:">
+ <text/>
+ </attribute>
+
+ <attribute name="role" a:defaultValue="">
+ <text/>
+ </attribute>
+
+ <attribute name="runas_default" a:defaultValue="root">
+ <ref name="username_pattern"/>
+ </attribute>
+
+ <attribute name="syslog_badpri" a:defaultValue="alert">
+ <choice>
+ <value>emerg</value>
+ <value>alert</value>
+ <value>crit</value>
+ <value>err</value>
+ <value>warning</value>
+ <value>notice</value>
+ <value>info</value>
+ <value>debug</value>
+ </choice>
+ </attribute>
+
+ <attribute name="syslog_goodpri" a:defaultValue="notice">
+ <choice>
+ <value>emerg</value>
+ <value>alert</value>
+ <value>crit</value>
+ <value>err</value>
+ <value>warning</value>
+ <value>notice</value>
+ <value>info</value>
+ <value>debug</value>
+ </choice>
+ </attribute>
+
+ <attribute name="timestampdir" a:defaultValue="/var/db/sudo">
+ <data type="string">
+ <param name="pattern">/.*</param>
+ </data>
+ </attribute>
+
+ <attribute name="timestampowner" a:defaultValue="root">
+ <ref name="username_pattern"/>
+ </attribute>
+
+ <attribute name="type" a:defaultValue="">
+ <text/>
+ </attribute>
+
+ <!-- string/boolean options -->
+
+<!-- possibly bad option for us -->
+ <attribute name="exempt_group" a:defaultValue="off">
+ <text/>
+ </attribute>
+
+ <attribute name="lecture" a:defaultValue="once">
+ <choice>
+ <value>always</value>
+ <value>never</value>
+ <value>once</value>
+ </choice>
+ </attribute>
+
+ <attribute name="lecture_file" a:defaultValue="built-in">
+ <data type="string">
+ <param name="pattern">(/.*|built-in)</param>
+ </data>
+ </attribute>
+
+<!-- possibly bad for us -->
+ <attribute name="listpw" a:defaultValue="any">
+ <choice>
+ <value>all</value>
+ <value>always</value>
+ <value>any</value>
+ <value>never</value>
+ </choice>
+ </attribute>
+
+ <attribute name="logfile" a:defaultValue="off">
+ <data type="string">
+ <param name="pattern">(/.*|off)</param>
+ </data>
+ </attribute>
+
+ <attribute name="mailerflags" a:defaultValue="-t">
+ <text/>
+ </attribute>
+
+ <attribute name="mailerpath" a:defaultValue="/PATH/TO/SENDMAIL">
+ <text/>
+ </attribute>
+
+ <attribute name="syslog" a:defaultValue="authpriv">
+ <choice>
+ <value>auth</value>
+ <value>authpriv</value>
+ <value>daemon</value>
+ <value>user</value>
+ <value>local0</value>
+ <value>local1</value>
+ <value>local2</value>
+ <value>local3</value>
+ <value>local4</value>
+ <value>local5</value>
+ <value>local6</value>
+ <value>local7</value>
+ <value>off</value>
+ </choice>
+ </attribute>
+
+ <attribute name="verifypw" a:defaultValue="all">
+ <choice>
+ <value>all</value>
+ <value>always</value>
+ <value>any</value>
+ <value>never</value>
+ </choice>
+ </attribute>
+
+ <!-- list/boolean options -->
+
+ <attribute name="env_check" a:defaultValue="">
+ <list>
+ <oneOrMore>
+ <data type="string"/>
+ </oneOrMore>
+ </list>
+ </attribute>
+
+ <attribute name="env_delete" a:defaultValue="">
+ <list>
+ <oneOrMore>
+ <data type="string"/>
+ </oneOrMore>
+ </list>
+ </attribute>
+
+ <attribute name="env_keep" a:defaultValue="">
+ <list>
+ <oneOrMore>
+ <data type="string"/>
+ </oneOrMore>
+ </list>
+ </attribute>
+
+ </choice>
+ </oneOrMore>
+ </define> <!-- options -->
+</grammar>
+
diff --git a/sudoers/posixGroup.rng b/sudoers/posixGroup.rng
new file mode 100644
index 0000000..e3f4d86
--- /dev/null
+++ b/sudoers/posixGroup.rng
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+ datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
+ xmlns:a="http://freeipa.org/xml/rng/ns/annotations/1.0">
+
+ <a:doc> posixGroup configuration</a:doc>
+
+ <start ns="http://freeipa.org/xml/rng/posixGroup/1.0">
+ <element name="posixGroup">
+ <ref name="options"/>
+ <ref name="anyElement"/>
+ </element>
+
+ </start>
+
+ <define name="options">
+ <zeroOrMore>
+ <choice>
+ <attribute name="gids">
+ <text/>
+ </attribute>
+ </choice>
+ </zeroOrMore>
+ </define>
+ <!--
+ This allows anything in another namespace.
+ The way nsName works requires that this be in each schema (grammar);
+ it cannot be included.
+ -->
+ <define name="anyElement">
+ <element>
+ <anyName>
+ <except>
+ <nsName/>
+ <nsName ns=""/>
+ </except>
+ </anyName>
+ <zeroOrMore>
+ <choice>
+ <attribute>
+ <anyName/>
+ </attribute>
+ <text/>
+ <ref name="anyElement"/>
+ </choice>
+ </zeroOrMore>
+ </element>
+ </define>
+</grammar>
diff --git a/sudoers/sudoOptions.rng b/sudoers/sudoOptions.rng
new file mode 100644
index 0000000..c87c02b
--- /dev/null
+++ b/sudoers/sudoOptions.rng
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+ datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
+ xmlns:a="http://freeipa.org/xml/rng/ns/annotations/1.0">
+
+ <a:doc> Sudo options configuration (Defaults) </a:doc>
+
+ <include href="options.rng"/>
+
+ <start ns="http://freeipa.org/xml/rng/sudo/sudoOptions/1.0">
+ <element name="sudoOptions">
+ <ref name="options"/>
+ <!-- we can enclose anything -->
+ <!--
+ NOTE: This allows only a single element to be enclosed.
+ The problem with allowing multiple elements is, how to
+ resolve conflicting configs?
+
+ <sudoOptions ...>
+ <posixGroup name="wheel">
+ <sudoers .../>
+ </posixGroup>
+ <posixGroup name="staff">
+ <sudoers .../>
+ </posixGroup>
+ <posixUser name="user">
+ <sudoers .../>
+ </posixGroup>
+ </sudoOptions>
+
+ If user foo is in both groups, and (e.g.) conflicting
+ options are used in the <sudoers> element, which applies?
+ For the <posixUser> case, we could simply say it is more
+ specific and it wins, but not so easy for the groups case.
+
+ NOTE: This is a general problem. Even if sudoOptions allows only
+ a single element, what about other grouping elements?
+ -->
+ <ref name="anyElement"/>
+ </element>
+ </start>
+
+ <!--
+ This allows anything in another namespace.
+ The way nsName works requires that this be in each schema (grammar);
+ it cannot be included.
+ -->
+ <define name="anyElement">
+ <zeroOrMore>
+ <element>
+ <anyName>
+ <except>
+ <nsName/>
+ <nsName ns=""/>
+<!--
+ <nsName ns="http://freeipa.org/xml/rng/sudo/sudoOptions/1.0"/>
+-->
+ </except>
+ </anyName>
+ <zeroOrMore>
+ <choice>
+ <attribute>
+ <anyName/>
+ </attribute>
+ <text/>
+ <ref name="anyElement"/>
+ </choice>
+ </zeroOrMore>
+ </element>
+ </zeroOrMore>
+ </define>
+</grammar>
+
diff --git a/sudoers/sudoers.rng b/sudoers/sudoers.rng
new file mode 100644
index 0000000..6787df7
--- /dev/null
+++ b/sudoers/sudoers.rng
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+ datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
+ xmlns:a="http://freeipa.org/xml/rng/ns/annotations/1.0">
+
+ <a:doc> Sudo configuration (/etc/sudoers) </a:doc>
+
+ <include href="username.rng"/>
+
+ <start ns="http://freeipa.org/xml/rng/sudo/sudoers/1.0">
+ <element name="sudoers">
+
+ <oneOrMore>
+ <element name="command">
+ <optional>
+ <element name="options">
+ <ref name="options"/>
+ </element>
+ </optional>
+
+ <element name="path">
+ <text/>
+ </element>
+
+ <zeroOrMore>
+ <element name="args">
+ <text/>
+ </element>
+ </zeroOrMore>
+
+ <zeroOrMore>
+ <element name="tag">
+ <choice>
+ <value>NOPASSWD</value>
+ <value>PASSWD</value>
+ <value>NOEXEC</value>
+ <value>EXEC</value>
+ <value>SETENV</value>
+ <value>NOSETENV</value>
+ </choice>
+ </element>
+ </zeroOrMore>
+
+<!-- XXX actually needs to be user,group,netgroup -->
+ <zeroOrMore>
+ <element name="runas">
+ <ref name="username_pattern"/>
+ </element>
+ </zeroOrMore>
+
+ </element> <!-- command -->
+ </oneOrMore>
+
+ </element> <!-- sudoers -->
+ </start>
+ <define name="options">
+ <zeroOrMore>
+ <choice>
+ <attribute name="dummy_attribute"/>
+ </choice>
+ </zeroOrMore>
+ </define>
+</grammar>
+
diff --git a/sudoers/sudoers.xslt b/sudoers/sudoers.xslt
new file mode 100644
index 0000000..4b19e51
--- /dev/null
+++ b/sudoers/sudoers.xslt
@@ -0,0 +1,566 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:md="http://freeipa.org/xsl/metadata/1.0"
+ xmlns:ipa10="http://freeipa.org/xml/rng/ipa/1.0"
+ xmlns:user10="http://freeipa.org/xml/rng/user/1.0"
+ xmlns:group10="http://freeipa.org/xml/rng/posixGroup/1.0"
+ xmlns:netgroup10="http://freeipa.org/xml/rng/netgroup/1.0"
+ xmlns:sudoOptions10="http://freeipa.org/xml/rng/sudo/sudoOptions/1.0"
+ xmlns:sudoers10="http://freeipa.org/xml/rng/sudo/sudoers/1.0">
+
+ <md:output_file name="/etc/sudoers" owner="root" group="root" permission="440"/>
+ <xsl:output method="text" indent="no"/>
+ <xsl:strip-space elements="*"/>
+
+ <xsl:template match="/">
+ <xsl:text># IPA generated /etc/sudoers: DO NOT EDIT&#xA;&#xA;</xsl:text>
+ <xsl:apply-templates select="ipa10:ipa"/>
+ </xsl:template>
+
+ <xsl:template match="ipa10:ipa">
+ <xsl:apply-templates>
+ <xsl:with-param name="users" select="''"/>
+ </xsl:apply-templates>
+ </xsl:template>
+
+ <xsl:template match="user10:user">
+ <xsl:param name="users"/>
+ <xsl:variable name="uids">
+ <xsl:call-template name="tokenize">
+ <xsl:with-param name="s" select="@uids"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="space">
+ <xsl:choose>
+ <xsl:when test="$users = ''">
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text> </xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:apply-templates>
+ <xsl:with-param name="users" select="concat($users, $space, $uids)"/>
+ </xsl:apply-templates>
+
+ </xsl:template>
+
+ <xsl:template match="group10:posixGroup">
+ <xsl:param name="users"/>
+ <xsl:variable name="gids">
+ <xsl:call-template name="tokenize">
+ <xsl:with-param name="s" select="@gids"/>
+ <xsl:with-param name="prefix" select="'%'"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="space">
+ <xsl:choose>
+ <xsl:when test="$users = ''">
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text> </xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:apply-templates>
+ <xsl:with-param name="users" select="concat($users, $space, $gids)"/>
+ </xsl:apply-templates>
+ </xsl:template>
+
+ <xsl:template match="netgroup10:netgroup">
+ <xsl:param name="users"/>
+ <xsl:variable name="netgroups">
+ <xsl:call-template name="tokenize">
+ <!--<xsl:with-param name="s" select="@netgroup"/> -->
+ <xsl:with-param name="s" select="@groupnames"/>
+ <xsl:with-param name="prefix" select="'+'"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="space">
+ <xsl:choose>
+ <xsl:when test="$users = ''">
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text> </xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:apply-templates>
+ <xsl:with-param name="users" select="concat($users, $space, $netgroups)"/>
+ </xsl:apply-templates>
+ </xsl:template>
+
+ <xsl:template match="sudoOptions10:sudoOptions">
+ <xsl:param name="users"/>
+
+ <xsl:call-template name="defaults">
+ <xsl:with-param name="users" select="$users"/>
+ </xsl:call-template>
+ <xsl:apply-templates>
+ <xsl:with-param name="users" select="$users"/>
+ </xsl:apply-templates>
+ </xsl:template>
+
+ <xsl:template match="sudoers10:sudoers">
+ <xsl:param name="users"/>
+ <!-- Add this if <sudoers> is allowed to carry option (Defaults) attributes
+ <xsl:call-template name="defaults"/>
+ -->
+ <xsl:apply-templates select="sudoers10:command">
+ <xsl:with-param name="users" select="$users"/>
+ </xsl:apply-templates>
+ </xsl:template>
+
+ <xsl:template match="sudoers10:command">
+ <xsl:param name="users"/>
+
+ <xsl:variable name="runas">
+ <xsl:choose>
+ <xsl:when test="sudoers10:runas">
+ <xsl:for-each select="sudoers10:runas">
+ <xsl:if test="position() > 1">
+ <xsl:text>,</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="."/>
+ </xsl:for-each>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="'ALL'"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:variable name="tags">
+ <xsl:choose>
+ <xsl:when test="sudoers10:tag">
+ <xsl:call-template name="tokenize">
+ <xsl:with-param name="s" select="sudoers10:tag"/>
+ <xsl:with-param name="suffix" select="':'"/>
+ </xsl:call-template>
+ <!-- Include a trailing space for easier output formatting. -->
+ <xsl:text> </xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="''"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:variable name="command" select="sudoers10:path"/>
+ <xsl:variable name="args" select="sudoers10:args"/>
+
+ <!--
+ user|%group|+netgroup.USER HOST|+netgroup.HOST = (runas) tags: commands
+ runas::= user|#uid|%group|+netgroup.USER
+ -->
+ <xsl:call-template name="tokenize">
+ <xsl:with-param name="s" select="$users"/>
+ <xsl:with-param name="suffix">
+ <xsl:text> </xsl:text>
+ <xsl:text>ALL</xsl:text>
+ <xsl:text> = (</xsl:text>
+ <xsl:value-of select="$runas"/>
+ <xsl:text>) </xsl:text>
+ <xsl:value-of select="$tags"/>
+ <xsl:value-of select="$command"/>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="$args"/>
+ </xsl:with-param>
+ <xsl:with-param name="separator" select="'&#xA;'"/>
+ </xsl:call-template>
+
+ <xsl:text>&#xA;</xsl:text>
+ </xsl:template>
+
+ <xsl:template name="defaults">
+ <xsl:param name="users"/>
+
+ <xsl:for-each select="@*">
+ <xsl:variable name="var" select="name()"/>
+ <xsl:variable name="val" select="."/>
+ <xsl:variable name="prefix">
+ <xsl:choose>
+ <xsl:when test="$users = ''">
+ <xsl:text>Defaults</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>Defaults:</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:call-template name="tokenize">
+ <xsl:with-param name="s" select="$users"/>
+ <xsl:with-param name="prefix">
+ <xsl:choose>
+ <xsl:when test="$users = ''">
+ <xsl:text>Defaults</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>Defaults:</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:with-param>
+ <xsl:with-param name="suffix">
+ <xsl:text> </xsl:text>
+
+ <xsl:choose>
+ <!-- boolean handling -->
+
+ <xsl:when test="name() = 'always_set_home'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'authenticate'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'env_editor'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'env_reset'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'fqdn'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'ignore_local_sudoers'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'insults'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'log_host'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'log_year'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'long_otp_prompt'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'mail_always'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'mail_badpass'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'mail_no_host'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'mail_no_perms'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'mail_no_user'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'noexec'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'path_info'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'passprompt_override'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'preserve_groups'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'requiretty'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'root_sudo'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'rootpw'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'runaspw'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'set_home'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'set_logname'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'setenv'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'shell_noargs'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'stay_setuid'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'targetpw'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <xsl:when test="name() = 'ttytickets'">
+ <xsl:if test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+
+ <!-- boolean/string handling -->
+
+ <xsl:when test="name() = 'syslog'">
+ <xsl:choose>
+ <xsl:when test=". = 'off'">
+ <xsl:text>!</xsl:text>
+ <xsl:value-of select="name()"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="name()"/>
+ <xsl:text>=</xsl:text>
+ <xsl:value-of select="."/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <!-- quoted/list handling -->
+
+ <xsl:when test="name() = 'badpass_message'">
+ <xsl:value-of select="$var"/>
+ <xsl:text>= "</xsl:text>
+ <xsl:value-of select="$val"/>
+ <xsl:text>"</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="name() = 'mailsub'">
+ <xsl:value-of select="$var"/>
+ <xsl:text>= "</xsl:text>
+ <xsl:value-of select="$val"/>
+ <xsl:text>"</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="name() = 'passprompt'">
+ <xsl:value-of select="$var"/>
+ <xsl:text>= "</xsl:text>
+ <xsl:value-of select="$val"/>
+ <xsl:text>"</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="name() = 'role'">
+ <xsl:value-of select="$var"/>
+ <xsl:text>= "</xsl:text>
+ <xsl:value-of select="$val"/>
+ <xsl:text>"</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="name() = 'mailerflags'">
+ <xsl:value-of select="$var"/>
+ <xsl:text>= "</xsl:text>
+ <xsl:value-of select="$val"/>
+ <xsl:text>"</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="name() = 'env_check'">
+ <xsl:value-of select="$var"/>
+ <xsl:text>= "</xsl:text>
+ <xsl:value-of select="$val"/>
+ <xsl:text>"</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="name() = 'env_delete'">
+ <xsl:value-of select="$var"/>
+ <xsl:text>= "</xsl:text>
+ <xsl:value-of select="$val"/>
+ <xsl:text>"</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="name() = 'env_keep'">
+ <xsl:value-of select="$var"/>
+ <xsl:text>= "</xsl:text>
+ <xsl:value-of select="$val"/>
+ <xsl:text>"</xsl:text>
+ </xsl:when>
+
+ <!-- default handling -->
+ <xsl:otherwise>
+ <xsl:value-of select="name()"/>
+ <xsl:text>=</xsl:text>
+ <xsl:value-of select="."/>
+ </xsl:otherwise>
+
+ </xsl:choose>
+
+ <xsl:text>&#xA;</xsl:text>
+ </xsl:with-param>
+ <xsl:with-param name="separator" select="''"/>
+ </xsl:call-template>
+
+ <xsl:text>&#xA;</xsl:text>
+ </xsl:for-each>
+ </xsl:template>
+
+ <xsl:template name="tokenize">
+ <xsl:param name="s" select="."/>
+ <xsl:param name="delim" select="' '"/>
+ <xsl:param name="prefix" select="''"/>
+ <xsl:param name="suffix" select="''"/>
+ <xsl:param name="separator" select="' '"/>
+ <xsl:param name="action" select="'output'"/>
+ <xsl:param name="first" select="true()"/>
+
+ <xsl:choose>
+ <xsl:when test="contains($s, $delim)">
+ <xsl:choose>
+ <xsl:when test="$action = 'output'">
+ <xsl:if test="$first != true()">
+ <xsl:value-of select="$separator"/>
+ </xsl:if>
+ <xsl:value-of select="$prefix"/>
+ <xsl:value-of select="substring-before($s, $delim)"/>
+ <xsl:value-of select="$suffix"/>
+ </xsl:when>
+ </xsl:choose>
+ <xsl:call-template name="tokenize">
+ <xsl:with-param name="first" select="false()"/>
+ <xsl:with-param name="s" select="substring-after($s, $delim)"/>
+ <xsl:with-param name="delim" select="$delim"/>
+ <xsl:with-param name="prefix" select="$prefix"/>
+ <xsl:with-param name="suffix" select="$suffix"/>
+ <xsl:with-param name="separator" select="$separator"/>
+ <xsl:with-param name="action" select="$action"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="$action = 'output'">
+ <xsl:if test="$first != true()">
+ <xsl:value-of select="$separator"/>
+ </xsl:if>
+ <xsl:value-of select="$prefix"/>
+ <xsl:value-of select="$s"/>
+ <xsl:value-of select="$suffix"/>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/sudoers/sudoers2xml b/sudoers/sudoers2xml
new file mode 100644
index 0000000..4ce9b18
--- /dev/null
+++ b/sudoers/sudoers2xml
@@ -0,0 +1,214 @@
+#!/usr/bin/env perl
+use strict;
+use feature 'state';
+use Data::Dumper;
+#
+# Converts a sudoers file to LDIF format in prepration for loading into
+# the LDAP server.
+#
+# $Sudo: sudoers2ldif,v 1.2.2.1 2007/06/28 14:45:19 millert Exp $
+#
+
+# BUGS:
+# Does not yet handle multiple lines with : in them
+# Does not yet remove quotation marks from options
+# Does not yet escape + at the beginning of a dn
+# Does not yet handle line wraps correctly
+# Does not yet handle multiple roles with same name (needs tiebreaker)
+# Sudoers entries can have multiple Runas entries that override former ones,
+# with LDAP sudoRunas applies to all commands in a sudoRole
+
+my %UA;
+my %HA;
+my %CA;
+my %RA;
+##my $base=$ENV{SUDOERS_BASE} or die "$0: Container SUDOERS_BASE undefined\n";
+my @options=();
+
+my %user_group_namespaces=();
+$user_group_namespaces{'netgroup'}='xmlns="http://freeipa.org/xml/rng/netgroup/1.0"';
+$user_group_namespaces{'posixGroup'}='xmlns="http://freeipa.org/xml/rng/posixGroup/1.0"';
+$user_group_namespaces{'user'}='xmlns="http://freeipa.org/xml/rng/user/1.0"';
+my %user_group_attributename=();
+$user_group_attributename{'netgroup'}='groupnames';
+$user_group_attributename{'posixGroup'}='gids';
+$user_group_attributename{'user'}='uids';
+
+my $did_defaults=0;
+
+print '<?xml version="1.0" encoding="UTF-8"?>'."\n";
+print '<ipa xmlns="http://freeipa.org/xml/rng/ipa/1.0">'."\n";
+
+# parse sudoers one line at a time
+while (<>){
+
+ # remove comment
+ s/#.*//;
+
+ # line continuation
+ $_.=<> while s/\\\s*$//s;
+
+ # cleanup newline
+ chomp;
+
+ # ignore blank lines
+ next if /^\s*$/;
+
+ if (/^Defaults\s+/i) {
+ my $opt=$';
+ $opt=~s/\s+$//; # remove trailing whitespace
+ push @options,$opt;
+ } elsif (/^Defaults[:@>]/i) {
+ #ignore
+ my $opt=$';
+ } elsif (/^(\S+)\s+(.+)=\s*(.*)/) {
+
+ # Aliases or Definitions
+ my ($p1,$p2,$p3)=($1,$2,$3);
+ $p2=~s/\s+$//; # remove trailing whitespace
+ $p3=~s/\s+$//; # remove trailing whitespace
+
+ if ($p1 eq "User_Alias") {
+ $UA{$p2}=$p3;
+ } elsif ($p1 eq "Host_Alias") {
+ $HA{$p2}=$p3;
+ } elsif ($p1 eq "Cmnd_Alias") {
+ $CA{$p2}=$p3;
+ } elsif ($p1 eq "Runas_Alias") {
+ $RA{$p2}=$p3;
+ } else {
+ if (!$did_defaults++){
+ # do this once
+ ##print "dn: cn=defaults,$base\n";
+ ##print "objectClass: top\n";
+ ##print "objectClass: sudoRole\n";
+ ##print "cn: defaults\n";
+ ##print "description: Default sudoOption's go here\n";
+ ##print "sudoOption: $_\n" foreach @options;
+ print '<sudoOptions xmlns="http://freeipa.org/xml/rng/sudo/sudoOptions/1.0" ';
+ #print "$_ " foreach @options;
+ foreach ( split /,/, (join ',', @options) ) {
+ $_ =~ s/=(.*)/=\"$1\"/;
+ print "$_ ";
+ }
+ print "/>\n";
+ print "\n";
+ }
+ # Definition
+ my @users=split /\s*,\s*/,$p1;
+ my @hosts=split /\s*,\s*/,$p2;
+ $p3 =~ s/\\,/\\#/g;
+ my @cmds= split /\s*,\s*/,$p3;
+ s/\\#/,/g foreach @cmds;
+ @options=();
+ foreach my $user (expand(\%UA, @users)) {
+ my $type="user";
+ $type="posixGroup" if $user =~ s/^%//;
+ $type="netgroup" if $user =~ s/^\+//;
+ print "<$type $user_group_namespaces{$type} $user_group_attributename{$type}=\"$user\">\n";
+ tl(1);
+ # only "global" user options here!!!
+ #if (@options) {
+ # print tl() . '<sudoOptions xmlns="http://freeipa.org/xml/rng/sudo/sudoOptions/1.0"';
+ # print "$_ " foreach @options;
+ # print "/>\n";
+ #}
+ print tl() . '<sudoers xmlns="http://freeipa.org/xml/rng/sudo/sudoers/1.0">'."\n";
+ tl(1);
+#print Dumper(%CA, @cmds);
+ my @mycmds=@cmds;
+ #foreach my $command (expand(\%CA,@cmds)) {
+ foreach my $command (@mycmds) {
+ my $runas='';
+ if ( $command =~ s/^\(([^\)]+)\)\s*//) {
+ $runas .= "<runas>$_</runas>" foreach expand(\%RA, split(/,\s*/, $1));
+ }
+ foreach (expand(\%CA,$command)) {
+ my $tag='';
+ if (@options) {
+ $tag = '<tag>';
+ $tag .= "$_ " foreach @options;
+ $tag =~ s/ $/<\/tag>/;
+ }
+ print tl() . "<command><path>$_</path>$tag$runas</command>\n"
+ }
+ @options=();
+ }
+ tl(-1);
+ print tl() . "</sudoers>\n";
+ tl(-1);
+ ##print "dn: cn=$users[0],$base\n";
+ ##print "objectClass: top\n";
+ ##print "objectClass: sudoRole\n";
+ ##print "cn: $users[0]\n";
+ # will clobber options
+ ##print "sudoUser: $_\n" foreach expand(\%UA,@users);
+ ##print "sudoHost: $_\n" foreach expand(\%HA,@hosts);
+ ##my $runas = undef;
+ ##foreach (@cmds) {
+ ##if (s/^\(([^\)]+)\)\s*//) {
+ ##print "sudoRunas: $_\n" foreach expand(\%UA, split(/,\s*/, $1));
+ ##}
+ ##}
+ ##print "sudoCommand: $_\n" foreach expand(\%CA,@cmds);
+ ##print "sudoOption: $_\n" foreach @options;
+ print "</$type>\n";
+ print "\n";
+ }
+ }
+
+ } else {
+ print "parse error: $_\n";
+ }
+
+}
+print '</ipa>'."\n";
+
+#
+# recursively expand hash elements
+sub expand{
+ my $ref=shift;
+ my @a=();
+
+ # preen the line a little
+ foreach (@_){
+ # if NOPASSWD: directive found, mark entire entry as not requiring
+ s/NOPASSWD:\s*// && push @options,'NOPASSWD';
+ s/PASSWD:\s*// && push @options,'PASSWD';
+ s/NOEXEC:\s*// && push @options,"NOEXEC";
+ s/EXEC:\s*// && push @options,"EXEC";
+ s/SETENV:\s*// && push @options,"SETENV";
+ s/NOSETENV:\s*// && push @options,"NOSETENV";
+ s/\w+://; # silently remove other directives
+ s/\s+$//; # right trim
+ }
+
+ # do the expanding
+ #push @a,$ref->{$_} ? expand($ref,split /\s*,\s*/,$ref->{$_}):$_ foreach @_;
+ foreach (@_) {
+ if ( s/^!// ) {
+ my @dummy = $ref->{$_} ? expand($ref,split /\s*,\s*/,$ref->{$_}):$_;
+ s/^/!/ foreach @dummy;
+ push @a, @dummy;
+ } else {
+ push @a, $ref->{$_} ? expand($ref,split /\s*,\s*/,$ref->{$_}):$_;
+ }
+ }
+ @a;
+}
+
+sub tl {
+ my $d = @_?$_[0]:0;
+
+ state $tablevel=0;
+
+#print $d."\n";
+#print "t=" . $tablevel."\n";
+
+ $tablevel += $d;
+#print "t2=" . $tablevel."\n";
+ $tablevel=0 if ( $tablevel < 0 );
+#print "t3=" . $tablevel."\n";
+
+ return "\t" x $tablevel;
+}
diff --git a/sudoers/ttygroup.rng b/sudoers/ttygroup.rng
new file mode 100644
index 0000000..c8dfce6
--- /dev/null
+++ b/sudoers/ttygroup.rng
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+ datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
+ xmlns:a="http://freeipa.org/xml/rng/ns/annotations/1.0">
+
+ <a:doc> ttygroup configuration</a:doc>
+
+ <start ns="http://freeipa.org/xml/rng/ttygroup/1.0">
+ <element name="ttygroup">
+ <ref name="options"/>
+ <ref name="anyElement"/>
+ </element>
+
+ </start>
+
+ <define name="options">
+ <zeroOrMore>
+ <choice>
+ <attribute name="terminals">
+ <text/>
+ </attribute>
+ </choice>
+ </zeroOrMore>
+ </define>
+ <!--
+ This allows anything in another namespace.
+ The way nsName works requires that this be in each schema (grammar);
+ it cannot be included.
+ -->
+ <define name="anyElement">
+ <element>
+ <anyName>
+ <except>
+ <nsName/>
+ <nsName ns=""/>
+ </except>
+ </anyName>
+ <zeroOrMore>
+ <choice>
+ <attribute>
+ <anyName/>
+ </attribute>
+ <text/>
+ <ref name="anyElement"/>
+ </choice>
+ </zeroOrMore>
+ </element>
+ </define>
+</grammar>
diff --git a/sudoers/user.rng b/sudoers/user.rng
new file mode 100644
index 0000000..b0aec32
--- /dev/null
+++ b/sudoers/user.rng
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+ datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
+ xmlns:a="http://freeipa.org/xml/rng/ns/annotations/1.0">
+
+ <a:doc> user configuration</a:doc>
+
+ <start ns="http://freeipa.org/xml/rng/user/1.0">
+ <element name="user">
+ <ref name="options"/>
+ <ref name="anyElement"/>
+ </element>
+
+ </start>
+
+ <define name="options">
+ <zeroOrMore>
+ <choice>
+ <attribute name="uids">
+ <text/>
+ </attribute>
+ </choice>
+ </zeroOrMore>
+ </define>
+ <!--
+ This allows anything in another namespace.
+ The way nsName works requires that this be in each schema (grammar);
+ it cannot be included.
+ -->
+ <define name="anyElement">
+ <element>
+ <anyName>
+ <except>
+ <nsName/>
+ <nsName ns=""/>
+ </except>
+ </anyName>
+ <zeroOrMore>
+ <choice>
+ <attribute>
+ <anyName/>
+ </attribute>
+ <text/>
+ <ref name="anyElement"/>
+ </choice>
+ </zeroOrMore>
+ </element>
+ </define>
+</grammar>
diff --git a/sudoers/username.rng b/sudoers/username.rng
new file mode 100644
index 0000000..9b3f37f
--- /dev/null
+++ b/sudoers/username.rng
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+ datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
+ xmlns:a="http://freeipa.org/xml/rng/ns/annotations/1.0">
+ <define name="username_pattern" combine="choice">
+ <data type="string">
+ <param name="pattern">[A-Za-z0-9_-]{1,16}</param>
+ </data>
+ </define>
+</grammar>
+