[elinks-dev] [patch]: negotiate-auth

Karel Zak kzak at redhat.com
Tue Jun 13 16:49:40 PDT 2006


 Hi guys,

 here is a patch with negotiate-auth support based on GSSAPI. It's
 very useful feature for intranet web sites where is possible
 authenticate users by Kerberos. (This feature is already implemented
 in firefox and libcurl.)

    Karel



 Makefile.config.in                 |    1 
 configure.in                       |   24 +++
 src/protocol/http/Makefile         |    2 
 src/protocol/http/http.c           |   46 ++++--
 src/protocol/http/http_negotiate.c |  287 ++++++++++++++++++++++++++++++++++++
 src/protocol/http/http_negotiate.h |   16 ++
 src/util/base64.c                  |   36 ++++-
 src/util/base64.h                  |    3 
 8 files changed, 398 insertions(+), 17 deletions(-)
 create mode 100644 src/protocol/http/http_negotiate.c
 create mode 100644 src/protocol/http/http_negotiate.h

diff --git a/Makefile.config.in b/Makefile.config.in
index 4ab2bf2..8854477 100644
--- a/Makefile.config.in
+++ b/Makefile.config.in
@@ -158,6 +158,7 @@ CONFIG_SYSMOUSE = @CONFIG_SYSMOUSE@
 CONFIG_URI_REWRITE = @CONFIG_URI_REWRITE@
 CONFIG_XBEL_BOOKMARKS = @CONFIG_XBEL_BOOKMARKS@
 CONFIG_XMLTO = @CONFIG_XMLTO@
+CONFIG_GSSAPI = @CONFIG_GSSAPI@
 
 DEFS = @DEFS@
 CFLAGS = @CFLAGS@
diff --git a/configure.in b/configure.in
index 024ebc3..ad2831d 100644
--- a/configure.in
+++ b/configure.in
@@ -453,6 +453,30 @@ fi
 EL_ARG_ENABLE(CONFIG_LZMA, lzma, [lzma],
 	      [  --enable-lzma           enable lzma encoding support])
 dnl ===================================================================
+dnl Check for GSSAPI, optional even if installed.
+dnl ===================================================================
+
+enable_gssapi="no";
+
+AC_ARG_WITH(gssapi, [  --with-gssapi           enable GSSAPI support],
+            [ if test "x$withval" != xno; then enable_gssapi=yes; fi ])
+
+AC_MSG_CHECKING([for GSSAPI])
+
+if test "$enable_gssapi" = "yes"; then
+	AC_MSG_RESULT(yes)
+	GSSAPI_CFLAGS=`krb5-config --cflags gssapi`
+	GSSAPI_LIBS=`krb5-config --libs gssapi`
+	CFLAGS="$GSSAPI_CFLAGS $CFLAGS"
+	LIBS="$GSSAPI_LIBS $LIBS"
+ 	EL_CONFIG(CONFIG_GSSAPI, [GssApi])
+else
+	AC_MSG_RESULT(no)
+fi
+
+AC_SUBST(CONFIG_GSSAPI)
+
+dnl ===================================================================
 dnl Bookmark and XBEL support
 dnl ===================================================================
 
diff --git a/src/protocol/http/Makefile b/src/protocol/http/Makefile
index 10ffb86..fb4dfa0 100644
--- a/src/protocol/http/Makefile
+++ b/src/protocol/http/Makefile
@@ -1,6 +1,8 @@
 top_builddir=../../..
 include $(top_builddir)/Makefile.config
 
+OBJS-$(CONFIG_GSSAPI)	+= http_negotiate.o
+
 OBJS = blacklist.o codes.o http.o
 
 include $(top_srcdir)/Makefile.lib
diff --git a/src/protocol/http/http.c b/src/protocol/http/http.c
index d05a99d..aa9c8fb 100644
--- a/src/protocol/http/http.c
+++ b/src/protocol/http/http.c
@@ -47,6 +47,9 @@ #include "util/conv.h"
 #include "util/memory.h"
 #include "util/string.h"
 
+#ifdef CONFIG_GSSAPI
+#include "http_negotiate.h"
+#endif
 
 struct http_version {
 	int major;
@@ -549,7 +552,7 @@ http_send_header(struct socket *socket)
 	int trace = get_opt_bool("protocol.http.trace");
 	struct string header;
 	unsigned char *post_data = NULL;
-	struct auth_entry *entry;
+	struct auth_entry *entry = NULL;
 	struct uri *uri = conn->proxied_uri; /* Set to the real uri */
 	unsigned char *optstr;
 	int use_connect, talking_to_proxy;
@@ -806,7 +809,11 @@ #endif
 		add_crlf_to_string(&header);
 	}
 
-	entry = find_auth(uri);
+#ifdef CONFIG_GSSAPI
+	if (http_negotiate_output(uri, &header) != 0)
+#endif
+		entry = find_auth(uri);
+	
 	if (entry) {
 		if (entry->digest) {
 			unsigned char *response;
@@ -1325,12 +1332,13 @@ get_header(struct read_buffer *rb)
 	return 0;
 }
 
-
-static void
-check_http_authentication(struct uri *uri, unsigned char *header,
-			  unsigned char *header_field)
+/* returns 1 if we need retry the connection (for negotiate-auth only) */
+static int
+check_http_authentication(struct connection *conn, struct uri *uri, 
+		unsigned char *header, unsigned char *header_field)
 {
 	unsigned char *str, *d;
+	int ret = 0;
 
 	d = parse_header(header, header_field, &str);
 	while (d) {
@@ -1356,10 +1364,24 @@ check_http_authentication(struct uri *ur
 			mem_free(d);
 			break;
 		}
-
+#ifdef CONFIG_GSSAPI
+		else if (!strncasecmp(d, "GSS-Negotiate", 13)) {
+			if (http_negotiate_input(conn, uri, PROTOCOL_HTTP_GSSNEG, str)==0)
+				ret = 1;
+			mem_free(d);
+			break;
+		}
+		else if (!strncasecmp(d, "Negotiate", 9)) {
+			if (http_negotiate_input(conn, uri, PROTOCOL_HTTP_NEG, str)==0)
+				ret = 1;
+			mem_free(d);
+			break;
+		}
+#endif
 		mem_free(d);
 		d = parse_header(str, header_field, &str);
 	}
+	return ret;
 }
 
 
@@ -1586,11 +1608,13 @@ #endif
 	}
 
 	if (h == 401) {
-		unsigned char *head = conn->cached->head;
-
-		check_http_authentication(uri, head, "WWW-Authenticate");
+		if (check_http_authentication(conn, uri, 
+				conn->cached->head, "WWW-Authenticate")) {
+			retry_connection(conn, S_RESTART);
+			return;
+		}
+	
 	}
-
 	if (h == 407) {
 		unsigned char *str;
 
diff --git a/src/protocol/http/http_negotiate.c b/src/protocol/http/http_negotiate.c
new file mode 100644
index 0000000..a376c2e
--- /dev/null
+++ b/src/protocol/http/http_negotiate.c
@@ -0,0 +1,287 @@
+/* 
+ * HTTP Negotiate authentication method -- based on GSSAPI
+ *
+ * The Microsoft version with SPNEGO is unsupported. If you look for way how
+ * extend this code with SPNEGO see libcurl or firefox source code where is
+ * supported GSSAPI+SPNEGO.
+ *
+ * Copyright (C) 2006 Red Hat, Inc.
+ * Karel Zak <kzak at redhat.com> 
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <gssapi/gssapi.h>
+
+#include "elinks.h"
+#include "network/connection.h"
+#include "protocol/uri.h"
+#include "protocol/http/http.h"
+#include "protocol/http/http_negotiate.h"
+#include "util/base64.h"
+#include "main/object.h"
+#include "util/lists.h"
+
+struct negotiate {
+	OBJECT_HEAD(struct negotiate);
+
+	struct uri 	*uri;
+	
+	int 		type; 			/* GSS-Negotiate or Negotiate or zero */
+	OM_uint32	status;
+	gss_ctx_id_t	context;
+	gss_name_t	server_name;
+	gss_buffer_desc output_token;
+	gss_buffer_desc input_token;
+};
+
+static INIT_LIST_HEAD(negotiate_list);
+
+static struct negotiate *
+http_negotiate_get(struct uri *uri, int *isnew, int alloc)
+{
+	struct negotiate *neg;
+	
+	foreach (neg, negotiate_list) {	
+		if (compare_uri(neg->uri, uri, URI_HTTP_REFERRER_HOST))
+			return neg;
+	}
+	if (!alloc)	
+		return NULL;
+	if (isnew)
+		*isnew = 1;
+
+	if (!(neg = mem_calloc(1, sizeof(*neg))))
+		return NULL;
+		
+	memset(neg, 0, sizeof(*neg));
+	neg->uri = get_uri_reference(uri);	
+	
+	return neg;
+}
+
+static void
+http_negotiate_save(struct negotiate *neg)
+{
+	add_to_list(negotiate_list, neg);
+}
+
+static void 
+http_negotiate_cleanup(struct negotiate *neg, int full)
+{
+	OM_uint32 minor_status;
+
+	if (neg->context != GSS_C_NO_CONTEXT)
+		gss_delete_sec_context(&minor_status, &neg->context, GSS_C_NO_BUFFER);
+
+	if (neg->output_token.length != 0)
+		gss_release_buffer(&minor_status, &neg->output_token);
+
+	if (full && neg->server_name)
+		gss_release_name(&minor_status, &neg->server_name);
+
+	if (full && neg->input_token.length != 0) {
+		/* allocated by mem_free().. so beter not use gss_release_buffer() */
+		mem_free(neg->input_token.value);
+		neg->input_token.length = 0;
+	}
+	
+	if (full)
+		memset(neg, 0, sizeof(*neg));
+}
+
+static int
+http_negotiate_get_name(struct connection *conn, struct negotiate *neg)
+{
+	OM_uint32 major_status, minor_status;
+	gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
+	char name[2048];
+	const char* service;
+	struct uri *uri = conn->proxied_uri;
+
+	/* GSSAPI implementation by Globus (known as GSI) requires the name to be
+	of form "<service>/<fqdn>" instead of <service>@<fqdn> (ie. slash instead
+	of at-sign). Also GSI servers are often identified as 'host' not 'khttp'.
+	Change following lines if you want to use GSI */
+
+	/* IIS uses the <service>@<fqdn> form but uses 'http' as the service name */
+
+	if (neg->type == PROTOCOL_HTTP_GSSNEG)
+		service = "KHTTP";
+	else
+		service = "HTTP";
+
+	token.length = strlen(service) + 1 + uri->hostlen + 1;
+	if (token.length + 1 > sizeof(name))
+		return -1;
+
+	snprintf(name, token.length, "%s@%*s", service, uri->hostlen, uri->host);
+
+	token.value = (void *) name;
+	major_status = gss_import_name(&minor_status,
+			 &token,
+			 GSS_C_NT_HOSTBASED_SERVICE,
+			 &neg->server_name);
+
+	return GSS_ERROR(major_status) ? -1 : 0;
+}
+
+#define GSSNEG_LEN	sizeof("GSS-Negotiate")
+#define NEG_LEN		sizeof("Negotiate")
+
+static int
+http_negotiate_parse_data(unsigned char *data, int type, 
+			gss_buffer_desc *token)
+{
+	int len = 0;
+	unsigned char *end;
+
+	if (data==NULL || *data=='\0')
+		return 0;
+	
+	data += type==PROTOCOL_HTTP_GSSNEG ? GSSNEG_LEN : NEG_LEN;
+	
+	while(*data && isspace((int)*data)) 
+		data++;
+	
+	if (*data=='\0' || *data==ASCII_CR || *data==ASCII_LF)
+		return 0;	/* no data */
+	
+	end = data;
+	while (isalnum((int) *end) || *end=='=')
+		end++;
+
+	/* Ignore line if we encountered an unexpected char. */
+	if (*end != ASCII_CR && *end != ASCII_LF)
+		return 0;
+
+	len = end - data;
+	
+	if (!len)
+		return 0;
+	
+	token->value = (void *) base64_decode_bin(data, len, &token->length);
+	
+	if (!token->value)
+		return -1;
+
+	return 0;
+}
+
+static int
+http_negotiate_create_context(struct negotiate *neg)
+{
+	OM_uint32 major_status, minor_status;
+
+	major_status = gss_init_sec_context(&minor_status,
+					    GSS_C_NO_CREDENTIAL,
+					    &neg->context,
+					    neg->server_name,
+					    GSS_C_NO_OID,
+					    GSS_C_DELEG_FLAG,
+					    0,
+					    GSS_C_NO_CHANNEL_BINDINGS,
+					    &neg->input_token,
+					    NULL,
+					    &neg->output_token,
+					    NULL,
+					    NULL);
+	neg->status = major_status;
+
+	if (GSS_ERROR(major_status)) 
+		return -1;
+	if (neg->output_token.length == 0) 
+		return -1;
+
+	return 0;
+}
+
+/*
+ * Register new negotiate-auth request
+ *
+ * It's possible that server sends to client input token (at least
+ * libcurl supports it) in WWW-Authenticate header, but ususaly 
+ * is this input token undefined.
+ */
+int 
+http_negotiate_input(struct connection *conn, struct uri *uri, 
+					int type, unsigned char *data)
+{
+	struct negotiate *neg;
+	int ret = 0, isnew = 0;
+
+	neg = http_negotiate_get(uri, &isnew, 1);
+	
+	if (neg->context) {
+		if (type != PROTOCOL_HTTP_GSSNEG) 
+			return -1;
+	}
+	neg->type = type;
+
+	if (neg->context && neg->status == GSS_S_COMPLETE) {
+		/* We finished succesfully our part of authentication, but server
+		 * rejected it (since we're again here). Exit with an error since we
+		 * can't invent anything better */
+		http_negotiate_cleanup(neg, 1);
+		return -1;
+	}
+	if (neg->server_name == NULL && http_negotiate_get_name(conn, neg) < 0)
+		return -1;
+	if (data && http_negotiate_parse_data(data, type, &neg->input_token)) 
+		return -1;
+	if ((ret=http_negotiate_create_context(neg)) == 0 && isnew)
+		http_negotiate_save(neg);
+
+	return ret;
+}
+
+/*
+ * Fill output token to "Authorization: Negotiate <token>".
+ */
+int
+http_negotiate_output(struct uri *uri, struct string *header)
+{
+	struct negotiate *neg;
+	char *encoded = NULL;
+	int len = 0;
+
+	if (!(neg = http_negotiate_get(uri, NULL, 0)))
+		return -1;
+	
+	if (neg->output_token.length==0) {
+		if (http_negotiate_create_context(neg) < 0) {		
+			/* full cleanup on error and ask for 
+			   new WWW-Authenticate from server */
+			http_negotiate_cleanup(neg, 1);
+			return -1;
+		}
+	}
+	
+	encoded = base64_encode_bin((unsigned char *) neg->output_token.value, 
+				neg->output_token.length, &len);
+
+	if (encoded==NULL || len==0)
+	    return -1;
+
+	add_to_string(header, "Authorization: ");
+	add_to_string(header, neg->type==PROTOCOL_HTTP_GSSNEG ? 
+				"GSS-Negotiate " : "Negotiate ");
+	add_to_string(header, encoded);
+	add_crlf_to_string(header);
+	
+	http_negotiate_cleanup(neg, 0);
+
+	mem_free(encoded);
+	
+	return 0;
+}
+
diff --git a/src/protocol/http/http_negotiate.h b/src/protocol/http/http_negotiate.h
new file mode 100644
index 0000000..cfd240f
--- /dev/null
+++ b/src/protocol/http/http_negotiate.h
@@ -0,0 +1,16 @@
+
+#ifndef EL__PROTOCOL_HTTP_HTTP_NEGOTIATE_H
+#define EL__PROTOCOL_HTTP_HTTP_NEGOTIATE_H
+
+#define PROTOCOL_HTTP_GSSNEG	1
+#define PROTOCOL_HTTP_NEG	2
+
+
+int http_negotiate_input(struct connection *conn, struct uri *uri, 
+			int type, unsigned char *data);
+
+int http_negotiate_output(struct uri *uri, struct string *header);
+
+
+#endif /* EL_PROTOCOL_HTTP_HTTP_NEGOTIATE_H */
+
diff --git a/src/util/base64.c b/src/util/base64.c
index 8a3918e..7f4e74e 100644
--- a/src/util/base64.c
+++ b/src/util/base64.c
@@ -17,14 +17,21 @@ static unsigned char base64_chars[] = "A
 unsigned char *
 base64_encode(register unsigned char *in)
 {
+	assert(in && *in);
+	if_assert_failed return NULL;
+	
+	return base64_encode_bin((char *) in, strlen(in), NULL);
+}
+
+unsigned char *
+base64_encode_bin(register unsigned char *in, int inlen, int *outlen)
+{
 	unsigned char *out;
 	unsigned char *outstr;
-	int inlen;
 
 	assert(in && *in);
 	if_assert_failed return NULL;
 
-	inlen = strlen(in);
 	out = outstr = mem_alloc((inlen / 3) * 4 + 4 + 1);
 	if (!out) return NULL;
 
@@ -49,16 +56,29 @@ base64_encode(register unsigned char *in
 	}
 	*out = 0;
 
+	if (outlen)
+		*outlen = out-outstr;
+
 	return outstr;
 }
 
-/* Base64 decoding is used only with the CONFIG_FORMHIST feature, so i'll #ifdef it */
-#ifdef CONFIG_FORMHIST
+/* Base64 decoding is used only with the CONFIG_FORMHIST or CONFIG_GSSAPI 
+   feature, so i'll #ifdef it */
+#if  defined(CONFIG_FORMHIST) || defined(CONFIG_GSSAPI)
+
+unsigned char *
+base64_decode(register unsigned char *in) 
+{
+	assert(in && *in);
+	if_assert_failed return NULL;
+
+	return base64_decode_bin(in, strlen(in), NULL);
+}
 
 /* base64_decode:  @in string to decode
  *		   returns the string decoded (must be freed by the caller) */
 unsigned char *
-base64_decode(register unsigned char *in)
+base64_decode_bin(register unsigned char *in, int inlen, int *outlen)
 {
 	static unsigned char is_base64_char[256]; /* static to force initialization at zero */
 	static unsigned char decode[256];
@@ -71,7 +91,7 @@ base64_decode(register unsigned char *in
 	assert(in && *in);
 	if_assert_failed return NULL;
 
-	outstr = out = mem_alloc(strlen(in) / 4 * 3 + 1);
+	outstr = out = mem_alloc(inlen / 4 * 3 + 1);
 	if (!outstr) return NULL;
 
 	if (!once) {
@@ -123,6 +143,10 @@ base64_decode(register unsigned char *in
 	}
 
 	*out = 0;
+
+	if (outlen)
+		*outlen = out-outstr;
+
 	return outstr;
 
 decode_error:
diff --git a/src/util/base64.h b/src/util/base64.h
index cb5cd73..2bdf0e4 100644
--- a/src/util/base64.h
+++ b/src/util/base64.h
@@ -4,4 +4,7 @@ #define EL__UTIL_BASE64_H
 unsigned char *base64_encode(unsigned char *);
 unsigned char *base64_decode(unsigned char *);
 
+unsigned char *base64_encode_bin(unsigned char *, int, int *);
+unsigned char *base64_decode_bin(unsigned char *, int, int *);
+
 #endif



More information about the elinks-dev mailing list