#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>
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 */
int use_sasl;
int rootuse_sasl;
int ssl_mode;
+ int timed;
char *host;
struct ldap_config_list_str *uri;
char *binddn;
{ "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 },
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
*/
struct group *grp;
size_t sz;
char *buf;
+ char timebuffer[TIMEFILTER_LENGTH];
int i;
/* Start with (|(sudoUser=USERNAME)(sudoUser=ALL)) + NUL */
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);
}
/* 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);
}
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
mayContain: sudoRunAsUser\r
mayContain: sudoRunAsGroup\r
mayContain: sudoUser\r
+mayContain: sudoNotBefore
+mayContain: sudoNotAfter
rDNAttID: cn\r
showInAdvancedViewOnly: FALSE\r
adminDisplayName: sudoRole\r
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 )
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' )
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
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
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
# 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>
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