]> granicus.if.org Git - sudo/commitdiff
Add timed entry support from Andreas Mueller.
authorTodd C. Miller <Todd.Miller@courtesan.com>
Thu, 11 Nov 2010 21:54:45 +0000 (16:54 -0500)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Thu, 11 Nov 2010 21:54:45 +0000 (16:54 -0500)
--HG--
branch : 1.7

ldap.c
schema.ActiveDirectory
schema.OpenLDAP
schema.iPlanet
sudoers.ldap.pod

diff --git a/ldap.c b/ldap.c
index ce742b38a03fce86168ec8d10b1dd1c68004f739..c99f03c1ccb45cc2076235394e6a27a50b2aacf9 100644 (file)
--- a/ldap.c
+++ b/ldap.c
@@ -40,6 +40,9 @@
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #endif /* HAVE_UNISTD_H */
+#if TIME_WITH_SYS_TIME
+# include <time.h>
+#endif
 #include <ctype.h>
 #include <pwd.h>
 #include <grp.h>
@@ -118,6 +121,15 @@ struct ldap_result_list {
     LDAPMessage *result;
 };
 
+/* The TIMEFILTER_LENGTH includes the filter itself plus the global AND
+   wrapped around the user filter and the time filter when timed entries
+   are used. The length is computed as follows:
+       85       for the filter
+       + 2 * 13 for the now timestamp
+       +      3 for the global AND
+*/
+#define TIMEFILTER_LENGTH      114    
+
 struct ldap_config_table {
     const char *conf_str;      /* config file string */
     short type;                        /* CONF_BOOL, CONF_INT, CONF_STR */
@@ -143,6 +155,7 @@ static struct ldap_config {
     int use_sasl;
     int rootuse_sasl;
     int ssl_mode;
+    int timed;
     char *host;
     struct ldap_config_list_str *uri;
     char *binddn;
@@ -224,6 +237,7 @@ static struct ldap_config_table ldap_conf_table[] = {
     { "bindpw", CONF_STR, FALSE, -1, &ldap_conf.bindpw },
     { "rootbinddn", CONF_STR, FALSE, -1, &ldap_conf.rootbinddn },
     { "sudoers_base", CONF_LIST_STR, FALSE, -1, &ldap_conf.base },
+    { "sudoers_timed", CONF_BOOL, FALSE, -1, &ldap_conf.timed },
 #ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S
     { "use_sasl", CONF_BOOL, FALSE, -1, &ldap_conf.use_sasl },
     { "sasl_auth_id", CONF_STR, FALSE, -1, &ldap_conf.sasl_auth_id },
@@ -842,6 +856,63 @@ sudo_ldap_parse_options(ld, entry)
     ldap_value_free_len(bv);
 }
 
+/*
+ * Build an LDAP timefilter.
+ *
+ * Stores a filter in the buffer that makes sure only entries
+ * are selected that have a sudoNotBefore in the past and a
+ * sudoNotAfter in the future, i.e. a filter of the following
+ * structure (spaced out a little more for better readability:
+ *
+ * (&
+ *   (|
+ *     (!(sudoNotAfter=*))
+ *     (sudoNotAfter>__now__)
+ *   )
+ *   (|
+ *     (!(sudoNotBefore=*))
+ *     (sudoNotBefore<__now__)
+ *   )
+ * )
+ *
+ * If either the sudoNotAfter or sudoNotBefore attributes are missing,
+ * no time restriction shall be imposed.
+ */
+static int
+sudo_ldap_timefilter(buffer, buffersize)
+    char *buffer;
+    size_t buffersize;
+{
+    struct tm *tp;
+    time_t now;
+    char timebuffer[16];
+    int bytes = 0;
+
+    /* Make sure we have a formatted timestamp for __now__. */
+    time(&now);
+    if ((tp = gmtime(&now)) == NULL) {
+       warning("unable to get GMT");
+       goto done;
+    }
+
+    /* Format the timestamp according to the RFC. */
+    if (strftime(timebuffer, sizeof(timebuffer), "%Y%m%d%H%MZ", tp) == 0) {
+       warning("unable to format timestamp");
+       goto done;
+    }
+
+    /* Build filter. */
+    bytes = snprintf(buffer, buffersize, "(&(|(!(sudoNotAfter=*))(sudoNotAfter>=%s))(|(!(sudoNotBefore=*))(sudoNotBefore<=%s)))",
+       timebuffer, timebuffer);
+    if (bytes < 0 || bytes >= buffersize) {
+       warning("unable to build time filter");
+       bytes = 0;
+    }
+
+done:
+    return(bytes);
+}
+
 /*
  * builds together a filter to check against ldap
  */
@@ -852,6 +923,7 @@ sudo_ldap_build_pass1(pw)
     struct group *grp;
     size_t sz;
     char *buf;
+    char timebuffer[TIMEFILTER_LENGTH];
     int i;
 
     /* Start with (|(sudoUser=USERNAME)(sudoUser=ALL)) + NUL */
@@ -870,10 +942,22 @@ sudo_ldap_build_pass1(pw)
            gr_delref(grp);
        }
     }
+
+    /* If timed, add space for time limits. */
+    if (ldap_conf.timed)
+       sz += TIMEFILTER_LENGTH;
     buf = emalloc(sz);
+    *buf = '\0';
+
+    /*
+     * If timed, start a global AND clause that will have the time limits
+     * as the second leg.
+     */
+    if (ldap_conf.timed)
+       (void) strlcpy(buf, "(&", sz);
 
     /* Global OR + sudoUser=user_name filter */
-    (void) strlcpy(buf, "(|(sudoUser=", sz);
+    (void) strlcat(buf, "(|(sudoUser=", sz);
     (void) strlcat(buf, pw->pw_name, sz);
     (void) strlcat(buf, ")", sz);
 
@@ -898,9 +982,17 @@ sudo_ldap_build_pass1(pw)
     }
 
     /* Add ALL to list and end the global OR */
-    if (strlcat(buf, "(sudoUser=ALL))", sz) >= sz)
+    if (strlcat(buf, "(sudoUser=ALL)", sz) >= sz)
        errorx(1, "sudo_ldap_build_pass1 allocation mismatch");
 
+    /* Add the time restriction, or simply end the global OR. */
+    if (ldap_conf.timed) {
+       strlcat(buf, ")", sz); /* closes the global OR */
+       sudo_ldap_timefilter(timebuffer, sizeof(timebuffer));
+       strlcat(buf, timebuffer, sz);
+    }
+    strlcat(buf, ")", sz); /* closes the global OR or the global AND */
+
     return(buf);
 }
 
index 4b87e054e0799f5d5ce4a2aea109c17ad1a0600d..fb758ac3ce8e884a38dcdd8c44ddd9dcc01d61fe 100644 (file)
@@ -158,6 +158,44 @@ name: sudoRunAsGroup
 schemaIDGUID:: xJhSt/Yd3RGJPTB1VtiVkw==\r
 objectCategory: CN=Attribute-Schema,CN=Schema,CN=Configuration,DC=X\r
 \r
+dn: CN=sudoNotBefore,CN=Schema,CN=Configuration,DC=X
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: sudoNotBefore
+distinguishedName: CN=sudoNotBefore,CN=Schema,CN=Configuration,DC=X
+instanceType: 4
+attributeID: 1.3.6.1.4.1.15953.9.1.8
+attributeSyntax: 1.3.6.1.4.1.1466.115.121.1.24
+isSingleValued: TRUE
+showInAdvancedViewOnly: TRUE
+adminDisplayName: sudoNotBefore
+adminDescription: Start of time interval for which the entry is valid
+oMSyntax: 22
+lDAPDisplayName:  sudoNotBefore
+name: sudoNotBefore
+schemaIDGUID:: xJhSt/Yd3RGJPTB1VtiVkw==
+objectCategory: CN=Attribute-Schema,CN=Schema,CN=Configuration,DC=X
+
+dn: CN=sudoNotAfter,CN=Schema,CN=Configuration,DC=X
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: sudoNotAfter
+distinguishedName: CN=sudoNotAfter,CN=Schema,CN=Configuration,DC=X
+instanceType: 4
+attributeID: 1.3.6.1.4.1.15953.9.1.9
+attributeSyntax: 1.3.6.1.4.1.1466.115.121.1.24
+isSingleValued: TRUE
+showInAdvancedViewOnly: TRUE
+adminDisplayName: sudoNotAfter
+adminDescription: End of time interval for which the entry is valid
+oMSyntax: 22
+lDAPDisplayName:  sudoNotAfter
+name: sudoNotAfter
+schemaIDGUID:: xJhSt/Yd3RGJPTB1VtiVkw==
+objectCategory: CN=Attribute-Schema,CN=Schema,CN=Configuration,DC=X
+
 dn:\r
 changetype: modify\r
 add: schemaUpdateNow\r
@@ -182,6 +220,8 @@ mayContain: sudoRunAs
 mayContain: sudoRunAsUser\r
 mayContain: sudoRunAsGroup\r
 mayContain: sudoUser\r
+mayContain: sudoNotBefore
+mayContain: sudoNotAfter
 rDNAttID: cn\r
 showInAdvancedViewOnly: FALSE\r
 adminDisplayName: sudoRole\r
index df3fc0fab6546574cdf5c871fdaa8a19e222edb1..5da4c2b82e8a425619fac2b33c20902e3a8ee212 100644 (file)
@@ -47,6 +47,20 @@ attributetype ( 1.3.6.1.4.1.15953.9.1.7
     EQUALITY caseExactIA5Match
     SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
 
+attributetype ( 1.3.6.1.4.1.15953.9.1.8
+    NAME 'sudoNotBefore'
+    DESC 'Start of time interval for which the entry is valid'
+    EQUALITY generalizedTimeMatch
+    ORDERING generalizedTimeOrderingMatch
+    SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )
+
+attributetype ( 1.3.6.1.4.1.15953.9.1.9
+    NAME 'sudoNotAfter'
+    DESC 'End of time interval for which the entry is valid'
+    EQUALITY generalizedTimeMatch
+    ORDERING generalizedTimeOrderingMatch
+    SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )
+
 objectclass ( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' SUP top STRUCTURAL
     DESC 'Sudoer Entries'
     MUST ( cn )
index 3718fd7fa7882d789f753186dbc6ba92393d4450..0cb00388e1a0ece1220c89114f6a563fe2f55b47 100644 (file)
@@ -6,4 +6,6 @@ attributeTypes: ( 1.3.6.1.4.1.15953.9.1.4 NAME 'sudoRunAs' DESC 'User(s) imperso
 attributeTypes: ( 1.3.6.1.4.1.15953.9.1.5 NAME 'sudoOption' DESC 'Options(s) followed by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'SUDO' )
 attributeTypes: ( 1.3.6.1.4.1.15953.9.1.6 NAME 'sudoRunAsUser' DESC 'User(s) impersonated by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'SUDO' )
 attributeTypes: ( 1.3.6.1.4.1.15953.9.1.7 NAME 'sudoRunAsGroup' DESC 'Group(s) impersonated by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'SUDO' )
+attributeTypes: ( 1.3.6.1.4.1.15953.9.1.8 NAME 'sudoNotBefore' DESC 'Start of time interval for which the entry is valid' EQUALITY generalizedTimeMatch ORDERING generalizedTimeOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )
+attributeTypes: ( 1.3.6.1.4.1.15953.9.1.9 NAME 'sudoNotAfter' DESC 'End of time interval for which the entry is valid' EQUALITY generalizedTimeMatch ORDERING generalizedTimeOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )
 objectClasses: ( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' SUP top STRUCTURAL DESC 'Sudoer Entries' MUST ( cn ) MAY ( sudoUser $ sudoHost $ sudoCommand $ sudoRunAs $ sudoRunAsUser $ sudoRunAsGroup $ sudoOption $ description ) X-ORIGIN 'SUDO' )
index f7a39c93425dc183ddd141d5f72dcc5cd8ab9e29..4622bc773c0ca8e94bb4c4ac305ae84684fc7c8f 100644 (file)
@@ -138,6 +138,18 @@ The special value C<ALL> will match any user.
 A Unix group or gid (prefixed with C<'#'>) that commands may be run as.
 The special value C<ALL> will match any group.
 
+=item B<sudoNotBefore>
+
+A timestamp in the form C<yyyymmddHHMMZ> that indicates start of validity
+of this C<sudoRole>.
+If multiple B<sudoNotBefore> entries are present, the earliest is used.
+
+=item B<sudoNotAfter>
+
+A timestamp in the form C<yyyymmddHHMMZ> that indicates end of validity
+of this C<sudoRole>.
+If multiple B<sudoNotAfter> entries are present, the last one is used.
+
 =back
 
 Each component listed above should contain a single value, but there
@@ -165,6 +177,10 @@ in this query too.)  If no match is returned for the user's name
 and groups, a third query returns all entries containing user
 netgroups and checks to see if the user belongs to any of them.
 
+If timed entries are enabled with the B<SUDOERS_TIMED> configuration
+directive, the LDAP queries include a subfilter that limits retrieval
+to entries that satisfy the time constraints, if any are present.
+
 =head2 Differences between LDAP and non-LDAP sudoers
 
 There are some subtle differences in the way sudoers is handled
@@ -306,6 +322,11 @@ this is of the form C<ou=SUDOers,dc=example,dc=com> for the domain
 C<example.com>.  Multiple B<SUDOERS_BASE> lines may be specified,
 in which case they are queried in the order specified.
 
+=item B<SUDOERS_TIMED> on/true/yes/off/false/no
+
+Whether or not to evaluate the sudoNotBefore and sudoNotAfter
+attributes that implement time-dependent sudoers entries.
+
 =item B<SUDOERS_DEBUG> debug_level
 
 This sets the debug level for B<sudo> LDAP queries.  Debugging
@@ -577,6 +598,9 @@ determines sudoers source order on AIX
   # verbose sudoers matching from ldap
   #sudoers_debug 2
   #
+  # Enable support for time-based entries in sudoers.
+  #sudoers_timed yes
+  #
   # optional proxy credentials
   #binddn        <who to search as>
   #bindpw        <password>
@@ -704,11 +728,26 @@ C<include> line in C<slapd.conf> and restart B<slapd>.
     EQUALITY caseExactIA5Match
     SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
 
+ attributetype ( 1.3.6.1.4.1.15953.9.1.8
+    NAME 'sudoNotBefore'
+    DESC 'Start of time interval for which the entry is valid'
+    EQUALITY generalizedTimeMatch
+    ORDERING generalizedTimeOrderingMatch
+    SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )
+
+ attributetype ( 1.3.6.1.4.1.15953.9.1.9
+    NAME 'sudoNotAfter'
+    DESC 'End of time interval for which the entry is valid'
+    EQUALITY generalizedTimeMatch
+    ORDERING generalizedTimeOrderingMatch
+    SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )
+
  objectclass ( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' SUP top STRUCTURAL
     DESC 'Sudoer Entries'
     MUST ( cn )
     MAY ( sudoUser $ sudoHost $ sudoCommand $ sudoRunAs $ sudoRunAsUser $
-         sudoRunAsGroup $ sudoOption $ description )
+         sudoRunAsGroup $ sudoOption $ sudoNotBefore $ sudoNotAfter $
+         description )
     )
 
 =head1 SEE ALSO