X-Git-Url: https://git.decadent.org.uk/gitweb/?p=nfs-utils.git;a=blobdiff_plain;f=support%2Frpc%2Fsvc_auth_gss.c;fp=support%2Frpc%2Fsvc_auth_gss.c;h=02153f9a6b317f7c0b3e3b61f75b219aa5a0b3f8;hp=0000000000000000000000000000000000000000;hb=f1bfe0916c04d93de7a4fae5315fff6e4ccac23f;hpb=981d25a37fe4a71eddd162672a658da223453985 diff --git a/support/rpc/svc_auth_gss.c b/support/rpc/svc_auth_gss.c new file mode 100644 index 0000000..02153f9 --- /dev/null +++ b/support/rpc/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 . + 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 +#include +#include +#include +#include + +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 " + "", + 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 " + "", + 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 = # + 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); +}