From e4433748798466a9dd7bcb103073f060220c591a Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Tue, 24 Jun 2014 18:30:01 +0200 Subject: [PATCH 5/5] PAM, NSS: allow UPN login names With this patch the NSS and PAM responders can handle user principal names besides the fully qualified user names. User principal names are build from a user name and a domain suffix separated by an '@' sign. But the domain suffix does not necessarily has to be the same as the configured domain name in sssd.conf of the dynamically discovered DNS domain name of a domain. The typical use case is an Active Directory forest with lots of different domains. To not force the users to remember the name of the individual domain they belong to the AD administrator can set a common domain suffix for all users from all domains in the forest. This is typically the domain name used for emails to make it even more easy to the users to remember it. Since SSSD splits name and domain part at the '@' sign and the common domain suffix might not be resolvable by DNS or the given user is not a member of that domain (e.g. in the case where the forest root is used as common domain suffix) SSSD might fail to look up the user. With this patch the NSS and PAM responder will do an extra lookup for a UPN if the domain part of the given name is not known or the user was not found and the login name contained the '@' sign. Resolves https://fedorahosted.org/sssd/ticket/1749 --- src/providers/data_provider.h | 1 + src/responder/nss/nsssrv_cmd.c | 82 ++++++++++++++++++++++++++++++++++++-- src/responder/nss/nsssrv_private.h | 1 + src/responder/pam/pamsrv_cmd.c | 45 ++++++++++++++------- 4 files changed, 111 insertions(+), 18 deletions(-) diff --git a/src/providers/data_provider.h b/src/providers/data_provider.h index 20b7aaf..50dc9f6 100644 --- a/src/providers/data_provider.h +++ b/src/providers/data_provider.h @@ -174,6 +174,7 @@ struct pam_data { struct sss_auth_token *newauthtok; uint32_t cli_pid; char *logon_name; + bool name_is_upn; int pam_status; int response_delay; diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c index 1b7c9ae..d50857b 100644 --- a/src/responder/nss/nsssrv_cmd.c +++ b/src/responder/nss/nsssrv_cmd.c @@ -719,13 +719,16 @@ static int nss_cmd_getpwnam_search(struct nss_dom_ctx *dctx) char *name = NULL; struct nss_ctx *nctx; int ret; + static const char *user_attrs[] = SYSDB_PW_ATTRS; + struct ldb_message *msg; 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) { + while (dom && cmdctx->check_next && dom->fqnames + && !cmdctx->name_is_upn) { dom = get_next_domain(dom, false); } @@ -774,7 +777,37 @@ static int nss_cmd_getpwnam_search(struct nss_dom_ctx *dctx) return EIO; } - ret = sysdb_getpwnam(cmdctx, dom, name, &dctx->res); + if (cmdctx->name_is_upn) { + ret = sysdb_search_user_by_upn(cmdctx, dom, name, user_attrs, &msg); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_upn failed.\n"); + return ret; + } + + dctx->res = talloc_zero(cmdctx, struct ldb_result); + if (dctx->res == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + if (ret == ENOENT) { + dctx->res->count = 0; + dctx->res->msgs = NULL; + ret = EOK; + } else { + dctx->res->count = 1; + dctx->res->msgs = talloc_array(dctx->res, + struct ldb_message *, 1); + if (dctx->res->msgs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + return ENOMEM; + } + + dctx->res->msgs[0] = talloc_steal(dctx->res->msgs, msg); + } + } else { + ret = sysdb_getpwnam(cmdctx, dom, name, &dctx->res); + } if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to make request to our cache!\n"); @@ -818,7 +851,8 @@ static int nss_cmd_getpwnam_search(struct nss_dom_ctx *dctx) * yet) then verify that the cache is uptodate */ if (dctx->check_provider) { ret = check_cache(dctx, nctx, dctx->res, - SSS_DP_USER, name, 0, NULL, + SSS_DP_USER, name, 0, + cmdctx->name_is_upn ? "U" : NULL, nss_cmd_getby_dp_callback, dctx); if (ret != EOK) { @@ -841,6 +875,30 @@ static int nss_cmd_getpwnam_search(struct nss_dom_ctx *dctx) return ENOENT; } +static int nss_cmd_assume_upn(struct nss_dom_ctx *dctx) +{ + int ret; + + if (dctx->domain == NULL) { + dctx->domain = dctx->cmdctx->cctx->rctx->domains; + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + dctx->cmdctx->check_next = true; + dctx->cmdctx->name = talloc_strdup(dctx->cmdctx, dctx->rawname); + dctx->cmdctx->name_is_upn = true; + if (dctx->cmdctx->name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + return ENOMEM; + } + } + + ret = nss_cmd_getpwnam_search(dctx); + if (ret == EOK) { + ret = nss_cmd_getpw_send_reply(dctx, false); + } + + return ret; +} + static int nss_cmd_getgrnam_search(struct nss_dom_ctx *dctx); static int nss_cmd_getgr_send_reply(struct nss_dom_ctx *dctx, bool filter); static int nss_cmd_initgroups_search(struct nss_dom_ctx *dctx); @@ -989,6 +1047,12 @@ static void nss_cmd_getby_dp_callback(uint16_t err_maj, uint32_t err_min, } done: + if (ret == ENOENT && dctx->cmdctx->cmd == SSS_NSS_GETPWNAM \ + && strchr(dctx->rawname, '@') != NULL) { + /* assume Kerberos principal */ + ret = nss_cmd_assume_upn(dctx); + } + ret = nss_cmd_done(cmdctx, ret); if (ret) { NSS_CMD_FATAL_ERROR(cctx); @@ -1194,6 +1258,9 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx) if (ret == EOK) { /* we have results to return */ ret = nss_cmd_getpw_send_reply(dctx, false); + } else if (ret == ENOENT && strchr(dctx->rawname, '@') != NULL) { + /* assume Kerberos principal */ + ret = nss_cmd_assume_upn(dctx); } break; case SSS_NSS_GETGRNAM: @@ -1244,7 +1311,11 @@ static void nss_cmd_getbynam_done(struct tevent_req *req) ret = sss_parse_name_for_domains(cmdctx, cctx->rctx->domains, cctx->rctx->default_domain, rawname, &domname, &cmdctx->name); - if (ret != EOK) { + if (ret == EAGAIN && dctx->cmdctx->cmd == SSS_NSS_GETPWNAM) { + /* assume Kerberos principal */ + ret = nss_cmd_assume_upn(dctx); + goto done; + } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Invalid name received [%s]\n", rawname); ret = ENOENT; goto done; @@ -1274,6 +1345,9 @@ static void nss_cmd_getbynam_done(struct tevent_req *req) if (ret == EOK) { /* we have results to return */ ret = nss_cmd_getpw_send_reply(dctx, false); + } else if (ret == ENOENT && strchr(dctx->rawname, '@') != NULL) { + /* assume Kerberos principal */ + ret = nss_cmd_assume_upn(dctx); } break; case SSS_NSS_GETGRNAM: diff --git a/src/responder/nss/nsssrv_private.h b/src/responder/nss/nsssrv_private.h index 23bb6a8..e5f3bde 100644 --- a/src/responder/nss/nsssrv_private.h +++ b/src/responder/nss/nsssrv_private.h @@ -31,6 +31,7 @@ struct nss_cmd_ctx { struct cli_ctx *cctx; enum sss_cli_command cmd; char *name; + bool name_is_upn; uint32_t id; char *secid; diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c index b7f9f34..4ff7389 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -882,7 +882,22 @@ static void pam_forwarder_cb(struct tevent_req *req) pd = preq->pd; ret = pam_forwarder_parse_data(cctx, pd); - if (ret != EOK) { + if (ret == EAGAIN) { + if (strchr(preq->pd->logon_name, '@') == NULL) { + goto done; + } + /* Assuming Kerberos principal */ + preq->domain = preq->cctx->rctx->domains; + preq->check_provider = NEED_CHECK_PROVIDER(preq->domain->provider); + preq->pd->user = talloc_strdup(preq->pd, preq->pd->logon_name); + if (preq->pd->user == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + preq->pd->name_is_upn = true; + preq->pd->domain = NULL; + } else if (ret != EOK) { ret = EINVAL; goto done; } @@ -917,11 +932,14 @@ static int pam_check_user_search(struct pam_auth_req *preq) struct pam_ctx *pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); struct ldb_result *res; + static const char *user_attrs[] = SYSDB_PW_ATTRS; + struct ldb_message *msg; while (dom) { /* if it is a domainless search, skip domains that require fully * qualified names instead */ - while (dom && !preq->pd->domain && dom->fqnames) { + while (dom && !preq->pd->domain && !preq->pd->name_is_upn + && dom->fqnames) { dom = get_next_domain(dom, false); } @@ -971,20 +989,18 @@ static int pam_check_user_search(struct pam_auth_req *preq) return EFAULT; } - ret = sysdb_getpwnam(preq, dom, name, &res); - if (ret != EOK) { + if (preq->pd->name_is_upn) { + ret = sysdb_search_user_by_upn(preq, dom, name, user_attrs, &msg); + } else { + ret = sysdb_search_user_by_name(preq, dom, name, user_attrs, &msg); + } + if (ret != EOK && ret != ENOENT) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to make request to our cache!\n"); return EIO; } - if (res->count > 1) { - DEBUG(SSSDBG_FATAL_FAILURE, - "getpwnam call returned more than one result !?!\n"); - return ENOENT; - } - - if (res->count == 0) { + if (ret == ENOENT) { if (preq->check_provider == false) { /* set negative cache only if not result of cache check */ ret = sss_ncache_set_user(pctx->ncache, false, dom, name); @@ -1013,7 +1029,7 @@ static int pam_check_user_search(struct pam_auth_req *preq) /* if we need to check the remote account go on */ if (preq->check_provider) { - cacheExpire = ldb_msg_find_attr_as_uint64(res->msgs[0], + cacheExpire = ldb_msg_find_attr_as_uint64(msg, SYSDB_CACHE_EXPIRE, 0); if (cacheExpire < time(NULL)) { break; @@ -1024,7 +1040,7 @@ static int pam_check_user_search(struct pam_auth_req *preq) "Returning info for user [%s@%s]\n", name, dom->name); /* We might have searched by alias. Pass on the primary name */ - ret = pd_set_primary_name(res->msgs[0], preq->pd); + ret = pd_set_primary_name(msg, preq->pd); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not canonicalize username\n"); return ret; @@ -1047,7 +1063,8 @@ static int pam_check_user_search(struct pam_auth_req *preq) dpreq = sss_dp_get_account_send(preq, preq->cctx->rctx, dom, false, SSS_DP_INITGROUPS, - name, 0, NULL); + name, 0, + preq->pd->name_is_upn ? "U" : NULL); if (!dpreq) { DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory sending data provider request\n"); -- 1.8.3.1