WIP Kerberized iwhd

Pete Zaitcev zaitcev at redhat.com
Mon Jun 27 23:09:56 UTC 2011


Dear Jim:

If I understood it right, you expressed interest in patches that do not
work... and this one does not. Still needs a way to set up a header for
all future transfers. But I think generally this is how it is going to
look.

Please let me know if you see anything terrible.

Cheers,
-- Pete

diff --git a/configure.ac b/configure.ac
index 8945f7c..9820033 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])
@@ -85,6 +88,12 @@ AC_CHECK_HEADER([mongo/client/dbclient.h], ,
   [#undef VERSION])
 AC_LANG_POP
 
+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..0826176 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};
@@ -2221,6 +2288,160 @@ parse_url (const char *url, my_state *ms)
 	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)
+{
+	const char		*val;
+	char			*buf;
+	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_cred_id_t		 gss_dcred = GSS_C_NO_CREDENTIAL;
+	struct MHD_Response	*resp;
+	unsigned int		 gss_flags;
+	bool			 rcb;
+
+	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;
+	}
+
+	len = strlen(val);
+/* P3 */ printf("[%ld] %s\n", (long)len, val);
+
+	rcb = nego_token(&buf, &gss_itok.length, val);
+	if (!rcb) {
+/* P3 */ printf("failed to extract token\n");
+		resp = MHD_create_response_from_data(0,NULL,MHD_NO,MHD_NO);
+		if (!resp) {
+			return -1;
+		}
+		MHD_queue_response(conn,MHD_HTTP_BAD_REQUEST,resp);
+		MHD_destroy_response(resp);
+		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) {
+		/* has something to send - put into WWW-Authenticate */
+		/* even if it was error */
+		/* XXX */
+	}
+	if (GSS_ERROR(gss_maj)) {
+		if (verbose) {
+			log_gss_error("gss_accept_sec_context", gss_min);
+		}
+		goto err_out;
+	}
+
+	if (gss_maj & GSS_S_CONTINUE_NEEDED) {
+		/* not in Kerberos */
+		error (EXIT_FAILURE, 0, _("gss_accept_sec_context continuing"));
+	}
+
+	gss_delete_sec_context(&dummy_min, &gss_ctx, GSS_C_NO_BUFFER);
+	return 0;
+
+ err_out:
+	resp = MHD_create_response_from_data(3,"403",MHD_NO,1);
+	if (!resp) {
+		/* XXX rc=-1; goto bail_out; */
+		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);
+		}
+		return -1;
+	}
+	MHD_queue_response(conn,MHD_HTTP_FORBIDDEN,resp);
+	MHD_destroy_response(resp);
+
+	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);
+	}
+	return 1;
+}
+
+/* P3 */ /* actually maybe ok to guard with verbose >= 2 */
+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,
@@ -2230,6 +2451,18 @@ access_handler_0 (void *cctx, struct MHD_Connection *conn, const char *url,
 	url_type		 utype;
 	struct MHD_Response	*resp;
 	my_state		*ms	= *rctx;
+	int			 rc;
+
+/* P3 */ printf(">> HTTP\n");
+/* P3 */ printf("headers: %d\n", MHD_get_connection_values(conn, MHD_HEADER_KIND, kv_iter_dump, NULL));
+
+	if (kerberos) {
+		rc = authenticate(conn);
+		if (rc < 0)
+			return MHD_NO;
+		if (rc > 0)
+			return MHD_YES;
+	}
 
 	if (ms) {
 		return ms->handler(cctx,conn,url,method,version,
@@ -2304,8 +2537,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,6 +2567,7 @@ 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, --krbsvc=SVC_NAME   Kerberos service name\n\
   -m, --master=HOST_PORT  master (upstream) server as ip[:port]\n\
   -p, --port=PORT         alternate listen port (default 9090)\n\
   -v, --verbose           verbose/debug output\n\
@@ -2347,6 +2584,77 @@ 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 +2664,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 +2675,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 +2692,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 +2703,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 +2764,9 @@ args_done:
 		usage (EXIT_FAILURE);
 	}
 
+	if (kerberos)
+		gss_init();
+
 	sem_init(&the_sem,0,0);
 
 	if (verbose) {


More information about the iwhd-devel mailing list