|
|
8ad293 |
From d09aa174b04a825979f31c61b05239de088a732f Mon Sep 17 00:00:00 2001
|
|
|
8ad293 |
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
|
|
|
8ad293 |
Date: Tue, 7 Jul 2020 11:05:37 +0200
|
|
|
8ad293 |
Subject: [PATCH 27/27] pam: add pam_sss_gss module for gssapi authentication
|
|
|
8ad293 |
|
|
|
8ad293 |
:feature: New PAM module `pam_sss_gss` for authentication using GSSAPI
|
|
|
8ad293 |
:packaging: Added `pam_sss_gss.so` PAM module and `pam_sss_gss.8` manual page
|
|
|
8ad293 |
|
|
|
8ad293 |
Reviewed-by: Robbie Harwood <rharwood@redhat.com>
|
|
|
8ad293 |
Reviewed-by: Sumit Bose <sbose@redhat.com>
|
|
|
8ad293 |
---
|
|
|
8ad293 |
Makefile.am | 33 +-
|
|
|
8ad293 |
configure.ac | 1 +
|
|
|
8ad293 |
contrib/sssd.spec.in | 2 +
|
|
|
8ad293 |
src/external/libgssapi_krb5.m4 | 8 +
|
|
|
8ad293 |
src/man/Makefile.am | 4 +-
|
|
|
8ad293 |
src/man/pam_sss_gss.8.xml | 209 ++++++++
|
|
|
8ad293 |
src/responder/pam/pamsrv.h | 4 +
|
|
|
8ad293 |
src/responder/pam/pamsrv_cmd.c | 2 +
|
|
|
8ad293 |
src/responder/pam/pamsrv_gssapi.c | 792 +++++++++++++++++++++++++++++
|
|
|
8ad293 |
src/sss_client/pam_sss_gss.c | 588 +++++++++++++++++++++
|
|
|
8ad293 |
src/sss_client/pam_sss_gss.exports | 4 +
|
|
|
8ad293 |
src/sss_client/sss_cli.h | 8 +
|
|
|
8ad293 |
src/tests/dlopen-tests.c | 1 +
|
|
|
8ad293 |
13 files changed, 1653 insertions(+), 3 deletions(-)
|
|
|
8ad293 |
create mode 100644 src/external/libgssapi_krb5.m4
|
|
|
8ad293 |
create mode 100644 src/man/pam_sss_gss.8.xml
|
|
|
8ad293 |
create mode 100644 src/responder/pam/pamsrv_gssapi.c
|
|
|
8ad293 |
create mode 100644 src/sss_client/pam_sss_gss.c
|
|
|
8ad293 |
create mode 100644 src/sss_client/pam_sss_gss.exports
|
|
|
8ad293 |
|
|
|
8ad293 |
diff --git a/Makefile.am b/Makefile.am
|
|
|
8ad293 |
index 430b4e842..1c82776ab 100644
|
|
|
8ad293 |
--- a/Makefile.am
|
|
|
8ad293 |
+++ b/Makefile.am
|
|
|
8ad293 |
@@ -1585,12 +1585,14 @@ sssd_pam_SOURCES = \
|
|
|
8ad293 |
src/responder/pam/pamsrv_cmd.c \
|
|
|
8ad293 |
src/responder/pam/pamsrv_p11.c \
|
|
|
8ad293 |
src/responder/pam/pamsrv_dp.c \
|
|
|
8ad293 |
+ src/responder/pam/pamsrv_gssapi.c \
|
|
|
8ad293 |
src/responder/pam/pam_prompting_config.c \
|
|
|
8ad293 |
src/sss_client/pam_sss_prompt_config.c \
|
|
|
8ad293 |
src/responder/pam/pam_helpers.c \
|
|
|
8ad293 |
$(SSSD_RESPONDER_OBJ)
|
|
|
8ad293 |
sssd_pam_CFLAGS = \
|
|
|
8ad293 |
$(AM_CFLAGS) \
|
|
|
8ad293 |
+ $(GSSAPI_KRB5_CFLAGS) \
|
|
|
8ad293 |
$(NULL)
|
|
|
8ad293 |
sssd_pam_LDADD = \
|
|
|
8ad293 |
$(LIBADD_DL) \
|
|
|
8ad293 |
@@ -1599,6 +1601,7 @@ sssd_pam_LDADD = \
|
|
|
8ad293 |
$(SELINUX_LIBS) \
|
|
|
8ad293 |
$(PAM_LIBS) \
|
|
|
8ad293 |
$(SYSTEMD_DAEMON_LIBS) \
|
|
|
8ad293 |
+ $(GSSAPI_KRB5_LIBS) \
|
|
|
8ad293 |
libsss_certmap.la \
|
|
|
8ad293 |
$(SSSD_INTERNAL_LTLIBS) \
|
|
|
8ad293 |
libsss_iface.la \
|
|
|
8ad293 |
@@ -2710,6 +2713,7 @@ pam_srv_tests_SOURCES = \
|
|
|
8ad293 |
src/sss_client/pam_message.c \
|
|
|
8ad293 |
src/responder/pam/pamsrv_cmd.c \
|
|
|
8ad293 |
src/responder/pam/pamsrv_p11.c \
|
|
|
8ad293 |
+ src/responder/pam/pamsrv_gssapi.c \
|
|
|
8ad293 |
src/responder/pam/pam_helpers.c \
|
|
|
8ad293 |
src/responder/pam/pamsrv_dp.c \
|
|
|
8ad293 |
src/responder/pam/pam_LOCAL_domain.c \
|
|
|
8ad293 |
@@ -2721,6 +2725,7 @@ pam_srv_tests_CFLAGS = \
|
|
|
8ad293 |
-I$(abs_builddir)/src \
|
|
|
8ad293 |
$(AM_CFLAGS) \
|
|
|
8ad293 |
$(CMOCKA_CFLAGS) \
|
|
|
8ad293 |
+ $(GSSAPI_KRB5_CFLAGS) \
|
|
|
8ad293 |
$(NULL)
|
|
|
8ad293 |
pam_srv_tests_LDFLAGS = \
|
|
|
8ad293 |
-Wl,-wrap,sss_packet_get_body \
|
|
|
8ad293 |
@@ -2736,6 +2741,7 @@ pam_srv_tests_LDADD = \
|
|
|
8ad293 |
$(SSSD_LIBS) \
|
|
|
8ad293 |
$(SSSD_INTERNAL_LTLIBS) \
|
|
|
8ad293 |
$(SYSTEMD_DAEMON_LIBS) \
|
|
|
8ad293 |
+ $(GSSAPI_KRB5_LIBS) \
|
|
|
8ad293 |
libsss_test_common.la \
|
|
|
8ad293 |
libsss_idmap.la \
|
|
|
8ad293 |
libsss_certmap.la \
|
|
|
8ad293 |
@@ -4149,6 +4155,28 @@ pam_sss_la_LDFLAGS = \
|
|
|
8ad293 |
-avoid-version \
|
|
|
8ad293 |
-Wl,--version-script,$(srcdir)/src/sss_client/sss_pam.exports
|
|
|
8ad293 |
|
|
|
8ad293 |
+pamlib_LTLIBRARIES += pam_sss_gss.la
|
|
|
8ad293 |
+pam_sss_gss_la_SOURCES = \
|
|
|
8ad293 |
+ src/sss_client/pam_sss_gss.c \
|
|
|
8ad293 |
+ src/sss_client/common.c \
|
|
|
8ad293 |
+ $(NULL)
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+pam_sss_gss_la_CFLAGS = \
|
|
|
8ad293 |
+ $(AM_CFLAGS) \
|
|
|
8ad293 |
+ $(GSSAPI_KRB5_CFLAGS) \
|
|
|
8ad293 |
+ $(NULL)
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+pam_sss_gss_la_LIBADD = \
|
|
|
8ad293 |
+ $(CLIENT_LIBS) \
|
|
|
8ad293 |
+ $(PAM_LIBS) \
|
|
|
8ad293 |
+ $(GSSAPI_KRB5_LIBS) \
|
|
|
8ad293 |
+ $(NULL)
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+pam_sss_gss_la_LDFLAGS = \
|
|
|
8ad293 |
+ -module \
|
|
|
8ad293 |
+ -avoid-version \
|
|
|
8ad293 |
+ -Wl,--version-script,$(srcdir)/src/sss_client/pam_sss_gss.exports
|
|
|
8ad293 |
+
|
|
|
8ad293 |
if BUILD_SUDO
|
|
|
8ad293 |
|
|
|
8ad293 |
libsss_sudo_la_SOURCES = \
|
|
|
8ad293 |
@@ -4187,7 +4215,10 @@ endif
|
|
|
8ad293 |
|
|
|
8ad293 |
dist_noinst_DATA += \
|
|
|
8ad293 |
src/sss_client/sss_nss.exports \
|
|
|
8ad293 |
- src/sss_client/sss_pam.exports
|
|
|
8ad293 |
+ src/sss_client/sss_pam.exports \
|
|
|
8ad293 |
+ src/sss_client/pam_sss_gss.exports \
|
|
|
8ad293 |
+ $(NULL)
|
|
|
8ad293 |
+
|
|
|
8ad293 |
if BUILD_SUDO
|
|
|
8ad293 |
dist_noinst_DATA += src/sss_client/sss_sudo.exports
|
|
|
8ad293 |
endif
|
|
|
8ad293 |
diff --git a/configure.ac b/configure.ac
|
|
|
8ad293 |
index 0d24c4b35..75dc81d53 100644
|
|
|
8ad293 |
--- a/configure.ac
|
|
|
8ad293 |
+++ b/configure.ac
|
|
|
8ad293 |
@@ -182,6 +182,7 @@ m4_include([src/external/libldb.m4])
|
|
|
8ad293 |
m4_include([src/external/libdhash.m4])
|
|
|
8ad293 |
m4_include([src/external/libcollection.m4])
|
|
|
8ad293 |
m4_include([src/external/libini_config.m4])
|
|
|
8ad293 |
+m4_include([src/external/libgssapi_krb5.m4])
|
|
|
8ad293 |
m4_include([src/external/pam.m4])
|
|
|
8ad293 |
m4_include([src/external/ldap.m4])
|
|
|
8ad293 |
m4_include([src/external/libpcre.m4])
|
|
|
8ad293 |
diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in
|
|
|
8ad293 |
index ed81da535..f7e5ce133 100644
|
|
|
8ad293 |
--- a/contrib/sssd.spec.in
|
|
|
8ad293 |
+++ b/contrib/sssd.spec.in
|
|
|
8ad293 |
@@ -1166,6 +1166,7 @@ done
|
|
|
8ad293 |
%license src/sss_client/COPYING src/sss_client/COPYING.LESSER
|
|
|
8ad293 |
/%{_lib}/libnss_sss.so.2
|
|
|
8ad293 |
/%{_lib}/security/pam_sss.so
|
|
|
8ad293 |
+/%{_lib}/security/pam_sss_gss.so
|
|
|
8ad293 |
%{_libdir}/krb5/plugins/libkrb5/sssd_krb5_locator_plugin.so
|
|
|
8ad293 |
%{_libdir}/krb5/plugins/authdata/sssd_pac_plugin.so
|
|
|
8ad293 |
%if (0%{?with_cifs_utils_plugin} == 1)
|
|
|
8ad293 |
@@ -1178,6 +1179,7 @@ done
|
|
|
8ad293 |
%dir %{_libdir}/%{name}/modules
|
|
|
8ad293 |
%{_libdir}/%{name}/modules/sssd_krb5_localauth_plugin.so
|
|
|
8ad293 |
%{_mandir}/man8/pam_sss.8*
|
|
|
8ad293 |
+%{_mandir}/man8/pam_sss_gss.8*
|
|
|
8ad293 |
%{_mandir}/man8/sssd_krb5_locator_plugin.8*
|
|
|
8ad293 |
|
|
|
8ad293 |
%files -n libsss_sudo
|
|
|
8ad293 |
diff --git a/src/external/libgssapi_krb5.m4 b/src/external/libgssapi_krb5.m4
|
|
|
8ad293 |
new file mode 100644
|
|
|
8ad293 |
index 000000000..67f3c464d
|
|
|
8ad293 |
--- /dev/null
|
|
|
8ad293 |
+++ b/src/external/libgssapi_krb5.m4
|
|
|
8ad293 |
@@ -0,0 +1,8 @@
|
|
|
8ad293 |
+AC_SUBST(GSSAPI_KRB5_CFLAGS)
|
|
|
8ad293 |
+AC_SUBST(GSSAPI_KRB5_LIBS)
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+PKG_CHECK_MODULES(GSSAPI_KRB5,
|
|
|
8ad293 |
+ krb5-gssapi,
|
|
|
8ad293 |
+ ,
|
|
|
8ad293 |
+ AC_MSG_ERROR("Please install krb5-devel")
|
|
|
8ad293 |
+ )
|
|
|
8ad293 |
diff --git a/src/man/Makefile.am b/src/man/Makefile.am
|
|
|
8ad293 |
index 351ab8015..c6890a792 100644
|
|
|
8ad293 |
--- a/src/man/Makefile.am
|
|
|
8ad293 |
+++ b/src/man/Makefile.am
|
|
|
8ad293 |
@@ -69,8 +69,8 @@ man_MANS = \
|
|
|
8ad293 |
sssd.8 sssd.conf.5 sssd-ldap.5 sssd-ldap-attributes.5 \
|
|
|
8ad293 |
sssd-krb5.5 sssd-simple.5 sss-certmap.5 \
|
|
|
8ad293 |
sssd_krb5_locator_plugin.8 \
|
|
|
8ad293 |
- pam_sss.8 sss_obfuscate.8 sss_cache.8 sss_debuglevel.8 sss_seed.8 \
|
|
|
8ad293 |
- sss_override.8 idmap_sss.8 sssctl.8 sssd-session-recording.5 \
|
|
|
8ad293 |
+ pam_sss.8 pam_sss_gss.8 sss_obfuscate.8 sss_cache.8 sss_debuglevel.8 \
|
|
|
8ad293 |
+ sss_seed.8 sss_override.8 idmap_sss.8 sssctl.8 sssd-session-recording.5 \
|
|
|
8ad293 |
$(NULL)
|
|
|
8ad293 |
|
|
|
8ad293 |
if BUILD_LOCAL_PROVIDER
|
|
|
8ad293 |
diff --git a/src/man/pam_sss_gss.8.xml b/src/man/pam_sss_gss.8.xml
|
|
|
8ad293 |
new file mode 100644
|
|
|
8ad293 |
index 000000000..ce5b11bff
|
|
|
8ad293 |
--- /dev/null
|
|
|
8ad293 |
+++ b/src/man/pam_sss_gss.8.xml
|
|
|
8ad293 |
@@ -0,0 +1,209 @@
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
|
|
|
8ad293 |
+<reference>
|
|
|
8ad293 |
+<title>SSSD Manual pages</title>
|
|
|
8ad293 |
+<refentry>
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ href="include/upstream.xml" />
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ <refmeta>
|
|
|
8ad293 |
+ <refentrytitle>pam_sss_gss</refentrytitle>
|
|
|
8ad293 |
+ <manvolnum>8</manvolnum>
|
|
|
8ad293 |
+ </refmeta>
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ <refnamediv id='name'>
|
|
|
8ad293 |
+ <refname>pam_sss_gss</refname>
|
|
|
8ad293 |
+ <refpurpose>PAM module for SSSD GSSAPI authentication</refpurpose>
|
|
|
8ad293 |
+ </refnamediv>
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ <refsynopsisdiv id='synopsis'>
|
|
|
8ad293 |
+ <cmdsynopsis>
|
|
|
8ad293 |
+ <command>pam_sss_gss.so</command>
|
|
|
8ad293 |
+ <arg choice='opt'>
|
|
|
8ad293 |
+ <replaceable>debug</replaceable>
|
|
|
8ad293 |
+ </arg>
|
|
|
8ad293 |
+ </cmdsynopsis>
|
|
|
8ad293 |
+ </refsynopsisdiv>
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ <refsect1 id='description'>
|
|
|
8ad293 |
+ <title>DESCRIPTION</title>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ <command>pam_sss_gss.so</command> authenticates user
|
|
|
8ad293 |
+ over GSSAPI in cooperation with SSSD.
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ This module will try to authenticate the user using the GSSAPI
|
|
|
8ad293 |
+ hostbased service name host@hostname which translates to
|
|
|
8ad293 |
+ host/hostname@REALM Kerberos principal. The
|
|
|
8ad293 |
+ <emphasis>REALM</emphasis> part of the Kerberos principal name is
|
|
|
8ad293 |
+ derived by Kerberos internal mechanisms and it can be set explicitly
|
|
|
8ad293 |
+ in configuration of [domain_realm] section in /etc/krb5.conf.
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ SSSD is used to provide desired service name and to validate the
|
|
|
8ad293 |
+ user's credentials using GSSAPI calls. If the service ticket is
|
|
|
8ad293 |
+ already present in the Kerberos credentials cache or if user's
|
|
|
8ad293 |
+ ticket granting ticket can be used to get the correct service ticket
|
|
|
8ad293 |
+ then the user will be authenticated.
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ If <option>pam_gssapi_check_upn</option> is True (default) then SSSD
|
|
|
8ad293 |
+ requires that the credentials used to obtain the service tickets can
|
|
|
8ad293 |
+ be associated with the user. This means that the principal that owns
|
|
|
8ad293 |
+ the Kerberos credentials must match with the user principal name as
|
|
|
8ad293 |
+ defined in LDAP.
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ To enable GSSAPI authentication in SSSD, set
|
|
|
8ad293 |
+ <option>pam_gssapi_services</option> option in [pam] or domain
|
|
|
8ad293 |
+ section of sssd.conf. The service credentials need to be stored
|
|
|
8ad293 |
+ in SSSD's keytab (it is already present if you use ipa or ad
|
|
|
8ad293 |
+ provider). The keytab location can be set with
|
|
|
8ad293 |
+ <option>krb5_keytab</option> option. See
|
|
|
8ad293 |
+ <citerefentry>
|
|
|
8ad293 |
+ <refentrytitle>sssd.conf</refentrytitle>
|
|
|
8ad293 |
+ <manvolnum>5</manvolnum>
|
|
|
8ad293 |
+ </citerefentry> and
|
|
|
8ad293 |
+ <citerefentry>
|
|
|
8ad293 |
+ <refentrytitle>sssd-krb5</refentrytitle>
|
|
|
8ad293 |
+ <manvolnum>5</manvolnum>
|
|
|
8ad293 |
+ </citerefentry> for more details on these options.
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ </refsect1>
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ <refsect1 id='options'>
|
|
|
8ad293 |
+ <title>OPTIONS</title>
|
|
|
8ad293 |
+ <variablelist remap='IP'>
|
|
|
8ad293 |
+ <varlistentry>
|
|
|
8ad293 |
+ <term>
|
|
|
8ad293 |
+ <option>debug</option>
|
|
|
8ad293 |
+ </term>
|
|
|
8ad293 |
+ <listitem>
|
|
|
8ad293 |
+ <para>Print debugging information.</para>
|
|
|
8ad293 |
+ </listitem>
|
|
|
8ad293 |
+ </varlistentry>
|
|
|
8ad293 |
+ </variablelist>
|
|
|
8ad293 |
+ </refsect1>
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ <refsect1 id='module_types_provides'>
|
|
|
8ad293 |
+ <title>MODULE TYPES PROVIDED</title>
|
|
|
8ad293 |
+ <para>Only the <option>auth</option> module type is provided.</para>
|
|
|
8ad293 |
+ </refsect1>
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ <refsect1 id="return_values">
|
|
|
8ad293 |
+ <title>RETURN VALUES</title>
|
|
|
8ad293 |
+ <variablelist>
|
|
|
8ad293 |
+ <varlistentry>
|
|
|
8ad293 |
+ <term>PAM_SUCCESS</term>
|
|
|
8ad293 |
+ <listitem>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ The PAM operation finished successfully.
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ </listitem>
|
|
|
8ad293 |
+ </varlistentry>
|
|
|
8ad293 |
+ <varlistentry>
|
|
|
8ad293 |
+ <term>PAM_USER_UNKNOWN</term>
|
|
|
8ad293 |
+ <listitem>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ The user is not known to the authentication service or
|
|
|
8ad293 |
+ the GSSAPI authentication is not supported.
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ </listitem>
|
|
|
8ad293 |
+ </varlistentry>
|
|
|
8ad293 |
+ <varlistentry>
|
|
|
8ad293 |
+ <term>PAM_AUTH_ERR</term>
|
|
|
8ad293 |
+ <listitem>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ Authentication failure.
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ </listitem>
|
|
|
8ad293 |
+ </varlistentry>
|
|
|
8ad293 |
+ <varlistentry>
|
|
|
8ad293 |
+ <term>PAM_AUTHINFO_UNAVAIL</term>
|
|
|
8ad293 |
+ <listitem>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ Unable to access the authentication information.
|
|
|
8ad293 |
+ This might be due to a network or hardware failure.
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ </listitem>
|
|
|
8ad293 |
+ </varlistentry>
|
|
|
8ad293 |
+ <varlistentry>
|
|
|
8ad293 |
+ <term>PAM_SYSTEM_ERR</term>
|
|
|
8ad293 |
+ <listitem>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ A system error occurred. The SSSD log files may contain
|
|
|
8ad293 |
+ additional information about the error.
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ </listitem>
|
|
|
8ad293 |
+ </varlistentry>
|
|
|
8ad293 |
+ </variablelist>
|
|
|
8ad293 |
+ </refsect1>
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ <refsect1 id='examples'>
|
|
|
8ad293 |
+ <title>EXAMPLES</title>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ The main use case is to provide password-less authentication in
|
|
|
8ad293 |
+ sudo but without the need to disable authentication completely.
|
|
|
8ad293 |
+ To achieve this, first enable GSSAPI authentication for sudo in
|
|
|
8ad293 |
+ sssd.conf:
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ <programlisting>
|
|
|
8ad293 |
+[domain/MYDOMAIN]
|
|
|
8ad293 |
+pam_gssapi_services = sudo, sudo-i
|
|
|
8ad293 |
+ </programlisting>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ And then enable the module in desired PAM stack
|
|
|
8ad293 |
+ (e.g. /etc/pam.d/sudo and /etc/pam.d/sudo-i).
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ <programlisting>
|
|
|
8ad293 |
+...
|
|
|
8ad293 |
+auth sufficient pam_sss_gss.so
|
|
|
8ad293 |
+...
|
|
|
8ad293 |
+ </programlisting>
|
|
|
8ad293 |
+ </refsect1>
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ <refsect1 id='troubleshooting'>
|
|
|
8ad293 |
+ <title>TROUBLESHOOTING</title>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ SSSD logs, pam_sss_gss debug output and syslog may contain helpful
|
|
|
8ad293 |
+ information about the error. Here are some common issues:
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ 1. I have KRB5CCNAME environment variable set and the authentication
|
|
|
8ad293 |
+ does not work: Depending on your sudo version, it is possible that
|
|
|
8ad293 |
+ sudo does not pass this variable to the PAM environment. Try adding
|
|
|
8ad293 |
+ KRB5CCNAME to <option>env_keep</option> in /etc/sudoers or in your
|
|
|
8ad293 |
+ LDAP sudo rules default options.
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ 2. Authentication does not work and syslog contains "Server not
|
|
|
8ad293 |
+ found in Kerberos database": Kerberos is probably not able to
|
|
|
8ad293 |
+ resolve correct realm for the service ticket based on the hostname.
|
|
|
8ad293 |
+ Try adding the hostname directly to
|
|
|
8ad293 |
+ <option>[domain_realm]</option> in /etc/krb5.conf like so:
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ 3. Authentication does not work and syslog contains "No Kerberos
|
|
|
8ad293 |
+ credentials available": You don't have any credentials that can be
|
|
|
8ad293 |
+ used to obtain the required service ticket. Use kinit or autheticate
|
|
|
8ad293 |
+ over SSSD to acquire those credentials.
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ <para>
|
|
|
8ad293 |
+ 4. Authentication does not work and SSSD sssd-pam log contains "User
|
|
|
8ad293 |
+ with UPN [$UPN] was not found." or "UPN [$UPN] does not match target
|
|
|
8ad293 |
+ user [$username].": You are using credentials that can not be mapped
|
|
|
8ad293 |
+ to the user that is being authenticated. Try to use kswitch to
|
|
|
8ad293 |
+ select different principal, make sure you authenticated with SSSD or
|
|
|
8ad293 |
+ consider disabling <option>pam_gssapi_check_upn</option>.
|
|
|
8ad293 |
+ </para>
|
|
|
8ad293 |
+ <programlisting>
|
|
|
8ad293 |
+[domain_realm]
|
|
|
8ad293 |
+.myhostname = MYREALM
|
|
|
8ad293 |
+ </programlisting>
|
|
|
8ad293 |
+ </refsect1>
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/seealso.xml" />
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+</refentry>
|
|
|
8ad293 |
+</reference>
|
|
|
8ad293 |
diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h
|
|
|
8ad293 |
index bf4dd75b0..355329691 100644
|
|
|
8ad293 |
--- a/src/responder/pam/pamsrv.h
|
|
|
8ad293 |
+++ b/src/responder/pam/pamsrv.h
|
|
|
8ad293 |
@@ -145,4 +145,8 @@ errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd);
|
|
|
8ad293 |
|
|
|
8ad293 |
enum pam_initgroups_scheme pam_initgroups_string_to_enum(const char *str);
|
|
|
8ad293 |
const char *pam_initgroup_enum_to_string(enum pam_initgroups_scheme scheme);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+int pam_cmd_gssapi_init(struct cli_ctx *cli_ctx);
|
|
|
8ad293 |
+int pam_cmd_gssapi_sec_ctx(struct cli_ctx *cctx);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
#endif /* __PAMSRV_H__ */
|
|
|
8ad293 |
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
|
|
|
8ad293 |
index acbfc0c39..9ea488be4 100644
|
|
|
8ad293 |
--- a/src/responder/pam/pamsrv_cmd.c
|
|
|
8ad293 |
+++ b/src/responder/pam/pamsrv_cmd.c
|
|
|
8ad293 |
@@ -2401,6 +2401,8 @@ struct sss_cmd_table *get_pam_cmds(void)
|
|
|
8ad293 |
{SSS_PAM_CHAUTHTOK, pam_cmd_chauthtok},
|
|
|
8ad293 |
{SSS_PAM_CHAUTHTOK_PRELIM, pam_cmd_chauthtok_prelim},
|
|
|
8ad293 |
{SSS_PAM_PREAUTH, pam_cmd_preauth},
|
|
|
8ad293 |
+ {SSS_GSSAPI_INIT, pam_cmd_gssapi_init},
|
|
|
8ad293 |
+ {SSS_GSSAPI_SEC_CTX, pam_cmd_gssapi_sec_ctx},
|
|
|
8ad293 |
{SSS_CLI_NULL, NULL}
|
|
|
8ad293 |
};
|
|
|
8ad293 |
|
|
|
8ad293 |
diff --git a/src/responder/pam/pamsrv_gssapi.c b/src/responder/pam/pamsrv_gssapi.c
|
|
|
8ad293 |
new file mode 100644
|
|
|
8ad293 |
index 000000000..099675e1c
|
|
|
8ad293 |
--- /dev/null
|
|
|
8ad293 |
+++ b/src/responder/pam/pamsrv_gssapi.c
|
|
|
8ad293 |
@@ -0,0 +1,792 @@
|
|
|
8ad293 |
+/*
|
|
|
8ad293 |
+ Authors:
|
|
|
8ad293 |
+ Pavel Březina <pbrezina@redhat.com>
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ Copyright (C) 2020 Red Hat
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ This program is free software; you can redistribute it and/or modify
|
|
|
8ad293 |
+ it under the terms of the GNU General Public License as published by
|
|
|
8ad293 |
+ the Free Software Foundation; either version 3 of the License, or
|
|
|
8ad293 |
+ (at your option) any later version.
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ This program is distributed in the hope that it will be useful,
|
|
|
8ad293 |
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
8ad293 |
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
8ad293 |
+ GNU General Public License for more details.
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ You should have received a copy of the GNU General Public License
|
|
|
8ad293 |
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
8ad293 |
+*/
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+#include <errno.h>
|
|
|
8ad293 |
+#include <gssapi.h>
|
|
|
8ad293 |
+#include <gssapi/gssapi_ext.h>
|
|
|
8ad293 |
+#include <gssapi/gssapi_krb5.h>
|
|
|
8ad293 |
+#include <stdint.h>
|
|
|
8ad293 |
+#include <stdlib.h>
|
|
|
8ad293 |
+#include <talloc.h>
|
|
|
8ad293 |
+#include <ldb.h>
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+#include "confdb/confdb.h"
|
|
|
8ad293 |
+#include "db/sysdb.h"
|
|
|
8ad293 |
+#include "responder/common/responder_packet.h"
|
|
|
8ad293 |
+#include "responder/common/responder.h"
|
|
|
8ad293 |
+#include "responder/common/cache_req/cache_req.h"
|
|
|
8ad293 |
+#include "responder/pam/pamsrv.h"
|
|
|
8ad293 |
+#include "sss_client/sss_cli.h"
|
|
|
8ad293 |
+#include "util/util.h"
|
|
|
8ad293 |
+#include "util/sss_utf8.h"
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static errno_t read_str(size_t body_len,
|
|
|
8ad293 |
+ uint8_t *body,
|
|
|
8ad293 |
+ size_t *pctr,
|
|
|
8ad293 |
+ const char **_str)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ size_t i;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ for (i = *pctr; i < body_len && body[i] != 0; i++) {
|
|
|
8ad293 |
+ /* counting */
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (i >= body_len) {
|
|
|
8ad293 |
+ return EINVAL;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (!sss_utf8_check(&body[*pctr], i - *pctr)) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_CRIT_FAILURE, "Body is not UTF-8 string!\n");
|
|
|
8ad293 |
+ return EINVAL;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ *_str = (const char *)&body[*pctr];
|
|
|
8ad293 |
+ *pctr = i + 1;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return EOK;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static bool pam_gssapi_should_check_upn(struct pam_ctx *pam_ctx,
|
|
|
8ad293 |
+ struct sss_domain_info *domain)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ if (domain->gssapi_check_upn != NULL) {
|
|
|
8ad293 |
+ if (strcasecmp(domain->gssapi_check_upn, "true") == 0) {
|
|
|
8ad293 |
+ return true;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (strcasecmp(domain->gssapi_check_upn, "false") == 0) {
|
|
|
8ad293 |
+ return false;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ DEBUG(SSSDBG_MINOR_FAILURE, "Invalid value for %s: %s\n",
|
|
|
8ad293 |
+ CONFDB_PAM_GSSAPI_CHECK_UPN, domain->gssapi_check_upn);
|
|
|
8ad293 |
+ return false;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return pam_ctx->gssapi_check_upn;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static bool pam_gssapi_allowed(struct pam_ctx *pam_ctx,
|
|
|
8ad293 |
+ struct sss_domain_info *domain,
|
|
|
8ad293 |
+ const char *service)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ char **list = pam_ctx->gssapi_services;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (domain->gssapi_services != NULL) {
|
|
|
8ad293 |
+ list = domain->gssapi_services;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (strcmp(service, "-") == 0) {
|
|
|
8ad293 |
+ /* Dash is used as a "not set" value to allow to explicitly disable
|
|
|
8ad293 |
+ * gssapi auth for specific domain. Disallow this service to be safe.
|
|
|
8ad293 |
+ */
|
|
|
8ad293 |
+ DEBUG(SSSDBG_TRACE_FUNC, "Dash - was used as a PAM service name. "
|
|
|
8ad293 |
+ "GSSAPI authentication is not allowed.\n");
|
|
|
8ad293 |
+ return false;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return string_in_list(service, list, true);
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static char *pam_gssapi_target(TALLOC_CTX *mem_ctx,
|
|
|
8ad293 |
+ struct sss_domain_info *domain)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ return talloc_asprintf(mem_ctx, "host@%s", domain->hostname);
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static const char *pam_gssapi_get_upn(struct cache_req_result *result)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ if (result->count == 0) {
|
|
|
8ad293 |
+ return NULL;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ /* Canonical UPN should be available if the user has kinited through SSSD.
|
|
|
8ad293 |
+ * Use it as a hint for GSSAPI. Default to empty string so it may be
|
|
|
8ad293 |
+ * more easily transffered over the wire. */
|
|
|
8ad293 |
+ return ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_CANONICAL_UPN, "");
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static const char *pam_gssapi_get_name(struct cache_req_result *result)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ if (result->count == 0) {
|
|
|
8ad293 |
+ return NULL;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ /* Return username known to SSSD to make sure we authenticated as the same
|
|
|
8ad293 |
+ * user after GSSAPI handshake. */
|
|
|
8ad293 |
+ return ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_NAME, NULL);
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static errno_t pam_gssapi_init_parse(struct cli_protocol *pctx,
|
|
|
8ad293 |
+ const char **_service,
|
|
|
8ad293 |
+ const char **_username)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ size_t body_len;
|
|
|
8ad293 |
+ size_t pctr = 0;
|
|
|
8ad293 |
+ uint8_t *body;
|
|
|
8ad293 |
+ errno_t ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ sss_packet_get_body(pctx->creq->in, &body, &body_len);
|
|
|
8ad293 |
+ if (body == NULL) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid input\n");
|
|
|
8ad293 |
+ return EINVAL;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = read_str(body_len, body, &pctr, _service);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = read_str(body_len, body, &pctr, _username);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return EOK;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static errno_t pam_gssapi_init_reply(struct cli_protocol *pctx,
|
|
|
8ad293 |
+ const char *domain,
|
|
|
8ad293 |
+ const char *target,
|
|
|
8ad293 |
+ const char *upn,
|
|
|
8ad293 |
+ const char *username)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ size_t reply_len;
|
|
|
8ad293 |
+ size_t body_len;
|
|
|
8ad293 |
+ size_t pctr;
|
|
|
8ad293 |
+ uint8_t *body;
|
|
|
8ad293 |
+ errno_t ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in),
|
|
|
8ad293 |
+ &pctx->creq->out);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create a new packet [%d]; %s\n",
|
|
|
8ad293 |
+ ret, sss_strerror(ret));
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ reply_len = strlen(username) + 1;
|
|
|
8ad293 |
+ reply_len += strlen(domain) + 1;
|
|
|
8ad293 |
+ reply_len += strlen(target) + 1;
|
|
|
8ad293 |
+ reply_len += strlen(upn) + 1;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = sss_packet_grow(pctx->creq->out, reply_len);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create response: %s\n",
|
|
|
8ad293 |
+ sss_strerror(ret));
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ sss_packet_get_body(pctx->creq->out, &body, &body_len);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ pctr = 0;
|
|
|
8ad293 |
+ SAFEALIGN_SETMEM_STRING(&body[pctr], username, strlen(username) + 1, &pctr);
|
|
|
8ad293 |
+ SAFEALIGN_SETMEM_STRING(&body[pctr], domain, strlen(domain) + 1, &pctr);
|
|
|
8ad293 |
+ SAFEALIGN_SETMEM_STRING(&body[pctr], target, strlen(target) + 1, &pctr);
|
|
|
8ad293 |
+ SAFEALIGN_SETMEM_STRING(&body[pctr], upn, strlen(upn) + 1, &pctr);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return EOK;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+struct gssapi_init_state {
|
|
|
8ad293 |
+ struct cli_ctx *cli_ctx;
|
|
|
8ad293 |
+ const char *username;
|
|
|
8ad293 |
+ const char *service;
|
|
|
8ad293 |
+};
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static void pam_cmd_gssapi_init_done(struct tevent_req *req);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+int pam_cmd_gssapi_init(struct cli_ctx *cli_ctx)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ struct gssapi_init_state *state;
|
|
|
8ad293 |
+ struct cli_protocol *pctx;
|
|
|
8ad293 |
+ struct tevent_req *req;
|
|
|
8ad293 |
+ const char *username;
|
|
|
8ad293 |
+ const char *service;
|
|
|
8ad293 |
+ const char *attrs[] = { SYSDB_NAME, SYSDB_CANONICAL_UPN, NULL };
|
|
|
8ad293 |
+ errno_t ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ state = talloc_zero(cli_ctx, struct gssapi_init_state);
|
|
|
8ad293 |
+ if (state == NULL) {
|
|
|
8ad293 |
+ return ENOMEM;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = pam_gssapi_init_parse(pctx, &service, &username);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse input [%d]: %s\n",
|
|
|
8ad293 |
+ ret, sss_strerror(ret));
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ state->cli_ctx = cli_ctx;
|
|
|
8ad293 |
+ state->service = service;
|
|
|
8ad293 |
+ state->username = username;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ DEBUG(SSSDBG_TRACE_ALL,
|
|
|
8ad293 |
+ "Requesting GSSAPI authentication of [%s] in service [%s]\n",
|
|
|
8ad293 |
+ username, service);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ req = cache_req_user_by_name_attrs_send(cli_ctx, cli_ctx->ev, cli_ctx->rctx,
|
|
|
8ad293 |
+ cli_ctx->rctx->ncache, 0,
|
|
|
8ad293 |
+ NULL, username, attrs);
|
|
|
8ad293 |
+ if (req == NULL) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n");
|
|
|
8ad293 |
+ ret = ENOMEM;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ tevent_req_set_callback(req, pam_cmd_gssapi_init_done, state);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = EOK;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+done:
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ sss_cmd_send_error(cli_ctx, ret);
|
|
|
8ad293 |
+ sss_cmd_done(cli_ctx, NULL);
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return EOK;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static void pam_cmd_gssapi_init_done(struct tevent_req *req)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ struct gssapi_init_state *state;
|
|
|
8ad293 |
+ struct cache_req_result *result;
|
|
|
8ad293 |
+ struct cli_protocol *pctx;
|
|
|
8ad293 |
+ struct pam_ctx *pam_ctx;
|
|
|
8ad293 |
+ const char *username;
|
|
|
8ad293 |
+ const char *upn;
|
|
|
8ad293 |
+ char *target;
|
|
|
8ad293 |
+ errno_t ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ state = tevent_req_callback_data(req, struct gssapi_init_state);
|
|
|
8ad293 |
+ pctx = talloc_get_type(state->cli_ctx->protocol_ctx, struct cli_protocol);
|
|
|
8ad293 |
+ pam_ctx = talloc_get_type(state->cli_ctx->rctx->pvt_ctx, struct pam_ctx);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = cache_req_user_by_name_attrs_recv(state, req, &result);
|
|
|
8ad293 |
+ talloc_zfree(req);
|
|
|
8ad293 |
+ if (ret == ENOENT || ret == ERR_DOMAIN_NOT_FOUND) {
|
|
|
8ad293 |
+ ret = ENOENT;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ } else if (ret != EOK) {
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (!pam_gssapi_allowed(pam_ctx, result->domain, state->service)) {
|
|
|
8ad293 |
+ ret = ENOTSUP;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ username = pam_gssapi_get_name(result);
|
|
|
8ad293 |
+ if (username == NULL) {
|
|
|
8ad293 |
+ /* User with no name? */
|
|
|
8ad293 |
+ ret = ERR_INTERNAL;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ upn = pam_gssapi_get_upn(result);
|
|
|
8ad293 |
+ if (upn == NULL) {
|
|
|
8ad293 |
+ /* UPN hint may be an empty string, but not NULL. */
|
|
|
8ad293 |
+ ret = ERR_INTERNAL;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ target = pam_gssapi_target(state, result->domain);
|
|
|
8ad293 |
+ if (target == NULL) {
|
|
|
8ad293 |
+ ret = ENOMEM;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ DEBUG(SSSDBG_TRACE_FUNC,
|
|
|
8ad293 |
+ "Trying GSSAPI auth: User[%s], Domain[%s], UPN[%s], Target[%s]\n",
|
|
|
8ad293 |
+ username, result->domain->name, upn, target);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = pam_gssapi_init_reply(pctx, result->domain->name, target, upn,
|
|
|
8ad293 |
+ username);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to construct reply [%d]: %s\n",
|
|
|
8ad293 |
+ ret, sss_strerror(ret));
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+done:
|
|
|
8ad293 |
+ DEBUG(SSSDBG_TRACE_FUNC, "Returning [%d]: %s\n", ret, sss_strerror(ret));
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (ret == EOK) {
|
|
|
8ad293 |
+ sss_packet_set_error(pctx->creq->out, EOK);
|
|
|
8ad293 |
+ } else {
|
|
|
8ad293 |
+ sss_cmd_send_error(state->cli_ctx, ret);
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ sss_cmd_done(state->cli_ctx, state);
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static void gssapi_log_status(int type, OM_uint32 status_code)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ OM_uint32 message_context = 0;
|
|
|
8ad293 |
+ gss_buffer_desc buf;
|
|
|
8ad293 |
+ OM_uint32 minor;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ do {
|
|
|
8ad293 |
+ gss_display_status(&minor, status_code, type, GSS_C_NO_OID,
|
|
|
8ad293 |
+ &message_context, &buf;;
|
|
|
8ad293 |
+ DEBUG(SSSDBG_OP_FAILURE, "GSSAPI: %.*s\n", (int)buf.length,
|
|
|
8ad293 |
+ (char *)buf.value);
|
|
|
8ad293 |
+ gss_release_buffer(&minor, &buf;;
|
|
|
8ad293 |
+ } while (message_context != 0);
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static void gssapi_log_error(OM_uint32 major, OM_uint32 minor)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ gssapi_log_status(GSS_C_GSS_CODE, major);
|
|
|
8ad293 |
+ gssapi_log_status(GSS_C_MECH_CODE, minor);
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static char *gssapi_get_name(TALLOC_CTX *mem_ctx, gss_name_t gss_name)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ gss_buffer_desc buf;
|
|
|
8ad293 |
+ OM_uint32 major;
|
|
|
8ad293 |
+ OM_uint32 minor;
|
|
|
8ad293 |
+ char *exported;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ major = gss_display_name(&minor, gss_name, &buf, NULL);
|
|
|
8ad293 |
+ if (major != GSS_S_COMPLETE) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to export name\n");
|
|
|
8ad293 |
+ return NULL;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ exported = talloc_strndup(mem_ctx, buf.value, buf.length);
|
|
|
8ad293 |
+ gss_release_buffer(&minor, &buf;;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (exported == NULL) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n");
|
|
|
8ad293 |
+ return NULL;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return exported;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+struct gssapi_state {
|
|
|
8ad293 |
+ struct cli_ctx *cli_ctx;
|
|
|
8ad293 |
+ struct sss_domain_info *domain;
|
|
|
8ad293 |
+ const char *username;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ char *authenticated_upn;
|
|
|
8ad293 |
+ bool established;
|
|
|
8ad293 |
+ gss_ctx_id_t ctx;
|
|
|
8ad293 |
+};
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+int gssapi_state_destructor(struct gssapi_state *state)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ OM_uint32 minor;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ gss_delete_sec_context(&minor, &state->ctx, NULL);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return 0;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static struct gssapi_state *gssapi_get_state(struct cli_ctx *cli_ctx,
|
|
|
8ad293 |
+ const char *username,
|
|
|
8ad293 |
+ struct sss_domain_info *domain)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ struct gssapi_state *state;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ state = talloc_get_type(cli_ctx->state_ctx, struct gssapi_state);
|
|
|
8ad293 |
+ if (state != NULL) {
|
|
|
8ad293 |
+ return state;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ state = talloc_zero(cli_ctx, struct gssapi_state);
|
|
|
8ad293 |
+ if (state == NULL) {
|
|
|
8ad293 |
+ return NULL;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ state->username = talloc_strdup(state, username);
|
|
|
8ad293 |
+ if (state == NULL) {
|
|
|
8ad293 |
+ talloc_free(state);
|
|
|
8ad293 |
+ return NULL;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ state->domain = domain;
|
|
|
8ad293 |
+ state->cli_ctx = cli_ctx;
|
|
|
8ad293 |
+ state->ctx = GSS_C_NO_CONTEXT;
|
|
|
8ad293 |
+ talloc_set_destructor(state, gssapi_state_destructor);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ cli_ctx->state_ctx = state;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return state;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static errno_t gssapi_get_creds(const char *keytab,
|
|
|
8ad293 |
+ const char *target,
|
|
|
8ad293 |
+ gss_cred_id_t *_creds)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ gss_key_value_set_desc cstore = {0, NULL};
|
|
|
8ad293 |
+ gss_key_value_element_desc el;
|
|
|
8ad293 |
+ gss_buffer_desc name_buf;
|
|
|
8ad293 |
+ gss_name_t name = GSS_C_NO_NAME;
|
|
|
8ad293 |
+ OM_uint32 major;
|
|
|
8ad293 |
+ OM_uint32 minor;
|
|
|
8ad293 |
+ errno_t ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (keytab != NULL) {
|
|
|
8ad293 |
+ el.key = "keytab";
|
|
|
8ad293 |
+ el.value = keytab;
|
|
|
8ad293 |
+ cstore.count = 1;
|
|
|
8ad293 |
+ cstore.elements = ⪙
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (target != NULL) {
|
|
|
8ad293 |
+ name_buf.value = discard_const(target);
|
|
|
8ad293 |
+ name_buf.length = strlen(target);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ major = gss_import_name(&minor, &name_buf, GSS_C_NT_HOSTBASED_SERVICE,
|
|
|
8ad293 |
+ &name);
|
|
|
8ad293 |
+ if (GSS_ERROR(major)) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_OP_FAILURE, "Could not import name [%s] "
|
|
|
8ad293 |
+ "[maj:0x%x, min:0x%x]\n", target, major, minor);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ gssapi_log_error(major, minor);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = EIO;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ major = gss_acquire_cred_from(&minor, name, GSS_C_INDEFINITE,
|
|
|
8ad293 |
+ GSS_C_NO_OID_SET, GSS_C_ACCEPT, &cstore,
|
|
|
8ad293 |
+ _creds, NULL, NULL);
|
|
|
8ad293 |
+ if (GSS_ERROR(major)) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to read credentials from [%s] "
|
|
|
8ad293 |
+ "[maj:0x%x, min:0x%x]\n", keytab ? keytab : "default",
|
|
|
8ad293 |
+ major, minor);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ gssapi_log_error(major, minor);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = EIO;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = EOK;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+done:
|
|
|
8ad293 |
+ gss_release_name(&minor, &name);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static errno_t
|
|
|
8ad293 |
+gssapi_handshake(struct gssapi_state *state,
|
|
|
8ad293 |
+ struct cli_protocol *pctx,
|
|
|
8ad293 |
+ const char *keytab,
|
|
|
8ad293 |
+ const char *target,
|
|
|
8ad293 |
+ uint8_t *gss_data,
|
|
|
8ad293 |
+ size_t gss_data_len)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ OM_uint32 flags = GSS_C_MUTUAL_FLAG;
|
|
|
8ad293 |
+ gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
|
|
|
8ad293 |
+ gss_buffer_desc input;
|
|
|
8ad293 |
+ gss_name_t client_name;
|
|
|
8ad293 |
+ gss_cred_id_t creds;
|
|
|
8ad293 |
+ OM_uint32 ret_flags;
|
|
|
8ad293 |
+ gss_OID mech_type;
|
|
|
8ad293 |
+ OM_uint32 major;
|
|
|
8ad293 |
+ OM_uint32 minor;
|
|
|
8ad293 |
+ errno_t ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ input.value = gss_data;
|
|
|
8ad293 |
+ input.length = gss_data_len;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = gssapi_get_creds(keytab, target, &creds);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ major = gss_accept_sec_context(&minor, &state->ctx, creds,
|
|
|
8ad293 |
+ &input, NULL, &client_name, &mech_type,
|
|
|
8ad293 |
+ &output, &ret_flags, NULL, NULL);
|
|
|
8ad293 |
+ if (major == GSS_S_CONTINUE_NEEDED || output.length > 0) {
|
|
|
8ad293 |
+ ret = sss_packet_set_body(pctx->creq->out, output.value, output.length);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (GSS_ERROR(major)) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to establish GSS context "
|
|
|
8ad293 |
+ "[maj:0x%x, min:0x%x]\n", major, minor);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ gssapi_log_error(major, minor);
|
|
|
8ad293 |
+ ret = EIO;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (major == GSS_S_CONTINUE_NEEDED) {
|
|
|
8ad293 |
+ ret = EOK;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ } else if (major != GSS_S_COMPLETE) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to establish GSS context, unexpected "
|
|
|
8ad293 |
+ "value: 0x%x\n", major);
|
|
|
8ad293 |
+ ret = EIO;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if ((ret_flags & flags) != flags) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_MINOR_FAILURE,
|
|
|
8ad293 |
+ "Negotiated context does not support requested flags\n");
|
|
|
8ad293 |
+ state->established = false;
|
|
|
8ad293 |
+ ret = EIO;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ state->authenticated_upn = gssapi_get_name(state, client_name);
|
|
|
8ad293 |
+ if (state->authenticated_upn == NULL) {
|
|
|
8ad293 |
+ state->established = false;
|
|
|
8ad293 |
+ ret = ENOMEM;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ DEBUG(SSSDBG_TRACE_FUNC, "Security context established with [%s]\n",
|
|
|
8ad293 |
+ state->authenticated_upn);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ state->established = true;
|
|
|
8ad293 |
+ ret = EOK;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+done:
|
|
|
8ad293 |
+ gss_release_cred(&minor, &creds);
|
|
|
8ad293 |
+ gss_release_buffer(&minor, &output);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static errno_t pam_cmd_gssapi_sec_ctx_parse(struct cli_protocol *pctx,
|
|
|
8ad293 |
+ const char **_pam_service,
|
|
|
8ad293 |
+ const char **_username,
|
|
|
8ad293 |
+ const char **_domain,
|
|
|
8ad293 |
+ uint8_t **_gss_data,
|
|
|
8ad293 |
+ size_t *_gss_data_len)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ size_t body_len;
|
|
|
8ad293 |
+ uint8_t *body;
|
|
|
8ad293 |
+ size_t pctr;
|
|
|
8ad293 |
+ errno_t ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ sss_packet_get_body(pctx->creq->in, &body, &body_len);
|
|
|
8ad293 |
+ if (body == NULL) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid input\n");
|
|
|
8ad293 |
+ return EINVAL;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ pctr = 0;
|
|
|
8ad293 |
+ ret = read_str(body_len, body, &pctr, _pam_service);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = read_str(body_len, body, &pctr, _username);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = read_str(body_len, body, &pctr, _domain);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ *_gss_data = (pctr == body_len) ? NULL : body + pctr;
|
|
|
8ad293 |
+ *_gss_data_len = body_len - pctr;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return EOK;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static void pam_cmd_gssapi_sec_ctx_done(struct tevent_req *req);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+int
|
|
|
8ad293 |
+pam_cmd_gssapi_sec_ctx(struct cli_ctx *cli_ctx)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ struct sss_domain_info *domain;
|
|
|
8ad293 |
+ struct gssapi_state *state;
|
|
|
8ad293 |
+ struct cli_protocol *pctx;
|
|
|
8ad293 |
+ struct pam_ctx *pam_ctx;
|
|
|
8ad293 |
+ struct tevent_req *req;
|
|
|
8ad293 |
+ const char *pam_service;
|
|
|
8ad293 |
+ const char *domain_name;
|
|
|
8ad293 |
+ const char *username;
|
|
|
8ad293 |
+ char *target;
|
|
|
8ad293 |
+ size_t gss_data_len;
|
|
|
8ad293 |
+ uint8_t *gss_data;
|
|
|
8ad293 |
+ errno_t ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol);
|
|
|
8ad293 |
+ pam_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct pam_ctx);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in),
|
|
|
8ad293 |
+ &pctx->creq->out);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create a new packet [%d]; %s\n",
|
|
|
8ad293 |
+ ret, sss_strerror(ret));
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = pam_cmd_gssapi_sec_ctx_parse(pctx, &pam_service, &username,
|
|
|
8ad293 |
+ &domain_name, &gss_data, &gss_data_len);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to parse input data [%d]: %s\n",
|
|
|
8ad293 |
+ ret, sss_strerror(ret));
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ domain = find_domain_by_name(cli_ctx->rctx->domains, domain_name, false);
|
|
|
8ad293 |
+ if (domain == NULL) {
|
|
|
8ad293 |
+ ret = EINVAL;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (!pam_gssapi_allowed(pam_ctx, domain, pam_service)) {
|
|
|
8ad293 |
+ ret = ENOTSUP;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ target = pam_gssapi_target(cli_ctx, domain);
|
|
|
8ad293 |
+ if (target == NULL) {
|
|
|
8ad293 |
+ ret = ENOMEM;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ state = gssapi_get_state(cli_ctx, username, domain);
|
|
|
8ad293 |
+ if (state == NULL) {
|
|
|
8ad293 |
+ ret = ENOMEM;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (strcmp(username, state->username) != 0 || state->domain != domain) {
|
|
|
8ad293 |
+ /* This should not happen, but be paranoid. */
|
|
|
8ad293 |
+ DEBUG(SSSDBG_CRIT_FAILURE, "Different input user then who initiated "
|
|
|
8ad293 |
+ "the request!\n");
|
|
|
8ad293 |
+ ret = EPERM;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (state->established) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_MINOR_FAILURE,
|
|
|
8ad293 |
+ "Security context is already established\n");
|
|
|
8ad293 |
+ ret = EPERM;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = gssapi_handshake(state, pctx, domain->krb5_keytab, target, gss_data,
|
|
|
8ad293 |
+ gss_data_len);
|
|
|
8ad293 |
+ if (ret != EOK || !state->established) {
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (!pam_gssapi_should_check_upn(pam_ctx, domain)) {
|
|
|
8ad293 |
+ /* We are done. */
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ /* We have established the security context. Now check the the principal
|
|
|
8ad293 |
+ * used for authorization can be associated with the user. We have
|
|
|
8ad293 |
+ * already done initgroups before so we could just search the sysdb
|
|
|
8ad293 |
+ * directly, but use cache req to avoid looking up a possible expired
|
|
|
8ad293 |
+ * object if the handshake took longer. */
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ DEBUG(SSSDBG_TRACE_FUNC, "Checking that target user matches UPN\n");
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ req = cache_req_user_by_upn_send(cli_ctx, cli_ctx->ev, cli_ctx->rctx,
|
|
|
8ad293 |
+ cli_ctx->rctx->ncache, 0, DOM_TYPE_POSIX,
|
|
|
8ad293 |
+ domain->name, state->authenticated_upn);
|
|
|
8ad293 |
+ if (req == NULL) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n");
|
|
|
8ad293 |
+ ret = ENOMEM;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ tevent_req_set_callback(req, pam_cmd_gssapi_sec_ctx_done, state);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return EOK;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+done:
|
|
|
8ad293 |
+ DEBUG(SSSDBG_TRACE_FUNC, "Returning [%d]: %s\n", ret, sss_strerror(ret));
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (ret == EOK) {
|
|
|
8ad293 |
+ sss_packet_set_error(pctx->creq->out, EOK);
|
|
|
8ad293 |
+ } else {
|
|
|
8ad293 |
+ sss_cmd_send_error(cli_ctx, ret);
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ sss_cmd_done(cli_ctx, NULL);
|
|
|
8ad293 |
+ return EOK;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static void pam_cmd_gssapi_sec_ctx_done(struct tevent_req *req)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ struct gssapi_state *state;
|
|
|
8ad293 |
+ struct cache_req_result *result;
|
|
|
8ad293 |
+ struct cli_protocol *pctx;
|
|
|
8ad293 |
+ const char *name;
|
|
|
8ad293 |
+ errno_t ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ state = tevent_req_callback_data(req, struct gssapi_state);
|
|
|
8ad293 |
+ pctx = talloc_get_type(state->cli_ctx->protocol_ctx, struct cli_protocol);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = cache_req_user_by_upn_recv(state, req, &result);
|
|
|
8ad293 |
+ talloc_zfree(req);
|
|
|
8ad293 |
+ if (ret == ENOENT || ret == ERR_DOMAIN_NOT_FOUND) {
|
|
|
8ad293 |
+ /* We have no match. Return failure. */
|
|
|
8ad293 |
+ DEBUG(SSSDBG_TRACE_FUNC, "User with UPN [%s] was not found. "
|
|
|
8ad293 |
+ "Authentication failed.\n", state->authenticated_upn);
|
|
|
8ad293 |
+ ret = EACCES;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ } else if (ret != EOK) {
|
|
|
8ad293 |
+ /* Generic error. Return failure. */
|
|
|
8ad293 |
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup user by UPN [%d]: %s\n",
|
|
|
8ad293 |
+ ret, sss_strerror(ret));
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ /* Check that username match. */
|
|
|
8ad293 |
+ name = ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_NAME, NULL);
|
|
|
8ad293 |
+ if (name == NULL || strcmp(name, state->username) != 0) {
|
|
|
8ad293 |
+ DEBUG(SSSDBG_TRACE_FUNC, "UPN [%s] does not match target user [%s]. "
|
|
|
8ad293 |
+ "Authentication failed.\n", state->authenticated_upn,
|
|
|
8ad293 |
+ state->username);
|
|
|
8ad293 |
+ ret = EACCES;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ DEBUG(SSSDBG_TRACE_FUNC, "User [%s] match UPN [%s]. Authentication was "
|
|
|
8ad293 |
+ "successful.\n", state->username, state->authenticated_upn);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = EOK;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+done:
|
|
|
8ad293 |
+ DEBUG(SSSDBG_TRACE_FUNC, "Returning [%d]: %s\n", ret, sss_strerror(ret));
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (ret == EOK) {
|
|
|
8ad293 |
+ sss_packet_set_error(pctx->creq->out, EOK);
|
|
|
8ad293 |
+ } else {
|
|
|
8ad293 |
+ sss_cmd_send_error(state->cli_ctx, ret);
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ sss_cmd_done(state->cli_ctx, state);
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
diff --git a/src/sss_client/pam_sss_gss.c b/src/sss_client/pam_sss_gss.c
|
|
|
8ad293 |
new file mode 100644
|
|
|
8ad293 |
index 000000000..cd38db7da
|
|
|
8ad293 |
--- /dev/null
|
|
|
8ad293 |
+++ b/src/sss_client/pam_sss_gss.c
|
|
|
8ad293 |
@@ -0,0 +1,588 @@
|
|
|
8ad293 |
+/*
|
|
|
8ad293 |
+ Authors:
|
|
|
8ad293 |
+ Pavel Březina <pbrezina@redhat.com>
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ Copyright (C) 2020 Red Hat
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ This program is free software; you can redistribute it and/or modify
|
|
|
8ad293 |
+ it under the terms of the GNU General Public License as published by
|
|
|
8ad293 |
+ the Free Software Foundation; either version 3 of the License, or
|
|
|
8ad293 |
+ (at your option) any later version.
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ This program is distributed in the hope that it will be useful,
|
|
|
8ad293 |
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
8ad293 |
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
8ad293 |
+ GNU General Public License for more details.
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ You should have received a copy of the GNU General Public License
|
|
|
8ad293 |
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
8ad293 |
+*/
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+#include <stdlib.h>
|
|
|
8ad293 |
+#include <stddef.h>
|
|
|
8ad293 |
+#include <stdbool.h>
|
|
|
8ad293 |
+#include <security/pam_modules.h>
|
|
|
8ad293 |
+#include <security/pam_ext.h>
|
|
|
8ad293 |
+#include <gssapi.h>
|
|
|
8ad293 |
+#include <gssapi/gssapi_ext.h>
|
|
|
8ad293 |
+#include <gssapi/gssapi_generic.h>
|
|
|
8ad293 |
+#include <errno.h>
|
|
|
8ad293 |
+#include <sys/types.h>
|
|
|
8ad293 |
+#include <sys/syslog.h>
|
|
|
8ad293 |
+#include <unistd.h>
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+#include "util/sss_format.h"
|
|
|
8ad293 |
+#include "sss_client/sss_cli.h"
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+bool debug_enabled;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+#define TRACE(pamh, fmt, ...) do { \
|
|
|
8ad293 |
+ if (debug_enabled) { \
|
|
|
8ad293 |
+ pam_info(pamh, "pam_sss_gss: " fmt, ## __VA_ARGS__); \
|
|
|
8ad293 |
+ } \
|
|
|
8ad293 |
+} while (0)
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+#define ERROR(pamh, fmt, ...) do { \
|
|
|
8ad293 |
+ if (debug_enabled) { \
|
|
|
8ad293 |
+ pam_error(pamh, "pam_sss_gss: " fmt, ## __VA_ARGS__); \
|
|
|
8ad293 |
+ pam_syslog(pamh, LOG_ERR, fmt, ## __VA_ARGS__); \
|
|
|
8ad293 |
+ } \
|
|
|
8ad293 |
+} while (0)
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static bool switch_euid(pam_handle_t *pamh, uid_t current, uid_t desired)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ int ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ TRACE(pamh, "Switching euid from %" SPRIuid " to %" SPRIuid, current,
|
|
|
8ad293 |
+ desired);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (current == desired) {
|
|
|
8ad293 |
+ return true;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = seteuid(desired);
|
|
|
8ad293 |
+ if (ret != 0) {
|
|
|
8ad293 |
+ ERROR(pamh, "Unable to set euid to %" SPRIuid, desired);
|
|
|
8ad293 |
+ return false;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return true;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static const char *get_item_as_string(pam_handle_t *pamh, int item)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ const char *str;
|
|
|
8ad293 |
+ int ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = pam_get_item(pamh, item, (void *)&str);
|
|
|
8ad293 |
+ if (ret != PAM_SUCCESS || str == NULL || str[0] == '\0') {
|
|
|
8ad293 |
+ return NULL;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return str;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static errno_t string_to_gss_name(pam_handle_t *pamh,
|
|
|
8ad293 |
+ const char *target,
|
|
|
8ad293 |
+ gss_OID type,
|
|
|
8ad293 |
+ gss_name_t *_name)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ gss_buffer_desc name_buf;
|
|
|
8ad293 |
+ OM_uint32 major;
|
|
|
8ad293 |
+ OM_uint32 minor;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ name_buf.value = (void *)(uintptr_t)target;
|
|
|
8ad293 |
+ name_buf.length = strlen(target);
|
|
|
8ad293 |
+ major = gss_import_name(&minor, &name_buf, type, _name);
|
|
|
8ad293 |
+ if (GSS_ERROR(major)) {
|
|
|
8ad293 |
+ ERROR(pamh, "Could not convert target to GSS name");
|
|
|
8ad293 |
+ return EIO;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return EOK;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static void gssapi_log_status(pam_handle_t *pamh,
|
|
|
8ad293 |
+ int type,
|
|
|
8ad293 |
+ OM_uint32 status_code)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ gss_buffer_desc buf;
|
|
|
8ad293 |
+ OM_uint32 message_context;
|
|
|
8ad293 |
+ OM_uint32 minor;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ message_context = 0;
|
|
|
8ad293 |
+ do {
|
|
|
8ad293 |
+ gss_display_status(&minor, status_code, type, GSS_C_NO_OID,
|
|
|
8ad293 |
+ &message_context, &buf;;
|
|
|
8ad293 |
+ ERROR(pamh, "GSSAPI: %.*s", (int)buf.length, (char *)buf.value);
|
|
|
8ad293 |
+ gss_release_buffer(&minor, &buf;;
|
|
|
8ad293 |
+ } while (message_context != 0);
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static void gssapi_log_error(pam_handle_t *pamh,
|
|
|
8ad293 |
+ OM_uint32 major,
|
|
|
8ad293 |
+ OM_uint32 minor)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ gssapi_log_status(pamh, GSS_C_GSS_CODE, major);
|
|
|
8ad293 |
+ gssapi_log_status(pamh, GSS_C_MECH_CODE, minor);
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static errno_t gssapi_get_creds(pam_handle_t *pamh,
|
|
|
8ad293 |
+ const char *ccache,
|
|
|
8ad293 |
+ const char *target,
|
|
|
8ad293 |
+ const char *upn,
|
|
|
8ad293 |
+ gss_cred_id_t *_creds)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ gss_key_value_set_desc cstore = {0, NULL};
|
|
|
8ad293 |
+ gss_key_value_element_desc el;
|
|
|
8ad293 |
+ gss_name_t name = GSS_C_NO_NAME;
|
|
|
8ad293 |
+ OM_uint32 major;
|
|
|
8ad293 |
+ OM_uint32 minor;
|
|
|
8ad293 |
+ errno_t ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (upn != NULL && upn[0] != '\0') {
|
|
|
8ad293 |
+ TRACE(pamh, "Acquiring credentials for principal [%s]", upn);
|
|
|
8ad293 |
+ ret = string_to_gss_name(pamh, upn, GSS_C_NT_USER_NAME, &name);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+ } else {
|
|
|
8ad293 |
+ TRACE(pamh, "Acquiring credentials, principal name will be derived");
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (ccache != NULL) {
|
|
|
8ad293 |
+ el.key = "ccache";
|
|
|
8ad293 |
+ el.value = ccache;
|
|
|
8ad293 |
+ cstore.count = 1;
|
|
|
8ad293 |
+ cstore.elements = ⪙
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ major = gss_acquire_cred_from(&minor, name, GSS_C_INDEFINITE,
|
|
|
8ad293 |
+ GSS_C_NO_OID_SET, GSS_C_INITIATE,
|
|
|
8ad293 |
+ &cstore, _creds, NULL, NULL);
|
|
|
8ad293 |
+ if (GSS_ERROR(major)) {
|
|
|
8ad293 |
+ /* TODO: Do not hardcode the error code. */
|
|
|
8ad293 |
+ if (minor == 2529639053 && name != GSS_C_NO_NAME) {
|
|
|
8ad293 |
+ /* Hint principal was not found. Try again and let GSSAPI choose. */
|
|
|
8ad293 |
+ TRACE(pamh, "Principal [%s] was not found in ccache", upn);
|
|
|
8ad293 |
+ ret = gssapi_get_creds(pamh, ccache, target, NULL, _creds);
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ } else {
|
|
|
8ad293 |
+ ERROR(pamh, "Unable to read credentials from [%s] "
|
|
|
8ad293 |
+ "[maj:0x%x, min:0x%x]", ccache == NULL ? "default" : ccache,
|
|
|
8ad293 |
+ major, minor);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ gssapi_log_error(pamh, major, minor);
|
|
|
8ad293 |
+ ret = EIO;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = EOK;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+done:
|
|
|
8ad293 |
+ gss_release_name(&minor, &name);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static errno_t sssd_gssapi_init_send(pam_handle_t *pamh,
|
|
|
8ad293 |
+ const char *pam_service,
|
|
|
8ad293 |
+ const char *pam_user,
|
|
|
8ad293 |
+ uint8_t **_reply,
|
|
|
8ad293 |
+ size_t *_reply_len)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ struct sss_cli_req_data req_data;
|
|
|
8ad293 |
+ size_t service_len;
|
|
|
8ad293 |
+ size_t user_len;
|
|
|
8ad293 |
+ uint8_t *data;
|
|
|
8ad293 |
+ errno_t ret;
|
|
|
8ad293 |
+ int ret_errno;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (pam_service == NULL || pam_user == NULL) {
|
|
|
8ad293 |
+ return EINVAL;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ service_len = strlen(pam_service) + 1;
|
|
|
8ad293 |
+ user_len = strlen(pam_user) + 1;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ req_data.len = (service_len + user_len) * sizeof(char);
|
|
|
8ad293 |
+ data = (uint8_t*)malloc(req_data.len);
|
|
|
8ad293 |
+ if (data == NULL) {
|
|
|
8ad293 |
+ return ENOMEM;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ memcpy(data, pam_service, service_len);
|
|
|
8ad293 |
+ memcpy(data + service_len, pam_user, user_len);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ req_data.data = data;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = sss_pam_make_request(SSS_GSSAPI_INIT, &req_data, _reply, _reply_len,
|
|
|
8ad293 |
+ &ret_errno);
|
|
|
8ad293 |
+ free(data);
|
|
|
8ad293 |
+ if (ret != PAM_SUCCESS) {
|
|
|
8ad293 |
+ if (ret_errno == ENOTSUP) {
|
|
|
8ad293 |
+ TRACE(pamh, "GSSAPI authentication is not supported for user %s "
|
|
|
8ad293 |
+ "and service %s", pam_user, pam_service);
|
|
|
8ad293 |
+ return ret_errno;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ERROR(pamh, "Communication error [%d, %d]: %s; %s", ret, ret_errno,
|
|
|
8ad293 |
+ pam_strerror(pamh, ret), strerror(ret_errno));
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return (ret_errno != EOK) ? ret_errno : EIO;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return ret_errno;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static errno_t sssd_gssapi_init_recv(uint8_t *reply,
|
|
|
8ad293 |
+ size_t reply_len,
|
|
|
8ad293 |
+ char **_username,
|
|
|
8ad293 |
+ char **_domain,
|
|
|
8ad293 |
+ char **_target,
|
|
|
8ad293 |
+ char **_upn)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ char *username = NULL;
|
|
|
8ad293 |
+ char *domain = NULL;
|
|
|
8ad293 |
+ char *target = NULL;
|
|
|
8ad293 |
+ char *upn = NULL;
|
|
|
8ad293 |
+ const char *buf;
|
|
|
8ad293 |
+ size_t pctr = 0;
|
|
|
8ad293 |
+ size_t dlen;
|
|
|
8ad293 |
+ errno_t ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ username = malloc(reply_len * sizeof(char));
|
|
|
8ad293 |
+ domain = malloc(reply_len * sizeof(char));
|
|
|
8ad293 |
+ target = malloc(reply_len * sizeof(char));
|
|
|
8ad293 |
+ upn = malloc(reply_len * sizeof(char));
|
|
|
8ad293 |
+ if (username == NULL || domain == NULL || target == NULL || upn == NULL) {
|
|
|
8ad293 |
+ return ENOMEM;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ buf = (const char*)reply;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ dlen = reply_len;
|
|
|
8ad293 |
+ ret = sss_readrep_copy_string(buf, &pctr, &reply_len, &dlen, &username,
|
|
|
8ad293 |
+ NULL);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ dlen = reply_len;
|
|
|
8ad293 |
+ ret = sss_readrep_copy_string(buf, &pctr, &reply_len, &dlen, &domain, NULL);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ dlen = reply_len;
|
|
|
8ad293 |
+ ret = sss_readrep_copy_string(buf, &pctr, &reply_len, &dlen, &target, NULL);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ dlen = reply_len;
|
|
|
8ad293 |
+ ret = sss_readrep_copy_string(buf, &pctr, &reply_len, &dlen, &upn, NULL);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ *_username = username;
|
|
|
8ad293 |
+ *_domain = domain;
|
|
|
8ad293 |
+ *_target = target;
|
|
|
8ad293 |
+ *_upn = upn;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+done:
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ free(username);
|
|
|
8ad293 |
+ free(domain);
|
|
|
8ad293 |
+ free(target);
|
|
|
8ad293 |
+ free(upn);
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static errno_t sssd_gssapi_init(pam_handle_t *pamh,
|
|
|
8ad293 |
+ const char *pam_service,
|
|
|
8ad293 |
+ const char *pam_user,
|
|
|
8ad293 |
+ char **_username,
|
|
|
8ad293 |
+ char **_domain,
|
|
|
8ad293 |
+ char **_target,
|
|
|
8ad293 |
+ char **_upn)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ size_t reply_len;
|
|
|
8ad293 |
+ uint8_t *reply;
|
|
|
8ad293 |
+ errno_t ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = sssd_gssapi_init_send(pamh, pam_service, pam_user, &reply,
|
|
|
8ad293 |
+ &reply_len);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = sssd_gssapi_init_recv(reply, reply_len, _username, _domain, _target,
|
|
|
8ad293 |
+ _upn);
|
|
|
8ad293 |
+ free(reply);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static errno_t sssd_establish_sec_ctx_send(pam_handle_t *pamh,
|
|
|
8ad293 |
+ const char *pam_service,
|
|
|
8ad293 |
+ const char *username,
|
|
|
8ad293 |
+ const char *domain,
|
|
|
8ad293 |
+ const void *gss_data,
|
|
|
8ad293 |
+ size_t gss_data_len,
|
|
|
8ad293 |
+ void **_reply,
|
|
|
8ad293 |
+ size_t *_reply_len)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ struct sss_cli_req_data req_data;
|
|
|
8ad293 |
+ size_t username_len;
|
|
|
8ad293 |
+ size_t service_len;
|
|
|
8ad293 |
+ size_t domain_len;
|
|
|
8ad293 |
+ uint8_t *data;
|
|
|
8ad293 |
+ int ret_errno;
|
|
|
8ad293 |
+ int ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ service_len = strlen(pam_service) + 1;
|
|
|
8ad293 |
+ username_len = strlen(username) + 1;
|
|
|
8ad293 |
+ domain_len = strlen(domain) + 1;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ req_data.len = (service_len + username_len + domain_len) * sizeof(char)
|
|
|
8ad293 |
+ + gss_data_len;
|
|
|
8ad293 |
+ data = malloc(req_data.len);
|
|
|
8ad293 |
+ if (data == NULL) {
|
|
|
8ad293 |
+ return ENOMEM;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ memcpy(data, pam_service, service_len);
|
|
|
8ad293 |
+ memcpy(data + service_len, username, username_len);
|
|
|
8ad293 |
+ memcpy(data + service_len + username_len, domain, domain_len);
|
|
|
8ad293 |
+ memcpy(data + service_len + username_len + domain_len, gss_data,
|
|
|
8ad293 |
+ gss_data_len);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ req_data.data = data;
|
|
|
8ad293 |
+ ret = sss_pam_make_request(SSS_GSSAPI_SEC_CTX, &req_data, (uint8_t**)_reply,
|
|
|
8ad293 |
+ _reply_len, &ret_errno);
|
|
|
8ad293 |
+ free(data);
|
|
|
8ad293 |
+ if (ret != PAM_SUCCESS) {
|
|
|
8ad293 |
+ /* ENOTSUP should not happend here so let's keep it as generic error. */
|
|
|
8ad293 |
+ ERROR(pamh, "Communication error [%d, %d]: %s; %s", ret, ret_errno,
|
|
|
8ad293 |
+ pam_strerror(pamh, ret), strerror(ret_errno));
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return (ret_errno != EOK) ? ret_errno : EIO;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return ret_errno;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static int sssd_establish_sec_ctx(pam_handle_t *pamh,
|
|
|
8ad293 |
+ const char *ccache,
|
|
|
8ad293 |
+ const char *pam_service,
|
|
|
8ad293 |
+ const char *username,
|
|
|
8ad293 |
+ const char *domain,
|
|
|
8ad293 |
+ const char *target,
|
|
|
8ad293 |
+ const char *upn)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
|
|
|
8ad293 |
+ gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
|
|
|
8ad293 |
+ gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
|
|
|
8ad293 |
+ OM_uint32 flags = GSS_C_MUTUAL_FLAG;
|
|
|
8ad293 |
+ gss_name_t gss_name;
|
|
|
8ad293 |
+ gss_cred_id_t creds;
|
|
|
8ad293 |
+ OM_uint32 ret_flags;
|
|
|
8ad293 |
+ OM_uint32 major;
|
|
|
8ad293 |
+ OM_uint32 minor;
|
|
|
8ad293 |
+ int ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = gssapi_get_creds(pamh, ccache, target, upn, &creds);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = string_to_gss_name(pamh, target, GSS_C_NT_HOSTBASED_SERVICE, &gss_name);
|
|
|
8ad293 |
+ if (ret != 0) {
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ do {
|
|
|
8ad293 |
+ major = gss_init_sec_context(&minor, creds, &ctx,
|
|
|
8ad293 |
+ gss_name, GSS_C_NO_OID, flags, 0, NULL,
|
|
|
8ad293 |
+ &input, NULL, &output,
|
|
|
8ad293 |
+ &ret_flags, NULL);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ free(input.value);
|
|
|
8ad293 |
+ memset(&input, 0, sizeof(gss_buffer_desc));
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if (GSS_ERROR(major)) {
|
|
|
8ad293 |
+ ERROR(pamh, "Unable to establish GSS context [maj:0x%x, min:0x%x]",
|
|
|
8ad293 |
+ major, minor);
|
|
|
8ad293 |
+ gssapi_log_error(pamh, major, minor);
|
|
|
8ad293 |
+ ret = EIO;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ } else if (major == GSS_S_CONTINUE_NEEDED || output.length > 0) {
|
|
|
8ad293 |
+ ret = sssd_establish_sec_ctx_send(pamh, pam_service,
|
|
|
8ad293 |
+ username, domain,
|
|
|
8ad293 |
+ output.value, output.length,
|
|
|
8ad293 |
+ &input.value, &input.length);
|
|
|
8ad293 |
+ gss_release_buffer(NULL, &output);
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+ } while (major != GSS_S_COMPLETE);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ if ((ret_flags & flags) != flags) {
|
|
|
8ad293 |
+ ERROR(pamh, "Negotiated context does not support requested flags\n");
|
|
|
8ad293 |
+ ret = EIO;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ ret = EOK;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+done:
|
|
|
8ad293 |
+ gss_delete_sec_context(&minor, &ctx, NULL);
|
|
|
8ad293 |
+ gss_release_name(&minor, &gss_name);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return ret;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+static int errno_to_pam(pam_handle_t *pamh, errno_t ret)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ switch (ret) {
|
|
|
8ad293 |
+ case EOK:
|
|
|
8ad293 |
+ TRACE(pamh, "Authentication successful");
|
|
|
8ad293 |
+ return PAM_SUCCESS;
|
|
|
8ad293 |
+ case ENOENT:
|
|
|
8ad293 |
+ TRACE(pamh, "User not found");
|
|
|
8ad293 |
+ return PAM_USER_UNKNOWN;
|
|
|
8ad293 |
+ case ENOTSUP:
|
|
|
8ad293 |
+ TRACE(pamh, "GSSAPI authentication is not enabled "
|
|
|
8ad293 |
+ "for given user and service");
|
|
|
8ad293 |
+ return PAM_USER_UNKNOWN;
|
|
|
8ad293 |
+ case ESSS_NO_SOCKET:
|
|
|
8ad293 |
+ TRACE(pamh, "SSSD socket does not exist");
|
|
|
8ad293 |
+ return PAM_AUTHINFO_UNAVAIL;
|
|
|
8ad293 |
+ case EPERM:
|
|
|
8ad293 |
+ TRACE(pamh, "Authentication failed");
|
|
|
8ad293 |
+ return PAM_AUTH_ERR;
|
|
|
8ad293 |
+ default:
|
|
|
8ad293 |
+ TRACE(pamh, "System error [%d]: %s",
|
|
|
8ad293 |
+ ret, strerror(ret));
|
|
|
8ad293 |
+ return PAM_SYSTEM_ERR;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+int pam_sm_authenticate(pam_handle_t *pamh,
|
|
|
8ad293 |
+ int flags,
|
|
|
8ad293 |
+ int argc,
|
|
|
8ad293 |
+ const char **argv)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ const char *pam_service;
|
|
|
8ad293 |
+ const char *pam_user;
|
|
|
8ad293 |
+ const char *ccache;
|
|
|
8ad293 |
+ char *username = NULL;
|
|
|
8ad293 |
+ char *domain = NULL;
|
|
|
8ad293 |
+ char *target = NULL;
|
|
|
8ad293 |
+ char *upn = NULL;
|
|
|
8ad293 |
+ uid_t uid;
|
|
|
8ad293 |
+ uid_t euid;
|
|
|
8ad293 |
+ errno_t ret;
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ debug_enabled = false;
|
|
|
8ad293 |
+ for (int i = 0; i < argc; i++) {
|
|
|
8ad293 |
+ if (strcmp(argv[i], "debug") == 0) {
|
|
|
8ad293 |
+ debug_enabled = true;
|
|
|
8ad293 |
+ break;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ /* Get non-default ccache if specified, may be NULL. */
|
|
|
8ad293 |
+ ccache = getenv("KRB5CCNAME");
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ uid = getuid();
|
|
|
8ad293 |
+ euid = geteuid();
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ /* Read PAM data. */
|
|
|
8ad293 |
+ pam_service = get_item_as_string(pamh, PAM_SERVICE);
|
|
|
8ad293 |
+ pam_user = get_item_as_string(pamh, PAM_USER);
|
|
|
8ad293 |
+ if (pam_service == NULL || pam_user == NULL) {
|
|
|
8ad293 |
+ ERROR(pamh, "Unable to get PAM data!");
|
|
|
8ad293 |
+ ret = EINVAL;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ /* Initialize GSSAPI authentication with SSSD. Get user domain
|
|
|
8ad293 |
+ * and target GSS service name. */
|
|
|
8ad293 |
+ TRACE(pamh, "Initializing GSSAPI authentication with SSSD");
|
|
|
8ad293 |
+ ret = sssd_gssapi_init(pamh, pam_service, pam_user, &username, &domain,
|
|
|
8ad293 |
+ &target, &upn;;
|
|
|
8ad293 |
+ if (ret != EOK) {
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ /* PAM is often called from set-user-id applications (sudo, su). we want to
|
|
|
8ad293 |
+ * make sure that we access credentials of the caller (real uid). */
|
|
|
8ad293 |
+ if (!switch_euid(pamh, euid, uid)) {
|
|
|
8ad293 |
+ ret = EFAULT;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ /* Authenticate the user by estabilishing security context. Authorization is
|
|
|
8ad293 |
+ * expected to be done by other modules through pam_access. */
|
|
|
8ad293 |
+ TRACE(pamh, "Trying to establish security context");
|
|
|
8ad293 |
+ TRACE(pamh, "SSSD User name: %s", username);
|
|
|
8ad293 |
+ TRACE(pamh, "User domain: %s", domain);
|
|
|
8ad293 |
+ TRACE(pamh, "User principal: %s", upn);
|
|
|
8ad293 |
+ TRACE(pamh, "Target name: %s", target);
|
|
|
8ad293 |
+ TRACE(pamh, "Using ccache: %s", ccache == NULL ? "default" : ccache);
|
|
|
8ad293 |
+ ret = sssd_establish_sec_ctx(pamh, ccache, pam_service,
|
|
|
8ad293 |
+ username, domain, target, upn);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ /* Restore original euid. */
|
|
|
8ad293 |
+ if (!switch_euid(pamh, uid, euid)) {
|
|
|
8ad293 |
+ ret = EFAULT;
|
|
|
8ad293 |
+ goto done;
|
|
|
8ad293 |
+ }
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+done:
|
|
|
8ad293 |
+ sss_pam_close_fd();
|
|
|
8ad293 |
+ free(domain);
|
|
|
8ad293 |
+ free(target);
|
|
|
8ad293 |
+ free(upn);
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+ return errno_to_pam(pamh, ret);
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ return PAM_IGNORE;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ return PAM_IGNORE;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+int pam_sm_open_session(pam_handle_t *pamh,
|
|
|
8ad293 |
+ int flags,
|
|
|
8ad293 |
+ int argc,
|
|
|
8ad293 |
+ const char **argv)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ return PAM_IGNORE;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+int pam_sm_close_session(pam_handle_t *pamh,
|
|
|
8ad293 |
+ int flags,
|
|
|
8ad293 |
+ int argc,
|
|
|
8ad293 |
+ const char **argv)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ return PAM_IGNORE;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
+
|
|
|
8ad293 |
+int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ return PAM_IGNORE;
|
|
|
8ad293 |
+}
|
|
|
8ad293 |
diff --git a/src/sss_client/pam_sss_gss.exports b/src/sss_client/pam_sss_gss.exports
|
|
|
8ad293 |
new file mode 100644
|
|
|
8ad293 |
index 000000000..9afa106be
|
|
|
8ad293 |
--- /dev/null
|
|
|
8ad293 |
+++ b/src/sss_client/pam_sss_gss.exports
|
|
|
8ad293 |
@@ -0,0 +1,4 @@
|
|
|
8ad293 |
+{
|
|
|
8ad293 |
+ global:
|
|
|
8ad293 |
+ *;
|
|
|
8ad293 |
+};
|
|
|
8ad293 |
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
|
|
|
8ad293 |
index d897f43b7..2c3c71bc4 100644
|
|
|
8ad293 |
--- a/src/sss_client/sss_cli.h
|
|
|
8ad293 |
+++ b/src/sss_client/sss_cli.h
|
|
|
8ad293 |
@@ -233,6 +233,8 @@ enum sss_cli_command {
|
|
|
8ad293 |
* an authentication request to find
|
|
|
8ad293 |
* out which authentication methods
|
|
|
8ad293 |
* are available for the given user. */
|
|
|
8ad293 |
+ SSS_GSSAPI_INIT = 0x00FA, /**< Initialize GSSAPI authentication. */
|
|
|
8ad293 |
+ SSS_GSSAPI_SEC_CTX = 0x00FB, /**< Establish GSSAPI security ctx. */
|
|
|
8ad293 |
|
|
|
8ad293 |
/* PAC responder calls */
|
|
|
8ad293 |
SSS_PAC_ADD_PAC_USER = 0x0101,
|
|
|
8ad293 |
@@ -721,4 +723,10 @@ errno_t sss_readrep_copy_string(const char *in,
|
|
|
8ad293 |
char **out,
|
|
|
8ad293 |
size_t *size);
|
|
|
8ad293 |
|
|
|
8ad293 |
+enum pam_gssapi_cmd {
|
|
|
8ad293 |
+ PAM_GSSAPI_GET_NAME,
|
|
|
8ad293 |
+ PAM_GSSAPI_INIT,
|
|
|
8ad293 |
+ PAM_GSSAPI_SENTINEL
|
|
|
8ad293 |
+};
|
|
|
8ad293 |
+
|
|
|
8ad293 |
#endif /* _SSSCLI_H */
|
|
|
8ad293 |
diff --git a/src/tests/dlopen-tests.c b/src/tests/dlopen-tests.c
|
|
|
8ad293 |
index ccf52abe9..bffa02188 100644
|
|
|
8ad293 |
--- a/src/tests/dlopen-tests.c
|
|
|
8ad293 |
+++ b/src/tests/dlopen-tests.c
|
|
|
8ad293 |
@@ -47,6 +47,7 @@ struct so {
|
|
|
8ad293 |
{ "libnss_sss.so", { LIBPFX"libnss_sss.so", NULL } },
|
|
|
8ad293 |
{ "libsss_certmap.so", { LIBPFX"libsss_certmap.so", NULL } },
|
|
|
8ad293 |
{ "pam_sss.so", { LIBPFX"pam_sss.so", NULL } },
|
|
|
8ad293 |
+ { "pam_sss_gss.so", { LIBPFX"pam_sss_gss.so", NULL } },
|
|
|
8ad293 |
#ifdef BUILD_WITH_LIBSECRET
|
|
|
8ad293 |
{ "libsss_secrets.so", { LIBPFX"libsss_secrets.so", NULL } },
|
|
|
8ad293 |
#endif /* BUILD_WITH_LIBSECRET */
|
|
|
8ad293 |
--
|
|
|
8ad293 |
2.21.3
|
|
|
8ad293 |
|