Update to the GSSAPI implementation from HEAD.

--- imported/neon/ne_auth.c	(revision 309)
+++ imported/neon/ne_auth.c	(working copy)
@@ -161,8 +161,11 @@
     /* This used for Basic auth */
     char *basic; 
 #ifdef HAVE_GSSAPI
-    /* This used for GSSAPI auth */
+    /* for the GSSAPI/Negotiate scheme: */
     char *gssapi_token;
+    gss_ctx_id_t gssctx;
+    gss_name_t gssname;
+    gss_OID gssmech;
 #endif
     /* These all used for Digest auth */
     char *realm;
@@ -202,7 +205,7 @@
     struct ne_md5_ctx response_body;
 
     /* Results of response-header callbacks */
-    char *auth_hdr, *auth_info_hdr;
+    ne_buffer *auth_hdr, *auth_info_hdr;
 };
 
 static void clean_session(auth_session *sess) 
@@ -214,6 +217,17 @@
     NE_FREE(sess->opaque);
     NE_FREE(sess->realm);
 #ifdef HAVE_GSSAPI
+    {
+        int major;
+
+        if (sess->gssctx != GSS_C_NO_CONTEXT)
+            gss_delete_sec_context(&major, sess->gssctx, GSS_C_NO_BUFFER);
+        
+        if (sess->gssmech != GSS_C_NO_OID) {
+            gss_release_oid(&major, &sess->gssmech);
+            sess->gssmech = GSS_C_NO_OID;
+        }
+    }
     NE_FREE(sess->gssapi_token);
 #endif
 }
@@ -321,69 +335,162 @@
 /* Add GSSAPI authentication credentials to a request */
 static char *request_gssapi(auth_session *sess) 
 {
-    return ne_concat("Negotiate ", sess->gssapi_token, "\r\n", NULL);
+    if (sess->gssapi_token) 
+        return ne_concat("Negotiate ", sess->gssapi_token, "\r\n", NULL);
+    else
+        return NULL;
 }
 
-static int get_gss_name(gss_name_t *server, auth_session *sess)
+/* Create an GSSAPI name for server HOSTNAME; returns non-zero on
+ * error. */
+static void get_gss_name(gss_name_t *server, const char *hostname)
 {
-    unsigned int major_status, minor_status;
+    unsigned int major, minor;
     gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
 
-    token.value = ne_concat("HTTP@", sess->sess->server.hostname, NULL);
+    token.value = ne_concat("HTTP@", hostname, NULL);
     token.length = strlen(token.value);
 
-    major_status = gss_import_name(&minor_status, &token,
-                                   GSS_C_NT_HOSTBASED_SERVICE,
-                                   server);
-    return GSS_ERROR(major_status) ? -1 : 0;
+    major = gss_import_name(&minor, &token, GSS_C_NT_HOSTBASED_SERVICE,
+                            server);
+    ne_free(token.value);
+    
+    if (GSS_ERROR(major)) {
+        NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: gss_import_name failed.\n");
+        *server = GSS_C_NO_NAME;
+    }
 }
 
-/* Examine a GSSAPI auth challenge; returns 0 if a valid challenge,
- * else non-zero. */
-static int 
-gssapi_challenge(auth_session *sess, struct auth_challenge *parms) 
+/* Append GSSAPI error(s) for STATUS of type TYPE to BUF; prepending
+ * ": " to each error if *FLAG is non-zero, setting *FLAG after an
+ * error has been appended. */
+static void make_gss_error(ne_buffer *buf, int *flag,
+                           unsigned int status, int type)
 {
-    gss_ctx_id_t context;
-    gss_name_t server_name;
-    unsigned int major_status, minor_status;
-    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
-    gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+    int major, minor;
+    int context = 0;
+    
+    do {
+        gss_buffer_desc msg;
+        major = gss_display_status(&minor, status, type,
+                                   GSS_C_NO_OID, &context, &msg);
+        if (major == GSS_S_COMPLETE && msg.length) {
+            if ((*flag)++) ne_buffer_append(buf, ": ", 2);
+            ne_buffer_append(buf, msg.value, msg.length);
+        }
+        if (msg.length) gss_release_buffer(&minor, &msg);
+    } while (context);
+}
 
-    clean_session(sess);
+/* Continue a GSS-API Negotiate exchange, using input TOKEN if
+ * non-NULL.  Returns non-zero on error. */
+static int continue_negotiate(auth_session *sess, const char *token)
+{
+    unsigned int major, minor;
+    gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
+    gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
+    unsigned char *bintoken = NULL;
+    int ret;
+    gss_OID mech = sess->gssmech;
 
-    if (get_gss_name(&server_name, sess))
+    if (token) {
+        input.length = ne_unbase64(token, &bintoken);
+        if (input.length == 0) {
+            NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Invalid input [%s].\n",
+                     token);
+            return -1;
+        }
+        input.value = bintoken;
+        NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Continuation token [%s]\n", token);
+    }
+    else if (sess->gssctx != GSS_C_NO_CONTEXT) {
+        NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Reset incomplete context.\n");
+        gss_delete_sec_context(&minor, &sess->gssctx, GSS_C_NO_BUFFER);
+    }
+
+    major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, &sess->gssctx,
+                                 sess->gssname, mech, 
+                                 GSS_C_MUTUAL_FLAG, GSS_C_INDEFINITE, 
+                                 GSS_C_NO_CHANNEL_BINDINGS,
+                                 &input, &sess->gssmech, &output, NULL, NULL);
+
+    /* done with the input token. */
+    if (bintoken) ne_free(bintoken);
+
+    if (GSS_ERROR(major)) {
+        ne_buffer *err = ne_buffer_create();
+        int flag = 0;
+
+        make_gss_error(err, &flag, major, GSS_C_GSS_CODE);
+        make_gss_error(err, &flag, minor, GSS_C_MECH_CODE);
+        NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Error: %s\n", err->data);
+        ne_set_error(sess->sess, _("GSSAPI authentication error (%s)"), 
+                     err->data);
+        ne_buffer_destroy(err);
         return -1;
+    }
 
-    major_status = gss_init_sec_context(&minor_status,
-                                        GSS_C_NO_CREDENTIAL,
-                                        &context,
-                                        server_name,
-                                        GSS_C_NO_OID,
-                                        0,
-                                        GSS_C_INDEFINITE,
-                                        GSS_C_NO_CHANNEL_BINDINGS,
-                                        &input_token,
-                                        NULL,
-                                        &output_token,
-                                        NULL,
-                                        NULL);
-    gss_release_name(&minor_status, &server_name);
+    if (major == GSS_S_CONTINUE_NEEDED || major == GSS_S_COMPLETE) {
+        NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: init_sec_context OK. (major=%d)\n",
+                 major);
+        ret = 0;
+    } 
+    else {
+        NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Init failure %d.\n", major);
+        ret = -1;
+    }
 
-    if (GSS_ERROR(major_status)) {
-        NE_DEBUG(NE_DBG_HTTPAUTH, "gss_init_sec_context failed.\n");
+    if (major != GSS_S_CONTINUE_NEEDED) {
+        /* context no longer needed: destroy it */
+        gss_delete_sec_context(&minor, &sess->gssctx, GSS_C_NO_BUFFER);
+    }
+
+    if (output.length) {
+        sess->gssapi_token = ne_base64(output.value, output.length);
+        NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Output token: [%s]\n", 
+                 sess->gssapi_token);
+        gss_release_buffer(&minor, &output);
+    } else {
+        NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: No output token.\n");
+    }
+
+    return ret;
+}
+
+/* Process a Negotiate challange CHALL in session SESS; returns zero
+ * if challenge is accepted. */
+static int gssapi_challenge(auth_session *sess, struct auth_challenge *chall) 
+{
+    int ret = continue_negotiate(sess, chall->opaque);
+    if (ret == 0) 
+        sess->scheme = auth_scheme_gssapi;
+    return ret;
+}
+
+/* Verify the header HDR in a Negotiate response. */
+static int verify_negotiate_response(auth_session *sess, char *hdr)
+{
+    char *sep, *ptr = strchr(hdr, ' ');
+
+    if (strncmp(hdr, "Negotiate", ptr - hdr) != 0) {
+        NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Not a Negotiate response!\n");
         return -1;
     }
     
-    if (output_token.length == 0)
-        return -1;
+    ptr++;
 
-    sess->gssapi_token = ne_base64(output_token.value, output_token.length);
-    gss_release_buffer(&major_status, &output_token);
+    if (strlen(ptr) == 0) {
+        NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: No token in Negotiate response!\n");
+        return 0;
+    }
 
-    NE_DEBUG(NE_DBG_HTTPAUTH, 
-             "Base64 encoded GSSAPI challenge: %s.\n", sess->gssapi_token);
-    sess->scheme = auth_scheme_gssapi;
-    return 0;
+    if ((sep = strchr(ptr, ',')) != NULL)
+        *sep = '\0';
+    if ((sep = strchr(ptr, ' ')) != NULL)
+        *sep = '\0';
+
+    NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Negotiate response token [%s]\n", ptr);
+    return continue_negotiate(sess, ptr);
 }
 #endif
 
@@ -637,7 +744,8 @@
 		*pnt = '\0';
 		*value = pnt + 1;
 		state = AFTER_EQ;
-	    } else if (*pnt == ' ' && ischall && *key != NULL) {
+	    } else if ((*pnt == ' ' || *pnt == ',') 
+                       && ischall && *key != NULL) {
 		*value = NULL;
 		*pnt = '\0';
 		*hdr = pnt + 1;
@@ -679,8 +787,8 @@
  *    0 if it gives a valid authentication for the server 
  *    non-zero otherwise (don't believe the response in this case!).
  */
-static int verify_response(struct auth_request *req, auth_session *sess,
-			   const char *value) 
+static int verify_digest_response(struct auth_request *req, auth_session *sess,
+                                  const char *value) 
 {
     char *hdr, *pnt, *key, *val;
     auth_qop qop = auth_qop_none;
@@ -842,36 +950,39 @@
     while (!tokenize(&pnt, &key, &val, 1)) {
 
 	if (val == NULL) {
-	    /* We have a new challenge */
-	    NE_DEBUG(NE_DBG_HTTPAUTH, "New challenge for scheme [%s]\n", key);
-	    chall = ne_calloc(sizeof *chall);
+            auth_scheme scheme;
 
-	    chall->next = challenges;
-	    challenges = chall;
-	    /* Initialize the challenge parameters */
-	    /* Which auth-scheme is it (case-insensitive matching) */
 	    if (strcasecmp(key, "basic") == 0) {
-		NE_DEBUG(NE_DBG_HTTPAUTH, "Basic scheme.\n");
-		chall->scheme = auth_scheme_basic;
+		scheme = auth_scheme_basic;
 	    } else if (strcasecmp(key, "digest") == 0) {
-		NE_DEBUG(NE_DBG_HTTPAUTH, "Digest scheme.\n");
-		chall->scheme = auth_scheme_digest;
-#ifdef HAVE_GSSAPI		
+		scheme = auth_scheme_digest;
+            }
+#ifdef HAVE_GSSAPI
+            /* cope with a Negotiate parameter which doesn't match the
+             * auth-param due to the broken spec. */
+            else if (chall && chall->scheme == auth_scheme_gssapi
+                     && chall->opaque == NULL) {
+                chall->opaque = key;
+                continue;
 	    } else if (strcasecmp(key, "negotiate") == 0) {
-		NE_DEBUG(NE_DBG_HTTPAUTH, "GSSAPI scheme.\n");
-		chall->scheme = auth_scheme_gssapi;
+		scheme = auth_scheme_gssapi;
+            }
 #endif
-	    } else {
-		NE_DEBUG(NE_DBG_HTTPAUTH, "Unknown scheme.\n");
-		ne_free(chall);
-		challenges = NULL;
-		break;
+            else {
+		NE_DEBUG(NE_DBG_HTTPAUTH, "Ignoring challenge '%s'.\n", key);
+                chall = NULL;
+                continue;
 	    }
+            
+            NE_DEBUG(NE_DBG_HTTPAUTH, "New '%s' challenge.\n", key);
+            chall = ne_calloc(sizeof *chall);
+            chall->scheme = scheme;
+            chall->next = challenges;
+            challenges = chall;
 	    continue;
 	} else if (chall == NULL) {
-	    /* If we haven't got an auth-scheme, and we're
-	     * haven't yet found a challenge, skip this pair.
-	     */
+	    /* Ignore pairs for an unknown challenge. */
+            NE_DEBUG(NE_DBG_HTTPAUTH, "Ignored pair: %s = %s\n", key, val);
 	    continue;
 	}
 
@@ -924,15 +1035,17 @@
     success = 0;
 
 #ifdef HAVE_GSSAPI
-    if (strcmp(ne_get_scheme(sess->sess), "https") == 0) {
+    /* Ignore Negotiate challenges from origin servers which don't
+     * come over SSL. */
+    if (sess->spec == &ah_proxy_class || sess->context != AUTH_ANY) {
         NE_DEBUG(NE_DBG_HTTPAUTH, "Looking for GSSAPI.\n");
         /* Try a GSSAPI challenge */
         for (chall = challenges; chall != NULL; chall = chall->next) {
             if (chall->scheme == auth_scheme_gssapi) {
-	    if (!gssapi_challenge(sess, chall)) {
-		success = 1;
-		break;
-	    }
+                if (!gssapi_challenge(sess, chall)) {
+                    success = 1;
+                    break;
+                }
             }
         }
     }
@@ -993,6 +1106,14 @@
     ne_md5_process_bytes(block, length, ctx);
 }
 
+/* Collect auth challenges into an ne_buffer */
+static void ah_collect_header(void *userdata, const char *value)
+{
+    ne_buffer *ar = userdata;
+    if (ne_buffer_size(ar)) ne_buffer_append(ar, ", ", 2);
+    ne_buffer_zappend(ar, value);
+}
+
 static void ah_create(ne_request *req, void *session, const char *method,
 		      const char *uri)
 {
@@ -1009,14 +1130,14 @@
         areq->method = method;
         areq->uri = uri;
         areq->request = req;
+        areq->auth_hdr = ne_buffer_create();
+        areq->auth_info_hdr = ne_buffer_create();
         
         ne_add_response_header_handler(req, sess->spec->resp_hdr,
-                                       ne_duplicate_header, &areq->auth_hdr);
-        
+                                       ah_collect_header, areq->auth_hdr);
 	
         ne_add_response_header_handler(req, sess->spec->resp_info_hdr,
-                                       ne_duplicate_header, 
-                                       &areq->auth_info_hdr);
+                                       ah_collect_header, areq->auth_info_hdr);
         
         sess->attempt = 0;
         
@@ -1035,7 +1156,7 @@
     } else {
 	char *value;
 
-	NE_DEBUG(NE_DBG_HTTPAUTH, "Handling.");
+	NE_DEBUG(NE_DBG_HTTPAUTH, "Handling auth session.\n");
 	req->will_handle = 1;
 
 	if (sess->qop == auth_qop_auth_int) {
@@ -1082,22 +1203,44 @@
 
     if (!areq) return NE_OK;
 
+#ifdef HAVE_GSSAPI
+    /* whatever happens: forget the GSSAPI token cached thus far */
+    if (sess->gssapi_token) {
+        ne_free(sess->gssapi_token);
+        sess->gssapi_token = NULL;
+    }
+#endif
+
     NE_DEBUG(NE_DBG_HTTPAUTH, 
 	     "ah_post_send (#%d), code is %d (want %d), %s is %s\n",
 	     sess->attempt, status->code, sess->spec->status_code, 
-	     sess->spec->resp_hdr, SAFELY(areq->auth_hdr));
-    if (areq->auth_info_hdr != NULL && 
-	verify_response(areq, sess, areq->auth_info_hdr)) {
-	NE_DEBUG(NE_DBG_HTTPAUTH, "Response authentication invalid.\n");
-	ne_set_error(sess->sess, "%s", _(sess->spec->fail_msg));
-	ret = NE_ERROR;
-    } else if ((status->code == sess->spec->status_code ||
-                (status->code == 401 && sess->context == AUTH_CONNECT)) &&
-	       areq->auth_hdr != NULL) {
+	     sess->spec->resp_hdr, areq->auth_hdr->data);
+    if (ne_buffer_size(areq->auth_info_hdr) 
+        && sess->scheme == auth_scheme_digest) {
+        if (verify_digest_response(areq, sess, areq->auth_info_hdr->data)) {
+            NE_DEBUG(NE_DBG_HTTPAUTH, "Response authentication invalid.\n");
+            ne_set_error(sess->sess, "%s", _(sess->spec->fail_msg));
+            ret = NE_ERROR;
+        }
+    }
+#ifdef HAVE_GSSAPI
+    /* one must wonder... has Mr Brezak actually read RFC2617? */
+    else if (sess->scheme == auth_scheme_gssapi 
+             && (status->klass == 2 || status->klass == 3)
+             && ne_buffer_size(areq->auth_hdr)) {
+        if (verify_negotiate_response(sess, areq->auth_hdr->data)) { 
+            NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Mutual auth failed.\n");
+            ret = NE_ERROR;
+        }
+    }
+#endif /* HAVE_GSSAPI */
+    else if ((status->code == sess->spec->status_code ||
+              (status->code == 401 && sess->context == AUTH_CONNECT)) &&
+	       ne_buffer_size(areq->auth_hdr)) {
         /* note above: allow a 401 in response to a CONNECT request
          * from a proxy since some buggy proxies send that. */
-	NE_DEBUG(NE_DBG_HTTPAUTH, "Got challenge with code %d.\n", status->code);
-	if (!auth_challenge(sess, areq->auth_hdr)) {
+	NE_DEBUG(NE_DBG_HTTPAUTH, "Got challenge (code %d).\n", status->code);
+	if (!auth_challenge(sess, areq->auth_hdr->data)) {
 	    ret = NE_RETRY;
 	} else {
 	    clean_session(sess);
@@ -1105,9 +1248,9 @@
 	}
     }
 
-    NE_FREE(areq->auth_info_hdr);
-    NE_FREE(areq->auth_hdr);
-    
+    ne_buffer_clear(areq->auth_hdr);
+    ne_buffer_clear(areq->auth_info_hdr);
+
     return ret;
 }
 
@@ -1115,13 +1258,25 @@
 {
     auth_session *sess = session;
     struct auth_request *areq = ne_get_request_private(req, sess->spec->id);
-    if (areq) ne_free(areq);
+
+    if (areq) {
+        ne_buffer_destroy(areq->auth_info_hdr);
+        ne_buffer_destroy(areq->auth_hdr);
+        ne_free(areq);
+    }
 }
 
 static void free_auth(void *cookie)
 {
     auth_session *sess = cookie;
 
+#ifdef HAVE_GSSAPI
+    if (sess->gssname != GSS_C_NO_NAME) {
+        int major;
+        gss_release_name(&major, sess->gssname);
+    }
+#endif
+
     clean_session(sess);
     ne_free(sess);
 }
@@ -1137,10 +1292,17 @@
     ahs->sess = sess;
     ahs->spec = ahc;
 
-    if (strcmp(ne_get_scheme(sess), "https") == 0)
+    if (strcmp(ne_get_scheme(sess), "https") == 0) {
         ahs->context = isproxy ? AUTH_CONNECT : AUTH_NOTCONNECT;
-    else
+    } else {
         ahs->context = AUTH_ANY;
+    }
+#ifdef HAVE_GSSAPI
+    get_gss_name(&ahs->gssname, (isproxy ? sess->proxy.hostname 
+                                 : sess->server.hostname));
+    ahs->gssctx = GSS_C_NO_CONTEXT;
+    ahs->gssmech = GSS_C_NO_OID;
+#endif
     
     /* Register hooks */
     ne_hook_create_request(sess, ah_create, ahs);