diff --git a/doc/using.xml b/doc/using.xml
index 4a3b733e..f38d63d7 100644
--- a/doc/using.xml
+++ b/doc/using.xml
@@ -104,7 +104,7 @@
chunked
.
- RFC 2617, HTTP Authentication: Basic and Digest Access Authentication
+ RFC 7616, HTTP Digest Access Authentication
&neon; is not strictly compliant with the quoting rules
given in the grammar for the Authorization
@@ -114,7 +114,19 @@
(Microsoft® IIS 5) rejects the request if these parameters
are not quoted. &neon; sends these parameters with
quotes—this is not known to cause any problems with
- other server implementations.
+ other server implementations.
+
+ RFC 7616 predates RFC 9112 and uses conflicting language
+ around URIs. &neon; uses the RFC 9112
+ request-target in both the
+ A2 grammar and the uri=
+ parameter of the Authorization
+ header. &neon; will accept (and resolve) any URI-reference in
+ the domain= parameter for
+ WWW-Authenticate response header
+ field.
+
+
Namespaces in XML
diff --git a/src/ne_auth.c b/src/ne_auth.c
index a4422bfa..af287468 100644
--- a/src/ne_auth.c
+++ b/src/ne_auth.c
@@ -1,6 +1,6 @@
/*
HTTP Authentication routines
- Copyright (C) 1999-2021, Joe Orton
+ Copyright (C) 1999-2024, Joe Orton
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
@@ -226,8 +226,8 @@ struct auth_request {
/*** Per-request details. ***/
ne_request *request; /* the request object. */
- /* The method and URI we are using for the current request */
- const char *uri;
+ /* The request-target and method for the current request, */
+ const char *target;
const char *method;
int attempt; /* number of times this request has been retries due
@@ -257,7 +257,7 @@ struct auth_protocol {
* message to the error buffer 'errmsg'. */
int (*challenge)(auth_session *sess, int attempt,
struct auth_challenge *chall,
- const char *uri, ne_buffer **errmsg);
+ const char *target, ne_buffer **errmsg);
/* Return the string to send in the -Authenticate request header:
* (ne_malloc-allocated, NUL-terminated string) */
@@ -450,7 +450,7 @@ static char *get_scope_path(const char *uri)
* Returns 0 if an valid challenge, else non-zero. */
static int basic_challenge(auth_session *sess, int attempt,
struct auth_challenge *parms,
- const char *uri, ne_buffer **errmsg)
+ const char *target, ne_buffer **errmsg)
{
char *tmp, password[ABUFSIZE];
@@ -481,14 +481,14 @@ static int basic_challenge(auth_session *sess, int attempt,
ne__strzero(password, sizeof password);
- if (strcmp(uri, "*") == 0) {
- /* If the request-target is "*" the auth scope is explicitly
- * the whole server. */
+ if (strcmp(target, "*") == 0 || sess->context == AUTH_CONNECT) {
+ /* For CONNECT, or if the request-target is "*", the auth
+ * scope is implicitly the whole server. */
return 0;
}
sess->domains = ne_malloc(sizeof *sess->domains);
- sess->domains[0] = get_scope_path(uri);
+ sess->domains[0] = get_scope_path(target);
sess->ndomains = 1;
NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Basic auth scope is: %s\n",
@@ -500,7 +500,7 @@ 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 (sess->ndomains && !inside_domain(sess, req->uri)) {
+ if (sess->ndomains && !inside_domain(sess, req->target)) {
return NULL;
}
@@ -634,7 +634,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,
- const char *uri, ne_buffer **errmsg)
+ const char *target, ne_buffer **errmsg)
{
const char *token = chall->opaque;
@@ -731,7 +731,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,
- const char *uri, ne_buffer **errmsg)
+ const char *target, ne_buffer **errmsg)
{
int ntlm = ne_strcasecmp(parms->protocol->name, "NTLM") == 0;
@@ -777,7 +777,7 @@ static int parse_domain(auth_session *sess, const char *domain)
do {
char *token = ne_token(&p, ' ');
ne_uri rel, absolute;
-
+
if (ne_uri_parse(token, &rel) == 0) {
/* Resolve relative to the Request-URI. */
base.path = "/";
@@ -838,7 +838,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,
- const char *uri, ne_buffer **errmsg)
+ const char *target, ne_buffer **errmsg)
{
int status;
@@ -954,7 +954,7 @@ static char *get_digest_h_urp(auth_session *sess, ne_buffer **errmsg,
* else non-zero. */
static int digest_challenge(auth_session *sess, int attempt,
struct auth_challenge *parms,
- const char *uri, ne_buffer **errmsg)
+ const char *target, ne_buffer **errmsg)
{
char *p, *h_urp = NULL;
@@ -1064,9 +1064,9 @@ static int digest_challenge(auth_session *sess, int attempt,
return 0;
}
-/* Returns non-zero if given Request-URI is inside the authentication
- * domain defined for the session. */
-static int inside_domain(auth_session *sess, const char *req_uri)
+/* Returns non-zero if given request-target is inside the
+ * authentication domain defined for the session. */
+static int inside_domain(auth_session *sess, const char *target)
{
int inside = 0;
size_t n;
@@ -1074,7 +1074,7 @@ static int inside_domain(auth_session *sess, const char *req_uri)
/* Parse the Request-URI; it will be an absoluteURI if using a
* proxy, and possibly '*'. */
- if (strcmp(req_uri, "*") == 0 || ne_uri_parse(req_uri, &uri) != 0) {
+ if (strcmp(target, "*") == 0 || ne_uri_parse(target, &uri) != 0) {
/* Presume outside the authentication domain. */
return 0;
}
@@ -1104,12 +1104,15 @@ static char *request_digest(auth_session *sess, struct auth_request *req)
/* Do not submit credentials if an auth domain is defined and this
* request-uri fails outside it. */
- if (sess->ndomains && !inside_domain(sess, req->uri)) {
+ if (sess->ndomains && !inside_domain(sess, req->target)) {
return NULL;
}
- /* H(A2): https://tools.ietf.org/html/rfc7616#section-3.4.3 */
- h_a2 = ne_strhash(hash, req->method, ":", req->uri, NULL);
+ /* H(A2): https://tools.ietf.org/html/rfc7616#section-3.4.3 - Note
+ * that the RFC specifies that "request-uri" is used in the A2
+ * grammar, which matches the RFC 9112 'request-target', which is
+ * what was passed through by ah_create. */
+ h_a2 = ne_strhash(hash, req->method, ":", req->target, NULL);
NE_DEBUG(NE_DBG_HTTPAUTH, "auth: H(A2): %s\n", h_a2);
/* Calculate the 'response' to the Digest challenge to send the
@@ -1140,7 +1143,7 @@ static char *request_digest(auth_session *sess, struct auth_request *req)
ne_buffer_concat(ret,
"Digest realm=\"", sess->realm, "\", "
"nonce=\"", sess->nonce, "\", "
- "uri=\"", req->uri, "\", "
+ "uri=\"", req->target, "\", "
"response=\"", response, "\", "
"algorithm=\"", sess->alg->name, "\"",
NULL);
@@ -1322,7 +1325,7 @@ static int verify_digest_response(struct auth_request *req, auth_session *sess,
char *h_a2, *response;
unsigned int hash = sess->alg->hash;
- h_a2 = ne_strhash(hash, ":", req->uri, NULL);
+ h_a2 = ne_strhash(hash, ":", req->target, NULL);
response = ne_strhash(hash, sess->h_a1, ":", sess->response_rhs,
":", h_a2, NULL);
ne_free(h_a2);
@@ -1589,7 +1592,7 @@ static int auth_challenge(auth_session *sess, int attempt, const char *uri,
}
static void ah_create(ne_request *req, void *session, const char *method,
- const char *uri)
+ const char *target)
{
auth_session *sess = session;
int is_connect = strcmp(method, "CONNECT") == 0;
@@ -1603,7 +1606,7 @@ static void ah_create(ne_request *req, void *session, const char *method,
NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Create for %s\n", sess->spec->resp_hdr);
areq->method = method;
- areq->uri = uri;
+ areq->target = target;
areq->request = req;
ne_set_request_private(req, sess->spec->id, areq);
@@ -1693,7 +1696,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++, areq->uri, auth_hdr)) {
+ if (!auth_challenge(sess, areq->attempt++, areq->target, auth_hdr)) {
ret = NE_RETRY;
} else {
clean_session(sess);
diff --git a/test/auth.c b/test/auth.c
index 90a5f582..6f5a0dad 100644
--- a/test/auth.c
+++ b/test/auth.c
@@ -409,6 +409,7 @@ static void dup_header(char *header)
#define PARM_LEGACY_ONLY (0x0100)
#define PARM_QOP (0x0200) /* use qop= */
#define PARM_RFC2617 (0x0204) /* use algorithm= and qop= */
+#define PARM_OPTSTAR (0x0400) /* use OPTIONS * */
struct digest_parms {
const char *realm, *nonce, *opaque, *domain;
@@ -497,6 +498,8 @@ static char *make_digest(struct digest_state *state, struct digest_parms *parms,
h_a1 = sess_h_a1;
}
+ NE_DEBUG(NE_DBG_HTTP, "H(A2) from %s:%s\n",
+ !auth_info ? state->method : "", state->uri);
h_a2 = hash(parms, !auth_info ? state->method : "", ":", state->uri, NULL);
if (parms->flags & PARM_QOP) {
@@ -767,14 +770,20 @@ static int serve_digest(ne_socket *sock, void *userdata)
struct digest_parms *parms = userdata;
struct digest_state state;
char resp[NE_BUFSIZ], *rspdigest;
+
+ state.method = "GET";
if ((parms->flags & PARM_PROXY))
state.uri = "http://www.example.com/fish";
else if (parms->domain)
state.uri = "/fish/0";
+ else if ((parms->flags & PARM_OPTSTAR)) {
+ state.method = "OPTIONS";
+ state.uri = "*";
+ }
else
state.uri = "/fish";
- state.method = "GET";
+
state.realm = parms->realm;
state.nonce = parms->nonce;
state.opaque = parms->opaque;
@@ -926,7 +935,10 @@ static int test_digest(struct digest_parms *parms)
}
do {
- CALL(any_2xx_request(sess, "/fish"));
+ if (parms->flags & PARM_OPTSTAR)
+ CALL(any_2xx_request_method(sess, "OPTIONS", "*"));
+ else
+ CALL(any_2xx_request(sess, "/fish"));
} while (--parms->num_requests);
return destroy_and_wait(sess);
@@ -969,6 +981,9 @@ static int digest(void)
/* Proxy + nextnonce */
{ "WallyWorld", "this-is-also-a-nonce", "opaque-string", NULL, ALG_MD5, PARM_RFC2617|PARM_AINFO|PARM_PROXY, 1, 0, fail_not },
+ /* OPTIONS * test */
+ { "WallyWorld", "options-nonce", "new-opaque", NULL, ALG_MD5, PARM_RFC2617|PARM_USERHASH|PARM_OPTSTAR, 1, 0, fail_not },
+
{ NULL }
};
size_t n;
diff --git a/test/common/child.c b/test/common/child.c
index 14112225..17188f58 100644
--- a/test/common/child.c
+++ b/test/common/child.c
@@ -1,6 +1,6 @@
/*
Framework for testing with a server process
- Copyright (C) 2001-2010, Joe Orton
+ Copyright (C) 2001-2024, Joe Orton
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -386,9 +386,9 @@ int new_spawn_server2(int count, server_fn fn, void *userdata,
*addr = ne_iaddr_make(ne_iaddr_ipv6, sa.in6.sin6_addr.s6_addr);
}
- NE_DEBUG(NE_DBG_SOCKET, "child using port %u\n", *port);
+ NE_DEBUG(NE_DBG_SOCKET, "child: using port %u\n", *port);
- NE_DEBUG(NE_DBG_SOCKET, "child forking now...\n");
+ NE_DEBUG(NE_DBG_SOCKET, "child: forking now...\n");
child = fork();
ONN("failed to fork server", child == -1);
@@ -406,7 +406,7 @@ int new_spawn_server2(int count, server_fn fn, void *userdata,
char errbuf[256];
int cret;
- NE_DEBUG(NE_DBG_HTTP, "child iteration #%d (of %d), "
+ NE_DEBUG(NE_DBG_HTTP, "child: iteration #%d (of %d), "
"awaiting connection...\n", iter, count);
if (ne_sock_accept(sock, ls)) {
@@ -415,19 +415,24 @@ int new_spawn_server2(int count, server_fn fn, void *userdata,
exit(FAIL);
}
- NE_DEBUG(NE_DBG_HTTP, "child got connection, invoking server\n");
+ NE_DEBUG(NE_DBG_HTTP, "child: got connection, invoking server\n");
+ if (iter == count) {
+ close(ls);
+ NE_DEBUG(NE_DBG_HTTP, "child: closed listening socket.\n");
+ }
+
ret = fn(sock, userdata);
- NE_DEBUG(NE_DBG_HTTP, "child iteration #%d returns %d\n",
+ NE_DEBUG(NE_DBG_HTTP, "child: iteration #%d returns %d\n",
iter, ret);
cret = close_socket(sock);
- NE_DEBUG(NE_DBG_HTTP, "child closed connection, %d: %s.\n", cret,
+ NE_DEBUG(NE_DBG_HTTP, "child: closed connection, %d: %s.\n", cret,
cret ? ne_strerror(cret, errbuf, sizeof errbuf)
: "no error");
} while (ret == 0 && ++iter <= count);
- NE_DEBUG(NE_DBG_HTTP, "child terminating with %d\n", ret);
+ NE_DEBUG(NE_DBG_HTTP, "child: terminating with %d\n", ret);
exit(ret);
}
diff --git a/test/ssl.c b/test/ssl.c
index 2131d756..93f95b3e 100644
--- a/test/ssl.c
+++ b/test/ssl.c
@@ -469,28 +469,6 @@ static int simple(void)
return accept_signed_cert(SERVER_CERT);
}
-#if 0 /* No longer works for modern SSL libraries, rightly so. */
-/* Test for SSL operation when server uses SSLv2 */
-static int simple_sslv2(void)
-{
- ne_session *sess = ne_session_create("https", "localhost", 7777);
- struct ssl_server_args args = {SERVER_CERT, 0};
-
- args.use_ssl2 = 1;
- ne_set_session_flag(sess, NE_SESSFLAG_SSLv2, 1);
-
- if (ne_get_session_flag(sess, NE_SESSFLAG_SSLv2) != 1) {
- t_context("no SSLv2 support in SSL library");
- ne_session_destroy(sess);
- return SKIP;
- }
-
- CALL(any_ssl_request(sess, ssl_server, &args, CA_CERT, NULL, NULL));
- ne_session_destroy(sess);
- return OK;
-}
-#endif
-
/* Test read-til-EOF behaviour with SSL. */
static int simple_eof(void)
{
@@ -1263,27 +1241,42 @@ static int proxy_tunnel(void)
return OK;
}
-#define RESP_0LENGTH "HTTP/1.1 200 OK\r\n" "Content-Length: 0\r\n" "\r\n"
+struct tunnel_args {
+ int iteration;
+ const char *first_response; /* first CONNECT response. */
+ const char *second_response; /* second CONNECT response. */
+ struct ssl_server_args *args;
+};
-/* a tricky test which requires spawning a second server process in
- * time for a new connection after a 407. */
-static int apt_post_send(ne_request *req, void *ud, const ne_status *st)
+/* Server which acts as a proxy accepting a CONNECT request. */
+static int serve_auth_tunnel(ne_socket *sock, void *ud)
{
- int *code = ud;
- if (st->code == *code) {
- struct ssl_server_args args = {SERVER_CERT, NULL};
+ struct tunnel_args *args = ud;
+
+ /* check for a server auth function */
+ want_header = "Authorization";
+ got_header = tunnel_header;
+ got_server_auth = 0;
+
+ CALL(discard_request(sock));
+
+ if (got_server_auth) {
+ SEND_STRING(sock, "HTTP/1.1 500 Leaked Server Auth Creds\r\n"
+ "Content-Length: 0\r\n" "Server: serve_tunnel\r\n\r\n");
+ return 0;
+ }
- if (*code == 407) args.numreqs = 2;
- args.response = RESP_0LENGTH;
-
- NE_DEBUG(NE_DBG_HTTP, "Got challenge, awaiting server...\n");
- CALL(await_server());
- NE_DEBUG(NE_DBG_HTTP, "Spawning proper tunnel server...\n");
- /* serve *two* 200 OK responses. */
- CALL(spawn_server(7777, serve_tunnel, &args));
- NE_DEBUG(NE_DBG_HTTP, "Spawned.\n");
+ if (args->iteration++ == 0) {
+ /* give the plaintext tunnel reply, acting as the proxy */
+
+ SEND_STRING(sock, args->first_response);
+
+ return OK;
}
- return OK;
+
+ SEND_STRING(sock, args->second_response);
+
+ return ssl_server(sock, args->args);
}
static int apt_creds(void *userdata, const char *realm, int attempt,
@@ -1299,23 +1292,27 @@ static int apt_creds(void *userdata, const char *realm, int attempt,
* 0.24.0. */
static int auth_proxy_tunnel(void)
{
- ne_session *sess = ne_session_create("https", "localhost", 443);
- int ret, code = 407;
+ struct ssl_server_args args = {SERVER_CERT, NULL};
+ struct tunnel_args tunnel;
+ ne_session *sess;
+
+ tunnel.first_response =
+ "HTTP/1.0 407 I WANT MORE BISCUITS\r\n"
+ "Server: auth_proxy_tunnel\r\n"
+ "Proxy-Authenticate: Basic realm=\"bigbluesea\"\r\n"
+ "Connection: close\r\n" "\r\n";
+ tunnel.second_response = "HTTP/1.1 200 OK\r\n"
+ "Server: auth_proxy_tunnel r2\r\n\r\n";
+ tunnel.iteration = 0;
+ tunnel.args = &args;
+
+ CALL(proxied_multi_session_server(2, &sess, "https", "localhost", 443,
+ serve_auth_tunnel, &tunnel));
- ne_session_proxy(sess, "localhost", 7777);
- ne_hook_post_send(sess, apt_post_send, &code);
ne_set_proxy_auth(sess, apt_creds, NULL);
ne_ssl_trust_cert(sess, def_ca_cert);
- CALL(spawn_server(7777, single_serve_string,
- "HTTP/1.0 407 I WANT MORE BISCUITS\r\n"
- "Proxy-Authenticate: Basic realm=\"bigbluesea\"\r\n"
- "Connection: close\r\n" "\r\n"));
-
- /* run two requests over the tunnel. */
- ret = any_2xx_request(sess, "/foobar");
- if (!ret) ret = any_2xx_request(sess, "/foobar2");
- CALL(ret);
+ CALL(any_2xx_request(sess, "/foobar"));
return destroy_and_wait(sess);
}
@@ -1324,20 +1321,27 @@ static int auth_proxy_tunnel(void)
* proxy in a CONNECT request. */
static int auth_tunnel_creds(void)
{
- ne_session *sess = ne_session_create("https", "localhost", 443);
- int code = 401;
- struct ssl_server_args args = {SERVER_CERT, 0};
+ struct ssl_server_args args = {SERVER_CERT, NULL};
+ struct tunnel_args tunnel;
+ ne_session *sess;
+
+ args.response = "HTTP/1.1 401 I want a Shrubbery\r\n"
+ "WWW-Authenticate: Basic realm=\"bigredocean\"\r\n"
+ "Server: auth_tunnel_creds\r\n" "Content-Length: 0\r\n" "\r\n"
+ ""
+ "HTTP/1.1 200 OK\r\n\r\n";
+
+ tunnel.second_response = "HTTP/1.1 200 OK\r\n\r\n";
+ tunnel.args = &args;
+ tunnel.iteration = 1;
+
+ CALL(proxied_multi_session_server(2, &sess, "https", "localhost", 443,
+ serve_auth_tunnel, &tunnel));
- ne_session_proxy(sess, "localhost", 7777);
- ne_hook_post_send(sess, apt_post_send, &code);
ne_set_server_auth(sess, apt_creds, NULL);
ne_ssl_trust_cert(sess, def_ca_cert);
- args.response = "HTTP/1.1 401 I want a Shrubbery\r\n"
- "WWW-Authenticate: Basic realm=\"bigredocean\"\r\n"
- "Server: Python\r\n" "Content-Length: 0\r\n" "\r\n";
-
- CALL(spawn_server(7777, serve_tunnel, &args));
+ CALL(any_2xx_request(sess, "/foobar"));
CALL(any_2xx_request(sess, "/foobar"));
return destroy_and_wait(sess);
@@ -1899,9 +1903,6 @@ ne_test tests[] = {
T(clicert_import),
T(simple),
-#if 0
- T(simple_sslv2),
-#endif
T(simple_eof),
T(empty_truncated_eof),
T(fail_not_ssl),
diff --git a/test/utils.c b/test/utils.c
index 272747a0..8acfecfb 100644
--- a/test/utils.c
+++ b/test/utils.c
@@ -223,13 +223,14 @@ int session_server(ne_session **sess, server_fn fn, void *userdata)
return multi_session_server(sess, "http", session_host, 1, fn, userdata);
}
-int proxied_session_server(ne_session **sess, const char *scheme,
- const char *host, unsigned int fakeport,
- server_fn fn, void *userdata)
+int proxied_multi_session_server(int count, ne_session **sess,
+ const char *scheme, const char *host,
+ unsigned int fakeport,
+ server_fn fn, void *userdata)
{
unsigned int port;
- CALL(new_spawn_server(1, fn, userdata, &port));
+ CALL(new_spawn_server(count, fn, userdata, &port));
*sess = ne_session_create(scheme, host, fakeport);
@@ -240,6 +241,15 @@ int proxied_session_server(ne_session **sess, const char *scheme,
return OK;
}
+
+int proxied_session_server(ne_session **sess, const char *scheme,
+ const char *host, unsigned int fakeport,
+ server_fn fn, void *userdata)
+{
+ return proxied_multi_session_server(1, sess, scheme, host, fakeport,
+ fn, userdata);
+}
+
static void fakesess_destroy(void *userdata)
{
ne_inet_addr *addr = userdata;
diff --git a/test/utils.h b/test/utils.h
index fe8441c0..b7226e31 100644
--- a/test/utils.h
+++ b/test/utils.h
@@ -141,6 +141,11 @@ int proxied_session_server(ne_session **sess, const char *scheme,
const char *host, unsigned int fakeport,
server_fn fn, void *userdata);
+int proxied_multi_session_server(int count, ne_session **sess,
+ const char *scheme, const char *host,
+ unsigned int fakeport,
+ server_fn fn, void *userdata);
+
/* As per proxied_session_server, but uses a "fake" (direct) TCP proxy
* rather than an HTTP proxy. */
int fakeproxied_session_server(ne_session **sess, const char *scheme,