proxy_child.c revision 19b4bb652f5cdc2797b66595eaf8811881aa9873
/*
SSSD
Pam Proxy Child
Authors:
Sumit Bose <sbose@redhat.com>
Copyright (C) 2010 Red Hat
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <sys/time.h>
#include <errno.h>
#include <dlfcn.h>
#include <popt.h>
#include <dbus/dbus.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include "util/util.h"
#include "confdb/confdb.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;
};
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;
const char *password;
size_t pwlen;
errno_t ret;
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;
ret = sss_authtok_get_password(auth_data->authtok,
&password, &pwlen);
if (ret) goto failed;
reply[i].resp = calloc(pwlen + 1, sizeof(char));
if (reply[i].resp == NULL) goto failed;
memcpy(reply[i].resp, password, pwlen + 1);
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 int proxy_chauthtok_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;
const char *password;
size_t pwlen;
errno_t ret;
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;
if (!auth_data->sent_old) {
/* The first prompt will be asking for the old authtok */
ret = sss_authtok_get_password(auth_data->authtok,
&password, &pwlen);
if (ret) goto failed;
reply[i].resp = calloc(pwlen + 1, sizeof(char));
if (reply[i].resp == NULL) goto failed;
memcpy(reply[i].resp, password, pwlen + 1);
auth_data->sent_old = true;
}
else {
/* Subsequent prompts are looking for the new authtok */
ret = sss_authtok_get_password(auth_data->newauthtok,
&password, &pwlen);
if (ret) goto failed;
reply[i].resp = calloc(pwlen + 1, sizeof(char));
if (reply[i].resp == NULL) goto failed;
memcpy(reply[i].resp, password, pwlen + 1);
auth_data->sent_old = true;
}
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;
if (pd->cmd == SSS_PAM_CHAUTHTOK) {
conv.conv=proxy_chauthtok_conv;
}
else {
conv.conv=proxy_internal_conv;
}
auth_data = talloc_zero(pd, struct authtok_conv);
if (auth_data == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_zero failed.\n"));
return ENOMEM;
}
auth_data->authtok = sss_authtok_new(auth_data);
if (auth_data->authtok == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, ("sss_authtok_new failed.\n"));
ret = ENOMEM;
goto fail;
}
auth_data->newauthtok = sss_authtok_new(auth_data);
if (auth_data->newauthtok == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, ("sss_authtok_new failed.\n"));
ret = ENOMEM;
goto fail;
}
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:
sss_authtok_copy(pd->authtok, auth_data->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:
sss_authtok_copy(pd->authtok, auth_data->authtok);
if (pd->priv != 1) {
pam_status = pam_authenticate(pamh, 0);
auth_data->sent_old = false;
if (pam_status != PAM_SUCCESS) break;
}
sss_authtok_copy(pd->newauthtok, auth_data->newauthtok);
pam_status = pam_chauthtok(pamh, 0);
break;
case SSS_PAM_CHAUTHTOK_PRELIM:
if (pd->priv != 1) {
sss_authtok_copy(pd->authtok, auth_data->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;
fail:
talloc_free(auth_data);
return ret;
}
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(SSSDBG_FUNC_DATA, ("Sending ID to Proxy Backend: (%d,%"PRIu32")\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
};
/* Set debug level to invalid value so we can deside if -d 0 was used. */
debug_level = SSSDBG_INVALID;
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);
DEBUG_INIT(debug_level);
/* 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;
}