Whamcloud - gitweb
LU-8191 utils: remove unused, fix non-static functions
[fs/lustre-release.git] / lustre / utils / gss / lgss_keyring.c
index 6748f7c..825d1c3 100644 (file)
@@ -27,7 +27,6 @@
  */
 /*
  * This file is part of Lustre, http://www.lustre.org/
- * Lustre is a trademark of Sun Microsystems, Inc.
  *
  * lustre/utils/gss/lgss_keyring.c
  *
 #include <keyutils.h>
 #include <gssapi/gssapi.h>
 #include <sys/wait.h>
+#include <getopt.h>
 
 #include <libcfs/util/param.h>
 #include <libcfs/util/string.h>
+#include <uapi/linux/lustre/lgss.h>
 #include "lsupport.h"
 #include "lgss_utils.h"
+#include "lgss_krb5_utils.h"
 #include "write_bytes.h"
 #include "context.h"
 
@@ -192,10 +194,10 @@ out_params:
        return rc;
 }
 
-int do_nego_rpc(struct lgss_nego_data *lnd,
-               gss_buffer_desc *gss_token,
-               struct lgss_init_res *gr,
-               int req_fd[2], int reply_fd[2])
+static int do_nego_rpc(struct lgss_nego_data *lnd,
+                      gss_buffer_desc *gss_token,
+                      struct lgss_init_res *gr,
+                      int req_fd[2], int reply_fd[2])
 {
        struct lgssd_ioctl_param param;
        struct passwd *pw;
@@ -261,7 +263,7 @@ int do_nego_rpc(struct lgss_nego_data *lnd,
        logmsg(LL_TRACE, "do_nego_rpc: to parse reply\n");
        if (param.status) {
                logmsg(LL_ERR, "status: %ld (%s)\n",
-                      param.status, strerror((int)(-param.status)));
+                      (long int)param.status, strerror((int)(-param.status)));
                return param.status;
        }
 
@@ -391,6 +393,12 @@ static int lgssc_negotiation(struct lgss_nego_data *lnd, int req_fd[2],
                                                &ret_flags,
                                                NULL);        /* time rec */
 
+               logmsg_gss(LL_TRACE, lnd->lnd_mech, maj_stat, min_stat,
+                          "gss_init_sec_context");
+
+               logmsg(LL_TRACE, "send_token:\n");
+               log_hexl(LL_TRACE, send_token.value, send_token.length);
+
                if (recv_tokenp != GSS_C_NO_BUFFER) {
                        gss_release_buffer(&min_stat, &gr.gr_token);
                        recv_tokenp = GSS_C_NO_BUFFER;
@@ -527,7 +535,7 @@ static int lgssc_init_nego_data(struct lgss_nego_data *lnd,
         return 0;
 }
 
-void lgssc_fini_nego_data(struct lgss_nego_data *lnd)
+static void lgssc_fini_nego_data(struct lgss_nego_data *lnd)
 {
         OM_uint32       maj_stat, min_stat;
 
@@ -546,91 +554,154 @@ void lgssc_fini_nego_data(struct lgss_nego_data *lnd)
         }
 }
 
-static
-int error_kernel_key(key_serial_t keyid, int rpc_error, int gss_error)
+static int fork_and_switch_id(int uid, pid_t *child)
 {
-        int      seqwin = 0;
-        char     buf[32];
-        char    *p, *end;
+       int status, rc = 0;
+
+       *child = fork();
+       if (*child == -1) {
+               logmsg(LL_ERR, "cannot fork child for user %u: %s\n",
+                      uid, strerror(errno));
+               rc = errno;
+       } else if (*child == 0) {
+               /* switch identity */
+               rc = switch_identity(uid);
+               if (rc)
+                       rc = errno;
+       } else {
+               if (wait(&status) < 0) {
+                       rc = errno;
+                       logmsg(LL_ERR, "child %d failed: %s\n",
+                              *child, strerror(rc));
+               } else {
+                       rc = WEXITSTATUS(status);
+                       if (rc)
+                               logmsg(LL_ERR, "child %d terminated with %d\n",
+                                      *child, rc);
+               }
+       }
+       return rc;
+}
 
-        logmsg(LL_TRACE, "revoking kernel key %08x\n", keyid);
+static int do_keyctl_update(char *reason, key_serial_t keyid,
+                           const void *payload, size_t plen)
+{
+       while (keyctl_update(keyid, payload, plen)) {
+               if (errno != EAGAIN) {
+                       logmsg(LL_ERR, "%se key %08x: %s\n",
+                              reason, keyid, strerror(errno));
+                       return -1;
+               }
 
-        p = buf;
-        end = buf + sizeof(buf);
+               logmsg(LL_WARN, "key %08x: %sing too soon, try again\n",
+                      keyid, reason);
+               sleep(2);
+       }
 
-        WRITE_BYTES(&p, end, seqwin);
-        WRITE_BYTES(&p, end, rpc_error);
-        WRITE_BYTES(&p, end, gss_error);
+       logmsg(LL_INFO, "key %08x: %sed\n", keyid, reason);
+       return 0;
+}
 
-again:
-        if (keyctl_update(keyid, buf, p - buf)) {
-                if (errno != EAGAIN) {
-                        logmsg(LL_ERR, "revoke key %08x: %s\n",
-                               keyid, strerror(errno));
-                        return -1;
-                }
+static int error_kernel_key(key_serial_t keyid, int rpc_error, int gss_error,
+                           uid_t uid)
+{
+       key_serial_t inst_keyring = KEY_SPEC_SESSION_KEYRING;
+       pid_t child = 1;
+       int seqwin = 0;
+       char *p, *end;
+       char buf[32];
+       int rc;
 
-                logmsg(LL_WARN, "key %08x: revoking too soon, try again\n",
-                       keyid);
-                sleep(2);
-                goto again;
-        }
+       logmsg(LL_TRACE, "revoking kernel key %08x\n", keyid);
 
-        logmsg(LL_INFO, "key %08x: revoked\n", keyid);
-        return 0;
+       /* Only possessor and uid can update the key. So for a user key that is
+        * linked to the user keyring, switch uid/gid in a subprocess to not
+        * change identity in main process.
+        */
+       if (uid) {
+               rc = fork_and_switch_id(uid, &child);
+               if (rc || child)
+                       goto out;
+               inst_keyring = KEY_SPEC_USER_KEYRING;
+       }
+
+       p = buf;
+       end = buf + sizeof(buf);
+
+       WRITE_BYTES(&p, end, seqwin);
+       WRITE_BYTES(&p, end, rpc_error);
+       WRITE_BYTES(&p, end, gss_error);
+
+       rc = do_keyctl_update("revok", keyid, buf, p - buf);
+       if (rc)
+               goto out;
+       rc = keyctl_unlink(keyid, inst_keyring);
+       if (rc)
+               logmsg(LL_ERR, "unlink key %08x from %d: %s\n",
+                      keyid, inst_keyring, strerror(errno));
+       else
+               logmsg(LL_INFO, "key %08x: unlinked from %d\n",
+                      keyid, inst_keyring);
+
+out:
+       if (child == 0)
+               /* job done for child */
+               exit(rc);
+       return rc;
 }
 
-static
-int update_kernel_key(key_serial_t keyid,
-                      struct lgss_nego_data *lnd,
-                      gss_buffer_desc *ctx_token)
+static int update_kernel_key(key_serial_t keyid,
+                            struct lgss_nego_data *lnd,
+                            gss_buffer_desc *ctx_token)
 {
-        char        *buf = NULL, *p = NULL, *end = NULL;
-        unsigned int buf_size = 0;
-        int          rc;
-
-        logmsg(LL_TRACE, "updating kernel key %08x\n", keyid);
-
-        buf_size = sizeof(lnd->lnd_seq_win) +
-                   sizeof(lnd->lnd_rmt_ctx.length) + lnd->lnd_rmt_ctx.length +
-                   sizeof(ctx_token->length) + ctx_token->length;
-        buf = malloc(buf_size);
-        if (buf == NULL) {
-                logmsg(LL_ERR, "key %08x: can't alloc update buf: size %d\n",
-                       keyid, buf_size);
-                return 1;
-        }
+       char *buf = NULL, *p = NULL, *end = NULL;
+       unsigned int buf_size = 0;
+       pid_t child = 1;
+       int uid, rc = 0;
 
-        p = buf;
-        end = buf + buf_size;
-        rc = -1;
-
-        if (WRITE_BYTES(&p, end, lnd->lnd_seq_win))
-                goto out;
-        if (write_buffer(&p, end, &lnd->lnd_rmt_ctx))
-                goto out;
-        if (write_buffer(&p, end, ctx_token))
-                goto out;
-
-again:
-        if (keyctl_update(keyid, buf, p - buf)) {
-                if (errno != EAGAIN) {
-                        logmsg(LL_ERR, "update key %08x: %s\n",
-                               keyid, strerror(errno));
-                        goto out;
-                }
+       logmsg(LL_TRACE, "updating kernel key %08x\n", keyid);
 
-                logmsg(LL_DEBUG, "key %08x: updating too soon, try again\n",
-                       keyid);
-                sleep(2);
-                goto again;
-        }
+       /* Only possessor and uid can update the key. So for a user key that is
+        * linked to the user keyring, switch uid/gid in a subprocess to not
+        * change identity in main process.
+        */
+       uid = lnd->lnd_uid;
+       if (uid) {
+               rc = fork_and_switch_id(uid, &child);
+               if (rc || child)
+                       goto out;
+       }
+
+       buf_size = sizeof(lnd->lnd_seq_win) +
+               sizeof(lnd->lnd_rmt_ctx.length) + lnd->lnd_rmt_ctx.length +
+               sizeof(ctx_token->length) + ctx_token->length;
+       buf = malloc(buf_size);
+       if (buf == NULL) {
+               logmsg(LL_ERR, "key %08x: can't alloc update buf: size %d\n",
+                      keyid, buf_size);
+               rc = ENOMEM;
+               goto out;
+       }
+
+       p = buf;
+       end = buf + buf_size;
+       rc = -1;
+
+       if (WRITE_BYTES(&p, end, lnd->lnd_seq_win))
+               goto out;
+       if (write_buffer(&p, end, &lnd->lnd_rmt_ctx))
+               goto out;
+       if (write_buffer(&p, end, ctx_token))
+               goto out;
+
+       rc = do_keyctl_update("updat", keyid, buf, p - buf);
 
-        rc = 0;
-        logmsg(LL_DEBUG, "key %08x: updated\n", keyid);
 out:
-        free(buf);
-        return rc;
+       free(buf);
+       if (child == 0)
+               /* job done for child */
+               exit(rc);
+       return rc;
 }
 
 static int lgssc_kr_negotiate_krb(key_serial_t keyid, struct lgss_cred *cred,
@@ -645,13 +716,13 @@ static int lgssc_kr_negotiate_krb(key_serial_t keyid, struct lgss_cred *cred,
        if (lgss_get_service_str(&g_service, kup->kup_svc, kup->kup_nid)) {
                logmsg(LL_ERR, "key %08x: failed to construct service "
                       "string\n", keyid);
-               error_kernel_key(keyid, -EACCES, 0);
+               error_kernel_key(keyid, -EACCES, 0, cred->lc_uid);
                goto out_cred;
        }
 
        if (lgss_using_cred(cred)) {
                logmsg(LL_ERR, "key %08x: can't using cred\n", keyid);
-               error_kernel_key(keyid, -EACCES, 0);
+               error_kernel_key(keyid, -EACCES, 0, cred->lc_uid);
                goto out_cred;
        }
 
@@ -660,7 +731,8 @@ retry_nego:
        if (lgssc_init_nego_data(&lnd, kup, cred->lc_mech->lmt_mech_n)) {
                logmsg(LL_ERR, "key %08x: failed to initialize "
                       "negotiation data\n", keyid);
-               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err);
+               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err,
+                                cred->lc_uid);
                goto out_cred;
        }
 
@@ -671,15 +743,16 @@ retry_nego:
                goto retry_nego;
        } else if (rc) {
                logmsg(LL_ERR, "key %08x: failed to negotiation\n", keyid);
-               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err);
+               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err,
+                                cred->lc_uid);
                goto out;
        }
 
-       rc = serialize_context_for_kernel(lnd.lnd_ctx, &lnd.lnd_ctx_token,
+       rc = serialize_context_for_kernel(&lnd.lnd_ctx, &lnd.lnd_ctx_token,
                                          lnd.lnd_mech);
        if (rc) {
                logmsg(LL_ERR, "key %08x: failed to export context\n", keyid);
-               error_kernel_key(keyid, rc, lnd.lnd_gss_err);
+               error_kernel_key(keyid, rc, lnd.lnd_gss_err, cred->lc_uid);
                goto out;
        }
 
@@ -692,7 +765,7 @@ retry_nego:
               keyid, kup->kup_uid);
 out:
        if (lnd.lnd_ctx_token.length != 0)
-               gss_release_buffer(&min_stat, &lnd.lnd_ctx_token);
+               (void)gss_release_buffer(&min_stat, &lnd.lnd_ctx_token);
 
        lgssc_fini_nego_data(&lnd);
 
@@ -714,14 +787,14 @@ static int lgssc_kr_negotiate_manual(key_serial_t keyid, struct lgss_cred *cred,
        if (rc) {
                logmsg(LL_ERR, "key %08x: failed to construct service "
                       "string\n", keyid);
-               error_kernel_key(keyid, -EACCES, 0);
+               error_kernel_key(keyid, -EACCES, 0, 0);
                goto out_cred;
        }
 
        rc = lgss_using_cred(cred);
        if (rc) {
                logmsg(LL_ERR, "key %08x: can't use cred\n", keyid);
-               error_kernel_key(keyid, -EACCES, 0);
+               error_kernel_key(keyid, -EACCES, 0, 0);
                goto out_cred;
        }
 
@@ -731,7 +804,7 @@ retry:
        if (rc) {
                logmsg(LL_ERR, "key %08x: failed to initialize "
                       "negotiation data\n", keyid);
-               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err);
+               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err, 0);
                goto out_cred;
        }
 
@@ -747,7 +820,7 @@ retry:
                goto retry;
        } else if (rc) {
                logmsg(LL_ERR, "key %08x: failed to negotiate\n", keyid);
-               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err);
+               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err, 0);
                goto out;
        }
 
@@ -902,7 +975,6 @@ out:
 
 static int associate_with_ns(char *path)
 {
-#ifdef HAVE_SETNS
        int fd, rc = -1;
 
        fd = open(path, O_RDONLY);
@@ -912,42 +984,75 @@ static int associate_with_ns(char *path)
        }
 
        return rc;
-#else
-       return -1;
-#endif /* HAVE_SETNS */
 }
 
 static int prepare_and_instantiate(struct lgss_cred *cred, key_serial_t keyid,
                                   uint32_t uid)
 {
        key_serial_t inst_keyring;
+       bool prepared = true;
+       pid_t child = 1;
+       int rc = 0;
 
        if (lgss_prepare_cred(cred)) {
                logmsg(LL_ERR, "key %08x: failed to prepare credentials "
                       "for user %d\n", keyid, uid);
-               return 1;
+               /* prepare failed, but still instantiate the key for regular
+                * user, so that it can be used to report the error later
+                * in the process
+                */
+               if (cred->lc_root_flags)
+                       return 1;
+               prepared = false;
        }
 
-       /* pre initialize the key. note the keyring linked to is actually of the
-        * original requesting process, not _this_ upcall process. if it's for
+       /* Pre initialize the key. Note the keyring linked to is actually of the
+        * original requesting process, not _this_ upcall process. If it's for
         * root user, don't link to any keyrings because we want fully control
-        * on it, and share it among all root sessions; otherswise link to
-        * session keyring.
+        * on it, and share it among all root sessions.
+        * Otherswise link to user keyring, which requires switching uid/gid.
+        * Do this in a subprocess because other operations need privileges.
         */
-       if (cred->lc_root_flags != 0)
+       if (cred->lc_root_flags) {
                inst_keyring = 0;
-       else
-               inst_keyring = KEY_SPEC_SESSION_KEYRING;
+       } else {
+               inst_keyring = KEY_SPEC_USER_KEYRING;
+               /* fork to not change identity in main process */
+               rc = fork_and_switch_id(uid, &child);
+               if (rc || child)
+                       goto out;
+       }
 
-       if (keyctl_instantiate(keyid, NULL, 0, inst_keyring)) {
-               logmsg(LL_ERR, "instantiate key %08x: %s\n",
-                      keyid, strerror(errno));
-               return 1;
+       /* if dealing with a user key, grant user write permission,
+        * it will be required for key update
+        */
+       if (child == 0) {
+               key_perm_t perm;
+
+               perm = KEY_POS_VIEW | KEY_POS_WRITE | KEY_POS_SEARCH |
+                       KEY_POS_LINK | KEY_POS_SETATTR |
+                       KEY_USR_VIEW | KEY_USR_WRITE;
+               if (keyctl_setperm(keyid, perm))
+                       logmsg(LL_ERR, "setperm %08x on key %08x: %s\n",
+                              perm, keyid, strerror(errno));
        }
 
-       logmsg(LL_TRACE, "instantiated kernel key %08x\n", keyid);
+       rc = keyctl_instantiate(keyid, NULL, 0, inst_keyring);
+       if (rc) {
+               rc = errno;
+               logmsg(LL_ERR, "instantiate key %08x in keyring id %d: %s\n",
+                      keyid, inst_keyring, strerror(rc));
+       } else {
+               logmsg(LL_TRACE,
+                      "instantiated kernel key %08x in keyring id %d\n",
+                      keyid, inst_keyring);
+       }
 
-       return 0;
+out:
+       if (child == 0)
+               /* job done for child */
+               exit(rc);
+       return prepared ? rc : 1;
 }
 
 /****************************************
@@ -956,25 +1061,47 @@ static int prepare_and_instantiate(struct lgss_cred *cred, key_serial_t keyid,
 
 int main(int argc, char *argv[])
 {
-       struct keyring_upcall_param   uparam;
-       key_serial_t                  keyid;
-       key_serial_t                  sring;
-       pid_t                         child;
-       int                           req_fd[2] = { -1, -1 };
-       int                           reply_fd[2] = { -1, -1 };
-       struct lgss_mech_type        *mech;
-       struct lgss_cred             *cred;
-       char                          path[PATH_MAX] = "";
-       int                           other_ns = 0;
-       int                           rc = 0;
-#ifdef HAVE_SETNS
-       struct stat                   parent_ns = { .st_ino = 0 };
-       struct stat                   caller_ns = { .st_ino = 0 };
-#endif
+       struct keyring_upcall_param uparam;
+       key_serial_t keyid;
+       key_serial_t sring;
+       pid_t child;
+       int req_fd[2] = { -1, -1 };
+       int reply_fd[2] = { -1, -1 };
+       struct lgss_mech_type *mech;
+       struct lgss_cred *cred;
+       char path[PATH_MAX] = "";
+       int other_ns = 0;
+       int rc = 0, opt;
+       struct stat parent_ns = { .st_ino = 0 };
+       struct stat caller_ns = { .st_ino = 0 };
+
+       static struct option long_opts[] = {
+               { .name = "realm", .has_arg = required_argument, .val = 'R'},
+               { .name = NULL, } };
 
        set_log_level();
 
        logmsg(LL_TRACE, "start parsing parameters\n");
+
+       /* one possible option before upcall parameters: -R REALM */
+       while ((opt = getopt_long(argc, argv, "R:", long_opts, NULL)) != EOF) {
+               switch (opt) {
+               case 'R':
+                       lgss_client_realm = optarg;
+                       break;
+               default:
+                       logmsg(LL_ERR, "invalid parameter %s\n",
+                              argv[optind - 1]);
+                       return 1;
+               }
+       }
+
+       if (lgss_client_realm) {
+               /* shift args to meet expected upcall parameters */
+               argc -= optind - 1;
+               argv += optind - 1;
+       }
+
        /*
         * parse & sanity check upcall parameters
         * expected to be called with:
@@ -994,21 +1121,27 @@ int main(int argc, char *argv[])
                return 1;
        }
 
-       logmsg(LL_INFO, "key %s, desc %s, ugid %s:%s, sring %s, coinfo %s\n",
-              argv[2], argv[4], argv[6], argv[7], argv[10], argv[5]);
-
        memset(&uparam, 0, sizeof(uparam));
 
        if (strcmp(argv[1], "create") != 0) {
-               logmsg(LL_ERR, "invalid OP %s\n", argv[1]);
+               logmsg(LL_ERR,
+                      "invalid OP %s (key %s, desc %s, ugid %s:%s, sring %s, coinfo %s)\n",
+                      argv[1], argv[2], argv[4], argv[6], argv[7], argv[10],
+                      argv[5]);
                return 1;
        }
 
        if (sscanf(argv[2], "%d", &keyid) != 1) {
-               logmsg(LL_ERR, "can't extract KeyID: %s\n", argv[2]);
+               logmsg(LL_ERR,
+                      "can't extract KeyID: %s (key %s, desc %s, ugid %s:%s, sring %s, coinfo %s)\n",
+                      argv[2], argv[2], argv[4], argv[6], argv[7], argv[10],
+                      argv[5]);
                return 1;
        }
 
+       logmsg(LL_INFO, "key %08x, desc %s, ugid %s:%s, sring %s, coinfo %s\n",
+              keyid, argv[4], argv[6], argv[7], argv[10], argv[5]);
+
        if (sscanf(argv[6], "%d", &uparam.kup_fsuid) != 1) {
                logmsg(LL_ERR, "can't extract UID: %s\n", argv[6]);
                return 1;
@@ -1064,25 +1197,32 @@ int main(int argc, char *argv[])
        cred->lc_svc_type = uparam.kup_svc_type;
        cred->lc_self_nid = uparam.kup_selfnid;
 
-#ifdef HAVE_SETNS
        /* Is caller in different namespace? */
-       snprintf(path, sizeof(path), "/proc/%d/ns/mnt", getpid());
-       if (stat(path, &parent_ns))
-               logmsg(LL_ERR, "cannot stat %s: %s\n", path, strerror(errno));
-       snprintf(path, sizeof(path), "/proc/%d/ns/mnt", uparam.kup_pid);
-       if (stat(path, &caller_ns))
-               logmsg(LL_ERR, "cannot stat %s: %s\n", path, strerror(errno));
-       if (caller_ns.st_ino != parent_ns.st_ino) {
-               other_ns = 1;
+       /* If passed caller's pid is 0, it means we have to stick
+        * with current namespace.
+        */
+       if (uparam.kup_pid) {
+               snprintf(path, sizeof(path), "/proc/%d/ns/mnt", getpid());
+               if (stat(path, &parent_ns)) {
+                       logmsg(LL_DEBUG, "cannot stat %s: %s\n",
+                              path, strerror(errno));
+               } else {
+                       snprintf(path, sizeof(path), "/proc/%d/ns/mnt",
+                                uparam.kup_pid);
+                       if (stat(path, &caller_ns))
+                               logmsg(LL_DEBUG, "cannot stat %s: %s\n",
+                                      path, strerror(errno));
+                       else if (caller_ns.st_ino != parent_ns.st_ino)
+                               other_ns = 1;
+               }
        }
-#endif /* HAVE_SETNS */
 
        /*
         * if caller's namespace is different, fork a child and associate it
         * with caller's namespace to do credentials preparation
         */
        if (other_ns) {
-               logmsg(LL_TRACE, "caller's namespace is diffent\n");
+               logmsg(LL_TRACE, "caller's namespace is different\n");
 
                /* use pipes to pass info between child and parent processes */
                if (pipe(req_fd) == -1) {
@@ -1226,13 +1366,19 @@ out_pipe:
                close(req_fd[1]);
                close(reply_fd[0]);
                close(reply_fd[1]);
+               lgss_fini(cred);
                return rc;
        } else {
-               logmsg(LL_TRACE, "caller's namespace is the same\n");
-
+               if (uparam.kup_pid)
+                       logmsg(LL_TRACE, "caller's namespace is the same\n");
+               else
+                       logmsg(LL_TRACE, "stick with current namespace\n");
+
+               /* In case of prepare error, a key will be instantiated
+                * all the same. But then we will have to error this key
+                * instead of doing normal gss negotiation.
+                */
                rc = prepare_and_instantiate(cred, keyid, uparam.kup_uid);
-               if (rc != 0)
-                       return rc;
 
                /*
                 * fork a child to do the real gss negotiation
@@ -1241,13 +1387,23 @@ out_pipe:
                if (child == -1) {
                        logmsg(LL_ERR, "key %08x: can't create child: %s\n",
                               keyid, strerror(errno));
-                       return 1;
+                       rc = 1;
+                       goto out_reg;
                } else if (child == 0) {
-                       return lgssc_kr_negotiate(keyid, cred, &uparam,
-                                                 req_fd, reply_fd);
+                       if (rc)
+                               rc = error_kernel_key(keyid, -ENOKEY, 0,
+                                                     cred->lc_uid);
+                       else
+                               rc = lgssc_kr_negotiate(keyid, cred, &uparam,
+                                                       req_fd, reply_fd);
+                       goto out_reg;
+               } else {
+                       logmsg(LL_TRACE, "forked child %d\n", child);
+                       return 0;
                }
 
-               logmsg(LL_TRACE, "forked child %d\n", child);
-               return 0;
+out_reg:
+               lgss_fini(cred);
+               return rc;
        }
 }