summaryrefslogtreecommitdiff
path: root/src/svc_auth_gss.c
diff options
context:
space:
mode:
authorSteve Dickson <steved@redhat.com>2007-04-20 14:56:09 -0400
committerSteve Dickson <steved@redhat.com>2007-04-20 14:56:09 -0400
commitee5471e0e4bcfab5b4da3faf46c96e5dbb49da47 (patch)
tree704fe6cab3d309429a2b39b1d35684ac953d943a /src/svc_auth_gss.c
downloadti-rpc-ee5471e0e4bcfab5b4da3faf46c96e5dbb49da47.tar.gz
Initial commit of libtirpc 0.1.7
Signed-off-by: Steve Dickson <steved@redhat.com>
Diffstat (limited to 'src/svc_auth_gss.c')
-rw-r--r--src/svc_auth_gss.c582
1 files changed, 582 insertions, 0 deletions
diff --git a/src/svc_auth_gss.c b/src/svc_auth_gss.c
new file mode 100644
index 0000000..02153f9
--- /dev/null
+++ b/src/svc_auth_gss.c
@@ -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);
+}