Add ccache collection support to tools
authorGreg Hudson <ghudson@mit.edu>
Mon, 5 Sep 2011 16:26:48 +0000 (16:26 +0000)
committerGreg Hudson <ghudson@mit.edu>
Mon, 5 Sep 2011 16:26:48 +0000 (16:26 +0000)
* "kdestroy -A" destroys all caches in collection.
* "kinit princ" searches the collection for a matching cache and
  overwrites it, or creates a new cache in the collection, if the
  type of the default cache is collection-enabled.  The chosen cache
  also becomes the primary cache for the collection.
* "klist -l" lists (in summary form) the caches in the collection.
* "klist -A" lists the content of all of the caches in the collection.
* "kswitch -c cache" (new command) makes cache the primary cache.
* "kswitch -p princ" makes the cache for princ the primary cache.

ticket: 6956

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@25157 dc483132-0cff-0310-8789-dd5450dbe970

23 files changed:
doc/rst_source/conf.py
doc/rst_source/krb_users/user_commands/index.rst
doc/rst_source/krb_users/user_commands/kdestroy.rst
doc/rst_source/krb_users/user_commands/kinit.rst
doc/rst_source/krb_users/user_commands/klist.rst
doc/rst_source/krb_users/user_commands/kswitch.rst [new file with mode: 0644]
src/clients/Makefile.in
src/clients/kdestroy/kdestroy.M
src/clients/kdestroy/kdestroy.c
src/clients/kinit/kinit.M
src/clients/kinit/kinit.c
src/clients/klist/klist.M
src/clients/klist/klist.c
src/clients/kswitch/Makefile.in [new file with mode: 0644]
src/clients/kswitch/deps [new file with mode: 0644]
src/clients/kswitch/kswitch.M [new file with mode: 0644]
src/clients/kswitch/kswitch.c [new file with mode: 0644]
src/configure.in
src/lib/krb5/ccache/cccursor.c
src/tests/Makefile.in
src/tests/t_cccol.py [new file with mode: 0644]
src/util/k5test.py
src/util/testrealm.py

index 6ea2c6d33804dfc10b91eab3fe9238d93a4bbc0a..abc63e9ce1ad2e3d931016b6fa8bfb1157bca75f 100644 (file)
@@ -221,6 +221,7 @@ man_pages = [
     ('krb_users/user_commands/kinit', 'kinit', u'obtain and cache Kerberos ticket-granting ticket', [u'MIT'], 1),
     ('krb_users/user_commands/klist', 'klist', u'list cached Kerberos tickets', [u'MIT'], 1),
     ('krb_users/user_commands/kdestroy', 'kdestroy', u'destroy Kerberos tickets', [u'MIT'], 1),
+    ('krb_users/user_commands/kswitch', 'kswitch', u'switch primary ticket cache', [u'MIT'], 1),
     ('krb_users/user_commands/kpasswd', 'kpasswd', u'change a user\'s Kerberos password', [u'MIT'], 1),
     ('krb_users/user_commands/kvno', 'kvno', u'print key version numbers of Kerberos principals', [u'MIT'], 1),
     ('krb_users/user_commands/ksu', 'ksu', u'Kerberized super-user', [u'MIT'], 1),
index 5232eb818e769e21ac57af4ed45a887993a0bc4b..4c68c0899368fbea0a522f09cf7404cdf5b5c237 100644 (file)
@@ -10,6 +10,7 @@ User commands
    kinit.rst
    klist.rst
    kdestroy.rst
+   kswitch.rst
    kpasswd.rst
    kvno.rst
    ksu.rst
index 78a3014a97e138f1880c79ea037298a1ffdc5692..b86137afe2a392d503ddb7ea292c8950f827a86b 100644 (file)
@@ -5,6 +5,7 @@ SYNOPSIS
 ~~~~~~~~~~~~~
 
 *kdestroy*
+         [**-A**]
          [**-q**]
          [**-c** *cache_name*]
 
@@ -21,6 +22,10 @@ cache is not specified, the default credentials cache is destroyed.
 OPTIONS
 ~~~~~~~~~~~~~
 
+     **-A**
+        Destroys all caches in the collection, if a cache collection is
+        available.
+
      **-q**
         Run quietly. Normally *kdestroy* beeps if it fails to destroy the user's tickets. The *-q* flag suppresses this behavior.
 
@@ -45,7 +50,14 @@ ENVIRONMENT
 
 *kdestroy* uses the following environment variables:
 
-     **KRB5CCNAME**  - Location of the Kerberos 5 credentials (ticket) cache.
+     **KRB5CCNAME**
+          Location of the default Kerberos 5 credentials (ticket)
+          cache, in the form *type*:*residual*.  If no type prefix is
+          present, the **FILE** type is assumed.  The type of the
+          default cache may determine the availability of a cache
+          collection; for instance, a default cache of type **DIR**
+          causes caches within the directory to be present in the
+          collection.
 
 
 FILES
index 3e816d352976580610477aa7f9a1aa824c8c6c18..49a2564707eac06bd4ef624998a5772e9a57d7e7 100644 (file)
@@ -129,8 +129,15 @@ OPTIONS
           use *cache_name* as the Kerberos 5  credentials  (ticket) cache  name  and  location;
           if this option is not used, the default cache name and location are used.
 
-          The default credentials cache may vary between systems.  If  the  **KRB5CCNAME**  environment  variable  is set, its
-          value is used to name the default  ticket  cache.   Any existing contents of the cache are destroyed by kinit.
+          The default credentials cache may vary between systems.  If
+          the **KRB5CCNAME** environment variable is set, its value is
+          used to name the default ticket cache.  If a principal name
+          is specified and the type of the default credentials cache
+          supports a collection (such as the DIR type), an existing
+          cache containing credentials for the principal is selected
+          or a new one is created and becomes the new primary cache.
+          Otherwise, any existing contents of the default cache are
+          destroyed by kinit.
 
      **-S** *service_name*
           specify an alternate service name to use  when  getting initial tickets.
@@ -162,7 +169,14 @@ ENVIRONMENT
 
 *kinit* uses the following environment variables:
 
-       **KRB5CCNAME**  Location of the Kerberos 5 credentials (ticket) cache.
+     **KRB5CCNAME**
+          Location of the default Kerberos 5 credentials (ticket)
+          cache, in the form *type*:*residual*.  If no type prefix is
+          present, the **FILE** type is assumed.  The type of the
+          default cache may determine the availability of a cache
+          collection; for instance, a default cache of type **DIR**
+          causes caches within the directory to be present in the
+          collection.
 
 
 FILES
index 86a514c8edd3eac0de93e7b9df7668e67293adaa..3886a9ff0ecdd8658bb15bfa3b35643e8fd1c9a9 100644 (file)
@@ -7,7 +7,7 @@ SYNOPSIS
 
 **klist**
       [**-e**] 
-      [[**-c**] [**-f**] [**-s**] [**-a** [**-n**]]]
+      [[**-c**] [**-l**] [**-A**] [**-f**] [**-s**] [**-a** [**-n**]]]
       [**-k**  [**-t**]  [**-K**]]
       [**-V**]
       [*cache_name* | *keytab_name*]
@@ -26,6 +26,14 @@ OPTIONS
           Displays the encryption types of the session key and the ticket for each credential in the credential cache,
           or each key in the keytab file.
 
+     **-l**
+          If a cache collection is available, displays a table
+          summarizing the caches present in the collection.
+
+     **-A**
+          If a cache collection is available, displays the contents of
+          all of the caches in the collection.
+
      **-c**
           List tickets held in a credentials cache. This is the default if neither *-c* nor *-k* is specified.
 
@@ -79,7 +87,14 @@ ENVIRONMENT
 
 *klist* uses the following environment variables:
 
-     **KRB5CCNAME** - Location of the Kerberos 5 credentials (ticket) cache.
+     **KRB5CCNAME**
+          Location of the default Kerberos 5 credentials (ticket)
+          cache, in the form *type*:*residual*.  If no type prefix is
+          present, the **FILE** type is assumed.  The type of the
+          default cache may determine the availability of a cache
+          collection; for instance, a default cache of type **DIR**
+          causes caches within the directory to be present in the
+          collection.
 
 
 FILES
diff --git a/doc/rst_source/krb_users/user_commands/kswitch.rst b/doc/rst_source/krb_users/user_commands/kswitch.rst
new file mode 100644 (file)
index 0000000..fad2d9a
--- /dev/null
@@ -0,0 +1,53 @@
+kswitch - switch primary credential cache
+=========================================
+
+
+SYNOPSIS
+~~~~~~~~
+
+**kswitch** {**-c** *cachename* | **-p** *principal*}
+
+DESCRIPTION
+~~~~~~~~~~~
+
+*kswitch* makes the specified credential cache the primary cache for
+the collection, if a cache collection is available.
+
+
+OPTIONS
+~~~~~~~
+
+     **-c** *cachename*
+          Directly specifies the credential cache to be made primary.
+
+     **-p** *principal*
+          Causes the cache collection to be searched for a cache
+          containing credentials for *principal*.  If one is found,
+          that collection is made primary.
+
+
+ENVIRONMENT
+~~~~~~~~~~~
+
+*kswitch* uses the following environment variables:
+
+     **KRB5CCNAME**
+          Location of the default Kerberos 5 credentials (ticket)
+          cache, in the form *type*:*residual*.  If no type prefix is
+          present, the **FILE** type is assumed.  The type of the
+          default cache may determine the availability of a cache
+          collection; for instance, a default cache of type **DIR**
+          causes caches within the directory to be present in the
+          collection.
+
+
+FILES
+~~~~~
+
+/tmp/krb5cc_[uid]  - Default location of Kerberos 5 credentials cache ([*uid*] is the decimal UID of the user).
+
+
+SEE ALSO
+~~~~~~~~
+
+kinit(1), kdestroy(1), klist(1), kerberos(1)
index 4ae8361946bca219f58e0579942ef64a2c56359a..4beb32a61894a0f544e676f0776cfc6cb264d345 100644 (file)
@@ -1,7 +1,7 @@
 mydir=clients
 BUILDTOP=$(REL)..
 
-SUBDIRS= klist kinit kdestroy kpasswd ksu kvno kcpytkt kdeltkt
-WINSUBDIRS= klist kinit kdestroy kpasswd kvno kcpytkt kdeltkt
+SUBDIRS= klist kinit kdestroy kpasswd ksu kvno kcpytkt kdeltkt kswitch
+WINSUBDIRS= klist kinit kdestroy kpasswd kvno kcpytkt kdeltkt kswitch
 
 NO_OUTPRE=1
index ada2ae3dcf1600e76d42ef67ead396aada2377b7..4deaa5fde20869f5b325f09a38daf4a88425cf95 100644 (file)
@@ -26,7 +26,7 @@
 kdestroy \- destroy Kerberos tickets
 .SH SYNOPSIS
 .B kdestroy
-[\fB\-q\fP] [\fB\-c\fP \fIcache_name]
+[\fB\-A\fP] [\fB\-q\fP] [\fB\-c\fP \fIcache_name]
 .br
 .SH DESCRIPTION
 The
@@ -37,6 +37,9 @@ the credentials cache is not specified, the default credentials cache is
 destroyed.
 .SH OPTIONS
 .TP
+.B \-A
+Destroys all caches in the collection, if a cache collection is
+available.
 .B \-q
 Run quietly.  Normally
 .B kdestroy
@@ -65,7 +68,12 @@ file, so that your tickets are destroyed automatically when you log out.
 uses the following environment variables:
 .TP "\w'.SM KRB5CCNAME\ \ 'u"
 .SM KRB5CCNAME
-Location of the Kerberos 5 credentials (ticket) cache.
+Location of the default Kerberos 5 credentials (ticket) cache, in the
+form \fItype\fP:\fIresidual\fP.  If no type prefix is present, the
+\fBFILE\fP type is assumed.  The type of the default cache may
+determine the availability of a cache collection; for instance, a
+default cache of type \fBDIR\fP causes caches within the directory to
+be present in the collection.
 .SH FILES
 .TP "\w'/tmp/krb5cc_[uid]\ \ 'u"
 /tmp/krb5cc_[uid]
index 73ce04459b506b4425d91561fcac801ee469ca93..abe49533d43a99dd34f5f810194a43c09920a657 100644 (file)
@@ -55,7 +55,8 @@ static void usage()
 {
 #define KRB_AVAIL_STRING(x) ((x)?"available":"not available")
 
-    fprintf(stderr, _("Usage: %s [-q] [-c cache_name]\n"), progname);
+    fprintf(stderr, _("Usage: %s [-A] [-q] [-c cache_name]\n"), progname);
+    fprintf(stderr, _("\t-A destroy all credential caches in collection\n"));
     fprintf(stderr, _("\t-q quiet mode\n"));
     fprintf(stderr, _("\t-c specify name of credentials cache\n"));
     exit(2);
@@ -70,16 +71,21 @@ main(argc, argv)
     krb5_error_code retval;
     int c;
     krb5_ccache cache = NULL;
+    krb5_cccol_cursor cursor;
     char *cache_name = NULL;
     int code = 0;
     int errflg = 0;
     int quiet = 0;
+    int all = 0;
 
     setlocale(LC_MESSAGES, "");
     progname = GET_PROGNAME(argv[0]);
 
-    while ((c = getopt(argc, argv, "54qc:")) != -1) {
+    while ((c = getopt(argc, argv, "54Aqc:")) != -1) {
         switch (c) {
+        case 'A':
+            all = 1;
+            break;
         case 'q':
             quiet = 1;
             break;
@@ -117,6 +123,30 @@ main(argc, argv)
         exit(1);
     }
 
+    if (all) {
+        code = krb5_cccol_cursor_new(kcontext, &cursor);
+        if (code) {
+            com_err(progname, code, _("while listing credential caches"));
+            exit(1);
+        }
+        while ((code = krb5_cccol_cursor_next(kcontext, cursor,
+                                              &cache)) == 0 && cache != NULL) {
+            code = krb5_cc_get_full_name(kcontext, cache, &cache_name);
+            if (code) {
+                com_err(progname, code, _("composing ccache name"));
+                exit(1);
+            }
+            code = krb5_cc_destroy(kcontext, cache);
+            if (code && code != KRB5_FCC_NOFILE) {
+                com_err(progname, code, _("while destroying cache %s"),
+                        cache_name);
+            }
+            krb5_free_string(kcontext, cache_name);
+        }
+        krb5_cccol_cursor_free(kcontext, &cursor);
+        return 0;
+    }
+
     if (cache_name) {
         code = krb5_cc_resolve (kcontext, cache_name, &cache);
         if (code != 0) {
index 3d95a62b366fd1133553e98b4cfabde7474f89c5..0a919c09f16ff7e09065e9a64de64346ad35041d 100644 (file)
@@ -182,7 +182,11 @@ option is not used, the default cache name and location are used.
 The default credentials cache may vary between systems.  If the
 .B KRB5CCNAME
 environment variable is set, its value is used to name the default
-ticket cache.  Any existing contents of the cache are destroyed by
+ticket cache.  If a principal name is specified and the type of the
+default credentials cache supports a collection (such as the DIR
+type), an existing cache containing credentials for the principal is
+selected or a new one is created and becomes the new primary cache.
+Otherwise, any existing contents of the default cache are destroyed by
 .IR kinit .
 .TP
 \fB\-S\fP \fIservice_name\fP
@@ -215,7 +219,12 @@ pre-authentication mechanism:
 uses the following environment variables:
 .TP "\w'.SM KRB5CCNAME\ \ 'u"
 .SM KRB5CCNAME
-Location of the Kerberos 5 credentials (ticket) cache.
+Location of the default Kerberos 5 credentials (ticket) cache, in the
+form \fItype\fP:\fIresidual\fP.  If no type prefix is present, the
+\fBFILE\fP type is assumed.  The type of the default cache may
+determine the availability of a cache collection; for instance, a
+default cache of type \fBDIR\fP causes caches within the directory to
+be present in the collection.
 .SH FILES
 .TP "\w'/tmp/krb5cc_[uid]\ \ 'u"
 /tmp/krb5cc_[uid]
@@ -227,4 +236,4 @@ default location for the local host's
 .B keytab
 file.
 .SH SEE ALSO
-klist(1), kdestroy(1), kerberos(1)
+klist(1), kdestroy(1), kswitch(1), kerberos(1)
index 802229ae002fa8dce588293142659f11d2a9f432..1bf7564a1299e7b4753ce1d67d7f1d9d09ffc517 100644 (file)
@@ -133,6 +133,7 @@ struct k5_data
     krb5_ccache cc;
     krb5_principal me;
     char* name;
+    krb5_boolean switch_to_cache;
 };
 
 #ifdef GETOPT_LONG
@@ -438,6 +439,8 @@ k5_begin(opts, k5)
 {
     krb5_error_code code = 0;
     int flags = opts->enterprise ? KRB5_PRINCIPAL_PARSE_ENTERPRISE : 0;
+    krb5_ccache defcache;
+    const char *deftype;
 
     code = krb5_init_context(&k5->ctx);
     if (code) {
@@ -445,8 +448,18 @@ k5_begin(opts, k5)
         return 0;
     }
     errctx = k5->ctx;
-    if (opts->k5_cache_name)
-    {
+
+    /* Parse specified principal name now if we got one. */
+    if (opts->principal_name) {
+        if ((code = krb5_parse_name_flags(k5->ctx, opts->principal_name,
+                                          flags, &k5->me))) {
+            com_err(progname, code, _("when parsing name %s"),
+                    opts->principal_name);
+            return 0;
+        }
+    }
+
+    if (opts->k5_cache_name) {
         code = krb5_cc_resolve(k5->ctx, opts->k5_cache_name, &k5->cc);
         if (code != 0) {
             com_err(progname, code, _("resolving ccache %s"),
@@ -457,31 +470,48 @@ k5_begin(opts, k5)
             fprintf(stderr, _("Using specified cache: %s\n"),
                     opts->k5_cache_name);
         }
-    }
-    else
-    {
-        if ((code = krb5_cc_default(k5->ctx, &k5->cc))) {
+    } else {
+        if ((code = krb5_cc_default(k5->ctx, &defcache))) {
             com_err(progname, code, _("while getting default ccache"));
             return 0;
         }
-        if (opts->verbose) {
-            fprintf(stderr, _("Using default cache: %s\n"),
-                    krb5_cc_get_name(k5->ctx, k5->cc));
+        deftype = krb5_cc_get_type(k5->ctx, defcache);
+        if (k5->me != NULL && krb5_cc_support_switch(k5->ctx, deftype)) {
+            /* Use an existing cache for the specified principal if we can. */
+            code = krb5_cc_cache_match(k5->ctx, k5->me, &k5->cc);
+            if (code != 0 && code != KRB5_CC_NOTFOUND) {
+                com_err(progname, code, _("while searching for ccache for %s"),
+                        opts->principal_name);
+                krb5_cc_close(k5->ctx, defcache);
+                return 0;
+            }
+            if (code == KRB5_CC_NOTFOUND) {
+                code = krb5_cc_new_unique(k5->ctx, deftype, NULL, &k5->cc);
+                if (code) {
+                    com_err(progname, code, _("while generating new ccache"));
+                    krb5_cc_close(k5->ctx, defcache);
+                    return 0;
+                }
+                if (opts->verbose) {
+                    fprintf(stderr, _("Using new cache: %s\n"),
+                            krb5_cc_get_name(k5->ctx, k5->cc));
+                }
+            } else if (opts->verbose) {
+                fprintf(stderr, _("Using existing cache: %s\n"),
+                        krb5_cc_get_name(k5->ctx, k5->cc));
+            }
+            krb5_cc_close(k5->ctx, defcache);
+            k5->switch_to_cache = 1;
+        } else {
+            k5->cc = defcache;
+            if (opts->verbose) {
+                fprintf(stderr, _("Using default cache: %s\n"),
+                        krb5_cc_get_name(k5->ctx, k5->cc));
+            }
         }
     }
 
-    if (opts->principal_name)
-    {
-        /* Use specified name */
-        if ((code = krb5_parse_name_flags(k5->ctx, opts->principal_name,
-                                          flags, &k5->me))) {
-            com_err(progname, code, _("when parsing name %s"),
-                    opts->principal_name);
-            return 0;
-        }
-    }
-    else
-    {
+    if (!k5->me) {
         /* No principal name specified */
         if (opts->anonymous) {
             char *defrealm;
@@ -756,6 +786,14 @@ k5_kinit(opts, k5)
     }
     notix = 0;
 
+    if (k5->switch_to_cache) {
+        code = krb5_cc_switch(k5->ctx, k5->cc);
+        if (code) {
+            com_err(progname, code, _("while switching to new ccache"));
+            goto cleanup;
+        }
+    }
+
 cleanup:
     if (options)
         krb5_get_init_creds_opt_free(k5->ctx, options);
index 0f9bb03cc6a3e95b4f4fc54dcf42e8f2ea7aef28..32aed10ac98208cbe13bab3f2215fd7233ef0037 100644 (file)
@@ -25,7 +25,7 @@
 .SH NAME
 klist \- list cached Kerberos tickets
 .SH SYNOPSIS
-\fBklist\fP [\fB\-e\fP] [[\fB\-c\fP] [\fB\-f\fP] 
+\fBklist\fP [\fB\-e\fP] [[\fB\-c\fP] [\fB\-l\fP] [\fB\-A\fP] [\fB\-f\fP] 
 [\fB\-s\fP] [\fB\-a\fP  [\fB\-n\fP]]]
 [\fB\-k\fP [\fB\-t\fP] [\fB\-K\fP]]
 [\fIcache_name\fP | \fIkeytab_name\fP]
@@ -50,6 +50,14 @@ nor
 .B \-k
 is specified.
 .TP
+.B \-l
+If a cache collection is available, displays a table summarizing the
+caches present in the collection.
+.TP
+.B \-A
+If a cache collection is available, displays the contents of all of
+the caches in the collection.
+.TP
 .B \-f
 shows the flags present in the credentials, using the following
 abbreviations:
@@ -119,7 +127,12 @@ ticket cache.
 uses the following environment variables:
 .TP "\w'.SM KRB5CCNAME\ \ 'u"
 .SM KRB5CCNAME
-Location of the Kerberos 5 credentials (ticket) cache.
+Location of the default Kerberos 5 credentials (ticket) cache, in the
+form \fItype\fP:\fIresidual\fP.  If no type prefix is present, the
+\fBFILE\fP type is assumed.  The type of the default cache may
+determine the availability of a cache collection; for instance, a
+default cache of type \fBDIR\fP causes caches within the directory to
+be present in the collection.
 .SH FILES
 .TP "\w'/tmp/krb5cc_[uid]\ \ 'u"
 /tmp/krb5cc_[uid]
index d191f85c820cf359bc6f4f3c9bf8195d9a24c622..4757f381a4bdd84f3af62677209f0e6343a7bb1f 100644 (file)
@@ -54,7 +54,7 @@ extern int optind;
 
 int show_flags = 0, show_time = 0, status_only = 0, show_keys = 0;
 int show_etype = 0, show_addresses = 0, no_resolve = 0, print_version = 0;
-int show_adtype = 0;
+int show_adtype = 0, show_all = 0, list_all = 0;
 char *defname;
 char *progname;
 krb5_int32 now;
@@ -65,7 +65,11 @@ krb5_context kcontext;
 char * etype_string (krb5_enctype );
 void show_credential (krb5_creds *);
 
-void do_ccache (char *);
+void list_all_ccaches (void);
+int list_ccache (krb5_ccache);
+void show_all_ccaches (void);
+void do_ccache_name (char *);
+int do_ccache (krb5_ccache);
 void do_keytab (char *);
 void printtime (time_t);
 void one_addr (krb5_address *);
@@ -79,11 +83,13 @@ static void usage()
 {
 #define KRB_AVAIL_STRING(x) ((x)?"available":"not available")
 
-    fprintf(stderr, _("Usage: %s [-e] [-V] [[-c] [-d] [-f] [-s] [-a [-n]]] "
-                      "[-k [-t] [-K]] [name]\n"), progname);
+    fprintf(stderr, _("Usage: %s [-e] [-V] [[-c] [-l] [-A] [-d] [-f] [-s] "
+                      "[-a [-n]]] [-k [-t] [-K]] [name]\n"), progname);
     fprintf(stderr, _("\t-c specifies credentials cache\n"));
     fprintf(stderr, _("\t-k specifies keytab\n"));
     fprintf(stderr, _("\t   (Default is credentials cache)\n"));
+    fprintf(stderr, _("\t-l lists credential caches in collection\n"));
+    fprintf(stderr, _("\t-A shows content of all credential caches\n"));
     fprintf(stderr, _("\t-e shows the encryption type\n"));
     fprintf(stderr, _("\t-V shows the Kerberos version and exits\n"));
     fprintf(stderr, _("\toptions for credential caches:\n"));
@@ -115,7 +121,7 @@ main(argc, argv)
     name = NULL;
     mode = DEFAULT;
     /* V=version so v can be used for verbose later if desired.  */
-    while ((c = getopt(argc, argv, "dfetKsnack45V")) != -1) {
+    while ((c = getopt(argc, argv, "dfetKsnack45lAV")) != -1) {
         switch (c) {
         case 'd':
             show_adtype = 1;
@@ -155,6 +161,12 @@ main(argc, argv)
             break;
         case '5':
             break;
+        case 'l':
+            list_all = 1;
+            break;
+        case 'A':
+            show_all = 1;
+            break;
         case 'V':
             print_version = 1;
             break;
@@ -171,8 +183,11 @@ main(argc, argv)
     if (mode == DEFAULT || mode == CCACHE) {
         if (show_time || show_keys)
             usage();
+        if ((show_all && list_all) || (status_only && list_all))
+            usage();
     } else {
-        if (show_flags || status_only || show_addresses)
+        if (show_flags || status_only || show_addresses ||
+            show_all || list_all)
             usage();
     }
 
@@ -213,8 +228,12 @@ main(argc, argv)
             exit(1);
         }
 
-        if (mode == DEFAULT || mode == CCACHE)
-            do_ccache(name);
+        if (list_all)
+            list_all_ccaches();
+        else if (show_all)
+            show_all_ccaches();
+        else if (mode == DEFAULT || mode == CCACHE)
+            do_ccache_name(name);
         else
             do_keytab(name);
     }
@@ -306,20 +325,103 @@ void do_keytab(name)
     }
     exit(0);
 }
-void do_ccache(name)
-    char *name;
+
+void
+list_all_ccaches(void)
 {
-    krb5_ccache cache = NULL;
-    krb5_cc_cursor cur;
-    krb5_creds creds;
-    krb5_principal princ;
-    krb5_flags flags;
     krb5_error_code code;
-    int exit_status = 0;
+    krb5_ccache cache;
+    krb5_cccol_cursor cursor;
+    int exit_status;
+
+    code = krb5_cccol_cursor_new(kcontext, &cursor);
+    if (code) {
+        if (!status_only)
+            com_err(progname, code, _("while listing ccache collection"));
+        exit(1);
+    }
+
+    /* XXX Translating would disturb table alignment; skip for now. */
+    printf("%-30s %s\n", "Principal name", "Cache name");
+    printf("%-30s %s\n", "--------------", "----------");
+    exit_status = 1;
+    while (!(code = krb5_cccol_cursor_next(kcontext, cursor, &cache)) &&
+           cache != NULL) {
+        exit_status = list_ccache(cache) && exit_status;
+        krb5_cc_close(kcontext, cache);
+    }
+    krb5_cccol_cursor_free(kcontext, &cursor);
+    exit(exit_status);
+}
+
+int
+list_ccache(krb5_ccache cache)
+{
+    krb5_error_code code;
+    krb5_principal princ = NULL;
+    char *princname = NULL, *ccname = NULL;
+    int expired, status = 1;
+
+    code = krb5_cc_get_principal(kcontext, cache, &princ);
+    if (code)                   /* Uninitialized cache file, probably. */
+        goto cleanup;
+    code = krb5_unparse_name(kcontext, princ, &princname);
+    if (code)
+        goto cleanup;
+    code = krb5_cc_get_full_name(kcontext, cache, &ccname);
+    if (code)
+        goto cleanup;
+
+    status_only = 1;
+    expired = do_ccache(cache);
+
+    printf("%-30.30s %s", princname, ccname);
+    if (expired)
+        printf(" %s", _("(Expired)"));
+    printf("\n");
+
+    status = 0;
+cleanup:
+    krb5_free_principal(kcontext, princ);
+    free(princname);
+    free(ccname);
+    return status;
+}
 
-    if (status_only)
-        /* exit_status is set back to 0 if a valid tgt is found */
-        exit_status = 1;
+void
+show_all_ccaches(void)
+{
+    krb5_error_code code;
+    krb5_ccache cache;
+    krb5_cccol_cursor cursor;
+    krb5_boolean first;
+    int exit_status;
+
+    code = krb5_cccol_cursor_new(kcontext, &cursor);
+    if (code) {
+        if (!status_only)
+            com_err(progname, code, _("while listing ccache collection"));
+        exit(1);
+    }
+    exit_status = 1;
+    first = TRUE;
+    while (!(code = krb5_cccol_cursor_next(kcontext, cursor, &cache)) &&
+           cache != NULL) {
+        if (!first)
+            printf("\n");
+        first = FALSE;
+        exit_status = do_ccache(cache) && exit_status;
+        krb5_cc_close(kcontext, cache);
+    }
+    krb5_cccol_cursor_free(kcontext, &cursor);
+    exit(exit_status);
+}
+
+void
+do_ccache_name(char *name)
+{
+    krb5_error_code code;
+    krb5_ccache cache;
 
     if (name == NULL) {
         if ((code = krb5_cc_default(kcontext, &cache))) {
@@ -335,6 +437,21 @@ void do_ccache(name)
             exit(1);
         }
     }
+    exit(do_ccache(cache));
+}
+
+int
+do_ccache(krb5_ccache cache)
+{
+    krb5_cc_cursor cur;
+    krb5_creds creds;
+    krb5_principal princ;
+    krb5_flags flags;
+    krb5_error_code code;
+    int exit_status = 0;
+
+    /* For status_only, exit_status is reset to 0 if a valid tgt is found. */
+    exit_status = (status_only) ? 1 : 0;
 
     flags = 0;                          /* turns off OPENCLOSE mode */
     if ((code = krb5_cc_set_flags(kcontext, cache, flags))) {
@@ -355,17 +472,17 @@ void do_ccache(name)
                         krb5_cc_get_type(kcontext, cache),
                         krb5_cc_get_name(kcontext, cache));
         }
-        exit(1);
+        return 1;
     }
     if ((code = krb5_cc_get_principal(kcontext, cache, &princ))) {
         if (!status_only)
             com_err(progname, code, _("while retrieving principal name"));
-        exit(1);
+        return 1;
     }
     if ((code = krb5_unparse_name(kcontext, princ, &defname))) {
         if (!status_only)
             com_err(progname, code, _("while unparsing principal name"));
-        exit(1);
+        return 1;
     }
     if (!status_only) {
         printf(_("Ticket cache: %s:%s\nDefault principal: %s\n\n"),
@@ -383,7 +500,7 @@ void do_ccache(name)
     if ((code = krb5_cc_start_seq_get(kcontext, cache, &cur))) {
         if (!status_only)
             com_err(progname, code, _("while starting to retrieve tickets"));
-        exit(1);
+        return 1;
     }
     while (!(code = krb5_cc_next_cred(kcontext, cache, &cur, &creds))) {
         if (krb5_is_config_principal(kcontext, creds.server))
@@ -404,23 +521,23 @@ void do_ccache(name)
         if ((code = krb5_cc_end_seq_get(kcontext, cache, &cur))) {
             if (!status_only)
                 com_err(progname, code, _("while finishing ticket retrieval"));
-            exit(1);
+            return 1;
         }
         flags = KRB5_TC_OPENCLOSE;      /* turns on OPENCLOSE mode */
         if ((code = krb5_cc_set_flags(kcontext, cache, flags))) {
             if (!status_only)
                 com_err(progname, code, _("while closing ccache"));
-            exit(1);
+            return 1;
         }
 #ifdef KRB5_KRB4_COMPAT
         if (name == NULL && !status_only)
             do_v4_ccache(0);
 #endif
-        exit(exit_status);
+        return exit_status;
     } else {
         if (!status_only)
             com_err(progname, code, _("while retrieving a ticket"));
-        exit(1);
+        return 1;
     }
 }
 
diff --git a/src/clients/kswitch/Makefile.in b/src/clients/kswitch/Makefile.in
new file mode 100644 (file)
index 0000000..c82d8d6
--- /dev/null
@@ -0,0 +1,39 @@
+mydir=clients$(S)kswitch
+BUILDTOP=$(REL)..$(S)..
+DEFS=
+
+SRCS=kswitch.c
+
+PROG_LIBPATH=-L$(TOPLIBD)
+PROG_RPATH=$(KRB5_LIBDIR)
+
+##WIN32##VERSIONRC = $(BUILDTOP)\windows\version.rc
+##WIN32##RCFLAGS=$(CPPFLAGS) -I$(top_srcdir) -D_WIN32 -DRES_ONLY
+
+##WIN32##KSWITCH=$(OUTPRE)kswitch.exe
+
+##WIN32##EXERES=$(KSWITCH:.exe=.res)
+
+##WIN32##$(EXERES): $(VERSIONRC)
+##WIN32##        $(RC) $(RCFLAGS) -DKSWITCH_APP -fo $@ -r $**
+
+all-unix:: kswitch
+##WIN32##all-windows:: $(KSWITCH)
+
+kswitch: kswitch.o $(KRB5_BASE_DEPLIBS)
+       $(CC_LINK) -o $@ kswitch.o $(KRB5_BASE_LIBS)
+
+##WIN32##$(KSWITCH): $(OUTPRE)kswitch.obj $(BUILDTOP)\util\windows\$(OUTPRE)getopt.obj $(KLIB) $(CLIB) $(EXERES)
+##WIN32##      link $(EXE_LINKOPTS) -out:$@ $**
+##WIN32##      $(_VC_MANIFEST_EMBED_EXE)
+
+clean-unix::
+       $(RM) kswitch.o kswitch
+
+install-unix::
+       for f in kswitch; do \
+         $(INSTALL_PROGRAM) $$f \
+               $(DESTDIR)$(CLIENT_BINDIR)/`echo $$f|sed '$(transform)'`; \
+         $(INSTALL_DATA) $(srcdir)/$$f.M \
+               $(DESTDIR)$(CLIENT_MANDIR)/`echo $$f|sed '$(transform)'`.1; \
+       done
diff --git a/src/clients/kswitch/deps b/src/clients/kswitch/deps
new file mode 100644 (file)
index 0000000..7343171
--- /dev/null
@@ -0,0 +1,14 @@
+# 
+# Generated makefile dependencies follow.
+#
+$(OUTPRE)kswitch.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+  $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+  $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
+  $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+  $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+  $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+  $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+  $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+  $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/krb5/preauth_plugin.h \
+  $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
+  kswitch.c
diff --git a/src/clients/kswitch/kswitch.M b/src/clients/kswitch/kswitch.M
new file mode 100644 (file)
index 0000000..4076975
--- /dev/null
@@ -0,0 +1,61 @@
+.\" clients/kswitch/kswitch.M
+.\"
+.\" Copyright 2011 by the Massachusetts Institute of Technology.
+.\"
+.\" Export of this software from the United States of America may
+.\"   require a specific license from the United States Government.
+.\"   It is the responsibility of any person or organization contemplating
+.\"   export to obtain such a license before exporting.
+.\" 
+.\" WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+.\" distribute this software and its documentation for any purpose and
+.\" without fee is hereby granted, provided that the above copyright
+.\" notice appear in all copies and that both that copyright notice and
+.\" this permission notice appear in supporting documentation, and that
+.\" the name of M.I.T. not be used in advertising or publicity pertaining
+.\" to distribution of the software without specific, written prior
+.\" permission.  Furthermore if you modify this software you must label
+.\" your software as modified software and not distribute it in such a
+.\" fashion that it might be confused with the original M.I.T. software.
+.\" M.I.T. makes no representations about the suitability of
+.\" this software for any purpose.  It is provided "as is" without express
+.\" or implied warranty.
+.\" "
+.TH KSWITCH 1
+.SH NAME
+kswitch \- switch primary credential cache
+.SH SYNOPSIS
+\fBkswitch\fP {\fB\-c\fP \fIcachename\fP | \fB\-p\fP \fIprincipal\fP}
+.SH DESCRIPTION
+.I kswitch
+makes the specified credential cache the primary cache for the
+collection, if a cache collection is available.
+.SH OPTIONS
+.TP
+.B \-c
+.I cachename
+directly specifies the credential cache to be made primary.
+.TP
+.B \-p
+.I principal
+causes the cache collection to be searched for a cache containing
+credentials for \fIprincipal\fP.  If one is found, that collection is
+made primary.
+.SH ENVIRONMENT
+.B kswitch
+uses the following environment variables:
+.TP "\w'.SM KRB5CCNAME\ \ 'u"
+.SM KRB5CCNAME
+Location of the default Kerberos 5 credentials (ticket) cache, in the
+form \fItype\fP:\fIresidual\fP.  If no type prefix is present, the
+\fBFILE\fP type is assumed.  The type of the default cache may
+determine the availability of a cache collection; for instance, a
+default cache of type \fBDIR\fP causes caches within the directory to
+be present in the collection.
+.SH FILES
+.TP "\w'/tmp/krb5cc_[uid]\ \ 'u"
+/tmp/krb5cc_[uid]
+default location of Kerberos 5 credentials cache 
+([uid] is the decimal UID of the user).
+.SH SEE ALSO
+kinit(1), kdestroy(1), klist(1), kerberos(1)
diff --git a/src/clients/kswitch/kswitch.c b/src/clients/kswitch/kswitch.c
new file mode 100644 (file)
index 0000000..42cc9d6
--- /dev/null
@@ -0,0 +1,127 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* clients/kswitch/kswitch.c - Switch primary credential cache */
+/*
+ * Copyright 2011 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+
+extern int optind;
+extern char *optarg;
+
+#ifndef _WIN32
+#define GET_PROGNAME(x) (strrchr((x), '/') ? strrchr((x), '/')+1 : (x))
+#else
+#define GET_PROGNAME(x) max(max(strrchr((x), '/'), strrchr((x), '\\')) + 1,(x))
+#endif
+
+static char *progname;
+
+static void
+usage(void)
+{
+    fprintf(stderr, _("Usage: %s {-c cache_name | -p principal}\n"), progname);
+    fprintf(stderr, _("\t-c specify name of credentials cache\n"));
+    fprintf(stderr, _("\t-p specify name of principal\n"));
+    exit(2);
+}
+
+int
+main(int argc, char **argv)
+{
+    krb5_context context;
+    krb5_error_code ret;
+    int c;
+    krb5_ccache cache = NULL;
+    krb5_principal princ = NULL;
+    const char *cache_name = NULL, *princ_name = NULL;
+    krb5_boolean errflag = FALSE;
+
+    setlocale(LC_MESSAGES, "");
+    progname = GET_PROGNAME(argv[0]);
+
+    while ((c = getopt(argc, argv, "c:p:")) != -1) {
+        switch (c) {
+        case 'c':
+        case 'p':
+            if (cache_name || princ_name) {
+                fprintf(stderr, _("Only one -c or -p option allowed\n"));
+                errflag = TRUE;
+            } else if (c == 'c') {
+                cache_name = optarg;
+            } else {
+                princ_name = optarg;
+            }
+            break;
+        case '?':
+        default:
+            errflag = TRUE;
+            break;
+        }
+    }
+
+    if (optind != argc)
+        errflag = TRUE;
+
+    if (!cache_name && !princ_name) {
+        fprintf(stderr, _("One of -c or -p must be specified\n"));
+        errflag = TRUE;
+    }
+
+    if (errflag)
+        usage();
+
+    ret = krb5_init_context(&context);
+    if (ret) {
+        com_err(progname, ret, _("while initializing krb5"));
+        exit(1);
+    }
+
+    if (cache_name) {
+        ret = krb5_cc_resolve(context, cache_name, &cache);
+        if (ret != 0) {
+            com_err(progname, ret, _("while resolving %s"), cache_name);
+            exit(1);
+        }
+    } else {
+        ret = krb5_parse_name(context, princ_name, &princ);
+        if (ret) {
+            com_err(progname, ret, _("while parsing principal name %s"),
+                    princ_name);
+            exit(1);
+        }
+        ret = krb5_cc_cache_match(context, princ, &cache);
+        if (ret) {
+            com_err(progname, ret, _("while searching for ccache for %s"),
+                    princ_name);
+            exit(1);
+        }
+    }
+
+    ret = krb5_cc_switch(context, cache);
+    if (ret != 0) {
+        com_err(progname, ret, _("while switching to credential cache"));
+        exit(1);
+    }
+    return 0;
+}
index c765c91e3374577066e93d30e2db442f5e89dea6..38889cf529ed0ee14d0b9b12bb658dba4ec576e3 100644 (file)
@@ -1239,7 +1239,7 @@ dnl       ccapi ccapi/lib ccapi/lib/unix ccapi/server ccapi/server/unix ccapi/test
        plugins/authdata/greet_server
 
        clients clients/klist clients/kinit clients/kvno
-       clients/kdestroy clients/kpasswd clients/ksu
+       clients/kdestroy clients/kpasswd clients/ksu clients/kswitch
 
        kadmin kadmin/cli kadmin/dbutil kadmin/ktutil kadmin/server
        kadmin/testing kadmin/testing/scripts kadmin/testing/util
index 2efaa322c76753d3b3156fef45cecab8a33a64ca..23681f99f1a8343d21e462dc2928669038ee7120 100644 (file)
@@ -181,7 +181,7 @@ krb5_cc_cache_match(krb5_context context, krb5_principal client,
 {
     krb5_error_code ret;
     krb5_cccol_cursor cursor;
-    krb5_ccache cache;
+    krb5_ccache cache = NULL;
     krb5_principal princ;
     char *name;
     krb5_boolean eq;
index 987238cfbcf139961d8426eacb68489adba58f65..0d6f0b7950c81f1853929668dbef307675fcc3d5 100644 (file)
@@ -68,6 +68,7 @@ check-pytests::
        $(RUNPYTEST) $(srcdir)/t_keyrollover.py $(PYTESTFLAGS)
        $(RUNPYTEST) $(srcdir)/t_renew.py $(PYTESTFLAGS)
        $(RUNPYTEST) $(srcdir)/t_renprinc.py $(PYTESTFLAGS)
+       $(RUNPYTEST) $(srcdir)/t_cccol.py $(PYTESTFLAGS)
 
 clean::
        $(RM) kdc.conf
diff --git a/src/tests/t_cccol.py b/src/tests/t_cccol.py
new file mode 100644 (file)
index 0000000..d8db275
--- /dev/null
@@ -0,0 +1,77 @@
+# Copyright (C) 2011 by the Massachusetts Institute of Technology.
+# All rights reserved.
+
+# Export of this software from the United States of America may
+#   require a specific license from the United States Government.
+#   It is the responsibility of any person or organization contemplating
+#   export to obtain such a license before exporting.
+#
+# WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+# distribute this software and its documentation for any purpose and
+# without fee is hereby granted, provided that the above copyright
+# notice appear in all copies and that both that copyright notice and
+# this permission notice appear in supporting documentation, and that
+# the name of M.I.T. not be used in advertising or publicity pertaining
+# to distribution of the software without specific, written prior
+# permission.  Furthermore if you modify this software you must label
+# your software as modified software and not distribute it in such a
+# fashion that it might be confused with the original M.I.T. software.
+# M.I.T. makes no representations about the suitability of
+# this software for any purpose.  It is provided "as is" without express
+# or implied warranty.
+
+#!/usr/bin/python
+from k5test import *
+
+realm = K5Realm(start_kadmind=False, create_user=False, create_host=False)
+
+# Make a directory collection and use it for client commands in both realms.
+ccdir = os.path.join(realm.testdir, 'cc')
+ccname = 'DIR:' + ccdir
+os.mkdir(ccdir)
+realm.env_client['KRB5CCNAME'] = ccname
+
+realm.addprinc('alice', password('alice'))
+realm.addprinc('bob', password('bob'))
+realm.addprinc('carol', password('carol'))
+
+realm.kinit('alice', password('alice'))
+output = realm.run_as_client([klist])
+if 'Default principal: alice@' not in output:
+    fail('Initial kinit failed to get credentials for alice.')
+realm.run_as_client([kdestroy])
+output = realm.run_as_client([klist], expected_code=1)
+if 'No credentials cache found' not in output:
+    fail('Initial kdestroy failed to destroy primary cache.')
+output = realm.run_as_client([klist, '-l'], expected_code=1)
+if not output.endswith('---\n') or output.count('\n') != 2:
+    fail('Initial kdestroy failed to empty cache collection.')
+
+realm.kinit('alice', password('alice'))
+realm.kinit('carol', password('carol'))
+output = realm.run_as_client([klist, '-l'])
+if '---\ncarol@' not in output or '\nalice@' not in output:
+    fail('klist -l did not show expected output after two kinits.')
+realm.kinit('alice', password('alice'))
+output = realm.run_as_client([klist, '-l'])
+if '---\nalice@' not in output or output.count('\n') != 4:
+    fail('klist -l did not show expected output after re-kinit for alice.')
+realm.kinit('bob', password('bob'))
+output = realm.run_as_client([klist, '-A'])
+if 'bob@' not in output.splitlines()[1] or 'alice@' not in output or \
+        'carol' not in output or output.count('Default principal:') != 3:
+    fail('klist -A did not show expected output after kinit for bob.')
+realm.run_as_client([kswitch, '-p', 'carol'])
+output = realm.run_as_client([klist, '-l'])
+if '---\ncarol@' not in output or output.count('\n') != 5:
+    fail('klist -l did not show expected output after kswitch to carol.')
+realm.run_as_client([kdestroy])
+output = realm.run_as_client([klist, '-l'])
+if 'carol@' in output or 'bob@' not in output or output.count('\n') != 4:
+    fail('kdestroy failed to remove only primary ccache.')
+realm.run_as_client([kdestroy, '-A'])
+output = realm.run_as_client([klist, '-l'], expected_code=1)
+if not output.endswith('---\n') or output.count('\n') != 2:
+    fail('kdestroy -a failed to empty cache collection.')
+
+success('Credential cache collection tests.')
index cc3a7a1d63b5210a83416a1121d9f06c4101262e..ba4f8302bf63d051268b6cb4cb5d045b41f198db 100644 (file)
@@ -181,6 +181,7 @@ Scripts may use the following functions and variables:
   - ktutil
   - kinit
   - klist
+  - kswitch
   - kvno
   - kdestroy
   - kpasswd
@@ -1074,6 +1075,7 @@ kdb5_util = os.path.join(buildtop, 'kadmin', 'dbutil', 'kdb5_util')
 ktutil = os.path.join(buildtop, 'kadmin', 'ktutil', 'ktutil')
 kinit = os.path.join(buildtop, 'clients', 'kinit', 'kinit')
 klist = os.path.join(buildtop, 'clients', 'klist', 'klist')
+kswitch = os.path.join(buildtop, 'clients', 'kswitch', 'kswitch')
 kvno = os.path.join(buildtop, 'clients', 'kvno', 'kvno')
 kdestroy = os.path.join(buildtop, 'clients', 'kdestroy', 'kdestroy')
 kpasswd = os.path.join(buildtop, 'clients', 'kpasswd', 'kpasswd')
index 30466314c779fe3759778ea11485610d6517d6f9..30b3256edd0ef8c1dde08006ea6f774c2b6dfe60 100644 (file)
@@ -39,6 +39,7 @@ progpaths = [
     os.path.join('clients', 'kpasswd'),
     os.path.join('clients', 'ksu'),
     os.path.join('clients', 'kvno'),
+    os.path.join('clients', 'kswitch'),
     'slave'
 ]