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 83a525ca8252e7eaf607064a24a3989f5f6dbb21 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/6] 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 6bc0d7de855b2e46b81cc728aa6f9adb1e8767fe 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/6] 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 dd8991119567415619ea257a38a50a485e9915bf 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/6] 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..f2a3321fc9 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 f88dab2e937ff97a399fd8de905413abf7a8ac1e 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 4/6] 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 c9b1a6c4affd3d943dacea0b4b43ff854bd5ba84 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 5/6] pam: add pam_gssapi_services option

---
 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             |  2 ++
 src/config/etc/sssd.api.conf         |  2 ++
 src/man/sssd.conf.5.xml              | 24 ++++++++++++++++++++++++
 src/responder/pam/pamsrv.c           | 23 +++++++++++++++++++++++
 src/responder/pam/pamsrv.h           |  3 +++
 9 files changed, 75 insertions(+), 2 deletions(-)

diff --git a/src/confdb/confdb.c b/src/confdb/confdb.c
index f2a3321fc9..cf28833eec 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,
+                                      NULL);
+    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..3ec6820014 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
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/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml
index d247400bfb..9c24f4eef5 100644
--- a/src/man/sssd.conf.5.xml
+++ b/src/man/sssd.conf.5.xml
@@ -1706,6 +1706,30 @@ p11_uri = library-description=OpenSC%20smartcard%20framework;slot-id=2
                         </para>
                     </listitem>
                 </varlistentry>
+                <varlistentry>
+                    <term>pam_gssapi_services</term>
+                    <listitem>
+                        <para>
+                            Comman 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.
+                        </para>
+                        <para>
+                            Example:
+                            <programlisting>
+pam_gssapi_services = sudo, sudo-i
+                            </programlisting>
+                        </para>
+                        <para>
+                            Default: Not set (GSSAPI authentication is disabled)
+                        </para>
+                    </listitem>
+                </varlistentry>
             </variablelist>
         </refsect2>
 
diff --git a/src/responder/pam/pamsrv.c b/src/responder/pam/pamsrv.c
index 1f1ee608b8..499dbb5f24 100644
--- a/src/responder/pam/pamsrv.c
+++ b/src/responder/pam/pamsrv.c
@@ -327,6 +327,29 @@ 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, NULL, &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) {
+        pctx->gssapi_services = NULL;
+    } else {
+        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 75701944a0af601692448036d537c84b81450191 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 6/6] pam: add pam_sss_gss module for gssapi authentication

---
 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          | 160 ++++++++
 src/responder/pam/pamsrv.h         |   4 +
 src/responder/pam/pamsrv_cmd.c     |   2 +
 src/responder/pam/pamsrv_gssapi.c  | 594 +++++++++++++++++++++++++++++
 src/sss_client/pam_sss_gss.c       | 477 +++++++++++++++++++++++
 src/sss_client/pam_sss_gss.exports |   4 +
 src/sss_client/sss_cli.h           |   8 +
 src/tests/dlopen-tests.c           |   1 +
 13 files changed, 1295 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 97aa1ec661..02ceee4b15 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 \
@@ -2708,6 +2711,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 \
@@ -2719,6 +2723,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 \
@@ -2734,6 +2739,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 \
@@ -4145,6 +4151,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 = \
@@ -4183,7 +4211,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 1af1d1785f..c516a8b8ba 100644
--- a/configure.ac
+++ b/configure.ac
@@ -181,6 +181,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..ee1fadf918
--- /dev/null
+++ b/src/man/pam_sss_gss.8.xml
@@ -0,0 +1,160 @@
+<?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 host@hostname
+            service ticket (which translates to host/hostname@REALM Kerberos
+            principal). If the 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>
+        <para>
+            SSSD is used to provide correct service name that should be used
+            for validation (host@hostname where hostname is determined from
+            user's domain) and later to validate the user's credentials.
+        </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>
+
+    <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 730dee2881..23ba014af6 100644
--- a/src/responder/pam/pamsrv.h
+++ b/src/responder/pam/pamsrv.h
@@ -144,4 +144,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..39364e2c88
--- /dev/null
+++ b/src/responder/pam/pamsrv_gssapi.c
@@ -0,0 +1,594 @@
+/*
+    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 <stdint.h>
+#include <stdlib.h>
+#include <talloc.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; i++) {
+        if (body[i] == '\0') {
+            break;
+        }
+    }
+
+    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_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;
+    }
+
+    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 errno_t pam_gssapi_init_parse(struct cli_protocol *pctx,
+                                        const char **_service,
+                                        const char **_username)
+{
+    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, _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)
+{
+    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(domain) + 1 + strlen(target) + 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], domain, strlen(domain) + 1, &pctr);
+    SAFEALIGN_SETMEM_STRING(&body[pctr], target, strlen(target) + 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;
+    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_send(cli_ctx, cli_ctx->ev, cli_ctx->rctx,
+                                      cli_ctx->rctx->ncache, 0,
+                                      CACHE_REQ_POSIX_DOM, NULL, username);
+    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;
+    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_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;
+    }
+
+    target = pam_gssapi_target(state, result->domain);
+    if (target == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = pam_gssapi_init_reply(pctx, result->domain->name, target);
+    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)
+{
+    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);
+        DEBUG(SSSDBG_OP_FAILURE, "GSSAPI: %.*s\n", (int)buf.length,
+              (char *)buf.value);
+        gss_release_buffer(&minor, &buf);
+    } while (message_context != 0);
+}
+
+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 {
+    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)
+{
+    struct gssapi_state *state;
+
+    state = talloc_get_type(cli_ctx->state_ctx, struct gssapi_state);
+    if (state == NULL) {
+        state = talloc_zero(cli_ctx, struct gssapi_state);
+        if (state == NULL) {
+            return NULL;
+        }
+
+        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 = &el;
+    }
+
+    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_status(GSS_C_GSS_CODE, major);
+            gssapi_log_status(GSS_C_MECH_CODE, 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, major, minor);
+
+        gssapi_log_status(GSS_C_GSS_CODE, major);
+        gssapi_log_status(GSS_C_MECH_CODE, 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;
+    char *display_name;
+    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_status(GSS_C_GSS_CODE, major);
+        gssapi_log_status(GSS_C_MECH_CODE, minor);
+        ret = EIO;
+        goto done;
+    }
+
+    switch (major) {
+    case GSS_S_COMPLETE:
+        if ((ret_flags & flags) != flags) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  "Negotiated context does not support requested flags\n");
+            state->established = false;
+            ret = EIO;
+            break;
+        }
+
+        display_name = gssapi_get_name(state, client_name);
+        if (display_name == NULL) {
+            state->established = false;
+            ret = ENOMEM;
+            break;
+        }
+
+        DEBUG(SSSDBG_TRACE_FUNC, "Security context established with [%s]\n",
+              display_name);
+        talloc_free(display_name);
+
+        state->established = true;
+        ret = EOK;
+        break;
+    case GSS_S_CONTINUE_NEEDED:
+        ret = EOK;
+        break;
+    default:
+        DEBUG(SSSDBG_OP_FAILURE, "Unable to establish GSS context, unexpected "
+              "value: 0x%x\n", major);
+        ret = EIO;
+        break;
+    }
+
+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 **_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, _domain);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    *_gss_data = body_len - pctr == 0 ? NULL : body + pctr;
+    *_gss_data_len = body_len - pctr;
+
+    return EOK;
+}
+
+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;
+    const char *pam_service;
+    const char *domain_name;
+    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, &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);
+    if (state == NULL) {
+        return ENOMEM;
+    }
+
+    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);
+
+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;
+}
diff --git a/src/sss_client/pam_sss_gss.c b/src/sss_client/pam_sss_gss.c
new file mode 100644
index 0000000000..f44c6e1b04
--- /dev/null
+++ b/src/sss_client/pam_sss_gss.c
@@ -0,0 +1,477 @@
+/*
+    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 <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__); \
+    } \
+} while (0)
+
+static bool switch_euid(pam_handle_t *pamh, uid_t current, uid_t desired)
+{
+    int ret;
+
+    if (current == desired) {
+        return true;
+    }
+
+    TRACE(pamh, "Switching euid from %" SPRIuid " to %" SPRIuid, current,
+          desired);
+
+    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_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, GSS_C_NT_HOSTBASED_SERVICE,
+                            _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 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) {
+        ERROR(pamh, "Communication error [%d, %d]: %s", ret, ret_errno,
+              pam_strerror(pamh, ret));
+        return ret_errno != EOK ? ret_errno : EIO;
+    }
+
+    return ret_errno;
+}
+
+static errno_t sssd_gssapi_init_recv(pam_handle_t *pamh,
+                                     uint8_t *reply,
+                                     size_t reply_len,
+                                     char **_domain,
+                                     char **_target)
+{
+    char *domain = NULL;
+    char *target = NULL;
+    const char *buf;
+    size_t dlen;
+    size_t pctr;
+    errno_t ret;
+
+    domain = malloc(reply_len * sizeof(char*));
+    target = malloc(reply_len * sizeof(char*));
+    if (domain == NULL || target == NULL) {
+        return ENOMEM;
+    }
+
+    buf = (const char*)reply;
+    pctr = 0;
+
+    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;
+    }
+
+    *_domain = domain;
+    *_target = target;
+
+done:
+    if (ret != EOK) {
+        free(domain);
+        free(target);
+    }
+
+    return ret;
+}
+
+static errno_t sssd_gssapi_init(pam_handle_t *pamh,
+                                const char *pam_service,
+                                const char *pam_user,
+                                char **_domain,
+                                char **_target)
+{
+    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(pamh, reply, reply_len, _domain, _target);
+    free(reply);
+
+    return ret;
+}
+
+static errno_t sssd_establish_sec_ctx_send(pam_handle_t *pamh,
+                                           const char *pam_service,
+                                           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 service_len;
+    size_t domain_len;
+    uint8_t *data;
+    int ret_errno;
+    int ret;
+
+    service_len = strlen(pam_service) + 1;
+    domain_len = strlen(domain) + 1;
+
+    req_data.len = (service_len + domain_len) * sizeof(char) + gss_data_len;
+    data = (uint8_t*)malloc(req_data.len);
+    if (data == NULL) {
+        return ENOMEM;
+    }
+
+    memcpy(data, pam_service, service_len);
+    memcpy(data + service_len, domain, domain_len);
+    memcpy(data + service_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) {
+        ERROR(pamh, "Communication error [%d, %d]: %s", ret, ret_errno,
+              pam_strerror(pamh, ret));
+        return ret_errno != EOK ? ret_errno : EIO;
+    }
+
+    return ret_errno;
+}
+
+static int sssd_establish_sec_ctx(pam_handle_t *pamh,
+                                  const char *pam_service,
+                                  const char *domain,
+                                  const char *target)
+{
+    bool established;
+    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;
+    OM_uint32 ret_flags;
+    OM_uint32 major;
+    OM_uint32 minor;
+    int ret;
+
+    ret = string_to_gss_name(pamh, target, &gss_name);
+    if (ret != 0) {
+        return ret;
+    }
+
+    /* Do the handshake. */
+    established = false;
+    while (!established) {
+        major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, &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 (major == GSS_S_CONTINUE_NEEDED || output.length > 0) {
+            ret = sssd_establish_sec_ctx_send(pamh, pam_service, domain,
+                                              output.value, output.length,
+                                              &input.value, &input.length);
+            if (ret != EOK) {
+                goto done;
+            }
+        }
+
+        gss_release_buffer(NULL, &output);
+
+        if (GSS_ERROR(major)) {
+            ERROR(pamh, "Unable to establish GSS context [maj:0x%x, min:0x%x]",
+                  major, minor);
+            gssapi_log_status(pamh, GSS_C_GSS_CODE, major);
+            gssapi_log_status(pamh, GSS_C_MECH_CODE, minor);
+            ret = EIO;
+            goto done;
+        }
+
+        switch (major)
+        {
+        case GSS_S_COMPLETE:
+            established = true;
+            break;
+        case GSS_S_CONTINUE_NEEDED:
+            break;
+        default:
+            ERROR(pamh, "Context is not established but major has "
+                  "unexpected value: %x\n", major);
+            ret = EIO;
+            goto done;
+        }
+    }
+
+    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;
+    char *domain = NULL;
+    char *target = NULL;
+    uid_t uid;
+    uid_t euid;
+    errno_t ret;
+    int i;
+
+    debug_enabled = false;
+    for (i = 0; i < argc; i++) {
+        if (strcmp(argv[i], "debug") == 0) {
+            debug_enabled = true;
+            break;
+        }
+    }
+
+    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, &domain, &target);
+    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, "User domain: %s, Target name: %s", domain, target);
+    ret = sssd_establish_sec_ctx(pamh, pam_service, domain, target);
+
+    /* Restore original euid. */
+    if (!switch_euid(pamh, uid, euid)) {
+        ret = EFAULT;
+        goto done;
+    }
+
+done:
+    sss_pam_close_fd();
+    free(domain);
+    free(target);
+
+    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

Reply via email to