From 2b0fca31c0117627c008d7d5e377e7dc6c289c6d Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Fri, 12 Nov 2010 15:26:35 -0500 Subject: [PATCH] Add timed entry support from Andreas Mueller. --- doc/schema.ActiveDirectory | 40 ++++++++++++++++ doc/schema.OpenLDAP | 14 ++++++ doc/schema.iPlanet | 2 + doc/sudoers.ldap.pod | 41 +++++++++++++++- plugins/sudoers/ldap.c | 96 +++++++++++++++++++++++++++++++++++++- 5 files changed, 190 insertions(+), 3 deletions(-) diff --git a/doc/schema.ActiveDirectory b/doc/schema.ActiveDirectory index 4b87e054e..fb758ac3c 100644 --- a/doc/schema.ActiveDirectory +++ b/doc/schema.ActiveDirectory @@ -158,6 +158,44 @@ name: sudoRunAsGroup schemaIDGUID:: xJhSt/Yd3RGJPTB1VtiVkw== objectCategory: CN=Attribute-Schema,CN=Schema,CN=Configuration,DC=X +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: changetype: modify add: schemaUpdateNow @@ -182,6 +220,8 @@ mayContain: sudoRunAs mayContain: sudoRunAsUser mayContain: sudoRunAsGroup mayContain: sudoUser +mayContain: sudoNotBefore +mayContain: sudoNotAfter rDNAttID: cn showInAdvancedViewOnly: FALSE adminDisplayName: sudoRole diff --git a/doc/schema.OpenLDAP b/doc/schema.OpenLDAP index df3fc0fab..5da4c2b82 100644 --- a/doc/schema.OpenLDAP +++ b/doc/schema.OpenLDAP @@ -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 ) diff --git a/doc/schema.iPlanet b/doc/schema.iPlanet index 3718fd7fa..0cb00388e 100644 --- a/doc/schema.iPlanet +++ b/doc/schema.iPlanet @@ -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' ) diff --git a/doc/sudoers.ldap.pod b/doc/sudoers.ldap.pod index f7a39c934..4622bc773 100644 --- a/doc/sudoers.ldap.pod +++ b/doc/sudoers.ldap.pod @@ -138,6 +138,18 @@ The special value C will match any user. A Unix group or gid (prefixed with C<'#'>) that commands may be run as. The special value C will match any group. +=item B + +A timestamp in the form C that indicates start of validity +of this C. +If multiple B entries are present, the earliest is used. + +=item B + +A timestamp in the form C that indicates end of validity +of this C. +If multiple B 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 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 for the domain C. Multiple B lines may be specified, in which case they are queried in the order specified. +=item B on/true/yes/off/false/no + +Whether or not to evaluate the sudoNotBefore and sudoNotAfter +attributes that implement time-dependent sudoers entries. + =item B debug_level This sets the debug level for B 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 #bindpw @@ -704,11 +728,26 @@ C line in C and restart B. 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 diff --git a/plugins/sudoers/ldap.c b/plugins/sudoers/ldap.c index ca7f783da..0856bb117 100644 --- a/plugins/sudoers/ldap.c +++ b/plugins/sudoers/ldap.c @@ -40,6 +40,9 @@ #ifdef HAVE_UNISTD_H # include #endif /* HAVE_UNISTD_H */ +#if TIME_WITH_SYS_TIME +# include +#endif #include #include #include @@ -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 }, @@ -817,6 +831,63 @@ sudo_ldap_parse_options(LDAP *ld, LDAPMessage *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 */ @@ -826,6 +897,7 @@ sudo_ldap_build_pass1(struct passwd *pw) struct group *grp; size_t sz; char *buf; + char timebuffer[TIMEFILTER_LENGTH]; int i; /* Start with (|(sudoUser=USERNAME)(sudoUser=ALL)) + NUL */ @@ -844,10 +916,22 @@ sudo_ldap_build_pass1(struct passwd *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); @@ -872,9 +956,17 @@ sudo_ldap_build_pass1(struct passwd *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); } -- 2.40.0