nsssrv_dp.c revision 57df88bb0b4ce656855410a8c2969d93475c2f11
/*
SSSD
NSS Responder - Data Provider Interfaces
Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
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 <sys/time.h>
#include <time.h>
#include "util/util.h"
#include "nss/nsssrv.h"
#include "providers/data_provider.h"
#include "sbus/sbus_client.h"
#include "providers/dp_sbus.h"
struct nss_dp_req {
nss_dp_callback_t callback;
void *callback_ctx;
struct timed_event *te;
DBusPendingCall *pending_reply;
};
static int nss_dp_req_destructor(void *ptr)
{
struct nss_dp_req *req = talloc_get_type(ptr, struct nss_dp_req);
if (req->pending_reply) {
dbus_pending_call_cancel(req->pending_reply);
}
return 0;
}
static void nss_dp_send_acct_timeout(struct event_context *ev,
struct timed_event *te,
struct timeval t, void *data)
{
struct nss_dp_req *ndp_req;
dbus_uint16_t err_maj = DP_ERR_TIMEOUT;
dbus_uint32_t err_min = EIO;
const char *err_msg = "Request timed out";
ndp_req = talloc_get_type(data, struct nss_dp_req);
ndp_req->callback(err_maj, err_min, err_msg, ndp_req->callback_ctx);
talloc_free(ndp_req);
}
static int nss_dp_get_reply(DBusPendingCall *pending,
dbus_uint16_t *err_maj,
dbus_uint32_t *err_min,
const char **err_msg);
static void nss_dp_send_acct_callback(DBusPendingCall *pending, void *ptr)
{
struct nss_dp_req *ndp_req;
dbus_uint16_t err_maj;
dbus_uint32_t err_min;
const char *err_msg;
int ret;
ndp_req = talloc_get_type(ptr, struct nss_dp_req);
/* free timeout event and remove request destructor */
talloc_free(ndp_req->te);
talloc_set_destructor(ndp_req, NULL);
ret = nss_dp_get_reply(pending, &err_maj, &err_min, &err_msg);
if (ret != EOK) {
err_maj = DP_ERR_FATAL;
err_min = ret;
err_msg = "Failed to get reply from Data Provider";
}
ndp_req->callback(err_maj, err_min, err_msg, ndp_req->callback_ctx);
talloc_free(ndp_req);
}
int nss_dp_send_acct_req(struct nss_ctx *nctx, TALLOC_CTX *memctx,
nss_dp_callback_t callback, void *callback_ctx,
int timeout, const char *domain, int type,
const char *opt_name, uint32_t opt_id)
{
struct nss_dp_req *ndp_req;
DBusMessage *msg;
DBusPendingCall *pending_reply;
DBusConnection *conn;
dbus_bool_t ret;
uint32_t be_type;
const char *attrs = "core";
char *filter;
struct timeval tv;
/* either, or, not both */
if (opt_name && opt_id) {
return EINVAL;
}
if (!domain) {
return EINVAL;
}
switch (type) {
case NSS_DP_USER:
be_type = BE_REQ_USER;
break;
case NSS_DP_GROUP:
be_type = BE_REQ_GROUP;
break;
case NSS_DP_INITGROUPS:
be_type = BE_REQ_INITGROUPS;
break;
default:
return EINVAL;
}
if (opt_name) {
filter = talloc_asprintf(memctx, "name=%s", opt_name);
} else if (opt_id) {
filter = talloc_asprintf(memctx, "idnumber=%u", opt_id);
} else {
filter = talloc_strdup(memctx, "name=*");
}
if (!filter) {
talloc_free(nctx);
return ENOMEM;
}
conn = sbus_get_connection(nctx->dp_ctx->scon_ctx);
/* create the message */
msg = dbus_message_new_method_call(NULL,
DP_CLI_PATH,
DP_CLI_INTERFACE,
DP_SRV_METHOD_GETACCTINFO);
if (msg == NULL) {
DEBUG(0,("Out of memory?!\n"));
return ENOMEM;
}
DEBUG(4, ("Sending request for [%s][%u][%s][%s]\n",
domain, be_type, attrs, filter));
ret = dbus_message_append_args(msg,
DBUS_TYPE_STRING, &domain,
DBUS_TYPE_UINT32, &be_type,
DBUS_TYPE_STRING, &attrs,
DBUS_TYPE_STRING, &filter,
DBUS_TYPE_INVALID);
if (!ret) {
DEBUG(1,("Failed to build message\n"));
return EIO;
}
ret = dbus_connection_send_with_reply(conn, msg, &pending_reply,
600000 /* TODO: set timeout */);
if (!ret) {
/*
* Critical Failure
* We can't communicate on this connection
* We'll drop it using the default destructor.
*/
DEBUG(0, ("D-BUS send failed.\n"));
dbus_message_unref(msg);
return EIO;
}
ndp_req = talloc_zero(memctx, struct nss_dp_req);
if (!ndp_req) {
dbus_message_unref(msg);
return ENOMEM;
}
ndp_req->callback = callback;
ndp_req->callback_ctx = callback_ctx;
/* set up destructor */
ndp_req->pending_reply = pending_reply;
talloc_set_destructor((TALLOC_CTX *)ndp_req, nss_dp_req_destructor);
/* setup the timeout handler */
gettimeofday(&tv, NULL);
tv.tv_sec += timeout/1000;
tv.tv_usec += (timeout%1000) * 1000;
ndp_req->te = event_add_timed(nctx->ev, memctx, tv,
nss_dp_send_acct_timeout, ndp_req);
/* Set up the reply handler */
dbus_pending_call_set_notify(pending_reply,
nss_dp_send_acct_callback,
ndp_req, NULL);
dbus_message_unref(msg);
return EOK;
}
static int nss_dp_get_reply(DBusPendingCall *pending,
dbus_uint16_t *err_maj,
dbus_uint32_t *err_min,
const char **err_msg)
{
DBusMessage *reply;
DBusError dbus_error;
dbus_bool_t ret;
int type;
int err = EOK;
dbus_error_init(&dbus_error);
reply = dbus_pending_call_steal_reply(pending);
if (!reply) {
/* reply should never be null. This function shouldn't be called
* until reply is valid or timeout has occurred. If reply is NULL
* here, something is seriously wrong and we should bail out.
*/
DEBUG(0, ("Severe error. A reply callback was called but no reply was received and no timeout occurred\n"));
/* FIXME: Destroy this connection ? */
err = EIO;
goto done;
}
type = dbus_message_get_type(reply);
switch (type) {
case DBUS_MESSAGE_TYPE_METHOD_RETURN:
ret = dbus_message_get_args(reply, &dbus_error,
DBUS_TYPE_UINT16, err_maj,
DBUS_TYPE_UINT32, err_min,
DBUS_TYPE_STRING, err_msg,
DBUS_TYPE_INVALID);
if (!ret) {
DEBUG(1,("Filed to parse message\n"));
/* FIXME: Destroy this connection ? */
if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error);
err = EIO;
goto done;
}
DEBUG(4, ("Got reply (%u, %u, %s) from Data Provider\n",
(unsigned int)*err_maj, (unsigned int)*err_min, *err_msg));
break;
case DBUS_MESSAGE_TYPE_ERROR:
DEBUG(0,("The Data Provider returned an error [%s]\n",
dbus_message_get_error_name(reply)));
/* Falling through to default intentionally*/
default:
/*
* Timeout or other error occurred or something
* unexpected happened.
* It doesn't matter which, because either way we
* know that this connection isn't trustworthy.
* We'll destroy it now.
*/
/* FIXME: Destroy this connection ? */
err = EIO;
}
done:
dbus_pending_call_unref(pending);
dbus_message_unref(reply);
return err;
}
static int nss_dp_identity(DBusMessage *message, struct sbus_conn_ctx *sconn)
{
dbus_uint16_t version = DATA_PROVIDER_VERSION;
dbus_uint16_t clitype = DP_CLI_FRONTEND;
const char *cliname = "NSS";
const char *nullname = "";
DBusMessage *reply;
dbus_bool_t ret;
DEBUG(4,("Sending ID reply: (%d,%d,%s)\n",
clitype, version, cliname));
reply = dbus_message_new_method_return(message);
if (!reply) return ENOMEM;
ret = dbus_message_append_args(reply,
DBUS_TYPE_UINT16, &clitype,
DBUS_TYPE_UINT16, &version,
DBUS_TYPE_STRING, &cliname,
DBUS_TYPE_STRING, &nullname,
DBUS_TYPE_INVALID);
if (!ret) {
dbus_message_unref(reply);
return EIO;
}
/* send reply back */
sbus_conn_send_reply(sconn, reply);
dbus_message_unref(reply);
return EOK;
}
struct sbus_method nss_dp_methods[] = {
{ DP_CLI_METHOD_IDENTITY, nss_dp_identity },
{ NULL, NULL }
};
struct nss_dp_pvt_ctx {
struct nss_ctx *nctx;
struct sbus_method *methods;
time_t last_retry;
int retries;
};
static int nss_dp_conn_destructor(void *data);
static void nss_dp_reconnect(struct event_context *ev,
struct timed_event *te,
struct timeval tv, void *data);
static void nss_dp_conn_reconnect(struct nss_dp_pvt_ctx *pvt)
{
struct nss_ctx *nctx;
struct timed_event *te;
struct timeval tv;
struct sbus_method_ctx *sm_ctx;
char *sbus_address;
time_t now;
int ret;
now = time(NULL);
/* reset retry if last reconnect was > 60 sec. ago */
if (pvt->last_retry + 60 < now) pvt->retries = 0;
if (pvt->retries >= 3) {
DEBUG(4, ("Too many reconnect retries! Giving up\n"));
return;
}
pvt->last_retry = now;
pvt->retries++;
nctx = pvt->nctx;
ret = dp_get_sbus_address(nctx, nctx->cdb, &sbus_address);
if (ret != EOK) {
DEBUG(0, ("Could not locate data provider address.\n"));
return;
}
ret = dp_init_sbus_methods(nctx, pvt->methods, &sm_ctx);
if (ret != EOK) {
DEBUG(0, ("Could not initialize SBUS methods.\n"));
return;
}
ret = sbus_client_init(nctx, nctx->ev,
sbus_address, sm_ctx,
pvt, nss_dp_conn_destructor,
&nctx->dp_ctx);
if (ret != EOK) {
DEBUG(4, ("Failed to reconnect [%d(%s)]!\n", ret, strerror(ret)));
tv.tv_sec = now +5;
tv.tv_usec = 0;
te = event_add_timed(nctx->ev, nctx, tv, nss_dp_reconnect, pvt);
if (te == NULL) {
DEBUG(4, ("Failed to add timed event! Giving up\n"));
} else {
DEBUG(4, ("Retrying in 5 seconds\n"));
}
}
}
static void nss_dp_reconnect(struct event_context *ev,
struct timed_event *te,
struct timeval tv, void *data)
{
struct nss_dp_pvt_ctx *pvt;
pvt = talloc_get_type(data, struct nss_dp_pvt_ctx);
nss_dp_conn_reconnect(pvt);
}
int nss_dp_conn_destructor(void *data)
{
struct nss_dp_pvt_ctx *pvt;
struct sbus_conn_ctx *scon;
scon = talloc_get_type(data, struct sbus_conn_ctx);
if (!scon) return 0;
/* if this is a regular disconnect just quit */
if (sbus_conn_disconnecting(scon)) return 0;
pvt = talloc_get_type(sbus_conn_get_private_data(scon),
struct nss_dp_pvt_ctx);
if (pvt) return 0;
nss_dp_conn_reconnect(pvt);
return 0;
}
int nss_dp_init(struct nss_ctx *nctx)
{
struct nss_dp_pvt_ctx *pvt;
pvt = talloc_zero(nctx, struct nss_dp_pvt_ctx);
if (!pvt) return ENOMEM;
pvt->nctx = nctx;
pvt->methods = nss_dp_methods;
nss_dp_conn_reconnect(pvt);
return EOK;
}