[patch RFC] Add Kerberos authentication

Pete Zaitcev zaitcev at redhat.com
Wed Jul 20 22:19:17 UTC 2011


This patch adds an authentication with Kerberos, which is tested to work
with curl client. It also features an attempt at a build-time test.

diff --git a/configure.ac b/configure.ac
index e23d1fa..eb0c4c1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -70,6 +70,9 @@ AC_CHECK_LIB([uuid], [uuid_generate_random],
 	[AC_MSG_ERROR([Missing required uuid lib])])
 AC_SUBST([UUID_LIB])
 
+dnl How to check properly?
+LIBS="$LIBS -lgssapi_krb5"
+
 PKG_CHECK_MODULES([HAIL],[libhail >= 0.8])
 AC_SUBST([HAIL_LIBS])
 AC_SUBST([HAIL_CFLAGS])
@@ -97,6 +100,12 @@ AC_CACHE_CHECK([whether json_load_file takes 3 arguments],
 AC_DEFINE_UNQUOTED([JANSSON_LOAD_FLAG], $iw_cv_func_jansson_flag,
   [Define to "0," if json_load_file and json_loads require a flags arguments.])
 
+AC_CHECK_HEADER([gssapi.h], [],
+  [AC_MSG_ERROR([Missing gssapi.h header])])
+dnl Maybe we should use setenv("KRB5_KTNAME",...) after all? That always works.
+AC_CHECK_HEADER([gssapi/gssapi_krb5.h], [],
+  [AC_MSG_ERROR([Missing gssapi/gssapi_krb5.h header])])
+
 # from http://www.gnu.org/software/autoconf-archive/
 AX_BOOST_BASE
 AX_BOOST_SYSTEM
diff --git a/rest.c b/rest.c
index 11b2971..d470de2 100644
--- a/rest.c
+++ b/rest.c
@@ -29,10 +29,13 @@
 #include <sys/stat.h>
 #include <assert.h>
 #include <errno.h>
+#include <stdbool.h>
 
 #include <microhttpd.h>
 #include <hstor.h>	/* only for ARRAY_SIZE at this point */
 #include <curl/curl.h>
+#include <gssapi.h>
+#include <gssapi/gssapi_krb5.h>
 
 #include "configmake.h" /* for LOCALEDIR */
 #include "dirname.h"
@@ -50,6 +53,7 @@
 #include "mpipe.h"
 #include "state_defs.h"
 #include "version-etc.h"
+#include "base64.h"
 
 const char version_etc_copyright[] =
   /* Do *not* mark this string for translation.  %s is a copyright
@@ -104,7 +108,70 @@ typedef struct {
 	MHD_AccessHandlerCallback	 handler;
 } rule;
 
+static void log_gss_error(const char *msg, unsigned int gss_min)
+{
+	char buf[700];
+	int len, rem;
+	int rc;
+	unsigned int gss_maj, dummy_min;
+	unsigned int msg_ctx = 0;
+	gss_buffer_desc gss_str;
+
+	rem = sizeof(buf);
+	len = 0;
+	buf[0] = 0;
+
+	rc = snprintf(buf+len, rem, "%s", msg);
+	if (rc >= rem)
+		goto out;
+	rem -= rc;
+	len += rc;
+
+	/*
+	 * See RFC-2744 clause 5.11. However, the practical code is different.
+	 *  - Everyone relies on the returned string nul-terminated,
+	 *    while RFC uses gss_buf.length.
+	 *  - Some people parse major codes (using different mech_type),
+	 *    while RFC gives no hint how.
+	 */
+	do {
+		gss_maj = gss_display_status(&dummy_min, gss_min,
+					     GSS_C_MECH_CODE, GSS_C_NO_OID,
+					     &msg_ctx, &gss_str);
+		if (gss_maj != GSS_S_COMPLETE) {
+			/*
+			 * If we put "unknown" here, someone may localize it.
+			 */
+			rc = snprintf(buf+len, rem, ": UNK");
+			if (rc >= rem)
+				goto out;
+			rem -= rc;
+			len += rc;
+			goto out;
+		}
+
+		rc = snprintf(buf+len, rem, ": %s", (char*)gss_str.value);
+		if (rc >= rem)
+			goto out;
+		rem -= rc;
+		len += rc;
+
+		gss_release_buffer(&dummy_min, &gss_str);
+	} while(msg_ctx);
+ out:
+	/* there's always a nul at buf[len] */
+	fprintf(stderr, "%s\n", buf);
+}
+
 static unsigned short		 my_port	= MY_PORT;
+static char			 my_hostname[HOST_NAME_MAX+1];
+static bool			 kerberos	= false;
+/* XXX Make keytab location to follow --prefix. */
+static const char		*krb_keytab	= "/etc/iwhd/krb.keytab";
+static const char		*krb_svc_name	= "IWHD";
+#if 0 /* RFC says use default */
+static gss_cred_id_t		 gss_creds; /* struct* */   /* XXX needed? */
+#endif
 
 static const char *const (reserved_name[]) = {"_default", "_new", "_policy", "_query", NULL};
 static const char *const (reserved_attr[]) = {"_attrs", "_bucket", "_date", "_etag", "_key", "_loc", "_size", NULL};
@@ -163,6 +230,35 @@ validate_url (const char *url)
 	return !is_reserved(slash+1,reserved_name);
 }
 
+static int do_resp(struct MHD_Connection *conn, my_state *ms,
+		   unsigned int status_code, const char *etag,
+		   const char *datastr)
+{
+	struct MHD_Response	*resp;
+	size_t datalen = datastr? strlen(datastr): 0;
+
+	resp = MHD_create_response_from_data(datalen,(void *)datastr,
+					     MHD_NO,MHD_NO);
+	if (!resp) {
+		return MHD_NO;
+	}
+
+	/*
+	 * This special-casing seems asking for some kind of a generic header
+	 * list, but every time we try to write it, it comes out even worse.
+	 */
+	if (ms && ms->auth_hdr) {
+		MHD_add_response_header(resp,"WWW-Authenticate",ms->auth_hdr);
+	}
+	if (etag) {
+		MHD_add_response_header(resp,"ETag",etag);
+	}
+
+	MHD_queue_response(conn,status_code,resp);
+	MHD_destroy_response(resp);
+	return MHD_YES;
+}
+
 /**********
  * The proxy has MHD on one side and CURL on the other.  The CURL side is
  * always run in a child thread.  Yes, there are both context switches
@@ -307,11 +403,7 @@ proxy_get_data (void *cctx, struct MHD_Connection *conn, const char *url,
 			conn, MHD_HEADER_KIND, "If-None-Match");
 		if (user_etag && !strcmp(user_etag,my_etag)) {
 			DPRINTF("ETag match!\n");
-			resp = MHD_create_response_from_data(0,NULL,
-				MHD_NO,MHD_NO);
-			MHD_queue_response(conn,MHD_HTTP_NOT_MODIFIED,resp);
-			MHD_destroy_response(resp);
-			return MHD_YES;
+			return do_resp(conn,ms,MHD_HTTP_NOT_MODIFIED,NULL,NULL);
 		}
 		ms->from_master = 0;
 	}
@@ -319,11 +411,7 @@ proxy_get_data (void *cctx, struct MHD_Connection *conn, const char *url,
 		DPRINTF("%s/%s not found locally\n",ms->bucket,ms->key);
 		if (!master_host) {
 			DPRINTF("  that means it doesn't exist\n");
-			resp = MHD_create_response_from_data(0,NULL,
-				MHD_NO,MHD_NO);
-			MHD_queue_response(conn,MHD_HTTP_NOT_FOUND,resp);
-			MHD_destroy_response(resp);
-			return MHD_YES;
+			return do_resp(conn,ms,MHD_HTTP_NOT_FOUND,NULL,NULL);
 		}
 		DPRINTF("  will fetch from %s:%u\n", master_host,master_port);
 		ms->from_master = 1;
@@ -369,6 +457,9 @@ proxy_get_data (void *cctx, struct MHD_Connection *conn, const char *url,
 		child_closer(pp);
 		return MHD_NO;
 	}
+	if (ms->auth_hdr) {
+		MHD_add_response_header(resp,"WWW-Authenticate",ms->auth_hdr);
+	}
 	MHD_queue_response(conn,ms->rc,resp);
 	MHD_destroy_response(resp);
 
@@ -419,7 +510,6 @@ proxy_put_data (void *cctx, struct MHD_Connection *conn, const char *url,
 		const char *method, const char *version, const char *data,
 		size_t *data_size, void **rctx)
 {
-	struct MHD_Response	*resp;
 	my_state		*ms	= *rctx;
 	int			 rc;
 
@@ -432,14 +522,7 @@ proxy_put_data (void *cctx, struct MHD_Connection *conn, const char *url,
 	if (ms->state == MS_NEW) {
 		if (!validate_put(conn) || !validate_url(url)) {
 			DPRINTF("rejecting %s\n",url);
-			resp = MHD_create_response_from_data(0,NULL,
-				MHD_NO,MHD_NO);
-			if (!resp) {
-				return MHD_NO;
-			}
-			MHD_queue_response(conn,MHD_HTTP_FORBIDDEN,resp);
-			MHD_destroy_response(resp);
-			return MHD_YES;
+			return do_resp(conn,ms,MHD_HTTP_FORBIDDEN,NULL,NULL);
 		}
 		ms->state = MS_NORMAL;
 		ms->url = strdup(url);
@@ -474,14 +557,8 @@ proxy_put_data (void *cctx, struct MHD_Connection *conn, const char *url,
 		rc = pipe_prod_wait_init(&ms->pipe);
 		if (rc != 0) {
 			DPRINTF("producer wait failed\n");
-			resp = MHD_create_response_from_data(0,NULL,
-				MHD_NO,MHD_NO);
-			if (!resp) {
-				return MHD_NO;
-			}
-			MHD_queue_response(conn,MHD_HTTP_INTERNAL_SERVER_ERROR,
-				resp);
-			MHD_destroy_response(resp);
+			return do_resp(conn,ms,MHD_HTTP_INTERNAL_SERVER_ERROR,
+				       NULL,NULL);
 		} else if (rc > 0) {
 			/*
 			 * Note that we fail here even if 1 of N replicas fail.
@@ -490,14 +567,8 @@ proxy_put_data (void *cctx, struct MHD_Connection *conn, const char *url,
 			 */
 			DPRINTF("producer replicas failed (%u of %u)\n",
 				rc, ms->pipe.cons_total);
-			resp = MHD_create_response_from_data(0,NULL,
-				MHD_NO,MHD_NO);
-			if (!resp) {
-				return MHD_NO;
-			}
-			MHD_queue_response(conn,MHD_HTTP_INTERNAL_SERVER_ERROR,
-				resp);
-			MHD_destroy_response(resp);
+			return do_resp(conn,ms,MHD_HTTP_INTERNAL_SERVER_ERROR,
+				       NULL,NULL);
 		} else {
 			DPRINTF("producer proceeding\n");
 		}
@@ -535,15 +606,7 @@ proxy_put_data (void *cctx, struct MHD_Connection *conn, const char *url,
 			recheck_replication(ms,NULL);
 			rc = MHD_HTTP_CREATED;
 		}
-		resp = MHD_create_response_from_data(0,NULL,MHD_NO,MHD_NO);
-		if (!resp) {
-			return MHD_NO;
-		}
-		if (etag) {
-			MHD_add_response_header(resp,"ETag",etag);
-		}
-		MHD_queue_response(conn,rc,resp);
-		MHD_destroy_response(resp);
+		return do_resp(conn,ms,rc,etag,NULL);
 	}
 
 	return MHD_YES;
@@ -576,14 +639,11 @@ proxy_list_attrs (void *cctx, struct MHD_Connection *conn, const char *url,
 			 * means a request-specific routine (e.g. show_parts)
 			 * created its own response.  Therefore we shouldn't.
 			 */
-			struct MHD_Response *resp
-			  = MHD_create_response_from_data(0,NULL, MHD_NO,MHD_NO);
-			if (!resp) {
+			if (do_resp(conn,ms,rc,NULL,NULL) != MHD_YES) {
 				fprintf(stderr,"MHD_crfd failed\n");
 				return MHD_NO;
 			}
-			MHD_queue_response(conn,rc,resp);
-			MHD_destroy_response(resp);
+			return MHD_YES;
 		}
 	}
 
@@ -595,10 +655,8 @@ proxy_get_attr (void *cctx, struct MHD_Connection *conn, const char *url,
 		const char *method, const char *version, const char *data,
 		size_t *data_size, void **rctx)
 {
-	struct MHD_Response	*resp;
 	char			*fixed;
 	my_state		*ms	= *rctx;
-	int			 rc	= MHD_HTTP_NOT_FOUND;
 
 	(void)cctx;
 	(void)method;
@@ -609,20 +667,11 @@ proxy_get_attr (void *cctx, struct MHD_Connection *conn, const char *url,
 	DPRINTF("PROXY GET ATTR %s\n",url);
 
 	if (meta_get_value(ms->bucket,ms->key,ms->attr,&fixed) == 0) {
-		resp = MHD_create_response_from_data(strlen(fixed),fixed,
-			MHD_NO,MHD_NO);
-		rc = MHD_HTTP_OK;
+		return do_resp(conn,ms,MHD_HTTP_OK,NULL,fixed);
 	}
 	else {
-		resp = MHD_create_response_from_data(0,NULL,MHD_NO,MHD_NO);
+		return do_resp(conn,ms,MHD_HTTP_NOT_FOUND,NULL,NULL);
 	}
-	if (!resp) {
-		return MHD_NO;
-	}
-	MHD_queue_response(conn,rc,resp);
-	MHD_destroy_response(resp);
-
-	return MHD_YES;
 }
 
 static int
@@ -630,7 +679,6 @@ proxy_put_attr (void *cctx, struct MHD_Connection *conn, const char *url,
 		const char *method, const char *version, const char *data,
 		size_t *data_size, void **rctx)
 {
-	struct MHD_Response	*resp;
 	my_state		*ms	= *rctx;
 	int			 send_resp = 0;
 
@@ -679,15 +727,7 @@ proxy_put_attr (void *cctx, struct MHD_Connection *conn, const char *url,
 			return MHD_NO;
 		}
 		if (is_reserved(ms->attr,reserved_attr)) {
-			resp = MHD_create_response_from_data(
-				0,NULL,MHD_NO,MHD_NO);
-			if (!resp) {
-				return MHD_NO;
-			}
-			MHD_queue_response(conn,MHD_HTTP_BAD_REQUEST,
-				resp);
-			MHD_destroy_response(resp);
-			return MHD_YES;
+			return do_resp(conn,ms,MHD_HTTP_BAD_REQUEST,NULL,NULL);
 		}
 		meta_set_value(ms->bucket,ms->key,ms->attr,ms->pipe.data_ptr);
 		/*
@@ -700,12 +740,7 @@ proxy_put_attr (void *cctx, struct MHD_Connection *conn, const char *url,
 	}
 
 	if (send_resp) {
-		resp = MHD_create_response_from_data(0,NULL,MHD_NO,MHD_NO);
-		if (!resp) {
-			return MHD_NO;
-		}
-		MHD_queue_response(conn,MHD_HTTP_CREATED,resp);
-		MHD_destroy_response(resp);
+		return do_resp(conn,ms,MHD_HTTP_CREATED,NULL,NULL);
 		/*
 		 * TBD: check if the attribute was a replication policy, and
 		 * start/stop replication activities as appropriate.
@@ -930,11 +965,7 @@ proxy_query (void *cctx, struct MHD_Connection *conn, const char *url,
 		}
 		int rc = proxy_query_init(ms, ms->pipe.data_ptr);
 		if (rc != MHD_HTTP_OK) {
-			resp = MHD_create_response_from_data(0,NULL,
-				MHD_NO,MHD_NO);
-			MHD_queue_response(conn,rc,resp);
-			MHD_destroy_response(resp);
-			return MHD_YES;
+			return do_resp(conn,ms,rc,NULL,NULL);
 		}
 		resp = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN,
 			CB_BLOCK_SIZE, proxy_query_func, ms, simple_closer);
@@ -943,6 +974,10 @@ proxy_query (void *cctx, struct MHD_Connection *conn, const char *url,
 			simple_closer(ms);
 			return MHD_NO;
 		}
+		if (ms->auth_hdr) {
+			MHD_add_response_header(resp,"WWW-Authenticate",
+						ms->auth_hdr);
+		}
 		MHD_queue_response(conn,MHD_HTTP_OK,resp);
 		MHD_destroy_response(resp);
 	}
@@ -967,10 +1002,7 @@ proxy_list_objs (void *cctx, struct MHD_Connection *conn, const char *url,
 
 	int rc = proxy_query_init(ms, NULL);
 	if (rc != MHD_HTTP_OK) {
-		resp = MHD_create_response_from_data(0,NULL, MHD_NO,MHD_NO);
-		MHD_queue_response(conn,rc,resp);
-		MHD_destroy_response(resp);
-		return MHD_YES;
+		return do_resp(conn,ms,rc,NULL,NULL);
 	}
 	resp = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN,
 		CB_BLOCK_SIZE, proxy_query_func, ms, simple_closer);
@@ -979,7 +1011,9 @@ proxy_list_objs (void *cctx, struct MHD_Connection *conn, const char *url,
 		simple_closer(ms);
 		return MHD_NO;
 	}
-
+	if (ms->auth_hdr) {
+		MHD_add_response_header(resp,"WWW-Authenticate", ms->auth_hdr);
+	}
 	MHD_queue_response(conn,MHD_HTTP_OK,resp);
 	MHD_destroy_response(resp);
 	return MHD_YES;
@@ -991,7 +1025,6 @@ proxy_delete (void *cctx, struct MHD_Connection *conn, const char *url,
 	      size_t *data_size, void **rctx)
 {
 	my_state		*ms	= *rctx;
-	struct MHD_Response	*resp;
 	char			*copied_url;
 	char			*bucket;
 	char			*key;
@@ -1019,15 +1052,7 @@ proxy_delete (void *cctx, struct MHD_Connection *conn, const char *url,
 		replicate_delete(url,ms);
 	}
 
-	resp = MHD_create_response_from_data(0,NULL,MHD_NO,MHD_NO);
-	if (!resp) {
-		return MHD_NO;
-	}
-	error (0, 0, "DELETE BUCKET: rc=%d", rc);
-	MHD_queue_response(conn,rc,resp);
-	MHD_destroy_response(resp);
-
-	return MHD_YES;
+	return do_resp(conn,ms,rc,NULL,NULL);
 }
 
 /* TBD: get actual bucket list */
@@ -1153,11 +1178,13 @@ proxy_api_root (void *cctx, struct MHD_Connection *conn, const char *url,
 	if (!resp) {
 		return MHD_NO;
 	}
+	if (ms->auth_hdr) {
+		MHD_add_response_header(resp,"WWW-Authenticate", ms->auth_hdr);
+	}
 	MHD_queue_response(conn,rc,resp);
 	MHD_destroy_response(resp);
 
 	return MHD_YES;
-
 }
 
 static int
@@ -1329,8 +1356,8 @@ control_api_root (void *cctx, struct MHD_Connection *conn, const char *url,
 	}
 
 	if (len >= (int)sizeof(buf)) {
-		len = 0;
-		rc = MHD_HTTP_INTERNAL_SERVER_ERROR;
+		return do_resp(conn,ms,MHD_HTTP_INTERNAL_SERVER_ERROR,
+			       NULL,NULL);
 	}
 
 	/* NB The last arg tells MHD to copy the arg and free it later. */
@@ -1338,6 +1365,9 @@ control_api_root (void *cctx, struct MHD_Connection *conn, const char *url,
 	if (!resp) {
 		return MHD_NO;
 	}
+	if (ms->auth_hdr) {
+		MHD_add_response_header(resp,"WWW-Authenticate", ms->auth_hdr);
+	}
 	MHD_queue_response(conn,rc,resp);
 	MHD_destroy_response(resp);
 
@@ -1349,7 +1379,6 @@ proxy_bucket_post (void *cctx, struct MHD_Connection *conn, const char *url,
 		   const char *method, const char *version, const char *data,
 		   size_t *data_size, void **rctx)
 {
-	struct MHD_Response	*resp;
 	my_state		*ms	= *rctx;
 	char			*key;
 
@@ -1398,13 +1427,10 @@ proxy_bucket_post (void *cctx, struct MHD_Connection *conn, const char *url,
 		else  {
 			DPRINTF("A parameter is MISSING (fail)\n");
 		}
-		resp = MHD_create_response_from_data(0,NULL,MHD_NO,MHD_NO);
-		if (!resp) {
+		if (do_resp(conn,ms,rc,NULL,NULL) != MHD_YES) {
 			fprintf(stderr,"MHD_crfd failed\n");
 			return MHD_NO;
 		}
-		MHD_queue_response(conn,rc,resp);
-		MHD_destroy_response(resp);
 	}
 
 	return MHD_YES;
@@ -1554,6 +1580,9 @@ show_parts (struct MHD_Connection *conn, my_state *ms)
 		simple_closer(ms);
 		return MHD_NO;
 	}
+	if (ms->auth_hdr) {
+		MHD_add_response_header(resp,"WWW-Authenticate", ms->auth_hdr);
+	}
 	MHD_queue_response(conn,MHD_HTTP_OK,resp);
 	MHD_destroy_response(resp);
 	return MHD_HTTP_PROCESSING;
@@ -1564,7 +1593,6 @@ proxy_object_post (void *cctx, struct MHD_Connection *conn, const char *url,
 		   const char *method, const char *version, const char *data,
 		   size_t *data_size, void **rctx)
 {
-	struct MHD_Response	*resp;
 	my_state		*ms	= *rctx;
 	char			*op;
 
@@ -1625,19 +1653,14 @@ proxy_object_post (void *cctx, struct MHD_Connection *conn, const char *url,
 			 * means a request-specific routine (e.g. show_parts)
 			 * created its own response.  Therefore we shouldn't.
 			 */
-			resp = MHD_create_response_from_data(0,NULL,
-				MHD_NO,MHD_NO);
-			if (!resp) {
+			if (do_resp(conn,ms,rc,NULL,NULL) != MHD_YES) {
 				fprintf(stderr,"MHD_crfd failed\n");
 				return MHD_NO;
 			}
-			MHD_queue_response(conn,rc,resp);
-			MHD_destroy_response(resp);
 		}
 	}
 
 	return MHD_YES;
-
 }
 
 /* Derived from gnulib's x2nrealloc.  */
@@ -1844,6 +1867,9 @@ proxy_list_provs (void *cctx, struct MHD_Connection *conn, const char *url,
 		simple_closer(ms);
 		return MHD_NO;
 	}
+	if (ms->auth_hdr) {
+		MHD_add_response_header(resp,"WWW-Authenticate", ms->auth_hdr);
+	}
 	MHD_queue_response(conn,MHD_HTTP_OK,resp);
 	MHD_destroy_response(resp);
 
@@ -1886,6 +1912,7 @@ proxy_primary_prov (void *cctx, struct MHD_Connection *conn, const char *url,
 		    const char *method, const char *version, const char *data,
 		    size_t *data_size, void **rctx)
 {
+	my_state		*ms	= *rctx;
 	(void)cctx;
 	(void)method;
 	(void)version;
@@ -1894,23 +1921,12 @@ proxy_primary_prov (void *cctx, struct MHD_Connection *conn, const char *url,
 	DPRINTF("PROXY GET PRIMARY PROVIDER (%s)\n", url);
 
 	// "/_providers/_primary" is the only one we accept for now.
-	bool valid = strcmp (url, "/_providers/_primary") == 0;
-	unsigned int rc = (valid ? MHD_HTTP_OK : MHD_HTTP_BAD_REQUEST);
-	if (!valid)
+	if (strcmp (url, "/_providers/_primary") != 0) {
 		error (0, 0, _("invalid request: %s"), url);
-
-	const char *name = get_main_provider()->name;
-	struct MHD_Response *resp;
-	resp = MHD_create_response_from_data(valid ? strlen (name) : 0,
-					     valid ? (void *) name : NULL,
-					     MHD_NO, MHD_NO);
-	if (!resp) {
-		return MHD_NO;
+		return do_resp(conn,ms,MHD_HTTP_BAD_REQUEST,NULL,NULL);
 	}
-	MHD_queue_response(conn,rc,resp);
-	MHD_destroy_response(resp);
 
-	return MHD_YES;
+	return do_resp(conn,ms,MHD_HTTP_OK,NULL,get_main_provider()->name);
 }
 
 static int
@@ -1918,6 +1934,7 @@ proxy_set_primary (void *cctx, struct MHD_Connection *conn, const char *url,
 		   const char *method, const char *version, const char *data,
 		   size_t *data_size, void **rctx)
 {
+	my_state		*ms	= *rctx;
 	(void)cctx;
 	(void)method;
 	(void)version;
@@ -1955,16 +1972,7 @@ proxy_set_primary (void *cctx, struct MHD_Connection *conn, const char *url,
 	}
 
  bad_set:;
-
-	struct MHD_Response *resp;
-	resp = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO);
-	if (!resp) {
-		return MHD_NO;
-	}
-	MHD_queue_response(conn,rc,resp);
-	MHD_destroy_response(resp);
-
-	return MHD_YES;
+	return do_resp(conn,ms,rc,NULL,NULL);
 }
 
 static int
@@ -1972,6 +1980,7 @@ proxy_delete_prov (void *cctx, struct MHD_Connection *conn, const char *url,
 		   const char *method, const char *version, const char *data,
 		   size_t *data_size, void **rctx)
 {
+	my_state		*ms	= *rctx;
 	DPRINTF("PROXY DELETE PROVIDER %s\n",url);
 	(void)cctx;
 	(void)method;
@@ -1979,31 +1988,19 @@ proxy_delete_prov (void *cctx, struct MHD_Connection *conn, const char *url,
 	(void)data;
 	(void)data_size;
 
-	struct MHD_Response *resp
-	  = MHD_create_response_from_data(0,NULL,MHD_NO,MHD_NO);
-	if (!resp) {
-		return MHD_NO;
-	}
-
 	char *prov_name = url_to_provider_name (url);
 	provider_t *prov = find_provider (prov_name);
 
+	if (!prov)
+		return do_resp(conn,ms,MHD_HTTP_NOT_FOUND,NULL,NULL);
+
 	// don't allow removal of current main provider.
 	if (prov == get_main_provider())
-		prov = NULL;
-
-	int rc = prov ? MHD_HTTP_OK : MHD_HTTP_NOT_FOUND;
-
-	DPRINTF("PROXY DELETE PROVIDER prov=%s rc=%d\n", prov_name, rc);
-
-	if (prov) {
-		prov->deleted = 1;
-	}
-
-	MHD_queue_response(conn,rc,resp);
-	MHD_destroy_response(resp);
+		return do_resp(conn,ms,MHD_HTTP_BAD_REQUEST,NULL,NULL);
 
-	return MHD_YES;
+	DPRINTF("PROXY DELETE PROVIDER prov=%s rc=%d\n", prov_name,MHD_HTTP_OK);
+	prov->deleted = 1;
+	return do_resp(conn,ms,MHD_HTTP_OK,NULL,NULL);
 }
 
 static int
@@ -2012,7 +2009,6 @@ proxy_add_prov (void *cctx, struct MHD_Connection *conn, const char *url,
 		   size_t *data_size, void **rctx)
 {
 	DPRINTF("PROXY ADD PROVIDER %s\n",url);
-	struct MHD_Response	*resp;
 	my_state		*ms	= *rctx;
 
 	(void)cctx;
@@ -2073,13 +2069,10 @@ proxy_add_prov (void *cctx, struct MHD_Connection *conn, const char *url,
 		}
 
 	add_fail:
-		resp = MHD_create_response_from_data(0,NULL,MHD_NO,MHD_NO);
-		if (!resp) {
+		if (do_resp(conn,ms,rc,NULL,NULL) != MHD_YES) {
 			fprintf(stderr,"MHD_crfd failed\n");
 			return MHD_NO;
 		}
-		MHD_queue_response(conn,rc,resp);
-		MHD_destroy_response(resp);
 	}
 
 	return MHD_YES;
@@ -2102,15 +2095,10 @@ proxy_create_bucket (void *cctx, struct MHD_Connection *conn, const char *url,
 	/* curl -T moo.empty http://localhost:9090/_new   by accident */
 	int rc = create_bucket(ms->bucket,ms);
 
-	struct MHD_Response *resp
-	  = MHD_create_response_from_data(0,NULL, MHD_NO,MHD_NO);
-	if (!resp) {
+	if (do_resp(conn,ms,rc,NULL,NULL) != MHD_YES) {
 		fprintf(stderr,"MHD_crfd failed\n");
 		return MHD_NO;
 	}
-	MHD_queue_response(conn,rc,resp);
-	MHD_destroy_response(resp);
-
 	return MHD_YES;
 }
 
@@ -2216,11 +2204,195 @@ parse_url (const char *url, my_state *ms)
 	else if (eindex == URL_ATTR && !strcmp (parts[URL_ATTR], "_attrs"))
 	  eindex = URL_LIST_ATTRS;
 
-	DPRINTF("parse_url: %d: %s %s %s", eindex, parts[URL_BUCKET],
+	DPRINTF("parse_url: %d: %s %s %s\n", eindex, parts[URL_BUCKET],
 		parts[URL_OBJECT], parts[URL_ATTR]);
 	return eindex;
 }
 
+static bool nego_token(char **pbuf, size_t *plen, const char *val)
+{
+	char *buf;
+	size_t len;
+	const char *p;
+	bool rcb;
+
+	p = val;
+	while (*p == ' ') {
+		if (*p == 0) {
+			return false;
+		}
+		p++;
+	}
+	if (strncmp(p, "Negotiate ", sizeof("Negotiate ")-1) != 0)
+		return false;
+	p += sizeof("Negotiate ")-1;
+	while (*p == ' ') {
+		if (*p == 0) {
+			return false;
+		}
+		p++;
+	}
+
+	len = strlen(p);
+	if (len == 0) {
+		return false;
+	}
+
+	/* actually should be 3/4, but whatever */
+	buf = malloc(len);
+	if (!buf)
+		return false;	// or maybe just abort()?
+
+	rcb = base64_decode(p, len, buf, &len);
+	if (!rcb) {
+		// free(buf);
+		return false;
+	}
+
+	*pbuf = buf;
+	*plen = len;
+	return true;
+}
+
+static int authenticate(struct MHD_Connection *conn, my_state *ms)
+{
+	const char		*val;
+	char			*buf;
+	char			*p;
+	size_t			 len;
+	unsigned int gss_maj, gss_min, dummy_min;
+	gss_ctx_id_t		 gss_ctx = GSS_C_NO_CONTEXT;
+	gss_buffer_desc		 gss_itok;
+	gss_buffer_desc		 gss_otok = GSS_C_EMPTY_BUFFER;
+	gss_name_t		 gss_cli_name = GSS_C_NO_NAME;
+	gss_buffer_desc		 gss_nbuf;
+	gss_cred_id_t		 gss_dcred = GSS_C_NO_CREDENTIAL;
+	struct MHD_Response	*resp;
+	unsigned int		 gss_flags;
+	bool			 rcb;
+	int			 ret;
+
+	val = MHD_lookup_connection_value(conn,MHD_HEADER_KIND,"Authorization");
+	if (!val) {
+		resp = MHD_create_response_from_data(0,NULL,MHD_NO,MHD_NO);
+		if (!resp) {
+			return -1;
+		}
+		MHD_add_response_header(resp,"WWW-Authenticate","Negotiate");
+		MHD_queue_response(conn,MHD_HTTP_UNAUTHORIZED,resp);
+		MHD_destroy_response(resp);
+		return 1;
+	}
+
+	rcb = nego_token(&buf, &gss_itok.length, val);
+	if (!rcb) {
+		do_resp(conn,NULL,MHD_HTTP_BAD_REQUEST,NULL,"400\r\n");
+		return -1;
+	}
+	gss_itok.value = buf;	/* gcc */
+
+	gss_maj = gss_accept_sec_context(&gss_min,
+		&gss_ctx,
+		GSS_C_NO_CREDENTIAL,
+		&gss_itok,
+		GSS_C_NO_CHANNEL_BINDINGS,
+		&gss_cli_name,
+		NULL,
+		&gss_otok,
+		&gss_flags,
+		NULL,
+		&gss_dcred);
+	if (gss_otok.length) {
+		len = ((gss_otok.length+2)/3) * 4;
+		len += sizeof("Negotiate ")-1;
+		buf = malloc(len + 1);
+		if (!buf) {
+			do_resp(conn,ms,MHD_HTTP_INTERNAL_SERVER_ERROR,
+				NULL,NULL);
+			ret = -1;
+			goto out;
+		}
+		strcpy(buf, "Negotiate ");
+		base64_encode(gss_otok.value, gss_otok.length,
+			      buf + (sizeof("Negotiate ")-1),
+			      len - (sizeof("Negotiate ")-1));
+		buf[len] = 0;	/* base64_encode zero-pads if smaller */
+
+		/* GC frees the previous one, right? */
+		ms->auth_hdr = buf;
+	}
+	if (GSS_ERROR(gss_maj)) {
+		if (verbose) {
+			log_gss_error("gss_accept_sec_context", gss_min);
+		}
+		do_resp(conn,ms,MHD_HTTP_FORBIDDEN,NULL,"403\r\n");
+		ret = -1;
+		goto out;
+	}
+
+	if (gss_maj & GSS_S_CONTINUE_NEEDED) {
+		/* Continuations are impossible in Kerberos, right? */
+		error (EXIT_FAILURE, 0, _("gss_accept_sec_context continuing"));
+	}
+
+	gss_maj = gss_display_name(&gss_min, gss_cli_name, &gss_nbuf, NULL);
+	if (GSS_ERROR(gss_maj)) {
+		/*
+		 * We authenticated successfuly, but if we cannot get the
+		 * client name, it is no use: we will not be able to match.
+		 * Have to refuse this. Return 500 for now, this is a problem.
+		 */
+		log_gss_error("gss_display_name error", gss_min);
+		ret = -1;
+		goto out;
+	}
+	p = memchr((char*)gss_nbuf.value,'@',gss_nbuf.length);
+	if (p) {
+		buf = strndup((char*)gss_nbuf.value,p-(char*)gss_nbuf.value);
+		if (!buf) {
+			ret = -1;
+			goto out;
+		}
+	}
+	else {
+		buf = strndup((char*)gss_nbuf.value,gss_nbuf.length);
+		if (!buf) {
+			ret = -1;
+			goto out;
+		}
+	}
+	ms->auth_user = buf;
+	DPRINTF("auth user %s\n", buf);
+
+	ret = 0;
+ out:
+	if (gss_nbuf.length != 0) {
+		gss_release_buffer(&dummy_min, &gss_nbuf);
+	}
+	if (gss_otok.length != 0) {
+		gss_release_buffer(&dummy_min, &gss_otok);
+	}
+	if (gss_ctx != GSS_C_NO_CONTEXT) {
+		gss_delete_sec_context(&dummy_min, &gss_ctx, GSS_C_NO_BUFFER);
+	}
+	if (gss_dcred != GSS_C_NO_CREDENTIAL) {
+		gss_release_cred(&dummy_min, &gss_dcred);
+	}
+	if (gss_cli_name != GSS_C_NO_NAME) {
+		gss_release_name(&dummy_min, &gss_cli_name);
+	}
+	return ret;
+}
+
+static int kv_iter_dump(void *cls, enum MHD_ValueKind kind,
+			const char *key, const char *value)
+{
+	/* ok to use printf when MHD_USE_THREAD_PER_CONNECTION maybe? */
+	printf("%s: %s\n", key, value);
+
+	return MHD_YES;
+}
+
 static int
 access_handler_0 (void *cctx, struct MHD_Connection *conn, const char *url,
 		  const char *method, const char *version, const char *data,
@@ -2228,10 +2400,24 @@ access_handler_0 (void *cctx, struct MHD_Connection *conn, const char *url,
 {
 	unsigned int		 i;
 	url_type		 utype;
-	struct MHD_Response	*resp;
 	my_state		*ms	= *rctx;
+	int			 rc;
+
+	if (verbose >= 2) {
+		printf(">> HTTP\n");
+		rc = MHD_get_connection_values(conn, MHD_HEADER_KIND,
+					       kv_iter_dump, NULL);
+		printf(">> headers: %d\n", rc);
+	}
 
 	if (ms) {
+		if (kerberos) {
+			rc = authenticate(conn, ms);
+			if (rc < 0)
+				return MHD_NO;
+			if (rc > 0)
+				return MHD_YES;
+		}
 		return ms->handler(cctx,conn,url,method,version,
 			data,data_size,rctx);
 	}
@@ -2259,24 +2445,25 @@ access_handler_0 (void *cctx, struct MHD_Connection *conn, const char *url,
 		ms->post	= NULL;
 		ms->conn	= conn;
 		*rctx = ms;
+		if (kerberos) {
+			rc = authenticate(conn, ms);
+			if (rc < 0)
+				return MHD_NO;
+			if (rc > 0)
+				return MHD_YES;
+		}
 		return ms->handler(cctx,conn,url,method,version,
 			data,data_size,rctx);
 	}
 
+	/* This is *unauthenticated*. Remove altogether? TBD */
 	if (!strcmp(method,"QUIT")) {
 		(void)sem_post((sem_t *)cctx);
 		return MHD_NO;
 	}
 
 	fprintf(stderr,"bad request m=%s u=%s\n",method,url);
-
-	resp = MHD_create_response_from_data(0,NULL,MHD_NO,MHD_NO);
-	if (!resp) {
-		return MHD_NO;
-	}
-	MHD_queue_response(conn,MHD_HTTP_NOT_FOUND,resp);
-	MHD_destroy_response(resp);
-	return MHD_YES;
+	return do_resp(conn,ms,MHD_HTTP_NOT_FOUND,NULL,NULL);
 }
 
 static int
@@ -2304,8 +2491,11 @@ static const struct option my_options[] = {
 	{ "autostart", no_argument,     NULL, 'a' },
 	{ "config",  required_argument, NULL, 'c' },
 	{ "db",      required_argument, NULL, 'd' },
+	{ "kerberos", no_argument,      NULL, 'k' },
 	{ "master",  required_argument, NULL, 'm' },
+	{ "krbsvcnm", required_argument, NULL, 'n' },
 	{ "port",    required_argument, NULL, 'p' },
+	{ "krbkeytab", required_argument, NULL, 't' },
 	{ "verbose", no_argument,       NULL, 'v' },
 	{ "version", no_argument,       NULL, GETOPT_VERSION_CHAR },
 	{ "help", no_argument,          NULL, GETOPT_HELP_CHAR },
@@ -2331,8 +2521,11 @@ A configuration file must be specified.\n\
   -a, --autostart         start necessary back-end services\n\
   -c, --config=FILE       config file [required]\n\
   -d, --db=HOST_PORT      database server as ip[:port]\n\
+  -k, --kerberos          enable Kerberos\n\
   -m, --master=HOST_PORT  master (upstream) server as ip[:port]\n\
+  -n, --krbsvcnm=NAME     Kerberos service name (default IWHD)\n\
   -p, --port=PORT         alternate listen port (default 9090)\n\
+  -t, --krbkeytab=FILE    location of Kerberos keytab\n\
   -v, --verbose           verbose/debug output\n\
 \n\
       --help     display this help and exit\n\
@@ -2347,6 +2540,78 @@ Report %s bugs to %s.\n\
   exit (status);
 }
 
+static void gss_init(void)
+{
+	char *gss_sname;
+	gss_buffer_desc gss_nbuf; /* struct */
+	unsigned int gss_maj, gss_min, dummy_min;
+	gss_name_t gss_iname; /* struct* */
+	int rc;
+
+	gss_maj = krb5_gss_register_acceptor_identity(krb_keytab);
+	if (gss_maj != 0)
+		error (EXIT_FAILURE, 0, _("cannot set keytab %s"), krb_keytab);
+
+	if (gethostname(my_hostname, HOST_NAME_MAX) != 0)
+		error (EXIT_FAILURE, errno, _("gethostname"));
+	my_hostname[HOST_NAME_MAX] = 0;
+
+	if (strchr(krb_svc_name, '@') != NULL ||
+	    strchr(krb_svc_name, '/') != NULL) {
+		gss_sname = strdup(krb_svc_name);
+		if (!gss_sname)
+			error (EXIT_FAILURE, 0, _("out of memory"));
+	}
+	else {
+		rc = asprintf(&gss_sname, "%s@%s", krb_svc_name, my_hostname);
+		if (rc < 0)
+			error (EXIT_FAILURE, 0, _("out of memory"));
+	}
+
+	gss_nbuf.length = strlen(gss_sname)+1;
+	gss_nbuf.value = gss_sname;
+
+	gss_maj = gss_import_name(&gss_min, &gss_nbuf,
+				  GSS_C_NT_HOSTBASED_SERVICE, &gss_iname);
+	if (GSS_ERROR(gss_maj)) {
+		log_gss_error("gss_import_name error", gss_min);
+		exit(EXIT_FAILURE);
+	}
+
+	/*
+	 * Why re-import only to print? We just exported the gss_sname, right?
+	 * Well, who knows, maybe something went pear-shaped.
+	 * Not sure what to do with the type OID though, so just NULL it.
+	 */
+	if (verbose) {
+		gss_maj = gss_display_name(&gss_min, gss_iname, &gss_nbuf,NULL);
+		if (GSS_ERROR(gss_maj)) {
+			log_gss_error("gss_display_name error", gss_min);
+			exit(EXIT_FAILURE);
+		}
+		printf("kerberos service: %s\n", (char*)gss_nbuf.value);
+		gss_release_buffer(&dummy_min, &gss_nbuf);
+	}
+
+#if 0 /* RFC says use default */
+	/*
+	 * The GSS_C_INDEFINITE does not yield a credential with an
+	 * indefinite lifetime, merely "the maximum permitted lifetime".
+	 * So, this function has to be called from the main loop somewhere. XXX
+	 * Then, we need to call gss_release_cred(), too.
+	 */
+	gss_maj = gss_acquire_cred(&gss_min, gss_iname, GSS_C_INDEFINITE,
+				   GSS_C_NO_OID_SET, GSS_C_ACCEPT, &gss_creds,
+				   NULL, NULL);
+	if (GSS_ERROR(gss_maj)) {
+		log_gss_error("gss_acquire_cred error", gss_min);
+		exit(EXIT_FAILURE);
+	}
+#endif
+
+	gss_release_name(&dummy_min, &gss_iname);
+	// free(gss_sname); -- don't crash GC
+}
 
 int
 main (int argc, char **argv)
@@ -2356,7 +2621,7 @@ main (int argc, char **argv)
 	char			*stctx = NULL;
 	char			*port_tmp;
 	bool			 autostart = false;
-	char *cfg_file = NULL;
+	char			*cfg_file = NULL;
 
 	set_program_name (argv[0]);
 	setlocale (LC_ALL, "");
@@ -2367,7 +2632,9 @@ main (int argc, char **argv)
 
 	GC_INIT ();
 
-	for (;;) switch (getopt_long(argc,argv,"ac:d:m:p:v",my_options,NULL)) {
+	for (;;)
+	switch (getopt_long(argc,argv,"ac:d:km:n:p:t:v",my_options,NULL))
+	{
 	case 'a':
 		autostart = true;
 		break;
@@ -2382,6 +2649,9 @@ main (int argc, char **argv)
 			db_port = (unsigned short)strtoul(port_tmp,NULL,10);
 		}
 		break;
+	case 'k':
+		kerberos = true;
+		break;
 	case 'm':
 		assert (optarg);
 		master_host = strtok_r(optarg,":",&stctx);
@@ -2390,9 +2660,15 @@ main (int argc, char **argv)
 			master_port = (unsigned short)strtoul(port_tmp,NULL,10);
 		}
 		break;
+	case 'n':
+		krb_svc_name = optarg;
+		break;
 	case 'p':
 		my_port = (unsigned short)strtoul(optarg,NULL,10);
 		break;
+	case 't':
+		krb_keytab = optarg;
+		break;
 	case 'v':
 		++verbose;
 		break;
@@ -2445,6 +2721,9 @@ args_done:
 		usage (EXIT_FAILURE);
 	}
 
+	if (kerberos)
+		gss_init();
+
 	sem_init(&the_sem,0,0);
 
 	if (verbose) {
diff --git a/state_defs.h b/state_defs.h
index 30bebb9..0f7904a 100644
--- a/state_defs.h
+++ b/state_defs.h
@@ -47,6 +47,8 @@ typedef struct _my_state {
 	/* for everyone */
 	MHD_AccessHandlerCallback	 handler;
 	ms_state			 state;
+	char				*auth_hdr;
+	char				*auth_user;
 	/* for proxy ops */
 	char				*url;
 	char				 bucket[MAX_FIELD_LEN];
diff --git a/t/.gitignore b/t/.gitignore
new file mode 100644
index 0000000..a58f1a1
--- /dev/null
+++ b/t/.gitignore
@@ -0,0 +1 @@
+gssapi_test.so
diff --git a/t/Makefile.am b/t/Makefile.am
index 133de13..69dd36d 100644
--- a/t/Makefile.am
+++ b/t/Makefile.am
@@ -22,11 +22,13 @@ TESTS =						\
   provider					\
   replication					\
   auto						\
-  registration
+  registration					\
+  kerberos
 
 lock_dir = $(abs_builddir)/lock-dir
 clean-local:
 	$(AM_V_GEN)rm -rf "$(lock_dir)"
+	$(AM_V_GEN)rm -f gssapi_test.so
 
 .PHONY: prereq
 prereq:
@@ -34,10 +36,16 @@ prereq:
 
 $(TEST_LOGS): prereq
 
+gssapi_test.so:  $(srcdir)/gssapi_test.c
+	gcc -Wall -O2 -fpic -shared -o gssapi_test.so $<
+
+kerberos:  gssapi_test.so
+
 EXTRA_DIST =					\
   $(TESTS)					\
   init.cfg					\
-  init.sh
+  init.sh					\
+  gssapi_test.c
 
 TESTS_ENVIRONMENT =				\
   tmp__=$$TMPDIR; test -d "$$tmp__" || tmp__=.; \
diff --git a/t/auto b/t/auto
index dee4c4e..c9f18ab 100644
--- a/t/auto
+++ b/t/auto
@@ -3,9 +3,9 @@
 
 . "${srcdir=.}/init.sh"; path_prepend_ ..
 
-mkdir iwhd || framework_failure_ mkdir failed
+mkdir FS mongod iwhd || framework_failure_ mkdir failed
 
-port=9094
+port=$(get_port 9095 $lock_dir/i-) || fail_ "failed to get iwhd port"
 m_port=27018 # auto-start default
 
 printf '[{"path": "FS", "type": "fs", "name": "primary"}]\n' \
diff --git a/t/gssapi_test.c b/t/gssapi_test.c
new file mode 100644
index 0000000..974fd97
--- /dev/null
+++ b/t/gssapi_test.c
@@ -0,0 +1,261 @@
+/* Copyright (C) 2011 Red Hat, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* #include <config.h> */
+
+#include <sys/types.h>
+#include <pwd.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <gssapi/gssapi.h>
+/* #include <gssapi/gssapi_generic.h> */
+#include <gssapi/gssapi_krb5.h>
+
+struct gss_name_struct {
+	const char *name;
+};
+
+struct gss_ctx_id_struct {
+	unsigned char tag[4];
+};
+
+static const unsigned char tag0[4] = { 'A', 'b', '5', '3' };
+
+static struct gss_name_struct *new_name(char *p, size_t len)
+{
+	struct gss_name_struct *ret;
+	char *nm;
+
+	ret = malloc(sizeof(struct gss_name_struct));
+	if (!ret) {
+		return NULL;
+	}
+
+	nm = strndup(p, len);
+	if (!nm) {
+		free(ret);
+		return NULL;
+	}
+
+	ret->name = nm;
+	return ret;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_import_name(
+    OM_uint32 *minor_status,
+    gss_buffer_t input_name_buffer,
+    gss_OID input_name_type,
+    gss_name_t *output_name)
+{
+	struct gss_name_struct *out;
+
+	out = new_name(input_name_buffer->value, input_name_buffer->length);
+	if (!out) {
+		*minor_status = 1;
+		return GSS_S_FAILURE;
+	}
+	*output_name = out;
+	return 0;
+	// return GSS_S_CALL_BAD_STRUCTURE;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_display_status(
+    OM_uint32 *minor_status,
+    OM_uint32 status_value,
+    int status_type,
+    gss_OID mech_type,
+    OM_uint32 *message_context,
+    gss_buffer_t status_string)
+{
+	char *out;
+
+	out = malloc(30);
+	if (!out) {
+		/* This is so going to crash in the app later... */
+		status_string->value = NULL;
+		*message_context = 0;
+		*minor_status = 1;
+		return GSS_S_FAILURE;
+	}
+	snprintf(out, 30, "maj=0x%x", status_value);
+	status_string->value = out;
+	status_string->length = strlen(out);
+	*message_context = 0;
+	*minor_status = 0;
+	return GSS_S_COMPLETE;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_display_name(
+    OM_uint32 *minor_status,
+    gss_name_t input_name,
+    gss_buffer_t output_name_buffer,
+    gss_OID *output_name_type)
+{
+	char *out;
+
+	out = strdup(input_name->name);
+	if (!out) {
+		output_name_buffer->value = NULL;
+		output_name_buffer->length = 0;
+		*minor_status = 1;
+		return GSS_S_FAILURE;
+	}
+
+	output_name_buffer->value = out;
+	output_name_buffer->length = strlen(out);
+	return 0;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_init_sec_context(
+    OM_uint32 *minor_status,
+    gss_cred_id_t claimant_cred_handle,
+    gss_ctx_id_t *context_handle,	/* return this (later) */
+    gss_name_t target_name,
+    gss_OID mech_type,
+    OM_uint32 req_flags,
+    OM_uint32 time_req,
+    gss_channel_bindings_t input_chan_bindings,
+    gss_buffer_t input_token,
+    gss_OID *actual_mech_type,
+    gss_buffer_t output_token,		/* return this as necessary */
+    OM_uint32 *ret_flags,
+    OM_uint32 *time_rec)
+{
+	unsigned char *obuf;
+	struct gss_ctx_id_struct *ctx;
+	char pwbuf[200];
+	struct passwd pwb, *pw;
+	size_t len;
+	int rc;
+
+	rc = getpwuid_r(getuid(), &pwb, pwbuf, sizeof(pwbuf), &pw);
+	if (rc != 0 || pw == NULL) {
+		*minor_status = 1;
+		return GSS_S_FAILURE;
+	}
+
+	/* Kerberos has no server challenge (input_token), so ignore it. */
+
+	ctx = *context_handle;
+	if (!ctx) {
+		ctx = malloc(sizeof(struct gss_ctx_id_struct));
+		if (!ctx) {
+			*minor_status = 1;
+			return GSS_S_FAILURE;
+		}
+		memcpy(ctx->tag, tag0, 4);
+		*context_handle = ctx;
+	}
+
+	len = strlen(pw->pw_name);
+	obuf = malloc(4 + len);
+	if (!obuf) {
+		free(ctx);
+		*minor_status = 2;
+		return GSS_S_FAILURE;
+	}
+	memcpy(obuf, tag0, 4);
+	memcpy(obuf+4, pw->pw_name, len);
+	output_token->value = obuf;
+	output_token->length = 4 + len;
+
+	return 0;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_accept_sec_context(
+    OM_uint32 *minor_status,
+    gss_ctx_id_t *context_handle,
+    gss_cred_id_t acceptor_cred_handle,
+    gss_buffer_t input_token_buffer,
+    gss_channel_bindings_t input_chan_bindings,
+    gss_name_t *src_name,
+    gss_OID *mech_type,
+    gss_buffer_t output_token,
+    OM_uint32 *ret_flags,
+    OM_uint32 *time_rec,
+    gss_cred_id_t *delegated_cred_handle)
+{
+	struct gss_name_struct *out;
+
+	if (input_token_buffer->length <= 4) {
+		*minor_status = 0;
+		return GSS_S_DEFECTIVE_TOKEN;	/* 0x90000 */
+	}
+	if (memcmp(input_token_buffer->value, tag0, 4) != 0) {
+		*minor_status = 1;
+		return GSS_S_DEFECTIVE_TOKEN;	/* 0x90000 */
+	}
+
+	out = new_name(input_token_buffer->value + 4,
+		       input_token_buffer->length - 4);
+	if (!out) {
+		*minor_status = 1;
+		return GSS_S_FAILURE;
+	}
+
+	*src_name = out;
+	return GSS_S_COMPLETE;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_delete_sec_context(
+    OM_uint32 *minor_status,
+    gss_ctx_id_t *context_handle,
+    gss_buffer_t output_token)
+{
+	/* We leak on purpose: afraid to crash with GC. */
+	return 0;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_release_buffer(
+    OM_uint32 *minstat,	/* minor_status */
+    gss_buffer_t buf)	/* buffer */
+{
+	return 0;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_release_name(
+    OM_uint32 *minstat,	/* minor_status */
+    gss_name_t *name)	/* input_name */
+{
+	return 0;
+}
+
+#if 0
+OM_uint32 KRB5_CALLCONV
+gss_acquire_cred(
+    OM_uint32 *,        /* minor_status */
+    gss_name_t,         /* desired_name */
+    OM_uint32,          /* time_req */
+    gss_OID_set,        /* desired_mechs */
+    gss_cred_usage_t,   /* cred_usage */
+    gss_cred_id_t *,    /* output_cred_handle */
+    gss_OID_set *,      /* actual_mechs */
+    OM_uint32 *);       /* time_rec */
+#endif
+
+OM_uint32 KRB5_CALLCONV krb5_gss_register_acceptor_identity(const char *fname)
+{
+	/* Just ignore the keytab for the moment. */
+	return 0;
+}
diff --git a/t/kerberos b/t/kerberos
new file mode 100644
index 0000000..b42158d
--- /dev/null
+++ b/t/kerberos
@@ -0,0 +1,52 @@
+#!/bin/sh
+# Test authentication functionality.
+
+. "${srcdir=.}/init.sh"; path_prepend_ ..
+
+mkdir FS mongod iwhd || framework_failure_ mkdir failed
+
+# This actually mostly helps against weirdness with global variables.
+# The makefile ensures that we only ever get here with the .so built.
+test -f ${abs_top_builddir}/t/gssapi_test.so \
+  || fail_ "no $abs_top_builddir/t/gssapi_test.so"
+#test -f ./t/gssapi_test.so || fail_ "no ./t/gssapi_test.so"
+
+m_port=$(get_port $mongo_base_port $lock_dir/m-) \
+  || fail_ "failed to get mongodb port"
+
+mongod --port $m_port --pidfilepath mongod/pid --dbpath mongod > mongod.log 2>&1 &
+mongo_pid=$!
+cleanup_() { kill -9 $mongo_pid; }
+
+# Wait for up to 5 seconds for mongod to begin listening.
+wait_for .1 50 'mongo localhost:$m_port < /dev/null' \
+  || framework_failure_ mongod failed to start
+
+port=$(get_port 9095 $lock_dir/i-) || fail_ "failed to get iwhd port"
+
+ulimit -c unlimited
+
+printf '[{"path": "FS", "type": "fs", "name": "primary"}]\n' \
+  > iwhd.cfg || fail=1
+
+LD_PRELOAD=${abs_top_builddir}/t/gssapi_test.so iwhd -v -k \
+  -p $port -c iwhd.cfg -d localhost:$m_port &
+iwhd_pid=$!
+cleanup_() { kill -9 $mongo_pid; kill $iwhd_pid; }
+
+## Wait for up to 5 seconds for iwhd to begin listening on $port.
+#wait_for .1 50 "curl -s http://localhost:$port" \
+#  || { echo iwhd failed to listen; Exit 1; }
+sleep 5
+
+fail=0
+
+# LD_PRELOAD=/q/zaitcev/hail/iwhd-tip/t/gssapi_test.so curl -0 -v --negotiate -u : http://localhost:9090/
+# We use -0 because of a mysterious problem with curl attempting to reuse
+# the server connection and failing in a spectacular fashion. Perhaps iwhd
+# does not implement HTTP 1.1 pipelining properly.
+LD_PRELOAD=${abs_top_builddir}/t/gssapi_test.so curl -0 -v \
+  --negotiate -u : http://localhost:$port/ \
+  || fail=1
+
+Exit $fail


More information about the iwhd-devel mailing list