diff --git a/Makefile.am b/Makefile.am
index 97aa1ec6612..02ceee4b158 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 1af1d1785f8..c516a8b8bac 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 ed81da53569..f7e5ce13324 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 00000000000..67f3c464d8c
--- /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 351ab80151e..c6890a792ed 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 00000000000..ee1fadf9188
--- /dev/null
+++ b/src/man/pam_sss_gss.8.xml
@@ -0,0 +1,160 @@
+
+
+
+SSSD Manual pages
+
+
+
+
+ pam_sss_gss
+ 8
+
+
+
+ pam_sss_gss
+ PAM module for SSSD GSSAPI authentication
+
+
+
+
+ pam_sss_gss.so
+
+ debug
+
+
+
+
+
+ DESCRIPTION
+
+ pam_sss_gss.so authenticates user
+ over GSSAPI in cooperation with SSSD.
+
+
+ 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.
+
+
+ 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.
+
+
+ To enable GSSAPI authentication in SSSD, set
+ 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. See
+
+ sssd.conf
+ 5
+ and
+
+ sssd-krb5
+ 5
+ for more details on these options.
+
+
+
+
+ OPTIONS
+
+
+
+
+
+
+ Print debugging information.
+
+
+
+
+
+
+ MODULE TYPES PROVIDED
+ Only the module type is provided.
+
+
+
+ RETURN VALUES
+
+
+ PAM_SUCCESS
+
+
+ The PAM operation finished successfully.
+
+
+
+
+ PAM_USER_UNKNOWN
+
+
+ The user is not known to the authentication service or
+ the GSSAPI authentication is not supported.
+
+
+
+
+ PAM_AUTH_ERR
+
+
+ Authentication failure.
+
+
+
+
+ PAM_AUTHINFO_UNAVAIL
+
+
+ Unable to access the authentication information.
+ This might be due to a network or hardware failure.
+
+
+
+
+ PAM_SYSTEM_ERR
+
+
+ A system error occurred. The SSSD log files may contain
+ additional information about the error.
+
+
+
+
+
+
+
+ EXAMPLES
+
+ 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:
+
+
+[domain/MYDOMAIN]
+pam_gssapi_services = sudo, sudo-i
+
+
+ And then enable the module in desired PAM stack
+ (e.g. /etc/pam.d/sudo and /etc/pam.d/sudo-i).
+
+
+...
+auth sufficient pam_sss_gss.so
+...
+
+
+
+
+
+
+
diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h
index 730dee2881f..23ba014af65 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 acbfc0c39f4..9ea488be4f4 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 00000000000..39364e2c880
--- /dev/null
+++ b/src/responder/pam/pamsrv_gssapi.c
@@ -0,0 +1,594 @@
+/*
+ Authors:
+ Pavel Březina
+
+ 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 .
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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 = ⪙
+ }
+
+ 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 00000000000..f44c6e1b042
--- /dev/null
+++ b/src/sss_client/pam_sss_gss.c
@@ -0,0 +1,477 @@
+/*
+ Authors:
+ Pavel Březina
+
+ 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 .
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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 00000000000..9afa106be96
--- /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 d897f43b74f..2c3c71bc41a 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 ccf52abe9c1..bffa021884a 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 */