Whamcloud - gitweb
LU-13474 gss: do not return -ERESTART when gss rpc times out
[fs/lustre-release.git] / lustre / utils / gss / lgss_keyring.c
index cf252aa..6748f7c 100644 (file)
  *
  * You should have received a copy of the GNU General Public License
  * version 2 along with this program; If not, see
- * http://www.sun.com/software/products/lustre/docs/GPLv2.pdf
- *
- * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
- * CA 95054 USA or visit www.sun.com if you need additional information or
- * have any questions.
+ * http://www.gnu.org/licenses/gpl-2.0.html
  *
  * GPL HEADER END
  */
@@ -27,7 +23,7 @@
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
  * Use is subject to license terms.
  *
- * Copyright (c) 2011, 2014, Intel Corporation.
+ * Copyright (c) 2011, Intel Corporation.
  */
 /*
  * This file is part of Lustre, http://www.lustre.org/
  * Author: Eric Mei <ericm@clusterfs.com>
  */
 
+#include <sched.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <fcntl.h>
+#include <inttypes.h>
 #include <string.h>
 #include <errno.h>
 #include <pwd.h>
 #include <keyutils.h>
 #include <gssapi/gssapi.h>
+#include <sys/wait.h>
 
+#include <libcfs/util/param.h>
+#include <libcfs/util/string.h>
 #include "lsupport.h"
 #include "lgss_utils.h"
 #include "write_bytes.h"
@@ -64,263 +67,422 @@ static char *g_service = NULL;
  * all data about negotiation
  */
 struct lgss_nego_data {
-        uint32_t        lnd_established:1;
-
-        int             lnd_secid;
-        uint32_t        lnd_uid;
-        uint32_t        lnd_lsvc;
-        char           *lnd_uuid;
-
-        gss_OID         lnd_mech;               /* mech OID */
-        gss_name_t      lnd_svc_name;           /* service name */
-        u_int           lnd_req_flags;          /* request flags */
-        gss_cred_id_t   lnd_cred;               /* credential */
-        gss_ctx_id_t    lnd_ctx;                /* session context */
-        gss_buffer_desc lnd_rmt_ctx;            /* remote handle of context */
-        uint32_t        lnd_seq_win;            /* sequence window */
-
-        int             lnd_rpc_err;
-        int             lnd_gss_err;
+       uint32_t        lnd_established:1;
+
+       int             lnd_secid;
+       uint32_t        lnd_uid;
+       uint32_t        lnd_lsvc;
+       char            *lnd_uuid;
+
+       gss_OID         lnd_mech;               /* mech OID */
+       gss_name_t      lnd_svc_name;           /* service name */
+       unsigned int    lnd_req_flags;          /* request flags */
+       gss_cred_id_t   lnd_cred;               /* credential */
+       gss_ctx_id_t    lnd_ctx;                /* session context */
+       gss_buffer_desc lnd_rmt_ctx;            /* remote handle of context */
+       gss_buffer_desc lnd_ctx_token;          /* context token for kernel */
+       uint32_t        lnd_seq_win;            /* sequence window */
+
+       int             lnd_rpc_err;
+       int             lnd_gss_err;
 };
 
 /*
  * context creation response
  */
 struct lgss_init_res {
-        gss_buffer_desc gr_ctx;         /* context handle */
-        u_int           gr_major;       /* major status */
-        u_int           gr_minor;       /* minor status */
-        u_int           gr_win;         /* sequence window */
-        gss_buffer_desc gr_token;       /* token */
+       gss_buffer_desc gr_ctx;         /* context handle */
+       unsigned int    gr_major;       /* major status */
+       unsigned int    gr_minor;       /* minor status */
+       unsigned int    gr_win;         /* sequence window */
+       gss_buffer_desc gr_token;       /* token */
 };
 
 struct keyring_upcall_param {
-        uint32_t        kup_ver;
-        uint32_t        kup_secid;
-        uint32_t        kup_uid;
-        uint32_t        kup_fsuid;
-        uint32_t        kup_gid;
-        uint32_t        kup_fsgid;
-        uint32_t        kup_svc;
-        uint64_t        kup_nid;
-        char            kup_tgt[64];
-        char            kup_mech[16];
-        unsigned int    kup_is_root:1,
-                        kup_is_mdt:1,
-                        kup_is_ost:1;
+       uint32_t        kup_ver;
+       uint32_t        kup_secid;
+       uint32_t        kup_uid;
+       uint32_t        kup_fsuid;
+       uint32_t        kup_gid;
+       uint32_t        kup_fsgid;
+       uint32_t        kup_svc;
+       uint64_t        kup_nid;
+       uint64_t        kup_selfnid;
+       char            kup_svc_type;
+       char            kup_tgt[64];
+       char            kup_mech[16];
+       unsigned int    kup_is_root:1,
+                       kup_is_mdt:1,
+                       kup_is_ost:1;
+       uint32_t        kup_pid;
 };
 
 /****************************************
  * child process: gss negotiation       *
  ****************************************/
 
-#define INIT_CHANNEL    "/proc/fs/lustre/sptlrpc/gss/init_channel"
-
-int do_nego_rpc(struct lgss_nego_data *lnd,
-                gss_buffer_desc *gss_token,
-                struct lgss_init_res *gr)
+static int send_to(int fd, const void *buf, size_t size)
 {
-        struct lgssd_ioctl_param  param;
-        struct passwd            *pw;
-        int                       fd, ret, res;
-        char                      outbuf[8192];
-        unsigned int             *p;
-
-        logmsg(LL_TRACE, "start negotiation rpc\n");
-
-        pw = getpwuid(lnd->lnd_uid);
-        if (!pw) {
-                logmsg(LL_ERR, "no uid %u in local user database\n",
-                       lnd->lnd_uid);
-                return -EACCES;
-        }
+       ssize_t sz;
+
+       sz = write(fd, buf, size);
+       if (sz == -1) {
+               logmsg(LL_ERR, "cannot send to GSS process: %s\n",
+                      strerror(errno));
+               return -errno;
+       }
+       if (sz < size) {
+               logmsg(LL_ERR, "short write sending to GSS process: %d/%d\n",
+                      (int)sz, (int)size);
+               return -EPROTO;
+       }
 
-        param.version = GSSD_INTERFACE_VERSION;
-        param.secid = lnd->lnd_secid;
-        param.uuid = lnd->lnd_uuid;
-        param.lustre_svc = lnd->lnd_lsvc;
-        param.uid = lnd->lnd_uid;
-        param.gid = pw->pw_gid;
-        param.send_token_size = gss_token->length;
-        param.send_token = (char *) gss_token->value;
-        param.reply_buf_size = sizeof(outbuf);
-        param.reply_buf = outbuf;
-
-        logmsg(LL_TRACE, "to open " INIT_CHANNEL "\n");
-
-        fd = open(INIT_CHANNEL, O_WRONLY);
-        if (fd < 0) {
-                logmsg(LL_ERR, "can't open " INIT_CHANNEL "\n");
-                return -EACCES;
-        }
+       return 0;
+}
 
-        logmsg(LL_TRACE, "to down-write\n");
+static int receive_from(int fd, void *buf, size_t size)
+{
+       ssize_t sz;
+
+       sz = read(fd, buf, size);
+       if (sz == -1) {
+               logmsg(LL_ERR, "cannot receive from GSS process: %s\n",
+                      strerror(errno));
+               return -errno;
+       }
+       if (sz < size) {
+               logmsg(LL_ERR, "short read receiving from GSS process: %d/%d\n",
+                      (int)sz, (int)size);
+               return -EPROTO;
+       }
 
-        ret = write(fd, &param, sizeof(param));
-        if (ret != sizeof(param)) {
-                logmsg(LL_ERR, "lustre ioctl err: %d\n", strerror(errno));
-                close(fd);
-                return -EACCES;
-        }
-        close(fd);
-
-        logmsg(LL_TRACE, "do_nego_rpc: to parse reply\n");
-        if (param.status) {
-                logmsg(LL_ERR, "status: %d (%s)\n",
-                       param.status, strerror((int)param.status));
-
-                /* kernel return -ETIMEDOUT means the rpc timedout, we should
-                 * notify the caller to reinitiate the gss negotiation, by
-                 * returning -ERESTART
-                 */
-                if (param.status == -ETIMEDOUT)
-                        return -ERESTART;
-                else
-                        return param.status;
-        }
+       return 0;
+}
 
-        p = (unsigned int *)outbuf;
-        res = *p++;
-        gr->gr_major = *p++;
-        gr->gr_minor = *p++;
-        gr->gr_win = *p++;
+static int gss_do_ioctl(struct lgssd_ioctl_param *param)
+{
+       int fd, ret;
+       glob_t path;
+       int rc;
+
+       rc = cfs_get_param_paths(&path, "sptlrpc/gss/init_channel");
+       if (rc != 0)
+               return rc;
+
+       logmsg(LL_TRACE, "to open %s\n", path.gl_pathv[0]);
+
+       fd = open(path.gl_pathv[0], O_WRONLY);
+       if (fd < 0) {
+               logmsg(LL_ERR, "can't open %s\n", path.gl_pathv[0]);
+               rc = -EACCES;
+               goto out_params;
+       }
+
+       logmsg(LL_TRACE, "to down-write\n");
+
+       ret = write(fd, param, sizeof(*param));
+       close(fd);
+       if (ret != sizeof(*param)) {
+               logmsg(LL_ERR, "lustre ioctl err: %s\n", strerror(errno));
+               rc = -EACCES;
+       }
+
+out_params:
+       cfs_free_param_data(&path);
+       return rc;
+}
 
-        gr->gr_ctx.length = *p++;
-        gr->gr_ctx.value = malloc(gr->gr_ctx.length);
-        memcpy(gr->gr_ctx.value, p, gr->gr_ctx.length);
-        p += (((gr->gr_ctx.length + 3) & ~3) / 4);
+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;
+       int res;
+       char outbuf[8192] = { 0 };
+       unsigned int *p;
+       int rc = 0;
+
+       logmsg(LL_TRACE, "start negotiation rpc\n");
+
+       pw = getpwuid(lnd->lnd_uid);
+       if (!pw) {
+               logmsg(LL_ERR, "no uid %u in local user database\n",
+                      lnd->lnd_uid);
+               return -EACCES;
+       }
+
+       param.version = GSSD_INTERFACE_VERSION;
+       param.secid = lnd->lnd_secid;
+       param.uuid = lnd->lnd_uuid;
+       param.lustre_svc = lnd->lnd_lsvc;
+       param.uid = lnd->lnd_uid;
+       param.gid = pw->pw_gid;
+       param.send_token_size = gss_token->length;
+       param.send_token = (char *) gss_token->value;
+
+       if (req_fd[0] == -1 && reply_fd[0] == -1) {
+               /* we can do the ioctl directly */
+               param.reply_buf_size = sizeof(outbuf);
+               param.reply_buf = outbuf;
+
+               rc = gss_do_ioctl(&param);
+               if (rc != 0)
+                       return rc;
+       } else {
+               /* looks like we are running in a container,
+                * so we cannot do the ioctl ourselves: delegate to
+                * parent process running directly on host */
+
+               /* send ioctl buffer to parent */
+               rc = send_to(req_fd[1], &param, sizeof(param));
+               if (rc != 0)
+                       return rc;
+               /* send gss token to parent */
+               rc = send_to(req_fd[1], gss_token->value, gss_token->length);
+               if (rc != 0)
+                       return rc;
+
+               /* read ioctl status from parent */
+               rc = receive_from(reply_fd[0], &param.status,
+                                 sizeof(param.status));
+               if (rc != 0)
+                       return rc;
+
+               if (param.status == 0) {
+                       /* read reply buffer from parent */
+                       rc = receive_from(reply_fd[0], outbuf, sizeof(outbuf));
+                       if (rc != 0)
+                               return rc;
+               }
+       }
+
+       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)));
+               return param.status;
+       }
+
+       p = (unsigned int *)outbuf;
+       res = *p++;
+       gr->gr_major = *p++;
+       gr->gr_minor = *p++;
+       gr->gr_win = *p++;
+
+       gr->gr_ctx.length = *p++;
+       gr->gr_ctx.value = malloc(gr->gr_ctx.length);
+       if (gr->gr_ctx.value == NULL)
+               return -ENOMEM;
+       memcpy(gr->gr_ctx.value, p, gr->gr_ctx.length);
+       p += (((gr->gr_ctx.length + 3) & ~3) / 4);
+
+       gr->gr_token.length = *p++;
+       gr->gr_token.value = malloc(gr->gr_token.length);
+       if (gr->gr_token.value == NULL) {
+               free(gr->gr_ctx.value);
+               return -ENOMEM;
+       }
+       memcpy(gr->gr_token.value, p, gr->gr_token.length);
+       p += (((gr->gr_token.length + 3) & ~3) / 4);
+
+       logmsg(LL_DEBUG, "do_nego_rpc: receive handle len %zu, token len %zu, "
+              "res %d\n", gr->gr_ctx.length, gr->gr_token.length, res);
 
-        gr->gr_token.length = *p++;
-        gr->gr_token.value = malloc(gr->gr_token.length);
-        memcpy(gr->gr_token.value, p, gr->gr_token.length);
-        p += (((gr->gr_token.length + 3) & ~3) / 4);
+       return rc;
+}
 
-       logmsg(LL_DEBUG, "do_nego_rpc: receive handle len %d, token len %d, " \
-              "res %d\n", gr->gr_ctx.length, gr->gr_token.length, res);
+/* This is used by incomplete GSSAPI implementations that can't use
+ * gss_init_sec_context and will parse the token themselves (gssnull and sk).
+ * Callers should have cred->lc_mech_token pointing to a gss_buffer_desc
+ * token to send to the peer as part of the SEC_CTX_INIT operation.  The return
+ * RPC's token with be in gr.gr_token which is validated using
+ * lgss_validate_cred. */
+static int lgssc_negotiation_manual(struct lgss_nego_data *lnd,
+                                   struct lgss_cred *cred,
+                                   int req_fd[2], int reply_fd[2])
+{
+       struct lgss_init_res gr;
+       OM_uint32 min_stat;
+       int rc;
+
+       logmsg(LL_TRACE, "starting gss negotation\n");
+       memset(&gr, 0, sizeof(gr));
+
+       lnd->lnd_rpc_err = do_nego_rpc(lnd, &cred->lc_mech_token, &gr,
+                                      req_fd, reply_fd);
+       if (lnd->lnd_rpc_err) {
+               logmsg(LL_ERR, "negotiation rpc error %d\n", lnd->lnd_rpc_err);
+               rc = lnd->lnd_rpc_err;
+               goto out_error;
+       }
+
+       if (gr.gr_major == GSS_S_CONTINUE_NEEDED) {
+               rc = -EAGAIN;
+               goto out_error;
+
+       } else if (gr.gr_major != GSS_S_COMPLETE) {
+               lnd->lnd_gss_err = gr.gr_major;
+               logmsg(LL_ERR, "negotiation gss error %x\n", lnd->lnd_gss_err);
+               rc = -ENOTCONN;
+               goto out_error;
+       }
+
+       if (gr.gr_ctx.length == 0 || gr.gr_token.length == 0) {
+               logmsg(LL_ERR, "zero length context or token received\n");
+               rc = -EINVAL;
+               goto out_error;
+       }
+
+       rc = lgss_validate_cred(cred, &gr.gr_token, &lnd->lnd_ctx_token);
+       if (rc) {
+               logmsg(LL_ERR, "peer token failed validation\n");
+               goto out_error;
+       }
+
+       lnd->lnd_established = 1;
+       lnd->lnd_seq_win = gr.gr_win;
+       lnd->lnd_rmt_ctx = gr.gr_ctx;
+
+       if (gr.gr_token.length != 0)
+               gss_release_buffer(&min_stat, &gr.gr_token);
+
+       logmsg(LL_DEBUG, "successfully negotiated a context\n");
        return 0;
+
+out_error:
+       if (gr.gr_ctx.length != 0)
+               gss_release_buffer(&min_stat, &gr.gr_ctx);
+       if (gr.gr_token.length != 0)
+               gss_release_buffer(&min_stat, &gr.gr_token);
+
+       return rc;
 }
 
 /*
  * if return error, the lnd_rpc_err or lnd_gss_err is set.
  */
-static int lgssc_negotiation(struct lgss_nego_data *lnd)
+static int lgssc_negotiation(struct lgss_nego_data *lnd, int req_fd[2],
+                            int reply_fd[2])
 {
-        struct lgss_init_res    gr;
-        gss_buffer_desc        *recv_tokenp, send_token;
-        OM_uint32               maj_stat, min_stat, ret_flags;
-
-        logmsg(LL_TRACE, "start gss negotiation\n");
-
-        /* GSS context establishment loop. */
-        memset(&gr, 0, sizeof(gr));
-        recv_tokenp = GSS_C_NO_BUFFER;
-
-        for (;;) {
-                maj_stat = gss_init_sec_context(&min_stat,
-                                                lnd->lnd_cred,
-                                                &lnd->lnd_ctx,
-                                                lnd->lnd_svc_name,
-                                                lnd->lnd_mech,
-                                                lnd->lnd_req_flags,
-                                                0,            /* time req */
-                                                NULL,         /* channel */
-                                                recv_tokenp,
-                                                NULL,         /* used mech */
-                                                &send_token,
-                                                &ret_flags,
-                                                NULL);        /* time rec */
-
-                if (recv_tokenp != GSS_C_NO_BUFFER) {
-                        gss_release_buffer(&min_stat, &gr.gr_token);
-                        recv_tokenp = GSS_C_NO_BUFFER;
-                }
-
-                if (maj_stat != GSS_S_COMPLETE &&
-                    maj_stat != GSS_S_CONTINUE_NEEDED) {
-                        lnd->lnd_gss_err = maj_stat;
-
-                        logmsg_gss(LL_ERR, lnd->lnd_mech, maj_stat, min_stat,
-                                   "failed init context");
-                        break;
-                }
-
-                if (send_token.length != 0) {
-                        memset(&gr, 0, sizeof(gr));
-
-                        lnd->lnd_rpc_err = do_nego_rpc(lnd, &send_token, &gr);
-                        gss_release_buffer(&min_stat, &send_token);
-
-                        if (lnd->lnd_rpc_err) {
-                                logmsg(LL_ERR, "negotiation rpc error: %d\n",
-                                       lnd->lnd_rpc_err);
-                                return -1;
-                        }
-
-                        if (gr.gr_major != GSS_S_COMPLETE &&
-                            gr.gr_major != GSS_S_CONTINUE_NEEDED) {
-                                lnd->lnd_gss_err = gr.gr_major;
-
-                                logmsg(LL_ERR, "negotiation gss error %x\n",
-                                       lnd->lnd_gss_err);
-                                return -1;
-                        }
-
-                        if (gr.gr_ctx.length != 0) {
-                                if (lnd->lnd_rmt_ctx.value)
-                                        gss_release_buffer(&min_stat,
-                                                           &lnd->lnd_rmt_ctx);
-                                lnd->lnd_rmt_ctx = gr.gr_ctx;
-                        }
-
-                        if (gr.gr_token.length != 0) {
-                                if (maj_stat != GSS_S_CONTINUE_NEEDED)
-                                        break;
-                                recv_tokenp = &gr.gr_token;
-                        }
-                }
-
-                /* GSS_S_COMPLETE => check gss header verifier,
-                 * usually checked in gss_validate
-                 */
-                if (maj_stat == GSS_S_COMPLETE) {
-                        lnd->lnd_established = 1;
-                        lnd->lnd_seq_win = gr.gr_win;
-                        break;
-                }
-        }
-
-        /* End context negotiation loop. */
-        if (!lnd->lnd_established) {
-                if (gr.gr_token.length != 0)
-                        gss_release_buffer(&min_stat, &gr.gr_token);
-
-                if (lnd->lnd_gss_err == GSS_S_COMPLETE)
-                        lnd->lnd_rpc_err = -EACCES;
-
-                logmsg(LL_ERR, "context negotiation failed\n");
-                return -1;
-        }
-
-        logmsg(LL_DEBUG, "successfully negotiated a context\n");
-        return 0;
+       struct lgss_init_res    gr;
+       gss_buffer_desc        *recv_tokenp, send_token;
+       OM_uint32               maj_stat, min_stat, ret_flags;
+
+       logmsg(LL_TRACE, "start gss negotiation\n");
+
+       /* GSS context establishment loop. */
+       memset(&gr, 0, sizeof(gr));
+       recv_tokenp = GSS_C_NO_BUFFER;
+
+       for (;;) {
+               maj_stat = gss_init_sec_context(&min_stat,
+                                               lnd->lnd_cred,
+                                               &lnd->lnd_ctx,
+                                               lnd->lnd_svc_name,
+                                               lnd->lnd_mech,
+                                               lnd->lnd_req_flags,
+                                               0,            /* time req */
+                                               NULL,         /* channel */
+                                               recv_tokenp,
+                                               NULL,         /* used mech */
+                                               &send_token,
+                                               &ret_flags,
+                                               NULL);        /* time rec */
+
+               if (recv_tokenp != GSS_C_NO_BUFFER) {
+                       gss_release_buffer(&min_stat, &gr.gr_token);
+                       recv_tokenp = GSS_C_NO_BUFFER;
+               }
+
+               if (maj_stat != GSS_S_COMPLETE &&
+                   maj_stat != GSS_S_CONTINUE_NEEDED) {
+                       lnd->lnd_gss_err = maj_stat;
+
+                       logmsg_gss(LL_ERR, lnd->lnd_mech, maj_stat, min_stat,
+                                  "failed init context");
+                       break;
+               }
+
+               if (send_token.length != 0) {
+                       memset(&gr, 0, sizeof(gr));
+
+                       lnd->lnd_rpc_err = do_nego_rpc(lnd, &send_token, &gr,
+                                                      req_fd, reply_fd);
+                       gss_release_buffer(&min_stat, &send_token);
+
+                       if (lnd->lnd_rpc_err) {
+                               logmsg(LL_ERR, "negotiation rpc error: %d\n",
+                                      lnd->lnd_rpc_err);
+                               return lnd->lnd_rpc_err;
+                       }
+
+                       if (gr.gr_major != GSS_S_COMPLETE &&
+                           gr.gr_major != GSS_S_CONTINUE_NEEDED) {
+                               lnd->lnd_gss_err = gr.gr_major;
+
+                               logmsg(LL_ERR, "negotiation gss error %x\n",
+                                      lnd->lnd_gss_err);
+                               return -ENOTCONN;
+                       }
+
+                       if (gr.gr_ctx.length != 0) {
+                               if (lnd->lnd_rmt_ctx.value)
+                                       gss_release_buffer(&min_stat,
+                                                          &lnd->lnd_rmt_ctx);
+                               lnd->lnd_rmt_ctx = gr.gr_ctx;
+                       }
+
+                       if (gr.gr_token.length != 0) {
+                               if (maj_stat != GSS_S_CONTINUE_NEEDED)
+                                       break;
+                               recv_tokenp = &gr.gr_token;
+                       }
+               }
+
+               /* GSS_S_COMPLETE => check gss header verifier,
+                * usually checked in gss_validate
+                */
+               if (maj_stat == GSS_S_COMPLETE) {
+                       lnd->lnd_established = 1;
+                       lnd->lnd_seq_win = gr.gr_win;
+                       break;
+               }
+       }
+
+       /* End context negotiation loop. */
+       if (!lnd->lnd_established) {
+               if (gr.gr_token.length != 0)
+                       gss_release_buffer(&min_stat, &gr.gr_token);
+
+               if (lnd->lnd_gss_err == GSS_S_COMPLETE)
+                       lnd->lnd_rpc_err = -EACCES;
+
+               logmsg(LL_ERR, "context negotiation failed\n");
+               return -1;
+       }
+
+       logmsg(LL_DEBUG, "successfully negotiated a context\n");
+       return 0;
 }
 
 /*
  * if return error, the lnd_rpc_err or lnd_gss_err is set.
  */
 static int lgssc_init_nego_data(struct lgss_nego_data *lnd,
-                                struct keyring_upcall_param *kup,
-                                lgss_mech_t mech)
+                               struct keyring_upcall_param *kup,
+                               enum lgss_mech mech)
 {
         gss_buffer_desc         sname;
         OM_uint32               maj_stat, min_stat;
 
         memset(lnd, 0, sizeof(*lnd));
 
-        lnd->lnd_secid = kup->kup_secid;
-        lnd->lnd_uid = kup->kup_uid;
-        lnd->lnd_lsvc = kup->kup_svc;
-        lnd->lnd_uuid = kup->kup_tgt;
+       lnd->lnd_secid = kup->kup_secid;
+       lnd->lnd_uid = kup->kup_uid;
+       lnd->lnd_lsvc = kup->kup_svc | mech << LUSTRE_GSS_MECH_SHIFT;
+       lnd->lnd_uuid = kup->kup_tgt;
 
         lnd->lnd_established = 0;
         lnd->lnd_svc_name = GSS_C_NO_NAME;
@@ -330,14 +492,23 @@ static int lgssc_init_nego_data(struct lgss_nego_data *lnd,
         lnd->lnd_seq_win = 0;
 
         switch (mech) {
-        case LGSS_MECH_KRB5:
-                lnd->lnd_mech = (gss_OID) &krb5oid;
-                lnd->lnd_req_flags = GSS_C_MUTUAL_FLAG;
-                break;
-        default:
-                logmsg(LL_ERR, "invalid mech: %d\n", mech);
-                lnd->lnd_rpc_err = -EACCES;
-                return -1;
+       case LGSS_MECH_KRB5:
+               lnd->lnd_mech = (gss_OID)&krb5oid;
+               lnd->lnd_req_flags = GSS_C_MUTUAL_FLAG;
+               break;
+       case LGSS_MECH_NULL:
+               lnd->lnd_mech = (gss_OID)&nulloid;
+               break;
+#ifdef HAVE_OPENSSL_SSK
+       case LGSS_MECH_SK:
+               lnd->lnd_mech = (gss_OID)&skoid;
+               lnd->lnd_req_flags = GSS_C_MUTUAL_FLAG;
+               break;
+#endif
+       default:
+               logmsg(LL_ERR, "invalid mech: %d\n", mech);
+               lnd->lnd_rpc_err = -EACCES;
+               return -1;
         }
 
         sname.value = g_service;
@@ -462,72 +633,168 @@ out:
         return rc;
 }
 
-/*
- * note we inherited assumed authority from parent process
- */
-static int lgssc_kr_negotiate(key_serial_t keyid, struct lgss_cred *cred,
-                              struct keyring_upcall_param *kup)
+static int lgssc_kr_negotiate_krb(key_serial_t keyid, struct lgss_cred *cred,
+                                 struct keyring_upcall_param *kup,
+                                 int req_fd[2], int reply_fd[2])
 {
-        struct lgss_nego_data   lnd;
-        gss_buffer_desc         token = GSS_C_EMPTY_BUFFER;
-        OM_uint32               min_stat;
-        int                     rc = -1;
-
-        logmsg(LL_TRACE, "child start on behalf of key %08x: "
-               "cred %p, uid %u, svc %u, nid %llx, uids: %u:%u/%u:%u\n",
-               keyid, cred, cred->lc_uid, cred->lc_tgt_svc, cred->lc_tgt_nid,
-               kup->kup_uid, kup->kup_gid, kup->kup_fsuid, kup->kup_fsgid);
-
-        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);
-                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);
-                goto out_cred;
-        }
-
-        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);
-                goto out_cred;
-        }
-
-        rc = lgssc_negotiation(&lnd);
-        if (rc) {
-                logmsg(LL_ERR, "key %08x: failed to negotiation\n", keyid);
-                error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err);
-                goto out;
-        }
+       struct lgss_nego_data lnd;
+       OM_uint32 min_stat;
+       int rc = -1;
+       bool redo = true;
+
+       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);
+               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);
+               goto out_cred;
+       }
+
+retry_nego:
+       memset(&lnd, 0, sizeof(lnd));
+       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);
+               goto out_cred;
+       }
+
+       rc = lgssc_negotiation(&lnd, req_fd, reply_fd);
+       if (rc == -EAGAIN || (rc == -ETIMEDOUT && redo)) {
+               logmsg(LL_ERR, "Failed negotiation must retry\n");
+               redo = false;
+               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);
+               goto out;
+       }
+
+       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);
+               goto out;
+       }
+
+       rc = update_kernel_key(keyid,  &lnd, &lnd.lnd_ctx_token);
+       if (rc)
+               goto out;
+
+       rc = 0;
+       logmsg(LL_INFO, "key %08x for user %u is updated OK!\n",
+              keyid, kup->kup_uid);
+out:
+       if (lnd.lnd_ctx_token.length != 0)
+               gss_release_buffer(&min_stat, &lnd.lnd_ctx_token);
 
-        rc = serialize_context_for_kernel(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);
-                goto out;
-        }
+       lgssc_fini_nego_data(&lnd);
 
-        rc = update_kernel_key(keyid,  &lnd, &token);
-        if (rc)
-                goto out;
+out_cred:
+       lgss_release_cred(cred);
+       return rc;
+}
 
-        rc = 0;
-        logmsg(LL_INFO, "key %08x for user %u is updated OK!\n",
-               keyid, kup->kup_uid);
+static int lgssc_kr_negotiate_manual(key_serial_t keyid, struct lgss_cred *cred,
+                                    struct keyring_upcall_param *kup,
+                                    int req_fd[2], int reply_fd[2])
+{
+       struct lgss_nego_data lnd;
+       OM_uint32 min_stat;
+       int rc;
+       bool redo = true;
+
+       rc = lgss_get_service_str(&g_service, kup->kup_svc, kup->kup_nid);
+       if (rc) {
+               logmsg(LL_ERR, "key %08x: failed to construct service "
+                      "string\n", keyid);
+               error_kernel_key(keyid, -EACCES, 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);
+               goto out_cred;
+       }
+
+retry:
+       memset(&lnd, 0, sizeof(lnd));
+       rc = lgssc_init_nego_data(&lnd, kup, cred->lc_mech->lmt_mech_n);
+       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);
+               goto out_cred;
+       }
+
+       /*
+        * Handles the negotiation but then calls lgss_validate to make sure
+        * the token is valid.  It also populates the lnd_ctx_token for the
+        * update to the kernel key
+        */
+       rc = lgssc_negotiation_manual(&lnd, cred, req_fd, reply_fd);
+       if (rc == -EAGAIN || (rc == -ETIMEDOUT && redo)) {
+               logmsg(LL_ERR, "Failed negotiation must retry\n");
+               redo = false;
+               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);
+               goto out;
+       }
+
+       rc = update_kernel_key(keyid,  &lnd, &lnd.lnd_ctx_token);
+       if (rc)
+               goto out;
+
+       logmsg(LL_INFO, "key %08x for user %u is updated OK!\n",
+              keyid, kup->kup_uid);
 out:
-        if (token.length != 0)
-                gss_release_buffer(&min_stat, &token);
+       if (lnd.lnd_ctx_token.length != 0)
+               gss_release_buffer(&min_stat, &lnd.lnd_ctx_token);
 
-        lgssc_fini_nego_data(&lnd);
+       lgssc_fini_nego_data(&lnd);
 
 out_cred:
-        lgss_release_cred(cred);
-        return rc;
+       lgss_release_cred(cred);
+       return rc;
+}
+
+/*
+ * note we inherited assumed authority from parent process
+ */
+static int lgssc_kr_negotiate(key_serial_t keyid, struct lgss_cred *cred,
+                             struct keyring_upcall_param *kup,
+                             int req_fd[2], int reply_fd[2])
+{
+       int rc;
+
+       logmsg(LL_TRACE, "child start on behalf of key %08x: "
+              "cred %p, uid %u, svc %u, nid %"PRIx64", uids: %u:%u/%u:%u\n",
+              keyid, cred, cred->lc_uid, cred->lc_tgt_svc, cred->lc_tgt_nid,
+              kup->kup_uid, kup->kup_gid, kup->kup_fsuid, kup->kup_fsgid);
+
+       switch (cred->lc_mech->lmt_mech_n) {
+       case LGSS_MECH_NULL:
+       case LGSS_MECH_SK:
+               rc = lgssc_kr_negotiate_manual(keyid, cred, kup,
+                                              req_fd, reply_fd);
+               break;
+       case LGSS_MECH_KRB5:
+       default:
+               rc = lgssc_kr_negotiate_krb(keyid, cred, kup, req_fd, reply_fd);
+               break;
+       }
+
+       return rc;
 }
 
 /*
@@ -537,19 +804,22 @@ out_cred:
  *  [2]: uid            (uint)
  *  [3]: gid            (uint)
  *  [4]: flags          (string) FMT: r-root; m-mdt; o-ost
- *  [5]: lustre_svc     (uint)
- *  [6]: target_nid     (uint64)
- *  [7]: target_uuid    (string)
+ *  [5]: svc type       (char)
+ *  [6]: lustre_svc     (int)
+ *  [7]: target_nid     (uint64)
+ *  [8]: target_uuid    (string)
+ *  [9]: self_nid        (uint64)
+ *  [10]: pid            (uint)
  */
 static int parse_callout_info(const char *coinfo,
                               struct keyring_upcall_param *uparam)
 {
-        const int       nargs = 8;
-        char            buf[1024];
-        char           *string = buf;
-        int             length, i;
-        char           *data[nargs];
-        char           *pos;
+       const int       nargs = 11;
+       char            buf[1024];
+       char           *string = buf;
+       int             length, i;
+       char           *data[nargs];
+       char           *pos;
 
         length = strlen(coinfo) + 1;
         if (length > 1024) {
@@ -571,53 +841,113 @@ static int parse_callout_info(const char *coinfo,
         }
         data[i] = string;
 
-        logmsg(LL_TRACE, "components: %s,%s,%s,%s,%s,%s,%s,%s\n",
-               data[0], data[1], data[2], data[3], data[4], data[5],
-               data[6], data[7]);
-
-        uparam->kup_secid = strtol(data[0], NULL, 0);
-       strlcpy(uparam->kup_mech, data[1], sizeof(uparam->kup_mech));
-        uparam->kup_uid = strtol(data[2], NULL, 0);
-        uparam->kup_gid = strtol(data[3], NULL, 0);
-        if (strchr(data[4], 'r'))
-                uparam->kup_is_root = 1;
-        if (strchr(data[4], 'm'))
-                uparam->kup_is_mdt = 1;
-        if (strchr(data[4], 'o'))
-                uparam->kup_is_ost = 1;
-        uparam->kup_svc = strtol(data[5], NULL, 0);
-        uparam->kup_nid = strtoll(data[6], NULL, 0);
-       strlcpy(uparam->kup_tgt, data[7], sizeof(uparam->kup_tgt));
-
-        logmsg(LL_DEBUG, "parse call out info: secid %d, mech %s, ugid %u:%u "
-               "is_root %d, is_mdt %d, is_ost %d, svc %d, nid 0x%llx, tgt %s\n",
-               uparam->kup_secid, uparam->kup_mech,
-               uparam->kup_uid, uparam->kup_gid,
-               uparam->kup_is_root, uparam->kup_is_mdt, uparam->kup_is_ost,
-               uparam->kup_svc, uparam->kup_nid, uparam->kup_tgt);
-        return 0;
+       logmsg(LL_TRACE, "components: %s,%s,%s,%s,%s,%c,%s,%s,%s,%s,%s\n",
+              data[0], data[1], data[2], data[3], data[4], data[5][0],
+              data[6], data[7], data[8], data[9], data[10]);
+
+       uparam->kup_secid = strtol(data[0], NULL, 0);
+       snprintf(uparam->kup_mech, sizeof(uparam->kup_mech), "%s", data[1]);
+       uparam->kup_uid = strtol(data[2], NULL, 0);
+       uparam->kup_gid = strtol(data[3], NULL, 0);
+       if (strchr(data[4], 'r'))
+               uparam->kup_is_root = 1;
+       if (strchr(data[4], 'm'))
+               uparam->kup_is_mdt = 1;
+       if (strchr(data[4], 'o'))
+               uparam->kup_is_ost = 1;
+       uparam->kup_svc_type = data[5][0];
+       uparam->kup_svc = strtol(data[6], NULL, 0);
+       uparam->kup_nid = strtoll(data[7], NULL, 0);
+       snprintf(uparam->kup_tgt, sizeof(uparam->kup_tgt), "%s", data[8]);
+       uparam->kup_selfnid = strtoll(data[9], NULL, 0);
+       uparam->kup_pid = strtol(data[10], NULL, 0);
+
+       logmsg(LL_DEBUG, "parse call out info: secid %d, mech %s, ugid %u:%u, "
+              "is_root %d, is_mdt %d, is_ost %d, svc type %c, svc %d, "
+              "nid 0x%"PRIx64", tgt %s, self nid 0x%"PRIx64", pid %d\n",
+              uparam->kup_secid, uparam->kup_mech,
+              uparam->kup_uid, uparam->kup_gid,
+              uparam->kup_is_root, uparam->kup_is_mdt, uparam->kup_is_ost,
+              uparam->kup_svc_type, uparam->kup_svc, uparam->kup_nid,
+              uparam->kup_tgt, uparam->kup_selfnid, uparam->kup_pid);
+       return 0;
 }
 
-#define LOG_LEVEL_PATH  "/proc/fs/lustre/sptlrpc/gss/lgss_keyring/debug_level"
-
 static void set_log_level()
 {
-        FILE         *file;
-        unsigned int  level;
-
-        file = fopen(LOG_LEVEL_PATH, "r");
-        if (file == NULL)
-                return;
+       unsigned int level;
+       glob_t path;
+       FILE *file;
+
+       if (cfs_get_param_paths(&path,
+                               "sptlrpc/gss/lgss_keyring/debug_level") != 0)
+               return;
+       file = fopen(path.gl_pathv[0], "r");
+       if (file == NULL) {
+               cfs_free_param_data(&path);
+               return;
+       }
+
+       if (fscanf(file, "%u", &level) != 1)
+               goto out;
+
+       if (level >= LL_MAX)
+               goto out;
+
+       lgss_set_loglevel(level);
+out:
+       cfs_free_param_data(&path);
+       fclose(file);
+}
 
-        if (fscanf(file, "%u", &level) != 1)
-                goto out;
+static int associate_with_ns(char *path)
+{
+#ifdef HAVE_SETNS
+       int fd, rc = -1;
+
+       fd = open(path, O_RDONLY);
+       if (fd != -1) {
+               rc = setns(fd, 0);
+               close(fd);
+       }
+
+       return rc;
+#else
+       return -1;
+#endif /* HAVE_SETNS */
+}
 
-        if (level >= LL_MAX)
-                goto out;
+static int prepare_and_instantiate(struct lgss_cred *cred, key_serial_t keyid,
+                                  uint32_t uid)
+{
+       key_serial_t inst_keyring;
+
+       if (lgss_prepare_cred(cred)) {
+               logmsg(LL_ERR, "key %08x: failed to prepare credentials "
+                      "for user %d\n", keyid, uid);
+               return 1;
+       }
+
+       /* 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.
+        */
+       if (cred->lc_root_flags != 0)
+               inst_keyring = 0;
+       else
+               inst_keyring = KEY_SPEC_SESSION_KEYRING;
+
+       if (keyctl_instantiate(keyid, NULL, 0, inst_keyring)) {
+               logmsg(LL_ERR, "instantiate key %08x: %s\n",
+                      keyid, strerror(errno));
+               return 1;
+       }
+
+       logmsg(LL_TRACE, "instantiated kernel key %08x\n", keyid);
 
-        lgss_set_loglevel(level);
-out:
-        fclose(file);
+       return 0;
 }
 
 /****************************************
@@ -626,140 +956,298 @@ out:
 
 int main(int argc, char *argv[])
 {
-        struct keyring_upcall_param     uparam;
-        key_serial_t                    keyid;
-        key_serial_t                    sring;
-        key_serial_t                    inst_keyring;
-        pid_t                           child;
-        struct lgss_mech_type          *mech;
-        struct lgss_cred               *cred;
-
-        set_log_level();
-
-        logmsg(LL_TRACE, "start parsing parameters\n");
-        /*
-         * parse & sanity check upcall parameters
-         * expected to be called with:
-         * [1]:  operation
-         * [2]:  key ID
-         * [3]:  key type
-         * [4]:  key description
-         * [5]:  call out info
-         * [6]:  UID
-         * [7]:  GID
-         * [8]:  thread keyring
-         * [9]:  process keyring
-         * [10]: session keyring
-         */
-        if (argc != 10 + 1) {
-                logmsg(LL_ERR, "invalid parameter number %d\n", argc);
-                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]);
-                return 1;
-        }
-
-        if (sscanf(argv[2], "%d", &keyid) != 1) {
-                logmsg(LL_ERR, "can't extract KeyID: %s\n", argv[2]);
-                return 1;
-        }
-
-        if (sscanf(argv[6], "%d", &uparam.kup_fsuid) != 1) {
-                logmsg(LL_ERR, "can't extract UID: %s\n", argv[6]);
-                return 1;
-        }
-
-        if (sscanf(argv[7], "%d", &uparam.kup_fsgid) != 1) {
-                logmsg(LL_ERR, "can't extract GID: %s\n", argv[7]);
-                return 1;
-        }
-
-        if (sscanf(argv[10], "%d", &sring) != 1) {
-                logmsg(LL_ERR, "can't extract session keyring: %s\n", argv[10]);
-                return 1;
-        }
-
-        if (parse_callout_info(argv[5], &uparam)) {
-                logmsg(LL_ERR, "can't extract callout info: %s\n", argv[5]);
-                return 1;
-        }
-
-        logmsg(LL_TRACE, "parsing parameters OK\n");
-
-        /*
-         * prepare a cred
-         */
-        mech = lgss_name2mech(uparam.kup_mech);
-        if (mech == NULL) {
-                logmsg(LL_ERR, "key %08x: unsupported mech: %s\n",
-                       keyid, uparam.kup_mech);
-                return 1;
-        }
-
-        if (lgss_mech_initialize(mech)) {
-                logmsg(LL_ERR, "key %08x: can't initialize mech %s\n",
-                       keyid, mech->lmt_name);
-                return 1;
-        }
-
-        cred = lgss_create_cred(mech);
-        if (cred == NULL) {
-                logmsg(LL_ERR, "key %08x: can't create a new %s cred\n",
-                       keyid, mech->lmt_name);
-                return 1;
-        }
-
-        cred->lc_uid = uparam.kup_uid;
-        cred->lc_root_flags |= uparam.kup_is_root ? LGSS_ROOT_CRED_ROOT : 0;
-        cred->lc_root_flags |= uparam.kup_is_mdt ? LGSS_ROOT_CRED_MDT : 0;
-        cred->lc_root_flags |= uparam.kup_is_ost ? LGSS_ROOT_CRED_OST : 0;
-        cred->lc_tgt_nid = uparam.kup_nid;
-        cred->lc_tgt_svc = uparam.kup_svc;
-
-        if (lgss_prepare_cred(cred)) {
-                logmsg(LL_ERR, "key %08x: failed to prepare credentials "
-                       "for user %d\n", keyid, uparam.kup_uid);
-                return 1;
-        }
-
-        /* 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.
-         */
-        if (cred->lc_root_flags != 0)
-                inst_keyring = 0;
-        else
-                inst_keyring = KEY_SPEC_SESSION_KEYRING;
-
-        if (keyctl_instantiate(keyid, NULL, 0, inst_keyring)) {
-                logmsg(LL_ERR, "instantiate key %08x: %s\n",
-                       keyid, strerror(errno));
-                return 1;
-        }
-
-        logmsg(LL_TRACE, "instantiated kernel key %08x\n", keyid);
-
-        /*
-         * fork a child to do the real gss negotiation
-         */
-        child = fork();
-        if (child == -1) {
-                logmsg(LL_ERR, "key %08x: can't create child: %s\n",
-                       keyid, strerror(errno));
-                return 1;
-        } else if (child == 0) {
-                return lgssc_kr_negotiate(keyid, cred, &uparam);
-        }
-
-        logmsg(LL_TRACE, "forked child %d\n", child);
-        return 0;
+       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
+
+       set_log_level();
+
+       logmsg(LL_TRACE, "start parsing parameters\n");
+       /*
+        * parse & sanity check upcall parameters
+        * expected to be called with:
+        * [1]:  operation
+        * [2]:  key ID
+        * [3]:  key type
+        * [4]:  key description
+        * [5]:  call out info
+        * [6]:  UID
+        * [7]:  GID
+        * [8]:  thread keyring
+        * [9]:  process keyring
+        * [10]: session keyring
+        */
+       if (argc != 10 + 1) {
+               logmsg(LL_ERR, "invalid parameter number %d\n", argc);
+               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]);
+               return 1;
+       }
+
+       if (sscanf(argv[2], "%d", &keyid) != 1) {
+               logmsg(LL_ERR, "can't extract KeyID: %s\n", argv[2]);
+               return 1;
+       }
+
+       if (sscanf(argv[6], "%d", &uparam.kup_fsuid) != 1) {
+               logmsg(LL_ERR, "can't extract UID: %s\n", argv[6]);
+               return 1;
+       }
+
+       if (sscanf(argv[7], "%d", &uparam.kup_fsgid) != 1) {
+               logmsg(LL_ERR, "can't extract GID: %s\n", argv[7]);
+               return 1;
+       }
+
+       if (sscanf(argv[10], "%d", &sring) != 1) {
+               logmsg(LL_ERR, "can't extract session keyring: %s\n", argv[10]);
+               return 1;
+       }
+
+       if (parse_callout_info(argv[5], &uparam)) {
+               logmsg(LL_ERR, "can't extract callout info: %s\n", argv[5]);
+               return 1;
+       }
+
+       logmsg(LL_TRACE, "parsing parameters OK\n");
+
+       /*
+        * prepare a cred
+        */
+       mech = lgss_name2mech(uparam.kup_mech);
+       if (mech == NULL) {
+               logmsg(LL_ERR, "key %08x: unsupported mech: %s\n",
+                      keyid, uparam.kup_mech);
+               return 1;
+       }
+
+       if (lgss_mech_initialize(mech)) {
+               logmsg(LL_ERR, "key %08x: can't initialize mech %s\n",
+                      keyid, mech->lmt_name);
+               return 1;
+       }
+
+       cred = lgss_create_cred(mech);
+       if (cred == NULL) {
+               logmsg(LL_ERR, "key %08x: can't create a new %s cred\n",
+                      keyid, mech->lmt_name);
+               return 1;
+       }
+
+       cred->lc_uid = uparam.kup_uid;
+       cred->lc_root_flags |= uparam.kup_is_root ? LGSS_ROOT_CRED_ROOT : 0;
+       cred->lc_root_flags |= uparam.kup_is_mdt ? LGSS_ROOT_CRED_MDT : 0;
+       cred->lc_root_flags |= uparam.kup_is_ost ? LGSS_ROOT_CRED_OST : 0;
+       cred->lc_tgt_nid = uparam.kup_nid;
+       cred->lc_tgt_svc = uparam.kup_svc;
+       cred->lc_tgt_uuid = uparam.kup_tgt;
+       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;
+       }
+#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");
+
+               /* use pipes to pass info between child and parent processes */
+               if (pipe(req_fd) == -1) {
+                       logmsg(LL_ERR, "key %08x: pipe failed: %s\n",
+                              keyid, strerror(errno));
+                       return 1;
+               }
+               if (pipe(reply_fd) == -1) {
+                       logmsg(LL_ERR, "key %08x: pipe failed: %s\n",
+                              keyid, strerror(errno));
+                       return 1;
+               }
+
+               child = fork();
+               if (child == -1) {
+                       logmsg(LL_ERR, "key %08x: can't create child: %s\n",
+                              keyid, strerror(errno));
+                       rc = 1;
+                       goto out_pipe;
+               } else if (child == 0) {
+                       int rc2;
+                       /* child process: carry out credentials preparation
+                        * in caller's namespace */
+
+                       close(req_fd[0]); /* close unsed read end */
+                       req_fd[0] = -1;
+                       close(reply_fd[1]); /* close unsed write end */
+                       reply_fd[1] = -1;
+
+                       if (associate_with_ns(path) != 0) {
+                               logmsg(LL_ERR,
+                                      "failed to attach to pid %d namespace: "
+                                      "%s\n", uparam.kup_pid, strerror(errno));
+                               rc = 1;
+                               goto out_pipe;
+                       }
+                       logmsg(LL_TRACE, "working in namespace of pid %d\n",
+                              uparam.kup_pid);
+
+                       rc = prepare_and_instantiate(cred, keyid,
+                                                    uparam.kup_uid);
+
+                       /* send to parent the status of credentials preparation
+                        * and key instantiation */
+                       rc2 = send_to(req_fd[1], &rc, sizeof(rc));
+                       rc = (rc == 0 ? rc2 : rc);
+                       if (rc != 0)
+                               goto out_pipe;
+
+                       /* now do real gss negotiation
+                        * parent main process will not wait for us,
+                        * as it has to be done in the background */
+                       rc = lgssc_kr_negotiate(keyid, cred, &uparam,
+                                               req_fd, reply_fd);
+                       goto out_pipe;
+               } else {
+                       int rc2;
+                       /* parent process: exchange info with child carrying out
+                        * credentials preparation */
+
+                       close(req_fd[1]); /* close unsed write end */
+                       req_fd[1] = -1;
+                       close(reply_fd[0]); /* close unsed read end */
+                       reply_fd[0] = -1;
+
+                       /* get status of credentials preparation
+                        * and key instantiation */
+                       rc2 = receive_from(req_fd[0], &rc, sizeof(rc));
+                       if (rc2 != 0 || rc != 0) {
+                               logmsg(LL_ERR, "child failed preparing creds: "
+                                      "%s\n",
+                                      rc2 != 0 ? strerror(-rc2)
+                                               : strerror(rc));
+                               goto out_pipe;
+                       }
+
+                       /*
+                        * fork a child here to participate in gss negotiation,
+                        * as it has to be done in the background
+                        */
+                       child = fork();
+                       if (child == -1) {
+                               logmsg(LL_ERR,
+                                      "key %08x: can't create child: %s\n",
+                                      keyid, strerror(errno));
+                               rc = 1;
+                               goto out_pipe;
+                       } else if (child == 0) {
+                               struct lgssd_ioctl_param param;
+                               char outbuf[8192] = { 0 };
+                               void *gss_token = NULL;
+
+                               /* get ioctl buffer from child */
+                               rc = receive_from(req_fd[0], &param,
+                                                 sizeof(param));
+                               if (rc != 0)
+                                       goto out_pipe;
+
+                               gss_token = calloc(1, param.send_token_size);
+                               if (gss_token == NULL)
+                                       goto out_pipe;
+
+                               /* get gss token from child */
+                               rc = receive_from(req_fd[0], gss_token,
+                                                 param.send_token_size);
+                               if (rc != 0)
+                                       goto out_token;
+
+                               param.send_token = (char *)gss_token;
+                               param.reply_buf_size = sizeof(outbuf);
+                               param.reply_buf = outbuf;
+
+                               /* do ioctl in place of child process carrying
+                                * out credentials negotiation: as it runs in
+                                * a container, it might not be able to
+                                * perform ioctl */
+                               rc = gss_do_ioctl(&param);
+                               if (rc != 0)
+                                       goto out_token;
+
+                               /* send ioctl status to child */
+                               rc = send_to(reply_fd[1], &param.status,
+                                            sizeof(param.status));
+                               if (rc != 0)
+                                       goto out_token;
+                               /* send reply buffer to child */
+                               rc = send_to(reply_fd[1], outbuf,
+                                            sizeof(outbuf));
+                               if (rc != 0)
+                                       goto out_token;
+
+out_token:
+                               free(gss_token);
+                               goto out_pipe;
+                       }
+
+                       logmsg(LL_TRACE, "forked child %d\n", child);
+               }
+out_pipe:
+               close(req_fd[0]);
+               close(req_fd[1]);
+               close(reply_fd[0]);
+               close(reply_fd[1]);
+               return rc;
+       } else {
+               logmsg(LL_TRACE, "caller's namespace is the same\n");
+
+               rc = prepare_and_instantiate(cred, keyid, uparam.kup_uid);
+               if (rc != 0)
+                       return rc;
+
+               /*
+                * fork a child to do the real gss negotiation
+                */
+               child = fork();
+               if (child == -1) {
+                       logmsg(LL_ERR, "key %08x: can't create child: %s\n",
+                              keyid, strerror(errno));
+                       return 1;
+               } else if (child == 0) {
+                       return lgssc_kr_negotiate(keyid, cred, &uparam,
+                                                 req_fd, reply_fd);
+               }
+
+               logmsg(LL_TRACE, "forked child %d\n", child);
+               return 0;
+       }
 }