]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - support/rpc/auth_gss.c
Add gss support from citi @ umich
[nfs-utils.git] / support / rpc / auth_gss.c
diff --git a/support/rpc/auth_gss.c b/support/rpc/auth_gss.c
new file mode 100644 (file)
index 0000000..f41d678
--- /dev/null
@@ -0,0 +1,628 @@
+/*
+  auth_gss.c
+
+  RPCSEC_GSS client routines.
+
+  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 <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <rpc/types.h>
+#include <rpc/xdr.h>
+#include <rpc/auth.h>
+#include <rpc/auth_gss.h>
+#include <rpc/clnt.h>
+#include <netinet/in.h>
+#include <gssapi/gssapi.h>
+
+static void    authgss_nextverf();
+static bool_t  authgss_marshal();
+static bool_t  authgss_refresh();
+static bool_t  authgss_validate();
+static void    authgss_destroy();
+static void    authgss_destroy_context();
+static bool_t  authgss_wrap();
+static bool_t  authgss_unwrap();
+
+
+/*
+ * 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 struct auth_ops authgss_ops = {
+       authgss_nextverf,
+       authgss_marshal,
+       authgss_validate,
+       authgss_refresh,
+       authgss_destroy,
+       authgss_wrap,
+       authgss_unwrap
+};
+
+#ifdef DEBUG
+
+/* useful as i add more mechanisms */
+void
+print_rpc_gss_sec(struct rpc_gss_sec *ptr)
+{
+int i;
+char *p;
+
+       log_debug("rpc_gss_sec:");
+       if(ptr->mech == NULL)
+               log_debug("NULL gss_OID mech");
+       else {
+               fprintf(stderr, "     mechanism_OID: {");
+               p = (char *)ptr->mech->elements;
+               for (i=0; i < ptr->mech->length; i++)
+                       /* First byte of OIDs encoded to save a byte */
+                       if (i == 0) {
+                               int first, second;
+                               if (*p < 40) {
+                                       first = 0;
+                                       second = *p;
+                               }
+                               else if (40 <= *p && *p < 80) {
+                                       first = 1;
+                                       second = *p - 40;
+                               }
+                               else if (80 <= *p && *p < 127) {
+                                       first = 2;
+                                       second = *p - 80;
+                               }
+                               else {
+                                       /* Invalid value! */
+                                       first = -1;
+                                       second = -1;
+                               }
+                               fprintf(stderr, " %u %u", first, second);
+                               p++;
+                       }
+                       else {
+                               fprintf(stderr, " %u", (unsigned char)*p++);
+                       }
+               fprintf(stderr, " }\n");
+       }
+       fprintf(stderr, "     qop: %d\n", ptr->qop);
+       fprintf(stderr, "     service: %d\n", ptr->svc);
+       fprintf(stderr, "     cred: %p\n", ptr->cred);
+}
+#endif /*DEBUG*/
+
+struct rpc_gss_data {
+       bool_t                   established;   /* context established */
+       gss_buffer_desc          gc_wire_verf;  /* save GSS_S_COMPLETE NULL RPC verfier
+                                                * to process at end of context negotiation*/
+       CLIENT                  *clnt;          /* client handle */
+       gss_name_t               name;          /* service name */
+       struct rpc_gss_sec       sec;           /* security tuple */
+       gss_ctx_id_t             ctx;           /* context id */
+       struct rpc_gss_cred      gc;            /* client credentials */
+       u_int                    win;           /* sequence window */
+};
+
+#define        AUTH_PRIVATE(auth)      ((struct rpc_gss_data *)auth->ah_private)
+
+static struct timeval AUTH_TIMEOUT = { 25, 0 };
+
+AUTH *
+authgss_create(CLIENT *clnt, gss_name_t name, struct rpc_gss_sec *sec)
+{
+       AUTH                    *auth, *save_auth;
+       struct rpc_gss_data     *gd;
+       OM_uint32               min_stat = 0;
+
+       log_debug("in authgss_create()");
+
+       memset(&rpc_createerr, 0, sizeof(rpc_createerr));
+
+       if ((auth = calloc(sizeof(*auth), 1)) == NULL) {
+               rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+               rpc_createerr.cf_error.re_errno = ENOMEM;
+               return (NULL);
+       }
+       if ((gd = calloc(sizeof(*gd), 1)) == NULL) {
+               rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+               rpc_createerr.cf_error.re_errno = ENOMEM;
+               free(auth);
+               return (NULL);
+       }
+#ifdef DEBUG
+       fprintf(stderr, "authgss_create: name is %p\n", name);
+#endif
+       if (name != GSS_C_NO_NAME) {
+               if (gss_duplicate_name(&min_stat, name, &gd->name)
+                                               != GSS_S_COMPLETE) {
+                       rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+                       rpc_createerr.cf_error.re_errno = ENOMEM;
+                       free(auth);
+                       return (NULL);
+               }
+       }
+       else
+               gd->name = name;
+
+#ifdef DEBUG
+       fprintf(stderr, "authgss_create: gd->name is %p\n", gd->name);
+#endif
+       gd->clnt = clnt;
+       gd->ctx = GSS_C_NO_CONTEXT;
+       gd->sec = *sec;
+
+       gd->gc.gc_v = RPCSEC_GSS_VERSION;
+       gd->gc.gc_proc = RPCSEC_GSS_INIT;
+       gd->gc.gc_svc = gd->sec.svc;
+
+       auth->ah_ops = &authgss_ops;
+       auth->ah_private = (caddr_t)gd;
+
+       save_auth = clnt->cl_auth;
+       clnt->cl_auth = auth;
+
+       if (!authgss_refresh(auth))
+               auth = NULL;
+
+       clnt->cl_auth = save_auth;
+
+       return (auth);
+}
+
+AUTH *
+authgss_create_default(CLIENT *clnt, char *service, struct rpc_gss_sec *sec)
+{
+       AUTH                    *auth;
+       OM_uint32                maj_stat = 0, min_stat = 0;
+       gss_buffer_desc          sname;
+       gss_name_t               name = GSS_C_NO_NAME;
+
+       log_debug("in authgss_create_default()");
+
+
+       sname.value = service;
+       sname.length = strlen(service);
+
+       maj_stat = gss_import_name(&min_stat, &sname,
+               GSS_C_NT_HOSTBASED_SERVICE,
+               &name);
+
+       if (maj_stat != GSS_S_COMPLETE) {
+               log_status("gss_import_name", maj_stat, min_stat);
+               rpc_createerr.cf_stat = RPC_AUTHERROR;
+               return (NULL);
+       }
+
+       auth = authgss_create(clnt, name, sec);
+
+       if (name != GSS_C_NO_NAME) {
+#ifdef DEBUG
+       fprintf(stderr, "authgss_create_default: freeing name %p\n", name);
+#endif
+               gss_release_name(&min_stat, &name);
+       }
+
+       return (auth);
+}
+
+bool_t
+authgss_get_private_data(AUTH *auth, struct authgss_private_data *pd)
+{
+       struct rpc_gss_data     *gd;
+
+       log_debug("in authgss_get_private_data()");
+
+       if (!auth || !pd)
+               return (FALSE);
+
+       gd = AUTH_PRIVATE(auth);
+
+       if (!gd || !gd->established)
+               return (FALSE);
+
+       pd->pd_ctx = gd->ctx;
+       pd->pd_ctx_hndl = gd->gc.gc_ctx;
+       pd->pd_seq_win = gd->win;
+
+       return (TRUE);
+}
+
+static void
+authgss_nextverf(AUTH *auth)
+{
+       log_debug("in authgss_nextverf()");
+       /* no action necessary */
+}
+
+static bool_t
+authgss_marshal(AUTH *auth, XDR *xdrs)
+{
+       XDR                      tmpxdrs;
+       char                     tmp[MAX_AUTH_BYTES];
+       struct rpc_gss_data     *gd;
+       gss_buffer_desc          rpcbuf, checksum;
+       OM_uint32                maj_stat, min_stat;
+       bool_t                   xdr_stat;
+
+       log_debug("in authgss_marshal()");
+
+       gd = AUTH_PRIVATE(auth);
+
+       if (gd->established)
+               gd->gc.gc_seq++;
+
+       xdrmem_create(&tmpxdrs, tmp, sizeof(tmp), XDR_ENCODE);
+
+       if (!xdr_rpc_gss_cred(&tmpxdrs, &gd->gc)) {
+               XDR_DESTROY(&tmpxdrs);
+               return (FALSE);
+       }
+       auth->ah_cred.oa_flavor = RPCSEC_GSS;
+       auth->ah_cred.oa_base = tmp;
+       auth->ah_cred.oa_length = XDR_GETPOS(&tmpxdrs);
+
+       XDR_DESTROY(&tmpxdrs);
+
+       if (!xdr_opaque_auth(xdrs, &auth->ah_cred))
+               return (FALSE);
+
+       if (gd->gc.gc_proc == RPCSEC_GSS_INIT ||
+           gd->gc.gc_proc == RPCSEC_GSS_CONTINUE_INIT) {
+               return (xdr_opaque_auth(xdrs, &_null_auth));
+       }
+       /* Checksum serialized RPC header, up to and including credential. */
+       rpcbuf.length = XDR_GETPOS(xdrs);
+       XDR_SETPOS(xdrs, 0);
+       rpcbuf.value = XDR_INLINE(xdrs, rpcbuf.length);
+
+       maj_stat = gss_get_mic(&min_stat, gd->ctx, gd->sec.qop,
+                           &rpcbuf, &checksum);
+
+       if (maj_stat != GSS_S_COMPLETE) {
+               log_status("gss_get_mic", maj_stat, min_stat);
+               if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
+                       gd->established = FALSE;
+                       authgss_destroy_context(auth);
+               }
+               return (FALSE);
+       }
+       auth->ah_verf.oa_flavor = RPCSEC_GSS;
+       auth->ah_verf.oa_base = checksum.value;
+       auth->ah_verf.oa_length = checksum.length;
+
+       xdr_stat = xdr_opaque_auth(xdrs, &auth->ah_verf);
+       gss_release_buffer(&min_stat, &checksum);
+
+       return (xdr_stat);
+}
+
+static bool_t
+authgss_validate(AUTH *auth, struct opaque_auth *verf)
+{
+       struct rpc_gss_data     *gd;
+       u_int                    num, qop_state;
+       gss_buffer_desc          signbuf, checksum;
+       OM_uint32                maj_stat, min_stat;
+
+       log_debug("in authgss_validate()");
+
+       gd = AUTH_PRIVATE(auth);
+
+       if (gd->established == FALSE) {
+               /* would like to do this only on NULL rpc --
+                * gc->established is good enough.
+                * save the on the wire verifier to validate last
+                * INIT phase packet after decode if the major
+                * status is GSS_S_COMPLETE
+                */
+               if ((gd->gc_wire_verf.value =
+                               mem_alloc(verf->oa_length)) == NULL) {
+                       fprintf(stderr, "gss_validate: out of memory\n");
+                       return (FALSE);
+               }
+               memcpy(gd->gc_wire_verf.value, verf->oa_base, verf->oa_length);
+               gd->gc_wire_verf.length = verf->oa_length;
+               return (TRUE);
+       }
+
+       if (gd->gc.gc_proc == RPCSEC_GSS_INIT ||
+           gd->gc.gc_proc == RPCSEC_GSS_CONTINUE_INIT) {
+               num = htonl(gd->win);
+       }
+       else num = htonl(gd->gc.gc_seq);
+
+       signbuf.value = &num;
+       signbuf.length = sizeof(num);
+
+       checksum.value = verf->oa_base;
+       checksum.length = verf->oa_length;
+
+       maj_stat = gss_verify_mic(&min_stat, gd->ctx, &signbuf,
+                                 &checksum, &qop_state);
+       if (maj_stat != GSS_S_COMPLETE || qop_state != gd->sec.qop) {
+               log_status("gss_verify_mic", maj_stat, min_stat);
+               if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
+                       gd->established = FALSE;
+                       authgss_destroy_context(auth);
+               }
+               return (FALSE);
+       }
+       return (TRUE);
+}
+
+static bool_t
+authgss_refresh(AUTH *auth)
+{
+       struct rpc_gss_data     *gd;
+       struct rpc_gss_init_res  gr;
+       gss_buffer_desc         *recv_tokenp, send_token;
+       OM_uint32                maj_stat, min_stat, call_stat, ret_flags;
+       OM_uint32                req_flags=0;
+
+       log_debug("in authgss_refresh()");
+
+       gd = AUTH_PRIVATE(auth);
+
+       if (gd->established)
+               return (TRUE);
+
+       /* GSS context establishment loop. */
+       memset(&gr, 0, sizeof(gr));
+       recv_tokenp = GSS_C_NO_BUFFER;
+
+#ifdef DEBUG
+       print_rpc_gss_sec(&gd->sec);
+#endif /*DEBUG*/
+
+       for (;;) {
+#ifdef DEBUG
+               /* print the token we just received */
+               if (recv_tokenp != GSS_C_NO_BUFFER) {
+                       log_debug("The token we just received (length %d):",
+                                 recv_tokenp->length);
+                       log_hexdump(recv_tokenp->value, recv_tokenp->length, 0);
+               }
+#endif
+               maj_stat = gss_init_sec_context(&min_stat,
+                                               gd->sec.cred,
+                                               &gd->ctx,
+                                               gd->name,
+                                               gd->sec.mech,
+                                               gd->sec.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) {
+                       log_status("gss_init_sec_context", maj_stat, min_stat);
+                       break;
+               }
+               if (send_token.length != 0) {
+                       memset(&gr, 0, sizeof(gr));
+
+#ifdef DEBUG
+                       /* print the token we are about to send */
+                       log_debug("The token being sent (length %d):",
+                                 send_token.length);
+                       log_hexdump(send_token.value, send_token.length, 0);
+#endif
+
+                       call_stat = clnt_call(gd->clnt, NULLPROC,
+                                             xdr_rpc_gss_init_args,
+                                             &send_token,
+                                             xdr_rpc_gss_init_res,
+                                             (caddr_t)&gr, AUTH_TIMEOUT);
+
+                       gss_release_buffer(&min_stat, &send_token);
+
+                       if (call_stat != RPC_SUCCESS ||
+                           (gr.gr_major != GSS_S_COMPLETE &&
+                            gr.gr_major != GSS_S_CONTINUE_NEEDED))
+                               return FALSE;
+
+                       if (gr.gr_ctx.length != 0) {
+                               if (gd->gc.gc_ctx.value)
+                                       gss_release_buffer(&min_stat,
+                                                          &gd->gc.gc_ctx);
+                               gd->gc.gc_ctx = gr.gr_ctx;
+                       }
+                       if (gr.gr_token.length != 0) {
+                               if (maj_stat != GSS_S_CONTINUE_NEEDED)
+                                       break;
+                               recv_tokenp = &gr.gr_token;
+                       }
+                       gd->gc.gc_proc = RPCSEC_GSS_CONTINUE_INIT;
+               }
+
+               /* GSS_S_COMPLETE => check gss header verifier,
+                * usually checked in gss_validate
+                */
+               if (maj_stat == GSS_S_COMPLETE) {
+                       gss_buffer_desc   bufin;
+                       gss_buffer_desc   bufout;
+                       u_int seq, qop_state = 0;
+
+                       seq = htonl(gr.gr_win);
+                       bufin.value = (unsigned char *)&seq;
+                       bufin.length = sizeof(seq);
+                       bufout.value = (unsigned char *)gd->gc_wire_verf.value;
+                       bufout.length = gd->gc_wire_verf.length;
+
+                       maj_stat = gss_verify_mic(&min_stat, gd->ctx,
+                               &bufin, &bufout, &qop_state);
+
+                       if (maj_stat != GSS_S_COMPLETE
+                                       || qop_state != gd->sec.qop) {
+                               log_status("gss_verify_mic", maj_stat, min_stat);
+                               if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
+                                       gd->established = FALSE;
+                                       authgss_destroy_context(auth);
+                               }
+                               return (FALSE);
+                       }
+                       gd->established = TRUE;
+                       gd->gc.gc_proc = RPCSEC_GSS_DATA;
+                       gd->gc.gc_seq = 0;
+                       gd->win = gr.gr_win;
+                       break;
+               }
+       }
+       /* End context negotiation loop. */
+       if (gd->gc.gc_proc != RPCSEC_GSS_DATA) {
+               if (gr.gr_token.length != 0)
+                       gss_release_buffer(&min_stat, &gr.gr_token);
+
+               authgss_destroy(auth);
+               auth = NULL;
+               rpc_createerr.cf_stat = RPC_AUTHERROR;
+
+               return (FALSE);
+       }
+       return (TRUE);
+}
+
+bool_t
+authgss_service(AUTH *auth, int svc)
+{
+       struct rpc_gss_data     *gd;
+
+       log_debug("in authgss_service()");
+
+       if (!auth)
+               return(FALSE);
+       gd = AUTH_PRIVATE(auth);
+       if (!gd || !gd->established)
+               return (FALSE);
+       gd->sec.svc = svc;
+       gd->gc.gc_svc = svc;
+       return (TRUE);
+}
+
+static void
+authgss_destroy_context(AUTH *auth)
+{
+       struct rpc_gss_data     *gd;
+       OM_uint32                min_stat;
+
+       log_debug("in authgss_destroy_context()");
+
+       gd = AUTH_PRIVATE(auth);
+
+       if (gd->gc.gc_ctx.length != 0) {
+               if (gd->established) {
+                       gd->gc.gc_proc = RPCSEC_GSS_DESTROY;
+                       clnt_call(gd->clnt, NULLPROC, xdr_void, NULL,
+                                 xdr_void, NULL, AUTH_TIMEOUT);
+               }
+               gss_release_buffer(&min_stat, &gd->gc.gc_ctx);
+               /* XXX ANDROS check size of context  - should be 8 */
+               memset(&gd->gc.gc_ctx, 0, sizeof(gd->gc.gc_ctx));
+       }
+       if (gd->ctx != GSS_C_NO_CONTEXT) {
+               gss_delete_sec_context(&min_stat, &gd->ctx, NULL);
+               gd->ctx = GSS_C_NO_CONTEXT;
+       }
+       gd->established = FALSE;
+}
+
+static void
+authgss_destroy(AUTH *auth)
+{
+       struct rpc_gss_data     *gd;
+       OM_uint32                min_stat;
+
+       log_debug("in authgss_destroy()");
+
+       gd = AUTH_PRIVATE(auth);
+
+       authgss_destroy_context(auth);
+
+#ifdef DEBUG
+       fprintf(stderr, "authgss_destroy: freeing name %p\n", gd->name);
+#endif
+       if (gd->name != GSS_C_NO_NAME)
+               gss_release_name(&min_stat, &gd->name);
+
+       free(gd);
+       free(auth);
+}
+
+bool_t
+authgss_wrap(AUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
+{
+       struct rpc_gss_data     *gd;
+
+       log_debug("in authgss_wrap()");
+
+       gd = AUTH_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->gc.gc_seq));
+}
+
+bool_t
+authgss_unwrap(AUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
+{
+       struct rpc_gss_data     *gd;
+
+       log_debug("in authgss_unwrap()");
+
+       gd = AUTH_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->gc.gc_seq));
+}