From 2774d0438489edaf69cfa892fb7393e1fd4496dd Mon Sep 17 00:00:00 2001 From: Joe Orton Date: Sat, 21 Nov 2020 14:33:43 +0000 Subject: Follow RFC 7617 rules for Basic authentication scope (issue #32): * src/ne_auth.c (struct auth_protocol): Pass request URI to challenge functions. (negotiate_challenge, ntlm_challenge, sspi_challenge): Adjust accordingly. (get_scope_path): New function. (basic_challenge): Set up single domain per Digest for Basic scope. (request_basic): Check scope/domain. * test/auth.c (serve_basic_scope_checker): New function. (basic_scope): Add test case. --- src/ne_auth.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++--------- test/auth.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 9 deletions(-) diff --git a/src/ne_auth.c b/src/ne_auth.c index 1b397f5..2ff4e40 100644 --- a/src/ne_auth.c +++ b/src/ne_auth.c @@ -267,7 +267,8 @@ struct auth_protocol { * On failure, challenge_error() should be used to append an error * message to the error buffer 'errmsg'. */ int (*challenge)(auth_session *sess, int attempt, - struct auth_challenge *chall, ne_buffer **errmsg); + struct auth_challenge *chall, + const char *uri, ne_buffer **errmsg); /* Return the string to send in the -Authenticate request header: * (ne_malloc-allocated, NUL-terminated string) */ @@ -288,6 +289,8 @@ struct auth_protocol { static void challenge_error(ne_buffer **errmsg, const char *fmt, ...) ne_attribute((format(printf, 2, 3))); +static int inside_domain(auth_session *sess, const char *req_uri); + /* Free the domains array, precondition sess->ndomains > 0. */ static void free_domains(auth_session *sess) { @@ -426,11 +429,37 @@ static int get_credentials(auth_session *sess, ne_buffer **errmsg, int attempt, return -1; } +/* Return the scope of the Basic authentication domain following rule + * in RFC 7617. Malloc-allocated path is returned. */ +static char *get_scope_path(const char *uri) +{ + ne_uri base, udot, parent; + char *s; + + memset(&udot, 0, sizeof udot); + udot.path = "."; + + if (strcmp(uri, "*") == 0 || ne_uri_parse(uri, &base) != 0) { + /* Assume scope is whole origin. */ + return ne_strdup("/"); + } + + ne_uri_resolve(&base, &udot, &parent); + + s = parent.path; + parent.path = NULL; + + ne_uri_free(&parent); + ne_uri_free(&base); + + return s; +} + /* Examine a Basic auth challenge. * Returns 0 if an valid challenge, else non-zero. */ static int basic_challenge(auth_session *sess, int attempt, struct auth_challenge *parms, - ne_buffer **errmsg) + const char *uri, ne_buffer **errmsg) { char *tmp, password[ABUFSIZE]; @@ -459,6 +488,13 @@ static int basic_challenge(auth_session *sess, int attempt, sess->basic = ne_base64((unsigned char *)tmp, strlen(tmp)); ne_free(tmp); + if (sess->ndomains != 1) { + sess->domains = ne_realloc(sess->domains, sizeof(*sess->domains)); + sess->ndomains = 1; + } + sess->domains[0] = get_scope_path(uri); + NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Basic auth scope is: %s\n", sess->domains[0]); + /* Paranoia. */ memset(password, 0, sizeof password); @@ -468,6 +504,10 @@ static int basic_challenge(auth_session *sess, int attempt, /* Add Basic authentication credentials to a request */ static char *request_basic(auth_session *sess, struct auth_request *req) { + if (!inside_domain(sess, req->uri)) { + return NULL; + } + return ne_concat("Basic ", sess->basic, "\r\n", NULL); } @@ -598,7 +638,7 @@ static int continue_negotiate(auth_session *sess, const char *token, * if challenge is accepted. */ static int negotiate_challenge(auth_session *sess, int attempt, struct auth_challenge *chall, - ne_buffer **errmsg) + const char *uri, ne_buffer **errmsg) { const char *token = chall->opaque; @@ -695,7 +735,7 @@ static int continue_sspi(auth_session *sess, int ntlm, const char *hdr) static int sspi_challenge(auth_session *sess, int attempt, struct auth_challenge *parms, - ne_buffer **errmsg) + const char *uri, ne_buffer **errmsg) { int ntlm = ne_strcasecmp(parms->protocol->name, "NTLM") == 0; @@ -802,7 +842,7 @@ static char *request_ntlm(auth_session *sess, struct auth_request *request) static int ntlm_challenge(auth_session *sess, int attempt, struct auth_challenge *parms, - ne_buffer **errmsg) + const char *uri, ne_buffer **errmsg) { int status; @@ -837,7 +877,7 @@ static int ntlm_challenge(auth_session *sess, int attempt, * else non-zero. */ static int digest_challenge(auth_session *sess, int attempt, struct auth_challenge *parms, - ne_buffer **errmsg) + const char *uri, ne_buffer **errmsg) { char password[ABUFSIZE]; unsigned int hash; @@ -1349,7 +1389,7 @@ static void challenge_error(ne_buffer **errbuf, const char *fmt, ...) /* Passed the value of a "(Proxy,WWW)-Authenticate: " header field. * Returns 0 if valid challenge was accepted; non-zero if no valid * challenge was found. */ -static int auth_challenge(auth_session *sess, int attempt, +static int auth_challenge(auth_session *sess, int attempt, const char *uri, const char *value) { char *pnt, *key, *val, *hdr, sep; @@ -1484,7 +1524,7 @@ static int auth_challenge(auth_session *sess, int attempt, for (chall = challenges; chall != NULL; chall = chall->next) { NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Trying %s challenge...\n", chall->protocol->name); - if (chall->protocol->challenge(sess, attempt, chall, &errmsg) == 0) { + if (chall->protocol->challenge(sess, attempt, chall, uri, &errmsg) == 0) { NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Accepted %s challenge.\n", chall->protocol->name); sess->protocol = chall->protocol; @@ -1615,7 +1655,7 @@ static int ah_post_send(ne_request *req, void *cookie, const ne_status *status) /* 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, "auth: Got challenge (code %d).\n", status->code); - if (!auth_challenge(sess, areq->attempt++, auth_hdr)) { + if (!auth_challenge(sess, areq->attempt++, areq->uri, auth_hdr)) { ret = NE_RETRY; } else { clean_session(sess); diff --git a/test/auth.c b/test/auth.c index 8186c91..f6ccad1 100644 --- a/test/auth.c +++ b/test/auth.c @@ -1545,6 +1545,65 @@ static int forget(void) return await_server(); } +static int serve_basic_scope_checker(ne_socket *sock, void *userdata) +{ + /* --- GET /fish/0.txt -- first request */ + digest_hdr = NULL; + got_header = dup_header; + want_header = "Authorization"; + CALL(discard_request(sock)); + if (digest_hdr) { + t_context("Got WWW-Auth header on initial request"); + return error_response(sock, FAIL); + } + + send_response(sock, CHAL_WALLY, 401, 0); + + /* Retry of GET /fish/0 - expect Basic creds */ + auth_failed = 0; + got_header = auth_hdr; + CALL(discard_request(sock)); + if (auth_failed) { + t_context("bad Basic Auth on first request"); + return error_response(sock, FAIL); + } + send_response(sock, CHAL_WALLY, 200, 0); + + /* --- GET /not/inside -- second request */ + got_header = dup_header; + CALL(discard_request(sock)); + if (digest_hdr) { + t_context("Basic auth sent outside of credentials scope"); + return error_response(sock, FAIL); + } + send_response(sock, CHAL_WALLY, 200, 0); + + /* --- GET /fish/1 -- third request */ + got_header = auth_hdr; + CALL(discard_request(sock)); + send_response(sock, NULL, auth_failed?500:200, 1); + + return 0; +} + +/* Check that Basic auth follows the RFC7617 rules around scope. */ +static int basic_scope(void) +{ + ne_session *sess; + + CALL(make_session(&sess, serve_basic_scope_checker, NULL)); + + ne_set_server_auth(sess, auth_cb, NULL); + + CALL(any_2xx_request(sess, "/fish/0.txt")); /* must use auth */ + CALL(any_2xx_request(sess, "/not/inside")); /* must NOT use auth credentials */ + CALL(any_2xx_request(sess, "/fish/1")); /* must use auth credentials */ + + ne_session_destroy(sess); + + return await_server(); +} + /* proxy auth, proxy AND origin */ ne_test tests[] = { @@ -1567,5 +1626,6 @@ ne_test tests[] = { T(defaults), T(CVE_2008_3746), T(forget), + T(basic_scope), T(NULL) }; -- cgit v1.2.1