From 1c48b5a62f73234ed26bb20f0ab345ab61cda0ab Mon Sep 17 00:00:00 2001 From: Stephen Gallagher Date: Thu, 18 Feb 2010 07:49:04 -0500 Subject: Rename server/ directory to src/ Also update BUILD.txt --- src/responder/common/responder.h | 152 ++ src/responder/common/responder_cmd.c | 103 + src/responder/common/responder_common.c | 589 ++++++ src/responder/common/responder_dp.c | 590 ++++++ src/responder/common/responder_packet.c | 253 +++ src/responder/common/responder_packet.h | 43 + src/responder/nss/nsssrv.c | 367 ++++ src/responder/nss/nsssrv.h | 70 + src/responder/nss/nsssrv_cmd.c | 3182 +++++++++++++++++++++++++++++++ src/responder/nss/nsssrv_nc.c | 321 ++++ src/responder/nss/nsssrv_nc.h | 51 + src/responder/pam/pam_LOCAL_domain.c | 476 +++++ src/responder/pam/pamsrv.c | 224 +++ src/responder/pam/pamsrv.h | 57 + src/responder/pam/pamsrv_cmd.c | 1181 ++++++++++++ src/responder/pam/pamsrv_dp.c | 142 ++ 16 files changed, 7801 insertions(+) create mode 100644 src/responder/common/responder.h create mode 100644 src/responder/common/responder_cmd.c create mode 100644 src/responder/common/responder_common.c create mode 100644 src/responder/common/responder_dp.c create mode 100644 src/responder/common/responder_packet.c create mode 100644 src/responder/common/responder_packet.h create mode 100644 src/responder/nss/nsssrv.c create mode 100644 src/responder/nss/nsssrv.h create mode 100644 src/responder/nss/nsssrv_cmd.c create mode 100644 src/responder/nss/nsssrv_nc.c create mode 100644 src/responder/nss/nsssrv_nc.h create mode 100644 src/responder/pam/pam_LOCAL_domain.c create mode 100644 src/responder/pam/pamsrv.c create mode 100644 src/responder/pam/pamsrv.h create mode 100644 src/responder/pam/pamsrv_cmd.c create mode 100644 src/responder/pam/pamsrv_dp.c (limited to 'src/responder') diff --git a/src/responder/common/responder.h b/src/responder/common/responder.h new file mode 100644 index 000000000..ea6ba5831 --- /dev/null +++ b/src/responder/common/responder.h @@ -0,0 +1,152 @@ +/* + SSSD + + SSS Client Responder, header file + + Copyright (C) Simo Sorce 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 . +*/ + +#ifndef __SSS_RESPONDER_H__ +#define __SSS_RESPONDER_H__ + +#include +#include +#include +#include "config.h" +#include "talloc.h" +#include "tevent.h" +#include "ldb.h" +#include "dhash.h" +#include "sbus/sssd_dbus.h" +#include "sss_client/sss_cli.h" + +extern hash_table_t *dp_requests; + +/* if there is a provider other than the special local */ +#define NEED_CHECK_PROVIDER(provider) \ + (provider != NULL && strcmp(provider, "local") != 0) + +/* needed until nsssrv.h is updated */ +struct cli_request { + + /* original request from the wire */ + struct sss_packet *in; + + /* reply data */ + struct sss_packet *out; +}; + +struct cli_protocol_version { + uint32_t version; + const char *date; + const char *description; +}; + +struct be_conn { + struct be_conn *next; + struct be_conn *prev; + + const char *cli_name; + struct sss_domain_info *domain; + + char *sbus_address; + struct sbus_interface *intf; + struct sbus_connection *conn; +}; + +struct resp_ctx { + struct tevent_context *ev; + struct tevent_fd *lfde; + int lfd; + struct tevent_fd *priv_lfde; + int priv_lfd; + struct confdb_ctx *cdb; + const char *sock_name; + const char *priv_sock_name; + + struct sbus_connection *mon_conn; + struct be_conn *be_conns; + + struct sss_domain_info *domains; + struct sysdb_ctx_list *db_list; + + struct sss_cmd_table *sss_cmds; + const char *sss_pipe_name; + const char *confdb_service_path; + + struct sss_names_ctx *names; + + void *pvt_ctx; +}; + +struct cli_ctx { + struct tevent_context *ev; + struct resp_ctx *rctx; + int cfd; + struct tevent_fd *cfde; + struct sockaddr_un addr; + struct cli_request *creq; + struct cli_protocol_version *cli_protocol_version; + int priv; +}; + +struct sss_cmd_table { + enum sss_cli_command cmd; + int (*fn)(struct cli_ctx *cctx); +}; + +/* responder_common.c */ +int sss_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb, + struct sss_cmd_table sss_cmds[], + const char *sss_pipe_name, + const char *sss_priv_pipe_name, + const char *confdb_service_path, + const char *svc_name, + uint16_t svc_version, + struct sbus_interface *monitor_intf, + const char *cli_name, + struct sbus_interface *dp_intf, + struct resp_ctx **responder_ctx); + +int sss_parse_name(TALLOC_CTX *memctx, + struct sss_names_ctx *snctx, + const char *orig, char **domain, char **name); + +int sss_dp_get_domain_conn(struct resp_ctx *rctx, const char *domain, + struct be_conn **_conn); + +/* responder_cmd.c */ +int sss_cmd_execute(struct cli_ctx *cctx, struct sss_cmd_table *sss_cmds); +void sss_cmd_done(struct cli_ctx *cctx, void *freectx); +int sss_cmd_get_version(struct cli_ctx *cctx); +struct cli_protocol_version *register_cli_protocol_version(void); + +#define SSS_DP_USER 1 +#define SSS_DP_GROUP 2 +#define SSS_DP_INITGROUPS 3 + +typedef void (*sss_dp_callback_t)(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); + +int sss_dp_send_acct_req(struct resp_ctx *rctx, TALLOC_CTX *callback_memctx, + sss_dp_callback_t callback, void *callback_ctx, + int timeout, const char *domain, + bool fast_reply, int type, + const char *opt_name, uint32_t opt_id); + +#endif /* __SSS_RESPONDER_H__ */ diff --git a/src/responder/common/responder_cmd.c b/src/responder/common/responder_cmd.c new file mode 100644 index 000000000..cd9890305 --- /dev/null +++ b/src/responder/common/responder_cmd.c @@ -0,0 +1,103 @@ +/* + SSSD + + SSS Client Responder, command parser + + Copyright (C) Simo Sorce 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 . +*/ +#include +#include "util/util.h" +#include "responder/common/responder.h" +#include "responder/common/responder_packet.h" + + +void sss_cmd_done(struct cli_ctx *cctx, void *freectx) +{ + /* now that the packet is in place, unlock queue + * making the event writable */ + TEVENT_FD_WRITEABLE(cctx->cfde); + + /* free all request related data through the talloc hierarchy */ + talloc_free(freectx); +} + +int sss_cmd_get_version(struct cli_ctx *cctx) +{ + uint8_t *req_body; + size_t req_blen; + uint8_t *body; + size_t blen; + int ret; + uint32_t client_version; + int i; + static struct cli_protocol_version *cli_protocol_version = NULL; + + cctx->cli_protocol_version = NULL; + + if (cli_protocol_version == NULL) { + cli_protocol_version = register_cli_protocol_version(); + } + + if (cli_protocol_version != NULL) { + cctx->cli_protocol_version = &cli_protocol_version[0]; + + sss_packet_get_body(cctx->creq->in, &req_body, &req_blen); + if (req_blen == sizeof(uint32_t)) { + memcpy(&client_version, req_body, sizeof(uint32_t)); + DEBUG(5, ("Received client version [%d].\n", client_version)); + + i=0; + while(cli_protocol_version[i].version>0) { + if (cli_protocol_version[i].version == client_version) { + cctx->cli_protocol_version = &cli_protocol_version[i]; + break; + } + i++; + } + } + } + + /* create response packet */ + ret = sss_packet_new(cctx->creq, sizeof(uint32_t), + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + return ret; + } + sss_packet_get_body(cctx->creq->out, &body, &blen); + ((uint32_t *)body)[0] = cctx->cli_protocol_version!=NULL ? + cctx->cli_protocol_version->version : 0; + DEBUG(5, ("Offered version [%d].\n", ((uint32_t *)body)[0])); + + sss_cmd_done(cctx, NULL); + return EOK; +} + +int sss_cmd_execute(struct cli_ctx *cctx, struct sss_cmd_table *sss_cmds) +{ + enum sss_cli_command cmd; + int i; + + cmd = sss_packet_get_cmd(cctx->creq->in); + + for (i = 0; sss_cmds[i].cmd != SSS_CLI_NULL; i++) { + if (cmd == sss_cmds[i].cmd) { + return sss_cmds[i].fn(cctx); + } + } + + return EINVAL; +} diff --git a/src/responder/common/responder_common.c b/src/responder/common/responder_common.c new file mode 100644 index 000000000..37bbcb30f --- /dev/null +++ b/src/responder/common/responder_common.c @@ -0,0 +1,589 @@ +/* + SSSD + + Common Responder methods + + Copyright (C) Simo Sorce 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "popt.h" +#include "util/util.h" +#include "db/sysdb.h" +#include "confdb/confdb.h" +#include "dbus/dbus.h" +#include "sbus/sssd_dbus.h" +#include "responder/common/responder.h" +#include "responder/common/responder_packet.h" +#include "providers/data_provider.h" +#include "monitor/monitor_interfaces.h" +#include "sbus/sbus_client.h" + +static void set_nonblocking(int fd) +{ + unsigned v; + v = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, v | O_NONBLOCK); +} + +static void set_close_on_exec(int fd) +{ + unsigned v; + v = fcntl(fd, F_GETFD, 0); + fcntl(fd, F_SETFD, v | FD_CLOEXEC); +} + +static int client_destructor(struct cli_ctx *ctx) +{ + if (ctx->cfd > 0) close(ctx->cfd); + return 0; +} + +static void client_send(struct tevent_context *ev, struct cli_ctx *cctx) +{ + int ret; + + ret = sss_packet_send(cctx->creq->out, cctx->cfd); + if (ret == EAGAIN) { + /* not all data was sent, loop again */ + return; + } + if (ret != EOK) { + DEBUG(0, ("Failed to read request, aborting client!\n")); + talloc_free(cctx); + return; + } + + /* ok all sent */ + TEVENT_FD_NOT_WRITEABLE(cctx->cfde); + TEVENT_FD_READABLE(cctx->cfde); + talloc_free(cctx->creq); + cctx->creq = NULL; + return; +} + +static void client_recv(struct tevent_context *ev, struct cli_ctx *cctx) +{ + int ret; + + if (!cctx->creq) { + cctx->creq = talloc_zero(cctx, struct cli_request); + if (!cctx->creq) { + DEBUG(0, ("Failed to alloc request, aborting client!\n")); + talloc_free(cctx); + return; + } + } + + if (!cctx->creq->in) { + ret = sss_packet_new(cctx->creq, SSS_PACKET_MAX_RECV_SIZE, + 0, &cctx->creq->in); + if (ret != EOK) { + DEBUG(0, ("Failed to alloc request, aborting client!\n")); + talloc_free(cctx); + return; + } + } + + ret = sss_packet_recv(cctx->creq->in, cctx->cfd); + switch (ret) { + case EOK: + /* do not read anymore */ + TEVENT_FD_NOT_READABLE(cctx->cfde); + /* execute command */ + ret = sss_cmd_execute(cctx, cctx->rctx->sss_cmds); + if (ret != EOK) { + DEBUG(0, ("Failed to execute request, aborting client!\n")); + talloc_free(cctx); + } + /* past this point cctx can be freed at any time by callbacks + * in case of error, do not use it */ + return; + + case EAGAIN: + /* need to read still some data, loop again */ + break; + + case EINVAL: + DEBUG(6, ("Invalid data from client, closing connection!\n")); + talloc_free(cctx); + break; + + case ENODATA: + DEBUG(5, ("Client disconnected!\n")); + talloc_free(cctx); + break; + + default: + DEBUG(6, ("Failed to read request, aborting client!\n")); + talloc_free(cctx); + } + + return; +} + +static void client_fd_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, void *ptr) +{ + struct cli_ctx *cctx = talloc_get_type(ptr, struct cli_ctx); + + if (flags & TEVENT_FD_READ) { + client_recv(ev, cctx); + return; + } + if (flags & TEVENT_FD_WRITE) { + client_send(ev, cctx); + return; + } +} + +/* TODO: this is a copy of accept_fd_handler, maybe both can be put into on + * handler. */ +static void accept_priv_fd_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, void *ptr) +{ + /* accept and attach new event handler */ + struct resp_ctx *rctx = talloc_get_type(ptr, struct resp_ctx); + struct cli_ctx *cctx; + socklen_t len; + struct stat stat_buf; + int ret; + + ret = stat(rctx->priv_sock_name, &stat_buf); + if (ret == -1) { + DEBUG(1, ("stat on privileged pipe failed: [%d][%s].\n", errno, + strerror(errno))); + return; + } + + if ( ! (stat_buf.st_uid == 0 && stat_buf.st_gid == 0 && + (stat_buf.st_mode&(S_IFSOCK|S_IRUSR|S_IWUSR)) == stat_buf.st_mode)) { + DEBUG(1, ("privileged pipe has an illegal status.\n")); +/* TODO: what is the best response to this condition? Terminate? */ + return; + } + + + cctx = talloc_zero(rctx, struct cli_ctx); + if (!cctx) { + struct sockaddr_un addr; + int fd; + DEBUG(0, ("Out of memory trying to setup client context on privileged pipe!\n")); + /* accept and close to signal the client we have a problem */ + memset(&addr, 0, sizeof(addr)); + len = sizeof(addr); + fd = accept(rctx->priv_lfd, (struct sockaddr *)&addr, &len); + if (fd == -1) { + return; + } + close(fd); + return; + } + + len = sizeof(cctx->addr); + cctx->cfd = accept(rctx->priv_lfd, (struct sockaddr *)&cctx->addr, &len); + if (cctx->cfd == -1) { + DEBUG(1, ("Accept failed [%s]", strerror(errno))); + talloc_free(cctx); + return; + } + + cctx->priv = 1; + + cctx->cfde = tevent_add_fd(ev, cctx, cctx->cfd, + TEVENT_FD_READ, client_fd_handler, cctx); + if (!cctx->cfde) { + close(cctx->cfd); + talloc_free(cctx); + DEBUG(2, ("Failed to queue client handler on privileged pipe\n")); + } + + cctx->ev = ev; + cctx->rctx = rctx; + + talloc_set_destructor(cctx, client_destructor); + + DEBUG(4, ("Client connected to privileged pipe!\n")); + + return; +} + +static void accept_fd_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, void *ptr) +{ + /* accept and attach new event handler */ + struct resp_ctx *rctx = talloc_get_type(ptr, struct resp_ctx); + struct cli_ctx *cctx; + socklen_t len; + + cctx = talloc_zero(rctx, struct cli_ctx); + if (!cctx) { + struct sockaddr_un addr; + int fd; + DEBUG(0, ("Out of memory trying to setup client context!\n")); + /* accept and close to signal the client we have a problem */ + memset(&addr, 0, sizeof(addr)); + len = sizeof(addr); + fd = accept(rctx->lfd, (struct sockaddr *)&addr, &len); + if (fd == -1) { + return; + } + close(fd); + return; + } + + len = sizeof(cctx->addr); + cctx->cfd = accept(rctx->lfd, (struct sockaddr *)&cctx->addr, &len); + if (cctx->cfd == -1) { + DEBUG(1, ("Accept failed [%s]", strerror(errno))); + talloc_free(cctx); + return; + } + + cctx->cfde = tevent_add_fd(ev, cctx, cctx->cfd, + TEVENT_FD_READ, client_fd_handler, cctx); + if (!cctx->cfde) { + close(cctx->cfd); + talloc_free(cctx); + DEBUG(2, ("Failed to queue client handler\n")); + } + + cctx->ev = ev; + cctx->rctx = rctx; + + talloc_set_destructor(cctx, client_destructor); + + DEBUG(4, ("Client connected!\n")); + + return; +} + +static int sss_monitor_init(struct resp_ctx *rctx, + struct sbus_interface *intf, + const char *svc_name, + uint16_t svc_version) +{ + char *sbus_address; + int ret; + + /* Set up SBUS connection to the monitor */ + ret = monitor_get_sbus_address(rctx, &sbus_address); + if (ret != EOK) { + DEBUG(0, ("Could not locate monitor address.\n")); + return ret; + } + + ret = sbus_client_init(rctx, rctx->ev, sbus_address, + intf, &rctx->mon_conn, + NULL, NULL); + if (ret != EOK) { + DEBUG(0, ("Failed to connect to monitor services.\n")); + return ret; + } + + /* Identify ourselves to the monitor */ + ret = monitor_common_send_id(rctx->mon_conn, svc_name, svc_version); + if (ret != EOK) { + DEBUG(0, ("Failed to identify to the monitor!\n")); + return ret; + } + + return EOK; +} + +static int sss_dp_init(struct resp_ctx *rctx, + struct sbus_interface *intf, + const char *cli_name, + struct sss_domain_info *domain) +{ + struct be_conn *be_conn; + int ret; + + be_conn = talloc_zero(rctx, struct be_conn); + if (!be_conn) return ENOMEM; + + be_conn->cli_name = cli_name; + be_conn->domain = domain; + be_conn->intf = intf; + + /* Set up SBUS connection to the monitor */ + ret = dp_get_sbus_address(be_conn, &be_conn->sbus_address, domain->name); + if (ret != EOK) { + DEBUG(0, ("Could not locate DP address.\n")); + return ret; + } + ret = sbus_client_init(rctx, rctx->ev, + be_conn->sbus_address, + intf, &be_conn->conn, + NULL, NULL); + if (ret != EOK) { + DEBUG(0, ("Failed to connect to monitor services.\n")); + return ret; + } + + DLIST_ADD_END(rctx->be_conns, be_conn, struct be_conn *); + + /* Identify ourselves to the DP */ + ret = dp_common_send_id(be_conn->conn, + DATA_PROVIDER_VERSION, + cli_name, domain->name); + if (ret != EOK) { + DEBUG(0, ("Failed to identify to the DP!\n")); + return ret; + } + + return EOK; +} + +/* create a unix socket and listen to it */ +static int set_unix_socket(struct resp_ctx *rctx) +{ + struct sockaddr_un addr; + +/* for future use */ +#if 0 + char *default_pipe; + int ret; + + default_pipe = talloc_asprintf(rctx, "%s/%s", PIPE_PATH, + rctx->sss_pipe_name); + if (!default_pipe) { + return ENOMEM; + } + + ret = confdb_get_string(rctx->cdb, rctx, + rctx->confdb_socket_path, "unixSocket", + default_pipe, &rctx->sock_name); + if (ret != EOK) { + talloc_free(default_pipe); + return ret; + } + talloc_free(default_pipe); + + default_pipe = talloc_asprintf(rctx, "%s/private/%s", PIPE_PATH, + rctx->sss_pipe_name); + if (!default_pipe) { + return ENOMEM; + } + + ret = confdb_get_string(rctx->cdb, rctx, + rctx->confdb_socket_path, "privUnixSocket", + default_pipe, &rctx->priv_sock_name); + if (ret != EOK) { + talloc_free(default_pipe); + return ret; + } + talloc_free(default_pipe); +#endif + + if (rctx->sock_name != NULL ) { + rctx->lfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (rctx->lfd == -1) { + return EIO; + } + + /* Set the umask so that permissions are set right on the socket. + * It must be readable and writable by anybody on the system. */ + umask(0111); + + set_nonblocking(rctx->lfd); + set_close_on_exec(rctx->lfd); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, rctx->sock_name, sizeof(addr.sun_path)); + + /* make sure we have no old sockets around */ + unlink(rctx->sock_name); + + if (bind(rctx->lfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + DEBUG(0,("Unable to bind on socket '%s'\n", rctx->sock_name)); + goto failed; + } + if (listen(rctx->lfd, 10) != 0) { + DEBUG(0,("Unable to listen on socket '%s'\n", rctx->sock_name)); + goto failed; + } + + rctx->lfde = tevent_add_fd(rctx->ev, rctx, rctx->lfd, + TEVENT_FD_READ, accept_fd_handler, rctx); + if (!rctx->lfde) { + DEBUG(0, ("Failed to queue handler on pipe\n")); + goto failed; + } + } + + if (rctx->priv_sock_name != NULL ) { + /* create privileged pipe */ + rctx->priv_lfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (rctx->priv_lfd == -1) { + close(rctx->lfd); + return EIO; + } + + umask(0177); + + set_nonblocking(rctx->priv_lfd); + set_close_on_exec(rctx->priv_lfd); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, rctx->priv_sock_name, sizeof(addr.sun_path)); + + unlink(rctx->priv_sock_name); + + if (bind(rctx->priv_lfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + DEBUG(0,("Unable to bind on socket '%s'\n", rctx->priv_sock_name)); + goto failed; + } + if (listen(rctx->priv_lfd, 10) != 0) { + DEBUG(0,("Unable to listen on socket '%s'\n", rctx->priv_sock_name)); + goto failed; + } + + rctx->priv_lfde = tevent_add_fd(rctx->ev, rctx, rctx->priv_lfd, + TEVENT_FD_READ, accept_priv_fd_handler, rctx); + if (!rctx->priv_lfde) { + DEBUG(0, ("Failed to queue handler on privileged pipe\n")); + goto failed; + } + } + + /* we want default permissions on created files to be very strict, + so set our umask to 0177 */ + umask(0177); + return EOK; + +failed: + /* we want default permissions on created files to be very strict, + so set our umask to 0177 */ + umask(0177); + close(rctx->lfd); + close(rctx->priv_lfd); + return EIO; +} + +int sss_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb, + struct sss_cmd_table sss_cmds[], + const char *sss_pipe_name, + const char *sss_priv_pipe_name, + const char *confdb_service_path, + const char *svc_name, + uint16_t svc_version, + struct sbus_interface *monitor_intf, + const char *cli_name, + struct sbus_interface *dp_intf, + struct resp_ctx **responder_ctx) +{ + struct resp_ctx *rctx; + struct sss_domain_info *dom; + int ret; + + rctx = talloc_zero(mem_ctx, struct resp_ctx); + if (!rctx) { + DEBUG(0, ("fatal error initializing resp_ctx\n")); + return ENOMEM; + } + rctx->ev = ev; + rctx->cdb = cdb; + rctx->sss_cmds = sss_cmds; + rctx->sock_name = sss_pipe_name; + rctx->priv_sock_name = sss_priv_pipe_name; + rctx->confdb_service_path = confdb_service_path; + + ret = confdb_get_domains(rctx->cdb, &rctx->domains); + if (ret != EOK) { + DEBUG(0, ("fatal error setting up domain map\n")); + return ret; + } + + ret = sss_monitor_init(rctx, monitor_intf, svc_name, svc_version); + if (ret != EOK) { + DEBUG(0, ("fatal error setting up message bus\n")); + return ret; + } + + for (dom = rctx->domains; dom; dom = dom->next) { + + /* skip local domain, it doesn't have a backend */ + if (strcasecmp(dom->provider, "local") == 0) { + continue; + } + + ret = sss_dp_init(rctx, dp_intf, cli_name, dom); + if (ret != EOK) { + DEBUG(0, ("fatal error setting up backend connector\n")); + return ret; + } + } + + ret = sysdb_init(rctx, ev, cdb, NULL, false, &rctx->db_list); + if (ret != EOK) { + DEBUG(0, ("fatal error initializing resp_ctx\n")); + return ret; + } + + ret = sss_names_init(rctx, rctx->cdb, &rctx->names); + if (ret != EOK) { + DEBUG(0, ("fatal error initializing regex data\n")); + return ret; + } + + /* after all initializations we are ready to listen on our socket */ + ret = set_unix_socket(rctx); + if (ret != EOK) { + DEBUG(0, ("fatal error initializing socket\n")); + return ret; + } + + DEBUG(1, ("Responder Initialization complete\n")); + + *responder_ctx = rctx; + return EOK; +} + +int sss_dp_get_domain_conn(struct resp_ctx *rctx, const char *domain, + struct be_conn **_conn) +{ + struct be_conn *iter; + + if (!rctx->be_conns) return ENOENT; + + for (iter = rctx->be_conns; iter; iter = iter->next) { + if (strcasecmp(domain, iter->domain->name) == 0) break; + } + + if (!iter) return ENOENT; + + *_conn = iter; + + return EOK; +} + diff --git a/src/responder/common/responder_dp.c b/src/responder/common/responder_dp.c new file mode 100644 index 000000000..782befb17 --- /dev/null +++ b/src/responder/common/responder_dp.c @@ -0,0 +1,590 @@ +/* + Authors: + Simo Sorce + Stephen Gallagher + + Copyright (C) 2009 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 "util/util.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "providers/data_provider.h" +#include "sbus/sbus_client.h" + +hash_table_t *dp_requests = NULL; + +struct sss_dp_req; + +struct sss_dp_callback { + struct sss_dp_callback *prev; + struct sss_dp_callback *next; + + struct sss_dp_req *sdp_req; + + sss_dp_callback_t callback; + void *callback_ctx; +}; + +struct sss_dp_req { + struct tevent_context *ev; + DBusPendingCall *pending_reply; + + char *key; + + struct tevent_timer *tev; + struct sss_dp_callback *cb_list; + + dbus_uint16_t err_maj; + dbus_uint32_t err_min; + char *err_msg; +}; + +static int sss_dp_callback_destructor(void *ptr) +{ + struct sss_dp_callback *cb = talloc_get_type(ptr, struct sss_dp_callback); + + DLIST_REMOVE(cb->sdp_req->cb_list, cb); + + return EOK; +} + +static int sss_dp_req_destructor(void *ptr) +{ + struct sss_dp_req *sdp_req = talloc_get_type(ptr, struct sss_dp_req); + struct sss_dp_callback *cb, *next; + hash_key_t key; + + /* Cancel Dbus pending reply if still pending */ + if (sdp_req->pending_reply) { + dbus_pending_call_cancel(sdp_req->pending_reply); + sdp_req->pending_reply = NULL; + } + + /* Destroy the hash entry */ + key.type = HASH_KEY_STRING; + key.str = sdp_req->key; + int hret = hash_delete(dp_requests, &key); + if (hret != HASH_SUCCESS) { + /* This should never happen */ + DEBUG(0, ("Could not clear entry from request queue\n")); + } + + /* Free any remaining callback */ + if (sdp_req->err_maj == DP_ERR_OK) { + sdp_req->err_maj = DP_ERR_FATAL; + sdp_req->err_min = EIO; + sdp_req->err_msg = discard_const_p(char, "Internal Error"); + } + + cb = sdp_req->cb_list; + while (cb) { + cb->callback(sdp_req->err_maj, + sdp_req->err_min, + sdp_req->err_msg, + cb->callback_ctx); + next = cb->next; + talloc_free(cb); + cb = next; + } + + return 0; +} + +static void sdp_req_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr) +{ + struct sss_dp_req *sdp_req = talloc_get_type(ptr, struct sss_dp_req); + + sdp_req->err_maj = DP_ERR_FATAL; + sdp_req->err_min = ETIMEDOUT; + sdp_req->err_msg = discard_const_p(char, "Timed out"); + + /* steal te on NULL because it will be freed as soon as the handler + * returns. Causing a double free if we don't, as te is allocated on + * sdp_req and we are just going to free it */ + talloc_steal(NULL, te); + + talloc_free(sdp_req); +} + +static int sss_dp_get_reply(DBusPendingCall *pending, + dbus_uint16_t *err_maj, + dbus_uint32_t *err_min, + char **err_msg); + +static void sss_dp_invoke_callback(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr) +{ + struct sss_dp_req *sdp_req = talloc_get_type(ptr, struct sss_dp_req); + struct sss_dp_callback *cb; + struct timeval tv; + struct tevent_timer *tev; + + cb = sdp_req->cb_list; + /* Remove the callback from the list, the caller may free it, within the + * callback. */ + talloc_set_destructor((TALLOC_CTX *)cb, NULL); + DLIST_REMOVE(sdp_req->cb_list, cb); + + cb->callback(sdp_req->err_maj, + sdp_req->err_min, + sdp_req->err_msg, + cb->callback_ctx); + + /* Call the next callback if needed */ + if (sdp_req->cb_list != NULL) { + tv = tevent_timeval_current(); + tev = tevent_add_timer(sdp_req->ev, sdp_req, tv, + sss_dp_invoke_callback, sdp_req); + if (!te) { + /* Out of memory or other serious error */ + goto done; + } + + return; + } + + /* No more callbacks to invoke. Destroy the request */ +done: + /* steal te on NULL because it will be freed as soon as the handler + * returns. Causing a double free if we don't, as te is allocated on + * sdp_req and we are just going to free it */ + talloc_steal(NULL, te); + + talloc_zfree(sdp_req); +} + +static void sss_dp_send_acct_callback(DBusPendingCall *pending, void *ptr) +{ + int ret; + struct sss_dp_req *sdp_req; + struct sss_dp_callback *cb; + struct timeval tv; + struct tevent_timer *te; + + sdp_req = talloc_get_type(ptr, struct sss_dp_req); + + /* prevent trying to cancel a reply that we already received */ + sdp_req->pending_reply = NULL; + + ret = sss_dp_get_reply(pending, + &sdp_req->err_maj, + &sdp_req->err_min, + &sdp_req->err_msg); + if (ret != EOK) { + if (ret == ETIME) { + sdp_req->err_maj = DP_ERR_TIMEOUT; + sdp_req->err_min = ret; + sdp_req->err_msg = talloc_strdup(sdp_req, "Request timed out"); + } + else { + sdp_req->err_maj = DP_ERR_FATAL; + sdp_req->err_min = ret; + sdp_req->err_msg = + talloc_strdup(sdp_req, + "Failed to get reply from Data Provider"); + } + } + + /* Check whether we need to issue any callbacks */ + cb = sdp_req->cb_list; + if (sdp_req->cb_list == NULL) { + if (cb == NULL) { + /* No callbacks to invoke. Destroy the hash entry */ + talloc_zfree(sdp_req); + return; + } + } + + /* Queue up all callbacks */ + tv = tevent_timeval_current(); + te = tevent_add_timer(sdp_req->ev, sdp_req, tv, + sss_dp_invoke_callback, sdp_req); + if (!te) { + /* Out of memory or other serious error */ + goto error; + } + + return; + +error: + talloc_zfree(sdp_req); +} + +static int sss_dp_send_acct_req_create(struct resp_ctx *rctx, + TALLOC_CTX *callback_memctx, + const char *domain, + uint32_t be_type, + char *filter, + int timeout, + sss_dp_callback_t callback, + void *callback_ctx, + struct sss_dp_req **ndp); + +int sss_dp_send_acct_req(struct resp_ctx *rctx, TALLOC_CTX *callback_memctx, + sss_dp_callback_t callback, void *callback_ctx, + int timeout, const char *domain, + bool fast_reply, int type, + const char *opt_name, uint32_t opt_id) +{ + int ret, hret; + uint32_t be_type; + char *filter; + hash_key_t key; + hash_value_t value; + TALLOC_CTX *tmp_ctx; + struct timeval tv; + struct sss_dp_req *sdp_req = NULL; + struct sss_dp_callback *cb; + + /* either, or, not both */ + if (opt_name && opt_id) { + return EINVAL; + } + + if (!domain) { + return EINVAL; + } + + switch (type) { + case SSS_DP_USER: + be_type = BE_REQ_USER; + break; + case SSS_DP_GROUP: + be_type = BE_REQ_GROUP; + break; + case SSS_DP_INITGROUPS: + be_type = BE_REQ_INITGROUPS; + break; + default: + return EINVAL; + } + + if (fast_reply) { + be_type |= BE_REQ_FAST; + } + + if (dp_requests == NULL) { + /* Create a hash table to handle queued update requests */ + ret = hash_create(10, &dp_requests, NULL, NULL); + if (ret != HASH_SUCCESS) { + fprintf(stderr, "cannot create hash table (%s)\n", hash_error_string(ret)); + return EIO; + } + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + key.type = HASH_KEY_STRING; + key.str = NULL; + + if (opt_name) { + filter = talloc_asprintf(tmp_ctx, "name=%s", opt_name); + key.str = talloc_asprintf(tmp_ctx, "%d%s@%s", type, opt_name, domain); + } else if (opt_id) { + filter = talloc_asprintf(tmp_ctx, "idnumber=%u", opt_id); + key.str = talloc_asprintf(tmp_ctx, "%d%d@%s", type, opt_id, domain); + } else { + filter = talloc_strdup(tmp_ctx, "name=*"); + key.str = talloc_asprintf(tmp_ctx, "%d*@%s", type, domain); + } + if (!filter || !key.str) { + talloc_zfree(tmp_ctx); + return ENOMEM; + } + + /* Check whether there's already a request in progress */ + hret = hash_lookup(dp_requests, &key, &value); + switch (hret) { + case HASH_SUCCESS: + /* Request already in progress + * Add an additional callback if needed and return + */ + DEBUG(2, ("Identical request in progress\n")); + + if (callback) { + /* We have a new request asking for a callback */ + sdp_req = talloc_get_type(value.ptr, struct sss_dp_req); + if (!sdp_req) { + DEBUG(0, ("Could not retrieve DP request context\n")); + ret = EIO; + goto done; + } + + cb = talloc_zero(callback_memctx, struct sss_dp_callback); + if (!cb) { + ret = ENOMEM; + goto done; + } + + cb->callback = callback; + cb->callback_ctx = callback_ctx; + cb->sdp_req = sdp_req; + + DLIST_ADD_END(sdp_req->cb_list, cb, struct sss_dp_callback *); + talloc_set_destructor((TALLOC_CTX *)cb, sss_dp_callback_destructor); + } + + ret = EOK; + break; + + case HASH_ERROR_KEY_NOT_FOUND: + /* No such request in progress + * Create a new request + */ + ret = sss_dp_send_acct_req_create(rctx, callback_memctx, domain, + be_type, filter, timeout, + callback, callback_ctx, + &sdp_req); + if (ret != EOK) { + goto done; + } + + value.type = HASH_VALUE_PTR; + value.ptr = sdp_req; + hret = hash_enter(dp_requests, &key, &value); + if (hret != HASH_SUCCESS) { + DEBUG(0, ("Could not store request query (%s)", + hash_error_string(hret))); + talloc_zfree(sdp_req); + ret = EIO; + goto done; + } + + sdp_req->key = talloc_strdup(sdp_req, key.str); + + tv = tevent_timeval_current_ofs(timeout, 0); + sdp_req->tev = tevent_add_timer(sdp_req->ev, sdp_req, tv, + sdp_req_timeout, sdp_req); + if (!sdp_req->tev) { + DEBUG(0, ("Out of Memory!?")); + talloc_zfree(sdp_req); + ret = ENOMEM; + goto done; + } + + talloc_set_destructor((TALLOC_CTX *)sdp_req, sss_dp_req_destructor); + + ret = EOK; + break; + + default: + DEBUG(0,("Could not query request list (%s)\n", + hash_error_string(hret))); + talloc_zfree(sdp_req); + ret = EIO; + } + +done: + talloc_zfree(tmp_ctx); + return ret; +} + +static int sss_dp_send_acct_req_create(struct resp_ctx *rctx, + TALLOC_CTX *callback_memctx, + const char *domain, + uint32_t be_type, + char *filter, + int timeout, + sss_dp_callback_t callback, + void *callback_ctx, + struct sss_dp_req **ndp) +{ + DBusConnection *dbus_conn; + DBusMessage *msg; + DBusPendingCall *pending_reply; + dbus_bool_t dbret; + struct sss_dp_callback *cb; + struct sss_dp_req *sdp_req; + uint32_t attrs = BE_ATTR_CORE; + struct be_conn *be_conn; + int ret; + + /* double check dp_ctx has actually been initialized. + * in some pathological cases it may happen that nss starts up before + * dp connection code is actually able to establish a connection. + */ + ret = sss_dp_get_domain_conn(rctx, domain, &be_conn); + if (ret != EOK) { + DEBUG(1, ("The Data Provider connection for %s is not available!" + " This maybe a bug, it shouldn't happen!\n", domain)); + return EIO; + } + dbus_conn = sbus_get_connection(be_conn->conn); + + /* create the message */ + msg = dbus_message_new_method_call(NULL, + DP_PATH, + DP_INTERFACE, + DP_METHOD_GETACCTINFO); + if (msg == NULL) { + DEBUG(0,("Out of memory?!\n")); + return ENOMEM; + } + + DEBUG(4, ("Sending request for [%s][%u][%d][%s]\n", + domain, be_type, attrs, filter)); + + dbret = dbus_message_append_args(msg, + DBUS_TYPE_UINT32, &be_type, + DBUS_TYPE_UINT32, &attrs, + DBUS_TYPE_STRING, &filter, + DBUS_TYPE_INVALID); + if (!dbret) { + DEBUG(1,("Failed to build message\n")); + return EIO; + } + + dbret = dbus_connection_send_with_reply(dbus_conn, msg, + &pending_reply, timeout); + if (!dbret || pending_reply == NULL) { + /* + * 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; + } + + sdp_req = talloc_zero(rctx, struct sss_dp_req); + if (!sdp_req) { + dbus_message_unref(msg); + return ENOMEM; + } + sdp_req->ev = rctx->ev; + sdp_req->pending_reply = pending_reply; + + if (callback) { + cb = talloc_zero(callback_memctx, struct sss_dp_callback); + if (!cb) { + dbus_message_unref(msg); + talloc_zfree(sdp_req); + return ENOMEM; + } + cb->callback = callback; + cb->callback_ctx = callback_ctx; + cb->sdp_req = sdp_req; + + DLIST_ADD(sdp_req->cb_list, cb); + talloc_set_destructor((TALLOC_CTX *)cb, sss_dp_callback_destructor); + } + + /* Set up the reply handler */ + dbret = dbus_pending_call_set_notify(pending_reply, + sss_dp_send_acct_callback, + sdp_req, NULL); + if (!dbret) { + DEBUG(0, ("Could not queue up pending request!")); + talloc_zfree(sdp_req); + dbus_pending_call_cancel(pending_reply); + dbus_message_unref(msg); + return EIO; + } + + dbus_message_unref(msg); + + *ndp = sdp_req; + + return EOK; +} + +static int sss_dp_get_reply(DBusPendingCall *pending, + dbus_uint16_t *err_maj, + dbus_uint32_t *err_min, + 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,("Failed 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: + if (strcmp(dbus_message_get_error_name(reply), + DBUS_ERROR_NO_REPLY) == 0) { + err = ETIME; + goto done; + } + 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; +} + diff --git a/src/responder/common/responder_packet.c b/src/responder/common/responder_packet.c new file mode 100644 index 000000000..6e581a3c9 --- /dev/null +++ b/src/responder/common/responder_packet.c @@ -0,0 +1,253 @@ +/* + SSSD + + SSS Client Responder, command parser + + Copyright (C) Simo Sorce 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 . +*/ + +#include +#include +#include +#include +#include "talloc.h" +#include "util/util.h" +#include "responder/common/responder_packet.h" + +#define SSSSRV_PACKET_MEM_SIZE 512 + +struct sss_packet { + size_t memsize; + uint8_t *buffer; + + /* header */ + uint32_t *len; + uint32_t *cmd; + uint32_t *status; + uint32_t *reserved; + + uint8_t *body; + + /* io pointer */ + size_t iop; +}; + +/* + * Allocate a new packet structure + * + * - if size is defined use it otherwise the default packet will be + * SSSSRV_PACKET_MEM_SIZE bytes. + */ +int sss_packet_new(TALLOC_CTX *mem_ctx, size_t size, + enum sss_cli_command cmd, + struct sss_packet **rpacket) +{ + struct sss_packet *packet; + + packet = talloc(mem_ctx, struct sss_packet); + if (!packet) return ENOMEM; + + if (size) { + int n = (size + SSS_NSS_HEADER_SIZE) % SSSSRV_PACKET_MEM_SIZE; + packet->memsize = (n + 1) * SSSSRV_PACKET_MEM_SIZE; + } else { + packet->memsize = SSSSRV_PACKET_MEM_SIZE; + } + + packet->buffer = talloc_size(packet, packet->memsize); + if (!packet->buffer) { + talloc_free(packet); + return ENOMEM; + } + memset(packet->buffer, 0, SSS_NSS_HEADER_SIZE); + + packet->len = &((uint32_t *)packet->buffer)[0]; + packet->cmd = &((uint32_t *)packet->buffer)[1]; + packet->status = &((uint32_t *)packet->buffer)[2]; + packet->reserved = &((uint32_t *)packet->buffer)[3]; + packet->body = (uint8_t *)&((uint32_t *)packet->buffer)[4]; + + *(packet->len) = size + SSS_NSS_HEADER_SIZE; + *(packet->cmd) = cmd; + + packet->iop = 0; + + *rpacket = packet; + + return EOK; +} + +/* grows a packet size only in SSSSRV_PACKET_MEM_SIZE chunks */ +int sss_packet_grow(struct sss_packet *packet, size_t size) +{ + size_t totlen, len; + uint8_t *newmem; + + if (size == 0) { + return EOK; + } + + totlen = packet->memsize; + len = *packet->len + size; + + /* make sure we do not overflow */ + if (totlen < len) { + int n = len % SSSSRV_PACKET_MEM_SIZE + 1; + totlen += n * SSSSRV_PACKET_MEM_SIZE; + if (totlen < len) { + return EINVAL; + } + } + + if (totlen > packet->memsize) { + newmem = talloc_realloc_size(packet, packet->buffer, totlen); + if (!newmem) { + return ENOMEM; + } + + packet->memsize = totlen; + + /* re-set pointers if realloc had to move memory */ + if (newmem != packet->buffer) { + packet->buffer = newmem; + packet->len = &((uint32_t *)packet->buffer)[0]; + packet->cmd = &((uint32_t *)packet->buffer)[1]; + packet->status = &((uint32_t *)packet->buffer)[2]; + packet->reserved = &((uint32_t *)packet->buffer)[3]; + packet->body = (uint8_t *)&((uint32_t *)packet->buffer)[4]; + } + } + + *(packet->len) += size; + + return 0; +} + +/* reclaim backet previously resrved space in the packet + * usually done in functione recovering from not fatal erros */ +int sss_packet_shrink(struct sss_packet *packet, size_t size) +{ + size_t newlen; + + if (size > *(packet->len)) return EINVAL; + + newlen = *(packet->len) - size; + if (newlen < SSS_NSS_HEADER_SIZE) return EINVAL; + + *(packet->len) = newlen; + return 0; +} + +int sss_packet_set_size(struct sss_packet *packet, size_t size) +{ + size_t newlen; + + newlen = SSS_NSS_HEADER_SIZE + size; + + /* make sure we do not overflow */ + if (packet->memsize < newlen) return EINVAL; + + *(packet->len) = newlen; + + return 0; +} + +int sss_packet_recv(struct sss_packet *packet, int fd) +{ + size_t rb; + size_t len; + void *buf; + + buf = packet->buffer + packet->iop; + if (packet->iop > 4) len = *packet->len - packet->iop; + else len = packet->memsize - packet->iop; + + /* check for wrapping */ + if (len > packet->memsize) { + return EINVAL; + } + + errno = 0; + rb = recv(fd, buf, len, 0); + + if (rb == -1 && errno == EAGAIN) { + return EAGAIN; + } + + if (rb == 0) { + return ENODATA; + } + + if (*packet->len > packet->memsize) { + return EINVAL; + } + + packet->iop += rb; + if (packet->iop < 4) { + return EAGAIN; + } + + if (packet->iop < *packet->len) { + return EAGAIN; + } + + return EOK; +} + +int sss_packet_send(struct sss_packet *packet, int fd) +{ + size_t rb; + size_t len; + void *buf; + + buf = packet->buffer + packet->iop; + len = *packet->len - packet->iop; + + errno = 0; + rb = send(fd, buf, len, 0); + + if (rb == -1 && errno == EAGAIN) { + return EAGAIN; + } + + if (rb == 0) { + return EIO; + } + + packet->iop += rb; + + if (packet->iop < *packet->len) { + return EAGAIN; + } + + return EOK; +} + +enum sss_cli_command sss_packet_get_cmd(struct sss_packet *packet) +{ + return (enum sss_cli_command)(*packet->cmd); +} + +void sss_packet_get_body(struct sss_packet *packet, uint8_t **body, size_t *blen) +{ + *body = packet->body; + *blen = *packet->len - SSS_NSS_HEADER_SIZE; +} + +void sss_packet_set_error(struct sss_packet *packet, int error) +{ + *(packet->status) = error; +} diff --git a/src/responder/common/responder_packet.h b/src/responder/common/responder_packet.h new file mode 100644 index 000000000..2bfdc8a32 --- /dev/null +++ b/src/responder/common/responder_packet.h @@ -0,0 +1,43 @@ +/* + SSSD + + SSS Client Responder, header file + + Copyright (C) Simo Sorce 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 . +*/ + +#ifndef __SSSSRV_PACKET_H__ +#define __SSSSRV_PACKET_H__ + +#include "sss_client/sss_cli.h" + +#define SSS_PACKET_MAX_RECV_SIZE 1024 + +struct sss_packet; + +int sss_packet_new(TALLOC_CTX *mem_ctx, size_t size, + enum sss_cli_command cmd, + struct sss_packet **rpacket); +int sss_packet_grow(struct sss_packet *packet, size_t size); +int sss_packet_shrink(struct sss_packet *packet, size_t size); +int sss_packet_set_size(struct sss_packet *packet, size_t size); +int sss_packet_recv(struct sss_packet *packet, int fd); +int sss_packet_send(struct sss_packet *packet, int fd); +enum sss_cli_command sss_packet_get_cmd(struct sss_packet *packet); +void sss_packet_get_body(struct sss_packet *packet, uint8_t **body, size_t *blen); +void sss_packet_set_error(struct sss_packet *packet, int error); + +#endif /* __SSSSRV_PACKET_H__ */ diff --git a/src/responder/nss/nsssrv.c b/src/responder/nss/nsssrv.c new file mode 100644 index 000000000..7de346f0c --- /dev/null +++ b/src/responder/nss/nsssrv.c @@ -0,0 +1,367 @@ +/* + SSSD + + NSS Responder + + Copyright (C) Simo Sorce 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "popt.h" +#include "util/util.h" +#include "responder/nss/nsssrv.h" +#include "responder/nss/nsssrv_nc.h" +#include "db/sysdb.h" +#include "confdb/confdb.h" +#include "dbus/dbus.h" +#include "sbus/sssd_dbus.h" +#include "responder/common/responder_packet.h" +#include "providers/data_provider.h" +#include "monitor/monitor_interfaces.h" +#include "sbus/sbus_client.h" + +#define SSS_NSS_PIPE_NAME "nss" + +#define DEFAULT_PWFIELD "*" + +static int service_reload(DBusMessage *message, struct sbus_connection *conn); + +struct sbus_method monitor_nss_methods[] = { + { MON_CLI_METHOD_PING, monitor_common_pong }, + { MON_CLI_METHOD_RELOAD, service_reload }, + { MON_CLI_METHOD_RES_INIT, monitor_common_res_init }, + { NULL, NULL } +}; + +struct sbus_interface monitor_nss_interface = { + MONITOR_INTERFACE, + MONITOR_PATH, + SBUS_DEFAULT_VTABLE, + monitor_nss_methods, + NULL +}; + +static int service_reload(DBusMessage *message, struct sbus_connection *conn) +{ + /* Monitor calls this function when we need to reload + * our configuration information. Perform whatever steps + * are needed to update the configuration objects. + */ + + /* Send an empty reply to acknowledge receipt */ + return monitor_common_pong(message, conn); +} + +static int nss_get_config(struct nss_ctx *nctx, + struct resp_ctx *rctx, + struct confdb_ctx *cdb) +{ + TALLOC_CTX *tmpctx; + struct sss_domain_info *dom; + char *domain, *name; + char **filter_list; + int ret, i; + + tmpctx = talloc_new(nctx); + if (!tmpctx) return ENOMEM; + + ret = confdb_get_int(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_ENUM_CACHE_TIMEOUT, 120, + &nctx->enum_cache_timeout); + if (ret != EOK) goto done; + + ret = confdb_get_int(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_ENTRY_NEG_TIMEOUT, 15, + &nctx->neg_timeout); + if (ret != EOK) goto done; + + ret = confdb_get_bool(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_FILTER_USERS_IN_GROUPS, true, + &nctx->filter_users_in_groups); + if (ret != EOK) goto done; + + + ret = confdb_get_int(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_ENTRY_CACHE_NOWAIT_PERCENTAGE, 0, + &nctx->cache_refresh_percent); + if (ret != EOK) goto done; + if (nctx->cache_refresh_percent < 0 || + nctx->cache_refresh_percent > 99) { + DEBUG(0,("Configuration error: entry_cache_nowait_percentage is" + "invalid. Disabling feature.\n")); + nctx->cache_refresh_percent = 0; + } + + ret = confdb_get_string_as_list(cdb, tmpctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_FILTER_USERS, &filter_list); + if (ret == ENOENT) { + filter_list = talloc_array(tmpctx, char *, 2); + filter_list[0] = talloc_strdup(tmpctx, "root"); + filter_list[1] = NULL; + if (!filter_list || !filter_list[0]) { + ret = ENOMEM; + goto done; + } + ret = EOK; + } + else if (ret != EOK) goto done; + + for (i = 0; (filter_list && filter_list[i]); i++) { + ret = sss_parse_name(tmpctx, nctx->rctx->names, + filter_list[i], &domain, &name); + if (ret != EOK) { + DEBUG(1, ("Invalid name in filterUsers list: [%s] (%d)\n", + filter_list[i], ret)); + continue; + } + if (domain) { + ret = nss_ncache_set_user(nctx->ncache, true, domain, name); + if (ret != EOK) { + DEBUG(1, ("Failed to store permanent user filter for [%s]" + " (%d [%s])\n", filter_list[i], + ret, strerror(ret))); + continue; + } + } else { + for (dom = rctx->domains; dom; dom = dom->next) { + ret = nss_ncache_set_user(nctx->ncache, true, dom->name, name); + if (ret != EOK) { + DEBUG(1, ("Failed to store permanent user filter for" + " [%s:%s] (%d [%s])\n", + dom->name, filter_list[i], + ret, strerror(ret))); + continue; + } + } + } + } + + ret = confdb_get_string_as_list(cdb, tmpctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_FILTER_GROUPS, &filter_list); + if (ret == ENOENT) { + filter_list = talloc_array(tmpctx, char *, 2); + filter_list[0] = talloc_strdup(tmpctx, "root"); + filter_list[1] = NULL; + if (!filter_list || !filter_list[0]) { + ret = ENOMEM; + goto done; + } + ret = EOK; + } + else if (ret != EOK) goto done; + + for (i = 0; (filter_list && filter_list[i]); i++) { + ret = sss_parse_name(tmpctx, nctx->rctx->names, + filter_list[i], &domain, &name); + if (ret != EOK) { + DEBUG(1, ("Invalid name in filterGroups list: [%s] (%d)\n", + filter_list[i], ret)); + continue; + } + if (domain) { + ret = nss_ncache_set_group(nctx->ncache, true, domain, name); + if (ret != EOK) { + DEBUG(1, ("Failed to store permanent group filter for" + " [%s] (%d [%s])\n", filter_list[i], + ret, strerror(ret))); + continue; + } + } else { + for (dom = rctx->domains; dom; dom = dom->next) { + ret = nss_ncache_set_group(nctx->ncache, true, dom->name, name); + if (ret != EOK) { + DEBUG(1, ("Failed to store permanent group filter for" + " [%s:%s] (%d [%s])\n", + dom->name, filter_list[i], + ret, strerror(ret))); + continue; + } + } + } + } + + ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_PWFIELD, DEFAULT_PWFIELD, + &nctx->pwfield); + if (ret != EOK) goto done; + + ret = 0; +done: + talloc_free(tmpctx); + return ret; +} + +static struct sbus_method nss_dp_methods[] = { + { NULL, NULL } +}; + +struct sbus_interface nss_dp_interface = { + DP_INTERFACE, + DP_PATH, + SBUS_DEFAULT_VTABLE, + nss_dp_methods, + NULL +}; + + +static void nss_dp_reconnect_init(struct sbus_connection *conn, + int status, void *pvt) +{ + struct be_conn *be_conn = talloc_get_type(pvt, struct be_conn); + int ret; + + /* Did we reconnect successfully? */ + if (status == SBUS_RECONNECT_SUCCESS) { + DEBUG(1, ("Reconnected to the Data Provider.\n")); + + /* Identify ourselves to the data provider */ + ret = dp_common_send_id(be_conn->conn, + DATA_PROVIDER_VERSION, + "NSS", be_conn->domain->name); + /* all fine */ + if (ret == EOK) return; + } + + /* Failed to reconnect */ + DEBUG(0, ("Could not reconnect to %s provider.\n", + be_conn->domain->name)); + + /* FIXME: kill the frontend and let the monitor restart it ? */ + /* nss_shutdown(rctx); */ +} + +int nss_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb) +{ + struct sss_cmd_table *nss_cmds; + struct be_conn *iter; + struct nss_ctx *nctx; + int ret, max_retries; + + nctx = talloc_zero(mem_ctx, struct nss_ctx); + if (!nctx) { + DEBUG(0, ("fatal error initializing nss_ctx\n")); + return ENOMEM; + } + + ret = nss_ncache_init(nctx, &nctx->ncache); + if (ret != EOK) { + DEBUG(0, ("fatal error initializing negative cache\n")); + return ret; + } + + nss_cmds = get_nss_cmds(); + + ret = sss_process_init(nctx, ev, cdb, + nss_cmds, + SSS_NSS_SOCKET_NAME, NULL, + CONFDB_NSS_CONF_ENTRY, + NSS_SBUS_SERVICE_NAME, + NSS_SBUS_SERVICE_VERSION, + &monitor_nss_interface, + "NSS", &nss_dp_interface, + &nctx->rctx); + if (ret != EOK) { + return ret; + } + nctx->rctx->pvt_ctx = nctx; + + ret = nss_get_config(nctx, nctx->rctx, cdb); + if (ret != EOK) { + DEBUG(0, ("fatal error getting nss config\n")); + return ret; + } + + /* Enable automatic reconnection to the Data Provider */ + ret = confdb_get_int(nctx->rctx->cdb, nctx->rctx, + CONFDB_NSS_CONF_ENTRY, + CONFDB_SERVICE_RECON_RETRIES, + 3, &max_retries); + if (ret != EOK) { + DEBUG(0, ("Failed to set up automatic reconnection\n")); + return ret; + } + + for (iter = nctx->rctx->be_conns; iter; iter = iter->next) { + sbus_reconnect_init(iter->conn, max_retries, + nss_dp_reconnect_init, iter); + } + + DEBUG(1, ("NSS Initialization complete\n")); + + return EOK; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + struct main_context *main_ctx; + int ret; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + { NULL } + }; + + 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; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc... */ + debug_log_file = "sssd_nss"; + + ret = server_setup("sssd[nss]", 0, CONFDB_NSS_CONF_ENTRY, &main_ctx); + if (ret != EOK) return 2; + + 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 = nss_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx); + if (ret != EOK) return 3; + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} + diff --git a/src/responder/nss/nsssrv.h b/src/responder/nss/nsssrv.h new file mode 100644 index 000000000..a6c661835 --- /dev/null +++ b/src/responder/nss/nsssrv.h @@ -0,0 +1,70 @@ +/* + SSSD + + NSS Responder, header file + + Copyright (C) Simo Sorce 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 . +*/ + +#ifndef __NSSSRV_H__ +#define __NSSSRV_H__ + +#include +#include +#include "config.h" +#include "talloc.h" +#include "tevent.h" +#include "ldb.h" +#include "dbus/dbus.h" +#include "sbus/sssd_dbus.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "responder/nss/nsssrv_nc.h" + +#define NSS_SBUS_SERVICE_VERSION 0x0001 +#define NSS_SBUS_SERVICE_NAME "nss" + +#define NSS_PACKET_MAX_RECV_SIZE 1024 + +struct getent_ctx; + +struct nss_ctx { + struct resp_ctx *rctx; + + int neg_timeout; + struct nss_nc_ctx *ncache; + + int cache_refresh_percent; + + int enum_cache_timeout; + time_t last_user_enum; + time_t last_group_enum; + + struct getent_ctx *pctx; + struct getent_ctx *gctx; + + bool filter_users_in_groups; + + char *pwfield; +}; + +struct nss_packet; + +int nss_cmd_execute(struct cli_ctx *cctx); + +struct sss_cmd_table *get_nss_cmds(void); + +#endif /* __NSSSRV_H__ */ diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c new file mode 100644 index 000000000..46d4a2361 --- /dev/null +++ b/src/responder/nss/nsssrv_cmd.c @@ -0,0 +1,3182 @@ +/* + SSSD + + NSS Responder + + Copyright (C) Simo Sorce 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 . +*/ + +#include "util/util.h" +#include "responder/nss/nsssrv.h" +#include "confdb/confdb.h" +#include "db/sysdb.h" +#include + +struct nss_cmd_ctx { + struct cli_ctx *cctx; + char *name; + uint32_t id; + + bool immediate; + bool check_next; + bool enum_cached; +}; + +struct dom_ctx { + struct sss_domain_info *domain; + struct ldb_result *res; + int cur; +}; + +struct getent_ctx { + struct dom_ctx *doms; + int num; + int cur; +}; + +struct nss_dom_ctx { + struct nss_cmd_ctx *cmdctx; + struct sss_domain_info *domain; + + bool check_provider; + + /* cache results */ + struct ldb_result *res; +}; + +static int nss_cmd_send_error(struct nss_cmd_ctx *cmdctx, int err) +{ + struct cli_ctx *cctx = cmdctx->cctx; + int ret; + + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + return ret; + } + + sss_packet_set_error(cctx->creq->out, err); + return EOK; +} + +#define NSS_CMD_FATAL_ERROR(cctx) do { \ + DEBUG(1,("Fatal error, killing connection!")); \ + talloc_free(cctx); \ + return; \ +} while(0) + +#define NSS_CMD_FATAL_ERROR_CODE(cctx, ret) do { \ + DEBUG(1,("Fatal error, killing connection!")); \ + talloc_free(cctx); \ + return ret; \ +} while(0) + +static struct sss_domain_info *nss_get_dom(struct sss_domain_info *doms, + const char *domain) +{ + struct sss_domain_info *dom; + + for (dom = doms; dom; dom = dom->next) { + if (strcasecmp(dom->name, domain) == 0) break; + } + if (!dom) DEBUG(2, ("Unknown domain [%s]!\n", domain)); + + return dom; +} + +static int fill_empty(struct sss_packet *packet) +{ + uint8_t *body; + size_t blen; + int ret; + + ret = sss_packet_grow(packet, 2*sizeof(uint32_t)); + if (ret != EOK) return ret; + + sss_packet_get_body(packet, &body, &blen); + ((uint32_t *)body)[0] = 0; /* num results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + + return EOK; +} + +/**************************************************************************** + * PASSWD db related functions + ***************************************************************************/ + +static int fill_pwent(struct sss_packet *packet, + struct sss_domain_info *dom, + struct nss_ctx *nctx, + bool filter_users, + struct ldb_message **msgs, + int count) +{ + struct ldb_message *msg; + uint8_t *body; + const char *name; + const char *gecos; + const char *homedir; + const char *shell; + uint32_t uid; + uint32_t gid; + size_t rsize, rp, blen; + size_t s1, s2, s3, s4, s5; + size_t dom_len = 0; + int delim = 1; + int i, ret, num, t; + bool add_domain = dom->fqnames; + const char *domain = dom->name; + const char *namefmt = nctx->rctx->names->fq_fmt; + bool packet_initialized = false; + int ncret; + + if (add_domain) dom_len = strlen(domain); + + rp = 2*sizeof(uint32_t); + + num = 0; + for (i = 0; i < count; i++) { + msg = msgs[i]; + + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + uid = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0); + gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0); + + if (!name || !uid || !gid) { + DEBUG(1, ("Incomplete user object for %s[%llu]! Skipping\n", + name?name:"", (unsigned long long int)uid)); + continue; + } + + if (filter_users) { + ncret = nss_ncache_check_user(nctx->ncache, + nctx->neg_timeout, + domain, name); + if (ncret == EEXIST) { + DEBUG(4, ("User [%s@%s] filtered out! (negative cache)\n", + name, domain)); + continue; + } + } + + if (!packet_initialized) { + /* first 2 fields (len and reserved), filled up later */ + ret = sss_packet_grow(packet, 2*sizeof(uint32_t)); + if (ret != EOK) return ret; + packet_initialized = true; + } + + gecos = ldb_msg_find_attr_as_string(msg, SYSDB_GECOS, NULL); + homedir = ldb_msg_find_attr_as_string(msg, SYSDB_HOMEDIR, NULL); + shell = ldb_msg_find_attr_as_string(msg, SYSDB_SHELL, NULL); + + if (!gecos) gecos = ""; + if (!homedir) homedir = "/"; + if (!shell) shell = ""; + + s1 = strlen(name) + 1; + s2 = strlen(gecos) + 1; + s3 = strlen(homedir) + 1; + s4 = strlen(shell) + 1; + s5 = strlen(nctx->pwfield) + 1; + if (add_domain) s1 += delim + dom_len; + + rsize = 2*sizeof(uint32_t) +s1 + s2 + s3 + s4 + s5; + + ret = sss_packet_grow(packet, rsize); + if (ret != EOK) { + num = 0; + goto done; + } + sss_packet_get_body(packet, &body, &blen); + + ((uint32_t *)(&body[rp]))[0] = uid; + ((uint32_t *)(&body[rp]))[1] = gid; + rp += 2*sizeof(uint32_t); + + if (add_domain) { + ret = snprintf((char *)&body[rp], s1, namefmt, name, domain); + if (ret >= s1) { + /* need more space, got creative with the print format ? */ + t = ret - s1 + 1; + ret = sss_packet_grow(packet, t); + if (ret != EOK) { + num = 0; + goto done; + } + delim += t; + s1 += t; + sss_packet_get_body(packet, &body, &blen); + + /* retry */ + ret = snprintf((char *)&body[rp], s1, namefmt, name, domain); + } + + if (ret != s1-1) { + DEBUG(1, ("Failed to generate a fully qualified name for user " + "[%s] in [%s]! Skipping user.\n", name, domain)); + continue; + } + } else { + memcpy(&body[rp], name, s1); + } + rp += s1; + + memcpy(&body[rp], nctx->pwfield, s5); + rp += s5; + memcpy(&body[rp], gecos, s2); + rp += s2; + memcpy(&body[rp], homedir, s3); + rp += s3; + memcpy(&body[rp], shell, s4); + rp += s4; + + num++; + } + +done: + /* if there are no results just return ENOENT, + * let the caller decide if this is the last packet or not */ + if (!packet_initialized) return ENOENT; + + sss_packet_get_body(packet, &body, &blen); + ((uint32_t *)body)[0] = num; /* num results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + + return EOK; +} + +static errno_t check_cache(struct nss_dom_ctx *dctx, + struct nss_ctx *nctx, + struct ldb_result *res, + int req_type, + const char *opt_name, + uint32_t opt_id, + sss_dp_callback_t callback) +{ + errno_t ret; + int timeout; + time_t now; + uint64_t lastUpdate; + uint64_t cacheExpire = 0; + uint64_t midpoint_refresh; + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + bool off_band_update = false; + + /* when searching for a user, more than one reply is a db error */ + if ((req_type == SSS_DP_USER) && (res->count > 1)) { + DEBUG(1, ("getpwXXX call returned more than one result!" + " DB Corrupted?\n")); + ret = nss_cmd_send_error(cmdctx, ENOENT); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR_CODE(cctx, ENOENT); + } + sss_cmd_done(cctx, cmdctx); + return ENOENT; + } + + /* if we have any reply let's check cache validity */ + if (res->count > 0) { + + now = time(NULL); + + lastUpdate = ldb_msg_find_attr_as_uint64(res->msgs[0], + SYSDB_LAST_UPDATE, 0); + if (req_type == SSS_DP_INITGROUPS) { + cacheExpire = ldb_msg_find_attr_as_uint64(res->msgs[0], + SYSDB_INITGR_EXPIRE, 1); + } + if (cacheExpire == 0) { + cacheExpire = ldb_msg_find_attr_as_uint64(res->msgs[0], + SYSDB_CACHE_EXPIRE, 0); + } + + midpoint_refresh = 0; + if(nctx->cache_refresh_percent) { + midpoint_refresh = lastUpdate + + (cacheExpire - lastUpdate)*nctx->cache_refresh_percent/100; + if (midpoint_refresh - lastUpdate < 10) { + /* If the percentage results in an expiration + * less than ten seconds after the lastUpdate time, + * that's too often we will simply set it to 10s + */ + midpoint_refresh = lastUpdate+10; + } + } + + if (cacheExpire > now) { + /* cache still valid */ + + if (midpoint_refresh && midpoint_refresh < now) { + /* We're past the the cache refresh timeout + * We'll return the value from the cache, but we'll also + * queue the cache entry for update out-of-band. + */ + DEBUG(6, ("Performing midpoint cache update on [%s]\n", + opt_name)); + off_band_update = true; + } + else { + + /* Cache is still valid. Just return it. */ + return EOK; + } + } + } + + if (off_band_update) { + + timeout = SSS_CLI_SOCKET_TIMEOUT/2; + + /* No callback required + * This was an out-of-band update. We'll return EOK + * so the calling function can return the cached entry + * immediately. + */ + ret = sss_dp_send_acct_req(cctx->rctx, NULL, NULL, NULL, + timeout, dctx->domain->name, + true, req_type, + opt_name, opt_id); + if (ret != EOK) { + DEBUG(3, ("Failed to dispatch request: %d(%s)\n", + ret, strerror(ret))); + } else { + + DEBUG(3, ("Updating cache out-of-band\n")); + } + + } else { + /* This is a cache miss. Or the cache is expired. + * We need to get the updated user information before returning it. + */ + + /* dont loop forever :-) */ + dctx->check_provider = false; + timeout = SSS_CLI_SOCKET_TIMEOUT/2; + + /* keep around current data in case backend is offline */ + if (res->count) { + dctx->res = talloc_steal(dctx, res); + } + + ret = sss_dp_send_acct_req(cctx->rctx, cmdctx, + callback, dctx, timeout, + dctx->domain->name, + true, req_type, + opt_name, opt_id); + if (ret != EOK) { + DEBUG(3, ("Failed to dispatch request: %d(%s)\n", + ret, strerror(ret))); + ret = nss_cmd_send_error(cmdctx, ret); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR_CODE(cctx, EIO); + } + sss_cmd_done(cctx, cmdctx); + return EIO; + } + + return EAGAIN; + } + + return EOK; +} + +static void nss_cmd_getpwnam_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); + +static void nss_cmd_getpwnam_callback(void *ptr, int status, + struct ldb_result *res) +{ + struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + struct sysdb_ctx *sysdb; + struct sss_domain_info *dom; + struct nss_ctx *nctx; + uint8_t *body; + size_t blen; + bool neghit = false; + int ncret; + int ret; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + if (status != LDB_SUCCESS) { + ret = nss_cmd_send_error(cmdctx, status); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + + if (dctx->check_provider) { + ret = check_cache(dctx, nctx, res, + SSS_DP_USER, cmdctx->name, 0, + nss_cmd_getpwnam_dp_callback); + if (ret != EOK) { + /* Anything but EOK means we should reenter the mainloop + * because we may be refreshing the cache + */ + return; + } + } + + switch (res->count) { + case 0: + if (cmdctx->check_next) { + + ret = EOK; + + /* skip domains that require FQnames or have negative caches */ + for (dom = dctx->domain->next; dom; dom = dom->next) { + + if (dom->fqnames) continue; + + ncret = nss_ncache_check_user(nctx->ncache, + nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; + + neghit = true; + } + /* reset neghit if we still have a domain to check */ + if (dom) neghit = false; + + if (neghit) { + DEBUG(2, ("User [%s] does not exist! (negative cache)\n", + cmdctx->name)); + ret = ENOENT; + } + if (dom == NULL) { + DEBUG(2, ("No matching domain found for [%s], fail!\n", + cmdctx->name)); + ret = ENOENT; + } + + if (ret == EOK) { + dctx->domain = dom; + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + if (dctx->res) talloc_free(res); + dctx->res = NULL; + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getpwnam(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getpwnam_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + } + + /* we made another call, end here */ + if (ret == EOK) return; + } + + DEBUG(2, ("No results for getpwnam call\n")); + + /* set negative cache only if not result of cache check */ + if (!neghit) { + ret = nss_ncache_set_user(nctx->ncache, false, + dctx->domain->name, cmdctx->name); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + } + + ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_packet_get_body(cctx->creq->out, &body, &blen); + ((uint32_t *)body)[0] = 0; /* 0 results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + break; + + case 1: + DEBUG(6, ("Returning info for user [%s]\n", cmdctx->name)); + + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + ret = fill_pwent(cctx->creq->out, + dctx->domain, + nctx, false, + res->msgs, res->count); + if (ret == ENOENT) { + ret = fill_empty(cctx->creq->out); + } + sss_packet_set_error(cctx->creq->out, ret); + + break; + + default: + DEBUG(1, ("getpwnam call returned more than one result !?!\n")); + ret = nss_cmd_send_error(cmdctx, ENOENT); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + } + + sss_cmd_done(cctx, cmdctx); +} + +static void nss_cmd_getpwnam_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr) +{ + struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + struct sysdb_ctx *sysdb; + int ret; + + if (err_maj) { + DEBUG(2, ("Unable to get information from Data Provider\n" + "Error: %u, %u, %s\n" + "Will try to return what we have in cache\n", + (unsigned int)err_maj, (unsigned int)err_min, err_msg)); + + if (!dctx->res) { + /* return 0 results */ + dctx->res = talloc_zero(dctx, struct ldb_result); + if (!dctx->res) { + ret = ENOMEM; + goto done; + } + } + + nss_cmd_getpwnam_callback(dctx, LDB_SUCCESS, dctx->res); + return; + } + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getpwnam(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getpwnam_callback, dctx); + +done: + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache! (%d [%s])\n", + ret, strerror(ret))); + + ret = nss_cmd_send_error(cmdctx, ret); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + } +} + +static int nss_cmd_getpwnam(struct cli_ctx *cctx) +{ + struct nss_cmd_ctx *cmdctx; + struct nss_dom_ctx *dctx; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + const char *rawname; + char *domname; + uint8_t *body; + size_t blen; + int ret; + int ncret; + bool neghit = false; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); + if (!cmdctx) { + return ENOMEM; + } + cmdctx->cctx = cctx; + + dctx = talloc_zero(cmdctx, struct nss_dom_ctx); + if (!dctx) { + ret = ENOMEM; + goto done; + } + dctx->cmdctx = cmdctx; + + /* get user name to query */ + sss_packet_get_body(cctx->creq->in, &body, &blen); + + /* if not terminated fail */ + if (body[blen -1] != '\0') { + ret = EINVAL; + goto done; + } + rawname = (const char *)body; + + domname = NULL; + ret = sss_parse_name(cmdctx, cctx->rctx->names, rawname, + &domname, &cmdctx->name); + if (ret != EOK) { + DEBUG(2, ("Invalid name received [%s]\n", rawname)); + ret = ENOENT; + goto done; + } + + DEBUG(4, ("Requesting info for [%s] from [%s]\n", + cmdctx->name, domname?domname:"")); + + if (domname) { + dctx->domain = nss_get_dom(cctx->rctx->domains, domname); + if (!dctx->domain) { + ret = ENOENT; + goto done; + } + + /* verify this user has not yet been negatively cached, + * or has been permanently filtered */ + ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout, + dctx->domain->name, cmdctx->name); + if (ncret == EEXIST) { + neghit = true; + } + } + else { + /* skip domains that require FQnames or have negative caches */ + for (dom = cctx->rctx->domains; dom; dom = dom->next) { + + if (dom->fqnames) continue; + + /* verify this user has not yet been negatively cached, + * or has been permanently filtered */ + ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; + + neghit = true; + } + /* reset neghit if we still have a domain to check */ + if (dom) neghit = false; + + dctx->domain = dom; + } + if (neghit) { + DEBUG(2, ("User [%s] does not exist! (negative cache)\n", rawname)); + ret = ENOENT; + goto done; + } + if (dctx->domain == NULL) { + DEBUG(2, ("No matching domain found for [%s], fail!\n", rawname)); + ret = ENOENT; + goto done; + } + + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + + if (!domname) { + /* this is a multidomain search */ + cmdctx->check_next = true; + } + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + ret = EFAULT; + goto done; + } + ret = sysdb_getpwnam(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getpwnam_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + +done: + if (ret != EOK) { + if (ret == ENOENT) { + /* we do not have any entry to return */ + ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret == EOK) { + sss_packet_get_body(cctx->creq->out, &body, &blen); + ((uint32_t *)body)[0] = 0; /* 0 results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + } + } + if (ret != EOK) { + ret = nss_cmd_send_error(cmdctx, ret); + } + if (ret == EOK) { + sss_cmd_done(cctx, cmdctx); + } + return ret; + } + + return EOK; +} + +static void nss_cmd_getpwuid_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); + +static void nss_cmd_getpwuid_callback(void *ptr, int status, + struct ldb_result *res) +{ + struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + uint8_t *body; + size_t blen; + bool neghit = false; + int ret; + int ncret; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + if (status != LDB_SUCCESS) { + ret = nss_cmd_send_error(cmdctx, status); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + + if (dctx->check_provider) { + ret = check_cache(dctx, nctx, res, + SSS_DP_USER, NULL, cmdctx->id, + nss_cmd_getpwuid_dp_callback); + if (ret != EOK) { + /* Anything but EOK means we should reenter the mainloop + * because we may be refreshing the cache + */ + return; + } + } + + switch (res->count) { + case 0: + if (cmdctx->check_next) { + + ret = EOK; + + dom = dctx->domain->next; + ncret = nss_ncache_check_uid(nctx->ncache, nctx->neg_timeout, + cmdctx->id); + if (ncret == EEXIST) { + DEBUG(3, ("Uid [%lu] does not exist! (negative cache)\n", + (unsigned long)cmdctx->id)); + ret = ENOENT; + } + if (dom == NULL) { + DEBUG(0, ("No matching domain found for [%lu], fail!\n", + (unsigned long)cmdctx->id)); + ret = ENOENT; + } + + if (ret == EOK) { + dctx->domain = dom; + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + if (dctx->res) talloc_free(res); + dctx->res = NULL; + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getpwuid(cmdctx, sysdb, + dctx->domain, cmdctx->id, + nss_cmd_getpwuid_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + } + + /* we made another call, end here */ + if (ret == EOK) return; + } + + DEBUG(2, ("No results for getpwuid call\n")); + + /* set negative cache only if not result of cache check */ + if (!neghit) { + ret = nss_ncache_set_uid(nctx->ncache, false, cmdctx->id); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + } + + ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_packet_get_body(cctx->creq->out, &body, &blen); + ((uint32_t *)body)[0] = 0; /* 0 results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + break; + + case 1: + DEBUG(6, ("Returning info for user [%u]\n", (unsigned)cmdctx->id)); + + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + + ret = fill_pwent(cctx->creq->out, + dctx->domain, + nctx, true, + res->msgs, res->count); + if (ret == ENOENT) { + ret = fill_empty(cctx->creq->out); + } + sss_packet_set_error(cctx->creq->out, ret); + + break; + + default: + DEBUG(1, ("getpwnam call returned more than one result !?!\n")); + ret = nss_cmd_send_error(cmdctx, ENOENT); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + } + + sss_cmd_done(cctx, cmdctx); +} + +static void nss_cmd_getpwuid_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr) +{ + struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + struct sysdb_ctx *sysdb; + int ret; + + if (err_maj) { + DEBUG(2, ("Unable to get information from Data Provider\n" + "Error: %u, %u, %s\n" + "Will try to return what we have in cache\n", + (unsigned int)err_maj, (unsigned int)err_min, err_msg)); + + if (!dctx->res) { + /* return 0 results */ + dctx->res = talloc_zero(dctx, struct ldb_result); + if (!dctx->res) { + ret = ENOMEM; + goto done; + } + } + + nss_cmd_getpwuid_callback(dctx, LDB_SUCCESS, dctx->res); + return; + } + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getpwuid(cmdctx, sysdb, + dctx->domain, cmdctx->id, + nss_cmd_getpwuid_callback, dctx); + +done: + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + + ret = nss_cmd_send_error(cmdctx, ret); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + } +} + +static int nss_cmd_getpwuid(struct cli_ctx *cctx) +{ + struct nss_cmd_ctx *cmdctx; + struct nss_dom_ctx *dctx; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + uint8_t *body; + size_t blen; + int ret; + int ncret; + + ret = ENOENT; + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); + if (!cmdctx) { + return ENOMEM; + } + cmdctx->cctx = cctx; + + dctx = talloc_zero(cmdctx, struct nss_dom_ctx); + if (!dctx) { + ret = ENOMEM; + goto done; + } + dctx->cmdctx = cmdctx; + + /* get uid to query */ + sss_packet_get_body(cctx->creq->in, &body, &blen); + + if (blen != sizeof(uint32_t)) { + ret = EINVAL; + goto done; + } + cmdctx->id = *((uint32_t *)body); + + /* this is a multidomain search */ + cmdctx->check_next = true; + + for (dom = cctx->rctx->domains; dom; dom = dom->next) { + /* verify this user has not yet been negatively cached, + * or has been permanently filtered */ + ncret = nss_ncache_check_uid(nctx->ncache, nctx->neg_timeout, + cmdctx->id); + if (ncret == EEXIST) { + DEBUG(3, ("Uid [%lu] does not exist! (negative cache)\n", + (unsigned long)cmdctx->id)); + continue; + } + + /* check that the uid is valid for this domain */ + if ((dom->id_min && (cmdctx->id < dom->id_min)) || + (dom->id_max && (cmdctx->id > dom->id_max))) { + DEBUG(4, ("Uid [%lu] does not exist in domain [%s]! " + "(id out of range)\n", + (unsigned long)cmdctx->id, dom->name)); + continue; + } + + dctx->domain = dom; + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + + DEBUG(4, ("Requesting info for [%lu@%s]\n", + cmdctx->id, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + ret = EFAULT; + goto done; + } + ret = sysdb_getpwuid(cmdctx, sysdb, + dctx->domain, cmdctx->id, + nss_cmd_getpwuid_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + + break; + } + +done: + if (ret != EOK) { + if (ret == ENOENT) { + /* we do not have any entry to return */ + ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret == EOK) { + sss_packet_get_body(cctx->creq->out, &body, &blen); + ((uint32_t *)body)[0] = 0; /* 0 results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + } + } + if (ret != EOK) { + ret = nss_cmd_send_error(cmdctx, ret); + } + if (ret == EOK) { + sss_cmd_done(cctx, cmdctx); + } + return ret; + } + + return EOK; +} + +/* to keep it simple at this stage we are retrieving the + * full enumeration again for each request for each process + * and we also block on setpwent() for the full time needed + * to retrieve the data. And endpwent() frees all the data. + * Next steps are: + * - use an nsssrv wide cache with data already structured + * so that it can be immediately returned (see nscd way) + * - use mutexes so that setpwent() can return immediately + * even if the data is still being fetched + * - make getpwent() wait on the mutex + */ +static int nss_cmd_getpwent_immediate(struct nss_cmd_ctx *cmdctx); + +static void nss_cmd_setpw_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); + +static void nss_cmd_setpwent_callback(void *ptr, int status, + struct ldb_result *res) +{ + struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct getent_ctx *pctx; + struct nss_ctx *nctx; + int timeout; + int ret; + + if (status != LDB_SUCCESS) { + ret = nss_cmd_send_error(cmdctx, ENOENT); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + pctx = nctx->pctx; + if (pctx == NULL) { + pctx = talloc_zero(nctx, struct getent_ctx); + if (!pctx) { + ret = nss_cmd_send_error(cmdctx, ENOMEM); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + nctx->pctx = pctx; + } + + pctx->doms = talloc_realloc(pctx, pctx->doms, struct dom_ctx, pctx->num +1); + if (!pctx->doms) { + talloc_free(pctx); + nctx->pctx = NULL; + NSS_CMD_FATAL_ERROR(cctx); + } + + pctx->doms[pctx->num].domain = dctx->domain; + pctx->doms[pctx->num].res = talloc_steal(pctx->doms, res); + pctx->doms[pctx->num].cur = 0; + + pctx->num++; + + /* do not reply until all domain searches are done */ + for (dom = dctx->domain->next; dom; dom = dom->next) { + if (dom->enumerate != 0) break; + } + dctx->domain = dom; + + if (dctx->domain != NULL) { + if (cmdctx->enum_cached) { + dctx->check_provider = false; + } else { + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + } + + if (dctx->check_provider) { + timeout = SSS_CLI_SOCKET_TIMEOUT; + ret = sss_dp_send_acct_req(cctx->rctx, cmdctx, + nss_cmd_setpw_dp_callback, dctx, + timeout, dom->name, true, + SSS_DP_USER, NULL, 0); + } else { + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_enumpwent(dctx, sysdb, + dctx->domain, NULL, + nss_cmd_setpwent_callback, dctx); + } + if (ret != EOK) { + /* FIXME: shutdown ? */ + DEBUG(1, ("Failed to send enumeration request for domain [%s]!\n", + dom->name)); + + ret = nss_cmd_send_error(cmdctx, ret); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + } + return; + } + + /* set cache mark */ + nctx->last_user_enum = time(NULL); + + if (cmdctx->immediate) { + /* this was a getpwent call w/o setpwent, + * return immediately one result */ + ret = nss_cmd_getpwent_immediate(cmdctx); + if (ret != EOK) NSS_CMD_FATAL_ERROR(cctx); + return; + } + + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); +} + +static void nss_cmd_setpw_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr) +{ + struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + struct sysdb_ctx *sysdb; + int ret; + + if (err_maj) { + DEBUG(2, ("Unable to get information from Data Provider\n" + "Error: %u, %u, %s\n" + "Will try to return what we have in cache\n", + (unsigned int)err_maj, (unsigned int)err_min, err_msg)); + } + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_enumpwent(cmdctx, sysdb, + dctx->domain, NULL, + nss_cmd_setpwent_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + + ret = nss_cmd_send_error(cmdctx, ret); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + } +} + +static int nss_cmd_setpwent_ext(struct cli_ctx *cctx, bool immediate) +{ + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_cmd_ctx *cmdctx; + struct nss_dom_ctx *dctx; + struct nss_ctx *nctx; + time_t now = time(NULL); + int timeout; + uint8_t *body; + size_t blen; + int ret; + + DEBUG(4, ("Requesting info for all users\n")); + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + talloc_free(nctx->pctx); + nctx->pctx = NULL; + + cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); + if (!cmdctx) { + return ENOMEM; + } + cmdctx->cctx = cctx; + cmdctx->immediate = immediate; + + dctx = talloc_zero(cmdctx, struct nss_dom_ctx); + if (!dctx) { + ret = ENOMEM; + goto done; + } + dctx->cmdctx = cmdctx; + + /* do not query backends if we have a recent enumeration */ + if (nctx->enum_cache_timeout) { + if (nctx->last_user_enum + + nctx->enum_cache_timeout > now) { + cmdctx->enum_cached = true; + } + } + + /* check if enumeration is enabled in any domain */ + for (dom = cctx->rctx->domains; dom; dom = dom->next) { + if (dom->enumerate != 0) break; + } + dctx->domain = dom; + + if (dctx->domain == NULL) { + DEBUG(2, ("Enumeration disabled on all domains!\n")); + ret = ENOENT; + goto done; + } + + if (cmdctx->enum_cached) { + dctx->check_provider = false; + } else { + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + } + + if (dctx->check_provider) { + timeout = SSS_CLI_SOCKET_TIMEOUT; + ret = sss_dp_send_acct_req(cctx->rctx, cmdctx, + nss_cmd_setpw_dp_callback, dctx, + timeout, dom->name, true, + SSS_DP_USER, NULL, 0); + } else { + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + ret = EFAULT; + goto done; + } + ret = sysdb_enumpwent(dctx, sysdb, + dctx->domain, NULL, + nss_cmd_setpwent_callback, dctx); + } + if (ret != EOK) { + /* FIXME: shutdown ? */ + DEBUG(1, ("Failed to send enumeration request for domain [%s]!\n", + dom->name)); + } + +done: + if (ret != EOK) { + if (ret == ENOENT) { + if (cmdctx->immediate) { + /* we do not have any entry to return */ + ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret == EOK) { + sss_packet_get_body(cctx->creq->out, &body, &blen); + ((uint32_t *)body)[0] = 0; /* 0 results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + } + } + else { + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + } + } + if (ret != EOK) { + ret = nss_cmd_send_error(cmdctx, ret); + } + if (ret == EOK) { + sss_cmd_done(cctx, cmdctx); + } + return ret; + } + + return EOK; +} + +static int nss_cmd_setpwent(struct cli_ctx *cctx) +{ + return nss_cmd_setpwent_ext(cctx, false); +} + + +static int nss_cmd_retpwent(struct cli_ctx *cctx, int num) +{ + struct nss_ctx *nctx; + struct getent_ctx *pctx; + struct ldb_message **msgs = NULL; + struct dom_ctx *pdom = NULL; + int n = 0; + int ret; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + pctx = nctx->pctx; + +retry: + if (pctx->cur >= pctx->num) goto none; + + pdom = &pctx->doms[pctx->cur]; + + n = pdom->res->count - pdom->cur; + if (n == 0 && (pctx->cur+1 < pctx->num)) { + pctx->cur++; + pdom = &pctx->doms[pctx->cur]; + n = pdom->res->count - pdom->cur; + } + + if (!n) goto none; + + if (n > num) n = num; + + msgs = &(pdom->res->msgs[pdom->cur]); + pdom->cur += n; + + ret = fill_pwent(cctx->creq->out, pdom->domain, nctx, true, msgs, n); + if (ret == ENOENT) goto retry; + return ret; + +none: + return fill_empty(cctx->creq->out); +} + +/* used only if a process calls getpwent() without first calling setpwent() + */ +static int nss_cmd_getpwent_immediate(struct nss_cmd_ctx *cmdctx) +{ + struct cli_ctx *cctx = cmdctx->cctx; + uint8_t *body; + size_t blen; + uint32_t num; + int ret; + + /* get max num of entries to return in one call */ + sss_packet_get_body(cctx->creq->in, &body, &blen); + if (blen != sizeof(uint32_t)) { + return EINVAL; + } + num = *((uint32_t *)body); + + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + return ret; + } + + ret = nss_cmd_retpwent(cctx, num); + + sss_packet_set_error(cctx->creq->out, ret); + sss_cmd_done(cctx, cmdctx); + + return EOK; +} + +static int nss_cmd_getpwent(struct cli_ctx *cctx) +{ + struct nss_ctx *nctx; + struct nss_cmd_ctx *cmdctx; + + DEBUG(4, ("Requesting info for all accounts\n")); + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + /* see if we need to trigger an implicit setpwent() */ + if (nctx->pctx == NULL) { + nctx->pctx = talloc_zero(nctx, struct getent_ctx); + if (!nctx->pctx) return ENOMEM; + + return nss_cmd_setpwent_ext(cctx, true); + } + + cmdctx = talloc(cctx, struct nss_cmd_ctx); + if (!cmdctx) { + return ENOMEM; + } + cmdctx->cctx = cctx; + + return nss_cmd_getpwent_immediate(cmdctx); +} + +static int nss_cmd_endpwent(struct cli_ctx *cctx) +{ + struct nss_ctx *nctx; + int ret; + + DEBUG(4, ("Terminating request info for all accounts\n")); + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + + if (nctx->pctx == NULL) goto done; + + /* free results and reset */ + talloc_free(nctx->pctx); + nctx->pctx = NULL; + +done: + sss_cmd_done(cctx, NULL); + return EOK; +} + +/**************************************************************************** + * GROUP db related functions + ***************************************************************************/ + +#define GID_ROFFSET 0 +#define MNUM_ROFFSET sizeof(uint32_t) +#define STRS_ROFFSET 2*sizeof(uint32_t) + +static int fill_grent(struct sss_packet *packet, + struct sss_domain_info *dom, + struct nss_ctx *nctx, + bool filter_groups, + struct ldb_message **msgs, + int max, int *count) +{ + struct ldb_message *msg; + struct ldb_message_element *el; + uint8_t *body; + size_t blen; + uint32_t gid; + const char *name; + size_t nsize; + size_t delim; + size_t dom_len; + size_t pwlen; + int i = 0; + int j = 0; + int ret, num, memnum; + size_t rzero, rsize; + bool add_domain = dom->fqnames; + const char *domain = dom->name; + const char *namefmt = nctx->rctx->names->fq_fmt; + + if (add_domain) { + delim = 1; + dom_len = strlen(domain); + } else { + delim = 0; + dom_len = 0; + } + + num = 0; + pwlen = strlen(nctx->pwfield) + 1; + + /* first 2 fields (len and reserved), filled up later */ + ret = sss_packet_grow(packet, 2*sizeof(uint32_t)); + if (ret != EOK) { + goto done; + } + sss_packet_get_body(packet, &body, &blen); + rzero = 2*sizeof(uint32_t); + rsize = 0; + + for (i = 0; i < *count; i++) { + msg = msgs[i]; + + /* new group */ + if (!ldb_msg_check_string_attribute(msg, "objectClass", + SYSDB_GROUP_CLASS)) { + DEBUG(1, ("Wrong object (%s) found on stack!\n", + ldb_dn_get_linearized(msg->dn))); + continue; + } + + /* if we reached the max allowed entries, simply return */ + if (num >= max) { + goto done; + } + + /* new result starts at end of previous result */ + rzero += rsize; + rsize = 0; + + /* find group name/gid */ + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0); + if (!name || !gid) { + DEBUG(1, ("Incomplete group object for %s[%llu]! Skipping\n", + name?name:"", (unsigned long long int)gid)); + continue; + } + + if (filter_groups) { + ret = nss_ncache_check_group(nctx->ncache, + nctx->neg_timeout, domain, name); + if (ret == EEXIST) { + DEBUG(4, ("Group [%s@%s] filtered out! (negative cache)\n", + name, domain)); + continue; + } + } + + nsize = strlen(name) + 1; /* includes terminating \0 */ + if (add_domain) nsize += delim + dom_len; + + /* fill in gid and name and set pointer for number of members */ + rsize = STRS_ROFFSET + nsize + pwlen; /* name\0x\0 */ + + ret = sss_packet_grow(packet, rsize); + if (ret != EOK) { + num = 0; + goto done; + } + sss_packet_get_body(packet, &body, &blen); + + /* 0-3: 32bit number gid */ + ((uint32_t *)(&body[rzero+GID_ROFFSET]))[0] = gid; + + /* 4-7: 32bit unsigned number of members */ + ((uint32_t *)(&body[rzero+MNUM_ROFFSET]))[0] = 0; + + /* 8-X: sequence of strings (name, passwd, mem..) */ + if (add_domain) { + ret = snprintf((char *)&body[rzero+STRS_ROFFSET], + nsize, namefmt, name, domain); + if (ret >= nsize) { + /* need more space, got creative with the print format ? */ + int t = ret - nsize + 1; + ret = sss_packet_grow(packet, t); + if (ret != EOK) { + num = 0; + goto done; + } + sss_packet_get_body(packet, &body, &blen); + rsize += t; + delim += t; + nsize += t; + + /* retry */ + ret = snprintf((char *)&body[rzero+STRS_ROFFSET], + nsize, namefmt, name, domain); + } + + if (ret != nsize-1) { + DEBUG(1, ("Failed to generate a fully qualified name for" + " group [%s] in [%s]! Skipping\n", name, domain)); + /* reclaim space */ + ret = sss_packet_shrink(packet, rsize); + if (ret != EOK) { + num = 0; + goto done; + } + rsize = 0; + continue; + } + } else { + memcpy(&body[rzero+STRS_ROFFSET], name, nsize); + } + + /* group passwd field */ + memcpy(&body[rzero + rsize -pwlen], nctx->pwfield, pwlen); + + el = ldb_msg_find_element(msg, SYSDB_MEMBERUID); + if (el) { + memnum = 0; + + for (j = 0; j < el->num_values; j++) { + name = (const char *)el->values[j].data; + + if (nctx->filter_users_in_groups) { + ret = nss_ncache_check_user(nctx->ncache, + nctx->neg_timeout, + domain, name); + if (ret == EEXIST) { + DEBUG(6, ("Group [%s] member [%s@%s] filtered out!" + " (negative cache)\n", + (char *)&body[rzero+STRS_ROFFSET], + name, domain)); + continue; + } + } + + nsize = strlen(name) + 1; /* includes terminating \0 */ + if (add_domain) nsize += delim + dom_len; + + ret = sss_packet_grow(packet, nsize); + if (ret != EOK) { + num = 0; + goto done; + } + sss_packet_get_body(packet, &body, &blen); + + if (add_domain) { + ret = snprintf((char *)&body[rzero + rsize], + nsize, namefmt, name, domain); + if (ret >= nsize) { + /* need more space, + * got creative with the print format ? */ + int t = ret - nsize + 1; + ret = sss_packet_grow(packet, t); + if (ret != EOK) { + num = 0; + goto done; + } + sss_packet_get_body(packet, &body, &blen); + delim += t; + nsize += t; + + /* retry */ + ret = snprintf((char *)&body[rzero + rsize], + nsize, namefmt, name, domain); + } + + if (ret != nsize-1) { + DEBUG(1, ("Failed to generate a fully qualified name" + " for member [%s@%s] of group [%s]!" + " Skipping\n", name, domain, + (char *)&body[rzero+STRS_ROFFSET])); + /* reclaim space */ + ret = sss_packet_shrink(packet, nsize); + if (ret != EOK) { + num = 0; + goto done; + } + continue; + } + + } else { + memcpy(&body[rzero + rsize], name, nsize); + } + + rsize += nsize; + + memnum++; + } + + if (memnum) { + /* set num of members */ + ((uint32_t *)(&body[rzero+MNUM_ROFFSET]))[0] = memnum; + } + } + + num++; + continue; + } + +done: + *count = i; + + if (num == 0) { + /* if num is 0 most probably something went wrong, + * reset packet and return ENOENT */ + ret = sss_packet_set_size(packet, 0); + if (ret != EOK) return ret; + return ENOENT; + } + + ((uint32_t *)body)[0] = num; /* num results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + + return EOK; +} + +static void nss_cmd_getgrnam_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); + +static void nss_cmd_getgrnam_callback(void *ptr, int status, + struct ldb_result *res) +{ + struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + uint8_t *body; + size_t blen; + bool neghit = false; + int ncret; + int i, ret; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + if (status != LDB_SUCCESS) { + ret = nss_cmd_send_error(cmdctx, status); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + + if (dctx->check_provider) { + ret = check_cache(dctx, nctx, res, + SSS_DP_GROUP, cmdctx->name, 0, + nss_cmd_getgrnam_dp_callback); + if (ret != EOK) { + /* Anything but EOK means we should reenter the mainloop + * because we may be refreshing the cache + */ + return; + } + } + + switch (res->count) { + case 0: + if (cmdctx->check_next) { + + ret = EOK; + + /* skip domains that require FQnames or have negative caches */ + for (dom = dctx->domain->next; dom; dom = dom->next) { + + if (dom->fqnames) continue; + + ncret = nss_ncache_check_group(nctx->ncache, + nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; + + neghit = true; + } + /* reset neghit if we still have a domain to check */ + if (dom) neghit = false; + + if (neghit) { + DEBUG(2, ("Group [%s] does not exist! (negative cache)\n", + cmdctx->name)); + ret = ENOENT; + } + if (dom == NULL) { + DEBUG(2, ("No matching domain found for [%s], fail!\n", + cmdctx->name)); + ret = ENOENT; + } + + if (ret == EOK) { + dctx->domain = dom; + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + if (dctx->res) talloc_free(res); + dctx->res = NULL; + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getgrnam(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getgrnam_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + } + + /* we made another call, end here */ + if (ret == EOK) return; + } + + + DEBUG(2, ("No results for getgrnam call\n")); + + /* set negative cache only if not result of cache check */ + if (!neghit) { + ret = nss_ncache_set_group(nctx->ncache, false, + dctx->domain->name, cmdctx->name); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + } + + ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_packet_get_body(cctx->creq->out, &body, &blen); + ((uint32_t *)body)[0] = 0; /* 0 results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + break; + + default: + + DEBUG(6, ("Returning info for group [%s]\n", cmdctx->name)); + + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + i = res->count; + ret = fill_grent(cctx->creq->out, + dctx->domain, + nctx, false, + res->msgs, 1, &i); + if (ret == ENOENT) { + ret = fill_empty(cctx->creq->out); + } + sss_packet_set_error(cctx->creq->out, ret); + } + + sss_cmd_done(cctx, cmdctx); +} + +static void nss_cmd_getgrnam_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr) +{ + struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + struct sysdb_ctx *sysdb; + int ret; + + if (err_maj) { + DEBUG(2, ("Unable to get information from Data Provider\n" + "Error: %u, %u, %s\n" + "Will try to return what we have in cache\n", + (unsigned int)err_maj, (unsigned int)err_min, err_msg)); + + if (!dctx->res) { + /* return 0 results */ + dctx->res = talloc_zero(dctx, struct ldb_result); + if (!dctx->res) { + ret = ENOMEM; + goto done; + } + } + + nss_cmd_getgrnam_callback(dctx, LDB_SUCCESS, dctx->res); + return; + } + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getgrnam(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getgrnam_callback, dctx); + +done: + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache! (%d [%s])\n", + ret, strerror(ret))); + + ret = nss_cmd_send_error(cmdctx, ret); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + } +} + +static int nss_cmd_getgrnam(struct cli_ctx *cctx) +{ + struct nss_cmd_ctx *cmdctx; + struct nss_dom_ctx *dctx; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + const char *rawname; + char *domname; + uint8_t *body; + size_t blen; + int ret; + int ncret; + bool neghit = false; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); + if (!cmdctx) { + return ENOMEM; + } + cmdctx->cctx = cctx; + + dctx = talloc_zero(cmdctx, struct nss_dom_ctx); + if (!dctx) { + ret = ENOMEM; + goto done; + } + dctx->cmdctx = cmdctx; + + /* get user name to query */ + sss_packet_get_body(cctx->creq->in, &body, &blen); + + /* if not terminated fail */ + if (body[blen -1] != '\0') { + ret = EINVAL; + goto done; + } + rawname = (const char *)body; + + domname = NULL; + ret = sss_parse_name(cmdctx, cctx->rctx->names, rawname, + &domname, &cmdctx->name); + if (ret != EOK) { + DEBUG(2, ("Invalid name received [%s]\n", rawname)); + ret = ENOENT; + goto done; + } + + DEBUG(4, ("Requesting info for [%s] from [%s]\n", + cmdctx->name, domname?domname:"")); + + if (domname) { + dctx->domain = nss_get_dom(cctx->rctx->domains, domname); + if (!dctx->domain) { + ret = ENOENT; + goto done; + } + + /* verify this user has not yet been negatively cached, + * or has been permanently filtered */ + ncret = nss_ncache_check_group(nctx->ncache, nctx->neg_timeout, + dctx->domain->name, cmdctx->name); + if (ncret == EEXIST) { + neghit = true; + } + } + else { + /* skip domains that require FQnames or have negative caches */ + for (dom = cctx->rctx->domains; dom; dom = dom->next) { + + if (dom->fqnames) continue; + + /* verify this user has not yet been negatively cached, + * or has been permanently filtered */ + ncret = nss_ncache_check_group(nctx->ncache, nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; + + neghit = true; + } + /* reset neghit if we still have a domain to check */ + if (dom) neghit = false; + + dctx->domain = dom; + } + if (neghit) { + DEBUG(2, ("Group [%s] does not exist! (negative cache)\n", rawname)); + ret = ENOENT; + goto done; + } + if (dctx->domain == NULL) { + DEBUG(2, ("No matching domain found for [%s], fail!\n", rawname)); + ret = ENOENT; + goto done; + } + + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + + if (!domname) { + /* this is a multidomain search */ + cmdctx->check_next = true; + } + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + ret = EFAULT; + goto done; + } + ret = sysdb_getgrnam(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getgrnam_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + +done: + if (ret != EOK) { + if (ret == ENOENT) { + /* we do not have any entry to return */ + ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret == EOK) { + sss_packet_get_body(cctx->creq->out, &body, &blen); + ((uint32_t *)body)[0] = 0; /* 0 results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + } + } + if (ret != EOK) { + ret = nss_cmd_send_error(cmdctx, ret); + } + if (ret == EOK) { + sss_cmd_done(cctx, cmdctx); + } + return ret; + } + + return EOK; +} + +static void nss_cmd_getgrgid_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); + +static void nss_cmd_getgrgid_callback(void *ptr, int status, + struct ldb_result *res) +{ + struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + uint8_t *body; + size_t blen; + bool neghit = false; + int i, ret; + int ncret; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + if (status != LDB_SUCCESS) { + ret = nss_cmd_send_error(cmdctx, status); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + + if (dctx->check_provider) { + ret = check_cache(dctx, nctx, res, + SSS_DP_GROUP, NULL, cmdctx->id, + nss_cmd_getgrgid_dp_callback); + if (ret != EOK) { + /* Anything but EOK means we should reenter the mainloop + * because we may be refreshing the cache + */ + return; + } + } + + switch (res->count) { + case 0: + if (cmdctx->check_next) { + + ret = EOK; + + dom = dctx->domain->next; + + ncret = nss_ncache_check_gid(nctx->ncache, nctx->neg_timeout, + cmdctx->id); + if (ncret == EEXIST) { + DEBUG(3, ("Gid [%lu] does not exist! (negative cache)\n", + (unsigned long)cmdctx->id)); + ret = ENOENT; + } + if (dom == NULL) { + DEBUG(0, ("No matching domain found for [%lu], fail!\n", + (unsigned long)cmdctx->id)); + ret = ENOENT; + } + + if (ret == EOK) { + dctx->domain = dom; + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + if (dctx->res) talloc_free(res); + dctx->res = NULL; + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getgrgid(cmdctx, sysdb, + dctx->domain, cmdctx->id, + nss_cmd_getgrgid_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + } + + /* we made another call, end here */ + if (ret == EOK) return; + } + + DEBUG(2, ("No results for getgrgid call\n")); + + /* set negative cache only if not result of cache check */ + if (!neghit) { + ret = nss_ncache_set_gid(nctx->ncache, false, cmdctx->id); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + } + + ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_packet_get_body(cctx->creq->out, &body, &blen); + ((uint32_t *)body)[0] = 0; /* 0 results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + break; + + default: + + DEBUG(6, ("Returning info for group [%u]\n", (unsigned)cmdctx->id)); + + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + i = res->count; + ret = fill_grent(cctx->creq->out, + dctx->domain, + nctx, true, + res->msgs, 1, &i); + if (ret == ENOENT) { + ret = fill_empty(cctx->creq->out); + } + sss_packet_set_error(cctx->creq->out, ret); + } + + sss_cmd_done(cctx, cmdctx); +} + +static void nss_cmd_getgrgid_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr) +{ + struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + struct sysdb_ctx *sysdb; + int ret; + + if (err_maj) { + DEBUG(2, ("Unable to get information from Data Provider\n" + "Error: %u, %u, %s\n" + "Will try to return what we have in cache\n", + (unsigned int)err_maj, (unsigned int)err_min, err_msg)); + + if (!dctx->res) { + /* return 0 results */ + dctx->res = talloc_zero(dctx, struct ldb_result); + if (!dctx->res) { + ret = ENOMEM; + goto done; + } + } + + nss_cmd_getgrgid_callback(dctx, LDB_SUCCESS, dctx->res); + return; + } + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getgrgid(cmdctx, sysdb, + dctx->domain, cmdctx->id, + nss_cmd_getgrgid_callback, dctx); + +done: + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + + ret = nss_cmd_send_error(cmdctx, ret); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + } +} + +static int nss_cmd_getgrgid(struct cli_ctx *cctx) +{ + struct nss_cmd_ctx *cmdctx; + struct nss_dom_ctx *dctx; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + uint8_t *body; + size_t blen; + int ret; + int ncret; + + ret = ENOENT; + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); + if (!cmdctx) { + return ENOMEM; + } + cmdctx->cctx = cctx; + + dctx = talloc_zero(cmdctx, struct nss_dom_ctx); + if (!dctx) { + ret = ENOMEM; + goto done; + } + dctx->cmdctx = cmdctx; + + /* get uid to query */ + sss_packet_get_body(cctx->creq->in, &body, &blen); + + if (blen != sizeof(uint32_t)) { + ret = EINVAL; + goto done; + } + cmdctx->id = *((uint32_t *)body); + + /* this is a multidomain search */ + cmdctx->check_next = true; + + for (dom = cctx->rctx->domains; dom; dom = dom->next) { + /* verify this user has not yet been negatively cached, + * or has been permanently filtered */ + ncret = nss_ncache_check_gid(nctx->ncache, nctx->neg_timeout, + cmdctx->id); + if (ncret == EEXIST) { + DEBUG(3, ("Gid [%lu] does not exist! (negative cache)\n", + (unsigned long)cmdctx->id)); + continue; + } + + /* check that the uid is valid for this domain */ + if ((dom->id_min && (cmdctx->id < dom->id_min)) || + (dom->id_max && (cmdctx->id > dom->id_max))) { + DEBUG(4, ("Gid [%lu] does not exist in domain [%s]! " + "(id out of range)\n", + (unsigned long)cmdctx->id, dom->name)); + continue; + } + + dctx->domain = dom; + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + + DEBUG(4, ("Requesting info for [%lu@%s]\n", + cmdctx->id, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + ret = EFAULT; + goto done; + } + ret = sysdb_getgrgid(cmdctx, sysdb, + dctx->domain, cmdctx->id, + nss_cmd_getgrgid_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + + break; + } + +done: + if (ret != EOK) { + if (ret == ENOENT) { + /* we do not have any entry to return */ + ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret == EOK) { + sss_packet_get_body(cctx->creq->out, &body, &blen); + ((uint32_t *)body)[0] = 0; /* 0 results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + } + } + if (ret != EOK) { + ret = nss_cmd_send_error(cmdctx, ret); + } + if (ret == EOK) { + sss_cmd_done(cctx, cmdctx); + } + return ret; + } + + return EOK; +} + +/* to keep it simple at this stage we are retrieving the + * full enumeration again for each request for each process + * and we also block on setgrent() for the full time needed + * to retrieve the data. And endgrent() frees all the data. + * Next steps are: + * - use and nsssrv wide cache with data already structured + * so that it can be immediately returned (see nscd way) + * - use mutexes so that setgrent() can return immediately + * even if the data is still being fetched + * - make getgrent() wait on the mutex + */ +static int nss_cmd_getgrent_immediate(struct nss_cmd_ctx *cmdctx); + +static void nss_cmd_setgr_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); + +static void nss_cmd_setgrent_callback(void *ptr, int status, + struct ldb_result *res) +{ + struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct getent_ctx *gctx; + struct nss_ctx *nctx; + int timeout; + int ret; + + if (status != LDB_SUCCESS) { + ret = nss_cmd_send_error(cmdctx, ENOENT); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + gctx = nctx->gctx; + if (gctx == NULL) { + gctx = talloc_zero(nctx, struct getent_ctx); + if (!gctx) { + ret = nss_cmd_send_error(cmdctx, ENOMEM); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + nctx->gctx = gctx; + } + + gctx->doms = talloc_realloc(gctx, gctx->doms, struct dom_ctx, gctx->num +1); + if (!gctx->doms) NSS_CMD_FATAL_ERROR(cctx); + + gctx->doms[gctx->num].domain = dctx->domain; + gctx->doms[gctx->num].res = talloc_steal(gctx->doms, res); + gctx->doms[gctx->num].cur = 0; + + gctx->num++; + + /* do not reply until all domain searches are done */ + for (dom = dctx->domain->next; dom; dom = dom->next) { + if (dom->enumerate != 0) break; + } + dctx->domain = dom; + + if (dctx->domain != NULL) { + if (cmdctx->enum_cached) { + dctx->check_provider = false; + } else { + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + } + + if (dctx->check_provider) { + timeout = SSS_CLI_SOCKET_TIMEOUT; + ret = sss_dp_send_acct_req(cctx->rctx, cmdctx, + nss_cmd_setgr_dp_callback, dctx, + timeout, dom->name, true, + SSS_DP_GROUP, NULL, 0); + } else { + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_enumgrent(dctx, sysdb, + dctx->domain, + nss_cmd_setgrent_callback, dctx); + } + if (ret != EOK) { + /* FIXME: shutdown ? */ + DEBUG(1, ("Failed to send enumeration request for domain [%s]!\n", + dom->name)); + + ret = nss_cmd_send_error(cmdctx, ret); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + } + return; + } + + /* set cache mark */ + nctx->last_group_enum = time(NULL); + + if (cmdctx->immediate) { + /* this was a getgrent call w/o setgrent, + * return immediately one result */ + ret = nss_cmd_getgrent_immediate(cmdctx); + if (ret != EOK) NSS_CMD_FATAL_ERROR(cctx); + return; + } + + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); +} + +static void nss_cmd_setgr_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr) +{ + struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + struct sysdb_ctx *sysdb; + int ret; + + if (err_maj) { + DEBUG(2, ("Unable to get information from Data Provider\n" + "Error: %u, %u, %s\n" + "Will try to return what we have in cache\n", + (unsigned int)err_maj, (unsigned int)err_min, err_msg)); + } + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_enumgrent(dctx, sysdb, + dctx->domain, + nss_cmd_setgrent_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + + ret = nss_cmd_send_error(cmdctx, ret); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + } +} + +static int nss_cmd_setgrent_ext(struct cli_ctx *cctx, bool immediate) +{ + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_cmd_ctx *cmdctx; + struct nss_dom_ctx *dctx; + struct nss_ctx *nctx; + time_t now = time(NULL); + int timeout; + uint8_t *body; + size_t blen; + int ret; + + DEBUG(4, ("Requesting info for all groups\n")); + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + talloc_free(nctx->gctx); + nctx->gctx = NULL; + + cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); + if (!cmdctx) { + return ENOMEM; + } + cmdctx->cctx = cctx; + cmdctx->immediate = immediate; + + dctx = talloc_zero(cmdctx, struct nss_dom_ctx); + if (!dctx) { + ret = ENOMEM; + goto done; + } + dctx->cmdctx = cmdctx; + + /* do not query backends if we have a recent enumeration */ + if (nctx->enum_cache_timeout) { + if (nctx->last_group_enum + + nctx->enum_cache_timeout > now) { + cmdctx->enum_cached = true; + } + } + + /* check if enumeration is enabled in any domain */ + for (dom = cctx->rctx->domains; dom; dom = dom->next) { + if (dom->enumerate != 0) break; + } + dctx->domain = dom; + + if (dctx->domain == NULL) { + DEBUG(2, ("Enumeration disabled on all domains!\n")); + ret = ENOENT; + goto done; + } + + if (cmdctx->enum_cached) { + dctx->check_provider = false; + } else { + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + } + + if (dctx->check_provider) { + timeout = SSS_CLI_SOCKET_TIMEOUT; + ret = sss_dp_send_acct_req(cctx->rctx, cmdctx, + nss_cmd_setgr_dp_callback, dctx, + timeout, dom->name, true, + SSS_DP_GROUP, NULL, 0); + } else { + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + ret = EFAULT; + goto done; + } + ret = sysdb_enumgrent(dctx, sysdb, + dctx->domain, + nss_cmd_setgrent_callback, dctx); + } + if (ret != EOK) { + /* FIXME: shutdown ? */ + DEBUG(1, ("Failed to send enumeration request for domain [%s]!\n", + dom->name)); + } + +done: + if (ret != EOK) { + if (ret == ENOENT) { + if (cmdctx->immediate) { + /* we do not have any entry to return */ + ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret == EOK) { + sss_packet_get_body(cctx->creq->out, &body, &blen); + ((uint32_t *)body)[0] = 0; /* 0 results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + } + } + else { + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + } + } + if (ret != EOK) { + ret = nss_cmd_send_error(cmdctx, ret); + } + if (ret == EOK) { + sss_cmd_done(cctx, cmdctx); + } + return ret; + } + + return EOK; +} + +static int nss_cmd_setgrent(struct cli_ctx *cctx) +{ + return nss_cmd_setgrent_ext(cctx, false); +} + +static int nss_cmd_retgrent(struct cli_ctx *cctx, int num) +{ + struct nss_ctx *nctx; + struct getent_ctx *gctx; + struct ldb_message **msgs = NULL; + struct dom_ctx *gdom = NULL; + int n = 0; + int ret; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + gctx = nctx->gctx; + + do { + if (gctx->cur >= gctx->num) goto none; + + gdom = &gctx->doms[gctx->cur]; + + n = gdom->res->count - gdom->cur; + if (n == 0 && (gctx->cur+1 < gctx->num)) { + gctx->cur++; + gdom = &gctx->doms[gctx->cur]; + n = gdom->res->count - gdom->cur; + } + + if (!n) goto none; + + msgs = &(gdom->res->msgs[gdom->cur]); + + ret = fill_grent(cctx->creq->out, gdom->domain, nctx, true, msgs, num, &n); + + gdom->cur += n; + + } while(ret == ENOENT); + + return ret; + +none: + return fill_empty(cctx->creq->out); +} + +/* used only if a process calls getpwent() without first calling setpwent() + */ +static int nss_cmd_getgrent_immediate(struct nss_cmd_ctx *cmdctx) +{ + struct cli_ctx *cctx = cmdctx->cctx; + uint8_t *body; + size_t blen; + uint32_t num; + int ret; + + /* get max num of entries to return in one call */ + sss_packet_get_body(cctx->creq->in, &body, &blen); + if (blen != sizeof(uint32_t)) { + return EINVAL; + } + num = *((uint32_t *)body); + + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + return ret; + } + + ret = nss_cmd_retgrent(cctx, num); + + sss_packet_set_error(cctx->creq->out, ret); + sss_cmd_done(cctx, cmdctx); + + return EOK; +} + +static int nss_cmd_getgrent(struct cli_ctx *cctx) +{ + struct nss_ctx *nctx; + struct nss_cmd_ctx *cmdctx; + + DEBUG(4, ("Requesting info for all groups\n")); + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + /* see if we need to trigger an implicit setpwent() */ + if (nctx->gctx == NULL) { + nctx->gctx = talloc_zero(nctx, struct getent_ctx); + if (!nctx->gctx) return ENOMEM; + + return nss_cmd_setgrent_ext(cctx, true); + } + + cmdctx = talloc(cctx, struct nss_cmd_ctx); + if (!cmdctx) { + return ENOMEM; + } + cmdctx->cctx = cctx; + + return nss_cmd_getgrent_immediate(cmdctx); +} + +static int nss_cmd_endgrent(struct cli_ctx *cctx) +{ + struct nss_ctx *nctx; + int ret; + + DEBUG(4, ("Terminating request info for all groups\n")); + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + + if (nctx->gctx == NULL) goto done; + + /* free results and reset */ + talloc_free(nctx->gctx); + nctx->gctx = NULL; + +done: + sss_cmd_done(cctx, NULL); + return EOK; +} + +static int fill_initgr(struct sss_packet *packet, struct ldb_result *res) +{ + uint8_t *body; + size_t blen; + gid_t gid; + int ret, i, num; + + if (res->count == 0) { + return ENOENT; + } + + /* one less, the first one is the user entry */ + num = res->count -1; + + ret = sss_packet_grow(packet, (2 + res->count) * sizeof(uint32_t)); + if (ret != EOK) { + return ret; + } + sss_packet_get_body(packet, &body, &blen); + + /* skip first entry, it's the user entry */ + for (i = 0; i < num; i++) { + gid = ldb_msg_find_attr_as_uint64(res->msgs[i + 1], SYSDB_GIDNUM, 0); + if (!gid) { + DEBUG(1, ("Incomplete group object for initgroups! Aborting\n")); + return EFAULT; + } + ((uint32_t *)body)[2 + i] = gid; + } + + ((uint32_t *)body)[0] = num; /* num results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + + return EOK; +} + +static void nss_cmd_getinitgr_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); + +static void nss_cmd_getinitgr_callback(void *ptr, int status, + struct ldb_result *res) +{ + struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + uint8_t *body; + size_t blen; + bool neghit = false; + int ncret; + int ret; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + if (status != LDB_SUCCESS) { + ret = nss_cmd_send_error(cmdctx, status); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + + if (dctx->check_provider) { + ret = check_cache(dctx, nctx, res, + SSS_DP_INITGROUPS, cmdctx->name, 0, + nss_cmd_getinitgr_dp_callback); + if (ret != EOK) { + /* Anything but EOK means we should reenter the mainloop + * because we may be refreshing the cache + */ + return; + } + } + + switch (res->count) { + case 0: + if (cmdctx->check_next) { + + ret = EOK; + + /* skip domains that require FQnames or have negative caches */ + for (dom = dctx->domain->next; dom; dom = dom->next) { + + if (dom->fqnames) continue; + + ncret = nss_ncache_check_user(nctx->ncache, + nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; + + neghit = true; + } + /* reset neghit if we still have a domain to check */ + if (dom) neghit = false; + + if (neghit) { + DEBUG(2, ("User [%s] does not exist! (negative cache)\n", + cmdctx->name)); + ret = ENOENT; + } + if (dom == NULL) { + DEBUG(2, ("No matching domain found for [%s], fail!\n", + cmdctx->name)); + ret = ENOENT; + } + + if (ret == EOK) { + dctx->domain = dom; + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + if (dctx->res) talloc_free(res); + dctx->res = NULL; + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_initgroups(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getinitgr_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + } + + /* we made another call, end here */ + if (ret == EOK) return; + } + + DEBUG(2, ("No results for initgroups call\n")); + + /* set negative cache only if not result of cache check */ + if (!neghit) { + ret = nss_ncache_set_user(nctx->ncache, false, + dctx->domain->name, cmdctx->name); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + } + + ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_packet_get_body(cctx->creq->out, &body, &blen); + ((uint32_t *)body)[0] = 0; /* 0 results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + break; + + default: + + DEBUG(6, ("Returning initgr for user [%s]\n", cmdctx->name)); + + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + ret = fill_initgr(cctx->creq->out, res); + if (ret == ENOENT) { + ret = fill_empty(cctx->creq->out); + } + sss_packet_set_error(cctx->creq->out, ret); + } + + sss_cmd_done(cctx, cmdctx); +} + +static void nss_cmd_getinitgr_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr) +{ + struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + struct sysdb_ctx *sysdb; + int ret; + + if (err_maj) { + DEBUG(2, ("Unable to get information from Data Provider\n" + "Error: %u, %u, %s\n" + "Will try to return what we have in cache\n", + (unsigned int)err_maj, (unsigned int)err_min, err_msg)); + + if (!dctx->res) { + /* return 0 results */ + dctx->res = talloc_zero(dctx, struct ldb_result); + if (!dctx->res) { + ret = ENOMEM; + goto done; + } + } + + nss_cmd_getinitgr_callback(dctx, LDB_SUCCESS, dctx->res); + return; + } + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_initgroups(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getinitgr_callback, dctx); + +done: + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + + ret = nss_cmd_send_error(cmdctx, ret); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + } +} + +/* for now, if we are online, try to always query the backend */ +static int nss_cmd_initgroups(struct cli_ctx *cctx) +{ + struct nss_cmd_ctx *cmdctx; + struct nss_dom_ctx *dctx; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + const char *rawname; + char *domname; + uint8_t *body; + size_t blen; + int ret; + int ncret; + bool neghit = false; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); + if (!cmdctx) { + return ENOMEM; + } + cmdctx->cctx = cctx; + + dctx = talloc_zero(cmdctx, struct nss_dom_ctx); + if (!dctx) { + ret = ENOMEM; + goto done; + } + dctx->cmdctx = cmdctx; + + /* get user name to query */ + sss_packet_get_body(cctx->creq->in, &body, &blen); + + /* if not terminated fail */ + if (body[blen -1] != '\0') { + ret = EINVAL; + goto done; + } + rawname = (const char *)body; + + domname = NULL; + ret = sss_parse_name(cmdctx, cctx->rctx->names, rawname, + &domname, &cmdctx->name); + if (ret != EOK) { + DEBUG(2, ("Invalid name received [%s]\n", rawname)); + ret = ENOENT; + goto done; + } + + DEBUG(4, ("Requesting info for [%s] from [%s]\n", + cmdctx->name, domname ? : "")); + + if (domname) { + dctx->domain = nss_get_dom(cctx->rctx->domains, domname); + if (!dctx->domain) { + ret = ENOENT; + goto done; + } + + /* verify this user has not yet been negatively cached, + * or has been permanently filtered */ + ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout, + domname, cmdctx->name); + if (ncret == EEXIST) { + neghit = true; + } + } + else { + /* skip domains that require FQnames or have negative caches */ + for (dom = cctx->rctx->domains; dom; dom = dom->next) { + + if (dom->fqnames) continue; + + /* verify this user has not yet been negatively cached, + * or has been permanently filtered */ + ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; + + neghit = true; + } + /* reset neghit if we still have a domain to check */ + if (dom) neghit = false; + + dctx->domain = dom; + } + if (neghit) { + DEBUG(2, ("User [%s] does not exist! (negative cache)\n", rawname)); + ret = ENOENT; + goto done; + } + if (dctx->domain == NULL) { + DEBUG(2, ("No matching domain found for [%s], fail!\n", rawname)); + ret = ENOENT; + goto done; + } + + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + + if (!domname) { + /* this is a multidomain search */ + cmdctx->check_next = true; + } + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + ret = EFAULT; + goto done; + } + ret = sysdb_initgroups(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getinitgr_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + +done: + if (ret != EOK) { + if (ret == ENOENT) { + /* we do not have any entry to return */ + ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret == EOK) { + sss_packet_get_body(cctx->creq->out, &body, &blen); + ((uint32_t *)body)[0] = 0; /* 0 results */ + ((uint32_t *)body)[1] = 0; /* reserved */ + } + } + if (ret != EOK) { + ret = nss_cmd_send_error(cmdctx, ret); + } + if (ret == EOK) { + sss_cmd_done(cctx, cmdctx); + } + return ret; + } + + return EOK; +} + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version nss_cli_protocol_version[] = { + {1, "2008-09-05", "initial version, \\0 terminated strings"}, + {0, NULL, NULL} + }; + + return nss_cli_protocol_version; +} + +static struct sss_cmd_table nss_cmds[] = { + {SSS_GET_VERSION, sss_cmd_get_version}, + {SSS_NSS_GETPWNAM, nss_cmd_getpwnam}, + {SSS_NSS_GETPWUID, nss_cmd_getpwuid}, + {SSS_NSS_SETPWENT, nss_cmd_setpwent}, + {SSS_NSS_GETPWENT, nss_cmd_getpwent}, + {SSS_NSS_ENDPWENT, nss_cmd_endpwent}, + {SSS_NSS_GETGRNAM, nss_cmd_getgrnam}, + {SSS_NSS_GETGRGID, nss_cmd_getgrgid}, + {SSS_NSS_SETGRENT, nss_cmd_setgrent}, + {SSS_NSS_GETGRENT, nss_cmd_getgrent}, + {SSS_NSS_ENDGRENT, nss_cmd_endgrent}, + {SSS_NSS_INITGR, nss_cmd_initgroups}, + {SSS_CLI_NULL, NULL} +}; + +struct sss_cmd_table *get_nss_cmds(void) { + return nss_cmds; +} + +int nss_cmd_execute(struct cli_ctx *cctx) +{ + enum sss_cli_command cmd; + int i; + + cmd = sss_packet_get_cmd(cctx->creq->in); + + for (i = 0; nss_cmds[i].cmd != SSS_CLI_NULL; i++) { + if (cmd == nss_cmds[i].cmd) { + return nss_cmds[i].fn(cctx); + } + } + + return EINVAL; +} + diff --git a/src/responder/nss/nsssrv_nc.c b/src/responder/nss/nsssrv_nc.c new file mode 100644 index 000000000..1fa7d612f --- /dev/null +++ b/src/responder/nss/nsssrv_nc.c @@ -0,0 +1,321 @@ +/* + SSSD + + NSS Responder + + Copyright (C) Simo Sorce 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 . +*/ + +#include "util/util.h" +#include +#include +#include "tdb.h" + +#define NC_ENTRY_PREFIX "NCE/" +#define NC_USER_PREFIX NC_ENTRY_PREFIX"USER" +#define NC_GROUP_PREFIX NC_ENTRY_PREFIX"GROUP" +#define NC_UID_PREFIX NC_ENTRY_PREFIX"UID" +#define NC_GID_PREFIX NC_ENTRY_PREFIX"GID" + +struct nss_nc_ctx { + struct tdb_context *tdb; +}; + +static int string_to_tdb_data(char *str, TDB_DATA *ret) +{ + if (!str || !ret) return EINVAL; + + ret->dptr = (uint8_t *)str; + ret->dsize = strlen(str)+1; + + return EOK; +} + +int nss_ncache_init(TALLOC_CTX *memctx, struct nss_nc_ctx **_ctx) +{ + struct nss_nc_ctx *ctx; + + ctx = talloc_zero(memctx, struct nss_nc_ctx); + if (!ctx) return ENOMEM; + + errno = 0; + /* open a memory only tdb with default hash size */ + ctx->tdb = tdb_open("memcache", 0, TDB_INTERNAL, O_RDWR|O_CREAT, 0); + if (!ctx->tdb) return errno; + + *_ctx = ctx; + return EOK; +}; + +static int nss_ncache_check_str(struct nss_nc_ctx *ctx, char *str, int ttl) +{ + TDB_DATA key; + TDB_DATA data; + unsigned long long int timestamp; + bool expired = false; + char *ep; + int ret; + + ret = string_to_tdb_data(str, &key); + if (ret != EOK) goto done; + + data = tdb_fetch(ctx->tdb, key); + + if (!data.dptr) { + ret = ENOENT; + goto done; + } + + if (ttl == -1) { + /* a negative ttl means: never expires */ + ret = EEXIST; + goto done; + } + + errno = 0; + timestamp = strtoull((const char *)data.dptr, &ep, 0); + if (errno != 0 || *ep != '\0') { + /* Malformed entry, remove it and return no entry */ + expired = true; + goto done; + } + + if (timestamp == 0) { + /* a 0 timestamp means this is a permanent entry */ + ret = EEXIST; + goto done; + } + + if (timestamp + ttl > time(NULL)) { + /* still valid */ + ret = EEXIST; + goto done; + } + + expired = true; + +done: + if (expired) { + /* expired, remove and return no entry */ + tdb_delete(ctx->tdb, key); + ret = ENOENT; + } + + return ret; +} + +static int nss_ncache_set_str(struct nss_nc_ctx *ctx, + char *str, bool permanent) +{ + TDB_DATA key; + TDB_DATA data; + char *timest; + int ret; + + ret = string_to_tdb_data(str, &key); + if (ret != EOK) return ret; + + if (permanent) { + timest = talloc_strdup(ctx, "0"); + } else { + timest = talloc_asprintf(ctx, "%llu", + (unsigned long long int)time(NULL)); + } + if (!timest) return ENOMEM; + + ret = string_to_tdb_data(timest, &data); + if (ret != EOK) goto done; + + ret = tdb_store(ctx->tdb, key, data, TDB_REPLACE); + if (ret != 0) { + DEBUG(1, ("Negative cache failed to set entry: [%s]", + tdb_errorstr(ctx->tdb))); + ret = EFAULT; + } + +done: + talloc_free(timest); + return ret; +} + +int nss_ncache_check_user(struct nss_nc_ctx *ctx, int ttl, + const char *domain, const char *name) +{ + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", NC_USER_PREFIX, domain, name); + if (!str) return ENOMEM; + + ret = nss_ncache_check_str(ctx, str, ttl); + + talloc_free(str); + return ret; +} + +int nss_ncache_check_group(struct nss_nc_ctx *ctx, int ttl, + const char *domain, const char *name) +{ + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", NC_GROUP_PREFIX, domain, name); + if (!str) return ENOMEM; + + ret = nss_ncache_check_str(ctx, str, ttl); + + talloc_free(str); + return ret; +} + +int nss_ncache_check_uid(struct nss_nc_ctx *ctx, int ttl, uid_t uid) +{ + char *str; + int ret; + + str = talloc_asprintf(ctx, "%s/%u", NC_UID_PREFIX, uid); + if (!str) return ENOMEM; + + ret = nss_ncache_check_str(ctx, str, ttl); + + talloc_free(str); + return ret; +} + +int nss_ncache_check_gid(struct nss_nc_ctx *ctx, int ttl, gid_t gid) +{ + char *str; + int ret; + + str = talloc_asprintf(ctx, "%s/%u", NC_GID_PREFIX, gid); + if (!str) return ENOMEM; + + ret = nss_ncache_check_str(ctx, str, ttl); + + talloc_free(str); + return ret; +} + +int nss_ncache_set_user(struct nss_nc_ctx *ctx, bool permanent, + const char *domain, const char *name) +{ + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", NC_USER_PREFIX, domain, name); + if (!str) return ENOMEM; + + ret = nss_ncache_set_str(ctx, str, permanent); + + talloc_free(str); + return ret; +} + +int nss_ncache_set_group(struct nss_nc_ctx *ctx, bool permanent, + const char *domain, const char *name) +{ + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", NC_GROUP_PREFIX, domain, name); + if (!str) return ENOMEM; + + ret = nss_ncache_set_str(ctx, str, permanent); + + talloc_free(str); + return ret; +} + +int nss_ncache_set_uid(struct nss_nc_ctx *ctx, bool permanent, uid_t uid) +{ + char *str; + int ret; + + str = talloc_asprintf(ctx, "%s/%u", NC_UID_PREFIX, uid); + if (!str) return ENOMEM; + + ret = nss_ncache_set_str(ctx, str, permanent); + + talloc_free(str); + return ret; +} + +int nss_ncache_set_gid(struct nss_nc_ctx *ctx, bool permanent, gid_t gid) +{ + char *str; + int ret; + + str = talloc_asprintf(ctx, "%s/%u", NC_GID_PREFIX, gid); + if (!str) return ENOMEM; + + ret = nss_ncache_set_str(ctx, str, permanent); + + talloc_free(str); + return ret; +} + +static int delete_permanent(struct tdb_context *tdb, + TDB_DATA key, TDB_DATA data, void *state) +{ + unsigned long long int timestamp; + bool remove_key = false; + char *ep; + + if (strncmp((char *)key.dptr, + NC_ENTRY_PREFIX, sizeof(NC_ENTRY_PREFIX)) != 0) { + /* not interested in this key */ + return 0; + } + + errno = 0; + timestamp = strtoull((const char *)data.dptr, &ep, 0); + if (errno != 0 || *ep != '\0') { + /* Malformed entry, remove it */ + remove_key = true; + goto done; + } + + if (timestamp == 0) { + /* a 0 timestamp means this is a permanent entry */ + remove_key = true; + } + +done: + if (remove_key) { + return tdb_delete(tdb, key); + } + + return 0; +} + +int nss_ncache_reset_permament(struct nss_nc_ctx *ctx) +{ + int ret; + + ret = tdb_traverse(ctx->tdb, delete_permanent, NULL); + if (ret < 0) + return EIO; + + return EOK; +} diff --git a/src/responder/nss/nsssrv_nc.h b/src/responder/nss/nsssrv_nc.h new file mode 100644 index 000000000..c0fa197c2 --- /dev/null +++ b/src/responder/nss/nsssrv_nc.h @@ -0,0 +1,51 @@ +/* + SSSD + + NSS Responder + + Copyright (C) Simo Sorce 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 . +*/ + +#ifndef _NSS_NEG_CACHE_H_ +#define _NSS_NEG_CACHE_H_ + +struct nss_nc_ctx; + +/* init the in memory negative cache */ +int nss_ncache_init(TALLOC_CTX *memctx, struct nss_nc_ctx **_ctx); + +/* check if the user is expired according to the passed in time to live */ +int nss_ncache_check_user(struct nss_nc_ctx *ctx, int ttl, + const char *domain, const char *name); +int nss_ncache_check_group(struct nss_nc_ctx *ctx, int ttl, + const char *domain, const char *name); +int nss_ncache_check_uid(struct nss_nc_ctx *ctx, int ttl, uid_t uid); +int nss_ncache_check_gid(struct nss_nc_ctx *ctx, int ttl, gid_t gid); + +/* add a new neg-cache entry setting the timestamp to "now" unless + * "permanent" is set to true, in which case the timestamps is set to 0 + * and the negative cache never expires (used to permanently filter out + * users and groups) */ +int nss_ncache_set_user(struct nss_nc_ctx *ctx, bool permanent, + const char *domain, const char *name); +int nss_ncache_set_group(struct nss_nc_ctx *ctx, bool permanent, + const char *domain, const char *name); +int nss_ncache_set_uid(struct nss_nc_ctx *ctx, bool permanent, uid_t uid); +int nss_ncache_set_gid(struct nss_nc_ctx *ctx, bool permanent, gid_t gid); + +int nss_ncache_reset_permament(struct nss_nc_ctx *ctx); + +#endif /* _NSS_NEG_CACHE_H_ */ diff --git a/src/responder/pam/pam_LOCAL_domain.c b/src/responder/pam/pam_LOCAL_domain.c new file mode 100644 index 000000000..34f0c8dd5 --- /dev/null +++ b/src/responder/pam/pam_LOCAL_domain.c @@ -0,0 +1,476 @@ +/* + SSSD + + PAM e credentials + + Copyright (C) Sumit Bose 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 "util/util.h" +#include "db/sysdb.h" +#include "util/sha512crypt.h" +#include "providers/data_provider.h" +#include "responder/pam/pamsrv.h" + + +#define NULL_CHECK_OR_JUMP(var, msg, ret, err, label) do { \ + if (var == NULL) { \ + DEBUG(1, (msg)); \ + ret = (err); \ + goto label; \ + } \ +} while(0) + +#define NEQ_CHECK_OR_JUMP(var, val, msg, ret, err, label) do { \ + if (var != (val)) { \ + DEBUG(1, (msg)); \ + ret = (err); \ + goto label; \ + } \ +} while(0) + + +struct LOCAL_request { + struct tevent_context *ev; + struct sysdb_ctx *dbctx; + struct sysdb_attrs *mod_attrs; + struct sysdb_handle *handle; + + struct ldb_result *res; + int error; + + struct pam_auth_req *preq; +}; + +static void prepare_reply(struct LOCAL_request *lreq) +{ + struct pam_data *pd; + + pd = lreq->preq->pd; + + if (lreq->error != EOK && pd->pam_status == PAM_SUCCESS) + pd->pam_status = PAM_SYSTEM_ERR; + + lreq->preq->callback(lreq->preq); +} + +static void set_user_attr_done(struct tevent_req *req) +{ + struct LOCAL_request *lreq; + int ret; + + lreq = tevent_req_callback_data(req, struct LOCAL_request); + + ret = sysdb_transaction_commit_recv(req); + if (ret) { + DEBUG(2, ("set_user_attr failed.\n")); + lreq->error =ret; + } + + prepare_reply(lreq); +} + +static void set_user_attr_req_done(struct tevent_req *subreq); +static void set_user_attr_req(struct tevent_req *req) +{ + struct LOCAL_request *lreq = tevent_req_callback_data(req, + struct LOCAL_request); + struct tevent_req *subreq; + int ret; + + DEBUG(4, ("entering set_user_attr_req\n")); + + ret = sysdb_transaction_recv(req, lreq, &lreq->handle); + if (ret) { + lreq->error = ret; + return prepare_reply(lreq); + } + + subreq = sysdb_set_user_attr_send(lreq, lreq->ev, lreq->handle, + lreq->preq->domain, + lreq->preq->pd->user, + lreq->mod_attrs, SYSDB_MOD_REP); + if (!subreq) { + /* cancel transaction */ + talloc_zfree(lreq->handle); + lreq->error = ret; + return prepare_reply(lreq); + } + tevent_req_set_callback(subreq, set_user_attr_req_done, lreq); +} + +static void set_user_attr_req_done(struct tevent_req *subreq) +{ + struct LOCAL_request *lreq = tevent_req_callback_data(subreq, + struct LOCAL_request); + struct tevent_req *req; + int ret; + + ret = sysdb_set_user_attr_recv(subreq); + talloc_zfree(subreq); + + DEBUG(4, ("set_user_attr_callback, status [%d][%s]\n", ret, strerror(ret))); + + if (ret) { + lreq->error = ret; + goto fail; + } + + req = sysdb_transaction_commit_send(lreq, lreq->ev, lreq->handle); + if (!req) { + lreq->error = ENOMEM; + goto fail; + } + tevent_req_set_callback(req, set_user_attr_done, lreq); + + return; + +fail: + DEBUG(2, ("set_user_attr failed.\n")); + + /* cancel transaction */ + talloc_zfree(lreq->handle); + + prepare_reply(lreq); +} + +static void do_successful_login(struct LOCAL_request *lreq) +{ + struct tevent_req *req; + int ret; + + lreq->mod_attrs = sysdb_new_attrs(lreq); + NULL_CHECK_OR_JUMP(lreq->mod_attrs, ("sysdb_new_attrs failed.\n"), + lreq->error, ENOMEM, done); + + ret = sysdb_attrs_add_long(lreq->mod_attrs, + SYSDB_LAST_LOGIN, (long)time(NULL)); + NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"), + lreq->error, ret, done); + + ret = sysdb_attrs_add_long(lreq->mod_attrs, SYSDB_FAILED_LOGIN_ATTEMPTS, 0L); + NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"), + lreq->error, ret, done); + + req = sysdb_transaction_send(lreq, lreq->ev, lreq->dbctx); + if (!req) { + lreq->error = ENOMEM; + goto done; + } + tevent_req_set_callback(req, set_user_attr_req, lreq); + + return; + +done: + + prepare_reply(lreq); +} + +static void do_failed_login(struct LOCAL_request *lreq) +{ + struct tevent_req *req; + int ret; + int failedLoginAttempts; + struct pam_data *pd; + + pd = lreq->preq->pd; + pd->pam_status = PAM_AUTH_ERR; +/* TODO: maybe add more inteligent delay calculation */ + pd->response_delay = 3; + + lreq->mod_attrs = sysdb_new_attrs(lreq); + NULL_CHECK_OR_JUMP(lreq->mod_attrs, ("sysdb_new_attrs failed.\n"), + lreq->error, ENOMEM, done); + + ret = sysdb_attrs_add_long(lreq->mod_attrs, + SYSDB_LAST_FAILED_LOGIN, (long)time(NULL)); + NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"), + lreq->error, ret, done); + + failedLoginAttempts = ldb_msg_find_attr_as_int(lreq->res->msgs[0], + SYSDB_FAILED_LOGIN_ATTEMPTS, + 0); + failedLoginAttempts++; + + ret = sysdb_attrs_add_long(lreq->mod_attrs, + SYSDB_FAILED_LOGIN_ATTEMPTS, + (long)failedLoginAttempts); + NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"), + lreq->error, ret, done); + + req = sysdb_transaction_send(lreq, lreq->ev, lreq->dbctx); + if (!req) { + lreq->error = ENOMEM; + goto done; + } + tevent_req_set_callback(req, set_user_attr_req, lreq); + + return; + +done: + + prepare_reply(lreq); +} + +static void do_pam_acct_mgmt(struct LOCAL_request *lreq) +{ + const char *disabled; + struct pam_data *pd; + + pd = lreq->preq->pd; + + disabled = ldb_msg_find_attr_as_string(lreq->res->msgs[0], + SYSDB_DISABLED, NULL); + if ((disabled != NULL) && + (strncasecmp(disabled, "false",5) != 0) && + (strncasecmp(disabled, "no",2) != 0) ) { + pd->pam_status = PAM_PERM_DENIED; + } + + prepare_reply(lreq); +} + +static void do_pam_chauthtok(struct LOCAL_request *lreq) +{ + struct tevent_req *req; + int ret; + char *newauthtok; + char *salt; + char *new_hash; + struct pam_data *pd; + + pd = lreq->preq->pd; + + newauthtok = talloc_strndup(lreq, (char *) pd->newauthtok, + pd->newauthtok_size); + NULL_CHECK_OR_JUMP(newauthtok, ("talloc_strndup failed.\n"), lreq->error, + ENOMEM, done); + memset(pd->newauthtok, 0, pd->newauthtok_size); + + if (strlen(newauthtok) == 0) { + /* TODO: should we allow null passwords via a config option ? */ + DEBUG(1, ("Empty passwords are not allowed!")); + ret = EINVAL; + goto done; + } + + ret = s3crypt_gen_salt(lreq, &salt); + NEQ_CHECK_OR_JUMP(ret, EOK, ("Salt generation failed.\n"), + lreq->error, ret, done); + DEBUG(4, ("Using salt [%s]\n", salt)); + + ret = s3crypt_sha512(lreq, newauthtok, salt, &new_hash); + NEQ_CHECK_OR_JUMP(ret, EOK, ("Hash generation failed.\n"), + lreq->error, ret, done); + DEBUG(4, ("New hash [%s]\n", new_hash)); + memset(newauthtok, 0, pd->newauthtok_size); + + lreq->mod_attrs = sysdb_new_attrs(lreq); + NULL_CHECK_OR_JUMP(lreq->mod_attrs, ("sysdb_new_attrs failed.\n"), + lreq->error, ENOMEM, done); + + ret = sysdb_attrs_add_string(lreq->mod_attrs, SYSDB_PWD, new_hash); + NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_string failed.\n"), + lreq->error, ret, done); + + ret = sysdb_attrs_add_long(lreq->mod_attrs, + "lastPasswordChange", (long)time(NULL)); + NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"), + lreq->error, ret, done); + + req = sysdb_transaction_send(lreq, lreq->ev, lreq->dbctx); + if (!req) { + lreq->error = ENOMEM; + goto done; + } + tevent_req_set_callback(req, set_user_attr_req, lreq); + + return; +done: + + prepare_reply(lreq); +} + +static void local_handler_callback(void *pvt, int ldb_status, + struct ldb_result *res) +{ + struct LOCAL_request *lreq; + const char *username = NULL; + const char *password = NULL; + char *newauthtok = NULL; + char *new_hash = NULL; + char *authtok = NULL; + struct pam_data *pd; + int ret; + + lreq = talloc_get_type(pvt, struct LOCAL_request); + pd = lreq->preq->pd; + + DEBUG(4, ("pam_handler_callback called with ldb_status [%d].\n", + ldb_status)); + + NEQ_CHECK_OR_JUMP(ldb_status, LDB_SUCCESS, ("ldb search failed.\n"), + lreq->error, sysdb_error_to_errno(ldb_status), done); + + + if (res->count < 1) { + DEBUG(4, ("No user found with filter ["SYSDB_PWNAM_FILTER"]\n", + pd->user)); + pd->pam_status = PAM_USER_UNKNOWN; + goto done; + } else if (res->count > 1) { + DEBUG(4, ("More than one object found with filter ["SYSDB_PWNAM_FILTER"]\n")); + lreq->error = EFAULT; + goto done; + } + + username = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_NAME, NULL); + if (strcmp(username, pd->user) != 0) { + DEBUG(1, ("Expected username [%s] get [%s].\n", pd->user, username)); + lreq->error = EINVAL; + goto done; + } + + lreq->res = res; + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + case SSS_PAM_CHAUTHTOK: + case SSS_PAM_CHAUTHTOK_PRELIM: + if ((pd->cmd == SSS_PAM_CHAUTHTOK || + pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) && + lreq->preq->cctx->priv == 1) { +/* TODO: maybe this is a candiate for an explicit audit message. */ + DEBUG(4, ("allowing root to reset a password.\n")); + break; + } + authtok = talloc_strndup(lreq, (char *) pd->authtok, + pd->authtok_size); + NULL_CHECK_OR_JUMP(authtok, ("talloc_strndup failed.\n"), + lreq->error, ENOMEM, done); + memset(pd->authtok, 0, pd->authtok_size); + + password = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_PWD, NULL); + NULL_CHECK_OR_JUMP(password, ("No password stored.\n"), + lreq->error, LDB_ERR_NO_SUCH_ATTRIBUTE, done); + DEBUG(4, ("user: [%s], password hash: [%s]\n", username, password)); + + ret = s3crypt_sha512(lreq, authtok, password, &new_hash); + memset(authtok, 0, pd->authtok_size); + NEQ_CHECK_OR_JUMP(ret, EOK, ("nss_sha512_crypt failed.\n"), + lreq->error, ret, done); + + DEBUG(4, ("user: [%s], new hash: [%s]\n", username, new_hash)); + + if (strcmp(new_hash, password) != 0) { + DEBUG(1, ("Passwords do not match.\n")); + do_failed_login(lreq); + return; + } + + break; + } + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + do_successful_login(lreq); + return; + break; + case SSS_PAM_CHAUTHTOK: + do_pam_chauthtok(lreq); + return; + break; + case SSS_PAM_ACCT_MGMT: + do_pam_acct_mgmt(lreq); + return; + break; + case SSS_PAM_SETCRED: + break; + case SSS_PAM_OPEN_SESSION: + break; + case SSS_PAM_CLOSE_SESSION: + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + break; + default: + lreq->error = EINVAL; + DEBUG(1, ("Unknown PAM task [%d].\n")); + } + +done: + if (pd->authtok != NULL) + memset(pd->authtok, 0, pd->authtok_size); + if (authtok != NULL) + memset(authtok, 0, pd->authtok_size); + if (pd->newauthtok != NULL) + memset(pd->newauthtok, 0, pd->newauthtok_size); + if (newauthtok != NULL) + memset(newauthtok, 0, pd->newauthtok_size); + + prepare_reply(lreq); +} + +int LOCAL_pam_handler(struct pam_auth_req *preq) +{ + int ret; + struct LOCAL_request *lreq; + + static const char *attrs[] = {SYSDB_NAME, + SYSDB_PWD, + SYSDB_DISABLED, + SYSDB_LAST_LOGIN, + "lastPasswordChange", + "accountExpires", + SYSDB_FAILED_LOGIN_ATTEMPTS, + "passwordHint", + "passwordHistory", + SYSDB_LAST_FAILED_LOGIN, + NULL}; + + DEBUG(4, ("LOCAL pam handler.\n")); + + lreq = talloc_zero(preq, struct LOCAL_request); + if (!lreq) { + return ENOMEM; + } + + ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list, + preq->domain, &lreq->dbctx); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + talloc_free(lreq); + return ret; + } + lreq->ev = preq->cctx->ev; + lreq->preq = preq; + + preq->pd->pam_status = PAM_SUCCESS; + + ret = sysdb_get_user_attr(lreq, lreq->dbctx, + preq->domain, preq->pd->user, attrs, + local_handler_callback, lreq); + + if (ret != EOK) { + DEBUG(1, ("sysdb_get_user_attr failed.\n")); + talloc_free(lreq); + return ret; + } + + return EOK; +} diff --git a/src/responder/pam/pamsrv.c b/src/responder/pam/pamsrv.c new file mode 100644 index 000000000..84b13dc45 --- /dev/null +++ b/src/responder/pam/pamsrv.c @@ -0,0 +1,224 @@ +/* + SSSD + + PAM Responder + + Copyright (C) Simo Sorce 2009 + Copyright (C) Sumit Bose 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 + +#include "popt.h" +#include "util/util.h" +#include "db/sysdb.h" +#include "confdb/confdb.h" +#include "dbus/dbus.h" +#include "sbus/sssd_dbus.h" +#include "responder/common/responder_packet.h" +#include "providers/data_provider.h" +#include "monitor/monitor_interfaces.h" +#include "sbus/sbus_client.h" +#include "responder/pam/pamsrv.h" + +#define SSS_PAM_SBUS_SERVICE_VERSION 0x0001 +#define SSS_PAM_SBUS_SERVICE_NAME "pam" + +static int service_reload(DBusMessage *message, struct sbus_connection *conn); + +struct sbus_method monitor_pam_methods[] = { + { MON_CLI_METHOD_PING, monitor_common_pong }, + { MON_CLI_METHOD_RELOAD, service_reload }, + { MON_CLI_METHOD_RES_INIT, monitor_common_res_init }, + { NULL, NULL } +}; + +struct sbus_interface monitor_pam_interface = { + MONITOR_INTERFACE, + MONITOR_PATH, + SBUS_DEFAULT_VTABLE, + monitor_pam_methods, + NULL +}; + +static int service_reload(DBusMessage *message, struct sbus_connection *conn) { + /* Monitor calls this function when we need to reload + * our configuration information. Perform whatever steps + * are needed to update the configuration objects. + */ + + /* Send an empty reply to acknowledge receipt */ + return monitor_common_pong(message, conn); +} + +static struct sbus_method pam_dp_methods[] = { + { NULL, NULL } +}; + +struct sbus_interface pam_dp_interface = { + DP_INTERFACE, + DP_PATH, + SBUS_DEFAULT_VTABLE, + pam_dp_methods, + NULL +}; + + +static void pam_dp_reconnect_init(struct sbus_connection *conn, int status, void *pvt) +{ + struct be_conn *be_conn = talloc_get_type(pvt, struct be_conn); + int ret; + + /* Did we reconnect successfully? */ + if (status == SBUS_RECONNECT_SUCCESS) { + DEBUG(1, ("Reconnected to the Data Provider.\n")); + + /* Identify ourselves to the data provider */ + ret = dp_common_send_id(be_conn->conn, + DATA_PROVIDER_VERSION, + "PAM", be_conn->domain->name); + /* all fine */ + if (ret == EOK) return; + } + + /* Handle failure */ + DEBUG(0, ("Could not reconnect to %s provider.\n", + be_conn->domain->name)); + + /* FIXME: kill the frontend and let the monitor restart it ? */ + /* pam_shutdown(rctx); */ +} + +static errno_t pam_get_config(struct pam_ctx *pctx, + struct resp_ctx *rctx, + struct confdb_ctx *cdb) +{ + int ret = EOK; + ret = confdb_get_int(cdb, pctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CRED_TIMEOUT, 0, + &pctx->cred_expiration); + return ret; +} + +static int pam_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb) +{ + struct sss_cmd_table *pam_cmds; + struct be_conn *iter; + struct pam_ctx *pctx; + int ret, max_retries; + + pctx = talloc_zero(mem_ctx, struct pam_ctx); + if (!pctx) { + return ENOMEM; + } + + pam_cmds = get_pam_cmds(); + ret = sss_process_init(pctx, ev, cdb, + pam_cmds, + SSS_PAM_SOCKET_NAME, + SSS_PAM_PRIV_SOCKET_NAME, + CONFDB_PAM_CONF_ENTRY, + SSS_PAM_SBUS_SERVICE_NAME, + SSS_PAM_SBUS_SERVICE_VERSION, + &monitor_pam_interface, + "PAM", &pam_dp_interface, + &pctx->rctx); + if (ret != EOK) { + return ret; + } + + pctx->rctx->pvt_ctx = pctx; + ret = pam_get_config(pctx, pctx->rctx, pctx->rctx->cdb); + + /* Enable automatic reconnection to the Data Provider */ + + /* FIXME: "retries" is too generic, either get it from a global config + * or specify these retries are about the sbus connections to DP */ + ret = confdb_get_int(pctx->rctx->cdb, pctx->rctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_SERVICE_RECON_RETRIES, 3, &max_retries); + if (ret != EOK) { + DEBUG(0, ("Failed to set up automatic reconnection\n")); + return ret; + } + + for (iter = pctx->rctx->be_conns; iter; iter = iter->next) { + sbus_reconnect_init(iter->conn, max_retries, + pam_dp_reconnect_init, iter); + } + + return EOK; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + struct main_context *main_ctx; + int ret; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + { NULL } + }; + + 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; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc... */ + debug_log_file = "sssd_pam"; + + ret = server_setup("sssd[pam]", 0, CONFDB_PAM_CONF_ENTRY, &main_ctx); + if (ret != EOK) return 2; + + 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 = pam_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx); + if (ret != EOK) return 3; + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} + diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h new file mode 100644 index 000000000..60f9c66ae --- /dev/null +++ b/src/responder/pam/pamsrv.h @@ -0,0 +1,57 @@ +/* + Authors: + Simo Sorce + Sumit Bose + + Copyright (C) 2009 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 __PAMSRV_H__ +#define __PAMSRV_H__ + +#include +#include "util/util.h" +#include "sbus/sssd_dbus.h" +#include "responder/common/responder.h" + +struct pam_auth_req; + +typedef void (pam_dp_callback_t)(struct pam_auth_req *preq); + +struct pam_ctx { + int cred_expiration; + struct resp_ctx *rctx; +}; + +struct pam_auth_req { + struct cli_ctx *cctx; + struct sss_domain_info *domain; + + struct pam_data *pd; + + pam_dp_callback_t *callback; + + bool check_provider; + void *data; +}; + +struct sss_cmd_table *get_pam_cmds(void); + +int pam_dp_send_req(struct pam_auth_req *preq, int timeout); + +int LOCAL_pam_handler(struct pam_auth_req *preq); + +#endif /* __PAMSRV_H__ */ diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c new file mode 100644 index 000000000..37aad8299 --- /dev/null +++ b/src/responder/pam/pamsrv_cmd.c @@ -0,0 +1,1181 @@ +/* + SSSD + + PAM Responder + + Copyright (C) Simo Sorce 2009 + Copyright (C) Sumit Bose 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 "util/util.h" +#include "db/sysdb.h" +#include "confdb/confdb.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "providers/data_provider.h" +#include "responder/pam/pamsrv.h" +#include "db/sysdb.h" + +static void pam_reply(struct pam_auth_req *preq); + +static int extract_authtok(uint32_t *type, uint32_t *size, uint8_t **tok, uint8_t *body, size_t blen, size_t *c) { + uint32_t data_size; + + if (blen-(*c) < 2*sizeof(uint32_t)) return EINVAL; + + memcpy(&data_size, &body[*c], sizeof(uint32_t)); + *c += sizeof(uint32_t); + if (data_size < sizeof(uint32_t) || (*c)+(data_size) > blen) return EINVAL; + *size = data_size - sizeof(uint32_t); + + memcpy(type, &body[*c], sizeof(uint32_t)); + *c += sizeof(uint32_t); + + *tok = body+(*c); + + *c += (*size); + + return EOK; +} + +static int extract_string(char **var, uint8_t *body, size_t blen, size_t *c) { + uint32_t size; + uint8_t *str; + + if (blen-(*c) < sizeof(uint32_t)+1) return EINVAL; + + memcpy(&size, &body[*c], sizeof(uint32_t)); + *c += sizeof(uint32_t); + if (*c+size > blen) return EINVAL; + + str = body+(*c); + + if (str[size-1]!='\0') return EINVAL; + + *c += size; + + *var = (char *) str; + + return EOK; +} + +static int extract_uint32_t(uint32_t *var, uint8_t *body, size_t blen, size_t *c) { + uint32_t size; + + if (blen-(*c) < 2*sizeof(uint32_t)) return EINVAL; + + memcpy(&size, &body[*c], sizeof(uint32_t)); + *c += sizeof(uint32_t); + + memcpy(var, &body[*c], sizeof(uint32_t)); + *c += sizeof(uint32_t); + + return EOK; +} + +static int pam_parse_in_data_v2(struct sss_names_ctx *snctx, + struct pam_data *pd, + uint8_t *body, size_t blen) +{ + size_t c; + uint32_t type; + uint32_t size; + char *pam_user; + int ret; + uint32_t terminator = SSS_END_OF_PAM_REQUEST; + + if (blen < 4*sizeof(uint32_t)+2 || + ((uint32_t *)body)[0] != SSS_START_OF_PAM_REQUEST || + memcmp(&body[blen - sizeof(uint32_t)], &terminator, sizeof(uint32_t)) != 0) { + DEBUG(1, ("Received data is invalid.\n")); + return EINVAL; + } + + c = sizeof(uint32_t); + do { + memcpy(&type, &body[c], sizeof(uint32_t)); + c += sizeof(uint32_t); + if (c > blen) return EINVAL; + + switch(type) { + case SSS_PAM_ITEM_USER: + ret = extract_string(&pam_user, body, blen, &c); + if (ret != EOK) return ret; + + ret = sss_parse_name(pd, snctx, pam_user, + &pd->domain, &pd->user); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_SERVICE: + ret = extract_string(&pd->service, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_TTY: + ret = extract_string(&pd->tty, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_RUSER: + ret = extract_string(&pd->ruser, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_RHOST: + ret = extract_string(&pd->rhost, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_CLI_PID: + ret = extract_uint32_t(&pd->cli_pid, + body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_AUTHTOK: + ret = extract_authtok(&pd->authtok_type, &pd->authtok_size, + &pd->authtok, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_NEWAUTHTOK: + ret = extract_authtok(&pd->newauthtok_type, + &pd->newauthtok_size, + &pd->newauthtok, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_END_OF_PAM_REQUEST: + if (c != blen) return EINVAL; + break; + default: + DEBUG(1,("Ignoring unknown data type [%d].\n", type)); + size = ((uint32_t *)&body[c])[0]; + c += size+sizeof(uint32_t); + } + } while(c < blen); + + if (pd->user == NULL || *pd->user == '\0') return EINVAL; + + DEBUG_PAM_DATA(4, pd); + + return EOK; + +} + +static int pam_parse_in_data_v3(struct sss_names_ctx *snctx, + struct pam_data *pd, + uint8_t *body, size_t blen) +{ + int ret; + + ret = pam_parse_in_data_v2(snctx, pd, body, blen); + if (ret != EOK) { + DEBUG(1, ("pam_parse_in_data_v2 failed.\n")); + return ret; + } + + if (pd->cli_pid == 0) { + DEBUG(1, ("Missing client PID.\n")); + return EINVAL; + } + + return EOK; +} + +static int pam_parse_in_data(struct sss_names_ctx *snctx, + struct pam_data *pd, + uint8_t *body, size_t blen) +{ + int start; + int end; + int last; + int ret; + + last = blen - 1; + end = 0; + + /* user name */ + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + + ret = sss_parse_name(pd, snctx, (char *)&body[start], &pd->domain, &pd->user); + if (ret != EOK) return ret; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->service = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->tty = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->ruser = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->rhost = (char *) &body[start]; + + start = end; + pd->authtok_type = (int) body[start]; + + start += sizeof(uint32_t); + pd->authtok_size = (int) body[start]; + + start += sizeof(uint32_t); + end = start + pd->authtok_size; + if (pd->authtok_size == 0) { + pd->authtok = NULL; + } else { + if (end <= blen) { + pd->authtok = (uint8_t *) &body[start]; + } else { + DEBUG(1, ("Invalid authtok size: %d\n", pd->authtok_size)); + return EINVAL; + } + } + + start = end; + pd->newauthtok_type = (int) body[start]; + + start += sizeof(uint32_t); + pd->newauthtok_size = (int) body[start]; + + start += sizeof(uint32_t); + end = start + pd->newauthtok_size; + + if (pd->newauthtok_size == 0) { + pd->newauthtok = NULL; + } else { + if (end <= blen) { + pd->newauthtok = (uint8_t *) &body[start]; + } else { + DEBUG(1, ("Invalid newauthtok size: %d\n", pd->newauthtok_size)); + return EINVAL; + } + } + + DEBUG_PAM_DATA(4, pd); + + return EOK; +} + +/*=Save-Last-Login-State===================================================*/ + +struct set_last_login_state { + struct tevent_context *ev; + struct sysdb_ctx *dbctx; + + struct sss_domain_info *dom; + const char *username; + struct sysdb_attrs *attrs; + + struct sysdb_handle *handle; + + struct ldb_result *res; +}; + +static void set_last_login_trans_done(struct tevent_req *subreq); +static void set_last_login_attrs_done(struct tevent_req *subreq); +static void set_last_login_done(struct tevent_req *subreq); + +static struct tevent_req *set_last_login_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sysdb_ctx *dbctx, + struct sss_domain_info *dom, + const char *username, + struct sysdb_attrs *attrs) +{ + struct tevent_req *req, *subreq; + struct set_last_login_state *state; + + req = tevent_req_create(memctx, &state, struct set_last_login_state); + if (!req) { + return NULL; + } + + state->ev = ev; + state->dbctx = dbctx; + state->dom = dom; + state->username = username; + state->attrs = attrs; + state->handle = NULL; + + subreq = sysdb_transaction_send(state, state->ev, state->dbctx); + if (!subreq) { + talloc_free(req); + return NULL; + } + tevent_req_set_callback(subreq, set_last_login_trans_done, req); + + return req; +} + +static void set_last_login_trans_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct set_last_login_state *state = tevent_req_data(req, + struct set_last_login_state); + int ret; + + ret = sysdb_transaction_recv(subreq, state, &state->handle); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(1, ("Unable to acquire sysdb transaction lock\n")); + tevent_req_error(req, ret); + return; + } + + subreq = sysdb_set_user_attr_send(state, state->ev, state->handle, + state->dom, state->username, + state->attrs, SYSDB_MOD_REP); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, set_last_login_attrs_done, req); +} + +static void set_last_login_attrs_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct set_last_login_state *state = tevent_req_data(req, + struct set_last_login_state); + int ret; + + ret = sysdb_set_user_attr_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(4, ("set_user_attr_callback, status [%d][%s]\n", + ret, strerror(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, set_last_login_done, req); +} + +static void set_last_login_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); + if (ret != EOK) { + DEBUG(2, ("set_last_login failed.\n")); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static int set_last_login_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/*=========================================================================*/ + + +static void set_last_login_reply(struct tevent_req *req); + +static errno_t set_last_login(struct pam_auth_req *preq) +{ + struct tevent_req *req; + struct sysdb_ctx *dbctx; + struct sysdb_attrs *attrs; + errno_t ret; + + attrs = sysdb_new_attrs(preq); + if (!attrs) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_ONLINE_AUTH, time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list, preq->domain, + &dbctx); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb context not found for this domain!\n")); + goto fail; + } + + req = set_last_login_send(preq, preq->cctx->ev, dbctx, + preq->domain, preq->pd->user, attrs); + if (!req) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(req, set_last_login_reply, preq); + + return EOK; + +fail: + return ret; +} + +static void set_last_login_reply(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + int ret; + + ret = set_last_login_recv(req); + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + } else { + preq->pd->last_auth_saved = true; + } + + preq->callback(preq); +} + +static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct pam_auth_req *preq; + + DEBUG(4, ("pam_reply_delay get called.\n")); + + preq = talloc_get_type(pvt, struct pam_auth_req); + + pam_reply(preq); +} + +static void pam_cache_auth_done(struct tevent_req *req); + +static void pam_reply(struct pam_auth_req *preq) +{ + struct cli_ctx *cctx; + uint8_t *body; + size_t blen; + int ret; + int32_t resp_c; + int32_t resp_size; + struct response_data *resp; + int p; + struct timeval tv; + struct tevent_timer *te; + struct pam_data *pd; + struct tevent_req *req; + struct sysdb_ctx *sysdb; + struct pam_ctx *pctx; + uint32_t user_info_type; + + pd = preq->pd; + cctx = preq->cctx; + + DEBUG(4, ("pam_reply get called.\n")); + + if (pd->pam_status == PAM_AUTHINFO_UNAVAIL) { + switch(pd->cmd) { + case SSS_PAM_AUTHENTICATE: + if ((preq->domain != NULL) && + (preq->domain->cache_credentials == true) && + (pd->offline_auth == false)) { + + /* do auth with offline credentials */ + pd->offline_auth = true; + + ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list, + preq->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for " + "domain [%s]!\n", preq->domain->name)); + goto done; + } + + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, + struct pam_ctx); + + req = sysdb_cache_auth_send(preq, preq->cctx->ev, sysdb, + preq->domain, pd->user, + pd->authtok, pd->authtok_size, + pctx->rctx->cdb); + if (req == NULL) { + DEBUG(1, ("Failed to setup offline auth.\n")); + /* this error is not fatal, continue */ + } else { + tevent_req_set_callback(req, pam_cache_auth_done, preq); + return; + } + } + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + case SSS_PAM_CHAUTHTOK: + DEBUG(5, ("Password change not possible while offline.\n")); + pd->pam_status = PAM_AUTHTOK_ERR; + user_info_type = SSS_PAM_USER_INFO_OFFLINE_CHPASS; + pam_add_response(pd, SSS_PAM_USER_INFO, sizeof(uint32_t), + (const uint8_t *) &user_info_type); + break; +/* TODO: we need the pam session cookie here to make sure that cached + * authentication was successful */ + case SSS_PAM_SETCRED: + case SSS_PAM_ACCT_MGMT: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + DEBUG(2, ("Assuming offline authentication setting status for " + "pam call %d to PAM_SUCCESS.\n", pd->cmd)); + pd->pam_status = PAM_SUCCESS; + break; + default: + DEBUG(1, ("Unknown PAM call [%d].\n", pd->cmd)); + pd->pam_status = PAM_MODULE_UNKNOWN; + } + } + + if (pd->response_delay > 0) { + ret = gettimeofday(&tv, NULL); + if (ret != EOK) { + DEBUG(1, ("gettimeofday failed [%d][%s].\n", + errno, strerror(errno))); + goto done; + } + tv.tv_sec += pd->response_delay; + tv.tv_usec = 0; + pd->response_delay = 0; + + te = tevent_add_timer(cctx->ev, cctx, tv, pam_reply_delay, preq); + if (te == NULL) { + DEBUG(1, ("Failed to add event pam_reply_delay.\n")); + goto done; + } + + return; + } + + /* If this was a successful login, save the lastLogin time */ + if (pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_SUCCESS && + preq->domain->cache_credentials && + !pd->offline_auth && + !pd->last_auth_saved && + NEED_CHECK_PROVIDER(preq->domain->provider)) { + ret = set_last_login(preq); + if (ret != EOK) { + goto done; + } + + return; + } + + ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + goto done; + } + + if (pd->domain != NULL) { + pam_add_response(pd, SSS_PAM_DOMAIN_NAME, strlen(pd->domain)+1, + (uint8_t *) pd->domain); + } + + resp_c = 0; + resp_size = 0; + resp = pd->resp_list; + while(resp != NULL) { + resp_c++; + resp_size += resp->len; + resp = resp->next; + } + + ret = sss_packet_grow(cctx->creq->out, sizeof(int32_t) + + sizeof(int32_t) + + resp_c * 2* sizeof(int32_t) + + resp_size); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(cctx->creq->out, &body, &blen); + DEBUG(4, ("blen: %d\n", blen)); + p = 0; + + memcpy(&body[p], &pd->pam_status, sizeof(int32_t)); + p += sizeof(int32_t); + + memcpy(&body[p], &resp_c, sizeof(int32_t)); + p += sizeof(int32_t); + + resp = pd->resp_list; + while(resp != NULL) { + memcpy(&body[p], &resp->type, sizeof(int32_t)); + p += sizeof(int32_t); + memcpy(&body[p], &resp->len, sizeof(int32_t)); + p += sizeof(int32_t); + memcpy(&body[p], resp->data, resp->len); + p += resp->len; + + resp = resp->next; + } + +done: + sss_cmd_done(cctx, preq); +} + +static void pam_cache_auth_done(struct tevent_req *req) +{ + int ret; + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + uint32_t resp_type; + size_t resp_len; + uint8_t *resp; + time_t expire_date = 0; + time_t delayed_until = -1; + long long dummy; + + ret = sysdb_cache_auth_recv(req, &expire_date, &delayed_until); + talloc_zfree(req); + + switch (ret) { + case EOK: + preq->pd->pam_status = PAM_SUCCESS; + + resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH; + resp_len = sizeof(uint32_t) + sizeof(long long); + resp = talloc_size(preq->pd, resp_len); + if (resp == NULL) { + DEBUG(1, ("talloc_size failed, cannot prepare user info.\n")); + } else { + memcpy(resp, &resp_type, sizeof(uint32_t)); + dummy = (long long) expire_date; + memcpy(resp+sizeof(uint32_t), &dummy, sizeof(long long)); + ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, + (const uint8_t *) resp); + if (ret != EOK) { + DEBUG(1, ("pam_add_response failed.\n")); + } + } + break; + case ENOENT: + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + break; + case EINVAL: + preq->pd->pam_status = PAM_AUTH_ERR; + break; + case EACCES: + preq->pd->pam_status = PAM_PERM_DENIED; + if (delayed_until >= 0) { + resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED; + resp_len = sizeof(uint32_t) + sizeof(long long); + resp = talloc_size(preq->pd, resp_len); + if (resp == NULL) { + DEBUG(1, ("talloc_size failed, cannot prepare user info.\n")); + } else { + memcpy(resp, &resp_type, sizeof(uint32_t)); + dummy = (long long) delayed_until; + memcpy(resp+sizeof(uint32_t), &dummy, sizeof(long long)); + ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, + (const uint8_t *) resp); + if (ret != EOK) { + DEBUG(1, ("pam_add_response failed.\n")); + } + } + } + break; + default: + preq->pd->pam_status = PAM_SYSTEM_ERR; + } + + pam_reply(preq); + return; +} + +static void pam_check_user_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); +static void pam_check_user_callback(void *ptr, int status, + struct ldb_result *res); +static void pam_dom_forwarder(struct pam_auth_req *preq); + +/* TODO: we should probably return some sort of cookie that is set in the + * PAM_ENVIRONMENT, so that we can save performing some calls and cache + * data. */ + +static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd) +{ + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct pam_auth_req *preq; + struct pam_data *pd; + uint8_t *body; + size_t blen; + int timeout; + int ret; + uint32_t terminator = SSS_END_OF_PAM_REQUEST; + preq = talloc_zero(cctx, struct pam_auth_req); + if (!preq) { + return ENOMEM; + } + preq->cctx = cctx; + + preq->pd = talloc_zero(preq, struct pam_data); + if (!preq->pd) { + talloc_free(preq); + return ENOMEM; + } + pd = preq->pd; + + sss_packet_get_body(cctx->creq->in, &body, &blen); + if (blen >= sizeof(uint32_t) && + memcmp(&body[blen - sizeof(uint32_t)], &terminator, sizeof(uint32_t)) != 0) { + DEBUG(1, ("Received data not terminated.\n")); + ret = EINVAL; + goto done; + } + + pd->cmd = pam_cmd; + pd->priv = cctx->priv; + + switch (cctx->cli_protocol_version->version) { + case 1: + ret = pam_parse_in_data(cctx->rctx->names, pd, body, blen); + break; + case 2: + ret = pam_parse_in_data_v2(cctx->rctx->names, pd, body, blen); + break; + case 3: + ret = pam_parse_in_data_v3(cctx->rctx->names, pd, body, blen); + break; + default: + DEBUG(1, ("Illegal protocol version [%d].\n", + cctx->cli_protocol_version->version)); + ret = EINVAL; + } + if (ret != EOK) { + ret = EINVAL; + goto done; + } + + /* now check user is valid */ + if (pd->domain) { + for (dom = cctx->rctx->domains; dom; dom = dom->next) { + if (strcasecmp(dom->name, pd->domain) == 0) break; + } + if (!dom) { + ret = ENOENT; + goto done; + } + preq->domain = dom; + } + else { + for (dom = preq->cctx->rctx->domains; dom; dom = dom->next) { + if (dom->fqnames) continue; + +/* FIXME: need to support negative cache */ +#if HAVE_NEG_CACHE + ncret = sss_ncache_check_user(nctx->ncache, nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; +#endif + break; + } + if (!dom) { + ret = ENOENT; + goto done; + } + preq->domain = dom; + } + + if (preq->domain->provider == NULL) { + DEBUG(1, ("Domain [%s] has no auth provider.\n", preq->domain->name)); + ret = EINVAL; + goto done; + } + + /* When auth is requested always search the provider first, + * do not rely on cached data unless the provider is completely + * offline */ + if (NEED_CHECK_PROVIDER(preq->domain->provider) && + (pam_cmd == SSS_PAM_AUTHENTICATE || pam_cmd == SSS_PAM_SETCRED)) { + + /* no need to re-check later on */ + preq->check_provider = false; + timeout = SSS_CLI_SOCKET_TIMEOUT/2; + + ret = sss_dp_send_acct_req(preq->cctx->rctx, preq, + pam_check_user_dp_callback, preq, + timeout, preq->domain->name, + false, SSS_DP_INITGROUPS, + preq->pd->user, 0); + } + else { + preq->check_provider = NEED_CHECK_PROVIDER(preq->domain->provider); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + preq->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + goto done; + } + ret = sysdb_getpwnam(preq, sysdb, + preq->domain, preq->pd->user, + pam_check_user_callback, preq); + } + +done: + if (ret != EOK) { + switch (ret) { + case ENOENT: + pd->pam_status = PAM_USER_UNKNOWN; + default: + pd->pam_status = PAM_SYSTEM_ERR; + } + pam_reply(preq); + } + return EOK; +} + +static void pam_check_user_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr) +{ + struct pam_auth_req *preq = talloc_get_type(ptr, struct pam_auth_req); + struct sysdb_ctx *sysdb; + int ret; + + if (err_maj) { + DEBUG(2, ("Unable to get information from Data Provider\n" + "Error: %u, %u, %s\n", + (unsigned int)err_maj, (unsigned int)err_min, err_msg)); + } + + /* always try to see if we have the user in cache even if the provider + * returned an error */ + ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list, + preq->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + goto done; + } + ret = sysdb_getpwnam(preq, sysdb, + preq->domain, preq->pd->user, + pam_check_user_callback, preq); + +done: + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +static void pam_check_user_callback(void *ptr, int status, + struct ldb_result *res) +{ + struct pam_auth_req *preq = talloc_get_type(ptr, struct pam_auth_req); + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + uint64_t cacheExpire; + bool call_provider = false; + time_t timeout; + int ret; + + if (status != LDB_SUCCESS) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + return; + } + + timeout = SSS_CLI_SOCKET_TIMEOUT/2; + + if (preq->check_provider) { + switch (res->count) { + case 0: + call_provider = true; + break; + + case 1: + cacheExpire = ldb_msg_find_attr_as_uint64(res->msgs[0], + SYSDB_CACHE_EXPIRE, 0); + if (cacheExpire < time(NULL)) { + call_provider = true; + } + break; + + default: + DEBUG(1, ("check user call returned more than one result !?!\n")); + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + return; + } + } + + if (call_provider) { + + /* dont loop forever :-) */ + preq->check_provider = false; + + /* keep around current data in case backend is offline */ + if (res->count) { + preq->data = talloc_steal(preq, res); + } + + ret = sss_dp_send_acct_req(preq->cctx->rctx, preq, + pam_check_user_dp_callback, preq, + timeout, preq->domain->name, + false, SSS_DP_USER, + preq->pd->user, 0); + if (ret != EOK) { + DEBUG(3, ("Failed to dispatch request: %d(%s)\n", + ret, strerror(ret))); + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } + return; + } + + switch (res->count) { + case 0: + if (!preq->pd->domain) { + /* search next as the domain was unknown */ + + ret = EOK; + + /* skip domains that require FQnames or have negative caches */ + for (dom = preq->domain->next; dom; dom = dom->next) { + + if (dom->fqnames) continue; + +#if HAVE_NEG_CACHE + ncret = nss_ncache_check_user(nctx->ncache, + nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; + + neghit = true; +#endif + break; + } +#if HAVE_NEG_CACHE + /* reset neghit if we still have a domain to check */ + if (dom) neghit = false; + + if (neghit) { + DEBUG(2, ("User [%s] does not exist! (negative cache)\n", + cmdctx->name)); + ret = ENOENT; + } +#endif + if (dom == NULL) { + DEBUG(2, ("No matching domain found for [%s], fail!\n", + preq->pd->user)); + ret = ENOENT; + } + + if (ret == EOK) { + preq->domain = dom; + preq->data = NULL; + + DEBUG(4, ("Requesting info for [%s@%s]\n", + preq->pd->user, preq->domain->name)); + + /* When auth is requested always search the provider first, + * do not rely on cached data unless the provider is + * completely offline */ + if (NEED_CHECK_PROVIDER(preq->domain->provider) && + (preq->pd->cmd == SSS_PAM_AUTHENTICATE || + preq->pd->cmd == SSS_PAM_SETCRED)) { + + /* no need to re-check later on */ + preq->check_provider = false; + + ret = sss_dp_send_acct_req(preq->cctx->rctx, preq, + pam_check_user_dp_callback, + preq, timeout, + preq->domain->name, + false, SSS_DP_USER, + preq->pd->user, 0); + } + else { + preq->check_provider = NEED_CHECK_PROVIDER(preq->domain->provider); + + ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list, + preq->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + return; + } + ret = sysdb_getpwnam(preq, sysdb, + preq->domain, preq->pd->user, + pam_check_user_callback, preq); + } + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + } + + /* we made another call, end here */ + if (ret == EOK) return; + } + else { + ret = ENOENT; + } + + DEBUG(2, ("No results for check user call\n")); + +#if HAVE_NEG_CACHE + /* set negative cache only if not result of cache check */ + if (!neghit) { + ret = nss_ncache_set_user(nctx->ncache, false, + dctx->domain->name, cmdctx->name); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + } +#endif + + if (ret != EOK) { + if (ret == ENOENT) { + preq->pd->pam_status = PAM_USER_UNKNOWN; + } else { + preq->pd->pam_status = PAM_SYSTEM_ERR; + } + pam_reply(preq); + return; + } + break; + + case 1: + + /* BINGO */ + preq->pd->pw_uid = + ldb_msg_find_attr_as_int(res->msgs[0], SYSDB_UIDNUM, -1); + if (preq->pd->pw_uid == -1) { + DEBUG(1, ("Failed to find uid for user [%s] in domain [%s].\n", + preq->pd->user, preq->pd->domain)); + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } + + preq->pd->gr_gid = + ldb_msg_find_attr_as_int(res->msgs[0], SYSDB_GIDNUM, -1); + if (preq->pd->gr_gid == -1) { + DEBUG(1, ("Failed to find gid for user [%s] in domain [%s].\n", + preq->pd->user, preq->pd->domain)); + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } + + pam_dom_forwarder(preq); + return; + + default: + DEBUG(1, ("check user call returned more than one result !?!\n")); + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +static void pam_dom_forwarder(struct pam_auth_req *preq) +{ + int ret; + + if (!preq->pd->domain) { + preq->pd->domain = preq->domain->name; + } + + if (!NEED_CHECK_PROVIDER(preq->domain->provider)) { + preq->callback = pam_reply; + ret = LOCAL_pam_handler(preq); + } + else { + preq->callback = pam_reply; + ret = pam_dp_send_req(preq, SSS_CLI_SOCKET_TIMEOUT/2); + DEBUG(4, ("pam_dp_send_req returned %d\n", ret)); + } + + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +static int pam_cmd_authenticate(struct cli_ctx *cctx) { + DEBUG(4, ("entering pam_cmd_authenticate\n")); + return pam_forwarder(cctx, SSS_PAM_AUTHENTICATE); +} + +static int pam_cmd_setcred(struct cli_ctx *cctx) { + DEBUG(4, ("entering pam_cmd_setcred\n")); + return pam_forwarder(cctx, SSS_PAM_SETCRED); +} + +static int pam_cmd_acct_mgmt(struct cli_ctx *cctx) { + DEBUG(4, ("entering pam_cmd_acct_mgmt\n")); + return pam_forwarder(cctx, SSS_PAM_ACCT_MGMT); +} + +static int pam_cmd_open_session(struct cli_ctx *cctx) { + DEBUG(4, ("entering pam_cmd_open_session\n")); + return pam_forwarder(cctx, SSS_PAM_OPEN_SESSION); +} + +static int pam_cmd_close_session(struct cli_ctx *cctx) { + DEBUG(4, ("entering pam_cmd_close_session\n")); + return pam_forwarder(cctx, SSS_PAM_CLOSE_SESSION); +} + +static int pam_cmd_chauthtok(struct cli_ctx *cctx) { + DEBUG(4, ("entering pam_cmd_chauthtok\n")); + return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK); +} + +static int pam_cmd_chauthtok_prelim(struct cli_ctx *cctx) { + DEBUG(4, ("entering pam_cmd_chauthtok_prelim\n")); + return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK_PRELIM); +} + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version pam_cli_protocol_version[] = { + {3, "2009-09-14", "make cli_pid mandatory"}, + {2, "2009-05-12", "new format "}, + {1, "2008-09-05", "initial version, \\0 terminated strings"}, + {0, NULL, NULL} + }; + + return pam_cli_protocol_version; +} + +struct sss_cmd_table *get_pam_cmds(void) +{ + static struct sss_cmd_table sss_cmds[] = { + {SSS_GET_VERSION, sss_cmd_get_version}, + {SSS_PAM_AUTHENTICATE, pam_cmd_authenticate}, + {SSS_PAM_SETCRED, pam_cmd_setcred}, + {SSS_PAM_ACCT_MGMT, pam_cmd_acct_mgmt}, + {SSS_PAM_OPEN_SESSION, pam_cmd_open_session}, + {SSS_PAM_CLOSE_SESSION, pam_cmd_close_session}, + {SSS_PAM_CHAUTHTOK, pam_cmd_chauthtok}, + {SSS_PAM_CHAUTHTOK_PRELIM, pam_cmd_chauthtok_prelim}, + {SSS_CLI_NULL, NULL} + }; + + return sss_cmds; +} diff --git a/src/responder/pam/pamsrv_dp.c b/src/responder/pam/pamsrv_dp.c new file mode 100644 index 000000000..071d09b8e --- /dev/null +++ b/src/responder/pam/pamsrv_dp.c @@ -0,0 +1,142 @@ +/* + SSSD + + NSS Responder - Data Provider Interfaces + + Copyright (C) Simo Sorce 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 . +*/ + +#include +#include + +#include +#include + +#include "util/util.h" +#include "responder/common/responder_packet.h" +#include "providers/data_provider.h" +#include "sbus/sbus_client.h" +#include "responder/pam/pamsrv.h" + +static void pam_dp_process_reply(DBusPendingCall *pending, void *ptr) +{ + DBusError dbus_error; + DBusMessage* msg; + int ret; + int type; + struct pam_auth_req *preq; + + preq = talloc_get_type(ptr, struct pam_auth_req); + + dbus_error_init(&dbus_error); + + dbus_pending_call_block(pending); + msg = dbus_pending_call_steal_reply(pending); + if (msg == NULL) { + DEBUG(0, ("Severe error. A reply callback was called but no reply was received and no timeout occurred\n")); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + + type = dbus_message_get_type(msg); + switch (type) { + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + ret = dp_unpack_pam_response(msg, preq->pd, &dbus_error); + if (!ret) { + DEBUG(0, ("Failed to parse reply.\n")); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + DEBUG(4, ("received: [%d][%s]\n", preq->pd->pam_status, preq->pd->domain)); + break; + case DBUS_MESSAGE_TYPE_ERROR: + DEBUG(0, ("Reply error.\n")); + preq->pd->pam_status = PAM_SYSTEM_ERR; + break; + default: + DEBUG(0, ("Default... what now?.\n")); + preq->pd->pam_status = PAM_SYSTEM_ERR; + } + + +done: + dbus_pending_call_unref(pending); + dbus_message_unref(msg); + preq->callback(preq); +} + +int pam_dp_send_req(struct pam_auth_req *preq, int timeout) +{ + struct pam_data *pd = preq->pd; + struct be_conn *be_conn; + DBusMessage *msg; + DBusPendingCall *pending_reply; + DBusConnection *dbus_conn; + dbus_bool_t ret; + int res; + + /* double check dp_ctx has actually been initialized. + * in some pathological cases it may happen that nss starts up before + * dp connection code is actually able to establish a connection. + */ + res = sss_dp_get_domain_conn(preq->cctx->rctx, + preq->domain->name, &be_conn); + if (res != EOK) { + DEBUG(1, ("The Data Provider connection for %s is not available!" + " This maybe a bug, it shouldn't happen!\n", preq->domain)); + return EIO; + } + dbus_conn = sbus_get_connection(be_conn->conn); + + msg = dbus_message_new_method_call(NULL, + DP_PATH, + DP_INTERFACE, + DP_METHOD_PAMHANDLER); + if (msg == NULL) { + DEBUG(0,("Out of memory?!\n")); + return ENOMEM; + } + + + DEBUG(4, ("Sending request with the following data:\n")); + DEBUG_PAM_DATA(4, pd); + + ret = dp_pack_pam_request(msg, pd); + if (!ret) { + DEBUG(1,("Failed to build message\n")); + return EIO; + } + + ret = dbus_connection_send_with_reply(dbus_conn, msg, &pending_reply, timeout); + if (!ret || pending_reply == NULL) { + /* + * 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; + } + + dbus_pending_call_set_notify(pending_reply, + pam_dp_process_reply, preq, NULL); + dbus_message_unref(msg); + + return EOK; +} + -- cgit