Blame SOURCES/Add-three-kvno-options-from-Heimdal-kgetcred.patch

f7a442
From a1f38973435b60c7f147abfca12b95c6a0a64406 Mon Sep 17 00:00:00 2001
f7a442
From: Greg Hudson <ghudson@mit.edu>
f7a442
Date: Wed, 17 Jun 2020 20:48:38 -0400
f7a442
Subject: [PATCH] Add three kvno options from Heimdal kgetcred
f7a442
f7a442
Add the flags --cached-only and --no-store, which pass the
f7a442
corresponding options to krb5_get_credentials().  Add the option
f7a442
--out-cache to write the retrieved credentials to a specified output
f7a442
cache.
f7a442
f7a442
Add a Python test script for kvno command-line options, including
f7a442
tests for the new options.
f7a442
f7a442
ticket: 8917 (new)
f7a442
---
f7a442
 doc/user/user_commands/kvno.rst |  13 ++++
f7a442
 src/clients/kvno/Makefile.in    |   3 +
f7a442
 src/clients/kvno/kvno.c         | 115 +++++++++++++++++++++++---------
f7a442
 src/clients/kvno/t_kvno.py      |  75 +++++++++++++++++++++
f7a442
 src/man/kvno.man                |  13 ++++
f7a442
 5 files changed, 187 insertions(+), 32 deletions(-)
f7a442
 create mode 100644 src/clients/kvno/t_kvno.py
f7a442
f7a442
diff --git a/doc/user/user_commands/kvno.rst b/doc/user/user_commands/kvno.rst
f7a442
index 3892f0ca5..718313576 100644
f7a442
--- a/doc/user/user_commands/kvno.rst
f7a442
+++ b/doc/user/user_commands/kvno.rst
f7a442
@@ -74,6 +74,19 @@ OPTIONS
f7a442
     client principal with the X.509 certificate in *cert_file*.  The
f7a442
     certificate file must be in PEM format.
f7a442
 
f7a442
+**--cached-only**
f7a442
+    Only retrieve credentials already present in the cache, not from
f7a442
+    the KDC.
f7a442
+
f7a442
+**--no-store**
f7a442
+    Do not store retrieved credentials in the cache.  If
f7a442
+    **--out-cache** is also specified, credentials will still be
f7a442
+    stored into the output credential cache.
f7a442
+
f7a442
+**--out-cache** *ccache*
f7a442
+    Initialize *ccache* and store all retrieved credentials into it.
f7a442
+    Do not store acquired credentials in the input cache.
f7a442
+
f7a442
 **--u2u** *ccache*
f7a442
     Requests a user-to-user ticket.  *ccache* must contain a local
f7a442
     krbtgt ticket for the server principal.  The reported version
f7a442
diff --git a/src/clients/kvno/Makefile.in b/src/clients/kvno/Makefile.in
f7a442
index 1c3f79392..5ba877271 100644
f7a442
--- a/src/clients/kvno/Makefile.in
f7a442
+++ b/src/clients/kvno/Makefile.in
f7a442
@@ -26,6 +26,9 @@ kvno: kvno.o $(KRB5_BASE_DEPLIBS)
f7a442
 ##WIN32##	link $(EXE_LINKOPTS) /out:$@ $**
f7a442
 ##WIN32##	$(_VC_MANIFEST_EMBED_EXE)
f7a442
 
f7a442
+check-pytests: kvno
f7a442
+	$(RUNPYTEST) $(srcdir)/t_kvno.py $(PYTESTFLAGS)
f7a442
+
f7a442
 clean-unix::
f7a442
 	$(RM) kvno.o kvno
f7a442
 
f7a442
diff --git a/src/clients/kvno/kvno.c b/src/clients/kvno/kvno.c
f7a442
index 2472c0cfe..9d85864f6 100644
f7a442
--- a/src/clients/kvno/kvno.c
f7a442
+++ b/src/clients/kvno/kvno.c
f7a442
@@ -44,14 +44,17 @@ xusage()
f7a442
     fprintf(stderr, _("usage: %s [-C] [-u] [-c ccache] [-e etype]\n"), prog);
f7a442
     fprintf(stderr, _("\t[-k keytab] [-S sname] [{-I | -U} for_user | "
f7a442
                       "[-F cert_file] [-P]]\n"));
f7a442
-    fprintf(stderr, _("\t[--u2u ccache] service1 service2 ...\n"));
f7a442
+    fprintf(stderr, _("\t[--cached-only] [--no-store] [--out-cache ccache] "
f7a442
+                      "[--u2u ccache]\n"));
f7a442
+    fprintf(stderr, _("\tservice1 service2 ...\n"));
f7a442
     exit(1);
f7a442
 }
f7a442
 
f7a442
 static void do_v5_kvno(int argc, char *argv[], char *ccachestr, char *etypestr,
f7a442
-                       char *keytab_name, char *sname, int canon, int unknown,
f7a442
-                       char *for_user, int for_user_enterprise,
f7a442
-                       char *for_user_cert_file, int proxy,
f7a442
+                       char *keytab_name, char *sname, int cached_only,
f7a442
+                       int canon, int no_store, int unknown, char *for_user,
f7a442
+                       int for_user_enterprise, char *for_user_cert_file,
f7a442
+                       int proxy, const char *out_ccname,
f7a442
                        const char *u2u_ccname);
f7a442
 
f7a442
 #include <com_err.h>
f7a442
@@ -61,18 +64,21 @@ static void extended_com_err_fn(const char *myprog, errcode_t code,
f7a442
 int
f7a442
 main(int argc, char *argv[])
f7a442
 {
f7a442
-    enum { OPTION_U2U = 256 };
f7a442
-    struct option lopts[] = {
f7a442
-        { "u2u", 1, NULL, OPTION_U2U },
f7a442
-        { NULL, 0, NULL, 0 }
f7a442
-    };
f7a442
+    enum { OPTION_U2U = 256, OPTION_OUT_CACHE = 257 };
f7a442
     const char *shopts = "uCc:e:hk:qPS:I:U:F:";
f7a442
     int option;
f7a442
     char *etypestr = NULL, *ccachestr = NULL, *keytab_name = NULL;
f7a442
     char *sname = NULL, *for_user = NULL, *u2u_ccname = NULL;
f7a442
-    char *for_user_cert_file = NULL;
f7a442
+    char *for_user_cert_file = NULL, *out_ccname = NULL;
f7a442
     int canon = 0, unknown = 0, proxy = 0, for_user_enterprise = 0;
f7a442
-    int impersonate = 0;
f7a442
+    int impersonate = 0, cached_only = 0, no_store = 0;
f7a442
+    struct option lopts[] = {
f7a442
+        { "cached-only", 0, &cached_only, 1 },
f7a442
+        { "no-store", 0, &no_store, 1 },
f7a442
+        { "out-cache", 1, NULL, OPTION_OUT_CACHE },
f7a442
+        { "u2u", 1, NULL, OPTION_U2U },
f7a442
+        { NULL, 0, NULL, 0 }
f7a442
+    };
f7a442
 
f7a442
     setlocale(LC_ALL, "");
f7a442
     set_com_err_hook(extended_com_err_fn);
f7a442
@@ -135,6 +141,12 @@ main(int argc, char *argv[])
f7a442
         case OPTION_U2U:
f7a442
             u2u_ccname = optarg;
f7a442
             break;
f7a442
+        case OPTION_OUT_CACHE:
f7a442
+            out_ccname = optarg;
f7a442
+            break;
f7a442
+        case 0:
f7a442
+            /* If this option set a flag, do nothing else now. */
f7a442
+            break;
f7a442
         default:
f7a442
             xusage();
f7a442
             break;
f7a442
@@ -159,8 +171,9 @@ main(int argc, char *argv[])
f7a442
         xusage();
f7a442
 
f7a442
     do_v5_kvno(argc - optind, argv + optind, ccachestr, etypestr, keytab_name,
f7a442
-               sname, canon, unknown, for_user, for_user_enterprise,
f7a442
-               for_user_cert_file, proxy, u2u_ccname);
f7a442
+               sname, cached_only, canon, no_store, unknown, for_user,
f7a442
+               for_user_enterprise, for_user_cert_file, proxy, out_ccname,
f7a442
+               u2u_ccname);
f7a442
     return 0;
f7a442
 }
f7a442
 
f7a442
@@ -274,14 +287,16 @@ static krb5_error_code
f7a442
 kvno(const char *name, krb5_ccache ccache, krb5_principal me,
f7a442
      krb5_enctype etype, krb5_keytab keytab, const char *sname,
f7a442
      krb5_flags options, int unknown, krb5_principal for_user_princ,
f7a442
-     krb5_data *for_user_cert, int proxy, krb5_data *u2u_ticket)
f7a442
+     krb5_data *for_user_cert, int proxy, krb5_data *u2u_ticket,
f7a442
+     krb5_creds **creds_out)
f7a442
 {
f7a442
     krb5_error_code ret;
f7a442
     krb5_principal server = NULL;
f7a442
     krb5_ticket *ticket = NULL;
f7a442
-    krb5_creds in_creds, *out_creds = NULL;
f7a442
+    krb5_creds in_creds, *creds = NULL;
f7a442
     char *princ = NULL;
f7a442
 
f7a442
+    *creds_out = NULL;
f7a442
     memset(&in_creds, 0, sizeof(in_creds));
f7a442
 
f7a442
     if (sname != NULL) {
f7a442
@@ -321,13 +336,12 @@ kvno(const char *name, krb5_ccache ccache, krb5_principal me,
f7a442
         in_creds.client = for_user_princ;
f7a442
         in_creds.server = me;
f7a442
         ret = krb5_get_credentials_for_user(context, options, ccache,
f7a442
-                                            &in_creds, for_user_cert,
f7a442
-                                            &out_creds);
f7a442
+                                            &in_creds, for_user_cert, &creds);
f7a442
     } else {
f7a442
         in_creds.client = me;
f7a442
         in_creds.server = server;
f7a442
         ret = krb5_get_credentials(context, options, ccache, &in_creds,
f7a442
-                                   &out_creds);
f7a442
+                                   &creds);
f7a442
     }
f7a442
 
f7a442
     if (ret) {
f7a442
@@ -336,7 +350,7 @@ kvno(const char *name, krb5_ccache ccache, krb5_principal me,
f7a442
     }
f7a442
 
f7a442
     /* We need a native ticket. */
f7a442
-    ret = krb5_decode_ticket(&out_creds->ticket, &ticket);
f7a442
+    ret = krb5_decode_ticket(&creds->ticket, &ticket);
f7a442
     if (ret) {
f7a442
         com_err(prog, ret, _("while decoding ticket for %s"), princ);
f7a442
         goto cleanup;
f7a442
@@ -362,15 +376,15 @@ kvno(const char *name, krb5_ccache ccache, krb5_principal me,
f7a442
     }
f7a442
 
f7a442
     if (proxy) {
f7a442
-        in_creds.client = out_creds->client;
f7a442
-        out_creds->client = NULL;
f7a442
-        krb5_free_creds(context, out_creds);
f7a442
-        out_creds = NULL;
f7a442
+        in_creds.client = creds->client;
f7a442
+        creds->client = NULL;
f7a442
+        krb5_free_creds(context, creds);
f7a442
+        creds = NULL;
f7a442
         in_creds.server = server;
f7a442
 
f7a442
         ret = krb5_get_credentials_for_proxy(context, KRB5_GC_CANONICALIZE,
f7a442
                                              ccache, &in_creds, ticket,
f7a442
-                                             &out_creds);
f7a442
+                                             &creds);
f7a442
         krb5_free_principal(context, in_creds.client);
f7a442
         if (ret) {
f7a442
             com_err(prog, ret, _("%s: constrained delegation failed"),
f7a442
@@ -379,10 +393,13 @@ kvno(const char *name, krb5_ccache ccache, krb5_principal me,
f7a442
         }
f7a442
     }
f7a442
 
f7a442
+    *creds_out = creds;
f7a442
+    creds = NULL;
f7a442
+
f7a442
 cleanup:
f7a442
     krb5_free_principal(context, server);
f7a442
     krb5_free_ticket(context, ticket);
f7a442
-    krb5_free_creds(context, out_creds);
f7a442
+    krb5_free_creds(context, creds);
f7a442
     krb5_free_unparsed_name(context, princ);
f7a442
     return ret;
f7a442
 }
f7a442
@@ -428,19 +445,28 @@ cleanup:
f7a442
 
f7a442
 static void
f7a442
 do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,
f7a442
-           char *keytab_name, char *sname, int canon, int unknown,
f7a442
-           char *for_user, int for_user_enterprise,
f7a442
-           char *for_user_cert_file, int proxy, const char *u2u_ccname)
f7a442
+           char *keytab_name, char *sname, int cached_only, int canon,
f7a442
+           int no_store, int unknown, char *for_user, int for_user_enterprise,
f7a442
+           char *for_user_cert_file, int proxy, const char *out_ccname,
f7a442
+           const char *u2u_ccname)
f7a442
 {
f7a442
     krb5_error_code ret;
f7a442
-    int i, errors, flags;
f7a442
+    int i, errors, flags, initialized = 0;
f7a442
     krb5_enctype etype;
f7a442
-    krb5_ccache ccache;
f7a442
+    krb5_ccache ccache, out_ccache = NULL;
f7a442
     krb5_principal me;
f7a442
     krb5_keytab keytab = NULL;
f7a442
     krb5_principal for_user_princ = NULL;
f7a442
-    krb5_flags options = canon ? KRB5_GC_CANONICALIZE : 0;
f7a442
+    krb5_flags options = 0;
f7a442
     krb5_data cert_data = empty_data(), *user_cert = NULL, *u2u_ticket = NULL;
f7a442
+    krb5_creds *creds;
f7a442
+
f7a442
+    if (canon)
f7a442
+        options |= KRB5_GC_CANONICALIZE;
f7a442
+    if (cached_only)
f7a442
+        options |= KRB5_GC_CACHED;
f7a442
+    if (no_store || out_ccname != NULL)
f7a442
+        options |= KRB5_GC_NO_STORE;
f7a442
 
f7a442
     ret = krb5_init_context(&context);
f7a442
     if (ret) {
f7a442
@@ -467,6 +493,14 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,
f7a442
         exit(1);
f7a442
     }
f7a442
 
f7a442
+    if (out_ccname != NULL) {
f7a442
+        ret = krb5_cc_resolve(context, out_ccname, &out_ccache);
f7a442
+        if (ret) {
f7a442
+            com_err(prog, ret, _("while resolving output ccache"));
f7a442
+            exit(1);
f7a442
+        }
f7a442
+    }
f7a442
+
f7a442
     if (keytab_name != NULL) {
f7a442
         ret = krb5_kt_resolve(context, keytab_name, &keytab);
f7a442
         if (ret) {
f7a442
@@ -513,8 +547,25 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,
f7a442
     errors = 0;
f7a442
     for (i = 0; i < count; i++) {
f7a442
         if (kvno(names[i], ccache, me, etype, keytab, sname, options, unknown,
f7a442
-                 for_user_princ, user_cert, proxy, u2u_ticket) != 0)
f7a442
+                 for_user_princ, user_cert, proxy, u2u_ticket, &creds) != 0) {
f7a442
             errors++;
f7a442
+        } else if (out_ccache != NULL) {
f7a442
+            if (!initialized) {
f7a442
+                ret = krb5_cc_initialize(context, out_ccache, creds->client);
f7a442
+                if (ret) {
f7a442
+                    com_err(prog, ret, _("while initializing output ccache"));
f7a442
+                    exit(1);
f7a442
+                }
f7a442
+                initialized = 1;
f7a442
+            }
f7a442
+            ret = krb5_cc_store_cred(context, out_ccache, creds);
f7a442
+            if (ret) {
f7a442
+                com_err(prog, ret, _("while storing creds in output ccache"));
f7a442
+                exit(1);
f7a442
+            }
f7a442
+        }
f7a442
+
f7a442
+        krb5_free_creds(context, creds);
f7a442
     }
f7a442
 
f7a442
     if (keytab != NULL)
f7a442
diff --git a/src/clients/kvno/t_kvno.py b/src/clients/kvno/t_kvno.py
f7a442
new file mode 100644
f7a442
index 000000000..e98b90e8a
f7a442
--- /dev/null
f7a442
+++ b/src/clients/kvno/t_kvno.py
f7a442
@@ -0,0 +1,75 @@
f7a442
+from k5test import *
f7a442
+
f7a442
+realm = K5Realm()
f7a442
+
f7a442
+def check_cache(ccache, expected_services):
f7a442
+    # Fetch the klist output and skip past the header.
f7a442
+    lines = realm.run([klist, '-c', ccache]).splitlines()
f7a442
+    lines = lines[4:]
f7a442
+
f7a442
+    # For each line not beginning with an indent, match against the
f7a442
+    # expected service principals.
f7a442
+    svcs = {x: True for x in expected_services}
f7a442
+    for l in lines:
f7a442
+        if not l.startswith('\t'):
f7a442
+            svcprinc = l.split()[4]
f7a442
+            if svcprinc in svcs:
f7a442
+                del svcs[svcprinc]
f7a442
+            else:
f7a442
+                fail('unexpected service princ ' + svcprinc)
f7a442
+
f7a442
+    if svcs:
f7a442
+        fail('services not found in klist output: ' + ' '.join(svcs.keys()))
f7a442
+
f7a442
+
f7a442
+mark('no options')
f7a442
+realm.run([kvno, realm.user_princ], expected_msg='user@KRBTEST.COM: kvno = 1')
f7a442
+check_cache(realm.ccache, [realm.krbtgt_princ, realm.user_princ])
f7a442
+
f7a442
+mark('-e')
f7a442
+msgs = ('etypes requested in TGS request: camellia128-cts',
f7a442
+        '/KDC has no support for encryption type')
f7a442
+realm.run([kvno, '-e', 'camellia128-cts', realm.host_princ],
f7a442
+          expected_code=1, expected_trace=msgs)
f7a442
+
f7a442
+mark('--cached-only')
f7a442
+realm.run([kvno, '--cached-only', realm.user_princ], expected_msg='kvno = 1')
f7a442
+realm.run([kvno, '--cached-only', realm.host_princ],
f7a442
+          expected_code=1, expected_msg='Matching credential not found')
f7a442
+check_cache(realm.ccache, [realm.krbtgt_princ, realm.user_princ])
f7a442
+
f7a442
+mark('--no-store')
f7a442
+realm.run([kvno, '--no-store', realm.host_princ], expected_msg='kvno = 1')
f7a442
+check_cache(realm.ccache, [realm.krbtgt_princ, realm.user_princ])
f7a442
+
f7a442
+mark('--out-cache') # and multiple services
f7a442
+out_ccache = os.path.join(realm.testdir, 'ccache.out')
f7a442
+realm.run([kvno, '--out-cache', out_ccache,
f7a442
+           realm.host_princ, realm.admin_princ])
f7a442
+check_cache(realm.ccache, [realm.krbtgt_princ, realm.user_princ])
f7a442
+check_cache(out_ccache, [realm.host_princ, realm.admin_princ])
f7a442
+
f7a442
+mark('--out-cache --cached-only') # tests out-cache overwriting, and -q
f7a442
+realm.run([kvno, '--out-cache', out_ccache, '--cached-only', realm.host_princ],
f7a442
+          expected_code=1, expected_msg='Matching credential not found')
f7a442
+out = realm.run([kvno, '-q', '--out-cache', out_ccache, '--cached-only',
f7a442
+                 realm.user_princ])
f7a442
+if out:
f7a442
+    fail('unexpected kvno output with -q')
f7a442
+check_cache(out_ccache, [realm.user_princ])
f7a442
+
f7a442
+mark('-U') # and -c
f7a442
+svc_ccache = os.path.join(realm.testdir, 'ccache.svc')
f7a442
+realm.run([kinit, '-k', '-c', svc_ccache, realm.host_princ])
f7a442
+realm.run([kvno, '-c', svc_ccache, '-U', 'user', realm.host_princ])
f7a442
+realm.run([klist, '-c', svc_ccache], expected_msg='for client user@')
f7a442
+realm.run([kvno, '-c', svc_ccache, '-U', 'user', '--out-cache', out_ccache,
f7a442
+           realm.host_princ])
f7a442
+out = realm.run([klist, '-c', out_ccache])
f7a442
+if ('Default principal: user@KRBTEST.COM' not in out):
f7a442
+    fail('wrong default principal in klist output')
f7a442
+
f7a442
+# More S4U options are tested in tests/gssapi/t_s4u.py.
f7a442
+# --u2u is tested in tests/t_u2u.py.
f7a442
+
f7a442
+success('kvno tests')
f7a442
diff --git a/src/man/kvno.man b/src/man/kvno.man
f7a442
index 005a2ec97..b9f6739eb 100644
f7a442
--- a/src/man/kvno.man
f7a442
+++ b/src/man/kvno.man
f7a442
@@ -95,6 +95,19 @@ Specifies that protocol transition is to be used, identifying the
f7a442
 client principal with the X.509 certificate in \fIcert_file\fP\&.  The
f7a442
 certificate file must be in PEM format.
f7a442
 .TP
f7a442
+\fB\-\-cached\-only\fP
f7a442
+Only retrieve credentials already present in the cache, not from
f7a442
+the KDC.
f7a442
+.TP
f7a442
+\fB\-\-no\-store\fP
f7a442
+Do not store retrieved credentials in the cache.  If
f7a442
+\fB\-\-out\-cache\fP is also specified, credentials will still be
f7a442
+stored into the output credential cache.
f7a442
+.TP
f7a442
+\fB\-\-out\-cache\fP \fIccache\fP
f7a442
+Initialize \fIccache\fP and store all retrieved credentials into it.
f7a442
+Do not store acquired credentials in the input cache.
f7a442
+.TP
f7a442
 \fB\-\-u2u\fP \fIccache\fP
f7a442
 Requests a user\-to\-user ticket.  \fIccache\fP must contain a local
f7a442
 krbtgt ticket for the server principal.  The reported version