nsssrv_cmd.c revision 9f26cae5ae716a0542261167b5a91d7d2e1763e7
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder/*
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder SSSD
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder NSS Responder
98890889ffb2e8f6f722b00e265a211f13b5a861Corneliu-Claudiu Prodescu
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder This program is free software; you can redistribute it and/or modify
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder it under the terms of the GNU General Public License as published by
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder the Free Software Foundation; either version 3 of the License, or
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder (at your option) any later version.
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder
486db0a875bcdd0b80cf0d447d14c9c00a92ae94Simon Ulbricht This program is distributed in the hope that it will be useful,
486db0a875bcdd0b80cf0d447d14c9c00a92ae94Simon Ulbricht but WITHOUT ANY WARRANTY; without even the implied warranty of
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
c208973c890b8f993297720fd0247bc7481d4304Christian Maeder GNU General Public License for more details.
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder
863c98ae89e37c21c0c04b9b130b5136688976eeChristian Maeder You should have received a copy of the GNU General Public License
486db0a875bcdd0b80cf0d447d14c9c00a92ae94Simon Ulbricht along with this program. If not, see <http://www.gnu.org/licenses/>.
863c98ae89e37c21c0c04b9b130b5136688976eeChristian Maeder*/
863c98ae89e37c21c0c04b9b130b5136688976eeChristian Maeder
863c98ae89e37c21c0c04b9b130b5136688976eeChristian Maeder#include "util/util.h"
863c98ae89e37c21c0c04b9b130b5136688976eeChristian Maeder#include "util/sss_nss.h"
3f9fabb8ac5cfd9234431ecf19b51ff3e985595aChristian Maeder#include "responder/nss/nsssrv.h"
f0d823af5e37881b77328bbcff8c96b58b92c89fChristian Maeder#include "responder/nss/nsssrv_private.h"
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder#include "responder/nss/nsssrv_netgroup.h"
df0e8744a2befcba003ea6d93214601c743bde74Christian Maeder#include "responder/nss/nsssrv_services.h"
df0e8744a2befcba003ea6d93214601c743bde74Christian Maeder#include "responder/nss/nsssrv_mmap_cache.h"
0607966fab6924a9fda8b55a9dccb28236a856deSimon Ulbricht#include "responder/common/negcache.h"
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder#include "confdb/confdb.h"
863c98ae89e37c21c0c04b9b130b5136688976eeChristian Maeder#include "db/sysdb.h"
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder#include <time.h>
df0e8744a2befcba003ea6d93214601c743bde74Christian Maeder
bea536c79193020edac8fc6ebdd51b11f885b5afChristian Maederstatic int nss_cmd_send_error(struct nss_cmd_ctx *cmdctx, int err)
9aec0bc9d57df2669c8095fb1b4bd954d80b5537Christian Maeder{
863c98ae89e37c21c0c04b9b130b5136688976eeChristian Maeder return sss_cmd_send_error(cmdctx->cctx, err);
f0d823af5e37881b77328bbcff8c96b58b92c89fChristian Maeder}
b8cd2804f426fd97148615fe31c1f47afac7a683Christian Maeder
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maederstatic int nss_cmd_send_empty(struct nss_cmd_ctx *cmdctx)
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder{
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder struct cli_ctx *cctx = cmdctx->cctx;
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder return sss_cmd_send_empty(cctx, cmdctx);
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder}
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder
e46d78f7c6324ed9f1a191d46b6e5732e61e1835Simon Ulbrichtint nss_cmd_done(struct nss_cmd_ctx *cmdctx, int ret)
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder{
d864f0a0e04e61b5f87963496765eafcf646ed7bChristian Maeder switch (ret) {
3ddbdc6f84dbf9a59e178575db8359d3aab1dd2fChristian Maeder case EOK:
3ddbdc6f84dbf9a59e178575db8359d3aab1dd2fChristian Maeder /* all fine, just return here */
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder break;
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder
f318d03bd3eb55a9edfae36ffdc28737b71b4df5Simon Ulbricht case ENOENT:
d864f0a0e04e61b5f87963496765eafcf646ed7bChristian Maeder ret = nss_cmd_send_empty(cmdctx);
a43c1a7fa08c12524415386aa13a566cc9e53a4fChristian Maeder if (ret) {
6204e46b2e31a71e6e98c6ecc5efc11e61a76a01Christian Maeder return EFAULT;
09cff718cd8961fac2566baad00779f604786bbfChristian Maeder }
09cff718cd8961fac2566baad00779f604786bbfChristian Maeder break;
863c98ae89e37c21c0c04b9b130b5136688976eeChristian Maeder
863c98ae89e37c21c0c04b9b130b5136688976eeChristian Maeder case EAGAIN:
46b207daf66b64930a59f3615c8b127aac0b8e43Christian Maeder /* async processing, just return here */
f0d823af5e37881b77328bbcff8c96b58b92c89fChristian Maeder break;
f0d823af5e37881b77328bbcff8c96b58b92c89fChristian Maeder
f0d823af5e37881b77328bbcff8c96b58b92c89fChristian Maeder case EFAULT:
f0d823af5e37881b77328bbcff8c96b58b92c89fChristian Maeder /* very bad error */
f0d823af5e37881b77328bbcff8c96b58b92c89fChristian Maeder return EFAULT;
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder
f0d823af5e37881b77328bbcff8c96b58b92c89fChristian Maeder default:
f0d823af5e37881b77328bbcff8c96b58b92c89fChristian Maeder ret = nss_cmd_send_error(cmdctx, ret);
f0d823af5e37881b77328bbcff8c96b58b92c89fChristian Maeder if (ret) {
f0d823af5e37881b77328bbcff8c96b58b92c89fChristian Maeder return EFAULT;
f0d823af5e37881b77328bbcff8c96b58b92c89fChristian Maeder }
2f2d2bc388803936d992afbb3596ff1c6ebcb197Christian Maeder sss_cmd_done(cmdctx->cctx, cmdctx);
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder break;
f0d823af5e37881b77328bbcff8c96b58b92c89fChristian Maeder }
f0d823af5e37881b77328bbcff8c96b58b92c89fChristian Maeder
081deee1bac477ab8db717646baba47f0fe95479Christian Maeder return EOK;
081deee1bac477ab8db717646baba47f0fe95479Christian Maeder}
081deee1bac477ab8db717646baba47f0fe95479Christian Maeder
081deee1bac477ab8db717646baba47f0fe95479Christian Maeder/***************************
081deee1bac477ab8db717646baba47f0fe95479Christian Maeder * Enumeration procedures *
92ae4d5885ea837ffe3dae9b2de742f871229b94Christian Maeder ***************************/
92ae4d5885ea837ffe3dae9b2de742f871229b94Christian Maedererrno_t nss_setent_add_ref(TALLOC_CTX *memctx,
92ae4d5885ea837ffe3dae9b2de742f871229b94Christian Maeder struct getent_ctx *getent_ctx,
863c98ae89e37c21c0c04b9b130b5136688976eeChristian Maeder struct tevent_req *req)
5b68f1141555736e0b7ddbe14218bcabcc44636fChristian Maeder{
5b68f1141555736e0b7ddbe14218bcabcc44636fChristian Maeder return setent_add_ref(memctx, getent_ctx, &getent_ctx->reqs, req);
5b68f1141555736e0b7ddbe14218bcabcc44636fChristian Maeder}
5b68f1141555736e0b7ddbe14218bcabcc44636fChristian Maeder
5b68f1141555736e0b7ddbe14218bcabcc44636fChristian Maedervoid nss_setent_notify_error(struct getent_ctx *getent_ctx, errno_t ret)
595bf3e172e12ef62c72b9a87ff7aedbfed416ffSimon Ulbricht{
595bf3e172e12ef62c72b9a87ff7aedbfed416ffSimon Ulbricht return setent_notify(&getent_ctx->reqs, ret);
595bf3e172e12ef62c72b9a87ff7aedbfed416ffSimon Ulbricht}
595bf3e172e12ef62c72b9a87ff7aedbfed416ffSimon Ulbricht
595bf3e172e12ef62c72b9a87ff7aedbfed416ffSimon Ulbrichtvoid nss_setent_notify_done(struct getent_ctx *getent_ctx)
595bf3e172e12ef62c72b9a87ff7aedbfed416ffSimon Ulbricht{
4e1239f8b5fa139bd9be8d0431d711c7b88a58c2Christian Maeder return setent_notify_done(&getent_ctx->reqs);
5b68f1141555736e0b7ddbe14218bcabcc44636fChristian Maeder}
863c98ae89e37c21c0c04b9b130b5136688976eeChristian Maeder
e49fd57c63845c7806860a9736ad09f6d44dbaedChristian Maederstruct setent_ctx {
863c98ae89e37c21c0c04b9b130b5136688976eeChristian Maeder struct cli_ctx *client;
863c98ae89e37c21c0c04b9b130b5136688976eeChristian Maeder struct nss_ctx *nctx;
e4147f07c61def118b6052ccdf57207b0c113921Simon Ulbricht struct nss_dom_ctx *dctx;
595bf3e172e12ef62c72b9a87ff7aedbfed416ffSimon Ulbricht struct getent_ctx *getent_ctx;
e3d7fd1b63d824960b1c17b6c7009d52d7528c1eChristian Maeder};
e3d7fd1b63d824960b1c17b6c7009d52d7528c1eChristian Maeder
deb58cca57a0ec5eacf69ec6770d70b4701fa8b4Christian Maeder/****************************************************************************
e3d7fd1b63d824960b1c17b6c7009d52d7528c1eChristian Maeder * PASSWD db related functions
e46d78f7c6324ed9f1a191d46b6e5732e61e1835Simon Ulbricht ***************************************************************************/
92ae4d5885ea837ffe3dae9b2de742f871229b94Christian Maeder
e46d78f7c6324ed9f1a191d46b6e5732e61e1835Simon Ulbrichtstatic gid_t get_gid_override(struct ldb_message *msg,
2de7f67ad6bfc38baabe7176c1bad5d8f176bd48Christian Maeder struct sss_domain_info *dom)
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder{
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder return dom->override_gid ?
09e4825f8cdc3fb95ae24125d0246eb1b7bf1e22Christian Maeder dom->override_gid :
863c98ae89e37c21c0c04b9b130b5136688976eeChristian Maeder ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0);
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder}
3f9fabb8ac5cfd9234431ecf19b51ff3e985595aChristian Maeder
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maederstatic const char *get_homedir_override(TALLOC_CTX *mem_ctx,
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder struct ldb_message *msg,
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder struct nss_ctx *nctx,
3f9fabb8ac5cfd9234431ecf19b51ff3e985595aChristian Maeder struct sss_domain_info *dom,
34db68bf7b0a0cb624373cc364a56442c1b3f0f7Christian Maeder const char *name,
34db68bf7b0a0cb624373cc364a56442c1b3f0f7Christian Maeder uint32_t uid)
34db68bf7b0a0cb624373cc364a56442c1b3f0f7Christian Maeder{
34db68bf7b0a0cb624373cc364a56442c1b3f0f7Christian Maeder const char *homedir;
34db68bf7b0a0cb624373cc364a56442c1b3f0f7Christian Maeder
34db68bf7b0a0cb624373cc364a56442c1b3f0f7Christian Maeder /* Check whether we are unconditionally overriding the server
34db68bf7b0a0cb624373cc364a56442c1b3f0f7Christian Maeder * for home directory locations.
2fe147b05116aea0862eab9c0342b3c4be8ec1fdChristian Maeder */
0607966fab6924a9fda8b55a9dccb28236a856deSimon Ulbricht if (dom->override_homedir) {
0607966fab6924a9fda8b55a9dccb28236a856deSimon Ulbricht return expand_homedir_template(mem_ctx, dom->override_homedir,
069541f0927770d9f3eded5e163a815f3e59767fChristian Maeder name, uid, dom->name);
54233d1f5ebf82ebb341a0f481e8ae657fc90e91Christian Maeder } else if (nctx->override_homedir) {
863c98ae89e37c21c0c04b9b130b5136688976eeChristian Maeder return expand_homedir_template(mem_ctx, nctx->override_homedir,
34db68bf7b0a0cb624373cc364a56442c1b3f0f7Christian Maeder name, uid, dom->name);
34db68bf7b0a0cb624373cc364a56442c1b3f0f7Christian Maeder }
a58949e654464bf3ae767a41d10d6ffd67d697ecChristian Maeder
c62c1b82e3316ff978475d6e61a5ed172fe60b0bChristian Maeder homedir = ldb_msg_find_attr_as_string(msg, SYSDB_HOMEDIR, NULL);
eccd7c7446e270bda674c07248d04fccc41cba0bSimon Ulbricht if (!homedir || *homedir == '\0') {
eccd7c7446e270bda674c07248d04fccc41cba0bSimon Ulbricht /* In the case of a NULL or empty homedir, check to see if
dd8d2aeeb3648de87bd70d261a9e0a49b8239f4bSimon Ulbricht * we have a fallback homedir to use.
dd8d2aeeb3648de87bd70d261a9e0a49b8239f4bSimon Ulbricht */
dd8d2aeeb3648de87bd70d261a9e0a49b8239f4bSimon Ulbricht if (dom->fallback_homedir) {
eccd7c7446e270bda674c07248d04fccc41cba0bSimon Ulbricht return expand_homedir_template(mem_ctx, dom->fallback_homedir,
eccd7c7446e270bda674c07248d04fccc41cba0bSimon Ulbricht name, uid, dom->name);
34db68bf7b0a0cb624373cc364a56442c1b3f0f7Christian Maeder } else if (nctx->fallback_homedir) {
bea536c79193020edac8fc6ebdd51b11f885b5afChristian Maeder return expand_homedir_template(mem_ctx, nctx->fallback_homedir,
54233d1f5ebf82ebb341a0f481e8ae657fc90e91Christian Maeder name, uid, dom->name);
bea536c79193020edac8fc6ebdd51b11f885b5afChristian Maeder }
bea536c79193020edac8fc6ebdd51b11f885b5afChristian Maeder }
bea536c79193020edac8fc6ebdd51b11f885b5afChristian Maeder
bea536c79193020edac8fc6ebdd51b11f885b5afChristian Maeder /* Return the value we got from the provider */
4e1239f8b5fa139bd9be8d0431d711c7b88a58c2Christian Maeder return talloc_strdup(mem_ctx, homedir);
eccd7c7446e270bda674c07248d04fccc41cba0bSimon Ulbricht}
eccd7c7446e270bda674c07248d04fccc41cba0bSimon Ulbricht
c62c1b82e3316ff978475d6e61a5ed172fe60b0bChristian Maederstatic const char *get_shell_override(TALLOC_CTX *mem_ctx,
c62c1b82e3316ff978475d6e61a5ed172fe60b0bChristian Maeder struct ldb_message *msg,
c62c1b82e3316ff978475d6e61a5ed172fe60b0bChristian Maeder struct nss_ctx *nctx)
c62c1b82e3316ff978475d6e61a5ed172fe60b0bChristian Maeder{
54233d1f5ebf82ebb341a0f481e8ae657fc90e91Christian Maeder const char *user_shell;
54233d1f5ebf82ebb341a0f481e8ae657fc90e91Christian Maeder int i;
54233d1f5ebf82ebb341a0f481e8ae657fc90e91Christian Maeder
54233d1f5ebf82ebb341a0f481e8ae657fc90e91Christian Maeder user_shell = ldb_msg_find_attr_as_string(msg, SYSDB_SHELL, NULL);
54233d1f5ebf82ebb341a0f481e8ae657fc90e91Christian Maeder if (!user_shell) {
54233d1f5ebf82ebb341a0f481e8ae657fc90e91Christian Maeder /* Check whether there is a default shell specified */
54233d1f5ebf82ebb341a0f481e8ae657fc90e91Christian Maeder if (nctx->default_shell) {
54233d1f5ebf82ebb341a0f481e8ae657fc90e91Christian Maeder return talloc_strdup(mem_ctx, nctx->default_shell);
0a5165c161ce13d434b5c0488b533a8de98aafaaChristian Maeder }
0a5165c161ce13d434b5c0488b533a8de98aafaaChristian Maeder return NULL;
0a5165c161ce13d434b5c0488b533a8de98aafaaChristian Maeder }
0a5165c161ce13d434b5c0488b533a8de98aafaaChristian Maeder if (!nctx->allowed_shells && !nctx->vetoed_shells) return talloc_strdup(mem_ctx, user_shell);
0a5165c161ce13d434b5c0488b533a8de98aafaaChristian Maeder
0a5165c161ce13d434b5c0488b533a8de98aafaaChristian Maeder if (nctx->vetoed_shells) {
61116f0ae514354dddda28edc7af459e8b23e8b4Christian Maeder for (i=0; nctx->vetoed_shells[i]; i++) {
92ae4d5885ea837ffe3dae9b2de742f871229b94Christian Maeder if (strcmp(nctx->vetoed_shells[i], user_shell) == 0) {
486db0a875bcdd0b80cf0d447d14c9c00a92ae94Simon Ulbricht DEBUG(5, ("The shell '%s' is vetoed. "
486db0a875bcdd0b80cf0d447d14c9c00a92ae94Simon Ulbricht "Using fallback\n", user_shell));
486db0a875bcdd0b80cf0d447d14c9c00a92ae94Simon Ulbricht return talloc_strdup(mem_ctx, nctx->shell_fallback);
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder }
486db0a875bcdd0b80cf0d447d14c9c00a92ae94Simon Ulbricht }
486db0a875bcdd0b80cf0d447d14c9c00a92ae94Simon Ulbricht }
486db0a875bcdd0b80cf0d447d14c9c00a92ae94Simon Ulbricht
a7b34c1a61dabe150288424d90389d5988bf9d7aChristian Maeder if (nctx->etc_shells) {
a7b34c1a61dabe150288424d90389d5988bf9d7aChristian Maeder for (i=0; nctx->etc_shells[i]; i++) {
c30231257d9116b514dce02703a515fe21cd427dTill Mossakowski if (strcmp(user_shell, nctx->etc_shells[i]) == 0) {
486db0a875bcdd0b80cf0d447d14c9c00a92ae94Simon Ulbricht DEBUG(9, ("Shell %s found in /etc/shells\n",
a7b34c1a61dabe150288424d90389d5988bf9d7aChristian Maeder nctx->etc_shells[i]));
486db0a875bcdd0b80cf0d447d14c9c00a92ae94Simon Ulbricht break;
a7b34c1a61dabe150288424d90389d5988bf9d7aChristian Maeder }
a7b34c1a61dabe150288424d90389d5988bf9d7aChristian Maeder }
bb9642ff292545658dc11251b83a7b7af3c1fccbChristian Maeder
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder if (nctx->etc_shells[i]) {
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder DEBUG(9, ("Using original shell '%s'\n", user_shell));
bb9642ff292545658dc11251b83a7b7af3c1fccbChristian Maeder return talloc_strdup(mem_ctx, user_shell);
fbb98c3714200d240ae693dc673cc36198cff2f5Till Mossakowski }
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder }
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder if (nctx->allowed_shells) {
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder for (i=0; nctx->allowed_shells[i]; i++) {
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder if (strcmp(nctx->allowed_shells[i], user_shell) == 0) {
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder DEBUG(5, ("The shell '%s' is allowed but does not exist. "
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder "Using fallback\n", user_shell));
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder return talloc_strdup(mem_ctx, nctx->shell_fallback);
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder }
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder }
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder }
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder
62631c8934cb171853a6c3d7910d1cd335007bf5Christian Maeder DEBUG(5, ("The shell '%s' is not allowed and does not exist.\n",
fbb98c3714200d240ae693dc673cc36198cff2f5Till Mossakowski user_shell));
fbb98c3714200d240ae693dc673cc36198cff2f5Till Mossakowski return talloc_strdup(mem_ctx, NOLOGIN_SHELL);
fbb98c3714200d240ae693dc673cc36198cff2f5Till Mossakowski}
9dd36c71632e08a1bf73b7bf4a61380e3474758aChristian Maeder
9dd36c71632e08a1bf73b7bf4a61380e3474758aChristian Maederstatic int fill_pwent(struct sss_packet *packet,
377dd477158c0aca3244fd1c2cbb99b090356b9cTill Mossakowski struct sss_domain_info *dom,
struct nss_ctx *nctx,
bool filter_users, bool pw_mmap_cache,
struct ldb_message **msgs,
int *count)
{
struct ldb_message *msg;
uint8_t *body;
const char *tmpstr;
const char *orig_name;
struct sized_string name;
struct sized_string gecos;
struct sized_string homedir;
struct sized_string shell;
struct sized_string pwfield;
struct sized_string fullname;
uint32_t uid;
uint32_t gid;
size_t rsize, rp, blen;
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;
TALLOC_CTX *tmp_ctx = NULL;
if (add_domain) dom_len = strlen(domain);
to_sized_string(&pwfield, nctx->pwfield);
rp = 2*sizeof(uint32_t);
num = 0;
for (i = 0; i < *count; i++) {
talloc_zfree(tmp_ctx);
tmp_ctx = talloc_new(NULL);
msg = msgs[i];
orig_name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
uid = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0);
gid = get_gid_override(msg, dom);
if (!orig_name || !uid || !gid) {
DEBUG(2, ("Incomplete or fake user object for %s[%llu]! Skipping\n",
orig_name?orig_name:"<NULL>", (unsigned long long int)uid));
continue;
}
if (filter_users) {
ncret = sss_ncache_check_user(nctx->ncache,
nctx->neg_timeout,
dom, orig_name);
if (ncret == EEXIST) {
DEBUG(4, ("User [%s@%s] filtered out! (negative cache)\n",
orig_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;
}
tmpstr = sss_get_cased_name(tmp_ctx, orig_name, dom->case_sensitive);
if (tmpstr == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
("sss_get_cased_name failed, skipping\n"));
continue;
}
to_sized_string(&name, tmpstr);
tmpstr = ldb_msg_find_attr_as_string(msg, SYSDB_GECOS, NULL);
if (!tmpstr) {
to_sized_string(&gecos, "");
} else {
to_sized_string(&gecos, tmpstr);
}
tmpstr = get_homedir_override(tmp_ctx, msg, nctx, dom, name.str, uid);
if (!tmpstr) {
to_sized_string(&homedir, "/");
} else {
to_sized_string(&homedir, tmpstr);
}
tmpstr = get_shell_override(tmp_ctx, msg, nctx);
if (!tmpstr) {
to_sized_string(&shell, "");
} else {
to_sized_string(&shell, tmpstr);
}
rsize = 2 * sizeof(uint32_t) + name.len + gecos.len +
homedir.len + shell.len + pwfield.len;
if (add_domain) rsize += delim + dom_len;
ret = sss_packet_grow(packet, rsize);
if (ret != EOK) {
num = 0;
goto done;
}
sss_packet_get_body(packet, &body, &blen);
SAFEALIGN_SET_UINT32(&body[rp], uid, &rp);
SAFEALIGN_SET_UINT32(&body[rp], gid, &rp);
if (add_domain) {
ret = snprintf((char *)&body[rp],
name.len + delim + dom_len,
namefmt, name.str, domain);
if (ret >= (name.len + delim + dom_len)) {
/* need more space, got creative with the print format ? */
t = ret - (name.len + delim + dom_len) + 1;
ret = sss_packet_grow(packet, t);
if (ret != EOK) {
num = 0;
goto done;
}
delim += t;
sss_packet_get_body(packet, &body, &blen);
/* retry */
ret = snprintf((char *)&body[rp],
name.len + delim + dom_len,
namefmt, name.str, domain);
}
if (ret != name.len + delim + dom_len - 1) {
DEBUG(1, ("Failed to generate a fully qualified name for user "
"[%s] in [%s]! Skipping user.\n", name.str, domain));
continue;
}
} else {
memcpy(&body[rp], name.str, name.len);
}
to_sized_string(&fullname, (const char *)&body[rp]);
rp += fullname.len;
memcpy(&body[rp], pwfield.str, pwfield.len);
rp += pwfield.len;
memcpy(&body[rp], gecos.str, gecos.len);
rp += gecos.len;
memcpy(&body[rp], homedir.str, homedir.len);
rp += homedir.len;
memcpy(&body[rp], shell.str, shell.len);
rp += shell.len;
num++;
if (pw_mmap_cache && nctx->pwd_mc_ctx) {
ret = sss_mmap_cache_pw_store(nctx->pwd_mc_ctx,
&fullname, &pwfield,
uid, gid,
&gecos, &homedir, &shell);
if (ret != EOK && ret != ENOMEM) {
DEBUG(1, ("Failed to store user %s(%s) in mmap cache!",
name.str, domain));
}
}
}
talloc_zfree(tmp_ctx);
done:
*count = i;
/* 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 int nss_cmd_getpw_send_reply(struct nss_dom_ctx *dctx, bool filter)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct cli_ctx *cctx = cmdctx->cctx;
struct nss_ctx *nctx;
int ret;
int i;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
ret = sss_packet_new(cctx->creq, 0,
sss_packet_get_cmd(cctx->creq->in),
&cctx->creq->out);
if (ret != EOK) {
return EFAULT;
}
i = dctx->res->count;
ret = fill_pwent(cctx->creq->out,
dctx->domain,
nctx, filter, true,
dctx->res->msgs, &i);
if (ret) {
return ret;
}
sss_packet_set_error(cctx->creq->out, EOK);
sss_cmd_done(cctx, cmdctx);
return EOK;
}
static void nsssrv_dp_send_acct_req_done(struct tevent_req *req);
/* FIXME: do not check res->count, but get in a msgs and check in parent */
/* FIXME: do not sss_cmd_done, but return error and let parent do it */
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,
void *pvt)
{
errno_t ret;
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct cli_ctx *cctx = cmdctx->cctx;
struct tevent_req *req = NULL;
struct dp_callback_ctx *cb_ctx = NULL;
uint64_t cacheExpire = 0;
/* when searching for a user or netgroup, more than one reply is a
* db error
*/
if ((req_type == SSS_DP_USER || req_type == SSS_DP_NETGR) &&
(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) {
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);
}
/* if we have any reply let's check cache validity */
ret = sss_cmd_check_cache(res->msgs[0], nctx->cache_refresh_percent,
cacheExpire);
if (ret == EOK) {
DEBUG(SSSDBG_TRACE_FUNC, ("Cached entry is valid, returning..\n"));
return EOK;
} else if (ret != EAGAIN && ret != ENOENT) {
DEBUG(SSSDBG_CRIT_FAILURE, ("Error checking cache: %d\n", ret));
goto error;
}
} else {
/* No replies */
ret = ENOENT;
}
/* EAGAIN (off band) or ENOENT (cache miss) -> check cache */
if (ret == EAGAIN) {
/* No callback required
* This was an out-of-band update. We'll return EOK
* so the calling function can return the cached entry
* immediately.
*/
DEBUG(SSSDBG_TRACE_FUNC,
("Performing midpoint cache update on [%s]\n", opt_name));
req = sss_dp_get_account_send(cctx, cctx->rctx, dctx->domain, true,
req_type, opt_name, opt_id, NULL);
if (!req) {
DEBUG(SSSDBG_CRIT_FAILURE,
("Out of memory sending out-of-band data provider "
"request\n"));
/* This is non-fatal, so we'll continue here */
} else {
DEBUG(SSSDBG_TRACE_FUNC, ("Updating cache out-of-band\n"));
}
/* We don't need to listen for a reply, so we will free the
* request here.
*/
talloc_zfree(req);
} 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;
/* keep around current data in case backend is offline */
if (res->count) {
dctx->res = talloc_steal(dctx, res);
}
req = sss_dp_get_account_send(cctx, cctx->rctx, dctx->domain, true,
req_type, opt_name, opt_id, NULL);
if (!req) {
DEBUG(SSSDBG_CRIT_FAILURE,
("Out of memory sending data provider request\n"));
ret = ENOMEM;
goto error;
}
cb_ctx = talloc_zero(dctx, struct dp_callback_ctx);
if(!cb_ctx) {
talloc_zfree(req);
ret = ENOMEM;
goto error;
}
cb_ctx->callback = callback;
cb_ctx->ptr = pvt;
cb_ctx->cctx = dctx->cmdctx->cctx;
cb_ctx->mem_ctx = dctx;
tevent_req_set_callback(req, nsssrv_dp_send_acct_req_done, cb_ctx);
return EAGAIN;
}
return EOK;
error:
ret = nss_cmd_send_error(cmdctx, ret);
if (ret != EOK) {
NSS_CMD_FATAL_ERROR_CODE(cctx, ret);
}
sss_cmd_done(cctx, cmdctx);
return EOK;
}
static void nsssrv_dp_send_acct_req_done(struct tevent_req *req)
{
struct dp_callback_ctx *cb_ctx =
tevent_req_callback_data(req, struct dp_callback_ctx);
errno_t ret;
dbus_uint16_t err_maj;
dbus_uint32_t err_min;
char *err_msg;
ret = sss_dp_get_account_recv(cb_ctx->mem_ctx, req,
&err_maj, &err_min,
&err_msg);
talloc_zfree(req);
if (ret != EOK) {
NSS_CMD_FATAL_ERROR(cb_ctx->cctx);
}
cb_ctx->callback(err_maj, err_min, err_msg, cb_ctx->ptr);
}
static void nss_cmd_getpwnam_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr);
/* search for a user.
* Returns:
* ENOENT, if user is definitely not found
* EAGAIN, if user is beeing fetched from backend via async operations
* EOK, if found
* anything else on a fatal error
*/
static int nss_cmd_getpwnam_search(struct nss_dom_ctx *dctx)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct sss_domain_info *dom = dctx->domain;
struct cli_ctx *cctx = cmdctx->cctx;
char *name = NULL;
struct sysdb_ctx *sysdb;
struct nss_ctx *nctx;
int ret;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
while (dom) {
/* if it is a domainless search, skip domains that require fully
* qualified names instead */
while (dom && cmdctx->check_next && dom->fqnames) {
dom = dom->next;
}
if (!dom) break;
if (dom != dctx->domain) {
/* make sure we reset the check_provider flag when we check
* a new domain */
dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
}
/* make sure to update the dctx if we changed domain */
dctx->domain = dom;
talloc_free(name);
name = sss_get_cased_name(dctx, cmdctx->name, dom->case_sensitive);
if (!name) return ENOMEM;
/* verify this user has not yet been negatively cached,
* or has been permanently filtered */
ret = sss_ncache_check_user(nctx->ncache, nctx->neg_timeout,
dom, name);
/* if neg cached, return we didn't find it */
if (ret == EEXIST) {
DEBUG(2, ("User [%s] does not exist in [%s]! (negative cache)\n",
name, dom->name));
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = dom->next;
continue;
}
/* There are no further domains or this was a
* fully-qualified user request.
*/
return ENOENT;
}
DEBUG(4, ("Requesting info for [%s@%s]\n", name, dom->name));
sysdb = dom->sysdb;
if (sysdb == NULL) {
DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
return EIO;
}
ret = sysdb_getpwnam(cmdctx, sysdb, name, &dctx->res);
if (ret != EOK) {
DEBUG(1, ("Failed to make request to our cache!\n"));
return EIO;
}
if (dctx->res->count > 1) {
DEBUG(0, ("getpwnam call returned more than one result !?!\n"));
return ENOENT;
}
if (dctx->res->count == 0 && !dctx->check_provider) {
/* set negative cache only if not result of cache check */
ret = sss_ncache_set_user(nctx->ncache, false, dom, name);
if (ret != EOK) {
return ret;
}
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = dom->next;
if (dom) continue;
}
DEBUG(2, ("No results for getpwnam call\n"));
return ENOENT;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
ret = check_cache(dctx, nctx, dctx->res,
SSS_DP_USER, name, 0,
nss_cmd_getpwnam_dp_callback,
dctx);
if (ret != EOK) {
/* Anything but EOK means we should reenter the mainloop
* because we may be refreshing the cache
*/
return ret;
}
}
/* One result found */
DEBUG(6, ("Returning info for user [%s@%s]\n", name, dom->name));
return EOK;
}
DEBUG(SSSDBG_MINOR_FAILURE,
("No matching domain found for [%s], fail!\n", cmdctx->name));
return ENOENT;
}
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;
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 && dctx->res->count == 1) {
ret = nss_cmd_getpw_send_reply(dctx, false);
goto done;
}
/* no previous results, just loop to next domain if possible */
if (dctx->domain->next && cmdctx->check_next) {
dctx->domain = dctx->domain->next;
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
} else {
/* nothing available */
ret = ENOENT;
goto done;
}
}
/* ok the backend returned, search to see if we have updated results */
ret = nss_cmd_getpwnam_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getpw_send_reply(dctx, false);
}
done:
ret = nss_cmd_done(cmdctx, ret);
if (ret) {
NSS_CMD_FATAL_ERROR(cctx);
}
}
static void nss_cmd_getpwnam_cb(struct tevent_req *req);
static int nss_cmd_getpwnam(struct cli_ctx *cctx)
{
struct tevent_req *req;
struct nss_cmd_ctx *cmdctx;
struct nss_dom_ctx *dctx;
const char *rawname;
char *domname;
uint8_t *body;
size_t blen;
int ret;
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;
}
/* If the body isn't valid UTF-8, fail */
if (!sss_utf8_check(body, blen -1)) {
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:"<ALL>"));
if (domname) {
dctx->domain = responder_get_domain(dctx, cctx->rctx, domname);
if (!dctx->domain) {
req = sss_dp_get_domains_send(cctx->rctx, cctx->rctx, true, domname);
if (req == NULL) {
ret = ENOMEM;
} else {
dctx->domname = domname;
tevent_req_set_callback(req, nss_cmd_getpwnam_cb, dctx);
ret = EAGAIN;
}
goto done;
}
} else {
/* this is a multidomain search */
dctx->domain = cctx->rctx->domains;
cmdctx->check_next = true;
if (cctx->rctx->get_domains_last_call.tv_sec == 0) {
req = sss_dp_get_domains_send(cctx->rctx, cctx->rctx, false, NULL);
if (req == NULL) {
ret = ENOMEM;
} else {
tevent_req_set_callback(req, nss_cmd_getpwnam_cb, dctx);
ret = EAGAIN;
}
goto done;
}
}
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
/* ok, find it ! */
ret = nss_cmd_getpwnam_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getpw_send_reply(dctx, false);
}
done:
return nss_cmd_done(cmdctx, ret);
}
static void nss_cmd_getpwnam_cb(struct tevent_req *req)
{
struct nss_dom_ctx *dctx = tevent_req_callback_data(req, struct nss_dom_ctx);
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct cli_ctx *cctx = cmdctx->cctx;
errno_t ret;
ret = sss_dp_get_domains_recv(req);
talloc_free(req);
if (ret != EOK) {
goto done;
}
if (dctx->domname) {
dctx->domain = responder_get_domain(dctx, cctx->rctx, dctx->domname);
if (dctx->domain == NULL) {
ret = ENOENT;
goto done;
}
}
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
/* ok, find it ! */
ret = nss_cmd_getpwnam_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getpw_send_reply(dctx, false);
}
done:
nss_cmd_done(cmdctx, ret);
}
static void nss_cmd_getpwuid_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr);
/* search for a uid.
* Returns:
* ENOENT, if uid is definitely not found
* EAGAIN, if uid is beeing fetched from backend via async operations
* EOK, if found
* anything else on a fatal error
*/
struct sss_domain_info *get_next_dom_or_subdom(struct sss_domain_info *dom)
{
/* Note that we don't know if the dom is a domain or a subdomain,
* therefore:
*
* If it is a subdomain and it doesn't have any siblings (subdomains
* of the same primary domain), return next primary domain
*/
if (dom->next == NULL && dom->parent != NULL) {
return dom->parent->next;
}
/* If it's primary domain, the next returned should be its first
* subdomain */
if (dom->subdomains != NULL) {
return dom->subdomains[0];
}
/* Any other scenario */
return dom->next;
}
static int nss_cmd_getpwuid_search(struct nss_dom_ctx *dctx)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct sss_domain_info *dom = dctx->domain;
struct cli_ctx *cctx = cmdctx->cctx;
struct sysdb_ctx *sysdb;
struct nss_ctx *nctx;
int ret;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
while (dom) {
/* 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));
if (cmdctx->check_next) {
dom = get_next_dom_or_subdom(dom);
continue;
}
return ENOENT;
}
if (dom != dctx->domain) {
/* make sure we reset the check_provider flag when we check
* a new domain */
dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
}
/* make sure to update the dctx if we changed domain */
dctx->domain = dom;
DEBUG(4, ("Requesting info for [%d@%s]\n", cmdctx->id, dom->name));
sysdb = dom->sysdb;
if (sysdb == NULL) {
DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
return EIO;
}
ret = sysdb_getpwuid(cmdctx, sysdb, cmdctx->id, &dctx->res);
if (ret != EOK) {
DEBUG(1, ("Failed to make request to our cache!\n"));
return EIO;
}
if (dctx->res->count > 1) {
DEBUG(0, ("getpwuid call returned more than one result !?!\n"));
return ENOENT;
}
if (dctx->res->count == 0 && !dctx->check_provider) {
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = get_next_dom_or_subdom(dom);
continue;
}
DEBUG(2, ("No results for getpwuid call\n"));
/* set negative cache only if not result of cache check */
ret = sss_ncache_set_uid(nctx->ncache, false, cmdctx->id);
if (ret != EOK) {
return ret;
}
return ENOENT;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
ret = check_cache(dctx, nctx, dctx->res,
SSS_DP_USER, NULL, cmdctx->id,
nss_cmd_getpwuid_dp_callback,
dctx);
if (ret != EOK) {
/* Anything but EOK means we should reenter the mainloop
* because we may be refreshing the cache
*/
return ret;
}
}
/* One result found */
DEBUG(6, ("Returning info for uid [%d@%s]\n", cmdctx->id, dom->name));
return EOK;
}
DEBUG(2, ("No matching domain found for [%d], fail!\n", cmdctx->id));
return ENOENT;
}
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;
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 && dctx->res->count == 1) {
ret = nss_cmd_getpw_send_reply(dctx, true);
goto done;
}
/* no previous results, just loop to next domain if possible */
if (dctx->domain->next && cmdctx->check_next) {
dctx->domain = dctx->domain->next;
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
} else {
/* nothing available */
ret = ENOENT;
goto done;
}
}
/* ok the backend returned, search to see if we have updated results */
ret = nss_cmd_getpwuid_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getpw_send_reply(dctx, true);
}
done:
ret = nss_cmd_done(cmdctx, ret);
if (ret) {
NSS_CMD_FATAL_ERROR(cctx);
}
}
static int nss_cmd_getpwuid(struct cli_ctx *cctx)
{
struct nss_cmd_ctx *cmdctx;
struct nss_dom_ctx *dctx;
struct nss_ctx *nctx;
uint8_t *body;
size_t blen;
int ret;
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);
ret = sss_ncache_check_uid(nctx->ncache, nctx->neg_timeout, cmdctx->id);
if (ret == EEXIST) {
DEBUG(3, ("Uid [%lu] does not exist! (negative cache)\n",
(unsigned long)cmdctx->id));
ret = ENOENT;
goto done;
}
/* uid searches are always multidomain */
dctx->domain = cctx->rctx->domains;
cmdctx->check_next = true;
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
/* ok, find it ! */
ret = nss_cmd_getpwuid_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getpw_send_reply(dctx, true);
}
done:
return nss_cmd_done(cmdctx, ret);
}
/* 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
*
* Alternatively:
* - use a smarter search mechanism that keeps track of the
* last user searched and return the next X users doing
* an alphabetic sort and starting from the user following
* the last returned user.
*/
static int nss_cmd_getpwent_immediate(struct nss_cmd_ctx *cmdctx);
struct tevent_req * nss_cmd_setpwent_send(TALLOC_CTX *mem_ctx,
struct cli_ctx *client);
static void nss_cmd_setpwent_done(struct tevent_req *req);
static int nss_cmd_setpwent(struct cli_ctx *cctx)
{
struct nss_cmd_ctx *cmdctx;
struct tevent_req *req;
errno_t ret = EOK;
cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
if (!cmdctx) {
return ENOMEM;
}
cmdctx->cctx = cctx;
req = nss_cmd_setpwent_send(cmdctx, cctx);
if (!req) {
DEBUG(0, ("Fatal error calling nss_cmd_setpwent_send\n"));
ret = EIO;
goto done;
}
tevent_req_set_callback(req, nss_cmd_setpwent_done, cmdctx);
done:
return nss_cmd_done(cmdctx, ret);
}
static errno_t nss_cmd_setpwent_step(struct setent_step_ctx *step_ctx);
struct tevent_req *nss_cmd_setpwent_send(TALLOC_CTX *mem_ctx,
struct cli_ctx *client)
{
errno_t ret;
struct nss_ctx *nctx;
struct tevent_req *req;
struct setent_ctx *state;
struct sss_domain_info *dom;
struct setent_step_ctx *step_ctx;
DEBUG(4, ("Received setpwent request\n"));
nctx = talloc_get_type(client->rctx->pvt_ctx, struct nss_ctx);
/* Reset the read pointers */
client->pwent_dom_idx = 0;
client->pwent_cur = 0;
req = tevent_req_create(mem_ctx, &state, struct setent_ctx);
if (!req) {
DEBUG(0, ("Could not create tevent request for setpwent\n"));
return NULL;
}
state->nctx = nctx;
state->client = client;
state->dctx = talloc_zero(state, struct nss_dom_ctx);
if (!state->dctx) {
ret = ENOMEM;
goto error;
}
/* check if enumeration is enabled in any domain */
for (dom = client->rctx->domains; dom; dom = dom->next) {
if (dom->enumerate != 0) break;
}
state->dctx->domain = dom;
if (state->dctx->domain == NULL) {
DEBUG(2, ("Enumeration disabled on all domains!\n"));
ret = ENOENT;
goto error;
}
state->dctx->check_provider =
NEED_CHECK_PROVIDER(state->dctx->domain->provider);
/* Is the result context already available */
if (state->nctx->pctx) {
if (state->nctx->pctx->ready) {
/* All of the necessary data is in place
* We can return now, getpwent requests will work at this point
*/
tevent_req_done(req);
tevent_req_post(req, state->nctx->rctx->ev);
}
else {
/* Object is still being constructed
* Register for notification when it's
* ready.
*/
ret = nss_setent_add_ref(state, state->nctx->pctx, req);
if (ret != EOK) {
talloc_free(req);
return NULL;
}
}
return req;
}
/* Create a new result context
* We are creating it on the nss_ctx so that it doesn't
* go away if the original request does. We will delete
* it when the refcount goes to zero;
*/
state->nctx->pctx = talloc_zero(nctx, struct getent_ctx);
if (!state->nctx->pctx) {
ret = ENOMEM;
goto error;
}
state->getent_ctx = nctx->pctx;
/* Add a callback reference for ourselves */
ret = nss_setent_add_ref(state, state->nctx->pctx, req);
if (ret) goto error;
/* ok, start the searches */
step_ctx = talloc_zero(state->getent_ctx, struct setent_step_ctx);
if (!step_ctx) {
ret = ENOMEM;
goto error;
}
/* Steal the dom_ctx onto the step_ctx so it doesn't go out of scope if
* this request is canceled while other requests are in-progress.
*/
step_ctx->dctx = talloc_steal(step_ctx, state->dctx);
step_ctx->nctx = state->nctx;
step_ctx->getent_ctx = state->getent_ctx;
step_ctx->rctx = client->rctx;
step_ctx->cctx = client;
step_ctx->returned_to_mainloop = false;
ret = nss_cmd_setpwent_step(step_ctx);
if (ret != EOK && ret != EAGAIN) goto error;
if (ret == EOK) {
tevent_req_post(req, client->rctx->ev);
}
return req;
error:
tevent_req_error(req, ret);
tevent_req_post(req, client->rctx->ev);
return req;
}
static void nss_cmd_setpwent_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr);
static void setpwent_result_timeout(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
void *pvt);
/* nss_cmd_setpwent_step returns
* EOK if everything is done and the request needs to be posted explicitly
* EAGAIN if the caller can safely return to the main loop
*/
static errno_t nss_cmd_setpwent_step(struct setent_step_ctx *step_ctx)
{
errno_t ret;
struct sss_domain_info *dom = step_ctx->dctx->domain;
struct resp_ctx *rctx = step_ctx->rctx;
struct nss_dom_ctx *dctx = step_ctx->dctx;
struct getent_ctx *pctx = step_ctx->getent_ctx;
struct nss_ctx *nctx = step_ctx->nctx;
struct sysdb_ctx *sysdb;
struct ldb_result *res;
struct timeval tv;
struct tevent_timer *te;
struct tevent_req *dpreq;
struct dp_callback_ctx *cb_ctx;
while (dom) {
while (dom && dom->enumerate == 0) {
dom = dom->next;
}
if (!dom) break;
if (dom != dctx->domain) {
/* make sure we reset the check_provider flag when we check
* a new domain */
dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
}
/* make sure to update the dctx if we changed domain */
dctx->domain = dom;
DEBUG(6, ("Requesting info for domain [%s]\n", dom->name));
sysdb = dom->sysdb;
if (sysdb == NULL) {
DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
return EIO;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
step_ctx->returned_to_mainloop = true;
/* Only do this once per provider */
dctx->check_provider = false;
dpreq = sss_dp_get_account_send(step_ctx, rctx, dctx->domain, true,
SSS_DP_USER, NULL, 0, NULL);
if (!dpreq) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Enum Cache refresh for domain [%s] failed."
" Trying to return what we have in cache!\n",
dom->name));
} else {
cb_ctx = talloc_zero(step_ctx, struct dp_callback_ctx);
if(!cb_ctx) {
talloc_zfree(dpreq);
return ENOMEM;
}
cb_ctx->callback = nss_cmd_setpwent_dp_callback;
cb_ctx->ptr = step_ctx;
cb_ctx->cctx = step_ctx->cctx;
cb_ctx->mem_ctx = step_ctx;
tevent_req_set_callback(dpreq, nsssrv_dp_send_acct_req_done, cb_ctx);
return EAGAIN;
}
}
ret = sysdb_enumpwent(dctx, sysdb, &res);
if (ret != EOK) {
DEBUG(1, ("Enum from cache failed, skipping domain [%s]\n",
dom->name));
dom = dom->next;
continue;
}
if (res->count == 0) {
DEBUG(4, ("Domain [%s] has no users, skipping.\n", dom->name));
dom = dom->next;
continue;
}
nctx->pctx->doms = talloc_realloc(pctx, pctx->doms,
struct dom_ctx, pctx->num +1);
if (!pctx->doms) {
talloc_free(pctx);
nctx->pctx = NULL;
return ENOMEM;
}
nctx->pctx->doms[pctx->num].domain = dctx->domain;
nctx->pctx->doms[pctx->num].res = talloc_steal(pctx->doms, res);
nctx->pctx->num++;
/* do not reply until all domain searches are done */
dom = dom->next;
}
/* We've finished all our lookups
* The result object is now safe to read.
*/
nctx->pctx->ready = true;
/* Set up a lifetime timer for this result object
* We don't want this result object to outlive the
* enum cache refresh timeout
*/
tv = tevent_timeval_current_ofs(nctx->enum_cache_timeout, 0);
te = tevent_add_timer(rctx->ev, nctx->pctx, tv,
setpwent_result_timeout, nctx);
if (!te) {
DEBUG(0, ("Could not set up life timer for setpwent result object. "
"Entries may become stale.\n"));
}
/* Notify the waiting clients */
nss_setent_notify_done(nctx->pctx);
if (step_ctx->returned_to_mainloop) {
return EAGAIN;
} else {
return EOK;
}
}
static void setpwent_result_timeout(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
void *pvt)
{
struct nss_ctx *nctx = talloc_get_type(pvt, struct nss_ctx);
DEBUG(1, ("setpwent result object has expired. Cleaning up.\n"));
/* Free the passwd enumeration context.
* If additional getpwent requests come in, they will invoke
* an implicit setpwent and refresh the result object.
*/
talloc_zfree(nctx->pctx);
}
static void nss_cmd_setpwent_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr)
{
struct setent_step_ctx *step_ctx =
talloc_get_type(ptr, struct setent_step_ctx);
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 = nss_cmd_setpwent_step(step_ctx);
if (ret != EOK && ret != EAGAIN) {
/* Notify any waiting processes of failure */
nss_setent_notify_error(step_ctx->nctx->pctx, ret);
}
}
static errno_t nss_cmd_setpwent_recv(struct tevent_req *req)
{
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}
static void nss_cmd_setpwent_done(struct tevent_req *req)
{
errno_t ret;
struct nss_cmd_ctx *cmdctx =
tevent_req_callback_data(req, struct nss_cmd_ctx);
ret = nss_cmd_setpwent_recv(req);
talloc_zfree(req);
if (ret == EOK || ret == ENOENT) {
/* Either we succeeded or no domains were eligible */
ret = sss_packet_new(cmdctx->cctx->creq, 0,
sss_packet_get_cmd(cmdctx->cctx->creq->in),
&cmdctx->cctx->creq->out);
if (ret == EOK) {
sss_cmd_done(cmdctx->cctx, cmdctx);
return;
}
}
/* Something bad happened */
nss_cmd_done(cmdctx, ret);
}
static void nss_cmd_implicit_setpwent_done(struct tevent_req *req);
static int nss_cmd_getpwent(struct cli_ctx *cctx)
{
struct nss_ctx *nctx;
struct nss_cmd_ctx *cmdctx;
struct tevent_req *req;
DEBUG(4, ("Requesting info for all accounts\n"));
cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
if (!cmdctx) {
return ENOMEM;
}
cmdctx->cctx = cctx;
/* Save the current index and cursor locations
* If we end up calling setpwent implicitly, because the response object
* expired and has to be recreated, we want to resume from the same
* location.
*/
cmdctx->saved_dom_idx = cctx->pwent_dom_idx;
cmdctx->saved_cur = cctx->pwent_cur;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
if(!nctx->pctx || !nctx->pctx->ready) {
/* Make sure we invoke setpwent if it hasn't been run or is still
* processing from another client
*/
req = nss_cmd_setpwent_send(cctx, cctx);
if (!req) {
return EIO;
}
tevent_req_set_callback(req, nss_cmd_implicit_setpwent_done, cmdctx);
return EOK;
}
return nss_cmd_getpwent_immediate(cmdctx);
}
static int nss_cmd_retpwent(struct cli_ctx *cctx, int num);
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_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 = ENOENT;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
if (!nctx->pctx) goto none;
pctx = nctx->pctx;
while (ret == ENOENT) {
if (cctx->pwent_dom_idx >= pctx->num) break;
pdom = &pctx->doms[cctx->pwent_dom_idx];
n = pdom->res->count - cctx->pwent_cur;
if (n <= 0 && (cctx->pwent_dom_idx+1 < pctx->num)) {
cctx->pwent_dom_idx++;
pdom = &pctx->doms[cctx->pwent_dom_idx];
n = pdom->res->count;
cctx->pwent_cur = 0;
}
if (!n) break;
if (n > num) n = num;
msgs = &(pdom->res->msgs[cctx->pwent_cur]);
ret = fill_pwent(cctx->creq->out, pdom->domain, nctx,
true, false, msgs, &n);
cctx->pwent_cur += n;
}
none:
if (ret == ENOENT) {
ret = sss_cmd_empty_packet(cctx->creq->out);
}
return ret;
}
static void nss_cmd_implicit_setpwent_done(struct tevent_req *req)
{
errno_t ret;
struct nss_cmd_ctx *cmdctx =
tevent_req_callback_data(req, struct nss_cmd_ctx);
ret = nss_cmd_setpwent_recv(req);
talloc_zfree(req);
/* ENOENT is acceptable, as it just means that there were no entries
* to be returned. This will be handled gracefully in nss_cmd_retpwent
* later.
*/
if (ret != EOK && ret != ENOENT) {
DEBUG(0, ("Implicit setpwent failed with unexpected error [%d][%s]\n",
ret, strerror(ret)));
NSS_CMD_FATAL_ERROR(cmdctx);
}
/* Restore the saved index and cursor locations */
cmdctx->cctx->pwent_dom_idx = cmdctx->saved_dom_idx;
cmdctx->cctx->pwent_cur = cmdctx->saved_cur;
ret = nss_cmd_getpwent_immediate(cmdctx);
if (ret != EOK) {
DEBUG(0, ("Immediate retrieval failed with unexpected error "
"[%d][%s]\n", ret, strerror(ret)));
NSS_CMD_FATAL_ERROR(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 (ret != EOK) {
return ret;
}
if (nctx->pctx == NULL) goto done;
/* Reset the indices so that subsequent requests start at zero */
cctx->pwent_dom_idx = 0;
cctx->pwent_cur = 0;
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_members(struct sss_packet *packet,
struct sss_domain_info *dom,
struct nss_ctx *nctx,
struct ldb_message_element *el,
size_t *_rzero,
size_t *_rsize,
int *_memnum)
{
int i, ret = EOK;
int memnum = *_memnum;
size_t rzero= *_rzero;
size_t rsize = *_rsize;
char *tmpstr;
struct sized_string name;
const char *namefmt = nctx->rctx->names->fq_fmt;
TALLOC_CTX *tmp_ctx = NULL;
size_t delim;
size_t dom_len;
uint8_t *body;
size_t blen;
const char *domain = dom->name;
bool add_domain = dom->fqnames;
if (add_domain) {
delim = 1;
dom_len = strlen(domain);
} else {
delim = 0;
dom_len = 0;
}
tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {
return ENOMEM;
}
sss_packet_get_body(packet, &body, &blen);
for (i = 0; i < el->num_values; i++) {
tmpstr = sss_get_cased_name(tmp_ctx, (char *)el->values[i].data,
dom->case_sensitive);
if (tmpstr == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
("sss_get_cased_name failed, skipping\n"));
continue;
}
if (nctx->filter_users_in_groups) {
ret = sss_ncache_check_user(nctx->ncache,
nctx->neg_timeout,
dom, tmpstr);
if (ret == EEXIST) {
DEBUG(SSSDBG_TRACE_FUNC, ("Group [%s] member [%s@%s] filtered out!"
" (negative cache)\n",
(char *)&body[rzero+STRS_ROFFSET],
tmpstr, domain));
continue;
}
}
to_sized_string(&name, tmpstr);
if (add_domain) {
ret = sss_packet_grow(packet, name.len + delim + dom_len);
} else {
ret = sss_packet_grow(packet, name.len);
}
if (ret != EOK) {
goto done;
}
sss_packet_get_body(packet, &body, &blen);
if (add_domain) {
ret = snprintf((char *)&body[rzero + rsize],
name.len + delim + dom_len,
namefmt, name.str, domain);
if (ret >= (name.len + delim + dom_len)) {
/* need more space,
* got creative with the print format ? */
int t = ret - name.len + delim + dom_len + 1;
ret = sss_packet_grow(packet, t);
if (ret != EOK) {
goto done;
}
sss_packet_get_body(packet, &body, &blen);
delim += t;
/* retry */
ret = snprintf((char *)&body[rzero + rsize],
name.len + delim + dom_len,
namefmt, name.str, domain);
}
if (ret != name.len + delim + dom_len - 1) {
DEBUG(SSSDBG_OP_FAILURE, ("Failed to generate a fully qualified name"
" for member [%s@%s] of group [%s]!"
" Skipping\n", name.str, domain,
(char *)&body[rzero+STRS_ROFFSET]));
/* reclaim space */
ret = sss_packet_shrink(packet, name.len + delim + dom_len);
if (ret != EOK) {
goto done;
}
continue;
}
} else {
memcpy(&body[rzero + rsize], name.str, name.len);
}
if (add_domain) {
rsize += name.len + delim + dom_len;
} else {
rsize += name.len;
}
memnum++;
}
done:
*_memnum = memnum;
*_rzero = rzero;
*_rsize = rsize;
talloc_zfree(tmp_ctx);
return ret;
}
static int fill_grent(struct sss_packet *packet,
struct sss_domain_info *dom,
struct nss_ctx *nctx,
bool filter_groups, bool gr_mmap_cache,
struct ldb_message **msgs,
int *count)
{
struct ldb_message *msg;
struct ldb_message_element *el;
uint8_t *body;
size_t blen;
uint32_t gid;
const char *tmpstr;
const char *orig_name;
struct sized_string name;
struct sized_string pwfield;
struct sized_string fullname;
size_t delim;
size_t dom_len;
int i = 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;
TALLOC_CTX *tmp_ctx = NULL;
if (add_domain) {
delim = 1;
dom_len = strlen(domain);
} else {
delim = 0;
dom_len = 0;
}
to_sized_string(&pwfield, nctx->pwfield);
num = 0;
/* 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++) {
talloc_zfree(tmp_ctx);
tmp_ctx = talloc_new(NULL);
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;
}
/* new result starts at end of previous result */
rzero += rsize;
rsize = 0;
/* find group name/gid */
orig_name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0);
if (!orig_name || !gid) {
DEBUG(2, ("Incomplete group object for %s[%llu]! Skipping\n",
orig_name?orig_name:"<NULL>", (unsigned long long int)gid));
continue;
}
if (filter_groups) {
ret = sss_ncache_check_group(nctx->ncache,
nctx->neg_timeout, dom, orig_name);
if (ret == EEXIST) {
DEBUG(4, ("Group [%s@%s] filtered out! (negative cache)\n",
orig_name, domain));
continue;
}
}
tmpstr = sss_get_cased_name(tmp_ctx, orig_name, dom->case_sensitive);
if (tmpstr == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
("sss_get_cased_name failed, skipping\n"));
continue;
}
to_sized_string(&name, tmpstr);
/* fill in gid and name and set pointer for number of members */
rsize = STRS_ROFFSET + name.len + pwfield.len; /* name\0x\0 */
if (add_domain) rsize += delim + dom_len;
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 */
SAFEALIGN_SET_UINT32(&body[rzero+GID_ROFFSET], gid, NULL);
/* 4-7: 32bit unsigned number of members */
SAFEALIGN_SET_UINT32(&body[rzero+MNUM_ROFFSET], 0, NULL);
/* 8-X: sequence of strings (name, passwd, mem..) */
if (add_domain) {
ret = snprintf((char *)&body[rzero+STRS_ROFFSET],
name.len + delim + dom_len,
namefmt, name.str, domain);
if (ret >= (name.len + delim + dom_len)) {
/* need more space, got creative with the print format ? */
int t = ret - (name.len + delim + dom_len) + 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;
/* retry */
ret = snprintf((char *)&body[rzero+STRS_ROFFSET],
name.len + delim + dom_len,
namefmt, name.str, domain);
}
if (ret != name.len + delim + dom_len - 1) {
DEBUG(1, ("Failed to generate a fully qualified name for"
" group [%s] in [%s]! Skipping\n", name.str, 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.str, name.len);
}
to_sized_string(&fullname, (const char *)&body[rzero+STRS_ROFFSET]);
/* group passwd field */
memcpy(&body[rzero+STRS_ROFFSET + fullname.len],
pwfield.str, pwfield.len);
memnum = 0;
el = ldb_msg_find_element(msg, SYSDB_MEMBERUID);
if (el) {
ret = fill_members(packet, dom, nctx, el, &rzero, &rsize, &memnum);
if (ret != EOK) {
num = 0;
goto done;
}
sss_packet_get_body(packet, &body, &blen);
}
el = ldb_msg_find_element(msg, SYSDB_GHOST);
if (el) {
ret = fill_members(packet, dom, nctx, el, &rzero, &rsize, &memnum);
if (ret != EOK) {
num = 0;
goto done;
}
sss_packet_get_body(packet, &body, &blen);
}
if (memnum) {
/* set num of members */
SAFEALIGN_SET_UINT32(&body[rzero+MNUM_ROFFSET], memnum, NULL);
}
num++;
if (gr_mmap_cache && nctx->grp_mc_ctx) {
/* body was reallocated, so fullname might be pointing to
* where body used to be, not where it is */
to_sized_string(&fullname, (const char *)&body[rzero+STRS_ROFFSET]);
ret = sss_mmap_cache_gr_store(nctx->grp_mc_ctx,
&fullname, &pwfield, gid, memnum,
(char *)&body[rzero] + STRS_ROFFSET +
fullname.len + pwfield.len,
rsize - STRS_ROFFSET -
fullname.len - pwfield.len);
if (ret != EOK && ret != ENOMEM) {
DEBUG(SSSDBG_OP_FAILURE,
("Failed to store group %s(%s) in mmap cache!",
name.str, domain));
}
}
continue;
}
talloc_zfree(tmp_ctx);
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 int nss_cmd_getgr_send_reply(struct nss_dom_ctx *dctx, bool filter)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct cli_ctx *cctx = cmdctx->cctx;
struct nss_ctx *nctx;
int ret;
int i;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
ret = sss_packet_new(cctx->creq, 0,
sss_packet_get_cmd(cctx->creq->in),
&cctx->creq->out);
if (ret != EOK) {
return EFAULT;
}
i = dctx->res->count;
ret = fill_grent(cctx->creq->out,
dctx->domain,
nctx, filter, true,
dctx->res->msgs, &i);
if (ret) {
return ret;
}
sss_packet_set_error(cctx->creq->out, EOK);
sss_cmd_done(cctx, cmdctx);
return EOK;
}
static void nss_cmd_getgrnam_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr);
/* search for a group.
* Returns:
* ENOENT, if group is definitely not found
* EAGAIN, if group is beeing fetched from backend via async operations
* EOK, if found
* anything else on a fatal error
*/
static int nss_cmd_getgrnam_search(struct nss_dom_ctx *dctx)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct sss_domain_info *dom = dctx->domain;
struct cli_ctx *cctx = cmdctx->cctx;
char *name = NULL;
struct sysdb_ctx *sysdb;
struct nss_ctx *nctx;
int ret;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
while (dom) {
/* if it is a domainless search, skip domains that require fully
* qualified names instead */
while (dom && cmdctx->check_next && dom->fqnames) {
dom = dom->next;
}
if (!dom) break;
if (dom != dctx->domain) {
/* make sure we reset the check_provider flag when we check
* a new domain */
dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
}
/* make sure to update the dctx if we changed domain */
dctx->domain = dom;
talloc_free(name);
name = sss_get_cased_name(dctx, cmdctx->name, dom->case_sensitive);
if (!name) return ENOMEM;
/* verify this group has not yet been negatively cached,
* or has been permanently filtered */
ret = sss_ncache_check_group(nctx->ncache, nctx->neg_timeout,
dom, name);
/* if neg cached, return we didn't find it */
if (ret == EEXIST) {
DEBUG(2, ("Group [%s] does not exist in [%s]! (negative cache)\n",
name, dom->name));
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = dom->next;
continue;
}
/* There are no further domains or this was a
* fully-qualified user request.
*/
return ENOENT;
}
DEBUG(4, ("Requesting info for [%s@%s]\n", name, dom->name));
sysdb = dom->sysdb;
if (sysdb == NULL) {
DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
return EIO;
}
ret = sysdb_getgrnam(cmdctx, sysdb, name, &dctx->res);
if (ret != EOK) {
DEBUG(1, ("Failed to make request to our cache!\n"));
return EIO;
}
if (dctx->res->count > 1) {
DEBUG(0, ("getgrnam call returned more than one result !?!\n"));
return ENOENT;
}
if (dctx->res->count == 0 && !dctx->check_provider) {
/* set negative cache only if not result of cache check */
ret = sss_ncache_set_group(nctx->ncache, false, dom, name);
if (ret != EOK) {
return ret;
}
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = dom->next;
if (dom) continue;
}
DEBUG(2, ("No results for getgrnam call\n"));
return ENOENT;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
ret = check_cache(dctx, nctx, dctx->res,
SSS_DP_GROUP, name, 0,
nss_cmd_getgrnam_dp_callback,
dctx);
if (ret != EOK) {
/* Anything but EOK means we should reenter the mainloop
* because we may be refreshing the cache
*/
return ret;
}
}
/* One result found */
DEBUG(6, ("Returning info for group [%s@%s]\n", name, dom->name));
return EOK;
}
DEBUG(SSSDBG_MINOR_FAILURE,
("No matching domain found for [%s], fail!\n", cmdctx->name));
return ENOENT;
}
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;
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 && dctx->res->count == 1) {
ret = nss_cmd_getgr_send_reply(dctx, false);
goto done;
}
/* no previous results, just loop to next domain if possible */
if (dctx->domain->next && cmdctx->check_next) {
dctx->domain = dctx->domain->next;
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
} else {
/* nothing available */
ret = ENOENT;
goto done;
}
}
/* ok the backend returned, search to see if we have updated results */
ret = nss_cmd_getgrnam_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getgr_send_reply(dctx, false);
}
done:
ret = nss_cmd_done(cmdctx, ret);
if (ret) {
NSS_CMD_FATAL_ERROR(cctx);
}
}
static void nss_cmd_getgrnam_cb(struct tevent_req *req);
static int nss_cmd_getgrnam(struct cli_ctx *cctx)
{
struct tevent_req *req;
struct nss_cmd_ctx *cmdctx;
struct nss_dom_ctx *dctx;
const char *rawname;
char *domname;
uint8_t *body;
size_t blen;
int ret;
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;
}
/* If the body isn't valid UTF-8, fail */
if (!sss_utf8_check(body, blen -1)) {
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:"<ALL>"));
if (domname) {
dctx->domain = responder_get_domain(dctx, cctx->rctx, domname);
if (!dctx->domain) {
req = sss_dp_get_domains_send(cctx->rctx, cctx->rctx, true, domname);
if (req == NULL) {
ret = ENOMEM;
} else {
dctx->domname = domname;
tevent_req_set_callback(req, nss_cmd_getgrnam_cb, dctx);
ret = EAGAIN;
}
goto done;
}
} else {
/* this is a multidomain search */
dctx->domain = cctx->rctx->domains;
cmdctx->check_next = true;
if (cctx->rctx->get_domains_last_call.tv_sec == 0) {
req = sss_dp_get_domains_send(cctx->rctx, cctx->rctx, false, NULL);
if (req == NULL) {
ret = ENOMEM;
} else {
tevent_req_set_callback(req, nss_cmd_getgrnam_cb, dctx);
ret = EAGAIN;
}
goto done;
}
}
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
/* ok, find it ! */
ret = nss_cmd_getgrnam_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getgr_send_reply(dctx, false);
}
done:
return nss_cmd_done(cmdctx, ret);
}
static void nss_cmd_getgrnam_cb(struct tevent_req *req)
{
struct nss_dom_ctx *dctx = tevent_req_callback_data(req, struct nss_dom_ctx);
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct cli_ctx *cctx = cmdctx->cctx;
errno_t ret;
ret = sss_dp_get_domains_recv(req);
talloc_free(req);
if (ret != EOK) {
goto done;
}
if (dctx->domname) {
dctx->domain = responder_get_domain(dctx, cctx->rctx, dctx->domname);
if (dctx->domain == NULL) {
ret = ENOENT;
goto done;
}
}
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
/* ok, find it ! */
ret = nss_cmd_getgrnam_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getgr_send_reply(dctx, false);
}
done:
nss_cmd_done(cmdctx, ret);
}
static void nss_cmd_getgrgid_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr);
/* search for a gid.
* Returns:
* ENOENT, if gid is definitely not found
* EAGAIN, if gid is beeing fetched from backend via async operations
* EOK, if found
* anything else on a fatal error
*/
static int nss_cmd_getgrgid_search(struct nss_dom_ctx *dctx)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct sss_domain_info *dom = dctx->domain;
struct cli_ctx *cctx = cmdctx->cctx;
struct sysdb_ctx *sysdb;
struct nss_ctx *nctx;
int ret;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
while (dom) {
/* check that the gid 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));
if (cmdctx->check_next) {
dom = get_next_dom_or_subdom(dom);
continue;
}
return ENOENT;
}
if (dom != dctx->domain) {
/* make sure we reset the check_provider flag when we check
* a new domain */
dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
}
/* make sure to update the dctx if we changed domain */
dctx->domain = dom;
DEBUG(4, ("Requesting info for [%d@%s]\n", cmdctx->id, dom->name));
sysdb = dom->sysdb;
if (sysdb == NULL) {
DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
return EIO;
}
ret = sysdb_getgrgid(cmdctx, sysdb, cmdctx->id, &dctx->res);
if (ret != EOK) {
DEBUG(1, ("Failed to make request to our cache!\n"));
return EIO;
}
if (dctx->res->count > 1) {
DEBUG(0, ("getgrgid call returned more than one result !?!\n"));
return ENOENT;
}
if (dctx->res->count == 0 && !dctx->check_provider) {
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = get_next_dom_or_subdom(dom);
continue;
}
DEBUG(2, ("No results for getgrgid call\n"));
/* set negative cache only if not result of cache check */
ret = sss_ncache_set_gid(nctx->ncache, false, cmdctx->id);
if (ret != EOK) {
return ret;
}
return ENOENT;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
ret = check_cache(dctx, nctx, dctx->res,
SSS_DP_GROUP, NULL, cmdctx->id,
nss_cmd_getgrgid_dp_callback,
dctx);
if (ret != EOK) {
/* Anything but EOK means we should reenter the mainloop
* because we may be refreshing the cache
*/
return ret;
}
}
/* One result found */
DEBUG(6, ("Returning info for gid [%d@%s]\n", cmdctx->id, dom->name));
return EOK;
}
DEBUG(2, ("No matching domain found for [%d], fail!\n", cmdctx->id));
return ENOENT;
}
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;
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 && dctx->res->count == 1) {
ret = nss_cmd_getgr_send_reply(dctx, true);
goto done;
}
/* no previous results, just loop to next domain if possible */
if (dctx->domain->next && cmdctx->check_next) {
dctx->domain = dctx->domain->next;
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
} else {
/* nothing available */
ret = ENOENT;
goto done;
}
}
/* ok the backend returned, search to see if we have updated results */
ret = nss_cmd_getgrgid_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getgr_send_reply(dctx, true);
}
done:
ret = nss_cmd_done(cmdctx, ret);
if (ret) {
NSS_CMD_FATAL_ERROR(cctx);
}
}
static int nss_cmd_getgrgid(struct cli_ctx *cctx)
{
struct nss_cmd_ctx *cmdctx;
struct nss_dom_ctx *dctx;
struct nss_ctx *nctx;
uint8_t *body;
size_t blen;
int ret;
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);
ret = sss_ncache_check_gid(nctx->ncache, nctx->neg_timeout, cmdctx->id);
if (ret == EEXIST) {
DEBUG(3, ("Gid [%lu] does not exist! (negative cache)\n",
(unsigned long)cmdctx->id));
ret = ENOENT;
goto done;
}
/* gid searches are always multidomain */
dctx->domain = cctx->rctx->domains;
cmdctx->check_next = true;
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
/* ok, find it ! */
ret = nss_cmd_getgrgid_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getgr_send_reply(dctx, true);
}
done:
return nss_cmd_done(cmdctx, ret);
}
/* 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
*/
struct tevent_req *nss_cmd_setgrent_send(TALLOC_CTX *mem_ctx,
struct cli_ctx *client);
static void nss_cmd_setgrent_done(struct tevent_req *req);
static int nss_cmd_setgrent(struct cli_ctx *cctx)
{
struct nss_cmd_ctx *cmdctx;
struct tevent_req *req;
errno_t ret = EOK;
cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
if (!cmdctx) {
return ENOMEM;
}
cmdctx->cctx = cctx;
req = nss_cmd_setgrent_send(cmdctx, cctx);
if (!req) {
DEBUG(0, ("Fatal error calling nss_cmd_setgrent_send\n"));
ret = EIO;
goto done;
}
tevent_req_set_callback(req, nss_cmd_setgrent_done, cmdctx);
done:
return nss_cmd_done(cmdctx, ret);
}
static errno_t nss_cmd_setgrent_step(struct setent_step_ctx *step_ctx);
struct tevent_req *nss_cmd_setgrent_send(TALLOC_CTX *mem_ctx,
struct cli_ctx *client)
{
errno_t ret;
struct nss_ctx *nctx;
struct tevent_req *req;
struct setent_ctx *state;
struct sss_domain_info *dom;
struct setent_step_ctx *step_ctx;
DEBUG(4, ("Received setgrent request\n"));
nctx = talloc_get_type(client->rctx->pvt_ctx, struct nss_ctx);
/* Reset the read pointers */
client->grent_dom_idx = 0;
client->grent_cur = 0;
req = tevent_req_create(mem_ctx, &state, struct setent_ctx);
if (!req) {
DEBUG(0, ("Could not create tevent request for setgrent\n"));
return NULL;
}
state->nctx = nctx;
state->client = client;
state->dctx = talloc_zero(state, struct nss_dom_ctx);
if (!state->dctx) {
ret = ENOMEM;
goto error;
}
/* check if enumeration is enabled in any domain */
for (dom = client->rctx->domains; dom; dom = dom->next) {
if (dom->enumerate != 0) break;
}
state->dctx->domain = dom;
if (state->dctx->domain == NULL) {
DEBUG(2, ("Enumeration disabled on all domains!\n"));
ret = ENOENT;
goto error;
}
state->dctx->check_provider =
NEED_CHECK_PROVIDER(state->dctx->domain->provider);
/* Is the result context already available */
if (state->nctx->gctx) {
if (state->nctx->gctx->ready) {
/* All of the necessary data is in place
* We can return now, getgrent requests will work at this point
*/
tevent_req_done(req);
tevent_req_post(req, state->nctx->rctx->ev);
}
else {
/* Object is still being constructed
* Register for notification when it's
* ready.
*/
ret = nss_setent_add_ref(state, state->nctx->gctx, req);
if (ret != EOK) {
talloc_free(req);
return NULL;
}
}
return req;
}
/* Create a new result context
* We are creating it on the nss_ctx so that it doesn't
* go away if the original request does. We will delete
* it when the refcount goes to zero;
*/
state->nctx->gctx = talloc_zero(nctx, struct getent_ctx);
if (!state->nctx->gctx) {
ret = ENOMEM;
goto error;
}
state->getent_ctx = nctx->gctx;
/* Add a callback reference for ourselves */
ret = nss_setent_add_ref(state, state->nctx->gctx, req);
if (ret) goto error;
/* ok, start the searches */
step_ctx = talloc_zero(state->getent_ctx, struct setent_step_ctx);
if (!step_ctx) {
ret = ENOMEM;
goto error;
}
/* Steal the dom_ctx onto the step_ctx so it doesn't go out of scope if
* this request is canceled while other requests are in-progress.
*/
step_ctx->dctx = talloc_steal(step_ctx, state->dctx);
step_ctx->nctx = state->nctx;
step_ctx->getent_ctx = state->getent_ctx;
step_ctx->rctx = client->rctx;
step_ctx->cctx = client;
step_ctx->returned_to_mainloop = false;
ret = nss_cmd_setgrent_step(step_ctx);
if (ret != EOK && ret != EAGAIN) goto error;
if (ret == EOK) {
tevent_req_post(req, client->rctx->ev);
}
return req;
error:
tevent_req_error(req, ret);
tevent_req_post(req, client->rctx->ev);
return req;
}
static void nss_cmd_setgrent_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr);
static void setgrent_result_timeout(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
void *pvt);
/* nss_cmd_setgrent_step returns
* EOK if everything is done and the request needs to be posted explicitly
* EAGAIN if the caller can safely return to the main loop
*/
static errno_t nss_cmd_setgrent_step(struct setent_step_ctx *step_ctx)
{
errno_t ret;
struct sss_domain_info *dom = step_ctx->dctx->domain;
struct resp_ctx *rctx = step_ctx->rctx;
struct nss_dom_ctx *dctx = step_ctx->dctx;
struct getent_ctx *gctx = step_ctx->getent_ctx;
struct nss_ctx *nctx = step_ctx->nctx;
struct sysdb_ctx *sysdb;
struct ldb_result *res;
struct timeval tv;
struct tevent_timer *te;
struct tevent_req *dpreq;
struct dp_callback_ctx *cb_ctx;
while (dom) {
while (dom && dom->enumerate == 0) {
dom = dom->next;
}
if (!dom) break;
if (dom != dctx->domain) {
/* make sure we reset the check_provider flag when we check
* a new domain */
dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
}
/* make sure to update the dctx if we changed domain */
dctx->domain = dom;
DEBUG(6, ("Requesting info for domain [%s]\n", dom->name));
sysdb = dom->sysdb;
if (sysdb == NULL) {
DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
return EIO;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
step_ctx->returned_to_mainloop = true;
/* Only do this once per provider */
dctx->check_provider = false;
dpreq = sss_dp_get_account_send(step_ctx, rctx, dctx->domain, true,
SSS_DP_GROUP, NULL, 0, NULL);
if (!dpreq) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Enum Cache refresh for domain [%s] failed."
" Trying to return what we have in cache!\n",
dom->name));
} else {
cb_ctx = talloc_zero(step_ctx, struct dp_callback_ctx);
if(!cb_ctx) {
talloc_zfree(dpreq);
return ENOMEM;
}
cb_ctx->callback = nss_cmd_setgrent_dp_callback;
cb_ctx->ptr = step_ctx;
cb_ctx->cctx = step_ctx->cctx;
cb_ctx->mem_ctx = step_ctx;
tevent_req_set_callback(dpreq, nsssrv_dp_send_acct_req_done, cb_ctx);
return EAGAIN;
}
}
ret = sysdb_enumgrent(dctx, sysdb, &res);
if (ret != EOK) {
DEBUG(1, ("Enum from cache failed, skipping domain [%s]\n",
dom->name));
dom = dom->next;
continue;
}
if (res->count == 0) {
DEBUG(4, ("Domain [%s] has no groups, skipping.\n", dom->name));
dom = dom->next;
continue;
}
nctx->gctx->doms = talloc_realloc(gctx, gctx->doms,
struct dom_ctx, gctx->num +1);
if (!gctx->doms) {
talloc_free(gctx);
nctx->gctx = NULL;
return ENOMEM;
}
nctx->gctx->doms[gctx->num].domain = dctx->domain;
nctx->gctx->doms[gctx->num].res = talloc_steal(gctx->doms, res);
nctx->gctx->num++;
/* do not reply until all domain searches are done */
dom = dom->next;
}
/* We've finished all our lookups
* The result object is now safe to read.
*/
nctx->gctx->ready = true;
/* Set up a lifetime timer for this result object
* We don't want this result object to outlive the
* enum cache refresh timeout
*/
tv = tevent_timeval_current_ofs(nctx->enum_cache_timeout, 0);
te = tevent_add_timer(rctx->ev, nctx->gctx, tv,
setgrent_result_timeout, nctx);
if (!te) {
DEBUG(0, ("Could not set up life timer for setgrent result object. "
"Entries may become stale.\n"));
}
/* Notify the waiting clients */
nss_setent_notify_done(nctx->gctx);
if (step_ctx->returned_to_mainloop) {
return EAGAIN;
} else {
return EOK;
}
}
static void setgrent_result_timeout(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
void *pvt)
{
struct nss_ctx *nctx = talloc_get_type(pvt, struct nss_ctx);
DEBUG(1, ("setgrent result object has expired. Cleaning up.\n"));
/* Free the group enumeration context.
* If additional getgrent requests come in, they will invoke
* an implicit setgrent and refresh the result object.
*/
talloc_zfree(nctx->gctx);
}
static void nss_cmd_setgrent_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr)
{
struct setent_step_ctx *step_ctx =
talloc_get_type(ptr, struct setent_step_ctx);
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 = nss_cmd_setgrent_step(step_ctx);
if (ret != EOK && ret != EAGAIN) {
/* Notify any waiting processes of failure */
nss_setent_notify_error(step_ctx->nctx->gctx, ret);
}
}
static errno_t nss_cmd_setgrent_recv(struct tevent_req *req)
{
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}
static void nss_cmd_setgrent_done(struct tevent_req *req)
{
errno_t ret;
struct nss_cmd_ctx *cmdctx =
tevent_req_callback_data(req, struct nss_cmd_ctx);
ret = nss_cmd_setgrent_recv(req);
talloc_zfree(req);
if (ret == EOK || ret == ENOENT) {
/* Either we succeeded or no domains were eligible */
ret = sss_packet_new(cmdctx->cctx->creq, 0,
sss_packet_get_cmd(cmdctx->cctx->creq->in),
&cmdctx->cctx->creq->out);
if (ret == EOK) {
sss_cmd_done(cmdctx->cctx, cmdctx);
return;
}
}
/* Something bad happened */
nss_cmd_done(cmdctx, ret);
}
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 = ENOENT;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
if (!nctx->gctx) goto none;
gctx = nctx->gctx;
while (ret == ENOENT) {
if (cctx->grent_dom_idx >= gctx->num) break;
gdom = &gctx->doms[cctx->grent_dom_idx];
n = gdom->res->count - cctx->grent_cur;
if (n <= 0 && (cctx->grent_dom_idx+1 < gctx->num)) {
cctx->grent_dom_idx++;
gdom = &gctx->doms[cctx->grent_dom_idx];
n = gdom->res->count;
cctx->grent_cur = 0;
}
if (!n) break;
if (n > num) n = num;
msgs = &(gdom->res->msgs[cctx->grent_cur]);
ret = fill_grent(cctx->creq->out,
gdom->domain,
nctx, true, false, msgs, &n);
cctx->grent_cur += n;
}
none:
if (ret == ENOENT) {
ret = sss_cmd_empty_packet(cctx->creq->out);
}
return ret;
}
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 void nss_cmd_implicit_setgrent_done(struct tevent_req *req);
static int nss_cmd_getgrent(struct cli_ctx *cctx)
{
struct nss_ctx *nctx;
struct nss_cmd_ctx *cmdctx;
struct tevent_req *req;
DEBUG(4, ("Requesting info for all groups\n"));
cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
if (!cmdctx) {
return ENOMEM;
}
cmdctx->cctx = cctx;
/* Save the current index and cursor locations
* If we end up calling setgrent implicitly, because the response object
* expired and has to be recreated, we want to resume from the same
* location.
*/
cmdctx->saved_dom_idx = cctx->grent_dom_idx;
cmdctx->saved_cur = cctx->grent_cur;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
if(!nctx->gctx || !nctx->gctx->ready) {
/* Make sure we invoke setgrent if it hasn't been run or is still
* processing from another client
*/
req = nss_cmd_setgrent_send(cctx, cctx);
if (!req) {
return EIO;
}
tevent_req_set_callback(req, nss_cmd_implicit_setgrent_done, cmdctx);
return EOK;
}
return nss_cmd_getgrent_immediate(cmdctx);
}
static errno_t nss_cmd_setgrent_recv(struct tevent_req *req);
static void nss_cmd_implicit_setgrent_done(struct tevent_req *req)
{
errno_t ret;
struct nss_cmd_ctx *cmdctx =
tevent_req_callback_data(req, struct nss_cmd_ctx);
ret = nss_cmd_setgrent_recv(req);
talloc_zfree(req);
/* ENOENT is acceptable, as it just means that there were no entries
* to be returned. This will be handled gracefully in nss_cmd_retpwent
* later.
*/
if (ret != EOK && ret != ENOENT) {
DEBUG(0, ("Implicit setgrent failed with unexpected error [%d][%s]\n",
ret, strerror(ret)));
NSS_CMD_FATAL_ERROR(cmdctx);
}
/* Restore the saved index and cursor locations */
cmdctx->cctx->grent_dom_idx = cmdctx->saved_dom_idx;
cmdctx->cctx->grent_cur = cmdctx->saved_cur;
ret = nss_cmd_getgrent_immediate(cmdctx);
if (ret != EOK) {
DEBUG(0, ("Immediate retrieval failed with unexpected error "
"[%d][%s]\n", ret, strerror(ret)));
NSS_CMD_FATAL_ERROR(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 (ret != EOK) {
return ret;
}
if (nctx->gctx == NULL) goto done;
/* Reset the indices so that subsequent requests start at zero */
cctx->grent_dom_idx = 0;
cctx->grent_cur = 0;
done:
sss_cmd_done(cctx, NULL);
return EOK;
}
/* FIXME: what about mpg, should we return the user's GID ? */
/* FIXME: should we filter out GIDs ? */
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, bindex;
int skipped = 0;
const char *posix;
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 */
bindex = 0;
for (i = 0; i < num; i++) {
gid = ldb_msg_find_attr_as_uint64(res->msgs[i + 1], SYSDB_GIDNUM, 0);
posix = ldb_msg_find_attr_as_string(res->msgs[i + 1], SYSDB_POSIX, NULL);
if (!gid) {
if (posix && strcmp(posix, "FALSE") == 0) {
skipped++;
continue;
} else {
DEBUG(1, ("Incomplete group object for initgroups! Aborting\n"));
return EFAULT;
}
}
((uint32_t *)body)[2 + bindex] = gid;
bindex++;
}
((uint32_t *)body)[0] = num-skipped; /* num results */
((uint32_t *)body)[1] = 0; /* reserved */
return EOK;
}
static int nss_cmd_initgr_send_reply(struct nss_dom_ctx *dctx)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct cli_ctx *cctx = cmdctx->cctx;
int ret;
ret = sss_packet_new(cctx->creq, 0,
sss_packet_get_cmd(cctx->creq->in),
&cctx->creq->out);
if (ret != EOK) {
return EFAULT;
}
ret = fill_initgr(cctx->creq->out, dctx->res);
if (ret) {
return ret;
}
sss_packet_set_error(cctx->creq->out, EOK);
sss_cmd_done(cctx, cmdctx);
return EOK;
}
static void nss_cmd_initgroups_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr);
static int nss_cmd_initgroups_search(struct nss_dom_ctx *dctx)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct sss_domain_info *dom = dctx->domain;
struct cli_ctx *cctx = cmdctx->cctx;
char *name = NULL;
struct sysdb_ctx *sysdb;
struct nss_ctx *nctx;
int ret;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
while (dom) {
/* if it is a domainless search, skip domains that require fully
* qualified names instead */
while (dom && cmdctx->check_next && dom->fqnames) {
dom = dom->next;
}
if (!dom) break;
if (dom != dctx->domain) {
/* make sure we reset the check_provider flag when we check
* a new domain */
dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
}
/* make sure to update the dctx if we changed domain */
dctx->domain = dom;
talloc_free(name);
name = sss_get_cased_name(dctx, cmdctx->name, dom->case_sensitive);
if (!name) return ENOMEM;
/* verify this user has not yet been negatively cached,
* or has been permanently filtered */
ret = sss_ncache_check_user(nctx->ncache, nctx->neg_timeout,
dom, name);
/* if neg cached, return we didn't find it */
if (ret == EEXIST) {
DEBUG(2, ("User [%s] does not exist in [%s]! (negative cache)\n",
name, dom->name));
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = dom->next;
continue;
}
/* There are no further domains or this was a
* fully-qualified user request.
*/
return ENOENT;
}
DEBUG(4, ("Requesting info for [%s@%s]\n", name, dom->name));
sysdb = dom->sysdb;
if (sysdb == NULL) {
DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
return EIO;
}
ret = sysdb_initgroups(cmdctx, sysdb, name, &dctx->res);
if (ret != EOK) {
DEBUG(1, ("Failed to make request to our cache! [%d][%s]\n",
ret, strerror(ret)));
return EIO;
}
if (dctx->res->count == 0 && !dctx->check_provider) {
/* set negative cache only if not result of cache check */
ret = sss_ncache_set_user(nctx->ncache, false, dom, name);
if (ret != EOK) {
return ret;
}
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = dom->next;
if (dom) continue;
}
DEBUG(2, ("No results for initgroups call\n"));
return ENOENT;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
ret = check_cache(dctx, nctx, dctx->res,
SSS_DP_INITGROUPS, name, 0,
nss_cmd_initgroups_dp_callback,
dctx);
if (ret != EOK) {
/* Anything but EOK means we should reenter the mainloop
* because we may be refreshing the cache
*/
return ret;
}
}
DEBUG(6, ("Initgroups for [%s@%s] completed\n", name, dom->name));
return EOK;
}
DEBUG(SSSDBG_MINOR_FAILURE,
("No matching domain found for [%s], fail!\n", cmdctx->name));
return ENOENT;
}
static void nss_cmd_initgroups_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;
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 && dctx->res->count != 0) {
ret = nss_cmd_initgr_send_reply(dctx);
goto done;
}
/* no previous results, just loop to next domain if possible */
if (dctx->domain->next && cmdctx->check_next) {
dctx->domain = dctx->domain->next;
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
} else {
/* nothing available */
ret = ENOENT;
goto done;
}
}
/* ok the backend returned, search to see if we have updated results */
ret = nss_cmd_initgroups_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_initgr_send_reply(dctx);
}
done:
ret = nss_cmd_done(cmdctx, ret);
if (ret) {
NSS_CMD_FATAL_ERROR(cctx);
}
}
/* for now, if we are online, try to always query the backend */
static void nss_cmd_initgroups_cb(struct tevent_req *req);
static int nss_cmd_initgroups(struct cli_ctx *cctx)
{
struct tevent_req *req;
struct nss_cmd_ctx *cmdctx;
struct nss_dom_ctx *dctx;
const char *rawname;
char *domname;
uint8_t *body;
size_t blen;
int ret;
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;
}
/* If the body isn't valid UTF-8, fail */
if (!sss_utf8_check(body, blen -1)) {
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:"<ALL>"));
if (domname) {
dctx->domain = responder_get_domain(dctx, cctx->rctx, domname);
if (!dctx->domain) {
req = sss_dp_get_domains_send(cctx->rctx, cctx->rctx, true, domname);
if (req == NULL) {
ret = ENOMEM;
} else {
dctx->domname = domname;
tevent_req_set_callback(req, nss_cmd_initgroups_cb, dctx);
ret = EAGAIN;
}
goto done;
}
} else {
/* this is a multidomain search */
dctx->domain = cctx->rctx->domains;
cmdctx->check_next = true;
if (cctx->rctx->get_domains_last_call.tv_sec == 0) {
req = sss_dp_get_domains_send(cctx->rctx, cctx->rctx, false, NULL);
if (req == NULL) {
ret = ENOMEM;
} else {
tevent_req_set_callback(req, nss_cmd_initgroups_cb, dctx);
ret = EAGAIN;
}
goto done;
}
}
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
/* ok, find it ! */
ret = nss_cmd_initgroups_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_initgr_send_reply(dctx);
}
done:
return nss_cmd_done(cmdctx, ret);
}
static void nss_cmd_initgroups_cb(struct tevent_req *req)
{
struct nss_dom_ctx *dctx = tevent_req_callback_data(req, struct nss_dom_ctx);
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct cli_ctx *cctx = cmdctx->cctx;
errno_t ret;
ret = sss_dp_get_domains_recv(req);
talloc_free(req);
if (ret != EOK) {
goto done;
}
if (dctx->domname) {
dctx->domain = responder_get_domain(dctx, cctx->rctx, dctx->domname);
if (dctx->domain == NULL) {
ret = ENOENT;
goto done;
}
}
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
/* ok, find it ! */
ret = nss_cmd_initgroups_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_initgr_send_reply(dctx);
}
done:
nss_cmd_done(cmdctx, ret);
}
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_NSS_SETNETGRENT, nss_cmd_setnetgrent},
{SSS_NSS_GETNETGRENT, nss_cmd_getnetgrent},
{SSS_NSS_ENDNETGRENT, nss_cmd_endnetgrent},
{SSS_NSS_GETSERVBYNAME, nss_cmd_getservbyname},
{SSS_NSS_GETSERVBYPORT, nss_cmd_getservbyport},
{SSS_NSS_SETSERVENT, nss_cmd_setservent},
{SSS_NSS_GETSERVENT, nss_cmd_getservent},
{SSS_NSS_ENDSERVENT, nss_cmd_endservent},
{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;
}