Blame SOURCES/cyrus-sasl-2.1.27-Add-support-for-setting-max-ssf-0-to-GSS-SPNEGO.patch

df6e3e
From 49e965f41257a0ed299c58a7cf1c120ddf944aaa Mon Sep 17 00:00:00 2001
df6e3e
From: Simo Sorce <simo@redhat.com>
df6e3e
Date: Tue, 5 May 2020 14:51:36 -0400
df6e3e
Subject: [PATCH] Add support for setting max ssf 0 to GSS-SPNEGO
df6e3e
df6e3e
Bacport form this proposed PR (still open at bacport time):
df6e3e
https://github.com/cyrusimap/cyrus-sasl/pull/603
df6e3e
df6e3e
Signed-off-by: Simo Sorce <simo@redhat.com>
df6e3e
---
df6e3e
 m4/sasl2.m4          | 13 +++++++
df6e3e
 plugins/gssapi.c     | 44 ++++++++++++++++++++-
df6e3e
 tests/runtests.py    | 91 ++++++++++++++++++++++++++++++++++++++++----
df6e3e
 tests/t_common.c     | 13 ++++---
df6e3e
 tests/t_common.h     |  3 +-
df6e3e
 tests/t_gssapi_cli.c | 25 ++++++++++--
df6e3e
 tests/t_gssapi_srv.c | 28 +++++++++++---
df6e3e
 7 files changed, 194 insertions(+), 23 deletions(-)
df6e3e
df6e3e
diff --git a/m4/sasl2.m4 b/m4/sasl2.m4
df6e3e
index 56e0504..6effe99 100644
df6e3e
--- a/m4/sasl2.m4
df6e3e
+++ b/m4/sasl2.m4
df6e3e
@@ -287,6 +287,19 @@ if test "$gssapi" != no; then
df6e3e
   AC_CHECK_FUNCS(gss_oid_equal)
df6e3e
   LIBS="$cmu_save_LIBS"
df6e3e
 
df6e3e
+  cmu_save_LIBS="$LIBS"
df6e3e
+  LIBS="$LIBS $GSSAPIBASE_LIBS"
df6e3e
+  if test "$ac_cv_header_gssapi_gssapi_krb5_h" = "yes"; then
df6e3e
+    AC_CHECK_DECL(GSS_KRB5_CRED_NO_CI_FLAGS_X,
df6e3e
+                  [AC_DEFINE(HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X,1,
df6e3e
+                             [Define if your GSSAPI implementation supports GSS_KRB5_CRED_NO_CI_FLAGS_X])],,
df6e3e
+                  [
df6e3e
+                    AC_INCLUDES_DEFAULT
df6e3e
+                    #include <gssapi/gssapi_krb5.h>
df6e3e
+                    ])
df6e3e
+  fi
df6e3e
+  LIBS="$cmu_save_LIBS"
df6e3e
+
df6e3e
   cmu_save_LIBS="$LIBS"
df6e3e
   LIBS="$LIBS $GSSAPIBASE_LIBS"
df6e3e
   AC_CHECK_FUNCS(gss_get_name_attribute)
df6e3e
diff --git a/plugins/gssapi.c b/plugins/gssapi.c
df6e3e
index 5d900c5..7480316 100644
df6e3e
--- a/plugins/gssapi.c
df6e3e
+++ b/plugins/gssapi.c
df6e3e
@@ -1783,7 +1783,49 @@ static int gssapi_client_mech_step(void *conn_context,
df6e3e
 		/* We want to try for privacy */
df6e3e
 		req_flags |= GSS_C_CONF_FLAG;
df6e3e
 	    }
df6e3e
-	}
df6e3e
+#ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X
df6e3e
+        /* The krb5 mechanism automatically adds INTEG and CONF flags even when
df6e3e
+         * not specified, this has the effect of rendering explicit requests
df6e3e
+         * of no confidentiality and integrity via setting maxssf 0 moot.
df6e3e
+         * However to interoperate with Windows machines it needs to be
df6e3e
+         * possible to unset these flags as Windows machines refuse to allow
df6e3e
+         * two layers (say TLS and GSSAPI) to both provide these services.
df6e3e
+         * So if we do not suppress these flags a SASL/GSS-SPNEGO negotiation
df6e3e
+         * over, say, LDAPS will fail against Windows Servers */
df6e3e
+	} else if (params->props.max_ssf == 0) {
df6e3e
+            gss_buffer_desc empty_buffer = GSS_C_EMPTY_BUFFER;
df6e3e
+            if (client_creds == GSS_C_NO_CREDENTIAL) {
df6e3e
+                gss_OID_set_desc mechs = { 0 };
df6e3e
+                gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
df6e3e
+                if (text->mech_type != GSS_C_NO_OID) {
df6e3e
+                    mechs.count = 1;
df6e3e
+                    mechs.elements = text->mech_type;
df6e3e
+                    desired_mechs = &mechs;
df6e3e
+                }
df6e3e
+
df6e3e
+                maj_stat = gss_acquire_cred(&min_stat, GSS_C_NO_NAME,
df6e3e
+                                            GSS_C_INDEFINITE, desired_mechs,
df6e3e
+                                            GSS_C_INITIATE,
df6e3e
+                                            &text->client_creds, NULL, NULL);
df6e3e
+                if (GSS_ERROR(maj_stat)) {
df6e3e
+                    sasl_gss_seterror(text->utils, maj_stat, min_stat);
df6e3e
+                    sasl_gss_free_context_contents(text);
df6e3e
+                    return SASL_FAIL;
df6e3e
+                }
df6e3e
+                client_creds = text->client_creds;
df6e3e
+            }
df6e3e
+
df6e3e
+            maj_stat = gss_set_cred_option(&min_stat, &client_creds,
df6e3e
+                                           (gss_OID)GSS_KRB5_CRED_NO_CI_FLAGS_X,
df6e3e
+                                            &empty_buffer);
df6e3e
+            if (GSS_ERROR(maj_stat)) {
df6e3e
+                sasl_gss_seterror(text->utils, maj_stat, min_stat);
df6e3e
+                sasl_gss_free_context_contents(text);
df6e3e
+                return SASL_FAIL;
df6e3e
+            }
df6e3e
+#endif
df6e3e
+        }
df6e3e
+
df6e3e
 
df6e3e
 	if (params->props.security_flags & SASL_SEC_PASS_CREDENTIALS) {
df6e3e
 	    req_flags = req_flags |  GSS_C_DELEG_FLAG;
df6e3e
diff --git a/tests/runtests.py b/tests/runtests.py
df6e3e
index fc9cf24..4106401 100755
df6e3e
--- a/tests/runtests.py
df6e3e
+++ b/tests/runtests.py
df6e3e
@@ -6,6 +6,7 @@ import os
df6e3e
 import shutil
df6e3e
 import signal
df6e3e
 import subprocess
df6e3e
+import sys
df6e3e
 import time
df6e3e
 from string import Template
df6e3e
 
df6e3e
@@ -149,11 +150,12 @@ def gssapi_basic_test(kenv):
df6e3e
                 srv.returncode, srv.stderr.read().decode('utf-8')))
df6e3e
     except Exception as e:
df6e3e
         print("FAIL: {}".format(e))
df6e3e
-        return
df6e3e
+        return 1
df6e3e
 
df6e3e
     print("PASS: CLI({}) SRV({})".format(
df6e3e
         cli.stdout.read().decode('utf-8').strip(),
df6e3e
         srv.stdout.read().decode('utf-8').strip()))
df6e3e
+    return 0
df6e3e
 
df6e3e
 def gssapi_channel_binding_test(kenv):
df6e3e
     try:
df6e3e
@@ -178,11 +180,12 @@ def gssapi_channel_binding_test(kenv):
df6e3e
                 srv.returncode, srv.stderr.read().decode('utf-8')))
df6e3e
     except Exception as e:
df6e3e
         print("FAIL: {}".format(e))
df6e3e
-        return
df6e3e
+        return 1
df6e3e
 
df6e3e
     print("PASS: CLI({}) SRV({})".format(
df6e3e
         cli.stdout.read().decode('utf-8').strip(),
df6e3e
         srv.stdout.read().decode('utf-8').strip()))
df6e3e
+    return 0
df6e3e
 
df6e3e
 def gssapi_channel_binding_mismatch_test(kenv):
df6e3e
     result = "FAIL"
df6e3e
@@ -212,11 +215,70 @@ def gssapi_channel_binding_mismatch_test(kenv):
df6e3e
                 cli.returncode, cli_err, srv.returncode, srv_err))
df6e3e
     except Exception as e:
df6e3e
         print("{}: {}".format(result, e))
df6e3e
-        return
df6e3e
+        return 0
df6e3e
 
df6e3e
     print("FAIL: This test should fail [CLI({}) SRV({})]".format(
df6e3e
         cli.stdout.read().decode('utf-8').strip(),
df6e3e
         srv.stdout.read().decode('utf-8').strip()))
df6e3e
+    return 1
df6e3e
+
df6e3e
+def gss_spnego_basic_test(kenv):
df6e3e
+    try:
df6e3e
+        srv = subprocess.Popen(["../tests/t_gssapi_srv", "-N"],
df6e3e
+                               stdout=subprocess.PIPE,
df6e3e
+                               stderr=subprocess.PIPE, env=kenv)
df6e3e
+        srv.stdout.readline() # Wait for srv to say it is ready
df6e3e
+        cli = subprocess.Popen(["../tests/t_gssapi_cli", "-N"],
df6e3e
+                               stdout=subprocess.PIPE,
df6e3e
+                               stderr=subprocess.PIPE, env=kenv)
df6e3e
+        try:
df6e3e
+            cli.wait(timeout=5)
df6e3e
+            srv.wait(timeout=5)
df6e3e
+        except Exception as e:
df6e3e
+            print("Failed on {}".format(e));
df6e3e
+            cli.kill()
df6e3e
+            srv.kill()
df6e3e
+        if cli.returncode != 0 or srv.returncode != 0:
df6e3e
+            raise Exception("CLI ({}): {} --> SRV ({}): {}".format(
df6e3e
+                cli.returncode, cli.stderr.read().decode('utf-8'),
df6e3e
+                srv.returncode, srv.stderr.read().decode('utf-8')))
df6e3e
+    except Exception as e:
df6e3e
+        print("FAIL: {}".format(e))
df6e3e
+        return 1
df6e3e
+
df6e3e
+    print("PASS: CLI({}) SRV({})".format(
df6e3e
+        cli.stdout.read().decode('utf-8').strip(),
df6e3e
+        srv.stdout.read().decode('utf-8').strip()))
df6e3e
+    return 0
df6e3e
+
df6e3e
+def gss_spnego_zeromaxssf_test(kenv):
df6e3e
+    try:
df6e3e
+        srv = subprocess.Popen(["../tests/t_gssapi_srv", "-N", "-z"],
df6e3e
+                               stdout=subprocess.PIPE,
df6e3e
+                               stderr=subprocess.PIPE, env=kenv)
df6e3e
+        srv.stdout.readline() # Wait for srv to say it is ready
df6e3e
+        cli = subprocess.Popen(["../tests/t_gssapi_cli", "-N", "-z"],
df6e3e
+                               stdout=subprocess.PIPE,
df6e3e
+                               stderr=subprocess.PIPE, env=kenv)
df6e3e
+        try:
df6e3e
+            cli.wait(timeout=5)
df6e3e
+            srv.wait(timeout=5)
df6e3e
+        except Exception as e:
df6e3e
+            print("Failed on {}".format(e));
df6e3e
+            cli.kill()
df6e3e
+            srv.kill()
df6e3e
+        if cli.returncode != 0 or srv.returncode != 0:
df6e3e
+            raise Exception("CLI ({}): {} --> SRV ({}): {}".format(
df6e3e
+                cli.returncode, cli.stderr.read().decode('utf-8'),
df6e3e
+                srv.returncode, srv.stderr.read().decode('utf-8')))
df6e3e
+    except Exception as e:
df6e3e
+        print("FAIL: {}".format(e))
df6e3e
+        return 1
df6e3e
+
df6e3e
+    print("PASS: CLI({}) SRV({})".format(
df6e3e
+        cli.stdout.read().decode('utf-8').strip(),
df6e3e
+        srv.stdout.read().decode('utf-8').strip()))
df6e3e
+    return 0
df6e3e
 
df6e3e
 def gssapi_tests(testdir):
df6e3e
     """ SASL/GSSAPI Tests """
df6e3e
@@ -225,20 +287,32 @@ def gssapi_tests(testdir):
df6e3e
     #print("KDC: {}, ENV: {}".format(kdc, kenv))
df6e3e
     kenv['KRB5_TRACE'] = os.path.join(testdir, 'trace.log')
df6e3e
 
df6e3e
+    err = 0
df6e3e
+
df6e3e
     print('GSSAPI BASIC:')
df6e3e
     print('    ', end='')
df6e3e
-    gssapi_basic_test(kenv)
df6e3e
+    err += gssapi_basic_test(kenv)
df6e3e
 
df6e3e
     print('GSSAPI CHANNEL BINDING:')
df6e3e
     print('    ', end='')
df6e3e
-    gssapi_channel_binding_test(kenv)
df6e3e
+    err += gssapi_channel_binding_test(kenv)
df6e3e
 
df6e3e
     print('GSSAPI CHANNEL BINDING MISMTACH:')
df6e3e
     print('    ', end='')
df6e3e
-    gssapi_channel_binding_mismatch_test(kenv)
df6e3e
+    err += gssapi_channel_binding_mismatch_test(kenv)
df6e3e
+
df6e3e
+    print('GSS-SPNEGO BASIC:')
df6e3e
+    print('    ', end='')
df6e3e
+    err += gss_spnego_basic_test(kenv)
df6e3e
+
df6e3e
+    print('GSS-SPNEGO 0 MAXSSF:')
df6e3e
+    print('    ', end='')
df6e3e
+    err += gss_spnego_zeromaxssf_test(kenv)
df6e3e
 
df6e3e
     os.killpg(kdc.pid, signal.SIGTERM)
df6e3e
 
df6e3e
+    return err
df6e3e
+
df6e3e
 
df6e3e
 if __name__ == "__main__":
df6e3e
 
df6e3e
@@ -253,4 +327,7 @@ if __name__ == "__main__":
df6e3e
         shutil.rmtree(T)
df6e3e
     os.makedirs(T)
df6e3e
 
df6e3e
-    gssapi_tests(T)
df6e3e
+    err = gssapi_tests(T)
df6e3e
+    if err != 0:
df6e3e
+        print('{} test(s) FAILED'.format(err))
df6e3e
+        sys.exit(-1)
df6e3e
diff --git a/tests/t_common.c b/tests/t_common.c
df6e3e
index 478e6a1..f56098e 100644
df6e3e
--- a/tests/t_common.c
df6e3e
+++ b/tests/t_common.c
df6e3e
@@ -23,20 +23,21 @@ void send_string(int sd, const char *s, unsigned int l)
df6e3e
     if (ret != l) s_error("send data", ret, l, errno);
df6e3e
 }
df6e3e
 
df6e3e
-void recv_string(int sd, char *buf, unsigned int *buflen)
df6e3e
+void recv_string(int sd, char *buf, unsigned int *buflen, bool allow_eof)
df6e3e
 {
df6e3e
+    unsigned int bufsize = *buflen;
df6e3e
     unsigned int l;
df6e3e
     ssize_t ret;
df6e3e
 
df6e3e
+    *buflen = 0;
df6e3e
+
df6e3e
     ret = recv(sd, &l, sizeof(l), MSG_WAITALL);
df6e3e
+    if (allow_eof && ret == 0) return;
df6e3e
     if (ret != sizeof(l)) s_error("recv size", ret, sizeof(l), errno);
df6e3e
 
df6e3e
-    if (l == 0) {
df6e3e
-        *buflen = 0;
df6e3e
-        return;
df6e3e
-    }
df6e3e
+    if (l == 0) return;
df6e3e
 
df6e3e
-    if (*buflen < l) s_error("recv len", l, *buflen, E2BIG);
df6e3e
+    if (bufsize < l) s_error("recv len", l, bufsize, E2BIG);
df6e3e
 
df6e3e
     ret = recv(sd, buf, l, 0);
df6e3e
     if (ret != l) s_error("recv data", ret, l, errno);
df6e3e
diff --git a/tests/t_common.h b/tests/t_common.h
df6e3e
index a10def1..be24a53 100644
df6e3e
--- a/tests/t_common.h
df6e3e
+++ b/tests/t_common.h
df6e3e
@@ -4,6 +4,7 @@
df6e3e
 #include "config.h"
df6e3e
 
df6e3e
 #include <errno.h>
df6e3e
+#include <stdbool.h>
df6e3e
 #include <stdio.h>
df6e3e
 #include <sys/socket.h>
df6e3e
 
df6e3e
@@ -12,7 +13,7 @@
df6e3e
 
df6e3e
 void s_error(const char *hdr, ssize_t ret, ssize_t len, int err);
df6e3e
 void send_string(int sd, const char *s, unsigned int l);
df6e3e
-void recv_string(int sd, char *buf, unsigned int *buflen);
df6e3e
+void recv_string(int sd, char *buf, unsigned int *buflen, bool allow_eof);
df6e3e
 void saslerr(int why, const char *what);
df6e3e
 int getpath(void *context __attribute__((unused)), const char **path);
df6e3e
 void parse_cb(sasl_channel_binding_t *cb, char *buf, unsigned max, char *in);
df6e3e
diff --git a/tests/t_gssapi_cli.c b/tests/t_gssapi_cli.c
df6e3e
index a44a3f5..d9eafe1 100644
df6e3e
--- a/tests/t_gssapi_cli.c
df6e3e
+++ b/tests/t_gssapi_cli.c
df6e3e
@@ -46,12 +46,21 @@ int main(int argc, char *argv[])
df6e3e
     char cb_buf[256];
df6e3e
     int sd;
df6e3e
     int c, r;
df6e3e
+    const char *sasl_mech = "GSSAPI";
df6e3e
+    bool spnego = false;
df6e3e
+    bool zeromaxssf = false;
df6e3e
 
df6e3e
-    while ((c = getopt(argc, argv, "c:")) != EOF) {
df6e3e
+    while ((c = getopt(argc, argv, "c:zN")) != EOF) {
df6e3e
         switch (c) {
df6e3e
         case 'c':
df6e3e
             parse_cb(&cb, cb_buf, 256, optarg);
df6e3e
             break;
df6e3e
+        case 'z':
df6e3e
+            zeromaxssf = true;
df6e3e
+            break;
df6e3e
+        case 'N':
df6e3e
+            spnego = true;
df6e3e
+            break;
df6e3e
         default:
df6e3e
             break;
df6e3e
         }
df6e3e
@@ -78,7 +87,17 @@ int main(int argc, char *argv[])
df6e3e
         sasl_setprop(conn, SASL_CHANNEL_BINDING, &cb;;
df6e3e
     }
df6e3e
 
df6e3e
-    r = sasl_client_start(conn, "GSSAPI", NULL, &data, &len, &chosenmech);
df6e3e
+    if (spnego) {
df6e3e
+        sasl_mech = "GSS-SPNEGO";
df6e3e
+    }
df6e3e
+
df6e3e
+    if (zeromaxssf) {
df6e3e
+        /* set all security properties to 0 including maxssf */
df6e3e
+        sasl_security_properties_t secprops = { 0 };
df6e3e
+        sasl_setprop(conn, SASL_SEC_PROPS, &secprops);
df6e3e
+    }
df6e3e
+
df6e3e
+    r = sasl_client_start(conn, sasl_mech, NULL, &data, &len, &chosenmech);
df6e3e
     if (r != SASL_OK && r != SASL_CONTINUE) {
df6e3e
 	saslerr(r, "starting SASL negotiation");
df6e3e
 	printf("\n%s\n", sasl_errdetail(conn));
df6e3e
@@ -90,7 +109,7 @@ int main(int argc, char *argv[])
df6e3e
     while (r == SASL_CONTINUE) {
df6e3e
         send_string(sd, data, len);
df6e3e
         len = 8192;
df6e3e
-        recv_string(sd, buf, &len;;
df6e3e
+        recv_string(sd, buf, &len, false);
df6e3e
 
df6e3e
 	r = sasl_client_step(conn, buf, len, NULL, &data, &len;;
df6e3e
 	if (r != SASL_OK && r != SASL_CONTINUE) {
df6e3e
diff --git a/tests/t_gssapi_srv.c b/tests/t_gssapi_srv.c
df6e3e
index ef1217f..448a218 100644
df6e3e
--- a/tests/t_gssapi_srv.c
df6e3e
+++ b/tests/t_gssapi_srv.c
df6e3e
@@ -56,12 +56,21 @@ int main(int argc, char *argv[])
df6e3e
     unsigned char cb_buf[256];
df6e3e
     int sd;
df6e3e
     int c, r;
df6e3e
+    const char *sasl_mech = "GSSAPI";
df6e3e
+    bool spnego = false;
df6e3e
+    bool zeromaxssf = false;
df6e3e
 
df6e3e
-    while ((c = getopt(argc, argv, "c:")) != EOF) {
df6e3e
+    while ((c = getopt(argc, argv, "c:zN")) != EOF) {
df6e3e
         switch (c) {
df6e3e
         case 'c':
df6e3e
             parse_cb(&cb, cb_buf, 256, optarg);
df6e3e
             break;
df6e3e
+        case 'z':
df6e3e
+            zeromaxssf = true;
df6e3e
+            break;
df6e3e
+        case 'N':
df6e3e
+            spnego = true;
df6e3e
+            break;
df6e3e
         default:
df6e3e
             break;
df6e3e
         }
df6e3e
@@ -90,12 +99,22 @@ int main(int argc, char *argv[])
df6e3e
         sasl_setprop(conn, SASL_CHANNEL_BINDING, &cb;;
df6e3e
     }
df6e3e
 
df6e3e
+    if (spnego) {
df6e3e
+        sasl_mech = "GSS-SPNEGO";
df6e3e
+    }
df6e3e
+
df6e3e
+    if (zeromaxssf) {
df6e3e
+        /* set all security properties to 0 including maxssf */
df6e3e
+        sasl_security_properties_t secprops = { 0 };
df6e3e
+        sasl_setprop(conn, SASL_SEC_PROPS, &secprops);
df6e3e
+    }
df6e3e
+
df6e3e
     sd = setup_socket();
df6e3e
 
df6e3e
     len = 8192;
df6e3e
-    recv_string(sd, buf, &len;;
df6e3e
+    recv_string(sd, buf, &len, false);
df6e3e
 
df6e3e
-    r = sasl_server_start(conn, "GSSAPI", buf, len, &data, &len;;
df6e3e
+    r = sasl_server_start(conn, sasl_mech, buf, len, &data, &len;;
df6e3e
     if (r != SASL_OK && r != SASL_CONTINUE) {
df6e3e
 	saslerr(r, "starting SASL negotiation");
df6e3e
 	printf("\n%s\n", sasl_errdetail(conn));
df6e3e
@@ -105,7 +124,7 @@ int main(int argc, char *argv[])
df6e3e
     while (r == SASL_CONTINUE) {
df6e3e
         send_string(sd, data, len);
df6e3e
         len = 8192;
df6e3e
-        recv_string(sd, buf, &len;;
df6e3e
+        recv_string(sd, buf, &len, true);
df6e3e
 
df6e3e
 	r = sasl_server_step(conn, buf, len, &data, &len;;
df6e3e
 	if (r != SASL_OK && r != SASL_CONTINUE) {
df6e3e
@@ -113,7 +132,6 @@ int main(int argc, char *argv[])
df6e3e
 	    printf("\n%s\n", sasl_errdetail(conn));
df6e3e
 	    exit(-1);
df6e3e
 	}
df6e3e
-
df6e3e
     }
df6e3e
 
df6e3e
     if (r != SASL_OK) exit(-1);
df6e3e
-- 
df6e3e
2.18.2
df6e3e