diff options
Diffstat (limited to 'src/providers/proxy/proxy_id.c')
-rw-r--r-- | src/providers/proxy/proxy_id.c | 1962 |
1 files changed, 1962 insertions, 0 deletions
diff --git a/src/providers/proxy/proxy_id.c b/src/providers/proxy/proxy_id.c new file mode 100644 index 0000000..b1d0c22 --- /dev/null +++ b/src/providers/proxy/proxy_id.c @@ -0,0 +1,1962 @@ +/* + SSSD + + proxy_id.c + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2010 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <dhash.h> +#include "config.h" + +#include "util/sss_format.h" +#include "util/strtonum.h" +#include "providers/proxy/proxy.h" + +/* =Getpwnam-wrapper======================================================*/ + +static int save_user(struct sss_domain_info *domain, + struct passwd *pwd, const char *real_name, + const char *alias); + +static int +handle_getpw_result(enum nss_status status, struct passwd *pwd, + struct sss_domain_info *dom, bool *del_user); + +static int +delete_user(struct sss_domain_info *domain, + const char *name, uid_t uid); + +int get_pw_name(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *i_name) +{ + TALLOC_CTX *tmpctx; + struct passwd *pwd; + enum nss_status status; + char *buffer; + size_t buflen; + int ret; + uid_t uid; + bool del_user; + struct ldb_result *cached_pwd = NULL; + const char *real_name = NULL; + char *shortname_or_alias; + + DEBUG(SSSDBG_TRACE_FUNC, "Searching user by name (%s)\n", i_name); + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmpctx, i_name, &shortname_or_alias, NULL); + if (ret != EOK) { + goto done; + } + + pwd = talloc_zero(tmpctx, struct passwd); + if (!pwd) { + ret = ENOMEM; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + /* FIXME: should we move this call outside the transaction to keep the + * transaction as short as possible? */ + status = ctx->ops.getpwnam_r(shortname_or_alias, pwd, buffer, buflen, &ret); + ret = handle_getpw_result(status, pwd, dom, &del_user); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "getpwnam failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + if (del_user) { + ret = delete_user(dom, i_name, 0); + goto done; + } + + uid = pwd->pw_uid; + + /* Canonicalize the username in case it was actually an alias */ + + if (ctx->fast_alias == true) { + ret = sysdb_getpwuid(tmpctx, dom, uid, &cached_pwd); + if (ret != EOK) { + /* Non-fatal, attempt to canonicalize online */ + DEBUG(SSSDBG_TRACE_FUNC, "Request to cache failed [%d]: %s\n", + ret, strerror(ret)); + } + + if (ret == EOK && cached_pwd->count == 1) { + real_name = ldb_msg_find_attr_as_string(cached_pwd->msgs[0], + SYSDB_NAME, NULL); + if (!real_name) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cached user has no name?\n"); + } + } + } + + if (real_name == NULL) { + memset(buffer, 0, buflen); + + status = ctx->ops.getpwuid_r(uid, pwd, buffer, buflen, &ret); + ret = handle_getpw_result(status, pwd, dom, &del_user); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "getpwuid failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + real_name = sss_create_internal_fqname(tmpctx, pwd->pw_name, dom->name); + if (real_name == NULL) { + ret = ENOMEM; + goto done; + } + } + + if (del_user) { + ret = delete_user(dom, i_name, uid); + goto done; + } + + /* Both lookups went fine, we can save the user now */ + ret = save_user(dom, pwd, real_name, i_name); + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy -> getpwnam_r failed for '%s' <%d>: %s\n", + i_name, ret, strerror(ret)); + } + return ret; +} + +static int +handle_getpw_result(enum nss_status status, struct passwd *pwd, + struct sss_domain_info *dom, bool *del_user) +{ + int ret = EOK; + + if (!del_user) { + return EINVAL; + } + *del_user = false; + + switch (status) { + case NSS_STATUS_NOTFOUND: + + DEBUG(SSSDBG_TRACE_FUNC, "User not found.\n"); + *del_user = true; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(SSSDBG_TRACE_FUNC, "User found: (%s, %"SPRIuid", %"SPRIgid")\n", + pwd->pw_name, pwd->pw_uid, pwd->pw_gid); + + /* uid=0 or gid=0 are invalid values */ + /* also check that the id is in the valid range for this domain */ + if (OUT_OF_ID_RANGE(pwd->pw_uid, dom->id_min, dom->id_max) || + OUT_OF_ID_RANGE(pwd->pw_gid, dom->id_min, dom->id_max)) { + + DEBUG(SSSDBG_MINOR_FAILURE, + "User filtered out! (id out of range)\n"); + *del_user = true; + break; + } + break; + + case NSS_STATUS_UNAVAIL: + DEBUG(SSSDBG_MINOR_FAILURE, + "Remote back end is not available. Entering offline mode\n"); + ret = ENXIO; + break; + + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown return code %d\n", status); + ret = EIO; + break; + } + + return ret; +} + +static int +delete_user(struct sss_domain_info *domain, + const char *name, uid_t uid) +{ + int ret = EOK; + + if (name != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "User %s does not exist (or is invalid) on remote server," + " deleting!\n", name); + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "User with UID %"SPRIuid" does not exist (or is invalid) " + "on remote server, deleting!\n", uid); + } + + ret = sysdb_delete_user(domain, name, uid); + if (ret == ENOENT) { + ret = EOK; + } + + return ret; +} + +static int +prepare_attrs_for_saving_ops(TALLOC_CTX *mem_ctx, + bool case_sensitive, + const char *real_name, /* already_qualified */ + const char *alias, /* already qualified */ + struct sysdb_attrs **attrs) +{ + const char *lc_name = NULL; + const char *cased_alias = NULL; + errno_t ret; + + if (!case_sensitive || alias != NULL) { + if (*attrs == NULL) { + *attrs = sysdb_new_attrs(mem_ctx); + if (*attrs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Allocation error?!\n"); + ret = ENOMEM; + goto done; + } + } + } + + if (!case_sensitive) { + lc_name = sss_tc_utf8_str_tolower(*attrs, real_name); + if (lc_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot convert name to lowercase.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(*attrs, SYSDB_NAME_ALIAS, lc_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add name alias\n"); + ret = ENOMEM; + goto done; + } + + } + + if (alias != NULL) { + cased_alias = sss_get_cased_name(*attrs, alias, case_sensitive); + if (cased_alias == NULL) { + ret = ENOMEM; + goto done; + } + + /* Add the alias only if it differs from lowercased pw_name */ + if (lc_name == NULL || strcmp(cased_alias, lc_name) != 0) { + ret = sysdb_attrs_add_string(*attrs, SYSDB_NAME_ALIAS, + cased_alias); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add name alias\n"); + goto done; + } + } + } + + ret = EOK; +done: + return ret; +} + +static int save_user(struct sss_domain_info *domain, + struct passwd *pwd, + const char *real_name, /* already qualified */ + const char *alias) /* already qualified */ +{ + const char *shell; + const char *gecos; + struct sysdb_attrs *attrs = NULL; + errno_t ret; + + if (pwd->pw_shell && pwd->pw_shell[0] != '\0') { + shell = pwd->pw_shell; + } else { + shell = NULL; + } + + if (pwd->pw_gecos && pwd->pw_gecos[0] != '\0') { + gecos = pwd->pw_gecos; + } else { + gecos = NULL; + } + + ret = prepare_attrs_for_saving_ops(NULL, domain->case_sensitive, + real_name, alias, &attrs); + if (ret != EOK) { + goto done; + } + + ret = sysdb_store_user(domain, + real_name, + pwd->pw_passwd, + pwd->pw_uid, + pwd->pw_gid, + gecos, + pwd->pw_dir, + shell, + NULL, + attrs, + NULL, + domain->user_timeout, + 0); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add user to cache\n"); + goto done; + } + +done: + talloc_zfree(attrs); + return ret; +} + +/* =Getpwuid-wrapper======================================================*/ + +static int get_pw_uid(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + uid_t uid) +{ + TALLOC_CTX *tmpctx; + struct passwd *pwd; + enum nss_status status; + char *buffer; + size_t buflen; + bool del_user = false; + int ret; + char *name; + + DEBUG(SSSDBG_TRACE_FUNC, "Searching user by uid (%"SPRIuid")\n", uid); + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + return ENOMEM; + } + + pwd = talloc_zero(tmpctx, struct passwd); + if (!pwd) { + ret = ENOMEM; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getpwuid_r(uid, pwd, buffer, buflen, &ret); + ret = handle_getpw_result(status, pwd, dom, &del_user); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "getpwuid failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + if (del_user) { + ret = delete_user(dom, NULL, uid); + goto done; + } + + name = sss_create_internal_fqname(tmpctx, pwd->pw_name, dom->name); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to qualify name '%s'\n", + pwd->pw_name); + goto done; + } + ret = save_user(dom, pwd, name, NULL); + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "proxy -> getpwuid_r failed for '%"SPRIuid"' <%d>: %s\n", + uid, ret, strerror(ret)); + } + return ret; +} + +/* =Getpwent-wrapper======================================================*/ + +static int enum_users(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom) +{ + TALLOC_CTX *tmpctx; + bool in_transaction = false; + struct passwd *pwd; + enum nss_status status; + size_t buflen; + char *buffer; + char *newbuf; + int ret; + errno_t sret; + bool again; + char *name; + + DEBUG(SSSDBG_TRACE_LIBS, "Enumerating users\n"); + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + pwd = talloc_zero(tmpctx, struct passwd); + if (!pwd) { + ret = ENOMEM; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + status = ctx->ops.setpwent(); + if (status != NSS_STATUS_SUCCESS) { + ret = EIO; + goto done; + } + + do { + again = false; + + /* always zero out the pwd structure */ + memset(pwd, 0, sizeof(struct passwd)); + + /* get entry */ + status = ctx->ops.getpwent_r(pwd, buffer, buflen, &ret); + + switch (status) { + case NSS_STATUS_TRYAGAIN: + /* buffer too small? */ + if (buflen < MAX_BUF_SIZE) { + buflen *= 2; + } + if (buflen > MAX_BUF_SIZE) { + buflen = MAX_BUF_SIZE; + } + newbuf = talloc_realloc_size(tmpctx, buffer, buflen); + if (!newbuf) { + ret = ENOMEM; + goto done; + } + buffer = newbuf; + again = true; + break; + + case NSS_STATUS_NOTFOUND: + + /* we are done here */ + DEBUG(SSSDBG_TRACE_LIBS, "Enumeration completed.\n"); + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(SSSDBG_TRACE_LIBS, + "User found (%s, %"SPRIuid", %"SPRIgid")\n", + pwd->pw_name, pwd->pw_uid, pwd->pw_gid); + + /* uid=0 or gid=0 are invalid values */ + /* also check that the id is in the valid range for this domain + */ + if (OUT_OF_ID_RANGE(pwd->pw_uid, dom->id_min, dom->id_max) || + OUT_OF_ID_RANGE(pwd->pw_gid, dom->id_min, dom->id_max)) { + + DEBUG(SSSDBG_OP_FAILURE, "User [%s] filtered out! (id out" + " of range)\n", pwd->pw_name); + + again = true; + break; + } + + name = sss_create_internal_fqname(tmpctx, pwd->pw_name, dom->name); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "failed to create internal name '%s'\n", + pwd->pw_name); + goto done; + } + ret = save_user(dom, pwd, name, NULL); + if (ret) { + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + DEBUG(SSSDBG_OP_FAILURE, "Failed to store user %s." + " Ignoring.\n", pwd->pw_name); + } + again = true; + break; + + case NSS_STATUS_UNAVAIL: + /* "remote" backend unavailable. Enter offline mode */ + ret = ENXIO; + break; + + default: + ret = EIO; + DEBUG(SSSDBG_OP_FAILURE, "proxy -> getpwent_r failed (%d)[%s]" + "\n", ret, strerror(ret)); + break; + } + } while (again); + +done: + talloc_zfree(tmpctx); + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + ctx->ops.endpwent(); + return ret; +} + +/* =Save-group-utilities=================================================*/ +#define DEBUG_GR_MEM(level, grp) \ + do { \ + if (!grp->gr_mem || !grp->gr_mem[0]) { \ + DEBUG(level, "Group %s has no members!\n", \ + grp->gr_name); \ + } else { \ + int i = 0; \ + while (grp->gr_mem[i]) { \ + /* count */ \ + i++; \ + } \ + DEBUG(level, "Group %s has %d members!\n", \ + grp->gr_name, i); \ + } \ + } while(0) + + +static errno_t remove_duplicate_group_members(TALLOC_CTX *mem_ctx, + const struct group *orig_grp, + struct group **_grp) +{ + TALLOC_CTX *tmp_ctx; + hash_table_t *member_tbl = NULL; + struct hash_iter_context_t *iter; + hash_entry_t *entry; + hash_key_t key; + hash_value_t value; + struct group *grp; + size_t orig_member_count= 0; + size_t member_count= 0; + size_t i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); + return ENOMEM; + } + + grp = talloc(tmp_ctx, struct group); + if (grp == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto done; + } + + grp->gr_gid = orig_grp->gr_gid; + + grp->gr_name = talloc_strdup(grp, orig_grp->gr_name); + if (grp->gr_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + grp->gr_passwd = talloc_strdup(grp, orig_grp->gr_passwd); + if (grp->gr_passwd == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + if (orig_grp->gr_mem == NULL) { + grp->gr_mem = NULL; + ret = EOK; + goto done; + } + + for (i=0; orig_grp->gr_mem[i] != NULL; ++i) /* no-op: just counting */; + + orig_member_count = i; + + if (orig_member_count == 0) { + grp->gr_mem = talloc_zero_array(grp, char *, 1); + if (grp->gr_mem == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + grp->gr_mem[0] = NULL; + ret = EOK; + goto done; + } + + ret = sss_hash_create(tmp_ctx, orig_member_count, &member_tbl); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create hash table.\n"); + ret = ENOMEM; + goto done; + } + + for (i=0; i < orig_member_count; ++i) { + key.type = HASH_KEY_STRING; + key.str = orig_grp->gr_mem[i]; /* hash_enter() makes copy itself */ + + value.type = HASH_VALUE_PTR; + /* no need to put copy in hash_table since + copy will be created during construction of new grp */ + value.ptr = orig_grp->gr_mem[i]; + + ret = hash_enter(member_tbl, &key, &value); + if (ret != HASH_SUCCESS) { + ret = ENOMEM; + goto done; + } + } + + member_count = hash_count(member_tbl); + if (member_count == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Empty resulting hash table - must be internal bug.\n"); + ret = EINVAL; + goto done; + } + + grp->gr_mem = talloc_zero_array(grp, char *, member_count + 1); + if (grp->gr_mem == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + iter = new_hash_iter_context(member_tbl); + if (iter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "new_hash_iter_context failed.\n"); + ret = EINVAL; + goto done; + } + + i = 0; + while ((entry = iter->next(iter)) != NULL) { + grp->gr_mem[i] = talloc_strdup(grp, entry->key.str); + if (grp->gr_mem[i] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + i++; + } + grp->gr_mem[i] = NULL; + + ret = EOK; + +done: + if (ret == EOK) { + *_grp = talloc_steal(mem_ctx, grp); + } + talloc_zfree(tmp_ctx); + + return ret; +} + +static errno_t proxy_process_missing_users(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sysdb_attrs *group_attrs, + const char *const*fq_gr_mem, + time_t now); +static int save_group(struct sysdb_ctx *sysdb, struct sss_domain_info *dom, + const struct group *grp, + const char *real_name, /* already qualified */ + const char *alias) /* already qualified */ +{ + errno_t ret, sret; + struct group *ngroup = NULL; + struct sysdb_attrs *attrs = NULL; + TALLOC_CTX *tmp_ctx; + time_t now = time(NULL); + bool in_transaction = false; + char **fq_gr_mem; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = remove_duplicate_group_members(tmp_ctx, grp, &ngroup); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to remove duplicate group members\n"); + goto done; + } + + DEBUG_GR_MEM(SSSDBG_TRACE_LIBS, ngroup); + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + if (ngroup->gr_mem && ngroup->gr_mem[0]) { + attrs = sysdb_new_attrs(tmp_ctx); + if (!attrs) { + DEBUG(SSSDBG_CRIT_FAILURE, "Allocation error?!\n"); + ret = ENOMEM; + goto done; + } + + fq_gr_mem = sss_create_internal_fqname_list( + tmp_ctx, + (const char *const*) ngroup->gr_mem, + dom->name); + if (fq_gr_mem == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_users_from_str_list( + attrs, SYSDB_MEMBER, dom->name, + (const char *const *) fq_gr_mem); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add group members\n"); + goto done; + } + + /* Create ghost users */ + ret = proxy_process_missing_users(sysdb, dom, attrs, + (const char *const*) fq_gr_mem, now); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add missing members\n"); + goto done; + } + } + + ret = prepare_attrs_for_saving_ops(tmp_ctx, dom->case_sensitive, + real_name, alias, &attrs); + if (ret != EOK) { + goto done; + } + + ret = sysdb_store_group(dom, + real_name, + ngroup->gr_gid, + attrs, + dom->group_timeout, + now); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add group to cache\n"); + goto done; + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not commit transaction: [%s]\n", + strerror(ret)); + goto done; + } + in_transaction = false; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t proxy_process_missing_users(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sysdb_attrs *group_attrs, + const char *const*fq_gr_mem, + time_t now) +{ + errno_t ret; + size_t i; + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_message *msg; + + if (!sysdb || !fq_gr_mem) return EINVAL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + for (i = 0; fq_gr_mem[i]; i++) { + ret = sysdb_search_user_by_name(tmp_ctx, domain, fq_gr_mem[i], + NULL, &msg); + if (ret == EOK) { + /* Member already exists in the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Member [%s] already cached\n", fq_gr_mem[i]); + /* clean up */ + talloc_zfree(msg); + continue; + } else if (ret == ENOENT) { + /* No entry for this user. Create a ghost user */ + DEBUG(SSSDBG_TRACE_LIBS, + "Member [%s] not cached, creating ghost user entry\n", + fq_gr_mem[i]); + + ret = sysdb_attrs_add_string(group_attrs, SYSDB_GHOST, fq_gr_mem[i]); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot store ghost user entry: [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } else { + /* Unexpected error */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Error searching cache for user [%s]: [%s]\n", + fq_gr_mem[i], strerror(ret)); + goto done; + } + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +/* =Getgrnam-wrapper======================================================*/ +static char * +grow_group_buffer(TALLOC_CTX *mem_ctx, + char **buffer, size_t *buflen) +{ + char *newbuf; + + if (*buflen == 0) { + *buflen = DEFAULT_BUFSIZE; + } + if (*buflen < MAX_BUF_SIZE) { + *buflen *= 2; + } + if (*buflen > MAX_BUF_SIZE) { + *buflen = MAX_BUF_SIZE; + } + + newbuf = talloc_realloc_size(mem_ctx, *buffer, *buflen); + if (!newbuf) { + return NULL; + } + *buffer = newbuf; + + return *buffer; +} + +static errno_t +handle_getgr_result(enum nss_status status, struct group *grp, + struct sss_domain_info *dom, + bool *delete_group) +{ + if (delete_group) { + *delete_group = false; + } + + switch (status) { + case NSS_STATUS_TRYAGAIN: + DEBUG(SSSDBG_MINOR_FAILURE, "Buffer too small\n"); + return EAGAIN; + + case NSS_STATUS_NOTFOUND: + DEBUG(SSSDBG_MINOR_FAILURE, "Group not found.\n"); + if (delete_group) { + *delete_group = true; + } + break; + + case NSS_STATUS_SUCCESS: + DEBUG(SSSDBG_FUNC_DATA, "Group found: (%s, %"SPRIgid")\n", + grp->gr_name, grp->gr_gid); + + /* gid=0 is an invalid value */ + /* also check that the id is in the valid range for this domain */ + if (OUT_OF_ID_RANGE(grp->gr_gid, dom->id_min, dom->id_max)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Group filtered out! (id out of range)\n"); + if (delete_group) { + *delete_group = true; + } + break; + } + break; + + case NSS_STATUS_UNAVAIL: + DEBUG(SSSDBG_MINOR_FAILURE, + "Remote back end is not available. Entering offline mode\n"); + return ENXIO; + + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown return code %d\n", status); + return EIO; + } + + return EOK; +} + +static int get_gr_name(struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + const char *i_name) +{ + TALLOC_CTX *tmpctx; + struct group *grp; + enum nss_status status; + char *buffer = 0; + size_t buflen = 0; + bool delete_group = false; + int ret; + gid_t gid; + struct ldb_result *cached_grp = NULL; + const char *real_name = NULL; + char *shortname_or_alias; + + DEBUG(SSSDBG_FUNC_DATA, "Searching group by name (%s)\n", i_name); + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmpctx, i_name, &shortname_or_alias, NULL); + if (ret != EOK) { + goto done; + } + + grp = talloc(tmpctx, struct group); + if (!grp) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "talloc() failed\n"); + goto done; + } + + do { + /* always zero out the grp structure */ + memset(grp, 0, sizeof(struct group)); + buffer = grow_group_buffer(tmpctx, &buffer, &buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getgrnam_r(shortname_or_alias, grp, buffer, + buflen, &ret); + ret = handle_getgr_result(status, grp, dom, &delete_group); + } while (ret == EAGAIN); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "getgrnam failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + if (delete_group) { + DEBUG(SSSDBG_TRACE_FUNC, + "Group %s does not exist (or is invalid) on remote server," + " deleting!\n", i_name); + + ret = sysdb_delete_group(dom, i_name, 0); + if (ret == ENOENT) { + ret = EOK; + } + goto done; + } + + gid = grp->gr_gid; + + /* Canonicalize the group name in case it was actually an alias */ + if (ctx->fast_alias == true) { + ret = sysdb_getgrgid(tmpctx, dom, gid, &cached_grp); + if (ret != EOK) { + /* Non-fatal, attempt to canonicalize online */ + DEBUG(SSSDBG_TRACE_FUNC, "Request to cache failed [%d]: %s\n", + ret, strerror(ret)); + } + + if (ret == EOK && cached_grp->count == 1) { + real_name = ldb_msg_find_attr_as_string(cached_grp->msgs[0], + SYSDB_NAME, NULL); + if (!real_name) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cached group has no name?\n"); + } + } + } + + if (real_name == NULL) { + talloc_zfree(buffer); + buflen = 0; + + do { + memset(grp, 0, sizeof(struct group)); + buffer = grow_group_buffer(tmpctx, &buffer, &buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getgrgid_r(gid, grp, buffer, buflen, &ret); + + ret = handle_getgr_result(status, grp, dom, &delete_group); + } while (ret == EAGAIN); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "getgrgid failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + real_name = sss_create_internal_fqname(tmpctx, grp->gr_name, dom->name); + if (real_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create fqdn '%s'\n", + grp->gr_name); + ret = ENOMEM; + goto done; + } + } + + if (delete_group) { + DEBUG(SSSDBG_TRACE_FUNC, + "Group %s does not exist (or is invalid) on remote server," + " deleting!\n", i_name); + + ret = sysdb_delete_group(dom, i_name, gid); + if (ret == ENOENT) { + ret = EOK; + } + goto done; + } + + ret = save_group(sysdb, dom, grp, real_name, i_name); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot save group [%d]: %s\n", ret, strerror(ret)); + goto done; + } + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy -> getgrnam_r failed for '%s' <%d>: %s\n", + i_name, ret, strerror(ret)); + } + return ret; +} + +/* =Getgrgid-wrapper======================================================*/ +static int get_gr_gid(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + gid_t gid, + time_t now) +{ + TALLOC_CTX *tmpctx; + struct group *grp; + enum nss_status status; + char *buffer = NULL; + size_t buflen = 0; + bool delete_group = false; + int ret; + char *name; + + DEBUG(SSSDBG_TRACE_FUNC, "Searching group by gid (%"SPRIgid")\n", gid); + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + grp = talloc(tmpctx, struct group); + if (!grp) { + ret = ENOMEM; + goto done; + } + + do { + /* always zero out the grp structure */ + memset(grp, 0, sizeof(struct group)); + buffer = grow_group_buffer(tmpctx, &buffer, &buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getgrgid_r(gid, grp, buffer, buflen, &ret); + + ret = handle_getgr_result(status, grp, dom, &delete_group); + } while (ret == EAGAIN); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "getgrgid failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + if (delete_group) { + DEBUG(SSSDBG_TRACE_FUNC, + "Group %"SPRIgid" does not exist (or is invalid) on remote " + "server, deleting!\n", gid); + + ret = sysdb_delete_group(dom, NULL, gid); + if (ret == ENOENT) { + ret = EOK; + } + goto done; + } + + name = sss_create_internal_fqname(tmpctx, grp->gr_name, dom->name); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + ret = save_group(sysdb, dom, grp, name, NULL); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot save user [%d]: %s\n", ret, strerror(ret)); + goto done; + } + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy -> getgrgid_r failed for '%"SPRIgid"' <%d>: %s\n", + gid, ret, strerror(ret)); + } + return ret; +} + +/* =Getgrent-wrapper======================================================*/ + +static int enum_groups(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom) +{ + TALLOC_CTX *tmpctx; + bool in_transaction = false; + struct group *grp; + enum nss_status status; + size_t buflen; + char *buffer; + char *newbuf; + int ret; + errno_t sret; + bool again; + char *name; + + DEBUG(SSSDBG_TRACE_LIBS, "Enumerating groups\n"); + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + grp = talloc(tmpctx, struct group); + if (!grp) { + ret = ENOMEM; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + status = ctx->ops.setgrent(); + if (status != NSS_STATUS_SUCCESS) { + ret = EIO; + goto done; + } + + do { + again = false; + + /* always zero out the grp structure */ + memset(grp, 0, sizeof(struct group)); + + /* get entry */ + status = ctx->ops.getgrent_r(grp, buffer, buflen, &ret); + + switch (status) { + case NSS_STATUS_TRYAGAIN: + /* buffer too small? */ + if (buflen < MAX_BUF_SIZE) { + buflen *= 2; + } + if (buflen > MAX_BUF_SIZE) { + buflen = MAX_BUF_SIZE; + } + newbuf = talloc_realloc_size(tmpctx, buffer, buflen); + if (!newbuf) { + ret = ENOMEM; + goto done; + } + buffer = newbuf; + again = true; + break; + + case NSS_STATUS_NOTFOUND: + + /* we are done here */ + DEBUG(SSSDBG_TRACE_LIBS, "Enumeration completed.\n"); + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(SSSDBG_OP_FAILURE, "Group found (%s, %"SPRIgid")\n", + grp->gr_name, grp->gr_gid); + + /* gid=0 is an invalid value */ + /* also check that the id is in the valid range for this domain + */ + if (OUT_OF_ID_RANGE(grp->gr_gid, dom->id_min, dom->id_max)) { + + DEBUG(SSSDBG_OP_FAILURE, "Group [%s] filtered out! (id" + "out of range)\n", grp->gr_name); + + again = true; + break; + } + + name = sss_create_internal_fqname(tmpctx, grp->gr_name, + dom->name); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create internal fqname " + "Ignoring\n"); + ret = ENOMEM; + } + ret = save_group(sysdb, dom, grp, name, NULL); + if (ret) { + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + DEBUG(SSSDBG_OP_FAILURE, "Failed to store group." + "Ignoring\n"); + } + again = true; + break; + + case NSS_STATUS_UNAVAIL: + /* "remote" backend unavailable. Enter offline mode */ + ret = ENXIO; + break; + + default: + ret = EIO; + DEBUG(SSSDBG_OP_FAILURE, "proxy -> getgrent_r failed (%d)[%s]" + "\n", ret, strerror(ret)); + break; + } + } while (again); + +done: + talloc_zfree(tmpctx); + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + ctx->ops.endgrent(); + return ret; +} + + +/* =Initgroups-wrapper====================================================*/ + +static int get_initgr_groups_process(TALLOC_CTX *memctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct passwd *pwd); + +static int get_initgr(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + const char *i_name) +{ + TALLOC_CTX *tmpctx; + bool in_transaction = false; + struct passwd *pwd; + enum nss_status status; + char *buffer; + size_t buflen; + int ret; + errno_t sret; + bool del_user; + uid_t uid; + struct ldb_result *cached_pwd = NULL; + const char *real_name = NULL; + char *shortname_or_alias; + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmpctx, i_name, &shortname_or_alias, NULL); + if (ret != EOK) { + goto done; + } + + pwd = talloc_zero(tmpctx, struct passwd); + if (!pwd) { + ret = ENOMEM; + goto fail; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto fail; + } + in_transaction = true; + + /* FIXME: should we move this call outside the transaction to keep the + * transaction as short as possible? */ + status = ctx->ops.getpwnam_r(shortname_or_alias, pwd, + buffer, buflen, &ret); + ret = handle_getpw_result(status, pwd, dom, &del_user); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "getpwnam failed [%d]: %s\n", ret, strerror(ret)); + goto fail; + } + + if (del_user) { + ret = delete_user(dom, i_name, 0); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not delete user\n"); + goto fail; + } + goto done; + } + + uid = pwd->pw_uid; + + /* Canonicalize the username in case it was actually an alias */ + if (ctx->fast_alias == true) { + ret = sysdb_getpwuid(tmpctx, dom, uid, &cached_pwd); + if (ret != EOK) { + /* Non-fatal, attempt to canonicalize online */ + DEBUG(SSSDBG_TRACE_FUNC, "Request to cache failed [%d]: %s\n", + ret, strerror(ret)); + } + + if (ret == EOK && cached_pwd->count == 1) { + real_name = ldb_msg_find_attr_as_string(cached_pwd->msgs[0], + SYSDB_NAME, NULL); + if (!real_name) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cached user has no name?\n"); + } + } + } + + if (real_name == NULL) { + memset(buffer, 0, buflen); + + status = ctx->ops.getpwuid_r(uid, pwd, buffer, buflen, &ret); + ret = handle_getpw_result(status, pwd, dom, &del_user); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "getpwuid failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + real_name = sss_create_internal_fqname(tmpctx, pwd->pw_name, dom->name); + if (real_name == NULL) { + ret = ENOMEM; + goto done; + } + } + + if (del_user) { + ret = delete_user(dom, i_name, uid); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not delete user\n"); + goto fail; + } + goto done; + } + + ret = save_user(dom, pwd, real_name, i_name); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not save user\n"); + goto fail; + } + + ret = get_initgr_groups_process(tmpctx, ctx, sysdb, dom, pwd); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not process initgroups\n"); + goto fail; + } + +done: + ret = sysdb_transaction_commit(sysdb); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to commit transaction\n"); + goto fail; + } + in_transaction = false; + +fail: + talloc_zfree(tmpctx); + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + return ret; +} + +static int remove_group_members(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const struct passwd *pwd, + long int num_gids, + const gid_t *gids, + long int num_cached_gids, + const gid_t *cached_gids) +{ + TALLOC_CTX *tmp_ctx = NULL; + int i = 0, j = 0; + int ret = EOK; + const char *groupname = NULL; + const char *username = NULL; + bool group_found = false; + struct ldb_result *res = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + username = sss_create_internal_fqname(tmp_ctx, pwd->pw_name, dom->name); + if (username == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create fqdn '%s'\n", pwd->pw_name); + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_cached_gids; i++) { + group_found = false; + /* group 0 is the primary group so it can be skipped */ + for (j = 1; j < num_gids; j++) { + if (cached_gids[i] == gids[j]) { + group_found = true; + break; + } + } + + if (!group_found) { + ret = sysdb_getgrgid(tmp_ctx, dom, cached_gids[i], &res); + if (ret != EOK || res->count != 1) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_getgrgid failed for GID [%d].\n", cached_gids[i]); + continue; + } + + groupname = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_NAME, NULL); + if (groupname == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Attribute is missing but this should never happen!\n"); + continue; + } + + ret = sysdb_remove_group_member(dom, groupname, + username, + SYSDB_MEMBER_USER, false); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not remove member [%s] from group [%s]\n", + username, groupname); + continue; + } + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int get_cached_user_groups(struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + const struct passwd *pwd, + unsigned int *_num_cached_gids, + gid_t **_cached_gids) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret = EOK; + int i = 0, j = 0; + gid_t gid = 0; + gid_t *cached_gids = NULL; + const char *username = NULL; + struct ldb_result *res = NULL; + + if (_num_cached_gids == NULL || _cached_gids == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + goto done; + } + + username = sss_create_internal_fqname(tmp_ctx, pwd->pw_name, dom->name); + if (username == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create fqdn '%s'\n", pwd->pw_name); + ret = ENOMEM; + goto done; + } + + ret = sysdb_initgroups(tmp_ctx, dom, username, &res); + /* the first element is the user itself so it can be skipped */ + if (ret == EOK && res->count > 1) { + cached_gids = talloc_array(tmp_ctx, gid_t, res->count - 1); + + for (i = 1; i < res->count; i++) { + gid = ldb_msg_find_attr_as_uint(res->msgs[i], SYSDB_GIDNUM, 0); + if (gid != 0) { + cached_gids[j] = gid; + j++; + } + } + + *_num_cached_gids = j; + *_cached_gids = talloc_steal(sysdb, cached_gids); + } else if (ret == EOK) { + *_num_cached_gids = 0; + *_cached_gids = NULL; + } else { + goto done; + } + + ret = EOK; + +done: + talloc_zfree(tmp_ctx); + + return ret; +} + +static int get_initgr_groups_process(TALLOC_CTX *memctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct passwd *pwd) +{ + enum nss_status status; + long int limit; + long int size; + long int num; + long int num_gids; + gid_t *gids; + int ret; + int i; + time_t now; + gid_t *cached_gids = NULL; + unsigned int num_cached_gids = 0; + + num_gids = 0; + limit = 4096; + num = 4096; + size = num*sizeof(gid_t); + gids = talloc_size(memctx, size); + if (!gids) { + return ENOMEM; + } + + /* nss modules may skip the primary group when we pass it in so always add + * it in advance */ + gids[0] = pwd->pw_gid; + num_gids++; + + /* FIXME: should we move this call outside the transaction to keep the + * transaction as short as possible? */ + do { + status = ctx->ops.initgroups_dyn(pwd->pw_name, pwd->pw_gid, &num_gids, + &num, &gids, limit, &ret); + + if (status == NSS_STATUS_TRYAGAIN) { + /* buffer too small? */ + if (size < MAX_BUF_SIZE) { + num *= 2; + size = num*sizeof(gid_t); + } + if (size > MAX_BUF_SIZE) { + size = MAX_BUF_SIZE; + num = size/sizeof(gid_t); + } + limit = num; + gids = talloc_realloc_size(memctx, gids, size); + if (!gids) { + return ENOMEM; + } + } + } while(status == NSS_STATUS_TRYAGAIN); + + switch (status) { + case NSS_STATUS_NOTFOUND: + DEBUG(SSSDBG_FUNC_DATA, "The initgroups call returned 'NOTFOUND'. " + "Assume the user is only member of its " + "primary group (%"SPRIgid")\n", pwd->pw_gid); + /* fall through */ + SSS_ATTRIBUTE_FALLTHROUGH; + case NSS_STATUS_SUCCESS: + DEBUG(SSSDBG_CONF_SETTINGS, "User [%s] appears to be member of %lu " + "groups\n", pwd->pw_name, num_gids); + + ret = get_cached_user_groups(sysdb, dom, pwd, &num_cached_gids, &cached_gids); + if (ret) { + return ret; + } + ret = remove_group_members(ctx, dom, pwd, num_gids, gids, num_cached_gids, cached_gids); + talloc_free(cached_gids); + if (ret) { + return ret; + } + + now = time(NULL); + for (i = 0; i < num_gids; i++) { + ret = get_gr_gid(memctx, ctx, sysdb, dom, gids[i], now); + if (ret) { + return ret; + } + } + ret = EOK; + + break; + + default: + DEBUG(SSSDBG_OP_FAILURE, "proxy -> initgroups_dyn failed (%d)[%s]\n", + ret, strerror(ret)); + ret = EIO; + break; + } + + return ret; +} + +/* =Proxy_Id-Functions====================================================*/ + +static struct dp_reply_std +proxy_account_info(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *ctx, + struct dp_id_data *data, + struct be_ctx *be_ctx, + struct sss_domain_info *domain) +{ + struct dp_reply_std reply; + struct sysdb_ctx *sysdb; + uid_t uid; + gid_t gid; + errno_t ret; + char *endptr; + + sysdb = domain->sysdb; + + /* Proxy provider does not support security ID lookups. */ + if (data->filter_type == BE_FILTER_SECID) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENOSYS, + "Security lookups are not supported"); + return reply; + } + + switch (data->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: /* user */ + switch (data->filter_type) { + case BE_FILTER_ENUM: + ret = enum_users(mem_ctx, ctx, sysdb, domain); + break; + + case BE_FILTER_NAME: + ret = get_pw_name(ctx, domain, data->filter_value); + break; + + case BE_FILTER_IDNUM: + uid = (uid_t) strtouint32(data->filter_value, &endptr, 10); + if (errno || *endptr || (data->filter_value == endptr)) { + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid attr type"); + return reply; + } + ret = get_pw_uid(ctx, domain, uid); + break; + default: + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + break; + + case BE_REQ_GROUP: /* group */ + switch (data->filter_type) { + case BE_FILTER_ENUM: + ret = enum_groups(mem_ctx, ctx, sysdb, domain); + break; + case BE_FILTER_NAME: + ret = get_gr_name(ctx, sysdb, domain, data->filter_value); + break; + case BE_FILTER_IDNUM: + gid = (gid_t) strtouint32(data->filter_value, &endptr, 10); + if (errno || *endptr || (data->filter_value == endptr)) { + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid attr type"); + return reply; + } + ret = get_gr_gid(mem_ctx, ctx, sysdb, domain, gid, 0); + break; + default: + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + break; + + case BE_REQ_INITGROUPS: /* init groups for user */ + if (data->filter_type != BE_FILTER_NAME) { + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + if (ctx->ops.initgroups_dyn == NULL) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENODEV, + "Initgroups call not supported"); + return reply; + } + ret = get_initgr(mem_ctx, ctx, sysdb, domain, data->filter_value); + break; + + case BE_REQ_NETGROUP: + if (data->filter_type != BE_FILTER_NAME) { + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + if (ctx->ops.setnetgrent == NULL || ctx->ops.getnetgrent_r == NULL || + ctx->ops.endnetgrent == NULL) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENODEV, + "Netgroups are not supported"); + return reply; + } + + ret = get_netgroup(ctx, domain, data->filter_value); + break; + + case BE_REQ_SERVICES: + switch (data->filter_type) { + case BE_FILTER_NAME: + if (ctx->ops.getservbyname_r == NULL) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENODEV, + "Services are not supported"); + return reply; + } + ret = get_serv_byname(ctx, domain, + data->filter_value, + data->extra_value); + break; + case BE_FILTER_IDNUM: + if (ctx->ops.getservbyport_r == NULL) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENODEV, + "Services are not supported"); + return reply; + } + ret = get_serv_byport(ctx, domain, + data->filter_value, + data->extra_value); + break; + case BE_FILTER_ENUM: + if (!ctx->ops.setservent + || !ctx->ops.getservent_r + || !ctx->ops.endservent) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENODEV, + "Services are not supported"); + return reply; + } + ret = enum_services(ctx, sysdb, domain); + break; + default: + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + break; + + case BE_REQ_BY_CERT: + if (data->filter_type != BE_FILTER_CERT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected filter type for lookup by cert: %d\n", + data->filter_type); + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Unexpected filter type for lookup by cert"); + return reply; + } + + if (ctx->sss_certmap_ctx == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Certificate mapping not configured.\n"); + ret = EOK; + break; + } + + ret = proxy_map_cert_to_user(ctx, data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "proxy_map_cert_to_user failed\n"); + } + break; + + default: /*fail*/ + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + + if (ret) { + if (ret == ENXIO) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy returned UNAVAIL error, going offline!\n"); + be_mark_offline(be_ctx); + } + + dp_reply_std_set(&reply, DP_ERR_FATAL, ret, NULL); + return reply; + } + + dp_reply_std_set(&reply, DP_ERR_OK, EOK, NULL); + return reply; +} + +struct proxy_account_info_handler_state { + struct dp_reply_std reply; +}; + +struct tevent_req * +proxy_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params) +{ + struct proxy_account_info_handler_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct proxy_account_info_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->reply = proxy_account_info(state, id_ctx, data, params->be_ctx, + params->be_ctx->domain); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +errno_t proxy_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct proxy_account_info_handler_state *state = NULL; + + state = tevent_req_data(req, struct proxy_account_info_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} |