Update to the GSSAPI implementation from HEAD. --- neon/src/ne_auth.c (revision 309) +++ neon/src/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);