From 10afbe39cb81a1810dba486c4b8e46578bb300bb Mon Sep 17 00:00:00 2001 From: Stephen Gallagher Date: Wed, 26 May 2010 14:42:36 -0400 Subject: Proxy provider PAM handling in child process This patch adds a new tevent_req to the proxy provider, which will spawn short-lived child processes to handle PAM requests. These processes then call the proxied PAM stack and return the results via SBUS method reply. Once it is returned, the parent process kills the child. There is a maximum of ten child processes running simultaneously, after which requests will be queued for sending once a child slot frees up. The maximum processes will be made configurable at a later date (as this would violate string freeze). --- src/providers/proxy.c | 1650 ------------------------ src/providers/proxy/proxy.c | 2502 +++++++++++++++++++++++++++++++++++++ src/providers/proxy/proxy.h | 30 + src/providers/proxy/proxy_child.c | 507 ++++++++ 4 files changed, 3039 insertions(+), 1650 deletions(-) delete mode 100644 src/providers/proxy.c create mode 100644 src/providers/proxy/proxy.c create mode 100644 src/providers/proxy/proxy.h create mode 100644 src/providers/proxy/proxy_child.c (limited to 'src/providers') diff --git a/src/providers/proxy.c b/src/providers/proxy.c deleted file mode 100644 index ac5cf7fe..00000000 --- a/src/providers/proxy.c +++ /dev/null @@ -1,1650 +0,0 @@ -/* - SSSD - - Proxy Module - - Copyright (C) Simo Sorce 2008-2009 - - 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 . -*/ - -#include -#include -#include -#include -#include - -#include -#include - -#include "util/util.h" -#include "providers/dp_backend.h" -#include "db/sysdb.h" - -struct proxy_nss_ops { - enum nss_status (*getpwnam_r)(const char *name, struct passwd *result, - char *buffer, size_t buflen, int *errnop); - enum nss_status (*getpwuid_r)(uid_t uid, struct passwd *result, - char *buffer, size_t buflen, int *errnop); - enum nss_status (*setpwent)(void); - enum nss_status (*getpwent_r)(struct passwd *result, - char *buffer, size_t buflen, int *errnop); - enum nss_status (*endpwent)(void); - - enum nss_status (*getgrnam_r)(const char *name, struct group *result, - char *buffer, size_t buflen, int *errnop); - enum nss_status (*getgrgid_r)(gid_t gid, struct group *result, - char *buffer, size_t buflen, int *errnop); - enum nss_status (*setgrent)(void); - enum nss_status (*getgrent_r)(struct group *result, - char *buffer, size_t buflen, int *errnop); - enum nss_status (*endgrent)(void); - enum nss_status (*initgroups_dyn)(const char *user, gid_t group, - long int *start, long int *size, - gid_t **groups, long int limit, - int *errnop); -}; - -struct proxy_ctx { - struct be_ctx *be; - int entry_cache_timeout; - struct proxy_nss_ops ops; -}; - -struct proxy_auth_ctx { - struct be_ctx *be; - char *pam_target; -}; - -struct authtok_conv { - uint32_t authtok_size; - uint8_t *authtok; -}; - -static int proxy_internal_conv(int num_msg, const struct pam_message **msgm, - struct pam_response **response, - void *appdata_ptr) { - int i; - struct pam_response *reply; - struct authtok_conv *auth_data; - - auth_data = talloc_get_type(appdata_ptr, struct authtok_conv); - - if (num_msg <= 0) return PAM_CONV_ERR; - - reply = (struct pam_response *) calloc(num_msg, - sizeof(struct pam_response)); - if (reply == NULL) return PAM_CONV_ERR; - - for (i=0; i < num_msg; i++) { - switch( msgm[i]->msg_style ) { - case PAM_PROMPT_ECHO_OFF: - DEBUG(4, ("Conversation message: [%s]\n", msgm[i]->msg)); - reply[i].resp_retcode = 0; - reply[i].resp = calloc(auth_data->authtok_size + 1, - sizeof(char)); - if (reply[i].resp == NULL) goto failed; - memcpy(reply[i].resp, auth_data->authtok, auth_data->authtok_size); - - break; - default: - DEBUG(1, ("Conversation style %d not supported.\n", - msgm[i]->msg_style)); - goto failed; - } - } - - *response = reply; - reply = NULL; - - return PAM_SUCCESS; - -failed: - free(reply); - return PAM_CONV_ERR; -} - -static void proxy_reply(struct be_req *req, int dp_err, - int error, const char *errstr); - -static void proxy_pam_handler(struct be_req *req) { - int ret; - int pam_status; - pam_handle_t *pamh=NULL; - struct authtok_conv *auth_data; - struct pam_conv conv; - struct pam_data *pd; - struct proxy_auth_ctx *ctx;; - bool cache_auth_data = false; - - pd = talloc_get_type(req->req_data, struct pam_data); - - switch (pd->cmd) { - case SSS_PAM_AUTHENTICATE: - ctx = talloc_get_type(req->be_ctx->bet_info[BET_AUTH].pvt_bet_data, - struct proxy_auth_ctx); - break; - case SSS_PAM_CHAUTHTOK: - case SSS_PAM_CHAUTHTOK_PRELIM: - ctx = talloc_get_type(req->be_ctx->bet_info[BET_CHPASS].pvt_bet_data, - struct proxy_auth_ctx); - break; - case SSS_PAM_ACCT_MGMT: - ctx = talloc_get_type(req->be_ctx->bet_info[BET_ACCESS].pvt_bet_data, - struct proxy_auth_ctx); - break; - case SSS_PAM_SETCRED: - case SSS_PAM_OPEN_SESSION: - case SSS_PAM_CLOSE_SESSION: - pd->pam_status = PAM_SUCCESS; - proxy_reply(req, DP_ERR_OK, EOK, NULL); - return; - default: - DEBUG(1, ("Unsupported PAM task.\n")); - pd->pam_status = PAM_MODULE_UNKNOWN; - proxy_reply(req, DP_ERR_OK, EINVAL, "Unsupported PAM task"); - return; - } - - conv.conv=proxy_internal_conv; - auth_data = talloc_zero(req, struct authtok_conv); - conv.appdata_ptr=auth_data; - - ret = pam_start(ctx->pam_target, pd->user, &conv, &pamh); - if (ret == PAM_SUCCESS) { - DEBUG(1, ("Pam transaction started.\n")); - ret = pam_set_item(pamh, PAM_TTY, pd->tty); - if (ret != PAM_SUCCESS) { - DEBUG(1, ("Setting PAM_TTY failed: %s.\n", pam_strerror(pamh, ret))); - } - ret = pam_set_item(pamh, PAM_RUSER, pd->ruser); - if (ret != PAM_SUCCESS) { - DEBUG(1, ("Setting PAM_RUSER failed: %s.\n", pam_strerror(pamh, ret))); - } - ret = pam_set_item(pamh, PAM_RHOST, pd->rhost); - if (ret != PAM_SUCCESS) { - DEBUG(1, ("Setting PAM_RHOST failed: %s.\n", pam_strerror(pamh, ret))); - } - switch (pd->cmd) { - case SSS_PAM_AUTHENTICATE: - auth_data->authtok_size = pd->authtok_size; - auth_data->authtok = pd->authtok; - pam_status = pam_authenticate(pamh, 0); - if ((pam_status == PAM_SUCCESS) && - (req->be_ctx->domain->cache_credentials)) { - cache_auth_data = true; - } - break; - case SSS_PAM_SETCRED: - pam_status=pam_setcred(pamh, 0); - break; - case SSS_PAM_ACCT_MGMT: - pam_status=pam_acct_mgmt(pamh, 0); - break; - case SSS_PAM_OPEN_SESSION: - pam_status=pam_open_session(pamh, 0); - break; - case SSS_PAM_CLOSE_SESSION: - pam_status=pam_close_session(pamh, 0); - break; - case SSS_PAM_CHAUTHTOK: - if (pd->priv != 1) { - auth_data->authtok_size = pd->authtok_size; - auth_data->authtok = pd->authtok; - pam_status = pam_authenticate(pamh, 0); - if (pam_status != PAM_SUCCESS) break; - } - auth_data->authtok_size = pd->newauthtok_size; - auth_data->authtok = pd->newauthtok; - pam_status = pam_chauthtok(pamh, 0); - if ((pam_status == PAM_SUCCESS) && - (req->be_ctx->domain->cache_credentials)) { - cache_auth_data = true; - } - break; - case SSS_PAM_CHAUTHTOK_PRELIM: - if (pd->priv != 1) { - auth_data->authtok_size = pd->authtok_size; - auth_data->authtok = pd->authtok; - pam_status = pam_authenticate(pamh, 0); - } else { - pam_status = PAM_SUCCESS; - } - break; - default: - DEBUG(1, ("unknown PAM call\n")); - pam_status=PAM_ABORT; - } - - DEBUG(4, ("Pam result: [%d][%s]\n", pam_status, - pam_strerror(pamh, pam_status))); - - if (pam_status == PAM_AUTHINFO_UNAVAIL) { - be_mark_offline(req->be_ctx); - } - - ret = pam_end(pamh, pam_status); - if (ret != PAM_SUCCESS) { - pamh=NULL; - DEBUG(1, ("Cannot terminate pam transaction.\n")); - } - - } else { - DEBUG(1, ("Failed to initialize pam transaction.\n")); - pam_status = PAM_SYSTEM_ERR; - } - - pd->pam_status = pam_status; - - if (cache_auth_data) { - char *password; - - password = talloc_size(req, auth_data->authtok_size + 1); - if (!password) { - /* password caching failures are not fatal errors */ - return proxy_reply(req, DP_ERR_OK, EOK, NULL); - } - memcpy(password, auth_data->authtok, auth_data->authtok_size); - password[auth_data->authtok_size] = '\0'; - talloc_set_destructor((TALLOC_CTX *)password, password_destructor); - - ret = sysdb_cache_password(req, req->be_ctx->sysdb, - req->be_ctx->domain, - pd->user, password); - - /* password caching failures are not fatal errors */ - /* so we just log it any return */ - if (ret) { - DEBUG(2, ("Failed to cache password (%d)[%s]!?\n", - ret, strerror(ret))); - } - } - - proxy_reply(req, DP_ERR_OK, EOK, NULL); -} - -static void proxy_reply(struct be_req *req, int dp_err, - int error, const char *errstr) -{ - if (!req->be_ctx->offstat.offline) { - /* This action took place online. - * Fire any online callbacks if necessary. - * Note: we're checking the offline value directly, - * because if the activity took a long time to - * complete, calling be_is_offline() might report false - * incorrectly. - */ - be_run_online_cb(req->be_ctx); - } - return req->fn(req, dp_err, error, errstr); -} - -/* =Common-proxy-tevent_req-utils=========================================*/ - -#define DEFAULT_BUFSIZE 4096 -#define MAX_BUF_SIZE 1024*1024 /* max 1MiB */ - -/* =Getpwnam-wrapper======================================================*/ - -static int delete_user(TALLOC_CTX *mem_ctx, struct sysdb_ctx *sysdb, - struct sss_domain_info *domain, const char *name); - -static int get_pw_name(TALLOC_CTX *mem_ctx, - struct proxy_ctx *ctx, - struct sysdb_ctx *sysdb, - struct sss_domain_info *dom, - const char *name) -{ - TALLOC_CTX *tmpctx; - struct passwd *pwd; - enum nss_status status; - char *buffer; - size_t buflen; - int ret; - - DEBUG(7, ("Searching user by name (%s)\n", name)); - - 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; - } - - /* FIXME: should we move this call outside the transaction to keep the - * transaction as short as possible ? */ - status = ctx->ops.getpwnam_r(name, pwd, buffer, buflen, &ret); - - switch (status) { - case NSS_STATUS_NOTFOUND: - - DEBUG(7, ("User %s not found.\n", name)); - ret = delete_user(tmpctx, sysdb, dom, name); - if (ret) { - goto done; - } - break; - - case NSS_STATUS_SUCCESS: - - DEBUG(7, ("User %s found: (%s, %d, %d)\n", - name, 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(2, ("User [%s] filtered out! (id out of range)\n", name)); - ret = delete_user(tmpctx, sysdb, dom, name); - if (ret) { - goto done; - } - break; - } - - ret = sysdb_store_user(tmpctx, sysdb, dom, - pwd->pw_name, - pwd->pw_passwd, - pwd->pw_uid, - pwd->pw_gid, - pwd->pw_gecos, - pwd->pw_dir, - pwd->pw_shell, - NULL, ctx->entry_cache_timeout); - if (ret) { - goto done; - } - break; - - case NSS_STATUS_UNAVAIL: - /* "remote" backend unavailable. Enter offline mode */ - ret = ENXIO; - goto done; - - default: - ret = EIO; - goto done; - } - -done: - talloc_zfree(tmpctx); - if (ret) { - DEBUG(2, ("proxy -> getpwnam_r failed for '%s' <%d>\n", - name, status)); - } - return ret; -} - -static int delete_user(TALLOC_CTX *mem_ctx, struct sysdb_ctx *sysdb, - struct sss_domain_info *domain, const char *name) -{ - struct ldb_dn *dn; - - DEBUG(7, ("User %s does not exist (or is invalid) on remote server," - " deleting!\n", name)); - - dn = sysdb_user_dn(sysdb, mem_ctx, domain->name, name); - if (!dn) { - return ENOMEM; - } - - return sysdb_delete_entry(sysdb, dn, true); -} - -/* =Getpwuid-wrapper======================================================*/ - -static int get_pw_uid(TALLOC_CTX *mem_ctx, - struct proxy_ctx *ctx, - struct sysdb_ctx *sysdb, - 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; - - DEBUG(7, ("Searching user by uid (%d)\n", uid)); - - tmpctx = talloc_new(mem_ctx); - if (!tmpctx) { - return ENOMEM; - } - - pwd = talloc_zero(tmpctx, struct passwd); - if (!pwd) { - ret = ENOMEM; - DEBUG(1, ("proxy -> getpwuid_r failed for '%d': [%d] %s\n", - uid, ret, strerror(ret))); - return ret; - } - - buflen = DEFAULT_BUFSIZE; - buffer = talloc_size(tmpctx, buflen); - if (!buffer) { - ret = ENOMEM; - DEBUG(1, ("proxy -> getpwuid_r failed for '%d': [%d] %s\n", - uid, ret, strerror(ret))); - return ret; - } - - status = ctx->ops.getpwuid_r(uid, pwd, buffer, buflen, &ret); - - switch (status) { - case NSS_STATUS_NOTFOUND: - - DEBUG(7, ("User %d not found.\n", uid)); - del_user = true; - break; - - case NSS_STATUS_SUCCESS: - - DEBUG(7, ("User %d found (%s, %d, %d)\n", - uid, 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(2, ("User [%s] filtered out! (id out of range)\n", - pwd->pw_name)); - del_user = true; - break; - } - - ret = sysdb_store_user(tmpctx, sysdb, dom, - pwd->pw_name, - pwd->pw_passwd, - pwd->pw_uid, - pwd->pw_gid, - pwd->pw_gecos, - pwd->pw_dir, - pwd->pw_shell, - NULL, ctx->entry_cache_timeout); - if (ret) { - goto done; - } - break; - - case NSS_STATUS_UNAVAIL: - /* "remote" backend unavailable. Enter offline mode */ - ret = ENXIO; - goto done; - - default: - ret = EIO; - goto done; - } - - if (del_user) { - DEBUG(7, ("User %d does not exist (or is invalid) on remote server," - " deleting!\n", uid)); - - ret = sysdb_delete_user(tmpctx, sysdb, dom, NULL, uid); - if (ret) { - goto done; - } - } - -done: - talloc_zfree(tmpctx); - if (ret) { - DEBUG(2, ("proxy -> getpwuid_r failed for '%d' <%d>\n", uid, status)); - } - return ret; -} - -/* =Getpwent-wrapper======================================================*/ - -static int enum_users(TALLOC_CTX *mem_ctx, - struct proxy_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; - - DEBUG(7, ("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) { - goto done; - } - in_transaction = true; - - status = ctx->ops.setpwent(); - if (status != NSS_STATUS_SUCCESS) { - ret = EIO; - goto done; - } - -again: - /* 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; - goto again; - - case NSS_STATUS_NOTFOUND: - - /* we are done here */ - DEBUG(7, ("Enumeration completed.\n")); - - ret = sysdb_transaction_commit(sysdb); - in_transaction = false; - break; - - case NSS_STATUS_SUCCESS: - - DEBUG(7, ("User found (%s, %d, %d)\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(2, ("User [%s] filtered out! (id out of range)\n", - pwd->pw_name)); - - goto again; /* skip */ - } - - ret = sysdb_store_user(tmpctx, sysdb, dom, - pwd->pw_name, - pwd->pw_passwd, - pwd->pw_uid, - pwd->pw_gid, - pwd->pw_gecos, - pwd->pw_dir, - pwd->pw_shell, - NULL, ctx->entry_cache_timeout); - if (ret) { - /* Do not fail completely on errors. - * Just report the failure to save and go on */ - DEBUG(2, ("Failed to store user %s. Ignoring.\n", - pwd->pw_name)); - } - goto again; /* next */ - - case NSS_STATUS_UNAVAIL: - /* "remote" backend unavailable. Enter offline mode */ - ret = ENXIO; - break; - - default: - DEBUG(2, ("proxy -> getpwent_r failed (%d)[%s]\n", - ret, strerror(ret))); - break; - } - -done: - talloc_zfree(tmpctx); - if (in_transaction) { - sysdb_transaction_cancel(sysdb); - } - ctx->ops.endpwent(); - return ret; -} - -/* =Getgrnam-wrapper======================================================*/ - -#define DEBUG_GR_MEM(level, grp) \ - do { \ - if (debug_level >= level) { \ - 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 int get_gr_name(TALLOC_CTX *mem_ctx, - struct proxy_ctx *ctx, - struct sysdb_ctx *sysdb, - struct sss_domain_info *dom, - const char *name) -{ - TALLOC_CTX *tmpctx; - struct group *grp; - enum nss_status status; - char *buffer; - char *newbuf; - size_t buflen; - bool delete_group = false; - struct sysdb_attrs *members; - int ret; - - DEBUG(7, ("Searching group by name (%s)\n", name)); - - tmpctx = talloc_new(mem_ctx); - if (!tmpctx) { - return ENOMEM; - } - - grp = talloc(tmpctx, struct group); - if (!grp) { - ret = ENOMEM; - DEBUG(1, ("proxy -> getgrnam_r failed for '%s': [%d] %s\n", - name, ret, strerror(ret))); - return ret; - } - - buflen = DEFAULT_BUFSIZE; - buffer = talloc_size(tmpctx, buflen); - if (!buffer) { - ret = ENOMEM; - DEBUG(1, ("proxy -> getgrnam_r failed for '%s': [%d] %s\n", - name, ret, strerror(ret))); - return ret; - } - - /* FIXME: should we move this call outside the transaction to keep the - * transaction as short as possible ? */ -again: - /* always zero out the grp structure */ - memset(grp, 0, sizeof(struct group)); - - status = ctx->ops.getgrnam_r(name, 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; - goto again; - - case NSS_STATUS_NOTFOUND: - - DEBUG(7, ("Group %s not found.\n", name)); - delete_group = true; - break; - - case NSS_STATUS_SUCCESS: - - DEBUG(7, ("Group %s found: (%s, %d)\n", - name, 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(2, ("Group [%s] filtered out! (id out of range)\n", - name)); - delete_group = true; - break; - } - - DEBUG_GR_MEM(7, grp); - - if (grp->gr_mem && grp->gr_mem[0]) { - members = sysdb_new_attrs(tmpctx); - if (!members) { - ret = ENOMEM; - goto done; - } - ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER, - dom->name, - (const char **)grp->gr_mem); - if (ret) { - goto done; - } - } else { - members = NULL; - } - - ret = sysdb_store_group(tmpctx, sysdb, dom, - grp->gr_name, - grp->gr_gid, - members, - ctx->entry_cache_timeout); - if (ret) { - goto done; - } - break; - - case NSS_STATUS_UNAVAIL: - /* "remote" backend unavailable. Enter offline mode */ - ret = ENXIO; - goto done; - - default: - goto done; - } - - if (delete_group) { - struct ldb_dn *dn; - - DEBUG(7, ("Group %s does not exist (or is invalid) on remote server," - " deleting!\n", name)); - - dn = sysdb_group_dn(sysdb, tmpctx, dom->name, name); - if (!dn) { - ret = ENOMEM; - goto done; - } - - ret = sysdb_delete_entry(sysdb, dn, true); - if (ret) { - goto done; - } - } - -done: - talloc_zfree(tmpctx); - if (ret) { - DEBUG(2, ("proxy -> getgrnam_r failed for '%s' <%d>\n", - name, status)); - } - return ret; -} - -/* =Getgrgid-wrapper======================================================*/ - -static int get_gr_gid(TALLOC_CTX *mem_ctx, - struct proxy_ctx *ctx, - struct sysdb_ctx *sysdb, - struct sss_domain_info *dom, - gid_t gid) -{ - TALLOC_CTX *tmpctx; - struct group *grp; - enum nss_status status; - char *buffer; - char *newbuf; - size_t buflen; - bool delete_group = false; - struct sysdb_attrs *members; - int ret; - - DEBUG(7, ("Searching group by gid (%d)\n", gid)); - - tmpctx = talloc_new(mem_ctx); - if (!tmpctx) { - return ENOMEM; - } - - grp = talloc(tmpctx, struct group); - if (!grp) { - ret = ENOMEM; - DEBUG(1, ("proxy -> getgrgid_r failed for '%d': [%d] %s\n", - gid, ret, strerror(ret))); - return ret; - } - - buflen = DEFAULT_BUFSIZE; - buffer = talloc_size(tmpctx, buflen); - if (!buffer) { - ret = ENOMEM; - DEBUG(1, ("proxy -> getgrgid_r failed for '%d': [%d] %s\n", - gid, ret, strerror(ret))); - return ret; - } - -again: - /* always zero out the group structure */ - memset(grp, 0, sizeof(struct group)); - - status = ctx->ops.getgrgid_r(gid, 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; - goto again; - - case NSS_STATUS_NOTFOUND: - - DEBUG(7, ("Group %d not found.\n", gid)); - delete_group = true; - break; - - case NSS_STATUS_SUCCESS: - - DEBUG(7, ("Group %d found (%s, %d)\n", - gid, 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(2, ("Group [%s] filtered out! (id out of range)\n", - grp->gr_name)); - delete_group = true; - break; - } - - DEBUG_GR_MEM(7, grp); - - if (grp->gr_mem && grp->gr_mem[0]) { - members = sysdb_new_attrs(tmpctx); - if (!members) { - ret = ENOMEM; - goto done; - } - ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER, - dom->name, - (const char **)grp->gr_mem); - if (ret) { - goto done; - } - } else { - members = NULL; - } - - ret = sysdb_store_group(tmpctx, sysdb, dom, - grp->gr_name, - grp->gr_gid, - members, - ctx->entry_cache_timeout); - if (ret) { - goto done; - } - break; - - case NSS_STATUS_UNAVAIL: - /* "remote" backend unavailable. Enter offline mode */ - ret = ENXIO; - goto done; - - default: - ret = EIO; - goto done; - } - - if (delete_group) { - - DEBUG(7, ("Group %d does not exist (or is invalid) on remote server," - " deleting!\n", gid)); - - ret = sysdb_delete_group(tmpctx, sysdb, dom, NULL, gid); - if (ret) { - goto done; - } - } - -done: - talloc_zfree(tmpctx); - if (ret) { - DEBUG(2, ("proxy -> getgrgid_r failed for '%d' <%d>\n", - gid, status)); - } - return ret; -} - -/* =Getgrent-wrapper======================================================*/ - -static int enum_groups(TALLOC_CTX *mem_ctx, - struct proxy_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; - struct sysdb_attrs *members; - char *newbuf; - int ret; - - DEBUG(7, ("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) { - goto done; - } - in_transaction = true; - - status = ctx->ops.setgrent(); - if (status != NSS_STATUS_SUCCESS) { - ret = EIO; - goto done; - } - -again: - /* 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; - goto again; - - case NSS_STATUS_NOTFOUND: - - /* we are done here */ - DEBUG(7, ("Enumeration completed.\n")); - - ret = sysdb_transaction_commit(sysdb); - in_transaction = false; - break; - - case NSS_STATUS_SUCCESS: - - DEBUG(7, ("Group found (%s, %d)\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(2, ("Group [%s] filtered out! (id out of range)\n", - grp->gr_name)); - - goto again; /* skip */ - } - - DEBUG_GR_MEM(7, grp); - - if (grp->gr_mem && grp->gr_mem[0]) { - members = sysdb_new_attrs(tmpctx); - if (!members) { - ret = ENOMEM; - goto done; - } - ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER, - dom->name, - (const char **)grp->gr_mem); - if (ret) { - goto done; - } - } else { - members = NULL; - } - - ret = sysdb_store_group(tmpctx, sysdb, dom, - grp->gr_name, - grp->gr_gid, - members, - ctx->entry_cache_timeout); - if (ret) { - /* Do not fail completely on errors. - * Just report the failure to save and go on */ - DEBUG(2, ("Failed to store group. Ignoring.\n")); - } - goto again; /* next */ - - case NSS_STATUS_UNAVAIL: - /* "remote" backend unavailable. Enter offline mode */ - ret = ENXIO; - break; - - default: - DEBUG(2, ("proxy -> getgrent_r failed (%d)[%s]\n", - ret, strerror(ret))); - break; - } - -done: - talloc_zfree(tmpctx); - if (in_transaction) { - sysdb_transaction_cancel(sysdb); - } - ctx->ops.endgrent(); - return ret; -} - - -/* =Initgroups-wrapper====================================================*/ - -static int get_initgr_groups_process(TALLOC_CTX *memctx, - struct proxy_ctx *ctx, - struct sysdb_ctx *sysdb, - struct sss_domain_info *dom, - struct passwd *pwd); - -static int get_initgr(TALLOC_CTX *mem_ctx, - struct proxy_ctx *ctx, - struct sysdb_ctx *sysdb, - struct sss_domain_info *dom, - const char *name) -{ - TALLOC_CTX *tmpctx; - bool in_transaction = false; - struct passwd *pwd; - enum nss_status status; - char *buffer; - size_t buflen; - int ret; - - 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) { - goto done; - } - 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(name, pwd, buffer, buflen, &ret); - - switch (status) { - case NSS_STATUS_NOTFOUND: - - DEBUG(7, ("User %s not found.\n", name)); - ret = delete_user(tmpctx, sysdb, dom, name); - if (ret) { - goto done; - } - break; - - case NSS_STATUS_SUCCESS: - - /* 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(2, ("User [%s] filtered out! (id out of range)\n", - name)); - ret = delete_user(tmpctx, sysdb, dom, name); - break; - } - - ret = sysdb_store_user(tmpctx, sysdb, dom, - pwd->pw_name, - pwd->pw_passwd, - pwd->pw_uid, - pwd->pw_gid, - pwd->pw_gecos, - pwd->pw_dir, - pwd->pw_shell, - NULL, ctx->entry_cache_timeout); - if (ret) { - goto done; - } - - ret = get_initgr_groups_process(tmpctx, ctx, sysdb, dom, pwd); - if (ret == EOK) { - ret = sysdb_transaction_commit(sysdb); - in_transaction = true; - } - break; - - case NSS_STATUS_UNAVAIL: - /* "remote" backend unavailable. Enter offline mode */ - ret = ENXIO; - break; - - default: - DEBUG(2, ("proxy -> getpwnam_r failed for '%s' <%d>\n", - name, status)); - ret = EIO; - break; - } - -done: - talloc_zfree(tmpctx); - if (in_transaction) { - sysdb_transaction_cancel(sysdb); - } - return ret; -} - -static int get_initgr_groups_process(TALLOC_CTX *memctx, - struct proxy_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; - - num_gids = 0; - limit = 4096; - num = 4096; - size = num*sizeof(gid_t); - gids = talloc_size(memctx, size); - if (!gids) { - return ENOMEM; - } - -again: - /* FIXME: should we move this call outside the transaction to keep the - * transaction as short as possible ? */ - status = ctx->ops.initgroups_dyn(pwd->pw_name, pwd->pw_gid, &num_gids, - &num, &gids, limit, &ret); - switch (status) { - case 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; - } - goto again; /* retry with more memory */ - - case NSS_STATUS_SUCCESS: - DEBUG(4, ("User [%s] appears to be member of %lu groups\n", - pwd->pw_name, num_gids)); - - for (i = 0; i < num_gids; i++) { - ret = get_gr_gid(memctx, ctx, sysdb, dom, gids[i]); - if (ret) { - return ret; - } - } - break; - - default: - DEBUG(2, ("proxy -> initgroups_dyn failed (%d)[%s]\n", - ret, strerror(ret))); - ret = EIO; - break; - } - - return ret; -} - -/* =Proxy_Id-Functions====================================================*/ - -static void proxy_get_account_info(struct be_req *breq) -{ - struct be_acct_req *ar; - struct proxy_ctx *ctx; - struct tevent_context *ev; - struct sysdb_ctx *sysdb; - struct sss_domain_info *domain; - uid_t uid; - gid_t gid; - int ret; - - ar = talloc_get_type(breq->req_data, struct be_acct_req); - ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, struct proxy_ctx); - ev = breq->be_ctx->ev; - sysdb = breq->be_ctx->sysdb; - domain = breq->be_ctx->domain; - - if (be_is_offline(breq->be_ctx)) { - return proxy_reply(breq, DP_ERR_OFFLINE, EAGAIN, "Offline"); - } - - /* for now we support only core attrs */ - if (ar->attr_type != BE_ATTR_CORE) { - return proxy_reply(breq, DP_ERR_FATAL, EINVAL, "Invalid attr type"); - } - - switch (ar->entry_type & 0xFFF) { - case BE_REQ_USER: /* user */ - switch (ar->filter_type) { - case BE_FILTER_NAME: - if (strchr(ar->filter_value, '*')) { - ret = enum_users(breq, ctx, sysdb, domain); - } else { - ret = get_pw_name(breq, ctx, sysdb, domain, ar->filter_value); - } - break; - - case BE_FILTER_IDNUM: - if (strchr(ar->filter_value, '*')) { - return proxy_reply(breq, DP_ERR_FATAL, - EINVAL, "Invalid attr type"); - } else { - char *endptr; - errno = 0; - uid = (uid_t)strtol(ar->filter_value, &endptr, 0); - if (errno || *endptr || (ar->filter_value == endptr)) { - return proxy_reply(breq, DP_ERR_FATAL, - EINVAL, "Invalid attr type"); - } - ret = get_pw_uid(breq, ctx, sysdb, domain, uid); - } - break; - default: - return proxy_reply(breq, DP_ERR_FATAL, - EINVAL, "Invalid filter type"); - } - break; - - case BE_REQ_GROUP: /* group */ - switch (ar->filter_type) { - case BE_FILTER_NAME: - if (strchr(ar->filter_value, '*')) { - ret = enum_groups(breq, ctx, sysdb, domain); - } else { - ret = get_gr_name(breq, ctx, sysdb, domain, ar->filter_value); - } - break; - case BE_FILTER_IDNUM: - if (strchr(ar->filter_value, '*')) { - return proxy_reply(breq, DP_ERR_FATAL, - EINVAL, "Invalid attr type"); - } else { - char *endptr; - errno = 0; - gid = (gid_t)strtol(ar->filter_value, &endptr, 0); - if (errno || *endptr || (ar->filter_value == endptr)) { - return proxy_reply(breq, DP_ERR_FATAL, - EINVAL, "Invalid attr type"); - } - ret = get_gr_gid(breq, ctx, sysdb, domain, gid); - } - break; - default: - return proxy_reply(breq, DP_ERR_FATAL, - EINVAL, "Invalid filter type"); - } - break; - - case BE_REQ_INITGROUPS: /* init groups for user */ - if (ar->filter_type != BE_FILTER_NAME) { - return proxy_reply(breq, DP_ERR_FATAL, - EINVAL, "Invalid filter type"); - } - if (strchr(ar->filter_value, '*')) { - return proxy_reply(breq, DP_ERR_FATAL, - EINVAL, "Invalid filter value"); - } - if (ctx->ops.initgroups_dyn == NULL) { - return proxy_reply(breq, DP_ERR_FATAL, - ENODEV, "Initgroups call not supported"); - } - ret = get_initgr(breq, ctx, sysdb, domain, ar->filter_value); - break; - - default: /*fail*/ - return proxy_reply(breq, DP_ERR_FATAL, - EINVAL, "Invalid request type"); - } - - if (ret) { - if (ret == ENXIO) { - DEBUG(2, ("proxy returned UNAVAIL error, going offline!\n")); - be_mark_offline(breq->be_ctx); - } - proxy_reply(breq, DP_ERR_FATAL, ret, NULL); - return; - } - proxy_reply(breq, DP_ERR_OK, EOK, NULL); -} - -static void proxy_shutdown(struct be_req *req) -{ - /* TODO: Clean up any internal data */ - req->fn(req, DP_ERR_OK, EOK, NULL); -} - -static void proxy_auth_shutdown(struct be_req *req) -{ - talloc_free(req->be_ctx->bet_info[BET_AUTH].pvt_bet_data); - req->fn(req, DP_ERR_OK, EOK, NULL); -} - -struct bet_ops proxy_id_ops = { - .handler = proxy_get_account_info, - .finalize = proxy_shutdown -}; - -struct bet_ops proxy_auth_ops = { - .handler = proxy_pam_handler, - .finalize = proxy_auth_shutdown -}; - -struct bet_ops proxy_access_ops = { - .handler = proxy_pam_handler, - .finalize = proxy_auth_shutdown -}; - -struct bet_ops proxy_chpass_ops = { - .handler = proxy_pam_handler, - .finalize = proxy_auth_shutdown -}; - -static void *proxy_dlsym(void *handle, const char *functemp, char *libname) -{ - char *funcname; - void *funcptr; - - funcname = talloc_asprintf(NULL, functemp, libname); - if (funcname == NULL) return NULL; - - funcptr = dlsym(handle, funcname); - talloc_free(funcname); - - return funcptr; -} - -int sssm_proxy_id_init(struct be_ctx *bectx, - struct bet_ops **ops, void **pvt_data) -{ - struct proxy_ctx *ctx; - char *libname; - char *libpath; - void *handle; - int ret; - - ctx = talloc_zero(bectx, struct proxy_ctx); - if (!ctx) { - return ENOMEM; - } - ctx->be = bectx; - - ret = confdb_get_int(bectx->cdb, ctx, bectx->conf_path, - CONFDB_DOMAIN_ENTRY_CACHE_TIMEOUT, 600, - &ctx->entry_cache_timeout); - if (ret != EOK) goto done; - - ret = confdb_get_string(bectx->cdb, ctx, bectx->conf_path, - CONFDB_PROXY_LIBNAME, NULL, &libname); - if (ret != EOK) goto done; - if (libname == NULL) { - ret = ENOENT; - goto done; - } - - libpath = talloc_asprintf(ctx, "libnss_%s.so.2", libname); - if (!libpath) { - ret = ENOMEM; - goto done; - } - - handle = dlopen(libpath, RTLD_NOW); - if (!handle) { - DEBUG(0, ("Unable to load %s module with path, error: %s\n", - libpath, dlerror())); - ret = ELIBACC; - goto done; - } - - ctx->ops.getpwnam_r = proxy_dlsym(handle, "_nss_%s_getpwnam_r", libname); - if (!ctx->ops.getpwnam_r) { - DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); - ret = ELIBBAD; - goto done; - } - - ctx->ops.getpwuid_r = proxy_dlsym(handle, "_nss_%s_getpwuid_r", libname); - if (!ctx->ops.getpwuid_r) { - DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); - ret = ELIBBAD; - goto done; - } - - ctx->ops.setpwent = proxy_dlsym(handle, "_nss_%s_setpwent", libname); - if (!ctx->ops.setpwent) { - DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); - ret = ELIBBAD; - goto done; - } - - ctx->ops.getpwent_r = proxy_dlsym(handle, "_nss_%s_getpwent_r", libname); - if (!ctx->ops.getpwent_r) { - DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); - ret = ELIBBAD; - goto done; - } - - ctx->ops.endpwent = proxy_dlsym(handle, "_nss_%s_endpwent", libname); - if (!ctx->ops.endpwent) { - DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); - ret = ELIBBAD; - goto done; - } - - ctx->ops.getgrnam_r = proxy_dlsym(handle, "_nss_%s_getgrnam_r", libname); - if (!ctx->ops.getgrnam_r) { - DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); - ret = ELIBBAD; - goto done; - } - - ctx->ops.getgrgid_r = proxy_dlsym(handle, "_nss_%s_getgrgid_r", libname); - if (!ctx->ops.getgrgid_r) { - DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); - ret = ELIBBAD; - goto done; - } - - ctx->ops.setgrent = proxy_dlsym(handle, "_nss_%s_setgrent", libname); - if (!ctx->ops.setgrent) { - DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); - ret = ELIBBAD; - goto done; - } - - ctx->ops.getgrent_r = proxy_dlsym(handle, "_nss_%s_getgrent_r", libname); - if (!ctx->ops.getgrent_r) { - DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); - ret = ELIBBAD; - goto done; - } - - ctx->ops.endgrent = proxy_dlsym(handle, "_nss_%s_endgrent", libname); - if (!ctx->ops.endgrent) { - DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); - ret = ELIBBAD; - goto done; - } - - ctx->ops.initgroups_dyn = proxy_dlsym(handle, "_nss_%s_initgroups_dyn", - libname); - if (!ctx->ops.initgroups_dyn) { - DEBUG(1, ("The '%s' library does not provides the " - "_nss_XXX_initgroups_dyn function!\n" - "initgroups will be slow as it will require " - "full groups enumeration!\n", libname)); - } - - *ops = &proxy_id_ops; - *pvt_data = ctx; - ret = EOK; - -done: - if (ret != EOK) { - talloc_free(ctx); - } - return ret; -} - -int sssm_proxy_auth_init(struct be_ctx *bectx, - struct bet_ops **ops, void **pvt_data) -{ - struct proxy_auth_ctx *ctx; - int ret; - - ctx = talloc(bectx, struct proxy_auth_ctx); - if (!ctx) { - return ENOMEM; - } - ctx->be = bectx; - - ret = confdb_get_string(bectx->cdb, ctx, bectx->conf_path, - CONFDB_PROXY_PAM_TARGET, NULL, - &ctx->pam_target); - if (ret != EOK) goto done; - if (!ctx->pam_target) { - DEBUG(1, ("Missing option proxy_pam_target.\n")); - ret = EINVAL; - goto done; - } - - *ops = &proxy_auth_ops; - *pvt_data = ctx; - -done: - if (ret != EOK) { - talloc_free(ctx); - } - return ret; -} - -int sssm_proxy_access_init(struct be_ctx *bectx, - struct bet_ops **ops, void **pvt_data) -{ - int ret; - ret = sssm_proxy_auth_init(bectx, ops, pvt_data); - *ops = &proxy_access_ops; - return ret; -} - -int sssm_proxy_chpass_init(struct be_ctx *bectx, - struct bet_ops **ops, void **pvt_data) -{ - int ret; - ret = sssm_proxy_auth_init(bectx, ops, pvt_data); - *ops = &proxy_chpass_ops; - return ret; -} diff --git a/src/providers/proxy/proxy.c b/src/providers/proxy/proxy.c new file mode 100644 index 00000000..9a8dc4a2 --- /dev/null +++ b/src/providers/proxy/proxy.c @@ -0,0 +1,2502 @@ +/* + SSSD + + Proxy Module + + Copyright (C) Simo Sorce 2008-2009 + + 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "util/util.h" +#include "providers/dp_backend.h" +#include "db/sysdb.h" +#include "proxy.h" +#include + +struct proxy_nss_ops { + enum nss_status (*getpwnam_r)(const char *name, struct passwd *result, + char *buffer, size_t buflen, int *errnop); + enum nss_status (*getpwuid_r)(uid_t uid, struct passwd *result, + char *buffer, size_t buflen, int *errnop); + enum nss_status (*setpwent)(void); + enum nss_status (*getpwent_r)(struct passwd *result, + char *buffer, size_t buflen, int *errnop); + enum nss_status (*endpwent)(void); + + enum nss_status (*getgrnam_r)(const char *name, struct group *result, + char *buffer, size_t buflen, int *errnop); + enum nss_status (*getgrgid_r)(gid_t gid, struct group *result, + char *buffer, size_t buflen, int *errnop); + enum nss_status (*setgrent)(void); + enum nss_status (*getgrent_r)(struct group *result, + char *buffer, size_t buflen, int *errnop); + enum nss_status (*endgrent)(void); + enum nss_status (*initgroups_dyn)(const char *user, gid_t group, + long int *start, long int *size, + gid_t **groups, long int limit, + int *errnop); +}; + +struct proxy_ctx { + struct be_ctx *be; + int entry_cache_timeout; + struct proxy_nss_ops ops; +}; + +struct proxy_auth_ctx { + struct be_ctx *be; + char *pam_target; + + uint32_t max_children; + uint32_t running; + uint32_t next_id; + hash_table_t *request_table; + struct sbus_connection *sbus_srv; + int timeout_ms; +}; + +static int client_registration(DBusMessage *message, + struct sbus_connection *conn); + +static struct sbus_method proxy_methods[] = { + { DP_METHOD_REGISTER, client_registration }, + { NULL, NULL } +}; + +struct sbus_interface proxy_interface = { + DP_INTERFACE, + DP_PATH, + SBUS_DEFAULT_VTABLE, + proxy_methods, + NULL +}; + +struct authtok_conv { + uint32_t authtok_size; + uint8_t *authtok; +}; + +struct proxy_client_ctx { + struct be_req *be_req; + struct proxy_auth_ctx *auth_ctx; +}; + +static void proxy_reply(struct be_req *req, int dp_err, + int error, const char *errstr); + +static struct tevent_req *proxy_child_send(TALLOC_CTX *mem_ctx, + struct proxy_auth_ctx *ctx, + struct be_req *be_req); +static void proxy_child_done(struct tevent_req *child_req); +static void proxy_pam_handler(struct be_req *req) { + struct pam_data *pd; + struct proxy_auth_ctx *ctx; + struct tevent_req *child_req = NULL; + struct proxy_client_ctx *client_ctx; + + pd = talloc_get_type(req->req_data, struct pam_data); + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + ctx = talloc_get_type(req->be_ctx->bet_info[BET_AUTH].pvt_bet_data, + struct proxy_auth_ctx); + break; + case SSS_PAM_CHAUTHTOK: + case SSS_PAM_CHAUTHTOK_PRELIM: + ctx = talloc_get_type(req->be_ctx->bet_info[BET_CHPASS].pvt_bet_data, + struct proxy_auth_ctx); + break; + case SSS_PAM_ACCT_MGMT: + ctx = talloc_get_type(req->be_ctx->bet_info[BET_ACCESS].pvt_bet_data, + struct proxy_auth_ctx); + break; + case SSS_PAM_SETCRED: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + pd->pam_status = PAM_SUCCESS; + proxy_reply(req, DP_ERR_OK, EOK, NULL); + return; + default: + DEBUG(1, ("Unsupported PAM task.\n")); + pd->pam_status = PAM_MODULE_UNKNOWN; + proxy_reply(req, DP_ERR_OK, EINVAL, "Unsupported PAM task"); + return; + } + + client_ctx = talloc(req, struct proxy_client_ctx); + if (client_ctx == NULL) { + proxy_reply(req, DP_ERR_FATAL, ENOMEM, NULL); + return; + } + client_ctx->auth_ctx = ctx; + client_ctx->be_req = req; + + /* Queue the request and spawn a child if there + * is an available slot. + */ + child_req = proxy_child_send(req, ctx, req); + if (child_req == NULL) { + /* Could not queue request + * Return an error + */ + proxy_reply(req, DP_ERR_FATAL, EINVAL, "Could not queue request\n"); + return; + } + tevent_req_set_callback(child_req, proxy_child_done, client_ctx); + return; +} + +struct pc_init_ctx; +struct proxy_child_ctx { + struct proxy_auth_ctx *auth_ctx; + struct be_req *be_req; + struct pam_data *pd; + + uint32_t id; + pid_t pid; + bool running; + + struct sbus_connection *conn; + struct tevent_timer *timer; + + struct tevent_req *init_req; +}; + +static int proxy_child_destructor(TALLOC_CTX *ctx) +{ + struct proxy_child_ctx *child_ctx = + talloc_get_type(ctx, struct proxy_child_ctx); + hash_key_t key; + + DEBUG(8, ("Removing proxy child id [%d]\n", child_ctx->id)); + key.type = HASH_KEY_ULONG; + key.ul = child_ctx->id; + hash_delete(child_ctx->auth_ctx->request_table, &key); + return 0; +} + +static struct tevent_req *proxy_child_init_send(TALLOC_CTX *mem_ctx, + struct proxy_child_ctx *child_ctx, + struct proxy_auth_ctx *auth_ctx); +static void proxy_child_init_done(struct tevent_req *subreq); +static struct tevent_req *proxy_child_send(TALLOC_CTX *mem_ctx, + struct proxy_auth_ctx *auth_ctx, + struct be_req *be_req) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct proxy_child_ctx *state; + int hret; + hash_key_t key; + hash_value_t value; + uint32_t first; + + req = tevent_req_create(mem_ctx, &state, struct proxy_child_ctx); + if (req == NULL) { + DEBUG(1, ("Could not send PAM request to child\n")); + return NULL; + } + + state->be_req = be_req; + state->auth_ctx = auth_ctx; + state->pd = talloc_get_type(be_req->req_data, struct pam_data); + + /* Find an available key */ + key.type = HASH_KEY_ULONG; + key.ul = auth_ctx->next_id; + + first = auth_ctx->next_id; + while (auth_ctx->next_id == 0 || + hash_has_key(auth_ctx->request_table, &key)) { + /* Handle overflow, zero is a reserved value + * Also handle the unlikely case where the next ID + * is still awaiting being run + */ + auth_ctx->next_id++; + key.ul = auth_ctx->next_id; + + if (auth_ctx->next_id == first) { + /* We've looped through all possible integers! */ + DEBUG(0, ("Serious error: queue is too long!\n")); + talloc_zfree(req); + return NULL; + } + } + + state->id = auth_ctx->next_id; + auth_ctx->next_id++; + + value.type = HASH_VALUE_PTR; + value.ptr = req; + DEBUG(8, ("Queueing request [%d]\n", key.ul)); + hret = hash_enter(auth_ctx->request_table, + &key, &value); + if (hret != HASH_SUCCESS) { + DEBUG(1, ("Could not add request to the queue\n")); + talloc_zfree(req); + return NULL; + } + + talloc_set_destructor((TALLOC_CTX *) state, + proxy_child_destructor); + + if (auth_ctx->running < auth_ctx->max_children) { + /* There's an available slot; start a child + * to handle the request + */ + + auth_ctx->running++; + subreq = proxy_child_init_send(auth_ctx, state, auth_ctx); + if (!subreq) { + DEBUG(1, ("Could not fork child process\n")); + auth_ctx->running--; + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, proxy_child_init_done, req); + + state->running = true; + } + else { + /* If there was no available slot, it will be queued + * until a slot is available + */ + DEBUG(8, ("All available child slots are full, queuing request\n")); + } + return req; +} + +struct pc_init_ctx { + char *command; + pid_t pid; + struct tevent_timer *timeout; + struct tevent_signal *sige; + struct proxy_child_ctx *child_ctx; + struct sbus_connection *conn; +}; + +static int pc_init_destructor (TALLOC_CTX *ctx) +{ + struct pc_init_ctx *init_ctx = + talloc_get_type(ctx, struct pc_init_ctx); + + /* If the init request has died, forcibly kill the child */ + kill(init_ctx->pid, SIGKILL); + return 0; +} + +static void pc_init_sig_handler(struct tevent_context *ev, + struct tevent_signal *sige, int signum, + int count, void *__siginfo, void *pvt); +static void pc_init_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr); +static struct tevent_req *proxy_child_init_send(TALLOC_CTX *mem_ctx, + struct proxy_child_ctx *child_ctx, + struct proxy_auth_ctx *auth_ctx) +{ + struct tevent_req *req; + struct pc_init_ctx *state; + char **proxy_child_args; + struct timeval tv; + errno_t ret; + pid_t pid; + + req = tevent_req_create(mem_ctx, &state, struct pc_init_ctx); + if (req == NULL) { + DEBUG(1, ("Could not create tevent_req\n")); + return NULL; + } + + state->child_ctx = child_ctx; + + state->command = talloc_asprintf(req, + "%s/proxy_child -d %d%s%s --domain %s --id %d", + SSSD_LIBEXEC_PATH, debug_level, + (debug_timestamps ? "" : " --debug-timestamps=0"), + (debug_to_file ? " --debug-to-files" : ""), + auth_ctx->be->domain->name, + child_ctx->id); + if (state->command == NULL) { + DEBUG(1, ("talloc_asprintf failed.\n")); + return NULL; + } + + DEBUG(7, ("Starting proxy child with args [%s]\n", state->command)); + + pid = fork(); + if (pid < 0) { + ret = errno; + DEBUG(1, ("fork failed [%d][%s].\n", ret, strerror(ret))); + talloc_zfree(req); + return NULL; + } + + if (pid == 0) { /* child */ + proxy_child_args = parse_args(state->command); + execvp(proxy_child_args[0], proxy_child_args); + + ret = errno; + DEBUG(0, ("Could not start proxy child [%s]: [%d][%s].\n", + state->command, ret, strerror(ret))); + + _exit(1); + } + + else { /* parent */ + state->pid = pid; + /* Make sure to kill the child process if we abort */ + talloc_set_destructor((TALLOC_CTX *)state, pc_init_destructor); + + state->sige = tevent_add_signal(auth_ctx->be->ev, req, + SIGCHLD, SA_SIGINFO, + pc_init_sig_handler, req); + if (state->sige == NULL) { + DEBUG(1, ("tevent_add_signal failed.\n")); + talloc_zfree(req); + return NULL; + } + + /* Save the init request to the child context. + * This is technically a layering violation, + * but it's the only sane way to be able to + * identify which client is which when it + * connects to the backend in + * client_registration() + */ + child_ctx->init_req = req; + + /* Wait six seconds for the child to connect + * This is because the connection handler will add + * its own five-second timeout, and we don't want to + * be faster here. + */ + tv = tevent_timeval_current_ofs(6, 0); + state->timeout = tevent_add_timer(auth_ctx->be->ev, req, + tv, pc_init_timeout, req); + + /* processing will continue once the connection is received + * in proxy_client_init() + */ + return req; + } +} + +static void pc_init_sig_handler(struct tevent_context *ev, + struct tevent_signal *sige, int signum, + int count, void *__siginfo, void *pvt) +{ + int ret; + int child_status; + struct tevent_req *req; + struct pc_init_ctx *init_ctx; + + if (count <= 0) { + DEBUG(0, ("SIGCHLD handler called with invalid child count\n")); + return; + } + + req = talloc_get_type(pvt, struct tevent_req); + init_ctx = tevent_req_data(req, struct pc_init_ctx); + + DEBUG(7, ("Waiting for child [%d].\n", init_ctx->pid)); + + errno = 0; + ret = waitpid(init_ctx->pid, &child_status, WNOHANG); + + if (ret == -1) { + ret = errno; + DEBUG(1, ("waitpid failed [%d][%s].\n", ret, strerror(ret))); + } else if (ret == 0) { + DEBUG(1, ("waitpid did not find a child with changed status.\n")); + } else { + if (WIFEXITED(child_status)) { + DEBUG(4, ("child [%d] exited with status [%d].\n", ret, + WEXITSTATUS(child_status))); + tevent_req_error(req, EIO); + } else if (WIFSIGNALED(child_status)) { + DEBUG(4, ("child [%d] was terminate by signal [%d].\n", ret, + WTERMSIG(child_status))); + tevent_req_error(req, EIO); + } else { + if (WIFSTOPPED(child_status)) { + DEBUG(1, ("child [%d] was stopped by signal [%d].\n", ret, + WSTOPSIG(child_status))); + } + if (WIFCONTINUED(child_status)) { + DEBUG(1, ("child [%d] was resumed by delivery of SIGCONT.\n", + ret)); + } + DEBUG(1, ("Child is still running, no new child is started.\n")); + return; + } + } +} + +static void pc_init_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr) +{ + struct tevent_req *req; + struct pc_init_ctx *state; + + DEBUG(2, ("Client timed out before Identification!\n")); + + req = talloc_get_type(ptr, struct tevent_req); + state = tevent_req_data(req, struct pc_init_ctx); + + tevent_req_error(req, ETIMEDOUT); +} + +static errno_t proxy_child_init_recv(struct tevent_req *req, + pid_t *pid, + struct sbus_connection **conn) +{ + struct pc_init_ctx *state; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + state = tevent_req_data(req, struct pc_init_ctx); + + /* Unset the destructor since we initialized successfully. + * We don't want to kill the child now that it's properly + * set up. + */ + talloc_set_destructor((TALLOC_CTX *)state, NULL); + + *pid = state->pid; + *conn = state->conn; + + return EOK; +} + +struct proxy_child_sig_ctx { + struct proxy_auth_ctx *auth_ctx; + pid_t pid; +}; +static void proxy_child_sig_handler(struct tevent_context *ev, + struct tevent_signal *sige, int signum, + int count, void *__siginfo, void *pvt); +static struct tevent_req *proxy_pam_conv_send(TALLOC_CTX *mem_ctx, + struct proxy_auth_ctx *auth_ctx, + struct sbus_connection *conn, + struct pam_data *pd, + pid_t pid); +static void proxy_pam_conv_done(struct tevent_req *subreq); +static void proxy_child_init_done(struct tevent_req *subreq) { + int ret; + struct tevent_signal *sige; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct proxy_child_ctx *child_ctx = + tevent_req_data(req, struct proxy_child_ctx); + struct proxy_child_sig_ctx *sig_ctx; + + ret = proxy_child_init_recv(subreq, &child_ctx->pid, &child_ctx->conn); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(6, ("Proxy child init failed [%d]\n", ret)); + tevent_req_error(req, ret); + return; + } + + /* An initialized child is available, awaiting the PAM command */ + subreq = proxy_pam_conv_send(req, child_ctx->auth_ctx, + child_ctx->conn, child_ctx->pd, + child_ctx->pid); + if (!subreq) { + DEBUG(1,("Could not start PAM conversation\n")); + tevent_req_error(req, EIO); + return; + } + tevent_req_set_callback(subreq, proxy_pam_conv_done, req); + + /* Add a signal handler for the child under the auth_ctx, + * that way if the child exits after completion of the + * request, it will still be handled. + */ + sig_ctx = talloc_zero(child_ctx->auth_ctx, struct proxy_child_sig_ctx); + if(sig_ctx == NULL) { + DEBUG(1, ("tevent_add_signal failed.\n")); + tevent_req_error(req, ENOMEM); + return; + } + sig_ctx->auth_ctx = child_ctx->auth_ctx; + sig_ctx->pid = child_ctx->pid; + + sige = tevent_add_signal(child_ctx->auth_ctx->be->ev, + child_ctx->auth_ctx, + SIGCHLD, SA_SIGINFO, + proxy_child_sig_handler, + sig_ctx); + if (sige == NULL) { + DEBUG(1, ("tevent_add_signal failed.\n")); + tevent_req_error(req, ENOMEM); + return; + } + + /* Steal the signal context onto the signal event + * so that when the signal is freed, the context + * will go with it. + */ + talloc_steal(sige, sig_ctx); +} + +static void remove_sige(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt); +static void run_proxy_child_queue(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt); +static void proxy_child_sig_handler(struct tevent_context *ev, + struct tevent_signal *sige, int signum, + int count, void *__siginfo, void *pvt) +{ + int ret; + int child_status; + struct proxy_child_sig_ctx *sig_ctx; + struct tevent_immediate *imm; + struct tevent_immediate *imm2; + + if (count <= 0) { + DEBUG(0, ("SIGCHLD handler called with invalid child count\n")); + return; + } + + sig_ctx = talloc_get_type(pvt, struct proxy_child_sig_ctx); + DEBUG(7, ("Waiting for child [%d].\n", sig_ctx->pid)); + + errno = 0; + ret = waitpid(sig_ctx->pid, &child_status, WNOHANG); + + if (ret == -1) { + ret = errno; + DEBUG(1, ("waitpid failed [%d][%s].\n", ret, strerror(ret))); + } else if (ret == 0) { + DEBUG(1, ("waitpid did not found a child with changed status.\n")); + } else { + if (WIFEXITED(child_status)) { + DEBUG(4, ("child [%d] exited with status [%d].\n", ret, + WEXITSTATUS(child_status))); + } else if (WIFSIGNALED(child_status)) { + DEBUG(4, ("child [%d] was terminated by signal [%d].\n", ret, + WTERMSIG(child_status))); + } else { + if (WIFSTOPPED(child_status)) { + DEBUG(1, ("child [%d] was stopped by signal [%d].\n", ret, + WSTOPSIG(child_status))); + } + if (WIFCONTINUED(child_status)) { + DEBUG(1, ("child [%d] was resumed by delivery of SIGCONT.\n", + ret)); + } + DEBUG(1, ("Child is still running, no new child is started.\n")); + return; + } + + imm = tevent_create_immediate(ev); + if (imm == NULL) { + DEBUG(1, ("tevent_create_immediate failed.\n")); + return; + } + + tevent_schedule_immediate(imm, ev, run_proxy_child_queue, + sig_ctx->auth_ctx); + + /* schedule another immediate timer to delete the sigchld handler */ + imm2 = tevent_create_immediate(ev); + if (imm == NULL) { + DEBUG(1, ("tevent_create_immediate failed.\n")); + return; + } + + tevent_schedule_immediate(imm2, ev, remove_sige, sige); + } + + return; +} + +static void remove_sige(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt) +{ + talloc_free(pvt); +} + +struct proxy_conv_ctx { + struct proxy_auth_ctx *auth_ctx; + struct sbus_connection *conn; + struct pam_data *pd; + pid_t pid; +}; +static void proxy_pam_conv_reply(DBusPendingCall *pending, void *ptr); +static struct tevent_req *proxy_pam_conv_send(TALLOC_CTX *mem_ctx, + struct proxy_auth_ctx *auth_ctx, + struct sbus_connection *conn, + struct pam_data *pd, + pid_t pid) +{ + errno_t ret; + bool dp_ret; + DBusMessage *msg; + struct tevent_req *req; + struct proxy_conv_ctx *state; + + req = tevent_req_create(mem_ctx, &state, struct proxy_conv_ctx); + if (req == NULL) { + return NULL; + } + + state->auth_ctx = auth_ctx; + state->conn = conn; + state->pd = pd; + state->pid = pid; + + msg = dbus_message_new_method_call(NULL, + DP_PATH, + DP_INTERFACE, + DP_METHOD_PAMHANDLER); + if (msg == NULL) { + DEBUG(1, ("dbus_message_new_method_call failed.\n")); + talloc_zfree(req); + return NULL; + } + + DEBUG(4, ("Sending request with the following data:\n")); + DEBUG_PAM_DATA(4, pd); + + dp_ret = dp_pack_pam_request(msg, pd); + if (!dp_ret) { + DEBUG(1, ("Failed to build message\n")); + dbus_message_unref(msg); + talloc_zfree(req); + return NULL; + } + + ret = sbus_conn_send(state->conn, msg, state->auth_ctx->timeout_ms, + proxy_pam_conv_reply, req, NULL); + if (ret != EOK) { + dbus_message_unref(msg); + talloc_zfree(req); + return NULL; + } + + dbus_message_unref(msg); + return req; +} + +static void proxy_pam_conv_reply(DBusPendingCall *pending, void *ptr) +{ + struct tevent_req *req; + struct proxy_conv_ctx *state; + DBusError dbus_error; + DBusMessage *reply; + int type; + int ret; + + DEBUG(8, ("Handling pam conversation reply\n")); + + req = talloc_get_type(ptr, struct tevent_req); + state = tevent_req_data(req, struct proxy_conv_ctx); + + dbus_error_init(&dbus_error); + + reply = dbus_pending_call_steal_reply(pending); + dbus_pending_call_unref(pending); + if (reply == NULL) { + DEBUG(0, ("Severe error. A reply callback was called but no reply was" + "received and no timeout occurred\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + tevent_req_error(req, EIO); + } + + type = dbus_message_get_type(reply); + switch (type) { + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + ret = dp_unpack_pam_response(reply, state->pd, &dbus_error); + if (!ret) { + DEBUG(0, ("Failed to parse reply.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + dbus_message_unref(reply); + tevent_req_error(req, EIO); + return; + } + DEBUG(4, ("received: [%d][%s]\n", + state->pd->pam_status, + state->pd->domain)); + break; + case DBUS_MESSAGE_TYPE_ERROR: + DEBUG(0, ("Reply error [%s].\n", + dbus_message_get_error_name(reply))); + state->pd->pam_status = PAM_SYSTEM_ERR; + break; + default: + DEBUG(0, ("Default... what now?.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + } + dbus_message_unref(reply); + + /* Kill the child */ + kill(state->pid, SIGKILL); + + /* Conversation is finished */ + tevent_req_done(req); +} + +static errno_t proxy_pam_conv_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static void proxy_pam_conv_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = proxy_pam_conv_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(6, ("Proxy PAM conversation failed [%d]\n", ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static int proxy_child_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct pam_data **pd) +{ + struct proxy_child_ctx *ctx; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + ctx = tevent_req_data(req, struct proxy_child_ctx); + *pd = talloc_steal(mem_ctx, ctx->pd); + + return EOK; +} + +static void proxy_child_done(struct tevent_req *req) +{ + struct proxy_client_ctx *client_ctx = + tevent_req_callback_data(req, struct proxy_client_ctx); + struct pam_data *pd; + char *password; + int ret; + struct tevent_immediate *imm; + + ret = proxy_child_recv(req, client_ctx, &pd); + talloc_zfree(req); + if (ret != EOK) { + /* Pam child failed */ + client_ctx->auth_ctx->running--; + proxy_reply(client_ctx->be_req, DP_ERR_FATAL, ret, + "PAM child failed"); + + /* Start the next auth in the queue, if any */ + imm = tevent_create_immediate(client_ctx->be_req->be_ctx->ev); + if (imm == NULL) { + DEBUG(1, ("tevent_create_immediate failed.\n")); + return; + } + + tevent_schedule_immediate(imm, + client_ctx->be_req->be_ctx->ev, + run_proxy_child_queue, + client_ctx->auth_ctx); + return; + } + + /* Check if we need to save the cached credentials */ + if ((pd->cmd == SSS_PAM_AUTHENTICATE || pd->cmd == SSS_PAM_CHAUTHTOK) && + pd->pam_status == PAM_SUCCESS && + client_ctx->be_req->be_ctx->domain->cache_credentials) { + password = talloc_strndup(client_ctx->be_req, + (char *) pd->authtok, + pd->authtok_size); + if (!password) { + /* password caching failures are not fatal errors */ + DEBUG(2, ("Failed to cache password\n")); + goto done; + } + talloc_set_destructor((TALLOC_CTX *)password, password_destructor); + + ret = sysdb_cache_password(client_ctx, + client_ctx->be_req->be_ctx->sysdb, + client_ctx->be_req->be_ctx->domain, + pd->user, password); + + /* password caching failures are not fatal errors */ + /* so we just log it any return */ + if (ret != EOK) { + DEBUG(2, ("Failed to cache password (%d)[%s]!?\n", + ret, strerror(ret))); + } + } + +done: + proxy_reply(client_ctx->be_req, DP_ERR_OK, EOK, NULL); +} + +static void run_proxy_child_queue(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt) +{ + struct proxy_auth_ctx *auth_ctx; + struct hash_iter_context_t *iter; + struct hash_entry_t *entry; + struct tevent_req *req; + struct tevent_req *subreq; + struct proxy_child_ctx *state; + + auth_ctx = talloc_get_type(pvt, struct proxy_auth_ctx); + + /* Launch next queued request */ + iter = new_hash_iter_context(auth_ctx->request_table); + while ((entry = iter->next(iter)) != NULL) { + req = talloc_get_type(entry->value.ptr, struct tevent_req); + state = tevent_req_data(req, struct proxy_child_ctx); + if (!state->running) { + break; + } + } + + if (!entry) { + /* Nothing pending on the queue */ + return; + } + + if (auth_ctx->running < auth_ctx->max_children) { + /* There's an available slot; start a child + * to handle the request + */ + auth_ctx->running++; + subreq = proxy_child_init_send(auth_ctx, state, auth_ctx); + if (!subreq) { + DEBUG(1, ("Could not fork child process\n")); + auth_ctx->running--; + talloc_zfree(req); + return; + } + tevent_req_set_callback(subreq, proxy_child_init_done, req); + + state->running = true; + } +} + +static void proxy_reply(struct be_req *req, int dp_err, + int error, const char *errstr) +{ + if (!req->be_ctx->offstat.offline) { + /* This action took place online. + * Fire any online callbacks if necessary. + * Note: we're checking the offline value directly, + * because if the activity took a long time to + * complete, calling be_is_offline() might report false + * incorrectly. + */ + be_run_online_cb(req->be_ctx); + } + return req->fn(req, dp_err, error, errstr); +} + +/* =Common-proxy-tevent_req-utils=========================================*/ + +#define DEFAULT_BUFSIZE 4096 +#define MAX_BUF_SIZE 1024*1024 /* max 1MiB */ + +/* =Getpwnam-wrapper======================================================*/ + +static int delete_user(TALLOC_CTX *mem_ctx, struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, const char *name); + +static int get_pw_name(TALLOC_CTX *mem_ctx, + struct proxy_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + const char *name) +{ + TALLOC_CTX *tmpctx; + struct passwd *pwd; + enum nss_status status; + char *buffer; + size_t buflen; + int ret; + + DEBUG(7, ("Searching user by name (%s)\n", name)); + + 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; + } + + /* FIXME: should we move this call outside the transaction to keep the + * transaction as short as possible ? */ + status = ctx->ops.getpwnam_r(name, pwd, buffer, buflen, &ret); + + switch (status) { + case NSS_STATUS_NOTFOUND: + + DEBUG(7, ("User %s not found.\n", name)); + ret = delete_user(tmpctx, sysdb, dom, name); + if (ret) { + goto done; + } + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(7, ("User %s found: (%s, %d, %d)\n", + name, 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(2, ("User [%s] filtered out! (id out of range)\n", name)); + ret = delete_user(tmpctx, sysdb, dom, name); + if (ret) { + goto done; + } + break; + } + + ret = sysdb_store_user(tmpctx, sysdb, dom, + pwd->pw_name, + pwd->pw_passwd, + pwd->pw_uid, + pwd->pw_gid, + pwd->pw_gecos, + pwd->pw_dir, + pwd->pw_shell, + NULL, ctx->entry_cache_timeout); + if (ret) { + goto done; + } + break; + + case NSS_STATUS_UNAVAIL: + /* "remote" backend unavailable. Enter offline mode */ + ret = ENXIO; + goto done; + + default: + ret = EIO; + goto done; + } + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(2, ("proxy -> getpwnam_r failed for '%s' <%d>\n", + name, status)); + } + return ret; +} + +static int delete_user(TALLOC_CTX *mem_ctx, struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, const char *name) +{ + struct ldb_dn *dn; + + DEBUG(7, ("User %s does not exist (or is invalid) on remote server," + " deleting!\n", name)); + + dn = sysdb_user_dn(sysdb, mem_ctx, domain->name, name); + if (!dn) { + return ENOMEM; + } + + return sysdb_delete_entry(sysdb, dn, true); +} + +/* =Getpwuid-wrapper======================================================*/ + +static int get_pw_uid(TALLOC_CTX *mem_ctx, + struct proxy_ctx *ctx, + struct sysdb_ctx *sysdb, + 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; + + DEBUG(7, ("Searching user by uid (%d)\n", uid)); + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + pwd = talloc_zero(tmpctx, struct passwd); + if (!pwd) { + ret = ENOMEM; + DEBUG(1, ("proxy -> getpwuid_r failed for '%d': [%d] %s\n", + uid, ret, strerror(ret))); + return ret; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + DEBUG(1, ("proxy -> getpwuid_r failed for '%d': [%d] %s\n", + uid, ret, strerror(ret))); + return ret; + } + + status = ctx->ops.getpwuid_r(uid, pwd, buffer, buflen, &ret); + + switch (status) { + case NSS_STATUS_NOTFOUND: + + DEBUG(7, ("User %d not found.\n", uid)); + del_user = true; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(7, ("User %d found (%s, %d, %d)\n", + uid, 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(2, ("User [%s] filtered out! (id out of range)\n", + pwd->pw_name)); + del_user = true; + break; + } + + ret = sysdb_store_user(tmpctx, sysdb, dom, + pwd->pw_name, + pwd->pw_passwd, + pwd->pw_uid, + pwd->pw_gid, + pwd->pw_gecos, + pwd->pw_dir, + pwd->pw_shell, + NULL, ctx->entry_cache_timeout); + if (ret) { + goto done; + } + break; + + case NSS_STATUS_UNAVAIL: + /* "remote" backend unavailable. Enter offline mode */ + ret = ENXIO; + goto done; + + default: + ret = EIO; + goto done; + } + + if (del_user) { + DEBUG(7, ("User %d does not exist (or is invalid) on remote server," + " deleting!\n", uid)); + + ret = sysdb_delete_user(tmpctx, sysdb, dom, NULL, uid); + if (ret) { + goto done; + } + } + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(2, ("proxy -> getpwuid_r failed for '%d' <%d>\n", uid, status)); + } + return ret; +} + +/* =Getpwent-wrapper======================================================*/ + +static int enum_users(TALLOC_CTX *mem_ctx, + struct proxy_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; + + DEBUG(7, ("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) { + goto done; + } + in_transaction = true; + + status = ctx->ops.setpwent(); + if (status != NSS_STATUS_SUCCESS) { + ret = EIO; + goto done; + } + +again: + /* 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; + goto again; + + case NSS_STATUS_NOTFOUND: + + /* we are done here */ + DEBUG(7, ("Enumeration completed.\n")); + + ret = sysdb_transaction_commit(sysdb); + in_transaction = false; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(7, ("User found (%s, %d, %d)\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(2, ("User [%s] filtered out! (id out of range)\n", + pwd->pw_name)); + + goto again; /* skip */ + } + + ret = sysdb_store_user(tmpctx, sysdb, dom, + pwd->pw_name, + pwd->pw_passwd, + pwd->pw_uid, + pwd->pw_gid, + pwd->pw_gecos, + pwd->pw_dir, + pwd->pw_shell, + NULL, ctx->entry_cache_timeout); + if (ret) { + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + DEBUG(2, ("Failed to store user %s. Ignoring.\n", + pwd->pw_name)); + } + goto again; /* next */ + + case NSS_STATUS_UNAVAIL: + /* "remote" backend unavailable. Enter offline mode */ + ret = ENXIO; + break; + + default: + DEBUG(2, ("proxy -> getpwent_r failed (%d)[%s]\n", + ret, strerror(ret))); + break; + } + +done: + talloc_zfree(tmpctx); + if (in_transaction) { + sysdb_transaction_cancel(sysdb); + } + ctx->ops.endpwent(); + return ret; +} + +/* =Getgrnam-wrapper======================================================*/ + +#define DEBUG_GR_MEM(level, grp) \ + do { \ + if (debug_level >= level) { \ + 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 int get_gr_name(TALLOC_CTX *mem_ctx, + struct proxy_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + const char *name) +{ + TALLOC_CTX *tmpctx; + struct group *grp; + enum nss_status status; + char *buffer; + char *newbuf; + size_t buflen; + bool delete_group = false; + struct sysdb_attrs *members; + int ret; + + DEBUG(7, ("Searching group by name (%s)\n", name)); + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + grp = talloc(tmpctx, struct group); + if (!grp) { + ret = ENOMEM; + DEBUG(1, ("proxy -> getgrnam_r failed for '%s': [%d] %s\n", + name, ret, strerror(ret))); + return ret; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + DEBUG(1, ("proxy -> getgrnam_r failed for '%s': [%d] %s\n", + name, ret, strerror(ret))); + return ret; + } + + /* FIXME: should we move this call outside the transaction to keep the + * transaction as short as possible ? */ +again: + /* always zero out the grp structure */ + memset(grp, 0, sizeof(struct group)); + + status = ctx->ops.getgrnam_r(name, 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; + goto again; + + case NSS_STATUS_NOTFOUND: + + DEBUG(7, ("Group %s not found.\n", name)); + delete_group = true; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(7, ("Group %s found: (%s, %d)\n", + name, 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(2, ("Group [%s] filtered out! (id out of range)\n", + name)); + delete_group = true; + break; + } + + DEBUG_GR_MEM(7, grp); + + if (grp->gr_mem && grp->gr_mem[0]) { + members = sysdb_new_attrs(tmpctx); + if (!members) { + ret = ENOMEM; + goto done; + } + ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER, + dom->name, + (const char **)grp->gr_mem); + if (ret) { + goto done; + } + } else { + members = NULL; + } + + ret = sysdb_store_group(tmpctx, sysdb, dom, + grp->gr_name, + grp->gr_gid, + members, + ctx->entry_cache_timeout); + if (ret) { + goto done; + } + break; + + case NSS_STATUS_UNAVAIL: + /* "remote" backend unavailable. Enter offline mode */ + ret = ENXIO; + goto done; + + default: + goto done; + } + + if (delete_group) { + struct ldb_dn *dn; + + DEBUG(7, ("Group %s does not exist (or is invalid) on remote server," + " deleting!\n", name)); + + dn = sysdb_group_dn(sysdb, tmpctx, dom->name, name); + if (!dn) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_delete_entry(sysdb, dn, true); + if (ret) { + goto done; + } + } + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(2, ("proxy -> getgrnam_r failed for '%s' <%d>\n", + name, status)); + } + return ret; +} + +/* =Getgrgid-wrapper======================================================*/ + +static int get_gr_gid(TALLOC_CTX *mem_ctx, + struct proxy_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + gid_t gid) +{ + TALLOC_CTX *tmpctx; + struct group *grp; + enum nss_status status; + char *buffer; + char *newbuf; + size_t buflen; + bool delete_group = false; + struct sysdb_attrs *members; + int ret; + + DEBUG(7, ("Searching group by gid (%d)\n", gid)); + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + grp = talloc(tmpctx, struct group); + if (!grp) { + ret = ENOMEM; + DEBUG(1, ("proxy -> getgrgid_r failed for '%d': [%d] %s\n", + gid, ret, strerror(ret))); + return ret; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + DEBUG(1, ("proxy -> getgrgid_r failed for '%d': [%d] %s\n", + gid, ret, strerror(ret))); + return ret; + } + +again: + /* always zero out the group structure */ + memset(grp, 0, sizeof(struct group)); + + status = ctx->ops.getgrgid_r(gid, 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; + goto again; + + case NSS_STATUS_NOTFOUND: + + DEBUG(7, ("Group %d not found.\n", gid)); + delete_group = true; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(7, ("Group %d found (%s, %d)\n", + gid, 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(2, ("Group [%s] filtered out! (id out of range)\n", + grp->gr_name)); + delete_group = true; + break; + } + + DEBUG_GR_MEM(7, grp); + + if (grp->gr_mem && grp->gr_mem[0]) { + members = sysdb_new_attrs(tmpctx); + if (!members) { + ret = ENOMEM; + goto done; + } + ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER, + dom->name, + (const char **)grp->gr_mem); + if (ret) { + goto done; + } + } else { + members = NULL; + } + + ret = sysdb_store_group(tmpctx, sysdb, dom, + grp->gr_name, + grp->gr_gid, + members, + ctx->entry_cache_timeout); + if (ret) { + goto done; + } + break; + + case NSS_STATUS_UNAVAIL: + /* "remote" backend unavailable. Enter offline mode */ + ret = ENXIO; + goto done; + + default: + ret = EIO; + goto done; + } + + if (delete_group) { + + DEBUG(7, ("Group %d does not exist (or is invalid) on remote server," + " deleting!\n", gid)); + + ret = sysdb_delete_group(tmpctx, sysdb, dom, NULL, gid); + if (ret) { + goto done; + } + } + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(2, ("proxy -> getgrgid_r failed for '%d' <%d>\n", + gid, status)); + } + return ret; +} + +/* =Getgrent-wrapper======================================================*/ + +static int enum_groups(TALLOC_CTX *mem_ctx, + struct proxy_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; + struct sysdb_attrs *members; + char *newbuf; + int ret; + + DEBUG(7, ("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) { + goto done; + } + in_transaction = true; + + status = ctx->ops.setgrent(); + if (status != NSS_STATUS_SUCCESS) { + ret = EIO; + goto done; + } + +again: + /* 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; + goto again; + + case NSS_STATUS_NOTFOUND: + + /* we are done here */ + DEBUG(7, ("Enumeration completed.\n")); + + ret = sysdb_transaction_commit(sysdb); + in_transaction = false; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(7, ("Group found (%s, %d)\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(2, ("Group [%s] filtered out! (id out of range)\n", + grp->gr_name)); + + goto again; /* skip */ + } + + DEBUG_GR_MEM(7, grp); + + if (grp->gr_mem && grp->gr_mem[0]) { + members = sysdb_new_attrs(tmpctx); + if (!members) { + ret = ENOMEM; + goto done; + } + ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER, + dom->name, + (const char **)grp->gr_mem); + if (ret) { + goto done; + } + } else { + members = NULL; + } + + ret = sysdb_store_group(tmpctx, sysdb, dom, + grp->gr_name, + grp->gr_gid, + members, + ctx->entry_cache_timeout); + if (ret) { + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + DEBUG(2, ("Failed to store group. Ignoring.\n")); + } + goto again; /* next */ + + case NSS_STATUS_UNAVAIL: + /* "remote" backend unavailable. Enter offline mode */ + ret = ENXIO; + break; + + default: + DEBUG(2, ("proxy -> getgrent_r failed (%d)[%s]\n", + ret, strerror(ret))); + break; + } + +done: + talloc_zfree(tmpctx); + if (in_transaction) { + sysdb_transaction_cancel(sysdb); + } + ctx->ops.endgrent(); + return ret; +} + + +/* =Initgroups-wrapper====================================================*/ + +static int get_initgr_groups_process(TALLOC_CTX *memctx, + struct proxy_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct passwd *pwd); + +static int get_initgr(TALLOC_CTX *mem_ctx, + struct proxy_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + const char *name) +{ + TALLOC_CTX *tmpctx; + bool in_transaction = false; + struct passwd *pwd; + enum nss_status status; + char *buffer; + size_t buflen; + int ret; + + 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) { + goto done; + } + 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(name, pwd, buffer, buflen, &ret); + + switch (status) { + case NSS_STATUS_NOTFOUND: + + DEBUG(7, ("User %s not found.\n", name)); + ret = delete_user(tmpctx, sysdb, dom, name); + if (ret) { + goto done; + } + break; + + case NSS_STATUS_SUCCESS: + + /* 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(2, ("User [%s] filtered out! (id out of range)\n", + name)); + ret = delete_user(tmpctx, sysdb, dom, name); + break; + } + + ret = sysdb_store_user(tmpctx, sysdb, dom, + pwd->pw_name, + pwd->pw_passwd, + pwd->pw_uid, + pwd->pw_gid, + pwd->pw_gecos, + pwd->pw_dir, + pwd->pw_shell, + NULL, ctx->entry_cache_timeout); + if (ret) { + goto done; + } + + ret = get_initgr_groups_process(tmpctx, ctx, sysdb, dom, pwd); + if (ret == EOK) { + ret = sysdb_transaction_commit(sysdb); + in_transaction = true; + } + break; + + case NSS_STATUS_UNAVAIL: + /* "remote" backend unavailable. Enter offline mode */ + ret = ENXIO; + break; + + default: + DEBUG(2, ("proxy -> getpwnam_r failed for '%s' <%d>\n", + name, status)); + ret = EIO; + break; + } + +done: + talloc_zfree(tmpctx); + if (in_transaction) { + sysdb_transaction_cancel(sysdb); + } + return ret; +} + +static int get_initgr_groups_process(TALLOC_CTX *memctx, + struct proxy_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; + + num_gids = 0; + limit = 4096; + num = 4096; + size = num*sizeof(gid_t); + gids = talloc_size(memctx, size); + if (!gids) { + return ENOMEM; + } + +again: + /* FIXME: should we move this call outside the transaction to keep the + * transaction as short as possible ? */ + status = ctx->ops.initgroups_dyn(pwd->pw_name, pwd->pw_gid, &num_gids, + &num, &gids, limit, &ret); + switch (status) { + case 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; + } + goto again; /* retry with more memory */ + + case NSS_STATUS_SUCCESS: + DEBUG(4, ("User [%s] appears to be member of %lu groups\n", + pwd->pw_name, num_gids)); + + for (i = 0; i < num_gids; i++) { + ret = get_gr_gid(memctx, ctx, sysdb, dom, gids[i]); + if (ret) { + return ret; + } + } + break; + + default: + DEBUG(2, ("proxy -> initgroups_dyn failed (%d)[%s]\n", + ret, strerror(ret))); + ret = EIO; + break; + } + + return ret; +} + +/* =Proxy_Id-Functions====================================================*/ + +static void proxy_get_account_info(struct be_req *breq) +{ + struct be_acct_req *ar; + struct proxy_ctx *ctx; + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + uid_t uid; + gid_t gid; + int ret; + + ar = talloc_get_type(breq->req_data, struct be_acct_req); + ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, struct proxy_ctx); + ev = breq->be_ctx->ev; + sysdb = breq->be_ctx->sysdb; + domain = breq->be_ctx->domain; + + if (be_is_offline(breq->be_ctx)) { + return proxy_reply(breq, DP_ERR_OFFLINE, EAGAIN, "Offline"); + } + + /* for now we support only core attrs */ + if (ar->attr_type != BE_ATTR_CORE) { + return proxy_reply(breq, DP_ERR_FATAL, EINVAL, "Invalid attr type"); + } + + switch (ar->entry_type & 0xFFF) { + case BE_REQ_USER: /* user */ + switch (ar->filter_type) { + case BE_FILTER_NAME: + if (strchr(ar->filter_value, '*')) { + ret = enum_users(breq, ctx, sysdb, domain); + } else { + ret = get_pw_name(breq, ctx, sysdb, domain, ar->filter_value); + } + break; + + case BE_FILTER_IDNUM: + if (strchr(ar->filter_value, '*')) { + return proxy_reply(breq, DP_ERR_FATAL, + EINVAL, "Invalid attr type"); + } else { + char *endptr; + errno = 0; + uid = (uid_t)strtol(ar->filter_value, &endptr, 0); + if (errno || *endptr || (ar->filter_value == endptr)) { + return proxy_reply(breq, DP_ERR_FATAL, + EINVAL, "Invalid attr type"); + } + ret = get_pw_uid(breq, ctx, sysdb, domain, uid); + } + break; + default: + return proxy_reply(breq, DP_ERR_FATAL, + EINVAL, "Invalid filter type"); + } + break; + + case BE_REQ_GROUP: /* group */ + switch (ar->filter_type) { + case BE_FILTER_NAME: + if (strchr(ar->filter_value, '*')) { + ret = enum_groups(breq, ctx, sysdb, domain); + } else { + ret = get_gr_name(breq, ctx, sysdb, domain, ar->filter_value); + } + break; + case BE_FILTER_IDNUM: + if (strchr(ar->filter_value, '*')) { + return proxy_reply(breq, DP_ERR_FATAL, + EINVAL, "Invalid attr type"); + } else { + char *endptr; + errno = 0; + gid = (gid_t)strtol(ar->filter_value, &endptr, 0); + if (errno || *endptr || (ar->filter_value == endptr)) { + return proxy_reply(breq, DP_ERR_FATAL, + EINVAL, "Invalid attr type"); + } + ret = get_gr_gid(breq, ctx, sysdb, domain, gid); + } + break; + default: + return proxy_reply(breq, DP_ERR_FATAL, + EINVAL, "Invalid filter type"); + } + break; + + case BE_REQ_INITGROUPS: /* init groups for user */ + if (ar->filter_type != BE_FILTER_NAME) { + return proxy_reply(breq, DP_ERR_FATAL, + EINVAL, "Invalid filter type"); + } + if (strchr(ar->filter_value, '*')) { + return proxy_reply(breq, DP_ERR_FATAL, + EINVAL, "Invalid filter value"); + } + if (ctx->ops.initgroups_dyn == NULL) { + return proxy_reply(breq, DP_ERR_FATAL, + ENODEV, "Initgroups call not supported"); + } + ret = get_initgr(breq, ctx, sysdb, domain, ar->filter_value); + break; + + default: /*fail*/ + return proxy_reply(breq, DP_ERR_FATAL, + EINVAL, "Invalid request type"); + } + + if (ret) { + if (ret == ENXIO) { + DEBUG(2, ("proxy returned UNAVAIL error, going offline!\n")); + be_mark_offline(breq->be_ctx); + } + proxy_reply(breq, DP_ERR_FATAL, ret, NULL); + return; + } + proxy_reply(breq, DP_ERR_OK, EOK, NULL); +} + +static void proxy_shutdown(struct be_req *req) +{ + /* TODO: Clean up any internal data */ + req->fn(req, DP_ERR_OK, EOK, NULL); +} + +static void proxy_auth_shutdown(struct be_req *req) +{ + talloc_free(req->be_ctx->bet_info[BET_AUTH].pvt_bet_data); + req->fn(req, DP_ERR_OK, EOK, NULL); +} + +struct bet_ops proxy_id_ops = { + .handler = proxy_get_account_info, + .finalize = proxy_shutdown +}; + +struct bet_ops proxy_auth_ops = { + .handler = proxy_pam_handler, + .finalize = proxy_auth_shutdown +}; + +struct bet_ops proxy_access_ops = { + .handler = proxy_pam_handler, + .finalize = proxy_auth_shutdown +}; + +struct bet_ops proxy_chpass_ops = { + .handler = proxy_pam_handler, + .finalize = proxy_auth_shutdown +}; + +static void *proxy_dlsym(void *handle, const char *functemp, char *libname) +{ + char *funcname; + void *funcptr; + + funcname = talloc_asprintf(NULL, functemp, libname); + if (funcname == NULL) return NULL; + + funcptr = dlsym(handle, funcname); + talloc_free(funcname); + + return funcptr; +} + +int sssm_proxy_id_init(struct be_ctx *bectx, + struct bet_ops **ops, void **pvt_data) +{ + struct proxy_ctx *ctx; + char *libname; + char *libpath; + void *handle; + int ret; + + ctx = talloc_zero(bectx, struct proxy_ctx); + if (!ctx) { + return ENOMEM; + } + ctx->be = bectx; + + ret = confdb_get_int(bectx->cdb, ctx, bectx->conf_path, + CONFDB_DOMAIN_ENTRY_CACHE_TIMEOUT, 600, + &ctx->entry_cache_timeout); + if (ret != EOK) goto done; + + ret = confdb_get_string(bectx->cdb, ctx, bectx->conf_path, + CONFDB_PROXY_LIBNAME, NULL, &libname); + if (ret != EOK) goto done; + if (libname == NULL) { + ret = ENOENT; + goto done; + } + + libpath = talloc_asprintf(ctx, "libnss_%s.so.2", libname); + if (!libpath) { + ret = ENOMEM; + goto done; + } + + handle = dlopen(libpath, RTLD_NOW); + if (!handle) { + DEBUG(0, ("Unable to load %s module with path, error: %s\n", + libpath, dlerror())); + ret = ELIBACC; + goto done; + } + + ctx->ops.getpwnam_r = proxy_dlsym(handle, "_nss_%s_getpwnam_r", libname); + if (!ctx->ops.getpwnam_r) { + DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); + ret = ELIBBAD; + goto done; + } + + ctx->ops.getpwuid_r = proxy_dlsym(handle, "_nss_%s_getpwuid_r", libname); + if (!ctx->ops.getpwuid_r) { + DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); + ret = ELIBBAD; + goto done; + } + + ctx->ops.setpwent = proxy_dlsym(handle, "_nss_%s_setpwent", libname); + if (!ctx->ops.setpwent) { + DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); + ret = ELIBBAD; + goto done; + } + + ctx->ops.getpwent_r = proxy_dlsym(handle, "_nss_%s_getpwent_r", libname); + if (!ctx->ops.getpwent_r) { + DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); + ret = ELIBBAD; + goto done; + } + + ctx->ops.endpwent = proxy_dlsym(handle, "_nss_%s_endpwent", libname); + if (!ctx->ops.endpwent) { + DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); + ret = ELIBBAD; + goto done; + } + + ctx->ops.getgrnam_r = proxy_dlsym(handle, "_nss_%s_getgrnam_r", libname); + if (!ctx->ops.getgrnam_r) { + DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); + ret = ELIBBAD; + goto done; + } + + ctx->ops.getgrgid_r = proxy_dlsym(handle, "_nss_%s_getgrgid_r", libname); + if (!ctx->ops.getgrgid_r) { + DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); + ret = ELIBBAD; + goto done; + } + + ctx->ops.setgrent = proxy_dlsym(handle, "_nss_%s_setgrent", libname); + if (!ctx->ops.setgrent) { + DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); + ret = ELIBBAD; + goto done; + } + + ctx->ops.getgrent_r = proxy_dlsym(handle, "_nss_%s_getgrent_r", libname); + if (!ctx->ops.getgrent_r) { + DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); + ret = ELIBBAD; + goto done; + } + + ctx->ops.endgrent = proxy_dlsym(handle, "_nss_%s_endgrent", libname); + if (!ctx->ops.endgrent) { + DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror())); + ret = ELIBBAD; + goto done; + } + + ctx->ops.initgroups_dyn = proxy_dlsym(handle, "_nss_%s_initgroups_dyn", + libname); + if (!ctx->ops.initgroups_dyn) { + DEBUG(1, ("The '%s' library does not provides the " + "_nss_XXX_initgroups_dyn function!\n" + "initgroups will be slow as it will require " + "full groups enumeration!\n", libname)); + } + + *ops = &proxy_id_ops; + *pvt_data = ctx; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(ctx); + } + return ret; +} + +struct proxy_client { + struct proxy_auth_ctx *proxy_auth_ctx; + struct sbus_connection *conn; + struct tevent_timer *timeout; + bool initialized; +}; + +static void init_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr); +static int proxy_client_init(struct sbus_connection *conn, void *data) +{ + struct proxy_auth_ctx *proxy_auth_ctx; + struct proxy_client *proxy_cli; + struct timeval tv; + + proxy_auth_ctx = talloc_get_type(data, struct proxy_auth_ctx); + + /* hang off this memory to the connection so that when the connection + * is freed we can potentially call a destructor */ + + proxy_cli = talloc_zero(conn, struct proxy_client); + if (!proxy_cli) { + DEBUG(0,("Out of memory?!\n")); + talloc_zfree(conn); + return ENOMEM; + } + proxy_cli->proxy_auth_ctx = proxy_auth_ctx; + proxy_cli->conn = conn; + proxy_cli->initialized = false; + + /* 5 seconds should be plenty */ + tv = tevent_timeval_current_ofs(5, 0); + + proxy_cli->timeout = tevent_add_timer(proxy_auth_ctx->be->ev, proxy_cli, + tv, init_timeout, proxy_cli); + if (!proxy_cli->timeout) { + DEBUG(0,("Out of memory?!\n")); + talloc_zfree(conn); + return ENOMEM; + } + DEBUG(4, ("Set-up proxy client ID timeout [%p]\n", proxy_cli->timeout)); + + /* Attach the client context to the connection context, so that it is + * always available when we need to manage the connection. */ + sbus_conn_set_private_data(conn, proxy_cli); + + return EOK; +} + +static void init_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr) +{ + struct proxy_client *proxy_cli; + + DEBUG(2, ("Client timed out before Identification [%p]!\n", te)); + + proxy_cli = talloc_get_type(ptr, struct proxy_client); + + sbus_disconnect(proxy_cli->conn); + talloc_zfree(proxy_cli); + + /* If we time out here, we will also time out to + * pc_init_timeout(), so we'll finish the request + * there. + */ +} + +static int client_registration(DBusMessage *message, + struct sbus_connection *conn) +{ + dbus_uint16_t version = DATA_PROVIDER_VERSION; + struct proxy_client *proxy_cli; + DBusMessage *reply; + DBusError dbus_error; + dbus_uint16_t cli_ver; + uint32_t cli_id; + dbus_bool_t dbret; + void *data; + int hret; + hash_key_t key; + hash_value_t value; + struct tevent_req *req; + struct proxy_child_ctx *child_ctx; + struct pc_init_ctx *init_ctx; + + data = sbus_conn_get_private_data(conn); + proxy_cli = talloc_get_type(data, struct proxy_client); + if (!proxy_cli) { + DEBUG(0, ("Connection holds no valid init data\n")); + return EINVAL; + } + + /* First thing, cancel the timeout */ + DEBUG(4, ("Cancel proxy client ID timeout [%p]\n", proxy_cli->timeout)); + talloc_zfree(proxy_cli->timeout); + + dbus_error_init(&dbus_error); + + dbret = dbus_message_get_args(message, &dbus_error, + DBUS_TYPE_UINT16, &cli_ver, + DBUS_TYPE_UINT32, &cli_id, + DBUS_TYPE_INVALID); + if (!dbret) { + DEBUG(1, ("Failed to parse message, killing connection\n")); + if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error); + sbus_disconnect(conn); + /* FIXME: should we just talloc_zfree(conn) ? */ + return EIO; + } + + DEBUG(4, ("Proxy client [%ld] connected\n", cli_id)); + + /* Check the hash table */ + key.type = HASH_KEY_ULONG; + key.ul = cli_id; + if (!hash_has_key(proxy_cli->proxy_auth_ctx->request_table, &key)) { + DEBUG(1, ("Unknown child ID. Killing the connection\n")); + sbus_disconnect(proxy_cli->conn); + return EIO; + } + + /* reply that all is ok */ + reply = dbus_message_new_method_return(message); + if (!reply) { + DEBUG(0, ("Dbus Out of memory!\n")); + return ENOMEM; + } + + dbret = dbus_message_append_args(reply, + DBUS_TYPE_UINT16, &version, + DBUS_TYPE_INVALID); + if (!dbret) { + DEBUG(0, ("Failed to build dbus reply\n")); + dbus_message_unref(reply); + sbus_disconnect(conn); + return EIO; + } + + /* send reply back */ + sbus_conn_send_reply(conn, reply); + dbus_message_unref(reply); + + hret = hash_lookup(proxy_cli->proxy_auth_ctx->request_table, &key, &value); + if (hret != HASH_SUCCESS) { + DEBUG(1, ("Hash error [%d][%s]\n", hret, hash_error_string(hret))); + sbus_disconnect(conn); + } + + /* Signal that the child is up and ready to receive the request */ + req = talloc_get_type(value.ptr, struct tevent_req); + child_ctx = tevent_req_data(req, struct proxy_child_ctx); + + if (!child_ctx->running) { + /* This should hopefully be impossible, but protect + * against it anyway. If we're not marked running, then + * the init_req will be NULL below and things will + * break. + */ + DEBUG(1, ("Client connection from a request " + "that's not marked as running\n")); + return EIO; + } + + init_ctx = tevent_req_data(child_ctx->init_req, struct pc_init_ctx); + init_ctx->conn = conn; + tevent_req_done(child_ctx->init_req); + child_ctx->init_req = NULL; + + return EOK; +} + +int sssm_proxy_auth_init(struct be_ctx *bectx, + struct bet_ops **ops, void **pvt_data) +{ + struct proxy_auth_ctx *ctx; + int ret; + int hret; + char *sbus_address; + + /* If we're already set up, just return that */ + if(bectx->bet_info[BET_AUTH].mod_name && + strcmp("proxy", bectx->bet_info[BET_AUTH].mod_name) == 0) { + DEBUG(8, ("Re-using proxy_auth_ctx for this provider\n")); + *ops = bectx->bet_info[BET_AUTH].bet_ops; + *pvt_data = bectx->bet_info[BET_AUTH].pvt_bet_data; + return EOK; + } + + ctx = talloc_zero(bectx, struct proxy_auth_ctx); + if (!ctx) { + return ENOMEM; + } + ctx->be = bectx; + ctx->timeout_ms = SSS_CLI_SOCKET_TIMEOUT/4; + ctx->next_id = 1; + + ret = confdb_get_string(bectx->cdb, ctx, bectx->conf_path, + CONFDB_PROXY_PAM_TARGET, NULL, + &ctx->pam_target); + if (ret != EOK) goto done; + if (!ctx->pam_target) { + DEBUG(1, ("Missing option proxy_pam_target.\n")); + ret = EINVAL; + goto done; + } + + sbus_address = talloc_asprintf(ctx, "unix:path=%s/%s_%s", PIPE_PATH, + PROXY_CHILD_PIPE, bectx->domain->name); + if (sbus_address == NULL) { + DEBUG(1, ("talloc_asprintf failed.\n")); + ret = ENOMEM; + goto done; + } + + ret = sbus_new_server(ctx, bectx->ev, sbus_address, &proxy_interface, + &ctx->sbus_srv, proxy_client_init, ctx); + if (ret != EOK) { + DEBUG(0, ("Could not set up sbus server.\n")); + goto done; + } + + /* Set up request hash table */ + /* FIXME: get max_children from configuration file */ + ctx->max_children = 10; + + hret = hash_create(ctx->max_children * 2, &ctx->request_table, + NULL, NULL); + if (hret != HASH_SUCCESS) { + DEBUG(0, ("Could not initialize request table\n")); + ret = EIO; + goto done; + } + + *ops = &proxy_auth_ops; + *pvt_data = ctx; + +done: + if (ret != EOK) { + talloc_free(ctx); + } + return ret; +} + +int sssm_proxy_access_init(struct be_ctx *bectx, + struct bet_ops **ops, void **pvt_data) +{ + int ret; + ret = sssm_proxy_auth_init(bectx, ops, pvt_data); + *ops = &proxy_access_ops; + return ret; +} + +int sssm_proxy_chpass_init(struct be_ctx *bectx, + struct bet_ops **ops, void **pvt_data) +{ + int ret; + ret = sssm_proxy_auth_init(bectx, ops, pvt_data); + *ops = &proxy_chpass_ops; + return ret; +} diff --git a/src/providers/proxy/proxy.h b/src/providers/proxy/proxy.h new file mode 100644 index 00000000..d95e50ef --- /dev/null +++ b/src/providers/proxy/proxy.h @@ -0,0 +1,30 @@ +/* + SSSD + + Proxy provider, private header file + + Authors: + Sumit Bose + + 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 . +*/ + +#ifndef __PROXY_H__ +#define __PROXY_H__ + +#define PROXY_CHILD_PIPE "private/proxy_child" + +#endif /* __PROXY_H__ */ diff --git a/src/providers/proxy/proxy_child.c b/src/providers/proxy/proxy_child.c new file mode 100644 index 00000000..c8f1eeb5 --- /dev/null +++ b/src/providers/proxy/proxy_child.c @@ -0,0 +1,507 @@ +/* + SSSD + + Pam Proxy Child + + Authors: + + Sumit Bose + + 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "popt.h" +#include "util/util.h" +#include "confdb/confdb.h" +#include "dbus/dbus.h" +#include "sbus/sssd_dbus.h" +#include "providers/proxy/proxy.h" + +#include "providers/dp_backend.h" + +static int pc_pam_handler(DBusMessage *message, struct sbus_connection *conn); + +struct sbus_method pc_methods[] = { + { DP_METHOD_PAMHANDLER, pc_pam_handler }, + { NULL, NULL } +}; + +struct sbus_interface pc_interface = { + DP_INTERFACE, + DP_PATH, + SBUS_DEFAULT_VTABLE, + pc_methods, + NULL +}; + +struct pc_ctx { + struct tevent_context *ev; + struct confdb_ctx *cdb; + struct sss_domain_info *domain; + const char *identity; + const char *conf_path; + struct sbus_connection *mon_conn; + struct sbus_connection *conn; + const char *pam_target; + uint32_t id; +}; + +struct authtok_conv { + uint32_t authtok_size; + uint8_t *authtok; +}; + +static int proxy_internal_conv(int num_msg, const struct pam_message **msgm, + struct pam_response **response, + void *appdata_ptr) { + int i; + struct pam_response *reply; + struct authtok_conv *auth_data; + + auth_data = talloc_get_type(appdata_ptr, struct authtok_conv); + + if (num_msg <= 0) return PAM_CONV_ERR; + + reply = (struct pam_response *) calloc(num_msg, + sizeof(struct pam_response)); + if (reply == NULL) return PAM_CONV_ERR; + + for (i=0; i < num_msg; i++) { + switch( msgm[i]->msg_style ) { + case PAM_PROMPT_ECHO_OFF: + DEBUG(4, ("Conversation message: [%s]\n", msgm[i]->msg)); + reply[i].resp_retcode = 0; + reply[i].resp = calloc(auth_data->authtok_size + 1, + sizeof(char)); + if (reply[i].resp == NULL) goto failed; + memcpy(reply[i].resp, auth_data->authtok, + auth_data->authtok_size); + + break; + default: + DEBUG(1, ("Conversation style %d not supported.\n", + msgm[i]->msg_style)); + goto failed; + } + } + + *response = reply; + reply = NULL; + + return PAM_SUCCESS; + +failed: + free(reply); + return PAM_CONV_ERR; +} + +static errno_t call_pam_stack(const char *pam_target, struct pam_data *pd) +{ + int ret; + int pam_status; + pam_handle_t *pamh=NULL; + struct authtok_conv *auth_data; + struct pam_conv conv; + + conv.conv=proxy_internal_conv; + auth_data = talloc_zero(pd, struct authtok_conv); + conv.appdata_ptr=auth_data; + + ret = pam_start(pam_target, pd->user, &conv, &pamh); + if (ret == PAM_SUCCESS) { + DEBUG(7, ("Pam transaction started with service name [%s].\n", + pam_target)); + ret = pam_set_item(pamh, PAM_TTY, pd->tty); + if (ret != PAM_SUCCESS) { + DEBUG(1, ("Setting PAM_TTY failed: %s.\n", + pam_strerror(pamh, ret))); + } + ret = pam_set_item(pamh, PAM_RUSER, pd->ruser); + if (ret != PAM_SUCCESS) { + DEBUG(1, ("Setting PAM_RUSER failed: %s.\n", + pam_strerror(pamh, ret))); + } + ret = pam_set_item(pamh, PAM_RHOST, pd->rhost); + if (ret != PAM_SUCCESS) { + DEBUG(1, ("Setting PAM_RHOST failed: %s.\n", + pam_strerror(pamh, ret))); + } + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + auth_data->authtok_size = pd->authtok_size; + auth_data->authtok = pd->authtok; + pam_status = pam_authenticate(pamh, 0); + break; + case SSS_PAM_SETCRED: + pam_status=pam_setcred(pamh, 0); + break; + case SSS_PAM_ACCT_MGMT: + pam_status=pam_acct_mgmt(pamh, 0); + break; + case SSS_PAM_OPEN_SESSION: + pam_status=pam_open_session(pamh, 0); + break; + case SSS_PAM_CLOSE_SESSION: + pam_status=pam_close_session(pamh, 0); + break; + case SSS_PAM_CHAUTHTOK: + if (pd->priv != 1) { + auth_data->authtok_size = pd->authtok_size; + auth_data->authtok = pd->authtok; + pam_status = pam_authenticate(pamh, 0); + if (pam_status != PAM_SUCCESS) break; + } + auth_data->authtok_size = pd->newauthtok_size; + auth_data->authtok = pd->newauthtok; + pam_status = pam_chauthtok(pamh, 0); + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + if (pd->priv != 1) { + auth_data->authtok_size = pd->authtok_size; + auth_data->authtok = pd->authtok; + pam_status = pam_authenticate(pamh, 0); + } else { + pam_status = PAM_SUCCESS; + } + break; + default: + DEBUG(1, ("unknown PAM call\n")); + pam_status=PAM_ABORT; + } + + DEBUG(4, ("Pam result: [%d][%s]\n", pam_status, + pam_strerror(pamh, pam_status))); + + ret = pam_end(pamh, pam_status); + if (ret != PAM_SUCCESS) { + pamh=NULL; + DEBUG(1, ("Cannot terminate pam transaction.\n")); + } + + } else { + DEBUG(1, ("Failed to initialize pam transaction.\n")); + pam_status = PAM_SYSTEM_ERR; + } + + pd->pam_status = pam_status; + + return EOK; +} + +static int pc_pam_handler(DBusMessage *message, struct sbus_connection *conn) +{ + DBusError dbus_error; + DBusMessage *reply; + struct pc_ctx *pc_ctx; + errno_t ret; + void *user_data; + struct pam_data *pd = NULL; + + user_data = sbus_conn_get_private_data(conn); + if (!user_data) { + ret = EINVAL; + goto done; + } + pc_ctx = talloc_get_type(user_data, struct pc_ctx); + if (!pc_ctx) { + ret = EINVAL; + goto done; + } + + reply = dbus_message_new_method_return(message); + if (!reply) { + DEBUG(1, ("dbus_message_new_method_return failed, " + "cannot send reply.\n")); + ret = ENOMEM; + goto done; + } + + dbus_error_init(&dbus_error); + + ret = dp_unpack_pam_request(message, pc_ctx, &pd, &dbus_error); + if (!ret) { + DEBUG(1,("Failed, to parse message!\n")); + ret = EIO; + goto done; + } + + pd->pam_status = PAM_SYSTEM_ERR; + pd->domain = talloc_strdup(pd, pc_ctx->domain->name); + if (pd->domain == NULL) { + talloc_free(pd); + ret = ENOMEM; + goto done; + } + + DEBUG(4, ("Got request with the following data\n")); + DEBUG_PAM_DATA(4, pd); + + ret = call_pam_stack(pc_ctx->pam_target, pd); + if (ret != EOK) { + DEBUG(1, ("call_pam_stack failed.\n")); + } + + DEBUG(4, ("Sending result [%d][%s]\n", + pd->pam_status, pd->domain)); + + ret = dp_pack_pam_response(reply, pd); + if (!ret) { + DEBUG(1, ("Failed to generate dbus reply\n")); + talloc_free(pd); + dbus_message_unref(reply); + ret = EIO; + goto done; + } + + sbus_conn_send_reply(conn, reply); + dbus_message_unref(reply); + talloc_free(pd); + + /* We'll return the message and let the + * parent process kill us. + */ + return EOK; + +done: + exit(ret); +} + +int proxy_child_send_id(struct sbus_connection *conn, + uint16_t version, + uint32_t id); +static int proxy_cli_init(struct pc_ctx *ctx) +{ + char *sbus_address; + int ret; + + sbus_address = talloc_asprintf(ctx, "unix:path=%s/%s_%s", + PIPE_PATH, PROXY_CHILD_PIPE, + ctx->domain->name); + if (sbus_address == NULL) { + DEBUG(1, ("talloc_asprintf failed.\n")); + return ENOMEM; + } + + ret = sbus_client_init(ctx, ctx->ev, sbus_address, + &pc_interface, &ctx->conn, + NULL, ctx); + if (ret != EOK) { + DEBUG(1, ("sbus_client_init failed.\n")); + return ret; + } + + ret = proxy_child_send_id(ctx->conn, DATA_PROVIDER_VERSION, ctx->id); + if (ret != EOK) { + DEBUG(0, ("dp_common_send_id failed.\n")); + return ret; + } + + return EOK; +} + +int proxy_child_send_id(struct sbus_connection *conn, + uint16_t version, + uint32_t id) +{ + DBusMessage *msg; + dbus_bool_t ret; + int retval; + + /* create the message */ + msg = dbus_message_new_method_call(NULL, + DP_PATH, + DP_INTERFACE, + DP_METHOD_REGISTER); + if (msg == NULL) { + DEBUG(0, ("Out of memory?!\n")); + return ENOMEM; + } + + DEBUG(4, ("Sending ID to Proxy Backend: (%d,%ld)\n", + version, id)); + + ret = dbus_message_append_args(msg, + DBUS_TYPE_UINT16, &version, + DBUS_TYPE_UINT32, &id, + DBUS_TYPE_INVALID); + if (!ret) { + DEBUG(1, ("Failed to build message\n")); + return EIO; + } + + retval = sbus_conn_send(conn, msg, 30000, dp_id_callback, NULL, NULL); + + dbus_message_unref(msg); + return retval; +} + +int proxy_child_process_init(TALLOC_CTX *mem_ctx, const char *domain, + struct tevent_context *ev, struct confdb_ctx *cdb, + const char *pam_target, uint32_t id) +{ + struct pc_ctx *ctx; + int ret; + + ctx = talloc_zero(mem_ctx, struct pc_ctx); + if (!ctx) { + DEBUG(0, ("fatal error initializing pc_ctx\n")); + return ENOMEM; + } + ctx->ev = ev; + ctx->cdb = cdb; + ctx->pam_target = talloc_steal(ctx, pam_target); + ctx->id = id; + ctx->conf_path = talloc_asprintf(ctx, CONFDB_DOMAIN_PATH_TMPL, domain); + if (!ctx->conf_path) { + DEBUG(0, ("Out of memory!?\n")); + return ENOMEM; + } + + ret = confdb_get_domain(cdb, domain, &ctx->domain); + if (ret != EOK) { + DEBUG(0, ("fatal error retrieving domain configuration\n")); + return ret; + } + + ret = proxy_cli_init(ctx); + if (ret != EOK) { + DEBUG(0, ("fatal error setting up server bus\n")); + return ret; + } + + return EOK; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *domain = NULL; + char *srv_name = NULL; + char *conf_entry = NULL; + struct main_context *main_ctx; + int ret; + long id; + char *pam_target = NULL; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + {"domain", 0, POPT_ARG_STRING, &domain, 0, + _("Domain of the information provider (mandatory)"), NULL }, + {"id", 0, POPT_ARG_LONG, &id, 0, + _("Child identifier (mandatory)"), NULL }, + POPT_TABLEEND + }; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + if (domain == NULL) { + fprintf(stderr, "\nMissing option, " + "--domain is a mandatory option.\n\n"); + poptPrintUsage(pc, stderr, 0); + return 1; + } + + if (id == 0) { + fprintf(stderr, "\nMissing option, " + "--id is a mandatory option.\n\n"); + poptPrintUsage(pc, stderr, 0); + return 1; + } + + poptFreeContext(pc); + + + /* set up things like debug , signals, daemonization, etc... */ + debug_log_file = talloc_asprintf(NULL, "proxy_child_%s", domain); + if (!debug_log_file) return 2; + + srv_name = talloc_asprintf(NULL, "sssd[proxy_child[%s]]", domain); + if (!srv_name) return 2; + + conf_entry = talloc_asprintf(NULL, CONFDB_DOMAIN_PATH_TMPL, domain); + if (!conf_entry) return 2; + + ret = server_setup(srv_name, 0, conf_entry, &main_ctx); + if (ret != EOK) { + DEBUG(0, ("Could not set up mainloop [%d]\n", ret)); + return 2; + } + + ret = unsetenv("_SSS_LOOPS"); + if (ret != EOK) { + DEBUG(1, ("Failed to unset _SSS_LOOPS, " + "pam modules might not work as expected.\n")); + } + + ret = confdb_get_string(main_ctx->confdb_ctx, main_ctx, conf_entry, + CONFDB_PROXY_PAM_TARGET, NULL, &pam_target); + if (ret != EOK) { + DEBUG(0, ("Error reading from confdb (%d) [%s]\n", + ret, strerror(ret))); + return 4; + } + if (pam_target == NULL) { + DEBUG(1, ("Missing option proxy_pam_target.\n")); + return 4; + } + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(2, ("Could not set up to exit when parent process does\n")); + } + + ret = proxy_child_process_init(main_ctx, domain, main_ctx->event_ctx, + main_ctx->confdb_ctx, pam_target, + (uint32_t)id); + if (ret != EOK) { + DEBUG(0, ("Could not initialize proxy child [%d].\n", ret)); + return 3; + } + + DEBUG(1, ("Proxy child for domain [%s] started!\n", domain)); + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} -- cgit