Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test and docs updates #154

Merged
merged 6 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions doc/using.xml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
<quote>chunked</quote>.</para></sect2>

<sect2>
<title>RFC 2617, HTTP Authentication: Basic and Digest Access Authentication</title>
<title>RFC 7616, HTTP Digest Access Authentication</title>

<para>&neon; is not strictly compliant with the quoting rules
given in the grammar for the <literal>Authorization</literal>
Expand All @@ -114,7 +114,19 @@
(Microsoft&reg; IIS 5) rejects the request if these parameters
are not quoted. &neon; sends these parameters with
quotes&mdash;this is not known to cause any problems with
other server implementations.</para></sect2>
other server implementations.</para>

<para>RFC 7616 predates RFC 9112 and uses conflicting language
around URIs. &neon; uses the RFC 9112
<literal>request-target</literal> in both the
<literal>A2</literal> grammar and the <literal>uri=</literal>
parameter of the <literal>Authorization</literal>
header. &neon; will accept (and resolve) any URI-reference in
the <literal>domain=</literal> parameter for
<literal>WWW-Authenticate</literal> response header
field.</para>

</sect2>

<sect2>
<title>Namespaces in XML</title>
Expand Down
57 changes: 30 additions & 27 deletions src/ne_auth.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
HTTP Authentication routines
Copyright (C) 1999-2021, Joe Orton <joe@manyfish.co.uk>
Copyright (C) 1999-2024, Joe Orton <joe@manyfish.co.uk>

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) */
Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -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",
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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 = "/";
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -1064,17 +1064,17 @@ 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;
ne_uri 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;
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
19 changes: 17 additions & 2 deletions test/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
21 changes: 13 additions & 8 deletions test/common/child.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
Framework for testing with a server process
Copyright (C) 2001-2010, Joe Orton <joe@manyfish.co.uk>
Copyright (C) 2001-2024, Joe Orton <joe@manyfish.co.uk>

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
Expand Down Expand Up @@ -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);
Expand All @@ -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)) {
Expand All @@ -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);
}

Expand Down
Loading
Loading