proxy.c revision 8f4aaae28c88c707853f8f28d8babc4efe0c1bf6
/*
SSSD
Proxy Module
Copyright (C) Simo Sorce <ssorce@redhat.com> 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 <http://www.gnu.org/licenses/>.
*/
#include <nss.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <dlfcn.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#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_pam_handler_cache_done(struct tevent_req *treq);
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) {
struct tevent_req *subreq;
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);
subreq = sysdb_cache_password_send(req, req->be_ctx->ev,
req->be_ctx->sysdb, NULL,
req->be_ctx->domain,
pd->user, password);
if (!subreq) {
/* password caching failures are not fatal errors */
return proxy_reply(req, DP_ERR_OK, EOK, NULL);
}
tevent_req_set_callback(subreq, proxy_pam_handler_cache_done, req);
}
proxy_reply(req, DP_ERR_OK, EOK, NULL);
}
static void proxy_pam_handler_cache_done(struct tevent_req *subreq)
{
struct be_req *req = tevent_req_callback_data(subreq, struct be_req);
int ret;
/* password caching failures are not fatal errors */
ret = sysdb_cache_password_recv(subreq);
talloc_zfree(subreq);
/* so we just log it any return */
if (ret) {
DEBUG(2, ("Failed to cache password (%d)[%s]!?\n",
ret, strerror(ret)));
}
return proxy_reply(req, DP_ERR_OK, EOK, NULL);
}
static void proxy_reply(struct be_req *req, int dp_err,
int error, const char *errstr)
{
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 */
struct proxy_state {
struct tevent_context *ev;
struct proxy_ctx *ctx;
struct sysdb_ctx *sysdb;
struct sss_domain_info *domain;
const char *name;
struct sysdb_handle *handle;
struct passwd *pwd;
struct group *grp;
uid_t uid;
gid_t gid;
};
static void proxy_default_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
int ret;
ret = sysdb_transaction_commit_recv(subreq);
talloc_zfree(subreq);
if (ret) {
tevent_req_error(req, ret);
return;
}
tevent_req_done(req);
}
static int proxy_default_recv(struct tevent_req *req)
{
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}
/* =Getpwnam-wrapper======================================================*/
static void get_pw_name_process(struct tevent_req *subreq);
static void get_pw_name_remove_done(struct tevent_req *subreq);
static void get_pw_name_add_done(struct tevent_req *subreq);
static struct tevent_req *get_pw_name_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct proxy_ctx *ctx,
struct sysdb_ctx *sysdb,
struct sss_domain_info *domain,
const char *name)
{
struct tevent_req *req, *subreq;
struct proxy_state *state;
req = tevent_req_create(mem_ctx, &state, struct proxy_state);
if (!req) return NULL;
memset(state, 0, sizeof(struct proxy_state));
state->ev = ev;
state->ctx = ctx;
state->sysdb = sysdb;
state->domain = domain;
state->name = name;
subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
if (!subreq) {
talloc_zfree(req);
return NULL;
}
tevent_req_set_callback(subreq, get_pw_name_process, req);
return req;
}
static void get_pw_name_process(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct proxy_state *state = tevent_req_data(req,
struct proxy_state);
struct proxy_ctx *ctx = state->ctx;
struct sss_domain_info *dom = ctx->be->domain;
enum nss_status status;
char *buffer;
size_t buflen;
bool delete_user = false;
int ret;
DEBUG(7, ("Searching user by name (%s)\n", state->name));
ret = sysdb_transaction_recv(subreq, state, &state->handle);
if (ret) {
tevent_req_error(req, ret);
return;
}
talloc_zfree(subreq);
state->pwd = talloc(state, struct passwd);
if (!state->pwd) {
tevent_req_error(req, ENOMEM);
return;
}
buflen = DEFAULT_BUFSIZE;
buffer = talloc_size(state, buflen);
if (!buffer) {
tevent_req_error(req, ENOMEM);
return;
}
/* FIXME: should we move this call outside the transaction to keep the
* transaction as short as possible ? */
status = ctx->ops.getpwnam_r(state->name, state->pwd,
buffer, buflen, &ret);
switch (status) {
case NSS_STATUS_NOTFOUND:
DEBUG(7, ("User %s not found.\n", state->name));
delete_user = true;
break;
case NSS_STATUS_SUCCESS:
DEBUG(7, ("User %s found: (%s, %d, %d)\n",
state->name, state->pwd->pw_name,
state->pwd->pw_uid, state->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(state->pwd->pw_uid, dom->id_min, dom->id_max) ||
OUT_OF_ID_RANGE(state->pwd->pw_gid, dom->id_min, dom->id_max)) {
DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
state->name));
delete_user = true;
break;
}
subreq = sysdb_store_user_send(state, state->ev, state->handle,
state->domain,
state->pwd->pw_name,
state->pwd->pw_passwd,
state->pwd->pw_uid,
state->pwd->pw_gid,
state->pwd->pw_gecos,
state->pwd->pw_dir,
state->pwd->pw_shell,
NULL, ctx->entry_cache_timeout);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, get_pw_name_add_done, req);
return;
case NSS_STATUS_UNAVAIL:
/* "remote" backend unavailable. Enter offline mode */
tevent_req_error(req, ENXIO);
return;
default:
DEBUG(2, ("proxy -> getpwnam_r failed for '%s' <%d>\n",
state->name, status));
tevent_req_error(req, EIO);
return;
}
if (delete_user) {
struct ldb_dn *dn;
DEBUG(7, ("User %s does not exist (or is invalid) on remote server,"
" deleting!\n", state->name));
dn = sysdb_user_dn(state->sysdb, state,
state->domain->name, state->name);
if (!dn) {
tevent_req_error(req, ENOMEM);
return;
}
subreq = sysdb_delete_entry_send(state, state->ev, state->handle, dn, true);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, get_pw_name_remove_done, req);
}
}
static void get_pw_name_add_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct proxy_state *state = tevent_req_data(req,
struct proxy_state);
int ret;
ret = sysdb_store_user_recv(subreq);
talloc_zfree(subreq);
if (ret) {
tevent_req_error(req, ret);
return;
}
subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, proxy_default_done, req);
}
static void get_pw_name_remove_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct proxy_state *state = tevent_req_data(req,
struct proxy_state);
int ret;
ret = sysdb_delete_entry_recv(subreq);
talloc_zfree(subreq);
if (ret) {
tevent_req_error(req, ret);
return;
}
subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, proxy_default_done, req);
}
/* =Getpwuid-wrapper======================================================*/
static void get_pw_uid_process(struct tevent_req *subreq);
static void get_pw_uid_remove_done(struct tevent_req *subreq);
static struct tevent_req *get_pw_uid_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct proxy_ctx *ctx,
struct sysdb_ctx *sysdb,
struct sss_domain_info *domain,
uid_t uid)
{
struct tevent_req *req, *subreq;
struct proxy_state *state;
req = tevent_req_create(mem_ctx, &state, struct proxy_state);
if (!req) return NULL;
memset(state, 0, sizeof(struct proxy_state));
state->ev = ev;
state->ctx = ctx;
state->sysdb = sysdb;
state->domain = domain;
state->uid = uid;
subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
if (!subreq) {
talloc_zfree(req);
return NULL;
}
tevent_req_set_callback(subreq, get_pw_uid_process, req);
return req;
}
static void get_pw_uid_process(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct proxy_state *state = tevent_req_data(req,
struct proxy_state);
struct proxy_ctx *ctx = state->ctx;
struct sss_domain_info *dom = ctx->be->domain;
enum nss_status status;
char *buffer;
size_t buflen;
bool delete_user = false;
int ret;
DEBUG(7, ("Searching user by uid (%d)\n", state->uid));
ret = sysdb_transaction_recv(subreq, state, &state->handle);
if (ret) {
tevent_req_error(req, ret);
return;
}
talloc_zfree(subreq);
state->pwd = talloc(state, struct passwd);
if (!state->pwd) {
tevent_req_error(req, ENOMEM);
return;
}
buflen = DEFAULT_BUFSIZE;
buffer = talloc_size(state, buflen);
if (!buffer) {
tevent_req_error(req, ENOMEM);
return;
}
/* always zero out the pwd structure */
memset(state->pwd, 0, sizeof(struct passwd));
status = ctx->ops.getpwuid_r(state->uid, state->pwd,
buffer, buflen, &ret);
switch (status) {
case NSS_STATUS_NOTFOUND:
DEBUG(7, ("User %d not found.\n", state->uid));
delete_user = true;
break;
case NSS_STATUS_SUCCESS:
DEBUG(7, ("User %d found (%s, %d, %d)\n",
state->uid, state->pwd->pw_name,
state->pwd->pw_uid, state->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(state->pwd->pw_uid, dom->id_min, dom->id_max) ||
OUT_OF_ID_RANGE(state->pwd->pw_gid, dom->id_min, dom->id_max)) {
DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
state->name));
delete_user = true;
break;
}
subreq = sysdb_store_user_send(state, state->ev, state->handle,
state->domain,
state->pwd->pw_name,
state->pwd->pw_passwd,
state->pwd->pw_uid,
state->pwd->pw_gid,
state->pwd->pw_gecos,
state->pwd->pw_dir,
state->pwd->pw_shell,
NULL, ctx->entry_cache_timeout);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, get_pw_name_add_done, req);
return;
case NSS_STATUS_UNAVAIL:
/* "remote" backend unavailable. Enter offline mode */
tevent_req_error(req, ENXIO);
return;
default:
DEBUG(2, ("proxy -> getpwnam_r failed for '%s' <%d>\n",
state->name, status));
tevent_req_error(req, EIO);
return;
}
if (delete_user) {
DEBUG(7, ("User %d does not exist (or is invalid) on remote server,"
" deleting!\n", state->uid));
subreq = sysdb_delete_user_send(state, state->ev,
NULL, state->handle,
state->domain,
NULL, state->uid);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, get_pw_uid_remove_done, req);
}
}
static void get_pw_uid_remove_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct proxy_state *state = tevent_req_data(req,
struct proxy_state);
int ret;
ret = sysdb_delete_user_recv(subreq);
talloc_zfree(subreq);
if (ret && ret != ENOENT) {
tevent_req_error(req, ret);
return;
}
subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, proxy_default_done, req);
}
/* =Getpwent-wrapper======================================================*/
struct enum_users_state {
struct tevent_context *ev;
struct proxy_ctx *ctx;
struct sysdb_ctx *sysdb;
struct sss_domain_info *domain;
struct sysdb_handle *handle;
struct passwd *pwd;
size_t buflen;
char *buffer;
bool in_transaction;
};
static void enum_users_process(struct tevent_req *subreq);
static struct tevent_req *enum_users_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct proxy_ctx *ctx,
struct sysdb_ctx *sysdb,
struct sss_domain_info *domain)
{
struct tevent_req *req, *subreq;
struct enum_users_state *state;
enum nss_status status;
DEBUG(7, ("Enumerating users\n"));
req = tevent_req_create(mem_ctx, &state, struct enum_users_state);
if (!req) return NULL;
state->ev = ev;
state->ctx = ctx;
state->sysdb = sysdb;
state->domain = domain;
state->handle = NULL;
state->pwd = talloc(state, struct passwd);
if (!state->pwd) {
tevent_req_error(req, ENOMEM);
goto fail;
}
state->buflen = DEFAULT_BUFSIZE;
state->buffer = talloc_size(state, state->buflen);
if (!state->buffer) {
tevent_req_error(req, ENOMEM);
goto fail;
}
state->in_transaction = false;
status = ctx->ops.setpwent();
if (status != NSS_STATUS_SUCCESS) {
tevent_req_error(req, EIO);
goto fail;
}
subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
if (!subreq) {
tevent_req_error(req, ENOMEM);
goto fail;
}
tevent_req_set_callback(subreq, enum_users_process, req);
return req;
fail:
tevent_req_post(req, ev);
return req;
}
static void enum_users_process(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct enum_users_state *state = tevent_req_data(req,
struct enum_users_state);
struct proxy_ctx *ctx = state->ctx;
struct sss_domain_info *dom = ctx->be->domain;
enum nss_status status;
char *newbuf;
int ret;
if (!state->in_transaction) {
ret = sysdb_transaction_recv(subreq, state, &state->handle);
if (ret) {
goto fail;
}
talloc_zfree(subreq);
state->in_transaction = true;
} else {
ret = sysdb_store_user_recv(subreq);
if (ret) {
/* Do not fail completely on errors.
* Just report the failure to save and go on */
DEBUG(2, ("Failed to store user. Ignoring.\n"));
}
talloc_zfree(subreq);
}
again:
/* always zero out the pwd structure */
memset(state->pwd, 0, sizeof(struct passwd));
/* get entry */
status = ctx->ops.getpwent_r(state->pwd,
state->buffer, state->buflen, &ret);
switch (status) {
case NSS_STATUS_TRYAGAIN:
/* buffer too small ? */
if (state->buflen < MAX_BUF_SIZE) {
state->buflen *= 2;
}
if (state->buflen > MAX_BUF_SIZE) {
state->buflen = MAX_BUF_SIZE;
}
newbuf = talloc_realloc_size(state, state->buffer, state->buflen);
if (!newbuf) {
ret = ENOMEM;
goto fail;
}
state->buffer = newbuf;
goto again;
case NSS_STATUS_NOTFOUND:
/* we are done here */
DEBUG(7, ("Enumeration completed.\n"));
ctx->ops.endpwent();
subreq = sysdb_transaction_commit_send(state, state->ev,
state->handle);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, proxy_default_done, req);
return;
case NSS_STATUS_SUCCESS:
DEBUG(7, ("User found (%s, %d, %d)\n", state->pwd->pw_name,
state->pwd->pw_uid, state->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(state->pwd->pw_uid, dom->id_min, dom->id_max) ||
OUT_OF_ID_RANGE(state->pwd->pw_gid, dom->id_min, dom->id_max)) {
DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
state->pwd->pw_name));
goto again; /* skip */
}
subreq = sysdb_store_user_send(state, state->ev, state->handle,
state->domain,
state->pwd->pw_name,
state->pwd->pw_passwd,
state->pwd->pw_uid,
state->pwd->pw_gid,
state->pwd->pw_gecos,
state->pwd->pw_dir,
state->pwd->pw_shell,
NULL, ctx->entry_cache_timeout);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, enum_users_process, req);
return;
case NSS_STATUS_UNAVAIL:
/* "remote" backend unavailable. Enter offline mode */
ret = ENXIO;
goto fail;
default:
DEBUG(2, ("proxy -> getpwent_r failed (%d)[%s]\n",
ret, strerror(ret)));
goto fail;
}
fail:
ctx->ops.endpwent();
tevent_req_error(req, ret);
}
/* =Getgrnam-wrapper======================================================*/
#define DEBUG_GR_MEM(level, state) \
do { \
if (debug_level >= level) { \
if (!state->grp->gr_mem || !state->grp->gr_mem[0]) { \
DEBUG(level, ("Group %s has no members!\n", \
state->grp->gr_name)); \
} else { \
int i = 0; \
while (state->grp->gr_mem[i]) { \
/* count */ \
i++; \
} \
DEBUG(level, ("Group %s has %d members!\n", \
state->grp->gr_name, i)); \
} \
} \
} while(0)
static void get_gr_name_process(struct tevent_req *subreq);
static void get_gr_name_remove_done(struct tevent_req *subreq);
static void get_gr_name_add_done(struct tevent_req *subreq);
static struct tevent_req *get_gr_name_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct proxy_ctx *ctx,
struct sysdb_ctx *sysdb,
struct sss_domain_info *domain,
const char *name)
{
struct tevent_req *req, *subreq;
struct proxy_state *state;
req = tevent_req_create(mem_ctx, &state, struct proxy_state);
if (!req) return NULL;
memset(state, 0, sizeof(struct proxy_state));
state->ev = ev;
state->ctx = ctx;
state->sysdb = sysdb;
state->domain = domain;
state->name = name;
subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
if (!subreq) {
talloc_zfree(req);
return NULL;
}
tevent_req_set_callback(subreq, get_gr_name_process, req);
return req;
}
static void get_gr_name_process(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct proxy_state *state = tevent_req_data(req,
struct proxy_state);
struct proxy_ctx *ctx = state->ctx;
struct sss_domain_info *dom = ctx->be->domain;
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", state->name));
ret = sysdb_transaction_recv(subreq, state, &state->handle);
if (ret) {
tevent_req_error(req, ret);
return;
}
talloc_zfree(subreq);
state->grp = talloc(state, struct group);
if (!state->grp) {
tevent_req_error(req, ENOMEM);
return;
}
buflen = DEFAULT_BUFSIZE;
buffer = talloc_size(state, buflen);
if (!buffer) {
tevent_req_error(req, ENOMEM);
return;
}
/* 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(state->grp, 0, sizeof(struct group));
status = ctx->ops.getgrnam_r(state->name, state->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(state, buffer, buflen);
if (!newbuf) {
tevent_req_error(req, ENOMEM);
return;
}
buffer = newbuf;
goto again;
case NSS_STATUS_NOTFOUND:
DEBUG(7, ("Group %s not found.\n", state->name));
delete_group = true;
break;
case NSS_STATUS_SUCCESS:
DEBUG(7, ("Group %s found: (%s, %d)\n", state->name,
state->grp->gr_name, state->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(state->grp->gr_gid, dom->id_min, dom->id_max)) {
DEBUG(2, ("Group [%s] filtered out! (id out of range)\n",
state->name));
delete_group = true;
break;
}
DEBUG_GR_MEM(7, state);
if (state->grp->gr_mem && state->grp->gr_mem[0]) {
members = sysdb_new_attrs(state);
if (!members) {
tevent_req_error(req, ENOMEM);
return;
}
ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER,
state->domain->name,
(const char **)state->grp->gr_mem);
if (ret) {
tevent_req_error(req, ret);
return;
}
} else {
members = NULL;
}
subreq = sysdb_store_group_send(state, state->ev, state->handle,
state->domain,
state->grp->gr_name,
state->grp->gr_gid,
members,
ctx->entry_cache_timeout);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, get_gr_name_add_done, req);
return;
case NSS_STATUS_UNAVAIL:
/* "remote" backend unavailable. Enter offline mode */
tevent_req_error(req, ENXIO);
return;
default:
DEBUG(2, ("proxy -> getgrnam_r failed for '%s' <%d>\n",
state->name, status));
tevent_req_error(req, EIO);
return;
}
if (delete_group) {
struct ldb_dn *dn;
DEBUG(7, ("Group %s does not exist (or is invalid) on remote server,"
" deleting!\n", state->name));
dn = sysdb_group_dn(state->sysdb, state,
state->domain->name, state->name);
if (!dn) {
tevent_req_error(req, ENOMEM);
return;
}
subreq = sysdb_delete_entry_send(state, state->ev, state->handle, dn, true);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, get_gr_name_remove_done, req);
}
}
static void get_gr_name_add_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct proxy_state *state = tevent_req_data(req,
struct proxy_state);
int ret;
ret = sysdb_store_group_recv(subreq);
talloc_zfree(subreq);
if (ret) {
tevent_req_error(req, ret);
return;
}
subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, proxy_default_done, req);
}
static void get_gr_name_remove_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct proxy_state *state = tevent_req_data(req,
struct proxy_state);
int ret;
ret = sysdb_delete_entry_recv(subreq);
talloc_zfree(subreq);
if (ret) {
tevent_req_error(req, ret);
return;
}
subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, proxy_default_done, req);
}
/* =Getgrgid-wrapper======================================================*/
static void get_gr_gid_process(struct tevent_req *subreq);
static void get_gr_gid_remove_done(struct tevent_req *subreq);
static struct tevent_req *get_gr_gid_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct proxy_ctx *ctx,
struct sysdb_ctx *sysdb,
struct sss_domain_info *domain,
gid_t gid)
{
struct tevent_req *req, *subreq;
struct proxy_state *state;
req = tevent_req_create(mem_ctx, &state, struct proxy_state);
if (!req) return NULL;
memset(state, 0, sizeof(struct proxy_state));
state->ev = ev;
state->ctx = ctx;
state->sysdb = sysdb;
state->domain = domain;
state->gid = gid;
subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
if (!subreq) {
talloc_zfree(req);
return NULL;
}
tevent_req_set_callback(subreq, get_gr_gid_process, req);
return req;
}
static void get_gr_gid_process(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct proxy_state *state = tevent_req_data(req,
struct proxy_state);
struct proxy_ctx *ctx = state->ctx;
struct sss_domain_info *dom = ctx->be->domain;
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", state->gid));
ret = sysdb_transaction_recv(subreq, state, &state->handle);
if (ret) {
tevent_req_error(req, ret);
return;
}
talloc_zfree(subreq);
state->grp = talloc(state, struct group);
if (!state->grp) {
tevent_req_error(req, ENOMEM);
return;
}
buflen = DEFAULT_BUFSIZE;
buffer = talloc_size(state, buflen);
if (!buffer) {
tevent_req_error(req, ENOMEM);
return;
}
again:
/* always zero out the group structure */
memset(state->grp, 0, sizeof(struct group));
status = ctx->ops.getgrgid_r(state->gid, state->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(state, buffer, buflen);
if (!newbuf) {
tevent_req_error(req, ENOMEM);
return;
}
buffer = newbuf;
goto again;
case NSS_STATUS_NOTFOUND:
DEBUG(7, ("Group %d not found.\n", state->gid));
delete_group = true;
break;
case NSS_STATUS_SUCCESS:
DEBUG(7, ("Group %d found (%s, %d)\n", state->gid,
state->grp->gr_name, state->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(state->grp->gr_gid, dom->id_min, dom->id_max)) {
DEBUG(2, ("Group [%s] filtered out! (id out of range)\n",
state->grp->gr_name));
delete_group = true;
break;
}
DEBUG_GR_MEM(7, state);
if (state->grp->gr_mem && state->grp->gr_mem[0]) {
members = sysdb_new_attrs(state);
if (!members) {
tevent_req_error(req, ENOMEM);
return;
}
ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER,
state->domain->name,
(const char **)state->grp->gr_mem);
if (ret) {
tevent_req_error(req, ret);
return;
}
} else {
members = NULL;
}
subreq = sysdb_store_group_send(state, state->ev, state->handle,
state->domain,
state->grp->gr_name,
state->grp->gr_gid,
members,
ctx->entry_cache_timeout);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, get_gr_name_add_done, req);
return;
case NSS_STATUS_UNAVAIL:
/* "remote" backend unavailable. Enter offline mode */
tevent_req_error(req, ENXIO);
return;
default:
DEBUG(2, ("proxy -> getgrgid_r failed for '%d' <%d>\n",
state->gid, status));
tevent_req_error(req, EIO);
return;
}
if (delete_group) {
DEBUG(7, ("Group %d does not exist (or is invalid) on remote server,"
" deleting!\n", state->gid));
subreq = sysdb_delete_group_send(state, state->ev,
NULL, state->handle,
state->domain,
NULL, state->gid);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, get_gr_gid_remove_done, req);
}
}
static void get_gr_gid_remove_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct proxy_state *state = tevent_req_data(req,
struct proxy_state);
int ret;
ret = sysdb_delete_group_recv(subreq);
talloc_zfree(subreq);
if (ret && ret != ENOENT) {
tevent_req_error(req, ret);
return;
}
subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, proxy_default_done, req);
}
/* =Getgrent-wrapper======================================================*/
struct enum_groups_state {
struct tevent_context *ev;
struct proxy_ctx *ctx;
struct sysdb_ctx *sysdb;
struct sss_domain_info *domain;
struct sysdb_handle *handle;
struct group *grp;
size_t buflen;
char *buffer;
bool in_transaction;
};
static void enum_groups_process(struct tevent_req *subreq);
static struct tevent_req *enum_groups_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct proxy_ctx *ctx,
struct sysdb_ctx *sysdb,
struct sss_domain_info *domain)
{
struct tevent_req *req, *subreq;
struct enum_groups_state *state;
enum nss_status status;
DEBUG(7, ("Enumerating groups\n"));
req = tevent_req_create(mem_ctx, &state, struct enum_groups_state);
if (!req) return NULL;
state->ev = ev;
state->ctx = ctx;
state->sysdb = sysdb;
state->domain = domain;
state->handle = NULL;
state->grp = talloc(state, struct group);
if (!state->grp) {
tevent_req_error(req, ENOMEM);
goto fail;
}
state->buflen = DEFAULT_BUFSIZE;
state->buffer = talloc_size(state, state->buflen);
if (!state->buffer) {
tevent_req_error(req, ENOMEM);
goto fail;
}
state->in_transaction = false;
status = ctx->ops.setgrent();
if (status != NSS_STATUS_SUCCESS) {
tevent_req_error(req, EIO);
goto fail;
}
subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
if (!subreq) {
tevent_req_error(req, ENOMEM);
goto fail;
}
tevent_req_set_callback(subreq, enum_groups_process, req);
return req;
fail:
tevent_req_post(req, ev);
return req;
}
static void enum_groups_process(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct enum_groups_state *state = tevent_req_data(req,
struct enum_groups_state);
struct proxy_ctx *ctx = state->ctx;
struct sss_domain_info *dom = ctx->be->domain;
enum nss_status status;
struct sysdb_attrs *members;
char *newbuf;
int ret;
if (!state->in_transaction) {
ret = sysdb_transaction_recv(subreq, state, &state->handle);
if (ret) {
tevent_req_error(req, ret);
return;
}
talloc_zfree(subreq);
state->in_transaction = true;
} else {
ret = sysdb_store_group_recv(subreq);
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"));
}
talloc_zfree(subreq);
}
again:
/* always zero out the grp structure */
memset(state->grp, 0, sizeof(struct group));
/* get entry */
status = ctx->ops.getgrent_r(state->grp,
state->buffer, state->buflen, &ret);
switch (status) {
case NSS_STATUS_TRYAGAIN:
/* buffer too small ? */
if (state->buflen < MAX_BUF_SIZE) {
state->buflen *= 2;
}
if (state->buflen > MAX_BUF_SIZE) {
state->buflen = MAX_BUF_SIZE;
}
newbuf = talloc_realloc_size(state, state->buffer, state->buflen);
if (!newbuf) {
ret = ENOMEM;
goto fail;
}
state->buffer = newbuf;
goto again;
case NSS_STATUS_NOTFOUND:
/* we are done here */
DEBUG(7, ("Enumeration completed.\n"));
ctx->ops.endgrent();
subreq = sysdb_transaction_commit_send(state, state->ev,
state->handle);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, proxy_default_done, req);
return;
case NSS_STATUS_SUCCESS:
DEBUG(7, ("Group found (%s, %d)\n",
state->grp->gr_name, state->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(state->grp->gr_gid, dom->id_min, dom->id_max)) {
DEBUG(2, ("Group [%s] filtered out! (id out of range)\n",
state->grp->gr_name));
goto again; /* skip */
}
DEBUG_GR_MEM(7, state);
if (state->grp->gr_mem && state->grp->gr_mem[0]) {
members = sysdb_new_attrs(state);
if (!members) {
tevent_req_error(req, ENOMEM);
return;
}
ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER,
state->domain->name,
(const char **)state->grp->gr_mem);
if (ret) {
tevent_req_error(req, ret);
return;
}
} else {
members = NULL;
}
subreq = sysdb_store_group_send(state, state->ev, state->handle,
state->domain,
state->grp->gr_name,
state->grp->gr_gid,
members,
ctx->entry_cache_timeout);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, enum_groups_process, req);
return;
case NSS_STATUS_UNAVAIL:
/* "remote" backend unavailable. Enter offline mode */
ret = ENXIO;
goto fail;
default:
DEBUG(2, ("proxy -> getgrent_r failed (%d)[%s]\n",
ret, strerror(ret)));
goto fail;
}
fail:
ctx->ops.endgrent();
tevent_req_error(req, ret);
}
/* =Initgroups-wrapper====================================================*/
static void get_initgr_process(struct tevent_req *subreq);
static void get_initgr_groups_process(struct tevent_req *subreq);
static void get_initgr_groups_done(struct tevent_req *subreq);
static struct tevent_req *get_groups_by_gid_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct sysdb_handle *handle,
struct proxy_ctx *ctx,
struct sss_domain_info *domain,
gid_t *gids, int num_gids);
static int get_groups_by_gid_recv(struct tevent_req *req);
static void get_groups_by_gid_process(struct tevent_req *subreq);
static struct tevent_req *get_group_from_gid_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct sysdb_handle *handle,
struct proxy_ctx *ctx,
struct sss_domain_info *domain,
gid_t gid);
static int get_group_from_gid_recv(struct tevent_req *req);
static void get_group_from_gid_send_del_done(struct tevent_req *subreq);
static void get_group_from_gid_send_add_done(struct tevent_req *subreq);
static struct tevent_req *get_initgr_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct proxy_ctx *ctx,
struct sysdb_ctx *sysdb,
struct sss_domain_info *domain,
const char *name)
{
struct tevent_req *req, *subreq;
struct proxy_state *state;
req = tevent_req_create(mem_ctx, &state, struct proxy_state);
if (!req) return NULL;
memset(state, 0, sizeof(struct proxy_state));
state->ev = ev;
state->ctx = ctx;
state->sysdb = sysdb;
state->domain = domain;
state->name = name;
subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
if (!subreq) {
talloc_zfree(req);
return NULL;
}
tevent_req_set_callback(subreq, get_initgr_process, req);
return req;
}
static void get_initgr_process(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct proxy_state *state = tevent_req_data(req,
struct proxy_state);
struct proxy_ctx *ctx = state->ctx;
struct sss_domain_info *dom = ctx->be->domain;
enum nss_status status;
char *buffer;
size_t buflen;
bool delete_user = false;
int ret;
ret = sysdb_transaction_recv(subreq, state, &state->handle);
if (ret) {
tevent_req_error(req, ret);
return;
}
talloc_zfree(subreq);
state->pwd = talloc(state, struct passwd);
if (!state->pwd) {
tevent_req_error(req, ENOMEM);
return;
}
buflen = DEFAULT_BUFSIZE;
buffer = talloc_size(state, buflen);
if (!buffer) {
tevent_req_error(req, ENOMEM);
return;
}
/* FIXME: should we move this call outside the transaction to keep the
* transaction as short as possible ? */
status = ctx->ops.getpwnam_r(state->name, state->pwd,
buffer, buflen, &ret);
switch (status) {
case NSS_STATUS_NOTFOUND:
delete_user = true;
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(state->pwd->pw_uid, dom->id_min, dom->id_max) ||
OUT_OF_ID_RANGE(state->pwd->pw_gid, dom->id_min, dom->id_max)) {
DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
state->name));
delete_user = true;
break;
}
subreq = sysdb_store_user_send(state, state->ev, state->handle,
state->domain,
state->pwd->pw_name,
state->pwd->pw_passwd,
state->pwd->pw_uid,
state->pwd->pw_gid,
state->pwd->pw_gecos,
state->pwd->pw_dir,
state->pwd->pw_shell,
NULL, ctx->entry_cache_timeout);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, get_initgr_groups_process, req);
return;
case NSS_STATUS_UNAVAIL:
/* "remote" backend unavailable. Enter offline mode */
tevent_req_error(req, ENXIO);
return;
default:
DEBUG(2, ("proxy -> getpwnam_r failed for '%s' <%d>\n",
state->name, status));
tevent_req_error(req, EIO);
return;
}
if (delete_user) {
struct ldb_dn *dn;
dn = sysdb_user_dn(state->sysdb, state,
state->domain->name, state->name);
if (!dn) {
tevent_req_error(req, ENOMEM);
return;
}
subreq = sysdb_delete_entry_send(state, state->ev, state->handle, dn, true);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, get_pw_name_remove_done, req);
}
}
static void get_initgr_groups_process(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct proxy_state *state = tevent_req_data(req,
struct proxy_state);
struct proxy_ctx *ctx = state->ctx;
enum nss_status status;
long int limit;
long int size;
long int num;
long int num_gids;
gid_t *gids;
int ret;
ret = sysdb_store_user_recv(subreq);
if (ret) {
tevent_req_error(req, ret);
return;
}
talloc_zfree(subreq);
num_gids = 0;
limit = 4096;
num = 4096;
size = num*sizeof(gid_t);
gids = talloc_size(state, size);
if (!gids) {
tevent_req_error(req, ENOMEM);
return;
}
state->gid = state->pwd->pw_gid;
again:
/* FIXME: should we move this call outside the transaction to keep the
* transaction as short as possible ? */
status = ctx->ops.initgroups_dyn(state->name, state->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(state, gids, size);
if (!gids) {
tevent_req_error(req, ENOMEM);
return;
}
goto again; /* retry with more memory */
case NSS_STATUS_SUCCESS:
DEBUG(4, ("User [%s] appears to be member of %lu groups\n",
state->name, num_gids));
subreq = get_groups_by_gid_send(state, state->ev, state->handle,
state->ctx, state->domain,
gids, num_gids);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, get_initgr_groups_done, req);
break;
default:
DEBUG(2, ("proxy -> initgroups_dyn failed (%d)[%s]\n",
ret, strerror(ret)));
tevent_req_error(req, EIO);
return;
}
}
static void get_initgr_groups_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct proxy_state *state = tevent_req_data(req,
struct proxy_state);
int ret;
ret = get_groups_by_gid_recv(subreq);
talloc_zfree(subreq);
if (ret) {
tevent_req_error(req, ret);
return;
}
subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, proxy_default_done, req);
}
struct get_groups_state {
struct tevent_context *ev;
struct sysdb_handle *handle;
struct proxy_ctx *ctx;
struct sss_domain_info *domain;
gid_t *gids;
int num_gids;
int cur_gid;
};
static struct tevent_req *get_groups_by_gid_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct sysdb_handle *handle,
struct proxy_ctx *ctx,
struct sss_domain_info *domain,
gid_t *gids, int num_gids)
{
struct tevent_req *req, *subreq;
struct get_groups_state *state;
req = tevent_req_create(mem_ctx, &state, struct get_groups_state);
if (!req) return NULL;
state->ev = ev;
state->handle = handle;
state->ctx = ctx;
state->domain = domain;
state->gids = gids;
state->num_gids = num_gids;
state->cur_gid = 0;
subreq = get_group_from_gid_send(state, ev, handle, ctx, domain, gids[0]);
if (!subreq) {
talloc_zfree(req);
return NULL;
}
tevent_req_set_callback(subreq, get_groups_by_gid_process, req);
return req;
}
static void get_groups_by_gid_process(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct get_groups_state *state = tevent_req_data(req,
struct get_groups_state);
int ret;
ret = get_group_from_gid_recv(subreq);
talloc_zfree(subreq);
if (ret) {
tevent_req_error(req, ret);
return;
}
state->cur_gid++;
if (state->cur_gid >= state->num_gids) {
tevent_req_done(req);
return;
}
subreq = get_group_from_gid_send(state,
state->ev, state->handle,
state->ctx, state->domain,
state->gids[state->cur_gid]);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, get_groups_by_gid_process, req);
}
static int get_groups_by_gid_recv(struct tevent_req *req)
{
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}
static struct tevent_req *get_group_from_gid_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct sysdb_handle *handle,
struct proxy_ctx *ctx,
struct sss_domain_info *domain,
gid_t gid)
{
struct tevent_req *req, *subreq;
struct proxy_state *state;
struct sss_domain_info *dom = ctx->be->domain;
enum nss_status status;
char *buffer;
char *newbuf;
size_t buflen;
bool delete_group = false;
struct sysdb_attrs *members;
int ret;
req = tevent_req_create(mem_ctx, &state, struct proxy_state);
if (!req) return NULL;
memset(state, 0, sizeof(struct proxy_state));
state->ev = ev;
state->handle = handle;
state->ctx = ctx;
state->domain = domain;
state->gid = gid;
state->grp = talloc(state, struct group);
if (!state->grp) {
ret = ENOMEM;
goto fail;
}
buflen = DEFAULT_BUFSIZE;
buffer = talloc_size(state, buflen);
if (!buffer) {
ret = ENOMEM;
goto fail;
}
again:
/* always zero out the grp structure */
memset(state->grp, 0, sizeof(struct group));
status = ctx->ops.getgrgid_r(state->gid, state->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(state, buffer, buflen);
if (!newbuf) {
ret = ENOMEM;
goto fail;
}
buffer = newbuf;
goto again;
case NSS_STATUS_NOTFOUND:
delete_group = true;
break;
case NSS_STATUS_SUCCESS:
/* gid=0 is an invalid value */
/* also check that the id is in the valid range for this domain */
if (OUT_OF_ID_RANGE(state->grp->gr_gid, dom->id_min, dom->id_max)) {
DEBUG(2, ("Group [%s] filtered out! (id out of range)\n",
state->grp->gr_name));
delete_group = true;
break;
}
if (state->grp->gr_mem && state->grp->gr_mem[0]) {
members = sysdb_new_attrs(state);
if (!members) {
ret = ENOMEM;
goto fail;
}
ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER,
state->domain->name,
(const char **)state->grp->gr_mem);
if (ret) {
goto fail;
}
} else {
members = NULL;
}
subreq = sysdb_store_group_send(state, state->ev, state->handle,
state->domain,
state->grp->gr_name,
state->grp->gr_gid,
members,
ctx->entry_cache_timeout);
if (!subreq) {
ret = ENOMEM;
goto fail;
}
tevent_req_set_callback(subreq, get_group_from_gid_send_add_done, req);
break;
case NSS_STATUS_UNAVAIL:
/* "remote" backend unavailable. Enter offline mode */
ret = ENXIO;
goto fail;
default:
DEBUG(2, ("proxy -> getgrgid_r failed for '%d' <%d>\n",
state->gid, status));
ret = EIO;
goto fail;
}
if (delete_group) {
subreq = sysdb_delete_group_send(state, state->ev,
NULL, state->handle,
state->domain,
NULL, state->gid);
if (!subreq) {
ret = ENOMEM;
goto fail;
}
tevent_req_set_callback(subreq, get_group_from_gid_send_del_done, req);
}
return req;
fail:
tevent_req_error(req, ret);
tevent_req_post(req, ev);
return req;
}
static void get_group_from_gid_send_add_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
int ret;
ret = sysdb_store_group_recv(subreq);
talloc_zfree(subreq);
if (ret) {
tevent_req_error(req, ret);
return;
}
tevent_req_done(req);
}
static void get_group_from_gid_send_del_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
int ret;
ret = sysdb_delete_group_recv(subreq);
talloc_zfree(subreq);
if (ret && ret != ENOENT) {
tevent_req_error(req, ret);
return;
}
tevent_req_done(req);
}
static int get_group_from_gid_recv(struct tevent_req *req)
{
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}
/* =Proxy_Id-Functions====================================================*/
static void proxy_get_account_info_done(struct tevent_req *subreq);
/* TODO: See if we can use async_req code */
static void proxy_get_account_info(struct be_req *breq)
{
struct tevent_req *subreq;
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;
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, '*')) {
subreq = enum_users_send(breq, ev, ctx,
sysdb, domain);
if (!subreq) {
return proxy_reply(breq, DP_ERR_FATAL,
ENOMEM, "Out of memory");
}
tevent_req_set_callback(subreq,
proxy_get_account_info_done, breq);
return;
} else {
subreq = get_pw_name_send(breq, ev, ctx,
sysdb, domain,
ar->filter_value);
if (!subreq) {
return proxy_reply(breq, DP_ERR_FATAL,
ENOMEM, "Out of memory");
}
tevent_req_set_callback(subreq,
proxy_get_account_info_done, breq);
return;
}
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");
}
subreq = get_pw_uid_send(breq, ev, ctx,
sysdb, domain, uid);
if (!subreq) {
return proxy_reply(breq, DP_ERR_FATAL,
ENOMEM, "Out of memory");
}
tevent_req_set_callback(subreq,
proxy_get_account_info_done, breq);
return;
}
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, '*')) {
subreq = enum_groups_send(breq, ev, ctx,
sysdb, domain);
if (!subreq) {
return proxy_reply(breq, DP_ERR_FATAL,
ENOMEM, "Out of memory");
}
tevent_req_set_callback(subreq,
proxy_get_account_info_done, breq);
return;
} else {
subreq = get_gr_name_send(breq, ev, ctx,
sysdb, domain,
ar->filter_value);
if (!subreq) {
return proxy_reply(breq, DP_ERR_FATAL,
ENOMEM, "Out of memory");
}
tevent_req_set_callback(subreq,
proxy_get_account_info_done, breq);
return;
}
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");
}
subreq = get_gr_gid_send(breq, ev, ctx,
sysdb, domain, gid);
if (!subreq) {
return proxy_reply(breq, DP_ERR_FATAL,
ENOMEM, "Out of memory");
}
tevent_req_set_callback(subreq,
proxy_get_account_info_done, breq);
return;
}
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");
}
subreq = get_initgr_send(breq, ev, ctx, sysdb,
domain, ar->filter_value);
if (!subreq) {
return proxy_reply(breq, DP_ERR_FATAL,
ENOMEM, "Out of memory");
}
tevent_req_set_callback(subreq,
proxy_get_account_info_done, breq);
return;
default: /*fail*/
break;
}
return proxy_reply(breq, DP_ERR_FATAL,
EINVAL, "Invalid request type");
}
static void proxy_get_account_info_done(struct tevent_req *subreq)
{
struct be_req *breq = tevent_req_callback_data(subreq,
struct be_req);
int ret;
ret = proxy_default_recv(subreq);
talloc_zfree(subreq);
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_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;
}