summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Orton <joe@manyfish.uk>2020-11-21 14:33:43 +0000
committerJoe Orton <jorton@apache.org>2020-11-21 17:28:45 +0000
commit2774d0438489edaf69cfa892fb7393e1fd4496dd (patch)
tree5078d96e2b0b3cb936f2c864a8479c735cb9c433
parent5f08a8bffcde015f0f44ff2e2aada081deeb0ddb (diff)
downloadneon-git-2774d0438489edaf69cfa892fb7393e1fd4496dd.tar.gz
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.
-rw-r--r--src/ne_auth.c58
-rw-r--r--test/auth.c60
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)
};