]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - support/rpc/svc_auth_gss.c
Add gss support from citi @ umich
[nfs-utils.git] / support / rpc / svc_auth_gss.c
diff --git a/support/rpc/svc_auth_gss.c b/support/rpc/svc_auth_gss.c
new file mode 100644 (file)
index 0000000..02153f9
--- /dev/null
@@ -0,0 +1,582 @@
+/*
+  svc_auth_gss.c
+
+  Copyright (c) 2000 The Regents of the University of Michigan.
+  All rights reserved.
+
+  Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
+  All rights reserved, all wrongs reversed.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions
+  are met:
+
+  1. Redistributions of source code must retain the above copyright
+     notice, this list of conditions and the following disclaimer.
+  2. Redistributions in binary form must reproduce the above copyright
+     notice, this list of conditions and the following disclaimer in the
+     documentation and/or other materials provided with the distribution.
+  3. Neither the name of the University nor the names of its
+     contributors may be used to endorse or promote products derived
+     from this software without specific prior written permission.
+
+  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <rpc/rpc.h>
+#include <gssapi/gssapi.h>
+
+extern SVCAUTH svc_auth_none;
+
+/*
+ * from mit-krb5-1.2.1 mechglue/mglueP.h:
+ * Array of context IDs typed by mechanism OID
+ */
+typedef struct gss_union_ctx_id_t {
+  gss_OID     mech_type;
+  gss_ctx_id_t    internal_ctx_id;
+} gss_union_ctx_id_desc, *gss_union_ctx_id_t;
+
+
+
+static bool_t  svcauth_gss_destroy();
+static bool_t   svcauth_gss_wrap();
+static bool_t   svcauth_gss_unwrap();
+
+struct svc_auth_ops svc_auth_gss_ops = {
+       svcauth_gss_wrap,
+       svcauth_gss_unwrap,
+       svcauth_gss_destroy
+};
+
+struct svc_rpc_gss_data {
+       bool_t                  established;    /* context established */
+       gss_ctx_id_t            ctx;            /* context id */
+       struct rpc_gss_sec      sec;            /* security triple */
+       gss_buffer_desc         cname;          /* GSS client name */
+       u_int                   seq;            /* sequence number */
+       u_int                   win;            /* sequence window */
+       u_int                   seqlast;        /* last sequence number */
+       u_int32_t               seqmask;        /* bitmask of seqnums */
+       gss_name_t              client_name;    /* unparsed name string */
+};
+
+#define SVCAUTH_PRIVATE(auth) \
+       ((struct svc_rpc_gss_data *)(auth)->svc_ah_private)
+
+/* Global server credentials. */
+gss_cred_id_t          _svcauth_gss_creds;
+static gss_name_t      _svcauth_gss_name = NULL;
+
+bool_t
+svcauth_gss_set_svc_name(gss_name_t name)
+{
+       OM_uint32       maj_stat, min_stat;
+
+       log_debug("in svcauth_gss_set_svc_name()");
+
+       if (_svcauth_gss_name != NULL) {
+               maj_stat = gss_release_name(&min_stat, &_svcauth_gss_name);
+
+               if (maj_stat != GSS_S_COMPLETE) {
+                       log_status("gss_release_name", maj_stat, min_stat);
+                       return (FALSE);
+               }
+               _svcauth_gss_name = NULL;
+       }
+       maj_stat = gss_duplicate_name(&min_stat, name, &_svcauth_gss_name);
+
+       if (maj_stat != GSS_S_COMPLETE) {
+               log_status("gss_duplicate_name", maj_stat, min_stat);
+               return (FALSE);
+       }
+
+       return (TRUE);
+}
+
+static bool_t
+svcauth_gss_import_name(char *service)
+{
+       gss_name_t      name;
+       gss_buffer_desc namebuf;
+       OM_uint32       maj_stat, min_stat;
+
+       log_debug("in svcauth_gss_import_name()");
+
+       namebuf.value = service;
+       namebuf.length = strlen(service);
+
+       maj_stat = gss_import_name(&min_stat, &namebuf,
+                                  GSS_C_NT_HOSTBASED_SERVICE, &name);
+
+       if (maj_stat != GSS_S_COMPLETE) {
+               log_status("gss_import_name", maj_stat, min_stat);
+               return (FALSE);
+       }
+       if (svcauth_gss_set_svc_name(name) != TRUE) {
+               gss_release_name(&min_stat, &name);
+               return (FALSE);
+       }
+       return (TRUE);
+}
+
+static bool_t
+svcauth_gss_acquire_cred(void)
+{
+       OM_uint32       maj_stat, min_stat;
+
+       log_debug("in svcauth_gss_acquire_cred()");
+
+       maj_stat = gss_acquire_cred(&min_stat, _svcauth_gss_name, 0,
+                                   GSS_C_NULL_OID_SET, GSS_C_ACCEPT,
+                                   &_svcauth_gss_creds, NULL, NULL);
+
+       if (maj_stat != GSS_S_COMPLETE) {
+               log_status("gss_acquire_cred", maj_stat, min_stat);
+               return (FALSE);
+       }
+       return (TRUE);
+}
+
+static bool_t
+svcauth_gss_release_cred(void)
+{
+       OM_uint32       maj_stat, min_stat;
+
+       log_debug("in svcauth_gss_release_cred()");
+
+       maj_stat = gss_release_cred(&min_stat, &_svcauth_gss_creds);
+
+       if (maj_stat != GSS_S_COMPLETE) {
+               log_status("gss_release_cred", maj_stat, min_stat);
+               return (FALSE);
+       }
+
+       _svcauth_gss_creds = NULL;
+
+       return (TRUE);
+}
+
+static bool_t
+svcauth_gss_accept_sec_context(struct svc_req *rqst,
+                              struct rpc_gss_init_res *gr)
+{
+       struct svc_rpc_gss_data *gd;
+       struct rpc_gss_cred     *gc;
+       gss_buffer_desc          recv_tok, seqbuf, checksum;
+       gss_OID                  mech;
+       OM_uint32                maj_stat = 0, min_stat = 0, ret_flags, seq;
+
+       log_debug("in svcauth_gss_accept_context()");
+
+       gd = SVCAUTH_PRIVATE(rqst->rq_xprt->xp_auth);
+       gc = (struct rpc_gss_cred *)rqst->rq_clntcred;
+       memset(gr, 0, sizeof(*gr));
+
+       /* Deserialize arguments. */
+       memset(&recv_tok, 0, sizeof(recv_tok));
+
+       if (!svc_getargs(rqst->rq_xprt, xdr_rpc_gss_init_args,
+                        (caddr_t)&recv_tok))
+               return (FALSE);
+
+       gr->gr_major = gss_accept_sec_context(&gr->gr_minor,
+                                             &gd->ctx,
+                                             _svcauth_gss_creds,
+                                             &recv_tok,
+                                             GSS_C_NO_CHANNEL_BINDINGS,
+                                             &gd->client_name,
+                                             &mech,
+                                             &gr->gr_token,
+                                             &ret_flags,
+                                             NULL,
+                                             NULL);
+
+       if (gr->gr_major != GSS_S_COMPLETE &&
+           gr->gr_major != GSS_S_CONTINUE_NEEDED) {
+               log_status("accept_sec_context", gr->gr_major, gr->gr_minor);
+               gd->ctx = GSS_C_NO_CONTEXT;
+               gss_release_buffer(&min_stat, &gr->gr_token);
+               return (FALSE);
+       }
+       /* ANDROS: krb5 mechglue returns ctx of size 8 - two pointers,
+        * one to the mechanism oid, one to the internal_ctx_id */
+       if ((gr->gr_ctx.value = mem_alloc(sizeof(gss_union_ctx_id_desc))) == NULL) {
+               fprintf(stderr, "svcauth_gss_accept_context: out of memory\n");
+               return (FALSE);
+       }
+       memcpy(gr->gr_ctx.value, gd->ctx, sizeof(gss_union_ctx_id_desc));
+       gr->gr_ctx.length = sizeof(gss_union_ctx_id_desc);
+
+       /* ANDROS: change for debugging linux kernel version...
+       gr->gr_win = sizeof(gd->seqmask) * 8;
+       */
+       gr->gr_win = 0x00000005;
+
+       /* Save client info. */
+       gd->sec.mech = mech;
+       gd->sec.qop = GSS_C_QOP_DEFAULT;
+       gd->sec.svc = gc->gc_svc;
+       gd->seq = gc->gc_seq;
+       gd->win = gr->gr_win;
+
+       if (gr->gr_major == GSS_S_COMPLETE) {
+               maj_stat = gss_display_name(&min_stat, gd->client_name,
+                                           &gd->cname, &gd->sec.mech);
+               if (maj_stat != GSS_S_COMPLETE) {
+                       log_status("display_name", maj_stat, min_stat);
+                       return (FALSE);
+               }
+#ifdef DEBUG
+#ifdef HAVE_KRB5
+               {
+                       gss_buffer_desc mechname;
+
+                       gss_oid_to_str(&min_stat, mech, &mechname);
+
+                       log_debug("accepted context for %.*s with "
+                                 "<mech %.*s, qop %d, svc %d>",
+                                 gd->cname.length, (char *)gd->cname.value,
+                                 mechname.length, (char *)mechname.value,
+                                 gd->sec.qop, gd->sec.svc);
+
+                       gss_release_buffer(&min_stat, &mechname);
+               }
+#elif HAVE_HEIMDAL
+               log_debug("accepted context for %.*s with "
+                         "<mech {}, qop %d, svc %d>",
+                         gd->cname.length, (char *)gd->cname.value,
+                         gd->sec.qop, gd->sec.svc);
+#endif
+#endif /* DEBUG */
+               seq = htonl(gr->gr_win);
+               seqbuf.value = &seq;
+               seqbuf.length = sizeof(seq);
+
+               maj_stat = gss_sign(&min_stat, gd->ctx, GSS_C_QOP_DEFAULT,
+                                   &seqbuf, &checksum);
+
+               if (maj_stat != GSS_S_COMPLETE)
+                       return (FALSE);
+
+               rqst->rq_xprt->xp_verf.oa_flavor = RPCSEC_GSS;
+               rqst->rq_xprt->xp_verf.oa_base = checksum.value;
+               rqst->rq_xprt->xp_verf.oa_length = checksum.length;
+       }
+       return (TRUE);
+}
+
+static bool_t
+svcauth_gss_validate(struct svc_rpc_gss_data *gd, struct rpc_msg *msg)
+{
+       struct opaque_auth      *oa;
+       gss_buffer_desc          rpcbuf, checksum;
+       OM_uint32                maj_stat, min_stat, qop_state;
+       u_char                   rpchdr[128];
+       int32_t                 *buf;
+
+       log_debug("in svcauth_gss_validate()");
+
+       memset(rpchdr, 0, sizeof(rpchdr));
+
+       /* XXX - Reconstruct RPC header for signing (from xdr_callmsg). */
+       buf = (int32_t *)rpchdr;
+       IXDR_PUT_LONG(buf, msg->rm_xid);
+       IXDR_PUT_ENUM(buf, msg->rm_direction);
+       IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
+       IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
+       IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
+       IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
+       oa = &msg->rm_call.cb_cred;
+       IXDR_PUT_ENUM(buf, oa->oa_flavor);
+       IXDR_PUT_LONG(buf, oa->oa_length);
+       if (oa->oa_length) {
+               memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
+               buf += RNDUP(oa->oa_length) / sizeof(int32_t);
+       }
+       rpcbuf.value = rpchdr;
+       rpcbuf.length = (u_char *)buf - rpchdr;
+
+       checksum.value = msg->rm_call.cb_verf.oa_base;
+       checksum.length = msg->rm_call.cb_verf.oa_length;
+
+       maj_stat = gss_verify_mic(&min_stat, gd->ctx, &rpcbuf, &checksum,
+                                 &qop_state);
+
+       if (maj_stat != GSS_S_COMPLETE) {
+               log_status("gss_verify_mic", maj_stat, min_stat);
+               return (FALSE);
+       }
+       return (TRUE);
+}
+
+bool_t
+svcauth_gss_nextverf(struct svc_req *rqst, u_int num)
+{
+       struct svc_rpc_gss_data *gd;
+       gss_buffer_desc          signbuf, checksum;
+       OM_uint32                maj_stat, min_stat;
+
+       log_debug("in svcauth_gss_nextverf()");
+
+       if (rqst->rq_xprt->xp_auth == NULL)
+               return (FALSE);
+
+       gd = SVCAUTH_PRIVATE(rqst->rq_xprt->xp_auth);
+
+       signbuf.value = &num;
+       signbuf.length = sizeof(num);
+
+       maj_stat = gss_get_mic(&min_stat, gd->ctx, gd->sec.qop,
+                              &signbuf, &checksum);
+
+       if (maj_stat != GSS_S_COMPLETE) {
+               log_status("gss_get_mic", maj_stat, min_stat);
+               return (FALSE);
+       }
+       rqst->rq_xprt->xp_verf.oa_flavor = RPCSEC_GSS;
+       rqst->rq_xprt->xp_verf.oa_base = (caddr_t)checksum.value;
+       rqst->rq_xprt->xp_verf.oa_length = (u_int)checksum.length;
+
+       return (TRUE);
+}
+
+enum auth_stat
+_svcauth_gss(struct svc_req *rqst, struct rpc_msg *msg, bool_t *no_dispatch)
+{
+       XDR                      xdrs;
+       SVCAUTH                 *auth;
+       struct svc_rpc_gss_data *gd;
+       struct rpc_gss_cred     *gc;
+       struct rpc_gss_init_res  gr;
+       int                      call_stat, offset;
+
+       log_debug("in svcauth_gss()");
+
+       /* Initialize reply. */
+       rqst->rq_xprt->xp_verf = _null_auth;
+
+       /* Allocate and set up server auth handle. */
+       if (rqst->rq_xprt->xp_auth == NULL ||
+           rqst->rq_xprt->xp_auth == &svc_auth_none) {
+               if ((auth = calloc(sizeof(*auth), 1)) == NULL) {
+                       fprintf(stderr, "svcauth_gss: out_of_memory\n");
+                       return (AUTH_FAILED);
+               }
+               if ((gd = calloc(sizeof(*gd), 1)) == NULL) {
+                       fprintf(stderr, "svcauth_gss: out_of_memory\n");
+                       return (AUTH_FAILED);
+               }
+               auth->svc_ah_ops = &svc_auth_gss_ops;
+               SVCAUTH_PRIVATE(auth) = gd;
+               rqst->rq_xprt->xp_auth = auth;
+       }
+       else gd = SVCAUTH_PRIVATE(rqst->rq_xprt->xp_auth);
+
+       /* Deserialize client credentials. */
+       if (rqst->rq_cred.oa_length <= 0)
+               return (AUTH_BADCRED);
+
+       gc = (struct rpc_gss_cred *)rqst->rq_clntcred;
+       memset(gc, 0, sizeof(*gc));
+
+       xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
+                     rqst->rq_cred.oa_length, XDR_DECODE);
+
+       if (!xdr_rpc_gss_cred(&xdrs, gc)) {
+               XDR_DESTROY(&xdrs);
+               return (AUTH_BADCRED);
+       }
+       XDR_DESTROY(&xdrs);
+
+       /* Check version. */
+       if (gc->gc_v != RPCSEC_GSS_VERSION)
+               return (AUTH_BADCRED);
+
+       /* Check RPCSEC_GSS service. */
+       if (gc->gc_svc != RPCSEC_GSS_SVC_NONE &&
+           gc->gc_svc != RPCSEC_GSS_SVC_INTEGRITY &&
+           gc->gc_svc != RPCSEC_GSS_SVC_PRIVACY)
+               return (AUTH_BADCRED);
+
+       /* Check sequence number. */
+       if (gd->established) {
+               if (gc->gc_seq > MAXSEQ)
+                       return (RPCSEC_GSS_CTXPROBLEM);
+
+               if ((offset = gd->seqlast - gc->gc_seq) < 0) {
+                       gd->seqlast = gc->gc_seq;
+                       offset = 0 - offset;
+                       gd->seqmask <<= offset;
+                       offset = 0;
+               }
+               else if (offset >= gd->win || (gd->seqmask & (1 << offset))) {
+                       *no_dispatch = 1;
+                       return (RPCSEC_GSS_CTXPROBLEM);
+               }
+               gd->seq = gc->gc_seq;
+               gd->seqmask |= (1 << offset);
+       }
+
+       if (gd->established) {
+               rqst->rq_clntname = (char *)gd->client_name;
+               rqst->rq_svcname = (char *)gd->ctx;
+       }
+
+       /* Handle RPCSEC_GSS control procedure. */
+       switch (gc->gc_proc) {
+
+       case RPCSEC_GSS_INIT:
+       case RPCSEC_GSS_CONTINUE_INIT:
+               if (rqst->rq_proc != NULLPROC)
+                       return (AUTH_FAILED);           /* XXX ? */
+
+               if (_svcauth_gss_name == NULL) {
+                       if (!svcauth_gss_import_name("nfs"))
+                               return (AUTH_FAILED);
+               }
+
+               if (!svcauth_gss_acquire_cred())
+                       return (AUTH_FAILED);
+
+               if (!svcauth_gss_accept_sec_context(rqst, &gr))
+                       return (AUTH_REJECTEDCRED);
+
+               if (!svcauth_gss_nextverf(rqst, htonl(gr.gr_win)))
+                       return (AUTH_FAILED);
+
+               *no_dispatch = TRUE;
+
+               call_stat = svc_sendreply(rqst->rq_xprt, xdr_rpc_gss_init_res,
+                                         (caddr_t)&gr);
+
+               if (!call_stat)
+                       return (AUTH_FAILED);
+
+               if (gr.gr_major == GSS_S_COMPLETE)
+                       gd->established = TRUE;
+
+               break;
+
+       case RPCSEC_GSS_DATA:
+               if (!svcauth_gss_validate(gd, msg))
+                       return (RPCSEC_GSS_CREDPROBLEM);
+
+               if (!svcauth_gss_nextverf(rqst, htonl(gc->gc_seq)))
+                       return (AUTH_FAILED);
+               break;
+
+       case RPCSEC_GSS_DESTROY:
+               if (rqst->rq_proc != NULLPROC)
+                       return (AUTH_FAILED);           /* XXX ? */
+
+               if (!svcauth_gss_validate(gd, msg))
+                       return (RPCSEC_GSS_CREDPROBLEM);
+
+               if (!svcauth_gss_nextverf(rqst, htonl(gc->gc_seq)))
+                       return (AUTH_FAILED);
+
+               if (!svcauth_gss_release_cred())
+                       return (AUTH_FAILED);
+
+               SVCAUTH_DESTROY(rqst->rq_xprt->xp_auth);
+               rqst->rq_xprt->xp_auth = &svc_auth_none;
+
+               break;
+
+       default:
+               return (AUTH_REJECTEDCRED);
+               break;
+       }
+       return (AUTH_OK);
+}
+
+bool_t
+svcauth_gss_destroy(SVCAUTH *auth)
+{
+       struct svc_rpc_gss_data *gd;
+       OM_uint32                min_stat;
+
+       log_debug("in svcauth_gss_destroy()");
+
+       gd = SVCAUTH_PRIVATE(auth);
+
+       gss_delete_sec_context(&min_stat, &gd->ctx, GSS_C_NO_BUFFER);
+       gss_release_buffer(&min_stat, &gd->cname);
+
+       if (gd->client_name)
+               gss_release_name(&min_stat, &gd->client_name);
+
+       mem_free(gd, sizeof(*gd));
+       mem_free(auth, sizeof(*auth));
+
+       return (TRUE);
+}
+
+bool_t
+svcauth_gss_wrap(SVCAUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
+{
+       struct svc_rpc_gss_data *gd;
+
+       log_debug("in svcauth_gss_wrap()");
+
+       gd = SVCAUTH_PRIVATE(auth);
+
+       if (!gd->established || gd->sec.svc == RPCSEC_GSS_SVC_NONE) {
+               return ((*xdr_func)(xdrs, xdr_ptr));
+       }
+       return (xdr_rpc_gss_data(xdrs, xdr_func, xdr_ptr,
+                                gd->ctx, gd->sec.qop,
+                                gd->sec.svc, gd->seq));
+}
+
+bool_t
+svcauth_gss_unwrap(SVCAUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
+{
+       struct svc_rpc_gss_data *gd;
+
+       log_debug("in svcauth_gss_unwrap()");
+
+       gd = SVCAUTH_PRIVATE(auth);
+
+       if (!gd->established || gd->sec.svc == RPCSEC_GSS_SVC_NONE) {
+               return ((*xdr_func)(xdrs, xdr_ptr));
+       }
+       return (xdr_rpc_gss_data(xdrs, xdr_func, xdr_ptr,
+                                gd->ctx, gd->sec.qop,
+                                gd->sec.svc, gd->seq));
+}
+
+char *
+svcauth_gss_get_principal(SVCAUTH *auth)
+{
+       struct svc_rpc_gss_data *gd;
+       char *pname;
+
+       gd = SVCAUTH_PRIVATE(auth);
+
+       if (gd->cname.length == 0)
+               return (NULL);
+
+       if ((pname = malloc(gd->cname.length + 1)) == NULL)
+               return (NULL);
+
+       memcpy(pname, gd->cname.value, gd->cname.length);
+       pname[gd->cname.length] = '\0';
+
+       return (pname);
+}