summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO4
-rw-r--r--src/ne_auth.c113
-rw-r--r--test/auth.c102
3 files changed, 191 insertions, 28 deletions
diff --git a/TODO b/TODO
index 80970bc..7426009 100644
--- a/TODO
+++ b/TODO
@@ -10,10 +10,6 @@ For one-point-oh
Longer term
-----------
-2. Add proper domain support to authentication code. (requires full
- URI parsing support). Need to tell the auth layer the server
- details.
-
6. PUT with ranges... ne_put_range
9. DeltaV support (http://www.webdav.org/deltav/). See also the
diff --git a/src/ne_auth.c b/src/ne_auth.c
index 178d705..8bee30e 100644
--- a/src/ne_auth.c
+++ b/src/ne_auth.c
@@ -107,9 +107,7 @@ struct auth_handler {
struct auth_challenge {
const struct auth_protocol *protocol;
struct auth_handler *handler;
- const char *realm;
- const char *nonce;
- const char *opaque;
+ const char *realm, *nonce, *opaque, *domain;
unsigned int stale; /* if stale=true */
unsigned int got_qop; /* we were given a qop directive */
unsigned int qop_auth; /* "auth" token in qop attrib */
@@ -178,6 +176,8 @@ typedef struct {
char *nonce;
char *cnonce;
char *opaque;
+ char **domains; /* list of paths given as domain. */
+ size_t ndomains; /* size of domains array */
auth_qop qop;
auth_algorithm alg;
unsigned int nonce_count;
@@ -244,6 +244,16 @@ struct auth_protocol {
static void challenge_error(ne_buffer **errmsg, const char *fmt, ...)
ne_attribute((format(printf, 2, 3)));
+/* Free the domains array, precondition sess->ndomains > 0. */
+static void free_domains(auth_session *sess)
+{
+ do {
+ ne_free(sess->domains[sess->ndomains - 1]);
+ } while (--sess->ndomains);
+ ne_free(sess->domains);
+ sess->domains = NULL;
+}
+
static void clean_session(auth_session *sess)
{
if (sess->basic) ne_free(sess->basic);
@@ -257,6 +267,7 @@ static void clean_session(auth_session *sess)
ne_md5_destroy_ctx(sess->stored_rdig);
sess->stored_rdig = NULL;
}
+ if (sess->ndomains) free_domains(sess);
#ifdef HAVE_GSSAPI
{
unsigned int major;
@@ -612,6 +623,61 @@ static int sspi_challenge(auth_session *sess, int attempt,
}
#endif
+/* Parse the "domain" challenge parameter and set the domains array up
+ * in the session appropriately. */
+static int parse_domain(auth_session *sess, const char *domain)
+{
+ char *cp = ne_strdup(domain), *p = cp;
+ ne_uri base = {0};
+ int invalid = 0;
+
+ ne_fill_server_uri(sess->sess, &base);
+
+ do {
+ char *token = ne_token(&p, ',');
+ ne_uri rel, absolute;
+
+ if (ne_uri_parse(token, &rel) == 0) {
+ /* Resolve relative to the Request-URI. */
+ ne_uri_resolve(&base, &rel, &absolute);
+
+ base.path = absolute.path;
+
+ /* Ignore URIs not on this server. */
+ if (ne_uri_cmp(&absolute, &base) == 0) {
+ sess->domains = ne_realloc(sess->domains,
+ ++sess->ndomains *
+ sizeof(*sess->domains));
+ sess->domains[sess->ndomains - 1] = ne_strdup(absolute.path);
+ NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Using domain %s from %s\n",
+ absolute.path, token);
+ absolute.path = NULL;
+ }
+ else {
+ NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Ignoring domain %s\n",
+ token);
+ }
+
+ ne_uri_free(&absolute);
+ }
+ else {
+ invalid = 1;
+ }
+
+ ne_uri_free(&rel);
+
+ } while (p && !invalid);
+
+ if (invalid && sess->ndomains) {
+ free_domains(sess);
+ }
+
+ ne_free(cp);
+ ne_uri_free(&base);
+
+ return invalid;
+}
+
/* Examine a digest challenge: return 0 if it is a valid Digest challenge,
* else non-zero. */
static int digest_challenge(auth_session *sess, int attempt,
@@ -648,6 +714,14 @@ static int digest_challenge(auth_session *sess, int attempt,
/* Non-stale challenge: clear session and request credentials. */
clean_session(sess);
+ /* The domain paramater must be parsed after the session is
+ * cleaned; ignore domain for proxy auth. */
+ if (parms->domain && sess->spec == &ah_server_class
+ && parse_domain(sess, parms->domain)) {
+ challenge_error(errmsg, _("could not parse domain in Digest challenge"));
+ return -1;
+ }
+
sess->realm = ne_strdup(parms->realm);
sess->alg = parms->alg;
sess->cnonce = get_cnonce();
@@ -721,6 +795,30 @@ static int digest_challenge(auth_session *sess, int attempt,
return 0;
}
+/* Returns non-zero if given URI is inside the authentication domain
+ * defined for the session. */
+static int inside_domain(auth_session *sess, const char *uri)
+{
+ size_t n;
+ int inside = 0;
+
+ /* For non-abs_path URIs, ignore. */
+ if (uri[0] != '/') {
+ return 1;
+ }
+
+ for (n = 0; n < sess->ndomains && !inside; n++) {
+ const char *d = sess->domains[n];
+
+ inside = (d[strlen(d)-1] == '/' && strncmp(uri, d, strlen(d)) == 0)
+ || strcmp(d, uri) == 0;
+ }
+
+ NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Inside auth domain: %d.\n", inside);
+
+ return inside;
+}
+
/* Return Digest authentication credentials header value for the given
* session. */
static char *request_digest(auth_session *sess, struct auth_request *req)
@@ -731,6 +829,12 @@ static char *request_digest(auth_session *sess, struct auth_request *req)
const char *qop_value = "auth"; /* qop-value */
ne_buffer *ret;
+ /* 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)) {
+ return NULL;
+ }
+
/* Increase the nonce-count */
if (sess->qop != auth_qop_none) {
sess->nonce_count++;
@@ -1163,6 +1267,9 @@ static int auth_challenge(auth_session *sess, int attempt,
chall->got_qop = chall->qop_auth;
}
+ else if (ne_strcasecmp(key, "domain") == 0) {
+ chall->domain = val;
+ }
}
sess->protocol = NULL;
diff --git a/test/auth.c b/test/auth.c
index 7473346..19a632d 100644
--- a/test/auth.c
+++ b/test/auth.c
@@ -368,7 +368,7 @@ static void dup_header(char *header)
}
struct digest_parms {
- const char *realm, *nonce, *opaque;
+ const char *realm, *nonce, *opaque, *domain;
int rfc2617;
int send_ainfo;
int md5_sess;
@@ -389,7 +389,8 @@ struct digest_parms {
fail_ai_bad_cnonce,
fail_ai_omit_cnonce,
fail_ai_omit_digest,
- fail_ai_omit_nc
+ fail_ai_omit_nc,
+ fail_outside_domain
} failure;
};
@@ -647,6 +648,10 @@ static char *make_digest_header(struct digest_state *state,
ne_buffer_concat(buf, "opaque=\"", parms->opaque, "\", ", NULL);
}
+ if (parms->domain) {
+ ne_buffer_concat(buf, "domain=\"", parms->domain, "\", ", NULL);
+ }
+
if (parms->failure == fail_req0_stale
|| parms->stale == parms->num_requests) {
ne_buffer_concat(buf, "stale='true', ", NULL);
@@ -664,7 +669,12 @@ static int serve_digest(ne_socket *sock, void *userdata)
struct digest_state state;
char resp[NE_BUFSIZ];
- state.uri = parms->proxy ? "http://www.example.com/fish" : "/fish";
+ if (parms->proxy)
+ state.uri = "http://www.example.com/fish";
+ else if (parms->domain)
+ state.uri = "/fish/0";
+ else
+ state.uri = "/fish";
state.method = "GET";
state.realm = parms->realm;
state.nonce = parms->nonce;
@@ -708,11 +718,31 @@ static int serve_digest(ne_socket *sock, void *userdata)
}
do {
+ digest_hdr = NULL;
CALL(discard_request(sock));
- ONN("no Authorization header sent", digest_hdr == NULL);
-
- CALL(verify_digest_header(&state, parms, digest_hdr));
+ if (digest_hdr && parms->domain && (parms->num_requests & 1) == 0) {
+ SEND_STRING(sock, "HTTP/1.1 400 Used Auth Outside Domain\r\n\r\n");
+ return OK;
+ }
+ else if (parms->domain && (parms->num_requests & 1) == 0) {
+ /* Do nothing. */
+ }
+ else if (parms->domain && parms->num_requests == 5) {
+ /* next URI */
+ state.uri = "/fish/2";
+ }
+ else if (parms->domain && parms->num_requests == 3) {
+ state.uri = "/other";
+ }
+ else if (parms->domain && parms->num_requests == 1) {
+ state.uri = "*";
+ }
+ else {
+ ONN("no Authorization header sent", digest_hdr == NULL);
+
+ CALL(verify_digest_header(&state, parms, digest_hdr));
+ }
if (parms->num_requests == parms->stale) {
state.nonce = ne_concat("stale-", state.nonce, NULL);
@@ -781,27 +811,27 @@ static int digest(void)
{
struct digest_parms parms[] = {
/* RFC 2617-style */
- { "WallyWorld", "this-is-a-nonce", NULL, 1, 0, 0, 0, 0, 1, 0, fail_not },
- { "WallyWorld", "this-is-also-a-nonce", "opaque-string", 1, 0, 0, 0, 0, 1, 0, fail_not },
+ { "WallyWorld", "this-is-a-nonce", NULL, NULL, 1, 0, 0, 0, 0, 1, 0, fail_not },
+ { "WallyWorld", "this-is-also-a-nonce", "opaque-string", NULL, 1, 0, 0, 0, 0, 1, 0, fail_not },
/* ... with A-I */
- { "WallyWorld", "nonce-nonce-nonce", "opaque-string", 1, 1, 0, 0, 0, 1, 0, fail_not },
+ { "WallyWorld", "nonce-nonce-nonce", "opaque-string", NULL, 1, 1, 0, 0, 0, 1, 0, fail_not },
/* ... with md5-sess. */
- { "WallyWorld", "nonce-nonce-nonce", "opaque-string", 1, 1, 1, 0, 0, 1, 0, fail_not },
+ { "WallyWorld", "nonce-nonce-nonce", "opaque-string", NULL, 1, 1, 1, 0, 0, 1, 0, fail_not },
/* many requests, with changing nonces; tests for next-nonce handling bug. */
- { "WallyWorld", "this-is-a-nonce", "opaque-thingy", 1, 1, 0, 0, 1, 20, 0, fail_not },
+ { "WallyWorld", "this-is-a-nonce", "opaque-thingy", NULL, 1, 1, 0, 0, 1, 20, 0, fail_not },
/* staleness. */
- { "WallyWorld", "this-is-a-nonce", "opaque-thingy", 1, 1, 0, 0, 0, 3, 2, fail_not },
+ { "WallyWorld", "this-is-a-nonce", "opaque-thingy", NULL, 1, 1, 0, 0, 0, 3, 2, fail_not },
/* RFC 2069-style */
- { "WallyWorld", "lah-di-da-di-dah", NULL, 0, 0, 0, 0, 0, 1, 0, fail_not },
- { "WallyWorld", "fee-fi-fo-fum", "opaque-string", 0, 0, 0, 0, 0, 1, 0, fail_not },
- { "WallyWorld", "fee-fi-fo-fum", "opaque-string", 0, 1, 0, 0, 0, 1, 0, fail_not },
+ { "WallyWorld", "lah-di-da-di-dah", NULL, NULL, 0, 0, 0, 0, 0, 1, 0, fail_not },
+ { "WallyWorld", "fee-fi-fo-fum", "opaque-string", NULL, 0, 0, 0, 0, 0, 1, 0, fail_not },
+ { "WallyWorld", "fee-fi-fo-fum", "opaque-string", NULL, 0, 1, 0, 0, 0, 1, 0, fail_not },
/* Proxy auth */
- { "WallyWorld", "this-is-also-a-nonce", "opaque-string", 1, 1, 0, 0, 0, 1, 0, fail_not },
+ { "WallyWorld", "this-is-also-a-nonce", "opaque-string", NULL, 1, 1, 0, 0, 0, 1, 0, fail_not },
/* Proxy + A-I */
- { "WallyWorld", "this-is-also-a-nonce", "opaque-string", 1, 1, 0, 1, 0, 1, 0, fail_not },
+ { "WallyWorld", "this-is-also-a-nonce", "opaque-string", NULL, 1, 1, 0, 1, 0, 1, 0, fail_not },
{ NULL }
};
@@ -835,16 +865,14 @@ static int digest_failures(void)
};
size_t n;
+ memset(&parms, 0, sizeof parms);
+
parms.realm = "WallyWorld";
parms.nonce = "random-invented-string";
parms.opaque = NULL;
parms.rfc2617 = 1;
parms.send_ainfo = 1;
- parms.md5_sess = 0;
- parms.proxy = 0;
- parms.send_nextnonce = 0;
parms.num_requests = 1;
- parms.stale = 0;
for (n = 0; fails[n].message; n++) {
ne_session *sess = ne_session_create("http", "localhost", 7777);
@@ -906,6 +934,8 @@ static int fail_challenge(void)
"unknown algorithm in Digest challenge" },
{ "Digest algorithm=MD5-sess, realm=\"foo\"",
"incompatible algorithm in Digest challenge" },
+ { "Digest algorithm=MD5, qop=auth, nonce=\"foo\", realm=\"foo\", "
+ "domain=\"http://[::1/\"", "could not parse domain" },
/* Multiple challenge failure cases: */
{ "Basic, Digest",
@@ -1016,6 +1046,35 @@ static int multi_handler(void)
return await_server();
}
+static int domains(void)
+{
+ ne_session *sess;
+ struct digest_parms parms;
+
+ memset(&parms, 0, sizeof parms);
+ parms.realm = "WallyWorld";
+ parms.rfc2617 = 1;
+ parms.nonce = "agoog";
+ parms.domain = "http://localhost:7777/fish/,https://example.com/agaor,/other";
+ parms.num_requests = 7;
+
+ CALL(make_session(&sess, serve_digest, &parms));
+
+ ne_set_server_auth(sess, auth_cb, NULL);
+
+ CALL(any_2xx_request(sess, "/fish/0"));
+ CALL(any_2xx_request(sess, "/outside"));
+ CALL(any_2xx_request(sess, "/other"));
+ CALL(any_2xx_request(sess, "/fish"));
+ CALL(any_2xx_request(sess, "/fish/2"));
+ CALL(any_2xx_request(sess, "/goop"));
+ CALL(any_2xx_request(sess, "*"));
+
+ ne_session_destroy(sess);
+
+ return await_server();
+}
+
/* test logout */
/* proxy auth, proxy AND origin */
@@ -1031,5 +1090,6 @@ ne_test tests[] = {
T(digest_failures),
T(fail_challenge),
T(multi_handler),
+ T(domains),
T(NULL)
};