From 28dab46aaa2c0d65160492f1d9d7c88652d8c714 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Tue, 17 May 2011 16:28:15 +0200 Subject: Support overriding attribute values locally Add a new option to override primary GID number https://fedorahosted.org/sssd/ticket/742 Add a new option to override home directory value https://fedorahosted.org/sssd/ticket/551 Add new options to override shell value https://fedorahosted.org/sssd/ticket/742 Conflicts: src/conf_macros.m4 --- configure.ac | 1 + src/conf_macros.m4 | 14 ++++ src/confdb/confdb.c | 16 +++- src/confdb/confdb.h | 8 ++ src/config/SSSDConfig.py | 4 + src/config/SSSDConfigTest.py | 4 + src/config/etc/sssd.api.conf | 5 ++ src/man/sssd.conf.5.xml | 90 ++++++++++++++++++++ src/responder/nss/nsssrv.c | 89 +++++++++++++++++++- src/responder/nss/nsssrv.h | 5 ++ src/responder/nss/nsssrv_cmd.c | 183 ++++++++++++++++++++++++++++++++++++++++- 11 files changed, 414 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index f0fa0725..84b83eb1 100644 --- a/configure.ac +++ b/configure.ac @@ -95,6 +95,7 @@ WITH_SELINUX WITH_NSCD WITH_SEMANAGE WITH_LIBNL +WITH_NOLOGIN_SHELL m4_include([src/external/pkg.m4]) m4_include([src/external/libpopt.m4]) diff --git a/src/conf_macros.m4 b/src/conf_macros.m4 index d759c48e..8ead0b29 100644 --- a/src/conf_macros.m4 +++ b/src/conf_macros.m4 @@ -251,3 +251,17 @@ AC_DEFUN([WITH_LIBNL], AC_SUBST(BUILD_LIBNL) fi ]) + +AC_DEFUN([WITH_NOLOGIN_SHELL], + [ AC_ARG_WITH([nologin-shell], + [AC_HELP_STRING([--with-nologin-shell=PATH], + [The shell used to deny access to users [/sbin/nologin]] + ) + ] + ) + nologin_shell="/sbin/nologin" + if test x"$with_nologin_shell" != x; then + nologin_shell=$with_nologin_shell + fi + AC_DEFINE_UNQUOTED(NOLOGIN_SHELL, "$nologin_shell", [The shell used to deny access to users]) + ]) diff --git a/src/confdb/confdb.c b/src/confdb/confdb.c index 4975a427..87d33f9b 100644 --- a/src/confdb/confdb.c +++ b/src/confdb/confdb.c @@ -842,9 +842,23 @@ static int confdb_get_domain_internal(struct confdb_ctx *cdb, goto done; } + ret = get_entry_as_uint32(res->msgs[0], &domain->override_gid, + CONFDB_DOMAIN_OVERRIDE_GID, 0); + if (ret != EOK) { + DEBUG(0, ("Invalid value for [%s]\n", CONFDB_DOMAIN_OVERRIDE_GID)); + goto done; + } + + tmp = ldb_msg_find_attr_as_string(res->msgs[0], + CONFDB_NSS_OVERRIDE_HOMEDIR, NULL); + domain->override_homedir = talloc_strdup(domain, tmp); + if (!domain->name) { + ret = ENOMEM; + goto done; + } + *_domain = domain; ret = EOK; - done: talloc_free(tmp_ctx); return ret; diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index 7173c9fc..c2ae9fcb 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -41,6 +41,7 @@ #define CONFDB_DEFAULT_CONFIG_FILE SSSD_CONF_DIR"/sssd.conf" #define SSSD_MIN_ID 1 #define SSSD_LOCAL_MINID 1000 +#define CONFDB_DEFAULT_SHELL_FALLBACK "/bin/sh" /* Configuration options */ @@ -72,6 +73,9 @@ #define CONFDB_NSS_FILTER_USERS "filter_users" #define CONFDB_NSS_FILTER_GROUPS "filter_groups" #define CONFDB_NSS_PWFIELD "pwfield" +#define CONFDB_NSS_OVERRIDE_HOMEDIR "override_homedir" +#define CONFDB_NSS_ALLOWED_SHELL "allowed_shells" +#define CONFDB_NSS_SHELL_FALLBACK "shell_fallback" /* PAM */ #define CONFDB_PAM_CONF_ENTRY "config/pam" @@ -109,6 +113,7 @@ #define CONFDB_DOMAIN_DNS_DISCOVERY_NAME "dns_discovery_domain" #define CONFDB_DOMAIN_FAMILY_ORDER "lookup_family_order" #define CONFDB_DOMAIN_ACCOUNT_CACHE_EXPIRATION "account_cache_expiration" +#define CONFDB_DOMAIN_OVERRIDE_GID "override_gid" /* Local Provider */ #define CONFDB_LOCAL_DEFAULT_SHELL "default_shell" @@ -143,6 +148,9 @@ struct sss_domain_info { bool cache_credentials; bool legacy_passwords; + gid_t override_gid; + const char *override_homedir; + uint32_t entry_cache_timeout; struct sss_domain_info *next; diff --git a/src/config/SSSDConfig.py b/src/config/SSSDConfig.py index 6026bf4f..fed19f07 100644 --- a/src/config/SSSDConfig.py +++ b/src/config/SSSDConfig.py @@ -58,6 +58,9 @@ option_strings = { 'filter_groups' : _('Groups that SSSD should explicitly ignore'), 'filter_users_in_groups' : _('Should filtered users appear in groups'), 'pwfield' : _('The value of the password field the NSS provider should return'), + 'override_homedir' : _('Override homedir value from the identity provider with this value'), + 'allowed_shells' : _('The list of shells users are allowed to log in with'), + 'shell_fallback' : _('If a shell stored in central directory is allowed but not available, use this fallback'), # [pam] 'offline_credentials_expiration' : _('How long to allow cached logins between online logins (days)'), @@ -85,6 +88,7 @@ option_strings = { 'account_cache_expiration' : _('How long to keep cached entries after last successful login (days)'), 'dns_resolver_timeout' : _('How long to wait for replies from DNS when resolving servers (seconds)'), 'dns_discovery_domain' : _('The domain part of service discovery DNS query'), + 'override_gid' : _('Override GID value from the identity provider with this value'), # [provider/ipa] 'ipa_domain' : _('IPA domain'), diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py index cad183ea..0e5a630e 100755 --- a/src/config/SSSDConfigTest.py +++ b/src/config/SSSDConfigTest.py @@ -479,6 +479,8 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'account_cache_expiration', 'dns_resolver_timeout', 'dns_discovery_domain', + 'override_gid', + 'override_homedir', 'id_provider', 'auth_provider', 'access_provider', @@ -808,6 +810,8 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'lookup_family_order', 'dns_resolver_timeout', 'dns_discovery_domain', + 'override_gid', + 'override_homedir', 'id_provider', 'auth_provider', 'access_provider', diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf index e9159716..49836e45 100644 --- a/src/config/etc/sssd.api.conf +++ b/src/config/etc/sssd.api.conf @@ -27,6 +27,9 @@ filter_users = list, str, false filter_groups = list, str, false filter_users_in_groups = bool, None, false pwfield = str, None, false +override_homedir = str, None, false +allowed_shells = list, str, false +shell_fallback = str, None, false [pam] # Authentication service @@ -63,6 +66,8 @@ filter_users = list, str, false filter_groups = list, str, false dns_resolver_timeout = int, None, false dns_discovery_domain = str, None, false +override_gid = int, None, false +override_homedir = str, None, false # Special providers [provider/permit] diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index 6ac9de89..b4f38465 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -354,6 +354,87 @@ + + override_homedir (string) + + + Override the user's home directory. You + can either provide an absolute value or a + template. In the template, the following + sequences are substituted: + + + %u + login name + + + %U + UID number + + + %d + domain name + + + %f + fully qualified user name (user@domain) + + + %% + a literal '%' + + + + + + This option can also be set per-domain. + + + + + allowed_shells (string) + + + Restrict user shell to one of the listed values. The order of evaluation is: + + + 1. If the shell is present in + /etc/shells, it is used. + + + 2. If the shell is in the allowed_shells list but + not in /etc/shells, use the + value of the shell_fallback parameter. + + + 3. If the shell is not in the allowed_shells list and + not in /etc/shells, a nologin shell + is used. + + + An empty string for shell is passed as-is to libc. + + + The /etc/shells is only read on SSSD start up, which means that + a restart of the SSSD is required in case a new shell is installed. + + + Default: Not set. The user shell is automatically used. + + + + + shell_fallback (string) + + + The default shell to use if an allowed shell is not + installed on the machine. + + + Default: /bin/sh + + + @@ -807,6 +888,15 @@ + + + override_gid (integer) + + + Override the primary GID value with the one specified. + + + diff --git a/src/responder/nss/nsssrv.c b/src/responder/nss/nsssrv.c index dfb0312e..dde2e95e 100644 --- a/src/responder/nss/nsssrv.c +++ b/src/responder/nss/nsssrv.c @@ -48,6 +48,9 @@ #define DEFAULT_PWFIELD "*" +#define SHELL_REALLOC_INCREMENT 5 +#define SHELL_REALLOC_MAX 50 + struct sbus_method monitor_nss_methods[] = { { MON_CLI_METHOD_PING, monitor_common_pong }, { MON_CLI_METHOD_RES_INIT, monitor_common_res_init }, @@ -63,6 +66,71 @@ struct sbus_interface monitor_nss_interface = { NULL }; +static errno_t nss_get_etc_shells(TALLOC_CTX *mem_ctx, char ***_shells) +{ + int i = 0; + char *sh; + char **shells = NULL; + TALLOC_CTX *tmp_ctx; + errno_t ret; + int size; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + shells = talloc_array(tmp_ctx, char *, SHELL_REALLOC_INCREMENT); + if (!shells) { + ret = ENOMEM; + goto done; + } + size = SHELL_REALLOC_INCREMENT; + + setusershell(); + while ((sh = getusershell())) { + shells[i] = talloc_strdup(shells, sh); + if (!shells[i]) { + endusershell(); + ret = ENOMEM; + goto done; + } + DEBUG(6, ("Found shell %s in /etc/shells\n", shells[i])); + i++; + + if (i == size) { + size += SHELL_REALLOC_INCREMENT; + if (size > SHELL_REALLOC_MAX) { + DEBUG(0, ("Reached maximum number of shells [%d]. " + "Users may be denied access. " + "Please check /etc/shells for sanity\n", + SHELL_REALLOC_MAX)); + break; + } + shells = talloc_realloc(NULL, shells, char *, + size); + if (!shells) { + ret = ENOMEM; + goto done; + } + } + } + endusershell(); + + if (i + 1 < size) { + shells = talloc_realloc(NULL, shells, char *, i + 1); + if (!shells) { + ret = ENOMEM; + goto done; + } + } + shells[i] = NULL; + + *_shells = talloc_move(mem_ctx, &shells); + ret = EOK; +done: + talloc_zfree(tmp_ctx); + return ret; +} + static int nss_get_config(struct nss_ctx *nctx, struct resp_ctx *rctx, struct confdb_ctx *cdb) @@ -94,7 +162,7 @@ static int nss_get_config(struct nss_ctx *nctx, if (ret != EOK) goto done; if (nctx->cache_refresh_percent < 0 || nctx->cache_refresh_percent > 99) { - DEBUG(0,("Configuration error: entry_cache_nowait_percentage is" + DEBUG(0,("Configuration error: entry_cache_nowait_percentage is " "invalid. Disabling feature.\n")); nctx->cache_refresh_percent = 0; } @@ -110,6 +178,25 @@ static int nss_get_config(struct nss_ctx *nctx, &nctx->pwfield); if (ret != EOK) goto done; + ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_OVERRIDE_HOMEDIR, NULL, + &nctx->override_homedir); + if (ret != EOK) goto done; + + ret = confdb_get_string_as_list(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_ALLOWED_SHELL, + &nctx->allowed_shells); + if (ret != EOK && ret != ENOENT) goto done; + + ret = nss_get_etc_shells(nctx, &nctx->etc_shells); + if (ret != EOK) goto done; + + ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_SHELL_FALLBACK, + CONFDB_DEFAULT_SHELL_FALLBACK, + &nctx->shell_fallback); + if (ret != EOK) goto done; + ret = 0; done: talloc_free(tmpctx); diff --git a/src/responder/nss/nsssrv.h b/src/responder/nss/nsssrv.h index 062d937f..f9aff566 100644 --- a/src/responder/nss/nsssrv.h +++ b/src/responder/nss/nsssrv.h @@ -57,6 +57,11 @@ struct nss_ctx { bool filter_users_in_groups; char *pwfield; + + char *override_homedir; + char **allowed_shells; + char **etc_shells; + char *shell_fallback; }; struct nss_packet; diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c index 2f918606..74c56a31 100644 --- a/src/responder/nss/nsssrv_cmd.c +++ b/src/responder/nss/nsssrv_cmd.c @@ -169,6 +169,178 @@ struct setent_ctx { /**************************************************************************** * PASSWD db related functions ***************************************************************************/ +char *expand_homedir_template(TALLOC_CTX *mem_ctx, const char *template, + const char *username, uint32_t uid, + const char *domain) +{ + char *copy; + char *p; + char *n; + char *result = NULL; + char *res = NULL; + TALLOC_CTX *tmp_ctx = NULL; + + if (template == NULL) { + DEBUG(1, ("Missing template.\n")); + return NULL; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return NULL; + + copy = talloc_strdup(tmp_ctx, template); + if (copy == NULL) { + DEBUG(1, ("talloc_strdup failed.\n")); + goto done; + } + + result = talloc_strdup(tmp_ctx, ""); + if (result == NULL) { + DEBUG(1, ("talloc_strdup failed.\n")); + goto done; + } + + p = copy; + while ( (n = strchr(p, '%')) != NULL) { + *n = '\0'; + n++; + if ( *n == '\0' ) { + DEBUG(1, ("format error, single %% at the end of the template.\n")); + goto done; + } + switch( *n ) { + case 'u': + if (username == NULL) { + DEBUG(1, ("Cannot expand user name template " + "because user name is empty.\n")); + goto done; + } + result = talloc_asprintf_append(result, "%s%s", p, + username); + break; + + case 'U': + if (uid == 0) { + DEBUG(1, ("Cannot expand uid template " + "because uid is invalid.\n")); + goto done; + } + result = talloc_asprintf_append(result, "%s%d", p, + uid); + break; + + case 'd': + if (domain == NULL) { + DEBUG(1, ("Cannot expand domain name template " + "because domain name is empty.\n")); + goto done; + } + result = talloc_asprintf_append(result, "%s%s", p, + domain); + break; + + case 'f': + if (domain == NULL || username == NULL) { + DEBUG(1, ("Cannot expand fully qualified name template " + "because domain or user name is empty.\n")); + goto done; + } + result = talloc_asprintf_append(result, "%s%s@%s", p, + username, domain); + break; + + case '%': + result = talloc_asprintf_append(result, "%s%%", p); + break; + + default: + DEBUG(1, ("format error, unknown template [%%%c].\n", *n)); + goto done; + } + + if (result == NULL) { + DEBUG(1, ("talloc_asprintf_append failed.\n")); + goto done; + } + + p = n + 1; + } + + result = talloc_asprintf_append(result, "%s", p); + if (result == NULL) { + DEBUG(1, ("talloc_asprintf_append failed.\n")); + goto done; + } + + res = talloc_move(mem_ctx, &result); +done: + talloc_zfree(tmp_ctx); + return res; +} + +static gid_t get_gid_override(struct ldb_message *msg, + struct sss_domain_info *dom) +{ + return dom->override_gid ? + dom->override_gid : + ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0); +} + +static const char *get_homedir_override(TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + struct nss_ctx *nctx, + struct sss_domain_info *dom, + const char *name, + uint32_t uid) +{ + if (dom->override_homedir) { + return expand_homedir_template(mem_ctx, dom->override_homedir, + name, uid, dom->name); + } else if (nctx->override_homedir) { + return expand_homedir_template(mem_ctx, nctx->override_homedir, + name, uid, dom->name); + } + + return talloc_strdup(mem_ctx, + ldb_msg_find_attr_as_string(msg, SYSDB_HOMEDIR, NULL)); +} + +static const char *get_shell_override(TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + struct nss_ctx *nctx) +{ + const char *user_shell; + int i; + + user_shell = ldb_msg_find_attr_as_string(msg, SYSDB_SHELL, NULL); + if (!user_shell) return NULL; + if (!nctx->allowed_shells) return talloc_strdup(mem_ctx, user_shell); + + for (i=0; nctx->etc_shells[i]; i++) { + if (strcmp(user_shell, nctx->etc_shells[i]) == 0) { + DEBUG(9, ("Shell %s found in /etc/shells\n", + nctx->etc_shells[i])); + break; + } + } + + if (nctx->etc_shells[i]) { + DEBUG(9, ("Using original shell '%s'\n", user_shell)); + return talloc_strdup(mem_ctx, user_shell); + } + + for (i=0; nctx->allowed_shells[i]; i++) { + if (strcmp(nctx->allowed_shells[i], user_shell) == 0) { + DEBUG(5, ("The shell '%s' is allowed but does not exist. " + "Using fallback\n", user_shell)); + return talloc_strdup(mem_ctx, nctx->shell_fallback); + } + } + + DEBUG(5, ("The shell '%s' is not allowed and does not exist.\n", + user_shell)); + return talloc_strdup(mem_ctx, NOLOGIN_SHELL); +} static int fill_pwent(struct sss_packet *packet, struct sss_domain_info *dom, @@ -195,6 +367,7 @@ static int fill_pwent(struct sss_packet *packet, const char *namefmt = nctx->rctx->names->fq_fmt; bool packet_initialized = false; int ncret; + TALLOC_CTX *tmp_ctx = NULL; if (add_domain) dom_len = strlen(domain); @@ -202,11 +375,14 @@ static int fill_pwent(struct sss_packet *packet, num = 0; for (i = 0; i < *count; i++) { + talloc_zfree(tmp_ctx); + tmp_ctx = talloc_new(NULL); + msg = msgs[i]; name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); uid = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0); - gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0); + gid = get_gid_override(msg, dom); if (!name || !uid || !gid) { DEBUG(2, ("Incomplete or fake user object for %s[%llu]! Skipping\n", @@ -233,8 +409,8 @@ static int fill_pwent(struct sss_packet *packet, } gecos = ldb_msg_find_attr_as_string(msg, SYSDB_GECOS, NULL); - homedir = ldb_msg_find_attr_as_string(msg, SYSDB_HOMEDIR, NULL); - shell = ldb_msg_find_attr_as_string(msg, SYSDB_SHELL, NULL); + homedir = get_homedir_override(tmp_ctx, msg, nctx, dom, name, uid); + shell = get_shell_override(tmp_ctx, msg, nctx); if (!gecos) gecos = ""; if (!homedir) homedir = "/"; @@ -298,6 +474,7 @@ static int fill_pwent(struct sss_packet *packet, num++; } + talloc_zfree(tmp_ctx); done: *count = i; -- cgit