summaryrefslogtreecommitdiff
path: root/src/lib/rpc/svc_auth_gssapi.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/rpc/svc_auth_gssapi.c')
-rw-r--r--src/lib/rpc/svc_auth_gssapi.c1136
1 files changed, 1136 insertions, 0 deletions
diff --git a/src/lib/rpc/svc_auth_gssapi.c b/src/lib/rpc/svc_auth_gssapi.c
new file mode 100644
index 000000000000..f3b3e35b8e2c
--- /dev/null
+++ b/src/lib/rpc/svc_auth_gssapi.c
@@ -0,0 +1,1136 @@
+/*
+ * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved.
+ *
+ * $Id$
+ *
+ */
+
+/*
+ * svc_auth_gssapi.c
+ * Handles the GSS-API flavor authentication parameters on the service
+ * side of RPC.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <gssrpc/rpc.h>
+#include <sys/stat.h>
+
+#include <gssapi/gssapi_generic.h>
+#include <gssrpc/auth_gssapi.h>
+
+#ifdef GSS_BACKWARD_HACK
+#include <gssapi/gssapi_krb5.h>
+#endif
+
+#include "gssrpcint.h"
+
+#ifdef GSSAPI_KRB5
+/* This is here for the krb5_error_code typedef and the
+ * KRB5KRB_AP_ERR_NOT_US #define.*/
+#include <krb5.h>
+#endif
+
+#include <sys/file.h>
+#include <fcntl.h>
+#include <time.h>
+
+#define INITIATION_TIMEOUT 60*15 /* seconds until partially created */
+ /* context is destroed */
+#define INDEF_EXPIRE 60*60*24 /* seconds until an context with no */
+ /* expiration time is expired */
+
+#ifdef __CODECENTER__
+#define DEBUG_GSSAPI 1
+#endif
+
+#ifdef DEBUG_GSSAPI
+int svc_debug_gssapi = DEBUG_GSSAPI;
+void gssrpcint_printf(const char *format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+#if 1
+ vprintf(format, ap);
+#else
+ {
+ static FILE *f;
+ if (f == NULL)
+ f = fopen("/dev/pts/4", "a");
+ if (f) {
+ vfprintf(f, format, ap);
+ fflush(f);
+ }
+ }
+#endif
+ va_end(ap);
+}
+#define L_PRINTF(l,args) if (svc_debug_gssapi >= l) gssrpcint_printf args
+#define PRINTF(args) L_PRINTF(99, args)
+#define AUTH_GSSAPI_DISPLAY_STATUS(args) \
+ if (svc_debug_gssapi) auth_gssapi_display_status args
+#else
+#define PRINTF(args)
+#define L_PRINTF(l, args)
+#define AUTH_GSSAPI_DISPLAY_STATUS(args)
+#endif
+
+typedef struct _svc_auth_gssapi_data {
+ bool_t established;
+
+ gss_ctx_id_t context;
+ gss_name_t client_name, server_name;
+ gss_cred_id_t server_creds;
+
+ uint32_t expiration;
+ uint32_t seq_num;
+ uint32_t key;
+
+ SVCAUTH svcauth;
+
+ /* kludge to free verifiers on next call */
+ gss_buffer_desc prev_verf;
+} svc_auth_gssapi_data;
+
+#define SVCAUTH_PRIVATE(auth) \
+ ((svc_auth_gssapi_data *)(auth)->svc_ah_private)
+
+static bool_t svc_auth_gssapi_wrap(SVCAUTH *, XDR *, xdrproc_t, caddr_t);
+static bool_t svc_auth_gssapi_unwrap(SVCAUTH *, XDR *, xdrproc_t, caddr_t);
+static bool_t svc_auth_gssapi_destroy(SVCAUTH *);
+
+static svc_auth_gssapi_data *create_client(void);
+static svc_auth_gssapi_data *get_client
+ (gss_buffer_t client_handle);
+static void destroy_client
+ (svc_auth_gssapi_data *client_data);
+static void clean_client(void), cleanup(void);
+static void client_expire
+ (svc_auth_gssapi_data *client_data, uint32_t exp);
+static void dump_db (char *msg);
+
+struct svc_auth_ops svc_auth_gssapi_ops = {
+ svc_auth_gssapi_wrap,
+ svc_auth_gssapi_unwrap,
+ svc_auth_gssapi_destroy
+};
+
+/*
+ * Globals! Eeek! Run for the hills!
+ */
+static gss_cred_id_t *server_creds_list = NULL;
+static gss_name_t *server_name_list = NULL;
+static int server_creds_count = 0;
+
+static auth_gssapi_log_badauth_func log_badauth = NULL;
+static caddr_t log_badauth_data = NULL;
+static auth_gssapi_log_badauth2_func log_badauth2 = NULL;
+static caddr_t log_badauth2_data = NULL;
+static auth_gssapi_log_badverf_func log_badverf = NULL;
+static caddr_t log_badverf_data = NULL;
+static auth_gssapi_log_miscerr_func log_miscerr = NULL;
+static caddr_t log_miscerr_data = NULL;
+
+#define LOG_MISCERR(arg) if (log_miscerr) \
+ (*log_miscerr)(rqst, msg, arg, log_miscerr_data)
+
+typedef struct _client_list {
+ svc_auth_gssapi_data *client;
+ struct _client_list *next;
+} client_list;
+
+static client_list *clients = NULL;
+
+
+/* Invoke log_badauth callbacks for an authentication failure. */
+static void
+badauth(OM_uint32 maj, OM_uint32 minor, SVCXPRT *xprt)
+{
+ if (log_badauth != NULL)
+ (*log_badauth)(maj, minor, &xprt->xp_raddr, log_badauth_data);
+ if (log_badauth2 != NULL)
+ (*log_badauth2)(maj, minor, xprt, log_badauth2_data);
+}
+
+enum auth_stat gssrpc__svcauth_gssapi(
+ register struct svc_req *rqst,
+ register struct rpc_msg *msg,
+ bool_t *no_dispatch)
+{
+ XDR xdrs;
+ auth_gssapi_creds creds;
+ auth_gssapi_init_arg call_arg;
+ auth_gssapi_init_res call_res;
+ gss_buffer_desc output_token, in_buf, out_buf;
+ gss_cred_id_t server_creds;
+ struct gss_channel_bindings_struct bindings, *bindp;
+ OM_uint32 gssstat, minor_stat, time_rec;
+ struct opaque_auth *cred, *verf;
+ svc_auth_gssapi_data *client_data;
+ int i;
+ enum auth_stat ret;
+ OM_uint32 ret_flags;
+ uint32_t seq_num;
+
+ PRINTF(("svcauth_gssapi: starting\n"));
+
+ /* clean up expired entries */
+ clean_client();
+
+ /* use AUTH_NONE until there is a client_handle */
+ rqst->rq_xprt->xp_auth = &svc_auth_none;
+
+ memset(&call_res, 0, sizeof(call_res));
+ creds.client_handle.length = 0;
+ creds.client_handle.value = NULL;
+
+ cred = &msg->rm_call.cb_cred;
+ verf = &msg->rm_call.cb_verf;
+
+ if (cred->oa_length == 0) {
+ PRINTF(("svcauth_gssapi: empty creds, failing\n"));
+ LOG_MISCERR("empty client credentials");
+ ret = AUTH_BADCRED;
+ goto error;
+ }
+
+ PRINTF(("svcauth_gssapi: decoding credentials\n"));
+ xdrmem_create(&xdrs, cred->oa_base, cred->oa_length, XDR_DECODE);
+ memset(&creds, 0, sizeof(creds));
+ if (! xdr_authgssapi_creds(&xdrs, &creds)) {
+ PRINTF(("svcauth_gssapi: failed decoding creds\n"));
+ LOG_MISCERR("protocol error in client credentials");
+ xdr_free(xdr_authgssapi_creds, &creds);
+ XDR_DESTROY(&xdrs);
+ ret = AUTH_BADCRED;
+ goto error;
+ }
+ XDR_DESTROY(&xdrs);
+
+ PRINTF(("svcauth_gssapi: got credentials, version %d, client_handle len %d\n",
+ creds.version, (int) creds.client_handle.length));
+
+ if (creds.version != 2) {
+ PRINTF(("svcauth_gssapi: bad credential version\n"));
+ LOG_MISCERR("unsupported client credentials version");
+ ret = AUTH_BADCRED;
+ goto error;
+ }
+
+#ifdef DEBUG_GSSAPI
+ if (svc_debug_gssapi) {
+ if (creds.auth_msg && rqst->rq_proc == AUTH_GSSAPI_EXIT) {
+ PRINTF(("svcauth_gssapi: GSSAPI_EXIT, cleaning up\n"));
+ svc_sendreply(rqst->rq_xprt, xdr_void, NULL);
+ xdr_free(xdr_authgssapi_creds, &creds);
+ cleanup();
+ exit(0);
+ }
+ }
+#endif
+
+ /*
+ * If this is an auth_msg and proc is GSSAPI_INIT, then create a
+ * client handle for this client. Otherwise, look up the
+ * existing handle.
+ */
+ if (creds.auth_msg && rqst->rq_proc == AUTH_GSSAPI_INIT) {
+ if (creds.client_handle.length != 0) {
+ PRINTF(("svcauth_gssapi: non-empty handle on GSSAPI_INIT\n"));
+ LOG_MISCERR("protocol error in client handle");
+ ret = AUTH_FAILED;
+ goto error;
+ }
+
+ PRINTF(("svcauth_gssapi: GSSAPI_INIT, creating client.\n"));
+
+ client_data = create_client();
+ if (client_data == NULL) {
+ PRINTF(("svcauth_gssapi: create_client failed\n"));
+ LOG_MISCERR("internal error creating client record");
+ ret = AUTH_FAILED;
+ goto error;
+ }
+ } else {
+ if (creds.client_handle.length == 0) {
+ PRINTF(("svcauth_gssapi: expected non-empty creds\n"));
+ LOG_MISCERR("protocol error in client credentials");
+ ret = AUTH_FAILED;
+ goto error;
+ }
+
+ PRINTF(("svcauth_gssapi: incoming client_handle %d, len %d\n",
+ *((uint32_t *) creds.client_handle.value),
+ (int) creds.client_handle.length));
+
+ client_data = get_client(&creds.client_handle);
+ if (client_data == NULL) {
+ PRINTF(("svcauth_gssapi: client_handle lookup failed\n"));
+ LOG_MISCERR("invalid client handle received");
+ ret = AUTH_BADCRED;
+ goto error;
+ }
+ PRINTF(("svcauth_gssapi: client_handle lookup succeeded\n"));
+ }
+
+ /* any response we send will use client_handle, so set it now */
+ call_res.client_handle.length = sizeof(client_data->key);
+ call_res.client_handle.value = (char *) &client_data->key;
+
+ /* mark this call as using AUTH_GSSAPI via client_data's SVCAUTH */
+ rqst->rq_xprt->xp_auth = &client_data->svcauth;
+
+ if (client_data->established == FALSE) {
+ PRINTF(("svcauth_gssapi: context is not established\n"));
+
+ if (creds.auth_msg == FALSE) {
+ PRINTF(("svcauth_gssapi: expected auth_msg TRUE\n"));
+ LOG_MISCERR("protocol error on incomplete connection");
+ ret = AUTH_REJECTEDCRED;
+ goto error;
+ }
+
+ /*
+ * If the context is not established, then only GSSAPI_INIT
+ * and _CONTINUE requests are valid.
+ */
+ if (rqst->rq_proc != AUTH_GSSAPI_INIT && rqst->rq_proc !=
+ AUTH_GSSAPI_CONTINUE_INIT) {
+ PRINTF(("svcauth_gssapi: unacceptable procedure %d\n",
+ rqst->rq_proc));
+ LOG_MISCERR("protocol error on incomplete connection");
+ ret = AUTH_FAILED;
+ goto error;
+ }
+
+ /* call is for us, deserialize arguments */
+ memset(&call_arg, 0, sizeof(call_arg));
+ if (! svc_getargs(rqst->rq_xprt, xdr_authgssapi_init_arg,
+ &call_arg)) {
+ PRINTF(("svcauth_gssapi: cannot decode args\n"));
+ LOG_MISCERR("protocol error in procedure arguments");
+ ret = AUTH_BADCRED;
+ goto error;
+ }
+
+ /*
+ * Process the call arg version number.
+ *
+ * Set the krb5_gss backwards-compatibility mode based on client
+ * version. This controls whether the AP_REP message is
+ * encrypted with the session key (version 2+, correct) or the
+ * session subkey (version 1, incorrect). This function can
+ * never fail, so we don't bother checking its return value.
+ */
+ switch (call_arg.version) {
+ case 1:
+ case 2:
+ LOG_MISCERR("Warning: Accepted old RPC protocol request");
+ call_res.version = 1;
+ break;
+ case 3:
+ case 4:
+ /* 3 and 4 are essentially the same, don't bother warning */
+ call_res.version = call_arg.version;
+ break;
+ default:
+ PRINTF(("svcauth_gssapi: bad GSSAPI_INIT version\n"));
+ LOG_MISCERR("unsupported GSSAPI_INIT version");
+ ret = AUTH_BADCRED;
+ goto error;
+ }
+
+#ifdef GSS_BACKWARD_HACK
+ krb5_gss_set_backward_mode(&minor_stat, call_arg.version == 1);
+#endif
+
+ if (call_arg.version >= 3) {
+ memset(&bindings, 0, sizeof(bindings));
+ bindings.application_data.length = 0;
+ bindings.initiator_addrtype = GSS_C_AF_INET;
+ bindings.initiator_address.length = 4;
+ bindings.initiator_address.value =
+ &svc_getcaller(rqst->rq_xprt)->sin_addr.s_addr;
+
+ if (rqst->rq_xprt->xp_laddrlen > 0) {
+ bindings.acceptor_addrtype = GSS_C_AF_INET;
+ bindings.acceptor_address.length = 4;
+ bindings.acceptor_address.value =
+ &rqst->rq_xprt->xp_laddr.sin_addr.s_addr;
+ } else {
+ LOG_MISCERR("cannot get local address");
+ ret = AUTH_FAILED;
+ goto error;
+ }
+
+
+ bindp = &bindings;
+ } else {
+ bindp = GSS_C_NO_CHANNEL_BINDINGS;
+ }
+
+ /*
+ * If the client's server_creds is already set, use it.
+ * Otherwise, try each credential in server_creds_list until
+ * one of them succeedes, then set the client server_creds
+ * to that. If all fail, the client's server_creds isn't
+ * set (which is fine, because the client will be gc'ed
+ * anyway).
+ *
+ * If accept_sec_context returns something other than
+ * success and GSS_S_FAILURE, then assume different
+ * credentials won't help and stop looping.
+ *
+ * Note that there are really two cases here: (1) the client
+ * has a server_creds already, and (2) it does not. They
+ * are both written in the same loop so that there is only
+ * one textual call to gss_accept_sec_context; in fact, in
+ * case (1), the loop is executed exactly once.
+ */
+ for (i = 0; i < server_creds_count; i++) {
+ if (client_data->server_creds != NULL) {
+ PRINTF(("svcauth_gssapi: using's clients server_creds\n"));
+ server_creds = client_data->server_creds;
+ } else {
+ PRINTF(("svcauth_gssapi: trying creds %d\n", i));
+ server_creds = server_creds_list[i];
+ }
+
+ /* Free previous output_token from loop */
+ if(i != 0) gss_release_buffer(&minor_stat, &output_token);
+
+ call_res.gss_major =
+ gss_accept_sec_context(&call_res.gss_minor,
+ &client_data->context,
+ server_creds,
+ &call_arg.token,
+ bindp,
+ &client_data->client_name,
+ NULL,
+ &output_token,
+ &ret_flags,
+ &time_rec,
+ NULL);
+
+ if (server_creds == client_data->server_creds)
+ break;
+
+ PRINTF(("accept_sec_context returned 0x%x 0x%x not-us=%#x\n",
+ call_res.gss_major, call_res.gss_minor,
+ (int) KRB5KRB_AP_ERR_NOT_US));
+ if (call_res.gss_major == GSS_S_COMPLETE ||
+ call_res.gss_major == GSS_S_CONTINUE_NEEDED) {
+ /* server_creds was right, set it! */
+ PRINTF(("svcauth_gssapi: creds are correct, storing\n"));
+ client_data->server_creds = server_creds;
+ client_data->server_name = server_name_list[i];
+ break;
+ } else if (call_res.gss_major != GSS_S_FAILURE
+#ifdef GSSAPI_KRB5
+ /*
+ * hard-coded because there is no other way
+ * to prevent all GSS_S_FAILURES from
+ * returning a "wrong principal in request"
+ * error
+ */
+ || ((krb5_error_code) call_res.gss_minor !=
+ (krb5_error_code) KRB5KRB_AP_ERR_NOT_US)
+#endif
+ ) {
+ break;
+ }
+ }
+
+ gssstat = call_res.gss_major;
+ minor_stat = call_res.gss_minor;
+
+ /* done with call args */
+ xdr_free(xdr_authgssapi_init_arg, &call_arg);
+
+ PRINTF(("svcauth_gssapi: accept_sec_context returned %#x %#x\n",
+ call_res.gss_major, call_res.gss_minor));
+ if (call_res.gss_major != GSS_S_COMPLETE &&
+ call_res.gss_major != GSS_S_CONTINUE_NEEDED) {
+ AUTH_GSSAPI_DISPLAY_STATUS(("accepting context",
+ call_res.gss_major,
+ call_res.gss_minor));
+
+ badauth(call_res.gss_major, call_res.gss_minor, rqst->rq_xprt);
+
+ gss_release_buffer(&minor_stat, &output_token);
+ svc_sendreply(rqst->rq_xprt, xdr_authgssapi_init_res,
+ (caddr_t) &call_res);
+ *no_dispatch = TRUE;
+ ret = AUTH_OK;
+ goto error;
+ }
+
+ if (output_token.length != 0) {
+ PRINTF(("svcauth_gssapi: got new output token\n"));
+ GSS_COPY_BUFFER(call_res.token, output_token);
+ }
+
+ if (gssstat == GSS_S_COMPLETE) {
+ client_data->seq_num = rand();
+ client_expire(client_data,
+ (time_rec == GSS_C_INDEFINITE ?
+ INDEF_EXPIRE : time_rec) + time(0));
+
+ PRINTF(("svcauth_gssapi: context established, isn %d\n",
+ client_data->seq_num));
+
+ if (auth_gssapi_seal_seq(client_data->context,
+ client_data->seq_num,
+ &call_res.signed_isn) ==
+ FALSE) {
+ ret = AUTH_FAILED;
+ LOG_MISCERR("internal error sealing sequence number");
+ gss_release_buffer(&minor_stat, &output_token);
+ goto error;
+ }
+ }
+
+ PRINTF(("svcauth_gssapi: sending reply\n"));
+ svc_sendreply(rqst->rq_xprt, xdr_authgssapi_init_res,
+ (caddr_t) &call_res);
+ *no_dispatch = TRUE;
+
+ /*
+ * If appropriate, set established to TRUE *after* sending
+ * response (otherwise, the client will receive the final
+ * token encrypted)
+ */
+ if (gssstat == GSS_S_COMPLETE) {
+ gss_release_buffer(&minor_stat, &call_res.signed_isn);
+ client_data->established = TRUE;
+ }
+ gss_release_buffer(&minor_stat, &output_token);
+ } else {
+ PRINTF(("svcauth_gssapi: context is established\n"));
+
+ /* check the verifier */
+ PRINTF(("svcauth_gssapi: checking verifier, len %d\n",
+ verf->oa_length));
+
+ in_buf.length = verf->oa_length;
+ in_buf.value = verf->oa_base;
+
+ if (auth_gssapi_unseal_seq(client_data->context, &in_buf,
+ &seq_num) == FALSE) {
+ ret = AUTH_BADVERF;
+ LOG_MISCERR("internal error unsealing sequence number");
+ goto error;
+ }
+
+ if (seq_num != client_data->seq_num + 1) {
+ PRINTF(("svcauth_gssapi: expected isn %d, got %d\n",
+ client_data->seq_num + 1, seq_num));
+ if (log_badverf != NULL)
+ (*log_badverf)(client_data->client_name,
+ client_data->server_name,
+ rqst, msg, log_badverf_data);
+
+ ret = AUTH_REJECTEDVERF;
+ goto error;
+ }
+ client_data->seq_num++;
+
+ PRINTF(("svcauth_gssapi: seq_num %d okay\n", seq_num));
+
+ /* free previous response verifier, if any */
+ if (client_data->prev_verf.length != 0) {
+ gss_release_buffer(&minor_stat, &client_data->prev_verf);
+ client_data->prev_verf.length = 0;
+ }
+
+ /* prepare response verifier */
+ seq_num = client_data->seq_num + 1;
+ if (auth_gssapi_seal_seq(client_data->context, seq_num,
+ &out_buf) == FALSE) {
+ ret = AUTH_FAILED;
+ LOG_MISCERR("internal error sealing sequence number");
+ goto error;
+ }
+
+ client_data->seq_num++;
+
+ PRINTF(("svcauth_gssapi; response seq_num %d\n", seq_num));
+
+ rqst->rq_xprt->xp_verf.oa_flavor = AUTH_GSSAPI;
+ rqst->rq_xprt->xp_verf.oa_base = out_buf.value;
+ rqst->rq_xprt->xp_verf.oa_length = out_buf.length;
+
+ /* save verifier so it can be freed next time */
+ client_data->prev_verf.value = out_buf.value;
+ client_data->prev_verf.length = out_buf.length;
+
+ /*
+ * Message is authentic. If auth_msg if true, process the
+ * call; otherwise, return AUTH_OK so it will be dispatched
+ * to the application server.
+ */
+
+ if (creds.auth_msg == TRUE) {
+ /*
+ * If process_token fails, then the token probably came
+ * from an attacker. No response (error or otherwise)
+ * should be returned to the client, since it won't be
+ * accepting one.
+ */
+
+ switch (rqst->rq_proc) {
+ case AUTH_GSSAPI_MSG:
+ PRINTF(("svcauth_gssapi: GSSAPI_MSG, getting args\n"));
+ memset(&call_arg, 0, sizeof(call_arg));
+ if (! svc_getargs(rqst->rq_xprt, xdr_authgssapi_init_arg,
+ &call_arg)) {
+ PRINTF(("svcauth_gssapi: cannot decode args\n"));
+ LOG_MISCERR("protocol error in call arguments");
+ xdr_free(xdr_authgssapi_init_arg, &call_arg);
+ ret = AUTH_BADCRED;
+ goto error;
+ }
+
+ PRINTF(("svcauth_gssapi: processing token\n"));
+ gssstat = gss_process_context_token(&minor_stat,
+ client_data->context,
+ &call_arg.token);
+
+ /* done with call args */
+ xdr_free(xdr_authgssapi_init_arg, &call_arg);
+
+ if (gssstat != GSS_S_COMPLETE) {
+ AUTH_GSSAPI_DISPLAY_STATUS(("processing token",
+ gssstat, minor_stat));
+ ret = AUTH_FAILED;
+ goto error;
+ }
+
+ svc_sendreply(rqst->rq_xprt, xdr_void, NULL);
+ *no_dispatch = TRUE;
+ break;
+
+ case AUTH_GSSAPI_DESTROY:
+ PRINTF(("svcauth_gssapi: GSSAPI_DESTROY\n"));
+
+ PRINTF(("svcauth_gssapi: sending reply\n"));
+ svc_sendreply(rqst->rq_xprt, xdr_void, NULL);
+ *no_dispatch = TRUE;
+
+ destroy_client(client_data);
+ rqst->rq_xprt->xp_auth = NULL;
+ break;
+
+ default:
+ PRINTF(("svcauth_gssapi: unacceptable procedure %d\n",
+ rqst->rq_proc));
+ LOG_MISCERR("invalid call procedure number");
+ ret = AUTH_FAILED;
+ goto error;
+ }
+ } else {
+ /* set credentials for app server; comment in svc.c */
+ /* seems to imply this is incorrect, but I don't see */
+ /* any problem with it... */
+ rqst->rq_clntcred = (char *)client_data->client_name;
+ rqst->rq_svccred = (char *)client_data->context;
+ }
+ }
+
+ if (creds.client_handle.length != 0) {
+ PRINTF(("svcauth_gssapi: freeing client_handle len %d\n",
+ (int) creds.client_handle.length));
+ xdr_free(xdr_authgssapi_creds, &creds);
+ }
+
+ PRINTF(("\n"));
+ return AUTH_OK;
+
+error:
+ if (creds.client_handle.length != 0) {
+ PRINTF(("svcauth_gssapi: freeing client_handle len %d\n",
+ (int) creds.client_handle.length));
+ xdr_free(xdr_authgssapi_creds, &creds);
+ }
+
+ PRINTF(("\n"));
+ return ret;
+}
+
+static void cleanup(void)
+{
+ client_list *c, *c2;
+
+ PRINTF(("cleanup_and_exit: starting\n"));
+
+ c = clients;
+ while (c) {
+ c2 = c;
+ c = c->next;
+ destroy_client(c2->client);
+ free(c2);
+ }
+
+ exit(0);
+}
+
+/*
+ * Function: create_client
+ *
+ * Purpose: Creates an new client_data structure and stores it in the
+ * database.
+ *
+ * Returns: the new client_data structure, or NULL on failure.
+ *
+ * Effects:
+ *
+ * A new client_data is created and stored in the hash table and
+ * b-tree. A new key that is unique in the current database is
+ * chosen; this key should be used as the client's client_handle.
+ */
+static svc_auth_gssapi_data *create_client(void)
+{
+ client_list *c;
+ svc_auth_gssapi_data *client_data;
+ static int client_key = 1;
+
+ PRINTF(("svcauth_gssapi: empty creds, creating\n"));
+
+ client_data = (svc_auth_gssapi_data *) malloc(sizeof(*client_data));
+ if (client_data == NULL)
+ return NULL;
+ memset(client_data, 0, sizeof(*client_data));
+ L_PRINTF(2, ("create_client: new client_data = %p\n",
+ (void *) client_data));
+
+ /* set up client data structure */
+ client_data->established = 0;
+ client_data->context = GSS_C_NO_CONTEXT;
+ client_data->expiration = time(0) + INITIATION_TIMEOUT;
+
+ /* set up psycho-recursive SVCAUTH hack */
+ client_data->svcauth.svc_ah_ops = &svc_auth_gssapi_ops;
+ client_data->svcauth.svc_ah_private = (caddr_t) client_data;
+
+ client_data->key = client_key++;
+
+ c = (client_list *) malloc(sizeof(client_list));
+ if (c == NULL)
+ return NULL;
+ c->client = client_data;
+ c->next = NULL;
+
+
+ if (clients == NULL)
+ clients = c;
+ else {
+ c->next = clients;
+ clients = c;
+ }
+
+ PRINTF(("svcauth_gssapi: new handle %d\n", client_data->key));
+ L_PRINTF(2, ("create_client: done\n"));
+
+ return client_data;
+}
+
+/*
+ * Function: client_expire
+ *
+ * Purpose: change the expiration time of a client in the database
+ *
+ * Arguments:
+ *
+ * client_data (r) the client_data to expire
+ * exp (r) the new expiration time
+ *
+ * Effects:
+ *
+ * client_data->expiration = exp
+ *
+ * This function used to remove client_data from the database, change
+ * its expiration time, and re-add it, which was necessary because the
+ * database was sorted by expiration time so a simple modification
+ * would break the rep invariant. Now the database is an unsorted
+ * linked list, so it doesn't matter.
+ */
+static void client_expire(
+ svc_auth_gssapi_data *client_data,
+ uint32_t exp)
+{
+ client_data->expiration = exp;
+}
+
+/*
+ * Function get_client
+ *
+ * Purpose: retrieve a client_data structure from the database based
+ * on its client handle (key)
+ *
+ * Arguments:
+ *
+ * client_handle (r) the handle (key) to retrieve
+ *
+ * Effects:
+ *
+ * Searches the list and returns the client_data whose key field
+ * matches the contents of client_handle, or returns NULL if none was
+ * found.
+ */
+static svc_auth_gssapi_data *get_client(gss_buffer_t client_handle)
+{
+ client_list *c;
+ uint32_t handle;
+
+ memcpy(&handle, client_handle->value, 4);
+
+ L_PRINTF(2, ("get_client: looking for client %d\n", handle));
+
+ c = clients;
+ while (c) {
+ if (c->client->key == handle)
+ return c->client;
+ c = c->next;
+ }
+
+ L_PRINTF(2, ("get_client: client_handle lookup failed\n"));
+ return NULL;
+}
+
+/*
+ * Function: destroy_client
+ *
+ * Purpose: destroys a client entry and removes it from the database
+ *
+ * Arguments:
+ *
+ * client_data (r) the client to be destroyed
+ *
+ * Effects:
+ *
+ * client_data->context is deleted with gss_delete_sec_context.
+ * client_data's entry in the database is destroyed. client_data is
+ * freed.
+ */
+static void destroy_client(svc_auth_gssapi_data *client_data)
+{
+ OM_uint32 gssstat, minor_stat;
+ gss_buffer_desc out_buf;
+ client_list *c, *c2;
+
+ PRINTF(("destroy_client: destroying client_data\n"));
+ L_PRINTF(2, ("destroy_client: client_data = %p\n", (void *) client_data));
+
+#ifdef DEBUG_GSSAPI
+ if (svc_debug_gssapi >= 3)
+ dump_db("before frees");
+#endif
+
+ /* destroy client struct even if error occurs */
+
+ gssstat = gss_delete_sec_context(&minor_stat, &client_data->context,
+ &out_buf);
+ if (gssstat != GSS_S_COMPLETE)
+ AUTH_GSSAPI_DISPLAY_STATUS(("deleting context", gssstat,
+ minor_stat));
+
+ gss_release_buffer(&minor_stat, &out_buf);
+ gss_release_name(&minor_stat, &client_data->client_name);
+ if (client_data->prev_verf.length != 0)
+ gss_release_buffer(&minor_stat, &client_data->prev_verf);
+
+ if (clients == NULL) {
+ PRINTF(("destroy_client: called on empty database\n"));
+ abort();
+ } else if (clients->client == client_data) {
+ c = clients;
+ clients = clients->next;
+ free(c);
+ } else {
+ c2 = clients;
+ c = clients->next;
+ while (c) {
+ if (c->client == client_data) {
+ c2->next = c->next;
+ free(c);
+ goto done;
+ } else {
+ c2 = c;
+ c = c->next;
+ }
+ }
+ PRINTF(("destroy_client: client_handle delete failed\n"));
+ abort();
+ }
+
+done:
+
+ L_PRINTF(2, ("destroy_client: client %d destroyed\n", client_data->key));
+
+ free(client_data);
+
+#if 0 /*ifdef PURIFY*/
+ purify_watch_n(client_data, sizeof(*client_data), "rw");
+#endif
+}
+
+static void dump_db(char *msg)
+{
+ svc_auth_gssapi_data *client_data;
+ client_list *c;
+
+ L_PRINTF(3, ("dump_db: %s:\n", msg));
+
+ c = clients;
+ while (c) {
+ client_data = c->client;
+ L_PRINTF(3, ("\tclient_data = %p, exp = %d\n",
+ (void *) client_data, client_data->expiration));
+ c = c->next;
+ }
+
+ L_PRINTF(3, ("\n"));
+}
+
+static void clean_client(void)
+{
+ svc_auth_gssapi_data *client_data;
+ client_list *c;
+
+ PRINTF(("clean_client: starting\n"));
+
+ c = clients;
+ while (c) {
+ client_data = c->client;
+
+ L_PRINTF(2, ("clean_client: client_data = %p\n",
+ (void *) client_data));
+
+ if (client_data->expiration < time(0)) {
+ PRINTF(("clean_client: client %d expired\n",
+ client_data->key));
+ destroy_client(client_data);
+ c = clients; /* start over, just to be safe */
+ } else {
+ c = c->next;
+ }
+ }
+
+ PRINTF(("clean_client: done\n"));
+}
+
+/*
+ * Function: svcauth_gssapi_set_names
+ *
+ * Purpose: Sets the list of service names for which incoming
+ * authentication requests should be honored.
+ *
+ * See functional specifications.
+ */
+bool_t svcauth_gssapi_set_names(
+ auth_gssapi_name *names,
+ int num)
+{
+ OM_uint32 gssstat, minor_stat;
+ gss_buffer_desc in_buf;
+ int i;
+
+ if (num == 0)
+ for (; names[num].name != NULL; num++)
+ ;
+
+ server_creds_list = NULL;
+ server_name_list = NULL;
+
+ server_creds_list = (gss_cred_id_t *) malloc(num*sizeof(gss_cred_id_t));
+ if (server_creds_list == NULL)
+ goto fail;
+ server_name_list = (gss_name_t *) malloc(num*sizeof(gss_name_t));
+ if (server_name_list == NULL)
+ goto fail;
+
+ for (i = 0; i < num; i++) {
+ server_name_list[i] = 0;
+ server_creds_list[i] = 0;
+ }
+
+ server_creds_count = num;
+
+ for (i = 0; i < num; i++) {
+ in_buf.value = names[i].name;
+ in_buf.length = strlen(in_buf.value) + 1;
+
+ PRINTF(("svcauth_gssapi_set_names: importing %s\n", names[i].name));
+
+ gssstat = gss_import_name(&minor_stat, &in_buf, names[i].type,
+ &server_name_list[i]);
+
+ if (gssstat != GSS_S_COMPLETE) {
+ AUTH_GSSAPI_DISPLAY_STATUS(("importing name", gssstat,
+ minor_stat));
+ goto fail;
+ }
+
+ gssstat = gss_acquire_cred(&minor_stat, server_name_list[i], 0,
+ GSS_C_NULL_OID_SET, GSS_C_ACCEPT,
+ &server_creds_list[i], NULL, NULL);
+ if (gssstat != GSS_S_COMPLETE) {
+ AUTH_GSSAPI_DISPLAY_STATUS(("acquiring credentials",
+ gssstat, minor_stat));
+ goto fail;
+ }
+ }
+
+ return TRUE;
+
+fail:
+ svcauth_gssapi_unset_names();
+
+ return FALSE;
+}
+
+/* Function: svcauth_gssapi_unset_names
+ *
+ * Purpose: releases the names and credentials allocated by
+ * svcauth_gssapi_set_names
+ */
+
+void svcauth_gssapi_unset_names(void)
+{
+ int i;
+ OM_uint32 minor_stat;
+
+ if (server_creds_list) {
+ for (i = 0; i < server_creds_count; i++)
+ if (server_creds_list[i])
+ gss_release_cred(&minor_stat, &server_creds_list[i]);
+ free(server_creds_list);
+ server_creds_list = NULL;
+ }
+
+ if (server_name_list) {
+ for (i = 0; i < server_creds_count; i++)
+ if (server_name_list[i])
+ gss_release_name(&minor_stat, &server_name_list[i]);
+ free(server_name_list);
+ server_name_list = NULL;
+ }
+ server_creds_count = 0;
+}
+
+
+/*
+ * Function: svcauth_gssapi_set_log_badauth_func
+ *
+ * Purpose: sets the logging function called when an invalid RPC call
+ * arrives
+ *
+ * See functional specifications.
+ */
+void svcauth_gssapi_set_log_badauth_func(
+ auth_gssapi_log_badauth_func func,
+ caddr_t data)
+{
+ log_badauth = func;
+ log_badauth_data = data;
+}
+
+void
+svcauth_gssapi_set_log_badauth2_func(auth_gssapi_log_badauth2_func func,
+ caddr_t data)
+{
+ log_badauth2 = func;
+ log_badauth2_data = data;
+}
+
+/*
+ * Function: svcauth_gssapi_set_log_badverf_func
+ *
+ * Purpose: sets the logging function called when an invalid RPC call
+ * arrives
+ *
+ * See functional specifications.
+ */
+void svcauth_gssapi_set_log_badverf_func(
+ auth_gssapi_log_badverf_func func,
+ caddr_t data)
+{
+ log_badverf = func;
+ log_badverf_data = data;
+}
+
+/*
+ * Function: svcauth_gssapi_set_log_miscerr_func
+ *
+ * Purpose: sets the logging function called when a miscellaneous
+ * AUTH_GSSAPI error occurs
+ *
+ * See functional specifications.
+ */
+void svcauth_gssapi_set_log_miscerr_func(
+ auth_gssapi_log_miscerr_func func,
+ caddr_t data)
+{
+ log_miscerr = func;
+ log_miscerr_data = data;
+}
+
+/*
+ * Encrypt the serialized arguments from xdr_func applied to xdr_ptr
+ * and write the result to xdrs.
+ */
+static bool_t svc_auth_gssapi_wrap(
+ SVCAUTH *auth,
+ XDR *out_xdrs,
+ bool_t (*xdr_func)(),
+ caddr_t xdr_ptr)
+{
+ OM_uint32 gssstat, minor_stat;
+
+ if (! SVCAUTH_PRIVATE(auth)->established) {
+ PRINTF(("svc_gssapi_wrap: not established, noop\n"));
+ return (*xdr_func)(out_xdrs, xdr_ptr);
+ } else if (! auth_gssapi_wrap_data(&gssstat, &minor_stat,
+ SVCAUTH_PRIVATE(auth)->context,
+ SVCAUTH_PRIVATE(auth)->seq_num,
+ out_xdrs, xdr_func, xdr_ptr)) {
+ if (gssstat != GSS_S_COMPLETE)
+ AUTH_GSSAPI_DISPLAY_STATUS(("encrypting function arguments",
+ gssstat, minor_stat));
+ return FALSE;
+ } else
+ return TRUE;
+}
+
+static bool_t svc_auth_gssapi_unwrap(
+ SVCAUTH *auth,
+ XDR *in_xdrs,
+ bool_t (*xdr_func)(),
+ caddr_t xdr_ptr)
+{
+ svc_auth_gssapi_data *client_data = SVCAUTH_PRIVATE(auth);
+ OM_uint32 gssstat, minor_stat;
+
+ if (! client_data->established) {
+ PRINTF(("svc_gssapi_unwrap: not established, noop\n"));
+ return (*xdr_func)(in_xdrs, (auth_gssapi_init_arg *)(void *) xdr_ptr);
+ } else if (! auth_gssapi_unwrap_data(&gssstat, &minor_stat,
+ client_data->context,
+ client_data->seq_num-1,
+ in_xdrs, xdr_func, xdr_ptr)) {
+ if (gssstat != GSS_S_COMPLETE)
+ AUTH_GSSAPI_DISPLAY_STATUS(("decrypting function arguments",
+ gssstat, minor_stat));
+ return FALSE;
+ } else
+ return TRUE;
+}
+
+static bool_t svc_auth_gssapi_destroy(SVCAUTH *auth)
+{
+ svc_auth_gssapi_data *client_data = SVCAUTH_PRIVATE(auth);
+
+ destroy_client(client_data);
+ return TRUE;
+}