URL: https://github.com/SSSD/sssd/pull/5367 Author: pbrezina Title: #5367: pam: add pam_sss_gss module for gssapi authentication Action: synchronized
To pull the PR as Git branch: git remote add ghsssd https://github.com/SSSD/sssd git fetch ghsssd pull/5367/head:pr5367 git checkout pr5367
From dc024571c8c90f2929992fb71c9e3a2dba57194d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrez...@redhat.com> Date: Fri, 2 Oct 2020 14:04:24 +0200 Subject: [PATCH 1/8] sss_format.h: include config.h config.h is required for the definitions to work correctly. Compilation will fail if sss_format.h is included in a file that does not include directly or indirectly config.h --- src/util/sss_format.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/sss_format.h b/src/util/sss_format.h index 5cf080842e..9a30417042 100644 --- a/src/util/sss_format.h +++ b/src/util/sss_format.h @@ -27,6 +27,8 @@ #ifndef __SSS_FORMAT_H__ #define __SSS_FORMAT_H__ +#include "config.h" + #include <inttypes.h> /* key_serial_t is defined in keyutils.h as typedef int32_t */ From 64c349e457305405b037acb317445a9123bf2157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrez...@redhat.com> Date: Thu, 8 Oct 2020 13:25:17 +0200 Subject: [PATCH 2/8] packet: add sss_packet_set_body --- src/responder/common/responder_packet.c | 19 +++++++++++++++++++ src/responder/common/responder_packet.h | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/src/responder/common/responder_packet.c b/src/responder/common/responder_packet.c index ab15b1dacb..f56d922760 100644 --- a/src/responder/common/responder_packet.c +++ b/src/responder/common/responder_packet.c @@ -302,6 +302,25 @@ void sss_packet_get_body(struct sss_packet *packet, uint8_t **body, size_t *blen *blen = sss_packet_get_len(packet) - SSS_NSS_HEADER_SIZE; } +errno_t sss_packet_set_body(struct sss_packet *packet, + uint8_t *body, + size_t blen) +{ + uint8_t *pbody; + size_t plen; + errno_t ret; + + ret = sss_packet_grow(packet, blen); + if (ret != EOK) { + return ret; + } + + sss_packet_get_body(packet, &pbody, &plen); + memcpy(pbody, body, blen); + + return EOK; +} + void sss_packet_set_error(struct sss_packet *packet, int error) { SAFEALIGN_SETMEM_UINT32(packet->buffer + SSS_PACKET_ERR_OFFSET, error, diff --git a/src/responder/common/responder_packet.h b/src/responder/common/responder_packet.h index afceb4aaef..509a22a9a9 100644 --- a/src/responder/common/responder_packet.h +++ b/src/responder/common/responder_packet.h @@ -42,4 +42,9 @@ uint32_t sss_packet_get_status(struct sss_packet *packet); void sss_packet_get_body(struct sss_packet *packet, uint8_t **body, size_t *blen); void sss_packet_set_error(struct sss_packet *packet, int error); +/* Grow packet and set its body. */ +errno_t sss_packet_set_body(struct sss_packet *packet, + uint8_t *body, + size_t blen); + #endif /* __SSSSRV_PACKET_H__ */ From c20fe91fb86991841b1093a13cff07c23c1f1aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrez...@redhat.com> Date: Thu, 8 Oct 2020 13:25:58 +0200 Subject: [PATCH 3/8] domain: store hostname and keytab path --- src/confdb/confdb.c | 45 +++++++++++++++++++++++++++++++++++++++ src/confdb/confdb.h | 6 ++++++ src/db/sysdb_subdomains.c | 12 +++++++++++ 3 files changed, 63 insertions(+) diff --git a/src/confdb/confdb.c b/src/confdb/confdb.c index d2fc018fd0..f981ddf1e6 100644 --- a/src/confdb/confdb.c +++ b/src/confdb/confdb.c @@ -871,6 +871,35 @@ static int confdb_get_domain_section(TALLOC_CTX *mem_ctx, return ret; } +static char *confdb_get_domain_hostname(TALLOC_CTX *mem_ctx, + struct ldb_result *res, + const char *provider) +{ + char sys[HOST_NAME_MAX + 1] = {'\0'}; + const char *opt = NULL; + int ret; + + if (strcasecmp(provider, "ad") == 0) { + opt = ldb_msg_find_attr_as_string(res->msgs[0], "ad_hostname", NULL); + } else if (strcasecmp(provider, "ipa") == 0) { + opt = ldb_msg_find_attr_as_string(res->msgs[0], "ipa_hostname", NULL); + } + + if (opt != NULL) { + return talloc_strdup(mem_ctx, opt); + } + + ret = gethostname(sys, sizeof(sys)); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get hostname [%d]: %s\n", ret, + sss_strerror(ret)); + return NULL; + } + + return talloc_strdup(mem_ctx, sys); +} + static int confdb_get_domain_internal(struct confdb_ctx *cdb, TALLOC_CTX *mem_ctx, const char *name, @@ -1536,6 +1565,22 @@ static int confdb_get_domain_internal(struct confdb_ctx *cdb, goto done; } + domain->hostname = confdb_get_domain_hostname(domain, res, domain->provider); + if (domain->hostname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get domain hostname\n"); + goto done; + } + + domain->krb5_keytab = NULL; + tmp = ldb_msg_find_attr_as_string(res->msgs[0], "krb5_keytab", NULL); + if (tmp != NULL) { + domain->krb5_keytab = talloc_strdup(domain, tmp); + if (domain->krb5_keytab == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get domain keytab!\n"); + goto done; + } + } + domain->has_views = false; domain->view_name = NULL; diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index fd6d76cde4..54e3f73805 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -425,6 +425,12 @@ struct sss_domain_info { /* Do not use the _output_fqnames property directly in new code, but rather * use sss_domain_info_{get,set}_output_fqnames(). */ bool output_fqnames; + + /* Hostname associated with this domain. */ + const char *hostname; + + /* Keytab used by this domain. */ + const char *krb5_keytab; }; /** diff --git a/src/db/sysdb_subdomains.c b/src/db/sysdb_subdomains.c index d256817a66..5b42f9bdc2 100644 --- a/src/db/sysdb_subdomains.c +++ b/src/db/sysdb_subdomains.c @@ -125,6 +125,18 @@ struct sss_domain_info *new_subdomain(TALLOC_CTX *mem_ctx, } } + dom->hostname = talloc_strdup(dom, parent->hostname); + if (dom->hostname == NULL && parent->hostname != NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to copy hostname.\n"); + goto fail; + } + + dom->krb5_keytab = talloc_strdup(dom, parent->krb5_keytab); + if (dom->krb5_keytab == NULL && parent->krb5_keytab != NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to copy krb5_keytab.\n"); + goto fail; + } + dom->enumerate = enumerate; dom->fqnames = true; dom->mpg_mode = mpg_mode; From 925d607fb1d81d46e7e10483ef4f01f2d057c0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrez...@redhat.com> Date: Thu, 10 Dec 2020 19:28:58 +0100 Subject: [PATCH 4/8] cache_req: add helper to call user by upn search --- src/responder/common/cache_req/cache_req.h | 13 +++++++++++ .../cache_req/plugins/cache_req_user_by_upn.c | 23 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/responder/common/cache_req/cache_req.h b/src/responder/common/cache_req/cache_req.h index d36cb2d3b0..d301a076e4 100644 --- a/src/responder/common/cache_req/cache_req.h +++ b/src/responder/common/cache_req/cache_req.h @@ -277,6 +277,19 @@ cache_req_user_by_name_attrs_send(TALLOC_CTX *mem_ctx, #define cache_req_user_by_name_attrs_recv(mem_ctx, req, _result) \ cache_req_single_domain_recv(mem_ctx, req, _result) +struct tevent_req * +cache_req_user_by_upn_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *upn); + +#define cache_req_user_by_upn_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result); + struct tevent_req * cache_req_user_by_id_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, diff --git a/src/responder/common/cache_req/plugins/cache_req_user_by_upn.c b/src/responder/common/cache_req/plugins/cache_req_user_by_upn.c index e08ab70ae0..037994c8c2 100644 --- a/src/responder/common/cache_req/plugins/cache_req_user_by_upn.c +++ b/src/responder/common/cache_req/plugins/cache_req_user_by_upn.c @@ -133,3 +133,26 @@ const struct cache_req_plugin cache_req_user_by_upn = { .dp_get_domain_send_fn = NULL, .dp_get_domain_recv_fn = NULL, }; + +struct tevent_req * +cache_req_user_by_upn_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *upn) +{ + struct cache_req_data *data; + + data = cache_req_data_name(mem_ctx, CACHE_REQ_USER_BY_UPN, upn); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + req_dom_type, domain, + data); +} From 5f29d7c313580d3e5f2c9f11067c9967cc986435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrez...@redhat.com> Date: Thu, 1 Oct 2020 14:02:44 +0200 Subject: [PATCH 5/8] pam: fix typo in debug message --- src/responder/pam/pamsrv_cmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c index 1d02514979..acbfc0c39f 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -1941,7 +1941,7 @@ static void pam_check_user_search_next(struct tevent_req *req) talloc_zfree(req); if (ret != EOK && ret != ENOENT) { DEBUG(SSSDBG_OP_FAILURE, "Cache lookup failed, trying to get fresh " - "data from the backened.\n"); + "data from the backend.\n"); } DEBUG(SSSDBG_TRACE_ALL, "PAM initgroups scheme [%s].\n", From aaffbc8edd97e3ce7caa77b6e4cc94fc4cd8711a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrez...@redhat.com> Date: Fri, 9 Oct 2020 13:03:54 +0200 Subject: [PATCH 6/8] pam: add pam_gssapi_services option :config: Added `pam_gssapi_services` to list PAM services that can authenticate using GSSAPI --- src/confdb/confdb.c | 12 ++++++++++++ src/confdb/confdb.h | 4 ++++ src/config/SSSDConfig/sssdoptions.py | 1 + src/config/SSSDConfigTest.py | 6 ++++-- src/config/cfg_rules.ini | 3 +++ src/config/etc/sssd.api.conf | 2 ++ src/db/sysdb_subdomains.c | 13 +++++++++++++ src/man/sssd.conf.5.xml | 27 +++++++++++++++++++++++++++ src/responder/pam/pamsrv.c | 21 +++++++++++++++++++++ src/responder/pam/pamsrv.h | 3 +++ 10 files changed, 90 insertions(+), 2 deletions(-) diff --git a/src/confdb/confdb.c b/src/confdb/confdb.c index f981ddf1e6..7f1956d6d9 100644 --- a/src/confdb/confdb.c +++ b/src/confdb/confdb.c @@ -1581,6 +1581,18 @@ static int confdb_get_domain_internal(struct confdb_ctx *cdb, } } + tmp = ldb_msg_find_attr_as_string(res->msgs[0], CONFDB_PAM_GSSAPI_SERVICES, + "-"); + if (tmp != NULL) { + ret = split_on_separator(domain, tmp, ',', true, true, + &domain->gssapi_services, NULL); + if (ret != 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot parse %s\n", CONFDB_PAM_GSSAPI_SERVICES); + goto done; + } + } + domain->has_views = false; domain->view_name = NULL; diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index 54e3f73805..7a3bc8bb50 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -144,6 +144,7 @@ #define CONFDB_PAM_P11_ALLOWED_SERVICES "pam_p11_allowed_services" #define CONFDB_PAM_P11_URI "p11_uri" #define CONFDB_PAM_INITGROUPS_SCHEME "pam_initgroups_scheme" +#define CONFDB_PAM_GSSAPI_SERVICES "pam_gssapi_services" /* SUDO */ #define CONFDB_SUDO_CONF_ENTRY "config/sudo" @@ -431,6 +432,9 @@ struct sss_domain_info { /* Keytab used by this domain. */ const char *krb5_keytab; + + /* List of PAM services that are allowed to authenticate with GSSAPI. */ + char **gssapi_services; }; /** diff --git a/src/config/SSSDConfig/sssdoptions.py b/src/config/SSSDConfig/sssdoptions.py index de96db6f44..f59fe8d9f2 100644 --- a/src/config/SSSDConfig/sssdoptions.py +++ b/src/config/SSSDConfig/sssdoptions.py @@ -104,6 +104,7 @@ def __init__(self): 'p11_wait_for_card_timeout': _('Additional timeout to wait for a card if requested'), 'p11_uri': _('PKCS#11 URI to restrict the selection of devices for Smartcard authentication'), 'pam_initgroups_scheme' : _('When shall the PAM responder force an initgroups request'), + 'pam_gssapi_services' : _('List of PAM services that are allowed to authenticate with GSSAPI.'), # [sudo] 'sudo_timed': _('Whether to evaluate the time-based attributes in sudo rules'), diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py index 323be5ed3c..21fffe1b6b 100755 --- a/src/config/SSSDConfigTest.py +++ b/src/config/SSSDConfigTest.py @@ -653,7 +653,8 @@ def testListOptions(self): 'full_name_format', 're_expression', 'cached_auth_timeout', - 'auto_private_groups'] + 'auto_private_groups', + 'pam_gssapi_services'] self.assertTrue(type(options) == dict, "Options should be a dictionary") @@ -1030,7 +1031,8 @@ def testRemoveProvider(self): 'full_name_format', 're_expression', 'cached_auth_timeout', - 'auto_private_groups'] + 'auto_private_groups', + 'pam_gssapi_services'] self.assertTrue(type(options) == dict, "Options should be a dictionary") diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini index 773afd8bba..c6dfd56483 100644 --- a/src/config/cfg_rules.ini +++ b/src/config/cfg_rules.ini @@ -139,6 +139,7 @@ option = pam_p11_allowed_services option = p11_wait_for_card_timeout option = p11_uri option = pam_initgroups_scheme +option = pam_gssapi_services [rule/allowed_sudo_options] validator = ini_allowed_options @@ -437,6 +438,7 @@ option = wildcard_limit option = full_name_format option = re_expression option = auto_private_groups +option = pam_gssapi_services #Entry cache timeouts option = entry_cache_user_timeout @@ -831,6 +833,7 @@ option = ad_backup_server option = ad_site option = use_fully_qualified_names option = auto_private_groups +option = pam_gssapi_services [rule/sssd_checks] validator = sssd_checks diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf index 623160ffd6..f46f3c46d2 100644 --- a/src/config/etc/sssd.api.conf +++ b/src/config/etc/sssd.api.conf @@ -80,6 +80,7 @@ pam_p11_allowed_services = str, None, false p11_wait_for_card_timeout = int, None, false p11_uri = str, None, false pam_initgroups_scheme = str, None, false +pam_gssapi_services = str, None, false [sudo] # sudo service @@ -199,6 +200,7 @@ cached_auth_timeout = int, None, false full_name_format = str, None, false re_expression = str, None, false auto_private_groups = str, None, false +pam_gssapi_services = str, None, false #Entry cache timeouts entry_cache_user_timeout = int, None, false diff --git a/src/db/sysdb_subdomains.c b/src/db/sysdb_subdomains.c index 5b42f9bdc2..bfc6df0f5f 100644 --- a/src/db/sysdb_subdomains.c +++ b/src/db/sysdb_subdomains.c @@ -184,6 +184,8 @@ struct sss_domain_info *new_subdomain(TALLOC_CTX *mem_ctx, dom->homedir_substr = parent->homedir_substr; dom->override_gid = parent->override_gid; + dom->gssapi_services = parent->gssapi_services; + if (parent->sysdb == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Missing sysdb context in parent domain.\n"); goto fail; @@ -241,6 +243,17 @@ check_subdom_config_file(struct confdb_ctx *confdb, sd_conf_path, CONFDB_DOMAIN_FQ, subdomain->fqnames ? "TRUE" : "FALSE"); + /* allow to set pam_gssapi_services */ + ret = confdb_get_string_as_list(confdb, subdomain, sd_conf_path, + CONFDB_PAM_GSSAPI_SERVICES, + &subdomain->gssapi_services); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get %s option for the subdomain: %s\n", + CONFDB_PAM_GSSAPI_SERVICES, subdomain->name); + goto done; + } + ret = EOK; done: talloc_free(tmp_ctx); diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index d247400bfb..08f6d2d1a7 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -1706,6 +1706,32 @@ p11_uri = library-description=OpenSC%20smartcard%20framework;slot-id=2 </para> </listitem> </varlistentry> + <varlistentry> + <term>pam_gssapi_services</term> + <listitem> + <para> + Comma separated list of PAM services that are + allowed to try GSSAPI authentication using + pam_sss_gss.so module. This option can be also set + in domain section. + </para> + <para> + Note: This option can also be set per-domain which + overwrites the value in [pam] section. It can also + be set for trusted domain which overwrites the value + in the domain section. + </para> + <para> + Example: + <programlisting> +pam_gssapi_services = sudo, sudo-i + </programlisting> + </para> + <para> + Default: - (GSSAPI authentication is disabled) + </para> + </listitem> + </varlistentry> </variablelist> </refsect2> @@ -3780,6 +3806,7 @@ ldap_user_extra_attrs = phone:telephoneNumber <para>ad_backup_server,</para> <para>ad_site,</para> <para>use_fully_qualified_names</para> + <para>pam_gssapi_services</para> <para> For more details about these options see their individual description in the manual page. diff --git a/src/responder/pam/pamsrv.c b/src/responder/pam/pamsrv.c index 1f1ee608b8..0492569c7f 100644 --- a/src/responder/pam/pamsrv.c +++ b/src/responder/pam/pamsrv.c @@ -327,6 +327,27 @@ static int pam_process_init(TALLOC_CTX *mem_ctx, } } + ret = confdb_get_string(pctx->rctx->cdb, pctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_GSSAPI_SERVICES, "-", &tmpstr); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to determine gssapi services.\n"); + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Found value [%s] for option [%s].\n", tmpstr, + CONFDB_PAM_GSSAPI_SERVICES); + + if (tmpstr != NULL) { + ret = split_on_separator(pctx, tmpstr, ',', true, true, + &pctx->gssapi_services, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "split_on_separator() failed [%d]: [%s].\n", ret, + sss_strerror(ret)); + goto done; + } + } + /* The responder is initialized. Now tell it to the monitor. */ ret = sss_monitor_service_init(rctx, rctx->ev, SSS_BUS_PAM, SSS_PAM_SBUS_SERVICE_NAME, diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h index 24d307a14e..730dee2881 100644 --- a/src/responder/pam/pamsrv.h +++ b/src/responder/pam/pamsrv.h @@ -62,6 +62,9 @@ struct pam_ctx { int num_prompting_config_sections; enum pam_initgroups_scheme initgroups_scheme; + + /* List of PAM services that are allowed to authenticate with GSSAPI. */ + char **gssapi_services; }; struct pam_auth_req { From d2bf456d1c72ba8c2807b4613cba037177b684a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrez...@redhat.com> Date: Thu, 10 Dec 2020 22:05:30 +0100 Subject: [PATCH 7/8] pam: add pam_gssapi_check_upn option :config: Added `pam_gssapi_check_upn` to enforce authentication only with principal that can be associated with target user. --- src/confdb/confdb.c | 10 ++++++++++ src/confdb/confdb.h | 2 ++ src/config/SSSDConfig/sssdoptions.py | 1 + src/config/SSSDConfigTest.py | 6 ++++-- src/config/cfg_rules.ini | 3 +++ src/config/etc/sssd.api.conf | 2 ++ src/db/sysdb_subdomains.c | 12 ++++++++++++ src/man/sssd.conf.5.xml | 26 ++++++++++++++++++++++++++ src/responder/pam/pamsrv.c | 9 +++++++++ src/responder/pam/pamsrv.h | 1 + 10 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/confdb/confdb.c b/src/confdb/confdb.c index 7f1956d6d9..2881ce5da7 100644 --- a/src/confdb/confdb.c +++ b/src/confdb/confdb.c @@ -1593,6 +1593,16 @@ static int confdb_get_domain_internal(struct confdb_ctx *cdb, } } + tmp = ldb_msg_find_attr_as_string(res->msgs[0], CONFDB_PAM_GSSAPI_CHECK_UPN, + NULL); + if (tmp != NULL) { + domain->gssapi_check_upn = talloc_strdup(domain, tmp); + if (domain->gssapi_check_upn == NULL) { + ret = ENOMEM; + goto done; + } + } + domain->has_views = false; domain->view_name = NULL; diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index 7a3bc8bb50..036f9ecadf 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -145,6 +145,7 @@ #define CONFDB_PAM_P11_URI "p11_uri" #define CONFDB_PAM_INITGROUPS_SCHEME "pam_initgroups_scheme" #define CONFDB_PAM_GSSAPI_SERVICES "pam_gssapi_services" +#define CONFDB_PAM_GSSAPI_CHECK_UPN "pam_gssapi_check_upn" /* SUDO */ #define CONFDB_SUDO_CONF_ENTRY "config/sudo" @@ -435,6 +436,7 @@ struct sss_domain_info { /* List of PAM services that are allowed to authenticate with GSSAPI. */ char **gssapi_services; + char *gssapi_check_upn; /* true | false | NULL */ }; /** diff --git a/src/config/SSSDConfig/sssdoptions.py b/src/config/SSSDConfig/sssdoptions.py index f59fe8d9f2..5da52a9379 100644 --- a/src/config/SSSDConfig/sssdoptions.py +++ b/src/config/SSSDConfig/sssdoptions.py @@ -105,6 +105,7 @@ def __init__(self): 'p11_uri': _('PKCS#11 URI to restrict the selection of devices for Smartcard authentication'), 'pam_initgroups_scheme' : _('When shall the PAM responder force an initgroups request'), 'pam_gssapi_services' : _('List of PAM services that are allowed to authenticate with GSSAPI.'), + 'pam_gssapi_check_upn' : _('Whether to match authenticated UPN with target user'), # [sudo] 'sudo_timed': _('Whether to evaluate the time-based attributes in sudo rules'), diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py index 21fffe1b6b..ea4e4f6c98 100755 --- a/src/config/SSSDConfigTest.py +++ b/src/config/SSSDConfigTest.py @@ -654,7 +654,8 @@ def testListOptions(self): 're_expression', 'cached_auth_timeout', 'auto_private_groups', - 'pam_gssapi_services'] + 'pam_gssapi_services', + 'pam_gssapi_check_upn'] self.assertTrue(type(options) == dict, "Options should be a dictionary") @@ -1032,7 +1033,8 @@ def testRemoveProvider(self): 're_expression', 'cached_auth_timeout', 'auto_private_groups', - 'pam_gssapi_services'] + 'pam_gssapi_services', + 'pam_gssapi_check_upn'] self.assertTrue(type(options) == dict, "Options should be a dictionary") diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini index c6dfd56483..6642c63213 100644 --- a/src/config/cfg_rules.ini +++ b/src/config/cfg_rules.ini @@ -140,6 +140,7 @@ option = p11_wait_for_card_timeout option = p11_uri option = pam_initgroups_scheme option = pam_gssapi_services +option = pam_gssapi_check_upn [rule/allowed_sudo_options] validator = ini_allowed_options @@ -439,6 +440,7 @@ option = full_name_format option = re_expression option = auto_private_groups option = pam_gssapi_services +option = pam_gssapi_check_upn #Entry cache timeouts option = entry_cache_user_timeout @@ -834,6 +836,7 @@ option = ad_site option = use_fully_qualified_names option = auto_private_groups option = pam_gssapi_services +option = pam_gssapi_check_upn [rule/sssd_checks] validator = sssd_checks diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf index f46f3c46d2..d3cad73809 100644 --- a/src/config/etc/sssd.api.conf +++ b/src/config/etc/sssd.api.conf @@ -81,6 +81,7 @@ p11_wait_for_card_timeout = int, None, false p11_uri = str, None, false pam_initgroups_scheme = str, None, false pam_gssapi_services = str, None, false +pam_gssapi_check_upn = bool, None, false [sudo] # sudo service @@ -201,6 +202,7 @@ full_name_format = str, None, false re_expression = str, None, false auto_private_groups = str, None, false pam_gssapi_services = str, None, false +pam_gssapi_check_upn = bool, None, false #Entry cache timeouts entry_cache_user_timeout = int, None, false diff --git a/src/db/sysdb_subdomains.c b/src/db/sysdb_subdomains.c index bfc6df0f5f..03ba121646 100644 --- a/src/db/sysdb_subdomains.c +++ b/src/db/sysdb_subdomains.c @@ -254,6 +254,18 @@ check_subdom_config_file(struct confdb_ctx *confdb, goto done; } + /* allow to set pam_gssapi_check_upn */ + ret = confdb_get_string(confdb, subdomain, sd_conf_path, + CONFDB_PAM_GSSAPI_CHECK_UPN, + subdomain->parent->gssapi_check_upn, + &subdomain->gssapi_check_upn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get %s option for the subdomain: %s\n", + CONFDB_PAM_GSSAPI_CHECK_UPN, subdomain->name); + goto done; + } + ret = EOK; done: talloc_free(tmp_ctx); diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index 08f6d2d1a7..69cd4c3a11 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -1732,6 +1732,31 @@ pam_gssapi_services = sudo, sudo-i </para> </listitem> </varlistentry> + <varlistentry> + <term>pam_gssapi_check_upn</term> + <listitem> + <para> + If True, SSSD will require that the Kerberos user + principal that successfully authenticated through + GSSAPI can be associated with the user who is being + authenticated. Authentication will fail if the check + fails. + </para> + <para> + If False, every user that is able to obtained + required service ticket will be authenticated. + </para> + <para> + Note: This option can also be set per-domain which + overwrites the value in [pam] section. It can also + be set for trusted domain which overwrites the value + in the domain section. + </para> + <para> + Default: True + </para> + </listitem> + </varlistentry> </variablelist> </refsect2> @@ -3807,6 +3832,7 @@ ldap_user_extra_attrs = phone:telephoneNumber <para>ad_site,</para> <para>use_fully_qualified_names</para> <para>pam_gssapi_services</para> + <para>pam_gssapi_check_upn</para> <para> For more details about these options see their individual description in the manual page. diff --git a/src/responder/pam/pamsrv.c b/src/responder/pam/pamsrv.c index 0492569c7f..0db2824ff0 100644 --- a/src/responder/pam/pamsrv.c +++ b/src/responder/pam/pamsrv.c @@ -348,6 +348,15 @@ static int pam_process_init(TALLOC_CTX *mem_ctx, } } + ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_GSSAPI_CHECK_UPN, true, + &pctx->gssapi_check_upn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to read %s [%d]: %s\n", + CONFDB_PAM_GSSAPI_CHECK_UPN, ret, sss_strerror(ret)); + goto done; + } + /* The responder is initialized. Now tell it to the monitor. */ ret = sss_monitor_service_init(rctx, rctx->ev, SSS_BUS_PAM, SSS_PAM_SBUS_SERVICE_NAME, diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h index 730dee2881..bf4dd75b04 100644 --- a/src/responder/pam/pamsrv.h +++ b/src/responder/pam/pamsrv.h @@ -65,6 +65,7 @@ struct pam_ctx { /* List of PAM services that are allowed to authenticate with GSSAPI. */ char **gssapi_services; + bool gssapi_check_upn; }; struct pam_auth_req { From 2cfe91e563d4d61057edb0a4c8396d4f2b6cb605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrez...@redhat.com> Date: Tue, 7 Jul 2020 11:05:37 +0200 Subject: [PATCH 8/8] pam: add pam_sss_gss module for gssapi authentication :feature: New PAM module `pam_sss_gss` for authentication using GSSAPI :packaging: Added `pam_sss_gss.so` PAM module and `pam_sss_gss.8` manual page --- Makefile.am | 33 +- configure.ac | 1 + contrib/sssd.spec.in | 2 + src/external/libgssapi_krb5.m4 | 8 + src/man/Makefile.am | 4 +- src/man/pam_sss_gss.8.xml | 199 ++++++++ src/responder/pam/pamsrv.h | 4 + src/responder/pam/pamsrv_cmd.c | 2 + src/responder/pam/pamsrv_gssapi.c | 792 +++++++++++++++++++++++++++++ src/sss_client/pam_sss_gss.c | 588 +++++++++++++++++++++ src/sss_client/pam_sss_gss.exports | 4 + src/sss_client/sss_cli.h | 8 + src/tests/dlopen-tests.c | 1 + 13 files changed, 1643 insertions(+), 3 deletions(-) create mode 100644 src/external/libgssapi_krb5.m4 create mode 100644 src/man/pam_sss_gss.8.xml create mode 100644 src/responder/pam/pamsrv_gssapi.c create mode 100644 src/sss_client/pam_sss_gss.c create mode 100644 src/sss_client/pam_sss_gss.exports diff --git a/Makefile.am b/Makefile.am index 430b4e8424..1c82776ab3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1585,12 +1585,14 @@ sssd_pam_SOURCES = \ src/responder/pam/pamsrv_cmd.c \ src/responder/pam/pamsrv_p11.c \ src/responder/pam/pamsrv_dp.c \ + src/responder/pam/pamsrv_gssapi.c \ src/responder/pam/pam_prompting_config.c \ src/sss_client/pam_sss_prompt_config.c \ src/responder/pam/pam_helpers.c \ $(SSSD_RESPONDER_OBJ) sssd_pam_CFLAGS = \ $(AM_CFLAGS) \ + $(GSSAPI_KRB5_CFLAGS) \ $(NULL) sssd_pam_LDADD = \ $(LIBADD_DL) \ @@ -1599,6 +1601,7 @@ sssd_pam_LDADD = \ $(SELINUX_LIBS) \ $(PAM_LIBS) \ $(SYSTEMD_DAEMON_LIBS) \ + $(GSSAPI_KRB5_LIBS) \ libsss_certmap.la \ $(SSSD_INTERNAL_LTLIBS) \ libsss_iface.la \ @@ -2710,6 +2713,7 @@ pam_srv_tests_SOURCES = \ src/sss_client/pam_message.c \ src/responder/pam/pamsrv_cmd.c \ src/responder/pam/pamsrv_p11.c \ + src/responder/pam/pamsrv_gssapi.c \ src/responder/pam/pam_helpers.c \ src/responder/pam/pamsrv_dp.c \ src/responder/pam/pam_LOCAL_domain.c \ @@ -2721,6 +2725,7 @@ pam_srv_tests_CFLAGS = \ -I$(abs_builddir)/src \ $(AM_CFLAGS) \ $(CMOCKA_CFLAGS) \ + $(GSSAPI_KRB5_CFLAGS) \ $(NULL) pam_srv_tests_LDFLAGS = \ -Wl,-wrap,sss_packet_get_body \ @@ -2736,6 +2741,7 @@ pam_srv_tests_LDADD = \ $(SSSD_LIBS) \ $(SSSD_INTERNAL_LTLIBS) \ $(SYSTEMD_DAEMON_LIBS) \ + $(GSSAPI_KRB5_LIBS) \ libsss_test_common.la \ libsss_idmap.la \ libsss_certmap.la \ @@ -4149,6 +4155,28 @@ pam_sss_la_LDFLAGS = \ -avoid-version \ -Wl,--version-script,$(srcdir)/src/sss_client/sss_pam.exports +pamlib_LTLIBRARIES += pam_sss_gss.la +pam_sss_gss_la_SOURCES = \ + src/sss_client/pam_sss_gss.c \ + src/sss_client/common.c \ + $(NULL) + +pam_sss_gss_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(GSSAPI_KRB5_CFLAGS) \ + $(NULL) + +pam_sss_gss_la_LIBADD = \ + $(CLIENT_LIBS) \ + $(PAM_LIBS) \ + $(GSSAPI_KRB5_LIBS) \ + $(NULL) + +pam_sss_gss_la_LDFLAGS = \ + -module \ + -avoid-version \ + -Wl,--version-script,$(srcdir)/src/sss_client/pam_sss_gss.exports + if BUILD_SUDO libsss_sudo_la_SOURCES = \ @@ -4187,7 +4215,10 @@ endif dist_noinst_DATA += \ src/sss_client/sss_nss.exports \ - src/sss_client/sss_pam.exports + src/sss_client/sss_pam.exports \ + src/sss_client/pam_sss_gss.exports \ + $(NULL) + if BUILD_SUDO dist_noinst_DATA += src/sss_client/sss_sudo.exports endif diff --git a/configure.ac b/configure.ac index 0d24c4b354..75dc81d533 100644 --- a/configure.ac +++ b/configure.ac @@ -182,6 +182,7 @@ m4_include([src/external/libldb.m4]) m4_include([src/external/libdhash.m4]) m4_include([src/external/libcollection.m4]) m4_include([src/external/libini_config.m4]) +m4_include([src/external/libgssapi_krb5.m4]) m4_include([src/external/pam.m4]) m4_include([src/external/ldap.m4]) m4_include([src/external/libpcre.m4]) diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in index ed81da5356..f7e5ce1332 100644 --- a/contrib/sssd.spec.in +++ b/contrib/sssd.spec.in @@ -1166,6 +1166,7 @@ done %license src/sss_client/COPYING src/sss_client/COPYING.LESSER /%{_lib}/libnss_sss.so.2 /%{_lib}/security/pam_sss.so +/%{_lib}/security/pam_sss_gss.so %{_libdir}/krb5/plugins/libkrb5/sssd_krb5_locator_plugin.so %{_libdir}/krb5/plugins/authdata/sssd_pac_plugin.so %if (0%{?with_cifs_utils_plugin} == 1) @@ -1178,6 +1179,7 @@ done %dir %{_libdir}/%{name}/modules %{_libdir}/%{name}/modules/sssd_krb5_localauth_plugin.so %{_mandir}/man8/pam_sss.8* +%{_mandir}/man8/pam_sss_gss.8* %{_mandir}/man8/sssd_krb5_locator_plugin.8* %files -n libsss_sudo diff --git a/src/external/libgssapi_krb5.m4 b/src/external/libgssapi_krb5.m4 new file mode 100644 index 0000000000..67f3c464d8 --- /dev/null +++ b/src/external/libgssapi_krb5.m4 @@ -0,0 +1,8 @@ +AC_SUBST(GSSAPI_KRB5_CFLAGS) +AC_SUBST(GSSAPI_KRB5_LIBS) + +PKG_CHECK_MODULES(GSSAPI_KRB5, + krb5-gssapi, + , + AC_MSG_ERROR("Please install krb5-devel") + ) diff --git a/src/man/Makefile.am b/src/man/Makefile.am index 351ab80151..c6890a792e 100644 --- a/src/man/Makefile.am +++ b/src/man/Makefile.am @@ -69,8 +69,8 @@ man_MANS = \ sssd.8 sssd.conf.5 sssd-ldap.5 sssd-ldap-attributes.5 \ sssd-krb5.5 sssd-simple.5 sss-certmap.5 \ sssd_krb5_locator_plugin.8 \ - pam_sss.8 sss_obfuscate.8 sss_cache.8 sss_debuglevel.8 sss_seed.8 \ - sss_override.8 idmap_sss.8 sssctl.8 sssd-session-recording.5 \ + pam_sss.8 pam_sss_gss.8 sss_obfuscate.8 sss_cache.8 sss_debuglevel.8 \ + sss_seed.8 sss_override.8 idmap_sss.8 sssctl.8 sssd-session-recording.5 \ $(NULL) if BUILD_LOCAL_PROVIDER diff --git a/src/man/pam_sss_gss.8.xml b/src/man/pam_sss_gss.8.xml new file mode 100644 index 0000000000..d4bb705e3b --- /dev/null +++ b/src/man/pam_sss_gss.8.xml @@ -0,0 +1,199 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN" +"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> +<reference> +<title>SSSD Manual pages</title> +<refentry> + <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" + href="include/upstream.xml" /> + + <refmeta> + <refentrytitle>pam_sss_gss</refentrytitle> + <manvolnum>8</manvolnum> + </refmeta> + + <refnamediv id='name'> + <refname>pam_sss_gss</refname> + <refpurpose>PAM module for SSSD GSSAPI authentication</refpurpose> + </refnamediv> + + <refsynopsisdiv id='synopsis'> + <cmdsynopsis> + <command>pam_sss_gss.so</command> + <arg choice='opt'> + <replaceable>debug</replaceable> + </arg> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1 id='description'> + <title>DESCRIPTION</title> + <para> + <command>pam_sss_gss.so</command> authenticates user + over GSSAPI in cooperation with SSSD. + </para> + <para> + This module will try to authenticate the user using the GSSAPI + hostbased service name host@hostname which translates to + host/hostname@REALM Kerberos principal. The + <emphasis>REALM</emphasis> part of the Kerberos principal name is + derived by Kerberos internal mechanisms and it can be set explicitly + in configuration of [domain_realm] section in /etc/krb5.conf. + </para> + <para> + SSSD is used to provide desired service name and to validate the + user's credentials using GSSAPI calls. If the service ticket is + already present in the Kerberos credentials cache or if user's + ticket granting ticket can be used to get the correct service ticket + then the user will be authenticated. + <para> + If <option>pam_gssapi_check_upn</option> is True (default) then SSSD + requires that the credentials used to obtain the service tickets can + be associated with the user. This means that the principal that owns + the Kerberos credentials must match with the user principal name as + defined in LDAP. + </para> + <para> + To enable GSSAPI authentication in SSSD, set + <option>pam_gssapi_services</option> option in [pam] or domain + section of sssd.conf. The service credentials need to be stored + in SSSD's keytab (it is already present if you use ipa or ad + provider). The keytab location can be set with + <option>krb5_keytab</option> option. See + <citerefentry> + <refentrytitle>sssd.conf</refentrytitle> + <manvolnum>5</manvolnum> + </citerefentry> and + <citerefentry> + <refentrytitle>sssd-krb5</refentrytitle> + <manvolnum>5</manvolnum> + </citerefentry> for more details on these options. + </para> + </refsect1> + + <refsect1 id='options'> + <title>OPTIONS</title> + <variablelist remap='IP'> + <varlistentry> + <term> + <option>debug</option> + </term> + <listitem> + <para>Print debugging information.</para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1 id='module_types_provides'> + <title>MODULE TYPES PROVIDED</title> + <para>Only the <option>auth</option> module type is provided.</para> + </refsect1> + + <refsect1 id="return_values"> + <title>RETURN VALUES</title> + <variablelist> + <varlistentry> + <term>PAM_SUCCESS</term> + <listitem> + <para> + The PAM operation finished successfully. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term>PAM_USER_UNKNOWN</term> + <listitem> + <para> + The user is not known to the authentication service or + the GSSAPI authentication is not supported. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term>PAM_AUTH_ERR</term> + <listitem> + <para> + Authentication failure. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term>PAM_AUTHINFO_UNAVAIL</term> + <listitem> + <para> + Unable to access the authentication information. + This might be due to a network or hardware failure. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term>PAM_SYSTEM_ERR</term> + <listitem> + <para> + A system error occurred. The SSSD log files may contain + additional information about the error. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1 id='examples'> + <title>EXAMPLES</title> + <para> + The main use case is to provide password-less authentication in + sudo but without the need to disable authentication completely. + To achieve this, first enable GSSAPI authentication for sudo in + sssd.conf: + </para> + <programlisting> +[domain/MYDOMAIN] +pam_gssapi_services = sudo, sudo-i + </programlisting> + <para> + And then enable the module in desired PAM stack + (e.g. /etc/pam.d/sudo and /etc/pam.d/sudo-i). + </para> + <programlisting> +... +auth sufficient pam_sss_gss.so +... + </programlisting> + </refsect1> + + <refsect1 id='troubleshooting'> + <title>TROUBLESHOOTING</title> + <para> + SSSD logs, pam_sss_gss debug output and syslog may contain helpful + information about the error. Here are some common issues: + </para> + <para> + 1. I have KRB5CCNAME environment variable set and the authentication + does not work: Depending on your sudo version, it is possible that + sudo does not pass this variable to the PAM environment. Try adding + KRB5CCNAME to <option>env_keep</option> in /etc/sudoers or in your + LDAP sudo rules default options. + </para> + <para> + 2. Authentication does not work and syslog contains "Server not + found in Kerberos database": Kerberos is probably not able to + resolve correct realm for the service ticket based on the hostname. + Try adding the hostname directly to + <option>[domain_realm]</option> in /etc/krb5.conf like so: + </para> + <para> + 2. Authentication does not work and syslog contains "Can't find + client principal $NAME in cache collection": Try to kinit with the + required principal name. + </para> + <programlisting> +[domain_realm] +.myhostname = MYREALM + </programlisting> + </refsect1> + + <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/seealso.xml" /> + +</refentry> +</reference> diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h index bf4dd75b04..3553296918 100644 --- a/src/responder/pam/pamsrv.h +++ b/src/responder/pam/pamsrv.h @@ -145,4 +145,8 @@ errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd); enum pam_initgroups_scheme pam_initgroups_string_to_enum(const char *str); const char *pam_initgroup_enum_to_string(enum pam_initgroups_scheme scheme); + +int pam_cmd_gssapi_init(struct cli_ctx *cli_ctx); +int pam_cmd_gssapi_sec_ctx(struct cli_ctx *cctx); + #endif /* __PAMSRV_H__ */ diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c index acbfc0c39f..9ea488be4f 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -2401,6 +2401,8 @@ struct sss_cmd_table *get_pam_cmds(void) {SSS_PAM_CHAUTHTOK, pam_cmd_chauthtok}, {SSS_PAM_CHAUTHTOK_PRELIM, pam_cmd_chauthtok_prelim}, {SSS_PAM_PREAUTH, pam_cmd_preauth}, + {SSS_GSSAPI_INIT, pam_cmd_gssapi_init}, + {SSS_GSSAPI_SEC_CTX, pam_cmd_gssapi_sec_ctx}, {SSS_CLI_NULL, NULL} }; diff --git a/src/responder/pam/pamsrv_gssapi.c b/src/responder/pam/pamsrv_gssapi.c new file mode 100644 index 0000000000..099675e1c7 --- /dev/null +++ b/src/responder/pam/pamsrv_gssapi.c @@ -0,0 +1,792 @@ +/* + Authors: + Pavel Březina <pbrez...@redhat.com> + + Copyright (C) 2020 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <errno.h> +#include <gssapi.h> +#include <gssapi/gssapi_ext.h> +#include <gssapi/gssapi_krb5.h> +#include <stdint.h> +#include <stdlib.h> +#include <talloc.h> +#include <ldb.h> + +#include "confdb/confdb.h" +#include "db/sysdb.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/pam/pamsrv.h" +#include "sss_client/sss_cli.h" +#include "util/util.h" +#include "util/sss_utf8.h" + +static errno_t read_str(size_t body_len, + uint8_t *body, + size_t *pctr, + const char **_str) +{ + size_t i; + + for (i = *pctr; i < body_len && body[i] != 0; i++) { + /* counting */ + } + + if (i >= body_len) { + return EINVAL; + } + + if (!sss_utf8_check(&body[*pctr], i - *pctr)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Body is not UTF-8 string!\n"); + return EINVAL; + } + + *_str = (const char *)&body[*pctr]; + *pctr = i + 1; + + return EOK; +} + +static bool pam_gssapi_should_check_upn(struct pam_ctx *pam_ctx, + struct sss_domain_info *domain) +{ + if (domain->gssapi_check_upn != NULL) { + if (strcasecmp(domain->gssapi_check_upn, "true") == 0) { + return true; + } + + if (strcasecmp(domain->gssapi_check_upn, "false") == 0) { + return false; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "Invalid value for %s: %s\n", + CONFDB_PAM_GSSAPI_CHECK_UPN, domain->gssapi_check_upn); + return false; + } + + return pam_ctx->gssapi_check_upn; +} + +static bool pam_gssapi_allowed(struct pam_ctx *pam_ctx, + struct sss_domain_info *domain, + const char *service) +{ + char **list = pam_ctx->gssapi_services; + + if (domain->gssapi_services != NULL) { + list = domain->gssapi_services; + } + + if (strcmp(service, "-") == 0) { + /* Dash is used as a "not set" value to allow to explicitly disable + * gssapi auth for specific domain. Disallow this service to be safe. + */ + DEBUG(SSSDBG_TRACE_FUNC, "Dash - was used as a PAM service name. " + "GSSAPI authentication is not allowed.\n"); + return false; + } + + return string_in_list(service, list, true); +} + +static char *pam_gssapi_target(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain) +{ + return talloc_asprintf(mem_ctx, "host@%s", domain->hostname); +} + +static const char *pam_gssapi_get_upn(struct cache_req_result *result) +{ + if (result->count == 0) { + return NULL; + } + + /* Canonical UPN should be available if the user has kinited through SSSD. + * Use it as a hint for GSSAPI. Default to empty string so it may be + * more easily transffered over the wire. */ + return ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_CANONICAL_UPN, ""); +} + +static const char *pam_gssapi_get_name(struct cache_req_result *result) +{ + if (result->count == 0) { + return NULL; + } + + /* Return username known to SSSD to make sure we authenticated as the same + * user after GSSAPI handshake. */ + return ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_NAME, NULL); +} + +static errno_t pam_gssapi_init_parse(struct cli_protocol *pctx, + const char **_service, + const char **_username) +{ + size_t body_len; + size_t pctr = 0; + uint8_t *body; + errno_t ret; + + sss_packet_get_body(pctx->creq->in, &body, &body_len); + if (body == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid input\n"); + return EINVAL; + } + + ret = read_str(body_len, body, &pctr, _service); + if (ret != EOK) { + return ret; + } + + ret = read_str(body_len, body, &pctr, _username); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +static errno_t pam_gssapi_init_reply(struct cli_protocol *pctx, + const char *domain, + const char *target, + const char *upn, + const char *username) +{ + size_t reply_len; + size_t body_len; + size_t pctr; + uint8_t *body; + errno_t ret; + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create a new packet [%d]; %s\n", + ret, sss_strerror(ret)); + return ret; + } + + reply_len = strlen(username) + 1; + reply_len += strlen(domain) + 1; + reply_len += strlen(target) + 1; + reply_len += strlen(upn) + 1; + + ret = sss_packet_grow(pctx->creq->out, reply_len); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create response: %s\n", + sss_strerror(ret)); + return ret; + } + + sss_packet_get_body(pctx->creq->out, &body, &body_len); + + pctr = 0; + SAFEALIGN_SETMEM_STRING(&body[pctr], username, strlen(username) + 1, &pctr); + SAFEALIGN_SETMEM_STRING(&body[pctr], domain, strlen(domain) + 1, &pctr); + SAFEALIGN_SETMEM_STRING(&body[pctr], target, strlen(target) + 1, &pctr); + SAFEALIGN_SETMEM_STRING(&body[pctr], upn, strlen(upn) + 1, &pctr); + + return EOK; +} + +struct gssapi_init_state { + struct cli_ctx *cli_ctx; + const char *username; + const char *service; +}; + +static void pam_cmd_gssapi_init_done(struct tevent_req *req); + +int pam_cmd_gssapi_init(struct cli_ctx *cli_ctx) +{ + struct gssapi_init_state *state; + struct cli_protocol *pctx; + struct tevent_req *req; + const char *username; + const char *service; + const char *attrs[] = { SYSDB_NAME, SYSDB_CANONICAL_UPN, NULL }; + errno_t ret; + + state = talloc_zero(cli_ctx, struct gssapi_init_state); + if (state == NULL) { + return ENOMEM; + } + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + ret = pam_gssapi_init_parse(pctx, &service, &username); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse input [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + state->cli_ctx = cli_ctx; + state->service = service; + state->username = username; + + DEBUG(SSSDBG_TRACE_ALL, + "Requesting GSSAPI authentication of [%s] in service [%s]\n", + username, service); + + req = cache_req_user_by_name_attrs_send(cli_ctx, cli_ctx->ev, cli_ctx->rctx, + cli_ctx->rctx->ncache, 0, + NULL, username, attrs); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_cmd_gssapi_init_done, state); + + ret = EOK; + +done: + if (ret != EOK) { + sss_cmd_send_error(cli_ctx, ret); + sss_cmd_done(cli_ctx, NULL); + } + + return EOK; +} + +static void pam_cmd_gssapi_init_done(struct tevent_req *req) +{ + struct gssapi_init_state *state; + struct cache_req_result *result; + struct cli_protocol *pctx; + struct pam_ctx *pam_ctx; + const char *username; + const char *upn; + char *target; + errno_t ret; + + state = tevent_req_callback_data(req, struct gssapi_init_state); + pctx = talloc_get_type(state->cli_ctx->protocol_ctx, struct cli_protocol); + pam_ctx = talloc_get_type(state->cli_ctx->rctx->pvt_ctx, struct pam_ctx); + + ret = cache_req_user_by_name_attrs_recv(state, req, &result); + talloc_zfree(req); + if (ret == ENOENT || ret == ERR_DOMAIN_NOT_FOUND) { + ret = ENOENT; + goto done; + } else if (ret != EOK) { + goto done; + } + + if (!pam_gssapi_allowed(pam_ctx, result->domain, state->service)) { + ret = ENOTSUP; + goto done; + } + + username = pam_gssapi_get_name(result); + if (username == NULL) { + /* User with no name? */ + ret = ERR_INTERNAL; + goto done; + } + + upn = pam_gssapi_get_upn(result); + if (upn == NULL) { + /* UPN hint may be an empty string, but not NULL. */ + ret = ERR_INTERNAL; + goto done; + } + + target = pam_gssapi_target(state, result->domain); + if (target == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Trying GSSAPI auth: User[%s], Domain[%s], UPN[%s], Target[%s]\n", + username, result->domain->name, upn, target); + + ret = pam_gssapi_init_reply(pctx, result->domain->name, target, upn, + username); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to construct reply [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + DEBUG(SSSDBG_TRACE_FUNC, "Returning [%d]: %s\n", ret, sss_strerror(ret)); + + if (ret == EOK) { + sss_packet_set_error(pctx->creq->out, EOK); + } else { + sss_cmd_send_error(state->cli_ctx, ret); + } + + sss_cmd_done(state->cli_ctx, state); +} + +static void gssapi_log_status(int type, OM_uint32 status_code) +{ + OM_uint32 message_context = 0; + gss_buffer_desc buf; + OM_uint32 minor; + + do { + gss_display_status(&minor, status_code, type, GSS_C_NO_OID, + &message_context, &buf); + DEBUG(SSSDBG_OP_FAILURE, "GSSAPI: %.*s\n", (int)buf.length, + (char *)buf.value); + gss_release_buffer(&minor, &buf); + } while (message_context != 0); +} + +static void gssapi_log_error(OM_uint32 major, OM_uint32 minor) +{ + gssapi_log_status(GSS_C_GSS_CODE, major); + gssapi_log_status(GSS_C_MECH_CODE, minor); +} + +static char *gssapi_get_name(TALLOC_CTX *mem_ctx, gss_name_t gss_name) +{ + gss_buffer_desc buf; + OM_uint32 major; + OM_uint32 minor; + char *exported; + + major = gss_display_name(&minor, gss_name, &buf, NULL); + if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to export name\n"); + return NULL; + } + + exported = talloc_strndup(mem_ctx, buf.value, buf.length); + gss_release_buffer(&minor, &buf); + + if (exported == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + return NULL; + } + + return exported; +} + +struct gssapi_state { + struct cli_ctx *cli_ctx; + struct sss_domain_info *domain; + const char *username; + + char *authenticated_upn; + bool established; + gss_ctx_id_t ctx; +}; + +int gssapi_state_destructor(struct gssapi_state *state) +{ + OM_uint32 minor; + + gss_delete_sec_context(&minor, &state->ctx, NULL); + + return 0; +} + +static struct gssapi_state *gssapi_get_state(struct cli_ctx *cli_ctx, + const char *username, + struct sss_domain_info *domain) +{ + struct gssapi_state *state; + + state = talloc_get_type(cli_ctx->state_ctx, struct gssapi_state); + if (state != NULL) { + return state; + } + + state = talloc_zero(cli_ctx, struct gssapi_state); + if (state == NULL) { + return NULL; + } + + state->username = talloc_strdup(state, username); + if (state == NULL) { + talloc_free(state); + return NULL; + } + + state->domain = domain; + state->cli_ctx = cli_ctx; + state->ctx = GSS_C_NO_CONTEXT; + talloc_set_destructor(state, gssapi_state_destructor); + + cli_ctx->state_ctx = state; + + return state; +} + +static errno_t gssapi_get_creds(const char *keytab, + const char *target, + gss_cred_id_t *_creds) +{ + gss_key_value_set_desc cstore = {0, NULL}; + gss_key_value_element_desc el; + gss_buffer_desc name_buf; + gss_name_t name = GSS_C_NO_NAME; + OM_uint32 major; + OM_uint32 minor; + errno_t ret; + + if (keytab != NULL) { + el.key = "keytab"; + el.value = keytab; + cstore.count = 1; + cstore.elements = ⪙ + } + + if (target != NULL) { + name_buf.value = discard_const(target); + name_buf.length = strlen(target); + + major = gss_import_name(&minor, &name_buf, GSS_C_NT_HOSTBASED_SERVICE, + &name); + if (GSS_ERROR(major)) { + DEBUG(SSSDBG_OP_FAILURE, "Could not import name [%s] " + "[maj:0x%x, min:0x%x]\n", target, major, minor); + + gssapi_log_error(major, minor); + + ret = EIO; + goto done; + } + } + + major = gss_acquire_cred_from(&minor, name, GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, GSS_C_ACCEPT, &cstore, + _creds, NULL, NULL); + if (GSS_ERROR(major)) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to read credentials from [%s] " + "[maj:0x%x, min:0x%x]\n", keytab ? keytab : "default", + major, minor); + + gssapi_log_error(major, minor); + + ret = EIO; + goto done; + } + + ret = EOK; + +done: + gss_release_name(&minor, &name); + + return ret; +} + +static errno_t +gssapi_handshake(struct gssapi_state *state, + struct cli_protocol *pctx, + const char *keytab, + const char *target, + uint8_t *gss_data, + size_t gss_data_len) +{ + OM_uint32 flags = GSS_C_MUTUAL_FLAG; + gss_buffer_desc output = GSS_C_EMPTY_BUFFER; + gss_buffer_desc input; + gss_name_t client_name; + gss_cred_id_t creds; + OM_uint32 ret_flags; + gss_OID mech_type; + OM_uint32 major; + OM_uint32 minor; + errno_t ret; + + input.value = gss_data; + input.length = gss_data_len; + + ret = gssapi_get_creds(keytab, target, &creds); + if (ret != EOK) { + return ret; + } + + major = gss_accept_sec_context(&minor, &state->ctx, creds, + &input, NULL, &client_name, &mech_type, + &output, &ret_flags, NULL, NULL); + if (major == GSS_S_CONTINUE_NEEDED || output.length > 0) { + ret = sss_packet_set_body(pctx->creq->out, output.value, output.length); + if (ret != EOK) { + goto done; + } + } + + if (GSS_ERROR(major)) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to establish GSS context " + "[maj:0x%x, min:0x%x]\n", major, minor); + + gssapi_log_error(major, minor); + ret = EIO; + goto done; + } + + if (major == GSS_S_CONTINUE_NEEDED) { + ret = EOK; + goto done; + } else if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to establish GSS context, unexpected " + "value: 0x%x\n", major); + ret = EIO; + goto done; + } + + if ((ret_flags & flags) != flags) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Negotiated context does not support requested flags\n"); + state->established = false; + ret = EIO; + goto done; + } + + state->authenticated_upn = gssapi_get_name(state, client_name); + if (state->authenticated_upn == NULL) { + state->established = false; + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Security context established with [%s]\n", + state->authenticated_upn); + + state->established = true; + ret = EOK; + +done: + gss_release_cred(&minor, &creds); + gss_release_buffer(&minor, &output); + + return ret; +} + +static errno_t pam_cmd_gssapi_sec_ctx_parse(struct cli_protocol *pctx, + const char **_pam_service, + const char **_username, + const char **_domain, + uint8_t **_gss_data, + size_t *_gss_data_len) +{ + size_t body_len; + uint8_t *body; + size_t pctr; + errno_t ret; + + sss_packet_get_body(pctx->creq->in, &body, &body_len); + if (body == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid input\n"); + return EINVAL; + } + + pctr = 0; + ret = read_str(body_len, body, &pctr, _pam_service); + if (ret != EOK) { + return ret; + } + + ret = read_str(body_len, body, &pctr, _username); + if (ret != EOK) { + return ret; + } + + ret = read_str(body_len, body, &pctr, _domain); + if (ret != EOK) { + return ret; + } + + *_gss_data = (pctr == body_len) ? NULL : body + pctr; + *_gss_data_len = body_len - pctr; + + return EOK; +} + +static void pam_cmd_gssapi_sec_ctx_done(struct tevent_req *req); + +int +pam_cmd_gssapi_sec_ctx(struct cli_ctx *cli_ctx) +{ + struct sss_domain_info *domain; + struct gssapi_state *state; + struct cli_protocol *pctx; + struct pam_ctx *pam_ctx; + struct tevent_req *req; + const char *pam_service; + const char *domain_name; + const char *username; + char *target; + size_t gss_data_len; + uint8_t *gss_data; + errno_t ret; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + pam_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct pam_ctx); + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create a new packet [%d]; %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = pam_cmd_gssapi_sec_ctx_parse(pctx, &pam_service, &username, + &domain_name, &gss_data, &gss_data_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to parse input data [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + domain = find_domain_by_name(cli_ctx->rctx->domains, domain_name, false); + if (domain == NULL) { + ret = EINVAL; + goto done; + } + + if (!pam_gssapi_allowed(pam_ctx, domain, pam_service)) { + ret = ENOTSUP; + goto done; + } + + target = pam_gssapi_target(cli_ctx, domain); + if (target == NULL) { + ret = ENOMEM; + goto done; + } + + state = gssapi_get_state(cli_ctx, username, domain); + if (state == NULL) { + ret = ENOMEM; + goto done; + } + + if (strcmp(username, state->username) != 0 || state->domain != domain) { + /* This should not happen, but be paranoid. */ + DEBUG(SSSDBG_CRIT_FAILURE, "Different input user then who initiated " + "the request!\n"); + ret = EPERM; + goto done; + } + + if (state->established) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Security context is already established\n"); + ret = EPERM; + goto done; + } + + ret = gssapi_handshake(state, pctx, domain->krb5_keytab, target, gss_data, + gss_data_len); + if (ret != EOK || !state->established) { + goto done; + } + + if (!pam_gssapi_should_check_upn(pam_ctx, domain)) { + /* We are done. */ + goto done; + } + + /* We have established the security context. Now check the the principal + * used for authorization can be associated with the user. We have + * already done initgroups before so we could just search the sysdb + * directly, but use cache req to avoid looking up a possible expired + * object if the handshake took longer. */ + + DEBUG(SSSDBG_TRACE_FUNC, "Checking that target user matches UPN\n"); + + req = cache_req_user_by_upn_send(cli_ctx, cli_ctx->ev, cli_ctx->rctx, + cli_ctx->rctx->ncache, 0, DOM_TYPE_POSIX, + domain->name, state->authenticated_upn); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_cmd_gssapi_sec_ctx_done, state); + + return EOK; + +done: + DEBUG(SSSDBG_TRACE_FUNC, "Returning [%d]: %s\n", ret, sss_strerror(ret)); + + if (ret == EOK) { + sss_packet_set_error(pctx->creq->out, EOK); + } else { + sss_cmd_send_error(cli_ctx, ret); + } + + sss_cmd_done(cli_ctx, NULL); + return EOK; +} + +static void pam_cmd_gssapi_sec_ctx_done(struct tevent_req *req) +{ + struct gssapi_state *state; + struct cache_req_result *result; + struct cli_protocol *pctx; + const char *name; + errno_t ret; + + state = tevent_req_callback_data(req, struct gssapi_state); + pctx = talloc_get_type(state->cli_ctx->protocol_ctx, struct cli_protocol); + + ret = cache_req_user_by_upn_recv(state, req, &result); + talloc_zfree(req); + if (ret == ENOENT || ret == ERR_DOMAIN_NOT_FOUND) { + /* We have no match. Return failure. */ + DEBUG(SSSDBG_TRACE_FUNC, "User with UPN [%s] was not found. " + "Authentication failed.\n", state->authenticated_upn); + ret = EACCES; + goto done; + } else if (ret != EOK) { + /* Generic error. Return failure. */ + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup user by UPN [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Check that username match. */ + name = ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_NAME, NULL); + if (name == NULL || strcmp(name, state->username) != 0) { + DEBUG(SSSDBG_TRACE_FUNC, "UPN [%s] does not match target user [%s]. " + "Authentication failed.\n", state->authenticated_upn, + state->username); + ret = EACCES; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "User [%s] match UPN [%s]. Authentication was " + "successful.\n", state->username, state->authenticated_upn); + + ret = EOK; + +done: + DEBUG(SSSDBG_TRACE_FUNC, "Returning [%d]: %s\n", ret, sss_strerror(ret)); + + if (ret == EOK) { + sss_packet_set_error(pctx->creq->out, EOK); + } else { + sss_cmd_send_error(state->cli_ctx, ret); + } + + sss_cmd_done(state->cli_ctx, state); +} diff --git a/src/sss_client/pam_sss_gss.c b/src/sss_client/pam_sss_gss.c new file mode 100644 index 0000000000..cd38db7da7 --- /dev/null +++ b/src/sss_client/pam_sss_gss.c @@ -0,0 +1,588 @@ +/* + Authors: + Pavel Březina <pbrez...@redhat.com> + + Copyright (C) 2020 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdlib.h> +#include <stddef.h> +#include <stdbool.h> +#include <security/pam_modules.h> +#include <security/pam_ext.h> +#include <gssapi.h> +#include <gssapi/gssapi_ext.h> +#include <gssapi/gssapi_generic.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/syslog.h> +#include <unistd.h> + +#include "util/sss_format.h" +#include "sss_client/sss_cli.h" + +bool debug_enabled; + +#define TRACE(pamh, fmt, ...) do { \ + if (debug_enabled) { \ + pam_info(pamh, "pam_sss_gss: " fmt, ## __VA_ARGS__); \ + } \ +} while (0) + +#define ERROR(pamh, fmt, ...) do { \ + if (debug_enabled) { \ + pam_error(pamh, "pam_sss_gss: " fmt, ## __VA_ARGS__); \ + pam_syslog(pamh, LOG_ERR, fmt, ## __VA_ARGS__); \ + } \ +} while (0) + +static bool switch_euid(pam_handle_t *pamh, uid_t current, uid_t desired) +{ + int ret; + + TRACE(pamh, "Switching euid from %" SPRIuid " to %" SPRIuid, current, + desired); + + if (current == desired) { + return true; + } + + ret = seteuid(desired); + if (ret != 0) { + ERROR(pamh, "Unable to set euid to %" SPRIuid, desired); + return false; + } + + return true; +} + +static const char *get_item_as_string(pam_handle_t *pamh, int item) +{ + const char *str; + int ret; + + ret = pam_get_item(pamh, item, (void *)&str); + if (ret != PAM_SUCCESS || str == NULL || str[0] == '\0') { + return NULL; + } + + return str; +} + +static errno_t string_to_gss_name(pam_handle_t *pamh, + const char *target, + gss_OID type, + gss_name_t *_name) +{ + gss_buffer_desc name_buf; + OM_uint32 major; + OM_uint32 minor; + + name_buf.value = (void *)(uintptr_t)target; + name_buf.length = strlen(target); + major = gss_import_name(&minor, &name_buf, type, _name); + if (GSS_ERROR(major)) { + ERROR(pamh, "Could not convert target to GSS name"); + return EIO; + } + + return EOK; +} + +static void gssapi_log_status(pam_handle_t *pamh, + int type, + OM_uint32 status_code) +{ + gss_buffer_desc buf; + OM_uint32 message_context; + OM_uint32 minor; + + message_context = 0; + do { + gss_display_status(&minor, status_code, type, GSS_C_NO_OID, + &message_context, &buf); + ERROR(pamh, "GSSAPI: %.*s", (int)buf.length, (char *)buf.value); + gss_release_buffer(&minor, &buf); + } while (message_context != 0); +} + +static void gssapi_log_error(pam_handle_t *pamh, + OM_uint32 major, + OM_uint32 minor) +{ + gssapi_log_status(pamh, GSS_C_GSS_CODE, major); + gssapi_log_status(pamh, GSS_C_MECH_CODE, minor); +} + +static errno_t gssapi_get_creds(pam_handle_t *pamh, + const char *ccache, + const char *target, + const char *upn, + gss_cred_id_t *_creds) +{ + gss_key_value_set_desc cstore = {0, NULL}; + gss_key_value_element_desc el; + gss_name_t name = GSS_C_NO_NAME; + OM_uint32 major; + OM_uint32 minor; + errno_t ret; + + if (upn != NULL && upn[0] != '\0') { + TRACE(pamh, "Acquiring credentials for principal [%s]", upn); + ret = string_to_gss_name(pamh, upn, GSS_C_NT_USER_NAME, &name); + if (ret != EOK) { + goto done; + } + } else { + TRACE(pamh, "Acquiring credentials, principal name will be derived"); + } + + if (ccache != NULL) { + el.key = "ccache"; + el.value = ccache; + cstore.count = 1; + cstore.elements = ⪙ + } + + major = gss_acquire_cred_from(&minor, name, GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, GSS_C_INITIATE, + &cstore, _creds, NULL, NULL); + if (GSS_ERROR(major)) { + /* TODO: Do not hardcode the error code. */ + if (minor == 2529639053 && name != GSS_C_NO_NAME) { + /* Hint principal was not found. Try again and let GSSAPI choose. */ + TRACE(pamh, "Principal [%s] was not found in ccache", upn); + ret = gssapi_get_creds(pamh, ccache, target, NULL, _creds); + goto done; + } else { + ERROR(pamh, "Unable to read credentials from [%s] " + "[maj:0x%x, min:0x%x]", ccache == NULL ? "default" : ccache, + major, minor); + + gssapi_log_error(pamh, major, minor); + ret = EIO; + goto done; + } + } + + ret = EOK; + +done: + gss_release_name(&minor, &name); + + return ret; +} + +static errno_t sssd_gssapi_init_send(pam_handle_t *pamh, + const char *pam_service, + const char *pam_user, + uint8_t **_reply, + size_t *_reply_len) +{ + struct sss_cli_req_data req_data; + size_t service_len; + size_t user_len; + uint8_t *data; + errno_t ret; + int ret_errno; + + if (pam_service == NULL || pam_user == NULL) { + return EINVAL; + } + + service_len = strlen(pam_service) + 1; + user_len = strlen(pam_user) + 1; + + req_data.len = (service_len + user_len) * sizeof(char); + data = (uint8_t*)malloc(req_data.len); + if (data == NULL) { + return ENOMEM; + } + + memcpy(data, pam_service, service_len); + memcpy(data + service_len, pam_user, user_len); + + req_data.data = data; + + ret = sss_pam_make_request(SSS_GSSAPI_INIT, &req_data, _reply, _reply_len, + &ret_errno); + free(data); + if (ret != PAM_SUCCESS) { + if (ret_errno == ENOTSUP) { + TRACE(pamh, "GSSAPI authentication is not supported for user %s " + "and service %s", pam_user, pam_service); + return ret_errno; + } + + ERROR(pamh, "Communication error [%d, %d]: %s; %s", ret, ret_errno, + pam_strerror(pamh, ret), strerror(ret_errno)); + + return (ret_errno != EOK) ? ret_errno : EIO; + } + + return ret_errno; +} + +static errno_t sssd_gssapi_init_recv(uint8_t *reply, + size_t reply_len, + char **_username, + char **_domain, + char **_target, + char **_upn) +{ + char *username = NULL; + char *domain = NULL; + char *target = NULL; + char *upn = NULL; + const char *buf; + size_t pctr = 0; + size_t dlen; + errno_t ret; + + username = malloc(reply_len * sizeof(char)); + domain = malloc(reply_len * sizeof(char)); + target = malloc(reply_len * sizeof(char)); + upn = malloc(reply_len * sizeof(char)); + if (username == NULL || domain == NULL || target == NULL || upn == NULL) { + return ENOMEM; + } + + buf = (const char*)reply; + + dlen = reply_len; + ret = sss_readrep_copy_string(buf, &pctr, &reply_len, &dlen, &username, + NULL); + if (ret != EOK) { + goto done; + } + + dlen = reply_len; + ret = sss_readrep_copy_string(buf, &pctr, &reply_len, &dlen, &domain, NULL); + if (ret != EOK) { + goto done; + } + + dlen = reply_len; + ret = sss_readrep_copy_string(buf, &pctr, &reply_len, &dlen, &target, NULL); + if (ret != EOK) { + goto done; + } + + dlen = reply_len; + ret = sss_readrep_copy_string(buf, &pctr, &reply_len, &dlen, &upn, NULL); + if (ret != EOK) { + goto done; + } + + *_username = username; + *_domain = domain; + *_target = target; + *_upn = upn; + +done: + if (ret != EOK) { + free(username); + free(domain); + free(target); + free(upn); + } + + return ret; +} + +static errno_t sssd_gssapi_init(pam_handle_t *pamh, + const char *pam_service, + const char *pam_user, + char **_username, + char **_domain, + char **_target, + char **_upn) +{ + size_t reply_len; + uint8_t *reply; + errno_t ret; + + ret = sssd_gssapi_init_send(pamh, pam_service, pam_user, &reply, + &reply_len); + if (ret != EOK) { + return ret; + } + + ret = sssd_gssapi_init_recv(reply, reply_len, _username, _domain, _target, + _upn); + free(reply); + + return ret; +} + +static errno_t sssd_establish_sec_ctx_send(pam_handle_t *pamh, + const char *pam_service, + const char *username, + const char *domain, + const void *gss_data, + size_t gss_data_len, + void **_reply, + size_t *_reply_len) +{ + struct sss_cli_req_data req_data; + size_t username_len; + size_t service_len; + size_t domain_len; + uint8_t *data; + int ret_errno; + int ret; + + service_len = strlen(pam_service) + 1; + username_len = strlen(username) + 1; + domain_len = strlen(domain) + 1; + + req_data.len = (service_len + username_len + domain_len) * sizeof(char) + + gss_data_len; + data = malloc(req_data.len); + if (data == NULL) { + return ENOMEM; + } + + memcpy(data, pam_service, service_len); + memcpy(data + service_len, username, username_len); + memcpy(data + service_len + username_len, domain, domain_len); + memcpy(data + service_len + username_len + domain_len, gss_data, + gss_data_len); + + req_data.data = data; + ret = sss_pam_make_request(SSS_GSSAPI_SEC_CTX, &req_data, (uint8_t**)_reply, + _reply_len, &ret_errno); + free(data); + if (ret != PAM_SUCCESS) { + /* ENOTSUP should not happend here so let's keep it as generic error. */ + ERROR(pamh, "Communication error [%d, %d]: %s; %s", ret, ret_errno, + pam_strerror(pamh, ret), strerror(ret_errno)); + + return (ret_errno != EOK) ? ret_errno : EIO; + } + + return ret_errno; +} + +static int sssd_establish_sec_ctx(pam_handle_t *pamh, + const char *ccache, + const char *pam_service, + const char *username, + const char *domain, + const char *target, + const char *upn) +{ + gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output = GSS_C_EMPTY_BUFFER; + OM_uint32 flags = GSS_C_MUTUAL_FLAG; + gss_name_t gss_name; + gss_cred_id_t creds; + OM_uint32 ret_flags; + OM_uint32 major; + OM_uint32 minor; + int ret; + + ret = gssapi_get_creds(pamh, ccache, target, upn, &creds); + if (ret != EOK) { + return ret; + } + + ret = string_to_gss_name(pamh, target, GSS_C_NT_HOSTBASED_SERVICE, &gss_name); + if (ret != 0) { + return ret; + } + + do { + major = gss_init_sec_context(&minor, creds, &ctx, + gss_name, GSS_C_NO_OID, flags, 0, NULL, + &input, NULL, &output, + &ret_flags, NULL); + + free(input.value); + memset(&input, 0, sizeof(gss_buffer_desc)); + + if (GSS_ERROR(major)) { + ERROR(pamh, "Unable to establish GSS context [maj:0x%x, min:0x%x]", + major, minor); + gssapi_log_error(pamh, major, minor); + ret = EIO; + goto done; + } else if (major == GSS_S_CONTINUE_NEEDED || output.length > 0) { + ret = sssd_establish_sec_ctx_send(pamh, pam_service, + username, domain, + output.value, output.length, + &input.value, &input.length); + gss_release_buffer(NULL, &output); + if (ret != EOK) { + goto done; + } + } + } while (major != GSS_S_COMPLETE); + + if ((ret_flags & flags) != flags) { + ERROR(pamh, "Negotiated context does not support requested flags\n"); + ret = EIO; + goto done; + } + + ret = EOK; + +done: + gss_delete_sec_context(&minor, &ctx, NULL); + gss_release_name(&minor, &gss_name); + + return ret; +} + +static int errno_to_pam(pam_handle_t *pamh, errno_t ret) +{ + switch (ret) { + case EOK: + TRACE(pamh, "Authentication successful"); + return PAM_SUCCESS; + case ENOENT: + TRACE(pamh, "User not found"); + return PAM_USER_UNKNOWN; + case ENOTSUP: + TRACE(pamh, "GSSAPI authentication is not enabled " + "for given user and service"); + return PAM_USER_UNKNOWN; + case ESSS_NO_SOCKET: + TRACE(pamh, "SSSD socket does not exist"); + return PAM_AUTHINFO_UNAVAIL; + case EPERM: + TRACE(pamh, "Authentication failed"); + return PAM_AUTH_ERR; + default: + TRACE(pamh, "System error [%d]: %s", + ret, strerror(ret)); + return PAM_SYSTEM_ERR; + } +} + +int pam_sm_authenticate(pam_handle_t *pamh, + int flags, + int argc, + const char **argv) +{ + const char *pam_service; + const char *pam_user; + const char *ccache; + char *username = NULL; + char *domain = NULL; + char *target = NULL; + char *upn = NULL; + uid_t uid; + uid_t euid; + errno_t ret; + + debug_enabled = false; + for (int i = 0; i < argc; i++) { + if (strcmp(argv[i], "debug") == 0) { + debug_enabled = true; + break; + } + } + + + /* Get non-default ccache if specified, may be NULL. */ + ccache = getenv("KRB5CCNAME"); + + uid = getuid(); + euid = geteuid(); + + /* Read PAM data. */ + pam_service = get_item_as_string(pamh, PAM_SERVICE); + pam_user = get_item_as_string(pamh, PAM_USER); + if (pam_service == NULL || pam_user == NULL) { + ERROR(pamh, "Unable to get PAM data!"); + ret = EINVAL; + goto done; + } + + /* Initialize GSSAPI authentication with SSSD. Get user domain + * and target GSS service name. */ + TRACE(pamh, "Initializing GSSAPI authentication with SSSD"); + ret = sssd_gssapi_init(pamh, pam_service, pam_user, &username, &domain, + &target, &upn); + if (ret != EOK) { + goto done; + } + + /* PAM is often called from set-user-id applications (sudo, su). we want to + * make sure that we access credentials of the caller (real uid). */ + if (!switch_euid(pamh, euid, uid)) { + ret = EFAULT; + goto done; + } + + /* Authenticate the user by estabilishing security context. Authorization is + * expected to be done by other modules through pam_access. */ + TRACE(pamh, "Trying to establish security context"); + TRACE(pamh, "SSSD User name: %s", username); + TRACE(pamh, "User domain: %s", domain); + TRACE(pamh, "User principal: %s", upn); + TRACE(pamh, "Target name: %s", target); + TRACE(pamh, "Using ccache: %s", ccache == NULL ? "default" : ccache); + ret = sssd_establish_sec_ctx(pamh, ccache, pam_service, + username, domain, target, upn); + + /* Restore original euid. */ + if (!switch_euid(pamh, uid, euid)) { + ret = EFAULT; + goto done; + } + +done: + sss_pam_close_fd(); + free(domain); + free(target); + free(upn); + + return errno_to_pam(pamh, ret); +} + +int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) +{ + return PAM_IGNORE; +} + +int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) +{ + return PAM_IGNORE; +} + +int pam_sm_open_session(pam_handle_t *pamh, + int flags, + int argc, + const char **argv) +{ + return PAM_IGNORE; +} + +int pam_sm_close_session(pam_handle_t *pamh, + int flags, + int argc, + const char **argv) +{ + return PAM_IGNORE; +} + +int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) +{ + return PAM_IGNORE; +} diff --git a/src/sss_client/pam_sss_gss.exports b/src/sss_client/pam_sss_gss.exports new file mode 100644 index 0000000000..9afa106be9 --- /dev/null +++ b/src/sss_client/pam_sss_gss.exports @@ -0,0 +1,4 @@ +{ + global: + *; +}; diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h index d897f43b74..2c3c71bc41 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -233,6 +233,8 @@ enum sss_cli_command { * an authentication request to find * out which authentication methods * are available for the given user. */ + SSS_GSSAPI_INIT = 0x00FA, /**< Initialize GSSAPI authentication. */ + SSS_GSSAPI_SEC_CTX = 0x00FB, /**< Establish GSSAPI security ctx. */ /* PAC responder calls */ SSS_PAC_ADD_PAC_USER = 0x0101, @@ -721,4 +723,10 @@ errno_t sss_readrep_copy_string(const char *in, char **out, size_t *size); +enum pam_gssapi_cmd { + PAM_GSSAPI_GET_NAME, + PAM_GSSAPI_INIT, + PAM_GSSAPI_SENTINEL +}; + #endif /* _SSSCLI_H */ diff --git a/src/tests/dlopen-tests.c b/src/tests/dlopen-tests.c index ccf52abe9c..bffa021884 100644 --- a/src/tests/dlopen-tests.c +++ b/src/tests/dlopen-tests.c @@ -47,6 +47,7 @@ struct so { { "libnss_sss.so", { LIBPFX"libnss_sss.so", NULL } }, { "libsss_certmap.so", { LIBPFX"libsss_certmap.so", NULL } }, { "pam_sss.so", { LIBPFX"pam_sss.so", NULL } }, + { "pam_sss_gss.so", { LIBPFX"pam_sss_gss.so", NULL } }, #ifdef BUILD_WITH_LIBSECRET { "libsss_secrets.so", { LIBPFX"libsss_secrets.so", NULL } }, #endif /* BUILD_WITH_LIBSECRET */
_______________________________________________ sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org Fedora Code of Conduct: https://docs.fedoraproject.org/en-US/project/code-of-conduct/ List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines List Archives: https://lists.fedorahosted.org/archives/list/sssd-devel@lists.fedorahosted.org